diff --git a/Cargo.lock b/Cargo.lock index 9e669da..20906c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12,7 +12,6 @@ version = "0.1.0" dependencies = [ "domain", "elf", - "hw-virtio", "kernel", "platform", "types", @@ -42,6 +41,14 @@ version = "0.1.0" name = "camera-server" version = "0.1.0" +[[package]] +name = "cat" +version = "0.1.0" +dependencies = [ + "libkos", + "types", +] + [[package]] name = "clipboard-server" version = "0.1.0" @@ -224,6 +231,14 @@ dependencies = [ "types", ] +[[package]] +name = "ls" +version = "0.1.0" +dependencies = [ + "libkos", + "types", +] + [[package]] name = "media-index-server" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 46e1f18..519510b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,8 @@ members = [ "crates/adapter-riscv", "crates/apps/hello", "crates/apps/shell", + "crates/apps/cat", + "crates/apps/ls", "crates/apps/echo", "crates/apps/smptest", "crates/apps/spawntest", diff --git a/Makefile b/Makefile index 845b92b..f8c4f5e 100644 --- a/Makefile +++ b/Makefile @@ -55,7 +55,7 @@ build: -p init -p echo -p snake \ -p display-qemu -p input-qemu -p storage-qemu \ -p log-server -p permission-manager -p session-server -p smptest -p spawntest -p hello \ - -p terminal-server -p shell + -p terminal-server -p shell -p cat -p ls cargo build --target $(RISCV_TARGET) --release -p adapter-riscv image: build diff --git a/crates/apps/cat/Cargo.toml b/crates/apps/cat/Cargo.toml new file mode 100644 index 0000000..deec1cc --- /dev/null +++ b/crates/apps/cat/Cargo.toml @@ -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 } diff --git a/crates/apps/cat/src/main.rs b/crates/apps/cat/src/main.rs new file mode 100644 index 0000000..99e9d29 --- /dev/null +++ b/crates/apps/cat/src/main.rs @@ -0,0 +1,45 @@ +//! Prints files: `cat ...`. + +#![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 ..."); + } +} diff --git a/crates/apps/ls/Cargo.toml b/crates/apps/ls/Cargo.toml new file mode 100644 index 0000000..76a1151 --- /dev/null +++ b/crates/apps/ls/Cargo.toml @@ -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 } diff --git a/crates/apps/ls/src/main.rs b/crates/apps/ls/src/main.rs new file mode 100644 index 0000000..fd6ff2a --- /dev/null +++ b/crates/apps/ls/src/main.rs @@ -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); + } +} diff --git a/crates/apps/shell/src/main.rs b/crates/apps/shell/src/main.rs index 2e0396a..5367873 100644 --- a/crates/apps/shell/src/main.rs +++ b/crates/apps/shell/src/main.rs @@ -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 , 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"), } } diff --git a/crates/init/src/launch.rs b/crates/init/src/launch.rs index 0d19515..18fc6e9 100644 --- a/crates/init/src/launch.rs +++ b/crates/init/src/launch.rs @@ -153,6 +153,6 @@ fn hand_over_console(log: CapSlot) { pub(crate) fn start(program: &LoadedProcess, console: Option) { 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); } diff --git a/crates/init/src/manifest.rs b/crates/init/src/manifest.rs index 25ea3cc..06b0040 100644 --- a/crates/init/src/manifest.rs +++ b/crates/init/src/manifest.rs @@ -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) }]; diff --git a/crates/libkos/src/io.rs b/crates/libkos/src/io.rs index a3d3c61..ca9c79e 100644 --- a/crates/libkos/src/io.rs +++ b/crates/libkos/src/io.rs @@ -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(); diff --git a/crates/libkos/src/spawn.rs b/crates/libkos/src/spawn.rs index 75c9a68..65d17bb 100644 --- a/crates/libkos/src/spawn.rs +++ b/crates/libkos/src/spawn.rs @@ -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 bool { - let arg = types::start_arg::encode(None, self.log); + /// The log endpoint init gave the program. + pub fn log(&self) -> Option { + 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 { + 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::() + 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() }) } } diff --git a/crates/libkos/src/start.rs b/crates/libkos/src/start.rs index 6575b97..ca53651 100644 --- a/crates/libkos/src/start.rs +++ b/crates/libkos/src/start.rs @@ -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 { - 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) { /// The log server endpoint this process prints to, if any. pub fn log() -> Option { - 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 { + StartArg::decode(arg()).storage +} + +/// This program's arguments, its name first; none without an args page. +pub fn args() -> impl Iterator { + 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. diff --git a/crates/servers/storage-server/src/fs/ext4/mod.rs b/crates/servers/storage-server/src/fs/ext4/mod.rs index 07917b6..743255c 100644 --- a/crates/servers/storage-server/src/fs/ext4/mod.rs +++ b/crates/servers/storage-server/src/fs/ext4/mod.rs @@ -106,9 +106,15 @@ impl FileSystem for Ext4 { 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); diff --git a/crates/servers/storage-server/src/server.rs b/crates/servers/storage-server/src/server.rs index f32e3e0..839a0ab 100644 --- a/crates/servers/storage-server/src/server.rs +++ b/crates/servers/storage-server/src/server.rs @@ -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, } pub struct StorageServer { @@ -77,9 +79,11 @@ impl StorageServer { 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) { - 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 StorageServer { }; 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) } diff --git a/crates/servers/storage-server/src/tests/ext4_real.rs b/crates/servers/storage-server/src/tests/ext4_real.rs index 2fe385f..dd29cea 100644 --- a/crates/servers/storage-server/src/tests/ext4_real.rs +++ b/crates/servers/storage-server/src/tests/ext4_real.rs @@ -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); +} diff --git a/crates/servers/storage-server/src/tests/server.rs b/crates/servers/storage-server/src/tests/server.rs index b044e2c..1b6f1d5 100644 --- a/crates/servers/storage-server/src/tests/server.rs +++ b/crates/servers/storage-server/src/tests/server.rs @@ -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] diff --git a/crates/types/src/start_arg.rs b/crates/types/src/start_arg.rs index 7be31de..eb07889 100644 --- a/crates/types/src/start_arg.rs +++ b/crates/types/src/start_arg.rs @@ -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, + /// Where `println!` goes: the log server, or a terminal. + pub log: Option, + pub storage: Option, + pub args: bool, +} fn field(arg: usize, shift: u32) -> Option { let v = (arg >> shift) & 0xFF; @@ -16,16 +32,22 @@ fn put(slot: Option, shift: u32) -> usize { slot.map_or(0, |s| (s.value() as usize + 1) << shift) } -pub fn encode(console: Option, log: Option) -> 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 { - field(arg, CONSOLE_SHIFT) -} - -pub fn log(arg: usize) -> Option { - 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()); } } diff --git a/disk/docs/shell.txt b/disk/docs/shell.txt new file mode 100644 index 0000000..d46c82e --- /dev/null +++ b/disk/docs/shell.txt @@ -0,0 +1,7 @@ +The shell runs a program by asking init to load it (init alone may +create processes), then gives the program the terminal as its output +and the storage server, writes its arguments onto a page it moves +into the program, starts it and waits for it to die. + +Built-ins: help, echo, clear. Anything else is a program on disk: +`name args...` runs name.elf. diff --git a/disk/readme.txt b/disk/readme.txt new file mode 100644 index 0000000..49b736d --- /dev/null +++ b/disk/readme.txt @@ -0,0 +1,7 @@ +Welcome to k-os. + +Every program here runs with only the capabilities it was given: +this file reached your screen because the shell handed `cat` the +terminal and the storage server, and nothing else. + +Try: ls ls docs cat docs/shell.txt diff --git a/docs/ipc.md b/docs/ipc.md index dbbfee6..461e55c 100644 --- a/docs/ipc.md +++ b/docs/ipc.md @@ -74,7 +74,10 @@ calling it (`types::log`) if init gave it a log endpoint. A thread made by `SpawnThread`/`SpawnThreadIn` starts with the caller's `arg` in `a0`. A process's first thread reads it as `types::start_arg`: -the slots of the console and log capabilities it was handed. +the slots of the console, log and storage capabilities it was handed, and +whether its parent put an arguments page at `ARGS_VA` (NUL-separated, the +program's name first; the parent fills a page and `MapPage`s it over, so +the program owns it and it goes when the program does). Boot loads only init and storage, both packed into the kernel image (they are what reads the disk, so they cannot come from it). The disk is one ext4 @@ -102,7 +105,12 @@ font. Its output side is the log protocol (`types::log`), so a program whose log endpoint is the terminal prints there with `println!`: standard output is whatever text endpoint a program was given. `READ` (`types::terminal`) blocks until a line is typed and returns it in chunks, the last ending in -`\n`. The desktop is the shell, given the terminal. +`\n`. The desktop is the shell, given the terminal, storage and a spawn +allowance. A command that is not a built-in runs `name.elf`: the shell +spawns it, gives it call-only copies of the terminal (as its log, so its +output) and of storage, writes its arguments page, starts it and waits for +its death. The programs share the shell's storage badge; storage hands a +client that attaches again the same buffer, so they run one at a time. Only init holds `ProcessCreate`. Everyone else runs programs through init's spawn service (`types::spawn`, client `libkos::spawn`), which init serves diff --git a/scripts/make-disk-image.sh b/scripts/make-disk-image.sh index 18fb371..83312ce 100755 --- a/scripts/make-disk-image.sh +++ b/scripts/make-disk-image.sh @@ -30,6 +30,10 @@ copy permission-manager "permis.elf" copy session-server "session.elf" copy terminal-server "terminal.elf" copy shell "shell.elf" +copy cat "cat.elf" +copy ls "ls.elf" +# Files that ship on the disk. +cp -r disk/. "${ROOT}/" # Only the test image: its presence makes init run the self-test. if [ -n "${SMPTEST:-}" ]; then copy smptest "smptest.elf"