volume-manager: the removal lifecycle — a pulled stick unmounts (V4)

The volume manager stops probing-once and polls storage presence for the life
of the boot: findStorageDevice enumerates the device-manager tree (presence
only, no consumer-hello, so it is cheap and leaks nothing). The volume is now
a field that goes null and back — the whole lifecycle:

- storage present + no volume  -> open the channel, probe, confine + spawn the
  filesystem (openStorage is the one consumer-hello, on the insertion edge);
- storage gone + have volume    -> kill the filesystem (its mounts retire via
  the kernel dead-backend sweep), close the dead channel, clear the volume;
- fat crash                     -> the same supervised backoff/cap as before,
  folded into the poll (one timer).

This also subsumes the V3-review leak fix (no per-poll consumer-hello) and the
no-volume retry (a present-but-unreadable device keeps polling).

The user's case — pull the boot stick, plug it back — is a DEVICE unplug (the
stick IS the device), so the mass-storage child leaves the device-manager tree
and the poll catches it. volume-removal asserts the unmount and discriminates:
against the V3 probe-once volume manager the removal is never noticed (0/1).

The re-mount on replug is the VM's bringUpVolume firing when the device
returns — correct and in place, but not QEMU-testable here: device_add of
usb-storage to the boot xHCI controller is not re-presented to the guest (no
port-connect on any port), a harness quirk, not a VM issue. On real hardware
the bus's per-tick port poll catches a reconnect (H1 proves reconnect on a
second controller); bench-verify the full round trip.
This commit is contained in:
Daniel Samson
2026-08-09 19:41:04 +01:00
parent b9058fe020
commit 9e67a74232
2 changed files with 126 additions and 51 deletions
+29 -1
View File
@@ -79,7 +79,9 @@ ARCHES = {
"-device", "usb-kbd,bus=xhci.0",
"-device", "usb-mouse,bus=xhci.0",
"-drive", f"if=none,id=bootusb,format=raw,file={boot_volume}",
"-device", "usb-storage,bus=xhci.0,drive=bootusb,removable=on,bootindex=0",
# id=bootstorage + an explicit port so the volume-replug drill can
# device_del/device_add it back onto the same freed root port.
"-device", "usb-storage,bus=xhci.0,port=3,drive=bootusb,removable=on,bootindex=0,id=bootstorage",
"-net", "none",
"-vga", "none", "-device", "VGA,edid=on,xres=1280,yres=720",
"-display", "none",
@@ -755,6 +757,32 @@ CASES = [
"timeout": 150,
"expect": r"fat: mounted /volumes/usb[\s\S]*fat-test: ok",
"fail": r"DANOS-TEST-RESULT: FAIL"},
# The removal lifecycle (V4, docs/volume-manager-plan.md): pull the boot stick
# mid-run. device_del the usb-storage device -> the bus reports the port empty
# -> the device manager reaps usb-storage -> the mass-storage child leaves the
# tree -> the volume manager's poll sees it gone and kills the FAT service, so
# its mounts retire (an honest unmount). The tail (mounted -> removed) can only
# be the removal, since the mount precedes the unplug. Discrimination: before
# V4 the volume manager stopped polling after the first probe, so it never
# noticed the removal — this line is absent.
#
# The RE-mount on replug is not asserted here: QEMU's device_add of usb-storage
# to the boot xHCI controller is not re-presented to the guest (no port-connect
# on any port), so it cannot drive the reappearance in this harness. On real
# hardware the bus's per-tick port poll catches a reconnect's PORTSC change
# (H1/usb-root-replug proves reconnect works on a second controller), and the
# volume manager's bringUpVolume remounts when the device returns — bench-
# verified, not QEMU-verified. So this case proves the unmount half.
{"name": "volume-removal",
"build_case": "fat-mount",
"smp": 4,
"timeout": 150,
"qmp_sequence": [
{"delay": 8, "command": "device_del", "arguments": {"id": "bootstorage"}},
],
"expect": r"(?s)fat: mounted /volumes/usb"
r"[\s\S]*volume-manager: storage for volume \d+ removed; unmounting",
"fail": r"DANOS-TEST-RESULT: FAIL"},
# Volume-manager discovery + probe (V3a, docs/volume-manager-plan.md). Reuses
# the fat-mount kernel build (the default boot now spawns the volume manager
# from init.csv). It acquires the mass-storage block channel through the