Mon Sep 18 09:13:29 2006

Asterisk developer's documentation


res_monitor.c

Go to the documentation of this file.
00001 /*
00002  * Asterisk -- An open source telephony toolkit.
00003  *
00004  * Copyright (C) 1999 - 2005, 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 PBX channel monitoring
00022  *
00023  */
00024  
00025 #include <stdio.h>
00026 #include <stdlib.h>
00027 #include <errno.h>
00028 #include <string.h>
00029 #include <sys/types.h>
00030 #include <sys/stat.h>
00031 #include <libgen.h>     /* dirname() */
00032 
00033 #include "asterisk.h"
00034 
00035 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 11561 $")
00036 
00037 #include "asterisk/lock.h"
00038 #include "asterisk/channel.h"
00039 #include "asterisk/logger.h"
00040 #include "asterisk/file.h"
00041 #include "asterisk/pbx.h"
00042 #include "asterisk/module.h"
00043 #include "asterisk/manager.h"
00044 #include "asterisk/cli.h"
00045 #include "asterisk/monitor.h"
00046 #include "asterisk/app.h"
00047 #include "asterisk/utils.h"
00048 #include "asterisk/config.h"
00049 
00050 AST_MUTEX_DEFINE_STATIC(monitorlock);
00051 
00052 static unsigned long seq = 0;
00053 
00054 static char *monitor_synopsis = "Monitor a channel";
00055 
00056 static char *monitor_descrip = "Monitor([file_format[:urlbase]|[fname_base]|[options]]):\n"
00057 "Used to start monitoring a channel. The channel's input and output\n"
00058 "voice packets are logged to files until the channel hangs up or\n"
00059 "monitoring is stopped by the StopMonitor application.\n"
00060 "  file_format    optional, if not set, defaults to \"wav\"\n"
00061 "  fname_base     if set, changes the filename used to the one specified.\n"
00062 "  options:\n"
00063 "    m   - when the recording ends mix the two leg files into one and\n"
00064 "          delete the two leg files.  If the variable MONITOR_EXEC is set, the\n"
00065 "          application referenced in it will be executed instead of\n"
00066 "          soxmix and the raw leg files will NOT be deleted automatically.\n"
00067 "          soxmix or MONITOR_EXEC is handed 3 arguments, the two leg files\n"
00068 "          and a target mixed file name which is the same as the leg file names\n"
00069 "          only without the in/out designator.\n"
00070 "          If MONITOR_EXEC_ARGS is set, the contents will be passed on as\n"
00071 "          additional arguements to MONITOR_EXEC\n"
00072 "          Both MONITOR_EXEC and the Mix flag can be set from the\n"
00073 "          administrator interface\n"
00074 "\n"
00075 "    b   - Don't begin recording unless a call is bridged to another channel\n"
00076 "\nReturns -1 if monitor files can't be opened or if the channel is already\n"
00077 "monitored, otherwise 0.\n"
00078 ;
00079 
00080 static char *stopmonitor_synopsis = "Stop monitoring a channel";
00081 
00082 static char *stopmonitor_descrip = "StopMonitor\n"
00083    "Stops monitoring a channel. Has no effect if the channel is not monitored\n";
00084 
00085 static char *changemonitor_synopsis = "Change monitoring filename of a channel";
00086 
00087 static char *changemonitor_descrip = "ChangeMonitor(filename_base)\n"
00088    "Changes monitoring filename of a channel. Has no effect if the channel is not monitored\n"
00089    "The argument is the new filename base to use for monitoring this channel.\n";
00090 
00091 /* Start monitoring a channel */
00092 int ast_monitor_start(  struct ast_channel *chan, const char *format_spec,
00093       const char *fname_base, int need_lock)
00094 {
00095    int res = 0;
00096    char tmp[256];
00097 
00098    if (need_lock) {
00099       if (ast_mutex_lock(&chan->lock)) {
00100          ast_log(LOG_WARNING, "Unable to lock channel\n");
00101          return -1;
00102       }
00103    }
00104 
00105    if (!(chan->monitor)) {
00106       struct ast_channel_monitor *monitor;
00107       char *channel_name, *p;
00108 
00109       /* Create monitoring directory if needed */
00110       if (mkdir(ast_config_AST_MONITOR_DIR, 0770) < 0) {
00111          if (errno != EEXIST) {
00112             ast_log(LOG_WARNING, "Unable to create audio monitor directory: %s\n",
00113                strerror(errno));
00114          }
00115       }
00116 
00117       monitor = malloc(sizeof(struct ast_channel_monitor));
00118       if (!monitor) {
00119          if (need_lock) 
00120             ast_mutex_unlock(&chan->lock);
00121          return -1;
00122       }
00123       memset(monitor, 0, sizeof(struct ast_channel_monitor));
00124 
00125       /* Determine file names */
00126       if (!ast_strlen_zero(fname_base)) {
00127          int directory = strchr(fname_base, '/') ? 1 : 0;
00128          /* try creating the directory just in case it doesn't exist */
00129          if (directory) {
00130             char *name = strdup(fname_base);
00131             snprintf(tmp, sizeof(tmp), "mkdir -p \"%s\"",dirname(name));
00132             free(name);
00133             ast_safe_system(tmp);
00134          }
00135          snprintf(monitor->read_filename, FILENAME_MAX, "%s/%s-in",
00136                   directory ? "" : ast_config_AST_MONITOR_DIR, fname_base);
00137          snprintf(monitor->write_filename, FILENAME_MAX, "%s/%s-out",
00138                   directory ? "" : ast_config_AST_MONITOR_DIR, fname_base);
00139          ast_copy_string(monitor->filename_base, fname_base, sizeof(monitor->filename_base));
00140       } else {
00141          ast_mutex_lock(&monitorlock);
00142          snprintf(monitor->read_filename, FILENAME_MAX, "%s/audio-in-%ld",
00143                   ast_config_AST_MONITOR_DIR, seq);
00144          snprintf(monitor->write_filename, FILENAME_MAX, "%s/audio-out-%ld",
00145                   ast_config_AST_MONITOR_DIR, seq);
00146          seq++;
00147          ast_mutex_unlock(&monitorlock);
00148 
00149          if((channel_name = ast_strdupa(chan->name))) {
00150             while((p = strchr(channel_name, '/'))) {
00151                *p = '-';
00152             }
00153             snprintf(monitor->filename_base, FILENAME_MAX, "%s/%d-%s",
00154                    ast_config_AST_MONITOR_DIR, (int)time(NULL),channel_name);
00155             monitor->filename_changed = 1;
00156          } else {
00157             ast_log(LOG_ERROR,"Failed to allocate Memory\n");
00158             return -1;
00159          }
00160       }
00161 
00162       monitor->stop = ast_monitor_stop;
00163 
00164       /* Determine file format */
00165       if (!ast_strlen_zero(format_spec)) {
00166          monitor->format = strdup(format_spec);
00167       } else {
00168          monitor->format = strdup("wav");
00169       }
00170       
00171       /* open files */
00172       if (ast_fileexists(monitor->read_filename, NULL, NULL) > 0) {
00173          ast_filedelete(monitor->read_filename, NULL);
00174       }
00175       if (!(monitor->read_stream = ast_writefile(monitor->read_filename,
00176                   monitor->format, NULL,
00177                   O_CREAT|O_TRUNC|O_WRONLY, 0, 0644))) {
00178          ast_log(LOG_WARNING, "Could not create file %s\n",
00179                   monitor->read_filename);
00180          free(monitor);
00181          ast_mutex_unlock(&chan->lock);
00182          return -1;
00183       }
00184       if (ast_fileexists(monitor->write_filename, NULL, NULL) > 0) {
00185          ast_filedelete(monitor->write_filename, NULL);
00186       }
00187       if (!(monitor->write_stream = ast_writefile(monitor->write_filename,
00188                   monitor->format, NULL,
00189                   O_CREAT|O_TRUNC|O_WRONLY, 0, 0644))) {
00190          ast_log(LOG_WARNING, "Could not create file %s\n",
00191                   monitor->write_filename);
00192          ast_closestream(monitor->read_stream);
00193          free(monitor);
00194          ast_mutex_unlock(&chan->lock);
00195          return -1;
00196       }
00197       chan->monitor = monitor;
00198       /* so we know this call has been monitored in case we need to bill for it or something */
00199       pbx_builtin_setvar_helper(chan, "__MONITORED","true");
00200    } else {
00201       ast_log(LOG_DEBUG,"Cannot start monitoring %s, already monitored\n",
00202                chan->name);
00203       res = -1;
00204    }
00205 
00206    if (need_lock) {
00207       ast_mutex_unlock(&chan->lock);
00208    }
00209    return res;
00210 }
00211 
00212 /* Stop monitoring a channel */
00213 int ast_monitor_stop(struct ast_channel *chan, int need_lock)
00214 {
00215    char *execute, *execute_args;
00216    int delfiles = 0;
00217 
00218    if (need_lock) {
00219       if (ast_mutex_lock(&chan->lock)) {
00220          ast_log(LOG_WARNING, "Unable to lock channel\n");
00221          return -1;
00222       }
00223    }
00224 
00225    if (chan->monitor) {
00226       char filename[ FILENAME_MAX ];
00227 
00228       if (chan->monitor->read_stream) {
00229          ast_closestream(chan->monitor->read_stream);
00230       }
00231       if (chan->monitor->write_stream) {
00232          ast_closestream(chan->monitor->write_stream);
00233       }
00234 
00235       if (chan->monitor->filename_changed && !ast_strlen_zero(chan->monitor->filename_base)) {
00236          if (ast_fileexists(chan->monitor->read_filename,NULL,NULL) > 0) {
00237             snprintf(filename, FILENAME_MAX, "%s-in", chan->monitor->filename_base);
00238             if (ast_fileexists(filename, NULL, NULL) > 0) {
00239                ast_filedelete(filename, NULL);
00240             }
00241             ast_filerename(chan->monitor->read_filename, filename, chan->monitor->format);
00242          } else {
00243             ast_log(LOG_WARNING, "File %s not found\n", chan->monitor->read_filename);
00244          }
00245 
00246          if (ast_fileexists(chan->monitor->write_filename,NULL,NULL) > 0) {
00247             snprintf(filename, FILENAME_MAX, "%s-out", chan->monitor->filename_base);
00248             if (ast_fileexists(filename, NULL, NULL) > 0) {
00249                ast_filedelete(filename, NULL);
00250             }
00251             ast_filerename(chan->monitor->write_filename, filename, chan->monitor->format);
00252          } else {
00253             ast_log(LOG_WARNING, "File %s not found\n", chan->monitor->write_filename);
00254          }
00255       }
00256 
00257       if (chan->monitor->joinfiles && !ast_strlen_zero(chan->monitor->filename_base)) {
00258          char tmp[1024];
00259          char tmp2[1024];
00260          char *format = !strcasecmp(chan->monitor->format,"wav49") ? "WAV" : chan->monitor->format;
00261          char *name = chan->monitor->filename_base;
00262          int directory = strchr(name, '/') ? 1 : 0;
00263          char *dir = directory ? "" : ast_config_AST_MONITOR_DIR;
00264 
00265          /* Set the execute application */
00266          execute = pbx_builtin_getvar_helper(chan, "MONITOR_EXEC");
00267          if (ast_strlen_zero(execute)) { 
00268             execute = "nice -n 19 soxmix";
00269             delfiles = 1;
00270          } 
00271          execute_args = pbx_builtin_getvar_helper(chan, "MONITOR_EXEC_ARGS");
00272          if (ast_strlen_zero(execute_args)) {
00273             execute_args = "";
00274          }
00275          
00276          snprintf(tmp, sizeof(tmp), "%s \"%s/%s-in.%s\" \"%s/%s-out.%s\" \"%s/%s.%s\" %s &", execute, dir, name, format, dir, name, format, dir, name, format,execute_args);
00277          if (delfiles) {
00278             snprintf(tmp2,sizeof(tmp2), "( %s& rm -f \"%s/%s-\"* ) &",tmp, dir ,name); /* remove legs when done mixing */
00279             ast_copy_string(tmp, tmp2, sizeof(tmp));
00280          }
00281          ast_log(LOG_DEBUG,"monitor executing %s\n",tmp);
00282          if (ast_safe_system(tmp) == -1)
00283             ast_log(LOG_WARNING, "Execute of %s failed.\n",tmp);
00284       }
00285       
00286       free(chan->monitor->format);
00287       free(chan->monitor);
00288       chan->monitor = NULL;
00289    }
00290 
00291    if (need_lock)
00292       ast_mutex_unlock(&chan->lock);
00293    return 0;
00294 }
00295 
00296 /* Change monitoring filename of a channel */
00297 int ast_monitor_change_fname(struct ast_channel *chan, const char *fname_base, int need_lock)
00298 {
00299    char tmp[256];
00300    if (ast_strlen_zero(fname_base)) {
00301       ast_log(LOG_WARNING, "Cannot change monitor filename of channel %s to null", chan->name);
00302       return -1;
00303    }
00304    
00305    if (need_lock) {
00306       if (ast_mutex_lock(&chan->lock)) {
00307          ast_log(LOG_WARNING, "Unable to lock channel\n");
00308          return -1;
00309       }
00310    }
00311 
00312    if (chan->monitor) {
00313       int directory = strchr(fname_base, '/') ? 1 : 0;
00314       /* try creating the directory just in case it doesn't exist */
00315       if (directory) {
00316          char *name = strdup(fname_base);
00317          snprintf(tmp, sizeof(tmp), "mkdir -p %s",dirname(name));
00318          free(name);
00319          ast_safe_system(tmp);
00320       }
00321 
00322       snprintf(chan->monitor->filename_base, FILENAME_MAX, "%s/%s", directory ? "" : ast_config_AST_MONITOR_DIR, fname_base);
00323    } else {
00324       ast_log(LOG_WARNING, "Cannot change monitor filename of channel %s to %s, monitoring not started\n", chan->name, fname_base);
00325    }
00326 
00327    if (need_lock)
00328       ast_mutex_unlock(&chan->lock);
00329 
00330    return 0;
00331 }
00332 
00333 static int start_monitor_exec(struct ast_channel *chan, void *data)
00334 {
00335    char *arg = NULL;
00336    char *format = NULL;
00337    char *fname_base = NULL;
00338    char *options = NULL;
00339    char *delay = NULL;
00340    char *urlprefix = NULL;
00341    char tmp[256];
00342    int joinfiles = 0;
00343    int waitforbridge = 0;
00344    int res = 0;
00345    
00346    /* Parse arguments. */
00347    if (!ast_strlen_zero((char*)data)) {
00348       arg = ast_strdupa((char*)data);
00349       format = arg;
00350       fname_base = strchr(arg, '|');
00351       if (fname_base) {
00352          *fname_base = 0;
00353          fname_base++;
00354          if ((options = strchr(fname_base, '|'))) {
00355             *options = 0;
00356             options++;
00357             if (strchr(options, 'm'))
00358                joinfiles = 1;
00359             if (strchr(options, 'b'))
00360                waitforbridge = 1;
00361          }
00362       }
00363       arg = strchr(format,':');
00364       if (arg) {
00365          *arg++ = 0;
00366          urlprefix = arg;
00367       }
00368    }
00369    if (urlprefix) {
00370       snprintf(tmp,sizeof(tmp) - 1,"%s/%s.%s",urlprefix,fname_base,
00371          ((strcmp(format,"gsm")) ? "wav" : "gsm"));
00372       if (!chan->cdr)
00373          chan->cdr = ast_cdr_alloc();
00374       ast_cdr_setuserfield(chan, tmp);
00375    }
00376    if (waitforbridge) {
00377       /* We must remove the "b" option if listed.  In principle none of
00378          the following could give NULL results, but we check just to
00379          be pedantic. Reconstructing with checks for 'm' option does not
00380          work if we end up adding more options than 'm' in the future. */
00381       delay = ast_strdupa((char*)data);
00382       if (delay) {
00383          options = strrchr(delay, '|');
00384          if (options) {
00385             arg = strchr(options, 'b');
00386             if (arg) {
00387                *arg = 'X';
00388                pbx_builtin_setvar_helper(chan,"AUTO_MONITOR",delay);
00389             }
00390          }
00391       }
00392       return 0;
00393    }
00394 
00395    res = ast_monitor_start(chan, format, fname_base, 1);
00396    if (res < 0)
00397       res = ast_monitor_change_fname(chan, fname_base, 1);
00398    ast_monitor_setjoinfiles(chan, joinfiles);
00399 
00400    return res;
00401 }
00402 
00403 static int stop_monitor_exec(struct ast_channel *chan, void *data)
00404 {
00405    return ast_monitor_stop(chan, 1);
00406 }
00407 
00408 static int change_monitor_exec(struct ast_channel *chan, void *data)
00409 {
00410    return ast_monitor_change_fname(chan, (const char*)data, 1);
00411 }
00412 
00413 static char start_monitor_action_help[] =
00414 "Description: The 'Monitor' action may be used to record the audio on a\n"
00415 "  specified channel.  The following parameters may be used to control\n"
00416 "  this:\n"
00417 "  Channel     - Required.  Used to specify the channel to record.\n"
00418 "  File        - Optional.  Is the name of the file created in the\n"
00419 "                monitor spool directory.  Defaults to the same name\n"
00420 "                as the channel (with slashes replaced with dashes).\n"
00421 "  Format      - Optional.  Is the audio recording format.  Defaults\n"
00422 "                to \"wav\".\n"
00423 "  Mix         - Optional.  Boolean parameter as to whether to mix\n"
00424 "                the input and output channels together after the\n"
00425 "                recording is finished.\n";
00426 
00427 static int start_monitor_action(struct mansession *s, struct message *m)
00428 {
00429    struct ast_channel *c = NULL;
00430    char *name = astman_get_header(m, "Channel");
00431    char *fname = astman_get_header(m, "File");
00432    char *format = astman_get_header(m, "Format");
00433    char *mix = astman_get_header(m, "Mix");
00434    char *d;
00435    
00436    if (ast_strlen_zero(name)) {
00437       astman_send_error(s, m, "No channel specified");
00438       return 0;
00439    }
00440    c = ast_get_channel_by_name_locked(name);
00441    if (!c) {
00442       astman_send_error(s, m, "No such channel");
00443       return 0;
00444    }
00445 
00446    if (ast_strlen_zero(fname)) {
00447       /* No filename base specified, default to channel name as per CLI */
00448       fname = malloc (FILENAME_MAX);
00449       if (!fname) {
00450          astman_send_error(s, m, "Could not start monitoring channel");
00451          ast_mutex_unlock(&c->lock);
00452          return 0;
00453       }
00454       memset(fname, 0, FILENAME_MAX);
00455       ast_copy_string(fname, c->name, FILENAME_MAX);
00456       /* Channels have the format technology/channel_name - have to replace that /  */
00457       if ((d=strchr(fname, '/'))) *d='-';
00458    }
00459    
00460    if (ast_monitor_start(c, format, fname, 1)) {
00461       if (ast_monitor_change_fname(c, fname, 1)) {
00462          astman_send_error(s, m, "Could not start monitoring channel");
00463          ast_mutex_unlock(&c->lock);
00464          return 0;
00465       }
00466    }
00467 
00468    if (ast_true(mix)) {
00469       ast_monitor_setjoinfiles(c, 1);
00470    }
00471 
00472    ast_mutex_unlock(&c->lock);
00473    astman_send_ack(s, m, "Started monitoring channel");
00474    return 0;
00475 }
00476 
00477 static char stop_monitor_action_help[] =
00478 "Description: The 'StopMonitor' action may be used to end a previously\n"
00479 "  started 'Monitor' action.  The only parameter is 'Channel', the name\n"
00480 "  of the channel monitored.\n";
00481 
00482 static int stop_monitor_action(struct mansession *s, struct message *m)
00483 {
00484    struct ast_channel *c = NULL;
00485    char *name = astman_get_header(m, "Channel");
00486    int res;
00487    if (ast_strlen_zero(name)) {
00488       astman_send_error(s, m, "No channel specified");
00489       return 0;
00490    }
00491    c = ast_get_channel_by_name_locked(name);
00492    if (!c) {
00493       astman_send_error(s, m, "No such channel");
00494       return 0;
00495    }
00496    res = ast_monitor_stop(c, 1);
00497    ast_mutex_unlock(&c->lock);
00498    if (res) {
00499       astman_send_error(s, m, "Could not stop monitoring channel");
00500       return 0;
00501    }
00502    astman_send_ack(s, m, "Stopped monitoring channel");
00503    return 0;
00504 }
00505 
00506 static char change_monitor_action_help[] =
00507 "Description: The 'ChangeMonitor' action may be used to change the file\n"
00508 "  started by a previous 'Monitor' action.  The following parameters may\n"
00509 "  be used to control this:\n"
00510 "  Channel     - Required.  Used to specify the channel to record.\n"
00511 "  File        - Required.  Is the new name of the file created in the\n"
00512 "                monitor spool directory.\n";
00513 
00514 static int change_monitor_action(struct mansession *s, struct message *m)
00515 {
00516    struct ast_channel *c = NULL;
00517    char *name = astman_get_header(m, "Channel");
00518    char *fname = astman_get_header(m, "File");
00519    if (ast_strlen_zero(name)) {
00520       astman_send_error(s, m, "No channel specified");
00521       return 0;
00522    }
00523    if (ast_strlen_zero(fname)) {
00524       astman_send_error(s, m, "No filename specified");
00525       return 0;
00526    }
00527    c = ast_get_channel_by_name_locked(name);
00528    if (!c) {
00529       astman_send_error(s, m, "No such channel");
00530       return 0;
00531    }
00532    if (ast_monitor_change_fname(c, fname, 1)) {
00533       astman_send_error(s, m, "Could not change monitored filename of channel");
00534       ast_mutex_unlock(&c->lock);
00535       return 0;
00536    }
00537    ast_mutex_unlock(&c->lock);
00538    astman_send_ack(s, m, "Changed monitor filename");
00539    return 0;
00540 }
00541 
00542 void ast_monitor_setjoinfiles(struct ast_channel *chan, int turnon)
00543 {
00544    if (chan->monitor)
00545       chan->monitor->joinfiles = turnon;
00546 }
00547 
00548 int load_module(void)
00549 {
00550    ast_register_application("Monitor", start_monitor_exec, monitor_synopsis, monitor_descrip);
00551    ast_register_application("StopMonitor", stop_monitor_exec, stopmonitor_synopsis, stopmonitor_descrip);
00552    ast_register_application("ChangeMonitor", change_monitor_exec, changemonitor_synopsis, changemonitor_descrip);
00553    ast_manager_register2("Monitor", EVENT_FLAG_CALL, start_monitor_action, monitor_synopsis, start_monitor_action_help);
00554    ast_manager_register2("StopMonitor", EVENT_FLAG_CALL, stop_monitor_action, stopmonitor_synopsis, stop_monitor_action_help);
00555    ast_manager_register2("ChangeMonitor", EVENT_FLAG_CALL, change_monitor_action, changemonitor_synopsis, change_monitor_action_help);
00556 
00557    return 0;
00558 }
00559 
00560 int unload_module(void)
00561 {
00562    ast_unregister_application("Monitor");
00563    ast_unregister_application("StopMonitor");
00564    ast_unregister_application("ChangeMonitor");
00565    ast_manager_unregister("Monitor");
00566    ast_manager_unregister("StopMonitor");
00567    ast_manager_unregister("ChangeMonitor");
00568    return 0;
00569 }
00570 
00571 char *description(void)
00572 {
00573    return "Call Monitoring Resource";
00574 }
00575 
00576 int usecount(void)
00577 {
00578    /* Never allow monitor to be unloaded because it will
00579       unresolve needed symbols in the channel */
00580 #if 0
00581    int res;
00582    STANDARD_USECOUNT(res);
00583    return res;
00584 #else
00585    return 1;
00586 #endif
00587 }
00588 
00589 char *key()
00590 {
00591    return ASTERISK_GPL_KEY;
00592 }

Generated on Mon Sep 18 09:13:29 2006 for Asterisk - the Open Source PBX by  doxygen 1.4.7