Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

netconsole: Dynamic allocation of userdata buffer

The userdata buffer in struct netconsole_target is currently statically
allocated with a size of MAX_USERDATA_ITEMS * MAX_EXTRADATA_ENTRY_LEN
(16 * 256 = 4096 bytes). This wastes memory when userdata entries are
not used or when only a few entries are configured, which is common in
typical usage scenarios. It also forces us to keep MAX_USERDATA_ITEMS
small to limit the memory wasted.

Change the userdata buffer from a static array to a dynamically
allocated pointer. The buffer is now allocated on-demand in
update_userdata() whenever userdata entries are added, modified, or
removed via configfs. The implementation calculates the exact size
needed for all current userdata entries, allocates a new buffer of that
size, formats the entries into it, and atomically swaps it with the old
buffer.

This approach provides several benefits:
- Memory efficiency: Targets with no userdata use zero bytes instead of
4KB, and targets with userdata only allocate what they need;
- Scalability: Makes it practical to increase MAX_USERDATA_ITEMS to a
much larger value without imposing a fixed memory cost on every
target;
- No hot-path overhead: Allocation occurs during configuration (write to
configfs), not during message transmission

If memory allocation fails during userdata update, -ENOMEM is returned
to userspace through the configfs attribute write operation.

The sysdata buffer remains statically allocated since it has a smaller
fixed size (MAX_SYSDATA_ITEMS * MAX_EXTRADATA_ENTRY_LEN = 4 * 256 = 1024
bytes) and its content length is less predictable.

Signed-off-by: Gustavo Luiz Duarte <gustavold@gmail.com>
Reviewed-by: Breno Leitao <leitao@debian.org>
Link: https://patch.msgid.link/20251119-netconsole_dynamic_extradata-v3-3-497ac3191707@meta.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

authored by

Gustavo Luiz Duarte and committed by
Jakub Kicinski
eb83801a 9dc10f50

+73 -33
+73 -33
drivers/net/netconsole.c
··· 155 155 #ifdef CONFIG_NETCONSOLE_DYNAMIC 156 156 struct config_group group; 157 157 struct config_group userdata_group; 158 - char userdata[MAX_EXTRADATA_ENTRY_LEN * MAX_USERDATA_ITEMS]; 158 + char *userdata; 159 159 size_t userdata_length; 160 160 char sysdata[MAX_EXTRADATA_ENTRY_LEN * MAX_SYSDATA_ITEMS]; 161 161 ··· 875 875 return sysfs_emit(buf, "%s\n", &(to_userdatum(item)->value[0])); 876 876 } 877 877 878 - static void update_userdata(struct netconsole_target *nt) 878 + /* Navigate configfs and calculate the lentgh of the formatted string 879 + * representing userdata. 880 + * Must be called holding netconsole_subsys.su_mutex 881 + */ 882 + static int calc_userdata_len(struct netconsole_target *nt) 879 883 { 884 + struct userdatum *udm_item; 885 + struct config_item *item; 880 886 struct list_head *entry; 881 - int child_count = 0; 882 - unsigned long flags; 883 - 884 - spin_lock_irqsave(&target_list_lock, flags); 885 - 886 - /* Clear the current string in case the last userdatum was deleted */ 887 - nt->userdata_length = 0; 888 - nt->userdata[0] = 0; 887 + int len = 0; 889 888 890 889 list_for_each(entry, &nt->userdata_group.cg_children) { 891 - struct userdatum *udm_item; 892 - struct config_item *item; 893 - 894 - if (child_count >= MAX_USERDATA_ITEMS) { 895 - spin_unlock_irqrestore(&target_list_lock, flags); 896 - WARN_ON_ONCE(1); 897 - return; 898 - } 899 - child_count++; 900 - 901 890 item = container_of(entry, struct config_item, ci_entry); 902 891 udm_item = to_userdatum(item); 903 - 904 892 /* Skip userdata with no value set */ 905 - if (strnlen(udm_item->value, MAX_EXTRADATA_VALUE_LEN) == 0) 906 - continue; 907 - 908 - /* This doesn't overflow userdata since it will write 909 - * one entry length (1/MAX_USERDATA_ITEMS long), entry count is 910 - * checked to not exceed MAX items with child_count above 911 - */ 912 - nt->userdata_length += scnprintf(&nt->userdata[nt->userdata_length], 913 - MAX_EXTRADATA_ENTRY_LEN, " %s=%s\n", 914 - item->ci_name, udm_item->value); 893 + if (udm_item->value[0]) { 894 + len += snprintf(NULL, 0, " %s=%s\n", item->ci_name, 895 + udm_item->value); 896 + } 915 897 } 898 + return len; 899 + } 900 + 901 + static int update_userdata(struct netconsole_target *nt) 902 + { 903 + struct userdatum *udm_item; 904 + struct config_item *item; 905 + struct list_head *entry; 906 + char *old_buf = NULL; 907 + char *new_buf = NULL; 908 + unsigned long flags; 909 + int offset = 0; 910 + int len; 911 + 912 + /* Calculate required buffer size */ 913 + len = calc_userdata_len(nt); 914 + 915 + if (WARN_ON_ONCE(len > MAX_EXTRADATA_ENTRY_LEN * MAX_USERDATA_ITEMS)) 916 + return -ENOSPC; 917 + 918 + /* Allocate new buffer */ 919 + if (len) { 920 + new_buf = kmalloc(len + 1, GFP_KERNEL); 921 + if (!new_buf) 922 + return -ENOMEM; 923 + } 924 + 925 + /* Write userdata to new buffer */ 926 + list_for_each(entry, &nt->userdata_group.cg_children) { 927 + item = container_of(entry, struct config_item, ci_entry); 928 + udm_item = to_userdatum(item); 929 + /* Skip userdata with no value set */ 930 + if (udm_item->value[0]) { 931 + offset += scnprintf(&new_buf[offset], len + 1 - offset, 932 + " %s=%s\n", item->ci_name, 933 + udm_item->value); 934 + } 935 + } 936 + 937 + WARN_ON_ONCE(offset != len); 938 + 939 + /* Switch to new buffer and free old buffer */ 940 + spin_lock_irqsave(&target_list_lock, flags); 941 + old_buf = nt->userdata; 942 + nt->userdata = new_buf; 943 + nt->userdata_length = offset; 916 944 spin_unlock_irqrestore(&target_list_lock, flags); 945 + 946 + kfree(old_buf); 947 + 948 + return 0; 917 949 } 918 950 919 951 static ssize_t userdatum_value_store(struct config_item *item, const char *buf, ··· 969 937 970 938 ud = to_userdata(item->ci_parent); 971 939 nt = userdata_to_target(ud); 972 - update_userdata(nt); 940 + ret = update_userdata(nt); 941 + if (ret < 0) 942 + goto out_unlock; 973 943 ret = count; 974 944 out_unlock: 975 945 mutex_unlock(&dynamic_netconsole_mutex); ··· 1227 1193 1228 1194 static void netconsole_target_release(struct config_item *item) 1229 1195 { 1230 - kfree(to_target(item)); 1196 + struct netconsole_target *nt = to_target(item); 1197 + 1198 + kfree(nt->userdata); 1199 + kfree(nt); 1231 1200 } 1232 1201 1233 1202 static struct configfs_item_operations netconsole_target_item_ops = { ··· 1911 1874 static void free_param_target(struct netconsole_target *nt) 1912 1875 { 1913 1876 netpoll_cleanup(&nt->np); 1877 + #ifdef CONFIG_NETCONSOLE_DYNAMIC 1878 + kfree(nt->userdata); 1879 + #endif 1914 1880 kfree(nt); 1915 1881 } 1916 1882