// --------------------------------------------------------------------------- // exec — run an external binary (ADR 0006 Phase 2). // // Resolution: name → \bin\\ on each mounted volume → None. // The shell calls exec::spawn with a fully constructed Exports table // (streams point at its pipeline/tty) and clean argv. The binary runs as // its own task; on exit it posts TaskMsg::User(exit_code) to the spawner. // // The Exports table and argv block are heap boxes moved into static slots // indexed by task id — the trampoline adopts them before jumping to the // image entry. One slot per MAX_TASKS; spawn fails if the slot is busy. // --------------------------------------------------------------------------- use crate::load; use crate::sched::{self, TaskMsg}; use alloc::string::String; use alloc::vec::Vec; use rsapi::{Arg, EntryPoint, Exports}; /// Per-task invocation context adopted by the trampoline. struct ExecCtx { exports: &'static Exports, /// Heap block holding the argv strings (kept alive until task exit — /// the Arg pointers reference into it). #[allow(dead_code)] argv_buf: Vec, args: Vec, /// Image pages to free when the task dies. image: load::Image, } static mut CTX: [Option; sched::MAX_TASKS] = [const { None }; sched::MAX_TASKS]; /// Error of exec::resolve / exec::spawn. pub enum ExecError { NotFound, LoadFailed(&'static str), Busy, } /// Scan every mounted volume for `\bin\\` (LFN exact match). /// Returns the VFS handle of the first hit. pub fn resolve(kind: &str, name: &str) -> Option { for vol in 0..crate::fat::volume_count() { if !crate::fat::is_mounted(vol) { continue; } let uid = String::from(crate::fat::uid(vol)); let path = alloc::format!("/disk/{}/bin/{}/{}.efi", uid, kind, name); if let Some(h) = crate::fs::resolve(&path) { return Some(h); } } None } /// Read the whole file into a heap buffer (public: the shell uses this /// to fetch binary bytes before calling load::load_image). #[allow(dead_code)] pub fn read_file(h: usize) -> Result, ()> { let size = crate::fs::size(h) as usize; let mut buf = Vec::with_capacity(size); let mut chunk = [0u8; 512]; let mut pos = 0u64; loop { let n = crate::fs::read(h, &mut chunk, pos)?; if n == 0 { break; } buf.extend_from_slice(&chunk[..n]); pos += n as u64; } Ok(buf) } /// Spawn `image` as a new task running entry(exports, argc, argv). /// /// The caller keeps `exports` alive (it is constructed per invocation in /// shell-owned memory that outlives the command — the pipeline/tty live /// in the blocked spawner). pub fn spawn( image: load::Image, exports: &'static Exports, argv: &[String], name: &str, ) -> Result { // Copy the argv strings into one contiguous block owned by the task. let mut argv_buf: Vec = Vec::new(); let mut args: Vec = Vec::with_capacity(argv.len()); for a in argv { let off = argv_buf.len(); argv_buf.extend_from_slice(a.as_bytes()); args.push(Arg { ptr: unsafe { argv_buf.as_ptr().add(off) }, len: a.len(), }); } // Reserve a task slot first so the context is in place before the // task can possibly run (spawn may schedule immediately). let id = sched::reserve_slot().ok_or(ExecError::Busy)?; unsafe { CTX[id] = Some(ExecCtx { exports, argv_buf, args, image, }); } if !sched::launch_at(id, trampoline, name) { unsafe { CTX[id] = None; } return Err(ExecError::Busy); } Ok(id) } /// Trampoline: adopt the exec context, call the entry, report completion. fn trampoline() { let id = sched::current(); let taken = unsafe { let ctx_ptr = core::ptr::addr_of_mut!(CTX); (*ctx_ptr)[id].take() }; let Some(ctx) = taken else { sched::abort_current("exec: no context") }; // Call the image entry with the table; the return value is the exit code. let code = unsafe { let f: EntryPoint = core::mem::transmute(ctx.image.entry); f(ctx.exports, ctx.args.len(), ctx.args.as_ptr()) }; // Report completion to the spawner (the shell), then die. The spawner // frees the image pages after reaping the message. let _ = sched::send(sched::SHELL_TID, TaskMsg::User(code as u64)); drop(ctx); }