|
@@ -16,6 +16,7 @@
|
|
|
#include <linux/pagemap.h>
|
|
|
#include <linux/splice.h>
|
|
|
#include <linux/compat.h>
|
|
|
+#include <linux/mount.h>
|
|
|
#include "internal.h"
|
|
|
|
|
|
#include <asm/uaccess.h>
|
|
@@ -1327,3 +1328,122 @@ COMPAT_SYSCALL_DEFINE4(sendfile64, int, out_fd, int, in_fd,
|
|
|
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;
|
|
|
+}
|