From: Chuck Lever When a filesystem is unmounted while NFS is exporting it, the unmount can fail with EBUSY even after NFSv4 state has been revoked. This is because the nfsd_file cache can hold open NFSv2/3 file handles that pin the filesystem, preventing the unmount from completing. Extend the mechanism that revokes NFSv4 state on unmount to also close cached file handles. nfsd_file_close_sb() walks the nfsd_file cache and disposes of entries belonging to the target superblock. It runs after NFSv4 state revocation, so it handles only NFSv2/3 file handles that remain in the cache. Entries still under construction (with nf_file not yet set) are skipped; these have no open file to close. Signed-off-by: Chuck Lever --- fs/nfsd/filecache.c | 44 ++++++++++++++++++++++++++++++++++++++++++++ fs/nfsd/filecache.h | 1 + fs/nfsd/pin.c | 6 ++++-- 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/fs/nfsd/filecache.c b/fs/nfsd/filecache.c index 93798575b807..b921a9553f36 100644 --- a/fs/nfsd/filecache.c +++ b/fs/nfsd/filecache.c @@ -894,6 +894,50 @@ __nfsd_file_cache_purge(struct net *net) nfsd_file_dispose_list(&dispose); } +/** + * nfsd_file_close_sb - close GC-managed cached files for a superblock + * @sb: target superblock + * + * Walk the nfsd_file cache and close out GC-managed entries (those + * acquired via nfsd_file_acquire_gc) that belong to @sb. Called during + * filesystem unmount after NFSv4 state revocation to release remaining + * cached file handles that may be pinning the filesystem. + */ +void nfsd_file_close_sb(struct super_block *sb) +{ + struct rhashtable_iter iter; + struct nfsd_file *nf; + LIST_HEAD(dispose); + + mutex_lock(&nfsd_mutex); + if (test_bit(NFSD_FILE_CACHE_UP, &nfsd_file_flags) == 0) { + mutex_unlock(&nfsd_mutex); + return; + } + + rhltable_walk_enter(&nfsd_file_rhltable, &iter); + do { + rhashtable_walk_start(&iter); + + nf = rhashtable_walk_next(&iter); + while (!IS_ERR_OR_NULL(nf)) { + if (test_bit(NFSD_FILE_GC, &nf->nf_flags) == 0) + goto next; + /* Skip entries under construction (nf_file not yet set) */ + if (nf->nf_file && file_inode(nf->nf_file)->i_sb == sb) + nfsd_file_cond_queue(nf, &dispose); +next: + nf = rhashtable_walk_next(&iter); + } + + rhashtable_walk_stop(&iter); + } while (nf == ERR_PTR(-EAGAIN)); + rhashtable_walk_exit(&iter); + mutex_unlock(&nfsd_mutex); + + nfsd_file_dispose_list(&dispose); +} + static struct nfsd_fcache_disposal * nfsd_alloc_fcache_disposal(void) { diff --git a/fs/nfsd/filecache.h b/fs/nfsd/filecache.h index b383dbc5b921..66ca7fc6189b 100644 --- a/fs/nfsd/filecache.h +++ b/fs/nfsd/filecache.h @@ -70,6 +70,7 @@ struct net *nfsd_file_put_local(struct nfsd_file __rcu **nf); struct nfsd_file *nfsd_file_get(struct nfsd_file *nf); struct file *nfsd_file_file(struct nfsd_file *nf); void nfsd_file_close_inode_sync(struct inode *inode); +void nfsd_file_close_sb(struct super_block *sb); void nfsd_file_net_dispose(struct nfsd_net *nn); bool nfsd_file_is_cached(struct inode *inode); __be32 nfsd_file_acquire_gc(struct svc_rqst *rqstp, struct svc_fh *fhp, diff --git a/fs/nfsd/pin.c b/fs/nfsd/pin.c index eefa4baff82c..a404611c20a0 100644 --- a/fs/nfsd/pin.c +++ b/fs/nfsd/pin.c @@ -19,6 +19,7 @@ #include "nfsd.h" #include "netns.h" #include "state.h" +#include "filecache.h" #define NFSDDBG_FACILITY NFSDDBG_PROC @@ -49,8 +50,8 @@ static void nfsd_fs_pin_free_rcu(struct rcu_head *rcu) /* * Work function for nfsd_fs_pin - runs in process context. - * Cancels async COPYs, releases NLM locks, and revokes NFSv4 state for - * the superblock. + * Cancels async COPYs, releases NLM locks, revokes NFSv4 state, and closes + * cached NFSv2/3 files for the superblock. */ static void nfsd_fs_pin_work(struct work_struct *work) { @@ -63,6 +64,7 @@ static void nfsd_fs_pin_work(struct work_struct *work) /* Errors are logged by lockd; no recovery is possible. */ (void)nlmsvc_unlock_all_by_sb(p->sb); nfsd4_revoke_states(nn, p->sb); + nfsd_file_close_sb(p->sb); pr_info("nfsd: state revocation for %s complete\n", p->sb->s_id); -- 2.52.0