Fri Nov 12 11:47:10 2010

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

Generated by  doxygen 1.6.2