Repository navigation
Conversation
Debian ships virtiofsd in /usr/libexec and Arch in /usr/lib; neither is on $PATH, so start_vm failed on both. Resolve it there too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…er tree Reproducer for a field report: podman's bcachefs graph driver snapshots each layer from its parent and replaces files by unlink and create, so a base-image inode is whited out in some layers and alive in others. After snapshot deletion was interrupted mid-collapse, fsck reported key_in_missing_inode for those files and deleted extents that surviving layers still read. Two variants: one interrupted round, and a second round that moves the collapse terminal. Snapshot deletion interruption is factored into interrupt_snapshot_deletion(), shared with test_interrupted_deletion. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
root_image symlinks /nix to /host/nix, which dangles on a host without a store; mkdir -p /nix/store then fails under errexit and the VM never runs the test. Mount only when the host exports a store, and let a failed mount surface instead of being swallowed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nd userspace fsck The field report's snapshot ids were far apart, putting is_ancestor on skiplists, and it ran userspace fsck; the first variants covered neither. Container churn on the kept layer spreads ids past the 128-id bitmap, and the userspace variant unsets BCACHEFS_KERNEL_ONLY, which silently overrides fsck -K. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…shots bcachefs-tools issue #1109. Snapshot a subvolume twice, delete the original: its root inode's version at the now-interior node keeps naming the deleted subvolume. check_inode strips bi_subvol (inode_bi_subvol_missing), the version becomes an orphaned directory and gets reattached into the lost+found inside itself, and from then on check_inodes and check_dirents undo each other's backpointer repair on every run. Repair-oracle test: fails until fsck converges. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ointed deleted_original_after_snapshots converges: the reattach's child fixup whiteouts the new dirent in the snapshot roots' leaves. The field report has the roots' backpointers naming that dirent instead, which the same fixup writes when the roots had no backpointer. Model that outcome on the repaired image and assert convergence; fails until check_inodes and check_dirents agree on it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
ls -d exits 2 when "other" is absent, and under pipefail the command substitution takes the ERR trap, so capture_state dies in every test that doesn't build the standard tree. The names are fixed: glob them and let the directory test skip what's missing. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
ticpu
force-pushed
the
snapshot-collapse-repro
branch
from
October 8, 2026 15:46
ae6803f to
81ad16c
Compare
ticpu
marked this pull request as ready for review
October 8, 2026 15:55
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two groups of snapshot-inject tests, both grown from the same filesystem: a podman graph root whose layers were snapshotted flat into a new subvolume and then kept in use. Every test here is a repair oracle in the file's usual contract (fsck -y must detect, fsck -n must then be clean, data in the snapshots the test didn't destroy must survive) and fails until the repair converges. Results below are against bcachefs-tools master 289ae7df3, kernel 7.3-rc5, in-kernel offline fsck.
Deleting an original that has been snapshotted (bcachefs-tools#1109)
test_deleted_original_after_snapshotsis three commands and no injection: create a subvolume, snapshot it twice, delete it. The deleted subvolume's root inode keeps a version at the node that became interior, naming the deleted subvolume, and nothing removes that key: the node has two live children, so the sweep leaves it alone. On the next fsck, check_inodes takes the stale bi_subvol for damage (inode_bi_subvol_missing, since v1.39.1, when the leaf gate on that block went), strips bi_subvol and bi_parent_subvol, and the version is now an orphaned directory at an interior snapshot. check_unreachable_inodes reattaches it, and the lost+found it resolves for that snapshot is the one inside that very inode, the root of every snapshot in the family:reattached at (loop at <inum>:<snapshot>)/lost+found/<inum>.On v1.39.7 that is where it stops: the reattach's child fixup whiteouts the new dirent in the snapshot roots' leaves, fsck converges, and every snapshot of the family now carries a lost+found naming the tree's own root. The test fails on that, by asserting the root inum is named by no dirent.
On master it no longer converges at all. check_directory_structure had been a no-op since v1.38.4 (51bf7dc36 kept POS_MIN as the start of a reverse walk; fixed by 57bae0829), which is why the field runs printed it done in 0 seconds. Now it finds the loop, and its repair removes the backpointer and reattaches the inode into lost+found, which is the lost+found inside the loop. A second loop follows, then
inode points to dirent that does not point backon the lost+found inode itself,check_path_loop(): error removing dirent ENOENT_dirent_doesnt_match_inode, and recovery fails with exit 8. The second fsck does the same. A filesystem fsck cannot complete on, from deleting a subvolume that had been snapshotted.test_deleted_original_after_snapshots_repointedis the field report's state. There the 204 cycling subvolume roots have backpointers naming the reattach dirent rather than their DT_SUBVOL names, which is what bch2_reattach_inode()'s child fixup writes when the descendant versions reach it without a backpointer: it points them at the new dirent instead of whiteouting it in their snapshot. The test runs the organic setup and one repair, then models that outcome with kvdb (drop the two whiteouts, point both roots at the reattach dirent) and asserts convergence. Result on master, before the same loop abort: inode_points_to_missing_dirent and inode_dir_missing_backpointer alternating on both roots, inode_wrong_backpointer, the dirent retyped dir to subvol, dirent_not_visible_in_parent_subvol, the subvolume's fs_path_parent rewritten to its sibling and the root's bi_parent_subvol with it. That is the report: a 2-cycle between check_inodes, which resolves a subvolume root's backpointer through its parent subvolume's leaf and finds nothing there, and check_dirents, which walks the dirent's descendant versions and puts the backpointer back. What put the field's roots in front of that fixup arm without backpointers is not reproduced here; with current code check_unreachable_inodes reaches the leaf versions first and reattaches them as subvol-N before the ancestor's fixup runs.walk_subvolsis fixed on the way:ls -d snap-* otherexits 2 whenotheris absent, and under pipefail the substitution takes the ERR trap, so capture_state died in every test that doesn't build the standard tree.Interrupted collapse under a container layer tree
The podman bcachefs graph driver snapshots each layer from its parent and replaces files by unlink and create, so a base-image inode is whited out in some layers and alive in others.
test_interrupted_collapse_replaced_filebuilds that shape, interrupts snapshot deletion mid-collapse withsnapshot_delete_bail_before_inodes(CONFIG_BCACHEFS_DEBUG), and asserts fsck finishes the collapse without reportingkey in missing inodefor extents the surviving layers still read; the field report had fsck delete them and the files read back as zeros._twiceadds a second interrupted round that moves the collapse terminal,_churnspreads snapshot ids past the is_ancestor bitmap with 150 create/delete cycles on the kept layer, as in the field, and_churn_userspaceruns the userspace fsck the field used, with BCACHEFS_KERNEL_ONLY unset since it silently overrides -K.interrupt_snapshot_deletion()is factored out oftest_interrupted_deletion.Two small runner commits ride along:
ktest: find virtiofsd off $PATHandtestrunner: only mount the nix store on nix hosts.Full logs of the runs quoted above are available on request.