summaryrefslogtreecommitdiffstats
path: root/kernel/printk/index.c
blob: a6b27526baaf6f1eedde94858ec50e52dc2ac060 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
// 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);