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