forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
195 lines
4.3 KiB
195 lines
4.3 KiB
// SPDX-License-Identifier: GPL-2.0 |
|
/* |
|
* Userspace indexing of printk formats |
|
*/ |
|
|
|
#include <linux/debugfs.h> |
|
#include <linux/module.h> |
|
#include <linux/printk.h> |
|
#include <linux/slab.h> |
|
#include <linux/string_helpers.h> |
|
|
|
#include "internal.h" |
|
|
|
extern struct pi_entry *__start_printk_index[]; |
|
extern struct pi_entry *__stop_printk_index[]; |
|
|
|
/* The base dir for module formats, typically debugfs/printk/index/ */ |
|
static struct dentry *dfs_index; |
|
|
|
static struct pi_entry *pi_get_entry(const struct module *mod, loff_t pos) |
|
{ |
|
struct pi_entry **entries; |
|
unsigned int nr_entries; |
|
|
|
#ifdef CONFIG_MODULES |
|
if (mod) { |
|
entries = mod->printk_index_start; |
|
nr_entries = mod->printk_index_size; |
|
} |
|
#endif |
|
|
|
if (!mod) { |
|
/* vmlinux, comes from linker symbols */ |
|
entries = __start_printk_index; |
|
nr_entries = __stop_printk_index - __start_printk_index; |
|
} |
|
|
|
if (pos >= nr_entries) |
|
return NULL; |
|
|
|
return entries[pos]; |
|
} |
|
|
|
static void *pi_next(struct seq_file *s, void *v, loff_t *pos) |
|
{ |
|
const struct module *mod = s->file->f_inode->i_private; |
|
struct pi_entry *entry = pi_get_entry(mod, *pos); |
|
|
|
(*pos)++; |
|
|
|
return entry; |
|
} |
|
|
|
static void *pi_start(struct seq_file *s, loff_t *pos) |
|
{ |
|
/* |
|
* Make show() print the header line. Do not update *pos because |
|
* pi_next() still has to return the entry at index 0 later. |
|
*/ |
|
if (*pos == 0) |
|
return SEQ_START_TOKEN; |
|
|
|
return pi_next(s, NULL, pos); |
|
} |
|
|
|
/* |
|
* We need both ESCAPE_ANY and explicit characters from ESCAPE_SPECIAL in @only |
|
* because otherwise ESCAPE_NAP will cause double quotes and backslashes to be |
|
* ignored for quoting. |
|
*/ |
|
#define seq_escape_printf_format(s, src) \ |
|
seq_escape_str(s, src, ESCAPE_ANY | ESCAPE_NAP | ESCAPE_APPEND, "\"\\") |
|
|
|
static int pi_show(struct seq_file *s, void *v) |
|
{ |
|
const struct pi_entry *entry = v; |
|
int level = LOGLEVEL_DEFAULT; |
|
enum printk_info_flags flags = 0; |
|
u16 prefix_len = 0; |
|
|
|
if (v == SEQ_START_TOKEN) { |
|
seq_puts(s, "# <level/flags> filename:line function \"format\"\n"); |
|
return 0; |
|
} |
|
|
|
if (!entry->fmt) |
|
return 0; |
|
|
|
if (entry->level) |
|
printk_parse_prefix(entry->level, &level, &flags); |
|
else |
|
prefix_len = printk_parse_prefix(entry->fmt, &level, &flags); |
|
|
|
|
|
if (flags & LOG_CONT) { |
|
/* |
|
* LOGLEVEL_DEFAULT here means "use the same level as the |
|
* message we're continuing from", not the default message |
|
* loglevel, so don't display it as such. |
|
*/ |
|
if (level == LOGLEVEL_DEFAULT) |
|
seq_puts(s, "<c>"); |
|
else |
|
seq_printf(s, "<%d,c>", level); |
|
} else |
|
seq_printf(s, "<%d>", level); |
|
|
|
seq_printf(s, " %s:%d %s \"", entry->file, entry->line, entry->func); |
|
if (entry->subsys_fmt_prefix) |
|
seq_escape_printf_format(s, entry->subsys_fmt_prefix); |
|
seq_escape_printf_format(s, entry->fmt + prefix_len); |
|
seq_puts(s, "\"\n"); |
|
|
|
return 0; |
|
} |
|
|
|
static void pi_stop(struct seq_file *p, void *v) { } |
|
|
|
static const struct seq_operations dfs_index_sops = { |
|
.start = pi_start, |
|
.next = pi_next, |
|
.show = pi_show, |
|
.stop = pi_stop, |
|
}; |
|
|
|
DEFINE_SEQ_ATTRIBUTE(dfs_index); |
|
|
|
#ifdef CONFIG_MODULES |
|
static const char *pi_get_module_name(struct module *mod) |
|
{ |
|
return mod ? mod->name : "vmlinux"; |
|
} |
|
#else |
|
static const char *pi_get_module_name(struct module *mod) |
|
{ |
|
return "vmlinux"; |
|
} |
|
#endif |
|
|
|
static void pi_create_file(struct module *mod) |
|
{ |
|
debugfs_create_file(pi_get_module_name(mod), 0444, dfs_index, |
|
mod, &dfs_index_fops); |
|
} |
|
|
|
#ifdef CONFIG_MODULES |
|
static void pi_remove_file(struct module *mod) |
|
{ |
|
debugfs_remove(debugfs_lookup(pi_get_module_name(mod), dfs_index)); |
|
} |
|
|
|
static int pi_module_notify(struct notifier_block *nb, unsigned long op, |
|
void *data) |
|
{ |
|
struct module *mod = data; |
|
|
|
switch (op) { |
|
case MODULE_STATE_COMING: |
|
pi_create_file(mod); |
|
break; |
|
case MODULE_STATE_GOING: |
|
pi_remove_file(mod); |
|
break; |
|
default: /* we don't care about other module states */ |
|
break; |
|
} |
|
|
|
return NOTIFY_OK; |
|
} |
|
|
|
static struct notifier_block module_printk_fmts_nb = { |
|
.notifier_call = pi_module_notify, |
|
}; |
|
|
|
static void __init pi_setup_module_notifier(void) |
|
{ |
|
register_module_notifier(&module_printk_fmts_nb); |
|
} |
|
#else |
|
static inline void __init pi_setup_module_notifier(void) { } |
|
#endif |
|
|
|
static int __init pi_init(void) |
|
{ |
|
struct dentry *dfs_root = debugfs_create_dir("printk", NULL); |
|
|
|
dfs_index = debugfs_create_dir("index", dfs_root); |
|
pi_setup_module_notifier(); |
|
pi_create_file(NULL); |
|
|
|
return 0; |
|
} |
|
|
|
/* debugfs comes up on core and must be initialised first */ |
|
postcore_initcall(pi_init);
|
|
|