Repository navigation
A block connector opens only the partitions its badge grants, and a write its grant does not make is refused ReadOnly; update and the slot table reach diskserver's disk through sessions - #826
Conversation
…and the slot table reach diskserver's disk through sessions The supervisor mints every connector to the block port with a `toyos_blockring::wire::Grant` as its badge (`SYS_PORT_MINT`): one partition by unique GUID, or every partition of one type, writing or not. diskserver reads the badge the kernel stamped on each connection and lists only what it admits, refuses an open of anything else `NotGranted` (whether or not the table carries it), answers a write on a session whose grant does not write `Invalid` before the device sees it, and reaches nothing through the port's own unbadged connector, which no program is handed any more. A file server on a served disk gets `block` minted for its role: the loader's LOG partition writing, the loader's BOOT volume read-only, every TOYOS-DATA partition writing. The block service starts before the file servers its grants are minted on. The slot grant takes the running ROOT from the loader's `Loaded(Root)` record instead of `PartState::Kernel`. Where the inventory lists that partition, the kernel drives its disk and the grant is claims as before; otherwise the supervisor mints read-only grants for the slot table's, the volumes' and the ROOTs' types, lists them and reads the table through a session on its file worker (a call into a service it restarts), checks the idle slot with `toyos_update::slots::grant`, and hands `update` a namespace of three connectors, each minted for one partition. `update` writes through `diskserver::disk::Disk`, which moves out of fileserver into diskserver's client library so both share one; fileserver keeps `Ram`. Closes the-block-port-opens-every-partition-of-the-disk and an-image-on-a-disk-diskserver-drives-cannot-write-its-slots; a-file-server-can-open-every-partition-diskserver-serves narrows to its claim half, the-boot-volumes-server-holds-a-claim-that-writes: a partition claim has no DUP to narrow, so the boot server's claim on the stick still writes until usbd serves it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
|
Mutation patches for head a81a401. Each was applied with m1-admissiondiff --git a/userland/diskserver/src/main.rs b/userland/diskserver/src/main.rs
index ca0ecd8aa..9157cd8e8 100644
--- a/userland/diskserver/src/main.rs
+++ b/userland/diskserver/src/main.rs
@@ -200,7 +200,7 @@ fn admitted(parts: &[Part], running: Option<[u8; 16]>, guid: [u8; 16], grant: Op
let part = parts.iter().find(|p| p.unique == guid && guid != [0; 16]);
// A partition the table does not carry has no type: only a unique grant
// naming it reaches it, to be told `NotFound`.
- if !grant.admits(guid, part.map_or([0; 16], |p| p.kind)) {
+ if false && !grant.admits(guid, part.map_or([0; 16], |p| p.kind)) {
return Err(Refusal::NotGranted);
}
let span = match part.map(|p| &p.span) {m2-read-onlydiff --git a/toyos-blockring/src/server.rs b/toyos-blockring/src/server.rs
index 5c66442ab..8b85ef3ed 100644
--- a/toyos-blockring/src/server.rs
+++ b/toyos-blockring/src/server.rs
@@ -88,7 +88,7 @@ impl ServerSession {
return Taken::Answer(Completion { tag, status: Status::Invalid })
}
};
- if !self.writes && matches!(request.op, Op::Write { .. }) {
+ if false && !self.writes && matches!(request.op, Op::Write { .. }) {
return Taken::Answer(Completion { tag: request.tag, status: Status::Invalid });
}
if self.inflight.iter().any(|&(tag, _)| tag == request.tag) {m3-boot-writesdiff --git a/userland/supervisor/src/main.rs b/userland/supervisor/src/main.rs
index 2e5169fc4..73e9ffa9b 100644
--- a/userland/supervisor/src/main.rs
+++ b/userland/supervisor/src/main.rs
@@ -2584,7 +2584,7 @@ fn storage_endowment(
let Some(guid) = loaded(&records, which) else {
return Err(StartError::Partition(format!("the loader named no `{role}` partition")));
};
- let grant = BlockGrant { scope: Scope::Unique(guid), writes: role == "log" };
+ let grant = BlockGrant { scope: Scope::Unique(guid), writes: true };
(on_kernel_disk(&|p| p.unique_guid == guid), Some(guid), grant)
}
other => panic!("supervisor: `{other}` is no role; the build refuses it"),m4-whole-change-revertedProduced by |
|
Review of #826 at head Net lines ( Checked and found sound:
BLOCKER
NOTE
SEND BACK |
|
T14 at |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
…volume is read back and checked; the two new jobs stay off the shared boot Review round 1 of #826, and the T14 reading at a81a401. - `partition_grant` and `update_idle_slot` are on `RUST_SKIP`: each needs its machine test's boot (a controller nothing claims and the inventory; a staged image and a second slot), and the T14's testcases list ran both and reddened. Their machine tests now name them literally, so `suite_split` sees them driven and reds if either leaves the list. - blockring gains `Status::ReadOnly` and `Outcome::ReadOnly` (word 4), and `Invalid` means malformed again: a refusal of authority no longer reaches a client as a malformed request, nor `Served::write`'s caller as a device failure (`DiskError::ReadOnly`, which fileserver answers `PermissionDenied`). The word on a read, a flush or a reissued write is a server that does not know what it answered, and a violation. - The update test reads slot B's FAT volume off the disk: toyos-fat32-check over the whole volume, then the kernel, the boot parameter and the signed header byte for byte against the staged image's sections. - diskserver reads every hang-up its clients already made before it judges an open, so `update`'s open of the slot table is never refused `Held` for the supervisor's session, which ended before `update` started. - `listed` and `admitted` take the drive's table or its refusal, so the grant is asked once, first, in the pure functions. - The two-copy slot table read is `toyos_update::slots::read`, which the supervisor's two branches and `update` call with their own reader. - `held_by_their_holders_alone` refuses a `slots` row in `[boot] start`: the supervisor grants the slots to a launch alone. - Prose this branch made false is deleted from toyos-manifest, src/build.rs and toyos-update's slots. - The launcher's move off its stick, which the closed issue recorded as waiting on this exit, is filed: issues/the-launcher-boots-off-a-stick-though-an-image-on-nvme-can-update-itself.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
… holds `Fat32::sync` flushes its device, and `Cached::flush` is that flush: the `into_device().flush()` after it found nothing held, which is why the review's mutation of it (m5, applied at fa1bcdd) left `update_writes_the_idle_slot_through_the_block_service` green, exit 0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
|
Round 2 mutation patches. Each was applied with m5-volume-unflushed--- a/userland/update/src/main.rs
+++ b/userland/update/src/main.rs
@@ -213,7 +213,7 @@
fs.flush_meta(&mut file, time).map_err(|e| format!("recording {path}: {e:?}"))?;
}
fs.sync().map_err(|e| format!("the idle volume's metadata: {e:?}"))?;
- fs.into_device().flush().map_err(|e| format!("the idle volume's blocks: {e:?}"))
+ { let device = fs.into_device(); let _ = device.capacity(); Ok(()) }
}
mod volume;m5b-volume-blocks-dropped--- a/userland/update/src/volume.rs
+++ b/userland/update/src/volume.rs
@@ -81,7 +81,8 @@
/// is the partition's flush, which the caller asks once every volume is
/// written.
fn flush(&mut self) -> Result<(), IoError> {
- let held = std::mem::take(&mut self.dirty);
+ self.dirty.clear();
+ let held: BTreeMap<u64, Box<[u8; BLOCK]>> = BTreeMap::new();
let mut run: Vec<u8> = Vec::new();
let mut first = 0u64;
for (n, block) in held {m6-readonly-word--- a/toyos-blockring/src/server.rs
+++ b/toyos-blockring/src/server.rs
@@ -92,7 +92,7 @@
return Taken::Answer(Completion { tag: request.tag, status: Status::Invalid });
}
if !self.writes && matches!(request.op, Op::Write { .. }) {
- return Taken::Answer(Completion { tag: request.tag, status: Status::ReadOnly });
+ return Taken::Answer(Completion { tag: request.tag, status: Status::Invalid });
}
self.inflight.push((request.tag, request.op));
Taken::Issue(request)m7-skip--- a/tests/toyos.rs
+++ b/tests/toyos.rs
@@ -192,7 +192,6 @@
// It claims the NVMe controller a boot off that disk starts no server
// for, and reads the inventory: `block_grants_reach_their_partitions`
// runs it on tests/blockgrantcase.
- "partition_grant",
// It installs the image its machine test staged into a second slot on
// the disk diskserver drives: `update_writes_the_idle_slot_through_the_block_service`
// runs it on tests/slotscase.m8-boot-start--- a/src/build.rs
+++ b/src/build.rs
@@ -595,7 +595,7 @@
}
// The supervisor grants the idle slot to a launch alone, so a row it
// starts at boot, or again, would run holding nothing.
- if program.slots && config.boot.start.contains(name) {
+ if false && program.slots && config.boot.start.contains(name) {
return Err(format!("`{name}` asks for `slots` and is in `[boot] start`, and the slots are granted to a launch alone"));
}
} |
|
Review of #826 at head Round 1's BLOCKERs:
Round 1's NOTEs are addressed:
Net lines ( Gates at 4b4991b, from logs that each begin with their head:
BLOCKERNone open. NOTE
LAND AFTER NAMED CHANGES |
|
T14 at |
… it, and diskserver reads a hang-up in one place The review of round 2 asked the BOOT server to carry DiskError::ReadOnly through Bytes or to say why it cannot arrive. Carrying it is not small: toyos-fat32 has one device word because its repair contract reads every refused write as of unknown outcome, and a second word would reach into that crate's queued-repair machinery. It cannot arrive: the one read-only grant is the BOOT server's, whose volume is mounted unwritable, whose clients are refused every changing operation before the volume (rights::changes), and whose close and sync write nothing. The reason sits at the impl, and a_read_only_volume_never_writes_its_disk holds the last clause on a disk that panics on a write or a flush: m9, which drops sync's unwritable return, is red with "a read-only volume flushed its disk". diskserver's two reads of a session's doorbells, in place and in the loop, are one Service::hear. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
…nsole wire (#805), into the per-partition block authority Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
|
Round 3 mutation patch, run on 4b4991b with this round's edits uncommitted, which 5093967 commits unchanged ( m9-read-only-sync-writes
--- a/userland/fileserver/src/fat.rs
+++ b/userland/fileserver/src/fat.rs
@@ -402,9 +402,6 @@
fn sync(&mut self) -> Result<Vec<(Node, SyscallError)>, SyscallError> {
let mut unlevel = Vec::new();
- if !self.writable {
- return Ok(unlevel);
- }
let nodes: Vec<Node> = self.open.keys().copied().collect();
for node in nodes {
match self.level(node) { |
…h toyos-sha2 - userland/update/src/main.rs: the branch's stream_root over the block service's Disk (STREAM_BLOCKS of BLOCK bytes), hashed with main's toyos_sha2::Sha256 in place of sha2. - tests/toyos.rs: both sides' RUST_SKIP entries, MACHINE_TESTS entries, dispatch arms and functions kept, the branch's first; the branch's update_writes_the_idle_slot_through_the_block_service keeps its own close. - Cargo.lock: main's, regenerated by cargo against the merged manifests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
|
Merge of
Gates at d07350c: |
|
Merge head build-x86_64build-aarch64 |
|
Merge head |
|
Merge head |
|
Merge head |
|
Merge head |
|
Merge head |
|
Merge head |
|
Merge head |
|
Merge head |
|
Merge head |
|
Merge head |
|
Merge head |
…rity (#826), into consent Three files conflicted; every hunk of both sides is kept. - src/build.rs: ALL_CONFIGS lists both tests/blockgrantcase and tests/consentcase, in order. The holders test keeps #826's case that a `slots` row in `[boot] start` is refused, beside this branch's grants cases and its two new tests. - userland/supervisor/src/main.rs: a launch starts with #826's `slot_storage` and this branch's `minted` launcher pair; build_namespace withholds the swap, grants and block ports from `receives`, with both sides' reasons. - tests/toyos-rust-tests/Cargo.lock: main's lockfile, with `cargo update --workspace --offline` adding this branch's toyos-desktop. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
…toyos-sha2-hw #826 moved update's stream_root onto the block service (a `&mut dyn Disk`, STREAM_BLOCKS bytes per read and write); this branch had routed its hasher through toyos-sha2-hw. The resolution keeps #826's function whole and takes the branch's `toyos_sha2_hw::sha256()` for its hasher, as every other SHA-256 caller on this branch does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
Clean: #826 touches tests/common/qemu.rs and tests/toyos.rs away from the AMD-Vi profile and row. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
…uthority (#826), the SMMUv3 bring-up (#825), the hotkey removal (#824) and evidence on the pull request (#835), into virtio-sound and the shared PCI claim Two conflicts, both resolved by keeping every hunk of both sides: - tests/common/qemu.rs: the virtio-gpu device arm (this branch) and the two iommu-testdev arms (#825) are separate `if`s on separate Shape fields; HeadlessVirtioGpu and VirtSmmu are disjoint profiles, so no machine gains or reorders a device. - tests/toyos.rs RUST_SKIP: virtio_sound_counts (this branch) and partition_grant, update_idle_slot (#826) all kept. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
… batch: the icons' and wallpaper's digests hash with toyos-sha2 Cargo.toml keeps both sides' entries: main's toyos-sha2 and usbd members and toyos-sha2 dependency, and the batch's removal of `image`. `sha2` was replaced by toyos-sha2 on main and left in place on the batch, whose #813 and #814 added two tests hashing with it; as main meant every SHA-256 the build takes to be toyos-sha2's, those two tests now hash with toyos-sha2 and the root manifest drops `sha2`. Cargo.lock is the batch's, re-resolved by `cargo metadata --offline`; its delta from the batch's head is exactly main's delta from the merge base. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
, #826, #835, #833, #831 and #822, into wt/toyos-netperf No hunk conflicted. userland/netstack/src/main.rs took both sides: main's removal of `mod device` and the branch's batched `node.receive` and its module-doc line. The TCP window-scaling and loss-probe commits main carries were already in the branch from #820, so their files merged to main's text plus the branch's own delta. Both lockfiles are main's and pass `cargo metadata --locked`. The branch's new issue still cites `VirtioNet::poll_rx` and `toyos_i219::RX_BUDGET` as they stand on main. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
Stage P of taking USB storage out of the kernel: per-partition block authority, independent of usbd. A block connector opens only the partitions its badge grants;
updateand the supervisor's slot table reach diskserver's disk through sessions; the running ROOT is the loader'sLoaded(Root).What changed, per decision
The badge is the authority (
toyos-blockring/src/wire.rs,Grant/Scope,Refusal::NotGranted). The supervisor mints every connector toblockwithSYS_PORT_MINT:Scope::Unique(guid)(one partition) orScope::Kind(type)(every partition of one type), withwrites. A type scope exists for DATA, which the supervisor cannot name by unique GUID without asking diskserver at the file server's start; a file server already refuses two DATA partitions by name (fileserver::data::find).diskserver honours it (
userland/diskserver/src/main.rs): it reads the badge the kernel stamped on each connection (SYS_PORT_BADGE). A listing names only what the grant admits. An open of anything else is refusedNotGranted, whether or not the table carries it. The grant is asked first, then the drive, then the table, so a connection learns nothing of a partition it cannot reach. An unbadged connection, or a badge no minter of this protocol stamps, reaches nothing. The decisions are pure functions, host-tested:listedandadmittedtake the drive's table or its refusal (Drive::parts), so the grant is checked once.Read-only sessions, refused by their own word (
toyos-blockring):ServerSession::new(first, blocks, writes). A write on a session whose grant does not write is answeredStatus::ReadOnly(word 4), unissued, and the client hearsOutcome::ReadOnly.Invalidmeans a malformed request again.Served::writemaps the word toDiskError::ReadOnly. DATA's server answers itPermissionDenied, though DATA's grant always writes, so that arm is not reached today. The BOOT server, the one holder of a read-only grant, turns every disk refusal intotoyos-fat32's one device word,IoError::Device, and does not carryReadOnly: carrying it is not small, sincetoyos-fat32reads every refused write as of unknown outcome and queues its repair, so a second device word reaches into that contract. It never meets the word: its volume is mounted unwritable, its clients are refused every changing operation before the volume (rights::changes), and its close and sync write nothing. That reason is a comment atfat.rs'sBlockAccessimpl, anda_read_only_volume_never_writes_its_diskholds the last clause on a disk that panics on a write or a flush (m9). AReadOnlyanswer to a read, a flush or a reissued write is a server that does not know what it answered, so the client treats it as a violation (read_only_answers_a_write_alone).The port's own connector is handed to no one (
build_namespaceskipsblockfromreceives). A storage row'sblockis the connector minted for it (Storage::ports, folded into the namespaceview):Unique(loader's LOG), writing.Unique(loader's BOOT), read-only.Kind(TOYOS-DATA), writing.A block port closed for good refuses the role's start by name. The block service row now starts before the file servers whose grants are minted on its acceptor (
boot's rank).A client that hung up holds nothing (
Service::place). Before an open is judged, diskserver reads every hang-up its clients have already made and retires those sessions. The supervisor's read-only session on the slot table ends beforeupdatestarts. Without this,update's open of the same partition depended on diskserver having polled that hang-up first.placeand the loop read a session's doorbells through one method,Service::hear.The slot grant (
Supervisor::slot_grant). The running ROOT isLoaded(Root), notPartState::Kernel.claimed_slots). This is the branch usbd's cutover deletes.Kindgrants for SLOTS, BOOT and ROOT, lists them, and reads the table through a session on its file worker (Supervisor::files). A call into a service it restarts, made from the loop, would wait for a process only the loop can start. It checks the idle slot withtoyos_update::slots::grantand endowsupdatea namespace of three connectors underslots:table/slots:boot/slots:root, eachUniqueand writing.serve_launchand passed tostartas itsStorage.held_by_their_holders_alone(src/build.rs) now refuses aslotsrow in[boot] start: the slots are granted to a launch alone, and such a row would otherwise run holding nothing.toyos_update::slots::read. The supervisor's two branches andupdateeach call it with their own reader.updatewrites throughdiskserver::disk::Disk.Disk,ClaimedandServedmove fromfileserverinto diskserver's client library, soupdateand the file servers share one.fileserverkeepsRam(ram.rs).updatetakes a claim where one is endowed under a label. Otherwise it lists and opens the connector of that name, which is minted for exactly one partition.write_volumeends atFat32::sync, which flushes its device, and that flush isCached::flush. Theinto_device().flush()that followed found nothing held and is deleted (m5 below).The two new jobs are off every shared list.
partition_grantandupdate_idle_slotare onRUST_SKIP, each with the reason its machine test exists. Their machine tests name them literally (run_test("test_rs_…")), sosuite_splitsees them driven and goes red if either leaves the list (m7). Before this round both were named throughformat!, whichsuite_splitcannot read. The T14'stestcaseslist therefore ran both at a81a401, and both went red.Prose this branch made false is deleted, on the orchestrator's widened fence:
toyos-manifest/src/lib.rs(theslotsrecord andProgram::slots),src/build.rs(ProgramConfig::slots),toyos-update/src/slots.rs(the labels,NotThisBoot,grant,idle).Issues:
the-block-port-opens-every-partition-of-the-diskis closed; its exit isblock_grants_reach_their_partitions.an-image-on-a-disk-diskserver-drives-cannot-write-its-slotsis closed; its exit isupdate_writes_the_idle_slot_through_the_block_service. It also recorded that the launcher boots off a stick until that exit is met. That consequence is filed asissues/the-launcher-boots-off-a-stick-though-an-image-on-nvme-can-update-itself.md(owner: the launcher,src/qemu.rs; exit:cargo run's machine boots off its NVMe disk andupdateinstalls into its idle slot). Moving the launcher changes wheretarget/nvme.img's DATA and LOG live across rebuilds, which is more than a line.a-file-server-can-open-every-partition-diskserver-servesnarrows to its unmet half and is renamedthe-boot-volumes-server-holds-a-claim-that-writes. A partition claim has noDUP, so the supervisor cannot hand the boot server a duplicate narrowed to reading. Its exit is now usbd's read-only session, or a kernel claim minted withoutWRITE.No citation of any of the three closed slugs exists (
git grep).After this, every userland caller of
SYS_PARTITION_*is a "partition on a disk the kernel drives" branch (git grep -n 'SYS_PARTITION_\|partition_read\|partition_write\|PartitionDev\|claim_partition' -- ':!kernel' ':!issues' ':!toyos-abi/src/syscall.rs'):userland/supervisor/src/main.rs:claimed_slots, the claim of the slot table on the kernel's disk and itspartition_readreader;storage_endowment'son_kernel_diskclaims;DeviceRequest::Partitionarm of a manifestdevicesrow, which no config in the tree uses.userland/diskserver/src/disk.rsClaimed, used by:userland/fileserver/src/main.rs'sclaims()/describe();userland/update/src/main.rs'sheld()when a claim is endowed.toyosandtoyos-abi.Gates (head d07350c)
d07350c merges
origin/mainat 38df0b5 (#812, #820, #827) into 4ab3203. The previous head, 4ab3203, had mergedorigin/mainat a1eb2c0 (#805) into 5093967, this round's commit. The merge's conflicts were inuserland/update/src/main.rs,tests/toyos.rsandCargo.lock. Each resolution is noted in a comment on this PR, and the scrubbed logs follow it, each starting with its head and command.cargo run -- --ci hostHost: 78 step(s), all green)cargo run -- --build-onlycargo run -- --build-only --arch aarch64cargo test(whole guest suite)53 passed, 53 total, includingPASS block_grants_reach_their_partitions,PASS update_writes_the_idle_slot_through_the_block_serviceand #827'sPASS usbd_drives_the_spareThe host was shared with other agents.
uptimejust after the suite:load averages: 23.51 43.54 49.13.Metal. At a81a401 the orchestrator's T14 run of
boot:testcasesgave EXIT=1,249 passed, 2 failed: the two new QEMU-only jobs, run without the harness's setup. At 4b4991b it gave EXIT=0,[metal] 249 passed, 0 failed, 2 boot(s), each boot'stoyos-metal --fat32-checkexit 0, and neither QEMU-only job ran (the orchestrator's comment on this PR). Round 3's own delta changes no successful path:fat.rsgains a comment and a host test, andService::hearreads doorbells as the two reads it replaces did, except that it skips a session already closing, whose bytes nothing takes. The merges bring main's #805, #812, #820 and #827 into the image, and that reading booted none of them.Checks of high-risk code (security boundary: storage authority)
Each mutation was applied as a checked patch (
git apply --check), built and run, then restored withgit apply -Rin the same script. Each log endsrestored: []. Round 1's (m1–m4), round 2's (m5–m8) and round 3's (m9) patches are posted as comments on this PR.admitted()no longer asks the grantcargo test -p diskserver; guestblock_grants_reach_their_partitionsan_open_reaches_only_what_its_grant_admits;the log's grant opened the boot volume, which nothing holdscargo test -p toyos-blockring; guestblock_grants_reach_their_partitionsa_read_only_session_refuses_a_write_unissued;a session whose grant does not write wrote the boot volumenvme_disk_keeps_log_and_homediskserver opened 0 read-only sessions, and only the slot's volume is granted oneCargo.lockback toorigin/mainno slot to grant: the machine has 0 ROOT partitions the kernel holds, the recorded failure of the base on aStorage::Diskmachinewrite_volumedropsinto_device().flush()update_writes_the_idle_slot_through_the_block_serviceFat32::syncalready flushed the device. The line was dead and is deleted at 4b4991bCached::flushdiscards every held block, so the volume's writes never reach the diskslot B's volume: the volume has no toyos/image.sig, whileupdatestill saidinstalled version … in slot Band the table and ROOT checks passedInvalidcargo test -p toyos-blockring; guestblock_grants_reach_their_partitionsa_read_only_session_refuses_a_write_unissued;left: Ok(Invalid) right: Ok(ReadOnly)partition_grantleavesRUST_SKIPcargo test --test toyos-checks -- suite_split["partition_grant"] are driven by a machine test and also run on the shared boot[boot] startrefusal is skippedcargo test --lib -- build::tests::only_their_holders_may_hold_the_swap_port_and_the_slotsbootedcaseFatVolume::syncdrops its unwritable returncargo test -p fileserver -- fat::tests::a_read_only_volume_never_writes_its_diska read-only volume flushed its diskIndependent oracles for the update. After the guest reports
update: installed version N in slot B, the host reads the guest's disk file itself:slot_table_of, through the host's GPT reader, must mark B at version N.root_file_onmust mount slot B's ROOT with the host's bcachefs reader and find the staged marker byte for byte.partition_extentcuts slot B's BOOT volume out of the disk, andtoyos_fat32_check::checkjudges it whole. That checker shares no code withtoyos-fat32, whichupdatewrites with.toyos/image.sig,toyos/kernel.elfandtoyos/cmdlineare read off that volume and compared byte for byte with the signed header, the kernel and the boot parameter cut from the staged image.Where reading has to suffice.
placehas no test that can fail. The race it closes needs diskserver to judgeupdate's open before it polls the supervisor's hang-up, and no guest arranges that on demand. With the read, the order is causal: the hang-up happens beforeupdateexists, and it is read before the open is judged.Why the new guest tests need QEMU
block_grants_reach_their_partitions(tests/blockgrantcase,HeadlessNoUsb, jobpartition_grant). Host tests cover the grant's decisions, in diskserver (an_open_reaches_only_what_its_grant_admits,a_listing_names_only_what_its_grant_admits,an_unusable_or_unclaimed_controller_is_refused_and_an_absent_one_lists_nothing) and toyos-blockring (a_grant_reaches_its_own_partitions_and_decodes_only_whole,a_read_only_session_refuses_a_write_unissued,read_only_answers_a_write_alone). The existingport_badgeguest test already holds that a connection's badge is the one its connector was minted with. What only a guest reaches is the wiring: diskserver applying each grant, andwritesreachingadmit, on a claimed NVMe controller against real partitions nothing else holds. That has no host build, and the T14 boots from a stick, so no disk diskserver drives there carries a partition the loader named. The job starts diskserver itself, holding its claim and the acceptor, and mints each grant as the supervisor does.update_writes_the_idle_slot_through_the_block_service(tests/slotscase,HeadlessNoUsbwith a second slot, jobupdate_idle_slot). It runs the supervisor's session read of the slot table, the launcher's slot grant, andupdatewriting and marking through diskserver. None of these has a host build, and the T14's slots are on its stick, which only the kernel drives until usbd. test-runner is aloginrow listingupdate, so the job's launch opens a login session.nvme_disk_keeps_log_and_homegains one assertion: exactly one of the three sessions is read-only. This is what makes the supervisor's BOOT grant observable (m3).Dependencies
No new external crate.
supervisorandupdatedepend on the in-treediskserverlibrary (the session client andDisk).updatedropstoyos-abi.tests/toyos-rust-testsaddsdiskserver,toyos-blockringandtoyos-gpt, all in-tree.Size
git diff --shortstat origin/main...4ab32038a: 37 files, +1370 −448.tests/)Against round 1 (production +726 −304 at a81a401), this table counts +114 −41 more (its split puts
src/build.rs, the twoALL_CONFIGSrows included, under production): theReadOnlyword through blockring, diskserver and fileserver with its client test,slots::readin place of three hand-written reads, the hang-up read inplace, and the build gate. Round 3 adds +58 −14 to production: the reason atfat.rs's impl with its host test, andService::hearin place of the two doorbell reads.What I am unsure of
serve_launchin one line (let storage = self.slot_storage(program)and passing it tostart) and removestart'sprogram.slotsblock. A package's launch asks the person at the screen for a folder of the home, in a prompt nothing else can cover or type into; grants keep the answer per exact binary #819 edits the same function.updatewaits on the file worker for the table read, up toFILES_BOUND.slots::read's home. It sits intoyos-update, whose manifest says pure. It does no I/O itself: the caller's closure reads the blocks.🤖 Generated with Claude Code
https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C