diff options
Diffstat (limited to '')
-rw-r--r-- | src/doveadm/doveadm-log.c | 406 |
1 files changed, 406 insertions, 0 deletions
diff --git a/src/doveadm/doveadm-log.c b/src/doveadm/doveadm-log.c new file mode 100644 index 0000000..2654d9b --- /dev/null +++ b/src/doveadm/doveadm-log.c @@ -0,0 +1,406 @@ +/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "ioloop.h" +#include "istream.h" +#include "hash.h" +#include "str.h" +#include "strescape.h" +#include "time-util.h" +#include "master-service-private.h" +#include "master-service-settings.h" +#include "doveadm.h" +#include "doveadm-print.h" + +#include <stdio.h> +#include <unistd.h> +#include <fcntl.h> +#include <dirent.h> +#include <signal.h> +#include <sys/stat.h> + +#define LAST_LOG_TYPE LOG_TYPE_PANIC +#define TEST_LOG_MSG_PREFIX "This is Dovecot's " +#define LOG_ERRORS_FNAME "log-errors" +#define LOG_TIMESTAMP_FORMAT "%b %d %H:%M:%S" + +static void ATTR_NULL(2) +cmd_log_test(struct doveadm_cmd_context *cctx ATTR_UNUSED) +{ + struct failure_context ctx; + unsigned int i; + + master_service->log_initialized = FALSE; + master_service->flags |= MASTER_SERVICE_FLAG_DONT_LOG_TO_STDERR; + master_service_init_log(master_service); + + i_zero(&ctx); + for (i = 0; i < LAST_LOG_TYPE; i++) { + const char *prefix = failure_log_type_prefixes[i]; + + /* add timestamp so that syslog won't just write + "repeated message" text */ + ctx.type = i; + i_log_type(&ctx, TEST_LOG_MSG_PREFIX"%s log (%u)", + t_str_lcase(t_strcut(prefix, ':')), + (unsigned int)ioloop_time); + } +} + +static void cmd_log_reopen(struct doveadm_cmd_context *cctx ATTR_UNUSED) +{ + doveadm_master_send_signal(SIGUSR1); +} + +struct log_find_file { + const char *path; + uoff_t size; + + /* 1 << enum log_type */ + unsigned int mask; +}; + +struct log_find_context { + pool_t pool; + HASH_TABLE(char *, struct log_find_file *) files; +}; + +static void cmd_log_find_add(struct log_find_context *ctx, + const char *path, enum log_type type) +{ + struct log_find_file *file; + char *key; + + file = hash_table_lookup(ctx->files, path); + if (file == NULL) { + file = p_new(ctx->pool, struct log_find_file, 1); + file->path = key = p_strdup(ctx->pool, path); + hash_table_insert(ctx->files, key, file); + } + + file->mask |= 1 << type; +} + +static void +cmd_log_find_syslog_files(struct log_find_context *ctx, const char *path) +{ + struct log_find_file *file; + DIR *dir; + struct dirent *d; + struct stat st; + char *key; + string_t *full_path; + size_t dir_len; + + dir = opendir(path); + if (dir == NULL) { + i_error("opendir(%s) failed: %m", path); + return; + } + + full_path = t_str_new(256); + str_append(full_path, path); + str_append_c(full_path, '/'); + dir_len = str_len(full_path); + + while ((d = readdir(dir)) != NULL) { + if (d->d_name[0] == '.') + continue; + + str_truncate(full_path, dir_len); + str_append(full_path, d->d_name); + if (stat(str_c(full_path), &st) < 0) + continue; + + if (S_ISDIR(st.st_mode)) { + /* recursively go through all subdirectories */ + cmd_log_find_syslog_files(ctx, str_c(full_path)); + } else if (hash_table_lookup(ctx->files, + str_c(full_path)) == NULL) { + file = p_new(ctx->pool, struct log_find_file, 1); + file->size = st.st_size; + file->path = key = + p_strdup(ctx->pool, str_c(full_path)); + hash_table_insert(ctx->files, key, file); + } + } + + (void)closedir(dir); +} + +static bool log_type_find(const char *str, enum log_type *type_r) +{ + unsigned int i; + size_t len = strlen(str); + + for (i = 0; i < LAST_LOG_TYPE; i++) { + if (strncasecmp(str, failure_log_type_prefixes[i], len) == 0 && + failure_log_type_prefixes[i][len] == ':') { + *type_r = i; + return TRUE; + } + } + return FALSE; +} + +static void cmd_log_find_syslog_file_messages(struct log_find_file *file) +{ + struct istream *input; + const char *line, *p; + enum log_type type; + int fd; + + fd = open(file->path, O_RDONLY); + if (fd == -1) + return; + + input = i_stream_create_fd_autoclose(&fd, 1024); + i_stream_seek(input, file->size); + while ((line = i_stream_read_next_line(input)) != NULL) { + p = strstr(line, TEST_LOG_MSG_PREFIX); + if (p == NULL) + continue; + p += strlen(TEST_LOG_MSG_PREFIX); + + /* <type> log */ + T_BEGIN { + if (log_type_find(t_strcut(p, ' '), &type)) + file->mask |= 1 << type; + } T_END; + } + i_stream_destroy(&input); +} + +static void cmd_log_find_syslog_messages(struct log_find_context *ctx) +{ + struct hash_iterate_context *iter; + struct stat st; + char *key; + struct log_find_file *file; + + iter = hash_table_iterate_init(ctx->files); + while (hash_table_iterate(iter, ctx->files, &key, &file)) { + if (stat(file->path, &st) < 0 || + (uoff_t)st.st_size <= file->size) + continue; + + cmd_log_find_syslog_file_messages(file); + } + hash_table_iterate_deinit(&iter); +} + +static void +cmd_log_find_syslog(struct log_find_context *ctx, + struct doveadm_cmd_context *cctx) +{ + const char *log_dir; + struct stat st; + + if (doveadm_cmd_param_str(cctx, "log-dir", &log_dir)) + ; + else if (stat("/var/log", &st) == 0 && S_ISDIR(st.st_mode)) + log_dir = "/var/log"; + else if (stat("/var/adm", &st) == 0 && S_ISDIR(st.st_mode)) + log_dir = "/var/adm"; + else + return; + + printf("Looking for log files from %s\n", log_dir); + cmd_log_find_syslog_files(ctx, log_dir); + cmd_log_test(cctx); + + /* give syslog some time to write the messages to files */ + sleep(1); + cmd_log_find_syslog_messages(ctx); +} + +static void cmd_log_find(struct doveadm_cmd_context *cctx) +{ + const struct master_service_settings *set; + const char *log_file_path; + struct log_find_context ctx; + unsigned int i; + + i_zero(&ctx); + ctx.pool = pool_alloconly_create("log file", 1024*32); + hash_table_create(&ctx.files, ctx.pool, 0, str_hash, strcmp); + + /* first get the paths that we know are used */ + set = master_service_settings_get(master_service); + log_file_path = set->log_path; + if (strcmp(log_file_path, "syslog") == 0) + log_file_path = ""; + if (*log_file_path != '\0') { + cmd_log_find_add(&ctx, log_file_path, LOG_TYPE_WARNING); + cmd_log_find_add(&ctx, log_file_path, LOG_TYPE_ERROR); + cmd_log_find_add(&ctx, log_file_path, LOG_TYPE_FATAL); + } + + if (strcmp(set->info_log_path, "syslog") != 0) { + if (*set->info_log_path != '\0') + log_file_path = set->info_log_path; + if (*log_file_path != '\0') + cmd_log_find_add(&ctx, log_file_path, LOG_TYPE_INFO); + } + + if (strcmp(set->debug_log_path, "syslog") != 0) { + if (*set->debug_log_path != '\0') + log_file_path = set->debug_log_path; + if (*log_file_path != '\0') + cmd_log_find_add(&ctx, log_file_path, LOG_TYPE_DEBUG); + } + + if (*set->log_path == '\0' || + strcmp(set->log_path, "syslog") == 0 || + strcmp(set->info_log_path, "syslog") == 0 || + strcmp(set->debug_log_path, "syslog") == 0) { + /* at least some logs were logged via syslog */ + cmd_log_find_syslog(&ctx, cctx); + } + + /* print them */ + for (i = 0; i < LAST_LOG_TYPE; i++) { + struct hash_iterate_context *iter; + char *key; + struct log_find_file *file; + bool found = FALSE; + + iter = hash_table_iterate_init(ctx.files); + while (hash_table_iterate(iter, ctx.files, &key, &file)) { + if ((file->mask & (1 << i)) != 0) { + printf("%s%s\n", failure_log_type_prefixes[i], + file->path); + found = TRUE; + } + } + hash_table_iterate_deinit(&iter); + + if (!found) + printf("%sNot found\n", failure_log_type_prefixes[i]); + } + hash_table_destroy(&ctx.files); + pool_unref(&ctx.pool); +} + +static const char *t_cmd_log_error_trim(const char *orig) +{ + size_t pos; + + /* Trim whitespace from suffix and remove ':' if it exists */ + for (pos = strlen(orig); pos > 0; pos--) { + if (orig[pos-1] != ' ') { + if (orig[pos-1] == ':') + pos--; + break; + } + } + return orig[pos] == '\0' ? orig : t_strndup(orig, pos); +} + +static void cmd_log_error_write(const char *const *args, time_t min_timestamp) +{ + /* <type> <timestamp> <prefix> <text> */ + const char *type_prefix = "?"; + unsigned int type; + time_t t; + + /* find type's prefix */ + for (type = 0; type < LOG_TYPE_COUNT; type++) { + if (strcmp(args[0], failure_log_type_names[type]) == 0) { + type_prefix = failure_log_type_prefixes[type]; + break; + } + } + + if (str_to_time(args[1], &t) < 0) { + i_error("Invalid timestamp: %s", args[1]); + t = 0; + } + if (t >= min_timestamp) { + doveadm_print(t_strflocaltime(LOG_TIMESTAMP_FORMAT, t)); + doveadm_print(t_cmd_log_error_trim(args[2])); + doveadm_print(t_cmd_log_error_trim(type_prefix)); + doveadm_print(args[3]); + } +} + +static void cmd_log_errors(struct doveadm_cmd_context *cctx) +{ + struct istream *input; + const char *path, *line, *const *args; + time_t min_timestamp = 0; + int64_t since_int64; + int fd; + + if (doveadm_cmd_param_int64(cctx, "since", &since_int64)) + min_timestamp = since_int64; + + path = t_strconcat(doveadm_settings->base_dir, + "/"LOG_ERRORS_FNAME, NULL); + fd = net_connect_unix(path); + if (fd == -1) + i_fatal("net_connect_unix(%s) failed: %m", path); + net_set_nonblock(fd, FALSE); + + input = i_stream_create_fd_autoclose(&fd, SIZE_MAX); + + doveadm_print_init(DOVEADM_PRINT_TYPE_FORMATTED); + doveadm_print_formatted_set_format("%{timestamp} %{type}: %{prefix}: %{text}\n"); + + doveadm_print_header_simple("timestamp"); + doveadm_print_header_simple("prefix"); + doveadm_print_header_simple("type"); + doveadm_print_header_simple("text"); + + while ((line = i_stream_read_next_line(input)) != NULL) T_BEGIN { + args = t_strsplit_tabescaped(line); + if (str_array_length(args) == 4) + cmd_log_error_write(args, min_timestamp); + else { + i_error("Invalid input from log: %s", line); + doveadm_exit_code = EX_PROTOCOL; + } + } T_END; + i_stream_destroy(&input); +} + +struct doveadm_cmd_ver2 doveadm_cmd_log[] = { +{ + .name = "log test", + .cmd = cmd_log_test, + .usage = "", +DOVEADM_CMD_PARAMS_START +DOVEADM_CMD_PARAMS_END +}, +{ + .name = "log reopen", + .cmd = cmd_log_reopen, + .usage = "", +DOVEADM_CMD_PARAMS_START +DOVEADM_CMD_PARAMS_END +}, +{ + .name = "log find", + .cmd = cmd_log_find, + .usage = "[<dir>]", +DOVEADM_CMD_PARAMS_START +DOVEADM_CMD_PARAM('\0', "log-dir", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAMS_END +}, +{ + .name = "log errors", + .usage = "[-s <min_timestamp>]", + .cmd = cmd_log_errors, +DOVEADM_CMD_PARAMS_START +DOVEADM_CMD_PARAM('s', "since", CMD_PARAM_INT64, 0) +DOVEADM_CMD_PARAMS_END +} +}; + +void doveadm_register_log_commands(void) +{ + unsigned int i; + + for (i = 0; i < N_ELEMENTS(doveadm_cmd_log); i++) + doveadm_cmd_register_ver2(&doveadm_cmd_log[i]); +} |