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
18 changes: 11 additions & 7 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,13 @@ members = [
"lib/acpiserver-api",
"lib/bcachefs",
"lib/blackbox",
"lib/blockdev",
"lib/blockhold",
"lib/blockring",
"lib/blockserve",
"lib/bootmap",
"lib/crypto",
"lib/device-memory",
"lib/disk",
"lib/elf",
"lib/edid",
"lib/elide",
Expand Down Expand Up @@ -220,6 +220,9 @@ toyos-symbols = { path = "lib/symbols" }
# `llvm-objdump`, is a C++ tool where a Rust one does the job.
iced-x86 = { version = "1.21", default-features = false, features = ["std", "decoder", "instr_info"] }
toyos-fat32 = { path = "lib/fat32" }
# The device `toyos-fat32` writes and reads a volume through: here, the bytes
# being built.
toyos-blockdev = { path = "lib/blockdev" }
# The names the loader looks a CPU's microcode up by, which `microcode` writes
# each patch it splits out of a vendor's file under.
toyos-microcode = { path = "lib/microcode" }
Expand Down
42 changes: 11 additions & 31 deletions build/bin/imgstat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,34 +12,8 @@ use std::collections::BTreeMap;
use std::path::PathBuf;

use bcachefs::{Mounted, VecBlockIO};
use toyos_fat32::{BlockAccess, Fat32, IoError};

/// A partition, in memory, byte-addressed. Read-only: this reports.
struct Volume(Vec<u8>);

impl BlockAccess for Volume {
fn capacity(&self) -> u64 {
self.0.len() as u64
}

fn read_at(&mut self, offset: u64, buf: &mut [u8]) -> Result<(), IoError> {
let start = usize::try_from(offset).map_err(|_| IoError::Device)?;
let end = start.checked_add(buf.len()).ok_or(IoError::Device)?;
if end > self.0.len() {
return Err(IoError::Device);
}
buf.copy_from_slice(&self.0[start..end]);
Ok(())
}

fn write_at(&mut self, _offset: u64, _buf: &[u8]) -> Result<(), IoError> {
Err(IoError::Device)
}

fn flush(&mut self) -> Result<(), IoError> {
Ok(())
}
}
use toyos_blockdev::{block_on_ready, Bytes, BLOCK};
use toyos_fat32::Fat32;

/// GPT geometry, as `build/image.rs` writes it.
const LBA: usize = 512;
Expand Down Expand Up @@ -74,9 +48,15 @@ fn main() {
(last - first + 1) * LBA as u64
);

let bytes = disk[first as usize * LBA..(last as usize + 1) * LBA].to_vec();
if let Ok(mut fs) = Fat32::mount(Volume(bytes.clone())) {
match fs.walk("", 64) {
let mut bytes = disk[first as usize * LBA..(last as usize + 1) * LBA].to_vec();
let walked = bytes.len().is_multiple_of(BLOCK).then(|| {
block_on_ready(async {
let mut fs = Fat32::mount(Bytes::new(&mut bytes[..])).await.ok()?;
Some(fs.walk("", 64).await)
})
});
if let Some(Some(walked)) = walked {
match walked {
Ok(entries) => {
for (file, size) in &entries {
println!(" {file:40} {size}");
Expand Down
5 changes: 4 additions & 1 deletion build/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2769,7 +2769,9 @@ mod tests {
/// negative gates select at run time, `toyos-crypto`'s `portable`, `wpa2`
/// and `rustls-provider`, which `--ci host` tests as forms of its own, and
/// of the guest libraries, `toyos-font`'s `ttf`, its TrueType rendering,
/// and `toyos-pci-claim`'s `host-grant`, which netstack's host tests build.
/// `toyos-pci-claim`'s `host-grant`, which netstack's host tests build, and
/// `toyos-blockdev`'s `model`, the crash model a filesystem's tests check
/// its writes against, which the crate's own tests build regardless.
const NOT_A_CONTROL: &[&str] = &[
"core",
"default",
Expand All @@ -2778,6 +2780,7 @@ mod tests {
"flaws",
"host-grant",
"loom",
"model",
"portable",
"rustc-dep-of-std",
"rustls-provider",
Expand Down
16 changes: 9 additions & 7 deletions build/fatformat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,13 +180,15 @@ mod tests {
fn an_empty_volume_mounts_free_and_checks_clean() {
for bytes in SIZES {
let mut volume = format(bytes, *b"TOYOS-LOG ");
let mut fs = toyos_fat32::Fat32::mount(crate::image::VolumeIo(&mut volume)).expect("it mounts");
assert!(fs.walk("", 8).expect("it walks").is_empty(), "{bytes}: an empty volume lists files");
let geometry = *fs.geometry();
let cluster = u64::from(geometry.bytes_per_cluster());
assert_eq!(fs.free_bytes().expect("it counts"), (u64::from(geometry.cluster_count) - 1) * cluster, "{bytes}");
fs.sync().expect("it records the count");
drop(fs);
toyos_blockdev::block_on_ready(async {
let mut fs = toyos_fat32::Fat32::mount(toyos_blockdev::Bytes::new(&mut volume[..])).await.expect("it mounts");
assert!(fs.walk("", 8).await.expect("it walks").is_empty(), "{bytes}: an empty volume lists files");
let geometry = *fs.geometry();
let cluster = u64::from(geometry.bytes_per_cluster());
let free = fs.free_bytes().await.expect("it counts");
assert_eq!(free, (u64::from(geometry.cluster_count) - 1) * cluster, "{bytes}");
fs.sync().await.expect("it records the count");
});
let complaints = toyos_fat32_check::check(&volume);
assert!(complaints.is_empty(), "{bytes}:\n{}", toyos_fat32_check::describe(&complaints));
}
Expand Down
132 changes: 54 additions & 78 deletions build/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ use std::path::Path;
use bcachefs::{BlockBuf, BlockIO, BlockNum, Formatted, FsUuid, Superblock, VecBlockIO};

use crate::arch::Arch;
use toyos_fat32::{BlockAccess, Fat32, FatTime, IoError};
use toyos_blockdev::{block_on_ready, Bytes, BLOCK};
use toyos_fat32::{Fat32, FatTime};
use toyos_gpt::Guid;

/// The image that goes on the ROOT partition, named by a UUID **derived, never
Expand Down Expand Up @@ -372,11 +373,14 @@ pub fn read_file_on(file: &mut std::fs::File, guid: [u8; 16], name: &str) -> Res
file.seek(SeekFrom::Start(start))
.and_then(|_| file.read_exact(&mut volume))
.map_err(|e| format!("reading the volume at byte {start}: {e}"))?;
let mut fs = Fat32::mount(VolumeIo(&mut volume)).map_err(|e| format!("the volume does not mount: {e}"))?;
let mut found = fs.open(name).map_err(|e| format!("the volume has no {name}: {e}"))?;
let mut bytes = vec![0u8; usize::try_from(found.len()).unwrap_or(usize::MAX)];
fs.read(&mut found, 0, &mut bytes).map_err(|e| format!("reading {name}: {e}"))?;
Ok(bytes)
let device = device_of(&mut volume)?;
block_on_ready(async {
let mut fs = Fat32::mount(device).await.map_err(|e| format!("the volume does not mount: {e}"))?;
let mut found = fs.open(name).await.map_err(|e| format!("the volume has no {name}: {e}"))?;
let mut bytes = vec![0u8; usize::try_from(found.len()).unwrap_or(usize::MAX)];
fs.read(&mut found, 0, &mut bytes).await.map_err(|e| format!("reading {name}: {e}"))?;
Ok(bytes)
})
}

/// Every file in the root of the image's log volume (its one Microsoft basic
Expand All @@ -389,17 +393,29 @@ pub fn log_volume_files(file: &mut std::fs::File) -> Result<Vec<(String, Vec<u8>
file.seek(SeekFrom::Start(start))
.and_then(|_| file.read_exact(&mut volume))
.map_err(|e| format!("reading the log volume at byte {start}: {e}"))?;
let mut fs = Fat32::mount(VolumeIo(&mut volume)).map_err(|e| format!("the log volume does not mount: {e}"))?;
let entries = fs.read_dir("/", usize::MAX).map_err(|e| format!("listing the log volume: {e}"))?;
let mut files = Vec::new();
for entry in entries.into_iter().filter(|e| !e.is_dir) {
let path = format!("/{}", entry.name);
let mut found = fs.open(&path).map_err(|e| format!("opening {path}: {e}"))?;
let mut bytes = vec![0u8; usize::try_from(found.len()).unwrap_or(usize::MAX)];
fs.read(&mut found, 0, &mut bytes).map_err(|e| format!("reading {path}: {e}"))?;
files.push((entry.name, bytes));
let device = device_of(&mut volume)?;
block_on_ready(async {
let mut fs = Fat32::mount(device).await.map_err(|e| format!("the log volume does not mount: {e}"))?;
let entries = fs.read_dir("/", usize::MAX).await.map_err(|e| format!("listing the log volume: {e}"))?;
let mut files = Vec::new();
for entry in entries.into_iter().filter(|e| !e.is_dir) {
let path = format!("/{}", entry.name);
let mut found = fs.open(&path).await.map_err(|e| format!("opening {path}: {e}"))?;
let mut bytes = vec![0u8; usize::try_from(found.len()).unwrap_or(usize::MAX)];
fs.read(&mut found, 0, &mut bytes).await.map_err(|e| format!("reading {path}: {e}"))?;
files.push((entry.name, bytes));
}
Ok(files)
})
}

/// A volume read off an image, as the device `toyos-fat32` mounts: whole
/// blocks, which every partition this build writes is ([`PARTITION_ALIGN`]).
fn device_of(volume: &mut [u8]) -> Result<Bytes<&mut [u8]>, String> {
match volume.len().is_multiple_of(BLOCK) {
true => Ok(Bytes::new(volume)),
false => Err(format!("a {}-byte volume, which is no whole {BLOCK}-byte blocks", volume.len())),
}
Ok(files)
}

/// Why a scan's `out[0]` and `matched` count did not pick out exactly one
Expand Down Expand Up @@ -521,47 +537,6 @@ fn format_fat32(bytes: usize, label: &str) -> Vec<u8> {
crate::fatformat::format(bytes, fat_label(label))
}

/// The volume being built, as [`toyos_fat32`] sees it.
///
/// Byte-addressed with no block size of its own, because there is none: the
/// volume is a `Vec` in this process. The kernel's adapter is where the
/// read-modify-write against 4 KiB device blocks lives, and `toyos-fat32`'s own
/// host suite is where that shape is exercised.
pub(crate) struct VolumeIo<'a>(pub(crate) &'a mut [u8]);

impl VolumeIo<'_> {
fn range(&self, offset: u64, len: usize) -> Result<core::ops::Range<usize>, IoError> {
let start = usize::try_from(offset).map_err(|_| IoError::Device)?;
let end = start.checked_add(len).ok_or(IoError::Device)?;
if end > self.0.len() {
return Err(IoError::Device);
}
Ok(start..end)
}
}

impl BlockAccess for VolumeIo<'_> {
fn capacity(&self) -> u64 {
self.0.len() as u64
}

fn read_at(&mut self, offset: u64, buf: &mut [u8]) -> Result<(), IoError> {
let at = self.range(offset, buf.len())?;
buf.copy_from_slice(&self.0[at]);
Ok(())
}

fn write_at(&mut self, offset: u64, buf: &[u8]) -> Result<(), IoError> {
let at = self.range(offset, buf.len())?;
self.0[at].copy_from_slice(buf);
Ok(())
}

fn flush(&mut self) -> Result<(), IoError> {
Ok(())
}
}

/// When the build ran, which is what the files on the stick are dated.
///
/// A host with a clock behind 1980 or past 2107 gets FAT's nearest
Expand All @@ -587,24 +562,23 @@ fn build_time() -> FatTime {
/// counts the FAT when the volume arrived without a hint and `sync` writes it.
fn populate(volume: &mut [u8], label: &str, files: &[(&str, &[u8])]) {
let time = build_time();
let mut fs = Fat32::mount(VolumeIo(volume))
.unwrap_or_else(|e| panic!("the freshly formatted {label} volume does not mount: {e}"));
for (path, data) in files {
if let Some((dir, _)) = path.rsplit_once('/') {
fs.create_dir_all(dir, time)
.unwrap_or_else(|e| panic!("creating {dir}/ on {label}: {e}"));
block_on_ready(async {
let mut fs = Fat32::mount(Bytes::new(volume))
.await
.unwrap_or_else(|e| panic!("the freshly formatted {label} volume does not mount: {e}"));
for (path, data) in files {
if let Some((dir, _)) = path.rsplit_once('/') {
fs.create_dir_all(dir, time).await.unwrap_or_else(|e| panic!("creating {dir}/ on {label}: {e}"));
}
let mut file = fs.create(path, time).await.unwrap_or_else(|e| panic!("creating {path} on {label}: {e}"));
fs.write(&mut file, 0, data)
.await
.unwrap_or_else(|e| panic!("writing {} bytes to {path} on {label}: {e}", data.len()));
fs.flush_meta(&mut file, time).await.unwrap_or_else(|e| panic!("recording {path} on {label}: {e}"));
}
let mut file = fs
.create(path, time)
.unwrap_or_else(|e| panic!("creating {path} on {label}: {e}"));
fs.write(&mut file, 0, data)
.unwrap_or_else(|e| panic!("writing {} bytes to {path} on {label}: {e}", data.len()));
fs.flush_meta(&mut file, time)
.unwrap_or_else(|e| panic!("recording {path} on {label}: {e}"));
}
fs.free_bytes()
.unwrap_or_else(|e| panic!("counting the {label} volume's free clusters: {e}"));
fs.sync().unwrap_or_else(|e| panic!("syncing the {label} volume: {e}"));
fs.free_bytes().await.unwrap_or_else(|e| panic!("counting the {label} volume's free clusters: {e}"));
fs.sync().await.unwrap_or_else(|e| panic!("syncing the {label} volume: {e}"));
})
}

/// The partition firmware boots from: the bootloader, and the name of the
Expand Down Expand Up @@ -1112,10 +1086,12 @@ mod tests {
}

/// Every file on `volume`, sorted.
fn files_of(mut volume: Vec<u8>) -> Vec<String> {
let mut fs = Fat32::mount(VolumeIo(&mut volume)).expect("mount a volume we just built");
let mut found: Vec<String> = fs
.walk("", 64)
fn files_of(volume: Vec<u8>) -> Vec<String> {
let walked = block_on_ready(async {
let mut fs = Fat32::mount(Bytes::new(volume)).await.expect("mount a volume we just built");
fs.walk("", 64).await
});
let mut found: Vec<String> = walked
.expect("walk the volume")
.into_iter()
.map(|(path, _)| path.trim_start_matches('/').to_string())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ and `wait` (`system/diskserver/src/session.rs`) hold a `Region`, a
`Connection` and a `Poller`, so no host reaches them. A page whose completion
ring holds the dead session's answer ends the session only through `drain`'s
refusal of a completion for no ticket in flight (`Violation::Tag`, through
`client.complete`); `disk::Served` then answers its caller `Gone`, which a file
server's caller hears as `Io`. That nothing else ends it is
`client.complete`); `disk::Served` then reconnects and asks again, and answers
its caller `Gone` once a reconnect fails or the request has been refused across
more than `MAX_RECONNECTS` ends, which a file server's caller hears as `Io`. That nothing else ends it is
`issues/a-block-session-waits-forever-on-a-server-past-its-submission-ring.md`.

**Exit**: a host test reopens a client's session over a page a killed server
Expand Down
Loading