[LTP] [PATCH v2 2/5] kernel/uevent: Add uevent01

Clemens Famulla-Conrad cfamullaconrad@suse.de
Wed Aug 21 18:35:22 CEST 2019


Hi Cyril,

On Tue, 2019-08-20 at 17:18 +0200, Cyril Hrubis wrote:
> Simple test that attached and detaches a file to a loop device and
> checks that kernel broadcasts correct events to the kernel uevent
> broadcast group.
> 
> Signed-off-by: Cyril Hrubis <chrubis@suse.cz>
> ---
>  runtest/uevent                      |   1 +
>  scenario_groups/default             |   1 +
>  testcases/kernel/uevents/.gitignore |   1 +
>  testcases/kernel/uevents/Makefile   |   6 +
>  testcases/kernel/uevents/uevent.h   | 176
> ++++++++++++++++++++++++++++
>  testcases/kernel/uevents/uevent01.c |  90 ++++++++++++++
>  6 files changed, 275 insertions(+)
>  create mode 100644 runtest/uevent
>  create mode 100644 testcases/kernel/uevents/.gitignore
>  create mode 100644 testcases/kernel/uevents/Makefile
>  create mode 100644 testcases/kernel/uevents/uevent.h
>  create mode 100644 testcases/kernel/uevents/uevent01.c
> 
> diff --git a/runtest/uevent b/runtest/uevent
> new file mode 100644
> index 000000000..e9cdf26b8
> --- /dev/null
> +++ b/runtest/uevent
> @@ -0,0 +1 @@
> +uevent01 uevent01
> diff --git a/scenario_groups/default b/scenario_groups/default
> index 093f5f706..62ae0759d 100644
> --- a/scenario_groups/default
> +++ b/scenario_groups/default
> @@ -29,3 +29,4 @@ input
>  cve
>  crypto
>  kernel_misc
> +uevent
> diff --git a/testcases/kernel/uevents/.gitignore
> b/testcases/kernel/uevents/.gitignore
> new file mode 100644
> index 000000000..53d0b546a
> --- /dev/null
> +++ b/testcases/kernel/uevents/.gitignore
> @@ -0,0 +1 @@
> +uevent01
> diff --git a/testcases/kernel/uevents/Makefile
> b/testcases/kernel/uevents/Makefile
> new file mode 100644
> index 000000000..cba769739
> --- /dev/null
> +++ b/testcases/kernel/uevents/Makefile
> @@ -0,0 +1,6 @@
> +# SPDX-License-Identifier: GPL-2.0-or-later
> +
> +top_srcdir			?= ../../..
> +
> +include $(top_srcdir)/include/mk/testcases.mk
> +include $(top_srcdir)/include/mk/generic_leaf_target.mk
> diff --git a/testcases/kernel/uevents/uevent.h
> b/testcases/kernel/uevents/uevent.h
> new file mode 100644
> index 000000000..2c32dd534
> --- /dev/null
> +++ b/testcases/kernel/uevents/uevent.h
> @@ -0,0 +1,176 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Copyright (C) 2019 Cyril Hrubis <chrubis@suse.cz>
> + */
> +
> +#ifndef UEVENT_H__
> +#define UEVENT_H__
> +
> +#include "tst_netlink.h"
> +
> +/*
> + * There are two broadcast groups defined for the
> NETLINK_KOBJECT_UEVENT. The
> + * primary consument of the KERNEL group is udev which handles the
> hotplug
> + * events and then, once udev does it's magic the events are
> rebroadcasted to
> + * the UDEV group which is consumed by various daemons in the
> userspace.
> + */
> +enum monitor_netlink_group {
> +	MONITOR_GROUP_NONE,
> +	MONITOR_GROUP_KERNEL,
> +	MONITOR_GROUP_UDEV,
> +};
> +
> +/*
> + * The messages received from the NETLINK_KOBJECT_UEVENT socket are
> stored as a
> + * sequence of a null-terminated strings. First in the buffer is a
> summary of a
> + * action i.e. "$ACTION@$DEVPATH" which is then followed by a bunch
> of
> + * key-value pairs.
> + *
> + * For example attaching a file to loopback device generates event:
> + *
> + * "change@/devices/virtual/block/loop0\0
> + *  ACTION=change\0
> + *  DEVPATH=/devices/virtual/block/loop0\0
> + *  SUBSYSTEM=block\0
> + *  MAJOR=7\0
> + *  MINOR=0\0
> + *  DEVNAME=loop0\0
> + *  DEVTYPE=disk\0
> + *  SEQNUM=2677\0"
> + */
> +
> +/*
> + * Prints uevent.
> + */
> +static inline void print_uevent(const char *event, int len)
> +{
> +	int consumed = 0;
> +
> +	tst_res(TINFO, "Got uevent:");
> +
> +	while (consumed < len) {
> +		tst_res(TINFO, "%s", event);
> +		int l = strlen(event) + 1;
> +		consumed += l;
> +		event += l;
> +	}
> +}
> +
> +/*
> + * Uevents read from the socket are matched against this
> description.
> + *
> + * The msg is the overall action description e.g.
> + * "add@/class/input/input4/mouse1" which has to be matched exactly
> before we
> + * event attempt to check the key-value pairs stored in the values
> array. The
> + * event is considered to match if all key-value pairs in the values
> has been
> + * found in the received event.
> + */
> +struct uevent_desc {
> +	const char *msg;
> +	int value_cnt;
> +	const char **values;
> +};
> +
> +static inline int uevent_match(const char *event, int len,
> +                               const struct uevent_desc *uevent)
> +{
> +	int consumed = 0;
> +	int val_matches = 0;
> +
> +	if (memcmp(event, uevent->msg, strlen(uevent->msg)))
> +		return 0;
> +
> +	int l = strlen(event) + 1;
> +
> +	consumed += l;
> +	event += l;
> +
> +	while (consumed < len) {
> +		int i;
> +		for (i = 0; i < uevent->value_cnt; i++) {
> +			if (!strcmp(event, uevent->values[i])) {
> +				val_matches++;
> +				break;
> +			}
> +		}
> +
> +		l = strlen(event) + 1;
> +		consumed += l;
> +		event += l;
> +	}
> +
> +	return val_matches == uevent->value_cnt;
> +}
> +
> +static inline int open_uevent_netlink(void)
> +{
> +	int fd;
> +	struct sockaddr_nl nl_addr = {
> +		.nl_family = AF_NETLINK,
> +		.nl_groups = MONITOR_GROUP_KERNEL,
> +	};
> +
> +	fd = SAFE_SOCKET(AF_NETLINK, SOCK_RAW,
> NETLINK_KOBJECT_UEVENT);
> +
> +	SAFE_BIND(fd, (struct sockaddr *)&nl_addr, sizeof(nl_addr));
> +
> +	return fd;
> +}
> +
> +/*
> + * Reads events from uevent netlink socket until all expected events
> passed in
> + * the uevent array are matched.
> + */
> +static inline void wait_for_uevents(int fd, const struct uevent_desc
> *const uevents[])
> +{
> +	int i = 0;
> +
> +	while (1) {
> +		int len;
> +		char buf[4096];
> +
> +		len = recv(fd, &buf, sizeof(buf), 0);
> +
> +		if (len == 0)
> +			continue;
> +
> +		print_uevent(buf, len);
> +
> +		if (uevent_match(buf, len, uevents[i])) {
> +			tst_res(TPASS, "Got expected UEVENT");
> +			if (!uevents[++i]) {
> +				close(fd);
> +				exit(0);
> +			}
> +		}
> +	}
> +}
> +
> +/*
> + * Waits 5 seconds for a child to exit, kills the child after a
> timeout.
> + */
> +static inline void wait_for_pid(int pid)
> +{
> +	int status, ret;
> +	int retries = 5000;
> +
> +	do {
> +		ret = waitpid(pid, &status, WNOHANG);
> +		usleep(1000);
> +	} while (ret == 0 && retries--);
> +
> +	if (ret == pid) {
> +		if (WIFEXITED(status) && WEXITSTATUS(status) == 0)
> +			return;
> +
> +		tst_res(TFAIL, "Child exitted with %s",
> tst_strstatus(status));
> +	}
> +
> +	SAFE_KILL(pid, SIGKILL);
> +
> +	SAFE_WAITPID(pid, NULL, 0);
> +
> +	tst_res(TFAIL, "Did not get all expected UEVENTS");
> +}
> +
> +#endif /* UEVENT_H__ */
> diff --git a/testcases/kernel/uevents/uevent01.c
> b/testcases/kernel/uevents/uevent01.c
> new file mode 100644
> index 000000000..41cd01b1f
> --- /dev/null
> +++ b/testcases/kernel/uevents/uevent01.c
> @@ -0,0 +1,90 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Copyright (C) 2019 Cyril Hrubis <chrubis@suse.cz>
> + */
> +
> +/*
> + * Very simple uevent netlink socket test.
> + *
> + * We fork a child that listens for a kernel events while parents 
> attaches and
> + * detaches a loop device which should produce two change events.
> + */
> +
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <sys/wait.h>
> +#include "tst_test.h"
> +
> +#include "uevent.h"
> +
> +static void generate_device_events(const char *dev_path)
> +{
> +	tst_fill_file("loop.img", 0, 1024, 1024);
> +
> +	tst_res(TINFO, "Attaching device %s", dev_path);
> +	tst_attach_device(dev_path, "loop.img");
> +	tst_res(TINFO, "Detaching device %s", dev_path);
> +	tst_detach_device(dev_path);
> +}
> +
> +static void verify_uevent(void)
> +{
> +	int pid, fd, dev_num;
> +	char dev_path[1024];
> +	char ev_msg[1024];
> +	char ev_dev_path[1024];
> +	char ev_dev_minor[128];
> +	char ev_dev_name[128];
> +
> +	struct uevent_desc desc = {
> +		.msg = ev_msg,
> +		.value_cnt = 7,
> +		.values = (const char*[]) {
> +			"ACTION=change",
> +			ev_dev_path,
> +			"SUBSYSTEM=block",
> +			"MAJOR=7",
> +			ev_dev_minor,
> +			ev_dev_name,
> +			"DEVTYPE=disk",
> +		}
> +	};
> +
> +	dev_num = tst_find_free_loopdev(dev_path, sizeof(dev_path));

Maybe it isn't worth to check if dev_num is a valid number.

> +
> +	snprintf(ev_msg, sizeof(ev_msg),
> +	         "change@/devices/virtual/block/loop%i", dev_num);
> +
> +	snprintf(ev_dev_path, sizeof(ev_dev_path),
> +	         "DEVPATH=/devices/virtual/block/loop%i", dev_num);
> +
> +	snprintf(ev_dev_minor, sizeof(ev_dev_minor), "MINOR=%i",
> dev_num);
> +	snprintf(ev_dev_name, sizeof(ev_dev_name), "DEVNAME=loop%i",
> dev_num);
> +
> +	const struct uevent_desc *const uevents[] = {
> +		&desc,
> +		&desc,
> +		NULL
> +	};
> +
> +	pid = SAFE_FORK();
> +	if (!pid) {
> +		fd = open_uevent_netlink();
> +		TST_CHECKPOINT_WAKE(0);
> +		wait_for_uevents(fd, uevents);

For me it wasn't obvious that wait_for_uevents() does the exit(). Not
sure if we should do the exit better here or name the function like
exit_on_uevents().

> +	}
> +
> +	TST_CHECKPOINT_WAIT(0);
> +
> +	generate_device_events(dev_path);
> +
> +	wait_for_pid(pid);
> +}
> +
> +static struct tst_test test = {
> +	.test_all = verify_uevent,
> +	.forks_child = 1,
> +	.needs_tmpdir = 1,

Just curious, where do we need the tmpdir?

> +	.needs_checkpoints = 1,
> +	.needs_root = 1,
> +};
> -- 
> 2.21.0
> 
> 

thx
Clemens


More information about the ltp mailing list