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 6b91c9fa58..ac0d019780 100644 --- a/images/system.toml +++ b/images/system.toml @@ -270,7 +270,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/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/issues/the-running-slots-volume-is-claimable.md b/issues/the-running-slots-volume-is-claimable.md index ee88d7c47e..2eaff6b3e9 100644 --- a/issues/the-running-slots-volume-is-claimable.md +++ b/issues/the-running-slots-volume-is-claimable.md @@ -9,24 +9,23 @@ 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 — 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 (`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..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 @@ -165,6 +166,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 7755ab5f41..dfee846b68 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"), } } } @@ -163,6 +167,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/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 aa1da11175..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)>, } @@ -121,12 +123,17 @@ 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(); + 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); @@ -150,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/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..5549d085ae 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( @@ -1088,16 +1088,18 @@ 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 - /// 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. + /// 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. fn slot_grant(&mut self) -> Result { use toyos_abi::inventory::Role; 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()); @@ -3037,37 +3041,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 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 214b4ed5da..bab54d9721 100644 --- a/tests/images/metalcase.toml +++ b/tests/images/metalcase.toml @@ -82,7 +82,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 cb08b4f7f6..7fe8cb4253 100644 --- a/tests/images/metaldevicecase.toml +++ b/tests/images/metaldevicecase.toml @@ -70,7 +70,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 3a21dc8f79..52242cae07 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 2c141af5c4..9889f9eae9 100644 --- a/tests/images/testcases.toml +++ b/tests/images/testcases.toml @@ -104,7 +104,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]