From 3fd6d46983ac8e8f1b3a2d5ada7acae34577398b Mon Sep 17 00:00:00 2001 From: japabu Date: Tue, 20 Oct 2026 19:49:01 +0200 Subject: [PATCH 1/4] diskserver refuses no session for being the running ROOT The owner decided (2026-10-20) that explicit grants alone govern access to the partition the machine runs from. diskserver's `--running ` flag made it refuse every session to that partition `Held`, on top of the grant its connector was minted with. Delete the flag, its parse, the `running` field and argument through `Service` and `admitted`, the log line naming the partition, and its unit-test assertions; the supervisor no longer reads the inventory to tell the block service the loaded ROOT, so `storage_endowment` now serves file servers only and loses its `program` parameter. Ordinary authorization is unchanged: a connection with no grant reaches nothing, a grant admits only its unique GUID or kind, and a session whose grant does not write answers every write `ReadOnly`. No grant the supervisor mints writes the running ROOT: file servers get DATA by kind, the log and boot volume by unique GUID; the slot reader lists ROOTs by kind read-only; the updater's write grants name only the idle slot, which `toyos_update::slots::grant` refuses to be the running one's. The issue that asked for the refusal is now a rejected record. Co-Authored-By: Claude Opus 5.5 --- ...r-serves-the-slot-the-machine-runs-from.md | 24 ------ ...ervice-does-not-refuse-the-running-root.md | 17 ++++ system/diskserver/src/main.rs | 83 ++++++------------- system/supervisor/src/main.rs | 29 ++----- 4 files changed, 49 insertions(+), 104 deletions(-) delete mode 100644 issues/diskserver-serves-the-slot-the-machine-runs-from.md create mode 100644 issues/the-block-service-does-not-refuse-the-running-root.md diff --git a/issues/diskserver-serves-the-slot-the-machine-runs-from.md b/issues/diskserver-serves-the-slot-the-machine-runs-from.md deleted file mode 100644 index dbc4b9376a..0000000000 --- a/issues/diskserver-serves-the-slot-the-machine-runs-from.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -status: open -kind: defect -opened: 2026-09-26 ---- - -# blockd would serve the partition the machine is running from - -blockd (`userland/blockd/src/main.rs`) opens any partition of its disk whose -table entry checks out, to whoever holds its port. The kernel's own claims -refuse the running ROOT slot (`rootfs::hold_source`), because a writer there -can change the image under the kernel that booted from it; blockd has no such -refusal, and nothing tells it which of its partitions the machine booted. - -It cannot bite today: blockd drives a controller the kernel does not, and the -boot medium is always the kernel's (the stick, or the first NVMe controller). -It bites the day blockd drives the disk the loader read ROOT from — the -small-kernel track's step 9 (`issues/the-kernel-is-small-interrupts-post-and-threads-wait.md`), -or an installed machine booting off NVMe once the kernel stops driving it. - -**Exit condition.** blockd learns the running slot's unique GUID from the -loader's handoff, through whatever starts it, and refuses a session on it by -name; a guest test boots with the running ROOT on blockd's disk and sees the -open refused while the idle slot opens. diff --git a/issues/the-block-service-does-not-refuse-the-running-root.md b/issues/the-block-service-does-not-refuse-the-running-root.md new file mode 100644 index 0000000000..ad9ab8740f --- /dev/null +++ b/issues/the-block-service-does-not-refuse-the-running-root.md @@ -0,0 +1,17 @@ +--- +status: none +kind: rejected +opened: 2026-09-26 +--- + +# The block service does not refuse the running ROOT + +diskserver serves the partition the loader read ROOT from like any other: +the grant its connector was minted with decides whether a session opens it +and whether that session writes. It is not told which partition the machine +runs from and refuses nothing for being that partition. + +The owner declined a blanket refusal on 2026-10-20. An authorized write there +can invalidate the stored image and leave the installation unbootable; that +is the grant holder's authority, and no automatic rollback is assumed to +recover it — the update policy is fix-forward. diff --git a/system/diskserver/src/main.rs b/system/diskserver/src/main.rs index a95d854a6b..aecd1929cb 100644 --- a/system/diskserver/src/main.rs +++ b/system/diskserver/src/main.rs @@ -28,10 +28,6 @@ //! (`--claim-refused `), and a listing and every open are refused //! `ClaimRefused`, so a file server says so rather than finding no disk. //! -//! **The partition the machine runs from is refused to every session**: its -//! starter names it (`--running `, the ROOT the loader read), because a -//! writer there changes the image under the kernel that booted from it. -//! //! **A connection reaches what its grant says** ([`wire::Grant`]): the badge //! the supervisor minted its connector with on this service's port, which the //! kernel stamps on it and answers this acceptor alone. A listing names the @@ -81,10 +77,6 @@ const MAX_SESSIONS: usize = 8; const MAX_PENDING: usize = 16; const HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(2); -/// Argv, followed by a unique GUID: the partition the machine runs from, which -/// no session opens. -const RUNNING: &str = "--running"; - /// Argv, followed by a device name of this service's row: on this machine, and /// its claim refused. const CLAIM_REFUSED: &str = "--claim-refused"; @@ -195,14 +187,8 @@ fn listed(parts: Result<&[Part], Refusal>, grant: Option) -> Result, - running: Option<[u8; 16]>, - guid: [u8; 16], - grant: Option, -) -> Result<(u64, u64), Refusal> { +/// whether the table carries it; then the drive, then the table. +fn admitted(parts: Result<&[Part], Refusal>, guid: [u8; 16], grant: Option) -> Result<(u64, u64), Refusal> { let grant = grant.ok_or(Refusal::NotGranted)?; 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 @@ -210,15 +196,11 @@ fn admitted( if !grant.admits(guid, part.map_or([0; 16], |p| p.kind)) { return Err(Refusal::NotGranted); } - let span = match part.map(|p| &p.span) { - None => return Err(Refusal::NotFound), - Some(Err(_)) => return Err(Refusal::Unusable), - Some(Ok(span)) => *span, - }; - if running == Some(guid) { - return Err(Refusal::Held); + match part.map(|p| &p.span) { + None => Err(Refusal::NotFound), + Some(Err(_)) => Err(Refusal::Unusable), + Some(Ok(span)) => Ok(*span), } - Ok(span) } /// A connection that has not opened a session yet. @@ -299,10 +281,6 @@ struct Service { losses: u64, sessions: BTreeMap, next_id: u64, - /// The partition the machine is running from, as the loader named it: - /// refused to every session, since a writer there changes the image - /// under the kernel that booted from it. - running: Option<[u8; 16]>, } /// A session decided on and not yet told to its client. @@ -315,8 +293,8 @@ struct Opening { } impl Service { - fn new(ctrl: Drive, running: Option<[u8; 16]>) -> Self { - Self { ctrl, holds: Holds::new(), losses: 0, sessions: BTreeMap::new(), next_id: 0, running } + fn new(ctrl: Drive) -> Self { + Self { ctrl, holds: Holds::new(), losses: 0, sessions: BTreeMap::new(), next_id: 0 } } /// What a listing through `grant` answers: every partition of the table @@ -334,7 +312,7 @@ impl Service { /// a holder that is gone — only for one whose commands are still on the /// device. fn place(&mut self, guid: [u8; 16], grant: Option) -> Result<(u64, u64), Refusal> { - let (first, blocks) = admitted(self.ctrl.parts(), self.running, guid, grant)?; + let (first, blocks) = admitted(self.ctrl.parts(), guid, grant)?; let ids: Vec = self.sessions.keys().copied().collect(); for id in ids { self.hear(id); @@ -582,12 +560,6 @@ fn claim() -> Option { fn main() { let args: Vec = std::env::args().collect(); - let running = args.iter().position(|a| a == RUNNING).map(|at| { - args.get(at + 1) - .and_then(|text| PartGuid::parse(text)) - .map(|guid| guid.0) - .unwrap_or_else(|| panic!("diskserver: {RUNNING} takes the running ROOT's unique GUID")) - }); let refused = args.iter().position(|a| a == CLAIM_REFUSED).map(|at| { args.get(at + 1).unwrap_or_else(|| panic!("diskserver: {CLAIM_REFUSED} takes the device its claim was refused on")) }); @@ -597,17 +569,17 @@ fn main() { "diskserver: NOT SERVING — {name} is on this machine and the kernel refused this service its \ claim; every partition on it is refused" ); - serve(&mut Service::new(Drive::ClaimRefused, running), &acceptor); + serve(&mut Service::new(Drive::ClaimRefused), &acceptor); } let Some(dev) = claim() else { println!("diskserver: no NVMe controller this row names is on this machine; serving no partition"); - serve(&mut Service::new(Drive::Absent, running), &acceptor); + serve(&mut Service::new(Drive::Absent), &acceptor); }; let mut ctrl = match Controller::open(dev) { Ok(ctrl) => ctrl, Err(why) => { println!("diskserver: NOT SERVING — {why}; serving no partition"); - serve(&mut Service::new(Drive::Unusable, running), &acceptor); + serve(&mut Service::new(Drive::Unusable), &acceptor); } }; println!( @@ -622,17 +594,13 @@ fn main() { let parts = read_table(&mut ctrl); for part in &parts { match &part.span { - Ok(_) if running == Some(part.unique) => println!( - "diskserver: partition {} is the ROOT this machine runs from; refusing every session to it", - guid_text(part.unique) - ), Ok((first, blocks)) => { println!("diskserver: partition {} at block {first}, {blocks} blocks", guid_text(part.unique)) } Err(why) => println!("diskserver: partition {} is not served: {why}", guid_text(part.unique)), } } - serve(&mut Service::new(Drive::Up(ctrl, parts), running), &acceptor); + serve(&mut Service::new(Drive::Up(ctrl, parts)), &acceptor); } fn serve(service: &mut Service, acceptor: &toyos::port::Acceptor) -> ! { @@ -795,13 +763,13 @@ mod tests { fn an_unusable_or_unclaimed_controller_is_refused_and_an_absent_one_lists_nothing() { let guid = [7; 16]; let grant = Some(Grant { scope: Scope::Unique(guid), writes: true }); - let mut unusable = Service::new(Drive::Unusable, None); + let mut unusable = Service::new(Drive::Unusable); assert_eq!(unusable.listing(grant), Err(Refusal::Unusable)); assert_eq!(unusable.place(guid, grant), Err(Refusal::Unusable)); - let mut unclaimed = Service::new(Drive::ClaimRefused, None); + let mut unclaimed = Service::new(Drive::ClaimRefused); assert_eq!(unclaimed.listing(grant), Err(Refusal::ClaimRefused)); assert_eq!(unclaimed.place(guid, grant), Err(Refusal::ClaimRefused)); - let mut absent = Service::new(Drive::Absent, None); + let mut absent = Service::new(Drive::Absent); assert_eq!(absent.listing(grant), Ok(Vec::new())); assert_eq!(absent.place(guid, grant), Err(Refusal::NotFound)); for mut service in [unusable, unclaimed, absent] { @@ -833,24 +801,21 @@ mod tests { /// A unique grant opens its one partition and is refused every other of /// the same table by name, as is a connection with no grant; a kind grant - /// opens every partition of its type and no other; and the partition the - /// machine runs from is held whatever reaches it. + /// opens every partition of its type and no other. #[test] fn an_open_reaches_only_what_its_grant_admits() { let parts = table(); - assert_eq!(admitted(Ok(&parts), None, LOG, unique(LOG)), Ok((0, 100))); + assert_eq!(admitted(Ok(&parts), LOG, unique(LOG)), Ok((0, 100))); for other in [BOOT, DATA, ROOT] { - assert_eq!(admitted(Ok(&parts), None, other, unique(LOG)), Err(Refusal::NotGranted)); - assert_eq!(admitted(Ok(&parts), None, other, None), Err(Refusal::NotGranted)); + assert_eq!(admitted(Ok(&parts), other, unique(LOG)), Err(Refusal::NotGranted)); + assert_eq!(admitted(Ok(&parts), other, None), Err(Refusal::NotGranted)); } let data = Some(Grant { scope: Scope::Kind(DATA_KIND), writes: true }); - assert_eq!(admitted(Ok(&parts), None, DATA, data), Ok((200, 100))); - assert_eq!(admitted(Ok(&parts), None, LOG, data), Err(Refusal::NotGranted)); + assert_eq!(admitted(Ok(&parts), DATA, data), Ok((200, 100))); + assert_eq!(admitted(Ok(&parts), LOG, data), Err(Refusal::NotGranted)); // Nothing of a GUID the table does not carry, but to the grant that names it. - assert_eq!(admitted(Ok(&parts), None, [9; 16], data), Err(Refusal::NotGranted)); - assert_eq!(admitted(Ok(&parts), None, [9; 16], unique([9; 16])), Err(Refusal::NotFound)); - assert_eq!(admitted(Ok(&parts), Some(ROOT), ROOT, unique(ROOT)), Err(Refusal::Held)); - assert_eq!(admitted(Ok(&parts), Some(ROOT), ROOT, unique(LOG)), Err(Refusal::NotGranted)); + assert_eq!(admitted(Ok(&parts), [9; 16], data), Err(Refusal::NotGranted)); + assert_eq!(admitted(Ok(&parts), [9; 16], unique([9; 16])), Err(Refusal::NotFound)); } /// A listing names exactly what its grant admits, and nothing to a diff --git a/system/supervisor/src/main.rs b/system/supervisor/src/main.rs index 9d4b375813..00b4e7c64d 100644 --- a/system/supervisor/src/main.rs +++ b/system/supervisor/src/main.rs @@ -706,7 +706,7 @@ impl<'a> Service<'a> { std::io::Error::other(format!("{path} no longer holds what was installed: {why}")) })?; } - let storage = storage_endowment(self.program, self.role, syscap, grants)?; + let storage = storage_endowment(self.role, syscap, grants)?; let ports = self.ports.borrow(); let served = Served::Keep { acceptors: &ports, owed }; let (child, devices) = start( @@ -3037,37 +3037,24 @@ fn guid_text(guid: [u8; 16]) -> String { toyos_abi::part::PartGuid(guid).write_text(&mut buf).to_string() } -/// A storage row's arguments, claims and block connector for one start. +/// A file server's arguments, claims and block connector for one start. /// -/// A block service is told the ROOT the machine runs from, which it serves no -/// session on. A file server is told its role, and gets a claim on every -/// partition of its role a disk the kernel drives carries — the stick, until -/// usbd serves it — and otherwise the partition's GUID and `block` minted for +/// A file server is told its role, and gets a claim on every partition of its +/// role a disk the kernel drives carries — the stick, until usbd serves it — +/// and otherwise the partition's GUID and `block` minted for /// that partition alone, writing for the log and reading for the boot volume; /// DATA it finds by type, through `block` minted for every DATA partition, /// and counts with its claims. A log or boot role the loader named no /// partition for, a claim the kernel refuses, and a block port closed for /// good each refuse the start: the role is then absent, never served from /// memory. -fn storage_endowment( - program: &Program, - role: Option<&str>, - syscap: &SysCap, - grants: &Grants<'_>, -) -> Result { +fn storage_endowment(role: Option<&str>, syscap: &SysCap, grants: &Grants<'_>) -> Result { use toyos_abi::inventory::{Record, Role}; let mut storage = Extra::default(); - if !is_block(program) && role.is_none() { + let Some(role) = role else { return Ok(storage); - } + }; let records = inventory(syscap).map_err(|why| StartError::Other(std::io::Error::other(why)))?; - if is_block(program) { - if let Some(root) = loaded(&records, Role::Root) { - storage.args.extend(["--running".to_string(), guid_text(root)]); - } - return Ok(storage); - } - let role = role.expect("checked above"); storage.args.push(role.to_string()); let on_kernel_disk = |pick: &dyn Fn(&toyos_abi::inventory::Partition) -> bool| -> Vec<[u8; 16]> { records From 8587da6e723b99f111ab2b47657a6a578cd9b542 Mon Sep 17 00:00:00 2001 From: japabu Date: Tue, 20 Oct 2026 20:09:40 +0200 Subject: [PATCH 2/4] loader refuses a slot whose ROOT partition is not typed TOYOS-ROOT The slot table names a slot's ROOT by unique GUID alone, and the loader read whatever partition carried that GUID. Block grants reach partitions by type: the DATA file server's grant, which writes, admits every TOYOS-DATA partition. With diskserver no longer refusing the running ROOT (the previous commit), a ROOT typed DATA would be writable through that grant. The loader now holds the located partition's GPT type to TOYOS-ROOT before reading a byte of the slot, and refuses the slot by name otherwise, handing the kernel the new refusal word `type`. The decision is `toyos_update::policy::typed`, host-tested beside the loader's other slot decisions; a mutation that accepts any type fails the test. The running slot's volume issue now closes with the deletion of the kernel's USB storage path (the small-kernel track's step 10), after which grants alone govern every partition, rather than with a new kernel hold. Co-Authored-By: Claude Opus 5.5 --- .../the-running-slots-volume-is-claimable.md | 11 +++++---- lib/update/src/policy.rs | 24 +++++++++++++++++++ loader/src/slot.rs | 8 +++++-- 3 files changed, 37 insertions(+), 6 deletions(-) diff --git a/issues/the-running-slots-volume-is-claimable.md b/issues/the-running-slots-volume-is-claimable.md index ee88d7c47e..9061f93fbf 100644 --- a/issues/the-running-slots-volume-is-claimable.md +++ b/issues/the-running-slots-volume-is-claimable.md @@ -26,7 +26,10 @@ signature at the next boot and refuses the slot by name. It is a denial — the running slot is refused next boot and the machine falls back to an older image, or to nothing on a one-slot image. -**Exit**: the loader hands the kernel the booted slot's volume by its unique -GUID beside ROOT's, and the kernel holds it as it holds ROOT's, so a claim on -it is refused as `PermissionDenied` like every partition the kernel holds; a -guest test asserts the refusal. +**Exit**: the kernel's USB storage path is deleted with its xHCI driver +(`issues/the-kernel-is-small-interrupts-post-and-threads-wait.md`, step 10). +The kernel then serves no partition claim, `hold_source` goes with it, and +the running slot's volume and ROOT are reached only through the block +service's grants, as every partition on diskserver's disk already is +(`issues/the-block-service-does-not-refuse-the-running-root.md`). No kernel +hold of the volume is added in the meantime. diff --git a/lib/update/src/policy.rs b/lib/update/src/policy.rs index 7755ab5f41..4426d43b45 100644 --- a/lib/update/src/policy.rs +++ b/lib/update/src/policy.rs @@ -45,6 +45,8 @@ pub enum Refusal { Hash(&'static str), /// A section could not be read. Unreadable(&'static str), + /// The partition the slot table names for a section is not of its type. + Type(&'static str), } impl Refusal { @@ -60,6 +62,7 @@ impl Refusal { Self::BelowFloor { .. } | Self::Older { .. } => "version", Self::Hash(_) => "hash", Self::Unreadable(_) => "unreadable", + Self::Type(_) => "type", } } } @@ -78,6 +81,7 @@ impl core::fmt::Display for Refusal { Self::Older { version, than } => write!(f, "its version {version} is older than {than}"), Self::Hash(section) => write!(f, "its {section} is not the bytes its signed header names"), Self::Unreadable(what) => write!(f, "its {what} could not be read"), + Self::Type(what) => write!(f, "its {what} partition is not typed as one"), } } } @@ -90,6 +94,17 @@ pub fn admits(floor: u64, version: u64) -> Result<(), Refusal> { Ok(()) } +/// Whether the partition the slot table names for `section` is typed `kind`. +/// The table names it by unique GUID alone, and a block grant reaches +/// partitions by type: a ROOT of another type is reachable through a grant +/// for that type. +pub fn typed(section: &'static str, stated: [u8; 16], kind: [u8; 16]) -> Result<(), Refusal> { + if stated != kind { + return Err(Refusal::Type(section)); + } + Ok(()) +} + /// The slots a boot tries, in order: the marked one, then the other where the /// table carries it. pub fn order(table: &Table) -> [Option; 2] { @@ -142,6 +157,14 @@ mod tests { assert_eq!(installable(12, 10, Some(12)), Ok(())); } + #[test] + fn a_partition_is_taken_only_of_its_sections_type() { + let (root, data) = ([0x40; 16], [0x30; 16]); + assert_eq!(typed("root", root, root), Ok(())); + assert_eq!(typed("root", data, root), Err(Refusal::Type("root"))); + assert_eq!(typed("root", [0; 16], root), Err(Refusal::Type("root"))); + } + #[test] fn a_boot_tries_the_marked_slot_first_and_the_other_only_if_there_is_one() { let slot = Some(Slot { boot: [1; 16], root: [2; 16], version: 1 }); @@ -163,6 +186,7 @@ mod tests { Refusal::BelowFloor { version: 1, floor: 2 }, Refusal::Hash("kernel"), Refusal::Unreadable("root"), + Refusal::Type("root"), ] { assert!(!r.word().is_empty() && !r.word().contains([',', ' ', ':', '=']), "{r:?}"); } diff --git a/loader/src/slot.rs b/loader/src/slot.rs index aa1da11175..08a93a7ee7 100644 --- a/loader/src/slot.rs +++ b/loader/src/slot.rs @@ -121,12 +121,16 @@ fn verify( ) -> Result { let letter = which.letter(); // First, because it costs one read of the table and refuses by name a - // ROOT its own table does not vouch for — one overlapping a neighbour — - // before a byte of the slot is read. + // ROOT its own table does not vouch for — one overlapping a neighbour, or + // of another type — before a byte of the slot is read. let part = disk.locate(slot.root).map_err(|why| { println!("{HEAD} {letter}: {why}"); Refusal::Unreadable("root") })?; + let stated = part.partition().type_guid(); + policy::typed("root", stated.0, toyos_gpt::Guid::TOYOS_ROOT.0).inspect_err(|_| { + println!("{HEAD} {letter}: ROOT's partition is typed {stated}, not TOYOS-ROOT"); + })?; let (header, digest) = signed_header(bs, which, &slot)?; if record.is_some_and(|record| record::died(record, which, &digest)) { return Err(Refusal::Died); From ce4f91acebf683c2d0a35e01eeb1d48aa123135a Mon Sep 17 00:00:00 2001 From: japabu Date: Tue, 20 Oct 2026 20:29:17 +0200 Subject: [PATCH 3/4] the loader names the booted slot volume, and no grant names it idle `slots::grant` learned the running slot's volume from the slot table, which the updater it grants to writes. A table pairing a decoy volume with the running ROOT as one slot, and the booted volume with the idle ROOT as the other, was granted the booted volume for writing on the next boot: a holder widening its own next grant. The previous commits' message overstated this as closed ("the updater's write grants name only the idle slot"); that held for ROOT and not for the volume. The loader now hands the kernel the chosen slot's volume GUID (`KernelArgs::slot_volume_guid`, appended at offset 1488, so `LAYOUT` changes), the kernel names it in the inventory as `Role::Slot` beside `Role::Root`, and `slots::grant` takes it and refuses an idle slot naming either the booted volume or the running ROOT, `Stray::Running`, judging the running slot by the loader's word alone. Both supervisor branches, the kernel-claim one and the block-service one, pass it; a boot whose inventory names no slot volume grants nothing. `Role::Boot`'s doc called it the running slot's volume; it is the partition firmware loaded the loader from (the ESP), and the manifests' comments on the boot file server said the same; both are corrected. The ROOT type check is inlined in the loader's `verify` as review suggested: `policy::typed` and its test go, `Refusal::Type` stays. `issues/the-running-slots-volume-is-claimable.md` keeps only its kernel-claim half, which closes with the kernel's USB storage at step 10. Co-Authored-By: Claude Opus 5.5 --- images/console.toml | 2 +- images/system.toml | 2 +- .../the-running-slots-volume-is-claimable.md | 22 ++++----- kernel/src/gpt.rs | 3 ++ lib/inspect/src/dev.rs | 1 + lib/update/src/policy.rs | 19 ------- lib/update/src/slots.rs | 49 +++++++++++++++---- loader/src/main.rs | 5 +- loader/src/slot.rs | 9 ++-- sdk/abi/src/boot.rs | 8 ++- sdk/abi/src/inventory.rs | 6 ++- system/supervisor/src/main.rs | 16 +++--- 12 files changed, 86 insertions(+), 56 deletions(-) diff --git a/images/console.toml b/images/console.toml index 5c28649cb0..a8ee84176f 100644 --- a/images/console.toml +++ b/images/console.toml @@ -101,7 +101,7 @@ restart = true serves = ["block"] devices = ["pci:01:08:02/1b36:0010"] -# The file servers: DATA, the log and the running slot's volume, one process +# The file servers: DATA, the log and the boot volume, one process # each, serving every program the directories of its role. Started again when # one ends, on the same ports. [programs.fileserver] diff --git a/images/system.toml b/images/system.toml index 2ac7e2a196..8e2a6a8180 100644 --- a/images/system.toml +++ b/images/system.toml @@ -261,7 +261,7 @@ restart = true serves = ["block"] devices = ["pci:01:08:02/1b36:0010"] -# The file servers: DATA, the log and the running slot's volume, one process +# The file servers: DATA, the log and the boot volume, one process # each, serving every program the directories of its role. Started again when # one ends, on the same ports. [programs.fileserver] diff --git a/issues/the-running-slots-volume-is-claimable.md b/issues/the-running-slots-volume-is-claimable.md index 9061f93fbf..2eaff6b3e9 100644 --- a/issues/the-running-slots-volume-is-claimable.md +++ b/issues/the-running-slots-volume-is-claimable.md @@ -9,17 +9,14 @@ opened: 2026-09-26 The kernel holds the partition its ROOT image came from for the machine's life (`kernel/src/rootfs.rs`, `hold_source`), so no claim can write the ROOT a boot runs. It holds nothing of the same slot's FAT volume — the kernel, its boot -parameter and the signed header the loader read — so any process whose -`SysCap` carries `device` can claim that partition by its unique GUID and write -it. The test estate's `test-runner` holds `device`, and so would any row -a config gives it to. +parameter and the signed header the loader read — so on a disk the kernel +drives, any process whose `SysCap` carries `device` can claim that partition +by its unique GUID and write it. The test estate's `test-runner` holds +`device`, and so would any row a config gives it to. -`/system/bin/update` reaches it too. `slots::grant` (`lib/update/src/slots.rs`) -knows which volume is the running slot's only from the slot table, and -`update` writes that table: one naming a decoy volume beside the running ROOT -as slot A, and the running volume beside the idle ROOT as slot B, is granted -the running volume on the next boot. Nothing the kernel holds names the booted -volume, so init has nothing to check the table's word against. +`/system/bin/update` does not: the loader names the booted volume in the +inventory (`Role::Slot`), and `slots::grant` refuses an idle slot naming it, +whatever the table the updater wrote says. A write there is not a silent compromise: the loader holds every byte to the signature at the next boot and refuses the slot by name. It is a denial — @@ -30,6 +27,5 @@ image, or to nothing on a one-slot image. (`issues/the-kernel-is-small-interrupts-post-and-threads-wait.md`, step 10). The kernel then serves no partition claim, `hold_source` goes with it, and the running slot's volume and ROOT are reached only through the block -service's grants, as every partition on diskserver's disk already is -(`issues/the-block-service-does-not-refuse-the-running-root.md`). No kernel -hold of the volume is added in the meantime. +service's grants (`issues/the-block-service-does-not-refuse-the-running-root.md`). +No kernel hold of the volume is added in the meantime. diff --git a/kernel/src/gpt.rs b/kernel/src/gpt.rs index 16fcffe081..4b54ff00ff 100644 --- a/kernel/src/gpt.rs +++ b/kernel/src/gpt.rs @@ -165,6 +165,9 @@ pub fn init(args: &KernelArgs) { if args.root_partition_guid != [0; 16] { loaded.push((Role::Root, Guid(args.root_partition_guid))); } + if args.slot_volume_guid != [0; 16] { + loaded.push((Role::Slot, Guid(args.slot_volume_guid))); + } if args.log_partition_guid != [0; 16] { let log_guid = Guid(args.log_partition_guid); log!("gpt: the boot volume names {log_guid} as the log partition"); diff --git a/lib/inspect/src/dev.rs b/lib/inspect/src/dev.rs index 284856d4f3..ce59d5b671 100644 --- a/lib/inspect/src/dev.rs +++ b/lib/inspect/src/dev.rs @@ -175,6 +175,7 @@ pub fn render(machine: &Machine, records: &[Record]) -> Result "root", Role::Boot => "boot", Role::Log => "log", + Role::Slot => "slot", }; out.put(format!("loaded.{role}"), guid(l.unique_guid).into())?; } diff --git a/lib/update/src/policy.rs b/lib/update/src/policy.rs index 4426d43b45..dfee846b68 100644 --- a/lib/update/src/policy.rs +++ b/lib/update/src/policy.rs @@ -94,17 +94,6 @@ pub fn admits(floor: u64, version: u64) -> Result<(), Refusal> { Ok(()) } -/// Whether the partition the slot table names for `section` is typed `kind`. -/// The table names it by unique GUID alone, and a block grant reaches -/// partitions by type: a ROOT of another type is reachable through a grant -/// for that type. -pub fn typed(section: &'static str, stated: [u8; 16], kind: [u8; 16]) -> Result<(), Refusal> { - if stated != kind { - return Err(Refusal::Type(section)); - } - Ok(()) -} - /// The slots a boot tries, in order: the marked one, then the other where the /// table carries it. pub fn order(table: &Table) -> [Option; 2] { @@ -157,14 +146,6 @@ mod tests { assert_eq!(installable(12, 10, Some(12)), Ok(())); } - #[test] - fn a_partition_is_taken_only_of_its_sections_type() { - let (root, data) = ([0x40; 16], [0x30; 16]); - assert_eq!(typed("root", root, root), Ok(())); - assert_eq!(typed("root", data, root), Err(Refusal::Type("root"))); - assert_eq!(typed("root", [0; 16], root), Err(Refusal::Type("root"))); - } - #[test] fn a_boot_tries_the_marked_slot_first_and_the_other_only_if_there_is_one() { let slot = Some(Slot { boot: [1; 16], root: [2; 16], version: 1 }); diff --git a/lib/update/src/slots.rs b/lib/update/src/slots.rs index a2245996ff..97ffeed861 100644 --- a/lib/update/src/slots.rs +++ b/lib/update/src/slots.rs @@ -290,19 +290,26 @@ pub struct Kinds { } /// The idle slot a grant may claim, given the ROOT this boot runs -/// (`running`) and every partition the machine lists. +/// (`running`), the volume it was booted from (`volume`), both as the loader +/// named them, and every partition the machine lists. /// /// **The table is the grantee's to write**, so nothing it names is taken on /// its word: each of the idle slot's two partitions must be on the running -/// ROOT's disk, of its kind's type, and neither of the running slot's — else -/// the holder of one grant could name its next one anywhere. -pub fn grant(table: &Table, running: &Listed, listed: &[Listed], kinds: Kinds) -> Result<(Which, Slot), NoIdle> { +/// ROOT's disk, of its kind's type, and neither of the running slot's as the +/// loader named it — else the holder of one grant could name its next one +/// anywhere. +pub fn grant( + table: &Table, + running: &Listed, + volume: [u8; 16], + listed: &[Listed], + kinds: Kinds, +) -> Result<(Which, Slot), NoIdle> { let (idle, slot) = idle(table, &running.unique_guid)?; - let runs = table.slot(idle.other()).expect("`idle` found the running slot in the table"); for (part, guid, kind) in [("volume", slot.boot, kinds.boot), ("ROOT", slot.root, kinds.root)] { let mut named = listed.iter().filter(|p| p.unique_guid == guid); let why = match (named.next(), named.next()) { - _ if guid == runs.boot || guid == runs.root => Some(Stray::Running), + _ if guid == volume || guid == running.unique_guid => Some(Stray::Running), (None, _) => Some(Stray::Unlisted), (Some(p), None) if p.device != running.device => Some(Stray::OtherDisk), (Some(p), None) if p.type_guid != kind => Some(Stray::Type), @@ -423,12 +430,13 @@ mod tests { at(0, [0xEF; 16], ESP), at(1, KINDS.boot, [5; 16]), ]; + let volume = [1; 16]; let t = table(Which::A, 1); - assert_eq!(grant(&t, &running, &listed, KINDS).map(|(w, _)| w), Ok(Which::B)); + assert_eq!(grant(&t, &running, volume, &listed, KINDS).map(|(w, _)| w), Ok(Which::B)); let bent = |boot: [u8; 16], root: [u8; 16]| { let mut t = t; t.slots[1] = Some(Slot { boot, root, version: 0 }); - grant(&t, &running, &listed, KINDS) + grant(&t, &running, volume, &listed, KINDS) }; let stray = |part, why| Err(NoIdle::Stray { part, why }); assert_eq!(bent([1; 16], [4; 16]), stray("volume", Stray::Running), "the running slot's volume"); @@ -439,6 +447,29 @@ mod tests { assert_eq!(bent([9; 16], [4; 16]), stray("volume", Stray::Unlisted)); let mut twice = listed.to_vec(); twice.push(at(1, KINDS.boot, [3; 16])); - assert_eq!(grant(&t, &running, &twice, KINDS), stray("volume", Stray::Duplicate), "a GUID two disks carry"); + assert_eq!(grant(&t, &running, volume, &twice, KINDS), stray("volume", Stray::Duplicate), "a GUID two disks carry"); + } + + /// **The running slot is the loader's word, not the table's**: a table + /// pairing a decoy volume with the running ROOT, and the booted volume + /// with the idle ROOT, is refused the booted volume. + #[test] + fn a_table_cannot_name_the_booted_volume_idle() { + const KINDS: Kinds = Kinds { boot: [0xB0; 16], root: [0xA0; 16] }; + let at = |type_guid, unique_guid| Listed { device: 0, type_guid, unique_guid }; + let (booted, decoy, running, idle_root) = ([1; 16], [3; 16], [2; 16], [4; 16]); + let listed = [at(KINDS.boot, booted), at(KINDS.boot, decoy), at(KINDS.root, running), at(KINDS.root, idle_root)]; + let decoyed = Table { + sequence: 2, + marked: Which::A, + slots: [ + Some(Slot { boot: decoy, root: running, version: 1 }), + Some(Slot { boot: booted, root: idle_root, version: 0 }), + ], + }; + assert_eq!( + grant(&decoyed, &at(KINDS.root, running), booted, &listed, KINDS), + Err(NoIdle::Stray { part: "volume", why: Stray::Running }) + ); } } diff --git a/loader/src/main.rs b/loader/src/main.rs index d389a5da27..00f31cd7c8 100644 --- a/loader/src/main.rs +++ b/loader/src/main.rs @@ -516,7 +516,7 @@ fn report_reach(what: &str, at: u64, len: u64) { } #[allow(clippy::too_many_arguments)] -fn start_kernel(kernel: LoadedKernel, kernel_elf_bytes: vec::Vec, cmdline: vec::Vec, rsdp_addr: u64, gop: Option, boot_part: Option, log_partition_guid: [u8; 16], root_image: rootimage::RootImage, microcode: Option<&'static [u8]>, entry_counter: u64, wall_clock: Option<(toyos_wallclock::Civil, u64)>, system_table: SystemTable) -> ! { +fn start_kernel(kernel: LoadedKernel, kernel_elf_bytes: vec::Vec, cmdline: vec::Vec, rsdp_addr: u64, gop: Option, boot_part: Option, log_partition_guid: [u8; 16], root_image: rootimage::RootImage, slot_volume_guid: [u8; 16], microcode: Option<&'static [u8]>, entry_counter: u64, wall_clock: Option<(toyos_wallclock::Civil, u64)>, system_table: SystemTable) -> ! { // Said before it is refused, for `report_reach`'s reason. match arch::cpu_as_entered() { Ok(None) => {} @@ -664,6 +664,7 @@ fn start_kernel(kernel: LoadedKernel, kernel_elf_bytes: vec::Vec, cmdline: v gop_edid_len: 0, microcode_addr, microcode_len, + slot_volume_guid, }; if let Some(edid) = gop.as_ref().and_then(|g| g.edid) { kernel_args.gop_edid = edid; @@ -994,5 +995,5 @@ fn main(handle: Handle, system_table: SystemTable) -> Status { watchdog::arm(&system_table, rsdp_addr, params); println!("Starting kernel..."); - start_kernel(loaded_kernel, kernel_bytes, cmdline, rsdp_addr, gop, boot_part, log_guid, chosen.root, microcode, entry_counter, wall_clock, system_table); + start_kernel(loaded_kernel, kernel_bytes, cmdline, rsdp_addr, gop, boot_part, log_guid, chosen.root, chosen.volume, microcode, entry_counter, wall_clock, system_table); } diff --git a/loader/src/slot.rs b/loader/src/slot.rs index 08a93a7ee7..9e026020ee 100644 --- a/loader/src/slot.rs +++ b/loader/src/slot.rs @@ -47,6 +47,8 @@ pub struct Chosen { pub kernel: Vec, pub cmdline: Vec, pub root: RootImage, + /// The unique GUID of its volume, which the kernel and its parameter were read from. + pub volume: [u8; 16], /// The slot tried before this one, and why it was refused. pub refused: Option<(Which, Refusal)>, } @@ -128,9 +130,10 @@ fn verify( Refusal::Unreadable("root") })?; let stated = part.partition().type_guid(); - policy::typed("root", stated.0, toyos_gpt::Guid::TOYOS_ROOT.0).inspect_err(|_| { + if stated != toyos_gpt::Guid::TOYOS_ROOT { println!("{HEAD} {letter}: ROOT's partition is typed {stated}, not TOYOS-ROOT"); - })?; + return Err(Refusal::Type("root")); + } let (header, digest) = signed_header(bs, which, &slot)?; if record.is_some_and(|record| record::died(record, which, &digest)) { return Err(Refusal::Died); @@ -154,7 +157,7 @@ fn verify( return Err(Refusal::Hash("root")); } println!("{HEAD} {letter}: kernel, cmdline and ROOT are the bytes the signed header names"); - Ok(Chosen { which, version: header.version, digest, kernel, cmdline, root, refused: None }) + Ok(Chosen { which, version: header.version, digest, kernel, cmdline, root, volume: slot.boot, refused: None }) } /// `slot`'s signed header, held to [`KEY`], and its SHA-256. diff --git a/sdk/abi/src/boot.rs b/sdk/abi/src/boot.rs index 12d0feff02..4190b97681 100644 --- a/sdk/abi/src/boot.rs +++ b/sdk/abi/src/boot.rs @@ -141,6 +141,10 @@ pub struct KernelArgs { /// holds none for the CPU, or the CPU's architecture loads none. pub microcode_addr: u64, pub microcode_len: u64, + /// The unique GUID of the booted slot's volume, the partition the loader + /// read the kernel and its parameter from, raw as in the GPT entry like + /// [`Self::root_partition_guid`]; zero with no image. + pub slot_volume_guid: [u8; 16], } /// The bytes of [`KernelArgs::gop_edid`]: one EDID base block. @@ -273,7 +277,8 @@ const _: () = { assert!(offset_of!(KernelArgs, gop_edid_len) == 1464); assert!(offset_of!(KernelArgs, microcode_addr) == 1472); assert!(offset_of!(KernelArgs, microcode_len) == 1480); - assert!(size_of::() == 1488); + assert!(offset_of!(KernelArgs, slot_volume_guid) == 1488); + assert!(size_of::() == 1504); assert!(LAYOUT as i32 > 1440 || (LAYOUT as i32) < -1440); assert!(align_of::() == 8); assert!(size_of::() == 16); @@ -377,6 +382,7 @@ mod tests { gop_edid_len: 0, microcode_addr: 0, microcode_len: 0, + slot_volume_guid: [0; 16], }; #[test] diff --git a/sdk/abi/src/inventory.rs b/sdk/abi/src/inventory.rs index 5ff4170520..d5096f951a 100644 --- a/sdk/abi/src/inventory.rs +++ b/sdk/abi/src/inventory.rs @@ -216,10 +216,12 @@ pub struct Claim { pub enum Role { /// The ROOT this boot runs, read whole by the loader. Root, - /// The running slot's volume: the kernel and its parameter. + /// The partition firmware loaded the loader from. Boot, /// The log partition. Log, + /// The booted slot's volume: the kernel and its parameter. + Slot, } /// A partition the loader named, by its unique GUID: what a file server for @@ -417,6 +419,7 @@ impl Record { Role::Root => 0, Role::Boot => 1, Role::Log => 2, + Role::Slot => 3, }, ); o.bytes(8, &l.unique_guid); @@ -490,6 +493,7 @@ impl Record { 0 => Some(Role::Root), 1 => Some(Role::Boot), 2 => Some(Role::Log), + 3 => Some(Role::Slot), _ => None, })?, unique_guid: r.array(8), diff --git a/system/supervisor/src/main.rs b/system/supervisor/src/main.rs index 00b4e7c64d..4cec9cbf26 100644 --- a/system/supervisor/src/main.rs +++ b/system/supervisor/src/main.rs @@ -1088,8 +1088,9 @@ impl<'a> Supervisor<'a> { } /// **Which slot is idle is the loader's word, not the table's**: the - /// running ROOT is the one the loader read, the table is the one on that - /// ROOT's disk, and the idle slot is the one whose ROOT is not it — and + /// running ROOT and volume are the ones the loader read, the table is the + /// one on that ROOT's disk, and the idle slot is the one whose ROOT is not + /// it — and /// its two partitions are reached only as `toyos_update::slots::grant` /// admits them: claimed on a disk the kernel drives, and on one the block /// service serves, through connectors minted for each. @@ -1098,6 +1099,7 @@ impl<'a> Supervisor<'a> { use toyos_update::slots::{BOOT_LABEL, ROOT_LABEL, TABLE_LABEL}; let records = inventory(self.syscap)?; let running = loaded(&records, Role::Root).ok_or("the loader named no ROOT")?; + let volume = loaded(&records, Role::Slot).ok_or("the loader named no slot volume")?; let parts: Vec<_> = records .iter() .filter_map(|r| match r { @@ -1106,7 +1108,7 @@ impl<'a> Supervisor<'a> { }) .collect(); if let Some(root) = parts.iter().find(|p| p.unique_guid == running) { - return claimed_slots(self.syscap, &parts, *root); + return claimed_slots(self.syscap, &parts, *root, volume); } let mint = |grants: &Grants<'_>, grant: BlockGrant| { grants @@ -1121,7 +1123,7 @@ impl<'a> Supervisor<'a> { let Some(runs) = listed.iter().find(|p| p.unique_guid == running && p.type_guid == SLOT_KINDS.root) else { return Err("the ROOT this boot runs is on no disk the kernel or the block service serves".into()); }; - let (idle, slot) = toyos_update::slots::grant(&table, runs, &listed, SLOT_KINDS).map_err(|why| why.to_string())?; + let (idle, slot) = toyos_update::slots::grant(&table, runs, volume, &listed, SLOT_KINDS).map_err(|why| why.to_string())?; let mut ports = Vec::new(); for (label, guid) in [(TABLE_LABEL, table_guid), (BOOT_LABEL, slot.boot), (ROOT_LABEL, slot.root)] { ports.push((label.to_string(), mint(&self.grants, BlockGrant { scope: Scope::Unique(guid), writes: true })?)); @@ -2315,11 +2317,13 @@ fn resolve<'a, V>(system: &'a Manifest, path: &str, judge: impl FnOnce(Target<'_ /// The slot table's partition and the idle slot's two, claimed on a disk the /// kernel drives: the grant a `slots` row is endowed (`toyos_update::slots`), -/// where the ROOT this boot runs, `running`, is one of `parts`. +/// where the ROOT this boot runs, `running`, is one of `parts`, and `volume` +/// is the slot volume it was booted from. fn claimed_slots( syscap: &SysCap, parts: &[toyos_abi::inventory::Partition], running: toyos_abi::inventory::Partition, + volume: [u8; 16], ) -> Result { use toyos_abi::inventory::Partition; let tables: Vec<&Partition> = parts @@ -2344,7 +2348,7 @@ fn claimed_slots( unique_guid: p.unique_guid, }; let all: Vec<_> = parts.iter().map(listed).collect(); - let (idle, slot) = toyos_update::slots::grant(&table, &listed(&running), &all, SLOT_KINDS).map_err(|why| why.to_string())?; + let (idle, slot) = toyos_update::slots::grant(&table, &listed(&running), volume, &all, SLOT_KINDS).map_err(|why| why.to_string())?; let boot = claim(slot.boot, "idle slot's volume")?; let root = claim(slot.root, "idle slot's ROOT")?; say!("supervisor: the idle slot is {}, granted with the slot table", idle.letter()); From b15771475f8fce708e6d4014efc49ebf6d28c56c Mon Sep 17 00:00:00 2001 From: japabu Date: Tue, 20 Oct 2026 20:47:05 +0200 Subject: [PATCH 4/4] Say what the boot volume and the loader's four partitions are The test images' comments on the file servers called the boot role "the running slot's volume", as the shipping manifests did before the previous commit; it is the ESP. kernel/src/gpt.rs's header named three loader partitions; it names four now that Role::Slot is one, and no file server serves the slot volume. The supervisor's slot_grant doc is rewrapped. Co-Authored-By: Claude Opus 5.5 --- kernel/src/gpt.rs | 11 ++++++----- system/supervisor/src/main.rs | 8 ++++---- tests/images/acpicase.toml | 2 +- tests/images/jobcase.toml | 2 +- tests/images/latencycase.toml | 2 +- tests/images/metalcase.toml | 2 +- tests/images/metaldevicecase.toml | 2 +- tests/images/netcase.toml | 2 +- tests/images/proctreecase.toml | 2 +- tests/images/testcases.toml | 2 +- 10 files changed, 18 insertions(+), 17 deletions(-) diff --git a/kernel/src/gpt.rs b/kernel/src/gpt.rs index 4b54ff00ff..49f1f92555 100644 --- a/kernel/src/gpt.rs +++ b/kernel/src/gpt.rs @@ -1,11 +1,12 @@ //! The partitions of the disks this kernel drives — the USB disks, until usbd //! drives the controller — and the ones the bootloader named. //! -//! **The loader names three partitions** ([`KernelArgs`]): the ROOT it read, -//! the running slot's volume and the log's. This kernel mounts none of them -//! but ROOT, which is in memory (`rootfs`); it answers their GUIDs in the -//! inventory ([`loaded`]) so the file servers for those roles find them, on -//! whatever disk and whoever drives it. +//! **The loader names four partitions** ([`KernelArgs`]): the ROOT it read, +//! the booted slot's volume, the log's and the ESP firmware loaded it from +//! (`Role::Boot`). This kernel mounts none of them but ROOT, which is in +//! memory (`rootfs`); it answers their GUIDs in the inventory ([`loaded`]), +//! so the log and boot file servers find theirs and the supervisor knows the +//! running slot, on whatever disk and whoever drives it. //! //! The boot partition's location must match firmware's account on a disk //! [`probe`] read, and [`probe_usb_disks`] waits for the disk that carries it diff --git a/system/supervisor/src/main.rs b/system/supervisor/src/main.rs index 4cec9cbf26..5549d085ae 100644 --- a/system/supervisor/src/main.rs +++ b/system/supervisor/src/main.rs @@ -1090,10 +1090,10 @@ impl<'a> Supervisor<'a> { /// **Which slot is idle is the loader's word, not the table's**: the /// running ROOT and volume are the ones the loader read, the table is the /// one on that ROOT's disk, and the idle slot is the one whose ROOT is not - /// it — and - /// its two partitions are reached only as `toyos_update::slots::grant` - /// admits them: claimed on a disk the kernel drives, and on one the block - /// service serves, through connectors minted for each. + /// it — and its two partitions are reached only as + /// `toyos_update::slots::grant` admits them: claimed on a disk the kernel + /// drives, and on one the block service serves, through connectors minted + /// for each. fn slot_grant(&mut self) -> Result { use toyos_abi::inventory::Role; use toyos_update::slots::{BOOT_LABEL, ROOT_LABEL, TABLE_LABEL}; diff --git a/tests/images/acpicase.toml b/tests/images/acpicase.toml index b7fdf1134b..889652cdb2 100644 --- a/tests/images/acpicase.toml +++ b/tests/images/acpicase.toml @@ -38,7 +38,7 @@ restart = true serves = ["block"] devices = ["pci:01:08:02/1b36:0010"] -# The file servers: DATA, the log and the running slot's volume, one process +# The file servers: DATA, the log and the boot volume, one process # each, serving every program the directories of its role. Started again when # one ends, on the same ports. [programs.fileserver] diff --git a/tests/images/jobcase.toml b/tests/images/jobcase.toml index 252c685e28..7593af8c91 100644 --- a/tests/images/jobcase.toml +++ b/tests/images/jobcase.toml @@ -29,7 +29,7 @@ restart = true serves = ["block"] devices = ["pci:01:08:02/1b36:0010"] -# The file servers: DATA, the log and the running slot's volume, one process +# The file servers: DATA, the log and the boot volume, one process # each, serving every program the directories of its role. Started again when # one ends, on the same ports. [programs.fileserver] diff --git a/tests/images/latencycase.toml b/tests/images/latencycase.toml index cb4496bb0f..aa8393be15 100644 --- a/tests/images/latencycase.toml +++ b/tests/images/latencycase.toml @@ -37,7 +37,7 @@ restart = true serves = ["block"] devices = ["pci:01:08:02/1b36:0010"] -# The file servers: DATA, the log and the running slot's volume, one process +# The file servers: DATA, the log and the boot volume, one process # each, serving every program the directories of its role. Started again when # one ends, on the same ports. [programs.fileserver] diff --git a/tests/images/metalcase.toml b/tests/images/metalcase.toml index ab7636b74e..9ff0221c3d 100644 --- a/tests/images/metalcase.toml +++ b/tests/images/metalcase.toml @@ -73,7 +73,7 @@ restart = true serves = ["block"] devices = ["pci:01:08:02/1b36:0010"] -# The file servers: DATA, the log and the running slot's volume, one process +# The file servers: DATA, the log and the boot volume, one process # each, serving every program the directories of its role. Started again when # one ends, on the same ports. [programs.fileserver] diff --git a/tests/images/metaldevicecase.toml b/tests/images/metaldevicecase.toml index b45d12bcfa..6be45fea16 100644 --- a/tests/images/metaldevicecase.toml +++ b/tests/images/metaldevicecase.toml @@ -62,7 +62,7 @@ restart = true serves = ["block"] devices = ["pci:01:08:02/1b36:0010"] -# The file servers: DATA, the log and the running slot's volume, one process +# The file servers: DATA, the log and the boot volume, one process # each, serving every program the directories of its role. Started again when # one ends, on the same ports. [programs.fileserver] diff --git a/tests/images/netcase.toml b/tests/images/netcase.toml index 556f9a2a69..b30ac7c2a7 100644 --- a/tests/images/netcase.toml +++ b/tests/images/netcase.toml @@ -44,7 +44,7 @@ restart = true serves = ["block"] devices = ["pci:01:08:02/1b36:0010"] -# The file servers: DATA, the log and the running slot's volume, one process +# The file servers: DATA, the log and the boot volume, one process # each, serving every program the directories of its role. Started again when # one ends, on the same ports. [programs.fileserver] diff --git a/tests/images/proctreecase.toml b/tests/images/proctreecase.toml index d5cf2de025..d43b01f1cf 100644 --- a/tests/images/proctreecase.toml +++ b/tests/images/proctreecase.toml @@ -57,7 +57,7 @@ restart = true serves = ["block"] devices = ["pci:01:08:02/1b36:0010"] -# The file servers: DATA, the log and the running slot's volume, one process +# The file servers: DATA, the log and the boot volume, one process # each, serving every program the directories of its role. Started again when # one ends, on the same ports. [programs.fileserver] diff --git a/tests/images/testcases.toml b/tests/images/testcases.toml index 0935a0a933..c5ad1ec174 100644 --- a/tests/images/testcases.toml +++ b/tests/images/testcases.toml @@ -95,7 +95,7 @@ restart = true serves = ["block"] devices = ["pci:01:08:02/1b36:0010"] -# The file servers: DATA, the log and the running slot's volume, one process +# The file servers: DATA, the log and the boot volume, one process # each, serving every program the directories of its role. Started again when # one ends, on the same ports. [programs.fileserver]