feat(userspace): programs in /bin w/o .elf; bin manifests (needs = display) so shell runs snake; Ctrl+C kills foreground via terminal interrupt notification; ctrl keys; sleep

This commit is contained in:
2026-10-27 21:16:00 +01:00
parent 1fc5a54f06
commit 83809bbe94
33 changed files with 608 additions and 121 deletions

View File

@@ -11,6 +11,7 @@ pub mod id;
pub mod input;
pub mod log;
pub mod macros;
pub mod manifest;
pub mod mapping;
pub mod permission;
#[cfg(kani)]

View File

@@ -0,0 +1,64 @@
//! A program's manifest, `/bin/<name>.manifest`: what it needs beyond the
//! terminal and the storage every program gets. Lines of `needs = a, b`;
//! `#` starts a comment. No manifest means nothing more.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Needs {
/// Draws on the screen itself and takes input focus.
pub display: bool,
}
/// A line or need the parser does not know: the program asks for
/// something nobody can say it may have, so it should not run.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Unknown<'a>(pub &'a [u8]);
pub fn parse(text: &[u8]) -> Result<Needs, Unknown<'_>> {
let mut needs = Needs::default();
for line in text.split(|b| *b == b'\n') {
let line = line
.split(|b| *b == b'#')
.next()
.unwrap_or(&[])
.trim_ascii();
if line.is_empty() {
continue;
}
let mut sides = line.splitn(2, |b| *b == b'=');
let (key, value) = (sides.next().unwrap_or(&[]).trim_ascii(), sides.next());
let (b"needs", Some(value)) = (key, value) else {
return Err(Unknown(line));
};
for need in value
.split(|b| *b == b',')
.map(<[u8]>::trim_ascii)
.filter(|n| !n.is_empty())
{
match need {
b"display" => needs.display = true,
other => return Err(Unknown(other)),
}
}
}
Ok(needs)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn needs_are_read_and_comments_skipped() {
let text = b"# snake\nneeds = display # the screen\n\n";
assert_eq!(parse(text), Ok(Needs { display: true }));
assert_eq!(parse(b""), Ok(Needs::default()));
assert_eq!(parse(b"needs = "), Ok(Needs::default()));
}
#[test]
fn anything_unknown_is_refused() {
assert_eq!(parse(b"needs = display, network"), Err(Unknown(b"network")));
assert_eq!(parse(b"wants = display"), Err(Unknown(b"wants = display")));
assert_eq!(parse(b"display"), Err(Unknown(b"display")));
}
}

View File

@@ -84,8 +84,8 @@ mod tests {
notifications: 3,
regions: 255,
};
let (name, len, back) = parse(request(b"hello.elf", l).unwrap()).unwrap();
assert_eq!(&name[..len], b"hello.elf");
let (name, len, back) = parse(request(b"bin/hello", l).unwrap()).unwrap();
assert_eq!(&name[..len], b"bin/hello");
assert_eq!(back, l);
}

View File

@@ -8,6 +8,7 @@ const CONSOLE_SHIFT: u32 = 0;
const LOG_SHIFT: u32 = 8;
const STORAGE_SHIFT: u32 = 16;
const ARGS_BIT: usize = 1 << 24;
const DISPLAY_SHIFT: u32 = 32;
/// Where the arguments page sits: the program's arguments, NUL separated,
/// the first its name. Below the stack, which ends at the image.
@@ -21,6 +22,8 @@ pub struct StartArg {
pub log: Option<CapSlot>,
pub storage: Option<CapSlot>,
pub args: bool,
/// The display server, for a program that draws on the screen.
pub display: Option<CapSlot>,
}
fn field(arg: usize, shift: u32) -> Option<CapSlot> {
@@ -38,6 +41,7 @@ impl StartArg {
| put(self.log, LOG_SHIFT)
| put(self.storage, STORAGE_SHIFT)
| if self.args { ARGS_BIT } else { 0 }
| put(self.display, DISPLAY_SHIFT)
}
pub fn decode(arg: usize) -> Self {
@@ -46,6 +50,7 @@ impl StartArg {
log: field(arg, LOG_SHIFT),
storage: field(arg, STORAGE_SHIFT),
args: arg & ARGS_BIT != 0,
display: field(arg, DISPLAY_SHIFT),
}
}
}
@@ -61,6 +66,7 @@ mod tests {
log: CapSlot::new(63),
storage: CapSlot::new(5),
args: true,
display: CapSlot::new(7),
};
assert_eq!(StartArg::decode(a.encode()), a);
assert_eq!(StartArg::decode(0), StartArg::default());

View File

@@ -5,3 +5,15 @@
/// the line's last chunk ends with `\n`. Blocks until a line is entered.
pub const READ: usize = 10;
pub const CLEAR: usize = 11;
/// Repaints the whole screen and takes input focus back, after a program
/// that drew on the screen itself.
pub const REDRAW: usize = 12;
/// Carries a notification the terminal signals on Ctrl+C while no line is
/// being read. Accepted once.
pub const ON_INTERRUPT: usize = 13;
/// What Ctrl+C types.
pub const CTRL_C: u8 = 3;
/// The terminal's notification bits: keys from the display, its flush timer.
pub const KEYS_BIT: usize = 1 << 1;
pub const TIMER_BIT: usize = 1 << 2;