root/kernel/printk/index.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_lookup_and_remove(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);