]> 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 8c90e1e4a216053fd7fdb94590fa28817d5d3c9d..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
@@ -900,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;
@@ -1193,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;
 
@@ -1273,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;
 
@@ -1435,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;
@@ -1618,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;
 
@@ -1637,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;
                                 }
@@ -1698,28 +1722,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);
@@ -1897,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;
@@ -2029,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;