// SPDX-License-Identifier: GPL-2.0-only /* * Copyright 2023 Red Hat */ #include "logger.h" #include <linux/delay.h> #include <linux/hardirq.h> #include <linux/module.h> #include <linux/printk.h> #include <linux/sched.h> #include "thread-device.h" #include "uds-threads.h" struct priority_name { const char *name; const int priority; }; static const struct priority_name PRIORITIES[] = { { "ALERT", UDS_LOG_ALERT }, { "CRITICAL", UDS_LOG_CRIT }, { "CRIT", UDS_LOG_CRIT }, { "DEBUG", UDS_LOG_DEBUG }, { "EMERGENCY", UDS_LOG_EMERG }, { "EMERG", UDS_LOG_EMERG }, { "ERROR", UDS_LOG_ERR }, { "ERR", UDS_LOG_ERR }, { "INFO", UDS_LOG_INFO }, { "NOTICE", UDS_LOG_NOTICE }, { "PANIC", UDS_LOG_EMERG }, { "WARN", UDS_LOG_WARNING }, { "WARNING", UDS_LOG_WARNING }, { NULL, -1 }, }; static const char *const PRIORITY_STRINGS[] = { "EMERGENCY", "ALERT", "CRITICAL", "ERROR", "WARN", "NOTICE", "INFO", "DEBUG", }; static int log_level = UDS_LOG_INFO; int uds_get_log_level(void) { return log_level; } void uds_set_log_level(int new_log_level) { log_level = new_log_level; } int uds_log_string_to_priority(const char *string) { int i; for (i = 0; PRIORITIES[i].name != NULL; i++) { if (strcasecmp(string, PRIORITIES[i].name) == 0) return PRIORITIES[i].priority; } return UDS_LOG_INFO; } const char *uds_log_priority_to_string(int priority) { if ((priority < 0) || (priority >= (int) ARRAY_SIZE(PRIORITY_STRINGS))) return "unknown"; return PRIORITY_STRINGS[priority]; } static const char *get_current_interrupt_type(void) { if (in_nmi()) return "NMI"; if (in_irq()) return "HI"; if (in_softirq()) return "SI"; return "INTR"; } /** * emit_log_message_to_kernel() - Emit a log message to the kernel at the specified priority. * * @priority: The priority at which to log the message * @fmt: The format string of the message */ static void emit_log_message_to_kernel(int priority, const char *fmt, ...) { va_list args; struct va_format vaf; if (priority > uds_get_log_level()) return; va_start(args, fmt); vaf.fmt = fmt; vaf.va = &args; switch (priority) { case UDS_LOG_EMERG: case UDS_LOG_ALERT: case UDS_LOG_CRIT: printk(KERN_CRIT "%pV", &vaf); break; case UDS_LOG_ERR: printk(KERN_ERR "%pV", &vaf); break; case UDS_LOG_WARNING: printk(KERN_WARNING "%pV", &vaf); break; case UDS_LOG_NOTICE: printk(KERN_NOTICE "%pV", &vaf); break; case UDS_LOG_INFO: printk(KERN_INFO "%pV", &vaf); break; case UDS_LOG_DEBUG: printk(KERN_DEBUG "%pV", &vaf); break; default: printk(KERN_DEFAULT "%pV", &vaf); break; } va_end(args); } /** * emit_log_message() - Emit a log message to the kernel log in a format suited to the current * thread context. * * Context info formats: * * interrupt: uds[NMI]: blah * kvdo thread: kvdo12:foobarQ: blah * thread w/device id: kvdo12:myprog: blah * other thread: uds: myprog: blah * * Fields: module name, interrupt level, process name, device ID. * * @priority: the priority at which to log the message * @module: The name of the module doing the logging * @prefix: The prefix of the log message * @vaf1: The first message format descriptor * @vaf2: The second message format descriptor */ static void emit_log_message(int priority, const char *module, const char *prefix, const struct va_format *vaf1, const struct va_format *vaf2) { int device_instance; /* * In interrupt context, identify the interrupt type and module. Ignore the process/thread * since it could be anything. */ if (in_interrupt()) { const char *type = get_current_interrupt_type(); emit_log_message_to_kernel(priority, "%s[%s]: %s%pV%pV\n", module, type, prefix, vaf1, vaf2); return; } /* Not at interrupt level; we have a process we can look at, and might have a device ID. */ device_instance = uds_get_thread_device_id(); if (device_instance >= 0) { emit_log_message_to_kernel(priority, "%s%u:%s: %s%pV%pV\n", module, device_instance, current->comm, prefix, vaf1, vaf2); return; } /* * If it's a kernel thread and the module name is a prefix of its name, assume it is ours * and only identify the thread. */ if (((current->flags & PF_KTHREAD) != 0) && (strncmp(module, current->comm, strlen(module)) == 0)) { emit_log_message_to_kernel(priority, "%s: %s%pV%pV\n", current->comm, prefix, vaf1, vaf2); return; } /* Identify the module and the process. */ emit_log_message_to_kernel(priority, "%s: %s: %s%pV%pV\n", module, current->comm, prefix, vaf1, vaf2); } /* * uds_log_embedded_message() - Log a message embedded within another message. * @priority: the priority at which to log the message * @module: the name of the module doing the logging * @prefix: optional string prefix to message, may be NULL * @fmt1: format of message first part (required) * @args1: arguments for message first part (required) * @fmt2: format of message second part */ void uds_log_embedded_message(int priority, const char *module, const char *prefix, const char *fmt1, va_list args1, const char *fmt2, ...) { va_list args1_copy; va_list args2; struct va_format vaf1, vaf2; va_start(args2, fmt2); if (module == NULL) module = UDS_LOGGING_MODULE_NAME; if (prefix == NULL) prefix = ""; /* * It is implementation dependent whether va_list is defined as an array type that decays * to a pointer when passed as an argument. Copy args1 and args2 with va_copy so that vaf1 * and vaf2 get proper va_list pointers irrespective of how va_list is defined. */ va_copy(args1_copy, args1); vaf1.fmt = fmt1; vaf1.va = &args1_copy; vaf2.fmt = fmt2; vaf2.va = &args2; emit_log_message(priority, module, prefix, &vaf1, &vaf2); va_end(args1_copy); va_end(args2); } int uds_vlog_strerror(int priority, int errnum, const char *module, const char *format, va_list args) { char errbuf[UDS_MAX_ERROR_MESSAGE_SIZE]; const char *message = uds_string_error(errnum, errbuf, sizeof(errbuf)); uds_log_embedded_message(priority, module, NULL, format, args, ": %s (%d)", message, errnum); return errnum; } int __uds_log_strerror(int priority, int errnum, const char *module, const char *format, ...) { va_list args; va_start(args, format); uds_vlog_strerror(priority, errnum, module, format, args); va_end(args); return errnum; } void uds_log_backtrace(int priority) { if (priority > uds_get_log_level()) return; dump_stack(); } void __uds_log_message(int priority, const char *module, const char *format, ...) { va_list args; va_start(args, format); uds_log_embedded_message(priority, module, NULL, format, args, "%s", ""); va_end(args); } /* * Sleep or delay a few milliseconds in an attempt to allow the log buffers to be flushed lest they * be overrun. */ void uds_pause_for_logger(void) { fsleep(4000); }