NFSv4: Fix a deadlock when recovering state on a sillyrenamed file
authorTrond Myklebust <trond.myklebust@hammerspace.com>
Sat, 1 Feb 2025 20:00:09 +0000 (15:00 -0500)
committerAnna Schumaker <anna.schumaker@oracle.com>
Wed, 19 Feb 2025 21:45:24 +0000 (16:45 -0500)
If the file is sillyrenamed, and slated for delete on close, it is
possible for a server reboot to triggeer an open reclaim, with can again
race with the application call to close(). When that happens, the call
to put_nfs_open_context() can trigger a synchronous delegreturn call
which deadlocks because it is not marked as privileged.

Instead, ensure that the call to nfs4_inode_return_delegation_on_close()
catches the delegreturn, and schedules it asynchronously.

Reported-by: Li Lingfeng <lilingfeng3@huawei.com>
Fixes: adb4b42d19ae ("Return the delegation when deleting sillyrenamed files")
Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com>
Signed-off-by: Anna Schumaker <anna.schumaker@oracle.com>
fs/nfs/delegation.c
fs/nfs/delegation.h
fs/nfs/nfs4proc.c

index 035ba52742a5047e5102095bb006b26633242df9..4db912f56230554ee260bb307aac5c41a9fbff54 100644 (file)
@@ -780,6 +780,43 @@ int nfs4_inode_return_delegation(struct inode *inode)
        return 0;
 }
 
+/**
+ * nfs4_inode_set_return_delegation_on_close - asynchronously return a delegation
+ * @inode: inode to process
+ *
+ * This routine is called to request that the delegation be returned as soon
+ * as the file is closed. If the file is already closed, the delegation is
+ * immediately returned.
+ */
+void nfs4_inode_set_return_delegation_on_close(struct inode *inode)
+{
+       struct nfs_delegation *delegation;
+       struct nfs_delegation *ret = NULL;
+
+       if (!inode)
+               return;
+       rcu_read_lock();
+       delegation = nfs4_get_valid_delegation(inode);
+       if (!delegation)
+               goto out;
+       spin_lock(&delegation->lock);
+       if (!delegation->inode)
+               goto out_unlock;
+       if (list_empty(&NFS_I(inode)->open_files) &&
+           !test_and_set_bit(NFS_DELEGATION_RETURNING, &delegation->flags)) {
+               /* Refcount matched in nfs_end_delegation_return() */
+               ret = nfs_get_delegation(delegation);
+       } else
+               set_bit(NFS_DELEGATION_RETURN_IF_CLOSED, &delegation->flags);
+out_unlock:
+       spin_unlock(&delegation->lock);
+       if (ret)
+               nfs_clear_verifier_delegated(inode);
+out:
+       rcu_read_unlock();
+       nfs_end_delegation_return(inode, ret, 0);
+}
+
 /**
  * nfs4_inode_return_delegation_on_close - asynchronously return a delegation
  * @inode: inode to process
index 71524d34ed207ca5cc53cc83559f82bef5c6f6f2..8ff5ab9c5c2565bca5415d090016dc05adb20f1b 100644 (file)
@@ -49,6 +49,7 @@ void nfs_inode_reclaim_delegation(struct inode *inode, const struct cred *cred,
                                  unsigned long pagemod_limit, u32 deleg_type);
 int nfs4_inode_return_delegation(struct inode *inode);
 void nfs4_inode_return_delegation_on_close(struct inode *inode);
+void nfs4_inode_set_return_delegation_on_close(struct inode *inode);
 int nfs_async_inode_return_delegation(struct inode *inode, const nfs4_stateid *stateid);
 void nfs_inode_evict_delegation(struct inode *inode);
 
index df9669d4ded7f593c505ff33b10dffbd47b59b77..c25ecdb76d304a8d2d061401c8add775429dce0c 100644 (file)
@@ -3906,8 +3906,11 @@ nfs4_atomic_open(struct inode *dir, struct nfs_open_context *ctx,
 
 static void nfs4_close_context(struct nfs_open_context *ctx, int is_sync)
 {
+       struct dentry *dentry = ctx->dentry;
        if (ctx->state == NULL)
                return;
+       if (dentry->d_flags & DCACHE_NFSFS_RENAMED)
+               nfs4_inode_set_return_delegation_on_close(d_inode(dentry));
        if (is_sync)
                nfs4_close_sync(ctx->state, _nfs4_ctx_to_openmode(ctx));
        else