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:
@@ -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"),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user