]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
NFS: Add support for fallocate(FALLOC_FL_ZERO_RANGE)
authorAnna Schumaker <anna.schumaker@oracle.com>
Fri, 11 Apr 2025 19:47:32 +0000 (15:47 -0400)
committerAnna Schumaker <anna.schumaker@oracle.com>
Wed, 28 May 2025 21:17:13 +0000 (17:17 -0400)
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>
fs/nfs/nfs42.h
fs/nfs/nfs42proc.c
fs/nfs/nfs42xdr.c
fs/nfs/nfs4file.c
fs/nfs/nfs4proc.c
fs/nfs/nfs4xdr.c
include/linux/nfs4.h
include/linux/nfs_fs_sb.h

index 0282d93c8bccb386d744017e9d2741abcb9a5f13..aafd15a4afce04be5b0d24f4c06ae3739a9c50dd 100644 (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,
                        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 *);
index 5cf52ece96ac1c30ad0cb2cea1ae6a9c5315907c..01c01f45358b7cf6b29455b447a9f059049ca68b 100644 (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);
        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;
index b1b663468249be0a8744b3794c718750fd8b8eb4..4cc915d5741d47627318ac01b2eb73d00d3b499a 100644 (file)
                                         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 @@ out:
        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
  */
index 1cd9652f3c280358209f22503ea573a906a6194e..5e9d66f3466c8db3f3b36dbf5c26e1c80739ca91 100644 (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))
                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);
 }
 
index f837ed7ad90c01f35f14389c740f094ec5ec92bf..a7c4194ed2a0bb45444790393a5b88e82592bb10 100644 (file)
@@ -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
index 55bef5fbfa4778d39688658e2e7d4a3ea68b4c43..318afde38057832f603139276275a60549a0d273 100644 (file)
@@ -7711,6 +7711,7 @@ const struct rpc_procinfo nfs4_procedures[] = {
        PROC42(LISTXATTRS,      enc_listxattrs,         dec_listxattrs),
        PROC42(REMOVEXATTR,     enc_removexattr,        dec_removexattr),
        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)];
index d8cad844870aa74ce1e0cc78c499fb001d898c93..07635f87a3fd77802924846ded1efcdc8c1208d6 100644 (file)
@@ -678,6 +678,7 @@ enum {
        NFSPROC4_CLNT_SEEK,
        NFSPROC4_CLNT_ALLOCATE,
        NFSPROC4_CLNT_DEALLOCATE,
+       NFSPROC4_CLNT_ZERO_RANGE,
        NFSPROC4_CLNT_LAYOUTSTATS,
        NFSPROC4_CLNT_CLONE,
        NFSPROC4_CLNT_COPY,
index e02f4c4e9cc494b1d772e81f114dacd1fee3c00f..63141320c2a8124d7d78859c2ae90a94eab60585 100644 (file)
@@ -304,6 +304,7 @@ struct nfs_server {
 #define NFS_CAP_CASE_PRESERVING        (1U << 7)
 #define NFS_CAP_REBOOT_LAYOUTRETURN    (1U << 8)
 #define NFS_CAP_OFFLOAD_STATUS (1U << 9)
+#define NFS_CAP_ZERO_RANGE     (1U << 10)
 #define NFS_CAP_OPEN_XOR       (1U << 12)
 #define NFS_CAP_DELEGTIME      (1U << 13)
 #define NFS_CAP_POSIX_LOCK     (1U << 14)