#include <linux/pagemap.h>
 #include <linux/splice.h>
 #include <linux/compat.h>
+#include <linux/mount.h>
 #include "internal.h"
 
 #include <asm/uaccess.h>
        return do_sendfile(out_fd, in_fd, NULL, count, 0);
 }
 #endif
+
+/*
+ * copy_file_range() differs from regular file read and write in that it
+ * specifically allows return partial success.  When it does so is up to
+ * the copy_file_range method.
+ */
+ssize_t vfs_copy_file_range(struct file *file_in, loff_t pos_in,
+                           struct file *file_out, loff_t pos_out,
+                           size_t len, unsigned int flags)
+{
+       struct inode *inode_in = file_inode(file_in);
+       struct inode *inode_out = file_inode(file_out);
+       ssize_t ret;
+
+       if (flags != 0)
+               return -EINVAL;
+
+       /* copy_file_range allows full ssize_t len, ignoring MAX_RW_COUNT  */
+       ret = rw_verify_area(READ, file_in, &pos_in, len);
+       if (ret >= 0)
+               ret = rw_verify_area(WRITE, file_out, &pos_out, len);
+       if (ret < 0)
+               return ret;
+
+       if (!(file_in->f_mode & FMODE_READ) ||
+           !(file_out->f_mode & FMODE_WRITE) ||
+           (file_out->f_flags & O_APPEND) ||
+           !file_out->f_op->copy_file_range)
+               return -EBADF;
+
+       /* this could be relaxed once a method supports cross-fs copies */
+       if (inode_in->i_sb != inode_out->i_sb)
+               return -EXDEV;
+
+       if (len == 0)
+               return 0;
+
+       ret = mnt_want_write_file(file_out);
+       if (ret)
+               return ret;
+
+       ret = file_out->f_op->copy_file_range(file_in, pos_in, file_out, pos_out,
+                                             len, flags);
+       if (ret > 0) {
+               fsnotify_access(file_in);
+               add_rchar(current, ret);
+               fsnotify_modify(file_out);
+               add_wchar(current, ret);
+       }
+       inc_syscr(current);
+       inc_syscw(current);
+
+       mnt_drop_write_file(file_out);
+
+       return ret;
+}
+EXPORT_SYMBOL(vfs_copy_file_range);
+
+SYSCALL_DEFINE6(copy_file_range, int, fd_in, loff_t __user *, off_in,
+               int, fd_out, loff_t __user *, off_out,
+               size_t, len, unsigned int, flags)
+{
+       loff_t pos_in;
+       loff_t pos_out;
+       struct fd f_in;
+       struct fd f_out;
+       ssize_t ret = -EBADF;
+
+       f_in = fdget(fd_in);
+       if (!f_in.file)
+               goto out2;
+
+       f_out = fdget(fd_out);
+       if (!f_out.file)
+               goto out1;
+
+       ret = -EFAULT;
+       if (off_in) {
+               if (copy_from_user(&pos_in, off_in, sizeof(loff_t)))
+                       goto out;
+       } else {
+               pos_in = f_in.file->f_pos;
+       }
+
+       if (off_out) {
+               if (copy_from_user(&pos_out, off_out, sizeof(loff_t)))
+                       goto out;
+       } else {
+               pos_out = f_out.file->f_pos;
+       }
+
+       ret = vfs_copy_file_range(f_in.file, pos_in, f_out.file, pos_out, len,
+                                 flags);
+       if (ret > 0) {
+               pos_in += ret;
+               pos_out += ret;
+
+               if (off_in) {
+                       if (copy_to_user(off_in, &pos_in, sizeof(loff_t)))
+                               ret = -EFAULT;
+               } else {
+                       f_in.file->f_pos = pos_in;
+               }
+
+               if (off_out) {
+                       if (copy_to_user(off_out, &pos_out, sizeof(loff_t)))
+                               ret = -EFAULT;
+               } else {
+                       f_out.file->f_pos = pos_out;
+               }
+       }
+
+out:
+       fdput(f_out);
+out1:
+       fdput(f_in);
+out2:
+       return ret;
+}
 
 #ifndef CONFIG_MMU
        unsigned (*mmap_capabilities)(struct file *);
 #endif
+       ssize_t (*copy_file_range)(struct file *, loff_t, struct file *, loff_t, size_t, unsigned int);
 };
 
 struct inode_operations {
                unsigned long, loff_t *);
 extern ssize_t vfs_writev(struct file *, const struct iovec __user *,
                unsigned long, loff_t *);
+extern ssize_t vfs_copy_file_range(struct file *, loff_t , struct file *,
+                                  loff_t, size_t, unsigned int);
 
 struct super_operations {
        struct inode *(*alloc_inode)(struct super_block *sb);