Merge tag 'for-4.20-rc1-tag' of git://git.kernel.org/pub/scm/linux/kernel/git/kdave...
[linux-block.git] / fs / btrfs / ioctl.c
index a990a904513929d8e735ba00d3f120dbed975f6c..802a628e9f7d7fe629a76e8d108b75c04ed4246e 100644 (file)
@@ -3488,6 +3488,8 @@ static int btrfs_extent_same_range(struct inode *src, u64 loff, u64 olen,
                        const u64 sz = BTRFS_I(src)->root->fs_info->sectorsize;
 
                        len = round_down(i_size_read(src), sz) - loff;
+                       if (len == 0)
+                               return 0;
                        olen = len;
                }
        }
@@ -3629,26 +3631,6 @@ out_unlock:
        return ret;
 }
 
-int btrfs_dedupe_file_range(struct file *src_file, loff_t src_loff,
-                           struct file *dst_file, loff_t dst_loff,
-                           u64 olen)
-{
-       struct inode *src = file_inode(src_file);
-       struct inode *dst = file_inode(dst_file);
-       u64 bs = BTRFS_I(src)->root->fs_info->sb->s_blocksize;
-
-       if (WARN_ON_ONCE(bs < PAGE_SIZE)) {
-               /*
-                * Btrfs does not support blocksize < page_size. As a
-                * result, btrfs_cmp_data() won't correctly handle
-                * this situation without an update.
-                */
-               return -EINVAL;
-       }
-
-       return btrfs_extent_same(src, src_loff, olen, dst, dst_loff);
-}
-
 static int clone_finish_inode_update(struct btrfs_trans_handle *trans,
                                     struct inode *inode,
                                     u64 endoff,
@@ -4277,9 +4259,17 @@ static noinline int btrfs_clone_files(struct file *file, struct file *file_src,
                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;
@@ -4350,10 +4340,34 @@ out_unlock:
        return ret;
 }
 
-int btrfs_clone_file_range(struct file *src_file, loff_t off,
-               struct file *dst_file, loff_t destoff, u64 len)
+loff_t btrfs_remap_file_range(struct file *src_file, loff_t off,
+               struct file *dst_file, loff_t destoff, loff_t len,
+               unsigned int remap_flags)
 {
-       return btrfs_clone_files(dst_file, src_file, off, len, destoff);
+       int ret;
+
+       if (remap_flags & ~(REMAP_FILE_DEDUP | REMAP_FILE_ADVISORY))
+               return -EINVAL;
+
+       if (remap_flags & REMAP_FILE_DEDUP) {
+               struct inode *src = file_inode(src_file);
+               struct inode *dst = file_inode(dst_file);
+               u64 bs = BTRFS_I(src)->root->fs_info->sb->s_blocksize;
+
+               if (WARN_ON_ONCE(bs < PAGE_SIZE)) {
+                       /*
+                        * Btrfs does not support blocksize < page_size. As a
+                        * result, btrfs_cmp_data() won't correctly handle
+                        * this situation without an update.
+                        */
+                       return -EINVAL;
+               }
+
+               ret = btrfs_extent_same(src, off, len, dst, destoff);
+       } else {
+               ret = btrfs_clone_files(dst_file, src_file, off, len, destoff);
+       }
+       return ret < 0 ? ret : len;
 }
 
 static long btrfs_ioctl_default_subvol(struct file *file, void __user *argp)