]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/basic/btrfs-util.c
tree-wide: use TAKE_PTR() and TAKE_FD() macros
[thirdparty/systemd.git] / src / basic / btrfs-util.c
index d07d1df5a8872d7d024dc849fa4b76cd4963d42b..8b7d51f85a51de79963b69d9475103cb1669403c 100644 (file)
@@ -1,5 +1,4 @@
-/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
-
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
@@ -22,6 +21,7 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <inttypes.h>
+#include <linux/fs.h>
 #include <linux/loop.h>
 #include <stddef.h>
 #include <stdio.h>
 #include <sys/sysmacros.h>
 #include <unistd.h>
 
-#ifdef HAVE_LINUX_BTRFS_H
+#if HAVE_LINUX_BTRFS_H
 #include <linux/btrfs.h>
 #endif
 
 #include "alloc-util.h"
+#include "blockdev-util.h"
 #include "btrfs-ctree.h"
 #include "btrfs-util.h"
+#include "chattr-util.h"
 #include "copy.h"
+#include "device-nodes.h"
 #include "fd-util.h"
 #include "fileio.h"
+#include "io-util.h"
 #include "macro.h"
 #include "missing.h"
 #include "path-util.h"
-#include "selinux-util.h"
+#include "rm-rf.h"
 #include "smack-util.h"
 #include "sparse-endian.h"
 #include "stat-util.h"
@@ -170,24 +174,6 @@ int btrfs_subvol_make(const char *path) {
         return 0;
 }
 
-int btrfs_subvol_make_label(const char *path) {
-        int r;
-
-        assert(path);
-
-        r = mac_selinux_create_file_prepare(path, S_IFDIR);
-        if (r < 0)
-                return r;
-
-        r = btrfs_subvol_make(path);
-        mac_selinux_create_file_clear();
-
-        if (r < 0)
-                return r;
-
-        return mac_smack_fix(path, false, false);
-}
-
 int btrfs_subvol_set_read_only_fd(int fd, bool b) {
         uint64_t flags, nflags;
         struct stat st;
@@ -246,23 +232,18 @@ int btrfs_subvol_get_read_only_fd(int fd) {
 }
 
 int btrfs_reflink(int infd, int outfd) {
-        struct stat st;
         int r;
 
         assert(infd >= 0);
         assert(outfd >= 0);
 
-        /* Make sure we invoke the ioctl on a regular file, so that no
-         * device driver accidentally gets it. */
+        /* Make sure we invoke the ioctl on a regular file, so that no device driver accidentally gets it. */
 
-        if (fstat(outfd, &st) < 0)
-                return -errno;
-
-        if (!S_ISREG(st.st_mode))
-                return -EINVAL;
-
-        r = ioctl(outfd, BTRFS_IOC_CLONE, infd);
+        r = fd_verify_regular(outfd);
         if (r < 0)
+                return r;
+
+        if (ioctl(outfd, BTRFS_IOC_CLONE, infd) < 0)
                 return -errno;
 
         return 0;
@@ -275,21 +256,17 @@ int btrfs_clone_range(int infd, uint64_t in_offset, int outfd, uint64_t out_offs
                 .src_length = sz,
                 .dest_offset = out_offset,
         };
-        struct stat st;
         int r;
 
         assert(infd >= 0);
         assert(outfd >= 0);
         assert(sz > 0);
 
-        if (fstat(outfd, &st) < 0)
-                return -errno;
-
-        if (!S_ISREG(st.st_mode))
-                return -EINVAL;
-
-        r = ioctl(outfd, BTRFS_IOC_CLONE_RANGE, &args);
+        r = fd_verify_regular(outfd);
         if (r < 0)
+                return r;
+
+        if (ioctl(outfd, BTRFS_IOC_CLONE_RANGE, &args) < 0)
                 return -errno;
 
         return 0;
@@ -774,15 +751,13 @@ int btrfs_subvol_get_subtree_quota(const char *path, uint64_t subvol_id, BtrfsQu
 }
 
 int btrfs_defrag_fd(int fd) {
-        struct stat st;
+        int r;
 
         assert(fd >= 0);
 
-        if (fstat(fd, &st) < 0)
-                return -errno;
-
-        if (!S_ISREG(st.st_mode))
-                return -EINVAL;
+        r = fd_verify_regular(fd);
+        if (r < 0)
+                return r;
 
         if (ioctl(fd, BTRFS_IOC_DEFRAG, NULL) < 0)
                 return -errno;
@@ -907,12 +882,17 @@ int btrfs_subvol_set_subtree_quota_limit(const char *path, uint64_t subvol_id, u
 
 int btrfs_resize_loopback_fd(int fd, uint64_t new_size, bool grow_only) {
         struct btrfs_ioctl_vol_args args = {};
-        _cleanup_free_ char *p = NULL, *loop = NULL, *backing = NULL;
+        char p[SYS_BLOCK_PATH_MAX("/loop/backing_file")];
+        _cleanup_free_ char *backing = NULL;
         _cleanup_close_ int loop_fd = -1, backing_fd = -1;
         struct stat st;
         dev_t dev = 0;
         int r;
 
+        /* In contrast to btrfs quota ioctls ftruncate() cannot make sense of "infinity" or file sizes > 2^31 */
+        if (!FILE_SIZE_VALID(new_size))
+                return -EINVAL;
+
         /* btrfs cannot handle file systems < 16M, hence use this as minimum */
         if (new_size < 16*1024*1024)
                 new_size = 16*1024*1024;
@@ -923,8 +903,7 @@ int btrfs_resize_loopback_fd(int fd, uint64_t new_size, bool grow_only) {
         if (r == 0)
                 return -ENODEV;
 
-        if (asprintf(&p, "/sys/dev/block/%u:%u/loop/backing_file", major(dev), minor(dev)) < 0)
-                return -ENOMEM;
+        xsprintf_sys_block_path(p, "/loop/backing_file", dev);
         r = read_one_line_file(p, &backing);
         if (r == -ENOENT)
                 return -ENODEV;
@@ -948,9 +927,8 @@ int btrfs_resize_loopback_fd(int fd, uint64_t new_size, bool grow_only) {
         if (grow_only && new_size < (uint64_t) st.st_size)
                 return -EINVAL;
 
-        if (asprintf(&loop, "/dev/block/%u:%u", major(dev), minor(dev)) < 0)
-                return -ENOMEM;
-        loop_fd = open(loop, O_RDWR|O_CLOEXEC|O_NOCTTY);
+        xsprintf_sys_block_path(p, NULL, dev);
+        loop_fd = open(p, O_RDWR|O_CLOEXEC|O_NOCTTY);
         if (loop_fd < 0)
                 return -errno;
 
@@ -1206,7 +1184,7 @@ static int subvol_remove_children(int fd, const char *subvolume, uint64_t subvol
         if (!S_ISDIR(st.st_mode))
                 return -EINVAL;
 
-        subvol_fd = openat(fd, subvolume, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY);
+        subvol_fd = openat(fd, subvolume, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW);
         if (subvol_fd < 0)
                 return -errno;
 
@@ -1286,7 +1264,7 @@ static int subvol_remove_children(int fd, const char *subvolume, uint64_t subvol
                                  * hence we need to open the
                                  * containing directory first */
 
-                                child_fd = openat(subvol_fd, ino_args.name, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY);
+                                child_fd = openat(subvol_fd, ino_args.name, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW);
                                 if (child_fd < 0)
                                         return -errno;
 
@@ -1635,15 +1613,15 @@ static int subvol_snapshot_children(int old_fd, int new_fd, const char *subvolum
                         if (!c)
                                 return -ENOMEM;
 
-                        old_child_fd = openat(old_fd, c, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY);
+                        old_child_fd = openat(old_fd, c, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW);
                         if (old_child_fd < 0)
                                 return -errno;
 
-                        np = strjoin(subvolume, "/", ino_args.name, NULL);
+                        np = strjoin(subvolume, "/", ino_args.name);
                         if (!np)
                                 return -ENOMEM;
 
-                        new_child_fd = openat(new_fd, np, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY);
+                        new_child_fd = openat(new_fd, np, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW);
                         if (new_child_fd < 0)
                                 return -errno;
 
@@ -1654,7 +1632,7 @@ static int subvol_snapshot_children(int old_fd, int new_fd, const char *subvolum
                                  * into place. */
 
                                 if (subvolume_fd < 0) {
-                                        subvolume_fd = openat(new_fd, subvolume, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY);
+                                        subvolume_fd = openat(new_fd, subvolume, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW);
                                         if (subvolume_fd < 0)
                                                 return -errno;
                                 }
@@ -1715,28 +1693,46 @@ int btrfs_subvol_snapshot_fd(int old_fd, const char *new_path, BtrfsSnapshotFlag
         if (r < 0)
                 return r;
         if (r == 0) {
+                bool plain_directory = false;
+
+                /* If the source isn't a proper subvolume, fail unless fallback is requested */
                 if (!(flags & BTRFS_SNAPSHOT_FALLBACK_COPY))
                         return -EISDIR;
 
                 r = btrfs_subvol_make(new_path);
-                if (r < 0)
-                        return r;
+                if (r == -ENOTTY && (flags & BTRFS_SNAPSHOT_FALLBACK_DIRECTORY)) {
+                        /* If the destination doesn't support subvolumes, then use a plain directory, if that's requested. */
+                        if (mkdir(new_path, 0755) < 0)
+                                return r;
 
-                r = copy_directory_fd(old_fd, new_path, true);
-                if (r < 0) {
-                        (void) btrfs_subvol_remove(new_path, BTRFS_REMOVE_QUOTA);
+                        plain_directory = true;
+                } else if (r < 0)
                         return r;
-                }
+
+                r = copy_directory_fd(old_fd, new_path, COPY_MERGE|COPY_REFLINK);
+                if (r < 0)
+                        goto fallback_fail;
 
                 if (flags & BTRFS_SNAPSHOT_READ_ONLY) {
-                        r = btrfs_subvol_set_read_only(new_path, true);
-                        if (r < 0) {
-                                (void) btrfs_subvol_remove(new_path, BTRFS_REMOVE_QUOTA);
-                                return r;
+
+                        if (plain_directory) {
+                                /* Plain directories have no recursive read-only flag, but something pretty close to
+                                 * it: the IMMUTABLE bit. Let's use this here, if this is requested. */
+
+                                if (flags & BTRFS_SNAPSHOT_FALLBACK_IMMUTABLE)
+                                        (void) chattr_path(new_path, FS_IMMUTABLE_FL, FS_IMMUTABLE_FL);
+                        } else {
+                                r = btrfs_subvol_set_read_only(new_path, true);
+                                if (r < 0)
+                                        goto fallback_fail;
                         }
                 }
 
                 return 0;
+
+        fallback_fail:
+                (void) rm_rf(new_path, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME);
+                return r;
         }
 
         r = extract_subvolume_name(new_path, &subvolume);
@@ -1845,8 +1841,7 @@ int btrfs_qgroup_find_parents(int fd, uint64_t qgroupid, uint64_t **ret) {
                 return 0;
         }
 
-        *ret = items;
-        items = NULL;
+        *ret = TAKE_PTR(items);
 
         return (int) n_items;
 }