Added SILC Thread Queue API
[crypto.git] / apps / irssi / src / core / log.c
1 /*
2  log.c : irssi
3
4     Copyright (C) 1999-2000 Timo Sirainen
5
6     This program is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     This program is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with this program; if not, write to the Free Software
18     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19 */
20
21 #include "module.h"
22 #include "signals.h"
23 #include "commands.h"
24 #include "levels.h"
25 #include "misc.h"
26 #include "servers.h"
27 #include "log.h"
28 #include "write-buffer.h"
29
30 #include "lib-config/iconfig.h"
31 #include "settings.h"
32
33 #define DEFAULT_LOG_FILE_CREATE_MODE 600
34
35 #ifdef HAVE_FCNTL
36 static struct flock lock;
37 #endif
38
39 GSList *logs;
40
41 static const char *log_item_types[] = {
42         "target",
43         "window",
44
45         NULL
46 };
47
48 const char *log_timestamp;
49 static int log_file_create_mode;
50 static int log_dir_create_mode;
51 static int rotate_tag;
52
53 static int log_item_str2type(const char *type)
54 {
55         int n;
56
57         for (n = 0; log_item_types[n] != NULL; n++) {
58                 if (g_strcasecmp(log_item_types[n], type) == 0)
59                         return n;
60         }
61
62         return -1;
63 }
64
65 static void log_write_timestamp(int handle, const char *format,
66                                 const char *text, time_t stamp)
67 {
68         struct tm *tm;
69         char str[256];
70
71         g_return_if_fail(format != NULL);
72         if (*format == '\0') return;
73
74         tm = localtime(&stamp);
75         if (strftime(str, sizeof(str), format, tm) > 0)
76                 write_buffer(handle, str, strlen(str));
77         if (text != NULL) write_buffer(handle, text, strlen(text));
78 }
79
80 static char *log_filename(LOG_REC *log)
81 {
82         char *str, fname[1024];
83         struct tm *tm;
84         size_t ret;
85         time_t now;
86
87         now = time(NULL);
88         tm = localtime(&now);
89
90         str = convert_home(log->fname);
91         ret = strftime(fname, sizeof(fname), str, tm);
92         g_free(str);
93
94         if (ret <= 0) {
95                 g_warning("log_filename() : strftime() failed");
96                 return NULL;
97         }
98
99         return g_strdup(fname);
100 }
101
102 int log_start_logging(LOG_REC *log)
103 {
104         char *dir;
105
106         g_return_val_if_fail(log != NULL, FALSE);
107
108         if (log->handle != -1)
109                 return TRUE;
110
111         /* Append/create log file */
112         g_free_not_null(log->real_fname);
113         log->real_fname = log_filename(log);
114
115         if (log->real_fname != NULL &&
116             strcmp(log->real_fname, log->fname) != 0) {
117                 /* path may contain variables (%time, $vars),
118                    make sure the directory is created */
119                 dir = g_dirname(log->real_fname);
120                 mkpath(dir, log_dir_create_mode);
121                 g_free(dir);
122         }
123
124         log->handle = log->real_fname == NULL ? -1 :
125                 open(log->real_fname, O_WRONLY | O_APPEND | O_CREAT,
126                      log_file_create_mode);
127         if (log->handle == -1) {
128                 signal_emit("log create failed", 1, log);
129                 log->failed = TRUE;
130                 return FALSE;
131         }
132 #ifdef HAVE_FCNTL
133         memset(&lock, 0, sizeof(lock));
134         lock.l_type = F_WRLCK;
135         if (fcntl(log->handle, F_SETLK, &lock) == -1 && errno == EACCES) {
136                 close(log->handle);
137                 log->handle = -1;
138                 signal_emit("log locked", 1, log);
139                 log->failed = TRUE;
140                 return FALSE;
141         }
142 #endif
143         lseek(log->handle, 0, SEEK_END);
144
145         log->opened = log->last = time(NULL);
146         log_write_timestamp(log->handle,
147                             settings_get_str("log_open_string"),
148                             "\n", log->last);
149
150         signal_emit("log started", 1, log);
151         log->failed = FALSE;
152         return TRUE;
153 }
154
155 void log_stop_logging(LOG_REC *log)
156 {
157         g_return_if_fail(log != NULL);
158
159         if (log->handle == -1)
160                 return;
161
162         signal_emit("log stopped", 1, log);
163
164         log_write_timestamp(log->handle,
165                             settings_get_str("log_close_string"),
166                             "\n", time(NULL));
167
168 #ifdef HAVE_FCNTL
169         memset(&lock, 0, sizeof(lock));
170         lock.l_type = F_UNLCK;
171         fcntl(log->handle, F_SETLK, &lock);
172 #endif
173
174         write_buffer_flush();
175         close(log->handle);
176         log->handle = -1;
177 }
178
179 static void log_rotate_check(LOG_REC *log)
180 {
181         char *new_fname;
182
183         g_return_if_fail(log != NULL);
184
185         if (log->handle == -1 || log->real_fname == NULL)
186                 return;
187
188         new_fname = log_filename(log);
189         if (strcmp(new_fname, log->real_fname) != 0) {
190                 /* rotate log */
191                 log_stop_logging(log);
192                 signal_emit("log rotated", 1, log);
193
194                 log_start_logging(log);
195         }
196         g_free(new_fname);
197 }
198
199 void log_write_rec(LOG_REC *log, const char *str, int level)
200 {
201         char *colorstr;
202         struct tm *tm;
203         time_t now;
204         int hour, day;
205
206         g_return_if_fail(log != NULL);
207         g_return_if_fail(str != NULL);
208
209         if (log->handle == -1)
210                 return;
211
212         now = time(NULL);
213         tm = localtime(&now);
214         hour = tm->tm_hour;
215         day = tm->tm_mday;
216
217         tm = localtime(&log->last);
218         day -= tm->tm_mday; /* tm breaks in log_rotate_check() .. */
219         if (tm->tm_hour != hour) {
220                 /* hour changed, check if we need to rotate log file */
221                 log_rotate_check(log);
222         }
223
224         if (day != 0) {
225                 /* day changed */
226                 log_write_timestamp(log->handle,
227                                     settings_get_str("log_day_changed"),
228                                     "\n", now);
229         }
230
231         log->last = now;
232
233         if (log->colorizer == NULL)
234                 colorstr = NULL;
235         else
236                 str = colorstr = log->colorizer(str);
237
238         if ((level & MSGLEVEL_LASTLOG) == 0)
239                 log_write_timestamp(log->handle, log_timestamp, str, now);
240         else
241                 write_buffer(log->handle, str, strlen(str));
242         write_buffer(log->handle, "\n", 1);
243
244         signal_emit("log written", 2, log, str);
245
246         g_free_not_null(colorstr);
247 }
248
249 static int itemcmp(const char *patt, const char *item)
250 {
251         /* returns 0 on match, nonzero otherwise */
252
253         if (item == NULL)
254                 return g_strcasecmp(patt, "*") != 0;
255         if (*patt == '*')
256                 return 0;
257         return g_strcasecmp(patt, item);
258 }
259
260 LOG_ITEM_REC *log_item_find(LOG_REC *log, int type, const char *item,
261                             const char *servertag)
262 {
263         GSList *tmp;
264
265         g_return_val_if_fail(log != NULL, NULL);
266
267         for (tmp = log->items; tmp != NULL; tmp = tmp->next) {
268                 LOG_ITEM_REC *rec = tmp->data;
269
270                 if (rec->type == type && itemcmp(rec->name, item) == 0 &&
271                     (rec->servertag == NULL || (servertag != NULL &&
272                         g_strcasecmp(rec->servertag, servertag) == 0)))
273                         return rec;
274         }
275
276         return NULL;
277 }
278
279 void log_file_write(const char *server_tag, const char *item, int level,
280                     const char *str, int no_fallbacks)
281 {
282         GSList *tmp, *fallbacks;
283         char *tmpstr;
284         int found;
285
286         g_return_if_fail(str != NULL);
287
288         if (logs == NULL)
289                 return;
290
291         fallbacks = NULL; found = FALSE;
292
293         for (tmp = logs; tmp != NULL; tmp = tmp->next) {
294                 LOG_REC *rec = tmp->data;
295
296                 if (rec->handle == -1)
297                         continue; /* log not opened yet */
298
299                 if ((level & rec->level) == 0)
300                         continue;
301
302                 if (rec->items == NULL)
303                         fallbacks = g_slist_append(fallbacks, rec);
304                 else if (item != NULL &&
305                          log_item_find(rec, LOG_ITEM_TARGET, item,
306                                        server_tag) != NULL)
307                         log_write_rec(rec, str, level);
308         }
309
310         if (!found && !no_fallbacks && fallbacks != NULL) {
311                 /* not found from any items, so write it to all main logs */
312                 tmpstr = (level & MSGLEVEL_PUBLIC) && item != NULL ?
313                         g_strconcat(item, ": ", str, NULL) :
314                         g_strdup(str);
315
316                 for (tmp = fallbacks; tmp != NULL; tmp = tmp->next)
317                         log_write_rec(tmp->data, tmpstr, level);
318
319                 g_free(tmpstr);
320         }
321         g_slist_free(fallbacks);
322 }
323
324 LOG_REC *log_find(const char *fname)
325 {
326         GSList *tmp;
327
328         for (tmp = logs; tmp != NULL; tmp = tmp->next) {
329                 LOG_REC *rec = tmp->data;
330
331                 if (strcmp(rec->fname, fname) == 0)
332                         return rec;
333         }
334
335         return NULL;
336 }
337
338 static void log_items_update_config(LOG_REC *log, CONFIG_NODE *parent)
339 {
340         GSList *tmp;
341         CONFIG_NODE *node;
342
343         parent = config_node_section(parent, "items", NODE_TYPE_LIST);
344         for (tmp = log->items; tmp != NULL; tmp = tmp->next) {
345                 LOG_ITEM_REC *rec = tmp->data;
346
347                 node = config_node_section(parent, NULL, NODE_TYPE_BLOCK);
348                 iconfig_node_set_str(node, "type", log_item_types[rec->type]);
349                 iconfig_node_set_str(node, "name", rec->name);
350                 iconfig_node_set_str(node, "server", rec->servertag);
351         }
352 }
353
354 static void log_update_config(LOG_REC *log)
355 {
356         CONFIG_NODE *node;
357         char *levelstr;
358
359         if (log->temp)
360                 return;
361
362         node = iconfig_node_traverse("logs", TRUE);
363         node = config_node_section(node, log->fname, NODE_TYPE_BLOCK);
364
365         if (log->autoopen)
366                 iconfig_node_set_bool(node, "auto_open", TRUE);
367         else
368                 iconfig_node_set_str(node, "auto_open", NULL);
369
370         levelstr = bits2level(log->level);
371         iconfig_node_set_str(node, "level", levelstr);
372         g_free(levelstr);
373
374         iconfig_node_set_str(node, "items", NULL);
375
376         if (log->items != NULL)
377                 log_items_update_config(log, node);
378
379         signal_emit("log config save", 2, log, node);
380 }
381
382 static void log_remove_config(LOG_REC *log)
383 {
384         iconfig_set_str("logs", log->fname, NULL);
385 }
386
387 LOG_REC *log_create_rec(const char *fname, int level)
388 {
389         LOG_REC *rec;
390
391         g_return_val_if_fail(fname != NULL, NULL);
392
393         rec = log_find(fname);
394         if (rec == NULL) {
395                 rec = g_new0(LOG_REC, 1);
396                 rec->fname = g_strdup(fname);
397                 rec->real_fname = log_filename(rec);
398                 rec->handle = -1;
399         }
400
401         rec->level = level;
402         return rec;
403 }
404
405 void log_item_add(LOG_REC *log, int type, const char *name,
406                   const char *servertag)
407 {
408         LOG_ITEM_REC *rec;
409
410         g_return_if_fail(log != NULL);
411         g_return_if_fail(name != NULL);
412
413         if (log_item_find(log, type, name, servertag))
414                 return;
415
416         rec = g_new0(LOG_ITEM_REC, 1);
417         rec->type = type;
418         rec->name = g_strdup(name);
419         rec->servertag = g_strdup(servertag);
420
421         log->items = g_slist_append(log->items, rec);
422 }
423
424 void log_update(LOG_REC *log)
425 {
426         g_return_if_fail(log != NULL);
427
428         if (log_find(log->fname) == NULL) {
429                 logs = g_slist_append(logs, log);
430                 log->handle = -1;
431         }
432
433         log_update_config(log);
434         signal_emit("log new", 1, log);
435 }
436
437 void log_item_destroy(LOG_REC *log, LOG_ITEM_REC *item)
438 {
439         log->items = g_slist_remove(log->items, item);
440
441         g_free(item->name);
442         g_free_not_null(item->servertag);
443         g_free(item);
444 }
445
446 static void log_destroy(LOG_REC *log)
447 {
448         g_return_if_fail(log != NULL);
449
450         if (log->handle != -1)
451                 log_stop_logging(log);
452
453         logs = g_slist_remove(logs, log);
454         signal_emit("log remove", 1, log);
455
456         while (log->items != NULL)
457                 log_item_destroy(log, log->items->data);
458         g_free(log->fname);
459         g_free_not_null(log->real_fname);
460         g_free(log);
461 }
462
463 void log_close(LOG_REC *log)
464 {
465         g_return_if_fail(log != NULL);
466
467         log_remove_config(log);
468         log_destroy(log);
469 }
470
471 static int sig_rotate_check(void)
472 {
473         static int last_hour = -1;
474         struct tm tm;
475         time_t now;
476
477         /* don't do anything until hour is changed */
478         now = time(NULL);
479         memcpy(&tm, localtime(&now), sizeof(tm));
480         if (tm.tm_hour != last_hour) {
481                 last_hour = tm.tm_hour;
482                 g_slist_foreach(logs, (GFunc) log_rotate_check, NULL);
483         }
484         return 1;
485 }
486
487 static void log_items_read_config(CONFIG_NODE *node, LOG_REC *log)
488 {
489         LOG_ITEM_REC *rec;
490         GSList *tmp;
491         char *item;
492         int type;
493
494         tmp = config_node_first(node->value);
495         for (; tmp != NULL; tmp = config_node_next(tmp)) {
496                 node = tmp->data;
497
498                 if (node->type != NODE_TYPE_BLOCK)
499                         continue;
500
501                 item = config_node_get_str(node, "name", NULL);
502                 type = log_item_str2type(config_node_get_str(node, "type", NULL));
503                 if (item == NULL || type == -1)
504                         continue;
505
506                 rec = g_new0(LOG_ITEM_REC, 1);
507                 rec->type = type;
508                 rec->name = g_strdup(item);
509                 rec->servertag = g_strdup(config_node_get_str(node, "server", NULL));
510
511                 log->items = g_slist_append(log->items, rec);
512         }
513 }
514
515 static void log_read_config(void)
516 {
517         CONFIG_NODE *node;
518         LOG_REC *log;
519         GSList *tmp, *next, *fnames;
520
521         /* close old logs, save list of open logs */
522         fnames = NULL;
523         for (tmp = logs; tmp != NULL; tmp = next) {
524                 log = tmp->data;
525
526                 next = tmp->next;
527                 if (log->temp)
528                         continue;
529
530                 if (log->handle != -1)
531                         fnames = g_slist_append(fnames, g_strdup(log->fname));
532                 log_destroy(log);
533         }
534
535         node = iconfig_node_traverse("logs", FALSE);
536         if (node == NULL) return;
537
538         tmp = config_node_first(node->value);
539         for (; tmp != NULL; tmp = config_node_next(tmp)) {
540                 node = tmp->data;
541
542                 if (node->type != NODE_TYPE_BLOCK)
543                         continue;
544
545                 log = g_new0(LOG_REC, 1);
546                 logs = g_slist_append(logs, log);
547
548                 log->handle = -1;
549                 log->fname = g_strdup(node->key);
550                 log->autoopen = config_node_get_bool(node, "auto_open", FALSE);
551                 log->level = level2bits(config_node_get_str(node, "level", 0));
552
553                 signal_emit("log config read", 2, log, node);
554
555                 node = config_node_section(node, "items", -1);
556                 if (node != NULL)
557                         log_items_read_config(node, log);
558
559                 if (log->autoopen || gslist_find_string(fnames, log->fname))
560                         log_start_logging(log);
561         }
562
563         g_slist_foreach(fnames, (GFunc) g_free, NULL);
564         g_slist_free(fnames);
565 }
566
567 static void read_settings(void)
568 {
569         log_timestamp = settings_get_str("log_timestamp");
570         log_file_create_mode = octal2dec(settings_get_int("log_create_mode"));
571
572         log_dir_create_mode = log_file_create_mode;
573         if (log_file_create_mode & 0400) log_dir_create_mode |= 0100;
574         if (log_file_create_mode & 0040) log_dir_create_mode |= 0010;
575         if (log_file_create_mode & 0004) log_dir_create_mode |= 0001;
576 }
577
578 void log_init(void)
579 {
580         rotate_tag = g_timeout_add(60000, (GSourceFunc) sig_rotate_check, NULL);
581         logs = NULL;
582
583         settings_add_int("log", "log_create_mode",
584                          DEFAULT_LOG_FILE_CREATE_MODE);
585         settings_add_str("log", "log_timestamp", "%H:%M ");
586         settings_add_str("log", "log_open_string",
587                          "--- Log opened %a %b %d %H:%M:%S %Y");
588         settings_add_str("log", "log_close_string",
589                          "--- Log closed %a %b %d %H:%M:%S %Y");
590         settings_add_str("log", "log_day_changed",
591                          "--- Day changed %a %b %d %Y");
592
593         read_settings();
594         signal_add("setup changed", (SIGNAL_FUNC) read_settings);
595         signal_add("setup reread", (SIGNAL_FUNC) log_read_config);
596         signal_add("irssi init finished", (SIGNAL_FUNC) log_read_config);
597 }
598
599 void log_deinit(void)
600 {
601         g_source_remove(rotate_tag);
602
603         while (logs != NULL)
604                 log_close(logs->data);
605
606         signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
607         signal_remove("setup reread", (SIGNAL_FUNC) log_read_config);
608         signal_remove("irssi init finished", (SIGNAL_FUNC) log_read_config);
609 }