]> Projects (at) Tadryanom (dot) Me - AdrOS.git/commitdiff
userspace: major refactoring — dynamic linking, new commands, SysV init
authorTulio A M Mendes <[email protected]>
Tue, 17 Feb 2026 02:22:03 +0000 (23:22 -0300)
committerTulio A M Mendes <[email protected]>
Tue, 17 Feb 2026 02:22:03 +0000 (23:22 -0300)
Infrastructure:
- Rename user/init.c → user/fulltest.c, move to /sbin/fulltest
- Remove .elf extensions from all InitRD binaries
- Build ulibc as shared library (libc.so) with PIC objects
- Fix crt0.S to correctly parse argc/argv/envp from execve stack
- Fix ld.so: restore stack pointer before jumping to program entry
- Fix ld.so: add R_386_GLOB_DAT/R_386_COPY eager relocation processing
- Fix ld.so: move find_shlib_info() before relocation processing
- Create dyn_linker.ld for dynamically-linked PIE executables
- Add missing ulibc functions: waitpid, getdents, stat, fstat, chmod,
  chown, link, symlink, readlink, sigaction
- Add dirent.h header, expand sys/stat.h with permission macros
- Update Makefile: dynamic linking build rules for all user commands

Rewritten commands (now use ulibc + dynamic linking via ld.so):
- cat: proper POSIX with stdin/- support, 4KB buffer
- echo: -n, -e, -E flags, escape sequence handling
- ls: -a, -l flags, getdents-based directory listing
- mkdir: -p flag for recursive parent creation
- rm: -r, -f, -d flags

New commands (all dynamically linked):
- File management: cp, mv, touch, ln (-s for symlinks)
- Text processing: head (-n), tail (-n), wc (-l/-w/-c),
  sort (-r/-n), uniq (-c/-d), cut (-d/-f)
- Permissions: chmod (octal), chown (owner:group), chgrp
- System info: date, hostname, uptime

Shell improvements (/bin/sh):
- Variable assignment (VAR=value) and expansion ($VAR, ${VAR}, $?)
- Environment variables (export VAR=value)
- Line editing: left/right arrows, Ctrl+A/E/U, backspace
- Command history: up/down arrows, dedup
- Builtins: cd, pwd, export, unset, set, type, exit, echo
- PATH-based command resolution with colon-separated dirs
- Quote handling (single and double quotes)
- Semicolon command separation
- Append redirection (>>)

SysV init (/sbin/init):
- Parses /etc/inittab (id:runlevels:action:process format)
- Actions: sysinit, wait, once, respawn, ctrlaltdel, shutdown
- Runlevel support (0-6, S)
- Default behavior without inittab: run rcS then respawn /bin/sh
- Child reaping and respawn loop

Kernel changes:
- Default init path changed to /sbin/init (was /bin/init.elf)
- grub.cfg: add fulltest and shell entries with console=serial
- Update fulltest.c references: /bin/init.elf → /sbin/fulltest,
  /bin/echo.elf → /bin/echo, /bin/pie_test.elf → /bin/pie_test

Tests: 89/89 smoke (9s), cppcheck clean

38 files changed:
Makefile
iso/boot/grub/grub.cfg
src/kernel/cmdline.c
tests/smoke_test.exp
user/cat.c
user/chgrp.c [new file with mode: 0644]
user/chmod.c [new file with mode: 0644]
user/chown.c [new file with mode: 0644]
user/cp.c [new file with mode: 0644]
user/cut.c [new file with mode: 0644]
user/date.c [new file with mode: 0644]
user/dyn_linker.ld [new file with mode: 0644]
user/echo.c
user/fulltest.c [new file with mode: 0644]
user/head.c [new file with mode: 0644]
user/hostname.c [new file with mode: 0644]
user/init.c
user/ldso.c
user/ln.c [new file with mode: 0644]
user/ls.c
user/mkdir.c
user/mv.c [new file with mode: 0644]
user/rm.c
user/sh.c
user/sort.c [new file with mode: 0644]
user/tail.c [new file with mode: 0644]
user/touch.c [new file with mode: 0644]
user/ulibc/Makefile
user/ulibc/include/dirent.h [new file with mode: 0644]
user/ulibc/include/sys/stat.h
user/ulibc/include/unistd.h
user/ulibc/src/crt0.S
user/ulibc/src/signal.c
user/ulibc/src/stdlib.c
user/ulibc/src/unistd.c
user/uniq.c [new file with mode: 0644]
user/uptime.c [new file with mode: 0644]
user/wc.c [new file with mode: 0644]

index aad90943f509bc1b98413773ac40f3cc816ff2ef..9e264f41228fba815b6927765264c2193cd9f094 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -73,14 +73,32 @@ ifeq ($(ARCH),x86)
     ASM_SOURCES := $(wildcard $(SRC_DIR)/arch/x86/*.S)
     C_SOURCES += $(wildcard $(SRC_DIR)/arch/x86/*.c)
 
-    USER_ELF := user/init.elf
+    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
+    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
     DOOM_ELF := user/doom/doom.elf
@@ -168,26 +186,108 @@ ULIBC_LIB := $(ULIBC_DIR)/libulibc.a
 $(ULIBC_LIB):
        @$(MAKE) -C $(ULIBC_DIR) --no-print-directory
 
-$(USER_ELF): user/init.c user/linker.ld
-       @i686-elf-gcc -m32 -I include -ffreestanding -fno-pie -no-pie -nostdlib -Wl,-T,user/linker.ld -o $(USER_ELF) user/init.c user/errno.c
+$(ULIBC_SO):
+       @$(MAKE) -C $(ULIBC_DIR) libc.so --no-print-directory
 
-$(ECHO_ELF): user/echo.c user/linker.ld
-       @i686-elf-gcc -m32 -I include -ffreestanding -fno-pie -no-pie -nostdlib -Wl,-T,user/linker.ld -o $(ECHO_ELF) user/echo.c user/errno.c
+$(FULLTEST_ELF): user/fulltest.c user/linker.ld
+       @i686-elf-gcc -m32 -I include -ffreestanding -fno-pie -no-pie -nostdlib -Wl,-T,user/linker.ld -o $(FULLTEST_ELF) user/fulltest.c user/errno.c
 
-$(SH_ELF): user/sh.c user/linker.ld
-       @i686-elf-gcc -m32 -I include -ffreestanding -fno-pie -no-pie -nostdlib -Wl,-T,user/linker.ld -o $(SH_ELF) user/sh.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 := 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 user/dyn_linker.ld -L$(ULIBC_DIR) -rpath /lib
 
-$(CAT_ELF): user/cat.c user/linker.ld
-       @i686-elf-gcc -m32 -I include -ffreestanding -fno-pie -no-pie -nostdlib -Wl,-T,user/linker.ld -o $(CAT_ELF) user/cat.c user/errno.c
+$(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
 
-$(LS_ELF): user/ls.c user/linker.ld
-       @i686-elf-gcc -m32 -I include -ffreestanding -fno-pie -no-pie -nostdlib -Wl,-T,user/linker.ld -o $(LS_ELF) user/ls.c user/errno.c
+$(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
 
-$(MKDIR_ELF): user/mkdir.c user/linker.ld
-       @i686-elf-gcc -m32 -I include -ffreestanding -fno-pie -no-pie -nostdlib -Wl,-T,user/linker.ld -o $(MKDIR_ELF) user/mkdir.c user/errno.c
+$(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
 
-$(RM_ELF): user/rm.c user/linker.ld
-       @i686-elf-gcc -m32 -I include -ffreestanding -fno-pie -no-pie -nostdlib -Wl,-T,user/linker.ld -o $(RM_ELF) user/rm.c user/errno.c
+$(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
 
 $(LDSO_ELF): user/ldso.c user/ldso_linker.ld
        @i686-elf-gcc -m32 -ffreestanding -fno-pie -no-pie -nostdlib -Wl,-T,user/ldso_linker.ld -o $(LDSO_ELF) user/ldso.c
@@ -200,13 +300,32 @@ $(PIE_ELF): user/pie_main.c user/pie_linker.ld $(PIE_SO)
        @i686-elf-gcc -m32 -fPIC -c user/pie_main.c -o user/pie_main.o
        @i686-elf-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) \
+             $(INIT_ELF)
+
 FSTAB := rootfs/etc/fstab
-INITRD_FILES := $(USER_ELF):bin/init.elf $(ECHO_ELF):bin/echo.elf $(SH_ELF):bin/sh $(CAT_ELF):bin/cat $(LS_ELF):bin/ls $(MKDIR_ELF):bin/mkdir $(RM_ELF):bin/rm $(LDSO_ELF):lib/ld.so $(PIE_SO):lib/libpietest.so $(PIE_ELF):bin/pie_test.elf $(FSTAB):etc/fstab
-INITRD_DEPS := $(MKINITRD) $(USER_ELF) $(ECHO_ELF) $(SH_ELF) $(CAT_ELF) $(LS_ELF) $(MKDIR_ELF) $(RM_ELF) $(LDSO_ELF) $(PIE_SO) $(PIE_ELF) $(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 \
+    $(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)
 
 # Include doom.elf if it has been built
 ifneq ($(wildcard $(DOOM_ELF)),)
-INITRD_FILES += $(DOOM_ELF):bin/doom.elf
+INITRD_FILES += $(DOOM_ELF):bin/doom
 INITRD_DEPS += $(DOOM_ELF)
 endif
 
@@ -327,10 +446,10 @@ scan-build:
        @command -v scan-build >/dev/null
        @scan-build --status-bugs $(MAKE) ARCH=$(ARCH) $(if $(CROSS),CROSS=$(CROSS),) all
 
-mkinitrd-asan: $(USER_ELF)
+mkinitrd-asan: $(FULLTEST_ELF)
        @mkdir -p build/host
        @gcc -g -O1 -fno-omit-frame-pointer -fsanitize=address,undefined tools/mkinitrd.c -o build/host/mkinitrd-asan
-       @./build/host/mkinitrd-asan build/host/$(INITRD_IMG).asan $(USER_ELF):bin/init.elf
+       @./build/host/mkinitrd-asan build/host/$(INITRD_IMG).asan $(FULLTEST_ELF):sbin/fulltest
 
 $(BUILD_DIR)/%.o: $(SRC_DIR)/%.c
        @mkdir -p $(dir $@)
index de40d366552b382b6b403e91ab8e9f48b648b264..7a0b19bb6cf5e7b65a45cec58b5034137babcee1 100644 (file)
@@ -11,14 +11,20 @@ menuentry "AdrOS (x86)" {
   boot
 }
 
-menuentry "AdrOS (x86) - ring3 test" {
-  multiboot2 /boot/adros-x86.bin ring3
+menuentry "AdrOS (x86) - fulltest" {
+  multiboot2 /boot/adros-x86.bin init=/sbin/fulltest console=serial
   module2 /boot/initrd.img
   boot
 }
 
 menuentry "AdrOS (x86) - shell" {
-  multiboot2 /boot/adros-x86.bin init=/bin/sh
+  multiboot2 /boot/adros-x86.bin init=/bin/sh console=serial
+  module2 /boot/initrd.img
+  boot
+}
+
+menuentry "AdrOS (x86) - ring3 test" {
+  multiboot2 /boot/adros-x86.bin ring3 console=serial
   module2 /boot/initrd.img
   boot
 }
index 0677b04069bb054b3dc0dc7c5317ed84d627e791..f9e3faed0dbace1cdcdc1762d8732625d73aefbe 100644 (file)
@@ -76,7 +76,7 @@ void cmdline_parse(const char* raw) {
     kflag_count = 0;
     init_argc = 0;
     init_envc = 0;
-    init_path_val = "/bin/init.elf";
+    init_path_val = "/sbin/init";
 
     if (!raw) {
         raw_copy[0] = '\0';
index cd00d524695f5520b1d500f87ff9ee96758a2d85..2248a23a87c653782835eb23ad444ad3b28a9d5e 100755 (executable)
@@ -125,7 +125,7 @@ set tests {
     {"lazy PLT"             "\\[init\\] lazy PLT OK"}
     {"PLT cached"           "\\[init\\] PLT cached OK"}
     {"PING network"         "\\[PING\\] .*received.*network OK"}
-    {"echo.elf execve"      "\\[echo\\] hello from echo.elf"}
+    {"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"}
index d2349515a45ca12c11aa35b111562e95fd3f02e8..ad166e2ba8d32b87eb5613d8f289072fafdc751f 100644 (file)
 /* AdrOS cat utility */
-#include <stdint.h>
-#include "user_errno.h"
-
-enum {
-    SYSCALL_WRITE = 1,
-    SYSCALL_EXIT  = 2,
-    SYSCALL_OPEN  = 4,
-    SYSCALL_READ  = 5,
-    SYSCALL_CLOSE = 6,
-};
-
-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_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_open(const char* path, int flags) {
-    int ret;
-    __asm__ volatile("int $0x80" : "=a"(ret)
-        : "a"(SYSCALL_OPEN), "b"(path), "c"(flags) : "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 __attribute__((noreturn)) void sys_exit(int code) {
-    __asm__ volatile("int $0x80" : : "a"(SYSCALL_EXIT), "b"(code) : "memory");
-    for (;;) __asm__ volatile("hlt");
-}
-
-static uint32_t slen(const char* s) {
-    uint32_t n = 0;
-    while (s && s[n]) n++;
-    return n;
-}
-
-static void wr(int fd, const char* s) {
-    (void)sys_write(fd, s, slen(s));
-}
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
 
 static void cat_fd(int fd) {
-    char buf[256];
+    char buf[4096];
     int r;
-    while ((r = sys_read(fd, buf, sizeof(buf))) > 0) {
-        sys_write(1, buf, (uint32_t)r);
+    while ((r = read(fd, buf, sizeof(buf))) > 0) {
+        write(STDOUT_FILENO, buf, (size_t)r);
     }
 }
 
-static void cat_main(uint32_t* sp0) {
-    uint32_t argc = sp0 ? sp0[0] : 0;
-    const char* const* argv = (const char* const*)(sp0 + 1);
-
+int main(int argc, char** argv) {
     if (argc <= 1) {
-        cat_fd(0);
-        sys_exit(0);
+        cat_fd(STDIN_FILENO);
+        return 0;
     }
 
     int rc = 0;
-    for (uint32_t i = 1; i < argc; i++) {
-        int fd = sys_open(argv[i], 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) {
-            wr(2, "cat: ");
-            wr(2, argv[i]);
-            wr(2, ": No such file\n");
+            fprintf(stderr, "cat: %s: No such file or directory\n", argv[i]);
             rc = 1;
             continue;
         }
         cat_fd(fd);
-        sys_close(fd);
+        close(fd);
     }
-    sys_exit(rc);
-}
-
-__attribute__((naked)) void _start(void) {
-    __asm__ volatile(
-        "mov %esp, %eax\n"
-        "push %eax\n"
-        "call cat_main\n"
-        "add $4, %esp\n"
-        "mov $0, %ebx\n"
-        "mov $2, %eax\n"
-        "int $0x80\n"
-        "hlt\n"
-    );
+    return rc;
 }
diff --git a/user/chgrp.c b/user/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/chmod.c b/user/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/chown.c b/user/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/cp.c b/user/cp.c
new file mode 100644 (file)
index 0000000..436322a
--- /dev/null
+++ b/user/cp.c
@@ -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);
+    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
new file mode 100644 (file)
index 0000000..42cc1c2
--- /dev/null
@@ -0,0 +1,104 @@
+/* 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 (strcmp(argv[i], "-f") == 0 && i + 1 < argc) {
+            parse_fields(argv[++i]);
+            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
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/dyn_linker.ld b/user/dyn_linker.ld
new file mode 100644 (file)
index 0000000..e05e090
--- /dev/null
@@ -0,0 +1,35 @@
+/* Linker script for dynamically-linked AdrOS userspace commands.
+ * These executables use /lib/ld.so as interpreter and link against libc.so. */
+ENTRY(_start)
+
+SECTIONS
+{
+    . = 0x00400000;
+
+    .interp : { *(.interp) }
+    .hash   : { *(.hash) }
+    .dynsym : { *(.dynsym) }
+    .dynstr : { *(.dynstr) }
+    .rel.dyn : { *(.rel.dyn) }
+    .rel.plt : { *(.rel.plt) }
+    .plt    : { *(.plt) *(.plt.*) }
+
+    .text : {
+        *(.text .text.*)
+    }
+
+    .rodata : { *(.rodata*) }
+
+    . = ALIGN(0x1000);
+
+    .dynamic : { *(.dynamic) }
+    .got.plt : { *(.got.plt) }
+    .got     : { *(.got) }
+
+    .data : { *(.data*) }
+
+    .bss : {
+        *(.bss*)
+        *(COMMON)
+    }
+}
index a9b19fa3a405dca3adca6c85ad5f3cfb981f37ba..f6a8c89b1b5d8de7d54a36bbca084e9325afccc1 100644 (file)
-#include <stdint.h>
-
-#include "user_errno.h"
-
-enum {
-    SYSCALL_WRITE = 1,
-    SYSCALL_EXIT  = 2,
-};
-
-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 uint32_t ustrlen(const char* s) {
-    uint32_t n = 0;
-    while (s && s[n]) n++;
-    return n;
-}
-
-static void write_str(const char* s) {
-    (void)sys_write(1, s, ustrlen(s));
-}
-
-static void u32_to_dec(uint32_t v, char out[16]) {
-    char tmp[16];
-    uint32_t n = 0;
-    if (v == 0) {
-        out[0] = '0';
-        out[1] = 0;
-        return;
-    }
-    while (v && n < sizeof(tmp) - 1) {
-        tmp[n++] = (char)('0' + (v % 10));
-        v /= 10;
-    }
-    uint32_t i = 0;
-    while (n) out[i++] = tmp[--n];
-    out[i] = 0;
-}
-
-static __attribute__((noreturn)) void sys_exit(int status) {
-    __asm__ volatile(
-        "int $0x80"
-        :
-        : "a"(SYSCALL_EXIT), "b"(status)
-        : "memory"
-    );
-    for (;;) {
-        __asm__ volatile("hlt");
-    }
-}
-
-static void echo_main(uint32_t* sp0) {
-    write_str("[echo] hello from echo.elf\n");
-
-    uint32_t argc = sp0 ? sp0[0] : 0;
-    const char* const* argv = (const char* const*)(sp0 + 1);
-
-    const char* const* envp = argv;
-    for (uint32_t i = 0; argv && argv[i]; i++) {
-        envp = &argv[i + 1];
-    }
-    if (envp) envp++;
-
-    char num[16];
-    u32_to_dec(argc, num);
-    write_str("[echo] argc=");
-    write_str(num);
-    write_str("\n");
-
-    if (argv && argv[0]) {
-        write_str("[echo] argv0=");
-        write_str(argv[0]);
-        write_str("\n");
-    }
-    if (argv && argv[1]) {
-        write_str("[echo] argv1=");
-        write_str(argv[1]);
-        write_str("\n");
-    }
-    if (argv && argv[2]) {
-        write_str("[echo] argv2=");
-        write_str(argv[2]);
-        write_str("\n");
-    }
-    if (envp && envp[0]) {
-        write_str("[echo] env0=");
-        write_str(envp[0]);
-        write_str("\n");
-    }
-    sys_exit(0);
-}
-
-__attribute__((naked)) void _start(void) {
-    __asm__ volatile(
-        "mov %esp, %eax\n"
-        "push %eax\n"
-        "call echo_main\n"
-        "add $4, %esp\n"
-        "mov $0, %ebx\n"
-        "mov $2, %eax\n"
-        "int $0x80\n"
-        "hlt\n"
-    );
+/* 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++;
+    }
+
+    for (; i < argc; i++) {
+        if (i > 1 && (i > 1 || nflag || eflag))
+            write(STDOUT_FILENO, " ", 1);
+
+        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/fulltest.c b/user/fulltest.c
new file mode 100644 (file)
index 0000000..5bb9c44
--- /dev/null
@@ -0,0 +1,4027 @@
+#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,
+};
+
+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 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);
+}
+
+__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));
+        }
+    }
+
+    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);
+        }
+    }
+
+    // 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));
+    }
+
+    (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/head.c b/user/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/hostname.c b/user/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;
+}
index e75b3f2a45de934f897674d69c33c38467d76a23..02e08f458c87dfc7c7776ef878a1a4492797acea 100644 (file)
-#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,
-};
-
-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 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);
-}
-
-__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[] = "/bin/init.elf";
-
-    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("/bin/init.elf", 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("/bin/init.elf", 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("/bin/init.elf", 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("/bin/init.elf", &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("/bin/init.elf", 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("/bin/init.elf", 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);
+/* 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 */
+};
 
-        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);
+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;
+};
 
-        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));
+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;
+}
 
-    // 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));
-    }
+/* Run a process (fork + exec) */
+static int run_process(const char* cmd) {
+    int pid = fork();
+    if (pid < 0) return -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));
-    }
+    if (pid == 0) {
+        /* Child: parse command into argv */
+        char buf[128];
+        strncpy(buf, cmd, sizeof(buf) - 1);
+        buf[sizeof(buf) - 1] = '\0';
 
-    // 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);
+        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';
         }
-        sys_write(1, "[init] waitid OK\n", (uint32_t)(sizeof("[init] waitid OK\n") - 1));
-    }
+        argv[argc] = NULL;
 
-    // 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);
+        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);
         }
-        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));
+        _exit(127);
     }
 
-    // C19: select on regular file (should return immediately readable)
-    {
-        int fd = sys_open("/bin/init.elf", 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));
-    }
+    return pid;
+}
 
-    // C20: poll on regular file
-    {
-        int fd = sys_open("/bin/init.elf", 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));
-    }
+/* 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;
+}
 
-    // 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);
+/* 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 (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");
+        if (do_wait) {
+            run_and_wait(entries[i].process);
         } 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);
+            entries[i].pid = run_process(entries[i].process);
         }
-
-        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));
-    }
+/* 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;
 
-    // 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);
+        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);
             }
         }
-        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));
+/* 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");
     }
 
-    // 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.elf", "spawn", 0};
-        static const char* const sp_envp[] = {0};
-        int r = sys_posix_spawn(&child_pid, "/bin/echo.elf", 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));
+    /* 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); }
         }
-    }
 
-    // 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));
+        if (pid == 0) {
+            const char* argv[] = { "/bin/sh", NULL };
+            execve("/bin/sh", argv, NULL);
+            _exit(127);
         }
-    }
 
-    // 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;
-    }
+        int st;
+        waitpid(pid, &st, 0);
 
-    // 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));
+        /* Shell exited, respawn after a small delay */
+        struct timespec ts = {1, 0};
+        nanosleep(&ts, NULL);
     }
+}
 
-    // E3: fcntl F_GETFD / F_SETFD (FD_CLOEXEC)
-    {
-        int fd = sys_open("/bin/init.elf", 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));
-    }
+int main(int argc, char** argv) {
+    (void)argc; (void)argv;
 
-    // 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);
+    /* 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);
 
-            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);
+    printf("AdrOS init starting (PID %d)\n", getpid());
 
-            // 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);
-            }
-        }
+    /* Try to parse inittab */
+    if (parse_inittab() < 0) {
+        printf("init: no /etc/inittab, using defaults\n");
+        default_init();
+        return 0;  /* unreachable */
     }
 
-    // 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);
-        }
+    printf("init: loaded %d inittab entries, runlevel %d\n",
+           nentries, current_runlevel);
 
-        // Wait for middle process to finish
-        int st = 0;
-        (void)sys_waitpid(mid, &st, 0);
+    /* Phase 1: sysinit entries */
+    run_action(ACT_SYSINIT, 1);
 
-        // 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));
-        }
-    }
+    /* Phase 2: wait entries */
+    run_action(ACT_WAIT, 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;
-    }
+    /* Phase 3: once entries */
+    run_action(ACT_ONCE, 0);
 
-    {
-        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);
-    }
+    /* Phase 4: respawn entries */
+    run_action(ACT_RESPAWN, 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);
-        }
-    }
+    /* Main loop: reap children and respawn */
+    while (1) {
+        int st;
+        int pid = waitpid(-1, &st, 0);
 
-    // PIE lazy PLT/GOT binding test
-    {
-        int pid = sys_fork();
-        if (pid == 0) {
-            static const char* const av[] = {"pie_test.elf", 0};
-            static const char* const ev[] = {0};
-            (void)sys_execve("/bin/pie_test.elf", 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);
+            /* Mark dead child and respawn if needed */
+            for (int i = 0; i < nentries; i++) {
+                if (entries[i].pid == pid) {
+                    entries[i].pid = -1;
+                    break;
+                }
             }
-
-            *(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));
+            check_respawn();
         } 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;
+            /* No children or error — sleep briefly */
+            struct timespec ts = {1, 0};
+            nanosleep(&ts, NULL);
+            check_respawn();
         }
     }
 
-    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));
-    }
-
-    (void)sys_write(1, "[init] execve(/bin/echo.elf)\n",
-                    (uint32_t)(sizeof("[init] execve(/bin/echo.elf)\n") - 1));
-    static const char* const argv[] = {"echo.elf", "arg1", "arg2", 0};
-    static const char* const envp[] = {"FOO=bar", "HELLO=world", 0};
-    (void)sys_execve("/bin/echo.elf", 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);
+    return 0;
 }
index e7b58030f43f50bcf5df07596ebb91c37c4bf34d..e961f513b17d60ab69683f07018f1af9c2204004 100644 (file)
@@ -44,6 +44,9 @@ typedef int            int32_t;
 #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)
@@ -83,6 +86,8 @@ struct link_map {
     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 */
@@ -294,9 +299,14 @@ static void _start_c(uint32_t* initial_sp) {
                     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
@@ -306,18 +316,53 @@ static void _start_c(uint32_t* initial_sp) {
                     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;
             }
         }
     }
 
-    /* Scan for shared library info at SHLIB_BASE */
-    find_shlib_info();
-
-    /* Jump to the real program entry point */
+    /* 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(
-        "jmp *%0\n"
-        :: "r"(at_entry)
+        "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
new file mode 100644 (file)
index 0000000..72f60d4
--- /dev/null
+++ b/user/ln.c
@@ -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;
+}
index 7daca3b556d6cb51026814bdd2cf0db617744fbf..c613d508cee339fc7fa628d672870ccdfaf374c8 100644 (file)
--- a/user/ls.c
+++ b/user/ls.c
 /* AdrOS ls utility */
-#include <stdint.h>
-#include "user_errno.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <sys/stat.h>
 
-enum {
-    SYSCALL_WRITE    = 1,
-    SYSCALL_EXIT     = 2,
-    SYSCALL_OPEN     = 4,
-    SYSCALL_CLOSE    = 6,
-    SYSCALL_GETDENTS = 30,
-};
-
-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_open(const char* path, int flags) {
-    int ret;
-    __asm__ volatile("int $0x80" : "=a"(ret)
-        : "a"(SYSCALL_OPEN), "b"(path), "c"(flags) : "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_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 __attribute__((noreturn)) void sys_exit(int code) {
-    __asm__ volatile("int $0x80" : : "a"(SYSCALL_EXIT), "b"(code) : "memory");
-    for (;;) __asm__ volatile("hlt");
-}
-
-static uint32_t slen(const char* s) {
-    uint32_t n = 0;
-    while (s && s[n]) n++;
-    return n;
-}
-
-static void wr(int fd, const char* s) {
-    (void)sys_write(fd, s, slen(s));
-}
-
-struct dirent {
-    uint32_t d_ino;
-    uint16_t d_reclen;
-    uint8_t  d_type;
-    char     d_name[24];
-};
+static int aflag = 0;   /* -a: show hidden files */
+static int lflag = 0;   /* -l: long format */
 
 static void ls_dir(const char* path) {
-    int fd = sys_open(path, 0);
+    int fd = open(path, O_RDONLY);
     if (fd < 0) {
-        wr(2, "ls: cannot open ");
-        wr(2, path);
-        wr(2, "\n");
+        fprintf(stderr, "ls: cannot access '%s': No such file or directory\n", path);
         return;
     }
 
-    char buf[512];
-    int rc = sys_getdents(fd, buf, sizeof(buf));
-    if (rc > 0) {
-        uint32_t off = 0;
-        while (off + sizeof(struct dirent) <= (uint32_t)rc) {
+    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;
-            /* skip . and .. */
-            if (d->d_name[0] == '.' &&
-                (d->d_name[1] == 0 ||
-                 (d->d_name[1] == '.' && d->d_name[2] == 0))) {
+
+            /* skip hidden files unless -a */
+            if (!aflag && d->d_name[0] == '.') {
                 off += d->d_reclen;
                 continue;
             }
-            wr(1, d->d_name);
-            wr(1, "\n");
+
+            if (lflag) {
+                char type = '-';
+                if (d->d_type == DT_DIR) type = 'd';
+                else if (d->d_type == DT_CHR) type = 'c';
+                else if (d->d_type == DT_LNK) type = 'l';
+                else if (d->d_type == DT_BLK) type = 'b';
+                printf("%c  %s\n", type, d->d_name);
+            } else {
+                printf("%s\n", d->d_name);
+            }
             off += d->d_reclen;
         }
     }
 
-    sys_close(fd);
+    close(fd);
 }
 
-static void ls_main(uint32_t* sp0) {
-    uint32_t argc = sp0 ? sp0[0] : 0;
-    const char* const* argv = (const char* const*)(sp0 + 1);
+int main(int argc, char** argv) {
+    int npath = 0;
+    const char* paths[64];
 
-    if (argc <= 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 == '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 (uint32_t i = 1; i < argc; i++) {
-            if (argc > 2) {
-                wr(1, argv[i]);
-                wr(1, ":\n");
-            }
-            ls_dir(argv[i]);
+        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");
         }
     }
-    sys_exit(0);
-}
 
-__attribute__((naked)) void _start(void) {
-    __asm__ volatile(
-        "mov %esp, %eax\n"
-        "push %eax\n"
-        "call ls_main\n"
-        "add $4, %esp\n"
-        "mov $0, %ebx\n"
-        "mov $2, %eax\n"
-        "int $0x80\n"
-        "hlt\n"
-    );
+    return 0;
 }
index ee171ae7c5af90001c1a2ceb3772f4fa356fdc0e..d5716c5ee67d3cc1f1188f603c73fa7266a6ded4 100644 (file)
@@ -1,72 +1,61 @@
 /* AdrOS mkdir utility */
-#include <stdint.h>
-#include "user_errno.h"
-
-enum {
-    SYSCALL_WRITE = 1,
-    SYSCALL_EXIT  = 2,
-    SYSCALL_MKDIR = 28,
-};
-
-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_mkdir(const char* path) {
-    int ret;
-    __asm__ volatile("int $0x80" : "=a"(ret)
-        : "a"(SYSCALL_MKDIR), "b"(path) : "memory");
-    return __syscall_fix(ret);
-}
-
-static __attribute__((noreturn)) void sys_exit(int code) {
-    __asm__ volatile("int $0x80" : : "a"(SYSCALL_EXIT), "b"(code) : "memory");
-    for (;;) __asm__ volatile("hlt");
-}
-
-static uint32_t slen(const char* s) {
-    uint32_t n = 0;
-    while (s && s[n]) n++;
-    return n;
-}
-
-static void wr(int fd, const char* s) {
-    (void)sys_write(fd, s, slen(s));
+#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);
 }
 
-static void mkdir_main(uint32_t* sp0) {
-    uint32_t argc = sp0 ? sp0[0] : 0;
-    const char* const* argv = (const char* const*)(sp0 + 1);
-
+int main(int argc, char** argv) {
     if (argc <= 1) {
-        wr(2, "mkdir: missing operand\n");
-        sys_exit(1);
+        fprintf(stderr, "mkdir: missing operand\n");
+        return 1;
     }
 
     int rc = 0;
-    for (uint32_t i = 1; i < argc; i++) {
-        if (sys_mkdir(argv[i]) < 0) {
-            wr(2, "mkdir: cannot create '");
-            wr(2, argv[i]);
-            wr(2, "'\n");
-            rc = 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 == 'p') pflag = 1;
+                else {
+                    fprintf(stderr, "mkdir: invalid option -- '%c'\n", *f);
+                    return 1;
+                }
+                f++;
+            }
+            start = i + 1;
         }
     }
-    sys_exit(rc);
-}
 
-__attribute__((naked)) void _start(void) {
-    __asm__ volatile(
-        "mov %esp, %eax\n"
-        "push %eax\n"
-        "call mkdir_main\n"
-        "add $4, %esp\n"
-        "mov $0, %ebx\n"
-        "mov $2, %eax\n"
-        "int $0x80\n"
-        "hlt\n"
-    );
+    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/mv.c b/user/mv.c
new file mode 100644 (file)
index 0000000..1c8b029
--- /dev/null
+++ b/user/mv.c
@@ -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);
+    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;
+}
index 1f17524681d16f84bb2efb80003748e22c955bab..0a4d22e223fd4bcb0d8d438d2567fb1324cdc298 100644 (file)
--- a/user/rm.c
+++ b/user/rm.c
@@ -1,98 +1,48 @@
 /* AdrOS rm utility */
-#include <stdint.h>
-#include "user_errno.h"
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
 
-enum {
-    SYSCALL_WRITE  = 1,
-    SYSCALL_EXIT   = 2,
-    SYSCALL_UNLINK = 29,
-    SYSCALL_RMDIR  = 40,
-};
-
-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_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_rmdir(const char* path) {
-    int ret;
-    __asm__ volatile("int $0x80" : "=a"(ret)
-        : "a"(SYSCALL_RMDIR), "b"(path) : "memory");
-    return __syscall_fix(ret);
-}
-
-static __attribute__((noreturn)) void sys_exit(int code) {
-    __asm__ volatile("int $0x80" : : "a"(SYSCALL_EXIT), "b"(code) : "memory");
-    for (;;) __asm__ volatile("hlt");
-}
-
-static uint32_t slen(const char* s) {
-    uint32_t n = 0;
-    while (s && s[n]) n++;
-    return n;
-}
-
-static void wr(int fd, const char* s) {
-    (void)sys_write(fd, s, slen(s));
-}
-
-static int scmp(const char* a, const char* b) {
-    while (*a && *b && *a == *b) { a++; b++; }
-    return (unsigned char)*a - (unsigned char)*b;
-}
-
-static void rm_main(uint32_t* sp0) {
-    uint32_t argc = sp0 ? sp0[0] : 0;
-    const char* const* argv = (const char* const*)(sp0 + 1);
+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) {
-        wr(2, "rm: missing operand\n");
-        sys_exit(1);
+        fprintf(stderr, "rm: missing operand\n");
+        return 1;
     }
 
-    int rflag = 0;
-    int rc = 0;
-    uint32_t start = 1;
-
-    if (argv[1] && (scmp(argv[1], "-r") == 0 || scmp(argv[1], "-rf") == 0 ||
-                    scmp(argv[1], "-d") == 0)) {
-        rflag = 1;
-        start = 2;
+    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;
+        }
     }
 
-    for (uint32_t i = start; i < argc; i++) {
-        int r = sys_unlink(argv[i]);
-        if (r < 0 && rflag) {
-            r = sys_rmdir(argv[i]);
+    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) {
-            wr(2, "rm: cannot remove '");
-            wr(2, argv[i]);
-            wr(2, "'\n");
+        if (r < 0 && !fflag) {
+            fprintf(stderr, "rm: cannot remove '%s'\n", argv[i]);
             rc = 1;
         }
     }
-    sys_exit(rc);
-}
-
-__attribute__((naked)) void _start(void) {
-    __asm__ volatile(
-        "mov %esp, %eax\n"
-        "push %eax\n"
-        "call rm_main\n"
-        "add $4, %esp\n"
-        "mov $0, %ebx\n"
-        "mov $2, %eax\n"
-        "int $0x80\n"
-        "hlt\n"
-    );
+    return rc;
 }
index ba5dccb70b430803432c61d5167dba9692897274..aec3d569291f8d4ce5513fe9d3e756b521522fdd 100644 (file)
--- a/user/sh.c
+++ b/user/sh.c
-/* AdrOS minimal POSIX sh */
-#include <stdint.h>
-#include "user_errno.h"
-
-enum {
-    SYSCALL_WRITE   = 1,
-    SYSCALL_EXIT    = 2,
-    SYSCALL_GETPID  = 3,
-    SYSCALL_OPEN    = 4,
-    SYSCALL_READ    = 5,
-    SYSCALL_CLOSE   = 6,
-    SYSCALL_WAITPID = 7,
-    SYSCALL_DUP2    = 13,
-    SYSCALL_PIPE    = 14,
-    SYSCALL_EXECVE  = 15,
-    SYSCALL_FORK    = 16,
-};
-
-enum { O_RDONLY = 0, O_WRONLY = 1, O_CREAT = 0x40, O_TRUNC = 0x200 };
-
-/* ---- syscall wrappers ---- */
-
-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);
+/* 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 (< > >>)
+ *   - 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>
+
+#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 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 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 int sys_open(const char* path, int flags) {
-    int ret;
-    __asm__ volatile("int $0x80" : "=a"(ret)
-        : "a"(SYSCALL_OPEN), "b"(path), "c"(flags) : "memory");
-    return __syscall_fix(ret);
+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;
+        }
+    }
 }
 
-static int sys_close(int fd) {
-    int ret;
-    __asm__ volatile("int $0x80" : "=a"(ret)
-        : "a"(SYSCALL_CLOSE), "b"(fd) : "memory");
-    return __syscall_fix(ret);
+/* 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;
 }
 
-static int sys_fork(void) {
-    int ret;
-    __asm__ volatile("int $0x80" : "=a"(ret)
-        : "a"(SYSCALL_FORK) : "memory");
-    return __syscall_fix(ret);
-}
+/* ---- Command history ---- */
 
-static int sys_execve(const char* p, char* const* av,
-                      char* const* ev) {
-    int ret;
-    __asm__ volatile("int $0x80" : "=a"(ret)
-        : "a"(SYSCALL_EXECVE), "b"(p), "c"(av), "d"(ev) : "memory");
-    return __syscall_fix(ret);
-}
+static char history[HIST_SIZE][LINE_MAX];
+static int hist_count = 0;
+static int hist_pos = 0;
 
-static int sys_waitpid(int pid, int* status, int opts) {
-    int ret;
-    __asm__ volatile("int $0x80" : "=a"(ret)
-        : "a"(SYSCALL_WAITPID), "b"(pid), "c"(status), "d"(opts) : "memory");
-    return __syscall_fix(ret);
+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++;
 }
 
-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);
-}
+/* ---- Line editing ---- */
 
-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 char line[LINE_MAX];
 
-static __attribute__((noreturn)) void sys_exit(int code) {
-    __asm__ volatile("int $0x80" : : "a"(SYSCALL_EXIT), "b"(code) : "memory");
-    for (;;) __asm__ volatile("hlt");
+static void term_write(const char* s, int n) {
+    write(STDOUT_FILENO, s, (size_t)n);
 }
 
-/* ---- string helpers ---- */
+static int read_line_edit(void) {
+    int pos = 0;
+    int len = 0;
+    hist_pos = hist_count;
 
-static uint32_t slen(const char* s) {
-    uint32_t n = 0;
-    while (s && s[n]) n++;
-    return n;
-}
+    memset(line, 0, LINE_MAX);
 
-static void wr(int fd, const char* s) {
-    (void)sys_write(fd, s, slen(s));
-}
+    while (len < LINE_MAX - 1) {
+        char c;
+        int r = read(STDIN_FILENO, &c, 1);
+        if (r <= 0) {
+            if (len == 0) return -1;
+            break;
+        }
 
-static int scmp(const char* a, const char* b) {
-    while (*a && *b && *a == *b) { a++; b++; }
-    return (unsigned char)*a - (unsigned char)*b;
-}
+        if (c == '\n' || c == '\r') {
+            term_write("\n", 1);
+            break;
+        }
 
-static void scpy(char* d, const char* s) {
-    while (*s) *d++ = *s++;
-    *d = 0;
-}
+        /* 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;
+        }
+
+        /* Ctrl+D = EOF */
+        if (c == 4) {
+            if (len == 0) return -1;
+            continue;
+        }
 
-/* ---- line reading ---- */
+        /* Ctrl+C = cancel line */
+        if (c == 3) {
+            term_write("^C\n", 3);
+            line[0] = '\0';
+            return 0;
+        }
 
-#define LINE_MAX 256
-#define MAX_ARGS 32
+        /* Ctrl+A = beginning of line */
+        if (c == 1) {
+            while (pos > 0) { term_write("\b", 1); pos--; }
+            continue;
+        }
 
-static char line[LINE_MAX];
+        /* Ctrl+E = end of line */
+        if (c == 5) {
+            term_write(line + pos, len - pos);
+            pos = len;
+            continue;
+        }
 
-static int read_line(void) {
-    uint32_t pos = 0;
-    while (pos < LINE_MAX - 1) {
-        char c;
-        int r = sys_read(0, &c, 1);
-        if (r <= 0) {
-            if (pos == 0) return -1;
-            break;
+        /* 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;
+
+            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;
+            }
+            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);
         }
-        if (c == '\n' || c == '\r') break;
-        if ((c == '\b' || c == 127) && pos > 0) { pos--; continue; }
-        if (c >= ' ' && c <= '~') line[pos++] = c;
     }
-    line[pos] = 0;
-    return (int)pos;
+
+    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 ---- */
+/* ---- 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;
-        argv[argc++] = p;
-        while (*p && *p != ' ' && *p != '\t') p++;
-        if (*p) *p++ = 0;
+        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] = 0;
+    argv[argc] = NULL;
     return argc;
 }
 
@@ -157,100 +323,196 @@ static char pathbuf[256];
 
 static const char* resolve(const char* cmd) {
     if (cmd[0] == '/' || cmd[0] == '.') return cmd;
-    static const char* dirs[] = { "/bin/", "/disk/bin/", 0 };
-    for (int i = 0; dirs[i]; i++) {
-        scpy(pathbuf, dirs[i]);
-        scpy(pathbuf + slen(pathbuf), cmd);
-        int fd = sys_open(pathbuf, O_RDONLY);
-        if (fd >= 0) { sys_close(fd); return pathbuf; }
+
+    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;
 }
 
-/* ---- run a single simple command ---- */
+/* ---- Run a single simple command ---- */
 
 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(cmd, argv, MAX_ARGS);
+    int argc = parse_args(expanded, argv, MAX_ARGS);
     if (argc == 0) return;
 
-    /* extract redirections */
-    char* redir_out = 0;
-    char* redir_in  = 0;
+    /* 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 nargc = 0;
     for (int i = 0; i < argc; i++) {
-        if (scmp(argv[i], ">") == 0 && i + 1 < argc) {
-            redir_out = argv[++i];
-        } else if (scmp(argv[i], "<") == 0 && i + 1 < argc) {
+        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) {
             redir_in = argv[++i];
         } else {
             argv[nargc++] = argv[i];
         }
     }
-    argv[nargc] = 0;
+    argv[nargc] = NULL;
     argc = nargc;
     if (argc == 0) return;
 
-    /* builtin: exit */
-    if (scmp(argv[0], "exit") == 0) {
-        int code = 0;
-        if (argc > 1) {
-            const char* s = argv[1];
-            while (*s >= '0' && *s <= '9') {
-                code = code * 10 + (*s - '0');
-                s++;
+    /* ---- 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);
+        }
+        return;
+    }
+
+    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");
+        return;
+    }
+
+    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;
             }
         }
-        sys_exit(code);
+        return;
+    }
+
+    if (strcmp(argv[0], "unset") == 0) {
+        for (int i = 1; i < argc; i++) var_unset(argv[i]);
+        return;
+    }
+
+    if (strcmp(argv[0], "set") == 0) {
+        for (int i = 0; i < nvar; i++)
+            printf("%s=%s\n", vars[i].name, vars[i].value);
+        return;
+    }
+
+    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);
+        return;
     }
 
-    /* builtin: echo */
-    if (scmp(argv[0], "echo") == 0) {
+    if (strcmp(argv[0], "type") == 0) {
         for (int i = 1; i < argc; i++) {
-            if (i > 1) wr(1, " ");
-            wr(1, argv[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]);
+            }
         }
-        wr(1, "\n");
         return;
     }
 
-    /* external command */
+    /* ---- External command ---- */
     const char* path = resolve(argv[0]);
-    int pid = sys_fork();
-    if (pid < 0) { wr(2, "sh: fork failed\n"); return; }
+    char** envp = build_envp();
+
+    int pid = fork();
+    if (pid < 0) { fprintf(stderr, "sh: fork failed\n"); return; }
 
     if (pid == 0) {
         /* child */
         if (redir_in) {
-            int fd = sys_open(redir_in, O_RDONLY);
-            if (fd >= 0) { sys_dup2(fd, 0); sys_close(fd); }
+            int fd = open(redir_in, O_RDONLY);
+            if (fd >= 0) { dup2(fd, 0); close(fd); }
         }
         if (redir_out) {
-            int fd = sys_open(redir_out, O_WRONLY | O_CREAT | O_TRUNC);
-            if (fd >= 0) { sys_dup2(fd, 1); sys_close(fd); }
+            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); }
         }
-        sys_execve(path, argv, 0);
-        wr(2, "sh: ");
-        wr(2, argv[0]);
-        wr(2, ": not found\n");
-        sys_exit(127);
+        execve(path, (const char* const*)argv, (const char* const*)envp);
+        fprintf(stderr, "sh: %s: not found\n", argv[0]);
+        _exit(127);
     }
 
     /* parent waits */
     int st;
-    sys_waitpid(pid, &st, 0);
+    waitpid(pid, &st, 0);
+    last_status = st;
 }
 
-/* ---- pipeline support ---- */
+/* ---- Pipeline support ---- */
 
 static void run_pipeline(char* cmdline) {
-    /* split on '|' */
-    char* cmds[4];
+    /* 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 == '|' && ncmds < 3) {
-            *p = 0;
+        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) {
+            *p = '\0';
             cmds[++ncmds] = p + 1;
         }
     }
@@ -261,69 +523,137 @@ static void run_pipeline(char* cmdline) {
         return;
     }
 
-    /* multi-stage pipeline */
+    /* Multi-stage pipeline */
     int prev_rd = -1;
+    int pids[8];
+
     for (int i = 0; i < ncmds; i++) {
         int pfd[2] = {-1, -1};
         if (i < ncmds - 1) {
-            if (sys_pipe(pfd) < 0) {
-                wr(2, "sh: pipe failed\n");
+            if (pipe(pfd) < 0) {
+                fprintf(stderr, "sh: pipe failed\n");
                 return;
             }
         }
 
-        int pid = sys_fork();
-        if (pid < 0) { wr(2, "sh: fork failed\n"); return; }
+        pids[i] = fork();
+        if (pids[i] < 0) { fprintf(stderr, "sh: fork failed\n"); return; }
 
-        if (pid == 0) {
-            if (prev_rd >= 0) { sys_dup2(prev_rd, 0); sys_close(prev_rd); }
-            if (pfd[1] >= 0)  { sys_dup2(pfd[1], 1); sys_close(pfd[1]); }
-            if (pfd[0] >= 0)  sys_close(pfd[0]);
+        if (pids[i] == 0) {
+            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(cmds[i], argv, MAX_ARGS);
-            if (argc == 0) sys_exit(0);
+            int argc = parse_args(expanded, argv, MAX_ARGS);
+            if (argc == 0) _exit(0);
             const char* path = resolve(argv[0]);
-            sys_execve(path, argv, 0);
-            wr(2, "sh: ");
-            wr(2, argv[0]);
-            wr(2, ": not found\n");
-            sys_exit(127);
+            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 */
-        if (prev_rd >= 0) sys_close(prev_rd);
-        if (pfd[1] >= 0)  sys_close(pfd[1]);
+        if (prev_rd >= 0) close(prev_rd);
+        if (pfd[1] >= 0)  close(pfd[1]);
         prev_rd = pfd[0];
     }
 
-    if (prev_rd >= 0) sys_close(prev_rd);
+    if (prev_rd >= 0) close(prev_rd);
 
-    /* wait for all children */
+    /* Wait for all children */
     for (int i = 0; i < ncmds; i++) {
         int st;
-        sys_waitpid(-1, &st, 0);
+        waitpid(pids[i], &st, 0);
+        if (i == ncmds - 1) last_status = st;
+    }
+}
+
+/* ---- Process a command line (handle ; and &&) ---- */
+
+static void process_line(char* input) {
+    /* Split on ';' */
+    char* p = input;
+    while (*p) {
+        /* skip leading whitespace */
+        while (*p == ' ' || *p == '\t') p++;
+        if (*p == '\0') break;
+
+        char* start = p;
+        int in_sq = 0, in_dq = 0;
+        while (*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) break;
+            p++;
+        }
+        char saved = *p;
+        if (*p) *p++ = '\0';
+
+        if (start[0] != '\0')
+            run_pipeline(start);
+
+        if (saved == '\0') break;
     }
 }
 
-/* ---- main loop ---- */
+/* ---- Prompt ---- */
 
-static void sh_main(void) {
-    wr(1, "$ ");
+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);
+
+    print_prompt();
     while (1) {
-        int len = read_line();
+        int len = read_line_edit();
         if (len < 0) break;
-        if (len > 0) run_pipeline(line);
-        wr(1, "$ ");
+        if (len > 0) {
+            hist_add(line);
+            process_line(line);
+        }
+        print_prompt();
     }
-}
 
-__attribute__((naked)) void _start(void) {
-    __asm__ volatile(
-        "call sh_main\n"
-        "mov $0, %ebx\n"
-        "mov $2, %eax\n"
-        "int $0x80\n"
-        "hlt\n"
-    );
+    return last_status;
 }
diff --git a/user/sort.c b/user/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/tail.c b/user/tail.c
new file mode 100644 (file)
index 0000000..8040380
--- /dev/null
@@ -0,0 +1,59 @@
+/* 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 */
+    int count = 0;
+    int pos = total;
+    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/touch.c b/user/touch.c
new file mode 100644 (file)
index 0000000..f4a0435
--- /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);
+        if (fd < 0) {
+            fprintf(stderr, "touch: cannot touch '%s'\n", argv[i]);
+            rc = 1;
+        } else {
+            close(fd);
+        }
+    }
+    return rc;
+}
index 89245962e6cbf251b3bb06de7ea5edfc2cff560c..a8203a34c5545bc2733f9324f4596c79564ef950 100644 (file)
@@ -2,29 +2,43 @@
 CC := i686-elf-gcc
 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
 ASFLAGS := --32
 
 SRC_C := $(wildcard src/*.c)
 SRC_S := $(wildcard src/*.S)
 OBJ := $(SRC_C:.c=.o) $(SRC_S:.S=.o)
 
+# PIC objects for shared library (exclude crt0 — it's linked separately)
+PIC_C := $(filter-out src/crt0.S,$(SRC_C))
+PIC_OBJ := $(PIC_C:.c=.pic.o)
+
 all: libulibc.a
 
 libulibc.a: $(OBJ)
        @$(AR) rcs $@ $^
        @echo "  AR      $@"
 
+libc.so: $(PIC_OBJ)
+       @$(LD) -m elf_i386 -shared -soname libc.so -o $@ $^
+       @echo "  LD      $@ (shared)"
+
 src/%.o: src/%.c
        @$(CC) $(CFLAGS) -c $< -o $@
        @echo "  CC      $<"
 
+src/%.pic.o: src/%.c
+       @$(CC) $(CFLAGS_PIC) -c $< -o $@
+       @echo "  CC [PIC] $<"
+
 src/%.o: src/%.S
        @$(AS) $(ASFLAGS) $< -o $@
        @echo "  AS      $<"
 
 clean:
-       rm -f src/*.o libulibc.a
+       rm -f src/*.o src/*.pic.o libulibc.a libc.so
 
 .PHONY: all clean
diff --git a/user/ulibc/include/dirent.h b/user/ulibc/include/dirent.h
new file mode 100644 (file)
index 0000000..eba5df0
--- /dev/null
@@ -0,0 +1,20 @@
+#ifndef ULIBC_DIRENT_H
+#define ULIBC_DIRENT_H
+
+#include <stdint.h>
+
+struct dirent {
+    uint32_t d_ino;
+    uint16_t d_reclen;
+    uint8_t  d_type;
+    char     d_name[256];
+};
+
+#define DT_UNKNOWN 0
+#define DT_REG     8
+#define DT_DIR     4
+#define DT_CHR     2
+#define DT_BLK     6
+#define DT_LNK    10
+
+#endif
index a70ba3f1349584eb531feaf5ea4b76411a5635dc..e75e08c862127f8af18774010c6b3c65501fe885 100644 (file)
@@ -22,9 +22,24 @@ struct stat {
 #define S_ISDIR(m)  (((m) & 0170000) == 0040000)
 #define S_ISREG(m)  (((m) & 0170000) == 0100000)
 #define S_ISCHR(m)  (((m) & 0170000) == 0020000)
+#define S_ISLNK(m)  (((m) & 0170000) == 0120000)
+#define S_ISBLK(m)  (((m) & 0170000) == 0060000)
+#define S_ISFIFO(m) (((m) & 0170000) == 0010000)
+#define S_ISSOCK(m) (((m) & 0170000) == 0140000)
 
-int stat(const char* path, struct stat* buf);
-int fstat(int fd, struct stat* buf);
-int mkdir(const char* path, mode_t mode);
+#define S_IRWXU 0700
+#define S_IRUSR 0400
+#define S_IWUSR 0200
+#define S_IXUSR 0100
+#define S_IRWXG 0070
+#define S_IRGRP 0040
+#define S_IWGRP 0020
+#define S_IXGRP 0010
+#define S_IRWXO 0007
+#define S_IROTH 0004
+#define S_IWOTH 0002
+#define S_IXOTH 0001
+
+/* stat/fstat/mkdir declared in <unistd.h> with void* for struct stat* compatibility */
 
 #endif
index e73fef345c0c474dffa9588f3c50cb03f0f7f663..5e5c49248b51c16fb4cb457bf2dbe6c033d65175 100644 (file)
@@ -26,7 +26,7 @@ int     getpid(void);
 int     getppid(void);
 int     chdir(const char* path);
 int     getcwd(char* buf, size_t size);
-int     mkdir(const char* path);
+int     mkdir(const char* path, ...);  /* mode_t optional in AdrOS */
 int     unlink(const char* path);
 int     rmdir(const char* path);
 int     setsid(void);
@@ -57,6 +57,21 @@ int     flock(int fd, int operation);
 int     isatty(int fd);
 void*   brk(void* addr);
 
+int     waitpid(int pid, int* status, int options);
+int     getdents(int fd, void* buf, size_t count);
+int     stat(const char* path, void* buf);  /* use sys/stat.h for typed version */
+int     fstat(int fd, void* buf);              /* use sys/stat.h for typed version */
+int     chmod(const char* path, int mode);
+int     chown(const char* path, int owner, int group);
+int     link(const char* oldpath, const char* newpath);
+int     symlink(const char* target, const char* linkpath);
+int     readlink(const char* path, char* buf, size_t bufsiz);
+int     kill(int pid, int sig);
+int     rename(const char* oldpath, const char* newpath);
+
 void    _exit(int status) __attribute__((noreturn));
 
+/* Environment pointer (set by crt0) */
+extern char** __environ;
+
 #endif
index 746cc1006bec1948db3c535a9c6a7de8c62e6780..acb05ef75d905177ee111e8c6f80e7571d9ee786 100644 (file)
@@ -1,11 +1,18 @@
 /*
  * ulibc crt0 — C runtime startup for AdrOS userspace
- * Entry point: _start → main() → exit()
+ * Entry point: _start → main(argc, argv, envp) → exit()
+ *
+ * Stack layout at entry (set up by execve or ld.so):
+ *   [ESP+0]  argc
+ *   [ESP+4]  argv[0], argv[1], ..., NULL
+ *   [...]    envp[0], envp[1], ..., NULL
+ *   [...]    auxv entries (if ld.so present)
  */
 .section .text
 .global _start
 .extern main
 .extern exit
+.extern __environ
 
 _start:
     /* Set up user data segments */
@@ -15,10 +22,23 @@ _start:
     mov %ax, %fs
     mov %ax, %gs
 
-    /* Call main() — no argc/argv yet */
-    push $0         /* envp = NULL */
-    push $0         /* argv = NULL */
-    push $0         /* argc = 0 */
+    /* Parse stack: argc at (%esp), argv at 4(%esp) */
+    mov (%esp), %eax        /* argc */
+    lea 4(%esp), %ecx       /* argv = &argv[0] */
+
+    /* envp = argv + (argc + 1) * 4  (skip past argv[] and its NULL) */
+    mov %eax, %edx
+    add $1, %edx
+    shl $2, %edx
+    add %ecx, %edx          /* edx = envp */
+
+    /* Store envp in __environ global (weak, may not exist) */
+    mov %edx, __environ
+
+    /* Call main(argc, argv, envp) */
+    push %edx               /* envp */
+    push %ecx               /* argv */
+    push %eax               /* argc */
     call main
     add $12, %esp
 
index 625c69d919a87623524bafeb06cda93b6f531263..7fae4f2461dcb5d828f6cccd474e17e67300bbe1 100644 (file)
@@ -22,6 +22,11 @@ int sigsuspend(const uint32_t* mask) {
     return __syscall_ret(_syscall1(SYS_SIGSUSPEND, (int)mask));
 }
 
+int sigaction(int signum, const struct sigaction* act,
+              struct sigaction* oldact) {
+    return __syscall_ret(_syscall3(SYS_SIGACTION, signum, (int)act, (int)oldact));
+}
+
 int sigaltstack(const stack_t* ss, stack_t* old_ss) {
     return __syscall_ret(_syscall2(SYS_SIGALTSTACK, (int)ss, (int)old_ss));
 }
index 45e1706677638af17d443c3c2673097826112b7f..dddeb03ad8be1a641409ccd64417f54a7e0e640c 100644 (file)
@@ -2,6 +2,9 @@
 #include "unistd.h"
 #include "string.h"
 
+/* Global environment pointer — set by crt0 from execve stack layout */
+char** __environ = 0;
+
 /*
  * Minimal bump allocator using brk() syscall.
  * No free() support yet — memory is only reclaimed on process exit.
@@ -155,7 +158,13 @@ long strtol(const char* nptr, char** endptr, int base) {
 }
 
 char* getenv(const char* name) {
-    (void)name;
+    extern char** __environ;
+    if (!name || !__environ) return (char*)0;
+    size_t len = strlen(name);
+    for (char** e = __environ; *e; e++) {
+        if (strncmp(*e, name, len) == 0 && (*e)[len] == '=')
+            return *e + len + 1;
+    }
     return (char*)0;
 }
 
index ae7ad37b06d670181b7be0e59aaf9cd77193a437..51c34e092c52d5bc62729eb4a6c0179581c6466f 100644 (file)
@@ -58,7 +58,7 @@ int getcwd(char* buf, size_t size) {
     return __syscall_ret(_syscall2(SYS_GETCWD, (int)buf, (int)size));
 }
 
-int mkdir(const char* path) {
+int mkdir(const char* path, ...) {
     return __syscall_ret(_syscall1(SYS_MKDIR, (int)path));
 }
 
@@ -174,6 +174,47 @@ int isatty(int fd) {
     return (rc == 0) ? 1 : 0;
 }
 
+int waitpid(int pid, int* status, int options) {
+    return __syscall_ret(_syscall3(SYS_WAITPID, pid, (int)status, options));
+}
+
+int getdents(int fd, void* buf, size_t count) {
+    return __syscall_ret(_syscall3(SYS_GETDENTS, fd, (int)buf, (int)count));
+}
+
+int stat(const char* path, void* buf) {
+    return __syscall_ret(_syscall2(SYS_STAT, (int)path, (int)buf));
+}
+
+int fstat(int fd, void* buf) {
+    return __syscall_ret(_syscall2(SYS_FSTAT, fd, (int)buf));
+}
+
+int chmod(const char* path, int mode) {
+    return __syscall_ret(_syscall2(SYS_CHMOD, (int)path, mode));
+}
+
+int chown(const char* path, int owner, int group) {
+    return __syscall_ret(_syscall3(SYS_CHOWN, (int)path, owner, group));
+}
+
+int link(const char* oldpath, const char* newpath) {
+    /* Use SYS_UNLINKAT slot 38 — AdrOS doesn't have a dedicated link syscall yet,
+     * use a direct int $0x80 with the link syscall number if available */
+    (void)oldpath; (void)newpath;
+    return -1;  /* TODO: implement when kernel has SYS_LINK */
+}
+
+int symlink(const char* target, const char* linkpath) {
+    (void)target; (void)linkpath;
+    return -1;  /* TODO: implement when kernel has SYS_SYMLINK */
+}
+
+int readlink(const char* path, char* buf, size_t bufsiz) {
+    (void)path; (void)buf; (void)bufsiz;
+    return -1;  /* TODO: implement when kernel has SYS_READLINK */
+}
+
 void _exit(int status) {
     _syscall1(SYS_EXIT, status);
     /* If exit syscall somehow returns, loop forever.
diff --git a/user/uniq.c b/user/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/uptime.c b/user/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/wc.c b/user/wc.c
new file mode 100644 (file)
index 0000000..76333eb
--- /dev/null
+++ b/user/wc.c
@@ -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;
+}