Commit d2e1d783 authored by Anna Schumaker's avatar Anna Schumaker
Browse files

NFS: Add support for fallocate(FALLOC_FL_ZERO_RANGE)



This implements a suggestion from Trond that we can mimic
FALLOC_FL_ZERO_RANGE by sending a compound that first does a DEALLOCATE
to punch a hole in a file, and then an ALLOCATE to fill the hole with
zeroes. There might technically be a race here, but once the DEALLOCATE
finishes any reads from the region would return zeroes anyway, so I
don't expect it to cause problems.

Signed-off-by: default avatarAnna Schumaker <anna.schumaker@oracle.com>
parent 4c10fa44
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ int nfs42_proc_allocate(struct file *, loff_t, loff_t);
ssize_t nfs42_proc_copy(struct file *, loff_t, struct file *, loff_t, size_t,
			struct nl4_server *, nfs4_stateid *, bool);
int nfs42_proc_deallocate(struct file *, loff_t, loff_t);
int nfs42_proc_zero_range(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 *);
+27 −2
Original line number Diff line number Diff line
@@ -146,7 +146,8 @@ int nfs42_proc_allocate(struct file *filep, loff_t offset, loff_t len)

	err = nfs42_proc_fallocate(&msg, filep, offset, len);
	if (err == -EOPNOTSUPP)
		NFS_SERVER(inode)->caps &= ~NFS_CAP_ALLOCATE;
		NFS_SERVER(inode)->caps &= ~(NFS_CAP_ALLOCATE |
					     NFS_CAP_ZERO_RANGE);

	inode_unlock(inode);
	return err;
@@ -169,7 +170,31 @@ int nfs42_proc_deallocate(struct file *filep, loff_t offset, loff_t len)
	if (err == 0)
		truncate_pagecache_range(inode, offset, (offset + len) -1);
	if (err == -EOPNOTSUPP)
		NFS_SERVER(inode)->caps &= ~NFS_CAP_DEALLOCATE;
		NFS_SERVER(inode)->caps &= ~(NFS_CAP_DEALLOCATE |
					     NFS_CAP_ZERO_RANGE);

	inode_unlock(inode);
	return err;
}

int nfs42_proc_zero_range(struct file *filep, loff_t offset, loff_t len)
{
	struct rpc_message msg = {
		.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_ZERO_RANGE],
	};
	struct inode *inode = file_inode(filep);
	int err;

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

	inode_lock(inode);

	err = nfs42_proc_fallocate(&msg, filep, offset, len);
	if (err == 0)
		truncate_pagecache_range(inode, offset, (offset + len) -1);
	if (err == -EOPNOTSUPP)
		NFS_SERVER(inode)->caps &= ~NFS_CAP_ZERO_RANGE;

	inode_unlock(inode);
	return err;
+64 −0
Original line number Diff line number Diff line
@@ -174,6 +174,18 @@
					 decode_putfh_maxsz + \
					 decode_deallocate_maxsz + \
					 decode_getattr_maxsz)
#define NFS4_enc_zero_range_sz		(compound_encode_hdr_maxsz + \
					 encode_sequence_maxsz + \
					 encode_putfh_maxsz + \
					 encode_deallocate_maxsz + \
					 encode_allocate_maxsz + \
					 encode_getattr_maxsz)
#define NFS4_dec_zero_range_sz		(compound_decode_hdr_maxsz + \
					 decode_sequence_maxsz + \
					 decode_putfh_maxsz + \
					 decode_deallocate_maxsz + \
					 decode_allocate_maxsz + \
					 decode_getattr_maxsz)
#define NFS4_enc_read_plus_sz		(compound_encode_hdr_maxsz + \
					 encode_sequence_maxsz + \
					 encode_putfh_maxsz + \
@@ -648,6 +660,27 @@ static void nfs4_xdr_enc_deallocate(struct rpc_rqst *req,
	encode_nops(&hdr);
}

/*
 * Encode ZERO_RANGE request
 */
static void nfs4_xdr_enc_zero_range(struct rpc_rqst *req,
				    struct xdr_stream *xdr,
				    const void *data)
{
	const struct nfs42_falloc_args *args = data;
	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->falloc_fh, &hdr);
	encode_deallocate(xdr, args, &hdr);
	encode_allocate(xdr, args, &hdr);
	encode_getfattr(xdr, args->falloc_bitmask, &hdr);
	encode_nops(&hdr);
}

/*
 * Encode READ_PLUS request
 */
@@ -1510,6 +1543,37 @@ static int nfs4_xdr_dec_deallocate(struct rpc_rqst *rqstp,
	return status;
}

/*
 * Decode ZERO_RANGE request
 */
static int nfs4_xdr_dec_zero_range(struct rpc_rqst *rqstp,
				   struct xdr_stream *xdr,
				   void *data)
{
	struct nfs42_falloc_res *res = data;
	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_deallocate(xdr, res);
	if (status)
		goto out;
	status = decode_allocate(xdr, res);
	if (status)
		goto out;
	decode_getfattr(xdr, res->falloc_fattr, res->falloc_server);
out:
	return status;
}

/*
 * Decode READ_PLUS request
 */
+9 −1
Original line number Diff line number Diff line
@@ -225,8 +225,14 @@ static long nfs42_fallocate(struct file *filep, int mode, loff_t offset, loff_t
	if (!S_ISREG(inode->i_mode))
		return -EOPNOTSUPP;

	if ((mode != 0) && (mode != (FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE)))
	switch (mode) {
	case 0:
	case FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE:
	case FALLOC_FL_ZERO_RANGE:
		break;
	default:
		return -EOPNOTSUPP;
	}

	ret = inode_newsize_ok(inode, offset + len);
	if (ret < 0)
@@ -234,6 +240,8 @@ static long nfs42_fallocate(struct file *filep, int mode, loff_t offset, loff_t

	if (mode & FALLOC_FL_PUNCH_HOLE)
		return nfs42_proc_deallocate(filep, offset, len);
	else if (mode & FALLOC_FL_ZERO_RANGE)
		return nfs42_proc_zero_range(filep, offset ,len);
	return nfs42_proc_allocate(filep, offset, len);
}

+1 −0
Original line number Diff line number Diff line
@@ -10822,6 +10822,7 @@ static const struct nfs4_minor_version_ops nfs_v4_2_minor_ops = {
		| NFS_CAP_OFFLOAD_CANCEL
		| NFS_CAP_COPY_NOTIFY
		| NFS_CAP_DEALLOCATE
		| NFS_CAP_ZERO_RANGE
		| NFS_CAP_SEEK
		| NFS_CAP_LAYOUTSTATS
		| NFS_CAP_CLONE
Loading