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/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)) { 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; diff --git a/src/repart/repart.c b/src/repart/repart.c index b74d9bb552..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" @@ -855,6 +856,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, @@ -7260,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 @@ -10177,10 +10184,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; } diff --git a/src/shared/blockdev-util.c b/src/shared/blockdev-util.c index 42a5c08b72..bac1471cee 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); @@ -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; @@ -414,6 +418,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) @@ -798,27 +808,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; @@ -898,3 +887,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 87b40054da..eddb521e4a 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); @@ -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); @@ -51,9 +51,10 @@ 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); 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/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/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/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-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); 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); 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); } 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);