Merged silc_1_1_branch to trunk.
[silc.git] / lib / silcutil / silclog.c
1 /*
2
3   silclog.c
4
5   Author: Pekka Riikonen <priikone@silcnet.org>
6
7   Copyright (C) 1997 - 2007 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; version 2 of the License.
12
13   This program is distributed in the hope that it will be useful,
14   but WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16   GNU General Public License for more details.
17
18 */
19 /* $Id$ */
20
21 #include "silc.h"
22
23 /* SilcLogSettings context */
24 typedef struct {
25   SilcUInt32 flushdelay;
26
27   char debug_string[128];
28   SilcLogDebugCb debug_cb;
29   void *debug_context;
30   SilcLogHexdumpCb hexdump_cb;
31   void *hexdump_context;
32
33   unsigned int timestamp       : 1;
34   unsigned int quick           : 1;
35   unsigned int debug           : 1;
36   unsigned int debug_hexdump   : 1;
37   unsigned int scheduled       : 1;
38   unsigned int no_init         : 1;
39   unsigned int starting        : 1;
40 } *SilcLogSettings, SilcLogSettingsStruct;
41
42 /* SilcLog context */
43 typedef struct {
44   char filename[256];
45   FILE *fp;
46   SilcUInt64 maxsize;
47   const char *typename;
48   SilcLogType type;
49   SilcLogCb cb;
50   void *context;
51 } *SilcLog, SilcLogStruct;
52
53 #ifndef SILC_SYMBIAN
54
55 /* Default settings */
56 static SilcLogSettingsStruct silclog =
57 {
58   300,
59   { 0 },
60   NULL, NULL,
61   NULL, NULL,
62   TRUE,
63   FALSE,
64   FALSE,
65   FALSE,
66   FALSE,
67   FALSE,
68   TRUE,
69 };
70
71 #endif /* !SILC_SYMBIAN */
72
73 /* Default log contexts */
74 #ifndef SILC_SYMBIAN
75 static SilcLogStruct silclogs[4] =
76 #else
77 const SilcLogStruct silclogs[4] =
78 #endif /* !SILC_SYMBIAN */
79 {
80   {"", NULL, 0, "Info", SILC_LOG_INFO, NULL, NULL},
81   {"", NULL, 0, "Warning", SILC_LOG_WARNING, NULL, NULL},
82   {"", NULL, 0, "Error", SILC_LOG_ERROR, NULL, NULL},
83   {"", NULL, 0, "Fatal", SILC_LOG_FATAL, NULL, NULL},
84 };
85
86 /* Return log context by type */
87
88 static SilcLog silc_log_get_context(SilcLogType type)
89 {
90   if (type < 1 || type > 4)
91     return NULL;
92   return (SilcLog)&silclogs[(int)type - 1];
93 }
94
95 /* Check log file site and cycle log file if it is over max size. */
96
97 static void silc_log_checksize(SilcLog log)
98 {
99   char newname[256];
100   SilcUInt64 size;
101
102   if (!log || !log->fp || !log->maxsize)
103     return;
104
105   size = silc_file_size(log->filename);
106   if (!size) {
107     fclose(log->fp);
108     log->fp = NULL;
109   }
110
111   if (size < log->maxsize)
112     return;
113
114   /* Cycle log file */
115   fprintf(log->fp,
116           "[%s] [%s] Cycling log file, over max log size (%lu kilobytes)\n",
117           silc_time_string(0), log->typename,
118           (unsigned long)log->maxsize / 1024);
119   fflush(log->fp);
120   fclose(log->fp);
121
122   memset(newname, 0, sizeof(newname));
123   silc_snprintf(newname, sizeof(newname) - 1, "%s.old", log->filename);
124   unlink(newname);
125   rename(log->filename, newname);
126
127   log->fp = fopen(log->filename, "w");
128   if (!log->fp)
129     SILC_LOG_WARNING(("Couldn't reopen log file '%s' for type '%s': %s",
130                       log->filename, log->typename, strerror(errno)));
131 #ifdef HAVE_CHMOD
132   chmod(log->filename, 0600);
133 #endif /* HAVE_CHMOD */
134 }
135
136 /* Internal timeout callback to flush log channels and check file sizes */
137
138 SILC_TASK_CALLBACK(silc_log_fflush_callback)
139 {
140 #ifndef SILC_SYMBIAN
141   SilcLog log;
142
143   if (!silclog.quick) {
144     silc_log_flush_all();
145     log = silc_log_get_context(SILC_LOG_INFO);
146     silc_log_checksize(log);
147     log = silc_log_get_context(SILC_LOG_WARNING);
148     silc_log_checksize(log);
149     log = silc_log_get_context(SILC_LOG_ERROR);
150     silc_log_checksize(log);
151     log = silc_log_get_context(SILC_LOG_FATAL);
152     silc_log_checksize(log);
153   }
154
155   silclog.starting = FALSE;
156
157   if (silclog.flushdelay < 2)
158     silclog.flushdelay = 2;
159   silc_schedule_task_add_timeout(context, silc_log_fflush_callback, context,
160                                  silclog.flushdelay, 0);
161 #endif /* !SILC_SYMBIAN */
162 }
163
164 /* Output log message to log file */
165
166 void silc_log_output(SilcLogType type, char *string)
167 {
168   const char *typename = NULL;
169   SilcLog log = silc_log_get_context(type);
170   FILE *fp;
171
172   if (!log)
173     goto end;
174
175   /* Forward to callback if set */
176   if (log->cb)
177     if ((*log->cb)(type, string, log->context))
178       goto end;
179
180   typename = log->typename;
181
182 #ifndef SILC_SYMBIAN
183   if (!silclog.scheduled) {
184     if (silclog.no_init == FALSE) {
185       fprintf(stderr,
186               "Warning, trying to output without log files initialization, "
187               "log output is going to stderr\n");
188       silclog.no_init = TRUE;
189     }
190
191     fp = stderr;
192     log = NULL;
193     goto found;
194   }
195 #endif /* !SILC_SYMBIAN */
196
197   /* Find open log file */
198   while (log) {
199     if (log->fp) {
200       fp = log->fp;
201       break;
202     }
203
204     log = silc_log_get_context(--type);
205   }
206   if (!log || !log->fp)
207     goto end;
208
209 #ifndef SILC_SYMBIAN
210  found:
211   if (silclog.timestamp)
212     fprintf(fp, "[%s] [%s] %s\n", silc_time_string(0), typename, string);
213   else
214     fprintf(fp, "[%s] %s\n", typename, string);
215
216   if (silclog.quick || silclog.starting) {
217     fflush(fp);
218     if (log)
219       silc_log_checksize(log);
220   }
221 #endif /* !SILC_SYMBIAN */
222
223  end:
224 #ifndef SILC_SYMBIAN
225   /* Output log to stderr if debugging */
226   if (typename && silclog.debug) {
227     fprintf(stderr, "[Logging] [%s] %s\n", typename, string);
228     fflush(stderr);
229   }
230 #else
231   fprintf(stderr, "[Logging] [%s] %s\n", typename, string);
232 #endif /* !SILC_SYMBIAN */
233
234   silc_free(string);
235 }
236
237 /* Set and initialize the specified log file. */
238
239 SilcBool silc_log_set_file(SilcLogType type, char *filename,
240                            SilcUInt32 maxsize, SilcSchedule scheduler)
241 {
242 #ifndef SILC_SYMBIAN
243   FILE *fp = NULL;
244   SilcLog log;
245
246   log = silc_log_get_context(type);
247   if (!log)
248     return FALSE;
249
250   SILC_LOG_DEBUG(("Setting '%s' file to %s (max size=%d)",
251                   log->typename, filename, maxsize));
252
253   /* Open log file */
254   if (filename) {
255     fp = fopen(filename, "a+");
256     if (!fp) {
257       fprintf(stderr, "warning: couldn't open log file '%s': %s\n",
258               filename, strerror(errno));
259       return FALSE;
260     }
261 #ifdef HAVE_CHMOD
262     chmod(filename, 0600);
263 #endif /* HAVE_CHMOD */
264   }
265
266   /* Close previous log file if it exists */
267   if (strlen(log->filename)) {
268     if (log->fp)
269       fclose(log->fp);
270     memset(log->filename, 0, sizeof(log->filename));
271     log->fp = NULL;
272   }
273
274   /* Set new log file */
275   if (fp) {
276     log->fp = fp;
277     log->maxsize = maxsize;
278
279     memset(log->filename, 0, sizeof(log->filename));
280     silc_strncat(log->filename, sizeof(log->filename), filename,
281                  strlen(filename));
282   }
283
284   /* Add flush timeout */
285   if (scheduler) {
286     silc_schedule_task_del_by_callback(scheduler, silc_log_fflush_callback);
287     silc_schedule_task_add_timeout(scheduler, silc_log_fflush_callback,
288                                    scheduler, 10, 0);
289     silclog.scheduled = TRUE;
290   }
291
292 #endif /* !SILC_SYMBIAN */
293   return TRUE;
294 }
295
296 /* Return log filename */
297
298 char *silc_log_get_file(SilcLogType type)
299 {
300   SilcLog log = silc_log_get_context(type);
301   return log && log->fp ? log->filename : NULL;
302 }
303
304 /* Sets a log callback, set callback to NULL to return to default behaviour */
305
306 void silc_log_set_callback(SilcLogType type, SilcLogCb cb, void *context)
307 {
308 #ifndef SILC_SYMBIAN
309   SilcLog log = silc_log_get_context(type);
310   if (log) {
311     log->cb = cb;
312     log->context = context;
313   }
314 #endif /* !SILC_SYMBIAN */
315 }
316
317 /* Reset log callbacks */
318
319 void silc_log_reset_callbacks(void)
320 {
321 #ifndef SILC_SYMBIAN
322   SilcLog log;
323   log = silc_log_get_context(SILC_LOG_INFO);
324   log->cb = log->context = NULL;
325   log = silc_log_get_context(SILC_LOG_WARNING);
326   log->cb = log->context = NULL;
327   log = silc_log_get_context(SILC_LOG_ERROR);
328   log->cb = log->context = NULL;
329   log = silc_log_get_context(SILC_LOG_FATAL);
330   log->cb = log->context = NULL;
331 #endif /* !SILC_SYMBIAN */
332 }
333
334 /* Flush all log files */
335
336 void silc_log_flush_all(void)
337 {
338   SilcLog log;
339   log = silc_log_get_context(SILC_LOG_INFO);
340   if (log->fp)
341     fflush(log->fp);
342   log = silc_log_get_context(SILC_LOG_WARNING);
343   if (log->fp)
344     fflush(log->fp);
345   log = silc_log_get_context(SILC_LOG_ERROR);
346   if (log->fp)
347     fflush(log->fp);
348   log = silc_log_get_context(SILC_LOG_FATAL);
349   if (log->fp)
350     fflush(log->fp);
351 }
352
353 /* Reset a log file */
354
355 static void silc_log_reset(SilcLog log)
356 {
357   if (log->fp) {
358     fflush(log->fp);
359     fclose(log->fp);
360   }
361
362   if (!strlen(log->filename))
363     return;
364
365   log->fp = fopen(log->filename, "a+");
366   if (!log->fp)
367     SILC_LOG_WARNING(("Couldn't reset log file '%s' for type '%s': %s",
368                       log->filename, log->typename, strerror(errno)));
369 }
370
371 /* Reset all log files */
372
373 void silc_log_reset_all(void)
374 {
375   SilcLog log;
376   log = silc_log_get_context(SILC_LOG_INFO);
377   if (log->fp)
378     silc_log_reset(log);
379   log = silc_log_get_context(SILC_LOG_WARNING);
380   if (log->fp)
381     silc_log_reset(log);
382   log = silc_log_get_context(SILC_LOG_ERROR);
383   if (log->fp)
384     silc_log_reset(log);
385   log = silc_log_get_context(SILC_LOG_FATAL);
386   if (log->fp)
387     silc_log_reset(log);
388   silc_log_flush_all();
389 }
390
391 /* Sets debug callbacks */
392
393 void silc_log_set_debug_callbacks(SilcLogDebugCb debug_cb,
394                                   void *debug_context,
395                                   SilcLogHexdumpCb hexdump_cb,
396                                   void *hexdump_context)
397 {
398 #ifndef SILC_SYMBIAN
399   silclog.debug_cb = debug_cb;
400   silclog.debug_context = debug_context;
401   silclog.hexdump_cb = hexdump_cb;
402   silclog.hexdump_context = hexdump_context;
403 #endif /* !SILC_SYMBIAN */
404 }
405
406 /* Resets debug callbacks */
407
408 void silc_log_reset_debug_callbacks()
409 {
410 #ifndef SILC_SYMBIAN
411   silclog.debug_cb = NULL;
412   silclog.debug_context = NULL;
413   silclog.hexdump_cb = NULL;
414   silclog.hexdump_context = NULL;
415 #endif /* !SILC_SYMBIAN */
416 }
417
418 /* Set current debug string */
419
420 void silc_log_set_debug_string(const char *debug_string)
421 {
422 #ifndef SILC_SYMBIAN
423   char *string;
424   int len;
425   if ((strchr(debug_string, '(') && strchr(debug_string, ')')) ||
426       strchr(debug_string, '$'))
427     string = strdup(debug_string);
428   else
429     string = silc_string_regexify(debug_string);
430   len = strlen(string);
431   if (len >= sizeof(silclog.debug_string))
432     len = sizeof(silclog.debug_string) - 1;
433   memset(silclog.debug_string, 0, sizeof(silclog.debug_string));
434   strncpy(silclog.debug_string, string, len);
435   silc_free(string);
436 #endif /* !SILC_SYMBIAN */
437 }
438
439 /* Set timestamp */
440
441 void silc_log_timestamp(SilcBool enable)
442 {
443 #ifndef SILC_SYMBIAN
444   silclog.timestamp = enable;
445 #endif /* !SILC_SYMBIAN */
446 }
447
448 /* Set flushdelay */
449
450 void silc_log_flushdelay(SilcUInt32 flushdelay)
451 {
452 #ifndef SILC_SYMBIAN
453   silclog.flushdelay = flushdelay;
454 #endif /* !SILC_SYMBIAN */
455 }
456
457 /* Set quick logging */
458
459 void silc_log_quick(SilcBool enable)
460 {
461 #ifndef SILC_SYMBIAN
462   silclog.quick = enable;
463 #endif /* !SILC_SYMBIAN */
464 }
465
466 /* Set debugging */
467
468 void silc_log_debug(SilcBool enable)
469 {
470 #ifndef SILC_SYMBIAN
471   silclog.debug = enable;
472 #endif /* !SILC_SYMBIAN */
473 }
474
475 /* Set debug hexdump */
476
477 void silc_log_debug_hexdump(SilcBool enable)
478 {
479 #ifndef SILC_SYMBIAN
480   silclog.debug_hexdump = enable;
481 #endif /* !SILC_SYMBIAN */
482 }
483
484 /* Outputs the debug message to stderr. */
485
486 void silc_log_output_debug(char *file, const char *function,
487                            int line, char *string)
488 {
489   SilcTimeStruct curtime;
490
491 #ifndef SILC_SYMBIAN
492   if (!silclog.debug)
493     goto end;
494
495   if (!silc_string_regex_match(silclog.debug_string, file) &&
496       !silc_string_regex_match(silclog.debug_string, function))
497     goto end;
498
499   if (silclog.debug_cb) {
500     if ((*silclog.debug_cb)(file, (char *)function, line, string,
501                             silclog.debug_context))
502       goto end;
503   }
504 #endif /* !SILC_SYMBIAN */
505
506   silc_time_value(0, &curtime);
507
508 #ifdef SILC_WIN32
509   if (strrchr(function, '\\'))
510     fprintf(stderr, "%s:%d: %s\n", strrchr(function, '\\') + 1, line, string);
511   else
512 #endif /* SILC_WIN32 */
513 #ifdef SILC_SYMBIAN
514   silc_symbian_debug(function, line, string);
515 #else
516   fprintf(stderr, "%02d:%02d:%02d %s:%d: %s\n", curtime.hour,
517           curtime.minute, curtime.second, function, line,
518           string);
519   fflush(stderr);
520 #endif /* SILC_SYMBIAN */
521
522  end:
523   silc_free(string);
524 }
525
526 /* Hexdumps a message */
527
528 void silc_log_output_hexdump(char *file, const char *function,
529                              int line, void *data_in,
530                              SilcUInt32 len, char *string)
531 {
532   int i, k;
533   int off, pos, count;
534   unsigned char *data = (unsigned char *)data_in;
535
536 #ifndef SILC_SYMBIAN
537   if (!silclog.debug_hexdump)
538     goto end;
539
540   if (!silc_string_regex_match(silclog.debug_string, file) &&
541       !silc_string_regex_match(silclog.debug_string, function))
542     goto end;
543
544   if (silclog.hexdump_cb) {
545     if ((*silclog.hexdump_cb)(file, (char *)function, line,
546                               data_in, len, string, silclog.hexdump_context))
547       goto end;
548   }
549 #endif /* !SILC_SYMBIAN */
550
551   fprintf(stderr, "%s:%d: %s\n", function, line, string);
552
553   k = 0;
554   pos = 0;
555   count = 16;
556   off = len % 16;
557   while (1) {
558     if (off) {
559       if ((len - pos) < 16 && (len - pos <= len - off))
560         count = off;
561     } else {
562       if (pos == len)
563         count = 0;
564     }
565     if (off == len)
566       count = len;
567
568     if (count)
569       fprintf(stderr, "%08X  ", k++ * 16);
570
571     for (i = 0; i < count; i++) {
572       fprintf(stderr, "%02X ", data[pos + i]);
573
574       if ((i + 1) % 4 == 0)
575         fprintf(stderr, " ");
576     }
577
578     if (count && count < 16) {
579       int j;
580
581       for (j = 0; j < 16 - count; j++) {
582         fprintf(stderr, "   ");
583
584         if ((j + count + 1) % 4 == 0)
585           fprintf(stderr, " ");
586       }
587     }
588
589     for (i = 0; i < count; i++) {
590       char ch;
591
592       if (data[pos] < 32 || data[pos] >= 127)
593         ch = '.';
594       else
595         ch = data[pos];
596
597       fprintf(stderr, "%c", ch);
598       pos++;
599     }
600
601     if (count)
602       fprintf(stderr, "\n");
603
604     if (count < 16)
605       break;
606   }
607
608  end:
609   silc_free(string);
610 }