summaryrefslogtreecommitdiffstats
path: root/rsnix/src/exec.rs
blob: 7ba39aa924d812050d14cea66f56e322e5783dd5 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// ---------------------------------------------------------------------------
// exec — run an external binary (ADR 0006 Phase 2).
//
// Resolution: name → \bin\<kind>\<name> 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<u8>,
    args: Vec<Arg>,
    /// Image pages to free when the task dies.
    image: load::Image,
}

static mut CTX: [Option<ExecCtx>; 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\<kind>\<name>` (LFN exact match).
/// Returns the VFS handle of the first hit.
pub fn resolve(kind: &str, name: &str) -> Option<usize> {
    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<Vec<u8>, ()> {
    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<usize, ExecError> {
    // Copy the argv strings into one contiguous block owned by the task.
    let mut argv_buf: Vec<u8> = Vec::new();
    let mut args: Vec<Arg> = 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);
}