From 602ad3b4dd578631a250edb03f36f8e5f911ec1a Mon Sep 17 00:00:00 2001 From: Matthew Wood Date: Sun, 4 Feb 2024 15:27:32 -0800 Subject: net: netconsole: cleanup formatting lints Address checkpatch lint suggestions in preparation for later changes Signed-off-by: Matthew Wood Reviewed-by: Breno Leitao Signed-off-by: David S. Miller --- drivers/net/netconsole.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) (limited to 'drivers/net/netconsole.c') diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c index 6e14ba5e06c8..93fc3b509706 100644 --- a/drivers/net/netconsole.c +++ b/drivers/net/netconsole.c @@ -49,7 +49,7 @@ 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-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"); @@ -501,6 +501,7 @@ static ssize_t local_ip_store(struct config_item *item, const char *buf, 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 +511,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; } @@ -537,6 +538,7 @@ static ssize_t remote_ip_store(struct config_item *item, const char *buf, 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 +548,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; } @@ -781,6 +783,7 @@ restart: spin_unlock_irqrestore(&target_list_lock, flags); if (stopped) { const char *msg = "had an event"; + switch (event) { case NETDEV_UNREGISTER: msg = "unregistered"; -- cgit v1.2.3 From bd9c69a36efd863ac915caa0f9e6dd57be0dde3e Mon Sep 17 00:00:00 2001 From: Matthew Wood Date: Sun, 4 Feb 2024 15:27:33 -0800 Subject: net: netconsole: move netconsole_target config_item to config_group In order to support a nested userdata config_group in later patches, use a config_group for netconsole_target instead of a config_item. It's a no-op functionality-wise, since config_group maintains all features of a config_item via the cg_item member. Signed-off-by: Matthew Wood Reviewed-by: Breno Leitao Signed-off-by: David S. Miller --- drivers/net/netconsole.c | 58 ++++++++++++++++++++++++++---------------------- 1 file changed, 31 insertions(+), 27 deletions(-) (limited to 'drivers/net/netconsole.c') diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c index 93fc3b509706..12bfb7eaae7f 100644 --- a/drivers/net/netconsole.c +++ b/drivers/net/netconsole.c @@ -79,7 +79,7 @@ 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. * @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 +102,7 @@ static struct console netconsole_ext; struct netconsole_target { struct list_head list; #ifdef CONFIG_NETCONSOLE_DYNAMIC - struct config_item item; + struct config_group group; #endif bool enabled; bool extended; @@ -134,14 +134,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 */ @@ -221,9 +221,13 @@ static struct netconsole_target *alloc_and_init(void) 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); } /* @@ -370,7 +374,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 +402,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; } @@ -425,7 +429,7 @@ static ssize_t dev_name_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)); mutex_unlock(&dynamic_netconsole_mutex); return -EINVAL; } @@ -450,7 +454,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 +477,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,7 +499,7 @@ 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; } @@ -532,7 +536,7 @@ 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; } @@ -570,7 +574,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; } @@ -638,7 +642,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; } @@ -652,8 +656,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; @@ -666,7 +670,7 @@ static struct config_item *make_netconsole_target(struct config_group *group, strlen(NETCONSOLE_PARAM_TARGET_PREFIX))) { nt = find_cmdline_target(name); if (nt) - return &nt->item; + return &nt->group; } nt = alloc_and_init(); @@ -674,14 +678,14 @@ static struct config_item *make_netconsole_target(struct config_group *group, return ERR_PTR(-ENOMEM); /* Initialize the config_item member */ - config_item_init_type_name(&nt->item, name, &netconsole_target_type); + config_group_init_type_name(&nt->group, name, &netconsole_target_type); /* 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, @@ -701,11 +705,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, }; @@ -731,8 +735,8 @@ 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); + config_group_init_type_name(&nt->group, target_name, + &netconsole_target_type); } #endif /* CONFIG_NETCONSOLE_DYNAMIC */ -- cgit v1.2.3 From ae001dc67907618423fd15bbab2014308c00ad0b Mon Sep 17 00:00:00 2001 From: Matthew Wood Date: Sun, 4 Feb 2024 15:27:34 -0800 Subject: net: netconsole: move newline trimming to function Move newline trimming logic from `dev_name_store()` to a new function (trim_newline()) for shared use in netconsole.c Signed-off-by: Matthew Wood Signed-off-by: David S. Miller --- drivers/net/netconsole.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) (limited to 'drivers/net/netconsole.c') diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c index 12bfb7eaae7f..e6c3b15fe95d 100644 --- a/drivers/net/netconsole.c +++ b/drivers/net/netconsole.c @@ -230,6 +230,16 @@ static struct netconsole_target *to_target(struct config_item *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'; +} + /* * Attribute operations for netconsole_target. */ @@ -424,7 +434,6 @@ 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) { @@ -435,11 +444,7 @@ static ssize_t dev_name_store(struct config_item *item, const char *buf, } 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); -- cgit v1.2.3 From 8a6d5fec6c7f952d66ad362c249100d040a21e3f Mon Sep 17 00:00:00 2001 From: Matthew Wood Date: Sun, 4 Feb 2024 15:27:36 -0800 Subject: net: netconsole: add a userdata config_group member to netconsole_target Create configfs machinery for netconsole userdata appending, which depends on CONFIG_NETCONSOLE_DYNAMIC (for configfs interface). Add a userdata config_group to netconsole_target for managing userdata entries as a tree under the netconsole configfs subsystem. Directory names created under the userdata directory become userdatum keys; the userdatum value is the content of the value file. Include the minimum-viable-changes for userdata configfs config_group. init_target_config_group() ties in the complete configfs machinery to avoid unused func/variable errors during build. Initializing the netconsole_target->group is moved to init_target_config_group, which will also init and add the userdata config_group. Each userdatum entry has a limit of 256 bytes (54 for the key/directory, 200 for the value, and 2 for '=' and '\n' characters), which is enforced by the configfs functions for updating the userdata config_group. When a new netconsole_target is created, initialize the userdata config_group and add it as a default group for netconsole_target config_group, allowing the userdata configfs sub-tree to be presented in the netconsole configfs tree under the userdata directory. Co-developed-by: Breno Leitao Signed-off-by: Breno Leitao Signed-off-by: Matthew Wood Signed-off-by: David S. Miller --- drivers/net/netconsole.c | 147 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 142 insertions(+), 5 deletions(-) (limited to 'drivers/net/netconsole.c') diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c index e6c3b15fe95d..3618b9ebcce4 100644 --- a/drivers/net/netconsole.c +++ b/drivers/net/netconsole.c @@ -43,6 +43,10 @@ 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]; @@ -80,6 +84,7 @@ static struct console netconsole_ext; * struct netconsole_target - Represents a configured netconsole target. * @list: Links this target into the target_list. * @group: Links us into the configfs subsystem hierarchy. + * @userdata_group: Links to the userdata configfs hierarchy * @enabled: On / off knob to enable / disable target. * Visible from userspace (read-write). * We maintain a strict 1:1 correspondence between this and @@ -103,6 +108,7 @@ struct netconsole_target { struct list_head list; #ifdef CONFIG_NETCONSOLE_DYNAMIC struct config_group group; + struct config_group userdata_group; #endif bool enabled; bool extended; @@ -215,6 +221,10 @@ static struct netconsole_target *alloc_and_init(void) * | remote_ip * | local_mac * | remote_mac + * | userdata/ + * | / + * | value + * | ... * | * /... */ @@ -596,6 +606,123 @@ out_unlock: 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 ssize_t userdatum_value_store(struct config_item *item, const char *buf, + size_t count) +{ + struct userdatum *udm = to_userdatum(item); + 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)); + + 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 struct configfs_attribute *userdata_attrs[] = { + NULL, +}; + +static struct configfs_group_operations userdata_ops = { + .make_item = userdatum_make_item, +}; + +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); @@ -640,6 +767,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; @@ -674,16 +810,18 @@ static struct config_group *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) + 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_group_init_type_name(&nt->group, 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); @@ -740,8 +878,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_group_init_type_name(&nt->group, target_name, - &netconsole_target_type); + init_target_config_group(nt, target_name); } #endif /* CONFIG_NETCONSOLE_DYNAMIC */ -- cgit v1.2.3 From df03f830d099f0811281a222aefdd9d400fa0b72 Mon Sep 17 00:00:00 2001 From: Matthew Wood Date: Sun, 4 Feb 2024 15:27:37 -0800 Subject: net: netconsole: cache userdata formatted string in netconsole_target Store a formatted string for userdata that will be appended to netconsole messages. The string has a capacity of 4KB, as calculated by the userdatum entry length of 256 bytes and a max of 16 userdata entries. Update the stored netconsole_target->userdata_complete string with the new formatted userdata values when a userdatum is created, edited, or removed. Each userdata entry contains a trailing newline, which will be formatted as such in netconsole messages:: 6.7.0-rc8-virtme,12,500,1646292204,-;test release=foo something=bar 6.7.0-rc8-virtme,12,500,1646292204,-;another test release=foo something=bar Enforcement of MAX_USERDATA_ITEMS is done in userdatum_make_item; update_userdata will not check for this case but will skip any userdata children over the limit of MAX_USERDATA_ITEMs. If a userdata entry/dir is created but no value is provided, that entry will be skipped. This is in part because update_userdata() can't be called in userdatum_make_item() since the item will not have been added to the userdata config_group children yet. To preserve the experience of adding an empty userdata that doesn't show up in the netconsole messages, purposefully skip empty userdata items even when update_userdata() can be called. Co-developed-by: Breno Leitao Signed-off-by: Breno Leitao Signed-off-by: Matthew Wood Signed-off-by: David S. Miller --- drivers/net/netconsole.c | 61 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) (limited to 'drivers/net/netconsole.c') diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c index 3618b9ebcce4..e4d6ba0b50ef 100644 --- a/drivers/net/netconsole.c +++ b/drivers/net/netconsole.c @@ -85,6 +85,8 @@ static struct console netconsole_ext; * @list: Links this target into the target_list. * @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 @@ -109,6 +111,8 @@ struct netconsole_target { #ifdef CONFIG_NETCONSOLE_DYNAMIC 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; @@ -638,10 +642,48 @@ 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) @@ -654,6 +696,10 @@ static ssize_t userdatum_value_store(struct config_item *item, const char *buf, 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: @@ -708,12 +754,27 @@ static struct config_item *userdatum_make_item(struct config_group *group, 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 = { -- cgit v1.2.3 From b4ab4f2c0ff5f6b5aeccd71c5caeef2cbcfea47d Mon Sep 17 00:00:00 2001 From: Matthew Wood Date: Sun, 4 Feb 2024 15:27:38 -0800 Subject: net: netconsole: append userdata to netconsole messages Append userdata to outgoing unfragmented (<1000 bytes) netconsole messages. When sending messages the userdata string is already formatted and stored in netconsole_target->userdata_complete. Always write the outgoing message to buf, so userdata can be appended in a standard fashion. This is a change from only using buf when the release needs to be prepended to the message. Co-developed-by: Breno Leitao Signed-off-by: Breno Leitao Signed-off-by: Matthew Wood Signed-off-by: David S. Miller --- drivers/net/netconsole.c | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) (limited to 'drivers/net/netconsole.c') diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c index e4d6ba0b50ef..d53bb1172336 100644 --- a/drivers/net/netconsole.c +++ b/drivers/net/netconsole.c @@ -1034,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; } -- cgit v1.2.3 From 1ec9daf950936c2a1c591596e83c09ce2eb12ade Mon Sep 17 00:00:00 2001 From: Matthew Wood Date: Sun, 4 Feb 2024 15:27:39 -0800 Subject: net: netconsole: append userdata to fragmented netconsole messages Regardless of whether the original message body or formatted userdata exceeds the MAX_PRINT_CHUNK, append userdata to the netconsole message starting with the first chunk that has available space after writing the body. Co-developed-by: Breno Leitao Signed-off-by: Breno Leitao Signed-off-by: Matthew Wood Signed-off-by: David S. Miller --- drivers/net/netconsole.c | 50 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 37 insertions(+), 13 deletions(-) (limited to 'drivers/net/netconsole.c') diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c index d53bb1172336..0de108a1c0c8 100644 --- a/drivers/net/netconsole.c +++ b/drivers/net/netconsole.c @@ -1085,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); - - 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); - - netpoll_send_udp(&nt->np, buf, this_header + this_chunk); + ",ncfrag=%d/%d;", offset, + body_len + userdata_len); + + /* 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; + + if (WARN_ON_ONCE(sent_userdata < 0)) + return; + + 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; } } -- cgit v1.2.3 From 2b3953585953a42cd29045c80b20b2bdd6633225 Mon Sep 17 00:00:00 2001 From: Matthew Wood Date: Thu, 7 Mar 2024 16:25:24 -0800 Subject: net: netconsole: Add continuation line prefix to userdata messages Add a space (' ') prefix to every userdata line to match docs for dev-kmsg. To account for this extra character in each userdata entry, reduce userdata entry names (directory name) from 54 characters to 53. According to the dev-kmsg docs, a space is used for subsequent lines to mark them as continuation lines. > A line starting with ' ', is a continuation line, adding > key/value pairs to the log message, which provide the machine > readable context of the message, for reliable processing in > userspace. Testing for this patch:: cd /sys/kernel/config/netconsole && mkdir cmdline0 cd cmdline0 mkdir userdata/test && echo "hello" > userdata/test/value mkdir userdata/test2 && echo "hello2" > userdata/test2/value echo "message" > /dev/kmsg Outputs:: 6.8.0-rc5-virtme,12,493,231373579,-;message test=hello test2=hello2 And I confirmed all testing works as expected from the original patchset Fixes: df03f830d099 ("net: netconsole: cache userdata formatted string in netconsole_target") Signed-off-by: Matthew Wood Reviewed-by: Breno Leitao Link: https://lore.kernel.org/r/20240308002525.248672-1-thepacketgeek@gmail.com Signed-off-by: Jakub Kicinski --- Documentation/networking/netconsole.rst | 8 ++++---- drivers/net/netconsole.c | 12 +++++++----- 2 files changed, 11 insertions(+), 9 deletions(-) (limited to 'drivers/net/netconsole.c') diff --git a/Documentation/networking/netconsole.rst b/Documentation/networking/netconsole.rst index b28c525e5d1e..d55c2a22ec7a 100644 --- a/Documentation/networking/netconsole.rst +++ b/Documentation/networking/netconsole.rst @@ -180,7 +180,7 @@ 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 +Directories (keys) under `userdata` are limited to 53 character length, and data in `userdata//value` are limited to 200 bytes:: cd /sys/kernel/config/netconsole && mkdir cmdline0 @@ -197,8 +197,8 @@ Messages will now include this additional user data:: Sends:: 12,607,22085407756,-;This is a message - foo=bar - qux=baz + foo=bar + qux=baz Preview the userdata that will be appended with:: @@ -218,7 +218,7 @@ 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 + foo=bar Delete `userdata` entries with `rmdir`:: diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c index 0de108a1c0c8..d7070dd4fe73 100644 --- a/drivers/net/netconsole.c +++ b/drivers/net/netconsole.c @@ -42,12 +42,14 @@ MODULE_AUTHOR("Maintainer: Matt Mackall "); 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_PARAM_LENGTH 256 #define MAX_USERDATA_ENTRY_LENGTH 256 +#define MAX_USERDATA_VALUE_LENGTH 200 +/* The number 3 comes from userdata entry format characters (' ', '=', '\n') */ +#define MAX_USERDATA_NAME_LENGTH (MAX_USERDATA_ENTRY_LENGTH - \ + MAX_USERDATA_VALUE_LENGTH - 3) #define MAX_USERDATA_ITEMS 16 -#define MAX_PRINT_CHUNK 1000 +#define MAX_PRINT_CHUNK 1000 static char config[MAX_PARAM_LENGTH]; module_param_string(netconsole, config, MAX_PARAM_LENGTH, 0); @@ -671,7 +673,7 @@ static void update_userdata(struct netconsole_target *nt) * 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", + MAX_USERDATA_ENTRY_LENGTH, " %s=%s\n", item->ci_name, udm_item->value); } nt->userdata_length = strnlen(nt->userdata_complete, -- cgit v1.2.3