]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/basic/btrfs-util.c
Add SPDX license identifiers to source files under the LGPL
[thirdparty/systemd.git] / src / basic / btrfs-util.c
index 4c90bc0c80bf3c2d04b833435575e575447c478e..b7e237fc0dfbda75d93e57f88d632f0a30aded38 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.
 
   along with systemd; If not, see <http://www.gnu.org/licenses/>.
 ***/
 
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <linux/fs.h>
+#include <linux/loop.h>
+#include <stddef.h>
+#include <stdio.h>
 #include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
 #include <sys/stat.h>
-#include <sys/vfs.h>
-#ifdef HAVE_LINUX_BTRFS_H
+#include <sys/statfs.h>
+#include <sys/sysmacros.h>
+#include <unistd.h>
+
+#if HAVE_LINUX_BTRFS_H
 #include <linux/btrfs.h>
 #endif
 
 #include "alloc-util.h"
 #include "btrfs-ctree.h"
 #include "btrfs-util.h"
+#include "chattr-util.h"
 #include "copy.h"
 #include "fd-util.h"
 #include "fileio.h"
+#include "io-util.h"
 #include "macro.h"
 #include "missing.h"
 #include "path-util.h"
+#include "rm-rf.h"
 #include "selinux-util.h"
 #include "smack-util.h"
+#include "sparse-endian.h"
 #include "stat-util.h"
 #include "string-util.h"
+#include "time-util.h"
 #include "util.h"
 
 /* WARNING: Be careful with file system ioctls! When we get an fd, we
@@ -105,7 +121,7 @@ int btrfs_is_filesystem(int fd) {
         return F_TYPE_EQUAL(sfs.f_type, BTRFS_SUPER_MAGIC);
 }
 
-int btrfs_is_subvol(int fd) {
+int btrfs_is_subvol_fd(int fd) {
         struct stat st;
 
         assert(fd >= 0);
@@ -121,6 +137,18 @@ int btrfs_is_subvol(int fd) {
         return btrfs_is_filesystem(fd);
 }
 
+int btrfs_is_subvol(const char *path) {
+        _cleanup_close_ int fd = -1;
+
+        assert(path);
+
+        fd = open(path, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY);
+        if (fd < 0)
+                return -errno;
+
+        return btrfs_is_subvol_fd(fd);
+}
+
 int btrfs_subvol_make(const char *path) {
         struct btrfs_ioctl_vol_args args = {};
         _cleanup_close_ int fd = -1;
@@ -575,8 +603,12 @@ int btrfs_qgroup_get_quota_fd(int fd, uint64_t qgroupid, BtrfsQuotaInfo *ret) {
                 unsigned i;
 
                 args.key.nr_items = 256;
-                if (ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args) < 0)
+                if (ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args) < 0) {
+                        if (errno == ENOENT) /* quota tree is missing: quota disabled */
+                                break;
+
                         return -errno;
+                }
 
                 if (args.key.nr_items <= 0)
                         break;
@@ -884,6 +916,10 @@ int btrfs_resize_loopback_fd(int fd, uint64_t new_size, bool grow_only) {
         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;
@@ -1006,6 +1042,10 @@ static int qgroup_create_or_destroy(int fd, bool b, uint64_t qgroupid) {
         for (c = 0;; c++) {
                 if (ioctl(fd, BTRFS_IOC_QGROUP_CREATE, &args) < 0) {
 
+                        /* If quota is not enabled, we get EINVAL. Turn this into a recognizable error */
+                        if (errno == EINVAL)
+                                return -ENOPROTOOPT;
+
                         if (errno == EBUSY && c < 10) {
                                 (void) btrfs_quota_scan_wait(fd);
                                 continue;
@@ -1173,7 +1213,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;
 
@@ -1253,7 +1293,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;
 
@@ -1335,8 +1375,12 @@ int btrfs_qgroup_copy_limits(int fd, uint64_t old_qgroupid, uint64_t new_qgroupi
                 unsigned i;
 
                 args.key.nr_items = 256;
-                if (ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args) < 0)
+                if (ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args) < 0) {
+                        if (errno == ENOENT) /* quota tree missing: quota is not enabled, hence nothing to copy */
+                                break;
+
                         return -errno;
+                }
 
                 if (args.key.nr_items <= 0)
                         break;
@@ -1411,12 +1455,16 @@ static int copy_quota_hierarchy(int fd, uint64_t old_subvol_id, uint64_t new_sub
                 return n_old_qgroups;
 
         r = btrfs_subvol_get_parent(fd, old_subvol_id, &old_parent_id);
-        if (r < 0)
+        if (r == -ENXIO)
+                /* We have no parent, hence nothing to copy. */
+                n_old_parent_qgroups = 0;
+        else if (r < 0)
                 return r;
-
-        n_old_parent_qgroups = btrfs_qgroup_find_parents(fd, old_parent_id, &old_parent_qgroups);
-        if (n_old_parent_qgroups < 0)
-                return n_old_parent_qgroups;
+        else {
+                n_old_parent_qgroups = btrfs_qgroup_find_parents(fd, old_parent_id, &old_parent_qgroups);
+                if (n_old_parent_qgroups < 0)
+                        return n_old_parent_qgroups;
+        }
 
         for (i = 0; i < n_old_qgroups; i++) {
                 uint64_t id;
@@ -1594,15 +1642,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;
 
@@ -1613,7 +1661,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;
                                 }
@@ -1670,32 +1718,50 @@ int btrfs_subvol_snapshot_fd(int old_fd, const char *new_path, BtrfsSnapshotFlag
         assert(old_fd >= 0);
         assert(new_path);
 
-        r = btrfs_is_subvol(old_fd);
+        r = btrfs_is_subvol_fd(old_fd);
         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);
@@ -1766,8 +1832,12 @@ int btrfs_qgroup_find_parents(int fd, uint64_t qgroupid, uint64_t **ret) {
                 unsigned i;
 
                 args.key.nr_items = 256;
-                if (ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args) < 0)
+                if (ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args) < 0) {
+                        if (errno == ENOENT) /* quota tree missing: quota is disabled */
+                                break;
+
                         return -errno;
+                }
 
                 if (args.key.nr_items <= 0)
                         break;
@@ -1852,7 +1922,7 @@ int btrfs_subvol_auto_qgroup_fd(int fd, uint64_t subvol_id, bool insert_intermed
          */
 
         if (subvol_id == 0) {
-                r = btrfs_is_subvol(fd);
+                r = btrfs_is_subvol_fd(fd);
                 if (r < 0)
                         return r;
                 if (!r)
@@ -1869,14 +1939,19 @@ int btrfs_subvol_auto_qgroup_fd(int fd, uint64_t subvol_id, bool insert_intermed
         if (n > 0) /* already parent qgroups set up, let's bail */
                 return 0;
 
+        qgroups = mfree(qgroups);
+
         r = btrfs_subvol_get_parent(fd, subvol_id, &parent_subvol);
-        if (r < 0)
+        if (r == -ENXIO)
+                /* No parent, hence no qgroup memberships */
+                n = 0;
+        else if (r < 0)
                 return r;
-
-        qgroups = mfree(qgroups);
-        n = btrfs_qgroup_find_parents(fd, parent_subvol, &qgroups);
-        if (n < 0)
-                return n;
+        else {
+                n = btrfs_qgroup_find_parents(fd, parent_subvol, &qgroups);
+                if (n < 0)
+                        return n;
+        }
 
         if (insert_intermediary_qgroup) {
                 uint64_t lowest = 256, new_qgroupid;
@@ -2001,7 +2076,7 @@ int btrfs_subvol_get_parent(int fd, uint64_t subvol_id, uint64_t *ret) {
 
                 args.key.nr_items = 256;
                 if (ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args) < 0)
-                        return -errno;
+                        return negative_errno();
 
                 if (args.key.nr_items <= 0)
                         break;