diff options
| author | salmonik <salmonik@tuta.io> | 2026-08-27 17:16:04 +0200 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-08-27 17:16:04 +0200 |
| commit | 8767dbecb45bbe5f748952fb9171b1a96c173f67 (patch) | |
| tree | c10a66c91a53522feb980d8b59aa8c87293537f8 | |
| parent | streams: per-invocation channel, pumping mode, filter pipeline wiring (diff) | |
| download | rs-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.
| -rw-r--r-- | Cargo.lock | 70 | ||||
| -rw-r--r-- | Cargo.toml | 2 | ||||
| -rw-r--r-- | bins/cat/Cargo.toml | 8 | ||||
| -rw-r--r-- | bins/cat/src/main.rs | 68 | ||||
| -rw-r--r-- | bins/date/Cargo.toml | 8 | ||||
| -rw-r--r-- | bins/date/src/main.rs | 38 | ||||
| -rw-r--r-- | bins/info/Cargo.toml | 8 | ||||
| -rw-r--r-- | bins/info/src/main.rs | 22 | ||||
| -rw-r--r-- | bins/klog/Cargo.toml | 8 | ||||
| -rw-r--r-- | bins/klog/src/main.rs | 19 | ||||
| -rw-r--r-- | bins/ls/Cargo.toml | 8 | ||||
| -rw-r--r-- | bins/ls/src/main.rs | 47 | ||||
| -rw-r--r-- | bins/mem/Cargo.toml | 8 | ||||
| -rw-r--r-- | bins/mem/src/main.rs | 47 | ||||
| -rw-r--r-- | bins/mkdir/Cargo.toml | 8 | ||||
| -rw-r--r-- | bins/mkdir/src/main.rs | 18 | ||||
| -rw-r--r-- | bins/pci/Cargo.toml | 8 | ||||
| -rw-r--r-- | bins/pci/src/main.rs | 53 | ||||
| -rw-r--r-- | bins/rm/Cargo.toml | 8 | ||||
| -rw-r--r-- | bins/rm/src/main.rs | 17 | ||||
| -rw-r--r-- | bins/touch/Cargo.toml | 8 | ||||
| -rw-r--r-- | bins/touch/src/main.rs | 17 | ||||
| -rw-r--r-- | rsapi/src/lib.rs | 117 | ||||
| -rw-r--r-- | shell/src/lib.rs | 211 |
24 files changed, 799 insertions, 27 deletions
| @@ -10,6 +10,13 @@ dependencies = [ | |||
| 10 | ] | 10 | ] |
| 11 | 11 | ||
| 12 | [[package]] | 12 | [[package]] |
| 13 | name = "cat" | ||
| 14 | version = "0.6.0" | ||
| 15 | dependencies = [ | ||
| 16 | "rsapi", | ||
| 17 | ] | ||
| 18 | |||
| 19 | [[package]] | ||
| 13 | name = "count" | 20 | name = "count" |
| 14 | version = "0.6.0" | 21 | version = "0.6.0" |
| 15 | dependencies = [ | 22 | dependencies = [ |
| @@ -17,6 +24,13 @@ dependencies = [ | |||
| 17 | ] | 24 | ] |
| 18 | 25 | ||
| 19 | [[package]] | 26 | [[package]] |
| 27 | name = "date" | ||
| 28 | version = "0.6.0" | ||
| 29 | dependencies = [ | ||
| 30 | "rsapi", | ||
| 31 | ] | ||
| 32 | |||
| 33 | [[package]] | ||
| 20 | name = "hello" | 34 | name = "hello" |
| 21 | version = "0.6.0" | 35 | version = "0.6.0" |
| 22 | dependencies = [ | 36 | dependencies = [ |
| @@ -24,6 +38,55 @@ dependencies = [ | |||
| 24 | ] | 38 | ] |
| 25 | 39 | ||
| 26 | [[package]] | 40 | [[package]] |
| 41 | name = "info" | ||
| 42 | version = "0.6.0" | ||
| 43 | dependencies = [ | ||
| 44 | "rsapi", | ||
| 45 | ] | ||
| 46 | |||
| 47 | [[package]] | ||
| 48 | name = "klog" | ||
| 49 | version = "0.6.0" | ||
| 50 | dependencies = [ | ||
| 51 | "rsapi", | ||
| 52 | ] | ||
| 53 | |||
| 54 | [[package]] | ||
| 55 | name = "ls" | ||
| 56 | version = "0.6.0" | ||
| 57 | dependencies = [ | ||
| 58 | "rsapi", | ||
| 59 | ] | ||
| 60 | |||
| 61 | [[package]] | ||
| 62 | name = "mem" | ||
| 63 | version = "0.6.0" | ||
| 64 | dependencies = [ | ||
| 65 | "rsapi", | ||
| 66 | ] | ||
| 67 | |||
| 68 | [[package]] | ||
| 69 | name = "mkdir" | ||
| 70 | version = "0.6.0" | ||
| 71 | dependencies = [ | ||
| 72 | "rsapi", | ||
| 73 | ] | ||
| 74 | |||
| 75 | [[package]] | ||
| 76 | name = "pci" | ||
| 77 | version = "0.6.0" | ||
| 78 | dependencies = [ | ||
| 79 | "rsapi", | ||
| 80 | ] | ||
| 81 | |||
| 82 | [[package]] | ||
| 83 | name = "rm" | ||
| 84 | version = "0.6.0" | ||
| 85 | dependencies = [ | ||
| 86 | "rsapi", | ||
| 87 | ] | ||
| 88 | |||
| 89 | [[package]] | ||
| 27 | name = "rsapi" | 90 | name = "rsapi" |
| 28 | version = "0.6.0" | 91 | version = "0.6.0" |
| 29 | 92 | ||
| @@ -65,3 +128,10 @@ dependencies = [ | |||
| 65 | "rsnix", | 128 | "rsnix", |
| 66 | "rspaint", | 129 | "rspaint", |
| 67 | ] | 130 | ] |
| 131 | |||
| 132 | [[package]] | ||
| 133 | name = "touch" | ||
| 134 | version = "0.6.0" | ||
| 135 | dependencies = [ | ||
| 136 | "rsapi", | ||
| 137 | ] | ||
| @@ -1,6 +1,6 @@ | |||
| 1 | [workspace] | 1 | [workspace] |
| 2 | resolver = "2" | 2 | resolver = "2" |
| 3 | members = ["rsnix", "shell", "rsdos", "rsdosedit", "rspaint", "rsapi", "bins/hello", "bins/boom", "bins/count"] | 3 | members = ["rsnix", "shell", "rsdos", "rsdosedit", "rspaint", "rsapi", "bins/hello", "bins/boom", "bins/count", "bins/date", "bins/mem", "bins/pci", "bins/cat", "bins/mkdir", "bins/touch", "bins/rm", "bins/ls", "bins/klog", "bins/info"] |
| 4 | 4 | ||
| 5 | [workspace.package] | 5 | [workspace.package] |
| 6 | version = "0.6.0" | 6 | version = "0.6.0" |
diff --git a/bins/cat/Cargo.toml b/bins/cat/Cargo.toml new file mode 100644 index 0000000..c5990d5 --- /dev/null +++ b/bins/cat/Cargo.toml | |||
| @@ -0,0 +1,8 @@ | |||
| 1 | [package] | ||
| 2 | name = "cat" | ||
| 3 | version.workspace = true | ||
| 4 | edition.workspace = true | ||
| 5 | [lints] | ||
| 6 | workspace = true | ||
| 7 | [dependencies] | ||
| 8 | rsapi = { path = "../../rsapi" } | ||
diff --git a/bins/cat/src/main.rs b/bins/cat/src/main.rs new file mode 100644 index 0000000..6683c83 --- /dev/null +++ b/bins/cat/src/main.rs | |||
| @@ -0,0 +1,68 @@ | |||
| 1 | //! cat — print files or stdin via the exports table. | ||
| 2 | //! | ||
| 3 | //! Args: [path ...] — each path is printed as raw text. | ||
| 4 | //! No args = read from get_line (stdin) until EOF. | ||
| 5 | |||
| 6 | #![no_std] | ||
| 7 | #![no_main] | ||
| 8 | |||
| 9 | #[panic_handler] | ||
| 10 | fn panic(_info: &core::panic::PanicInfo) -> ! { | ||
| 11 | loop { core::hint::spin_loop(); } | ||
| 12 | } | ||
| 13 | |||
| 14 | fn put(exports: &rsapi::Exports, buf: &[u8]) { | ||
| 15 | (exports.put_line)(rsapi::Line { ptr: buf.as_ptr(), len: buf.len() }); | ||
| 16 | } | ||
| 17 | |||
| 18 | /// Read one file and put_line each chunk. | ||
| 19 | fn dump_file(exports: &rsapi::Exports, path: &str) -> i32 { | ||
| 20 | let h = (exports.fs_resolve)(rsapi::Line { ptr: path.as_ptr(), len: path.len() }); | ||
| 21 | if h < 0 { | ||
| 22 | return -1; | ||
| 23 | } | ||
| 24 | let mut buf = [0u8; 512]; | ||
| 25 | let mut pos: u64 = 0; | ||
| 26 | loop { | ||
| 27 | let n = (exports.fs_read)(h, buf.as_mut_ptr(), pos, 512); | ||
| 28 | if n <= 0 { break; } | ||
| 29 | if !(exports.put_line)(rsapi::Line { ptr: buf.as_ptr(), len: n as usize }) { | ||
| 30 | break; // pipeline says stop | ||
| 31 | } | ||
| 32 | pos += n as u64; | ||
| 33 | } | ||
| 34 | (exports.fs_close)(h); | ||
| 35 | 0 | ||
| 36 | } | ||
| 37 | |||
| 38 | #[unsafe(no_mangle)] | ||
| 39 | pub extern "C" fn efi_main(exports: &'static rsapi::Exports, argc: usize, argv: *const rsapi::Arg) -> i32 { | ||
| 40 | let args = unsafe { core::slice::from_raw_parts(argv, argc) }; | ||
| 41 | if argc > 0 { | ||
| 42 | // Print each path argument | ||
| 43 | let mut code = 0; | ||
| 44 | for a in args.iter() { | ||
| 45 | let path = core::str::from_utf8(unsafe { | ||
| 46 | core::slice::from_raw_parts(a.ptr, a.len) | ||
| 47 | }).unwrap_or(""); | ||
| 48 | if dump_file(exports, path) != 0 { | ||
| 49 | code = 1; | ||
| 50 | } | ||
| 51 | } | ||
| 52 | code | ||
| 53 | } else { | ||
| 54 | // No args: read from stdin (get_line) until EOF/Abort | ||
| 55 | loop { | ||
| 56 | let mut line = rsapi::Line { ptr: core::ptr::null(), len: 0 }; | ||
| 57 | match (exports.get_line)(&mut line) { | ||
| 58 | rsapi::StreamResult::Line => { | ||
| 59 | put(exports, unsafe { | ||
| 60 | core::slice::from_raw_parts(line.ptr, line.len) | ||
| 61 | }); | ||
| 62 | } | ||
| 63 | rsapi::StreamResult::Eof | rsapi::StreamResult::Abort => break, | ||
| 64 | } | ||
| 65 | } | ||
| 66 | 0 | ||
| 67 | } | ||
| 68 | } | ||
diff --git a/bins/date/Cargo.toml b/bins/date/Cargo.toml new file mode 100644 index 0000000..795cc6c --- /dev/null +++ b/bins/date/Cargo.toml | |||
| @@ -0,0 +1,8 @@ | |||
| 1 | [package] | ||
| 2 | name = "date" | ||
| 3 | version.workspace = true | ||
| 4 | edition.workspace = true | ||
| 5 | [lints] | ||
| 6 | workspace = true | ||
| 7 | [dependencies] | ||
| 8 | rsapi = { path = "../../rsapi" } | ||
diff --git a/bins/date/src/main.rs b/bins/date/src/main.rs new file mode 100644 index 0000000..880833e --- /dev/null +++ b/bins/date/src/main.rs | |||
| @@ -0,0 +1,38 @@ | |||
| 1 | //! date — show current RTC time via the exports table. | ||
| 2 | |||
| 3 | #![no_std] | ||
| 4 | #![no_main] | ||
| 5 | |||
| 6 | #[panic_handler] | ||
| 7 | fn panic(_info: &core::panic::PanicInfo) -> ! { | ||
| 8 | loop { core::hint::spin_loop(); } | ||
| 9 | } | ||
| 10 | |||
| 11 | fn put(exports: &rsapi::Exports, buf: &[u8]) { | ||
| 12 | (exports.put_line)(rsapi::Line { ptr: buf.as_ptr(), len: buf.len() }); | ||
| 13 | } | ||
| 14 | |||
| 15 | fn dig(b: u8) -> [u8; 2] { [b'0' + b / 10, b'0' + b % 10] } | ||
| 16 | |||
| 17 | #[unsafe(no_mangle)] | ||
| 18 | pub extern "C" fn efi_main(exports: &'static rsapi::Exports, _argc: usize, _argv: *const rsapi::Arg) -> i32 { | ||
| 19 | let mut t = rsapi::RtcTime { year: 0, month: 0, day: 0, hour: 0, minute: 0, second: 0, _pad: 0 }; | ||
| 20 | (exports.rtc_now)(&mut t); | ||
| 21 | // "YYYY-MM-DD HH:MM:SS" = 19 bytes | ||
| 22 | let mut buf = [0u8; 19]; | ||
| 23 | let y = t.year; | ||
| 24 | buf[0] = b'0' + (y / 1000) as u8; buf[1] = b'0' + (y / 100) as u8 % 10; | ||
| 25 | buf[2] = b'0' + (y / 10) as u8 % 10; buf[3] = b'0' + y as u8 % 10; | ||
| 26 | buf[4] = b'-'; | ||
| 27 | let m = dig(t.month); buf[5] = m[0]; buf[6] = m[1]; | ||
| 28 | buf[7] = b'-'; | ||
| 29 | let d = dig(t.day); buf[8] = d[0]; buf[9] = d[1]; | ||
| 30 | buf[10] = b' '; | ||
| 31 | let h = dig(t.hour); buf[11] = h[0]; buf[12] = h[1]; | ||
| 32 | buf[13] = b':'; | ||
| 33 | let mi = dig(t.minute); buf[14] = mi[0]; buf[15] = mi[1]; | ||
| 34 | buf[16] = b':'; | ||
| 35 | let s = dig(t.second); buf[17] = s[0]; buf[18] = s[1]; | ||
| 36 | put(exports, &buf); | ||
| 37 | 0 | ||
| 38 | } | ||
diff --git a/bins/info/Cargo.toml b/bins/info/Cargo.toml new file mode 100644 index 0000000..b22a534 --- /dev/null +++ b/bins/info/Cargo.toml | |||
| @@ -0,0 +1,8 @@ | |||
| 1 | [package] | ||
| 2 | name = "info" | ||
| 3 | version.workspace = true | ||
| 4 | edition.workspace = true | ||
| 5 | [lints] | ||
| 6 | workspace = true | ||
| 7 | [dependencies] | ||
| 8 | rsapi = { path = "../../rsapi" } | ||
diff --git a/bins/info/src/main.rs b/bins/info/src/main.rs new file mode 100644 index 0000000..cf1bc9f --- /dev/null +++ b/bins/info/src/main.rs | |||
| @@ -0,0 +1,22 @@ | |||
| 1 | //! info — show system information via the exports table. | ||
| 2 | |||
| 3 | #![no_std] | ||
| 4 | #![no_main] | ||
| 5 | |||
| 6 | #[panic_handler] | ||
| 7 | fn panic(_info: &core::panic::PanicInfo) -> ! { | ||
| 8 | loop { core::hint::spin_loop(); } | ||
| 9 | } | ||
| 10 | |||
| 11 | fn put(exports: &rsapi::Exports, s: &str) { | ||
| 12 | (exports.put_line)(rsapi::Line { ptr: s.as_ptr(), len: s.len() }); | ||
| 13 | } | ||
| 14 | |||
| 15 | #[unsafe(no_mangle)] | ||
| 16 | pub extern "C" fn efi_main(exports: &'static rsapi::Exports, _argc: usize, _argv: *const rsapi::Arg) -> i32 { | ||
| 17 | let mut t = rsapi::RtcTime { year: 0, month: 0, day: 0, hour: 0, minute: 0, second: 0, _pad: 0 }; | ||
| 18 | (exports.rtc_now)(&mut t); | ||
| 19 | put(exports, "rs-dos v0.6.0 - external info"); | ||
| 20 | put(exports, "kernel: rsnix 0.6.0"); | ||
| 21 | 0 | ||
| 22 | } | ||
diff --git a/bins/klog/Cargo.toml b/bins/klog/Cargo.toml new file mode 100644 index 0000000..73957ec --- /dev/null +++ b/bins/klog/Cargo.toml | |||
| @@ -0,0 +1,8 @@ | |||
| 1 | [package] | ||
| 2 | name = "klog" | ||
| 3 | version.workspace = true | ||
| 4 | edition.workspace = true | ||
| 5 | [lints] | ||
| 6 | workspace = true | ||
| 7 | [dependencies] | ||
| 8 | rsapi = { path = "../../rsapi" } | ||
diff --git a/bins/klog/src/main.rs b/bins/klog/src/main.rs new file mode 100644 index 0000000..fcd3512 --- /dev/null +++ b/bins/klog/src/main.rs | |||
| @@ -0,0 +1,19 @@ | |||
| 1 | //! klog — dump kernel log via the exports table. | ||
| 2 | |||
| 3 | #![no_std] | ||
| 4 | #![no_main] | ||
| 5 | |||
| 6 | #[panic_handler] | ||
| 7 | fn panic(_info: &core::panic::PanicInfo) -> ! { | ||
| 8 | loop { core::hint::spin_loop(); } | ||
| 9 | } | ||
| 10 | |||
| 11 | #[unsafe(no_mangle)] | ||
| 12 | pub extern "C" fn efi_main(exports: &'static rsapi::Exports, _argc: usize, _argv: *const rsapi::Arg) -> i32 { | ||
| 13 | let mut buf = [0u8; 2048]; | ||
| 14 | let n = (exports.klog_dump)(buf.as_mut_ptr(), buf.len() as u64) as usize; | ||
| 15 | if n > 0 { | ||
| 16 | (exports.put_line)(rsapi::Line { ptr: buf.as_ptr(), len: n }); | ||
| 17 | } | ||
| 18 | 0 | ||
| 19 | } | ||
diff --git a/bins/ls/Cargo.toml b/bins/ls/Cargo.toml new file mode 100644 index 0000000..78497bd --- /dev/null +++ b/bins/ls/Cargo.toml | |||
| @@ -0,0 +1,8 @@ | |||
| 1 | [package] | ||
| 2 | name = "ls" | ||
| 3 | version.workspace = true | ||
| 4 | edition.workspace = true | ||
| 5 | [lints] | ||
| 6 | workspace = true | ||
| 7 | [dependencies] | ||
| 8 | rsapi = { path = "../../rsapi" } | ||
diff --git a/bins/ls/src/main.rs b/bins/ls/src/main.rs new file mode 100644 index 0000000..956bf18 --- /dev/null +++ b/bins/ls/src/main.rs | |||
| @@ -0,0 +1,47 @@ | |||
| 1 | //! ls — list directory contents via the exports table. | ||
| 2 | //! | ||
| 3 | //! Args: [path] — defaults to "." (root). | ||
| 4 | |||
| 5 | #![no_std] | ||
| 6 | #![no_main] | ||
| 7 | |||
| 8 | #[panic_handler] | ||
| 9 | fn panic(_info: &core::panic::PanicInfo) -> ! { | ||
| 10 | loop { core::hint::spin_loop(); } | ||
| 11 | } | ||
| 12 | |||
| 13 | fn put(exports: &rsapi::Exports, buf: &[u8]) { | ||
| 14 | (exports.put_line)(rsapi::Line { ptr: buf.as_ptr(), len: buf.len() }); | ||
| 15 | } | ||
| 16 | |||
| 17 | #[unsafe(no_mangle)] | ||
| 18 | pub extern "C" fn efi_main(exports: &'static rsapi::Exports, argc: usize, argv: *const rsapi::Arg) -> i32 { | ||
| 19 | let path_str = if argc > 0 { | ||
| 20 | let a = unsafe { &*argv }; | ||
| 21 | unsafe { core::str::from_utf8_unchecked(core::slice::from_raw_parts(a.ptr, a.len)) } | ||
| 22 | } else { | ||
| 23 | "/" | ||
| 24 | }; | ||
| 25 | let h = (exports.fs_resolve)(rsapi::Line { ptr: path_str.as_ptr(), len: path_str.len() }); | ||
| 26 | if h < 0 { return 1; } | ||
| 27 | let mut entry = rsapi::DirEntry { | ||
| 28 | name: rsapi::Line { ptr: core::ptr::null(), len: 0 }, | ||
| 29 | is_dir: false, | ||
| 30 | _pad: 0, | ||
| 31 | size: 0, | ||
| 32 | }; | ||
| 33 | while (exports.fs_dir_next)(h, &mut entry) { | ||
| 34 | let name = unsafe { core::str::from_utf8_unchecked(core::slice::from_raw_parts(entry.name.ptr, entry.name.len)) }; | ||
| 35 | if entry.is_dir { | ||
| 36 | let mut buf = [0u8; 64]; | ||
| 37 | let nb = name.as_bytes(); | ||
| 38 | buf[..nb.len()].copy_from_slice(nb); | ||
| 39 | buf[nb.len()] = b'/'; | ||
| 40 | put(exports, &buf[..nb.len() + 1]); | ||
| 41 | } else { | ||
| 42 | put(exports, name.as_bytes()); | ||
| 43 | } | ||
| 44 | } | ||
| 45 | (exports.fs_close)(h); | ||
| 46 | 0 | ||
| 47 | } | ||
diff --git a/bins/mem/Cargo.toml b/bins/mem/Cargo.toml new file mode 100644 index 0000000..2306fc7 --- /dev/null +++ b/bins/mem/Cargo.toml | |||
| @@ -0,0 +1,8 @@ | |||
| 1 | [package] | ||
| 2 | name = "mem" | ||
| 3 | version.workspace = true | ||
| 4 | edition.workspace = true | ||
| 5 | [lints] | ||
| 6 | workspace = true | ||
| 7 | [dependencies] | ||
| 8 | rsapi = { path = "../../rsapi" } | ||
diff --git a/bins/mem/src/main.rs b/bins/mem/src/main.rs new file mode 100644 index 0000000..fd99f47 --- /dev/null +++ b/bins/mem/src/main.rs | |||
| @@ -0,0 +1,47 @@ | |||
| 1 | //! mem — show memory statistics via the exports table. | ||
| 2 | |||
| 3 | #![no_std] | ||
| 4 | #![no_main] | ||
| 5 | |||
| 6 | #[panic_handler] | ||
| 7 | fn panic(_info: &core::panic::PanicInfo) -> ! { | ||
| 8 | loop { core::hint::spin_loop(); } | ||
| 9 | } | ||
| 10 | |||
| 11 | fn put(exports: &rsapi::Exports, buf: &[u8]) { | ||
| 12 | (exports.put_line)(rsapi::Line { ptr: buf.as_ptr(), len: buf.len() }); | ||
| 13 | } | ||
| 14 | |||
| 15 | fn fmt_kib(exports: &rsapi::Exports, v: u64) { | ||
| 16 | let mut buf = [0u8; 32]; | ||
| 17 | let kb = v / 1024; | ||
| 18 | let mut i = buf.len(); | ||
| 19 | if kb == 0 { i -= 1; buf[i] = b'0'; } | ||
| 20 | else { let mut n = kb; while n > 0 { i -= 1; buf[i] = b'0' + (n % 10) as u8; n /= 10; } } | ||
| 21 | let s = b" KiB"; | ||
| 22 | buf[i..i+s.len()].copy_from_slice(s); | ||
| 23 | put(exports, &buf[i..i+s.len()]); | ||
| 24 | } | ||
| 25 | |||
| 26 | #[unsafe(no_mangle)] | ||
| 27 | pub extern "C" fn efi_main(exports: &'static rsapi::Exports, _argc: usize, _argv: *const rsapi::Arg) -> i32 { | ||
| 28 | let mut s = rsapi::MemStats { heap_total: 0, heap_free: 0, pages_total: 0, pages_free: 0 }; | ||
| 29 | (exports.mem_stats)(&mut s); | ||
| 30 | let msg = b"heap:"; | ||
| 31 | put(exports, msg); | ||
| 32 | put(exports, b" free"); fmt_kib(exports, s.heap_free); | ||
| 33 | put(exports, b" / total"); fmt_kib(exports, s.heap_total); | ||
| 34 | let msg2 = b"pages:"; | ||
| 35 | put(exports, msg2); | ||
| 36 | put(exports, b" free"); | ||
| 37 | let mut fb = [0u8; 16]; | ||
| 38 | let mut i = fb.len(); | ||
| 39 | let n = s.pages_free; if n==0 { i-=1; fb[i]=b'0'; } else { let mut m=n; while m>0 { i-=1; fb[i]=b'0'+(m%10) as u8; m/=10; } } | ||
| 40 | put(exports, &fb[i..]); | ||
| 41 | put(exports, b" / "); | ||
| 42 | let mut tb = [0u8; 16]; | ||
| 43 | let mut j = tb.len(); | ||
| 44 | let t = s.pages_total; if t==0 { j-=1; tb[j]=b'0'; } else { let mut m=t; while m>0 { j-=1; tb[j]=b'0'+(m%10) as u8; m/=10; } } | ||
| 45 | put(exports, &tb[j..]); | ||
| 46 | 0 | ||
| 47 | } | ||
diff --git a/bins/mkdir/Cargo.toml b/bins/mkdir/Cargo.toml new file mode 100644 index 0000000..ca97f6c --- /dev/null +++ b/bins/mkdir/Cargo.toml | |||
| @@ -0,0 +1,8 @@ | |||
| 1 | [package] | ||
| 2 | name = "mkdir" | ||
| 3 | version.workspace = true | ||
| 4 | edition.workspace = true | ||
| 5 | [lints] | ||
| 6 | workspace = true | ||
| 7 | [dependencies] | ||
| 8 | rsapi = { path = "../../rsapi" } | ||
diff --git a/bins/mkdir/src/main.rs b/bins/mkdir/src/main.rs new file mode 100644 index 0000000..f6f7e66 --- /dev/null +++ b/bins/mkdir/src/main.rs | |||
| @@ -0,0 +1,18 @@ | |||
| 1 | //! mkdir — create directories via the exports table. | ||
| 2 | |||
| 3 | #![no_std] | ||
| 4 | #![no_main] | ||
| 5 | |||
| 6 | #[panic_handler] | ||
| 7 | fn panic(_info: &core::panic::PanicInfo) -> ! { | ||
| 8 | loop { core::hint::spin_loop(); } | ||
| 9 | } | ||
| 10 | |||
| 11 | #[unsafe(no_mangle)] | ||
| 12 | pub extern "C" fn efi_main(exports: &'static rsapi::Exports, argc: usize, argv: *const rsapi::Arg) -> i32 { | ||
| 13 | let args = unsafe { core::slice::from_raw_parts(argv, argc) }; | ||
| 14 | if args.is_empty() { return 1; } | ||
| 15 | let path = unsafe { core::str::from_utf8_unchecked(core::slice::from_raw_parts(args[0].ptr, args[0].len)) }; | ||
| 16 | let rc = (exports.fs_create)(rsapi::Line { ptr: path.as_ptr(), len: path.len() }, true); | ||
| 17 | rc | ||
| 18 | } | ||
diff --git a/bins/pci/Cargo.toml b/bins/pci/Cargo.toml new file mode 100644 index 0000000..85590f8 --- /dev/null +++ b/bins/pci/Cargo.toml | |||
| @@ -0,0 +1,8 @@ | |||
| 1 | [package] | ||
| 2 | name = "pci" | ||
| 3 | version.workspace = true | ||
| 4 | edition.workspace = true | ||
| 5 | [lints] | ||
| 6 | workspace = true | ||
| 7 | [dependencies] | ||
| 8 | rsapi = { path = "../../rsapi" } | ||
diff --git a/bins/pci/src/main.rs b/bins/pci/src/main.rs new file mode 100644 index 0000000..1182d83 --- /dev/null +++ b/bins/pci/src/main.rs | |||
| @@ -0,0 +1,53 @@ | |||
| 1 | //! pci — list PCI devices via the exports table. | ||
| 2 | |||
| 3 | #![no_std] | ||
| 4 | #![no_main] | ||
| 5 | |||
| 6 | #[panic_handler] | ||
| 7 | fn panic(_info: &core::panic::PanicInfo) -> ! { | ||
| 8 | loop { core::hint::spin_loop(); } | ||
| 9 | } | ||
| 10 | |||
| 11 | fn put(exports: &rsapi::Exports, buf: &[u8]) { | ||
| 12 | (exports.put_line)(rsapi::Line { ptr: buf.as_ptr(), len: buf.len() }); | ||
| 13 | } | ||
| 14 | |||
| 15 | fn hex2(b: u8, out: &mut [u8]) { | ||
| 16 | let hi = b >> 4; let lo = b & 0xf; | ||
| 17 | out[0] = if hi < 10 { b'0' + hi } else { b'a' + hi - 10 }; | ||
| 18 | out[1] = if lo < 10 { b'0' + lo } else { b'a' + lo - 10 }; | ||
| 19 | } | ||
| 20 | |||
| 21 | #[unsafe(no_mangle)] | ||
| 22 | pub extern "C" fn efi_main(exports: &'static rsapi::Exports, _argc: usize, _argv: *const rsapi::Arg) -> i32 { | ||
| 23 | let count = (exports.pci_probe)(); | ||
| 24 | let mut i = 0u32; | ||
| 25 | while i < count { | ||
| 26 | let mut d = rsapi::PciDevice { bus: 0, device: 0, function: 0, class: 0, subclass: 0, vendor: 0, device_id: 0 }; | ||
| 27 | if (exports.pci_device)(i, &mut d) { | ||
| 28 | // "XX:XX.F class=XX:XX ven=XXXX dev=XXXX" = 42 bytes max | ||
| 29 | let mut buf = [0u8; 42]; | ||
| 30 | hex2(d.bus, &mut buf[0..2]); buf[2] = b':'; | ||
| 31 | hex2(d.device, &mut buf[3..5]); buf[5] = b'.'; | ||
| 32 | buf[6] = b'0' + d.function; | ||
| 33 | let class_lbl = b" class="; | ||
| 34 | buf[7..7+class_lbl.len()].copy_from_slice(class_lbl); | ||
| 35 | let o = 7 + class_lbl.len(); | ||
| 36 | hex2(d.class, &mut buf[o..o+2]); buf[o+2] = b':'; | ||
| 37 | hex2(d.subclass, &mut buf[o+3..o+5]); | ||
| 38 | let ven_lbl = b" ven="; | ||
| 39 | buf[o+5..o+5+ven_lbl.len()].copy_from_slice(ven_lbl); | ||
| 40 | let v = o + 5 + ven_lbl.len(); | ||
| 41 | hex2((d.vendor >> 8) as u8, &mut buf[v..v+2]); | ||
| 42 | hex2((d.vendor & 0xff) as u8, &mut buf[v+2..v+4]); | ||
| 43 | let dev_lbl = b" dev="; | ||
| 44 | buf[v+4..v+4+dev_lbl.len()].copy_from_slice(dev_lbl); | ||
| 45 | let dd = v + 4 + dev_lbl.len(); | ||
| 46 | hex2((d.device_id >> 8) as u8, &mut buf[dd..dd+2]); | ||
| 47 | hex2((d.device_id & 0xff) as u8, &mut buf[dd+2..dd+4]); | ||
| 48 | put(exports, &buf[..dd+4]); | ||
| 49 | } | ||
| 50 | i += 1; | ||
| 51 | } | ||
| 52 | 0 | ||
| 53 | } | ||
diff --git a/bins/rm/Cargo.toml b/bins/rm/Cargo.toml new file mode 100644 index 0000000..b49abac --- /dev/null +++ b/bins/rm/Cargo.toml | |||
| @@ -0,0 +1,8 @@ | |||
| 1 | [package] | ||
| 2 | name = "rm" | ||
| 3 | version.workspace = true | ||
| 4 | edition.workspace = true | ||
| 5 | [lints] | ||
| 6 | workspace = true | ||
| 7 | [dependencies] | ||
| 8 | rsapi = { path = "../../rsapi" } | ||
diff --git a/bins/rm/src/main.rs b/bins/rm/src/main.rs new file mode 100644 index 0000000..3407563 --- /dev/null +++ b/bins/rm/src/main.rs | |||
| @@ -0,0 +1,17 @@ | |||
| 1 | //! rm — remove a file or empty directory via the exports table. | ||
| 2 | |||
| 3 | #![no_std] | ||
| 4 | #![no_main] | ||
| 5 | |||
| 6 | #[panic_handler] | ||
| 7 | fn panic(_info: &core::panic::PanicInfo) -> ! { | ||
| 8 | loop { core::hint::spin_loop(); } | ||
| 9 | } | ||
| 10 | |||
| 11 | #[unsafe(no_mangle)] | ||
| 12 | pub extern "C" fn efi_main(exports: &'static rsapi::Exports, argc: usize, argv: *const rsapi::Arg) -> i32 { | ||
| 13 | let args = unsafe { core::slice::from_raw_parts(argv, argc) }; | ||
| 14 | if args.is_empty() { return 1; } | ||
| 15 | let path = unsafe { core::str::from_utf8_unchecked(core::slice::from_raw_parts(args[0].ptr, args[0].len)) }; | ||
| 16 | (exports.fs_remove)(rsapi::Line { ptr: path.as_ptr(), len: path.len() }) | ||
| 17 | } | ||
diff --git a/bins/touch/Cargo.toml b/bins/touch/Cargo.toml new file mode 100644 index 0000000..54824e7 --- /dev/null +++ b/bins/touch/Cargo.toml | |||
| @@ -0,0 +1,8 @@ | |||
| 1 | [package] | ||
| 2 | name = "touch" | ||
| 3 | version.workspace = true | ||
| 4 | edition.workspace = true | ||
| 5 | [lints] | ||
| 6 | workspace = true | ||
| 7 | [dependencies] | ||
| 8 | rsapi = { path = "../../rsapi" } | ||
diff --git a/bins/touch/src/main.rs b/bins/touch/src/main.rs new file mode 100644 index 0000000..5cd61e3 --- /dev/null +++ b/bins/touch/src/main.rs | |||
| @@ -0,0 +1,17 @@ | |||
| 1 | //! touch — create an empty file via the exports table. | ||
| 2 | |||
| 3 | #![no_std] | ||
| 4 | #![no_main] | ||
| 5 | |||
| 6 | #[panic_handler] | ||
| 7 | fn panic(_info: &core::panic::PanicInfo) -> ! { | ||
| 8 | loop { core::hint::spin_loop(); } | ||
| 9 | } | ||
| 10 | |||
| 11 | #[unsafe(no_mangle)] | ||
| 12 | pub extern "C" fn efi_main(exports: &'static rsapi::Exports, argc: usize, argv: *const rsapi::Arg) -> i32 { | ||
| 13 | let args = unsafe { core::slice::from_raw_parts(argv, argc) }; | ||
| 14 | if args.is_empty() { return 1; } | ||
| 15 | let path = unsafe { core::str::from_utf8_unchecked(core::slice::from_raw_parts(args[0].ptr, args[0].len)) }; | ||
| 16 | (exports.fs_create)(rsapi::Line { ptr: path.as_ptr(), len: path.len() }, false) | ||
| 17 | } | ||
diff --git a/rsapi/src/lib.rs b/rsapi/src/lib.rs index d7b69a2..dfe75d6 100644 --- a/rsapi/src/lib.rs +++ b/rsapi/src/lib.rs | |||
| @@ -15,13 +15,12 @@ pub const EXPORTS_MAGIC: u32 = 0x5245_5853; | |||
| 15 | 15 | ||
| 16 | /// ABI revision. Bump on any signature change; the kernel refuses to run | 16 | /// ABI revision. Bump on any signature change; the kernel refuses to run |
| 17 | /// binaries whose required major exceeds its own. | 17 | /// binaries whose required major exceeds its own. |
| 18 | pub const ABI_VERSION: u32 = 1; | 18 | pub const ABI_VERSION: u32 = 2; |
| 19 | 19 | ||
| 20 | /// Entry-point signature. `extern "C"` on the x86_64-unknown-uefi | 20 | /// Entry-point signature. `extern "C"` on the x86_64-unknown-uefi |
| 21 | /// target is the Win64 convention: the three arguments arrive in | 21 | /// target is the Win64 convention: the three arguments arrive in |
| 22 | /// RCX/RDX/R8 on both sides of the table. | 22 | /// RCX/RDX/R8 on both sides of the table. |
| 23 | pub type EntryPoint = | 23 | pub type EntryPoint = extern "C" fn(exports: &'static Exports, argc: usize, argv: *const Arg) -> i32; |
| 24 | extern "C" fn(exports: &'static Exports, argc: usize, argv: *const Arg) -> i32; | ||
| 25 | 24 | ||
| 26 | /// One C-style argv element: pointer + length (rs-dos strings are not | 25 | /// One C-style argv element: pointer + length (rs-dos strings are not |
| 27 | /// NUL-terminated in memory). | 26 | /// NUL-terminated in memory). |
| @@ -39,7 +38,7 @@ pub struct Line { | |||
| 39 | } | 38 | } |
| 40 | 39 | ||
| 41 | impl Line { | 40 | impl Line { |
| 42 | /// Borrow as a &str (lossy is impossible: lines are always UTF-8). | 41 | /// Borrow as a &str. |
| 43 | pub fn as_str(&self) -> &'static str { | 42 | pub fn as_str(&self) -> &'static str { |
| 44 | unsafe { core::str::from_utf8_unchecked(core::slice::from_raw_parts(self.ptr, self.len)) } | 43 | unsafe { core::str::from_utf8_unchecked(core::slice::from_raw_parts(self.ptr, self.len)) } |
| 45 | } | 44 | } |
| @@ -48,16 +47,75 @@ impl Line { | |||
| 48 | /// Outcome of `get_line`. | 47 | /// Outcome of `get_line`. |
| 49 | #[derive(Clone, Copy, PartialEq, Eq)] | 48 | #[derive(Clone, Copy, PartialEq, Eq)] |
| 50 | pub enum StreamResult { | 49 | pub enum StreamResult { |
| 51 | /// A line is available (see `out`). | ||
| 52 | Line, | 50 | Line, |
| 53 | /// Input exhausted. | ||
| 54 | Eof, | 51 | Eof, |
| 55 | /// Aborted (^C or upstream killed); stop immediately. | ||
| 56 | Abort, | 52 | Abort, |
| 57 | } | 53 | } |
| 58 | 54 | ||
| 59 | // --------------------------------------------------------------------------- | 55 | // --------------------------------------------------------------------------- |
| 60 | // Exports table — v1 | 56 | // v2 types |
| 57 | // --------------------------------------------------------------------------- | ||
| 58 | |||
| 59 | /// Opaque file/directory handle returned by `fs_resolve` / `fs_create`. | ||
| 60 | pub type FsHandle = i32; | ||
| 61 | |||
| 62 | /// One directory entry returned by `fs_dir_next`. | ||
| 63 | #[repr(C)] | ||
| 64 | #[derive(Clone, Copy)] | ||
| 65 | pub struct DirEntry { | ||
| 66 | pub name: Line, | ||
| 67 | pub is_dir: bool, | ||
| 68 | pub _pad: u8, | ||
| 69 | pub size: u64, | ||
| 70 | } | ||
| 71 | |||
| 72 | /// Real-time clock snapshot. | ||
| 73 | #[repr(C)] | ||
| 74 | #[derive(Clone, Copy)] | ||
| 75 | pub struct RtcTime { | ||
| 76 | pub year: u16, | ||
| 77 | pub month: u8, | ||
| 78 | pub day: u8, | ||
| 79 | pub hour: u8, | ||
| 80 | pub minute: u8, | ||
| 81 | pub second: u8, | ||
| 82 | pub _pad: u8, | ||
| 83 | } | ||
| 84 | |||
| 85 | /// Memory statistics (heap + page allocator). | ||
| 86 | #[repr(C)] | ||
| 87 | #[derive(Clone, Copy)] | ||
| 88 | pub struct MemStats { | ||
| 89 | pub heap_total: u64, | ||
| 90 | pub heap_free: u64, | ||
| 91 | pub pages_total: u64, | ||
| 92 | pub pages_free: u64, | ||
| 93 | } | ||
| 94 | |||
| 95 | /// One PCI device discovered at probe time. | ||
| 96 | #[repr(C)] | ||
| 97 | #[derive(Clone, Copy)] | ||
| 98 | pub struct PciDevice { | ||
| 99 | pub bus: u8, | ||
| 100 | pub device: u8, | ||
| 101 | pub function: u8, | ||
| 102 | pub class: u8, | ||
| 103 | pub subclass: u8, | ||
| 104 | pub vendor: u16, | ||
| 105 | pub device_id: u16, | ||
| 106 | } | ||
| 107 | |||
| 108 | /// Kernel version information. | ||
| 109 | #[derive(Clone, Copy)] | ||
| 110 | pub struct KernelVersion { | ||
| 111 | pub name: Line, | ||
| 112 | pub major: u16, | ||
| 113 | pub minor: u16, | ||
| 114 | pub patch: u16, | ||
| 115 | } | ||
| 116 | |||
| 117 | // --------------------------------------------------------------------------- | ||
| 118 | // Exports table — v2 | ||
| 61 | // --------------------------------------------------------------------------- | 119 | // --------------------------------------------------------------------------- |
| 62 | 120 | ||
| 63 | /// The function table a binary receives. Layout is fixed by `version`; | 121 | /// The function table a binary receives. Layout is fixed by `version`; |
| @@ -69,19 +127,44 @@ pub struct Exports { | |||
| 69 | pub version: u32, | 127 | pub version: u32, |
| 70 | 128 | ||
| 71 | // ---- console / streams (v1) ---- | 129 | // ---- console / streams (v1) ---- |
| 72 | /// Print one line to the command's output stream. | ||
| 73 | /// Returns false when the consumer says stop (pager quit / head limit / | ||
| 74 | /// ^C) — the binary should wrap up promptly. | ||
| 75 | pub put_line: fn(line: Line) -> bool, | 130 | pub put_line: fn(line: Line) -> bool, |
| 76 | /// Read one line from the command's input stream (only meaningful for | ||
| 77 | /// input consumers like cat/hexdump; otherwise returns Eof). | ||
| 78 | /// `out` receives the line when the result is `StreamResult::Line`. | ||
| 79 | pub get_line: fn(out: &mut Line) -> StreamResult, | 131 | pub get_line: fn(out: &mut Line) -> StreamResult, |
| 80 | 132 | ||
| 81 | // ---- lifecycle (v1) ---- | 133 | // ---- lifecycle (v1) ---- |
| 82 | /// Log one line to the kernel log ring (/dev/klog). | ||
| 83 | pub log_line: fn(line: Line), | 134 | pub log_line: fn(line: Line), |
| 84 | /// Exit code for the completion message to the spawner. | ||
| 85 | /// (Calling this is optional; returning from the entry point exits 0.) | ||
| 86 | pub exit: fn(code: i32) -> !, | 135 | pub exit: fn(code: i32) -> !, |
| 136 | |||
| 137 | // ---- filesystem queries (v2) ---- | ||
| 138 | /// Resolve a path to a handle; returns -1 on error. | ||
| 139 | pub fs_resolve: fn(path: Line) -> FsHandle, | ||
| 140 | /// Iterate directory entries: returns true and fills `out` while | ||
| 141 | /// entries remain; returns false when exhausted. Resets position. | ||
| 142 | pub fs_dir_next: fn(handle: FsHandle, out: &mut DirEntry) -> bool, | ||
| 143 | /// Create a file (is_dir=false) or directory (is_dir=true) at path. | ||
| 144 | /// Returns 0 on success, -1 on error. | ||
| 145 | pub fs_create: fn(path: Line, is_dir: bool) -> i32, | ||
| 146 | /// Remove a file or empty directory. Returns 0 on success. | ||
| 147 | pub fs_remove: fn(path: Line) -> i32, | ||
| 148 | /// Read `len` bytes from `handle` at `pos`. Returns bytes actually | ||
| 149 | /// read, or -1 on error. | ||
| 150 | pub fs_read: fn(handle: FsHandle, buf: *mut u8, pos: u64, len: u64) -> i64, | ||
| 151 | /// Write `len` bytes to `handle` at `pos`. Returns bytes written. | ||
| 152 | pub fs_write: fn(handle: FsHandle, buf: *const u8, pos: u64, len: u64) -> i64, | ||
| 153 | /// Get file/directory size in bytes. | ||
| 154 | pub fs_size: fn(handle: FsHandle) -> u64, | ||
| 155 | /// Close a handle (currently a no-op; reserved for future fd mgmt). | ||
| 156 | pub fs_close: fn(handle: FsHandle), | ||
| 157 | |||
| 158 | // ---- hardware queries (v2) ---- | ||
| 159 | pub rtc_now: fn(out: &mut RtcTime), | ||
| 160 | pub mem_stats: fn(out: &mut MemStats), | ||
| 161 | /// Enumerate PCI: returns number of devices discovered. | ||
| 162 | pub pci_probe: fn() -> u32, | ||
| 163 | /// Get one PCI device by index (0-based). Returns true on success. | ||
| 164 | pub pci_device: fn(index: u32, out: &mut PciDevice) -> bool, | ||
| 165 | /// Copy up to `cap` bytes of the kernel log ring into `buf`. | ||
| 166 | /// Returns bytes copied. | ||
| 167 | pub klog_dump: fn(buf: *mut u8, cap: u64) -> u64, | ||
| 168 | /// Fill `out` with kernel version info. | ||
| 169 | pub kernel_version: fn(out: &mut KernelVersion), | ||
| 87 | } | 170 | } |
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; | |||
| 28 | use alloc::string::{String, ToString}; | 28 | use alloc::string::{String, ToString}; |
| 29 | use alloc::vec::Vec; | 29 | use alloc::vec::Vec; |
| 30 | use rsnix::tty::{self, LineInput}; | 30 | use rsnix::tty::{self, LineInput}; |
| 31 | use alloc::boxed::Box; | ||
| 31 | use rsnix::{gop_print, gop_println}; | 32 | use 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 | ||
| 955 | static 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, | 958 | struct 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, | 962 | static mut DIR_CACHE: [Option<DirIter>; 16] = [const { None }; 16]; |
| 962 | }; | 963 | |
| 964 | fn cache_key(h: i32) -> usize { ((h as usize) & 0x7FFFFFFF) % 16 } | ||
| 965 | |||
| 966 | /// ABI: resolve a path to a handle (-1 on error). | ||
| 967 | fn 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. | ||
| 975 | fn 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. | ||
| 999 | fn 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. | ||
| 1015 | fn 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. | ||
| 1023 | fn 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. | ||
| 1045 | fn 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. | ||
| 1056 | fn abi_fs_size(handle: rsapi::FsHandle) -> u64 { | ||
| 1057 | rsnix::fs::size(handle as usize) | ||
| 1058 | } | ||
| 1059 | |||
| 1060 | /// ABI: close handle (currently no-op). | ||
| 1061 | fn abi_fs_close(_handle: rsapi::FsHandle) {} | ||
| 1062 | |||
| 1063 | /// ABI: current real-time clock. | ||
| 1064 | fn 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. | ||
| 1075 | fn 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. | ||
| 1085 | fn abi_pci_probe() -> u32 { | ||
| 1086 | rsnix::pci::probe().len() as u32 | ||
| 1087 | } | ||
| 1088 | |||
| 1089 | /// ABI: get one PCI device by index. | ||
| 1090 | fn 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. | ||
| 1108 | fn 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. | ||
| 1117 | fn 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). | ||
| 1131 | fn 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 |
