1
1
mirror of https://github.com/systemd/systemd-stable.git synced 2025-01-11 05:17:44 +03:00

Merge pull request #22872 from yuwata/udevadm-wait

udevadm: introduce 'wait' command
This commit is contained in:
Yu Watanabe 2022-04-01 18:41:03 +09:00 committed by GitHub
commit b8529cf376
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 785 additions and 171 deletions

View File

@ -48,6 +48,9 @@
<cmdsynopsis>
<command>udevadm test-builtin <optional>options</optional> <replaceable>command</replaceable> <replaceable>devpath</replaceable></command>
</cmdsynopsis>
<cmdsynopsis>
<command>udevadm wait <optional>options</optional> <replaceable>device|syspath</replaceable></command>
</cmdsynopsis>
</refsynopsisdiv>
<refsect1><title>Description</title>
@ -405,15 +408,9 @@
<para>When <option>--initialized-nomatch</option> is specified, trigger events for devices
that are not initialized by <command>systemd-udevd</command> yet, and skip devices that
are already initialized.</para>
<para>Here, initialized devices are those for which at least one udev rule already
completed execution for any action but <literal>remove</literal> — that set a property
or other device setting (and thus has an entry in the udev device database). Devices are
no longer considered initialized if a <literal>remove</literal> action is seen for them
(which removes their entry in the udev device database). Note that devices that have no
udev rules are never considered initialized, but might still be announced via the sd-device
API (or similar). Typically, it is thus essential that applications which intend to use
such a match, make sure a suitable udev rule is installed that sets at least one property
on devices that shall be matched.</para>
<para>Typically, it is essential that applications which intend to use such a match, make
sure a suitable udev rule is installed that sets at least one property on devices that
shall be matched. See also Initialized Devices section below for more details.</para>
<para>WARNING: <option>--initialized-nomatch</option> can potentially save a significant
amount of time compared to re-triggering all devices in the system and e.g. can be used to
optimize boot time. However, this is not safe to be used in a boot sequence in general.
@ -694,6 +691,73 @@
<xi:include href="standard-options.xml" xpointer="help" />
</variablelist>
</refsect2>
<refsect2>
<title>udevadm wait
<arg choice="opt"><replaceable>options</replaceable></arg>
<arg choice="opt"><replaceable>device|syspath</replaceable></arg>
</title>
<para>Wait for devices or device symlinks being created and initialized by
<command>systemd-udevd</command>. Each device path must start with
<literal>/dev/</literal> or <literal>/sys/</literal>, e.g. <literal>/dev/sda</literal>,
<literal>/dev/disk/by-path/pci-0000:3c:00.0-nvme-1-part1</literal>,
<literal>/sys/devices/pci0000:00/0000:00:1f.6/net/eth0</literal>, or
<literal>/sys/class/net/eth0</literal>. This can take multiple devices. This may be useful for
waiting for devices being processed by <command>systemd-udevd</command> after e.g. partitioning
or formatting the devices.</para>
<variablelist>
<varlistentry>
<term><option>-t</option></term>
<term><option>--timeout=<replaceable>SECONDS</replaceable></option></term>
<listitem>
<para>Maximum number of seconds to wait for the specified devices or device symlinks being
created, initialized, or removed. The default value is <literal>infinity</literal>.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--initialized=<replaceable>BOOL</replaceable></option></term>
<listitem>
<para>Check if <command>systemd-udevd</command> initialized devices. Defaults to true. When
false, the command only checks if the specified devices exist. Set false to this setting if
there is no udev rules for the specified devices, as the devices will never be considered
as initialized in that case. See Initialized Devices section below for more details.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--removed</option></term>
<listitem>
<para>When specified, the command wait for devices being removed instead of created or
initialized. If this is specified, <option>--initialized=</option> will be ignored.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--settle</option></term>
<listitem>
<para>When specified, also watches the udev event queue, and wait for all queued events
being processed by <command>systemd-udevd</command>.</para>
</listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="help" />
</variablelist>
</refsect2>
</refsect1>
<refsect1>
<title>Initialized Devices</title>
<para>Initialized devices are those for which at least one udev rule already completed execution
for any action but <literal>remove</literal> — that set a property or other device setting (and
thus has an entry in the udev device database). Devices are no longer considered initialized if a
<literal>remove</literal> action is seen for them (which removes their entry in the udev device
database). Note that devices that have no udev rules are never considered initialized, but might
still be announced via the sd-device API (or similar).</para>
</refsect1>
<refsect1>

View File

@ -32,7 +32,7 @@ __get_all_sysdevs() {
__get_all_devs() {
local i
for i in /dev/* /dev/*/*; do
for i in /dev/* /dev/*/* /dev/*/*/*; do
echo $i
done
}
@ -64,9 +64,10 @@ _udevadm() {
[MONITOR_ARG]='-s --subsystem-match -t --tag-match'
[TEST]='-a --action -N --resolve-names'
[TEST_BUILTIN]='-a --action'
[WAIT]='-t --timeout --initialized=no --removed --settle'
)
local verbs=(info trigger settle control monitor test-builtin test)
local verbs=(info trigger settle control monitor test-builtin test wait)
local builtins=(blkid btrfs hwdb input_id keyboard kmod net_id net_setup_link path_id usb_id uaccess)
for ((i=0; i < COMP_CWORD; i++)); do
@ -245,6 +246,25 @@ _udevadm() {
fi
;;
'wait')
if __contains_word "$prev" ${OPTS[WAIT]}; then
case $prev in
*)
comps=''
;;
esac
COMPREPLY=( $(compgen -W '$comps' -- "$cur") )
return 0
fi
if [[ $cur = -* ]]; then
comps="${OPTS[COMMON]} ${OPTS[WAIT]}"
else
comps=$( __get_all_devs )
local IFS=$'\n'
fi
;;
*)
comps=${VERBS[*]}
;;

View File

@ -104,6 +104,17 @@ _udevadm_test-builtin(){
fi
}
(( $+functions[_udevadm_wait] )) ||
_udevadm_wait(){
_arguments \
'--timeout=[Maximum number of seconds to wait for the devices being created.]' \
'--initialized=[Wait for devices being initialized by systemd-udevd.]:boolean:(yes no)' \
'--removed[Wait for devices being removed.]' \
'--settle[Also wait for udev queue being empty.]' \
'--help[Print help text.]' \
'*::devpath:_files -P /dev/ -W /dev'
}
(( $+functions[_udevadm_mounts] )) ||
_udevadm_mounts(){
local dev_tmp dpath_tmp mp_tmp mline

View File

@ -3,6 +3,7 @@
#include <errno.h>
#include <fcntl.h>
#include <linux/btrfs.h>
#include <linux/fs.h>
#include <linux/magic.h>
#include <sys/ioctl.h>
#include <sys/resource.h>
@ -17,6 +18,7 @@
#include "io-util.h"
#include "macro.h"
#include "missing_fcntl.h"
#include "missing_fs.h"
#include "missing_syscall.h"
#include "parse-util.h"
#include "path-util.h"
@ -788,3 +790,24 @@ int btrfs_defrag_fd(int fd) {
return RET_NERRNO(ioctl(fd, BTRFS_IOC_DEFRAG, NULL));
}
int fd_get_diskseq(int fd, uint64_t *ret) {
uint64_t diskseq;
assert(fd >= 0);
assert(ret);
if (ioctl(fd, BLKGETDISKSEQ, &diskseq) < 0) {
/* Note that the kernel is weird: non-existing ioctls currently return EINVAL
* rather than ENOTTY on loopback block devices. They should fix that in the kernel,
* but in the meantime we accept both here. */
if (!ERRNO_IS_NOT_SUPPORTED(errno) && errno != EINVAL)
return -errno;
return -EOPNOTSUPP;
}
*ret = diskseq;
return 0;
}

View File

@ -109,6 +109,7 @@ static inline int make_null_stdio(void) {
int fd_reopen(int fd, int flags);
int read_nr_open(void);
int btrfs_defrag_fd(int fd);
int fd_get_diskseq(int fd, uint64_t *ret);
/* The maximum length a buffer for a /proc/self/fd/<fd> path needs */
#define PROC_FD_PATH_MAX \

View File

@ -6,6 +6,10 @@
#define RENAME_NOREPLACE (1 << 0)
#endif
#ifndef BLKGETDISKSEQ
#define BLKGETDISKSEQ _IOR(0x12,128,__u64)
#endif
/* linux/fs.h or sys/mount.h */
#ifndef MS_MOVE
#define MS_MOVE 8192

View File

@ -1,7 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <linux/fs.h>
#include <linux/loop.h>
#ifndef LOOP_CONFIGURE
@ -15,10 +14,6 @@ struct loop_config {
#define LOOP_CONFIGURE 0x4C0A
#endif
#ifndef BLKGETDISKSEQ
#define BLKGETDISKSEQ _IOR(0x12,128,__u64)
#endif
#ifndef LOOP_SET_STATUS_SETTABLE_FLAGS
#define LOOP_SET_STATUS_SETTABLE_FLAGS (LO_FLAGS_AUTOCLEAR | LO_FLAGS_PARTSCAN)
#endif

View File

@ -773,4 +773,7 @@ global:
LIBSYSTEMD_251 {
global:
sd_id128_to_uuid_string;
sd_device_new_from_devname;
sd_device_new_from_path;
sd_device_open;
} LIBSYSTEMD_250;

View File

@ -415,6 +415,46 @@ _public_ int sd_device_new_from_stat_rdev(sd_device **ret, const struct stat *st
return sd_device_new_from_devnum(ret, type, st->st_rdev);
}
_public_ int sd_device_new_from_devname(sd_device **ret, const char *devname) {
struct stat st;
assert_return(ret, -EINVAL);
assert_return(devname, -EINVAL);
/* This function actually accepts both devlinks and devnames, i.e. both symlinks and device
* nodes below /dev/. */
/* Also ignore when the specified path is "/dev". */
if (isempty(path_startswith(devname, "/dev")))
return -EINVAL;
if (device_path_parse_major_minor(devname, NULL, NULL) >= 0) {
_cleanup_free_ char *syspath = NULL;
/* Let's shortcut when "/dev/block/maj:min" or "/dev/char/maj:min" is specified.
* In that case, we directly convert the path to syspath, hence it is not necessary
* that the specified path exists. So, this works fine without udevd being running. */
syspath = path_join("/sys", devname);
return sd_device_new_from_syspath(ret, syspath);
}
if (stat(devname, &st) < 0)
return ERRNO_IS_DEVICE_ABSENT(errno) ? -ENODEV : -errno;
return sd_device_new_from_stat_rdev(ret, &st);
}
_public_ int sd_device_new_from_path(sd_device **ret, const char *path) {
assert_return(ret, -EINVAL);
assert_return(path, -EINVAL);
if (path_startswith(path, "/dev"))
return sd_device_new_from_devname(ret, path);
return sd_device_new_from_syspath(ret, path);
}
int device_set_devtype(sd_device *device, const char *devtype) {
_cleanup_free_ char *t = NULL;
int r;
@ -2197,3 +2237,68 @@ _public_ int sd_device_trigger_with_uuid(
*ret_uuid = u;
return 0;
}
_public_ int sd_device_open(sd_device *device, int flags) {
_cleanup_close_ int fd = -1, fd2 = -1;
const char *devname, *subsystem = NULL;
uint64_t q, diskseq = 0;
struct stat st;
dev_t devnum;
int r;
assert_return(device, -EINVAL);
assert_return(FLAGS_SET(flags, O_PATH) || !FLAGS_SET(flags, O_NOFOLLOW), -EINVAL);
r = sd_device_get_devname(device, &devname);
if (r == -ENOENT)
return -ENOEXEC;
if (r < 0)
return r;
r = sd_device_get_devnum(device, &devnum);
if (r == -ENOENT)
return -ENOEXEC;
if (r < 0)
return r;
r = sd_device_get_subsystem(device, &subsystem);
if (r < 0 && r != -ENOENT)
return r;
r = sd_device_get_diskseq(device, &diskseq);
if (r < 0 && r != -ENOENT)
return r;
fd = open(devname, FLAGS_SET(flags, O_PATH) ? flags : O_CLOEXEC|O_NOFOLLOW|O_PATH);
if (fd < 0)
return -errno;
if (fstat(fd, &st) < 0)
return -errno;
if (st.st_rdev != devnum)
return -ENXIO;
if (streq_ptr(subsystem, "block") ? !S_ISBLK(st.st_mode) : !S_ISCHR(st.st_mode))
return -ENXIO;
/* If flags has O_PATH, then we cannot check diskseq. Let's return earlier. */
if (FLAGS_SET(flags, O_PATH))
return TAKE_FD(fd);
fd2 = open(FORMAT_PROC_FD_PATH(fd), flags);
if (fd2 < 0)
return -errno;
if (diskseq == 0)
return TAKE_FD(fd2);
r = fd_get_diskseq(fd2, &q);
if (r < 0)
return r;
if (q != diskseq)
return -ENXIO;
return TAKE_FD(fd2);
}

View File

@ -1,56 +1,139 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <fcntl.h>
#include "device-enumerator-private.h"
#include "device-internal.h"
#include "device-private.h"
#include "device-util.h"
#include "errno-util.h"
#include "fd-util.h"
#include "hashmap.h"
#include "nulstr-util.h"
#include "path-util.h"
#include "string-util.h"
#include "tests.h"
#include "time-util.h"
static void test_sd_device_one(sd_device *d) {
_cleanup_(sd_device_unrefp) sd_device *device_from_id = NULL;
const char *syspath, *subsystem, *id, *val;
_cleanup_(sd_device_unrefp) sd_device *dev = NULL;
const char *syspath, *sysname, *subsystem = NULL, *id, *devname, *val;
bool is_block = false;
dev_t devnum;
usec_t usec;
int i, r;
assert_se(sd_device_get_syspath(d, &syspath) >= 0);
assert_se(path_startswith(syspath, "/sys"));
assert_se(sd_device_new_from_syspath(&dev, syspath) >= 0);
assert_se(sd_device_get_syspath(dev, &val) >= 0);
assert_se(streq(syspath, val));
dev = sd_device_unref(dev);
assert_se(sd_device_new_from_path(&dev, syspath) >= 0);
assert_se(sd_device_get_syspath(dev, &val) >= 0);
assert_se(streq(syspath, val));
dev = sd_device_unref(dev);
assert_se(sd_device_get_sysname(d, &sysname) >= 0);
r = sd_device_get_subsystem(d, &subsystem);
assert_se(r >= 0 || r == -ENOENT);
if (r >= 0) {
const char *name;
if (streq(subsystem, "drivers"))
name = strjoina(d->driver_subsystem, ":", sysname);
else
name = sysname;
assert_se(sd_device_new_from_subsystem_sysname(&dev, subsystem, name) >= 0);
assert_se(sd_device_get_syspath(dev, &val) >= 0);
assert_se(streq(syspath, val));
dev = sd_device_unref(dev);
} else
assert_se(r == -ENOENT);
is_block = streq_ptr(subsystem, "block");
r = sd_device_get_devname(d, &devname);
if (r >= 0) {
r = sd_device_new_from_devname(&dev, devname);
if (r >= 0) {
assert_se(sd_device_get_syspath(dev, &val) >= 0);
assert_se(streq(syspath, val));
dev = sd_device_unref(dev);
} else
assert_se(r == -ENODEV || ERRNO_IS_PRIVILEGE(r));
r = sd_device_new_from_path(&dev, devname);
if (r >= 0) {
assert_se(sd_device_get_syspath(dev, &val) >= 0);
assert_se(streq(syspath, val));
dev = sd_device_unref(dev);
_cleanup_close_ int fd = -1;
fd = sd_device_open(d, O_CLOEXEC| O_NONBLOCK | (is_block ? O_RDONLY : O_NOCTTY | O_PATH));
assert_se(fd >= 0 || ERRNO_IS_PRIVILEGE(fd));
} else
assert_se(r == -ENODEV || ERRNO_IS_PRIVILEGE(r));
} else
assert_se(r == -ENOENT);
r = sd_device_get_devnum(d, &devnum);
if (r >= 0) {
_cleanup_free_ char *p = NULL;
assert_se(major(devnum) > 0);
assert_se(sd_device_new_from_devnum(&dev, is_block ? 'b' : 'c', devnum) >= 0);
assert_se(sd_device_get_syspath(dev, &val) >= 0);
assert_se(streq(syspath, val));
dev = sd_device_unref(dev);
assert_se(asprintf(&p, "/dev/%s/%u:%u", is_block ? "block" : "char", major(devnum), minor(devnum)) >= 0);
assert_se(sd_device_new_from_devname(&dev, p) >= 0);
assert_se(sd_device_get_syspath(dev, &val) >= 0);
assert_se(streq(syspath, val));
dev = sd_device_unref(dev);
assert_se(sd_device_new_from_path(&dev, p) >= 0);
assert_se(sd_device_get_syspath(dev, &val) >= 0);
assert_se(streq(syspath, val));
dev = sd_device_unref(dev);
} else
assert_se(r == -ENOENT);
r = sd_device_get_ifindex(d, &i);
if (r >= 0) {
assert_se(i > 0);
assert_se(sd_device_new_from_ifindex(&dev, i) >= 0);
assert_se(sd_device_get_syspath(dev, &val) >= 0);
assert_se(streq(syspath, val));
dev = sd_device_unref(dev);
assert_se(sd_device_new_from_ifname(&dev, sysname) >= 0);
assert_se(sd_device_get_syspath(dev, &val) >= 0);
assert_se(streq(syspath, val));
dev = sd_device_unref(dev);
} else
assert_se(r == -ENOENT);
assert_se(sd_device_get_devpath(d, &val) >= 0);
r = sd_device_get_devtype(d, &val);
assert_se(r >= 0 || r == -ENOENT);
r = sd_device_get_devnum(d, &devnum);
assert_se((r >= 0 && major(devnum) > 0) || r == -ENOENT);
r = sd_device_get_ifindex(d, &i);
assert_se((r >= 0 && i > 0) || r == -ENOENT);
r = sd_device_get_driver(d, &val);
assert_se(r >= 0 || r == -ENOENT);
assert_se(sd_device_get_devpath(d, &val) >= 0);
r = sd_device_get_devname(d, &val);
assert_se(r >= 0 || r == -ENOENT);
assert_se(sd_device_get_sysname(d, &val) >= 0);
r = sd_device_get_sysnum(d, &val);
assert_se(r >= 0 || r == -ENOENT);
i = sd_device_get_is_initialized(d);
assert_se(i >= 0);
if (i > 0) {
r = sd_device_get_is_initialized(d);
if (r > 0) {
r = sd_device_get_usec_since_initialized(d, &usec);
assert_se((r >= 0 && usec > 0) || r == -ENODATA);
}
} else
assert(r == 0);
r = sd_device_get_sysattr_value(d, "name_assign_type", &val);
assert_se(r >= 0 || ERRNO_IS_PRIVILEGE(r) || IN_SET(r, -ENOENT, -EINVAL));
@ -58,14 +141,9 @@ static void test_sd_device_one(sd_device *d) {
r = sd_device_get_property_value(d, "ID_NET_DRIVER", &val);
assert_se(r >= 0 || r == -ENOENT);
r = device_get_device_id(d, &id);
assert_se(r >= 0);
r = sd_device_new_from_device_id(&device_from_id, id);
assert_se(r >= 0);
r = sd_device_get_syspath(device_from_id, &val);
assert_se(r >= 0);
assert_se(device_get_device_id(d, &id) >= 0);
assert_se(sd_device_new_from_device_id(&dev, id) >= 0);
assert_se(sd_device_get_syspath(dev, &val) >= 0);
assert_se(streq(syspath, val));
log_info("syspath:%s subsystem:%s id:%s initialized:%s", syspath, strna(subsystem), id, yes_no(i));

View File

@ -128,27 +128,6 @@ static int device_has_block_children(sd_device *d) {
return 0;
}
static int loop_get_diskseq(int fd, uint64_t *ret_diskseq) {
uint64_t diskseq;
assert(fd >= 0);
assert(ret_diskseq);
if (ioctl(fd, BLKGETDISKSEQ, &diskseq) < 0) {
/* Note that the kernel is weird: non-existing ioctls currently return EINVAL
* rather than ENOTTY on loopback block devices. They should fix that in the kernel,
* but in the meantime we accept both here. */
if (!ERRNO_IS_NOT_SUPPORTED(errno) && errno != EINVAL)
return -errno;
return -EOPNOTSUPP;
}
*ret_diskseq = diskseq;
return 0;
}
static int loop_configure(
int fd,
int nr,
@ -453,7 +432,7 @@ static int loop_device_make_internal(
if (copy < 0)
return copy;
r = loop_get_diskseq(copy, &diskseq);
r = fd_get_diskseq(copy, &diskseq);
if (r < 0 && r != -EOPNOTSUPP)
return r;
@ -592,7 +571,7 @@ static int loop_device_make_internal(
assert(S_ISBLK(st.st_mode));
uint64_t diskseq = 0;
r = loop_get_diskseq(loop_with_fd, &diskseq);
r = fd_get_diskseq(loop_with_fd, &diskseq);
if (r < 0 && r != -EOPNOTSUPP)
return r;

View File

@ -62,6 +62,8 @@ int sd_device_new_from_devnum(sd_device **ret, char type, dev_t devnum);
int sd_device_new_from_subsystem_sysname(sd_device **ret, const char *subsystem, const char *sysname);
int sd_device_new_from_device_id(sd_device **ret, const char *id);
int sd_device_new_from_stat_rdev(sd_device **ret, const struct stat *st);
int sd_device_new_from_devname(sd_device **ret, const char *devname);
int sd_device_new_from_path(sd_device **ret, const char *path);
int sd_device_new_from_ifname(sd_device **ret, const char *ifname);
int sd_device_new_from_ifindex(sd_device **ret, int ifindex);
@ -107,6 +109,7 @@ int sd_device_set_sysattr_value(sd_device *device, const char *sysattr, const ch
int sd_device_set_sysattr_valuef(sd_device *device, const char *sysattr, const char *format, ...) _sd_printf_(3, 4);
int sd_device_trigger(sd_device *device, sd_device_action_t action);
int sd_device_trigger_with_uuid(sd_device *device, sd_device_action_t action, sd_id128_t *ret_uuid);
int sd_device_open(sd_device *device, int flags);
/* device enumerator */

View File

@ -13,6 +13,7 @@ udevadm_sources = files(
'udevadm-trigger.c',
'udevadm-util.c',
'udevadm-util.h',
'udevadm-wait.c',
'udevd.c',
)

View File

@ -302,11 +302,12 @@ static int builtin_blkid(sd_device *dev, sd_netlink **rtnl, int argc, char *argv
if (r < 0)
return log_device_debug_errno(dev, r, "Failed to get device name: %m");
fd = open(devnode, O_RDONLY|O_CLOEXEC|O_NONBLOCK);
fd = sd_device_open(dev, O_RDONLY|O_CLOEXEC|O_NONBLOCK);
if (fd < 0) {
log_device_debug_errno(dev, errno, "Failed to open block device %s%s: %m",
devnode, errno == ENOENT ? ", ignoring" : "");
return errno == ENOENT ? 0 : -errno;
bool ignore = ERRNO_IS_DEVICE_ABSENT(fd);
log_device_debug_errno(dev, fd, "Failed to open block device %s%s: %m",
devnode, ignore ? ", ignoring" : "");
return ignore ? 0 : fd;
}
errno = 0;

View File

@ -45,12 +45,12 @@ static int abs_size_mm(const struct input_absinfo *absinfo) {
return (absinfo->maximum - absinfo->minimum) / absinfo->resolution;
}
static void extract_info(sd_device *dev, const char *devpath, bool test) {
static void extract_info(sd_device *dev, bool test) {
char width[DECIMAL_STR_MAX(int)], height[DECIMAL_STR_MAX(int)];
struct input_absinfo xabsinfo = {}, yabsinfo = {};
_cleanup_close_ int fd = -1;
fd = open(devpath, O_RDONLY|O_CLOEXEC);
fd = sd_device_open(dev, O_RDONLY|O_CLOEXEC|O_NONBLOCK);
if (fd < 0)
return;
@ -330,7 +330,7 @@ static int builtin_input_id(sd_device *dev, sd_netlink **rtnl, int argc, char *a
unsigned long bitmask_key[NBITS(KEY_MAX)];
unsigned long bitmask_rel[NBITS(REL_MAX)];
unsigned long bitmask_props[NBITS(INPUT_PROP_MAX)];
const char *sysname, *devnode;
const char *sysname;
bool is_pointer;
bool is_key;
@ -375,10 +375,9 @@ static int builtin_input_id(sd_device *dev, sd_netlink **rtnl, int argc, char *a
}
if (sd_device_get_devname(dev, &devnode) >= 0 &&
sd_device_get_sysname(dev, &sysname) >= 0 &&
if (sd_device_get_sysname(dev, &sysname) >= 0 &&
startswith(sysname, "event"))
extract_info(dev, devnode, test);
extract_info(dev, test);
return 0;
}

View File

@ -195,9 +195,9 @@ static int builtin_keyboard(sd_device *dev, sd_netlink **rtnl, int argc, char *a
}
if (fd < 0) {
fd = open(node, O_RDWR|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
fd = sd_device_open(dev, O_RDWR|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
if (fd < 0)
return log_device_error_errno(dev, errno, "Failed to open device '%s': %m", node);
return log_device_error_errno(dev, fd, "Failed to open device '%s': %m", node);
}
(void) map_keycode(dev, fd, scancode, keycode);
@ -212,9 +212,9 @@ static int builtin_keyboard(sd_device *dev, sd_netlink **rtnl, int argc, char *a
}
if (fd < 0) {
fd = open(node, O_RDWR|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
fd = sd_device_open(dev, O_RDWR|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
if (fd < 0)
return log_device_error_errno(dev, errno, "Failed to open device '%s': %m", node);
return log_device_error_errno(dev, fd, "Failed to open device '%s': %m", node);
}
if (has_abs == -1) {

View File

@ -598,7 +598,7 @@ int udev_node_apply_permissions(
gid_t gid,
OrderedHashmap *seclabel_list) {
const char *devnode, *subsystem, *id = NULL;
const char *devnode, *subsystem;
bool apply_mode, apply_uid, apply_gid;
_cleanup_close_ int node_fd = -1;
struct stat stats;
@ -616,33 +616,25 @@ int udev_node_apply_permissions(
r = sd_device_get_devnum(dev, &devnum);
if (r < 0)
return log_device_debug_errno(dev, r, "Failed to get devnum: %m");
(void) device_get_device_id(dev, &id);
if (streq(subsystem, "block"))
mode |= S_IFBLK;
else
mode |= S_IFCHR;
node_fd = open(devnode, O_PATH|O_NOFOLLOW|O_CLOEXEC);
node_fd = sd_device_open(dev, O_PATH|O_CLOEXEC);
if (node_fd < 0) {
if (errno == ENOENT) {
log_device_debug_errno(dev, errno, "Device node %s is missing, skipping handling.", devnode);
if (ERRNO_IS_DEVICE_ABSENT(node_fd)) {
log_device_debug_errno(dev, node_fd, "Device node %s is missing, skipping handling.", devnode);
return 0; /* This is necessarily racey, so ignore missing the device */
}
return log_device_debug_errno(dev, errno, "Cannot open node %s: %m", devnode);
return log_device_debug_errno(dev, node_fd, "Cannot open node %s: %m", devnode);
}
if (fstat(node_fd, &stats) < 0)
return log_device_debug_errno(dev, errno, "cannot stat() node %s: %m", devnode);
if ((mode != MODE_INVALID && (stats.st_mode & S_IFMT) != (mode & S_IFMT)) || stats.st_rdev != devnum) {
log_device_debug(dev, "Found node '%s' with non-matching devnum %s, skipping handling.",
devnode, strna(id));
return 0; /* We might process a device that already got replaced by the time we have a look
* at it, handle this gracefully and step away. */
}
apply_mode = mode != MODE_INVALID && (stats.st_mode & 0777) != (mode & 0777);
apply_uid = uid_is_valid(uid) && stats.st_uid != uid;
apply_gid = gid_is_valid(gid) && stats.st_gid != gid;

378
src/udev/udevadm-wait.c Normal file
View File

@ -0,0 +1,378 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include <getopt.h>
#include <unistd.h>
#include "sd-event.h"
#include "alloc-util.h"
#include "chase-symlinks.h"
#include "device-util.h"
#include "errno-util.h"
#include "fd-util.h"
#include "inotify-util.h"
#include "parse-util.h"
#include "path-util.h"
#include "static-destruct.h"
#include "string-table.h"
#include "strv.h"
#include "udev-util.h"
#include "udevadm.h"
typedef enum WaitUntil {
WAIT_UNTIL_INITIALIZED,
WAIT_UNTIL_ADDED,
WAIT_UNTIL_REMOVED,
_WAIT_UNTIL_MAX,
_WAIT_UNTIL_INVALID = -EINVAL,
} WaitUntil;
static WaitUntil arg_wait_until = WAIT_UNTIL_INITIALIZED;
static usec_t arg_timeout_usec = USEC_INFINITY;
static bool arg_settle = false;
static char **arg_devices = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_devices, strv_freep);
static const char * const wait_until_table[_WAIT_UNTIL_MAX] = {
[WAIT_UNTIL_INITIALIZED] = "initialized",
[WAIT_UNTIL_ADDED] = "added",
[WAIT_UNTIL_REMOVED] = "removed",
};
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(wait_until, WaitUntil);
static int check_device(const char *path) {
_cleanup_(sd_device_unrefp) sd_device *dev = NULL;
int r;
assert(path);
r = sd_device_new_from_path(&dev, path);
if (r == -ENODEV)
return arg_wait_until == WAIT_UNTIL_REMOVED;
if (r < 0)
return r;
switch (arg_wait_until) {
case WAIT_UNTIL_INITIALIZED:
return sd_device_get_is_initialized(dev);
case WAIT_UNTIL_ADDED:
return true;
case WAIT_UNTIL_REMOVED:
return false;
default:
assert_not_reached();
}
}
static bool check(void) {
int r;
if (arg_settle) {
r = udev_queue_is_empty();
if (r == 0)
return false;
if (r < 0)
log_warning_errno(r, "Failed to check if udev queue is empty, assuming empty: %m");
}
STRV_FOREACH(p, arg_devices) {
r = check_device(*p);
if (r <= 0) {
if (r < 0)
log_warning_errno(r, "Failed to check if device \"%s\" is %s, assuming not %s: %m",
*p,
wait_until_to_string(arg_wait_until),
wait_until_to_string(arg_wait_until));
return false;
}
}
return true;
}
static int check_and_exit(sd_event *event) {
assert(event);
if (check())
return sd_event_exit(event, 0);
return 0;
}
static int device_monitor_handler(sd_device_monitor *monitor, sd_device *device, void *userdata) {
const char *name;
int r;
assert(monitor);
assert(device);
if (device_for_action(device, SD_DEVICE_REMOVE) != (arg_wait_until == WAIT_UNTIL_REMOVED))
return 0;
if (arg_wait_until == WAIT_UNTIL_REMOVED)
/* On removed event, the received device may not contain enough information.
* Let's unconditionally check all requested devices are removed. */
return check_and_exit(sd_device_monitor_get_event(monitor));
/* For other events, at first check if the received device matches with the requested devices,
* to avoid calling check() so many times within a short time. */
r = sd_device_get_sysname(device, &name);
if (r < 0) {
log_device_warning_errno(device, r, "Failed to get sysname of received device, ignoring: %m");
return 0;
}
STRV_FOREACH(p, arg_devices) {
const char *s;
if (!path_startswith(*p, "/sys"))
continue;
r = path_find_last_component(*p, false, NULL, &s);
if (r < 0) {
log_warning_errno(r, "Failed to extract filename from \"%s\", ignoring: %m", *p);
continue;
}
if (r == 0)
continue;
if (strneq(s, name, r))
return check_and_exit(sd_device_monitor_get_event(monitor));
}
r = sd_device_get_devname(device, &name);
if (r < 0) {
if (r != -ENOENT)
log_device_warning_errno(device, r, "Failed to get devname of received device, ignoring: %m");
return 0;
}
if (path_strv_contains(arg_devices, name))
return check_and_exit(sd_device_monitor_get_event(monitor));
STRV_FOREACH(p, arg_devices) {
const char *link;
if (!path_startswith(*p, "/dev"))
continue;
FOREACH_DEVICE_DEVLINK(device, link)
if (path_equal(*p, link))
return check_and_exit(sd_device_monitor_get_event(monitor));
}
return 0;
}
static int setup_monitor(sd_event *event, sd_device_monitor **ret) {
_cleanup_(sd_device_monitor_unrefp) sd_device_monitor *monitor = NULL;
int r;
assert(event);
assert(ret);
r = sd_device_monitor_new(&monitor);
if (r < 0)
return r;
(void) sd_device_monitor_set_receive_buffer_size(monitor, 128*1024*1024);
r = sd_device_monitor_attach_event(monitor, event);
if (r < 0)
return r;
r = sd_device_monitor_start(monitor, device_monitor_handler, NULL);
if (r < 0)
return r;
r = sd_event_source_set_description(sd_device_monitor_get_event_source(monitor),
"device-monitor-event-source");
if (r < 0)
return r;
*ret = TAKE_PTR(monitor);
return 0;
}
static int on_inotify(sd_event_source *s, const struct inotify_event *event, void *userdata) {
return check_and_exit(sd_event_source_get_event(s));
}
static int setup_inotify(sd_event *event) {
_cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL;
int r;
assert(event);
if (!arg_settle)
return 0;
r = sd_event_add_inotify(event, &s, "/run/udev" , IN_CREATE | IN_DELETE, on_inotify, NULL);
if (r < 0)
return r;
r = sd_event_source_set_description(s, "inotify-event-source");
if (r < 0)
return r;
return sd_event_source_set_floating(s, true);
}
static int setup_timer(sd_event *event) {
_cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL;
int r;
assert(event);
if (arg_timeout_usec == USEC_INFINITY)
return 0;
r = sd_event_add_time_relative(event, &s, CLOCK_BOOTTIME, arg_timeout_usec, 0,
NULL, INT_TO_PTR(-ETIMEDOUT));
if (r < 0)
return r;
r = sd_event_source_set_description(s, "timeout-event-source");
if (r < 0)
return r;
return sd_event_source_set_floating(s, true);
}
static int help(void) {
printf("%s wait [OPTIONS] DEVICE [DEVICE…]\n\n"
"Wait for devices or device symlinks being created.\n\n"
" -h --help Print this message\n"
" -V --version Print version of the program\n"
" -t --timeout=SEC Maximum time to wait for the device\n"
" --initialized=BOOL Wait for devices being initialized by systemd-udevd\n"
" --removed Wait for devices being removed\n"
" --settle Also wait for all queued events being processed\n",
program_invocation_short_name);
return 0;
}
static int parse_argv(int argc, char *argv[]) {
enum {
ARG_INITIALIZED = 0x100,
ARG_REMOVED,
ARG_SETTLE,
};
static const struct option options[] = {
{ "timeout", required_argument, NULL, 't' },
{ "initialized", required_argument, NULL, ARG_INITIALIZED },
{ "removed", no_argument, NULL, ARG_REMOVED },
{ "settle", no_argument, NULL, ARG_SETTLE },
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, 'V' },
{}
};
int c, r;
while ((c = getopt_long(argc, argv, "t:hV", options, NULL)) >= 0)
switch (c) {
case 't':
r = parse_sec(optarg, &arg_timeout_usec);
if (r < 0)
return log_error_errno(r, "Failed to parse -t/--timeout= parameter: %s", optarg);
break;
case ARG_INITIALIZED:
r = parse_boolean(optarg);
if (r < 0)
return log_error_errno(r, "Failed to parse --initialized= parameter: %s", optarg);
arg_wait_until = r ? WAIT_UNTIL_INITIALIZED : WAIT_UNTIL_ADDED;
break;
case ARG_REMOVED:
arg_wait_until = WAIT_UNTIL_REMOVED;
break;
case ARG_SETTLE:
arg_settle = true;
break;
case 'V':
return print_version();
case 'h':
return help();
case '?':
return -EINVAL;
default:
assert_not_reached();
}
if (optind >= argc)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Too few arguments, expected at least one device path or device symlink.");
arg_devices = strv_copy(argv + optind);
if (!arg_devices)
return log_oom();
return 1; /* work to do */
}
int wait_main(int argc, char *argv[], void *userdata) {
_cleanup_(sd_device_monitor_unrefp) sd_device_monitor *monitor = NULL;
_cleanup_(sd_event_unrefp) sd_event *event = NULL;
int r;
r = parse_argv(argc, argv);
if (r <= 0)
return r;
STRV_FOREACH(p, arg_devices) {
path_simplify(*p);
if (!path_is_safe(*p))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Device path cannot contain \"..\".");
if (!is_device_path(*p))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Specified path \"%s\" does not start with \"/dev/\" or \"/sys/\".", *p);
}
/* Check before configuring event sources, as devices may be already initialized. */
if (check())
return 0;
r = sd_event_default(&event);
if (r < 0)
return log_error_errno(r, "Failed to initialize sd-event: %m");
r = setup_timer(event);
if (r < 0)
return log_error_errno(r, "Failed to set up timeout: %m");
r = setup_inotify(event);
if (r < 0)
return log_error_errno(r, "Failed to set up inotify: %m");
r = setup_monitor(event, &monitor);
if (r < 0)
return log_error_errno(r, "Failed to set up device monitor: %m");
/* Check before entering the event loop, as devices may be initialized during setting up event sources. */
if (check())
return 0;
r = sd_event_loop(event);
if (r == -ETIMEDOUT)
return log_error_errno(r, "Timed out for waiting devices being %s.",
wait_until_to_string(arg_wait_until));
if (r < 0)
return log_error_errno(r, "Event loop failed: %m");
return 0;
}

View File

@ -26,6 +26,7 @@ static int help(void) {
{ "monitor", "Listen to kernel and udev events" },
{ "test", "Test an event run" },
{ "test-builtin", "Test a built-in command" },
{ "wait", "Wait for device or device symlink" },
};
_cleanup_free_ char *link = NULL;
@ -101,6 +102,7 @@ static int udevadm_main(int argc, char *argv[]) {
{ "hwdb", VERB_ANY, VERB_ANY, 0, hwdb_main },
{ "test", VERB_ANY, VERB_ANY, 0, test_main },
{ "test-builtin", VERB_ANY, VERB_ANY, 0, builtin_main },
{ "wait", VERB_ANY, VERB_ANY, 0, wait_main },
{ "version", VERB_ANY, VERB_ANY, 0, version_main },
{ "help", VERB_ANY, VERB_ANY, 0, help_main },
{}

View File

@ -13,6 +13,7 @@ int monitor_main(int argc, char *argv[], void *userdata);
int hwdb_main(int argc, char *argv[], void *userdata);
int test_main(int argc, char *argv[], void *userdata);
int builtin_main(int argc, char *argv[], void *userdata);
int wait_main(int argc, char *argv[], void *userdata);
static inline int print_version(void) {
/* Dracut relies on the version being a single integer */

View File

@ -422,12 +422,11 @@ static int worker_send_result(Manager *manager, int result) {
return loop_write(manager->worker_watch[WRITE_END], &result, sizeof(result), false);
}
static int device_get_whole_disk(sd_device *dev, const char **ret) {
static int device_get_whole_disk(sd_device *dev, sd_device **ret_device, const char **ret_devname) {
const char *val;
int r;
assert(dev);
assert(ret);
if (device_for_action(dev, SD_DEVICE_REMOVE))
goto irrelevant;
@ -463,16 +462,23 @@ static int device_get_whole_disk(sd_device *dev, const char **ret) {
if (r < 0)
return log_device_debug_errno(dev, r, "Failed to get devname: %m");
*ret = val;
if (ret_device)
*ret_device = dev;
if (ret_devname)
*ret_devname = val;
return 1;
irrelevant:
*ret = NULL;
if (ret_device)
*ret_device = NULL;
if (ret_devname)
*ret_devname = NULL;
return 0;
}
static int worker_lock_whole_disk(sd_device *dev, int *ret_fd) {
_cleanup_close_ int fd = -1;
sd_device *dev_whole_disk;
const char *val;
int r;
@ -484,19 +490,19 @@ static int worker_lock_whole_disk(sd_device *dev, int *ret_fd) {
* event handling; in the case udev acquired the lock, the external process can block until udev has
* finished its event handling. */
r = device_get_whole_disk(dev, &val);
r = device_get_whole_disk(dev, &dev_whole_disk, &val);
if (r < 0)
return r;
if (r == 0)
goto nolock;
fd = open(val, O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NONBLOCK);
fd = sd_device_open(dev_whole_disk, O_RDONLY|O_CLOEXEC|O_NONBLOCK);
if (fd < 0) {
bool ignore = ERRNO_IS_DEVICE_ABSENT(errno);
bool ignore = ERRNO_IS_DEVICE_ABSENT(fd);
log_device_debug_errno(dev, errno, "Failed to open '%s'%s: %m", val, ignore ? ", ignoring" : "");
log_device_debug_errno(dev, fd, "Failed to open '%s'%s: %m", val, ignore ? ", ignoring" : "");
if (!ignore)
return -errno;
return fd;
goto nolock;
}
@ -545,15 +551,9 @@ static int worker_mark_block_device_read_only(sd_device *dev) {
if (STARTSWITH_SET(val, "dm-", "md", "drbd", "loop", "nbd", "zram"))
return 0;
r = sd_device_get_devname(dev, &val);
if (r == -ENOENT)
return 0;
if (r < 0)
return log_device_debug_errno(dev, r, "Failed to get devname: %m");
fd = open(val, O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NONBLOCK);
fd = sd_device_open(dev, O_RDONLY|O_CLOEXEC|O_NONBLOCK);
if (fd < 0)
return log_device_debug_errno(dev, errno, "Failed to open '%s', ignoring: %m", val);
return log_device_debug_errno(dev, fd, "Failed to open '%s', ignoring: %m", val);
if (ioctl(fd, BLKROSET, &state) < 0)
return log_device_warning_errno(dev, errno, "Failed to mark block device '%s' read-only: %m", val);
@ -1093,7 +1093,7 @@ static int event_queue_assume_block_device_unlocked(Manager *manager, sd_device
* device is not locked anymore. The assumption may not be true, but that should not cause any
* issues, as in that case events will be requeued soon. */
r = device_get_whole_disk(dev, &devname);
r = device_get_whole_disk(dev, NULL, &devname);
if (r <= 0)
return r;
@ -1106,7 +1106,7 @@ static int event_queue_assume_block_device_unlocked(Manager *manager, sd_device
if (event->retry_again_next_usec == 0)
continue;
if (device_get_whole_disk(event->dev, &event_devname) <= 0)
if (device_get_whole_disk(event->dev, NULL, &event_devname) <= 0)
continue;
if (!streq(devname, event_devname))
@ -1403,18 +1403,13 @@ static int synthesize_change(sd_device *dev) {
!startswith(sysname, "dm-")) {
_cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL;
bool part_table_read = false, has_partitions = false;
const char *devname;
sd_device *d;
int fd;
r = sd_device_get_devname(dev, &devname);
if (r < 0)
return r;
/* Try to re-read the partition table. This only succeeds if none of the devices is
* busy. The kernel returns 0 if no partition table is found, and we will not get an
* event for the disk. */
fd = open(devname, O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NONBLOCK);
fd = sd_device_open(dev, O_RDONLY|O_CLOEXEC|O_NONBLOCK);
if (fd >= 0) {
r = flock(fd, LOCK_EX|LOCK_NB);
if (r >= 0)

View File

@ -41,23 +41,6 @@ helper_check_device_symlinks() {(
done < <(find "${paths[@]}" -type l)
)}
# Wait for a specific device link to appear
# Arguments:
# $1 - device path
# $2 - number of retries (default: 10)
helper_wait_for_dev() {
local dev="${1:?}"
local ntries="${2:-10}"
local i
for ((i = 0; i < ntries; i++)); do
test ! -e "$dev" || return 0
sleep .2
done
return 1
}
# Wrapper around `helper_wait_for_lvm_activate()` and `helper_wait_for_pvscan()`
# functions to cover differences between pre and post lvm 2.03.14, which introduced
# a new way of vgroup autoactivation
@ -583,15 +566,7 @@ testcase_iscsi_lvm() {
# Configure the iSCSI initiator
iscsiadm --mode discoverydb --type sendtargets --portal "$target_ip" --discover
iscsiadm --mode node --targetname "$target_name" --portal "$target_ip:$target_port" --login
udevadm settle
# Check if all device symlinks are valid and if all expected device symlinks exist
for link in "${expected_symlinks[@]}"; do
# We need to do some active waiting anyway, as it may take kernel a bit
# to attach the newly connected SCSI devices
helper_wait_for_dev "$link"
test -e "$link"
done
udevadm settle
udevadm wait --settle --timeout=30 "${expected_symlinks[@]}"
helper_check_device_symlinks
# Cleanup
iscsiadm --mode node --targetname "$target_name" --portal "$target_ip:$target_port" --logout
@ -626,15 +601,7 @@ testcase_iscsi_lvm() {
# Configure the iSCSI initiator
iscsiadm --mode discoverydb --type sendtargets --portal "$target_ip" --discover
iscsiadm --mode node --targetname "$target_name" --portal "$target_ip:$target_port" --login
udevadm settle
# Check if all device symlinks are valid and if all expected device symlinks exist
for link in "${expected_symlinks[@]}"; do
# We need to do some active waiting anyway, as it may take kernel a bit
# to attach the newly connected SCSI devices
helper_wait_for_dev "$link"
test -e "$link"
done
udevadm settle
udevadm wait --settle --timeout=30 "${expected_symlinks[@]}"
helper_check_device_symlinks
# Add all iSCSI devices into a LVM volume group, create two logical volumes,
# and check if necessary symlinks exist (and are valid)
@ -658,24 +625,16 @@ testcase_iscsi_lvm() {
# "Reset" the DM state, since we yanked the backing storage from under the LVM,
# so the currently active VGs/LVs are invalid
dmsetup remove_all --deferred
udevadm settle
# The LVM and iSCSI related symlinks should be gone
test ! -e "/dev/$vgroup"
test ! -e "/dev/disk/by-label/mylvpart1"
for link in "${expected_symlinks[@]}"; do
test ! -e "$link"
done
udevadm wait --settle --timeout=30 --removed "/dev/$vgroup" "/dev/disk/by-label/mylvpart1" "${expected_symlinks[@]}"
helper_check_device_symlinks "/dev/disk"
# Reconnect the iSCSI devices and check if everything get detected correctly
iscsiadm --mode discoverydb --type sendtargets --portal "$target_ip" --discover
iscsiadm --mode node --targetname "$target_name" --portal "$target_ip:$target_port" --login
udevadm settle
udevadm wait --settle --timeout=30 "${expected_symlinks[@]}"
for link in "${expected_symlinks[@]}"; do
helper_wait_for_dev "$link"
helper_wait_for_vgroup "$link" "$vgroup"
test -e "$link"
done
udevadm settle
test -e "/dev/$vgroup/mypart1"
test -e "/dev/$vgroup/mypart2"
test -e "/dev/disk/by-label/mylvpart1"