Technical Root Cause Analysis: VFS Busy Inodes BUG During NILFS2 Unmount
1. Crash Site & Context
- Panic Location:
fs/super.c:731-733 inside generic_shutdown_super():
CHECK_DATA_CORRUPTION(!list_empty(&sb->s_inodes),
"VFS: Busy inodes after unmount of %s (%s)",
sb->s_id, sb->s_type->name);
- Call Trace:
cleanup_mnt() $\to$ deactivate_locked_super() $\to$ kill_block_super() $\to$ generic_shutdown_super() $\to$ nilfs_put_super()
- Preceding Kernel Warning:
NILFS (loop4): disposed unprocessed dirty file(s) when stopping log writer
VFS: Busy inodes after unmount of loop4 (nilfs2)
2. Involved Files, Functions, and Data Structures
- Files:
fs/super.c: Superblock shutdown logic in generic_shutdown_super().
fs/nilfs2/super.c: nilfs_put_super().
fs/nilfs2/segment.c: nilfs_detach_log_writer(), nilfs_segctor_destroy(), nilfs_dispose_list(), nilfs_segctor_write_out(), nilfs_segctor_drop_written_files().
fs/nilfs2/inode.c: nilfs_set_file_dirty(), nilfs_evict_inode(), __nilfs_mark_inode_dirty(), nilfs_writepages().
- Structures & Fields:
struct super_block: s_inodes, s_flags (SB_ACTIVE).
struct the_nilfs: ns_writer, ns_dirty_files, ns_inode_lock, ns_segctor_sem.
struct nilfs_sc_info: sc_dirty_files, sc_iput_queue, sc_iput_work.
struct nilfs_inode_info: i_dirty, i_state (NILFS_I_DIRTY, NILFS_I_QUEUED, NILFS_I_BUSY, NILFS_I_UPDATED), vfs_inode.
3. Root Cause Mechanism & Invariant Violation
A. VFS Superblock Teardown Order
During unmount, generic_shutdown_super(sb) executes in the following order:
shrink_dcache_for_umount(sb): Drops all dentries, pruning VFS cache references.
sb->s_flags &= ~SB_ACTIVE: Clears the active superblock flag.
evict_inodes(sb): Iterates over sb->s_inodes and evicts all inodes with i_count == 0. Inodes with i_count > 0 are explicitly skipped.
sop->put_super(sb) $\to$ nilfs_put_super(sb): Intended to release all remaining filesystem-internal inode references (metadata files and dirty file queues).
CHECK_DATA_CORRUPTION(!list_empty(&sb->s_inodes), ...): Asserts that no inodes remain on sb->s_inodes.
B. Dirty File Inode Lifecycle in NILFS2
When a file is modified (e.g. nilfs_write_end() or nilfs_page_mkwrite() $\to$ nilfs_set_file_dirty()):
- NILFS2 calls
igrab(inode) to increment inode->i_count before adding ii->i_dirty to nilfs->ns_dirty_files.
- Later, the segment constructor collects files from
ns_dirty_files into sci->sc_dirty_files.
- Because of this
igrab(), any file residing on ns_dirty_files or sci->sc_dirty_files has i_count > 0. Consequently, step 3 (evict_inodes(sb)) skips them during unmount.
C. Teardown Flaw in nilfs_detach_log_writer() and nilfs_segctor_destroy()
When nilfs_put_super() calls nilfs_detach_log_writer():
- It calls
nilfs_segctor_destroy(nilfs->ns_writer).
- Inside
nilfs_segctor_destroy():
- In
nilfs_dispose_list(), iput(&ii->vfs_inode) is executed for each unprocessed inode:
- Crucially, this runs before
nilfs->ns_writer = NULL is set and before set_nilfs_purging(nilfs) is set in nilfs_detach_log_writer().
- When
iput() drops i_count to 0 under !(sb->s_flags & SB_ACTIVE):
- If
i_nlink > 0, iput_final() invokes write_inode_now(inode, 1) $\to$ nilfs_writepages() $\to$ nilfs_construct_dsync_segment().
- In
nilfs_segctor_drop_written_files(), during_mount = !(sci->sc_super->s_flags & SB_ACTIVE) evaluates to true, causing written files to be appended to sci->sc_iput_queue and scheduling sci->sc_iput_work (which was already flushed and will not run before teardown finishes).
- If
i_nlink == 0, iput_final() enters nilfs_evict_inode(). Because nilfs->ns_writer is not yet NULL, nilfs_evict_inode() starts a transaction and marks metadata (ii->i_root->ifile) dirty rather than taking the abort/clear path.
- Furthermore, if any inode in
sc_dirty_files has another reference held (or failed to be completely unlinked/evicted synchronously), iput() does not drop i_count to 0.
As a result, one or more inodes remain linked to sb->s_inodes when nilfs_put_super() returns, triggering the CHECK_DATA_CORRUPTION BUG() at fs/super.c:733.
4. Verified Findings vs. Unverified/Inconclusive Aspects
- Verified Facts:
- The panic directly manifests from
!list_empty(&sb->s_inodes) after nilfs_put_super().
- The warning
"disposed unprocessed dirty file(s) when stopping log writer" at fs/nilfs2/segment.c:2831 confirms that sci->sc_dirty_files was non-empty when nilfs_segctor_destroy() entered cleanup.
nilfs_dispose_list() is called while nilfs->ns_writer is still non-NULL and nilfs_purging(nilfs) is not set.
- In
nilfs_detach_log_writer(), set_nilfs_purging(nilfs) is only enabled after nilfs_segctor_destroy() completes.
- Inconclusive / Unverified Aspects:
- The exact failure condition during segment construction (e.g. injected error,
-EROFS, or -ENOSPC) that caused nilfs_segctor_write_out() to leave files in sc_dirty_files was not recorded in the crash report.
- Whether the remaining busy inode on
sb->s_inodes was the dirty regular file itself (due to deferred iput via sc_iput_queue / during_mount), or a secondary metadata file (root->ifile / btnc_inode) dirtied during eviction cannot be determined from the register dump alone without a live memory dump of sb->s_inodes.