diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index 8cdf312f58..6b0616e4bb 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -874,7 +874,7 @@ static int action_umount(const char *path) { return log_error_errno(r, "Failed to get devname of block device " DEVNUM_FORMAT_STR ": %m", DEVNUM_FORMAT_VAL(devno)); - r = loop_device_open(devname, 0, LOCK_EX, &d); + r = loop_device_open_from_path(devname, 0, LOCK_EX, &d); if (r < 0) return log_error_errno(r, "Failed to open loop device '%s': %m", devname); diff --git a/src/gpt-auto-generator/gpt-auto-generator.c b/src/gpt-auto-generator/gpt-auto-generator.c index 3155a7cb60..31377d877d 100644 --- a/src/gpt-auto-generator/gpt-auto-generator.c +++ b/src/gpt-auto-generator/gpt-auto-generator.c @@ -647,7 +647,7 @@ static int enumerate_partitions(dev_t devnum) { /* Let's take a LOCK_SH lock on the block device, in case udevd is already running. If we don't take * the lock, udevd might end up issuing BLKRRPART in the middle, and we don't want that, since that * might remove all partitions while we are operating on them. */ - r = loop_device_open(devname, O_RDONLY, LOCK_SH, &loop); + r = loop_device_open_from_path(devname, O_RDONLY, LOCK_SH, &loop); if (r < 0) return log_debug_errno(r, "Failed to open %s: %m", devname); diff --git a/src/home/homework-luks.c b/src/home/homework-luks.c index 993a3143c9..43e6ee02a8 100644 --- a/src/home/homework-luks.c +++ b/src/home/homework-luks.c @@ -1284,7 +1284,7 @@ int home_setup_luks( return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to determine backing device for DM %s.", setup->dm_name); if (!setup->loop) { - r = loop_device_open(n, O_RDWR, LOCK_UN, &setup->loop); + r = loop_device_open_from_path(n, O_RDWR, LOCK_UN, &setup->loop); if (r < 0) return log_error_errno(r, "Failed to open loopback device %s: %m", n); } diff --git a/src/shared/blockdev-util.c b/src/shared/blockdev-util.c index 4402b7a97c..72fad160ed 100644 --- a/src/shared/blockdev-util.c +++ b/src/shared/blockdev-util.c @@ -20,6 +20,56 @@ #include "missing_magic.h" #include "parse-util.h" +static int fd_get_devnum(int fd, BlockDeviceLookupFlag flags, dev_t *ret) { + struct stat st; + dev_t devnum; + int r; + + assert(fd >= 0); + assert(ret); + + if (fstat(fd, &st) < 0) + return -errno; + + if (S_ISBLK(st.st_mode)) + devnum = st.st_rdev; + else if (!FLAGS_SET(flags, BLOCK_DEVICE_LOOKUP_BACKING)) + return -ENOTBLK; + else if (!S_ISREG(st.st_mode) && !S_ISDIR(st.st_mode)) + return -ENOTBLK; + else if (major(st.st_dev) != 0) + devnum = st.st_dev; + else { + /* If major(st.st_dev) is zero, this might mean we are backed by btrfs, which needs special + * handing, to get the backing device node. */ + + r = fcntl(fd, F_GETFL); + if (r < 0) + return -errno; + + if (FLAGS_SET(r, O_PATH)) { + _cleanup_close_ int regfd = -1; + + /* The fstat() above we can execute on an O_PATH fd. But the btrfs ioctl we cannot. + * Hence acquire a "real" fd first, without the O_PATH flag. */ + + regfd = fd_reopen(fd, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); + if (regfd < 0) + return regfd; + + r = btrfs_get_block_device_fd(regfd, &devnum); + } else + r = btrfs_get_block_device_fd(fd, &devnum); + if (r == -ENOTTY) /* not btrfs */ + return -ENOTBLK; + if (r < 0) + return r; + } + + *ret = devnum; + return 0; +} + int block_device_is_whole_disk(sd_device *dev) { const char *s; int r; @@ -69,6 +119,113 @@ int block_device_get_whole_disk(sd_device *dev, sd_device **ret) { return 0; } +static int block_device_get_originating(sd_device *dev, sd_device **ret) { + _cleanup_(sd_device_unrefp) sd_device *first_found = NULL; + const char *suffix; + sd_device *child; + dev_t devnum = 0; /* avoid false maybe-uninitialized warning */ + + /* For the specified block device tries to chase it through the layers, in case LUKS-style DM + * stacking is used, trying to find the next underlying layer. */ + + assert(dev); + assert(ret); + + FOREACH_DEVICE_CHILD_WITH_SUFFIX(dev, child, suffix) { + sd_device *child_whole_disk; + dev_t n; + + if (!path_startswith(suffix, "slaves")) + continue; + + if (block_device_get_whole_disk(child, &child_whole_disk) < 0) + continue; + + if (sd_device_get_devnum(child_whole_disk, &n) < 0) + continue; + + if (!first_found) { + first_found = sd_device_ref(child); + devnum = n; + continue; + } + + /* We found a device backed by multiple other devices. We don't really support automatic + * discovery on such setups, with the exception of dm-verity partitions. In this case there + * are two backing devices: the data partition and the hash partition. We are fine with such + * setups, however, only if both partitions are on the same physical device. Hence, let's + * verify this by iterating over every node in the 'slaves/' directory and comparing them with + * the first that gets returned by readdir(), to ensure they all point to the same device. */ + if (n != devnum) + return -ENOTUNIQ; + } + + if (!first_found) + return -ENOENT; + + *ret = TAKE_PTR(first_found); + return 1; /* found */ +} + +int block_device_new_from_fd(int fd, BlockDeviceLookupFlag flags, sd_device **ret) { + _cleanup_(sd_device_unrefp) sd_device *dev = NULL; + dev_t devnum; + int r; + + assert(fd >= 0); + assert(ret); + + r = fd_get_devnum(fd, flags, &devnum); + if (r < 0) + return r; + + r = sd_device_new_from_devnum(&dev, 'b', devnum); + if (r < 0) + return r; + + if (FLAGS_SET(flags, BLOCK_DEVICE_LOOKUP_ORIGINATING)) { + _cleanup_(sd_device_unrefp) sd_device *dev_origin = NULL; + sd_device *dev_whole_disk; + + r = block_device_get_whole_disk(dev, &dev_whole_disk); + if (r < 0) + return r; + + r = block_device_get_originating(dev_whole_disk, &dev_origin); + if (r < 0 && r != -ENOENT) + return r; + if (r > 0) + device_unref_and_replace(dev, dev_origin); + } + + if (FLAGS_SET(flags, BLOCK_DEVICE_LOOKUP_WHOLE_DISK)) { + sd_device *dev_whole_disk; + + r = block_device_get_whole_disk(dev, &dev_whole_disk); + if (r < 0) + return r; + + *ret = sd_device_ref(dev_whole_disk); + return 0; + } + + *ret = sd_device_ref(dev); + return 0; +} + +int block_device_new_from_path(const char *path, BlockDeviceLookupFlag flags, sd_device **ret) { + _cleanup_close_ int fd = -1; + + assert(path); + assert(ret); + + fd = open(path, O_CLOEXEC|O_PATH); + if (fd < 0) + return -errno; + + return block_device_new_from_fd(fd, flags, ret); +} + int block_get_whole_disk(dev_t d, dev_t *ret) { char p[SYS_BLOCK_PATH_MAX("/partition")]; _cleanup_free_ char *s = NULL; @@ -169,86 +326,20 @@ int get_block_device(const char *path, dev_t *ret) { } int block_get_originating(dev_t dt, dev_t *ret) { - _cleanup_closedir_ DIR *d = NULL; - _cleanup_free_ char *t = NULL; - char p[SYS_BLOCK_PATH_MAX("/slaves")]; - _cleanup_free_ char *first_found = NULL; - const char *q; - dev_t devt; + _cleanup_(sd_device_unrefp) sd_device *dev = NULL, *origin = NULL; int r; - /* For the specified block device tries to chase it through the layers, in case LUKS-style DM stacking is used, - * trying to find the next underlying layer. */ + assert(ret); - xsprintf_sys_block_path(p, "/slaves", dt); - d = opendir(p); - if (!d) - return -errno; - - FOREACH_DIRENT_ALL(de, d, return -errno) { - - if (dot_or_dot_dot(de->d_name)) - continue; - - if (!IN_SET(de->d_type, DT_LNK, DT_UNKNOWN)) - continue; - - if (first_found) { - _cleanup_free_ char *u = NULL, *v = NULL, *a = NULL, *b = NULL; - - /* We found a device backed by multiple other devices. We don't really support - * automatic discovery on such setups, with the exception of dm-verity partitions. In - * this case there are two backing devices: the data partition and the hash - * partition. We are fine with such setups, however, only if both partitions are on - * the same physical device. Hence, let's verify this by iterating over every node - * in the 'slaves/' directory and comparing them with the first that gets returned by - * readdir(), to ensure they all point to the same device. */ - - u = path_join(p, de->d_name, "../dev"); - if (!u) - return -ENOMEM; - - v = path_join(p, first_found, "../dev"); - if (!v) - return -ENOMEM; - - r = read_one_line_file(u, &a); - if (r < 0) - return log_debug_errno(r, "Failed to read %s: %m", u); - - r = read_one_line_file(v, &b); - if (r < 0) - return log_debug_errno(r, "Failed to read %s: %m", v); - - /* Check if the parent device is the same. If not, then the two backing devices are on - * different physical devices, and we don't support that. */ - if (!streq(a, b)) - return -ENOTUNIQ; - } else { - first_found = strdup(de->d_name); - if (!first_found) - return -ENOMEM; - } - } - - if (!first_found) - return -ENOENT; - - q = strjoina(p, "/", first_found, "/dev"); - - r = read_one_line_file(q, &t); + r = sd_device_new_from_devnum(&dev, 'b', dt); if (r < 0) return r; - r = parse_devnum(t, &devt); + r = block_device_get_originating(dev, &origin); if (r < 0) - return -EINVAL; + return r; - if (major(devt) == 0) - return -ENOENT; - - *ret = devt; - return 1; + return sd_device_get_devnum(origin, ret); } int get_block_device_harder_fd(int fd, dev_t *ret) { @@ -445,38 +536,14 @@ int path_is_encrypted(const char *path) { int fd_get_whole_disk(int fd, bool backing, dev_t *ret) { dev_t devt; - struct stat st; int r; + assert(fd >= 0); assert(ret); - if (fstat(fd, &st) < 0) - return -errno; - - if (S_ISBLK(st.st_mode)) - devt = st.st_rdev; - else if (!backing) - return -ENOTBLK; - else if (!S_ISREG(st.st_mode) && !S_ISDIR(st.st_mode)) - return -ENOTBLK; - else if (major(st.st_dev) != 0) - devt = st.st_dev; - else { - _cleanup_close_ int regfd = -1; - - /* If major(st.st_dev) is zero, this might mean we are backed by btrfs, which needs special - * handing, to get the backing device node. */ - - regfd = fd_reopen(fd, O_RDONLY|O_CLOEXEC|O_NONBLOCK); - if (regfd < 0) - return regfd; - - r = btrfs_get_block_device_fd(regfd, &devt); - if (r == -ENOTTY) - return -ENOTBLK; - if (r < 0) - return r; - } + r = fd_get_devnum(fd, backing ? BLOCK_DEVICE_LOOKUP_BACKING : 0, &devt); + if (r < 0) + return r; return block_get_whole_disk(devt, ret); } @@ -633,12 +700,7 @@ int block_device_remove_all_partitions(sd_device *dev, int fd) { assert(dev || fd >= 0); if (!dev) { - struct stat st; - - if (fstat(fd, &st) < 0) - return -errno; - - r = sd_device_new_from_stat_rdev(&dev_unref, &st); + r = block_device_new_from_fd(fd, 0, &dev_unref); if (r < 0) return r; diff --git a/src/shared/blockdev-util.h b/src/shared/blockdev-util.h index 1f612157fc..b2c14102ae 100644 --- a/src/shared/blockdev-util.h +++ b/src/shared/blockdev-util.h @@ -14,6 +14,17 @@ #define xsprintf_sys_block_path(buf, suffix, devno) \ xsprintf(buf, "/sys/dev/block/%u:%u%s", major(devno), minor(devno), strempty(suffix)) +typedef enum BlockDeviceLookupFlag { + 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; + +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_is_whole_disk(sd_device *dev); int block_device_get_whole_disk(sd_device *dev, sd_device **ret); diff --git a/src/shared/loop-util.c b/src/shared/loop-util.c index 54251f0fb7..84f4e79c07 100644 --- a/src/shared/loop-util.c +++ b/src/shared/loop-util.c @@ -414,7 +414,7 @@ static int loop_device_make_internal( /* If this is already a block device and we are supposed to cover the whole of it * then store an fd to the original open device node — and do not actually create an * unnecessary loopback device for it. */ - return loop_device_open_full(NULL, fd, open_flags, lock_op, ret); + return loop_device_open_from_fd(fd, open_flags, lock_op, ret); } else { r = stat_verify_regular(&st); if (r < 0) @@ -713,56 +713,40 @@ void loop_device_unrelinquish(LoopDevice *d) { d->relinquished = false; } -int loop_device_open_full( - const char *loop_path, - int loop_fd, +int loop_device_open( + sd_device *dev, int open_flags, int lock_op, LoopDevice **ret) { - _cleanup_(sd_device_unrefp) sd_device *dev = NULL; _cleanup_close_ int fd = -1, lock_fd = -1; - _cleanup_free_ char *p = NULL, *backing_file = NULL; + _cleanup_free_ char *node = NULL, *backing_file = NULL; struct loop_info64 info; uint64_t diskseq = 0; - struct stat st; LoopDevice *d; + const char *s; + dev_t devnum; int r, nr = -1; - assert(loop_path || loop_fd >= 0); + assert(dev); assert(IN_SET(open_flags, O_RDWR, O_RDONLY)); assert(ret); - if (loop_fd < 0) { - fd = open(loop_path, O_CLOEXEC|O_NONBLOCK|O_NOCTTY|open_flags); - if (fd < 0) - return -errno; - loop_fd = fd; + /* Even if fd is provided through the argument in loop_device_open_from_fd(), we reopen the inode + * here, instead of keeping just a dup() clone of it around, since we want to ensure that the + * O_DIRECT flag of the handle we keep is off, we have our own file index, and have the right + * read/write mode in effect. */ + fd = sd_device_open(dev, O_CLOEXEC|O_NONBLOCK|O_NOCTTY|open_flags); + if (fd < 0) + return fd; + + if ((lock_op & ~LOCK_NB) != LOCK_UN) { + lock_fd = open_lock_fd(fd, lock_op); + if (lock_fd < 0) + return lock_fd; } - if (fstat(loop_fd, &st) < 0) - return -errno; - if (!S_ISBLK(st.st_mode)) - return -ENOTBLK; - - r = sd_device_new_from_stat_rdev(&dev, &st); - if (r < 0) - return r; - - if (fd < 0) { - /* If loop_fd is provided through the argument, then we reopen the inode here, instead of - * keeping just a dup() clone of it around, since we want to ensure that the O_DIRECT - * flag of the handle we keep is off, we have our own file index, and have the right - * read/write mode in effect.*/ - fd = fd_reopen(loop_fd, O_CLOEXEC|O_NONBLOCK|O_NOCTTY|open_flags); - if (fd < 0) - return fd; - loop_fd = fd; - } - - if (ioctl(loop_fd, LOOP_GET_STATUS64, &info) >= 0) { - const char *s; - + if (ioctl(fd, LOOP_GET_STATUS64, &info) >= 0) { #if HAVE_VALGRIND_MEMCHECK_H /* Valgrind currently doesn't know LOOP_GET_STATUS64. Remove this once it does */ VALGRIND_MAKE_MEM_DEFINED(&info, sizeof(info)); @@ -776,22 +760,20 @@ int loop_device_open_full( } } - r = fd_get_diskseq(loop_fd, &diskseq); + r = fd_get_diskseq(fd, &diskseq); if (r < 0 && r != -EOPNOTSUPP) return r; - if ((lock_op & ~LOCK_NB) != LOCK_UN) { - lock_fd = open_lock_fd(loop_fd, lock_op); - if (lock_fd < 0) - return lock_fd; - } - - r = sd_device_get_devname(dev, &loop_path); + r = sd_device_get_devnum(dev, &devnum); if (r < 0) return r; - p = strdup(loop_path); - if (!p) + r = sd_device_get_devname(dev, &s); + if (r < 0) + return r; + + node = strdup(s); + if (!node) return -ENOMEM; d = new(LoopDevice, 1); @@ -803,18 +785,54 @@ int loop_device_open_full( .fd = TAKE_FD(fd), .lock_fd = TAKE_FD(lock_fd), .nr = nr, - .node = TAKE_PTR(p), - .dev = TAKE_PTR(dev), + .node = TAKE_PTR(node), + .dev = sd_device_ref(dev), .backing_file = TAKE_PTR(backing_file), .relinquished = true, /* It's not ours, don't try to destroy it when this object is freed */ - .devno = st.st_rdev, + .devno = devnum, .diskseq = diskseq, .uevent_seqnum_not_before = UINT64_MAX, .timestamp_not_before = USEC_INFINITY, }; *ret = d; - return d->fd; + return 0; +} + +int loop_device_open_from_fd( + int fd, + int open_flags, + int lock_op, + LoopDevice **ret) { + + _cleanup_(sd_device_unrefp) sd_device *dev = NULL; + int r; + + assert(fd >= 0); + + r = block_device_new_from_fd(fd, 0, &dev); + if (r < 0) + return r; + + return loop_device_open(dev, open_flags, lock_op, ret); +} + +int loop_device_open_from_path( + const char *path, + int open_flags, + int lock_op, + LoopDevice **ret) { + + _cleanup_(sd_device_unrefp) sd_device *dev = NULL; + int r; + + assert(path); + + r = block_device_new_from_path(path, 0, &dev); + if (r < 0) + return r; + + return loop_device_open(dev, open_flags, lock_op, ret); } static int resize_partition(int partition_fd, uint64_t offset, uint64_t size) { diff --git a/src/shared/loop-util.h b/src/shared/loop-util.h index 756041adaa..1658cd3592 100644 --- a/src/shared/loop-util.h +++ b/src/shared/loop-util.h @@ -30,10 +30,9 @@ struct LoopDevice { int loop_device_make(int fd, int open_flags, uint64_t offset, uint64_t size, uint32_t loop_flags, int lock_op, LoopDevice **ret); int loop_device_make_by_path(const char *path, int open_flags, uint32_t loop_flags, int lock_op, LoopDevice **ret); -int loop_device_open_full(const char *loop_path, int loop_fd, int open_flags, int lock_op, LoopDevice **ret); -static inline int loop_device_open(const char *loop_path, int open_flags, int lock_op, LoopDevice **ret) { - return loop_device_open_full(loop_path, -1, open_flags, lock_op, ret); -} +int loop_device_open(sd_device *dev, int open_flags, int lock_op, LoopDevice **ret); +int loop_device_open_from_fd(int fd, int open_flags, int lock_op, LoopDevice **ret); +int loop_device_open_from_path(const char *path, int open_flags, int lock_op, LoopDevice **ret); LoopDevice* loop_device_ref(LoopDevice *d); LoopDevice* loop_device_unref(LoopDevice *d);