# Summary: drop_progress reset on resumed snapshot deletion, A/B test

Bug: `btrfs_drop_snapshot()` resume path restores `wc->update_progress` from the root item but
leaves `wc->drop_progress`/`wc->drop_level` zero. A checkpoint before the resumed run drops its
first reference writes drop key (0 0 0) level 0. The next mount restarts the drop from slot 0,
treats children shared with a surviving snapshot as solely owned and tries to free them again:
transaction abort (`unable to find ref byte nr ...`, `errno=-117`), backref mismatches, and the
surviving snapshot left unreadable (EIO).

Patch: 0001-btrfs-keep-drop_progress-when-resuming-snapshot-deletion.patch
(`git format-patch` against v7.3.1): copy the resume key into `wc->drop_progress` and set
`wc->drop_level` on resume.

## Result (testvm, Ubuntu 26.10, 7.3.0-9-generic)

repro-simple2.sh (minimal, ~70 s), 3 runs per module:

| Module | Abort (`unable to find ref`) |
|---|---|
| stock btrfs.ko.zst | 3/3 |
| self-built, unpatched | 3/3 |
| self-built, patched | 0/3 |

repro-simple.sh (full oracles), 3 runs per module:

| Module | Drop key zeroed | Abort | `btrfs check --readonly` | S2 content vs S1 manifest | Drop completed |
|---|---|---|---|---|---|
| stock | 3/3 | 3/3 | 3/3 errors | 3/3 unreadable (EIO under S2/data) | 0/3 |
| self-built, unpatched | 3/3 | 3/3 | 3/3 errors | 3/3 unreadable (EIO) | 0/3 |
| self-built, patched | 0/3 | 0/3 | 3/3 clean | 3/3 identical | 3/3 |

Patched: drop key stayed at the step-3 resume point through 20 mount/umount cycles; the next
uninterrupted mount finished the drop (~60 s); no BTRFS errors in dmesg.

fstests, `-g quick`, FSTYP=btrfs, unpatched vs patched module (same kernel, same devices),
three passes as the test setup was fixed:

| Pass | Module | Selected | Not run | Executed | Passed | Failed | `.dmesg` |
|---|---|---|---|---|---|---|---|
| 1 | unpatched / patched | 939 | 195 | 744 | 732 | 12 | 0 |
| 2 | unpatched / patched | 939 | 177 | 762 | 762 | 0 | 0 |
| 3 | unpatched / patched | 939 | 173 | 766 | 765 | 1 | 0 |

- In every pass the ran, not-run and failed lists are identical between the two modules.
- Pass 1 failures (12, all from the test userland, byte-identical .out.bad in both modules):
  uutils coreutils message/option differences (generic/193 294 321 356 452 597 634 635,
  btrfs/313), locktest not building against glibc 2.44 (generic/131), no `newuidmap`
  (btrfs/300), `TERM` unset under nohup (btrfs/058).
- Pass 2 setup: GNU coreutils (`coreutils-from-gnu`), uidmap, TERM, local locktest build fix
  fsverity, acct, libbtrfs-dev (t_btrfs_received_uuid_ioctl),
  5 x 5 GB pool (btrfs/197 199 217, generic/620), LOGWRITES_DEV (dm-log-writes tests).
- Pass 3 setup: keyutils, `btrfs-corrupt-block` built from the btrfs-progs 7.1 source (only that
  tool; mkfs/btrfs stay the distro ones). Newly run: btrfs/290, generic/574, generic/577, generic/625.
  generic/574 (fs-verity corruption detection, 1K verity block size) fails in both modules: reads
  of the corrupted 1K-block file succeed. Not related to the patch; not investigated.
- Still not run (173): mostly btrfs-inherent (VFS quota, fscrypt, exchangerange, collapse/insert
  range, sectorsize, dax, casefold, atomic writes); kernel config (fail_make_request, dm-dust,
  SELinux, zoned, read_policy); raid-stripe-tree needs experimental btrfs-progs (5);
  block-group-tree default in mkfs (3); 16 GB scratch (generic/781 793); send stream v3 (btrfs/277).
- fstests: master 22348afe338c (2026-09-20). Devices: loop on sparse files on ext4. ~45 min per pass.

Regression test btrfs/355 (new, not upstream; fstests/355 here),
final 30k-file version, SATA SSD (same pattern with 100k files and on tmpfs):

| Module | Result | Drop key after 1st resume | Abort | S2 | btrfs check |
|---|---|---|---|---|---|
| stock | 3/3 fail | 0 | 3/3 | changed | errors |
| self-built, unpatched | 3/3 fail | 0 | 3/3 | changed | errors |
| self-built, patched | 3/3 pass | unchanged over 5 resumes | 0/3 | identical | clean |

- No dm-delay: 30k files with one 3000-byte xattr each at nodesize 4096 give a level-3 tree;
  sync-poll until a root child is dropped, unmount; then mount + immediate unmount (5x) and read
  the drop key with dump-tree after each; finally let the drop finish and compare S2's xattrs.
- Skips (notrun) instead of passing if the tree is too shallow or the first run can't be caught.
- 15-17 s per run on testvm (100k-file version ~52 s).
- Final version: 30000 files (still level 3), groups `auto quick snapshot subvol`.
  SATA SSD: stock 3/3 fail, unpatched 3/3 fail, patched 3/3 pass, 15-17 s
  (logs/btrfs-355/355-30k/). Loop files on tmpfs: 30k and 100k
  files, unpatched 3/3 fail and patched 3/3 pass each, no notrun (logs/btrfs-355/355-ram/).
- Header comment and fstests commit message trimmed (code unchanged): unpatched 2/2 fail,
  patched 2/2 pass (logs/btrfs-355/355-trim/).

## Environment

- testvm: Xen HVM guest, Ubuntu 26.10 cloud image, 4 vCPU, 5 GB RAM, root ext4.
  Kernel 7.3.0-9-generic (Ubuntu 7.3.0-9.9, based on v7.3-rc6), btrfs-progs from Ubuntu.
- `module.sig_enforce=N`: unsigned self-built modules load (taint 12800).
- Stock module: /lib/modules/7.3.0-9-generic/kernel/fs/btrfs/btrfs.ko.zst (srcversion E1EF4DA3...).
- Self-built modules:
  - Source: Ubuntu source package `linux 7.3.0-9.9` (`apt-get source linux=7.3.0-9.9`),
    the exact source of the running kernel; not an upstream tag (Ubuntu carries own patches).
    Bug present there unchanged.
  - .config and Module.symvers from `linux-headers-7.3.0-9-generic`; `olddefconfig` differs only
    in Rust symbols (no rustc installed; btrfs has no Rust).
  - Same toolchain as the stock build: gcc 15.3.0-4ubuntu1, ld 2.47, pahole 1.31.
  - Both builds: vermagic, `.gnu.linkonce.this_module` size (0x500) and full CRC table equal stock.
  - Unpatched and patched differ only by the patch.
  - SHA-256: logs/ab/modules.sha256; script hashes in the run logs.

## Reproducers

- `repro-simple2.sh` (root, 24 lines): 2G loop + dm-delay (read 15 ms, write 5 ms),
  `mkfs.btrfs -n 4096`, S1 with 100000 files (mkfiles.py), `snapshot -r S1 S2`, delete S1,
  sync + drop_caches, umount; mount + umount; mount, `btrfs subvolume sync` up to 150 s.
  Prints `BUG: ...` and exits 1 on the abort.
- `repro-simple.sh` (root): same setup with oracles: drop key via `dump-tree -t root` between
  steps, dmesg after a /dev/kmsg marker, `btrfs check --readonly`, S2 manifest (sha256 of data
  and xattrs, mounted ro,noatime) compared with S1's. Exit 0 bug, 1 correct, 2 inconclusive.

## Limits

- One host (testvm), one kernel (Ubuntu 7.3.0-9-generic = v7.3-rc6 + Ubuntu), one tree shape
  (level 3, nodesize 4096; 100k files for the scripts, 30k and 100k for btrfs/355), 3 runs per
  module and script.
- Upstream v7.3.1 / v7.4-rc1 not run, only built (v7.3.1) and source-checked.
- fstests: `-g quick` only (not `-g auto`), one run per module per pass; 173 tests not run.
- btrfs/355: one machine (4 vCPU, several-year-old SATA SSD); also run with loop files on tmpfs
  (faster than any NVMe). Not checked on HDD or a much faster CPU (a missed window gives notrun
  or a false pass, not a false fail).
