195 lines
4.3 KiB
C
195 lines
4.3 KiB
C
// 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;
|
|
} else
|
|
#endif
|
|
{
|
|
/* 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);
|