[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