6a617f8979686c476ae46fb67f3ecbb61a9faf4d
[asterisk/asterisk.git] / logger.c
1 /*
2  * Asterisk Logger
3  * 
4  * Mark Spencer <markster@marko.net>
5  *
6  * Copyright(C)1999, Linux Support Services, Inc.
7  * 
8  * Distributed under the terms of the GNU General Public License (GPL) Version 2
9  *
10  * Logging routines
11  *
12  */
13
14 #include <signal.h>
15 #include <stdarg.h>
16 #include <stdio.h>
17 #include <unistd.h>
18 #include <time.h>
19 #include <asterisk/lock.h>
20 #include <asterisk/options.h>
21 #include <asterisk/channel.h>
22 #include <asterisk/config.h>
23 #include <asterisk/term.h>
24 #include <asterisk/cli.h>
25 #include <asterisk/utils.h>
26 #include <string.h>
27 #include <stdlib.h>
28 #include <errno.h>
29 #include <sys/stat.h>
30 #include "asterisk.h"
31 #include "astconf.h"
32
33 #define SYSLOG_NAMES /* so we can map syslog facilities names to their numeric values,
34                         from <syslog.h> which is included by logger.h */
35 #include <syslog.h>
36 static int syslog_level_map[] = {
37         LOG_DEBUG,
38         LOG_INFO,    /* arbitrary equivalent of LOG_EVENT */
39         LOG_NOTICE,
40         LOG_WARNING,
41         LOG_ERR,
42         LOG_DEBUG
43 };
44
45 #define SYSLOG_NLEVELS 6
46
47 #include <asterisk/logger.h>
48
49 #define MAX_MSG_QUEUE 200
50
51 #if defined(__linux__) && defined(__NR_gettid)
52 #include <asm/unistd.h>
53 #define GETTID() syscall(__NR_gettid)
54 #else
55 #define GETTID() getpid()
56 #endif
57
58 static char dateformat[256] = "%b %e %T";               /* Original Asterisk Format */
59 AST_MUTEX_DEFINE_STATIC(msglist_lock);
60 AST_MUTEX_DEFINE_STATIC(loglock);
61 static int pending_logger_reload = 0;
62 static int global_logmask = -1;
63
64 static struct {
65         unsigned int queue_log:1;
66         unsigned int event_log:1;
67 } logfiles = { 1, 1 };
68
69 static struct msglist {
70         char *msg;
71         struct msglist *next;
72 } *list = NULL, *last = NULL;
73
74 static char hostname[256];
75
76 struct logchannel {
77         int logmask;
78         int facility; /* syslog */
79         int syslog; /* syslog flag */
80         int console;  /* console logging */
81         FILE *fileptr; /* logfile logging */
82         char filename[256];
83         struct logchannel *next;
84 };
85
86 static struct logchannel *logchannels = NULL;
87
88 static int msgcnt = 0;
89
90 static FILE *eventlog = NULL;
91
92 static char *levels[] = {
93         "DEBUG",
94         "EVENT",
95         "NOTICE",
96         "WARNING",
97         "ERROR",
98         "VERBOSE"
99 };
100
101 static int colors[] = {
102         COLOR_BRGREEN,
103         COLOR_BRBLUE,
104         COLOR_YELLOW,
105         COLOR_BRRED,
106         COLOR_RED,
107         COLOR_GREEN
108 };
109
110 static int make_components(char *s, int lineno)
111 {
112         char *w;
113         int res = 0;
114         char *stringp=NULL;
115         stringp=s;
116         w = strsep(&stringp, ",");
117         while(w) {
118             while(*w && (*w < 33))
119                 w++;
120             if (!strcasecmp(w, "error")) 
121                 res |= (1 << __LOG_ERROR);
122             else if (!strcasecmp(w, "warning"))
123                 res |= (1 << __LOG_WARNING);
124             else if (!strcasecmp(w, "notice"))
125                 res |= (1 << __LOG_NOTICE);
126             else if (!strcasecmp(w, "event"))
127                 res |= (1 << __LOG_EVENT);
128             else if (!strcasecmp(w, "debug"))
129                 res |= (1 << __LOG_DEBUG);
130             else if (!strcasecmp(w, "verbose"))
131                 res |= (1 << __LOG_VERBOSE);
132             else {
133                 fprintf(stderr, "Logfile Warning: Unknown keyword '%s' at line %d of logger.conf\n", w, lineno);
134             }
135             w = strsep(&stringp, ",");
136         }
137         return res;
138 }
139
140 static struct logchannel *make_logchannel(char *channel, char *components, int lineno)
141 {
142         struct logchannel *chan;
143         char *facility;
144 #ifndef SOLARIS
145         CODE *cptr;
146 #endif
147
148         if (ast_strlen_zero(channel))
149                 return NULL;
150         chan = malloc(sizeof(struct logchannel));
151
152         if (chan) {
153                 memset(chan, 0, sizeof(struct logchannel));
154                 if (!strcasecmp(channel, "console")) {
155                     chan->console = 1;
156                 } else if (!strncasecmp(channel, "syslog", 6)) {
157                     /*
158                      * syntax is:
159                      *  syslog.facility => level,level,level
160                      */
161                     facility = strchr(channel, '.');
162                     if(!facility++ || !facility) {
163                         facility = "local0";
164                     }
165
166 #ifndef SOLARIS
167                     /*
168                      * Walk through the list of facilitynames (defined in sys/syslog.h)
169                      * to see if we can find the one we have been given
170                      */
171                     chan->facility = -1;
172                     cptr = facilitynames;
173                     while (cptr->c_name) {
174                         if (!strcasecmp(facility, cptr->c_name)) {
175                             chan->facility = cptr->c_val;
176                             break;
177                         }
178                         cptr++;
179                     }
180 #else
181                     chan->facility = -1;
182                     if (!strcasecmp(facility, "kern")) 
183                         chan->facility = LOG_KERN;
184                     else if (!strcasecmp(facility, "USER")) 
185                         chan->facility = LOG_USER;
186                     else if (!strcasecmp(facility, "MAIL")) 
187                         chan->facility = LOG_MAIL;
188                     else if (!strcasecmp(facility, "DAEMON")) 
189                         chan->facility = LOG_DAEMON;
190                     else if (!strcasecmp(facility, "AUTH")) 
191                         chan->facility = LOG_AUTH;
192                     else if (!strcasecmp(facility, "SYSLOG")) 
193                         chan->facility = LOG_SYSLOG;
194                     else if (!strcasecmp(facility, "LPR")) 
195                         chan->facility = LOG_LPR;
196                     else if (!strcasecmp(facility, "NEWS")) 
197                         chan->facility = LOG_NEWS;
198                     else if (!strcasecmp(facility, "UUCP")) 
199                         chan->facility = LOG_UUCP;
200                     else if (!strcasecmp(facility, "CRON")) 
201                         chan->facility = LOG_CRON;
202                     else if (!strcasecmp(facility, "LOCAL0")) 
203                         chan->facility = LOG_LOCAL0;
204                     else if (!strcasecmp(facility, "LOCAL1")) 
205                         chan->facility = LOG_LOCAL1;
206                     else if (!strcasecmp(facility, "LOCAL2")) 
207                         chan->facility = LOG_LOCAL2;
208                     else if (!strcasecmp(facility, "LOCAL3")) 
209                         chan->facility = LOG_LOCAL3;
210                     else if (!strcasecmp(facility, "LOCAL4")) 
211                         chan->facility = LOG_LOCAL4;
212                     else if (!strcasecmp(facility, "LOCAL5")) 
213                         chan->facility = LOG_LOCAL5;
214                     else if (!strcasecmp(facility, "LOCAL6")) 
215                         chan->facility = LOG_LOCAL6;
216                     else if (!strcasecmp(facility, "LOCAL7")) 
217                         chan->facility = LOG_LOCAL7;
218 #endif
219
220                     if (0 > chan->facility) {
221                         fprintf(stderr, "Logger Warning: bad syslog facility in logger.conf\n");
222                         free(chan);
223                         return NULL;
224                     }
225
226                     chan->syslog = 1;
227                     openlog("asterisk", LOG_PID, chan->facility);
228                 } else {
229                         if (channel[0] == '/') {
230                                 if(!ast_strlen_zero(hostname)) { 
231                                         snprintf(chan->filename, sizeof(chan->filename) - 1,"%s.%s", channel, hostname);
232                                 } else {
233                                         strncpy(chan->filename, channel, sizeof(chan->filename) - 1);
234                                 }
235                         }                 
236                         
237                         if(!ast_strlen_zero(hostname)) {
238                                 snprintf(chan->filename, sizeof(chan->filename), "%s/%s.%s",(char *)ast_config_AST_LOG_DIR, channel, hostname);
239                         } else {
240                                 snprintf(chan->filename, sizeof(chan->filename), "%s/%s", (char *)ast_config_AST_LOG_DIR, channel);
241                         }
242                         chan->fileptr = fopen(chan->filename, "a");
243                         if (!chan->fileptr) {
244                                 /* Can't log here, since we're called with a lock */
245                                 fprintf(stderr, "Logger Warning: Unable to open log file '%s': %s\n", chan->filename, strerror(errno));
246                         }
247                 }
248                 chan->logmask = make_components(components, lineno);
249         }
250         return chan;
251 }
252
253 static void init_logger_chain(void)
254 {
255         struct logchannel *chan, *cur;
256         struct ast_config *cfg;
257         struct ast_variable *var;
258         char *s;
259
260         /* delete our list of log channels */
261         ast_mutex_lock(&loglock);
262         chan = logchannels;
263         while (chan) {
264             cur = chan->next;
265             free(chan);
266             chan = cur;
267         }
268         logchannels = NULL;
269         ast_mutex_unlock(&loglock);
270         
271         global_logmask = 0;
272         /* close syslog */
273         closelog();
274         
275         cfg = ast_config_load("logger.conf");
276         
277         /* If no config file, we're fine */
278         if (!cfg)
279             return;
280         
281         ast_mutex_lock(&loglock);
282         if ((s = ast_variable_retrieve(cfg, "general", "appendhostname"))) {
283                 if(ast_true(s)) {
284                         if(gethostname(hostname, sizeof(hostname))) {
285                                 strncpy(hostname, "unknown", sizeof(hostname)-1);
286                                 ast_log(LOG_WARNING, "What box has no hostname???\n");
287                         }
288                 } else
289                         hostname[0] = '\0';
290         } else
291                 hostname[0] = '\0';
292         if ((s = ast_variable_retrieve(cfg, "general", "dateformat"))) {
293                 strncpy(dateformat, s, sizeof(dateformat) - 1);
294         } else
295                 strncpy(dateformat, "%b %e %T", sizeof(dateformat) - 1);
296         if ((s = ast_variable_retrieve(cfg, "general", "queue_log"))) {
297                 logfiles.queue_log = ast_true(s);
298         }
299         if ((s = ast_variable_retrieve(cfg, "general", "event_log"))) {
300                 logfiles.event_log = ast_true(s);
301         }
302
303         var = ast_variable_browse(cfg, "logfiles");
304         while(var) {
305                 chan = make_logchannel(var->name, var->value, var->lineno);
306                 if (chan) {
307                         chan->next = logchannels;
308                         logchannels = chan;
309                         global_logmask |= chan->logmask;
310                 }
311                 var = var->next;
312         }
313
314         ast_config_destroy(cfg);
315         ast_mutex_unlock(&loglock);
316 }
317
318 static FILE *qlog = NULL;
319 AST_MUTEX_DEFINE_STATIC(qloglock);
320
321 void ast_queue_log(const char *queuename, const char *callid, const char *agent, const char *event, const char *fmt, ...)
322 {
323         va_list ap;
324         ast_mutex_lock(&qloglock);
325         if (qlog) {
326                 va_start(ap, fmt);
327                 fprintf(qlog, "%ld|%s|%s|%s|%s|", (long)time(NULL), callid, queuename, agent, event);
328                 vfprintf(qlog, fmt, ap);
329                 fprintf(qlog, "\n");
330                 va_end(ap);
331                 fflush(qlog);
332         }
333         ast_mutex_unlock(&qloglock);
334 }
335
336 static void queue_log_init(void)
337 {
338         char filename[256];
339         int reloaded = 0;
340         ast_mutex_lock(&qloglock);
341         if (qlog) {
342                 reloaded = 1;
343                 fclose(qlog);
344                 qlog = NULL;
345         }
346         snprintf(filename, sizeof(filename), "%s/%s", (char *)ast_config_AST_LOG_DIR, "queue_log");
347         if (logfiles.queue_log) {
348                 qlog = fopen(filename, "a");
349         }
350         ast_mutex_unlock(&qloglock);
351         if (reloaded) 
352                 ast_queue_log("NONE", "NONE", "NONE", "CONFIGRELOAD", "%s", "");
353         else
354                 ast_queue_log("NONE", "NONE", "NONE", "QUEUESTART", "%s", "");
355 }
356
357 int reload_logger(int rotate)
358 {
359         char old[AST_CONFIG_MAX_PATH] = "";
360         char new[AST_CONFIG_MAX_PATH];
361         struct logchannel *f;
362         FILE *myf;
363
364         int x;
365         ast_mutex_lock(&loglock);
366         if (eventlog) 
367                 fclose(eventlog);
368         else 
369                 rotate = 0;
370         eventlog = NULL;
371
372
373
374         mkdir((char *)ast_config_AST_LOG_DIR, 0755);
375         snprintf(old, sizeof(old), "%s/%s", (char *)ast_config_AST_LOG_DIR, EVENTLOG);
376
377         if (logfiles.event_log) {
378                 if (rotate) {
379                         for (x=0;;x++) {
380                                 snprintf(new, sizeof(new), "%s/%s.%d", (char *)ast_config_AST_LOG_DIR, EVENTLOG,x);
381                                 myf = fopen((char *)new, "r");
382                                 if (myf) 
383                                         fclose(myf);
384                                 else
385                                         break;
386                         }
387         
388                         /* do it */
389                         if (rename(old,new))
390                                 fprintf(stderr, "Unable to rename file '%s' to '%s'\n", old, new);
391                 }
392
393                 eventlog = fopen(old, "a");
394         }
395
396         f = logchannels;
397         while(f) {
398                 if (f->fileptr && (f->fileptr != stdout) && (f->fileptr != stderr)) {
399                         fclose(f->fileptr);
400                         f->fileptr = NULL;
401                         if(rotate) {
402                                 strncpy(old, f->filename, sizeof(old) - 1);
403         
404                                 for(x=0;;x++) {
405                                         snprintf(new, sizeof(new), "%s.%d", f->filename, x);
406                                         myf = fopen((char *)new, "r");
407                                         if (myf) {
408                                                 fclose(myf);
409                                         } else {
410                                                 break;
411                                         }
412                                 }
413             
414                                 /* do it */
415                                 if (rename(old,new))
416                                         fprintf(stderr, "Unable to rename file '%s' to '%s'\n", old, new);
417                         }
418                 }
419                 f = f->next;
420         }
421
422         ast_mutex_unlock(&loglock);
423
424         queue_log_init();
425         init_logger_chain();
426
427         if (logfiles.event_log) {
428                 if (eventlog) {
429                         ast_log(LOG_EVENT, "Restarted Asterisk Event Logger\n");
430                         if (option_verbose)
431                                 ast_verbose("Asterisk Event Logger restarted\n");
432                         return 0;
433                 } else 
434                         ast_log(LOG_ERROR, "Unable to create event log: %s\n", strerror(errno));
435         }
436         pending_logger_reload = 0;
437         return -1;
438 }
439
440 static int handle_logger_reload(int fd, int argc, char *argv[])
441 {
442         if(reload_logger(0))
443         {
444                 ast_cli(fd, "Failed to reload the logger\n");
445                 return RESULT_FAILURE;
446         }
447         else
448                 return RESULT_SUCCESS;
449 }
450
451 static int handle_logger_rotate(int fd, int argc, char *argv[])
452 {
453         if(reload_logger(1))
454         {
455                 ast_cli(fd, "Failed to reload the logger and rotate log files\n");
456                 return RESULT_FAILURE;
457         }
458         else
459                 return RESULT_SUCCESS;
460 }
461
462 static struct verb {
463         void (*verboser)(const char *string, int opos, int replacelast, int complete);
464         struct verb *next;
465 } *verboser = NULL;
466
467
468 static char logger_reload_help[] =
469 "Usage: logger reload\n"
470 "       Reloads the logger subsystem state.  Use after restarting syslogd(8)\n";
471
472 static char logger_rotate_help[] =
473 "Usage: logger rotate\n"
474 "       Rotates and Reopens the log files.\n";
475
476 static struct ast_cli_entry reload_logger_cli = 
477         { { "logger", "reload", NULL }, 
478         handle_logger_reload, "Reopens the log files",
479         logger_reload_help };
480
481 static struct ast_cli_entry rotate_logger_cli = 
482         { { "logger", "rotate", NULL }, 
483         handle_logger_rotate, "Rotates and reopens the log files",
484         logger_rotate_help };
485
486 static int handle_SIGXFSZ(int sig) 
487 {
488         /* Indicate need to reload */
489         pending_logger_reload = 1;
490         return 0;
491 }
492
493 int init_logger(void)
494 {
495         char tmp[256];
496
497         /* auto rotate if sig SIGXFSZ comes a-knockin */
498         (void) signal(SIGXFSZ,(void *) handle_SIGXFSZ);
499
500         /* register the relaod logger cli command */
501         ast_cli_register(&reload_logger_cli);
502         ast_cli_register(&rotate_logger_cli);
503
504         /* initialize queue logger */
505         queue_log_init();
506
507         /* create log channels */
508         init_logger_chain();
509
510         /* create the eventlog */
511         if (logfiles.event_log) {
512                 mkdir((char *)ast_config_AST_LOG_DIR, 0755);
513                 snprintf(tmp, sizeof(tmp), "%s/%s", (char *)ast_config_AST_LOG_DIR, EVENTLOG);
514                 eventlog = fopen((char *)tmp, "a");
515                 if (eventlog) {
516                         ast_log(LOG_EVENT, "Started Asterisk Event Logger\n");
517                         if (option_verbose)
518                                 ast_verbose("Asterisk Event Logger Started %s\n",(char *)tmp);
519                         return 0;
520                 } else 
521                         ast_log(LOG_ERROR, "Unable to create event log: %s\n", strerror(errno));
522         }
523
524         return -1;
525 }
526
527 void close_logger(void)
528 {
529         struct msglist *m, *tmp;
530
531         ast_mutex_lock(&msglist_lock);
532         m = list;
533         while(m) {
534                 if (m->msg) {
535                         free(m->msg);
536                 }
537                 tmp = m->next;
538                 free(m);
539                 m = tmp;
540         }
541         list = last = NULL;
542         msgcnt = 0;
543         ast_mutex_unlock(&msglist_lock);
544         return;
545 }
546
547 static void ast_log_vsyslog(int level, const char *file, int line, const char *function, const char *fmt, va_list args) 
548 {
549         char buf[BUFSIZ];
550
551         if (level >= SYSLOG_NLEVELS) {
552                 /* we are locked here, so cannot ast_log() */
553                 fprintf(stderr, "ast_log_vsyslog called with bogus level: %d\n", level);
554                 return;
555         }
556         if (level == __LOG_VERBOSE) {
557                 snprintf(buf, sizeof(buf), "VERBOSE[%ld]: ", (long)GETTID());
558                 level = __LOG_DEBUG;
559         } else {
560                 snprintf(buf, sizeof(buf), "%s[%ld]: %s:%d in %s: ",
561                         levels[level], (long)GETTID(), file, line, function);
562         }
563         vsnprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), fmt, args);
564         syslog(syslog_level_map[level], "%s", buf);
565 }
566
567 /*
568  * send log messages to syslog and/or the console
569  */
570 void ast_log(int level, const char *file, int line, const char *function, const char *fmt, ...)
571 {
572         struct logchannel *chan;
573         char buf[BUFSIZ];
574         time_t t;
575         struct tm tm;
576         char date[256];
577
578         va_list ap;
579         
580         if (!option_verbose && !option_debug && (level == __LOG_DEBUG)) {
581                 return;
582         }
583         /* Ignore anything that never gets logged anywhere */
584         if (!(global_logmask & (1 << level)))
585                 return;
586         
587         /* Ignore anything other than the currently debugged file if there is one */
588         if ((level == __LOG_DEBUG) && !ast_strlen_zero(debug_filename) && strcasecmp(debug_filename, file))
589                 return;
590
591         /* begin critical section */
592         ast_mutex_lock(&loglock);
593
594         time(&t);
595         localtime_r(&t, &tm);
596         strftime(date, sizeof(date), dateformat, &tm);
597
598         if (logfiles.event_log && level == __LOG_EVENT) {
599                 va_start(ap, fmt);
600
601                 fprintf(eventlog, "%s asterisk[%d]: ", date, getpid());
602                 vfprintf(eventlog, fmt, ap);
603                 fflush(eventlog);
604
605                 va_end(ap);
606                 ast_mutex_unlock(&loglock);
607                 return;
608         }
609
610         if (logchannels) {
611                 chan = logchannels;
612                 while(chan) {
613                         if (chan->syslog && (chan->logmask & (1 << level))) {
614                                 va_start(ap, fmt);
615                                 ast_log_vsyslog(level, file, line, function, fmt, ap);
616                                 va_end(ap);
617                         } else if ((chan->logmask & (1 << level)) && (chan->console)) {
618                                 char linestr[128];
619                                 char tmp1[80], tmp2[80], tmp3[80], tmp4[80];
620
621                                 if (level != __LOG_VERBOSE) {
622                                         sprintf(linestr, "%d", line);
623                                         snprintf(buf, sizeof(buf), option_timestamp ? "[%s] %s[%ld]: %s:%s %s: " : "%s %s[%ld]: %s:%s %s: ",
624                                                 date,
625                                                 term_color(tmp1, levels[level], colors[level], 0, sizeof(tmp1)),
626                                                 (long)GETTID(),
627                                                 term_color(tmp2, file, COLOR_BRWHITE, 0, sizeof(tmp2)),
628                                                 term_color(tmp3, linestr, COLOR_BRWHITE, 0, sizeof(tmp3)),
629                                                 term_color(tmp4, function, COLOR_BRWHITE, 0, sizeof(tmp4)));
630                                         
631                                         ast_console_puts(buf);
632                                         va_start(ap, fmt);
633                                         vsnprintf(buf, sizeof(buf), fmt, ap);
634                                         va_end(ap);
635                                         ast_console_puts(buf);
636                                 }
637                         } else if ((chan->logmask & (1 << level)) && (chan->fileptr)) {
638                                 snprintf(buf, sizeof(buf), option_timestamp ? "[%s] %s[%ld]: " : "%s %s[%ld]: ", date,
639                                         levels[level], (long)GETTID());
640                                 fprintf(chan->fileptr, buf);
641                                 va_start(ap, fmt);
642                                 vsnprintf(buf, sizeof(buf), fmt, ap);
643                                 va_end(ap);
644                                 fputs(buf, chan->fileptr);
645                                 fflush(chan->fileptr);
646                         }
647                         chan = chan->next;
648                 }
649         } else {
650                 /* 
651                  * we don't have the logger chain configured yet,
652                  * so just log to stdout 
653                 */
654                 if (level != __LOG_VERBOSE) {
655                         va_start(ap, fmt);
656                         vsnprintf(buf, sizeof(buf), fmt, ap);
657                         va_end(ap);
658                         fputs(buf, stdout);
659                 }
660         }
661
662         ast_mutex_unlock(&loglock);
663         /* end critical section */
664         if (pending_logger_reload) {
665                 reload_logger(1);
666                 ast_log(LOG_EVENT,"Rotated Logs Per SIGXFSZ\n");
667                 if (option_verbose)
668                         ast_verbose("Rotated Logs Per SIGXFSZ\n");
669         }
670 }
671
672 extern void ast_verbose(const char *fmt, ...)
673 {
674         static char stuff[4096];
675         static int pos = 0, opos;
676         static int replacelast = 0, complete;
677         struct msglist *m;
678         struct verb *v;
679         time_t t;
680         struct tm tm;
681         char date[40];
682         char *datefmt;
683         
684         va_list ap;
685         va_start(ap, fmt);
686         ast_mutex_lock(&msglist_lock);
687         time(&t);
688         localtime_r(&t, &tm);
689         strftime(date, sizeof(date), dateformat, &tm);
690
691         if (option_timestamp) {
692                 datefmt = alloca(strlen(date) + 3 + strlen(fmt) + 1);
693                 if (datefmt) {
694                         sprintf(datefmt, "[%s] %s", date, fmt);
695                         fmt = datefmt;
696                 }
697         }
698         vsnprintf(stuff + pos, sizeof(stuff) - pos, fmt, ap);
699         opos = pos;
700         pos = strlen(stuff);
701
702
703         if (stuff[strlen(stuff)-1] == '\n') 
704                 complete = 1;
705         else
706                 complete=0;
707         if (complete) {
708                 if (msgcnt < MAX_MSG_QUEUE) {
709                         /* Allocate new structure */
710                         m = malloc(sizeof(struct msglist));
711                         msgcnt++;
712                 } else {
713                         /* Recycle the oldest entry */
714                         m = list;
715                         list = list->next;
716                         free(m->msg);
717                 }
718                 if (m) {
719                         m->msg = strdup(stuff);
720                         if (m->msg) {
721                                 if (last)
722                                         last->next = m;
723                                 else
724                                         list = m;
725                                 m->next = NULL;
726                                 last = m;
727                         } else {
728                                 msgcnt--;
729                                 ast_log(LOG_ERROR, "Out of memory\n");
730                                 free(m);
731                         }
732                 }
733         }
734         if (verboser) {
735                 v = verboser;
736                 while(v) {
737                         v->verboser(stuff, opos, replacelast, complete);
738                         v = v->next;
739                 }
740         } /* else
741                 fprintf(stdout, stuff + opos); */
742         ast_log(LOG_VERBOSE, "%s", stuff);
743         if (strlen(stuff)) {
744                 if (stuff[strlen(stuff)-1] != '\n') 
745                         replacelast = 1;
746                 else 
747                         replacelast = pos = 0;
748         }
749         va_end(ap);
750
751         ast_mutex_unlock(&msglist_lock);
752 }
753
754 int ast_verbose_dmesg(void (*v)(const char *string, int opos, int replacelast, int complete))
755 {
756         struct msglist *m;
757         ast_mutex_lock(&msglist_lock);
758         m = list;
759         while(m) {
760                 /* Send all the existing entries that we have queued (i.e. they're likely to have missed) */
761                 v(m->msg, 0, 0, 1);
762                 m = m->next;
763         }
764         ast_mutex_unlock(&msglist_lock);
765         return 0;
766 }
767
768 int ast_register_verbose(void (*v)(const char *string, int opos, int replacelast, int complete)) 
769 {
770         struct msglist *m;
771         struct verb *tmp;
772         /* XXX Should be more flexible here, taking > 1 verboser XXX */
773         if ((tmp = malloc(sizeof (struct verb)))) {
774                 tmp->verboser = v;
775                 ast_mutex_lock(&msglist_lock);
776                 tmp->next = verboser;
777                 verboser = tmp;
778                 m = list;
779                 while(m) {
780                         /* Send all the existing entries that we have queued (i.e. they're likely to have missed) */
781                         v(m->msg, 0, 0, 1);
782                         m = m->next;
783                 }
784                 ast_mutex_unlock(&msglist_lock);
785                 return 0;
786         }
787         return -1;
788 }
789
790 int ast_unregister_verbose(void (*v)(const char *string, int opos, int replacelast, int complete))
791 {
792         int res = -1;
793         struct verb *tmp, *tmpl=NULL;
794         ast_mutex_lock(&msglist_lock);
795         tmp = verboser;
796         while(tmp) {
797                 if (tmp->verboser == v) {
798                         if (tmpl)
799                                 tmpl->next = tmp->next;
800                         else
801                                 verboser = tmp->next;
802                         free(tmp);
803                         break;
804                 }
805                 tmpl = tmp;
806                 tmp = tmp->next;
807         }
808         if (tmp)
809                 res = 0;
810         ast_mutex_unlock(&msglist_lock);
811         return res;
812 }