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