Summary
Fix a panic in flush_newblk_dep() caused by treating the soft-updates newblk hash as though a physical block number uniquely identified a dependency.
The panic was reported in PR 297976:
panic: flush_newblk_dep: Bad newblk
flush_newblk_dep() is called while flushing the body of a newly created directory. It obtains the directory's physical block number and calls newblk_lookup(). The existing code assumes that any dependency returned for that block must be a D_ALLOCDIRECT:
if (newblk->nb_list.wk_type != D_ALLOCDIRECT)
panic("flush_newblk_dep: Bad newblk %p", newblk);That uniqueness assumption is not valid. The hash can retain completed dependencies for an earlier allocation after the physical block has been freed and reused. In particular, newblk_find() deliberately ignores converted dependencies when called with DEPALLOC, allowing a new dependency for the same physical block to coexist with an older one.
Analysis of the crash dump showed the following sequence:
- /var/db/pkg/local.sqlite-wal initially had logical blocks 125 through 128 allocated at filesystem blocks 1340656 through 1340680.
- ffs_reallocblks() relocated this cluster to blocks 1393160 through 1393184.
- The live indirect pointer for WAL logical block 128 referenced block 1393184, proving that block 1340680 was no longer owned by the WAL.
- Block 1340680 was subsequently allocated as direct block zero of the newly created /usr/local/lib/X11 directory.
- The old WAL D_ALLOCINDIR for block 1340680 remained in an indirdep ir_completehd, waiting for the parent indirect-block allocation to become durable. This dependency no longer represented live ownership of block 1340680, but it remained in the newblk hash as required by the existing dependency lifecycle.
The panic was then triggered by a race in flush_pagedep_deps():
- While holding the soft-updates lock, it observed MKDIR_BODY on the diradd for the new directory.
- It dropped the lock to obtain the directory vnode with get_parent_vp().
- During this interval, the directory body and its allocation completed. MKDIR_BODY was cleared, the directory's D_ALLOCDIRECT was removed, and the diradd moved to the pagedep's pending list.
- Without revalidating that state, flush_pagedep_deps() entered flush_newblk_dep().
- The generic block-number lookup found the retained WAL D_ALLOCINDIR, causing the type assertion to fire.
The crash dump captured the diradd in state ONWORKLIST | ATTACHED | COMPLETE | DEPCOMPLETE, with MKDIR_BODY already clear. It was also present on pd_pendinghd rather than the diraddhd being processed. The cylinder-group bitmap and live vnode pointers were consistent with block 1340680 belonging exclusively to the X11 directory. Thus, this was not a duplicate live allocation or an allocator bitmap inconsistency.
Solution
Change flush_newblk_dep() to search the block's hash bucket for the specific allocation dependency it intends to flush. A matching dependency must have all of the following properties:
- The same physical block number.
- Type D_ALLOCDIRECT.
- An inodedep belonging to the vnode's inode.
- An ad_offset matching the requested logical block.
If no such dependency exists, the allocation has already completed and the function follows its existing "dependency not found" path.
This preserves the important consistency checks. The change does not simply ignore an unexpected dependency type: unrelated or historical hash entries are excluded by identity, while flush_pagedep_deps() retains its existing postcondition check. If the same diradd remains active with MKDIR_BODY still set after the flush attempt, the existing MKDIR_BODY panic will still report a genuine failure to satisfy the directory dependency.
The hash traversal remains protected by the per-filesystem soft-updates lock, as was the previous newblk_lookup() call. It introduces no new lock transitions, object-lifetime requirements, or interface changes. The operation remains a traversal of one hash bucket and occurs only on this forced dependency-flush path.
Testing
- Built the complete UFS kernel module with -Werror.
- Verified the change with git diff --check.
- Evaluated the new matching logic against the captured crash dump. The relevant hash bucket contained only the historical WAL D_ALLOCINDIR, so the new lookup correctly reports that no matching X11 D_ALLOCDIRECT remains.
- Confirmed from the dump that the caller's MKDIR_BODY dependency had completed and the diradd had already moved to the pending list, making the existing not-found behavior the correct result.
PR: https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=297976
Follow-up to: https://reviews.freebsd.org/D26136