1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Userspace indexing of printk formats |
4 | */ |
5 | |
6 | #include <linux/debugfs.h> |
7 | #include <linux/module.h> |
8 | #include <linux/printk.h> |
9 | #include <linux/slab.h> |
10 | #include <linux/string_helpers.h> |
11 | |
12 | #include "internal.h" |
13 | |
14 | extern struct pi_entry *__start_printk_index[]; |
15 | extern struct pi_entry *__stop_printk_index[]; |
16 | |
17 | /* The base dir for module formats, typically debugfs/printk/index/ */ |
18 | static struct dentry *dfs_index; |
19 | |
20 | static struct pi_entry *pi_get_entry(const struct module *mod, loff_t pos) |
21 | { |
22 | struct pi_entry **entries; |
23 | unsigned int nr_entries; |
24 | |
25 | #ifdef CONFIG_MODULES |
26 | if (mod) { |
27 | entries = mod->printk_index_start; |
28 | nr_entries = mod->printk_index_size; |
29 | } else |
30 | #endif |
31 | { |
32 | /* vmlinux, comes from linker symbols */ |
33 | entries = __start_printk_index; |
34 | nr_entries = __stop_printk_index - __start_printk_index; |
35 | } |
36 | |
37 | if (pos >= nr_entries) |
38 | return NULL; |
39 | |
40 | return entries[pos]; |
41 | } |
42 | |
43 | static void *pi_next(struct seq_file *s, void *v, loff_t *pos) |
44 | { |
45 | const struct module *mod = s->file->f_inode->i_private; |
46 | struct pi_entry *entry = pi_get_entry(mod, pos: *pos); |
47 | |
48 | (*pos)++; |
49 | |
50 | return entry; |
51 | } |
52 | |
53 | static void *pi_start(struct seq_file *s, loff_t *pos) |
54 | { |
55 | /* |
56 | * Make show() print the header line. Do not update *pos because |
57 | * pi_next() still has to return the entry at index 0 later. |
58 | */ |
59 | if (*pos == 0) |
60 | return SEQ_START_TOKEN; |
61 | |
62 | return pi_next(s, NULL, pos); |
63 | } |
64 | |
65 | /* |
66 | * We need both ESCAPE_ANY and explicit characters from ESCAPE_SPECIAL in @only |
67 | * because otherwise ESCAPE_NAP will cause double quotes and backslashes to be |
68 | * ignored for quoting. |
69 | */ |
70 | #define seq_escape_printf_format(s, src) \ |
71 | seq_escape_str(s, src, ESCAPE_ANY | ESCAPE_NAP | ESCAPE_APPEND, "\"\\") |
72 | |
73 | static int pi_show(struct seq_file *s, void *v) |
74 | { |
75 | const struct pi_entry *entry = v; |
76 | int level = LOGLEVEL_DEFAULT; |
77 | enum printk_info_flags flags = 0; |
78 | u16 prefix_len = 0; |
79 | |
80 | if (v == SEQ_START_TOKEN) { |
81 | seq_puts(m: s, s: "# <level/flags> filename:line function \"format\"\n" ); |
82 | return 0; |
83 | } |
84 | |
85 | if (!entry->fmt) |
86 | return 0; |
87 | |
88 | if (entry->level) |
89 | printk_parse_prefix(text: entry->level, level: &level, flags: &flags); |
90 | else |
91 | prefix_len = printk_parse_prefix(text: entry->fmt, level: &level, flags: &flags); |
92 | |
93 | |
94 | if (flags & LOG_CONT) { |
95 | /* |
96 | * LOGLEVEL_DEFAULT here means "use the same level as the |
97 | * message we're continuing from", not the default message |
98 | * loglevel, so don't display it as such. |
99 | */ |
100 | if (level == LOGLEVEL_DEFAULT) |
101 | seq_puts(m: s, s: "<c>" ); |
102 | else |
103 | seq_printf(m: s, fmt: "<%d,c>" , level); |
104 | } else |
105 | seq_printf(m: s, fmt: "<%d>" , level); |
106 | |
107 | seq_printf(m: s, fmt: " %s:%d %s \"" , entry->file, entry->line, entry->func); |
108 | if (entry->subsys_fmt_prefix) |
109 | seq_escape_printf_format(s, entry->subsys_fmt_prefix); |
110 | seq_escape_printf_format(s, entry->fmt + prefix_len); |
111 | seq_puts(m: s, s: "\"\n" ); |
112 | |
113 | return 0; |
114 | } |
115 | |
116 | static void pi_stop(struct seq_file *p, void *v) { } |
117 | |
118 | static const struct seq_operations dfs_index_sops = { |
119 | .start = pi_start, |
120 | .next = pi_next, |
121 | .show = pi_show, |
122 | .stop = pi_stop, |
123 | }; |
124 | |
125 | DEFINE_SEQ_ATTRIBUTE(dfs_index); |
126 | |
127 | #ifdef CONFIG_MODULES |
128 | static const char *pi_get_module_name(struct module *mod) |
129 | { |
130 | return mod ? mod->name : "vmlinux" ; |
131 | } |
132 | #else |
133 | static const char *pi_get_module_name(struct module *mod) |
134 | { |
135 | return "vmlinux" ; |
136 | } |
137 | #endif |
138 | |
139 | static void pi_create_file(struct module *mod) |
140 | { |
141 | debugfs_create_file(name: pi_get_module_name(mod), mode: 0444, parent: dfs_index, |
142 | data: mod, fops: &dfs_index_fops); |
143 | } |
144 | |
145 | #ifdef CONFIG_MODULES |
146 | static void pi_remove_file(struct module *mod) |
147 | { |
148 | debugfs_lookup_and_remove(name: pi_get_module_name(mod), parent: dfs_index); |
149 | } |
150 | |
151 | static int pi_module_notify(struct notifier_block *nb, unsigned long op, |
152 | void *data) |
153 | { |
154 | struct module *mod = data; |
155 | |
156 | switch (op) { |
157 | case MODULE_STATE_COMING: |
158 | pi_create_file(mod); |
159 | break; |
160 | case MODULE_STATE_GOING: |
161 | pi_remove_file(mod); |
162 | break; |
163 | default: /* we don't care about other module states */ |
164 | break; |
165 | } |
166 | |
167 | return NOTIFY_OK; |
168 | } |
169 | |
170 | static struct notifier_block module_printk_fmts_nb = { |
171 | .notifier_call = pi_module_notify, |
172 | }; |
173 | |
174 | static void __init pi_setup_module_notifier(void) |
175 | { |
176 | register_module_notifier(nb: &module_printk_fmts_nb); |
177 | } |
178 | #else |
179 | static inline void __init pi_setup_module_notifier(void) { } |
180 | #endif |
181 | |
182 | static int __init pi_init(void) |
183 | { |
184 | struct dentry *dfs_root = debugfs_create_dir(name: "printk" , NULL); |
185 | |
186 | dfs_index = debugfs_create_dir(name: "index" , parent: dfs_root); |
187 | pi_setup_module_notifier(); |
188 | pi_create_file(NULL); |
189 | |
190 | return 0; |
191 | } |
192 | |
193 | /* debugfs comes up on core and must be initialised first */ |
194 | postcore_initcall(pi_init); |
195 | |