diff options
Diffstat (limited to '')
| -rw-r--r-- | shell/src/lib.rs | 111 |
1 files changed, 111 insertions, 0 deletions
diff --git a/shell/src/lib.rs b/shell/src/lib.rs index d1710cc..9ca999e 100644 --- a/shell/src/lib.rs +++ b/shell/src/lib.rs | |||
| @@ -745,6 +745,15 @@ write `{} <path> TIL <marker>`", | |||
| 745 | } | 745 | } |
| 746 | } | 746 | } |
| 747 | 747 | ||
| 748 | // Resolution rule (ADR 0006): \bin\<kind>\<name> first, else the | ||
| 749 | // built-in. The frozen core (help/clear/echo/poweroff/exit/halt/ | ||
| 750 | // rescan) is exempt — it exists to fix the system and never migrates. | ||
| 751 | if !matches!(cmd, "help" | "clear" | "echo" | "halt" | "poweroff" | "exit" | "rescan") | ||
| 752 | && exec_external(&parsed) | ||
| 753 | { | ||
| 754 | return; | ||
| 755 | } | ||
| 756 | |||
| 748 | match cmd { | 757 | match cmd { |
| 749 | "help" => cmd_help(&parsed), | 758 | "help" => cmd_help(&parsed), |
| 750 | "echo" => cmd_echo(&parsed), | 759 | "echo" => cmd_echo(&parsed), |
| @@ -792,6 +801,108 @@ write `{} <path> TIL <marker>`", | |||
| 792 | } | 801 | } |
| 793 | 802 | ||
| 794 | // --------------------------------------------------------------------------- | 803 | // --------------------------------------------------------------------------- |
| 804 | // External binaries (ADR 0006 Phase 2) | ||
| 805 | // | ||
| 806 | // Resolution: \bin\cli\<name> on each mounted volume; else "unknown | ||
| 807 | // command". The Exports table is constructed per invocation: v1 wires | ||
| 808 | // put_line to the active terminal and get_line to EOF (stream injection | ||
| 809 | // through the filter pipeline lands in Phase 3). | ||
| 810 | // --------------------------------------------------------------------------- | ||
| 811 | |||
| 812 | static mut LAST_EXIT_CODE: i32 = 0; | ||
| 813 | |||
| 814 | /// Last exit code of an external command ($? equivalent for scripts). | ||
| 815 | pub fn last_exit_code() -> i32 { | ||
| 816 | unsafe { core::ptr::addr_of!(LAST_EXIT_CODE).read() } | ||
| 817 | } | ||
| 818 | |||
| 819 | /// ABI callback: print one line to the invoking terminal. | ||
| 820 | fn abi_put_line(line: rsapi::Line) -> bool { | ||
| 821 | term_println(line.as_str()); | ||
| 822 | true | ||
| 823 | } | ||
| 824 | |||
| 825 | /// ABI callback: read one input line — no stream source wired yet (Phase 3). | ||
| 826 | fn abi_get_line(_out: &mut rsapi::Line) -> rsapi::StreamResult { | ||
| 827 | rsapi::StreamResult::Eof | ||
| 828 | } | ||
| 829 | |||
| 830 | /// ABI callback: kernel log. | ||
| 831 | fn abi_log_line(line: rsapi::Line) { | ||
| 832 | rsnix::klog::append(line.as_str(), 0); | ||
| 833 | } | ||
| 834 | |||
| 835 | /// ABI callback: exit with a code. | ||
| 836 | fn abi_exit(code: i32) -> ! { | ||
| 837 | unsafe { | ||
| 838 | LAST_EXIT_CODE = code; | ||
| 839 | } | ||
| 840 | // Unwind the binary by longjmp-style task abort: the trampoline never | ||
| 841 | // returns here. We report the code via the static and kill the task. | ||
| 842 | rsnix::sched::abort_current("exit") | ||
| 843 | } | ||
| 844 | |||
| 845 | static EXPORTS_V1: rsapi::Exports = rsapi::Exports { | ||
| 846 | magic: rsapi::EXPORTS_MAGIC, | ||
| 847 | version: rsapi::ABI_VERSION, | ||
| 848 | put_line: abi_put_line, | ||
| 849 | get_line: abi_get_line, | ||
| 850 | log_line: abi_log_line, | ||
| 851 | exit: abi_exit, | ||
| 852 | }; | ||
| 853 | |||
| 854 | /// Run `cmd` as an external PE32+ binary from /bin/cli/. Returns false | ||
| 855 | /// when no external binary resolved (the caller falls back to built-ins). | ||
| 856 | fn exec_external(p: &Parsed) -> bool { | ||
| 857 | let Some(h) = rsnix::exec::resolve("cli", &p.cmd) else { | ||
| 858 | return false; | ||
| 859 | }; | ||
| 860 | let bytes = match rsnix::exec::read_file(h) { | ||
| 861 | Ok(b) => b, | ||
| 862 | Err(_) => { | ||
| 863 | term_err(&alloc::format!("{}: read failed", p.cmd)); | ||
| 864 | return true; | ||
| 865 | } | ||
| 866 | }; | ||
| 867 | let image = match rsnix::load::load_image(&bytes) { | ||
| 868 | Ok(img) => img, | ||
| 869 | Err(e) => { | ||
| 870 | term_err(&alloc::format!("{}: cannot load ({:?})", p.cmd, e)); | ||
| 871 | return true; | ||
| 872 | } | ||
| 873 | }; | ||
| 874 | let mut argv: Vec<String> = Vec::with_capacity(p.args.len() + 1); | ||
| 875 | argv.push(p.cmd.clone()); | ||
| 876 | argv.extend(p.args.iter().cloned()); | ||
| 877 | let name = alloc::format!("{}", p.cmd); | ||
| 878 | match rsnix::exec::spawn(image, &EXPORTS_V1, &argv, &name) { | ||
| 879 | Ok(id) => { | ||
| 880 | // Wait for the completion message (TaskMsg::User(exit_code)). | ||
| 881 | loop { | ||
| 882 | match rsnix::sched::recv_blocking() { | ||
| 883 | rsnix::sched::TaskMsg::User(code) => { | ||
| 884 | unsafe { | ||
| 885 | LAST_EXIT_CODE = code as i32; | ||
| 886 | } | ||
| 887 | break; | ||
| 888 | } | ||
| 889 | rsnix::sched::TaskMsg::Key(rsnix::ps2::KeyEvent::Ctrl('c')) => { | ||
| 890 | // ^C while waiting: kill the command task. | ||
| 891 | let _ = rsnix::sched::kill(id); | ||
| 892 | term_err("^C"); | ||
| 893 | break; | ||
| 894 | } | ||
| 895 | _ => {} | ||
| 896 | } | ||
| 897 | } | ||
| 898 | } | ||
| 899 | Err(rsnix::exec::ExecError::Busy) => term_err(&alloc::format!("{}: task table full", p.cmd)), | ||
| 900 | Err(_) => term_err(&alloc::format!("{}: cannot start", p.cmd)), | ||
| 901 | } | ||
| 902 | true | ||
| 903 | } | ||
| 904 | |||
| 905 | // --------------------------------------------------------------------------- | ||
| 795 | // Built-in commands | 906 | // Built-in commands |
| 796 | // --------------------------------------------------------------------------- | 907 | // --------------------------------------------------------------------------- |
| 797 | 908 | ||
