Re: [PATCH] Introduce cifs_copy_file_range()

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



2017-02-07 3:33 GMT-08:00 Sachin Prabhu <sprabhu@xxxxxxxxxx>:
> The patch introduces the file_operations helper cifs_copy_file_range().
>
> The vfs helper vfs_copy_file_range() first calls clone_file_range
> which for cifs uses
> SMB2_ioctl(.., FSCTL_DUPLICATE_EXTENTS_TO_FILE, ..)
> to do a server side copy of the file. This ioctl is only supported on
> SMB3 and later versions.
>
> Once this call fails, vfs_copy_file_range() calls copy_file_range()
> corresponding to the cifs filesystem which is introduced by this patch.
> This calls the more widely available
> SMB2_ioctl(.., FSCTL_SRV_COPYCHUNK_WRITE, ..)
>
> The upstream changes in this area was first made by the commit
> 04b38d601239 ("vfs: pull btrfs clone API to vfs layer")
> This commit also introduces the ioctls FICLONE and FICLONERANGE which
> removes the need for separate cifs ioctls to perform server side copies
> and hence can be removed.
>

ioctls FICLONE and FICLONERANGE perform a file clone operation which
for cifs is implemented through FSCTL_DUPLICATE_EXTENTS_TO_FILE for
SMB3 only. ioctl CIFS_IOC_COPYCHUNK_FILE performs a file copy
operation which is implemented through FSCTL_SRV_COPYCHUNK_WRITE. So,
this generic ioctls can't substitute the existing cifs one.

Also there is a copy_file_range() system call that calls
vfs_copy_file_range(). With this patch applied it tries to clone first
and then copy. It means that for SMB3 it will still call
DUPLICATE_EXTENTS_TO_FILE and we end up with two calls issuing
DUPLICATE_EXTENTS_TO_FILE and zero -SRV_COPYCHUNK_WRITE.

Thus I suggest not to remove a separate cifs ioctl that always calls
COPYCHUNK_WRITE for possible scenarios where DUPLICATE_EXTENTS_TO_FILE
is not suitable.

> Signed-off-by: Sachin Prabhu <sprabhu@xxxxxxxxxx>
> ---
>  fs/cifs/cifsfs.c   |  72 +++++++++++++++++++++++++++++++++++
>  fs/cifs/cifsglob.h |   2 +-
>  fs/cifs/ioctl.c    | 107 -----------------------------------------------------
>  fs/cifs/smb2ops.c  |  16 +++++---
>  4 files changed, 84 insertions(+), 113 deletions(-)
>
> diff --git a/fs/cifs/cifsfs.c b/fs/cifs/cifsfs.c
> index 70f4e65..33bc53d 100644
> --- a/fs/cifs/cifsfs.c
> +++ b/fs/cifs/cifsfs.c
> @@ -972,6 +972,72 @@ static int cifs_clone_file_range(struct file *src_file, loff_t off,
>         return rc;
>  }
>
> +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)
> +{
> +       struct inode *src_inode = file_inode(src_file);
> +       struct inode *target_inode = file_inode(dst_file);
> +       struct cifsFileInfo *smb_file_src = src_file->private_data;
> +       struct cifsFileInfo *smb_file_target = dst_file->private_data;
> +       struct cifs_tcon *src_tcon;
> +       struct cifs_tcon *target_tcon;
> +       unsigned int xid;
> +       int rc;
> +
> +       xid = get_xid();
> +
> +       cifs_dbg(FYI, "copy file 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;
> +       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->clone_range)
> +               rc = target_tcon->ses->server->ops->clone_range(xid,
> +                       smb_file_src, smb_file_target, off, len, destoff);

I think there is a naming confusion here. I suggest to rename
clone_range() callback to copy_range() to reflect its actual logic.

> +       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:
> +       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 +1050,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 +1068,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 +1086,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 +1104,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 +1121,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 +1138,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,
> diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h
> index 7ea8a33..ae0c095 100644
> --- a/fs/cifs/cifsglob.h
> +++ b/fs/cifs/cifsglob.h
> @@ -405,7 +405,7 @@ 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 (*clone_range)(const unsigned int, struct cifsFileInfo *src_file,
> +       ssize_t (*clone_range)(const unsigned int, struct cifsFileInfo *src_file,
>                         struct cifsFileInfo *target_file, u64 src_off, u64 len,
>                         u64 dest_off);
>         int (*duplicate_extents)(const unsigned int, struct cifsFileInfo *src,
> diff --git a/fs/cifs/ioctl.c b/fs/cifs/ioctl.c
> index 0015287..14975b7 100644
> --- a/fs/cifs/ioctl.c
> +++ b/fs/cifs/ioctl.c
> @@ -34,110 +34,6 @@
>  #include "cifs_ioctl.h"
>  #include <linux/btrfs.h>
>
> -static int cifs_file_clone_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 clone 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->clone_range)
> -               rc = target_tcon->ses->server->ops->clone_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_clone(unsigned int xid, struct file *dst_file,
> -                       unsigned long srcfd)
> -{
> -       int rc;
> -       struct fd src_file;
> -       struct inode *src_inode;
> -
> -       cifs_dbg(FYI, "ioctl clone range\n");
> -       /* the destination must be opened for writing */
> -       if (!(dst_file->f_mode & FMODE_WRITE)) {
> -               cifs_dbg(FYI, "file target not open for write\n");
> -               return -EINVAL;
> -       }
> -
> -       /* check if target volume is readonly and take reference */
> -       rc = mnt_want_write_file(dst_file);
> -       if (rc) {
> -               cifs_dbg(FYI, "mnt_want_write failed with rc %d\n", rc);
> -               return rc;
> -       }
> -
> -       src_file = fdget(srcfd);
> -       if (!src_file.file) {
> -               rc = -EBADF;
> -               goto out_drop_write;
> -       }
> -
> -       if (src_file.file->f_op->unlocked_ioctl != cifs_ioctl) {
> -               rc = -EBADF;
> -               cifs_dbg(VFS, "src file seems to be from a different filesystem type\n");
> -               goto out_fput;
> -       }
> -
> -       src_inode = file_inode(src_file.file);
> -       rc = -EINVAL;
> -       if (S_ISDIR(src_inode->i_mode))
> -               goto out_fput;
> -
> -       rc = cifs_file_clone_range(xid, src_file.file, dst_file);
> -
> -out_fput:
> -       fdput(src_file);
> -out_drop_write:
> -       mnt_drop_write_file(dst_file);
> -       return rc;
> -}
> -
>  static long smb_mnt_get_fsinfo(unsigned int xid, struct cifs_tcon *tcon,
>                                 void __user *arg)
>  {
> @@ -250,9 +146,6 @@ long cifs_ioctl(struct file *filep, unsigned int command, unsigned long arg)
>                                 cifs_dbg(FYI, "set compress flag rc %d\n", rc);
>                         }
>                         break;
> -               case CIFS_IOC_COPYCHUNK_FILE:
> -                       rc = cifs_ioctl_clone(xid, filep, arg);
> -                       break;
>                 case CIFS_IOC_SET_INTEGRITY:
>                         if (pSMBFile == NULL)
>                                 break;
> diff --git a/fs/cifs/smb2ops.c b/fs/cifs/smb2ops.c
> index 5d456eb..b7aa0926 100644
> --- a/fs/cifs/smb2ops.c
> +++ b/fs/cifs/smb2ops.c
> @@ -586,7 +586,7 @@ SMB2_request_res_key(const unsigned int xid, struct cifs_tcon *tcon,
>         return rc;
>  }
>
> -static int
> +static ssize_t
>  smb2_clone_range(const unsigned int xid,
>                         struct cifsFileInfo *srcfile,
>                         struct cifsFileInfo *trgtfile, u64 src_off,
> @@ -599,6 +599,7 @@ smb2_clone_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);
>
> @@ -662,14 +663,16 @@ smb2_clone_range(const unsigned int xid,
>                         }
>                         chunks_copied++;
>
> +                       bytes_written = le32_to_cpu(retbuf->TotalBytesWritten);
>                         src_off += le32_to_cpu(retbuf->TotalBytesWritten);
>                         dest_off += le32_to_cpu(retbuf->TotalBytesWritten);
> -                       len -= le32_to_cpu(retbuf->TotalBytesWritten);
> +                       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;
> @@ -706,7 +709,10 @@ smb2_clone_range(const unsigned int xid,
>  cchunk_out:
>         kfree(pcchunk);
>         kfree(retbuf);
> -       return rc;
> +       if (rc)
> +               return rc;
> +       else
> +               return total_bytes_written;
>  }
>
>  static int
> --
> 2.9.3
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-cifs" in
> the body of a message to majordomo@xxxxxxxxxxxxxxx
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

Other than the comments above the patch looks good.

-- 
Best regards,
Pavel Shilovsky
--
To unsubscribe from this list: send the line "unsubscribe linux-cifs" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux