summaryrefslogtreecommitdiffstats
path: root/shell/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'shell/src/lib.rs')
-rw-r--r--shell/src/lib.rs111
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
812static mut LAST_EXIT_CODE: i32 = 0;
813
814/// Last exit code of an external command ($? equivalent for scripts).
815pub 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.
820fn 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).
826fn abi_get_line(_out: &mut rsapi::Line) -> rsapi::StreamResult {
827 rsapi::StreamResult::Eof
828}
829
830/// ABI callback: kernel log.
831fn abi_log_line(line: rsapi::Line) {
832 rsnix::klog::append(line.as_str(), 0);
833}
834
835/// ABI callback: exit with a code.
836fn 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
845static 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).
856fn 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