updates.
[silc.git] / lib / silcutil / silclog.c
1 /*
2
3   silclog.c
4
5   Author: Johnny Mnemonic <johnny@themnemonic.org>
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 /* default flush time (5 minutes) */
25 #define SILC_LOG_TIMEOUT 300
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;
34   FILE *fp;
35   uint32 maxsize;
36   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 */
44 static struct SilcLogStruct silclogs[SILC_LOG_MAX] = {
45   {NULL, NULL, 0, "Info", SILC_LOG_INFO, NULL, NULL},
46   {NULL, NULL, 0, "Error", SILC_LOG_ERROR, NULL, NULL},
47   {NULL, NULL, 0, "Warning", SILC_LOG_WARNING, NULL, NULL},
48   {NULL, NULL, 0, "Fatal", SILC_LOG_FATAL, NULL, NULL},
49 };
50
51 /* If TRUE, log files will be flushed for each log input */
52 bool silc_log_quick = FALSE;
53
54 /* Set TRUE/FALSE to enable/disable debugging */
55 bool silc_debug = FALSE;
56 bool silc_debug_hexdump = FALSE;
57
58 /* Regular pattern matching expression for the debug output */
59 static char *silc_log_debug_string = NULL;
60
61 /* Debug callbacks. If set these are used instead of default ones. */
62 static SilcLogDebugCb silc_log_debug_cb = NULL;
63 static void *silc_log_debug_context = NULL;
64 static SilcLogHexdumpCb silc_log_hexdump_cb = NULL;
65 static void *silc_log_hexdump_context = NULL;
66
67 /* Did we register already our functions to the scheduler? */
68 static bool silc_log_scheduled = FALSE;
69 static bool silc_log_no_init = FALSE;
70
71 /* The type wrapper utility. Translates a SilcLogType id to the corresponding
72  * logfile, or NULL if not found. */
73 static SilcLog silc_log_find_by_type(SilcLogType type)
74 {
75   /* this is not really needed, but i think it's more secure */
76   switch (type) {
77     case SILC_LOG_INFO:
78       return &silclogs[SILC_LOG_INFO];
79     case SILC_LOG_WARNING:
80       return &silclogs[SILC_LOG_WARNING];
81     case SILC_LOG_ERROR:
82       return &silclogs[SILC_LOG_ERROR];
83     case SILC_LOG_FATAL:
84       return &silclogs[SILC_LOG_FATAL];
85     default:
86       return NULL;
87   }
88   return NULL;
89 }
90
91 /* Given an open log file, checks the size and rotates it if there is a
92  * max size set less then the current size */
93 static void silc_log_checksize(SilcLog log)
94 {
95   char newname[127];
96   long size;
97
98   if (!log || !log->fp || !log->maxsize)
99     return; /* we are not interested */
100   if ((size = ftell(log->fp)) < 0) {
101     /* OMG, EBADF is here.. we'll try our best.. */
102     FILE *oldfp = log->fp;
103     fclose(oldfp); /* we can discard the error */
104     log->fp = NULL; /* make sure we don't get here recursively */
105     SILC_LOG_ERROR(("Error while checking size of the log file %s, fp=%d",
106                     log->filename, oldfp));
107     return;
108   }
109   if (size < log->maxsize) return;
110
111   /* It's too big */
112   fprintf(log->fp, "[%s] [%s] Cycling log file, over max "
113           "logsize (%lu kilobytes)\n",
114           silc_get_time(), log->typename, log->maxsize / 1024);
115   fflush(log->fp);
116   fclose(log->fp);
117   snprintf(newname, sizeof(newname), "%s.old", log->filename);
118   unlink(newname);
119
120   /* I heard the following syscall may cause portability issues, but I don't
121    * have any other solution since SILC library doesn't provide any other
122    * function like this. -Johnny */
123   rename(log->filename, newname);
124   if (!(log->fp = fopen(log->filename, "w")))
125     SILC_LOG_WARNING(("Couldn't reopen logfile %s for type \"%s\": %s",
126                       log->filename, log->typename, strerror(errno)));
127 }
128
129 /* Reset a logging channel (close and reopen) */
130
131 static bool silc_log_reset(SilcLog log)
132 {
133   if (!log) return FALSE;
134   if (log->fp) {
135     fflush(log->fp);
136     fclose(log->fp);
137   }
138   if (!(log->fp = fopen(log->filename, "a+"))) {
139     SILC_LOG_WARNING(("Couldn't reset logfile %s for type \"%s\": %s",
140         log->filename, log->typename, strerror(errno)));
141     return FALSE;
142   }
143   return TRUE;
144 }
145
146 /* Internal timeout callback to flush log channels and check file sizes */
147
148 SILC_TASK_CALLBACK(silc_log_fflush_callback)
149 {
150   unsigned int u;
151   if (!silc_log_quick) {
152     silc_log_flush_all();
153     SILC_FOREACH_LOG(u)
154       silc_log_checksize(&silclogs[u]);
155   }
156   silc_schedule_task_add((SilcSchedule) context, 0, silc_log_fflush_callback,
157                          context, SILC_LOG_TIMEOUT, 0, SILC_TASK_TIMEOUT,
158                          SILC_TASK_PRI_NORMAL);
159 }
160
161 /* Outputs the log message to the first available channel. Channels are
162  * ordered by importance (see SilcLogType documentation).
163  * More importants channels can be printed on less important ones, but not
164  * vice-versa. */
165
166 void silc_log_output(SilcLogType type, char *string)
167 {
168   char *typename;
169   SilcLog log;
170
171   if ((type > SILC_LOG_MAX) || !(log = silc_log_find_by_type(type)))
172     goto end;
173
174   /* If there is a custom callback set, use it and return. */
175   if (log->cb) {
176     if ((*log->cb)(type, string, log->context))
177       goto end;
178   }
179
180   if (!silc_log_scheduled) {
181     if (silc_log_no_init == FALSE) {
182       fprintf(stderr, 
183               "Warning, trying to output without log files initialization, "
184               "log output is going to stderr\n");
185       silc_log_no_init = TRUE;
186     }
187
188     fprintf(stderr, "%s\n", string);
189     goto end;
190   }
191
192   /* save the original typename, because if we redirect the channel we
193    * keep however the original destination channel name */
194   typename = log->typename;
195
196   /* ok, now we have to find an open stream */
197   while (TRUE) {
198     if (log && log->fp) goto found;
199     if (type == 0) break;
200     log = silc_log_find_by_type(--type);
201   }
202
203   /* Couldn't find any open stream.. sorry :( */
204   SILC_LOG_DEBUG(("Warning! couldn't find any available log channel!"));
205   goto end;
206
207  found:
208   fprintf(log->fp, "[%s] [%s] %s\n", silc_get_time(), typename, string);
209   if (silc_log_quick) {
210     fflush(log->fp);
211     silc_log_checksize(log);
212   }
213
214  end:
215   silc_free(string);
216 }
217
218 /* returns an internally allocated pointer to a logging channel file */
219 char *silc_log_get_file(SilcLogType type)
220 {
221   SilcLog log;
222
223   if (!(log = silc_log_find_by_type(type)))
224     return NULL;
225   if (log->fp)
226     return log->filename;
227   return NULL;
228 }
229
230 /* Set and initialize the specified logging channel. See the API reference */
231 bool silc_log_set_file(SilcLogType type, char *filename, uint32 maxsize,
232                        SilcSchedule scheduler)
233 {
234   FILE *fp = NULL;
235   SilcLog log;
236
237   log = silc_log_find_by_type(type);
238   if (!log || !scheduler)
239     return FALSE;
240
241   SILC_LOG_DEBUG(("Setting \"%s\" file to %s (max size=%d)",
242                   log->typename, filename, maxsize));
243
244   /* before assuming the new file, make sure we can open it */
245   if (filename) {
246     if (!(fp = fopen(filename, "a+"))) {
247       fprintf(stderr, "warning: couldn't open log file %s: %s\n",
248               filename, strerror(errno));
249       return FALSE;
250     }
251   }
252
253   /* remove old file */
254   if (log->filename) {
255     if (log->fp) {
256       fflush(fp);
257       fclose(fp);
258     }
259     silc_free(log->filename);
260     log->filename = NULL;
261     log->fp = NULL;
262   }
263
264   if (fp) {
265     log->filename = strdup(filename);
266     log->fp = fp;
267     log->maxsize = maxsize;
268   }
269
270   if (silc_log_scheduled)
271     return TRUE;
272
273   /* make sure we write to the disk sometimes */
274   silc_schedule_task_add(scheduler, 0, silc_log_fflush_callback,
275                          (void *) scheduler, SILC_LOG_TIMEOUT, 0,
276                          SILC_TASK_TIMEOUT, SILC_TASK_PRI_NORMAL);
277
278   silc_log_scheduled = TRUE;
279
280   return TRUE;
281 }
282
283 /* Sets a log callback, set callback to NULL to return to default behaviour */
284
285 void silc_log_set_callback(SilcLogType type, SilcLogCb cb, void *context)
286 {
287   SilcLog log;
288
289   if (!(log = silc_log_find_by_type(type)))
290     return;
291
292   log->cb = cb;
293   log->context = context;
294 }
295
296 /* Resets log callbacks */
297
298 void silc_log_reset_callbacks()
299 {
300   unsigned int u;
301   SILC_FOREACH_LOG(u) {
302     silclogs[u].cb = NULL;
303     silclogs[u].context = NULL;
304   }
305 }
306
307 /* Flushes all opened logging channels */
308
309 void silc_log_flush_all() {
310   unsigned int u;
311   SILC_LOG_DEBUG(("Flushing all logs"));
312   SILC_FOREACH_LOG(u) {
313     if (silclogs[u].fp)
314       fflush(silclogs[u].fp);
315   }
316 }
317
318 /* Resets all known logging channels */
319
320 void silc_log_reset_all() {
321   unsigned int u;
322   SILC_LOG_DEBUG(("Resetting all logs"));
323   SILC_FOREACH_LOG(u)
324     silc_log_reset(&silclogs[u]);
325 }
326
327 /* Outputs the debug message to stderr. */
328
329 void silc_log_output_debug(char *file, char *function,
330                            int line, char *string)
331 {
332   if (!silc_debug)
333     goto end;
334   if (silc_log_debug_string &&
335         !silc_string_regex_match(silc_log_debug_string, file) &&
336         !silc_string_regex_match(silc_log_debug_string, function))
337     goto end;
338   if (silc_log_debug_cb) {
339     if ((*silc_log_debug_cb)(file, function, line, string,
340                              silc_log_debug_context))
341       goto end;
342   }
343   fprintf(stderr, "%s:%d: %s\n", function, line, string);
344   fflush(stderr);
345 end:
346   silc_free(string);
347 }
348
349 /* Hexdumps a message */
350
351 void silc_log_output_hexdump(char *file, char *function,
352                              int line, void *data_in,
353                              uint32 len, char *string)
354 {
355   int i, k;
356   int off, pos, count;
357   unsigned char *data = (unsigned char *)data_in;
358
359   if (!silc_debug_hexdump)
360     goto end;
361   if (silc_log_debug_string &&
362         !silc_string_regex_match(silc_log_debug_string, file) &&
363         !silc_string_regex_match(silc_log_debug_string, function))
364     goto end;
365   if (silc_log_hexdump_cb) {
366     if ((*silc_log_hexdump_cb)(file, function, line, data_in, len, string,
367                                silc_log_hexdump_context))
368       goto end;
369   }
370
371   fprintf(stderr, "%s:%d: %s\n", function, line, string);
372   silc_free(string);
373
374   k = 0;
375   pos = 0;
376   count = 16;
377   off = len % 16;
378   while (1) {
379     if (off) {
380       if ((len - pos) < 16 && (len - pos <= len - off))
381         count = off;
382     } else {
383       if (pos == len)
384         count = 0;
385     }
386     if (off == len)
387       count = len;
388
389     if (count)
390       fprintf(stderr, "%08X  ", k++ * 16);
391
392     for (i = 0; i < count; i++) {
393       fprintf(stderr, "%02X ", data[pos + i]);
394
395       if ((i + 1) % 4 == 0)
396         fprintf(stderr, " ");
397     }
398
399     if (count && count < 16) {
400       int j;
401
402       for (j = 0; j < 16 - count; j++) {
403         fprintf(stderr, "   ");
404
405         if ((j + count + 1) % 4 == 0)
406           fprintf(stderr, " ");
407       }
408     }
409
410     for (i = 0; i < count; i++) {
411       char ch;
412
413       if (data[pos] < 32 || data[pos] >= 127)
414         ch = '.';
415       else
416         ch = data[pos];
417
418       fprintf(stderr, "%c", ch);
419       pos++;
420     }
421
422     if (count)
423       fprintf(stderr, "\n");
424
425     if (count < 16)
426       break;
427   }
428   return;
429
430 end:
431   silc_free(string);
432 }
433
434 /* Sets debug callbacks */
435
436 void silc_log_set_debug_callbacks(SilcLogDebugCb debug_cb,
437                                   void *debug_context,
438                                   SilcLogHexdumpCb hexdump_cb,
439                                   void *hexdump_context)
440 {
441   silc_log_debug_cb = debug_cb;
442   silc_log_debug_context = debug_context;
443   silc_log_hexdump_cb = hexdump_cb;
444   silc_log_hexdump_context = hexdump_context;
445 }
446
447 /* Resets debug callbacks */
448
449 void silc_log_reset_debug_callbacks()
450 {
451   silc_log_debug_cb = NULL;
452   silc_log_debug_context = NULL;
453   silc_log_hexdump_cb = NULL;
454   silc_log_hexdump_context = NULL;
455 }
456
457 /* Set current debug string */
458
459 void silc_log_set_debug_string(const char *debug_string)
460 {
461   silc_free(silc_log_debug_string);
462   if ((strchr(debug_string, '(') &&
463         strchr(debug_string, ')')) ||
464         strchr(debug_string, '$'))
465     silc_log_debug_string = strdup(debug_string);
466   else
467     silc_log_debug_string = silc_string_regexify(debug_string);
468 }