Fri Aug 24 02:22:17 2007

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>zaptel</use>
00031  ***/
00032 
00033 #include "asterisk.h"
00034 
00035 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
00036 
00037 #include <stdlib.h>
00038 #include <errno.h>
00039 #include <unistd.h>
00040 #include <string.h>
00041 #include <signal.h>
00042 #include <stdlib.h>
00043 #include <stdio.h>
00044 #include <sys/time.h>
00045 #include <sys/signal.h>
00046 #include <netinet/in.h>
00047 #include <sys/stat.h>
00048 #include <dirent.h>
00049 #include <unistd.h>
00050 #include <sys/ioctl.h>
00051 #ifdef SOLARIS
00052 #include <thread.h>
00053 #endif
00054 
00055 #ifdef HAVE_ZAPTEL
00056 #include <zaptel/zaptel.h>
00057 #endif
00058 
00059 #include "asterisk/lock.h"
00060 #include "asterisk/file.h"
00061 #include "asterisk/logger.h"
00062 #include "asterisk/channel.h"
00063 #include "asterisk/pbx.h"
00064 #include "asterisk/options.h"
00065 #include "asterisk/module.h"
00066 #include "asterisk/translate.h"
00067 #include "asterisk/say.h"
00068 #include "asterisk/musiconhold.h"
00069 #include "asterisk/config.h"
00070 #include "asterisk/utils.h"
00071 #include "asterisk/cli.h"
00072 #include "asterisk/stringfields.h"
00073 #include "asterisk/linkedlists.h"
00074 
00075 #define INITIAL_NUM_FILES   8
00076 
00077 static char *app0 = "MusicOnHold";
00078 static char *app1 = "WaitMusicOnHold";
00079 static char *app2 = "SetMusicOnHold";
00080 static char *app3 = "StartMusicOnHold";
00081 static char *app4 = "StopMusicOnHold";
00082 
00083 static char *synopsis0 = "Play Music On Hold indefinitely";
00084 static char *synopsis1 = "Wait, playing Music On Hold";
00085 static char *synopsis2 = "Set default Music On Hold class";
00086 static char *synopsis3 = "Play Music On Hold";
00087 static char *synopsis4 = "Stop Playing Music On Hold";
00088 
00089 static char *descrip0 = "MusicOnHold(class): "
00090 "Plays hold music specified by class.  If omitted, the default\n"
00091 "music source for the channel will be used. Set the default \n"
00092 "class with the SetMusicOnHold() application.\n"
00093 "Returns -1 on hangup.\n"
00094 "Never returns otherwise.\n";
00095 
00096 static char *descrip1 = "WaitMusicOnHold(delay): "
00097 "Plays hold music specified number of seconds.  Returns 0 when\n"
00098 "done, or -1 on hangup.  If no hold music is available, the delay will\n"
00099 "still occur with no sound.\n";
00100 
00101 static char *descrip2 = "SetMusicOnHold(class): "
00102 "Sets the default class for music on hold for a given channel.  When\n"
00103 "music on hold is activated, this class will be used to select which\n"
00104 "music is played.\n";
00105 
00106 static char *descrip3 = "StartMusicOnHold(class): "
00107 "Starts playing music on hold, uses default music class for channel.\n"
00108 "Starts playing music specified by class.  If omitted, the default\n"
00109 "music source for the channel will be used.  Always returns 0.\n";
00110 
00111 static char *descrip4 = "StopMusicOnHold: "
00112 "Stops playing music on hold.\n";
00113 
00114 static int respawn_time = 20;
00115 
00116 struct moh_files_state {
00117    struct mohclass *class;
00118    int origwfmt;
00119    int samples;
00120    int sample_queue;
00121    unsigned char pos;
00122    unsigned char save_pos;
00123 };
00124 
00125 #define MOH_QUIET    (1 << 0)
00126 #define MOH_SINGLE      (1 << 1)
00127 #define MOH_CUSTOM      (1 << 2)
00128 #define MOH_RANDOMIZE      (1 << 3)
00129 
00130 struct mohclass {
00131    char name[MAX_MUSICCLASS];
00132    char dir[256];
00133    char args[256];
00134    char mode[80];
00135    /*! A dynamically sized array to hold the list of filenames in "files" mode */
00136    char **filearray;
00137    /*! The current size of the filearray */
00138    int allowed_files;
00139    /*! The current number of files loaded into the filearray */
00140    int total_files;
00141    unsigned int flags;
00142    /*! The format from the MOH source, not applicable to "files" mode */
00143    int format;
00144    /*! The pid of the external application delivering MOH */
00145    int pid;
00146    time_t start;
00147    pthread_t thread;
00148    /*! Source of audio */
00149    int srcfd;
00150    /*! FD for timing source */
00151    int pseudofd;
00152    /*! Number of users */
00153    int inuse;
00154    unsigned int delete:1;
00155    AST_LIST_HEAD_NOLOCK(, mohdata) members;
00156    AST_LIST_ENTRY(mohclass) list;
00157 };
00158 
00159 struct mohdata {
00160    int pipe[2];
00161    int origwfmt;
00162    struct mohclass *parent;
00163    struct ast_frame f;
00164    AST_LIST_ENTRY(mohdata) list;
00165 };
00166 
00167 AST_LIST_HEAD_STATIC(mohclasses, mohclass);
00168 
00169 #define LOCAL_MPG_123 "/usr/local/bin/mpg123"
00170 #define MPG_123 "/usr/bin/mpg123"
00171 #define MAX_MP3S 256
00172 
00173 static int ast_moh_destroy_one(struct mohclass *moh);
00174 static int reload(void);
00175 
00176 static void ast_moh_free_class(struct mohclass **mohclass) 
00177 {
00178    struct mohdata *member;
00179    struct mohclass *class = *mohclass;
00180    int i;
00181    
00182    while ((member = AST_LIST_REMOVE_HEAD(&class->members, list)))
00183       free(member);
00184    
00185    if (class->thread) {
00186       pthread_cancel(class->thread);
00187       class->thread = 0;
00188    }
00189 
00190    if (class->filearray) {
00191       for (i = 0; i < class->total_files; i++)
00192          free(class->filearray[i]);
00193       free(class->filearray);
00194    }
00195 
00196    free(class);
00197    *mohclass = NULL;
00198 }
00199 
00200 
00201 static void moh_files_release(struct ast_channel *chan, void *data)
00202 {
00203    struct moh_files_state *state = chan->music_state;
00204 
00205    if (chan && state) {
00206       if (chan->stream) {
00207                         ast_closestream(chan->stream);
00208                         chan->stream = NULL;
00209                 }
00210       if (option_verbose > 2)
00211          ast_verbose(VERBOSE_PREFIX_3 "Stopped music on hold on %s\n", chan->name);
00212 
00213       if (state->origwfmt && ast_set_write_format(chan, state->origwfmt)) {
00214          ast_log(LOG_WARNING, "Unable to restore channel '%s' to format '%d'\n", chan->name, state->origwfmt);
00215       }
00216       state->save_pos = state->pos;
00217    }
00218    if (state->class->delete && ast_atomic_dec_and_test(&state->class->inuse))
00219       ast_moh_destroy_one(state->class);
00220 }
00221 
00222 
00223 static int ast_moh_files_next(struct ast_channel *chan) 
00224 {
00225    struct moh_files_state *state = chan->music_state;
00226    int tries;
00227 
00228    /* Discontinue a stream if it is running already */
00229    if (chan->stream) {
00230       ast_closestream(chan->stream);
00231       chan->stream = NULL;
00232    }
00233 
00234    /* If a specific file has been saved, use it */
00235    if (state->save_pos) {
00236       state->pos = state->save_pos;
00237       state->save_pos = 0;
00238    } else if (ast_test_flag(state->class, MOH_RANDOMIZE)) {
00239       /* Get a random file and ensure we can open it */
00240       for (tries = 0; tries < 20; tries++) {
00241          state->pos = rand() % state->class->total_files;
00242          if (ast_fileexists(state->class->filearray[state->pos], NULL, NULL) > 0)
00243             break;
00244       }
00245       state->samples = 0;
00246    } else {
00247       /* This is easy, just increment our position and make sure we don't exceed the total file count */
00248       state->pos++;
00249       state->pos %= state->class->total_files;
00250       state->samples = 0;
00251    }
00252 
00253    if (!ast_openstream_full(chan, state->class->filearray[state->pos], chan->language, 1)) {
00254       ast_log(LOG_WARNING, "Unable to open file '%s': %s\n", state->class->filearray[state->pos], strerror(errno));
00255       state->pos++;
00256       state->pos %= state->class->total_files;
00257       return -1;
00258    }
00259 
00260    if (option_debug)
00261       ast_log(LOG_DEBUG, "%s Opened file %d '%s'\n", chan->name, state->pos, state->class->filearray[state->pos]);
00262 
00263    if (state->samples)
00264       ast_seekstream(chan->stream, state->samples, SEEK_SET);
00265 
00266    return 0;
00267 }
00268 
00269 
00270 static struct ast_frame *moh_files_readframe(struct ast_channel *chan) 
00271 {
00272    struct ast_frame *f = NULL;
00273    
00274    if (!(chan->stream && (f = ast_readframe(chan->stream)))) {
00275       if (!ast_moh_files_next(chan))
00276          f = ast_readframe(chan->stream);
00277    }
00278 
00279    return f;
00280 }
00281 
00282 static int moh_files_generator(struct ast_channel *chan, void *data, int len, int samples)
00283 {
00284    struct moh_files_state *state = chan->music_state;
00285    struct ast_frame *f = NULL;
00286    int res = 0;
00287 
00288    state->sample_queue += samples;
00289 
00290    while (state->sample_queue > 0) {
00291       if ((f = moh_files_readframe(chan))) {
00292          state->samples += f->samples;
00293          res = ast_write(chan, f);
00294          state->sample_queue -= f->samples;
00295          ast_frfree(f);
00296          if (res < 0) {
00297             ast_log(LOG_WARNING, "Failed to write frame to '%s': %s\n", chan->name, strerror(errno));
00298             return -1;
00299          }
00300       } else
00301          return -1;  
00302    }
00303    return res;
00304 }
00305 
00306 
00307 static void *moh_files_alloc(struct ast_channel *chan, void *params)
00308 {
00309    struct moh_files_state *state;
00310    struct mohclass *class = params;
00311 
00312    if (!chan->music_state && (state = ast_calloc(1, sizeof(*state)))) {
00313       chan->music_state = state;
00314       state->class = class;
00315    } else 
00316       state = chan->music_state;
00317 
00318    if (state) {
00319       if (state->class != class) {
00320          /* initialize */
00321          memset(state, 0, sizeof(*state));
00322          state->class = class;
00323          if (ast_test_flag(state->class, MOH_RANDOMIZE))
00324             state->pos = ast_random() % class->total_files;
00325       }
00326 
00327       state->origwfmt = chan->writeformat;
00328 
00329       if (option_verbose > 2)
00330          ast_verbose(VERBOSE_PREFIX_3 "Started music on hold, class '%s', on %s\n", class->name, chan->name);
00331    }
00332    
00333    return chan->music_state;
00334 }
00335 
00336 static struct ast_generator moh_file_stream = 
00337 {
00338    alloc: moh_files_alloc,
00339    release: moh_files_release,
00340    generate: moh_files_generator,
00341 };
00342 
00343 static int spawn_mp3(struct mohclass *class)
00344 {
00345    int fds[2];
00346    int files = 0;
00347    char fns[MAX_MP3S][80];
00348    char *argv[MAX_MP3S + 50];
00349    char xargs[256];
00350    char *argptr;
00351    int argc = 0;
00352    DIR *dir = NULL;
00353    struct dirent *de;
00354    sigset_t signal_set, old_set;
00355 
00356    
00357    if (!strcasecmp(class->dir, "nodir")) {
00358       files = 1;
00359    } else {
00360       dir = opendir(class->dir);
00361       if (!dir && !strstr(class->dir,"http://") && !strstr(class->dir,"HTTP://")) {
00362          ast_log(LOG_WARNING, "%s is not a valid directory\n", class->dir);
00363          return -1;
00364       }
00365    }
00366 
00367    if (!ast_test_flag(class, MOH_CUSTOM)) {
00368       argv[argc++] = "mpg123";
00369       argv[argc++] = "-q";
00370       argv[argc++] = "-s";
00371       argv[argc++] = "--mono";
00372       argv[argc++] = "-r";
00373       argv[argc++] = "8000";
00374       
00375       if (!ast_test_flag(class, MOH_SINGLE)) {
00376          argv[argc++] = "-b";
00377          argv[argc++] = "2048";
00378       }
00379       
00380       argv[argc++] = "-f";
00381       
00382       if (ast_test_flag(class, MOH_QUIET))
00383          argv[argc++] = "4096";
00384       else
00385          argv[argc++] = "8192";
00386       
00387       /* Look for extra arguments and add them to the list */
00388       ast_copy_string(xargs, class->args, sizeof(xargs));
00389       argptr = xargs;
00390       while (!ast_strlen_zero(argptr)) {
00391          argv[argc++] = argptr;
00392          strsep(&argptr, ",");
00393       }
00394    } else  {
00395       /* Format arguments for argv vector */
00396       ast_copy_string(xargs, class->args, sizeof(xargs));
00397       argptr = xargs;
00398       while (!ast_strlen_zero(argptr)) {
00399          argv[argc++] = argptr;
00400          strsep(&argptr, " ");
00401       }
00402    }
00403 
00404 
00405    if (strstr(class->dir,"http://") || strstr(class->dir,"HTTP://")) {
00406       ast_copy_string(fns[files], class->dir, sizeof(fns[files]));
00407       argv[argc++] = fns[files];
00408       files++;
00409    } else if (dir) {
00410       while ((de = readdir(dir)) && (files < MAX_MP3S)) {
00411          if ((strlen(de->d_name) > 3) && 
00412              ((ast_test_flag(class, MOH_CUSTOM) && 
00413                (!strcasecmp(de->d_name + strlen(de->d_name) - 4, ".raw") || 
00414                 !strcasecmp(de->d_name + strlen(de->d_name) - 4, ".sln"))) ||
00415               !strcasecmp(de->d_name + strlen(de->d_name) - 4, ".mp3"))) {
00416             ast_copy_string(fns[files], de->d_name, sizeof(fns[files]));
00417             argv[argc++] = fns[files];
00418             files++;
00419          }
00420       }
00421    }
00422    argv[argc] = NULL;
00423    if (dir) {
00424       closedir(dir);
00425    }
00426    if (pipe(fds)) {  
00427       ast_log(LOG_WARNING, "Pipe failed\n");
00428       return -1;
00429    }
00430    if (!files) {
00431       ast_log(LOG_WARNING, "Found no files in '%s'\n", class->dir);
00432       close(fds[0]);
00433       close(fds[1]);
00434       return -1;
00435    }
00436    if (time(NULL) - class->start < respawn_time) {
00437       sleep(respawn_time - (time(NULL) - class->start));
00438    }
00439 
00440    /* Block signals during the fork() */
00441    sigfillset(&signal_set);
00442    pthread_sigmask(SIG_BLOCK, &signal_set, &old_set);
00443 
00444    time(&class->start);
00445    class->pid = fork();
00446    if (class->pid < 0) {
00447       close(fds[0]);
00448       close(fds[1]);
00449       ast_log(LOG_WARNING, "Fork failed: %s\n", strerror(errno));
00450       return -1;
00451    }
00452    if (!class->pid) {
00453       int x;
00454 
00455       if (ast_opt_high_priority)
00456          ast_set_priority(0);
00457 
00458       /* Reset ignored signals back to default */
00459       signal(SIGPIPE, SIG_DFL);
00460       pthread_sigmask(SIG_UNBLOCK, &signal_set, NULL);
00461 
00462       close(fds[0]);
00463       /* Stdout goes to pipe */
00464       dup2(fds[1], STDOUT_FILENO);
00465       /* Close unused file descriptors */
00466       for (x=3;x<8192;x++) {
00467          if (-1 != fcntl(x, F_GETFL)) {
00468             close(x);
00469          }
00470       }
00471       /* Child */
00472       chdir(class->dir);
00473       if (ast_test_flag(class, MOH_CUSTOM)) {
00474          execv(argv[0], argv);
00475       } else {
00476          /* Default install is /usr/local/bin */
00477          execv(LOCAL_MPG_123, argv);
00478          /* Many places have it in /usr/bin */
00479          execv(MPG_123, argv);
00480          /* Check PATH as a last-ditch effort */
00481          execvp("mpg123", argv);
00482       }
00483       ast_log(LOG_WARNING, "Exec failed: %s\n", strerror(errno));
00484       close(fds[1]);
00485       _exit(1);
00486    } else {
00487       /* Parent */
00488       pthread_sigmask(SIG_SETMASK, &old_set, NULL);
00489       close(fds[1]);
00490    }
00491    return fds[0];
00492 }
00493 
00494 static void *monmp3thread(void *data)
00495 {
00496 #define  MOH_MS_INTERVAL      100
00497 
00498    struct mohclass *class = data;
00499    struct mohdata *moh;
00500    char buf[8192];
00501    short sbuf[8192];
00502    int res, res2;
00503    int len;
00504    struct timeval tv, tv_tmp;
00505 
00506    tv.tv_sec = 0;
00507    tv.tv_usec = 0;
00508    for(;/* ever */;) {
00509       pthread_testcancel();
00510       /* Spawn mp3 player if it's not there */
00511       if (class->srcfd < 0) {
00512          if ((class->srcfd = spawn_mp3(class)) < 0) {
00513             ast_log(LOG_WARNING, "Unable to spawn mp3player\n");
00514             /* Try again later */
00515             sleep(500);
00516             pthread_testcancel();
00517          }
00518       }
00519       if (class->pseudofd > -1) {
00520 #ifdef SOLARIS
00521          thr_yield();
00522 #endif
00523          /* Pause some amount of time */
00524          res = read(class->pseudofd, buf, sizeof(buf));
00525          pthread_testcancel();
00526       } else {
00527          long delta;
00528          /* Reliable sleep */
00529          tv_tmp = ast_tvnow();
00530          if (ast_tvzero(tv))
00531             tv = tv_tmp;
00532          delta = ast_tvdiff_ms(tv_tmp, tv);
00533          if (delta < MOH_MS_INTERVAL) {   /* too early */
00534             tv = ast_tvadd(tv, ast_samp2tv(MOH_MS_INTERVAL, 1000));  /* next deadline */
00535             usleep(1000 * (MOH_MS_INTERVAL - delta));
00536             pthread_testcancel();
00537          } else {
00538             ast_log(LOG_NOTICE, "Request to schedule in the past?!?!\n");
00539             tv = tv_tmp;
00540          }
00541          res = 8 * MOH_MS_INTERVAL; /* 8 samples per millisecond */
00542       }
00543       if (AST_LIST_EMPTY(&class->members))
00544          continue;
00545       /* Read mp3 audio */
00546       len = ast_codec_get_len(class->format, res);
00547       
00548       if ((res2 = read(class->srcfd, sbuf, len)) != len) {
00549          if (!res2) {
00550             close(class->srcfd);
00551             class->srcfd = -1;
00552             pthread_testcancel();
00553             if (class->pid > 1) {
00554                kill(class->pid, SIGHUP);
00555                usleep(100000);
00556                kill(class->pid, SIGTERM);
00557                usleep(100000);
00558                kill(class->pid, SIGKILL);
00559                class->pid = 0;
00560             }
00561          } else
00562             ast_log(LOG_DEBUG, "Read %d bytes of audio while expecting %d\n", res2, len);
00563          continue;
00564       }
00565       pthread_testcancel();
00566       AST_LIST_LOCK(&mohclasses);
00567       AST_LIST_TRAVERSE(&class->members, moh, list) {
00568          /* Write data */
00569          if ((res = write(moh->pipe[1], sbuf, res2)) != res2) {
00570             if (option_debug)
00571                ast_log(LOG_DEBUG, "Only wrote %d of %d bytes to pipe\n", res, res2);
00572          }
00573       }
00574       AST_LIST_UNLOCK(&mohclasses);
00575    }
00576    return NULL;
00577 }
00578 
00579 static int moh0_exec(struct ast_channel *chan, void *data)
00580 {
00581    if (ast_moh_start(chan, data, NULL)) {
00582       ast_log(LOG_WARNING, "Unable to start music on hold (class '%s') on channel %s\n", (char *)data, chan->name);
00583       return 0;
00584    }
00585    while (!ast_safe_sleep(chan, 10000));
00586    ast_moh_stop(chan);
00587    return -1;
00588 }
00589 
00590 static int moh1_exec(struct ast_channel *chan, void *data)
00591 {
00592    int res;
00593    if (!data || !atoi(data)) {
00594       ast_log(LOG_WARNING, "WaitMusicOnHold requires an argument (number of seconds to wait)\n");
00595       return -1;
00596    }
00597    if (ast_moh_start(chan, NULL, NULL)) {
00598       ast_log(LOG_WARNING, "Unable to start music on hold for %d seconds on channel %s\n", atoi(data), chan->name);
00599       return 0;
00600    }
00601    res = ast_safe_sleep(chan, atoi(data) * 1000);
00602    ast_moh_stop(chan);
00603    return res;
00604 }
00605 
00606 static int moh2_exec(struct ast_channel *chan, void *data)
00607 {
00608    if (ast_strlen_zero(data)) {
00609       ast_log(LOG_WARNING, "SetMusicOnHold requires an argument (class)\n");
00610       return -1;
00611    }
00612    ast_string_field_set(chan, musicclass, data);
00613    return 0;
00614 }
00615 
00616 static int moh3_exec(struct ast_channel *chan, void *data)
00617 {
00618    char *class = NULL;
00619    if (data && strlen(data))
00620       class = data;
00621    if (ast_moh_start(chan, class, NULL)) 
00622       ast_log(LOG_NOTICE, "Unable to start music on hold class '%s' on channel %s\n", class ? class : "default", chan->name);
00623 
00624    return 0;
00625 }
00626 
00627 static int moh4_exec(struct ast_channel *chan, void *data)
00628 {
00629    ast_moh_stop(chan);
00630 
00631    return 0;
00632 }
00633 
00634 /*! \note This function should be called with the mohclasses list locked */
00635 static struct mohclass *get_mohbyname(const char *name, int warn)
00636 {
00637    struct mohclass *moh = NULL;
00638 
00639    AST_LIST_TRAVERSE(&mohclasses, moh, list) {
00640       if (!strcasecmp(name, moh->name))
00641          break;
00642    }
00643 
00644    if (!moh && warn)
00645       ast_log(LOG_WARNING, "Music on Hold class '%s' not found\n", name);
00646 
00647    return moh;
00648 }
00649 
00650 static struct mohdata *mohalloc(struct mohclass *cl)
00651 {
00652    struct mohdata *moh;
00653    long flags; 
00654    
00655    if (!(moh = ast_calloc(1, sizeof(*moh))))
00656       return NULL;
00657    
00658    if (pipe(moh->pipe)) {
00659       ast_log(LOG_WARNING, "Failed to create pipe: %s\n", strerror(errno));
00660       free(moh);
00661       return NULL;
00662    }
00663 
00664    /* Make entirely non-blocking */
00665    flags = fcntl(moh->pipe[0], F_GETFL);
00666    fcntl(moh->pipe[0], F_SETFL, flags | O_NONBLOCK);
00667    flags = fcntl(moh->pipe[1], F_GETFL);
00668    fcntl(moh->pipe[1], F_SETFL, flags | O_NONBLOCK);
00669 
00670    moh->f.frametype = AST_FRAME_VOICE;
00671    moh->f.subclass = cl->format;
00672    moh->f.offset = AST_FRIENDLY_OFFSET;
00673 
00674    moh->parent = cl;
00675 
00676    AST_LIST_LOCK(&mohclasses);
00677    AST_LIST_INSERT_HEAD(&cl->members, moh, list);
00678    AST_LIST_UNLOCK(&mohclasses);
00679    
00680    return moh;
00681 }
00682 
00683 static void moh_release(struct ast_channel *chan, void *data)
00684 {
00685    struct mohdata *moh = data;
00686    int oldwfmt;
00687 
00688    AST_LIST_LOCK(&mohclasses);
00689    AST_LIST_REMOVE(&moh->parent->members, moh, list); 
00690    AST_LIST_UNLOCK(&mohclasses);
00691    
00692    close(moh->pipe[0]);
00693    close(moh->pipe[1]);
00694    oldwfmt = moh->origwfmt;
00695    if (moh->parent->delete && ast_atomic_dec_and_test(&moh->parent->inuse))
00696       ast_moh_destroy_one(moh->parent);
00697    free(moh);
00698    if (chan) {
00699       if (oldwfmt && ast_set_write_format(chan, oldwfmt)) 
00700          ast_log(LOG_WARNING, "Unable to restore channel '%s' to format %s\n", chan->name, ast_getformatname(oldwfmt));
00701       if (option_verbose > 2)
00702          ast_verbose(VERBOSE_PREFIX_3 "Stopped music on hold on %s\n", chan->name);
00703    }
00704 }
00705 
00706 static void *moh_alloc(struct ast_channel *chan, void *params)
00707 {
00708    struct mohdata *res;
00709    struct mohclass *class = params;
00710 
00711    if ((res = mohalloc(class))) {
00712       res->origwfmt = chan->writeformat;
00713       if (ast_set_write_format(chan, class->format)) {
00714          ast_log(LOG_WARNING, "Unable to set channel '%s' to format '%s'\n", chan->name, ast_codec2str(class->format));
00715          moh_release(NULL, res);
00716          res = NULL;
00717       }
00718       if (option_verbose > 2)
00719          ast_verbose(VERBOSE_PREFIX_3 "Started music on hold, class '%s', on channel '%s'\n", class->name, chan->name);
00720    }
00721    return res;
00722 }
00723 
00724 static int moh_generate(struct ast_channel *chan, void *data, int len, int samples)
00725 {
00726    struct mohdata *moh = data;
00727    short buf[1280 + AST_FRIENDLY_OFFSET / 2];
00728    int res;
00729 
00730    if (!moh->parent->pid)
00731       return -1;
00732 
00733    len = ast_codec_get_len(moh->parent->format, samples);
00734 
00735    if (len > sizeof(buf) - AST_FRIENDLY_OFFSET) {
00736       ast_log(LOG_WARNING, "Only doing %d of %d requested bytes on %s\n", (int)sizeof(buf), len, chan->name);
00737       len = sizeof(buf) - AST_FRIENDLY_OFFSET;
00738    }
00739    res = read(moh->pipe[0], buf + AST_FRIENDLY_OFFSET/2, len);
00740    if (res <= 0)
00741       return 0;
00742 
00743    moh->f.datalen = res;
00744    moh->f.data = buf + AST_FRIENDLY_OFFSET / 2;
00745    moh->f.samples = ast_codec_get_samples(&moh->f);
00746 
00747    if (ast_write(chan, &moh->f) < 0) {
00748       ast_log(LOG_WARNING, "Failed to write frame to '%s': %s\n", chan->name, strerror(errno));
00749       return -1;
00750    }
00751 
00752    return 0;
00753 }
00754 
00755 static struct ast_generator mohgen = 
00756 {
00757    alloc: moh_alloc,
00758    release: moh_release,
00759    generate: moh_generate,
00760 };
00761 
00762 static int moh_add_file(struct mohclass *class, const char *filepath)
00763 {
00764    if (!class->allowed_files) {
00765       if (!(class->filearray = ast_calloc(1, INITIAL_NUM_FILES * sizeof(*class->filearray))))
00766          return -1;
00767       class->allowed_files = INITIAL_NUM_FILES;
00768    } else if (class->total_files == class->allowed_files) {
00769       if (!(class->filearray = ast_realloc(class->filearray, class->allowed_files * sizeof(*class->filearray) * 2))) {
00770          class->allowed_files = 0;
00771          class->total_files = 0;
00772          return -1;
00773       }
00774       class->allowed_files *= 2;
00775    }
00776 
00777    if (!(class->filearray[class->total_files] = ast_strdup(filepath)))
00778       return -1;
00779 
00780    class->total_files++;
00781 
00782    return 0;
00783 }
00784 
00785 static int moh_scan_files(struct mohclass *class) {
00786 
00787    DIR *files_DIR;
00788    struct dirent *files_dirent;
00789    char path[PATH_MAX];
00790    char filepath[PATH_MAX];
00791    char *ext;
00792    struct stat statbuf;
00793    int dirnamelen;
00794    int i;
00795    
00796    files_DIR = opendir(class->dir);
00797    if (!files_DIR) {
00798       ast_log(LOG_WARNING, "Cannot open dir %s or dir does not exist\n", class->dir);
00799       return -1;
00800    }
00801 
00802    for (i = 0; i < class->total_files; i++)
00803       free(class->filearray[i]);
00804 
00805    class->total_files = 0;
00806    dirnamelen = strlen(class->dir) + 2;
00807    getcwd(path, sizeof(path));
00808    chdir(class->dir);
00809    while ((files_dirent = readdir(files_DIR))) {
00810       /* The file name must be at least long enough to have the file type extension */
00811       if ((strlen(files_dirent->d_name) < 4))
00812          continue;
00813 
00814       /* Skip files that starts with a dot */
00815       if (files_dirent->d_name[0] == '.')
00816          continue;
00817 
00818       /* Skip files without extensions... they are not audio */
00819       if (!strchr(files_dirent->d_name, '.'))
00820          continue;
00821 
00822       snprintf(filepath, sizeof(filepath), "%s/%s", class->dir, files_dirent->d_name);
00823 
00824       if (stat(filepath, &statbuf))
00825          continue;
00826 
00827       if (!S_ISREG(statbuf.st_mode))
00828          continue;
00829 
00830       if ((ext = strrchr(filepath, '.'))) {
00831          *ext = '\0';
00832          ext++;
00833       }
00834 
00835       /* if the file is present in multiple formats, ensure we only put it into the list once */
00836       for (i = 0; i < class->total_files; i++)
00837          if (!strcmp(filepath, class->filearray[i]))
00838             break;
00839 
00840       if (i == class->total_files) {
00841          if (moh_add_file(class, filepath))
00842             break;
00843       }
00844    }
00845 
00846    closedir(files_DIR);
00847    chdir(path);
00848    return class->total_files;
00849 }
00850 
00851 static int moh_register(struct mohclass *moh, int reload)
00852 {
00853 #ifdef HAVE_ZAPTEL
00854    int x;
00855 #endif
00856    struct mohclass *mohclass = NULL;
00857 
00858    AST_LIST_LOCK(&mohclasses);
00859    if ((mohclass = get_mohbyname(moh->name, 0))) {
00860       mohclass->delete = 0;
00861       if (reload) {
00862          ast_log(LOG_DEBUG, "Music on Hold class '%s' left alone from initial load.\n", moh->name);
00863       } else {
00864          ast_log(LOG_WARNING, "Music on Hold class '%s' already exists\n", moh->name);
00865       }
00866       free(moh);  
00867       AST_LIST_UNLOCK(&mohclasses);
00868       return -1;
00869    }
00870    AST_LIST_UNLOCK(&mohclasses);
00871 
00872    time(&moh->start);
00873    moh->start -= respawn_time;
00874    
00875    if (!strcasecmp(moh->mode, "files")) {
00876       if (!moh_scan_files(moh)) {
00877          ast_moh_free_class(&moh);
00878          return -1;
00879       }
00880       if (strchr(moh->args, 'r'))
00881          ast_set_flag(moh, MOH_RANDOMIZE);
00882    } else if (!strcasecmp(moh->mode, "mp3") || !strcasecmp(moh->mode, "mp3nb") || !strcasecmp(moh->mode, "quietmp3") || !strcasecmp(moh->mode, "quietmp3nb") || !strcasecmp(moh->mode, "httpmp3") || !strcasecmp(moh->mode, "custom")) {
00883 
00884       if (!strcasecmp(moh->mode, "custom"))
00885          ast_set_flag(moh, MOH_CUSTOM);
00886       else if (!strcasecmp(moh->mode, "mp3nb"))
00887          ast_set_flag(moh, MOH_SINGLE);
00888       else if (!strcasecmp(moh->mode, "quietmp3nb"))
00889          ast_set_flag(moh, MOH_SINGLE | MOH_QUIET);
00890       else if (!strcasecmp(moh->mode, "quietmp3"))
00891          ast_set_flag(moh, MOH_QUIET);
00892       
00893       moh->srcfd = -1;
00894 #ifdef HAVE_ZAPTEL
00895       /* Open /dev/zap/pseudo for timing...  Is
00896          there a better, yet reliable way to do this? */
00897       moh->pseudofd = open("/dev/zap/pseudo", O_RDONLY);
00898       if (moh->pseudofd < 0) {
00899          ast_log(LOG_WARNING, "Unable to open pseudo channel for timing...  Sound may be choppy.\n");
00900       } else {
00901          x = 320;
00902          ioctl(moh->pseudofd, ZT_SET_BLOCKSIZE, &x);
00903       }
00904 #else
00905       moh->pseudofd = -1;
00906 #endif
00907       if (ast_pthread_create_background(&moh->thread, NULL, monmp3thread, moh)) {
00908          ast_log(LOG_WARNING, "Unable to create moh...\n");
00909          if (moh->pseudofd > -1)
00910             close(moh->pseudofd);
00911          ast_moh_free_class(&moh);
00912          return -1;
00913       }
00914    } else {
00915       ast_log(LOG_WARNING, "Don't know how to do a mode '%s' music on hold\n", moh->mode);
00916       ast_moh_free_class(&moh);
00917       return -1;
00918    }
00919 
00920    AST_LIST_LOCK(&mohclasses);
00921    AST_LIST_INSERT_HEAD(&mohclasses, moh, list);
00922    AST_LIST_UNLOCK(&mohclasses);
00923    
00924    return 0;
00925 }
00926 
00927 static void local_ast_moh_cleanup(struct ast_channel *chan)
00928 {
00929    if (chan->music_state) {
00930       free(chan->music_state);
00931       chan->music_state = NULL;
00932    }
00933 }
00934 
00935 static int local_ast_moh_start(struct ast_channel *chan, const char *mclass, const char *interpclass)
00936 {
00937    struct mohclass *mohclass = NULL;
00938 
00939    /* The following is the order of preference for which class to use:
00940     * 1) The channels explicitly set musicclass, which should *only* be
00941     *    set by a call to Set(CHANNEL(musicclass)=whatever) in the dialplan.
00942     * 2) The mclass argument. If a channel is calling ast_moh_start() as the
00943     *    result of receiving a HOLD control frame, this should be the
00944     *    payload that came with the frame.
00945     * 3) The interpclass argument. This would be from the mohinterpret
00946     *    option from channel drivers. This is the same as the old musicclass
00947     *    option.
00948     * 4) The default class.
00949     */
00950    AST_LIST_LOCK(&mohclasses);
00951    if (!ast_strlen_zero(chan->musicclass))
00952       mohclass = get_mohbyname(chan->musicclass, 1);
00953    if (!mohclass && !ast_strlen_zero(mclass))
00954       mohclass = get_mohbyname(mclass, 1);
00955    if (!mohclass && !ast_strlen_zero(interpclass))
00956       mohclass = get_mohbyname(interpclass, 1);
00957    if (!mohclass) 
00958       mohclass = get_mohbyname("default", 1);
00959    if (mohclass)
00960       ast_atomic_fetchadd_int(&mohclass->inuse, +1);
00961    AST_LIST_UNLOCK(&mohclasses);
00962 
00963    if (!mohclass)
00964       return -1;
00965 
00966    ast_set_flag(chan, AST_FLAG_MOH);
00967    if (mohclass->total_files) {
00968       return ast_activate_generator(chan, &moh_file_stream, mohclass);
00969    } else
00970       return ast_activate_generator(chan, &mohgen, mohclass);
00971 }
00972 
00973 static void local_ast_moh_stop(struct ast_channel *chan)
00974 {
00975    ast_clear_flag(chan, AST_FLAG_MOH);
00976    ast_deactivate_generator(chan);
00977 
00978    if (chan->music_state) {
00979       if (chan->stream) {
00980          ast_closestream(chan->stream);
00981          chan->stream = NULL;
00982       }
00983    }
00984 }
00985 
00986 static struct mohclass *moh_class_malloc(void)
00987 {
00988    struct mohclass *class;
00989 
00990    if ((class = ast_calloc(1, sizeof(*class))))
00991       class->format = AST_FORMAT_SLINEAR;
00992 
00993    return class;
00994 }
00995 
00996 static int load_moh_classes(int reload)
00997 {
00998    struct ast_config *cfg;
00999    struct ast_variable *var;
01000    struct mohclass *class; 
01001    char *data;
01002    char *args;
01003    char *cat;
01004    int numclasses = 0;
01005    static int dep_warning = 0;
01006 
01007    cfg = ast_config_load("musiconhold.conf");
01008 
01009    if (!cfg)
01010       return 0;
01011 
01012    if (reload) {
01013       AST_LIST_LOCK(&mohclasses);
01014       AST_LIST_TRAVERSE(&mohclasses, class, list)
01015          class->delete = 1;
01016       AST_LIST_UNLOCK(&mohclasses);
01017    }
01018 
01019    cat = ast_category_browse(cfg, NULL);
01020    for (; cat; cat = ast_category_browse(cfg, cat)) {
01021       if (strcasecmp(cat, "classes") && strcasecmp(cat, "moh_files")) {       
01022          if (!(class = moh_class_malloc())) {
01023             break;
01024          }           
01025          ast_copy_string(class->name, cat, sizeof(class->name));  
01026          var = ast_variable_browse(cfg, cat);
01027          while (var) {
01028             if (!strcasecmp(var->name, "mode"))
01029                ast_copy_string(class->mode, var->value, sizeof(class->mode)); 
01030             else if (!strcasecmp(var->name, "directory"))
01031                ast_copy_string(class->dir, var->value, sizeof(class->dir));
01032             else if (!strcasecmp(var->name, "application"))
01033                ast_copy_string(class->args, var->value, sizeof(class->args));
01034             else if (!strcasecmp(var->name, "random"))
01035                ast_set2_flag(class, ast_true(var->value), MOH_RANDOMIZE);
01036             else if (!strcasecmp(var->name, "format")) {
01037                class->format = ast_getformatbyname(var->value);
01038                if (!class->format) {
01039                   ast_log(LOG_WARNING, "Unknown format '%s' -- defaulting to SLIN\n", var->value);
01040                   class->format = AST_FORMAT_SLINEAR;
01041                }
01042             }
01043             var = var->next;
01044          }
01045 
01046          if (ast_strlen_zero(class->dir)) {
01047             if (!strcasecmp(class->mode, "custom")) {
01048                strcpy(class->dir, "nodir");
01049             } else {
01050                ast_log(LOG_WARNING, "A directory must be specified for class '%s'!\n", class->name);
01051                free(class);
01052                continue;
01053             }
01054          }
01055          if (ast_strlen_zero(class->mode)) {
01056             ast_log(LOG_WARNING, "A mode must be specified for class '%s'!\n", class->name);
01057             free(class);
01058             continue;
01059          }
01060          if (ast_strlen_zero(class->args) && !strcasecmp(class->mode, "custom")) {
01061             ast_log(LOG_WARNING, "An application must be specified for class '%s'!\n", class->name);
01062             free(class);
01063             continue;
01064          }
01065 
01066          /* Don't leak a class when it's already registered */
01067          moh_register(class, reload);
01068 
01069          numclasses++;
01070       }
01071    }
01072    
01073 
01074    /* Deprecated Old-School Configuration */
01075    var = ast_variable_browse(cfg, "classes");
01076    while (var) {
01077       if (!dep_warning) {
01078          ast_log(LOG_WARNING, "The old musiconhold.conf syntax has been deprecated!  Please refer to the sample configuration for information on the new syntax.\n");
01079          dep_warning = 1;
01080       }
01081       data = strchr(var->value, ':');
01082       if (data) {
01083          *data++ = '\0';
01084          args = strchr(data, ',');
01085          if (args)
01086             *args++ = '\0';
01087          if (!(get_mohbyname(var->name, 0))) {        
01088             if (!(class = moh_class_malloc())) {
01089                break;
01090             }
01091             
01092             ast_copy_string(class->name, var->name, sizeof(class->name));
01093             ast_copy_string(class->dir, data, sizeof(class->dir));
01094             ast_copy_string(class->mode, var->value, sizeof(class->mode));
01095             if (args)
01096                ast_copy_string(class->args, args, sizeof(class->args));
01097             
01098             moh_register(class, reload);
01099             numclasses++;
01100          }
01101       }
01102       var = var->next;
01103    }
01104    var = ast_variable_browse(cfg, "moh_files");
01105    while (var) {
01106       if (!dep_warning) {
01107          ast_log(LOG_WARNING, "The old musiconhold.conf syntax has been deprecated!  Please refer to the sample configuration for information on the new syntax.\n");
01108          dep_warning = 1;
01109       }
01110       if (!(get_mohbyname(var->name, 0))) {
01111          args = strchr(var->value, ',');
01112          if (args)
01113             *args++ = '\0';         
01114          if (!(class = moh_class_malloc())) {
01115             break;
01116          }
01117          
01118          ast_copy_string(class->name, var->name, sizeof(class->name));
01119          ast_copy_string(class->dir, var->value, sizeof(class->dir));
01120          strcpy(class->mode, "files");
01121          if (args)   
01122             ast_copy_string(class->args, args, sizeof(class->args));
01123          
01124          moh_register(class, reload);
01125          numclasses++;
01126       }
01127       var = var->next;
01128    }
01129 
01130    ast_config_destroy(cfg);
01131 
01132    return numclasses;
01133 }
01134 
01135 static int ast_moh_destroy_one(struct mohclass *moh)
01136 {
01137    char buff[8192];
01138    int bytes, tbytes = 0, stime = 0, pid = 0;
01139 
01140    if (moh) {
01141       if (moh->pid > 1) {
01142          ast_log(LOG_DEBUG, "killing %d!\n", moh->pid);
01143          stime = time(NULL) + 2;
01144          pid = moh->pid;
01145          moh->pid = 0;
01146          /* Back when this was just mpg123, SIGKILL was fine.  Now we need
01147           * to give the process a reason and time enough to kill off its
01148           * children. */
01149          kill(pid, SIGHUP);
01150          usleep(100000);
01151          kill(pid, SIGTERM);
01152          usleep(100000);
01153          kill(pid, SIGKILL);
01154          while ((ast_wait_for_input(moh->srcfd, 100) > 0) && (bytes = read(moh->srcfd, buff, 8192)) && time(NULL) < stime)
01155             tbytes = tbytes + bytes;
01156          ast_log(LOG_DEBUG, "mpg123 pid %d and child died after %d bytes read\n", pid, tbytes);
01157          close(moh->srcfd);
01158       }
01159       ast_moh_free_class(&moh);
01160    }
01161 
01162    return 0;
01163 }
01164 
01165 static void ast_moh_destroy(void)
01166 {
01167    struct mohclass *moh;
01168 
01169    if (option_verbose > 1)
01170       ast_verbose(VERBOSE_PREFIX_2 "Destroying musiconhold processes\n");
01171 
01172    AST_LIST_LOCK(&mohclasses);
01173    while ((moh = AST_LIST_REMOVE_HEAD(&mohclasses, list))) {
01174       ast_moh_destroy_one(moh);
01175    }
01176    AST_LIST_UNLOCK(&mohclasses);
01177 }
01178 
01179 static int moh_cli(int fd, int argc, char *argv[]) 
01180 {
01181    reload();
01182    return 0;
01183 }
01184 
01185 static int cli_files_show(int fd, int argc, char *argv[])
01186 {
01187    int i;
01188    struct mohclass *class;
01189 
01190    AST_LIST_LOCK(&mohclasses);
01191    AST_LIST_TRAVERSE(&mohclasses, class, list) {
01192       if (!class->total_files)
01193          continue;
01194 
01195       ast_cli(fd, "Class: %s\n", class->name);
01196       for (i = 0; i < class->total_files; i++)
01197          ast_cli(fd, "\tFile: %s\n", class->filearray[i]);
01198    }
01199    AST_LIST_UNLOCK(&mohclasses);
01200 
01201    return 0;
01202 }
01203 
01204 static int moh_classes_show(int fd, int argc, char *argv[])
01205 {
01206    struct mohclass *class;
01207 
01208    AST_LIST_LOCK(&mohclasses);
01209    AST_LIST_TRAVERSE(&mohclasses, class, list) {
01210       ast_cli(fd, "Class: %s\n", class->name);
01211       ast_cli(fd, "\tMode: %s\n", S_OR(class->mode, "<none>"));
01212       ast_cli(fd, "\tDirectory: %s\n", S_OR(class->dir, "<none>"));
01213       ast_cli(fd, "\tUse Count: %d\n", class->inuse);
01214       if (ast_test_flag(class, MOH_CUSTOM))
01215          ast_cli(fd, "\tApplication: %s\n", S_OR(class->args, "<none>"));
01216       if (strcasecmp(class->mode, "files"))
01217          ast_cli(fd, "\tFormat: %s\n", ast_getformatname(class->format));
01218    }
01219    AST_LIST_UNLOCK(&mohclasses);
01220 
01221    return 0;
01222 }
01223 
01224 static struct ast_cli_entry cli_moh_classes_show_deprecated = {
01225    { "moh", "classes", "show"},
01226    moh_classes_show, NULL,
01227    NULL };
01228 
01229 static struct ast_cli_entry cli_moh_files_show_deprecated = {
01230    { "moh", "files", "show"},
01231    cli_files_show, NULL,
01232    NULL };
01233 
01234 static struct ast_cli_entry cli_moh[] = {
01235    { { "moh", "reload"},
01236    moh_cli, "Music On Hold",
01237    "Music On Hold" },
01238 
01239    { { "moh", "show", "classes"},
01240    moh_classes_show, "List MOH classes",
01241    "Lists all MOH classes", NULL, &cli_moh_classes_show_deprecated },
01242 
01243    { { "moh", "show", "files"},
01244    cli_files_show, "List MOH file-based classes",
01245    "Lists all loaded file-based MOH classes and their files", NULL, &cli_moh_files_show_deprecated },
01246 };
01247 
01248 static int init_classes(int reload) 
01249 {
01250    struct mohclass *moh;
01251     
01252    if (!load_moh_classes(reload))      /* Load classes from config */
01253       return 0;         /* Return if nothing is found */
01254 
01255    AST_LIST_LOCK(&mohclasses);
01256    AST_LIST_TRAVERSE_SAFE_BEGIN(&mohclasses, moh, list) {
01257       if (reload && moh->delete) {
01258          AST_LIST_REMOVE_CURRENT(&mohclasses, list);
01259          if (!moh->inuse)
01260             ast_moh_destroy_one(moh);
01261       } else if (moh->total_files)
01262          moh_scan_files(moh);
01263    }
01264    AST_LIST_TRAVERSE_SAFE_END
01265    AST_LIST_UNLOCK(&mohclasses);
01266 
01267    return 1;
01268 }
01269 
01270 static int load_module(void)
01271 {
01272    int res;
01273 
01274    res = ast_register_application(app0, moh0_exec, synopsis0, descrip0);
01275    ast_register_atexit(ast_moh_destroy);
01276    ast_cli_register_multiple(cli_moh, sizeof(cli_moh) / sizeof(struct ast_cli_entry));
01277    if (!res)
01278       res = ast_register_application(app1, moh1_exec, synopsis1, descrip1);
01279    if (!res)
01280       res = ast_register_application(app2, moh2_exec, synopsis2, descrip2);
01281    if (!res)
01282       res = ast_register_application(app3, moh3_exec, synopsis3, descrip3);
01283    if (!res)
01284       res = ast_register_application(app4, moh4_exec, synopsis4, descrip4);
01285 
01286    if (!init_classes(0)) {    /* No music classes configured, so skip it */
01287       ast_log(LOG_WARNING, "No music on hold classes configured, disabling music on hold.\n");
01288    } else {
01289       ast_install_music_functions(local_ast_moh_start, local_ast_moh_stop, local_ast_moh_cleanup);
01290    }
01291 
01292    return 0;
01293 }
01294 
01295 static int reload(void)
01296 {
01297    if (init_classes(1))
01298       ast_install_music_functions(local_ast_moh_start, local_ast_moh_stop, local_ast_moh_cleanup);
01299 
01300    return 0;
01301 }
01302 
01303 static int unload_module(void)
01304 {
01305    return -1;
01306 }
01307 
01308 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Music On Hold Resource",
01309       .load = load_module,
01310       .unload = unload_module,
01311       .reload = reload,
01312           );

Generated on Fri Aug 24 02:22:17 2007 for Asterisk - the Open Source PBX by  doxygen 1.5.1