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

Commit 51e0164e authored by Trond Myklebust's avatar Trond Myklebust
Browse files

Merge branch 'nfsclone'

* nfsclone:
  nfs: add missing linux/types.h
  NFS: Fix an 'unused variable' complaint when #ifndef CONFIG_NFS_V4_2
  nfs42: add NFS_IOC_CLONE_RANGE ioctl
  nfs42: respect clone_blksize
  nfs: get clone_blksize when probing fsinfo
  nfs42: add NFS_IOC_CLONE ioctl
  nfs42: add CLONE proc functions
  nfs42: add CLONE xdr functions
parents 120bf961 b9788a44
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -764,6 +764,7 @@ static void nfs_server_set_fsinfo(struct nfs_server *server,

	server->time_delta = fsinfo->time_delta;

	server->clone_blksize = fsinfo->clone_blksize;
	/* We're airborne Set socket buffersize */
	rpc_setbufsize(server->client, server->wsize + 100, server->rsize + 100);
}
+1 −0
Original line number Diff line number Diff line
@@ -17,5 +17,6 @@ int nfs42_proc_deallocate(struct file *, loff_t, loff_t);
loff_t nfs42_proc_llseek(struct file *, loff_t, int);
int nfs42_proc_layoutstats_generic(struct nfs_server *,
				   struct nfs42_layoutstat_data *);
int nfs42_proc_clone(struct file *, struct file *, loff_t, loff_t, loff_t);

#endif /* __LINUX_FS_NFS_NFS4_2_H */
+71 −0
Original line number Diff line number Diff line
@@ -271,3 +271,74 @@ int nfs42_proc_layoutstats_generic(struct nfs_server *server,
		return PTR_ERR(task);
	return 0;
}

static int _nfs42_proc_clone(struct rpc_message *msg, struct file *src_f,
			     struct file *dst_f, loff_t src_offset,
			     loff_t dst_offset, loff_t count)
{
	struct inode *src_inode = file_inode(src_f);
	struct inode *dst_inode = file_inode(dst_f);
	struct nfs_server *server = NFS_SERVER(dst_inode);
	struct nfs42_clone_args args = {
		.src_fh = NFS_FH(src_inode),
		.dst_fh = NFS_FH(dst_inode),
		.src_offset = src_offset,
		.dst_offset = dst_offset,
		.dst_bitmask = server->cache_consistency_bitmask,
	};
	struct nfs42_clone_res res = {
		.server	= server,
	};
	int status;

	msg->rpc_argp = &args;
	msg->rpc_resp = &res;

	status = nfs42_set_rw_stateid(&args.src_stateid, src_f, FMODE_READ);
	if (status)
		return status;

	status = nfs42_set_rw_stateid(&args.dst_stateid, dst_f, FMODE_WRITE);
	if (status)
		return status;

	res.dst_fattr = nfs_alloc_fattr();
	if (!res.dst_fattr)
		return -ENOMEM;

	status = nfs4_call_sync(server->client, server, msg,
				&args.seq_args, &res.seq_res, 0);
	if (status == 0)
		status = nfs_post_op_update_inode(dst_inode, res.dst_fattr);

	kfree(res.dst_fattr);
	return status;
}

int nfs42_proc_clone(struct file *src_f, struct file *dst_f,
		     loff_t src_offset, loff_t dst_offset, loff_t count)
{
	struct rpc_message msg = {
		.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_CLONE],
	};
	struct inode *inode = file_inode(src_f);
	struct nfs_server *server = NFS_SERVER(file_inode(src_f));
	struct nfs4_exception exception = { };
	int err;

	if (!nfs_server_capable(inode, NFS_CAP_CLONE))
		return -EOPNOTSUPP;

	do {
		err = _nfs42_proc_clone(&msg, src_f, dst_f, src_offset,
					dst_offset, count);
		if (err == -ENOTSUPP || err == -EOPNOTSUPP) {
			NFS_SERVER(inode)->caps &= ~NFS_CAP_CLONE;
			return -EOPNOTSUPP;
		}
		err = nfs4_handle_exception(server, err, &exception);
	} while (exception.retry);

	return err;

}
+96 −1
Original line number Diff line number Diff line
@@ -34,6 +34,12 @@
					1 /* opaque devaddr4 length */ + \
					XDR_QUADLEN(PNFS_LAYOUTSTATS_MAXSIZE))
#define decode_layoutstats_maxsz	(op_decode_hdr_maxsz)
#define encode_clone_maxsz		(encode_stateid_maxsz + \
					encode_stateid_maxsz + \
					2 /* src offset */ + \
					2 /* dst offset */ + \
					2 /* count */)
#define decode_clone_maxsz		(op_decode_hdr_maxsz)

#define NFS4_enc_allocate_sz		(compound_encode_hdr_maxsz + \
					 encode_putfh_maxsz + \
@@ -65,7 +71,20 @@
					 decode_sequence_maxsz + \
					 decode_putfh_maxsz + \
					 PNFS_LAYOUTSTATS_MAXDEV * decode_layoutstats_maxsz)

#define NFS4_enc_clone_sz		(compound_encode_hdr_maxsz + \
					 encode_sequence_maxsz + \
					 encode_putfh_maxsz + \
					 encode_savefh_maxsz + \
					 encode_putfh_maxsz + \
					 encode_clone_maxsz + \
					 encode_getattr_maxsz)
#define NFS4_dec_clone_sz		(compound_decode_hdr_maxsz + \
					 decode_sequence_maxsz + \
					 decode_putfh_maxsz + \
					 decode_savefh_maxsz + \
					 decode_putfh_maxsz + \
					 decode_clone_maxsz + \
					 decode_getattr_maxsz)

static void encode_fallocate(struct xdr_stream *xdr,
			     struct nfs42_falloc_args *args)
@@ -128,6 +147,21 @@ static void encode_layoutstats(struct xdr_stream *xdr,
		encode_uint32(xdr, 0);
}

static void encode_clone(struct xdr_stream *xdr,
			 struct nfs42_clone_args *args,
			 struct compound_hdr *hdr)
{
	__be32 *p;

	encode_op_hdr(xdr, OP_CLONE, decode_clone_maxsz, hdr);
	encode_nfs4_stateid(xdr, &args->src_stateid);
	encode_nfs4_stateid(xdr, &args->dst_stateid);
	p = reserve_space(xdr, 3*8);
	p = xdr_encode_hyper(p, args->src_offset);
	p = xdr_encode_hyper(p, args->dst_offset);
	xdr_encode_hyper(p, args->count);
}

/*
 * Encode ALLOCATE request
 */
@@ -206,6 +240,27 @@ static void nfs4_xdr_enc_layoutstats(struct rpc_rqst *req,
	encode_nops(&hdr);
}

/*
 * Encode CLONE request
 */
static void nfs4_xdr_enc_clone(struct rpc_rqst *req,
			       struct xdr_stream *xdr,
			       struct nfs42_clone_args *args)
{
	struct compound_hdr hdr = {
		.minorversion = nfs4_xdr_minorversion(&args->seq_args),
	};

	encode_compound_hdr(xdr, req, &hdr);
	encode_sequence(xdr, &args->seq_args, &hdr);
	encode_putfh(xdr, args->src_fh, &hdr);
	encode_savefh(xdr, &hdr);
	encode_putfh(xdr, args->dst_fh, &hdr);
	encode_clone(xdr, args, &hdr);
	encode_getfattr(xdr, args->dst_bitmask, &hdr);
	encode_nops(&hdr);
}

static int decode_allocate(struct xdr_stream *xdr, struct nfs42_falloc_res *res)
{
	return decode_op_hdr(xdr, OP_ALLOCATE);
@@ -243,6 +298,11 @@ static int decode_layoutstats(struct xdr_stream *xdr)
	return decode_op_hdr(xdr, OP_LAYOUTSTATS);
}

static int decode_clone(struct xdr_stream *xdr)
{
	return decode_op_hdr(xdr, OP_CLONE);
}

/*
 * Decode ALLOCATE request
 */
@@ -351,4 +411,39 @@ static int nfs4_xdr_dec_layoutstats(struct rpc_rqst *rqstp,
	return status;
}

/*
 * Decode CLONE request
 */
static int nfs4_xdr_dec_clone(struct rpc_rqst *rqstp,
			      struct xdr_stream *xdr,
			      struct nfs42_clone_res *res)
{
	struct compound_hdr hdr;
	int status;

	status = decode_compound_hdr(xdr, &hdr);
	if (status)
		goto out;
	status = decode_sequence(xdr, &res->seq_res, rqstp);
	if (status)
		goto out;
	status = decode_putfh(xdr);
	if (status)
		goto out;
	status = decode_savefh(xdr);
	if (status)
		goto out;
	status = decode_putfh(xdr);
	if (status)
		goto out;
	status = decode_clone(xdr);
	if (status)
		goto out;
	status = decode_getfattr(xdr, res->dst_fattr, res->server);

out:
	res->rpc_status = status;
	return status;
}

#endif /* __LINUX_FS_NFS_NFS4_2XDR_H */
+136 −0
Original line number Diff line number Diff line
@@ -4,6 +4,7 @@
 *  Copyright (C) 1992  Rick Sladkey
 */
#include <linux/fs.h>
#include <linux/file.h>
#include <linux/falloc.h>
#include <linux/nfs_fs.h>
#include "delegation.h"
@@ -192,8 +193,138 @@ static long nfs42_fallocate(struct file *filep, int mode, loff_t offset, loff_t
		return nfs42_proc_deallocate(filep, offset, len);
	return nfs42_proc_allocate(filep, offset, len);
}

static noinline long
nfs42_ioctl_clone(struct file *dst_file, unsigned long srcfd,
		  u64 src_off, u64 dst_off, u64 count)
{
	struct inode *dst_inode = file_inode(dst_file);
	struct nfs_server *server = NFS_SERVER(dst_inode);
	struct fd src_file;
	struct inode *src_inode;
	unsigned int bs = server->clone_blksize;
	int ret;

	/* dst file must be opened for writing */
	if (!(dst_file->f_mode & FMODE_WRITE))
		return -EINVAL;

	ret = mnt_want_write_file(dst_file);
	if (ret)
		return ret;

	src_file = fdget(srcfd);
	if (!src_file.file) {
		ret = -EBADF;
		goto out_drop_write;
	}

	src_inode = file_inode(src_file.file);

	/* src and dst must be different files */
	ret = -EINVAL;
	if (src_inode == dst_inode)
		goto out_fput;

	/* src file must be opened for reading */
	if (!(src_file.file->f_mode & FMODE_READ))
		goto out_fput;

	/* src and dst must be regular files */
	ret = -EISDIR;
	if (!S_ISREG(src_inode->i_mode) || !S_ISREG(dst_inode->i_mode))
		goto out_fput;

	ret = -EXDEV;
	if (src_file.file->f_path.mnt != dst_file->f_path.mnt ||
	    src_inode->i_sb != dst_inode->i_sb)
		goto out_fput;

	/* check alignment w.r.t. clone_blksize */
	ret = -EINVAL;
	if (bs) {
		if (!IS_ALIGNED(src_off, bs) || !IS_ALIGNED(dst_off, bs))
			goto out_fput;
		if (!IS_ALIGNED(count, bs) && i_size_read(src_inode) != (src_off + count))
			goto out_fput;
	}

	/* XXX: do we lock at all? what if server needs CB_RECALL_LAYOUT? */
	if (dst_inode < src_inode) {
		mutex_lock_nested(&dst_inode->i_mutex, I_MUTEX_PARENT);
		mutex_lock_nested(&src_inode->i_mutex, I_MUTEX_CHILD);
	} else {
		mutex_lock_nested(&src_inode->i_mutex, I_MUTEX_PARENT);
		mutex_lock_nested(&dst_inode->i_mutex, I_MUTEX_CHILD);
	}

	/* flush all pending writes on both src and dst so that server
	 * has the latest data */
	ret = nfs_sync_inode(src_inode);
	if (ret)
		goto out_unlock;
	ret = nfs_sync_inode(dst_inode);
	if (ret)
		goto out_unlock;

	ret = nfs42_proc_clone(src_file.file, dst_file, src_off, dst_off, count);

	/* truncate inode page cache of the dst range so that future reads can fetch
	 * new data from server */
	if (!ret)
		truncate_inode_pages_range(&dst_inode->i_data, dst_off, dst_off + count - 1);

out_unlock:
	if (dst_inode < src_inode) {
		mutex_unlock(&src_inode->i_mutex);
		mutex_unlock(&dst_inode->i_mutex);
	} else {
		mutex_unlock(&dst_inode->i_mutex);
		mutex_unlock(&src_inode->i_mutex);
	}
out_fput:
	fdput(src_file);
out_drop_write:
	mnt_drop_write_file(dst_file);
	return ret;
}

static long nfs42_ioctl_clone_range(struct file *dst_file, void __user *argp)
{
	struct nfs_ioctl_clone_range_args args;

	if (copy_from_user(&args, argp, sizeof(args)))
		return -EFAULT;

	return nfs42_ioctl_clone(dst_file, args.src_fd, args.src_off, args.dst_off, args.count);
}
#else
static long nfs42_ioctl_clone(struct file *dst_file, unsigned long srcfd,
		u64 src_off, u64 dst_off, u64 count)
{
	return -ENOTTY;
}

static long nfs42_ioctl_clone_range(struct file *dst_file, void __user *argp)
{
	return -ENOTTY;
}
#endif /* CONFIG_NFS_V4_2 */

long nfs4_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	void __user *argp = (void __user *)arg;

	switch (cmd) {
	case NFS_IOC_CLONE:
		return nfs42_ioctl_clone(file, arg, 0, 0, 0);
	case NFS_IOC_CLONE_RANGE:
		return nfs42_ioctl_clone_range(file, argp);
	}

	return -ENOTTY;
}

const struct file_operations nfs4_file_operations = {
#ifdef CONFIG_NFS_V4_2
	.llseek		= nfs4_file_llseek,
@@ -216,4 +347,9 @@ const struct file_operations nfs4_file_operations = {
#endif /* CONFIG_NFS_V4_2 */
	.check_flags	= nfs_check_flags,
	.setlease	= simple_nosetlease,
#ifdef CONFIG_COMPAT
	.unlocked_ioctl = nfs4_ioctl,
#else
	.compat_ioctl	= nfs4_ioctl,
#endif /* CONFIG_COMPAT */
};
Loading