linux-nfs.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v2 00/20] Delegation bugfixes
@ 2019-10-31 22:40 Trond Myklebust
  2019-10-31 22:40 ` [PATCH v2 01/20] NFSv4: Don't allow a cached open with a revoked delegation Trond Myklebust
  0 siblings, 1 reply; 21+ messages in thread
From: Trond Myklebust @ 2019-10-31 22:40 UTC (permalink / raw)
  To: linux-nfs

The following patchset fixes up a number of issues with delegations,
but mainly attempts to fix a race condition between open and
delegreturn, where the open and the delegreturn get re-ordered so
that the delegreturn ends up revoking the delegation that was returned
by the open.
The root cause is that in certain circumstances, we may currently end
up freeing the delegation from delegreturn, so when we later receive
the reply to the open, we've lost track of the fact that the seqid
predates the one that was returned.

This patchset fixes that case by ensuring that we always keep track
of the last delegation stateid that was returned for any given inode.
That way, if we later see a delegation stateid with the same opaque
field, but an older seqid, we know we cannot trust it, and so we
ask to replay the OPEN compound.

Trond Myklebust (20):
  NFSv4: Don't allow a cached open with a revoked delegation
  NFS: Fix an RCU lock leak in nfs4_refresh_delegation_stateid()
  NFSv4: Fix delegation handling in update_open_stateid()
  NFSv4: nfs4_callback_getattr() should ignore revoked delegations
  NFSv4: Delegation recalls should not find revoked delegations
  NFSv4: fail nfs4_refresh_delegation_stateid() when the delegation was
    revoked
  NFS: Rename nfs_inode_return_delegation_noreclaim()
  NFSv4: Don't remove the delegation from the super_list more than once
  NFSv4: Hold the delegation spinlock when updating the seqid
  NFSv4: Clear the NFS_DELEGATION_REVOKED flag in
    nfs_update_inplace_delegation()
  NFSv4: Update the stateid seqid in nfs_revoke_delegation()
  NFSv4: Revoke the delegation on success in nfs4_delegreturn_done()
  NFSv4: Ignore requests to return the delegation if it was revoked
  NFSv4: Don't reclaim delegations that have been returned or revoked
  NFSv4: nfs4_return_incompatible_delegation() should check delegation
    validity
  NFSv4: Fix nfs4_inode_make_writeable()
  NFS: nfs_inode_find_state_and_recover() fix stateid matching
  NFSv4: Fix races between open and delegreturn
  NFSv4: Handle NFS4ERR_OLD_STATEID in delegreturn
  NFSv4: Don't retry the GETATTR on old stateid in
    nfs4_delegreturn_done()

 fs/nfs/callback_proc.c |   4 +-
 fs/nfs/delegation.c    | 170 +++++++++++++++++++++++++++++------------
 fs/nfs/delegation.h    |   4 +-
 fs/nfs/nfs4_fs.h       |   6 ++
 fs/nfs/nfs4proc.c      |  25 +++---
 fs/nfs/nfs4state.c     |   7 +-
 fs/nfs/nfs4super.c     |   4 +-
 7 files changed, 151 insertions(+), 69 deletions(-)

-- 
2.23.0


^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH v2 01/20] NFSv4: Don't allow a cached open with a revoked delegation
  2019-10-31 22:40 [PATCH v2 00/20] Delegation bugfixes Trond Myklebust
@ 2019-10-31 22:40 ` Trond Myklebust
  2019-10-31 22:40   ` [PATCH v2 02/20] NFS: Fix an RCU lock leak in nfs4_refresh_delegation_stateid() Trond Myklebust
  0 siblings, 1 reply; 21+ messages in thread
From: Trond Myklebust @ 2019-10-31 22:40 UTC (permalink / raw)
  To: linux-nfs

If the delegation is marked as being revoked, we must not use it
for cached opens.

Fixes: 869f9dfa4d6d ("NFSv4: Fix races between nfs_remove_bad_delegation() and delegation return")
Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com>
---
 fs/nfs/delegation.c | 10 ++++++++++
 fs/nfs/delegation.h |  1 +
 fs/nfs/nfs4proc.c   |  7 ++-----
 3 files changed, 13 insertions(+), 5 deletions(-)

diff --git a/fs/nfs/delegation.c b/fs/nfs/delegation.c
index 071b90a45933..ccdfb5f98f35 100644
--- a/fs/nfs/delegation.c
+++ b/fs/nfs/delegation.c
@@ -53,6 +53,16 @@ nfs4_is_valid_delegation(const struct nfs_delegation *delegation,
 	return false;
 }
 
+struct nfs_delegation *nfs4_get_valid_delegation(const struct inode *inode)
+{
+	struct nfs_delegation *delegation;
+
+	delegation = rcu_dereference(NFS_I(inode)->delegation);
+	if (nfs4_is_valid_delegation(delegation, 0))
+		return delegation;
+	return NULL;
+}
+
 static int
 nfs4_do_check_delegation(struct inode *inode, fmode_t flags, bool mark)
 {
diff --git a/fs/nfs/delegation.h b/fs/nfs/delegation.h
index 9eb87ae4c982..8b14d441e699 100644
--- a/fs/nfs/delegation.h
+++ b/fs/nfs/delegation.h
@@ -68,6 +68,7 @@ int nfs4_lock_delegation_recall(struct file_lock *fl, struct nfs4_state *state,
 bool nfs4_copy_delegation_stateid(struct inode *inode, fmode_t flags, nfs4_stateid *dst, const struct cred **cred);
 bool nfs4_refresh_delegation_stateid(nfs4_stateid *dst, struct inode *inode);
 
+struct nfs_delegation *nfs4_get_valid_delegation(const struct inode *inode);
 void nfs_mark_delegation_referenced(struct nfs_delegation *delegation);
 int nfs4_have_delegation(struct inode *inode, fmode_t flags);
 int nfs4_check_delegation(struct inode *inode, fmode_t flags);
diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c
index ab8ca20fd579..caacf5e7f5e1 100644
--- a/fs/nfs/nfs4proc.c
+++ b/fs/nfs/nfs4proc.c
@@ -1440,8 +1440,6 @@ static int can_open_delegated(struct nfs_delegation *delegation, fmode_t fmode,
 		return 0;
 	if ((delegation->type & fmode) != fmode)
 		return 0;
-	if (test_bit(NFS_DELEGATION_RETURNING, &delegation->flags))
-		return 0;
 	switch (claim) {
 	case NFS4_OPEN_CLAIM_NULL:
 	case NFS4_OPEN_CLAIM_FH:
@@ -1810,7 +1808,6 @@ static void nfs4_return_incompatible_delegation(struct inode *inode, fmode_t fmo
 static struct nfs4_state *nfs4_try_open_cached(struct nfs4_opendata *opendata)
 {
 	struct nfs4_state *state = opendata->state;
-	struct nfs_inode *nfsi = NFS_I(state->inode);
 	struct nfs_delegation *delegation;
 	int open_mode = opendata->o_arg.open_flags;
 	fmode_t fmode = opendata->o_arg.fmode;
@@ -1827,7 +1824,7 @@ static struct nfs4_state *nfs4_try_open_cached(struct nfs4_opendata *opendata)
 		}
 		spin_unlock(&state->owner->so_lock);
 		rcu_read_lock();
-		delegation = rcu_dereference(nfsi->delegation);
+		delegation = nfs4_get_valid_delegation(state->inode);
 		if (!can_open_delegated(delegation, fmode, claim)) {
 			rcu_read_unlock();
 			break;
@@ -2371,7 +2368,7 @@ static void nfs4_open_prepare(struct rpc_task *task, void *calldata)
 					data->o_arg.open_flags, claim))
 			goto out_no_action;
 		rcu_read_lock();
-		delegation = rcu_dereference(NFS_I(data->state->inode)->delegation);
+		delegation = nfs4_get_valid_delegation(data->state->inode);
 		if (can_open_delegated(delegation, data->o_arg.fmode, claim))
 			goto unlock_no_action;
 		rcu_read_unlock();
-- 
2.23.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v2 02/20] NFS: Fix an RCU lock leak in nfs4_refresh_delegation_stateid()
  2019-10-31 22:40 ` [PATCH v2 01/20] NFSv4: Don't allow a cached open with a revoked delegation Trond Myklebust
@ 2019-10-31 22:40   ` Trond Myklebust
  2019-10-31 22:40     ` [PATCH v2 03/20] NFSv4: Fix delegation handling in update_open_stateid() Trond Myklebust
  0 siblings, 1 reply; 21+ messages in thread
From: Trond Myklebust @ 2019-10-31 22:40 UTC (permalink / raw)
  To: linux-nfs

A typo in nfs4_refresh_delegation_stateid() means we're leaking an
RCU lock, and always returning a value of 'false'. As the function
description states, we were always supposed to return 'true' if a
matching delegation was found.

Fixes: 12f275cdd163 ("NFSv4: Retry CLOSE and DELEGRETURN on NFS4ERR_OLD_STATEID.")
Cc: stable@vger.kernel.org # v4.15+
Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com>
---
 fs/nfs/delegation.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/fs/nfs/delegation.c b/fs/nfs/delegation.c
index ccdfb5f98f35..af549d70ec50 100644
--- a/fs/nfs/delegation.c
+++ b/fs/nfs/delegation.c
@@ -1191,7 +1191,7 @@ bool nfs4_refresh_delegation_stateid(nfs4_stateid *dst, struct inode *inode)
 	if (delegation != NULL &&
 	    nfs4_stateid_match_other(dst, &delegation->stateid)) {
 		dst->seqid = delegation->stateid.seqid;
-		return ret;
+		ret = true;
 	}
 	rcu_read_unlock();
 out:
-- 
2.23.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v2 03/20] NFSv4: Fix delegation handling in update_open_stateid()
  2019-10-31 22:40   ` [PATCH v2 02/20] NFS: Fix an RCU lock leak in nfs4_refresh_delegation_stateid() Trond Myklebust
@ 2019-10-31 22:40     ` Trond Myklebust
  2019-10-31 22:40       ` [PATCH v2 04/20] NFSv4: nfs4_callback_getattr() should ignore revoked delegations Trond Myklebust
  0 siblings, 1 reply; 21+ messages in thread
From: Trond Myklebust @ 2019-10-31 22:40 UTC (permalink / raw)
  To: linux-nfs

If the delegation is marked as being revoked, then don't use it in
the open state structure.

Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com>
---
 fs/nfs/nfs4proc.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c
index caacf5e7f5e1..217885e32852 100644
--- a/fs/nfs/nfs4proc.c
+++ b/fs/nfs/nfs4proc.c
@@ -1737,7 +1737,7 @@ static int update_open_stateid(struct nfs4_state *state,
 		ret = 1;
 	}
 
-	deleg_cur = rcu_dereference(nfsi->delegation);
+	deleg_cur = nfs4_get_valid_delegation(state->inode);
 	if (deleg_cur == NULL)
 		goto no_delegation;
 
@@ -1749,7 +1749,7 @@ static int update_open_stateid(struct nfs4_state *state,
 
 	if (delegation == NULL)
 		delegation = &deleg_cur->stateid;
-	else if (!nfs4_stateid_match(&deleg_cur->stateid, delegation))
+	else if (!nfs4_stateid_match_other(&deleg_cur->stateid, delegation))
 		goto no_delegation_unlock;
 
 	nfs_mark_delegation_referenced(deleg_cur);
-- 
2.23.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v2 04/20] NFSv4: nfs4_callback_getattr() should ignore revoked delegations
  2019-10-31 22:40     ` [PATCH v2 03/20] NFSv4: Fix delegation handling in update_open_stateid() Trond Myklebust
@ 2019-10-31 22:40       ` Trond Myklebust
  2019-10-31 22:40         ` [PATCH v2 05/20] NFSv4: Delegation recalls should not find " Trond Myklebust
  0 siblings, 1 reply; 21+ messages in thread
From: Trond Myklebust @ 2019-10-31 22:40 UTC (permalink / raw)
  To: linux-nfs

If the delegation has been revoked, ignore it.

Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com>
---
 fs/nfs/callback_proc.c | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/fs/nfs/callback_proc.c b/fs/nfs/callback_proc.c
index db3e7771e597..cd4c6bc81cae 100644
--- a/fs/nfs/callback_proc.c
+++ b/fs/nfs/callback_proc.c
@@ -26,7 +26,6 @@ __be32 nfs4_callback_getattr(void *argp, void *resp,
 	struct cb_getattrargs *args = argp;
 	struct cb_getattrres *res = resp;
 	struct nfs_delegation *delegation;
-	struct nfs_inode *nfsi;
 	struct inode *inode;
 
 	res->status = htonl(NFS4ERR_OP_NOT_IN_SESSION);
@@ -47,9 +46,8 @@ __be32 nfs4_callback_getattr(void *argp, void *resp,
 				-ntohl(res->status));
 		goto out;
 	}
-	nfsi = NFS_I(inode);
 	rcu_read_lock();
-	delegation = rcu_dereference(nfsi->delegation);
+	delegation = nfs4_get_valid_delegation(inode);
 	if (delegation == NULL || (delegation->type & FMODE_WRITE) == 0)
 		goto out_iput;
 	res->size = i_size_read(inode);
-- 
2.23.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v2 05/20] NFSv4: Delegation recalls should not find revoked delegations
  2019-10-31 22:40       ` [PATCH v2 04/20] NFSv4: nfs4_callback_getattr() should ignore revoked delegations Trond Myklebust
@ 2019-10-31 22:40         ` Trond Myklebust
  2019-10-31 22:40           ` [PATCH v2 06/20] NFSv4: fail nfs4_refresh_delegation_stateid() when the delegation was revoked Trond Myklebust
  0 siblings, 1 reply; 21+ messages in thread
From: Trond Myklebust @ 2019-10-31 22:40 UTC (permalink / raw)
  To: linux-nfs

If we're processsing a delegation recall, ignore the delegations that
have already been revoked or returned.

Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com>
---
 fs/nfs/delegation.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/fs/nfs/delegation.c b/fs/nfs/delegation.c
index af549d70ec50..c34bb81d37e2 100644
--- a/fs/nfs/delegation.c
+++ b/fs/nfs/delegation.c
@@ -840,7 +840,7 @@ int nfs_async_inode_return_delegation(struct inode *inode,
 	struct nfs_delegation *delegation;
 
 	rcu_read_lock();
-	delegation = rcu_dereference(NFS_I(inode)->delegation);
+	delegation = nfs4_get_valid_delegation(inode);
 	if (delegation == NULL)
 		goto out_enoent;
 	if (stateid != NULL &&
@@ -866,6 +866,7 @@ nfs_delegation_find_inode_server(struct nfs_server *server,
 	list_for_each_entry_rcu(delegation, &server->delegations, super_list) {
 		spin_lock(&delegation->lock);
 		if (delegation->inode != NULL &&
+		    !test_bit(NFS_DELEGATION_REVOKED, &delegation->flags) &&
 		    nfs_compare_fh(fhandle, &NFS_I(delegation->inode)->fh) == 0) {
 			freeme = igrab(delegation->inode);
 			if (freeme && nfs_sb_active(freeme->i_sb))
-- 
2.23.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v2 06/20] NFSv4: fail nfs4_refresh_delegation_stateid() when the delegation was revoked
  2019-10-31 22:40         ` [PATCH v2 05/20] NFSv4: Delegation recalls should not find " Trond Myklebust
@ 2019-10-31 22:40           ` Trond Myklebust
  2019-10-31 22:40             ` [PATCH v2 07/20] NFS: Rename nfs_inode_return_delegation_noreclaim() Trond Myklebust
  0 siblings, 1 reply; 21+ messages in thread
From: Trond Myklebust @ 2019-10-31 22:40 UTC (permalink / raw)
  To: linux-nfs

If the delegation was revoked, we don't want to retry the delegreturn.

Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com>
---
 fs/nfs/delegation.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/fs/nfs/delegation.c b/fs/nfs/delegation.c
index c34bb81d37e2..630167e243be 100644
--- a/fs/nfs/delegation.c
+++ b/fs/nfs/delegation.c
@@ -1190,7 +1190,8 @@ bool nfs4_refresh_delegation_stateid(nfs4_stateid *dst, struct inode *inode)
 	rcu_read_lock();
 	delegation = rcu_dereference(NFS_I(inode)->delegation);
 	if (delegation != NULL &&
-	    nfs4_stateid_match_other(dst, &delegation->stateid)) {
+	    nfs4_stateid_match_other(dst, &delegation->stateid) &&
+	    !test_bit(NFS_DELEGATION_REVOKED, &delegation->flags)) {
 		dst->seqid = delegation->stateid.seqid;
 		ret = true;
 	}
-- 
2.23.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v2 07/20] NFS: Rename nfs_inode_return_delegation_noreclaim()
  2019-10-31 22:40           ` [PATCH v2 06/20] NFSv4: fail nfs4_refresh_delegation_stateid() when the delegation was revoked Trond Myklebust
@ 2019-10-31 22:40             ` Trond Myklebust
  2019-10-31 22:40               ` [PATCH v2 08/20] NFSv4: Don't remove the delegation from the super_list more than once Trond Myklebust
  0 siblings, 1 reply; 21+ messages in thread
From: Trond Myklebust @ 2019-10-31 22:40 UTC (permalink / raw)
  To: linux-nfs

Rename nfs_inode_return_delegation_noreclaim() to
nfs_inode_evict_delegation(), which better describes what it
does.

Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com>
---
 fs/nfs/delegation.c | 11 +++++++----
 fs/nfs/delegation.h |  2 +-
 fs/nfs/nfs4super.c  |  4 ++--
 3 files changed, 10 insertions(+), 7 deletions(-)

diff --git a/fs/nfs/delegation.c b/fs/nfs/delegation.c
index 630167e243be..0c9339d559f5 100644
--- a/fs/nfs/delegation.c
+++ b/fs/nfs/delegation.c
@@ -585,19 +585,22 @@ int nfs_client_return_marked_delegations(struct nfs_client *clp)
 }
 
 /**
- * nfs_inode_return_delegation_noreclaim - return delegation, don't reclaim opens
+ * nfs_inode_evict_delegation - return delegation, don't reclaim opens
  * @inode: inode to process
  *
  * Does not protect against delegation reclaims, therefore really only safe
- * to be called from nfs4_clear_inode().
+ * to be called from nfs4_clear_inode(). Guaranteed to always free
+ * the delegation structure.
  */
-void nfs_inode_return_delegation_noreclaim(struct inode *inode)
+void nfs_inode_evict_delegation(struct inode *inode)
 {
 	struct nfs_delegation *delegation;
 
 	delegation = nfs_inode_detach_delegation(inode);
-	if (delegation != NULL)
+	if (delegation != NULL) {
+		set_bit(NFS_DELEGATION_INODE_FREEING, &delegation->flags);
 		nfs_do_return_delegation(inode, delegation, 1);
+	}
 }
 
 /**
diff --git a/fs/nfs/delegation.h b/fs/nfs/delegation.h
index 8b14d441e699..74b7fb601365 100644
--- a/fs/nfs/delegation.h
+++ b/fs/nfs/delegation.h
@@ -43,7 +43,7 @@ void nfs_inode_reclaim_delegation(struct inode *inode, const struct cred *cred,
 		fmode_t type, const nfs4_stateid *stateid, unsigned long pagemod_limit);
 int nfs4_inode_return_delegation(struct inode *inode);
 int nfs_async_inode_return_delegation(struct inode *inode, const nfs4_stateid *stateid);
-void nfs_inode_return_delegation_noreclaim(struct inode *inode);
+void nfs_inode_evict_delegation(struct inode *inode);
 
 struct inode *nfs_delegation_find_inode(struct nfs_client *clp, const struct nfs_fh *fhandle);
 void nfs_server_return_all_delegations(struct nfs_server *);
diff --git a/fs/nfs/nfs4super.c b/fs/nfs/nfs4super.c
index 04c57066a11a..2c9cbade561a 100644
--- a/fs/nfs/nfs4super.c
+++ b/fs/nfs/nfs4super.c
@@ -92,8 +92,8 @@ static void nfs4_evict_inode(struct inode *inode)
 {
 	truncate_inode_pages_final(&inode->i_data);
 	clear_inode(inode);
-	/* If we are holding a delegation, return it! */
-	nfs_inode_return_delegation_noreclaim(inode);
+	/* If we are holding a delegation, return and free it */
+	nfs_inode_evict_delegation(inode);
 	/* Note that above delegreturn would trigger pnfs return-on-close */
 	pnfs_return_layout(inode);
 	pnfs_destroy_layout(NFS_I(inode));
-- 
2.23.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v2 08/20] NFSv4: Don't remove the delegation from the super_list more than once
  2019-10-31 22:40             ` [PATCH v2 07/20] NFS: Rename nfs_inode_return_delegation_noreclaim() Trond Myklebust
@ 2019-10-31 22:40               ` Trond Myklebust
  2019-10-31 22:40                 ` [PATCH v2 09/20] NFSv4: Hold the delegation spinlock when updating the seqid Trond Myklebust
  0 siblings, 1 reply; 21+ messages in thread
From: Trond Myklebust @ 2019-10-31 22:40 UTC (permalink / raw)
  To: linux-nfs

Add a check to ensure that we haven't already removed the delegation
from the inode after we take all the relevant locks.

Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com>
---
 fs/nfs/delegation.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/fs/nfs/delegation.c b/fs/nfs/delegation.c
index 0c9339d559f5..e80419a63fb5 100644
--- a/fs/nfs/delegation.c
+++ b/fs/nfs/delegation.c
@@ -298,6 +298,10 @@ nfs_detach_delegation_locked(struct nfs_inode *nfsi,
 		return NULL;
 
 	spin_lock(&delegation->lock);
+	if (!delegation->inode) {
+		spin_unlock(&delegation->lock);
+		return NULL;
+	}
 	set_bit(NFS_DELEGATION_RETURNING, &delegation->flags);
 	list_del_rcu(&delegation->super_list);
 	delegation->inode = NULL;
-- 
2.23.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v2 09/20] NFSv4: Hold the delegation spinlock when updating the seqid
  2019-10-31 22:40               ` [PATCH v2 08/20] NFSv4: Don't remove the delegation from the super_list more than once Trond Myklebust
@ 2019-10-31 22:40                 ` Trond Myklebust
  2019-10-31 22:40                   ` [PATCH v2 10/20] NFSv4: Clear the NFS_DELEGATION_REVOKED flag in nfs_update_inplace_delegation() Trond Myklebust
  0 siblings, 1 reply; 21+ messages in thread
From: Trond Myklebust @ 2019-10-31 22:40 UTC (permalink / raw)
  To: linux-nfs

Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com>
---
 fs/nfs/delegation.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/fs/nfs/delegation.c b/fs/nfs/delegation.c
index e80419a63fb5..7ebeb57cb597 100644
--- a/fs/nfs/delegation.c
+++ b/fs/nfs/delegation.c
@@ -387,8 +387,10 @@ int nfs_inode_set_delegation(struct inode *inode, const struct cred *cred,
 		/* Is this an update of the existing delegation? */
 		if (nfs4_stateid_match_other(&old_delegation->stateid,
 					&delegation->stateid)) {
+			spin_lock(&old_delegation->lock);
 			nfs_update_inplace_delegation(old_delegation,
 					delegation);
+			spin_unlock(&old_delegation->lock);
 			goto out;
 		}
 		/*
-- 
2.23.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v2 10/20] NFSv4: Clear the NFS_DELEGATION_REVOKED flag in nfs_update_inplace_delegation()
  2019-10-31 22:40                 ` [PATCH v2 09/20] NFSv4: Hold the delegation spinlock when updating the seqid Trond Myklebust
@ 2019-10-31 22:40                   ` Trond Myklebust
  2019-10-31 22:40                     ` [PATCH v2 11/20] NFSv4: Update the stateid seqid in nfs_revoke_delegation() Trond Myklebust
  0 siblings, 1 reply; 21+ messages in thread
From: Trond Myklebust @ 2019-10-31 22:40 UTC (permalink / raw)
  To: linux-nfs

If the server sent us a new delegation stateid that is more recent than
the one that got revoked, then clear the NFS_DELEGATION_REVOKED flag.

Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com>
---
 fs/nfs/delegation.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/fs/nfs/delegation.c b/fs/nfs/delegation.c
index 7ebeb57cb597..a0f798d3c74f 100644
--- a/fs/nfs/delegation.c
+++ b/fs/nfs/delegation.c
@@ -343,6 +343,7 @@ nfs_update_inplace_delegation(struct nfs_delegation *delegation,
 		delegation->stateid.seqid = update->stateid.seqid;
 		smp_wmb();
 		delegation->type = update->type;
+		clear_bit(NFS_DELEGATION_REVOKED, &delegation->flags);
 	}
 }
 
-- 
2.23.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v2 11/20] NFSv4: Update the stateid seqid in nfs_revoke_delegation()
  2019-10-31 22:40                   ` [PATCH v2 10/20] NFSv4: Clear the NFS_DELEGATION_REVOKED flag in nfs_update_inplace_delegation() Trond Myklebust
@ 2019-10-31 22:40                     ` Trond Myklebust
  2019-10-31 22:40                       ` [PATCH v2 12/20] NFSv4: Revoke the delegation on success in nfs4_delegreturn_done() Trond Myklebust
  0 siblings, 1 reply; 21+ messages in thread
From: Trond Myklebust @ 2019-10-31 22:40 UTC (permalink / raw)
  To: linux-nfs

If we revoke a delegation, but the stateid's seqid is newer, then
ensure we update the seqid when marking the delegation as revoked.

Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com>
---
 fs/nfs/delegation.c | 15 +++++++++++++--
 1 file changed, 13 insertions(+), 2 deletions(-)

diff --git a/fs/nfs/delegation.c b/fs/nfs/delegation.c
index a0f798d3c74f..aff2416dc277 100644
--- a/fs/nfs/delegation.c
+++ b/fs/nfs/delegation.c
@@ -771,8 +771,19 @@ static bool nfs_revoke_delegation(struct inode *inode,
 	if (stateid == NULL) {
 		nfs4_stateid_copy(&tmp, &delegation->stateid);
 		stateid = &tmp;
-	} else if (!nfs4_stateid_match(stateid, &delegation->stateid))
-		goto out;
+	} else {
+		if (!nfs4_stateid_match_other(stateid, &delegation->stateid))
+			goto out;
+		spin_lock(&delegation->lock);
+		if (stateid->seqid) {
+			if (nfs4_stateid_is_newer(&delegation->stateid, stateid)) {
+				spin_unlock(&delegation->lock);
+				goto out;
+			}
+			delegation->stateid.seqid = stateid->seqid;
+		}
+		spin_unlock(&delegation->lock);
+	}
 	nfs_mark_delegation_revoked(NFS_SERVER(inode), delegation);
 	ret = true;
 out:
-- 
2.23.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v2 12/20] NFSv4: Revoke the delegation on success in nfs4_delegreturn_done()
  2019-10-31 22:40                     ` [PATCH v2 11/20] NFSv4: Update the stateid seqid in nfs_revoke_delegation() Trond Myklebust
@ 2019-10-31 22:40                       ` Trond Myklebust
  2019-10-31 22:40                         ` [PATCH v2 13/20] NFSv4: Ignore requests to return the delegation if it was revoked Trond Myklebust
  0 siblings, 1 reply; 21+ messages in thread
From: Trond Myklebust @ 2019-10-31 22:40 UTC (permalink / raw)
  To: linux-nfs

If the delegation was successfully returned, then mark it as revoked.

Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com>
---
 fs/nfs/delegation.c | 36 ++++++++++++++++++++++++++++++++++++
 fs/nfs/delegation.h |  1 +
 fs/nfs/nfs4proc.c   |  1 +
 3 files changed, 38 insertions(+)

diff --git a/fs/nfs/delegation.c b/fs/nfs/delegation.c
index aff2416dc277..8c176c921554 100644
--- a/fs/nfs/delegation.c
+++ b/fs/nfs/delegation.c
@@ -806,6 +806,42 @@ void nfs_remove_bad_delegation(struct inode *inode,
 }
 EXPORT_SYMBOL_GPL(nfs_remove_bad_delegation);
 
+void nfs_delegation_mark_returned(struct inode *inode,
+		const nfs4_stateid *stateid)
+{
+	struct nfs_delegation *delegation;
+
+	if (!inode)
+		return;
+
+	rcu_read_lock();
+	delegation = rcu_dereference(NFS_I(inode)->delegation);
+	if (!delegation)
+		goto out_rcu_unlock;
+
+	spin_lock(&delegation->lock);
+	if (!nfs4_stateid_match_other(stateid, &delegation->stateid))
+		goto out_spin_unlock;
+	if (stateid->seqid) {
+		/* If delegation->stateid is newer, dont mark as returned */
+		if (nfs4_stateid_is_newer(&delegation->stateid, stateid))
+			goto out_clear_returning;
+		if (delegation->stateid.seqid != stateid->seqid)
+			delegation->stateid.seqid = stateid->seqid;
+	}
+
+	set_bit(NFS_DELEGATION_REVOKED, &delegation->flags);
+
+out_clear_returning:
+	clear_bit(NFS_DELEGATION_RETURNING, &delegation->flags);
+out_spin_unlock:
+	spin_unlock(&delegation->lock);
+out_rcu_unlock:
+	rcu_read_unlock();
+
+	nfs_inode_find_state_and_recover(inode, stateid);
+}
+
 /**
  * nfs_expire_unused_delegation_types
  * @clp: client to process
diff --git a/fs/nfs/delegation.h b/fs/nfs/delegation.h
index 74b7fb601365..15d3484be028 100644
--- a/fs/nfs/delegation.h
+++ b/fs/nfs/delegation.h
@@ -53,6 +53,7 @@ void nfs_expire_unreferenced_delegations(struct nfs_client *clp);
 int nfs_client_return_marked_delegations(struct nfs_client *clp);
 int nfs_delegations_present(struct nfs_client *clp);
 void nfs_remove_bad_delegation(struct inode *inode, const nfs4_stateid *stateid);
+void nfs_delegation_mark_returned(struct inode *inode, const nfs4_stateid *stateid);
 
 void nfs_delegation_mark_reclaim(struct nfs_client *clp);
 void nfs_delegation_reap_unclaimed(struct nfs_client *clp);
diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c
index 217885e32852..a222122e7151 100644
--- a/fs/nfs/nfs4proc.c
+++ b/fs/nfs/nfs4proc.c
@@ -6214,6 +6214,7 @@ static void nfs4_delegreturn_done(struct rpc_task *task, void *calldata)
 		if (exception.retry)
 			goto out_restart;
 	}
+	nfs_delegation_mark_returned(data->inode, data->args.stateid);
 	data->rpc_status = task->tk_status;
 	return;
 out_restart:
-- 
2.23.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v2 13/20] NFSv4: Ignore requests to return the delegation if it was revoked
  2019-10-31 22:40                       ` [PATCH v2 12/20] NFSv4: Revoke the delegation on success in nfs4_delegreturn_done() Trond Myklebust
@ 2019-10-31 22:40                         ` Trond Myklebust
  2019-10-31 22:40                           ` [PATCH v2 14/20] NFSv4: Don't reclaim delegations that have been returned or revoked Trond Myklebust
  0 siblings, 1 reply; 21+ messages in thread
From: Trond Myklebust @ 2019-10-31 22:40 UTC (permalink / raw)
  To: linux-nfs

If the delegation was revoked, or is already being returned, just
clear the NFS_DELEGATION_RETURN and NFS_DELEGATION_RETURN_IF_CLOSED
flags and keep going.

Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com>
---
 fs/nfs/delegation.c | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/fs/nfs/delegation.c b/fs/nfs/delegation.c
index 8c176c921554..ebd83e4db300 100644
--- a/fs/nfs/delegation.c
+++ b/fs/nfs/delegation.c
@@ -476,8 +476,6 @@ static bool nfs_delegation_need_return(struct nfs_delegation *delegation)
 {
 	bool ret = false;
 
-	if (test_bit(NFS_DELEGATION_RETURNING, &delegation->flags))
-		goto out;
 	if (test_and_clear_bit(NFS_DELEGATION_RETURN, &delegation->flags))
 		ret = true;
 	if (test_and_clear_bit(NFS_DELEGATION_RETURN_IF_CLOSED, &delegation->flags) && !ret) {
@@ -489,7 +487,10 @@ static bool nfs_delegation_need_return(struct nfs_delegation *delegation)
 			ret = true;
 		spin_unlock(&delegation->lock);
 	}
-out:
+	if (test_bit(NFS_DELEGATION_RETURNING, &delegation->flags) ||
+	    test_bit(NFS_DELEGATION_REVOKED, &delegation->flags))
+		ret = false;
+
 	return ret;
 }
 
-- 
2.23.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v2 14/20] NFSv4: Don't reclaim delegations that have been returned or revoked
  2019-10-31 22:40                         ` [PATCH v2 13/20] NFSv4: Ignore requests to return the delegation if it was revoked Trond Myklebust
@ 2019-10-31 22:40                           ` Trond Myklebust
  2019-10-31 22:40                             ` [PATCH v2 15/20] NFSv4: nfs4_return_incompatible_delegation() should check delegation validity Trond Myklebust
  0 siblings, 1 reply; 21+ messages in thread
From: Trond Myklebust @ 2019-10-31 22:40 UTC (permalink / raw)
  To: linux-nfs

If the delegation has already been revoked, we want to avoid reclaiming
it on reboot.

Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com>
---
 fs/nfs/delegation.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/fs/nfs/delegation.c b/fs/nfs/delegation.c
index ebd83e4db300..78df1cde286e 100644
--- a/fs/nfs/delegation.c
+++ b/fs/nfs/delegation.c
@@ -199,7 +199,7 @@ void nfs_inode_reclaim_delegation(struct inode *inode, const struct cred *cred,
 	delegation = rcu_dereference(NFS_I(inode)->delegation);
 	if (delegation != NULL) {
 		spin_lock(&delegation->lock);
-		if (delegation->inode != NULL) {
+		if (nfs4_is_valid_delegation(delegation, 0)) {
 			nfs4_stateid_copy(&delegation->stateid, stateid);
 			delegation->type = type;
 			delegation->pagemod_limit = pagemod_limit;
-- 
2.23.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v2 15/20] NFSv4: nfs4_return_incompatible_delegation() should check delegation validity
  2019-10-31 22:40                           ` [PATCH v2 14/20] NFSv4: Don't reclaim delegations that have been returned or revoked Trond Myklebust
@ 2019-10-31 22:40                             ` Trond Myklebust
  2019-10-31 22:40                               ` [PATCH v2 16/20] NFSv4: Fix nfs4_inode_make_writeable() Trond Myklebust
  0 siblings, 1 reply; 21+ messages in thread
From: Trond Myklebust @ 2019-10-31 22:40 UTC (permalink / raw)
  To: linux-nfs

Ensure that we check that the delegation is valid in
nfs4_return_incompatible_delegation() before we try to return it.

Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com>
---
 fs/nfs/nfs4proc.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c
index a222122e7151..c7e4a9ba8420 100644
--- a/fs/nfs/nfs4proc.c
+++ b/fs/nfs/nfs4proc.c
@@ -1796,7 +1796,7 @@ static void nfs4_return_incompatible_delegation(struct inode *inode, fmode_t fmo
 
 	fmode &= FMODE_READ|FMODE_WRITE;
 	rcu_read_lock();
-	delegation = rcu_dereference(NFS_I(inode)->delegation);
+	delegation = nfs4_get_valid_delegation(inode);
 	if (delegation == NULL || (delegation->type & fmode) == fmode) {
 		rcu_read_unlock();
 		return;
-- 
2.23.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v2 16/20] NFSv4: Fix nfs4_inode_make_writeable()
  2019-10-31 22:40                             ` [PATCH v2 15/20] NFSv4: nfs4_return_incompatible_delegation() should check delegation validity Trond Myklebust
@ 2019-10-31 22:40                               ` Trond Myklebust
  2019-10-31 22:40                                 ` [PATCH v2 17/20] NFS: nfs_inode_find_state_and_recover() fix stateid matching Trond Myklebust
  0 siblings, 1 reply; 21+ messages in thread
From: Trond Myklebust @ 2019-10-31 22:40 UTC (permalink / raw)
  To: linux-nfs

Fix the checks in nfs4_inode_make_writeable() to ignore the case where
we hold no delegations. Currently, in such a case, we automatically
flush writes.

Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com>
---
 fs/nfs/delegation.c | 16 ++++++++++++----
 1 file changed, 12 insertions(+), 4 deletions(-)

diff --git a/fs/nfs/delegation.c b/fs/nfs/delegation.c
index 78df1cde286e..e3d8055f0c6d 100644
--- a/fs/nfs/delegation.c
+++ b/fs/nfs/delegation.c
@@ -644,10 +644,18 @@ int nfs4_inode_return_delegation(struct inode *inode)
  */
 int nfs4_inode_make_writeable(struct inode *inode)
 {
-	if (!nfs4_has_session(NFS_SERVER(inode)->nfs_client) ||
-	    !nfs4_check_delegation(inode, FMODE_WRITE))
-		return nfs4_inode_return_delegation(inode);
-	return 0;
+	struct nfs_delegation *delegation;
+
+	rcu_read_lock();
+	delegation = nfs4_get_valid_delegation(inode);
+	if (delegation == NULL ||
+	    (nfs4_has_session(NFS_SERVER(inode)->nfs_client) &&
+	     (delegation->type & FMODE_WRITE))) {
+		rcu_read_unlock();
+		return 0;
+	}
+	rcu_read_unlock();
+	return nfs4_inode_return_delegation(inode);
 }
 
 static void nfs_mark_return_if_closed_delegation(struct nfs_server *server,
-- 
2.23.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v2 17/20] NFS: nfs_inode_find_state_and_recover() fix stateid matching
  2019-10-31 22:40                               ` [PATCH v2 16/20] NFSv4: Fix nfs4_inode_make_writeable() Trond Myklebust
@ 2019-10-31 22:40                                 ` Trond Myklebust
  2019-10-31 22:40                                   ` [PATCH v2 18/20] NFSv4: Fix races between open and delegreturn Trond Myklebust
  0 siblings, 1 reply; 21+ messages in thread
From: Trond Myklebust @ 2019-10-31 22:40 UTC (permalink / raw)
  To: linux-nfs

In nfs_inode_find_state_and_recover() we want to mark for recovery
only those stateids that match or are older than the supplied
stateid parameter.

Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com>
---
 fs/nfs/delegation.c | 3 ++-
 fs/nfs/nfs4_fs.h    | 6 ++++++
 fs/nfs/nfs4state.c  | 7 ++++---
 3 files changed, 12 insertions(+), 4 deletions(-)

diff --git a/fs/nfs/delegation.c b/fs/nfs/delegation.c
index e3d8055f0c6d..902baea1ecc6 100644
--- a/fs/nfs/delegation.c
+++ b/fs/nfs/delegation.c
@@ -1207,7 +1207,8 @@ void nfs_inode_find_delegation_state_and_recover(struct inode *inode,
 	rcu_read_lock();
 	delegation = rcu_dereference(NFS_I(inode)->delegation);
 	if (delegation &&
-	    nfs4_stateid_match_other(&delegation->stateid, stateid)) {
+	    nfs4_stateid_match_or_older(&delegation->stateid, stateid) &&
+	    !test_bit(NFS_DELEGATION_REVOKED, &delegation->flags)) {
 		nfs_mark_test_expired_delegation(NFS_SERVER(inode), delegation);
 		found = true;
 	}
diff --git a/fs/nfs/nfs4_fs.h b/fs/nfs/nfs4_fs.h
index 16b2e5cc3e94..40ab5540c2ae 100644
--- a/fs/nfs/nfs4_fs.h
+++ b/fs/nfs/nfs4_fs.h
@@ -572,6 +572,12 @@ static inline bool nfs4_stateid_is_newer(const nfs4_stateid *s1, const nfs4_stat
 	return (s32)(be32_to_cpu(s1->seqid) - be32_to_cpu(s2->seqid)) > 0;
 }
 
+static inline bool nfs4_stateid_match_or_older(const nfs4_stateid *dst, const nfs4_stateid *src)
+{
+	return nfs4_stateid_match_other(dst, src) &&
+		!(src->seqid && nfs4_stateid_is_newer(dst, src));
+}
+
 static inline void nfs4_stateid_seqid_inc(nfs4_stateid *s1)
 {
 	u32 seqid = be32_to_cpu(s1->seqid);
diff --git a/fs/nfs/nfs4state.c b/fs/nfs/nfs4state.c
index 0c6d53dc3672..a66acb6573d4 100644
--- a/fs/nfs/nfs4state.c
+++ b/fs/nfs/nfs4state.c
@@ -1407,7 +1407,7 @@ nfs_state_find_lock_state_by_stateid(struct nfs4_state *state,
 	list_for_each_entry(pos, &state->lock_states, ls_locks) {
 		if (!test_bit(NFS_LOCK_INITIALIZED, &pos->ls_flags))
 			continue;
-		if (nfs4_stateid_match_other(&pos->ls_stateid, stateid))
+		if (nfs4_stateid_match_or_older(&pos->ls_stateid, stateid))
 			return pos;
 	}
 	return NULL;
@@ -1441,12 +1441,13 @@ void nfs_inode_find_state_and_recover(struct inode *inode,
 		state = ctx->state;
 		if (state == NULL)
 			continue;
-		if (nfs4_stateid_match_other(&state->stateid, stateid) &&
+		if (nfs4_stateid_match_or_older(&state->stateid, stateid) &&
 		    nfs4_state_mark_reclaim_nograce(clp, state)) {
 			found = true;
 			continue;
 		}
-		if (nfs4_stateid_match_other(&state->open_stateid, stateid) &&
+		if (test_bit(NFS_OPEN_STATE, &state->flags) &&
+		    nfs4_stateid_match_or_older(&state->open_stateid, stateid) &&
 		    nfs4_state_mark_reclaim_nograce(clp, state)) {
 			found = true;
 			continue;
-- 
2.23.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v2 18/20] NFSv4: Fix races between open and delegreturn
  2019-10-31 22:40                                 ` [PATCH v2 17/20] NFS: nfs_inode_find_state_and_recover() fix stateid matching Trond Myklebust
@ 2019-10-31 22:40                                   ` Trond Myklebust
  2019-10-31 22:40                                     ` [PATCH v2 19/20] NFSv4: Handle NFS4ERR_OLD_STATEID in delegreturn Trond Myklebust
  0 siblings, 1 reply; 21+ messages in thread
From: Trond Myklebust @ 2019-10-31 22:40 UTC (permalink / raw)
  To: linux-nfs

If the server returns the same delegation in an open that we just used
in a delegreturn, we need to ensure we don't apply that stateid if
the delegreturn has freed it on the server.
To do so, we ensure that we do not free the storage for the delegation
until either it is replaced by a new one, or we throw the inode out of
cache.

Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com>
---
 fs/nfs/delegation.c | 64 ++++++++++++++++++++-------------------------
 1 file changed, 29 insertions(+), 35 deletions(-)

diff --git a/fs/nfs/delegation.c b/fs/nfs/delegation.c
index 902baea1ecc6..48f3c6c9672f 100644
--- a/fs/nfs/delegation.c
+++ b/fs/nfs/delegation.c
@@ -229,7 +229,6 @@ static int nfs_do_return_delegation(struct inode *inode, struct nfs_delegation *
 				delegation->cred,
 				&delegation->stateid,
 				issync);
-	nfs_free_delegation(delegation);
 	return res;
 }
 
@@ -302,7 +301,6 @@ nfs_detach_delegation_locked(struct nfs_inode *nfsi,
 		spin_unlock(&delegation->lock);
 		return NULL;
 	}
-	set_bit(NFS_DELEGATION_RETURNING, &delegation->flags);
 	list_del_rcu(&delegation->super_list);
 	delegation->inode = NULL;
 	rcu_assign_pointer(nfsi->delegation, NULL);
@@ -329,10 +327,12 @@ nfs_inode_detach_delegation(struct inode *inode)
 	struct nfs_server *server = NFS_SERVER(inode);
 	struct nfs_delegation *delegation;
 
-	delegation = nfs_start_delegation_return(nfsi);
-	if (delegation == NULL)
-		return NULL;
-	return nfs_detach_delegation(nfsi, delegation, server);
+	rcu_read_lock();
+	delegation = rcu_dereference(nfsi->delegation);
+	if (delegation != NULL)
+		delegation = nfs_detach_delegation(nfsi, delegation, server);
+	rcu_read_unlock();
+	return delegation;
 }
 
 static void
@@ -384,16 +384,18 @@ int nfs_inode_set_delegation(struct inode *inode, const struct cred *cred,
 	spin_lock(&clp->cl_lock);
 	old_delegation = rcu_dereference_protected(nfsi->delegation,
 					lockdep_is_held(&clp->cl_lock));
-	if (old_delegation != NULL) {
-		/* Is this an update of the existing delegation? */
-		if (nfs4_stateid_match_other(&old_delegation->stateid,
-					&delegation->stateid)) {
-			spin_lock(&old_delegation->lock);
-			nfs_update_inplace_delegation(old_delegation,
-					delegation);
-			spin_unlock(&old_delegation->lock);
-			goto out;
-		}
+	if (old_delegation == NULL)
+		goto add_new;
+	/* Is this an update of the existing delegation? */
+	if (nfs4_stateid_match_other(&old_delegation->stateid,
+				&delegation->stateid)) {
+		spin_lock(&old_delegation->lock);
+		nfs_update_inplace_delegation(old_delegation,
+				delegation);
+		spin_unlock(&old_delegation->lock);
+		goto out;
+	}
+	if (!test_bit(NFS_DELEGATION_REVOKED, &old_delegation->flags)) {
 		/*
 		 * Deal with broken servers that hand out two
 		 * delegations for the same file.
@@ -412,11 +414,11 @@ int nfs_inode_set_delegation(struct inode *inode, const struct cred *cred,
 		if (test_and_set_bit(NFS_DELEGATION_RETURNING,
 					&old_delegation->flags))
 			goto out;
-		freeme = nfs_detach_delegation_locked(nfsi,
-				old_delegation, clp);
-		if (freeme == NULL)
-			goto out;
 	}
+	freeme = nfs_detach_delegation_locked(nfsi, old_delegation, clp);
+	if (freeme == NULL)
+		goto out;
+add_new:
 	list_add_tail_rcu(&delegation->super_list, &server->delegations);
 	rcu_assign_pointer(nfsi->delegation, delegation);
 	delegation = NULL;
@@ -431,8 +433,10 @@ int nfs_inode_set_delegation(struct inode *inode, const struct cred *cred,
 	spin_unlock(&clp->cl_lock);
 	if (delegation != NULL)
 		nfs_free_delegation(delegation);
-	if (freeme != NULL)
+	if (freeme != NULL) {
 		nfs_do_return_delegation(inode, freeme, 0);
+		nfs_free_delegation(freeme);
+	}
 	return status;
 }
 
@@ -442,7 +446,6 @@ int nfs_inode_set_delegation(struct inode *inode, const struct cred *cred,
 static int nfs_end_delegation_return(struct inode *inode, struct nfs_delegation *delegation, int issync)
 {
 	struct nfs_client *clp = NFS_SERVER(inode)->nfs_client;
-	struct nfs_inode *nfsi = NFS_I(inode);
 	int err = 0;
 
 	if (delegation == NULL)
@@ -464,8 +467,6 @@ static int nfs_end_delegation_return(struct inode *inode, struct nfs_delegation
 		nfs_abort_delegation_return(delegation, clp);
 		goto out;
 	}
-	if (!nfs_detach_delegation(nfsi, delegation, NFS_SERVER(inode)))
-		goto out;
 
 	err = nfs_do_return_delegation(inode, delegation, issync);
 out:
@@ -608,6 +609,7 @@ void nfs_inode_evict_delegation(struct inode *inode)
 	if (delegation != NULL) {
 		set_bit(NFS_DELEGATION_INODE_FREEING, &delegation->flags);
 		nfs_do_return_delegation(inode, delegation, 1);
+		nfs_free_delegation(delegation);
 	}
 }
 
@@ -763,10 +765,9 @@ static void nfs_mark_delegation_revoked(struct nfs_server *server,
 {
 	set_bit(NFS_DELEGATION_REVOKED, &delegation->flags);
 	delegation->stateid.type = NFS4_INVALID_STATEID_TYPE;
-	nfs_mark_return_delegation(server, delegation);
 }
 
-static bool nfs_revoke_delegation(struct inode *inode,
+static void nfs_revoke_delegation(struct inode *inode,
 		const nfs4_stateid *stateid)
 {
 	struct nfs_delegation *delegation;
@@ -799,19 +800,12 @@ static bool nfs_revoke_delegation(struct inode *inode,
 	rcu_read_unlock();
 	if (ret)
 		nfs_inode_find_state_and_recover(inode, stateid);
-	return ret;
 }
 
 void nfs_remove_bad_delegation(struct inode *inode,
 		const nfs4_stateid *stateid)
 {
-	struct nfs_delegation *delegation;
-
-	if (!nfs_revoke_delegation(inode, stateid))
-		return;
-	delegation = nfs_inode_detach_delegation(inode);
-	if (delegation)
-		nfs_free_delegation(delegation);
+	nfs_revoke_delegation(inode, stateid);
 }
 EXPORT_SYMBOL_GPL(nfs_remove_bad_delegation);
 
@@ -839,7 +833,7 @@ void nfs_delegation_mark_returned(struct inode *inode,
 			delegation->stateid.seqid = stateid->seqid;
 	}
 
-	set_bit(NFS_DELEGATION_REVOKED, &delegation->flags);
+	nfs_mark_delegation_revoked(NFS_SERVER(inode), delegation);
 
 out_clear_returning:
 	clear_bit(NFS_DELEGATION_RETURNING, &delegation->flags);
-- 
2.23.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v2 19/20] NFSv4: Handle NFS4ERR_OLD_STATEID in delegreturn
  2019-10-31 22:40                                   ` [PATCH v2 18/20] NFSv4: Fix races between open and delegreturn Trond Myklebust
@ 2019-10-31 22:40                                     ` Trond Myklebust
  2019-10-31 22:40                                       ` [PATCH v2 20/20] NFSv4: Don't retry the GETATTR on old stateid in nfs4_delegreturn_done() Trond Myklebust
  0 siblings, 1 reply; 21+ messages in thread
From: Trond Myklebust @ 2019-10-31 22:40 UTC (permalink / raw)
  To: linux-nfs

If the server returns NFS4ERR_OLD_STATEID in response to our delegreturn,
we want to sync to the most recent seqid for the delegation stateid. However
if we are already at the most recent, we have two possibilities:

- an OPEN reply is still outstanding and will return a new seqid
- an earlier OPEN reply was dropped on the floor due to a timeout.

In the latter case, we may end up unable to complete the delegreturn,
so we want to bump the seqid to a value greater than the cached value.
While this may cause us to lose the delegation in the former case,
it should now be safe to assume that the client will replay the OPEN
if necessary in order to get a new valid stateid.

Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com>
---
 fs/nfs/delegation.c | 1 +
 fs/nfs/nfs4proc.c   | 7 +++----
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/fs/nfs/delegation.c b/fs/nfs/delegation.c
index 48f3c6c9672f..fe57b2b5314a 100644
--- a/fs/nfs/delegation.c
+++ b/fs/nfs/delegation.c
@@ -1252,6 +1252,7 @@ bool nfs4_refresh_delegation_stateid(nfs4_stateid *dst, struct inode *inode)
 	delegation = rcu_dereference(NFS_I(inode)->delegation);
 	if (delegation != NULL &&
 	    nfs4_stateid_match_other(dst, &delegation->stateid) &&
+	    nfs4_stateid_is_newer(&delegation->stateid, dst) &&
 	    !test_bit(NFS_DELEGATION_REVOKED, &delegation->flags)) {
 		dst->seqid = delegation->stateid.seqid;
 		ret = true;
diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c
index c7e4a9ba8420..33a8e53e976c 100644
--- a/fs/nfs/nfs4proc.c
+++ b/fs/nfs/nfs4proc.c
@@ -6196,10 +6196,9 @@ static void nfs4_delegreturn_done(struct rpc_task *task, void *calldata)
 		task->tk_status = 0;
 		break;
 	case -NFS4ERR_OLD_STATEID:
-		if (nfs4_refresh_delegation_stateid(&data->stateid, data->inode))
-			goto out_restart;
-		task->tk_status = 0;
-		break;
+		if (!nfs4_refresh_delegation_stateid(&data->stateid, data->inode))
+			nfs4_stateid_seqid_inc(&data->stateid);
+		goto out_restart;
 	case -NFS4ERR_ACCESS:
 		if (data->args.bitmask) {
 			data->args.bitmask = NULL;
-- 
2.23.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v2 20/20] NFSv4: Don't retry the GETATTR on old stateid in nfs4_delegreturn_done()
  2019-10-31 22:40                                     ` [PATCH v2 19/20] NFSv4: Handle NFS4ERR_OLD_STATEID in delegreturn Trond Myklebust
@ 2019-10-31 22:40                                       ` Trond Myklebust
  0 siblings, 0 replies; 21+ messages in thread
From: Trond Myklebust @ 2019-10-31 22:40 UTC (permalink / raw)
  To: linux-nfs

If the server returns NFS4ERR_OLD_STATEID, then just skip retrying the
GETATTR when replaying the delegreturn compound. We know nothing will
have changed on the server.

Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com>
---
 fs/nfs/nfs4proc.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c
index 33a8e53e976c..a64ce9518776 100644
--- a/fs/nfs/nfs4proc.c
+++ b/fs/nfs/nfs4proc.c
@@ -6198,6 +6198,10 @@ static void nfs4_delegreturn_done(struct rpc_task *task, void *calldata)
 	case -NFS4ERR_OLD_STATEID:
 		if (!nfs4_refresh_delegation_stateid(&data->stateid, data->inode))
 			nfs4_stateid_seqid_inc(&data->stateid);
+		if (data->args.bitmask) {
+			data->args.bitmask = NULL;
+			data->res.fattr = NULL;
+		}
 		goto out_restart;
 	case -NFS4ERR_ACCESS:
 		if (data->args.bitmask) {
-- 
2.23.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

end of thread, other threads:[~2019-10-31 22:43 UTC | newest]

Thread overview: 21+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-10-31 22:40 [PATCH v2 00/20] Delegation bugfixes Trond Myklebust
2019-10-31 22:40 ` [PATCH v2 01/20] NFSv4: Don't allow a cached open with a revoked delegation Trond Myklebust
2019-10-31 22:40   ` [PATCH v2 02/20] NFS: Fix an RCU lock leak in nfs4_refresh_delegation_stateid() Trond Myklebust
2019-10-31 22:40     ` [PATCH v2 03/20] NFSv4: Fix delegation handling in update_open_stateid() Trond Myklebust
2019-10-31 22:40       ` [PATCH v2 04/20] NFSv4: nfs4_callback_getattr() should ignore revoked delegations Trond Myklebust
2019-10-31 22:40         ` [PATCH v2 05/20] NFSv4: Delegation recalls should not find " Trond Myklebust
2019-10-31 22:40           ` [PATCH v2 06/20] NFSv4: fail nfs4_refresh_delegation_stateid() when the delegation was revoked Trond Myklebust
2019-10-31 22:40             ` [PATCH v2 07/20] NFS: Rename nfs_inode_return_delegation_noreclaim() Trond Myklebust
2019-10-31 22:40               ` [PATCH v2 08/20] NFSv4: Don't remove the delegation from the super_list more than once Trond Myklebust
2019-10-31 22:40                 ` [PATCH v2 09/20] NFSv4: Hold the delegation spinlock when updating the seqid Trond Myklebust
2019-10-31 22:40                   ` [PATCH v2 10/20] NFSv4: Clear the NFS_DELEGATION_REVOKED flag in nfs_update_inplace_delegation() Trond Myklebust
2019-10-31 22:40                     ` [PATCH v2 11/20] NFSv4: Update the stateid seqid in nfs_revoke_delegation() Trond Myklebust
2019-10-31 22:40                       ` [PATCH v2 12/20] NFSv4: Revoke the delegation on success in nfs4_delegreturn_done() Trond Myklebust
2019-10-31 22:40                         ` [PATCH v2 13/20] NFSv4: Ignore requests to return the delegation if it was revoked Trond Myklebust
2019-10-31 22:40                           ` [PATCH v2 14/20] NFSv4: Don't reclaim delegations that have been returned or revoked Trond Myklebust
2019-10-31 22:40                             ` [PATCH v2 15/20] NFSv4: nfs4_return_incompatible_delegation() should check delegation validity Trond Myklebust
2019-10-31 22:40                               ` [PATCH v2 16/20] NFSv4: Fix nfs4_inode_make_writeable() Trond Myklebust
2019-10-31 22:40                                 ` [PATCH v2 17/20] NFS: nfs_inode_find_state_and_recover() fix stateid matching Trond Myklebust
2019-10-31 22:40                                   ` [PATCH v2 18/20] NFSv4: Fix races between open and delegreturn Trond Myklebust
2019-10-31 22:40                                     ` [PATCH v2 19/20] NFSv4: Handle NFS4ERR_OLD_STATEID in delegreturn Trond Myklebust
2019-10-31 22:40                                       ` [PATCH v2 20/20] NFSv4: Don't retry the GETATTR on old stateid in nfs4_delegreturn_done() Trond Myklebust

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).