]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/basic/blockdev-util.c
Merge pull request #16678 from poettering/loop-configure
[thirdparty/systemd.git] / src / basic / blockdev-util.c
index b15b8cf7703c05b24cd8fe489e2d5b1f05bf60a0..21ff3ba1b13c018ac283476216d308b12585b19b 100644 (file)
@@ -1,12 +1,7 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
-/***
-  This file is part of systemd.
 
-  Copyright 2010 Lennart Poettering
-***/
-
-#include <sys/stat.h>
-#include <sys/statfs.h>
+#include <sys/file.h>
+#include <unistd.h>
 
 #include "alloc-util.h"
 #include "blockdev-util.h"
 #include "dirent-util.h"
 #include "fd-util.h"
 #include "fileio.h"
-#include "missing.h"
+#include "missing_magic.h"
+#include "parse-util.h"
 #include "stat-util.h"
 
 int block_get_whole_disk(dev_t d, dev_t *ret) {
         char p[SYS_BLOCK_PATH_MAX("/partition")];
         _cleanup_free_ char *s = NULL;
-        unsigned n, m;
+        dev_t devt;
         int r;
 
         assert(ret);
 
+        if (major(d) == 0)
+                return -ENODEV;
+
         /* If it has a queue this is good enough for us */
         xsprintf_sys_block_path(p, "/queue", d);
         if (access(p, F_OK) >= 0) {
                 *ret = d;
                 return 0;
         }
+        if (errno != ENOENT)
+                return -errno;
 
         /* If it is a partition find the originating device */
         xsprintf_sys_block_path(p, "/partition", d);
         if (access(p, F_OK) < 0)
-                return -ENOENT;
+                return -errno;
 
         /* Get parent dev_t */
         xsprintf_sys_block_path(p, "/../dev", d);
@@ -43,76 +44,68 @@ int block_get_whole_disk(dev_t d, dev_t *ret) {
         if (r < 0)
                 return r;
 
-        r = sscanf(s, "%u:%u", &m, &n);
-        if (r != 2)
-                return -EINVAL;
+        r = parse_dev(s, &devt);
+        if (r < 0)
+                return r;
 
         /* Only return this if it is really good enough for us. */
-        xsprintf_sys_block_path(p, "/queue", makedev(m, n));
+        xsprintf_sys_block_path(p, "/queue", devt);
         if (access(p, F_OK) < 0)
-                return -ENOENT;
+                return -errno;
 
-        *ret = makedev(m, n);
-        return 0;
+        *ret = devt;
+        return 1;
 }
 
-int get_block_device(const char *path, dev_t *dev) {
+int get_block_device(const char *path, dev_t *ret) {
+        _cleanup_close_ int fd = -1;
         struct stat st;
-        struct statfs sfs;
+        int r;
 
         assert(path);
-        assert(dev);
+        assert(ret);
 
-        /* Get's the block device directly backing a file system. If
-         * the block device is encrypted, returns the device mapper
-         * block device. */
+        /* Gets the block device directly backing a file system. If the block device is encrypted, returns
+         * the device mapper block device. */
 
-        if (lstat(path, &st))
+        fd = open(path, O_NOFOLLOW|O_CLOEXEC);
+        if (fd < 0)
+                return -errno;
+
+        if (fstat(fd, &st))
                 return -errno;
 
         if (major(st.st_dev) != 0) {
-                *dev = st.st_dev;
+                *ret = st.st_dev;
                 return 1;
         }
 
-        if (statfs(path, &sfs) < 0)
-                return -errno;
-
-        if (F_TYPE_EQUAL(sfs.f_type, BTRFS_SUPER_MAGIC))
-                return btrfs_get_block_device(path, dev);
+        r = btrfs_get_block_device_fd(fd, ret);
+        if (r > 0)
+                return 1;
+        if (r != -ENOTTY) /* not btrfs */
+                return r;
 
+        *ret = 0;
         return 0;
 }
 
-int get_block_device_harder(const char *path, dev_t *dev) {
+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")];
         struct dirent *de, *found = NULL;
         const char *q;
-        unsigned maj, min;
-        dev_t dt;
+        dev_t devt;
         int r;
 
-        assert(path);
-        assert(dev);
-
-        /* Gets the backing block device for a file system, and
-         * handles LUKS encrypted file systems, looking for its
-         * immediate parent, if there is one. */
-
-        r = get_block_device(path, &dt);
-        if (r <= 0)
-                return 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) {
-                if (errno == ENOENT)
-                        goto fallback;
-
+        if (!d)
                 return -errno;
-        }
 
         FOREACH_DIRENT_ALL(de, d, return -errno) {
 
@@ -131,56 +124,130 @@ int get_block_device_harder(const char *path, dev_t *dev) {
                          * setups, however, only if both partitions are on the same physical device. Hence, let's
                          * verify this. */
 
-                        u = strjoin(p, "/", de->d_name, "/../dev");
+                        u = path_join(p, de->d_name, "../dev");
                         if (!u)
                                 return -ENOMEM;
 
-                        v = strjoin(p, "/", found->d_name, "/../dev");
+                        v = path_join(p, found->d_name, "../dev");
                         if (!v)
                                 return -ENOMEM;
 
                         r = read_one_line_file(u, &a);
-                        if (r < 0) {
-                                log_debug_errno(r, "Failed to read %s: %m", u);
-                                goto fallback;
-                        }
+                        if (r < 0)
+                                return log_debug_errno(r, "Failed to read %s: %m", u);
 
                         r = read_one_line_file(v, &b);
-                        if (r < 0) {
-                                log_debug_errno(r, "Failed to read %s: %m", v);
-                                goto fallback;
-                        }
+                        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))
-                                goto fallback;
+                                return -ENOTUNIQ;
                 }
 
                 found = de;
         }
 
         if (!found)
-                goto fallback;
+                return -ENOENT;
 
         q = strjoina(p, "/", found->d_name, "/dev");
 
         r = read_one_line_file(q, &t);
-        if (r == -ENOENT)
-                goto fallback;
         if (r < 0)
                 return r;
 
-        if (sscanf(t, "%u:%u", &maj, &min) != 2)
+        r = parse_dev(t, &devt);
+        if (r < 0)
                 return -EINVAL;
 
-        if (maj == 0)
-                goto fallback;
+        if (major(devt) == 0)
+                return -ENOENT;
 
-        *dev = makedev(maj, min);
+        *ret = devt;
         return 1;
+}
+
+int get_block_device_harder(const char *path, dev_t *ret) {
+        int r;
+
+        assert(path);
+        assert(ret);
+
+        /* Gets the backing block device for a file system, and handles LUKS encrypted file systems, looking for its
+         * immediate parent, if there is one. */
+
+        r = get_block_device(path, ret);
+        if (r <= 0)
+                return r;
+
+        r = block_get_originating(*ret, ret);
+        if (r < 0)
+                log_debug_errno(r, "Failed to chase block device '%s', ignoring: %m", path);
 
-fallback:
-        *dev = dt;
         return 1;
 }
+
+int lock_whole_block_device(dev_t devt, int operation) {
+        _cleanup_free_ char *whole_node = NULL;
+        _cleanup_close_ int lock_fd = -1;
+        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 */
+
+        r = block_get_whole_disk(devt, &whole_devt);
+        if (r < 0)
+                return r;
+
+        r = device_path_make_major_minor(S_IFBLK, whole_devt, &whole_node);
+        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;
+
+        return TAKE_FD(lock_fd);
+}
+
+int blockdev_partscan_enabled(int fd) {
+        _cleanup_free_ char *p = NULL, *buf = NULL;
+        unsigned long long ull;
+        struct stat st;
+        int r;
+
+        /* Checks if partition scanning is correctly enabled on the block device */
+
+        if (fstat(fd, &st) < 0)
+                return -errno;
+
+        if (!S_ISBLK(st.st_mode))
+                return -ENOTBLK;
+
+        if (asprintf(&p, "/sys/dev/block/%u:%u/capability", major(st.st_rdev), minor(st.st_rdev)) < 0)
+                return -ENOMEM;
+
+        r = read_one_line_file(p, &buf);
+        if (r == -ENOENT) /* If the capability file doesn't exist then we are most likely looking at a
+                           * partition block device, not the whole block device. And that means we have no
+                           * partition scanning on for it (we do for its parent, but not for the partition
+                           * itself). */
+                return false;
+        if (r < 0)
+                return r;
+
+        r = safe_atollu_full(buf, 16, &ull);
+        if (r < 0)
+                return r;
+
+#ifndef GENHD_FL_NO_PART_SCAN
+#define GENHD_FL_NO_PART_SCAN (0x0200)
+#endif
+
+        return !FLAGS_SET(ull, GENHD_FL_NO_PART_SCAN);
+}