]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
rereadpt: implement userspace-based BLKRRPART re-implementation
authorLennart Poettering <lennart@poettering.net>
Mon, 20 Oct 2025 10:36:29 +0000 (12:36 +0200)
committerLennart Poettering <lennart@poettering.net>
Wed, 22 Oct 2025 20:56:01 +0000 (22:56 +0200)
src/shared/meson.build
src/shared/reread-partition-table.c [new file with mode: 0644]
src/shared/reread-partition-table.h [new file with mode: 0644]
src/test/meson.build
src/test/test-reread-partition-table-manual.c [new file with mode: 0644]
src/test/test-reread-partition-table.c [new file with mode: 0644]

index 38915f38851b464a7e1dbbcfab4818162c64d24c..b2f8f61f237e513cbfa9f293e8ba54a821c1cab9 100644 (file)
@@ -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 (file)
index 0000000..78f0759
--- /dev/null
@@ -0,0 +1,396 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <linux/fs.h>
+#include <sys/file.h>
+#include <sys/ioctl.h>
+
+#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 (file)
index 0000000..240ab52
--- /dev/null
@@ -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);
index 446336048aa2fc3a04736abc7f0f05ac1a463d28..0b69fc8b9f68ddf21ea9001f99c75f0a54bab2b5 100644 (file)
@@ -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 (file)
index 0000000..b9d3547
--- /dev/null
@@ -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 (file)
index 0000000..07a0a02
--- /dev/null
@@ -0,0 +1,162 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <linux/loop.h>
+
+#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);