| 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 = { |