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