From b09ea23978a79237e441b786781437bb1b5e02be Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 20 Oct 2025 12:34:32 +0200 Subject: [PATCH 01/10] sd-device: add device_get_property_uint() helper This is just like device_get_property_int() but operates on unsigned ints. --- src/libsystemd/sd-device/device-private.h | 1 + src/libsystemd/sd-device/sd-device.c | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/libsystemd/sd-device/device-private.h b/src/libsystemd/sd-device/device-private.h index 74d6ffd119..9068b5497f 100644 --- a/src/libsystemd/sd-device/device-private.h +++ b/src/libsystemd/sd-device/device-private.h @@ -14,6 +14,7 @@ int device_opendir(sd_device *device, const char *subdir, DIR **ret); int device_get_sysnum_unsigned(sd_device *device, unsigned *ret); int device_get_property_bool(sd_device *device, const char *key); int device_get_property_int(sd_device *device, const char *key, int *ret); +int device_get_property_uint(sd_device *device, const char *key, unsigned *ret); int device_get_ifname(sd_device *device, const char **ret); int device_get_sysattr_int(sd_device *device, const char *sysattr, int *ret_value); int device_get_sysattr_unsigned_full(sd_device *device, const char *sysattr, unsigned base, unsigned *ret_value); diff --git a/src/libsystemd/sd-device/sd-device.c b/src/libsystemd/sd-device/sd-device.c index 3edebdda66..0b77126b2c 100644 --- a/src/libsystemd/sd-device/sd-device.c +++ b/src/libsystemd/sd-device/sd-device.c @@ -2319,6 +2319,27 @@ int device_get_property_int(sd_device *device, const char *key, int *ret) { return 0; } +int device_get_property_uint(sd_device *device, const char *key, unsigned *ret) { + const char *value; + int r; + + assert(device); + assert(key); + + r = sd_device_get_property_value(device, key, &value); + if (r < 0) + return r; + + unsigned v; + r = safe_atou(value, &v); + if (r < 0) + return r; + + if (ret) + *ret = v; + return 0; +} + _public_ int sd_device_get_trigger_uuid(sd_device *device, sd_id128_t *ret) { const char *s; sd_id128_t id; From a251345cf537ff0229fa3d4921e6ffd0ae7ed8cd Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 20 Oct 2025 17:58:37 +0200 Subject: [PATCH 02/10] blockdev-util: rename BlockDeviceLookupFlag to plural This is a flags type and a flag function argument, let's name it in plural, because it allows many flags combinations. Internally, the implementation already used plural, but let's fix the prototypes too. --- src/shared/blockdev-util.c | 6 +++--- src/shared/blockdev-util.h | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/shared/blockdev-util.c b/src/shared/blockdev-util.c index 42a5c08b72..7641f17aec 100644 --- a/src/shared/blockdev-util.c +++ b/src/shared/blockdev-util.c @@ -24,7 +24,7 @@ #include "path-util.h" #include "string-util.h" -static int fd_get_devnum(int fd, BlockDeviceLookupFlag flags, dev_t *ret) { +static int fd_get_devnum(int fd, BlockDeviceLookupFlags flags, dev_t *ret) { struct stat st; dev_t devnum; int r; @@ -148,7 +148,7 @@ int block_device_get_originating(sd_device *dev, sd_device **ret) { return 0; } -int block_device_new_from_fd(int fd, BlockDeviceLookupFlag flags, sd_device **ret) { +int block_device_new_from_fd(int fd, BlockDeviceLookupFlags flags, sd_device **ret) { _cleanup_(sd_device_unrefp) sd_device *dev = NULL; dev_t devnum; int r; @@ -194,7 +194,7 @@ int block_device_new_from_fd(int fd, BlockDeviceLookupFlag flags, sd_device **re return 0; } -int block_device_new_from_path(const char *path, BlockDeviceLookupFlag flags, sd_device **ret) { +int block_device_new_from_path(const char *path, BlockDeviceLookupFlags flags, sd_device **ret) { _cleanup_close_ int fd = -EBADF; assert(path); diff --git a/src/shared/blockdev-util.h b/src/shared/blockdev-util.h index 87b40054da..3c3153bacc 100644 --- a/src/shared/blockdev-util.h +++ b/src/shared/blockdev-util.h @@ -11,16 +11,16 @@ #define xsprintf_sys_block_path(buf, suffix, devno) \ xsprintf(buf, "/sys/dev/block/%u:%u%s", major(devno), minor(devno), suffix ?: "") -typedef enum BlockDeviceLookupFlag { +typedef enum BlockDeviceLookupFlags { BLOCK_DEVICE_LOOKUP_WHOLE_DISK = 1 << 0, /* whole block device, e.g. sda, nvme0n1, or loop0. */ BLOCK_DEVICE_LOOKUP_BACKING = 1 << 1, /* fd may be regular file or directory on file system, in * which case backing block device is determined. */ BLOCK_DEVICE_LOOKUP_ORIGINATING = 1 << 2, /* Try to find the underlying layer device for stacked * block device, e.g. LUKS-style DM. */ -} BlockDeviceLookupFlag; +} BlockDeviceLookupFlags; -int block_device_new_from_fd(int fd, BlockDeviceLookupFlag flag, sd_device **ret); -int block_device_new_from_path(const char *path, BlockDeviceLookupFlag flag, sd_device **ret); +int block_device_new_from_fd(int fd, BlockDeviceLookupFlags flags, sd_device **ret); +int block_device_new_from_path(const char *path, BlockDeviceLookupFlags flags, sd_device **ret); int block_device_is_whole_disk(sd_device *dev); int block_device_get_whole_disk(sd_device *dev, sd_device **ret); From bb45a893c2cd44b7e30b31221303b6c4e10ae1ec Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 20 Oct 2025 12:35:05 +0200 Subject: [PATCH 03/10] blockdev-util: in blockdev_partscan_enabled() check if we are operating on block device first The function makes no sense on any other type of fd, hence we better check this explicitly. --- src/shared/blockdev-util.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/shared/blockdev-util.c b/src/shared/blockdev-util.c index 7641f17aec..4db38374be 100644 --- a/src/shared/blockdev-util.c +++ b/src/shared/blockdev-util.c @@ -414,6 +414,12 @@ int blockdev_partscan_enabled(sd_device *dev) { assert(dev); + r = device_in_subsystem(dev, "block"); + if (r < 0) + return r; + if (r == 0) + return -ENOTBLK; + /* For v6.10 or newer. */ r = device_get_sysattr_bool(dev, "partscan"); if (r != -ENOENT) From 524ebfe28ac979171bc683d7148b55e0daa4ec0f Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 20 Oct 2025 12:36:05 +0200 Subject: [PATCH 04/10] blockdev-util: split out partition device node generation from dissect-util.c --- src/shared/blockdev-util.c | 43 +++++++++++++++++++++++++++++++++++ src/shared/blockdev-util.h | 2 ++ src/shared/dissect-image.c | 11 +++------ src/test/test-blockdev-util.c | 26 +++++++++++++++++++++ 4 files changed, 74 insertions(+), 8 deletions(-) diff --git a/src/shared/blockdev-util.c b/src/shared/blockdev-util.c index 4db38374be..4a9567ce51 100644 --- a/src/shared/blockdev-util.c +++ b/src/shared/blockdev-util.c @@ -904,3 +904,46 @@ int blockdev_get_root(int level, dev_t *ret) { return 1; } + +int partition_node_of(const char *node, unsigned nr, char **ret) { + int r; + + assert(node); + assert(nr > 0); + assert(ret); + + /* Given a device node path to a block device returns the device node path to the partition block + * device of the specified partition */ + + _cleanup_free_ char *fn = NULL; + r = path_extract_filename(node, &fn); + if (r < 0) + return r; + if (r == O_DIRECTORY) + return -EISDIR; + + _cleanup_free_ char *dn = NULL; + r = path_extract_directory(node, &dn); + if (r < 0 && r != -EDESTADDRREQ) /* allow if only filename is specified */ + return r; + + size_t l = strlen(fn); + assert(l > 0); /* underflow check for the subtraction below */ + + bool need_p = ascii_isdigit(fn[l-1]); /* Last char a digit? */ + + _cleanup_free_ char *subnode = NULL; + if (asprintf(&subnode, "%s%s%u", fn, need_p ? "p" : "", nr) < 0) + return -ENOMEM; + + if (dn) { + _cleanup_free_ char *j = path_join(dn, subnode); + if (!j) + return -ENOMEM; + + *ret = TAKE_PTR(j); + } else + *ret = TAKE_PTR(subnode); + + return 0; +} diff --git a/src/shared/blockdev-util.h b/src/shared/blockdev-util.h index 3c3153bacc..6c60c89e0c 100644 --- a/src/shared/blockdev-util.h +++ b/src/shared/blockdev-util.h @@ -57,3 +57,5 @@ int blockdev_get_sector_size(int fd, uint32_t *ret); int blockdev_get_device_size(int fd, uint64_t *ret); int blockdev_get_root(int level, dev_t *ret); + +int partition_node_of(const char *node, unsigned nr, char **ret); diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index 0cbb7d02cf..f00e1cf355 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -627,14 +627,9 @@ static int make_partition_devname( if (!s) return -ENOMEM; } else { - size_t l = strlen(whole_devname); - if (l < 1) /* underflow check for the subtraction below */ - return -EINVAL; - - bool need_p = ascii_isdigit(whole_devname[l-1]); /* Last char a digit? */ - - if (asprintf(&s, "%s%s%i", whole_devname, need_p ? "p" : "", nr) < 0) - return -ENOMEM; + r = partition_node_of(whole_devname, nr, &s); + if (r < 0) + return r; } } else { if (nr < 0) /* whole disk? */ diff --git a/src/test/test-blockdev-util.c b/src/test/test-blockdev-util.c index 5e5eee0e60..5e67487fcc 100644 --- a/src/test/test-blockdev-util.c +++ b/src/test/test-blockdev-util.c @@ -77,4 +77,30 @@ TEST(partscan_enabled) { } } +static void test_partition_node_of_one(const char *main, unsigned partition, const char *result, int retval) { + _cleanup_free_ char *s = NULL; + int r; + + r = partition_node_of(main, partition, &s); + ASSERT_EQ(r, retval); + if (r < 0) + return; + ASSERT_STREQ(s, result); + + log_info("%s with %u → %s", main, partition, result); +} + +TEST(partition_node_of) { + test_partition_node_of_one("/dev/sda", 2, "/dev/sda2", 0); + test_partition_node_of_one("sda", 3, "sda3", 0); + test_partition_node_of_one("/dev/nvme0n1", 7, "/dev/nvme0n1p7", 0); + test_partition_node_of_one("nvme0n1", 8, "nvme0n1p8", 0); + test_partition_node_of_one("/dev/loop1", 3, "/dev/loop1p3", 0); + test_partition_node_of_one("", 1, NULL, -EINVAL); + test_partition_node_of_one("/", 1, NULL, -EADDRNOTAVAIL); + test_partition_node_of_one("/dev/", 1, NULL, -EISDIR); + test_partition_node_of_one("/sda", 1, "/sda1", 0); + test_partition_node_of_one(".", 1, NULL, -EADDRNOTAVAIL); +} + DEFINE_TEST_MAIN(LOG_INFO); From 757887d01dd96125be1774f4b23b12f2fbda9a8b Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 20 Oct 2025 12:36:29 +0200 Subject: [PATCH 05/10] rereadpt: implement userspace-based BLKRRPART re-implementation --- src/shared/meson.build | 1 + src/shared/reread-partition-table.c | 396 ++++++++++++++++++ src/shared/reread-partition-table.h | 14 + src/test/meson.build | 7 + src/test/test-reread-partition-table-manual.c | 28 ++ src/test/test-reread-partition-table.c | 162 +++++++ 6 files changed, 608 insertions(+) create mode 100644 src/shared/reread-partition-table.c create mode 100644 src/shared/reread-partition-table.h create mode 100644 src/test/test-reread-partition-table-manual.c create mode 100644 src/test/test-reread-partition-table.c diff --git a/src/shared/meson.build b/src/shared/meson.build index 38915f3885..b2f8f61f23 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -167,6 +167,7 @@ shared_sources = files( 'quota-util.c', 'reboot-util.c', 'recovery-key.c', + 'reread-partition-table.c', 'resize-fs.c', 'resolve-util.c', 'rm-rf.c', diff --git a/src/shared/reread-partition-table.c b/src/shared/reread-partition-table.c new file mode 100644 index 0000000000..78f0759827 --- /dev/null +++ b/src/shared/reread-partition-table.c @@ -0,0 +1,396 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include + +#include "sd-device.h" + +#include "alloc-util.h" +#include "blkid-util.h" +#include "blockdev-util.h" +#include "device-private.h" +#include "device-util.h" +#include "errno-util.h" +#include "fd-util.h" +#include "log.h" +#include "parse-util.h" +#include "reread-partition-table.h" +#include "set.h" +#include "string-util.h" + +static int trigger_partitions(sd_device *dev, bool blkrrpart_success) { + int ret = 0, r; + + assert(dev); + + /* search for partitions */ + _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL; + r = partition_enumerator_new(dev, &e); + if (r < 0) + return log_device_debug_errno(dev, r, "Failed to initialize partition enumerator: %m"); + + /* We have partitions and re-read the table, the kernel already sent out a "change" + * event for the disk, and "remove/add" for all partitions. */ + if (blkrrpart_success && sd_device_enumerator_get_device_first(e)) + return 0; + + /* We have partitions but re-reading the partition table did not work, synthesize + * "change" for the disk and all partitions. */ + r = sd_device_trigger(dev, SD_DEVICE_CHANGE); + if (r < 0) + RET_GATHER(ret, log_device_debug_errno(dev, r, "Failed to trigger 'change' uevent, proceeding: %m")); + + FOREACH_DEVICE(e, d) { + r = sd_device_trigger(d, SD_DEVICE_CHANGE); + if (r < 0) + RET_GATHER(ret, log_device_debug_errno(d, r, "Failed to trigger 'change' uevent, proceeding: %m")); + } + + return ret; +} + +static int fallback_ioctl(sd_device *d, int fd, RereadPartitionTableFlags flags) { + int r; + + assert(d); + assert(fd >= 0); + + r = RET_NERRNO(ioctl(fd, BLKRRPART, 0)); + if (r < 0) + log_device_debug_errno(d, r, "Failed to reread partition table via BLKRRPART: %m"); + else + log_device_debug(d, "Successfully reread partition table via BLKRRPART."); + + if (FLAGS_SET(flags, REREADPT_FORCE_UEVENT)) + RET_GATHER(r, trigger_partitions(d, r >= 0)); + + return r; +} + +#if HAVE_BLKID +static int process_partition( + sd_device *d, + int fd, + blkid_partition pp, + sd_device_enumerator *e, + Set **partnos, + RereadPartitionTableFlags flags, + bool *changed) { + + int r; + + assert(d); + assert(fd >= 0); + assert(pp); + assert(e); + assert(partnos); + assert(changed); + + const char *node; + r = sd_device_get_devname(d, &node); + if (r < 0) + return log_device_debug_errno(d, r, "Failed to acquire device node path: %m"); + + errno = 0; + int nr = sym_blkid_partition_get_partno(pp); + if (nr < 0) + return log_debug_errno(errno_or_else(EIO), "Failed to read partition number of partition: %m"); + + log_device_debug(d, "Processing partition %i...", nr); + + errno = 0; + blkid_loff_t start = sym_blkid_partition_get_start(pp); + if (start < 0) + return log_debug_errno(errno_or_else(EIO), "Failed to read partition start offset of partition %i: %m", nr); + assert((uint64_t) start < UINT64_MAX / 512U); + + errno = 0; + blkid_loff_t size = sym_blkid_partition_get_size(pp); + if (size < 0) + return log_debug_errno(errno_or_else(EIO), "Failed to read partition size of partition %i: %m", nr); + assert((uint64_t) size < UINT64_MAX / 512U); + + if (set_ensure_put(partnos, /* hash_ops= */ NULL, UINT_TO_PTR(nr)) < 0) + return log_oom_debug(); + + _cleanup_free_ char *subnode = NULL; + r = partition_node_of(node, nr, &subnode); + if (r < 0) + return log_device_debug_errno(d, r, "Failed to determine partition node %i for '%s': %m", nr, node); + + _cleanup_(sd_device_unrefp) sd_device *partition = NULL; + r = sd_device_new_from_devname(&partition, subnode); + if (r < 0) { + if (r != -ENODEV) + return log_device_debug_errno(d, r, "Failed to acquire device '%s': %m", subnode); + } else { + uint64_t start_kernel; + r = device_get_sysattr_u64(partition, "start", &start_kernel); + if (r < 0) + return log_device_debug_errno(partition, r, "Failed to get start of kernel partition device '%s': %m", subnode); + + uint64_t size_kernel; + r = device_get_sysattr_u64(partition, "size", &size_kernel); + if (r < 0) + return log_device_debug_errno(partition, r, "Failed to get size of kernel partition device '%s': %m", subnode); + + if (start_kernel == (uint64_t) start && size_kernel == (uint64_t) size) { + log_device_debug(partition, "Kernel partition device '%s' already matches partition table, not modifying.", subnode); + + if (FLAGS_SET(flags, REREADPT_FORCE_UEVENT)) { + if (!*changed) { + /* Make sure to synthesize a change event on the main device, before we issue the first one on a partition device */ + r = sd_device_trigger(d, SD_DEVICE_CHANGE); + if (r < 0) + return log_device_debug_errno(d, r, "Failed to issue 'change' uevent on device '%s': %m", node); + + log_device_debug(d, "Successfully issued 'change' uevent on device '%s'.", node); + *changed = true; + } + + r = sd_device_trigger(partition, SD_DEVICE_CHANGE); + if (r < 0) + return log_device_debug_errno(partition, r, "Failed to issue 'change' uevent on partition '%s': %m", subnode); + + log_device_debug(partition, "Successfully issued 'change' uevent on partition '%s'.", subnode); + } + + return 0; + } + + if (start_kernel == (uint64_t) start) { + /* If the start offsize doesn't change we can just resize the partition */ + log_device_debug(partition, "Resizing partition %i...", nr); + + r = block_device_resize_partition(fd, nr, (uint64_t) start * 512U, (uint64_t) size * 512U); + if (r < 0) + return log_device_debug_errno(partition, r, "Failed to resize kernel partition device '%s' to partition table values: %m", subnode); + + log_device_debug(partition, "Successfully resized kernel partition device '%s' to match partition table.", subnode); + *changed = true; + return 1; + } + + /* If the start offset changed we need to remove and recreate the partition */ + log_device_debug(partition, "Removing and recreating partition %i...", nr); + + /* NB: when logging below we use the parent device now, after all the partition device ceased + * existing by now, most likely. Let's explicitly get rid of the obsolete device object now, + * just to make a point. */ + partition = sd_device_unref(partition); + + r = block_device_remove_partition(fd, subnode, (int) nr); + if (r < 0) + return log_device_debug_errno(d, r, "Failed to remove kernel partition device '%s' in order to recreate it: %m", subnode); + + /* And now add it the partition anew*/ + log_device_debug(d, "Successfully removed kernel partition device '%s' in order to recreate it.", subnode); + } + + log_device_debug(d, "Adding partition %i...", nr); + + r = block_device_add_partition(fd, subnode, nr, (uint64_t) start * 512U, (uint64_t) size * 512U); + if (r < 0) + return log_device_debug_errno(d, r, "Failed to add kernel partition device %i to partition table values: %m", nr); + + log_device_debug(d, "Successfully added kernel partition device %i to match partition table.", nr); + *changed = true; + return 1; +} + +static int remove_partitions(sd_device *d, int fd, sd_device_enumerator *e, Set *partnos, bool *changed) { + int r; + + assert(d); + assert(fd >= 0); + assert(e); + assert(changed); + + /* Removes all partitions of the specified device that we didn't find in the partition table (as + * listed in the specified Set object) */ + + int ret = 0; + FOREACH_DEVICE(e, partition) { + const char *devname; + + r = sd_device_get_devname(partition, &devname); + if (r < 0) + return log_device_debug_errno(partition, r, "Failed to get name of partition: %m"); + + unsigned nr; + r = device_get_property_uint(partition, "PARTN", &nr); + if (r < 0) + return log_device_debug_errno(partition, r, "Failed to read partition number property: %m"); + if (set_contains(partnos, UINT_TO_PTR(nr))) { + log_device_debug(partition, "Found kernel partition device %u in partition table, leaving around.", nr); + continue; + } + + log_device_debug(partition, "Kernel knows partition %u which we didn't find, removing.", nr); + + r = block_device_remove_partition(fd, devname, (int) nr); + if (r < 0) /* NB: when logging we use the parent device below, after all the partition device ceased existing by now, most likely */ + RET_GATHER(ret, log_device_debug_errno(d, r, "Failed to remove kernel partition device '%s' that vanished from partition table: %m", devname)); + else { + log_device_debug(d, "Removed partition %u from kernel.", nr); + *changed = true; + } + } + + return ret; +} +#endif + +static int reread_partition_table_full(sd_device *dev, int fd, RereadPartitionTableFlags flags) { + int r; + + assert(dev); + assert(fd >= 0); + + const char *p; + r = sd_device_get_devname(dev, &p); + if (r < 0) + return log_device_debug_errno(dev, r, "Failed to get block device name: %m"); + + _cleanup_close_ int lock_fd = -EBADF; + if (FLAGS_SET(flags, REREADPT_BSD_LOCK)) { + lock_fd = fd_reopen(fd, O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (lock_fd < 0) + return log_device_debug_errno(dev, r, "Failed top open lock fd for block device '%s': %m", p); + + if (flock(lock_fd, LOCK_SH|LOCK_NB) < 0) + return log_device_debug_errno(dev, errno, "Failed to take BSD lock on block device '%s': %m", p); + } + + r = blockdev_partscan_enabled(dev); + if (r < 0) + return log_device_debug_errno(dev, r, "Failed to test if block device '%s' knows partition scanning: %m", p); + if (r == 0) { + /* No partition scanning? Generate a uevent at least, if that's requested */ + if (FLAGS_SET(flags, REREADPT_FORCE_UEVENT)) { + r = sd_device_trigger(dev, SD_DEVICE_CHANGE); + if (r < 0) + return log_device_debug_errno(dev, r, "Failed to trigger 'change' uevent, proceeding: %m"); + + return 0; + } + + return log_device_debug_errno(dev, SYNTHETIC_ERRNO(ENOTTY), "Block device '%s' does not support partition scanning.", p); + } + +#if HAVE_BLKID + r = dlopen_libblkid(); + if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) { + log_device_debug(dev, "We don't have libblkid, falling back to BLKRRPART on '%s'.", p); + return fallback_ioctl(dev, fd, flags); + } + if (r < 0) + return log_device_debug_errno(dev, r, "Failed to load libblkid: %m"); + + _cleanup_(blkid_free_probep) blkid_probe b = sym_blkid_new_probe(); + if (!b) + return log_oom_debug(); + + errno = 0; + r = sym_blkid_probe_set_device(b, fd, /* off= */ 0, /* size= */ 0); + if (r != 0) + return log_device_debug_errno(dev, errno_or_else(ENOMEM), "Failed to open block device '%s': %m", p); + + (void) sym_blkid_probe_enable_partitions(b, 1); + (void) sym_blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS); + + errno = 0; + r = sym_blkid_do_safeprobe(b); + if (r == _BLKID_SAFEPROBE_ERROR) + return log_device_debug_errno(dev, errno_or_else(EIO), "Unable to probe for partition table of '%s': %m", p); + if (IN_SET(r, _BLKID_SAFEPROBE_AMBIGUOUS, _BLKID_SAFEPROBE_NOT_FOUND)) { + log_device_debug(dev, "Didn't find partition table on block device '%s', falling back to BLKRRPART.", p); + return fallback_ioctl(dev, fd, flags); + } + + assert(r == _BLKID_SAFEPROBE_FOUND); + + const char *pttype = NULL; + (void) sym_blkid_probe_lookup_value(b, "PTTYPE", &pttype, NULL); + if (!streq_ptr(pttype, "gpt")) { + log_device_debug(dev, "Didn't find a GPT partition table on '%s', falling back to BLKRRPART.", p); + return fallback_ioctl(dev, fd, flags); + } + + errno = 0; + blkid_partlist pl = sym_blkid_probe_get_partitions(b); + if (!pl) + return log_device_debug_errno(dev, errno_or_else(EIO), "Unable to read partition table of '%s': %m", p); + + errno = 0; + int n_partitions = sym_blkid_partlist_numof_partitions(pl); + if (n_partitions < 0) + return log_device_debug_errno(dev, errno_or_else(EIO), "Unable to acquire number of entries in partition table of '%s': %m", p); + + _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL; + r = partition_enumerator_new(dev, &e); + if (r < 0) + return log_device_debug_errno(dev, r, "Failed to enumerate kernel partition devices: %m"); + + log_device_debug(dev, "Updating/adding kernel partition devices..."); + + _cleanup_(set_freep) Set *found_partnos = NULL; + bool changed = false; + int ret = 0; + for (int i = 0; i < n_partitions; i++) { + errno = 0; + blkid_partition pp = sym_blkid_partlist_get_partition(pl, i); + if (!pp) + return log_device_debug_errno(dev, errno_or_else(EIO), "Unable to get partition data of partition %i of partition table of '%s': %m", i, p); + + RET_GATHER(ret, process_partition(dev, fd, pp, e, &found_partnos, flags, &changed)); + } + + /* Only delete unrecognized partitions if everything else worked */ + if (ret < 0) + return ret; + + log_device_debug(dev, "Removing old kernel partition devices..."); + + r = remove_partitions(dev, fd, e, found_partnos, &changed); + if (r < 0) + return r; + + if (changed) + return 1; + + if (FLAGS_SET(flags, REREADPT_FORCE_UEVENT)) { + /* No change? Then trigger an event manually if we were told to */ + r = sd_device_trigger(dev, SD_DEVICE_CHANGE); + if (r < 0) + return log_device_debug_errno(dev, r, "Failed to issue 'change' uevent on device '%s': %m", p); + } + + return 0; +#else + log_device_debug(dev, "We don't have libblkid, falling back to BLKRRPART on '%s'.", p); + return fallback_ioctl(dev, fd, flags); +#endif +} + +int reread_partition_table(sd_device *dev, RereadPartitionTableFlags flags) { + assert(dev); + + _cleanup_close_ int fd = sd_device_open(dev, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); + if (fd < 0) + return log_debug_errno(fd, "Failed to open block device: %m"); + + return reread_partition_table_full(dev, fd, flags); +} + +int reread_partition_table_fd(int fd, RereadPartitionTableFlags flags) { + int r; + + _cleanup_(sd_device_unrefp) sd_device *dev = NULL; + r = block_device_new_from_fd(fd, /* flags= */ 0, &dev); + if (r < 0) + return log_debug_errno(r, "Failed to get block device object: %m"); + + return reread_partition_table_full(dev, fd, flags); +} diff --git a/src/shared/reread-partition-table.h b/src/shared/reread-partition-table.h new file mode 100644 index 0000000000..240ab5222c --- /dev/null +++ b/src/shared/reread-partition-table.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-forward.h" + +#include "shared-forward.h" + +typedef enum RereadPartitionTableFlags { + REREADPT_FORCE_UEVENT = 1 << 0, /* Force a "change" ueven out on partitions we didn't resize/remove/add */ + REREADPT_BSD_LOCK = 1 << 1, /* Take a BSD lock on the device around the rescan operation */ +} RereadPartitionTableFlags; + +int reread_partition_table_fd(int fd, RereadPartitionTableFlags flags); +int reread_partition_table(sd_device *dev, RereadPartitionTableFlags flags); diff --git a/src/test/meson.build b/src/test/meson.build index 446336048a..0b69fc8b9f 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -432,6 +432,13 @@ executables += [ 'dependencies' : libm, 'timeout' : 120, }, + test_template + { + 'sources' : files('test-reread-partition-table.c'), + }, + test_template + { + 'sources' : files('test-reread-partition-table-manual.c'), + 'type' : 'manual', + }, test_template + { 'sources' : files('test-sbat.c'), 'conditions' : ['ENABLE_BOOTLOADER'], diff --git a/src/test/test-reread-partition-table-manual.c b/src/test/test-reread-partition-table-manual.c new file mode 100644 index 0000000000..b9d3547504 --- /dev/null +++ b/src/test/test-reread-partition-table-manual.c @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "fd-util.h" +#include "log.h" +#include "main-func.h" +#include "reread-partition-table.h" + +static int run(int argc, char *argv[]) { + int r; + + log_set_max_level(LOG_DEBUG); + log_setup(); + + if (argc != 2) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected single parameter, the device node to open."); + + _cleanup_close_ int fd = open(argv[1], O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); + if (fd < 0) + return log_error_errno(errno, "Failed to open '%s': %m", argv[1]); + + r = reread_partition_table_fd(fd, REREADPT_BSD_LOCK|REREADPT_FORCE_UEVENT); + if (r < 0) + return log_error_errno(r, "Failed to reread partition table: %m"); + + return 0; +} + +DEFINE_MAIN_FUNCTION(run); diff --git a/src/test/test-reread-partition-table.c b/src/test/test-reread-partition-table.c new file mode 100644 index 0000000000..07a0a02e15 --- /dev/null +++ b/src/test/test-reread-partition-table.c @@ -0,0 +1,162 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "blockdev-util.h" +#include "fd-util.h" +#include "loop-util.h" +#include "memfd-util.h" +#include "path-util.h" +#include "process-util.h" +#include "reread-partition-table.h" +#include "tests.h" +#include "tmpfile-util.h" +#include "virt.h" + +static void sfdisk(const char *sfdisk_path, LoopDevice *loop, const char *definition) { + int r; + + assert(sfdisk_path); + assert(loop); + assert(definition); + + _cleanup_close_ int memfd = memfd_new_and_seal("sfdisk", definition, SIZE_MAX); + ASSERT_OK(memfd); + + r = safe_fork_full( + "(sfdisk)", + (int[]) { memfd, STDOUT_FILENO, STDERR_FILENO }, + /* except_fds= */ NULL, + /* n_except_fds= */ 0, + FORK_CLOSE_ALL_FDS|FORK_RESET_SIGNALS|FORK_REARRANGE_STDIO|FORK_LOG|FORK_WAIT, + /* ret_pid= */ NULL); + if (r == 0) { + /* child */ + execl(sfdisk_path, "fdisk", "--no-tell-kernel", "--no-reread", loop->node, NULL); + _exit(EXIT_FAILURE); + } + + ASSERT_OK(r); +} + +TEST(rereadpt) { + int r; + + if (detect_container() > 0) + return (void) log_tests_skipped("test not available in container"); + if (running_in_chroot() > 0) + return (void) log_tests_skipped("test not available in chroot()"); + + _cleanup_free_ char *sfdisk_path = NULL; + r = find_executable("sfdisk", &sfdisk_path); + if (r == -ENOENT) + return (void) log_tests_skipped("sfdisk not found"); + ASSERT_OK(r); + + _cleanup_close_ int fd = open_tmpfile_unlinkable("/var/tmp", O_RDWR); + ASSERT_FD(fd); + + ASSERT_OK_ERRNO(ftruncate(fd, 100 * 1024 * 1024)); + + _cleanup_(loop_device_unrefp) LoopDevice *loop = NULL; + r = loop_device_make( + fd, + O_RDWR, + /* offset= */ 0, + /* size= */ UINT64_MAX, + /* sector_size= */ 512U, + LO_FLAGS_PARTSCAN, + LOCK_EX, &loop); + if (ERRNO_IS_NEG_PRIVILEGE(r) || ERRNO_IS_NOT_SUPPORTED(r)) + return (void) log_tests_skipped("loopback block devices not available"); + + _cleanup_free_ char *p = NULL; + ASSERT_OK(partition_node_of(loop->node, 1, &p)); + ASSERT_ERROR_ERRNO(access(p, F_OK), ENOENT); + + /* No change */ + ASSERT_OK(reread_partition_table_fd(loop->fd, /* flags= */ 0)); + ASSERT_ERROR_ERRNO(access(p, F_OK), ENOENT); + + /* Create */ + log_notice("CREATING 20M"); + sfdisk(sfdisk_path, + loop, + "label: gpt\n" + "start=, size=20M, type=EBD0A0A2-B9E5-4433-87C0-68B6B72699C7\n"); + + ASSERT_ERROR_ERRNO(access(p, F_OK), ENOENT); + ASSERT_OK(reread_partition_table_fd(loop->fd, /* flags= */ 0)); + + ASSERT_OK_ZERO_ERRNO(access(p, F_OK)); + + _cleanup_close_ int pfd = open(p, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); + ASSERT_OK_ERRNO(pfd); + uint64_t size; + ASSERT_OK(blockdev_get_device_size(pfd, &size)); + ASSERT_EQ(size, 20U*1024U*1024U); + + /* No change */ + ASSERT_OK(reread_partition_table_fd(loop->fd, /* flags= */ 0)); + ASSERT_OK_ZERO_ERRNO(access(p, F_OK)); + + /* No change, but synthesize change anyway */ + ASSERT_OK(reread_partition_table_fd(loop->fd, /* flags= */ REREADPT_FORCE_UEVENT)); + ASSERT_OK_ZERO_ERRNO(access(p, F_OK)); + + /* Resize */ + log_notice("RESIZING TO 30M"); + sfdisk(sfdisk_path, + loop, + "label: gpt\n" + "start=, size=30M, type=EBD0A0A2-B9E5-4433-87C0-68B6B72699C7\n"); + + ASSERT_OK_ZERO_ERRNO(access(p, F_OK)); + ASSERT_OK(reread_partition_table_fd(loop->fd, /* flags= */ 0)); + ASSERT_OK_ZERO_ERRNO(access(p, F_OK)); + + ASSERT_OK(blockdev_get_device_size(pfd, &size)); + ASSERT_EQ(size, 30U*1024U*1024U); + + /* No change */ + ASSERT_OK(reread_partition_table_fd(loop->fd, /* flags= */ 0)); + + /* Move */ + log_notice("MOVING BY 50M"); + sfdisk(sfdisk_path, + loop, + "label: gpt\n" + "start=50M, size=15M, type=EBD0A0A2-B9E5-4433-87C0-68B6B72699C7\n"); + + ASSERT_OK_ZERO_ERRNO(access(p, F_OK)); + ASSERT_ERROR(reread_partition_table_fd(loop->fd, /* flags= */ 0), EBUSY); + ASSERT_OK_ZERO_ERRNO(access(p, F_OK)); + + safe_close(pfd); + + ASSERT_OK(reread_partition_table_fd(loop->fd, /* flags= */ 0)); + + pfd = open(p, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); + ASSERT_OK_ERRNO(pfd); + ASSERT_OK(blockdev_get_device_size(pfd, &size)); + ASSERT_EQ(size, 15U*1024U*1024U); + + /* No change */ + ASSERT_OK(reread_partition_table_fd(loop->fd, /* flags= */ 0)); + + /* Remove */ + log_notice("REMOVING"); + sfdisk(sfdisk_path, + loop, + "label: gpt\n"); + + ASSERT_OK_ZERO_ERRNO(access(p, F_OK)); + ASSERT_ERROR(reread_partition_table_fd(loop->fd, /* flags= */ 0), EBUSY); + + ASSERT_OK_ZERO_ERRNO(access(p, F_OK)); + pfd = safe_close(pfd); + ASSERT_OK(reread_partition_table_fd(loop->fd, /* flags= */ 0)); + ASSERT_ERROR_ERRNO(access(p, F_OK), ENOENT); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); From 0ef4118c7825fe1f3a317e1f708d10ec831e8e83 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 20 Oct 2025 15:18:42 +0200 Subject: [PATCH 06/10] homed: switch from raw BLKRRPART to rereadpt_fd() --- src/home/homework-luks.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/home/homework-luks.c b/src/home/homework-luks.c index a0b4368a2a..25b3c191dd 100644 --- a/src/home/homework-luks.c +++ b/src/home/homework-luks.c @@ -53,6 +53,7 @@ #include "path-util.h" #include "process-util.h" #include "random-util.h" +#include "reread-partition-table.h" #include "resize-fs.h" #include "string-util.h" #include "strv.h" @@ -2497,7 +2498,7 @@ int home_create_luks( if (disk_uuid_path) /* Reread partition table if this is a block device */ - (void) ioctl(setup->image_fd, BLKRRPART, 0); + (void) reread_partition_table_fd(setup->image_fd, /* flags= */ 0); else { assert(setup->temporary_image_path); @@ -3469,8 +3470,8 @@ int home_resize_luks( if (r > 0) log_info("Growing of partition completed."); - if (S_ISBLK(st.st_mode) && ioctl(image_fd, BLKRRPART, 0) < 0) - log_debug_errno(errno, "BLKRRPART failed on block device, ignoring: %m"); + if (S_ISBLK(st.st_mode)) + (void) reread_partition_table_fd(image_fd, /* flags= */ 0); /* Tell LUKS about the new bigger size too */ r = sym_crypt_resize(setup->crypt_device, setup->dm_name, new_fs_size / 512U); @@ -3569,8 +3570,8 @@ int home_resize_luks( if (r > 0) log_info("Shrinking of partition completed."); - if (S_ISBLK(st.st_mode) && ioctl(image_fd, BLKRRPART, 0) < 0) - log_debug_errno(errno, "BLKRRPART failed on block device, ignoring: %m"); + if (S_ISBLK(st.st_mode)) + (void) reread_partition_table_fd(image_fd, /* flags= */ 0); } else { /* → Grow */ if (!FLAGS_SET(flags, HOME_SETUP_RESIZE_DONT_SYNC_IDENTITIES)) { From aa47d8ade18cc4a079fef5a1aaa37d763507104e Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 20 Oct 2025 15:21:44 +0200 Subject: [PATCH 07/10] udev: switch over to rereadpt() rather than raw BLKRRPART Fixes: #38672 --- src/shared/blockdev-util.c | 21 --------------------- src/shared/blockdev-util.h | 1 - src/udev/udev-watch.c | 33 ++------------------------------- 3 files changed, 2 insertions(+), 53 deletions(-) diff --git a/src/shared/blockdev-util.c b/src/shared/blockdev-util.c index 4a9567ce51..d86238b84e 100644 --- a/src/shared/blockdev-util.c +++ b/src/shared/blockdev-util.c @@ -804,27 +804,6 @@ int block_device_remove_all_partitions(sd_device *dev, int fd) { return k < 0 ? k : has_partitions; } - -int blockdev_reread_partition_table(sd_device *dev) { - _cleanup_close_ int fd = -EBADF; - - assert(dev); - - /* Try to re-read the partition table. This only succeeds if none of the devices is busy. */ - - fd = sd_device_open(dev, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); - if (fd < 0) - return fd; - - if (flock(fd, LOCK_EX|LOCK_NB) < 0) - return -errno; - - if (ioctl(fd, BLKRRPART, 0) < 0) - return -errno; - - return 0; -} - int blockdev_get_sector_size(int fd, uint32_t *ret) { int ssz = 0; diff --git a/src/shared/blockdev-util.h b/src/shared/blockdev-util.h index 6c60c89e0c..3a481dd396 100644 --- a/src/shared/blockdev-util.h +++ b/src/shared/blockdev-util.h @@ -51,7 +51,6 @@ int block_device_remove_partition(int fd, const char *name, int nr); int block_device_resize_partition(int fd, int nr, uint64_t start, uint64_t size); int partition_enumerator_new(sd_device *dev, sd_device_enumerator **ret); int block_device_remove_all_partitions(sd_device *dev, int fd); -int blockdev_reread_partition_table(sd_device *dev); int blockdev_get_sector_size(int fd, uint32_t *ret); int blockdev_get_device_size(int fd, uint64_t *ret); diff --git a/src/udev/udev-watch.c b/src/udev/udev-watch.c index c8f555ef71..c421063438 100644 --- a/src/udev/udev-watch.c +++ b/src/udev/udev-watch.c @@ -22,6 +22,7 @@ #include "parse-util.h" #include "pidref.h" #include "process-util.h" +#include "reread-partition-table.h" #include "rm-rf.h" #include "set.h" #include "signal-util.h" @@ -161,36 +162,6 @@ static int synthesize_change_one(sd_device *dev, sd_device *target) { return 0; } -static int synthesize_change_all(sd_device *dev) { - int r; - - assert(dev); - - r = blockdev_reread_partition_table(dev); - if (r < 0) - log_device_debug_errno(dev, r, "Failed to re-read partition table, ignoring: %m"); - bool part_table_read = r >= 0; - - /* search for partitions */ - _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL; - r = partition_enumerator_new(dev, &e); - if (r < 0) - return log_device_debug_errno(dev, r, "Failed to initialize partition enumerator, ignoring: %m"); - - /* We have partitions and re-read the table, the kernel already sent out a "change" - * event for the disk, and "remove/add" for all partitions. */ - if (part_table_read && sd_device_enumerator_get_device_first(e)) - return 0; - - /* We have partitions but re-reading the partition table did not work, synthesize - * "change" for the disk and all partitions. */ - r = synthesize_change_one(dev, dev); - FOREACH_DEVICE(e, d) - RET_GATHER(r, synthesize_change_one(dev, d)); - - return r; -} - static int synthesize_change_child_handler(sd_event_source *s, const siginfo_t *si, void *userdata) { Manager *manager = ASSERT_PTR(userdata); assert(s); @@ -226,7 +197,7 @@ static int synthesize_change(Manager *manager, sd_device *dev) { return r; if (r == 0) { /* child */ - (void) synthesize_change_all(dev); + (void) reread_partition_table(dev, REREADPT_FORCE_UEVENT|REREADPT_BSD_LOCK); _exit(EXIT_SUCCESS); } From 78c017a8ed01c260e091bdf844508f8f695c60e1 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 20 Oct 2025 16:09:52 +0200 Subject: [PATCH 08/10] repart: split out that disarms automatic artifact removal --- src/repart/repart.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/repart/repart.c b/src/repart/repart.c index b74d9bb552..0af92eea61 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -855,6 +855,16 @@ static Context* context_free(Context *context) { DEFINE_TRIVIAL_CLEANUP_FUNC(Context*, context_free); +static void context_disarm_auto_removal(Context *context) { + assert(context); + + /* Make sure automatic removal of half-written artifacts is disarmed */ + context->node = mfree(context->node); + + LIST_FOREACH(partitions, p, context->partitions) + p->split_path = mfree(p->split_path); +} + static int context_add_free_area( Context *context, uint64_t size, @@ -10177,10 +10187,7 @@ static int run(int argc, char *argv[]) { (void) context_dump(context, /*late=*/ true); - context->node = mfree(context->node); - - LIST_FOREACH(partitions, p, context->partitions) - p->split_path = mfree(p->split_path); + context_disarm_auto_removal(context); return 0; } From 46da450f139487e62efc2f566fe7c1536648ee75 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 20 Oct 2025 16:13:31 +0200 Subject: [PATCH 09/10] repart: switch things over to our own partition reread logic --- src/repart/repart.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/repart/repart.c b/src/repart/repart.c index 0af92eea61..1ca825f39d 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -65,6 +65,7 @@ #include "process-util.h" #include "random-util.h" #include "ratelimit.h" +#include "reread-partition-table.h" #include "resize-fs.h" #include "rm-rf.h" #include "set.h" @@ -7270,12 +7271,8 @@ static int context_write_partition_table(Context *context) { else if (capable < 0) return log_error_errno(capable, "Failed to check if block device supports partition scanning: %m"); else if (capable > 0) { - log_info("Telling kernel to reread partition table."); - - if (context->from_scratch) - r = fdisk_reread_partition_table(context->fdisk_context); - else - r = fdisk_reread_changes(context->fdisk_context, original_table); + log_info("Informing kernel about changed partitions..."); + r = reread_partition_table_fd(fdisk_get_devfd(context->fdisk_context), /* flags= */ 0); if (r < 0) return log_error_errno(r, "Failed to reread partition table: %m"); } else From e582484789a6d889d11b97d9c2afa74c3c985130 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 22 Oct 2025 22:47:53 +0200 Subject: [PATCH 10/10] tree-wide: open block device locks in writable mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit udev's block device locking protocol has one pitfall not even the example in the documentation got right so far (even though this is explained in all detail above): udev's rescanning is only triggered when an fd that is opened for writing is closed. This means that if a separate locking fd is opened on a block device – one that is maintained independently of the fd actually used for writing – it must be opened for writing too, so that closing the lock definitely triggers a rescan. This matters in cases where the lock fd is kept for longer than the fd used for writing to disk. (Because otherwise udev might get the IN_CLOSE_WRITE event, but when it tries to rescan will find the device locked, and never retry because no IN_CLOSE_WRITE is triggred anymore.) Let's fix that across the codebase, at 4 places: 1. in makefs (a lock fd is kept, and mkfs then invoked as child, which uses a different fd, and the lock fd is closed only once the child died) 2. in udevadm lock (embarassing!): which is intended to be used to wrap tools that modify disk contents, very similar to the makefs case. The lock is also kept until after the tool exited. 3. In storagetm: the kernel nvme-tcp layer writes to the device directly, we just keep a lock fd. 4. the example in BLOCK_DEVICE_LOCKING.md --- docs/BLOCK_DEVICE_LOCKING.md | 5 +++-- src/growfs/makefs.c | 2 +- src/shared/blockdev-util.c | 14 +++++++++----- src/shared/blockdev-util.h | 2 +- src/storagetm/storagetm.c | 2 +- src/udev/udevadm-lock.c | 3 ++- 6 files changed, 17 insertions(+), 11 deletions(-) diff --git a/docs/BLOCK_DEVICE_LOCKING.md b/docs/BLOCK_DEVICE_LOCKING.md index a6e3374bc7..a3edd80829 100644 --- a/docs/BLOCK_DEVICE_LOCKING.md +++ b/docs/BLOCK_DEVICE_LOCKING.md @@ -223,11 +223,12 @@ int main(int argc, char **argv) { return EXIT_FAILURE; } - // try to take an exclusive and nonblocking BSD lock + // try to take an exclusive and nonblocking BSD lock (use O_WRONLY mode to ensure udev + // rescans the device once the lock is closed) __attribute__((cleanup(closep))) int fd = lock_whole_disk_from_devname( argv[1], - O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY, + O_WRONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY, LOCK_EX|LOCK_NB); if (fd < 0) diff --git a/src/growfs/makefs.c b/src/growfs/makefs.c index 897b9a7600..22bea9e24a 100644 --- a/src/growfs/makefs.c +++ b/src/growfs/makefs.c @@ -41,7 +41,7 @@ static int run(int argc, char *argv[]) { if (S_ISBLK(st.st_mode)) { /* Lock the device so that udev doesn't interfere with our work */ - lock_fd = lock_whole_block_device(st.st_rdev, LOCK_EX); + lock_fd = lock_whole_block_device(st.st_rdev, O_WRONLY, LOCK_EX); if (lock_fd < 0) return log_error_errno(lock_fd, "Failed to lock whole block device of \"%s\": %m", device); } else diff --git a/src/shared/blockdev-util.c b/src/shared/blockdev-util.c index d86238b84e..bac1471cee 100644 --- a/src/shared/blockdev-util.c +++ b/src/shared/blockdev-util.c @@ -340,20 +340,24 @@ int get_block_device_harder(const char *path, dev_t *ret) { return get_block_device_harder_fd(fd, ret); } -int lock_whole_block_device(dev_t devt, int operation) { +int lock_whole_block_device(dev_t devt, int open_flags, int operation) { _cleanup_close_ int lock_fd = -EBADF; dev_t whole_devt; int r; - /* Let's get a BSD file lock on the whole block device, as per: https://systemd.io/BLOCK_DEVICE_LOCKING */ + /* Let's get a BSD file lock on the whole block device, as per: https://systemd.io/BLOCK_DEVICE_LOCKING + * + * NB: it matters whether open_flags indicates open for write: only then will the eventual closing of + * the fd trigger udev's partitioning rescanning of the device (as it watches for IN_CLOSE_WRITE), + * hence make sure to pass the right value there. */ r = block_get_whole_disk(devt, &whole_devt); if (r < 0) return r; - lock_fd = r = device_open_from_devnum(S_IFBLK, whole_devt, O_RDONLY|O_CLOEXEC|O_NONBLOCK, NULL); - if (r < 0) - return r; + lock_fd = device_open_from_devnum(S_IFBLK, whole_devt, open_flags|O_CLOEXEC|O_NONBLOCK|O_NOCTTY, NULL); + if (lock_fd < 0) + return lock_fd; if (flock(lock_fd, operation) < 0) return -errno; diff --git a/src/shared/blockdev-util.h b/src/shared/blockdev-util.h index 3a481dd396..eddb521e4a 100644 --- a/src/shared/blockdev-util.h +++ b/src/shared/blockdev-util.h @@ -35,7 +35,7 @@ int get_block_device(const char *path, dev_t *dev); int get_block_device_harder_fd(int fd, dev_t *dev); int get_block_device_harder(const char *path, dev_t *dev); -int lock_whole_block_device(dev_t devt, int operation); +int lock_whole_block_device(dev_t devt, int open_flags, int operation); int blockdev_partscan_enabled(sd_device *d); int blockdev_partscan_enabled_fd(int fd); diff --git a/src/storagetm/storagetm.c b/src/storagetm/storagetm.c index 972b892ca1..54d516ef12 100644 --- a/src/storagetm/storagetm.c +++ b/src/storagetm/storagetm.c @@ -390,7 +390,7 @@ static int nvme_subsystem_add(const char *node, int consumed_fd, sd_device *devi return log_oom(); if (fd < 0) { - fd = RET_NERRNO(open(node, O_RDONLY|O_CLOEXEC|O_NONBLOCK)); + fd = RET_NERRNO(open(node, O_RDWR|O_CLOEXEC|O_NONBLOCK)); if (fd < 0) return log_error_errno(fd, "Failed to open '%s': %m", node); } diff --git a/src/udev/udevadm-lock.c b/src/udev/udevadm-lock.c index 00f4deafd6..17b9e2d3e9 100644 --- a/src/udev/udevadm-lock.c +++ b/src/udev/udevadm-lock.c @@ -184,7 +184,8 @@ static int lock_device( struct stat st; int r; - fd = open(path, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); + /* We open in O_WRONLY mode here, to trigger a rescan in udev once we are done */ + fd = open(path, O_WRONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); if (fd < 0) return log_error_errno(errno, "Failed to open '%s': %m", path);