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