]> git.ipfire.org Git - people/arne_f/kernel.git/blobdiff - fs/btrfs/ioctl.c
Btrfs: do not allow trimming when a fs is mounted with the nologreplay option
[people/arne_f/kernel.git] / fs / btrfs / ioctl.c
index 7acbd2cf6192ee8d967236f4b3aeecf1bfc98658..a67143c579aa3f8b5eb3dd9630f1610fba29dfb4 100644 (file)
@@ -380,12 +380,21 @@ static noinline int btrfs_ioctl_fitrim(struct file *file, void __user *arg)
        struct fstrim_range range;
        u64 minlen = ULLONG_MAX;
        u64 num_devices = 0;
-       u64 total_bytes = btrfs_super_total_bytes(fs_info->super_copy);
        int ret;
 
        if (!capable(CAP_SYS_ADMIN))
                return -EPERM;
 
+       /*
+        * If the fs is mounted with nologreplay, which requires it to be
+        * mounted in RO mode as well, we can not allow discard on free space
+        * inside block groups, because log trees refer to extents that are not
+        * pinned in a block group's free space cache (pinning the extents is
+        * precisely the first phase of replaying a log tree).
+        */
+       if (btrfs_test_opt(fs_info, NOLOGREPLAY))
+               return -EROFS;
+
        rcu_read_lock();
        list_for_each_entry_rcu(device, &fs_info->fs_devices->devices,
                                dev_list) {
@@ -404,11 +413,15 @@ static noinline int btrfs_ioctl_fitrim(struct file *file, void __user *arg)
                return -EOPNOTSUPP;
        if (copy_from_user(&range, arg, sizeof(range)))
                return -EFAULT;
-       if (range.start > total_bytes ||
-           range.len < fs_info->sb->s_blocksize)
+
+       /*
+        * NOTE: Don't truncate the range using super->total_bytes.  Bytenr of
+        * block group is in the logical address space, which can be any
+        * sectorsize aligned bytenr in  the range [0, U64_MAX].
+        */
+       if (range.len < fs_info->sb->s_blocksize)
                return -EINVAL;
 
-       range.len = min(range.len, total_bytes - range.start);
        range.minlen = max(range.minlen, minlen);
        ret = btrfs_trim_fs(fs_info->tree_root, &range);
        if (ret < 0)
@@ -2244,7 +2257,7 @@ static noinline int btrfs_search_path_in_tree(struct btrfs_fs_info *info,
        if (!path)
                return -ENOMEM;
 
-       ptr = &name[BTRFS_INO_LOOKUP_PATH_MAX];
+       ptr = &name[BTRFS_INO_LOOKUP_PATH_MAX - 1];
 
        key.objectid = tree_id;
        key.type = BTRFS_ROOT_ITEM_KEY;
@@ -2708,8 +2721,10 @@ static long btrfs_ioctl_rm_dev_v2(struct file *file, void __user *arg)
        }
 
        /* Check for compatibility reject unknown flags */
-       if (vol_args->flags & ~BTRFS_VOL_ARG_V2_FLAGS_SUPPORTED)
-               return -EOPNOTSUPP;
+       if (vol_args->flags & ~BTRFS_VOL_ARG_V2_FLAGS_SUPPORTED) {
+               ret = -EOPNOTSUPP;
+               goto out;
+       }
 
        if (atomic_xchg(&root->fs_info->mutually_exclusive_operation_running,
                        1)) {
@@ -3052,7 +3067,7 @@ static int btrfs_cmp_data_prepare(struct inode *src, u64 loff,
 out:
        if (ret)
                btrfs_cmp_data_free(cmp);
-       return 0;
+       return ret;
 }
 
 static int btrfs_cmp_data(struct inode *src, u64 loff, struct inode *dst,
@@ -3887,11 +3902,6 @@ static noinline int btrfs_clone_files(struct file *file, struct file *file_src,
            src->i_sb != inode->i_sb)
                return -EXDEV;
 
-       /* don't make the dst file partly checksummed */
-       if ((BTRFS_I(src)->flags & BTRFS_INODE_NODATASUM) !=
-           (BTRFS_I(inode)->flags & BTRFS_INODE_NODATASUM))
-               return -EINVAL;
-
        if (S_ISDIR(src->i_mode) || S_ISDIR(inode->i_mode))
                return -EISDIR;
 
@@ -3901,15 +3911,30 @@ static noinline int btrfs_clone_files(struct file *file, struct file *file_src,
                inode_lock(src);
        }
 
+       /* don't make the dst file partly checksummed */
+       if ((BTRFS_I(src)->flags & BTRFS_INODE_NODATASUM) !=
+           (BTRFS_I(inode)->flags & BTRFS_INODE_NODATASUM)) {
+               ret = -EINVAL;
+               goto out_unlock;
+       }
+
        /* determine range to clone */
        ret = -EINVAL;
        if (off + len > src->i_size || off + len < off)
                goto out_unlock;
        if (len == 0)
                olen = len = src->i_size - off;
-       /* if we extend to eof, continue to block boundary */
-       if (off + len == src->i_size)
+       /*
+        * If we extend to eof, continue to block boundary if and only if the
+        * destination end offset matches the destination file's size, otherwise
+        * we would be corrupting data by placing the eof block into the middle
+        * of a file.
+        */
+       if (off + len == src->i_size) {
+               if (!IS_ALIGNED(len, bs) && destoff + len < inode->i_size)
+                       goto out_unlock;
                len = ALIGN(src->i_size, bs) - off;
+       }
 
        if (len == 0) {
                ret = 0;
@@ -4082,6 +4107,10 @@ static long btrfs_ioctl_default_subvol(struct file *file, void __user *argp)
                ret = PTR_ERR(new_root);
                goto out;
        }
+       if (!is_fstree(new_root->objectid)) {
+               ret = -ENOENT;
+               goto out;
+       }
 
        path = btrfs_alloc_path();
        if (!path) {
@@ -5648,6 +5677,10 @@ long btrfs_ioctl(struct file *file, unsigned int
 #ifdef CONFIG_COMPAT
 long btrfs_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
 {
+       /*
+        * These all access 32-bit values anyway so no further
+        * handling is necessary.
+        */
        switch (cmd) {
        case FS_IOC32_GETFLAGS:
                cmd = FS_IOC_GETFLAGS;
@@ -5658,8 +5691,6 @@ long btrfs_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
        case FS_IOC32_GETVERSION:
                cmd = FS_IOC_GETVERSION;
                break;
-       default:
-               return -ENOIOCTLCMD;
        }
 
        return btrfs_ioctl(file, cmd, (unsigned long) compat_ptr(arg));