00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033 #include "asterisk.h"
00034
00035 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 168198 $")
00036
00037 #include <stdlib.h>
00038 #include <errno.h>
00039 #include <unistd.h>
00040 #include <string.h>
00041 #include <signal.h>
00042 #include <stdlib.h>
00043 #include <stdio.h>
00044 #include <sys/time.h>
00045 #include <sys/signal.h>
00046 #include <netinet/in.h>
00047 #include <sys/stat.h>
00048 #include <dirent.h>
00049 #include <unistd.h>
00050 #include <sys/ioctl.h>
00051 #ifdef SOLARIS
00052 #include <thread.h>
00053 #endif
00054
00055 #include "asterisk/lock.h"
00056 #include "asterisk/file.h"
00057 #include "asterisk/logger.h"
00058 #include "asterisk/channel.h"
00059 #include "asterisk/pbx.h"
00060 #include "asterisk/options.h"
00061 #include "asterisk/module.h"
00062 #include "asterisk/translate.h"
00063 #include "asterisk/say.h"
00064 #include "asterisk/musiconhold.h"
00065 #include "asterisk/config.h"
00066 #include "asterisk/utils.h"
00067 #include "asterisk/cli.h"
00068 #include "asterisk/stringfields.h"
00069 #include "asterisk/linkedlists.h"
00070 #include "asterisk/astobj2.h"
00071
00072 #include "asterisk/dahdi_compat.h"
00073
00074 #define INITIAL_NUM_FILES 8
00075
00076 static char *app0 = "MusicOnHold";
00077 static char *app1 = "WaitMusicOnHold";
00078 static char *app2 = "SetMusicOnHold";
00079 static char *app3 = "StartMusicOnHold";
00080 static char *app4 = "StopMusicOnHold";
00081
00082 static char *synopsis0 = "Play Music On Hold indefinitely";
00083 static char *synopsis1 = "Wait, playing Music On Hold";
00084 static char *synopsis2 = "Set default Music On Hold class";
00085 static char *synopsis3 = "Play Music On Hold";
00086 static char *synopsis4 = "Stop Playing Music On Hold";
00087
00088 static char *descrip0 = "MusicOnHold(class): "
00089 "Plays hold music specified by class. If omitted, the default\n"
00090 "music source for the channel will be used. Set the default \n"
00091 "class with the SetMusicOnHold() application.\n"
00092 "Returns -1 on hangup.\n"
00093 "Never returns otherwise.\n";
00094
00095 static char *descrip1 = "WaitMusicOnHold(delay): "
00096 "Plays hold music specified number of seconds. Returns 0 when\n"
00097 "done, or -1 on hangup. If no hold music is available, the delay will\n"
00098 "still occur with no sound.\n";
00099
00100 static char *descrip2 = "SetMusicOnHold(class): "
00101 "Sets the default class for music on hold for a given channel. When\n"
00102 "music on hold is activated, this class will be used to select which\n"
00103 "music is played.\n";
00104
00105 static char *descrip3 = "StartMusicOnHold(class): "
00106 "Starts playing music on hold, uses default music class for channel.\n"
00107 "Starts playing music specified by class. If omitted, the default\n"
00108 "music source for the channel will be used. Always returns 0.\n";
00109
00110 static char *descrip4 = "StopMusicOnHold: "
00111 "Stops playing music on hold.\n";
00112
00113 static int respawn_time = 20;
00114
00115 struct moh_files_state {
00116 struct mohclass *class;
00117 int origwfmt;
00118 int samples;
00119 int sample_queue;
00120 int pos;
00121 int save_pos;
00122 char *save_pos_filename;
00123 };
00124
00125 #define MOH_QUIET (1 << 0)
00126 #define MOH_SINGLE (1 << 1)
00127 #define MOH_CUSTOM (1 << 2)
00128 #define MOH_RANDOMIZE (1 << 3)
00129
00130 struct mohclass {
00131 char name[MAX_MUSICCLASS];
00132 char dir[256];
00133 char args[256];
00134 char mode[80];
00135
00136 char **filearray;
00137
00138 int allowed_files;
00139
00140 int total_files;
00141 unsigned int flags;
00142
00143 int format;
00144
00145 int pid;
00146 time_t start;
00147 pthread_t thread;
00148
00149 int srcfd;
00150
00151 int pseudofd;
00152 unsigned int delete:1;
00153 AST_LIST_HEAD_NOLOCK(, mohdata) members;
00154 AST_LIST_ENTRY(mohclass) list;
00155 };
00156
00157 struct mohdata {
00158 int pipe[2];
00159 int origwfmt;
00160 struct mohclass *parent;
00161 struct ast_frame f;
00162 AST_LIST_ENTRY(mohdata) list;
00163 };
00164
00165 static struct ao2_container *mohclasses;
00166
00167 #define LOCAL_MPG_123 "/usr/local/bin/mpg123"
00168 #define MPG_123 "/usr/bin/mpg123"
00169 #define MAX_MP3S 256
00170
00171 static int reload(void);
00172
00173 #define mohclass_ref(class) (ao2_ref((class), +1), class)
00174 #define mohclass_unref(class) (ao2_ref((class), -1), (struct mohclass *) NULL)
00175
00176 static void moh_files_release(struct ast_channel *chan, void *data)
00177 {
00178 struct moh_files_state *state;
00179
00180 if (!chan || !chan->music_state) {
00181 return;
00182 }
00183
00184 state = chan->music_state;
00185
00186 if (chan->stream) {
00187 ast_closestream(chan->stream);
00188 chan->stream = NULL;
00189 }
00190
00191 if (option_verbose > 2) {
00192 ast_verbose(VERBOSE_PREFIX_3 "Stopped music on hold on %s\n", chan->name);
00193 }
00194
00195 if (state->origwfmt && ast_set_write_format(chan, state->origwfmt)) {
00196 ast_log(LOG_WARNING, "Unable to restore channel '%s' to format '%d'\n", chan->name, state->origwfmt);
00197 }
00198
00199 state->save_pos = state->pos;
00200
00201 state->class = mohclass_unref(state->class);
00202 }
00203
00204
00205 static int ast_moh_files_next(struct ast_channel *chan)
00206 {
00207 struct moh_files_state *state = chan->music_state;
00208 int tries;
00209
00210
00211 if (chan->stream) {
00212 ast_closestream(chan->stream);
00213 chan->stream = NULL;
00214 }
00215
00216 if (!state->class->total_files) {
00217 ast_log(LOG_WARNING, "No files available for class '%s'\n", state->class->name);
00218 return -1;
00219 }
00220
00221
00222 if (state->save_pos >= 0 && state->save_pos < state->class->total_files && state->class->filearray[state->save_pos] == state->save_pos_filename) {
00223 state->pos = state->save_pos;
00224 state->save_pos = -1;
00225 } else if (ast_test_flag(state->class, MOH_RANDOMIZE)) {
00226
00227 for (tries = 0; tries < 20; tries++) {
00228 state->pos = ast_random() % state->class->total_files;
00229 if (ast_fileexists(state->class->filearray[state->pos], NULL, NULL) > 0)
00230 break;
00231 }
00232 state->save_pos = -1;
00233 state->samples = 0;
00234 } else {
00235
00236 state->pos++;
00237 state->pos %= state->class->total_files;
00238 state->save_pos = -1;
00239 state->samples = 0;
00240 }
00241
00242 if (!ast_openstream_full(chan, state->class->filearray[state->pos], chan->language, 1)) {
00243 ast_log(LOG_WARNING, "Unable to open file '%s': %s\n", state->class->filearray[state->pos], strerror(errno));
00244 state->pos++;
00245 state->pos %= state->class->total_files;
00246 return -1;
00247 }
00248
00249
00250 state->save_pos_filename = state->class->filearray[state->pos];
00251
00252 if (option_debug)
00253 ast_log(LOG_DEBUG, "%s Opened file %d '%s'\n", chan->name, state->pos, state->class->filearray[state->pos]);
00254
00255 if (state->samples)
00256 ast_seekstream(chan->stream, state->samples, SEEK_SET);
00257
00258 return 0;
00259 }
00260
00261
00262 static struct ast_frame *moh_files_readframe(struct ast_channel *chan)
00263 {
00264 struct ast_frame *f = NULL;
00265
00266 if (!(chan->stream && (f = ast_readframe(chan->stream)))) {
00267 if (!ast_moh_files_next(chan))
00268 f = ast_readframe(chan->stream);
00269 }
00270
00271 return f;
00272 }
00273
00274 static int moh_files_generator(struct ast_channel *chan, void *data, int len, int samples)
00275 {
00276 struct moh_files_state *state = chan->music_state;
00277 struct ast_frame *f = NULL;
00278 int res = 0;
00279
00280 state->sample_queue += samples;
00281
00282 while (state->sample_queue > 0) {
00283 if ((f = moh_files_readframe(chan))) {
00284 state->samples += f->samples;
00285 state->sample_queue -= f->samples;
00286 res = ast_write(chan, f);
00287 ast_frfree(f);
00288 if (res < 0) {
00289 ast_log(LOG_WARNING, "Failed to write frame to '%s': %s\n", chan->name, strerror(errno));
00290 return -1;
00291 }
00292 } else
00293 return -1;
00294 }
00295 return res;
00296 }
00297
00298
00299 static void *moh_files_alloc(struct ast_channel *chan, void *params)
00300 {
00301 struct moh_files_state *state;
00302 struct mohclass *class = params;
00303
00304 if (!chan->music_state && (state = ast_calloc(1, sizeof(*state)))) {
00305 chan->music_state = state;
00306 state->class = mohclass_ref(class);
00307 state->save_pos = -1;
00308 } else {
00309 state = chan->music_state;
00310 }
00311
00312 if (!state) {
00313 return NULL;
00314 }
00315
00316 if (state->class != class) {
00317
00318 if (state->class) {
00319 state->class = mohclass_unref(state->class);
00320 }
00321 memset(state, 0, sizeof(*state));
00322 state->class = mohclass_ref(class);
00323 if (ast_test_flag(state->class, MOH_RANDOMIZE) && class->total_files) {
00324 state->pos = ast_random() % class->total_files;
00325 }
00326 }
00327
00328 state->origwfmt = chan->writeformat;
00329
00330 if (option_verbose > 2) {
00331 ast_verbose(VERBOSE_PREFIX_3 "Started music on hold, class '%s', on %s\n",
00332 class->name, chan->name);
00333 }
00334
00335 return chan->music_state;
00336 }
00337
00338 static struct ast_generator moh_file_stream = {
00339 .alloc = moh_files_alloc,
00340 .release = moh_files_release,
00341 .generate = moh_files_generator,
00342 };
00343
00344 static int spawn_mp3(struct mohclass *class)
00345 {
00346 int fds[2];
00347 int files = 0;
00348 char fns[MAX_MP3S][80];
00349 char *argv[MAX_MP3S + 50];
00350 char xargs[256];
00351 char *argptr;
00352 int argc = 0;
00353 DIR *dir = NULL;
00354 struct dirent *de;
00355 sigset_t signal_set, old_set;
00356
00357
00358 if (!strcasecmp(class->dir, "nodir")) {
00359 files = 1;
00360 } else {
00361 dir = opendir(class->dir);
00362 if (!dir && strncasecmp(class->dir, "http://", 7)) {
00363 ast_log(LOG_WARNING, "%s is not a valid directory\n", class->dir);
00364 return -1;
00365 }
00366 }
00367
00368 if (!ast_test_flag(class, MOH_CUSTOM)) {
00369 argv[argc++] = "mpg123";
00370 argv[argc++] = "-q";
00371 argv[argc++] = "-s";
00372 argv[argc++] = "--mono";
00373 argv[argc++] = "-r";
00374 argv[argc++] = "8000";
00375
00376 if (!ast_test_flag(class, MOH_SINGLE)) {
00377 argv[argc++] = "-b";
00378 argv[argc++] = "2048";
00379 }
00380
00381 argv[argc++] = "-f";
00382
00383 if (ast_test_flag(class, MOH_QUIET))
00384 argv[argc++] = "4096";
00385 else
00386 argv[argc++] = "8192";
00387
00388
00389 ast_copy_string(xargs, class->args, sizeof(xargs));
00390 argptr = xargs;
00391 while (!ast_strlen_zero(argptr)) {
00392 argv[argc++] = argptr;
00393 strsep(&argptr, ",");
00394 }
00395 } else {
00396
00397 ast_copy_string(xargs, class->args, sizeof(xargs));
00398 argptr = xargs;
00399 while (!ast_strlen_zero(argptr)) {
00400 argv[argc++] = argptr;
00401 strsep(&argptr, " ");
00402 }
00403 }
00404
00405 if (!strncasecmp(class->dir, "http://", 7)) {
00406 ast_copy_string(fns[files], class->dir, sizeof(fns[files]));
00407 argv[argc++] = fns[files];
00408 files++;
00409 } else if (dir) {
00410 while ((de = readdir(dir)) && (files < MAX_MP3S)) {
00411 if ((strlen(de->d_name) > 3) &&
00412 ((ast_test_flag(class, MOH_CUSTOM) &&
00413 (!strcasecmp(de->d_name + strlen(de->d_name) - 4, ".raw") ||
00414 !strcasecmp(de->d_name + strlen(de->d_name) - 4, ".sln"))) ||
00415 !strcasecmp(de->d_name + strlen(de->d_name) - 4, ".mp3"))) {
00416 ast_copy_string(fns[files], de->d_name, sizeof(fns[files]));
00417 argv[argc++] = fns[files];
00418 files++;
00419 }
00420 }
00421 }
00422 argv[argc] = NULL;
00423 if (dir) {
00424 closedir(dir);
00425 }
00426 if (pipe(fds)) {
00427 ast_log(LOG_WARNING, "Pipe failed\n");
00428 return -1;
00429 }
00430 if (!files) {
00431 ast_log(LOG_WARNING, "Found no files in '%s'\n", class->dir);
00432 close(fds[0]);
00433 close(fds[1]);
00434 return -1;
00435 }
00436 if (!strncasecmp(class->dir, "http://", 7) && time(NULL) - class->start < respawn_time) {
00437 sleep(respawn_time - (time(NULL) - class->start));
00438 }
00439
00440
00441 sigfillset(&signal_set);
00442 pthread_sigmask(SIG_BLOCK, &signal_set, &old_set);
00443
00444 time(&class->start);
00445 class->pid = fork();
00446 if (class->pid < 0) {
00447 close(fds[0]);
00448 close(fds[1]);
00449 ast_log(LOG_WARNING, "Fork failed: %s\n", strerror(errno));
00450 return -1;
00451 }
00452 if (!class->pid) {
00453 int x;
00454
00455 if (ast_opt_high_priority)
00456 ast_set_priority(0);
00457
00458
00459 signal(SIGPIPE, SIG_DFL);
00460 pthread_sigmask(SIG_UNBLOCK, &signal_set, NULL);
00461
00462 close(fds[0]);
00463
00464 dup2(fds[1], STDOUT_FILENO);
00465
00466 for (x=3;x<8192;x++) {
00467 if (-1 != fcntl(x, F_GETFL)) {
00468 close(x);
00469 }
00470 }
00471
00472 if (strcasecmp(class->dir, "nodir") && chdir(class->dir) < 0) {
00473 ast_log(LOG_WARNING, "chdir() failed: %s\n", strerror(errno));
00474 _exit(1);
00475 }
00476 setpgid(0, getpid());
00477 if (ast_test_flag(class, MOH_CUSTOM)) {
00478 execv(argv[0], argv);
00479 } else {
00480
00481 execv(LOCAL_MPG_123, argv);
00482
00483 execv(MPG_123, argv);
00484
00485 execvp("mpg123", argv);
00486 }
00487 ast_log(LOG_WARNING, "Exec failed: %s\n", strerror(errno));
00488 close(fds[1]);
00489 _exit(1);
00490 } else {
00491
00492 pthread_sigmask(SIG_SETMASK, &old_set, NULL);
00493 close(fds[1]);
00494 }
00495 return fds[0];
00496 }
00497
00498 static void *monmp3thread(void *data)
00499 {
00500 #define MOH_MS_INTERVAL 100
00501
00502 struct mohclass *class = data;
00503 struct mohdata *moh;
00504 char buf[8192];
00505 short sbuf[8192];
00506 int res, res2;
00507 int len;
00508 struct timeval tv, tv_tmp;
00509
00510 tv.tv_sec = 0;
00511 tv.tv_usec = 0;
00512 for(;;) {
00513 pthread_testcancel();
00514
00515 if (class->srcfd < 0) {
00516 if ((class->srcfd = spawn_mp3(class)) < 0) {
00517 ast_log(LOG_WARNING, "Unable to spawn mp3player\n");
00518
00519 sleep(500);
00520 pthread_testcancel();
00521 }
00522 }
00523 if (class->pseudofd > -1) {
00524 #ifdef SOLARIS
00525 thr_yield();
00526 #endif
00527
00528 res = read(class->pseudofd, buf, sizeof(buf));
00529 pthread_testcancel();
00530 } else {
00531 long delta;
00532
00533 tv_tmp = ast_tvnow();
00534 if (ast_tvzero(tv))
00535 tv = tv_tmp;
00536 delta = ast_tvdiff_ms(tv_tmp, tv);
00537 if (delta < MOH_MS_INTERVAL) {
00538 tv = ast_tvadd(tv, ast_samp2tv(MOH_MS_INTERVAL, 1000));
00539 usleep(1000 * (MOH_MS_INTERVAL - delta));
00540 pthread_testcancel();
00541 } else {
00542 ast_log(LOG_NOTICE, "Request to schedule in the past?!?!\n");
00543 tv = tv_tmp;
00544 }
00545 res = 8 * MOH_MS_INTERVAL;
00546 }
00547 if (AST_LIST_EMPTY(&class->members))
00548 continue;
00549
00550 len = ast_codec_get_len(class->format, res);
00551
00552 if ((res2 = read(class->srcfd, sbuf, len)) != len) {
00553 if (!res2) {
00554 close(class->srcfd);
00555 class->srcfd = -1;
00556 pthread_testcancel();
00557 if (class->pid > 1) {
00558 killpg(class->pid, SIGHUP);
00559 usleep(100000);
00560 killpg(class->pid, SIGTERM);
00561 usleep(100000);
00562 killpg(class->pid, SIGKILL);
00563 class->pid = 0;
00564 }
00565 } else
00566 ast_log(LOG_DEBUG, "Read %d bytes of audio while expecting %d\n", res2, len);
00567 continue;
00568 }
00569
00570 pthread_testcancel();
00571
00572 ao2_lock(class);
00573 AST_LIST_TRAVERSE(&class->members, moh, list) {
00574
00575 if ((res = write(moh->pipe[1], sbuf, res2)) != res2) {
00576 if (option_debug)
00577 ast_log(LOG_DEBUG, "Only wrote %d of %d bytes to pipe\n", res, res2);
00578 }
00579 }
00580 ao2_unlock(class);
00581 }
00582 return NULL;
00583 }
00584
00585 static int moh0_exec(struct ast_channel *chan, void *data)
00586 {
00587 if (ast_moh_start(chan, data, NULL)) {
00588 ast_log(LOG_WARNING, "Unable to start music on hold (class '%s') on channel %s\n", (char *)data, chan->name);
00589 return 0;
00590 }
00591 while (!ast_safe_sleep(chan, 10000));
00592 ast_moh_stop(chan);
00593 return -1;
00594 }
00595
00596 static int moh1_exec(struct ast_channel *chan, void *data)
00597 {
00598 int res;
00599 if (!data || !atoi(data)) {
00600 ast_log(LOG_WARNING, "WaitMusicOnHold requires an argument (number of seconds to wait)\n");
00601 return -1;
00602 }
00603 if (ast_moh_start(chan, NULL, NULL)) {
00604 ast_log(LOG_WARNING, "Unable to start music on hold for %d seconds on channel %s\n", atoi(data), chan->name);
00605 return 0;
00606 }
00607 res = ast_safe_sleep(chan, atoi(data) * 1000);
00608 ast_moh_stop(chan);
00609 return res;
00610 }
00611
00612 static int moh2_exec(struct ast_channel *chan, void *data)
00613 {
00614 if (ast_strlen_zero(data)) {
00615 ast_log(LOG_WARNING, "SetMusicOnHold requires an argument (class)\n");
00616 return -1;
00617 }
00618 ast_string_field_set(chan, musicclass, data);
00619 return 0;
00620 }
00621
00622 static int moh3_exec(struct ast_channel *chan, void *data)
00623 {
00624 char *class = NULL;
00625 if (data && strlen(data))
00626 class = data;
00627 if (ast_moh_start(chan, class, NULL))
00628 ast_log(LOG_NOTICE, "Unable to start music on hold class '%s' on channel %s\n", class ? class : "default", chan->name);
00629
00630 return 0;
00631 }
00632
00633 static int moh4_exec(struct ast_channel *chan, void *data)
00634 {
00635 ast_moh_stop(chan);
00636
00637 return 0;
00638 }
00639
00640 static struct mohclass *get_mohbyname(const char *name, int warn)
00641 {
00642 struct mohclass *moh = NULL;
00643 struct mohclass tmp_class = {
00644 .flags = 0,
00645 };
00646
00647 ast_copy_string(tmp_class.name, name, sizeof(tmp_class.name));
00648
00649 moh = ao2_find(mohclasses, &tmp_class, 0);
00650
00651 if (!moh && warn) {
00652 ast_log(LOG_WARNING, "Music on Hold class '%s' not found\n", name);
00653 }
00654
00655 return moh;
00656 }
00657
00658 static struct mohdata *mohalloc(struct mohclass *cl)
00659 {
00660 struct mohdata *moh;
00661 long flags;
00662
00663 if (!(moh = ast_calloc(1, sizeof(*moh))))
00664 return NULL;
00665
00666 if (pipe(moh->pipe)) {
00667 ast_log(LOG_WARNING, "Failed to create pipe: %s\n", strerror(errno));
00668 free(moh);
00669 return NULL;
00670 }
00671
00672
00673 flags = fcntl(moh->pipe[0], F_GETFL);
00674 fcntl(moh->pipe[0], F_SETFL, flags | O_NONBLOCK);
00675 flags = fcntl(moh->pipe[1], F_GETFL);
00676 fcntl(moh->pipe[1], F_SETFL, flags | O_NONBLOCK);
00677
00678 moh->f.frametype = AST_FRAME_VOICE;
00679 moh->f.subclass = cl->format;
00680 moh->f.offset = AST_FRIENDLY_OFFSET;
00681
00682 moh->parent = mohclass_ref(cl);
00683
00684 ao2_lock(cl);
00685 AST_LIST_INSERT_HEAD(&cl->members, moh, list);
00686 ao2_unlock(cl);
00687
00688 return moh;
00689 }
00690
00691 static void moh_release(struct ast_channel *chan, void *data)
00692 {
00693 struct mohdata *moh = data;
00694 struct mohclass *class = moh->parent;
00695 int oldwfmt;
00696
00697 ao2_lock(class);
00698 AST_LIST_REMOVE(&moh->parent->members, moh, list);
00699 ao2_unlock(class);
00700
00701 close(moh->pipe[0]);
00702 close(moh->pipe[1]);
00703
00704 oldwfmt = moh->origwfmt;
00705
00706 moh->parent = class = mohclass_unref(class);
00707
00708 free(moh);
00709
00710 if (chan) {
00711 if (oldwfmt && ast_set_write_format(chan, oldwfmt)) {
00712 ast_log(LOG_WARNING, "Unable to restore channel '%s' to format %s\n",
00713 chan->name, ast_getformatname(oldwfmt));
00714 }
00715 if (option_verbose > 2) {
00716 ast_verbose(VERBOSE_PREFIX_3 "Stopped music on hold on %s\n", chan->name);
00717 }
00718 }
00719 }
00720
00721 static void *moh_alloc(struct ast_channel *chan, void *params)
00722 {
00723 struct mohdata *res;
00724 struct mohclass *class = params;
00725
00726 if ((res = mohalloc(class))) {
00727 res->origwfmt = chan->writeformat;
00728 if (ast_set_write_format(chan, class->format)) {
00729 ast_log(LOG_WARNING, "Unable to set channel '%s' to format '%s'\n", chan->name, ast_codec2str(class->format));
00730 moh_release(NULL, res);
00731 res = NULL;
00732 }
00733 if (option_verbose > 2)
00734 ast_verbose(VERBOSE_PREFIX_3 "Started music on hold, class '%s', on channel '%s'\n", class->name, chan->name);
00735 }
00736 return res;
00737 }
00738
00739 static int moh_generate(struct ast_channel *chan, void *data, int len, int samples)
00740 {
00741 struct mohdata *moh = data;
00742 short buf[1280 + AST_FRIENDLY_OFFSET / 2];
00743 int res;
00744
00745 len = ast_codec_get_len(moh->parent->format, samples);
00746
00747 if (len > sizeof(buf) - AST_FRIENDLY_OFFSET) {
00748 ast_log(LOG_WARNING, "Only doing %d of %d requested bytes on %s\n", (int)sizeof(buf), len, chan->name);
00749 len = sizeof(buf) - AST_FRIENDLY_OFFSET;
00750 }
00751 res = read(moh->pipe[0], buf + AST_FRIENDLY_OFFSET/2, len);
00752 if (res <= 0)
00753 return 0;
00754
00755 moh->f.datalen = res;
00756 moh->f.data = buf + AST_FRIENDLY_OFFSET / 2;
00757 moh->f.samples = ast_codec_get_samples(&moh->f);
00758
00759 if (ast_write(chan, &moh->f) < 0) {
00760 ast_log(LOG_WARNING, "Failed to write frame to '%s': %s\n", chan->name, strerror(errno));
00761 return -1;
00762 }
00763
00764 return 0;
00765 }
00766
00767 static struct ast_generator mohgen = {
00768 .alloc = moh_alloc,
00769 .release = moh_release,
00770 .generate = moh_generate,
00771 };
00772
00773 static int moh_add_file(struct mohclass *class, const char *filepath)
00774 {
00775 if (!class->allowed_files) {
00776 if (!(class->filearray = ast_calloc(1, INITIAL_NUM_FILES * sizeof(*class->filearray))))
00777 return -1;
00778 class->allowed_files = INITIAL_NUM_FILES;
00779 } else if (class->total_files == class->allowed_files) {
00780 if (!(class->filearray = ast_realloc(class->filearray, class->allowed_files * sizeof(*class->filearray) * 2))) {
00781 class->allowed_files = 0;
00782 class->total_files = 0;
00783 return -1;
00784 }
00785 class->allowed_files *= 2;
00786 }
00787
00788 if (!(class->filearray[class->total_files] = ast_strdup(filepath)))
00789 return -1;
00790
00791 class->total_files++;
00792
00793 return 0;
00794 }
00795
00796 static int moh_scan_files(struct mohclass *class) {
00797
00798 DIR *files_DIR;
00799 struct dirent *files_dirent;
00800 char path[PATH_MAX];
00801 char filepath[PATH_MAX];
00802 char *ext;
00803 struct stat statbuf;
00804 int dirnamelen;
00805 int i;
00806
00807 files_DIR = opendir(class->dir);
00808 if (!files_DIR) {
00809 ast_log(LOG_WARNING, "Cannot open dir %s or dir does not exist\n", class->dir);
00810 return -1;
00811 }
00812
00813 for (i = 0; i < class->total_files; i++)
00814 free(class->filearray[i]);
00815
00816 class->total_files = 0;
00817 dirnamelen = strlen(class->dir) + 2;
00818 if (!getcwd(path, sizeof(path))) {
00819 ast_log(LOG_WARNING, "getcwd() failed: %s\n", strerror(errno));
00820 return -1;
00821 }
00822 if (chdir(class->dir) < 0) {
00823 ast_log(LOG_WARNING, "chdir() failed: %s\n", strerror(errno));
00824 return -1;
00825 }
00826 while ((files_dirent = readdir(files_DIR))) {
00827
00828 if ((strlen(files_dirent->d_name) < 4))
00829 continue;
00830
00831
00832 if (files_dirent->d_name[0] == '.')
00833 continue;
00834
00835
00836 if (!strchr(files_dirent->d_name, '.'))
00837 continue;
00838
00839 snprintf(filepath, sizeof(filepath), "%s/%s", class->dir, files_dirent->d_name);
00840
00841 if (stat(filepath, &statbuf))
00842 continue;
00843
00844 if (!S_ISREG(statbuf.st_mode))
00845 continue;
00846
00847 if ((ext = strrchr(filepath, '.'))) {
00848 *ext = '\0';
00849 ext++;
00850 }
00851
00852
00853 for (i = 0; i < class->total_files; i++)
00854 if (!strcmp(filepath, class->filearray[i]))
00855 break;
00856
00857 if (i == class->total_files) {
00858 if (moh_add_file(class, filepath))
00859 break;
00860 }
00861 }
00862
00863 closedir(files_DIR);
00864 if (chdir(path) < 0) {
00865 ast_log(LOG_WARNING, "chdir() failed: %s\n", strerror(errno));
00866 return -1;
00867 }
00868 return class->total_files;
00869 }
00870
00871 static int init_files_class(struct mohclass *class)
00872 {
00873 int res;
00874
00875 res = moh_scan_files(class);
00876
00877 if (res < 0) {
00878 return -1;
00879 }
00880
00881 if (!res) {
00882 if (option_verbose > 2) {
00883 ast_verbose(VERBOSE_PREFIX_3 "Files not found in %s for moh class:%s\n",
00884 class->dir, class->name);
00885 }
00886 return -1;
00887 }
00888
00889 if (strchr(class->args, 'r')) {
00890 ast_set_flag(class, MOH_RANDOMIZE);
00891 }
00892
00893 return 0;
00894 }
00895
00896 static int init_app_class(struct mohclass *class)
00897 {
00898 #ifdef HAVE_DAHDI
00899 int x;
00900 #endif
00901
00902 if (!strcasecmp(class->mode, "custom")) {
00903 ast_set_flag(class, MOH_CUSTOM);
00904 } else if (!strcasecmp(class->mode, "mp3nb")) {
00905 ast_set_flag(class, MOH_SINGLE);
00906 } else if (!strcasecmp(class->mode, "quietmp3nb")) {
00907 ast_set_flag(class, MOH_SINGLE | MOH_QUIET);
00908 } else if (!strcasecmp(class->mode, "quietmp3")) {
00909 ast_set_flag(class, MOH_QUIET);
00910 }
00911
00912 class->srcfd = -1;
00913 class->pseudofd = -1;
00914
00915 #ifdef HAVE_DAHDI
00916
00917
00918 class->pseudofd = open(DAHDI_FILE_PSEUDO, O_RDONLY);
00919 if (class->pseudofd < 0) {
00920 ast_log(LOG_WARNING, "Unable to open pseudo channel for timing... Sound may be choppy.\n");
00921 } else {
00922 x = 320;
00923 ioctl(class->pseudofd, DAHDI_SET_BLOCKSIZE, &x);
00924 }
00925 #endif
00926
00927 if (ast_pthread_create_background(&class->thread, NULL, monmp3thread, class)) {
00928 ast_log(LOG_WARNING, "Unable to create moh thread...\n");
00929 if (class->pseudofd > -1) {
00930 close(class->pseudofd);
00931 class->pseudofd = -1;
00932 }
00933 return -1;
00934 }
00935
00936 return 0;
00937 }
00938
00939
00940
00941
00942 static int moh_register(struct mohclass *moh, int reload)
00943 {
00944 struct mohclass *mohclass = NULL;
00945
00946 if ((mohclass = get_mohbyname(moh->name, 0))) {
00947 if (!mohclass->delete) {
00948 ast_log(LOG_WARNING, "Music on Hold class '%s' already exists\n", moh->name);
00949 mohclass = mohclass_unref(mohclass);
00950 moh = mohclass_unref(moh);
00951 return -1;
00952 }
00953 mohclass = mohclass_unref(mohclass);
00954 }
00955
00956 time(&moh->start);
00957 moh->start -= respawn_time;
00958
00959 if (!strcasecmp(moh->mode, "files")) {
00960 if (init_files_class(moh)) {
00961 moh = mohclass_unref(moh);
00962 return -1;
00963 }
00964 } else if (!strcasecmp(moh->mode, "mp3") || !strcasecmp(moh->mode, "mp3nb") ||
00965 !strcasecmp(moh->mode, "quietmp3") || !strcasecmp(moh->mode, "quietmp3nb") ||
00966 !strcasecmp(moh->mode, "httpmp3") || !strcasecmp(moh->mode, "custom")) {
00967 if (init_app_class(moh)) {
00968 moh = mohclass_unref(moh);
00969 return -1;
00970 }
00971 } else {
00972 ast_log(LOG_WARNING, "Don't know how to do a mode '%s' music on hold\n", moh->mode);
00973 moh = mohclass_unref(moh);
00974 return -1;
00975 }
00976
00977 ao2_link(mohclasses, moh);
00978
00979 moh = mohclass_unref(moh);
00980
00981 return 0;
00982 }
00983
00984 static void local_ast_moh_cleanup(struct ast_channel *chan)
00985 {
00986 if (chan->music_state) {
00987 free(chan->music_state);
00988 chan->music_state = NULL;
00989 }
00990 }
00991
00992 static int local_ast_moh_start(struct ast_channel *chan, const char *mclass, const char *interpclass)
00993 {
00994 struct mohclass *mohclass = NULL;
00995 int res;
00996
00997
00998
00999
01000
01001
01002
01003
01004
01005
01006
01007
01008 if (!ast_strlen_zero(chan->musicclass)) {
01009 mohclass = get_mohbyname(chan->musicclass, 1);
01010 }
01011 if (!mohclass && !ast_strlen_zero(mclass)) {
01012 mohclass = get_mohbyname(mclass, 1);
01013 }
01014 if (!mohclass && !ast_strlen_zero(interpclass)) {
01015 mohclass = get_mohbyname(interpclass, 1);
01016 }
01017 if (!mohclass) {
01018 mohclass = get_mohbyname("default", 1);
01019 }
01020
01021 if (!mohclass) {
01022 return -1;
01023 }
01024
01025 ast_set_flag(chan, AST_FLAG_MOH);
01026
01027 if (mohclass->total_files) {
01028 res = ast_activate_generator(chan, &moh_file_stream, mohclass);
01029 } else {
01030 res = ast_activate_generator(chan, &mohgen, mohclass);
01031 }
01032
01033 mohclass = mohclass_unref(mohclass);
01034
01035 return res;
01036 }
01037
01038 static void local_ast_moh_stop(struct ast_channel *chan)
01039 {
01040 ast_clear_flag(chan, AST_FLAG_MOH);
01041 ast_deactivate_generator(chan);
01042
01043 if (chan->music_state) {
01044 if (chan->stream) {
01045 ast_closestream(chan->stream);
01046 chan->stream = NULL;
01047 }
01048 }
01049 }
01050
01051 static void moh_class_destructor(void *obj)
01052 {
01053 struct mohclass *class = obj;
01054 struct mohdata *member;
01055
01056 if (option_debug) {
01057 ast_log(LOG_DEBUG, "Destroying MOH class '%s'\n", class->name);
01058 }
01059
01060 if (class->pid > 1) {
01061 char buff[8192];
01062 int bytes, tbytes = 0, stime = 0, pid = 0;
01063
01064 ast_log(LOG_DEBUG, "killing %d!\n", class->pid);
01065
01066 stime = time(NULL) + 2;
01067 pid = class->pid;
01068 class->pid = 0;
01069
01070
01071
01072
01073 killpg(pid, SIGHUP);
01074 usleep(100000);
01075 killpg(pid, SIGTERM);
01076 usleep(100000);
01077 killpg(pid, SIGKILL);
01078
01079 while ((ast_wait_for_input(class->srcfd, 100) > 0) &&
01080 (bytes = read(class->srcfd, buff, 8192)) && time(NULL) < stime) {
01081 tbytes = tbytes + bytes;
01082 }
01083
01084 ast_log(LOG_DEBUG, "mpg123 pid %d and child died after %d bytes read\n", pid, tbytes);
01085
01086 close(class->srcfd);
01087 }
01088
01089 while ((member = AST_LIST_REMOVE_HEAD(&class->members, list))) {
01090 free(member);
01091 }
01092
01093 if (class->thread) {
01094 pthread_cancel(class->thread);
01095 class->thread = AST_PTHREADT_NULL;
01096 }
01097
01098 if (class->filearray) {
01099 int i;
01100 for (i = 0; i < class->total_files; i++) {
01101 free(class->filearray[i]);
01102 }
01103 free(class->filearray);
01104 class->filearray = NULL;
01105 }
01106 }
01107
01108 static struct mohclass *moh_class_malloc(void)
01109 {
01110 struct mohclass *class;
01111
01112 if ((class = ao2_alloc(sizeof(*class), moh_class_destructor))) {
01113 class->format = AST_FORMAT_SLINEAR;
01114 }
01115
01116 return class;
01117 }
01118
01119 static int moh_class_mark(void *obj, void *arg, int flags)
01120 {
01121 struct mohclass *class = obj;
01122
01123 class->delete = 1;
01124
01125 return 0;
01126 }
01127
01128 static int moh_classes_delete_marked(void *obj, void *arg, int flags)
01129 {
01130 struct mohclass *class = obj;
01131
01132 return class->delete ? CMP_MATCH : 0;
01133 }
01134
01135 static int load_moh_classes(int reload)
01136 {
01137 struct ast_config *cfg;
01138 struct ast_variable *var;
01139 struct mohclass *class;
01140 char *data;
01141 char *args;
01142 char *cat;
01143 int numclasses = 0;
01144 static int dep_warning = 0;
01145
01146 cfg = ast_config_load("musiconhold.conf");
01147
01148 if (!cfg) {
01149 return 0;
01150 }
01151
01152 if (reload) {
01153 ao2_callback(mohclasses, OBJ_NODATA, moh_class_mark, NULL);
01154 }
01155
01156 cat = ast_category_browse(cfg, NULL);
01157 for (; cat; cat = ast_category_browse(cfg, cat)) {
01158 if (!strcasecmp(cat, "classes") || !strcasecmp(cat, "moh_files")) {
01159 continue;
01160 }
01161
01162 if (!(class = moh_class_malloc())) {
01163 break;
01164 }
01165
01166 ast_copy_string(class->name, cat, sizeof(class->name));
01167
01168 for (var = ast_variable_browse(cfg, cat); var; var = var->next) {
01169 if (!strcasecmp(var->name, "mode")) {
01170 ast_copy_string(class->mode, var->value, sizeof(class->mode));
01171 } else if (!strcasecmp(var->name, "directory")) {
01172 ast_copy_string(class->dir, var->value, sizeof(class->dir));
01173 } else if (!strcasecmp(var->name, "application")) {
01174 ast_copy_string(class->args, var->value, sizeof(class->args));
01175 } else if (!strcasecmp(var->name, "random")) {
01176 ast_set2_flag(class, ast_true(var->value), MOH_RANDOMIZE);
01177 } else if (!strcasecmp(var->name, "format")) {
01178 class->format = ast_getformatbyname(var->value);
01179 if (!class->format) {
01180 ast_log(LOG_WARNING, "Unknown format '%s' -- defaulting to SLIN\n", var->value);
01181 class->format = AST_FORMAT_SLINEAR;
01182 }
01183 }
01184 }
01185
01186 if (ast_strlen_zero(class->dir)) {
01187 if (!strcasecmp(class->mode, "custom")) {
01188 ast_copy_string(class->dir, "nodir", sizeof(class->dir));
01189 } else {
01190 ast_log(LOG_WARNING, "A directory must be specified for class '%s'!\n", class->name);
01191 class = mohclass_unref(class);
01192 continue;
01193 }
01194 }
01195
01196 if (ast_strlen_zero(class->mode)) {
01197 ast_log(LOG_WARNING, "A mode must be specified for class '%s'!\n", class->name);
01198 class = mohclass_unref(class);
01199 continue;
01200 }
01201
01202 if (ast_strlen_zero(class->args) && !strcasecmp(class->mode, "custom")) {
01203 ast_log(LOG_WARNING, "An application must be specified for class '%s'!\n", class->name);
01204 class = mohclass_unref(class);
01205 continue;
01206 }
01207
01208
01209 moh_register(class, reload);
01210
01211 numclasses++;
01212 }
01213
01214
01215
01216 for (var = ast_variable_browse(cfg, "classes"); var; var = var->next) {
01217 struct mohclass *tmp_class;
01218
01219 if (!dep_warning) {
01220 ast_log(LOG_WARNING, "The old musiconhold.conf syntax has been deprecated! Please refer to the sample configuration for information on the new syntax.\n");
01221 dep_warning = 1;
01222 }
01223
01224 if (!(data = strchr(var->value, ':'))) {
01225 continue;
01226 }
01227 *data++ = '\0';
01228
01229 if ((args = strchr(data, ','))) {
01230 *args++ = '\0';
01231 }
01232
01233 if ((tmp_class = get_mohbyname(var->name, 0))) {
01234 tmp_class = mohclass_unref(tmp_class);
01235 continue;
01236 }
01237
01238 if (!(class = moh_class_malloc())) {
01239 break;
01240 }
01241
01242 ast_copy_string(class->name, var->name, sizeof(class->name));
01243 ast_copy_string(class->dir, data, sizeof(class->dir));
01244 ast_copy_string(class->mode, var->value, sizeof(class->mode));
01245 if (args) {
01246 ast_copy_string(class->args, args, sizeof(class->args));
01247 }
01248
01249 moh_register(class, reload);
01250 class = NULL;
01251
01252 numclasses++;
01253 }
01254
01255 for (var = ast_variable_browse(cfg, "moh_files"); var; var = var->next) {
01256 struct mohclass *tmp_class;
01257
01258 if (!dep_warning) {
01259 ast_log(LOG_WARNING, "The old musiconhold.conf syntax has been deprecated! Please refer to the sample configuration for information on the new syntax.\n");
01260 dep_warning = 1;
01261 }
01262
01263 if ((tmp_class = get_mohbyname(var->name, 0))) {
01264 tmp_class = mohclass_unref(tmp_class);
01265 continue;
01266 }
01267
01268 if ((args = strchr(var->value, ','))) {
01269 *args++ = '\0';
01270 }
01271
01272 if (!(class = moh_class_malloc())) {
01273 break;
01274 }
01275
01276 ast_copy_string(class->name, var->name, sizeof(class->name));
01277 ast_copy_string(class->dir, var->value, sizeof(class->dir));
01278 ast_copy_string(class->mode, "files", sizeof(class->mode));
01279 if (args) {
01280 ast_copy_string(class->args, args, sizeof(class->args));
01281 }
01282
01283 moh_register(class, reload);
01284 class = NULL;
01285
01286 numclasses++;
01287 }
01288
01289 ast_config_destroy(cfg);
01290
01291 ao2_callback(mohclasses, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE,
01292 moh_classes_delete_marked, NULL);
01293
01294 return numclasses;
01295 }
01296
01297 static void ast_moh_destroy(void)
01298 {
01299 if (option_verbose > 1) {
01300 ast_verbose(VERBOSE_PREFIX_2 "Destroying musiconhold processes\n");
01301 }
01302
01303 ao2_callback(mohclasses, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, NULL, NULL);
01304 }
01305
01306 static int moh_cli(int fd, int argc, char *argv[])
01307 {
01308 reload();
01309 return 0;
01310 }
01311
01312 static int cli_files_show(int fd, int argc, char *argv[])
01313 {
01314 struct mohclass *class;
01315 struct ao2_iterator i;
01316
01317 i = ao2_iterator_init(mohclasses, 0);
01318
01319 for (; (class = ao2_iterator_next(&i)); mohclass_unref(class)) {
01320 int x;
01321
01322 if (!class->total_files) {
01323 continue;
01324 }
01325
01326 ast_cli(fd, "Class: %s\n", class->name);
01327
01328 for (x = 0; x < class->total_files; x++) {
01329 ast_cli(fd, "\tFile: %s\n", class->filearray[x]);
01330 }
01331 }
01332
01333 return 0;
01334 }
01335
01336 static int moh_classes_show(int fd, int argc, char *argv[])
01337 {
01338 struct mohclass *class;
01339 struct ao2_iterator i;
01340
01341 i = ao2_iterator_init(mohclasses, 0);
01342
01343 for (; (class = ao2_iterator_next(&i)); mohclass_unref(class)) {
01344 ast_cli(fd, "Class: %s\n", class->name);
01345 ast_cli(fd, "\tMode: %s\n", S_OR(class->mode, "<none>"));
01346 ast_cli(fd, "\tDirectory: %s\n", S_OR(class->dir, "<none>"));
01347 if (ast_test_flag(class, MOH_CUSTOM)) {
01348 ast_cli(fd, "\tApplication: %s\n", S_OR(class->args, "<none>"));
01349 }
01350 if (strcasecmp(class->mode, "files")) {
01351 ast_cli(fd, "\tFormat: %s\n", ast_getformatname(class->format));
01352 }
01353 }
01354
01355 return 0;
01356 }
01357
01358 static struct ast_cli_entry cli_moh_classes_show_deprecated = {
01359 { "moh", "classes", "show"},
01360 moh_classes_show, NULL,
01361 NULL };
01362
01363 static struct ast_cli_entry cli_moh_files_show_deprecated = {
01364 { "moh", "files", "show"},
01365 cli_files_show, NULL,
01366 NULL };
01367
01368 static struct ast_cli_entry cli_moh[] = {
01369 { { "moh", "reload"},
01370 moh_cli, "Music On Hold",
01371 "Usage: moh reload\n Rereads configuration\n" },
01372
01373 { { "moh", "show", "classes"},
01374 moh_classes_show, "List MOH classes",
01375 "Usage: moh show classes\n Lists all MOH classes\n", NULL, &cli_moh_classes_show_deprecated },
01376
01377 { { "moh", "show", "files"},
01378 cli_files_show, "List MOH file-based classes",
01379 "Usage: moh show files\n Lists all loaded file-based MOH classes and their files\n", NULL, &cli_moh_files_show_deprecated },
01380 };
01381
01382 static int moh_class_hash(const void *obj, const int flags)
01383 {
01384 const struct mohclass *class = obj;
01385
01386 return ast_str_case_hash(class->name);
01387 }
01388
01389 static int moh_class_cmp(void *obj, void *arg, int flags)
01390 {
01391 struct mohclass *class = obj, *class2 = arg;
01392
01393 return strcasecmp(class->name, class2->name) ? 0 : CMP_MATCH | CMP_STOP;
01394 }
01395
01396 static int load_module(void)
01397 {
01398 int res;
01399
01400 if (!(mohclasses = ao2_container_alloc(53, moh_class_hash, moh_class_cmp))) {
01401 return AST_MODULE_LOAD_DECLINE;
01402 }
01403
01404 if (!load_moh_classes(0)) {
01405 ast_log(LOG_WARNING, "No music on hold classes configured, "
01406 "disabling music on hold.\n");
01407 } else {
01408 ast_install_music_functions(local_ast_moh_start, local_ast_moh_stop,
01409 local_ast_moh_cleanup);
01410 }
01411
01412 res = ast_register_application(app0, moh0_exec, synopsis0, descrip0);
01413 ast_register_atexit(ast_moh_destroy);
01414 ast_cli_register_multiple(cli_moh, ARRAY_LEN(cli_moh));
01415 if (!res)
01416 res = ast_register_application(app1, moh1_exec, synopsis1, descrip1);
01417 if (!res)
01418 res = ast_register_application(app2, moh2_exec, synopsis2, descrip2);
01419 if (!res)
01420 res = ast_register_application(app3, moh3_exec, synopsis3, descrip3);
01421 if (!res)
01422 res = ast_register_application(app4, moh4_exec, synopsis4, descrip4);
01423
01424 return AST_MODULE_LOAD_SUCCESS;
01425 }
01426
01427 static int reload(void)
01428 {
01429 if (load_moh_classes(1)) {
01430 ast_install_music_functions(local_ast_moh_start, local_ast_moh_stop,
01431 local_ast_moh_cleanup);
01432 }
01433
01434 return 0;
01435 }
01436
01437 static int moh_class_inuse(void *obj, void *arg, int flags)
01438 {
01439 struct mohclass *class = obj;
01440
01441 return AST_LIST_EMPTY(&class->members) ? 0 : CMP_MATCH | CMP_STOP;
01442 }
01443
01444 static int unload_module(void)
01445 {
01446 int res = 0;
01447 struct mohclass *class = NULL;
01448
01449
01450
01451 if ((class = ao2_callback(mohclasses, 0, moh_class_inuse, NULL))) {
01452 class = mohclass_unref(class);
01453 res = -1;
01454 }
01455
01456 if (res < 0) {
01457 ast_log(LOG_WARNING, "Unable to unload res_musiconhold due to active MOH channels\n");
01458 return res;
01459 }
01460
01461 ast_uninstall_music_functions();
01462
01463 ast_moh_destroy();
01464
01465 res = ast_unregister_application(app0);
01466 res |= ast_unregister_application(app1);
01467 res |= ast_unregister_application(app2);
01468 res |= ast_unregister_application(app3);
01469 res |= ast_unregister_application(app4);
01470
01471 ast_cli_unregister_multiple(cli_moh, ARRAY_LEN(cli_moh));
01472
01473 return res;
01474 }
01475
01476 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Music On Hold Resource",
01477 .load = load_module,
01478 .unload = unload_module,
01479 .reload = reload,
01480 );