diff options
Diffstat (limited to 'rsnix/src/exec.rs')
| -rw-r--r-- | rsnix/src/exec.rs | 137 |
1 files changed, 137 insertions, 0 deletions
diff --git a/rsnix/src/exec.rs b/rsnix/src/exec.rs new file mode 100644 index 0000000..7ba39aa --- /dev/null +++ b/rsnix/src/exec.rs | |||
| @@ -0,0 +1,137 @@ | |||
| 1 | // --------------------------------------------------------------------------- | ||
| 2 | // exec — run an external binary (ADR 0006 Phase 2). | ||
| 3 | // | ||
| 4 | // Resolution: name → \bin\<kind>\<name> on each mounted volume → None. | ||
| 5 | // The shell calls exec::spawn with a fully constructed Exports table | ||
| 6 | // (streams point at its pipeline/tty) and clean argv. The binary runs as | ||
| 7 | // its own task; on exit it posts TaskMsg::User(exit_code) to the spawner. | ||
| 8 | // | ||
| 9 | // The Exports table and argv block are heap boxes moved into static slots | ||
| 10 | // indexed by task id — the trampoline adopts them before jumping to the | ||
| 11 | // image entry. One slot per MAX_TASKS; spawn fails if the slot is busy. | ||
| 12 | // --------------------------------------------------------------------------- | ||
| 13 | use crate::load; | ||
| 14 | use crate::sched::{self, TaskMsg}; | ||
| 15 | use alloc::string::String; | ||
| 16 | use alloc::vec::Vec; | ||
| 17 | use rsapi::{Arg, EntryPoint, Exports}; | ||
| 18 | |||
| 19 | /// Per-task invocation context adopted by the trampoline. | ||
| 20 | struct ExecCtx { | ||
| 21 | exports: &'static Exports, | ||
| 22 | /// Heap block holding the argv strings (kept alive until task exit — | ||
| 23 | /// the Arg pointers reference into it). | ||
| 24 | #[allow(dead_code)] | ||
| 25 | argv_buf: Vec<u8>, | ||
| 26 | args: Vec<Arg>, | ||
| 27 | /// Image pages to free when the task dies. | ||
| 28 | image: load::Image, | ||
| 29 | } | ||
| 30 | |||
| 31 | static mut CTX: [Option<ExecCtx>; sched::MAX_TASKS] = [const { None }; sched::MAX_TASKS]; | ||
| 32 | |||
| 33 | /// Error of exec::resolve / exec::spawn. | ||
| 34 | pub enum ExecError { | ||
| 35 | NotFound, | ||
| 36 | LoadFailed(&'static str), | ||
| 37 | Busy, | ||
| 38 | } | ||
| 39 | |||
| 40 | /// Scan every mounted volume for `\bin\<kind>\<name>` (LFN exact match). | ||
| 41 | /// Returns the VFS handle of the first hit. | ||
| 42 | pub fn resolve(kind: &str, name: &str) -> Option<usize> { | ||
| 43 | for vol in 0..crate::fat::volume_count() { | ||
| 44 | if !crate::fat::is_mounted(vol) { | ||
| 45 | continue; | ||
| 46 | } | ||
| 47 | let uid = String::from(crate::fat::uid(vol)); | ||
| 48 | let path = alloc::format!("/disk/{}/bin/{}/{}.efi", uid, kind, name); | ||
| 49 | if let Some(h) = crate::fs::resolve(&path) { | ||
| 50 | return Some(h); | ||
| 51 | } | ||
| 52 | } | ||
| 53 | None | ||
| 54 | } | ||
| 55 | |||
| 56 | /// Read the whole file into a heap buffer (public: the shell uses this | ||
| 57 | /// to fetch binary bytes before calling load::load_image). | ||
| 58 | #[allow(dead_code)] | ||
| 59 | pub fn read_file(h: usize) -> Result<Vec<u8>, ()> { | ||
| 60 | let size = crate::fs::size(h) as usize; | ||
| 61 | let mut buf = Vec::with_capacity(size); | ||
| 62 | let mut chunk = [0u8; 512]; | ||
| 63 | let mut pos = 0u64; | ||
| 64 | loop { | ||
| 65 | let n = crate::fs::read(h, &mut chunk, pos)?; | ||
| 66 | if n == 0 { | ||
| 67 | break; | ||
| 68 | } | ||
| 69 | buf.extend_from_slice(&chunk[..n]); | ||
| 70 | pos += n as u64; | ||
| 71 | } | ||
| 72 | Ok(buf) | ||
| 73 | } | ||
| 74 | |||
| 75 | /// Spawn `image` as a new task running entry(exports, argc, argv). | ||
| 76 | /// | ||
| 77 | /// The caller keeps `exports` alive (it is constructed per invocation in | ||
| 78 | /// shell-owned memory that outlives the command — the pipeline/tty live | ||
| 79 | /// in the blocked spawner). | ||
| 80 | pub fn spawn( | ||
| 81 | image: load::Image, | ||
| 82 | exports: &'static Exports, | ||
| 83 | argv: &[String], | ||
| 84 | name: &str, | ||
| 85 | ) -> Result<usize, ExecError> { | ||
| 86 | // Copy the argv strings into one contiguous block owned by the task. | ||
| 87 | let mut argv_buf: Vec<u8> = Vec::new(); | ||
| 88 | let mut args: Vec<Arg> = Vec::with_capacity(argv.len()); | ||
| 89 | for a in argv { | ||
| 90 | let off = argv_buf.len(); | ||
| 91 | argv_buf.extend_from_slice(a.as_bytes()); | ||
| 92 | args.push(Arg { | ||
| 93 | ptr: unsafe { argv_buf.as_ptr().add(off) }, | ||
| 94 | len: a.len(), | ||
| 95 | }); | ||
| 96 | } | ||
| 97 | |||
| 98 | // Reserve a task slot first so the context is in place before the | ||
| 99 | // task can possibly run (spawn may schedule immediately). | ||
| 100 | let id = sched::reserve_slot().ok_or(ExecError::Busy)?; | ||
| 101 | unsafe { | ||
| 102 | CTX[id] = Some(ExecCtx { | ||
| 103 | exports, | ||
| 104 | argv_buf, | ||
| 105 | args, | ||
| 106 | image, | ||
| 107 | }); | ||
| 108 | } | ||
| 109 | if !sched::launch_at(id, trampoline, name) { | ||
| 110 | unsafe { | ||
| 111 | CTX[id] = None; | ||
| 112 | } | ||
| 113 | return Err(ExecError::Busy); | ||
| 114 | } | ||
| 115 | Ok(id) | ||
| 116 | } | ||
| 117 | |||
| 118 | /// Trampoline: adopt the exec context, call the entry, report completion. | ||
| 119 | fn trampoline() { | ||
| 120 | let id = sched::current(); | ||
| 121 | let taken = unsafe { | ||
| 122 | let ctx_ptr = core::ptr::addr_of_mut!(CTX); | ||
| 123 | (*ctx_ptr)[id].take() | ||
| 124 | }; | ||
| 125 | let Some(ctx) = taken else { | ||
| 126 | sched::abort_current("exec: no context") | ||
| 127 | }; | ||
| 128 | // Call the image entry with the table; the return value is the exit code. | ||
| 129 | let code = unsafe { | ||
| 130 | let f: EntryPoint = core::mem::transmute(ctx.image.entry); | ||
| 131 | f(ctx.exports, ctx.args.len(), ctx.args.as_ptr()) | ||
| 132 | }; | ||
| 133 | // Report completion to the spawner (the shell), then die. The spawner | ||
| 134 | // frees the image pages after reaping the message. | ||
| 135 | let _ = sched::send(sched::SHELL_TID, TaskMsg::User(code as u64)); | ||
| 136 | drop(ctx); | ||
| 137 | } | ||
