volume-manager: the flip — fat is spawned, confined, and handed its channel (V3b)

The load-bearing step. The FAT service stops acquiring its own volume: the
volume manager spawns it (per volume), defines its partition range on the
storage driver BEFORE it runs, and answers its startup hello with the
range-confined block channel over a new volume-manager protocol. fat never
finds its storage by name and never sees the whole device — establishment
by lineage, one layer up from the driver tree.

- New library/protocol/volume-manager: one verb, hello(volume-id) -> the
  block channel as the reply capability (the P0 reply-cap path).
- The volume manager becomes the confinement CONTROLLER: it defines the first
  range on usb-storage, so no other party can confine a filesystem. It
  supervises the filesystems it spawns and respawns one on death (the reap-
  and-rebuild the device manager proved, one layer up).
- fat: drops acquireVolume(device-manager); hellos the volume manager for its
  channel; reads its volume id from argv[1]. main takes process.Init now.
- init.csv no longer spawns fat (the volume manager does); protocol.csv
  rewires fat to be supervised by the volume manager (bind vfs, open
  volume-manager) and drops fat open device-manager.
- The block-range fixture boots registry + device-manager only (not the full
  tree), so the volume manager is absent and the fixture stays the sole
  confinement definer — otherwise the volume manager would take the
  controller first and refuse it.

Verified end to end (VM probes -> spawns fat -> confines it -> hands over the
channel -> fat mounts) and neutral: 18/18 across the fat family, logging,
shutdown, both IOMMU variants, usb restart, vfs, conformance, confinement.
This commit is contained in:
Daniel Samson
2026-08-09 18:28:44 +01:00
parent d56b1b81c0
commit 301bdcaf5b
10 changed files with 205 additions and 104 deletions
+1
View File
@@ -31,6 +31,7 @@ pub fn build(b: *std.Build) void {
.{ .name = "block-protocol", .root = "block/block-protocol.zig" },
.{ .name = "usb-transfer-protocol", .root = "usb-transfer/usb-transfer-protocol.zig" },
.{ .name = "device-manager-protocol", .root = "device-manager/device-manager-protocol.zig" },
.{ .name = "volume-manager-protocol", .root = "volume-manager/volume-manager-protocol.zig" },
.{ .name = "display-protocol", .root = "display/display-protocol.zig" },
.{ .name = "scanout-protocol", .root = "scanout/scanout-protocol.zig" },
.{ .name = "power-protocol", .root = "power/power-protocol.zig" },
@@ -0,0 +1,36 @@
//! The volume-manager protocol (docs/file-system-development/storage-architecture.md):
//! what a filesystem service says to the volume manager over
//! `/protocol/volume-manager`. Defined through the envelope, so every packet
//! begins with the folded `Header`.
//!
//! One verb. A filesystem the volume manager spawned announces itself with the
//! volume id it was given as argv[1] (folded into `Header.target`); the reply
//! carries that volume's block channel — already range-confined to the
//! filesystem's badge — as the call's returned capability. The filesystem never
//! finds its storage by name and never sees the whole device; establishment is
//! by lineage, exactly as a driver reaches its controller (communication.md
//! "Establishment: two planes"). No channel in the reply means the volume is not
//! ready yet — retryable, never a verdict.
const envelope = @import("envelope");
pub const version: u16 = 1;
/// The filesystem's handshake. Carries only its protocol version; the volume it
/// serves is `Header.target`, and the block channel it needs comes back as the
/// reply's capability.
pub const Hello = extern struct {
version: u16 = version,
_padding: u16 = 0,
};
pub const Protocol = envelope.Define(.{
.name = "volume-manager",
.version = 1,
.operations = &.{
.{ .name = "hello", .request = Hello },
},
});
pub const Operation = Protocol.Operation;
pub const message_maximum: usize = Protocol.message_maximum;