Make menu interruptible (bug #2377), Also fix PBX "add extension" CLI (bug #2289)
[asterisk/asterisk.git] / res / res_musiconhold.c
1 /*
2  * Asterisk -- A telephony toolkit for Linux.
3  *
4  * Routines implementing music on hold
5  * 
6  * Copyright (C) 1999-2004, Digium, Inc.
7  *
8  * Mark Spencer <markster@digium.com>
9  *
10  * This program is free software, distributed under the terms of
11  * the GNU General Public License
12  */
13
14 #include <asterisk/lock.h>
15 #include <asterisk/file.h>
16 #include <asterisk/logger.h>
17 #include <asterisk/channel.h>
18 #include <asterisk/pbx.h>
19 #include <asterisk/options.h>
20 #include <asterisk/module.h>
21 #include <asterisk/translate.h>
22 #include <asterisk/say.h>
23 #include <asterisk/channel_pvt.h>
24 #include <asterisk/musiconhold.h>
25 #include <asterisk/config.h>
26 #include <asterisk/utils.h>
27 #include <stdlib.h>
28 #include <errno.h>
29 #include <unistd.h>
30 #include <string.h>
31 #include <signal.h>
32 #include <stdlib.h>
33 #include <stdio.h>
34 #include <sys/time.h>
35 #include <sys/signal.h>
36 #include <netinet/in.h>
37 #include <sys/stat.h>
38 #include <dirent.h>
39 #ifdef ZAPATA_MOH
40 #ifdef __linux__
41 #include <linux/zaptel.h>
42 #else
43 #include <zaptel.h>
44 #endif /* __linux__ */
45 #endif
46 #include <unistd.h>
47 #include <sys/ioctl.h>
48
49 static char *app0 = "MusicOnHold";
50 static char *app1 = "WaitMusicOnHold";
51 static char *app2 = "SetMusicOnHold";
52
53 static char *synopsis0 = "Play Music On Hold indefinitely";
54 static char *synopsis1 = "Wait, playing Music On Hold";
55 static char *synopsis2 = "Set default Music On Hold class";
56
57 static char *descrip0 = "MusicOnHold(class): "
58 "Plays hold music specified by class.  If omitted, the default\n"
59 "music source for the channel will be used. Set the default \n"
60 "class with the SetMusicOnHold() application.\n"
61 "Returns -1 on hangup.\n"
62 "Never returns otherwise.\n";
63
64 static char *descrip1 = "WaitMusicOnHold(delay): "
65 "Plays hold music specified number of seconds.  Returns 0 when\n"
66 "done, or -1 on hangup.  If no hold music is available, the delay will\n"
67 "still occur with no sound.\n";
68
69 static char *descrip2 = "SetMusicOnHold(class): "
70 "Sets the default class for music on hold for a given channel.  When\n"
71 "music on hold is activated, this class will be used to select which\n"
72 "music is played.\n";
73
74 static int respawn_time = 20;
75
76 struct mohclass {
77         char class[80];
78         char dir[256];
79         char miscargs[256];
80         int destroyme;
81         int pid;                /* PID of mpg123 */
82         int quiet;
83         int single;
84         int custom;
85         time_t start;
86         pthread_t thread;
87         struct mohdata *members;
88         /* Source of audio */
89         int srcfd;
90         /* FD for timing source */
91         int pseudofd;
92         struct mohclass *next;
93 };
94
95 struct mohdata {
96         int pipe[2];
97         int origwfmt;
98         struct mohclass *parent;
99         struct mohdata *next;
100 };
101
102 static struct mohclass *mohclasses;
103
104
105 AST_MUTEX_DEFINE_STATIC(moh_lock);
106
107 #define LOCAL_MPG_123 "/usr/local/bin/mpg123"
108 #define MPG_123 "/usr/bin/mpg123"
109 #define MAX_MP3S 256
110
111 static int spawn_mp3(struct mohclass *class)
112 {
113         int fds[2];
114         int files;
115         char fns[MAX_MP3S][80];
116         char *argv[MAX_MP3S + 50];
117         char xargs[256];
118         char *argptr;
119         int argc = 0;
120         DIR *dir;
121         struct dirent *de;
122         dir = opendir(class->dir);
123         if (!dir && !strstr(class->dir,"http://") && !strstr(class->dir,"HTTP://")) {
124                 ast_log(LOG_WARNING, "%s is not a valid directory\n", class->dir);
125                 return -1;
126         }
127
128         if (!class->custom) {
129                 argv[argc++] = "mpg123";
130                 argv[argc++] = "-q";
131                 argv[argc++] = "-s";
132                 argv[argc++] = "--mono";
133                 argv[argc++] = "-r";
134                 argv[argc++] = "8000";
135                 
136                 if (!class->single) {
137                         argv[argc++] = "-b";
138                         argv[argc++] = "2048";
139                 }
140
141                 argv[argc++] = "-f";
142
143                 if (class->quiet)
144                         argv[argc++] = "4096";
145                 else
146                         argv[argc++] = "8192";
147
148                 /* Look for extra arguments and add them to the list */
149                 strncpy(xargs, class->miscargs, sizeof(xargs) - 1);
150                 argptr = xargs;
151                 while(argptr && !ast_strlen_zero(argptr)) {
152                         argv[argc++] = argptr;
153                         argptr = strchr(argptr, ',');
154                         if (argptr) {
155                                 *argptr = '\0';
156                                 argptr++;
157                         }
158                 }
159         } else {
160                 /* Format arguments for argv vector */
161                 strncpy(xargs, class->miscargs, sizeof(xargs) - 1);
162                 argptr = xargs;
163                 while(argptr && !ast_strlen_zero(argptr)) {
164                         argv[argc++] = argptr;
165                         argptr = strchr(argptr, ' ');
166                         if (argptr) {
167                                 *argptr = '\0';
168                                 argptr++;
169                         }
170                 }
171         }
172
173         files = 0;
174         if (strstr(class->dir,"http://") || strstr(class->dir,"HTTP://"))
175         {
176                 strncpy(fns[files], class->dir, sizeof(fns[files]) - 1);
177                 argv[argc++] = fns[files];
178                 files++;
179         }
180         else
181         {
182                 while((de = readdir(dir)) && (files < MAX_MP3S)) {
183                         if ((strlen(de->d_name) > 3) && !strcasecmp(de->d_name + strlen(de->d_name) - 4, ".mp3")) {
184                                 strncpy(fns[files], de->d_name, sizeof(fns[files]) - 1);
185                                 argv[argc++] = fns[files];
186                                 files++;
187                         }
188                 }
189         }
190         argv[argc] = NULL;
191         closedir(dir);
192         if (pipe(fds)) {        
193                 ast_log(LOG_WARNING, "Pipe failed\n");
194                 return -1;
195         }
196 #if 0
197         printf("%d files total, %d args total\n", files, argc);
198         {
199                 int x;
200                 for (x=0;argv[x];x++)
201                         printf("arg%d: %s\n", x, argv[x]);
202         }
203 #endif  
204         if (!files) {
205                 ast_log(LOG_WARNING, "Found no files in '%s'\n", class->dir);
206                 close(fds[0]);
207                 close(fds[1]);
208                 return -1;
209         }
210         if (time(NULL) - class->start < respawn_time) {
211                 sleep(respawn_time - (time(NULL) - class->start));
212         }
213         time(&class->start);
214         class->pid = fork();
215         if (class->pid < 0) {
216                 close(fds[0]);
217                 close(fds[1]);
218                 ast_log(LOG_WARNING, "Fork failed: %s\n", strerror(errno));
219                 return -1;
220         }
221         if (!class->pid) {
222                 int x;
223                 close(fds[0]);
224                 /* Stdout goes to pipe */
225                 dup2(fds[1], STDOUT_FILENO);
226                 /* Close unused file descriptors */
227                 for (x=3;x<8192;x++)
228                         close(x);
229                 /* Child */
230                 chdir(class->dir);
231                 if(class->custom) {
232                         execv(argv[0], argv);
233                 } else {
234                         /* Default install is /usr/local/bin */
235                         execv(LOCAL_MPG_123, argv);
236                         /* Many places have it in /usr/bin */
237                         execv(MPG_123, argv);
238                         /* Check PATH as a last-ditch effort */
239                         execvp("mpg123", argv);
240                 }
241                 ast_log(LOG_WARNING, "Exec failed: %s\n", strerror(errno));
242                 close(fds[1]);
243                 exit(1);
244         } else {
245                 /* Parent */
246                 close(fds[1]);
247         }
248         return fds[0];
249 }
250
251 static void *monmp3thread(void *data)
252 {
253 #define MOH_MS_INTERVAL         100
254
255         struct mohclass *class = data;
256         struct mohdata *moh;
257         char buf[8192];
258         short sbuf[8192];
259         int res, res2;
260         struct timeval tv;
261         struct timeval tv_tmp;
262         long error_sec, error_usec;
263         long delay;
264
265         tv_tmp.tv_sec = 0;
266         tv_tmp.tv_usec = 0;
267         tv.tv_sec = 0;
268         tv.tv_usec = 0;
269         error_sec = 0;
270         error_usec = 0;
271         for(;/* ever */;) {
272                 /* Spawn mp3 player if it's not there */
273                 if (class->srcfd < 0) {
274                         if ((class->srcfd = spawn_mp3(class)) < 0) {
275                                 ast_log(LOG_WARNING, "unable to spawn mp3player\n");
276                                 /* Try again later */
277                                 sleep(500);
278                         }
279                 }
280                 if (class->pseudofd > -1) {
281                         /* Pause some amount of time */
282                         res = read(class->pseudofd, buf, sizeof(buf));
283                 } else {
284                         /* Reliable sleep */
285                         if (gettimeofday(&tv_tmp, NULL) < 0) {
286                                 ast_log(LOG_NOTICE, "gettimeofday() failed!\n");
287                                 return NULL;
288                         }
289                         if (((unsigned long)(tv.tv_sec) > 0)&&((unsigned long)(tv.tv_usec) > 0)) {
290                                 if ((unsigned long)(tv_tmp.tv_usec) < (unsigned long)(tv.tv_usec)) {
291                                         tv_tmp.tv_usec += 1000000;
292                                         tv_tmp.tv_sec -= 1;
293                                 }
294                                 error_sec = (unsigned long)(tv_tmp.tv_sec) - (unsigned long)(tv.tv_sec);
295                                 error_usec = (unsigned long)(tv_tmp.tv_usec) - (unsigned long)(tv.tv_usec);
296                         } else {
297                                 error_sec = 0;
298                                 error_usec = 0;
299                         }
300                         if (error_sec * 1000 + error_usec / 1000 < MOH_MS_INTERVAL) {
301                                 tv.tv_sec = tv_tmp.tv_sec + (MOH_MS_INTERVAL/1000 - error_sec);
302                                 tv.tv_usec = tv_tmp.tv_usec + ((MOH_MS_INTERVAL % 1000) * 1000 - error_usec);
303                                 delay = (MOH_MS_INTERVAL/1000 - error_sec) * 1000 +
304                                                         ((MOH_MS_INTERVAL % 1000) * 1000 - error_usec) / 1000;
305                         } else {
306                                 ast_log(LOG_NOTICE, "Request to schedule in the past?!?!\n");
307                                 tv.tv_sec = tv_tmp.tv_sec;
308                                 tv.tv_usec = tv_tmp.tv_usec;
309                                 delay = 0;
310                         }
311                         if (tv.tv_usec > 1000000) {
312                                 tv.tv_sec++;
313                                 tv.tv_usec-= 1000000;
314                         }
315                         if (delay > 0)
316                                 usleep(delay * 1000);
317                         res = 800;              /* 800 samples */
318                 }
319                 if (!class->members)
320                         continue;
321                 /* Read mp3 audio */
322                 if ((res2 = read(class->srcfd, sbuf, res * 2)) != res * 2) {
323                         if (!res2) {
324                                 close(class->srcfd);
325                                 class->srcfd = -1;
326                                 if (class->pid) {
327                                         kill(class->pid, SIGKILL);
328                                         class->pid = 0;
329                                 }
330                         } else
331                                 ast_log(LOG_DEBUG, "Read %d bytes of audio while expecting %d\n", res2, res * 2);
332                         continue;
333                 }
334                 ast_mutex_lock(&moh_lock);
335                 moh = class->members;
336                 while(moh) {
337                         /* Write data */
338                         if ((res = write(moh->pipe[1], sbuf, res2)) != res2) 
339                                 if (option_debug)
340                                         ast_log(LOG_DEBUG, "Only wrote %d of %d bytes to pipe\n", res, res2);
341                         moh = moh->next;
342                 }
343                 ast_mutex_unlock(&moh_lock);
344         }
345         return NULL;
346 }
347
348 static int moh0_exec(struct ast_channel *chan, void *data)
349 {
350         if (ast_moh_start(chan, data)) {
351                 ast_log(LOG_WARNING, "Unable to start music on hold (class '%s') on channel %s\n", (char *)data, chan->name);
352                 return -1;
353         }
354         while(!ast_safe_sleep(chan, 10000));
355         return -1;
356 }
357
358 static int moh1_exec(struct ast_channel *chan, void *data)
359 {
360         int res;
361         if (!data || !atoi(data)) {
362                 ast_log(LOG_WARNING, "WaitMusicOnHold requires an argument (number of seconds to wait)\n");
363                 return -1;
364         }
365         if (ast_moh_start(chan, NULL)) {
366                 ast_log(LOG_WARNING, "Unable to start music on hold (class '%s') on channel %s\n", (char *)data, chan->name);
367                 return -1;
368         }
369         res = ast_safe_sleep(chan, atoi(data) * 1000);
370         ast_moh_stop(chan);
371         return res;
372 }
373
374 static int moh2_exec(struct ast_channel *chan, void *data)
375 {
376         if (!data || ast_strlen_zero(data)) {
377                 ast_log(LOG_WARNING, "SetMusicOnHold requires an argument (class)\n");
378                 return -1;
379         }
380         strncpy(chan->musicclass, data, sizeof(chan->musicclass) - 1);
381         return 0;
382 }
383
384 static struct mohclass *get_mohbyname(char *name)
385 {
386         struct mohclass *moh;
387         moh = mohclasses;
388         while(moh) {
389                 if (!strcasecmp(name, moh->class))
390                         return moh;
391                 moh = moh->next;
392         }
393         return NULL;
394 }
395
396 static struct mohdata *mohalloc(struct mohclass *cl)
397 {
398         struct mohdata *moh;
399         long flags;
400         moh = malloc(sizeof(struct mohdata));
401         if (!moh)
402                 return NULL;
403         memset(moh, 0, sizeof(struct mohdata));
404         if (pipe(moh->pipe)) {
405                 ast_log(LOG_WARNING, "Failed to create pipe: %s\n", strerror(errno));
406                 free(moh);
407                 return NULL;
408         }
409         /* Make entirely non-blocking */
410         flags = fcntl(moh->pipe[0], F_GETFL);
411         fcntl(moh->pipe[0], F_SETFL, flags | O_NONBLOCK);
412         flags = fcntl(moh->pipe[1], F_GETFL);
413         fcntl(moh->pipe[1], F_SETFL, flags | O_NONBLOCK);
414         moh->parent = cl;
415         moh->next = cl->members;
416         cl->members = moh;
417         return moh;
418 }
419
420 static void moh_release(struct ast_channel *chan, void *data)
421 {
422         struct mohdata *moh = data, *prev, *cur;
423         int oldwfmt;
424         ast_mutex_lock(&moh_lock);
425         /* Unlink */
426         prev = NULL;
427         cur = moh->parent->members;
428         while(cur) {
429                 if (cur == moh) {
430                         if (prev)
431                                 prev->next = cur->next;
432                         else
433                                 moh->parent->members = cur->next;
434                         break;
435                 }
436                 prev = cur;
437                 cur = cur->next;
438         }
439         ast_mutex_unlock(&moh_lock);
440         close(moh->pipe[0]);
441         close(moh->pipe[1]);
442         oldwfmt = moh->origwfmt;
443         free(moh);
444         if (chan) {
445                 if (oldwfmt && ast_set_write_format(chan, oldwfmt)) 
446                         ast_log(LOG_WARNING, "Unable to restore channel '%s' to format %s\n", chan->name, ast_getformatname(oldwfmt));
447                 if (option_verbose > 2)
448                         ast_verbose(VERBOSE_PREFIX_3 "Stopped music on hold on %s\n", chan->name);
449         }
450 }
451
452 static void *moh_alloc(struct ast_channel *chan, void *params)
453 {
454         struct mohdata *res;
455         struct mohclass *class;
456         ast_mutex_lock(&moh_lock);
457         class = get_mohbyname(params);
458         if (class)
459                 res = mohalloc(class);
460         else {
461                 if (strcasecmp(params, "default"))
462                         ast_log(LOG_WARNING, "No class: %s\n", (char *)params);
463                 res = NULL;
464         }
465         ast_mutex_unlock(&moh_lock);
466         if (res) {
467                 res->origwfmt = chan->writeformat;
468                 if (ast_set_write_format(chan, AST_FORMAT_SLINEAR)) {
469                         ast_log(LOG_WARNING, "Unable to set '%s' to signed linear format\n", chan->name);
470                         moh_release(NULL, res);
471                         res = NULL;
472                 }
473 #if 0
474                 /* Allow writes to interrupt */
475                 chan->writeinterrupt = 1;
476 #endif          
477                 if (option_verbose > 2)
478                         ast_verbose(VERBOSE_PREFIX_3 "Started music on hold, class '%s', on %s\n", (char *)params, chan->name);
479         }
480         return res;
481 }
482
483 static int moh_generate(struct ast_channel *chan, void *data, int len, int samples)
484 {
485         struct ast_frame f;
486         struct mohdata *moh = data;
487         short buf[1280 + AST_FRIENDLY_OFFSET / 2];
488         int res;
489
490         len = samples * 2;
491         if (len > sizeof(buf) - AST_FRIENDLY_OFFSET) {
492                 ast_log(LOG_WARNING, "Only doing %d of %d requested bytes on %s\n", (int)sizeof(buf), (int)len, chan->name);
493                 len = sizeof(buf) - AST_FRIENDLY_OFFSET;
494         }
495         res = read(moh->pipe[0], buf + AST_FRIENDLY_OFFSET/2, len);
496 #if 0
497         if (res != len) {
498                 ast_log(LOG_WARNING, "Read only %d of %d bytes: %s\n", res, len, strerror(errno));
499         }
500 #endif
501         if (res > 0) {
502                 memset(&f, 0, sizeof(f));
503                 f.frametype = AST_FRAME_VOICE;
504                 f.subclass = AST_FORMAT_SLINEAR;
505                 f.mallocd = 0;
506                 f.datalen = res;
507                 f.samples = res / 2;
508                 f.data = buf + AST_FRIENDLY_OFFSET / 2;
509                 f.offset = AST_FRIENDLY_OFFSET;
510                 if (ast_write(chan, &f)< 0) {
511                         ast_log(LOG_WARNING, "Failed to write frame to '%s': %s\n", chan->name, strerror(errno));
512                         return -1;
513                 }
514         }
515         return 0;
516 }
517
518 static struct ast_generator mohgen = 
519 {
520         alloc: moh_alloc,
521         release: moh_release,
522         generate: moh_generate,
523 };
524
525 static int moh_register(char *classname, char *mode, char *param, char *miscargs)
526 {
527         struct mohclass *moh;
528 #ifdef ZAPATA_MOH
529         int x;
530 #endif
531         ast_mutex_lock(&moh_lock);
532         moh = get_mohbyname(classname);
533         ast_mutex_unlock(&moh_lock);
534         if (moh) {
535                 ast_log(LOG_WARNING, "Music on Hold '%s' already exists\n", classname);
536                 return -1;
537         }
538         moh = malloc(sizeof(struct mohclass));
539         if (!moh)
540                 return -1;
541         memset(moh, 0, sizeof(struct mohclass));
542         time(&moh->start);
543         moh->start -= respawn_time;
544         strncpy(moh->class, classname, sizeof(moh->class) - 1);
545         if (miscargs)
546                 strncpy(moh->miscargs, miscargs, sizeof(moh->miscargs) - 1);
547         if (!strcasecmp(mode, "mp3") || !strcasecmp(mode, "mp3nb") || !strcasecmp(mode, "quietmp3") || !strcasecmp(mode, "quietmp3nb") || !strcasecmp(mode, "httpmp3") || !strcasecmp(mode, "custom")) {
548                 if (!strcasecmp(mode, "custom"))
549                         moh->custom = 1;
550                 if (!strcasecmp(mode, "mp3nb") || !strcasecmp(mode, "quietmp3nb"))
551                         moh->single = 1;
552                 if (!strcasecmp(mode, "quietmp3") || !strcasecmp(mode, "quietmp3nb"))
553                         moh->quiet = 1;
554                 strncpy(moh->dir, param, sizeof(moh->dir) - 1);
555                 moh->srcfd = -1;
556 #ifdef ZAPATA_MOH
557                 /* It's an MP3 Moh -- Open /dev/zap/pseudo for timing...  Is
558                    there a better, yet reliable way to do this? */
559                 moh->pseudofd = open("/dev/zap/pseudo", O_RDONLY);
560                 if (moh->pseudofd < 0) {
561                         ast_log(LOG_WARNING, "Unable to open pseudo channel for timing...  Sound may be choppy.\n");
562                 } else {
563                         x = 320;
564                         ioctl(moh->pseudofd, ZT_SET_BLOCKSIZE, &x);
565                 }
566 #else
567                 moh->pseudofd = -1;
568 #endif
569                 if (ast_pthread_create(&moh->thread, NULL, monmp3thread, moh)) {
570                         ast_log(LOG_WARNING, "Unable to create moh...\n");
571                         if (moh->pseudofd > -1)
572                                 close(moh->pseudofd);
573                         free(moh);
574                         return -1;
575                 }
576         } else {
577                 ast_log(LOG_WARNING, "Don't know how to do a mode '%s' music on hold\n", mode);
578                 free(moh);
579                 return -1;
580         }
581         ast_mutex_lock(&moh_lock);
582         moh->next = mohclasses;
583         mohclasses = moh;
584         ast_mutex_unlock(&moh_lock);
585         return 0;
586 }
587
588 int ast_moh_start(struct ast_channel *chan, char *class)
589 {
590         if (!class || ast_strlen_zero(class))
591                 class = chan->musicclass;
592         if (!class || ast_strlen_zero(class))
593                 class = "default";
594         return ast_activate_generator(chan, &mohgen, class);
595 }
596
597 void ast_moh_stop(struct ast_channel *chan)
598 {
599         ast_deactivate_generator(chan);
600 }
601
602 static void load_moh_classes(void)
603 {
604         struct ast_config *cfg;
605         struct ast_variable *var;
606         char *data;
607         char *args;
608         cfg = ast_load("musiconhold.conf");
609         if (cfg) {
610                 var = ast_variable_browse(cfg, "classes");
611                 while(var) {
612                         data = strchr(var->value, ':');
613                         if (data) {
614                                 *data = '\0';
615                                 data++;
616                                 args = strchr(data, ',');
617                                 if (args) {
618                                         *args = '\0';
619                                         args++;
620                                 }
621                                 moh_register(var->name, var->value, data,args);
622                         }
623                         var = var->next;
624                 }
625                 ast_destroy(cfg);
626         }
627 }
628
629 static void ast_moh_destroy(void)
630 {
631         struct mohclass *moh;
632         char buff[8192];
633         int bytes, tbytes=0, stime = 0;
634         if (option_verbose > 1)
635                 ast_verbose(VERBOSE_PREFIX_2 "Destroying any remaining musiconhold processes\n");
636         ast_mutex_lock(&moh_lock);
637         moh = mohclasses;
638         while(moh) {
639                 if (moh->pid) {
640                         ast_log(LOG_DEBUG, "killing %d!\n", moh->pid);
641                         stime = time(NULL);
642                         kill(moh->pid, SIGKILL);
643                         while ((bytes = read(moh->srcfd, buff, 8192)) && time(NULL) < stime + 5) {
644                                 tbytes = tbytes + bytes;
645                         }
646                         ast_log(LOG_DEBUG, "mpg123 pid %d and child died after %d bytes read\n", moh->pid, tbytes);
647                         close(moh->srcfd);
648                         moh->pid = 0;
649                 }
650                 moh = moh->next;
651         }
652         ast_mutex_unlock(&moh_lock);
653 }
654
655 int load_module(void)
656 {
657         int res;
658         load_moh_classes();
659         res = ast_register_application(app0, moh0_exec, synopsis0, descrip0);
660         ast_register_atexit(ast_moh_destroy);
661         if (!res)
662                 res = ast_register_application(app1, moh1_exec, synopsis1, descrip1);
663         if (!res)
664                 res = ast_register_application(app2, moh2_exec, synopsis2, descrip2);
665         return res;
666 }
667
668 int unload_module(void)
669 {
670         return -1;
671 }
672
673 char *description(void)
674 {
675         return "Music On Hold Resource";
676 }
677
678 int usecount(void)
679 {
680         /* Never allow Music On Hold to be unloaded
681            unresolve needed symbols in the dialer */
682 #if 0
683         int res;
684         STANDARD_USECOUNT(res);
685         return res;
686 #else
687         return 1;
688 #endif
689 }
690
691 char *key()
692 {
693         return ASTERISK_GPL_KEY;
694 }