Commit 20ad4018 authored by David S. Miller's avatar David S. Miller
Browse files

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: default avatarDavid S. Miller <davem@davemloft.net>
parents e7689879 1ec9daf9
Loading
Loading
Loading
Loading
+66 −0
Original line number Diff line number Diff line
@@ -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:
=================

+304 −55
Original line number Diff line number Diff line
@@ -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,7 +858,7 @@ 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,
static struct config_group *make_netconsole_target(struct config_group *group,
						   const char *name)
{
	struct netconsole_target *nt;
@@ -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);

		/* 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;

		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;
	}
}