NFSD: Enable write delegation support
authorDai Ngo <dai.ngo@oracle.com>
Fri, 30 Jun 2023 01:52:40 +0000 (18:52 -0700)
committerChuck Lever <chuck.lever@oracle.com>
Tue, 29 Aug 2023 21:45:22 +0000 (17:45 -0400)
This patch grants write delegations for OPEN with NFS4_SHARE_ACCESS_WRITE
if there is no conflict with other OPENs.

Write delegation conflicts with another OPEN, REMOVE, RENAME and SETATTR
are handled the same as read delegation using notify_change,
try_break_deleg.

The NFSv4.0 protocol does not enable a server to determine that a
conflicting GETATTR originated from the client holding the
delegation versus coming from some other client. With NFSv4.1 and
later, the SEQUENCE operation that begins each COMPOUND contains a
client ID, so delegation recall can be safely squelched in this case.

With NFSv4.0, however, the server must recall or send a CB_GETATTR
(per RFC 7530 Section 16.7.5) even when the GETATTR originates from
the client holding that delegation.

An NFSv4.0 client can trigger a pathological situation if it always
sends a DELEGRETURN preceded by a conflicting GETATTR in the same
COMPOUND. COMPOUND execution will always stop at the GETATTR and the
DELEGRETURN will never get executed. The server eventually revokes
the delegation, which can result in loss of open or lock state.

Tracepoint added to track whether read or write delegation is granted.

Signed-off-by: Dai Ngo <dai.ngo@oracle.com>
Signed-off-by: Jeff Layton <jlayton@kernel.org>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
fs/nfsd/nfs4state.c
fs/nfsd/trace.h

index b56ea72d43501db1135a2dda9122f76b717168a5..8534693eb6a4970141aee102d31740eab8b343a6 100644 (file)
@@ -649,6 +649,18 @@ find_readable_file(struct nfs4_file *f)
        return ret;
 }
 
+static struct nfsd_file *
+find_rw_file(struct nfs4_file *f)
+{
+       struct nfsd_file *ret;
+
+       spin_lock(&f->fi_lock);
+       ret = nfsd_file_get(f->fi_fds[O_RDWR]);
+       spin_unlock(&f->fi_lock);
+
+       return ret;
+}
+
 struct nfsd_file *
 find_any_file(struct nfs4_file *f)
 {
@@ -1144,7 +1156,7 @@ static void block_delegations(struct knfsd_fh *fh)
 
 static struct nfs4_delegation *
 alloc_init_deleg(struct nfs4_client *clp, struct nfs4_file *fp,
-                struct nfs4_clnt_odstate *odstate)
+                struct nfs4_clnt_odstate *odstate, u32 dl_type)
 {
        struct nfs4_delegation *dp;
        long n;
@@ -1170,7 +1182,7 @@ alloc_init_deleg(struct nfs4_client *clp, struct nfs4_file *fp,
        INIT_LIST_HEAD(&dp->dl_recall_lru);
        dp->dl_clnt_odstate = odstate;
        get_clnt_odstate(odstate);
-       dp->dl_type = NFS4_OPEN_DELEGATE_READ;
+       dp->dl_type = dl_type;
        dp->dl_retries = 1;
        dp->dl_recalled = false;
        nfsd4_init_cb(&dp->dl_recall, dp->dl_stid.sc_client,
@@ -5449,8 +5461,9 @@ nfs4_set_delegation(struct nfsd4_open *open, struct nfs4_ol_stateid *stp,
        struct nfs4_file *fp = stp->st_stid.sc_file;
        struct nfs4_clnt_odstate *odstate = stp->st_clnt_odstate;
        struct nfs4_delegation *dp;
-       struct nfsd_file *nf;
+       struct nfsd_file *nf = NULL;
        struct file_lock *fl;
+       u32 dl_type;
 
        /*
         * The fi_had_conflict and nfs_get_existing_delegation checks
@@ -5460,15 +5473,35 @@ nfs4_set_delegation(struct nfsd4_open *open, struct nfs4_ol_stateid *stp,
        if (fp->fi_had_conflict)
                return ERR_PTR(-EAGAIN);
 
-       nf = find_readable_file(fp);
-       if (!nf) {
-               /*
-                * We probably could attempt another open and get a read
-                * delegation, but for now, don't bother until the
-                * client actually sends us one.
-                */
-               return ERR_PTR(-EAGAIN);
+       /*
+        * Try for a write delegation first. RFC8881 section 10.4 says:
+        *
+        *  "An OPEN_DELEGATE_WRITE delegation allows the client to handle,
+        *   on its own, all opens."
+        *
+        * Furthermore the client can use a write delegation for most READ
+        * operations as well, so we require a O_RDWR file here.
+        *
+        * Offer a write delegation in the case of a BOTH open, and ensure
+        * we get the O_RDWR descriptor.
+        */
+       if ((open->op_share_access & NFS4_SHARE_ACCESS_BOTH) == NFS4_SHARE_ACCESS_BOTH) {
+               nf = find_rw_file(fp);
+               dl_type = NFS4_OPEN_DELEGATE_WRITE;
        }
+
+       /*
+        * If the file is being opened O_RDONLY or we couldn't get a O_RDWR
+        * file for some reason, then try for a read delegation instead.
+        */
+       if (!nf && (open->op_share_access & NFS4_SHARE_ACCESS_READ)) {
+               nf = find_readable_file(fp);
+               dl_type = NFS4_OPEN_DELEGATE_READ;
+       }
+
+       if (!nf)
+               return ERR_PTR(-EAGAIN);
+
        spin_lock(&state_lock);
        spin_lock(&fp->fi_lock);
        if (nfs4_delegation_exists(clp, fp))
@@ -5491,11 +5524,11 @@ nfs4_set_delegation(struct nfsd4_open *open, struct nfs4_ol_stateid *stp,
                return ERR_PTR(status);
 
        status = -ENOMEM;
-       dp = alloc_init_deleg(clp, fp, odstate);
+       dp = alloc_init_deleg(clp, fp, odstate, dl_type);
        if (!dp)
                goto out_delegees;
 
-       fl = nfs4_alloc_init_lease(dp, NFS4_OPEN_DELEGATE_READ);
+       fl = nfs4_alloc_init_lease(dp, dl_type);
        if (!fl)
                goto out_clnt_odstate;
 
@@ -5568,10 +5601,28 @@ static void nfsd4_open_deleg_none_ext(struct nfsd4_open *open, int status)
 }
 
 /*
- * Attempt to hand out a delegation.
+ * The Linux NFS server does not offer write delegations to NFSv4.0
+ * clients in order to avoid conflicts between write delegations and
+ * GETATTRs requesting CHANGE or SIZE attributes.
+ *
+ * With NFSv4.1 and later minorversions, the SEQUENCE operation that
+ * begins each COMPOUND contains a client ID. Delegation recall can
+ * be avoided when the server recognizes the client sending a
+ * GETATTR also holds write delegation it conflicts with.
+ *
+ * However, the NFSv4.0 protocol does not enable a server to
+ * determine that a GETATTR originated from the client holding the
+ * conflicting delegation versus coming from some other client. Per
+ * RFC 7530 Section 16.7.5, the server must recall or send a
+ * CB_GETATTR even when the GETATTR originates from the client that
+ * holds the conflicting delegation.
  *
- * Note we don't support write delegations, and won't until the vfs has
- * proper support for them.
+ * An NFSv4.0 client can trigger a pathological situation if it
+ * always sends a DELEGRETURN preceded by a conflicting GETATTR in
+ * the same COMPOUND. COMPOUND execution will always stop at the
+ * GETATTR and the DELEGRETURN will never get executed. The server
+ * eventually revokes the delegation, which can result in loss of
+ * open or lock state.
  */
 static void
 nfs4_open_delegation(struct nfsd4_open *open, struct nfs4_ol_stateid *stp,
@@ -5590,8 +5641,6 @@ nfs4_open_delegation(struct nfsd4_open *open, struct nfs4_ol_stateid *stp,
                case NFS4_OPEN_CLAIM_PREVIOUS:
                        if (!cb_up)
                                open->op_recall = 1;
-                       if (open->op_delegate_type != NFS4_OPEN_DELEGATE_READ)
-                               goto out_no_deleg;
                        break;
                case NFS4_OPEN_CLAIM_NULL:
                        parent = currentfh;
@@ -5606,6 +5655,9 @@ nfs4_open_delegation(struct nfsd4_open *open, struct nfs4_ol_stateid *stp,
                                goto out_no_deleg;
                        if (!cb_up || !(oo->oo_flags & NFS4_OO_CONFIRMED))
                                goto out_no_deleg;
+                       if (open->op_share_access & NFS4_SHARE_ACCESS_WRITE &&
+                                       !clp->cl_minorversion)
+                               goto out_no_deleg;
                        break;
                default:
                        goto out_no_deleg;
@@ -5616,8 +5668,13 @@ nfs4_open_delegation(struct nfsd4_open *open, struct nfs4_ol_stateid *stp,
 
        memcpy(&open->op_delegate_stateid, &dp->dl_stid.sc_stateid, sizeof(dp->dl_stid.sc_stateid));
 
-       trace_nfsd_deleg_read(&dp->dl_stid.sc_stateid);
-       open->op_delegate_type = NFS4_OPEN_DELEGATE_READ;
+       if (open->op_share_access & NFS4_SHARE_ACCESS_WRITE) {
+               open->op_delegate_type = NFS4_OPEN_DELEGATE_WRITE;
+               trace_nfsd_deleg_write(&dp->dl_stid.sc_stateid);
+       } else {
+               open->op_delegate_type = NFS4_OPEN_DELEGATE_READ;
+               trace_nfsd_deleg_read(&dp->dl_stid.sc_stateid);
+       }
        nfs4_put_stid(&dp->dl_stid);
        return;
 out_no_deleg:
index 2af74983f146173589724c2058ad243c7c1dd05d..693fe6d465aa6dd6d287f21d450139e35262bbb8 100644 (file)
@@ -607,6 +607,7 @@ DEFINE_STATEID_EVENT(layout_recall_release);
 
 DEFINE_STATEID_EVENT(open);
 DEFINE_STATEID_EVENT(deleg_read);
+DEFINE_STATEID_EVENT(deleg_write);
 DEFINE_STATEID_EVENT(deleg_return);
 DEFINE_STATEID_EVENT(deleg_recall);