]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/shared/blockdev-util.c
tree-wide: use -EBADF for fd initialization
[thirdparty/systemd.git] / src / shared / blockdev-util.c
index 0d921cc045c178bf5ac1a8fa527ff91f08312175..85ae8ff8078bc424e041ed3cbf0318dbf45d998d 100644 (file)
@@ -3,6 +3,7 @@
 #include <linux/blkpg.h>
 #include <sys/file.h>
 #include <sys/ioctl.h>
+#include <sys/mount.h>
 #include <unistd.h>
 
 #include "sd-device.h"
 #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 = 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;
+
+        assert(dev);
+
+        r = sd_device_get_subsystem(dev, &s);
+        if (r < 0)
+                return r;
+
+        if (!streq(s, "block"))
+                return -ENOTBLK;
+
+        r = sd_device_get_devtype(dev, &s);
+        if (r < 0)
+                return r;
+
+        return streq(s, "disk");
+}
+
+int block_device_get_whole_disk(sd_device *dev, sd_device **ret) {
+        int r;
+
+        assert(dev);
+        assert(ret);
+
+        /* Do not unref returned sd_device object. */
+
+        r = block_device_is_whole_disk(dev);
+        if (r < 0)
+                return r;
+        if (r == 0) {
+                r = sd_device_get_parent(dev, &dev);
+                if (r == -ENOENT) /* Already removed? Let's return a recognizable error. */
+                        return -ENODEV;
+                if (r < 0)
+                        return r;
+
+                r = block_device_is_whole_disk(dev);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        return -ENXIO;
+        }
+
+        *ret = dev;
+        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 = -EBADF;
+
+        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;
@@ -92,7 +283,7 @@ int get_block_device_fd(int fd, dev_t *ret) {
 }
 
 int get_block_device(const char *path, dev_t *ret) {
-        _cleanup_close_ int fd = -1;
+        _cleanup_close_ int fd = -EBADF;
 
         assert(path);
         assert(ret);
@@ -105,86 +296,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.  */
-
-        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");
+        assert(ret);
 
-        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;
-
-        if (major(devt) == 0)
-                return -ENOENT;
+                return r;
 
-        *ret = devt;
-        return 1;
+        return sd_device_get_devnum(origin, ret);
 }
 
 int get_block_device_harder_fd(int fd, dev_t *ret) {
@@ -208,7 +333,7 @@ int get_block_device_harder_fd(int fd, dev_t *ret) {
 }
 
 int get_block_device_harder(const char *path, dev_t *ret) {
-        _cleanup_close_ int fd = -1;
+        _cleanup_close_ int fd = -EBADF;
 
         assert(path);
         assert(ret);
@@ -221,8 +346,7 @@ int get_block_device_harder(const char *path, dev_t *ret) {
 }
 
 int lock_whole_block_device(dev_t devt, int operation) {
-        _cleanup_free_ char *whole_node = NULL;
-        _cleanup_close_ int lock_fd = -1;
+        _cleanup_close_ int lock_fd = -EBADF;
         dev_t whole_devt;
         int r;
 
@@ -232,14 +356,10 @@ int lock_whole_block_device(dev_t devt, int operation) {
         if (r < 0)
                 return r;
 
-        r = device_path_make_major_minor(S_IFBLK, whole_devt, &whole_node);
+        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 = open(whole_node, O_RDONLY|O_CLOEXEC|O_NONBLOCK);
-        if (lock_fd < 0)
-                return -errno;
-
         if (flock(lock_fd, operation) < 0)
                 return -errno;
 
@@ -386,44 +506,20 @@ 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);
 }
 
 int path_get_whole_disk(const char *path, bool backing, dev_t *ret) {
-        _cleanup_close_ int fd = -1;
+        _cleanup_close_ int fd = -EBADF;
 
         fd = open(path, O_CLOEXEC|O_PATH);
         if (fd < 0)
@@ -432,7 +528,13 @@ int path_get_whole_disk(const char *path, bool backing, dev_t *ret) {
         return fd_get_whole_disk(fd, backing, ret);
 }
 
-int block_device_add_partition(int fd, const char *name, int nr, uint64_t start, uint64_t size) {
+int block_device_add_partition(
+                int fd,
+                const char *name,
+                int nr,
+                uint64_t start,
+                uint64_t size) {
+
         assert(fd >= 0);
         assert(name);
         assert(nr > 0);
@@ -457,7 +559,11 @@ int block_device_add_partition(int fd, const char *name, int nr, uint64_t start,
         return RET_NERRNO(ioctl(fd, BLKPG, &ba));
 }
 
-int block_device_remove_partition(int fd, const char *name, int nr) {
+int block_device_remove_partition(
+                int fd,
+                const char *name,
+                int nr) {
+
         assert(fd >= 0);
         assert(name);
         assert(nr > 0);
@@ -480,29 +586,68 @@ int block_device_remove_partition(int fd, const char *name, int nr) {
         return RET_NERRNO(ioctl(fd, BLKPG, &ba));
 }
 
-int block_device_remove_all_partitions(int fd) {
-        struct stat stat;
-        _cleanup_(sd_device_unrefp) sd_device *dev = NULL;
+int block_device_resize_partition(
+                int fd,
+                int nr,
+                uint64_t start,
+                uint64_t size) {
+
+        assert(fd >= 0);
+        assert(nr > 0);
+
+        struct blkpg_partition bp = {
+                .pno = nr,
+                .start = start,
+                .length = size,
+        };
+
+        struct blkpg_ioctl_arg ba = {
+                .op = BLKPG_RESIZE_PARTITION,
+                .data = &bp,
+                .datalen = sizeof(bp),
+        };
+
+        return RET_NERRNO(ioctl(fd, BLKPG, &ba));
+}
+
+int partition_enumerator_new(sd_device *dev, sd_device_enumerator **ret) {
         _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL;
-        sd_device *part;
-        int r, k = 0;
+        const char *s;
+        int r;
 
-        if (fstat(fd, &stat) < 0)
-                return -errno;
+        assert(dev);
+        assert(ret);
 
-        r = sd_device_new_from_devnum(&dev, 'b', stat.st_rdev);
+        /* Refuse invocation on partition block device, insist on "whole" device */
+        r = block_device_is_whole_disk(dev);
         if (r < 0)
                 return r;
+        if (r == 0)
+                return -ENXIO; /* return a recognizable error */
 
         r = sd_device_enumerator_new(&e);
         if (r < 0)
                 return r;
 
+        r = sd_device_enumerator_allow_uninitialized(e);
+        if (r < 0)
+                return r;
+
         r = sd_device_enumerator_add_match_parent(e, dev);
         if (r < 0)
                 return r;
 
-        r = sd_device_enumerator_add_match_subsystem(e, "block", true);
+        r = sd_device_get_sysname(dev, &s);
+        if (r < 0)
+                return r;
+
+        /* Also add sysname check for safety. Hopefully, this also improves performance. */
+        s = strjoina(s, "*");
+        r = sd_device_enumerator_add_match_sysname(e, s);
+        if (r < 0)
+                return r;
+
+        r = sd_device_enumerator_add_match_subsystem(e, "block", /* match = */ true);
         if (r < 0)
                 return r;
 
@@ -510,10 +655,46 @@ int block_device_remove_all_partitions(int fd) {
         if (r < 0)
                 return r;
 
+        *ret = TAKE_PTR(e);
+        return 0;
+}
+
+int block_device_remove_all_partitions(sd_device *dev, int fd) {
+        _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL;
+        _cleanup_(sd_device_unrefp) sd_device *dev_unref = NULL;
+        _cleanup_close_ int fd_close = -EBADF;
+        bool has_partitions = false;
+        sd_device *part;
+        int r, k = 0;
+
+        assert(dev || fd >= 0);
+
+        if (!dev) {
+                r = block_device_new_from_fd(fd, 0, &dev_unref);
+                if (r < 0)
+                        return r;
+
+                dev = dev_unref;
+        }
+
+        r = partition_enumerator_new(dev, &e);
+        if (r < 0)
+                return r;
+
+        if (fd < 0) {
+                fd_close = sd_device_open(dev, O_CLOEXEC|O_NONBLOCK|O_NOCTTY|O_RDONLY);
+                if (fd_close < 0)
+                        return fd_close;
+
+                fd = fd_close;
+        }
+
         FOREACH_DEVICE(e, part) {
                 const char *v, *devname;
                 int nr;
 
+                has_partitions = true;
+
                 r = sd_device_get_devname(part, &devname);
                 if (r < 0)
                         return r;
@@ -533,12 +714,47 @@ int block_device_remove_all_partitions(int fd) {
                 }
                 if (r < 0) {
                         log_debug_errno(r, "Failed to remove partition %s: %m", devname);
-                        k = k ?: r;
+                        k = k < 0 ? k : r;
                         continue;
                 }
 
                 log_debug("Removed partition %s", devname);
         }
 
-        return k;
+        return k < 0 ? k : has_partitions;
+}
+
+int block_device_has_partitions(sd_device *dev) {
+        _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL;
+        int r;
+
+        assert(dev);
+
+        /* Checks if the specified device currently has partitions. */
+
+        r = partition_enumerator_new(dev, &e);
+        if (r < 0)
+                return r;
+
+        return !!sd_device_enumerator_get_device_first(e);
+}
+
+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;
 }