Introduce cifs_copy_file_range()
authorSachin Prabhu <sprabhu@redhat.com>
Fri, 10 Feb 2017 10:33:51 +0000 (16:03 +0530)
committerSteve French <smfrench@gmail.com>
Fri, 7 Apr 2017 13:04:41 +0000 (08:04 -0500)
The earlier changes to copy range for cifs unintentionally disabled the more
common form of server side copy.

The patch introduces the file_operations helper cifs_copy_file_range()
which is used by the syscall copy_file_range. The new file operations
helper allows us to perform server side copies for SMB2.0 and 2.1
servers as well as SMB 3.0+ servers which do not support the ioctl
FSCTL_DUPLICATE_EXTENTS_TO_FILE.

The new helper uses the ioctl FSCTL_SRV_COPYCHUNK_WRITE to perform
server side copies. The helper is called by vfs_copy_file_range() only
once an attempt to clone the file using the ioctl
FSCTL_DUPLICATE_EXTENTS_TO_FILE has failed.

Signed-off-by: Sachin Prabhu <sprabhu@redhat.com>
Reviewed-by: Pavel Shilovsky <pshilov@microsoft.com>
CC: Stable <stable@vger.kernel.org>
Signed-off-by: Steve French <smfrench@gmail.com>
fs/cifs/cifsfs.c
fs/cifs/cifsfs.h
fs/cifs/cifsglob.h
fs/cifs/ioctl.c
fs/cifs/smb2ops.c

index 15e1db8738aecad0c8a86888c0fa1ada5f9b7623..dd3f5fabfdf6a35e25f9dae28020dcc9bba65007 100644 (file)
@@ -972,6 +972,86 @@ out:
        return rc;
 }
 
+ssize_t cifs_file_copychunk_range(unsigned int xid,
+                               struct file *src_file, loff_t off,
+                               struct file *dst_file, loff_t destoff,
+                               size_t len, unsigned int flags)
+{
+       struct inode *src_inode = file_inode(src_file);
+       struct inode *target_inode = file_inode(dst_file);
+       struct cifsFileInfo *smb_file_src;
+       struct cifsFileInfo *smb_file_target;
+       struct cifs_tcon *src_tcon;
+       struct cifs_tcon *target_tcon;
+       ssize_t rc;
+
+       cifs_dbg(FYI, "copychunk range\n");
+
+       if (src_inode == target_inode) {
+               rc = -EINVAL;
+               goto out;
+       }
+
+       if (!src_file->private_data || !dst_file->private_data) {
+               rc = -EBADF;
+               cifs_dbg(VFS, "missing cifsFileInfo on copy range src file\n");
+               goto out;
+       }
+
+       rc = -EXDEV;
+       smb_file_target = dst_file->private_data;
+       smb_file_src = src_file->private_data;
+       src_tcon = tlink_tcon(smb_file_src->tlink);
+       target_tcon = tlink_tcon(smb_file_target->tlink);
+
+       if (src_tcon->ses != target_tcon->ses) {
+               cifs_dbg(VFS, "source and target of copy not on same server\n");
+               goto out;
+       }
+
+       /*
+        * Note: cifs case is easier than btrfs since server responsible for
+        * checks for proper open modes and file type and if it wants
+        * server could even support copy of range where source = target
+        */
+       lock_two_nondirectories(target_inode, src_inode);
+
+       cifs_dbg(FYI, "about to flush pages\n");
+       /* should we flush first and last page first */
+       truncate_inode_pages(&target_inode->i_data, 0);
+
+       if (target_tcon->ses->server->ops->copychunk_range)
+               rc = target_tcon->ses->server->ops->copychunk_range(xid,
+                       smb_file_src, smb_file_target, off, len, destoff);
+       else
+               rc = -EOPNOTSUPP;
+
+       /* force revalidate of size and timestamps of target file now
+        * that target is updated on the server
+        */
+       CIFS_I(target_inode)->time = 0;
+       /* although unlocking in the reverse order from locking is not
+        * strictly necessary here it is a little cleaner to be consistent
+        */
+       unlock_two_nondirectories(src_inode, target_inode);
+
+out:
+       return rc;
+}
+
+static ssize_t cifs_copy_file_range(struct file *src_file, loff_t off,
+                               struct file *dst_file, loff_t destoff,
+                               size_t len, unsigned int flags)
+{
+       unsigned int xid = get_xid();
+       ssize_t rc;
+
+       rc = cifs_file_copychunk_range(xid, src_file, off, dst_file, destoff,
+                                       len, flags);
+       free_xid(xid);
+       return rc;
+}
+
 const struct file_operations cifs_file_ops = {
        .read_iter = cifs_loose_read_iter,
        .write_iter = cifs_file_write_iter,
@@ -984,6 +1064,7 @@ const struct file_operations cifs_file_ops = {
        .splice_read = generic_file_splice_read,
        .llseek = cifs_llseek,
        .unlocked_ioctl = cifs_ioctl,
+       .copy_file_range = cifs_copy_file_range,
        .clone_file_range = cifs_clone_file_range,
        .setlease = cifs_setlease,
        .fallocate = cifs_fallocate,
@@ -1001,6 +1082,7 @@ const struct file_operations cifs_file_strict_ops = {
        .splice_read = generic_file_splice_read,
        .llseek = cifs_llseek,
        .unlocked_ioctl = cifs_ioctl,
+       .copy_file_range = cifs_copy_file_range,
        .clone_file_range = cifs_clone_file_range,
        .setlease = cifs_setlease,
        .fallocate = cifs_fallocate,
@@ -1018,6 +1100,7 @@ const struct file_operations cifs_file_direct_ops = {
        .mmap = cifs_file_mmap,
        .splice_read = generic_file_splice_read,
        .unlocked_ioctl  = cifs_ioctl,
+       .copy_file_range = cifs_copy_file_range,
        .clone_file_range = cifs_clone_file_range,
        .llseek = cifs_llseek,
        .setlease = cifs_setlease,
@@ -1035,6 +1118,7 @@ const struct file_operations cifs_file_nobrl_ops = {
        .splice_read = generic_file_splice_read,
        .llseek = cifs_llseek,
        .unlocked_ioctl = cifs_ioctl,
+       .copy_file_range = cifs_copy_file_range,
        .clone_file_range = cifs_clone_file_range,
        .setlease = cifs_setlease,
        .fallocate = cifs_fallocate,
@@ -1051,6 +1135,7 @@ const struct file_operations cifs_file_strict_nobrl_ops = {
        .splice_read = generic_file_splice_read,
        .llseek = cifs_llseek,
        .unlocked_ioctl = cifs_ioctl,
+       .copy_file_range = cifs_copy_file_range,
        .clone_file_range = cifs_clone_file_range,
        .setlease = cifs_setlease,
        .fallocate = cifs_fallocate,
@@ -1067,6 +1152,7 @@ const struct file_operations cifs_file_direct_nobrl_ops = {
        .mmap = cifs_file_mmap,
        .splice_read = generic_file_splice_read,
        .unlocked_ioctl  = cifs_ioctl,
+       .copy_file_range = cifs_copy_file_range,
        .clone_file_range = cifs_clone_file_range,
        .llseek = cifs_llseek,
        .setlease = cifs_setlease,
@@ -1078,6 +1164,7 @@ const struct file_operations cifs_dir_ops = {
        .release = cifs_closedir,
        .read    = generic_read_dir,
        .unlocked_ioctl  = cifs_ioctl,
+       .copy_file_range = cifs_copy_file_range,
        .clone_file_range = cifs_clone_file_range,
        .llseek = generic_file_llseek,
 };
index da717fee30260be533f0992d5ed93114907f1c31..30bf89b1fd9a789ec02070f534d9d9f68b593650 100644 (file)
@@ -139,6 +139,11 @@ extern ssize_t     cifs_listxattr(struct dentry *, char *, size_t);
 # define cifs_listxattr NULL
 #endif
 
+extern ssize_t cifs_file_copychunk_range(unsigned int xid,
+                                       struct file *src_file, loff_t off,
+                                       struct file *dst_file, loff_t destoff,
+                                       size_t len, unsigned int flags);
+
 extern long cifs_ioctl(struct file *filep, unsigned int cmd, unsigned long arg);
 #ifdef CONFIG_CIFS_NFSD_EXPORT
 extern const struct export_operations cifs_export_ops;
index 57c594827cb3da83fae474c7660e9e21c19cb2ef..d07f13a63369df6a177a2236b9498cfefee3cb97 100644 (file)
@@ -408,10 +408,10 @@ struct smb_version_operations {
        char * (*create_lease_buf)(u8 *, u8);
        /* parse lease context buffer and return oplock/epoch info */
        __u8 (*parse_lease_buf)(void *, unsigned int *);
-       int (*copychunk_range)(const unsigned int,
+       ssize_t (*copychunk_range)(const unsigned int,
                        struct cifsFileInfo *src_file,
-                       struct cifsFileInfo *target_file, u64 src_off, u64 len,
-                       u64 dest_off);
+                       struct cifsFileInfo *target_file,
+                       u64 src_off, u64 len, u64 dest_off);
        int (*duplicate_extents)(const unsigned int, struct cifsFileInfo *src,
                        struct cifsFileInfo *target_file, u64 src_off, u64 len,
                        u64 dest_off);
index 9bf0f94fae63d2b7075f5643489c8d4d17006221..265c45fe4ea5e5246f5304480c2b4b66bf9d6c73 100644 (file)
 #include "cifs_ioctl.h"
 #include <linux/btrfs.h>
 
-static int cifs_file_copychunk_range(unsigned int xid, struct file *src_file,
-                         struct file *dst_file)
-{
-       struct inode *src_inode = file_inode(src_file);
-       struct inode *target_inode = file_inode(dst_file);
-       struct cifsFileInfo *smb_file_src;
-       struct cifsFileInfo *smb_file_target;
-       struct cifs_tcon *src_tcon;
-       struct cifs_tcon *target_tcon;
-       int rc;
-
-       cifs_dbg(FYI, "ioctl copychunk range\n");
-
-       if (!src_file->private_data || !dst_file->private_data) {
-               rc = -EBADF;
-               cifs_dbg(VFS, "missing cifsFileInfo on copy range src file\n");
-               goto out;
-       }
-
-       rc = -EXDEV;
-       smb_file_target = dst_file->private_data;
-       smb_file_src = src_file->private_data;
-       src_tcon = tlink_tcon(smb_file_src->tlink);
-       target_tcon = tlink_tcon(smb_file_target->tlink);
-
-       if (src_tcon->ses != target_tcon->ses) {
-               cifs_dbg(VFS, "source and target of copy not on same server\n");
-               goto out;
-       }
-
-       /*
-        * Note: cifs case is easier than btrfs since server responsible for
-        * checks for proper open modes and file type and if it wants
-        * server could even support copy of range where source = target
-        */
-       lock_two_nondirectories(target_inode, src_inode);
-
-       cifs_dbg(FYI, "about to flush pages\n");
-       /* should we flush first and last page first */
-       truncate_inode_pages(&target_inode->i_data, 0);
-
-       if (target_tcon->ses->server->ops->copychunk_range)
-               rc = target_tcon->ses->server->ops->copychunk_range(xid,
-                       smb_file_src, smb_file_target, 0, src_inode->i_size, 0);
-       else
-               rc = -EOPNOTSUPP;
-
-       /* force revalidate of size and timestamps of target file now
-          that target is updated on the server */
-       CIFS_I(target_inode)->time = 0;
-       /* although unlocking in the reverse order from locking is not
-          strictly necessary here it is a little cleaner to be consistent */
-       unlock_two_nondirectories(src_inode, target_inode);
-out:
-       return rc;
-}
-
 static long cifs_ioctl_copychunk(unsigned int xid, struct file *dst_file,
                        unsigned long srcfd)
 {
@@ -129,7 +72,8 @@ static long cifs_ioctl_copychunk(unsigned int xid, struct file *dst_file,
        if (S_ISDIR(src_inode->i_mode))
                goto out_fput;
 
-       rc = cifs_file_copychunk_range(xid, src_file.file, dst_file);
+       rc = cifs_file_copychunk_range(xid, src_file.file, 0, dst_file, 0,
+                                       src_inode->i_size, 0);
 
 out_fput:
        fdput(src_file);
index 3f12e0992b9bc0558763a976c8f59949f379db19..063e59d543f92e30a243a6df0ae0e0a508d05b72 100644 (file)
@@ -592,7 +592,7 @@ req_res_key_exit:
        return rc;
 }
 
-static int
+static ssize_t
 smb2_copychunk_range(const unsigned int xid,
                        struct cifsFileInfo *srcfile,
                        struct cifsFileInfo *trgtfile, u64 src_off,
@@ -605,6 +605,7 @@ smb2_copychunk_range(const unsigned int xid,
        struct cifs_tcon *tcon;
        int chunks_copied = 0;
        bool chunk_sizes_updated = false;
+       ssize_t bytes_written, total_bytes_written = 0;
 
        pcchunk = kmalloc(sizeof(struct copychunk_ioctl), GFP_KERNEL);
 
@@ -669,14 +670,16 @@ smb2_copychunk_range(const unsigned int xid,
                        }
                        chunks_copied++;
 
-                       src_off += le32_to_cpu(retbuf->TotalBytesWritten);
-                       dest_off += le32_to_cpu(retbuf->TotalBytesWritten);
-                       len -= le32_to_cpu(retbuf->TotalBytesWritten);
+                       bytes_written = le32_to_cpu(retbuf->TotalBytesWritten);
+                       src_off += bytes_written;
+                       dest_off += bytes_written;
+                       len -= bytes_written;
+                       total_bytes_written += bytes_written;
 
-                       cifs_dbg(FYI, "Chunks %d PartialChunk %d Total %d\n",
+                       cifs_dbg(FYI, "Chunks %d PartialChunk %d Total %zu\n",
                                le32_to_cpu(retbuf->ChunksWritten),
                                le32_to_cpu(retbuf->ChunkBytesWritten),
-                               le32_to_cpu(retbuf->TotalBytesWritten));
+                               bytes_written);
                } else if (rc == -EINVAL) {
                        if (ret_data_len != sizeof(struct copychunk_ioctl_rsp))
                                goto cchunk_out;
@@ -713,7 +716,10 @@ smb2_copychunk_range(const unsigned int xid,
 cchunk_out:
        kfree(pcchunk);
        kfree(retbuf);
-       return rc;
+       if (rc)
+               return rc;
+       else
+               return total_bytes_written;
 }
 
 static int