diff --git a/base/cat/Cargo.toml b/base/cat/Cargo.toml new file mode 100644 index 00000000..d163b266 --- /dev/null +++ b/base/cat/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "cat" +version = "0.1.0" +edition = "2021" +authors = ["vibix hackers"] +license = "MIT OR Apache-2.0" + +[[bin]] +name = "cat" +path = "src/main.rs" + +# Standalone package — not part of the main workspace. Built with +# `-Z build-std` against the in-repo std fork (see xtask build). +[workspace] diff --git a/base/cat/src/main.rs b/base/cat/src/main.rs new file mode 100644 index 00000000..ec3d6bbf --- /dev/null +++ b/base/cat/src/main.rs @@ -0,0 +1,64 @@ +#![feature(restricted_std)] + +#[cfg(not(test))] +mod syscalls; + +use std::env; +use std::fs::File; +use std::io::{self, Read, Write}; +use std::process::ExitCode; + +fn cat_reader(mut reader: R, stdout: &mut io::StdoutLock<'_>) -> io::Result<()> { + let mut buf = [0u8; 4096]; + loop { + let n = reader.read(&mut buf)?; + if n == 0 { + break; + } + stdout.write_all(&buf[..n])?; + } + Ok(()) +} + +fn main() -> ExitCode { + let args: Vec = env::args().collect(); + let stdout = io::stdout(); + let mut stdout = stdout.lock(); + let mut status: u8 = 0; + + if args.len() <= 1 { + // No arguments: read stdin to stdout. + let stdin = io::stdin(); + let stdin = stdin.lock(); + if let Err(e) = cat_reader(stdin, &mut stdout) { + eprintln!("cat: {e}"); + return ExitCode::from(1); + } + } else { + for path in &args[1..] { + if path == "-" { + let stdin = io::stdin(); + let stdin = stdin.lock(); + if let Err(e) = cat_reader(stdin, &mut stdout) { + eprintln!("cat: -: {e}"); + status = 1; + } + } else { + match File::open(path) { + Ok(file) => { + if let Err(e) = cat_reader(file, &mut stdout) { + eprintln!("cat: {path}: {e}"); + status = 1; + } + } + Err(e) => { + eprintln!("cat: {path}: {e}"); + status = 1; + } + } + } + } + } + + ExitCode::from(status) +} diff --git a/base/cat/src/syscalls.rs b/base/cat/src/syscalls.rs new file mode 100644 index 00000000..760b3d45 --- /dev/null +++ b/base/cat/src/syscalls.rs @@ -0,0 +1,50 @@ +//! C-ABI syscall shims required by the vibix std fork. +//! +//! The in-repo std fork links against POSIX symbols (`close`, etc.) that are +//! not provided by a system libc on vibix. We supply them here via raw +//! syscall instructions, mirroring the approach in `base/sh/src/syscalls.rs`. + +use core::arch::asm; + +const SYS_CLOSE: u64 = 3; + +extern "C" { + fn __errno_location() -> *mut i32; +} + +#[inline(always)] +unsafe fn raw1(nr: u64, a0: u64) -> i64 { + let ret: i64; + unsafe { + asm!( + "syscall", + inlateout("rax") nr => ret, + inlateout("rdi") a0 => _, + lateout("rcx") _, + lateout("r11") _, + lateout("rdx") _, + lateout("rsi") _, + lateout("r8") _, + lateout("r9") _, + lateout("r10") _, + options(nostack, preserves_flags), + ); + } + ret +} + +/// Convert raw syscall return to C convention: on error set errno, return -1. +#[inline] +unsafe fn cvt(r: i64) -> i64 { + if r < 0 { + unsafe { *__errno_location() = (-r) as i32 }; + -1 + } else { + r + } +} + +#[no_mangle] +pub unsafe extern "C" fn close(fd: i32) -> i32 { + unsafe { cvt(raw1(SYS_CLOSE, fd as u64)) as i32 } +} diff --git a/base/ls/Cargo.toml b/base/ls/Cargo.toml new file mode 100644 index 00000000..b04994fb --- /dev/null +++ b/base/ls/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "ls" +version = "0.1.0" +edition = "2021" +authors = ["vibix hackers"] +license = "MIT OR Apache-2.0" + +[[bin]] +name = "ls" +path = "src/main.rs" + +# Standalone package — not part of the main workspace. Built with +# `-Z build-std` against the in-repo std fork (see xtask build). +[workspace] diff --git a/base/ls/src/main.rs b/base/ls/src/main.rs new file mode 100644 index 00000000..9f78e470 --- /dev/null +++ b/base/ls/src/main.rs @@ -0,0 +1,70 @@ +#![feature(restricted_std)] + +#[cfg(not(test))] +mod syscalls; + +use std::env; +use std::fs; +use std::process::ExitCode; + +fn list_dir(path: &str) -> u8 { + let entries = match fs::read_dir(path) { + Ok(entries) => entries, + Err(e) => { + eprintln!("ls: {path}: {e}"); + return 1; + } + }; + + let mut names: Vec<(String, bool)> = Vec::new(); + for entry in entries { + match entry { + Ok(entry) => { + let name = entry.file_name().to_string_lossy().into_owned(); + let is_dir = entry.file_type().map(|ft| ft.is_dir()).unwrap_or(false); + names.push((name, is_dir)); + } + Err(e) => { + eprintln!("ls: {path}: {e}"); + return 1; + } + } + } + + names.sort_by(|a, b| a.0.cmp(&b.0)); + + for (name, is_dir) in &names { + if *is_dir { + println!("{name}/"); + } else { + println!("{name}"); + } + } + + 0 +} + +fn main() -> ExitCode { + let args: Vec = env::args().collect(); + let mut status: u8 = 0; + + if args.len() <= 1 { + status = list_dir("."); + } else { + let show_header = args.len() > 2; + for (i, path) in args[1..].iter().enumerate() { + if show_header { + if i > 0 { + println!(); + } + println!("{path}:"); + } + let s = list_dir(path); + if s != 0 { + status = s; + } + } + } + + ExitCode::from(status) +} diff --git a/base/ls/src/syscalls.rs b/base/ls/src/syscalls.rs new file mode 100644 index 00000000..760b3d45 --- /dev/null +++ b/base/ls/src/syscalls.rs @@ -0,0 +1,50 @@ +//! C-ABI syscall shims required by the vibix std fork. +//! +//! The in-repo std fork links against POSIX symbols (`close`, etc.) that are +//! not provided by a system libc on vibix. We supply them here via raw +//! syscall instructions, mirroring the approach in `base/sh/src/syscalls.rs`. + +use core::arch::asm; + +const SYS_CLOSE: u64 = 3; + +extern "C" { + fn __errno_location() -> *mut i32; +} + +#[inline(always)] +unsafe fn raw1(nr: u64, a0: u64) -> i64 { + let ret: i64; + unsafe { + asm!( + "syscall", + inlateout("rax") nr => ret, + inlateout("rdi") a0 => _, + lateout("rcx") _, + lateout("r11") _, + lateout("rdx") _, + lateout("rsi") _, + lateout("r8") _, + lateout("r9") _, + lateout("r10") _, + options(nostack, preserves_flags), + ); + } + ret +} + +/// Convert raw syscall return to C convention: on error set errno, return -1. +#[inline] +unsafe fn cvt(r: i64) -> i64 { + if r < 0 { + unsafe { *__errno_location() = (-r) as i32 }; + -1 + } else { + r + } +} + +#[no_mangle] +pub unsafe extern "C" fn close(fd: i32) -> i32 { + unsafe { cvt(raw1(SYS_CLOSE, fd as u64)) as i32 } +} diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 6125b229..f18322ee 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -800,14 +800,16 @@ fn build_userspace_std_hello() -> R { Ok(bin) } -/// Build the `/bin/sh` binary — the vibix POSIX shell. +/// Build a standalone base-system binary (e.g. sh, cat, ls) using the +/// out-of-tree `-Z build-std` approach with the in-repo std fork. /// -/// Uses the same out-of-tree `-Z build-std` approach as `std_hello`. -/// The crate lives in `base/sh/` (base system program, not a test). -fn build_userspace_sh() -> R { +/// `manifest_rel` is the manifest path relative to the workspace root +/// (e.g. `"base/sh/Cargo.toml"`). `bin_name` is the expected output +/// binary name under `target/x86_64-unknown-vibix/debug/`. +fn build_userspace_std_bin(manifest_rel: &str, bin_name: &str) -> R { let ws = workspace_root(); let target_spec = ws.join(VIBIX_USERSPACE_TARGET); - let manifest = ws.join("base/sh/Cargo.toml"); + let manifest = ws.join(manifest_rel); let library_root = ws.join("library"); let target_dir = ws.join("target"); @@ -835,14 +837,29 @@ fn build_userspace_sh() -> R { let bin = target_dir .join("x86_64-unknown-vibix") .join("debug") - .join("sh"); + .join(bin_name); if !bin.exists() { - return Err(format!("sh binary missing at {} after build", bin.display()).into()); + return Err(format!("{bin_name} binary missing at {} after build", bin.display()).into()); } strip_debug(&bin)?; Ok(bin) } +/// Build the `/bin/sh` binary — the vibix POSIX shell. +fn build_userspace_sh() -> R { + build_userspace_std_bin("base/sh/Cargo.toml", "sh") +} + +/// Build the `/bin/cat` binary — concatenate files to stdout. +fn build_userspace_cat() -> R { + build_userspace_std_bin("base/cat/Cargo.toml", "cat") +} + +/// Build the `/bin/ls` binary — list directory contents. +fn build_userspace_ls() -> R { + build_userspace_std_bin("base/ls/Cargo.toml", "ls") +} + /// Generate a minimal stub dynamic-linker ELF for the #764 integration test. /// /// Produces an ET_DYN ELF64 with a single page-aligned PT_LOAD segment. @@ -1722,7 +1739,13 @@ fn run_with_root(opts: &BuildOpts, root_flag: Option<&str>, cmdline_extras: &[&s Some("ext2") => { let init_bin = build_userspace_init()?; let sh_bin = build_userspace_sh()?; - let extras: Vec<(&Path, &str)> = vec![(&sh_bin, "/bin/sh")]; + let cat_bin = build_userspace_cat()?; + let ls_bin = build_userspace_ls()?; + let extras: Vec<(&Path, &str)> = vec![ + (&sh_bin, "/bin/sh"), + (&cat_bin, "/bin/cat"), + (&ls_bin, "/bin/ls"), + ]; let img = ext2_image::build_with_extras(&workspace_root(), Some(&init_bin), &extras, true)?; println!("→ root=ext2: booting {}", img.display()); @@ -1738,7 +1761,13 @@ fn run_with_root(opts: &BuildOpts, root_flag: Option<&str>, cmdline_extras: &[&s None => { let init_bin = build_userspace_init()?; let sh_bin = build_userspace_sh()?; - let extras: Vec<(&Path, &str)> = vec![(&sh_bin, "/bin/sh")]; + let cat_bin = build_userspace_cat()?; + let ls_bin = build_userspace_ls()?; + let extras: Vec<(&Path, &str)> = vec![ + (&sh_bin, "/bin/sh"), + (&cat_bin, "/bin/cat"), + (&ls_bin, "/bin/ls"), + ]; let img = ext2_image::build_with_extras(&workspace_root(), Some(&init_bin), &extras, true)?; (img, Vec::new()) @@ -2079,7 +2108,13 @@ fn smoke(opts: &BuildOpts) -> R<()> { let kernel = build(opts)?; let userspace_init = build_userspace_init()?; let sh_bin = build_userspace_sh()?; - let extras: Vec<(&Path, &str)> = vec![(&sh_bin, "/bin/sh")]; + let cat_bin = build_userspace_cat()?; + let ls_bin = build_userspace_ls()?; + let extras: Vec<(&Path, &str)> = vec![ + (&sh_bin, "/bin/sh"), + (&cat_bin, "/bin/cat"), + (&ls_bin, "/bin/ls"), + ]; let disk = ext2_image::build_with_extras(&workspace_root(), Some(&userspace_init), &extras, true)?; let iso = workspace_root().join("target").join("vibix.iso"); @@ -2857,9 +2892,15 @@ fn sh_test(opts: &BuildOpts) -> R<()> { let kernel = build(opts)?; let init_bin = build_userspace_init()?; let sh_bin = build_userspace_sh()?; - - // Install init as /init and sh as /bin/sh in the ext2 rootfs image. - let extras: Vec<(&Path, &str)> = vec![(&sh_bin, "/bin/sh")]; + let cat_bin = build_userspace_cat()?; + let ls_bin = build_userspace_ls()?; + + // Install init as /init and sh/cat/ls as /bin/* in the ext2 rootfs image. + let extras: Vec<(&Path, &str)> = vec![ + (&sh_bin, "/bin/sh"), + (&cat_bin, "/bin/cat"), + (&ls_bin, "/bin/ls"), + ]; let disk = ext2_image::build_with_extras(&workspace_root(), Some(&init_bin), &extras, true)?; let iso = workspace_root().join("target").join("vibix-sh.iso"); make_iso_with_cmdline(&kernel, &iso, "iso_sh", "root=/dev/vda")?;