summaryrefslogtreecommitdiffstats
path: root/shell
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-08-27 17:16:04 +0200
committersalmonik <salmonik@tuta.io>2026-08-27 17:16:04 +0200
commit8767dbecb45bbe5f748952fb9171b1a96c173f67 (patch)
treec10a66c91a53522feb980d8b59aa8c87293537f8 /shell
parentstreams: per-invocation channel, pumping mode, filter pipeline wiring (diff)
downloadrs-dos-8767dbecb45bbe5f748952fb9171b1a96c173f67.tar.gz
rs-dos-8767dbecb45bbe5f748952fb9171b1a96c173f67.zip
tool migration: v2 exports + 10 external CLI binaries
rsapi v2: 14 new ABI function pointers appended after v1 — fs_resolve, fs_dir_next (iterator), fs_create, fs_remove, fs_read, fs_write, fs_size, fs_close, rtc_now, mem_stats, pci_probe, pci_device, klog_dump, kernel_version. New C-repr structs: DirEntry, RtcTime, MemStats, PciDevice, KernelVersion. ABI_VERSION bumped to 2. Shell-side: build_exports() constructs the full table at runtime per invocation (replaces static const). 14 kernel-side wrapper functions bridge rsapi types to rsnix kernel APIs; fs_dir_next maintains a per-handle cache of children() results for iteration. Tool binaries (10 crates, all linked against rsapi only): - date: rtc_now → formatted YYYY-MM-DD HH:MM:SS - mem: heap/pages free/total via mem_stats - pci: pci_probe + pci_device enumerate all devices - cat: fs_read paths or get_line stdin; put_line output - ls: fs_resolve + fs_dir_next iteration; trailing / for dirs - mkdir: fs_create(path, true) - touch: fs_create(path, false) - rm: fs_remove(path) - klog: klog_dump 2048 bytes → put_line - info: rtc_now + kernel_version (minimal; mem/pci deferred) Gate verified: date prints RTC; pci lists all 4 QEMU devices (8086:29c0, 1234:1111, 8086:10d3, 8086:2918); hello/boom/count still work; test-disk PASS; poweroff clean.
Diffstat (limited to '')
-rw-r--r--shell/src/lib.rs211
1 files changed, 202 insertions, 9 deletions
diff --git a/shell/src/lib.rs b/shell/src/lib.rs
index 5248ea9..11f5786 100644
--- a/shell/src/lib.rs
+++ b/shell/src/lib.rs
@@ -28,6 +28,7 @@ use alloc::collections::VecDeque;
28use alloc::string::{String, ToString}; 28use alloc::string::{String, ToString};
29use alloc::vec::Vec; 29use alloc::vec::Vec;
30use rsnix::tty::{self, LineInput}; 30use rsnix::tty::{self, LineInput};
31use alloc::boxed::Box;
31use rsnix::{gop_print, gop_println}; 32use rsnix::{gop_print, gop_println};
32 33
33/// Run the home-terminal shell (task 0). The home terminal is an 34/// Run the home-terminal shell (task 0). The home terminal is an
@@ -952,14 +953,205 @@ fn abi_exit(code: i32) -> ! {
952 rsnix::sched::abort_current("exit") 953 rsnix::sched::abort_current("exit")
953} 954}
954 955
955static EXPORTS_V1: rsapi::Exports = rsapi::Exports { 956
956 magic: rsapi::EXPORTS_MAGIC, 957/// Directory iteration cache: one slot per simultaneous fs_dir_next.
957 version: rsapi::ABI_VERSION, 958struct DirIter {
958 put_line: abi_put_line, 959 entries: Vec<(String, rsnix::fs::FileType, u64)>,
959 get_line: abi_get_line, 960 pos: usize,
960 log_line: abi_log_line, 961}
961 exit: abi_exit, 962static mut DIR_CACHE: [Option<DirIter>; 16] = [const { None }; 16];
962}; 963
964fn cache_key(h: i32) -> usize { ((h as usize) & 0x7FFFFFFF) % 16 }
965
966/// ABI: resolve a path to a handle (-1 on error).
967fn abi_fs_resolve(path: rsapi::Line) -> rsapi::FsHandle {
968 match rsnix::fs::resolve(path.as_str()) {
969 Some(h) => h as i32,
970 None => -1,
971 }
972}
973
974/// ABI: iterate directory children one at a time.
975fn abi_fs_dir_next(handle: rsapi::FsHandle, out: &mut rsapi::DirEntry) -> bool {
976 let key = cache_key(handle);
977 unsafe {
978 if DIR_CACHE[key].is_none() {
979 let h = handle as usize;
980 let kids = rsnix::fs::children(h);
981 DIR_CACHE[key] = Some(DirIter { entries: kids, pos: 0 });
982 }
983 let iter = DIR_CACHE[key].as_mut().unwrap();
984 if iter.pos < iter.entries.len() {
985 let (ref name, kind, size) = iter.entries[iter.pos];
986 out.name = rsapi::Line { ptr: name.as_ptr(), len: name.len() };
987 out.is_dir = kind == rsnix::fs::FileType::Dir;
988 out.size = size;
989 iter.pos += 1;
990 true
991 } else {
992 DIR_CACHE[key] = None;
993 false
994 }
995 }
996}
997
998/// ABI: create a file or directory at path.
999fn abi_fs_create(path: rsapi::Line, is_dir: bool) -> i32 {
1000 let p = path.as_str();
1001 if is_dir {
1002 match rsnix::fs::mkdir(p) {
1003 Ok(()) => 0,
1004 Err(()) => -1,
1005 }
1006 } else {
1007 match rsnix::fs::ensure_file(p) {
1008 Ok(_) => 0,
1009 Err(()) => -1,
1010 }
1011 }
1012}
1013
1014/// ABI: remove a file or empty directory.
1015fn abi_fs_remove(path: rsapi::Line) -> i32 {
1016 match rsnix::fs::remove_path(path.as_str()) {
1017 Ok(()) => 0,
1018 Err(()) => -1,
1019 }
1020}
1021
1022/// ABI: read bytes from a handle at a position.
1023fn abi_fs_read(handle: rsapi::FsHandle, buf: *mut u8, pos: u64, len: u64) -> i64 {
1024 let h = handle as usize;
1025 let mut tmp = [0u8; 512];
1026 let mut total = 0i64;
1027 let mut off = pos;
1028 let mut rem = len as usize;
1029 while rem > 0 {
1030 let chunk = rem.min(512);
1031 match rsnix::fs::read(h, &mut tmp[..chunk], off) {
1032 Ok(0) | Err(_) => break,
1033 Ok(n) => {
1034 unsafe { core::ptr::copy_nonoverlapping(tmp.as_ptr(), buf.add(total as usize), n); }
1035 total += n as i64;
1036 off += n as u64;
1037 rem -= n;
1038 }
1039 }
1040 }
1041 total
1042}
1043
1044/// ABI: write bytes to a handle at a position.
1045fn abi_fs_write(handle: rsapi::FsHandle, buf: *const u8, pos: u64, len: u64) -> i64 {
1046 let h = handle as usize;
1047 if len == 0 { return 0; }
1048 let slice = unsafe { core::slice::from_raw_parts(buf, len as usize) };
1049 match rsnix::fs::write(h, slice, pos) {
1050 Ok(n) => n as i64,
1051 Err(()) => -1,
1052 }
1053}
1054
1055/// ABI: get file size.
1056fn abi_fs_size(handle: rsapi::FsHandle) -> u64 {
1057 rsnix::fs::size(handle as usize)
1058}
1059
1060/// ABI: close handle (currently no-op).
1061fn abi_fs_close(_handle: rsapi::FsHandle) {}
1062
1063/// ABI: current real-time clock.
1064fn abi_rtc_now(out: &mut rsapi::RtcTime) {
1065 let t = rsnix::rtc::now();
1066 out.year = t.year;
1067 out.month = t.month;
1068 out.day = t.day;
1069 out.hour = t.hour;
1070 out.minute = t.minute;
1071 out.second = t.second;
1072}
1073
1074/// ABI: memory statistics.
1075fn abi_mem_stats(out: &mut rsapi::MemStats) {
1076 let s = rsnix::memory::heap_stats();
1077 out.heap_total = s.heap_size as u64;
1078 out.heap_free = s.free_bytes as u64;
1079 let ps = rsnix::memory::page_stats();
1080 out.pages_total = ps.span_pages as u64;
1081 out.pages_free = ps.free_pages as u64;
1082}
1083
1084/// ABI: enumerate PCI devices.
1085fn abi_pci_probe() -> u32 {
1086 rsnix::pci::probe().len() as u32
1087}
1088
1089/// ABI: get one PCI device by index.
1090fn abi_pci_device(index: u32, out: &mut rsapi::PciDevice) -> bool {
1091 let devs = rsnix::pci::probe();
1092 match devs.get(index as usize) {
1093 Some(d) => {
1094 out.bus = d.bus;
1095 out.device = d.device;
1096 out.function = d.func;
1097 out.class = d.class;
1098 out.subclass = d.subclass;
1099 out.vendor = d.vendor;
1100 out.device_id = d.device_id;
1101 true
1102 }
1103 None => false,
1104 }
1105}
1106
1107/// ABI: dump kernel log ring into buffer.
1108fn abi_klog_dump(buf: *mut u8, cap: u64) -> u64 {
1109 let s = rsnix::klog::dump();
1110 let bytes = s.as_bytes();
1111 let n = (bytes.len() as u64).min(cap) as usize;
1112 unsafe { core::ptr::copy_nonoverlapping(bytes.as_ptr(), buf, n); }
1113 n as u64
1114}
1115
1116/// ABI: kernel version info.
1117fn abi_kernel_version(out: &mut rsapi::KernelVersion) {
1118 let name = concat!(env!("CARGO_PKG_NAME"), " ", env!("CARGO_PKG_VERSION"));
1119 let mut it = env!("CARGO_PKG_VERSION").split('.');
1120 let major: u16 = it.next().and_then(|s| s.parse().ok()).unwrap_or(0);
1121 let minor: u16 = it.next().and_then(|s| s.parse().ok()).unwrap_or(0);
1122 let patch: u16 = it.next().and_then(|s| s.parse().ok()).unwrap_or(0);
1123 out.name = rsapi::Line { ptr: name.as_ptr(), len: name.len() };
1124 out.major = major;
1125 out.minor = minor;
1126 out.patch = patch;
1127}
1128
1129/// Construct the Exports table for this invocation. v2 fields are
1130/// populated at runtime (binary callbacks → shell → kernel).
1131fn build_exports() -> rsapi::Exports {
1132 rsapi::Exports {
1133 magic: rsapi::EXPORTS_MAGIC,
1134 version: rsapi::ABI_VERSION,
1135 put_line: abi_put_line,
1136 get_line: abi_get_line,
1137 log_line: abi_log_line,
1138 exit: abi_exit,
1139 fs_resolve: abi_fs_resolve,
1140 fs_dir_next: abi_fs_dir_next,
1141 fs_create: abi_fs_create,
1142 fs_remove: abi_fs_remove,
1143 fs_read: abi_fs_read,
1144 fs_write: abi_fs_write,
1145 fs_size: abi_fs_size,
1146 fs_close: abi_fs_close,
1147 rtc_now: abi_rtc_now,
1148 mem_stats: abi_mem_stats,
1149 pci_probe: abi_pci_probe,
1150 pci_device: abi_pci_device,
1151 klog_dump: abi_klog_dump,
1152 kernel_version: abi_kernel_version,
1153 }
1154}
963 1155
964/// Run `cmd` as an external PE32+ binary from /bin/cli/. Returns false 1156/// Run `cmd` as an external PE32+ binary from /bin/cli/. Returns false
965/// when no external binary resolved (the caller falls back to built-ins). 1157/// when no external binary resolved (the caller falls back to built-ins).
@@ -1026,7 +1218,8 @@ fn exec_external(p: &Parsed) -> bool {
1026 1218
1027 let mut cmd_done = false; 1219 let mut cmd_done = false;
1028 let mut exit_code: i64 = 0; 1220 let mut exit_code: i64 = 0;
1029 match rsnix::exec::spawn(image, &EXPORTS_V1, &argv, &name) { 1221 let exports = Box::leak(Box::new(build_exports()));
1222 match rsnix::exec::spawn(image, exports, &argv, &name) {
1030 Ok(_id) => { 1223 Ok(_id) => {
1031 // Pumping mode: assemble canonical lines from our queue into 1224 // Pumping mode: assemble canonical lines from our queue into
1032 // the channel while the command runs. The WM pump delivers 1225 // the channel while the command runs. The WM pump delivers