You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

177 lines
3.7 KiB

// 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);
return;
}
}
}
}
/*
* 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__ */