Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
b9f562d
The toolchain builds clang and ld.lld that run on ToyOS, statically l…
Japabu Oct 9, 2026
6ce9eac
The wallpaper ships as src/wallpaper.rs draws it, with no JPEG betwee…
Japabu Oct 9, 2026
f71697b
os-release's commit, time and dirty flag are read with `git`, and git…
Japabu Oct 9, 2026
0e05e6a
The build formats its FAT32 volumes itself, and `fatfs` is only the o…
Japabu Oct 9, 2026
1c87ae6
soundserver takes rubato without its FFT resampler
Japabu Oct 9, 2026
c466cae
Merge main at d6298c83e into the ToyOS-hosted clang and LLD
Japabu Oct 9, 2026
2849a83
The build writes its partition tables itself, and the `gpt` crate is …
Japabu Oct 9, 2026
d82fef9
A partition table's GUIDs are sixteen bytes from getrandom, and `uuid…
Japabu Oct 9, 2026
a0e5b76
The desktop's eight icons are drawn at build time, and resvg leaves t…
Japabu Oct 9, 2026
ea95e75
An ignored SIGALRM drops a due alarm, the corpus reads libc's new nam…
Japabu Oct 9, 2026
3b36e81
src/icons.rs takes the midpoints and the tag type clippy asks for
Japabu Oct 9, 2026
00e4c60
The loader calls UEFI through its own bindings, and uefi and uefi-ser…
Japabu Oct 9, 2026
d7cf659
The wallpaper's powf and expf are libm's, a host test pins its bytes,…
Japabu Oct 9, 2026
cae8344
An icon ships drawn only when git tracks it, and a moved pixel says h…
Japabu Oct 9, 2026
5105004
toyos-gpt answers an entry's name from the array it checked, and the …
Japabu Oct 9, 2026
ce7cd51
The FAT32 format keeps only the branches a volume reaches
Japabu Oct 9, 2026
24b3fb6
The host-tools row admits only os-release's git reads, and two citati…
Japabu Oct 9, 2026
d3ffcc4
Merge remote-tracking branch 'origin/main' into wt/toyos-uefi
Japabu Oct 9, 2026
a29d650
A load option's HARDDRIVE node is read by main's rule again, and a pa…
Japabu Oct 9, 2026
4f3a775
Merge origin/main at 198a9d38e into the ToyOS-hosted clang and LLD
Japabu Oct 9, 2026
a5d7972
SIGALRM is declared once, sigaction answers the previous action whole…
Japabu Oct 9, 2026
ab97725
The loader issue's stage 4 panics straight to the shutdown from EXITI…
Japabu Oct 9, 2026
4ce7ce7
The ToyOS-hosted clang builds from the sources the host's LLVM keeps …
Japabu Oct 9, 2026
85d3eb4
An LLVM the hosted clang needs is made with the worktree lock held ex…
Japabu Oct 9, 2026
dbe2bbb
Merge origin/main at 2b1a0e746 into the ToyOS-hosted clang and LLD
Japabu Oct 9, 2026
1bfc3e9
A held build decides whether its LLVM is whole under the key's use, a…
Japabu Oct 9, 2026
d83c3a9
Merge #811 (wt/toyos-rubato-r0): rubato without default features
Japabu Oct 10, 2026
6f64af2
Merge #813 (wt/toyos-builddeps): image, gix, fatfs, gpt and uuid out …
Japabu Oct 10, 2026
25c1ed3
Merge #814 (wt/toyos-icons): build-time icons, resvg out
Japabu Oct 10, 2026
df6be4b
Merge #815 (wt/toyos-uefi): the loader's own UEFI bindings
Japabu Oct 10, 2026
17fbad7
Merge #809 (wt/toyos-selfhost-s1): static clang and ld.lld for ToyOS
Japabu Oct 10, 2026
50d3c8e
Merge main at a1eb2c0b9, which carries DATA's atomic commits (#816), …
Japabu Oct 10, 2026
3fe556c
Merge origin/main (#812, #820, #824, #825, #826, #827, #835) into the…
Japabu Oct 10, 2026
a10107e
Merge origin/main at a5242a411 (#833, #822, #831, #844, #842, #836, #…
Japabu Oct 10, 2026
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
1,772 changes: 96 additions & 1,676 deletions Cargo.lock

Large diffs are not rendered by default.

17 changes: 9 additions & 8 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -163,12 +163,9 @@ license = "MIT OR Apache-2.0"
default-run = "toyos-build"

[dependencies]
fatfs = "0.3.6"
fontdue = "0.9"
gpt = "3.1.0"
libc = "0.2"
toml = "0.8"
uuid = { version = "1", features = ["v4"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
bcachefs = { path = "bcachefs" }
Expand Down Expand Up @@ -205,15 +202,18 @@ toyos-blackbox = { path = "toyos-blackbox" }
# The signed image, the slot table and the signer: `src/signing.rs` signs with
# the same crate the loader and `/system/bin/update` verify with.
toyos-update = { path = "toyos-update", features = ["sign"] }
# A throwaway key's seed, from the operating system. Already resolved here.
# A throwaway key's seed and every GUID a partition table names, from the
# operating system.
getrandom = "0.3"
# The wallpaper's `powf` and `expf`, in Rust and the same on every host, where
# `f32`'s own call the host's C library and ROOT's UUID is derived from its bytes.
libm = "0.2.16"
# The one `openssh-key-v1` reader and writer, shared with the SSH server's
# host key, and the `SHA256:` fingerprint the owner's key is named by.
toyos-ssh = { path = "toyos-ssh" }
# `classify`, so the host reading a log volume and `logkeeper` writing it cannot
# disagree about which files on it are `logkeeper`'s.
toyos-wallclock = { path = "toyos-wallclock" }
image = { version = "0.25", default-features = false, features = ["jpeg"] }
# 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.
Expand All @@ -227,15 +227,16 @@ flate2 = { version = "1", default-features = false, features = ["rust_backend"]
# takes (`issues/the-internet-clients-work-unchanged.md`), whose C
# and assembly the host's `cc` compiles.
ureq = { version = "3", default-features = false, features = ["rustls"] }
# The commit an image is built from, its time and whether the tree is that
# commit's, where `git` is a host's C tool (`src/build.rs`'s `release`).
gix = { version = "0.88", default-features = false, features = ["status", "sha1"] }
# The authorities `build::trust_roots` writes to ROOT, as the rustls project
# publishes the Mozilla root program, and the PEM it writes them in.
webpki-root-certs = "1"
pem = "4"

[dev-dependencies]
# The FAT implementation `src/fatformat.rs` is judged against, byte for byte.
fatfs = "0.3.6"
# The GPT writer `src/gptwrite.rs` is judged against, byte for byte.
gpt = "3.1.0"
# `USER_TOP`, so the harness judges a held `rsp` against the bound the kernel
# checks every user pointer against and not against a second copy of it.
toyos-userbound = { path = "toyos-userbound" }
Expand Down
3 changes: 2 additions & 1 deletion NOTICE
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,8 @@ would have arrived unremarked. `assets/` is walked whole for that reason.
MIT requires the notice to be included in copies. Before this file the only
copy of it in reach was a `phosphor-icons/` directory on one developer's disk,
which `.gitignore` line 7 keeps out of the repository. They ship as
`share/icons/*.svg` on every ROOT that carries `assets/`.
`share/icons/*.alpha`, drawn from them at build time by `src/icons.rs`, on
every ROOT that carries `assets/`.


assets/fonts/OpenSans-*.ttf — Open Sans, SIL Open Font License 1.1
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ Licensed under either of
at your option.

Some of what the repository carries is not ours and is under other terms:
`userland/doom` is GPL-2.0, `assets/` holds a font, a set of icons, a wallpaper
`userland/doom` is GPL-2.0, `assets/` holds a font, a set of icons
and id Software's Doom shareware IWAD,
and `tests/testcases/` holds TinyCC's test corpus. Third-party crates keep
their own upstream licenses. **[NOTICE](NOTICE) is the list**, item by item,
Expand Down
Binary file removed assets/wallpaper.jpg
Binary file not shown.
4 changes: 0 additions & 4 deletions bootloader/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,3 @@ toyos-update = { path = "../toyos-update" }
# soft-float, and the x86 backend reaches for SSE and SHA-NI registers a UEFI
# application may not assume are its own.
sha2 = { version = "0.10", default-features = false, features = ["force-soft"] }
# `alloc`: `variable_keys` and `get_variable_boxed`, which are how the boot
# entry pointing at this image is found among the firmware's own variables.
uefi = { version = "0.26.0", default-features = false, features = ["alloc"] }
uefi-services = { version = "0.23.0", features = ["panic_handler", "logger"] }
22 changes: 10 additions & 12 deletions bootloader/src/attempt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@

use alloc::string::String;
use toyos_update::record::{self, Record};
use uefi::proto::media::file::{Directory, File, FileAttribute, FileMode};
use uefi::prelude::*;
use uefi::{cstr16, CStr16};
use crate::efi::{cstr16, CStr16, File, Mode, Status, SystemTable};

use crate::loaderlog;

Expand All @@ -25,12 +23,12 @@ const NAME: &CStr16 = cstr16!("attempts");
/// **`Err` is not zero.** A volume this cannot read is one the bound is off on,
/// and the caller says so rather than treating an unreadable stick as a first
/// attempt — which would be a bound that silently never fires.
pub fn read(system_table: &SystemTable<Boot>, guid: &[u8; 16]) -> Result<Record, String> {
pub fn read(system_table: &SystemTable, guid: &[u8; 16]) -> Result<Record, String> {
loaderlog::with_volume(system_table, guid, |root| {
let file = match root.open(NAME, FileMode::Read, FileAttribute::empty()) {
let file = match root.open(NAME, Mode::Read) {
Ok(file) => file,
// No file is a first attempt, which is every freshly flashed image.
Err(e) if e.status() == Status::NOT_FOUND => return Ok(Record::default()),
Err(Status::NOT_FOUND) => return Ok(Record::default()),
Err(e) => return Err(alloc::format!("{NAME} would not open ({e})")),
};
let Some(mut file) = file.into_regular_file() else {
Expand All @@ -50,7 +48,7 @@ pub fn read(system_table: &SystemTable<Boot>, guid: &[u8; 16]) -> Result<Record,
///
/// Written **before the kernel is handed the machine**, because a count written
/// after it is a count a hang never gets to.
pub fn write(system_table: &SystemTable<Boot>, guid: &[u8; 16], record: &Record) -> Result<(), String> {
pub fn write(system_table: &SystemTable, guid: &[u8; 16], record: &Record) -> Result<(), String> {
loaderlog::with_volume(system_table, guid, |root| put(root, guid, record)).and_then(|inner| inner)
}

Expand All @@ -60,25 +58,25 @@ pub fn write_chosen(guid: &[u8; 16], record: &Record) -> Result<(), String> {
loaderlog::with_open_volume(|root| put(root, guid, record)).and_then(|inner| inner)
}

fn put(root: &mut Directory, guid: &[u8; 16], record: &Record) -> Result<(), String> {
fn put(root: &mut File, guid: &[u8; 16], record: &Record) -> Result<(), String> {
// Deleted and recreated rather than rewound: a fixed-width record is
// still a record a shorter write would leave the tail of.
match root.open(NAME, FileMode::ReadWrite, FileAttribute::empty()) {
match root.open(NAME, Mode::ReadWrite) {
Ok(stale) => {
if let Err(e) = stale.delete() {
return Err(alloc::format!("{NAME} would not delete ({e})"));
}
}
Err(e) if e.status() == Status::NOT_FOUND => {}
Err(Status::NOT_FOUND) => {}
Err(e) => return Err(alloc::format!("{NAME} would not open ({e})")),
}
let file = root
.open(NAME, FileMode::CreateReadWrite, FileAttribute::empty())
.open(NAME, Mode::CreateReadWrite)
.map_err(|e| alloc::format!("{NAME} would not be created ({e})"))?;
let Some(mut file) = file.into_regular_file() else {
return Err(alloc::format!("{NAME} on the log partition is a directory"));
};
file.write(&record.encode(guid)).map_err(|e| alloc::format!("{NAME} would not write ({e})"))?;
file.write(&record.encode(guid)).map_err(|(e, _)| alloc::format!("{NAME} would not write ({e})"))?;
// **Flushed here and not at the handoff.** What this file exists to
// survive is a power cut, and a byte in a cache survives nothing.
file.flush().map_err(|e| alloc::format!("{NAME} would not flush ({e})"))?;
Expand Down
11 changes: 3 additions & 8 deletions bootloader/src/blackbox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@

use alloc::string::String;
use alloc::vec::Vec;
use uefi::prelude::*;
use uefi::table::boot::{AllocateType, MemoryType};
use crate::efi::{AllocateType, SystemTable};

use toyos_blackbox::{BYTES, PHYS, State};
use toyos_wallclock::Civil;
Expand Down Expand Up @@ -53,12 +52,8 @@ pub struct Page(u64);
/// last one's file or replaces it is what the page decides — so a refusal is
/// returned as a line for the caller to write rather than printed here, where a
/// machine with no console would lose it.
pub fn claim(system_table: &SystemTable<Boot>) -> (Option<Page>, Option<String>) {
match system_table.boot_services().allocate_pages(
AllocateType::Address(PHYS),
MemoryType::LOADER_DATA,
toyos_blackbox::PAGES,
) {
pub fn claim(system_table: &SystemTable) -> (Option<Page>, Option<String>) {
match system_table.boot_services().allocate_pages(AllocateType::Address(PHYS), toyos_blackbox::PAGES) {
Ok(at) => {
// `AllocateType::Address` allocates that address or fails; firmware
// answering with another one has not done what was asked of it.
Expand Down
45 changes: 18 additions & 27 deletions bootloader/src/bootnext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,7 @@
//! that booted us from a removable-media fallback path has no entry of ours at
//! all and must be told so rather than have one guessed at.

use uefi::prelude::*;
use uefi::proto::device_path::media::PartitionSignature;
use uefi::proto::device_path::{DevicePath, DeviceSubType, DeviceType};
use uefi::proto::loaded_image::LoadedImage;
use uefi::table::runtime::{VariableAttributes, VariableVendor};
use uefi::CStr16;
use crate::efi::{cstr16, CStr16, DevicePath, Handle, HardDrive, LoadedImage, SystemTable, VariableAttributes, GLOBAL_VARIABLE};

/// The head of every line this module writes.
const HEAD: &str = "Boot chain:";
Expand All @@ -39,7 +34,7 @@ const LOAD_OPTION_HEAD: usize = 6;
/// A refusal is not a failure of the boot: the kernel still runs and still seals
/// its page. What is lost is the *next* boot, so the line says exactly that
/// rather than reporting a variable write.
pub fn point_at_us(handle: Handle, system_table: &SystemTable<Boot>) {
pub fn point_at_us(handle: Handle, system_table: &SystemTable) {
let Some(ours) = our_partition(handle, system_table) else {
return println!(
"{HEAD} firmware did not load this image off a GPT partition, so there is no entry \
Expand All @@ -54,7 +49,7 @@ pub fn point_at_us(handle: Handle, system_table: &SystemTable<Boot>) {
};
let write = system_table.runtime_services().set_variable(
cstr16!("BootNext"),
&VariableVendor::GLOBAL_VARIABLE,
&GLOBAL_VARIABLE,
// Non-volatile, because it has to survive the reset that is the whole point.
VariableAttributes::NON_VOLATILE
| VariableAttributes::BOOTSERVICE_ACCESS
Expand All @@ -71,24 +66,21 @@ pub fn point_at_us(handle: Handle, system_table: &SystemTable<Boot>) {
}

/// The GPT partition GUID of the volume firmware loaded this image from.
fn our_partition(handle: Handle, system_table: &SystemTable<Boot>) -> Option<[u8; 16]> {
fn our_partition(handle: Handle, system_table: &SystemTable) -> Option<[u8; 16]> {
let bs = system_table.boot_services();
let image = crate::protocol::exclusive::<LoadedImage>(bs, handle).ok()?;
let image = bs.exclusive::<LoadedImage>(handle).ok()?;
let device = image.device()?;
let path = crate::protocol::exclusive::<DevicePath>(bs, device).ok()?;
hard_drive_guid(path.node_iter())
let path = bs.exclusive::<DevicePath>(device).ok()?;
hard_drive_guid(path.nodes())
}

/// The GPT signature of the first HARDDRIVE node in a device path, or `None`
/// where the path has none — a network boot, or a disk with no GPT.
fn hard_drive_guid<'a>(nodes: impl Iterator<Item = &'a uefi::proto::device_path::DevicePathNode>) -> Option<[u8; 16]> {
for node in nodes {
if node.full_type() != (DeviceType::MEDIA, DeviceSubType::MEDIA_HARD_DRIVE) {
continue;
}
let hd = <&uefi::proto::device_path::media::HardDrive>::try_from(node).ok()?;
if let PartitionSignature::Guid(guid) = hd.partition_signature() {
return Some(guid.to_bytes());
fn hard_drive_guid<'a>(nodes: impl Iterator<Item = &'a [u8]>) -> Option<[u8; 16]> {
for node in nodes.filter(|node| (node[0], node[1]) == HardDrive::TYPE) {
let hd = HardDrive::parse(node)?;
if hd.signature_type == HardDrive::GUID_SIGNATURE {
return Some(hd.signature);
}
}
None
Expand All @@ -98,17 +90,16 @@ fn hard_drive_guid<'a>(nodes: impl Iterator<Item = &'a uefi::proto::device_path:
///
/// Every entry is read rather than only those in `BootOrder`: an entry the owner
/// has moved out of the order is still ours and still the one to come back to.
fn entry_for(system_table: &SystemTable<Boot>, ours: &[u8; 16]) -> Option<u16> {
fn entry_for(system_table: &SystemTable, ours: &[u8; 16]) -> Option<u16> {
let rt = system_table.runtime_services();
let keys = rt.variable_keys().ok()?;
let mut found: Option<u16> = None;
for key in keys {
if key.vendor != VariableVendor::GLOBAL_VARIABLE {
for (name, vendor) in keys {
if vendor != GLOBAL_VARIABLE {
continue;
}
let Ok(name) = key.name() else { continue };
let Some(number) = entry_number(name) else { continue };
let Ok((bytes, _)) = rt.get_variable_boxed(name, &key.vendor) else { continue };
let Some(number) = entry_number(&name) else { continue };
let Ok((bytes, _)) = rt.get_variable(&name, &vendor) else { continue };
if !load_option_names(&bytes, ours) {
continue;
}
Expand All @@ -121,7 +112,7 @@ fn entry_for(system_table: &SystemTable<Boot>, ours: &[u8; 16]) -> Option<u16> {

/// `Boot0003` is entry 3; anything else here is some other global variable.
fn entry_number(name: &CStr16) -> Option<u16> {
let mut chars = name.iter().map(|c| char::from(*c));
let mut chars = name.units().iter().map(|&unit| char::from_u32(u32::from(unit)).unwrap_or(char::REPLACEMENT_CHARACTER));
for want in ENTRY_PREFIX.chars() {
if chars.next()? != want {
return None;
Expand Down
Loading
Loading