Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 620d8745 authored by Sachin Prabhu's avatar Sachin Prabhu Committed by Steve French
Browse files

Introduce cifs_copy_file_range()



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: default avatarSachin Prabhu <sprabhu@redhat.com>
Reviewed-by: default avatarPavel Shilovsky <pshilov@microsoft.com>
CC: Stable  <stable@vger.kernel.org>
Signed-off-by: default avatarSteve French <smfrench@gmail.com>
parent 312bbc59
Loading
Loading
Loading
Loading
+87 −0
Original line number Diff line number Diff line
@@ -972,6 +972,86 @@ static int cifs_clone_file_range(struct file *src_file, loff_t off,
	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,
};
+5 −0
Original line number Diff line number Diff line
@@ -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;
+3 −3
Original line number Diff line number Diff line
@@ -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);
+2 −58
Original line number Diff line number Diff line
@@ -34,63 +34,6 @@
#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);
+13 −7
Original line number Diff line number Diff line
@@ -592,7 +592,7 @@ SMB2_request_res_key(const unsigned int xid, struct cifs_tcon *tcon,
	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);
	if (rc)
		return rc;
	else
		return total_bytes_written;
}

static int