Main Page   Alphabetical List   Data Structures   File List   Data Fields   Globals  

logger.c

Go to the documentation of this file.
00001 /*
00002  * Asterisk Logger
00003  * 
00004  * Mark Spencer <markster@marko.net>
00005  *
00006  * Copyright(C)1999, Linux Support Services, Inc.
00007  * 
00008  * Distributed under the terms of the GNU General Public License (GPL) Version 2
00009  *
00010  * Logging routines
00011  *
00012  */
00013 
00014 #include <signal.h>
00015 #include <stdarg.h>
00016 #include <stdio.h>
00017 #include <unistd.h>
00018 #include <time.h>
00019 #include <asterisk/lock.h>
00020 #include <asterisk/options.h>
00021 #include <asterisk/channel.h>
00022 #include <asterisk/config.h>
00023 #include <asterisk/term.h>
00024 #include <asterisk/cli.h>
00025 #include <asterisk/utils.h>
00026 #include <string.h>
00027 #include <stdlib.h>
00028 #include <errno.h>
00029 #include <sys/stat.h>
00030 #include "asterisk.h"
00031 #include "astconf.h"
00032 
00033 #define SYSLOG_NAMES /* so we can map syslog facilities names to their numeric values,
00034               from <syslog.h> which is included by logger.h */
00035 #include <syslog.h>
00036 static int syslog_level_map[] = {
00037    LOG_DEBUG,
00038    LOG_INFO,    /* arbitrary equivalent of LOG_EVENT */
00039    LOG_NOTICE,
00040    LOG_WARNING,
00041    LOG_ERR,
00042    LOG_DEBUG
00043 };
00044 
00045 #define SYSLOG_NLEVELS 6
00046 
00047 #include <asterisk/logger.h>
00048 
00049 #define MAX_MSG_QUEUE 200
00050 
00051 static char dateformat[256] = "%b %e %T";    /* Original Asterisk Format */
00052 AST_MUTEX_DEFINE_STATIC(msglist_lock);
00053 AST_MUTEX_DEFINE_STATIC(loglock);
00054 static int pending_logger_reload = 0;
00055 
00056 static struct msglist {
00057    char *msg;
00058    struct msglist *next;
00059 } *list = NULL, *last = NULL;
00060 
00061 static char hostname[256];
00062 
00063 struct logchannel {
00064    int logmask;
00065    int facility; /* syslog */
00066    int syslog; /* syslog flag */
00067    int console;  /* console logging */
00068    FILE *fileptr; /* logfile logging */
00069    char filename[256];
00070    struct logchannel *next;
00071 };
00072 
00073 static struct logchannel *logchannels = NULL;
00074 
00075 static int msgcnt = 0;
00076 
00077 static FILE *eventlog = NULL;
00078 
00079 static char *levels[] = {
00080    "DEBUG",
00081    "EVENT",
00082    "NOTICE",
00083    "WARNING",
00084    "ERROR",
00085    "VERBOSE"
00086 };
00087 
00088 static int colors[] = {
00089    COLOR_BRGREEN,
00090    COLOR_BRBLUE,
00091    COLOR_YELLOW,
00092    COLOR_BRRED,
00093    COLOR_RED,
00094    COLOR_GREEN
00095 };
00096 
00097 static int make_components(char *s, int lineno)
00098 {
00099    char *w;
00100    int res = 0;
00101    char *stringp=NULL;
00102    stringp=s;
00103    w = strsep(&stringp, ",");
00104    while(w) {
00105        while(*w && (*w < 33))
00106       w++;
00107        if (!strcasecmp(w, "error")) 
00108       res |= (1 << __LOG_ERROR);
00109        else if (!strcasecmp(w, "warning"))
00110       res |= (1 << __LOG_WARNING);
00111        else if (!strcasecmp(w, "notice"))
00112       res |= (1 << __LOG_NOTICE);
00113        else if (!strcasecmp(w, "event"))
00114       res |= (1 << __LOG_EVENT);
00115        else if (!strcasecmp(w, "debug"))
00116       res |= (1 << __LOG_DEBUG);
00117        else if (!strcasecmp(w, "verbose"))
00118       res |= (1 << __LOG_VERBOSE);
00119        else {
00120       fprintf(stderr, "Logfile Warning: Unknown keyword '%s' at line %d of logger.conf\n", w, lineno);
00121        }
00122        w = strsep(&stringp, ",");
00123    }
00124    return res;
00125 }
00126 
00127 static struct logchannel *make_logchannel(char *channel, char *components, int lineno)
00128 {
00129    struct logchannel *chan;
00130    char *facility;
00131    CODE *cptr;
00132 
00133    if (ast_strlen_zero(channel))
00134       return NULL;
00135    chan = malloc(sizeof(struct logchannel));
00136 
00137    if (chan) {
00138       memset(chan, 0, sizeof(struct logchannel));
00139       if (!strcasecmp(channel, "console")) {
00140           chan->console = 1;
00141       } else if (!strncasecmp(channel, "syslog", 6)) {
00142           /*
00143            * syntax is:
00144            *  syslog.facility => level,level,level
00145            */
00146           facility = strchr(channel, '.');
00147           if(!facility++ || !facility) {
00148          facility = "local0";
00149           }
00150           /*
00151            * Walk through the list of facilitynames (defined in sys/syslog.h)
00152            * to see if we can find the one we have been given
00153            */
00154           chan->facility = -1;
00155           cptr = facilitynames;
00156           while (cptr->c_name) {
00157          if (!strncasecmp(facility, cptr->c_name, sizeof(cptr->c_name))) {
00158              chan->facility = cptr->c_val;
00159              break;
00160          }
00161          cptr++;
00162           }
00163           if (0 > chan->facility) {
00164          fprintf(stderr, "Logger Warning: bad syslog facility in logger.conf\n");
00165          free(chan);
00166          return NULL;
00167           }
00168 
00169           chan->syslog = 1;
00170           openlog("asterisk", LOG_PID, chan->facility);
00171       } else {
00172          if (channel[0] == '/') {
00173             if(!ast_strlen_zero(hostname)) { 
00174                snprintf(chan->filename, sizeof(chan->filename) - 1,"%s.%s", channel, hostname);
00175             } else {
00176                strncpy(chan->filename, channel, sizeof(chan->filename) - 1);
00177             }
00178          }       
00179          
00180          if(!ast_strlen_zero(hostname)) {
00181             snprintf(chan->filename, sizeof(chan->filename), "%s/%s.%s",(char *)ast_config_AST_LOG_DIR, channel, hostname);
00182          } else {
00183             snprintf(chan->filename, sizeof(chan->filename), "%s/%s", (char *)ast_config_AST_LOG_DIR, channel);
00184          }
00185          chan->fileptr = fopen(chan->filename, "a");
00186          if (!chan->fileptr) {
00187             /* Can't log here, since we're called with a lock */
00188             fprintf(stderr, "Logger Warning: Unable to open log file '%s': %s\n", chan->filename, strerror(errno));
00189          }
00190       }
00191       chan->logmask = make_components(components, lineno);
00192    }
00193    return chan;
00194 }
00195 
00196 static void init_logger_chain(void)
00197 {
00198    struct logchannel *chan, *cur;
00199    struct ast_config *cfg;
00200    struct ast_variable *var;
00201    char *s;
00202 
00203    /* delete our list of log channels */
00204    ast_mutex_lock(&loglock);
00205    chan = logchannels;
00206    while (chan) {
00207        cur = chan->next;
00208        free(chan);
00209        chan = cur;
00210    }
00211    logchannels = NULL;
00212    ast_mutex_unlock(&loglock);
00213    
00214    /* close syslog */
00215    closelog();
00216    
00217    cfg = ast_load("logger.conf");
00218    
00219    /* If no config file, we're fine */
00220    if (!cfg)
00221        return;
00222    
00223    ast_mutex_lock(&loglock);
00224    if ((s = ast_variable_retrieve(cfg, "general", "appendhostname"))) {
00225       if(ast_true(s)) {
00226          if(gethostname(hostname, sizeof(hostname))) {
00227             strncpy(hostname, "unknown", sizeof(hostname)-1);
00228             ast_log(LOG_WARNING, "What box has no hostname???\n");
00229          }
00230       } else
00231          hostname[0] = '\0';
00232    } else
00233       hostname[0] = '\0';
00234    if ((s = ast_variable_retrieve(cfg, "general", "dateformat"))) {
00235       strncpy(dateformat, s, sizeof(dateformat) - 1);
00236    } else
00237       strncpy(dateformat, "%b %e %T", sizeof(dateformat) - 1);
00238    var = ast_variable_browse(cfg, "logfiles");
00239    while(var) {
00240       chan = make_logchannel(var->name, var->value, var->lineno);
00241       if (chan) {
00242          chan->next = logchannels;
00243          logchannels = chan;
00244       }
00245       var = var->next;
00246    }
00247 
00248    ast_destroy(cfg);
00249    ast_mutex_unlock(&loglock);
00250 }
00251 
00252 static FILE *qlog = NULL;
00253 AST_MUTEX_DEFINE_STATIC(qloglock);
00254 
00255 void ast_queue_log(const char *queuename, const char *callid, const char *agent, const char *event, const char *fmt, ...)
00256 {
00257    va_list ap;
00258    ast_mutex_lock(&qloglock);
00259    if (qlog) {
00260       va_start(ap, fmt);
00261       fprintf(qlog, "%ld|%s|%s|%s|%s|", (long)time(NULL), callid, queuename, agent, event);
00262       vfprintf(qlog, fmt, ap);
00263       fprintf(qlog, "\n");
00264       va_end(ap);
00265       fflush(qlog);
00266    }
00267    ast_mutex_unlock(&qloglock);
00268 }
00269 
00270 static void queue_log_init(void)
00271 {
00272    char filename[256];
00273    int reloaded = 0;
00274    ast_mutex_lock(&qloglock);
00275    if (qlog) {
00276       reloaded = 1;
00277       fclose(qlog);
00278       qlog = NULL;
00279    }
00280    snprintf(filename, sizeof(filename), "%s/%s", (char *)ast_config_AST_LOG_DIR, "queue_log");
00281    qlog = fopen(filename, "a");
00282    ast_mutex_unlock(&qloglock);
00283    if (reloaded) 
00284       ast_queue_log("NONE", "NONE", "NONE", "CONFIGRELOAD", "%s", "");
00285    else
00286       ast_queue_log("NONE", "NONE", "NONE", "QUEUESTART", "%s", "");
00287 }
00288 
00289 int reload_logger(int rotate)
00290 {
00291    char old[AST_CONFIG_MAX_PATH] = "";
00292    char new[AST_CONFIG_MAX_PATH];
00293    struct logchannel *f;
00294    FILE *myf;
00295 
00296    int x;
00297    ast_mutex_lock(&loglock);
00298    if (eventlog) 
00299       fclose(eventlog);
00300    else 
00301       rotate = 0;
00302    eventlog = NULL;
00303 
00304 
00305 
00306    mkdir((char *)ast_config_AST_LOG_DIR, 0755);
00307    snprintf(old, sizeof(old), "%s/%s", (char *)ast_config_AST_LOG_DIR, EVENTLOG);
00308 
00309    if(rotate) {
00310       for(x=0;;x++) {
00311          snprintf(new, sizeof(new), "%s/%s.%d", (char *)ast_config_AST_LOG_DIR, EVENTLOG,x);
00312          myf = fopen((char *)new, "r");
00313          if(myf) 
00314             fclose(myf);
00315          else
00316             break;
00317       }
00318    
00319       /* do it */
00320       if (rename(old,new))
00321          fprintf(stderr, "Unable to rename file '%s' to '%s'\n", old, new);
00322    }
00323 
00324    eventlog = fopen(old, "a");
00325 
00326    f = logchannels;
00327    while(f) {
00328       if (f->fileptr && (f->fileptr != stdout) && (f->fileptr != stderr)) {
00329          fclose(f->fileptr);
00330          f->fileptr = NULL;
00331          if(rotate) {
00332             strncpy(old, f->filename, sizeof(old) - 1);
00333    
00334             for(x=0;;x++) {
00335                snprintf(new, sizeof(new), "%s.%d", f->filename, x);
00336                myf = fopen((char *)new, "r");
00337                if (myf) {
00338                   fclose(myf);
00339                } else {
00340                   break;
00341                }
00342             }
00343        
00344             /* do it */
00345             if (rename(old,new))
00346                fprintf(stderr, "Unable to rename file '%s' to '%s'\n", old, new);
00347          }
00348       }
00349       f = f->next;
00350    }
00351 
00352    ast_mutex_unlock(&loglock);
00353 
00354    queue_log_init();
00355 
00356    if (eventlog) {
00357       init_logger_chain();
00358       ast_log(LOG_EVENT, "Restarted Asterisk Event Logger\n");
00359       if (option_verbose)
00360          ast_verbose("Asterisk Event Logger restarted\n");
00361       return 0;
00362    } else 
00363       ast_log(LOG_ERROR, "Unable to create event log: %s\n", strerror(errno));
00364    init_logger_chain();
00365    pending_logger_reload = 0;
00366    return -1;
00367 }
00368 
00369 static int handle_logger_reload(int fd, int argc, char *argv[])
00370 {
00371    if(reload_logger(0))
00372    {
00373       ast_cli(fd, "Failed to reloadthe logger\n");
00374       return RESULT_FAILURE;
00375    }
00376    else
00377       return RESULT_SUCCESS;
00378 }
00379 
00380 static int handle_logger_rotate(int fd, int argc, char *argv[])
00381 {
00382    if(reload_logger(1))
00383    {
00384       ast_cli(fd, "Failed to reloadthe logger\n");
00385       return RESULT_FAILURE;
00386    }
00387    else
00388       return RESULT_SUCCESS;
00389 }
00390 
00391 static struct verb {
00392    void (*verboser)(const char *string, int opos, int replacelast, int complete);
00393    struct verb *next;
00394 } *verboser = NULL;
00395 
00396 
00397 static char logger_reload_help[] =
00398 "Usage: logger reload\n"
00399 "       Reloads the logger subsystem state.  Use after restarting syslogd(8)\n";
00400 
00401 static char logger_rotate_help[] =
00402 "Usage: logger rotate\n"
00403 "       Rotates and Reopens the log files.\n";
00404 
00405 static struct ast_cli_entry reload_logger_cli = 
00406    { { "logger", "reload", NULL }, 
00407    handle_logger_reload, "Reopens the log files",
00408    logger_reload_help };
00409 
00410 static struct ast_cli_entry rotate_logger_cli = 
00411    { { "logger", "rotate", NULL }, 
00412    handle_logger_rotate, "Rotates and reopens the log files",
00413    logger_rotate_help };
00414 
00415 static int handle_SIGXFSZ(int sig) 
00416 {
00417    /* Indicate need to reload */
00418    pending_logger_reload = 1;
00419    return 0;
00420 }
00421 
00422 int init_logger(void)
00423 {
00424    char tmp[256];
00425 
00426    /* auto rotate if sig SIGXFSZ comes a-knockin */
00427    (void) signal(SIGXFSZ,(void *) handle_SIGXFSZ);
00428 
00429    /* register the relaod logger cli command */
00430    ast_cli_register(&reload_logger_cli);
00431    ast_cli_register(&rotate_logger_cli);
00432 
00433    /* initialize queue logger */
00434    queue_log_init();
00435 
00436    /* create the eventlog */
00437    mkdir((char *)ast_config_AST_LOG_DIR, 0755);
00438    snprintf(tmp, sizeof(tmp), "%s/%s", (char *)ast_config_AST_LOG_DIR, EVENTLOG);
00439    eventlog = fopen((char *)tmp, "a");
00440    if (eventlog) {
00441       init_logger_chain();
00442       ast_log(LOG_EVENT, "Started Asterisk Event Logger\n");
00443       if (option_verbose)
00444          ast_verbose("Asterisk Event Logger Started %s\n",(char *)tmp);
00445       return 0;
00446    } else 
00447       ast_log(LOG_ERROR, "Unable to create event log: %s\n", strerror(errno));
00448 
00449    /* create log channels */
00450    init_logger_chain();
00451    return -1;
00452 }
00453 
00454 void close_logger(void)
00455 {
00456    struct msglist *m, *tmp;
00457 
00458    ast_mutex_lock(&msglist_lock);
00459    m = list;
00460    while(m) {
00461       if (m->msg) {
00462          free(m->msg);
00463       }
00464       tmp = m->next;
00465       free(m);
00466       m = tmp;
00467    }
00468    list = last = NULL;
00469    msgcnt = 0;
00470    ast_mutex_unlock(&msglist_lock);
00471    return;
00472 }
00473 
00474 static void ast_log_vsyslog(int level, const char *file, int line, const char *function, const char *fmt, va_list args) 
00475 {
00476    char buf[BUFSIZ];
00477 
00478    if (level >= SYSLOG_NLEVELS) {
00479       /* we are locked here, so cannot ast_log() */
00480       fprintf(stderr, "ast_log_vsyslog called with bogus level: %d\n", level);
00481       return;
00482    }
00483    if (level == __LOG_VERBOSE) {
00484       snprintf(buf, sizeof(buf), "VERBOSE[%ld]: ", (long)pthread_self());
00485       level = __LOG_DEBUG;
00486    } else {
00487       snprintf(buf, sizeof(buf), "%s[%ld]: %s:%d in %s: ",
00488          levels[level], (long)pthread_self(), file, line, function);
00489    }
00490    vsnprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), fmt, args);
00491    syslog(syslog_level_map[level], "%s", buf);
00492 }
00493 
00494 /*
00495  * send log messages to syslog and/or the console
00496  */
00497 void ast_log(int level, const char *file, int line, const char *function, const char *fmt, ...)
00498 {
00499    struct logchannel *chan;
00500    char buf[BUFSIZ];
00501    time_t t;
00502    struct tm tm;
00503    char date[256];
00504 
00505    va_list ap;
00506    
00507    if (!option_verbose && !option_debug && (level == __LOG_DEBUG)) {
00508       return;
00509    }
00510 
00511    /* begin critical section */
00512    ast_mutex_lock(&loglock);
00513 
00514    time(&t);
00515    localtime_r(&t, &tm);
00516    strftime(date, sizeof(date), dateformat, &tm);
00517 
00518    if (level == __LOG_EVENT) {
00519       va_start(ap, fmt);
00520 
00521       fprintf(eventlog, "%s asterisk[%d]: ", date, getpid());
00522       vfprintf(eventlog, fmt, ap);
00523       fflush(eventlog);
00524 
00525       va_end(ap);
00526       ast_mutex_unlock(&loglock);
00527       return;
00528    }
00529 
00530    if (logchannels) {
00531       chan = logchannels;
00532       while(chan) {
00533          if (chan->syslog && (chan->logmask & (1 << level))) {
00534             va_start(ap, fmt);
00535             ast_log_vsyslog(level, file, line, function, fmt, ap);
00536             va_end(ap);
00537          } else if ((chan->logmask & (1 << level)) && (chan->console)) {
00538             char linestr[128];
00539             char tmp1[80], tmp2[80], tmp3[80], tmp4[80];
00540 
00541             if (level != __LOG_VERBOSE) {
00542                sprintf(linestr, "%d", line);
00543                snprintf(buf, sizeof(buf), "%s %s[%ld]: %s:%s %s: ",
00544                   date,
00545                   term_color(tmp1, levels[level], colors[level], 0, sizeof(tmp1)),
00546                   (long)pthread_self(),
00547                   term_color(tmp2, file, COLOR_BRWHITE, 0, sizeof(tmp2)),
00548                   term_color(tmp3, linestr, COLOR_BRWHITE, 0, sizeof(tmp3)),
00549                   term_color(tmp4, function, COLOR_BRWHITE, 0, sizeof(tmp4)));
00550           
00551                ast_console_puts(buf);
00552                va_start(ap, fmt);
00553                vsnprintf(buf, sizeof(buf), fmt, ap);
00554                va_end(ap);
00555                ast_console_puts(buf);
00556             }
00557          } else if ((chan->logmask & (1 << level)) && (chan->fileptr)) {
00558             snprintf(buf, sizeof(buf), "%s %s[%ld]: ", date,
00559                levels[level], (long)pthread_self());
00560             fprintf(chan->fileptr, buf);
00561             va_start(ap, fmt);
00562             vsnprintf(buf, sizeof(buf), fmt, ap);
00563             va_end(ap);
00564             fputs(buf, chan->fileptr);
00565             fflush(chan->fileptr);
00566          }
00567          chan = chan->next;
00568       }
00569    } else {
00570       /* 
00571        * we don't have the logger chain configured yet,
00572        * so just log to stdout 
00573       */
00574       if (level != __LOG_VERBOSE) {
00575          va_start(ap, fmt);
00576          vsnprintf(buf, sizeof(buf), fmt, ap);
00577          va_end(ap);
00578          fputs(buf, stdout);
00579       }
00580    }
00581 
00582    ast_mutex_unlock(&loglock);
00583    /* end critical section */
00584    if (pending_logger_reload) {
00585       reload_logger(1);
00586       ast_log(LOG_EVENT,"Rotated Logs Per SIGXFSZ\n");
00587       if (option_verbose)
00588          ast_verbose("Rotated Logs Per SIGXFSZ\n");
00589    }
00590 }
00591 
00592 extern void ast_verbose(const char *fmt, ...)
00593 {
00594    static char stuff[4096];
00595    static int pos = 0, opos;
00596    static int replacelast = 0, complete;
00597    struct msglist *m;
00598    struct verb *v;
00599    va_list ap;
00600    va_start(ap, fmt);
00601    ast_mutex_lock(&msglist_lock);
00602    vsnprintf(stuff + pos, sizeof(stuff) - pos, fmt, ap);
00603    opos = pos;
00604    pos = strlen(stuff);
00605    if (fmt[strlen(fmt)-1] == '\n') 
00606       complete = 1;
00607    else
00608       complete=0;
00609    if (complete) {
00610       if (msgcnt < MAX_MSG_QUEUE) {
00611          /* Allocate new structure */
00612          m = malloc(sizeof(struct msglist));
00613          msgcnt++;
00614       } else {
00615          /* Recycle the oldest entry */
00616          m = list;
00617          list = list->next;
00618          free(m->msg);
00619       }
00620       if (m) {
00621          m->msg = strdup(stuff);
00622          if (m->msg) {
00623             if (last)
00624                last->next = m;
00625             else
00626                list = m;
00627             m->next = NULL;
00628             last = m;
00629          } else {
00630             msgcnt--;
00631             ast_log(LOG_ERROR, "Out of memory\n");
00632             free(m);
00633          }
00634       }
00635    }
00636    if (verboser) {
00637       v = verboser;
00638       while(v) {
00639          v->verboser(stuff, opos, replacelast, complete);
00640          v = v->next;
00641       }
00642    } /* else
00643       fprintf(stdout, stuff + opos); */
00644 
00645    ast_log(LOG_VERBOSE, stuff);
00646 
00647    if (fmt[strlen(fmt)-1] != '\n') 
00648       replacelast = 1;
00649    else 
00650       replacelast = pos = 0;
00651    va_end(ap);
00652 
00653    ast_mutex_unlock(&msglist_lock);
00654 }
00655 
00656 int ast_verbose_dmesg(void (*v)(const char *string, int opos, int replacelast, int complete))
00657 {
00658    struct msglist *m;
00659    ast_mutex_lock(&msglist_lock);
00660    m = list;
00661    while(m) {
00662       /* Send all the existing entries that we have queued (i.e. they're likely to have missed) */
00663       v(m->msg, 0, 0, 1);
00664       m = m->next;
00665    }
00666    ast_mutex_unlock(&msglist_lock);
00667    return 0;
00668 }
00669 
00670 int ast_register_verbose(void (*v)(const char *string, int opos, int replacelast, int complete)) 
00671 {
00672    struct msglist *m;
00673    struct verb *tmp;
00674    /* XXX Should be more flexible here, taking > 1 verboser XXX */
00675    if ((tmp = malloc(sizeof (struct verb)))) {
00676       tmp->verboser = v;
00677       ast_mutex_lock(&msglist_lock);
00678       tmp->next = verboser;
00679       verboser = tmp;
00680       m = list;
00681       while(m) {
00682          /* Send all the existing entries that we have queued (i.e. they're likely to have missed) */
00683          v(m->msg, 0, 0, 1);
00684          m = m->next;
00685       }
00686       ast_mutex_unlock(&msglist_lock);
00687       return 0;
00688    }
00689    return -1;
00690 }
00691 
00692 int ast_unregister_verbose(void (*v)(const char *string, int opos, int replacelast, int complete))
00693 {
00694    int res = -1;
00695    struct verb *tmp, *tmpl=NULL;
00696    ast_mutex_lock(&msglist_lock);
00697    tmp = verboser;
00698    while(tmp) {
00699       if (tmp->verboser == v) {
00700          if (tmpl)
00701             tmpl->next = tmp->next;
00702          else
00703             verboser = tmp->next;
00704          free(tmp);
00705          break;
00706       }
00707       tmpl = tmp;
00708       tmp = tmp->next;
00709    }
00710    if (tmp)
00711       res = 0;
00712    ast_mutex_unlock(&msglist_lock);
00713    return res;
00714 }

Generated on Thu Oct 28 11:32:54 2004 for Asterisk by doxygen1.2.15