Asterisk.org: Developer Documentation (28 Oct 2009)

Wed Oct 28 15:47:55 2009

## Asterisk developer's documentation

# pbx_spool.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 Full-featured outgoing call spool support
    00022  *
    00023  */
    00024
    00025 #include <sys/stat.h>
    00026 #include <errno.h>
    00027 #include <time.h>
    00028 #include <utime.h>
    00029 #include <stdlib.h>
    00030 #include <unistd.h>
    00031 #include <dirent.h>
    00032 #include <string.h>
    00033 #include <string.h>
    00034 #include <stdio.h>
    00035 #include <unistd.h>
    00036
    00037 #include "asterisk.h"
    00038
    00039 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 211526 $")
    00040
    00041 #include "asterisk/lock.h"
    00042 #include "asterisk/file.h"
    00043 #include "asterisk/logger.h"
    00044 #include "asterisk/channel.h"
    00045 #include "asterisk/callerid.h"
    00046 #include "asterisk/pbx.h"
    00047 #include "asterisk/module.h"
    00048 #include "asterisk/options.h"
    00049 #include "asterisk/utils.h"
    00050
    00051 /*
    00052  * pbx_spool is similar in spirit to qcall, but with substantially enhanced functionality...
    00053  * The spool file contains a header
    00054  */
    00055
    00056 static char *tdesc = "Outgoing Spool Support";
    00057 static char qdir[255];
    00058
    00059 struct outgoing {
    00060    char fn[256];
    00061    /* Current number of retries */
    00062    int retries;
    00063    /* Maximum number of retries permitted */
    00064    int maxretries;
    00065    /* How long to wait between retries (in seconds) */
    00066    int retrytime;
    00067    /* How long to wait for an answer */
    00068    int waittime;
    00069    /* PID which is currently calling */
    00070    int callingpid;
    00071
    00072    /* What to connect to outgoing */
    00073    char tech[256];
    00074    char dest[256];
    00075
    00076    /* If application */
    00077    char app[256];
    00078    char data[256];
    00079
    00080    /* If extension/context/priority */
    00081    char exten[256];
    00082    char context[256];
    00083    int priority;
    00084
    00085    /* CallerID Information */
    00086    char cid_num[256];
    00087    char cid_name[256];
    00088
    00089    /* account code */
    00090    char account[AST_MAX_ACCOUNT_CODE];
    00091
    00092    /* Variables and Functions */
    00093    struct ast_variable *vars;
    00094
    00095    /* Maximum length of call */
    00096    int maxlen;
    00097 };
    00098
    00099 static void init_outgoing(struct outgoing *o)
    00100 {
    00101    memset(o, 0, sizeof(struct outgoing));
    00102    o->priority = 1;
    00103    o->retrytime = 300;
    00104    o->waittime = 45;
    00105 }
    00106
    00107 static void free_outgoing(struct outgoing *o)
    00108 {
    00109    free(o);
    00110 }
    00111
    00112 static int apply_outgoing(struct outgoing *o, char *fn, FILE *f)
    00113 {
    00114    char buf[256];
    00115    char *c, *c2;
    00116    int lineno = 0;
    00117    struct ast_variable *var;
    00118
    00119    while(fgets(buf, sizeof(buf), f)) {
    00120       lineno++;
    00121       /* Trim comments */
    00122       c = buf;
    00123       while ((c = strchr(c, '#'))) {
    00124          if ((c == buf) || (*(c-1) == ' ') || (*(c-1) == '\t'))
    00125             *c = '\0';
    00126          else
    00127             c++;
    00128       }
    00129
    00130       c = buf;
    00131       while ((c = strchr(c, ';'))) {
    00132          if ((c > buf) && (c[-1] == '\\')) {
    00133             memmove(c - 1, c, strlen(c) + 1);
    00134             c++;
    00135          } else {
    00136             *c = '\0';
    00137             break;
    00138          }
    00139       }
    00140
    00141       /* Trim trailing white space */
    00142       while(!ast_strlen_zero(buf) && buf[strlen(buf) - 1] < 33)
    00143          buf[strlen(buf) - 1] = '\0';
    00144       if (!ast_strlen_zero(buf)) {
    00145          c = strchr(buf, ':');
    00146          if (c) {
    00147             *c = '\0';
    00148             c++;
    00149             while ((*c) && (*c < 33))
    00150                c++;
    00151 #if 0
    00152             printf("'%s' is '%s' at line %d\n", buf, c, lineno);
    00153 #endif
    00154             if (!strcasecmp(buf, "channel")) {
    00155                strncpy(o->tech, c, sizeof(o->tech) - 1);
    00156                if ((c2 = strchr(o->tech, '/'))) {
    00157                   *c2 = '\0';
    00158                   c2++;
    00159                   strncpy(o->dest, c2, sizeof(o->dest) - 1);
    00160                } else {
    00161                   ast_log(LOG_NOTICE, "Channel should be in form Tech/Dest at line %d of %s\n", lineno, fn);
    00162                   o->tech[0] = '\0';
    00163                }
    00164             } else if (!strcasecmp(buf, "callerid")) {
    00165                ast_callerid_split(c, o->cid_name, sizeof(o->cid_name), o->cid_num, sizeof(o->cid_num));
    00166             } else if (!strcasecmp(buf, "application")) {
    00167                strncpy(o->app, c, sizeof(o->app) - 1);
    00168             } else if (!strcasecmp(buf, "data")) {
    00169                strncpy(o->data, c, sizeof(o->data) - 1);
    00170             } else if (!strcasecmp(buf, "maxretries")) {
    00171                if (sscanf(c, "%30d", &o->maxretries) != 1) {
    00172                   ast_log(LOG_WARNING, "Invalid max retries at line %d of %s\n", lineno, fn);
    00173                   o->maxretries = 0;
    00174                }
    00175             } else if (!strcasecmp(buf, "context")) {
    00176                strncpy(o->context, c, sizeof(o->context) - 1);
    00177             } else if (!strcasecmp(buf, "extension")) {
    00178                strncpy(o->exten, c, sizeof(o->exten) - 1);
    00179             } else if (!strcasecmp(buf, "priority")) {
    00180                if ((sscanf(c, "%30d", &o->priority) != 1) || (o->priority < 1)) {
    00181                   ast_log(LOG_WARNING, "Invalid priority at line %d of %s\n", lineno, fn);
    00182                   o->priority = 1;
    00183                }
    00184             } else if (!strcasecmp(buf, "retrytime")) {
    00185                if ((sscanf(c, "%30d", &o->retrytime) != 1) || (o->retrytime < 1)) {
    00186                   ast_log(LOG_WARNING, "Invalid retrytime at line %d of %s\n", lineno, fn);
    00187                   o->retrytime = 300;
    00188                }
    00189             } else if (!strcasecmp(buf, "waittime")) {
    00190                if ((sscanf(c, "%30d", &o->waittime) != 1) || (o->waittime < 1)) {
    00191                   ast_log(LOG_WARNING, "Invalid retrytime at line %d of %s\n", lineno, fn);
    00192                   o->waittime = 45;
    00193                }
    00194             } else if (!strcasecmp(buf, "retry")) {
    00195                o->retries++;
    00196             } else if (!strcasecmp(buf, "startretry")) {
    00197                if (sscanf(c, "%30d", &o->callingpid) != 1) {
    00198                   ast_log(LOG_WARNING, "Unable to retrieve calling PID!\n");
    00199                   o->callingpid = 0;
    00200                }
    00201             } else if (!strcasecmp(buf, "endretry") || !strcasecmp(buf, "abortretry")) {
    00202                o->callingpid = 0;
    00203                o->retries++;
    00204             } else if (!strcasecmp(buf, "delayedretry")) {
    00205             } else if (!strcasecmp(buf, "setvar") || !strcasecmp(buf, "set")) {
    00206                c2 = c;
    00207                strsep(&c2, "=");
    00208                if (c2) {
    00209                   var = ast_variable_new(c, c2);
    00210                   if (var) {
    00211                      var->next = o->vars;
    00212                      o->vars = var;
    00213                   }
    00214                } else {
    00215                   ast_log(LOG_WARNING, "Malformed Set: argument! Should be Set: Variable=value\n");
    00216                }
    00217             } else if (!strcasecmp(buf, "account")) {
    00218                ast_copy_string(o->account, c, sizeof(o->account));
    00219             } else {
    00220                ast_log(LOG_WARNING, "Unknown keyword '%s' at line %d of %s\n", buf, lineno, fn);
    00221             }
    00222          } else
    00223             ast_log(LOG_NOTICE, "Syntax error at line %d of %s\n", lineno, fn);
    00224       }
    00225    }
    00226    strncpy(o->fn, fn, sizeof(o->fn) - 1);
    00227    if (ast_strlen_zero(o->tech) || ast_strlen_zero(o->dest) || (ast_strlen_zero(o->app) && ast_strlen_zero(o->exten))) {
    00228       ast_log(LOG_WARNING, "At least one of app or extension must be specified, along with tech and dest in file %s\n", fn);
    00229       return -1;
    00230    }
    00231    return 0;
    00232 }
    00233
    00234 static void safe_append(struct outgoing *o, time_t now, char *s)
    00235 {
    00236    int fd;
    00237    FILE *f;
    00238    struct utimbuf tbuf;
    00239    fd = open(o->fn, O_WRONLY|O_APPEND);
    00240    if (fd > -1) {
    00241       f = fdopen(fd, "a");
    00242       if (f) {
    00243          fprintf(f, "\n%s: %ld %d (%ld)\n", s, (long)ast_mainpid, o->retries, (long) now);
    00244          fclose(f);
    00245       } else
    00246          close(fd);
    00247       /* Update the file time */
    00248       tbuf.actime = now;
    00249       tbuf.modtime = now + o->retrytime;
    00250       if (utime(o->fn, &tbuf))
    00251          ast_log(LOG_WARNING, "Unable to set utime on %s: %s\n", o->fn, strerror(errno));
    00252    }
    00253 }
    00254
    00255 static void *attempt_thread(void *data)
    00256 {
    00257    struct outgoing *o = data;
    00258    int res, reason;
    00259    if (!ast_strlen_zero(o->app)) {
    00260       if (option_verbose > 2)
    00261          ast_verbose(VERBOSE_PREFIX_3 "Attempting call on %s/%s for application %s(%s) (Retry %d)\n", o->tech, o->dest, o->app, o->data, o->retries);
    00262       res = ast_pbx_outgoing_app(o->tech, AST_FORMAT_SLINEAR, o->dest, o->waittime * 1000, o->app, o->data, &reason, 2 /* wait to finish */, o->cid_num, o->cid_name, o->vars, o->account, NULL);
    00263    } else {
    00264       if (option_verbose > 2)
    00265          ast_verbose(VERBOSE_PREFIX_3 "Attempting call on %s/%s for %s@%s:%d (Retry %d)\n", o->tech, o->dest, o->exten, o->context,o->priority, o->retries);
    00266       res = ast_pbx_outgoing_exten(o->tech, AST_FORMAT_SLINEAR, o->dest, o->waittime * 1000, o->context, o->exten, o->priority, &reason, 2 /* wait to finish */, o->cid_num, o->cid_name, o->vars, o->account, NULL);
    00267    }
    00268    if (res) {
    00269       ast_log(LOG_NOTICE, "Call failed to go through, reason %d\n", reason);
    00270       if (o->retries >= o->maxretries + 1) {
    00271          /* Max retries exceeded */
    00272          ast_log(LOG_EVENT, "Queued call to %s/%s expired without completion after %d attempt%s\n", o->tech, o->dest, o->retries - 1, ((o->retries - 1) != 1) ? "s" : "");
    00273          unlink(o->fn);
    00274       } else {
    00275          /* Notate that the call is still active */
    00276          safe_append(o, time(NULL), "EndRetry");
    00277       }
    00278    } else {
    00279       ast_log(LOG_NOTICE, "Call completed to %s/%s\n", o->tech, o->dest);
    00280       ast_log(LOG_EVENT, "Queued call to %s/%s completed\n", o->tech, o->dest);
    00281       unlink(o->fn);
    00282    }
    00283    free_outgoing(o);
    00284    return NULL;
    00285 }
    00286
    00287 static void launch_service(struct outgoing *o)
    00288 {
    00289    pthread_t t;
    00290    pthread_attr_t attr;
    00291    int ret;
    00292    pthread_attr_init(&attr);
    00293    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    00294    if ((ret = ast_pthread_create(&t,&attr,attempt_thread, o)) != 0) {
    00295       ast_log(LOG_WARNING, "Unable to create thread :( (returned error: %d)\n", ret);
    00296       free_outgoing(o);
    00297    }
    00298    pthread_attr_destroy(&attr);
    00299 }
    00300
    00301 static int scan_service(char *fn, time_t now, time_t atime)
    00302 {
    00303    struct outgoing *o;
    00304    FILE *f;
    00305    o = malloc(sizeof(struct outgoing));
    00306    if (o) {
    00307       init_outgoing(o);
    00308       f = fopen(fn, "r+");
    00309       if (f) {
    00310          if (!apply_outgoing(o, fn, f)) {
    00311 #if 0
    00312             printf("Filename: %s, Retries: %d, max: %d\n", fn, o->retries, o->maxretries);
    00313 #endif
    00314             fclose(f);
    00315             if (o->retries <= o->maxretries) {
    00316                now += o->retrytime;
    00317                if (o->callingpid && (o->callingpid == ast_mainpid)) {
    00318                   safe_append(o, time(NULL), "DelayedRetry");
    00319                   ast_log(LOG_DEBUG, "Delaying retry since we're currently running '%s'\n", o->fn);
    00320                   free_outgoing(o);
    00321                } else {
    00322                   /* Increment retries */
    00323                   o->retries++;
    00324                   /* If someone else was calling, they're presumably gone now
    00325                      so abort their retry and continue as we were... */
    00326                   if (o->callingpid)
    00327                      safe_append(o, time(NULL), "AbortRetry");
    00328
    00329                   safe_append(o, now, "StartRetry");
    00330                   launch_service(o);
    00331                }
    00332                return now;
    00333             } else {
    00334                ast_log(LOG_EVENT, "Queued call to %s/%s expired without completion after %d attempt%s\n", o->tech, o->dest, o->retries - 1, ((o->retries - 1) != 1) ? "s" : "");
    00335                free_outgoing(o);
    00336                unlink(fn);
    00337                return 0;
    00338             }
    00339          } else {
    00340             free_outgoing(o);
    00341             ast_log(LOG_WARNING, "Invalid file contents in %s, deleting\n", fn);
    00342             fclose(f);
    00343             unlink(fn);
    00344          }
    00345       } else {
    00346          free_outgoing(o);
    00347          ast_log(LOG_WARNING, "Unable to open %s: %s, deleting\n", fn, strerror(errno));
    00348          unlink(fn);
    00349       }
    00350    } else
    00351       ast_log(LOG_WARNING, "Out of memory :(\n");
    00352    return -1;
    00353 }
    00354
    00355 static void *scan_thread(void *unused)
    00356 {
    00357    struct stat st;
    00358    DIR *dir;
    00359    struct dirent *de;
    00360    char fn[256];
    00361    int res;
    00362    time_t last = 0, next = 0, now;
    00363    for(;;) {
    00364       /* Wait a sec */
    00365       sleep(1);
    00366       time(&now);
    00367       if (!stat(qdir, &st)) {
    00368          if ((st.st_mtime != last) || (next && (now > next))) {
    00369 #if 0
    00370             printf("atime: %ld, mtime: %ld, ctime: %ld\n", st.st_atime, st.st_mtime, st.st_ctime);
    00371             printf("Ooh, something changed / timeout\n");
    00372 #endif
    00373             next = 0;
    00374             last = st.st_mtime;
    00375             dir = opendir(qdir);
    00376             if (dir) {
    00377                while((de = readdir(dir))) {
    00378                   snprintf(fn, sizeof(fn), "%s/%s", qdir, de->d_name);
    00379                   if (!stat(fn, &st)) {
    00380                      if (S_ISREG(st.st_mode)) {
    00381                         if (st.st_mtime <= now) {
    00382                            res = scan_service(fn, now, st.st_atime);
    00383                            if (res > 0) {
    00384                               /* Update next service time */
    00385                               if (!next || (res < next)) {
    00386                                  next = res;
    00387                               }
    00388                            } else if (res)
    00389                               ast_log(LOG_WARNING, "Failed to scan service '%s'\n", fn);
    00390                         } else {
    00391                            /* Update "next" update if necessary */
    00392                            if (!next || (st.st_mtime < next))
    00393                               next = st.st_mtime;
    00394                         }
    00395                      }
    00396                   } else
    00397                      ast_log(LOG_WARNING, "Unable to stat %s: %s\n", fn, strerror(errno));
    00398                }
    00399                closedir(dir);
    00400             } else
    00401                ast_log(LOG_WARNING, "Unable to open directory %s: %s\n", qdir, strerror(errno));
    00402          }
    00403       } else
    00404          ast_log(LOG_WARNING, "Unable to stat %s\n", qdir);
    00405    }
    00406    return NULL;
    00407 }
    00408
    00409 int unload_module(void)
    00410 {
    00411    return -1;
    00412 }
    00413
    00414 int load_module(void)
    00415 {
    00416    pthread_t thread;
    00417    pthread_attr_t attr;
    00418    int ret;
    00419    snprintf(qdir, sizeof(qdir), "%s/%s", ast_config_AST_SPOOL_DIR, "outgoing");
    00420    if (mkdir(qdir, 0700) && (errno != EEXIST)) {
    00421       ast_log(LOG_WARNING, "Unable to create queue directory %s -- outgoing spool disabled\n", qdir);
    00422       return 0;
    00423    }
    00424    pthread_attr_init(&attr);
    00425    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    00426    if ((ret = ast_pthread_create(&thread,&attr,scan_thread, NULL)) != 0) {
    00427       ast_log(LOG_WARNING, "Unable to create thread :( (returned error: %d)\n", ret);
    00428       return -1;
    00429    }
    00430    pthread_attr_destroy(&attr);
    00431    return 0;
    00432 }
    00433
    00434 char *description(void)
    00435 {
    00436    return tdesc;
    00437 }
    00438
    00439 int usecount(void)
    00440 {
    00441    return 1;
    00442 }
    00443
    00444 char *key()
    00445 {
    00446    return ASTERISK_GPL_KEY;
    00447 }