feat(storage): scoped grants (subtree, ro/rw), narrow-only, release revokes all holders; IPC KEEP_DELEGATE opt-in; shell cd/pwd, programs get ro cwd
This commit is contained in:
@@ -1,12 +1,14 @@
|
||||
//! 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.
|
||||
//! its output and read-only storage rooted at the current directory, and
|
||||
//! nothing else; then waits for it and takes the storage back.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
extern crate libkos;
|
||||
|
||||
use libkos::fs::Storage;
|
||||
use libkos::spawn::{SpawnError, spawn};
|
||||
use libkos::{print, println, syscall};
|
||||
use types::bounded::CapSlot;
|
||||
@@ -22,9 +24,76 @@ const INIT: CapSlot = CapSlot::const_new(2);
|
||||
/// 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;
|
||||
/// Where storage's buffer is mapped.
|
||||
const STORAGE_BUF_VA: usize = 0x0180_0000;
|
||||
|
||||
/// Runs `args[0].elf` with `args` and waits for it to finish.
|
||||
fn run_program(args: &[&str], done: CapSlot) {
|
||||
/// The current directory, as a path on disk: no leading or trailing `/`.
|
||||
struct Cwd {
|
||||
path: [u8; types::storage::MAX_PATH],
|
||||
len: usize,
|
||||
}
|
||||
|
||||
impl Cwd {
|
||||
fn as_bytes(&self) -> &[u8] {
|
||||
&self.path[..self.len]
|
||||
}
|
||||
|
||||
/// Where `cd to` from here leads: `/` starts at the root, `..` goes up
|
||||
/// (the shell holds the whole disk, so that is its to resolve).
|
||||
fn join(&self, to: &str) -> Option<Cwd> {
|
||||
let mut next = match to.starts_with('/') {
|
||||
true => Cwd {
|
||||
path: [0; types::storage::MAX_PATH],
|
||||
len: 0,
|
||||
},
|
||||
false => Cwd {
|
||||
path: self.path,
|
||||
len: self.len,
|
||||
},
|
||||
};
|
||||
for part in to.split('/') {
|
||||
match part {
|
||||
"" | "." => {}
|
||||
".." => {
|
||||
next.len = next
|
||||
.as_bytes()
|
||||
.iter()
|
||||
.rposition(|b| *b == b'/')
|
||||
.unwrap_or(0)
|
||||
}
|
||||
name => {
|
||||
let sep = usize::from(next.len > 0);
|
||||
let end = next.len + sep + name.len();
|
||||
let room = next.path.get_mut(next.len..end)?;
|
||||
room[..sep].copy_from_slice(&b"/"[..sep]);
|
||||
room[sep..].copy_from_slice(name.as_bytes());
|
||||
next.len = end;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(next)
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Display for Cwd {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"/{}",
|
||||
core::str::from_utf8(self.as_bytes()).unwrap_or("?")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
struct Shell {
|
||||
fs: Storage,
|
||||
cwd: Cwd,
|
||||
done: CapSlot,
|
||||
}
|
||||
|
||||
/// Runs `args[0].elf` with `args` in the current directory, and waits
|
||||
/// for it to finish.
|
||||
fn run_program(sh: &mut Shell, args: &[&str]) {
|
||||
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| {
|
||||
@@ -42,23 +111,34 @@ fn run_program(args: &[&str], done: CapSlot) {
|
||||
Err(SpawnError::Failed) => return println!("shell: {}: cannot run", args[0]),
|
||||
};
|
||||
let call = PermissionSet::empty().with(Permission::Execute);
|
||||
let grant = sh.fs.grant(sh.cwd.as_bytes(), false).ok();
|
||||
let arg = StartArg {
|
||||
log: child.give(TERMINAL, call),
|
||||
storage: child.give(STORAGE, call),
|
||||
storage: grant.and_then(|g| child.give(g, call)),
|
||||
args: child.set_args(args),
|
||||
console: None,
|
||||
};
|
||||
let watched = syscall::watch_death(child.process, done).is_ok();
|
||||
let watched = syscall::watch_death(child.process, sh.done).is_ok();
|
||||
if child.start_with(arg) && watched {
|
||||
let _ = syscall::wait(done);
|
||||
let _ = syscall::wait(sh.done);
|
||||
} else {
|
||||
let _ = syscall::kill(child.process);
|
||||
println!("shell: {}: cannot start", args[0]);
|
||||
}
|
||||
syscall::drop_cap(child.process);
|
||||
if let Some(g) = grant {
|
||||
let _ = libkos::fs::release(g);
|
||||
}
|
||||
}
|
||||
|
||||
fn run(line: &str, done: CapSlot) {
|
||||
fn cd(sh: &mut Shell, to: &str) {
|
||||
match sh.cwd.join(to) {
|
||||
Some(next) if sh.fs.list_dir(next.as_bytes(), |_| {}).is_ok() => sh.cwd = next,
|
||||
_ => println!("cd: {}: no such directory", to),
|
||||
}
|
||||
}
|
||||
|
||||
fn run(sh: &mut Shell, line: &str) {
|
||||
let mut args = [""; MAX_ARGS];
|
||||
let mut n = 0;
|
||||
for w in line.split_ascii_whitespace().take(MAX_ARGS) {
|
||||
@@ -69,12 +149,15 @@ fn run(line: &str, done: CapSlot) {
|
||||
match args.first().copied() {
|
||||
None => {}
|
||||
Some("help") => {
|
||||
println!("built-ins: help, echo <text>, clear");
|
||||
println!("built-ins: help, echo <text>, clear, cd <dir>, pwd");
|
||||
println!("anything else runs a program from the disk: try ls, cat readme.txt");
|
||||
println!("programs see the current directory, read-only, and nothing above it");
|
||||
}
|
||||
Some("cd") => cd(sh, args.get(1).copied().unwrap_or("/")),
|
||||
Some("pwd") => println!("{}", sh.cwd),
|
||||
Some("echo") => println!("{}", Joined(&args[1..])),
|
||||
Some("clear") => libkos::terminal::clear(TERMINAL),
|
||||
Some(_) => run_program(args, done),
|
||||
Some(_) => run_program(sh, args),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,13 +184,25 @@ extern "C" fn main() {
|
||||
println!("shell: no notification to wait on");
|
||||
return;
|
||||
};
|
||||
let Ok(fs) = Storage::attach(STORAGE, STORAGE_BUF_VA) else {
|
||||
println!("shell: no storage");
|
||||
return;
|
||||
};
|
||||
let mut sh = Shell {
|
||||
fs,
|
||||
cwd: Cwd {
|
||||
path: [0; types::storage::MAX_PATH],
|
||||
len: 0,
|
||||
},
|
||||
done,
|
||||
};
|
||||
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'), done),
|
||||
Ok(line) => run(&mut sh, line.trim_end_matches('\n')),
|
||||
Err(_) => println!("shell: not text"),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user