1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * linux/drivers/net/netconsole.c |
4 | * |
5 | * Copyright (C) 2001 Ingo Molnar <mingo@redhat.com> |
6 | * |
7 | * This file contains the implementation of an IRQ-safe, crash-safe |
8 | * kernel console implementation that outputs kernel messages to the |
9 | * network. |
10 | * |
11 | * Modification history: |
12 | * |
13 | * 2001-09-17 started by Ingo Molnar. |
14 | * 2003-08-11 2.6 port by Matt Mackall |
15 | * simplified options |
16 | * generic card hooks |
17 | * works non-modular |
18 | * 2003-09-07 rewritten with netpoll api |
19 | */ |
20 | |
21 | /**************************************************************** |
22 | * |
23 | ****************************************************************/ |
24 | |
25 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
26 | |
27 | #include <linux/mm.h> |
28 | #include <linux/init.h> |
29 | #include <linux/module.h> |
30 | #include <linux/slab.h> |
31 | #include <linux/console.h> |
32 | #include <linux/moduleparam.h> |
33 | #include <linux/kernel.h> |
34 | #include <linux/string.h> |
35 | #include <linux/netpoll.h> |
36 | #include <linux/inet.h> |
37 | #include <linux/configfs.h> |
38 | #include <linux/etherdevice.h> |
39 | #include <linux/utsname.h> |
40 | |
41 | MODULE_AUTHOR("Maintainer: Matt Mackall <mpm@selenic.com>" ); |
42 | MODULE_DESCRIPTION("Console driver for network interfaces" ); |
43 | MODULE_LICENSE("GPL" ); |
44 | |
45 | #define MAX_PARAM_LENGTH 256 |
46 | #define MAX_USERDATA_ENTRY_LENGTH 256 |
47 | #define MAX_USERDATA_VALUE_LENGTH 200 |
48 | /* The number 3 comes from userdata entry format characters (' ', '=', '\n') */ |
49 | #define MAX_USERDATA_NAME_LENGTH (MAX_USERDATA_ENTRY_LENGTH - \ |
50 | MAX_USERDATA_VALUE_LENGTH - 3) |
51 | #define MAX_USERDATA_ITEMS 16 |
52 | #define MAX_PRINT_CHUNK 1000 |
53 | |
54 | static char config[MAX_PARAM_LENGTH]; |
55 | module_param_string(netconsole, config, MAX_PARAM_LENGTH, 0); |
56 | MODULE_PARM_DESC(netconsole, " netconsole=[src-port]@[src-ip]/[dev],[tgt-port]@<tgt-ip>/[tgt-macaddr]" ); |
57 | |
58 | static bool oops_only; |
59 | module_param(oops_only, bool, 0600); |
60 | MODULE_PARM_DESC(oops_only, "Only log oops messages" ); |
61 | |
62 | #define NETCONSOLE_PARAM_TARGET_PREFIX "cmdline" |
63 | |
64 | #ifndef MODULE |
65 | static int __init option_setup(char *opt) |
66 | { |
67 | strscpy(config, opt, MAX_PARAM_LENGTH); |
68 | return 1; |
69 | } |
70 | __setup("netconsole=" , option_setup); |
71 | #endif /* MODULE */ |
72 | |
73 | /* Linked list of all configured targets */ |
74 | static LIST_HEAD(target_list); |
75 | |
76 | /* This needs to be a spinlock because write_msg() cannot sleep */ |
77 | static DEFINE_SPINLOCK(target_list_lock); |
78 | |
79 | /* |
80 | * Console driver for extended netconsoles. Registered on the first use to |
81 | * avoid unnecessarily enabling ext message formatting. |
82 | */ |
83 | static struct console netconsole_ext; |
84 | |
85 | /** |
86 | * struct netconsole_target - Represents a configured netconsole target. |
87 | * @list: Links this target into the target_list. |
88 | * @group: Links us into the configfs subsystem hierarchy. |
89 | * @userdata_group: Links to the userdata configfs hierarchy |
90 | * @userdata_complete: Cached, formatted string of append |
91 | * @userdata_length: String length of userdata_complete |
92 | * @enabled: On / off knob to enable / disable target. |
93 | * Visible from userspace (read-write). |
94 | * We maintain a strict 1:1 correspondence between this and |
95 | * whether the corresponding netpoll is active or inactive. |
96 | * Also, other parameters of a target may be modified at |
97 | * runtime only when it is disabled (enabled == 0). |
98 | * @extended: Denotes whether console is extended or not. |
99 | * @release: Denotes whether kernel release version should be prepended |
100 | * to the message. Depends on extended console. |
101 | * @np: The netpoll structure for this target. |
102 | * Contains the other userspace visible parameters: |
103 | * dev_name (read-write) |
104 | * local_port (read-write) |
105 | * remote_port (read-write) |
106 | * local_ip (read-write) |
107 | * remote_ip (read-write) |
108 | * local_mac (read-only) |
109 | * remote_mac (read-write) |
110 | */ |
111 | struct netconsole_target { |
112 | struct list_head list; |
113 | #ifdef CONFIG_NETCONSOLE_DYNAMIC |
114 | struct config_group group; |
115 | struct config_group userdata_group; |
116 | char userdata_complete[MAX_USERDATA_ENTRY_LENGTH * MAX_USERDATA_ITEMS]; |
117 | size_t userdata_length; |
118 | #endif |
119 | bool enabled; |
120 | bool extended; |
121 | bool release; |
122 | struct netpoll np; |
123 | }; |
124 | |
125 | #ifdef CONFIG_NETCONSOLE_DYNAMIC |
126 | |
127 | static struct configfs_subsystem netconsole_subsys; |
128 | static DEFINE_MUTEX(dynamic_netconsole_mutex); |
129 | |
130 | static int __init dynamic_netconsole_init(void) |
131 | { |
132 | config_group_init(group: &netconsole_subsys.su_group); |
133 | mutex_init(&netconsole_subsys.su_mutex); |
134 | return configfs_register_subsystem(subsys: &netconsole_subsys); |
135 | } |
136 | |
137 | static void __exit dynamic_netconsole_exit(void) |
138 | { |
139 | configfs_unregister_subsystem(subsys: &netconsole_subsys); |
140 | } |
141 | |
142 | /* |
143 | * Targets that were created by parsing the boot/module option string |
144 | * do not exist in the configfs hierarchy (and have NULL names) and will |
145 | * never go away, so make these a no-op for them. |
146 | */ |
147 | static void netconsole_target_get(struct netconsole_target *nt) |
148 | { |
149 | if (config_item_name(item: &nt->group.cg_item)) |
150 | config_group_get(group: &nt->group); |
151 | } |
152 | |
153 | static void netconsole_target_put(struct netconsole_target *nt) |
154 | { |
155 | if (config_item_name(item: &nt->group.cg_item)) |
156 | config_group_put(group: &nt->group); |
157 | } |
158 | |
159 | #else /* !CONFIG_NETCONSOLE_DYNAMIC */ |
160 | |
161 | static int __init dynamic_netconsole_init(void) |
162 | { |
163 | return 0; |
164 | } |
165 | |
166 | static void __exit dynamic_netconsole_exit(void) |
167 | { |
168 | } |
169 | |
170 | /* |
171 | * No danger of targets going away from under us when dynamic |
172 | * reconfigurability is off. |
173 | */ |
174 | static void netconsole_target_get(struct netconsole_target *nt) |
175 | { |
176 | } |
177 | |
178 | static void netconsole_target_put(struct netconsole_target *nt) |
179 | { |
180 | } |
181 | |
182 | static void populate_configfs_item(struct netconsole_target *nt, |
183 | int cmdline_count) |
184 | { |
185 | } |
186 | #endif /* CONFIG_NETCONSOLE_DYNAMIC */ |
187 | |
188 | /* Allocate and initialize with defaults. |
189 | * Note that these targets get their config_item fields zeroed-out. |
190 | */ |
191 | static struct netconsole_target *alloc_and_init(void) |
192 | { |
193 | struct netconsole_target *nt; |
194 | |
195 | nt = kzalloc(size: sizeof(*nt), GFP_KERNEL); |
196 | if (!nt) |
197 | return nt; |
198 | |
199 | if (IS_ENABLED(CONFIG_NETCONSOLE_EXTENDED_LOG)) |
200 | nt->extended = true; |
201 | if (IS_ENABLED(CONFIG_NETCONSOLE_PREPEND_RELEASE)) |
202 | nt->release = true; |
203 | |
204 | nt->np.name = "netconsole" ; |
205 | strscpy(nt->np.dev_name, "eth0" , IFNAMSIZ); |
206 | nt->np.local_port = 6665; |
207 | nt->np.remote_port = 6666; |
208 | eth_broadcast_addr(addr: nt->np.remote_mac); |
209 | |
210 | return nt; |
211 | } |
212 | |
213 | #ifdef CONFIG_NETCONSOLE_DYNAMIC |
214 | |
215 | /* |
216 | * Our subsystem hierarchy is: |
217 | * |
218 | * /sys/kernel/config/netconsole/ |
219 | * | |
220 | * <target>/ |
221 | * | enabled |
222 | * | release |
223 | * | dev_name |
224 | * | local_port |
225 | * | remote_port |
226 | * | local_ip |
227 | * | remote_ip |
228 | * | local_mac |
229 | * | remote_mac |
230 | * | userdata/ |
231 | * | <key>/ |
232 | * | value |
233 | * | ... |
234 | * | |
235 | * <target>/... |
236 | */ |
237 | |
238 | static struct netconsole_target *to_target(struct config_item *item) |
239 | { |
240 | struct config_group *cfg_group; |
241 | |
242 | cfg_group = to_config_group(item); |
243 | if (!cfg_group) |
244 | return NULL; |
245 | return container_of(to_config_group(item), |
246 | struct netconsole_target, group); |
247 | } |
248 | |
249 | /* Get rid of possible trailing newline, returning the new length */ |
250 | static void trim_newline(char *s, size_t maxlen) |
251 | { |
252 | size_t len; |
253 | |
254 | len = strnlen(p: s, maxlen); |
255 | if (s[len - 1] == '\n') |
256 | s[len - 1] = '\0'; |
257 | } |
258 | |
259 | /* |
260 | * Attribute operations for netconsole_target. |
261 | */ |
262 | |
263 | static ssize_t enabled_show(struct config_item *item, char *buf) |
264 | { |
265 | return sysfs_emit(buf, fmt: "%d\n" , to_target(item)->enabled); |
266 | } |
267 | |
268 | static ssize_t extended_show(struct config_item *item, char *buf) |
269 | { |
270 | return sysfs_emit(buf, fmt: "%d\n" , to_target(item)->extended); |
271 | } |
272 | |
273 | static ssize_t release_show(struct config_item *item, char *buf) |
274 | { |
275 | return sysfs_emit(buf, fmt: "%d\n" , to_target(item)->release); |
276 | } |
277 | |
278 | static ssize_t dev_name_show(struct config_item *item, char *buf) |
279 | { |
280 | return sysfs_emit(buf, fmt: "%s\n" , to_target(item)->np.dev_name); |
281 | } |
282 | |
283 | static ssize_t local_port_show(struct config_item *item, char *buf) |
284 | { |
285 | return sysfs_emit(buf, fmt: "%d\n" , to_target(item)->np.local_port); |
286 | } |
287 | |
288 | static ssize_t remote_port_show(struct config_item *item, char *buf) |
289 | { |
290 | return sysfs_emit(buf, fmt: "%d\n" , to_target(item)->np.remote_port); |
291 | } |
292 | |
293 | static ssize_t local_ip_show(struct config_item *item, char *buf) |
294 | { |
295 | struct netconsole_target *nt = to_target(item); |
296 | |
297 | if (nt->np.ipv6) |
298 | return sysfs_emit(buf, fmt: "%pI6c\n" , &nt->np.local_ip.in6); |
299 | else |
300 | return sysfs_emit(buf, fmt: "%pI4\n" , &nt->np.local_ip); |
301 | } |
302 | |
303 | static ssize_t remote_ip_show(struct config_item *item, char *buf) |
304 | { |
305 | struct netconsole_target *nt = to_target(item); |
306 | |
307 | if (nt->np.ipv6) |
308 | return sysfs_emit(buf, fmt: "%pI6c\n" , &nt->np.remote_ip.in6); |
309 | else |
310 | return sysfs_emit(buf, fmt: "%pI4\n" , &nt->np.remote_ip); |
311 | } |
312 | |
313 | static ssize_t local_mac_show(struct config_item *item, char *buf) |
314 | { |
315 | struct net_device *dev = to_target(item)->np.dev; |
316 | static const u8 bcast[ETH_ALEN] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; |
317 | |
318 | return sysfs_emit(buf, fmt: "%pM\n" , dev ? dev->dev_addr : bcast); |
319 | } |
320 | |
321 | static ssize_t remote_mac_show(struct config_item *item, char *buf) |
322 | { |
323 | return sysfs_emit(buf, fmt: "%pM\n" , to_target(item)->np.remote_mac); |
324 | } |
325 | |
326 | /* |
327 | * This one is special -- targets created through the configfs interface |
328 | * are not enabled (and the corresponding netpoll activated) by default. |
329 | * The user is expected to set the desired parameters first (which |
330 | * would enable him to dynamically add new netpoll targets for new |
331 | * network interfaces as and when they come up). |
332 | */ |
333 | static ssize_t enabled_store(struct config_item *item, |
334 | const char *buf, size_t count) |
335 | { |
336 | struct netconsole_target *nt = to_target(item); |
337 | unsigned long flags; |
338 | bool enabled; |
339 | int err; |
340 | |
341 | mutex_lock(&dynamic_netconsole_mutex); |
342 | err = kstrtobool(s: buf, res: &enabled); |
343 | if (err) |
344 | goto out_unlock; |
345 | |
346 | err = -EINVAL; |
347 | if ((bool)enabled == nt->enabled) { |
348 | pr_info("network logging has already %s\n" , |
349 | nt->enabled ? "started" : "stopped" ); |
350 | goto out_unlock; |
351 | } |
352 | |
353 | if (enabled) { /* true */ |
354 | if (nt->release && !nt->extended) { |
355 | pr_err("Not enabling netconsole. Release feature requires extended log message" ); |
356 | goto out_unlock; |
357 | } |
358 | |
359 | if (nt->extended && !console_is_registered(con: &netconsole_ext)) |
360 | register_console(&netconsole_ext); |
361 | |
362 | /* |
363 | * Skip netpoll_parse_options() -- all the attributes are |
364 | * already configured via configfs. Just print them out. |
365 | */ |
366 | netpoll_print_options(np: &nt->np); |
367 | |
368 | err = netpoll_setup(np: &nt->np); |
369 | if (err) |
370 | goto out_unlock; |
371 | |
372 | pr_info("network logging started\n" ); |
373 | } else { /* false */ |
374 | /* We need to disable the netconsole before cleaning it up |
375 | * otherwise we might end up in write_msg() with |
376 | * nt->np.dev == NULL and nt->enabled == true |
377 | */ |
378 | spin_lock_irqsave(&target_list_lock, flags); |
379 | nt->enabled = false; |
380 | spin_unlock_irqrestore(lock: &target_list_lock, flags); |
381 | netpoll_cleanup(np: &nt->np); |
382 | } |
383 | |
384 | nt->enabled = enabled; |
385 | |
386 | mutex_unlock(lock: &dynamic_netconsole_mutex); |
387 | return strnlen(p: buf, maxlen: count); |
388 | out_unlock: |
389 | mutex_unlock(lock: &dynamic_netconsole_mutex); |
390 | return err; |
391 | } |
392 | |
393 | static ssize_t release_store(struct config_item *item, const char *buf, |
394 | size_t count) |
395 | { |
396 | struct netconsole_target *nt = to_target(item); |
397 | bool release; |
398 | int err; |
399 | |
400 | mutex_lock(&dynamic_netconsole_mutex); |
401 | if (nt->enabled) { |
402 | pr_err("target (%s) is enabled, disable to update parameters\n" , |
403 | config_item_name(&nt->group.cg_item)); |
404 | err = -EINVAL; |
405 | goto out_unlock; |
406 | } |
407 | |
408 | err = kstrtobool(s: buf, res: &release); |
409 | if (err) |
410 | goto out_unlock; |
411 | |
412 | nt->release = release; |
413 | |
414 | mutex_unlock(lock: &dynamic_netconsole_mutex); |
415 | return strnlen(p: buf, maxlen: count); |
416 | out_unlock: |
417 | mutex_unlock(lock: &dynamic_netconsole_mutex); |
418 | return err; |
419 | } |
420 | |
421 | static ssize_t extended_store(struct config_item *item, const char *buf, |
422 | size_t count) |
423 | { |
424 | struct netconsole_target *nt = to_target(item); |
425 | bool extended; |
426 | int err; |
427 | |
428 | mutex_lock(&dynamic_netconsole_mutex); |
429 | if (nt->enabled) { |
430 | pr_err("target (%s) is enabled, disable to update parameters\n" , |
431 | config_item_name(&nt->group.cg_item)); |
432 | err = -EINVAL; |
433 | goto out_unlock; |
434 | } |
435 | |
436 | err = kstrtobool(s: buf, res: &extended); |
437 | if (err) |
438 | goto out_unlock; |
439 | |
440 | nt->extended = extended; |
441 | |
442 | mutex_unlock(lock: &dynamic_netconsole_mutex); |
443 | return strnlen(p: buf, maxlen: count); |
444 | out_unlock: |
445 | mutex_unlock(lock: &dynamic_netconsole_mutex); |
446 | return err; |
447 | } |
448 | |
449 | static ssize_t dev_name_store(struct config_item *item, const char *buf, |
450 | size_t count) |
451 | { |
452 | struct netconsole_target *nt = to_target(item); |
453 | |
454 | mutex_lock(&dynamic_netconsole_mutex); |
455 | if (nt->enabled) { |
456 | pr_err("target (%s) is enabled, disable to update parameters\n" , |
457 | config_item_name(&nt->group.cg_item)); |
458 | mutex_unlock(lock: &dynamic_netconsole_mutex); |
459 | return -EINVAL; |
460 | } |
461 | |
462 | strscpy(nt->np.dev_name, buf, IFNAMSIZ); |
463 | trim_newline(s: nt->np.dev_name, IFNAMSIZ); |
464 | |
465 | mutex_unlock(lock: &dynamic_netconsole_mutex); |
466 | return strnlen(p: buf, maxlen: count); |
467 | } |
468 | |
469 | static ssize_t local_port_store(struct config_item *item, const char *buf, |
470 | size_t count) |
471 | { |
472 | struct netconsole_target *nt = to_target(item); |
473 | int rv = -EINVAL; |
474 | |
475 | mutex_lock(&dynamic_netconsole_mutex); |
476 | if (nt->enabled) { |
477 | pr_err("target (%s) is enabled, disable to update parameters\n" , |
478 | config_item_name(&nt->group.cg_item)); |
479 | goto out_unlock; |
480 | } |
481 | |
482 | rv = kstrtou16(s: buf, base: 10, res: &nt->np.local_port); |
483 | if (rv < 0) |
484 | goto out_unlock; |
485 | mutex_unlock(lock: &dynamic_netconsole_mutex); |
486 | return strnlen(p: buf, maxlen: count); |
487 | out_unlock: |
488 | mutex_unlock(lock: &dynamic_netconsole_mutex); |
489 | return rv; |
490 | } |
491 | |
492 | static ssize_t remote_port_store(struct config_item *item, |
493 | const char *buf, size_t count) |
494 | { |
495 | struct netconsole_target *nt = to_target(item); |
496 | int rv = -EINVAL; |
497 | |
498 | mutex_lock(&dynamic_netconsole_mutex); |
499 | if (nt->enabled) { |
500 | pr_err("target (%s) is enabled, disable to update parameters\n" , |
501 | config_item_name(&nt->group.cg_item)); |
502 | goto out_unlock; |
503 | } |
504 | |
505 | rv = kstrtou16(s: buf, base: 10, res: &nt->np.remote_port); |
506 | if (rv < 0) |
507 | goto out_unlock; |
508 | mutex_unlock(lock: &dynamic_netconsole_mutex); |
509 | return strnlen(p: buf, maxlen: count); |
510 | out_unlock: |
511 | mutex_unlock(lock: &dynamic_netconsole_mutex); |
512 | return rv; |
513 | } |
514 | |
515 | static ssize_t local_ip_store(struct config_item *item, const char *buf, |
516 | size_t count) |
517 | { |
518 | struct netconsole_target *nt = to_target(item); |
519 | |
520 | mutex_lock(&dynamic_netconsole_mutex); |
521 | if (nt->enabled) { |
522 | pr_err("target (%s) is enabled, disable to update parameters\n" , |
523 | config_item_name(&nt->group.cg_item)); |
524 | goto out_unlock; |
525 | } |
526 | |
527 | if (strnchr(buf, count, ':')) { |
528 | const char *end; |
529 | |
530 | if (in6_pton(src: buf, srclen: count, dst: nt->np.local_ip.in6.s6_addr, delim: -1, end: &end) > 0) { |
531 | if (*end && *end != '\n') { |
532 | pr_err("invalid IPv6 address at: <%c>\n" , *end); |
533 | goto out_unlock; |
534 | } |
535 | nt->np.ipv6 = true; |
536 | } else |
537 | goto out_unlock; |
538 | } else { |
539 | if (!nt->np.ipv6) |
540 | nt->np.local_ip.ip = in_aton(str: buf); |
541 | else |
542 | goto out_unlock; |
543 | } |
544 | |
545 | mutex_unlock(lock: &dynamic_netconsole_mutex); |
546 | return strnlen(p: buf, maxlen: count); |
547 | out_unlock: |
548 | mutex_unlock(lock: &dynamic_netconsole_mutex); |
549 | return -EINVAL; |
550 | } |
551 | |
552 | static ssize_t remote_ip_store(struct config_item *item, const char *buf, |
553 | size_t count) |
554 | { |
555 | struct netconsole_target *nt = to_target(item); |
556 | |
557 | mutex_lock(&dynamic_netconsole_mutex); |
558 | if (nt->enabled) { |
559 | pr_err("target (%s) is enabled, disable to update parameters\n" , |
560 | config_item_name(&nt->group.cg_item)); |
561 | goto out_unlock; |
562 | } |
563 | |
564 | if (strnchr(buf, count, ':')) { |
565 | const char *end; |
566 | |
567 | if (in6_pton(src: buf, srclen: count, dst: nt->np.remote_ip.in6.s6_addr, delim: -1, end: &end) > 0) { |
568 | if (*end && *end != '\n') { |
569 | pr_err("invalid IPv6 address at: <%c>\n" , *end); |
570 | goto out_unlock; |
571 | } |
572 | nt->np.ipv6 = true; |
573 | } else |
574 | goto out_unlock; |
575 | } else { |
576 | if (!nt->np.ipv6) |
577 | nt->np.remote_ip.ip = in_aton(str: buf); |
578 | else |
579 | goto out_unlock; |
580 | } |
581 | |
582 | mutex_unlock(lock: &dynamic_netconsole_mutex); |
583 | return strnlen(p: buf, maxlen: count); |
584 | out_unlock: |
585 | mutex_unlock(lock: &dynamic_netconsole_mutex); |
586 | return -EINVAL; |
587 | } |
588 | |
589 | static ssize_t remote_mac_store(struct config_item *item, const char *buf, |
590 | size_t count) |
591 | { |
592 | struct netconsole_target *nt = to_target(item); |
593 | u8 remote_mac[ETH_ALEN]; |
594 | |
595 | mutex_lock(&dynamic_netconsole_mutex); |
596 | if (nt->enabled) { |
597 | pr_err("target (%s) is enabled, disable to update parameters\n" , |
598 | config_item_name(&nt->group.cg_item)); |
599 | goto out_unlock; |
600 | } |
601 | |
602 | if (!mac_pton(s: buf, mac: remote_mac)) |
603 | goto out_unlock; |
604 | if (buf[3 * ETH_ALEN - 1] && buf[3 * ETH_ALEN - 1] != '\n') |
605 | goto out_unlock; |
606 | memcpy(nt->np.remote_mac, remote_mac, ETH_ALEN); |
607 | |
608 | mutex_unlock(lock: &dynamic_netconsole_mutex); |
609 | return strnlen(p: buf, maxlen: count); |
610 | out_unlock: |
611 | mutex_unlock(lock: &dynamic_netconsole_mutex); |
612 | return -EINVAL; |
613 | } |
614 | |
615 | struct userdatum { |
616 | struct config_item item; |
617 | char value[MAX_USERDATA_VALUE_LENGTH]; |
618 | }; |
619 | |
620 | static struct userdatum *to_userdatum(struct config_item *item) |
621 | { |
622 | return container_of(item, struct userdatum, item); |
623 | } |
624 | |
625 | struct userdata { |
626 | struct config_group group; |
627 | }; |
628 | |
629 | static struct userdata *to_userdata(struct config_item *item) |
630 | { |
631 | return container_of(to_config_group(item), struct userdata, group); |
632 | } |
633 | |
634 | static struct netconsole_target *userdata_to_target(struct userdata *ud) |
635 | { |
636 | struct config_group *netconsole_group; |
637 | |
638 | netconsole_group = to_config_group(item: ud->group.cg_item.ci_parent); |
639 | return to_target(item: &netconsole_group->cg_item); |
640 | } |
641 | |
642 | static ssize_t userdatum_value_show(struct config_item *item, char *buf) |
643 | { |
644 | return sysfs_emit(buf, fmt: "%s\n" , &(to_userdatum(item)->value[0])); |
645 | } |
646 | |
647 | static void update_userdata(struct netconsole_target *nt) |
648 | { |
649 | int complete_idx = 0, child_count = 0; |
650 | struct list_head *entry; |
651 | |
652 | /* Clear the current string in case the last userdatum was deleted */ |
653 | nt->userdata_length = 0; |
654 | nt->userdata_complete[0] = 0; |
655 | |
656 | list_for_each(entry, &nt->userdata_group.cg_children) { |
657 | struct userdatum *udm_item; |
658 | struct config_item *item; |
659 | |
660 | if (child_count >= MAX_USERDATA_ITEMS) |
661 | break; |
662 | child_count++; |
663 | |
664 | item = container_of(entry, struct config_item, ci_entry); |
665 | udm_item = to_userdatum(item); |
666 | |
667 | /* Skip userdata with no value set */ |
668 | if (strnlen(p: udm_item->value, MAX_USERDATA_VALUE_LENGTH) == 0) |
669 | continue; |
670 | |
671 | /* This doesn't overflow userdata_complete since it will write |
672 | * one entry length (1/MAX_USERDATA_ITEMS long), entry count is |
673 | * checked to not exceed MAX items with child_count above |
674 | */ |
675 | complete_idx += scnprintf(buf: &nt->userdata_complete[complete_idx], |
676 | MAX_USERDATA_ENTRY_LENGTH, fmt: " %s=%s\n" , |
677 | item->ci_name, udm_item->value); |
678 | } |
679 | nt->userdata_length = strnlen(p: nt->userdata_complete, |
680 | maxlen: sizeof(nt->userdata_complete)); |
681 | } |
682 | |
683 | static ssize_t userdatum_value_store(struct config_item *item, const char *buf, |
684 | size_t count) |
685 | { |
686 | struct userdatum *udm = to_userdatum(item); |
687 | struct netconsole_target *nt; |
688 | struct userdata *ud; |
689 | int ret; |
690 | |
691 | if (count > MAX_USERDATA_VALUE_LENGTH) |
692 | return -EMSGSIZE; |
693 | |
694 | mutex_lock(&dynamic_netconsole_mutex); |
695 | |
696 | ret = strscpy(udm->value, buf, sizeof(udm->value)); |
697 | if (ret < 0) |
698 | goto out_unlock; |
699 | trim_newline(s: udm->value, maxlen: sizeof(udm->value)); |
700 | |
701 | ud = to_userdata(item: item->ci_parent); |
702 | nt = userdata_to_target(ud); |
703 | update_userdata(nt); |
704 | |
705 | mutex_unlock(lock: &dynamic_netconsole_mutex); |
706 | return count; |
707 | out_unlock: |
708 | mutex_unlock(lock: &dynamic_netconsole_mutex); |
709 | return ret; |
710 | } |
711 | |
712 | CONFIGFS_ATTR(userdatum_, value); |
713 | |
714 | static struct configfs_attribute *userdatum_attrs[] = { |
715 | &userdatum_attr_value, |
716 | NULL, |
717 | }; |
718 | |
719 | static void userdatum_release(struct config_item *item) |
720 | { |
721 | kfree(objp: to_userdatum(item)); |
722 | } |
723 | |
724 | static struct configfs_item_operations userdatum_ops = { |
725 | .release = userdatum_release, |
726 | }; |
727 | |
728 | static const struct config_item_type userdatum_type = { |
729 | .ct_item_ops = &userdatum_ops, |
730 | .ct_attrs = userdatum_attrs, |
731 | .ct_owner = THIS_MODULE, |
732 | }; |
733 | |
734 | static struct config_item *userdatum_make_item(struct config_group *group, |
735 | const char *name) |
736 | { |
737 | struct netconsole_target *nt; |
738 | struct userdatum *udm; |
739 | struct userdata *ud; |
740 | size_t child_count; |
741 | |
742 | if (strlen(name) > MAX_USERDATA_NAME_LENGTH) |
743 | return ERR_PTR(error: -ENAMETOOLONG); |
744 | |
745 | ud = to_userdata(item: &group->cg_item); |
746 | nt = userdata_to_target(ud); |
747 | child_count = list_count_nodes(head: &nt->userdata_group.cg_children); |
748 | if (child_count >= MAX_USERDATA_ITEMS) |
749 | return ERR_PTR(error: -ENOSPC); |
750 | |
751 | udm = kzalloc(size: sizeof(*udm), GFP_KERNEL); |
752 | if (!udm) |
753 | return ERR_PTR(error: -ENOMEM); |
754 | |
755 | config_item_init_type_name(item: &udm->item, name, type: &userdatum_type); |
756 | return &udm->item; |
757 | } |
758 | |
759 | static void userdatum_drop(struct config_group *group, struct config_item *item) |
760 | { |
761 | struct netconsole_target *nt; |
762 | struct userdata *ud; |
763 | |
764 | ud = to_userdata(item: &group->cg_item); |
765 | nt = userdata_to_target(ud); |
766 | |
767 | mutex_lock(&dynamic_netconsole_mutex); |
768 | update_userdata(nt); |
769 | config_item_put(item); |
770 | mutex_unlock(lock: &dynamic_netconsole_mutex); |
771 | } |
772 | |
773 | static struct configfs_attribute *userdata_attrs[] = { |
774 | NULL, |
775 | }; |
776 | |
777 | static struct configfs_group_operations userdata_ops = { |
778 | .make_item = userdatum_make_item, |
779 | .drop_item = userdatum_drop, |
780 | }; |
781 | |
782 | static struct config_item_type userdata_type = { |
783 | .ct_item_ops = &userdatum_ops, |
784 | .ct_group_ops = &userdata_ops, |
785 | .ct_attrs = userdata_attrs, |
786 | .ct_owner = THIS_MODULE, |
787 | }; |
788 | |
789 | CONFIGFS_ATTR(, enabled); |
790 | CONFIGFS_ATTR(, extended); |
791 | CONFIGFS_ATTR(, dev_name); |
792 | CONFIGFS_ATTR(, local_port); |
793 | CONFIGFS_ATTR(, remote_port); |
794 | CONFIGFS_ATTR(, local_ip); |
795 | CONFIGFS_ATTR(, remote_ip); |
796 | CONFIGFS_ATTR_RO(, local_mac); |
797 | CONFIGFS_ATTR(, remote_mac); |
798 | CONFIGFS_ATTR(, release); |
799 | |
800 | static struct configfs_attribute *netconsole_target_attrs[] = { |
801 | &attr_enabled, |
802 | &attr_extended, |
803 | &attr_release, |
804 | &attr_dev_name, |
805 | &attr_local_port, |
806 | &attr_remote_port, |
807 | &attr_local_ip, |
808 | &attr_remote_ip, |
809 | &attr_local_mac, |
810 | &attr_remote_mac, |
811 | NULL, |
812 | }; |
813 | |
814 | /* |
815 | * Item operations and type for netconsole_target. |
816 | */ |
817 | |
818 | static void netconsole_target_release(struct config_item *item) |
819 | { |
820 | kfree(objp: to_target(item)); |
821 | } |
822 | |
823 | static struct configfs_item_operations netconsole_target_item_ops = { |
824 | .release = netconsole_target_release, |
825 | }; |
826 | |
827 | static const struct config_item_type netconsole_target_type = { |
828 | .ct_attrs = netconsole_target_attrs, |
829 | .ct_item_ops = &netconsole_target_item_ops, |
830 | .ct_owner = THIS_MODULE, |
831 | }; |
832 | |
833 | static void init_target_config_group(struct netconsole_target *nt, |
834 | const char *name) |
835 | { |
836 | config_group_init_type_name(group: &nt->group, name, type: &netconsole_target_type); |
837 | config_group_init_type_name(group: &nt->userdata_group, name: "userdata" , |
838 | type: &userdata_type); |
839 | configfs_add_default_group(new_group: &nt->userdata_group, group: &nt->group); |
840 | } |
841 | |
842 | static struct netconsole_target *find_cmdline_target(const char *name) |
843 | { |
844 | struct netconsole_target *nt, *ret = NULL; |
845 | unsigned long flags; |
846 | |
847 | spin_lock_irqsave(&target_list_lock, flags); |
848 | list_for_each_entry(nt, &target_list, list) { |
849 | if (!strcmp(nt->group.cg_item.ci_name, name)) { |
850 | ret = nt; |
851 | break; |
852 | } |
853 | } |
854 | spin_unlock_irqrestore(lock: &target_list_lock, flags); |
855 | |
856 | return ret; |
857 | } |
858 | |
859 | /* |
860 | * Group operations and type for netconsole_subsys. |
861 | */ |
862 | |
863 | static struct config_group *make_netconsole_target(struct config_group *group, |
864 | const char *name) |
865 | { |
866 | struct netconsole_target *nt; |
867 | unsigned long flags; |
868 | |
869 | /* Checking if a target by this name was created at boot time. If so, |
870 | * attach a configfs entry to that target. This enables dynamic |
871 | * control. |
872 | */ |
873 | if (!strncmp(name, NETCONSOLE_PARAM_TARGET_PREFIX, |
874 | strlen(NETCONSOLE_PARAM_TARGET_PREFIX))) { |
875 | nt = find_cmdline_target(name); |
876 | if (nt) { |
877 | init_target_config_group(nt, name); |
878 | return &nt->group; |
879 | } |
880 | } |
881 | |
882 | nt = alloc_and_init(); |
883 | if (!nt) |
884 | return ERR_PTR(error: -ENOMEM); |
885 | |
886 | /* Initialize the config_group member */ |
887 | init_target_config_group(nt, name); |
888 | |
889 | /* Adding, but it is disabled */ |
890 | spin_lock_irqsave(&target_list_lock, flags); |
891 | list_add(new: &nt->list, head: &target_list); |
892 | spin_unlock_irqrestore(lock: &target_list_lock, flags); |
893 | |
894 | return &nt->group; |
895 | } |
896 | |
897 | static void drop_netconsole_target(struct config_group *group, |
898 | struct config_item *item) |
899 | { |
900 | unsigned long flags; |
901 | struct netconsole_target *nt = to_target(item); |
902 | |
903 | spin_lock_irqsave(&target_list_lock, flags); |
904 | list_del(entry: &nt->list); |
905 | spin_unlock_irqrestore(lock: &target_list_lock, flags); |
906 | |
907 | /* |
908 | * The target may have never been enabled, or was manually disabled |
909 | * before being removed so netpoll may have already been cleaned up. |
910 | */ |
911 | if (nt->enabled) |
912 | netpoll_cleanup(np: &nt->np); |
913 | |
914 | config_item_put(&nt->group.cg_item); |
915 | } |
916 | |
917 | static struct configfs_group_operations netconsole_subsys_group_ops = { |
918 | .make_group = make_netconsole_target, |
919 | .drop_item = drop_netconsole_target, |
920 | }; |
921 | |
922 | static const struct config_item_type netconsole_subsys_type = { |
923 | .ct_group_ops = &netconsole_subsys_group_ops, |
924 | .ct_owner = THIS_MODULE, |
925 | }; |
926 | |
927 | /* The netconsole configfs subsystem */ |
928 | static struct configfs_subsystem netconsole_subsys = { |
929 | .su_group = { |
930 | .cg_item = { |
931 | .ci_namebuf = "netconsole" , |
932 | .ci_type = &netconsole_subsys_type, |
933 | }, |
934 | }, |
935 | }; |
936 | |
937 | static void populate_configfs_item(struct netconsole_target *nt, |
938 | int cmdline_count) |
939 | { |
940 | char target_name[16]; |
941 | |
942 | snprintf(buf: target_name, size: sizeof(target_name), fmt: "%s%d" , |
943 | NETCONSOLE_PARAM_TARGET_PREFIX, cmdline_count); |
944 | init_target_config_group(nt, name: target_name); |
945 | } |
946 | |
947 | #endif /* CONFIG_NETCONSOLE_DYNAMIC */ |
948 | |
949 | /* Handle network interface device notifications */ |
950 | static int netconsole_netdev_event(struct notifier_block *this, |
951 | unsigned long event, void *ptr) |
952 | { |
953 | unsigned long flags; |
954 | struct netconsole_target *nt; |
955 | struct net_device *dev = netdev_notifier_info_to_dev(info: ptr); |
956 | bool stopped = false; |
957 | |
958 | if (!(event == NETDEV_CHANGENAME || event == NETDEV_UNREGISTER || |
959 | event == NETDEV_RELEASE || event == NETDEV_JOIN)) |
960 | goto done; |
961 | |
962 | spin_lock_irqsave(&target_list_lock, flags); |
963 | restart: |
964 | list_for_each_entry(nt, &target_list, list) { |
965 | netconsole_target_get(nt); |
966 | if (nt->np.dev == dev) { |
967 | switch (event) { |
968 | case NETDEV_CHANGENAME: |
969 | strscpy(nt->np.dev_name, dev->name, IFNAMSIZ); |
970 | break; |
971 | case NETDEV_RELEASE: |
972 | case NETDEV_JOIN: |
973 | case NETDEV_UNREGISTER: |
974 | /* rtnl_lock already held |
975 | * we might sleep in __netpoll_cleanup() |
976 | */ |
977 | spin_unlock_irqrestore(lock: &target_list_lock, flags); |
978 | |
979 | __netpoll_cleanup(np: &nt->np); |
980 | |
981 | spin_lock_irqsave(&target_list_lock, flags); |
982 | netdev_put(dev: nt->np.dev, tracker: &nt->np.dev_tracker); |
983 | nt->np.dev = NULL; |
984 | nt->enabled = false; |
985 | stopped = true; |
986 | netconsole_target_put(nt); |
987 | goto restart; |
988 | } |
989 | } |
990 | netconsole_target_put(nt); |
991 | } |
992 | spin_unlock_irqrestore(lock: &target_list_lock, flags); |
993 | if (stopped) { |
994 | const char *msg = "had an event" ; |
995 | |
996 | switch (event) { |
997 | case NETDEV_UNREGISTER: |
998 | msg = "unregistered" ; |
999 | break; |
1000 | case NETDEV_RELEASE: |
1001 | msg = "released slaves" ; |
1002 | break; |
1003 | case NETDEV_JOIN: |
1004 | msg = "is joining a master device" ; |
1005 | break; |
1006 | } |
1007 | pr_info("network logging stopped on interface %s as it %s\n" , |
1008 | dev->name, msg); |
1009 | } |
1010 | |
1011 | done: |
1012 | return NOTIFY_DONE; |
1013 | } |
1014 | |
1015 | static struct notifier_block netconsole_netdev_notifier = { |
1016 | .notifier_call = netconsole_netdev_event, |
1017 | }; |
1018 | |
1019 | /** |
1020 | * send_ext_msg_udp - send extended log message to target |
1021 | * @nt: target to send message to |
1022 | * @msg: extended log message to send |
1023 | * @msg_len: length of message |
1024 | * |
1025 | * Transfer extended log @msg to @nt. If @msg is longer than |
1026 | * MAX_PRINT_CHUNK, it'll be split and transmitted in multiple chunks with |
1027 | * ncfrag header field added to identify them. |
1028 | */ |
1029 | static void send_ext_msg_udp(struct netconsole_target *nt, const char *msg, |
1030 | int msg_len) |
1031 | { |
1032 | static char buf[MAX_PRINT_CHUNK]; /* protected by target_list_lock */ |
1033 | const char *, *body; |
1034 | int offset = 0; |
1035 | int , body_len; |
1036 | const char *msg_ready = msg; |
1037 | const char *release; |
1038 | int release_len = 0; |
1039 | int userdata_len = 0; |
1040 | char *userdata = NULL; |
1041 | |
1042 | #ifdef CONFIG_NETCONSOLE_DYNAMIC |
1043 | userdata = nt->userdata_complete; |
1044 | userdata_len = nt->userdata_length; |
1045 | #endif |
1046 | |
1047 | if (nt->release) { |
1048 | release = init_utsname()->release; |
1049 | release_len = strlen(release) + 1; |
1050 | } |
1051 | |
1052 | if (msg_len + release_len + userdata_len <= MAX_PRINT_CHUNK) { |
1053 | /* No fragmentation needed */ |
1054 | if (nt->release) { |
1055 | scnprintf(buf, MAX_PRINT_CHUNK, fmt: "%s,%s" , release, msg); |
1056 | msg_len += release_len; |
1057 | } else { |
1058 | memcpy(buf, msg, msg_len); |
1059 | } |
1060 | |
1061 | if (userdata) |
1062 | msg_len += scnprintf(buf: &buf[msg_len], |
1063 | MAX_PRINT_CHUNK - msg_len, |
1064 | fmt: "%s" , userdata); |
1065 | |
1066 | msg_ready = buf; |
1067 | netpoll_send_udp(np: &nt->np, msg: msg_ready, len: msg_len); |
1068 | return; |
1069 | } |
1070 | |
1071 | /* need to insert extra header fields, detect header and body */ |
1072 | header = msg; |
1073 | body = memchr(p: msg, c: ';', size: msg_len); |
1074 | if (WARN_ON_ONCE(!body)) |
1075 | return; |
1076 | |
1077 | header_len = body - header; |
1078 | body_len = msg_len - header_len - 1; |
1079 | body++; |
1080 | |
1081 | /* |
1082 | * Transfer multiple chunks with the following extra header. |
1083 | * "ncfrag=<byte-offset>/<total-bytes>" |
1084 | */ |
1085 | if (nt->release) |
1086 | scnprintf(buf, MAX_PRINT_CHUNK, fmt: "%s," , release); |
1087 | memcpy(buf + release_len, header, header_len); |
1088 | header_len += release_len; |
1089 | |
1090 | while (offset < body_len + userdata_len) { |
1091 | int = header_len; |
1092 | int this_offset = 0; |
1093 | int this_chunk = 0; |
1094 | |
1095 | this_header += scnprintf(buf: buf + this_header, |
1096 | size: sizeof(buf) - this_header, |
1097 | fmt: ",ncfrag=%d/%d;" , offset, |
1098 | body_len + userdata_len); |
1099 | |
1100 | /* Not all body data has been written yet */ |
1101 | if (offset < body_len) { |
1102 | this_chunk = min(body_len - offset, |
1103 | MAX_PRINT_CHUNK - this_header); |
1104 | if (WARN_ON_ONCE(this_chunk <= 0)) |
1105 | return; |
1106 | memcpy(buf + this_header, body + offset, this_chunk); |
1107 | this_offset += this_chunk; |
1108 | } |
1109 | /* Body is fully written and there is pending userdata to write, |
1110 | * append userdata in this chunk |
1111 | */ |
1112 | if (offset + this_offset >= body_len && |
1113 | offset + this_offset < userdata_len + body_len) { |
1114 | int sent_userdata = (offset + this_offset) - body_len; |
1115 | int preceding_bytes = this_chunk + this_header; |
1116 | |
1117 | if (WARN_ON_ONCE(sent_userdata < 0)) |
1118 | return; |
1119 | |
1120 | this_chunk = min(userdata_len - sent_userdata, |
1121 | MAX_PRINT_CHUNK - preceding_bytes); |
1122 | if (WARN_ON_ONCE(this_chunk <= 0)) |
1123 | return; |
1124 | memcpy(buf + this_header + this_offset, |
1125 | userdata + sent_userdata, |
1126 | this_chunk); |
1127 | this_offset += this_chunk; |
1128 | } |
1129 | |
1130 | netpoll_send_udp(np: &nt->np, msg: buf, len: this_header + this_offset); |
1131 | offset += this_offset; |
1132 | } |
1133 | } |
1134 | |
1135 | static void write_ext_msg(struct console *con, const char *msg, |
1136 | unsigned int len) |
1137 | { |
1138 | struct netconsole_target *nt; |
1139 | unsigned long flags; |
1140 | |
1141 | if ((oops_only && !oops_in_progress) || list_empty(head: &target_list)) |
1142 | return; |
1143 | |
1144 | spin_lock_irqsave(&target_list_lock, flags); |
1145 | list_for_each_entry(nt, &target_list, list) |
1146 | if (nt->extended && nt->enabled && netif_running(dev: nt->np.dev)) |
1147 | send_ext_msg_udp(nt, msg, msg_len: len); |
1148 | spin_unlock_irqrestore(lock: &target_list_lock, flags); |
1149 | } |
1150 | |
1151 | static void write_msg(struct console *con, const char *msg, unsigned int len) |
1152 | { |
1153 | int frag, left; |
1154 | unsigned long flags; |
1155 | struct netconsole_target *nt; |
1156 | const char *tmp; |
1157 | |
1158 | if (oops_only && !oops_in_progress) |
1159 | return; |
1160 | /* Avoid taking lock and disabling interrupts unnecessarily */ |
1161 | if (list_empty(head: &target_list)) |
1162 | return; |
1163 | |
1164 | spin_lock_irqsave(&target_list_lock, flags); |
1165 | list_for_each_entry(nt, &target_list, list) { |
1166 | if (!nt->extended && nt->enabled && netif_running(dev: nt->np.dev)) { |
1167 | /* |
1168 | * We nest this inside the for-each-target loop above |
1169 | * so that we're able to get as much logging out to |
1170 | * at least one target if we die inside here, instead |
1171 | * of unnecessarily keeping all targets in lock-step. |
1172 | */ |
1173 | tmp = msg; |
1174 | for (left = len; left;) { |
1175 | frag = min(left, MAX_PRINT_CHUNK); |
1176 | netpoll_send_udp(np: &nt->np, msg: tmp, len: frag); |
1177 | tmp += frag; |
1178 | left -= frag; |
1179 | } |
1180 | } |
1181 | } |
1182 | spin_unlock_irqrestore(lock: &target_list_lock, flags); |
1183 | } |
1184 | |
1185 | /* Allocate new target (from boot/module param) and setup netpoll for it */ |
1186 | static struct netconsole_target *alloc_param_target(char *target_config, |
1187 | int cmdline_count) |
1188 | { |
1189 | struct netconsole_target *nt; |
1190 | int err; |
1191 | |
1192 | nt = alloc_and_init(); |
1193 | if (!nt) { |
1194 | err = -ENOMEM; |
1195 | goto fail; |
1196 | } |
1197 | |
1198 | if (*target_config == '+') { |
1199 | nt->extended = true; |
1200 | target_config++; |
1201 | } |
1202 | |
1203 | if (*target_config == 'r') { |
1204 | if (!nt->extended) { |
1205 | pr_err("Netconsole configuration error. Release feature requires extended log message" ); |
1206 | err = -EINVAL; |
1207 | goto fail; |
1208 | } |
1209 | nt->release = true; |
1210 | target_config++; |
1211 | } |
1212 | |
1213 | /* Parse parameters and setup netpoll */ |
1214 | err = netpoll_parse_options(np: &nt->np, opt: target_config); |
1215 | if (err) |
1216 | goto fail; |
1217 | |
1218 | err = netpoll_setup(np: &nt->np); |
1219 | if (err) |
1220 | goto fail; |
1221 | |
1222 | populate_configfs_item(nt, cmdline_count); |
1223 | nt->enabled = true; |
1224 | |
1225 | return nt; |
1226 | |
1227 | fail: |
1228 | kfree(objp: nt); |
1229 | return ERR_PTR(error: err); |
1230 | } |
1231 | |
1232 | /* Cleanup netpoll for given target (from boot/module param) and free it */ |
1233 | static void free_param_target(struct netconsole_target *nt) |
1234 | { |
1235 | netpoll_cleanup(np: &nt->np); |
1236 | kfree(objp: nt); |
1237 | } |
1238 | |
1239 | static struct console netconsole_ext = { |
1240 | .name = "netcon_ext" , |
1241 | .flags = CON_ENABLED | CON_EXTENDED, |
1242 | .write = write_ext_msg, |
1243 | }; |
1244 | |
1245 | static struct console netconsole = { |
1246 | .name = "netcon" , |
1247 | .flags = CON_ENABLED, |
1248 | .write = write_msg, |
1249 | }; |
1250 | |
1251 | static int __init init_netconsole(void) |
1252 | { |
1253 | int err; |
1254 | struct netconsole_target *nt, *tmp; |
1255 | unsigned int count = 0; |
1256 | bool extended = false; |
1257 | unsigned long flags; |
1258 | char *target_config; |
1259 | char *input = config; |
1260 | |
1261 | if (strnlen(p: input, MAX_PARAM_LENGTH)) { |
1262 | while ((target_config = strsep(&input, ";" ))) { |
1263 | nt = alloc_param_target(target_config, cmdline_count: count); |
1264 | if (IS_ERR(ptr: nt)) { |
1265 | err = PTR_ERR(ptr: nt); |
1266 | goto fail; |
1267 | } |
1268 | /* Dump existing printks when we register */ |
1269 | if (nt->extended) { |
1270 | extended = true; |
1271 | netconsole_ext.flags |= CON_PRINTBUFFER; |
1272 | } else { |
1273 | netconsole.flags |= CON_PRINTBUFFER; |
1274 | } |
1275 | |
1276 | spin_lock_irqsave(&target_list_lock, flags); |
1277 | list_add(new: &nt->list, head: &target_list); |
1278 | spin_unlock_irqrestore(lock: &target_list_lock, flags); |
1279 | count++; |
1280 | } |
1281 | } |
1282 | |
1283 | err = register_netdevice_notifier(nb: &netconsole_netdev_notifier); |
1284 | if (err) |
1285 | goto fail; |
1286 | |
1287 | err = dynamic_netconsole_init(); |
1288 | if (err) |
1289 | goto undonotifier; |
1290 | |
1291 | if (extended) |
1292 | register_console(&netconsole_ext); |
1293 | register_console(&netconsole); |
1294 | pr_info("network logging started\n" ); |
1295 | |
1296 | return err; |
1297 | |
1298 | undonotifier: |
1299 | unregister_netdevice_notifier(nb: &netconsole_netdev_notifier); |
1300 | |
1301 | fail: |
1302 | pr_err("cleaning up\n" ); |
1303 | |
1304 | /* |
1305 | * Remove all targets and destroy them (only targets created |
1306 | * from the boot/module option exist here). Skipping the list |
1307 | * lock is safe here, and netpoll_cleanup() will sleep. |
1308 | */ |
1309 | list_for_each_entry_safe(nt, tmp, &target_list, list) { |
1310 | list_del(entry: &nt->list); |
1311 | free_param_target(nt); |
1312 | } |
1313 | |
1314 | return err; |
1315 | } |
1316 | |
1317 | static void __exit cleanup_netconsole(void) |
1318 | { |
1319 | struct netconsole_target *nt, *tmp; |
1320 | |
1321 | if (console_is_registered(con: &netconsole_ext)) |
1322 | unregister_console(&netconsole_ext); |
1323 | unregister_console(&netconsole); |
1324 | dynamic_netconsole_exit(); |
1325 | unregister_netdevice_notifier(nb: &netconsole_netdev_notifier); |
1326 | |
1327 | /* |
1328 | * Targets created via configfs pin references on our module |
1329 | * and would first be rmdir(2)'ed from userspace. We reach |
1330 | * here only when they are already destroyed, and only those |
1331 | * created from the boot/module option are left, so remove and |
1332 | * destroy them. Skipping the list lock is safe here, and |
1333 | * netpoll_cleanup() will sleep. |
1334 | */ |
1335 | list_for_each_entry_safe(nt, tmp, &target_list, list) { |
1336 | list_del(entry: &nt->list); |
1337 | free_param_target(nt); |
1338 | } |
1339 | } |
1340 | |
1341 | /* |
1342 | * Use late_initcall to ensure netconsole is |
1343 | * initialized after network device driver if built-in. |
1344 | * |
1345 | * late_initcall() and module_init() are identical if built as module. |
1346 | */ |
1347 | late_initcall(init_netconsole); |
1348 | module_exit(cleanup_netconsole); |
1349 | |