From 8767dbecb45bbe5f748952fb9171b1a96c173f67 Mon Sep 17 00:00:00 2001 From: salmonik Date: Thu, 27 Aug 2026 17:16:04 +0200 Subject: tool migration: v2 exports + 10 external CLI binaries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- Cargo.lock | 70 ++++++++++++++++ Cargo.toml | 2 +- bins/cat/Cargo.toml | 8 ++ bins/cat/src/main.rs | 68 ++++++++++++++++ bins/date/Cargo.toml | 8 ++ bins/date/src/main.rs | 38 +++++++++ bins/info/Cargo.toml | 8 ++ bins/info/src/main.rs | 22 ++++++ bins/klog/Cargo.toml | 8 ++ bins/klog/src/main.rs | 19 +++++ bins/ls/Cargo.toml | 8 ++ bins/ls/src/main.rs | 47 +++++++++++ bins/mem/Cargo.toml | 8 ++ bins/mem/src/main.rs | 47 +++++++++++ bins/mkdir/Cargo.toml | 8 ++ bins/mkdir/src/main.rs | 18 +++++ bins/pci/Cargo.toml | 8 ++ bins/pci/src/main.rs | 53 +++++++++++++ bins/rm/Cargo.toml | 8 ++ bins/rm/src/main.rs | 17 ++++ bins/touch/Cargo.toml | 8 ++ bins/touch/src/main.rs | 17 ++++ rsapi/src/lib.rs | 117 +++++++++++++++++++++++---- shell/src/lib.rs | 211 ++++++++++++++++++++++++++++++++++++++++++++++--- 24 files changed, 799 insertions(+), 27 deletions(-) create mode 100644 bins/cat/Cargo.toml create mode 100644 bins/cat/src/main.rs create mode 100644 bins/date/Cargo.toml create mode 100644 bins/date/src/main.rs create mode 100644 bins/info/Cargo.toml create mode 100644 bins/info/src/main.rs create mode 100644 bins/klog/Cargo.toml create mode 100644 bins/klog/src/main.rs create mode 100644 bins/ls/Cargo.toml create mode 100644 bins/ls/src/main.rs create mode 100644 bins/mem/Cargo.toml create mode 100644 bins/mem/src/main.rs create mode 100644 bins/mkdir/Cargo.toml create mode 100644 bins/mkdir/src/main.rs create mode 100644 bins/pci/Cargo.toml create mode 100644 bins/pci/src/main.rs create mode 100644 bins/rm/Cargo.toml create mode 100644 bins/rm/src/main.rs create mode 100644 bins/touch/Cargo.toml create mode 100644 bins/touch/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 0ca76eb..35ac614 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,6 +9,13 @@ dependencies = [ "rsapi", ] +[[package]] +name = "cat" +version = "0.6.0" +dependencies = [ + "rsapi", +] + [[package]] name = "count" version = "0.6.0" @@ -16,6 +23,13 @@ dependencies = [ "rsapi", ] +[[package]] +name = "date" +version = "0.6.0" +dependencies = [ + "rsapi", +] + [[package]] name = "hello" version = "0.6.0" @@ -23,6 +37,55 @@ dependencies = [ "rsapi", ] +[[package]] +name = "info" +version = "0.6.0" +dependencies = [ + "rsapi", +] + +[[package]] +name = "klog" +version = "0.6.0" +dependencies = [ + "rsapi", +] + +[[package]] +name = "ls" +version = "0.6.0" +dependencies = [ + "rsapi", +] + +[[package]] +name = "mem" +version = "0.6.0" +dependencies = [ + "rsapi", +] + +[[package]] +name = "mkdir" +version = "0.6.0" +dependencies = [ + "rsapi", +] + +[[package]] +name = "pci" +version = "0.6.0" +dependencies = [ + "rsapi", +] + +[[package]] +name = "rm" +version = "0.6.0" +dependencies = [ + "rsapi", +] + [[package]] name = "rsapi" version = "0.6.0" @@ -65,3 +128,10 @@ dependencies = [ "rsnix", "rspaint", ] + +[[package]] +name = "touch" +version = "0.6.0" +dependencies = [ + "rsapi", +] diff --git a/Cargo.toml b/Cargo.toml index feca451..42aa13f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] resolver = "2" -members = ["rsnix", "shell", "rsdos", "rsdosedit", "rspaint", "rsapi", "bins/hello", "bins/boom", "bins/count"] +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"] [workspace.package] 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 @@ +[package] +name = "cat" +version.workspace = true +edition.workspace = true +[lints] +workspace = true +[dependencies] +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 @@ +//! cat — print files or stdin via the exports table. +//! +//! Args: [path ...] — each path is printed as raw text. +//! No args = read from get_line (stdin) until EOF. + +#![no_std] +#![no_main] + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + loop { core::hint::spin_loop(); } +} + +fn put(exports: &rsapi::Exports, buf: &[u8]) { + (exports.put_line)(rsapi::Line { ptr: buf.as_ptr(), len: buf.len() }); +} + +/// Read one file and put_line each chunk. +fn dump_file(exports: &rsapi::Exports, path: &str) -> i32 { + let h = (exports.fs_resolve)(rsapi::Line { ptr: path.as_ptr(), len: path.len() }); + if h < 0 { + return -1; + } + let mut buf = [0u8; 512]; + let mut pos: u64 = 0; + loop { + let n = (exports.fs_read)(h, buf.as_mut_ptr(), pos, 512); + if n <= 0 { break; } + if !(exports.put_line)(rsapi::Line { ptr: buf.as_ptr(), len: n as usize }) { + break; // pipeline says stop + } + pos += n as u64; + } + (exports.fs_close)(h); + 0 +} + +#[unsafe(no_mangle)] +pub extern "C" fn efi_main(exports: &'static rsapi::Exports, argc: usize, argv: *const rsapi::Arg) -> i32 { + let args = unsafe { core::slice::from_raw_parts(argv, argc) }; + if argc > 0 { + // Print each path argument + let mut code = 0; + for a in args.iter() { + let path = core::str::from_utf8(unsafe { + core::slice::from_raw_parts(a.ptr, a.len) + }).unwrap_or(""); + if dump_file(exports, path) != 0 { + code = 1; + } + } + code + } else { + // No args: read from stdin (get_line) until EOF/Abort + loop { + let mut line = rsapi::Line { ptr: core::ptr::null(), len: 0 }; + match (exports.get_line)(&mut line) { + rsapi::StreamResult::Line => { + put(exports, unsafe { + core::slice::from_raw_parts(line.ptr, line.len) + }); + } + rsapi::StreamResult::Eof | rsapi::StreamResult::Abort => break, + } + } + 0 + } +} 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 @@ +[package] +name = "date" +version.workspace = true +edition.workspace = true +[lints] +workspace = true +[dependencies] +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 @@ +//! date — show current RTC time via the exports table. + +#![no_std] +#![no_main] + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + loop { core::hint::spin_loop(); } +} + +fn put(exports: &rsapi::Exports, buf: &[u8]) { + (exports.put_line)(rsapi::Line { ptr: buf.as_ptr(), len: buf.len() }); +} + +fn dig(b: u8) -> [u8; 2] { [b'0' + b / 10, b'0' + b % 10] } + +#[unsafe(no_mangle)] +pub extern "C" fn efi_main(exports: &'static rsapi::Exports, _argc: usize, _argv: *const rsapi::Arg) -> i32 { + let mut t = rsapi::RtcTime { year: 0, month: 0, day: 0, hour: 0, minute: 0, second: 0, _pad: 0 }; + (exports.rtc_now)(&mut t); + // "YYYY-MM-DD HH:MM:SS" = 19 bytes + let mut buf = [0u8; 19]; + let y = t.year; + buf[0] = b'0' + (y / 1000) as u8; buf[1] = b'0' + (y / 100) as u8 % 10; + buf[2] = b'0' + (y / 10) as u8 % 10; buf[3] = b'0' + y as u8 % 10; + buf[4] = b'-'; + let m = dig(t.month); buf[5] = m[0]; buf[6] = m[1]; + buf[7] = b'-'; + let d = dig(t.day); buf[8] = d[0]; buf[9] = d[1]; + buf[10] = b' '; + let h = dig(t.hour); buf[11] = h[0]; buf[12] = h[1]; + buf[13] = b':'; + let mi = dig(t.minute); buf[14] = mi[0]; buf[15] = mi[1]; + buf[16] = b':'; + let s = dig(t.second); buf[17] = s[0]; buf[18] = s[1]; + put(exports, &buf); + 0 +} 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 @@ +[package] +name = "info" +version.workspace = true +edition.workspace = true +[lints] +workspace = true +[dependencies] +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 @@ +//! info — show system information via the exports table. + +#![no_std] +#![no_main] + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + loop { core::hint::spin_loop(); } +} + +fn put(exports: &rsapi::Exports, s: &str) { + (exports.put_line)(rsapi::Line { ptr: s.as_ptr(), len: s.len() }); +} + +#[unsafe(no_mangle)] +pub extern "C" fn efi_main(exports: &'static rsapi::Exports, _argc: usize, _argv: *const rsapi::Arg) -> i32 { + let mut t = rsapi::RtcTime { year: 0, month: 0, day: 0, hour: 0, minute: 0, second: 0, _pad: 0 }; + (exports.rtc_now)(&mut t); + put(exports, "rs-dos v0.6.0 - external info"); + put(exports, "kernel: rsnix 0.6.0"); + 0 +} 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 @@ +[package] +name = "klog" +version.workspace = true +edition.workspace = true +[lints] +workspace = true +[dependencies] +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 @@ +//! klog — dump kernel log via the exports table. + +#![no_std] +#![no_main] + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + loop { core::hint::spin_loop(); } +} + +#[unsafe(no_mangle)] +pub extern "C" fn efi_main(exports: &'static rsapi::Exports, _argc: usize, _argv: *const rsapi::Arg) -> i32 { + let mut buf = [0u8; 2048]; + let n = (exports.klog_dump)(buf.as_mut_ptr(), buf.len() as u64) as usize; + if n > 0 { + (exports.put_line)(rsapi::Line { ptr: buf.as_ptr(), len: n }); + } + 0 +} 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 @@ +[package] +name = "ls" +version.workspace = true +edition.workspace = true +[lints] +workspace = true +[dependencies] +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 @@ +//! ls — list directory contents via the exports table. +//! +//! Args: [path] — defaults to "." (root). + +#![no_std] +#![no_main] + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + loop { core::hint::spin_loop(); } +} + +fn put(exports: &rsapi::Exports, buf: &[u8]) { + (exports.put_line)(rsapi::Line { ptr: buf.as_ptr(), len: buf.len() }); +} + +#[unsafe(no_mangle)] +pub extern "C" fn efi_main(exports: &'static rsapi::Exports, argc: usize, argv: *const rsapi::Arg) -> i32 { + let path_str = if argc > 0 { + let a = unsafe { &*argv }; + unsafe { core::str::from_utf8_unchecked(core::slice::from_raw_parts(a.ptr, a.len)) } + } else { + "/" + }; + let h = (exports.fs_resolve)(rsapi::Line { ptr: path_str.as_ptr(), len: path_str.len() }); + if h < 0 { return 1; } + let mut entry = rsapi::DirEntry { + name: rsapi::Line { ptr: core::ptr::null(), len: 0 }, + is_dir: false, + _pad: 0, + size: 0, + }; + while (exports.fs_dir_next)(h, &mut entry) { + let name = unsafe { core::str::from_utf8_unchecked(core::slice::from_raw_parts(entry.name.ptr, entry.name.len)) }; + if entry.is_dir { + let mut buf = [0u8; 64]; + let nb = name.as_bytes(); + buf[..nb.len()].copy_from_slice(nb); + buf[nb.len()] = b'/'; + put(exports, &buf[..nb.len() + 1]); + } else { + put(exports, name.as_bytes()); + } + } + (exports.fs_close)(h); + 0 +} 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 @@ +[package] +name = "mem" +version.workspace = true +edition.workspace = true +[lints] +workspace = true +[dependencies] +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 @@ +//! mem — show memory statistics via the exports table. + +#![no_std] +#![no_main] + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + loop { core::hint::spin_loop(); } +} + +fn put(exports: &rsapi::Exports, buf: &[u8]) { + (exports.put_line)(rsapi::Line { ptr: buf.as_ptr(), len: buf.len() }); +} + +fn fmt_kib(exports: &rsapi::Exports, v: u64) { + let mut buf = [0u8; 32]; + let kb = v / 1024; + let mut i = buf.len(); + if kb == 0 { i -= 1; buf[i] = b'0'; } + else { let mut n = kb; while n > 0 { i -= 1; buf[i] = b'0' + (n % 10) as u8; n /= 10; } } + let s = b" KiB"; + buf[i..i+s.len()].copy_from_slice(s); + put(exports, &buf[i..i+s.len()]); +} + +#[unsafe(no_mangle)] +pub extern "C" fn efi_main(exports: &'static rsapi::Exports, _argc: usize, _argv: *const rsapi::Arg) -> i32 { + let mut s = rsapi::MemStats { heap_total: 0, heap_free: 0, pages_total: 0, pages_free: 0 }; + (exports.mem_stats)(&mut s); + let msg = b"heap:"; + put(exports, msg); + put(exports, b" free"); fmt_kib(exports, s.heap_free); + put(exports, b" / total"); fmt_kib(exports, s.heap_total); + let msg2 = b"pages:"; + put(exports, msg2); + put(exports, b" free"); + let mut fb = [0u8; 16]; + let mut i = fb.len(); + 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; } } + put(exports, &fb[i..]); + put(exports, b" / "); + let mut tb = [0u8; 16]; + let mut j = tb.len(); + 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; } } + put(exports, &tb[j..]); + 0 +} 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 @@ +[package] +name = "mkdir" +version.workspace = true +edition.workspace = true +[lints] +workspace = true +[dependencies] +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 @@ +//! mkdir — create directories via the exports table. + +#![no_std] +#![no_main] + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + loop { core::hint::spin_loop(); } +} + +#[unsafe(no_mangle)] +pub extern "C" fn efi_main(exports: &'static rsapi::Exports, argc: usize, argv: *const rsapi::Arg) -> i32 { + let args = unsafe { core::slice::from_raw_parts(argv, argc) }; + if args.is_empty() { return 1; } + let path = unsafe { core::str::from_utf8_unchecked(core::slice::from_raw_parts(args[0].ptr, args[0].len)) }; + let rc = (exports.fs_create)(rsapi::Line { ptr: path.as_ptr(), len: path.len() }, true); + rc +} 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 @@ +[package] +name = "pci" +version.workspace = true +edition.workspace = true +[lints] +workspace = true +[dependencies] +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 @@ +//! pci — list PCI devices via the exports table. + +#![no_std] +#![no_main] + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + loop { core::hint::spin_loop(); } +} + +fn put(exports: &rsapi::Exports, buf: &[u8]) { + (exports.put_line)(rsapi::Line { ptr: buf.as_ptr(), len: buf.len() }); +} + +fn hex2(b: u8, out: &mut [u8]) { + let hi = b >> 4; let lo = b & 0xf; + out[0] = if hi < 10 { b'0' + hi } else { b'a' + hi - 10 }; + out[1] = if lo < 10 { b'0' + lo } else { b'a' + lo - 10 }; +} + +#[unsafe(no_mangle)] +pub extern "C" fn efi_main(exports: &'static rsapi::Exports, _argc: usize, _argv: *const rsapi::Arg) -> i32 { + let count = (exports.pci_probe)(); + let mut i = 0u32; + while i < count { + let mut d = rsapi::PciDevice { bus: 0, device: 0, function: 0, class: 0, subclass: 0, vendor: 0, device_id: 0 }; + if (exports.pci_device)(i, &mut d) { + // "XX:XX.F class=XX:XX ven=XXXX dev=XXXX" = 42 bytes max + let mut buf = [0u8; 42]; + hex2(d.bus, &mut buf[0..2]); buf[2] = b':'; + hex2(d.device, &mut buf[3..5]); buf[5] = b'.'; + buf[6] = b'0' + d.function; + let class_lbl = b" class="; + buf[7..7+class_lbl.len()].copy_from_slice(class_lbl); + let o = 7 + class_lbl.len(); + hex2(d.class, &mut buf[o..o+2]); buf[o+2] = b':'; + hex2(d.subclass, &mut buf[o+3..o+5]); + let ven_lbl = b" ven="; + buf[o+5..o+5+ven_lbl.len()].copy_from_slice(ven_lbl); + let v = o + 5 + ven_lbl.len(); + hex2((d.vendor >> 8) as u8, &mut buf[v..v+2]); + hex2((d.vendor & 0xff) as u8, &mut buf[v+2..v+4]); + let dev_lbl = b" dev="; + buf[v+4..v+4+dev_lbl.len()].copy_from_slice(dev_lbl); + let dd = v + 4 + dev_lbl.len(); + hex2((d.device_id >> 8) as u8, &mut buf[dd..dd+2]); + hex2((d.device_id & 0xff) as u8, &mut buf[dd+2..dd+4]); + put(exports, &buf[..dd+4]); + } + i += 1; + } + 0 +} 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 @@ +[package] +name = "rm" +version.workspace = true +edition.workspace = true +[lints] +workspace = true +[dependencies] +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 @@ +//! rm — remove a file or empty directory via the exports table. + +#![no_std] +#![no_main] + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + loop { core::hint::spin_loop(); } +} + +#[unsafe(no_mangle)] +pub extern "C" fn efi_main(exports: &'static rsapi::Exports, argc: usize, argv: *const rsapi::Arg) -> i32 { + let args = unsafe { core::slice::from_raw_parts(argv, argc) }; + if args.is_empty() { return 1; } + let path = unsafe { core::str::from_utf8_unchecked(core::slice::from_raw_parts(args[0].ptr, args[0].len)) }; + (exports.fs_remove)(rsapi::Line { ptr: path.as_ptr(), len: path.len() }) +} 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 @@ +[package] +name = "touch" +version.workspace = true +edition.workspace = true +[lints] +workspace = true +[dependencies] +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 @@ +//! touch — create an empty file via the exports table. + +#![no_std] +#![no_main] + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + loop { core::hint::spin_loop(); } +} + +#[unsafe(no_mangle)] +pub extern "C" fn efi_main(exports: &'static rsapi::Exports, argc: usize, argv: *const rsapi::Arg) -> i32 { + let args = unsafe { core::slice::from_raw_parts(argv, argc) }; + if args.is_empty() { return 1; } + let path = unsafe { core::str::from_utf8_unchecked(core::slice::from_raw_parts(args[0].ptr, args[0].len)) }; + (exports.fs_create)(rsapi::Line { ptr: path.as_ptr(), len: path.len() }, false) +} 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; /// ABI revision. Bump on any signature change; the kernel refuses to run /// binaries whose required major exceeds its own. -pub const ABI_VERSION: u32 = 1; +pub const ABI_VERSION: u32 = 2; /// Entry-point signature. `extern "C"` on the x86_64-unknown-uefi /// target is the Win64 convention: the three arguments arrive in /// RCX/RDX/R8 on both sides of the table. -pub type EntryPoint = - extern "C" fn(exports: &'static Exports, argc: usize, argv: *const Arg) -> i32; +pub type EntryPoint = extern "C" fn(exports: &'static Exports, argc: usize, argv: *const Arg) -> i32; /// One C-style argv element: pointer + length (rs-dos strings are not /// NUL-terminated in memory). @@ -39,7 +38,7 @@ pub struct Line { } impl Line { - /// Borrow as a &str (lossy is impossible: lines are always UTF-8). + /// Borrow as a &str. pub fn as_str(&self) -> &'static str { unsafe { core::str::from_utf8_unchecked(core::slice::from_raw_parts(self.ptr, self.len)) } } @@ -48,16 +47,75 @@ impl Line { /// Outcome of `get_line`. #[derive(Clone, Copy, PartialEq, Eq)] pub enum StreamResult { - /// A line is available (see `out`). Line, - /// Input exhausted. Eof, - /// Aborted (^C or upstream killed); stop immediately. Abort, } // --------------------------------------------------------------------------- -// Exports table — v1 +// v2 types +// --------------------------------------------------------------------------- + +/// Opaque file/directory handle returned by `fs_resolve` / `fs_create`. +pub type FsHandle = i32; + +/// One directory entry returned by `fs_dir_next`. +#[repr(C)] +#[derive(Clone, Copy)] +pub struct DirEntry { + pub name: Line, + pub is_dir: bool, + pub _pad: u8, + pub size: u64, +} + +/// Real-time clock snapshot. +#[repr(C)] +#[derive(Clone, Copy)] +pub struct RtcTime { + pub year: u16, + pub month: u8, + pub day: u8, + pub hour: u8, + pub minute: u8, + pub second: u8, + pub _pad: u8, +} + +/// Memory statistics (heap + page allocator). +#[repr(C)] +#[derive(Clone, Copy)] +pub struct MemStats { + pub heap_total: u64, + pub heap_free: u64, + pub pages_total: u64, + pub pages_free: u64, +} + +/// One PCI device discovered at probe time. +#[repr(C)] +#[derive(Clone, Copy)] +pub struct PciDevice { + pub bus: u8, + pub device: u8, + pub function: u8, + pub class: u8, + pub subclass: u8, + pub vendor: u16, + pub device_id: u16, +} + +/// Kernel version information. +#[derive(Clone, Copy)] +pub struct KernelVersion { + pub name: Line, + pub major: u16, + pub minor: u16, + pub patch: u16, +} + +// --------------------------------------------------------------------------- +// Exports table — v2 // --------------------------------------------------------------------------- /// The function table a binary receives. Layout is fixed by `version`; @@ -69,19 +127,44 @@ pub struct Exports { pub version: u32, // ---- console / streams (v1) ---- - /// Print one line to the command's output stream. - /// Returns false when the consumer says stop (pager quit / head limit / - /// ^C) — the binary should wrap up promptly. pub put_line: fn(line: Line) -> bool, - /// Read one line from the command's input stream (only meaningful for - /// input consumers like cat/hexdump; otherwise returns Eof). - /// `out` receives the line when the result is `StreamResult::Line`. pub get_line: fn(out: &mut Line) -> StreamResult, // ---- lifecycle (v1) ---- - /// Log one line to the kernel log ring (/dev/klog). pub log_line: fn(line: Line), - /// Exit code for the completion message to the spawner. - /// (Calling this is optional; returning from the entry point exits 0.) pub exit: fn(code: i32) -> !, + + // ---- filesystem queries (v2) ---- + /// Resolve a path to a handle; returns -1 on error. + pub fs_resolve: fn(path: Line) -> FsHandle, + /// Iterate directory entries: returns true and fills `out` while + /// entries remain; returns false when exhausted. Resets position. + pub fs_dir_next: fn(handle: FsHandle, out: &mut DirEntry) -> bool, + /// Create a file (is_dir=false) or directory (is_dir=true) at path. + /// Returns 0 on success, -1 on error. + pub fs_create: fn(path: Line, is_dir: bool) -> i32, + /// Remove a file or empty directory. Returns 0 on success. + pub fs_remove: fn(path: Line) -> i32, + /// Read `len` bytes from `handle` at `pos`. Returns bytes actually + /// read, or -1 on error. + pub fs_read: fn(handle: FsHandle, buf: *mut u8, pos: u64, len: u64) -> i64, + /// Write `len` bytes to `handle` at `pos`. Returns bytes written. + pub fs_write: fn(handle: FsHandle, buf: *const u8, pos: u64, len: u64) -> i64, + /// Get file/directory size in bytes. + pub fs_size: fn(handle: FsHandle) -> u64, + /// Close a handle (currently a no-op; reserved for future fd mgmt). + pub fs_close: fn(handle: FsHandle), + + // ---- hardware queries (v2) ---- + pub rtc_now: fn(out: &mut RtcTime), + pub mem_stats: fn(out: &mut MemStats), + /// Enumerate PCI: returns number of devices discovered. + pub pci_probe: fn() -> u32, + /// Get one PCI device by index (0-based). Returns true on success. + pub pci_device: fn(index: u32, out: &mut PciDevice) -> bool, + /// Copy up to `cap` bytes of the kernel log ring into `buf`. + /// Returns bytes copied. + pub klog_dump: fn(buf: *mut u8, cap: u64) -> u64, + /// Fill `out` with kernel version info. + pub kernel_version: fn(out: &mut KernelVersion), } 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; use alloc::string::{String, ToString}; use alloc::vec::Vec; use rsnix::tty::{self, LineInput}; +use alloc::boxed::Box; use rsnix::{gop_print, gop_println}; /// Run the home-terminal shell (task 0). The home terminal is an @@ -952,14 +953,205 @@ fn abi_exit(code: i32) -> ! { rsnix::sched::abort_current("exit") } -static EXPORTS_V1: rsapi::Exports = rsapi::Exports { - magic: rsapi::EXPORTS_MAGIC, - version: rsapi::ABI_VERSION, - put_line: abi_put_line, - get_line: abi_get_line, - log_line: abi_log_line, - exit: abi_exit, -}; + +/// Directory iteration cache: one slot per simultaneous fs_dir_next. +struct DirIter { + entries: Vec<(String, rsnix::fs::FileType, u64)>, + pos: usize, +} +static mut DIR_CACHE: [Option; 16] = [const { None }; 16]; + +fn cache_key(h: i32) -> usize { ((h as usize) & 0x7FFFFFFF) % 16 } + +/// ABI: resolve a path to a handle (-1 on error). +fn abi_fs_resolve(path: rsapi::Line) -> rsapi::FsHandle { + match rsnix::fs::resolve(path.as_str()) { + Some(h) => h as i32, + None => -1, + } +} + +/// ABI: iterate directory children one at a time. +fn abi_fs_dir_next(handle: rsapi::FsHandle, out: &mut rsapi::DirEntry) -> bool { + let key = cache_key(handle); + unsafe { + if DIR_CACHE[key].is_none() { + let h = handle as usize; + let kids = rsnix::fs::children(h); + DIR_CACHE[key] = Some(DirIter { entries: kids, pos: 0 }); + } + let iter = DIR_CACHE[key].as_mut().unwrap(); + if iter.pos < iter.entries.len() { + let (ref name, kind, size) = iter.entries[iter.pos]; + out.name = rsapi::Line { ptr: name.as_ptr(), len: name.len() }; + out.is_dir = kind == rsnix::fs::FileType::Dir; + out.size = size; + iter.pos += 1; + true + } else { + DIR_CACHE[key] = None; + false + } + } +} + +/// ABI: create a file or directory at path. +fn abi_fs_create(path: rsapi::Line, is_dir: bool) -> i32 { + let p = path.as_str(); + if is_dir { + match rsnix::fs::mkdir(p) { + Ok(()) => 0, + Err(()) => -1, + } + } else { + match rsnix::fs::ensure_file(p) { + Ok(_) => 0, + Err(()) => -1, + } + } +} + +/// ABI: remove a file or empty directory. +fn abi_fs_remove(path: rsapi::Line) -> i32 { + match rsnix::fs::remove_path(path.as_str()) { + Ok(()) => 0, + Err(()) => -1, + } +} + +/// ABI: read bytes from a handle at a position. +fn abi_fs_read(handle: rsapi::FsHandle, buf: *mut u8, pos: u64, len: u64) -> i64 { + let h = handle as usize; + let mut tmp = [0u8; 512]; + let mut total = 0i64; + let mut off = pos; + let mut rem = len as usize; + while rem > 0 { + let chunk = rem.min(512); + match rsnix::fs::read(h, &mut tmp[..chunk], off) { + Ok(0) | Err(_) => break, + Ok(n) => { + unsafe { core::ptr::copy_nonoverlapping(tmp.as_ptr(), buf.add(total as usize), n); } + total += n as i64; + off += n as u64; + rem -= n; + } + } + } + total +} + +/// ABI: write bytes to a handle at a position. +fn abi_fs_write(handle: rsapi::FsHandle, buf: *const u8, pos: u64, len: u64) -> i64 { + let h = handle as usize; + if len == 0 { return 0; } + let slice = unsafe { core::slice::from_raw_parts(buf, len as usize) }; + match rsnix::fs::write(h, slice, pos) { + Ok(n) => n as i64, + Err(()) => -1, + } +} + +/// ABI: get file size. +fn abi_fs_size(handle: rsapi::FsHandle) -> u64 { + rsnix::fs::size(handle as usize) +} + +/// ABI: close handle (currently no-op). +fn abi_fs_close(_handle: rsapi::FsHandle) {} + +/// ABI: current real-time clock. +fn abi_rtc_now(out: &mut rsapi::RtcTime) { + let t = rsnix::rtc::now(); + out.year = t.year; + out.month = t.month; + out.day = t.day; + out.hour = t.hour; + out.minute = t.minute; + out.second = t.second; +} + +/// ABI: memory statistics. +fn abi_mem_stats(out: &mut rsapi::MemStats) { + let s = rsnix::memory::heap_stats(); + out.heap_total = s.heap_size as u64; + out.heap_free = s.free_bytes as u64; + let ps = rsnix::memory::page_stats(); + out.pages_total = ps.span_pages as u64; + out.pages_free = ps.free_pages as u64; +} + +/// ABI: enumerate PCI devices. +fn abi_pci_probe() -> u32 { + rsnix::pci::probe().len() as u32 +} + +/// ABI: get one PCI device by index. +fn abi_pci_device(index: u32, out: &mut rsapi::PciDevice) -> bool { + let devs = rsnix::pci::probe(); + match devs.get(index as usize) { + Some(d) => { + out.bus = d.bus; + out.device = d.device; + out.function = d.func; + out.class = d.class; + out.subclass = d.subclass; + out.vendor = d.vendor; + out.device_id = d.device_id; + true + } + None => false, + } +} + +/// ABI: dump kernel log ring into buffer. +fn abi_klog_dump(buf: *mut u8, cap: u64) -> u64 { + let s = rsnix::klog::dump(); + let bytes = s.as_bytes(); + let n = (bytes.len() as u64).min(cap) as usize; + unsafe { core::ptr::copy_nonoverlapping(bytes.as_ptr(), buf, n); } + n as u64 +} + +/// ABI: kernel version info. +fn abi_kernel_version(out: &mut rsapi::KernelVersion) { + let name = concat!(env!("CARGO_PKG_NAME"), " ", env!("CARGO_PKG_VERSION")); + let mut it = env!("CARGO_PKG_VERSION").split('.'); + let major: u16 = it.next().and_then(|s| s.parse().ok()).unwrap_or(0); + let minor: u16 = it.next().and_then(|s| s.parse().ok()).unwrap_or(0); + let patch: u16 = it.next().and_then(|s| s.parse().ok()).unwrap_or(0); + out.name = rsapi::Line { ptr: name.as_ptr(), len: name.len() }; + out.major = major; + out.minor = minor; + out.patch = patch; +} + +/// Construct the Exports table for this invocation. v2 fields are +/// populated at runtime (binary callbacks → shell → kernel). +fn build_exports() -> rsapi::Exports { + rsapi::Exports { + magic: rsapi::EXPORTS_MAGIC, + version: rsapi::ABI_VERSION, + put_line: abi_put_line, + get_line: abi_get_line, + log_line: abi_log_line, + exit: abi_exit, + fs_resolve: abi_fs_resolve, + fs_dir_next: abi_fs_dir_next, + fs_create: abi_fs_create, + fs_remove: abi_fs_remove, + fs_read: abi_fs_read, + fs_write: abi_fs_write, + fs_size: abi_fs_size, + fs_close: abi_fs_close, + rtc_now: abi_rtc_now, + mem_stats: abi_mem_stats, + pci_probe: abi_pci_probe, + pci_device: abi_pci_device, + klog_dump: abi_klog_dump, + kernel_version: abi_kernel_version, + } +} /// Run `cmd` as an external PE32+ binary from /bin/cli/. Returns false /// when no external binary resolved (the caller falls back to built-ins). @@ -1026,7 +1218,8 @@ fn exec_external(p: &Parsed) -> bool { let mut cmd_done = false; let mut exit_code: i64 = 0; - match rsnix::exec::spawn(image, &EXPORTS_V1, &argv, &name) { + let exports = Box::leak(Box::new(build_exports())); + match rsnix::exec::spawn(image, exports, &argv, &name) { Ok(_id) => { // Pumping mode: assemble canonical lines from our queue into // the channel while the command runs. The WM pump delivers -- cgit v1.2.3