/* * Copyright 2021, João Valverde * * Wireshark - Network traffic analyzer * By Gerald Combs * Copyright 1998 Gerald Combs * * SPDX-License-Identifier: GPL-2.0-or-later */ #include "config.h" #if defined(WS_DISABLE_ASSERT) && !defined(NDEBUG) #define NDEBUG #endif #include "wslog.h" #include #include #include #include #include #ifdef HAVE_UNISTD_H #include #endif #ifdef _WIN32 #include #include #endif #include "file_util.h" #include "to_str.h" /* Runtime log level. */ #define ENV_VAR_LEVEL "WIRESHARK_LOG_LEVEL" /* Log domains enabled/disabled. */ #define ENV_VAR_DOMAIN "WIRESHARK_LOG_DOMAIN" /* Alias "domain" and "domains". */ #define ENV_VAR_DOMAIN_S "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)) #define VALID_FATAL_LEVEL(level) \ (level >= LOG_LEVEL_WARNING && level <= LOG_LEVEL_ERROR) /* * 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; /* If the module is not initialized by calling ws_log_init() all messages * will be printed regardless of log level. This is a feature, not a bug. */ 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 print_err(void (*vcmdarg_err)(const char *, va_list ap), int exit_failure, const char *fmt, ...); 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_UNDEFED(domain) ? "(none)" : domain; } static inline gboolean filter_contains(log_filter_t *filter, const char *domain) { if (filter == NULL || DOMAIN_UNDEFED(domain)) return FALSE; for (char **domv = filter->domainv; *domv != NULL; domv++) { if (g_ascii_strcasecmp(*domv, domain) == 0) { return TRUE; } } return FALSE; } static inline gboolean level_filter_matches(log_filter_t *filter, const char *domain, enum ws_log_level level, gboolean *active_ptr) { if (filter == NULL || DOMAIN_UNDEFED(domain)) return FALSE; if (!filter_contains(filter, domain)) return FALSE; if (filter->positive) { if (active_ptr) *active_ptr = level >= filter->min_level; return TRUE; } /* negative match */ if (level <= filter->min_level) { if (active_ptr) *active_ptr = 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; /* * Check if the level has been configured as fatal. */ if (level >= fatal_log_level) return TRUE; /* * The debug/noisy filter overrides the other parameters. */ if (DOMAIN_DEFINED(domain)) { gboolean active; if (level_filter_matches(noisy_filter, domain, level, &active)) return active; if (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; } void ws_log_set_level(enum ws_log_level level) { if (level <= LOG_LEVEL_NONE || level >= _LOG_LEVEL_LAST) return; current_log_level = 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"; /* Alias "domain" and "domains". */ static const char *opt_domain = "--log-domain"; 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 (*vcmdarg_err)(const char *, va_list ap), int exit_failure, const char *fmt, ...) { va_list ap; if (vcmdarg_err == NULL) return; va_start(ap, fmt); vcmdarg_err(fmt, ap); va_end(ap); if (exit_failure != LOG_ARGS_NOEXIT) exit(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 extra; if (argc_ptr == NULL || argv == NULL) return -1; /* Configure from command line. */ 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_domain)) { option = opt_domain; optlen = strlen(opt_domain); /* Alias "domain" and "domains". Last form wins. */ if (*(*ptr + optlen) == 's') { optlen += 1; } } 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: * --