summaryrefslogtreecommitdiffstats
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
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.
-rw-r--r--Cargo.lock70
-rw-r--r--Cargo.toml2
-rw-r--r--bins/cat/Cargo.toml8
-rw-r--r--bins/cat/src/main.rs68
-rw-r--r--bins/date/Cargo.toml8
-rw-r--r--bins/date/src/main.rs38
-rw-r--r--bins/info/Cargo.toml8
-rw-r--r--bins/info/src/main.rs22
-rw-r--r--bins/klog/Cargo.toml8
-rw-r--r--bins/klog/src/main.rs19
-rw-r--r--bins/ls/Cargo.toml8
-rw-r--r--bins/ls/src/main.rs47
-rw-r--r--bins/mem/Cargo.toml8
-rw-r--r--bins/mem/src/main.rs47
-rw-r--r--bins/mkdir/Cargo.toml8
-rw-r--r--bins/mkdir/src/main.rs18
-rw-r--r--bins/pci/Cargo.toml8
-rw-r--r--bins/pci/src/main.rs53
-rw-r--r--bins/rm/Cargo.toml8
-rw-r--r--bins/rm/src/main.rs17
-rw-r--r--bins/touch/Cargo.toml8
-rw-r--r--bins/touch/src/main.rs17
-rw-r--r--rsapi/src/lib.rs117
-rw-r--r--shell/src/lib.rs211
24 files changed, 799 insertions, 27 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 0ca76eb..35ac614 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -10,6 +10,13 @@ dependencies = [
10] 10]
11 11
12[[package]] 12[[package]]
13name = "cat"
14version = "0.6.0"
15dependencies = [
16 "rsapi",
17]
18
19[[package]]
13name = "count" 20name = "count"
14version = "0.6.0" 21version = "0.6.0"
15dependencies = [ 22dependencies = [
@@ -17,6 +24,13 @@ dependencies = [
17] 24]
18 25
19[[package]] 26[[package]]
27name = "date"
28version = "0.6.0"
29dependencies = [
30 "rsapi",
31]
32
33[[package]]
20name = "hello" 34name = "hello"
21version = "0.6.0" 35version = "0.6.0"
22dependencies = [ 36dependencies = [
@@ -24,6 +38,55 @@ dependencies = [
24] 38]
25 39
26[[package]] 40[[package]]
41name = "info"
42version = "0.6.0"
43dependencies = [
44 "rsapi",
45]
46
47[[package]]
48name = "klog"
49version = "0.6.0"
50dependencies = [
51 "rsapi",
52]
53
54[[package]]
55name = "ls"
56version = "0.6.0"
57dependencies = [
58 "rsapi",
59]
60
61[[package]]
62name = "mem"
63version = "0.6.0"
64dependencies = [
65 "rsapi",
66]
67
68[[package]]
69name = "mkdir"
70version = "0.6.0"
71dependencies = [
72 "rsapi",
73]
74
75[[package]]
76name = "pci"
77version = "0.6.0"
78dependencies = [
79 "rsapi",
80]
81
82[[package]]
83name = "rm"
84version = "0.6.0"
85dependencies = [
86 "rsapi",
87]
88
89[[package]]
27name = "rsapi" 90name = "rsapi"
28version = "0.6.0" 91version = "0.6.0"
29 92
@@ -65,3 +128,10 @@ dependencies = [
65 "rsnix", 128 "rsnix",
66 "rspaint", 129 "rspaint",
67] 130]
131
132[[package]]
133name = "touch"
134version = "0.6.0"
135dependencies = [
136 "rsapi",
137]
diff --git a/Cargo.toml b/Cargo.toml
index feca451..42aa13f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
1[workspace] 1[workspace]
2resolver = "2" 2resolver = "2"
3members = ["rsnix", "shell", "rsdos", "rsdosedit", "rspaint", "rsapi", "bins/hello", "bins/boom", "bins/count"] 3members = ["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]
6version = "0.6.0" 6version = "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]
2name = "cat"
3version.workspace = true
4edition.workspace = true
5[lints]
6workspace = true
7[dependencies]
8rsapi = { 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]
10fn panic(_info: &core::panic::PanicInfo) -> ! {
11 loop { core::hint::spin_loop(); }
12}
13
14fn 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.
19fn 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)]
39pub 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]
2name = "date"
3version.workspace = true
4edition.workspace = true
5[lints]
6workspace = true
7[dependencies]
8rsapi = { 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]
7fn panic(_info: &core::panic::PanicInfo) -> ! {
8 loop { core::hint::spin_loop(); }
9}
10
11fn put(exports: &rsapi::Exports, buf: &[u8]) {
12 (exports.put_line)(rsapi::Line { ptr: buf.as_ptr(), len: buf.len() });
13}
14
15fn dig(b: u8) -> [u8; 2] { [b'0' + b / 10, b'0' + b % 10] }
16
17#[unsafe(no_mangle)]
18pub 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]
2name = "info"
3version.workspace = true
4edition.workspace = true
5[lints]
6workspace = true
7[dependencies]
8rsapi = { 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]
7fn panic(_info: &core::panic::PanicInfo) -> ! {
8 loop { core::hint::spin_loop(); }
9}
10
11fn 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)]
16pub 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]
2name = "klog"
3version.workspace = true
4edition.workspace = true
5[lints]
6workspace = true
7[dependencies]
8rsapi = { 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]
7fn panic(_info: &core::panic::PanicInfo) -> ! {
8 loop { core::hint::spin_loop(); }
9}
10
11#[unsafe(no_mangle)]
12pub 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]
2name = "ls"
3version.workspace = true
4edition.workspace = true
5[lints]
6workspace = true
7[dependencies]
8rsapi = { 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]
9fn panic(_info: &core::panic::PanicInfo) -> ! {
10 loop { core::hint::spin_loop(); }
11}
12
13fn 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)]
18pub 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]
2name = "mem"
3version.workspace = true
4edition.workspace = true
5[lints]
6workspace = true
7[dependencies]
8rsapi = { 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]
7fn panic(_info: &core::panic::PanicInfo) -> ! {
8 loop { core::hint::spin_loop(); }
9}
10
11fn put(exports: &rsapi::Exports, buf: &[u8]) {
12 (exports.put_line)(rsapi::Line { ptr: buf.as_ptr(), len: buf.len() });
13}
14
15fn 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)]
27pub 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]
2name = "mkdir"
3version.workspace = true
4edition.workspace = true
5[lints]
6workspace = true
7[dependencies]
8rsapi = { 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]
7fn panic(_info: &core::panic::PanicInfo) -> ! {
8 loop { core::hint::spin_loop(); }
9}
10
11#[unsafe(no_mangle)]
12pub 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]
2name = "pci"
3version.workspace = true
4edition.workspace = true
5[lints]
6workspace = true
7[dependencies]
8rsapi = { 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]
7fn panic(_info: &core::panic::PanicInfo) -> ! {
8 loop { core::hint::spin_loop(); }
9}
10
11fn put(exports: &rsapi::Exports, buf: &[u8]) {
12 (exports.put_line)(rsapi::Line { ptr: buf.as_ptr(), len: buf.len() });
13}
14
15fn 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)]
22pub 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]
2name = "rm"
3version.workspace = true
4edition.workspace = true
5[lints]
6workspace = true
7[dependencies]
8rsapi = { 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]
7fn panic(_info: &core::panic::PanicInfo) -> ! {
8 loop { core::hint::spin_loop(); }
9}
10
11#[unsafe(no_mangle)]
12pub 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]
2name = "touch"
3version.workspace = true
4edition.workspace = true
5[lints]
6workspace = true
7[dependencies]
8rsapi = { 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]
7fn panic(_info: &core::panic::PanicInfo) -> ! {
8 loop { core::hint::spin_loop(); }
9}
10
11#[unsafe(no_mangle)]
12pub 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.
18pub const ABI_VERSION: u32 = 1; 18pub 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.
23pub type EntryPoint = 23pub 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
41impl Line { 40impl 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)]
50pub enum StreamResult { 49pub 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`.
60pub type FsHandle = i32;
61
62/// One directory entry returned by `fs_dir_next`.
63#[repr(C)]
64#[derive(Clone, Copy)]
65pub 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)]
75pub 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)]
88pub 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)]
98pub 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)]
110pub 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;
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