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:
13
crates/apps/cat/Cargo.toml
Normal file
13
crates/apps/cat/Cargo.toml
Normal file
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "cat"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[[bin]]
|
||||
name = "cat"
|
||||
test = false
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
types = { workspace = true }
|
||||
libkos = { workspace = true }
|
||||
45
crates/apps/cat/src/main.rs
Normal file
45
crates/apps/cat/src/main.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
//! Prints files: `cat <file>...`.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
extern crate libkos;
|
||||
|
||||
use libkos::fs::{FsError, Storage};
|
||||
use libkos::println;
|
||||
|
||||
/// Where storage's buffer is mapped.
|
||||
const STORAGE_BUF_VA: usize = 0x0180_0000;
|
||||
|
||||
fn cat(fs: &mut Storage, path: &str) -> Result<(), FsError> {
|
||||
let h = fs.open(path.as_bytes())?;
|
||||
let mut buf = [0u8; 4096];
|
||||
loop {
|
||||
let n = fs.read(h, &mut buf)?;
|
||||
libkos::io::write_bytes(&buf[..n]);
|
||||
if n < buf.len() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
fs.close(h)
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
extern "C" fn main() {
|
||||
let Some(mut fs) = libkos::start::storage().and_then(|ep| Storage::attach(ep, STORAGE_BUF_VA).ok()) else {
|
||||
println!("cat: no storage");
|
||||
return;
|
||||
};
|
||||
let mut any = false;
|
||||
for path in libkos::start::args().skip(1) {
|
||||
any = true;
|
||||
match cat(&mut fs, path) {
|
||||
Ok(()) => {}
|
||||
Err(FsError::NotFound) => println!("cat: {}: no such file", path),
|
||||
Err(_) => println!("cat: {}: cannot read", path),
|
||||
}
|
||||
}
|
||||
if !any {
|
||||
println!("usage: cat <file>...");
|
||||
}
|
||||
}
|
||||
13
crates/apps/ls/Cargo.toml
Normal file
13
crates/apps/ls/Cargo.toml
Normal file
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "ls"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[[bin]]
|
||||
name = "ls"
|
||||
test = false
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
types = { workspace = true }
|
||||
libkos = { workspace = true }
|
||||
35
crates/apps/ls/src/main.rs
Normal file
35
crates/apps/ls/src/main.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
//! Lists a directory: `ls [dir]`, the root by default.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
extern crate libkos;
|
||||
|
||||
use libkos::fs::Storage;
|
||||
use libkos::println;
|
||||
|
||||
/// Where storage's buffer is mapped.
|
||||
const STORAGE_BUF_VA: usize = 0x0180_0000;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
extern "C" fn main() {
|
||||
let Some(mut fs) = libkos::start::storage().and_then(|ep| Storage::attach(ep, STORAGE_BUF_VA).ok()) else {
|
||||
println!("ls: no storage");
|
||||
return;
|
||||
};
|
||||
let dir = libkos::start::args().nth(1).unwrap_or("/");
|
||||
let listed = fs.list_dir(dir.as_bytes(), |e| {
|
||||
let name = core::str::from_utf8(e.name()).unwrap_or("?");
|
||||
if name == "." || name == ".." {
|
||||
return;
|
||||
}
|
||||
if e.is_dir {
|
||||
println!("{}/", name);
|
||||
} else {
|
||||
println!("{:<24} {:>8}", name, e.size);
|
||||
}
|
||||
});
|
||||
if listed.is_err() {
|
||||
println!("ls: {}: no such directory", dir);
|
||||
}
|
||||
}
|
||||
@@ -1,33 +1,95 @@
|
||||
//! The shell, for now built-ins only; running programs comes next.
|
||||
//! The shell: a few built-ins, and anything else is a program on disk.
|
||||
//! `name args...` runs `name.elf` through init, handing it the terminal as
|
||||
//! its output and the storage server, and nothing else; then waits for it.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
extern crate libkos;
|
||||
|
||||
use libkos::{print, println};
|
||||
use libkos::spawn::{SpawnError, spawn};
|
||||
use libkos::{print, println, syscall};
|
||||
use types::bounded::CapSlot;
|
||||
use types::budget::Limits;
|
||||
use types::permission::{Permission, PermissionSet};
|
||||
use types::start_arg::StartArg;
|
||||
|
||||
/// Init's manifest gives it the terminal first.
|
||||
/// Where init's manifest put them: `uses`, then the spawn endpoint.
|
||||
const TERMINAL: CapSlot = CapSlot::const_new(0);
|
||||
const STORAGE: CapSlot = CapSlot::const_new(1);
|
||||
const INIT: CapSlot = CapSlot::const_new(2);
|
||||
|
||||
fn run(line: &str) {
|
||||
let mut words = line.split_ascii_whitespace();
|
||||
match words.next() {
|
||||
/// What each program gets, out of the shell's allowance.
|
||||
const PROGRAM: Limits = Limits { pages: 128, endpoints: 1, notifications: 1, regions: 1 };
|
||||
const MAX_ARGS: usize = 16;
|
||||
|
||||
/// Runs `args[0].elf` with `args` and waits for it to finish.
|
||||
fn run_program(args: &[&str], done: CapSlot) {
|
||||
let mut file = [0u8; types::spawn::MAX_NAME];
|
||||
let name = args[0].as_bytes();
|
||||
let Some(file) = file.get_mut(..name.len() + 4).map(|f| {
|
||||
f[..name.len()].copy_from_slice(name);
|
||||
f[name.len()..].copy_from_slice(b".elf");
|
||||
&*f
|
||||
}) else {
|
||||
println!("shell: {}: name too long", args[0]);
|
||||
return;
|
||||
};
|
||||
let child = match spawn(INIT, file, PROGRAM) {
|
||||
Ok(c) => c,
|
||||
Err(SpawnError::NotFound) => return println!("shell: {}: not found", args[0]),
|
||||
Err(SpawnError::OverAllowance) => return println!("shell: no room to run {}", args[0]),
|
||||
Err(SpawnError::Failed) => return println!("shell: {}: cannot run", args[0]),
|
||||
};
|
||||
let call = PermissionSet::empty().with(Permission::Execute);
|
||||
let arg = StartArg {
|
||||
log: child.give(TERMINAL, call),
|
||||
storage: child.give(STORAGE, call),
|
||||
args: child.set_args(args),
|
||||
console: None,
|
||||
};
|
||||
let watched = syscall::watch_death(child.process, done).is_ok();
|
||||
if child.start_with(arg) && watched {
|
||||
let _ = syscall::wait(done);
|
||||
} else {
|
||||
let _ = syscall::kill(child.process);
|
||||
println!("shell: {}: cannot start", args[0]);
|
||||
}
|
||||
syscall::drop_cap(child.process);
|
||||
}
|
||||
|
||||
fn run(line: &str, done: CapSlot) {
|
||||
let mut args = [""; MAX_ARGS];
|
||||
let mut n = 0;
|
||||
for w in line.split_ascii_whitespace().take(MAX_ARGS) {
|
||||
args[n] = w;
|
||||
n += 1;
|
||||
}
|
||||
let args = &args[..n];
|
||||
match args.first().copied() {
|
||||
None => {}
|
||||
Some("help") => {
|
||||
println!("built-ins: help, echo <text>, clear");
|
||||
println!("anything else runs a program from the disk: try ls, cat readme.txt");
|
||||
}
|
||||
Some("echo") => {
|
||||
let mut first = true;
|
||||
for w in words {
|
||||
print!("{}{}", if first { "" } else { " " }, w);
|
||||
first = false;
|
||||
}
|
||||
println!();
|
||||
}
|
||||
Some("echo") => println!("{}", Joined(&args[1..])),
|
||||
Some("clear") => libkos::terminal::clear(TERMINAL),
|
||||
Some(other) => println!("shell: {}: not found", other),
|
||||
Some(_) => run_program(args, done),
|
||||
}
|
||||
}
|
||||
|
||||
/// Words with single spaces between them.
|
||||
struct Joined<'a>(&'a [&'a str]);
|
||||
|
||||
impl core::fmt::Display for Joined<'_> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
for (i, w) in self.0.iter().enumerate() {
|
||||
if i > 0 {
|
||||
f.write_str(" ")?;
|
||||
}
|
||||
f.write_str(w)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,13 +97,17 @@ fn run(line: &str) {
|
||||
extern "C" fn main() {
|
||||
// Everything this shell prints goes to the terminal.
|
||||
libkos::start::set_log(Some(TERMINAL));
|
||||
let Ok(done) = syscall::create_notification() else {
|
||||
println!("shell: no notification to wait on");
|
||||
return;
|
||||
};
|
||||
println!("k-os shell. Type 'help'.");
|
||||
let mut buf = [0u8; 256];
|
||||
loop {
|
||||
print!("$ ");
|
||||
let n = libkos::terminal::read_line(TERMINAL, &mut buf);
|
||||
match core::str::from_utf8(&buf[..n]) {
|
||||
Ok(line) => run(line.trim_end_matches('\n')),
|
||||
Ok(line) => run(line.trim_end_matches('\n'), done),
|
||||
Err(_) => println!("shell: not text"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,6 +153,6 @@ fn hand_over_console(log: CapSlot) {
|
||||
pub(crate) fn start(program: &LoadedProcess, console: Option<CapSlot>) {
|
||||
let log = libkos::start::log()
|
||||
.and_then(|l| syscall::delegate(l, program.process_cap, perms(&[Permission::Execute])));
|
||||
let arg = types::start_arg::encode(console, log);
|
||||
let arg = types::start_arg::StartArg { console, log, ..Default::default() }.encode();
|
||||
syscall::spawn_thread_in(program.process_cap, program.entry_point, USER_STACK_TOP, arg);
|
||||
}
|
||||
|
||||
@@ -79,6 +79,8 @@ pub const TESTS: &[Program] = &[Program {
|
||||
/// The desktop; the test image runs the SMP self-test instead.
|
||||
pub const APPS: &[Program] = &[Program {
|
||||
serves: false,
|
||||
uses: &["terminal"],
|
||||
uses: &["terminal", "storage"],
|
||||
// Room for one program at a time, the way the shell runs them.
|
||||
spawns: Some(Limits { pages: 256, endpoints: 2, notifications: 2, regions: 2 }),
|
||||
..server("shell", b"shell.elf", SMALL)
|
||||
}];
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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() })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -106,9 +106,15 @@ impl<B: BlockDevice> FileSystem for Ext4<B> {
|
||||
let mut block_buf = [0u8; 4096];
|
||||
while done < to_read {
|
||||
let offset = file.offset + done;
|
||||
let phys_block = self.map_logical_block(&inode, (offset / bs) as u32)?;
|
||||
if !read_block(&mut self.device, phys_block, self.sb.sectors_per_block(), &mut block_buf) {
|
||||
return None;
|
||||
// A block no extent maps is a hole (mkfs leaves zero blocks
|
||||
// out): it reads as zeros.
|
||||
match self.map_logical_block(&inode, (offset / bs) as u32) {
|
||||
Some(phys_block) => {
|
||||
if !read_block(&mut self.device, phys_block, self.sb.sectors_per_block(), &mut block_buf) {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
None => block_buf.fill(0),
|
||||
}
|
||||
let byte_in_block = offset % bs;
|
||||
let chunk = (to_read - done).min(bs - byte_in_block);
|
||||
|
||||
@@ -23,6 +23,8 @@ pub trait Buffers {
|
||||
struct Client {
|
||||
badge: usize,
|
||||
buf: &'static mut [u8],
|
||||
/// What maps `buf`, sent again when the same client attaches again.
|
||||
cap: Option<CapSlot>,
|
||||
}
|
||||
|
||||
pub struct StorageServer<F: FileSystem, B: Buffers> {
|
||||
@@ -77,9 +79,11 @@ impl<F: FileSystem, B: Buffers> StorageServer<F, B> {
|
||||
self.clients.iter_mut().flatten().find(|c| c.badge == badge)
|
||||
}
|
||||
|
||||
/// A client attaching again gets its buffer again: the programs a shell
|
||||
/// runs one after another share its badge.
|
||||
fn attach(&mut self, badge: usize) -> (StorageResponse, Option<CapSlot>) {
|
||||
if self.client(badge).is_some() {
|
||||
return (StorageResponse::Error, None);
|
||||
if let Some(c) = self.client(badge) {
|
||||
return (StorageResponse::Attached { size: c.buf.len() }, c.cap);
|
||||
}
|
||||
let Some(index) = self.clients.iter().position(Option::is_none) else {
|
||||
return (StorageResponse::Error, None);
|
||||
@@ -89,7 +93,7 @@ impl<F: FileSystem, B: Buffers> StorageServer<F, B> {
|
||||
};
|
||||
let size = buf.len();
|
||||
if let Some(slot) = self.clients.get_mut(index) {
|
||||
*slot = Some(Client { badge, buf });
|
||||
*slot = Some(Client { badge, buf, cap });
|
||||
}
|
||||
(StorageResponse::Attached { size }, cap)
|
||||
}
|
||||
|
||||
@@ -170,3 +170,16 @@ fn one_read_fills_the_buffer_across_blocks() {
|
||||
assert_eq!(fs.read(h, &mut buf), Some(total - 10_000));
|
||||
assert_eq!(fs.read(h, &mut buf), Some(0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn holes_read_as_zeros() {
|
||||
let dir = scratch("sparse");
|
||||
// Data, then a zero block mkfs stores as a hole, then data again.
|
||||
let mut data = std::vec![b'x'; 4096];
|
||||
data.extend(std::vec![0u8; 4096]);
|
||||
data.extend(b"end");
|
||||
std::fs::write(dir.join("root/sparse.bin"), &data).unwrap();
|
||||
let Some(img) = mkfs(&dir, MKFS_FEATURES) else { return };
|
||||
let mut fs = Ext4::mount(FileDisk(img)).expect("mount");
|
||||
assert_eq!(read_all(&mut fs, b"sparse.bin"), data);
|
||||
}
|
||||
|
||||
@@ -67,10 +67,14 @@ fn nothing_works_before_attaching() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_client_attaches_once() {
|
||||
fn attaching_again_hands_back_the_same_buffer() {
|
||||
let mut s = server();
|
||||
Client::attach(&mut s, 0);
|
||||
assert_eq!(call(&mut s, 0, StorageRequest::Attach).0, StorageResponse::Error);
|
||||
let (first, cap) = call(&mut s, 0, StorageRequest::Attach);
|
||||
assert_eq!(call(&mut s, 0, StorageRequest::Attach), (first, cap));
|
||||
// Not a new client: the other seven places are still free.
|
||||
for badge in 1..8 {
|
||||
assert!(matches!(call(&mut s, badge, StorageRequest::Attach).0, StorageResponse::Attached { .. }));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,11 +1,27 @@
|
||||
//! The word a process's first thread gets in `a0`: the slots of the
|
||||
//! capabilities any process may be handed at start (console, log endpoint).
|
||||
//! Each field is slot + 1 in one byte; 0 means none.
|
||||
//! capabilities any process may be handed at start, and whether it has an
|
||||
//! arguments page. Each slot field is slot + 1 in one byte; 0 means none.
|
||||
|
||||
use crate::bounded::CapSlot;
|
||||
|
||||
const CONSOLE_SHIFT: u32 = 0;
|
||||
const LOG_SHIFT: u32 = 8;
|
||||
const STORAGE_SHIFT: u32 = 16;
|
||||
const ARGS_BIT: usize = 1 << 24;
|
||||
|
||||
/// Where the arguments page sits: the program's arguments, NUL separated,
|
||||
/// the first its name. Below the stack, which ends at the image.
|
||||
pub const ARGS_VA: usize = 0x8000;
|
||||
pub const ARGS_SIZE: usize = 4096;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub struct StartArg {
|
||||
pub console: Option<CapSlot>,
|
||||
/// Where `println!` goes: the log server, or a terminal.
|
||||
pub log: Option<CapSlot>,
|
||||
pub storage: Option<CapSlot>,
|
||||
pub args: bool,
|
||||
}
|
||||
|
||||
fn field(arg: usize, shift: u32) -> Option<CapSlot> {
|
||||
let v = (arg >> shift) & 0xFF;
|
||||
@@ -16,16 +32,22 @@ fn put(slot: Option<CapSlot>, shift: u32) -> usize {
|
||||
slot.map_or(0, |s| (s.value() as usize + 1) << shift)
|
||||
}
|
||||
|
||||
pub fn encode(console: Option<CapSlot>, log: Option<CapSlot>) -> usize {
|
||||
put(console, CONSOLE_SHIFT) | put(log, LOG_SHIFT)
|
||||
}
|
||||
impl StartArg {
|
||||
pub fn encode(&self) -> usize {
|
||||
put(self.console, CONSOLE_SHIFT)
|
||||
| put(self.log, LOG_SHIFT)
|
||||
| put(self.storage, STORAGE_SHIFT)
|
||||
| if self.args { ARGS_BIT } else { 0 }
|
||||
}
|
||||
|
||||
pub fn console(arg: usize) -> Option<CapSlot> {
|
||||
field(arg, CONSOLE_SHIFT)
|
||||
}
|
||||
|
||||
pub fn log(arg: usize) -> Option<CapSlot> {
|
||||
field(arg, LOG_SHIFT)
|
||||
pub fn decode(arg: usize) -> Self {
|
||||
Self {
|
||||
console: field(arg, CONSOLE_SHIFT),
|
||||
log: field(arg, LOG_SHIFT),
|
||||
storage: field(arg, STORAGE_SHIFT),
|
||||
args: arg & ARGS_BIT != 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -33,11 +55,14 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn slots_round_trip_and_zero_is_none() {
|
||||
let (c, l) = (CapSlot::new(0), CapSlot::new(63));
|
||||
let arg = encode(c, l);
|
||||
assert_eq!((console(arg), log(arg)), (c, l));
|
||||
assert_eq!((console(0), log(0)), (None, None));
|
||||
assert_eq!(log(encode(c, None)), None);
|
||||
fn every_field_round_trips_and_zero_is_none() {
|
||||
let a = StartArg {
|
||||
console: CapSlot::new(0),
|
||||
log: CapSlot::new(63),
|
||||
storage: CapSlot::new(5),
|
||||
args: true,
|
||||
};
|
||||
assert_eq!(StartArg::decode(a.encode()), a);
|
||||
assert_eq!(StartArg::decode(0), StartArg::default());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user