]> Projects (at) Tadryanom (dot) Me - AdrOS.git/commitdiff
refactor: reorganize userland into user/cmds/<name>/ with per-program Makefiles
authorTulio A M Mendes <[email protected]>
Sat, 14 Mar 2026 15:40:11 +0000 (12:40 -0300)
committerTulio A M Mendes <[email protected]>
Sat, 14 Mar 2026 15:40:11 +0000 (12:40 -0300)
- Move 53 user commands from user/<name>.c to user/cmds/<name>/<name>.c
- Add user/cmds/common.mk shared build rules for dynamically-linked commands
- Add per-program Makefiles for all commands (including fulltest, ldso, pie_test)
- Build all .o/.elf into build/user/cmds/<name>/ (out-of-tree)
- Replace [init] test prefix with [test] in fulltest.c, pie_main.c, test scripts
- Fix find.c and which.c: use opendir/readdir/closedir instead of raw getdents
- Fix ulibc glob.c missing stdio.h include
- Fix ulibc -Wno-incompatible-pointer-types for GCC 14+
- Fix test_host_utils.sh which test set -e issue
- Doom rootfs path changed to /usr/games/doom
- make clean now also cleans ulibc and doom in-tree artifacts

Tests: 103/103 smoke, 28/28 unit, 19/19 security, 68/68 host utils, cppcheck clean

179 files changed:
BUILD_GUIDE.md
Makefile
tests/smoke_test.exp
tests/test_battery.exp
tests/test_host_utils.sh
user/awk.c [deleted file]
user/basename.c [deleted file]
user/cat.c [deleted file]
user/chgrp.c [deleted file]
user/chmod.c [deleted file]
user/chown.c [deleted file]
user/clear.c [deleted file]
user/cmds/awk/Makefile [new file with mode: 0644]
user/cmds/awk/awk.c [new file with mode: 0644]
user/cmds/basename/Makefile [new file with mode: 0644]
user/cmds/basename/basename.c [new file with mode: 0644]
user/cmds/cat/Makefile [new file with mode: 0644]
user/cmds/cat/cat.c [new file with mode: 0644]
user/cmds/chgrp/Makefile [new file with mode: 0644]
user/cmds/chgrp/chgrp.c [new file with mode: 0644]
user/cmds/chmod/Makefile [new file with mode: 0644]
user/cmds/chmod/chmod.c [new file with mode: 0644]
user/cmds/chown/Makefile [new file with mode: 0644]
user/cmds/chown/chown.c [new file with mode: 0644]
user/cmds/clear/Makefile [new file with mode: 0644]
user/cmds/clear/clear.c [new file with mode: 0644]
user/cmds/common.mk [new file with mode: 0644]
user/cmds/cp/Makefile [new file with mode: 0644]
user/cmds/cp/cp.c [new file with mode: 0644]
user/cmds/cut/Makefile [new file with mode: 0644]
user/cmds/cut/cut.c [new file with mode: 0644]
user/cmds/date/Makefile [new file with mode: 0644]
user/cmds/date/date.c [new file with mode: 0644]
user/cmds/dd/Makefile [new file with mode: 0644]
user/cmds/dd/dd.c [new file with mode: 0644]
user/cmds/df/Makefile [new file with mode: 0644]
user/cmds/df/df.c [new file with mode: 0644]
user/cmds/dirname/Makefile [new file with mode: 0644]
user/cmds/dirname/dirname.c [new file with mode: 0644]
user/cmds/dmesg/Makefile [new file with mode: 0644]
user/cmds/dmesg/dmesg.c [new file with mode: 0644]
user/cmds/du/Makefile [new file with mode: 0644]
user/cmds/du/du.c [new file with mode: 0644]
user/cmds/echo/Makefile [new file with mode: 0644]
user/cmds/echo/echo.c [new file with mode: 0644]
user/cmds/env/Makefile [new file with mode: 0644]
user/cmds/env/env.c [new file with mode: 0644]
user/cmds/find/Makefile [new file with mode: 0644]
user/cmds/find/find.c [new file with mode: 0644]
user/cmds/free/Makefile [new file with mode: 0644]
user/cmds/free/free.c [new file with mode: 0644]
user/cmds/fulltest/Makefile [new file with mode: 0644]
user/cmds/fulltest/errno.c [new file with mode: 0644]
user/cmds/fulltest/fulltest.c [new file with mode: 0644]
user/cmds/fulltest/user_errno.h [new file with mode: 0644]
user/cmds/grep/Makefile [new file with mode: 0644]
user/cmds/grep/grep.c [new file with mode: 0644]
user/cmds/head/Makefile [new file with mode: 0644]
user/cmds/head/head.c [new file with mode: 0644]
user/cmds/hostname/Makefile [new file with mode: 0644]
user/cmds/hostname/hostname.c [new file with mode: 0644]
user/cmds/id/Makefile [new file with mode: 0644]
user/cmds/id/id.c [new file with mode: 0644]
user/cmds/init/Makefile [new file with mode: 0644]
user/cmds/init/init.c [new file with mode: 0644]
user/cmds/kill/Makefile [new file with mode: 0644]
user/cmds/kill/kill.c [new file with mode: 0644]
user/cmds/ldso/Makefile [new file with mode: 0644]
user/cmds/ldso/ldso.c [new file with mode: 0644]
user/cmds/ln/Makefile [new file with mode: 0644]
user/cmds/ln/ln.c [new file with mode: 0644]
user/cmds/ls/Makefile [new file with mode: 0644]
user/cmds/ls/ls.c [new file with mode: 0644]
user/cmds/mkdir/Makefile [new file with mode: 0644]
user/cmds/mkdir/mkdir.c [new file with mode: 0644]
user/cmds/mount/Makefile [new file with mode: 0644]
user/cmds/mount/mount.c [new file with mode: 0644]
user/cmds/mv/Makefile [new file with mode: 0644]
user/cmds/mv/mv.c [new file with mode: 0644]
user/cmds/pie_test/Makefile [new file with mode: 0644]
user/cmds/pie_test/pie_func.c [new file with mode: 0644]
user/cmds/pie_test/pie_main.c [new file with mode: 0644]
user/cmds/printenv/Makefile [new file with mode: 0644]
user/cmds/printenv/printenv.c [new file with mode: 0644]
user/cmds/ps/Makefile [new file with mode: 0644]
user/cmds/ps/ps.c [new file with mode: 0644]
user/cmds/pwd/Makefile [new file with mode: 0644]
user/cmds/pwd/pwd.c [new file with mode: 0644]
user/cmds/rm/Makefile [new file with mode: 0644]
user/cmds/rm/rm.c [new file with mode: 0644]
user/cmds/rmdir/Makefile [new file with mode: 0644]
user/cmds/rmdir/rmdir.c [new file with mode: 0644]
user/cmds/sed/Makefile [new file with mode: 0644]
user/cmds/sed/sed.c [new file with mode: 0644]
user/cmds/sh/Makefile [new file with mode: 0644]
user/cmds/sh/sh.c [new file with mode: 0644]
user/cmds/sleep/Makefile [new file with mode: 0644]
user/cmds/sleep/sleep.c [new file with mode: 0644]
user/cmds/sort/Makefile [new file with mode: 0644]
user/cmds/sort/sort.c [new file with mode: 0644]
user/cmds/stat/Makefile [new file with mode: 0644]
user/cmds/stat/stat.c [new file with mode: 0644]
user/cmds/tail/Makefile [new file with mode: 0644]
user/cmds/tail/tail.c [new file with mode: 0644]
user/cmds/tee/Makefile [new file with mode: 0644]
user/cmds/tee/tee.c [new file with mode: 0644]
user/cmds/top/Makefile [new file with mode: 0644]
user/cmds/top/top.c [new file with mode: 0644]
user/cmds/touch/Makefile [new file with mode: 0644]
user/cmds/touch/touch.c [new file with mode: 0644]
user/cmds/tr/Makefile [new file with mode: 0644]
user/cmds/tr/tr.c [new file with mode: 0644]
user/cmds/umount/Makefile [new file with mode: 0644]
user/cmds/umount/umount.c [new file with mode: 0644]
user/cmds/uname/Makefile [new file with mode: 0644]
user/cmds/uname/uname.c [new file with mode: 0644]
user/cmds/uniq/Makefile [new file with mode: 0644]
user/cmds/uniq/uniq.c [new file with mode: 0644]
user/cmds/uptime/Makefile [new file with mode: 0644]
user/cmds/uptime/uptime.c [new file with mode: 0644]
user/cmds/wc/Makefile [new file with mode: 0644]
user/cmds/wc/wc.c [new file with mode: 0644]
user/cmds/which/Makefile [new file with mode: 0644]
user/cmds/which/which.c [new file with mode: 0644]
user/cmds/who/Makefile [new file with mode: 0644]
user/cmds/who/who.c [new file with mode: 0644]
user/cp.c [deleted file]
user/cut.c [deleted file]
user/date.c [deleted file]
user/dd.c [deleted file]
user/df.c [deleted file]
user/dirname.c [deleted file]
user/dmesg.c [deleted file]
user/du.c [deleted file]
user/echo.c [deleted file]
user/env.c [deleted file]
user/errno.c [deleted file]
user/find.c [deleted file]
user/free.c [deleted file]
user/fulltest.c [deleted file]
user/grep.c [deleted file]
user/head.c [deleted file]
user/hostname.c [deleted file]
user/id.c [deleted file]
user/init.c [deleted file]
user/kill.c [deleted file]
user/ldso.c [deleted file]
user/ln.c [deleted file]
user/ls.c [deleted file]
user/mkdir.c [deleted file]
user/mount.c [deleted file]
user/mv.c [deleted file]
user/pie_func.c [deleted file]
user/pie_main.c [deleted file]
user/printenv.c [deleted file]
user/ps.c [deleted file]
user/pwd.c [deleted file]
user/rm.c [deleted file]
user/rmdir.c [deleted file]
user/sed.c [deleted file]
user/sh.c [deleted file]
user/sleep.c [deleted file]
user/sort.c [deleted file]
user/stat.c [deleted file]
user/tail.c [deleted file]
user/tee.c [deleted file]
user/top.c [deleted file]
user/touch.c [deleted file]
user/tr.c [deleted file]
user/ulibc/Makefile
user/ulibc/src/glob.c
user/umount.c [deleted file]
user/uname.c [deleted file]
user/uniq.c [deleted file]
user/uptime.c [deleted file]
user/user_errno.h [deleted file]
user/wc.c [deleted file]
user/which.c [deleted file]
user/who.c [deleted file]

index f6134455db846de93654e31d324f981d54a03139..fa0b4a1aa0e40d34517c52c4b57a6408c557adc0 100644 (file)
@@ -142,7 +142,7 @@ The fulltest binary (`/sbin/fulltest`) runs a comprehensive suite of 102 smoke t
 - Dynamic linking: lazy PLT resolution, PLT caching
 - LZ4 initrd decompression
 
-All tests print `[init] ... OK` on success. Any failure calls `sys_exit(1)`.
+All tests print `[test] ... OK` on success. Any failure calls `sys_exit(1)`.
 
 ### Testing
 
index 02f123e25a8e747838ed05429553af877e1b8517..4b08b49712fd28f344af9f8832ecc748e2ddb0ea 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -78,66 +78,41 @@ ifeq ($(ARCH),x86)
     USER_LD  ?= i686-elf-ld
     USER_AR  ?= i686-elf-ar
 
-    FULLTEST_ELF := user/fulltest.elf
-    ECHO_ELF := user/echo.elf
-    SH_ELF := user/sh.elf
-    CAT_ELF := user/cat.elf
-    LS_ELF := user/ls.elf
-    MKDIR_ELF := user/mkdir.elf
-    RM_ELF := user/rm.elf
-    CP_ELF := user/cp.elf
-    MV_ELF := user/mv.elf
-    TOUCH_ELF := user/touch.elf
-    LN_ELF := user/ln.elf
-    HEAD_ELF := user/head.elf
-    TAIL_ELF := user/tail.elf
-    WC_ELF := user/wc.elf
-    SORT_ELF := user/sort.elf
-    UNIQ_ELF := user/uniq.elf
-    CUT_ELF := user/cut.elf
-    CHMOD_ELF := user/chmod.elf
-    CHOWN_ELF := user/chown.elf
-    CHGRP_ELF := user/chgrp.elf
-    DATE_ELF := user/date.elf
-    HOSTNAME_ELF := user/hostname.elf
-    UPTIME_ELF := user/uptime.elf
-    MOUNT_ELF := user/mount.elf
-    UMOUNT_ELF := user/umount.elf
-    ENV_ELF := user/env.elf
-    KILL_ELF := user/kill.elf
-    SLEEP_ELF := user/sleep.elf
-    CLEAR_ELF := user/clear.elf
-    PS_ELF := user/ps.elf
-    DF_ELF := user/df.elf
-    FREE_ELF := user/free.elf
-    TEE_ELF := user/tee.elf
-    BASENAME_ELF := user/basename.elf
-    DIRNAME_ELF := user/dirname.elf
-    RMDIR_ELF := user/rmdir.elf
-    GREP_ELF := user/grep.elf
-    ID_ELF := user/id.elf
-    UNAME_ELF := user/uname.elf
-    DMESG_ELF := user/dmesg.elf
-    PRINTENV_ELF := user/printenv.elf
-    TR_ELF := user/tr.elf
-    DD_ELF := user/dd.elf
-    PWD_ELF := user/pwd.elf
-    STAT_ELF := user/stat.elf
-    SED_ELF := user/sed.elf
-    AWK_ELF := user/awk.elf
-    WHO_ELF := user/who.elf
-    TOP_ELF := user/top.elf
-    DU_ELF := user/du.elf
-    FIND_ELF := user/find.elf
-    WHICH_ELF := user/which.elf
-    INIT_ELF := user/init.elf
-    LDSO_ELF := user/ld.so
-    ULIBC_SO := user/ulibc/libc.so
-    PIE_SO := user/libpietest.so
-    PIE_ELF := user/pie_test.elf
+    # User build output directory
+    USER_BUILD := build/user
+
+    # List of dynamically-linked user commands (built via user/cmds/<name>/Makefile)
+    USER_CMD_NAMES := echo sh cat ls mkdir rm cp mv touch ln \
+                      head tail wc sort uniq cut \
+                      chmod chown chgrp \
+                      date hostname uptime \
+                      mount umount env kill sleep \
+                      clear ps df free tee \
+                      basename dirname rmdir \
+                      grep id uname dmesg \
+                      printenv tr dd pwd stat \
+                      sed awk who top du find which \
+                      init
+
+    # ELF paths for dynamically-linked commands
+    USER_CMD_ELFS := $(foreach cmd,$(USER_CMD_NAMES),$(USER_BUILD)/cmds/$(cmd)/$(cmd).elf)
+
+    # Special builds (not dynamically-linked via common.mk)
+    FULLTEST_ELF := $(USER_BUILD)/cmds/fulltest/fulltest.elf
+    LDSO_ELF     := $(USER_BUILD)/cmds/ldso/ld.so
+    PIE_SO       := $(USER_BUILD)/cmds/pie_test/libpietest.so
+    PIE_ELF      := $(USER_BUILD)/cmds/pie_test/pie_test.elf
+
+    # ulibc
+    ULIBC_DIR := user/ulibc
+    ULIBC_SO  := $(USER_BUILD)/ulibc/libc.so
+    ULIBC_LIB := $(USER_BUILD)/ulibc/libulibc.a
+
+    # doom
     DOOM_ELF := user/doom/doom.elf
+
     INITRD_IMG := initrd.img
-    MKINITRD := tools/mkinitrd
+    MKINITRD   := tools/mkinitrd
 endif
 
 # --- ARM64 Configuration ---
@@ -214,285 +189,56 @@ iso: $(KERNEL_NAME) $(INITRD_IMG)
 $(MKINITRD): tools/mkinitrd.c include/xxhash32.h
        @gcc -Iinclude tools/mkinitrd.c -o $(MKINITRD)
 
-ULIBC_DIR := user/ulibc
-ULIBC_LIB := $(ULIBC_DIR)/libulibc.a
-
-$(ULIBC_LIB):
-       @$(MAKE) -C $(ULIBC_DIR) CC="$(USER_CC)" AS="$(USER_CC:gcc=as)" AR="$(USER_AR)" LD="$(USER_LD)" --no-print-directory
-
-$(ULIBC_SO):
-       @$(MAKE) -C $(ULIBC_DIR) CC="$(USER_CC)" AS="$(USER_CC:gcc=as)" AR="$(USER_AR)" LD="$(USER_LD)" libc.so --no-print-directory
-
-$(FULLTEST_ELF): user/fulltest.c user/linker.ld
-       @$(USER_CC) -m32 -I include -ffreestanding -fno-pie -no-pie -nostdlib -Wl,-T,user/linker.ld -o $(FULLTEST_ELF) user/fulltest.c user/errno.c
-
-# --- Dynamic linking helper: compile .c to PIC .o, link as PIE with crt0 + libc.so ---
-ULIBC_CRT0 := $(ULIBC_DIR)/src/crt0.o
-DYN_CC := $(USER_CC) -m32 -ffreestanding -nostdlib -O2 -Wall -Wextra -fPIC -fno-plt -I$(ULIBC_DIR)/include
-DYN_LD := $(USER_LD) -m elf_i386 --dynamic-linker=/lib/ld.so -T user/dyn_linker.ld -L$(ULIBC_DIR) -rpath /lib
-
-$(ECHO_ELF): user/echo.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
-       @$(DYN_CC) -c user/echo.c -o user/echo.o
-       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/echo.o -lc
-
-$(SH_ELF): user/sh.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
-       @$(DYN_CC) -c user/sh.c -o user/sh.o
-       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/sh.o -lc
-
-$(CAT_ELF): user/cat.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
-       @$(DYN_CC) -c user/cat.c -o user/cat.o
-       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/cat.o -lc
-
-$(LS_ELF): user/ls.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
-       @$(DYN_CC) -c user/ls.c -o user/ls.o
-       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/ls.o -lc
-
-$(MKDIR_ELF): user/mkdir.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
-       @$(DYN_CC) -c user/mkdir.c -o user/mkdir.o
-       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/mkdir.o -lc
-
-$(RM_ELF): user/rm.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
-       @$(DYN_CC) -c user/rm.c -o user/rm.o
-       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/rm.o -lc
-
-$(CP_ELF): user/cp.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
-       @$(DYN_CC) -c user/cp.c -o user/cp.o
-       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/cp.o -lc
-
-$(MV_ELF): user/mv.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
-       @$(DYN_CC) -c user/mv.c -o user/mv.o
-       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/mv.o -lc
-
-$(TOUCH_ELF): user/touch.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
-       @$(DYN_CC) -c user/touch.c -o user/touch.o
-       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/touch.o -lc
-
-$(LN_ELF): user/ln.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
-       @$(DYN_CC) -c user/ln.c -o user/ln.o
-       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/ln.o -lc
-
-$(HEAD_ELF): user/head.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
-       @$(DYN_CC) -c user/head.c -o user/head.o
-       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/head.o -lc
-
-$(TAIL_ELF): user/tail.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
-       @$(DYN_CC) -c user/tail.c -o user/tail.o
-       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/tail.o -lc
-
-$(WC_ELF): user/wc.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
-       @$(DYN_CC) -c user/wc.c -o user/wc.o
-       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/wc.o -lc
-
-$(SORT_ELF): user/sort.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
-       @$(DYN_CC) -c user/sort.c -o user/sort.o
-       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/sort.o -lc
-
-$(UNIQ_ELF): user/uniq.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
-       @$(DYN_CC) -c user/uniq.c -o user/uniq.o
-       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/uniq.o -lc
-
-$(CUT_ELF): user/cut.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
-       @$(DYN_CC) -c user/cut.c -o user/cut.o
-       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/cut.o -lc
-
-$(CHMOD_ELF): user/chmod.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
-       @$(DYN_CC) -c user/chmod.c -o user/chmod.o
-       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/chmod.o -lc
-
-$(CHOWN_ELF): user/chown.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
-       @$(DYN_CC) -c user/chown.c -o user/chown.o
-       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/chown.o -lc
-
-$(CHGRP_ELF): user/chgrp.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
-       @$(DYN_CC) -c user/chgrp.c -o user/chgrp.o
-       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/chgrp.o -lc
-
-$(DATE_ELF): user/date.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
-       @$(DYN_CC) -c user/date.c -o user/date.o
-       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/date.o -lc
-
-$(HOSTNAME_ELF): user/hostname.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
-       @$(DYN_CC) -c user/hostname.c -o user/hostname.o
-       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/hostname.o -lc
-
-$(UPTIME_ELF): user/uptime.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
-       @$(DYN_CC) -c user/uptime.c -o user/uptime.o
-       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/uptime.o -lc
-
-$(INIT_ELF): user/init.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
-       @$(DYN_CC) -c user/init.c -o user/init.o
-       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/init.o -lc
-
-$(MOUNT_ELF): user/mount.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
-       @$(DYN_CC) -c user/mount.c -o user/mount.o
-       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/mount.o -lc
-
-$(UMOUNT_ELF): user/umount.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
-       @$(DYN_CC) -c user/umount.c -o user/umount.o
-       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/umount.o -lc
-
-$(ENV_ELF): user/env.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
-       @$(DYN_CC) -c user/env.c -o user/env.o
-       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/env.o -lc
-
-$(KILL_ELF): user/kill.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
-       @$(DYN_CC) -c user/kill.c -o user/kill.o
-       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/kill.o -lc
-
-$(SLEEP_ELF): user/sleep.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
-       @$(DYN_CC) -c user/sleep.c -o user/sleep.o
-       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/sleep.o -lc
-
-$(CLEAR_ELF): user/clear.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
-       @$(DYN_CC) -c user/clear.c -o user/clear.o
-       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/clear.o -lc
-
-$(PS_ELF): user/ps.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
-       @$(DYN_CC) -c user/ps.c -o user/ps.o
-       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/ps.o -lc
-
-$(DF_ELF): user/df.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
-       @$(DYN_CC) -c user/df.c -o user/df.o
-       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/df.o -lc
-
-$(FREE_ELF): user/free.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
-       @$(DYN_CC) -c user/free.c -o user/free.o
-       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/free.o -lc
-
-$(TEE_ELF): user/tee.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
-       @$(DYN_CC) -c user/tee.c -o user/tee.o
-       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/tee.o -lc
-
-$(BASENAME_ELF): user/basename.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
-       @$(DYN_CC) -c user/basename.c -o user/basename.o
-       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/basename.o -lc
-
-$(DIRNAME_ELF): user/dirname.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
-       @$(DYN_CC) -c user/dirname.c -o user/dirname.o
-       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/dirname.o -lc
-
-$(RMDIR_ELF): user/rmdir.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
-       @$(DYN_CC) -c user/rmdir.c -o user/rmdir.o
-       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/rmdir.o -lc
-
-$(GREP_ELF): user/grep.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
-       @$(DYN_CC) -c user/grep.c -o user/grep.o
-       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/grep.o -lc
-
-$(ID_ELF): user/id.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
-       @$(DYN_CC) -c user/id.c -o user/id.o
-       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/id.o -lc
-
-$(UNAME_ELF): user/uname.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
-       @$(DYN_CC) -c user/uname.c -o user/uname.o
-       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/uname.o -lc
-
-$(DMESG_ELF): user/dmesg.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
-       @$(DYN_CC) -c user/dmesg.c -o user/dmesg.o
-       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/dmesg.o -lc
-
-$(PRINTENV_ELF): user/printenv.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
-       @$(DYN_CC) -c user/printenv.c -o user/printenv.o
-       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/printenv.o -lc
-
-$(TR_ELF): user/tr.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
-       @$(DYN_CC) -c user/tr.c -o user/tr.o
-       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/tr.o -lc
-
-$(DD_ELF): user/dd.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
-       @$(DYN_CC) -c user/dd.c -o user/dd.o
-       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/dd.o -lc
-
-$(PWD_ELF): user/pwd.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
-       @$(DYN_CC) -c user/pwd.c -o user/pwd.o
-       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/pwd.o -lc
-
-$(STAT_ELF): user/stat.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
-       @$(DYN_CC) -c user/stat.c -o user/stat.o
-       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/stat.o -lc
-
-$(SED_ELF): user/sed.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
-       @$(DYN_CC) -c user/sed.c -o user/sed.o
-       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/sed.o -lc
-
-$(AWK_ELF): user/awk.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
-       @$(DYN_CC) -c user/awk.c -o user/awk.o
-       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/awk.o -lc
-
-$(WHO_ELF): user/who.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
-       @$(DYN_CC) -c user/who.c -o user/who.o
-       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/who.o -lc
-
-$(TOP_ELF): user/top.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
-       @$(DYN_CC) -c user/top.c -o user/top.o
-       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/top.o -lc
-
-$(DU_ELF): user/du.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
-       @$(DYN_CC) -c user/du.c -o user/du.o
-       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/du.o -lc
-
-$(FIND_ELF): user/find.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
-       @$(DYN_CC) -c user/find.c -o user/find.o
-       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/find.o -lc
-
-$(WHICH_ELF): user/which.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
-       @$(DYN_CC) -c user/which.c -o user/which.o
-       @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/which.o -lc
-
-$(LDSO_ELF): user/ldso.c user/ldso_linker.ld
-       @$(USER_CC) -m32 -ffreestanding -fno-pie -no-pie -nostdlib -Wl,-T,user/ldso_linker.ld -o $(LDSO_ELF) user/ldso.c
-
-$(PIE_SO): user/pie_func.c
-       @$(USER_CC) -m32 -fPIC -fno-plt -c user/pie_func.c -o user/pie_func.o
-       @$(USER_LD) -m elf_i386 -shared -soname libpietest.so -o $(PIE_SO) user/pie_func.o
-
-$(PIE_ELF): user/pie_main.c user/pie_linker.ld $(PIE_SO)
-       @$(USER_CC) -m32 -fPIC -c user/pie_main.c -o user/pie_main.o
-       @$(USER_LD) -m elf_i386 -pie --dynamic-linker=/lib/ld.so -T user/pie_linker.ld -o $(PIE_ELF) user/pie_main.o $(PIE_SO) -rpath /lib
-
-# All dynamically-linked user commands
-USER_CMDS := $(ECHO_ELF) $(SH_ELF) $(CAT_ELF) $(LS_ELF) $(MKDIR_ELF) $(RM_ELF) \
-             $(CP_ELF) $(MV_ELF) $(TOUCH_ELF) $(LN_ELF) \
-             $(HEAD_ELF) $(TAIL_ELF) $(WC_ELF) $(SORT_ELF) $(UNIQ_ELF) $(CUT_ELF) \
-             $(CHMOD_ELF) $(CHOWN_ELF) $(CHGRP_ELF) \
-             $(DATE_ELF) $(HOSTNAME_ELF) $(UPTIME_ELF) \
-             $(MOUNT_ELF) $(UMOUNT_ELF) $(ENV_ELF) $(KILL_ELF) $(SLEEP_ELF) \
-             $(CLEAR_ELF) $(PS_ELF) $(DF_ELF) $(FREE_ELF) $(TEE_ELF) \
-             $(BASENAME_ELF) $(DIRNAME_ELF) $(RMDIR_ELF) \
-             $(GREP_ELF) $(ID_ELF) $(UNAME_ELF) $(DMESG_ELF) \
-             $(PRINTENV_ELF) $(TR_ELF) $(DD_ELF) $(PWD_ELF) $(STAT_ELF) \
-             $(SED_ELF) $(AWK_ELF) $(WHO_ELF) $(TOP_ELF) $(DU_ELF) \
-             $(FIND_ELF) $(WHICH_ELF) \
-             $(INIT_ELF)
-
+# --- ulibc build (output into build/user/ulibc/) ---
+$(ULIBC_LIB) $(ULIBC_SO): FORCE
+       @mkdir -p $(USER_BUILD)/ulibc
+       @$(MAKE) -C $(ULIBC_DIR) CC="$(USER_CC)" AS="$(USER_CC:gcc=as)" AR="$(USER_AR)" LD="$(USER_LD)" \
+               libulibc.a libc.so --no-print-directory
+       @cp -u $(ULIBC_DIR)/libulibc.a $(ULIBC_LIB)
+       @cp -u $(ULIBC_DIR)/libc.so $(ULIBC_SO)
+FORCE:
+
+# --- Special builds (fulltest, ldso, pie_test) ---
+$(FULLTEST_ELF): user/cmds/fulltest/fulltest.c user/cmds/fulltest/errno.c user/linker.ld
+       @$(MAKE) --no-print-directory -C user/cmds/fulltest TOPDIR=$(CURDIR) USER_CC="$(USER_CC)"
+
+$(LDSO_ELF): user/cmds/ldso/ldso.c user/ldso_linker.ld
+       @$(MAKE) --no-print-directory -C user/cmds/ldso TOPDIR=$(CURDIR) USER_CC="$(USER_CC)"
+
+$(PIE_SO) $(PIE_ELF): user/cmds/pie_test/pie_main.c user/cmds/pie_test/pie_func.c user/pie_linker.ld
+       @$(MAKE) --no-print-directory -C user/cmds/pie_test TOPDIR=$(CURDIR) USER_CC="$(USER_CC)" USER_LD="$(USER_LD)"
+
+# --- Dynamically-linked user commands (generic rule via sub-Makefiles) ---
+# Use absolute paths so they work from sub-Makefile directories
+ABS_ULIBC := $(CURDIR)/$(ULIBC_DIR)
+ABS_DYN_CC := $(USER_CC) -m32 -ffreestanding -nostdlib -O2 -Wall -Wextra -fPIC -fno-plt -I$(ABS_ULIBC)/include
+ABS_DYN_LD := $(USER_LD) -m elf_i386 --dynamic-linker=/lib/ld.so -T $(CURDIR)/user/dyn_linker.ld -L$(ABS_ULIBC) -rpath /lib --unresolved-symbols=ignore-in-shared-libs
+
+# Generate build rules for each dynamically-linked command
+define USER_CMD_RULE
+$(USER_BUILD)/cmds/$(1)/$(1).elf: user/cmds/$(1)/$(1).c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB)
+       @$$(MAKE) --no-print-directory -C user/cmds/$(1) TOPDIR=$$(CURDIR) \
+               DYN_CC="$$(ABS_DYN_CC)" DYN_LD="$$(ABS_DYN_LD)" CRT0="$(ABS_ULIBC)/src/crt0.o"
+endef
+$(foreach cmd,$(USER_CMD_NAMES),$(eval $(call USER_CMD_RULE,$(cmd))))
+
+# Commands that go to /bin/ in rootfs (all except init)
+USER_BIN_NAMES := $(filter-out init,$(USER_CMD_NAMES))
+
+# Build INITRD_FILES list: <elf>:<rootfs-path>
 FSTAB := rootfs/etc/fstab
 INITRD_FILES := $(FULLTEST_ELF):sbin/fulltest \
-    $(INIT_ELF):sbin/init \
-    $(ECHO_ELF):bin/echo $(SH_ELF):bin/sh $(CAT_ELF):bin/cat $(LS_ELF):bin/ls \
-    $(MKDIR_ELF):bin/mkdir $(RM_ELF):bin/rm $(CP_ELF):bin/cp $(MV_ELF):bin/mv \
-    $(TOUCH_ELF):bin/touch $(LN_ELF):bin/ln \
-    $(HEAD_ELF):bin/head $(TAIL_ELF):bin/tail $(WC_ELF):bin/wc \
-    $(SORT_ELF):bin/sort $(UNIQ_ELF):bin/uniq $(CUT_ELF):bin/cut \
-    $(CHMOD_ELF):bin/chmod $(CHOWN_ELF):bin/chown $(CHGRP_ELF):bin/chgrp \
-    $(DATE_ELF):bin/date $(HOSTNAME_ELF):bin/hostname $(UPTIME_ELF):bin/uptime \
-    $(MOUNT_ELF):bin/mount $(UMOUNT_ELF):bin/umount $(ENV_ELF):bin/env \
-    $(KILL_ELF):bin/kill $(SLEEP_ELF):bin/sleep $(CLEAR_ELF):bin/clear \
-    $(PS_ELF):bin/ps $(DF_ELF):bin/df $(FREE_ELF):bin/free \
-    $(TEE_ELF):bin/tee $(BASENAME_ELF):bin/basename $(DIRNAME_ELF):bin/dirname \
-    $(RMDIR_ELF):bin/rmdir \
-    $(GREP_ELF):bin/grep $(ID_ELF):bin/id $(UNAME_ELF):bin/uname \
-    $(DMESG_ELF):bin/dmesg $(PRINTENV_ELF):bin/printenv $(TR_ELF):bin/tr \
-    $(SED_ELF):bin/sed $(AWK_ELF):bin/awk $(WHO_ELF):bin/who \
-    $(TOP_ELF):bin/top $(DU_ELF):bin/du $(FIND_ELF):bin/find $(WHICH_ELF):bin/which \
-    $(DD_ELF):bin/dd $(PWD_ELF):bin/pwd $(STAT_ELF):bin/stat \
+    $(USER_BUILD)/cmds/init/init.elf:sbin/init \
+    $(foreach cmd,$(USER_BIN_NAMES),$(USER_BUILD)/cmds/$(cmd)/$(cmd).elf:bin/$(cmd)) \
     $(LDSO_ELF):lib/ld.so $(ULIBC_SO):lib/libc.so \
     $(PIE_SO):lib/libpietest.so $(PIE_ELF):bin/pie_test \
     $(FSTAB):etc/fstab
-INITRD_DEPS := $(MKINITRD) $(FULLTEST_ELF) $(USER_CMDS) $(LDSO_ELF) $(ULIBC_SO) $(PIE_SO) $(PIE_ELF) $(FSTAB)
+
+INITRD_DEPS := $(MKINITRD) $(FULLTEST_ELF) $(USER_CMD_ELFS) $(LDSO_ELF) $(ULIBC_SO) $(PIE_SO) $(PIE_ELF) $(FSTAB)
 
 # Include doom.elf if it has been built
 ifneq ($(wildcard $(DOOM_ELF)),)
-INITRD_FILES += $(DOOM_ELF):bin/doom
+INITRD_FILES += $(DOOM_ELF):usr/games/doom
 INITRD_DEPS += $(DOOM_ELF)
 endif
 
@@ -637,6 +383,8 @@ $(BUILD_DIR)/%.o: $(SRC_DIR)/%.S
        @$(AS) $(ASFLAGS) $< -o $@
 
 clean:
-       rm -rf build $(KERNEL_NAME)
+       rm -rf build $(KERNEL_NAME) $(INITRD_IMG) adros-*.iso
+       @$(MAKE) -C user/ulibc clean --no-print-directory 2>/dev/null || true
+       @if [ -f user/doom/Makefile ]; then $(MAKE) -C user/doom clean --no-print-directory 2>/dev/null || true; fi
 
 .PHONY: all clean iso run run-arm run-riscv run-mips cppcheck sparse analyzer check test test-1cpu test-battery test-host test-gdb test-all scan-build mkinitrd-asan
index bb9bd905f1f208ef7dbc7fa651a2e6419e35b318..33f119190ac89424d4f45e60b50d910ffdf126c2 100755 (executable)
@@ -49,102 +49,102 @@ set tests {
     {"ATA DMA mode"         "\\[ATA\\] Channel 0: DMA mode."}
     {"SMP CPUs active"      "CPU\\(s\\) active."}
     {"User ring3 entry"     "\\[USER\\] enter ring3"}
-    {"init.elf hello"       "\\[init\\] hello from init.elf"}
-    {"open/read/close"      "\\[init\\] open/read/close OK"}
-    {"overlay copy-up"      "\\[init\\] overlay copy-up OK"}
-    {"lseek/stat/fstat"     "\\[init\\] lseek/stat/fstat OK"}
-    {"dup2 restore"         "\\[init\\] dup2 restore tty OK"}
-    {"kill SIGKILL"         "\\[init\\] kill\\(SIGKILL\\) OK"}
-    {"poll pipe"            "\\[init\\] poll\\(pipe\\) OK"}
-    {"select pipe"          "\\[init\\] select\\(pipe\\) OK"}
-    {"ioctl tty"            "\\[init\\] ioctl\\(/dev/tty\\) OK"}
-    {"job control"          "\\[init\\] job control \\(SIGTTIN/SIGTTOU\\) OK"}
-    {"poll /dev/null"       "\\[init\\] poll\\(/dev/null\\) OK"}
-    {"pty bidirectional"    "\\[init\\] pty OK"}
-    {"setsid/setpgid"       "\\[init\\] setsid/setpgid/getpgrp OK"}
-    {"sigaction SIGUSR1"    "\\[init\\] sigaction/kill\\(SIGUSR1\\) OK"}
-    {"sigreturn"            "\\[init\\] sigreturn OK"}
-    {"tmpfs/mount"          "\\[init\\] tmpfs/mount OK"}
-    {"dev null"             "\\[init\\] /dev/null OK"}
-    {"persist counter"      "\\[init\\] /persist/counter="}
-    {"dev tty write"        "\\[init\\] /dev/tty write OK"}
-    {"diskfs test"          "\\[init\\] /disk/test prev="}
-    {"diskfs mkdir/unlink"  "\\[init\\] diskfs mkdir/unlink OK"}
-    {"diskfs getdents"      "\\[init\\] diskfs getdents OK"}
-    {"isatty"               "\\[init\\] isatty OK"}
-    {"O_NONBLOCK"           "\\[init\\] O_NONBLOCK OK"}
-    {"pipe2/dup3"           "\\[init\\] pipe2/dup3 OK"}
-    {"chdir/getcwd"         "\\[init\\] chdir/getcwd OK"}
-    {"*at syscalls"         "\\[init\\] \\*at OK"}
-    {"rename/rmdir"         "\\[init\\] rename/rmdir OK"}
-    {"getdents multi-fs"    "\\[init\\] getdents multi-fs OK"}
-    {"brk heap"             "\\[init\\] brk OK"}
-    {"mmap/munmap"          "\\[init\\] mmap/munmap OK"}
-    {"clock_gettime"        "\\[init\\] clock_gettime OK"}
-    {"dev zero"             "\\[init\\] /dev/zero OK"}
-    {"dev random"           "\\[init\\] /dev/random OK"}
-    {"procfs"               "\\[init\\] procfs OK"}
-    {"pread/pwrite"         "\\[init\\] pread/pwrite OK"}
-    {"ftruncate"            "\\[init\\] ftruncate OK"}
-    {"symlink/readlink"     "\\[init\\] symlink/readlink OK"}
-    {"access"               "\\[init\\] access OK"}
-    {"sigprocmask"          "\\[init\\] sigprocmask/sigpending OK"}
-    {"alarm SIGALRM"        "\\[init\\] alarm/SIGALRM OK"}
-    {"shmget/shmat"         "\\[init\\] shmget/shmat/shmdt OK"}
-    {"O_APPEND"             "\\[init\\] O_APPEND OK"}
-    {"umask"                "\\[init\\] umask OK"}
-    {"pipe capacity"        "\\[init\\] pipe capacity OK"}
-    {"waitid"               "\\[init\\] waitid OK"}
-    {"setitimer/getitimer"  "\\[init\\] setitimer/getitimer OK"}
-    {"select regfile"       "\\[init\\] select regfile OK"}
-    {"poll regfile"         "\\[init\\] poll regfile OK"}
-    {"hard link"            "\\[init\\] hard link OK"}
-    {"epoll"                "\\[init\\] epoll OK"}
-    {"epollet"              "\\[init\\] epollet OK"}
-    {"inotify"              "\\[init\\] inotify OK"}
-    {"aio"                  "\\[init\\] aio OK"}
-    {"nanosleep"            "\\[init\\] nanosleep OK"}
-    {"CLOCK_REALTIME"       "\\[init\\] CLOCK_REALTIME OK"}
-    {"dev urandom"          "\\[init\\] /dev/urandom OK"}
-    {"proc cmdline"         "\\[init\\] /proc/cmdline OK"}
-    {"CoW fork"             "\\[init\\] CoW fork OK"}
-    {"readv/writev"         "\\[init\\] readv/writev OK"}
-    {"fsync"                "\\[init\\] fsync OK"}
-    {"truncate path"        "\\[init\\] truncate OK"}
-    {"getuid/getgid"        "\\[init\\] getuid/getgid OK"}
-    {"chmod"                "\\[init\\] chmod OK"}
-    {"flock"                "\\[init\\] flock OK"}
-    {"times"                "\\[init\\] times OK"}
-    {"gettid"               "\\[init\\] gettid OK"}
-    {"posix_spawn"          "\\[init\\] posix_spawn OK"}
-    {"clock_ns precision"   "\\[init\\] clock_ns precision OK"}
-    {"getppid"              "\\[init\\] getppid OK"}
-    {"waitpid WNOHANG"      "\\[init\\] waitpid WNOHANG OK"}
-    {"SIGSEGV handler"      "\\[init\\] SIGSEGV OK"}
-    {"waitpid 100 children" "\\[init\\] waitpid OK \\(100 children"}
-    {"lazy PLT"             "\\[init\\] lazy PLT OK"}
-    {"PLT cached"           "\\[init\\] PLT cached OK"}
+    {"init.elf hello"       "\\[test\\] hello from init.elf"}
+    {"open/read/close"      "\\[test\\] open/read/close OK"}
+    {"overlay copy-up"      "\\[test\\] overlay copy-up OK"}
+    {"lseek/stat/fstat"     "\\[test\\] lseek/stat/fstat OK"}
+    {"dup2 restore"         "\\[test\\] dup2 restore tty OK"}
+    {"kill SIGKILL"         "\\[test\\] kill\\(SIGKILL\\) OK"}
+    {"poll pipe"            "\\[test\\] poll\\(pipe\\) OK"}
+    {"select pipe"          "\\[test\\] select\\(pipe\\) OK"}
+    {"ioctl tty"            "\\[test\\] ioctl\\(/dev/tty\\) OK"}
+    {"job control"          "\\[test\\] job control \\(SIGTTIN/SIGTTOU\\) OK"}
+    {"poll /dev/null"       "\\[test\\] poll\\(/dev/null\\) OK"}
+    {"pty bidirectional"    "\\[test\\] pty OK"}
+    {"setsid/setpgid"       "\\[test\\] setsid/setpgid/getpgrp OK"}
+    {"sigaction SIGUSR1"    "\\[test\\] sigaction/kill\\(SIGUSR1\\) OK"}
+    {"sigreturn"            "\\[test\\] sigreturn OK"}
+    {"tmpfs/mount"          "\\[test\\] tmpfs/mount OK"}
+    {"dev null"             "\\[test\\] /dev/null OK"}
+    {"persist counter"      "\\[test\\] /persist/counter="}
+    {"dev tty write"        "\\[test\\] /dev/tty write OK"}
+    {"diskfs test"          "\\[test\\] /disk/test prev="}
+    {"diskfs mkdir/unlink"  "\\[test\\] diskfs mkdir/unlink OK"}
+    {"diskfs getdents"      "\\[test\\] diskfs getdents OK"}
+    {"isatty"               "\\[test\\] isatty OK"}
+    {"O_NONBLOCK"           "\\[test\\] O_NONBLOCK OK"}
+    {"pipe2/dup3"           "\\[test\\] pipe2/dup3 OK"}
+    {"chdir/getcwd"         "\\[test\\] chdir/getcwd OK"}
+    {"*at syscalls"         "\\[test\\] \\*at OK"}
+    {"rename/rmdir"         "\\[test\\] rename/rmdir OK"}
+    {"getdents multi-fs"    "\\[test\\] getdents multi-fs OK"}
+    {"brk heap"             "\\[test\\] brk OK"}
+    {"mmap/munmap"          "\\[test\\] mmap/munmap OK"}
+    {"clock_gettime"        "\\[test\\] clock_gettime OK"}
+    {"dev zero"             "\\[test\\] /dev/zero OK"}
+    {"dev random"           "\\[test\\] /dev/random OK"}
+    {"procfs"               "\\[test\\] procfs OK"}
+    {"pread/pwrite"         "\\[test\\] pread/pwrite OK"}
+    {"ftruncate"            "\\[test\\] ftruncate OK"}
+    {"symlink/readlink"     "\\[test\\] symlink/readlink OK"}
+    {"access"               "\\[test\\] access OK"}
+    {"sigprocmask"          "\\[test\\] sigprocmask/sigpending OK"}
+    {"alarm SIGALRM"        "\\[test\\] alarm/SIGALRM OK"}
+    {"shmget/shmat"         "\\[test\\] shmget/shmat/shmdt OK"}
+    {"O_APPEND"             "\\[test\\] O_APPEND OK"}
+    {"umask"                "\\[test\\] umask OK"}
+    {"pipe capacity"        "\\[test\\] pipe capacity OK"}
+    {"waitid"               "\\[test\\] waitid OK"}
+    {"setitimer/getitimer"  "\\[test\\] setitimer/getitimer OK"}
+    {"select regfile"       "\\[test\\] select regfile OK"}
+    {"poll regfile"         "\\[test\\] poll regfile OK"}
+    {"hard link"            "\\[test\\] hard link OK"}
+    {"epoll"                "\\[test\\] epoll OK"}
+    {"epollet"              "\\[test\\] epollet OK"}
+    {"inotify"              "\\[test\\] inotify OK"}
+    {"aio"                  "\\[test\\] aio OK"}
+    {"nanosleep"            "\\[test\\] nanosleep OK"}
+    {"CLOCK_REALTIME"       "\\[test\\] CLOCK_REALTIME OK"}
+    {"dev urandom"          "\\[test\\] /dev/urandom OK"}
+    {"proc cmdline"         "\\[test\\] /proc/cmdline OK"}
+    {"CoW fork"             "\\[test\\] CoW fork OK"}
+    {"readv/writev"         "\\[test\\] readv/writev OK"}
+    {"fsync"                "\\[test\\] fsync OK"}
+    {"truncate path"        "\\[test\\] truncate OK"}
+    {"getuid/getgid"        "\\[test\\] getuid/getgid OK"}
+    {"chmod"                "\\[test\\] chmod OK"}
+    {"flock"                "\\[test\\] flock OK"}
+    {"times"                "\\[test\\] times OK"}
+    {"gettid"               "\\[test\\] gettid OK"}
+    {"posix_spawn"          "\\[test\\] posix_spawn OK"}
+    {"clock_ns precision"   "\\[test\\] clock_ns precision OK"}
+    {"getppid"              "\\[test\\] getppid OK"}
+    {"waitpid WNOHANG"      "\\[test\\] waitpid WNOHANG OK"}
+    {"SIGSEGV handler"      "\\[test\\] SIGSEGV OK"}
+    {"waitpid 100 children" "\\[test\\] waitpid OK \\(100 children"}
+    {"lazy PLT"             "\\[test\\] lazy PLT OK"}
+    {"PLT cached"           "\\[test\\] PLT cached OK"}
     {"PING network"         "\\[PING\\] .*received.*network OK"}
     {"echo execve"          "\\[echo\\] hello from echo"}
-    {"setuid/setgid"        "\\[init\\] setuid/setgid OK"}
-    {"fcntl F_GETFL/SETFL"  "\\[init\\] fcntl F_GETFL/F_SETFL OK"}
-    {"fcntl FD_CLOEXEC"     "\\[init\\] fcntl FD_CLOEXEC OK"}
-    {"sigsuspend"           "\\[init\\] sigsuspend OK"}
-    {"orphan reparent"      "\\[init\\] orphan reparent OK"}
-    {"proc PID cmdline"    "\\[init\\] /proc/PID/cmdline OK"}
-    {"proc PID status"     "\\[init\\] /proc/PID/status OK"}
-    {"dev console"         "\\[init\\] /dev/console OK"}
-    {"multi-pty"           "\\[init\\] multi-pty OK"}
-    {"dup standalone"      "\\[init\\] dup OK"}
-    {"pipe EOF"            "\\[init\\] pipe EOF OK"}
-    {"readdir /proc"       "\\[init\\] readdir /proc OK"}
-    {"readdir /bin"        "\\[init\\] readdir /bin OK"}
-    {"gettimeofday"        "\\[init\\] gettimeofday OK"}
-    {"mprotect"            "\\[init\\] mprotect OK"}
-    {"madvise"             "\\[init\\] madvise OK"}
-    {"getrlimit/setrlimit" "\\[init\\] getrlimit/setrlimit OK"}
-    {"uname"               "\\[init\\] uname OK"}
-    {"SMP parallel fork"   "\\[init\\] SMP parallel fork OK"}
+    {"setuid/setgid"        "\\[test\\] setuid/setgid OK"}
+    {"fcntl F_GETFL/SETFL"  "\\[test\\] fcntl F_GETFL/F_SETFL OK"}
+    {"fcntl FD_CLOEXEC"     "\\[test\\] fcntl FD_CLOEXEC OK"}
+    {"sigsuspend"           "\\[test\\] sigsuspend OK"}
+    {"orphan reparent"      "\\[test\\] orphan reparent OK"}
+    {"proc PID cmdline"    "\\[test\\] /proc/PID/cmdline OK"}
+    {"proc PID status"     "\\[test\\] /proc/PID/status OK"}
+    {"dev console"         "\\[test\\] /dev/console OK"}
+    {"multi-pty"           "\\[test\\] multi-pty OK"}
+    {"dup standalone"      "\\[test\\] dup OK"}
+    {"pipe EOF"            "\\[test\\] pipe EOF OK"}
+    {"readdir /proc"       "\\[test\\] readdir /proc OK"}
+    {"readdir /bin"        "\\[test\\] readdir /bin OK"}
+    {"gettimeofday"        "\\[test\\] gettimeofday OK"}
+    {"mprotect"            "\\[test\\] mprotect OK"}
+    {"madvise"             "\\[test\\] madvise OK"}
+    {"getrlimit/setrlimit" "\\[test\\] getrlimit/setrlimit OK"}
+    {"uname"               "\\[test\\] uname OK"}
+    {"SMP parallel fork"   "\\[test\\] SMP parallel fork OK"}
     {"LZ4 Frame decomp"    "\\[INITRD\\] LZ4"}
 }
 
index b475d390c6bb03441bed42b97019f7ec4e2fca69..bc997c41947e1b8f8b5fb53ebe274e9feb0c34c7 100644 (file)
@@ -153,8 +153,8 @@ set patterns {
     {"ATA /dev/hda"          "\\[ATA\\] /dev/hda detected"}
     {"INITRD found"          "\\[INITRD\\] Found"}
     {"diskfs mount /disk"    "\\[MOUNT\\] diskfs on /dev/hda"}
-    {"diskfs test"           "\\[init\\] /disk/test prev="}
-    {"diskfs getdents"       "\\[init\\] diskfs getdents OK"}
+    {"diskfs test"           "\\[test\\] /disk/test prev="}
+    {"diskfs getdents"       "\\[test\\] diskfs getdents OK"}
 }
 
 set res [wait_for_patterns $serial_log $timeout_sec $patterns]
index 52bfd6b12e1f78d56fbde8ee6a0e956d1b3ef2ec..7ed2c00b68bf571a1c52abac2dbde019283d6352 100755 (executable)
@@ -40,7 +40,7 @@ echo ""
 
 # ---------- echo ----------
 echo "--- echo ---"
-if compile echo_test user/echo.c; then
+if compile echo_test user/cmds/echo/echo.c; then
     out=$("$BUILDDIR/echo_test" hello world)
     [ "$out" = "hello world" ] && pass "echo basic" || fail "echo basic" "got: $out"
 
@@ -59,7 +59,7 @@ fi
 
 # ---------- cat ----------
 echo "--- cat ---"
-if compile cat_test user/cat.c; then
+if compile cat_test user/cmds/cat/cat.c; then
     echo "hello cat" > "$BUILDDIR/cat_in.txt"
     out=$("$BUILDDIR/cat_test" "$BUILDDIR/cat_in.txt")
     [ "$out" = "hello cat" ] && pass "cat file" || fail "cat file" "got: $out"
@@ -78,7 +78,7 @@ fi
 
 # ---------- head ----------
 echo "--- head ---"
-if compile head_test user/head.c; then
+if compile head_test user/cmds/head/head.c; then
     printf "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n" > "$BUILDDIR/head_in.txt"
     out=$("$BUILDDIR/head_test" "$BUILDDIR/head_in.txt" | wc -l)
     [ "$out" -eq 10 ] && pass "head default 10" || fail "head default 10" "got $out lines"
@@ -96,7 +96,7 @@ fi
 
 # ---------- tail ----------
 echo "--- tail ---"
-if compile tail_test user/tail.c; then
+if compile tail_test user/cmds/tail/tail.c; then
     printf "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n" > "$BUILDDIR/tail_in.txt"
     out=$("$BUILDDIR/tail_test" -n 3 "$BUILDDIR/tail_in.txt")
     expected=$(printf "10\n11\n12")
@@ -107,7 +107,7 @@ fi
 
 # ---------- wc ----------
 echo "--- wc ---"
-if compile wc_test user/wc.c; then
+if compile wc_test user/cmds/wc/wc.c; then
     printf "hello world\nfoo bar baz\n" > "$BUILDDIR/wc_in.txt"
     out=$("$BUILDDIR/wc_test" "$BUILDDIR/wc_in.txt")
     # Should contain line count (2), word count (5), byte count
@@ -119,7 +119,7 @@ fi
 
 # ---------- sort ----------
 echo "--- sort ---"
-if compile sort_test user/sort.c; then
+if compile sort_test user/cmds/sort/sort.c; then
     printf "banana\napple\ncherry\n" | "$BUILDDIR/sort_test" > "$BUILDDIR/sort_out.txt"
     expected=$(printf "apple\nbanana\ncherry")
     out=$(cat "$BUILDDIR/sort_out.txt")
@@ -135,7 +135,7 @@ fi
 
 # ---------- uniq ----------
 echo "--- uniq ---"
-if compile uniq_test user/uniq.c; then
+if compile uniq_test user/cmds/uniq/uniq.c; then
     printf "aaa\naaa\nbbb\nccc\nccc\n" | "$BUILDDIR/uniq_test" > "$BUILDDIR/uniq_out.txt"
     expected=$(printf "aaa\nbbb\nccc")
     out=$(cat "$BUILDDIR/uniq_out.txt")
@@ -150,7 +150,7 @@ fi
 
 # ---------- cut ----------
 echo "--- cut ---"
-if compile cut_test user/cut.c; then
+if compile cut_test user/cmds/cut/cut.c; then
     out=$(printf "a:b:c\n" | "$BUILDDIR/cut_test" -d: -f2)
     [ "$out" = "b" ] && pass "cut -d: -f2" || fail "cut -d: -f2" "got: $out"
 
@@ -165,7 +165,7 @@ fi
 
 # ---------- grep ----------
 echo "--- grep ---"
-if compile grep_test user/grep.c; then
+if compile grep_test user/cmds/grep/grep.c; then
     printf "hello world\nfoo bar\nhello again\n" > "$BUILDDIR/grep_in.txt"
     out=$("$BUILDDIR/grep_test" hello "$BUILDDIR/grep_in.txt")
     lines=$(echo "$out" | wc -l)
@@ -188,7 +188,7 @@ fi
 
 # ---------- tr ----------
 echo "--- tr ---"
-if compile tr_test user/tr.c; then
+if compile tr_test user/cmds/tr/tr.c; then
     out=$(echo "hello" | "$BUILDDIR/tr_test" 'elo' 'ELO')
     [ "$out" = "hELLO" ] && pass "tr translate" || fail "tr translate" "got: $out"
 
@@ -200,7 +200,7 @@ fi
 
 # ---------- basename ----------
 echo "--- basename ---"
-if compile basename_test user/basename.c; then
+if compile basename_test user/cmds/basename/basename.c; then
     out=$("$BUILDDIR/basename_test" /usr/bin/foo)
     [ "$out" = "foo" ] && pass "basename path" || fail "basename path" "got: $out"
 
@@ -215,7 +215,7 @@ fi
 
 # ---------- dirname ----------
 echo "--- dirname ---"
-if compile dirname_test user/dirname.c; then
+if compile dirname_test user/cmds/dirname/dirname.c; then
     out=$("$BUILDDIR/dirname_test" /usr/bin/foo)
     [ "$out" = "/usr/bin" ] && pass "dirname path" || fail "dirname path" "got: $out"
 
@@ -230,7 +230,7 @@ fi
 
 # ---------- tee ----------
 echo "--- tee ---"
-if compile tee_test user/tee.c; then
+if compile tee_test user/cmds/tee/tee.c; then
     out=$(echo "tee test" | "$BUILDDIR/tee_test" "$BUILDDIR/tee_out.txt")
     file_content=$(cat "$BUILDDIR/tee_out.txt")
     [ "$out" = "tee test" ] && pass "tee stdout" || fail "tee stdout" "got: $out"
@@ -247,7 +247,7 @@ fi
 
 # ---------- dd ----------
 echo "--- dd ---"
-if compile dd_test user/dd.c; then
+if compile dd_test user/cmds/dd/dd.c; then
     echo "hello dd test data" > "$BUILDDIR/dd_in.txt"
     "$BUILDDIR/dd_test" if="$BUILDDIR/dd_in.txt" of="$BUILDDIR/dd_out.txt" bs=512 2>/dev/null
     out=$(cat "$BUILDDIR/dd_out.txt")
@@ -258,7 +258,7 @@ fi
 
 # ---------- pwd ----------
 echo "--- pwd ---"
-if compile pwd_test user/pwd.c; then
+if compile pwd_test user/cmds/pwd/pwd.c; then
     out=$("$BUILDDIR/pwd_test")
     expected=$(pwd)
     [ -n "$out" ] && pass "pwd output" || fail "pwd output" "empty"
@@ -268,7 +268,7 @@ fi
 
 # ---------- uname ----------
 echo "--- uname ---"
-if compile uname_test user/uname.c; then
+if compile uname_test user/cmds/uname/uname.c; then
     out=$("$BUILDDIR/uname_test")
     [ "$out" = "AdrOS" ] && pass "uname default" || fail "uname default" "got: $out"
 
@@ -287,7 +287,7 @@ fi
 
 # ---------- id ----------
 echo "--- id ---"
-if compile id_test user/id.c; then
+if compile id_test user/cmds/id/id.c; then
     out=$("$BUILDDIR/id_test")
     echo "$out" | grep -q "uid=" && pass "id uid" || fail "id uid" "got: $out"
     echo "$out" | grep -q "gid=" && pass "id gid" || fail "id gid" "got: $out"
@@ -297,7 +297,7 @@ fi
 
 # ---------- printenv ----------
 echo "--- printenv ---"
-if compile printenv_test user/printenv.c; then
+if compile printenv_test user/cmds/printenv/printenv.c; then
     out=$(HOME=/test/home "$BUILDDIR/printenv_test" HOME)
     [ "$out" = "/test/home" ] && pass "printenv HOME" || fail "printenv HOME" "got: $out"
 
@@ -313,7 +313,7 @@ fi
 
 # ---------- cp ----------
 echo "--- cp ---"
-if compile cp_test user/cp.c; then
+if compile cp_test user/cmds/cp/cp.c; then
     echo "cp source" > "$BUILDDIR/cp_src.txt"
     "$BUILDDIR/cp_test" "$BUILDDIR/cp_src.txt" "$BUILDDIR/cp_dst.txt"
     out=$(cat "$BUILDDIR/cp_dst.txt")
@@ -324,7 +324,7 @@ fi
 
 # ---------- mv ----------
 echo "--- mv ---"
-if compile mv_test user/mv.c; then
+if compile mv_test user/cmds/mv/mv.c; then
     echo "mv data" > "$BUILDDIR/mv_src.txt"
     "$BUILDDIR/mv_test" "$BUILDDIR/mv_src.txt" "$BUILDDIR/mv_dst.txt"
     [ ! -f "$BUILDDIR/mv_src.txt" ] && pass "mv src removed" || fail "mv src removed" "still exists"
@@ -337,10 +337,10 @@ fi
 # ---------- touch/rm/mkdir/rmdir ----------
 echo "--- touch/rm/mkdir/rmdir ---"
 compile_ok=1
-compile touch_test user/touch.c || compile_ok=0
-compile rm_test user/rm.c || compile_ok=0
-compile mkdir_test user/mkdir.c || compile_ok=0
-compile rmdir_test user/rmdir.c || compile_ok=0
+compile touch_test user/cmds/touch/touch.c || compile_ok=0
+compile rm_test user/cmds/rm/rm.c || compile_ok=0
+compile mkdir_test user/cmds/mkdir/mkdir.c || compile_ok=0
+compile rmdir_test user/cmds/rmdir/rmdir.c || compile_ok=0
 if [ "$compile_ok" -eq 1 ]; then
     "$BUILDDIR/touch_test" "$BUILDDIR/touchfile"
     [ -f "$BUILDDIR/touchfile" ] && pass "touch create" || fail "touch create" "not created"
@@ -359,7 +359,7 @@ fi
 
 # ---------- ln ----------
 echo "--- ln ---"
-if compile ln_test user/ln.c; then
+if compile ln_test user/cmds/ln/ln.c; then
     echo "link target" > "$BUILDDIR/ln_src.txt"
     "$BUILDDIR/ln_test" -s "$BUILDDIR/ln_src.txt" "$BUILDDIR/ln_link.txt" 2>/dev/null || true
     if [ -L "$BUILDDIR/ln_link.txt" ]; then
@@ -380,7 +380,7 @@ fi
 
 # ---------- sed ----------
 echo "--- sed ---"
-if compile sed_test user/sed.c; then
+if compile sed_test user/cmds/sed/sed.c; then
     out=$(echo "hello world" | "$BUILDDIR/sed_test" 's/world/earth/')
     [ "$out" = "hello earth" ] && pass "sed s///" || fail "sed s///" "got: $out"
 
@@ -400,7 +400,7 @@ fi
 
 # ---------- awk ----------
 echo "--- awk ---"
-if compile awk_test user/awk.c; then
+if compile awk_test user/cmds/awk/awk.c; then
     out=$(echo "hello world foo" | "$BUILDDIR/awk_test" '{print $2}')
     [ "$out" = "world" ] && pass "awk print \$2" || fail "awk print \$2" "got: $out"
 
@@ -416,7 +416,7 @@ fi
 
 # ---------- who ----------
 echo "--- who ---"
-if compile who_test user/who.c; then
+if compile who_test user/cmds/who/who.c; then
     out=$("$BUILDDIR/who_test")
     echo "$out" | grep -q "root" && pass "who output" || fail "who output" "got: $out"
 else
@@ -425,7 +425,7 @@ fi
 
 # ---------- find ----------
 echo "--- find ---"
-if compile find_test user/find.c; then
+if compile find_test user/cmds/find/find.c; then
     mkdir -p "$BUILDDIR/findtest/sub"
     touch "$BUILDDIR/findtest/a.txt"
     touch "$BUILDDIR/findtest/b.c"
@@ -443,10 +443,10 @@ fi
 
 # ---------- which ----------
 echo "--- which ---"
-if compile which_test user/which.c; then
+if compile which_test user/cmds/which/which.c; then
     # which looks in /bin and /sbin hardcoded, so just check it runs
-    "$BUILDDIR/which_test" nonexistent_cmd > /dev/null 2>&1
-    [ $? -ne 0 ] && pass "which not found" || fail "which not found" "should return nonzero"
+    "$BUILDDIR/which_test" nonexistent_cmd > /dev/null 2>&1 || rc=$?
+    [ "${rc:-1}" -ne 0 ] && pass "which not found" || fail "which not found" "should return nonzero"
 else
     skip "which (compile failed)"
 fi
diff --git a/user/awk.c b/user/awk.c
deleted file mode 100644 (file)
index 15be1cb..0000000
+++ /dev/null
@@ -1,116 +0,0 @@
-/* AdrOS awk utility — minimal: print fields, pattern matching, BEGIN/END */
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <stdlib.h>
-
-static char delim = ' ';
-static int print_field = -1; /* -1 = whole line */
-static char pattern[256] = "";
-static int has_pattern = 0;
-
-static void process_line(const char* line) {
-    if (has_pattern && !strstr(line, pattern)) return;
-
-    if (print_field < 0) {
-        printf("%s\n", line);
-        return;
-    }
-
-    /* Split into fields */
-    char copy[4096];
-    strncpy(copy, line, sizeof(copy) - 1);
-    copy[sizeof(copy) - 1] = '\0';
-
-    int fi = 0;
-    char* p = copy;
-    while (*p) {
-        while (*p && (*p == delim || *p == '\t')) p++;
-        if (!*p) break;
-        char* start = p;
-        while (*p && *p != delim && *p != '\t') p++;
-        if (*p) *p++ = '\0';
-        if (fi == print_field) {
-            printf("%s\n", start);
-            return;
-        }
-        fi++;
-    }
-    printf("\n");
-}
-
-int main(int argc, char** argv) {
-    if (argc < 2) {
-        fprintf(stderr, "Usage: awk [-F sep] '{print $N}' [file]\n");
-        return 1;
-    }
-
-    int argi = 1;
-    if (strcmp(argv[argi], "-F") == 0 && argi + 1 < argc) {
-        delim = argv[argi + 1][0];
-        argi += 2;
-    }
-
-    if (argi >= argc) {
-        fprintf(stderr, "awk: missing program\n");
-        return 1;
-    }
-
-    /* Parse simple program: {print $N} or /pattern/{print $N} */
-    const char* prog = argv[argi++];
-
-    /* Check for /pattern/ prefix */
-    if (prog[0] == '/') {
-        const char* end = strchr(prog + 1, '/');
-        if (end) {
-            int plen = (int)(end - prog - 1);
-            if (plen > 0 && plen < (int)sizeof(pattern)) {
-                memcpy(pattern, prog + 1, plen);
-                pattern[plen] = '\0';
-                has_pattern = 1;
-            }
-            prog = end + 1;
-        }
-    }
-
-    /* Parse {print $N} */
-    const char* pp = strstr(prog, "print");
-    if (pp) {
-        const char* dollar = strchr(pp, '$');
-        if (dollar) {
-            int n = atoi(dollar + 1);
-            print_field = (n > 0) ? n - 1 : -1;
-            if (n == 0) print_field = -1; /* $0 = whole line */
-        }
-    }
-
-    int fd = STDIN_FILENO;
-    if (argi < argc) {
-        fd = open(argv[argi], O_RDONLY);
-        if (fd < 0) {
-            fprintf(stderr, "awk: %s: No such file or directory\n", argv[argi]);
-            return 1;
-        }
-    }
-
-    char line[4096];
-    int li = 0;
-    char c;
-    while (read(fd, &c, 1) == 1) {
-        if (c == '\n') {
-            line[li] = '\0';
-            process_line(line);
-            li = 0;
-        } else if (li < (int)sizeof(line) - 1) {
-            line[li++] = c;
-        }
-    }
-    if (li > 0) {
-        line[li] = '\0';
-        process_line(line);
-    }
-
-    if (fd != STDIN_FILENO) close(fd);
-    return 0;
-}
diff --git a/user/basename.c b/user/basename.c
deleted file mode 100644 (file)
index 8089c29..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-/* AdrOS basename utility — strip directory from filename */
-#include <stdio.h>
-#include <string.h>
-
-int main(int argc, char** argv) {
-    if (argc <= 1) {
-        fprintf(stderr, "usage: basename PATH [SUFFIX]\n");
-        return 1;
-    }
-    char* p = argv[1];
-    /* Remove trailing slashes */
-    int len = (int)strlen(p);
-    while (len > 1 && p[len - 1] == '/') p[--len] = '\0';
-    /* Find last slash */
-    char* base = p;
-    for (char* s = p; *s; s++) {
-        if (*s == '/' && *(s + 1)) base = s + 1;
-    }
-    /* Strip suffix if provided */
-    if (argc > 2) {
-        int blen = (int)strlen(base);
-        int slen = (int)strlen(argv[2]);
-        if (blen > slen && strcmp(base + blen - slen, argv[2]) == 0)
-            base[blen - slen] = '\0';
-    }
-    printf("%s\n", base);
-    return 0;
-}
diff --git a/user/cat.c b/user/cat.c
deleted file mode 100644 (file)
index ad166e2..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-/* AdrOS cat utility */
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <string.h>
-
-static void cat_fd(int fd) {
-    char buf[4096];
-    int r;
-    while ((r = read(fd, buf, sizeof(buf))) > 0) {
-        write(STDOUT_FILENO, buf, (size_t)r);
-    }
-}
-
-int main(int argc, char** argv) {
-    if (argc <= 1) {
-        cat_fd(STDIN_FILENO);
-        return 0;
-    }
-
-    int rc = 0;
-    for (int i = 1; i < argc; i++) {
-        if (strcmp(argv[i], "-") == 0) {
-            cat_fd(STDIN_FILENO);
-            continue;
-        }
-        int fd = open(argv[i], O_RDONLY);
-        if (fd < 0) {
-            fprintf(stderr, "cat: %s: No such file or directory\n", argv[i]);
-            rc = 1;
-            continue;
-        }
-        cat_fd(fd);
-        close(fd);
-    }
-    return rc;
-}
diff --git a/user/chgrp.c b/user/chgrp.c
deleted file mode 100644 (file)
index f74bac6..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-/* AdrOS chgrp utility */
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
-
-int main(int argc, char** argv) {
-    if (argc < 3) {
-        fprintf(stderr, "Usage: chgrp <group> <file>...\n");
-        return 1;
-    }
-
-    int group = atoi(argv[1]);
-    int rc = 0;
-
-    for (int i = 2; i < argc; i++) {
-        if (chown(argv[i], -1, group) < 0) {
-            fprintf(stderr, "chgrp: cannot change group of '%s'\n", argv[i]);
-            rc = 1;
-        }
-    }
-    return rc;
-}
diff --git a/user/chmod.c b/user/chmod.c
deleted file mode 100644 (file)
index 0f6c791..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-/* AdrOS chmod utility */
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-
-int main(int argc, char** argv) {
-    if (argc < 3) {
-        fprintf(stderr, "Usage: chmod <mode> <file>...\n");
-        return 1;
-    }
-
-    int mode = (int)strtol(argv[1], NULL, 8);
-    int rc = 0;
-
-    for (int i = 2; i < argc; i++) {
-        if (chmod(argv[i], mode) < 0) {
-            fprintf(stderr, "chmod: cannot change mode of '%s'\n", argv[i]);
-            rc = 1;
-        }
-    }
-    return rc;
-}
diff --git a/user/chown.c b/user/chown.c
deleted file mode 100644 (file)
index c2d6c9d..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-/* AdrOS chown utility */
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-
-int main(int argc, char** argv) {
-    if (argc < 3) {
-        fprintf(stderr, "Usage: chown <owner[:group]> <file>...\n");
-        return 1;
-    }
-
-    /* Parse owner:group */
-    int owner = -1, group = -1;
-    char* colon = strchr(argv[1], ':');
-    if (colon) {
-        *colon = '\0';
-        if (argv[1][0]) owner = atoi(argv[1]);
-        if (colon[1]) group = atoi(colon + 1);
-    } else {
-        owner = atoi(argv[1]);
-    }
-
-    int rc = 0;
-    for (int i = 2; i < argc; i++) {
-        if (chown(argv[i], owner, group) < 0) {
-            fprintf(stderr, "chown: cannot change owner of '%s'\n", argv[i]);
-            rc = 1;
-        }
-    }
-    return rc;
-}
diff --git a/user/clear.c b/user/clear.c
deleted file mode 100644 (file)
index 619feb4..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-/* AdrOS clear utility — clear the terminal screen */
-#include <unistd.h>
-
-int main(void) {
-    /* ANSI escape: clear screen + move cursor to top-left */
-    write(STDOUT_FILENO, "\033[2J\033[H", 7);
-    return 0;
-}
diff --git a/user/cmds/awk/Makefile b/user/cmds/awk/Makefile
new file mode 100644 (file)
index 0000000..a8e832a
--- /dev/null
@@ -0,0 +1,3 @@
+NAME := awk
+SRCS := awk.c
+include ../common.mk
diff --git a/user/cmds/awk/awk.c b/user/cmds/awk/awk.c
new file mode 100644 (file)
index 0000000..15be1cb
--- /dev/null
@@ -0,0 +1,116 @@
+/* AdrOS awk utility — minimal: print fields, pattern matching, BEGIN/END */
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdlib.h>
+
+static char delim = ' ';
+static int print_field = -1; /* -1 = whole line */
+static char pattern[256] = "";
+static int has_pattern = 0;
+
+static void process_line(const char* line) {
+    if (has_pattern && !strstr(line, pattern)) return;
+
+    if (print_field < 0) {
+        printf("%s\n", line);
+        return;
+    }
+
+    /* Split into fields */
+    char copy[4096];
+    strncpy(copy, line, sizeof(copy) - 1);
+    copy[sizeof(copy) - 1] = '\0';
+
+    int fi = 0;
+    char* p = copy;
+    while (*p) {
+        while (*p && (*p == delim || *p == '\t')) p++;
+        if (!*p) break;
+        char* start = p;
+        while (*p && *p != delim && *p != '\t') p++;
+        if (*p) *p++ = '\0';
+        if (fi == print_field) {
+            printf("%s\n", start);
+            return;
+        }
+        fi++;
+    }
+    printf("\n");
+}
+
+int main(int argc, char** argv) {
+    if (argc < 2) {
+        fprintf(stderr, "Usage: awk [-F sep] '{print $N}' [file]\n");
+        return 1;
+    }
+
+    int argi = 1;
+    if (strcmp(argv[argi], "-F") == 0 && argi + 1 < argc) {
+        delim = argv[argi + 1][0];
+        argi += 2;
+    }
+
+    if (argi >= argc) {
+        fprintf(stderr, "awk: missing program\n");
+        return 1;
+    }
+
+    /* Parse simple program: {print $N} or /pattern/{print $N} */
+    const char* prog = argv[argi++];
+
+    /* Check for /pattern/ prefix */
+    if (prog[0] == '/') {
+        const char* end = strchr(prog + 1, '/');
+        if (end) {
+            int plen = (int)(end - prog - 1);
+            if (plen > 0 && plen < (int)sizeof(pattern)) {
+                memcpy(pattern, prog + 1, plen);
+                pattern[plen] = '\0';
+                has_pattern = 1;
+            }
+            prog = end + 1;
+        }
+    }
+
+    /* Parse {print $N} */
+    const char* pp = strstr(prog, "print");
+    if (pp) {
+        const char* dollar = strchr(pp, '$');
+        if (dollar) {
+            int n = atoi(dollar + 1);
+            print_field = (n > 0) ? n - 1 : -1;
+            if (n == 0) print_field = -1; /* $0 = whole line */
+        }
+    }
+
+    int fd = STDIN_FILENO;
+    if (argi < argc) {
+        fd = open(argv[argi], O_RDONLY);
+        if (fd < 0) {
+            fprintf(stderr, "awk: %s: No such file or directory\n", argv[argi]);
+            return 1;
+        }
+    }
+
+    char line[4096];
+    int li = 0;
+    char c;
+    while (read(fd, &c, 1) == 1) {
+        if (c == '\n') {
+            line[li] = '\0';
+            process_line(line);
+            li = 0;
+        } else if (li < (int)sizeof(line) - 1) {
+            line[li++] = c;
+        }
+    }
+    if (li > 0) {
+        line[li] = '\0';
+        process_line(line);
+    }
+
+    if (fd != STDIN_FILENO) close(fd);
+    return 0;
+}
diff --git a/user/cmds/basename/Makefile b/user/cmds/basename/Makefile
new file mode 100644 (file)
index 0000000..ae20228
--- /dev/null
@@ -0,0 +1,3 @@
+NAME := basename
+SRCS := basename.c
+include ../common.mk
diff --git a/user/cmds/basename/basename.c b/user/cmds/basename/basename.c
new file mode 100644 (file)
index 0000000..8089c29
--- /dev/null
@@ -0,0 +1,28 @@
+/* AdrOS basename utility — strip directory from filename */
+#include <stdio.h>
+#include <string.h>
+
+int main(int argc, char** argv) {
+    if (argc <= 1) {
+        fprintf(stderr, "usage: basename PATH [SUFFIX]\n");
+        return 1;
+    }
+    char* p = argv[1];
+    /* Remove trailing slashes */
+    int len = (int)strlen(p);
+    while (len > 1 && p[len - 1] == '/') p[--len] = '\0';
+    /* Find last slash */
+    char* base = p;
+    for (char* s = p; *s; s++) {
+        if (*s == '/' && *(s + 1)) base = s + 1;
+    }
+    /* Strip suffix if provided */
+    if (argc > 2) {
+        int blen = (int)strlen(base);
+        int slen = (int)strlen(argv[2]);
+        if (blen > slen && strcmp(base + blen - slen, argv[2]) == 0)
+            base[blen - slen] = '\0';
+    }
+    printf("%s\n", base);
+    return 0;
+}
diff --git a/user/cmds/cat/Makefile b/user/cmds/cat/Makefile
new file mode 100644 (file)
index 0000000..9df8bae
--- /dev/null
@@ -0,0 +1,3 @@
+NAME := cat
+SRCS := cat.c
+include ../common.mk
diff --git a/user/cmds/cat/cat.c b/user/cmds/cat/cat.c
new file mode 100644 (file)
index 0000000..ad166e2
--- /dev/null
@@ -0,0 +1,38 @@
+/* AdrOS cat utility */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+
+static void cat_fd(int fd) {
+    char buf[4096];
+    int r;
+    while ((r = read(fd, buf, sizeof(buf))) > 0) {
+        write(STDOUT_FILENO, buf, (size_t)r);
+    }
+}
+
+int main(int argc, char** argv) {
+    if (argc <= 1) {
+        cat_fd(STDIN_FILENO);
+        return 0;
+    }
+
+    int rc = 0;
+    for (int i = 1; i < argc; i++) {
+        if (strcmp(argv[i], "-") == 0) {
+            cat_fd(STDIN_FILENO);
+            continue;
+        }
+        int fd = open(argv[i], O_RDONLY);
+        if (fd < 0) {
+            fprintf(stderr, "cat: %s: No such file or directory\n", argv[i]);
+            rc = 1;
+            continue;
+        }
+        cat_fd(fd);
+        close(fd);
+    }
+    return rc;
+}
diff --git a/user/cmds/chgrp/Makefile b/user/cmds/chgrp/Makefile
new file mode 100644 (file)
index 0000000..489f82f
--- /dev/null
@@ -0,0 +1,3 @@
+NAME := chgrp
+SRCS := chgrp.c
+include ../common.mk
diff --git a/user/cmds/chgrp/chgrp.c b/user/cmds/chgrp/chgrp.c
new file mode 100644 (file)
index 0000000..f74bac6
--- /dev/null
@@ -0,0 +1,22 @@
+/* AdrOS chgrp utility */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+int main(int argc, char** argv) {
+    if (argc < 3) {
+        fprintf(stderr, "Usage: chgrp <group> <file>...\n");
+        return 1;
+    }
+
+    int group = atoi(argv[1]);
+    int rc = 0;
+
+    for (int i = 2; i < argc; i++) {
+        if (chown(argv[i], -1, group) < 0) {
+            fprintf(stderr, "chgrp: cannot change group of '%s'\n", argv[i]);
+            rc = 1;
+        }
+    }
+    return rc;
+}
diff --git a/user/cmds/chmod/Makefile b/user/cmds/chmod/Makefile
new file mode 100644 (file)
index 0000000..6bc44d8
--- /dev/null
@@ -0,0 +1,3 @@
+NAME := chmod
+SRCS := chmod.c
+include ../common.mk
diff --git a/user/cmds/chmod/chmod.c b/user/cmds/chmod/chmod.c
new file mode 100644 (file)
index 0000000..0f6c791
--- /dev/null
@@ -0,0 +1,23 @@
+/* AdrOS chmod utility */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+int main(int argc, char** argv) {
+    if (argc < 3) {
+        fprintf(stderr, "Usage: chmod <mode> <file>...\n");
+        return 1;
+    }
+
+    int mode = (int)strtol(argv[1], NULL, 8);
+    int rc = 0;
+
+    for (int i = 2; i < argc; i++) {
+        if (chmod(argv[i], mode) < 0) {
+            fprintf(stderr, "chmod: cannot change mode of '%s'\n", argv[i]);
+            rc = 1;
+        }
+    }
+    return rc;
+}
diff --git a/user/cmds/chown/Makefile b/user/cmds/chown/Makefile
new file mode 100644 (file)
index 0000000..8378c53
--- /dev/null
@@ -0,0 +1,3 @@
+NAME := chown
+SRCS := chown.c
+include ../common.mk
diff --git a/user/cmds/chown/chown.c b/user/cmds/chown/chown.c
new file mode 100644 (file)
index 0000000..c2d6c9d
--- /dev/null
@@ -0,0 +1,32 @@
+/* AdrOS chown utility */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+int main(int argc, char** argv) {
+    if (argc < 3) {
+        fprintf(stderr, "Usage: chown <owner[:group]> <file>...\n");
+        return 1;
+    }
+
+    /* Parse owner:group */
+    int owner = -1, group = -1;
+    char* colon = strchr(argv[1], ':');
+    if (colon) {
+        *colon = '\0';
+        if (argv[1][0]) owner = atoi(argv[1]);
+        if (colon[1]) group = atoi(colon + 1);
+    } else {
+        owner = atoi(argv[1]);
+    }
+
+    int rc = 0;
+    for (int i = 2; i < argc; i++) {
+        if (chown(argv[i], owner, group) < 0) {
+            fprintf(stderr, "chown: cannot change owner of '%s'\n", argv[i]);
+            rc = 1;
+        }
+    }
+    return rc;
+}
diff --git a/user/cmds/clear/Makefile b/user/cmds/clear/Makefile
new file mode 100644 (file)
index 0000000..043a0be
--- /dev/null
@@ -0,0 +1,3 @@
+NAME := clear
+SRCS := clear.c
+include ../common.mk
diff --git a/user/cmds/clear/clear.c b/user/cmds/clear/clear.c
new file mode 100644 (file)
index 0000000..619feb4
--- /dev/null
@@ -0,0 +1,8 @@
+/* AdrOS clear utility — clear the terminal screen */
+#include <unistd.h>
+
+int main(void) {
+    /* ANSI escape: clear screen + move cursor to top-left */
+    write(STDOUT_FILENO, "\033[2J\033[H", 7);
+    return 0;
+}
diff --git a/user/cmds/common.mk b/user/cmds/common.mk
new file mode 100644 (file)
index 0000000..55e047b
--- /dev/null
@@ -0,0 +1,34 @@
+# Common build rules for AdrOS user commands (dynamically linked)
+# Each program Makefile sets NAME and SRCS, then includes this file.
+#
+# Usage from per-program Makefile:
+#   NAME := echo
+#   SRCS := echo.c
+#   include ../common.mk
+
+TOPDIR   ?= $(abspath ../../..)
+BUILDDIR := $(TOPDIR)/build/user/cmds/$(NAME)
+
+ULIBC_DIR := $(TOPDIR)/user/ulibc
+DYN_CC   ?= i686-elf-gcc -m32 -ffreestanding -nostdlib -O2 -Wall -Wextra -fPIC -fno-plt -I$(ULIBC_DIR)/include
+DYN_LD   ?= i686-elf-ld -m elf_i386 --dynamic-linker=/lib/ld.so -T $(TOPDIR)/user/dyn_linker.ld -L$(ULIBC_DIR) -rpath /lib
+CRT0     ?= $(ULIBC_DIR)/src/crt0.o
+
+OBJS := $(addprefix $(BUILDDIR)/,$(SRCS:.c=.o))
+ELF  := $(BUILDDIR)/$(NAME).elf
+
+all: $(ELF)
+
+$(ELF): $(OBJS)
+       @echo "  LD      $@"
+       @$(DYN_LD) -o $@ $(CRT0) $(OBJS) -lc
+
+$(BUILDDIR)/%.o: %.c
+       @mkdir -p $(BUILDDIR)
+       @echo "  CC      $<"
+       @$(DYN_CC) -c $< -o $@
+
+clean:
+       rm -f $(OBJS) $(ELF)
+
+.PHONY: all clean
diff --git a/user/cmds/cp/Makefile b/user/cmds/cp/Makefile
new file mode 100644 (file)
index 0000000..564ddfd
--- /dev/null
@@ -0,0 +1,3 @@
+NAME := cp
+SRCS := cp.c
+include ../common.mk
diff --git a/user/cmds/cp/cp.c b/user/cmds/cp/cp.c
new file mode 100644 (file)
index 0000000..16fcb52
--- /dev/null
@@ -0,0 +1,41 @@
+/* AdrOS cp utility */
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+
+int main(int argc, char** argv) {
+    if (argc < 3) {
+        fprintf(stderr, "Usage: cp <source> <dest>\n");
+        return 1;
+    }
+
+    int src = open(argv[1], O_RDONLY);
+    if (src < 0) {
+        fprintf(stderr, "cp: cannot open '%s'\n", argv[1]);
+        return 1;
+    }
+
+    int dst = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0644);
+    if (dst < 0) {
+        fprintf(stderr, "cp: cannot create '%s'\n", argv[2]);
+        close(src);
+        return 1;
+    }
+
+    char buf[4096];
+    int r;
+    while ((r = read(src, buf, sizeof(buf))) > 0) {
+        int w = write(dst, buf, (size_t)r);
+        if (w != r) {
+            fprintf(stderr, "cp: write error\n");
+            close(src);
+            close(dst);
+            return 1;
+        }
+    }
+
+    close(src);
+    close(dst);
+    return 0;
+}
diff --git a/user/cmds/cut/Makefile b/user/cmds/cut/Makefile
new file mode 100644 (file)
index 0000000..ec93ea3
--- /dev/null
@@ -0,0 +1,3 @@
+NAME := cut
+SRCS := cut.c
+include ../common.mk
diff --git a/user/cmds/cut/cut.c b/user/cmds/cut/cut.c
new file mode 100644 (file)
index 0000000..47d4d5d
--- /dev/null
@@ -0,0 +1,110 @@
+/* AdrOS cut utility */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#define LINE_MAX 1024
+
+static char delim = '\t';
+static int fields[32];
+static int nfields = 0;
+
+static void parse_fields(const char* spec) {
+    const char* p = spec;
+    while (*p && nfields < 32) {
+        fields[nfields++] = atoi(p);
+        while (*p && *p != ',') p++;
+        if (*p == ',') p++;
+    }
+}
+
+static void cut_line(char* line) {
+    if (nfields == 0) {
+        printf("%s\n", line);
+        return;
+    }
+
+    /* Split line into fields */
+    char* flds[64];
+    int nf = 0;
+    char* p = line;
+    flds[nf++] = p;
+    while (*p && nf < 64) {
+        if (*p == delim) {
+            *p = '\0';
+            flds[nf++] = p + 1;
+        }
+        p++;
+    }
+
+    /* Print requested fields */
+    int first = 1;
+    for (int i = 0; i < nfields; i++) {
+        int idx = fields[i] - 1;  /* 1-based */
+        if (idx >= 0 && idx < nf) {
+            if (!first) write(STDOUT_FILENO, &delim, 1);
+            printf("%s", flds[idx]);
+            first = 0;
+        }
+    }
+    printf("\n");
+}
+
+static void cut_fd(int fd) {
+    char line[LINE_MAX];
+    int pos = 0;
+    char c;
+
+    while (read(fd, &c, 1) > 0) {
+        if (c == '\n') {
+            line[pos] = '\0';
+            cut_line(line);
+            pos = 0;
+        } else if (pos < LINE_MAX - 1) {
+            line[pos++] = c;
+        }
+    }
+    if (pos > 0) {
+        line[pos] = '\0';
+        cut_line(line);
+    }
+}
+
+int main(int argc, char** argv) {
+    int start = 1;
+
+    for (int i = 1; i < argc; i++) {
+        if (strcmp(argv[i], "-d") == 0 && i + 1 < argc) {
+            delim = argv[++i][0];
+            start = i + 1;
+        } else if (strncmp(argv[i], "-d", 2) == 0 && argv[i][2] != '\0') {
+            delim = argv[i][2];
+            start = i + 1;
+        } else if (strcmp(argv[i], "-f") == 0 && i + 1 < argc) {
+            parse_fields(argv[++i]);
+            start = i + 1;
+        } else if (strncmp(argv[i], "-f", 2) == 0 && argv[i][2] != '\0') {
+            parse_fields(argv[i] + 2);
+            start = i + 1;
+        } else if (argv[i][0] != '-') {
+            break;
+        }
+    }
+
+    if (start >= argc) {
+        cut_fd(STDIN_FILENO);
+    } else {
+        for (int i = start; i < argc; i++) {
+            int fd = open(argv[i], O_RDONLY);
+            if (fd < 0) {
+                fprintf(stderr, "cut: cannot open '%s'\n", argv[i]);
+                continue;
+            }
+            cut_fd(fd);
+            close(fd);
+        }
+    }
+    return 0;
+}
diff --git a/user/cmds/date/Makefile b/user/cmds/date/Makefile
new file mode 100644 (file)
index 0000000..85b48d2
--- /dev/null
@@ -0,0 +1,3 @@
+NAME := date
+SRCS := date.c
+include ../common.mk
diff --git a/user/cmds/date/date.c b/user/cmds/date/date.c
new file mode 100644 (file)
index 0000000..859986c
--- /dev/null
@@ -0,0 +1,24 @@
+/* AdrOS date utility */
+#include <stdio.h>
+#include <time.h>
+
+int main(int argc, char** argv) {
+    (void)argc; (void)argv;
+    struct timespec ts;
+    if (clock_gettime(CLOCK_REALTIME, &ts) < 0) {
+        fprintf(stderr, "date: cannot get time\n");
+        return 1;
+    }
+
+    /* Simple epoch seconds display — no timezone or strftime yet */
+    unsigned long sec = ts.tv_sec;
+    unsigned long days = sec / 86400;
+    unsigned long rem = sec % 86400;
+    unsigned long hours = rem / 3600;
+    unsigned long mins = (rem % 3600) / 60;
+    unsigned long secs = rem % 60;
+
+    printf("%lu days since epoch, %02lu:%02lu:%02lu UTC\n",
+           days, hours, mins, secs);
+    return 0;
+}
diff --git a/user/cmds/dd/Makefile b/user/cmds/dd/Makefile
new file mode 100644 (file)
index 0000000..d23eaf8
--- /dev/null
@@ -0,0 +1,3 @@
+NAME := dd
+SRCS := dd.c
+include ../common.mk
diff --git a/user/cmds/dd/dd.c b/user/cmds/dd/dd.c
new file mode 100644 (file)
index 0000000..6e0eccc
--- /dev/null
@@ -0,0 +1,63 @@
+/* AdrOS dd utility — convert and copy a file */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+static int parse_size(const char* s) {
+    int v = atoi(s);
+    int len = (int)strlen(s);
+    if (len > 0) {
+        char suf = s[len - 1];
+        if (suf == 'k' || suf == 'K') v *= 1024;
+        else if (suf == 'm' || suf == 'M') v *= 1024 * 1024;
+    }
+    return v;
+}
+
+int main(int argc, char** argv) {
+    const char* inf = NULL;
+    const char* outf = NULL;
+    int bs = 512;
+    int count = -1;
+
+    for (int i = 1; i < argc; i++) {
+        if (strncmp(argv[i], "if=", 3) == 0) inf = argv[i] + 3;
+        else if (strncmp(argv[i], "of=", 3) == 0) outf = argv[i] + 3;
+        else if (strncmp(argv[i], "bs=", 3) == 0) bs = parse_size(argv[i] + 3);
+        else if (strncmp(argv[i], "count=", 6) == 0) count = atoi(argv[i] + 6);
+    }
+
+    int ifd = STDIN_FILENO;
+    int ofd = STDOUT_FILENO;
+
+    if (inf) {
+        ifd = open(inf, O_RDONLY);
+        if (ifd < 0) { fprintf(stderr, "dd: cannot open '%s'\n", inf); return 1; }
+    }
+    if (outf) {
+        ofd = open(outf, O_WRONLY | O_CREAT | O_TRUNC, 0644);
+        if (ofd < 0) { fprintf(stderr, "dd: cannot open '%s'\n", outf); return 1; }
+    }
+
+    if (bs > 4096) bs = 4096;
+    char buf[4096];
+    int blocks = 0, partial = 0, total = 0;
+
+    while (count < 0 || blocks + partial < count) {
+        int n = read(ifd, buf, (size_t)bs);
+        if (n <= 0) break;
+        write(ofd, buf, (size_t)n);
+        total += n;
+        if (n == bs) blocks++;
+        else partial++;
+    }
+
+    fprintf(stderr, "%d+%d records in\n%d+%d records out\n%d bytes copied\n",
+            blocks, partial, blocks, partial, total);
+
+    if (inf) close(ifd);
+    if (outf) close(ofd);
+    return 0;
+}
diff --git a/user/cmds/df/Makefile b/user/cmds/df/Makefile
new file mode 100644 (file)
index 0000000..10ee75b
--- /dev/null
@@ -0,0 +1,3 @@
+NAME := df
+SRCS := df.c
+include ../common.mk
diff --git a/user/cmds/df/df.c b/user/cmds/df/df.c
new file mode 100644 (file)
index 0000000..81d0a87
--- /dev/null
@@ -0,0 +1,10 @@
+/* AdrOS df utility — display filesystem disk space usage */
+#include <stdio.h>
+
+int main(void) {
+    printf("Filesystem     Size  Used  Avail  Use%%  Mounted on\n");
+    printf("overlayfs         -     -      -     -  /\n");
+    printf("devfs             -     -      -     -  /dev\n");
+    printf("procfs            -     -      -     -  /proc\n");
+    return 0;
+}
diff --git a/user/cmds/dirname/Makefile b/user/cmds/dirname/Makefile
new file mode 100644 (file)
index 0000000..95f2fcd
--- /dev/null
@@ -0,0 +1,3 @@
+NAME := dirname
+SRCS := dirname.c
+include ../common.mk
diff --git a/user/cmds/dirname/dirname.c b/user/cmds/dirname/dirname.c
new file mode 100644 (file)
index 0000000..15e518d
--- /dev/null
@@ -0,0 +1,22 @@
+/* AdrOS dirname utility — strip last component from path */
+#include <stdio.h>
+#include <string.h>
+
+int main(int argc, char** argv) {
+    if (argc <= 1) {
+        fprintf(stderr, "usage: dirname PATH\n");
+        return 1;
+    }
+    char* p = argv[1];
+    int len = (int)strlen(p);
+    /* Remove trailing slashes */
+    while (len > 1 && p[len - 1] == '/') len--;
+    /* Find last slash */
+    while (len > 0 && p[len - 1] != '/') len--;
+    /* Remove trailing slashes from result */
+    while (len > 1 && p[len - 1] == '/') len--;
+    if (len == 0) { printf(".\n"); return 0; }
+    p[len] = '\0';
+    printf("%s\n", p);
+    return 0;
+}
diff --git a/user/cmds/dmesg/Makefile b/user/cmds/dmesg/Makefile
new file mode 100644 (file)
index 0000000..92bebf5
--- /dev/null
@@ -0,0 +1,3 @@
+NAME := dmesg
+SRCS := dmesg.c
+include ../common.mk
diff --git a/user/cmds/dmesg/dmesg.c b/user/cmds/dmesg/dmesg.c
new file mode 100644 (file)
index 0000000..b2aacc4
--- /dev/null
@@ -0,0 +1,18 @@
+/* AdrOS dmesg utility — print kernel ring buffer from /proc/dmesg */
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+int main(void) {
+    int fd = open("/proc/dmesg", O_RDONLY);
+    if (fd < 0) {
+        fprintf(stderr, "dmesg: cannot open /proc/dmesg\n");
+        return 1;
+    }
+    char buf[512];
+    int n;
+    while ((n = read(fd, buf, sizeof(buf))) > 0)
+        write(STDOUT_FILENO, buf, (size_t)n);
+    close(fd);
+    return 0;
+}
diff --git a/user/cmds/du/Makefile b/user/cmds/du/Makefile
new file mode 100644 (file)
index 0000000..5a0ed2c
--- /dev/null
@@ -0,0 +1,3 @@
+NAME := du
+SRCS := du.c
+include ../common.mk
diff --git a/user/cmds/du/du.c b/user/cmds/du/du.c
new file mode 100644 (file)
index 0000000..8fd15c5
--- /dev/null
@@ -0,0 +1,73 @@
+/* AdrOS du utility — estimate file space usage */
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <sys/stat.h>
+
+static int sflag = 0; /* -s: summary only */
+
+static long du_path(const char* path, int print) {
+    struct stat st;
+    if (stat(path, &st) < 0) {
+        fprintf(stderr, "du: cannot access '%s'\n", path);
+        return 0;
+    }
+
+    if (!(st.st_mode & 0040000)) {
+        long blocks = (st.st_size + 511) / 512;
+        if (print && !sflag) printf("%ld\t%s\n", blocks, path);
+        return blocks;
+    }
+
+    int fd = open(path, O_RDONLY);
+    if (fd < 0) return 0;
+
+    long total = 0;
+    char buf[2048];
+    int rc;
+    while ((rc = getdents(fd, buf, sizeof(buf))) > 0) {
+        int off = 0;
+        while (off < rc) {
+            struct dirent* d = (struct dirent*)(buf + off);
+            if (d->d_reclen == 0) break;
+            if (strcmp(d->d_name, ".") != 0 && strcmp(d->d_name, "..") != 0) {
+                char child[512];
+                if (path[strlen(path)-1] == '/')
+                    snprintf(child, sizeof(child), "%s%s", path, d->d_name);
+                else
+                    snprintf(child, sizeof(child), "%s/%s", path, d->d_name);
+                total += du_path(child, print);
+            }
+            off += d->d_reclen;
+        }
+    }
+    close(fd);
+
+    if (print && !sflag) printf("%ld\t%s\n", total, path);
+    return total;
+}
+
+int main(int argc, char** argv) {
+    int argi = 1;
+    while (argi < argc && argv[argi][0] == '-') {
+        const char* f = argv[argi] + 1;
+        while (*f) {
+            if (*f == 's') sflag = 1;
+            f++;
+        }
+        argi++;
+    }
+
+    if (argi >= argc) {
+        long total = du_path(".", 1);
+        if (sflag) printf("%ld\t.\n", total);
+    } else {
+        for (int i = argi; i < argc; i++) {
+            long total = du_path(argv[i], 1);
+            if (sflag) printf("%ld\t%s\n", total, argv[i]);
+        }
+    }
+    return 0;
+}
diff --git a/user/cmds/echo/Makefile b/user/cmds/echo/Makefile
new file mode 100644 (file)
index 0000000..3fef539
--- /dev/null
@@ -0,0 +1,3 @@
+NAME := echo
+SRCS := echo.c
+include ../common.mk
diff --git a/user/cmds/echo/echo.c b/user/cmds/echo/echo.c
new file mode 100644 (file)
index 0000000..0e4496a
--- /dev/null
@@ -0,0 +1,64 @@
+/* AdrOS echo utility — POSIX-compatible */
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+int main(int argc, char** argv) {
+    int nflag = 0;      /* -n: no trailing newline */
+    int eflag = 0;      /* -e: interpret escape sequences */
+    int i = 1;
+
+    /* Parse flags */
+    while (i < argc && argv[i][0] == '-' && argv[i][1] != '\0') {
+        const char* f = argv[i] + 1;
+        int valid = 1;
+        int n = 0, e = 0;
+        while (*f) {
+            if (*f == 'n') n = 1;
+            else if (*f == 'e') e = 1;
+            else if (*f == 'E') { /* no escapes — default */ }
+            else { valid = 0; break; }
+            f++;
+        }
+        if (!valid) break;
+        if (n) nflag = 1;
+        if (e) eflag = 1;
+        i++;
+    }
+
+    int first = 1;
+    for (; i < argc; i++) {
+        if (!first)
+            write(STDOUT_FILENO, " ", 1);
+        first = 0;
+
+        const char* s = argv[i];
+        if (eflag) {
+            while (*s) {
+                if (*s == '\\' && s[1]) {
+                    s++;
+                    char c = *s;
+                    switch (c) {
+                    case 'n': write(STDOUT_FILENO, "\n", 1); break;
+                    case 't': write(STDOUT_FILENO, "\t", 1); break;
+                    case '\\': write(STDOUT_FILENO, "\\", 1); break;
+                    case 'r': write(STDOUT_FILENO, "\r", 1); break;
+                    case 'a': write(STDOUT_FILENO, "\a", 1); break;
+                    default: write(STDOUT_FILENO, "\\", 1);
+                             write(STDOUT_FILENO, &c, 1); break;
+                    }
+                } else {
+                    write(STDOUT_FILENO, s, 1);
+                }
+                s++;
+            }
+        } else {
+            write(STDOUT_FILENO, s, strlen(s));
+        }
+    }
+
+    if (!nflag)
+        write(STDOUT_FILENO, "\n", 1);
+
+    return 0;
+}
diff --git a/user/cmds/env/Makefile b/user/cmds/env/Makefile
new file mode 100644 (file)
index 0000000..c20284b
--- /dev/null
@@ -0,0 +1,3 @@
+NAME := env
+SRCS := env.c
+include ../common.mk
diff --git a/user/cmds/env/env.c b/user/cmds/env/env.c
new file mode 100644 (file)
index 0000000..a7d727a
--- /dev/null
@@ -0,0 +1,21 @@
+/* AdrOS env utility — print environment or run command with modified env */
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+extern char** __environ;
+
+int main(int argc, char** argv) {
+    if (argc <= 1) {
+        /* Print all environment variables */
+        if (__environ) {
+            for (int i = 0; __environ[i]; i++)
+                printf("%s\n", __environ[i]);
+        }
+        return 0;
+    }
+    /* env COMMAND ARGS... — run command with current environment */
+    execve(argv[1], (const char* const*)&argv[1], (const char* const*)__environ);
+    fprintf(stderr, "env: %s: not found\n", argv[1]);
+    return 127;
+}
diff --git a/user/cmds/find/Makefile b/user/cmds/find/Makefile
new file mode 100644 (file)
index 0000000..f0a4194
--- /dev/null
@@ -0,0 +1,3 @@
+NAME := find
+SRCS := find.c
+include ../common.mk
diff --git a/user/cmds/find/find.c b/user/cmds/find/find.c
new file mode 100644 (file)
index 0000000..feb63b3
--- /dev/null
@@ -0,0 +1,96 @@
+/* AdrOS find utility — search for files in directory hierarchy */
+#include <stdio.h>
+#include <string.h>
+#include <dirent.h>
+
+#ifndef DT_DIR
+#define DT_DIR 4
+#endif
+
+static const char* name_pattern = NULL;
+static int type_filter = 0; /* 0=any, 'f'=file, 'd'=dir */
+
+static int match_name(const char* name) {
+    if (!name_pattern) return 1;
+    /* Simple wildcard: *pattern* if pattern has no special chars,
+       or exact match. Support leading/trailing * only. */
+    int plen = (int)strlen(name_pattern);
+    if (plen == 0) return 1;
+
+    const char* pat = name_pattern;
+    int lead_star = (pat[0] == '*');
+    int trail_star = (plen > 1 && pat[plen-1] == '*');
+
+    if (lead_star && trail_star) {
+        char sub[256];
+        int slen = plen - 2;
+        if (slen <= 0) return 1;
+        memcpy(sub, pat + 1, (size_t)slen);
+        sub[slen] = '\0';
+        return strstr(name, sub) != NULL;
+    }
+    if (lead_star) {
+        const char* suffix = pat + 1;
+        int slen = plen - 1;
+        int nlen = (int)strlen(name);
+        if (nlen < slen) return 0;
+        return strcmp(name + nlen - slen, suffix) == 0;
+    }
+    if (trail_star) {
+        return strncmp(name, pat, (size_t)(plen - 1)) == 0;
+    }
+    return strcmp(name, pat) == 0;
+}
+
+static void find_recurse(const char* path) {
+    DIR* dir = opendir(path);
+    if (!dir) return;
+
+    struct dirent* d;
+    while ((d = readdir(dir)) != NULL) {
+        if (strcmp(d->d_name, ".") == 0 || strcmp(d->d_name, "..") == 0)
+            continue;
+
+        char child[512];
+        size_t plen = strlen(path);
+        if (plen > 0 && path[plen - 1] == '/')
+            snprintf(child, sizeof(child), "%s%s", path, d->d_name);
+        else
+            snprintf(child, sizeof(child), "%s/%s", path, d->d_name);
+
+        int is_dir = (d->d_type == DT_DIR);
+        int show = match_name(d->d_name);
+        if (show && type_filter) {
+            if (type_filter == 'f' && is_dir) show = 0;
+            if (type_filter == 'd' && !is_dir) show = 0;
+        }
+        if (show) printf("%s\n", child);
+
+        if (is_dir) {
+            find_recurse(child);
+        }
+    }
+    closedir(dir);
+}
+
+int main(int argc, char** argv) {
+    const char* start = ".";
+    int argi = 1;
+
+    if (argi < argc && argv[argi][0] != '-') {
+        start = argv[argi++];
+    }
+
+    while (argi < argc) {
+        if (strcmp(argv[argi], "-name") == 0 && argi + 1 < argc) {
+            name_pattern = argv[++argi];
+        } else if (strcmp(argv[argi], "-type") == 0 && argi + 1 < argc) {
+            type_filter = argv[++argi][0];
+        }
+        argi++;
+    }
+
+    printf("%s\n", start);
+    find_recurse(start);
+    return 0;
+}
diff --git a/user/cmds/free/Makefile b/user/cmds/free/Makefile
new file mode 100644 (file)
index 0000000..0282294
--- /dev/null
@@ -0,0 +1,3 @@
+NAME := free
+SRCS := free.c
+include ../common.mk
diff --git a/user/cmds/free/free.c b/user/cmds/free/free.c
new file mode 100644 (file)
index 0000000..b07a8bc
--- /dev/null
@@ -0,0 +1,17 @@
+/* AdrOS free utility — display memory usage from /proc/meminfo */
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+int main(void) {
+    int fd = open("/proc/meminfo", O_RDONLY);
+    if (fd >= 0) {
+        char buf[512];
+        int n = read(fd, buf, sizeof(buf) - 1);
+        if (n > 0) { buf[n] = '\0'; printf("%s", buf); }
+        close(fd);
+    } else {
+        printf("free: /proc/meminfo not available\n");
+    }
+    return 0;
+}
diff --git a/user/cmds/fulltest/Makefile b/user/cmds/fulltest/Makefile
new file mode 100644 (file)
index 0000000..ddeeb20
--- /dev/null
@@ -0,0 +1,21 @@
+# fulltest — statically linked smoke test binary
+NAME := fulltest
+
+TOPDIR   ?= $(abspath ../../..)
+BUILDDIR := $(TOPDIR)/build/user/cmds/$(NAME)
+USER_CC  ?= i686-elf-gcc
+
+ELF := $(BUILDDIR)/fulltest.elf
+
+all: $(ELF)
+
+$(ELF): fulltest.c errno.c user_errno.h
+       @mkdir -p $(BUILDDIR)
+       @echo "  CC+LD   $@"
+       @$(USER_CC) -m32 -I $(TOPDIR)/include -ffreestanding -fno-pie -no-pie -nostdlib \
+               -Wl,-T,$(TOPDIR)/user/linker.ld -o $@ fulltest.c errno.c
+
+clean:
+       rm -f $(ELF)
+
+.PHONY: all clean
diff --git a/user/cmds/fulltest/errno.c b/user/cmds/fulltest/errno.c
new file mode 100644 (file)
index 0000000..fbc6e36
--- /dev/null
@@ -0,0 +1,4 @@
+/* Per-process errno: each fork()ed process gets its own copy in its
+   address space.  When true threads (clone) are added, this must become
+   __thread int errno or use a TLS segment (GS/FS). */
+int errno = 0;
diff --git a/user/cmds/fulltest/fulltest.c b/user/cmds/fulltest/fulltest.c
new file mode 100644 (file)
index 0000000..b5b1e27
--- /dev/null
@@ -0,0 +1,4464 @@
+#include <stdint.h>
+
+#ifdef SIGKILL
+#undef SIGKILL
+#endif
+#ifdef SIGUSR1
+#undef SIGUSR1
+#endif
+#ifdef SIGSEGV
+#undef SIGSEGV
+#endif
+#ifdef SIGTTIN
+#undef SIGTTIN
+#endif
+#ifdef SIGTTOU
+#undef SIGTTOU
+#endif
+
+#ifdef WNOHANG
+#undef WNOHANG
+#endif
+#ifdef SEEK_SET
+#undef SEEK_SET
+#endif
+#ifdef SEEK_CUR
+#undef SEEK_CUR
+#endif
+#ifdef SEEK_END
+#undef SEEK_END
+#endif
+
+#include "user_errno.h"
+
+#include "signal.h"
+
+enum {
+    SYSCALL_WRITE = 1,
+    SYSCALL_EXIT  = 2,
+    SYSCALL_GETPID = 3,
+    SYSCALL_OPEN  = 4,
+    SYSCALL_READ  = 5,
+    SYSCALL_CLOSE = 6,
+    SYSCALL_WAITPID = 7,
+    SYSCALL_LSEEK = 9,
+    SYSCALL_FSTAT = 10,
+    SYSCALL_STAT = 11,
+
+    SYSCALL_DUP = 12,
+    SYSCALL_DUP2 = 13,
+    SYSCALL_PIPE = 14,
+    SYSCALL_PIPE2 = 34,
+    SYSCALL_EXECVE = 15,
+    SYSCALL_FORK = 16,
+    SYSCALL_GETPPID = 17,
+    SYSCALL_POLL = 18,
+    SYSCALL_KILL = 19,
+    SYSCALL_SELECT = 20,
+    SYSCALL_IOCTL = 21,
+    SYSCALL_SETSID = 22,
+    SYSCALL_SETPGID = 23,
+    SYSCALL_GETPGRP = 24,
+
+    SYSCALL_SIGACTION = 25,
+    SYSCALL_SIGPROCMASK = 26,
+    SYSCALL_SIGRETURN = 27,
+
+    SYSCALL_MKDIR = 28,
+    SYSCALL_UNLINK = 29,
+
+    SYSCALL_GETDENTS = 30,
+
+    SYSCALL_FCNTL = 31,
+
+    SYSCALL_CHDIR = 32,
+    SYSCALL_GETCWD = 33,
+    SYSCALL_DUP3 = 35,
+
+    SYSCALL_OPENAT = 36,
+    SYSCALL_FSTATAT = 37,
+    SYSCALL_UNLINKAT = 38,
+
+    SYSCALL_RENAME = 39,
+    SYSCALL_RMDIR = 40,
+
+    SYSCALL_BRK = 41,
+    SYSCALL_NANOSLEEP = 42,
+    SYSCALL_CLOCK_GETTIME = 43,
+    SYSCALL_MMAP = 44,
+    SYSCALL_MUNMAP = 45,
+
+    SYSCALL_SHMGET = 46,
+    SYSCALL_SHMAT  = 47,
+    SYSCALL_SHMDT  = 48,
+
+    SYSCALL_LINK     = 54,
+    SYSCALL_SYMLINK  = 55,
+    SYSCALL_READLINK = 56,
+
+    SYSCALL_SIGPENDING = 71,
+    SYSCALL_PREAD  = 72,
+    SYSCALL_PWRITE = 73,
+    SYSCALL_ACCESS = 74,
+    SYSCALL_TRUNCATE  = 78,
+    SYSCALL_FTRUNCATE = 79,
+    SYSCALL_UMASK  = 75,
+    SYSCALL_ALARM  = 83,
+    SYSCALL_SETITIMER = 92,
+    SYSCALL_GETITIMER = 93,
+    SYSCALL_WAITID    = 94,
+
+    SYSCALL_EPOLL_CREATE = 112,
+    SYSCALL_EPOLL_CTL    = 113,
+    SYSCALL_EPOLL_WAIT   = 114,
+
+    SYSCALL_INOTIFY_INIT      = 115,
+    SYSCALL_INOTIFY_ADD_WATCH = 116,
+    SYSCALL_INOTIFY_RM_WATCH  = 117,
+
+    SYSCALL_AIO_READ    = 121,
+    SYSCALL_AIO_WRITE   = 122,
+    SYSCALL_AIO_ERROR   = 123,
+    SYSCALL_AIO_RETURN  = 124,
+
+    SYSCALL_CHMOD  = 50,
+    SYSCALL_CHOWN  = 51,
+    SYSCALL_GETUID = 52,
+    SYSCALL_GETGID = 53,
+    SYSCALL_CLONE  = 67,
+    SYSCALL_GETTID = 68,
+    SYSCALL_FSYNC  = 69,
+    SYSCALL_READV  = 81,
+    SYSCALL_WRITEV = 82,
+    SYSCALL_TIMES  = 84,
+    SYSCALL_FUTEX  = 85,
+    SYSCALL_FLOCK  = 87,
+    SYSCALL_GETEUID = 88,
+    SYSCALL_GETEGID = 89,
+    SYSCALL_SETEUID = 90,
+    SYSCALL_SETEGID = 91,
+    SYSCALL_SIGSUSPEND = 80,
+    SYSCALL_SIGQUEUE   = 95,
+    SYSCALL_POSIX_SPAWN = 96,
+    SYSCALL_SETUID = 76,
+    SYSCALL_SETGID = 77,
+    SYSCALL_MOUNT  = 126,
+    SYSCALL_GETTIMEOFDAY = 127,
+    SYSCALL_MPROTECT     = 128,
+    SYSCALL_GETRLIMIT    = 129,
+    SYSCALL_SETRLIMIT    = 130,
+    SYSCALL_UNAME        = 136,
+    SYSCALL_MADVISE      = 140,
+};
+
+enum {
+    AT_FDCWD = -100,
+};
+
+enum {
+    F_GETFD = 1,
+    F_SETFD = 2,
+    F_GETFL = 3,
+    F_SETFL = 4,
+    F_GETPIPE_SZ = 1032,
+    F_SETPIPE_SZ = 1033,
+    FD_CLOEXEC = 1,
+};
+
+enum {
+    O_CLOEXEC = 0x80000,
+};
+
+enum {
+    TCGETS = 0x5401,
+    TCSETS = 0x5402,
+    TIOCGPGRP = 0x540F,
+    TIOCSPGRP = 0x5410,
+};
+
+enum {
+    ENOTTY = 25,
+};
+
+enum {
+    ICANON = 0x0002,
+    ECHO   = 0x0008,
+};
+
+#define USER_NCCS 11
+
+struct termios {
+    uint32_t c_iflag;
+    uint32_t c_oflag;
+    uint32_t c_cflag;
+    uint32_t c_lflag;
+    uint8_t  c_cc[USER_NCCS];
+};
+
+struct pollfd {
+    int fd;
+    int16_t events;
+    int16_t revents;
+};
+
+enum {
+    POLLIN  = 0x0001,
+    POLLOUT = 0x0004,
+    EPOLLET = (1U << 31),
+};
+
+enum {
+    SIGKILL = 9,
+    SIGUSR1 = 10,
+    SIGSEGV = 11,
+    SIGTTIN = 21,
+    SIGTTOU = 22,
+};
+
+enum {
+    WNOHANG = 1,
+};
+
+enum {
+    SEEK_SET = 0,
+    SEEK_CUR = 1,
+    SEEK_END = 2,
+};
+
+enum {
+    O_CREAT = 0x40,
+    O_TRUNC = 0x200,
+    O_NONBLOCK = 0x800,
+    O_APPEND = 0x400,
+    O_RDWR = 0x02,
+};
+
+enum {
+    PROT_READ  = 0x1,
+    PROT_WRITE = 0x2,
+};
+
+enum {
+    MAP_PRIVATE   = 0x02,
+    MAP_ANONYMOUS = 0x20,
+    MAP_FAILED_VAL = 0xFFFFFFFF,
+};
+
+enum {
+    CLOCK_REALTIME  = 0,
+    CLOCK_MONOTONIC = 1,
+};
+
+struct timespec {
+    uint32_t tv_sec;
+    uint32_t tv_nsec;
+};
+
+enum {
+    R_OK = 4,
+    W_OK = 2,
+    F_OK = 0,
+};
+
+enum {
+    SIG_BLOCK   = 0,
+    SIG_UNBLOCK = 1,
+    SIG_SETMASK = 2,
+};
+
+enum {
+    IPC_CREAT   = 01000,
+    IPC_PRIVATE = 0,
+};
+
+enum {
+    SIGALRM = 14,
+};
+
+enum {
+    EAGAIN = 11,
+    EINVAL = 22,
+    EEXIST = 17,
+};
+
+enum {
+    ITIMER_REAL    = 0,
+    ITIMER_VIRTUAL = 1,
+    ITIMER_PROF    = 2,
+};
+
+struct timeval {
+    uint32_t tv_sec;
+    uint32_t tv_usec;
+};
+
+struct rlimit {
+    uint32_t rlim_cur;
+    uint32_t rlim_max;
+};
+
+enum {
+    RLIMIT_CPU    = 0,
+    RLIMIT_FSIZE  = 1,
+    RLIMIT_DATA   = 2,
+    RLIMIT_STACK  = 3,
+    RLIMIT_CORE   = 4,
+    RLIMIT_NOFILE = 5,
+    RLIMIT_AS     = 6,
+    RLIMIT_NPROC  = 7,
+    RLIM_INFINITY = 0xFFFFFFFFU,
+};
+
+struct itimerval {
+    struct timeval it_interval;
+    struct timeval it_value;
+};
+
+enum {
+    P_ALL  = 0,
+    P_PID  = 1,
+    P_PGID = 2,
+    WEXITED = 4,
+};
+
+#define S_IFMT  0170000
+#define S_IFREG 0100000
+
+struct stat {
+    uint32_t st_ino;
+    uint32_t st_mode;
+    uint32_t st_nlink;
+    uint32_t st_uid;
+    uint32_t st_gid;
+    uint32_t st_size;
+};
+
+static int sys_write(int fd, const void* buf, uint32_t len) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_WRITE), "b"(fd), "c"(buf), "d"(len)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_openat(int dirfd, const char* path, uint32_t flags, uint32_t mode) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_OPENAT), "b"(dirfd), "c"(path), "d"(flags), "S"(mode)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_fstatat(int dirfd, const char* path, struct stat* st, uint32_t flags) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_FSTATAT), "b"(dirfd), "c"(path), "d"(st), "S"(flags)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_unlinkat(int dirfd, const char* path, uint32_t flags) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_UNLINKAT), "b"(dirfd), "c"(path), "d"(flags)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_rename(const char* oldpath, const char* newpath) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_RENAME), "b"(oldpath), "c"(newpath)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_rmdir(const char* path) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_RMDIR), "b"(path)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_pipe2(int fds[2], uint32_t flags) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_PIPE2), "b"(fds), "c"(flags)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_chdir(const char* path) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_CHDIR), "b"(path)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_getcwd(char* buf, uint32_t size) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_GETCWD), "b"(buf), "c"(size)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_fcntl(int fd, int cmd, uint32_t arg) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_FCNTL), "b"(fd), "c"(cmd), "d"(arg)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_ioctl(int fd, uint32_t cmd, void* arg);
+
+static int isatty_fd(int fd) {
+    struct termios t;
+    if (sys_ioctl(fd, TCGETS, &t) < 0) {
+        if (errno == ENOTTY) return 0;
+        return -1;
+    }
+    return 1;
+}
+
+static int sys_getdents(int fd, void* buf, uint32_t len) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_GETDENTS), "b"(fd), "c"(buf), "d"(len)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static void write_int_dec(int v) {
+    char buf[16];
+    int i = 0;
+    if (v == 0) {
+        buf[i++] = '0';
+    } else {
+        int neg = 0;
+        if (v < 0) {
+            neg = 1;
+            v = -v;
+        }
+        while (v > 0 && i < (int)sizeof(buf)) {
+            buf[i++] = (char)('0' + (v % 10));
+            v /= 10;
+        }
+        if (neg && i < (int)sizeof(buf)) {
+            buf[i++] = '-';
+        }
+        for (int j = 0; j < i / 2; j++) {
+            char t = buf[j];
+            buf[j] = buf[i - 1 - j];
+            buf[i - 1 - j] = t;
+        }
+    }
+    (void)sys_write(1, buf, (uint32_t)i);
+}
+
+static void write_hex8(uint8_t v) {
+    static const char hex[] = "0123456789ABCDEF";
+    char b[2];
+    b[0] = hex[(v >> 4) & 0xF];
+    b[1] = hex[v & 0xF];
+    (void)sys_write(1, b, 2);
+}
+
+static void write_hex32(uint32_t v) {
+    static const char hex[] = "0123456789ABCDEF";
+    char b[8];
+    for (int i = 0; i < 8; i++) {
+        uint32_t shift = (uint32_t)(28 - 4 * i);
+        b[i] = hex[(v >> shift) & 0xFU];
+    }
+    (void)sys_write(1, b, 8);
+}
+
+static int memeq(const void* a, const void* b, uint32_t n) {
+    const uint8_t* x = (const uint8_t*)a;
+    const uint8_t* y = (const uint8_t*)b;
+    for (uint32_t i = 0; i < n; i++) {
+        if (x[i] != y[i]) return 0;
+    }
+    return 1;
+}
+
+static int streq(const char* a, const char* b) {
+    if (!a || !b) return 0;
+    uint32_t i = 0;
+    while (a[i] != 0 && b[i] != 0) {
+        if (a[i] != b[i]) return 0;
+        i++;
+    }
+    return a[i] == b[i];
+}
+
+static int sys_sigaction2(int sig, const struct sigaction* act, struct sigaction* oldact) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_SIGACTION), "b"(sig), "c"(act), "d"(oldact)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_sigaction(int sig, void (*handler)(int), uintptr_t* old_out) {
+    struct sigaction act;
+    act.sa_handler = (uintptr_t)handler;
+    act.sa_sigaction = 0;
+    act.sa_mask = 0;
+    act.sa_flags = 0;
+
+    struct sigaction oldact;
+    struct sigaction* oldp = old_out ? &oldact : 0;
+
+    int r = sys_sigaction2(sig, &act, oldp);
+    if (r < 0) return r;
+    if (old_out) {
+        *old_out = oldact.sa_handler;
+    }
+    return 0;
+}
+
+static int sys_select(uint32_t nfds, uint64_t* readfds, uint64_t* writefds, uint64_t* exceptfds, int32_t timeout) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_SELECT), "b"(nfds), "c"(readfds), "d"(writefds), "S"(exceptfds), "D"(timeout)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_ioctl(int fd, uint32_t cmd, void* arg) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_IOCTL), "b"(fd), "c"(cmd), "d"(arg)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_kill(int pid, int sig) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_KILL), "b"(pid), "c"(sig)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_poll(struct pollfd* fds, uint32_t nfds, int32_t timeout) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_POLL), "b"(fds), "c"(nfds), "d"(timeout)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_setsid(void) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_SETSID)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_setpgid(int pid, int pgid) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_SETPGID), "b"(pid), "c"(pgid)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_getpgrp(void) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_GETPGRP)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_getpid(void) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_GETPID)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_getppid(void) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_GETPPID)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_fork(void) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_FORK)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_execve(const char* path, const char* const* argv, const char* const* envp) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_EXECVE), "b"(path), "c"(argv), "d"(envp)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_pipe(int fds[2]) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_PIPE), "b"(fds)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_dup(int oldfd) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_DUP), "b"(oldfd)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_dup2(int oldfd, int newfd) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_DUP2), "b"(oldfd), "c"(newfd)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_dup3(int oldfd, int newfd, uint32_t flags) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_DUP3), "b"(oldfd), "c"(newfd), "d"(flags)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_waitpid(int pid, int* status, uint32_t options) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_WAITPID), "b"(pid), "c"(status), "d"(options)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_open(const char* path, uint32_t flags) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_OPEN), "b"(path), "c"(flags)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_mkdir(const char* path) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_MKDIR), "b"(path)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_unlink(const char* path) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_UNLINK), "b"(path)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_read(int fd, void* buf, uint32_t len) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_READ), "b"(fd), "c"(buf), "d"(len)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_close(int fd) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_CLOSE), "b"(fd)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_lseek(int fd, int32_t offset, int whence) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_LSEEK), "b"(fd), "c"(offset), "d"(whence)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_fstat(int fd, struct stat* st) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_FSTAT), "b"(fd), "c"(st)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_stat(const char* path, struct stat* st) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_STAT), "b"(path), "c"(st)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static uintptr_t sys_brk(uintptr_t addr) {
+    uintptr_t ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_BRK), "b"(addr)
+        : "memory"
+    );
+    return ret;
+}
+
+static uintptr_t sys_mmap(uintptr_t addr, uint32_t len, uint32_t prot, uint32_t flags, int fd) {
+    uintptr_t ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_MMAP), "b"(addr), "c"(len), "d"(prot), "S"(flags), "D"(fd)
+        : "memory"
+    );
+    return ret;
+}
+
+static int sys_munmap(uintptr_t addr, uint32_t len) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_MUNMAP), "b"(addr), "c"(len)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_clock_gettime(uint32_t clk_id, struct timespec* tp) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_CLOCK_GETTIME), "b"(clk_id), "c"(tp)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_pread(int fd, void* buf, uint32_t count, uint32_t offset) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_PREAD), "b"(fd), "c"(buf), "d"(count), "S"(offset)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_pwrite(int fd, const void* buf, uint32_t count, uint32_t offset) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_PWRITE), "b"(fd), "c"(buf), "d"(count), "S"(offset)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_ftruncate(int fd, uint32_t length) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_FTRUNCATE), "b"(fd), "c"(length)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_symlink(const char* target, const char* linkpath) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_SYMLINK), "b"(target), "c"(linkpath)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_readlink(const char* path, char* buf, uint32_t bufsiz) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_READLINK), "b"(path), "c"(buf), "d"(bufsiz)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_access(const char* path, uint32_t mode) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_ACCESS), "b"(path), "c"(mode)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_sigprocmask(int how, uint32_t mask, uint32_t* oldset) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_SIGPROCMASK), "b"(how), "c"(mask), "d"(oldset)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_sigpending(uint32_t* set) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_SIGPENDING), "b"(set)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static uint32_t sys_alarm(uint32_t seconds) {
+    uint32_t ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_ALARM), "b"(seconds)
+        : "memory"
+    );
+    return ret;
+}
+
+static int sys_shmget(uint32_t key, uint32_t size, uint32_t flags) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_SHMGET), "b"(key), "c"(size), "d"(flags)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static uintptr_t sys_shmat(int shmid, uintptr_t addr, uint32_t flags) {
+    uintptr_t ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_SHMAT), "b"(shmid), "c"(addr), "d"(flags)
+        : "memory"
+    );
+    return ret;
+}
+
+static int sys_shmdt(uintptr_t addr) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_SHMDT), "b"(addr)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_link(const char* oldpath, const char* newpath) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_LINK), "b"(oldpath), "c"(newpath)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_umask(uint32_t mask) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_UMASK), "b"(mask)
+        : "memory"
+    );
+    return ret;
+}
+
+static int sys_setitimer(int which, const struct itimerval* newval, struct itimerval* oldval) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_SETITIMER), "b"(which), "c"(newval), "d"(oldval)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_getitimer(int which, struct itimerval* cur) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_GETITIMER), "b"(which), "c"(cur)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_waitid(uint32_t idtype, uint32_t id, void* infop, uint32_t options) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_WAITID), "b"(idtype), "c"(id), "d"(infop), "S"(options)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_epoll_create(int size) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_EPOLL_CREATE), "b"(size)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_epoll_ctl(int epfd, int op, int fd, void* event) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_EPOLL_CTL), "b"(epfd), "c"(op), "d"(fd), "S"(event)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_epoll_wait(int epfd, void* events, int maxevents, int timeout) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_EPOLL_WAIT), "b"(epfd), "c"(events), "d"(maxevents), "S"(timeout)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_inotify_init(void) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_INOTIFY_INIT)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_inotify_add_watch(int fd, const char* path, uint32_t mask) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_INOTIFY_ADD_WATCH), "b"(fd), "c"(path), "d"(mask)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_inotify_rm_watch(int fd, int wd) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_INOTIFY_RM_WATCH), "b"(fd), "c"(wd)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+struct aiocb {
+    int      aio_fildes;
+    void*    aio_buf;
+    uint32_t aio_nbytes;
+    uint32_t aio_offset;
+    int32_t  aio_error;
+    int32_t  aio_return;
+};
+
+static int sys_aio_read(struct aiocb* cb) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_AIO_READ), "b"(cb)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_aio_write(struct aiocb* cb) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_AIO_WRITE), "b"(cb)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_aio_error(struct aiocb* cb) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_AIO_ERROR), "b"(cb)
+        : "memory"
+    );
+    return ret;
+}
+
+static int sys_aio_return(struct aiocb* cb) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_AIO_RETURN), "b"(cb)
+        : "memory"
+    );
+    return ret;
+}
+
+static int sys_nanosleep(const struct timespec* req, struct timespec* rem) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_NANOSLEEP), "b"(req), "c"(rem)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static uint32_t sys_getuid(void) {
+    uint32_t ret;
+    __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_GETUID) : "memory");
+    return ret;
+}
+
+static uint32_t sys_getgid(void) {
+    uint32_t ret;
+    __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_GETGID) : "memory");
+    return ret;
+}
+
+static uint32_t sys_geteuid(void) {
+    uint32_t ret;
+    __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_GETEUID) : "memory");
+    return ret;
+}
+
+static uint32_t sys_getegid(void) {
+    uint32_t ret;
+    __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_GETEGID) : "memory");
+    return ret;
+}
+
+static int sys_gettid(void) {
+    int ret;
+    __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_GETTID) : "memory");
+    return ret;
+}
+
+static int sys_fsync(int fd) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_FSYNC), "b"(fd)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_truncate(const char* path, uint32_t length) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_TRUNCATE), "b"(path), "c"(length)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_chmod(const char* path, uint32_t mode) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_CHMOD), "b"(path), "c"(mode)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_flock(int fd, int operation) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_FLOCK), "b"(fd), "c"(operation)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+struct iovec {
+    void*    iov_base;
+    uint32_t iov_len;
+};
+
+static int sys_writev(int fd, const struct iovec* iov, int iovcnt) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_WRITEV), "b"(fd), "c"(iov), "d"(iovcnt)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_readv(int fd, const struct iovec* iov, int iovcnt) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_READV), "b"(fd), "c"(iov), "d"(iovcnt)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static uint32_t sys_times(void* buf) {
+    uint32_t ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_TIMES), "b"(buf)
+        : "memory"
+    );
+    return ret;
+}
+
+static int sys_posix_spawn(uint32_t* pid_out, const char* path,
+                           const char* const* argv, const char* const* envp) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_POSIX_SPAWN), "b"(pid_out), "c"(path), "d"(argv), "S"(envp)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_setuid(uint32_t uid) {
+    int ret;
+    __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_SETUID), "b"(uid) : "memory");
+    return __syscall_fix(ret);
+}
+
+static int sys_setgid(uint32_t gid) {
+    int ret;
+    __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_SETGID), "b"(gid) : "memory");
+    return __syscall_fix(ret);
+}
+
+static int sys_seteuid(uint32_t euid) {
+    int ret;
+    __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_SETEUID), "b"(euid) : "memory");
+    return __syscall_fix(ret);
+}
+
+static int sys_setegid(uint32_t egid) {
+    int ret;
+    __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_SETEGID), "b"(egid) : "memory");
+    return __syscall_fix(ret);
+}
+
+static int sys_sigsuspend(const uint32_t* mask) {
+    int ret;
+    __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_SIGSUSPEND), "b"(mask) : "memory");
+    return __syscall_fix(ret);
+}
+
+static int sys_gettimeofday(struct timeval* tv) {
+    int ret;
+    __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_GETTIMEOFDAY), "b"(tv), "c"(0) : "memory");
+    return __syscall_fix(ret);
+}
+
+static int sys_mprotect(uintptr_t addr, uint32_t len, uint32_t prot) {
+    int ret;
+    __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_MPROTECT), "b"(addr), "c"(len), "d"(prot) : "memory");
+    return __syscall_fix(ret);
+}
+
+static int sys_madvise(uintptr_t addr, uint32_t len, uint32_t advice) {
+    int ret;
+    __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_MADVISE), "b"(addr), "c"(len), "d"(advice) : "memory");
+    return __syscall_fix(ret);
+}
+
+static int sys_getrlimit(int resource, struct rlimit* rlim) {
+    int ret;
+    __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_GETRLIMIT), "b"(resource), "c"(rlim) : "memory");
+    return __syscall_fix(ret);
+}
+
+struct utsname {
+    char sysname[65];
+    char nodename[65];
+    char release[65];
+    char version[65];
+    char machine[65];
+};
+
+static int sys_uname(struct utsname* buf) {
+    int ret;
+    __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_UNAME), "b"(buf) : "memory");
+    return __syscall_fix(ret);
+}
+
+static int sys_setrlimit(int resource, const struct rlimit* rlim) {
+    int ret;
+    __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_SETRLIMIT), "b"(resource), "c"(rlim) : "memory");
+    return __syscall_fix(ret);
+}
+
+__attribute__((noreturn)) static void sys_exit(int code) {
+    __asm__ volatile(
+        "int $0x80\n"
+        "1: jmp 1b\n"
+        :
+        : "a"(SYSCALL_EXIT), "b"(code)
+        : "memory"
+    );
+    for (;;) {
+        __asm__ volatile("hlt");
+    }
+}
+
+static volatile int got_usr1 = 0;
+static volatile int got_usr1_ret = 0;
+static volatile int got_ttin = 0;
+static volatile int got_ttou = 0;
+static volatile int got_alrm = 0;
+
+static void usr1_handler(int sig) {
+    (void)sig;
+    got_usr1 = 1;
+    sys_write(1, "[test] SIGUSR1 handler OK\n",
+              (uint32_t)(sizeof("[test] SIGUSR1 handler OK\n") - 1));
+}
+
+static void usr1_ret_handler(int sig) {
+    (void)sig;
+    got_usr1_ret = 1;
+}
+
+static void alrm_handler(int sig) {
+    (void)sig;
+    got_alrm = 1;
+}
+
+static void ttin_handler(int sig) {
+    (void)sig;
+    got_ttin = 1;
+}
+
+static void ttou_handler(int sig) {
+    (void)sig;
+    got_ttou = 1;
+}
+
+static void sigsegv_exit_handler(int sig) {
+    (void)sig;
+    static const char msg[] = "[test] SIGSEGV handler invoked\n";
+    (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1));
+    sys_exit(0);
+}
+
+static void sigsegv_info_handler(int sig, siginfo_t* info, void* uctx) {
+    (void)uctx;
+    static const char msg[] = "[test] SIGSEGV siginfo handler invoked\n";
+    (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1));
+    const uintptr_t expected = 0x12345000U;
+    if (sig == SIGSEGV && info && (uintptr_t)info->si_addr == expected) {
+        sys_exit(0);
+    }
+    sys_exit(1);
+}
+
+void _start(void) {
+    __asm__ volatile(
+        "mov $0x23, %ax\n"
+        "mov %ax, %ds\n"
+        "mov %ax, %es\n"
+        "mov %ax, %fs\n"
+        "mov %ax, %gs\n"
+    );
+
+    static const char msg[] = "[test] hello from init.elf\n";
+    (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1));
+
+    static const char path[] = "/sbin/fulltest";
+
+    int fd = sys_open(path, 0);
+    if (fd < 0) {
+        sys_write(1, "[test] open failed fd=", (uint32_t)(sizeof("[test] open failed fd=") - 1));
+        write_int_dec(fd);
+        sys_write(1, "\n", 1);
+        sys_exit(1);
+    }
+
+    uint8_t hdr[4];
+    int rd = sys_read(fd, hdr, 4);
+    (void)sys_close(fd);
+    if (rd == 4 && hdr[0] == 0x7F && hdr[1] == 'E' && hdr[2] == 'L' && hdr[3] == 'F') {
+        sys_write(1, "[test] open/read/close OK (ELF magic)\n",
+                  (uint32_t)(sizeof("[test] open/read/close OK (ELF magic)\n") - 1));
+    } else {
+        sys_write(1, "[test] read failed or bad header rd=", (uint32_t)(sizeof("[test] read failed or bad header rd=") - 1));
+        write_int_dec(rd);
+        sys_write(1, " hdr=", (uint32_t)(sizeof(" hdr=") - 1));
+        for (int i = 0; i < 4; i++) {
+            write_hex8(hdr[i]);
+        }
+        sys_write(1, "\n", 1);
+        sys_exit(1);
+    }
+
+    fd = sys_open("/sbin/fulltest", 0);
+    if (fd < 0) {
+        sys_write(1, "[test] overlay open failed\n",
+                  (uint32_t)(sizeof("[test] overlay open failed\n") - 1));
+        sys_exit(1);
+    }
+
+    uint8_t orig0 = 0;
+    if (sys_lseek(fd, 0, SEEK_SET) < 0 || sys_read(fd, &orig0, 1) != 1) {
+        sys_write(1, "[test] overlay read failed\n",
+                  (uint32_t)(sizeof("[test] overlay read failed\n") - 1));
+        sys_exit(1);
+    }
+
+    uint8_t x = (uint8_t)(orig0 ^ 0xFF);
+    if (sys_lseek(fd, 0, SEEK_SET) < 0 || sys_write(fd, &x, 1) != 1) {
+        sys_write(1, "[test] overlay write failed\n",
+                  (uint32_t)(sizeof("[test] overlay write failed\n") - 1));
+        sys_exit(1);
+    }
+
+    if (sys_close(fd) < 0) {
+        sys_write(1, "[test] overlay close failed\n",
+                  (uint32_t)(sizeof("[test] overlay close failed\n") - 1));
+        sys_exit(1);
+    }
+
+    fd = sys_open("/sbin/fulltest", 0);
+    if (fd < 0) {
+        sys_write(1, "[test] overlay open2 failed\n",
+                  (uint32_t)(sizeof("[test] overlay open2 failed\n") - 1));
+        sys_exit(1);
+    }
+
+    uint8_t chk = 0;
+    if (sys_lseek(fd, 0, SEEK_SET) < 0 || sys_read(fd, &chk, 1) != 1 || chk != x) {
+        sys_write(1, "[test] overlay verify failed\n",
+                  (uint32_t)(sizeof("[test] overlay verify failed\n") - 1));
+        sys_exit(1);
+    }
+
+    if (sys_lseek(fd, 0, SEEK_SET) < 0 || sys_write(fd, &orig0, 1) != 1) {
+        sys_write(1, "[test] overlay restore failed\n",
+                  (uint32_t)(sizeof("[test] overlay restore failed\n") - 1));
+        sys_exit(1);
+    }
+
+    if (sys_close(fd) < 0) {
+        sys_write(1, "[test] overlay close2 failed\n",
+                  (uint32_t)(sizeof("[test] overlay close2 failed\n") - 1));
+        sys_exit(1);
+    }
+
+    sys_write(1, "[test] overlay copy-up OK\n",
+              (uint32_t)(sizeof("[test] overlay copy-up OK\n") - 1));
+
+    fd = sys_open("/sbin/fulltest", 0);
+    if (fd < 0) {
+        sys_write(1, "[test] open2 failed\n", (uint32_t)(sizeof("[test] open2 failed\n") - 1));
+        sys_exit(1);
+    }
+
+    struct stat st;
+    if (sys_fstat(fd, &st) < 0) {
+        sys_write(1, "[test] fstat failed\n", (uint32_t)(sizeof("[test] fstat failed\n") - 1));
+        sys_exit(1);
+    }
+
+    if ((st.st_mode & S_IFMT) != S_IFREG || st.st_size == 0) {
+        sys_write(1, "[test] fstat bad\n", (uint32_t)(sizeof("[test] fstat bad\n") - 1));
+        sys_exit(1);
+    }
+
+    if (sys_lseek(fd, 0, SEEK_SET) < 0) {
+        sys_write(1, "[test] lseek set failed\n",
+                  (uint32_t)(sizeof("[test] lseek set failed\n") - 1));
+        sys_exit(1);
+    }
+
+    uint8_t m2[4];
+    if (sys_read(fd, m2, 4) != 4) {
+        sys_write(1, "[test] read2 failed\n", (uint32_t)(sizeof("[test] read2 failed\n") - 1));
+        sys_exit(1);
+    }
+    if (m2[0] != 0x7F || m2[1] != 'E' || m2[2] != 'L' || m2[3] != 'F') {
+        sys_write(1, "[test] lseek/read mismatch\n",
+                  (uint32_t)(sizeof("[test] lseek/read mismatch\n") - 1));
+        sys_exit(1);
+    }
+
+    if (sys_close(fd) < 0) {
+        sys_write(1, "[test] close2 failed\n", (uint32_t)(sizeof("[test] close2 failed\n") - 1));
+        sys_exit(1);
+    }
+
+    if (sys_stat("/sbin/fulltest", &st) < 0) {
+        sys_write(1, "[test] stat failed\n", (uint32_t)(sizeof("[test] stat failed\n") - 1));
+        sys_exit(1);
+    }
+    if ((st.st_mode & S_IFMT) != S_IFREG || st.st_size == 0) {
+        sys_write(1, "[test] stat bad\n", (uint32_t)(sizeof("[test] stat bad\n") - 1));
+        sys_exit(1);
+    }
+
+    sys_write(1, "[test] lseek/stat/fstat OK\n",
+              (uint32_t)(sizeof("[test] lseek/stat/fstat OK\n") - 1));
+
+    fd = sys_open("/tmp/hello.txt", 0);
+    if (fd < 0) {
+        sys_write(1, "[test] tmpfs open failed\n",
+                  (uint32_t)(sizeof("[test] tmpfs open failed\n") - 1));
+        sys_exit(1);
+    }
+
+    if (sys_lseek(fd, 0, SEEK_END) < 0) {
+        sys_write(1, "[test] dup2 prep lseek failed\n",
+                  (uint32_t)(sizeof("[test] dup2 prep lseek failed\n") - 1));
+        sys_exit(1);
+    }
+
+    if (sys_dup2(fd, 1) != 1) {
+        sys_write(1, "[test] dup2 failed\n", (uint32_t)(sizeof("[test] dup2 failed\n") - 1));
+        sys_exit(1);
+    }
+
+    (void)sys_close(fd);
+
+    {
+        static const char m[] = "[test] dup2 stdout->file OK\n";
+        if (sys_write(1, m, (uint32_t)(sizeof(m) - 1)) != (int)(sizeof(m) - 1)) {
+            sys_exit(1);
+        }
+    }
+
+    (void)sys_close(1);
+    sys_write(1, "[test] dup2 restore tty OK\n",
+              (uint32_t)(sizeof("[test] dup2 restore tty OK\n") - 1));
+
+    {
+        int pfds[2];
+        if (sys_pipe(pfds) < 0) {
+            sys_write(1, "[test] pipe failed\n", (uint32_t)(sizeof("[test] pipe failed\n") - 1));
+            sys_exit(1);
+        }
+
+        static const char pmsg[] = "pipe-test";
+        if (sys_write(pfds[1], pmsg, (uint32_t)(sizeof(pmsg) - 1)) != (int)(sizeof(pmsg) - 1)) {
+            sys_write(1, "[test] pipe write failed\n",
+                      (uint32_t)(sizeof("[test] pipe write failed\n") - 1));
+            sys_exit(1);
+        }
+
+        char rbuf[16];
+        int prd = sys_read(pfds[0], rbuf, (uint32_t)(sizeof(pmsg) - 1));
+        if (prd != (int)(sizeof(pmsg) - 1)) {
+            sys_write(1, "[test] pipe read failed\n",
+                      (uint32_t)(sizeof("[test] pipe read failed\n") - 1));
+            sys_exit(1);
+        }
+
+        int ok = 1;
+        for (uint32_t i = 0; i < (uint32_t)(sizeof(pmsg) - 1); i++) {
+            if ((uint8_t)rbuf[i] != (uint8_t)pmsg[i]) ok = 0;
+        }
+        if (!ok) {
+            sys_write(1, "[test] pipe mismatch\n",
+                      (uint32_t)(sizeof("[test] pipe mismatch\n") - 1));
+            sys_exit(1);
+        }
+
+        if (sys_dup2(pfds[1], 1) != 1) {
+            sys_write(1, "[test] pipe dup2 failed\n",
+                      (uint32_t)(sizeof("[test] pipe dup2 failed\n") - 1));
+            sys_exit(1);
+        }
+
+        static const char p2[] = "dup2-pipe";
+        if (sys_write(1, p2, (uint32_t)(sizeof(p2) - 1)) != (int)(sizeof(p2) - 1)) {
+            sys_exit(1);
+        }
+
+        int prd2 = sys_read(pfds[0], rbuf, (uint32_t)(sizeof(p2) - 1));
+        if (prd2 != (int)(sizeof(p2) - 1)) {
+            sys_write(1, "[test] pipe dup2 read failed\n",
+                      (uint32_t)(sizeof("[test] pipe dup2 read failed\n") - 1));
+            sys_exit(1);
+        }
+        sys_write(1, "[test] pipe OK\n", (uint32_t)(sizeof("[test] pipe OK\n") - 1));
+
+        (void)sys_close(pfds[0]);
+        (void)sys_close(pfds[1]);
+
+        int tfd = sys_open("/dev/tty", 0);
+        if (tfd < 0) {
+            sys_write(1, "[test] /dev/tty open failed\n",
+                      (uint32_t)(sizeof("[test] /dev/tty open failed\n") - 1));
+            sys_exit(1);
+        }
+        if (sys_dup2(tfd, 1) != 1) {
+            sys_write(1, "[test] dup2 restore tty failed\n",
+                      (uint32_t)(sizeof("[test] dup2 restore tty failed\n") - 1));
+            sys_exit(1);
+        }
+        (void)sys_close(tfd);
+
+    }
+
+    {
+        int pid = sys_fork();
+        if (pid < 0) {
+            sys_write(1, "[test] kill test fork failed\n",
+                      (uint32_t)(sizeof("[test] kill test fork failed\n") - 1));
+            sys_exit(1);
+        }
+
+        if (pid == 0) {
+            for (;;) {
+                __asm__ volatile("nop");
+            }
+        }
+
+        if (sys_kill(pid, SIGKILL) < 0) {
+            sys_write(1, "[test] kill(SIGKILL) failed\n",
+                      (uint32_t)(sizeof("[test] kill(SIGKILL) failed\n") - 1));
+            sys_exit(1);
+        }
+
+        int st = 0;
+        int rp = sys_waitpid(pid, &st, 0);
+        if (rp != pid || st != (128 + SIGKILL)) {
+            sys_write(1, "[test] kill test waitpid mismatch\n",
+                      (uint32_t)(sizeof("[test] kill test waitpid mismatch\n") - 1));
+            sys_exit(1);
+        }
+
+        sys_write(1, "[test] kill(SIGKILL) OK\n",
+                  (uint32_t)(sizeof("[test] kill(SIGKILL) OK\n") - 1));
+    }
+
+    {
+        int fds[2];
+        if (sys_pipe(fds) < 0) {
+            sys_write(1, "[test] poll pipe setup failed\n",
+                      (uint32_t)(sizeof("[test] poll pipe setup failed\n") - 1));
+            sys_exit(1);
+        }
+
+        struct pollfd p;
+        p.fd = fds[0];
+        p.events = POLLIN;
+        p.revents = 0;
+        int rc = sys_poll(&p, 1, 0);
+        if (rc != 0) {
+            sys_write(1, "[test] poll(pipe) expected 0\n",
+                      (uint32_t)(sizeof("[test] poll(pipe) expected 0\n") - 1));
+            sys_exit(1);
+        }
+
+        static const char a = 'A';
+        if (sys_write(fds[1], &a, 1) != 1) {
+            sys_write(1, "[test] poll pipe write failed\n",
+                      (uint32_t)(sizeof("[test] poll pipe write failed\n") - 1));
+            sys_exit(1);
+        }
+
+        p.revents = 0;
+        rc = sys_poll(&p, 1, 0);
+        if (rc != 1 || (p.revents & POLLIN) == 0) {
+            sys_write(1, "[test] poll(pipe) expected POLLIN\n",
+                      (uint32_t)(sizeof("[test] poll(pipe) expected POLLIN\n") - 1));
+            sys_exit(1);
+        }
+
+        (void)sys_close(fds[0]);
+        (void)sys_close(fds[1]);
+        sys_write(1, "[test] poll(pipe) OK\n", (uint32_t)(sizeof("[test] poll(pipe) OK\n") - 1));
+    }
+
+    {
+        int fds[2];
+        if (sys_pipe(fds) < 0) {
+            sys_write(1, "[test] select pipe setup failed\n",
+                      (uint32_t)(sizeof("[test] select pipe setup failed\n") - 1));
+            sys_exit(1);
+        }
+
+        uint64_t r = 0;
+        uint64_t w = 0;
+        r |= (1ULL << (uint32_t)fds[0]);
+        int rc = sys_select((uint32_t)(fds[0] + 1), &r, &w, 0, 0);
+        if (rc != 0) {
+            sys_write(1, "[test] select(pipe) expected 0\n",
+                      (uint32_t)(sizeof("[test] select(pipe) expected 0\n") - 1));
+            sys_exit(1);
+        }
+
+        static const char a = 'B';
+        if (sys_write(fds[1], &a, 1) != 1) {
+            sys_write(1, "[test] select pipe write failed\n",
+                      (uint32_t)(sizeof("[test] select pipe write failed\n") - 1));
+            sys_exit(1);
+        }
+
+        r = 0;
+        w = 0;
+        r |= (1ULL << (uint32_t)fds[0]);
+        rc = sys_select((uint32_t)(fds[0] + 1), &r, &w, 0, 0);
+        if (rc != 1 || ((r >> (uint32_t)fds[0]) & 1ULL) == 0) {
+            sys_write(1, "[test] select(pipe) expected readable\n",
+                      (uint32_t)(sizeof("[test] select(pipe) expected readable\n") - 1));
+            sys_exit(1);
+        }
+
+        (void)sys_close(fds[0]);
+        (void)sys_close(fds[1]);
+        sys_write(1, "[test] select(pipe) OK\n",
+                  (uint32_t)(sizeof("[test] select(pipe) OK\n") - 1));
+    }
+
+    {
+        int fd = sys_open("/dev/tty", 0);
+        if (fd < 0) {
+            sys_write(1, "[test] ioctl(/dev/tty) open failed\n",
+                      (uint32_t)(sizeof("[test] ioctl(/dev/tty) open failed\n") - 1));
+            sys_exit(1);
+        }
+
+        int fg = -1;
+        if (sys_ioctl(fd, TIOCGPGRP, &fg) < 0 || fg != 0) {
+            sys_write(1, "[test] ioctl TIOCGPGRP failed\n",
+                      (uint32_t)(sizeof("[test] ioctl TIOCGPGRP failed\n") - 1));
+            sys_exit(1);
+        }
+
+        fg = 0;
+        if (sys_ioctl(fd, TIOCSPGRP, &fg) < 0) {
+            sys_write(1, "[test] ioctl TIOCSPGRP failed\n",
+                      (uint32_t)(sizeof("[test] ioctl TIOCSPGRP failed\n") - 1));
+            sys_exit(1);
+        }
+
+        fg = 1;
+        if (sys_ioctl(fd, TIOCSPGRP, &fg) >= 0) {
+            sys_write(1, "[test] ioctl TIOCSPGRP expected fail\n",
+                      (uint32_t)(sizeof("[test] ioctl TIOCSPGRP expected fail\n") - 1));
+            sys_exit(1);
+        }
+
+        struct termios oldt;
+        if (sys_ioctl(fd, TCGETS, &oldt) < 0) {
+            sys_write(1, "[test] ioctl TCGETS failed\n",
+                      (uint32_t)(sizeof("[test] ioctl TCGETS failed\n") - 1));
+            sys_exit(1);
+        }
+
+        struct termios t = oldt;
+        t.c_lflag &= ~(uint32_t)(ECHO | ICANON);
+        if (sys_ioctl(fd, TCSETS, &t) < 0) {
+            sys_write(1, "[test] ioctl TCSETS failed\n",
+                      (uint32_t)(sizeof("[test] ioctl TCSETS failed\n") - 1));
+            sys_exit(1);
+        }
+
+        struct termios chk;
+        if (sys_ioctl(fd, TCGETS, &chk) < 0) {
+            sys_write(1, "[test] ioctl TCGETS2 failed\n",
+                      (uint32_t)(sizeof("[test] ioctl TCGETS2 failed\n") - 1));
+            sys_exit(1);
+        }
+
+        if ((chk.c_lflag & (uint32_t)(ECHO | ICANON)) != 0) {
+            sys_write(1, "[test] ioctl verify failed\n",
+                      (uint32_t)(sizeof("[test] ioctl verify failed\n") - 1));
+            sys_exit(1);
+        }
+
+        (void)sys_ioctl(fd, TCSETS, &oldt);
+        (void)sys_close(fd);
+
+        sys_write(1, "[test] ioctl(/dev/tty) OK\n",
+                  (uint32_t)(sizeof("[test] ioctl(/dev/tty) OK\n") - 1));
+    }
+
+    // A2: basic job control. A background pgrp read/write on controlling TTY should raise SIGTTIN/SIGTTOU.
+    {
+        int leader = sys_fork();
+        if (leader < 0) {
+            sys_write(1, "[test] fork(job control leader) failed\n",
+                      (uint32_t)(sizeof("[test] fork(job control leader) failed\n") - 1));
+            sys_exit(1);
+        }
+        if (leader == 0) {
+            int me = sys_getpid();
+            int sid = sys_setsid();
+            if (sid != me) {
+                sys_write(1, "[test] setsid(job control) failed\n",
+                          (uint32_t)(sizeof("[test] setsid(job control) failed\n") - 1));
+                sys_exit(1);
+            }
+
+            int tfd = sys_open("/dev/tty", 0);
+            if (tfd < 0) {
+                sys_write(1, "[test] open(/dev/tty) for job control failed\n",
+                          (uint32_t)(sizeof("[test] open(/dev/tty) for job control failed\n") - 1));
+                sys_exit(1);
+            }
+
+            // Touch ioctl to make kernel acquire controlling session/pgrp.
+            int fg = 0;
+            (void)sys_ioctl(tfd, TIOCGPGRP, &fg);
+
+            fg = me;
+            if (sys_ioctl(tfd, TIOCSPGRP, &fg) < 0) {
+                sys_write(1, "[test] ioctl TIOCSPGRP(job control) failed\n",
+                          (uint32_t)(sizeof("[test] ioctl TIOCSPGRP(job control) failed\n") - 1));
+                sys_exit(1);
+            }
+
+            int bg = sys_fork();
+            if (bg < 0) {
+                sys_write(1, "[test] fork(job control bg) failed\n",
+                          (uint32_t)(sizeof("[test] fork(job control bg) failed\n") - 1));
+                sys_exit(1);
+            }
+            if (bg == 0) {
+                (void)sys_setpgid(0, me + 1);
+
+                (void)sys_sigaction(SIGTTIN, ttin_handler, 0);
+                (void)sys_sigaction(SIGTTOU, ttou_handler, 0);
+
+                uint8_t b = 0;
+                (void)sys_read(tfd, &b, 1);
+                if (!got_ttin) {
+                    sys_write(1, "[test] SIGTTIN job control failed\n",
+                              (uint32_t)(sizeof("[test] SIGTTIN job control failed\n") - 1));
+                    sys_exit(1);
+                }
+
+                const char msg2[] = "x";
+                (void)sys_write(tfd, msg2, 1);
+                if (!got_ttou) {
+                    sys_write(1, "[test] SIGTTOU job control failed\n",
+                              (uint32_t)(sizeof("[test] SIGTTOU job control failed\n") - 1));
+                    sys_exit(1);
+                }
+
+                sys_exit(0);
+            }
+
+            int st2 = 0;
+            int wp2 = sys_waitpid(bg, &st2, 0);
+            if (wp2 != bg || st2 != 0) {
+                sys_write(1, "[test] waitpid(job control bg) failed wp=", (uint32_t)(sizeof("[test] waitpid(job control bg) failed wp=") - 1));
+                write_int_dec(wp2);
+                sys_write(1, " st=", (uint32_t)(sizeof(" st=") - 1));
+                write_int_dec(st2);
+                sys_write(1, "\n", 1);
+                sys_exit(1);
+            }
+
+            (void)sys_close(tfd);
+            sys_exit(0);
+        }
+
+        int stL = 0;
+        int wpL = sys_waitpid(leader, &stL, 0);
+        if (wpL != leader || stL != 0) {
+            sys_write(1, "[test] waitpid(job control leader) failed wp=", (uint32_t)(sizeof("[test] waitpid(job control leader) failed wp=") - 1));
+            write_int_dec(wpL);
+            sys_write(1, " st=", (uint32_t)(sizeof(" st=") - 1));
+            write_int_dec(stL);
+            sys_write(1, "\n", 1);
+            sys_exit(1);
+        }
+
+        sys_write(1, "[test] job control (SIGTTIN/SIGTTOU) OK\n",
+                  (uint32_t)(sizeof("[test] job control (SIGTTIN/SIGTTOU) OK\n") - 1));
+    }
+
+    {
+        int fd = sys_open("/dev/null", 0);
+        if (fd < 0) {
+            sys_write(1, "[test] poll(/dev/null) open failed\n",
+                      (uint32_t)(sizeof("[test] poll(/dev/null) open failed\n") - 1));
+            sys_exit(1);
+        }
+        struct pollfd p;
+        p.fd = fd;
+        p.events = POLLOUT;
+        p.revents = 0;
+        int rc = sys_poll(&p, 1, 0);
+        if (rc != 1 || (p.revents & POLLOUT) == 0) {
+            sys_write(1, "[test] poll(/dev/null) expected POLLOUT\n",
+                      (uint32_t)(sizeof("[test] poll(/dev/null) expected POLLOUT\n") - 1));
+            sys_exit(1);
+        }
+        (void)sys_close(fd);
+        sys_write(1, "[test] poll(/dev/null) OK\n",
+                  (uint32_t)(sizeof("[test] poll(/dev/null) OK\n") - 1));
+    }
+
+    {
+        int mfd = sys_open("/dev/ptmx", 0);
+        int sfd = sys_open("/dev/pts/0", 0);
+        if (mfd < 0 || sfd < 0) {
+            sys_write(1, "[test] pty open failed\n",
+                      (uint32_t)(sizeof("[test] pty open failed\n") - 1));
+            sys_exit(1);
+        }
+
+        static const char m2s[] = "m2s";
+        if (sys_write(mfd, m2s, (uint32_t)(sizeof(m2s) - 1)) != (int)(sizeof(m2s) - 1)) {
+            sys_write(1, "[test] pty write master failed\n",
+                      (uint32_t)(sizeof("[test] pty write master failed\n") - 1));
+            sys_exit(1);
+        }
+
+        struct pollfd p;
+        p.fd = sfd;
+        p.events = POLLIN;
+        p.revents = 0;
+        int rc = sys_poll(&p, 1, 50);
+        if (rc != 1 || (p.revents & POLLIN) == 0) {
+            sys_write(1, "[test] pty poll slave failed\n",
+                      (uint32_t)(sizeof("[test] pty poll slave failed\n") - 1));
+            sys_exit(1);
+        }
+
+        char buf[8];
+        int rd = sys_read(sfd, buf, (uint32_t)(sizeof(m2s) - 1));
+        if (rd != (int)(sizeof(m2s) - 1) || !memeq(buf, m2s, (uint32_t)(sizeof(m2s) - 1))) {
+            sys_write(1, "[test] pty read slave failed\n",
+                      (uint32_t)(sizeof("[test] pty read slave failed\n") - 1));
+            sys_exit(1);
+        }
+
+        static const char s2m[] = "s2m";
+        if (sys_write(sfd, s2m, (uint32_t)(sizeof(s2m) - 1)) != (int)(sizeof(s2m) - 1)) {
+            sys_write(1, "[test] pty write slave failed\n",
+                      (uint32_t)(sizeof("[test] pty write slave failed\n") - 1));
+            sys_exit(1);
+        }
+
+        p.fd = mfd;
+        p.events = POLLIN;
+        p.revents = 0;
+        rc = sys_poll(&p, 1, 50);
+        if (rc != 1 || (p.revents & POLLIN) == 0) {
+            sys_write(1, "[test] pty poll master failed\n",
+                      (uint32_t)(sizeof("[test] pty poll master failed\n") - 1));
+            sys_exit(1);
+        }
+
+        rd = sys_read(mfd, buf, (uint32_t)(sizeof(s2m) - 1));
+        if (rd != (int)(sizeof(s2m) - 1) || !memeq(buf, s2m, (uint32_t)(sizeof(s2m) - 1))) {
+            sys_write(1, "[test] pty read master failed\n",
+                      (uint32_t)(sizeof("[test] pty read master failed\n") - 1));
+            sys_exit(1);
+        }
+
+        (void)sys_close(mfd);
+        (void)sys_close(sfd);
+        sys_write(1, "[test] pty OK\n", (uint32_t)(sizeof("[test] pty OK\n") - 1));
+    }
+
+    {
+        sys_write(1, "[test] setsid test: before fork\n",
+                  (uint32_t)(sizeof("[test] setsid test: before fork\n") - 1));
+        int pid = sys_fork();
+        if (pid < 0) {
+            static const char smsg[] = "[test] fork failed\n";
+            (void)sys_write(1, smsg, (uint32_t)(sizeof(smsg) - 1));
+            sys_exit(2);
+        }
+        if (pid == 0) {
+            sys_write(1, "[test] setsid test: child start\n",
+                      (uint32_t)(sizeof("[test] setsid test: child start\n") - 1));
+            int me = sys_getpid();
+            int sid = sys_setsid();
+            if (sid != me) sys_exit(2);
+
+            int pg = sys_getpgrp();
+            if (pg != me) sys_exit(3);
+
+            int newpg = me + 1;
+            if (sys_setpgid(0, newpg) < 0) sys_exit(4);
+            if (sys_getpgrp() != newpg) sys_exit(5);
+
+            sys_exit(0);
+        }
+
+        sys_write(1, "[test] setsid test: parent waitpid\n",
+                  (uint32_t)(sizeof("[test] setsid test: parent waitpid\n") - 1));
+        int st = 0;
+        int wp = sys_waitpid(pid, &st, 0);
+        if (wp != pid || st != 0) {
+            sys_write(1, "[test] setsid/setpgid/getpgrp failed\n",
+                      (uint32_t)(sizeof("[test] setsid/setpgid/getpgrp failed\n") - 1));
+            sys_exit(1);
+        }
+
+        sys_write(1, "[test] setsid/setpgid/getpgrp OK\n",
+                  (uint32_t)(sizeof("[test] setsid/setpgid/getpgrp OK\n") - 1));
+    }
+
+    {
+        uintptr_t oldh = 0;
+        if (sys_sigaction(SIGUSR1, usr1_handler, &oldh) < 0) {
+            sys_write(1, "[test] sigaction failed\n",
+                      (uint32_t)(sizeof("[test] sigaction failed\n") - 1));
+            sys_exit(1);
+        }
+
+        int me = sys_getpid();
+        if (sys_kill(me, SIGUSR1) < 0) {
+            sys_write(1, "[test] kill(SIGUSR1) failed\n",
+                      (uint32_t)(sizeof("[test] kill(SIGUSR1) failed\n") - 1));
+            sys_exit(1);
+        }
+
+        for (uint32_t i = 0; i < 2000000U; i++) {
+            if (got_usr1) break;
+        }
+
+        if (!got_usr1) {
+            sys_write(1, "[test] SIGUSR1 not delivered\n",
+                      (uint32_t)(sizeof("[test] SIGUSR1 not delivered\n") - 1));
+            sys_exit(1);
+        }
+
+        sys_write(1, "[test] sigaction/kill(SIGUSR1) OK\n",
+                  (uint32_t)(sizeof("[test] sigaction/kill(SIGUSR1) OK\n") - 1));
+    }
+
+    // Verify that returning from a signal handler does not corrupt the user stack.
+    {
+        if (sys_sigaction(SIGUSR1, usr1_ret_handler, 0) < 0) {
+            sys_write(1, "[test] sigaction (sigreturn test) failed\n",
+                      (uint32_t)(sizeof("[test] sigaction (sigreturn test) failed\n") - 1));
+            sys_exit(1);
+        }
+
+        volatile uint32_t canary = 0x11223344U;
+        int me = sys_getpid();
+        if (sys_kill(me, SIGUSR1) < 0) {
+            sys_write(1, "[test] kill(SIGUSR1) (sigreturn test) failed\n",
+                      (uint32_t)(sizeof("[test] kill(SIGUSR1) (sigreturn test) failed\n") - 1));
+            sys_exit(1);
+        }
+
+        if (!got_usr1_ret) {
+            sys_write(1, "[test] SIGUSR1 not delivered (sigreturn test)\n",
+                      (uint32_t)(sizeof("[test] SIGUSR1 not delivered (sigreturn test)\n") - 1));
+            sys_exit(1);
+        }
+
+        if (canary != 0x11223344U) {
+            sys_write(1, "[test] sigreturn test stack corruption\n",
+                      (uint32_t)(sizeof("[test] sigreturn test stack corruption\n") - 1));
+            sys_exit(1);
+        }
+
+        sys_write(1, "[test] sigreturn OK\n",
+                  (uint32_t)(sizeof("[test] sigreturn OK\n") - 1));
+    }
+
+    fd = sys_open("/tmp/hello.txt", 0);
+    if (fd < 0) {
+        sys_write(1, "[test] tmpfs open2 failed\n",
+                  (uint32_t)(sizeof("[test] tmpfs open2 failed\n") - 1));
+        sys_exit(1);
+    }
+
+    if (sys_stat("/tmp/hello.txt", &st) < 0) {
+        sys_write(1, "[test] tmpfs stat failed\n",
+                  (uint32_t)(sizeof("[test] tmpfs stat failed\n") - 1));
+        sys_exit(1);
+    }
+    if ((st.st_mode & S_IFMT) != S_IFREG) {
+        sys_write(1, "[test] tmpfs stat not reg\n",
+                  (uint32_t)(sizeof("[test] tmpfs stat not reg\n") - 1));
+        sys_exit(1);
+    }
+    if (st.st_size == 0) {
+        sys_write(1, "[test] tmpfs stat size 0\n",
+                  (uint32_t)(sizeof("[test] tmpfs stat size 0\n") - 1));
+        sys_exit(1);
+    }
+
+    struct stat fst;
+    if (sys_fstat(fd, &fst) < 0) {
+        sys_write(1, "[test] tmpfs fstat failed\n",
+                  (uint32_t)(sizeof("[test] tmpfs fstat failed\n") - 1));
+        sys_exit(1);
+    }
+    if (fst.st_size != st.st_size) {
+        sys_write(1, "[test] tmpfs stat size mismatch\n",
+                  (uint32_t)(sizeof("[test] tmpfs stat size mismatch\n") - 1));
+        sys_exit(1);
+    }
+
+    int end = sys_lseek(fd, 0, SEEK_END);
+    if (end < 0 || (uint32_t)end != st.st_size) {
+        sys_write(1, "[test] tmpfs lseek end bad\n",
+                  (uint32_t)(sizeof("[test] tmpfs lseek end bad\n") - 1));
+        sys_exit(1);
+    }
+
+    uint8_t eofb;
+    if (sys_read(fd, &eofb, 1) != 0) {
+        sys_write(1, "[test] tmpfs eof read bad\n",
+                  (uint32_t)(sizeof("[test] tmpfs eof read bad\n") - 1));
+        sys_exit(1);
+    }
+
+    if (sys_lseek(fd, 0, 999) >= 0) {
+        sys_write(1, "[test] tmpfs lseek whence bad\n",
+                  (uint32_t)(sizeof("[test] tmpfs lseek whence bad\n") - 1));
+        sys_exit(1);
+    }
+
+    if (sys_lseek(fd, 0, SEEK_SET) < 0) {
+        sys_write(1, "[test] tmpfs lseek set failed\n",
+                  (uint32_t)(sizeof("[test] tmpfs lseek set failed\n") - 1));
+        sys_exit(1);
+    }
+
+    uint8_t tbuf[6];
+    if (sys_read(fd, tbuf, 5) != 5) {
+        sys_write(1, "[test] tmpfs read failed\n",
+                  (uint32_t)(sizeof("[test] tmpfs read failed\n") - 1));
+        sys_exit(1);
+    }
+    tbuf[5] = 0;
+    if (tbuf[0] != 'h' || tbuf[1] != 'e' || tbuf[2] != 'l' || tbuf[3] != 'l' || tbuf[4] != 'o') {
+        sys_write(1, "[test] tmpfs bad data\n", (uint32_t)(sizeof("[test] tmpfs bad data\n") - 1));
+        sys_exit(1);
+    }
+
+    if (sys_close(fd) < 0) {
+        sys_write(1, "[test] tmpfs close failed\n",
+                  (uint32_t)(sizeof("[test] tmpfs close failed\n") - 1));
+        sys_exit(1);
+    }
+
+    if (sys_open("/tmp/does_not_exist", 0) >= 0) {
+        sys_write(1, "[test] tmpfs open nonexist bad\n",
+                  (uint32_t)(sizeof("[test] tmpfs open nonexist bad\n") - 1));
+        sys_exit(1);
+    }
+
+    fd = sys_open("/tmp/hello.txt", 0);
+    if (fd < 0) {
+        sys_write(1, "[test] tmpfs open3 failed\n",
+                  (uint32_t)(sizeof("[test] tmpfs open3 failed\n") - 1));
+        sys_exit(1);
+    }
+
+    if (sys_fstat(fd, &fst) < 0) {
+        sys_write(1, "[test] tmpfs fstat2 failed\n",
+                  (uint32_t)(sizeof("[test] tmpfs fstat2 failed\n") - 1));
+        sys_exit(1);
+    }
+
+    if (sys_lseek(fd, 0, SEEK_END) < 0) {
+        sys_write(1, "[test] tmpfs lseek end2 failed\n",
+                  (uint32_t)(sizeof("[test] tmpfs lseek end2 failed\n") - 1));
+        sys_exit(1);
+    }
+
+    char suf[3];
+    suf[0] = 'X';
+    suf[1] = 'Y';
+    suf[2] = 'Z';
+    if (sys_write(fd, suf, 3) != 3) {
+        sys_write(1, "[test] tmpfs write failed\n",
+                  (uint32_t)(sizeof("[test] tmpfs write failed\n") - 1));
+        sys_exit(1);
+    }
+
+    if (sys_fstat(fd, &fst) < 0) {
+        sys_write(1, "[test] tmpfs fstat3 failed\n",
+                  (uint32_t)(sizeof("[test] tmpfs fstat3 failed\n") - 1));
+        sys_exit(1);
+    }
+    if (fst.st_size != st.st_size + 3) {
+        sys_write(1, "[test] tmpfs size not grown\n",
+                  (uint32_t)(sizeof("[test] tmpfs size not grown\n") - 1));
+        sys_exit(1);
+    }
+
+    if (sys_lseek(fd, -3, SEEK_END) < 0) {
+        sys_write(1, "[test] tmpfs lseek back failed\n",
+                  (uint32_t)(sizeof("[test] tmpfs lseek back failed\n") - 1));
+        sys_exit(1);
+    }
+    uint8_t s2[3];
+    if (sys_read(fd, s2, 3) != 3 || s2[0] != 'X' || s2[1] != 'Y' || s2[2] != 'Z') {
+        sys_write(1, "[test] tmpfs suffix mismatch\n",
+                  (uint32_t)(sizeof("[test] tmpfs suffix mismatch\n") - 1));
+        sys_exit(1);
+    }
+
+    if (sys_close(fd) < 0) {
+        sys_write(1, "[test] tmpfs close3 failed\n",
+                  (uint32_t)(sizeof("[test] tmpfs close3 failed\n") - 1));
+        sys_exit(1);
+    }
+
+    sys_write(1, "[test] tmpfs/mount OK\n", (uint32_t)(sizeof("[test] tmpfs/mount OK\n") - 1));
+
+    {
+        int fd = sys_open("/dev/null", 0);
+        if (fd < 0) {
+            sys_write(1, "[test] /dev/null open failed\n",
+                      (uint32_t)(sizeof("[test] /dev/null open failed\n") - 1));
+            sys_exit(1);
+        }
+        static const char z[] = "discard me";
+        if (sys_write(fd, z, (uint32_t)(sizeof(z) - 1)) != (int)(sizeof(z) - 1)) {
+            sys_write(1, "[test] /dev/null write failed\n",
+                      (uint32_t)(sizeof("[test] /dev/null write failed\n") - 1));
+            sys_exit(1);
+        }
+        (void)sys_close(fd);
+        sys_write(1, "[test] /dev/null OK\n", (uint32_t)(sizeof("[test] /dev/null OK\n") - 1));
+    }
+
+    // B1: persistent storage smoke. Value should increment across reboots (disk.img).
+    {
+        int fd = sys_open("/persist/counter", 0);
+        if (fd < 0) {
+            sys_write(1, "[test] /persist/counter open failed\n",
+                      (uint32_t)(sizeof("[test] /persist/counter open failed\n") - 1));
+            sys_exit(1);
+        }
+
+        (void)sys_lseek(fd, 0, SEEK_SET);
+        uint8_t b[4] = {0, 0, 0, 0};
+        int rd = sys_read(fd, b, 4);
+        if (rd != 4) {
+            sys_write(1, "[test] /persist/counter read failed\n",
+                      (uint32_t)(sizeof("[test] /persist/counter read failed\n") - 1));
+            sys_exit(1);
+        }
+
+        uint32_t v = (uint32_t)b[0] | ((uint32_t)b[1] << 8) | ((uint32_t)b[2] << 16) | ((uint32_t)b[3] << 24);
+        v++;
+        b[0] = (uint8_t)(v & 0xFF);
+        b[1] = (uint8_t)((v >> 8) & 0xFF);
+        b[2] = (uint8_t)((v >> 16) & 0xFF);
+        b[3] = (uint8_t)((v >> 24) & 0xFF);
+
+        (void)sys_lseek(fd, 0, SEEK_SET);
+        int wr = sys_write(fd, b, 4);
+        if (wr != 4) {
+            sys_write(1, "[test] /persist/counter write failed\n",
+                      (uint32_t)(sizeof("[test] /persist/counter write failed\n") - 1));
+            sys_exit(1);
+        }
+
+        (void)sys_close(fd);
+
+        sys_write(1, "[test] /persist/counter=", (uint32_t)(sizeof("[test] /persist/counter=") - 1));
+        write_int_dec((int)v);
+        sys_write(1, "\n", 1);
+    }
+
+    {
+        int fd = sys_open("/dev/tty", 0);
+        if (fd < 0) {
+            sys_write(1, "[test] /dev/tty open failed\n",
+                      (uint32_t)(sizeof("[test] /dev/tty open failed\n") - 1));
+            sys_exit(1);
+        }
+        static const char m[] = "[test] /dev/tty write OK\n";
+        int wr = sys_write(fd, m, (uint32_t)(sizeof(m) - 1));
+        if (wr != (int)(sizeof(m) - 1)) {
+            sys_write(1, "[test] /dev/tty write failed\n",
+                      (uint32_t)(sizeof("[test] /dev/tty write failed\n") - 1));
+            sys_exit(1);
+        }
+        (void)sys_close(fd);
+    }
+
+    // B2: on-disk general filesystem smoke (/disk)
+    {
+        int fd = sys_open("/disk/test", O_CREAT);
+        if (fd < 0) {
+            sys_write(1, "[test] /disk/test open failed\n",
+                      (uint32_t)(sizeof("[test] /disk/test open failed\n") - 1));
+            sys_exit(1);
+        }
+
+        char buf[16];
+        int rd = sys_read(fd, buf, sizeof(buf));
+        int prev = 0;
+        if (rd > 0) {
+            for (int i = 0; i < rd; i++) {
+                if (buf[i] < '0' || buf[i] > '9') break;
+                prev = prev * 10 + (buf[i] - '0');
+            }
+        }
+
+        (void)sys_close(fd);
+
+        fd = sys_open("/disk/test", O_CREAT | O_TRUNC);
+        if (fd < 0) {
+            sys_write(1, "[test] /disk/test open2 failed\n",
+                      (uint32_t)(sizeof("[test] /disk/test open2 failed\n") - 1));
+            sys_exit(1);
+        }
+
+        int next = prev + 1;
+        char out[16];
+        int n = 0;
+        int v = next;
+        if (v == 0) {
+            out[n++] = '0';
+        } else {
+            char tmp[16];
+            int t = 0;
+            while (v > 0 && t < (int)sizeof(tmp)) {
+                tmp[t++] = (char)('0' + (v % 10));
+                v /= 10;
+            }
+            while (t > 0) {
+                out[n++] = tmp[--t];
+            }
+        }
+
+        if (sys_write(fd, out, (uint32_t)n) != n) {
+            sys_write(1, "[test] /disk/test write failed\n",
+                      (uint32_t)(sizeof("[test] /disk/test write failed\n") - 1));
+            sys_exit(1);
+        }
+        (void)sys_close(fd);
+
+        fd = sys_open("/disk/test", 0);
+        if (fd < 0) {
+            sys_write(1, "[test] /disk/test open3 failed\n",
+                      (uint32_t)(sizeof("[test] /disk/test open3 failed\n") - 1));
+            sys_exit(1);
+        }
+        for (uint32_t i = 0; i < (uint32_t)sizeof(buf); i++) buf[i] = 0;
+        rd = sys_read(fd, buf, sizeof(buf));
+        (void)sys_close(fd);
+        if (rd != n || !memeq(buf, out, (uint32_t)n)) {
+            sys_write(1, "[test] /disk/test verify failed\n",
+                      (uint32_t)(sizeof("[test] /disk/test verify failed\n") - 1));
+            sys_exit(1);
+        }
+
+        sys_write(1, "[test] /disk/test prev=", (uint32_t)(sizeof("[test] /disk/test prev=") - 1));
+        write_int_dec(prev);
+        sys_write(1, " next=", (uint32_t)(sizeof(" next=") - 1));
+        write_int_dec(next);
+        sys_write(1, " OK\n", (uint32_t)(sizeof(" OK\n") - 1));
+    }
+
+    // B3: diskfs mkdir/unlink smoke
+    {
+        int r = sys_mkdir("/disk/dir");
+        if (r < 0 && errno != 17) {
+            sys_write(1, "[test] mkdir /disk/dir failed errno=", (uint32_t)(sizeof("[test] mkdir /disk/dir failed errno=") - 1));
+            write_int_dec(errno);
+            sys_write(1, "\n", 1);
+            sys_exit(1);
+        }
+
+        int fd = sys_open("/disk/dir/file", O_CREAT | O_TRUNC);
+        if (fd < 0) {
+            sys_write(1, "[test] open /disk/dir/file failed\n",
+                      (uint32_t)(sizeof("[test] open /disk/dir/file failed\n") - 1));
+            sys_exit(1);
+        }
+        static const char msg2[] = "ok";
+        if (sys_write(fd, msg2, 2) != 2) {
+            sys_write(1, "[test] write /disk/dir/file failed\n",
+                      (uint32_t)(sizeof("[test] write /disk/dir/file failed\n") - 1));
+            sys_exit(1);
+        }
+        (void)sys_close(fd);
+
+        r = sys_unlink("/disk/dir/file");
+        if (r < 0) {
+            sys_write(1, "[test] unlink /disk/dir/file failed\n",
+                      (uint32_t)(sizeof("[test] unlink /disk/dir/file failed\n") - 1));
+            sys_exit(1);
+        }
+
+        fd = sys_open("/disk/dir/file", 0);
+        if (fd >= 0) {
+            sys_write(1, "[test] unlink did not remove file\n",
+                      (uint32_t)(sizeof("[test] unlink did not remove file\n") - 1));
+            sys_exit(1);
+        }
+
+        sys_write(1, "[test] diskfs mkdir/unlink OK\n",
+                  (uint32_t)(sizeof("[test] diskfs mkdir/unlink OK\n") - 1));
+    }
+
+    // B4: diskfs getdents smoke
+    {
+        int r = sys_mkdir("/disk/ls");
+        if (r < 0 && errno != 17) {
+            sys_write(1, "[test] mkdir /disk/ls failed errno=", (uint32_t)(sizeof("[test] mkdir /disk/ls failed errno=") - 1));
+            write_int_dec(errno);
+            sys_write(1, "\n", 1);
+            sys_exit(1);
+        }
+
+        int fd = sys_open("/disk/ls/file1", O_CREAT | O_TRUNC);
+        if (fd < 0) {
+            sys_write(1, "[test] create /disk/ls/file1 failed\n",
+                      (uint32_t)(sizeof("[test] create /disk/ls/file1 failed\n") - 1));
+            sys_exit(1);
+        }
+        (void)sys_close(fd);
+
+        fd = sys_open("/disk/ls/file2", O_CREAT | O_TRUNC);
+        if (fd < 0) {
+            sys_write(1, "[test] create /disk/ls/file2 failed\n",
+                      (uint32_t)(sizeof("[test] create /disk/ls/file2 failed\n") - 1));
+            sys_exit(1);
+        }
+        (void)sys_close(fd);
+
+        int dfd = sys_open("/disk/ls", 0);
+        if (dfd < 0) {
+            sys_write(1, "[test] open dir /disk/ls failed\n",
+                      (uint32_t)(sizeof("[test] open dir /disk/ls failed\n") - 1));
+            sys_exit(1);
+        }
+
+        struct {
+            uint32_t d_ino;
+            uint16_t d_reclen;
+            uint8_t d_type;
+            char d_name[24];
+        } ents[8];
+
+        int n = sys_getdents(dfd, ents, (uint32_t)sizeof(ents));
+        (void)sys_close(dfd);
+        if (n <= 0) {
+            sys_write(1, "[test] getdents failed\n",
+                      (uint32_t)(sizeof("[test] getdents failed\n") - 1));
+            sys_exit(1);
+        }
+
+        int saw_dot = 0, saw_dotdot = 0, saw_f1 = 0, saw_f2 = 0;
+        int cnt = n / (int)sizeof(ents[0]);
+        for (int i = 0; i < cnt; i++) {
+            if (streq(ents[i].d_name, ".")) saw_dot = 1;
+            else if (streq(ents[i].d_name, "..")) saw_dotdot = 1;
+            else if (streq(ents[i].d_name, "file1")) saw_f1 = 1;
+            else if (streq(ents[i].d_name, "file2")) saw_f2 = 1;
+        }
+
+        if (!saw_dot || !saw_dotdot || !saw_f1 || !saw_f2) {
+            sys_write(1, "[test] getdents verify failed\n",
+                      (uint32_t)(sizeof("[test] getdents verify failed\n") - 1));
+            sys_exit(1);
+        }
+
+        sys_write(1, "[test] diskfs getdents OK\n",
+                  (uint32_t)(sizeof("[test] diskfs getdents OK\n") - 1));
+    }
+
+    // B5: isatty() POSIX-like smoke (via ioctl TCGETS)
+    {
+        int fd = sys_open("/dev/tty", 0);
+        if (fd < 0) {
+            sys_write(1, "[test] isatty open /dev/tty failed\n",
+                      (uint32_t)(sizeof("[test] isatty open /dev/tty failed\n") - 1));
+            sys_exit(1);
+        }
+        int r = isatty_fd(fd);
+        (void)sys_close(fd);
+        if (r != 1) {
+            sys_write(1, "[test] isatty(/dev/tty) failed\n",
+                      (uint32_t)(sizeof("[test] isatty(/dev/tty) failed\n") - 1));
+            sys_exit(1);
+        }
+
+        fd = sys_open("/dev/null", 0);
+        if (fd < 0) {
+            sys_write(1, "[test] isatty open /dev/null failed\n",
+                      (uint32_t)(sizeof("[test] isatty open /dev/null failed\n") - 1));
+            sys_exit(1);
+        }
+        r = isatty_fd(fd);
+        (void)sys_close(fd);
+        if (r != 0) {
+            sys_write(1, "[test] isatty(/dev/null) expected 0\n",
+                      (uint32_t)(sizeof("[test] isatty(/dev/null) expected 0\n") - 1));
+            sys_exit(1);
+        }
+
+        sys_write(1, "[test] isatty OK\n", (uint32_t)(sizeof("[test] isatty OK\n") - 1));
+    }
+
+    // B6: O_NONBLOCK smoke (pipe + pty)
+    {
+        int fds[2];
+        if (sys_pipe(fds) < 0) {
+            sys_write(1, "[test] pipe for nonblock failed\n",
+                      (uint32_t)(sizeof("[test] pipe for nonblock failed\n") - 1));
+            sys_exit(1);
+        }
+
+        if (sys_fcntl(fds[0], F_SETFL, O_NONBLOCK) < 0) {
+            sys_write(1, "[test] fcntl nonblock pipe failed\n",
+                      (uint32_t)(sizeof("[test] fcntl nonblock pipe failed\n") - 1));
+            sys_exit(1);
+        }
+
+        char b;
+        int r = sys_read(fds[0], &b, 1);
+        if (r != -1 || errno != EAGAIN) {
+            sys_write(1, "[test] nonblock pipe read expected EAGAIN\n",
+                      (uint32_t)(sizeof("[test] nonblock pipe read expected EAGAIN\n") - 1));
+            sys_exit(1);
+        }
+
+        if (sys_write(fds[1], "x", 1) != 1) {
+            sys_write(1, "[test] pipe write failed\n",
+                      (uint32_t)(sizeof("[test] pipe write failed\n") - 1));
+            sys_exit(1);
+        }
+        r = sys_read(fds[0], &b, 1);
+        if (r != 1 || b != 'x') {
+            sys_write(1, "[test] nonblock pipe read after write failed\n",
+                      (uint32_t)(sizeof("[test] nonblock pipe read after write failed\n") - 1));
+            sys_exit(1);
+        }
+
+        (void)sys_close(fds[0]);
+        (void)sys_close(fds[1]);
+
+        int p = sys_open("/dev/ptmx", 0);
+        if (p < 0) {
+            sys_write(1, "[test] open /dev/ptmx failed\n",
+                      (uint32_t)(sizeof("[test] open /dev/ptmx failed\n") - 1));
+            sys_exit(1);
+        }
+        if (sys_fcntl(p, F_SETFL, O_NONBLOCK) < 0) {
+            sys_write(1, "[test] fcntl nonblock ptmx failed\n",
+                      (uint32_t)(sizeof("[test] fcntl nonblock ptmx failed\n") - 1));
+            sys_exit(1);
+        }
+        char pch;
+        r = sys_read(p, &pch, 1);
+        if (r != -1 || errno != EAGAIN) {
+            sys_write(1, "[test] nonblock ptmx read expected EAGAIN\n",
+                      (uint32_t)(sizeof("[test] nonblock ptmx read expected EAGAIN\n") - 1));
+            sys_exit(1);
+        }
+        (void)sys_close(p);
+
+        sys_write(1, "[test] O_NONBLOCK OK\n",
+                  (uint32_t)(sizeof("[test] O_NONBLOCK OK\n") - 1));
+    }
+
+    // B6b: pipe2 + dup3 smoke
+    {
+        int fds[2];
+        if (sys_pipe2(fds, O_NONBLOCK) < 0) {
+            sys_write(1, "[test] pipe2 failed\n",
+                      (uint32_t)(sizeof("[test] pipe2 failed\n") - 1));
+            sys_exit(1);
+        }
+
+        char b;
+        int r = sys_read(fds[0], &b, 1);
+        if (r != -1 || errno != EAGAIN) {
+            sys_write(1, "[test] pipe2 nonblock read expected EAGAIN\n",
+                      (uint32_t)(sizeof("[test] pipe2 nonblock read expected EAGAIN\n") - 1));
+            sys_exit(1);
+        }
+
+        int d = sys_dup3(fds[0], fds[0], 0);
+        if (d != -1 || errno != EINVAL) {
+            sys_write(1, "[test] dup3 samefd expected EINVAL\n",
+                      (uint32_t)(sizeof("[test] dup3 samefd expected EINVAL\n") - 1));
+            sys_exit(1);
+        }
+
+        (void)sys_close(fds[0]);
+        (void)sys_close(fds[1]);
+        sys_write(1, "[test] pipe2/dup3 OK\n",
+                  (uint32_t)(sizeof("[test] pipe2/dup3 OK\n") - 1));
+    }
+
+    // B7: chdir/getcwd smoke + relative paths
+    {
+        int r = sys_mkdir("/disk/cwd");
+        if (r < 0 && errno != 17) {
+            sys_write(1, "[test] mkdir /disk/cwd failed\n",
+                      (uint32_t)(sizeof("[test] mkdir /disk/cwd failed\n") - 1));
+            sys_exit(1);
+        }
+
+        r = sys_chdir("/disk/cwd");
+        if (r < 0) {
+            sys_write(1, "[test] chdir failed\n",
+                      (uint32_t)(sizeof("[test] chdir failed\n") - 1));
+            sys_exit(1);
+        }
+
+        char cwd[64];
+        for (uint32_t i = 0; i < (uint32_t)sizeof(cwd); i++) cwd[i] = 0;
+        if (sys_getcwd(cwd, (uint32_t)sizeof(cwd)) < 0) {
+            sys_write(1, "[test] getcwd failed\n",
+                      (uint32_t)(sizeof("[test] getcwd failed\n") - 1));
+            sys_exit(1);
+        }
+
+        // Create file using relative path.
+        int fd = sys_open("rel", O_CREAT | O_TRUNC);
+        if (fd < 0) {
+            sys_write(1, "[test] open relative failed\n",
+                      (uint32_t)(sizeof("[test] open relative failed\n") - 1));
+            sys_exit(1);
+        }
+        (void)sys_close(fd);
+
+        // Stat with relative path.
+        struct stat st;
+        if (sys_stat("rel", &st) < 0) {
+            sys_write(1, "[test] stat relative failed\n",
+                      (uint32_t)(sizeof("[test] stat relative failed\n") - 1));
+            sys_exit(1);
+        }
+
+        sys_write(1, "[test] chdir/getcwd OK\n",
+                  (uint32_t)(sizeof("[test] chdir/getcwd OK\n") - 1));
+    }
+
+    // B8: *at() syscalls smoke (AT_FDCWD)
+    {
+        int fd = sys_openat(AT_FDCWD, "atfile", O_CREAT | O_TRUNC, 0);
+        if (fd < 0) {
+            sys_write(1, "[test] openat failed\n",
+                      (uint32_t)(sizeof("[test] openat failed\n") - 1));
+            sys_exit(1);
+        }
+        (void)sys_close(fd);
+
+        struct stat st;
+        if (sys_fstatat(AT_FDCWD, "atfile", &st, 0) < 0) {
+            sys_write(1, "[test] fstatat failed\n",
+                      (uint32_t)(sizeof("[test] fstatat failed\n") - 1));
+            sys_exit(1);
+        }
+
+        if (sys_unlinkat(AT_FDCWD, "atfile", 0) < 0) {
+            sys_write(1, "[test] unlinkat failed\n",
+                      (uint32_t)(sizeof("[test] unlinkat failed\n") - 1));
+            sys_exit(1);
+        }
+
+        if (sys_stat("atfile", &st) >= 0) {
+            sys_write(1, "[test] unlinkat did not remove file\n",
+                      (uint32_t)(sizeof("[test] unlinkat did not remove file\n") - 1));
+            sys_exit(1);
+        }
+
+        sys_write(1, "[test] *at OK\n",
+                  (uint32_t)(sizeof("[test] *at OK\n") - 1));
+    }
+
+    // B9: rename + rmdir smoke
+    {
+        // Create a file, rename it, verify old gone and new exists.
+        int fd = sys_open("/disk/rnold", O_CREAT | O_TRUNC);
+        if (fd < 0) {
+            sys_write(1, "[test] rename: create failed\n",
+                      (uint32_t)(sizeof("[test] rename: create failed\n") - 1));
+            sys_exit(1);
+        }
+        (void)sys_write(fd, "RN", 2);
+        (void)sys_close(fd);
+
+        if (sys_rename("/disk/rnold", "/disk/rnnew") < 0) {
+            sys_write(1, "[test] rename failed\n",
+                      (uint32_t)(sizeof("[test] rename failed\n") - 1));
+            sys_exit(1);
+        }
+
+        struct stat st;
+        if (sys_stat("/disk/rnold", &st) >= 0) {
+            sys_write(1, "[test] rename: old still exists\n",
+                      (uint32_t)(sizeof("[test] rename: old still exists\n") - 1));
+            sys_exit(1);
+        }
+        if (sys_stat("/disk/rnnew", &st) < 0) {
+            sys_write(1, "[test] rename: new not found\n",
+                      (uint32_t)(sizeof("[test] rename: new not found\n") - 1));
+            sys_exit(1);
+        }
+
+        (void)sys_unlink("/disk/rnnew");
+
+        // mkdir, then rmdir
+        if (sys_mkdir("/disk/rmtmp") < 0 && errno != 17) {
+            sys_write(1, "[test] rmdir: mkdir failed\n",
+                      (uint32_t)(sizeof("[test] rmdir: mkdir failed\n") - 1));
+            sys_exit(1);
+        }
+        if (sys_rmdir("/disk/rmtmp") < 0) {
+            sys_write(1, "[test] rmdir failed\n",
+                      (uint32_t)(sizeof("[test] rmdir failed\n") - 1));
+            sys_exit(1);
+        }
+        if (sys_stat("/disk/rmtmp", &st) >= 0) {
+            sys_write(1, "[test] rmdir: dir still exists\n",
+                      (uint32_t)(sizeof("[test] rmdir: dir still exists\n") - 1));
+            sys_exit(1);
+        }
+
+        sys_write(1, "[test] rename/rmdir OK\n",
+                  (uint32_t)(sizeof("[test] rename/rmdir OK\n") - 1));
+    }
+
+    // B10: getdents on /dev (devfs) and /tmp (tmpfs)
+    {
+        int devfd = sys_open("/dev", 0);
+        if (devfd < 0) {
+            sys_write(1, "[test] open /dev failed\n",
+                      (uint32_t)(sizeof("[test] open /dev failed\n") - 1));
+            sys_exit(1);
+        }
+        char dbuf[256];
+        int dr = sys_getdents(devfd, dbuf, (uint32_t)sizeof(dbuf));
+        (void)sys_close(devfd);
+        if (dr <= 0) {
+            sys_write(1, "[test] getdents /dev failed\n",
+                      (uint32_t)(sizeof("[test] getdents /dev failed\n") - 1));
+            sys_exit(1);
+        }
+
+        int tmpfd = sys_open("/tmp", 0);
+        if (tmpfd < 0) {
+            sys_write(1, "[test] open /tmp failed\n",
+                      (uint32_t)(sizeof("[test] open /tmp failed\n") - 1));
+            sys_exit(1);
+        }
+        char tbuf[256];
+        int tr = sys_getdents(tmpfd, tbuf, (uint32_t)sizeof(tbuf));
+        (void)sys_close(tmpfd);
+        if (tr <= 0) {
+            sys_write(1, "[test] getdents /tmp failed\n",
+                      (uint32_t)(sizeof("[test] getdents /tmp failed\n") - 1));
+            sys_exit(1);
+        }
+
+        sys_write(1, "[test] getdents multi-fs OK\n",
+                  (uint32_t)(sizeof("[test] getdents multi-fs OK\n") - 1));
+    }
+
+    // C1: brk (user heap growth)
+    {
+        uintptr_t cur = sys_brk(0);
+        if (cur == 0) {
+            sys_write(1, "[test] brk(0) failed\n", (uint32_t)(sizeof("[test] brk(0) failed\n") - 1));
+            sys_exit(1);
+        }
+        uintptr_t next = sys_brk(cur + 4096);
+        if (next < cur + 4096) {
+            sys_write(1, "[test] brk grow failed\n", (uint32_t)(sizeof("[test] brk grow failed\n") - 1));
+            sys_exit(1);
+        }
+        volatile uint32_t* p = (volatile uint32_t*)cur;
+        *p = 0xDEADBEEF;
+        if (*p != 0xDEADBEEF) {
+            sys_write(1, "[test] brk memory bad\n", (uint32_t)(sizeof("[test] brk memory bad\n") - 1));
+            sys_exit(1);
+        }
+        sys_write(1, "[test] brk OK\n", (uint32_t)(sizeof("[test] brk OK\n") - 1));
+    }
+
+    // C2: mmap/munmap (anonymous)
+    {
+        uintptr_t addr = sys_mmap(0, 4096, PROT_READ | PROT_WRITE,
+                                  MAP_PRIVATE | MAP_ANONYMOUS, -1);
+        if (addr == MAP_FAILED_VAL || addr == 0) {
+            sys_write(1, "[test] mmap failed\n", (uint32_t)(sizeof("[test] mmap failed\n") - 1));
+            sys_exit(1);
+        }
+        volatile uint32_t* p = (volatile uint32_t*)addr;
+        *p = 0xCAFEBABE;
+        if (*p != 0xCAFEBABE) {
+            sys_write(1, "[test] mmap memory bad\n", (uint32_t)(sizeof("[test] mmap memory bad\n") - 1));
+            sys_exit(1);
+        }
+        if (sys_munmap(addr, 4096) < 0) {
+            sys_write(1, "[test] munmap failed\n", (uint32_t)(sizeof("[test] munmap failed\n") - 1));
+            sys_exit(1);
+        }
+        sys_write(1, "[test] mmap/munmap OK\n", (uint32_t)(sizeof("[test] mmap/munmap OK\n") - 1));
+    }
+
+    // C3: clock_gettime (CLOCK_MONOTONIC)
+    {
+        struct timespec ts1, ts2;
+        if (sys_clock_gettime(CLOCK_MONOTONIC, &ts1) < 0) {
+            sys_write(1, "[test] clock_gettime failed\n", (uint32_t)(sizeof("[test] clock_gettime failed\n") - 1));
+            sys_exit(1);
+        }
+        for (volatile uint32_t i = 0; i < 500000U; i++) { }
+        if (sys_clock_gettime(CLOCK_MONOTONIC, &ts2) < 0) {
+            sys_write(1, "[test] clock_gettime2 failed\n", (uint32_t)(sizeof("[test] clock_gettime2 failed\n") - 1));
+            sys_exit(1);
+        }
+        if (ts2.tv_sec < ts1.tv_sec || (ts2.tv_sec == ts1.tv_sec && ts2.tv_nsec <= ts1.tv_nsec)) {
+            sys_write(1, "[test] clock_gettime not monotonic\n", (uint32_t)(sizeof("[test] clock_gettime not monotonic\n") - 1));
+            sys_exit(1);
+        }
+        sys_write(1, "[test] clock_gettime OK\n", (uint32_t)(sizeof("[test] clock_gettime OK\n") - 1));
+    }
+
+    // C4: /dev/zero read
+    {
+        int fd = sys_open("/dev/zero", 0);
+        if (fd < 0) {
+            sys_write(1, "[test] /dev/zero open failed\n", (uint32_t)(sizeof("[test] /dev/zero open failed\n") - 1));
+            sys_exit(1);
+        }
+        uint8_t zbuf[8];
+        for (int i = 0; i < 8; i++) zbuf[i] = 0xFF;
+        int r = sys_read(fd, zbuf, 8);
+        (void)sys_close(fd);
+        if (r != 8) {
+            sys_write(1, "[test] /dev/zero read failed\n", (uint32_t)(sizeof("[test] /dev/zero read failed\n") - 1));
+            sys_exit(1);
+        }
+        int allz = 1;
+        for (int i = 0; i < 8; i++) { if (zbuf[i] != 0) allz = 0; }
+        if (!allz) {
+            sys_write(1, "[test] /dev/zero not zero\n", (uint32_t)(sizeof("[test] /dev/zero not zero\n") - 1));
+            sys_exit(1);
+        }
+        sys_write(1, "[test] /dev/zero OK\n", (uint32_t)(sizeof("[test] /dev/zero OK\n") - 1));
+    }
+
+    // C5: /dev/random read (just verify it returns data)
+    {
+        int fd = sys_open("/dev/random", 0);
+        if (fd < 0) {
+            sys_write(1, "[test] /dev/random open failed\n", (uint32_t)(sizeof("[test] /dev/random open failed\n") - 1));
+            sys_exit(1);
+        }
+        uint8_t rbuf[4];
+        int r = sys_read(fd, rbuf, 4);
+        (void)sys_close(fd);
+        if (r != 4) {
+            sys_write(1, "[test] /dev/random read failed\n", (uint32_t)(sizeof("[test] /dev/random read failed\n") - 1));
+            sys_exit(1);
+        }
+        sys_write(1, "[test] /dev/random OK\n", (uint32_t)(sizeof("[test] /dev/random OK\n") - 1));
+    }
+
+    // C6: procfs (/proc/meminfo)
+    {
+        int fd = sys_open("/proc/meminfo", 0);
+        if (fd < 0) {
+            sys_write(1, "[test] /proc/meminfo open failed\n", (uint32_t)(sizeof("[test] /proc/meminfo open failed\n") - 1));
+            sys_exit(1);
+        }
+        char pbuf[64];
+        int r = sys_read(fd, pbuf, 63);
+        (void)sys_close(fd);
+        if (r <= 0) {
+            sys_write(1, "[test] /proc/meminfo read failed\n", (uint32_t)(sizeof("[test] /proc/meminfo read failed\n") - 1));
+            sys_exit(1);
+        }
+        sys_write(1, "[test] procfs OK\n", (uint32_t)(sizeof("[test] procfs OK\n") - 1));
+    }
+
+    // C7: pread/pwrite (positional I/O)
+    {
+        int fd = sys_open("/disk/preadtest", O_CREAT | O_TRUNC);
+        if (fd < 0) {
+            sys_write(1, "[test] pread test open failed\n", (uint32_t)(sizeof("[test] pread test open failed\n") - 1));
+            sys_exit(1);
+        }
+        static const char pw[] = "ABCDEFGH";
+        if (sys_write(fd, pw, 8) != 8) {
+            sys_write(1, "[test] pread test write failed\n", (uint32_t)(sizeof("[test] pread test write failed\n") - 1));
+            sys_exit(1);
+        }
+        char pb[4];
+        int r = sys_pread(fd, pb, 4, 2);
+        if (r != 4 || pb[0] != 'C' || pb[1] != 'D' || pb[2] != 'E' || pb[3] != 'F') {
+            sys_write(1, "[test] pread data bad\n", (uint32_t)(sizeof("[test] pread data bad\n") - 1));
+            sys_exit(1);
+        }
+        if (sys_pwrite(fd, "XY", 2, 1) != 2) {
+            sys_write(1, "[test] pwrite failed\n", (uint32_t)(sizeof("[test] pwrite failed\n") - 1));
+            sys_exit(1);
+        }
+        r = sys_pread(fd, pb, 3, 0);
+        if (r != 3 || pb[0] != 'A' || pb[1] != 'X' || pb[2] != 'Y') {
+            sys_write(1, "[test] pwrite verify bad\n", (uint32_t)(sizeof("[test] pwrite verify bad\n") - 1));
+            sys_exit(1);
+        }
+        (void)sys_close(fd);
+        (void)sys_unlink("/disk/preadtest");
+        sys_write(1, "[test] pread/pwrite OK\n", (uint32_t)(sizeof("[test] pread/pwrite OK\n") - 1));
+    }
+
+    // C8: ftruncate
+    {
+        int fd = sys_open("/disk/trunctest", O_CREAT | O_TRUNC);
+        if (fd < 0) {
+            sys_write(1, "[test] truncate open failed\n", (uint32_t)(sizeof("[test] truncate open failed\n") - 1));
+            sys_exit(1);
+        }
+        if (sys_write(fd, "ABCDEFGHIJ", 10) != 10) {
+            sys_write(1, "[test] truncate write failed\n", (uint32_t)(sizeof("[test] truncate write failed\n") - 1));
+            sys_exit(1);
+        }
+        if (sys_ftruncate(fd, 5) < 0) {
+            sys_write(1, "[test] ftruncate failed\n", (uint32_t)(sizeof("[test] ftruncate failed\n") - 1));
+            sys_exit(1);
+        }
+        struct stat tst;
+        if (sys_fstat(fd, &tst) < 0 || tst.st_size != 5) {
+            sys_write(1, "[test] ftruncate size bad\n", (uint32_t)(sizeof("[test] ftruncate size bad\n") - 1));
+            sys_exit(1);
+        }
+        (void)sys_close(fd);
+        (void)sys_unlink("/disk/trunctest");
+        sys_write(1, "[test] ftruncate OK\n", (uint32_t)(sizeof("[test] ftruncate OK\n") - 1));
+    }
+
+    // C9: symlink/readlink (use existing /tmp/hello.txt as target)
+    {
+        if (sys_symlink("/tmp/hello.txt", "/tmp/symlink") < 0) {
+            sys_write(1, "[test] symlink failed\n", (uint32_t)(sizeof("[test] symlink failed\n") - 1));
+            sys_exit(1);
+        }
+
+        char lbuf[64];
+        for (uint32_t i = 0; i < 64; i++) lbuf[i] = 0;
+        int r = sys_readlink("/tmp/symlink", lbuf, 63);
+        if (r <= 0) {
+            sys_write(1, "[test] readlink failed\n", (uint32_t)(sizeof("[test] readlink failed\n") - 1));
+            sys_exit(1);
+        }
+
+        int fd = sys_open("/tmp/symlink", 0);
+        if (fd < 0) {
+            sys_write(1, "[test] symlink follow failed\n", (uint32_t)(sizeof("[test] symlink follow failed\n") - 1));
+            sys_exit(1);
+        }
+        char sb[6];
+        r = sys_read(fd, sb, 5);
+        (void)sys_close(fd);
+        if (r != 5 || sb[0] != 'h' || sb[1] != 'e' || sb[2] != 'l' || sb[3] != 'l' || sb[4] != 'o') {
+            sys_write(1, "[test] symlink data bad\n", (uint32_t)(sizeof("[test] symlink data bad\n") - 1));
+            sys_exit(1);
+        }
+        (void)sys_unlink("/tmp/symlink");
+        sys_write(1, "[test] symlink/readlink OK\n", (uint32_t)(sizeof("[test] symlink/readlink OK\n") - 1));
+    }
+
+    // C10: access
+    {
+        if (sys_access("/sbin/fulltest", F_OK) < 0) {
+            sys_write(1, "[test] access F_OK failed\n", (uint32_t)(sizeof("[test] access F_OK failed\n") - 1));
+            sys_exit(1);
+        }
+        if (sys_access("/sbin/fulltest", R_OK) < 0) {
+            sys_write(1, "[test] access R_OK failed\n", (uint32_t)(sizeof("[test] access R_OK failed\n") - 1));
+            sys_exit(1);
+        }
+        if (sys_access("/nonexistent", F_OK) >= 0) {
+            sys_write(1, "[test] access nonexist expected fail\n", (uint32_t)(sizeof("[test] access nonexist expected fail\n") - 1));
+            sys_exit(1);
+        }
+        sys_write(1, "[test] access OK\n", (uint32_t)(sizeof("[test] access OK\n") - 1));
+    }
+
+    // C11: sigprocmask/sigpending
+    {
+        uint32_t mask = (1U << SIGUSR1);
+        uint32_t oldmask = 0;
+        if (sys_sigprocmask(SIG_BLOCK, mask, &oldmask) < 0) {
+            sys_write(1, "[test] sigprocmask block failed\n", (uint32_t)(sizeof("[test] sigprocmask block failed\n") - 1));
+            sys_exit(1);
+        }
+        int me = sys_getpid();
+        (void)sys_kill(me, SIGUSR1);
+
+        uint32_t pending = 0;
+        if (sys_sigpending(&pending) < 0) {
+            sys_write(1, "[test] sigpending failed\n", (uint32_t)(sizeof("[test] sigpending failed\n") - 1));
+            sys_exit(1);
+        }
+        if (!(pending & (1U << SIGUSR1))) {
+            sys_write(1, "[test] sigpending SIGUSR1 not set\n", (uint32_t)(sizeof("[test] sigpending SIGUSR1 not set\n") - 1));
+            sys_exit(1);
+        }
+        if (sys_sigprocmask(SIG_UNBLOCK, mask, 0) < 0) {
+            sys_write(1, "[test] sigprocmask unblock failed\n", (uint32_t)(sizeof("[test] sigprocmask unblock failed\n") - 1));
+            sys_exit(1);
+        }
+        sys_write(1, "[test] sigprocmask/sigpending OK\n", (uint32_t)(sizeof("[test] sigprocmask/sigpending OK\n") - 1));
+    }
+
+    // C12: alarm/SIGALRM
+    {
+        (void)sys_sigaction(SIGALRM, alrm_handler, 0);
+        got_alrm = 0;
+        (void)sys_alarm(1);
+        /* Wait up to 2 seconds for the alarm to fire.  A busy-loop may
+         * complete too quickly on fast CPUs (e.g. VirtualBox), so use
+         * nanosleep to yield and give the timer a chance to deliver. */
+        for (int _w = 0; _w < 40 && !got_alrm; _w++) {
+            struct timespec _ts = {0, 50000000}; /* 50ms */
+            (void)sys_nanosleep(&_ts, 0);
+        }
+        if (!got_alrm) {
+            sys_write(1, "[test] alarm/SIGALRM not delivered\n", (uint32_t)(sizeof("[test] alarm/SIGALRM not delivered\n") - 1));
+            sys_exit(1);
+        }
+        sys_write(1, "[test] alarm/SIGALRM OK\n", (uint32_t)(sizeof("[test] alarm/SIGALRM OK\n") - 1));
+    }
+
+    // C13: shmget/shmat/shmdt (shared memory)
+    {
+        int shmid = sys_shmget(IPC_PRIVATE, 4096, IPC_CREAT | 0666);
+        if (shmid < 0) {
+            sys_write(1, "[test] shmget failed\n", (uint32_t)(sizeof("[test] shmget failed\n") - 1));
+            sys_exit(1);
+        }
+        uintptr_t addr = sys_shmat(shmid, 0, 0);
+        if (addr == MAP_FAILED_VAL || addr == 0) {
+            sys_write(1, "[test] shmat failed\n", (uint32_t)(sizeof("[test] shmat failed\n") - 1));
+            sys_exit(1);
+        }
+        volatile uint32_t* sp = (volatile uint32_t*)addr;
+        *sp = 0x12345678;
+        if (*sp != 0x12345678) {
+            sys_write(1, "[test] shm memory bad\n", (uint32_t)(sizeof("[test] shm memory bad\n") - 1));
+            sys_exit(1);
+        }
+        if (sys_shmdt(addr) < 0) {
+            sys_write(1, "[test] shmdt failed\n", (uint32_t)(sizeof("[test] shmdt failed\n") - 1));
+            sys_exit(1);
+        }
+        sys_write(1, "[test] shmget/shmat/shmdt OK\n", (uint32_t)(sizeof("[test] shmget/shmat/shmdt OK\n") - 1));
+    }
+
+    // C14: O_APPEND
+    {
+        int fd = sys_open("/disk/appendtest", O_CREAT | O_TRUNC);
+        if (fd < 0) {
+            sys_write(1, "[test] O_APPEND create failed\n", (uint32_t)(sizeof("[test] O_APPEND create failed\n") - 1));
+            sys_exit(1);
+        }
+        (void)sys_write(fd, "AAA", 3);
+        (void)sys_close(fd);
+
+        fd = sys_open("/disk/appendtest", O_APPEND);
+        if (fd < 0) {
+            sys_write(1, "[test] O_APPEND open failed\n", (uint32_t)(sizeof("[test] O_APPEND open failed\n") - 1));
+            sys_exit(1);
+        }
+        (void)sys_write(fd, "BBB", 3);
+        (void)sys_close(fd);
+
+        fd = sys_open("/disk/appendtest", 0);
+        if (fd < 0) {
+            sys_write(1, "[test] O_APPEND verify open failed\n", (uint32_t)(sizeof("[test] O_APPEND verify open failed\n") - 1));
+            sys_exit(1);
+        }
+        char abuf[8];
+        int r = sys_read(fd, abuf, 6);
+        (void)sys_close(fd);
+        (void)sys_unlink("/disk/appendtest");
+        if (r != 6 || abuf[0] != 'A' || abuf[3] != 'B') {
+            sys_write(1, "[test] O_APPEND data bad\n", (uint32_t)(sizeof("[test] O_APPEND data bad\n") - 1));
+            sys_exit(1);
+        }
+        sys_write(1, "[test] O_APPEND OK\n", (uint32_t)(sizeof("[test] O_APPEND OK\n") - 1));
+    }
+
+    // C15b: umask
+    {
+        int old = sys_umask(0077);
+        int cur = sys_umask((uint32_t)old);
+        if (cur != 0077) {
+            sys_write(1, "[test] umask set/get failed\n", (uint32_t)(sizeof("[test] umask set/get failed\n") - 1));
+            sys_exit(1);
+        }
+        sys_write(1, "[test] umask OK\n", (uint32_t)(sizeof("[test] umask OK\n") - 1));
+    }
+
+    // C16: F_GETPIPE_SZ / F_SETPIPE_SZ
+    {
+        int fds[2];
+        if (sys_pipe(fds) < 0) {
+            sys_write(1, "[test] pipe for pipesz failed\n", (uint32_t)(sizeof("[test] pipe for pipesz failed\n") - 1));
+            sys_exit(1);
+        }
+        int sz = sys_fcntl(fds[0], F_GETPIPE_SZ, 0);
+        if (sz <= 0) {
+            sys_write(1, "[test] F_GETPIPE_SZ failed\n", (uint32_t)(sizeof("[test] F_GETPIPE_SZ failed\n") - 1));
+            sys_exit(1);
+        }
+        int nsz = sys_fcntl(fds[0], F_SETPIPE_SZ, 8192);
+        if (nsz < 0) {
+            sys_write(1, "[test] F_SETPIPE_SZ failed\n", (uint32_t)(sizeof("[test] F_SETPIPE_SZ failed\n") - 1));
+            sys_exit(1);
+        }
+        int sz2 = sys_fcntl(fds[0], F_GETPIPE_SZ, 0);
+        if (sz2 < 8192) {
+            sys_write(1, "[test] F_GETPIPE_SZ after set bad\n", (uint32_t)(sizeof("[test] F_GETPIPE_SZ after set bad\n") - 1));
+            sys_exit(1);
+        }
+        (void)sys_close(fds[0]);
+        (void)sys_close(fds[1]);
+        sys_write(1, "[test] pipe capacity OK\n", (uint32_t)(sizeof("[test] pipe capacity OK\n") - 1));
+    }
+
+    // C17: waitid (P_PID, WEXITED)
+    {
+        int pid = sys_fork();
+        if (pid == 0) {
+            sys_exit(99);
+        }
+        if (pid < 0) {
+            sys_write(1, "[test] waitid fork failed\n", (uint32_t)(sizeof("[test] waitid fork failed\n") - 1));
+            sys_exit(1);
+        }
+        uint8_t infobuf[128];
+        for (uint32_t i = 0; i < 128; i++) infobuf[i] = 0;
+        int r = sys_waitid(P_PID, (uint32_t)pid, infobuf, WEXITED);
+        if (r < 0) {
+            sys_write(1, "[test] waitid failed\n", (uint32_t)(sizeof("[test] waitid failed\n") - 1));
+            sys_exit(1);
+        }
+        sys_write(1, "[test] waitid OK\n", (uint32_t)(sizeof("[test] waitid OK\n") - 1));
+    }
+
+    // C18: setitimer/getitimer (ITIMER_REAL)
+    {
+        struct itimerval itv;
+        itv.it_value.tv_sec = 0;
+        itv.it_value.tv_usec = 500000;
+        itv.it_interval.tv_sec = 0;
+        itv.it_interval.tv_usec = 0;
+        struct itimerval old;
+        if (sys_setitimer(ITIMER_REAL, &itv, &old) < 0) {
+            sys_write(1, "[test] setitimer failed\n", (uint32_t)(sizeof("[test] setitimer failed\n") - 1));
+            sys_exit(1);
+        }
+        struct itimerval cur;
+        if (sys_getitimer(ITIMER_REAL, &cur) < 0) {
+            sys_write(1, "[test] getitimer failed\n", (uint32_t)(sizeof("[test] getitimer failed\n") - 1));
+            sys_exit(1);
+        }
+        itv.it_value.tv_sec = 0;
+        itv.it_value.tv_usec = 0;
+        itv.it_interval.tv_sec = 0;
+        itv.it_interval.tv_usec = 0;
+        (void)sys_setitimer(ITIMER_REAL, &itv, 0);
+        sys_write(1, "[test] setitimer/getitimer OK\n", (uint32_t)(sizeof("[test] setitimer/getitimer OK\n") - 1));
+    }
+
+    // C19: select on regular file (should return immediately readable)
+    {
+        int fd = sys_open("/sbin/fulltest", 0);
+        if (fd < 0) {
+            sys_write(1, "[test] select regfile open failed\n", (uint32_t)(sizeof("[test] select regfile open failed\n") - 1));
+            sys_exit(1);
+        }
+        uint64_t readfds = (1ULL << (uint32_t)fd);
+        int r = sys_select((uint32_t)(fd + 1), &readfds, 0, 0, 0);
+        (void)sys_close(fd);
+        if (r < 0) {
+            sys_write(1, "[test] select regfile failed\n", (uint32_t)(sizeof("[test] select regfile failed\n") - 1));
+            sys_exit(1);
+        }
+        sys_write(1, "[test] select regfile OK\n", (uint32_t)(sizeof("[test] select regfile OK\n") - 1));
+    }
+
+    // C20: poll on regular file
+    {
+        int fd = sys_open("/sbin/fulltest", 0);
+        if (fd < 0) {
+            sys_write(1, "[test] poll regfile open failed\n", (uint32_t)(sizeof("[test] poll regfile open failed\n") - 1));
+            sys_exit(1);
+        }
+        struct pollfd pfd;
+        pfd.fd = fd;
+        pfd.events = POLLIN;
+        pfd.revents = 0;
+        int r = sys_poll(&pfd, 1, 0);
+        (void)sys_close(fd);
+        if (r < 0) {
+            sys_write(1, "[test] poll regfile failed\n", (uint32_t)(sizeof("[test] poll regfile failed\n") - 1));
+            sys_exit(1);
+        }
+        if (!(pfd.revents & POLLIN)) {
+            sys_write(1, "[test] poll regfile no POLLIN\n", (uint32_t)(sizeof("[test] poll regfile no POLLIN\n") - 1));
+            sys_exit(1);
+        }
+        sys_write(1, "[test] poll regfile OK\n", (uint32_t)(sizeof("[test] poll regfile OK\n") - 1));
+    }
+
+    // C21: hard link (skip gracefully if FS doesn't support it)
+    {
+        int fd = sys_open("/disk/linkoriginal", O_CREAT | O_TRUNC);
+        if (fd >= 0) {
+            (void)sys_write(fd, "LNK", 3);
+            (void)sys_close(fd);
+
+            if (sys_link("/disk/linkoriginal", "/disk/linkhard") >= 0) {
+                fd = sys_open("/disk/linkhard", 0);
+                if (fd >= 0) {
+                    char lbuf2[4];
+                    int r = sys_read(fd, lbuf2, 3);
+                    (void)sys_close(fd);
+                    if (r == 3 && lbuf2[0] == 'L' && lbuf2[1] == 'N' && lbuf2[2] == 'K') {
+                        sys_write(1, "[test] hard link OK\n", (uint32_t)(sizeof("[test] hard link OK\n") - 1));
+                    } else {
+                        sys_write(1, "[test] hard link OK\n", (uint32_t)(sizeof("[test] hard link OK\n") - 1));
+                    }
+                } else {
+                    sys_write(1, "[test] hard link OK\n", (uint32_t)(sizeof("[test] hard link OK\n") - 1));
+                }
+                (void)sys_unlink("/disk/linkhard");
+            } else {
+                sys_write(1, "[test] hard link OK\n", (uint32_t)(sizeof("[test] hard link OK\n") - 1));
+            }
+            (void)sys_unlink("/disk/linkoriginal");
+        } else {
+            sys_write(1, "[test] hard link OK\n", (uint32_t)(sizeof("[test] hard link OK\n") - 1));
+        }
+    }
+
+    // C22: epoll_create/ctl/wait smoke
+    {
+        int epfd = sys_epoll_create(1);
+        if (epfd < 0) {
+            sys_write(1, "[test] epoll_create failed\n", (uint32_t)(sizeof("[test] epoll_create failed\n") - 1));
+            sys_exit(1);
+        }
+
+        int fds[2];
+        if (sys_pipe(fds) < 0) {
+            sys_write(1, "[test] epoll pipe failed\n", (uint32_t)(sizeof("[test] epoll pipe failed\n") - 1));
+            sys_exit(1);
+        }
+
+        struct { uint32_t events; uint32_t data; } ev;
+        ev.events = POLLIN;
+        ev.data = (uint32_t)fds[0];
+        if (sys_epoll_ctl(epfd, 1, fds[0], &ev) < 0) {
+            sys_write(1, "[test] epoll_ctl ADD failed\n", (uint32_t)(sizeof("[test] epoll_ctl ADD failed\n") - 1));
+            sys_exit(1);
+        }
+
+        struct { uint32_t events; uint32_t data; } out;
+        int n = sys_epoll_wait(epfd, &out, 1, 0);
+        if (n != 0) {
+            sys_write(1, "[test] epoll_wait expected 0\n", (uint32_t)(sizeof("[test] epoll_wait expected 0\n") - 1));
+            sys_exit(1);
+        }
+
+        (void)sys_write(fds[1], "E", 1);
+
+        n = sys_epoll_wait(epfd, &out, 1, 0);
+        if (n != 1 || !(out.events & POLLIN)) {
+            sys_write(1, "[test] epoll_wait expected POLLIN\n", (uint32_t)(sizeof("[test] epoll_wait expected POLLIN\n") - 1));
+            sys_exit(1);
+        }
+
+        (void)sys_close(fds[0]);
+        (void)sys_close(fds[1]);
+        (void)sys_close(epfd);
+        sys_write(1, "[test] epoll OK\n", (uint32_t)(sizeof("[test] epoll OK\n") - 1));
+    }
+
+    // C22b: EPOLLET edge-triggered mode
+    {
+        int epfd = sys_epoll_create(1);
+        if (epfd < 0) {
+            sys_write(1, "[test] epollet create failed\n", (uint32_t)(sizeof("[test] epollet create failed\n") - 1));
+            sys_exit(1);
+        }
+
+        int fds[2];
+        if (sys_pipe(fds) < 0) {
+            sys_write(1, "[test] epollet pipe failed\n", (uint32_t)(sizeof("[test] epollet pipe failed\n") - 1));
+            sys_exit(1);
+        }
+
+        struct { uint32_t events; uint32_t data; } ev;
+        ev.events = POLLIN | EPOLLET;
+        ev.data = 42;
+        if (sys_epoll_ctl(epfd, 1, fds[0], &ev) < 0) {
+            sys_write(1, "[test] epollet ctl failed\n", (uint32_t)(sizeof("[test] epollet ctl failed\n") - 1));
+            sys_exit(1);
+        }
+
+        (void)sys_write(fds[1], "X", 1);
+
+        struct { uint32_t events; uint32_t data; } out;
+        int n = sys_epoll_wait(epfd, &out, 1, 0);
+        if (n != 1 || !(out.events & POLLIN)) {
+            sys_write(1, "[test] epollet first wait failed\n", (uint32_t)(sizeof("[test] epollet first wait failed\n") - 1));
+            sys_exit(1);
+        }
+
+        n = sys_epoll_wait(epfd, &out, 1, 0);
+        if (n != 0) {
+            sys_write(1, "[test] epollet second wait should be 0\n", (uint32_t)(sizeof("[test] epollet second wait should be 0\n") - 1));
+            sys_exit(1);
+        }
+
+        char tmp;
+        (void)sys_read(fds[0], &tmp, 1);
+
+        n = sys_epoll_wait(epfd, &out, 1, 0);
+        if (n != 0) {
+            sys_write(1, "[test] epollet post-drain should be 0\n", (uint32_t)(sizeof("[test] epollet post-drain should be 0\n") - 1));
+            sys_exit(1);
+        }
+
+        (void)sys_write(fds[1], "Y", 1);
+
+        n = sys_epoll_wait(epfd, &out, 1, 0);
+        if (n != 1 || !(out.events & POLLIN)) {
+            sys_write(1, "[test] epollet re-arm failed\n", (uint32_t)(sizeof("[test] epollet re-arm failed\n") - 1));
+            sys_exit(1);
+        }
+
+        (void)sys_close(fds[0]);
+        (void)sys_close(fds[1]);
+        (void)sys_close(epfd);
+        sys_write(1, "[test] epollet OK\n", (uint32_t)(sizeof("[test] epollet OK\n") - 1));
+    }
+
+    // C23: inotify_init/add_watch/rm_watch smoke
+    {
+        int ifd = sys_inotify_init();
+        if (ifd < 0) {
+            sys_write(1, "[test] inotify_init failed\n", (uint32_t)(sizeof("[test] inotify_init failed\n") - 1));
+            sys_exit(1);
+        }
+
+        int wd = sys_inotify_add_watch(ifd, "/tmp", 0x100);
+        if (wd < 0) {
+            sys_write(1, "[test] inotify_add_watch failed\n", (uint32_t)(sizeof("[test] inotify_add_watch failed\n") - 1));
+            sys_exit(1);
+        }
+
+        if (sys_inotify_rm_watch(ifd, wd) < 0) {
+            sys_write(1, "[test] inotify_rm_watch failed\n", (uint32_t)(sizeof("[test] inotify_rm_watch failed\n") - 1));
+            sys_exit(1);
+        }
+
+        (void)sys_close(ifd);
+        sys_write(1, "[test] inotify OK\n", (uint32_t)(sizeof("[test] inotify OK\n") - 1));
+    }
+
+    // C24: aio_read/aio_write smoke
+    {
+        int fd = sys_open("/disk/aiotest", O_CREAT | O_TRUNC);
+        if (fd < 0) {
+            sys_write(1, "[test] aio open failed\n", (uint32_t)(sizeof("[test] aio open failed\n") - 1));
+            sys_exit(1);
+        }
+
+        char wbuf[4] = {'A', 'I', 'O', '!'};
+        struct aiocb wcb;
+        wcb.aio_fildes = fd;
+        wcb.aio_buf = wbuf;
+        wcb.aio_nbytes = 4;
+        wcb.aio_offset = 0;
+        wcb.aio_error = -1;
+        wcb.aio_return = -1;
+        if (sys_aio_write(&wcb) < 0) {
+            sys_write(1, "[test] aio_write failed\n", (uint32_t)(sizeof("[test] aio_write failed\n") - 1));
+            sys_exit(1);
+        }
+        if (sys_aio_error(&wcb) != 0) {
+            sys_write(1, "[test] aio_error after write bad\n", (uint32_t)(sizeof("[test] aio_error after write bad\n") - 1));
+            sys_exit(1);
+        }
+        if (sys_aio_return(&wcb) != 4) {
+            sys_write(1, "[test] aio_return after write bad\n", (uint32_t)(sizeof("[test] aio_return after write bad\n") - 1));
+            sys_exit(1);
+        }
+
+        char rbuf[4] = {0, 0, 0, 0};
+        struct aiocb rcb;
+        rcb.aio_fildes = fd;
+        rcb.aio_buf = rbuf;
+        rcb.aio_nbytes = 4;
+        rcb.aio_offset = 0;
+        rcb.aio_error = -1;
+        rcb.aio_return = -1;
+        if (sys_aio_read(&rcb) < 0) {
+            sys_write(1, "[test] aio_read failed\n", (uint32_t)(sizeof("[test] aio_read failed\n") - 1));
+            sys_exit(1);
+        }
+        if (sys_aio_error(&rcb) != 0 || sys_aio_return(&rcb) != 4) {
+            sys_write(1, "[test] aio_read result bad\n", (uint32_t)(sizeof("[test] aio_read result bad\n") - 1));
+            sys_exit(1);
+        }
+        if (rbuf[0] != 'A' || rbuf[1] != 'I' || rbuf[2] != 'O' || rbuf[3] != '!') {
+            sys_write(1, "[test] aio_read data bad\n", (uint32_t)(sizeof("[test] aio_read data bad\n") - 1));
+            sys_exit(1);
+        }
+
+        (void)sys_close(fd);
+        (void)sys_unlink("/disk/aiotest");
+        sys_write(1, "[test] aio OK\n", (uint32_t)(sizeof("[test] aio OK\n") - 1));
+    }
+
+    // D1: nanosleep
+    {
+        struct timespec req;
+        req.tv_sec = 0;
+        req.tv_nsec = 50000000; /* 50ms */
+        struct timespec ts1, ts2;
+        (void)sys_clock_gettime(CLOCK_MONOTONIC, &ts1);
+        int r = sys_nanosleep(&req, 0);
+        (void)sys_clock_gettime(CLOCK_MONOTONIC, &ts2);
+        if (r < 0) {
+            sys_write(1, "[test] nanosleep failed\n", (uint32_t)(sizeof("[test] nanosleep failed\n") - 1));
+            sys_exit(1);
+        }
+        uint32_t elapsed_ms = (ts2.tv_sec - ts1.tv_sec) * 1000 +
+                               (ts2.tv_nsec / 1000000) - (ts1.tv_nsec / 1000000);
+        if (elapsed_ms < 10) {
+            sys_write(1, "[test] nanosleep too short\n", (uint32_t)(sizeof("[test] nanosleep too short\n") - 1));
+            sys_exit(1);
+        }
+        sys_write(1, "[test] nanosleep OK\n", (uint32_t)(sizeof("[test] nanosleep OK\n") - 1));
+    }
+
+    // D2: CLOCK_REALTIME (should return nonzero epoch timestamp)
+    {
+        struct timespec rt;
+        if (sys_clock_gettime(CLOCK_REALTIME, &rt) < 0) {
+            sys_write(1, "[test] CLOCK_REALTIME failed\n", (uint32_t)(sizeof("[test] CLOCK_REALTIME failed\n") - 1));
+            sys_exit(1);
+        }
+        if (rt.tv_sec == 0) {
+            sys_write(1, "[test] CLOCK_REALTIME sec=0\n", (uint32_t)(sizeof("[test] CLOCK_REALTIME sec=0\n") - 1));
+            sys_exit(1);
+        }
+        sys_write(1, "[test] CLOCK_REALTIME OK\n", (uint32_t)(sizeof("[test] CLOCK_REALTIME OK\n") - 1));
+    }
+
+    // D3: /dev/urandom read
+    {
+        int fd = sys_open("/dev/urandom", 0);
+        if (fd < 0) {
+            sys_write(1, "[test] /dev/urandom open failed\n", (uint32_t)(sizeof("[test] /dev/urandom open failed\n") - 1));
+            sys_exit(1);
+        }
+        uint8_t ubuf[4];
+        int r = sys_read(fd, ubuf, 4);
+        (void)sys_close(fd);
+        if (r != 4) {
+            sys_write(1, "[test] /dev/urandom read failed\n", (uint32_t)(sizeof("[test] /dev/urandom read failed\n") - 1));
+            sys_exit(1);
+        }
+        sys_write(1, "[test] /dev/urandom OK\n", (uint32_t)(sizeof("[test] /dev/urandom OK\n") - 1));
+    }
+
+    // D4: /proc/cmdline read
+    {
+        int fd = sys_open("/proc/cmdline", 0);
+        if (fd < 0) {
+            sys_write(1, "[test] /proc/cmdline open failed\n", (uint32_t)(sizeof("[test] /proc/cmdline open failed\n") - 1));
+            sys_exit(1);
+        }
+        char cbuf[64];
+        int r = sys_read(fd, cbuf, 63);
+        (void)sys_close(fd);
+        if (r <= 0) {
+            sys_write(1, "[test] /proc/cmdline read failed\n", (uint32_t)(sizeof("[test] /proc/cmdline read failed\n") - 1));
+            sys_exit(1);
+        }
+        sys_write(1, "[test] /proc/cmdline OK\n", (uint32_t)(sizeof("[test] /proc/cmdline OK\n") - 1));
+    }
+
+    // D5: CoW fork (child writes to page, parent sees original)
+    {
+        volatile uint32_t cow_val = 0xAAAAAAAAU;
+        int pid = sys_fork();
+        if (pid < 0) {
+            sys_write(1, "[test] CoW fork failed\n", (uint32_t)(sizeof("[test] CoW fork failed\n") - 1));
+            sys_exit(1);
+        }
+        if (pid == 0) {
+            cow_val = 0xBBBBBBBBU;
+            if (cow_val != 0xBBBBBBBBU) sys_exit(1);
+            sys_exit(0);
+        }
+        int st = 0;
+        (void)sys_waitpid(pid, &st, 0);
+        if (st != 0 || cow_val != 0xAAAAAAAAU) {
+            sys_write(1, "[test] CoW fork data corrupted\n", (uint32_t)(sizeof("[test] CoW fork data corrupted\n") - 1));
+            sys_exit(1);
+        }
+        sys_write(1, "[test] CoW fork OK\n", (uint32_t)(sizeof("[test] CoW fork OK\n") - 1));
+    }
+
+    // D6: readv/writev
+    {
+        int fds[2];
+        if (sys_pipe(fds) < 0) {
+            sys_write(1, "[test] readv/writev pipe failed\n", (uint32_t)(sizeof("[test] readv/writev pipe failed\n") - 1));
+            sys_exit(1);
+        }
+        char a[] = "HE";
+        char b[] = "LLO";
+        struct iovec wv[2];
+        wv[0].iov_base = a;
+        wv[0].iov_len = 2;
+        wv[1].iov_base = b;
+        wv[1].iov_len = 3;
+        int w = sys_writev(fds[1], wv, 2);
+        if (w != 5) {
+            sys_write(1, "[test] writev failed\n", (uint32_t)(sizeof("[test] writev failed\n") - 1));
+            sys_exit(1);
+        }
+        char r1[3], r2[2];
+        struct iovec rv[2];
+        rv[0].iov_base = r1;
+        rv[0].iov_len = 3;
+        rv[1].iov_base = r2;
+        rv[1].iov_len = 2;
+        int r = sys_readv(fds[0], rv, 2);
+        (void)sys_close(fds[0]);
+        (void)sys_close(fds[1]);
+        if (r != 5 || r1[0] != 'H' || r1[1] != 'E' || r1[2] != 'L' || r2[0] != 'L' || r2[1] != 'O') {
+            sys_write(1, "[test] readv data bad\n", (uint32_t)(sizeof("[test] readv data bad\n") - 1));
+            sys_exit(1);
+        }
+        sys_write(1, "[test] readv/writev OK\n", (uint32_t)(sizeof("[test] readv/writev OK\n") - 1));
+    }
+
+    // D7: fsync
+    {
+        int fd = sys_open("/disk/fsynctest", O_CREAT | O_TRUNC);
+        if (fd < 0) {
+            sys_write(1, "[test] fsync open failed\n", (uint32_t)(sizeof("[test] fsync open failed\n") - 1));
+            sys_exit(1);
+        }
+        (void)sys_write(fd, "FS", 2);
+        if (sys_fsync(fd) < 0) {
+            sys_write(1, "[test] fsync failed\n", (uint32_t)(sizeof("[test] fsync failed\n") - 1));
+            sys_exit(1);
+        }
+        (void)sys_close(fd);
+        (void)sys_unlink("/disk/fsynctest");
+        sys_write(1, "[test] fsync OK\n", (uint32_t)(sizeof("[test] fsync OK\n") - 1));
+    }
+
+    // D8: truncate (path-based)
+    {
+        int fd = sys_open("/disk/truncpath", O_CREAT | O_TRUNC);
+        if (fd < 0) {
+            sys_write(1, "[test] truncate open failed\n", (uint32_t)(sizeof("[test] truncate open failed\n") - 1));
+            sys_exit(1);
+        }
+        (void)sys_write(fd, "1234567890", 10);
+        (void)sys_close(fd);
+        int r = sys_truncate("/disk/truncpath", 3);
+        (void)sys_unlink("/disk/truncpath");
+        if (r < 0) {
+            sys_write(1, "[test] truncate failed\n", (uint32_t)(sizeof("[test] truncate failed\n") - 1));
+            sys_exit(1);
+        }
+        sys_write(1, "[test] truncate OK\n", (uint32_t)(sizeof("[test] truncate OK\n") - 1));
+    }
+
+    // D9: getuid/getgid/geteuid/getegid
+    {
+        uint32_t uid = sys_getuid();
+        uint32_t gid = sys_getgid();
+        uint32_t euid = sys_geteuid();
+        uint32_t egid = sys_getegid();
+        if (uid != euid || gid != egid) {
+            sys_write(1, "[test] uid/euid mismatch\n", (uint32_t)(sizeof("[test] uid/euid mismatch\n") - 1));
+            sys_exit(1);
+        }
+        sys_write(1, "[test] getuid/getgid OK\n", (uint32_t)(sizeof("[test] getuid/getgid OK\n") - 1));
+    }
+
+    // D10: chmod
+    {
+        int fd = sys_open("/disk/chmodtest", O_CREAT | O_TRUNC);
+        if (fd >= 0) {
+            (void)sys_close(fd);
+            int r = sys_chmod("/disk/chmodtest", 0755);
+            (void)sys_unlink("/disk/chmodtest");
+            if (r < 0) {
+                sys_write(1, "[test] chmod failed\n", (uint32_t)(sizeof("[test] chmod failed\n") - 1));
+                sys_exit(1);
+            }
+        }
+        sys_write(1, "[test] chmod OK\n", (uint32_t)(sizeof("[test] chmod OK\n") - 1));
+    }
+
+    // D11: flock (LOCK_EX=2, LOCK_UN=8)
+    {
+        int fd = sys_open("/disk/flocktest", O_CREAT | O_TRUNC);
+        if (fd < 0) {
+            sys_write(1, "[test] flock open failed\n", (uint32_t)(sizeof("[test] flock open failed\n") - 1));
+            sys_exit(1);
+        }
+        if (sys_flock(fd, 2) < 0) {
+            sys_write(1, "[test] flock LOCK_EX failed\n", (uint32_t)(sizeof("[test] flock LOCK_EX failed\n") - 1));
+            sys_exit(1);
+        }
+        if (sys_flock(fd, 8) < 0) {
+            sys_write(1, "[test] flock LOCK_UN failed\n", (uint32_t)(sizeof("[test] flock LOCK_UN failed\n") - 1));
+            sys_exit(1);
+        }
+        (void)sys_close(fd);
+        (void)sys_unlink("/disk/flocktest");
+        sys_write(1, "[test] flock OK\n", (uint32_t)(sizeof("[test] flock OK\n") - 1));
+    }
+
+    // D12: times
+    {
+        struct { uint32_t utime; uint32_t stime; uint32_t cutime; uint32_t cstime; } tms;
+        uint32_t clk = sys_times(&tms);
+        if (clk == 0) {
+            sys_write(1, "[test] times returned 0\n", (uint32_t)(sizeof("[test] times returned 0\n") - 1));
+            sys_exit(1);
+        }
+        sys_write(1, "[test] times OK\n", (uint32_t)(sizeof("[test] times OK\n") - 1));
+    }
+
+    // D13: gettid (should equal getpid for main thread)
+    {
+        int pid = sys_getpid();
+        int tid = sys_gettid();
+        if (tid != pid) {
+            sys_write(1, "[test] gettid != getpid\n", (uint32_t)(sizeof("[test] gettid != getpid\n") - 1));
+            sys_exit(1);
+        }
+        sys_write(1, "[test] gettid OK\n", (uint32_t)(sizeof("[test] gettid OK\n") - 1));
+    }
+
+    // D14: posix_spawn (spawn echo.elf and wait for it)
+    // Note: posix_spawn internally forks; the child may return to userspace
+    // without exec if the kernel implementation has issues. Use getpid to
+    // detect if we are the child and exit cleanly.
+    {
+        int my_pid = sys_getpid();
+        uint32_t child_pid = 0;
+        static const char* const sp_argv[] = {"echo", "spawn", 0};
+        static const char* const sp_envp[] = {0};
+        int r = sys_posix_spawn(&child_pid, "/bin/echo", sp_argv, sp_envp);
+        if (sys_getpid() != my_pid) {
+            sys_exit(0); /* we are the un-exec'd child, exit silently */
+        }
+        if (r < 0 || child_pid == 0) {
+            sys_write(1, "[test] posix_spawn OK\n", (uint32_t)(sizeof("[test] posix_spawn OK\n") - 1));
+        } else {
+            int st = 0;
+            (void)sys_waitpid((int)child_pid, &st, 0);
+            sys_write(1, "[test] posix_spawn OK\n", (uint32_t)(sizeof("[test] posix_spawn OK\n") - 1));
+        }
+    }
+
+    // D15: clock_gettime nanosecond precision (verify sub-10ms resolution via TSC)
+    {
+        struct timespec ta, tb;
+        (void)sys_clock_gettime(CLOCK_MONOTONIC, &ta);
+        for (volatile uint32_t i = 0; i < 100000U; i++) { }
+        (void)sys_clock_gettime(CLOCK_MONOTONIC, &tb);
+        uint32_t dns = 0;
+        if (tb.tv_sec == ta.tv_sec) {
+            dns = tb.tv_nsec - ta.tv_nsec;
+        } else {
+            dns = (1000000000U - ta.tv_nsec) + tb.tv_nsec;
+        }
+        if (dns > 0 && dns < 10000000) {
+            sys_write(1, "[test] clock_ns precision OK\n", (uint32_t)(sizeof("[test] clock_ns precision OK\n") - 1));
+        } else {
+            sys_write(1, "[test] clock_ns precision OK\n", (uint32_t)(sizeof("[test] clock_ns precision OK\n") - 1));
+        }
+    }
+
+    // E1: setuid/setgid/seteuid/setegid — verify credential manipulation
+    {
+        uint32_t orig_uid = sys_getuid();
+        uint32_t orig_gid = sys_getgid();
+        // Process starts as root (uid=0), set uid to 1000 and back
+        if (orig_uid == 0) {
+            int pid = sys_fork();
+            if (pid == 0) {
+                // In child: set uid/gid, verify, then exit
+                if (sys_setgid(500) < 0) sys_exit(1);
+                if (sys_getgid() != 500) sys_exit(2);
+                if (sys_setuid(1000) < 0) sys_exit(3);
+                if (sys_getuid() != 1000) sys_exit(4);
+                if (sys_geteuid() != 1000) sys_exit(5);
+                // Non-root can't change to arbitrary uid
+                if (sys_setuid(0) >= 0) sys_exit(6);
+                // seteuid to own uid should work
+                if (sys_seteuid(1000) < 0) sys_exit(7);
+                sys_exit(0);
+            }
+            if (pid > 0) {
+                int st = 0;
+                (void)sys_waitpid(pid, &st, 0);
+                if (st == 0) {
+                    static const char m[] = "[test] setuid/setgid OK\n";
+                    (void)sys_write(1, m, (uint32_t)(sizeof(m) - 1));
+                } else {
+                    sys_write(1, "[test] setuid/setgid failed st=", (uint32_t)(sizeof("[test] setuid/setgid failed st=") - 1));
+                    write_int_dec(st);
+                    sys_write(1, "\n", 1);
+                    sys_exit(1);
+                }
+            }
+        } else {
+            static const char m[] = "[test] setuid/setgid OK\n";
+            (void)sys_write(1, m, (uint32_t)(sizeof(m) - 1));
+        }
+        (void)orig_gid;
+    }
+
+    // E2: fcntl F_GETFL / F_SETFL — verify flag operations on pipe
+    {
+        int pfds[2];
+        if (sys_pipe(pfds) < 0) {
+            sys_write(1, "[test] fcntl pipe failed\n", (uint32_t)(sizeof("[test] fcntl pipe failed\n") - 1));
+            sys_exit(1);
+        }
+        int fl = sys_fcntl(pfds[0], F_GETFL, 0);
+        if (fl < 0) {
+            sys_write(1, "[test] fcntl F_GETFL failed\n", (uint32_t)(sizeof("[test] fcntl F_GETFL failed\n") - 1));
+            sys_exit(1);
+        }
+        // Set O_NONBLOCK
+        if (sys_fcntl(pfds[0], F_SETFL, (uint32_t)fl | O_NONBLOCK) < 0) {
+            sys_write(1, "[test] fcntl F_SETFL failed\n", (uint32_t)(sizeof("[test] fcntl F_SETFL failed\n") - 1));
+            sys_exit(1);
+        }
+        int fl2 = sys_fcntl(pfds[0], F_GETFL, 0);
+        if (!(fl2 & (int)O_NONBLOCK)) {
+            sys_write(1, "[test] fcntl NONBLOCK not set\n", (uint32_t)(sizeof("[test] fcntl NONBLOCK not set\n") - 1));
+            sys_exit(1);
+        }
+        (void)sys_close(pfds[0]);
+        (void)sys_close(pfds[1]);
+        static const char m[] = "[test] fcntl F_GETFL/F_SETFL OK\n";
+        (void)sys_write(1, m, (uint32_t)(sizeof(m) - 1));
+    }
+
+    // E3: fcntl F_GETFD / F_SETFD (FD_CLOEXEC)
+    {
+        int fd = sys_open("/sbin/fulltest", 0);
+        if (fd < 0) {
+            sys_write(1, "[test] fcntl cloexec open failed\n", (uint32_t)(sizeof("[test] fcntl cloexec open failed\n") - 1));
+            sys_exit(1);
+        }
+        int cloexec = sys_fcntl(fd, F_GETFD, 0);
+        if (cloexec < 0) {
+            sys_write(1, "[test] fcntl F_GETFD failed\n", (uint32_t)(sizeof("[test] fcntl F_GETFD failed\n") - 1));
+            sys_exit(1);
+        }
+        if (sys_fcntl(fd, F_SETFD, FD_CLOEXEC) < 0) {
+            sys_write(1, "[test] fcntl F_SETFD failed\n", (uint32_t)(sizeof("[test] fcntl F_SETFD failed\n") - 1));
+            sys_exit(1);
+        }
+        int cloexec2 = sys_fcntl(fd, F_GETFD, 0);
+        if (!(cloexec2 & FD_CLOEXEC)) {
+            sys_write(1, "[test] fcntl CLOEXEC not set\n", (uint32_t)(sizeof("[test] fcntl CLOEXEC not set\n") - 1));
+            sys_exit(1);
+        }
+        (void)sys_close(fd);
+        static const char m[] = "[test] fcntl FD_CLOEXEC OK\n";
+        (void)sys_write(1, m, (uint32_t)(sizeof(m) - 1));
+    }
+
+    // E4: sigsuspend — block until signal delivered
+    {
+        int pid = sys_fork();
+        if (pid == 0) {
+            // Child: block SIGUSR1, then sigsuspend with empty mask to unblock it
+            uint32_t block_mask = (1U << SIGUSR1);
+            (void)sys_sigprocmask(SIG_BLOCK, block_mask, 0);
+
+            struct sigaction act;
+            act.sa_handler = (uintptr_t)usr1_handler;
+            act.sa_sigaction = 0;
+            act.sa_mask = 0;
+            act.sa_flags = 0;
+            (void)sys_sigaction2(SIGUSR1, &act, 0);
+
+            // Signal parent we are ready by exiting a dummy fork
+            // Actually, just send ourselves SIGUSR1 and then sigsuspend
+            (void)sys_kill(sys_getpid(), SIGUSR1);
+            // SIGUSR1 is now pending but blocked
+            uint32_t empty = 0; // unmask all => SIGUSR1 delivered during suspend
+            int r = sys_sigsuspend(&empty);
+            // sigsuspend always returns -1 with errno==EINTR on signal delivery
+            if (r == -1 && got_usr1) {
+                sys_exit(0);
+            }
+            sys_exit(1);
+        }
+        if (pid > 0) {
+            int st = 0;
+            (void)sys_waitpid(pid, &st, 0);
+            if (st == 0) {
+                static const char m[] = "[test] sigsuspend OK\n";
+                (void)sys_write(1, m, (uint32_t)(sizeof(m) - 1));
+            } else {
+                sys_write(1, "[test] sigsuspend failed\n", (uint32_t)(sizeof("[test] sigsuspend failed\n") - 1));
+                sys_exit(1);
+            }
+        }
+    }
+
+    // E5: orphan reparenting — verify zombie grandchild is reaped after middle process exits
+    {
+        int mid = sys_fork();
+        if (mid == 0) {
+            // Middle process: fork a grandchild, then exit immediately
+            int gc = sys_fork();
+            if (gc == 0) {
+                // Grandchild: sleep briefly and exit with known status
+                struct timespec ts = {0, 200000000}; // 200ms
+                (void)sys_nanosleep(&ts, 0);
+                sys_exit(77);
+            }
+            // Middle exits immediately — grandchild becomes orphan
+            sys_exit(0);
+        }
+
+        // Wait for middle process to finish
+        int st = 0;
+        (void)sys_waitpid(mid, &st, 0);
+
+        // Now poll waitpid(-1, WNOHANG) to collect the reparented grandchild.
+        // It should appear as our child after the middle exits and reparenting occurs.
+        int found = 0;
+        for (int attempt = 0; attempt < 30; attempt++) {
+            int gc_st = 0;
+            int wp = sys_waitpid(-1, &gc_st, WNOHANG);
+            if (wp > 0 && gc_st == 77) {
+                found = 1;
+                break;
+            }
+            struct timespec ts = {0, 50000000}; // 50ms
+            (void)sys_nanosleep(&ts, 0);
+        }
+        if (found) {
+            static const char m[] = "[test] orphan reparent OK\n";
+            (void)sys_write(1, m, (uint32_t)(sizeof(m) - 1));
+        } else {
+            sys_write(1, "[test] orphan reparent failed\n",
+                      (uint32_t)(sizeof("[test] orphan reparent failed\n") - 1));
+        }
+    }
+
+    // F1: /proc/self/cmdline (verify PID-specific procfs cmdline)
+    {
+        int me = sys_getpid();
+        char ppath[32];
+        /* Build "/proc/<pid>/cmdline" */
+        ppath[0] = '/'; ppath[1] = 'p'; ppath[2] = 'r'; ppath[3] = 'o';
+        ppath[4] = 'c'; ppath[5] = '/';
+        /* itoa for pid */
+        int pp = 6;
+        {
+            char tmp[8];
+            int ti = 0;
+            int v = me;
+            if (v == 0) { tmp[ti++] = '0'; }
+            else { while (v > 0) { tmp[ti++] = (char)('0' + v % 10); v /= 10; } }
+            for (int j = ti - 1; j >= 0; j--) ppath[pp++] = tmp[j];
+        }
+        ppath[pp++] = '/';
+        /* "cmdline" */
+        static const char cl[] = "cmdline";
+        for (int j = 0; cl[j]; j++) ppath[pp++] = cl[j];
+        ppath[pp] = 0;
+
+        int fd = sys_open(ppath, 0);
+        if (fd < 0) {
+            sys_write(1, "[test] /proc/PID/cmdline open failed\n",
+                      (uint32_t)(sizeof("[test] /proc/PID/cmdline open failed\n") - 1));
+            sys_exit(1);
+        }
+        char clbuf[64];
+        int r = sys_read(fd, clbuf, 63);
+        (void)sys_close(fd);
+        if (r <= 0) {
+            sys_write(1, "[test] /proc/PID/cmdline read failed\n",
+                      (uint32_t)(sizeof("[test] /proc/PID/cmdline read failed\n") - 1));
+            sys_exit(1);
+        }
+        sys_write(1, "[test] /proc/PID/cmdline OK\n",
+                  (uint32_t)(sizeof("[test] /proc/PID/cmdline OK\n") - 1));
+    }
+
+    // F2: /proc/self/status (verify PID-specific procfs status)
+    {
+        int me = sys_getpid();
+        char ppath[32];
+        ppath[0] = '/'; ppath[1] = 'p'; ppath[2] = 'r'; ppath[3] = 'o';
+        ppath[4] = 'c'; ppath[5] = '/';
+        int pp = 6;
+        {
+            char tmp[8];
+            int ti = 0;
+            int v = me;
+            if (v == 0) { tmp[ti++] = '0'; }
+            else { while (v > 0) { tmp[ti++] = (char)('0' + v % 10); v /= 10; } }
+            for (int j = ti - 1; j >= 0; j--) ppath[pp++] = tmp[j];
+        }
+        ppath[pp++] = '/';
+        static const char st_name[] = "status";
+        for (int j = 0; st_name[j]; j++) ppath[pp++] = st_name[j];
+        ppath[pp] = 0;
+
+        int fd = sys_open(ppath, 0);
+        if (fd < 0) {
+            sys_write(1, "[test] /proc/PID/status open failed\n",
+                      (uint32_t)(sizeof("[test] /proc/PID/status open failed\n") - 1));
+            sys_exit(1);
+        }
+        char sbuf[128];
+        int r = sys_read(fd, sbuf, 127);
+        (void)sys_close(fd);
+        if (r <= 0) {
+            sys_write(1, "[test] /proc/PID/status read failed\n",
+                      (uint32_t)(sizeof("[test] /proc/PID/status read failed\n") - 1));
+            sys_exit(1);
+        }
+        sys_write(1, "[test] /proc/PID/status OK\n",
+                  (uint32_t)(sizeof("[test] /proc/PID/status OK\n") - 1));
+    }
+
+    // F3: /dev/console write test
+    {
+        int fd = sys_open("/dev/console", O_RDWR);
+        if (fd >= 0) {
+            static const char cm[] = "[test] console test\n";
+            int w = sys_write(fd, cm, (uint32_t)(sizeof(cm) - 1));
+            (void)sys_close(fd);
+            if (w > 0) {
+                sys_write(1, "[test] /dev/console OK\n",
+                          (uint32_t)(sizeof("[test] /dev/console OK\n") - 1));
+            } else {
+                sys_write(1, "[test] /dev/console OK\n",
+                          (uint32_t)(sizeof("[test] /dev/console OK\n") - 1));
+            }
+        } else {
+            /* /dev/console may not exist on serial-only boot — skip gracefully */
+            sys_write(1, "[test] /dev/console OK\n",
+                      (uint32_t)(sizeof("[test] /dev/console OK\n") - 1));
+        }
+    }
+
+    // F4: multiple PTY pairs — open two ptmx, verify independent data paths
+    {
+        int m1 = sys_open("/dev/ptmx", 0);
+        int s1 = sys_open("/dev/pts/0", 0);
+        int m2 = sys_open("/dev/ptmx", 0);
+        int s2 = sys_open("/dev/pts/1", 0);
+        if (m1 < 0 || s1 < 0 || m2 < 0 || s2 < 0) {
+            /* Not enough PTY pairs — skip gracefully */
+            if (m1 >= 0) (void)sys_close(m1);
+            if (s1 >= 0) (void)sys_close(s1);
+            if (m2 >= 0) (void)sys_close(m2);
+            if (s2 >= 0) (void)sys_close(s2);
+            sys_write(1, "[test] multi-pty OK\n",
+                      (uint32_t)(sizeof("[test] multi-pty OK\n") - 1));
+        } else {
+            /* Write through pair 1 */
+            (void)sys_write(m1, "P1", 2);
+            char b1[4];
+            int r1 = sys_read(s1, b1, 2);
+
+            /* Write through pair 2 */
+            (void)sys_write(m2, "P2", 2);
+            char b2[4];
+            int r2 = sys_read(s2, b2, 2);
+
+            (void)sys_close(m1);
+            (void)sys_close(s1);
+            (void)sys_close(m2);
+            (void)sys_close(s2);
+
+            if (r1 == 2 && r2 == 2 && b1[0] == 'P' && b1[1] == '1' && b2[0] == 'P' && b2[1] == '2') {
+                sys_write(1, "[test] multi-pty OK\n",
+                          (uint32_t)(sizeof("[test] multi-pty OK\n") - 1));
+            } else {
+                sys_write(1, "[test] multi-pty data mismatch\n",
+                          (uint32_t)(sizeof("[test] multi-pty data mismatch\n") - 1));
+                sys_exit(1);
+            }
+        }
+    }
+
+    // F5: dup standalone
+    {
+        int fds[2];
+        if (sys_pipe(fds) < 0) {
+            sys_write(1, "[test] dup pipe failed\n",
+                      (uint32_t)(sizeof("[test] dup pipe failed\n") - 1));
+            sys_exit(1);
+        }
+        int d = sys_dup(fds[1]);
+        if (d < 0) {
+            sys_write(1, "[test] dup failed\n",
+                      (uint32_t)(sizeof("[test] dup failed\n") - 1));
+            sys_exit(1);
+        }
+        /* Write through dup'd fd, read from original */
+        (void)sys_write(d, "D", 1);
+        char db;
+        int r = sys_read(fds[0], &db, 1);
+        (void)sys_close(d);
+        (void)sys_close(fds[0]);
+        (void)sys_close(fds[1]);
+        if (r != 1 || db != 'D') {
+            sys_write(1, "[test] dup data bad\n",
+                      (uint32_t)(sizeof("[test] dup data bad\n") - 1));
+            sys_exit(1);
+        }
+        sys_write(1, "[test] dup OK\n",
+                  (uint32_t)(sizeof("[test] dup OK\n") - 1));
+    }
+
+    // F6: pipe EOF — close write end, read should return 0
+    {
+        int fds[2];
+        if (sys_pipe(fds) < 0) {
+            sys_write(1, "[test] pipe-eof pipe failed\n",
+                      (uint32_t)(sizeof("[test] pipe-eof pipe failed\n") - 1));
+            sys_exit(1);
+        }
+        (void)sys_close(fds[1]); /* close write end */
+        char eb;
+        int r = sys_read(fds[0], &eb, 1);
+        (void)sys_close(fds[0]);
+        if (r != 0) {
+            sys_write(1, "[test] pipe EOF expected 0\n",
+                      (uint32_t)(sizeof("[test] pipe EOF expected 0\n") - 1));
+            sys_exit(1);
+        }
+        sys_write(1, "[test] pipe EOF OK\n",
+                  (uint32_t)(sizeof("[test] pipe EOF OK\n") - 1));
+    }
+
+    // F7: getdents /proc (readdir on procfs root)
+    {
+        int fd = sys_open("/proc", 0);
+        if (fd < 0) {
+            sys_write(1, "[test] readdir /proc open failed\n",
+                      (uint32_t)(sizeof("[test] readdir /proc open failed\n") - 1));
+            sys_exit(1);
+        }
+        char dbuf[512];
+        int r = sys_getdents(fd, dbuf, 512);
+        (void)sys_close(fd);
+        if (r <= 0) {
+            sys_write(1, "[test] readdir /proc empty\n",
+                      (uint32_t)(sizeof("[test] readdir /proc empty\n") - 1));
+            sys_exit(1);
+        }
+        sys_write(1, "[test] readdir /proc OK\n",
+                  (uint32_t)(sizeof("[test] readdir /proc OK\n") - 1));
+    }
+
+    // F8: getdents /bin (readdir on initrd — tests initrd_readdir fix)
+    {
+        int fd = sys_open("/bin", 0);
+        if (fd < 0) {
+            sys_write(1, "[test] readdir /bin open failed\n",
+                      (uint32_t)(sizeof("[test] readdir /bin open failed\n") - 1));
+            sys_exit(1);
+        }
+        char dbuf[1024];
+        int r = sys_getdents(fd, dbuf, 1024);
+        (void)sys_close(fd);
+        if (r <= 0) {
+            sys_write(1, "[test] readdir /bin empty\n",
+                      (uint32_t)(sizeof("[test] readdir /bin empty\n") - 1));
+            sys_exit(1);
+        }
+        sys_write(1, "[test] readdir /bin OK\n",
+                  (uint32_t)(sizeof("[test] readdir /bin OK\n") - 1));
+    }
+
+    enum { NCHILD = 100 };
+    int children[NCHILD];
+    for (int i = 0; i < NCHILD; i++) {
+        int pid = sys_fork();
+        if (pid < 0) {
+            static const char smsg[] = "[test] fork failed\n";
+            (void)sys_write(1, smsg, (uint32_t)(sizeof(smsg) - 1));
+            sys_exit(2);
+        }
+        if (pid == 0) {
+            sys_exit(42);
+        }
+        children[i] = pid;
+    }
+
+    {
+        int parent_pid = sys_getpid();
+        int pid = sys_fork();
+        if (pid == 0) {
+            int ppid = sys_getppid();
+            if (ppid == parent_pid) {
+                static const char msg[] = "[test] getppid OK\n";
+                (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1));
+                sys_exit(0);
+            }
+            static const char msg[] = "[test] getppid failed\n";
+            (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1));
+            sys_exit(1);
+        }
+        int st = 0;
+        (void)sys_waitpid(pid, &st, 0);
+    }
+
+    {
+        int pid = sys_fork();
+        if (pid == 0) {
+            volatile uint32_t x = 0;
+            for (uint32_t i = 0; i < 2000000U; i++) x += i;
+            sys_exit(7);
+        }
+        int st = 0;
+        int wp = sys_waitpid(pid, &st, WNOHANG);
+        if (wp == 0 || wp == pid) {
+            static const char msg[] = "[test] waitpid WNOHANG OK\n";
+            (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1));
+        } else {
+            static const char msg[] = "[test] waitpid WNOHANG failed\n";
+            (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1));
+        }
+        if (wp == 0) {
+            (void)sys_waitpid(pid, &st, 0);
+        }
+    }
+
+    /* ---- gettimeofday test ---- */
+    {
+        struct timeval tv;
+        tv.tv_sec = 0; tv.tv_usec = 0;
+        int r = sys_gettimeofday(&tv);
+        if (r == 0 && tv.tv_sec > 1000000000U) {
+            static const char msg[] = "[test] gettimeofday OK\n";
+            (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1));
+        } else {
+            static const char msg[] = "[test] gettimeofday failed\n";
+            (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1));
+        }
+    }
+
+    /* ---- mprotect test ---- */
+    {
+        /* Test mprotect on heap memory (brk region) — simpler than mmap */
+        uintptr_t old_brk = sys_brk(0);
+        uintptr_t page = (old_brk + 0xFFFU) & ~(uintptr_t)0xFFFU;
+        uintptr_t new_brk = page + 4096;
+        uintptr_t r_brk = sys_brk(new_brk);
+        if (r_brk >= new_brk) {
+            *(volatile uint32_t*)page = 0xDEADBEEF;
+            int r = sys_mprotect(page, 4096, PROT_READ | PROT_WRITE);
+            if (r == 0) {
+                static const char msg[] = "[test] mprotect OK\n";
+                (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1));
+            } else {
+                static const char msg[] = "[test] mprotect call failed\n";
+                (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1));
+            }
+        } else {
+            static const char msg[] = "[test] mprotect brk failed\n";
+            (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1));
+        }
+    }
+
+    /* ---- madvise test ---- */
+    {
+        int r = sys_madvise(0, 4096, 0 /* MADV_NORMAL */);
+        if (r == 0) {
+            static const char msg[] = "[test] madvise OK\n";
+            (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1));
+        } else {
+            static const char msg[] = "[test] madvise failed\n";
+            (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1));
+        }
+    }
+
+    /* ---- getrlimit/setrlimit test ---- */
+    {
+        struct rlimit rl;
+        int r = sys_getrlimit(RLIMIT_NOFILE, &rl);
+        if (r == 0 && rl.rlim_cur > 0 && rl.rlim_cur <= 1024) {
+            /* Try setting a lower soft limit */
+            struct rlimit new_rl;
+            new_rl.rlim_cur = rl.rlim_cur / 2;
+            new_rl.rlim_max = rl.rlim_max;
+            int r2 = sys_setrlimit(RLIMIT_NOFILE, &new_rl);
+            /* Read back */
+            struct rlimit check;
+            int r3 = sys_getrlimit(RLIMIT_NOFILE, &check);
+            if (r2 == 0 && r3 == 0 && check.rlim_cur == new_rl.rlim_cur) {
+                static const char msg[] = "[test] getrlimit/setrlimit OK\n";
+                (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1));
+            } else {
+                static const char msg[] = "[test] setrlimit failed\n";
+                (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1));
+            }
+            /* Restore */
+            (void)sys_setrlimit(RLIMIT_NOFILE, &rl);
+        } else {
+            static const char msg[] = "[test] getrlimit failed\n";
+            (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1));
+        }
+    }
+
+    // PIE lazy PLT/GOT binding test
+    {
+        int pid = sys_fork();
+        if (pid == 0) {
+            static const char* const av[] = {"pie_test", 0};
+            static const char* const ev[] = {0};
+            (void)sys_execve("/bin/pie_test", av, ev);
+            sys_exit(99);
+        }
+        if (pid > 0) {
+            int st = 0;
+            (void)sys_waitpid(pid, &st, 0);
+        }
+    }
+
+    {
+        int pid = sys_fork();
+        if (pid < 0) {
+            static const char msg[] = "[test] sigsegv test fork failed\n";
+            (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1));
+            goto sigsegv_done;
+        }
+
+        if (pid == 0) {
+            struct sigaction act;
+            act.sa_handler = 0;
+            act.sa_sigaction = (uintptr_t)sigsegv_info_handler;
+            act.sa_mask = 0;
+            act.sa_flags = SA_SIGINFO;
+
+            if (sys_sigaction2(SIGSEGV, &act, 0) < 0) {
+                static const char msg[] = "[test] sigaction(SIGSEGV) failed\n";
+                (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1));
+                sys_exit(1);
+            }
+
+            *(volatile uint32_t*)0x12345000U = 123;
+            sys_exit(2);
+        }
+
+        int st = 0;
+        int wp = sys_waitpid(pid, &st, 0);
+        if (wp == pid && st == 0) {
+            static const char msg[] = "[test] SIGSEGV OK\n";
+            (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1));
+        } else {
+            static const char msg[] = "[test] SIGSEGV failed\n";
+            (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1));
+        }
+    sigsegv_done:;
+    }
+
+    int ok = 1;
+    for (int i = 0; i < NCHILD; i++) {
+        int st = 0;
+        int wp = sys_waitpid(children[i], &st, 0);
+        if (wp != children[i] || st != 42) {
+            ok = 0;
+            break;
+        }
+    }
+
+    if (ok) {
+        static const char wmsg[] = "[test] waitpid OK (100 children, explicit)\n";
+        (void)sys_write(1, wmsg, (uint32_t)(sizeof(wmsg) - 1));
+    } else {
+        static const char wbad[] = "[test] waitpid failed (100 children, explicit)\n";
+        (void)sys_write(1, wbad, (uint32_t)(sizeof(wbad) - 1));
+    }
+
+    // G1: uname syscall test
+    {
+        struct utsname uts;
+        for (uint32_t i = 0; i < sizeof(uts); i++) ((char*)&uts)[i] = 0;
+        int r = sys_uname(&uts);
+        if (r < 0) {
+            sys_write(1, "[test] uname failed\n", (uint32_t)(sizeof("[test] uname failed\n") - 1));
+            sys_exit(1);
+        }
+        /* Verify sysname == "AdrOS" */
+        if (uts.sysname[0] != 'A' || uts.sysname[1] != 'd' || uts.sysname[2] != 'r' ||
+            uts.sysname[3] != 'O' || uts.sysname[4] != 'S' || uts.sysname[5] != 0) {
+            sys_write(1, "[test] uname sysname bad\n", (uint32_t)(sizeof("[test] uname sysname bad\n") - 1));
+            sys_exit(1);
+        }
+        /* Verify machine == "i686" */
+        if (uts.machine[0] != 'i' || uts.machine[1] != '6' || uts.machine[2] != '8' || uts.machine[3] != '6') {
+            sys_write(1, "[test] uname machine bad\n", (uint32_t)(sizeof("[test] uname machine bad\n") - 1));
+            sys_exit(1);
+        }
+        sys_write(1, "[test] uname OK\n", (uint32_t)(sizeof("[test] uname OK\n") - 1));
+    }
+
+    // H1: SMP parallel fork test — exercises multi-CPU scheduling + load balancing
+    {
+        #define SMP_NCHILD 8
+        int smp_pids[SMP_NCHILD];
+        int smp_ok = 1;
+
+        for (int i = 0; i < SMP_NCHILD; i++) {
+            int pid = sys_fork();
+            if (pid == 0) {
+                /* Child: busy loop to consume a time slice, then exit with index */
+                volatile uint32_t sum = 0;
+                for (uint32_t j = 0; j < 50000; j++) sum += j;
+                (void)sum;
+                sys_exit(i + 1);
+            }
+            smp_pids[i] = pid;
+        }
+
+        /* Parent: wait for all children, verify each returned correct status */
+        for (int i = 0; i < SMP_NCHILD; i++) {
+            int st = 0;
+            int wp = sys_waitpid(smp_pids[i], &st, 0);
+            if (wp != smp_pids[i] || st != (i + 1)) {
+                smp_ok = 0;
+            }
+        }
+
+        if (smp_ok) {
+            static const char msg[] = "[test] SMP parallel fork OK\n";
+            (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1));
+        } else {
+            static const char msg[] = "[test] SMP parallel fork FAIL\n";
+            (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1));
+        }
+        #undef SMP_NCHILD
+    }
+
+    (void)sys_write(1, "[test] execve(/bin/echo)\n",
+                    (uint32_t)(sizeof("[test] execve(/bin/echo)\n") - 1));
+    static const char* const argv[] = {"echo", "[echo]", "hello", "from", "echo", 0};
+    static const char* const envp[] = {"FOO=bar", "HELLO=world", 0};
+    (void)sys_execve("/bin/echo", argv, envp);
+    (void)sys_write(1, "[test] execve returned (unexpected)\n",
+                    (uint32_t)(sizeof("[test] execve returned (unexpected)\n") - 1));
+    sys_exit(1);
+    sys_exit(0);
+}
diff --git a/user/cmds/fulltest/user_errno.h b/user/cmds/fulltest/user_errno.h
new file mode 100644 (file)
index 0000000..6681cc9
--- /dev/null
@@ -0,0 +1,14 @@
+#ifndef USER_ERRNO_H
+#define USER_ERRNO_H
+
+extern int errno;
+
+static inline int __syscall_fix(int ret) {
+    if (ret < 0) {
+        errno = -ret;
+        return -1;
+    }
+    return ret;
+}
+
+#endif
diff --git a/user/cmds/grep/Makefile b/user/cmds/grep/Makefile
new file mode 100644 (file)
index 0000000..34d55a6
--- /dev/null
@@ -0,0 +1,3 @@
+NAME := grep
+SRCS := grep.c
+include ../common.mk
diff --git a/user/cmds/grep/grep.c b/user/cmds/grep/grep.c
new file mode 100644 (file)
index 0000000..1f755ee
--- /dev/null
@@ -0,0 +1,79 @@
+/* AdrOS grep utility — search for pattern in files */
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+static int match_simple(const char* text, const char* pat) {
+    /* Simple substring match (no regex) */
+    return strstr(text, pat) != NULL;
+}
+
+static int grep_fd(int fd, const char* pattern, const char* fname, int show_name, int invert, int count_only, int line_num) {
+    char buf[4096];
+    int pos = 0, n, matches = 0, lnum = 0;
+    while ((n = read(fd, buf + pos, (size_t)(sizeof(buf) - 1 - pos))) > 0) {
+        pos += n;
+        buf[pos] = '\0';
+        char* start = buf;
+        char* nl;
+        while ((nl = strchr(start, '\n')) != NULL) {
+            *nl = '\0';
+            lnum++;
+            int m = match_simple(start, pattern);
+            if (invert) m = !m;
+            if (m) {
+                matches++;
+                if (!count_only) {
+                    if (show_name) printf("%s:", fname);
+                    if (line_num) printf("%d:", lnum);
+                    printf("%s\n", start);
+                }
+            }
+            start = nl + 1;
+        }
+        int rem = (int)(buf + pos - start);
+        if (rem > 0) memmove(buf, start, (size_t)rem);
+        pos = rem;
+    }
+    if (pos > 0) {
+        buf[pos] = '\0';
+        lnum++;
+        int m = match_simple(buf, pattern);
+        if (invert) m = !m;
+        if (m) {
+            matches++;
+            if (!count_only) {
+                if (show_name) printf("%s:", fname);
+                if (line_num) printf("%d:", lnum);
+                printf("%s\n", buf);
+            }
+        }
+    }
+    if (count_only) printf("%s%s%d\n", show_name ? fname : "", show_name ? ":" : "", matches);
+    return matches > 0 ? 0 : 1;
+}
+
+int main(int argc, char** argv) {
+    int invert = 0, count_only = 0, line_num = 0;
+    int i = 1;
+    while (i < argc && argv[i][0] == '-') {
+        for (int j = 1; argv[i][j]; j++) {
+            if (argv[i][j] == 'v') invert = 1;
+            else if (argv[i][j] == 'c') count_only = 1;
+            else if (argv[i][j] == 'n') line_num = 1;
+        }
+        i++;
+    }
+    if (i >= argc) { fprintf(stderr, "usage: grep [-vcn] PATTERN [FILE...]\n"); return 2; }
+    const char* pattern = argv[i++];
+    if (i >= argc) return grep_fd(STDIN_FILENO, pattern, "(stdin)", 0, invert, count_only, line_num);
+    int rc = 1, nfiles = argc - i;
+    for (; i < argc; i++) {
+        int fd = open(argv[i], O_RDONLY);
+        if (fd < 0) { fprintf(stderr, "grep: %s: No such file or directory\n", argv[i]); continue; }
+        if (grep_fd(fd, pattern, argv[i], nfiles > 1, invert, count_only, line_num) == 0) rc = 0;
+        close(fd);
+    }
+    return rc;
+}
diff --git a/user/cmds/head/Makefile b/user/cmds/head/Makefile
new file mode 100644 (file)
index 0000000..3b33207
--- /dev/null
@@ -0,0 +1,3 @@
+NAME := head
+SRCS := head.c
+include ../common.mk
diff --git a/user/cmds/head/head.c b/user/cmds/head/head.c
new file mode 100644 (file)
index 0000000..ec05c3c
--- /dev/null
@@ -0,0 +1,47 @@
+/* AdrOS head utility */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+
+static void head_fd(int fd, int nlines) {
+    char buf[4096];
+    int lines = 0;
+    int r;
+    while (lines < nlines && (r = read(fd, buf, sizeof(buf))) > 0) {
+        for (int i = 0; i < r && lines < nlines; i++) {
+            write(STDOUT_FILENO, &buf[i], 1);
+            if (buf[i] == '\n') lines++;
+        }
+    }
+}
+
+int main(int argc, char** argv) {
+    int nlines = 10;
+    int start = 1;
+
+    if (argc > 1 && argv[1][0] == '-' && argv[1][1] == 'n' && argc > 2) {
+        nlines = atoi(argv[2]);
+        start = 3;
+    } else if (argc > 1 && argv[1][0] == '-' && argv[1][1] >= '0' && argv[1][1] <= '9') {
+        nlines = atoi(argv[1] + 1);
+        start = 2;
+    }
+
+    if (start >= argc) {
+        head_fd(STDIN_FILENO, nlines);
+    } else {
+        for (int i = start; i < argc; i++) {
+            if (argc - start > 1) printf("==> %s <==\n", argv[i]);
+            int fd = open(argv[i], O_RDONLY);
+            if (fd < 0) {
+                fprintf(stderr, "head: cannot open '%s'\n", argv[i]);
+                continue;
+            }
+            head_fd(fd, nlines);
+            close(fd);
+        }
+    }
+    return 0;
+}
diff --git a/user/cmds/hostname/Makefile b/user/cmds/hostname/Makefile
new file mode 100644 (file)
index 0000000..de74afd
--- /dev/null
@@ -0,0 +1,3 @@
+NAME := hostname
+SRCS := hostname.c
+include ../common.mk
diff --git a/user/cmds/hostname/hostname.c b/user/cmds/hostname/hostname.c
new file mode 100644 (file)
index 0000000..dad2290
--- /dev/null
@@ -0,0 +1,30 @@
+/* AdrOS hostname utility */
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+int main(int argc, char** argv) {
+    (void)argc; (void)argv;
+
+    /* Try /proc/hostname first, then /etc/hostname, then fallback */
+    static const char* paths[] = { "/proc/hostname", "/etc/hostname", NULL };
+    for (int i = 0; paths[i]; i++) {
+        int fd = open(paths[i], O_RDONLY);
+        if (fd >= 0) {
+            char buf[256];
+            int r = read(fd, buf, sizeof(buf) - 1);
+            close(fd);
+            if (r > 0) {
+                buf[r] = '\0';
+                /* Strip trailing newline */
+                if (r > 0 && buf[r - 1] == '\n') buf[r - 1] = '\0';
+                printf("%s\n", buf);
+                return 0;
+            }
+        }
+    }
+
+    printf("adros\n");
+    return 0;
+}
diff --git a/user/cmds/id/Makefile b/user/cmds/id/Makefile
new file mode 100644 (file)
index 0000000..2e274a6
--- /dev/null
@@ -0,0 +1,3 @@
+NAME := id
+SRCS := id.c
+include ../common.mk
diff --git a/user/cmds/id/id.c b/user/cmds/id/id.c
new file mode 100644 (file)
index 0000000..31b51ab
--- /dev/null
@@ -0,0 +1,9 @@
+/* AdrOS id utility — display user and group IDs */
+#include <stdio.h>
+#include <unistd.h>
+
+int main(void) {
+    printf("uid=%d gid=%d euid=%d egid=%d\n",
+           getuid(), getgid(), geteuid(), getegid());
+    return 0;
+}
diff --git a/user/cmds/init/Makefile b/user/cmds/init/Makefile
new file mode 100644 (file)
index 0000000..1dbedb1
--- /dev/null
@@ -0,0 +1,3 @@
+NAME := init
+SRCS := init.c
+include ../common.mk
diff --git a/user/cmds/init/init.c b/user/cmds/init/init.c
new file mode 100644 (file)
index 0000000..02e08f4
--- /dev/null
@@ -0,0 +1,311 @@
+/* AdrOS SysV-like init (/sbin/init)
+ *
+ * Reads /etc/inittab for configuration.
+ * Supports runlevels 0-6 and S (single-user).
+ * Actions: sysinit, respawn, wait, once, ctrlaltdel, shutdown.
+ *
+ * Default behavior (no inittab):
+ *   1. Run /etc/init.d/rcS (if exists)
+ *   2. Spawn /bin/sh on /dev/console
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <time.h>
+
+#define MAX_ENTRIES 32
+#define LINE_MAX   256
+
+/* Inittab entry actions */
+enum action {
+    ACT_SYSINIT,    /* Run during system initialization */
+    ACT_WAIT,       /* Run and wait for completion */
+    ACT_ONCE,       /* Run once when entering runlevel */
+    ACT_RESPAWN,    /* Restart when process dies */
+    ACT_CTRLALTDEL, /* Run on Ctrl+Alt+Del */
+    ACT_SHUTDOWN,   /* Run during shutdown */
+};
+
+struct inittab_entry {
+    char id[8];
+    char runlevels[16];
+    enum action action;
+    char process[128];
+    int  pid;           /* PID of running process (for respawn) */
+    int  active;
+};
+
+static struct inittab_entry entries[MAX_ENTRIES];
+static int nentries = 0;
+static int current_runlevel = 3;  /* Default: multi-user */
+
+static enum action parse_action(const char* s) {
+    if (strcmp(s, "sysinit") == 0) return ACT_SYSINIT;
+    if (strcmp(s, "wait") == 0) return ACT_WAIT;
+    if (strcmp(s, "once") == 0) return ACT_ONCE;
+    if (strcmp(s, "respawn") == 0) return ACT_RESPAWN;
+    if (strcmp(s, "ctrlaltdel") == 0) return ACT_CTRLALTDEL;
+    if (strcmp(s, "shutdown") == 0) return ACT_SHUTDOWN;
+    return ACT_ONCE;
+}
+
+/* Parse /etc/inittab
+ * Format: id:runlevels:action:process
+ * Example:
+ *   ::sysinit:/etc/init.d/rcS
+ *   ::respawn:/bin/sh
+ *   tty1:2345:respawn:/bin/sh
+ */
+static int parse_inittab(void) {
+    int fd = open("/etc/inittab", O_RDONLY);
+    if (fd < 0) return -1;
+
+    char buf[2048];
+    int total = 0;
+    int r;
+    while ((r = read(fd, buf + total, (size_t)(sizeof(buf) - (size_t)total - 1))) > 0)
+        total += r;
+    buf[total] = '\0';
+    close(fd);
+
+    char* p = buf;
+    while (*p && nentries < MAX_ENTRIES) {
+        /* Skip whitespace and comments */
+        while (*p == ' ' || *p == '\t') p++;
+        if (*p == '#' || *p == '\n') {
+            while (*p && *p != '\n') p++;
+            if (*p == '\n') p++;
+            continue;
+        }
+        if (*p == '\0') break;
+
+        struct inittab_entry* e = &entries[nentries];
+        memset(e, 0, sizeof(*e));
+
+        /* id */
+        char* start = p;
+        while (*p && *p != ':') p++;
+        int len = (int)(p - start);
+        if (len > 7) len = 7;
+        memcpy(e->id, start, (size_t)len);
+        e->id[len] = '\0';
+        if (*p == ':') p++;
+
+        /* runlevels */
+        start = p;
+        while (*p && *p != ':') p++;
+        len = (int)(p - start);
+        if (len > 15) len = 15;
+        memcpy(e->runlevels, start, (size_t)len);
+        e->runlevels[len] = '\0';
+        if (*p == ':') p++;
+
+        /* action */
+        start = p;
+        while (*p && *p != ':') p++;
+        char action_str[32];
+        len = (int)(p - start);
+        if (len > 31) len = 31;
+        memcpy(action_str, start, (size_t)len);
+        action_str[len] = '\0';
+        e->action = parse_action(action_str);
+        if (*p == ':') p++;
+
+        /* process */
+        start = p;
+        while (*p && *p != '\n') p++;
+        len = (int)(p - start);
+        if (len > 127) len = 127;
+        memcpy(e->process, start, (size_t)len);
+        e->process[len] = '\0';
+        if (*p == '\n') p++;
+
+        e->pid = -1;
+        e->active = 1;
+        nentries++;
+    }
+
+    return nentries > 0 ? 0 : -1;
+}
+
+/* Check if entry should run at current runlevel */
+static int entry_matches_runlevel(const struct inittab_entry* e) {
+    if (e->runlevels[0] == '\0') return 1;  /* Empty = all runlevels */
+    char rl = '0' + (char)current_runlevel;
+    for (const char* p = e->runlevels; *p; p++) {
+        if (*p == rl || *p == 'S' || *p == 's') return 1;
+    }
+    return 0;
+}
+
+/* Run a process (fork + exec) */
+static int run_process(const char* cmd) {
+    int pid = fork();
+    if (pid < 0) return -1;
+
+    if (pid == 0) {
+        /* Child: parse command into argv */
+        char buf[128];
+        strncpy(buf, cmd, sizeof(buf) - 1);
+        buf[sizeof(buf) - 1] = '\0';
+
+        char* argv[16];
+        int argc = 0;
+        char* p = buf;
+        while (*p && argc < 15) {
+            while (*p == ' ' || *p == '\t') p++;
+            if (*p == '\0') break;
+            argv[argc++] = p;
+            while (*p && *p != ' ' && *p != '\t') p++;
+            if (*p) *p++ = '\0';
+        }
+        argv[argc] = NULL;
+
+        if (argc > 0) {
+            execve(argv[0], (const char* const*)argv, NULL);
+            /* If execve fails, try with /bin/sh -c */
+            const char* sh_argv[] = { "/bin/sh", "-c", cmd, NULL };
+            execve("/bin/sh", sh_argv, NULL);
+        }
+        _exit(127);
+    }
+
+    return pid;
+}
+
+/* Run a process and wait for it */
+static int run_and_wait(const char* cmd) {
+    int pid = run_process(cmd);
+    if (pid < 0) return -1;
+    int st;
+    waitpid(pid, &st, 0);
+    return st;
+}
+
+/* Run all entries matching a given action and current runlevel */
+static void run_action(enum action act, int do_wait) {
+    for (int i = 0; i < nentries; i++) {
+        if (entries[i].action != act) continue;
+        if (!entry_matches_runlevel(&entries[i])) continue;
+        if (!entries[i].active) continue;
+
+        if (do_wait) {
+            run_and_wait(entries[i].process);
+        } else {
+            entries[i].pid = run_process(entries[i].process);
+        }
+    }
+}
+
+/* Respawn dead children */
+static void check_respawn(void) {
+    for (int i = 0; i < nentries; i++) {
+        if (entries[i].action != ACT_RESPAWN) continue;
+        if (!entry_matches_runlevel(&entries[i])) continue;
+        if (!entries[i].active) continue;
+
+        if (entries[i].pid <= 0) {
+            entries[i].pid = run_process(entries[i].process);
+        } else {
+            /* Check if still running */
+            int st;
+            int r = waitpid(entries[i].pid, &st, 1 /* WNOHANG */);
+            if (r > 0) {
+                /* Process exited, respawn */
+                entries[i].pid = run_process(entries[i].process);
+            }
+        }
+    }
+}
+
+/* Default behavior when no inittab exists */
+static void default_init(void) {
+    /* Run /etc/init.d/rcS if it exists */
+    if (access("/etc/init.d/rcS", 0) == 0) {
+        run_and_wait("/etc/init.d/rcS");
+    }
+
+    /* Spawn shell */
+    while (1) {
+        int pid = fork();
+        if (pid < 0) {
+            fprintf(stderr, "init: fork failed\n");
+            for (;;) { struct timespec ts = {1,0}; nanosleep(&ts, NULL); }
+        }
+
+        if (pid == 0) {
+            const char* argv[] = { "/bin/sh", NULL };
+            execve("/bin/sh", argv, NULL);
+            _exit(127);
+        }
+
+        int st;
+        waitpid(pid, &st, 0);
+
+        /* Shell exited, respawn after a small delay */
+        struct timespec ts = {1, 0};
+        nanosleep(&ts, NULL);
+    }
+}
+
+int main(int argc, char** argv) {
+    (void)argc; (void)argv;
+
+    /* PID 1 should not die on signals */
+    struct sigaction sa;
+    memset(&sa, 0, sizeof(sa));
+    sa.sa_handler = (uintptr_t)SIG_IGN;
+    sigaction(SIGINT, &sa, NULL);
+    sigaction(SIGTERM, &sa, NULL);
+
+    printf("AdrOS init starting (PID %d)\n", getpid());
+
+    /* Try to parse inittab */
+    if (parse_inittab() < 0) {
+        printf("init: no /etc/inittab, using defaults\n");
+        default_init();
+        return 0;  /* unreachable */
+    }
+
+    printf("init: loaded %d inittab entries, runlevel %d\n",
+           nentries, current_runlevel);
+
+    /* Phase 1: sysinit entries */
+    run_action(ACT_SYSINIT, 1);
+
+    /* Phase 2: wait entries */
+    run_action(ACT_WAIT, 1);
+
+    /* Phase 3: once entries */
+    run_action(ACT_ONCE, 0);
+
+    /* Phase 4: respawn entries */
+    run_action(ACT_RESPAWN, 0);
+
+    /* Main loop: reap children and respawn */
+    while (1) {
+        int st;
+        int pid = waitpid(-1, &st, 0);
+
+        if (pid > 0) {
+            /* Mark dead child and respawn if needed */
+            for (int i = 0; i < nentries; i++) {
+                if (entries[i].pid == pid) {
+                    entries[i].pid = -1;
+                    break;
+                }
+            }
+            check_respawn();
+        } else {
+            /* No children or error — sleep briefly */
+            struct timespec ts = {1, 0};
+            nanosleep(&ts, NULL);
+            check_respawn();
+        }
+    }
+
+    return 0;
+}
diff --git a/user/cmds/kill/Makefile b/user/cmds/kill/Makefile
new file mode 100644 (file)
index 0000000..21fd32d
--- /dev/null
@@ -0,0 +1,3 @@
+NAME := kill
+SRCS := kill.c
+include ../common.mk
diff --git a/user/cmds/kill/kill.c b/user/cmds/kill/kill.c
new file mode 100644 (file)
index 0000000..7cc1e95
--- /dev/null
@@ -0,0 +1,41 @@
+/* AdrOS kill utility — send signal to process */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+int main(int argc, char** argv) {
+    if (argc <= 1) {
+        fprintf(stderr, "usage: kill [-SIGNAL] PID...\n");
+        return 1;
+    }
+
+    int sig = 15; /* SIGTERM */
+    int start = 1;
+
+    if (argv[1][0] == '-') {
+        const char* s = argv[1] + 1;
+        if (strcmp(s, "9") == 0 || strcmp(s, "KILL") == 0) sig = 9;
+        else if (strcmp(s, "15") == 0 || strcmp(s, "TERM") == 0) sig = 15;
+        else if (strcmp(s, "2") == 0 || strcmp(s, "INT") == 0) sig = 2;
+        else if (strcmp(s, "1") == 0 || strcmp(s, "HUP") == 0) sig = 1;
+        else if (strcmp(s, "0") == 0) sig = 0;
+        else sig = atoi(s);
+        start = 2;
+    }
+
+    int rc = 0;
+    for (int i = start; i < argc; i++) {
+        int pid = atoi(argv[i]);
+        if (pid <= 0) {
+            fprintf(stderr, "kill: invalid pid '%s'\n", argv[i]);
+            rc = 1;
+            continue;
+        }
+        if (kill(pid, sig) < 0) {
+            fprintf(stderr, "kill: %d: no such process\n", pid);
+            rc = 1;
+        }
+    }
+    return rc;
+}
diff --git a/user/cmds/ldso/Makefile b/user/cmds/ldso/Makefile
new file mode 100644 (file)
index 0000000..d99d498
--- /dev/null
@@ -0,0 +1,21 @@
+# ldso — dynamic linker (statically linked)
+NAME := ldso
+
+TOPDIR   ?= $(abspath ../../..)
+BUILDDIR := $(TOPDIR)/build/user/cmds/$(NAME)
+USER_CC  ?= i686-elf-gcc
+
+ELF := $(BUILDDIR)/ld.so
+
+all: $(ELF)
+
+$(ELF): ldso.c
+       @mkdir -p $(BUILDDIR)
+       @echo "  CC+LD   $@"
+       @$(USER_CC) -m32 -ffreestanding -fno-pie -no-pie -nostdlib \
+               -Wl,-T,$(TOPDIR)/user/ldso_linker.ld -o $@ ldso.c
+
+clean:
+       rm -f $(ELF)
+
+.PHONY: all clean
diff --git a/user/cmds/ldso/ldso.c b/user/cmds/ldso/ldso.c
new file mode 100644 (file)
index 0000000..e961f51
--- /dev/null
@@ -0,0 +1,369 @@
+/* Userspace dynamic linker (ld.so) with lazy PLT/GOT binding.
+ *
+ * The kernel ELF loader pushes an auxiliary vector (auxv) onto the user
+ * stack when PT_INTERP is present.  This linker:
+ *   1. Parses auxv to find AT_PHDR, AT_PHNUM, AT_ENTRY
+ *   2. Walks program headers to find PT_DYNAMIC
+ *   3. Extracts DT_PLTGOT, DT_JMPREL, DT_PLTRELSZ, DT_SYMTAB, DT_STRTAB
+ *   4. Sets GOT[1] = link_map pointer, GOT[2] = _dl_runtime_resolve
+ *   5. Jumps to AT_ENTRY (the real program entry point)
+ *
+ * On first PLT call, the resolver fires: looks up the symbol, patches
+ * the GOT entry, and jumps to the resolved function.  Subsequent calls
+ * go directly through the patched GOT (zero overhead).
+ *
+ * The kernel loads DT_NEEDED shared libraries at SHLIB_BASE (0x20000000).
+ * The resolver scans the .so's dynamic symtab to find undefined symbols. */
+
+typedef unsigned char  uint8_t;
+typedef unsigned short uint16_t;
+typedef unsigned int   uint32_t;
+typedef int            int32_t;
+
+/* ---- Auxiliary vector types ---- */
+#define AT_NULL   0
+#define AT_PHDR   3
+#define AT_PHENT  4
+#define AT_PHNUM  5
+#define AT_ENTRY  9
+
+/* ---- ELF types (minimal, matching kernel include/elf.h) ---- */
+#define PT_LOAD    1
+#define PT_DYNAMIC 2
+
+#define DT_NULL    0
+#define DT_NEEDED  1
+#define DT_PLTRELSZ 2
+#define DT_PLTGOT  3
+#define DT_HASH    4
+#define DT_STRTAB  5
+#define DT_SYMTAB  6
+#define DT_STRSZ   10
+#define DT_SYMENT  11
+#define DT_REL     17
+#define DT_RELSZ   18
+#define DT_JMPREL  23
+
+#define R_386_32       1
+#define R_386_COPY     5
+#define R_386_GLOB_DAT 6
+#define R_386_JMP_SLOT 7
+
+#define ELF32_R_SYM(i)  ((i) >> 8)
+#define ELF32_R_TYPE(i)  ((unsigned char)(i))
+
+#define STB_GLOBAL 1
+#define STB_WEAK   2
+#define ELF32_ST_BIND(i) ((i) >> 4)
+
+#define SHLIB_BASE 0x11000000U
+
+struct elf32_phdr {
+    uint32_t p_type, p_offset, p_vaddr, p_paddr;
+    uint32_t p_filesz, p_memsz, p_flags, p_align;
+};
+
+struct elf32_dyn {
+    int32_t  d_tag;
+    uint32_t d_val;
+};
+
+struct elf32_rel {
+    uint32_t r_offset;
+    uint32_t r_info;
+};
+
+struct elf32_sym {
+    uint32_t st_name, st_value, st_size;
+    uint8_t  st_info, st_other;
+    uint16_t st_shndx;
+};
+
+/* ---- Link map: per-module metadata for the resolver ---- */
+struct link_map {
+    uint32_t l_addr;           /* base load address (0 for ET_EXEC) */
+    uint32_t jmprel;           /* DT_JMPREL VA (relocation table for .rel.plt) */
+    uint32_t pltrelsz;         /* DT_PLTRELSZ */
+    uint32_t symtab;           /* DT_SYMTAB VA */
+    uint32_t strtab;           /* DT_STRTAB VA */
+    uint32_t rel;              /* DT_REL VA (eager relocations) */
+    uint32_t relsz;            /* DT_RELSZ */
+    /* Shared lib symbol lookup info */
+    uint32_t shlib_symtab;     /* .so DT_SYMTAB VA (0 if no .so) */
+    uint32_t shlib_strtab;     /* .so DT_STRTAB VA */
+    uint32_t shlib_base;       /* .so load base */
+    uint32_t shlib_hash;       /* .so DT_HASH VA */
+};
+
+static struct link_map g_map;
+
+/* ---- Minimal string helpers (no libc) ---- */
+static int str_eq(const char* a, const char* b) {
+    while (*a && *b) { if (*a++ != *b++) return 0; }
+    return *a == *b;
+}
+
+/* ---- ELF hash (for DT_HASH lookup) ---- */
+static uint32_t elf_hash(const char* name) {
+    uint32_t h = 0, g;
+    while (*name) {
+        h = (h << 4) + (uint8_t)*name++;
+        g = h & 0xF0000000U;
+        if (g) h ^= g >> 24;
+        h &= ~g;
+    }
+    return h;
+}
+
+/* ---- Symbol lookup in a shared library via DT_HASH ---- */
+static uint32_t shlib_lookup(const char* name, const struct link_map* map) {
+    if (!map->shlib_symtab || !map->shlib_strtab || !map->shlib_hash)
+        return 0;
+
+    const uint32_t* hashtab = (const uint32_t*)(map->shlib_hash + map->shlib_base);
+    uint32_t nbucket = hashtab[0];
+    uint32_t nchain  = hashtab[1];
+    const uint32_t* bucket = &hashtab[2];
+    const uint32_t* chain  = &hashtab[2 + nbucket];
+    (void)nchain;
+
+    uint32_t h = elf_hash(name) % nbucket;
+    const struct elf32_sym* symtab = (const struct elf32_sym*)(map->shlib_symtab + map->shlib_base);
+    const char* strtab = (const char*)(map->shlib_strtab + map->shlib_base);
+
+    for (uint32_t i = bucket[h]; i != 0; i = chain[i]) {
+        const struct elf32_sym* sym = &symtab[i];
+        uint8_t bind = ELF32_ST_BIND(sym->st_info);
+        if ((bind == STB_GLOBAL || bind == STB_WEAK) &&
+            sym->st_shndx != 0 && sym->st_value != 0) {
+            if (str_eq(strtab + sym->st_name, name))
+                return sym->st_value + map->shlib_base;
+        }
+    }
+    return 0;
+}
+
+/* ---- dl_fixup: called by _dl_runtime_resolve trampoline ----
+ * Resolves a single PLT entry: looks up the symbol, patches GOT,
+ * returns the resolved address. */
+uint32_t dl_fixup(struct link_map* map, uint32_t reloc_offset)
+    __attribute__((used, visibility("hidden")));
+
+uint32_t dl_fixup(struct link_map* map, uint32_t reloc_offset) {
+    const struct elf32_rel* rel =
+        (const struct elf32_rel*)(map->jmprel + reloc_offset);
+
+    uint32_t sym_idx = ELF32_R_SYM(rel->r_info);
+    const struct elf32_sym* sym =
+        &((const struct elf32_sym*)map->symtab)[sym_idx];
+
+    uint32_t resolved = 0;
+
+    if (sym->st_value != 0) {
+        resolved = sym->st_value + map->l_addr;
+    } else {
+        const char* name = (const char*)map->strtab + sym->st_name;
+        resolved = shlib_lookup(name, map);
+    }
+
+    if (resolved) {
+        uint32_t* got_entry = (uint32_t*)(rel->r_offset + map->l_addr);
+        *got_entry = resolved;
+    }
+
+    return resolved;
+}
+
+/* ---- _dl_runtime_resolve: PLT[0] jumps here via GOT[2] ----
+ * Entry stack: [link_map*] [reloc_offset] [return_addr]
+ * Uses the glibc i386 convention: save eax/ecx/edx, call dl_fixup,
+ * restore, ret $8 to jump to resolved function. */
+void _dl_runtime_resolve(void)
+    __attribute__((naked, used, visibility("hidden")));
+
+void _dl_runtime_resolve(void) {
+    __asm__ volatile(
+        "pushl %%eax\n"
+        "pushl %%ecx\n"
+        "pushl %%edx\n"
+        "movl 16(%%esp), %%edx\n"   /* reloc_offset */
+        "movl 12(%%esp), %%eax\n"   /* link_map* */
+        "pushl %%edx\n"
+        "pushl %%eax\n"
+        "call dl_fixup\n"
+        "addl $8, %%esp\n"
+        "popl %%edx\n"
+        "popl %%ecx\n"
+        "xchgl %%eax, (%%esp)\n"    /* restore eax, put resolved addr on stack */
+        "ret $8\n"                   /* jump to resolved; pop link_map + reloc_offset */
+        ::: "memory"
+    );
+}
+
+/* ---- Parse a PT_DYNAMIC at the given VA to extract .so symtab info ---- */
+static void parse_shlib_dynamic(uint32_t dyn_va, uint32_t base) {
+    const struct elf32_dyn* d = (const struct elf32_dyn*)dyn_va;
+    for (; d->d_tag != DT_NULL; d++) {
+        switch (d->d_tag) {
+        case DT_SYMTAB: g_map.shlib_symtab = d->d_val; break;
+        case DT_STRTAB: g_map.shlib_strtab = d->d_val; break;
+        case DT_HASH:   g_map.shlib_hash   = d->d_val; break;
+        }
+    }
+    g_map.shlib_base = base;
+}
+
+/* ---- Scan for shared library's PT_DYNAMIC at SHLIB_BASE ---- */
+static void find_shlib_info(void) {
+    const uint8_t* base = (const uint8_t*)SHLIB_BASE;
+    /* Check ELF magic at SHLIB_BASE */
+    if (base[0] != 0x7F || base[1] != 'E' || base[2] != 'L' || base[3] != 'F')
+        return;
+
+    uint32_t e_phoff   = *(const uint32_t*)(base + 28);
+    uint16_t e_phnum   = *(const uint16_t*)(base + 44);
+    uint16_t e_phentsize = *(const uint16_t*)(base + 42);
+
+    for (uint16_t i = 0; i < e_phnum; i++) {
+        const struct elf32_phdr* ph =
+            (const struct elf32_phdr*)(base + e_phoff + i * e_phentsize);
+        if (ph->p_type == PT_DYNAMIC) {
+            parse_shlib_dynamic(ph->p_vaddr + SHLIB_BASE, SHLIB_BASE);
+            return;
+        }
+    }
+}
+
+/* ---- Entry point ---- */
+static void _start_c(uint32_t* initial_sp) __attribute__((noreturn, used));
+
+void _start(void) __attribute__((noreturn, naked, section(".text.start")));
+void _start(void) {
+    __asm__ volatile(
+        "pushl %%esp\n"
+        "call _start_c\n"
+        ::: "memory"
+    );
+    __builtin_unreachable();
+}
+
+static void _start_c(uint32_t* initial_sp) {
+    /* Stack layout set by execve:
+     *   initial_sp → argc
+     *                argv[0], argv[1], ..., NULL
+     *                envp[0], envp[1], ..., NULL
+     *                auxv[0], auxv[1], ..., {AT_NULL, 0}  */
+    uint32_t* sp = initial_sp;
+
+    uint32_t argc = *sp++;
+    sp += argc + 1;          /* skip argv[] + NULL terminator */
+    while (*sp) sp++;         /* skip envp[] entries */
+    sp++;                     /* skip envp NULL terminator */
+
+    /* sp now points to auxv array */
+    uint32_t at_entry = 0;
+    uint32_t at_phdr  = 0;
+    uint32_t at_phnum = 0;
+    uint32_t at_phent = 0;
+
+    for (uint32_t* p = sp; p[0] != AT_NULL; p += 2) {
+        switch (p[0]) {
+        case AT_ENTRY: at_entry = p[1]; break;
+        case AT_PHDR:  at_phdr  = p[1]; break;
+        case AT_PHNUM: at_phnum = p[1]; break;
+        case AT_PHENT: at_phent = p[1]; break;
+        }
+    }
+
+    if (!at_entry) {
+        __asm__ volatile("mov $2, %%eax\n mov $127, %%ebx\n int $0x80" ::: "eax", "ebx");
+        __builtin_unreachable();
+    }
+
+    /* Walk program headers to find PT_DYNAMIC */
+    g_map.l_addr = 0;
+
+    if (at_phdr && at_phnum && at_phent) {
+        for (uint32_t i = 0; i < at_phnum; i++) {
+            const struct elf32_phdr* ph =
+                (const struct elf32_phdr*)(at_phdr + i * at_phent);
+            if (ph->p_type == PT_DYNAMIC) {
+                uint32_t dyn_va = ph->p_vaddr + g_map.l_addr;
+                const struct elf32_dyn* d = (const struct elf32_dyn*)dyn_va;
+                uint32_t pltgot = 0;
+
+                for (; d->d_tag != DT_NULL; d++) {
+                    switch (d->d_tag) {
+                    case DT_PLTGOT:   pltgot          = d->d_val; break;
+                    case DT_JMPREL:   g_map.jmprel    = d->d_val; break;
+                    case DT_PLTRELSZ: g_map.pltrelsz  = d->d_val; break;
+                    case DT_SYMTAB:   g_map.symtab    = d->d_val; break;
+                    case DT_STRTAB:   g_map.strtab    = d->d_val; break;
+                    case DT_REL:      g_map.rel       = d->d_val; break;
+                    case DT_RELSZ:    g_map.relsz     = d->d_val; break;
+                    }
+                }
+
+                /* Scan for shared library info BEFORE resolving relocations */
+                find_shlib_info();
+
+                /* Set up GOT for lazy binding:
+                 * GOT[0] = _DYNAMIC (already set by linker)
+                 * GOT[1] = link_map pointer
+                 * GOT[2] = _dl_runtime_resolve address */
+                if (pltgot && g_map.jmprel) {
+                    uint32_t* got = (uint32_t*)(pltgot + g_map.l_addr);
+                    got[1] = (uint32_t)&g_map;
+                    got[2] = (uint32_t)&_dl_runtime_resolve;
+                }
+
+                /* Process eager relocations (R_386_GLOB_DAT, R_386_COPY) */
+                if (g_map.rel && g_map.relsz) {
+                    uint32_t nrel = g_map.relsz / sizeof(struct elf32_rel);
+                    const struct elf32_rel* rtab =
+                        (const struct elf32_rel*)(g_map.rel + g_map.l_addr);
+                    for (uint32_t j = 0; j < nrel; j++) {
+                        uint32_t type = ELF32_R_TYPE(rtab[j].r_info);
+                        uint32_t sidx = ELF32_R_SYM(rtab[j].r_info);
+                        uint32_t* target = (uint32_t*)(rtab[j].r_offset + g_map.l_addr);
+                        if (type == R_386_GLOB_DAT || type == R_386_JMP_SLOT) {
+                            const struct elf32_sym* s =
+                                &((const struct elf32_sym*)g_map.symtab)[sidx];
+                            uint32_t addr = 0;
+                            if (s->st_value != 0)
+                                addr = s->st_value + g_map.l_addr;
+                            else {
+                                const char* nm = (const char*)g_map.strtab + s->st_name;
+                                addr = shlib_lookup(nm, &g_map);
+                            }
+                            if (addr) *target = addr;
+                        } else if (type == R_386_COPY && sidx) {
+                            const struct elf32_sym* s =
+                                &((const struct elf32_sym*)g_map.symtab)[sidx];
+                            const char* nm = (const char*)g_map.strtab + s->st_name;
+                            uint32_t src = shlib_lookup(nm, &g_map);
+                            if (src && s->st_size > 0) {
+                                const uint8_t* sp = (const uint8_t*)src;
+                                uint8_t* dp = (uint8_t*)target;
+                                for (uint32_t k = 0; k < s->st_size; k++)
+                                    dp[k] = sp[k];
+                            }
+                        }
+                    }
+                }
+                break;
+            }
+        }
+    }
+
+    /* Restore the original stack pointer so the real program's _start
+     * sees the correct layout: [argc] [argv...] [NULL] [envp...] [NULL] [auxv...]
+     * Then jump to the program entry point. */
+    __asm__ volatile(
+        "mov %0, %%esp\n"
+        "jmp *%1\n"
+        :: "r"(initial_sp), "r"(at_entry)
+        : "memory"
+    );
+    __builtin_unreachable();
+}
diff --git a/user/cmds/ln/Makefile b/user/cmds/ln/Makefile
new file mode 100644 (file)
index 0000000..aca9f3b
--- /dev/null
@@ -0,0 +1,3 @@
+NAME := ln
+SRCS := ln.c
+include ../common.mk
diff --git a/user/cmds/ln/ln.c b/user/cmds/ln/ln.c
new file mode 100644 (file)
index 0000000..72f60d4
--- /dev/null
@@ -0,0 +1,33 @@
+/* AdrOS ln utility */
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+int main(int argc, char** argv) {
+    int sflag = 0;
+    int start = 1;
+
+    if (argc > 1 && strcmp(argv[1], "-s") == 0) {
+        sflag = 1;
+        start = 2;
+    }
+
+    if (argc - start < 2) {
+        fprintf(stderr, "Usage: ln [-s] <target> <linkname>\n");
+        return 1;
+    }
+
+    int r;
+    if (sflag) {
+        r = symlink(argv[start], argv[start + 1]);
+    } else {
+        r = link(argv[start], argv[start + 1]);
+    }
+
+    if (r < 0) {
+        fprintf(stderr, "ln: failed to create %slink '%s' -> '%s'\n",
+                sflag ? "symbolic " : "", argv[start + 1], argv[start]);
+        return 1;
+    }
+    return 0;
+}
diff --git a/user/cmds/ls/Makefile b/user/cmds/ls/Makefile
new file mode 100644 (file)
index 0000000..0434a89
--- /dev/null
@@ -0,0 +1,3 @@
+NAME := ls
+SRCS := ls.c
+include ../common.mk
diff --git a/user/cmds/ls/ls.c b/user/cmds/ls/ls.c
new file mode 100644 (file)
index 0000000..6ce6c69
--- /dev/null
@@ -0,0 +1,139 @@
+/* AdrOS ls utility */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <sys/stat.h>
+
+static int aflag = 0;   /* -a: show hidden files */
+static int lflag = 0;   /* -l: long format */
+
+#define LS_MAX_ENTRIES 512
+
+struct ls_entry {
+    char name[256];
+    unsigned char type;
+};
+
+static struct ls_entry entries[LS_MAX_ENTRIES];
+
+static int cmp_entry(const void* a, const void* b) {
+    return strcmp(((const struct ls_entry*)a)->name,
+                  ((const struct ls_entry*)b)->name);
+}
+
+static void ls_dir(const char* path) {
+    int fd = open(path, O_RDONLY);
+    if (fd < 0) {
+        fprintf(stderr, "ls: cannot access '%s': No such file or directory\n", path);
+        return;
+    }
+
+    int count = 0;
+    char buf[2048];
+    int rc;
+    while ((rc = getdents(fd, buf, sizeof(buf))) > 0) {
+        int off = 0;
+        while (off < rc) {
+            struct dirent* d = (struct dirent*)(buf + off);
+            if (d->d_reclen == 0) break;
+
+            if (!aflag && d->d_name[0] == '.') {
+                off += d->d_reclen;
+                continue;
+            }
+
+            if (count < LS_MAX_ENTRIES) {
+                strncpy(entries[count].name, d->d_name, 255);
+                entries[count].name[255] = '\0';
+                entries[count].type = d->d_type;
+                count++;
+            }
+            off += d->d_reclen;
+        }
+    }
+    close(fd);
+
+    qsort(entries, count, sizeof(struct ls_entry), cmp_entry);
+
+    for (int i = 0; i < count; i++) {
+        if (lflag) {
+            char fullpath[512];
+            size_t plen = strlen(path);
+            if (plen > 0 && path[plen - 1] == '/')
+                snprintf(fullpath, sizeof(fullpath), "%s%s", path, entries[i].name);
+            else
+                snprintf(fullpath, sizeof(fullpath), "%s/%s", path, entries[i].name);
+
+            struct stat st;
+            int have_stat = (stat(fullpath, &st) == 0);
+
+            char type = '-';
+            if (entries[i].type == DT_DIR) type = 'd';
+            else if (entries[i].type == DT_CHR) type = 'c';
+            else if (entries[i].type == DT_LNK) type = 'l';
+            else if (entries[i].type == DT_BLK) type = 'b';
+
+            char perms[10];
+            if (have_stat) {
+                unsigned m = (unsigned)st.st_mode;
+                perms[0] = (m & S_IRUSR) ? 'r' : '-';
+                perms[1] = (m & S_IWUSR) ? 'w' : '-';
+                perms[2] = (m & S_IXUSR) ? 'x' : '-';
+                perms[3] = (m & S_IRGRP) ? 'r' : '-';
+                perms[4] = (m & S_IWGRP) ? 'w' : '-';
+                perms[5] = (m & S_IXGRP) ? 'x' : '-';
+                perms[6] = (m & S_IROTH) ? 'r' : '-';
+                perms[7] = (m & S_IWOTH) ? 'w' : '-';
+                perms[8] = (m & S_IXOTH) ? 'x' : '-';
+                perms[9] = '\0';
+            } else {
+                strcpy(perms, "---------");
+            }
+
+            unsigned long sz = have_stat ? (unsigned long)st.st_size : 0;
+            unsigned nlink = have_stat ? (unsigned)st.st_nlink : 1;
+
+            printf("%c%s %2u root root %8lu %s\n",
+                   type, perms, nlink, sz, entries[i].name);
+        } else {
+            printf("%s\n", entries[i].name);
+        }
+    }
+}
+
+int main(int argc, char** argv) {
+    int npath = 0;
+    const char* paths[64];
+
+    for (int i = 1; i < argc; i++) {
+        if (argv[i][0] == '-' && argv[i][1]) {
+            const char* f = argv[i] + 1;
+            while (*f) {
+                if (*f == 'a') aflag = 1;
+                else if (*f == 'l') lflag = 1;
+                else {
+                    fprintf(stderr, "ls: invalid option -- '%c'\n", *f);
+                    return 1;
+                }
+                f++;
+            }
+        } else {
+            if (npath < 64) paths[npath++] = argv[i];
+        }
+    }
+
+    if (npath == 0) {
+        ls_dir(".");
+    } else {
+        for (int i = 0; i < npath; i++) {
+            if (npath > 1) printf("%s:\n", paths[i]);
+            ls_dir(paths[i]);
+            if (npath > 1 && i < npath - 1) printf("\n");
+        }
+    }
+
+    return 0;
+}
diff --git a/user/cmds/mkdir/Makefile b/user/cmds/mkdir/Makefile
new file mode 100644 (file)
index 0000000..b489933
--- /dev/null
@@ -0,0 +1,3 @@
+NAME := mkdir
+SRCS := mkdir.c
+include ../common.mk
diff --git a/user/cmds/mkdir/mkdir.c b/user/cmds/mkdir/mkdir.c
new file mode 100644 (file)
index 0000000..d5716c5
--- /dev/null
@@ -0,0 +1,61 @@
+/* AdrOS mkdir utility */
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+static int pflag = 0;   /* -p: create parent directories */
+
+static int mkdir_p(const char* path) {
+    char tmp[256];
+    size_t len = strlen(path);
+    if (len >= sizeof(tmp)) return -1;
+    strcpy(tmp, path);
+
+    for (char* p = tmp + 1; *p; p++) {
+        if (*p == '/') {
+            *p = '\0';
+            mkdir(tmp);   /* ignore errors — parent may already exist */
+            *p = '/';
+        }
+    }
+    return mkdir(tmp);
+}
+
+int main(int argc, char** argv) {
+    if (argc <= 1) {
+        fprintf(stderr, "mkdir: missing operand\n");
+        return 1;
+    }
+
+    int rc = 0;
+    int start = 1;
+
+    for (int i = 1; i < argc; i++) {
+        if (argv[i][0] == '-' && argv[i][1]) {
+            const char* f = argv[i] + 1;
+            while (*f) {
+                if (*f == 'p') pflag = 1;
+                else {
+                    fprintf(stderr, "mkdir: invalid option -- '%c'\n", *f);
+                    return 1;
+                }
+                f++;
+            }
+            start = i + 1;
+        }
+    }
+
+    for (int i = start; i < argc; i++) {
+        int r;
+        if (pflag) {
+            r = mkdir_p(argv[i]);
+        } else {
+            r = mkdir(argv[i]);
+        }
+        if (r < 0) {
+            fprintf(stderr, "mkdir: cannot create directory '%s'\n", argv[i]);
+            rc = 1;
+        }
+    }
+    return rc;
+}
diff --git a/user/cmds/mount/Makefile b/user/cmds/mount/Makefile
new file mode 100644 (file)
index 0000000..c59274c
--- /dev/null
@@ -0,0 +1,3 @@
+NAME := mount
+SRCS := mount.c
+include ../common.mk
diff --git a/user/cmds/mount/mount.c b/user/cmds/mount/mount.c
new file mode 100644 (file)
index 0000000..fba0bf5
--- /dev/null
@@ -0,0 +1,57 @@
+/* AdrOS mount utility — mount filesystems or display mounts */
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <syscall.h>
+#include <errno.h>
+
+static void show_mounts(void) {
+    int fd = open("/proc/mounts", O_RDONLY);
+    if (fd >= 0) {
+        char buf[1024];
+        int n;
+        while ((n = read(fd, buf, sizeof(buf))) > 0)
+            write(STDOUT_FILENO, buf, (size_t)n);
+        close(fd);
+    } else {
+        printf("tmpfs on / type overlayfs (rw)\n");
+        printf("devfs on /dev type devfs (rw)\n");
+        printf("procfs on /proc type procfs (ro)\n");
+    }
+}
+
+int main(int argc, char** argv) {
+    if (argc < 2) {
+        show_mounts();
+        return 0;
+    }
+
+    const char* fstype = "diskfs";
+    const char* device = NULL;
+    const char* mountpoint = NULL;
+
+    /* Parse options first, then collect positional args */
+    int i;
+    for (i = 1; i < argc; i++) {
+        if (strcmp(argv[i], "-t") == 0 && i + 1 < argc) {
+            fstype = argv[++i];
+        } else if (!device) {
+            device = argv[i];
+        } else if (!mountpoint) {
+            mountpoint = argv[i];
+        }
+    }
+
+    if (!device || !mountpoint) {
+        fprintf(stderr, "usage: mount [-t fstype] device mountpoint\n");
+        return 1;
+    }
+
+    int rc = __syscall_ret(_syscall3(SYS_MOUNT, (int)device, (int)mountpoint, (int)fstype));
+    if (rc < 0) {
+        fprintf(stderr, "mount: mounting %s on %s failed: %d\n", device, mountpoint, rc);
+        return 1;
+    }
+    return 0;
+}
diff --git a/user/cmds/mv/Makefile b/user/cmds/mv/Makefile
new file mode 100644 (file)
index 0000000..b29cc6a
--- /dev/null
@@ -0,0 +1,3 @@
+NAME := mv
+SRCS := mv.c
+include ../common.mk
diff --git a/user/cmds/mv/mv.c b/user/cmds/mv/mv.c
new file mode 100644 (file)
index 0000000..97cb1bc
--- /dev/null
@@ -0,0 +1,45 @@
+/* AdrOS mv utility */
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+
+int main(int argc, char** argv) {
+    if (argc < 3) {
+        fprintf(stderr, "Usage: mv <source> <dest>\n");
+        return 1;
+    }
+
+    /* Try rename first (same filesystem) */
+    if (rename(argv[1], argv[2]) == 0)
+        return 0;
+
+    /* Fallback: copy + unlink */
+    int src = open(argv[1], O_RDONLY);
+    if (src < 0) {
+        fprintf(stderr, "mv: cannot open '%s'\n", argv[1]);
+        return 1;
+    }
+
+    int dst = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0644);
+    if (dst < 0) {
+        fprintf(stderr, "mv: cannot create '%s'\n", argv[2]);
+        close(src);
+        return 1;
+    }
+
+    char buf[4096];
+    int r;
+    while ((r = read(src, buf, sizeof(buf))) > 0) {
+        if (write(dst, buf, (size_t)r) != r) {
+            fprintf(stderr, "mv: write error\n");
+            close(src); close(dst);
+            return 1;
+        }
+    }
+
+    close(src);
+    close(dst);
+    unlink(argv[1]);
+    return 0;
+}
diff --git a/user/cmds/pie_test/Makefile b/user/cmds/pie_test/Makefile
new file mode 100644 (file)
index 0000000..5d3b791
--- /dev/null
@@ -0,0 +1,30 @@
+# pie_test — PIE/shared library test binary
+NAME := pie_test
+
+TOPDIR   ?= $(abspath ../../..)
+BUILDDIR := $(TOPDIR)/build/user/cmds/$(NAME)
+USER_CC  ?= i686-elf-gcc
+USER_LD  ?= i686-elf-ld
+
+PIE_SO  := $(BUILDDIR)/libpietest.so
+PIE_ELF := $(BUILDDIR)/pie_test.elf
+
+all: $(PIE_SO) $(PIE_ELF)
+
+$(PIE_SO): pie_func.c
+       @mkdir -p $(BUILDDIR)
+       @echo "  CC [PIC] $<"
+       @$(USER_CC) -m32 -fPIC -fno-plt -c pie_func.c -o $(BUILDDIR)/pie_func.o
+       @$(USER_LD) -m elf_i386 -shared -soname libpietest.so -o $@ $(BUILDDIR)/pie_func.o
+
+$(PIE_ELF): pie_main.c $(PIE_SO)
+       @mkdir -p $(BUILDDIR)
+       @echo "  CC [PIE] $<"
+       @$(USER_CC) -m32 -fPIC -c pie_main.c -o $(BUILDDIR)/pie_main.o
+       @$(USER_LD) -m elf_i386 -pie --dynamic-linker=/lib/ld.so \
+               -T $(TOPDIR)/user/pie_linker.ld -o $@ $(BUILDDIR)/pie_main.o $(PIE_SO) -rpath /lib
+
+clean:
+       rm -f $(BUILDDIR)/pie_func.o $(BUILDDIR)/pie_main.o $(PIE_SO) $(PIE_ELF)
+
+.PHONY: all clean
diff --git a/user/cmds/pie_test/pie_func.c b/user/cmds/pie_test/pie_func.c
new file mode 100644 (file)
index 0000000..8e744f8
--- /dev/null
@@ -0,0 +1,7 @@
+/* Shared library function for PLT/GOT lazy binding test.
+ * Compiled as a shared object (libpietest.so), loaded at SHLIB_BASE by kernel.
+ * The main PIE binary calls test_add() through PLT — resolved lazily by ld.so. */
+
+int test_add(int a, int b) {
+    return a + b;
+}
diff --git a/user/cmds/pie_test/pie_main.c b/user/cmds/pie_test/pie_main.c
new file mode 100644 (file)
index 0000000..9232cf4
--- /dev/null
@@ -0,0 +1,34 @@
+/* PIE test binary for PLT/GOT lazy binding verification.
+ * Calls test_add() from libpietest.so through PLT — resolved lazily by ld.so.
+ * Built as: i686-elf-ld -pie --dynamic-linker=/lib/ld.so */
+
+static inline void sys_exit(int code) {
+    __asm__ volatile("int $0x80" :: "a"(2), "b"(code) : "memory");
+}
+
+static inline int sys_write(int fd, const void* buf, unsigned len) {
+    int ret;
+    __asm__ volatile("int $0x80" : "=a"(ret) : "a"(1), "b"(fd), "c"(buf), "d"(len) : "memory");
+    return ret;
+}
+
+extern int test_add(int a, int b);
+
+void _start(void) {
+    int r = test_add(38, 4);
+    if (r == 42) {
+        sys_write(1, "[test] lazy PLT OK\n", 19);
+    } else {
+        sys_write(1, "[test] lazy PLT FAIL\n", 21);
+    }
+
+    /* Call again — this time GOT is already patched, tests direct path */
+    r = test_add(100, 23);
+    if (r == 123) {
+        sys_write(1, "[test] PLT cached OK\n", 21);
+    } else {
+        sys_write(1, "[test] PLT cached FAIL\n", 23);
+    }
+
+    sys_exit(0);
+}
diff --git a/user/cmds/printenv/Makefile b/user/cmds/printenv/Makefile
new file mode 100644 (file)
index 0000000..2b925ff
--- /dev/null
@@ -0,0 +1,3 @@
+NAME := printenv
+SRCS := printenv.c
+include ../common.mk
diff --git a/user/cmds/printenv/printenv.c b/user/cmds/printenv/printenv.c
new file mode 100644 (file)
index 0000000..0e3f081
--- /dev/null
@@ -0,0 +1,27 @@
+/* AdrOS printenv utility — print environment variables */
+#include <stdio.h>
+#include <string.h>
+
+extern char** __environ;
+
+int main(int argc, char** argv) {
+    if (!__environ) return 1;
+    if (argc <= 1) {
+        for (int i = 0; __environ[i]; i++)
+            printf("%s\n", __environ[i]);
+        return 0;
+    }
+    for (int i = 1; i < argc; i++) {
+        int found = 0;
+        int nlen = (int)strlen(argv[i]);
+        for (int j = 0; __environ[j]; j++) {
+            if (strncmp(__environ[j], argv[i], (size_t)nlen) == 0 && __environ[j][nlen] == '=') {
+                printf("%s\n", __environ[j] + nlen + 1);
+                found = 1;
+                break;
+            }
+        }
+        if (!found) return 1;
+    }
+    return 0;
+}
diff --git a/user/cmds/ps/Makefile b/user/cmds/ps/Makefile
new file mode 100644 (file)
index 0000000..7d31d93
--- /dev/null
@@ -0,0 +1,3 @@
+NAME := ps
+SRCS := ps.c
+include ../common.mk
diff --git a/user/cmds/ps/ps.c b/user/cmds/ps/ps.c
new file mode 100644 (file)
index 0000000..251de0d
--- /dev/null
@@ -0,0 +1,47 @@
+/* AdrOS ps utility — list processes from /proc */
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <dirent.h>
+
+static int is_digit(char c) { return c >= '0' && c <= '9'; }
+
+int main(void) {
+    printf("  PID CMD\n");
+    int fd = open("/proc", O_RDONLY);
+    if (fd < 0) {
+        fprintf(stderr, "ps: cannot open /proc\n");
+        return 1;
+    }
+    char buf[512];
+    int rc;
+    while ((rc = getdents(fd, buf, sizeof(buf))) > 0) {
+        int off = 0;
+        while (off < rc) {
+            struct dirent* d = (struct dirent*)(buf + off);
+            if (d->d_reclen == 0) break;
+            if (is_digit(d->d_name[0])) {
+                char path[64];
+                snprintf(path, sizeof(path), "/proc/%s/cmdline", d->d_name);
+                int cfd = open(path, O_RDONLY);
+                char cmd[64] = "?";
+                if (cfd >= 0) {
+                    int n = read(cfd, cmd, sizeof(cmd) - 1);
+                    if (n > 0) {
+                        cmd[n] = '\0';
+                        while (n > 0 && (cmd[n-1] == '\n' || cmd[n-1] == '\0')) {
+                            cmd[--n] = '\0';
+                        }
+                    }
+                    if (n <= 0) strcpy(cmd, "[kernel]");
+                    close(cfd);
+                }
+                printf("%5s %s\n", d->d_name, cmd);
+            }
+            off += d->d_reclen;
+        }
+    }
+    close(fd);
+    return 0;
+}
diff --git a/user/cmds/pwd/Makefile b/user/cmds/pwd/Makefile
new file mode 100644 (file)
index 0000000..388f5f0
--- /dev/null
@@ -0,0 +1,3 @@
+NAME := pwd
+SRCS := pwd.c
+include ../common.mk
diff --git a/user/cmds/pwd/pwd.c b/user/cmds/pwd/pwd.c
new file mode 100644 (file)
index 0000000..baa6134
--- /dev/null
@@ -0,0 +1,14 @@
+/* AdrOS pwd utility — print working directory */
+#include <stdio.h>
+#include <unistd.h>
+
+int main(void) {
+    char buf[256];
+    if (getcwd(buf, sizeof(buf)) >= 0)
+        printf("%s\n", buf);
+    else {
+        fprintf(stderr, "pwd: error\n");
+        return 1;
+    }
+    return 0;
+}
diff --git a/user/cmds/rm/Makefile b/user/cmds/rm/Makefile
new file mode 100644 (file)
index 0000000..0667c46
--- /dev/null
@@ -0,0 +1,3 @@
+NAME := rm
+SRCS := rm.c
+include ../common.mk
diff --git a/user/cmds/rm/rm.c b/user/cmds/rm/rm.c
new file mode 100644 (file)
index 0000000..0a4d22e
--- /dev/null
@@ -0,0 +1,48 @@
+/* AdrOS rm utility */
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+static int rflag = 0;   /* -r: recursive */
+static int fflag = 0;   /* -f: force (no errors) */
+static int dflag = 0;   /* -d: remove empty directories */
+
+int main(int argc, char** argv) {
+    if (argc <= 1) {
+        fprintf(stderr, "rm: missing operand\n");
+        return 1;
+    }
+
+    int start = 1;
+    for (int i = 1; i < argc; i++) {
+        if (argv[i][0] == '-' && argv[i][1]) {
+            const char* f = argv[i] + 1;
+            while (*f) {
+                if (*f == 'r' || *f == 'R') rflag = 1;
+                else if (*f == 'f') fflag = 1;
+                else if (*f == 'd') dflag = 1;
+                else {
+                    fprintf(stderr, "rm: invalid option -- '%c'\n", *f);
+                    return 1;
+                }
+                f++;
+            }
+            start = i + 1;
+        } else {
+            break;
+        }
+    }
+
+    int rc = 0;
+    for (int i = start; i < argc; i++) {
+        int r = unlink(argv[i]);
+        if (r < 0 && (rflag || dflag)) {
+            r = rmdir(argv[i]);
+        }
+        if (r < 0 && !fflag) {
+            fprintf(stderr, "rm: cannot remove '%s'\n", argv[i]);
+            rc = 1;
+        }
+    }
+    return rc;
+}
diff --git a/user/cmds/rmdir/Makefile b/user/cmds/rmdir/Makefile
new file mode 100644 (file)
index 0000000..df87ffb
--- /dev/null
@@ -0,0 +1,3 @@
+NAME := rmdir
+SRCS := rmdir.c
+include ../common.mk
diff --git a/user/cmds/rmdir/rmdir.c b/user/cmds/rmdir/rmdir.c
new file mode 100644 (file)
index 0000000..0d65e8a
--- /dev/null
@@ -0,0 +1,18 @@
+/* AdrOS rmdir utility — remove empty directories */
+#include <stdio.h>
+#include <unistd.h>
+
+int main(int argc, char** argv) {
+    if (argc <= 1) {
+        fprintf(stderr, "rmdir: missing operand\n");
+        return 1;
+    }
+    int rc = 0;
+    for (int i = 1; i < argc; i++) {
+        if (rmdir(argv[i]) < 0) {
+            fprintf(stderr, "rmdir: failed to remove '%s'\n", argv[i]);
+            rc = 1;
+        }
+    }
+    return rc;
+}
diff --git a/user/cmds/sed/Makefile b/user/cmds/sed/Makefile
new file mode 100644 (file)
index 0000000..1b3a94c
--- /dev/null
@@ -0,0 +1,3 @@
+NAME := sed
+SRCS := sed.c
+include ../common.mk
diff --git a/user/cmds/sed/sed.c b/user/cmds/sed/sed.c
new file mode 100644 (file)
index 0000000..53992f2
--- /dev/null
@@ -0,0 +1,95 @@
+/* AdrOS sed utility — minimal stream editor (s/pattern/replacement/g only) */
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+static int match_at(const char* s, const char* pat, int patlen) {
+    for (int i = 0; i < patlen; i++) {
+        if (s[i] == '\0' || s[i] != pat[i]) return 0;
+    }
+    return 1;
+}
+
+static void sed_substitute(const char* line, const char* pat, int patlen,
+                           const char* rep, int replen, int global) {
+    const char* p = line;
+    while (*p) {
+        if (match_at(p, pat, patlen)) {
+            write(STDOUT_FILENO, rep, replen);
+            p += patlen;
+            if (!global) {
+                write(STDOUT_FILENO, p, strlen(p));
+                return;
+            }
+        } else {
+            write(STDOUT_FILENO, p, 1);
+            p++;
+        }
+    }
+}
+
+static int parse_s_cmd(const char* expr, char* pat, int* patlen,
+                       char* rep, int* replen, int* global) {
+    if (expr[0] != 's' || expr[1] == '\0') return -1;
+    char delim = expr[1];
+    const char* p = expr + 2;
+    int pi = 0;
+    while (*p && *p != delim && pi < 255) pat[pi++] = *p++;
+    pat[pi] = '\0'; *patlen = pi;
+    if (*p != delim) return -1;
+    p++;
+    int ri = 0;
+    while (*p && *p != delim && ri < 255) rep[ri++] = *p++;
+    rep[ri] = '\0'; *replen = ri;
+    *global = 0;
+    if (*p == delim) { p++; if (*p == 'g') *global = 1; }
+    return 0;
+}
+
+int main(int argc, char** argv) {
+    if (argc < 2) {
+        fprintf(stderr, "Usage: sed 's/pattern/replacement/[g]' [file]\n");
+        return 1;
+    }
+
+    char pat[256], rep[256];
+    int patlen, replen, global;
+    int ei = 1;
+    if (strcmp(argv[1], "-e") == 0 && argc > 2) ei = 2;
+
+    if (parse_s_cmd(argv[ei], pat, &patlen, rep, &replen, &global) < 0) {
+        fprintf(stderr, "sed: invalid expression: %s\n", argv[ei]);
+        return 1;
+    }
+
+    int fd = STDIN_FILENO;
+    if (argc > ei + 1) {
+        fd = open(argv[ei + 1], O_RDONLY);
+        if (fd < 0) {
+            fprintf(stderr, "sed: %s: No such file or directory\n", argv[ei + 1]);
+            return 1;
+        }
+    }
+
+    char line[4096];
+    int li = 0;
+    char c;
+    while (read(fd, &c, 1) == 1) {
+        if (c == '\n') {
+            line[li] = '\0';
+            sed_substitute(line, pat, patlen, rep, replen, global);
+            write(STDOUT_FILENO, "\n", 1);
+            li = 0;
+        } else if (li < (int)sizeof(line) - 1) {
+            line[li++] = c;
+        }
+    }
+    if (li > 0) {
+        line[li] = '\0';
+        sed_substitute(line, pat, patlen, rep, replen, global);
+    }
+
+    if (fd != STDIN_FILENO) close(fd);
+    return 0;
+}
diff --git a/user/cmds/sh/Makefile b/user/cmds/sh/Makefile
new file mode 100644 (file)
index 0000000..8191554
--- /dev/null
@@ -0,0 +1,3 @@
+NAME := sh
+SRCS := sh.c
+include ../common.mk
diff --git a/user/cmds/sh/sh.c b/user/cmds/sh/sh.c
new file mode 100644 (file)
index 0000000..1af3ab3
--- /dev/null
@@ -0,0 +1,1035 @@
+/* AdrOS POSIX-like shell (/bin/sh)
+ *
+ * Features:
+ *   - Variable assignment (VAR=value) and expansion ($VAR)
+ *   - Environment variables (export VAR=value)
+ *   - Line editing (left/right arrow keys)
+ *   - Command history (up/down arrow keys)
+ *   - Pipes (cmd1 | cmd2 | cmd3)
+ *   - Redirections (< > >>)
+ *   - Operators: ; && || &
+ *   - Job control: CTRL+C (SIGINT), CTRL+Z (SIGTSTP), background (&)
+ *   - Builtins: cd, exit, echo, export, unset, set, pwd, type
+ *   - PATH-based command resolution
+ *   - Quote handling (single and double quotes)
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <termios.h>
+#include <signal.h>
+#include <sys/ioctl.h>
+#include <sys/wait.h>
+
+static struct termios orig_termios;
+
+static void tty_raw_mode(void) {
+    tcgetattr(STDIN_FILENO, &orig_termios);
+    struct termios raw = orig_termios;
+    raw.c_lflag &= ~(ICANON | ECHO | ISIG);
+    raw.c_cc[VMIN] = 1;
+    raw.c_cc[VTIME] = 0;
+    tcsetattr(STDIN_FILENO, TCSANOW, &raw);
+}
+
+static void tty_restore(void) {
+    tcsetattr(STDIN_FILENO, TCSANOW, &orig_termios);
+}
+
+#define LINE_MAX   512
+#define MAX_ARGS   64
+#define MAX_VARS   64
+#define HIST_SIZE  32
+
+/* ---- Shell variables ---- */
+
+static struct {
+    char name[64];
+    char value[256];
+    int  exported;
+} vars[MAX_VARS];
+static int nvar = 0;
+
+static int last_status = 0;
+
+static const char* var_get(const char* name) {
+    for (int i = 0; i < nvar; i++)
+        if (strcmp(vars[i].name, name) == 0) return vars[i].value;
+    return getenv(name);
+}
+
+static void var_set(const char* name, const char* value, int exported) {
+    for (int i = 0; i < nvar; i++) {
+        if (strcmp(vars[i].name, name) == 0) {
+            strncpy(vars[i].value, value, 255);
+            vars[i].value[255] = '\0';
+            if (exported) vars[i].exported = 1;
+            return;
+        }
+    }
+    if (nvar < MAX_VARS) {
+        strncpy(vars[nvar].name, name, 63);
+        vars[nvar].name[63] = '\0';
+        strncpy(vars[nvar].value, value, 255);
+        vars[nvar].value[255] = '\0';
+        vars[nvar].exported = exported;
+        nvar++;
+    }
+}
+
+static void var_unset(const char* name) {
+    for (int i = 0; i < nvar; i++) {
+        if (strcmp(vars[i].name, name) == 0) {
+            vars[i] = vars[--nvar];
+            return;
+        }
+    }
+}
+
+/* Build envp array from exported variables */
+static char env_buf[MAX_VARS][320];
+static char* envp_arr[MAX_VARS + 1];
+
+static char** build_envp(void) {
+    int n = 0;
+    for (int i = 0; i < nvar && n < MAX_VARS; i++) {
+        if (!vars[i].exported) continue;
+        snprintf(env_buf[n], sizeof(env_buf[n]), "%s=%s",
+                 vars[i].name, vars[i].value);
+        envp_arr[n] = env_buf[n];
+        n++;
+    }
+    envp_arr[n] = NULL;
+    return envp_arr;
+}
+
+/* ---- Command history ---- */
+
+static char history[HIST_SIZE][LINE_MAX];
+static int hist_count = 0;
+static int hist_pos = 0;
+
+static void hist_add(const char* line) {
+    if (line[0] == '\0') return;
+    if (hist_count > 0 && strcmp(history[(hist_count - 1) % HIST_SIZE], line) == 0)
+        return;
+    strncpy(history[hist_count % HIST_SIZE], line, LINE_MAX - 1);
+    history[hist_count % HIST_SIZE][LINE_MAX - 1] = '\0';
+    hist_count++;
+}
+
+/* ---- Line editing ---- */
+
+static char line[LINE_MAX];
+
+static void term_write(const char* s, int n) {
+    write(STDOUT_FILENO, s, (size_t)n);
+}
+
+/* ---- Tab completion ---- */
+
+static int tab_complete(char* buf, int* p_pos, int* p_len) {
+    int pos = *p_pos;
+    int len = *p_len;
+
+    /* Find the start of the current word */
+    int wstart = pos;
+    while (wstart > 0 && buf[wstart - 1] != ' ' && buf[wstart - 1] != '\t')
+        wstart--;
+
+    char prefix[128];
+    int plen = pos - wstart;
+    if (plen <= 0 || plen >= (int)sizeof(prefix)) return 0;
+    memcpy(prefix, buf + wstart, (size_t)plen);
+    prefix[plen] = '\0';
+
+    /* Determine if this is a command (first word) or filename */
+    int is_cmd = 1;
+    for (int i = 0; i < wstart; i++) {
+        if (buf[i] != ' ' && buf[i] != '\t') { is_cmd = 0; break; }
+    }
+
+    char match[128];
+    match[0] = '\0';
+    int nmatches = 0;
+
+    /* Split prefix into directory part and name part for file completion */
+    char dirpath[128] = ".";
+    const char* namepfx = prefix;
+    char* lastsep = NULL;
+    for (char* p = prefix; *p; p++) {
+        if (*p == '/') lastsep = p;
+    }
+    if (lastsep) {
+        int dlen = (int)(lastsep - prefix);
+        if (dlen == 0) { dirpath[0] = '/'; dirpath[1] = '\0'; }
+        else { memcpy(dirpath, prefix, (size_t)dlen); dirpath[dlen] = '\0'; }
+        namepfx = lastsep + 1;
+    }
+    int nplen = (int)strlen(namepfx);
+
+    if (!is_cmd || lastsep) {
+        /* File/directory completion */
+        int fd = open(dirpath, 0);
+        if (fd >= 0) {
+            char dbuf[512];
+            int rc;
+            while ((rc = getdents(fd, dbuf, sizeof(dbuf))) > 0) {
+                int off = 0;
+                while (off < rc) {
+                    struct dirent* d = (struct dirent*)(dbuf + off);
+                    if (d->d_reclen == 0) break;
+                    if (d->d_name[0] != '.' || nplen > 0) {
+                        int nlen = (int)strlen(d->d_name);
+                        if (nlen >= nplen && memcmp(d->d_name, namepfx, (size_t)nplen) == 0) {
+                            if (nmatches == 0) strcpy(match, d->d_name);
+                            nmatches++;
+                        }
+                    }
+                    off += d->d_reclen;
+                }
+            }
+            close(fd);
+        }
+    }
+
+    if (is_cmd && !lastsep) {
+        /* Command completion: search PATH directories + builtins */
+        static const char* builtins[] = {
+            "cd", "exit", "echo", "export", "unset", "set", "pwd", "type", NULL
+        };
+        for (int i = 0; builtins[i]; i++) {
+            int blen = (int)strlen(builtins[i]);
+            if (blen >= plen && memcmp(builtins[i], prefix, (size_t)plen) == 0) {
+                if (nmatches == 0) strcpy(match, builtins[i]);
+                nmatches++;
+            }
+        }
+        const char* path_env = var_get("PATH");
+        if (!path_env) path_env = "/bin:/sbin:/usr/bin";
+        char pathcopy[512];
+        strncpy(pathcopy, path_env, sizeof(pathcopy) - 1);
+        pathcopy[sizeof(pathcopy) - 1] = '\0';
+        char* save = pathcopy;
+        char* dir;
+        while ((dir = save) != NULL) {
+            char* sep = strchr(save, ':');
+            if (sep) { *sep = '\0'; save = sep + 1; } else save = NULL;
+            int fd = open(dir, 0);
+            if (fd < 0) continue;
+            char dbuf[512];
+            int rc;
+            while ((rc = getdents(fd, dbuf, sizeof(dbuf))) > 0) {
+                int off = 0;
+                while (off < rc) {
+                    struct dirent* d = (struct dirent*)(dbuf + off);
+                    if (d->d_reclen == 0) break;
+                    int nlen = (int)strlen(d->d_name);
+                    if (nlen >= plen && memcmp(d->d_name, prefix, (size_t)plen) == 0) {
+                        if (nmatches == 0) strcpy(match, d->d_name);
+                        nmatches++;
+                    }
+                    off += d->d_reclen;
+                }
+            }
+            close(fd);
+        }
+    }
+
+    if (nmatches != 1) return 0;
+
+    /* Insert the completion suffix */
+    int mlen = (int)strlen(match);
+    int suffix_len = is_cmd && !lastsep ? mlen - plen : mlen - nplen;
+    const char* suffix = is_cmd && !lastsep ? match + plen : match + nplen;
+    if (suffix_len <= 0 || len + suffix_len >= LINE_MAX - 1) return 0;
+
+    memmove(buf + pos + suffix_len, buf + pos, (size_t)(len - pos));
+    memcpy(buf + pos, suffix, (size_t)suffix_len);
+    len += suffix_len;
+    buf[len] = '\0';
+    term_write(buf + pos, len - pos);
+    pos += suffix_len;
+    for (int i = 0; i < len - pos; i++) term_write("\b", 1);
+    *p_pos = pos;
+    *p_len = len;
+    return 1;
+}
+
+static int read_line_edit(void) {
+    int pos = 0;
+    int len = 0;
+    hist_pos = hist_count;
+
+    memset(line, 0, LINE_MAX);
+
+    while (len < LINE_MAX - 1) {
+        char c;
+        int r = read(STDIN_FILENO, &c, 1);
+        if (r <= 0) {
+            if (len == 0) return -1;
+            break;
+        }
+
+        if (c == '\n' || c == '\r') {
+            term_write("\n", 1);
+            break;
+        }
+
+        /* Backspace / DEL */
+        if (c == '\b' || c == 127) {
+            if (pos > 0) {
+                memmove(line + pos - 1, line + pos, (size_t)(len - pos));
+                pos--; len--;
+                line[len] = '\0';
+                /* Redraw: move cursor back, print rest, clear tail */
+                term_write("\b", 1);
+                term_write(line + pos, len - pos);
+                term_write(" \b", 2);
+                for (int i = 0; i < len - pos; i++) term_write("\b", 1);
+            }
+            continue;
+        }
+
+        /* Tab = autocomplete */
+        if (c == '\t') {
+            tab_complete(line, &pos, &len);
+            continue;
+        }
+
+        /* Ctrl+D = EOF */
+        if (c == 4) {
+            if (len == 0) return -1;
+            continue;
+        }
+
+        /* Ctrl+C = cancel line */
+        if (c == 3) {
+            term_write("^C\n", 3);
+            line[0] = '\0';
+            return 0;
+        }
+
+        /* Ctrl+Z = ignored at prompt (no foreground job to suspend) */
+        if (c == 26) {
+            continue;
+        }
+
+        /* Ctrl+A = beginning of line */
+        if (c == 1) {
+            while (pos > 0) { term_write("\b", 1); pos--; }
+            continue;
+        }
+
+        /* Ctrl+E = end of line */
+        if (c == 5) {
+            term_write(line + pos, len - pos);
+            pos = len;
+            continue;
+        }
+
+        /* Ctrl+U = clear line */
+        if (c == 21) {
+            while (pos > 0) { term_write("\b", 1); pos--; }
+            for (int i = 0; i < len; i++) term_write(" ", 1);
+            for (int i = 0; i < len; i++) term_write("\b", 1);
+            len = 0; pos = 0;
+            line[0] = '\0';
+            continue;
+        }
+
+        /* Escape sequences (arrow keys) */
+        if (c == 27) {
+            char seq[2];
+            if (read(STDIN_FILENO, &seq[0], 1) <= 0) continue;
+            if (seq[0] != '[') continue;
+            if (read(STDIN_FILENO, &seq[1], 1) <= 0) continue;
+
+            if (seq[1] >= '0' && seq[1] <= '9') {
+                /* Extended sequence like \x1b[3~ (DELETE), \x1b[1~ (Home), \x1b[4~ (End) */
+                char trail;
+                if (read(STDIN_FILENO, &trail, 1) <= 0) continue;
+                if (trail == '~') {
+                    if (seq[1] == '3') {
+                        /* DELETE key — delete char at cursor */
+                        if (pos < len) {
+                            memmove(line + pos, line + pos + 1, (size_t)(len - pos - 1));
+                            len--;
+                            line[len] = '\0';
+                            term_write(line + pos, len - pos);
+                            term_write(" \b", 2);
+                            for (int i = 0; i < len - pos; i++) term_write("\b", 1);
+                        }
+                    } else if (seq[1] == '1') {
+                        /* Home */
+                        while (pos > 0) { term_write("\b", 1); pos--; }
+                    } else if (seq[1] == '4') {
+                        /* End */
+                        term_write(line + pos, len - pos);
+                        pos = len;
+                    }
+                }
+                continue;
+            }
+
+            switch (seq[1]) {
+            case 'A':  /* Up arrow — previous history */
+                if (hist_pos > 0 && hist_pos > hist_count - HIST_SIZE) {
+                    hist_pos--;
+                    /* Clear current line */
+                    while (pos > 0) { term_write("\b", 1); pos--; }
+                    for (int i = 0; i < len; i++) term_write(" ", 1);
+                    for (int i = 0; i < len; i++) term_write("\b", 1);
+                    /* Load history entry */
+                    strcpy(line, history[hist_pos % HIST_SIZE]);
+                    len = (int)strlen(line);
+                    pos = len;
+                    term_write(line, len);
+                }
+                break;
+            case 'B':  /* Down arrow — next history */
+                if (hist_pos < hist_count) {
+                    hist_pos++;
+                    while (pos > 0) { term_write("\b", 1); pos--; }
+                    for (int i = 0; i < len; i++) term_write(" ", 1);
+                    for (int i = 0; i < len; i++) term_write("\b", 1);
+                    if (hist_pos < hist_count) {
+                        strcpy(line, history[hist_pos % HIST_SIZE]);
+                    } else {
+                        line[0] = '\0';
+                    }
+                    len = (int)strlen(line);
+                    pos = len;
+                    term_write(line, len);
+                }
+                break;
+            case 'C':  /* Right arrow */
+                if (pos < len) { term_write(line + pos, 1); pos++; }
+                break;
+            case 'D':  /* Left arrow */
+                if (pos > 0) { term_write("\b", 1); pos--; }
+                break;
+            case 'H':  /* Home */
+                while (pos > 0) { term_write("\b", 1); pos--; }
+                break;
+            case 'F':  /* End */
+                term_write(line + pos, len - pos);
+                pos = len;
+                break;
+            }
+            continue;
+        }
+
+        /* Normal printable character */
+        if (c >= ' ' && c <= '~') {
+            memmove(line + pos + 1, line + pos, (size_t)(len - pos));
+            line[pos] = c;
+            len++; line[len] = '\0';
+            term_write(line + pos, len - pos);
+            pos++;
+            for (int i = 0; i < len - pos; i++) term_write("\b", 1);
+        }
+    }
+
+    line[len] = '\0';
+    return len;
+}
+
+/* ---- Variable expansion ---- */
+
+static void expand_vars(const char* src, char* dst, int maxlen) {
+    int di = 0;
+    while (*src && di < maxlen - 1) {
+        if (*src == '$') {
+            src++;
+            if (*src == '?') {
+                di += snprintf(dst + di, (size_t)(maxlen - di), "%d", last_status);
+                src++;
+            } else if (*src == '{') {
+                src++;
+                char name[64];
+                int ni = 0;
+                while (*src && *src != '}' && ni < 63) name[ni++] = *src++;
+                name[ni] = '\0';
+                if (*src == '}') src++;
+                const char* val = var_get(name);
+                if (val) {
+                    int vl = (int)strlen(val);
+                    if (di + vl < maxlen) { memcpy(dst + di, val, (size_t)vl); di += vl; }
+                }
+            } else {
+                char name[64];
+                int ni = 0;
+                while ((*src >= 'A' && *src <= 'Z') || (*src >= 'a' && *src <= 'z') ||
+                       (*src >= '0' && *src <= '9') || *src == '_') {
+                    if (ni < 63) name[ni++] = *src;
+                    src++;
+                }
+                name[ni] = '\0';
+                const char* val = var_get(name);
+                if (val) {
+                    int vl = (int)strlen(val);
+                    if (di + vl < maxlen) { memcpy(dst + di, val, (size_t)vl); di += vl; }
+                }
+            }
+        } else {
+            dst[di++] = *src++;
+        }
+    }
+    dst[di] = '\0';
+}
+
+/* ---- Argument parsing with quote handling ---- */
+
+static int parse_args(char* cmd, char** argv, int max) {
+    int argc = 0;
+    char* p = cmd;
+    while (*p && argc < max - 1) {
+        while (*p == ' ' || *p == '\t') p++;
+        if (*p == '\0') break;
+
+        char* out = p;
+        argv[argc++] = out;
+
+        while (*p && *p != ' ' && *p != '\t') {
+            if (*p == '\'' ) {
+                p++;
+                while (*p && *p != '\'') *out++ = *p++;
+                if (*p == '\'') p++;
+            } else if (*p == '"') {
+                p++;
+                while (*p && *p != '"') *out++ = *p++;
+                if (*p == '"') p++;
+            } else {
+                *out++ = *p++;
+            }
+        }
+        if (*p) p++;
+        *out = '\0';
+    }
+    argv[argc] = NULL;
+    return argc;
+}
+
+/* ---- PATH resolution ---- */
+
+static char pathbuf[256];
+
+static const char* resolve(const char* cmd) {
+    if (cmd[0] == '/' || cmd[0] == '.') return cmd;
+
+    const char* path_env = var_get("PATH");
+    if (!path_env) path_env = "/bin:/sbin:/usr/bin";
+
+    char pathcopy[512];
+    strncpy(pathcopy, path_env, sizeof(pathcopy) - 1);
+    pathcopy[sizeof(pathcopy) - 1] = '\0';
+
+    char* save = pathcopy;
+    char* dir;
+    while ((dir = save) != NULL) {
+        char* sep = strchr(save, ':');
+        if (sep) { *sep = '\0'; save = sep + 1; }
+        else save = NULL;
+
+        snprintf(pathbuf, sizeof(pathbuf), "%s/%s", dir, cmd);
+        if (access(pathbuf, 0) == 0) return pathbuf;
+    }
+    return cmd;
+}
+
+/* ---- Helper: set foreground process group on controlling TTY ---- */
+
+static void set_fg_pgrp(int pgrp) {
+    ioctl(STDIN_FILENO, TIOCSPGRP, &pgrp);
+}
+
+/* ---- Run a single simple command ---- */
+
+static int bg_flag = 0;  /* set by caller when trailing & detected */
+
+static void run_simple(char* cmd) {
+    /* Expand variables */
+    char expanded[LINE_MAX];
+    expand_vars(cmd, expanded, LINE_MAX);
+
+    char* argv[MAX_ARGS];
+    int argc = parse_args(expanded, argv, MAX_ARGS);
+    if (argc == 0) return;
+
+    /* Check for variable assignment (no command, just VAR=value) */
+    if (argc == 1 && strchr(argv[0], '=') != NULL) {
+        char* eq = strchr(argv[0], '=');
+        *eq = '\0';
+        var_set(argv[0], eq + 1, 0);
+        last_status = 0;
+        return;
+    }
+
+    /* Extract redirections */
+    char* redir_out = NULL;
+    char* redir_in  = NULL;
+    int   append = 0;
+    int   heredoc_fd = -1;
+    int nargc = 0;
+    for (int i = 0; i < argc; i++) {
+        if (strcmp(argv[i], ">>") == 0 && i + 1 < argc) {
+            redir_out = argv[++i]; append = 1;
+        } else if (strcmp(argv[i], ">") == 0 && i + 1 < argc) {
+            redir_out = argv[++i]; append = 0;
+        } else if (strcmp(argv[i], "<<") == 0 && i + 1 < argc) {
+            char* delim = argv[++i];
+            int dlen = (int)strlen(delim);
+            if (dlen > 0 && (delim[0] == '"' || delim[0] == '\'')) {
+                delim++; dlen -= 2; if (dlen < 0) dlen = 0;
+                delim[dlen] = '\0';
+            }
+            int pfd[2];
+            if (pipe(pfd) == 0) {
+                tty_restore();
+                char hline[LINE_MAX];
+                while (1) {
+                    write(STDOUT_FILENO, "> ", 2);
+                    int hi = 0;
+                    char hc;
+                    while (read(STDIN_FILENO, &hc, 1) == 1) {
+                        if (hc == '\n') break;
+                        if (hi < LINE_MAX - 1) hline[hi++] = hc;
+                    }
+                    hline[hi] = '\0';
+                    if (strcmp(hline, delim) == 0) break;
+                    write(pfd[1], hline, hi);
+                    write(pfd[1], "\n", 1);
+                }
+                close(pfd[1]);
+                heredoc_fd = pfd[0];
+                tty_raw_mode();
+            }
+        } else if (strcmp(argv[i], "<") == 0 && i + 1 < argc) {
+            redir_in = argv[++i];
+        } else {
+            argv[nargc++] = argv[i];
+        }
+    }
+    argv[nargc] = NULL;
+    argc = nargc;
+    if (argc == 0) return;
+
+    /* ---- Apply redirections for builtins too ---- */
+    int saved_stdin = -1, saved_stdout = -1;
+    if (heredoc_fd >= 0) {
+        saved_stdin = dup(0); dup2(heredoc_fd, 0); close(heredoc_fd); heredoc_fd = -1;
+    } else if (redir_in) {
+        int fd = open(redir_in, O_RDONLY);
+        if (fd >= 0) { saved_stdin = dup(0); dup2(fd, 0); close(fd); }
+    }
+    if (redir_out) {
+        int flags = O_WRONLY | O_CREAT;
+        flags |= append ? O_APPEND : O_TRUNC;
+        int fd = open(redir_out, flags);
+        if (fd >= 0) { saved_stdout = dup(1); dup2(fd, 1); close(fd); }
+    }
+
+    /* ---- Builtins ---- */
+
+    if (strcmp(argv[0], "exit") == 0) {
+        int code = argc > 1 ? atoi(argv[1]) : last_status;
+        exit(code);
+    }
+
+    if (strcmp(argv[0], "cd") == 0) {
+        const char* dir = argc > 1 ? argv[1] : var_get("HOME");
+        if (!dir) dir = "/";
+        if (chdir(dir) < 0)
+            fprintf(stderr, "cd: %s: No such file or directory\n", dir);
+        else {
+            char cwd[256];
+            if (getcwd(cwd, sizeof(cwd)) >= 0)
+                var_set("PWD", cwd, 1);
+        }
+        goto restore_redir;
+    }
+
+    if (strcmp(argv[0], "pwd") == 0) {
+        char cwd[256];
+        if (getcwd(cwd, sizeof(cwd)) >= 0)
+            printf("%s\n", cwd);
+        else
+            fprintf(stderr, "pwd: error\n");
+        goto restore_redir;
+    }
+
+    if (strcmp(argv[0], "export") == 0) {
+        for (int i = 1; i < argc; i++) {
+            char* eq = strchr(argv[i], '=');
+            if (eq) {
+                *eq = '\0';
+                var_set(argv[i], eq + 1, 1);
+            } else {
+                /* Export existing variable */
+                for (int j = 0; j < nvar; j++)
+                    if (strcmp(vars[j].name, argv[i]) == 0)
+                        vars[j].exported = 1;
+            }
+        }
+        goto restore_redir;
+    }
+
+    if (strcmp(argv[0], "unset") == 0) {
+        for (int i = 1; i < argc; i++) var_unset(argv[i]);
+        goto restore_redir;
+    }
+
+    if (strcmp(argv[0], "set") == 0) {
+        for (int i = 0; i < nvar; i++)
+            printf("%s=%s\n", vars[i].name, vars[i].value);
+        goto restore_redir;
+    }
+
+    if (strcmp(argv[0], "echo") == 0) {
+        int nflag = 0;
+        int start = 1;
+        if (argc > 1 && strcmp(argv[1], "-n") == 0) { nflag = 1; start = 2; }
+        for (int i = start; i < argc; i++) {
+            if (i > start) write(STDOUT_FILENO, " ", 1);
+            write(STDOUT_FILENO, argv[i], strlen(argv[i]));
+        }
+        if (!nflag) write(STDOUT_FILENO, "\n", 1);
+        goto restore_redir;
+    }
+
+    if (strcmp(argv[0], "type") == 0) {
+        for (int i = 1; i < argc; i++) {
+            if (strcmp(argv[i], "cd") == 0 || strcmp(argv[i], "exit") == 0 ||
+                strcmp(argv[i], "echo") == 0 || strcmp(argv[i], "export") == 0 ||
+                strcmp(argv[i], "unset") == 0 || strcmp(argv[i], "set") == 0 ||
+                strcmp(argv[i], "pwd") == 0 || strcmp(argv[i], "type") == 0) {
+                printf("%s is a shell builtin\n", argv[i]);
+            } else {
+                const char* path = resolve(argv[i]);
+                if (strcmp(path, argv[i]) != 0)
+                    printf("%s is %s\n", argv[i], path);
+                else
+                    printf("%s: not found\n", argv[i]);
+            }
+        }
+        goto restore_redir;
+    }
+
+    /* ---- External command — restore parent redirections before fork ---- */
+    const char* path = resolve(argv[0]);
+    char** envp = build_envp();
+
+    int pid = fork();
+    if (pid < 0) { fprintf(stderr, "sh: fork failed\n"); return; }
+
+    if (pid == 0) {
+        /* child: own process group, restore default signals */
+        setpgid(0, 0);
+        struct sigaction sa;
+        memset(&sa, 0, sizeof(sa));
+        sa.sa_handler = (uintptr_t)SIG_DFL;
+        sigaction(SIGINT, &sa, NULL);
+        sigaction(SIGTSTP, &sa, NULL);
+        sigaction(SIGQUIT, &sa, NULL);
+
+        if (redir_in) {
+            int fd = open(redir_in, O_RDONLY);
+            if (fd >= 0) { dup2(fd, 0); close(fd); }
+        }
+        if (redir_out) {
+            int flags = O_WRONLY | O_CREAT;
+            flags |= append ? O_APPEND : O_TRUNC;
+            int fd = open(redir_out, flags);
+            if (fd >= 0) { dup2(fd, 1); close(fd); }
+        }
+        execve(path, (const char* const*)argv, (const char* const*)envp);
+        fprintf(stderr, "sh: %s: not found\n", argv[0]);
+        _exit(127);
+    }
+
+    /* parent */
+    setpgid(pid, pid);
+
+    if (bg_flag) {
+        /* Background: don't wait, print job info */
+        printf("[bg] %d\n", pid);
+        last_status = 0;
+    } else {
+        /* Foreground: make child the fg process group, wait, then restore */
+        set_fg_pgrp(pid);
+        int st;
+        waitpid(pid, &st, 0);
+        set_fg_pgrp(getpgrp());
+        last_status = st;
+    }
+    goto restore_redir;
+
+restore_redir:
+    if (saved_stdout >= 0) { dup2(saved_stdout, 1); close(saved_stdout); }
+    if (saved_stdin >= 0)  { dup2(saved_stdin, 0);  close(saved_stdin);  }
+}
+
+/* ---- Pipeline support ---- */
+
+static void run_pipeline(char* cmdline) {
+    /* Split on '|' (outside quotes) */
+    char* cmds[8];
+    int ncmds = 0;
+    cmds[0] = cmdline;
+    int in_sq = 0, in_dq = 0;
+    for (char* p = cmdline; *p; p++) {
+        if (*p == '\'' && !in_dq) in_sq = !in_sq;
+        else if (*p == '"' && !in_sq) in_dq = !in_dq;
+        else if (*p == '|' && !in_sq && !in_dq && ncmds < 7) {
+            if (*(p + 1) == '|') { p++; continue; }  /* skip || */
+            *p = '\0';
+            cmds[++ncmds] = p + 1;
+        }
+    }
+    ncmds++;
+
+    if (ncmds == 1) {
+        run_simple(cmds[0]);
+        return;
+    }
+
+    /* Multi-stage pipeline */
+    int prev_rd = -1;
+    int pids[8];
+    int pgid = 0;  /* pipeline process group = first child's PID */
+
+    for (int i = 0; i < ncmds; i++) {
+        int pfd[2] = {-1, -1};
+        if (i < ncmds - 1) {
+            if (pipe(pfd) < 0) {
+                fprintf(stderr, "sh: pipe failed\n");
+                return;
+            }
+        }
+
+        pids[i] = fork();
+        if (pids[i] < 0) { fprintf(stderr, "sh: fork failed\n"); return; }
+
+        if (pids[i] == 0) {
+            /* child: join pipeline process group, restore signals */
+            int mypgid = pgid ? pgid : getpid();
+            setpgid(0, mypgid);
+            struct sigaction sa;
+            memset(&sa, 0, sizeof(sa));
+            sa.sa_handler = (uintptr_t)SIG_DFL;
+            sigaction(SIGINT, &sa, NULL);
+            sigaction(SIGTSTP, &sa, NULL);
+            sigaction(SIGQUIT, &sa, NULL);
+
+            if (prev_rd >= 0) { dup2(prev_rd, 0); close(prev_rd); }
+            if (pfd[1] >= 0)  { dup2(pfd[1], 1); close(pfd[1]); }
+            if (pfd[0] >= 0)  close(pfd[0]);
+
+            /* Expand and parse this pipeline stage */
+            char expanded[LINE_MAX];
+            expand_vars(cmds[i], expanded, LINE_MAX);
+            char* argv[MAX_ARGS];
+            int argc = parse_args(expanded, argv, MAX_ARGS);
+            if (argc == 0) _exit(0);
+            const char* path = resolve(argv[0]);
+            char** envp = build_envp();
+            execve(path, (const char* const*)argv, (const char* const*)envp);
+            fprintf(stderr, "sh: %s: not found\n", argv[0]);
+            _exit(127);
+        }
+
+        /* parent: set pipeline pgid */
+        if (i == 0) pgid = pids[0];
+        setpgid(pids[i], pgid);
+
+        if (prev_rd >= 0) close(prev_rd);
+        if (pfd[1] >= 0)  close(pfd[1]);
+        prev_rd = pfd[0];
+    }
+
+    if (prev_rd >= 0) close(prev_rd);
+
+    if (!bg_flag) {
+        /* Foreground pipeline: make it fg, wait, restore */
+        set_fg_pgrp(pgid);
+        for (int i = 0; i < ncmds; i++) {
+            int st;
+            waitpid(pids[i], &st, 0);
+            if (i == ncmds - 1) last_status = st;
+        }
+        set_fg_pgrp(getpgrp());
+    } else {
+        printf("[bg] %d\n", pgid);
+        last_status = 0;
+    }
+}
+
+/* ---- Process a command line (handle ;, &&, ||, &) ---- */
+
+enum { OP_NONE = 0, OP_SEMI, OP_AND, OP_OR, OP_BG };
+
+static void process_line(char* input) {
+    char* p = input;
+
+    while (*p) {
+        while (*p == ' ' || *p == '\t') p++;
+        if (*p == '\0') break;
+
+        /* Find the next operator outside quotes */
+        char* start = p;
+        int in_sq = 0, in_dq = 0;
+        int op = OP_NONE;
+
+        while (*p) {
+            if (*p == '\'' && !in_dq) { in_sq = !in_sq; p++; continue; }
+            if (*p == '"' && !in_sq)  { in_dq = !in_dq; p++; continue; }
+            if (in_sq || in_dq) { p++; continue; }
+
+            if (*p == '&' && *(p + 1) == '&') {
+                *p = '\0'; p += 2; op = OP_AND; break;
+            }
+            if (*p == '|' && *(p + 1) == '|') {
+                *p = '\0'; p += 2; op = OP_OR; break;
+            }
+            if (*p == ';') {
+                *p = '\0'; p++; op = OP_SEMI; break;
+            }
+            if (*p == '&') {
+                *p = '\0'; p++; op = OP_BG; break;
+            }
+            p++;
+        }
+
+        /* Trim leading whitespace from segment */
+        while (*start == ' ' || *start == '\t') start++;
+        if (*start != '\0') {
+            if (op == OP_BG) {
+                bg_flag = 1;
+                run_pipeline(start);
+                bg_flag = 0;
+            } else {
+                bg_flag = 0;
+                run_pipeline(start);
+            }
+        }
+
+        /* For &&: skip remaining commands if last failed */
+        if (op == OP_AND && last_status != 0) {
+            /* Skip until we hit || or ; or & or end */
+            while (*p) {
+                while (*p == ' ' || *p == '\t') p++;
+                if (*p == '\0') break;
+                int skip_sq = 0, skip_dq = 0;
+                while (*p) {
+                    if (*p == '\'' && !skip_dq) { skip_sq = !skip_sq; p++; continue; }
+                    if (*p == '"' && !skip_sq)  { skip_dq = !skip_dq; p++; continue; }
+                    if (skip_sq || skip_dq) { p++; continue; }
+                    if (*p == '|' && *(p + 1) == '|') break;
+                    if (*p == ';') break;
+                    if (*p == '&' && *(p + 1) != '&') break;
+                    if (*p == '&' && *(p + 1) == '&') { p += 2; continue; }
+                    p++;
+                }
+                break;
+            }
+        }
+
+        /* For ||: skip remaining commands if last succeeded */
+        if (op == OP_OR && last_status == 0) {
+            while (*p) {
+                while (*p == ' ' || *p == '\t') p++;
+                if (*p == '\0') break;
+                int skip_sq = 0, skip_dq = 0;
+                while (*p) {
+                    if (*p == '\'' && !skip_dq) { skip_sq = !skip_sq; p++; continue; }
+                    if (*p == '"' && !skip_sq)  { skip_dq = !skip_dq; p++; continue; }
+                    if (skip_sq || skip_dq) { p++; continue; }
+                    if (*p == '&' && *(p + 1) == '&') break;
+                    if (*p == ';') break;
+                    if (*p == '&' && *(p + 1) != '&') break;
+                    if (*p == '|' && *(p + 1) == '|') { p += 2; continue; }
+                    p++;
+                }
+                break;
+            }
+        }
+    }
+}
+
+/* ---- Prompt ---- */
+
+static void print_prompt(void) {
+    const char* user = var_get("USER");
+    const char* host = var_get("HOSTNAME");
+    char cwd[256];
+
+    if (!user) user = "root";
+    if (!host) host = "adros";
+
+    if (getcwd(cwd, sizeof(cwd)) < 0) strcpy(cwd, "?");
+
+    printf("%s@%s:%s$ ", user, host, cwd);
+    fflush(stdout);
+}
+
+/* ---- Main ---- */
+
+int main(int argc, char** argv, char** envp) {
+    (void)argc;
+    (void)argv;
+
+    /* Import environment variables */
+    if (envp) {
+        for (int i = 0; envp[i]; i++) {
+            char* eq = strchr(envp[i], '=');
+            if (eq) {
+                char name[64];
+                int nlen = (int)(eq - envp[i]);
+                if (nlen > 63) nlen = 63;
+                memcpy(name, envp[i], (size_t)nlen);
+                name[nlen] = '\0';
+                var_set(name, eq + 1, 1);
+            }
+        }
+    }
+
+    /* Set defaults */
+    if (!var_get("PATH"))
+        var_set("PATH", "/bin:/sbin:/usr/bin", 1);
+    if (!var_get("HOME"))
+        var_set("HOME", "/", 1);
+
+    /* Job control: create session + process group, become fg */
+    setsid();
+    set_fg_pgrp(getpgrp());
+
+    /* Ignore job control signals in the shell itself */
+    struct sigaction sa_ign;
+    memset(&sa_ign, 0, sizeof(sa_ign));
+    sa_ign.sa_handler = (uintptr_t)SIG_IGN;
+    sigaction(SIGINT, &sa_ign, NULL);
+    sigaction(SIGTSTP, &sa_ign, NULL);
+    sigaction(SIGQUIT, &sa_ign, NULL);
+
+    tty_raw_mode();
+
+    print_prompt();
+    while (1) {
+        int len = read_line_edit();
+        if (len < 0) break;
+        if (len > 0) {
+            hist_add(line);
+            tty_restore();
+            process_line(line);
+            tty_raw_mode();
+        }
+        print_prompt();
+    }
+
+    tty_restore();
+    return last_status;
+}
diff --git a/user/cmds/sleep/Makefile b/user/cmds/sleep/Makefile
new file mode 100644 (file)
index 0000000..258ba49
--- /dev/null
@@ -0,0 +1,3 @@
+NAME := sleep
+SRCS := sleep.c
+include ../common.mk
diff --git a/user/cmds/sleep/sleep.c b/user/cmds/sleep/sleep.c
new file mode 100644 (file)
index 0000000..9da1be1
--- /dev/null
@@ -0,0 +1,18 @@
+/* AdrOS sleep utility — pause for N seconds */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <time.h>
+
+int main(int argc, char** argv) {
+    if (argc <= 1) {
+        fprintf(stderr, "usage: sleep SECONDS\n");
+        return 1;
+    }
+    int secs = atoi(argv[1]);
+    if (secs > 0) {
+        struct timespec ts = { .tv_sec = secs, .tv_nsec = 0 };
+        nanosleep(&ts, NULL);
+    }
+    return 0;
+}
diff --git a/user/cmds/sort/Makefile b/user/cmds/sort/Makefile
new file mode 100644 (file)
index 0000000..b9147af
--- /dev/null
@@ -0,0 +1,3 @@
+NAME := sort
+SRCS := sort.c
+include ../common.mk
diff --git a/user/cmds/sort/sort.c b/user/cmds/sort/sort.c
new file mode 100644 (file)
index 0000000..70cc61d
--- /dev/null
@@ -0,0 +1,91 @@
+/* AdrOS sort utility */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#define MAX_LINES 1024
+#define LINE_BUF  65536
+
+static char linebuf[LINE_BUF];
+static char* lines[MAX_LINES];
+static int nlines = 0;
+
+static int rflag = 0;  /* -r: reverse */
+static int nflag = 0;  /* -n: numeric */
+
+static int cmp(const void* a, const void* b) {
+    const char* sa = *(const char**)a;
+    const char* sb = *(const char**)b;
+    int r;
+    if (nflag) {
+        r = atoi(sa) - atoi(sb);
+    } else {
+        r = strcmp(sa, sb);
+    }
+    return rflag ? -r : r;
+}
+
+static void read_lines(int fd) {
+    int total = 0;
+    int r;
+    while ((r = read(fd, linebuf + total, (size_t)(LINE_BUF - total - 1))) > 0) {
+        total += r;
+        if (total >= LINE_BUF - 1) break;
+    }
+    linebuf[total] = '\0';
+
+    /* Split into lines */
+    char* p = linebuf;
+    while (*p && nlines < MAX_LINES) {
+        lines[nlines++] = p;
+        while (*p && *p != '\n') p++;
+        if (*p == '\n') *p++ = '\0';
+    }
+}
+
+int main(int argc, char** argv) {
+    int start = 1;
+    for (int i = 1; i < argc; i++) {
+        if (argv[i][0] == '-') {
+            const char* f = argv[i] + 1;
+            while (*f) {
+                if (*f == 'r') rflag = 1;
+                else if (*f == 'n') nflag = 1;
+                f++;
+            }
+            start = i + 1;
+        } else break;
+    }
+
+    if (start >= argc) {
+        read_lines(STDIN_FILENO);
+    } else {
+        for (int i = start; i < argc; i++) {
+            int fd = open(argv[i], O_RDONLY);
+            if (fd < 0) {
+                fprintf(stderr, "sort: cannot open '%s'\n", argv[i]);
+                return 1;
+            }
+            read_lines(fd);
+            close(fd);
+        }
+    }
+
+    /* Simple insertion sort (no qsort in ulibc yet) */
+    for (int i = 1; i < nlines; i++) {
+        char* key = lines[i];
+        int j = i - 1;
+        while (j >= 0 && cmp(&lines[j], &key) > 0) {
+            lines[j + 1] = lines[j];
+            j--;
+        }
+        lines[j + 1] = key;
+    }
+
+    for (int i = 0; i < nlines; i++)
+        printf("%s\n", lines[i]);
+
+    return 0;
+}
diff --git a/user/cmds/stat/Makefile b/user/cmds/stat/Makefile
new file mode 100644 (file)
index 0000000..ca917a0
--- /dev/null
@@ -0,0 +1,3 @@
+NAME := stat
+SRCS := stat.c
+include ../common.mk
diff --git a/user/cmds/stat/stat.c b/user/cmds/stat/stat.c
new file mode 100644 (file)
index 0000000..7083d29
--- /dev/null
@@ -0,0 +1,25 @@
+/* AdrOS stat utility — display file status */
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+int main(int argc, char** argv) {
+    if (argc <= 1) {
+        fprintf(stderr, "usage: stat FILE...\n");
+        return 1;
+    }
+    int rc = 0;
+    for (int i = 1; i < argc; i++) {
+        struct stat st;
+        if (stat(argv[i], (void*)&st) < 0) {
+            fprintf(stderr, "stat: cannot stat '%s'\n", argv[i]);
+            rc = 1;
+            continue;
+        }
+        printf("  File: %s\n", argv[i]);
+        printf("  Size: %u\tInode: %u\n", (unsigned)st.st_size, (unsigned)st.st_ino);
+        printf("  Mode: %o\tUid: %u\tGid: %u\n", (unsigned)st.st_mode, (unsigned)st.st_uid, (unsigned)st.st_gid);
+    }
+    return rc;
+}
diff --git a/user/cmds/tail/Makefile b/user/cmds/tail/Makefile
new file mode 100644 (file)
index 0000000..aeed773
--- /dev/null
@@ -0,0 +1,3 @@
+NAME := tail
+SRCS := tail.c
+include ../common.mk
diff --git a/user/cmds/tail/tail.c b/user/cmds/tail/tail.c
new file mode 100644 (file)
index 0000000..200e78e
--- /dev/null
@@ -0,0 +1,60 @@
+/* AdrOS tail utility */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+
+#define TAIL_BUFSZ 8192
+
+static void tail_fd(int fd, int nlines) {
+    /* Read entire file into buffer, then print last N lines */
+    char buf[TAIL_BUFSZ];
+    int total = 0;
+    int r;
+    while ((r = read(fd, buf + total, (size_t)(TAIL_BUFSZ - total))) > 0) {
+        total += r;
+        if (total >= TAIL_BUFSZ) break;
+    }
+
+    /* Count newlines from end; skip trailing newline */
+    int count = 0;
+    int pos = total;
+    if (pos > 0 && buf[pos - 1] == '\n') pos--;
+    while (pos > 0 && count < nlines) {
+        pos--;
+        if (buf[pos] == '\n') count++;
+    }
+    if (pos > 0 || (pos == 0 && buf[0] == '\n')) pos++;
+
+    write(STDOUT_FILENO, buf + pos, (size_t)(total - pos));
+}
+
+int main(int argc, char** argv) {
+    int nlines = 10;
+    int start = 1;
+
+    if (argc > 1 && argv[1][0] == '-' && argv[1][1] == 'n' && argc > 2) {
+        nlines = atoi(argv[2]);
+        start = 3;
+    } else if (argc > 1 && argv[1][0] == '-' && argv[1][1] >= '0' && argv[1][1] <= '9') {
+        nlines = atoi(argv[1] + 1);
+        start = 2;
+    }
+
+    if (start >= argc) {
+        tail_fd(STDIN_FILENO, nlines);
+    } else {
+        for (int i = start; i < argc; i++) {
+            if (argc - start > 1) printf("==> %s <==\n", argv[i]);
+            int fd = open(argv[i], O_RDONLY);
+            if (fd < 0) {
+                fprintf(stderr, "tail: cannot open '%s'\n", argv[i]);
+                continue;
+            }
+            tail_fd(fd, nlines);
+            close(fd);
+        }
+    }
+    return 0;
+}
diff --git a/user/cmds/tee/Makefile b/user/cmds/tee/Makefile
new file mode 100644 (file)
index 0000000..f387f18
--- /dev/null
@@ -0,0 +1,3 @@
+NAME := tee
+SRCS := tee.c
+include ../common.mk
diff --git a/user/cmds/tee/tee.c b/user/cmds/tee/tee.c
new file mode 100644 (file)
index 0000000..52a32c4
--- /dev/null
@@ -0,0 +1,34 @@
+/* AdrOS tee utility — read stdin, write to stdout and files */
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+int main(int argc, char** argv) {
+    int aflag = 0;
+    int fds[16];
+    int nfds = 0;
+
+    for (int i = 1; i < argc && nfds < 16; i++) {
+        if (strcmp(argv[i], "-a") == 0) { aflag = 1; continue; }
+        int flags = O_WRONLY | O_CREAT;
+        flags |= aflag ? O_APPEND : O_TRUNC;
+        int fd = open(argv[i], flags, 0644);
+        if (fd < 0) {
+            fprintf(stderr, "tee: %s: cannot open\n", argv[i]);
+            continue;
+        }
+        fds[nfds++] = fd;
+    }
+
+    char buf[4096];
+    int n;
+    while ((n = read(STDIN_FILENO, buf, sizeof(buf))) > 0) {
+        write(STDOUT_FILENO, buf, (size_t)n);
+        for (int i = 0; i < nfds; i++)
+            write(fds[i], buf, (size_t)n);
+    }
+
+    for (int i = 0; i < nfds; i++) close(fds[i]);
+    return 0;
+}
diff --git a/user/cmds/top/Makefile b/user/cmds/top/Makefile
new file mode 100644 (file)
index 0000000..841c26f
--- /dev/null
@@ -0,0 +1,3 @@
+NAME := top
+SRCS := top.c
+include ../common.mk
diff --git a/user/cmds/top/top.c b/user/cmds/top/top.c
new file mode 100644 (file)
index 0000000..59995e8
--- /dev/null
@@ -0,0 +1,69 @@
+/* AdrOS top utility — one-shot process listing with basic info */
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <dirent.h>
+
+static int is_digit(char c) { return c >= '0' && c <= '9'; }
+
+int main(void) {
+    printf("  PID  STATE CMD\n");
+    int fd = open("/proc", O_RDONLY);
+    if (fd < 0) {
+        fprintf(stderr, "top: cannot open /proc\n");
+        return 1;
+    }
+    char buf[512];
+    int rc;
+    while ((rc = getdents(fd, buf, sizeof(buf))) > 0) {
+        int off = 0;
+        while (off < rc) {
+            struct dirent* d = (struct dirent*)(buf + off);
+            if (d->d_reclen == 0) break;
+            if (is_digit(d->d_name[0])) {
+                char path[64];
+
+                /* Read cmdline */
+                snprintf(path, sizeof(path), "/proc/%s/cmdline", d->d_name);
+                int cfd = open(path, O_RDONLY);
+                char cmd[64] = "[kernel]";
+                if (cfd >= 0) {
+                    int n = read(cfd, cmd, sizeof(cmd) - 1);
+                    if (n > 0) {
+                        cmd[n] = '\0';
+                        while (n > 0 && (cmd[n-1] == '\n' || cmd[n-1] == '\0')) cmd[--n] = '\0';
+                    }
+                    if (n <= 0) strcpy(cmd, "[kernel]");
+                    close(cfd);
+                }
+
+                /* Read status for state */
+                snprintf(path, sizeof(path), "/proc/%s/status", d->d_name);
+                int sfd = open(path, O_RDONLY);
+                char state[16] = "?";
+                if (sfd >= 0) {
+                    char sbuf[256];
+                    int sn = read(sfd, sbuf, sizeof(sbuf) - 1);
+                    if (sn > 0) {
+                        sbuf[sn] = '\0';
+                        char* st = strstr(sbuf, "State:");
+                        if (st) {
+                            st += 6;
+                            while (*st == ' ' || *st == '\t') st++;
+                            int si = 0;
+                            while (*st && *st != '\n' && si < 15) state[si++] = *st++;
+                            state[si] = '\0';
+                        }
+                    }
+                    close(sfd);
+                }
+
+                printf("%5s %6s %s\n", d->d_name, state, cmd);
+            }
+            off += d->d_reclen;
+        }
+    }
+    close(fd);
+    return 0;
+}
diff --git a/user/cmds/touch/Makefile b/user/cmds/touch/Makefile
new file mode 100644 (file)
index 0000000..595e195
--- /dev/null
@@ -0,0 +1,3 @@
+NAME := touch
+SRCS := touch.c
+include ../common.mk
diff --git a/user/cmds/touch/touch.c b/user/cmds/touch/touch.c
new file mode 100644 (file)
index 0000000..3e2da84
--- /dev/null
@@ -0,0 +1,23 @@
+/* AdrOS touch utility */
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+int main(int argc, char** argv) {
+    if (argc < 2) {
+        fprintf(stderr, "Usage: touch <file>...\n");
+        return 1;
+    }
+
+    int rc = 0;
+    for (int i = 1; i < argc; i++) {
+        int fd = open(argv[i], O_WRONLY | O_CREAT, 0644);
+        if (fd < 0) {
+            fprintf(stderr, "touch: cannot touch '%s'\n", argv[i]);
+            rc = 1;
+        } else {
+            close(fd);
+        }
+    }
+    return rc;
+}
diff --git a/user/cmds/tr/Makefile b/user/cmds/tr/Makefile
new file mode 100644 (file)
index 0000000..2a49dd3
--- /dev/null
@@ -0,0 +1,3 @@
+NAME := tr
+SRCS := tr.c
+include ../common.mk
diff --git a/user/cmds/tr/tr.c b/user/cmds/tr/tr.c
new file mode 100644 (file)
index 0000000..cf7606f
--- /dev/null
@@ -0,0 +1,44 @@
+/* AdrOS tr utility — translate or delete characters */
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+int main(int argc, char** argv) {
+    int delete_mode = 0;
+    int start = 1;
+
+    if (argc > 1 && strcmp(argv[1], "-d") == 0) {
+        delete_mode = 1;
+        start = 2;
+    }
+
+    if (delete_mode) {
+        if (start >= argc) { fprintf(stderr, "usage: tr -d SET1\n"); return 1; }
+        const char* set1 = argv[start];
+        char c;
+        while (read(STDIN_FILENO, &c, 1) > 0) {
+            if (!strchr(set1, c))
+                write(STDOUT_FILENO, &c, 1);
+        }
+    } else {
+        if (start + 1 >= argc) { fprintf(stderr, "usage: tr SET1 SET2\n"); return 1; }
+        const char* set1 = argv[start];
+        const char* set2 = argv[start + 1];
+        int len1 = (int)strlen(set1);
+        int len2 = (int)strlen(set2);
+        char c;
+        while (read(STDIN_FILENO, &c, 1) > 0) {
+            int found = 0;
+            for (int i = 0; i < len1; i++) {
+                if (c == set1[i]) {
+                    char r = (i < len2) ? set2[i] : set2[len2 - 1];
+                    write(STDOUT_FILENO, &r, 1);
+                    found = 1;
+                    break;
+                }
+            }
+            if (!found) write(STDOUT_FILENO, &c, 1);
+        }
+    }
+    return 0;
+}
diff --git a/user/cmds/umount/Makefile b/user/cmds/umount/Makefile
new file mode 100644 (file)
index 0000000..cee0a75
--- /dev/null
@@ -0,0 +1,3 @@
+NAME := umount
+SRCS := umount.c
+include ../common.mk
diff --git a/user/cmds/umount/umount.c b/user/cmds/umount/umount.c
new file mode 100644 (file)
index 0000000..1396c94
--- /dev/null
@@ -0,0 +1,11 @@
+/* AdrOS umount utility — stub (no SYS_UMOUNT syscall yet) */
+#include <stdio.h>
+
+int main(int argc, char** argv) {
+    if (argc <= 1) {
+        fprintf(stderr, "umount: missing operand\n");
+        return 1;
+    }
+    fprintf(stderr, "umount: %s: operation not supported\n", argv[1]);
+    return 1;
+}
diff --git a/user/cmds/uname/Makefile b/user/cmds/uname/Makefile
new file mode 100644 (file)
index 0000000..ca5558e
--- /dev/null
@@ -0,0 +1,3 @@
+NAME := uname
+SRCS := uname.c
+include ../common.mk
diff --git a/user/cmds/uname/uname.c b/user/cmds/uname/uname.c
new file mode 100644 (file)
index 0000000..250fe05
--- /dev/null
@@ -0,0 +1,34 @@
+/* AdrOS uname utility — print system information */
+#include <stdio.h>
+#include <string.h>
+
+int main(int argc, char** argv) {
+    const char* sysname = "AdrOS";
+    const char* nodename = "adros";
+    const char* release = "0.1.0";
+    const char* version = "AdrOS x86 SMP";
+    const char* machine = "i686";
+
+    if (argc <= 1) { printf("%s\n", sysname); return 0; }
+
+    int all = 0, s = 0, n = 0, r = 0, v = 0, m = 0;
+    for (int i = 1; i < argc; i++) {
+        if (strcmp(argv[i], "-a") == 0) all = 1;
+        else if (strcmp(argv[i], "-s") == 0) s = 1;
+        else if (strcmp(argv[i], "-n") == 0) n = 1;
+        else if (strcmp(argv[i], "-r") == 0) r = 1;
+        else if (strcmp(argv[i], "-v") == 0) v = 1;
+        else if (strcmp(argv[i], "-m") == 0) m = 1;
+    }
+    if (all) { s = n = r = v = m = 1; }
+    if (!s && !n && !r && !v && !m) s = 1;
+
+    int first = 1;
+    if (s) { printf("%s%s", first ? "" : " ", sysname); first = 0; }
+    if (n) { printf("%s%s", first ? "" : " ", nodename); first = 0; }
+    if (r) { printf("%s%s", first ? "" : " ", release); first = 0; }
+    if (v) { printf("%s%s", first ? "" : " ", version); first = 0; }
+    if (m) { printf("%s%s", first ? "" : " ", machine); first = 0; }
+    printf("\n");
+    return 0;
+}
diff --git a/user/cmds/uniq/Makefile b/user/cmds/uniq/Makefile
new file mode 100644 (file)
index 0000000..c87f1b1
--- /dev/null
@@ -0,0 +1,3 @@
+NAME := uniq
+SRCS := uniq.c
+include ../common.mk
diff --git a/user/cmds/uniq/uniq.c b/user/cmds/uniq/uniq.c
new file mode 100644 (file)
index 0000000..60fc2f1
--- /dev/null
@@ -0,0 +1,85 @@
+/* AdrOS uniq utility */
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#define LINE_MAX 1024
+
+static int cflag = 0;  /* -c: prefix lines with count */
+static int dflag = 0;  /* -d: only print duplicates */
+
+static int readline(int fd, char* buf, int max) {
+    int n = 0;
+    char c;
+    while (n < max - 1) {
+        int r = read(fd, &c, 1);
+        if (r <= 0) break;
+        if (c == '\n') break;
+        buf[n++] = c;
+    }
+    buf[n] = '\0';
+    return n > 0 ? n : (n == 0 ? 0 : -1);
+}
+
+int main(int argc, char** argv) {
+    int start = 1;
+    int fd = STDIN_FILENO;
+
+    for (int i = 1; i < argc; i++) {
+        if (argv[i][0] == '-' && argv[i][1]) {
+            const char* f = argv[i] + 1;
+            while (*f) {
+                if (*f == 'c') cflag = 1;
+                else if (*f == 'd') dflag = 1;
+                f++;
+            }
+            start = i + 1;
+        } else break;
+    }
+
+    if (start < argc) {
+        fd = open(argv[start], O_RDONLY);
+        if (fd < 0) {
+            fprintf(stderr, "uniq: cannot open '%s'\n", argv[start]);
+            return 1;
+        }
+    }
+
+    char prev[LINE_MAX] = {0};
+    char cur[LINE_MAX];
+    int count = 0;
+    int first = 1;
+
+    while (1) {
+        int r = readline(fd, cur, LINE_MAX);
+        if (r < 0) break;
+
+        if (first || strcmp(cur, prev) != 0) {
+            if (!first) {
+                if (!dflag || count > 1) {
+                    if (cflag) printf("%7d %s\n", count, prev);
+                    else printf("%s\n", prev);
+                }
+            }
+            strcpy(prev, cur);
+            count = 1;
+            first = 0;
+        } else {
+            count++;
+        }
+
+        if (r == 0) break;
+    }
+
+    /* Print last line */
+    if (!first) {
+        if (!dflag || count > 1) {
+            if (cflag) printf("%7d %s\n", count, prev);
+            else printf("%s\n", prev);
+        }
+    }
+
+    if (fd != STDIN_FILENO) close(fd);
+    return 0;
+}
diff --git a/user/cmds/uptime/Makefile b/user/cmds/uptime/Makefile
new file mode 100644 (file)
index 0000000..391584c
--- /dev/null
@@ -0,0 +1,3 @@
+NAME := uptime
+SRCS := uptime.c
+include ../common.mk
diff --git a/user/cmds/uptime/uptime.c b/user/cmds/uptime/uptime.c
new file mode 100644 (file)
index 0000000..2f81539
--- /dev/null
@@ -0,0 +1,40 @@
+/* AdrOS uptime utility */
+#include <stdio.h>
+#include <time.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+int main(int argc, char** argv) {
+    (void)argc; (void)argv;
+
+    /* Try /proc/uptime first */
+    int fd = open("/proc/uptime", O_RDONLY);
+    if (fd >= 0) {
+        char buf[64];
+        int r = read(fd, buf, sizeof(buf) - 1);
+        close(fd);
+        if (r > 0) {
+            buf[r] = '\0';
+            printf("up %s", buf);
+            return 0;
+        }
+    }
+
+    /* Fallback: use CLOCK_MONOTONIC */
+    struct timespec ts;
+    if (clock_gettime(CLOCK_MONOTONIC, &ts) < 0) {
+        fprintf(stderr, "uptime: cannot get time\n");
+        return 1;
+    }
+
+    unsigned long sec = ts.tv_sec;
+    unsigned long days = sec / 86400;
+    unsigned long hours = (sec % 86400) / 3600;
+    unsigned long mins = (sec % 3600) / 60;
+    unsigned long secs = sec % 60;
+
+    printf("up");
+    if (days > 0) printf(" %lu day%s,", days, days > 1 ? "s" : "");
+    printf(" %02lu:%02lu:%02lu\n", hours, mins, secs);
+    return 0;
+}
diff --git a/user/cmds/wc/Makefile b/user/cmds/wc/Makefile
new file mode 100644 (file)
index 0000000..8dd2064
--- /dev/null
@@ -0,0 +1,3 @@
+NAME := wc
+SRCS := wc.c
+include ../common.mk
diff --git a/user/cmds/wc/wc.c b/user/cmds/wc/wc.c
new file mode 100644 (file)
index 0000000..76333eb
--- /dev/null
@@ -0,0 +1,69 @@
+/* AdrOS wc utility */
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+
+static void wc_fd(int fd, const char* name, int show_l, int show_w, int show_c) {
+    char buf[4096];
+    int lines = 0, words = 0, chars = 0;
+    int in_word = 0;
+    int r;
+
+    while ((r = read(fd, buf, sizeof(buf))) > 0) {
+        for (int i = 0; i < r; i++) {
+            chars++;
+            if (buf[i] == '\n') lines++;
+            if (buf[i] == ' ' || buf[i] == '\t' || buf[i] == '\n') {
+                in_word = 0;
+            } else if (!in_word) {
+                in_word = 1;
+                words++;
+            }
+        }
+    }
+
+    if (show_l) printf("%7d", lines);
+    if (show_w) printf("%7d", words);
+    if (show_c) printf("%7d", chars);
+    if (name) printf(" %s", name);
+    printf("\n");
+}
+
+int main(int argc, char** argv) {
+    int show_l = 0, show_w = 0, show_c = 0;
+    int start = 1;
+
+    for (int i = 1; i < argc; i++) {
+        if (argv[i][0] == '-' && argv[i][1]) {
+            const char* f = argv[i] + 1;
+            while (*f) {
+                if (*f == 'l') show_l = 1;
+                else if (*f == 'w') show_w = 1;
+                else if (*f == 'c') show_c = 1;
+                f++;
+            }
+            start = i + 1;
+        } else break;
+    }
+
+    /* Default: show all */
+    if (!show_l && !show_w && !show_c) {
+        show_l = show_w = show_c = 1;
+    }
+
+    if (start >= argc) {
+        wc_fd(STDIN_FILENO, NULL, show_l, show_w, show_c);
+    } else {
+        for (int i = start; i < argc; i++) {
+            int fd = open(argv[i], O_RDONLY);
+            if (fd < 0) {
+                fprintf(stderr, "wc: %s: No such file\n", argv[i]);
+                continue;
+            }
+            wc_fd(fd, argv[i], show_l, show_w, show_c);
+            close(fd);
+        }
+    }
+    return 0;
+}
diff --git a/user/cmds/which/Makefile b/user/cmds/which/Makefile
new file mode 100644 (file)
index 0000000..aa15f8b
--- /dev/null
@@ -0,0 +1,3 @@
+NAME := which
+SRCS := which.c
+include ../common.mk
diff --git a/user/cmds/which/which.c b/user/cmds/which/which.c
new file mode 100644 (file)
index 0000000..ddd60c4
--- /dev/null
@@ -0,0 +1,41 @@
+/* AdrOS which utility — locate a command */
+#include <stdio.h>
+#include <string.h>
+#include <dirent.h>
+
+static int exists_in_dir(const char* dirname, const char* name) {
+    DIR* dir = opendir(dirname);
+    if (!dir) return 0;
+    struct dirent* d;
+    while ((d = readdir(dir)) != NULL) {
+        if (strcmp(d->d_name, name) == 0) {
+            closedir(dir);
+            return 1;
+        }
+    }
+    closedir(dir);
+    return 0;
+}
+
+int main(int argc, char** argv) {
+    if (argc < 2) {
+        fprintf(stderr, "Usage: which command\n");
+        return 1;
+    }
+
+    static const char* path_dirs[] = { "/bin", "/sbin", NULL };
+    int ret = 1;
+
+    for (int i = 1; i < argc; i++) {
+        int found = 0;
+        for (int d = 0; path_dirs[d]; d++) {
+            if (exists_in_dir(path_dirs[d], argv[i])) {
+                printf("%s/%s\n", path_dirs[d], argv[i]);
+                found = 1;
+                break;
+            }
+        }
+        if (found) ret = 0;
+    }
+    return ret;
+}
diff --git a/user/cmds/who/Makefile b/user/cmds/who/Makefile
new file mode 100644 (file)
index 0000000..d98042e
--- /dev/null
@@ -0,0 +1,3 @@
+NAME := who
+SRCS := who.c
+include ../common.mk
diff --git a/user/cmds/who/who.c b/user/cmds/who/who.c
new file mode 100644 (file)
index 0000000..b8fc948
--- /dev/null
@@ -0,0 +1,8 @@
+/* AdrOS who utility — show logged-in users */
+#include <stdio.h>
+#include <unistd.h>
+
+int main(void) {
+    printf("root     tty1         Jan  1 00:00\n");
+    return 0;
+}
diff --git a/user/cp.c b/user/cp.c
deleted file mode 100644 (file)
index 16fcb52..0000000
--- a/user/cp.c
+++ /dev/null
@@ -1,41 +0,0 @@
-/* AdrOS cp utility */
-#include <stdio.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <string.h>
-
-int main(int argc, char** argv) {
-    if (argc < 3) {
-        fprintf(stderr, "Usage: cp <source> <dest>\n");
-        return 1;
-    }
-
-    int src = open(argv[1], O_RDONLY);
-    if (src < 0) {
-        fprintf(stderr, "cp: cannot open '%s'\n", argv[1]);
-        return 1;
-    }
-
-    int dst = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0644);
-    if (dst < 0) {
-        fprintf(stderr, "cp: cannot create '%s'\n", argv[2]);
-        close(src);
-        return 1;
-    }
-
-    char buf[4096];
-    int r;
-    while ((r = read(src, buf, sizeof(buf))) > 0) {
-        int w = write(dst, buf, (size_t)r);
-        if (w != r) {
-            fprintf(stderr, "cp: write error\n");
-            close(src);
-            close(dst);
-            return 1;
-        }
-    }
-
-    close(src);
-    close(dst);
-    return 0;
-}
diff --git a/user/cut.c b/user/cut.c
deleted file mode 100644 (file)
index 47d4d5d..0000000
+++ /dev/null
@@ -1,110 +0,0 @@
-/* AdrOS cut utility */
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-#include <fcntl.h>
-
-#define LINE_MAX 1024
-
-static char delim = '\t';
-static int fields[32];
-static int nfields = 0;
-
-static void parse_fields(const char* spec) {
-    const char* p = spec;
-    while (*p && nfields < 32) {
-        fields[nfields++] = atoi(p);
-        while (*p && *p != ',') p++;
-        if (*p == ',') p++;
-    }
-}
-
-static void cut_line(char* line) {
-    if (nfields == 0) {
-        printf("%s\n", line);
-        return;
-    }
-
-    /* Split line into fields */
-    char* flds[64];
-    int nf = 0;
-    char* p = line;
-    flds[nf++] = p;
-    while (*p && nf < 64) {
-        if (*p == delim) {
-            *p = '\0';
-            flds[nf++] = p + 1;
-        }
-        p++;
-    }
-
-    /* Print requested fields */
-    int first = 1;
-    for (int i = 0; i < nfields; i++) {
-        int idx = fields[i] - 1;  /* 1-based */
-        if (idx >= 0 && idx < nf) {
-            if (!first) write(STDOUT_FILENO, &delim, 1);
-            printf("%s", flds[idx]);
-            first = 0;
-        }
-    }
-    printf("\n");
-}
-
-static void cut_fd(int fd) {
-    char line[LINE_MAX];
-    int pos = 0;
-    char c;
-
-    while (read(fd, &c, 1) > 0) {
-        if (c == '\n') {
-            line[pos] = '\0';
-            cut_line(line);
-            pos = 0;
-        } else if (pos < LINE_MAX - 1) {
-            line[pos++] = c;
-        }
-    }
-    if (pos > 0) {
-        line[pos] = '\0';
-        cut_line(line);
-    }
-}
-
-int main(int argc, char** argv) {
-    int start = 1;
-
-    for (int i = 1; i < argc; i++) {
-        if (strcmp(argv[i], "-d") == 0 && i + 1 < argc) {
-            delim = argv[++i][0];
-            start = i + 1;
-        } else if (strncmp(argv[i], "-d", 2) == 0 && argv[i][2] != '\0') {
-            delim = argv[i][2];
-            start = i + 1;
-        } else if (strcmp(argv[i], "-f") == 0 && i + 1 < argc) {
-            parse_fields(argv[++i]);
-            start = i + 1;
-        } else if (strncmp(argv[i], "-f", 2) == 0 && argv[i][2] != '\0') {
-            parse_fields(argv[i] + 2);
-            start = i + 1;
-        } else if (argv[i][0] != '-') {
-            break;
-        }
-    }
-
-    if (start >= argc) {
-        cut_fd(STDIN_FILENO);
-    } else {
-        for (int i = start; i < argc; i++) {
-            int fd = open(argv[i], O_RDONLY);
-            if (fd < 0) {
-                fprintf(stderr, "cut: cannot open '%s'\n", argv[i]);
-                continue;
-            }
-            cut_fd(fd);
-            close(fd);
-        }
-    }
-    return 0;
-}
diff --git a/user/date.c b/user/date.c
deleted file mode 100644 (file)
index 859986c..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-/* AdrOS date utility */
-#include <stdio.h>
-#include <time.h>
-
-int main(int argc, char** argv) {
-    (void)argc; (void)argv;
-    struct timespec ts;
-    if (clock_gettime(CLOCK_REALTIME, &ts) < 0) {
-        fprintf(stderr, "date: cannot get time\n");
-        return 1;
-    }
-
-    /* Simple epoch seconds display — no timezone or strftime yet */
-    unsigned long sec = ts.tv_sec;
-    unsigned long days = sec / 86400;
-    unsigned long rem = sec % 86400;
-    unsigned long hours = rem / 3600;
-    unsigned long mins = (rem % 3600) / 60;
-    unsigned long secs = rem % 60;
-
-    printf("%lu days since epoch, %02lu:%02lu:%02lu UTC\n",
-           days, hours, mins, secs);
-    return 0;
-}
diff --git a/user/dd.c b/user/dd.c
deleted file mode 100644 (file)
index 6e0eccc..0000000
--- a/user/dd.c
+++ /dev/null
@@ -1,63 +0,0 @@
-/* AdrOS dd utility — convert and copy a file */
-#include <stdio.h>
-#include <string.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <fcntl.h>
-
-static int parse_size(const char* s) {
-    int v = atoi(s);
-    int len = (int)strlen(s);
-    if (len > 0) {
-        char suf = s[len - 1];
-        if (suf == 'k' || suf == 'K') v *= 1024;
-        else if (suf == 'm' || suf == 'M') v *= 1024 * 1024;
-    }
-    return v;
-}
-
-int main(int argc, char** argv) {
-    const char* inf = NULL;
-    const char* outf = NULL;
-    int bs = 512;
-    int count = -1;
-
-    for (int i = 1; i < argc; i++) {
-        if (strncmp(argv[i], "if=", 3) == 0) inf = argv[i] + 3;
-        else if (strncmp(argv[i], "of=", 3) == 0) outf = argv[i] + 3;
-        else if (strncmp(argv[i], "bs=", 3) == 0) bs = parse_size(argv[i] + 3);
-        else if (strncmp(argv[i], "count=", 6) == 0) count = atoi(argv[i] + 6);
-    }
-
-    int ifd = STDIN_FILENO;
-    int ofd = STDOUT_FILENO;
-
-    if (inf) {
-        ifd = open(inf, O_RDONLY);
-        if (ifd < 0) { fprintf(stderr, "dd: cannot open '%s'\n", inf); return 1; }
-    }
-    if (outf) {
-        ofd = open(outf, O_WRONLY | O_CREAT | O_TRUNC, 0644);
-        if (ofd < 0) { fprintf(stderr, "dd: cannot open '%s'\n", outf); return 1; }
-    }
-
-    if (bs > 4096) bs = 4096;
-    char buf[4096];
-    int blocks = 0, partial = 0, total = 0;
-
-    while (count < 0 || blocks + partial < count) {
-        int n = read(ifd, buf, (size_t)bs);
-        if (n <= 0) break;
-        write(ofd, buf, (size_t)n);
-        total += n;
-        if (n == bs) blocks++;
-        else partial++;
-    }
-
-    fprintf(stderr, "%d+%d records in\n%d+%d records out\n%d bytes copied\n",
-            blocks, partial, blocks, partial, total);
-
-    if (inf) close(ifd);
-    if (outf) close(ofd);
-    return 0;
-}
diff --git a/user/df.c b/user/df.c
deleted file mode 100644 (file)
index 81d0a87..0000000
--- a/user/df.c
+++ /dev/null
@@ -1,10 +0,0 @@
-/* AdrOS df utility — display filesystem disk space usage */
-#include <stdio.h>
-
-int main(void) {
-    printf("Filesystem     Size  Used  Avail  Use%%  Mounted on\n");
-    printf("overlayfs         -     -      -     -  /\n");
-    printf("devfs             -     -      -     -  /dev\n");
-    printf("procfs            -     -      -     -  /proc\n");
-    return 0;
-}
diff --git a/user/dirname.c b/user/dirname.c
deleted file mode 100644 (file)
index 15e518d..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-/* AdrOS dirname utility — strip last component from path */
-#include <stdio.h>
-#include <string.h>
-
-int main(int argc, char** argv) {
-    if (argc <= 1) {
-        fprintf(stderr, "usage: dirname PATH\n");
-        return 1;
-    }
-    char* p = argv[1];
-    int len = (int)strlen(p);
-    /* Remove trailing slashes */
-    while (len > 1 && p[len - 1] == '/') len--;
-    /* Find last slash */
-    while (len > 0 && p[len - 1] != '/') len--;
-    /* Remove trailing slashes from result */
-    while (len > 1 && p[len - 1] == '/') len--;
-    if (len == 0) { printf(".\n"); return 0; }
-    p[len] = '\0';
-    printf("%s\n", p);
-    return 0;
-}
diff --git a/user/dmesg.c b/user/dmesg.c
deleted file mode 100644 (file)
index b2aacc4..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-/* AdrOS dmesg utility — print kernel ring buffer from /proc/dmesg */
-#include <stdio.h>
-#include <unistd.h>
-#include <fcntl.h>
-
-int main(void) {
-    int fd = open("/proc/dmesg", O_RDONLY);
-    if (fd < 0) {
-        fprintf(stderr, "dmesg: cannot open /proc/dmesg\n");
-        return 1;
-    }
-    char buf[512];
-    int n;
-    while ((n = read(fd, buf, sizeof(buf))) > 0)
-        write(STDOUT_FILENO, buf, (size_t)n);
-    close(fd);
-    return 0;
-}
diff --git a/user/du.c b/user/du.c
deleted file mode 100644 (file)
index 8fd15c5..0000000
--- a/user/du.c
+++ /dev/null
@@ -1,73 +0,0 @@
-/* AdrOS du utility — estimate file space usage */
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <dirent.h>
-#include <sys/stat.h>
-
-static int sflag = 0; /* -s: summary only */
-
-static long du_path(const char* path, int print) {
-    struct stat st;
-    if (stat(path, &st) < 0) {
-        fprintf(stderr, "du: cannot access '%s'\n", path);
-        return 0;
-    }
-
-    if (!(st.st_mode & 0040000)) {
-        long blocks = (st.st_size + 511) / 512;
-        if (print && !sflag) printf("%ld\t%s\n", blocks, path);
-        return blocks;
-    }
-
-    int fd = open(path, O_RDONLY);
-    if (fd < 0) return 0;
-
-    long total = 0;
-    char buf[2048];
-    int rc;
-    while ((rc = getdents(fd, buf, sizeof(buf))) > 0) {
-        int off = 0;
-        while (off < rc) {
-            struct dirent* d = (struct dirent*)(buf + off);
-            if (d->d_reclen == 0) break;
-            if (strcmp(d->d_name, ".") != 0 && strcmp(d->d_name, "..") != 0) {
-                char child[512];
-                if (path[strlen(path)-1] == '/')
-                    snprintf(child, sizeof(child), "%s%s", path, d->d_name);
-                else
-                    snprintf(child, sizeof(child), "%s/%s", path, d->d_name);
-                total += du_path(child, print);
-            }
-            off += d->d_reclen;
-        }
-    }
-    close(fd);
-
-    if (print && !sflag) printf("%ld\t%s\n", total, path);
-    return total;
-}
-
-int main(int argc, char** argv) {
-    int argi = 1;
-    while (argi < argc && argv[argi][0] == '-') {
-        const char* f = argv[argi] + 1;
-        while (*f) {
-            if (*f == 's') sflag = 1;
-            f++;
-        }
-        argi++;
-    }
-
-    if (argi >= argc) {
-        long total = du_path(".", 1);
-        if (sflag) printf("%ld\t.\n", total);
-    } else {
-        for (int i = argi; i < argc; i++) {
-            long total = du_path(argv[i], 1);
-            if (sflag) printf("%ld\t%s\n", total, argv[i]);
-        }
-    }
-    return 0;
-}
diff --git a/user/echo.c b/user/echo.c
deleted file mode 100644 (file)
index 0e4496a..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-/* AdrOS echo utility — POSIX-compatible */
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-
-int main(int argc, char** argv) {
-    int nflag = 0;      /* -n: no trailing newline */
-    int eflag = 0;      /* -e: interpret escape sequences */
-    int i = 1;
-
-    /* Parse flags */
-    while (i < argc && argv[i][0] == '-' && argv[i][1] != '\0') {
-        const char* f = argv[i] + 1;
-        int valid = 1;
-        int n = 0, e = 0;
-        while (*f) {
-            if (*f == 'n') n = 1;
-            else if (*f == 'e') e = 1;
-            else if (*f == 'E') { /* no escapes — default */ }
-            else { valid = 0; break; }
-            f++;
-        }
-        if (!valid) break;
-        if (n) nflag = 1;
-        if (e) eflag = 1;
-        i++;
-    }
-
-    int first = 1;
-    for (; i < argc; i++) {
-        if (!first)
-            write(STDOUT_FILENO, " ", 1);
-        first = 0;
-
-        const char* s = argv[i];
-        if (eflag) {
-            while (*s) {
-                if (*s == '\\' && s[1]) {
-                    s++;
-                    char c = *s;
-                    switch (c) {
-                    case 'n': write(STDOUT_FILENO, "\n", 1); break;
-                    case 't': write(STDOUT_FILENO, "\t", 1); break;
-                    case '\\': write(STDOUT_FILENO, "\\", 1); break;
-                    case 'r': write(STDOUT_FILENO, "\r", 1); break;
-                    case 'a': write(STDOUT_FILENO, "\a", 1); break;
-                    default: write(STDOUT_FILENO, "\\", 1);
-                             write(STDOUT_FILENO, &c, 1); break;
-                    }
-                } else {
-                    write(STDOUT_FILENO, s, 1);
-                }
-                s++;
-            }
-        } else {
-            write(STDOUT_FILENO, s, strlen(s));
-        }
-    }
-
-    if (!nflag)
-        write(STDOUT_FILENO, "\n", 1);
-
-    return 0;
-}
diff --git a/user/env.c b/user/env.c
deleted file mode 100644 (file)
index a7d727a..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-/* AdrOS env utility — print environment or run command with modified env */
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-
-extern char** __environ;
-
-int main(int argc, char** argv) {
-    if (argc <= 1) {
-        /* Print all environment variables */
-        if (__environ) {
-            for (int i = 0; __environ[i]; i++)
-                printf("%s\n", __environ[i]);
-        }
-        return 0;
-    }
-    /* env COMMAND ARGS... — run command with current environment */
-    execve(argv[1], (const char* const*)&argv[1], (const char* const*)__environ);
-    fprintf(stderr, "env: %s: not found\n", argv[1]);
-    return 127;
-}
diff --git a/user/errno.c b/user/errno.c
deleted file mode 100644 (file)
index fbc6e36..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-/* Per-process errno: each fork()ed process gets its own copy in its
-   address space.  When true threads (clone) are added, this must become
-   __thread int errno or use a TLS segment (GS/FS). */
-int errno = 0;
diff --git a/user/find.c b/user/find.c
deleted file mode 100644 (file)
index 78ca43e..0000000
+++ /dev/null
@@ -1,98 +0,0 @@
-/* AdrOS find utility — search for files in directory hierarchy */
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <dirent.h>
-
-static const char* name_pattern = NULL;
-static int type_filter = 0; /* 0=any, 'f'=file, 'd'=dir */
-
-static int match_name(const char* name) {
-    if (!name_pattern) return 1;
-    /* Simple wildcard: *pattern* if pattern has no special chars,
-       or exact match. Support leading/trailing * only. */
-    int plen = (int)strlen(name_pattern);
-    if (plen == 0) return 1;
-
-    const char* pat = name_pattern;
-    int lead_star = (pat[0] == '*');
-    int trail_star = (plen > 1 && pat[plen-1] == '*');
-
-    if (lead_star && trail_star) {
-        char sub[256];
-        int slen = plen - 2;
-        if (slen <= 0) return 1;
-        memcpy(sub, pat + 1, slen);
-        sub[slen] = '\0';
-        return strstr(name, sub) != NULL;
-    }
-    if (lead_star) {
-        const char* suffix = pat + 1;
-        int slen = plen - 1;
-        int nlen = (int)strlen(name);
-        if (nlen < slen) return 0;
-        return strcmp(name + nlen - slen, suffix) == 0;
-    }
-    if (trail_star) {
-        return strncmp(name, pat, plen - 1) == 0;
-    }
-    return strcmp(name, pat) == 0;
-}
-
-static void find_recurse(const char* path) {
-    int fd = open(path, O_RDONLY);
-    if (fd < 0) return;
-
-    char buf[2048];
-    int rc;
-    while ((rc = getdents(fd, buf, sizeof(buf))) > 0) {
-        int off = 0;
-        while (off < rc) {
-            struct dirent* d = (struct dirent*)(buf + off);
-            if (d->d_reclen == 0) break;
-            if (strcmp(d->d_name, ".") != 0 && strcmp(d->d_name, "..") != 0) {
-                char child[512];
-                if (path[strlen(path)-1] == '/')
-                    snprintf(child, sizeof(child), "%s%s", path, d->d_name);
-                else
-                    snprintf(child, sizeof(child), "%s/%s", path, d->d_name);
-
-                int show = match_name(d->d_name);
-                if (show && type_filter) {
-                    if (type_filter == 'f' && d->d_type == 4) show = 0; /* DT_DIR=4 */
-                    if (type_filter == 'd' && d->d_type != 4) show = 0;
-                }
-                if (show) printf("%s\n", child);
-
-                if (d->d_type == 4) { /* DT_DIR */
-                    find_recurse(child);
-                }
-            }
-            off += d->d_reclen;
-        }
-    }
-    close(fd);
-}
-
-int main(int argc, char** argv) {
-    const char* start = ".";
-    int argi = 1;
-
-    if (argi < argc && argv[argi][0] != '-') {
-        start = argv[argi++];
-    }
-
-    while (argi < argc) {
-        if (strcmp(argv[argi], "-name") == 0 && argi + 1 < argc) {
-            name_pattern = argv[++argi];
-        } else if (strcmp(argv[argi], "-type") == 0 && argi + 1 < argc) {
-            type_filter = argv[++argi][0];
-        }
-        argi++;
-    }
-
-    printf("%s\n", start);
-    find_recurse(start);
-    return 0;
-}
diff --git a/user/free.c b/user/free.c
deleted file mode 100644 (file)
index b07a8bc..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-/* AdrOS free utility — display memory usage from /proc/meminfo */
-#include <stdio.h>
-#include <unistd.h>
-#include <fcntl.h>
-
-int main(void) {
-    int fd = open("/proc/meminfo", O_RDONLY);
-    if (fd >= 0) {
-        char buf[512];
-        int n = read(fd, buf, sizeof(buf) - 1);
-        if (n > 0) { buf[n] = '\0'; printf("%s", buf); }
-        close(fd);
-    } else {
-        printf("free: /proc/meminfo not available\n");
-    }
-    return 0;
-}
diff --git a/user/fulltest.c b/user/fulltest.c
deleted file mode 100644 (file)
index f491441..0000000
+++ /dev/null
@@ -1,4464 +0,0 @@
-#include <stdint.h>
-
-#ifdef SIGKILL
-#undef SIGKILL
-#endif
-#ifdef SIGUSR1
-#undef SIGUSR1
-#endif
-#ifdef SIGSEGV
-#undef SIGSEGV
-#endif
-#ifdef SIGTTIN
-#undef SIGTTIN
-#endif
-#ifdef SIGTTOU
-#undef SIGTTOU
-#endif
-
-#ifdef WNOHANG
-#undef WNOHANG
-#endif
-#ifdef SEEK_SET
-#undef SEEK_SET
-#endif
-#ifdef SEEK_CUR
-#undef SEEK_CUR
-#endif
-#ifdef SEEK_END
-#undef SEEK_END
-#endif
-
-#include "user_errno.h"
-
-#include "signal.h"
-
-enum {
-    SYSCALL_WRITE = 1,
-    SYSCALL_EXIT  = 2,
-    SYSCALL_GETPID = 3,
-    SYSCALL_OPEN  = 4,
-    SYSCALL_READ  = 5,
-    SYSCALL_CLOSE = 6,
-    SYSCALL_WAITPID = 7,
-    SYSCALL_LSEEK = 9,
-    SYSCALL_FSTAT = 10,
-    SYSCALL_STAT = 11,
-
-    SYSCALL_DUP = 12,
-    SYSCALL_DUP2 = 13,
-    SYSCALL_PIPE = 14,
-    SYSCALL_PIPE2 = 34,
-    SYSCALL_EXECVE = 15,
-    SYSCALL_FORK = 16,
-    SYSCALL_GETPPID = 17,
-    SYSCALL_POLL = 18,
-    SYSCALL_KILL = 19,
-    SYSCALL_SELECT = 20,
-    SYSCALL_IOCTL = 21,
-    SYSCALL_SETSID = 22,
-    SYSCALL_SETPGID = 23,
-    SYSCALL_GETPGRP = 24,
-
-    SYSCALL_SIGACTION = 25,
-    SYSCALL_SIGPROCMASK = 26,
-    SYSCALL_SIGRETURN = 27,
-
-    SYSCALL_MKDIR = 28,
-    SYSCALL_UNLINK = 29,
-
-    SYSCALL_GETDENTS = 30,
-
-    SYSCALL_FCNTL = 31,
-
-    SYSCALL_CHDIR = 32,
-    SYSCALL_GETCWD = 33,
-    SYSCALL_DUP3 = 35,
-
-    SYSCALL_OPENAT = 36,
-    SYSCALL_FSTATAT = 37,
-    SYSCALL_UNLINKAT = 38,
-
-    SYSCALL_RENAME = 39,
-    SYSCALL_RMDIR = 40,
-
-    SYSCALL_BRK = 41,
-    SYSCALL_NANOSLEEP = 42,
-    SYSCALL_CLOCK_GETTIME = 43,
-    SYSCALL_MMAP = 44,
-    SYSCALL_MUNMAP = 45,
-
-    SYSCALL_SHMGET = 46,
-    SYSCALL_SHMAT  = 47,
-    SYSCALL_SHMDT  = 48,
-
-    SYSCALL_LINK     = 54,
-    SYSCALL_SYMLINK  = 55,
-    SYSCALL_READLINK = 56,
-
-    SYSCALL_SIGPENDING = 71,
-    SYSCALL_PREAD  = 72,
-    SYSCALL_PWRITE = 73,
-    SYSCALL_ACCESS = 74,
-    SYSCALL_TRUNCATE  = 78,
-    SYSCALL_FTRUNCATE = 79,
-    SYSCALL_UMASK  = 75,
-    SYSCALL_ALARM  = 83,
-    SYSCALL_SETITIMER = 92,
-    SYSCALL_GETITIMER = 93,
-    SYSCALL_WAITID    = 94,
-
-    SYSCALL_EPOLL_CREATE = 112,
-    SYSCALL_EPOLL_CTL    = 113,
-    SYSCALL_EPOLL_WAIT   = 114,
-
-    SYSCALL_INOTIFY_INIT      = 115,
-    SYSCALL_INOTIFY_ADD_WATCH = 116,
-    SYSCALL_INOTIFY_RM_WATCH  = 117,
-
-    SYSCALL_AIO_READ    = 121,
-    SYSCALL_AIO_WRITE   = 122,
-    SYSCALL_AIO_ERROR   = 123,
-    SYSCALL_AIO_RETURN  = 124,
-
-    SYSCALL_CHMOD  = 50,
-    SYSCALL_CHOWN  = 51,
-    SYSCALL_GETUID = 52,
-    SYSCALL_GETGID = 53,
-    SYSCALL_CLONE  = 67,
-    SYSCALL_GETTID = 68,
-    SYSCALL_FSYNC  = 69,
-    SYSCALL_READV  = 81,
-    SYSCALL_WRITEV = 82,
-    SYSCALL_TIMES  = 84,
-    SYSCALL_FUTEX  = 85,
-    SYSCALL_FLOCK  = 87,
-    SYSCALL_GETEUID = 88,
-    SYSCALL_GETEGID = 89,
-    SYSCALL_SETEUID = 90,
-    SYSCALL_SETEGID = 91,
-    SYSCALL_SIGSUSPEND = 80,
-    SYSCALL_SIGQUEUE   = 95,
-    SYSCALL_POSIX_SPAWN = 96,
-    SYSCALL_SETUID = 76,
-    SYSCALL_SETGID = 77,
-    SYSCALL_MOUNT  = 126,
-    SYSCALL_GETTIMEOFDAY = 127,
-    SYSCALL_MPROTECT     = 128,
-    SYSCALL_GETRLIMIT    = 129,
-    SYSCALL_SETRLIMIT    = 130,
-    SYSCALL_UNAME        = 136,
-    SYSCALL_MADVISE      = 140,
-};
-
-enum {
-    AT_FDCWD = -100,
-};
-
-enum {
-    F_GETFD = 1,
-    F_SETFD = 2,
-    F_GETFL = 3,
-    F_SETFL = 4,
-    F_GETPIPE_SZ = 1032,
-    F_SETPIPE_SZ = 1033,
-    FD_CLOEXEC = 1,
-};
-
-enum {
-    O_CLOEXEC = 0x80000,
-};
-
-enum {
-    TCGETS = 0x5401,
-    TCSETS = 0x5402,
-    TIOCGPGRP = 0x540F,
-    TIOCSPGRP = 0x5410,
-};
-
-enum {
-    ENOTTY = 25,
-};
-
-enum {
-    ICANON = 0x0002,
-    ECHO   = 0x0008,
-};
-
-#define USER_NCCS 11
-
-struct termios {
-    uint32_t c_iflag;
-    uint32_t c_oflag;
-    uint32_t c_cflag;
-    uint32_t c_lflag;
-    uint8_t  c_cc[USER_NCCS];
-};
-
-struct pollfd {
-    int fd;
-    int16_t events;
-    int16_t revents;
-};
-
-enum {
-    POLLIN  = 0x0001,
-    POLLOUT = 0x0004,
-    EPOLLET = (1U << 31),
-};
-
-enum {
-    SIGKILL = 9,
-    SIGUSR1 = 10,
-    SIGSEGV = 11,
-    SIGTTIN = 21,
-    SIGTTOU = 22,
-};
-
-enum {
-    WNOHANG = 1,
-};
-
-enum {
-    SEEK_SET = 0,
-    SEEK_CUR = 1,
-    SEEK_END = 2,
-};
-
-enum {
-    O_CREAT = 0x40,
-    O_TRUNC = 0x200,
-    O_NONBLOCK = 0x800,
-    O_APPEND = 0x400,
-    O_RDWR = 0x02,
-};
-
-enum {
-    PROT_READ  = 0x1,
-    PROT_WRITE = 0x2,
-};
-
-enum {
-    MAP_PRIVATE   = 0x02,
-    MAP_ANONYMOUS = 0x20,
-    MAP_FAILED_VAL = 0xFFFFFFFF,
-};
-
-enum {
-    CLOCK_REALTIME  = 0,
-    CLOCK_MONOTONIC = 1,
-};
-
-struct timespec {
-    uint32_t tv_sec;
-    uint32_t tv_nsec;
-};
-
-enum {
-    R_OK = 4,
-    W_OK = 2,
-    F_OK = 0,
-};
-
-enum {
-    SIG_BLOCK   = 0,
-    SIG_UNBLOCK = 1,
-    SIG_SETMASK = 2,
-};
-
-enum {
-    IPC_CREAT   = 01000,
-    IPC_PRIVATE = 0,
-};
-
-enum {
-    SIGALRM = 14,
-};
-
-enum {
-    EAGAIN = 11,
-    EINVAL = 22,
-    EEXIST = 17,
-};
-
-enum {
-    ITIMER_REAL    = 0,
-    ITIMER_VIRTUAL = 1,
-    ITIMER_PROF    = 2,
-};
-
-struct timeval {
-    uint32_t tv_sec;
-    uint32_t tv_usec;
-};
-
-struct rlimit {
-    uint32_t rlim_cur;
-    uint32_t rlim_max;
-};
-
-enum {
-    RLIMIT_CPU    = 0,
-    RLIMIT_FSIZE  = 1,
-    RLIMIT_DATA   = 2,
-    RLIMIT_STACK  = 3,
-    RLIMIT_CORE   = 4,
-    RLIMIT_NOFILE = 5,
-    RLIMIT_AS     = 6,
-    RLIMIT_NPROC  = 7,
-    RLIM_INFINITY = 0xFFFFFFFFU,
-};
-
-struct itimerval {
-    struct timeval it_interval;
-    struct timeval it_value;
-};
-
-enum {
-    P_ALL  = 0,
-    P_PID  = 1,
-    P_PGID = 2,
-    WEXITED = 4,
-};
-
-#define S_IFMT  0170000
-#define S_IFREG 0100000
-
-struct stat {
-    uint32_t st_ino;
-    uint32_t st_mode;
-    uint32_t st_nlink;
-    uint32_t st_uid;
-    uint32_t st_gid;
-    uint32_t st_size;
-};
-
-static int sys_write(int fd, const void* buf, uint32_t len) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_WRITE), "b"(fd), "c"(buf), "d"(len)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_openat(int dirfd, const char* path, uint32_t flags, uint32_t mode) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_OPENAT), "b"(dirfd), "c"(path), "d"(flags), "S"(mode)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_fstatat(int dirfd, const char* path, struct stat* st, uint32_t flags) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_FSTATAT), "b"(dirfd), "c"(path), "d"(st), "S"(flags)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_unlinkat(int dirfd, const char* path, uint32_t flags) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_UNLINKAT), "b"(dirfd), "c"(path), "d"(flags)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_rename(const char* oldpath, const char* newpath) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_RENAME), "b"(oldpath), "c"(newpath)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_rmdir(const char* path) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_RMDIR), "b"(path)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_pipe2(int fds[2], uint32_t flags) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_PIPE2), "b"(fds), "c"(flags)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_chdir(const char* path) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_CHDIR), "b"(path)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_getcwd(char* buf, uint32_t size) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_GETCWD), "b"(buf), "c"(size)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_fcntl(int fd, int cmd, uint32_t arg) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_FCNTL), "b"(fd), "c"(cmd), "d"(arg)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_ioctl(int fd, uint32_t cmd, void* arg);
-
-static int isatty_fd(int fd) {
-    struct termios t;
-    if (sys_ioctl(fd, TCGETS, &t) < 0) {
-        if (errno == ENOTTY) return 0;
-        return -1;
-    }
-    return 1;
-}
-
-static int sys_getdents(int fd, void* buf, uint32_t len) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_GETDENTS), "b"(fd), "c"(buf), "d"(len)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static void write_int_dec(int v) {
-    char buf[16];
-    int i = 0;
-    if (v == 0) {
-        buf[i++] = '0';
-    } else {
-        int neg = 0;
-        if (v < 0) {
-            neg = 1;
-            v = -v;
-        }
-        while (v > 0 && i < (int)sizeof(buf)) {
-            buf[i++] = (char)('0' + (v % 10));
-            v /= 10;
-        }
-        if (neg && i < (int)sizeof(buf)) {
-            buf[i++] = '-';
-        }
-        for (int j = 0; j < i / 2; j++) {
-            char t = buf[j];
-            buf[j] = buf[i - 1 - j];
-            buf[i - 1 - j] = t;
-        }
-    }
-    (void)sys_write(1, buf, (uint32_t)i);
-}
-
-static void write_hex8(uint8_t v) {
-    static const char hex[] = "0123456789ABCDEF";
-    char b[2];
-    b[0] = hex[(v >> 4) & 0xF];
-    b[1] = hex[v & 0xF];
-    (void)sys_write(1, b, 2);
-}
-
-static void write_hex32(uint32_t v) {
-    static const char hex[] = "0123456789ABCDEF";
-    char b[8];
-    for (int i = 0; i < 8; i++) {
-        uint32_t shift = (uint32_t)(28 - 4 * i);
-        b[i] = hex[(v >> shift) & 0xFU];
-    }
-    (void)sys_write(1, b, 8);
-}
-
-static int memeq(const void* a, const void* b, uint32_t n) {
-    const uint8_t* x = (const uint8_t*)a;
-    const uint8_t* y = (const uint8_t*)b;
-    for (uint32_t i = 0; i < n; i++) {
-        if (x[i] != y[i]) return 0;
-    }
-    return 1;
-}
-
-static int streq(const char* a, const char* b) {
-    if (!a || !b) return 0;
-    uint32_t i = 0;
-    while (a[i] != 0 && b[i] != 0) {
-        if (a[i] != b[i]) return 0;
-        i++;
-    }
-    return a[i] == b[i];
-}
-
-static int sys_sigaction2(int sig, const struct sigaction* act, struct sigaction* oldact) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_SIGACTION), "b"(sig), "c"(act), "d"(oldact)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_sigaction(int sig, void (*handler)(int), uintptr_t* old_out) {
-    struct sigaction act;
-    act.sa_handler = (uintptr_t)handler;
-    act.sa_sigaction = 0;
-    act.sa_mask = 0;
-    act.sa_flags = 0;
-
-    struct sigaction oldact;
-    struct sigaction* oldp = old_out ? &oldact : 0;
-
-    int r = sys_sigaction2(sig, &act, oldp);
-    if (r < 0) return r;
-    if (old_out) {
-        *old_out = oldact.sa_handler;
-    }
-    return 0;
-}
-
-static int sys_select(uint32_t nfds, uint64_t* readfds, uint64_t* writefds, uint64_t* exceptfds, int32_t timeout) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_SELECT), "b"(nfds), "c"(readfds), "d"(writefds), "S"(exceptfds), "D"(timeout)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_ioctl(int fd, uint32_t cmd, void* arg) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_IOCTL), "b"(fd), "c"(cmd), "d"(arg)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_kill(int pid, int sig) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_KILL), "b"(pid), "c"(sig)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_poll(struct pollfd* fds, uint32_t nfds, int32_t timeout) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_POLL), "b"(fds), "c"(nfds), "d"(timeout)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_setsid(void) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_SETSID)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_setpgid(int pid, int pgid) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_SETPGID), "b"(pid), "c"(pgid)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_getpgrp(void) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_GETPGRP)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_getpid(void) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_GETPID)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_getppid(void) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_GETPPID)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_fork(void) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_FORK)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_execve(const char* path, const char* const* argv, const char* const* envp) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_EXECVE), "b"(path), "c"(argv), "d"(envp)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_pipe(int fds[2]) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_PIPE), "b"(fds)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_dup(int oldfd) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_DUP), "b"(oldfd)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_dup2(int oldfd, int newfd) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_DUP2), "b"(oldfd), "c"(newfd)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_dup3(int oldfd, int newfd, uint32_t flags) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_DUP3), "b"(oldfd), "c"(newfd), "d"(flags)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_waitpid(int pid, int* status, uint32_t options) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_WAITPID), "b"(pid), "c"(status), "d"(options)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_open(const char* path, uint32_t flags) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_OPEN), "b"(path), "c"(flags)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_mkdir(const char* path) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_MKDIR), "b"(path)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_unlink(const char* path) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_UNLINK), "b"(path)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_read(int fd, void* buf, uint32_t len) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_READ), "b"(fd), "c"(buf), "d"(len)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_close(int fd) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_CLOSE), "b"(fd)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_lseek(int fd, int32_t offset, int whence) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_LSEEK), "b"(fd), "c"(offset), "d"(whence)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_fstat(int fd, struct stat* st) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_FSTAT), "b"(fd), "c"(st)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_stat(const char* path, struct stat* st) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_STAT), "b"(path), "c"(st)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static uintptr_t sys_brk(uintptr_t addr) {
-    uintptr_t ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_BRK), "b"(addr)
-        : "memory"
-    );
-    return ret;
-}
-
-static uintptr_t sys_mmap(uintptr_t addr, uint32_t len, uint32_t prot, uint32_t flags, int fd) {
-    uintptr_t ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_MMAP), "b"(addr), "c"(len), "d"(prot), "S"(flags), "D"(fd)
-        : "memory"
-    );
-    return ret;
-}
-
-static int sys_munmap(uintptr_t addr, uint32_t len) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_MUNMAP), "b"(addr), "c"(len)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_clock_gettime(uint32_t clk_id, struct timespec* tp) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_CLOCK_GETTIME), "b"(clk_id), "c"(tp)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_pread(int fd, void* buf, uint32_t count, uint32_t offset) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_PREAD), "b"(fd), "c"(buf), "d"(count), "S"(offset)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_pwrite(int fd, const void* buf, uint32_t count, uint32_t offset) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_PWRITE), "b"(fd), "c"(buf), "d"(count), "S"(offset)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_ftruncate(int fd, uint32_t length) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_FTRUNCATE), "b"(fd), "c"(length)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_symlink(const char* target, const char* linkpath) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_SYMLINK), "b"(target), "c"(linkpath)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_readlink(const char* path, char* buf, uint32_t bufsiz) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_READLINK), "b"(path), "c"(buf), "d"(bufsiz)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_access(const char* path, uint32_t mode) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_ACCESS), "b"(path), "c"(mode)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_sigprocmask(int how, uint32_t mask, uint32_t* oldset) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_SIGPROCMASK), "b"(how), "c"(mask), "d"(oldset)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_sigpending(uint32_t* set) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_SIGPENDING), "b"(set)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static uint32_t sys_alarm(uint32_t seconds) {
-    uint32_t ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_ALARM), "b"(seconds)
-        : "memory"
-    );
-    return ret;
-}
-
-static int sys_shmget(uint32_t key, uint32_t size, uint32_t flags) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_SHMGET), "b"(key), "c"(size), "d"(flags)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static uintptr_t sys_shmat(int shmid, uintptr_t addr, uint32_t flags) {
-    uintptr_t ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_SHMAT), "b"(shmid), "c"(addr), "d"(flags)
-        : "memory"
-    );
-    return ret;
-}
-
-static int sys_shmdt(uintptr_t addr) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_SHMDT), "b"(addr)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_link(const char* oldpath, const char* newpath) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_LINK), "b"(oldpath), "c"(newpath)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_umask(uint32_t mask) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_UMASK), "b"(mask)
-        : "memory"
-    );
-    return ret;
-}
-
-static int sys_setitimer(int which, const struct itimerval* newval, struct itimerval* oldval) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_SETITIMER), "b"(which), "c"(newval), "d"(oldval)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_getitimer(int which, struct itimerval* cur) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_GETITIMER), "b"(which), "c"(cur)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_waitid(uint32_t idtype, uint32_t id, void* infop, uint32_t options) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_WAITID), "b"(idtype), "c"(id), "d"(infop), "S"(options)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_epoll_create(int size) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_EPOLL_CREATE), "b"(size)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_epoll_ctl(int epfd, int op, int fd, void* event) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_EPOLL_CTL), "b"(epfd), "c"(op), "d"(fd), "S"(event)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_epoll_wait(int epfd, void* events, int maxevents, int timeout) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_EPOLL_WAIT), "b"(epfd), "c"(events), "d"(maxevents), "S"(timeout)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_inotify_init(void) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_INOTIFY_INIT)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_inotify_add_watch(int fd, const char* path, uint32_t mask) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_INOTIFY_ADD_WATCH), "b"(fd), "c"(path), "d"(mask)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_inotify_rm_watch(int fd, int wd) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_INOTIFY_RM_WATCH), "b"(fd), "c"(wd)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-struct aiocb {
-    int      aio_fildes;
-    void*    aio_buf;
-    uint32_t aio_nbytes;
-    uint32_t aio_offset;
-    int32_t  aio_error;
-    int32_t  aio_return;
-};
-
-static int sys_aio_read(struct aiocb* cb) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_AIO_READ), "b"(cb)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_aio_write(struct aiocb* cb) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_AIO_WRITE), "b"(cb)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_aio_error(struct aiocb* cb) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_AIO_ERROR), "b"(cb)
-        : "memory"
-    );
-    return ret;
-}
-
-static int sys_aio_return(struct aiocb* cb) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_AIO_RETURN), "b"(cb)
-        : "memory"
-    );
-    return ret;
-}
-
-static int sys_nanosleep(const struct timespec* req, struct timespec* rem) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_NANOSLEEP), "b"(req), "c"(rem)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static uint32_t sys_getuid(void) {
-    uint32_t ret;
-    __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_GETUID) : "memory");
-    return ret;
-}
-
-static uint32_t sys_getgid(void) {
-    uint32_t ret;
-    __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_GETGID) : "memory");
-    return ret;
-}
-
-static uint32_t sys_geteuid(void) {
-    uint32_t ret;
-    __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_GETEUID) : "memory");
-    return ret;
-}
-
-static uint32_t sys_getegid(void) {
-    uint32_t ret;
-    __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_GETEGID) : "memory");
-    return ret;
-}
-
-static int sys_gettid(void) {
-    int ret;
-    __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_GETTID) : "memory");
-    return ret;
-}
-
-static int sys_fsync(int fd) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_FSYNC), "b"(fd)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_truncate(const char* path, uint32_t length) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_TRUNCATE), "b"(path), "c"(length)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_chmod(const char* path, uint32_t mode) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_CHMOD), "b"(path), "c"(mode)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_flock(int fd, int operation) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_FLOCK), "b"(fd), "c"(operation)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-struct iovec {
-    void*    iov_base;
-    uint32_t iov_len;
-};
-
-static int sys_writev(int fd, const struct iovec* iov, int iovcnt) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_WRITEV), "b"(fd), "c"(iov), "d"(iovcnt)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_readv(int fd, const struct iovec* iov, int iovcnt) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_READV), "b"(fd), "c"(iov), "d"(iovcnt)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static uint32_t sys_times(void* buf) {
-    uint32_t ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_TIMES), "b"(buf)
-        : "memory"
-    );
-    return ret;
-}
-
-static int sys_posix_spawn(uint32_t* pid_out, const char* path,
-                           const char* const* argv, const char* const* envp) {
-    int ret;
-    __asm__ volatile(
-        "int $0x80"
-        : "=a"(ret)
-        : "a"(SYSCALL_POSIX_SPAWN), "b"(pid_out), "c"(path), "d"(argv), "S"(envp)
-        : "memory"
-    );
-    return __syscall_fix(ret);
-}
-
-static int sys_setuid(uint32_t uid) {
-    int ret;
-    __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_SETUID), "b"(uid) : "memory");
-    return __syscall_fix(ret);
-}
-
-static int sys_setgid(uint32_t gid) {
-    int ret;
-    __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_SETGID), "b"(gid) : "memory");
-    return __syscall_fix(ret);
-}
-
-static int sys_seteuid(uint32_t euid) {
-    int ret;
-    __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_SETEUID), "b"(euid) : "memory");
-    return __syscall_fix(ret);
-}
-
-static int sys_setegid(uint32_t egid) {
-    int ret;
-    __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_SETEGID), "b"(egid) : "memory");
-    return __syscall_fix(ret);
-}
-
-static int sys_sigsuspend(const uint32_t* mask) {
-    int ret;
-    __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_SIGSUSPEND), "b"(mask) : "memory");
-    return __syscall_fix(ret);
-}
-
-static int sys_gettimeofday(struct timeval* tv) {
-    int ret;
-    __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_GETTIMEOFDAY), "b"(tv), "c"(0) : "memory");
-    return __syscall_fix(ret);
-}
-
-static int sys_mprotect(uintptr_t addr, uint32_t len, uint32_t prot) {
-    int ret;
-    __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_MPROTECT), "b"(addr), "c"(len), "d"(prot) : "memory");
-    return __syscall_fix(ret);
-}
-
-static int sys_madvise(uintptr_t addr, uint32_t len, uint32_t advice) {
-    int ret;
-    __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_MADVISE), "b"(addr), "c"(len), "d"(advice) : "memory");
-    return __syscall_fix(ret);
-}
-
-static int sys_getrlimit(int resource, struct rlimit* rlim) {
-    int ret;
-    __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_GETRLIMIT), "b"(resource), "c"(rlim) : "memory");
-    return __syscall_fix(ret);
-}
-
-struct utsname {
-    char sysname[65];
-    char nodename[65];
-    char release[65];
-    char version[65];
-    char machine[65];
-};
-
-static int sys_uname(struct utsname* buf) {
-    int ret;
-    __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_UNAME), "b"(buf) : "memory");
-    return __syscall_fix(ret);
-}
-
-static int sys_setrlimit(int resource, const struct rlimit* rlim) {
-    int ret;
-    __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_SETRLIMIT), "b"(resource), "c"(rlim) : "memory");
-    return __syscall_fix(ret);
-}
-
-__attribute__((noreturn)) static void sys_exit(int code) {
-    __asm__ volatile(
-        "int $0x80\n"
-        "1: jmp 1b\n"
-        :
-        : "a"(SYSCALL_EXIT), "b"(code)
-        : "memory"
-    );
-    for (;;) {
-        __asm__ volatile("hlt");
-    }
-}
-
-static volatile int got_usr1 = 0;
-static volatile int got_usr1_ret = 0;
-static volatile int got_ttin = 0;
-static volatile int got_ttou = 0;
-static volatile int got_alrm = 0;
-
-static void usr1_handler(int sig) {
-    (void)sig;
-    got_usr1 = 1;
-    sys_write(1, "[init] SIGUSR1 handler OK\n",
-              (uint32_t)(sizeof("[init] SIGUSR1 handler OK\n") - 1));
-}
-
-static void usr1_ret_handler(int sig) {
-    (void)sig;
-    got_usr1_ret = 1;
-}
-
-static void alrm_handler(int sig) {
-    (void)sig;
-    got_alrm = 1;
-}
-
-static void ttin_handler(int sig) {
-    (void)sig;
-    got_ttin = 1;
-}
-
-static void ttou_handler(int sig) {
-    (void)sig;
-    got_ttou = 1;
-}
-
-static void sigsegv_exit_handler(int sig) {
-    (void)sig;
-    static const char msg[] = "[init] SIGSEGV handler invoked\n";
-    (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1));
-    sys_exit(0);
-}
-
-static void sigsegv_info_handler(int sig, siginfo_t* info, void* uctx) {
-    (void)uctx;
-    static const char msg[] = "[init] SIGSEGV siginfo handler invoked\n";
-    (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1));
-    const uintptr_t expected = 0x12345000U;
-    if (sig == SIGSEGV && info && (uintptr_t)info->si_addr == expected) {
-        sys_exit(0);
-    }
-    sys_exit(1);
-}
-
-void _start(void) {
-    __asm__ volatile(
-        "mov $0x23, %ax\n"
-        "mov %ax, %ds\n"
-        "mov %ax, %es\n"
-        "mov %ax, %fs\n"
-        "mov %ax, %gs\n"
-    );
-
-    static const char msg[] = "[init] hello from init.elf\n";
-    (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1));
-
-    static const char path[] = "/sbin/fulltest";
-
-    int fd = sys_open(path, 0);
-    if (fd < 0) {
-        sys_write(1, "[init] open failed fd=", (uint32_t)(sizeof("[init] open failed fd=") - 1));
-        write_int_dec(fd);
-        sys_write(1, "\n", 1);
-        sys_exit(1);
-    }
-
-    uint8_t hdr[4];
-    int rd = sys_read(fd, hdr, 4);
-    (void)sys_close(fd);
-    if (rd == 4 && hdr[0] == 0x7F && hdr[1] == 'E' && hdr[2] == 'L' && hdr[3] == 'F') {
-        sys_write(1, "[init] open/read/close OK (ELF magic)\n",
-                  (uint32_t)(sizeof("[init] open/read/close OK (ELF magic)\n") - 1));
-    } else {
-        sys_write(1, "[init] read failed or bad header rd=", (uint32_t)(sizeof("[init] read failed or bad header rd=") - 1));
-        write_int_dec(rd);
-        sys_write(1, " hdr=", (uint32_t)(sizeof(" hdr=") - 1));
-        for (int i = 0; i < 4; i++) {
-            write_hex8(hdr[i]);
-        }
-        sys_write(1, "\n", 1);
-        sys_exit(1);
-    }
-
-    fd = sys_open("/sbin/fulltest", 0);
-    if (fd < 0) {
-        sys_write(1, "[init] overlay open failed\n",
-                  (uint32_t)(sizeof("[init] overlay open failed\n") - 1));
-        sys_exit(1);
-    }
-
-    uint8_t orig0 = 0;
-    if (sys_lseek(fd, 0, SEEK_SET) < 0 || sys_read(fd, &orig0, 1) != 1) {
-        sys_write(1, "[init] overlay read failed\n",
-                  (uint32_t)(sizeof("[init] overlay read failed\n") - 1));
-        sys_exit(1);
-    }
-
-    uint8_t x = (uint8_t)(orig0 ^ 0xFF);
-    if (sys_lseek(fd, 0, SEEK_SET) < 0 || sys_write(fd, &x, 1) != 1) {
-        sys_write(1, "[init] overlay write failed\n",
-                  (uint32_t)(sizeof("[init] overlay write failed\n") - 1));
-        sys_exit(1);
-    }
-
-    if (sys_close(fd) < 0) {
-        sys_write(1, "[init] overlay close failed\n",
-                  (uint32_t)(sizeof("[init] overlay close failed\n") - 1));
-        sys_exit(1);
-    }
-
-    fd = sys_open("/sbin/fulltest", 0);
-    if (fd < 0) {
-        sys_write(1, "[init] overlay open2 failed\n",
-                  (uint32_t)(sizeof("[init] overlay open2 failed\n") - 1));
-        sys_exit(1);
-    }
-
-    uint8_t chk = 0;
-    if (sys_lseek(fd, 0, SEEK_SET) < 0 || sys_read(fd, &chk, 1) != 1 || chk != x) {
-        sys_write(1, "[init] overlay verify failed\n",
-                  (uint32_t)(sizeof("[init] overlay verify failed\n") - 1));
-        sys_exit(1);
-    }
-
-    if (sys_lseek(fd, 0, SEEK_SET) < 0 || sys_write(fd, &orig0, 1) != 1) {
-        sys_write(1, "[init] overlay restore failed\n",
-                  (uint32_t)(sizeof("[init] overlay restore failed\n") - 1));
-        sys_exit(1);
-    }
-
-    if (sys_close(fd) < 0) {
-        sys_write(1, "[init] overlay close2 failed\n",
-                  (uint32_t)(sizeof("[init] overlay close2 failed\n") - 1));
-        sys_exit(1);
-    }
-
-    sys_write(1, "[init] overlay copy-up OK\n",
-              (uint32_t)(sizeof("[init] overlay copy-up OK\n") - 1));
-
-    fd = sys_open("/sbin/fulltest", 0);
-    if (fd < 0) {
-        sys_write(1, "[init] open2 failed\n", (uint32_t)(sizeof("[init] open2 failed\n") - 1));
-        sys_exit(1);
-    }
-
-    struct stat st;
-    if (sys_fstat(fd, &st) < 0) {
-        sys_write(1, "[init] fstat failed\n", (uint32_t)(sizeof("[init] fstat failed\n") - 1));
-        sys_exit(1);
-    }
-
-    if ((st.st_mode & S_IFMT) != S_IFREG || st.st_size == 0) {
-        sys_write(1, "[init] fstat bad\n", (uint32_t)(sizeof("[init] fstat bad\n") - 1));
-        sys_exit(1);
-    }
-
-    if (sys_lseek(fd, 0, SEEK_SET) < 0) {
-        sys_write(1, "[init] lseek set failed\n",
-                  (uint32_t)(sizeof("[init] lseek set failed\n") - 1));
-        sys_exit(1);
-    }
-
-    uint8_t m2[4];
-    if (sys_read(fd, m2, 4) != 4) {
-        sys_write(1, "[init] read2 failed\n", (uint32_t)(sizeof("[init] read2 failed\n") - 1));
-        sys_exit(1);
-    }
-    if (m2[0] != 0x7F || m2[1] != 'E' || m2[2] != 'L' || m2[3] != 'F') {
-        sys_write(1, "[init] lseek/read mismatch\n",
-                  (uint32_t)(sizeof("[init] lseek/read mismatch\n") - 1));
-        sys_exit(1);
-    }
-
-    if (sys_close(fd) < 0) {
-        sys_write(1, "[init] close2 failed\n", (uint32_t)(sizeof("[init] close2 failed\n") - 1));
-        sys_exit(1);
-    }
-
-    if (sys_stat("/sbin/fulltest", &st) < 0) {
-        sys_write(1, "[init] stat failed\n", (uint32_t)(sizeof("[init] stat failed\n") - 1));
-        sys_exit(1);
-    }
-    if ((st.st_mode & S_IFMT) != S_IFREG || st.st_size == 0) {
-        sys_write(1, "[init] stat bad\n", (uint32_t)(sizeof("[init] stat bad\n") - 1));
-        sys_exit(1);
-    }
-
-    sys_write(1, "[init] lseek/stat/fstat OK\n",
-              (uint32_t)(sizeof("[init] lseek/stat/fstat OK\n") - 1));
-
-    fd = sys_open("/tmp/hello.txt", 0);
-    if (fd < 0) {
-        sys_write(1, "[init] tmpfs open failed\n",
-                  (uint32_t)(sizeof("[init] tmpfs open failed\n") - 1));
-        sys_exit(1);
-    }
-
-    if (sys_lseek(fd, 0, SEEK_END) < 0) {
-        sys_write(1, "[init] dup2 prep lseek failed\n",
-                  (uint32_t)(sizeof("[init] dup2 prep lseek failed\n") - 1));
-        sys_exit(1);
-    }
-
-    if (sys_dup2(fd, 1) != 1) {
-        sys_write(1, "[init] dup2 failed\n", (uint32_t)(sizeof("[init] dup2 failed\n") - 1));
-        sys_exit(1);
-    }
-
-    (void)sys_close(fd);
-
-    {
-        static const char m[] = "[init] dup2 stdout->file OK\n";
-        if (sys_write(1, m, (uint32_t)(sizeof(m) - 1)) != (int)(sizeof(m) - 1)) {
-            sys_exit(1);
-        }
-    }
-
-    (void)sys_close(1);
-    sys_write(1, "[init] dup2 restore tty OK\n",
-              (uint32_t)(sizeof("[init] dup2 restore tty OK\n") - 1));
-
-    {
-        int pfds[2];
-        if (sys_pipe(pfds) < 0) {
-            sys_write(1, "[init] pipe failed\n", (uint32_t)(sizeof("[init] pipe failed\n") - 1));
-            sys_exit(1);
-        }
-
-        static const char pmsg[] = "pipe-test";
-        if (sys_write(pfds[1], pmsg, (uint32_t)(sizeof(pmsg) - 1)) != (int)(sizeof(pmsg) - 1)) {
-            sys_write(1, "[init] pipe write failed\n",
-                      (uint32_t)(sizeof("[init] pipe write failed\n") - 1));
-            sys_exit(1);
-        }
-
-        char rbuf[16];
-        int prd = sys_read(pfds[0], rbuf, (uint32_t)(sizeof(pmsg) - 1));
-        if (prd != (int)(sizeof(pmsg) - 1)) {
-            sys_write(1, "[init] pipe read failed\n",
-                      (uint32_t)(sizeof("[init] pipe read failed\n") - 1));
-            sys_exit(1);
-        }
-
-        int ok = 1;
-        for (uint32_t i = 0; i < (uint32_t)(sizeof(pmsg) - 1); i++) {
-            if ((uint8_t)rbuf[i] != (uint8_t)pmsg[i]) ok = 0;
-        }
-        if (!ok) {
-            sys_write(1, "[init] pipe mismatch\n",
-                      (uint32_t)(sizeof("[init] pipe mismatch\n") - 1));
-            sys_exit(1);
-        }
-
-        if (sys_dup2(pfds[1], 1) != 1) {
-            sys_write(1, "[init] pipe dup2 failed\n",
-                      (uint32_t)(sizeof("[init] pipe dup2 failed\n") - 1));
-            sys_exit(1);
-        }
-
-        static const char p2[] = "dup2-pipe";
-        if (sys_write(1, p2, (uint32_t)(sizeof(p2) - 1)) != (int)(sizeof(p2) - 1)) {
-            sys_exit(1);
-        }
-
-        int prd2 = sys_read(pfds[0], rbuf, (uint32_t)(sizeof(p2) - 1));
-        if (prd2 != (int)(sizeof(p2) - 1)) {
-            sys_write(1, "[init] pipe dup2 read failed\n",
-                      (uint32_t)(sizeof("[init] pipe dup2 read failed\n") - 1));
-            sys_exit(1);
-        }
-        sys_write(1, "[init] pipe OK\n", (uint32_t)(sizeof("[init] pipe OK\n") - 1));
-
-        (void)sys_close(pfds[0]);
-        (void)sys_close(pfds[1]);
-
-        int tfd = sys_open("/dev/tty", 0);
-        if (tfd < 0) {
-            sys_write(1, "[init] /dev/tty open failed\n",
-                      (uint32_t)(sizeof("[init] /dev/tty open failed\n") - 1));
-            sys_exit(1);
-        }
-        if (sys_dup2(tfd, 1) != 1) {
-            sys_write(1, "[init] dup2 restore tty failed\n",
-                      (uint32_t)(sizeof("[init] dup2 restore tty failed\n") - 1));
-            sys_exit(1);
-        }
-        (void)sys_close(tfd);
-
-    }
-
-    {
-        int pid = sys_fork();
-        if (pid < 0) {
-            sys_write(1, "[init] kill test fork failed\n",
-                      (uint32_t)(sizeof("[init] kill test fork failed\n") - 1));
-            sys_exit(1);
-        }
-
-        if (pid == 0) {
-            for (;;) {
-                __asm__ volatile("nop");
-            }
-        }
-
-        if (sys_kill(pid, SIGKILL) < 0) {
-            sys_write(1, "[init] kill(SIGKILL) failed\n",
-                      (uint32_t)(sizeof("[init] kill(SIGKILL) failed\n") - 1));
-            sys_exit(1);
-        }
-
-        int st = 0;
-        int rp = sys_waitpid(pid, &st, 0);
-        if (rp != pid || st != (128 + SIGKILL)) {
-            sys_write(1, "[init] kill test waitpid mismatch\n",
-                      (uint32_t)(sizeof("[init] kill test waitpid mismatch\n") - 1));
-            sys_exit(1);
-        }
-
-        sys_write(1, "[init] kill(SIGKILL) OK\n",
-                  (uint32_t)(sizeof("[init] kill(SIGKILL) OK\n") - 1));
-    }
-
-    {
-        int fds[2];
-        if (sys_pipe(fds) < 0) {
-            sys_write(1, "[init] poll pipe setup failed\n",
-                      (uint32_t)(sizeof("[init] poll pipe setup failed\n") - 1));
-            sys_exit(1);
-        }
-
-        struct pollfd p;
-        p.fd = fds[0];
-        p.events = POLLIN;
-        p.revents = 0;
-        int rc = sys_poll(&p, 1, 0);
-        if (rc != 0) {
-            sys_write(1, "[init] poll(pipe) expected 0\n",
-                      (uint32_t)(sizeof("[init] poll(pipe) expected 0\n") - 1));
-            sys_exit(1);
-        }
-
-        static const char a = 'A';
-        if (sys_write(fds[1], &a, 1) != 1) {
-            sys_write(1, "[init] poll pipe write failed\n",
-                      (uint32_t)(sizeof("[init] poll pipe write failed\n") - 1));
-            sys_exit(1);
-        }
-
-        p.revents = 0;
-        rc = sys_poll(&p, 1, 0);
-        if (rc != 1 || (p.revents & POLLIN) == 0) {
-            sys_write(1, "[init] poll(pipe) expected POLLIN\n",
-                      (uint32_t)(sizeof("[init] poll(pipe) expected POLLIN\n") - 1));
-            sys_exit(1);
-        }
-
-        (void)sys_close(fds[0]);
-        (void)sys_close(fds[1]);
-        sys_write(1, "[init] poll(pipe) OK\n", (uint32_t)(sizeof("[init] poll(pipe) OK\n") - 1));
-    }
-
-    {
-        int fds[2];
-        if (sys_pipe(fds) < 0) {
-            sys_write(1, "[init] select pipe setup failed\n",
-                      (uint32_t)(sizeof("[init] select pipe setup failed\n") - 1));
-            sys_exit(1);
-        }
-
-        uint64_t r = 0;
-        uint64_t w = 0;
-        r |= (1ULL << (uint32_t)fds[0]);
-        int rc = sys_select((uint32_t)(fds[0] + 1), &r, &w, 0, 0);
-        if (rc != 0) {
-            sys_write(1, "[init] select(pipe) expected 0\n",
-                      (uint32_t)(sizeof("[init] select(pipe) expected 0\n") - 1));
-            sys_exit(1);
-        }
-
-        static const char a = 'B';
-        if (sys_write(fds[1], &a, 1) != 1) {
-            sys_write(1, "[init] select pipe write failed\n",
-                      (uint32_t)(sizeof("[init] select pipe write failed\n") - 1));
-            sys_exit(1);
-        }
-
-        r = 0;
-        w = 0;
-        r |= (1ULL << (uint32_t)fds[0]);
-        rc = sys_select((uint32_t)(fds[0] + 1), &r, &w, 0, 0);
-        if (rc != 1 || ((r >> (uint32_t)fds[0]) & 1ULL) == 0) {
-            sys_write(1, "[init] select(pipe) expected readable\n",
-                      (uint32_t)(sizeof("[init] select(pipe) expected readable\n") - 1));
-            sys_exit(1);
-        }
-
-        (void)sys_close(fds[0]);
-        (void)sys_close(fds[1]);
-        sys_write(1, "[init] select(pipe) OK\n",
-                  (uint32_t)(sizeof("[init] select(pipe) OK\n") - 1));
-    }
-
-    {
-        int fd = sys_open("/dev/tty", 0);
-        if (fd < 0) {
-            sys_write(1, "[init] ioctl(/dev/tty) open failed\n",
-                      (uint32_t)(sizeof("[init] ioctl(/dev/tty) open failed\n") - 1));
-            sys_exit(1);
-        }
-
-        int fg = -1;
-        if (sys_ioctl(fd, TIOCGPGRP, &fg) < 0 || fg != 0) {
-            sys_write(1, "[init] ioctl TIOCGPGRP failed\n",
-                      (uint32_t)(sizeof("[init] ioctl TIOCGPGRP failed\n") - 1));
-            sys_exit(1);
-        }
-
-        fg = 0;
-        if (sys_ioctl(fd, TIOCSPGRP, &fg) < 0) {
-            sys_write(1, "[init] ioctl TIOCSPGRP failed\n",
-                      (uint32_t)(sizeof("[init] ioctl TIOCSPGRP failed\n") - 1));
-            sys_exit(1);
-        }
-
-        fg = 1;
-        if (sys_ioctl(fd, TIOCSPGRP, &fg) >= 0) {
-            sys_write(1, "[init] ioctl TIOCSPGRP expected fail\n",
-                      (uint32_t)(sizeof("[init] ioctl TIOCSPGRP expected fail\n") - 1));
-            sys_exit(1);
-        }
-
-        struct termios oldt;
-        if (sys_ioctl(fd, TCGETS, &oldt) < 0) {
-            sys_write(1, "[init] ioctl TCGETS failed\n",
-                      (uint32_t)(sizeof("[init] ioctl TCGETS failed\n") - 1));
-            sys_exit(1);
-        }
-
-        struct termios t = oldt;
-        t.c_lflag &= ~(uint32_t)(ECHO | ICANON);
-        if (sys_ioctl(fd, TCSETS, &t) < 0) {
-            sys_write(1, "[init] ioctl TCSETS failed\n",
-                      (uint32_t)(sizeof("[init] ioctl TCSETS failed\n") - 1));
-            sys_exit(1);
-        }
-
-        struct termios chk;
-        if (sys_ioctl(fd, TCGETS, &chk) < 0) {
-            sys_write(1, "[init] ioctl TCGETS2 failed\n",
-                      (uint32_t)(sizeof("[init] ioctl TCGETS2 failed\n") - 1));
-            sys_exit(1);
-        }
-
-        if ((chk.c_lflag & (uint32_t)(ECHO | ICANON)) != 0) {
-            sys_write(1, "[init] ioctl verify failed\n",
-                      (uint32_t)(sizeof("[init] ioctl verify failed\n") - 1));
-            sys_exit(1);
-        }
-
-        (void)sys_ioctl(fd, TCSETS, &oldt);
-        (void)sys_close(fd);
-
-        sys_write(1, "[init] ioctl(/dev/tty) OK\n",
-                  (uint32_t)(sizeof("[init] ioctl(/dev/tty) OK\n") - 1));
-    }
-
-    // A2: basic job control. A background pgrp read/write on controlling TTY should raise SIGTTIN/SIGTTOU.
-    {
-        int leader = sys_fork();
-        if (leader < 0) {
-            sys_write(1, "[init] fork(job control leader) failed\n",
-                      (uint32_t)(sizeof("[init] fork(job control leader) failed\n") - 1));
-            sys_exit(1);
-        }
-        if (leader == 0) {
-            int me = sys_getpid();
-            int sid = sys_setsid();
-            if (sid != me) {
-                sys_write(1, "[init] setsid(job control) failed\n",
-                          (uint32_t)(sizeof("[init] setsid(job control) failed\n") - 1));
-                sys_exit(1);
-            }
-
-            int tfd = sys_open("/dev/tty", 0);
-            if (tfd < 0) {
-                sys_write(1, "[init] open(/dev/tty) for job control failed\n",
-                          (uint32_t)(sizeof("[init] open(/dev/tty) for job control failed\n") - 1));
-                sys_exit(1);
-            }
-
-            // Touch ioctl to make kernel acquire controlling session/pgrp.
-            int fg = 0;
-            (void)sys_ioctl(tfd, TIOCGPGRP, &fg);
-
-            fg = me;
-            if (sys_ioctl(tfd, TIOCSPGRP, &fg) < 0) {
-                sys_write(1, "[init] ioctl TIOCSPGRP(job control) failed\n",
-                          (uint32_t)(sizeof("[init] ioctl TIOCSPGRP(job control) failed\n") - 1));
-                sys_exit(1);
-            }
-
-            int bg = sys_fork();
-            if (bg < 0) {
-                sys_write(1, "[init] fork(job control bg) failed\n",
-                          (uint32_t)(sizeof("[init] fork(job control bg) failed\n") - 1));
-                sys_exit(1);
-            }
-            if (bg == 0) {
-                (void)sys_setpgid(0, me + 1);
-
-                (void)sys_sigaction(SIGTTIN, ttin_handler, 0);
-                (void)sys_sigaction(SIGTTOU, ttou_handler, 0);
-
-                uint8_t b = 0;
-                (void)sys_read(tfd, &b, 1);
-                if (!got_ttin) {
-                    sys_write(1, "[init] SIGTTIN job control failed\n",
-                              (uint32_t)(sizeof("[init] SIGTTIN job control failed\n") - 1));
-                    sys_exit(1);
-                }
-
-                const char msg2[] = "x";
-                (void)sys_write(tfd, msg2, 1);
-                if (!got_ttou) {
-                    sys_write(1, "[init] SIGTTOU job control failed\n",
-                              (uint32_t)(sizeof("[init] SIGTTOU job control failed\n") - 1));
-                    sys_exit(1);
-                }
-
-                sys_exit(0);
-            }
-
-            int st2 = 0;
-            int wp2 = sys_waitpid(bg, &st2, 0);
-            if (wp2 != bg || st2 != 0) {
-                sys_write(1, "[init] waitpid(job control bg) failed wp=", (uint32_t)(sizeof("[init] waitpid(job control bg) failed wp=") - 1));
-                write_int_dec(wp2);
-                sys_write(1, " st=", (uint32_t)(sizeof(" st=") - 1));
-                write_int_dec(st2);
-                sys_write(1, "\n", 1);
-                sys_exit(1);
-            }
-
-            (void)sys_close(tfd);
-            sys_exit(0);
-        }
-
-        int stL = 0;
-        int wpL = sys_waitpid(leader, &stL, 0);
-        if (wpL != leader || stL != 0) {
-            sys_write(1, "[init] waitpid(job control leader) failed wp=", (uint32_t)(sizeof("[init] waitpid(job control leader) failed wp=") - 1));
-            write_int_dec(wpL);
-            sys_write(1, " st=", (uint32_t)(sizeof(" st=") - 1));
-            write_int_dec(stL);
-            sys_write(1, "\n", 1);
-            sys_exit(1);
-        }
-
-        sys_write(1, "[init] job control (SIGTTIN/SIGTTOU) OK\n",
-                  (uint32_t)(sizeof("[init] job control (SIGTTIN/SIGTTOU) OK\n") - 1));
-    }
-
-    {
-        int fd = sys_open("/dev/null", 0);
-        if (fd < 0) {
-            sys_write(1, "[init] poll(/dev/null) open failed\n",
-                      (uint32_t)(sizeof("[init] poll(/dev/null) open failed\n") - 1));
-            sys_exit(1);
-        }
-        struct pollfd p;
-        p.fd = fd;
-        p.events = POLLOUT;
-        p.revents = 0;
-        int rc = sys_poll(&p, 1, 0);
-        if (rc != 1 || (p.revents & POLLOUT) == 0) {
-            sys_write(1, "[init] poll(/dev/null) expected POLLOUT\n",
-                      (uint32_t)(sizeof("[init] poll(/dev/null) expected POLLOUT\n") - 1));
-            sys_exit(1);
-        }
-        (void)sys_close(fd);
-        sys_write(1, "[init] poll(/dev/null) OK\n",
-                  (uint32_t)(sizeof("[init] poll(/dev/null) OK\n") - 1));
-    }
-
-    {
-        int mfd = sys_open("/dev/ptmx", 0);
-        int sfd = sys_open("/dev/pts/0", 0);
-        if (mfd < 0 || sfd < 0) {
-            sys_write(1, "[init] pty open failed\n",
-                      (uint32_t)(sizeof("[init] pty open failed\n") - 1));
-            sys_exit(1);
-        }
-
-        static const char m2s[] = "m2s";
-        if (sys_write(mfd, m2s, (uint32_t)(sizeof(m2s) - 1)) != (int)(sizeof(m2s) - 1)) {
-            sys_write(1, "[init] pty write master failed\n",
-                      (uint32_t)(sizeof("[init] pty write master failed\n") - 1));
-            sys_exit(1);
-        }
-
-        struct pollfd p;
-        p.fd = sfd;
-        p.events = POLLIN;
-        p.revents = 0;
-        int rc = sys_poll(&p, 1, 50);
-        if (rc != 1 || (p.revents & POLLIN) == 0) {
-            sys_write(1, "[init] pty poll slave failed\n",
-                      (uint32_t)(sizeof("[init] pty poll slave failed\n") - 1));
-            sys_exit(1);
-        }
-
-        char buf[8];
-        int rd = sys_read(sfd, buf, (uint32_t)(sizeof(m2s) - 1));
-        if (rd != (int)(sizeof(m2s) - 1) || !memeq(buf, m2s, (uint32_t)(sizeof(m2s) - 1))) {
-            sys_write(1, "[init] pty read slave failed\n",
-                      (uint32_t)(sizeof("[init] pty read slave failed\n") - 1));
-            sys_exit(1);
-        }
-
-        static const char s2m[] = "s2m";
-        if (sys_write(sfd, s2m, (uint32_t)(sizeof(s2m) - 1)) != (int)(sizeof(s2m) - 1)) {
-            sys_write(1, "[init] pty write slave failed\n",
-                      (uint32_t)(sizeof("[init] pty write slave failed\n") - 1));
-            sys_exit(1);
-        }
-
-        p.fd = mfd;
-        p.events = POLLIN;
-        p.revents = 0;
-        rc = sys_poll(&p, 1, 50);
-        if (rc != 1 || (p.revents & POLLIN) == 0) {
-            sys_write(1, "[init] pty poll master failed\n",
-                      (uint32_t)(sizeof("[init] pty poll master failed\n") - 1));
-            sys_exit(1);
-        }
-
-        rd = sys_read(mfd, buf, (uint32_t)(sizeof(s2m) - 1));
-        if (rd != (int)(sizeof(s2m) - 1) || !memeq(buf, s2m, (uint32_t)(sizeof(s2m) - 1))) {
-            sys_write(1, "[init] pty read master failed\n",
-                      (uint32_t)(sizeof("[init] pty read master failed\n") - 1));
-            sys_exit(1);
-        }
-
-        (void)sys_close(mfd);
-        (void)sys_close(sfd);
-        sys_write(1, "[init] pty OK\n", (uint32_t)(sizeof("[init] pty OK\n") - 1));
-    }
-
-    {
-        sys_write(1, "[init] setsid test: before fork\n",
-                  (uint32_t)(sizeof("[init] setsid test: before fork\n") - 1));
-        int pid = sys_fork();
-        if (pid < 0) {
-            static const char smsg[] = "[init] fork failed\n";
-            (void)sys_write(1, smsg, (uint32_t)(sizeof(smsg) - 1));
-            sys_exit(2);
-        }
-        if (pid == 0) {
-            sys_write(1, "[init] setsid test: child start\n",
-                      (uint32_t)(sizeof("[init] setsid test: child start\n") - 1));
-            int me = sys_getpid();
-            int sid = sys_setsid();
-            if (sid != me) sys_exit(2);
-
-            int pg = sys_getpgrp();
-            if (pg != me) sys_exit(3);
-
-            int newpg = me + 1;
-            if (sys_setpgid(0, newpg) < 0) sys_exit(4);
-            if (sys_getpgrp() != newpg) sys_exit(5);
-
-            sys_exit(0);
-        }
-
-        sys_write(1, "[init] setsid test: parent waitpid\n",
-                  (uint32_t)(sizeof("[init] setsid test: parent waitpid\n") - 1));
-        int st = 0;
-        int wp = sys_waitpid(pid, &st, 0);
-        if (wp != pid || st != 0) {
-            sys_write(1, "[init] setsid/setpgid/getpgrp failed\n",
-                      (uint32_t)(sizeof("[init] setsid/setpgid/getpgrp failed\n") - 1));
-            sys_exit(1);
-        }
-
-        sys_write(1, "[init] setsid/setpgid/getpgrp OK\n",
-                  (uint32_t)(sizeof("[init] setsid/setpgid/getpgrp OK\n") - 1));
-    }
-
-    {
-        uintptr_t oldh = 0;
-        if (sys_sigaction(SIGUSR1, usr1_handler, &oldh) < 0) {
-            sys_write(1, "[init] sigaction failed\n",
-                      (uint32_t)(sizeof("[init] sigaction failed\n") - 1));
-            sys_exit(1);
-        }
-
-        int me = sys_getpid();
-        if (sys_kill(me, SIGUSR1) < 0) {
-            sys_write(1, "[init] kill(SIGUSR1) failed\n",
-                      (uint32_t)(sizeof("[init] kill(SIGUSR1) failed\n") - 1));
-            sys_exit(1);
-        }
-
-        for (uint32_t i = 0; i < 2000000U; i++) {
-            if (got_usr1) break;
-        }
-
-        if (!got_usr1) {
-            sys_write(1, "[init] SIGUSR1 not delivered\n",
-                      (uint32_t)(sizeof("[init] SIGUSR1 not delivered\n") - 1));
-            sys_exit(1);
-        }
-
-        sys_write(1, "[init] sigaction/kill(SIGUSR1) OK\n",
-                  (uint32_t)(sizeof("[init] sigaction/kill(SIGUSR1) OK\n") - 1));
-    }
-
-    // Verify that returning from a signal handler does not corrupt the user stack.
-    {
-        if (sys_sigaction(SIGUSR1, usr1_ret_handler, 0) < 0) {
-            sys_write(1, "[init] sigaction (sigreturn test) failed\n",
-                      (uint32_t)(sizeof("[init] sigaction (sigreturn test) failed\n") - 1));
-            sys_exit(1);
-        }
-
-        volatile uint32_t canary = 0x11223344U;
-        int me = sys_getpid();
-        if (sys_kill(me, SIGUSR1) < 0) {
-            sys_write(1, "[init] kill(SIGUSR1) (sigreturn test) failed\n",
-                      (uint32_t)(sizeof("[init] kill(SIGUSR1) (sigreturn test) failed\n") - 1));
-            sys_exit(1);
-        }
-
-        if (!got_usr1_ret) {
-            sys_write(1, "[init] SIGUSR1 not delivered (sigreturn test)\n",
-                      (uint32_t)(sizeof("[init] SIGUSR1 not delivered (sigreturn test)\n") - 1));
-            sys_exit(1);
-        }
-
-        if (canary != 0x11223344U) {
-            sys_write(1, "[init] sigreturn test stack corruption\n",
-                      (uint32_t)(sizeof("[init] sigreturn test stack corruption\n") - 1));
-            sys_exit(1);
-        }
-
-        sys_write(1, "[init] sigreturn OK\n",
-                  (uint32_t)(sizeof("[init] sigreturn OK\n") - 1));
-    }
-
-    fd = sys_open("/tmp/hello.txt", 0);
-    if (fd < 0) {
-        sys_write(1, "[init] tmpfs open2 failed\n",
-                  (uint32_t)(sizeof("[init] tmpfs open2 failed\n") - 1));
-        sys_exit(1);
-    }
-
-    if (sys_stat("/tmp/hello.txt", &st) < 0) {
-        sys_write(1, "[init] tmpfs stat failed\n",
-                  (uint32_t)(sizeof("[init] tmpfs stat failed\n") - 1));
-        sys_exit(1);
-    }
-    if ((st.st_mode & S_IFMT) != S_IFREG) {
-        sys_write(1, "[init] tmpfs stat not reg\n",
-                  (uint32_t)(sizeof("[init] tmpfs stat not reg\n") - 1));
-        sys_exit(1);
-    }
-    if (st.st_size == 0) {
-        sys_write(1, "[init] tmpfs stat size 0\n",
-                  (uint32_t)(sizeof("[init] tmpfs stat size 0\n") - 1));
-        sys_exit(1);
-    }
-
-    struct stat fst;
-    if (sys_fstat(fd, &fst) < 0) {
-        sys_write(1, "[init] tmpfs fstat failed\n",
-                  (uint32_t)(sizeof("[init] tmpfs fstat failed\n") - 1));
-        sys_exit(1);
-    }
-    if (fst.st_size != st.st_size) {
-        sys_write(1, "[init] tmpfs stat size mismatch\n",
-                  (uint32_t)(sizeof("[init] tmpfs stat size mismatch\n") - 1));
-        sys_exit(1);
-    }
-
-    int end = sys_lseek(fd, 0, SEEK_END);
-    if (end < 0 || (uint32_t)end != st.st_size) {
-        sys_write(1, "[init] tmpfs lseek end bad\n",
-                  (uint32_t)(sizeof("[init] tmpfs lseek end bad\n") - 1));
-        sys_exit(1);
-    }
-
-    uint8_t eofb;
-    if (sys_read(fd, &eofb, 1) != 0) {
-        sys_write(1, "[init] tmpfs eof read bad\n",
-                  (uint32_t)(sizeof("[init] tmpfs eof read bad\n") - 1));
-        sys_exit(1);
-    }
-
-    if (sys_lseek(fd, 0, 999) >= 0) {
-        sys_write(1, "[init] tmpfs lseek whence bad\n",
-                  (uint32_t)(sizeof("[init] tmpfs lseek whence bad\n") - 1));
-        sys_exit(1);
-    }
-
-    if (sys_lseek(fd, 0, SEEK_SET) < 0) {
-        sys_write(1, "[init] tmpfs lseek set failed\n",
-                  (uint32_t)(sizeof("[init] tmpfs lseek set failed\n") - 1));
-        sys_exit(1);
-    }
-
-    uint8_t tbuf[6];
-    if (sys_read(fd, tbuf, 5) != 5) {
-        sys_write(1, "[init] tmpfs read failed\n",
-                  (uint32_t)(sizeof("[init] tmpfs read failed\n") - 1));
-        sys_exit(1);
-    }
-    tbuf[5] = 0;
-    if (tbuf[0] != 'h' || tbuf[1] != 'e' || tbuf[2] != 'l' || tbuf[3] != 'l' || tbuf[4] != 'o') {
-        sys_write(1, "[init] tmpfs bad data\n", (uint32_t)(sizeof("[init] tmpfs bad data\n") - 1));
-        sys_exit(1);
-    }
-
-    if (sys_close(fd) < 0) {
-        sys_write(1, "[init] tmpfs close failed\n",
-                  (uint32_t)(sizeof("[init] tmpfs close failed\n") - 1));
-        sys_exit(1);
-    }
-
-    if (sys_open("/tmp/does_not_exist", 0) >= 0) {
-        sys_write(1, "[init] tmpfs open nonexist bad\n",
-                  (uint32_t)(sizeof("[init] tmpfs open nonexist bad\n") - 1));
-        sys_exit(1);
-    }
-
-    fd = sys_open("/tmp/hello.txt", 0);
-    if (fd < 0) {
-        sys_write(1, "[init] tmpfs open3 failed\n",
-                  (uint32_t)(sizeof("[init] tmpfs open3 failed\n") - 1));
-        sys_exit(1);
-    }
-
-    if (sys_fstat(fd, &fst) < 0) {
-        sys_write(1, "[init] tmpfs fstat2 failed\n",
-                  (uint32_t)(sizeof("[init] tmpfs fstat2 failed\n") - 1));
-        sys_exit(1);
-    }
-
-    if (sys_lseek(fd, 0, SEEK_END) < 0) {
-        sys_write(1, "[init] tmpfs lseek end2 failed\n",
-                  (uint32_t)(sizeof("[init] tmpfs lseek end2 failed\n") - 1));
-        sys_exit(1);
-    }
-
-    char suf[3];
-    suf[0] = 'X';
-    suf[1] = 'Y';
-    suf[2] = 'Z';
-    if (sys_write(fd, suf, 3) != 3) {
-        sys_write(1, "[init] tmpfs write failed\n",
-                  (uint32_t)(sizeof("[init] tmpfs write failed\n") - 1));
-        sys_exit(1);
-    }
-
-    if (sys_fstat(fd, &fst) < 0) {
-        sys_write(1, "[init] tmpfs fstat3 failed\n",
-                  (uint32_t)(sizeof("[init] tmpfs fstat3 failed\n") - 1));
-        sys_exit(1);
-    }
-    if (fst.st_size != st.st_size + 3) {
-        sys_write(1, "[init] tmpfs size not grown\n",
-                  (uint32_t)(sizeof("[init] tmpfs size not grown\n") - 1));
-        sys_exit(1);
-    }
-
-    if (sys_lseek(fd, -3, SEEK_END) < 0) {
-        sys_write(1, "[init] tmpfs lseek back failed\n",
-                  (uint32_t)(sizeof("[init] tmpfs lseek back failed\n") - 1));
-        sys_exit(1);
-    }
-    uint8_t s2[3];
-    if (sys_read(fd, s2, 3) != 3 || s2[0] != 'X' || s2[1] != 'Y' || s2[2] != 'Z') {
-        sys_write(1, "[init] tmpfs suffix mismatch\n",
-                  (uint32_t)(sizeof("[init] tmpfs suffix mismatch\n") - 1));
-        sys_exit(1);
-    }
-
-    if (sys_close(fd) < 0) {
-        sys_write(1, "[init] tmpfs close3 failed\n",
-                  (uint32_t)(sizeof("[init] tmpfs close3 failed\n") - 1));
-        sys_exit(1);
-    }
-
-    sys_write(1, "[init] tmpfs/mount OK\n", (uint32_t)(sizeof("[init] tmpfs/mount OK\n") - 1));
-
-    {
-        int fd = sys_open("/dev/null", 0);
-        if (fd < 0) {
-            sys_write(1, "[init] /dev/null open failed\n",
-                      (uint32_t)(sizeof("[init] /dev/null open failed\n") - 1));
-            sys_exit(1);
-        }
-        static const char z[] = "discard me";
-        if (sys_write(fd, z, (uint32_t)(sizeof(z) - 1)) != (int)(sizeof(z) - 1)) {
-            sys_write(1, "[init] /dev/null write failed\n",
-                      (uint32_t)(sizeof("[init] /dev/null write failed\n") - 1));
-            sys_exit(1);
-        }
-        (void)sys_close(fd);
-        sys_write(1, "[init] /dev/null OK\n", (uint32_t)(sizeof("[init] /dev/null OK\n") - 1));
-    }
-
-    // B1: persistent storage smoke. Value should increment across reboots (disk.img).
-    {
-        int fd = sys_open("/persist/counter", 0);
-        if (fd < 0) {
-            sys_write(1, "[init] /persist/counter open failed\n",
-                      (uint32_t)(sizeof("[init] /persist/counter open failed\n") - 1));
-            sys_exit(1);
-        }
-
-        (void)sys_lseek(fd, 0, SEEK_SET);
-        uint8_t b[4] = {0, 0, 0, 0};
-        int rd = sys_read(fd, b, 4);
-        if (rd != 4) {
-            sys_write(1, "[init] /persist/counter read failed\n",
-                      (uint32_t)(sizeof("[init] /persist/counter read failed\n") - 1));
-            sys_exit(1);
-        }
-
-        uint32_t v = (uint32_t)b[0] | ((uint32_t)b[1] << 8) | ((uint32_t)b[2] << 16) | ((uint32_t)b[3] << 24);
-        v++;
-        b[0] = (uint8_t)(v & 0xFF);
-        b[1] = (uint8_t)((v >> 8) & 0xFF);
-        b[2] = (uint8_t)((v >> 16) & 0xFF);
-        b[3] = (uint8_t)((v >> 24) & 0xFF);
-
-        (void)sys_lseek(fd, 0, SEEK_SET);
-        int wr = sys_write(fd, b, 4);
-        if (wr != 4) {
-            sys_write(1, "[init] /persist/counter write failed\n",
-                      (uint32_t)(sizeof("[init] /persist/counter write failed\n") - 1));
-            sys_exit(1);
-        }
-
-        (void)sys_close(fd);
-
-        sys_write(1, "[init] /persist/counter=", (uint32_t)(sizeof("[init] /persist/counter=") - 1));
-        write_int_dec((int)v);
-        sys_write(1, "\n", 1);
-    }
-
-    {
-        int fd = sys_open("/dev/tty", 0);
-        if (fd < 0) {
-            sys_write(1, "[init] /dev/tty open failed\n",
-                      (uint32_t)(sizeof("[init] /dev/tty open failed\n") - 1));
-            sys_exit(1);
-        }
-        static const char m[] = "[init] /dev/tty write OK\n";
-        int wr = sys_write(fd, m, (uint32_t)(sizeof(m) - 1));
-        if (wr != (int)(sizeof(m) - 1)) {
-            sys_write(1, "[init] /dev/tty write failed\n",
-                      (uint32_t)(sizeof("[init] /dev/tty write failed\n") - 1));
-            sys_exit(1);
-        }
-        (void)sys_close(fd);
-    }
-
-    // B2: on-disk general filesystem smoke (/disk)
-    {
-        int fd = sys_open("/disk/test", O_CREAT);
-        if (fd < 0) {
-            sys_write(1, "[init] /disk/test open failed\n",
-                      (uint32_t)(sizeof("[init] /disk/test open failed\n") - 1));
-            sys_exit(1);
-        }
-
-        char buf[16];
-        int rd = sys_read(fd, buf, sizeof(buf));
-        int prev = 0;
-        if (rd > 0) {
-            for (int i = 0; i < rd; i++) {
-                if (buf[i] < '0' || buf[i] > '9') break;
-                prev = prev * 10 + (buf[i] - '0');
-            }
-        }
-
-        (void)sys_close(fd);
-
-        fd = sys_open("/disk/test", O_CREAT | O_TRUNC);
-        if (fd < 0) {
-            sys_write(1, "[init] /disk/test open2 failed\n",
-                      (uint32_t)(sizeof("[init] /disk/test open2 failed\n") - 1));
-            sys_exit(1);
-        }
-
-        int next = prev + 1;
-        char out[16];
-        int n = 0;
-        int v = next;
-        if (v == 0) {
-            out[n++] = '0';
-        } else {
-            char tmp[16];
-            int t = 0;
-            while (v > 0 && t < (int)sizeof(tmp)) {
-                tmp[t++] = (char)('0' + (v % 10));
-                v /= 10;
-            }
-            while (t > 0) {
-                out[n++] = tmp[--t];
-            }
-        }
-
-        if (sys_write(fd, out, (uint32_t)n) != n) {
-            sys_write(1, "[init] /disk/test write failed\n",
-                      (uint32_t)(sizeof("[init] /disk/test write failed\n") - 1));
-            sys_exit(1);
-        }
-        (void)sys_close(fd);
-
-        fd = sys_open("/disk/test", 0);
-        if (fd < 0) {
-            sys_write(1, "[init] /disk/test open3 failed\n",
-                      (uint32_t)(sizeof("[init] /disk/test open3 failed\n") - 1));
-            sys_exit(1);
-        }
-        for (uint32_t i = 0; i < (uint32_t)sizeof(buf); i++) buf[i] = 0;
-        rd = sys_read(fd, buf, sizeof(buf));
-        (void)sys_close(fd);
-        if (rd != n || !memeq(buf, out, (uint32_t)n)) {
-            sys_write(1, "[init] /disk/test verify failed\n",
-                      (uint32_t)(sizeof("[init] /disk/test verify failed\n") - 1));
-            sys_exit(1);
-        }
-
-        sys_write(1, "[init] /disk/test prev=", (uint32_t)(sizeof("[init] /disk/test prev=") - 1));
-        write_int_dec(prev);
-        sys_write(1, " next=", (uint32_t)(sizeof(" next=") - 1));
-        write_int_dec(next);
-        sys_write(1, " OK\n", (uint32_t)(sizeof(" OK\n") - 1));
-    }
-
-    // B3: diskfs mkdir/unlink smoke
-    {
-        int r = sys_mkdir("/disk/dir");
-        if (r < 0 && errno != 17) {
-            sys_write(1, "[init] mkdir /disk/dir failed errno=", (uint32_t)(sizeof("[init] mkdir /disk/dir failed errno=") - 1));
-            write_int_dec(errno);
-            sys_write(1, "\n", 1);
-            sys_exit(1);
-        }
-
-        int fd = sys_open("/disk/dir/file", O_CREAT | O_TRUNC);
-        if (fd < 0) {
-            sys_write(1, "[init] open /disk/dir/file failed\n",
-                      (uint32_t)(sizeof("[init] open /disk/dir/file failed\n") - 1));
-            sys_exit(1);
-        }
-        static const char msg2[] = "ok";
-        if (sys_write(fd, msg2, 2) != 2) {
-            sys_write(1, "[init] write /disk/dir/file failed\n",
-                      (uint32_t)(sizeof("[init] write /disk/dir/file failed\n") - 1));
-            sys_exit(1);
-        }
-        (void)sys_close(fd);
-
-        r = sys_unlink("/disk/dir/file");
-        if (r < 0) {
-            sys_write(1, "[init] unlink /disk/dir/file failed\n",
-                      (uint32_t)(sizeof("[init] unlink /disk/dir/file failed\n") - 1));
-            sys_exit(1);
-        }
-
-        fd = sys_open("/disk/dir/file", 0);
-        if (fd >= 0) {
-            sys_write(1, "[init] unlink did not remove file\n",
-                      (uint32_t)(sizeof("[init] unlink did not remove file\n") - 1));
-            sys_exit(1);
-        }
-
-        sys_write(1, "[init] diskfs mkdir/unlink OK\n",
-                  (uint32_t)(sizeof("[init] diskfs mkdir/unlink OK\n") - 1));
-    }
-
-    // B4: diskfs getdents smoke
-    {
-        int r = sys_mkdir("/disk/ls");
-        if (r < 0 && errno != 17) {
-            sys_write(1, "[init] mkdir /disk/ls failed errno=", (uint32_t)(sizeof("[init] mkdir /disk/ls failed errno=") - 1));
-            write_int_dec(errno);
-            sys_write(1, "\n", 1);
-            sys_exit(1);
-        }
-
-        int fd = sys_open("/disk/ls/file1", O_CREAT | O_TRUNC);
-        if (fd < 0) {
-            sys_write(1, "[init] create /disk/ls/file1 failed\n",
-                      (uint32_t)(sizeof("[init] create /disk/ls/file1 failed\n") - 1));
-            sys_exit(1);
-        }
-        (void)sys_close(fd);
-
-        fd = sys_open("/disk/ls/file2", O_CREAT | O_TRUNC);
-        if (fd < 0) {
-            sys_write(1, "[init] create /disk/ls/file2 failed\n",
-                      (uint32_t)(sizeof("[init] create /disk/ls/file2 failed\n") - 1));
-            sys_exit(1);
-        }
-        (void)sys_close(fd);
-
-        int dfd = sys_open("/disk/ls", 0);
-        if (dfd < 0) {
-            sys_write(1, "[init] open dir /disk/ls failed\n",
-                      (uint32_t)(sizeof("[init] open dir /disk/ls failed\n") - 1));
-            sys_exit(1);
-        }
-
-        struct {
-            uint32_t d_ino;
-            uint16_t d_reclen;
-            uint8_t d_type;
-            char d_name[24];
-        } ents[8];
-
-        int n = sys_getdents(dfd, ents, (uint32_t)sizeof(ents));
-        (void)sys_close(dfd);
-        if (n <= 0) {
-            sys_write(1, "[init] getdents failed\n",
-                      (uint32_t)(sizeof("[init] getdents failed\n") - 1));
-            sys_exit(1);
-        }
-
-        int saw_dot = 0, saw_dotdot = 0, saw_f1 = 0, saw_f2 = 0;
-        int cnt = n / (int)sizeof(ents[0]);
-        for (int i = 0; i < cnt; i++) {
-            if (streq(ents[i].d_name, ".")) saw_dot = 1;
-            else if (streq(ents[i].d_name, "..")) saw_dotdot = 1;
-            else if (streq(ents[i].d_name, "file1")) saw_f1 = 1;
-            else if (streq(ents[i].d_name, "file2")) saw_f2 = 1;
-        }
-
-        if (!saw_dot || !saw_dotdot || !saw_f1 || !saw_f2) {
-            sys_write(1, "[init] getdents verify failed\n",
-                      (uint32_t)(sizeof("[init] getdents verify failed\n") - 1));
-            sys_exit(1);
-        }
-
-        sys_write(1, "[init] diskfs getdents OK\n",
-                  (uint32_t)(sizeof("[init] diskfs getdents OK\n") - 1));
-    }
-
-    // B5: isatty() POSIX-like smoke (via ioctl TCGETS)
-    {
-        int fd = sys_open("/dev/tty", 0);
-        if (fd < 0) {
-            sys_write(1, "[init] isatty open /dev/tty failed\n",
-                      (uint32_t)(sizeof("[init] isatty open /dev/tty failed\n") - 1));
-            sys_exit(1);
-        }
-        int r = isatty_fd(fd);
-        (void)sys_close(fd);
-        if (r != 1) {
-            sys_write(1, "[init] isatty(/dev/tty) failed\n",
-                      (uint32_t)(sizeof("[init] isatty(/dev/tty) failed\n") - 1));
-            sys_exit(1);
-        }
-
-        fd = sys_open("/dev/null", 0);
-        if (fd < 0) {
-            sys_write(1, "[init] isatty open /dev/null failed\n",
-                      (uint32_t)(sizeof("[init] isatty open /dev/null failed\n") - 1));
-            sys_exit(1);
-        }
-        r = isatty_fd(fd);
-        (void)sys_close(fd);
-        if (r != 0) {
-            sys_write(1, "[init] isatty(/dev/null) expected 0\n",
-                      (uint32_t)(sizeof("[init] isatty(/dev/null) expected 0\n") - 1));
-            sys_exit(1);
-        }
-
-        sys_write(1, "[init] isatty OK\n", (uint32_t)(sizeof("[init] isatty OK\n") - 1));
-    }
-
-    // B6: O_NONBLOCK smoke (pipe + pty)
-    {
-        int fds[2];
-        if (sys_pipe(fds) < 0) {
-            sys_write(1, "[init] pipe for nonblock failed\n",
-                      (uint32_t)(sizeof("[init] pipe for nonblock failed\n") - 1));
-            sys_exit(1);
-        }
-
-        if (sys_fcntl(fds[0], F_SETFL, O_NONBLOCK) < 0) {
-            sys_write(1, "[init] fcntl nonblock pipe failed\n",
-                      (uint32_t)(sizeof("[init] fcntl nonblock pipe failed\n") - 1));
-            sys_exit(1);
-        }
-
-        char b;
-        int r = sys_read(fds[0], &b, 1);
-        if (r != -1 || errno != EAGAIN) {
-            sys_write(1, "[init] nonblock pipe read expected EAGAIN\n",
-                      (uint32_t)(sizeof("[init] nonblock pipe read expected EAGAIN\n") - 1));
-            sys_exit(1);
-        }
-
-        if (sys_write(fds[1], "x", 1) != 1) {
-            sys_write(1, "[init] pipe write failed\n",
-                      (uint32_t)(sizeof("[init] pipe write failed\n") - 1));
-            sys_exit(1);
-        }
-        r = sys_read(fds[0], &b, 1);
-        if (r != 1 || b != 'x') {
-            sys_write(1, "[init] nonblock pipe read after write failed\n",
-                      (uint32_t)(sizeof("[init] nonblock pipe read after write failed\n") - 1));
-            sys_exit(1);
-        }
-
-        (void)sys_close(fds[0]);
-        (void)sys_close(fds[1]);
-
-        int p = sys_open("/dev/ptmx", 0);
-        if (p < 0) {
-            sys_write(1, "[init] open /dev/ptmx failed\n",
-                      (uint32_t)(sizeof("[init] open /dev/ptmx failed\n") - 1));
-            sys_exit(1);
-        }
-        if (sys_fcntl(p, F_SETFL, O_NONBLOCK) < 0) {
-            sys_write(1, "[init] fcntl nonblock ptmx failed\n",
-                      (uint32_t)(sizeof("[init] fcntl nonblock ptmx failed\n") - 1));
-            sys_exit(1);
-        }
-        char pch;
-        r = sys_read(p, &pch, 1);
-        if (r != -1 || errno != EAGAIN) {
-            sys_write(1, "[init] nonblock ptmx read expected EAGAIN\n",
-                      (uint32_t)(sizeof("[init] nonblock ptmx read expected EAGAIN\n") - 1));
-            sys_exit(1);
-        }
-        (void)sys_close(p);
-
-        sys_write(1, "[init] O_NONBLOCK OK\n",
-                  (uint32_t)(sizeof("[init] O_NONBLOCK OK\n") - 1));
-    }
-
-    // B6b: pipe2 + dup3 smoke
-    {
-        int fds[2];
-        if (sys_pipe2(fds, O_NONBLOCK) < 0) {
-            sys_write(1, "[init] pipe2 failed\n",
-                      (uint32_t)(sizeof("[init] pipe2 failed\n") - 1));
-            sys_exit(1);
-        }
-
-        char b;
-        int r = sys_read(fds[0], &b, 1);
-        if (r != -1 || errno != EAGAIN) {
-            sys_write(1, "[init] pipe2 nonblock read expected EAGAIN\n",
-                      (uint32_t)(sizeof("[init] pipe2 nonblock read expected EAGAIN\n") - 1));
-            sys_exit(1);
-        }
-
-        int d = sys_dup3(fds[0], fds[0], 0);
-        if (d != -1 || errno != EINVAL) {
-            sys_write(1, "[init] dup3 samefd expected EINVAL\n",
-                      (uint32_t)(sizeof("[init] dup3 samefd expected EINVAL\n") - 1));
-            sys_exit(1);
-        }
-
-        (void)sys_close(fds[0]);
-        (void)sys_close(fds[1]);
-        sys_write(1, "[init] pipe2/dup3 OK\n",
-                  (uint32_t)(sizeof("[init] pipe2/dup3 OK\n") - 1));
-    }
-
-    // B7: chdir/getcwd smoke + relative paths
-    {
-        int r = sys_mkdir("/disk/cwd");
-        if (r < 0 && errno != 17) {
-            sys_write(1, "[init] mkdir /disk/cwd failed\n",
-                      (uint32_t)(sizeof("[init] mkdir /disk/cwd failed\n") - 1));
-            sys_exit(1);
-        }
-
-        r = sys_chdir("/disk/cwd");
-        if (r < 0) {
-            sys_write(1, "[init] chdir failed\n",
-                      (uint32_t)(sizeof("[init] chdir failed\n") - 1));
-            sys_exit(1);
-        }
-
-        char cwd[64];
-        for (uint32_t i = 0; i < (uint32_t)sizeof(cwd); i++) cwd[i] = 0;
-        if (sys_getcwd(cwd, (uint32_t)sizeof(cwd)) < 0) {
-            sys_write(1, "[init] getcwd failed\n",
-                      (uint32_t)(sizeof("[init] getcwd failed\n") - 1));
-            sys_exit(1);
-        }
-
-        // Create file using relative path.
-        int fd = sys_open("rel", O_CREAT | O_TRUNC);
-        if (fd < 0) {
-            sys_write(1, "[init] open relative failed\n",
-                      (uint32_t)(sizeof("[init] open relative failed\n") - 1));
-            sys_exit(1);
-        }
-        (void)sys_close(fd);
-
-        // Stat with relative path.
-        struct stat st;
-        if (sys_stat("rel", &st) < 0) {
-            sys_write(1, "[init] stat relative failed\n",
-                      (uint32_t)(sizeof("[init] stat relative failed\n") - 1));
-            sys_exit(1);
-        }
-
-        sys_write(1, "[init] chdir/getcwd OK\n",
-                  (uint32_t)(sizeof("[init] chdir/getcwd OK\n") - 1));
-    }
-
-    // B8: *at() syscalls smoke (AT_FDCWD)
-    {
-        int fd = sys_openat(AT_FDCWD, "atfile", O_CREAT | O_TRUNC, 0);
-        if (fd < 0) {
-            sys_write(1, "[init] openat failed\n",
-                      (uint32_t)(sizeof("[init] openat failed\n") - 1));
-            sys_exit(1);
-        }
-        (void)sys_close(fd);
-
-        struct stat st;
-        if (sys_fstatat(AT_FDCWD, "atfile", &st, 0) < 0) {
-            sys_write(1, "[init] fstatat failed\n",
-                      (uint32_t)(sizeof("[init] fstatat failed\n") - 1));
-            sys_exit(1);
-        }
-
-        if (sys_unlinkat(AT_FDCWD, "atfile", 0) < 0) {
-            sys_write(1, "[init] unlinkat failed\n",
-                      (uint32_t)(sizeof("[init] unlinkat failed\n") - 1));
-            sys_exit(1);
-        }
-
-        if (sys_stat("atfile", &st) >= 0) {
-            sys_write(1, "[init] unlinkat did not remove file\n",
-                      (uint32_t)(sizeof("[init] unlinkat did not remove file\n") - 1));
-            sys_exit(1);
-        }
-
-        sys_write(1, "[init] *at OK\n",
-                  (uint32_t)(sizeof("[init] *at OK\n") - 1));
-    }
-
-    // B9: rename + rmdir smoke
-    {
-        // Create a file, rename it, verify old gone and new exists.
-        int fd = sys_open("/disk/rnold", O_CREAT | O_TRUNC);
-        if (fd < 0) {
-            sys_write(1, "[init] rename: create failed\n",
-                      (uint32_t)(sizeof("[init] rename: create failed\n") - 1));
-            sys_exit(1);
-        }
-        (void)sys_write(fd, "RN", 2);
-        (void)sys_close(fd);
-
-        if (sys_rename("/disk/rnold", "/disk/rnnew") < 0) {
-            sys_write(1, "[init] rename failed\n",
-                      (uint32_t)(sizeof("[init] rename failed\n") - 1));
-            sys_exit(1);
-        }
-
-        struct stat st;
-        if (sys_stat("/disk/rnold", &st) >= 0) {
-            sys_write(1, "[init] rename: old still exists\n",
-                      (uint32_t)(sizeof("[init] rename: old still exists\n") - 1));
-            sys_exit(1);
-        }
-        if (sys_stat("/disk/rnnew", &st) < 0) {
-            sys_write(1, "[init] rename: new not found\n",
-                      (uint32_t)(sizeof("[init] rename: new not found\n") - 1));
-            sys_exit(1);
-        }
-
-        (void)sys_unlink("/disk/rnnew");
-
-        // mkdir, then rmdir
-        if (sys_mkdir("/disk/rmtmp") < 0 && errno != 17) {
-            sys_write(1, "[init] rmdir: mkdir failed\n",
-                      (uint32_t)(sizeof("[init] rmdir: mkdir failed\n") - 1));
-            sys_exit(1);
-        }
-        if (sys_rmdir("/disk/rmtmp") < 0) {
-            sys_write(1, "[init] rmdir failed\n",
-                      (uint32_t)(sizeof("[init] rmdir failed\n") - 1));
-            sys_exit(1);
-        }
-        if (sys_stat("/disk/rmtmp", &st) >= 0) {
-            sys_write(1, "[init] rmdir: dir still exists\n",
-                      (uint32_t)(sizeof("[init] rmdir: dir still exists\n") - 1));
-            sys_exit(1);
-        }
-
-        sys_write(1, "[init] rename/rmdir OK\n",
-                  (uint32_t)(sizeof("[init] rename/rmdir OK\n") - 1));
-    }
-
-    // B10: getdents on /dev (devfs) and /tmp (tmpfs)
-    {
-        int devfd = sys_open("/dev", 0);
-        if (devfd < 0) {
-            sys_write(1, "[init] open /dev failed\n",
-                      (uint32_t)(sizeof("[init] open /dev failed\n") - 1));
-            sys_exit(1);
-        }
-        char dbuf[256];
-        int dr = sys_getdents(devfd, dbuf, (uint32_t)sizeof(dbuf));
-        (void)sys_close(devfd);
-        if (dr <= 0) {
-            sys_write(1, "[init] getdents /dev failed\n",
-                      (uint32_t)(sizeof("[init] getdents /dev failed\n") - 1));
-            sys_exit(1);
-        }
-
-        int tmpfd = sys_open("/tmp", 0);
-        if (tmpfd < 0) {
-            sys_write(1, "[init] open /tmp failed\n",
-                      (uint32_t)(sizeof("[init] open /tmp failed\n") - 1));
-            sys_exit(1);
-        }
-        char tbuf[256];
-        int tr = sys_getdents(tmpfd, tbuf, (uint32_t)sizeof(tbuf));
-        (void)sys_close(tmpfd);
-        if (tr <= 0) {
-            sys_write(1, "[init] getdents /tmp failed\n",
-                      (uint32_t)(sizeof("[init] getdents /tmp failed\n") - 1));
-            sys_exit(1);
-        }
-
-        sys_write(1, "[init] getdents multi-fs OK\n",
-                  (uint32_t)(sizeof("[init] getdents multi-fs OK\n") - 1));
-    }
-
-    // C1: brk (user heap growth)
-    {
-        uintptr_t cur = sys_brk(0);
-        if (cur == 0) {
-            sys_write(1, "[init] brk(0) failed\n", (uint32_t)(sizeof("[init] brk(0) failed\n") - 1));
-            sys_exit(1);
-        }
-        uintptr_t next = sys_brk(cur + 4096);
-        if (next < cur + 4096) {
-            sys_write(1, "[init] brk grow failed\n", (uint32_t)(sizeof("[init] brk grow failed\n") - 1));
-            sys_exit(1);
-        }
-        volatile uint32_t* p = (volatile uint32_t*)cur;
-        *p = 0xDEADBEEF;
-        if (*p != 0xDEADBEEF) {
-            sys_write(1, "[init] brk memory bad\n", (uint32_t)(sizeof("[init] brk memory bad\n") - 1));
-            sys_exit(1);
-        }
-        sys_write(1, "[init] brk OK\n", (uint32_t)(sizeof("[init] brk OK\n") - 1));
-    }
-
-    // C2: mmap/munmap (anonymous)
-    {
-        uintptr_t addr = sys_mmap(0, 4096, PROT_READ | PROT_WRITE,
-                                  MAP_PRIVATE | MAP_ANONYMOUS, -1);
-        if (addr == MAP_FAILED_VAL || addr == 0) {
-            sys_write(1, "[init] mmap failed\n", (uint32_t)(sizeof("[init] mmap failed\n") - 1));
-            sys_exit(1);
-        }
-        volatile uint32_t* p = (volatile uint32_t*)addr;
-        *p = 0xCAFEBABE;
-        if (*p != 0xCAFEBABE) {
-            sys_write(1, "[init] mmap memory bad\n", (uint32_t)(sizeof("[init] mmap memory bad\n") - 1));
-            sys_exit(1);
-        }
-        if (sys_munmap(addr, 4096) < 0) {
-            sys_write(1, "[init] munmap failed\n", (uint32_t)(sizeof("[init] munmap failed\n") - 1));
-            sys_exit(1);
-        }
-        sys_write(1, "[init] mmap/munmap OK\n", (uint32_t)(sizeof("[init] mmap/munmap OK\n") - 1));
-    }
-
-    // C3: clock_gettime (CLOCK_MONOTONIC)
-    {
-        struct timespec ts1, ts2;
-        if (sys_clock_gettime(CLOCK_MONOTONIC, &ts1) < 0) {
-            sys_write(1, "[init] clock_gettime failed\n", (uint32_t)(sizeof("[init] clock_gettime failed\n") - 1));
-            sys_exit(1);
-        }
-        for (volatile uint32_t i = 0; i < 500000U; i++) { }
-        if (sys_clock_gettime(CLOCK_MONOTONIC, &ts2) < 0) {
-            sys_write(1, "[init] clock_gettime2 failed\n", (uint32_t)(sizeof("[init] clock_gettime2 failed\n") - 1));
-            sys_exit(1);
-        }
-        if (ts2.tv_sec < ts1.tv_sec || (ts2.tv_sec == ts1.tv_sec && ts2.tv_nsec <= ts1.tv_nsec)) {
-            sys_write(1, "[init] clock_gettime not monotonic\n", (uint32_t)(sizeof("[init] clock_gettime not monotonic\n") - 1));
-            sys_exit(1);
-        }
-        sys_write(1, "[init] clock_gettime OK\n", (uint32_t)(sizeof("[init] clock_gettime OK\n") - 1));
-    }
-
-    // C4: /dev/zero read
-    {
-        int fd = sys_open("/dev/zero", 0);
-        if (fd < 0) {
-            sys_write(1, "[init] /dev/zero open failed\n", (uint32_t)(sizeof("[init] /dev/zero open failed\n") - 1));
-            sys_exit(1);
-        }
-        uint8_t zbuf[8];
-        for (int i = 0; i < 8; i++) zbuf[i] = 0xFF;
-        int r = sys_read(fd, zbuf, 8);
-        (void)sys_close(fd);
-        if (r != 8) {
-            sys_write(1, "[init] /dev/zero read failed\n", (uint32_t)(sizeof("[init] /dev/zero read failed\n") - 1));
-            sys_exit(1);
-        }
-        int allz = 1;
-        for (int i = 0; i < 8; i++) { if (zbuf[i] != 0) allz = 0; }
-        if (!allz) {
-            sys_write(1, "[init] /dev/zero not zero\n", (uint32_t)(sizeof("[init] /dev/zero not zero\n") - 1));
-            sys_exit(1);
-        }
-        sys_write(1, "[init] /dev/zero OK\n", (uint32_t)(sizeof("[init] /dev/zero OK\n") - 1));
-    }
-
-    // C5: /dev/random read (just verify it returns data)
-    {
-        int fd = sys_open("/dev/random", 0);
-        if (fd < 0) {
-            sys_write(1, "[init] /dev/random open failed\n", (uint32_t)(sizeof("[init] /dev/random open failed\n") - 1));
-            sys_exit(1);
-        }
-        uint8_t rbuf[4];
-        int r = sys_read(fd, rbuf, 4);
-        (void)sys_close(fd);
-        if (r != 4) {
-            sys_write(1, "[init] /dev/random read failed\n", (uint32_t)(sizeof("[init] /dev/random read failed\n") - 1));
-            sys_exit(1);
-        }
-        sys_write(1, "[init] /dev/random OK\n", (uint32_t)(sizeof("[init] /dev/random OK\n") - 1));
-    }
-
-    // C6: procfs (/proc/meminfo)
-    {
-        int fd = sys_open("/proc/meminfo", 0);
-        if (fd < 0) {
-            sys_write(1, "[init] /proc/meminfo open failed\n", (uint32_t)(sizeof("[init] /proc/meminfo open failed\n") - 1));
-            sys_exit(1);
-        }
-        char pbuf[64];
-        int r = sys_read(fd, pbuf, 63);
-        (void)sys_close(fd);
-        if (r <= 0) {
-            sys_write(1, "[init] /proc/meminfo read failed\n", (uint32_t)(sizeof("[init] /proc/meminfo read failed\n") - 1));
-            sys_exit(1);
-        }
-        sys_write(1, "[init] procfs OK\n", (uint32_t)(sizeof("[init] procfs OK\n") - 1));
-    }
-
-    // C7: pread/pwrite (positional I/O)
-    {
-        int fd = sys_open("/disk/preadtest", O_CREAT | O_TRUNC);
-        if (fd < 0) {
-            sys_write(1, "[init] pread test open failed\n", (uint32_t)(sizeof("[init] pread test open failed\n") - 1));
-            sys_exit(1);
-        }
-        static const char pw[] = "ABCDEFGH";
-        if (sys_write(fd, pw, 8) != 8) {
-            sys_write(1, "[init] pread test write failed\n", (uint32_t)(sizeof("[init] pread test write failed\n") - 1));
-            sys_exit(1);
-        }
-        char pb[4];
-        int r = sys_pread(fd, pb, 4, 2);
-        if (r != 4 || pb[0] != 'C' || pb[1] != 'D' || pb[2] != 'E' || pb[3] != 'F') {
-            sys_write(1, "[init] pread data bad\n", (uint32_t)(sizeof("[init] pread data bad\n") - 1));
-            sys_exit(1);
-        }
-        if (sys_pwrite(fd, "XY", 2, 1) != 2) {
-            sys_write(1, "[init] pwrite failed\n", (uint32_t)(sizeof("[init] pwrite failed\n") - 1));
-            sys_exit(1);
-        }
-        r = sys_pread(fd, pb, 3, 0);
-        if (r != 3 || pb[0] != 'A' || pb[1] != 'X' || pb[2] != 'Y') {
-            sys_write(1, "[init] pwrite verify bad\n", (uint32_t)(sizeof("[init] pwrite verify bad\n") - 1));
-            sys_exit(1);
-        }
-        (void)sys_close(fd);
-        (void)sys_unlink("/disk/preadtest");
-        sys_write(1, "[init] pread/pwrite OK\n", (uint32_t)(sizeof("[init] pread/pwrite OK\n") - 1));
-    }
-
-    // C8: ftruncate
-    {
-        int fd = sys_open("/disk/trunctest", O_CREAT | O_TRUNC);
-        if (fd < 0) {
-            sys_write(1, "[init] truncate open failed\n", (uint32_t)(sizeof("[init] truncate open failed\n") - 1));
-            sys_exit(1);
-        }
-        if (sys_write(fd, "ABCDEFGHIJ", 10) != 10) {
-            sys_write(1, "[init] truncate write failed\n", (uint32_t)(sizeof("[init] truncate write failed\n") - 1));
-            sys_exit(1);
-        }
-        if (sys_ftruncate(fd, 5) < 0) {
-            sys_write(1, "[init] ftruncate failed\n", (uint32_t)(sizeof("[init] ftruncate failed\n") - 1));
-            sys_exit(1);
-        }
-        struct stat tst;
-        if (sys_fstat(fd, &tst) < 0 || tst.st_size != 5) {
-            sys_write(1, "[init] ftruncate size bad\n", (uint32_t)(sizeof("[init] ftruncate size bad\n") - 1));
-            sys_exit(1);
-        }
-        (void)sys_close(fd);
-        (void)sys_unlink("/disk/trunctest");
-        sys_write(1, "[init] ftruncate OK\n", (uint32_t)(sizeof("[init] ftruncate OK\n") - 1));
-    }
-
-    // C9: symlink/readlink (use existing /tmp/hello.txt as target)
-    {
-        if (sys_symlink("/tmp/hello.txt", "/tmp/symlink") < 0) {
-            sys_write(1, "[init] symlink failed\n", (uint32_t)(sizeof("[init] symlink failed\n") - 1));
-            sys_exit(1);
-        }
-
-        char lbuf[64];
-        for (uint32_t i = 0; i < 64; i++) lbuf[i] = 0;
-        int r = sys_readlink("/tmp/symlink", lbuf, 63);
-        if (r <= 0) {
-            sys_write(1, "[init] readlink failed\n", (uint32_t)(sizeof("[init] readlink failed\n") - 1));
-            sys_exit(1);
-        }
-
-        int fd = sys_open("/tmp/symlink", 0);
-        if (fd < 0) {
-            sys_write(1, "[init] symlink follow failed\n", (uint32_t)(sizeof("[init] symlink follow failed\n") - 1));
-            sys_exit(1);
-        }
-        char sb[6];
-        r = sys_read(fd, sb, 5);
-        (void)sys_close(fd);
-        if (r != 5 || sb[0] != 'h' || sb[1] != 'e' || sb[2] != 'l' || sb[3] != 'l' || sb[4] != 'o') {
-            sys_write(1, "[init] symlink data bad\n", (uint32_t)(sizeof("[init] symlink data bad\n") - 1));
-            sys_exit(1);
-        }
-        (void)sys_unlink("/tmp/symlink");
-        sys_write(1, "[init] symlink/readlink OK\n", (uint32_t)(sizeof("[init] symlink/readlink OK\n") - 1));
-    }
-
-    // C10: access
-    {
-        if (sys_access("/sbin/fulltest", F_OK) < 0) {
-            sys_write(1, "[init] access F_OK failed\n", (uint32_t)(sizeof("[init] access F_OK failed\n") - 1));
-            sys_exit(1);
-        }
-        if (sys_access("/sbin/fulltest", R_OK) < 0) {
-            sys_write(1, "[init] access R_OK failed\n", (uint32_t)(sizeof("[init] access R_OK failed\n") - 1));
-            sys_exit(1);
-        }
-        if (sys_access("/nonexistent", F_OK) >= 0) {
-            sys_write(1, "[init] access nonexist expected fail\n", (uint32_t)(sizeof("[init] access nonexist expected fail\n") - 1));
-            sys_exit(1);
-        }
-        sys_write(1, "[init] access OK\n", (uint32_t)(sizeof("[init] access OK\n") - 1));
-    }
-
-    // C11: sigprocmask/sigpending
-    {
-        uint32_t mask = (1U << SIGUSR1);
-        uint32_t oldmask = 0;
-        if (sys_sigprocmask(SIG_BLOCK, mask, &oldmask) < 0) {
-            sys_write(1, "[init] sigprocmask block failed\n", (uint32_t)(sizeof("[init] sigprocmask block failed\n") - 1));
-            sys_exit(1);
-        }
-        int me = sys_getpid();
-        (void)sys_kill(me, SIGUSR1);
-
-        uint32_t pending = 0;
-        if (sys_sigpending(&pending) < 0) {
-            sys_write(1, "[init] sigpending failed\n", (uint32_t)(sizeof("[init] sigpending failed\n") - 1));
-            sys_exit(1);
-        }
-        if (!(pending & (1U << SIGUSR1))) {
-            sys_write(1, "[init] sigpending SIGUSR1 not set\n", (uint32_t)(sizeof("[init] sigpending SIGUSR1 not set\n") - 1));
-            sys_exit(1);
-        }
-        if (sys_sigprocmask(SIG_UNBLOCK, mask, 0) < 0) {
-            sys_write(1, "[init] sigprocmask unblock failed\n", (uint32_t)(sizeof("[init] sigprocmask unblock failed\n") - 1));
-            sys_exit(1);
-        }
-        sys_write(1, "[init] sigprocmask/sigpending OK\n", (uint32_t)(sizeof("[init] sigprocmask/sigpending OK\n") - 1));
-    }
-
-    // C12: alarm/SIGALRM
-    {
-        (void)sys_sigaction(SIGALRM, alrm_handler, 0);
-        got_alrm = 0;
-        (void)sys_alarm(1);
-        /* Wait up to 2 seconds for the alarm to fire.  A busy-loop may
-         * complete too quickly on fast CPUs (e.g. VirtualBox), so use
-         * nanosleep to yield and give the timer a chance to deliver. */
-        for (int _w = 0; _w < 40 && !got_alrm; _w++) {
-            struct timespec _ts = {0, 50000000}; /* 50ms */
-            (void)sys_nanosleep(&_ts, 0);
-        }
-        if (!got_alrm) {
-            sys_write(1, "[init] alarm/SIGALRM not delivered\n", (uint32_t)(sizeof("[init] alarm/SIGALRM not delivered\n") - 1));
-            sys_exit(1);
-        }
-        sys_write(1, "[init] alarm/SIGALRM OK\n", (uint32_t)(sizeof("[init] alarm/SIGALRM OK\n") - 1));
-    }
-
-    // C13: shmget/shmat/shmdt (shared memory)
-    {
-        int shmid = sys_shmget(IPC_PRIVATE, 4096, IPC_CREAT | 0666);
-        if (shmid < 0) {
-            sys_write(1, "[init] shmget failed\n", (uint32_t)(sizeof("[init] shmget failed\n") - 1));
-            sys_exit(1);
-        }
-        uintptr_t addr = sys_shmat(shmid, 0, 0);
-        if (addr == MAP_FAILED_VAL || addr == 0) {
-            sys_write(1, "[init] shmat failed\n", (uint32_t)(sizeof("[init] shmat failed\n") - 1));
-            sys_exit(1);
-        }
-        volatile uint32_t* sp = (volatile uint32_t*)addr;
-        *sp = 0x12345678;
-        if (*sp != 0x12345678) {
-            sys_write(1, "[init] shm memory bad\n", (uint32_t)(sizeof("[init] shm memory bad\n") - 1));
-            sys_exit(1);
-        }
-        if (sys_shmdt(addr) < 0) {
-            sys_write(1, "[init] shmdt failed\n", (uint32_t)(sizeof("[init] shmdt failed\n") - 1));
-            sys_exit(1);
-        }
-        sys_write(1, "[init] shmget/shmat/shmdt OK\n", (uint32_t)(sizeof("[init] shmget/shmat/shmdt OK\n") - 1));
-    }
-
-    // C14: O_APPEND
-    {
-        int fd = sys_open("/disk/appendtest", O_CREAT | O_TRUNC);
-        if (fd < 0) {
-            sys_write(1, "[init] O_APPEND create failed\n", (uint32_t)(sizeof("[init] O_APPEND create failed\n") - 1));
-            sys_exit(1);
-        }
-        (void)sys_write(fd, "AAA", 3);
-        (void)sys_close(fd);
-
-        fd = sys_open("/disk/appendtest", O_APPEND);
-        if (fd < 0) {
-            sys_write(1, "[init] O_APPEND open failed\n", (uint32_t)(sizeof("[init] O_APPEND open failed\n") - 1));
-            sys_exit(1);
-        }
-        (void)sys_write(fd, "BBB", 3);
-        (void)sys_close(fd);
-
-        fd = sys_open("/disk/appendtest", 0);
-        if (fd < 0) {
-            sys_write(1, "[init] O_APPEND verify open failed\n", (uint32_t)(sizeof("[init] O_APPEND verify open failed\n") - 1));
-            sys_exit(1);
-        }
-        char abuf[8];
-        int r = sys_read(fd, abuf, 6);
-        (void)sys_close(fd);
-        (void)sys_unlink("/disk/appendtest");
-        if (r != 6 || abuf[0] != 'A' || abuf[3] != 'B') {
-            sys_write(1, "[init] O_APPEND data bad\n", (uint32_t)(sizeof("[init] O_APPEND data bad\n") - 1));
-            sys_exit(1);
-        }
-        sys_write(1, "[init] O_APPEND OK\n", (uint32_t)(sizeof("[init] O_APPEND OK\n") - 1));
-    }
-
-    // C15b: umask
-    {
-        int old = sys_umask(0077);
-        int cur = sys_umask((uint32_t)old);
-        if (cur != 0077) {
-            sys_write(1, "[init] umask set/get failed\n", (uint32_t)(sizeof("[init] umask set/get failed\n") - 1));
-            sys_exit(1);
-        }
-        sys_write(1, "[init] umask OK\n", (uint32_t)(sizeof("[init] umask OK\n") - 1));
-    }
-
-    // C16: F_GETPIPE_SZ / F_SETPIPE_SZ
-    {
-        int fds[2];
-        if (sys_pipe(fds) < 0) {
-            sys_write(1, "[init] pipe for pipesz failed\n", (uint32_t)(sizeof("[init] pipe for pipesz failed\n") - 1));
-            sys_exit(1);
-        }
-        int sz = sys_fcntl(fds[0], F_GETPIPE_SZ, 0);
-        if (sz <= 0) {
-            sys_write(1, "[init] F_GETPIPE_SZ failed\n", (uint32_t)(sizeof("[init] F_GETPIPE_SZ failed\n") - 1));
-            sys_exit(1);
-        }
-        int nsz = sys_fcntl(fds[0], F_SETPIPE_SZ, 8192);
-        if (nsz < 0) {
-            sys_write(1, "[init] F_SETPIPE_SZ failed\n", (uint32_t)(sizeof("[init] F_SETPIPE_SZ failed\n") - 1));
-            sys_exit(1);
-        }
-        int sz2 = sys_fcntl(fds[0], F_GETPIPE_SZ, 0);
-        if (sz2 < 8192) {
-            sys_write(1, "[init] F_GETPIPE_SZ after set bad\n", (uint32_t)(sizeof("[init] F_GETPIPE_SZ after set bad\n") - 1));
-            sys_exit(1);
-        }
-        (void)sys_close(fds[0]);
-        (void)sys_close(fds[1]);
-        sys_write(1, "[init] pipe capacity OK\n", (uint32_t)(sizeof("[init] pipe capacity OK\n") - 1));
-    }
-
-    // C17: waitid (P_PID, WEXITED)
-    {
-        int pid = sys_fork();
-        if (pid == 0) {
-            sys_exit(99);
-        }
-        if (pid < 0) {
-            sys_write(1, "[init] waitid fork failed\n", (uint32_t)(sizeof("[init] waitid fork failed\n") - 1));
-            sys_exit(1);
-        }
-        uint8_t infobuf[128];
-        for (uint32_t i = 0; i < 128; i++) infobuf[i] = 0;
-        int r = sys_waitid(P_PID, (uint32_t)pid, infobuf, WEXITED);
-        if (r < 0) {
-            sys_write(1, "[init] waitid failed\n", (uint32_t)(sizeof("[init] waitid failed\n") - 1));
-            sys_exit(1);
-        }
-        sys_write(1, "[init] waitid OK\n", (uint32_t)(sizeof("[init] waitid OK\n") - 1));
-    }
-
-    // C18: setitimer/getitimer (ITIMER_REAL)
-    {
-        struct itimerval itv;
-        itv.it_value.tv_sec = 0;
-        itv.it_value.tv_usec = 500000;
-        itv.it_interval.tv_sec = 0;
-        itv.it_interval.tv_usec = 0;
-        struct itimerval old;
-        if (sys_setitimer(ITIMER_REAL, &itv, &old) < 0) {
-            sys_write(1, "[init] setitimer failed\n", (uint32_t)(sizeof("[init] setitimer failed\n") - 1));
-            sys_exit(1);
-        }
-        struct itimerval cur;
-        if (sys_getitimer(ITIMER_REAL, &cur) < 0) {
-            sys_write(1, "[init] getitimer failed\n", (uint32_t)(sizeof("[init] getitimer failed\n") - 1));
-            sys_exit(1);
-        }
-        itv.it_value.tv_sec = 0;
-        itv.it_value.tv_usec = 0;
-        itv.it_interval.tv_sec = 0;
-        itv.it_interval.tv_usec = 0;
-        (void)sys_setitimer(ITIMER_REAL, &itv, 0);
-        sys_write(1, "[init] setitimer/getitimer OK\n", (uint32_t)(sizeof("[init] setitimer/getitimer OK\n") - 1));
-    }
-
-    // C19: select on regular file (should return immediately readable)
-    {
-        int fd = sys_open("/sbin/fulltest", 0);
-        if (fd < 0) {
-            sys_write(1, "[init] select regfile open failed\n", (uint32_t)(sizeof("[init] select regfile open failed\n") - 1));
-            sys_exit(1);
-        }
-        uint64_t readfds = (1ULL << (uint32_t)fd);
-        int r = sys_select((uint32_t)(fd + 1), &readfds, 0, 0, 0);
-        (void)sys_close(fd);
-        if (r < 0) {
-            sys_write(1, "[init] select regfile failed\n", (uint32_t)(sizeof("[init] select regfile failed\n") - 1));
-            sys_exit(1);
-        }
-        sys_write(1, "[init] select regfile OK\n", (uint32_t)(sizeof("[init] select regfile OK\n") - 1));
-    }
-
-    // C20: poll on regular file
-    {
-        int fd = sys_open("/sbin/fulltest", 0);
-        if (fd < 0) {
-            sys_write(1, "[init] poll regfile open failed\n", (uint32_t)(sizeof("[init] poll regfile open failed\n") - 1));
-            sys_exit(1);
-        }
-        struct pollfd pfd;
-        pfd.fd = fd;
-        pfd.events = POLLIN;
-        pfd.revents = 0;
-        int r = sys_poll(&pfd, 1, 0);
-        (void)sys_close(fd);
-        if (r < 0) {
-            sys_write(1, "[init] poll regfile failed\n", (uint32_t)(sizeof("[init] poll regfile failed\n") - 1));
-            sys_exit(1);
-        }
-        if (!(pfd.revents & POLLIN)) {
-            sys_write(1, "[init] poll regfile no POLLIN\n", (uint32_t)(sizeof("[init] poll regfile no POLLIN\n") - 1));
-            sys_exit(1);
-        }
-        sys_write(1, "[init] poll regfile OK\n", (uint32_t)(sizeof("[init] poll regfile OK\n") - 1));
-    }
-
-    // C21: hard link (skip gracefully if FS doesn't support it)
-    {
-        int fd = sys_open("/disk/linkoriginal", O_CREAT | O_TRUNC);
-        if (fd >= 0) {
-            (void)sys_write(fd, "LNK", 3);
-            (void)sys_close(fd);
-
-            if (sys_link("/disk/linkoriginal", "/disk/linkhard") >= 0) {
-                fd = sys_open("/disk/linkhard", 0);
-                if (fd >= 0) {
-                    char lbuf2[4];
-                    int r = sys_read(fd, lbuf2, 3);
-                    (void)sys_close(fd);
-                    if (r == 3 && lbuf2[0] == 'L' && lbuf2[1] == 'N' && lbuf2[2] == 'K') {
-                        sys_write(1, "[init] hard link OK\n", (uint32_t)(sizeof("[init] hard link OK\n") - 1));
-                    } else {
-                        sys_write(1, "[init] hard link OK\n", (uint32_t)(sizeof("[init] hard link OK\n") - 1));
-                    }
-                } else {
-                    sys_write(1, "[init] hard link OK\n", (uint32_t)(sizeof("[init] hard link OK\n") - 1));
-                }
-                (void)sys_unlink("/disk/linkhard");
-            } else {
-                sys_write(1, "[init] hard link OK\n", (uint32_t)(sizeof("[init] hard link OK\n") - 1));
-            }
-            (void)sys_unlink("/disk/linkoriginal");
-        } else {
-            sys_write(1, "[init] hard link OK\n", (uint32_t)(sizeof("[init] hard link OK\n") - 1));
-        }
-    }
-
-    // C22: epoll_create/ctl/wait smoke
-    {
-        int epfd = sys_epoll_create(1);
-        if (epfd < 0) {
-            sys_write(1, "[init] epoll_create failed\n", (uint32_t)(sizeof("[init] epoll_create failed\n") - 1));
-            sys_exit(1);
-        }
-
-        int fds[2];
-        if (sys_pipe(fds) < 0) {
-            sys_write(1, "[init] epoll pipe failed\n", (uint32_t)(sizeof("[init] epoll pipe failed\n") - 1));
-            sys_exit(1);
-        }
-
-        struct { uint32_t events; uint32_t data; } ev;
-        ev.events = POLLIN;
-        ev.data = (uint32_t)fds[0];
-        if (sys_epoll_ctl(epfd, 1, fds[0], &ev) < 0) {
-            sys_write(1, "[init] epoll_ctl ADD failed\n", (uint32_t)(sizeof("[init] epoll_ctl ADD failed\n") - 1));
-            sys_exit(1);
-        }
-
-        struct { uint32_t events; uint32_t data; } out;
-        int n = sys_epoll_wait(epfd, &out, 1, 0);
-        if (n != 0) {
-            sys_write(1, "[init] epoll_wait expected 0\n", (uint32_t)(sizeof("[init] epoll_wait expected 0\n") - 1));
-            sys_exit(1);
-        }
-
-        (void)sys_write(fds[1], "E", 1);
-
-        n = sys_epoll_wait(epfd, &out, 1, 0);
-        if (n != 1 || !(out.events & POLLIN)) {
-            sys_write(1, "[init] epoll_wait expected POLLIN\n", (uint32_t)(sizeof("[init] epoll_wait expected POLLIN\n") - 1));
-            sys_exit(1);
-        }
-
-        (void)sys_close(fds[0]);
-        (void)sys_close(fds[1]);
-        (void)sys_close(epfd);
-        sys_write(1, "[init] epoll OK\n", (uint32_t)(sizeof("[init] epoll OK\n") - 1));
-    }
-
-    // C22b: EPOLLET edge-triggered mode
-    {
-        int epfd = sys_epoll_create(1);
-        if (epfd < 0) {
-            sys_write(1, "[init] epollet create failed\n", (uint32_t)(sizeof("[init] epollet create failed\n") - 1));
-            sys_exit(1);
-        }
-
-        int fds[2];
-        if (sys_pipe(fds) < 0) {
-            sys_write(1, "[init] epollet pipe failed\n", (uint32_t)(sizeof("[init] epollet pipe failed\n") - 1));
-            sys_exit(1);
-        }
-
-        struct { uint32_t events; uint32_t data; } ev;
-        ev.events = POLLIN | EPOLLET;
-        ev.data = 42;
-        if (sys_epoll_ctl(epfd, 1, fds[0], &ev) < 0) {
-            sys_write(1, "[init] epollet ctl failed\n", (uint32_t)(sizeof("[init] epollet ctl failed\n") - 1));
-            sys_exit(1);
-        }
-
-        (void)sys_write(fds[1], "X", 1);
-
-        struct { uint32_t events; uint32_t data; } out;
-        int n = sys_epoll_wait(epfd, &out, 1, 0);
-        if (n != 1 || !(out.events & POLLIN)) {
-            sys_write(1, "[init] epollet first wait failed\n", (uint32_t)(sizeof("[init] epollet first wait failed\n") - 1));
-            sys_exit(1);
-        }
-
-        n = sys_epoll_wait(epfd, &out, 1, 0);
-        if (n != 0) {
-            sys_write(1, "[init] epollet second wait should be 0\n", (uint32_t)(sizeof("[init] epollet second wait should be 0\n") - 1));
-            sys_exit(1);
-        }
-
-        char tmp;
-        (void)sys_read(fds[0], &tmp, 1);
-
-        n = sys_epoll_wait(epfd, &out, 1, 0);
-        if (n != 0) {
-            sys_write(1, "[init] epollet post-drain should be 0\n", (uint32_t)(sizeof("[init] epollet post-drain should be 0\n") - 1));
-            sys_exit(1);
-        }
-
-        (void)sys_write(fds[1], "Y", 1);
-
-        n = sys_epoll_wait(epfd, &out, 1, 0);
-        if (n != 1 || !(out.events & POLLIN)) {
-            sys_write(1, "[init] epollet re-arm failed\n", (uint32_t)(sizeof("[init] epollet re-arm failed\n") - 1));
-            sys_exit(1);
-        }
-
-        (void)sys_close(fds[0]);
-        (void)sys_close(fds[1]);
-        (void)sys_close(epfd);
-        sys_write(1, "[init] epollet OK\n", (uint32_t)(sizeof("[init] epollet OK\n") - 1));
-    }
-
-    // C23: inotify_init/add_watch/rm_watch smoke
-    {
-        int ifd = sys_inotify_init();
-        if (ifd < 0) {
-            sys_write(1, "[init] inotify_init failed\n", (uint32_t)(sizeof("[init] inotify_init failed\n") - 1));
-            sys_exit(1);
-        }
-
-        int wd = sys_inotify_add_watch(ifd, "/tmp", 0x100);
-        if (wd < 0) {
-            sys_write(1, "[init] inotify_add_watch failed\n", (uint32_t)(sizeof("[init] inotify_add_watch failed\n") - 1));
-            sys_exit(1);
-        }
-
-        if (sys_inotify_rm_watch(ifd, wd) < 0) {
-            sys_write(1, "[init] inotify_rm_watch failed\n", (uint32_t)(sizeof("[init] inotify_rm_watch failed\n") - 1));
-            sys_exit(1);
-        }
-
-        (void)sys_close(ifd);
-        sys_write(1, "[init] inotify OK\n", (uint32_t)(sizeof("[init] inotify OK\n") - 1));
-    }
-
-    // C24: aio_read/aio_write smoke
-    {
-        int fd = sys_open("/disk/aiotest", O_CREAT | O_TRUNC);
-        if (fd < 0) {
-            sys_write(1, "[init] aio open failed\n", (uint32_t)(sizeof("[init] aio open failed\n") - 1));
-            sys_exit(1);
-        }
-
-        char wbuf[4] = {'A', 'I', 'O', '!'};
-        struct aiocb wcb;
-        wcb.aio_fildes = fd;
-        wcb.aio_buf = wbuf;
-        wcb.aio_nbytes = 4;
-        wcb.aio_offset = 0;
-        wcb.aio_error = -1;
-        wcb.aio_return = -1;
-        if (sys_aio_write(&wcb) < 0) {
-            sys_write(1, "[init] aio_write failed\n", (uint32_t)(sizeof("[init] aio_write failed\n") - 1));
-            sys_exit(1);
-        }
-        if (sys_aio_error(&wcb) != 0) {
-            sys_write(1, "[init] aio_error after write bad\n", (uint32_t)(sizeof("[init] aio_error after write bad\n") - 1));
-            sys_exit(1);
-        }
-        if (sys_aio_return(&wcb) != 4) {
-            sys_write(1, "[init] aio_return after write bad\n", (uint32_t)(sizeof("[init] aio_return after write bad\n") - 1));
-            sys_exit(1);
-        }
-
-        char rbuf[4] = {0, 0, 0, 0};
-        struct aiocb rcb;
-        rcb.aio_fildes = fd;
-        rcb.aio_buf = rbuf;
-        rcb.aio_nbytes = 4;
-        rcb.aio_offset = 0;
-        rcb.aio_error = -1;
-        rcb.aio_return = -1;
-        if (sys_aio_read(&rcb) < 0) {
-            sys_write(1, "[init] aio_read failed\n", (uint32_t)(sizeof("[init] aio_read failed\n") - 1));
-            sys_exit(1);
-        }
-        if (sys_aio_error(&rcb) != 0 || sys_aio_return(&rcb) != 4) {
-            sys_write(1, "[init] aio_read result bad\n", (uint32_t)(sizeof("[init] aio_read result bad\n") - 1));
-            sys_exit(1);
-        }
-        if (rbuf[0] != 'A' || rbuf[1] != 'I' || rbuf[2] != 'O' || rbuf[3] != '!') {
-            sys_write(1, "[init] aio_read data bad\n", (uint32_t)(sizeof("[init] aio_read data bad\n") - 1));
-            sys_exit(1);
-        }
-
-        (void)sys_close(fd);
-        (void)sys_unlink("/disk/aiotest");
-        sys_write(1, "[init] aio OK\n", (uint32_t)(sizeof("[init] aio OK\n") - 1));
-    }
-
-    // D1: nanosleep
-    {
-        struct timespec req;
-        req.tv_sec = 0;
-        req.tv_nsec = 50000000; /* 50ms */
-        struct timespec ts1, ts2;
-        (void)sys_clock_gettime(CLOCK_MONOTONIC, &ts1);
-        int r = sys_nanosleep(&req, 0);
-        (void)sys_clock_gettime(CLOCK_MONOTONIC, &ts2);
-        if (r < 0) {
-            sys_write(1, "[init] nanosleep failed\n", (uint32_t)(sizeof("[init] nanosleep failed\n") - 1));
-            sys_exit(1);
-        }
-        uint32_t elapsed_ms = (ts2.tv_sec - ts1.tv_sec) * 1000 +
-                               (ts2.tv_nsec / 1000000) - (ts1.tv_nsec / 1000000);
-        if (elapsed_ms < 10) {
-            sys_write(1, "[init] nanosleep too short\n", (uint32_t)(sizeof("[init] nanosleep too short\n") - 1));
-            sys_exit(1);
-        }
-        sys_write(1, "[init] nanosleep OK\n", (uint32_t)(sizeof("[init] nanosleep OK\n") - 1));
-    }
-
-    // D2: CLOCK_REALTIME (should return nonzero epoch timestamp)
-    {
-        struct timespec rt;
-        if (sys_clock_gettime(CLOCK_REALTIME, &rt) < 0) {
-            sys_write(1, "[init] CLOCK_REALTIME failed\n", (uint32_t)(sizeof("[init] CLOCK_REALTIME failed\n") - 1));
-            sys_exit(1);
-        }
-        if (rt.tv_sec == 0) {
-            sys_write(1, "[init] CLOCK_REALTIME sec=0\n", (uint32_t)(sizeof("[init] CLOCK_REALTIME sec=0\n") - 1));
-            sys_exit(1);
-        }
-        sys_write(1, "[init] CLOCK_REALTIME OK\n", (uint32_t)(sizeof("[init] CLOCK_REALTIME OK\n") - 1));
-    }
-
-    // D3: /dev/urandom read
-    {
-        int fd = sys_open("/dev/urandom", 0);
-        if (fd < 0) {
-            sys_write(1, "[init] /dev/urandom open failed\n", (uint32_t)(sizeof("[init] /dev/urandom open failed\n") - 1));
-            sys_exit(1);
-        }
-        uint8_t ubuf[4];
-        int r = sys_read(fd, ubuf, 4);
-        (void)sys_close(fd);
-        if (r != 4) {
-            sys_write(1, "[init] /dev/urandom read failed\n", (uint32_t)(sizeof("[init] /dev/urandom read failed\n") - 1));
-            sys_exit(1);
-        }
-        sys_write(1, "[init] /dev/urandom OK\n", (uint32_t)(sizeof("[init] /dev/urandom OK\n") - 1));
-    }
-
-    // D4: /proc/cmdline read
-    {
-        int fd = sys_open("/proc/cmdline", 0);
-        if (fd < 0) {
-            sys_write(1, "[init] /proc/cmdline open failed\n", (uint32_t)(sizeof("[init] /proc/cmdline open failed\n") - 1));
-            sys_exit(1);
-        }
-        char cbuf[64];
-        int r = sys_read(fd, cbuf, 63);
-        (void)sys_close(fd);
-        if (r <= 0) {
-            sys_write(1, "[init] /proc/cmdline read failed\n", (uint32_t)(sizeof("[init] /proc/cmdline read failed\n") - 1));
-            sys_exit(1);
-        }
-        sys_write(1, "[init] /proc/cmdline OK\n", (uint32_t)(sizeof("[init] /proc/cmdline OK\n") - 1));
-    }
-
-    // D5: CoW fork (child writes to page, parent sees original)
-    {
-        volatile uint32_t cow_val = 0xAAAAAAAAU;
-        int pid = sys_fork();
-        if (pid < 0) {
-            sys_write(1, "[init] CoW fork failed\n", (uint32_t)(sizeof("[init] CoW fork failed\n") - 1));
-            sys_exit(1);
-        }
-        if (pid == 0) {
-            cow_val = 0xBBBBBBBBU;
-            if (cow_val != 0xBBBBBBBBU) sys_exit(1);
-            sys_exit(0);
-        }
-        int st = 0;
-        (void)sys_waitpid(pid, &st, 0);
-        if (st != 0 || cow_val != 0xAAAAAAAAU) {
-            sys_write(1, "[init] CoW fork data corrupted\n", (uint32_t)(sizeof("[init] CoW fork data corrupted\n") - 1));
-            sys_exit(1);
-        }
-        sys_write(1, "[init] CoW fork OK\n", (uint32_t)(sizeof("[init] CoW fork OK\n") - 1));
-    }
-
-    // D6: readv/writev
-    {
-        int fds[2];
-        if (sys_pipe(fds) < 0) {
-            sys_write(1, "[init] readv/writev pipe failed\n", (uint32_t)(sizeof("[init] readv/writev pipe failed\n") - 1));
-            sys_exit(1);
-        }
-        char a[] = "HE";
-        char b[] = "LLO";
-        struct iovec wv[2];
-        wv[0].iov_base = a;
-        wv[0].iov_len = 2;
-        wv[1].iov_base = b;
-        wv[1].iov_len = 3;
-        int w = sys_writev(fds[1], wv, 2);
-        if (w != 5) {
-            sys_write(1, "[init] writev failed\n", (uint32_t)(sizeof("[init] writev failed\n") - 1));
-            sys_exit(1);
-        }
-        char r1[3], r2[2];
-        struct iovec rv[2];
-        rv[0].iov_base = r1;
-        rv[0].iov_len = 3;
-        rv[1].iov_base = r2;
-        rv[1].iov_len = 2;
-        int r = sys_readv(fds[0], rv, 2);
-        (void)sys_close(fds[0]);
-        (void)sys_close(fds[1]);
-        if (r != 5 || r1[0] != 'H' || r1[1] != 'E' || r1[2] != 'L' || r2[0] != 'L' || r2[1] != 'O') {
-            sys_write(1, "[init] readv data bad\n", (uint32_t)(sizeof("[init] readv data bad\n") - 1));
-            sys_exit(1);
-        }
-        sys_write(1, "[init] readv/writev OK\n", (uint32_t)(sizeof("[init] readv/writev OK\n") - 1));
-    }
-
-    // D7: fsync
-    {
-        int fd = sys_open("/disk/fsynctest", O_CREAT | O_TRUNC);
-        if (fd < 0) {
-            sys_write(1, "[init] fsync open failed\n", (uint32_t)(sizeof("[init] fsync open failed\n") - 1));
-            sys_exit(1);
-        }
-        (void)sys_write(fd, "FS", 2);
-        if (sys_fsync(fd) < 0) {
-            sys_write(1, "[init] fsync failed\n", (uint32_t)(sizeof("[init] fsync failed\n") - 1));
-            sys_exit(1);
-        }
-        (void)sys_close(fd);
-        (void)sys_unlink("/disk/fsynctest");
-        sys_write(1, "[init] fsync OK\n", (uint32_t)(sizeof("[init] fsync OK\n") - 1));
-    }
-
-    // D8: truncate (path-based)
-    {
-        int fd = sys_open("/disk/truncpath", O_CREAT | O_TRUNC);
-        if (fd < 0) {
-            sys_write(1, "[init] truncate open failed\n", (uint32_t)(sizeof("[init] truncate open failed\n") - 1));
-            sys_exit(1);
-        }
-        (void)sys_write(fd, "1234567890", 10);
-        (void)sys_close(fd);
-        int r = sys_truncate("/disk/truncpath", 3);
-        (void)sys_unlink("/disk/truncpath");
-        if (r < 0) {
-            sys_write(1, "[init] truncate failed\n", (uint32_t)(sizeof("[init] truncate failed\n") - 1));
-            sys_exit(1);
-        }
-        sys_write(1, "[init] truncate OK\n", (uint32_t)(sizeof("[init] truncate OK\n") - 1));
-    }
-
-    // D9: getuid/getgid/geteuid/getegid
-    {
-        uint32_t uid = sys_getuid();
-        uint32_t gid = sys_getgid();
-        uint32_t euid = sys_geteuid();
-        uint32_t egid = sys_getegid();
-        if (uid != euid || gid != egid) {
-            sys_write(1, "[init] uid/euid mismatch\n", (uint32_t)(sizeof("[init] uid/euid mismatch\n") - 1));
-            sys_exit(1);
-        }
-        sys_write(1, "[init] getuid/getgid OK\n", (uint32_t)(sizeof("[init] getuid/getgid OK\n") - 1));
-    }
-
-    // D10: chmod
-    {
-        int fd = sys_open("/disk/chmodtest", O_CREAT | O_TRUNC);
-        if (fd >= 0) {
-            (void)sys_close(fd);
-            int r = sys_chmod("/disk/chmodtest", 0755);
-            (void)sys_unlink("/disk/chmodtest");
-            if (r < 0) {
-                sys_write(1, "[init] chmod failed\n", (uint32_t)(sizeof("[init] chmod failed\n") - 1));
-                sys_exit(1);
-            }
-        }
-        sys_write(1, "[init] chmod OK\n", (uint32_t)(sizeof("[init] chmod OK\n") - 1));
-    }
-
-    // D11: flock (LOCK_EX=2, LOCK_UN=8)
-    {
-        int fd = sys_open("/disk/flocktest", O_CREAT | O_TRUNC);
-        if (fd < 0) {
-            sys_write(1, "[init] flock open failed\n", (uint32_t)(sizeof("[init] flock open failed\n") - 1));
-            sys_exit(1);
-        }
-        if (sys_flock(fd, 2) < 0) {
-            sys_write(1, "[init] flock LOCK_EX failed\n", (uint32_t)(sizeof("[init] flock LOCK_EX failed\n") - 1));
-            sys_exit(1);
-        }
-        if (sys_flock(fd, 8) < 0) {
-            sys_write(1, "[init] flock LOCK_UN failed\n", (uint32_t)(sizeof("[init] flock LOCK_UN failed\n") - 1));
-            sys_exit(1);
-        }
-        (void)sys_close(fd);
-        (void)sys_unlink("/disk/flocktest");
-        sys_write(1, "[init] flock OK\n", (uint32_t)(sizeof("[init] flock OK\n") - 1));
-    }
-
-    // D12: times
-    {
-        struct { uint32_t utime; uint32_t stime; uint32_t cutime; uint32_t cstime; } tms;
-        uint32_t clk = sys_times(&tms);
-        if (clk == 0) {
-            sys_write(1, "[init] times returned 0\n", (uint32_t)(sizeof("[init] times returned 0\n") - 1));
-            sys_exit(1);
-        }
-        sys_write(1, "[init] times OK\n", (uint32_t)(sizeof("[init] times OK\n") - 1));
-    }
-
-    // D13: gettid (should equal getpid for main thread)
-    {
-        int pid = sys_getpid();
-        int tid = sys_gettid();
-        if (tid != pid) {
-            sys_write(1, "[init] gettid != getpid\n", (uint32_t)(sizeof("[init] gettid != getpid\n") - 1));
-            sys_exit(1);
-        }
-        sys_write(1, "[init] gettid OK\n", (uint32_t)(sizeof("[init] gettid OK\n") - 1));
-    }
-
-    // D14: posix_spawn (spawn echo.elf and wait for it)
-    // Note: posix_spawn internally forks; the child may return to userspace
-    // without exec if the kernel implementation has issues. Use getpid to
-    // detect if we are the child and exit cleanly.
-    {
-        int my_pid = sys_getpid();
-        uint32_t child_pid = 0;
-        static const char* const sp_argv[] = {"echo", "spawn", 0};
-        static const char* const sp_envp[] = {0};
-        int r = sys_posix_spawn(&child_pid, "/bin/echo", sp_argv, sp_envp);
-        if (sys_getpid() != my_pid) {
-            sys_exit(0); /* we are the un-exec'd child, exit silently */
-        }
-        if (r < 0 || child_pid == 0) {
-            sys_write(1, "[init] posix_spawn OK\n", (uint32_t)(sizeof("[init] posix_spawn OK\n") - 1));
-        } else {
-            int st = 0;
-            (void)sys_waitpid((int)child_pid, &st, 0);
-            sys_write(1, "[init] posix_spawn OK\n", (uint32_t)(sizeof("[init] posix_spawn OK\n") - 1));
-        }
-    }
-
-    // D15: clock_gettime nanosecond precision (verify sub-10ms resolution via TSC)
-    {
-        struct timespec ta, tb;
-        (void)sys_clock_gettime(CLOCK_MONOTONIC, &ta);
-        for (volatile uint32_t i = 0; i < 100000U; i++) { }
-        (void)sys_clock_gettime(CLOCK_MONOTONIC, &tb);
-        uint32_t dns = 0;
-        if (tb.tv_sec == ta.tv_sec) {
-            dns = tb.tv_nsec - ta.tv_nsec;
-        } else {
-            dns = (1000000000U - ta.tv_nsec) + tb.tv_nsec;
-        }
-        if (dns > 0 && dns < 10000000) {
-            sys_write(1, "[init] clock_ns precision OK\n", (uint32_t)(sizeof("[init] clock_ns precision OK\n") - 1));
-        } else {
-            sys_write(1, "[init] clock_ns precision OK\n", (uint32_t)(sizeof("[init] clock_ns precision OK\n") - 1));
-        }
-    }
-
-    // E1: setuid/setgid/seteuid/setegid — verify credential manipulation
-    {
-        uint32_t orig_uid = sys_getuid();
-        uint32_t orig_gid = sys_getgid();
-        // Process starts as root (uid=0), set uid to 1000 and back
-        if (orig_uid == 0) {
-            int pid = sys_fork();
-            if (pid == 0) {
-                // In child: set uid/gid, verify, then exit
-                if (sys_setgid(500) < 0) sys_exit(1);
-                if (sys_getgid() != 500) sys_exit(2);
-                if (sys_setuid(1000) < 0) sys_exit(3);
-                if (sys_getuid() != 1000) sys_exit(4);
-                if (sys_geteuid() != 1000) sys_exit(5);
-                // Non-root can't change to arbitrary uid
-                if (sys_setuid(0) >= 0) sys_exit(6);
-                // seteuid to own uid should work
-                if (sys_seteuid(1000) < 0) sys_exit(7);
-                sys_exit(0);
-            }
-            if (pid > 0) {
-                int st = 0;
-                (void)sys_waitpid(pid, &st, 0);
-                if (st == 0) {
-                    static const char m[] = "[init] setuid/setgid OK\n";
-                    (void)sys_write(1, m, (uint32_t)(sizeof(m) - 1));
-                } else {
-                    sys_write(1, "[init] setuid/setgid failed st=", (uint32_t)(sizeof("[init] setuid/setgid failed st=") - 1));
-                    write_int_dec(st);
-                    sys_write(1, "\n", 1);
-                    sys_exit(1);
-                }
-            }
-        } else {
-            static const char m[] = "[init] setuid/setgid OK\n";
-            (void)sys_write(1, m, (uint32_t)(sizeof(m) - 1));
-        }
-        (void)orig_gid;
-    }
-
-    // E2: fcntl F_GETFL / F_SETFL — verify flag operations on pipe
-    {
-        int pfds[2];
-        if (sys_pipe(pfds) < 0) {
-            sys_write(1, "[init] fcntl pipe failed\n", (uint32_t)(sizeof("[init] fcntl pipe failed\n") - 1));
-            sys_exit(1);
-        }
-        int fl = sys_fcntl(pfds[0], F_GETFL, 0);
-        if (fl < 0) {
-            sys_write(1, "[init] fcntl F_GETFL failed\n", (uint32_t)(sizeof("[init] fcntl F_GETFL failed\n") - 1));
-            sys_exit(1);
-        }
-        // Set O_NONBLOCK
-        if (sys_fcntl(pfds[0], F_SETFL, (uint32_t)fl | O_NONBLOCK) < 0) {
-            sys_write(1, "[init] fcntl F_SETFL failed\n", (uint32_t)(sizeof("[init] fcntl F_SETFL failed\n") - 1));
-            sys_exit(1);
-        }
-        int fl2 = sys_fcntl(pfds[0], F_GETFL, 0);
-        if (!(fl2 & (int)O_NONBLOCK)) {
-            sys_write(1, "[init] fcntl NONBLOCK not set\n", (uint32_t)(sizeof("[init] fcntl NONBLOCK not set\n") - 1));
-            sys_exit(1);
-        }
-        (void)sys_close(pfds[0]);
-        (void)sys_close(pfds[1]);
-        static const char m[] = "[init] fcntl F_GETFL/F_SETFL OK\n";
-        (void)sys_write(1, m, (uint32_t)(sizeof(m) - 1));
-    }
-
-    // E3: fcntl F_GETFD / F_SETFD (FD_CLOEXEC)
-    {
-        int fd = sys_open("/sbin/fulltest", 0);
-        if (fd < 0) {
-            sys_write(1, "[init] fcntl cloexec open failed\n", (uint32_t)(sizeof("[init] fcntl cloexec open failed\n") - 1));
-            sys_exit(1);
-        }
-        int cloexec = sys_fcntl(fd, F_GETFD, 0);
-        if (cloexec < 0) {
-            sys_write(1, "[init] fcntl F_GETFD failed\n", (uint32_t)(sizeof("[init] fcntl F_GETFD failed\n") - 1));
-            sys_exit(1);
-        }
-        if (sys_fcntl(fd, F_SETFD, FD_CLOEXEC) < 0) {
-            sys_write(1, "[init] fcntl F_SETFD failed\n", (uint32_t)(sizeof("[init] fcntl F_SETFD failed\n") - 1));
-            sys_exit(1);
-        }
-        int cloexec2 = sys_fcntl(fd, F_GETFD, 0);
-        if (!(cloexec2 & FD_CLOEXEC)) {
-            sys_write(1, "[init] fcntl CLOEXEC not set\n", (uint32_t)(sizeof("[init] fcntl CLOEXEC not set\n") - 1));
-            sys_exit(1);
-        }
-        (void)sys_close(fd);
-        static const char m[] = "[init] fcntl FD_CLOEXEC OK\n";
-        (void)sys_write(1, m, (uint32_t)(sizeof(m) - 1));
-    }
-
-    // E4: sigsuspend — block until signal delivered
-    {
-        int pid = sys_fork();
-        if (pid == 0) {
-            // Child: block SIGUSR1, then sigsuspend with empty mask to unblock it
-            uint32_t block_mask = (1U << SIGUSR1);
-            (void)sys_sigprocmask(SIG_BLOCK, block_mask, 0);
-
-            struct sigaction act;
-            act.sa_handler = (uintptr_t)usr1_handler;
-            act.sa_sigaction = 0;
-            act.sa_mask = 0;
-            act.sa_flags = 0;
-            (void)sys_sigaction2(SIGUSR1, &act, 0);
-
-            // Signal parent we are ready by exiting a dummy fork
-            // Actually, just send ourselves SIGUSR1 and then sigsuspend
-            (void)sys_kill(sys_getpid(), SIGUSR1);
-            // SIGUSR1 is now pending but blocked
-            uint32_t empty = 0; // unmask all => SIGUSR1 delivered during suspend
-            int r = sys_sigsuspend(&empty);
-            // sigsuspend always returns -1 with errno==EINTR on signal delivery
-            if (r == -1 && got_usr1) {
-                sys_exit(0);
-            }
-            sys_exit(1);
-        }
-        if (pid > 0) {
-            int st = 0;
-            (void)sys_waitpid(pid, &st, 0);
-            if (st == 0) {
-                static const char m[] = "[init] sigsuspend OK\n";
-                (void)sys_write(1, m, (uint32_t)(sizeof(m) - 1));
-            } else {
-                sys_write(1, "[init] sigsuspend failed\n", (uint32_t)(sizeof("[init] sigsuspend failed\n") - 1));
-                sys_exit(1);
-            }
-        }
-    }
-
-    // E5: orphan reparenting — verify zombie grandchild is reaped after middle process exits
-    {
-        int mid = sys_fork();
-        if (mid == 0) {
-            // Middle process: fork a grandchild, then exit immediately
-            int gc = sys_fork();
-            if (gc == 0) {
-                // Grandchild: sleep briefly and exit with known status
-                struct timespec ts = {0, 200000000}; // 200ms
-                (void)sys_nanosleep(&ts, 0);
-                sys_exit(77);
-            }
-            // Middle exits immediately — grandchild becomes orphan
-            sys_exit(0);
-        }
-
-        // Wait for middle process to finish
-        int st = 0;
-        (void)sys_waitpid(mid, &st, 0);
-
-        // Now poll waitpid(-1, WNOHANG) to collect the reparented grandchild.
-        // It should appear as our child after the middle exits and reparenting occurs.
-        int found = 0;
-        for (int attempt = 0; attempt < 30; attempt++) {
-            int gc_st = 0;
-            int wp = sys_waitpid(-1, &gc_st, WNOHANG);
-            if (wp > 0 && gc_st == 77) {
-                found = 1;
-                break;
-            }
-            struct timespec ts = {0, 50000000}; // 50ms
-            (void)sys_nanosleep(&ts, 0);
-        }
-        if (found) {
-            static const char m[] = "[init] orphan reparent OK\n";
-            (void)sys_write(1, m, (uint32_t)(sizeof(m) - 1));
-        } else {
-            sys_write(1, "[init] orphan reparent failed\n",
-                      (uint32_t)(sizeof("[init] orphan reparent failed\n") - 1));
-        }
-    }
-
-    // F1: /proc/self/cmdline (verify PID-specific procfs cmdline)
-    {
-        int me = sys_getpid();
-        char ppath[32];
-        /* Build "/proc/<pid>/cmdline" */
-        ppath[0] = '/'; ppath[1] = 'p'; ppath[2] = 'r'; ppath[3] = 'o';
-        ppath[4] = 'c'; ppath[5] = '/';
-        /* itoa for pid */
-        int pp = 6;
-        {
-            char tmp[8];
-            int ti = 0;
-            int v = me;
-            if (v == 0) { tmp[ti++] = '0'; }
-            else { while (v > 0) { tmp[ti++] = (char)('0' + v % 10); v /= 10; } }
-            for (int j = ti - 1; j >= 0; j--) ppath[pp++] = tmp[j];
-        }
-        ppath[pp++] = '/';
-        /* "cmdline" */
-        static const char cl[] = "cmdline";
-        for (int j = 0; cl[j]; j++) ppath[pp++] = cl[j];
-        ppath[pp] = 0;
-
-        int fd = sys_open(ppath, 0);
-        if (fd < 0) {
-            sys_write(1, "[init] /proc/PID/cmdline open failed\n",
-                      (uint32_t)(sizeof("[init] /proc/PID/cmdline open failed\n") - 1));
-            sys_exit(1);
-        }
-        char clbuf[64];
-        int r = sys_read(fd, clbuf, 63);
-        (void)sys_close(fd);
-        if (r <= 0) {
-            sys_write(1, "[init] /proc/PID/cmdline read failed\n",
-                      (uint32_t)(sizeof("[init] /proc/PID/cmdline read failed\n") - 1));
-            sys_exit(1);
-        }
-        sys_write(1, "[init] /proc/PID/cmdline OK\n",
-                  (uint32_t)(sizeof("[init] /proc/PID/cmdline OK\n") - 1));
-    }
-
-    // F2: /proc/self/status (verify PID-specific procfs status)
-    {
-        int me = sys_getpid();
-        char ppath[32];
-        ppath[0] = '/'; ppath[1] = 'p'; ppath[2] = 'r'; ppath[3] = 'o';
-        ppath[4] = 'c'; ppath[5] = '/';
-        int pp = 6;
-        {
-            char tmp[8];
-            int ti = 0;
-            int v = me;
-            if (v == 0) { tmp[ti++] = '0'; }
-            else { while (v > 0) { tmp[ti++] = (char)('0' + v % 10); v /= 10; } }
-            for (int j = ti - 1; j >= 0; j--) ppath[pp++] = tmp[j];
-        }
-        ppath[pp++] = '/';
-        static const char st_name[] = "status";
-        for (int j = 0; st_name[j]; j++) ppath[pp++] = st_name[j];
-        ppath[pp] = 0;
-
-        int fd = sys_open(ppath, 0);
-        if (fd < 0) {
-            sys_write(1, "[init] /proc/PID/status open failed\n",
-                      (uint32_t)(sizeof("[init] /proc/PID/status open failed\n") - 1));
-            sys_exit(1);
-        }
-        char sbuf[128];
-        int r = sys_read(fd, sbuf, 127);
-        (void)sys_close(fd);
-        if (r <= 0) {
-            sys_write(1, "[init] /proc/PID/status read failed\n",
-                      (uint32_t)(sizeof("[init] /proc/PID/status read failed\n") - 1));
-            sys_exit(1);
-        }
-        sys_write(1, "[init] /proc/PID/status OK\n",
-                  (uint32_t)(sizeof("[init] /proc/PID/status OK\n") - 1));
-    }
-
-    // F3: /dev/console write test
-    {
-        int fd = sys_open("/dev/console", O_RDWR);
-        if (fd >= 0) {
-            static const char cm[] = "[init] console test\n";
-            int w = sys_write(fd, cm, (uint32_t)(sizeof(cm) - 1));
-            (void)sys_close(fd);
-            if (w > 0) {
-                sys_write(1, "[init] /dev/console OK\n",
-                          (uint32_t)(sizeof("[init] /dev/console OK\n") - 1));
-            } else {
-                sys_write(1, "[init] /dev/console OK\n",
-                          (uint32_t)(sizeof("[init] /dev/console OK\n") - 1));
-            }
-        } else {
-            /* /dev/console may not exist on serial-only boot — skip gracefully */
-            sys_write(1, "[init] /dev/console OK\n",
-                      (uint32_t)(sizeof("[init] /dev/console OK\n") - 1));
-        }
-    }
-
-    // F4: multiple PTY pairs — open two ptmx, verify independent data paths
-    {
-        int m1 = sys_open("/dev/ptmx", 0);
-        int s1 = sys_open("/dev/pts/0", 0);
-        int m2 = sys_open("/dev/ptmx", 0);
-        int s2 = sys_open("/dev/pts/1", 0);
-        if (m1 < 0 || s1 < 0 || m2 < 0 || s2 < 0) {
-            /* Not enough PTY pairs — skip gracefully */
-            if (m1 >= 0) (void)sys_close(m1);
-            if (s1 >= 0) (void)sys_close(s1);
-            if (m2 >= 0) (void)sys_close(m2);
-            if (s2 >= 0) (void)sys_close(s2);
-            sys_write(1, "[init] multi-pty OK\n",
-                      (uint32_t)(sizeof("[init] multi-pty OK\n") - 1));
-        } else {
-            /* Write through pair 1 */
-            (void)sys_write(m1, "P1", 2);
-            char b1[4];
-            int r1 = sys_read(s1, b1, 2);
-
-            /* Write through pair 2 */
-            (void)sys_write(m2, "P2", 2);
-            char b2[4];
-            int r2 = sys_read(s2, b2, 2);
-
-            (void)sys_close(m1);
-            (void)sys_close(s1);
-            (void)sys_close(m2);
-            (void)sys_close(s2);
-
-            if (r1 == 2 && r2 == 2 && b1[0] == 'P' && b1[1] == '1' && b2[0] == 'P' && b2[1] == '2') {
-                sys_write(1, "[init] multi-pty OK\n",
-                          (uint32_t)(sizeof("[init] multi-pty OK\n") - 1));
-            } else {
-                sys_write(1, "[init] multi-pty data mismatch\n",
-                          (uint32_t)(sizeof("[init] multi-pty data mismatch\n") - 1));
-                sys_exit(1);
-            }
-        }
-    }
-
-    // F5: dup standalone
-    {
-        int fds[2];
-        if (sys_pipe(fds) < 0) {
-            sys_write(1, "[init] dup pipe failed\n",
-                      (uint32_t)(sizeof("[init] dup pipe failed\n") - 1));
-            sys_exit(1);
-        }
-        int d = sys_dup(fds[1]);
-        if (d < 0) {
-            sys_write(1, "[init] dup failed\n",
-                      (uint32_t)(sizeof("[init] dup failed\n") - 1));
-            sys_exit(1);
-        }
-        /* Write through dup'd fd, read from original */
-        (void)sys_write(d, "D", 1);
-        char db;
-        int r = sys_read(fds[0], &db, 1);
-        (void)sys_close(d);
-        (void)sys_close(fds[0]);
-        (void)sys_close(fds[1]);
-        if (r != 1 || db != 'D') {
-            sys_write(1, "[init] dup data bad\n",
-                      (uint32_t)(sizeof("[init] dup data bad\n") - 1));
-            sys_exit(1);
-        }
-        sys_write(1, "[init] dup OK\n",
-                  (uint32_t)(sizeof("[init] dup OK\n") - 1));
-    }
-
-    // F6: pipe EOF — close write end, read should return 0
-    {
-        int fds[2];
-        if (sys_pipe(fds) < 0) {
-            sys_write(1, "[init] pipe-eof pipe failed\n",
-                      (uint32_t)(sizeof("[init] pipe-eof pipe failed\n") - 1));
-            sys_exit(1);
-        }
-        (void)sys_close(fds[1]); /* close write end */
-        char eb;
-        int r = sys_read(fds[0], &eb, 1);
-        (void)sys_close(fds[0]);
-        if (r != 0) {
-            sys_write(1, "[init] pipe EOF expected 0\n",
-                      (uint32_t)(sizeof("[init] pipe EOF expected 0\n") - 1));
-            sys_exit(1);
-        }
-        sys_write(1, "[init] pipe EOF OK\n",
-                  (uint32_t)(sizeof("[init] pipe EOF OK\n") - 1));
-    }
-
-    // F7: getdents /proc (readdir on procfs root)
-    {
-        int fd = sys_open("/proc", 0);
-        if (fd < 0) {
-            sys_write(1, "[init] readdir /proc open failed\n",
-                      (uint32_t)(sizeof("[init] readdir /proc open failed\n") - 1));
-            sys_exit(1);
-        }
-        char dbuf[512];
-        int r = sys_getdents(fd, dbuf, 512);
-        (void)sys_close(fd);
-        if (r <= 0) {
-            sys_write(1, "[init] readdir /proc empty\n",
-                      (uint32_t)(sizeof("[init] readdir /proc empty\n") - 1));
-            sys_exit(1);
-        }
-        sys_write(1, "[init] readdir /proc OK\n",
-                  (uint32_t)(sizeof("[init] readdir /proc OK\n") - 1));
-    }
-
-    // F8: getdents /bin (readdir on initrd — tests initrd_readdir fix)
-    {
-        int fd = sys_open("/bin", 0);
-        if (fd < 0) {
-            sys_write(1, "[init] readdir /bin open failed\n",
-                      (uint32_t)(sizeof("[init] readdir /bin open failed\n") - 1));
-            sys_exit(1);
-        }
-        char dbuf[1024];
-        int r = sys_getdents(fd, dbuf, 1024);
-        (void)sys_close(fd);
-        if (r <= 0) {
-            sys_write(1, "[init] readdir /bin empty\n",
-                      (uint32_t)(sizeof("[init] readdir /bin empty\n") - 1));
-            sys_exit(1);
-        }
-        sys_write(1, "[init] readdir /bin OK\n",
-                  (uint32_t)(sizeof("[init] readdir /bin OK\n") - 1));
-    }
-
-    enum { NCHILD = 100 };
-    int children[NCHILD];
-    for (int i = 0; i < NCHILD; i++) {
-        int pid = sys_fork();
-        if (pid < 0) {
-            static const char smsg[] = "[init] fork failed\n";
-            (void)sys_write(1, smsg, (uint32_t)(sizeof(smsg) - 1));
-            sys_exit(2);
-        }
-        if (pid == 0) {
-            sys_exit(42);
-        }
-        children[i] = pid;
-    }
-
-    {
-        int parent_pid = sys_getpid();
-        int pid = sys_fork();
-        if (pid == 0) {
-            int ppid = sys_getppid();
-            if (ppid == parent_pid) {
-                static const char msg[] = "[init] getppid OK\n";
-                (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1));
-                sys_exit(0);
-            }
-            static const char msg[] = "[init] getppid failed\n";
-            (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1));
-            sys_exit(1);
-        }
-        int st = 0;
-        (void)sys_waitpid(pid, &st, 0);
-    }
-
-    {
-        int pid = sys_fork();
-        if (pid == 0) {
-            volatile uint32_t x = 0;
-            for (uint32_t i = 0; i < 2000000U; i++) x += i;
-            sys_exit(7);
-        }
-        int st = 0;
-        int wp = sys_waitpid(pid, &st, WNOHANG);
-        if (wp == 0 || wp == pid) {
-            static const char msg[] = "[init] waitpid WNOHANG OK\n";
-            (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1));
-        } else {
-            static const char msg[] = "[init] waitpid WNOHANG failed\n";
-            (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1));
-        }
-        if (wp == 0) {
-            (void)sys_waitpid(pid, &st, 0);
-        }
-    }
-
-    /* ---- gettimeofday test ---- */
-    {
-        struct timeval tv;
-        tv.tv_sec = 0; tv.tv_usec = 0;
-        int r = sys_gettimeofday(&tv);
-        if (r == 0 && tv.tv_sec > 1000000000U) {
-            static const char msg[] = "[init] gettimeofday OK\n";
-            (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1));
-        } else {
-            static const char msg[] = "[init] gettimeofday failed\n";
-            (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1));
-        }
-    }
-
-    /* ---- mprotect test ---- */
-    {
-        /* Test mprotect on heap memory (brk region) — simpler than mmap */
-        uintptr_t old_brk = sys_brk(0);
-        uintptr_t page = (old_brk + 0xFFFU) & ~(uintptr_t)0xFFFU;
-        uintptr_t new_brk = page + 4096;
-        uintptr_t r_brk = sys_brk(new_brk);
-        if (r_brk >= new_brk) {
-            *(volatile uint32_t*)page = 0xDEADBEEF;
-            int r = sys_mprotect(page, 4096, PROT_READ | PROT_WRITE);
-            if (r == 0) {
-                static const char msg[] = "[init] mprotect OK\n";
-                (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1));
-            } else {
-                static const char msg[] = "[init] mprotect call failed\n";
-                (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1));
-            }
-        } else {
-            static const char msg[] = "[init] mprotect brk failed\n";
-            (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1));
-        }
-    }
-
-    /* ---- madvise test ---- */
-    {
-        int r = sys_madvise(0, 4096, 0 /* MADV_NORMAL */);
-        if (r == 0) {
-            static const char msg[] = "[init] madvise OK\n";
-            (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1));
-        } else {
-            static const char msg[] = "[init] madvise failed\n";
-            (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1));
-        }
-    }
-
-    /* ---- getrlimit/setrlimit test ---- */
-    {
-        struct rlimit rl;
-        int r = sys_getrlimit(RLIMIT_NOFILE, &rl);
-        if (r == 0 && rl.rlim_cur > 0 && rl.rlim_cur <= 1024) {
-            /* Try setting a lower soft limit */
-            struct rlimit new_rl;
-            new_rl.rlim_cur = rl.rlim_cur / 2;
-            new_rl.rlim_max = rl.rlim_max;
-            int r2 = sys_setrlimit(RLIMIT_NOFILE, &new_rl);
-            /* Read back */
-            struct rlimit check;
-            int r3 = sys_getrlimit(RLIMIT_NOFILE, &check);
-            if (r2 == 0 && r3 == 0 && check.rlim_cur == new_rl.rlim_cur) {
-                static const char msg[] = "[init] getrlimit/setrlimit OK\n";
-                (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1));
-            } else {
-                static const char msg[] = "[init] setrlimit failed\n";
-                (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1));
-            }
-            /* Restore */
-            (void)sys_setrlimit(RLIMIT_NOFILE, &rl);
-        } else {
-            static const char msg[] = "[init] getrlimit failed\n";
-            (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1));
-        }
-    }
-
-    // PIE lazy PLT/GOT binding test
-    {
-        int pid = sys_fork();
-        if (pid == 0) {
-            static const char* const av[] = {"pie_test", 0};
-            static const char* const ev[] = {0};
-            (void)sys_execve("/bin/pie_test", av, ev);
-            sys_exit(99);
-        }
-        if (pid > 0) {
-            int st = 0;
-            (void)sys_waitpid(pid, &st, 0);
-        }
-    }
-
-    {
-        int pid = sys_fork();
-        if (pid < 0) {
-            static const char msg[] = "[init] sigsegv test fork failed\n";
-            (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1));
-            goto sigsegv_done;
-        }
-
-        if (pid == 0) {
-            struct sigaction act;
-            act.sa_handler = 0;
-            act.sa_sigaction = (uintptr_t)sigsegv_info_handler;
-            act.sa_mask = 0;
-            act.sa_flags = SA_SIGINFO;
-
-            if (sys_sigaction2(SIGSEGV, &act, 0) < 0) {
-                static const char msg[] = "[init] sigaction(SIGSEGV) failed\n";
-                (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1));
-                sys_exit(1);
-            }
-
-            *(volatile uint32_t*)0x12345000U = 123;
-            sys_exit(2);
-        }
-
-        int st = 0;
-        int wp = sys_waitpid(pid, &st, 0);
-        if (wp == pid && st == 0) {
-            static const char msg[] = "[init] SIGSEGV OK\n";
-            (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1));
-        } else {
-            static const char msg[] = "[init] SIGSEGV failed\n";
-            (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1));
-        }
-    sigsegv_done:;
-    }
-
-    int ok = 1;
-    for (int i = 0; i < NCHILD; i++) {
-        int st = 0;
-        int wp = sys_waitpid(children[i], &st, 0);
-        if (wp != children[i] || st != 42) {
-            ok = 0;
-            break;
-        }
-    }
-
-    if (ok) {
-        static const char wmsg[] = "[init] waitpid OK (100 children, explicit)\n";
-        (void)sys_write(1, wmsg, (uint32_t)(sizeof(wmsg) - 1));
-    } else {
-        static const char wbad[] = "[init] waitpid failed (100 children, explicit)\n";
-        (void)sys_write(1, wbad, (uint32_t)(sizeof(wbad) - 1));
-    }
-
-    // G1: uname syscall test
-    {
-        struct utsname uts;
-        for (uint32_t i = 0; i < sizeof(uts); i++) ((char*)&uts)[i] = 0;
-        int r = sys_uname(&uts);
-        if (r < 0) {
-            sys_write(1, "[init] uname failed\n", (uint32_t)(sizeof("[init] uname failed\n") - 1));
-            sys_exit(1);
-        }
-        /* Verify sysname == "AdrOS" */
-        if (uts.sysname[0] != 'A' || uts.sysname[1] != 'd' || uts.sysname[2] != 'r' ||
-            uts.sysname[3] != 'O' || uts.sysname[4] != 'S' || uts.sysname[5] != 0) {
-            sys_write(1, "[init] uname sysname bad\n", (uint32_t)(sizeof("[init] uname sysname bad\n") - 1));
-            sys_exit(1);
-        }
-        /* Verify machine == "i686" */
-        if (uts.machine[0] != 'i' || uts.machine[1] != '6' || uts.machine[2] != '8' || uts.machine[3] != '6') {
-            sys_write(1, "[init] uname machine bad\n", (uint32_t)(sizeof("[init] uname machine bad\n") - 1));
-            sys_exit(1);
-        }
-        sys_write(1, "[init] uname OK\n", (uint32_t)(sizeof("[init] uname OK\n") - 1));
-    }
-
-    // H1: SMP parallel fork test — exercises multi-CPU scheduling + load balancing
-    {
-        #define SMP_NCHILD 8
-        int smp_pids[SMP_NCHILD];
-        int smp_ok = 1;
-
-        for (int i = 0; i < SMP_NCHILD; i++) {
-            int pid = sys_fork();
-            if (pid == 0) {
-                /* Child: busy loop to consume a time slice, then exit with index */
-                volatile uint32_t sum = 0;
-                for (uint32_t j = 0; j < 50000; j++) sum += j;
-                (void)sum;
-                sys_exit(i + 1);
-            }
-            smp_pids[i] = pid;
-        }
-
-        /* Parent: wait for all children, verify each returned correct status */
-        for (int i = 0; i < SMP_NCHILD; i++) {
-            int st = 0;
-            int wp = sys_waitpid(smp_pids[i], &st, 0);
-            if (wp != smp_pids[i] || st != (i + 1)) {
-                smp_ok = 0;
-            }
-        }
-
-        if (smp_ok) {
-            static const char msg[] = "[init] SMP parallel fork OK\n";
-            (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1));
-        } else {
-            static const char msg[] = "[init] SMP parallel fork FAIL\n";
-            (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1));
-        }
-        #undef SMP_NCHILD
-    }
-
-    (void)sys_write(1, "[init] execve(/bin/echo)\n",
-                    (uint32_t)(sizeof("[init] execve(/bin/echo)\n") - 1));
-    static const char* const argv[] = {"echo", "[echo]", "hello", "from", "echo", 0};
-    static const char* const envp[] = {"FOO=bar", "HELLO=world", 0};
-    (void)sys_execve("/bin/echo", argv, envp);
-    (void)sys_write(1, "[init] execve returned (unexpected)\n",
-                    (uint32_t)(sizeof("[init] execve returned (unexpected)\n") - 1));
-    sys_exit(1);
-    sys_exit(0);
-}
diff --git a/user/grep.c b/user/grep.c
deleted file mode 100644 (file)
index 1f755ee..0000000
+++ /dev/null
@@ -1,79 +0,0 @@
-/* AdrOS grep utility — search for pattern in files */
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-#include <fcntl.h>
-
-static int match_simple(const char* text, const char* pat) {
-    /* Simple substring match (no regex) */
-    return strstr(text, pat) != NULL;
-}
-
-static int grep_fd(int fd, const char* pattern, const char* fname, int show_name, int invert, int count_only, int line_num) {
-    char buf[4096];
-    int pos = 0, n, matches = 0, lnum = 0;
-    while ((n = read(fd, buf + pos, (size_t)(sizeof(buf) - 1 - pos))) > 0) {
-        pos += n;
-        buf[pos] = '\0';
-        char* start = buf;
-        char* nl;
-        while ((nl = strchr(start, '\n')) != NULL) {
-            *nl = '\0';
-            lnum++;
-            int m = match_simple(start, pattern);
-            if (invert) m = !m;
-            if (m) {
-                matches++;
-                if (!count_only) {
-                    if (show_name) printf("%s:", fname);
-                    if (line_num) printf("%d:", lnum);
-                    printf("%s\n", start);
-                }
-            }
-            start = nl + 1;
-        }
-        int rem = (int)(buf + pos - start);
-        if (rem > 0) memmove(buf, start, (size_t)rem);
-        pos = rem;
-    }
-    if (pos > 0) {
-        buf[pos] = '\0';
-        lnum++;
-        int m = match_simple(buf, pattern);
-        if (invert) m = !m;
-        if (m) {
-            matches++;
-            if (!count_only) {
-                if (show_name) printf("%s:", fname);
-                if (line_num) printf("%d:", lnum);
-                printf("%s\n", buf);
-            }
-        }
-    }
-    if (count_only) printf("%s%s%d\n", show_name ? fname : "", show_name ? ":" : "", matches);
-    return matches > 0 ? 0 : 1;
-}
-
-int main(int argc, char** argv) {
-    int invert = 0, count_only = 0, line_num = 0;
-    int i = 1;
-    while (i < argc && argv[i][0] == '-') {
-        for (int j = 1; argv[i][j]; j++) {
-            if (argv[i][j] == 'v') invert = 1;
-            else if (argv[i][j] == 'c') count_only = 1;
-            else if (argv[i][j] == 'n') line_num = 1;
-        }
-        i++;
-    }
-    if (i >= argc) { fprintf(stderr, "usage: grep [-vcn] PATTERN [FILE...]\n"); return 2; }
-    const char* pattern = argv[i++];
-    if (i >= argc) return grep_fd(STDIN_FILENO, pattern, "(stdin)", 0, invert, count_only, line_num);
-    int rc = 1, nfiles = argc - i;
-    for (; i < argc; i++) {
-        int fd = open(argv[i], O_RDONLY);
-        if (fd < 0) { fprintf(stderr, "grep: %s: No such file or directory\n", argv[i]); continue; }
-        if (grep_fd(fd, pattern, argv[i], nfiles > 1, invert, count_only, line_num) == 0) rc = 0;
-        close(fd);
-    }
-    return rc;
-}
diff --git a/user/head.c b/user/head.c
deleted file mode 100644 (file)
index ec05c3c..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-/* AdrOS head utility */
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <string.h>
-
-static void head_fd(int fd, int nlines) {
-    char buf[4096];
-    int lines = 0;
-    int r;
-    while (lines < nlines && (r = read(fd, buf, sizeof(buf))) > 0) {
-        for (int i = 0; i < r && lines < nlines; i++) {
-            write(STDOUT_FILENO, &buf[i], 1);
-            if (buf[i] == '\n') lines++;
-        }
-    }
-}
-
-int main(int argc, char** argv) {
-    int nlines = 10;
-    int start = 1;
-
-    if (argc > 1 && argv[1][0] == '-' && argv[1][1] == 'n' && argc > 2) {
-        nlines = atoi(argv[2]);
-        start = 3;
-    } else if (argc > 1 && argv[1][0] == '-' && argv[1][1] >= '0' && argv[1][1] <= '9') {
-        nlines = atoi(argv[1] + 1);
-        start = 2;
-    }
-
-    if (start >= argc) {
-        head_fd(STDIN_FILENO, nlines);
-    } else {
-        for (int i = start; i < argc; i++) {
-            if (argc - start > 1) printf("==> %s <==\n", argv[i]);
-            int fd = open(argv[i], O_RDONLY);
-            if (fd < 0) {
-                fprintf(stderr, "head: cannot open '%s'\n", argv[i]);
-                continue;
-            }
-            head_fd(fd, nlines);
-            close(fd);
-        }
-    }
-    return 0;
-}
diff --git a/user/hostname.c b/user/hostname.c
deleted file mode 100644 (file)
index dad2290..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-/* AdrOS hostname utility */
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-#include <fcntl.h>
-
-int main(int argc, char** argv) {
-    (void)argc; (void)argv;
-
-    /* Try /proc/hostname first, then /etc/hostname, then fallback */
-    static const char* paths[] = { "/proc/hostname", "/etc/hostname", NULL };
-    for (int i = 0; paths[i]; i++) {
-        int fd = open(paths[i], O_RDONLY);
-        if (fd >= 0) {
-            char buf[256];
-            int r = read(fd, buf, sizeof(buf) - 1);
-            close(fd);
-            if (r > 0) {
-                buf[r] = '\0';
-                /* Strip trailing newline */
-                if (r > 0 && buf[r - 1] == '\n') buf[r - 1] = '\0';
-                printf("%s\n", buf);
-                return 0;
-            }
-        }
-    }
-
-    printf("adros\n");
-    return 0;
-}
diff --git a/user/id.c b/user/id.c
deleted file mode 100644 (file)
index 31b51ab..0000000
--- a/user/id.c
+++ /dev/null
@@ -1,9 +0,0 @@
-/* AdrOS id utility — display user and group IDs */
-#include <stdio.h>
-#include <unistd.h>
-
-int main(void) {
-    printf("uid=%d gid=%d euid=%d egid=%d\n",
-           getuid(), getgid(), geteuid(), getegid());
-    return 0;
-}
diff --git a/user/init.c b/user/init.c
deleted file mode 100644 (file)
index 02e08f4..0000000
+++ /dev/null
@@ -1,311 +0,0 @@
-/* AdrOS SysV-like init (/sbin/init)
- *
- * Reads /etc/inittab for configuration.
- * Supports runlevels 0-6 and S (single-user).
- * Actions: sysinit, respawn, wait, once, ctrlaltdel, shutdown.
- *
- * Default behavior (no inittab):
- *   1. Run /etc/init.d/rcS (if exists)
- *   2. Spawn /bin/sh on /dev/console
- */
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <time.h>
-
-#define MAX_ENTRIES 32
-#define LINE_MAX   256
-
-/* Inittab entry actions */
-enum action {
-    ACT_SYSINIT,    /* Run during system initialization */
-    ACT_WAIT,       /* Run and wait for completion */
-    ACT_ONCE,       /* Run once when entering runlevel */
-    ACT_RESPAWN,    /* Restart when process dies */
-    ACT_CTRLALTDEL, /* Run on Ctrl+Alt+Del */
-    ACT_SHUTDOWN,   /* Run during shutdown */
-};
-
-struct inittab_entry {
-    char id[8];
-    char runlevels[16];
-    enum action action;
-    char process[128];
-    int  pid;           /* PID of running process (for respawn) */
-    int  active;
-};
-
-static struct inittab_entry entries[MAX_ENTRIES];
-static int nentries = 0;
-static int current_runlevel = 3;  /* Default: multi-user */
-
-static enum action parse_action(const char* s) {
-    if (strcmp(s, "sysinit") == 0) return ACT_SYSINIT;
-    if (strcmp(s, "wait") == 0) return ACT_WAIT;
-    if (strcmp(s, "once") == 0) return ACT_ONCE;
-    if (strcmp(s, "respawn") == 0) return ACT_RESPAWN;
-    if (strcmp(s, "ctrlaltdel") == 0) return ACT_CTRLALTDEL;
-    if (strcmp(s, "shutdown") == 0) return ACT_SHUTDOWN;
-    return ACT_ONCE;
-}
-
-/* Parse /etc/inittab
- * Format: id:runlevels:action:process
- * Example:
- *   ::sysinit:/etc/init.d/rcS
- *   ::respawn:/bin/sh
- *   tty1:2345:respawn:/bin/sh
- */
-static int parse_inittab(void) {
-    int fd = open("/etc/inittab", O_RDONLY);
-    if (fd < 0) return -1;
-
-    char buf[2048];
-    int total = 0;
-    int r;
-    while ((r = read(fd, buf + total, (size_t)(sizeof(buf) - (size_t)total - 1))) > 0)
-        total += r;
-    buf[total] = '\0';
-    close(fd);
-
-    char* p = buf;
-    while (*p && nentries < MAX_ENTRIES) {
-        /* Skip whitespace and comments */
-        while (*p == ' ' || *p == '\t') p++;
-        if (*p == '#' || *p == '\n') {
-            while (*p && *p != '\n') p++;
-            if (*p == '\n') p++;
-            continue;
-        }
-        if (*p == '\0') break;
-
-        struct inittab_entry* e = &entries[nentries];
-        memset(e, 0, sizeof(*e));
-
-        /* id */
-        char* start = p;
-        while (*p && *p != ':') p++;
-        int len = (int)(p - start);
-        if (len > 7) len = 7;
-        memcpy(e->id, start, (size_t)len);
-        e->id[len] = '\0';
-        if (*p == ':') p++;
-
-        /* runlevels */
-        start = p;
-        while (*p && *p != ':') p++;
-        len = (int)(p - start);
-        if (len > 15) len = 15;
-        memcpy(e->runlevels, start, (size_t)len);
-        e->runlevels[len] = '\0';
-        if (*p == ':') p++;
-
-        /* action */
-        start = p;
-        while (*p && *p != ':') p++;
-        char action_str[32];
-        len = (int)(p - start);
-        if (len > 31) len = 31;
-        memcpy(action_str, start, (size_t)len);
-        action_str[len] = '\0';
-        e->action = parse_action(action_str);
-        if (*p == ':') p++;
-
-        /* process */
-        start = p;
-        while (*p && *p != '\n') p++;
-        len = (int)(p - start);
-        if (len > 127) len = 127;
-        memcpy(e->process, start, (size_t)len);
-        e->process[len] = '\0';
-        if (*p == '\n') p++;
-
-        e->pid = -1;
-        e->active = 1;
-        nentries++;
-    }
-
-    return nentries > 0 ? 0 : -1;
-}
-
-/* Check if entry should run at current runlevel */
-static int entry_matches_runlevel(const struct inittab_entry* e) {
-    if (e->runlevels[0] == '\0') return 1;  /* Empty = all runlevels */
-    char rl = '0' + (char)current_runlevel;
-    for (const char* p = e->runlevels; *p; p++) {
-        if (*p == rl || *p == 'S' || *p == 's') return 1;
-    }
-    return 0;
-}
-
-/* Run a process (fork + exec) */
-static int run_process(const char* cmd) {
-    int pid = fork();
-    if (pid < 0) return -1;
-
-    if (pid == 0) {
-        /* Child: parse command into argv */
-        char buf[128];
-        strncpy(buf, cmd, sizeof(buf) - 1);
-        buf[sizeof(buf) - 1] = '\0';
-
-        char* argv[16];
-        int argc = 0;
-        char* p = buf;
-        while (*p && argc < 15) {
-            while (*p == ' ' || *p == '\t') p++;
-            if (*p == '\0') break;
-            argv[argc++] = p;
-            while (*p && *p != ' ' && *p != '\t') p++;
-            if (*p) *p++ = '\0';
-        }
-        argv[argc] = NULL;
-
-        if (argc > 0) {
-            execve(argv[0], (const char* const*)argv, NULL);
-            /* If execve fails, try with /bin/sh -c */
-            const char* sh_argv[] = { "/bin/sh", "-c", cmd, NULL };
-            execve("/bin/sh", sh_argv, NULL);
-        }
-        _exit(127);
-    }
-
-    return pid;
-}
-
-/* Run a process and wait for it */
-static int run_and_wait(const char* cmd) {
-    int pid = run_process(cmd);
-    if (pid < 0) return -1;
-    int st;
-    waitpid(pid, &st, 0);
-    return st;
-}
-
-/* Run all entries matching a given action and current runlevel */
-static void run_action(enum action act, int do_wait) {
-    for (int i = 0; i < nentries; i++) {
-        if (entries[i].action != act) continue;
-        if (!entry_matches_runlevel(&entries[i])) continue;
-        if (!entries[i].active) continue;
-
-        if (do_wait) {
-            run_and_wait(entries[i].process);
-        } else {
-            entries[i].pid = run_process(entries[i].process);
-        }
-    }
-}
-
-/* Respawn dead children */
-static void check_respawn(void) {
-    for (int i = 0; i < nentries; i++) {
-        if (entries[i].action != ACT_RESPAWN) continue;
-        if (!entry_matches_runlevel(&entries[i])) continue;
-        if (!entries[i].active) continue;
-
-        if (entries[i].pid <= 0) {
-            entries[i].pid = run_process(entries[i].process);
-        } else {
-            /* Check if still running */
-            int st;
-            int r = waitpid(entries[i].pid, &st, 1 /* WNOHANG */);
-            if (r > 0) {
-                /* Process exited, respawn */
-                entries[i].pid = run_process(entries[i].process);
-            }
-        }
-    }
-}
-
-/* Default behavior when no inittab exists */
-static void default_init(void) {
-    /* Run /etc/init.d/rcS if it exists */
-    if (access("/etc/init.d/rcS", 0) == 0) {
-        run_and_wait("/etc/init.d/rcS");
-    }
-
-    /* Spawn shell */
-    while (1) {
-        int pid = fork();
-        if (pid < 0) {
-            fprintf(stderr, "init: fork failed\n");
-            for (;;) { struct timespec ts = {1,0}; nanosleep(&ts, NULL); }
-        }
-
-        if (pid == 0) {
-            const char* argv[] = { "/bin/sh", NULL };
-            execve("/bin/sh", argv, NULL);
-            _exit(127);
-        }
-
-        int st;
-        waitpid(pid, &st, 0);
-
-        /* Shell exited, respawn after a small delay */
-        struct timespec ts = {1, 0};
-        nanosleep(&ts, NULL);
-    }
-}
-
-int main(int argc, char** argv) {
-    (void)argc; (void)argv;
-
-    /* PID 1 should not die on signals */
-    struct sigaction sa;
-    memset(&sa, 0, sizeof(sa));
-    sa.sa_handler = (uintptr_t)SIG_IGN;
-    sigaction(SIGINT, &sa, NULL);
-    sigaction(SIGTERM, &sa, NULL);
-
-    printf("AdrOS init starting (PID %d)\n", getpid());
-
-    /* Try to parse inittab */
-    if (parse_inittab() < 0) {
-        printf("init: no /etc/inittab, using defaults\n");
-        default_init();
-        return 0;  /* unreachable */
-    }
-
-    printf("init: loaded %d inittab entries, runlevel %d\n",
-           nentries, current_runlevel);
-
-    /* Phase 1: sysinit entries */
-    run_action(ACT_SYSINIT, 1);
-
-    /* Phase 2: wait entries */
-    run_action(ACT_WAIT, 1);
-
-    /* Phase 3: once entries */
-    run_action(ACT_ONCE, 0);
-
-    /* Phase 4: respawn entries */
-    run_action(ACT_RESPAWN, 0);
-
-    /* Main loop: reap children and respawn */
-    while (1) {
-        int st;
-        int pid = waitpid(-1, &st, 0);
-
-        if (pid > 0) {
-            /* Mark dead child and respawn if needed */
-            for (int i = 0; i < nentries; i++) {
-                if (entries[i].pid == pid) {
-                    entries[i].pid = -1;
-                    break;
-                }
-            }
-            check_respawn();
-        } else {
-            /* No children or error — sleep briefly */
-            struct timespec ts = {1, 0};
-            nanosleep(&ts, NULL);
-            check_respawn();
-        }
-    }
-
-    return 0;
-}
diff --git a/user/kill.c b/user/kill.c
deleted file mode 100644 (file)
index 7cc1e95..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-/* AdrOS kill utility — send signal to process */
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-
-int main(int argc, char** argv) {
-    if (argc <= 1) {
-        fprintf(stderr, "usage: kill [-SIGNAL] PID...\n");
-        return 1;
-    }
-
-    int sig = 15; /* SIGTERM */
-    int start = 1;
-
-    if (argv[1][0] == '-') {
-        const char* s = argv[1] + 1;
-        if (strcmp(s, "9") == 0 || strcmp(s, "KILL") == 0) sig = 9;
-        else if (strcmp(s, "15") == 0 || strcmp(s, "TERM") == 0) sig = 15;
-        else if (strcmp(s, "2") == 0 || strcmp(s, "INT") == 0) sig = 2;
-        else if (strcmp(s, "1") == 0 || strcmp(s, "HUP") == 0) sig = 1;
-        else if (strcmp(s, "0") == 0) sig = 0;
-        else sig = atoi(s);
-        start = 2;
-    }
-
-    int rc = 0;
-    for (int i = start; i < argc; i++) {
-        int pid = atoi(argv[i]);
-        if (pid <= 0) {
-            fprintf(stderr, "kill: invalid pid '%s'\n", argv[i]);
-            rc = 1;
-            continue;
-        }
-        if (kill(pid, sig) < 0) {
-            fprintf(stderr, "kill: %d: no such process\n", pid);
-            rc = 1;
-        }
-    }
-    return rc;
-}
diff --git a/user/ldso.c b/user/ldso.c
deleted file mode 100644 (file)
index e961f51..0000000
+++ /dev/null
@@ -1,369 +0,0 @@
-/* Userspace dynamic linker (ld.so) with lazy PLT/GOT binding.
- *
- * The kernel ELF loader pushes an auxiliary vector (auxv) onto the user
- * stack when PT_INTERP is present.  This linker:
- *   1. Parses auxv to find AT_PHDR, AT_PHNUM, AT_ENTRY
- *   2. Walks program headers to find PT_DYNAMIC
- *   3. Extracts DT_PLTGOT, DT_JMPREL, DT_PLTRELSZ, DT_SYMTAB, DT_STRTAB
- *   4. Sets GOT[1] = link_map pointer, GOT[2] = _dl_runtime_resolve
- *   5. Jumps to AT_ENTRY (the real program entry point)
- *
- * On first PLT call, the resolver fires: looks up the symbol, patches
- * the GOT entry, and jumps to the resolved function.  Subsequent calls
- * go directly through the patched GOT (zero overhead).
- *
- * The kernel loads DT_NEEDED shared libraries at SHLIB_BASE (0x20000000).
- * The resolver scans the .so's dynamic symtab to find undefined symbols. */
-
-typedef unsigned char  uint8_t;
-typedef unsigned short uint16_t;
-typedef unsigned int   uint32_t;
-typedef int            int32_t;
-
-/* ---- Auxiliary vector types ---- */
-#define AT_NULL   0
-#define AT_PHDR   3
-#define AT_PHENT  4
-#define AT_PHNUM  5
-#define AT_ENTRY  9
-
-/* ---- ELF types (minimal, matching kernel include/elf.h) ---- */
-#define PT_LOAD    1
-#define PT_DYNAMIC 2
-
-#define DT_NULL    0
-#define DT_NEEDED  1
-#define DT_PLTRELSZ 2
-#define DT_PLTGOT  3
-#define DT_HASH    4
-#define DT_STRTAB  5
-#define DT_SYMTAB  6
-#define DT_STRSZ   10
-#define DT_SYMENT  11
-#define DT_REL     17
-#define DT_RELSZ   18
-#define DT_JMPREL  23
-
-#define R_386_32       1
-#define R_386_COPY     5
-#define R_386_GLOB_DAT 6
-#define R_386_JMP_SLOT 7
-
-#define ELF32_R_SYM(i)  ((i) >> 8)
-#define ELF32_R_TYPE(i)  ((unsigned char)(i))
-
-#define STB_GLOBAL 1
-#define STB_WEAK   2
-#define ELF32_ST_BIND(i) ((i) >> 4)
-
-#define SHLIB_BASE 0x11000000U
-
-struct elf32_phdr {
-    uint32_t p_type, p_offset, p_vaddr, p_paddr;
-    uint32_t p_filesz, p_memsz, p_flags, p_align;
-};
-
-struct elf32_dyn {
-    int32_t  d_tag;
-    uint32_t d_val;
-};
-
-struct elf32_rel {
-    uint32_t r_offset;
-    uint32_t r_info;
-};
-
-struct elf32_sym {
-    uint32_t st_name, st_value, st_size;
-    uint8_t  st_info, st_other;
-    uint16_t st_shndx;
-};
-
-/* ---- Link map: per-module metadata for the resolver ---- */
-struct link_map {
-    uint32_t l_addr;           /* base load address (0 for ET_EXEC) */
-    uint32_t jmprel;           /* DT_JMPREL VA (relocation table for .rel.plt) */
-    uint32_t pltrelsz;         /* DT_PLTRELSZ */
-    uint32_t symtab;           /* DT_SYMTAB VA */
-    uint32_t strtab;           /* DT_STRTAB VA */
-    uint32_t rel;              /* DT_REL VA (eager relocations) */
-    uint32_t relsz;            /* DT_RELSZ */
-    /* Shared lib symbol lookup info */
-    uint32_t shlib_symtab;     /* .so DT_SYMTAB VA (0 if no .so) */
-    uint32_t shlib_strtab;     /* .so DT_STRTAB VA */
-    uint32_t shlib_base;       /* .so load base */
-    uint32_t shlib_hash;       /* .so DT_HASH VA */
-};
-
-static struct link_map g_map;
-
-/* ---- Minimal string helpers (no libc) ---- */
-static int str_eq(const char* a, const char* b) {
-    while (*a && *b) { if (*a++ != *b++) return 0; }
-    return *a == *b;
-}
-
-/* ---- ELF hash (for DT_HASH lookup) ---- */
-static uint32_t elf_hash(const char* name) {
-    uint32_t h = 0, g;
-    while (*name) {
-        h = (h << 4) + (uint8_t)*name++;
-        g = h & 0xF0000000U;
-        if (g) h ^= g >> 24;
-        h &= ~g;
-    }
-    return h;
-}
-
-/* ---- Symbol lookup in a shared library via DT_HASH ---- */
-static uint32_t shlib_lookup(const char* name, const struct link_map* map) {
-    if (!map->shlib_symtab || !map->shlib_strtab || !map->shlib_hash)
-        return 0;
-
-    const uint32_t* hashtab = (const uint32_t*)(map->shlib_hash + map->shlib_base);
-    uint32_t nbucket = hashtab[0];
-    uint32_t nchain  = hashtab[1];
-    const uint32_t* bucket = &hashtab[2];
-    const uint32_t* chain  = &hashtab[2 + nbucket];
-    (void)nchain;
-
-    uint32_t h = elf_hash(name) % nbucket;
-    const struct elf32_sym* symtab = (const struct elf32_sym*)(map->shlib_symtab + map->shlib_base);
-    const char* strtab = (const char*)(map->shlib_strtab + map->shlib_base);
-
-    for (uint32_t i = bucket[h]; i != 0; i = chain[i]) {
-        const struct elf32_sym* sym = &symtab[i];
-        uint8_t bind = ELF32_ST_BIND(sym->st_info);
-        if ((bind == STB_GLOBAL || bind == STB_WEAK) &&
-            sym->st_shndx != 0 && sym->st_value != 0) {
-            if (str_eq(strtab + sym->st_name, name))
-                return sym->st_value + map->shlib_base;
-        }
-    }
-    return 0;
-}
-
-/* ---- dl_fixup: called by _dl_runtime_resolve trampoline ----
- * Resolves a single PLT entry: looks up the symbol, patches GOT,
- * returns the resolved address. */
-uint32_t dl_fixup(struct link_map* map, uint32_t reloc_offset)
-    __attribute__((used, visibility("hidden")));
-
-uint32_t dl_fixup(struct link_map* map, uint32_t reloc_offset) {
-    const struct elf32_rel* rel =
-        (const struct elf32_rel*)(map->jmprel + reloc_offset);
-
-    uint32_t sym_idx = ELF32_R_SYM(rel->r_info);
-    const struct elf32_sym* sym =
-        &((const struct elf32_sym*)map->symtab)[sym_idx];
-
-    uint32_t resolved = 0;
-
-    if (sym->st_value != 0) {
-        resolved = sym->st_value + map->l_addr;
-    } else {
-        const char* name = (const char*)map->strtab + sym->st_name;
-        resolved = shlib_lookup(name, map);
-    }
-
-    if (resolved) {
-        uint32_t* got_entry = (uint32_t*)(rel->r_offset + map->l_addr);
-        *got_entry = resolved;
-    }
-
-    return resolved;
-}
-
-/* ---- _dl_runtime_resolve: PLT[0] jumps here via GOT[2] ----
- * Entry stack: [link_map*] [reloc_offset] [return_addr]
- * Uses the glibc i386 convention: save eax/ecx/edx, call dl_fixup,
- * restore, ret $8 to jump to resolved function. */
-void _dl_runtime_resolve(void)
-    __attribute__((naked, used, visibility("hidden")));
-
-void _dl_runtime_resolve(void) {
-    __asm__ volatile(
-        "pushl %%eax\n"
-        "pushl %%ecx\n"
-        "pushl %%edx\n"
-        "movl 16(%%esp), %%edx\n"   /* reloc_offset */
-        "movl 12(%%esp), %%eax\n"   /* link_map* */
-        "pushl %%edx\n"
-        "pushl %%eax\n"
-        "call dl_fixup\n"
-        "addl $8, %%esp\n"
-        "popl %%edx\n"
-        "popl %%ecx\n"
-        "xchgl %%eax, (%%esp)\n"    /* restore eax, put resolved addr on stack */
-        "ret $8\n"                   /* jump to resolved; pop link_map + reloc_offset */
-        ::: "memory"
-    );
-}
-
-/* ---- Parse a PT_DYNAMIC at the given VA to extract .so symtab info ---- */
-static void parse_shlib_dynamic(uint32_t dyn_va, uint32_t base) {
-    const struct elf32_dyn* d = (const struct elf32_dyn*)dyn_va;
-    for (; d->d_tag != DT_NULL; d++) {
-        switch (d->d_tag) {
-        case DT_SYMTAB: g_map.shlib_symtab = d->d_val; break;
-        case DT_STRTAB: g_map.shlib_strtab = d->d_val; break;
-        case DT_HASH:   g_map.shlib_hash   = d->d_val; break;
-        }
-    }
-    g_map.shlib_base = base;
-}
-
-/* ---- Scan for shared library's PT_DYNAMIC at SHLIB_BASE ---- */
-static void find_shlib_info(void) {
-    const uint8_t* base = (const uint8_t*)SHLIB_BASE;
-    /* Check ELF magic at SHLIB_BASE */
-    if (base[0] != 0x7F || base[1] != 'E' || base[2] != 'L' || base[3] != 'F')
-        return;
-
-    uint32_t e_phoff   = *(const uint32_t*)(base + 28);
-    uint16_t e_phnum   = *(const uint16_t*)(base + 44);
-    uint16_t e_phentsize = *(const uint16_t*)(base + 42);
-
-    for (uint16_t i = 0; i < e_phnum; i++) {
-        const struct elf32_phdr* ph =
-            (const struct elf32_phdr*)(base + e_phoff + i * e_phentsize);
-        if (ph->p_type == PT_DYNAMIC) {
-            parse_shlib_dynamic(ph->p_vaddr + SHLIB_BASE, SHLIB_BASE);
-            return;
-        }
-    }
-}
-
-/* ---- Entry point ---- */
-static void _start_c(uint32_t* initial_sp) __attribute__((noreturn, used));
-
-void _start(void) __attribute__((noreturn, naked, section(".text.start")));
-void _start(void) {
-    __asm__ volatile(
-        "pushl %%esp\n"
-        "call _start_c\n"
-        ::: "memory"
-    );
-    __builtin_unreachable();
-}
-
-static void _start_c(uint32_t* initial_sp) {
-    /* Stack layout set by execve:
-     *   initial_sp → argc
-     *                argv[0], argv[1], ..., NULL
-     *                envp[0], envp[1], ..., NULL
-     *                auxv[0], auxv[1], ..., {AT_NULL, 0}  */
-    uint32_t* sp = initial_sp;
-
-    uint32_t argc = *sp++;
-    sp += argc + 1;          /* skip argv[] + NULL terminator */
-    while (*sp) sp++;         /* skip envp[] entries */
-    sp++;                     /* skip envp NULL terminator */
-
-    /* sp now points to auxv array */
-    uint32_t at_entry = 0;
-    uint32_t at_phdr  = 0;
-    uint32_t at_phnum = 0;
-    uint32_t at_phent = 0;
-
-    for (uint32_t* p = sp; p[0] != AT_NULL; p += 2) {
-        switch (p[0]) {
-        case AT_ENTRY: at_entry = p[1]; break;
-        case AT_PHDR:  at_phdr  = p[1]; break;
-        case AT_PHNUM: at_phnum = p[1]; break;
-        case AT_PHENT: at_phent = p[1]; break;
-        }
-    }
-
-    if (!at_entry) {
-        __asm__ volatile("mov $2, %%eax\n mov $127, %%ebx\n int $0x80" ::: "eax", "ebx");
-        __builtin_unreachable();
-    }
-
-    /* Walk program headers to find PT_DYNAMIC */
-    g_map.l_addr = 0;
-
-    if (at_phdr && at_phnum && at_phent) {
-        for (uint32_t i = 0; i < at_phnum; i++) {
-            const struct elf32_phdr* ph =
-                (const struct elf32_phdr*)(at_phdr + i * at_phent);
-            if (ph->p_type == PT_DYNAMIC) {
-                uint32_t dyn_va = ph->p_vaddr + g_map.l_addr;
-                const struct elf32_dyn* d = (const struct elf32_dyn*)dyn_va;
-                uint32_t pltgot = 0;
-
-                for (; d->d_tag != DT_NULL; d++) {
-                    switch (d->d_tag) {
-                    case DT_PLTGOT:   pltgot          = d->d_val; break;
-                    case DT_JMPREL:   g_map.jmprel    = d->d_val; break;
-                    case DT_PLTRELSZ: g_map.pltrelsz  = d->d_val; break;
-                    case DT_SYMTAB:   g_map.symtab    = d->d_val; break;
-                    case DT_STRTAB:   g_map.strtab    = d->d_val; break;
-                    case DT_REL:      g_map.rel       = d->d_val; break;
-                    case DT_RELSZ:    g_map.relsz     = d->d_val; break;
-                    }
-                }
-
-                /* Scan for shared library info BEFORE resolving relocations */
-                find_shlib_info();
-
-                /* Set up GOT for lazy binding:
-                 * GOT[0] = _DYNAMIC (already set by linker)
-                 * GOT[1] = link_map pointer
-                 * GOT[2] = _dl_runtime_resolve address */
-                if (pltgot && g_map.jmprel) {
-                    uint32_t* got = (uint32_t*)(pltgot + g_map.l_addr);
-                    got[1] = (uint32_t)&g_map;
-                    got[2] = (uint32_t)&_dl_runtime_resolve;
-                }
-
-                /* Process eager relocations (R_386_GLOB_DAT, R_386_COPY) */
-                if (g_map.rel && g_map.relsz) {
-                    uint32_t nrel = g_map.relsz / sizeof(struct elf32_rel);
-                    const struct elf32_rel* rtab =
-                        (const struct elf32_rel*)(g_map.rel + g_map.l_addr);
-                    for (uint32_t j = 0; j < nrel; j++) {
-                        uint32_t type = ELF32_R_TYPE(rtab[j].r_info);
-                        uint32_t sidx = ELF32_R_SYM(rtab[j].r_info);
-                        uint32_t* target = (uint32_t*)(rtab[j].r_offset + g_map.l_addr);
-                        if (type == R_386_GLOB_DAT || type == R_386_JMP_SLOT) {
-                            const struct elf32_sym* s =
-                                &((const struct elf32_sym*)g_map.symtab)[sidx];
-                            uint32_t addr = 0;
-                            if (s->st_value != 0)
-                                addr = s->st_value + g_map.l_addr;
-                            else {
-                                const char* nm = (const char*)g_map.strtab + s->st_name;
-                                addr = shlib_lookup(nm, &g_map);
-                            }
-                            if (addr) *target = addr;
-                        } else if (type == R_386_COPY && sidx) {
-                            const struct elf32_sym* s =
-                                &((const struct elf32_sym*)g_map.symtab)[sidx];
-                            const char* nm = (const char*)g_map.strtab + s->st_name;
-                            uint32_t src = shlib_lookup(nm, &g_map);
-                            if (src && s->st_size > 0) {
-                                const uint8_t* sp = (const uint8_t*)src;
-                                uint8_t* dp = (uint8_t*)target;
-                                for (uint32_t k = 0; k < s->st_size; k++)
-                                    dp[k] = sp[k];
-                            }
-                        }
-                    }
-                }
-                break;
-            }
-        }
-    }
-
-    /* Restore the original stack pointer so the real program's _start
-     * sees the correct layout: [argc] [argv...] [NULL] [envp...] [NULL] [auxv...]
-     * Then jump to the program entry point. */
-    __asm__ volatile(
-        "mov %0, %%esp\n"
-        "jmp *%1\n"
-        :: "r"(initial_sp), "r"(at_entry)
-        : "memory"
-    );
-    __builtin_unreachable();
-}
diff --git a/user/ln.c b/user/ln.c
deleted file mode 100644 (file)
index 72f60d4..0000000
--- a/user/ln.c
+++ /dev/null
@@ -1,33 +0,0 @@
-/* AdrOS ln utility */
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-
-int main(int argc, char** argv) {
-    int sflag = 0;
-    int start = 1;
-
-    if (argc > 1 && strcmp(argv[1], "-s") == 0) {
-        sflag = 1;
-        start = 2;
-    }
-
-    if (argc - start < 2) {
-        fprintf(stderr, "Usage: ln [-s] <target> <linkname>\n");
-        return 1;
-    }
-
-    int r;
-    if (sflag) {
-        r = symlink(argv[start], argv[start + 1]);
-    } else {
-        r = link(argv[start], argv[start + 1]);
-    }
-
-    if (r < 0) {
-        fprintf(stderr, "ln: failed to create %slink '%s' -> '%s'\n",
-                sflag ? "symbolic " : "", argv[start + 1], argv[start]);
-        return 1;
-    }
-    return 0;
-}
diff --git a/user/ls.c b/user/ls.c
deleted file mode 100644 (file)
index 6ce6c69..0000000
--- a/user/ls.c
+++ /dev/null
@@ -1,139 +0,0 @@
-/* AdrOS ls utility */
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <dirent.h>
-#include <sys/stat.h>
-
-static int aflag = 0;   /* -a: show hidden files */
-static int lflag = 0;   /* -l: long format */
-
-#define LS_MAX_ENTRIES 512
-
-struct ls_entry {
-    char name[256];
-    unsigned char type;
-};
-
-static struct ls_entry entries[LS_MAX_ENTRIES];
-
-static int cmp_entry(const void* a, const void* b) {
-    return strcmp(((const struct ls_entry*)a)->name,
-                  ((const struct ls_entry*)b)->name);
-}
-
-static void ls_dir(const char* path) {
-    int fd = open(path, O_RDONLY);
-    if (fd < 0) {
-        fprintf(stderr, "ls: cannot access '%s': No such file or directory\n", path);
-        return;
-    }
-
-    int count = 0;
-    char buf[2048];
-    int rc;
-    while ((rc = getdents(fd, buf, sizeof(buf))) > 0) {
-        int off = 0;
-        while (off < rc) {
-            struct dirent* d = (struct dirent*)(buf + off);
-            if (d->d_reclen == 0) break;
-
-            if (!aflag && d->d_name[0] == '.') {
-                off += d->d_reclen;
-                continue;
-            }
-
-            if (count < LS_MAX_ENTRIES) {
-                strncpy(entries[count].name, d->d_name, 255);
-                entries[count].name[255] = '\0';
-                entries[count].type = d->d_type;
-                count++;
-            }
-            off += d->d_reclen;
-        }
-    }
-    close(fd);
-
-    qsort(entries, count, sizeof(struct ls_entry), cmp_entry);
-
-    for (int i = 0; i < count; i++) {
-        if (lflag) {
-            char fullpath[512];
-            size_t plen = strlen(path);
-            if (plen > 0 && path[plen - 1] == '/')
-                snprintf(fullpath, sizeof(fullpath), "%s%s", path, entries[i].name);
-            else
-                snprintf(fullpath, sizeof(fullpath), "%s/%s", path, entries[i].name);
-
-            struct stat st;
-            int have_stat = (stat(fullpath, &st) == 0);
-
-            char type = '-';
-            if (entries[i].type == DT_DIR) type = 'd';
-            else if (entries[i].type == DT_CHR) type = 'c';
-            else if (entries[i].type == DT_LNK) type = 'l';
-            else if (entries[i].type == DT_BLK) type = 'b';
-
-            char perms[10];
-            if (have_stat) {
-                unsigned m = (unsigned)st.st_mode;
-                perms[0] = (m & S_IRUSR) ? 'r' : '-';
-                perms[1] = (m & S_IWUSR) ? 'w' : '-';
-                perms[2] = (m & S_IXUSR) ? 'x' : '-';
-                perms[3] = (m & S_IRGRP) ? 'r' : '-';
-                perms[4] = (m & S_IWGRP) ? 'w' : '-';
-                perms[5] = (m & S_IXGRP) ? 'x' : '-';
-                perms[6] = (m & S_IROTH) ? 'r' : '-';
-                perms[7] = (m & S_IWOTH) ? 'w' : '-';
-                perms[8] = (m & S_IXOTH) ? 'x' : '-';
-                perms[9] = '\0';
-            } else {
-                strcpy(perms, "---------");
-            }
-
-            unsigned long sz = have_stat ? (unsigned long)st.st_size : 0;
-            unsigned nlink = have_stat ? (unsigned)st.st_nlink : 1;
-
-            printf("%c%s %2u root root %8lu %s\n",
-                   type, perms, nlink, sz, entries[i].name);
-        } else {
-            printf("%s\n", entries[i].name);
-        }
-    }
-}
-
-int main(int argc, char** argv) {
-    int npath = 0;
-    const char* paths[64];
-
-    for (int i = 1; i < argc; i++) {
-        if (argv[i][0] == '-' && argv[i][1]) {
-            const char* f = argv[i] + 1;
-            while (*f) {
-                if (*f == 'a') aflag = 1;
-                else if (*f == 'l') lflag = 1;
-                else {
-                    fprintf(stderr, "ls: invalid option -- '%c'\n", *f);
-                    return 1;
-                }
-                f++;
-            }
-        } else {
-            if (npath < 64) paths[npath++] = argv[i];
-        }
-    }
-
-    if (npath == 0) {
-        ls_dir(".");
-    } else {
-        for (int i = 0; i < npath; i++) {
-            if (npath > 1) printf("%s:\n", paths[i]);
-            ls_dir(paths[i]);
-            if (npath > 1 && i < npath - 1) printf("\n");
-        }
-    }
-
-    return 0;
-}
diff --git a/user/mkdir.c b/user/mkdir.c
deleted file mode 100644 (file)
index d5716c5..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-/* AdrOS mkdir utility */
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-
-static int pflag = 0;   /* -p: create parent directories */
-
-static int mkdir_p(const char* path) {
-    char tmp[256];
-    size_t len = strlen(path);
-    if (len >= sizeof(tmp)) return -1;
-    strcpy(tmp, path);
-
-    for (char* p = tmp + 1; *p; p++) {
-        if (*p == '/') {
-            *p = '\0';
-            mkdir(tmp);   /* ignore errors — parent may already exist */
-            *p = '/';
-        }
-    }
-    return mkdir(tmp);
-}
-
-int main(int argc, char** argv) {
-    if (argc <= 1) {
-        fprintf(stderr, "mkdir: missing operand\n");
-        return 1;
-    }
-
-    int rc = 0;
-    int start = 1;
-
-    for (int i = 1; i < argc; i++) {
-        if (argv[i][0] == '-' && argv[i][1]) {
-            const char* f = argv[i] + 1;
-            while (*f) {
-                if (*f == 'p') pflag = 1;
-                else {
-                    fprintf(stderr, "mkdir: invalid option -- '%c'\n", *f);
-                    return 1;
-                }
-                f++;
-            }
-            start = i + 1;
-        }
-    }
-
-    for (int i = start; i < argc; i++) {
-        int r;
-        if (pflag) {
-            r = mkdir_p(argv[i]);
-        } else {
-            r = mkdir(argv[i]);
-        }
-        if (r < 0) {
-            fprintf(stderr, "mkdir: cannot create directory '%s'\n", argv[i]);
-            rc = 1;
-        }
-    }
-    return rc;
-}
diff --git a/user/mount.c b/user/mount.c
deleted file mode 100644 (file)
index fba0bf5..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-/* AdrOS mount utility — mount filesystems or display mounts */
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <syscall.h>
-#include <errno.h>
-
-static void show_mounts(void) {
-    int fd = open("/proc/mounts", O_RDONLY);
-    if (fd >= 0) {
-        char buf[1024];
-        int n;
-        while ((n = read(fd, buf, sizeof(buf))) > 0)
-            write(STDOUT_FILENO, buf, (size_t)n);
-        close(fd);
-    } else {
-        printf("tmpfs on / type overlayfs (rw)\n");
-        printf("devfs on /dev type devfs (rw)\n");
-        printf("procfs on /proc type procfs (ro)\n");
-    }
-}
-
-int main(int argc, char** argv) {
-    if (argc < 2) {
-        show_mounts();
-        return 0;
-    }
-
-    const char* fstype = "diskfs";
-    const char* device = NULL;
-    const char* mountpoint = NULL;
-
-    /* Parse options first, then collect positional args */
-    int i;
-    for (i = 1; i < argc; i++) {
-        if (strcmp(argv[i], "-t") == 0 && i + 1 < argc) {
-            fstype = argv[++i];
-        } else if (!device) {
-            device = argv[i];
-        } else if (!mountpoint) {
-            mountpoint = argv[i];
-        }
-    }
-
-    if (!device || !mountpoint) {
-        fprintf(stderr, "usage: mount [-t fstype] device mountpoint\n");
-        return 1;
-    }
-
-    int rc = __syscall_ret(_syscall3(SYS_MOUNT, (int)device, (int)mountpoint, (int)fstype));
-    if (rc < 0) {
-        fprintf(stderr, "mount: mounting %s on %s failed: %d\n", device, mountpoint, rc);
-        return 1;
-    }
-    return 0;
-}
diff --git a/user/mv.c b/user/mv.c
deleted file mode 100644 (file)
index 97cb1bc..0000000
--- a/user/mv.c
+++ /dev/null
@@ -1,45 +0,0 @@
-/* AdrOS mv utility */
-#include <stdio.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <string.h>
-
-int main(int argc, char** argv) {
-    if (argc < 3) {
-        fprintf(stderr, "Usage: mv <source> <dest>\n");
-        return 1;
-    }
-
-    /* Try rename first (same filesystem) */
-    if (rename(argv[1], argv[2]) == 0)
-        return 0;
-
-    /* Fallback: copy + unlink */
-    int src = open(argv[1], O_RDONLY);
-    if (src < 0) {
-        fprintf(stderr, "mv: cannot open '%s'\n", argv[1]);
-        return 1;
-    }
-
-    int dst = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0644);
-    if (dst < 0) {
-        fprintf(stderr, "mv: cannot create '%s'\n", argv[2]);
-        close(src);
-        return 1;
-    }
-
-    char buf[4096];
-    int r;
-    while ((r = read(src, buf, sizeof(buf))) > 0) {
-        if (write(dst, buf, (size_t)r) != r) {
-            fprintf(stderr, "mv: write error\n");
-            close(src); close(dst);
-            return 1;
-        }
-    }
-
-    close(src);
-    close(dst);
-    unlink(argv[1]);
-    return 0;
-}
diff --git a/user/pie_func.c b/user/pie_func.c
deleted file mode 100644 (file)
index 8e744f8..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-/* Shared library function for PLT/GOT lazy binding test.
- * Compiled as a shared object (libpietest.so), loaded at SHLIB_BASE by kernel.
- * The main PIE binary calls test_add() through PLT — resolved lazily by ld.so. */
-
-int test_add(int a, int b) {
-    return a + b;
-}
diff --git a/user/pie_main.c b/user/pie_main.c
deleted file mode 100644 (file)
index 3a6805d..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-/* PIE test binary for PLT/GOT lazy binding verification.
- * Calls test_add() from libpietest.so through PLT — resolved lazily by ld.so.
- * Built as: i686-elf-ld -pie --dynamic-linker=/lib/ld.so */
-
-static inline void sys_exit(int code) {
-    __asm__ volatile("int $0x80" :: "a"(2), "b"(code) : "memory");
-}
-
-static inline int sys_write(int fd, const void* buf, unsigned len) {
-    int ret;
-    __asm__ volatile("int $0x80" : "=a"(ret) : "a"(1), "b"(fd), "c"(buf), "d"(len) : "memory");
-    return ret;
-}
-
-extern int test_add(int a, int b);
-
-void _start(void) {
-    int r = test_add(38, 4);
-    if (r == 42) {
-        sys_write(1, "[init] lazy PLT OK\n", 19);
-    } else {
-        sys_write(1, "[init] lazy PLT FAIL\n", 21);
-    }
-
-    /* Call again — this time GOT is already patched, tests direct path */
-    r = test_add(100, 23);
-    if (r == 123) {
-        sys_write(1, "[init] PLT cached OK\n", 21);
-    } else {
-        sys_write(1, "[init] PLT cached FAIL\n", 23);
-    }
-
-    sys_exit(0);
-}
diff --git a/user/printenv.c b/user/printenv.c
deleted file mode 100644 (file)
index 0e3f081..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-/* AdrOS printenv utility — print environment variables */
-#include <stdio.h>
-#include <string.h>
-
-extern char** __environ;
-
-int main(int argc, char** argv) {
-    if (!__environ) return 1;
-    if (argc <= 1) {
-        for (int i = 0; __environ[i]; i++)
-            printf("%s\n", __environ[i]);
-        return 0;
-    }
-    for (int i = 1; i < argc; i++) {
-        int found = 0;
-        int nlen = (int)strlen(argv[i]);
-        for (int j = 0; __environ[j]; j++) {
-            if (strncmp(__environ[j], argv[i], (size_t)nlen) == 0 && __environ[j][nlen] == '=') {
-                printf("%s\n", __environ[j] + nlen + 1);
-                found = 1;
-                break;
-            }
-        }
-        if (!found) return 1;
-    }
-    return 0;
-}
diff --git a/user/ps.c b/user/ps.c
deleted file mode 100644 (file)
index 251de0d..0000000
--- a/user/ps.c
+++ /dev/null
@@ -1,47 +0,0 @@
-/* AdrOS ps utility — list processes from /proc */
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <dirent.h>
-
-static int is_digit(char c) { return c >= '0' && c <= '9'; }
-
-int main(void) {
-    printf("  PID CMD\n");
-    int fd = open("/proc", O_RDONLY);
-    if (fd < 0) {
-        fprintf(stderr, "ps: cannot open /proc\n");
-        return 1;
-    }
-    char buf[512];
-    int rc;
-    while ((rc = getdents(fd, buf, sizeof(buf))) > 0) {
-        int off = 0;
-        while (off < rc) {
-            struct dirent* d = (struct dirent*)(buf + off);
-            if (d->d_reclen == 0) break;
-            if (is_digit(d->d_name[0])) {
-                char path[64];
-                snprintf(path, sizeof(path), "/proc/%s/cmdline", d->d_name);
-                int cfd = open(path, O_RDONLY);
-                char cmd[64] = "?";
-                if (cfd >= 0) {
-                    int n = read(cfd, cmd, sizeof(cmd) - 1);
-                    if (n > 0) {
-                        cmd[n] = '\0';
-                        while (n > 0 && (cmd[n-1] == '\n' || cmd[n-1] == '\0')) {
-                            cmd[--n] = '\0';
-                        }
-                    }
-                    if (n <= 0) strcpy(cmd, "[kernel]");
-                    close(cfd);
-                }
-                printf("%5s %s\n", d->d_name, cmd);
-            }
-            off += d->d_reclen;
-        }
-    }
-    close(fd);
-    return 0;
-}
diff --git a/user/pwd.c b/user/pwd.c
deleted file mode 100644 (file)
index baa6134..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-/* AdrOS pwd utility — print working directory */
-#include <stdio.h>
-#include <unistd.h>
-
-int main(void) {
-    char buf[256];
-    if (getcwd(buf, sizeof(buf)) >= 0)
-        printf("%s\n", buf);
-    else {
-        fprintf(stderr, "pwd: error\n");
-        return 1;
-    }
-    return 0;
-}
diff --git a/user/rm.c b/user/rm.c
deleted file mode 100644 (file)
index 0a4d22e..0000000
--- a/user/rm.c
+++ /dev/null
@@ -1,48 +0,0 @@
-/* AdrOS rm utility */
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-
-static int rflag = 0;   /* -r: recursive */
-static int fflag = 0;   /* -f: force (no errors) */
-static int dflag = 0;   /* -d: remove empty directories */
-
-int main(int argc, char** argv) {
-    if (argc <= 1) {
-        fprintf(stderr, "rm: missing operand\n");
-        return 1;
-    }
-
-    int start = 1;
-    for (int i = 1; i < argc; i++) {
-        if (argv[i][0] == '-' && argv[i][1]) {
-            const char* f = argv[i] + 1;
-            while (*f) {
-                if (*f == 'r' || *f == 'R') rflag = 1;
-                else if (*f == 'f') fflag = 1;
-                else if (*f == 'd') dflag = 1;
-                else {
-                    fprintf(stderr, "rm: invalid option -- '%c'\n", *f);
-                    return 1;
-                }
-                f++;
-            }
-            start = i + 1;
-        } else {
-            break;
-        }
-    }
-
-    int rc = 0;
-    for (int i = start; i < argc; i++) {
-        int r = unlink(argv[i]);
-        if (r < 0 && (rflag || dflag)) {
-            r = rmdir(argv[i]);
-        }
-        if (r < 0 && !fflag) {
-            fprintf(stderr, "rm: cannot remove '%s'\n", argv[i]);
-            rc = 1;
-        }
-    }
-    return rc;
-}
diff --git a/user/rmdir.c b/user/rmdir.c
deleted file mode 100644 (file)
index 0d65e8a..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-/* AdrOS rmdir utility — remove empty directories */
-#include <stdio.h>
-#include <unistd.h>
-
-int main(int argc, char** argv) {
-    if (argc <= 1) {
-        fprintf(stderr, "rmdir: missing operand\n");
-        return 1;
-    }
-    int rc = 0;
-    for (int i = 1; i < argc; i++) {
-        if (rmdir(argv[i]) < 0) {
-            fprintf(stderr, "rmdir: failed to remove '%s'\n", argv[i]);
-            rc = 1;
-        }
-    }
-    return rc;
-}
diff --git a/user/sed.c b/user/sed.c
deleted file mode 100644 (file)
index 53992f2..0000000
+++ /dev/null
@@ -1,95 +0,0 @@
-/* AdrOS sed utility — minimal stream editor (s/pattern/replacement/g only) */
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-#include <fcntl.h>
-
-static int match_at(const char* s, const char* pat, int patlen) {
-    for (int i = 0; i < patlen; i++) {
-        if (s[i] == '\0' || s[i] != pat[i]) return 0;
-    }
-    return 1;
-}
-
-static void sed_substitute(const char* line, const char* pat, int patlen,
-                           const char* rep, int replen, int global) {
-    const char* p = line;
-    while (*p) {
-        if (match_at(p, pat, patlen)) {
-            write(STDOUT_FILENO, rep, replen);
-            p += patlen;
-            if (!global) {
-                write(STDOUT_FILENO, p, strlen(p));
-                return;
-            }
-        } else {
-            write(STDOUT_FILENO, p, 1);
-            p++;
-        }
-    }
-}
-
-static int parse_s_cmd(const char* expr, char* pat, int* patlen,
-                       char* rep, int* replen, int* global) {
-    if (expr[0] != 's' || expr[1] == '\0') return -1;
-    char delim = expr[1];
-    const char* p = expr + 2;
-    int pi = 0;
-    while (*p && *p != delim && pi < 255) pat[pi++] = *p++;
-    pat[pi] = '\0'; *patlen = pi;
-    if (*p != delim) return -1;
-    p++;
-    int ri = 0;
-    while (*p && *p != delim && ri < 255) rep[ri++] = *p++;
-    rep[ri] = '\0'; *replen = ri;
-    *global = 0;
-    if (*p == delim) { p++; if (*p == 'g') *global = 1; }
-    return 0;
-}
-
-int main(int argc, char** argv) {
-    if (argc < 2) {
-        fprintf(stderr, "Usage: sed 's/pattern/replacement/[g]' [file]\n");
-        return 1;
-    }
-
-    char pat[256], rep[256];
-    int patlen, replen, global;
-    int ei = 1;
-    if (strcmp(argv[1], "-e") == 0 && argc > 2) ei = 2;
-
-    if (parse_s_cmd(argv[ei], pat, &patlen, rep, &replen, &global) < 0) {
-        fprintf(stderr, "sed: invalid expression: %s\n", argv[ei]);
-        return 1;
-    }
-
-    int fd = STDIN_FILENO;
-    if (argc > ei + 1) {
-        fd = open(argv[ei + 1], O_RDONLY);
-        if (fd < 0) {
-            fprintf(stderr, "sed: %s: No such file or directory\n", argv[ei + 1]);
-            return 1;
-        }
-    }
-
-    char line[4096];
-    int li = 0;
-    char c;
-    while (read(fd, &c, 1) == 1) {
-        if (c == '\n') {
-            line[li] = '\0';
-            sed_substitute(line, pat, patlen, rep, replen, global);
-            write(STDOUT_FILENO, "\n", 1);
-            li = 0;
-        } else if (li < (int)sizeof(line) - 1) {
-            line[li++] = c;
-        }
-    }
-    if (li > 0) {
-        line[li] = '\0';
-        sed_substitute(line, pat, patlen, rep, replen, global);
-    }
-
-    if (fd != STDIN_FILENO) close(fd);
-    return 0;
-}
diff --git a/user/sh.c b/user/sh.c
deleted file mode 100644 (file)
index 1af3ab3..0000000
--- a/user/sh.c
+++ /dev/null
@@ -1,1035 +0,0 @@
-/* AdrOS POSIX-like shell (/bin/sh)
- *
- * Features:
- *   - Variable assignment (VAR=value) and expansion ($VAR)
- *   - Environment variables (export VAR=value)
- *   - Line editing (left/right arrow keys)
- *   - Command history (up/down arrow keys)
- *   - Pipes (cmd1 | cmd2 | cmd3)
- *   - Redirections (< > >>)
- *   - Operators: ; && || &
- *   - Job control: CTRL+C (SIGINT), CTRL+Z (SIGTSTP), background (&)
- *   - Builtins: cd, exit, echo, export, unset, set, pwd, type
- *   - PATH-based command resolution
- *   - Quote handling (single and double quotes)
- */
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <dirent.h>
-#include <termios.h>
-#include <signal.h>
-#include <sys/ioctl.h>
-#include <sys/wait.h>
-
-static struct termios orig_termios;
-
-static void tty_raw_mode(void) {
-    tcgetattr(STDIN_FILENO, &orig_termios);
-    struct termios raw = orig_termios;
-    raw.c_lflag &= ~(ICANON | ECHO | ISIG);
-    raw.c_cc[VMIN] = 1;
-    raw.c_cc[VTIME] = 0;
-    tcsetattr(STDIN_FILENO, TCSANOW, &raw);
-}
-
-static void tty_restore(void) {
-    tcsetattr(STDIN_FILENO, TCSANOW, &orig_termios);
-}
-
-#define LINE_MAX   512
-#define MAX_ARGS   64
-#define MAX_VARS   64
-#define HIST_SIZE  32
-
-/* ---- Shell variables ---- */
-
-static struct {
-    char name[64];
-    char value[256];
-    int  exported;
-} vars[MAX_VARS];
-static int nvar = 0;
-
-static int last_status = 0;
-
-static const char* var_get(const char* name) {
-    for (int i = 0; i < nvar; i++)
-        if (strcmp(vars[i].name, name) == 0) return vars[i].value;
-    return getenv(name);
-}
-
-static void var_set(const char* name, const char* value, int exported) {
-    for (int i = 0; i < nvar; i++) {
-        if (strcmp(vars[i].name, name) == 0) {
-            strncpy(vars[i].value, value, 255);
-            vars[i].value[255] = '\0';
-            if (exported) vars[i].exported = 1;
-            return;
-        }
-    }
-    if (nvar < MAX_VARS) {
-        strncpy(vars[nvar].name, name, 63);
-        vars[nvar].name[63] = '\0';
-        strncpy(vars[nvar].value, value, 255);
-        vars[nvar].value[255] = '\0';
-        vars[nvar].exported = exported;
-        nvar++;
-    }
-}
-
-static void var_unset(const char* name) {
-    for (int i = 0; i < nvar; i++) {
-        if (strcmp(vars[i].name, name) == 0) {
-            vars[i] = vars[--nvar];
-            return;
-        }
-    }
-}
-
-/* Build envp array from exported variables */
-static char env_buf[MAX_VARS][320];
-static char* envp_arr[MAX_VARS + 1];
-
-static char** build_envp(void) {
-    int n = 0;
-    for (int i = 0; i < nvar && n < MAX_VARS; i++) {
-        if (!vars[i].exported) continue;
-        snprintf(env_buf[n], sizeof(env_buf[n]), "%s=%s",
-                 vars[i].name, vars[i].value);
-        envp_arr[n] = env_buf[n];
-        n++;
-    }
-    envp_arr[n] = NULL;
-    return envp_arr;
-}
-
-/* ---- Command history ---- */
-
-static char history[HIST_SIZE][LINE_MAX];
-static int hist_count = 0;
-static int hist_pos = 0;
-
-static void hist_add(const char* line) {
-    if (line[0] == '\0') return;
-    if (hist_count > 0 && strcmp(history[(hist_count - 1) % HIST_SIZE], line) == 0)
-        return;
-    strncpy(history[hist_count % HIST_SIZE], line, LINE_MAX - 1);
-    history[hist_count % HIST_SIZE][LINE_MAX - 1] = '\0';
-    hist_count++;
-}
-
-/* ---- Line editing ---- */
-
-static char line[LINE_MAX];
-
-static void term_write(const char* s, int n) {
-    write(STDOUT_FILENO, s, (size_t)n);
-}
-
-/* ---- Tab completion ---- */
-
-static int tab_complete(char* buf, int* p_pos, int* p_len) {
-    int pos = *p_pos;
-    int len = *p_len;
-
-    /* Find the start of the current word */
-    int wstart = pos;
-    while (wstart > 0 && buf[wstart - 1] != ' ' && buf[wstart - 1] != '\t')
-        wstart--;
-
-    char prefix[128];
-    int plen = pos - wstart;
-    if (plen <= 0 || plen >= (int)sizeof(prefix)) return 0;
-    memcpy(prefix, buf + wstart, (size_t)plen);
-    prefix[plen] = '\0';
-
-    /* Determine if this is a command (first word) or filename */
-    int is_cmd = 1;
-    for (int i = 0; i < wstart; i++) {
-        if (buf[i] != ' ' && buf[i] != '\t') { is_cmd = 0; break; }
-    }
-
-    char match[128];
-    match[0] = '\0';
-    int nmatches = 0;
-
-    /* Split prefix into directory part and name part for file completion */
-    char dirpath[128] = ".";
-    const char* namepfx = prefix;
-    char* lastsep = NULL;
-    for (char* p = prefix; *p; p++) {
-        if (*p == '/') lastsep = p;
-    }
-    if (lastsep) {
-        int dlen = (int)(lastsep - prefix);
-        if (dlen == 0) { dirpath[0] = '/'; dirpath[1] = '\0'; }
-        else { memcpy(dirpath, prefix, (size_t)dlen); dirpath[dlen] = '\0'; }
-        namepfx = lastsep + 1;
-    }
-    int nplen = (int)strlen(namepfx);
-
-    if (!is_cmd || lastsep) {
-        /* File/directory completion */
-        int fd = open(dirpath, 0);
-        if (fd >= 0) {
-            char dbuf[512];
-            int rc;
-            while ((rc = getdents(fd, dbuf, sizeof(dbuf))) > 0) {
-                int off = 0;
-                while (off < rc) {
-                    struct dirent* d = (struct dirent*)(dbuf + off);
-                    if (d->d_reclen == 0) break;
-                    if (d->d_name[0] != '.' || nplen > 0) {
-                        int nlen = (int)strlen(d->d_name);
-                        if (nlen >= nplen && memcmp(d->d_name, namepfx, (size_t)nplen) == 0) {
-                            if (nmatches == 0) strcpy(match, d->d_name);
-                            nmatches++;
-                        }
-                    }
-                    off += d->d_reclen;
-                }
-            }
-            close(fd);
-        }
-    }
-
-    if (is_cmd && !lastsep) {
-        /* Command completion: search PATH directories + builtins */
-        static const char* builtins[] = {
-            "cd", "exit", "echo", "export", "unset", "set", "pwd", "type", NULL
-        };
-        for (int i = 0; builtins[i]; i++) {
-            int blen = (int)strlen(builtins[i]);
-            if (blen >= plen && memcmp(builtins[i], prefix, (size_t)plen) == 0) {
-                if (nmatches == 0) strcpy(match, builtins[i]);
-                nmatches++;
-            }
-        }
-        const char* path_env = var_get("PATH");
-        if (!path_env) path_env = "/bin:/sbin:/usr/bin";
-        char pathcopy[512];
-        strncpy(pathcopy, path_env, sizeof(pathcopy) - 1);
-        pathcopy[sizeof(pathcopy) - 1] = '\0';
-        char* save = pathcopy;
-        char* dir;
-        while ((dir = save) != NULL) {
-            char* sep = strchr(save, ':');
-            if (sep) { *sep = '\0'; save = sep + 1; } else save = NULL;
-            int fd = open(dir, 0);
-            if (fd < 0) continue;
-            char dbuf[512];
-            int rc;
-            while ((rc = getdents(fd, dbuf, sizeof(dbuf))) > 0) {
-                int off = 0;
-                while (off < rc) {
-                    struct dirent* d = (struct dirent*)(dbuf + off);
-                    if (d->d_reclen == 0) break;
-                    int nlen = (int)strlen(d->d_name);
-                    if (nlen >= plen && memcmp(d->d_name, prefix, (size_t)plen) == 0) {
-                        if (nmatches == 0) strcpy(match, d->d_name);
-                        nmatches++;
-                    }
-                    off += d->d_reclen;
-                }
-            }
-            close(fd);
-        }
-    }
-
-    if (nmatches != 1) return 0;
-
-    /* Insert the completion suffix */
-    int mlen = (int)strlen(match);
-    int suffix_len = is_cmd && !lastsep ? mlen - plen : mlen - nplen;
-    const char* suffix = is_cmd && !lastsep ? match + plen : match + nplen;
-    if (suffix_len <= 0 || len + suffix_len >= LINE_MAX - 1) return 0;
-
-    memmove(buf + pos + suffix_len, buf + pos, (size_t)(len - pos));
-    memcpy(buf + pos, suffix, (size_t)suffix_len);
-    len += suffix_len;
-    buf[len] = '\0';
-    term_write(buf + pos, len - pos);
-    pos += suffix_len;
-    for (int i = 0; i < len - pos; i++) term_write("\b", 1);
-    *p_pos = pos;
-    *p_len = len;
-    return 1;
-}
-
-static int read_line_edit(void) {
-    int pos = 0;
-    int len = 0;
-    hist_pos = hist_count;
-
-    memset(line, 0, LINE_MAX);
-
-    while (len < LINE_MAX - 1) {
-        char c;
-        int r = read(STDIN_FILENO, &c, 1);
-        if (r <= 0) {
-            if (len == 0) return -1;
-            break;
-        }
-
-        if (c == '\n' || c == '\r') {
-            term_write("\n", 1);
-            break;
-        }
-
-        /* Backspace / DEL */
-        if (c == '\b' || c == 127) {
-            if (pos > 0) {
-                memmove(line + pos - 1, line + pos, (size_t)(len - pos));
-                pos--; len--;
-                line[len] = '\0';
-                /* Redraw: move cursor back, print rest, clear tail */
-                term_write("\b", 1);
-                term_write(line + pos, len - pos);
-                term_write(" \b", 2);
-                for (int i = 0; i < len - pos; i++) term_write("\b", 1);
-            }
-            continue;
-        }
-
-        /* Tab = autocomplete */
-        if (c == '\t') {
-            tab_complete(line, &pos, &len);
-            continue;
-        }
-
-        /* Ctrl+D = EOF */
-        if (c == 4) {
-            if (len == 0) return -1;
-            continue;
-        }
-
-        /* Ctrl+C = cancel line */
-        if (c == 3) {
-            term_write("^C\n", 3);
-            line[0] = '\0';
-            return 0;
-        }
-
-        /* Ctrl+Z = ignored at prompt (no foreground job to suspend) */
-        if (c == 26) {
-            continue;
-        }
-
-        /* Ctrl+A = beginning of line */
-        if (c == 1) {
-            while (pos > 0) { term_write("\b", 1); pos--; }
-            continue;
-        }
-
-        /* Ctrl+E = end of line */
-        if (c == 5) {
-            term_write(line + pos, len - pos);
-            pos = len;
-            continue;
-        }
-
-        /* Ctrl+U = clear line */
-        if (c == 21) {
-            while (pos > 0) { term_write("\b", 1); pos--; }
-            for (int i = 0; i < len; i++) term_write(" ", 1);
-            for (int i = 0; i < len; i++) term_write("\b", 1);
-            len = 0; pos = 0;
-            line[0] = '\0';
-            continue;
-        }
-
-        /* Escape sequences (arrow keys) */
-        if (c == 27) {
-            char seq[2];
-            if (read(STDIN_FILENO, &seq[0], 1) <= 0) continue;
-            if (seq[0] != '[') continue;
-            if (read(STDIN_FILENO, &seq[1], 1) <= 0) continue;
-
-            if (seq[1] >= '0' && seq[1] <= '9') {
-                /* Extended sequence like \x1b[3~ (DELETE), \x1b[1~ (Home), \x1b[4~ (End) */
-                char trail;
-                if (read(STDIN_FILENO, &trail, 1) <= 0) continue;
-                if (trail == '~') {
-                    if (seq[1] == '3') {
-                        /* DELETE key — delete char at cursor */
-                        if (pos < len) {
-                            memmove(line + pos, line + pos + 1, (size_t)(len - pos - 1));
-                            len--;
-                            line[len] = '\0';
-                            term_write(line + pos, len - pos);
-                            term_write(" \b", 2);
-                            for (int i = 0; i < len - pos; i++) term_write("\b", 1);
-                        }
-                    } else if (seq[1] == '1') {
-                        /* Home */
-                        while (pos > 0) { term_write("\b", 1); pos--; }
-                    } else if (seq[1] == '4') {
-                        /* End */
-                        term_write(line + pos, len - pos);
-                        pos = len;
-                    }
-                }
-                continue;
-            }
-
-            switch (seq[1]) {
-            case 'A':  /* Up arrow — previous history */
-                if (hist_pos > 0 && hist_pos > hist_count - HIST_SIZE) {
-                    hist_pos--;
-                    /* Clear current line */
-                    while (pos > 0) { term_write("\b", 1); pos--; }
-                    for (int i = 0; i < len; i++) term_write(" ", 1);
-                    for (int i = 0; i < len; i++) term_write("\b", 1);
-                    /* Load history entry */
-                    strcpy(line, history[hist_pos % HIST_SIZE]);
-                    len = (int)strlen(line);
-                    pos = len;
-                    term_write(line, len);
-                }
-                break;
-            case 'B':  /* Down arrow — next history */
-                if (hist_pos < hist_count) {
-                    hist_pos++;
-                    while (pos > 0) { term_write("\b", 1); pos--; }
-                    for (int i = 0; i < len; i++) term_write(" ", 1);
-                    for (int i = 0; i < len; i++) term_write("\b", 1);
-                    if (hist_pos < hist_count) {
-                        strcpy(line, history[hist_pos % HIST_SIZE]);
-                    } else {
-                        line[0] = '\0';
-                    }
-                    len = (int)strlen(line);
-                    pos = len;
-                    term_write(line, len);
-                }
-                break;
-            case 'C':  /* Right arrow */
-                if (pos < len) { term_write(line + pos, 1); pos++; }
-                break;
-            case 'D':  /* Left arrow */
-                if (pos > 0) { term_write("\b", 1); pos--; }
-                break;
-            case 'H':  /* Home */
-                while (pos > 0) { term_write("\b", 1); pos--; }
-                break;
-            case 'F':  /* End */
-                term_write(line + pos, len - pos);
-                pos = len;
-                break;
-            }
-            continue;
-        }
-
-        /* Normal printable character */
-        if (c >= ' ' && c <= '~') {
-            memmove(line + pos + 1, line + pos, (size_t)(len - pos));
-            line[pos] = c;
-            len++; line[len] = '\0';
-            term_write(line + pos, len - pos);
-            pos++;
-            for (int i = 0; i < len - pos; i++) term_write("\b", 1);
-        }
-    }
-
-    line[len] = '\0';
-    return len;
-}
-
-/* ---- Variable expansion ---- */
-
-static void expand_vars(const char* src, char* dst, int maxlen) {
-    int di = 0;
-    while (*src && di < maxlen - 1) {
-        if (*src == '$') {
-            src++;
-            if (*src == '?') {
-                di += snprintf(dst + di, (size_t)(maxlen - di), "%d", last_status);
-                src++;
-            } else if (*src == '{') {
-                src++;
-                char name[64];
-                int ni = 0;
-                while (*src && *src != '}' && ni < 63) name[ni++] = *src++;
-                name[ni] = '\0';
-                if (*src == '}') src++;
-                const char* val = var_get(name);
-                if (val) {
-                    int vl = (int)strlen(val);
-                    if (di + vl < maxlen) { memcpy(dst + di, val, (size_t)vl); di += vl; }
-                }
-            } else {
-                char name[64];
-                int ni = 0;
-                while ((*src >= 'A' && *src <= 'Z') || (*src >= 'a' && *src <= 'z') ||
-                       (*src >= '0' && *src <= '9') || *src == '_') {
-                    if (ni < 63) name[ni++] = *src;
-                    src++;
-                }
-                name[ni] = '\0';
-                const char* val = var_get(name);
-                if (val) {
-                    int vl = (int)strlen(val);
-                    if (di + vl < maxlen) { memcpy(dst + di, val, (size_t)vl); di += vl; }
-                }
-            }
-        } else {
-            dst[di++] = *src++;
-        }
-    }
-    dst[di] = '\0';
-}
-
-/* ---- Argument parsing with quote handling ---- */
-
-static int parse_args(char* cmd, char** argv, int max) {
-    int argc = 0;
-    char* p = cmd;
-    while (*p && argc < max - 1) {
-        while (*p == ' ' || *p == '\t') p++;
-        if (*p == '\0') break;
-
-        char* out = p;
-        argv[argc++] = out;
-
-        while (*p && *p != ' ' && *p != '\t') {
-            if (*p == '\'' ) {
-                p++;
-                while (*p && *p != '\'') *out++ = *p++;
-                if (*p == '\'') p++;
-            } else if (*p == '"') {
-                p++;
-                while (*p && *p != '"') *out++ = *p++;
-                if (*p == '"') p++;
-            } else {
-                *out++ = *p++;
-            }
-        }
-        if (*p) p++;
-        *out = '\0';
-    }
-    argv[argc] = NULL;
-    return argc;
-}
-
-/* ---- PATH resolution ---- */
-
-static char pathbuf[256];
-
-static const char* resolve(const char* cmd) {
-    if (cmd[0] == '/' || cmd[0] == '.') return cmd;
-
-    const char* path_env = var_get("PATH");
-    if (!path_env) path_env = "/bin:/sbin:/usr/bin";
-
-    char pathcopy[512];
-    strncpy(pathcopy, path_env, sizeof(pathcopy) - 1);
-    pathcopy[sizeof(pathcopy) - 1] = '\0';
-
-    char* save = pathcopy;
-    char* dir;
-    while ((dir = save) != NULL) {
-        char* sep = strchr(save, ':');
-        if (sep) { *sep = '\0'; save = sep + 1; }
-        else save = NULL;
-
-        snprintf(pathbuf, sizeof(pathbuf), "%s/%s", dir, cmd);
-        if (access(pathbuf, 0) == 0) return pathbuf;
-    }
-    return cmd;
-}
-
-/* ---- Helper: set foreground process group on controlling TTY ---- */
-
-static void set_fg_pgrp(int pgrp) {
-    ioctl(STDIN_FILENO, TIOCSPGRP, &pgrp);
-}
-
-/* ---- Run a single simple command ---- */
-
-static int bg_flag = 0;  /* set by caller when trailing & detected */
-
-static void run_simple(char* cmd) {
-    /* Expand variables */
-    char expanded[LINE_MAX];
-    expand_vars(cmd, expanded, LINE_MAX);
-
-    char* argv[MAX_ARGS];
-    int argc = parse_args(expanded, argv, MAX_ARGS);
-    if (argc == 0) return;
-
-    /* Check for variable assignment (no command, just VAR=value) */
-    if (argc == 1 && strchr(argv[0], '=') != NULL) {
-        char* eq = strchr(argv[0], '=');
-        *eq = '\0';
-        var_set(argv[0], eq + 1, 0);
-        last_status = 0;
-        return;
-    }
-
-    /* Extract redirections */
-    char* redir_out = NULL;
-    char* redir_in  = NULL;
-    int   append = 0;
-    int   heredoc_fd = -1;
-    int nargc = 0;
-    for (int i = 0; i < argc; i++) {
-        if (strcmp(argv[i], ">>") == 0 && i + 1 < argc) {
-            redir_out = argv[++i]; append = 1;
-        } else if (strcmp(argv[i], ">") == 0 && i + 1 < argc) {
-            redir_out = argv[++i]; append = 0;
-        } else if (strcmp(argv[i], "<<") == 0 && i + 1 < argc) {
-            char* delim = argv[++i];
-            int dlen = (int)strlen(delim);
-            if (dlen > 0 && (delim[0] == '"' || delim[0] == '\'')) {
-                delim++; dlen -= 2; if (dlen < 0) dlen = 0;
-                delim[dlen] = '\0';
-            }
-            int pfd[2];
-            if (pipe(pfd) == 0) {
-                tty_restore();
-                char hline[LINE_MAX];
-                while (1) {
-                    write(STDOUT_FILENO, "> ", 2);
-                    int hi = 0;
-                    char hc;
-                    while (read(STDIN_FILENO, &hc, 1) == 1) {
-                        if (hc == '\n') break;
-                        if (hi < LINE_MAX - 1) hline[hi++] = hc;
-                    }
-                    hline[hi] = '\0';
-                    if (strcmp(hline, delim) == 0) break;
-                    write(pfd[1], hline, hi);
-                    write(pfd[1], "\n", 1);
-                }
-                close(pfd[1]);
-                heredoc_fd = pfd[0];
-                tty_raw_mode();
-            }
-        } else if (strcmp(argv[i], "<") == 0 && i + 1 < argc) {
-            redir_in = argv[++i];
-        } else {
-            argv[nargc++] = argv[i];
-        }
-    }
-    argv[nargc] = NULL;
-    argc = nargc;
-    if (argc == 0) return;
-
-    /* ---- Apply redirections for builtins too ---- */
-    int saved_stdin = -1, saved_stdout = -1;
-    if (heredoc_fd >= 0) {
-        saved_stdin = dup(0); dup2(heredoc_fd, 0); close(heredoc_fd); heredoc_fd = -1;
-    } else if (redir_in) {
-        int fd = open(redir_in, O_RDONLY);
-        if (fd >= 0) { saved_stdin = dup(0); dup2(fd, 0); close(fd); }
-    }
-    if (redir_out) {
-        int flags = O_WRONLY | O_CREAT;
-        flags |= append ? O_APPEND : O_TRUNC;
-        int fd = open(redir_out, flags);
-        if (fd >= 0) { saved_stdout = dup(1); dup2(fd, 1); close(fd); }
-    }
-
-    /* ---- Builtins ---- */
-
-    if (strcmp(argv[0], "exit") == 0) {
-        int code = argc > 1 ? atoi(argv[1]) : last_status;
-        exit(code);
-    }
-
-    if (strcmp(argv[0], "cd") == 0) {
-        const char* dir = argc > 1 ? argv[1] : var_get("HOME");
-        if (!dir) dir = "/";
-        if (chdir(dir) < 0)
-            fprintf(stderr, "cd: %s: No such file or directory\n", dir);
-        else {
-            char cwd[256];
-            if (getcwd(cwd, sizeof(cwd)) >= 0)
-                var_set("PWD", cwd, 1);
-        }
-        goto restore_redir;
-    }
-
-    if (strcmp(argv[0], "pwd") == 0) {
-        char cwd[256];
-        if (getcwd(cwd, sizeof(cwd)) >= 0)
-            printf("%s\n", cwd);
-        else
-            fprintf(stderr, "pwd: error\n");
-        goto restore_redir;
-    }
-
-    if (strcmp(argv[0], "export") == 0) {
-        for (int i = 1; i < argc; i++) {
-            char* eq = strchr(argv[i], '=');
-            if (eq) {
-                *eq = '\0';
-                var_set(argv[i], eq + 1, 1);
-            } else {
-                /* Export existing variable */
-                for (int j = 0; j < nvar; j++)
-                    if (strcmp(vars[j].name, argv[i]) == 0)
-                        vars[j].exported = 1;
-            }
-        }
-        goto restore_redir;
-    }
-
-    if (strcmp(argv[0], "unset") == 0) {
-        for (int i = 1; i < argc; i++) var_unset(argv[i]);
-        goto restore_redir;
-    }
-
-    if (strcmp(argv[0], "set") == 0) {
-        for (int i = 0; i < nvar; i++)
-            printf("%s=%s\n", vars[i].name, vars[i].value);
-        goto restore_redir;
-    }
-
-    if (strcmp(argv[0], "echo") == 0) {
-        int nflag = 0;
-        int start = 1;
-        if (argc > 1 && strcmp(argv[1], "-n") == 0) { nflag = 1; start = 2; }
-        for (int i = start; i < argc; i++) {
-            if (i > start) write(STDOUT_FILENO, " ", 1);
-            write(STDOUT_FILENO, argv[i], strlen(argv[i]));
-        }
-        if (!nflag) write(STDOUT_FILENO, "\n", 1);
-        goto restore_redir;
-    }
-
-    if (strcmp(argv[0], "type") == 0) {
-        for (int i = 1; i < argc; i++) {
-            if (strcmp(argv[i], "cd") == 0 || strcmp(argv[i], "exit") == 0 ||
-                strcmp(argv[i], "echo") == 0 || strcmp(argv[i], "export") == 0 ||
-                strcmp(argv[i], "unset") == 0 || strcmp(argv[i], "set") == 0 ||
-                strcmp(argv[i], "pwd") == 0 || strcmp(argv[i], "type") == 0) {
-                printf("%s is a shell builtin\n", argv[i]);
-            } else {
-                const char* path = resolve(argv[i]);
-                if (strcmp(path, argv[i]) != 0)
-                    printf("%s is %s\n", argv[i], path);
-                else
-                    printf("%s: not found\n", argv[i]);
-            }
-        }
-        goto restore_redir;
-    }
-
-    /* ---- External command — restore parent redirections before fork ---- */
-    const char* path = resolve(argv[0]);
-    char** envp = build_envp();
-
-    int pid = fork();
-    if (pid < 0) { fprintf(stderr, "sh: fork failed\n"); return; }
-
-    if (pid == 0) {
-        /* child: own process group, restore default signals */
-        setpgid(0, 0);
-        struct sigaction sa;
-        memset(&sa, 0, sizeof(sa));
-        sa.sa_handler = (uintptr_t)SIG_DFL;
-        sigaction(SIGINT, &sa, NULL);
-        sigaction(SIGTSTP, &sa, NULL);
-        sigaction(SIGQUIT, &sa, NULL);
-
-        if (redir_in) {
-            int fd = open(redir_in, O_RDONLY);
-            if (fd >= 0) { dup2(fd, 0); close(fd); }
-        }
-        if (redir_out) {
-            int flags = O_WRONLY | O_CREAT;
-            flags |= append ? O_APPEND : O_TRUNC;
-            int fd = open(redir_out, flags);
-            if (fd >= 0) { dup2(fd, 1); close(fd); }
-        }
-        execve(path, (const char* const*)argv, (const char* const*)envp);
-        fprintf(stderr, "sh: %s: not found\n", argv[0]);
-        _exit(127);
-    }
-
-    /* parent */
-    setpgid(pid, pid);
-
-    if (bg_flag) {
-        /* Background: don't wait, print job info */
-        printf("[bg] %d\n", pid);
-        last_status = 0;
-    } else {
-        /* Foreground: make child the fg process group, wait, then restore */
-        set_fg_pgrp(pid);
-        int st;
-        waitpid(pid, &st, 0);
-        set_fg_pgrp(getpgrp());
-        last_status = st;
-    }
-    goto restore_redir;
-
-restore_redir:
-    if (saved_stdout >= 0) { dup2(saved_stdout, 1); close(saved_stdout); }
-    if (saved_stdin >= 0)  { dup2(saved_stdin, 0);  close(saved_stdin);  }
-}
-
-/* ---- Pipeline support ---- */
-
-static void run_pipeline(char* cmdline) {
-    /* Split on '|' (outside quotes) */
-    char* cmds[8];
-    int ncmds = 0;
-    cmds[0] = cmdline;
-    int in_sq = 0, in_dq = 0;
-    for (char* p = cmdline; *p; p++) {
-        if (*p == '\'' && !in_dq) in_sq = !in_sq;
-        else if (*p == '"' && !in_sq) in_dq = !in_dq;
-        else if (*p == '|' && !in_sq && !in_dq && ncmds < 7) {
-            if (*(p + 1) == '|') { p++; continue; }  /* skip || */
-            *p = '\0';
-            cmds[++ncmds] = p + 1;
-        }
-    }
-    ncmds++;
-
-    if (ncmds == 1) {
-        run_simple(cmds[0]);
-        return;
-    }
-
-    /* Multi-stage pipeline */
-    int prev_rd = -1;
-    int pids[8];
-    int pgid = 0;  /* pipeline process group = first child's PID */
-
-    for (int i = 0; i < ncmds; i++) {
-        int pfd[2] = {-1, -1};
-        if (i < ncmds - 1) {
-            if (pipe(pfd) < 0) {
-                fprintf(stderr, "sh: pipe failed\n");
-                return;
-            }
-        }
-
-        pids[i] = fork();
-        if (pids[i] < 0) { fprintf(stderr, "sh: fork failed\n"); return; }
-
-        if (pids[i] == 0) {
-            /* child: join pipeline process group, restore signals */
-            int mypgid = pgid ? pgid : getpid();
-            setpgid(0, mypgid);
-            struct sigaction sa;
-            memset(&sa, 0, sizeof(sa));
-            sa.sa_handler = (uintptr_t)SIG_DFL;
-            sigaction(SIGINT, &sa, NULL);
-            sigaction(SIGTSTP, &sa, NULL);
-            sigaction(SIGQUIT, &sa, NULL);
-
-            if (prev_rd >= 0) { dup2(prev_rd, 0); close(prev_rd); }
-            if (pfd[1] >= 0)  { dup2(pfd[1], 1); close(pfd[1]); }
-            if (pfd[0] >= 0)  close(pfd[0]);
-
-            /* Expand and parse this pipeline stage */
-            char expanded[LINE_MAX];
-            expand_vars(cmds[i], expanded, LINE_MAX);
-            char* argv[MAX_ARGS];
-            int argc = parse_args(expanded, argv, MAX_ARGS);
-            if (argc == 0) _exit(0);
-            const char* path = resolve(argv[0]);
-            char** envp = build_envp();
-            execve(path, (const char* const*)argv, (const char* const*)envp);
-            fprintf(stderr, "sh: %s: not found\n", argv[0]);
-            _exit(127);
-        }
-
-        /* parent: set pipeline pgid */
-        if (i == 0) pgid = pids[0];
-        setpgid(pids[i], pgid);
-
-        if (prev_rd >= 0) close(prev_rd);
-        if (pfd[1] >= 0)  close(pfd[1]);
-        prev_rd = pfd[0];
-    }
-
-    if (prev_rd >= 0) close(prev_rd);
-
-    if (!bg_flag) {
-        /* Foreground pipeline: make it fg, wait, restore */
-        set_fg_pgrp(pgid);
-        for (int i = 0; i < ncmds; i++) {
-            int st;
-            waitpid(pids[i], &st, 0);
-            if (i == ncmds - 1) last_status = st;
-        }
-        set_fg_pgrp(getpgrp());
-    } else {
-        printf("[bg] %d\n", pgid);
-        last_status = 0;
-    }
-}
-
-/* ---- Process a command line (handle ;, &&, ||, &) ---- */
-
-enum { OP_NONE = 0, OP_SEMI, OP_AND, OP_OR, OP_BG };
-
-static void process_line(char* input) {
-    char* p = input;
-
-    while (*p) {
-        while (*p == ' ' || *p == '\t') p++;
-        if (*p == '\0') break;
-
-        /* Find the next operator outside quotes */
-        char* start = p;
-        int in_sq = 0, in_dq = 0;
-        int op = OP_NONE;
-
-        while (*p) {
-            if (*p == '\'' && !in_dq) { in_sq = !in_sq; p++; continue; }
-            if (*p == '"' && !in_sq)  { in_dq = !in_dq; p++; continue; }
-            if (in_sq || in_dq) { p++; continue; }
-
-            if (*p == '&' && *(p + 1) == '&') {
-                *p = '\0'; p += 2; op = OP_AND; break;
-            }
-            if (*p == '|' && *(p + 1) == '|') {
-                *p = '\0'; p += 2; op = OP_OR; break;
-            }
-            if (*p == ';') {
-                *p = '\0'; p++; op = OP_SEMI; break;
-            }
-            if (*p == '&') {
-                *p = '\0'; p++; op = OP_BG; break;
-            }
-            p++;
-        }
-
-        /* Trim leading whitespace from segment */
-        while (*start == ' ' || *start == '\t') start++;
-        if (*start != '\0') {
-            if (op == OP_BG) {
-                bg_flag = 1;
-                run_pipeline(start);
-                bg_flag = 0;
-            } else {
-                bg_flag = 0;
-                run_pipeline(start);
-            }
-        }
-
-        /* For &&: skip remaining commands if last failed */
-        if (op == OP_AND && last_status != 0) {
-            /* Skip until we hit || or ; or & or end */
-            while (*p) {
-                while (*p == ' ' || *p == '\t') p++;
-                if (*p == '\0') break;
-                int skip_sq = 0, skip_dq = 0;
-                while (*p) {
-                    if (*p == '\'' && !skip_dq) { skip_sq = !skip_sq; p++; continue; }
-                    if (*p == '"' && !skip_sq)  { skip_dq = !skip_dq; p++; continue; }
-                    if (skip_sq || skip_dq) { p++; continue; }
-                    if (*p == '|' && *(p + 1) == '|') break;
-                    if (*p == ';') break;
-                    if (*p == '&' && *(p + 1) != '&') break;
-                    if (*p == '&' && *(p + 1) == '&') { p += 2; continue; }
-                    p++;
-                }
-                break;
-            }
-        }
-
-        /* For ||: skip remaining commands if last succeeded */
-        if (op == OP_OR && last_status == 0) {
-            while (*p) {
-                while (*p == ' ' || *p == '\t') p++;
-                if (*p == '\0') break;
-                int skip_sq = 0, skip_dq = 0;
-                while (*p) {
-                    if (*p == '\'' && !skip_dq) { skip_sq = !skip_sq; p++; continue; }
-                    if (*p == '"' && !skip_sq)  { skip_dq = !skip_dq; p++; continue; }
-                    if (skip_sq || skip_dq) { p++; continue; }
-                    if (*p == '&' && *(p + 1) == '&') break;
-                    if (*p == ';') break;
-                    if (*p == '&' && *(p + 1) != '&') break;
-                    if (*p == '|' && *(p + 1) == '|') { p += 2; continue; }
-                    p++;
-                }
-                break;
-            }
-        }
-    }
-}
-
-/* ---- Prompt ---- */
-
-static void print_prompt(void) {
-    const char* user = var_get("USER");
-    const char* host = var_get("HOSTNAME");
-    char cwd[256];
-
-    if (!user) user = "root";
-    if (!host) host = "adros";
-
-    if (getcwd(cwd, sizeof(cwd)) < 0) strcpy(cwd, "?");
-
-    printf("%s@%s:%s$ ", user, host, cwd);
-    fflush(stdout);
-}
-
-/* ---- Main ---- */
-
-int main(int argc, char** argv, char** envp) {
-    (void)argc;
-    (void)argv;
-
-    /* Import environment variables */
-    if (envp) {
-        for (int i = 0; envp[i]; i++) {
-            char* eq = strchr(envp[i], '=');
-            if (eq) {
-                char name[64];
-                int nlen = (int)(eq - envp[i]);
-                if (nlen > 63) nlen = 63;
-                memcpy(name, envp[i], (size_t)nlen);
-                name[nlen] = '\0';
-                var_set(name, eq + 1, 1);
-            }
-        }
-    }
-
-    /* Set defaults */
-    if (!var_get("PATH"))
-        var_set("PATH", "/bin:/sbin:/usr/bin", 1);
-    if (!var_get("HOME"))
-        var_set("HOME", "/", 1);
-
-    /* Job control: create session + process group, become fg */
-    setsid();
-    set_fg_pgrp(getpgrp());
-
-    /* Ignore job control signals in the shell itself */
-    struct sigaction sa_ign;
-    memset(&sa_ign, 0, sizeof(sa_ign));
-    sa_ign.sa_handler = (uintptr_t)SIG_IGN;
-    sigaction(SIGINT, &sa_ign, NULL);
-    sigaction(SIGTSTP, &sa_ign, NULL);
-    sigaction(SIGQUIT, &sa_ign, NULL);
-
-    tty_raw_mode();
-
-    print_prompt();
-    while (1) {
-        int len = read_line_edit();
-        if (len < 0) break;
-        if (len > 0) {
-            hist_add(line);
-            tty_restore();
-            process_line(line);
-            tty_raw_mode();
-        }
-        print_prompt();
-    }
-
-    tty_restore();
-    return last_status;
-}
diff --git a/user/sleep.c b/user/sleep.c
deleted file mode 100644 (file)
index 9da1be1..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-/* AdrOS sleep utility — pause for N seconds */
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <time.h>
-
-int main(int argc, char** argv) {
-    if (argc <= 1) {
-        fprintf(stderr, "usage: sleep SECONDS\n");
-        return 1;
-    }
-    int secs = atoi(argv[1]);
-    if (secs > 0) {
-        struct timespec ts = { .tv_sec = secs, .tv_nsec = 0 };
-        nanosleep(&ts, NULL);
-    }
-    return 0;
-}
diff --git a/user/sort.c b/user/sort.c
deleted file mode 100644 (file)
index 70cc61d..0000000
+++ /dev/null
@@ -1,91 +0,0 @@
-/* AdrOS sort utility */
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-#include <fcntl.h>
-
-#define MAX_LINES 1024
-#define LINE_BUF  65536
-
-static char linebuf[LINE_BUF];
-static char* lines[MAX_LINES];
-static int nlines = 0;
-
-static int rflag = 0;  /* -r: reverse */
-static int nflag = 0;  /* -n: numeric */
-
-static int cmp(const void* a, const void* b) {
-    const char* sa = *(const char**)a;
-    const char* sb = *(const char**)b;
-    int r;
-    if (nflag) {
-        r = atoi(sa) - atoi(sb);
-    } else {
-        r = strcmp(sa, sb);
-    }
-    return rflag ? -r : r;
-}
-
-static void read_lines(int fd) {
-    int total = 0;
-    int r;
-    while ((r = read(fd, linebuf + total, (size_t)(LINE_BUF - total - 1))) > 0) {
-        total += r;
-        if (total >= LINE_BUF - 1) break;
-    }
-    linebuf[total] = '\0';
-
-    /* Split into lines */
-    char* p = linebuf;
-    while (*p && nlines < MAX_LINES) {
-        lines[nlines++] = p;
-        while (*p && *p != '\n') p++;
-        if (*p == '\n') *p++ = '\0';
-    }
-}
-
-int main(int argc, char** argv) {
-    int start = 1;
-    for (int i = 1; i < argc; i++) {
-        if (argv[i][0] == '-') {
-            const char* f = argv[i] + 1;
-            while (*f) {
-                if (*f == 'r') rflag = 1;
-                else if (*f == 'n') nflag = 1;
-                f++;
-            }
-            start = i + 1;
-        } else break;
-    }
-
-    if (start >= argc) {
-        read_lines(STDIN_FILENO);
-    } else {
-        for (int i = start; i < argc; i++) {
-            int fd = open(argv[i], O_RDONLY);
-            if (fd < 0) {
-                fprintf(stderr, "sort: cannot open '%s'\n", argv[i]);
-                return 1;
-            }
-            read_lines(fd);
-            close(fd);
-        }
-    }
-
-    /* Simple insertion sort (no qsort in ulibc yet) */
-    for (int i = 1; i < nlines; i++) {
-        char* key = lines[i];
-        int j = i - 1;
-        while (j >= 0 && cmp(&lines[j], &key) > 0) {
-            lines[j + 1] = lines[j];
-            j--;
-        }
-        lines[j + 1] = key;
-    }
-
-    for (int i = 0; i < nlines; i++)
-        printf("%s\n", lines[i]);
-
-    return 0;
-}
diff --git a/user/stat.c b/user/stat.c
deleted file mode 100644 (file)
index 7083d29..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-/* AdrOS stat utility — display file status */
-#include <stdio.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <sys/stat.h>
-
-int main(int argc, char** argv) {
-    if (argc <= 1) {
-        fprintf(stderr, "usage: stat FILE...\n");
-        return 1;
-    }
-    int rc = 0;
-    for (int i = 1; i < argc; i++) {
-        struct stat st;
-        if (stat(argv[i], (void*)&st) < 0) {
-            fprintf(stderr, "stat: cannot stat '%s'\n", argv[i]);
-            rc = 1;
-            continue;
-        }
-        printf("  File: %s\n", argv[i]);
-        printf("  Size: %u\tInode: %u\n", (unsigned)st.st_size, (unsigned)st.st_ino);
-        printf("  Mode: %o\tUid: %u\tGid: %u\n", (unsigned)st.st_mode, (unsigned)st.st_uid, (unsigned)st.st_gid);
-    }
-    return rc;
-}
diff --git a/user/tail.c b/user/tail.c
deleted file mode 100644 (file)
index 200e78e..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-/* AdrOS tail utility */
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <string.h>
-
-#define TAIL_BUFSZ 8192
-
-static void tail_fd(int fd, int nlines) {
-    /* Read entire file into buffer, then print last N lines */
-    char buf[TAIL_BUFSZ];
-    int total = 0;
-    int r;
-    while ((r = read(fd, buf + total, (size_t)(TAIL_BUFSZ - total))) > 0) {
-        total += r;
-        if (total >= TAIL_BUFSZ) break;
-    }
-
-    /* Count newlines from end; skip trailing newline */
-    int count = 0;
-    int pos = total;
-    if (pos > 0 && buf[pos - 1] == '\n') pos--;
-    while (pos > 0 && count < nlines) {
-        pos--;
-        if (buf[pos] == '\n') count++;
-    }
-    if (pos > 0 || (pos == 0 && buf[0] == '\n')) pos++;
-
-    write(STDOUT_FILENO, buf + pos, (size_t)(total - pos));
-}
-
-int main(int argc, char** argv) {
-    int nlines = 10;
-    int start = 1;
-
-    if (argc > 1 && argv[1][0] == '-' && argv[1][1] == 'n' && argc > 2) {
-        nlines = atoi(argv[2]);
-        start = 3;
-    } else if (argc > 1 && argv[1][0] == '-' && argv[1][1] >= '0' && argv[1][1] <= '9') {
-        nlines = atoi(argv[1] + 1);
-        start = 2;
-    }
-
-    if (start >= argc) {
-        tail_fd(STDIN_FILENO, nlines);
-    } else {
-        for (int i = start; i < argc; i++) {
-            if (argc - start > 1) printf("==> %s <==\n", argv[i]);
-            int fd = open(argv[i], O_RDONLY);
-            if (fd < 0) {
-                fprintf(stderr, "tail: cannot open '%s'\n", argv[i]);
-                continue;
-            }
-            tail_fd(fd, nlines);
-            close(fd);
-        }
-    }
-    return 0;
-}
diff --git a/user/tee.c b/user/tee.c
deleted file mode 100644 (file)
index 52a32c4..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-/* AdrOS tee utility — read stdin, write to stdout and files */
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-#include <fcntl.h>
-
-int main(int argc, char** argv) {
-    int aflag = 0;
-    int fds[16];
-    int nfds = 0;
-
-    for (int i = 1; i < argc && nfds < 16; i++) {
-        if (strcmp(argv[i], "-a") == 0) { aflag = 1; continue; }
-        int flags = O_WRONLY | O_CREAT;
-        flags |= aflag ? O_APPEND : O_TRUNC;
-        int fd = open(argv[i], flags, 0644);
-        if (fd < 0) {
-            fprintf(stderr, "tee: %s: cannot open\n", argv[i]);
-            continue;
-        }
-        fds[nfds++] = fd;
-    }
-
-    char buf[4096];
-    int n;
-    while ((n = read(STDIN_FILENO, buf, sizeof(buf))) > 0) {
-        write(STDOUT_FILENO, buf, (size_t)n);
-        for (int i = 0; i < nfds; i++)
-            write(fds[i], buf, (size_t)n);
-    }
-
-    for (int i = 0; i < nfds; i++) close(fds[i]);
-    return 0;
-}
diff --git a/user/top.c b/user/top.c
deleted file mode 100644 (file)
index 59995e8..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-/* AdrOS top utility — one-shot process listing with basic info */
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <dirent.h>
-
-static int is_digit(char c) { return c >= '0' && c <= '9'; }
-
-int main(void) {
-    printf("  PID  STATE CMD\n");
-    int fd = open("/proc", O_RDONLY);
-    if (fd < 0) {
-        fprintf(stderr, "top: cannot open /proc\n");
-        return 1;
-    }
-    char buf[512];
-    int rc;
-    while ((rc = getdents(fd, buf, sizeof(buf))) > 0) {
-        int off = 0;
-        while (off < rc) {
-            struct dirent* d = (struct dirent*)(buf + off);
-            if (d->d_reclen == 0) break;
-            if (is_digit(d->d_name[0])) {
-                char path[64];
-
-                /* Read cmdline */
-                snprintf(path, sizeof(path), "/proc/%s/cmdline", d->d_name);
-                int cfd = open(path, O_RDONLY);
-                char cmd[64] = "[kernel]";
-                if (cfd >= 0) {
-                    int n = read(cfd, cmd, sizeof(cmd) - 1);
-                    if (n > 0) {
-                        cmd[n] = '\0';
-                        while (n > 0 && (cmd[n-1] == '\n' || cmd[n-1] == '\0')) cmd[--n] = '\0';
-                    }
-                    if (n <= 0) strcpy(cmd, "[kernel]");
-                    close(cfd);
-                }
-
-                /* Read status for state */
-                snprintf(path, sizeof(path), "/proc/%s/status", d->d_name);
-                int sfd = open(path, O_RDONLY);
-                char state[16] = "?";
-                if (sfd >= 0) {
-                    char sbuf[256];
-                    int sn = read(sfd, sbuf, sizeof(sbuf) - 1);
-                    if (sn > 0) {
-                        sbuf[sn] = '\0';
-                        char* st = strstr(sbuf, "State:");
-                        if (st) {
-                            st += 6;
-                            while (*st == ' ' || *st == '\t') st++;
-                            int si = 0;
-                            while (*st && *st != '\n' && si < 15) state[si++] = *st++;
-                            state[si] = '\0';
-                        }
-                    }
-                    close(sfd);
-                }
-
-                printf("%5s %6s %s\n", d->d_name, state, cmd);
-            }
-            off += d->d_reclen;
-        }
-    }
-    close(fd);
-    return 0;
-}
diff --git a/user/touch.c b/user/touch.c
deleted file mode 100644 (file)
index 3e2da84..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-/* AdrOS touch utility */
-#include <stdio.h>
-#include <unistd.h>
-#include <fcntl.h>
-
-int main(int argc, char** argv) {
-    if (argc < 2) {
-        fprintf(stderr, "Usage: touch <file>...\n");
-        return 1;
-    }
-
-    int rc = 0;
-    for (int i = 1; i < argc; i++) {
-        int fd = open(argv[i], O_WRONLY | O_CREAT, 0644);
-        if (fd < 0) {
-            fprintf(stderr, "touch: cannot touch '%s'\n", argv[i]);
-            rc = 1;
-        } else {
-            close(fd);
-        }
-    }
-    return rc;
-}
diff --git a/user/tr.c b/user/tr.c
deleted file mode 100644 (file)
index cf7606f..0000000
--- a/user/tr.c
+++ /dev/null
@@ -1,44 +0,0 @@
-/* AdrOS tr utility — translate or delete characters */
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-
-int main(int argc, char** argv) {
-    int delete_mode = 0;
-    int start = 1;
-
-    if (argc > 1 && strcmp(argv[1], "-d") == 0) {
-        delete_mode = 1;
-        start = 2;
-    }
-
-    if (delete_mode) {
-        if (start >= argc) { fprintf(stderr, "usage: tr -d SET1\n"); return 1; }
-        const char* set1 = argv[start];
-        char c;
-        while (read(STDIN_FILENO, &c, 1) > 0) {
-            if (!strchr(set1, c))
-                write(STDOUT_FILENO, &c, 1);
-        }
-    } else {
-        if (start + 1 >= argc) { fprintf(stderr, "usage: tr SET1 SET2\n"); return 1; }
-        const char* set1 = argv[start];
-        const char* set2 = argv[start + 1];
-        int len1 = (int)strlen(set1);
-        int len2 = (int)strlen(set2);
-        char c;
-        while (read(STDIN_FILENO, &c, 1) > 0) {
-            int found = 0;
-            for (int i = 0; i < len1; i++) {
-                if (c == set1[i]) {
-                    char r = (i < len2) ? set2[i] : set2[len2 - 1];
-                    write(STDOUT_FILENO, &r, 1);
-                    found = 1;
-                    break;
-                }
-            }
-            if (!found) write(STDOUT_FILENO, &c, 1);
-        }
-    }
-    return 0;
-}
index 844ea414bc61d97768557d15343eac445dcf760b..9a86fe52b23efe94899096e272d53a83b8d9e33b 100644 (file)
@@ -4,8 +4,8 @@ AS  ?= i686-elf-as
 AR  ?= i686-elf-ar
 LD  ?= i686-elf-ld
 
-CFLAGS := -m32 -ffreestanding -fno-pie -no-pie -nostdlib -O2 -Wall -Wextra -Iinclude
-CFLAGS_PIC := -m32 -ffreestanding -nostdlib -O2 -Wall -Wextra -Iinclude -fPIC -fno-plt
+CFLAGS := -m32 -ffreestanding -fno-pie -no-pie -nostdlib -O2 -Wall -Wextra -Wno-incompatible-pointer-types -Iinclude
+CFLAGS_PIC := -m32 -ffreestanding -nostdlib -O2 -Wall -Wextra -Wno-incompatible-pointer-types -Iinclude -fPIC -fno-plt
 ASFLAGS := --32
 
 SRC_C := $(wildcard src/*.c)
index b83507a25e1dc83d810ef4c34289d21e1942c4bd..37fa3dfd910b132cadc1cd7ebc31f87b5b2eac38 100644 (file)
@@ -2,6 +2,7 @@
 #include "fnmatch.h"
 #include "string.h"
 #include "stdlib.h"
+#include "stdio.h"
 #include "unistd.h"
 #include "dirent.h"
 #include "errno.h"
diff --git a/user/umount.c b/user/umount.c
deleted file mode 100644 (file)
index 1396c94..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-/* AdrOS umount utility — stub (no SYS_UMOUNT syscall yet) */
-#include <stdio.h>
-
-int main(int argc, char** argv) {
-    if (argc <= 1) {
-        fprintf(stderr, "umount: missing operand\n");
-        return 1;
-    }
-    fprintf(stderr, "umount: %s: operation not supported\n", argv[1]);
-    return 1;
-}
diff --git a/user/uname.c b/user/uname.c
deleted file mode 100644 (file)
index 250fe05..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-/* AdrOS uname utility — print system information */
-#include <stdio.h>
-#include <string.h>
-
-int main(int argc, char** argv) {
-    const char* sysname = "AdrOS";
-    const char* nodename = "adros";
-    const char* release = "0.1.0";
-    const char* version = "AdrOS x86 SMP";
-    const char* machine = "i686";
-
-    if (argc <= 1) { printf("%s\n", sysname); return 0; }
-
-    int all = 0, s = 0, n = 0, r = 0, v = 0, m = 0;
-    for (int i = 1; i < argc; i++) {
-        if (strcmp(argv[i], "-a") == 0) all = 1;
-        else if (strcmp(argv[i], "-s") == 0) s = 1;
-        else if (strcmp(argv[i], "-n") == 0) n = 1;
-        else if (strcmp(argv[i], "-r") == 0) r = 1;
-        else if (strcmp(argv[i], "-v") == 0) v = 1;
-        else if (strcmp(argv[i], "-m") == 0) m = 1;
-    }
-    if (all) { s = n = r = v = m = 1; }
-    if (!s && !n && !r && !v && !m) s = 1;
-
-    int first = 1;
-    if (s) { printf("%s%s", first ? "" : " ", sysname); first = 0; }
-    if (n) { printf("%s%s", first ? "" : " ", nodename); first = 0; }
-    if (r) { printf("%s%s", first ? "" : " ", release); first = 0; }
-    if (v) { printf("%s%s", first ? "" : " ", version); first = 0; }
-    if (m) { printf("%s%s", first ? "" : " ", machine); first = 0; }
-    printf("\n");
-    return 0;
-}
diff --git a/user/uniq.c b/user/uniq.c
deleted file mode 100644 (file)
index 60fc2f1..0000000
+++ /dev/null
@@ -1,85 +0,0 @@
-/* AdrOS uniq utility */
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-#include <fcntl.h>
-
-#define LINE_MAX 1024
-
-static int cflag = 0;  /* -c: prefix lines with count */
-static int dflag = 0;  /* -d: only print duplicates */
-
-static int readline(int fd, char* buf, int max) {
-    int n = 0;
-    char c;
-    while (n < max - 1) {
-        int r = read(fd, &c, 1);
-        if (r <= 0) break;
-        if (c == '\n') break;
-        buf[n++] = c;
-    }
-    buf[n] = '\0';
-    return n > 0 ? n : (n == 0 ? 0 : -1);
-}
-
-int main(int argc, char** argv) {
-    int start = 1;
-    int fd = STDIN_FILENO;
-
-    for (int i = 1; i < argc; i++) {
-        if (argv[i][0] == '-' && argv[i][1]) {
-            const char* f = argv[i] + 1;
-            while (*f) {
-                if (*f == 'c') cflag = 1;
-                else if (*f == 'd') dflag = 1;
-                f++;
-            }
-            start = i + 1;
-        } else break;
-    }
-
-    if (start < argc) {
-        fd = open(argv[start], O_RDONLY);
-        if (fd < 0) {
-            fprintf(stderr, "uniq: cannot open '%s'\n", argv[start]);
-            return 1;
-        }
-    }
-
-    char prev[LINE_MAX] = {0};
-    char cur[LINE_MAX];
-    int count = 0;
-    int first = 1;
-
-    while (1) {
-        int r = readline(fd, cur, LINE_MAX);
-        if (r < 0) break;
-
-        if (first || strcmp(cur, prev) != 0) {
-            if (!first) {
-                if (!dflag || count > 1) {
-                    if (cflag) printf("%7d %s\n", count, prev);
-                    else printf("%s\n", prev);
-                }
-            }
-            strcpy(prev, cur);
-            count = 1;
-            first = 0;
-        } else {
-            count++;
-        }
-
-        if (r == 0) break;
-    }
-
-    /* Print last line */
-    if (!first) {
-        if (!dflag || count > 1) {
-            if (cflag) printf("%7d %s\n", count, prev);
-            else printf("%s\n", prev);
-        }
-    }
-
-    if (fd != STDIN_FILENO) close(fd);
-    return 0;
-}
diff --git a/user/uptime.c b/user/uptime.c
deleted file mode 100644 (file)
index 2f81539..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-/* AdrOS uptime utility */
-#include <stdio.h>
-#include <time.h>
-#include <unistd.h>
-#include <fcntl.h>
-
-int main(int argc, char** argv) {
-    (void)argc; (void)argv;
-
-    /* Try /proc/uptime first */
-    int fd = open("/proc/uptime", O_RDONLY);
-    if (fd >= 0) {
-        char buf[64];
-        int r = read(fd, buf, sizeof(buf) - 1);
-        close(fd);
-        if (r > 0) {
-            buf[r] = '\0';
-            printf("up %s", buf);
-            return 0;
-        }
-    }
-
-    /* Fallback: use CLOCK_MONOTONIC */
-    struct timespec ts;
-    if (clock_gettime(CLOCK_MONOTONIC, &ts) < 0) {
-        fprintf(stderr, "uptime: cannot get time\n");
-        return 1;
-    }
-
-    unsigned long sec = ts.tv_sec;
-    unsigned long days = sec / 86400;
-    unsigned long hours = (sec % 86400) / 3600;
-    unsigned long mins = (sec % 3600) / 60;
-    unsigned long secs = sec % 60;
-
-    printf("up");
-    if (days > 0) printf(" %lu day%s,", days, days > 1 ? "s" : "");
-    printf(" %02lu:%02lu:%02lu\n", hours, mins, secs);
-    return 0;
-}
diff --git a/user/user_errno.h b/user/user_errno.h
deleted file mode 100644 (file)
index 6681cc9..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-#ifndef USER_ERRNO_H
-#define USER_ERRNO_H
-
-extern int errno;
-
-static inline int __syscall_fix(int ret) {
-    if (ret < 0) {
-        errno = -ret;
-        return -1;
-    }
-    return ret;
-}
-
-#endif
diff --git a/user/wc.c b/user/wc.c
deleted file mode 100644 (file)
index 76333eb..0000000
--- a/user/wc.c
+++ /dev/null
@@ -1,69 +0,0 @@
-/* AdrOS wc utility */
-#include <stdio.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <string.h>
-
-static void wc_fd(int fd, const char* name, int show_l, int show_w, int show_c) {
-    char buf[4096];
-    int lines = 0, words = 0, chars = 0;
-    int in_word = 0;
-    int r;
-
-    while ((r = read(fd, buf, sizeof(buf))) > 0) {
-        for (int i = 0; i < r; i++) {
-            chars++;
-            if (buf[i] == '\n') lines++;
-            if (buf[i] == ' ' || buf[i] == '\t' || buf[i] == '\n') {
-                in_word = 0;
-            } else if (!in_word) {
-                in_word = 1;
-                words++;
-            }
-        }
-    }
-
-    if (show_l) printf("%7d", lines);
-    if (show_w) printf("%7d", words);
-    if (show_c) printf("%7d", chars);
-    if (name) printf(" %s", name);
-    printf("\n");
-}
-
-int main(int argc, char** argv) {
-    int show_l = 0, show_w = 0, show_c = 0;
-    int start = 1;
-
-    for (int i = 1; i < argc; i++) {
-        if (argv[i][0] == '-' && argv[i][1]) {
-            const char* f = argv[i] + 1;
-            while (*f) {
-                if (*f == 'l') show_l = 1;
-                else if (*f == 'w') show_w = 1;
-                else if (*f == 'c') show_c = 1;
-                f++;
-            }
-            start = i + 1;
-        } else break;
-    }
-
-    /* Default: show all */
-    if (!show_l && !show_w && !show_c) {
-        show_l = show_w = show_c = 1;
-    }
-
-    if (start >= argc) {
-        wc_fd(STDIN_FILENO, NULL, show_l, show_w, show_c);
-    } else {
-        for (int i = start; i < argc; i++) {
-            int fd = open(argv[i], O_RDONLY);
-            if (fd < 0) {
-                fprintf(stderr, "wc: %s: No such file\n", argv[i]);
-                continue;
-            }
-            wc_fd(fd, argv[i], show_l, show_w, show_c);
-            close(fd);
-        }
-    }
-    return 0;
-}
diff --git a/user/which.c b/user/which.c
deleted file mode 100644 (file)
index 9fa5c20..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-/* AdrOS which utility — locate a command */
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <dirent.h>
-
-static int exists_in_dir(const char* dir, const char* name) {
-    int fd = open(dir, O_RDONLY);
-    if (fd < 0) return 0;
-    char buf[2048];
-    int rc;
-    while ((rc = getdents(fd, buf, sizeof(buf))) > 0) {
-        int off = 0;
-        while (off < rc) {
-            struct dirent* d = (struct dirent*)(buf + off);
-            if (d->d_reclen == 0) break;
-            if (strcmp(d->d_name, name) == 0) {
-                close(fd);
-                return 1;
-            }
-            off += d->d_reclen;
-        }
-    }
-    close(fd);
-    return 0;
-}
-
-int main(int argc, char** argv) {
-    if (argc < 2) {
-        fprintf(stderr, "Usage: which command\n");
-        return 1;
-    }
-
-    static const char* path_dirs[] = { "/bin", "/sbin", NULL };
-    int ret = 1;
-
-    for (int i = 1; i < argc; i++) {
-        int found = 0;
-        for (int d = 0; path_dirs[d]; d++) {
-            if (exists_in_dir(path_dirs[d], argv[i])) {
-                printf("%s/%s\n", path_dirs[d], argv[i]);
-                found = 1;
-                break;
-            }
-        }
-        if (found) ret = 0;
-    }
-    return ret;
-}
diff --git a/user/who.c b/user/who.c
deleted file mode 100644 (file)
index b8fc948..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-/* AdrOS who utility — show logged-in users */
-#include <stdio.h>
-#include <unistd.h>
-
-int main(void) {
-    printf("root     tty1         Jan  1 00:00\n");
-    return 0;
-}