diff options
Diffstat (limited to 'src/core/log.c')
-rw-r--r-- | src/core/log.c | 617 |
1 files changed, 617 insertions, 0 deletions
diff --git a/src/core/log.c b/src/core/log.c new file mode 100644 index 0000000..5bc953a --- /dev/null +++ b/src/core/log.c @@ -0,0 +1,617 @@ +/* + log.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "module.h" +#include <irssi/src/core/signals.h> +#include <irssi/src/core/commands.h> +#include <irssi/src/core/levels.h> +#include <irssi/src/core/misc.h> +#include <irssi/src/core/servers.h> +#include <irssi/src/core/log.h> +#include <irssi/src/core/write-buffer.h> +#ifdef HAVE_CAPSICUM +#include <irssi/src/core/capsicum.h> +#endif + +#include <irssi/src/lib-config/iconfig.h> +#include <irssi/src/core/settings.h> + +#define DEFAULT_LOG_FILE_CREATE_MODE 600 + +GSList *logs; +int log_file_create_mode; +int log_dir_create_mode; + +static const char *log_item_types[] = { + "target", + "window", + + NULL +}; + +static char *log_timestamp; +static int rotate_tag; + +static int log_item_str2type(const char *type) +{ + int n; + + for (n = 0; log_item_types[n] != NULL; n++) { + if (g_ascii_strcasecmp(log_item_types[n], type) == 0) + return n; + } + + return -1; +} + +static void log_write_timestamp(int handle, const char *format, + const char *text, time_t stamp) +{ + struct tm *tm; + char str[256]; + + g_return_if_fail(format != NULL); + if (*format == '\0') return; + + tm = localtime(&stamp); + if (strftime(str, sizeof(str), format, tm) > 0) + write_buffer(handle, str, strlen(str)); + if (text != NULL) write_buffer(handle, text, strlen(text)); +} + +static char *log_filename(LOG_REC *log) +{ + char *str, fname[1024]; + struct tm *tm; + size_t ret; + time_t now; + + now = time(NULL); + tm = localtime(&now); + + str = convert_home(log->fname); + ret = strftime(fname, sizeof(fname), str, tm); + g_free(str); + + if (ret <= 0) { + g_warning("log_filename() : strftime() failed"); + return NULL; + } + + return g_strdup(fname); +} + +int log_start_logging(LOG_REC *log) +{ + char *dir; + struct flock lock; + + g_return_val_if_fail(log != NULL, FALSE); + + if (log->handle != -1) + return TRUE; + + /* Append/create log file */ + g_free_not_null(log->real_fname); + log->real_fname = log_filename(log); + + if (log->real_fname != NULL && + g_strcmp0(log->real_fname, log->fname) != 0) { + /* path may contain variables (%time, $vars), + make sure the directory is created */ + dir = g_path_get_dirname(log->real_fname); +#ifdef HAVE_CAPSICUM + capsicum_mkdir_with_parents_wrapper(dir, log_dir_create_mode); +#else + g_mkdir_with_parents(dir, log_dir_create_mode); +#endif + g_free(dir); + } + +#ifdef HAVE_CAPSICUM + log->handle = log->real_fname == NULL ? -1 : + capsicum_open_wrapper(log->real_fname, O_WRONLY | O_APPEND | O_CREAT, + log_file_create_mode); +#else + log->handle = log->real_fname == NULL ? -1 : + open(log->real_fname, O_WRONLY | O_APPEND | O_CREAT, + log_file_create_mode); +#endif + if (log->handle == -1) { + signal_emit("log create failed", 1, log); + log->failed = TRUE; + return FALSE; + } + memset(&lock, 0, sizeof(lock)); + lock.l_type = F_WRLCK; + if (fcntl(log->handle, F_SETLK, &lock) == -1 && errno == EACCES) { + close(log->handle); + log->handle = -1; + signal_emit("log locked", 1, log); + log->failed = TRUE; + return FALSE; + } + lseek(log->handle, 0, SEEK_END); + + log->opened = log->last = time(NULL); + log_write_timestamp(log->handle, + settings_get_str("log_open_string"), + "\n", log->last); + + signal_emit("log started", 1, log); + log->failed = FALSE; + return TRUE; +} + +void log_stop_logging(LOG_REC *log) +{ + struct flock lock; + + g_return_if_fail(log != NULL); + + if (log->handle == -1) + return; + + signal_emit("log stopped", 1, log); + + log_write_timestamp(log->handle, + settings_get_str("log_close_string"), + "\n", time(NULL)); + + memset(&lock, 0, sizeof(lock)); + lock.l_type = F_UNLCK; + fcntl(log->handle, F_SETLK, &lock); + + write_buffer_flush(); + close(log->handle); + log->handle = -1; +} + +static void log_rotate_check(LOG_REC *log) +{ + char *new_fname; + + g_return_if_fail(log != NULL); + + if (log->handle == -1 || log->real_fname == NULL) + return; + + new_fname = log_filename(log); + if (g_strcmp0(new_fname, log->real_fname) != 0) { + /* rotate log */ + log_stop_logging(log); + signal_emit("log rotated", 1, log); + + log_start_logging(log); + } + g_free(new_fname); +} + +void log_write_rec(LOG_REC *log, const char *str, int level, time_t now) +{ + char *colorstr; + struct tm *tm; + int hour, day; + + g_return_if_fail(log != NULL); + g_return_if_fail(str != NULL); + + if (log->handle == -1) + return; + + if (now == (time_t) -1) + now = time(NULL); + tm = localtime(&now); + hour = tm->tm_hour; + day = tm->tm_mday; + + tm = localtime(&log->last); + day -= tm->tm_mday; /* tm breaks in log_rotate_check() .. */ + if (tm->tm_hour != hour) { + /* hour changed, check if we need to rotate log file */ + log_rotate_check(log); + } + + if (day != 0) { + /* day changed */ + log_write_timestamp(log->handle, + settings_get_str("log_day_changed"), + "\n", now); + } + + log->last = now; + + if (log->colorizer == NULL) + colorstr = NULL; + else + str = colorstr = log->colorizer(str); + + if ((level & MSGLEVEL_LASTLOG) == 0) + log_write_timestamp(log->handle, log_timestamp, str, now); + else + write_buffer(log->handle, str, strlen(str)); + write_buffer(log->handle, "\n", 1); + + signal_emit("log written", 2, log, str); + + g_free_not_null(colorstr); +} + +static int itemcmp(const char *patt, const char *item) +{ + /* returns 0 on match, nonzero otherwise */ + + if (!g_strcmp0(patt, "*")) + return 0; + return item ? g_ascii_strcasecmp(patt, item) : 1; +} + +LOG_ITEM_REC *log_item_find(LOG_REC *log, int type, const char *item, + const char *servertag) +{ + GSList *tmp; + + g_return_val_if_fail(log != NULL, NULL); + + for (tmp = log->items; tmp != NULL; tmp = tmp->next) { + LOG_ITEM_REC *rec = tmp->data; + + if (rec->type == type && itemcmp(rec->name, item) == 0 && + (rec->servertag == NULL || (servertag != NULL && + g_ascii_strcasecmp(rec->servertag, servertag) == 0))) + return rec; + } + + return NULL; +} + +void log_file_write(const char *server_tag, const char *item, int level, time_t t, const char *str, + int no_fallbacks) +{ + GSList *tmp, *fallbacks; + char *tmpstr; + int found; + + g_return_if_fail(str != NULL); + + if (logs == NULL) + return; + + fallbacks = NULL; found = FALSE; + + for (tmp = logs; tmp != NULL; tmp = tmp->next) { + LOG_REC *rec = tmp->data; + + if (rec->handle == -1) + continue; /* log not opened yet */ + + if ((level & rec->level) == 0) + continue; + + if (rec->items == NULL) + fallbacks = g_slist_append(fallbacks, rec); + else if (log_item_find(rec, LOG_ITEM_TARGET, item, + server_tag) != NULL) + log_write_rec(rec, str, level, t); + } + + if (!found && !no_fallbacks && fallbacks != NULL) { + /* not found from any items, so write it to all main logs */ + tmpstr = (level & MSGLEVEL_PUBLIC) && item != NULL ? + g_strconcat(item, ": ", str, NULL) : + g_strdup(str); + + for (tmp = fallbacks; tmp != NULL; tmp = tmp->next) + log_write_rec(tmp->data, tmpstr, level, t); + + g_free(tmpstr); + } + g_slist_free(fallbacks); +} + +LOG_REC *log_find(const char *fname) +{ + GSList *tmp; + + for (tmp = logs; tmp != NULL; tmp = tmp->next) { + LOG_REC *rec = tmp->data; + + if (g_strcmp0(rec->fname, fname) == 0) + return rec; + } + + return NULL; +} + +static void log_items_update_config(LOG_REC *log, CONFIG_NODE *parent) +{ + GSList *tmp; + CONFIG_NODE *node; + + parent = iconfig_node_section(parent, "items", NODE_TYPE_LIST); + for (tmp = log->items; tmp != NULL; tmp = tmp->next) { + LOG_ITEM_REC *rec = tmp->data; + + node = iconfig_node_section(parent, NULL, NODE_TYPE_BLOCK); + iconfig_node_set_str(node, "type", log_item_types[rec->type]); + iconfig_node_set_str(node, "name", rec->name); + iconfig_node_set_str(node, "server", rec->servertag); + } +} + +static void log_update_config(LOG_REC *log) +{ + CONFIG_NODE *node; + char *levelstr; + + if (log->temp) + return; + + node = iconfig_node_traverse("logs", TRUE); + node = iconfig_node_section(node, log->fname, NODE_TYPE_BLOCK); + + if (log->autoopen) + iconfig_node_set_bool(node, "auto_open", TRUE); + else + iconfig_node_set_str(node, "auto_open", NULL); + + levelstr = bits2level(log->level); + iconfig_node_set_str(node, "level", levelstr); + g_free(levelstr); + + iconfig_node_set_str(node, "items", NULL); + + if (log->items != NULL) + log_items_update_config(log, node); + + signal_emit("log config save", 2, log, node); +} + +static void log_remove_config(LOG_REC *log) +{ + iconfig_set_str("logs", log->fname, NULL); +} + +LOG_REC *log_create_rec(const char *fname, int level) +{ + LOG_REC *rec; + + g_return_val_if_fail(fname != NULL, NULL); + + rec = log_find(fname); + if (rec == NULL) { + rec = g_new0(LOG_REC, 1); + rec->fname = g_strdup(fname); + rec->real_fname = log_filename(rec); + rec->handle = -1; + } + + rec->level = level; + return rec; +} + +void log_item_add(LOG_REC *log, int type, const char *name, + const char *servertag) +{ + LOG_ITEM_REC *rec; + + g_return_if_fail(log != NULL); + g_return_if_fail(name != NULL); + + if (log_item_find(log, type, name, servertag)) + return; + + rec = g_new0(LOG_ITEM_REC, 1); + rec->type = type; + rec->name = g_strdup(name); + rec->servertag = g_strdup(servertag); + + log->items = g_slist_append(log->items, rec); +} + +void log_update(LOG_REC *log) +{ + g_return_if_fail(log != NULL); + + if (log_find(log->fname) == NULL) { + logs = g_slist_append(logs, log); + log->handle = -1; + } + + log_update_config(log); + signal_emit("log new", 1, log); +} + +void log_item_destroy(LOG_REC *log, LOG_ITEM_REC *item) +{ + log->items = g_slist_remove(log->items, item); + + g_free(item->name); + g_free_not_null(item->servertag); + g_free(item); +} + +static void log_destroy(LOG_REC *log) +{ + g_return_if_fail(log != NULL); + + if (log->handle != -1) + log_stop_logging(log); + + logs = g_slist_remove(logs, log); + signal_emit("log remove", 1, log); + + while (log->items != NULL) + log_item_destroy(log, log->items->data); + g_free(log->fname); + g_free_not_null(log->real_fname); + g_free(log); +} + +void log_close(LOG_REC *log) +{ + g_return_if_fail(log != NULL); + + log_remove_config(log); + log_destroy(log); +} + +static int sig_rotate_check(void) +{ + static int last_hour = -1; + struct tm tm; + time_t now; + + /* don't do anything until hour is changed */ + now = time(NULL); + memcpy(&tm, localtime(&now), sizeof(tm)); + if (tm.tm_hour != last_hour) { + last_hour = tm.tm_hour; + g_slist_foreach(logs, (GFunc) log_rotate_check, NULL); + } + return 1; +} + +static void log_items_read_config(CONFIG_NODE *node, LOG_REC *log) +{ + LOG_ITEM_REC *rec; + GSList *tmp; + char *item; + int type; + + tmp = config_node_first(node->value); + for (; tmp != NULL; tmp = config_node_next(tmp)) { + node = tmp->data; + + if (node->type != NODE_TYPE_BLOCK) + continue; + + item = config_node_get_str(node, "name", NULL); + type = log_item_str2type(config_node_get_str(node, "type", NULL)); + if (item == NULL || type == -1) + continue; + + rec = g_new0(LOG_ITEM_REC, 1); + rec->type = type; + rec->name = g_strdup(item); + rec->servertag = g_strdup(config_node_get_str(node, "server", NULL)); + + log->items = g_slist_append(log->items, rec); + } +} + +static void log_read_config(void) +{ + CONFIG_NODE *node; + LOG_REC *log; + GSList *tmp, *next, *fnames; + + /* close old logs, save list of open logs */ + fnames = NULL; + for (tmp = logs; tmp != NULL; tmp = next) { + log = tmp->data; + + next = tmp->next; + if (log->temp) + continue; + + if (log->handle != -1) + fnames = g_slist_append(fnames, g_strdup(log->fname)); + log_destroy(log); + } + + node = iconfig_node_traverse("logs", FALSE); + if (node == NULL) return; + + tmp = config_node_first(node->value); + for (; tmp != NULL; tmp = config_node_next(tmp)) { + node = tmp->data; + + if (node->type != NODE_TYPE_BLOCK) + continue; + + log = g_new0(LOG_REC, 1); + logs = g_slist_append(logs, log); + + log->handle = -1; + log->fname = g_strdup(node->key); + log->autoopen = config_node_get_bool(node, "auto_open", FALSE); + log->level = level2bits(config_node_get_str(node, "level", 0), NULL); + + signal_emit("log config read", 2, log, node); + + node = iconfig_node_section(node, "items", -1); + if (node != NULL) + log_items_read_config(node, log); + + if (log->autoopen || i_slist_find_string(fnames, log->fname)) + log_start_logging(log); + } + + g_slist_foreach(fnames, (GFunc) g_free, NULL); + g_slist_free(fnames); +} + +static void read_settings(void) +{ + g_free_not_null(log_timestamp); + log_timestamp = g_strdup(settings_get_str("log_timestamp")); + + log_file_create_mode = octal2dec(settings_get_int("log_create_mode")); + log_dir_create_mode = log_file_create_mode; + if (log_file_create_mode & 0400) log_dir_create_mode |= 0100; + if (log_file_create_mode & 0040) log_dir_create_mode |= 0010; + if (log_file_create_mode & 0004) log_dir_create_mode |= 0001; +} + +void log_init(void) +{ + rotate_tag = g_timeout_add(60000, (GSourceFunc) sig_rotate_check, NULL); + logs = NULL; + + settings_add_int("log", "log_create_mode", + DEFAULT_LOG_FILE_CREATE_MODE); + settings_add_str("log", "log_timestamp", "%H:%M "); + settings_add_str("log", "log_open_string", + "--- Log opened %a %b %d %H:%M:%S %Y"); + settings_add_str("log", "log_close_string", + "--- Log closed %a %b %d %H:%M:%S %Y"); + settings_add_str("log", "log_day_changed", + "--- Day changed %a %b %d %Y"); + + read_settings(); + signal_add("setup changed", (SIGNAL_FUNC) read_settings); + signal_add("setup reread", (SIGNAL_FUNC) log_read_config); + signal_add("irssi init finished", (SIGNAL_FUNC) log_read_config); +} + +void log_deinit(void) +{ + g_source_remove(rotate_tag); + + while (logs != NULL) + log_close(logs->data); + + g_free_not_null(log_timestamp); + + signal_remove("setup changed", (SIGNAL_FUNC) read_settings); + signal_remove("setup reread", (SIGNAL_FUNC) log_read_config); + signal_remove("irssi init finished", (SIGNAL_FUNC) log_read_config); +} |