FDS/OS 1.0

This commit is contained in:
2026-09-21 22:29:23 +08:00
commit 99bc3d15c5
430 changed files with 34876 additions and 0 deletions
+23
View File
@@ -0,0 +1,23 @@
[package]
name = "fds-test-helpers"
version = "0.1.0"
edition = "2024"
license = "MIT"
publish = false
description = "Disposable VM workloads; never installed in FDS images"
[dependencies]
clap.workspace = true
libc = "0.2"
[[bin]]
name = "m6-client"
path = "cartridge-client.rs"
[[bin]]
name = "m7-writer"
path = "data-writer.rs"
[[bin]]
name = "m11-stress"
path = "usb-stress.rs"
+52
View File
@@ -0,0 +1,52 @@
// Test-only static ARM client. Never installed in a production image.
use clap::Parser;
use std::{
io::{Read, Write},
os::unix::net::UnixStream,
process::{Command, ExitCode},
time::Duration,
};
#[derive(Parser)]
#[command(about = "Exercise the cartridge socket with slow, oversize, or a JSON request")]
struct Cli {
mode: String,
}
fn main() -> ExitCode {
let mode = Cli::parse().mode;
let socket = "/run/fds/control.sock";
let result = (|| -> std::io::Result<()> {
let mut stream = UnixStream::connect(socket)?;
stream.set_read_timeout(Some(Duration::from_secs(8)))?;
if mode == "slow" {
stream.write_all(b"{")?;
assert!(Command::new("fds").arg("bays").status()?.success());
println!("FDS_SLOW_CLIENT_OK");
} else if mode == "oversize" {
let _ = stream.write_all(&vec![b'x'; 65537]);
let mut bytes = Vec::new();
let read = stream.read_to_end(&mut bytes);
assert!(bytes.is_empty());
assert!(
read.is_ok() || read.unwrap_err().kind() == std::io::ErrorKind::ConnectionReset
);
println!("FDS_OVERSIZE_OK");
} else {
stream.write_all(mode.as_bytes())?;
stream.write_all(b"\n")?;
let mut text = String::new();
stream.take(65537).read_to_string(&mut text)?;
if text.is_empty() {
return Err(std::io::Error::other("peer rejected"));
}
println!("{text}");
}
Ok(())
})();
match result {
Ok(()) => ExitCode::SUCCESS,
Err(e) => {
eprintln!("test-client: {e}");
ExitCode::FAILURE
}
}
}
+55
View File
@@ -0,0 +1,55 @@
// Test-only workload: sustained dirty writes plus a child that ignores TERM.
use clap::{Parser, ValueEnum};
use std::{
fs::{self, File},
io::{Seek, SeekFrom, Write},
process::Command,
};
unsafe extern "C" {
fn signal(number: i32, handler: usize) -> usize;
}
#[derive(Parser)]
#[command(about = "Write disposable DATA stress output")]
struct Cli {
role: Option<Role>,
}
#[derive(Clone, ValueEnum)]
enum Role {
Child,
}
fn main() {
let cli = Cli::parse();
unsafe {
signal(15, 1);
}
if matches!(cli.role, Some(Role::Child)) {
loop {
std::thread::park();
}
}
fs::write(
"/data/process-status",
fs::read("/proc/self/status").unwrap(),
)
.unwrap();
let child = Command::new(std::env::current_exe().unwrap())
.arg("child")
.spawn()
.unwrap();
fs::write("/data/child-pid", child.id().to_string()).unwrap();
let mut file = File::create("/data/stress.bin").unwrap();
let buffer: Vec<u8> = (0..1024 * 1024).map(|i| (i % 256) as u8).collect();
let mut blocks = 0_u64;
loop {
if blocks % 64 == 0 {
file.seek(SeekFrom::Start(0)).unwrap();
}
if let Err(error) = file.write_all(&buffer) {
eprintln!("write fault: {error}");
return;
}
blocks += 1;
fs::write("/data/progress.next", blocks.to_string()).unwrap();
fs::rename("/data/progress.next", "/data/progress").unwrap();
}
}
+69
View File
@@ -0,0 +1,69 @@
// Test-only, unprivileged concurrent direct reads and durable DATA writes.
use clap::Parser;
use std::{
fs::{File, OpenOptions},
io::{Read, Seek, SeekFrom, Write},
os::unix::fs::OpenOptionsExt,
path::PathBuf,
sync::{Arc, Barrier},
time::Instant,
};
#[repr(align(16384))]
struct Block([u8; 65536]);
#[derive(Parser)]
#[command(about = "Read eleven cartridge fixtures while writing disposable DATA")]
struct Cli {
#[arg(required = true, num_args = 11)]
paths: Vec<PathBuf>,
}
fn main() {
let paths = Cli::parse().paths;
let barrier = Arc::new(Barrier::new(12));
let origin = Instant::now();
let mut jobs = Vec::new();
for (index, path) in paths.into_iter().enumerate() {
let barrier = barrier.clone();
jobs.push(std::thread::spawn(move || {
let mut file = OpenOptions::new()
.read(true)
.custom_flags(libc::O_DIRECT)
.open(path)
.unwrap(); // O_DIRECT
let mut block = Box::new(Block([0; 65536]));
barrier.wait();
let start = origin.elapsed().as_nanos();
for _ in 0..4 {
file.seek(SeekFrom::Start(0)).unwrap();
for _ in 0..128 {
file.read_exact(&mut block.0).unwrap();
assert!(block.0.iter().all(|b| *b == (index + 2) as u8));
}
}
(
index + 2,
start,
origin.elapsed().as_nanos(),
32 * 1024 * 1024,
)
}));
}
let writer = std::thread::spawn(move || {
let mut file = File::create("/data/stress.bin").unwrap();
let mut block = [0_u8; 65536];
for (i, byte) in block.iter_mut().enumerate() {
*byte = (i % 256) as u8;
}
barrier.wait();
let start = origin.elapsed().as_nanos();
for _ in 0..1024 {
file.write_all(&block).unwrap();
}
file.sync_all().unwrap();
(1, start, origin.elapsed().as_nanos(), 64 * 1024 * 1024)
});
let mut results: Vec<_> = jobs.into_iter().map(|j| j.join().unwrap()).collect();
results.push(writer.join().unwrap());
for (bay, start, end, bytes) in results {
println!("{{\"bay\":{bay},\"start_ns\":{start},\"end_ns\":{end},\"bytes\":{bytes}}}");
}
}