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