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 occurs because the nfsd_file cache holds open NFSv2/3 file handles that pin the filesystem. 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, handling NFSv2/3 file handles that remain in the cache. Entries under construction (nf_file not yet set) are skipped; these have no open file to close. The hashtable walk releases the mutex periodically to avoid blocking other NFSD operations during large cache walks. Entries are disposed incrementally in batches, keeping memory usage bounded and spreading the I/O load. A log message is emitted when cached file handles are closed during unmount, informing administrators that NFS clients may receive stale file handle errors. A flush_workqueue() call is added to nfsd_sb_watch_shutdown() to ensure that any work items still executing complete before shutdown proceeds. Without this, if an unmount notification returns early due to signal interruption while the work function is still running, nfsd_file_cache_shutdown() could destroy the file cache slab while nfsd_file_close_sb() is still disposing entries. Signed-off-by: Chuck Lever --- fs/nfsd/filecache.c | 45 +++++++++++++++++++++++++++++++++++++++++++++ fs/nfsd/filecache.h | 1 + fs/nfsd/sb_watch.c | 10 ++++++++++ 3 files changed, 56 insertions(+) diff --git a/fs/nfsd/filecache.c b/fs/nfsd/filecache.c index 1e2b38ed1d35..d1a6f7cf40b2 100644 --- a/fs/nfsd/filecache.c +++ b/fs/nfsd/filecache.c @@ -894,6 +894,51 @@ __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; + unsigned int closed = 0; + LIST_HEAD(dispose); + + if (!test_bit(NFSD_FILE_CACHE_UP, &nfsd_file_flags)) + 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) && + nf->nf_file && + file_inode(nf->nf_file)->i_sb == sb) { + nfsd_file_cond_queue(nf, &dispose); + closed++; + } + nf = rhashtable_walk_next(&iter); + } + + rhashtable_walk_stop(&iter); + } while (nf == ERR_PTR(-EAGAIN)); + rhashtable_walk_exit(&iter); + + nfsd_file_dispose_list(&dispose); + + if (closed) + pr_info("nfsd: closed %u cached file handle%s on %s\n", + closed, closed == 1 ? "" : "s", sb->s_id); +} + 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/sb_watch.c b/fs/nfsd/sb_watch.c index 8f711956a12e..34e50afe566c 100644 --- a/fs/nfsd/sb_watch.c +++ b/fs/nfsd/sb_watch.c @@ -65,6 +65,7 @@ static void nfsd_sb_revoke_work(struct work_struct *work) /* Errors are logged by lockd; no recovery is possible. */ (void)nlmsvc_unlock_all_by_sb(watch->sb); nfsd4_revoke_states(nn, watch->sb); + nfsd_file_close_sb(watch->sb); pr_info("nfsd: state revocation for %s complete\n", watch->sb->s_id); @@ -257,6 +258,15 @@ void nfsd_sb_watch_shutdown(struct nfsd_net *nn) { umount_unregister_notifier(&nn->nfsd_umount_notifier); nfsd_sb_watches_destroy(nn); + /* + * Ensure any work items still running complete before shutdown + * proceeds. This handles the case where an unmount notification + * returned early due to signal interruption but the work function + * is still executing nfsd_file_close_sb(). Without this flush, + * nfsd_file_cache_shutdown() could destroy the slab while the + * work function is still disposing file cache entries. + */ + flush_workqueue(nfsd_sb_watch_wq); } int nfsd_sb_watch_init(void) -- 2.53.0