chiark / gitweb /
importd: automatically grow /var/lib/machines/ loopback filesystem during downloads
[elogind.git] / src / shared / btrfs-util.c
index 254483c31a3ce5649fc0d722456c448a5461d450..256c5a6995ddf6f1796989ee771891464d2140d1 100644 (file)
 #include "util.h"
 #include "path-util.h"
 #include "macro.h"
-#include "strv.h"
 #include "copy.h"
 #include "selinux-util.h"
 #include "smack-util.h"
+#include "fileio.h"
 #include "btrfs-ctree.h"
 #include "btrfs-util.h"
 
@@ -228,14 +228,18 @@ int btrfs_subvol_remove(const char *path) {
         return 0;
 }
 
-int btrfs_subvol_set_read_only(const char *path, bool b) {
-        _cleanup_close_ int fd = -1;
+int btrfs_subvol_set_read_only_fd(int fd, bool b) {
         uint64_t flags, nflags;
+        struct stat st;
 
-        fd = open(path, O_RDONLY|O_NOCTTY|O_CLOEXEC);
-        if (fd < 0)
+        assert(fd >= 0);
+
+        if (fstat(fd, &st) < 0)
                 return -errno;
 
+        if (!S_ISDIR(st.st_mode) || st.st_ino != 256)
+                return -EINVAL;
+
         if (ioctl(fd, BTRFS_IOC_SUBVOL_GETFLAGS, &flags) < 0)
                 return -errno;
 
@@ -253,6 +257,16 @@ int btrfs_subvol_set_read_only(const char *path, bool b) {
         return 0;
 }
 
+int btrfs_subvol_set_read_only(const char *path, bool b) {
+        _cleanup_close_ int fd = -1;
+
+        fd = open(path, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY);
+        if (fd < 0)
+                return -errno;
+
+        return btrfs_subvol_set_read_only_fd(fd, b);
+}
+
 int btrfs_subvol_get_read_only_fd(int fd) {
         uint64_t flags;
 
@@ -295,18 +309,13 @@ int btrfs_clone_range(int infd, uint64_t in_offset, int outfd, uint64_t out_offs
         return 0;
 }
 
-int btrfs_get_block_device(const char *path, dev_t *dev) {
+int btrfs_get_block_device_fd(int fd, dev_t *dev) {
         struct btrfs_ioctl_fs_info_args fsi = {};
-        _cleanup_close_ int fd = -1;
         uint64_t id;
 
-        assert(path);
+        assert(fd >= 0);
         assert(dev);
 
-        fd = open(path, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY);
-        if (fd < 0)
-                return -errno;
-
         if (ioctl(fd, BTRFS_IOC_FS_INFO, &fsi) < 0)
                 return -errno;
 
@@ -343,6 +352,19 @@ int btrfs_get_block_device(const char *path, dev_t *dev) {
         return -ENODEV;
 }
 
+int btrfs_get_block_device(const char *path, dev_t *dev) {
+        _cleanup_close_ int fd = -1;
+
+        assert(path);
+        assert(dev);
+
+        fd = open(path, O_RDONLY|O_NOCTTY|O_CLOEXEC);
+        if (fd < 0)
+                return -errno;
+
+        return btrfs_get_block_device_fd(fd, dev);
+}
+
 int btrfs_subvol_get_id_fd(int fd, uint64_t *ret) {
         struct btrfs_ioctl_ino_lookup_args args = {
                 .objectid = BTRFS_FIRST_FREE_OBJECTID
@@ -633,3 +655,139 @@ int btrfs_defrag(const char *p) {
 
         return btrfs_defrag_fd(fd);
 }
+
+int btrfs_quota_enable_fd(int fd, bool b) {
+        struct btrfs_ioctl_quota_ctl_args args = {
+                .cmd = b ? BTRFS_QUOTA_CTL_ENABLE : BTRFS_QUOTA_CTL_DISABLE,
+        };
+
+        assert(fd >= 0);
+
+        if (ioctl(fd, BTRFS_IOC_QUOTA_CTL, &args) < 0)
+                return -errno;
+
+        return 0;
+}
+
+int btrfs_quota_enable(const char *path, bool b) {
+        _cleanup_close_ int fd = -1;
+
+        fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW);
+        if (fd < 0)
+                return -errno;
+
+        return btrfs_quota_enable_fd(fd, b);
+}
+
+int btrfs_quota_limit_fd(int fd, uint64_t referred_max) {
+        struct btrfs_ioctl_qgroup_limit_args args = {
+                .lim.max_rfer =
+                        referred_max == (uint64_t) -1 ? 0 :
+                        referred_max == 0 ? 1 : referred_max,
+                .lim.flags = BTRFS_QGROUP_LIMIT_MAX_RFER,
+        };
+
+        assert(fd >= 0);
+
+        if (ioctl(fd, BTRFS_IOC_QGROUP_LIMIT, &args) < 0)
+                return -errno;
+
+        return 0;
+}
+
+int btrfs_quota_limit(const char *path, uint64_t referred_max) {
+        _cleanup_close_ int fd = -1;
+
+        fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW);
+        if (fd < 0)
+                return -errno;
+
+        return btrfs_quota_limit_fd(fd, referred_max);
+}
+
+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;
+        _cleanup_close_ int loop_fd = -1, backing_fd = -1;
+        struct stat st;
+        dev_t dev;
+        int r;
+
+        /* btrfs cannot handle file systems < 16M, hence use this as minimum */
+        if (new_size < 16*1024*1024)
+                new_size = 16*1024*1024;
+
+        r = btrfs_get_block_device_fd(fd, &dev);
+        if (r < 0)
+                return r;
+        if (r == 0)
+                return -ENODEV;
+
+        if (asprintf(&p, "/sys/dev/block/%u:%u/loop/backing_file", major(dev), minor(dev)) < 0)
+                return -ENOMEM;
+        r = read_one_line_file(p, &backing);
+        if (r == -ENOENT)
+                return -ENODEV;
+        if (r < 0)
+                return r;
+        if (isempty(backing) || !path_is_absolute(backing))
+                return -ENODEV;
+
+        backing_fd = open(backing, O_RDWR|O_CLOEXEC|O_NOCTTY);
+        if (backing_fd < 0)
+                return -errno;
+
+        if (fstat(backing_fd, &st) < 0)
+                return -errno;
+        if (!S_ISREG(st.st_mode))
+                return -ENODEV;
+
+        if (new_size == (uint64_t) st.st_size)
+                return 0;
+
+        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);
+        if (loop_fd < 0)
+                return -errno;
+
+        if (snprintf(args.name, sizeof(args.name), "%" PRIu64, new_size) >= (int) sizeof(args.name))
+                return -EINVAL;
+
+        if (new_size < (uint64_t) st.st_size) {
+                /* Decrease size: first decrease btrfs size, then shorten loopback */
+                if (ioctl(fd, BTRFS_IOC_RESIZE, &args) < 0)
+                        return -errno;
+        }
+
+        if (ftruncate(backing_fd, new_size) < 0)
+                return -errno;
+
+        if (ioctl(loop_fd, LOOP_SET_CAPACITY, 0) < 0)
+                return -errno;
+
+        if (new_size > (uint64_t) st.st_size) {
+                /* Increase size: first enlarge loopback, then increase btrfs size */
+                if (ioctl(fd, BTRFS_IOC_RESIZE, &args) < 0)
+                        return -errno;
+        }
+
+        /* Make sure the free disk space is correctly updated for both file systems */
+        (void) fsync(fd);
+        (void) fsync(backing_fd);
+
+        return 1;
+}
+
+int btrfs_resize_loopback(const char *p, uint64_t new_size, bool grow_only) {
+        _cleanup_close_ int fd = -1;
+
+        fd = open(p, O_RDONLY|O_NOCTTY|O_CLOEXEC);
+        if (fd < 0)
+                return -errno;
+
+        return btrfs_resize_loopback_fd(fd, new_size, grow_only);
+}