Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
250 changes: 29 additions & 221 deletions Cargo.lock

Large diffs are not rendered by default.

14 changes: 5 additions & 9 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ members = [
"lib/blockring/transport",
"lib/bootmap",
"lib/device-memory",
"lib/disk",
"lib/elf",
"lib/elide",
"lib/fat32",
Expand Down Expand Up @@ -80,6 +81,7 @@ members = [
"sdk/font",
"sdk/keymap",
"sdk/libc/arch",
"sdk/std/copies",
"sdk/osrelease",
"sdk/toyos",
"sdk/window",
Expand Down Expand Up @@ -119,8 +121,10 @@ members = [
"system/symbolize",
"system/terminal",
"system/toybox",
"system/toybox/copies",
"system/trace",
"system/update",
"system/update/copies",
"system/usbd",
"tests/guest/kernelprobe",
"tests/guest/metalprobe",
Expand Down Expand Up @@ -235,8 +239,7 @@ toyos-ssh = { path = "build/ssh" }
# disagree about which files on it are `logkeeper`'s.
toyos-wallclock = { path = "lib/wallclock" }
# Every SHA-256 the build takes: `NOTICE`'s record of a committed file, a
# store key, a ROOT's name, a release asset's digest, and the harness's of a
# body a guest fetches over TLS.
# store key, a ROOT's name and a release asset's digest.
toyos-sha2-hw = { path = "lib/sha2-hw" }
# The toolchain release's tarball, packed in-process (`build/release.rs`): Rust's
# own tar and gzip, where the binaries are hosts' tools.
Expand Down Expand Up @@ -269,13 +272,6 @@ toyos-xhci = { path = "lib/xhci" }
# The ACPI server's count line, so the judge reads the words the server writes
# and the job that waits for them matches.
acpiserver-api = { path = "lib/acpiserver-api" }
# The desktop's geometry and the prompt's colour, so `consent_prompt` clicks
# where the compositor draws and finds the prompt by what the compositor paints.
toyos-desktop = { path = "system/compositor/desktop" }
# `common::https`'s server, the peer a guest's HTTPS client is judged
# against, and the authority and certificates it presents, made per run.
rustls = { version = "0.23", default-features = false, features = ["ring", "std", "tls12"] }
rcgen = "0.14"
# The metal loop, the suite's command line, and what the metal profile
# derives, times and reads off a boot.
toyos-metal = { path = "tests/metal" }
Expand Down
20 changes: 4 additions & 16 deletions build/bootlog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,16 +246,6 @@ pub fn kernel_records(log: &str) -> String {
log.split_inclusive('\n').filter(|line| !is_program_line(line)).collect()
}

/// One program's lines, by the name the supervisor started it under, as `logkeeper` read them
/// out of its log ring: each line's text, newline-terminated.
pub fn lines_of(log: &str, name: &str) -> String {
log.lines()
.filter_map(toyos_logstream::program_line)
.filter(|said| said.tag == name)
.map(|said| format!("{}\n", said.text))
.collect()
}

/// The AP bring-up record, in `kernel/src/arch/x86_64/smp.rs`. A reader asks for the
/// trailing ` online` as a separate word: the same head carries the failure.
pub const AP_BRINGUP: &str = "SMP: AP cpu";
Expand All @@ -268,7 +258,7 @@ const NAME_LEN: usize = 28;
/// truncated to what [`NAME_LEN`] holds.
///
/// **A predicate looking for the whole name finds nothing on a good boot**:
/// `test_rs_null_sink_client_exits` is `test_rs_null_sink_client_ex` on the wire.
/// `test_rs_munmap_reissues_read_window` is `test_rs_munmap_reissues_rea` on the wire.
pub fn recorded_name(binary: &str) -> String {
let base = binary.rsplit('/').next().unwrap_or(binary);
base[..base.len().min(NAME_LEN - 1)].to_string()
Expand Down Expand Up @@ -800,8 +790,8 @@ mod tests {
}

/// A program writing a kernel verdict's words, or another program's head,
/// is left out of the kernel's records and read as its own; a kernel
/// record's continuation line is the kernel's.
/// is left out of the kernel's records; a kernel record's continuation
/// line is the kernel's.
#[test]
fn a_programs_line_is_never_the_kernels() {
let log = "[2026-09-08 16:08:23 2.100 cpu0 kernel] exit: test_rs_job pid=4 code=3 cpu=1ms\n\
Expand All @@ -812,16 +802,14 @@ mod tests {
let kernel = kernel_records(log);
assert!(!kernel.contains("code=0"), "{kernel}");
assert!(kernel.contains("code=3") && kernel.contains(" its second line\n"), "{kernel}");
assert_eq!(lines_of(log, "netstack"), "netstack: MAC 52:54:00:12:34:56\n");
assert_eq!(lines_of(log, "test-runner").lines().count(), 2);
assert!(is_program_line(log.lines().nth(3).expect("five lines")));
}

/// The truncation, which is what a whole-name predicate would miss.
#[test]
fn a_long_binarys_record_is_the_prefix_the_kernel_keeps() {
assert_eq!(recorded_name("test_rs_mkdir_cap"), "test_rs_mkdir_cap");
assert_eq!(recorded_name("test_rs_null_sink_client_exits"), "test_rs_null_sink_client_ex");
assert_eq!(recorded_name("test_rs_munmap_reissues_read_window"), "test_rs_munmap_reissues_rea");
assert_eq!(recorded_name("/system/bin/echo"), "echo");
assert_eq!(recorded_name("").len(), 0);
}
Expand Down
10 changes: 1 addition & 9 deletions build/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -973,8 +973,6 @@ pub struct Plan {
pub params: Vec<String>,
/// The version the image's signed header names.
pub version: u64,
/// Room for a second slot, or none: a machine that cannot update itself.
pub second: Option<image::SecondSlot>,
}

impl Plan {
Expand All @@ -985,7 +983,6 @@ impl Plan {
features: features.iter().map(|f| (*f).to_string()).collect(),
params: params.iter().map(|p| (*p).to_string()).collect(),
version: image::version_now(),
second: None,
}
}
}
Expand Down Expand Up @@ -1014,7 +1011,6 @@ pub fn plan_for(root: &Path, boot: &Boot, debug: bool, args: &[String]) -> Plan
features: features.split(',').filter(|f| !f.is_empty()).map(Into::into).collect(),
params: param,
version: image::version_now(),
second: None,
}
}

Expand Down Expand Up @@ -1950,7 +1946,7 @@ pub fn build_test_image(
&parts.root,
&plan.params.join(","),
image::Signing { key: crate::signing::key(), version: plan.version },
plan.second,
None,
)
}

Expand Down Expand Up @@ -3016,18 +3012,14 @@ mod tests {
"images/diag.toml",
"images/console.toml",
"tests/images/acpicase.toml",
"tests/images/blockgrantcase.toml",
"tests/images/consentcase.toml",
"tests/images/hostedclangcase.toml",
"tests/images/jobcase.toml",
"tests/images/latencycase.toml",
"tests/images/logstallcase.toml",
"tests/images/metalcase.toml",
"tests/images/metaldevicecase.toml",
"tests/images/netcase.toml",
"tests/images/panelcase.toml",
"tests/images/proctreecase.toml",
"tests/images/slotscase.toml",
"tests/images/testcases.toml",
"tests/images/virtjobcase.toml",
"tests/images/virtpaniccase.toml",
Expand Down
15 changes: 15 additions & 0 deletions issues/the-supervisor-is-host-tested-and-owns-the-stop.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,21 @@ nothing.
the crate for it, named per decision in the PR; `toyos_manifest::launch`,
who may start what and the launcher's badge codec, moves into the crate
and out of `toyos-manifest`. No decision exists in two places.
**The guest arms this stage cuts.** The owner's testing ruling refuses
them, and they stand until this stage's host tests hold what they judge:
the `launch_toctou` metal row (a launch resolves to its row's own image,
never the caller's reopened path), the `app_view` metal row (an installed
package's view, the launcher refusing a package whose folder is a file,
grants refused in the machine's session) and `process_tree`'s launcher arms
(D launched and E asked of the supervisor, a launch whose place is a pipe
refused, and a `cat` a shell `detach`es outliving it). Two arms the test
sweep cut are this stage's to hold: the supervisor stopping the machine on
`acpiserver`'s press (`acpi_power_button`), and its grant of the idle
slot's partitions to `update` through the block service
(`update_writes_the_idle_slot_through_the_block_service`).
**Exit**, beside the one above: each behaviour named here has a host test
in the crate and a mutation that reds it, and the PR deletes the two rows
and the arms.
3. **The supervisor owns the stop.** It asks each service it started to
finish by a quit with reason terminate
(`issues/a-childs-end-is-an-event-and-a-parent-takes-its-children-down.md`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,26 @@ embedded-controller query the tables define, a T14 row reads the battery's
state as the interpreter evaluated it beside Linux's reading of the same
machine, and the press issue is closed by its own exit.

**Three T14 rows drive the server on a boot, which the testing ruling
refuses** (owner, #890: integration between modules is tested only for the
kernel): `acpi_server_events` reads each embedded-controller query number
once with its count, `acpi_tables_loaded` the 14 definition blocks loaded
and `\_S5`'s `SLP_TYPa` of 7, and `battery_read` the battery and adapter as
the interpreter evaluated them over the controller. `acpi_server_events`
and `battery_read` stay until host tests hold what each reads, because
nothing else reads the T14's firmware.
The oracle is the firmware's own table bytes, not another OS's reading of
them (the orchestrator, ruling on #893); what the tree keeps of them is
extracts only, as "Extracts only" says. A definition block's load takes
the whole block, which no extract is, so each block's load and `\_S5`
stay held by the T14's metal row that loads its own tables whole,
`acpi_tables_loaded`. Owner: this stage.
**Exit**: extracts of the T14's tables, masked, are committed with their
provenance; host tests evaluate each query method the controller raises,
and the battery's and adapter's methods, over a scripted host and
controller; and `acpi_server_events` and `battery_read` leave
`tests/toyos.rs` in that diff.

What that check found, which whoever builds on the interpreter would
otherwise pay to find again:

Expand Down
2 changes: 1 addition & 1 deletion lib/acpi/tests/fixtures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ fn a_second_firmwares_bytes_decode_to_that_machines_own_windows() {
}

/// q35's ICH9 power-management block at the base QEMU's firmware put it,
/// 0x600, as the FADT names it: the blocks the `acpi_power_button` guest test
/// 0x600, as the FADT names it: the blocks the `acpi_press_is_one_sci` guest test
/// takes its SCI through, and the 32-bit fields and their `X_` twins agreeing.
#[test]
fn the_q35_fadt_names_the_fixed_hardware_its_sci_is_served_through() {
Expand Down
9 changes: 9 additions & 0 deletions lib/disk/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "toyos-disk"
description = "A partition as its reader and writer holds it, in whole 4 KiB blocks, whoever serves it: the trait, pure."
version = "0.1.0"
edition = "2021"
license = "MIT OR Apache-2.0"

[lib]
doctest = false
45 changes: 45 additions & 0 deletions lib/disk/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//! A partition as a program that reads and writes its blocks holds it, in
//! whole 4 KiB blocks, whoever serves it: the one trait the block service's
//! sessions, a claim on the kernel's disk and every test's memory implement.
//!
//! **A block is 4 KiB here, on every source.** A partition that is not whole
//! 4 KiB blocks is refused before it is served, so nothing below has a second
//! block size.

pub const BLOCK: usize = 4096;

/// Why a disk did not do what it was asked.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DiskError {
/// The device's own word: the transfer was attempted and did not happen,
/// or a flush left writes the device would not keep.
Device,
/// The block service is gone and would not come back.
Gone,
/// Past the end of the partition.
Range,
/// A write the block service's grant to this holder does not make.
ReadOnly,
}

/// A partition, a block at a time or several.
pub trait Disk {
fn blocks(&self) -> u64;
/// `out.len()` is whole blocks.
fn read(&mut self, first: u64, out: &mut [u8]) -> Result<(), DiskError>;
/// `data.len()` is whole blocks; durable once [`Disk::flush`] answers `Ok`.
fn write(&mut self, first: u64, data: &[u8]) -> Result<(), DiskError>;
/// Every write answered before this, out of the device's cache.
fn flush(&mut self) -> Result<(), DiskError>;
}

/// How many blocks `len` bytes from `first` are, or `Range` past the end of
/// a partition `blocks` long.
pub fn span(first: u64, len: usize, blocks: u64) -> Result<u64, DiskError> {
assert!(len.is_multiple_of(BLOCK), "a transfer of {len} bytes is no whole blocks");
let count = (len / BLOCK) as u64;
match first.checked_add(count) {
Some(end) if end <= blocks => Ok(count),
_ => Err(DiskError::Range),
}
}
74 changes: 74 additions & 0 deletions sdk/libc/arch/src/datagram_sends.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
//! What a datagram socket asks netstack when it sends: a socket never bound is
//! bound once, at any address to a port the stack chooses, before its first
//! datagram, and a bind refused sends nothing.

use crate::dgramreq::{send_to, Datagrams};

/// What a socket asked of netstack, in order.
#[derive(Debug, PartialEq, Eq)]
enum Asked {
Bind([u8; 4], u16),
Send(Vec<u8>, [u8; 4], u16),
}

/// A socket over a netstack that answers every call, or refuses binds.
struct Socket {
held: bool,
binds: bool,
asked: Vec<Asked>,
}

impl Socket {
fn new(held: bool) -> Self {
Socket { held, binds: true, asked: Vec::new() }
}
}

impl Datagrams for Socket {
type Error = &'static str;
fn held(&self) -> bool {
self.held
}
fn bind(&mut self, ip: [u8; 4], port: u16) -> Result<(), Self::Error> {
self.asked.push(Asked::Bind(ip, port));
match self.binds {
true => {
self.held = true;
Ok(())
}
false => Err("no port free"),
}
}
fn send(&mut self, data: &[u8], ip: [u8; 4], port: u16) -> Result<usize, Self::Error> {
self.asked.push(Asked::Send(data.to_vec(), ip, port));
Ok(data.len())
}
}

const PEER: [u8; 4] = [10, 0, 2, 2];

#[test]
fn a_socket_never_bound_is_bound_to_a_port_the_stack_chooses_before_its_first_send() {
let mut socket = Socket::new(false);
assert_eq!(send_to(&mut socket, b"ping", PEER, 7), Ok(4));
assert_eq!(send_to(&mut socket, b"again", PEER, 7), Ok(5));
assert_eq!(
socket.asked,
[Asked::Bind([0; 4], 0), Asked::Send(b"ping".to_vec(), PEER, 7), Asked::Send(b"again".to_vec(), PEER, 7)],
"one bind, before the first send alone",
);
}

#[test]
fn a_bound_socket_sends_with_no_bind() {
let mut socket = Socket::new(true);
assert_eq!(send_to(&mut socket, b"ping", PEER, 7), Ok(4));
assert_eq!(socket.asked, [Asked::Send(b"ping".to_vec(), PEER, 7)]);
}

#[test]
fn a_refused_bind_sends_nothing() {
let mut socket = Socket { binds: false, ..Socket::new(false) };
assert_eq!(send_to(&mut socket, b"ping", PEER, 7), Err("no port free"));
assert_eq!(socket.asked, [Asked::Bind([0; 4], 0)]);
}
16 changes: 8 additions & 8 deletions sdk/libc/arch/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
//! IEEE 754's rounding of hexadecimal input computed exactly, AArch64's
//! `long double` widening against compiler-builtins', the errno codes against
//! `include/errno.h`, IPv4 addresses and their texts against the host C
//! library's, and the socket option rule against the host's `setsockopt` and
//! `getsockopt`, the rule that reads a stream's end, and `realpath`'s
//! resolution over a tree laid out here.
//! library's, the socket option rule against the host's `setsockopt` and
//! `getsockopt`, a datagram send's bind, and `realpath`'s resolution over a
//! tree laid out here.

#[cfg(test)]
extern crate alloc;
Expand All @@ -27,6 +27,9 @@ mod arch;
#[allow(dead_code)]
mod errcode;
#[cfg(test)]
#[path = "../../src/dgramreq.rs"]
mod dgramreq;
#[cfg(test)]
#[path = "../../src/elfsym.rs"]
mod elfsym;
#[cfg(test)]
Expand Down Expand Up @@ -60,9 +63,6 @@ mod sigmask;
#[path = "../../src/sockopt.rs"]
mod sockopt;
#[cfg(test)]
#[path = "../../src/streamend.rs"]
mod streamend;
#[cfg(test)]
#[path = "../../src/strtonum.rs"]
mod strtonum;
#[cfg(test)]
Expand All @@ -75,6 +75,8 @@ mod utf8;
#[cfg(test)]
mod alarm_requests;
#[cfg(test)]
mod datagram_sends;
#[cfg(test)]
mod descriptor_requests;
#[cfg(test)]
mod dladdr_symbols;
Expand Down Expand Up @@ -107,8 +109,6 @@ mod signal_masks;
#[cfg(test)]
mod socket_options;
#[cfg(test)]
mod stream_ends;
#[cfg(test)]
mod strtonum_differential;
#[cfg(test)]
mod text_differential;
Expand Down
Loading