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