/* * Wireshark - Network traffic analyzer * By Gerald Combs * Copyright 2021 Gerald Combs * * SPDX-License-Identifier: GPL-2.0-or-later */ #include "config.h" #include "wslog.h" #include #include #include #include #include #ifdef HAVE_UNISTD_H #include #endif #ifdef _WIN32 #include #endif #include #include #include /* Runtime log level. */ #define ENV_VAR_LEVEL "WIRESHARK_LOG_LEVEL" /* Log domains enabled/disabled. */ #define ENV_VAR_DOMAINS "WIRESHARK_LOG_DOMAINS" /* Log level that generates a trap and aborts. Can be "critical" * or "warning". */ #define ENV_VAR_FATAL "WIRESHARK_LOG_FATAL" /* Domains that will produce debug output, regardless of log level or * domain filter. */ #define ENV_VAR_DEBUG "WIRESHARK_LOG_DEBUG" /* Domains that will produce noisy output, regardless of log level or * domain filter. */ #define ENV_VAR_NOISY "WIRESHARK_LOG_NOISY" #define DEFAULT_LOG_LEVEL LOG_LEVEL_MESSAGE #define DEFAULT_PROGNAME "PID" #define DOMAIN_UNDEFED(domain) ((domain) == NULL || *(domain) == '\0') #define DOMAIN_DEFINED(domain) (!DOMAIN_UNDEFED(domain)) /* * Note: I didn't measure it but I assume using a string array is faster than * a GHashTable for small number N of domains. */ typedef struct { char **domainv; gboolean positive; /* positive or negative match */ enum ws_log_level min_level; /* for level filters */ } log_filter_t; static enum ws_log_level current_log_level = LOG_LEVEL_NONE; static gboolean color_enabled = FALSE; static const char *registered_progname = DEFAULT_PROGNAME; /* List of domains to filter. */ static log_filter_t *domain_filter = NULL; /* List of domains to output debug level unconditionally. */ static log_filter_t *debug_filter = NULL; /* List of domains to output noisy level unconditionally. */ static log_filter_t *noisy_filter = NULL; static ws_log_writer_cb *registered_log_writer = NULL; static void *registered_log_writer_data = NULL; static ws_log_writer_free_data_cb *registered_log_writer_data_free = NULL; static FILE *custom_log = NULL; static enum ws_log_level fatal_log_level = LOG_LEVEL_ERROR; #ifndef WS_DISABLE_DEBUG static gboolean init_complete = FALSE; #endif static void ws_log_cleanup(void); const char *ws_log_level_to_string(enum ws_log_level level) { switch (level) { case LOG_LEVEL_NONE: return "(zero)"; case LOG_LEVEL_ERROR: return "ERROR"; case LOG_LEVEL_CRITICAL: return "CRITICAL"; case LOG_LEVEL_WARNING: return "WARNING"; case LOG_LEVEL_MESSAGE: return "MESSAGE"; case LOG_LEVEL_INFO: return "INFO"; case LOG_LEVEL_DEBUG: return "DEBUG"; case LOG_LEVEL_NOISY: return "NOISY"; default: return "(BOGUS LOG LEVEL)"; } } static enum ws_log_level string_to_log_level(const char *str_level) { if (!str_level) return LOG_LEVEL_NONE; if (g_ascii_strcasecmp(str_level, "noisy") == 0) return LOG_LEVEL_NOISY; else if (g_ascii_strcasecmp(str_level, "debug") == 0) return LOG_LEVEL_DEBUG; else if (g_ascii_strcasecmp(str_level, "info") == 0) return LOG_LEVEL_INFO; else if (g_ascii_strcasecmp(str_level, "message") == 0) return LOG_LEVEL_MESSAGE; else if (g_ascii_strcasecmp(str_level, "warning") == 0) return LOG_LEVEL_WARNING; else if (g_ascii_strcasecmp(str_level, "critical") == 0) return LOG_LEVEL_CRITICAL; else if (g_ascii_strcasecmp(str_level, "error") == 0) return LOG_LEVEL_ERROR; else return LOG_LEVEL_NONE; } WS_RETNONNULL static inline const char *domain_to_string(const char *domain) { return (domain == NULL) ? "(none)" : domain; } static inline gboolean filter_contains(log_filter_t *filter, const char *domain) { ws_assert(filter); ws_assert(domain); char **domv; for (domv = filter->domainv; *domv != NULL; domv++) { if (g_ascii_strcasecmp(*domv, domain) == 0) { return TRUE; } } return FALSE; } static gboolean level_filter_matches(log_filter_t *filter, const char *domain, enum ws_log_level level, gboolean *active) { ws_assert(filter); ws_assert(filter->min_level != LOG_LEVEL_NONE); ws_assert(domain != NULL); ws_assert(level != LOG_LEVEL_NONE); ws_assert(active != NULL); if (filter_contains(filter, domain) == FALSE) return FALSE; if (filter->positive) { *active = level >= filter->min_level; return TRUE; } /* negative match */ if (level <= filter->min_level) { *active = FALSE; return TRUE; } return FALSE; } gboolean ws_log_msg_is_active(const char *domain, enum ws_log_level level) { /* * Higher numerical levels have higher priority. Critical and above * are always enabled. */ if (level >= LOG_LEVEL_CRITICAL) return TRUE; /* * The debug/noisy filter overrides the other parameters. */ if (DOMAIN_DEFINED(domain)) { gboolean active; if (noisy_filter && level_filter_matches(noisy_filter, domain, level, &active)) return active; if (debug_filter && level_filter_matches(debug_filter, domain, level, &active)) return active; } /* * If the priority is lower than the current minimum drop the * message. */ if (level < current_log_level) return FALSE; /* * If we don't have domain filtering enabled we are done. */ if (domain_filter == NULL) return TRUE; /* * We have a filter but we don't use it with the undefined domain, * pretty much every permanent call to ws_log should be using a * chosen domain. */ if (DOMAIN_UNDEFED(domain)) return TRUE; /* Check if the domain filter matches. */ if (filter_contains(domain_filter, domain)) return domain_filter->positive; /* We have a domain filter but it didn't match. */ return !domain_filter->positive; } enum ws_log_level ws_log_get_level(void) { return current_log_level; } enum ws_log_level ws_log_set_level(enum ws_log_level log_level) { ws_assert(log_level > LOG_LEVEL_NONE && log_level < _LOG_LEVEL_LAST); current_log_level = log_level; return current_log_level; } enum ws_log_level ws_log_set_level_str(const char *str_level) { enum ws_log_level level; level = string_to_log_level(str_level); if (level == LOG_LEVEL_NONE) return LOG_LEVEL_NONE; current_log_level = level; return current_log_level; } static const char *opt_level = "--log-level"; static const char *opt_domains = "--log-domains"; static const char *opt_file = "--log-file"; static const char *opt_fatal = "--log-fatal"; static const char *opt_debug = "--log-debug"; static const char *opt_noisy = "--log-noisy"; static void print_err(void (*log_args_print_err)(const char *, va_list ap), int log_args_exit_failure, const char *fmt, ...) { va_list ap; if (log_args_print_err == NULL) return; va_start(ap, fmt); log_args_print_err(fmt, ap); va_end(ap); if (log_args_exit_failure >= 0) exit(log_args_exit_failure); } int ws_log_parse_args(int *argc_ptr, char *argv[], void (*vcmdarg_err)(const char *, va_list ap), int exit_failure) { char **ptr = argv; int count = *argc_ptr; int ret = 0; size_t optlen; const char *option, *value; int prune_extra; while (*ptr != NULL) { if (g_str_has_prefix(*ptr, opt_level)) { option = opt_level; optlen = strlen(opt_level); } else if (g_str_has_prefix(*ptr, opt_domains)) { option = opt_domains; optlen = strlen(opt_domains); } else if (g_str_has_prefix(*ptr, opt_file)) { option = opt_file; optlen = strlen(opt_file); } else if (g_str_has_prefix(*ptr, opt_fatal)) { option = opt_fatal; optlen = strlen(opt_fatal); } else if (g_str_has_prefix(*ptr, opt_debug)) { option = opt_debug; optlen = strlen(opt_debug); } else if (g_str_has_prefix(*ptr, opt_noisy)) { option = opt_noisy; optlen = strlen(opt_noisy); } else { ptr += 1; count -= 1; continue; } value = *ptr + optlen; /* Two possibilities: * --