udevadm: allow trigger command to be synchronous

There are cases that we want to trigger and settle only specific
commands. For example, let's say at boot time we want to make sure all
the graphics devices are working correctly because it's critical for
booting, but not the USB subsystem (we'll trigger USB events later). So
we do:

  udevadm trigger --action="add" --subsystem-match="graphics"
  udevadm settle

However, we cannot block the kernel from emitting kernel events from
discovering USB devices. So if any of the USB kernel event was emitted
before the settle command, the settle command would still wait for the
entire queue to complete. And if the USB event takes a long time to be
processed, the system slows down.

The new `settle` option allows the `trigger` command to wait for only
the triggered events, and effectively solves this problem.
This commit is contained in:
Mao 2018-02-01 17:33:13 +08:00 committed by Zbigniew Jędrzejewski-Szmek
parent 302af1c250
commit 792cc203a6
2 changed files with 100 additions and 8 deletions

View File

@ -335,6 +335,17 @@
device.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>-w</option></term>
<term><option>--settle</option></term>
<listitem>
<para>Apart from triggering events, also waits for those events to
finish. Note that this is different from calling <command>udevadm
settle</command>. <command>udevadm settle</command> waits for all
events to finish. This option only waits for events triggered by
the same command to finish.</para>
</listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="version" />
<xi:include href="standard-options.xml" xpointer="help" />

View File

@ -24,6 +24,8 @@
#include <string.h>
#include <unistd.h>
#include "fd-util.h"
#include "set.h"
#include "string-util.h"
#include "udev-util.h"
#include "udev.h"
@ -33,21 +35,26 @@
static int verbose;
static int dry_run;
static void exec_list(struct udev_enumerate *udev_enumerate, const char *action) {
static void exec_list(struct udev_enumerate *udev_enumerate, const char *action, Set *settle_set) {
struct udev_list_entry *entry;
udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(udev_enumerate)) {
char filename[UTIL_PATH_SIZE];
const char *syspath;
int fd;
syspath = udev_list_entry_get_name(entry);
if (verbose)
printf("%s\n", udev_list_entry_get_name(entry));
printf("%s\n", syspath);
if (dry_run)
continue;
strscpyl(filename, sizeof(filename), udev_list_entry_get_name(entry), "/uevent", NULL);
strscpyl(filename, sizeof(filename), syspath, "/uevent", NULL);
fd = open(filename, O_WRONLY|O_CLOEXEC);
if (fd < 0)
continue;
if (settle_set != NULL)
set_put_strdup(settle_set, syspath);
if (write(fd, action, strlen(action)) < 0)
log_debug_errno(errno, "error writing '%s' to '%s': %m", action, filename);
close(fd);
@ -87,6 +94,7 @@ static void help(void) {
" -y --sysname-match=NAME Trigger devices with this /sys path\n"
" --name-match=NAME Trigger devices with this /dev name\n"
" -b --parent-match=NAME Trigger devices with that parent device\n"
" -w --settle Wait for the triggered events to complete\n"
, program_invocation_short_name);
}
@ -109,6 +117,7 @@ static int adm_trigger(struct udev *udev, int argc, char *argv[]) {
{ "sysname-match", required_argument, NULL, 'y' },
{ "name-match", required_argument, NULL, ARG_NAME },
{ "parent-match", required_argument, NULL, 'b' },
{ "settle", no_argument, NULL, 'w' },
{ "version", no_argument, NULL, 'V' },
{ "help", no_argument, NULL, 'h' },
{}
@ -119,13 +128,19 @@ static int adm_trigger(struct udev *udev, int argc, char *argv[]) {
} device_type = TYPE_DEVICES;
const char *action = "change";
_cleanup_udev_enumerate_unref_ struct udev_enumerate *udev_enumerate = NULL;
_cleanup_udev_monitor_unref_ struct udev_monitor *udev_monitor = NULL;
_cleanup_close_ int fd_ep = -1;
int fd_udev = -1;
struct epoll_event ep_udev;
bool settle = false;
_cleanup_set_free_free_ Set *settle_set = NULL;
int c, r;
udev_enumerate = udev_enumerate_new(udev);
if (udev_enumerate == NULL)
return 1;
while ((c = getopt_long(argc, argv, "vnt:c:s:S:a:A:p:g:y:b:Vh", options, NULL)) >= 0) {
while ((c = getopt_long(argc, argv, "vnt:c:s:S:a:A:p:g:y:b:wVh", options, NULL)) >= 0) {
const char *key;
const char *val;
char buf[UTIL_PATH_SIZE];
@ -223,6 +238,9 @@ static int adm_trigger(struct udev *udev, int argc, char *argv[]) {
}
break;
}
case 'w':
settle = true;
break;
case ARG_NAME: {
_cleanup_udev_device_unref_ struct udev_device *dev;
@ -270,18 +288,81 @@ static int adm_trigger(struct udev *udev, int argc, char *argv[]) {
}
}
if (settle) {
fd_ep = epoll_create1(EPOLL_CLOEXEC);
if (fd_ep < 0) {
log_error_errno(errno, "error creating epoll fd: %m");
return 1;
}
udev_monitor = udev_monitor_new_from_netlink(udev, "udev");
if (udev_monitor == NULL) {
log_error("error: unable to create netlink socket");
return 3;
}
fd_udev = udev_monitor_get_fd(udev_monitor);
if (udev_monitor_enable_receiving(udev_monitor) < 0) {
log_error("error: unable to subscribe to udev events");
return 4;
}
ep_udev = (struct epoll_event) { .events = EPOLLIN, .data.fd = fd_udev };
if (epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_udev, &ep_udev) < 0) {
log_error_errno(errno, "fail to add fd to epoll: %m");
return 5;
}
settle_set = set_new(&string_hash_ops);
if (settle_set == NULL) {
log_oom();
return 1;
}
}
switch (device_type) {
case TYPE_SUBSYSTEMS:
udev_enumerate_scan_subsystems(udev_enumerate);
exec_list(udev_enumerate, action);
return 0;
break;
case TYPE_DEVICES:
udev_enumerate_scan_devices(udev_enumerate);
exec_list(udev_enumerate, action);
return 0;
break;
default:
assert_not_reached("device_type");
}
exec_list(udev_enumerate, action, settle_set);
while (!set_isempty(settle_set)) {
int fdcount;
struct epoll_event ev[4];
int i;
fdcount = epoll_wait(fd_ep, ev, ELEMENTSOF(ev), -1);
if (fdcount < 0) {
if (errno != EINTR)
log_error_errno(errno, "error receiving uevent message: %m");
continue;
}
for (i = 0; i < fdcount; i++) {
if (ev[i].data.fd == fd_udev && ev[i].events & EPOLLIN) {
_cleanup_udev_device_unref_ struct udev_device *device;
const char *syspath = NULL;
device = udev_monitor_receive_device(udev_monitor);
if (device == NULL)
continue;
syspath = udev_device_get_syspath(device);
if (verbose)
printf("settle %s\n", syspath);
if (!set_remove(settle_set, syspath))
log_debug("Got epoll event on syspath %s not present in syspath set", syspath);
}
}
}
return 0;
}
const struct udevadm_cmd udevadm_trigger = {