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: Anna Schumaker <anna.schumaker@oracle.com>
pull/1285/head
Anna Schumaker 2025-04-11 15:47:32 -04:00
parent 4c10fa44bc
commit d2e1d783f2
8 changed files with 105 additions and 3 deletions

View File

@ -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, ssize_t nfs42_proc_copy(struct file *, loff_t, struct file *, loff_t, size_t,
struct nl4_server *, nfs4_stateid *, bool); struct nl4_server *, nfs4_stateid *, bool);
int nfs42_proc_deallocate(struct file *, loff_t, loff_t); 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); loff_t nfs42_proc_llseek(struct file *, loff_t, int);
int nfs42_proc_layoutstats_generic(struct nfs_server *, int nfs42_proc_layoutstats_generic(struct nfs_server *,
struct nfs42_layoutstat_data *); struct nfs42_layoutstat_data *);

View File

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

View File

@ -174,6 +174,18 @@
decode_putfh_maxsz + \ decode_putfh_maxsz + \
decode_deallocate_maxsz + \ decode_deallocate_maxsz + \
decode_getattr_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 + \ #define NFS4_enc_read_plus_sz (compound_encode_hdr_maxsz + \
encode_sequence_maxsz + \ encode_sequence_maxsz + \
encode_putfh_maxsz + \ encode_putfh_maxsz + \
@ -648,6 +660,27 @@ static void nfs4_xdr_enc_deallocate(struct rpc_rqst *req,
encode_nops(&hdr); 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 * Encode READ_PLUS request
*/ */
@ -1510,6 +1543,37 @@ out:
return status; 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 * Decode READ_PLUS request
*/ */

View File

@ -225,8 +225,14 @@ static long nfs42_fallocate(struct file *filep, int mode, loff_t offset, loff_t
if (!S_ISREG(inode->i_mode)) if (!S_ISREG(inode->i_mode))
return -EOPNOTSUPP; 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; return -EOPNOTSUPP;
}
ret = inode_newsize_ok(inode, offset + len); ret = inode_newsize_ok(inode, offset + len);
if (ret < 0) 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) if (mode & FALLOC_FL_PUNCH_HOLE)
return nfs42_proc_deallocate(filep, offset, len); 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); return nfs42_proc_allocate(filep, offset, len);
} }

View File

@ -10822,6 +10822,7 @@ static const struct nfs4_minor_version_ops nfs_v4_2_minor_ops = {
| NFS_CAP_OFFLOAD_CANCEL | NFS_CAP_OFFLOAD_CANCEL
| NFS_CAP_COPY_NOTIFY | NFS_CAP_COPY_NOTIFY
| NFS_CAP_DEALLOCATE | NFS_CAP_DEALLOCATE
| NFS_CAP_ZERO_RANGE
| NFS_CAP_SEEK | NFS_CAP_SEEK
| NFS_CAP_LAYOUTSTATS | NFS_CAP_LAYOUTSTATS
| NFS_CAP_CLONE | NFS_CAP_CLONE

View File

@ -7711,6 +7711,7 @@ const struct rpc_procinfo nfs4_procedures[] = {
PROC42(LISTXATTRS, enc_listxattrs, dec_listxattrs), PROC42(LISTXATTRS, enc_listxattrs, dec_listxattrs),
PROC42(REMOVEXATTR, enc_removexattr, dec_removexattr), PROC42(REMOVEXATTR, enc_removexattr, dec_removexattr),
PROC42(READ_PLUS, enc_read_plus, dec_read_plus), PROC42(READ_PLUS, enc_read_plus, dec_read_plus),
PROC42(ZERO_RANGE, enc_zero_range, dec_zero_range),
}; };
static unsigned int nfs_version4_counts[ARRAY_SIZE(nfs4_procedures)]; static unsigned int nfs_version4_counts[ARRAY_SIZE(nfs4_procedures)];

View File

@ -678,6 +678,7 @@ enum {
NFSPROC4_CLNT_SEEK, NFSPROC4_CLNT_SEEK,
NFSPROC4_CLNT_ALLOCATE, NFSPROC4_CLNT_ALLOCATE,
NFSPROC4_CLNT_DEALLOCATE, NFSPROC4_CLNT_DEALLOCATE,
NFSPROC4_CLNT_ZERO_RANGE,
NFSPROC4_CLNT_LAYOUTSTATS, NFSPROC4_CLNT_LAYOUTSTATS,
NFSPROC4_CLNT_CLONE, NFSPROC4_CLNT_CLONE,
NFSPROC4_CLNT_COPY, NFSPROC4_CLNT_COPY,

View File

@ -304,6 +304,7 @@ struct nfs_server {
#define NFS_CAP_CASE_PRESERVING (1U << 7) #define NFS_CAP_CASE_PRESERVING (1U << 7)
#define NFS_CAP_REBOOT_LAYOUTRETURN (1U << 8) #define NFS_CAP_REBOOT_LAYOUTRETURN (1U << 8)
#define NFS_CAP_OFFLOAD_STATUS (1U << 9) #define NFS_CAP_OFFLOAD_STATUS (1U << 9)
#define NFS_CAP_ZERO_RANGE (1U << 10)
#define NFS_CAP_OPEN_XOR (1U << 12) #define NFS_CAP_OPEN_XOR (1U << 12)
#define NFS_CAP_DELEGTIME (1U << 13) #define NFS_CAP_DELEGTIME (1U << 13)
#define NFS_CAP_POSIX_LOCK (1U << 14) #define NFS_CAP_POSIX_LOCK (1U << 14)