Modify hashtest2 to compile after r374213. Someone, somewhere, may care.
[asterisk/asterisk.git] / utils / muted.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 1999 - 2005, Digium, Inc.
5  *
6  * Mark Spencer <markster@digium.com>
7  *
8  * Updated for Mac OSX CoreAudio 
9  * by Josh Roberson <josh@asteriasgi.com>
10  *
11  * See http://www.asterisk.org for more information about
12  * the Asterisk project. Please do not directly contact
13  * any of the maintainers of this project for assistance;
14  * the project provides a web site, mailing lists and IRC
15  * channels for your use.
16  *
17  * This program is free software, distributed under the terms of
18  * the GNU General Public License Version 2. See the LICENSE file
19  * at the top of the source tree.
20  */
21
22 /*! \file
23  *
24  * \brief Mute Daemon
25  *
26  * \author Mark Spencer <markster@digium.com>
27  *
28  * Updated for Mac OSX CoreAudio 
29  * \arg Josh Roberson <josh@asteriasgi.com>
30  *
31  * \note Specially written for Malcolm Davenport, but I think I'll use it too
32  * Connects to the Asterisk Manager Interface, AMI, and listens for events
33  * on certain devices. If a phone call is connected to one of the devices (phones)
34  * the local sound is muted to a lower volume during the call.
35  *
36  */
37
38 /*** MODULEINFO
39         <support_level>extended</support_level>
40  ***/
41
42 #include "asterisk/autoconfig.h"
43
44 #ifdef __Darwin__
45 #include <CoreAudio/AudioHardware.h> 
46 #include <sys/types.h>
47 #include <pwd.h>
48 #include <sys/stat.h>
49 #elif defined(__linux__) || defined(__FreeBSD__) || defined(__GLIBC__)
50 #include <sys/soundcard.h>
51 #endif
52 #include <stdio.h>
53 #include <errno.h>
54 #include <stdlib.h>
55 #include <unistd.h>
56 #include <fcntl.h>
57 #include <string.h>
58 #include <netdb.h>
59 #include <sys/socket.h>
60 #include <sys/ioctl.h>
61 #include <netinet/in.h>
62 #include <arpa/inet.h>
63
64 #define ast_strlen_zero(a)      (!(*(a)))
65
66 static char *config = "/etc/asterisk/muted.conf";
67
68 static char host[256] = "";
69 static char user[256] = "";
70 static char pass[256] = "";
71 static int smoothfade = 0;
72 static int mutelevel = 20;
73 static int muted = 0;
74 static int needfork = 1;
75 static int debug = 0;
76 static int stepsize = 3;
77 #ifndef __Darwin__
78 static int mixchan = SOUND_MIXER_VOLUME;
79 #endif
80
81 struct subchannel {
82         char *name;
83         struct subchannel *next;
84 };
85
86 static struct channel {
87         char *tech;
88         char *location;
89         struct channel *next;
90         struct subchannel *subs;
91 } *channels;
92
93 static void add_channel(char *tech, char *location)
94 {
95         struct channel *chan;
96         chan = malloc(sizeof(struct channel));
97         if (chan) {
98                 memset(chan, 0, sizeof(struct channel));
99                 if (!(chan->tech = strdup(tech))) {
100                         free(chan);
101                         return;
102                 }
103                 if (!(chan->location = strdup(location))) {
104                         free(chan->tech);
105                         free(chan);
106                         return;
107                 }
108                 chan->next = channels;
109                 channels = chan;
110         }
111         
112 }
113
114 static int load_config(void)
115 {
116         FILE *f;
117         char buf[1024];
118         char *val;
119         char *val2;
120         int lineno=0;
121         int x;
122         f = fopen(config, "r");
123         if (!f) {
124                 fprintf(stderr, "Unable to open config file '%s': %s\n", config, strerror(errno));
125                 return -1;
126         }
127         while(!feof(f)) {
128                 if (!fgets(buf, sizeof(buf), f)) {
129                         continue;
130                 }
131                 if (!feof(f)) {
132                         lineno++;
133                         val = strchr(buf, '#');
134                         if (val) *val = '\0';
135                         while(strlen(buf) && (buf[strlen(buf) - 1] < 33))
136                                 buf[strlen(buf) - 1] = '\0';
137                         if (!strlen(buf))
138                                 continue;
139                         val = buf;
140                         while(*val) {
141                                 if (*val < 33)
142                                         break;
143                                 val++;
144                         }
145                         if (*val) {
146                                 *val = '\0';
147                                 val++;
148                                 while(*val && (*val < 33)) val++;
149                         }
150                         if (!strcasecmp(buf, "host")) {
151                                 if (val && strlen(val))
152                                         strncpy(host, val, sizeof(host) - 1);
153                                 else
154                                         fprintf(stderr, "host needs an argument (the host) at line %d\n", lineno);
155                         } else if (!strcasecmp(buf, "user")) {
156                                 if (val && strlen(val))
157                                         strncpy(user, val, sizeof(user) - 1);
158                                 else
159                                         fprintf(stderr, "user needs an argument (the user) at line %d\n", lineno);
160                         } else if (!strcasecmp(buf, "pass")) {
161                                 if (val && strlen(val))
162                                         strncpy(pass, val, sizeof(pass) - 1);
163                                 else
164                                         fprintf(stderr, "pass needs an argument (the password) at line %d\n", lineno);
165                         } else if (!strcasecmp(buf, "smoothfade")) {
166                                 smoothfade = 1;
167                         } else if (!strcasecmp(buf, "mutelevel")) {
168                                 if (val && (sscanf(val, "%3d", &x) == 1) && (x > -1) && (x < 101)) {
169                                         mutelevel = x;
170                                 } else 
171                                         fprintf(stderr, "mutelevel must be a number from 0 (most muted) to 100 (no mute) at line %d\n", lineno);
172                         } else if (!strcasecmp(buf, "channel")) {
173                                 if (val && strlen(val)) {
174                                         val2 = strchr(val, '/');
175                                         if (val2) {
176                                                 *val2 = '\0';
177                                                 val2++;
178                                                 add_channel(val, val2);
179                                         } else
180                                                 fprintf(stderr, "channel needs to be of the format Tech/Location at line %d\n", lineno);
181                                 } else
182                                         fprintf(stderr, "channel needs an argument (the channel) at line %d\n", lineno);
183                         } else {
184                                 fprintf(stderr, "ignoring unknown keyword '%s'\n", buf);
185                         }
186                 }
187         }
188         fclose(f);
189         if (!strlen(host))
190                 fprintf(stderr, "no 'host' specification in config file\n");
191         else if (!strlen(user))
192                 fprintf(stderr, "no 'user' specification in config file\n");
193         else if (!channels) 
194                 fprintf(stderr, "no 'channel' specifications in config file\n");
195         else
196                 return 0;
197         return -1;
198 }
199
200 static FILE *astf;
201 #ifndef __Darwin__
202 static int mixfd;
203
204 static int open_mixer(void)
205 {
206         mixfd = open("/dev/mixer", O_RDWR);
207         if (mixfd < 0) {
208                 fprintf(stderr, "Unable to open /dev/mixer: %s\n", strerror(errno));
209                 return -1;
210         }
211         return 0;
212 }
213 #endif /* !__Darwin */
214
215 /*! Connect to the asterisk manager interface */
216 static int connect_asterisk(void)
217 {
218         int sock;
219         struct hostent *hp;
220         char *ports;
221         int port = 5038;
222         struct sockaddr_in sin;
223
224         ports = strchr(host, ':');
225         if (ports) {
226                 *ports = '\0';
227                 ports++;
228                 if ((sscanf(ports, "%5d", &port) != 1) || (port < 1) || (port > 65535)) {
229                         fprintf(stderr, "'%s' is not a valid port number in the hostname\n", ports);
230                         return -1;
231                 }
232         }
233         hp = gethostbyname(host);
234         if (!hp) {
235                 fprintf(stderr, "Can't find host '%s'\n", host);
236                 return -1;
237         }
238         sock = socket(AF_INET, SOCK_STREAM, 0);
239         if (sock < 0) {
240                 fprintf(stderr, "Failed to create socket: %s\n", strerror(errno));
241                 return -1;
242         }
243         sin.sin_family = AF_INET;
244         sin.sin_port = htons(port);
245         memcpy(&sin.sin_addr, hp->h_addr, sizeof(sin.sin_addr));
246         if (connect(sock, (struct sockaddr *)&sin, sizeof(sin))) {
247                 fprintf(stderr, "Failed to connect to '%s' port '%d': %s\n", host, port, strerror(errno));
248                 close(sock);
249                 return -1;
250         }
251         astf = fdopen(sock, "r+");
252         if (!astf) {
253                 fprintf(stderr, "fdopen failed: %s\n", strerror(errno));
254                 close(sock);
255                 return -1;
256         }
257         return 0;
258 }
259
260 static char *get_line(void)
261 {
262         static char buf[1024];
263         if (fgets(buf, sizeof(buf), astf)) {
264                 while(strlen(buf) && (buf[strlen(buf) - 1] < 33))
265                         buf[strlen(buf) - 1] = '\0';
266                 return buf;
267         } else
268                 return NULL;
269 }
270
271 /*! Login to the asterisk manager interface */
272 static int login_asterisk(void)
273 {
274         char *welcome;
275         char *resp;
276         if (!(welcome = get_line())) {
277                 fprintf(stderr, "disconnected (1)\n");
278                 return -1;
279         }
280         fprintf(astf, 
281                 "Action: Login\r\n"
282                 "Username: %s\r\n"
283                 "Secret: %s\r\n\r\n", user, pass);
284         if (!(welcome = get_line())) {
285                 fprintf(stderr, "disconnected (2)\n");
286                 return -1;
287         }
288         if (strcasecmp(welcome, "Response: Success")) {
289                 fprintf(stderr, "login failed ('%s')\n", welcome);
290                 return -1;
291         }
292         /* Eat the rest of the event */
293         while((resp = get_line()) && strlen(resp));
294         if (!resp) {
295                 fprintf(stderr, "disconnected (3)\n");
296                 return -1;
297         }
298         fprintf(astf, 
299                 "Action: Status\r\n\r\n");
300         if (!(welcome = get_line())) {
301                 fprintf(stderr, "disconnected (4)\n");
302                 return -1;
303         }
304         if (strcasecmp(welcome, "Response: Success")) {
305                 fprintf(stderr, "status failed ('%s')\n", welcome);
306                 return -1;
307         }
308         /* Eat the rest of the event */
309         while((resp = get_line()) && strlen(resp));
310         if (!resp) {
311                 fprintf(stderr, "disconnected (5)\n");
312                 return -1;
313         }
314         return 0;
315 }
316
317 static struct channel *find_channel(char *channel)
318 {
319         char tmp[256] = "";
320         char *s, *t;
321         struct channel *chan;
322         strncpy(tmp, channel, sizeof(tmp) - 1);
323         s = strchr(tmp, '/');
324         if (s) {
325                 *s = '\0';
326                 s++;
327                 t = strrchr(s, '-');
328                 if (t) {
329                         *t = '\0';
330                 }
331                 if (debug)
332                         printf("Searching for '%s' tech, '%s' location\n", tmp, s);
333                 chan = channels;
334                 while(chan) {
335                         if (!strcasecmp(chan->tech, tmp) && !strcasecmp(chan->location, s)) {
336                                 if (debug)
337                                         printf("Found '%s'/'%s'\n", chan->tech, chan->location);
338                                 break;
339                         }
340                         chan = chan->next;
341                 }
342         } else
343                 chan = NULL;
344         return chan;
345 }
346
347 #ifndef __Darwin__
348 static int getvol(void)
349 {
350         int vol;
351
352         if (ioctl(mixfd, MIXER_READ(mixchan), &vol)) {
353 #else
354 static float getvol(void)
355 {
356         float volumeL, volumeR, vol;
357         OSStatus err;
358         AudioDeviceID device;
359         UInt32 size;
360         UInt32 channels[2];
361         AudioObjectPropertyAddress OutputAddr = { kAudioHardwarePropertyDefaultOutputDevice, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
362         AudioObjectPropertyAddress ChannelAddr = { kAudioDevicePropertyPreferredChannelsForStereo, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementWildcard };
363         AudioObjectPropertyAddress VolumeAddr = { kAudioDevicePropertyVolumeScalar, kAudioDevicePropertyScopeOutput, };
364
365         size = sizeof(device);
366         err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &OutputAddr, 0, NULL, &size, &device);
367         size = sizeof(channels);
368         if (!err) {
369                 err = AudioObjectGetPropertyData(device, &ChannelAddr, 0, NULL, &size, &channels);
370         }
371         size = sizeof(vol);
372         if (!err) {
373                 VolumeAddr.mElement = channels[0];
374                 err = AudioObjectGetPropertyData(device, &VolumeAddr, 0, NULL, &size, &volumeL);
375         }
376         if (!err) {
377                 VolumeAddr.mElement = channels[1];
378                 err = AudioObjectGetPropertyData(device, &VolumeAddr, 0, NULL, &size, &volumeR);
379         }
380         if (!err)
381                 vol = (volumeL < volumeR) ? volumeR : volumeL;
382         else {
383 #endif
384                 fprintf(stderr, "Unable to read mixer volume: %s\n", strerror(errno));
385                 return -1;
386         }
387         return vol;
388 }
389
390 #ifndef __Darwin__
391 static int setvol(int vol)
392 #else
393 static int setvol(float vol)
394 #endif
395 {
396 #ifndef __Darwin__
397         if (ioctl(mixfd, MIXER_WRITE(mixchan), &vol)) {
398 #else   
399         float volumeL = vol;
400         float volumeR = vol;
401         OSStatus err;
402         AudioDeviceID device;
403         UInt32 size;
404         UInt32 channels[2];
405         AudioObjectPropertyAddress OutputAddr = { kAudioHardwarePropertyDefaultOutputDevice, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
406         AudioObjectPropertyAddress ChannelAddr = { kAudioDevicePropertyPreferredChannelsForStereo, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementWildcard };
407         AudioObjectPropertyAddress VolumeAddr = { kAudioDevicePropertyVolumeScalar, kAudioDevicePropertyScopeOutput, };
408
409         size = sizeof(device);
410         err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &OutputAddr, 0, NULL, &size, &device);
411         size = sizeof(channels);
412         err = AudioObjectGetPropertyData(device, &ChannelAddr, 0, NULL, &size, &channels);
413         size = sizeof(vol);
414         if (!err) {
415                 VolumeAddr.mElement = channels[0];
416                 err = AudioObjectSetPropertyData(device, &VolumeAddr, 0, NULL, size, &volumeL);
417         }
418         if (!err) {
419                 VolumeAddr.mElement = channels[1];
420                 err = AudioObjectSetPropertyData(device, &VolumeAddr, 0, NULL, size, &volumeR);
421         }
422         if (err) {
423 #endif
424
425                 fprintf(stderr, "Unable to write mixer volume: %s\n", strerror(errno));
426                 return -1;
427
428         }
429         return 0;
430 }
431
432 #ifndef __Darwin__
433 static int oldvol = 0;
434 static int mutevol = 0;
435 #else
436 static float oldvol = 0;
437 static float mutevol = 0;
438 #endif
439
440 #ifndef __Darwin__
441 static int mutedlevel(int orig, int level)
442 {
443         int l = orig >> 8;
444         int r = orig & 0xff;
445         l = (float)(level) * (float)(l) / 100.0;
446         r = (float)(level) * (float)(r) / 100.0;
447
448         return (l << 8) | r;
449 #else
450 static float mutedlevel(float orig, float level)
451 {
452         float master = orig;
453         master = level * master / 100.0;
454         return master;
455 #endif
456         
457 }
458
459 static void mute(void)
460 {
461 #ifndef __Darwin__
462         int vol;
463         int start;
464         int x;
465 #else
466         float vol;
467         float start = 1.0;
468         float x;
469 #endif
470         vol = getvol();
471         oldvol = vol;
472         if (smoothfade)
473 #ifdef __Darwin__ 
474                 start = mutelevel;
475 #else
476                 start = 100;
477         else
478                 start = mutelevel;
479 #endif
480         for (x=start;x>=mutelevel;x-=stepsize) {
481                 mutevol = mutedlevel(vol, x);
482                 setvol(mutevol);
483                 /* Wait 0.01 sec */
484                 usleep(10000);
485         }
486         mutevol = mutedlevel(vol, mutelevel);
487         setvol(mutevol);
488         if (debug)
489 #ifdef __Darwin__
490                 printf("Mute from '%f' to '%f'!\n", oldvol, mutevol);
491 #else
492                 printf("Mute from '%04x' to '%04x'!\n", oldvol, mutevol);
493 #endif
494         muted = 1;
495 }
496
497 static void unmute(void)
498 {
499 #ifdef __Darwin__
500         float vol;
501         float start;
502         float x;
503 #else
504         int vol;
505         int start;
506         int x;
507 #endif
508         vol = getvol();
509         if (debug)
510 #ifdef __Darwin__
511                 printf("Unmute from '%f' (should be '%f') to '%f'!\n", vol, mutevol, oldvol);
512         mutevol = vol;
513         if (vol == mutevol) {
514 #else
515                 printf("Unmute from '%04x' (should be '%04x') to '%04x'!\n", vol, mutevol, oldvol);
516         if ((int)vol == mutevol) {
517 #endif
518                 if (smoothfade)
519                         start = mutelevel;
520                 else
521 #ifdef __Darwin__
522                         start = 1.0;
523 #else
524                         start = 100;
525 #endif
526                 for (x=start;x<100;x+=stepsize) {
527                         mutevol = mutedlevel(oldvol, x);
528                         setvol(mutevol);
529                         /* Wait 0.01 sec */
530                         usleep(10000);
531                 }
532                 setvol(oldvol);
533         } else
534                 printf("Whoops, it's already been changed!\n");
535         muted = 0;
536 }
537
538 static void check_mute(void)
539 {
540         int offhook = 0;
541         struct channel *chan;
542         chan = channels;
543         while(chan) {
544                 if (chan->subs) {
545                         offhook++;
546                         break;
547                 }
548                 chan = chan->next;
549         }
550         if (offhook && !muted)
551                 mute();
552         else if (!offhook && muted)
553                 unmute();
554 }
555
556 static void delete_sub(struct channel *chan, char *name)
557 {
558         struct subchannel *sub, *prev;
559         prev = NULL;
560         sub = chan->subs;
561         while(sub) {
562                 if (!strcasecmp(sub->name, name)) {
563                         if (prev)
564                                 prev->next = sub->next;
565                         else
566                                 chan->subs = sub->next;
567                         free(sub->name);
568                         free(sub);
569                         return;
570                 }
571                 prev = sub;
572                 sub = sub->next;
573         }
574 }
575
576 static void append_sub(struct channel *chan, char *name)
577 {
578         struct subchannel *sub;
579         sub = chan->subs;
580         while(sub) {
581                 if (!strcasecmp(sub->name, name)) 
582                         return;
583                 sub = sub->next;
584         }
585         sub = malloc(sizeof(struct subchannel));
586         if (sub) {
587                 memset(sub, 0, sizeof(struct subchannel));
588                 if (!(sub->name = strdup(name))) {
589                         free(sub);
590                         return;
591                 }
592                 sub->next = chan->subs;
593                 chan->subs = sub;
594         }
595 }
596
597 static void hangup_chan(char *channel)
598 {
599         struct channel *chan;
600         if (debug)
601                 printf("Hangup '%s'\n", channel);
602         chan = find_channel(channel);
603         if (chan)
604                 delete_sub(chan, channel);
605         check_mute();
606 }
607
608 static void offhook_chan(char *channel)
609 {
610         struct channel *chan;
611         if (debug)
612                 printf("Offhook '%s'\n", channel);
613         chan = find_channel(channel);
614         if (chan)
615                 append_sub(chan, channel);
616         check_mute();
617 }
618
619 static int wait_event(void)
620 {
621         char *resp;
622         char event[120]="";
623         char channel[120]="";
624         char oldname[120]="";
625         char newname[120]="";
626
627         resp = get_line();
628         if (!resp) {
629                 fprintf(stderr, "disconnected (6)\n");
630                 return -1;
631         }
632         if (!strncasecmp(resp, "Event: ", strlen("Event: "))) {
633                 strncpy(event, resp + strlen("Event: "), sizeof(event) - 1);
634                 /* Consume the rest of the non-event */
635                 while((resp = get_line()) && strlen(resp)) {
636                         if (!strncasecmp(resp, "Channel: ", strlen("Channel: ")))
637                                 strncpy(channel, resp + strlen("Channel: "), sizeof(channel) - 1);
638                         if (!strncasecmp(resp, "Newname: ", strlen("Newname: ")))
639                                 strncpy(newname, resp + strlen("Newname: "), sizeof(newname) - 1);
640                         if (!strncasecmp(resp, "Oldname: ", strlen("Oldname: ")))
641                                 strncpy(oldname, resp + strlen("Oldname: "), sizeof(oldname) - 1);
642                 }
643                 if (strlen(channel)) {
644                         if (!strcasecmp(event, "Hangup")) 
645                                 hangup_chan(channel);
646                         else
647                                 offhook_chan(channel);
648                 }
649                 if (strlen(newname) && strlen(oldname)) {
650                         if (!strcasecmp(event, "Rename")) {
651                                 hangup_chan(oldname);
652                                 offhook_chan(newname);
653                         }
654                 }
655         } else {
656                 /* Consume the rest of the non-event */
657                 while((resp = get_line()) && strlen(resp));
658         }
659         if (!resp) {
660                 fprintf(stderr, "disconnected (7)\n");
661                 return -1;
662         }
663         return 0;
664 }
665
666 static void usage(void)
667 {
668         printf("Usage: muted [-f] [-d]\n"
669                "        -f : Do not fork\n"
670                "        -d : Debug (implies -f)\n");
671 }
672
673 int main(int argc, char *argv[])
674 {
675         int x;
676         while((x = getopt(argc, argv, "fhd")) > 0) {
677                 switch(x) {
678                 case 'd':
679                         debug = 1;
680                         needfork = 0;
681                         break;
682                 case 'f':
683                         needfork = 0;
684                         break;
685                 case 'h':
686                         /* Fall through */
687                 default:
688                         usage();
689                         exit(1);
690                 }
691         }
692         if (load_config())
693                 exit(1);
694 #ifndef __Darwin__
695         if (open_mixer())
696                 exit(1);
697 #endif
698         if (connect_asterisk()) {
699 #ifndef __Darwin__
700                 close(mixfd);
701 #endif
702                 exit(1);
703         }
704         if (login_asterisk()) {
705 #ifndef __Darwin__              
706                 close(mixfd);
707 #endif
708                 fclose(astf);
709                 exit(1);
710         }
711 #ifdef HAVE_WORKING_FORK
712         if (needfork) {
713 #ifndef HAVE_SBIN_LAUNCHD
714                 if (daemon(0,0) < 0) {
715                         fprintf(stderr, "daemon() failed: %s\n", strerror(errno));
716                         exit(1);
717                 }
718 #else
719                 const char *found = NULL, *paths[] = {
720                         "/Library/LaunchAgents/org.asterisk.muted.plist",
721                         "/Library/LaunchDaemons/org.asterisk.muted.plist",
722                         "contrib/init.d/org.asterisk.muted.plist",
723                         "<path-to-asterisk-source>/contrib/init.d/org.asterisk.muted.plist" };
724                 char userpath[256];
725                 struct stat unused;
726                 struct passwd *pwd = getpwuid(getuid());
727                 int i;
728
729                 snprintf(userpath, sizeof(userpath), "%s%s", pwd->pw_dir, paths[0]);
730                 if (!stat(userpath, &unused)) {
731                         found = userpath;
732                 }
733
734                 if (!found) {
735                         for (i = 0; i < 3; i++) {
736                                 if (!stat(paths[i], &unused)) {
737                                         found = paths[i];
738                                         break;
739                                 }
740                         }
741                 }
742
743                 fprintf(stderr, "Mac OS X detected.  Use 'launchctl load -w %s' to launch.\n", found ? found : paths[3]);
744                 exit(1);
745 #endif /* !defined(HAVE_SBIN_LAUNCHD */
746         }
747 #endif
748         for(;;) {
749                 if (wait_event()) {
750                         fclose(astf);
751                         while(connect_asterisk()) {
752                                 sleep(5);
753                         }
754                         if (login_asterisk()) {
755                                 fclose(astf);
756                                 exit(1);
757                         }
758                 }
759         }
760         exit(0);
761 }