git: 98c53b0b5540 - stable/14 - nfsd: Fix delegation handled for atomic upgrade

From: Rick Macklem <rmacklem_at_FreeBSD.org>
Date: Fri, 05 Jul 2024 01:14:43 UTC
The branch stable/14 has been updated by rmacklem:

URL: https://cgit.FreeBSD.org/src/commit/?id=98c53b0b55401eaed374b235a40f3a547a5ab4e9

commit 98c53b0b55401eaed374b235a40f3a547a5ab4e9
Author:     Rick Macklem <rmacklem@FreeBSD.org>
AuthorDate: 2024-06-05 01:46:41 +0000
Commit:     Rick Macklem <rmacklem@FreeBSD.org>
CommitDate: 2024-07-05 01:12:26 +0000

    nfsd: Fix delegation handled for atomic upgrade
    
    For NFSv4.1/4.2, an atomic upgrade of a delegation from a
    read delegation to a write delegation is allowed and can
    result in signoficantly improved performance.
    
    This patch adds support for this atomic upgrade, plus fixes
    a couple of other delegation related bugs.  Since there were
    three cases where delegations were being issued, the patch
    factors this out into a separate function called
    nfsrv_issuedelegations().
    
    This patch should only affect the NFSv4.1/4.2 behaviour
    when delegations are enabled, which is not the default.
    
    (cherry picked from commit e2c9fad2e0ae3f7049831bf7f2be1a3573363cdc)
---
 sys/fs/nfsserver/nfs_nfsdserv.c  |   7 +
 sys/fs/nfsserver/nfs_nfsdstate.c | 297 ++++++++++++++++++---------------------
 2 files changed, 141 insertions(+), 163 deletions(-)

diff --git a/sys/fs/nfsserver/nfs_nfsdserv.c b/sys/fs/nfsserver/nfs_nfsdserv.c
index 0c8bda6dc6a6..47e3a20390f4 100644
--- a/sys/fs/nfsserver/nfs_nfsdserv.c
+++ b/sys/fs/nfsserver/nfs_nfsdserv.c
@@ -3244,6 +3244,13 @@ nfsrvd_open(struct nfsrv_descript *nd, __unused int isdgram,
 				NFSM_BUILD(tl, u_int32_t *, 2 * NFSX_UNSIGNED);
 				*tl++ = txdr_unsigned(NFSV4OPEN_RESOURCE);
 				*tl = newnfs_false;
+			} else if ((rflags &
+			    NFSV4OPEN_WDNOTSUPPDOWNGRADE) != 0) {
+				NFSM_BUILD(tl, uint32_t *, NFSX_UNSIGNED);
+				*tl = txdr_unsigned(NFSV4OPEN_NOTSUPPDOWNGRADE);
+			} else if ((rflags & NFSV4OPEN_WDNOTSUPPUPGRADE) != 0) {
+				NFSM_BUILD(tl, uint32_t *, NFSX_UNSIGNED);
+				*tl = txdr_unsigned(NFSV4OPEN_NOTSUPPUPGRADE);
 			} else {
 				NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED);
 				*tl = txdr_unsigned(NFSV4OPEN_NOTWANTED);
diff --git a/sys/fs/nfsserver/nfs_nfsdstate.c b/sys/fs/nfsserver/nfs_nfsdstate.c
index c73840277022..ce3f3481f04a 100644
--- a/sys/fs/nfsserver/nfs_nfsdstate.c
+++ b/sys/fs/nfsserver/nfs_nfsdstate.c
@@ -240,6 +240,11 @@ static int nfsrv_createdsfile(vnode_t vp, fhandle_t *fhp, struct pnfsdsfile *pf,
 static struct nfsdevice *nfsrv_findmirroredds(struct nfsmount *nmp);
 static int nfsrv_checkmachcred(int op, struct nfsrv_descript *nd,
     struct nfsclient *clp);
+static void nfsrv_issuedelegation(struct vnode *vp, struct nfsclient *clp,
+    struct nfsrv_descript *nd, int delegate, int writedeleg, int readonly,
+    u_quad_t filerev, uint64_t rdonly, struct nfsstate **new_delegp,
+    struct nfsstate *new_stp, struct nfslockfile *lfp, uint32_t *rflagsp,
+    nfsv4stateid_t *delegstateidp);
 
 /*
  * Scan the client list for a match and either return the current one,
@@ -442,7 +447,8 @@ nfsrv_setclient(struct nfsrv_descript *nd, struct nfsclient **new_clpp,
 		/*
 		 * If the verifier has changed, the client has rebooted
 		 * and a new client id is issued. The old state info
-		 * can be thrown away once the SETCLIENTID_CONFIRM occurs.
+		 * can be thrown away once the SetClientID_Confirm or
+		 * Create_Session that confirms the clientid occurs.
 		 */
 		LIST_REMOVE(clp, lc_hash);
 
@@ -2648,6 +2654,8 @@ tryagain:
 	 *    considered a conflict since the client with a read delegation
 	 *    could have done an Open with ReadAccess and WriteDeny
 	 *    locally and then not have checked for the WriteDeny.)
+	 *    The exception is a NFSv4.1/4.2 client that has requested
+	 *    an atomic upgrade to a write delegation.
 	 * Don't check for a Reclaim, since that will be dealt with
 	 * by nfsrv_openctrl().
 	 */
@@ -2657,9 +2665,10 @@ tryagain:
 	    while (stp != LIST_END(&lfp->lf_deleg)) {
 		nstp = LIST_NEXT(stp, ls_file);
 		if ((readonly && stp->ls_clp != clp &&
-		       (stp->ls_flags & NFSLCK_DELEGWRITE)) ||
+		     (stp->ls_flags & NFSLCK_DELEGWRITE) != 0) ||
 		    (!readonly && (stp->ls_clp != clp ||
-		         (stp->ls_flags & NFSLCK_DELEGREAD)))) {
+		     ((stp->ls_flags & NFSLCK_DELEGREAD) != 0 &&
+		      (new_stp->ls_flags & NFSLCK_WANTWDELEG) == 0)))) {
 			ret = nfsrv_delegconflict(stp, &haslock, p, vp);
 			if (ret) {
 			    /*
@@ -2944,6 +2953,8 @@ tryagain:
 	 *    considered a conflict since the client with a read delegation
 	 *    could have done an Open with ReadAccess and WriteDeny
 	 *    locally and then not have checked for the WriteDeny.)
+	 *    The exception is a NFSv4.1/4.2 client that has requested
+	 *    an atomic upgrade to a write delegation.
 	 */
 	if (!(new_stp->ls_flags & (NFSLCK_DELEGPREV | NFSLCK_DELEGCUR))) {
 	    stp = LIST_FIRST(&lfp->lf_deleg);
@@ -2951,12 +2962,15 @@ tryagain:
 		nstp = LIST_NEXT(stp, ls_file);
 		if (stp->ls_clp != clp && (stp->ls_flags & NFSLCK_DELEGREAD))
 			writedeleg = 0;
-		else
+		else if (stp->ls_clp != clp ||
+		    (stp->ls_flags & NFSLCK_DELEGWRITE) != 0 ||
+		    (new_stp->ls_flags & NFSLCK_WANTWDELEG) == 0)
 			delegate = 0;
 		if ((readonly && stp->ls_clp != clp &&
-		       (stp->ls_flags & NFSLCK_DELEGWRITE)) ||
+		     (stp->ls_flags & NFSLCK_DELEGWRITE) != 0) ||
 		    (!readonly && (stp->ls_clp != clp ||
-		         (stp->ls_flags & NFSLCK_DELEGREAD)))) {
+		     ((stp->ls_flags & NFSLCK_DELEGREAD) != 0 &&
+		      (new_stp->ls_flags & NFSLCK_WANTWDELEG) == 0)))) {
 		    if (new_stp->ls_flags & NFSLCK_RECLAIM) {
 			delegate = 2;
 		    } else {
@@ -3204,47 +3218,9 @@ tryagain:
 		    /*
 		     * This is where we can choose to issue a delegation.
 		     */
-		    if ((new_stp->ls_flags & NFSLCK_WANTNODELEG) != 0)
-			*rflagsp |= NFSV4OPEN_WDNOTWANTED;
-		    else if (nfsrv_issuedelegs == 0)
-			*rflagsp |= NFSV4OPEN_WDSUPPFTYPE;
-		    else if (NFSRV_V4DELEGLIMIT(nfsrv_delegatecnt))
-			*rflagsp |= NFSV4OPEN_WDRESOURCE;
-		    else if (delegate == 0 || writedeleg == 0 ||
-			NFSVNO_EXRDONLY(exp) || (readonly != 0 &&
-			nfsrv_writedelegifpos == 0) ||
-			!NFSVNO_DELEGOK(vp) ||
-			(new_stp->ls_flags & NFSLCK_WANTRDELEG) != 0 ||
-			(clp->lc_flags & (LCL_CALLBACKSON | LCL_CBDOWN)) !=
-			 LCL_CALLBACKSON)
-			*rflagsp |= NFSV4OPEN_WDCONTENTION;
-		    else {
-			new_deleg->ls_stateid.seqid = delegstateidp->seqid = 1;
-			new_deleg->ls_stateid.other[0] = delegstateidp->other[0]
-			    = clp->lc_clientid.lval[0];
-			new_deleg->ls_stateid.other[1] = delegstateidp->other[1]
-			    = clp->lc_clientid.lval[1];
-			new_deleg->ls_stateid.other[2] = delegstateidp->other[2]
-			    = nfsrv_nextstateindex(clp);
-			new_deleg->ls_flags = (NFSLCK_DELEGWRITE |
-			    NFSLCK_READACCESS | NFSLCK_WRITEACCESS);
-			*rflagsp |= NFSV4OPEN_WRITEDELEGATE;
-			new_deleg->ls_uid = new_stp->ls_uid;
-			new_deleg->ls_lfp = lfp;
-			new_deleg->ls_clp = clp;
-			new_deleg->ls_filerev = filerev;
-			new_deleg->ls_compref = nd->nd_compref;
-			new_deleg->ls_lastrecall = 0;
-			nfsrv_writedelegcnt++;
-			LIST_INSERT_HEAD(&lfp->lf_deleg, new_deleg, ls_file);
-			LIST_INSERT_HEAD(NFSSTATEHASH(clp,
-			    new_deleg->ls_stateid), new_deleg, ls_hash);
-			LIST_INSERT_HEAD(&clp->lc_deleg, new_deleg, ls_list);
-			new_deleg = NULL;
-			NFSD_VNET(nfsstatsv1_p)->srvdelegates++;
-			nfsrv_openpluslock++;
-			nfsrv_delegatecnt++;
-		    }
+		    nfsrv_issuedelegation(vp, clp, nd, delegate, writedeleg,
+			readonly, filerev, NFSVNO_EXRDONLY(exp), &new_deleg,
+			new_stp, lfp, rflagsp, delegstateidp);
 		} else {
 		    new_open->ls_stateid.seqid = 1;
 		    new_open->ls_stateid.other[0] = clp->lc_clientid.lval[0];
@@ -3269,52 +3245,9 @@ tryagain:
 		    /*
 		     * This is where we can choose to issue a delegation.
 		     */
-		    if ((new_stp->ls_flags & NFSLCK_WANTNODELEG) != 0)
-			*rflagsp |= NFSV4OPEN_WDNOTWANTED;
-		    else if (nfsrv_issuedelegs == 0)
-			*rflagsp |= NFSV4OPEN_WDSUPPFTYPE;
-		    else if (NFSRV_V4DELEGLIMIT(nfsrv_delegatecnt))
-			*rflagsp |= NFSV4OPEN_WDRESOURCE;
-		    else if (delegate == 0 || (writedeleg == 0 &&
-			readonly == 0) || !NFSVNO_DELEGOK(vp) ||
-			(clp->lc_flags & (LCL_CALLBACKSON | LCL_CBDOWN)) !=
-			 LCL_CALLBACKSON)
-			*rflagsp |= NFSV4OPEN_WDCONTENTION;
-		    else {
-			new_deleg->ls_stateid.seqid = delegstateidp->seqid = 1;
-			new_deleg->ls_stateid.other[0] = delegstateidp->other[0]
-			    = clp->lc_clientid.lval[0];
-			new_deleg->ls_stateid.other[1] = delegstateidp->other[1]
-			    = clp->lc_clientid.lval[1];
-			new_deleg->ls_stateid.other[2] = delegstateidp->other[2]
-			    = nfsrv_nextstateindex(clp);
-			if (writedeleg && !NFSVNO_EXRDONLY(exp) &&
-			    (nfsrv_writedelegifpos || !readonly) &&
-			    (new_stp->ls_flags & NFSLCK_WANTRDELEG) == 0) {
-			    new_deleg->ls_flags = (NFSLCK_DELEGWRITE |
-				NFSLCK_READACCESS | NFSLCK_WRITEACCESS);
-			    *rflagsp |= NFSV4OPEN_WRITEDELEGATE;
-			    nfsrv_writedelegcnt++;
-			} else {
-			    new_deleg->ls_flags = (NFSLCK_DELEGREAD |
-				NFSLCK_READACCESS);
-			    *rflagsp |= NFSV4OPEN_READDELEGATE;
-			}
-			new_deleg->ls_uid = new_stp->ls_uid;
-			new_deleg->ls_lfp = lfp;
-			new_deleg->ls_clp = clp;
-			new_deleg->ls_filerev = filerev;
-			new_deleg->ls_compref = nd->nd_compref;
-			new_deleg->ls_lastrecall = 0;
-			LIST_INSERT_HEAD(&lfp->lf_deleg, new_deleg, ls_file);
-			LIST_INSERT_HEAD(NFSSTATEHASH(clp,
-			    new_deleg->ls_stateid), new_deleg, ls_hash);
-			LIST_INSERT_HEAD(&clp->lc_deleg, new_deleg, ls_list);
-			new_deleg = NULL;
-			NFSD_VNET(nfsstatsv1_p)->srvdelegates++;
-			nfsrv_openpluslock++;
-			nfsrv_delegatecnt++;
-		    }
+		    nfsrv_issuedelegation(vp, clp, nd, delegate, writedeleg,
+			readonly, filerev, NFSVNO_EXRDONLY(exp), &new_deleg,
+			new_stp, lfp, rflagsp, delegstateidp);
 		}
 	} else {
 		/*
@@ -3337,78 +3270,28 @@ tryagain:
 		if (new_stp->ls_flags & NFSLCK_RECLAIM) {
 			new_stp->ls_flags = 0;
 		} else if ((nd->nd_flag & ND_NFSV41) != 0) {
-			/* NFSv4.1 never needs confirmation. */
-			new_stp->ls_flags = 0;
+		    /*
+		     * This is where we can choose to issue a delegation.
+		     */
+		    nfsrv_issuedelegation(vp, clp, nd, delegate, writedeleg,
+			readonly, filerev, NFSVNO_EXRDONLY(exp), &new_deleg,
+			new_stp, lfp, rflagsp, delegstateidp);
+		    /* NFSv4.1 never needs confirmation. */
+		    new_stp->ls_flags = 0;
 
-			/*
-			 * This is where we can choose to issue a delegation.
-			 */
-			if (delegate && nfsrv_issuedelegs &&
-			    (writedeleg || readonly) &&
-			    (clp->lc_flags & (LCL_CALLBACKSON | LCL_CBDOWN)) ==
-			     LCL_CALLBACKSON &&
-			    !NFSRV_V4DELEGLIMIT(nfsrv_delegatecnt) &&
-			    NFSVNO_DELEGOK(vp) &&
-			    ((nd->nd_flag & ND_NFSV41) == 0 ||
-			     (new_stp->ls_flags & NFSLCK_WANTNODELEG) == 0)) {
-				new_deleg->ls_stateid.seqid =
-				    delegstateidp->seqid = 1;
-				new_deleg->ls_stateid.other[0] =
-				    delegstateidp->other[0]
-				    = clp->lc_clientid.lval[0];
-				new_deleg->ls_stateid.other[1] =
-				    delegstateidp->other[1]
-				    = clp->lc_clientid.lval[1];
-				new_deleg->ls_stateid.other[2] =
-				    delegstateidp->other[2]
-				    = nfsrv_nextstateindex(clp);
-				if (writedeleg && !NFSVNO_EXRDONLY(exp) &&
-				    (nfsrv_writedelegifpos || !readonly) &&
-				    ((nd->nd_flag & ND_NFSV41) == 0 ||
-				     (new_stp->ls_flags & NFSLCK_WANTRDELEG) ==
-				     0)) {
-					new_deleg->ls_flags =
-					    (NFSLCK_DELEGWRITE |
-					     NFSLCK_READACCESS |
-					     NFSLCK_WRITEACCESS);
-					*rflagsp |= NFSV4OPEN_WRITEDELEGATE;
-					nfsrv_writedelegcnt++;
-				} else {
-					new_deleg->ls_flags =
-					    (NFSLCK_DELEGREAD |
-					     NFSLCK_READACCESS);
-					*rflagsp |= NFSV4OPEN_READDELEGATE;
-				}
-				new_deleg->ls_uid = new_stp->ls_uid;
-				new_deleg->ls_lfp = lfp;
-				new_deleg->ls_clp = clp;
-				new_deleg->ls_filerev = filerev;
-				new_deleg->ls_compref = nd->nd_compref;
-				new_deleg->ls_lastrecall = 0;
-				LIST_INSERT_HEAD(&lfp->lf_deleg, new_deleg,
-				    ls_file);
-				LIST_INSERT_HEAD(NFSSTATEHASH(clp,
-				    new_deleg->ls_stateid), new_deleg, ls_hash);
-				LIST_INSERT_HEAD(&clp->lc_deleg, new_deleg,
-				    ls_list);
-				new_deleg = NULL;
-				NFSD_VNET(nfsstatsv1_p)->srvdelegates++;
-				nfsrv_openpluslock++;
-				nfsrv_delegatecnt++;
-			}
-			/*
-			 * Since NFSv4.1 never does an OpenConfirm, the first
-			 * open state will be acquired here.
-			 */
-			if (!(clp->lc_flags & LCL_STAMPEDSTABLE)) {
-				clp->lc_flags |= LCL_STAMPEDSTABLE;
-				len = clp->lc_idlen;
-				NFSBCOPY(clp->lc_id, clidp, len);
-				gotstate = 1;
-			}
+		    /*
+		     * Since NFSv4.1 never does an OpenConfirm, the first
+		     * open state will be acquired here.
+		     */
+		    if (!(clp->lc_flags & LCL_STAMPEDSTABLE)) {
+			clp->lc_flags |= LCL_STAMPEDSTABLE;
+			len = clp->lc_idlen;
+			NFSBCOPY(clp->lc_id, clidp, len);
+			gotstate = 1;
+		    }
 		} else {
-			*rflagsp |= NFSV4OPEN_RESULTCONFIRM;
-			new_stp->ls_flags = NFSLCK_NEEDSCONFIRM;
+		    *rflagsp |= NFSV4OPEN_RESULTCONFIRM;
+		    new_stp->ls_flags = NFSLCK_NEEDSCONFIRM;
 		}
 		nfsrvd_refcache(new_stp->ls_op);
 		new_stp->ls_noopens = 0;
@@ -5179,6 +5062,11 @@ nfsrv_markreclaim(struct nfsclient *clp)
 	 * Now, just set the flag.
 	 */
 	sp->nst_flag |= NFSNST_RECLAIMED;
+
+	/*
+	 * Free up any old delegations.
+	 */
+	nfsrv_freedeleglist(&clp->lc_olddeleg);
 }
 
 /*
@@ -8943,3 +8831,86 @@ nfsrv_checkmachcred(int op, struct nfsrv_descript *nd, struct nfsclient *clp)
 		return (0);
 	return (NFSERR_AUTHERR | AUTH_TOOWEAK);
 }
+
+/*
+ * Issue a delegation and, optionally set rflagsp for why not.
+ */
+static void
+nfsrv_issuedelegation(struct vnode *vp, struct nfsclient *clp,
+    struct nfsrv_descript *nd, int delegate, int writedeleg, int readonly,
+    u_quad_t filerev, uint64_t rdonly, struct nfsstate **new_delegp,
+    struct nfsstate *new_stp, struct nfslockfile *lfp, uint32_t *rflagsp,
+    nfsv4stateid_t *delegstateidp)
+{
+	struct nfsstate *up_deleg, *new_deleg;
+
+	new_deleg = *new_delegp;
+	up_deleg = LIST_FIRST(&lfp->lf_deleg);
+	if ((new_stp->ls_flags & NFSLCK_WANTNODELEG) != 0)
+		*rflagsp |= NFSV4OPEN_WDNOTWANTED;
+	else if (nfsrv_issuedelegs == 0)
+		*rflagsp |= NFSV4OPEN_WDSUPPFTYPE;
+	else if (NFSRV_V4DELEGLIMIT(nfsrv_delegatecnt))
+		*rflagsp |= NFSV4OPEN_WDRESOURCE;
+	else if (delegate == 0 || !NFSVNO_DELEGOK(vp) ||
+	    (writedeleg == 0 && (readonly == 0 ||
+	    (new_stp->ls_flags & NFSLCK_WANTWDELEG) != 0)) ||
+	    (clp->lc_flags & (LCL_CALLBACKSON | LCL_CBDOWN)) !=
+	     LCL_CALLBACKSON) {
+		/* Is this a downgrade attempt? */
+		if (up_deleg != NULL && up_deleg->ls_clp == clp &&
+		    (up_deleg->ls_flags & NFSLCK_DELEGWRITE) != 0 &&
+		    (new_stp->ls_flags & NFSLCK_WANTRDELEG) != 0)
+			*rflagsp |= NFSV4OPEN_WDNOTSUPPDOWNGRADE;
+		else
+			*rflagsp |= NFSV4OPEN_WDCONTENTION;
+	} else if (up_deleg != NULL &&
+	    (up_deleg->ls_flags & NFSLCK_DELEGREAD) != 0 &&
+	    (new_stp->ls_flags & NFSLCK_WANTWDELEG) != 0) {
+		/* This is an atomic upgrade. */
+		up_deleg->ls_stateid.seqid++;
+		delegstateidp->seqid = up_deleg->ls_stateid.seqid;
+		delegstateidp->other[0] = up_deleg->ls_stateid.other[0];
+		delegstateidp->other[1] = up_deleg->ls_stateid.other[1];
+		delegstateidp->other[2] = up_deleg->ls_stateid.other[2];
+		up_deleg->ls_flags = (NFSLCK_DELEGWRITE |
+		    NFSLCK_READACCESS | NFSLCK_WRITEACCESS);
+		*rflagsp |= NFSV4OPEN_WRITEDELEGATE;
+		nfsrv_writedelegcnt++;
+	} else {
+		new_deleg->ls_stateid.seqid = delegstateidp->seqid = 1;
+		new_deleg->ls_stateid.other[0] = delegstateidp->other[0]
+		    = clp->lc_clientid.lval[0];
+		new_deleg->ls_stateid.other[1] = delegstateidp->other[1]
+		    = clp->lc_clientid.lval[1];
+		new_deleg->ls_stateid.other[2] = delegstateidp->other[2]
+		    = nfsrv_nextstateindex(clp);
+		if (writedeleg && !rdonly &&
+		    (nfsrv_writedelegifpos || !readonly) &&
+		    (new_stp->ls_flags & (NFSLCK_WANTRDELEG |
+		     NFSLCK_WANTWDELEG)) != NFSLCK_WANTRDELEG) {
+			new_deleg->ls_flags = (NFSLCK_DELEGWRITE |
+			    NFSLCK_READACCESS | NFSLCK_WRITEACCESS);
+			*rflagsp |= NFSV4OPEN_WRITEDELEGATE;
+			nfsrv_writedelegcnt++;
+		} else {
+			new_deleg->ls_flags = (NFSLCK_DELEGREAD |
+			    NFSLCK_READACCESS);
+			*rflagsp |= NFSV4OPEN_READDELEGATE;
+		}
+		new_deleg->ls_uid = new_stp->ls_uid;
+		new_deleg->ls_lfp = lfp;
+		new_deleg->ls_clp = clp;
+		new_deleg->ls_filerev = filerev;
+		new_deleg->ls_compref = nd->nd_compref;
+		new_deleg->ls_lastrecall = 0;
+		LIST_INSERT_HEAD(&lfp->lf_deleg, new_deleg, ls_file);
+		LIST_INSERT_HEAD(NFSSTATEHASH(clp, new_deleg->ls_stateid),
+		    new_deleg, ls_hash);
+		LIST_INSERT_HEAD(&clp->lc_deleg, new_deleg, ls_list);
+		*new_delegp = NULL;
+		NFSD_VNET(nfsstatsv1_p)->srvdelegates++;
+		nfsrv_openpluslock++;
+		nfsrv_delegatecnt++;
+	}
+}