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