mirror of
https://github.com/torvalds/linux.git
synced 2026-07-28 01:55:51 +02:00
Merge branch 'netconsole-userdata-append'
Matthew Wood says:
====================
netconsole: Add userdata append support
Add the ability to add custom userdata to every outbound netconsole message
as a collection of key/value pairs, allowing users to add metadata to every
netconsole message which can be used for for tagging, filtering, and
aggregating log messages.
In a previous patch series the ability to prepend the uname release was
added towards the goals above. This patch series builds on that
idea to allow any userdata, keyed by a user provided name, to be
included in netconsole messages.
If CONFIG_NETCONSOLE_DYNAMIC is enabled an additional userdata
directory will be presented in the netconsole configfs tree, allowing
the addition of userdata entries.
/sys/kernel/config/netconsole/
<target>/
enabled
release
dev_name
local_port
remote_port
local_ip
remote_ip
local_mac
remote_mac
userdata/
<key>/
value
<key>/
value
...
v1->v2:
* Updated netconsole_target docs, kdoc is now clean
v2->v3:
* Remove inline keyword from to_userdat* functions
* Break up some lines that exceeded 80 chars
* Replace typos and remove {} from single line if statement
* Remove unused variable
Testing for this series is as follows:
Build every patch without CONFIG_NETCONSOLE_DYNAMIC, and also built
with CONFIG_NETCONSOLE_DYNAMIC enabled for every patch after the config
option was added
Test Userdata configfs
# Adding userdata
cd /sys/kernel/config/netconsole/ && mkdir cmdline0 && cd cmdline0
mkdir userdata/release && echo hotfix1 > userdata/release/value
preview=$(for f in `ls userdata`; do echo $f=$(cat userdata/$f/value); done)
[[ "$preview" == $'release=hotfix1' ]] && echo pass || echo fail
mkdir userdata/testing && echo something > userdata/testing/value
preview=$(for f in `ls userdata`; do echo $f=$(cat userdata/$f/value); done)
[[ "$preview" == $'release=hotfix1\ntesting=something' ]] && echo pass || echo fail
#
# Removing Userdata
rmdir userdata/testing
preview=$(for f in `ls userdata`; do echo $f=$(cat userdata/$f/value); done)
[[ "$preview" == $'release=hotfix1' ]] && echo pass || echo fail
rmdir userdata/release
preview=$(for f in `ls userdata`; do echo $f=$(cat userdata/$f/value); done)
[[ "$preview" == $'' ]] && echo pass || echo fail
#
# Adding userdata key with too large of 6.7.0-rc8-virtme,12,481,17954104,-directory name [<54 chars]
mkdir userdata/testing12345678901234567890123456789012345678901234567890
[[ $? == 1 ]] && echo pass || echo fail
#
# Adding userdata value with too large of value [<200 chars]
mkdir userdata/testing
echo `for i in {1..201};do printf "%s" "v";done` > userdata/testing/value
[[ $? == 1 ]] && echo pass || echo fail
rmdir userdata/testing
- Output:
pass
pass
pass
pass
pass
mkdir: cannot create directory ‘cmdline0/userdata/testing12345678901234567890123456789012345678901234567890’: File name too long
pass
bash: echo: write error: Message too long
pass
Test netconsole messages (w/ msg fragmentation)
echo `for i in {1..996};do printf "%s" "v";done` > /dev/kmsg
- Output:
6.7.0-rc8-virtme,12,484,84321212,-,ncfrag=0/997;vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv6.7.0-rc8-virtme,12,484,84321212,-,ncfrag=952/997;vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
Test empty userdatum
cd /sys/kernel/config/netconsole/ && mkdir cmdline0
mkdir cmdline0/userdata/empty
echo test > /dev/kmsg
rmdir cmdline0/userdata/empty
- Output:
Test netconsole messages (w/o userdata fragmentation)
cd /sys/kernel/config/netconsole/ && mkdir cmdline0
mkdir cmdline0/userdata/release && echo hotfix1 > cmdline0/userdata/release/value
mkdir cmdline0/userdata/testing && echo something > cmdline0/userdata/testing/value
echo test > /dev/kmsg
rmdir cmdline0/userdata/release
rmdir cmdline0/userdata/testing
====================
Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
commit
20ad40187d
|
|
@ -15,6 +15,8 @@ Extended console support by Tejun Heo <tj@kernel.org>, May 1 2015
|
|||
|
||||
Release prepend support by Breno Leitao <leitao@debian.org>, Jul 7 2023
|
||||
|
||||
Userdata append support by Matthew Wood <thepacketgeek@gmail.com>, Jan 22 2024
|
||||
|
||||
Please send bug reports to Matt Mackall <mpm@selenic.com>
|
||||
Satyam Sharma <satyam.sharma@gmail.com>, and Cong Wang <xiyou.wangcong@gmail.com>
|
||||
|
||||
|
|
@ -171,6 +173,70 @@ You can modify these targets in runtime by creating the following targets::
|
|||
cat cmdline1/remote_ip
|
||||
10.0.0.3
|
||||
|
||||
Append User Data
|
||||
----------------
|
||||
|
||||
Custom user data can be appended to the end of messages with netconsole
|
||||
dynamic configuration enabled. User data entries can be modified without
|
||||
changing the "enabled" attribute of a target.
|
||||
|
||||
Directories (keys) under `userdata` are limited to 54 character length, and
|
||||
data in `userdata/<key>/value` are limited to 200 bytes::
|
||||
|
||||
cd /sys/kernel/config/netconsole && mkdir cmdline0
|
||||
cd cmdline0
|
||||
mkdir userdata/foo
|
||||
echo bar > userdata/foo/value
|
||||
mkdir userdata/qux
|
||||
echo baz > userdata/qux/value
|
||||
|
||||
Messages will now include this additional user data::
|
||||
|
||||
echo "This is a message" > /dev/kmsg
|
||||
|
||||
Sends::
|
||||
|
||||
12,607,22085407756,-;This is a message
|
||||
foo=bar
|
||||
qux=baz
|
||||
|
||||
Preview the userdata that will be appended with::
|
||||
|
||||
cd /sys/kernel/config/netconsole/cmdline0/userdata
|
||||
for f in `ls userdata`; do echo $f=$(cat userdata/$f/value); done
|
||||
|
||||
If a `userdata` entry is created but no data is written to the `value` file,
|
||||
the entry will be omitted from netconsole messages::
|
||||
|
||||
cd /sys/kernel/config/netconsole && mkdir cmdline0
|
||||
cd cmdline0
|
||||
mkdir userdata/foo
|
||||
echo bar > userdata/foo/value
|
||||
mkdir userdata/qux
|
||||
|
||||
The `qux` key is omitted since it has no value::
|
||||
|
||||
echo "This is a message" > /dev/kmsg
|
||||
12,607,22085407756,-;This is a message
|
||||
foo=bar
|
||||
|
||||
Delete `userdata` entries with `rmdir`::
|
||||
|
||||
rmdir /sys/kernel/config/netconsole/cmdline0/userdata/qux
|
||||
|
||||
.. warning::
|
||||
When writing strings to user data values, input is broken up per line in
|
||||
configfs store calls and this can cause confusing behavior::
|
||||
|
||||
mkdir userdata/testing
|
||||
printf "val1\nval2" > userdata/testing/value
|
||||
# userdata store value is called twice, first with "val1\n" then "val2"
|
||||
# so "val2" is stored, being the last value stored
|
||||
cat userdata/testing/value
|
||||
val2
|
||||
|
||||
It is recommended to not write user data values with newlines.
|
||||
|
||||
Extended console:
|
||||
=================
|
||||
|
||||
|
|
|
|||
|
|
@ -43,13 +43,17 @@ MODULE_DESCRIPTION("Console driver for network interfaces");
|
|||
MODULE_LICENSE("GPL");
|
||||
|
||||
#define MAX_PARAM_LENGTH 256
|
||||
#define MAX_USERDATA_NAME_LENGTH 54
|
||||
#define MAX_USERDATA_VALUE_LENGTH 200
|
||||
#define MAX_USERDATA_ENTRY_LENGTH 256
|
||||
#define MAX_USERDATA_ITEMS 16
|
||||
#define MAX_PRINT_CHUNK 1000
|
||||
|
||||
static char config[MAX_PARAM_LENGTH];
|
||||
module_param_string(netconsole, config, MAX_PARAM_LENGTH, 0);
|
||||
MODULE_PARM_DESC(netconsole, " netconsole=[src-port]@[src-ip]/[dev],[tgt-port]@<tgt-ip>/[tgt-macaddr]");
|
||||
|
||||
static bool oops_only = false;
|
||||
static bool oops_only;
|
||||
module_param(oops_only, bool, 0600);
|
||||
MODULE_PARM_DESC(oops_only, "Only log oops messages");
|
||||
|
||||
|
|
@ -79,7 +83,10 @@ static struct console netconsole_ext;
|
|||
/**
|
||||
* struct netconsole_target - Represents a configured netconsole target.
|
||||
* @list: Links this target into the target_list.
|
||||
* @item: Links us into the configfs subsystem hierarchy.
|
||||
* @group: Links us into the configfs subsystem hierarchy.
|
||||
* @userdata_group: Links to the userdata configfs hierarchy
|
||||
* @userdata_complete: Cached, formatted string of append
|
||||
* @userdata_length: String length of userdata_complete
|
||||
* @enabled: On / off knob to enable / disable target.
|
||||
* Visible from userspace (read-write).
|
||||
* We maintain a strict 1:1 correspondence between this and
|
||||
|
|
@ -102,7 +109,10 @@ static struct console netconsole_ext;
|
|||
struct netconsole_target {
|
||||
struct list_head list;
|
||||
#ifdef CONFIG_NETCONSOLE_DYNAMIC
|
||||
struct config_item item;
|
||||
struct config_group group;
|
||||
struct config_group userdata_group;
|
||||
char userdata_complete[MAX_USERDATA_ENTRY_LENGTH * MAX_USERDATA_ITEMS];
|
||||
size_t userdata_length;
|
||||
#endif
|
||||
bool enabled;
|
||||
bool extended;
|
||||
|
|
@ -134,14 +144,14 @@ static void __exit dynamic_netconsole_exit(void)
|
|||
*/
|
||||
static void netconsole_target_get(struct netconsole_target *nt)
|
||||
{
|
||||
if (config_item_name(&nt->item))
|
||||
config_item_get(&nt->item);
|
||||
if (config_item_name(&nt->group.cg_item))
|
||||
config_group_get(&nt->group);
|
||||
}
|
||||
|
||||
static void netconsole_target_put(struct netconsole_target *nt)
|
||||
{
|
||||
if (config_item_name(&nt->item))
|
||||
config_item_put(&nt->item);
|
||||
if (config_item_name(&nt->group.cg_item))
|
||||
config_group_put(&nt->group);
|
||||
}
|
||||
|
||||
#else /* !CONFIG_NETCONSOLE_DYNAMIC */
|
||||
|
|
@ -215,15 +225,33 @@ static struct netconsole_target *alloc_and_init(void)
|
|||
* | remote_ip
|
||||
* | local_mac
|
||||
* | remote_mac
|
||||
* | userdata/
|
||||
* | <key>/
|
||||
* | value
|
||||
* | ...
|
||||
* |
|
||||
* <target>/...
|
||||
*/
|
||||
|
||||
static struct netconsole_target *to_target(struct config_item *item)
|
||||
{
|
||||
return item ?
|
||||
container_of(item, struct netconsole_target, item) :
|
||||
NULL;
|
||||
struct config_group *cfg_group;
|
||||
|
||||
cfg_group = to_config_group(item);
|
||||
if (!cfg_group)
|
||||
return NULL;
|
||||
return container_of(to_config_group(item),
|
||||
struct netconsole_target, group);
|
||||
}
|
||||
|
||||
/* Get rid of possible trailing newline, returning the new length */
|
||||
static void trim_newline(char *s, size_t maxlen)
|
||||
{
|
||||
size_t len;
|
||||
|
||||
len = strnlen(s, maxlen);
|
||||
if (s[len - 1] == '\n')
|
||||
s[len - 1] = '\0';
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -370,7 +398,7 @@ static ssize_t release_store(struct config_item *item, const char *buf,
|
|||
mutex_lock(&dynamic_netconsole_mutex);
|
||||
if (nt->enabled) {
|
||||
pr_err("target (%s) is enabled, disable to update parameters\n",
|
||||
config_item_name(&nt->item));
|
||||
config_item_name(&nt->group.cg_item));
|
||||
err = -EINVAL;
|
||||
goto out_unlock;
|
||||
}
|
||||
|
|
@ -398,7 +426,7 @@ static ssize_t extended_store(struct config_item *item, const char *buf,
|
|||
mutex_lock(&dynamic_netconsole_mutex);
|
||||
if (nt->enabled) {
|
||||
pr_err("target (%s) is enabled, disable to update parameters\n",
|
||||
config_item_name(&nt->item));
|
||||
config_item_name(&nt->group.cg_item));
|
||||
err = -EINVAL;
|
||||
goto out_unlock;
|
||||
}
|
||||
|
|
@ -420,22 +448,17 @@ static ssize_t dev_name_store(struct config_item *item, const char *buf,
|
|||
size_t count)
|
||||
{
|
||||
struct netconsole_target *nt = to_target(item);
|
||||
size_t len;
|
||||
|
||||
mutex_lock(&dynamic_netconsole_mutex);
|
||||
if (nt->enabled) {
|
||||
pr_err("target (%s) is enabled, disable to update parameters\n",
|
||||
config_item_name(&nt->item));
|
||||
config_item_name(&nt->group.cg_item));
|
||||
mutex_unlock(&dynamic_netconsole_mutex);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
strscpy(nt->np.dev_name, buf, IFNAMSIZ);
|
||||
|
||||
/* Get rid of possible trailing newline from echo(1) */
|
||||
len = strnlen(nt->np.dev_name, IFNAMSIZ);
|
||||
if (nt->np.dev_name[len - 1] == '\n')
|
||||
nt->np.dev_name[len - 1] = '\0';
|
||||
trim_newline(nt->np.dev_name, IFNAMSIZ);
|
||||
|
||||
mutex_unlock(&dynamic_netconsole_mutex);
|
||||
return strnlen(buf, count);
|
||||
|
|
@ -450,7 +473,7 @@ static ssize_t local_port_store(struct config_item *item, const char *buf,
|
|||
mutex_lock(&dynamic_netconsole_mutex);
|
||||
if (nt->enabled) {
|
||||
pr_err("target (%s) is enabled, disable to update parameters\n",
|
||||
config_item_name(&nt->item));
|
||||
config_item_name(&nt->group.cg_item));
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
|
|
@ -473,7 +496,7 @@ static ssize_t remote_port_store(struct config_item *item,
|
|||
mutex_lock(&dynamic_netconsole_mutex);
|
||||
if (nt->enabled) {
|
||||
pr_err("target (%s) is enabled, disable to update parameters\n",
|
||||
config_item_name(&nt->item));
|
||||
config_item_name(&nt->group.cg_item));
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
|
|
@ -495,12 +518,13 @@ static ssize_t local_ip_store(struct config_item *item, const char *buf,
|
|||
mutex_lock(&dynamic_netconsole_mutex);
|
||||
if (nt->enabled) {
|
||||
pr_err("target (%s) is enabled, disable to update parameters\n",
|
||||
config_item_name(&nt->item));
|
||||
config_item_name(&nt->group.cg_item));
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
if (strnchr(buf, count, ':')) {
|
||||
const char *end;
|
||||
|
||||
if (in6_pton(buf, count, nt->np.local_ip.in6.s6_addr, -1, &end) > 0) {
|
||||
if (*end && *end != '\n') {
|
||||
pr_err("invalid IPv6 address at: <%c>\n", *end);
|
||||
|
|
@ -510,9 +534,9 @@ static ssize_t local_ip_store(struct config_item *item, const char *buf,
|
|||
} else
|
||||
goto out_unlock;
|
||||
} else {
|
||||
if (!nt->np.ipv6) {
|
||||
if (!nt->np.ipv6)
|
||||
nt->np.local_ip.ip = in_aton(buf);
|
||||
} else
|
||||
else
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
|
|
@ -531,12 +555,13 @@ static ssize_t remote_ip_store(struct config_item *item, const char *buf,
|
|||
mutex_lock(&dynamic_netconsole_mutex);
|
||||
if (nt->enabled) {
|
||||
pr_err("target (%s) is enabled, disable to update parameters\n",
|
||||
config_item_name(&nt->item));
|
||||
config_item_name(&nt->group.cg_item));
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
if (strnchr(buf, count, ':')) {
|
||||
const char *end;
|
||||
|
||||
if (in6_pton(buf, count, nt->np.remote_ip.in6.s6_addr, -1, &end) > 0) {
|
||||
if (*end && *end != '\n') {
|
||||
pr_err("invalid IPv6 address at: <%c>\n", *end);
|
||||
|
|
@ -546,9 +571,9 @@ static ssize_t remote_ip_store(struct config_item *item, const char *buf,
|
|||
} else
|
||||
goto out_unlock;
|
||||
} else {
|
||||
if (!nt->np.ipv6) {
|
||||
if (!nt->np.ipv6)
|
||||
nt->np.remote_ip.ip = in_aton(buf);
|
||||
} else
|
||||
else
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
|
|
@ -568,7 +593,7 @@ static ssize_t remote_mac_store(struct config_item *item, const char *buf,
|
|||
mutex_lock(&dynamic_netconsole_mutex);
|
||||
if (nt->enabled) {
|
||||
pr_err("target (%s) is enabled, disable to update parameters\n",
|
||||
config_item_name(&nt->item));
|
||||
config_item_name(&nt->group.cg_item));
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
|
|
@ -585,6 +610,180 @@ static ssize_t remote_mac_store(struct config_item *item, const char *buf,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
struct userdatum {
|
||||
struct config_item item;
|
||||
char value[MAX_USERDATA_VALUE_LENGTH];
|
||||
};
|
||||
|
||||
static struct userdatum *to_userdatum(struct config_item *item)
|
||||
{
|
||||
return container_of(item, struct userdatum, item);
|
||||
}
|
||||
|
||||
struct userdata {
|
||||
struct config_group group;
|
||||
};
|
||||
|
||||
static struct userdata *to_userdata(struct config_item *item)
|
||||
{
|
||||
return container_of(to_config_group(item), struct userdata, group);
|
||||
}
|
||||
|
||||
static struct netconsole_target *userdata_to_target(struct userdata *ud)
|
||||
{
|
||||
struct config_group *netconsole_group;
|
||||
|
||||
netconsole_group = to_config_group(ud->group.cg_item.ci_parent);
|
||||
return to_target(&netconsole_group->cg_item);
|
||||
}
|
||||
|
||||
static ssize_t userdatum_value_show(struct config_item *item, char *buf)
|
||||
{
|
||||
return sysfs_emit(buf, "%s\n", &(to_userdatum(item)->value[0]));
|
||||
}
|
||||
|
||||
static void update_userdata(struct netconsole_target *nt)
|
||||
{
|
||||
int complete_idx = 0, child_count = 0;
|
||||
struct list_head *entry;
|
||||
|
||||
/* Clear the current string in case the last userdatum was deleted */
|
||||
nt->userdata_length = 0;
|
||||
nt->userdata_complete[0] = 0;
|
||||
|
||||
list_for_each(entry, &nt->userdata_group.cg_children) {
|
||||
struct userdatum *udm_item;
|
||||
struct config_item *item;
|
||||
|
||||
if (child_count >= MAX_USERDATA_ITEMS)
|
||||
break;
|
||||
child_count++;
|
||||
|
||||
item = container_of(entry, struct config_item, ci_entry);
|
||||
udm_item = to_userdatum(item);
|
||||
|
||||
/* Skip userdata with no value set */
|
||||
if (strnlen(udm_item->value, MAX_USERDATA_VALUE_LENGTH) == 0)
|
||||
continue;
|
||||
|
||||
/* This doesn't overflow userdata_complete since it will write
|
||||
* one entry length (1/MAX_USERDATA_ITEMS long), entry count is
|
||||
* checked to not exceed MAX items with child_count above
|
||||
*/
|
||||
complete_idx += scnprintf(&nt->userdata_complete[complete_idx],
|
||||
MAX_USERDATA_ENTRY_LENGTH, "%s=%s\n",
|
||||
item->ci_name, udm_item->value);
|
||||
}
|
||||
nt->userdata_length = strnlen(nt->userdata_complete,
|
||||
sizeof(nt->userdata_complete));
|
||||
}
|
||||
|
||||
static ssize_t userdatum_value_store(struct config_item *item, const char *buf,
|
||||
size_t count)
|
||||
{
|
||||
struct userdatum *udm = to_userdatum(item);
|
||||
struct netconsole_target *nt;
|
||||
struct userdata *ud;
|
||||
int ret;
|
||||
|
||||
if (count > MAX_USERDATA_VALUE_LENGTH)
|
||||
return -EMSGSIZE;
|
||||
|
||||
mutex_lock(&dynamic_netconsole_mutex);
|
||||
|
||||
ret = strscpy(udm->value, buf, sizeof(udm->value));
|
||||
if (ret < 0)
|
||||
goto out_unlock;
|
||||
trim_newline(udm->value, sizeof(udm->value));
|
||||
|
||||
ud = to_userdata(item->ci_parent);
|
||||
nt = userdata_to_target(ud);
|
||||
update_userdata(nt);
|
||||
|
||||
mutex_unlock(&dynamic_netconsole_mutex);
|
||||
return count;
|
||||
out_unlock:
|
||||
mutex_unlock(&dynamic_netconsole_mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
CONFIGFS_ATTR(userdatum_, value);
|
||||
|
||||
static struct configfs_attribute *userdatum_attrs[] = {
|
||||
&userdatum_attr_value,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static void userdatum_release(struct config_item *item)
|
||||
{
|
||||
kfree(to_userdatum(item));
|
||||
}
|
||||
|
||||
static struct configfs_item_operations userdatum_ops = {
|
||||
.release = userdatum_release,
|
||||
};
|
||||
|
||||
static const struct config_item_type userdatum_type = {
|
||||
.ct_item_ops = &userdatum_ops,
|
||||
.ct_attrs = userdatum_attrs,
|
||||
.ct_owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static struct config_item *userdatum_make_item(struct config_group *group,
|
||||
const char *name)
|
||||
{
|
||||
struct netconsole_target *nt;
|
||||
struct userdatum *udm;
|
||||
struct userdata *ud;
|
||||
size_t child_count;
|
||||
|
||||
if (strlen(name) > MAX_USERDATA_NAME_LENGTH)
|
||||
return ERR_PTR(-ENAMETOOLONG);
|
||||
|
||||
ud = to_userdata(&group->cg_item);
|
||||
nt = userdata_to_target(ud);
|
||||
child_count = list_count_nodes(&nt->userdata_group.cg_children);
|
||||
if (child_count >= MAX_USERDATA_ITEMS)
|
||||
return ERR_PTR(-ENOSPC);
|
||||
|
||||
udm = kzalloc(sizeof(*udm), GFP_KERNEL);
|
||||
if (!udm)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
config_item_init_type_name(&udm->item, name, &userdatum_type);
|
||||
return &udm->item;
|
||||
}
|
||||
|
||||
static void userdatum_drop(struct config_group *group, struct config_item *item)
|
||||
{
|
||||
struct netconsole_target *nt;
|
||||
struct userdata *ud;
|
||||
|
||||
ud = to_userdata(&group->cg_item);
|
||||
nt = userdata_to_target(ud);
|
||||
|
||||
mutex_lock(&dynamic_netconsole_mutex);
|
||||
update_userdata(nt);
|
||||
config_item_put(item);
|
||||
mutex_unlock(&dynamic_netconsole_mutex);
|
||||
}
|
||||
|
||||
static struct configfs_attribute *userdata_attrs[] = {
|
||||
NULL,
|
||||
};
|
||||
|
||||
static struct configfs_group_operations userdata_ops = {
|
||||
.make_item = userdatum_make_item,
|
||||
.drop_item = userdatum_drop,
|
||||
};
|
||||
|
||||
static struct config_item_type userdata_type = {
|
||||
.ct_item_ops = &userdatum_ops,
|
||||
.ct_group_ops = &userdata_ops,
|
||||
.ct_attrs = userdata_attrs,
|
||||
.ct_owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
CONFIGFS_ATTR(, enabled);
|
||||
CONFIGFS_ATTR(, extended);
|
||||
CONFIGFS_ATTR(, dev_name);
|
||||
|
|
@ -629,6 +828,15 @@ static const struct config_item_type netconsole_target_type = {
|
|||
.ct_owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static void init_target_config_group(struct netconsole_target *nt,
|
||||
const char *name)
|
||||
{
|
||||
config_group_init_type_name(&nt->group, name, &netconsole_target_type);
|
||||
config_group_init_type_name(&nt->userdata_group, "userdata",
|
||||
&userdata_type);
|
||||
configfs_add_default_group(&nt->userdata_group, &nt->group);
|
||||
}
|
||||
|
||||
static struct netconsole_target *find_cmdline_target(const char *name)
|
||||
{
|
||||
struct netconsole_target *nt, *ret = NULL;
|
||||
|
|
@ -636,7 +844,7 @@ static struct netconsole_target *find_cmdline_target(const char *name)
|
|||
|
||||
spin_lock_irqsave(&target_list_lock, flags);
|
||||
list_for_each_entry(nt, &target_list, list) {
|
||||
if (!strcmp(nt->item.ci_name, name)) {
|
||||
if (!strcmp(nt->group.cg_item.ci_name, name)) {
|
||||
ret = nt;
|
||||
break;
|
||||
}
|
||||
|
|
@ -650,8 +858,8 @@ static struct netconsole_target *find_cmdline_target(const char *name)
|
|||
* Group operations and type for netconsole_subsys.
|
||||
*/
|
||||
|
||||
static struct config_item *make_netconsole_target(struct config_group *group,
|
||||
const char *name)
|
||||
static struct config_group *make_netconsole_target(struct config_group *group,
|
||||
const char *name)
|
||||
{
|
||||
struct netconsole_target *nt;
|
||||
unsigned long flags;
|
||||
|
|
@ -663,23 +871,25 @@ static struct config_item *make_netconsole_target(struct config_group *group,
|
|||
if (!strncmp(name, NETCONSOLE_PARAM_TARGET_PREFIX,
|
||||
strlen(NETCONSOLE_PARAM_TARGET_PREFIX))) {
|
||||
nt = find_cmdline_target(name);
|
||||
if (nt)
|
||||
return &nt->item;
|
||||
if (nt) {
|
||||
init_target_config_group(nt, name);
|
||||
return &nt->group;
|
||||
}
|
||||
}
|
||||
|
||||
nt = alloc_and_init();
|
||||
if (!nt)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
/* Initialize the config_item member */
|
||||
config_item_init_type_name(&nt->item, name, &netconsole_target_type);
|
||||
/* Initialize the config_group member */
|
||||
init_target_config_group(nt, name);
|
||||
|
||||
/* Adding, but it is disabled */
|
||||
spin_lock_irqsave(&target_list_lock, flags);
|
||||
list_add(&nt->list, &target_list);
|
||||
spin_unlock_irqrestore(&target_list_lock, flags);
|
||||
|
||||
return &nt->item;
|
||||
return &nt->group;
|
||||
}
|
||||
|
||||
static void drop_netconsole_target(struct config_group *group,
|
||||
|
|
@ -699,11 +909,11 @@ static void drop_netconsole_target(struct config_group *group,
|
|||
if (nt->enabled)
|
||||
netpoll_cleanup(&nt->np);
|
||||
|
||||
config_item_put(&nt->item);
|
||||
config_item_put(&nt->group.cg_item);
|
||||
}
|
||||
|
||||
static struct configfs_group_operations netconsole_subsys_group_ops = {
|
||||
.make_item = make_netconsole_target,
|
||||
.make_group = make_netconsole_target,
|
||||
.drop_item = drop_netconsole_target,
|
||||
};
|
||||
|
||||
|
|
@ -729,8 +939,7 @@ static void populate_configfs_item(struct netconsole_target *nt,
|
|||
|
||||
snprintf(target_name, sizeof(target_name), "%s%d",
|
||||
NETCONSOLE_PARAM_TARGET_PREFIX, cmdline_count);
|
||||
config_item_init_type_name(&nt->item, target_name,
|
||||
&netconsole_target_type);
|
||||
init_target_config_group(nt, target_name);
|
||||
}
|
||||
|
||||
#endif /* CONFIG_NETCONSOLE_DYNAMIC */
|
||||
|
|
@ -781,6 +990,7 @@ static int netconsole_netdev_event(struct notifier_block *this,
|
|||
spin_unlock_irqrestore(&target_list_lock, flags);
|
||||
if (stopped) {
|
||||
const char *msg = "had an event";
|
||||
|
||||
switch (event) {
|
||||
case NETDEV_UNREGISTER:
|
||||
msg = "unregistered";
|
||||
|
|
@ -824,19 +1034,34 @@ static void send_ext_msg_udp(struct netconsole_target *nt, const char *msg,
|
|||
const char *msg_ready = msg;
|
||||
const char *release;
|
||||
int release_len = 0;
|
||||
int userdata_len = 0;
|
||||
char *userdata = NULL;
|
||||
|
||||
#ifdef CONFIG_NETCONSOLE_DYNAMIC
|
||||
userdata = nt->userdata_complete;
|
||||
userdata_len = nt->userdata_length;
|
||||
#endif
|
||||
|
||||
if (nt->release) {
|
||||
release = init_utsname()->release;
|
||||
release_len = strlen(release) + 1;
|
||||
}
|
||||
|
||||
if (msg_len + release_len <= MAX_PRINT_CHUNK) {
|
||||
if (msg_len + release_len + userdata_len <= MAX_PRINT_CHUNK) {
|
||||
/* No fragmentation needed */
|
||||
if (nt->release) {
|
||||
scnprintf(buf, MAX_PRINT_CHUNK, "%s,%s", release, msg);
|
||||
msg_len += release_len;
|
||||
msg_ready = buf;
|
||||
} else {
|
||||
memcpy(buf, msg, msg_len);
|
||||
}
|
||||
|
||||
if (userdata)
|
||||
msg_len += scnprintf(&buf[msg_len],
|
||||
MAX_PRINT_CHUNK - msg_len,
|
||||
"%s", userdata);
|
||||
|
||||
msg_ready = buf;
|
||||
netpoll_send_udp(&nt->np, msg_ready, msg_len);
|
||||
return;
|
||||
}
|
||||
|
|
@ -860,24 +1085,48 @@ static void send_ext_msg_udp(struct netconsole_target *nt, const char *msg,
|
|||
memcpy(buf + release_len, header, header_len);
|
||||
header_len += release_len;
|
||||
|
||||
while (offset < body_len) {
|
||||
while (offset < body_len + userdata_len) {
|
||||
int this_header = header_len;
|
||||
int this_chunk;
|
||||
int this_offset = 0;
|
||||
int this_chunk = 0;
|
||||
|
||||
this_header += scnprintf(buf + this_header,
|
||||
sizeof(buf) - this_header,
|
||||
",ncfrag=%d/%d;", offset, body_len);
|
||||
",ncfrag=%d/%d;", offset,
|
||||
body_len + userdata_len);
|
||||
|
||||
this_chunk = min(body_len - offset,
|
||||
MAX_PRINT_CHUNK - this_header);
|
||||
if (WARN_ON_ONCE(this_chunk <= 0))
|
||||
return;
|
||||
/* Not all body data has been written yet */
|
||||
if (offset < body_len) {
|
||||
this_chunk = min(body_len - offset,
|
||||
MAX_PRINT_CHUNK - this_header);
|
||||
if (WARN_ON_ONCE(this_chunk <= 0))
|
||||
return;
|
||||
memcpy(buf + this_header, body + offset, this_chunk);
|
||||
this_offset += this_chunk;
|
||||
}
|
||||
/* Body is fully written and there is pending userdata to write,
|
||||
* append userdata in this chunk
|
||||
*/
|
||||
if (offset + this_offset >= body_len &&
|
||||
offset + this_offset < userdata_len + body_len) {
|
||||
int sent_userdata = (offset + this_offset) - body_len;
|
||||
int preceding_bytes = this_chunk + this_header;
|
||||
|
||||
memcpy(buf + this_header, body + offset, this_chunk);
|
||||
if (WARN_ON_ONCE(sent_userdata < 0))
|
||||
return;
|
||||
|
||||
netpoll_send_udp(&nt->np, buf, this_header + this_chunk);
|
||||
this_chunk = min(userdata_len - sent_userdata,
|
||||
MAX_PRINT_CHUNK - preceding_bytes);
|
||||
if (WARN_ON_ONCE(this_chunk <= 0))
|
||||
return;
|
||||
memcpy(buf + this_header + this_offset,
|
||||
userdata + sent_userdata,
|
||||
this_chunk);
|
||||
this_offset += this_chunk;
|
||||
}
|
||||
|
||||
offset += this_chunk;
|
||||
netpoll_send_udp(&nt->np, buf, this_header + this_offset);
|
||||
offset += this_offset;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user