736edb03f4daa8b671ef8a6183797dfec908b93d
[silc.git] / lib / silcutil / silclog.c
1 /*
2
3   silclog.c
4
5   Author: Giovanni Giacobbi <giovanni@giacobbi.net>
6
7   Copyright (C) 1997 - 2002 Pekka Riikonen
8
9   This program is free software; you can redistribute it and/or modify
10   it under the terms of the GNU General Public License as published by
11   the Free Software Foundation; either version 2 of the License, or
12   (at your option) any later version.
13
14   This program is distributed in the hope that it will be useful,
15   but WITHOUT ANY WARRANTY; without even the implied warranty of
16   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17   GNU General Public License for more details.
18
19 */
20 /* $Id$ */
21
22 #include "silcincludes.h"
23
24 /* Minimum time delay for log flushing calls (in seconds) */
25 #define SILC_LOG_FLUSH_MIN_DELAY 2
26
27 /* nice macro for looping through all logs -- makes the code more readable */
28 #define SILC_FOREACH_LOG(__x__) for (__x__ = 0; __x__ < SILC_LOG_MAX; __x__++)
29
30 /* Our working struct -- at the moment we keep it private, but this could
31  * change in the future */
32 struct SilcLogStruct {
33   char filename[256];
34   FILE *fp;
35   SilcUInt32 maxsize;
36   const char *typename;
37   SilcLogType type;
38   SilcLogCb cb;
39   void *context;
40 };
41 typedef struct SilcLogStruct *SilcLog;
42
43 /* These are the known logging channels.  We initialize this struct with most
44  * of the fields set to NULL, because we'll fill in those values at runtime. */
45 static struct SilcLogStruct silclogs[SILC_LOG_MAX] = {
46   {"", NULL, 0, "Info", SILC_LOG_INFO, NULL, NULL},
47   {"", NULL, 0, "Warning", SILC_LOG_WARNING, NULL, NULL},
48   {"", NULL, 0, "Error", SILC_LOG_ERROR, NULL, NULL},
49   {"", NULL, 0, "Fatal", SILC_LOG_FATAL, NULL, NULL},
50 };
51
52 /* Causes logging output to contain timestamps */
53 bool silc_log_timestamp = TRUE;
54
55 /* If TRUE, log files will be flushed for each log input */
56 bool silc_log_quick = FALSE;
57
58 /* Set TRUE/FALSE to enable/disable debugging */
59 bool silc_debug = FALSE;
60 bool silc_debug_hexdump = FALSE;
61
62 /* Flush delay (in seconds) */
63 long silc_log_flushdelay = 300;
64
65 /* Regular pattern matching expression for the debug output */
66 char silc_log_debug_string[128];
67
68 /* Debug callbacks. If set, these are triggered for each specific output. */
69 static SilcLogDebugCb silc_log_debug_cb = NULL;
70 static void *silc_log_debug_context = NULL;
71 static SilcLogHexdumpCb silc_log_hexdump_cb = NULL;
72 static void *silc_log_hexdump_context = NULL;
73
74 /* Did we register already our functions to the scheduler? */
75 static bool silc_log_scheduled = FALSE;
76 static bool silc_log_no_init = FALSE;
77
78 /* This is only needed during starting up -- don't lose any logging message */
79 static bool silc_log_starting = TRUE;
80
81 /* The type wrapper utility. Translates a SilcLogType id to the corresponding
82  * logfile, or NULL if not found. */
83 static SilcLog silc_log_find_by_type(SilcLogType type)
84 {
85   /* this is not really needed, but i think it's more secure */
86   switch (type) {
87     case SILC_LOG_INFO:
88       return &silclogs[SILC_LOG_INFO];
89     case SILC_LOG_WARNING:
90       return &silclogs[SILC_LOG_WARNING];
91     case SILC_LOG_ERROR:
92       return &silclogs[SILC_LOG_ERROR];
93     case SILC_LOG_FATAL:
94       return &silclogs[SILC_LOG_FATAL];
95     default:
96       return NULL;
97   }
98   return NULL;
99 }
100
101 /* Given an open log file, checks the size and rotates it if there is a
102  * max size set lower then the current size */
103 static void silc_log_checksize(SilcLog log)
104 {
105   char newname[127];
106   long size;
107
108   if (!log || !log->fp || !log->maxsize)
109     return; /* we are not interested */
110
111   if ((size = ftell(log->fp)) < 0) {
112     /* OMG, EBADF is here.. we'll try our best.. */
113     FILE *oldfp = log->fp;
114     fclose(oldfp); /* we can discard the error */
115     log->fp = NULL; /* make sure we don't get here recursively */
116     SILC_LOG_ERROR(("Error while checking size of the log file %s, fp=%p",
117                     log->filename, oldfp));
118     return;
119   }
120   if (size < log->maxsize)
121     return;
122
123   /* It's too big */
124   fprintf(log->fp, "[%s] [%s] Cycling log file, over max "
125           "logsize (%lu kilobytes)\n",
126           silc_get_time(0), log->typename, log->maxsize / 1024);
127   fflush(log->fp);
128   fclose(log->fp);
129   memset(newname, 0, sizeof(newname));
130   snprintf(newname, sizeof(newname) - 1, "%s.old", log->filename);
131   unlink(newname);
132
133   /* I heard the following syscall may cause portability issues, but I don't
134    * have any other solution since SILC library doesn't provide any other
135    * function like this. -Johnny */
136   rename(log->filename, newname);
137   if (!(log->fp = fopen(log->filename, "w")))
138     SILC_LOG_WARNING(("Couldn't reopen logfile %s for type \"%s\": %s",
139                       log->filename, log->typename, strerror(errno)));
140 }
141
142 /* Reset a logging channel (close and reopen) */
143
144 static bool silc_log_reset(SilcLog log)
145 {
146   if (!log) return FALSE;
147   if (log->fp) {
148     fflush(log->fp);
149     fclose(log->fp);
150   }
151   if (!log->filename[0]) return FALSE;
152   if (!(log->fp = fopen(log->filename, "a+"))) {
153     SILC_LOG_WARNING(("Couldn't reset logfile %s for type \"%s\": %s",
154                       log->filename, log->typename, strerror(errno)));
155     return FALSE;
156   }
157
158   return TRUE;
159 }
160
161 /* Internal timeout callback to flush log channels and check file sizes */
162
163 SILC_TASK_CALLBACK(silc_log_fflush_callback)
164 {
165   unsigned int u;
166   if (!silc_log_quick) {
167     silc_log_flush_all();
168     SILC_FOREACH_LOG(u)
169       silc_log_checksize(&silclogs[u]);
170   }
171   silc_log_starting = FALSE;
172   if (silc_log_flushdelay < SILC_LOG_FLUSH_MIN_DELAY)
173     silc_log_flushdelay = SILC_LOG_FLUSH_MIN_DELAY;
174   silc_schedule_task_add((SilcSchedule) context, 0, silc_log_fflush_callback,
175                          context, silc_log_flushdelay, 0, SILC_TASK_TIMEOUT,
176                          SILC_TASK_PRI_NORMAL);
177 }
178
179 /* Outputs the log message to the first available channel. Channels are
180  * ordered by importance (see SilcLogType documentation).
181  * More important channels can be printed on less important ones, but not
182  * vice-versa. */
183
184 void silc_log_output(SilcLogType type, char *string)
185 {
186   const char *typename = NULL;
187   FILE *fp;
188   SilcLog log;
189
190   if ((type > SILC_LOG_MAX) || !(log = silc_log_find_by_type(type)))
191     goto end;
192
193   /* Save the original typename, because even if we redirect the message
194    * to another channel we'll keep however the original channel name */
195   typename = log->typename;
196
197   /* If there is a custom callback set, use it and return. */
198   if (log->cb) {
199     if ((*log->cb)(type, string, log->context))
200       goto end;
201   }
202
203   if (!silc_log_scheduled) {
204     if (silc_log_no_init == FALSE) {
205       fprintf(stderr,
206               "Warning, trying to output without log files initialization, "
207               "log output is going to stderr\n");
208       silc_log_no_init = TRUE;
209     }
210     /* redirect output */
211     fp = stderr;
212     log = NULL;
213     goto found;
214   }
215
216   /* ok, now we have to find an open stream */
217   while (TRUE) {
218     if (log && (fp = log->fp)) goto found;
219     if (type == 0) break;
220     log = silc_log_find_by_type(--type);
221   }
222
223   /* Couldn't find any open stream.. sorry :( */
224   SILC_LOG_DEBUG(("Warning! couldn't find any available log channel!"));
225   goto end;
226
227  found:
228   /* writes the logging string to the selected channel */
229   if (silc_log_timestamp)
230     fprintf(fp, "[%s] [%s] %s\n", silc_get_time(0), typename, string);
231   else
232     fprintf(fp, "[%s] %s\n", typename, string);
233
234   if (silc_log_quick || silc_log_starting) {
235     fflush(fp);
236     if (log) /* we may have been redirected to stderr */
237       silc_log_checksize(log);
238   }
239
240  end:
241   /* If debugging, also output the logging message to the console */
242   if (typename && silc_debug) {
243     fprintf(stderr, "[Logging] [%s] %s\n", typename, string);
244     fflush(stderr);
245   }
246   silc_free(string);
247 }
248
249 /* returns an internally allocated pointer to a logging channel file */
250 char *silc_log_get_file(SilcLogType type)
251 {
252   SilcLog log;
253
254   if (!(log = silc_log_find_by_type(type)))
255     return NULL;
256   if (log->fp)
257     return log->filename;
258   return NULL;
259 }
260
261 /* Set and initialize the specified logging channel. See the API reference */
262 bool silc_log_set_file(SilcLogType type, char *filename, SilcUInt32 maxsize,
263                        SilcSchedule scheduler)
264 {
265   FILE *fp = NULL;
266   SilcLog log;
267
268   log = silc_log_find_by_type(type);
269   if (!log)
270     return FALSE;
271
272   SILC_LOG_DEBUG(("Setting \"%s\" file to %s (max size=%d)",
273                   log->typename, filename, maxsize));
274
275   /* before assuming the new file, make sure we can open it */
276   if (filename) {
277     if (!(fp = fopen(filename, "a+"))) {
278       fprintf(stderr, "warning: couldn't open log file %s: %s\n",
279               filename, strerror(errno));
280       return FALSE;
281     }
282   }
283
284   /* clean the logging channel */
285   if (strlen(log->filename)) {
286     if (log->fp)
287       fclose(log->fp);
288     memset(log->filename, 0, sizeof(log->filename));
289     log->fp = NULL;
290   }
291
292   if (fp) {
293     memset(log->filename, 0, sizeof(log->filename));
294     strncpy(log->filename, filename,
295             strlen(filename) < sizeof(log->filename) ? strlen(filename) :
296             sizeof(log->filename) - 1);
297     log->fp = fp;
298     log->maxsize = maxsize;
299   }
300
301   if (scheduler) {
302     if (silc_log_scheduled)
303       return TRUE;
304
305     /* Add schedule hook with a short delay to make sure we'll use 
306        right delay */
307     silc_schedule_task_add(scheduler, 0, silc_log_fflush_callback,
308                            (void *) scheduler, 10, 0,
309                            SILC_TASK_TIMEOUT, SILC_TASK_PRI_NORMAL);
310     silc_log_scheduled = TRUE;
311   }
312
313   return TRUE;
314 }
315
316 /* Sets a log callback, set callback to NULL to return to default behaviour */
317
318 void silc_log_set_callback(SilcLogType type, SilcLogCb cb, void *context)
319 {
320   SilcLog log;
321
322   if (!(log = silc_log_find_by_type(type)))
323     return;
324
325   log->cb = cb;
326   log->context = context;
327 }
328
329 /* Resets log callbacks */
330
331 void silc_log_reset_callbacks()
332 {
333   unsigned int u;
334   SILC_FOREACH_LOG(u) {
335     silclogs[u].cb = NULL;
336     silclogs[u].context = NULL;
337   }
338 }
339
340 /* Flushes all opened logging channels */
341
342 void silc_log_flush_all() {
343   unsigned int u;
344   SILC_LOG_DEBUG(("Flushing all logs"));
345   SILC_FOREACH_LOG(u) {
346     if (silclogs[u].fp)
347       fflush(silclogs[u].fp);
348   }
349 }
350
351 /* Resets all known logging channels */
352
353 void silc_log_reset_all() {
354   unsigned int u;
355   SILC_LOG_DEBUG(("Resetting all logs"));
356   SILC_FOREACH_LOG(u)
357     silc_log_reset(&silclogs[u]);
358   /* Immediately flush any possible warning message */
359   silc_log_flush_all();
360 }
361
362 /* Outputs the debug message to stderr. */
363
364 void silc_log_output_debug(char *file, char *function,
365                            int line, char *string)
366 {
367   if (!silc_debug)
368     goto end;
369
370   if (silc_log_debug_string &&
371       !silc_string_regex_match(silc_log_debug_string, file) &&
372       !silc_string_regex_match(silc_log_debug_string, function))
373     goto end;
374
375   if (silc_log_debug_cb) {
376     if ((*silc_log_debug_cb)(file, function, line, string,
377                              silc_log_debug_context))
378       goto end;
379   }
380
381   fprintf(stderr, "%s:%d: %s\n", function, line, string);
382   fflush(stderr);
383
384  end:
385   silc_free(string);
386 }
387
388 /* Hexdumps a message */
389
390 void silc_log_output_hexdump(char *file, char *function,
391                              int line, void *data_in,
392                              SilcUInt32 len, char *string)
393 {
394   int i, k;
395   int off, pos, count;
396   unsigned char *data = (unsigned char *)data_in;
397
398   if (!silc_debug_hexdump)
399     goto end;
400
401   if (silc_log_debug_string &&
402       !silc_string_regex_match(silc_log_debug_string, file) &&
403       !silc_string_regex_match(silc_log_debug_string, function))
404     goto end;
405
406   if (silc_log_hexdump_cb) {
407     if ((*silc_log_hexdump_cb)(file, function, line, data_in, len, string,
408                                silc_log_hexdump_context))
409       goto end;
410   }
411
412   fprintf(stderr, "%s:%d: %s\n", function, line, string);
413
414   k = 0;
415   pos = 0;
416   count = 16;
417   off = len % 16;
418   while (1) {
419     if (off) {
420       if ((len - pos) < 16 && (len - pos <= len - off))
421         count = off;
422     } else {
423       if (pos == len)
424         count = 0;
425     }
426     if (off == len)
427       count = len;
428
429     if (count)
430       fprintf(stderr, "%08X  ", k++ * 16);
431
432     for (i = 0; i < count; i++) {
433       fprintf(stderr, "%02X ", data[pos + i]);
434
435       if ((i + 1) % 4 == 0)
436         fprintf(stderr, " ");
437     }
438
439     if (count && count < 16) {
440       int j;
441
442       for (j = 0; j < 16 - count; j++) {
443         fprintf(stderr, "   ");
444
445         if ((j + count + 1) % 4 == 0)
446           fprintf(stderr, " ");
447       }
448     }
449
450     for (i = 0; i < count; i++) {
451       char ch;
452
453       if (data[pos] < 32 || data[pos] >= 127)
454         ch = '.';
455       else
456         ch = data[pos];
457
458       fprintf(stderr, "%c", ch);
459       pos++;
460     }
461
462     if (count)
463       fprintf(stderr, "\n");
464
465     if (count < 16)
466       break;
467   }
468
469  end:
470   silc_free(string);
471 }
472
473 /* Sets debug callbacks */
474
475 void silc_log_set_debug_callbacks(SilcLogDebugCb debug_cb,
476                                   void *debug_context,
477                                   SilcLogHexdumpCb hexdump_cb,
478                                   void *hexdump_context)
479 {
480   silc_log_debug_cb = debug_cb;
481   silc_log_debug_context = debug_context;
482   silc_log_hexdump_cb = hexdump_cb;
483   silc_log_hexdump_context = hexdump_context;
484 }
485
486 /* Resets debug callbacks */
487
488 void silc_log_reset_debug_callbacks()
489 {
490   silc_log_debug_cb = NULL;
491   silc_log_debug_context = NULL;
492   silc_log_hexdump_cb = NULL;
493   silc_log_hexdump_context = NULL;
494 }
495
496 /* Set current debug string */
497
498 void silc_log_set_debug_string(const char *debug_string)
499 {
500   char *string;
501   int len;
502   if ((strchr(debug_string, '(') && strchr(debug_string, ')')) ||
503       strchr(debug_string, '$'))
504     string = strdup(debug_string);
505   else
506     string = silc_string_regexify(debug_string);
507   len = strlen(string);
508   if (len >= sizeof(silc_log_debug_string))
509     len = sizeof(silc_log_debug_string) - 1;
510   memset(silc_log_debug_string, 0, sizeof(silc_log_debug_string));
511   strncpy(silc_log_debug_string, string, len);
512   silc_free(string);
513 }