--- /dev/null
+/* 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);
+}
--- /dev/null
+/* 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);