Mon Sep 20 2010 00:23:41

Asterisk developer's documentation


res_musiconhold.c

Go to the documentation of this file.
00001 /*
00002  * Asterisk -- An open source telephony toolkit.
00003  *
00004  * Copyright (C) 1999 - 2006, Digium, Inc.
00005  *
00006  * Mark Spencer <markster@digium.com>
00007  *
00008  * See http://www.asterisk.org for more information about
00009  * the Asterisk project. Please do not directly contact
00010  * any of the maintainers of this project for assistance;
00011  * the project provides a web site, mailing lists and IRC
00012  * channels for your use.
00013  *
00014  * This program is free software, distributed under the terms of
00015  * the GNU General Public License Version 2. See the LICENSE file
00016  * at the top of the source tree.
00017  */
00018 
00019 /*! \file
00020  *
00021  * \brief Routines implementing music on hold
00022  *
00023  * \arg See also \ref Config_moh
00024  * 
00025  * \author Mark Spencer <markster@digium.com>
00026  */
00027 
00028 /*** MODULEINFO
00029    <conflict>win32</conflict>
00030    <use>dahdi</use>
00031  ***/
00032 
00033 #include "asterisk.h"
00034 
00035 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 260373 $")
00036 
00037 #include <ctype.h>
00038 #include <signal.h>
00039 #include <sys/time.h>
00040 #include <sys/signal.h>
00041 #include <netinet/in.h>
00042 #include <sys/stat.h>
00043 #include <dirent.h>
00044 #include <sys/ioctl.h>
00045 #ifdef SOLARIS
00046 #include <thread.h>
00047 #endif
00048 
00049 #ifdef SOLARIS
00050 #include <thread.h>
00051 #endif
00052 
00053 #ifdef HAVE_DAHDI
00054 #include <dahdi/user.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/manager.h"
00072 #include "asterisk/paths.h"
00073 #include "asterisk/astobj2.h"
00074 
00075 #define INITIAL_NUM_FILES   8
00076 #define HANDLE_REF   1
00077 #define DONT_UNREF   0
00078 
00079 static char *play_moh = "MusicOnHold";
00080 static char *wait_moh = "WaitMusicOnHold";
00081 static char *set_moh = "SetMusicOnHold";
00082 static char *start_moh = "StartMusicOnHold";
00083 static char *stop_moh = "StopMusicOnHold";
00084 
00085 static char *play_moh_syn = "Play Music On Hold indefinitely";
00086 static char *wait_moh_syn = "Wait, playing Music On Hold";
00087 static char *set_moh_syn = "Set default Music On Hold class";
00088 static char *start_moh_syn = "Play Music On Hold";
00089 static char *stop_moh_syn = "Stop Playing Music On Hold";
00090 
00091 static char *play_moh_desc = "  MusicOnHold(class[,duration]):\n"
00092 "Plays hold music specified by class.  If omitted, the default\n"
00093 "music source for the channel will be used. Change the default \n"
00094 "class with Set(CHANNEL(musicclass)=...).\n"
00095 "If duration is given, hold music will be played specified number\n"
00096 "of seconds. If duration is ommited, music plays indefinitely.\n"
00097 "Returns 0 when done, -1 on hangup.\n";
00098 
00099 static char *wait_moh_desc = "  WaitMusicOnHold(delay):\n"
00100 "\n"
00101 "  !!! DEPRECATED. Use MusicOnHold instead !!!\n"
00102 "\n"
00103 "Plays hold music specified number of seconds.  Returns 0 when\n"
00104 "done, or -1 on hangup.  If no hold music is available, the delay will\n"
00105 "still occur with no sound.\n"
00106 "\n"
00107 "  !!! DEPRECATED. Use MusicOnHold instead !!!\n";
00108 
00109 static char *set_moh_desc = "  SetMusicOnHold(class):\n"
00110 "\n"
00111 "  !!! DEPRECATED. USe Set(CHANNEL(musicclass)=...) instead !!!\n"
00112 "\n"
00113 "Sets the default class for music on hold for a given channel.  When\n"
00114 "music on hold is activated, this class will be used to select which\n"
00115 "music is played.\n"
00116 "\n"
00117 "  !!! DEPRECATED. USe Set(CHANNEL(musicclass)=...) instead !!!\n";
00118 
00119 static char *start_moh_desc = "  StartMusicOnHold(class):\n"
00120 "Starts playing music on hold, uses default music class for channel.\n"
00121 "Starts playing music specified by class.  If omitted, the default\n"
00122 "music source for the channel will be used.  Always returns 0.\n";
00123 
00124 static char *stop_moh_desc = "  StopMusicOnHold(): "
00125 "Stops playing music on hold.\n";
00126 
00127 static int respawn_time = 20;
00128 
00129 struct moh_files_state {
00130    struct mohclass *class;
00131    char name[MAX_MUSICCLASS];
00132    int origwfmt;
00133    int samples;
00134    int sample_queue;
00135    int pos;
00136    int save_pos;
00137    int save_total;
00138    char *save_pos_filename;
00139 };
00140 
00141 #define MOH_QUIET    (1 << 0)
00142 #define MOH_SINGLE      (1 << 1)
00143 #define MOH_CUSTOM      (1 << 2)
00144 #define MOH_RANDOMIZE      (1 << 3)
00145 #define MOH_SORTALPHA      (1 << 4)
00146 
00147 #define MOH_CACHERTCLASSES      (1 << 5)        /*!< Should we use a separate instance of MOH for each user or not */
00148 
00149 /* Custom astobj2 flag */
00150 #define MOH_NOTDELETED          (1 << 30)       /*!< Find only records that aren't deleted? */
00151 
00152 static struct ast_flags global_flags[1] = {{0}};        /*!< global MOH_ flags */
00153 
00154 struct mohclass {
00155    char name[MAX_MUSICCLASS];
00156    char dir[256];
00157    char args[256];
00158    char mode[80];
00159    char digit;
00160    /*! A dynamically sized array to hold the list of filenames in "files" mode */
00161    char **filearray;
00162    /*! The current size of the filearray */
00163    int allowed_files;
00164    /*! The current number of files loaded into the filearray */
00165    int total_files;
00166    unsigned int flags;
00167    /*! The format from the MOH source, not applicable to "files" mode */
00168    int format;
00169    /*! The pid of the external application delivering MOH */
00170    int pid;
00171    time_t start;
00172    pthread_t thread;
00173    /*! Source of audio */
00174    int srcfd;
00175    /*! FD for timing source */
00176    int pseudofd;
00177    /*! Created on the fly, from RT engine */
00178    int realtime;
00179    unsigned int delete:1;
00180    AST_LIST_HEAD_NOLOCK(, mohdata) members;
00181    AST_LIST_ENTRY(mohclass) list;
00182 };
00183 
00184 struct mohdata {
00185    int pipe[2];
00186    int origwfmt;
00187    struct mohclass *parent;
00188    struct ast_frame f;
00189    AST_LIST_ENTRY(mohdata) list;
00190 };
00191 
00192 static struct ao2_container *mohclasses;
00193 
00194 #define LOCAL_MPG_123 "/usr/local/bin/mpg123"
00195 #define MPG_123 "/usr/bin/mpg123"
00196 #define MAX_MP3S 256
00197 
00198 static int reload(void);
00199 
00200 #define mohclass_ref(class,string)   (ao2_t_ref((class), +1, (string)), class)
00201 
00202 #ifndef REF_DEBUG
00203 #define mohclass_unref(class,string) (ao2_t_ref((class), -1, (string)), (struct mohclass *) NULL)
00204 #else
00205 #define mohclass_unref(class,string) _mohclass_unref(class, string, __FILE__,__LINE__,__PRETTY_FUNCTION__)
00206 static struct mohclass *_mohclass_unref(struct mohclass *class, const char *tag, const char *file, int line, const char *funcname)
00207 {
00208    struct mohclass *dup;
00209    if ((dup = ao2_find(mohclasses, class, OBJ_POINTER))) {
00210       if (_ao2_ref_debug(dup, -1, (char *) tag, (char *) file, line, funcname) == 2) {
00211          FILE *ref = fopen("/tmp/refs", "a");
00212          if (ref) {
00213             fprintf(ref, "%p =1   %s:%d:%s (%s) BAD ATTEMPT!\n", class, file, line, funcname, tag);
00214             fclose(ref);
00215          }
00216          ast_log(LOG_WARNING, "Attempt to unref mohclass %p (%s) when only 1 ref remained, and class is still in a container! (at %s:%d (%s))\n",
00217             class, class->name, file, line, funcname);
00218       } else {
00219          ao2_ref(class, -1);
00220       }
00221    } else {
00222       ao2_t_ref(class, -1, (char *) tag);
00223    }
00224    return NULL;
00225 }
00226 #endif
00227 
00228 static void moh_files_release(struct ast_channel *chan, void *data)
00229 {
00230    struct moh_files_state *state;
00231 
00232    if (!chan || !chan->music_state) {
00233       return;
00234    }
00235 
00236    state = chan->music_state;
00237 
00238    if (chan->stream) {
00239       ast_closestream(chan->stream);
00240       chan->stream = NULL;
00241    }
00242    
00243    if (option_verbose > 2) {
00244       ast_verbose(VERBOSE_PREFIX_3 "Stopped music on hold on %s\n", chan->name);
00245    }
00246 
00247    if (state->origwfmt && ast_set_write_format(chan, state->origwfmt)) {
00248       ast_log(LOG_WARNING, "Unable to restore channel '%s' to format '%d'\n", chan->name, state->origwfmt);
00249    }
00250 
00251    state->save_pos = state->pos;
00252 
00253    state->class = mohclass_unref(state->class, "Unreffing channel's music class upon deactivation of generator");
00254 }
00255 
00256 static int ast_moh_files_next(struct ast_channel *chan) 
00257 {
00258    struct moh_files_state *state = chan->music_state;
00259    int tries;
00260 
00261    /* Discontinue a stream if it is running already */
00262    if (chan->stream) {
00263       ast_closestream(chan->stream);
00264       chan->stream = NULL;
00265    }
00266 
00267    if (!state->class->total_files) {
00268       ast_log(LOG_WARNING, "No files available for class '%s'\n", state->class->name);
00269       return -1;
00270    }
00271 
00272    /* If a specific file has been saved confirm it still exists and that it is still valid */
00273    if (state->save_pos >= 0 && state->save_pos < state->class->total_files && state->class->filearray[state->save_pos] == state->save_pos_filename) {
00274       state->pos = state->save_pos;
00275       state->save_pos = -1;
00276    } else if (ast_test_flag(state->class, MOH_RANDOMIZE)) {
00277       /* Get a random file and ensure we can open it */
00278       for (tries = 0; tries < 20; tries++) {
00279          state->pos = ast_random() % state->class->total_files;
00280          if (ast_fileexists(state->class->filearray[state->pos], NULL, NULL) > 0)
00281             break;
00282       }
00283       state->save_pos = -1;
00284       state->samples = 0;
00285    } else {
00286       /* This is easy, just increment our position and make sure we don't exceed the total file count */
00287       state->pos++;
00288       state->pos %= state->class->total_files;
00289       state->save_pos = -1;
00290       state->samples = 0;
00291    }
00292 
00293    if (!ast_openstream_full(chan, state->class->filearray[state->pos], chan->language, 1)) {
00294       ast_log(LOG_WARNING, "Unable to open file '%s': %s\n", state->class->filearray[state->pos], strerror(errno));
00295       state->pos++;
00296       state->pos %= state->class->total_files;
00297       return -1;
00298    }
00299 
00300    /* Record the pointer to the filename for position resuming later */
00301    state->save_pos_filename = state->class->filearray[state->pos];
00302 
00303    ast_debug(1, "%s Opened file %d '%s'\n", chan->name, state->pos, state->class->filearray[state->pos]);
00304 
00305    if (state->samples)
00306       ast_seekstream(chan->stream, state->samples, SEEK_SET);
00307 
00308    return 0;
00309 }
00310 
00311 static struct ast_frame *moh_files_readframe(struct ast_channel *chan) 
00312 {
00313    struct ast_frame *f = NULL;
00314    
00315    if (!(chan->stream && (f = ast_readframe(chan->stream)))) {
00316       if (!ast_moh_files_next(chan))
00317          f = ast_readframe(chan->stream);
00318    }
00319 
00320    return f;
00321 }
00322 
00323 static int moh_files_generator(struct ast_channel *chan, void *data, int len, int samples)
00324 {
00325    struct moh_files_state *state = chan->music_state;
00326    struct ast_frame *f = NULL;
00327    int res = 0;
00328 
00329    state->sample_queue += samples;
00330 
00331    while (state->sample_queue > 0) {
00332       ast_channel_lock(chan);
00333       if ((f = moh_files_readframe(chan))) {
00334          /* We need to be sure that we unlock
00335           * the channel prior to calling
00336           * ast_write. Otherwise, the recursive locking
00337           * that occurs can cause deadlocks when using
00338           * indirect channels, like local channels
00339           */
00340          ast_channel_unlock(chan);
00341          state->samples += f->samples;
00342          state->sample_queue -= f->samples;
00343          res = ast_write(chan, f);
00344          ast_frfree(f);
00345          if (res < 0) {
00346             ast_log(LOG_WARNING, "Failed to write frame to '%s': %s\n", chan->name, strerror(errno));
00347             return -1;
00348          }
00349       } else {
00350          ast_channel_unlock(chan);
00351          return -1;  
00352       }
00353    }
00354    return res;
00355 }
00356 
00357 static void *moh_files_alloc(struct ast_channel *chan, void *params)
00358 {
00359    struct moh_files_state *state;
00360    struct mohclass *class = params;
00361 
00362    if (!chan->music_state && (state = ast_calloc(1, sizeof(*state)))) {
00363       chan->music_state = state;
00364       ast_module_ref(ast_module_info->self);
00365    } else {
00366       state = chan->music_state;
00367    }
00368 
00369    if (!state) {
00370       return NULL;
00371    }
00372 
00373    /* LOGIC: Comparing an unrefcounted pointer is a really bad idea, because
00374     * malloc may allocate a different class to the same memory block.  This
00375     * might only happen when two reloads are generated in a short period of
00376     * time, but it's still important to protect against.
00377     * PROG: Compare the quick operation first, to save CPU. */
00378    if (state->save_total != class->total_files || strcmp(state->name, class->name) != 0) {
00379       memset(state, 0, sizeof(*state));
00380       if (ast_test_flag(class, MOH_RANDOMIZE) && class->total_files) {
00381          state->pos = ast_random() % class->total_files;
00382       }
00383    }
00384 
00385    state->class = mohclass_ref(class, "Reffing music class for channel");
00386    state->origwfmt = chan->writeformat;
00387    /* For comparison on restart of MOH (see above) */
00388    ast_copy_string(state->name, class->name, sizeof(state->name));
00389    state->save_total = class->total_files;
00390 
00391    ast_verb(3, "Started music on hold, class '%s', on %s\n", class->name, chan->name);
00392    
00393    return chan->music_state;
00394 }
00395 
00396 static int moh_digit_match(void *obj, void *arg, int flags)
00397 {
00398    char *digit = arg;
00399    struct mohclass *class = obj;
00400 
00401    return (*digit == class->digit) ? CMP_MATCH | CMP_STOP : 0;
00402 }
00403 
00404 /*! \note This function should be called with the mohclasses list locked */
00405 static struct mohclass *get_mohbydigit(char digit)
00406 {
00407    return ao2_t_callback(mohclasses, 0, moh_digit_match, &digit, "digit callback");
00408 }
00409 
00410 static void moh_handle_digit(struct ast_channel *chan, char digit)
00411 {
00412    struct mohclass *class;
00413    const char *classname = NULL;
00414 
00415    if ((class = get_mohbydigit(digit))) {
00416       classname = ast_strdupa(class->name);
00417       class = mohclass_unref(class, "Unreffing ao2_find from finding by digit");
00418       ast_string_field_set(chan,musicclass,classname);
00419       ast_moh_stop(chan);
00420       ast_moh_start(chan, classname, NULL);
00421    }
00422 }
00423 
00424 static struct ast_generator moh_file_stream = 
00425 {
00426    .alloc    = moh_files_alloc,
00427    .release  = moh_files_release,
00428    .generate = moh_files_generator,
00429    .digit    = moh_handle_digit,
00430 };
00431 
00432 static int spawn_mp3(struct mohclass *class)
00433 {
00434    int fds[2];
00435    int files = 0;
00436    char fns[MAX_MP3S][80];
00437    char *argv[MAX_MP3S + 50];
00438    char xargs[256];
00439    char *argptr;
00440    int argc = 0;
00441    DIR *dir = NULL;
00442    struct dirent *de;
00443 
00444    
00445    if (!strcasecmp(class->dir, "nodir")) {
00446       files = 1;
00447    } else {
00448       dir = opendir(class->dir);
00449       if (!dir && strncasecmp(class->dir, "http://", 7)) {
00450          ast_log(LOG_WARNING, "%s is not a valid directory\n", class->dir);
00451          return -1;
00452       }
00453    }
00454 
00455    if (!ast_test_flag(class, MOH_CUSTOM)) {
00456       argv[argc++] = "mpg123";
00457       argv[argc++] = "-q";
00458       argv[argc++] = "-s";
00459       argv[argc++] = "--mono";
00460       argv[argc++] = "-r";
00461       argv[argc++] = "8000";
00462       
00463       if (!ast_test_flag(class, MOH_SINGLE)) {
00464          argv[argc++] = "-b";
00465          argv[argc++] = "2048";
00466       }
00467       
00468       argv[argc++] = "-f";
00469       
00470       if (ast_test_flag(class, MOH_QUIET))
00471          argv[argc++] = "4096";
00472       else
00473          argv[argc++] = "8192";
00474       
00475       /* Look for extra arguments and add them to the list */
00476       ast_copy_string(xargs, class->args, sizeof(xargs));
00477       argptr = xargs;
00478       while (!ast_strlen_zero(argptr)) {
00479          argv[argc++] = argptr;
00480          strsep(&argptr, ",");
00481       }
00482    } else  {
00483       /* Format arguments for argv vector */
00484       ast_copy_string(xargs, class->args, sizeof(xargs));
00485       argptr = xargs;
00486       while (!ast_strlen_zero(argptr)) {
00487          argv[argc++] = argptr;
00488          strsep(&argptr, " ");
00489       }
00490    }
00491 
00492    if (!strncasecmp(class->dir, "http://", 7)) {
00493       ast_copy_string(fns[files], class->dir, sizeof(fns[files]));
00494       argv[argc++] = fns[files];
00495       files++;
00496    } else if (dir) {
00497       while ((de = readdir(dir)) && (files < MAX_MP3S)) {
00498          if ((strlen(de->d_name) > 3) && 
00499              ((ast_test_flag(class, MOH_CUSTOM) && 
00500                (!strcasecmp(de->d_name + strlen(de->d_name) - 4, ".raw") || 
00501                 !strcasecmp(de->d_name + strlen(de->d_name) - 4, ".sln"))) ||
00502               !strcasecmp(de->d_name + strlen(de->d_name) - 4, ".mp3"))) {
00503             ast_copy_string(fns[files], de->d_name, sizeof(fns[files]));
00504             argv[argc++] = fns[files];
00505             files++;
00506          }
00507       }
00508    }
00509    argv[argc] = NULL;
00510    if (dir) {
00511       closedir(dir);
00512    }
00513    if (pipe(fds)) {  
00514       ast_log(LOG_WARNING, "Pipe failed\n");
00515       return -1;
00516    }
00517    if (!files) {
00518       ast_log(LOG_WARNING, "Found no files in '%s'\n", class->dir);
00519       close(fds[0]);
00520       close(fds[1]);
00521       return -1;
00522    }
00523    if (!strncasecmp(class->dir, "http://", 7) && time(NULL) - class->start < respawn_time) {
00524       sleep(respawn_time - (time(NULL) - class->start));
00525    }
00526 
00527    time(&class->start);
00528    class->pid = ast_safe_fork(0);
00529    if (class->pid < 0) {
00530       close(fds[0]);
00531       close(fds[1]);
00532       ast_log(LOG_WARNING, "Fork failed: %s\n", strerror(errno));
00533       return -1;
00534    }
00535    if (!class->pid) {
00536       if (ast_opt_high_priority)
00537          ast_set_priority(0);
00538 
00539       close(fds[0]);
00540       /* Stdout goes to pipe */
00541       dup2(fds[1], STDOUT_FILENO);
00542 
00543       /* Close everything else */
00544       ast_close_fds_above_n(STDERR_FILENO);
00545 
00546       /* Child */
00547       if (strncasecmp(class->dir, "http://", 7) && strcasecmp(class->dir, "nodir") && chdir(class->dir) < 0) {
00548          ast_log(LOG_WARNING, "chdir() failed: %s\n", strerror(errno));
00549          _exit(1);
00550       }
00551       setpgid(0, getpid());
00552       if (ast_test_flag(class, MOH_CUSTOM)) {
00553          execv(argv[0], argv);
00554       } else {
00555          /* Default install is /usr/local/bin */
00556          execv(LOCAL_MPG_123, argv);
00557          /* Many places have it in /usr/bin */
00558          execv(MPG_123, argv);
00559          /* Check PATH as a last-ditch effort */
00560          execvp("mpg123", argv);
00561       }
00562       /* Can't use logger, since log FDs are closed */
00563       fprintf(stderr, "MOH: exec failed: %s\n", strerror(errno));
00564       close(fds[1]);
00565       _exit(1);
00566    } else {
00567       /* Parent */
00568       close(fds[1]);
00569    }
00570    return fds[0];
00571 }
00572 
00573 static void *monmp3thread(void *data)
00574 {
00575 #define  MOH_MS_INTERVAL      100
00576 
00577    struct mohclass *class = data;
00578    struct mohdata *moh;
00579    char buf[8192];
00580    short sbuf[8192];
00581    int res, res2;
00582    int len;
00583    struct timeval deadline, tv_tmp;
00584 
00585    deadline.tv_sec = 0;
00586    deadline.tv_usec = 0;
00587    for(;/* ever */;) {
00588       pthread_testcancel();
00589       /* Spawn mp3 player if it's not there */
00590       if (class->srcfd < 0) {
00591          if ((class->srcfd = spawn_mp3(class)) < 0) {
00592             ast_log(LOG_WARNING, "Unable to spawn mp3player\n");
00593             /* Try again later */
00594             sleep(500);
00595             pthread_testcancel();
00596          }
00597       }
00598       if (class->pseudofd > -1) {
00599 #ifdef SOLARIS
00600          thr_yield();
00601 #endif
00602          /* Pause some amount of time */
00603          res = read(class->pseudofd, buf, sizeof(buf));
00604          pthread_testcancel();
00605       } else {
00606          long delta;
00607          /* Reliable sleep */
00608          tv_tmp = ast_tvnow();
00609          if (ast_tvzero(deadline))
00610             deadline = tv_tmp;
00611          delta = ast_tvdiff_ms(tv_tmp, deadline);
00612          if (delta < MOH_MS_INTERVAL) {   /* too early */
00613             deadline = ast_tvadd(deadline, ast_samp2tv(MOH_MS_INTERVAL, 1000));  /* next deadline */
00614             usleep(1000 * (MOH_MS_INTERVAL - delta));
00615             pthread_testcancel();
00616          } else {
00617             ast_log(LOG_NOTICE, "Request to schedule in the past?!?!\n");
00618             deadline = tv_tmp;
00619          }
00620          res = 8 * MOH_MS_INTERVAL; /* 8 samples per millisecond */
00621       }
00622       if ((strncasecmp(class->dir, "http://", 7) && strcasecmp(class->dir, "nodir")) && AST_LIST_EMPTY(&class->members))
00623          continue;
00624       /* Read mp3 audio */
00625       len = ast_codec_get_len(class->format, res);
00626 
00627       if ((res2 = read(class->srcfd, sbuf, len)) != len) {
00628          if (!res2) {
00629             close(class->srcfd);
00630             class->srcfd = -1;
00631             pthread_testcancel();
00632             if (class->pid > 1) {
00633                do {
00634                   if (killpg(class->pid, SIGHUP) < 0) {
00635                      if (errno == ESRCH) {
00636                         break;
00637                      }
00638                      ast_log(LOG_WARNING, "Unable to send a SIGHUP to MOH process?!!: %s\n", strerror(errno));
00639                   }
00640                   usleep(100000);
00641                   if (killpg(class->pid, SIGTERM) < 0) {
00642                      if (errno == ESRCH) {
00643                         break;
00644                      }
00645                      ast_log(LOG_WARNING, "Unable to terminate MOH process?!!: %s\n", strerror(errno));
00646                   }
00647                   usleep(100000);
00648                   if (killpg(class->pid, SIGKILL) < 0) {
00649                      if (errno == ESRCH) {
00650                         break;
00651                      }
00652                      ast_log(LOG_WARNING, "Unable to kill MOH process?!!: %s\n", strerror(errno));
00653                   }
00654                } while (0);
00655                class->pid = 0;
00656             }
00657          } else {
00658             ast_debug(1, "Read %d bytes of audio while expecting %d\n", res2, len);
00659          }
00660          continue;
00661       }
00662 
00663       pthread_testcancel();
00664 
00665       ao2_lock(class);
00666       AST_LIST_TRAVERSE(&class->members, moh, list) {
00667          /* Write data */
00668          if ((res = write(moh->pipe[1], sbuf, res2)) != res2) {
00669             ast_debug(1, "Only wrote %d of %d bytes to pipe\n", res, res2);
00670          }
00671       }
00672       ao2_unlock(class);
00673    }
00674    return NULL;
00675 }
00676 
00677 static int play_moh_exec(struct ast_channel *chan, void *data)
00678 {
00679    char *parse;
00680    char *class;
00681    int timeout = -1;
00682    int res;
00683    AST_DECLARE_APP_ARGS(args,
00684       AST_APP_ARG(class);
00685       AST_APP_ARG(duration);
00686    );
00687 
00688    parse = ast_strdupa(data);
00689 
00690    AST_STANDARD_APP_ARGS(args, parse);
00691 
00692    if (!ast_strlen_zero(args.duration)) {
00693       if (sscanf(args.duration, "%30d", &timeout) == 1) {
00694          timeout *= 1000;
00695       } else {
00696          ast_log(LOG_WARNING, "Invalid MusicOnHold duration '%s'. Will wait indefinitely.\n", args.duration);
00697       }
00698    }
00699 
00700    class = S_OR(args.class, NULL);
00701    if (ast_moh_start(chan, class, NULL)) {
00702       ast_log(LOG_WARNING, "Unable to start music on hold class '%s' on channel %s\n", class, chan->name);
00703       return 0;
00704    }
00705 
00706    if (timeout > 0)
00707       res = ast_safe_sleep(chan, timeout);
00708    else {
00709       while (!(res = ast_safe_sleep(chan, 10000)));
00710    }
00711 
00712    ast_moh_stop(chan);
00713 
00714    return res;
00715 }
00716 
00717 static int wait_moh_exec(struct ast_channel *chan, void *data)
00718 {
00719    static int deprecation_warning = 0;
00720    int res;
00721 
00722    if (!deprecation_warning) {
00723       deprecation_warning = 1;
00724       ast_log(LOG_WARNING, "WaitMusicOnHold application is deprecated and will be removed. Use MusicOnHold with duration parameter instead\n");
00725    }
00726 
00727    if (!data || !atoi(data)) {
00728       ast_log(LOG_WARNING, "WaitMusicOnHold requires an argument (number of seconds to wait)\n");
00729       return -1;
00730    }
00731    if (ast_moh_start(chan, NULL, NULL)) {
00732       ast_log(LOG_WARNING, "Unable to start music on hold for %d seconds on channel %s\n", atoi(data), chan->name);
00733       return 0;
00734    }
00735    res = ast_safe_sleep(chan, atoi(data) * 1000);
00736    ast_moh_stop(chan);
00737    return res;
00738 }
00739 
00740 static int set_moh_exec(struct ast_channel *chan, void *data)
00741 {
00742    static int deprecation_warning = 0;
00743 
00744    if (!deprecation_warning) {
00745       deprecation_warning = 1;
00746       ast_log(LOG_WARNING, "SetMusicOnHold application is deprecated and will be removed. Use Set(CHANNEL(musicclass)=...) instead\n");
00747    }
00748 
00749    if (ast_strlen_zero(data)) {
00750       ast_log(LOG_WARNING, "SetMusicOnHold requires an argument (class)\n");
00751       return -1;
00752    }
00753    ast_string_field_set(chan, musicclass, data);
00754    return 0;
00755 }
00756 
00757 static int start_moh_exec(struct ast_channel *chan, void *data)
00758 {
00759    char *parse;
00760    char *class;
00761    AST_DECLARE_APP_ARGS(args,
00762       AST_APP_ARG(class);
00763    );
00764 
00765    parse = ast_strdupa(data);
00766 
00767    AST_STANDARD_APP_ARGS(args, parse);
00768 
00769    class = S_OR(args.class, NULL);
00770    if (ast_moh_start(chan, class, NULL)) 
00771       ast_log(LOG_WARNING, "Unable to start music on hold class '%s' on channel %s\n", class, chan->name);
00772 
00773    return 0;
00774 }
00775 
00776 static int stop_moh_exec(struct ast_channel *chan, void *data)
00777 {
00778    ast_moh_stop(chan);
00779 
00780    return 0;
00781 }
00782 
00783 #define get_mohbyname(a,b,c)  _get_mohbyname(a,b,c,__FILE__,__LINE__,__PRETTY_FUNCTION__)
00784 
00785 static struct mohclass *_get_mohbyname(const char *name, int warn, int flags, const char *file, int lineno, const char *funcname)
00786 {
00787    struct mohclass *moh = NULL;
00788    struct mohclass tmp_class = {
00789       .flags = 0,
00790    };
00791 
00792    ast_copy_string(tmp_class.name, name, sizeof(tmp_class.name));
00793 
00794 #ifdef REF_DEBUG
00795    moh = _ao2_find_debug(mohclasses, &tmp_class, flags, "get_mohbyname", (char *) file, lineno, funcname);
00796 #else
00797    moh = _ao2_find(mohclasses, &tmp_class, flags);
00798 #endif
00799 
00800    if (!moh && warn) {
00801       ast_log(LOG_DEBUG, "Music on Hold class '%s' not found in memory\n", name);
00802    }
00803 
00804    return moh;
00805 }
00806 
00807 static struct mohdata *mohalloc(struct mohclass *cl)
00808 {
00809    struct mohdata *moh;
00810    long flags; 
00811    
00812    if (!(moh = ast_calloc(1, sizeof(*moh))))
00813       return NULL;
00814    
00815    if (pipe(moh->pipe)) {
00816       ast_log(LOG_WARNING, "Failed to create pipe: %s\n", strerror(errno));
00817       ast_free(moh);
00818       return NULL;
00819    }
00820 
00821    /* Make entirely non-blocking */
00822    flags = fcntl(moh->pipe[0], F_GETFL);
00823    fcntl(moh->pipe[0], F_SETFL, flags | O_NONBLOCK);
00824    flags = fcntl(moh->pipe[1], F_GETFL);
00825    fcntl(moh->pipe[1], F_SETFL, flags | O_NONBLOCK);
00826 
00827    moh->f.frametype = AST_FRAME_VOICE;
00828    moh->f.subclass = cl->format;
00829    moh->f.offset = AST_FRIENDLY_OFFSET;
00830 
00831    moh->parent = mohclass_ref(cl, "Reffing music class for mohdata parent");
00832 
00833    ao2_lock(cl);
00834    AST_LIST_INSERT_HEAD(&cl->members, moh, list);
00835    ao2_unlock(cl);
00836    
00837    return moh;
00838 }
00839 
00840 static void moh_release(struct ast_channel *chan, void *data)
00841 {
00842    struct mohdata *moh = data;
00843    struct mohclass *class = moh->parent;
00844    int oldwfmt;
00845 
00846    ao2_lock(class);
00847    AST_LIST_REMOVE(&moh->parent->members, moh, list); 
00848    ao2_unlock(class);
00849    
00850    close(moh->pipe[0]);
00851    close(moh->pipe[1]);
00852 
00853    oldwfmt = moh->origwfmt;
00854 
00855    moh->parent = class = mohclass_unref(class, "unreffing moh->parent upon deactivation of generator");
00856 
00857    ast_free(moh);
00858 
00859    if (chan) {
00860       if (oldwfmt && ast_set_write_format(chan, oldwfmt)) {
00861          ast_log(LOG_WARNING, "Unable to restore channel '%s' to format %s\n",
00862                chan->name, ast_getformatname(oldwfmt));
00863       }
00864 
00865       ast_verb(3, "Stopped music on hold on %s\n", chan->name);
00866    }
00867 }
00868 
00869 static void *moh_alloc(struct ast_channel *chan, void *params)
00870 {
00871    struct mohdata *res;
00872    struct mohclass *class = params;
00873    struct moh_files_state *state;
00874 
00875    /* Initiating music_state for current channel. Channel should know name of moh class */
00876    if (!chan->music_state && (state = ast_calloc(1, sizeof(*state)))) {
00877       chan->music_state = state;
00878       state->class = mohclass_ref(class, "Copying reference into state container");
00879       ast_module_ref(ast_module_info->self);
00880    } else
00881       state = chan->music_state;
00882    if (state && state->class != class) {
00883       memset(state, 0, sizeof(*state));
00884       state->class = class;
00885    }
00886 
00887    if ((res = mohalloc(class))) {
00888       res->origwfmt = chan->writeformat;
00889       if (ast_set_write_format(chan, class->format)) {
00890          ast_log(LOG_WARNING, "Unable to set channel '%s' to format '%s'\n", chan->name, ast_codec2str(class->format));
00891          moh_release(NULL, res);
00892          res = NULL;
00893       }
00894       ast_verb(3, "Started music on hold, class '%s', on channel '%s'\n", class->name, chan->name);
00895    }
00896    return res;
00897 }
00898 
00899 static int moh_generate(struct ast_channel *chan, void *data, int len, int samples)
00900 {
00901    struct mohdata *moh = data;
00902    short buf[1280 + AST_FRIENDLY_OFFSET / 2];
00903    int res;
00904 
00905    len = ast_codec_get_len(moh->parent->format, samples);
00906 
00907    if (len > sizeof(buf) - AST_FRIENDLY_OFFSET) {
00908       ast_log(LOG_WARNING, "Only doing %d of %d requested bytes on %s\n", (int)sizeof(buf), len, chan->name);
00909       len = sizeof(buf) - AST_FRIENDLY_OFFSET;
00910    }
00911    res = read(moh->pipe[0], buf + AST_FRIENDLY_OFFSET/2, len);
00912    if (res <= 0)
00913       return 0;
00914 
00915    moh->f.datalen = res;
00916    moh->f.data.ptr = buf + AST_FRIENDLY_OFFSET / 2;
00917    moh->f.samples = ast_codec_get_samples(&moh->f);
00918 
00919    if (ast_write(chan, &moh->f) < 0) {
00920       ast_log(LOG_WARNING, "Failed to write frame to '%s': %s\n", chan->name, strerror(errno));
00921       return -1;
00922    }
00923 
00924    return 0;
00925 }
00926 
00927 static struct ast_generator mohgen = {
00928    .alloc    = moh_alloc,
00929    .release  = moh_release,
00930    .generate = moh_generate,
00931    .digit    = moh_handle_digit,
00932 };
00933 
00934 static int moh_add_file(struct mohclass *class, const char *filepath)
00935 {
00936    if (!class->allowed_files) {
00937       if (!(class->filearray = ast_calloc(1, INITIAL_NUM_FILES * sizeof(*class->filearray))))
00938          return -1;
00939       class->allowed_files = INITIAL_NUM_FILES;
00940    } else if (class->total_files == class->allowed_files) {
00941       if (!(class->filearray = ast_realloc(class->filearray, class->allowed_files * sizeof(*class->filearray) * 2))) {
00942          class->allowed_files = 0;
00943          class->total_files = 0;
00944          return -1;
00945       }
00946       class->allowed_files *= 2;
00947    }
00948 
00949    if (!(class->filearray[class->total_files] = ast_strdup(filepath)))
00950       return -1;
00951 
00952    class->total_files++;
00953 
00954    return 0;
00955 }
00956 
00957 static int moh_sort_compare(const void *i1, const void *i2)
00958 {
00959    char *s1, *s2;
00960 
00961    s1 = ((char **)i1)[0];
00962    s2 = ((char **)i2)[0];
00963 
00964    return strcasecmp(s1, s2);
00965 }
00966 
00967 static int moh_scan_files(struct mohclass *class) {
00968 
00969    DIR *files_DIR;
00970    struct dirent *files_dirent;
00971    char dir_path[PATH_MAX];
00972    char path[PATH_MAX];
00973    char filepath[PATH_MAX];
00974    char *ext;
00975    struct stat statbuf;
00976    int dirnamelen;
00977    int i;
00978 
00979    if (class->dir[0] != '/') {
00980       ast_copy_string(dir_path, ast_config_AST_VAR_DIR, sizeof(dir_path));
00981       strncat(dir_path, "/", sizeof(dir_path) - 1);
00982       strncat(dir_path, class->dir, sizeof(dir_path) - 1);
00983    } else {
00984       ast_copy_string(dir_path, class->dir, sizeof(dir_path));
00985    }
00986    ast_debug(4, "Scanning '%s' for files for class '%s'\n", dir_path, class->name);
00987    files_DIR = opendir(dir_path);
00988    if (!files_DIR) {
00989       ast_log(LOG_WARNING, "Cannot open dir %s or dir does not exist\n", dir_path);
00990       return -1;
00991    }
00992 
00993    for (i = 0; i < class->total_files; i++)
00994       ast_free(class->filearray[i]);
00995 
00996    class->total_files = 0;
00997    dirnamelen = strlen(dir_path) + 2;
00998    if (!getcwd(path, sizeof(path))) {
00999       ast_log(LOG_WARNING, "getcwd() failed: %s\n", strerror(errno));
01000       return -1;
01001    }
01002    if (chdir(dir_path) < 0) {
01003       ast_log(LOG_WARNING, "chdir() failed: %s\n", strerror(errno));
01004       return -1;
01005    }
01006    while ((files_dirent = readdir(files_DIR))) {
01007       /* The file name must be at least long enough to have the file type extension */
01008       if ((strlen(files_dirent->d_name) < 4))
01009          continue;
01010 
01011       /* Skip files that starts with a dot */
01012       if (files_dirent->d_name[0] == '.')
01013          continue;
01014 
01015       /* Skip files without extensions... they are not audio */
01016       if (!strchr(files_dirent->d_name, '.'))
01017          continue;
01018 
01019       snprintf(filepath, sizeof(filepath), "%s/%s", dir_path, files_dirent->d_name);
01020 
01021       if (stat(filepath, &statbuf))
01022          continue;
01023 
01024       if (!S_ISREG(statbuf.st_mode))
01025          continue;
01026 
01027       if ((ext = strrchr(filepath, '.')))
01028          *ext = '\0';
01029 
01030       /* if the file is present in multiple formats, ensure we only put it into the list once */
01031       for (i = 0; i < class->total_files; i++)
01032          if (!strcmp(filepath, class->filearray[i]))
01033             break;
01034 
01035       if (i == class->total_files) {
01036          if (moh_add_file(class, filepath))
01037             break;
01038       }
01039    }
01040 
01041    closedir(files_DIR);
01042    if (chdir(path) < 0) {
01043       ast_log(LOG_WARNING, "chdir() failed: %s\n", strerror(errno));
01044       return -1;
01045    }
01046    if (ast_test_flag(class, MOH_SORTALPHA))
01047       qsort(&class->filearray[0], class->total_files, sizeof(char *), moh_sort_compare);
01048    return class->total_files;
01049 }
01050 
01051 static int init_files_class(struct mohclass *class)
01052 {
01053    int res;
01054 
01055    res = moh_scan_files(class);
01056 
01057    if (res < 0) {
01058       return -1;
01059    }
01060 
01061    if (!res) {
01062       if (option_verbose > 2) {
01063          ast_verbose(VERBOSE_PREFIX_3 "Files not found in %s for moh class:%s\n",
01064                class->dir, class->name);
01065       }
01066       return -1;
01067    }
01068 
01069    if (strchr(class->args, 'r')) {
01070       ast_set_flag(class, MOH_RANDOMIZE);
01071    }
01072 
01073    return 0;
01074 }
01075 
01076 
01077 static int moh_diff(struct mohclass *old, struct mohclass *new)
01078 {
01079    if (!old || !new) {
01080       return -1;
01081    }
01082 
01083    if (strcmp(old->dir, new->dir)) {
01084       return -1;
01085    } else if (strcmp(old->mode, new->mode)) {
01086       return -1;
01087    } else if (strcmp(old->args, new->args)) {
01088       return -1;
01089    } else if (old->flags != new->flags) {
01090       return -1;
01091    }
01092 
01093    return 0;
01094 }
01095 
01096 static int init_app_class(struct mohclass *class)
01097 {
01098 #ifdef HAVE_DAHDI
01099    int x;
01100 #endif
01101 
01102    if (!strcasecmp(class->mode, "custom")) {
01103       ast_set_flag(class, MOH_CUSTOM);
01104    } else if (!strcasecmp(class->mode, "mp3nb")) {
01105       ast_set_flag(class, MOH_SINGLE);
01106    } else if (!strcasecmp(class->mode, "quietmp3nb")) {
01107       ast_set_flag(class, MOH_SINGLE | MOH_QUIET);
01108    } else if (!strcasecmp(class->mode, "quietmp3")) {
01109       ast_set_flag(class, MOH_QUIET);
01110    }
01111       
01112    class->srcfd = -1;
01113    class->pseudofd = -1;
01114 
01115 #ifdef HAVE_DAHDI
01116    /* Open /dev/zap/pseudo for timing...  Is
01117       there a better, yet reliable way to do this? */
01118    class->pseudofd = open("/dev/dahdi/pseudo", O_RDONLY);
01119    if (class->pseudofd < 0) {
01120       ast_log(LOG_WARNING, "Unable to open pseudo channel for timing...  Sound may be choppy.\n");
01121    } else {
01122       x = 320;
01123       ioctl(class->pseudofd, DAHDI_SET_BLOCKSIZE, &x);
01124    }
01125 #endif
01126 
01127    if (ast_pthread_create_background(&class->thread, NULL, monmp3thread, class)) {
01128       ast_log(LOG_WARNING, "Unable to create moh thread...\n");
01129       if (class->pseudofd > -1) {
01130          close(class->pseudofd);
01131          class->pseudofd = -1;
01132       }
01133       return -1;
01134    }
01135 
01136    return 0;
01137 }
01138 
01139 /*!
01140  * \note This function owns the reference it gets to moh if unref is true
01141  */
01142 #define moh_register(a,b,c)   _moh_register(a,b,c,__FILE__,__LINE__,__PRETTY_FUNCTION__)
01143 static int _moh_register(struct mohclass *moh, int reload, int unref, const char *file, int line, const char *funcname)
01144 {
01145    struct mohclass *mohclass = NULL;
01146 
01147    if ((mohclass = _get_mohbyname(moh->name, 0, MOH_NOTDELETED, file, line, funcname)) && !moh_diff(mohclass, moh)) {
01148       ast_log(LOG_WARNING, "Music on Hold class '%s' already exists\n", moh->name);
01149       mohclass = mohclass_unref(mohclass, "unreffing mohclass we just found by name");
01150       if (unref) {
01151          moh = mohclass_unref(moh, "unreffing potential new moh class (it is a duplicate)");
01152       }
01153       return -1;
01154    } else if (mohclass) {
01155       /* Found a class, but it's different from the one being registered */
01156       mohclass = mohclass_unref(mohclass, "unreffing mohclass we just found by name");
01157    }
01158 
01159    time(&moh->start);
01160    moh->start -= respawn_time;
01161    
01162    if (!strcasecmp(moh->mode, "files")) {
01163       if (init_files_class(moh)) {
01164          if (unref) {
01165             moh = mohclass_unref(moh, "unreffing potential new moh class (init_files_class failed)");
01166          }
01167          return -1;
01168       }
01169    } else if (!strcasecmp(moh->mode, "mp3") || !strcasecmp(moh->mode, "mp3nb") || 
01170          !strcasecmp(moh->mode, "quietmp3") || !strcasecmp(moh->mode, "quietmp3nb") || 
01171          !strcasecmp(moh->mode, "httpmp3") || !strcasecmp(moh->mode, "custom")) {
01172       if (init_app_class(moh)) {
01173          if (unref) {
01174             moh = mohclass_unref(moh, "unreffing potential new moh class (init_app_class_failed)");
01175          }
01176          return -1;
01177       }
01178    } else {
01179       ast_log(LOG_WARNING, "Don't know how to do a mode '%s' music on hold\n", moh->mode);
01180       if (unref) {
01181          moh = mohclass_unref(moh, "unreffing potential new moh class (unknown mode)");
01182       }
01183       return -1;
01184    }
01185 
01186    ao2_t_link(mohclasses, moh, "Adding class to container");
01187 
01188    if (unref) {
01189       moh = mohclass_unref(moh, "Unreffing new moh class because we just added it to the container");
01190    }
01191    
01192    return 0;
01193 }
01194 
01195 static void local_ast_moh_cleanup(struct ast_channel *chan)
01196 {
01197    struct moh_files_state *state = chan->music_state;
01198 
01199    if (state) {
01200       if (state->class) {
01201          state->class = mohclass_unref(state->class, "Channel MOH state destruction");
01202       }
01203       ast_free(chan->music_state);
01204       chan->music_state = NULL;
01205       /* Only held a module reference if we had a music state */
01206       ast_module_unref(ast_module_info->self);
01207    }
01208 }
01209 
01210 static void moh_class_destructor(void *obj);
01211 
01212 #define moh_class_malloc() _moh_class_malloc(__FILE__,__LINE__,__PRETTY_FUNCTION__)
01213 
01214 static struct mohclass *_moh_class_malloc(const char *file, int line, const char *funcname)
01215 {
01216    struct mohclass *class;
01217 
01218    if ((class =
01219 #ifdef REF_DEBUG
01220          _ao2_alloc_debug(sizeof(*class), moh_class_destructor, "Allocating new moh class", file, line, funcname, 1)
01221 #elif defined(__AST_DEBUG_MALLOC)
01222          _ao2_alloc_debug(sizeof(*class), moh_class_destructor, "Allocating new moh class", file, line, funcname, 0)
01223 #else
01224          ao2_alloc(sizeof(*class), moh_class_destructor)
01225 #endif
01226       )) {
01227       class->format = AST_FORMAT_SLINEAR;
01228    }
01229 
01230    return class;
01231 }
01232 
01233 static int local_ast_moh_start(struct ast_channel *chan, const char *mclass, const char *interpclass)
01234 {
01235    struct mohclass *mohclass = NULL;
01236    struct moh_files_state *state = chan->music_state;
01237    struct ast_variable *var = NULL;
01238    int res;
01239    int realtime_possible = ast_check_realtime("musiconhold");
01240 
01241    /* The following is the order of preference for which class to use:
01242     * 1) The channels explicitly set musicclass, which should *only* be
01243     *    set by a call to Set(CHANNEL(musicclass)=whatever) in the dialplan.
01244     * 2) The mclass argument. If a channel is calling ast_moh_start() as the
01245     *    result of receiving a HOLD control frame, this should be the
01246     *    payload that came with the frame.
01247     * 3) The interpclass argument. This would be from the mohinterpret
01248     *    option from channel drivers. This is the same as the old musicclass
01249     *    option.
01250     * 4) The default class.
01251     */
01252    if (!ast_strlen_zero(chan->musicclass)) {
01253       mohclass = get_mohbyname(chan->musicclass, 1, 0);
01254       if (!mohclass && realtime_possible) {
01255          var = ast_load_realtime("musiconhold", "name", chan->musicclass, SENTINEL);
01256       }
01257    }
01258    if (!mohclass && !var && !ast_strlen_zero(mclass)) {
01259       mohclass = get_mohbyname(mclass, 1, 0);
01260       if (!mohclass && realtime_possible) {
01261          var = ast_load_realtime("musiconhold", "name", mclass, SENTINEL);
01262       }
01263    }
01264    if (!mohclass && !var && !ast_strlen_zero(interpclass)) {
01265       mohclass = get_mohbyname(interpclass, 1, 0);
01266       if (!mohclass && realtime_possible) {
01267          var = ast_load_realtime("musiconhold", "name", interpclass, SENTINEL);
01268       }
01269    }
01270 
01271    if (!mohclass && !var) {
01272       mohclass = get_mohbyname("default", 1, 0);
01273       if (!mohclass && realtime_possible) {
01274          var = ast_load_realtime("musiconhold", "name", "default", SENTINEL);
01275       }
01276    }
01277 
01278    /* If no moh class found in memory, then check RT. Note that the logic used
01279     * above guarantees that if var is non-NULL, then mohclass must be NULL.
01280     */
01281    if (var) {
01282       struct ast_variable *tmp = NULL;
01283 
01284       if ((mohclass = moh_class_malloc())) {
01285          mohclass->realtime = 1;
01286          for (tmp = var; tmp; tmp = tmp->next) {
01287             if (!strcasecmp(tmp->name, "name"))
01288                ast_copy_string(mohclass->name, tmp->value, sizeof(mohclass->name));
01289             else if (!strcasecmp(tmp->name, "mode"))
01290                ast_copy_string(mohclass->mode, tmp->value, sizeof(mohclass->mode)); 
01291             else if (!strcasecmp(tmp->name, "directory"))
01292                ast_copy_string(mohclass->dir, tmp->value, sizeof(mohclass->dir));
01293             else if (!strcasecmp(tmp->name, "application"))
01294                ast_copy_string(mohclass->args, tmp->value, sizeof(mohclass->args));
01295             else if (!strcasecmp(tmp->name, "digit") && (isdigit(*tmp->value) || strchr("*#", *tmp->value)))
01296                mohclass->digit = *tmp->value;
01297             else if (!strcasecmp(tmp->name, "random"))
01298                ast_set2_flag(mohclass, ast_true(tmp->value), MOH_RANDOMIZE);
01299             else if (!strcasecmp(tmp->name, "sort") && !strcasecmp(tmp->value, "random"))
01300                ast_set_flag(mohclass, MOH_RANDOMIZE);
01301             else if (!strcasecmp(tmp->name, "sort") && !strcasecmp(tmp->value, "alpha")) 
01302                ast_set_flag(mohclass, MOH_SORTALPHA);
01303             else if (!strcasecmp(tmp->name, "format")) {
01304                mohclass->format = ast_getformatbyname(tmp->value);
01305                if (!mohclass->format) {
01306                   ast_log(LOG_WARNING, "Unknown format '%s' -- defaulting to SLIN\n", tmp->value);
01307                   mohclass->format = AST_FORMAT_SLINEAR;
01308                }
01309             }
01310          }
01311          ast_variables_destroy(var);
01312          if (ast_strlen_zero(mohclass->dir)) {
01313             if (!strcasecmp(mohclass->mode, "custom")) {
01314                strcpy(mohclass->dir, "nodir");
01315             } else {
01316                ast_log(LOG_WARNING, "A directory must be specified for class '%s'!\n", mohclass->name);
01317                mohclass = mohclass_unref(mohclass, "unreffing potential mohclass (no directory specified)");
01318                return -1;
01319             }
01320          }
01321          if (ast_strlen_zero(mohclass->mode)) {
01322             ast_log(LOG_WARNING, "A mode must be specified for class '%s'!\n", mohclass->name);
01323             mohclass = mohclass_unref(mohclass, "unreffing potential mohclass (no mode specified)");
01324             return -1;
01325          }
01326          if (ast_strlen_zero(mohclass->args) && !strcasecmp(mohclass->mode, "custom")) {
01327             ast_log(LOG_WARNING, "An application must be specified for class '%s'!\n", mohclass->name);
01328             mohclass = mohclass_unref(mohclass, "unreffing potential mohclass (no app specified for custom mode");
01329             return -1;
01330          }
01331 
01332          if (ast_test_flag(global_flags, MOH_CACHERTCLASSES)) {
01333             /* CACHERTCLASSES enabled, let's add this class to default tree */
01334             if (state && state->class) {
01335                /* Class already exist for this channel */
01336                ast_log(LOG_NOTICE, "This channel already has a MOH class attached (%s)!\n", state->class->name);
01337                if (state->class->realtime && !ast_test_flag(global_flags, MOH_CACHERTCLASSES) && !strcasecmp(mohclass->name, state->class->name)) {
01338                   /* we found RT class with the same name, seems like we should continue playing existing one */
01339                   /* XXX This code is impossible to reach */
01340                   mohclass = mohclass_unref(mohclass, "unreffing potential mohclass (channel already has a class)");
01341                   mohclass = state->class;
01342                }
01343             }
01344             /* We don't want moh_register to unref the mohclass because we do it at the end of this function as well.
01345              * If we allowed moh_register to unref the mohclass,too, then the count would be off by one. The result would
01346              * be that the destructor would be called when the generator on the channel is deactivated. The container then
01347              * has a pointer to a freed mohclass, so any operations involving the mohclass container would result in reading
01348              * invalid memory.
01349              */
01350             moh_register(mohclass, 0, DONT_UNREF);
01351          } else {
01352             /* We don't register RT moh class, so let's init it manualy */
01353 
01354             time(&mohclass->start);
01355             mohclass->start -= respawn_time;
01356    
01357             if (!strcasecmp(mohclass->mode, "files")) {
01358                if (!moh_scan_files(mohclass)) {
01359                   mohclass = mohclass_unref(mohclass, "unreffing potential mohclass (moh_scan_files failed)");
01360                   return -1;
01361                }
01362                if (strchr(mohclass->args, 'r'))
01363                   ast_set_flag(mohclass, MOH_RANDOMIZE);
01364             } 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")) {
01365 
01366                if (!strcasecmp(mohclass->mode, "custom"))
01367                   ast_set_flag(mohclass, MOH_CUSTOM);
01368                else if (!strcasecmp(mohclass->mode, "mp3nb"))
01369                   ast_set_flag(mohclass, MOH_SINGLE);
01370                else if (!strcasecmp(mohclass->mode, "quietmp3nb"))
01371                   ast_set_flag(mohclass, MOH_SINGLE | MOH_QUIET);
01372                else if (!strcasecmp(mohclass->mode, "quietmp3"))
01373                   ast_set_flag(mohclass, MOH_QUIET);
01374          
01375                mohclass->srcfd = -1;
01376 #ifdef HAVE_DAHDI
01377                /* Open /dev/dahdi/pseudo for timing...  Is
01378                   there a better, yet reliable way to do this? */
01379                mohclass->pseudofd = open("/dev/dahdi/pseudo", O_RDONLY);
01380                if (mohclass->pseudofd < 0) {
01381                   ast_log(LOG_WARNING, "Unable to open pseudo channel for timing...  Sound may be choppy.\n");
01382                } else {
01383                   int x = 320;
01384                   ioctl(mohclass->pseudofd, DAHDI_SET_BLOCKSIZE, &x);
01385                }
01386 #else
01387                mohclass->pseudofd = -1;
01388 #endif
01389                /* Let's check if this channel already had a moh class before */
01390                if (state && state->class) {
01391                   /* Class already exist for this channel */
01392                   ast_log(LOG_NOTICE, "This channel already has a MOH class attached (%s)!\n", state->class->name);
01393                   if (state->class->realtime && !ast_test_flag(global_flags, MOH_CACHERTCLASSES) && !strcasecmp(mohclass->name, state->class->name)) {
01394                      /* we found RT class with the same name, seems like we should continue playing existing one */
01395                      mohclass = mohclass_unref(mohclass, "unreffing potential mohclass (channel already has one)");
01396                      mohclass = state->class;
01397                   }
01398                } else {
01399                   if (ast_pthread_create_background(&mohclass->thread, NULL, monmp3thread, mohclass)) {
01400                      ast_log(LOG_WARNING, "Unable to create moh...\n");
01401                      if (mohclass->pseudofd > -1) {
01402                         close(mohclass->pseudofd);
01403                         mohclass->pseudofd = -1;
01404                      }
01405                      mohclass = mohclass_unref(mohclass, "Unreffing potential mohclass (failed to create background thread)");
01406                      return -1;
01407                   }
01408                }
01409             } else {
01410                ast_log(LOG_WARNING, "Don't know how to do a mode '%s' music on hold\n", mohclass->mode);
01411                mohclass = mohclass_unref(mohclass, "unreffing potential mohclass (unknown mode)");
01412                return -1;
01413             }
01414          }
01415       } else {
01416          ast_variables_destroy(var);
01417       }
01418    }
01419 
01420    if (!mohclass) {
01421       return -1;
01422    }
01423 
01424    manager_event(EVENT_FLAG_CALL, "MusicOnHold",
01425       "State: Start\r\n"
01426       "Channel: %s\r\n"
01427       "UniqueID: %s\r\n",
01428       chan->name, chan->uniqueid);
01429 
01430    ast_set_flag(chan, AST_FLAG_MOH);
01431 
01432    if (mohclass->total_files) {
01433       res = ast_activate_generator(chan, &moh_file_stream, mohclass);
01434    } else {
01435       res = ast_activate_generator(chan, &mohgen, mohclass);
01436    }
01437 
01438    mohclass = mohclass_unref(mohclass, "unreffing local reference to mohclass in local_ast_moh_start");
01439 
01440    return res;
01441 }
01442 
01443 static void local_ast_moh_stop(struct ast_channel *chan)
01444 {
01445    ast_clear_flag(chan, AST_FLAG_MOH);
01446    ast_deactivate_generator(chan);
01447 
01448    ast_channel_lock(chan);
01449    if (chan->music_state) {
01450       if (chan->stream) {
01451          ast_closestream(chan->stream);
01452          chan->stream = NULL;
01453       }
01454    }
01455 
01456    manager_event(EVENT_FLAG_CALL, "MusicOnHold",
01457       "State: Stop\r\n"
01458       "Channel: %s\r\n"
01459       "UniqueID: %s\r\n",
01460       chan->name, chan->uniqueid);
01461    ast_channel_unlock(chan);
01462 }
01463 
01464 static void moh_class_destructor(void *obj)
01465 {
01466    struct mohclass *class = obj;
01467    struct mohdata *member;
01468    pthread_t tid = 0;
01469 
01470    ast_debug(1, "Destroying MOH class '%s'\n", class->name);
01471 
01472    /* Kill the thread first, so it cannot restart the child process while the
01473     * class is being destroyed */
01474    if (class->thread != AST_PTHREADT_NULL && class->thread != 0) {
01475       tid = class->thread;
01476       class->thread = AST_PTHREADT_NULL;
01477       pthread_cancel(tid);
01478       /* We'll collect the exit status later, after we ensure all the readers
01479        * are dead. */
01480    }
01481 
01482    if (class->pid > 1) {
01483       char buff[8192];
01484       int bytes, tbytes = 0, stime = 0, pid = 0;
01485 
01486       ast_log(LOG_DEBUG, "killing %d!\n", class->pid);
01487 
01488       stime = time(NULL) + 2;
01489       pid = class->pid;
01490       class->pid = 0;
01491 
01492       /* Back when this was just mpg123, SIGKILL was fine.  Now we need
01493        * to give the process a reason and time enough to kill off its
01494        * children. */
01495       do {
01496          if (killpg(pid, SIGHUP) < 0) {
01497             ast_log(LOG_WARNING, "Unable to send a SIGHUP to MOH process?!!: %s\n", strerror(errno));
01498          }
01499          usleep(100000);
01500          if (killpg(pid, SIGTERM) < 0) {
01501             if (errno == ESRCH) {
01502                break;
01503             }
01504             ast_log(LOG_WARNING, "Unable to terminate MOH process?!!: %s\n", strerror(errno));
01505          }
01506          usleep(100000);
01507          if (killpg(pid, SIGKILL) < 0) {
01508             if (errno == ESRCH) {
01509                break;
01510             }
01511             ast_log(LOG_WARNING, "Unable to kill MOH process?!!: %s\n", strerror(errno));
01512          }
01513       } while (0);
01514 
01515       while ((ast_wait_for_input(class->srcfd, 100) > 0) && 
01516             (bytes = read(class->srcfd, buff, 8192)) && time(NULL) < stime) {
01517          tbytes = tbytes + bytes;
01518       }
01519 
01520       ast_log(LOG_DEBUG, "mpg123 pid %d and child died after %d bytes read\n", pid, tbytes);
01521 
01522       close(class->srcfd);
01523    }
01524 
01525    while ((member = AST_LIST_REMOVE_HEAD(&class->members, list))) {
01526       free(member);
01527    }
01528 
01529    if (class->filearray) {
01530       int i;
01531       for (i = 0; i < class->total_files; i++) {
01532          free(class->filearray[i]);
01533       }
01534       free(class->filearray);
01535       class->filearray = NULL;
01536    }
01537 
01538    /* Finally, collect the exit status of the monitor thread */
01539    if (tid > 0) {
01540       pthread_join(tid, NULL);
01541    }
01542 }
01543 
01544 static int moh_class_mark(void *obj, void *arg, int flags)
01545 {
01546    struct mohclass *class = obj;
01547 
01548    class->delete = 1;
01549 
01550    return 0;
01551 }
01552 
01553 static int moh_classes_delete_marked(void *obj, void *arg, int flags)
01554 {
01555    struct mohclass *class = obj;
01556 
01557    return class->delete ? CMP_MATCH : 0;
01558 }
01559 
01560 static int load_moh_classes(int reload)
01561 {
01562    struct ast_config *cfg;
01563    struct ast_variable *var;
01564    struct mohclass *class; 
01565    char *cat;
01566    int numclasses = 0;
01567    struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
01568 
01569    cfg = ast_config_load("musiconhold.conf", config_flags);
01570 
01571    if (cfg == CONFIG_STATUS_FILEMISSING || cfg == CONFIG_STATUS_FILEUNCHANGED || cfg == CONFIG_STATUS_FILEINVALID) {
01572       return 0;
01573    }
01574 
01575    if (reload) {
01576       ao2_t_callback(mohclasses, OBJ_NODATA, moh_class_mark, NULL, "Mark deleted classes");
01577    }
01578    
01579    ast_clear_flag(global_flags, AST_FLAGS_ALL);
01580 
01581    cat = ast_category_browse(cfg, NULL);
01582    for (; cat; cat = ast_category_browse(cfg, cat)) {
01583       /* Setup common options from [general] section */
01584       if (!strcasecmp(cat, "general")) {
01585          for (var = ast_variable_browse(cfg, cat); var; var = var->next) {
01586             if (!strcasecmp(var->name, "cachertclasses")) {
01587                ast_set2_flag(global_flags, ast_true(var->value), MOH_CACHERTCLASSES);
01588             } else {
01589                ast_log(LOG_WARNING, "Unknown option '%s' in [general] section of musiconhold.conf\n", var->name);
01590             }
01591          }
01592       }
01593       /* These names were deprecated in 1.4 and should not be used until after the next major release. */
01594       if (!strcasecmp(cat, "classes") || !strcasecmp(cat, "moh_files") || 
01595             !strcasecmp(cat, "general")) {
01596          continue;
01597       }
01598 
01599       if (!(class = moh_class_malloc())) {
01600          break;
01601       }
01602 
01603       ast_copy_string(class->name, cat, sizeof(class->name));  
01604       for (var = ast_variable_browse(cfg, cat); var; var = var->next) {
01605          if (!strcasecmp(var->name, "mode"))
01606             ast_copy_string(class->mode, var->value, sizeof(class->mode)); 
01607          else if (!strcasecmp(var->name, "directory"))
01608             ast_copy_string(class->dir, var->value, sizeof(class->dir));
01609          else if (!strcasecmp(var->name, "application"))
01610             ast_copy_string(class->args, var->value, sizeof(class->args));
01611          else if (!strcasecmp(var->name, "digit") && (isdigit(*var->value) || strchr("*#", *var->value)))
01612             class->digit = *var->value;
01613          else if (!strcasecmp(var->name, "random"))
01614             ast_set2_flag(class, ast_true(var->value), MOH_RANDOMIZE);
01615          else if (!strcasecmp(var->name, "sort") && !strcasecmp(var->value, "random"))
01616             ast_set_flag(class, MOH_RANDOMIZE);
01617          else if (!strcasecmp(var->name, "sort") && !strcasecmp(var->value, "alpha")) 
01618             ast_set_flag(class, MOH_SORTALPHA);
01619          else if (!strcasecmp(var->name, "format")) {
01620             class->format = ast_getformatbyname(var->value);
01621             if (!class->format) {
01622                ast_log(LOG_WARNING, "Unknown format '%s' -- defaulting to SLIN\n", var->value);
01623                class->format = AST_FORMAT_SLINEAR;
01624             }
01625          }
01626       }
01627 
01628       if (ast_strlen_zero(class->dir)) {
01629          if (!strcasecmp(class->mode, "custom")) {
01630             strcpy(class->dir, "nodir");
01631          } else {
01632             ast_log(LOG_WARNING, "A directory must be specified for class '%s'!\n", class->name);
01633             class = mohclass_unref(class, "unreffing potential mohclass (no directory)");
01634             continue;
01635          }
01636       }
01637       if (ast_strlen_zero(class->mode)) {
01638          ast_log(LOG_WARNING, "A mode must be specified for class '%s'!\n", class->name);
01639          class = mohclass_unref(class, "unreffing potential mohclass (no mode)");
01640          continue;
01641       }
01642       if (ast_strlen_zero(class->args) && !strcasecmp(class->mode, "custom")) {
01643          ast_log(LOG_WARNING, "An application must be specified for class '%s'!\n", class->name);
01644          class = mohclass_unref(class, "unreffing potential mohclass (no app for custom mode)");
01645          continue;
01646       }
01647 
01648       /* Don't leak a class when it's already registered */
01649       if (!moh_register(class, reload, HANDLE_REF)) {
01650          numclasses++;
01651       }
01652    }
01653 
01654    ast_config_destroy(cfg);
01655 
01656    ao2_t_callback(mohclasses, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, 
01657          moh_classes_delete_marked, NULL, "Purge marked classes");
01658 
01659    return numclasses;
01660 }
01661 
01662 static void ast_moh_destroy(void)
01663 {
01664    ast_verb(2, "Destroying musiconhold processes\n");
01665    ao2_t_callback(mohclasses, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, NULL, NULL, "Destroy callback");
01666 }
01667 
01668 static char *handle_cli_moh_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
01669 {
01670    switch (cmd) {
01671    case CLI_INIT:
01672       e->command = "moh reload";
01673       e->usage =
01674          "Usage: moh reload\n"
01675          "       Reloads the MusicOnHold module.\n"
01676          "       Alias for 'module reload res_musiconhold.so'\n";
01677       return NULL;
01678    case CLI_GENERATE:
01679       return NULL;
01680    }
01681 
01682    if (a->argc != e->args)
01683       return CLI_SHOWUSAGE;
01684 
01685    reload();
01686 
01687    return CLI_SUCCESS;
01688 }
01689 
01690 static char *handle_cli_moh_show_files(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
01691 {
01692    struct mohclass *class;
01693    struct ao2_iterator i;
01694 
01695    switch (cmd) {
01696    case CLI_INIT:
01697       e->command = "moh show files";
01698       e->usage =
01699          "Usage: moh show files\n"
01700          "       Lists all loaded file-based MusicOnHold classes and their\n"
01701          "       files.\n";
01702       return NULL;
01703    case CLI_GENERATE:
01704       return NULL;
01705    }
01706 
01707    if (a->argc != e->args)
01708       return CLI_SHOWUSAGE;
01709 
01710    i = ao2_iterator_init(mohclasses, 0);
01711    for (; (class = ao2_t_iterator_next(&i, "Show files iterator")); mohclass_unref(class, "Unref iterator in moh show files")) {
01712       int x;
01713 
01714       if (!class->total_files) {
01715          continue;
01716       }
01717 
01718       ast_cli(a->fd, "Class: %s\n", class->name);
01719       for (x = 0; x < class->total_files; x++) {
01720          ast_cli(a->fd, "\tFile: %s\n", class->filearray[x]);
01721       }
01722    }
01723    ao2_iterator_destroy(&i);
01724 
01725    return CLI_SUCCESS;
01726 }
01727 
01728 static char *handle_cli_moh_show_classes(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
01729 {
01730    struct mohclass *class;
01731    struct ao2_iterator i;
01732 
01733    switch (cmd) {
01734    case CLI_INIT:
01735       e->command = "moh show classes";
01736       e->usage =
01737          "Usage: moh show classes\n"
01738          "       Lists all MusicOnHold classes.\n";
01739       return NULL;
01740    case CLI_GENERATE:
01741       return NULL;
01742    }
01743 
01744    if (a->argc != e->args)
01745       return CLI_SHOWUSAGE;
01746 
01747    i = ao2_iterator_init(mohclasses, 0);
01748    for (; (class = ao2_t_iterator_next(&i, "Show classes iterator")); mohclass_unref(class, "Unref iterator in moh show classes")) {
01749       ast_cli(a->fd, "Class: %s\n", class->name);
01750       ast_cli(a->fd, "\tMode: %s\n", S_OR(class->mode, "<none>"));
01751       ast_cli(a->fd, "\tDirectory: %s\n", S_OR(class->dir, "<none>"));
01752       if (ast_test_flag(class, MOH_CUSTOM)) {
01753          ast_cli(a->fd, "\tApplication: %s\n", S_OR(class->args, "<none>"));
01754       }
01755       if (strcasecmp(class->mode, "files")) {
01756          ast_cli(a->fd, "\tFormat: %s\n", ast_getformatname(class->format));
01757       }
01758    }
01759    ao2_iterator_destroy(&i);
01760 
01761    return CLI_SUCCESS;
01762 }
01763 
01764 static struct ast_cli_entry cli_moh[] = {
01765    AST_CLI_DEFINE(handle_cli_moh_reload,       "Reload MusicOnHold"),
01766    AST_CLI_DEFINE(handle_cli_moh_show_classes, "List MusicOnHold classes"),
01767    AST_CLI_DEFINE(handle_cli_moh_show_files,   "List MusicOnHold file-based classes")
01768 };
01769 
01770 static int moh_class_hash(const void *obj, const int flags)
01771 {
01772    const struct mohclass *class = obj;
01773 
01774    return ast_str_case_hash(class->name);
01775 }
01776 
01777 static int moh_class_cmp(void *obj, void *arg, int flags)
01778 {
01779    struct mohclass *class = obj, *class2 = arg;
01780 
01781    return strcasecmp(class->name, class2->name) ? 0 :
01782       (flags & MOH_NOTDELETED) && (class->delete || class2->delete) ? 0 :
01783       CMP_MATCH | CMP_STOP;
01784 }
01785 
01786 static int load_module(void)
01787 {
01788    int res;
01789 
01790    if (!(mohclasses = ao2_t_container_alloc(53, moh_class_hash, moh_class_cmp, "Moh class container"))) {
01791       return AST_MODULE_LOAD_DECLINE;
01792    }
01793 
01794    if (!load_moh_classes(0)) {   /* No music classes configured, so skip it */
01795       ast_log(LOG_WARNING, "No music on hold classes configured, "
01796             "disabling music on hold.\n");
01797    } else {
01798       ast_install_music_functions(local_ast_moh_start, local_ast_moh_stop,
01799             local_ast_moh_cleanup);
01800    }
01801 
01802    res = ast_register_application(play_moh, play_moh_exec, play_moh_syn, play_moh_desc);
01803    ast_register_atexit(ast_moh_destroy);
01804    ast_cli_register_multiple(cli_moh, ARRAY_LEN(cli_moh));
01805    if (!res)
01806       res = ast_register_application(wait_moh, wait_moh_exec, wait_moh_syn, wait_moh_desc);
01807    if (!res)
01808       res = ast_register_application(set_moh, set_moh_exec, set_moh_syn, set_moh_desc);
01809    if (!res)
01810       res = ast_register_application(start_moh, start_moh_exec, start_moh_syn, start_moh_desc);
01811    if (!res)
01812       res = ast_register_application(stop_moh, stop_moh_exec, stop_moh_syn, stop_moh_desc);
01813 
01814    return AST_MODULE_LOAD_SUCCESS;
01815 }
01816 
01817 static int reload(void)
01818 {
01819    if (load_moh_classes(1)) {
01820       ast_install_music_functions(local_ast_moh_start, local_ast_moh_stop,
01821             local_ast_moh_cleanup);
01822    }
01823 
01824    return AST_MODULE_LOAD_SUCCESS;
01825 }
01826 
01827 static int moh_class_inuse(void *obj, void *arg, int flags)
01828 {
01829    struct mohclass *class = obj;
01830 
01831    return AST_LIST_EMPTY(&class->members) ? 0 : CMP_MATCH | CMP_STOP;
01832 }
01833 
01834 static int unload_module(void)
01835 {
01836    int res = 0;
01837    struct mohclass *class = NULL;
01838 
01839    /* XXX This check shouldn't be required if module ref counting was being used
01840     * properly ... */
01841    if ((class = ao2_t_callback(mohclasses, 0, moh_class_inuse, NULL, "Module unload callback"))) {
01842       class = mohclass_unref(class, "unref of class from module unload callback");
01843       res = -1;
01844    }
01845 
01846    if (res < 0) {
01847       ast_log(LOG_WARNING, "Unable to unload res_musiconhold due to active MOH channels\n");
01848       return res;
01849    }
01850 
01851    ast_uninstall_music_functions();
01852 
01853    ast_moh_destroy();
01854    res = ast_unregister_application(play_moh);
01855    res |= ast_unregister_application(wait_moh);
01856    res |= ast_unregister_application(set_moh);
01857    res |= ast_unregister_application(start_moh);
01858    res |= ast_unregister_application(stop_moh);
01859    ast_cli_unregister_multiple(cli_moh, ARRAY_LEN(cli_moh));
01860    ast_unregister_atexit(ast_moh_destroy);
01861 
01862    return res;
01863 }
01864 
01865 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Music On Hold Resource",
01866    .load = load_module,
01867    .unload = unload_module,
01868    .reload = reload,
01869 );