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