blob: 02c6e03ffb4a324ea1cb2dfdf67cd22104b0075f [file] [log] [blame]
commit f69bcf878e6aad1f1991705f40c56cbb0ea73c6d
Author: Mao <littlecvr@gmail.com>
Date: Tue Feb 13 14:58:32 2018 +0800
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.
diff --git a/man/udevadm.xml b/man/udevadm.xml
index 8ef9e23aa..484cbac84 100644
--- a/man/udevadm.xml
+++ b/man/udevadm.xml
@@ -329,6 +329,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>
<varlistentry>
<term><option>-h</option></term>
<term><option>--help</option></term>
diff --git a/src/udev/udevadm-trigger.c b/src/udev/udevadm-trigger.c
index 7af9665f8..48564ba44 100644
--- a/src/udev/udevadm-trigger.c
+++ b/src/udev/udevadm-trigger.c
@@ -23,6 +23,7 @@
#include <errno.h>
#include <fcntl.h>
+#include "set.h"
#include "udev.h"
#include "udev-util.h"
#include "udevadm-util.h"
@@ -31,25 +32,36 @@
static int verbose;
static int dry_run;
-static void exec_list(struct udev_enumerate *udev_enumerate, const char *action) {
+static int exec_list(struct udev_enumerate *udev_enumerate, const char *action, Set *settle_set) {
struct udev_list_entry *entry;
+ int r;
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) {
+ r = set_put_strdup(settle_set, syspath);
+ if (r < 0)
+ return log_oom();
+ }
if (write(fd, action, strlen(action)) < 0)
log_debug_errno(errno, "error writing '%s' to '%s': %m", action, filename);
close(fd);
}
+
+ return 0;
}
static const char *keyval(const char *str, const char **val, char *buf, size_t size) {
@@ -85,6 +97,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);
}
@@ -107,6 +120,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' },
{ "help", no_argument, NULL, 'h' },
{}
};
@@ -116,13 +130,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)
+ if (!udev_enumerate)
return 1;
- while ((c = getopt_long(argc, argv, "vno:t:c:s:S:a:A:p:g:y:b:h", options, NULL)) >= 0) {
+ while ((c = getopt_long(argc, argv, "vno:t:c:s:S:a:A:p:g:y:b:wh", options, NULL)) >= 0) {
const char *key;
const char *val;
char buf[UTIL_PATH_SIZE];
@@ -208,7 +228,7 @@ static int adm_trigger(struct udev *udev, int argc, char *argv[]) {
_cleanup_udev_device_unref_ struct udev_device *dev;
dev = find_device(udev, optarg, "/sys");
- if (dev == NULL) {
+ if (!dev) {
log_error("unable to open the device '%s'", optarg);
return 2;
}
@@ -220,12 +240,15 @@ 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;
dev = find_device(udev, optarg, "/dev/");
- if (dev == NULL) {
+ if (!dev) {
log_error("unable to open the device '%s'", optarg);
return 2;
}
@@ -252,7 +275,7 @@ static int adm_trigger(struct udev *udev, int argc, char *argv[]) {
_cleanup_udev_device_unref_ struct udev_device *dev;
dev = find_device(udev, argv[optind], NULL);
- if (dev == NULL) {
+ if (!dev) {
log_error("unable to open the device '%s'", argv[optind]);
return 2;
}
@@ -264,18 +287,83 @@ 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) {
+ 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) {
+ 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");
}
+ r = exec_list(udev_enumerate, action, settle_set);
+ if (r < 0)
+ return 1;
+
+ 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)
+ 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 = {