Check that FD's are open before closing (bug #2858)
[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                         if (-1 != fcntl(x, F_GETFL)) {
229                                 close(x);
230                         }
231                 }
232                 /* Child */
233                 chdir(class->dir);
234                 if(class->custom) {
235                         execv(argv[0], argv);
236                 } else {
237                         /* Default install is /usr/local/bin */
238                         execv(LOCAL_MPG_123, argv);
239                         /* Many places have it in /usr/bin */
240                         execv(MPG_123, argv);
241                         /* Check PATH as a last-ditch effort */
242                         execvp("mpg123", argv);
243                 }
244                 ast_log(LOG_WARNING, "Exec failed: %s\n", strerror(errno));
245                 close(fds[1]);
246                 exit(1);
247         } else {
248                 /* Parent */
249                 close(fds[1]);
250         }
251         return fds[0];
252 }
253
254 static void *monmp3thread(void *data)
255 {
256 #define MOH_MS_INTERVAL         100
257
258         struct mohclass *class = data;
259         struct mohdata *moh;
260         char buf[8192];
261         short sbuf[8192];
262         int res, res2;
263         struct timeval tv;
264         struct timeval tv_tmp;
265         long error_sec, error_usec;
266         long delay;
267
268         tv_tmp.tv_sec = 0;
269         tv_tmp.tv_usec = 0;
270         tv.tv_sec = 0;
271         tv.tv_usec = 0;
272         error_sec = 0;
273         error_usec = 0;
274         for(;/* ever */;) {
275                 /* Spawn mp3 player if it's not there */
276                 if (class->srcfd < 0) {
277                         if ((class->srcfd = spawn_mp3(class)) < 0) {
278                                 ast_log(LOG_WARNING, "unable to spawn mp3player\n");
279                                 /* Try again later */
280                                 sleep(500);
281                         }
282                 }
283                 if (class->pseudofd > -1) {
284                         /* Pause some amount of time */
285                         res = read(class->pseudofd, buf, sizeof(buf));
286                 } else {
287                         /* Reliable sleep */
288                         if (gettimeofday(&tv_tmp, NULL) < 0) {
289                                 ast_log(LOG_NOTICE, "gettimeofday() failed!\n");
290                                 return NULL;
291                         }
292                         if (((unsigned long)(tv.tv_sec) > 0)&&((unsigned long)(tv.tv_usec) > 0)) {
293                                 if ((unsigned long)(tv_tmp.tv_usec) < (unsigned long)(tv.tv_usec)) {
294                                         tv_tmp.tv_usec += 1000000;
295                                         tv_tmp.tv_sec -= 1;
296                                 }
297                                 error_sec = (unsigned long)(tv_tmp.tv_sec) - (unsigned long)(tv.tv_sec);
298                                 error_usec = (unsigned long)(tv_tmp.tv_usec) - (unsigned long)(tv.tv_usec);
299                         } else {
300                                 error_sec = 0;
301                                 error_usec = 0;
302                         }
303                         if (error_sec * 1000 + error_usec / 1000 < MOH_MS_INTERVAL) {
304                                 tv.tv_sec = tv_tmp.tv_sec + (MOH_MS_INTERVAL/1000 - error_sec);
305                                 tv.tv_usec = tv_tmp.tv_usec + ((MOH_MS_INTERVAL % 1000) * 1000 - error_usec);
306                                 delay = (MOH_MS_INTERVAL/1000 - error_sec) * 1000 +
307                                                         ((MOH_MS_INTERVAL % 1000) * 1000 - error_usec) / 1000;
308                         } else {
309                                 ast_log(LOG_NOTICE, "Request to schedule in the past?!?!\n");
310                                 tv.tv_sec = tv_tmp.tv_sec;
311                                 tv.tv_usec = tv_tmp.tv_usec;
312                                 delay = 0;
313                         }
314                         if (tv.tv_usec > 1000000) {
315                                 tv.tv_sec++;
316                                 tv.tv_usec-= 1000000;
317                         }
318                         if (delay > 0)
319                                 usleep(delay * 1000);
320                         res = 800;              /* 800 samples */
321                 }
322                 if (!class->members)
323                         continue;
324                 /* Read mp3 audio */
325                 if ((res2 = read(class->srcfd, sbuf, res * 2)) != res * 2) {
326                         if (!res2) {
327                                 close(class->srcfd);
328                                 class->srcfd = -1;
329                                 if (class->pid) {
330                                         kill(class->pid, SIGKILL);
331                                         class->pid = 0;
332                                 }
333                         } else
334                                 ast_log(LOG_DEBUG, "Read %d bytes of audio while expecting %d\n", res2, res * 2);
335                         continue;
336                 }
337                 ast_mutex_lock(&moh_lock);
338                 moh = class->members;
339                 while(moh) {
340                         /* Write data */
341                         if ((res = write(moh->pipe[1], sbuf, res2)) != res2) 
342                                 if (option_debug)
343                                         ast_log(LOG_DEBUG, "Only wrote %d of %d bytes to pipe\n", res, res2);
344                         moh = moh->next;
345                 }
346                 ast_mutex_unlock(&moh_lock);
347         }
348         return NULL;
349 }
350
351 static int moh0_exec(struct ast_channel *chan, void *data)
352 {
353         if (ast_moh_start(chan, data)) {
354                 ast_log(LOG_WARNING, "Unable to start music on hold (class '%s') on channel %s\n", (char *)data, chan->name);
355                 return -1;
356         }
357         while(!ast_safe_sleep(chan, 10000));
358         return -1;
359 }
360
361 static int moh1_exec(struct ast_channel *chan, void *data)
362 {
363         int res;
364         if (!data || !atoi(data)) {
365                 ast_log(LOG_WARNING, "WaitMusicOnHold requires an argument (number of seconds to wait)\n");
366                 return -1;
367         }
368         if (ast_moh_start(chan, NULL)) {
369                 ast_log(LOG_WARNING, "Unable to start music on hold (class '%s') on channel %s\n", (char *)data, chan->name);
370                 return -1;
371         }
372         res = ast_safe_sleep(chan, atoi(data) * 1000);
373         ast_moh_stop(chan);
374         return res;
375 }
376
377 static int moh2_exec(struct ast_channel *chan, void *data)
378 {
379         if (!data || ast_strlen_zero(data)) {
380                 ast_log(LOG_WARNING, "SetMusicOnHold requires an argument (class)\n");
381                 return -1;
382         }
383         strncpy(chan->musicclass, data, sizeof(chan->musicclass) - 1);
384         return 0;
385 }
386
387 static struct mohclass *get_mohbyname(char *name)
388 {
389         struct mohclass *moh;
390         moh = mohclasses;
391         while(moh) {
392                 if (!strcasecmp(name, moh->class))
393                         return moh;
394                 moh = moh->next;
395         }
396         return NULL;
397 }
398
399 static struct mohdata *mohalloc(struct mohclass *cl)
400 {
401         struct mohdata *moh;
402         long flags;
403         moh = malloc(sizeof(struct mohdata));
404         if (!moh)
405                 return NULL;
406         memset(moh, 0, sizeof(struct mohdata));
407         if (pipe(moh->pipe)) {
408                 ast_log(LOG_WARNING, "Failed to create pipe: %s\n", strerror(errno));
409                 free(moh);
410                 return NULL;
411         }
412         /* Make entirely non-blocking */
413         flags = fcntl(moh->pipe[0], F_GETFL);
414         fcntl(moh->pipe[0], F_SETFL, flags | O_NONBLOCK);
415         flags = fcntl(moh->pipe[1], F_GETFL);
416         fcntl(moh->pipe[1], F_SETFL, flags | O_NONBLOCK);
417         moh->parent = cl;
418         moh->next = cl->members;
419         cl->members = moh;
420         return moh;
421 }
422
423 static void moh_release(struct ast_channel *chan, void *data)
424 {
425         struct mohdata *moh = data, *prev, *cur;
426         int oldwfmt;
427         ast_mutex_lock(&moh_lock);
428         /* Unlink */
429         prev = NULL;
430         cur = moh->parent->members;
431         while(cur) {
432                 if (cur == moh) {
433                         if (prev)
434                                 prev->next = cur->next;
435                         else
436                                 moh->parent->members = cur->next;
437                         break;
438                 }
439                 prev = cur;
440                 cur = cur->next;
441         }
442         ast_mutex_unlock(&moh_lock);
443         close(moh->pipe[0]);
444         close(moh->pipe[1]);
445         oldwfmt = moh->origwfmt;
446         free(moh);
447         if (chan) {
448                 if (oldwfmt && ast_set_write_format(chan, oldwfmt)) 
449                         ast_log(LOG_WARNING, "Unable to restore channel '%s' to format %s\n", chan->name, ast_getformatname(oldwfmt));
450                 if (option_verbose > 2)
451                         ast_verbose(VERBOSE_PREFIX_3 "Stopped music on hold on %s\n", chan->name);
452         }
453 }
454
455 static void *moh_alloc(struct ast_channel *chan, void *params)
456 {
457         struct mohdata *res;
458         struct mohclass *class;
459         ast_mutex_lock(&moh_lock);
460         class = get_mohbyname(params);
461         if (class)
462                 res = mohalloc(class);
463         else {
464                 if (strcasecmp(params, "default"))
465                         ast_log(LOG_WARNING, "No class: %s\n", (char *)params);
466                 res = NULL;
467         }
468         ast_mutex_unlock(&moh_lock);
469         if (res) {
470                 res->origwfmt = chan->writeformat;
471                 if (ast_set_write_format(chan, AST_FORMAT_SLINEAR)) {
472                         ast_log(LOG_WARNING, "Unable to set '%s' to signed linear format\n", chan->name);
473                         moh_release(NULL, res);
474                         res = NULL;
475                 }
476 #if 0
477                 /* Allow writes to interrupt */
478                 chan->writeinterrupt = 1;
479 #endif          
480                 if (option_verbose > 2)
481                         ast_verbose(VERBOSE_PREFIX_3 "Started music on hold, class '%s', on %s\n", (char *)params, chan->name);
482         }
483         return res;
484 }
485
486 static int moh_generate(struct ast_channel *chan, void *data, int len, int samples)
487 {
488         struct ast_frame f;
489         struct mohdata *moh = data;
490         short buf[1280 + AST_FRIENDLY_OFFSET / 2];
491         int res;
492
493         len = samples * 2;
494         if (len > sizeof(buf) - AST_FRIENDLY_OFFSET) {
495                 ast_log(LOG_WARNING, "Only doing %d of %d requested bytes on %s\n", (int)sizeof(buf), (int)len, chan->name);
496                 len = sizeof(buf) - AST_FRIENDLY_OFFSET;
497         }
498         res = read(moh->pipe[0], buf + AST_FRIENDLY_OFFSET/2, len);
499 #if 0
500         if (res != len) {
501                 ast_log(LOG_WARNING, "Read only %d of %d bytes: %s\n", res, len, strerror(errno));
502         }
503 #endif
504         if (res > 0) {
505                 memset(&f, 0, sizeof(f));
506                 f.frametype = AST_FRAME_VOICE;
507                 f.subclass = AST_FORMAT_SLINEAR;
508                 f.mallocd = 0;
509                 f.datalen = res;
510                 f.samples = res / 2;
511                 f.data = buf + AST_FRIENDLY_OFFSET / 2;
512                 f.offset = AST_FRIENDLY_OFFSET;
513                 if (ast_write(chan, &f)< 0) {
514                         ast_log(LOG_WARNING, "Failed to write frame to '%s': %s\n", chan->name, strerror(errno));
515                         return -1;
516                 }
517         }
518         return 0;
519 }
520
521 static struct ast_generator mohgen = 
522 {
523         alloc: moh_alloc,
524         release: moh_release,
525         generate: moh_generate,
526 };
527
528 static int moh_register(char *classname, char *mode, char *param, char *miscargs)
529 {
530         struct mohclass *moh;
531 #ifdef ZAPATA_MOH
532         int x;
533 #endif
534         ast_mutex_lock(&moh_lock);
535         moh = get_mohbyname(classname);
536         ast_mutex_unlock(&moh_lock);
537         if (moh) {
538                 ast_log(LOG_WARNING, "Music on Hold '%s' already exists\n", classname);
539                 return -1;
540         }
541         moh = malloc(sizeof(struct mohclass));
542         if (!moh)
543                 return -1;
544         memset(moh, 0, sizeof(struct mohclass));
545         time(&moh->start);
546         moh->start -= respawn_time;
547         strncpy(moh->class, classname, sizeof(moh->class) - 1);
548         if (miscargs)
549                 strncpy(moh->miscargs, miscargs, sizeof(moh->miscargs) - 1);
550         if (!strcasecmp(mode, "mp3") || !strcasecmp(mode, "mp3nb") || !strcasecmp(mode, "quietmp3") || !strcasecmp(mode, "quietmp3nb") || !strcasecmp(mode, "httpmp3") || !strcasecmp(mode, "custom")) {
551                 if (!strcasecmp(mode, "custom"))
552                         moh->custom = 1;
553                 if (!strcasecmp(mode, "mp3nb") || !strcasecmp(mode, "quietmp3nb"))
554                         moh->single = 1;
555                 if (!strcasecmp(mode, "quietmp3") || !strcasecmp(mode, "quietmp3nb"))
556                         moh->quiet = 1;
557                 strncpy(moh->dir, param, sizeof(moh->dir) - 1);
558                 moh->srcfd = -1;
559 #ifdef ZAPATA_MOH
560                 /* It's an MP3 Moh -- Open /dev/zap/pseudo for timing...  Is
561                    there a better, yet reliable way to do this? */
562                 moh->pseudofd = open("/dev/zap/pseudo", O_RDONLY);
563                 if (moh->pseudofd < 0) {
564                         ast_log(LOG_WARNING, "Unable to open pseudo channel for timing...  Sound may be choppy.\n");
565                 } else {
566                         x = 320;
567                         ioctl(moh->pseudofd, ZT_SET_BLOCKSIZE, &x);
568                 }
569 #else
570                 moh->pseudofd = -1;
571 #endif
572                 if (ast_pthread_create(&moh->thread, NULL, monmp3thread, moh)) {
573                         ast_log(LOG_WARNING, "Unable to create moh...\n");
574                         if (moh->pseudofd > -1)
575                                 close(moh->pseudofd);
576                         free(moh);
577                         return -1;
578                 }
579         } else {
580                 ast_log(LOG_WARNING, "Don't know how to do a mode '%s' music on hold\n", mode);
581                 free(moh);
582                 return -1;
583         }
584         ast_mutex_lock(&moh_lock);
585         moh->next = mohclasses;
586         mohclasses = moh;
587         ast_mutex_unlock(&moh_lock);
588         return 0;
589 }
590
591 int ast_moh_start(struct ast_channel *chan, char *class)
592 {
593         if (!class || ast_strlen_zero(class))
594                 class = chan->musicclass;
595         if (!class || ast_strlen_zero(class))
596                 class = "default";
597         return ast_activate_generator(chan, &mohgen, class);
598 }
599
600 void ast_moh_stop(struct ast_channel *chan)
601 {
602         ast_deactivate_generator(chan);
603 }
604
605 static void load_moh_classes(void)
606 {
607         struct ast_config *cfg;
608         struct ast_variable *var;
609         char *data;
610         char *args;
611         cfg = ast_load("musiconhold.conf");
612         if (cfg) {
613                 var = ast_variable_browse(cfg, "classes");
614                 while(var) {
615                         data = strchr(var->value, ':');
616                         if (data) {
617                                 *data = '\0';
618                                 data++;
619                                 args = strchr(data, ',');
620                                 if (args) {
621                                         *args = '\0';
622                                         args++;
623                                 }
624                                 moh_register(var->name, var->value, data,args);
625                         }
626                         var = var->next;
627                 }
628                 ast_destroy(cfg);
629         }
630 }
631
632 static void ast_moh_destroy(void)
633 {
634         struct mohclass *moh;
635         char buff[8192];
636         int bytes, tbytes=0, stime = 0;
637         if (option_verbose > 1)
638                 ast_verbose(VERBOSE_PREFIX_2 "Destroying any remaining musiconhold processes\n");
639         ast_mutex_lock(&moh_lock);
640         moh = mohclasses;
641         while(moh) {
642                 if (moh->pid) {
643                         ast_log(LOG_DEBUG, "killing %d!\n", moh->pid);
644                         stime = time(NULL);
645                         kill(moh->pid, SIGKILL);
646                         while ((bytes = read(moh->srcfd, buff, 8192)) && time(NULL) < stime + 5) {
647                                 tbytes = tbytes + bytes;
648                         }
649                         ast_log(LOG_DEBUG, "mpg123 pid %d and child died after %d bytes read\n", moh->pid, tbytes);
650                         close(moh->srcfd);
651                         moh->pid = 0;
652                 }
653                 moh = moh->next;
654         }
655         ast_mutex_unlock(&moh_lock);
656 }
657
658 int load_module(void)
659 {
660         int res;
661         load_moh_classes();
662         res = ast_register_application(app0, moh0_exec, synopsis0, descrip0);
663         ast_register_atexit(ast_moh_destroy);
664         if (!res)
665                 res = ast_register_application(app1, moh1_exec, synopsis1, descrip1);
666         if (!res)
667                 res = ast_register_application(app2, moh2_exec, synopsis2, descrip2);
668         return res;
669 }
670
671 int unload_module(void)
672 {
673         return -1;
674 }
675
676 char *description(void)
677 {
678         return "Music On Hold Resource";
679 }
680
681 int usecount(void)
682 {
683         /* Never allow Music On Hold to be unloaded
684            unresolve needed symbols in the dialer */
685 #if 0
686         int res;
687         STANDARD_USECOUNT(res);
688         return res;
689 #else
690         return 1;
691 #endif
692 }
693
694 char *key()
695 {
696         return ASTERISK_GPL_KEY;
697 }