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:
@@ -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)]
|
||||
|
||||
64
crates/types/src/manifest.rs
Normal file
64
crates/types/src/manifest.rs
Normal 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")));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user