diff --git a/kernel/src/arch_impl/aarch64/elf.rs b/kernel/src/arch_impl/aarch64/elf.rs index 3a724b3b..84348590 100644 --- a/kernel/src/arch_impl/aarch64/elf.rs +++ b/kernel/src/arch_impl/aarch64/elf.rs @@ -477,6 +477,15 @@ fn load_segment_into_page_table( } } + // Calculate offset within the page where data should go. + // For the first page of a non-page-aligned segment (p_vaddr not aligned), + // data starts partway into the page. + let page_offset = if vaddr.as_u64() > page_vaddr.as_u64() { + (vaddr.as_u64() - page_vaddr.as_u64()) as usize + } else { + 0usize + }; + // Calculate which part of the file data maps to this page let page_file_offset = if page_vaddr.as_u64() >= vaddr.as_u64() { page_vaddr.as_u64() - vaddr.as_u64() @@ -484,24 +493,24 @@ fn load_segment_into_page_table( 0 }; + // Limit copy to the remaining space in this page to prevent buffer overflow. + // Without this, non-page-aligned segments (e.g., BusyBox data at 0x4005ff78) + // would write past the end of the physical frame, corrupting adjacent memory. + let bytes_available_in_page = (4096 - page_offset) as u64; let copy_start_in_file = page_file_offset; - let copy_end_in_file = core::cmp::min(page_file_offset + 4096, file_size as u64); + let copy_end_in_file = core::cmp::min( + page_file_offset + bytes_available_in_page, + file_size as u64, + ); if copy_start_in_file < file_size as u64 && copy_end_in_file > copy_start_in_file { let file_data_start = (file_start as u64 + copy_start_in_file) as usize; let copy_size = (copy_end_in_file - copy_start_in_file) as usize; - // Calculate offset within the page where data should go - let page_offset = if vaddr.as_u64() > page_vaddr.as_u64() { - vaddr.as_u64() - page_vaddr.as_u64() - } else { - 0 - }; - // Copy data using physical memory access (Linux-style approach) unsafe { let src = data.as_ptr().add(file_data_start); - let dst = phys_ptr.add(page_offset as usize); + let dst = phys_ptr.add(page_offset); core::ptr::copy_nonoverlapping(src, dst, copy_size); } diff --git a/kernel/src/elf.rs b/kernel/src/elf.rs index 3b0dff03..9bb2f2e7 100644 --- a/kernel/src/elf.rs +++ b/kernel/src/elf.rs @@ -523,6 +523,15 @@ fn load_segment_into_page_table( // Copy data if this page overlaps with file data let page_start_vaddr = page.start_address(); + // Calculate offset within the page where data should go. + // For the first page of a non-page-aligned segment (p_vaddr not aligned), + // data starts partway into the page. + let page_offset = if vaddr > page_start_vaddr { + (vaddr.as_u64() - page_start_vaddr.as_u64()) as usize + } else { + 0usize + }; + // Calculate which part of the file data maps to this page let page_file_offset = if page_start_vaddr >= vaddr { page_start_vaddr.as_u64() - vaddr.as_u64() @@ -530,24 +539,24 @@ fn load_segment_into_page_table( 0 }; + // Limit copy to the remaining space in this page to prevent buffer overflow. + // Without this, non-page-aligned segments (e.g., BusyBox data at 0x4005ff78) + // would write past the end of the physical frame, corrupting adjacent memory. + let bytes_available_in_page = (4096 - page_offset) as u64; let copy_start_in_file = page_file_offset; - let copy_end_in_file = core::cmp::min(page_file_offset + 4096, file_size as u64); + let copy_end_in_file = core::cmp::min( + page_file_offset + bytes_available_in_page, + file_size as u64, + ); if copy_start_in_file < file_size as u64 && copy_end_in_file > copy_start_in_file { let file_data_start = (file_start as u64 + copy_start_in_file) as usize; let copy_size = (copy_end_in_file - copy_start_in_file) as usize; - // Calculate offset within the page where data should go - let page_offset = if vaddr > page_start_vaddr { - vaddr.as_u64() - page_start_vaddr.as_u64() - } else { - 0 - }; - // Copy using physical memory access (Linux-style approach) unsafe { let src = data.as_ptr().add(file_data_start); - let dst = phys_ptr.add(page_offset as usize); + let dst = phys_ptr.add(page_offset); core::ptr::copy_nonoverlapping(src, dst, copy_size); } diff --git a/kernel/src/syscall/fs.rs b/kernel/src/syscall/fs.rs index 624fdf56..118655e6 100644 --- a/kernel/src/syscall/fs.rs +++ b/kernel/src/syscall/fs.rs @@ -93,8 +93,14 @@ pub const S_IFDIR: u32 = 0o040000; // Directory pub const S_IFCHR: u32 = 0o020000; // Character device pub const S_IFIFO: u32 = 0o010000; // FIFO (pipe) -/// stat structure (Linux compatible) -/// Note: The layout is the same for x86_64 and aarch64 Linux. +/// stat structure (Linux x86_64 compatible - 144 bytes) +/// +/// x86_64 and aarch64 Linux have different struct stat layouts. +/// x86_64 uses the layout from arch/x86/include/uapi/asm/stat.h (144 bytes). +/// aarch64 uses the generic layout from include/uapi/asm-generic/stat.h (128 bytes). +/// Key differences: field order (nlink/mode swapped), nlink width (u64 vs u32), +/// blksize width (i64 vs i32), and trailing reserved space (24 vs 8 bytes). +#[cfg(target_arch = "x86_64")] #[repr(C)] pub struct Stat { pub st_dev: u64, @@ -117,6 +123,10 @@ pub struct Stat { _reserved: [i64; 3], } +#[cfg(target_arch = "x86_64")] +const _: () = assert!(core::mem::size_of::() == 144); + +#[cfg(target_arch = "x86_64")] impl Stat { /// Create a zeroed Stat structure pub fn zeroed() -> Self { @@ -143,6 +153,64 @@ impl Stat { } } +/// stat structure (Linux aarch64 compatible - 128 bytes) +/// +/// Layout from include/uapi/asm-generic/stat.h used by aarch64 Linux. +#[cfg(target_arch = "aarch64")] +#[repr(C)] +pub struct Stat { + pub st_dev: u64, + pub st_ino: u64, + pub st_mode: u32, + pub st_nlink: u32, + pub st_uid: u32, + pub st_gid: u32, + pub st_rdev: u64, + _pad1: u64, + pub st_size: i64, + pub st_blksize: i32, + _pad2: i32, + pub st_blocks: i64, + pub st_atime: i64, + pub st_atime_nsec: i64, + pub st_mtime: i64, + pub st_mtime_nsec: i64, + pub st_ctime: i64, + pub st_ctime_nsec: i64, + _reserved: [u32; 2], +} + +#[cfg(target_arch = "aarch64")] +const _: () = assert!(core::mem::size_of::() == 128); + +#[cfg(target_arch = "aarch64")] +impl Stat { + /// Create a zeroed Stat structure + pub fn zeroed() -> Self { + Self { + st_dev: 0, + st_ino: 0, + st_mode: 0, + st_nlink: 0, + st_uid: 0, + st_gid: 0, + st_rdev: 0, + _pad1: 0, + st_size: 0, + st_blksize: 0, + _pad2: 0, + st_blocks: 0, + st_atime: 0, + st_atime_nsec: 0, + st_mtime: 0, + st_mtime_nsec: 0, + st_ctime: 0, + st_ctime_nsec: 0, + _reserved: [0; 2], + } + } +} + /// sys_open - Open a file or directory /// /// Helper: sys_open write path (O_CREAT/O_TRUNC) — works on any Ext2Fs instance. @@ -696,7 +764,7 @@ pub fn sys_fstat(fd: i32, statbuf: u64) -> SyscallResult { stat.st_uid = inode_stat.uid; stat.st_gid = inode_stat.gid; stat.st_size = inode_stat.size; - stat.st_nlink = inode_stat.nlink; + stat.st_nlink = inode_stat.nlink as _; stat.st_atime = inode_stat.atime; stat.st_mtime = inode_stat.mtime; stat.st_ctime = inode_stat.ctime; @@ -716,7 +784,7 @@ pub fn sys_fstat(fd: i32, statbuf: u64) -> SyscallResult { stat.st_uid = inode_stat.uid; stat.st_gid = inode_stat.gid; stat.st_size = inode_stat.size; - stat.st_nlink = inode_stat.nlink; + stat.st_nlink = inode_stat.nlink as _; stat.st_atime = inode_stat.atime; stat.st_mtime = inode_stat.mtime; stat.st_ctime = inode_stat.ctime; @@ -3362,7 +3430,7 @@ pub fn sys_newfstatat(dirfd: i32, pathname: u64, statbuf: u64, _flags: u32) -> S stat.st_uid = inode_stat.uid; stat.st_gid = inode_stat.gid; stat.st_size = inode_stat.size; - stat.st_nlink = inode_stat.nlink; + stat.st_nlink = inode_stat.nlink as _; stat.st_atime = inode_stat.atime; stat.st_mtime = inode_stat.mtime; stat.st_ctime = inode_stat.ctime; diff --git a/kernel/src/test_exec.rs b/kernel/src/test_exec.rs index 99045a43..76c2d6f1 100644 --- a/kernel/src/test_exec.rs +++ b/kernel/src/test_exec.rs @@ -3000,11 +3000,11 @@ pub fn test_shell_pipe() { } } -/// Test the `btrue` coreutil +/// Test the `true` coreutil /// -/// Verifies that /bin/btrue correctly exits with code 0. +/// Verifies that /sbin/true correctly exits with code 0. pub fn test_true_coreutil() { - log::info!("Testing btrue coreutil (exit code 0)"); + log::info!("Testing true coreutil (exit code 0)"); #[cfg(feature = "testing")] let true_test_elf_buf = crate::userspace_test::get_test_binary("true_test"); @@ -3033,11 +3033,11 @@ pub fn test_true_coreutil() { } } -/// Test the `bfalse` coreutil +/// Test the `false` coreutil /// -/// Verifies that /bin/bfalse correctly exits with code 1. +/// Verifies that /bin/false correctly exits with code 1. pub fn test_false_coreutil() { - log::info!("Testing bfalse coreutil (exit code 1)"); + log::info!("Testing false coreutil (exit code 1)"); #[cfg(feature = "testing")] let false_test_elf_buf = crate::userspace_test::get_test_binary("false_test"); @@ -3066,11 +3066,11 @@ pub fn test_false_coreutil() { } } -/// Test the `bhead` coreutil +/// Test the `head` coreutil /// -/// Verifies that /bin/bhead correctly outputs the first N lines of files. +/// Verifies that /bin/head correctly outputs the first N lines of files. pub fn test_head_coreutil() { - log::info!("Testing bhead coreutil (first N lines)"); + log::info!("Testing head coreutil (first N lines)"); #[cfg(feature = "testing")] let head_test_elf_buf = crate::userspace_test::get_test_binary("head_test"); @@ -3099,11 +3099,11 @@ pub fn test_head_coreutil() { } } -/// Test the `btail` coreutil +/// Test the `tail` coreutil /// -/// Verifies that /bin/btail correctly outputs the last N lines of files. +/// Verifies that /bin/tail correctly outputs the last N lines of files. pub fn test_tail_coreutil() { - log::info!("Testing btail coreutil (last N lines)"); + log::info!("Testing tail coreutil (last N lines)"); #[cfg(feature = "testing")] let tail_test_elf_buf = crate::userspace_test::get_test_binary("tail_test"); @@ -3132,11 +3132,11 @@ pub fn test_tail_coreutil() { } } -/// Test the `bwc` coreutil +/// Test the `wc` coreutil /// -/// Verifies that /bin/bwc correctly counts lines, words, and bytes. +/// Verifies that /bin/wc correctly counts lines, words, and bytes. pub fn test_wc_coreutil() { - log::info!("Testing bwc coreutil (line/word/byte counts)"); + log::info!("Testing wc coreutil (line/word/byte counts)"); #[cfg(feature = "testing")] let wc_test_elf_buf = crate::userspace_test::get_test_binary("wc_test"); @@ -3165,11 +3165,11 @@ pub fn test_wc_coreutil() { } } -/// Test the `bwhich` coreutil +/// Test the `which` coreutil /// -/// Verifies that /bin/bwhich correctly locates commands in PATH. +/// Verifies that /bin/which correctly locates commands in PATH. pub fn test_which_coreutil() { - log::info!("Testing bwhich coreutil (command location)"); + log::info!("Testing which coreutil (command location)"); #[cfg(feature = "testing")] let which_test_elf_buf = crate::userspace_test::get_test_binary("which_test"); @@ -3198,11 +3198,11 @@ pub fn test_which_coreutil() { } } -/// Test the `bcat` coreutil +/// Test the `cat` coreutil /// -/// Verifies that /bin/bcat correctly outputs file contents. +/// Verifies that /bin/cat correctly outputs file contents. pub fn test_cat_coreutil() { - log::info!("Testing bcat coreutil (file concatenation)"); + log::info!("Testing cat coreutil (file concatenation)"); #[cfg(feature = "testing")] let cat_test_elf_buf = crate::userspace_test::get_test_binary("cat_test"); @@ -3231,11 +3231,11 @@ pub fn test_cat_coreutil() { } } -/// Test the `bls` coreutil +/// Test the `ls` coreutil /// -/// Verifies that /bin/bls correctly lists directory contents. +/// Verifies that /bin/ls correctly lists directory contents. pub fn test_ls_coreutil() { - log::info!("Testing bls coreutil (directory listing)"); + log::info!("Testing ls coreutil (directory listing)"); #[cfg(feature = "testing")] let ls_test_elf_buf = crate::userspace_test::get_test_binary("ls_test"); diff --git a/scripts/build-busybox.sh b/scripts/build-busybox.sh new file mode 100755 index 00000000..5e633de3 --- /dev/null +++ b/scripts/build-busybox.sh @@ -0,0 +1,165 @@ +#!/bin/bash +# Build BusyBox for Breenix (x86_64 and aarch64) +# +# Cross-compiles BusyBox 1.37.0 as a static musl binary for the target +# architecture, placing the text segment at 0x40000000 for Breenix userspace. +# +# Prerequisites: +# brew tap filosottile/musl-cross +# brew install musl-cross # x86_64-linux-musl-gcc +# brew install musl-cross --with-aarch64 # aarch64-linux-musl-gcc +# +# Usage: +# ./scripts/build-busybox.sh # Build for x86_64 (default) +# ./scripts/build-busybox.sh --arch aarch64 # Build for aarch64 +# ./scripts/build-busybox.sh --arch x86_64 # Build for x86_64 + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +BUSYBOX_DIR="$PROJECT_ROOT/third-party/busybox-1.37.0" +FRAGMENT="$PROJECT_ROOT/third-party/busybox-breenix-fragment.config" + +ARCH="x86_64" +while [[ $# -gt 0 ]]; do + case "$1" in + --arch) + ARCH="$2" + shift 2 + ;; + *) + echo "Usage: $0 [--arch x86_64|aarch64]" + exit 1 + ;; + esac +done + +case "$ARCH" in + x86_64) + CROSS_PREFIX="x86_64-linux-musl-" + OUTPUT_DIR="$PROJECT_ROOT/userspace/programs" + ;; + aarch64) + CROSS_PREFIX="aarch64-linux-musl-" + OUTPUT_DIR="$PROJECT_ROOT/userspace/programs/aarch64" + ;; + *) + echo "Error: unsupported arch '$ARCH' (use x86_64 or aarch64)" + exit 1 + ;; +esac + +# Verify cross-compiler is available +if ! command -v "${CROSS_PREFIX}gcc" &>/dev/null; then + echo "Error: ${CROSS_PREFIX}gcc not found in PATH" + echo "" + echo "Install with:" + echo " brew tap filosottile/musl-cross" + if [[ "$ARCH" == "x86_64" ]]; then + echo " brew install musl-cross" + else + echo " brew install musl-cross --with-aarch64" + fi + exit 1 +fi + +if [[ ! -d "$BUSYBOX_DIR" ]]; then + echo "Error: BusyBox source not found at $BUSYBOX_DIR" + echo "Download with:" + echo " cd third-party" + echo " curl -LO https://busybox.net/downloads/busybox-1.37.0.tar.bz2" + echo " tar xjf busybox-1.37.0.tar.bz2" + exit 1 +fi + +echo "Building BusyBox for Breenix ($ARCH)" +echo " Cross prefix: $CROSS_PREFIX" +echo " Output: $OUTPUT_DIR/busybox.elf" +echo "" + +cd "$BUSYBOX_DIR" + +# Start from allnoconfig +make allnoconfig >/dev/null 2>&1 + +# Apply fragment config using Python for reliable cross-platform text processing +python3 -c " +import re, sys + +# Read the current config +with open('.config', 'r') as f: + config = f.read() + +# Read the fragment +with open('$FRAGMENT', 'r') as f: + fragment = f.read() + +# Parse fragment for CONFIG_FOO=value lines +for line in fragment.splitlines(): + line = line.strip() + if not line or line.startswith('#'): + continue + m = re.match(r'^(CONFIG_[A-Z0-9_]+)=(.*)$', line) + if m: + key, value = m.group(1), m.group(2) + # Replace '# CONFIG_FOO is not set' with 'CONFIG_FOO=value' + pattern = f'# {key} is not set' + replacement = f'{key}={value}' + if pattern in config: + config = config.replace(pattern, replacement) + elif re.search(f'^{re.escape(key)}=', config, re.MULTILINE): + config = re.sub(f'^{re.escape(key)}=.*$', replacement, config, flags=re.MULTILINE) + else: + config += replacement + '\n' + +# Override cross compiler prefix for target arch +config = re.sub( + r'^CONFIG_CROSS_COMPILER_PREFIX=.*$', + 'CONFIG_CROSS_COMPILER_PREFIX=\"$CROSS_PREFIX\"', + config, + flags=re.MULTILINE +) + +with open('.config', 'w') as f: + f.write(config) + +print('Config fragment applied successfully') +" + +# Let Kconfig resolve dependencies (accept defaults for any new prompts) +yes "" | make oldconfig >/dev/null 2>&1 + +echo "Configuration applied. Building..." +echo "" + +# Build with parallel jobs +NPROC=$(sysctl -n hw.ncpu 2>/dev/null || nproc 2>/dev/null || echo 4) +make -j"$NPROC" 2>&1 + +# Verify the binary +if [[ ! -f busybox ]]; then + echo "Error: BusyBox build failed - no busybox binary produced" + exit 1 +fi + +# Check it's static +if file busybox | grep -q "dynamically linked"; then + echo "Warning: BusyBox is dynamically linked (expected static)" +fi + +echo "" +echo "BusyBox built successfully:" +file busybox +ls -lh busybox + +# Copy to output directory +mkdir -p "$OUTPUT_DIR" +cp busybox "$OUTPUT_DIR/busybox.elf" +echo "" +echo "Installed: $OUTPUT_DIR/busybox.elf" + +# Show enabled applets +echo "" +echo "Enabled applets:" +./busybox --list 2>/dev/null | head -60 || true diff --git a/scripts/create_ext2_disk.sh b/scripts/create_ext2_disk.sh index 95d7a3fd..7f763f08 100755 --- a/scripts/create_ext2_disk.sh +++ b/scripts/create_ext2_disk.sh @@ -3,9 +3,9 @@ # # This script creates an ext2 filesystem image (64MB default) with: # - Test files for filesystem testing -# - Coreutils binaries in /bin/ (bcat, bls, becho, bmkdir, brmdir, brm, bcp, bmv, bfalse, bhead, btail, bwc, bwhich) -# - /sbin/btrue for PATH order testing +# - BusyBox binary in /bin/busybox with symlinks for coreutils # - hello_world binary for exec testing +# - Test binaries in /usr/local/test/bin # # Requires Docker on macOS (or mke2fs on Linux). # @@ -53,14 +53,10 @@ else TESTDATA_FILE="$PROJECT_ROOT/testdata/ext2.img" fi -# Coreutils to install in /bin (b-prefixed Breenix coreutils) -COREUTILS="bcat bls becho bmkdir brmdir brm bcp bmv btrue bfalse bhead btail bwc bwhich" - echo "Creating ext2 disk image..." echo " Arch: $ARCH" echo " Output: $OUTPUT_FILE" echo " Size: ${SIZE_MB}MB" -echo " Coreutils: $COREUTILS" # Ensure target directory exists mkdir -p "$TARGET_DIR" @@ -111,12 +107,37 @@ if [[ "$(uname)" == "Darwin" ]]; then mkdir -p /mnt/ext2/usr/local/test/bin mkdir -p /mnt/ext2/usr/local/cbin - # Copy ALL binaries from /binaries directory + # Install BusyBox with symlinks for coreutils + if [ -f /binaries/busybox.elf ]; then + cp /binaries/busybox.elf /mnt/ext2/bin/busybox + chmod 755 /mnt/ext2/bin/busybox + + # Create hardlinks for all applets in /bin + # (hardlinks avoid needing symlink-following in kernel exec path) + for cmd in cat ls head tail wc grep more cp mv rm mkdir rmdir \ + echo which sh ash sed awk find sort uniq tee xargs \ + chmod chown chgrp df du free date sleep test expr seq \ + id whoami hostname basename dirname env printf cut tr \ + od hexdump md5sum sha256sum vi; do + ln /mnt/ext2/bin/busybox /mnt/ext2/bin/$cmd + done + + # /sbin applets (hardlinks) + for cmd in true false; do + ln /mnt/ext2/bin/busybox /mnt/ext2/sbin/$cmd + done + + echo " Installed BusyBox with hardlinks in /bin and /sbin" + else + echo " WARNING: busybox.elf not found, skipping coreutils" + fi + + # Copy remaining binaries from /binaries directory # Routing: musl C programs (*_musl*) -> /usr/local/cbin # test binaries (*_test, test_*) -> /usr/local/test/bin - # system binaries (true, telnetd, init) -> /sbin + # system binaries (telnetd, init, blogd) -> /sbin # everything else -> /bin - echo "Installing all binaries..." + echo "Installing other binaries..." bin_count=0 sbin_count=0 test_count=0 @@ -124,6 +145,8 @@ if [[ "$(uname)" == "Darwin" ]]; then for elf_file in /binaries/*.elf; do if [ -f "$elf_file" ]; then bin_name=$(basename "$elf_file" .elf) + # Skip busybox (already installed above) + [ "$bin_name" = "busybox" ] && continue if echo "$bin_name" | grep -qE "_musl"; then cp "$elf_file" /mnt/ext2/usr/local/cbin/${bin_name} chmod 755 /mnt/ext2/usr/local/cbin/${bin_name} @@ -132,7 +155,7 @@ if [[ "$(uname)" == "Darwin" ]]; then cp "$elf_file" /mnt/ext2/usr/local/test/bin/${bin_name} chmod 755 /mnt/ext2/usr/local/test/bin/${bin_name} test_count=$((test_count + 1)) - elif [ "$bin_name" = "btrue" ] || [ "$bin_name" = "telnetd" ] || [ "$bin_name" = "init" ] || [ "$bin_name" = "blogd" ]; then + elif [ "$bin_name" = "telnetd" ] || [ "$bin_name" = "init" ] || [ "$bin_name" = "blogd" ]; then cp "$elf_file" /mnt/ext2/sbin/${bin_name} chmod 755 /mnt/ext2/sbin/${bin_name} sbin_count=$((sbin_count + 1)) @@ -266,12 +289,36 @@ else mkdir -p "$MOUNT_DIR/usr/local/test/bin" mkdir -p "$MOUNT_DIR/usr/local/cbin" - # Copy ALL binaries from userspace directory + # Install BusyBox with hardlinks for coreutils + if [[ -f "$USERSPACE_DIR/busybox.elf" ]]; then + cp "$USERSPACE_DIR/busybox.elf" "$MOUNT_DIR/bin/busybox" + chmod 755 "$MOUNT_DIR/bin/busybox" + + # Create hardlinks for all applets in /bin + for cmd in cat ls head tail wc grep more cp mv rm mkdir rmdir \ + echo which sh ash sed awk find sort uniq tee xargs \ + chmod chown chgrp df du free date sleep test expr seq \ + id whoami hostname basename dirname env printf cut tr \ + od hexdump md5sum sha256sum vi; do + ln "$MOUNT_DIR/bin/busybox" "$MOUNT_DIR/bin/$cmd" + done + + # /sbin applets (hardlinks) + for cmd in true false; do + ln "$MOUNT_DIR/bin/busybox" "$MOUNT_DIR/sbin/$cmd" + done + + echo " Installed BusyBox with hardlinks in /bin and /sbin" + else + echo " WARNING: busybox.elf not found, skipping coreutils" + fi + + # Copy remaining binaries from userspace directory # Routing: musl C programs (*_musl*) -> /usr/local/cbin # test binaries (*_test, test_*) -> /usr/local/test/bin - # system binaries (true, telnetd, init) -> /sbin + # system binaries (telnetd, init, blogd) -> /sbin # everything else -> /bin - echo "Installing all binaries..." + echo "Installing other binaries..." bin_count=0 sbin_count=0 test_count=0 @@ -279,6 +326,8 @@ else for elf_file in "$USERSPACE_DIR"/*.elf; do if [ -f "$elf_file" ]; then bin_name=$(basename "$elf_file" .elf) + # Skip busybox (already installed above) + [ "$bin_name" = "busybox" ] && continue if echo "$bin_name" | grep -qE '_musl'; then cp "$elf_file" "$MOUNT_DIR/usr/local/cbin/${bin_name}" chmod 755 "$MOUNT_DIR/usr/local/cbin/${bin_name}" @@ -287,7 +336,7 @@ else cp "$elf_file" "$MOUNT_DIR/usr/local/test/bin/${bin_name}" chmod 755 "$MOUNT_DIR/usr/local/test/bin/${bin_name}" test_count=$((test_count + 1)) - elif [ "$bin_name" = "btrue" ] || [ "$bin_name" = "telnetd" ] || [ "$bin_name" = "init" ] || [ "$bin_name" = "blogd" ]; then + elif [ "$bin_name" = "telnetd" ] || [ "$bin_name" = "init" ] || [ "$bin_name" = "blogd" ]; then cp "$elf_file" "$MOUNT_DIR/sbin/${bin_name}" chmod 755 "$MOUNT_DIR/sbin/${bin_name}" sbin_count=$((sbin_count + 1)) @@ -398,9 +447,11 @@ if [[ -f "$OUTPUT_FILE" ]]; then echo " Size: $SIZE" echo "" echo "Contents:" - echo " /bin/* - Userspace binaries (coreutils, demos)" + echo " /bin/busybox - BusyBox multi-call binary" + echo " /bin/{cat,ls,head,...} - BusyBox hardlinks" + echo " /sbin/{true,false} - BusyBox hardlinks" + echo " /bin/* - Other userspace binaries (demos)" echo " /usr/local/test/bin/* - Test binaries (*_test, test_*)" - echo " /sbin/btrue - exit status coreutil (for PATH testing)" echo " /sbin/telnetd - telnet daemon" echo " /hello.txt - test file (1 line)" echo " /lines.txt - multi-line test file (15 lines) for head/tail/wc" diff --git a/userspace/programs/Cargo.toml b/userspace/programs/Cargo.toml index 531df955..7e756f58 100644 --- a/userspace/programs/Cargo.toml +++ b/userspace/programs/Cargo.toml @@ -238,63 +238,6 @@ path = "src/cow_readonly_test.rs" name = "cow_signal_test" path = "src/cow_signal_test.rs" -# Phase 3: Coreutils (b-prefixed to avoid collision with GNU coreutils) -[[bin]] -name = "btrue" -path = "src/btrue.rs" - -[[bin]] -name = "bfalse" -path = "src/bfalse.rs" - -[[bin]] -name = "becho" -path = "src/becho.rs" - -[[bin]] -name = "bcat" -path = "src/bcat.rs" - -[[bin]] -name = "bhead" -path = "src/bhead.rs" - -[[bin]] -name = "btail" -path = "src/btail.rs" - -[[bin]] -name = "bwc" -path = "src/bwc.rs" - -[[bin]] -name = "bwhich" -path = "src/bwhich.rs" - -[[bin]] -name = "bls" -path = "src/bls.rs" - -[[bin]] -name = "bmkdir" -path = "src/bmkdir.rs" - -[[bin]] -name = "brmdir" -path = "src/brmdir.rs" - -[[bin]] -name = "brm" -path = "src/brm.rs" - -[[bin]] -name = "bcp" -path = "src/bcp.rs" - -[[bin]] -name = "bmv" -path = "src/bmv.rs" - [[bin]] name = "resolution" path = "src/resolution.rs" diff --git a/userspace/programs/build.sh b/userspace/programs/build.sh index e8995d30..8506a3f8 100755 --- a/userspace/programs/build.sh +++ b/userspace/programs/build.sh @@ -167,21 +167,7 @@ STD_BINARIES=( "cow_readonly_test" "cow_signal_test" - # Phase 3: Coreutils (b-prefixed) - "btrue" - "bfalse" - "becho" - "bcat" - "bhead" - "btail" - "bwc" - "bwhich" - "bls" - "bmkdir" - "brmdir" - "brm" - "bcp" - "bmv" + # Phase 3: Coreutils now provided by BusyBox (see scripts/build-busybox.sh) "resolution" # Phase 4: init_shell diff --git a/userspace/programs/src/bcat.rs b/userspace/programs/src/bcat.rs deleted file mode 100644 index d8674296..00000000 --- a/userspace/programs/src/bcat.rs +++ /dev/null @@ -1,57 +0,0 @@ -//! cat - concatenate and print files -//! -//! Usage: cat [FILE...] -//! -//! Reads FILE(s) and prints their contents to stdout. -//! If no FILE is specified, reads from stdin. - -use std::env; -use std::fs; -use std::io::{self, Read, Write}; -use std::process; - -fn cat_stdin() -> io::Result<()> { - let mut buf = [0u8; 4096]; - let stdin = io::stdin(); - let mut handle = stdin.lock(); - let mut stdout = io::stdout().lock(); - loop { - let n = handle.read(&mut buf)?; - if n == 0 { - break; - } - stdout.write_all(&buf[..n])?; - } - Ok(()) -} - -fn cat_file(path: &str) -> io::Result<()> { - let contents = fs::read(path)?; - let mut stdout = io::stdout().lock(); - stdout.write_all(&contents)?; - Ok(()) -} - -fn main() { - let args: Vec = env::args().collect(); - - // If no arguments (besides program name), read from stdin - if args.len() < 2 { - if let Err(e) = cat_stdin() { - eprintln!("cat: error reading stdin: {}", e); - process::exit(1); - } - return; - } - - // Process each file argument - let mut exit_code = 0; - for path in &args[1..] { - if let Err(e) = cat_file(path) { - eprintln!("cat: {}: {}", path, e); - exit_code = 1; - } - } - - process::exit(exit_code); -} diff --git a/userspace/programs/src/bcp.rs b/userspace/programs/src/bcp.rs deleted file mode 100644 index 1056d55c..00000000 --- a/userspace/programs/src/bcp.rs +++ /dev/null @@ -1,35 +0,0 @@ -//! cp - copy files -//! -//! Usage: cp SOURCE DEST -//! -//! Copies SOURCE to DEST. Does not support recursive directory copy. - -use std::env; -use std::fs; -use std::process; - -fn print_usage() { - eprintln!("Usage: cp SOURCE DEST"); -} - -fn main() { - let args: Vec = env::args().collect(); - - if args.len() < 3 { - print_usage(); - process::exit(1); - } - - let src = &args[1]; - let dst = &args[2]; - - match fs::copy(src, dst) { - Ok(_) => { - println!("cp: file copied"); - } - Err(e) => { - eprintln!("cp: {}: {}", src, e); - process::exit(1); - } - } -} diff --git a/userspace/programs/src/becho.rs b/userspace/programs/src/becho.rs deleted file mode 100644 index b6a1c090..00000000 --- a/userspace/programs/src/becho.rs +++ /dev/null @@ -1,10 +0,0 @@ -//! echo - print arguments to stdout -//! -//! Usage: echo [STRING]... -//! -//! Prints the arguments separated by spaces, followed by a newline. - -fn main() { - let args: Vec = std::env::args().skip(1).collect(); - println!("{}", args.join(" ")); -} diff --git a/userspace/programs/src/bfalse.rs b/userspace/programs/src/bfalse.rs deleted file mode 100644 index 21c6202f..00000000 --- a/userspace/programs/src/bfalse.rs +++ /dev/null @@ -1,10 +0,0 @@ -//! false - return unsuccessful exit status -//! -//! Usage: false -//! -//! Exit with a status code indicating failure (1). -//! This command does nothing and always fails. - -fn main() { - std::process::exit(1); -} diff --git a/userspace/programs/src/bhead.rs b/userspace/programs/src/bhead.rs deleted file mode 100644 index 4f56994f..00000000 --- a/userspace/programs/src/bhead.rs +++ /dev/null @@ -1,129 +0,0 @@ -//! head - output the first part of files -//! -//! Usage: head [-n NUM] [FILE...] -//! -//! Print the first 10 lines of each FILE to standard output. -//! With more than one FILE, precede each with a header giving the file name. -//! -//! Options: -//! -n NUM print the first NUM lines instead of the first 10 - -use std::env; -use std::fs::File; -use std::io::{self, BufRead, BufReader, Write}; -use std::process; - -const DEFAULT_LINES: usize = 10; - -/// Read and output up to `max_lines` lines from a reader -fn head_from_reader(reader: R, max_lines: usize) -> io::Result<()> { - if max_lines == 0 { - return Ok(()); - } - - let stdout = io::stdout(); - let mut out = stdout.lock(); - let mut lines_output = 0; - - for line_result in reader.lines() { - let line = line_result?; - writeln!(out, "{}", line)?; - lines_output += 1; - if lines_output >= max_lines { - break; - } - } - - Ok(()) -} - -/// Read and output up to `max_lines` lines from stdin -fn head_stdin(max_lines: usize) -> io::Result<()> { - let stdin = io::stdin(); - let reader = stdin.lock(); - head_from_reader(reader, max_lines) -} - -/// Read and output up to `max_lines` lines from a file -fn head_file(path: &str, max_lines: usize) -> io::Result<()> { - let file = File::open(path)?; - let reader = BufReader::new(file); - head_from_reader(reader, max_lines) -} - -fn print_header(path: &str) { - println!("==> {} <==", path); -} - -fn main() { - let args: Vec = env::args().collect(); - - let mut max_lines = DEFAULT_LINES; - let mut file_start_idx = 1usize; - - // Parse -n option - if args.len() >= 2 { - let arg = &args[1]; - if arg.len() >= 2 && arg.starts_with("-n") { - if arg.len() > 2 { - // -nNUM format - match arg[2..].parse::() { - Ok(n) => { - max_lines = n; - file_start_idx = 2; - } - Err(_) => { - eprintln!("head: invalid number of lines"); - process::exit(1); - } - } - } else if args.len() >= 3 { - // -n NUM format - match args[2].parse::() { - Ok(n) => { - max_lines = n; - file_start_idx = 3; - } - Err(_) => { - eprintln!("head: invalid number of lines"); - process::exit(1); - } - } - } - } - } - - // If no files, read from stdin - if file_start_idx >= args.len() { - if let Err(_) = head_stdin(max_lines) { - eprintln!("head: error reading stdin"); - process::exit(1); - } - process::exit(0); - } - - // Process files - let mut exit_code = 0; - let file_count = args.len() - file_start_idx; - let mut first_file = true; - - for i in file_start_idx..args.len() { - let path = &args[i]; - - // Print header if multiple files - if file_count > 1 { - if !first_file { - println!(); - } - print_header(path); - } - first_file = false; - - if let Err(e) = head_file(path, max_lines) { - eprintln!("head: {}: {}", path, e); - exit_code = 1; - } - } - - process::exit(exit_code); -} diff --git a/userspace/programs/src/bls.rs b/userspace/programs/src/bls.rs deleted file mode 100644 index cbfc11a5..00000000 --- a/userspace/programs/src/bls.rs +++ /dev/null @@ -1,116 +0,0 @@ -//! bls - list directory contents -//! -//! Usage: bls [-l] [DIRECTORY] -//! -//! Lists entries in DIRECTORY (default: current directory). -//! Shows file type indicators: / for directories, @ for symlinks. -//! Entries are sorted alphabetically by name. -//! -//! Options: -//! -l Long format: show file size and type - -use std::env; -use std::fs; -use std::process; - -/// A collected directory entry -struct Entry { - name: String, - display: String, - size: u64, - is_dir: bool, -} - -fn format_size(size: u64) -> String { - if size < 1024 { - format!("{}", size) - } else if size < 1024 * 1024 { - format!("{}K", size / 1024) - } else { - format!("{}M", size / (1024 * 1024)) - } -} - -fn ls_directory(path: &str, long: bool) -> Result<(), String> { - let dir = fs::read_dir(path).map_err(|e| { - format!("bls: cannot access '{}': {}", path, e) - })?; - - let mut entries: Vec = Vec::new(); - - for entry in dir { - let entry = entry.map_err(|e| { - format!("bls: error reading entry: {}", e) - })?; - - let name = entry.file_name(); - let name_str = name.to_string_lossy().into_owned(); - - // Skip . and .. - if name_str == "." || name_str == ".." { - continue; - } - - let file_type = entry.file_type().map_err(|e| { - format!("bls: error reading file type: {}", e) - })?; - - let is_dir = file_type.is_dir(); - - let display = if is_dir { - format!("{}/", name_str) - } else if file_type.is_symlink() { - format!("{}@", name_str) - } else { - name_str.clone() - }; - - let size = if long { - entry.metadata().map(|m| m.len()).unwrap_or(0) - } else { - 0 - }; - - entries.push(Entry { name: name_str, display, size, is_dir }); - } - - // Sort alphabetically by name - entries.sort_by(|a, b| a.name.cmp(&b.name)); - - if long { - // Find max size width for alignment - let max_size = entries.iter().map(|e| format_size(e.size).len()).max().unwrap_or(0); - - for entry in &entries { - let type_char = if entry.is_dir { 'd' } else { '-' }; - let size_str = format_size(entry.size); - println!("{} {:>width$} {}", type_char, size_str, entry.display, width = max_size); - } - } else { - for entry in &entries { - println!("{}", entry.display); - } - } - - Ok(()) -} - -fn main() { - let args: Vec = env::args().collect(); - - let mut long = false; - let mut path = "."; - - for arg in args.iter().skip(1) { - if arg == "-l" { - long = true; - } else { - path = arg; - } - } - - if let Err(e) = ls_directory(path, long) { - eprintln!("{}", e); - process::exit(1); - } -} diff --git a/userspace/programs/src/bmkdir.rs b/userspace/programs/src/bmkdir.rs deleted file mode 100644 index b352bd24..00000000 --- a/userspace/programs/src/bmkdir.rs +++ /dev/null @@ -1,30 +0,0 @@ -//! mkdir - create directories -//! -//! Usage: mkdir DIRECTORY -//! -//! Creates the specified directory. - -use std::env; -use std::fs; -use std::process; - -fn main() { - let args: Vec = env::args().collect(); - - if args.len() < 2 { - eprintln!("Usage: mkdir DIRECTORY"); - process::exit(1); - } - - let path = &args[1]; - - match fs::create_dir(path) { - Ok(()) => { - println!("mkdir: directory created"); - } - Err(e) => { - eprintln!("mkdir: cannot create directory '{}': {}", path, e); - process::exit(1); - } - } -} diff --git a/userspace/programs/src/bmv.rs b/userspace/programs/src/bmv.rs deleted file mode 100644 index 5e840af5..00000000 --- a/userspace/programs/src/bmv.rs +++ /dev/null @@ -1,31 +0,0 @@ -//! mv - move (rename) files -//! -//! Usage: mv SOURCE DEST -//! -//! Renames SOURCE to DEST. - -use std::env; -use std::fs; -use std::process; - -fn main() { - let args: Vec = env::args().collect(); - - if args.len() < 3 { - eprintln!("Usage: mv SOURCE DEST"); - process::exit(1); - } - - let src = &args[1]; - let dst = &args[2]; - - match fs::rename(src, dst) { - Ok(()) => { - println!("mv: file moved"); - } - Err(e) => { - eprintln!("mv: cannot move '{}' to '{}': {}", src, dst, e); - process::exit(1); - } - } -} diff --git a/userspace/programs/src/brm.rs b/userspace/programs/src/brm.rs deleted file mode 100644 index 44ab5449..00000000 --- a/userspace/programs/src/brm.rs +++ /dev/null @@ -1,30 +0,0 @@ -//! rm - remove files -//! -//! Usage: rm FILE -//! -//! Removes the specified file (not directories - use rmdir). - -use std::env; -use std::fs; -use std::process; - -fn main() { - let args: Vec = env::args().collect(); - - if args.len() < 2 { - eprintln!("Usage: rm FILE"); - process::exit(1); - } - - let path = &args[1]; - - match fs::remove_file(path) { - Ok(()) => { - println!("rm: file removed"); - } - Err(e) => { - eprintln!("rm: cannot remove '{}': {}", path, e); - process::exit(1); - } - } -} diff --git a/userspace/programs/src/brmdir.rs b/userspace/programs/src/brmdir.rs deleted file mode 100644 index 80afa4d9..00000000 --- a/userspace/programs/src/brmdir.rs +++ /dev/null @@ -1,30 +0,0 @@ -//! rmdir - remove empty directories -//! -//! Usage: rmdir DIRECTORY -//! -//! Removes the specified directory (must be empty). - -use std::env; -use std::fs; -use std::process; - -fn main() { - let args: Vec = env::args().collect(); - - if args.len() < 2 { - eprintln!("Usage: rmdir DIRECTORY"); - process::exit(1); - } - - let path = &args[1]; - - match fs::remove_dir(path) { - Ok(()) => { - println!("rmdir: directory removed"); - } - Err(e) => { - eprintln!("rmdir: failed to remove '{}': {}", path, e); - process::exit(1); - } - } -} diff --git a/userspace/programs/src/bsh.rs b/userspace/programs/src/bsh.rs index 382fa455..edee4c1d 100644 --- a/userspace/programs/src/bsh.rs +++ b/userspace/programs/src/bsh.rs @@ -2211,7 +2211,7 @@ fn builtin_wrap(line: &str) -> Option { Some(String::from("undefined")) } "help" => { - Some(String::from(r#"print("bsh -- Breenish ECMAScript Shell\n\nShell builtins:\n cd Change directory\n pwd Print working directory\n exit [code] Exit the shell\n which Find command in PATH\n source Execute a script file\n fart [count] Play fart sound(s)\n help Show this help\n\nProcess execution:\n exec(cmd, ...args) Run a command, returns {exitCode, stdout, stderr}\n pipe(cmd1, cmd2, ...) Pipeline commands\n bls /bin Bare commands are auto-wrapped in exec()\n\nFile operations:\n readFile(path) Read file contents\n writeFile(path, data) Write to file\n glob(pattern) Wildcard expansion (*.rs)\n\nEnvironment:\n env() All environment variables\n env(name) Get variable\n env(name, value) Set variable\n\nJavaScript:\n Full ECMAScript: let/const, functions, arrows, closures,\n if/else, for/while, try/catch, async/await, template literals,\n destructuring, spread, Map, Set, JSON, Math, Promise\n\nUse Tab for auto-completion. Up/Down for history.\nSee: docs/user-guide/bsh-shell-guide.md for full documentation.")"#)) + Some(String::from(r#"print("bsh -- Breenish ECMAScript Shell\n\nShell builtins:\n cd Change directory\n pwd Print working directory\n exit [code] Exit the shell\n which Find command in PATH\n source Execute a script file\n fart [count] Play fart sound(s)\n help Show this help\n\nProcess execution:\n exec(cmd, ...args) Run a command, returns {exitCode, stdout, stderr}\n pipe(cmd1, cmd2, ...) Pipeline commands\n ls /bin Bare commands are auto-wrapped in exec()\n\nFile operations:\n readFile(path) Read file contents\n writeFile(path, data) Write to file\n glob(pattern) Wildcard expansion (*.rs)\n\nEnvironment:\n env() All environment variables\n env(name) Get variable\n env(name, value) Set variable\n\nJavaScript:\n Full ECMAScript: let/const, functions, arrows, closures,\n if/else, for/while, try/catch, async/await, template literals,\n destructuring, spread, Map, Set, JSON, Math, Promise\n\nUse Tab for auto-completion. Up/Down for history.\nSee: docs/user-guide/bsh-shell-guide.md for full documentation.")"#)) } _ => None, } diff --git a/userspace/programs/src/btail.rs b/userspace/programs/src/btail.rs deleted file mode 100644 index 67b2c06c..00000000 --- a/userspace/programs/src/btail.rs +++ /dev/null @@ -1,129 +0,0 @@ -//! tail - output the last part of files -//! -//! Usage: tail [-n NUM] [FILE...] -//! -//! Print the last 10 lines of each FILE to standard output. -//! With more than one FILE, precede each with a header giving the file name. -//! -//! Options: -//! -n NUM print the last NUM lines instead of the last 10 - -use std::collections::VecDeque; -use std::env; -use std::fs::File; -use std::io::{self, BufRead, BufReader, Write}; -use std::process; - -const DEFAULT_LINES: usize = 10; - -/// Read all lines from a reader and output the last `max_lines` lines -fn tail_from_reader(reader: R, max_lines: usize) -> io::Result<()> { - let mut ring: VecDeque = VecDeque::with_capacity(max_lines + 1); - - for line_result in reader.lines() { - let line = line_result?; - ring.push_back(line); - if ring.len() > max_lines { - ring.pop_front(); - } - } - - let stdout = io::stdout(); - let mut out = stdout.lock(); - for line in &ring { - writeln!(out, "{}", line)?; - } - - Ok(()) -} - -/// Read from stdin and output last N lines -fn tail_stdin(max_lines: usize) -> io::Result<()> { - let stdin = io::stdin(); - let reader = stdin.lock(); - tail_from_reader(reader, max_lines) -} - -/// Read from a file and output last N lines -fn tail_file(path: &str, max_lines: usize) -> io::Result<()> { - let file = File::open(path)?; - let reader = BufReader::new(file); - tail_from_reader(reader, max_lines) -} - -fn print_header(path: &str) { - println!("==> {} <==", path); -} - -fn main() { - let args: Vec = env::args().collect(); - - let mut max_lines = DEFAULT_LINES; - let mut file_start_idx = 1usize; - - // Parse -n option - if args.len() >= 2 { - let arg = &args[1]; - if arg.len() >= 2 && arg.starts_with("-n") { - if arg.len() > 2 { - // -nNUM format - match arg[2..].parse::() { - Ok(n) => { - max_lines = n; - file_start_idx = 2; - } - Err(_) => { - eprintln!("tail: invalid number of lines"); - process::exit(1); - } - } - } else if args.len() >= 3 { - // -n NUM format - match args[2].parse::() { - Ok(n) => { - max_lines = n; - file_start_idx = 3; - } - Err(_) => { - eprintln!("tail: invalid number of lines"); - process::exit(1); - } - } - } - } - } - - // If no files, read from stdin - if file_start_idx >= args.len() { - if let Err(_) = tail_stdin(max_lines) { - eprintln!("tail: error reading stdin"); - process::exit(1); - } - process::exit(0); - } - - // Process files - let mut exit_code = 0; - let file_count = args.len() - file_start_idx; - let mut first_file = true; - - for i in file_start_idx..args.len() { - let path = &args[i]; - - // Print header if multiple files - if file_count > 1 { - if !first_file { - println!(); - } - print_header(path); - } - first_file = false; - - if let Err(e) = tail_file(path, max_lines) { - eprintln!("tail: {}: {}", path, e); - exit_code = 1; - } - } - - process::exit(exit_code); -} diff --git a/userspace/programs/src/btrue.rs b/userspace/programs/src/btrue.rs deleted file mode 100644 index 113e8873..00000000 --- a/userspace/programs/src/btrue.rs +++ /dev/null @@ -1,10 +0,0 @@ -//! true - return successful exit status -//! -//! Usage: true -//! -//! Exit with a status code indicating success (0). -//! This command does nothing and always succeeds. - -fn main() { - std::process::exit(0); -} diff --git a/userspace/programs/src/bwc.rs b/userspace/programs/src/bwc.rs deleted file mode 100644 index a5ff8e70..00000000 --- a/userspace/programs/src/bwc.rs +++ /dev/null @@ -1,211 +0,0 @@ -//! wc - word, line, and byte count -//! -//! Usage: wc [OPTION]... [FILE]... -//! -//! Print newline, word, and byte counts for each FILE. -//! With no FILE, read standard input. -//! -//! Options: -//! -c print byte counts -//! -l print line counts -//! -w print word counts -//! -//! If no options given, print all three counts (lines words bytes). - -use std::env; -use std::fs::File; -use std::io::{self, Read, Write}; -use std::process; - -struct Counts { - lines: usize, - words: usize, - bytes: usize, -} - -impl Counts { - fn new() -> Self { - Counts { - lines: 0, - words: 0, - bytes: 0, - } - } - - fn add(&mut self, other: &Counts) { - self.lines += other.lines; - self.words += other.words; - self.bytes += other.bytes; - } -} - -struct Options { - show_lines: bool, - show_words: bool, - show_bytes: bool, -} - -impl Options { - fn new() -> Self { - Options { - show_lines: true, - show_words: true, - show_bytes: true, - } - } - - fn from_flags(lines: bool, words: bool, bytes: bool) -> Self { - // If no flags specified, show all - if !lines && !words && !bytes { - Options::new() - } else { - Options { - show_lines: lines, - show_words: words, - show_bytes: bytes, - } - } - } -} - -/// POSIX-compliant whitespace check: space, tab, newline, carriage return, -/// form feed (\f = 0x0c), and vertical tab (\v = 0x0b) -fn is_whitespace(c: u8) -> bool { - c == b' ' || c == b'\t' || c == b'\n' || c == b'\r' || c == 0x0c || c == 0x0b -} - -/// Count lines, words, bytes from a reader -fn wc_reader(reader: &mut R) -> io::Result { - let mut buf = [0u8; 4096]; - let mut counts = Counts::new(); - let mut in_word = false; - - loop { - let n = reader.read(&mut buf)?; - if n == 0 { - break; - } - - counts.bytes += n; - - for i in 0..n { - if buf[i] == b'\n' { - counts.lines += 1; - } - - if is_whitespace(buf[i]) { - in_word = false; - } else if !in_word { - in_word = true; - counts.words += 1; - } - } - } - - Ok(counts) -} - -/// Count lines, words, bytes from stdin -fn wc_stdin() -> io::Result { - let stdin = io::stdin(); - let mut reader = stdin.lock(); - wc_reader(&mut reader) -} - -/// Count lines, words, bytes from a file -fn wc_file(path: &str) -> io::Result { - let mut file = File::open(path)?; - wc_reader(&mut file) -} - -fn print_counts(counts: &Counts, opts: &Options, filename: Option<&str>) { - let stdout = io::stdout(); - let mut out = stdout.lock(); - - if opts.show_lines { - let _ = write!(out, "{:>8}", counts.lines); - } - if opts.show_words { - let _ = write!(out, "{:>8}", counts.words); - } - if opts.show_bytes { - let _ = write!(out, "{:>8}", counts.bytes); - } - - if let Some(name) = filename { - let _ = write!(out, " {}", name); - } - let _ = writeln!(out); -} - -fn main() { - let args: Vec = env::args().collect(); - - let mut show_lines = false; - let mut show_words = false; - let mut show_bytes = false; - let mut file_start_idx = 1usize; - - // Parse options - for i in 1..args.len() { - let arg = &args[i]; - if arg.len() >= 2 && arg.starts_with('-') { - // Option flag(s) - for c in arg[1..].chars() { - match c { - 'l' => show_lines = true, - 'w' => show_words = true, - 'c' => show_bytes = true, - _ => { - eprintln!("wc: invalid option -- '{}'", c); - process::exit(1); - } - } - } - file_start_idx = i + 1; - } else { - // Not an option, must be a filename - break; - } - } - - let opts = Options::from_flags(show_lines, show_words, show_bytes); - - // If no files, read from stdin - if file_start_idx >= args.len() { - match wc_stdin() { - Ok(counts) => print_counts(&counts, &opts, None), - Err(_) => { - eprintln!("wc: error reading stdin"); - process::exit(1); - } - } - process::exit(0); - } - - // Process files - let mut exit_code = 0; - let mut total = Counts::new(); - let file_count = args.len() - file_start_idx; - - for i in file_start_idx..args.len() { - let path = &args[i]; - match wc_file(path) { - Ok(counts) => { - print_counts(&counts, &opts, Some(path)); - total.add(&counts); - } - Err(e) => { - eprintln!("wc: {}: {}", path, e); - exit_code = 1; - } - } - } - - // Print total if multiple files - if file_count > 1 { - print_counts(&total, &opts, Some("total")); - } - - process::exit(exit_code); -} diff --git a/userspace/programs/src/bwhich.rs b/userspace/programs/src/bwhich.rs deleted file mode 100644 index b5c4afb9..00000000 --- a/userspace/programs/src/bwhich.rs +++ /dev/null @@ -1,57 +0,0 @@ -//! which - locate a command -//! -//! Usage: which COMMAND -//! -//! Search PATH (/bin, /sbin) for COMMAND and print its full path. -//! Exits 0 if found, 1 if not found. - -use libbreenix::fs; - -/// PATH directories to search, in order -const PATH_DIRS: &[&str] = &["/bin/", "/sbin/"]; - -/// Check if a file exists and is executable at the given path. -/// -/// Uses libbreenix::fs::access() with X_OK to check execute permission. -fn is_executable(path: &str) -> bool { - let c_path = format!("{}\0", path); - fs::access(&c_path, fs::X_OK).is_ok() -} - -fn main() { - let args: Vec = std::env::args().collect(); - - if args.len() < 2 { - eprintln!("which: missing command name"); - eprintln!("Usage: which COMMAND"); - std::process::exit(1); - } - - let cmd_name = &args[1]; - if cmd_name.is_empty() { - eprintln!("which: empty command name"); - std::process::exit(1); - } - - // If command contains '/', it's an explicit path - check it directly - if cmd_name.contains('/') { - if is_executable(cmd_name) { - println!("{}", cmd_name); - std::process::exit(0); - } else { - std::process::exit(1); - } - } - - // Search PATH directories - for dir in PATH_DIRS { - let full_path = format!("{}{}", dir, cmd_name); - if is_executable(&full_path) { - println!("{}", full_path); - std::process::exit(0); - } - } - - // Not found - std::process::exit(1); -} diff --git a/userspace/programs/src/cat_test.rs b/userspace/programs/src/cat_test.rs index 74ef0ca3..6e6dfb9a 100644 --- a/userspace/programs/src/cat_test.rs +++ b/userspace/programs/src/cat_test.rs @@ -1,6 +1,6 @@ //! Test for cat coreutil (std version) //! -//! Verifies that /bin/bcat correctly outputs file contents. +//! Verifies that /bin/cat correctly outputs file contents. //! Uses pipe+dup2 to capture stdout and verify actual output content. use libbreenix::Fd; @@ -66,8 +66,8 @@ fn main() { // Test 1: cat /hello.txt should output "Hello from ext2!\n" println!("Test 1: cat /hello.txt"); { - let program = b"/bin/bcat\0"; - let arg0 = b"bcat\0".as_ptr(); + let program = b"/bin/cat\0"; + let arg0 = b"cat\0".as_ptr(); let arg1 = b"/hello.txt\0".as_ptr(); let argv: [*const u8; 3] = [arg0, arg1, std::ptr::null()]; @@ -86,8 +86,8 @@ fn main() { // Test 2: cat /lines.txt should output all 15 lines (111 bytes) println!("Test 2: cat /lines.txt (15 lines)"); { - let program = b"/bin/bcat\0"; - let arg0 = b"bcat\0".as_ptr(); + let program = b"/bin/cat\0"; + let arg0 = b"cat\0".as_ptr(); let arg1 = b"/lines.txt\0".as_ptr(); let argv: [*const u8; 3] = [arg0, arg1, std::ptr::null()]; @@ -107,8 +107,8 @@ fn main() { // Test 3: cat /empty.txt should produce empty output println!("Test 3: cat /empty.txt (empty file)"); { - let program = b"/bin/bcat\0"; - let arg0 = b"bcat\0".as_ptr(); + let program = b"/bin/cat\0"; + let arg0 = b"cat\0".as_ptr(); let arg1 = b"/empty.txt\0".as_ptr(); let argv: [*const u8; 3] = [arg0, arg1, std::ptr::null()]; @@ -126,8 +126,8 @@ fn main() { // Test 4: cat /test/nested.txt (nested path) println!("Test 4: cat /test/nested.txt (nested path)"); { - let program = b"/bin/bcat\0"; - let arg0 = b"bcat\0".as_ptr(); + let program = b"/bin/cat\0"; + let arg0 = b"cat\0".as_ptr(); let arg1 = b"/test/nested.txt\0".as_ptr(); let argv: [*const u8; 3] = [arg0, arg1, std::ptr::null()]; @@ -146,8 +146,8 @@ fn main() { // Test 5: cat /deep/path/to/file/data.txt (deep nested path) println!("Test 5: cat /deep/path/to/file/data.txt (deep path)"); { - let program = b"/bin/bcat\0"; - let arg0 = b"bcat\0".as_ptr(); + let program = b"/bin/cat\0"; + let arg0 = b"cat\0".as_ptr(); let arg1 = b"/deep/path/to/file/data.txt\0".as_ptr(); let argv: [*const u8; 3] = [arg0, arg1, std::ptr::null()]; @@ -166,8 +166,8 @@ fn main() { // Test 6: cat on nonexistent file should fail println!("Test 6: cat /nonexistent returns error"); { - let program = b"/bin/bcat\0"; - let arg0 = b"bcat\0".as_ptr(); + let program = b"/bin/cat\0"; + let arg0 = b"cat\0".as_ptr(); let arg1 = b"/nonexistent_file_xyz\0".as_ptr(); let argv: [*const u8; 3] = [arg0, arg1, std::ptr::null()]; @@ -185,8 +185,8 @@ fn main() { // Test 7: cat multiple files should concatenate them println!("Test 7: cat /hello.txt /test/nested.txt (concatenation)"); { - let program = b"/bin/bcat\0"; - let arg0 = b"bcat\0".as_ptr(); + let program = b"/bin/cat\0"; + let arg0 = b"cat\0".as_ptr(); let arg1 = b"/hello.txt\0".as_ptr(); let arg2 = b"/test/nested.txt\0".as_ptr(); let argv: [*const u8; 4] = [arg0, arg1, arg2, std::ptr::null()]; @@ -206,8 +206,8 @@ fn main() { // Test 8: cat with partial failure (one file exists, one doesn't) println!("Test 8: cat /hello.txt /nonexistent (partial failure)"); { - let program = b"/bin/bcat\0"; - let arg0 = b"bcat\0".as_ptr(); + let program = b"/bin/cat\0"; + let arg0 = b"cat\0".as_ptr(); let arg1 = b"/hello.txt\0".as_ptr(); let arg2 = b"/nonexistent_file\0".as_ptr(); let argv: [*const u8; 4] = [arg0, arg1, arg2, std::ptr::null()]; diff --git a/userspace/programs/src/cp_mv_argv_test.rs b/userspace/programs/src/cp_mv_argv_test.rs index 9741be23..150d2e6b 100644 --- a/userspace/programs/src/cp_mv_argv_test.rs +++ b/userspace/programs/src/cp_mv_argv_test.rs @@ -38,8 +38,8 @@ fn run_cp() -> i32 { match process::fork() { Ok(ForkResult::Child) => { // Child: exec cp with two arguments - let program = b"bcp\0"; - let arg0 = b"bcp\0".as_ptr(); + let program = b"cp\0"; + let arg0 = b"cp\0".as_ptr(); let arg1 = b"/test_cp_mv_src\0".as_ptr(); let arg2 = b"/test_cp_mv_copy\0".as_ptr(); let argv: [*const u8; 4] = [arg0, arg1, arg2, std::ptr::null()]; @@ -70,8 +70,8 @@ fn run_mv() -> i32 { match process::fork() { Ok(ForkResult::Child) => { // Child: exec mv with two arguments - let program = b"bmv\0"; - let arg0 = b"bmv\0".as_ptr(); + let program = b"mv\0"; + let arg0 = b"mv\0".as_ptr(); let arg1 = b"/test_cp_mv_copy\0".as_ptr(); let arg2 = b"/test_cp_mv_moved\0".as_ptr(); let argv: [*const u8; 4] = [arg0, arg1, arg2, std::ptr::null()]; diff --git a/userspace/programs/src/echo_argv_test.rs b/userspace/programs/src/echo_argv_test.rs index 8644d8c8..a7d9532a 100644 --- a/userspace/programs/src/echo_argv_test.rs +++ b/userspace/programs/src/echo_argv_test.rs @@ -9,8 +9,8 @@ use libbreenix::process::{self, ForkResult}; fn run_echo_no_args() -> i32 { match process::fork() { Ok(ForkResult::Child) => { - let program = b"becho\0"; - let arg0 = b"becho\0".as_ptr(); + let program = b"echo\0"; + let arg0 = b"echo\0".as_ptr(); let argv: [*const u8; 2] = [arg0, std::ptr::null()]; let _ = process::execv(program, argv.as_ptr()); @@ -34,8 +34,8 @@ fn run_echo_no_args() -> i32 { fn run_echo_single_arg() -> i32 { match process::fork() { Ok(ForkResult::Child) => { - let program = b"becho\0"; - let arg0 = b"becho\0".as_ptr(); + let program = b"echo\0"; + let arg0 = b"echo\0".as_ptr(); let arg1 = b"hello\0".as_ptr(); let argv: [*const u8; 3] = [arg0, arg1, std::ptr::null()]; @@ -60,8 +60,8 @@ fn run_echo_single_arg() -> i32 { fn run_echo_multi_args() -> i32 { match process::fork() { Ok(ForkResult::Child) => { - let program = b"becho\0"; - let arg0 = b"becho\0".as_ptr(); + let program = b"echo\0"; + let arg0 = b"echo\0".as_ptr(); let arg1 = b"hello\0".as_ptr(); let arg2 = b"world\0".as_ptr(); let arg3 = b"test\0".as_ptr(); diff --git a/userspace/programs/src/exec_from_ext2_test.rs b/userspace/programs/src/exec_from_ext2_test.rs index 276bc783..d20fd9a1 100644 --- a/userspace/programs/src/exec_from_ext2_test.rs +++ b/userspace/programs/src/exec_from_ext2_test.rs @@ -156,19 +156,19 @@ fn main() { } } - // Test 5: exec("/bin/bls", ...) should succeed and exit 0 - println!("Test 5: exec /bin/bls"); + // Test 5: exec("/bin/ls", ...) should succeed and exit 0 + println!("Test 5: exec /bin/ls"); match fork() { Ok(ForkResult::Child) => { - let program = b"/bin/bls\0"; - let arg0 = b"bls\0".as_ptr(); + let program = b"/bin/ls\0"; + let arg0 = b"ls\0".as_ptr(); let arg1 = b"/\0".as_ptr(); let argv: [*const u8; 3] = [arg0, arg1, std::ptr::null()]; let err = execv(program, argv.as_ptr()).unwrap_err(); // If we get here, exec failed let Error::Os(e) = err; - println!("exec /bin/bls failed: errno={:?}", e); + println!("exec /bin/ls failed: errno={:?}", e); std::process::exit(1); } Ok(ForkResult::Parent(child_pid)) => { diff --git a/userspace/programs/src/false_test.rs b/userspace/programs/src/false_test.rs index f39e29c3..55d516c6 100644 --- a/userspace/programs/src/false_test.rs +++ b/userspace/programs/src/false_test.rs @@ -1,6 +1,6 @@ //! Test for false coreutil (std version) //! -//! Verifies that /bin/bfalse exits with code 1 and produces no output. +//! Verifies that /bin/false exits with code 1 and produces no output. //! Uses pipe+dup2 to capture stdout and verify no output is produced. use libbreenix::Errno; @@ -85,11 +85,11 @@ fn main() { let mut tests_failed = 0; let mut output_buf = [0u8; 64]; - // Test 1: /bin/bfalse should exit with 1 and produce no output - println!("Test 1: /bin/bfalse exits with 1 and no output"); + // Test 1: /bin/false should exit with 1 and produce no output + println!("Test 1: /bin/false exits with 1 and no output"); { - let program = b"/bin/bfalse\0"; - let arg0 = b"bfalse\0".as_ptr(); + let program = b"/bin/false\0"; + let arg0 = b"false\0".as_ptr(); let argv: [*const u8; 2] = [arg0, std::ptr::null()]; let (exit_code, output_len) = run_and_capture(program, &argv, &mut output_buf); @@ -106,11 +106,11 @@ fn main() { } } - // Test 2: /bin/bfalse with arguments should still exit 1 (ignores args) - println!("Test 2: /bin/bfalse --ignored arguments exits with 1"); + // Test 2: /bin/false with arguments should still exit 1 (ignores args) + println!("Test 2: /bin/false --ignored arguments exits with 1"); { - let program = b"/bin/bfalse\0"; - let arg0 = b"bfalse\0".as_ptr(); + let program = b"/bin/false\0"; + let arg0 = b"false\0".as_ptr(); let arg1 = b"--ignored\0".as_ptr(); let arg2 = b"arguments\0".as_ptr(); let argv: [*const u8; 4] = [arg0, arg1, arg2, std::ptr::null()]; diff --git a/userspace/programs/src/head_test.rs b/userspace/programs/src/head_test.rs index 32d25cbd..c5509bd1 100644 --- a/userspace/programs/src/head_test.rs +++ b/userspace/programs/src/head_test.rs @@ -1,6 +1,6 @@ //! Test for head coreutil (std version) //! -//! Verifies that /bin/bhead correctly outputs the first N lines of files. +//! Verifies that /bin/head correctly outputs the first N lines of files. //! Uses pipe+dup2 to capture stdout and verify actual output content. use libbreenix::Fd; @@ -73,8 +73,8 @@ fn main() { // Test 1: head /lines.txt should output first 10 lines (default) println!("Test 1: head /lines.txt outputs 10 lines (default)"); { - let program = b"/bin/bhead\0"; - let arg0 = b"bhead\0".as_ptr(); + let program = b"/bin/head\0"; + let arg0 = b"head\0".as_ptr(); let arg1 = b"/lines.txt\0".as_ptr(); let argv: [*const u8; 3] = [arg0, arg1, std::ptr::null()]; @@ -93,8 +93,8 @@ fn main() { // Test 2: head -n5 /lines.txt should output exactly 5 lines println!("Test 2: head -n5 /lines.txt outputs 5 lines"); { - let program = b"/bin/bhead\0"; - let arg0 = b"bhead\0".as_ptr(); + let program = b"/bin/head\0"; + let arg0 = b"head\0".as_ptr(); let arg1 = b"-n5\0".as_ptr(); let arg2 = b"/lines.txt\0".as_ptr(); let argv: [*const u8; 4] = [arg0, arg1, arg2, std::ptr::null()]; @@ -115,8 +115,8 @@ fn main() { // Test 3: head -n 3 /lines.txt (space-separated arg) println!("Test 3: head -n 3 /lines.txt outputs 3 lines"); { - let program = b"/bin/bhead\0"; - let arg0 = b"bhead\0".as_ptr(); + let program = b"/bin/head\0"; + let arg0 = b"head\0".as_ptr(); let arg1 = b"-n\0".as_ptr(); let arg2 = b"3\0".as_ptr(); let arg3 = b"/lines.txt\0".as_ptr(); @@ -138,8 +138,8 @@ fn main() { // Test 4: head -n1 /lines.txt should output exactly 1 line println!("Test 4: head -n1 /lines.txt outputs 1 line"); { - let program = b"/bin/bhead\0"; - let arg0 = b"bhead\0".as_ptr(); + let program = b"/bin/head\0"; + let arg0 = b"head\0".as_ptr(); let arg1 = b"-n1\0".as_ptr(); let arg2 = b"/lines.txt\0".as_ptr(); let argv: [*const u8; 4] = [arg0, arg1, arg2, std::ptr::null()]; @@ -161,8 +161,8 @@ fn main() { // Test 5: head -n0 should output nothing println!("Test 5: head -n0 /lines.txt outputs 0 lines"); { - let program = b"/bin/bhead\0"; - let arg0 = b"bhead\0".as_ptr(); + let program = b"/bin/head\0"; + let arg0 = b"head\0".as_ptr(); let arg1 = b"-n0\0".as_ptr(); let arg2 = b"/lines.txt\0".as_ptr(); let argv: [*const u8; 4] = [arg0, arg1, arg2, std::ptr::null()]; @@ -181,8 +181,8 @@ fn main() { // Test 6: head on nonexistent file should fail (exit 1) println!("Test 6: head /nonexistent returns error"); { - let program = b"/bin/bhead\0"; - let arg0 = b"bhead\0".as_ptr(); + let program = b"/bin/head\0"; + let arg0 = b"head\0".as_ptr(); let arg1 = b"/nonexistent_file_xyz\0".as_ptr(); let argv: [*const u8; 3] = [arg0, arg1, std::ptr::null()]; @@ -200,8 +200,8 @@ fn main() { // Test 7: head on file with fewer lines than requested println!("Test 7: head -n10 /hello.txt (file has only 1 line)"); { - let program = b"/bin/bhead\0"; - let arg0 = b"bhead\0".as_ptr(); + let program = b"/bin/head\0"; + let arg0 = b"head\0".as_ptr(); let arg1 = b"-n10\0".as_ptr(); let arg2 = b"/hello.txt\0".as_ptr(); let argv: [*const u8; 4] = [arg0, arg1, arg2, std::ptr::null()]; diff --git a/userspace/programs/src/ls_test.rs b/userspace/programs/src/ls_test.rs index 0ff5e004..eb00459d 100644 --- a/userspace/programs/src/ls_test.rs +++ b/userspace/programs/src/ls_test.rs @@ -1,6 +1,6 @@ //! Test for ls coreutil (std version) //! -//! Verifies that /bin/bls correctly lists directory contents. +//! Verifies that /bin/ls correctly lists directory contents. //! Uses pipe+dup2 to capture stdout and verify actual output content. use libbreenix::Fd; @@ -79,8 +79,8 @@ fn main() { // Test 1: ls / should list root directory contents println!("Test 1: ls / (root directory)"); { - let program = b"/bin/bls\0"; - let arg0 = b"bls\0".as_ptr(); + let program = b"/bin/ls\0"; + let arg0 = b"ls\0".as_ptr(); let arg1 = b"/\0".as_ptr(); let argv: [*const u8; 3] = [arg0, arg1, std::ptr::null()]; @@ -105,20 +105,20 @@ fn main() { // Test 2: ls /bin should list all binaries println!("Test 2: ls /bin (binaries)"); { - let program = b"/bin/bls\0"; - let arg0 = b"bls\0".as_ptr(); + let program = b"/bin/ls\0"; + let arg0 = b"ls\0".as_ptr(); let arg1 = b"/bin\0".as_ptr(); let argv: [*const u8; 3] = [arg0, arg1, std::ptr::null()]; let (exit_code, output) = run_and_capture(program, &argv); - let has_cat = contains_line(&output, b"bcat"); - let has_ls = contains_line(&output, b"bls"); - let has_echo = contains_line(&output, b"becho"); - let has_head = contains_line(&output, b"bhead"); - let has_tail = contains_line(&output, b"btail"); - let has_wc = contains_line(&output, b"bwc"); - let has_which = contains_line(&output, b"bwhich"); + let has_cat = contains_line(&output, b"cat"); + let has_ls = contains_line(&output, b"ls"); + let has_echo = contains_line(&output, b"echo"); + let has_head = contains_line(&output, b"head"); + let has_tail = contains_line(&output, b"tail"); + let has_wc = contains_line(&output, b"wc"); + let has_which = contains_line(&output, b"which"); let has_hello = contains_line(&output, b"hello_world"); let all_present = has_cat && has_ls && has_echo && has_head && has_tail && has_wc && has_which && has_hello; @@ -136,13 +136,13 @@ fn main() { // Test 3: ls /sbin should list sbin binaries println!("Test 3: ls /sbin (sbin directory)"); { - let program = b"/bin/bls\0"; - let arg0 = b"bls\0".as_ptr(); + let program = b"/bin/ls\0"; + let arg0 = b"ls\0".as_ptr(); let arg1 = b"/sbin\0".as_ptr(); let argv: [*const u8; 3] = [arg0, arg1, std::ptr::null()]; let (exit_code, output) = run_and_capture(program, &argv); - let has_true = contains_line(&output, b"btrue"); + let has_true = contains_line(&output, b"true"); if exit_code == 0 && has_true { println!("LS_SBIN_OK"); @@ -156,8 +156,8 @@ fn main() { // Test 4: ls /test should show nested.txt println!("Test 4: ls /test (nested directory)"); { - let program = b"/bin/bls\0"; - let arg0 = b"bls\0".as_ptr(); + let program = b"/bin/ls\0"; + let arg0 = b"ls\0".as_ptr(); let arg1 = b"/test\0".as_ptr(); let argv: [*const u8; 3] = [arg0, arg1, std::ptr::null()]; @@ -176,8 +176,8 @@ fn main() { // Test 5: ls /deep/path/to/file should show data.txt println!("Test 5: ls /deep/path/to/file (deep path)"); { - let program = b"/bin/bls\0"; - let arg0 = b"bls\0".as_ptr(); + let program = b"/bin/ls\0"; + let arg0 = b"ls\0".as_ptr(); let arg1 = b"/deep/path/to/file\0".as_ptr(); let argv: [*const u8; 3] = [arg0, arg1, std::ptr::null()]; @@ -196,8 +196,8 @@ fn main() { // Test 6: ls on nonexistent directory should fail println!("Test 6: ls /nonexistent returns error"); { - let program = b"/bin/bls\0"; - let arg0 = b"bls\0".as_ptr(); + let program = b"/bin/ls\0"; + let arg0 = b"ls\0".as_ptr(); let arg1 = b"/nonexistent_dir_xyz\0".as_ptr(); let argv: [*const u8; 3] = [arg0, arg1, std::ptr::null()]; @@ -215,8 +215,8 @@ fn main() { // Test 7: ls /deep shows path/ subdirectory with directory marker println!("Test 7: ls /deep (directory markers)"); { - let program = b"/bin/bls\0"; - let arg0 = b"bls\0".as_ptr(); + let program = b"/bin/ls\0"; + let arg0 = b"ls\0".as_ptr(); let arg1 = b"/deep\0".as_ptr(); let argv: [*const u8; 3] = [arg0, arg1, std::ptr::null()]; @@ -235,8 +235,8 @@ fn main() { // Test 8: ls with no argument should default to current directory println!("Test 8: ls (no argument, defaults to cwd)"); { - let program = b"/bin/bls\0"; - let arg0 = b"bls\0".as_ptr(); + let program = b"/bin/ls\0"; + let arg0 = b"ls\0".as_ptr(); let argv: [*const u8; 2] = [arg0, std::ptr::null()]; let (exit_code, output) = run_and_capture(program, &argv); @@ -256,8 +256,8 @@ fn main() { // Test 9: Verify ls does NOT output . and .. entries println!("Test 9: ls / excludes . and .. entries"); { - let program = b"/bin/bls\0"; - let arg0 = b"bls\0".as_ptr(); + let program = b"/bin/ls\0"; + let arg0 = b"ls\0".as_ptr(); let arg1 = b"/\0".as_ptr(); let argv: [*const u8; 3] = [arg0, arg1, std::ptr::null()]; diff --git a/userspace/programs/src/mkdir_argv_test.rs b/userspace/programs/src/mkdir_argv_test.rs index ba34215e..7ae9d09b 100644 --- a/userspace/programs/src/mkdir_argv_test.rs +++ b/userspace/programs/src/mkdir_argv_test.rs @@ -16,8 +16,8 @@ fn run_mkdir() -> i32 { match process::fork() { Ok(ForkResult::Child) => { // Child: exec mkdir with directory argument - let program = b"bmkdir\0"; - let arg0 = b"bmkdir\0".as_ptr(); + let program = b"mkdir\0"; + let arg0 = b"mkdir\0".as_ptr(); let arg1 = b"/test_mkdir_argv\0".as_ptr(); let argv: [*const u8; 3] = [arg0, arg1, std::ptr::null()]; @@ -49,8 +49,8 @@ fn run_rmdir() -> i32 { match process::fork() { Ok(ForkResult::Child) => { // Child: exec rmdir with directory argument - let program = b"brmdir\0"; - let arg0 = b"brmdir\0".as_ptr(); + let program = b"rmdir\0"; + let arg0 = b"rmdir\0".as_ptr(); let arg1 = b"/test_mkdir_argv\0".as_ptr(); let argv: [*const u8; 3] = [arg0, arg1, std::ptr::null()]; diff --git a/userspace/programs/src/rm_argv_test.rs b/userspace/programs/src/rm_argv_test.rs index 173451a3..cc97ecc5 100644 --- a/userspace/programs/src/rm_argv_test.rs +++ b/userspace/programs/src/rm_argv_test.rs @@ -30,8 +30,8 @@ fn run_rm() -> i32 { match process::fork() { Ok(ForkResult::Child) => { // Child: exec rm with file argument - let program = b"brm\0"; - let arg0 = b"brm\0".as_ptr(); + let program = b"rm\0"; + let arg0 = b"rm\0".as_ptr(); let arg1 = b"/test_rm_argv_file\0".as_ptr(); let argv: [*const u8; 3] = [arg0, arg1, std::ptr::null()]; diff --git a/userspace/programs/src/tail_test.rs b/userspace/programs/src/tail_test.rs index eabc79a7..a998e380 100644 --- a/userspace/programs/src/tail_test.rs +++ b/userspace/programs/src/tail_test.rs @@ -1,6 +1,6 @@ //! Test for tail coreutil (std version) //! -//! Verifies that /bin/btail correctly outputs the last N lines of files. +//! Verifies that /bin/tail correctly outputs the last N lines of files. //! Uses pipe+dup2 to capture stdout and verify actual output content. use libbreenix::Fd; @@ -73,8 +73,8 @@ fn main() { // Last 10 lines are: Line 6 through Line 15 println!("Test 1: tail /lines.txt outputs last 10 lines"); { - let program = b"/bin/btail\0"; - let arg0 = b"btail\0".as_ptr(); + let program = b"/bin/tail\0"; + let arg0 = b"tail\0".as_ptr(); let arg1 = b"/lines.txt\0".as_ptr(); let argv: [*const u8; 3] = [arg0, arg1, std::ptr::null()]; @@ -95,8 +95,8 @@ fn main() { // Test 2: tail -n5 /lines.txt should output last 5 lines (Line 11-15) println!("Test 2: tail -n5 /lines.txt outputs last 5 lines"); { - let program = b"/bin/btail\0"; - let arg0 = b"btail\0".as_ptr(); + let program = b"/bin/tail\0"; + let arg0 = b"tail\0".as_ptr(); let arg1 = b"-n5\0".as_ptr(); let arg2 = b"/lines.txt\0".as_ptr(); let argv: [*const u8; 4] = [arg0, arg1, arg2, std::ptr::null()]; @@ -118,8 +118,8 @@ fn main() { // Test 3: tail -n 3 /lines.txt (space-separated) outputs last 3 lines println!("Test 3: tail -n 3 /lines.txt outputs last 3 lines"); { - let program = b"/bin/btail\0"; - let arg0 = b"btail\0".as_ptr(); + let program = b"/bin/tail\0"; + let arg0 = b"tail\0".as_ptr(); let arg1 = b"-n\0".as_ptr(); let arg2 = b"3\0".as_ptr(); let arg3 = b"/lines.txt\0".as_ptr(); @@ -142,8 +142,8 @@ fn main() { // Test 4: tail -n1 /lines.txt should output exactly "Line 15\n" println!("Test 4: tail -n1 /lines.txt outputs last line"); { - let program = b"/bin/btail\0"; - let arg0 = b"btail\0".as_ptr(); + let program = b"/bin/tail\0"; + let arg0 = b"tail\0".as_ptr(); let arg1 = b"-n1\0".as_ptr(); let arg2 = b"/lines.txt\0".as_ptr(); let argv: [*const u8; 4] = [arg0, arg1, arg2, std::ptr::null()]; @@ -165,8 +165,8 @@ fn main() { // Test 5: tail -n0 /lines.txt should produce no output println!("Test 5: tail -n0 /lines.txt outputs nothing"); { - let program = b"/bin/btail\0"; - let arg0 = b"btail\0".as_ptr(); + let program = b"/bin/tail\0"; + let arg0 = b"tail\0".as_ptr(); let arg1 = b"-n0\0".as_ptr(); let arg2 = b"/lines.txt\0".as_ptr(); let argv: [*const u8; 4] = [arg0, arg1, arg2, std::ptr::null()]; @@ -185,8 +185,8 @@ fn main() { // Test 6: tail on nonexistent file should fail println!("Test 6: tail /nonexistent returns error"); { - let program = b"/bin/btail\0"; - let arg0 = b"btail\0".as_ptr(); + let program = b"/bin/tail\0"; + let arg0 = b"tail\0".as_ptr(); let arg1 = b"/nonexistent_file_xyz\0".as_ptr(); let argv: [*const u8; 3] = [arg0, arg1, std::ptr::null()]; @@ -204,8 +204,8 @@ fn main() { // Test 7: tail on file with fewer lines than requested println!("Test 7: tail -n10 /hello.txt (file has only 1 line)"); { - let program = b"/bin/btail\0"; - let arg0 = b"btail\0".as_ptr(); + let program = b"/bin/tail\0"; + let arg0 = b"tail\0".as_ptr(); let arg1 = b"-n10\0".as_ptr(); let arg2 = b"/hello.txt\0".as_ptr(); let argv: [*const u8; 4] = [arg0, arg1, arg2, std::ptr::null()]; diff --git a/userspace/programs/src/true_test.rs b/userspace/programs/src/true_test.rs index 741a74fb..62fa2588 100644 --- a/userspace/programs/src/true_test.rs +++ b/userspace/programs/src/true_test.rs @@ -1,6 +1,6 @@ //! Test for true coreutil (std version) //! -//! Verifies that /sbin/btrue exits with code 0 and produces no output. +//! Verifies that /sbin/true exits with code 0 and produces no output. //! Uses pipe+dup2 to capture stdout and verify no output is produced. use libbreenix::Errno; @@ -85,11 +85,11 @@ fn main() { let mut tests_failed = 0; let mut output_buf = [0u8; 64]; - // Test 1: /sbin/btrue should exit with 0 and produce no output - println!("Test 1: /sbin/btrue exits with 0 and no output"); + // Test 1: /sbin/true should exit with 0 and produce no output + println!("Test 1: /sbin/true exits with 0 and no output"); { - let program = b"/sbin/btrue\0"; - let arg0 = b"btrue\0".as_ptr(); + let program = b"/sbin/true\0"; + let arg0 = b"true\0".as_ptr(); let argv: [*const u8; 2] = [arg0, std::ptr::null()]; let (exit_code, output_len) = run_and_capture(program, &argv, &mut output_buf); @@ -106,11 +106,11 @@ fn main() { } } - // Test 2: /sbin/btrue with arguments should still exit 0 (ignores args) - println!("Test 2: /sbin/btrue --ignored arguments exits with 0"); + // Test 2: /sbin/true with arguments should still exit 0 (ignores args) + println!("Test 2: /sbin/true --ignored arguments exits with 0"); { - let program = b"/sbin/btrue\0"; - let arg0 = b"btrue\0".as_ptr(); + let program = b"/sbin/true\0"; + let arg0 = b"true\0".as_ptr(); let arg1 = b"--ignored\0".as_ptr(); let arg2 = b"arguments\0".as_ptr(); let argv: [*const u8; 4] = [arg0, arg1, arg2, std::ptr::null()]; diff --git a/userspace/programs/src/wc_test.rs b/userspace/programs/src/wc_test.rs index a24c2750..15847558 100644 --- a/userspace/programs/src/wc_test.rs +++ b/userspace/programs/src/wc_test.rs @@ -1,6 +1,6 @@ //! Test for wc coreutil (std version) //! -//! Verifies that /bin/bwc correctly counts lines, words, and bytes. +//! Verifies that /bin/wc correctly counts lines, words, and bytes. //! Uses pipe+dup2 to capture stdout and verify actual output content. use libbreenix::Fd; @@ -86,8 +86,8 @@ fn main() { // /hello.txt contains "Hello from ext2!\n" (1 line, 3 words, 17 bytes) println!("Test 1: wc /hello.txt (1 line, 3 words, 17 bytes)"); { - let program = b"/bin/bwc\0"; - let arg0 = b"bwc\0".as_ptr(); + let program = b"/bin/wc\0"; + let arg0 = b"wc\0".as_ptr(); let arg1 = b"/hello.txt\0".as_ptr(); let argv: [*const u8; 3] = [arg0, arg1, std::ptr::null()]; @@ -124,8 +124,8 @@ fn main() { // Test 2: wc /lines.txt (15 lines, 30 words, 111 bytes) println!("Test 2: wc /lines.txt (15 lines, 30 words, 111 bytes)"); { - let program = b"/bin/bwc\0"; - let arg0 = b"bwc\0".as_ptr(); + let program = b"/bin/wc\0"; + let arg0 = b"wc\0".as_ptr(); let arg1 = b"/lines.txt\0".as_ptr(); let argv: [*const u8; 3] = [arg0, arg1, std::ptr::null()]; @@ -162,8 +162,8 @@ fn main() { // Test 3: wc -l /lines.txt should output just line count (15) println!("Test 3: wc -l /lines.txt (lines only)"); { - let program = b"/bin/bwc\0"; - let arg0 = b"bwc\0".as_ptr(); + let program = b"/bin/wc\0"; + let arg0 = b"wc\0".as_ptr(); let arg1 = b"-l\0".as_ptr(); let arg2 = b"/lines.txt\0".as_ptr(); let argv: [*const u8; 4] = [arg0, arg1, arg2, std::ptr::null()]; @@ -188,8 +188,8 @@ fn main() { // Test 4: wc -w /lines.txt should output just word count (30) println!("Test 4: wc -w /lines.txt (words only)"); { - let program = b"/bin/bwc\0"; - let arg0 = b"bwc\0".as_ptr(); + let program = b"/bin/wc\0"; + let arg0 = b"wc\0".as_ptr(); let arg1 = b"-w\0".as_ptr(); let arg2 = b"/lines.txt\0".as_ptr(); let argv: [*const u8; 4] = [arg0, arg1, arg2, std::ptr::null()]; @@ -214,8 +214,8 @@ fn main() { // Test 5: wc -c /lines.txt should output just byte count (111) println!("Test 5: wc -c /lines.txt (bytes only)"); { - let program = b"/bin/bwc\0"; - let arg0 = b"bwc\0".as_ptr(); + let program = b"/bin/wc\0"; + let arg0 = b"wc\0".as_ptr(); let arg1 = b"-c\0".as_ptr(); let arg2 = b"/lines.txt\0".as_ptr(); let argv: [*const u8; 4] = [arg0, arg1, arg2, std::ptr::null()]; @@ -240,8 +240,8 @@ fn main() { // Test 6: wc -lw /lines.txt should output lines and words (15, 30) println!("Test 6: wc -lw /lines.txt (lines and words)"); { - let program = b"/bin/bwc\0"; - let arg0 = b"bwc\0".as_ptr(); + let program = b"/bin/wc\0"; + let arg0 = b"wc\0".as_ptr(); let arg1 = b"-lw\0".as_ptr(); let arg2 = b"/lines.txt\0".as_ptr(); let argv: [*const u8; 4] = [arg0, arg1, arg2, std::ptr::null()]; @@ -273,8 +273,8 @@ fn main() { // Test 7: wc on empty file should return 0 0 0 println!("Test 7: wc /empty.txt (empty file)"); { - let program = b"/bin/bwc\0"; - let arg0 = b"bwc\0".as_ptr(); + let program = b"/bin/wc\0"; + let arg0 = b"wc\0".as_ptr(); let arg1 = b"/empty.txt\0".as_ptr(); let argv: [*const u8; 3] = [arg0, arg1, std::ptr::null()]; @@ -311,8 +311,8 @@ fn main() { // Test 8: wc on nonexistent file should fail println!("Test 8: wc /nonexistent returns error"); { - let program = b"/bin/bwc\0"; - let arg0 = b"bwc\0".as_ptr(); + let program = b"/bin/wc\0"; + let arg0 = b"wc\0".as_ptr(); let arg1 = b"/nonexistent_file_xyz\0".as_ptr(); let argv: [*const u8; 3] = [arg0, arg1, std::ptr::null()]; diff --git a/userspace/programs/src/which_test.rs b/userspace/programs/src/which_test.rs index f02749a6..e631a633 100644 --- a/userspace/programs/src/which_test.rs +++ b/userspace/programs/src/which_test.rs @@ -1,6 +1,6 @@ //! Test for which coreutil (std version) //! -//! Verifies that /bin/bwhich correctly locates commands in PATH. +//! Verifies that /bin/which correctly locates commands in PATH. //! Uses pipe+dup2 to capture stdout and verify actual output content. use libbreenix::Fd; @@ -75,17 +75,17 @@ fn main() { let mut tests_passed = 0; let mut tests_failed = 0; - // Test 1: which bls -> /bin/bls (found in /bin) - println!("Test 1: which bls returns /bin/bls"); + // Test 1: which ls -> /bin/ls (found in /bin) + println!("Test 1: which ls returns /bin/ls"); { - let program = b"/bin/bwhich\0"; - let arg0 = b"bwhich\0".as_ptr(); - let arg1 = b"bls\0".as_ptr(); + let program = b"/bin/which\0"; + let arg0 = b"which\0".as_ptr(); + let arg1 = b"ls\0".as_ptr(); let argv: [*const u8; 3] = [arg0, arg1, std::ptr::null()]; let (exit_code, output) = run_and_capture(program, &argv); - if exit_code == 0 && output_matches(&output, b"/bin/bls") { + if exit_code == 0 && output_matches(&output, b"/bin/ls") { println!("WHICH_LS_OK"); tests_passed += 1; } else { @@ -94,17 +94,17 @@ fn main() { } } - // Test 2: which btrue -> /sbin/btrue (found in /sbin, not /bin) - println!("Test 2: which btrue returns /sbin/btrue"); + // Test 2: which true -> /sbin/true (found in /sbin, not /bin) + println!("Test 2: which true returns /sbin/true"); { - let program = b"/bin/bwhich\0"; - let arg0 = b"bwhich\0".as_ptr(); - let arg1 = b"btrue\0".as_ptr(); + let program = b"/bin/which\0"; + let arg0 = b"which\0".as_ptr(); + let arg1 = b"true\0".as_ptr(); let argv: [*const u8; 3] = [arg0, arg1, std::ptr::null()]; let (exit_code, output) = run_and_capture(program, &argv); - if exit_code == 0 && output_matches(&output, b"/sbin/btrue") { + if exit_code == 0 && output_matches(&output, b"/sbin/true") { println!("WHICH_TRUE_OK"); tests_passed += 1; } else { @@ -116,8 +116,8 @@ fn main() { // Test 3: which nonexistent -> exit 1 (not found) println!("Test 3: which nonexistent_cmd exits 1"); { - let program = b"/bin/bwhich\0"; - let arg0 = b"bwhich\0".as_ptr(); + let program = b"/bin/which\0"; + let arg0 = b"which\0".as_ptr(); let arg1 = b"nonexistent_cmd_xyz\0".as_ptr(); let argv: [*const u8; 3] = [arg0, arg1, std::ptr::null()]; @@ -132,17 +132,17 @@ fn main() { } } - // Test 4: which /bin/bls -> /bin/bls (explicit path, if executable) - println!("Test 4: which /bin/bls (explicit path)"); + // Test 4: which /bin/ls -> /bin/ls (explicit path, if executable) + println!("Test 4: which /bin/ls (explicit path)"); { - let program = b"/bin/bwhich\0"; - let arg0 = b"bwhich\0".as_ptr(); - let arg1 = b"/bin/bls\0".as_ptr(); + let program = b"/bin/which\0"; + let arg0 = b"which\0".as_ptr(); + let arg1 = b"/bin/ls\0".as_ptr(); let argv: [*const u8; 3] = [arg0, arg1, std::ptr::null()]; let (exit_code, output) = run_and_capture(program, &argv); - if exit_code == 0 && output_matches(&output, b"/bin/bls") { + if exit_code == 0 && output_matches(&output, b"/bin/ls") { println!("WHICH_EXPLICIT_OK"); tests_passed += 1; } else { @@ -154,8 +154,8 @@ fn main() { // Test 5: which (no args) -> exit 1 with usage println!("Test 5: which with no args exits 1"); { - let program = b"/bin/bwhich\0"; - let arg0 = b"bwhich\0".as_ptr(); + let program = b"/bin/which\0"; + let arg0 = b"which\0".as_ptr(); let argv: [*const u8; 2] = [arg0, std::ptr::null()]; let (exit_code, _) = run_and_capture(program, &argv); @@ -169,17 +169,17 @@ fn main() { } } - // Test 6: which bcat -> /bin/bcat (another /bin command) - println!("Test 6: which bcat returns /bin/bcat"); + // Test 6: which cat -> /bin/cat (another /bin command) + println!("Test 6: which cat returns /bin/cat"); { - let program = b"/bin/bwhich\0"; - let arg0 = b"bwhich\0".as_ptr(); - let arg1 = b"bcat\0".as_ptr(); + let program = b"/bin/which\0"; + let arg0 = b"which\0".as_ptr(); + let arg1 = b"cat\0".as_ptr(); let argv: [*const u8; 3] = [arg0, arg1, std::ptr::null()]; let (exit_code, output) = run_and_capture(program, &argv); - if exit_code == 0 && output_matches(&output, b"/bin/bcat") { + if exit_code == 0 && output_matches(&output, b"/bin/cat") { println!("WHICH_CAT_OK"); tests_passed += 1; } else { diff --git a/xtask/src/boot_stages.rs b/xtask/src/boot_stages.rs index 1f96605b..6965d021 100644 --- a/xtask/src/boot_stages.rs +++ b/xtask/src/boot_stages.rs @@ -1301,10 +1301,10 @@ fn shared_userspace_stages() -> Vec { check_hint: "Check load_elf_from_ext2() directory check", }, BootStage { - name: "Exec ext2 /bin/bls OK", + name: "Exec ext2 /bin/ls OK", marker: "EXEC_EXT2_LS_OK", - failure_meaning: "exec /bin/bls failed", - check_hint: "Check kernel exec path resolution and bls binary in ext2", + failure_meaning: "exec /bin/ls failed", + check_hint: "Check kernel exec path resolution and ls (BusyBox) in ext2", }, BootStage { name: "Exec ext2 test passed", @@ -1319,54 +1319,54 @@ fn shared_userspace_stages() -> Vec { check_hint: "Check fs_block_alloc_test.rs and fs/ext2/block_group.rs", }, - // Coreutil tests (b-prefixed Breenix coreutils) + // Coreutil tests (BusyBox applets) BootStage { - name: "btrue coreutil test passed", + name: "true coreutil test passed", marker: "TRUE_TEST_PASSED", - failure_meaning: "/bin/btrue did not exit with code 0", - check_hint: "Check btrue.rs and true_test.rs", + failure_meaning: "/sbin/true did not exit with code 0", + check_hint: "Check BusyBox true applet and true_test.rs", }, BootStage { - name: "bfalse coreutil test passed", + name: "false coreutil test passed", marker: "FALSE_TEST_PASSED", - failure_meaning: "/bin/bfalse did not exit with code 1", - check_hint: "Check bfalse.rs and false_test.rs", + failure_meaning: "/bin/false did not exit with code 1", + check_hint: "Check BusyBox false applet and false_test.rs", }, BootStage { - name: "bhead coreutil test passed", + name: "head coreutil test passed", marker: "HEAD_TEST_PASSED", - failure_meaning: "/bin/bhead failed to process files correctly", - check_hint: "Check bhead.rs and head_test.rs", + failure_meaning: "/bin/head failed to process files correctly", + check_hint: "Check BusyBox head applet and head_test.rs", }, BootStage { - name: "btail coreutil test passed", + name: "tail coreutil test passed", marker: "TAIL_TEST_PASSED", - failure_meaning: "/bin/btail failed to process files correctly", - check_hint: "Check btail.rs and tail_test.rs", + failure_meaning: "/bin/tail failed to process files correctly", + check_hint: "Check BusyBox tail applet and tail_test.rs", }, BootStage { - name: "bwc coreutil test passed", + name: "wc coreutil test passed", marker: "WC_TEST_PASSED", - failure_meaning: "/bin/bwc failed to count lines/words/bytes correctly", - check_hint: "Check bwc.rs and wc_test.rs", + failure_meaning: "/bin/wc failed to count lines/words/bytes correctly", + check_hint: "Check BusyBox wc applet and wc_test.rs", }, BootStage { - name: "bwhich coreutil test passed", + name: "which coreutil test passed", marker: "WHICH_TEST_PASSED", - failure_meaning: "/bin/bwhich failed to locate commands in PATH", - check_hint: "Check bwhich.rs and which_test.rs", + failure_meaning: "/bin/which failed to locate commands in PATH", + check_hint: "Check BusyBox which applet and which_test.rs", }, BootStage { - name: "bcat coreutil test passed", + name: "cat coreutil test passed", marker: "CAT_TEST_PASSED", - failure_meaning: "/bin/bcat failed to output file contents correctly", - check_hint: "Check bcat.rs and cat_test.rs", + failure_meaning: "/bin/cat failed to output file contents correctly", + check_hint: "Check BusyBox cat applet and cat_test.rs", }, BootStage { - name: "bls coreutil test passed", + name: "ls coreutil test passed", marker: "LS_TEST_PASSED", - failure_meaning: "/bin/bls failed to list directory contents correctly", - check_hint: "Check bls.rs and ls_test.rs", + failure_meaning: "/bin/ls failed to list directory contents correctly", + check_hint: "Check BusyBox ls applet and ls_test.rs", }, // Rust std library tests (from hello_std_real / hello_world.elf)