mm/tlb: Remove tlb_remove_table() non-concurrent condition
[linux-2.6-microblaze.git] / fs / read_write.c
index 153f8f6..39b4a21 100644 (file)
@@ -1964,6 +1964,44 @@ out_error:
 }
 EXPORT_SYMBOL(vfs_dedupe_file_range_compare);
 
+int vfs_dedupe_file_range_one(struct file *src_file, loff_t src_pos,
+                             struct file *dst_file, loff_t dst_pos, u64 len)
+{
+       s64 ret;
+
+       ret = mnt_want_write_file(dst_file);
+       if (ret)
+               return ret;
+
+       ret = clone_verify_area(dst_file, dst_pos, len, true);
+       if (ret < 0)
+               goto out_drop_write;
+
+       ret = -EINVAL;
+       if (!(capable(CAP_SYS_ADMIN) || (dst_file->f_mode & FMODE_WRITE)))
+               goto out_drop_write;
+
+       ret = -EXDEV;
+       if (src_file->f_path.mnt != dst_file->f_path.mnt)
+               goto out_drop_write;
+
+       ret = -EISDIR;
+       if (S_ISDIR(file_inode(dst_file)->i_mode))
+               goto out_drop_write;
+
+       ret = -EINVAL;
+       if (!dst_file->f_op->dedupe_file_range)
+               goto out_drop_write;
+
+       ret = dst_file->f_op->dedupe_file_range(src_file, src_pos,
+                                               dst_file, dst_pos, len);
+out_drop_write:
+       mnt_drop_write_file(dst_file);
+
+       return ret;
+}
+EXPORT_SYMBOL(vfs_dedupe_file_range_one);
+
 int vfs_dedupe_file_range(struct file *file, struct file_dedupe_range *same)
 {
        struct file_dedupe_range_info *info;
@@ -1972,11 +2010,8 @@ int vfs_dedupe_file_range(struct file *file, struct file_dedupe_range *same)
        u64 len;
        int i;
        int ret;
-       bool is_admin = capable(CAP_SYS_ADMIN);
        u16 count = same->dest_count;
-       struct file *dst_file;
-       loff_t dst_off;
-       ssize_t deduped;
+       int deduped;
 
        if (!(file->f_mode & FMODE_READ))
                return -EINVAL;
@@ -2003,6 +2038,9 @@ int vfs_dedupe_file_range(struct file *file, struct file_dedupe_range *same)
        if (off + len > i_size_read(src))
                return -EINVAL;
 
+       /* Arbitrary 1G limit on a single dedupe request, can be raised. */
+       len = min_t(u64, len, 1 << 30);
+
        /* pre-format output fields to sane values */
        for (i = 0; i < count; i++) {
                same->info[i].bytes_deduped = 0ULL;
@@ -2010,54 +2048,28 @@ int vfs_dedupe_file_range(struct file *file, struct file_dedupe_range *same)
        }
 
        for (i = 0, info = same->info; i < count; i++, info++) {
-               struct inode *dst;
                struct fd dst_fd = fdget(info->dest_fd);
+               struct file *dst_file = dst_fd.file;
 
-               dst_file = dst_fd.file;
                if (!dst_file) {
                        info->status = -EBADF;
                        goto next_loop;
                }
-               dst = file_inode(dst_file);
-
-               ret = mnt_want_write_file(dst_file);
-               if (ret) {
-                       info->status = ret;
-                       goto next_fdput;
-               }
-
-               dst_off = info->dest_offset;
-               ret = clone_verify_area(dst_file, dst_off, len, true);
-               if (ret < 0) {
-                       info->status = ret;
-                       goto next_file;
-               }
-               ret = 0;
 
                if (info->reserved) {
                        info->status = -EINVAL;
-               } else if (!(is_admin || (dst_file->f_mode & FMODE_WRITE))) {
-                       info->status = -EINVAL;
-               } else if (file->f_path.mnt != dst_file->f_path.mnt) {
-                       info->status = -EXDEV;
-               } else if (S_ISDIR(dst->i_mode)) {
-                       info->status = -EISDIR;
-               } else if (dst_file->f_op->dedupe_file_range == NULL) {
-                       info->status = -EINVAL;
-               } else {
-                       deduped = dst_file->f_op->dedupe_file_range(file, off,
-                                                       len, dst_file,
-                                                       info->dest_offset);
-                       if (deduped == -EBADE)
-                               info->status = FILE_DEDUPE_RANGE_DIFFERS;
-                       else if (deduped < 0)
-                               info->status = deduped;
-                       else
-                               info->bytes_deduped += deduped;
+                       goto next_fdput;
                }
 
-next_file:
-               mnt_drop_write_file(dst_file);
+               deduped = vfs_dedupe_file_range_one(file, off, dst_file,
+                                                   info->dest_offset, len);
+               if (deduped == -EBADE)
+                       info->status = FILE_DEDUPE_RANGE_DIFFERS;
+               else if (deduped < 0)
+                       info->status = deduped;
+               else
+                       info->bytes_deduped = len;
+
 next_fdput:
                fdput(dst_fd);
 next_loop: