feat(userspace): shell runs programs (ls, cat) via spawn w/ terminal+storage+args page; ext4 holes read as zeros; storage re-attach same buffer

This commit is contained in:
2026-10-27 17:22:08 +01:00
parent 0fd568cdad
commit c603a578c0
22 changed files with 396 additions and 66 deletions

View File

@@ -4,21 +4,26 @@ struct Writer;
impl fmt::Write for Writer {
fn write_str(&mut self, s: &str) -> fmt::Result {
// The log server first; a process without either prints nothing.
// A call, not a send: the text is out before the caller goes on.
if let Some(log) = crate::start::log() {
for chunk in s.as_bytes().chunks(types::log::TEXT_BYTES) {
let _ = crate::syscall::call(log, types::log::text_words(chunk), None);
}
} else if let Some(console) = crate::start::console() {
for &b in s.as_bytes() {
crate::syscall::debug_putchar(console, b);
}
}
write_bytes(s.as_bytes());
Ok(())
}
}
/// Bytes as they are to where `print!` goes: the log endpoint (the log
/// server, or a terminal), else the console; nowhere without either.
pub fn write_bytes(bytes: &[u8]) {
// A call, not a send: the text is out before the caller goes on.
if let Some(log) = crate::start::log() {
for chunk in bytes.chunks(types::log::TEXT_BYTES) {
let _ = crate::syscall::call(log, types::log::text_words(chunk), None);
}
} else if let Some(console) = crate::start::console() {
for &b in bytes {
crate::syscall::debug_putchar(console, b);
}
}
}
pub fn _print(args: fmt::Arguments) {
use fmt::Write;
Writer.write_fmt(args).ok();

View File

@@ -6,7 +6,9 @@ use crate::syscall;
use types::address::{EntryPoint, StackPointer};
use types::bounded::CapSlot;
use types::budget::Limits;
use types::permission::{Permission, PermissionSet};
use types::spawn::{STACK_TOP, reply, request};
use types::start_arg::{ARGS_SIZE, ARGS_VA, StartArg};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SpawnError {
@@ -44,10 +46,46 @@ pub fn spawn(init: CapSlot, name: &[u8], limits: Limits) -> Result<Spawned, Spaw
}
impl Spawned {
/// Runs the program, telling it where its log endpoint is.
pub fn start(&self) -> bool {
let arg = types::start_arg::encode(None, self.log);
/// The log endpoint init gave the program.
pub fn log(&self) -> Option<CapSlot> {
self.log
}
/// A copy of `cap` for the program, with at most `perms` (and never
/// Delegate: it is the program's, not to pass on); its slot there.
pub fn give(&self, cap: CapSlot, perms: PermissionSet) -> Option<CapSlot> {
syscall::delegate(cap, self.process, perms.without(Permission::Delegate))
}
/// Puts `args` (its name first) where the program finds them: a page
/// this process fills and then moves into the program, which owns it.
pub fn set_args(&self, args: &[&str]) -> bool {
let need: usize = args.iter().map(|a| a.len() + 1).sum::<usize>() + 1;
if need > ARGS_SIZE || args.iter().any(|a| a.contains('\0')) {
return false;
}
let Some(page) = types::bounded::PageCount::new(1).and_then(syscall::request_pages) else {
return false;
};
// SAFETY: the page was just mapped read-write here, ARGS_SIZE bytes.
let buf = unsafe { core::slice::from_raw_parts_mut(page.raw() as *mut u8, ARGS_SIZE) };
buf.fill(0);
let mut at = 0;
for a in args {
buf[at..at + a.len()].copy_from_slice(a.as_bytes());
at += a.len() + 1;
}
syscall::map_page(self.process, page, types::address::VirtualAddress::from_raw(ARGS_VA), false)
}
/// Runs the program with `arg` in its `a0` (see `types::start_arg`).
pub fn start_with(&self, arg: StartArg) -> bool {
let stack = StackPointer::from_raw(STACK_TOP);
syscall::spawn_thread_in(self.process, self.entry, stack, arg) != usize::MAX
syscall::spawn_thread_in(self.process, self.entry, stack, arg.encode()) != usize::MAX
}
/// Runs the program, printing to the log init gave it.
pub fn start(&self) -> bool {
self.start_with(StartArg { log: self.log, ..StartArg::default() })
}
}

View File

@@ -2,7 +2,7 @@
use core::sync::atomic::{AtomicUsize, Ordering};
use types::bounded::CapSlot;
use types::start_arg;
use types::start_arg::{ARGS_SIZE, ARGS_VA, StartArg};
/// Set by `_start` from `a0` before `main` runs.
#[unsafe(no_mangle)]
@@ -20,7 +20,7 @@ fn arg() -> usize {
/// The console capability this process prints through, if any.
pub fn console() -> Option<CapSlot> {
read(&CONSOLE, start_arg::console)
read(&CONSOLE, |a| StartArg::decode(a).console)
}
/// For a process that gets its console some other way (init, from BootInfo).
@@ -30,7 +30,25 @@ pub fn set_console(slot: Option<CapSlot>) {
/// The log server endpoint this process prints to, if any.
pub fn log() -> Option<CapSlot> {
read(&LOG, start_arg::log)
read(&LOG, |a| StartArg::decode(a).log)
}
/// The storage server endpoint this process was given, if any.
pub fn storage() -> Option<CapSlot> {
StartArg::decode(arg()).storage
}
/// This program's arguments, its name first; none without an args page.
pub fn args() -> impl Iterator<Item = &'static str> {
let page: &'static [u8] = if StartArg::decode(arg()).args {
// SAFETY: the args flag says the parent mapped this page here
// before starting the program; nothing writes it afterwards.
unsafe { core::slice::from_raw_parts(ARGS_VA as *const u8, ARGS_SIZE) }
} else {
&[]
};
let end = page.windows(2).position(|w| w == [0, 0]).map_or(0, |i| i + 1);
page[..end].split(|b| *b == 0).filter(|a| !a.is_empty()).filter_map(|a| core::str::from_utf8(a).ok())
}
/// For a process given its log endpoint after it started.