summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMao <littlecvr@gmail.com>2018-02-01 17:33:13 +0800
committerZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>2018-02-09 11:59:03 +0100
commit792cc203a67edb201073351f5c766fce3d5eab45 (patch)
tree4e3050518771ab7139574032b196bfea5f1b8843
parent302af1c250c2c64f608187ab1efb8e4f1f442951 (diff)
downloadsystemd-792cc203a67edb201073351f5c766fce3d5eab45.tar.gz
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.
-rw-r--r--man/udevadm.xml11
-rw-r--r--src/udev/udevadm-trigger.c97
2 files changed, 100 insertions, 8 deletions
diff --git a/man/udevadm.xml b/man/udevadm.xml
index 753ddf98da..1495f556f3 100644
--- a/man/udevadm.xml
+++ b/man/udevadm.xml
@@ -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" />
diff --git a/src/udev/udevadm-trigger.c b/src/udev/udevadm-trigger.c
index f78a2ba437..c5f9797fcc 100644
--- a/src/udev/udevadm-trigger.c
+++ b/src/udev/udevadm-trigger.c
@@ -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 = {