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:
2026-10-27 20:20:34 +01:00
parent c603a578c0
commit 1fc5a54f06
38 changed files with 1237 additions and 170 deletions

View File

@@ -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"),
}
}