From: Tulio A M Mendes Date: Sat, 14 Mar 2026 15:40:11 +0000 (-0300) Subject: refactor: reorganize userland into user/cmds// with per-program Makefiles X-Git-Url: https://projects.tadryanom.me/?a=commitdiff_plain;h=9e0854b0114442014cf80ce3224075ae4ff7c6d2;p=AdrOS.git refactor: reorganize userland into user/cmds// with per-program Makefiles - Move 53 user commands from user/.c to user/cmds//.c - Add user/cmds/common.mk shared build rules for dynamically-linked commands - Add per-program Makefiles for all commands (including fulltest, ldso, pie_test) - Build all .o/.elf into build/user/cmds// (out-of-tree) - Replace [init] test prefix with [test] in fulltest.c, pie_main.c, test scripts - Fix find.c and which.c: use opendir/readdir/closedir instead of raw getdents - Fix ulibc glob.c missing stdio.h include - Fix ulibc -Wno-incompatible-pointer-types for GCC 14+ - Fix test_host_utils.sh which test set -e issue - Doom rootfs path changed to /usr/games/doom - make clean now also cleans ulibc and doom in-tree artifacts Tests: 103/103 smoke, 28/28 unit, 19/19 security, 68/68 host utils, cppcheck clean --- diff --git a/BUILD_GUIDE.md b/BUILD_GUIDE.md index f613445..fa0b4a1 100644 --- a/BUILD_GUIDE.md +++ b/BUILD_GUIDE.md @@ -142,7 +142,7 @@ The fulltest binary (`/sbin/fulltest`) runs a comprehensive suite of 102 smoke t - Dynamic linking: lazy PLT resolution, PLT caching - LZ4 initrd decompression -All tests print `[init] ... OK` on success. Any failure calls `sys_exit(1)`. +All tests print `[test] ... OK` on success. Any failure calls `sys_exit(1)`. ### Testing diff --git a/Makefile b/Makefile index 02f123e..4b08b49 100644 --- a/Makefile +++ b/Makefile @@ -78,66 +78,41 @@ ifeq ($(ARCH),x86) USER_LD ?= i686-elf-ld USER_AR ?= i686-elf-ar - FULLTEST_ELF := user/fulltest.elf - ECHO_ELF := user/echo.elf - SH_ELF := user/sh.elf - CAT_ELF := user/cat.elf - LS_ELF := user/ls.elf - MKDIR_ELF := user/mkdir.elf - RM_ELF := user/rm.elf - CP_ELF := user/cp.elf - MV_ELF := user/mv.elf - TOUCH_ELF := user/touch.elf - LN_ELF := user/ln.elf - HEAD_ELF := user/head.elf - TAIL_ELF := user/tail.elf - WC_ELF := user/wc.elf - SORT_ELF := user/sort.elf - UNIQ_ELF := user/uniq.elf - CUT_ELF := user/cut.elf - CHMOD_ELF := user/chmod.elf - CHOWN_ELF := user/chown.elf - CHGRP_ELF := user/chgrp.elf - DATE_ELF := user/date.elf - HOSTNAME_ELF := user/hostname.elf - UPTIME_ELF := user/uptime.elf - MOUNT_ELF := user/mount.elf - UMOUNT_ELF := user/umount.elf - ENV_ELF := user/env.elf - KILL_ELF := user/kill.elf - SLEEP_ELF := user/sleep.elf - CLEAR_ELF := user/clear.elf - PS_ELF := user/ps.elf - DF_ELF := user/df.elf - FREE_ELF := user/free.elf - TEE_ELF := user/tee.elf - BASENAME_ELF := user/basename.elf - DIRNAME_ELF := user/dirname.elf - RMDIR_ELF := user/rmdir.elf - GREP_ELF := user/grep.elf - ID_ELF := user/id.elf - UNAME_ELF := user/uname.elf - DMESG_ELF := user/dmesg.elf - PRINTENV_ELF := user/printenv.elf - TR_ELF := user/tr.elf - DD_ELF := user/dd.elf - PWD_ELF := user/pwd.elf - STAT_ELF := user/stat.elf - SED_ELF := user/sed.elf - AWK_ELF := user/awk.elf - WHO_ELF := user/who.elf - TOP_ELF := user/top.elf - DU_ELF := user/du.elf - FIND_ELF := user/find.elf - WHICH_ELF := user/which.elf - INIT_ELF := user/init.elf - LDSO_ELF := user/ld.so - ULIBC_SO := user/ulibc/libc.so - PIE_SO := user/libpietest.so - PIE_ELF := user/pie_test.elf + # User build output directory + USER_BUILD := build/user + + # List of dynamically-linked user commands (built via user/cmds//Makefile) + USER_CMD_NAMES := echo sh cat ls mkdir rm cp mv touch ln \ + head tail wc sort uniq cut \ + chmod chown chgrp \ + date hostname uptime \ + mount umount env kill sleep \ + clear ps df free tee \ + basename dirname rmdir \ + grep id uname dmesg \ + printenv tr dd pwd stat \ + sed awk who top du find which \ + init + + # ELF paths for dynamically-linked commands + USER_CMD_ELFS := $(foreach cmd,$(USER_CMD_NAMES),$(USER_BUILD)/cmds/$(cmd)/$(cmd).elf) + + # Special builds (not dynamically-linked via common.mk) + FULLTEST_ELF := $(USER_BUILD)/cmds/fulltest/fulltest.elf + LDSO_ELF := $(USER_BUILD)/cmds/ldso/ld.so + PIE_SO := $(USER_BUILD)/cmds/pie_test/libpietest.so + PIE_ELF := $(USER_BUILD)/cmds/pie_test/pie_test.elf + + # ulibc + ULIBC_DIR := user/ulibc + ULIBC_SO := $(USER_BUILD)/ulibc/libc.so + ULIBC_LIB := $(USER_BUILD)/ulibc/libulibc.a + + # doom DOOM_ELF := user/doom/doom.elf + INITRD_IMG := initrd.img - MKINITRD := tools/mkinitrd + MKINITRD := tools/mkinitrd endif # --- ARM64 Configuration --- @@ -214,285 +189,56 @@ iso: $(KERNEL_NAME) $(INITRD_IMG) $(MKINITRD): tools/mkinitrd.c include/xxhash32.h @gcc -Iinclude tools/mkinitrd.c -o $(MKINITRD) -ULIBC_DIR := user/ulibc -ULIBC_LIB := $(ULIBC_DIR)/libulibc.a - -$(ULIBC_LIB): - @$(MAKE) -C $(ULIBC_DIR) CC="$(USER_CC)" AS="$(USER_CC:gcc=as)" AR="$(USER_AR)" LD="$(USER_LD)" --no-print-directory - -$(ULIBC_SO): - @$(MAKE) -C $(ULIBC_DIR) CC="$(USER_CC)" AS="$(USER_CC:gcc=as)" AR="$(USER_AR)" LD="$(USER_LD)" libc.so --no-print-directory - -$(FULLTEST_ELF): user/fulltest.c user/linker.ld - @$(USER_CC) -m32 -I include -ffreestanding -fno-pie -no-pie -nostdlib -Wl,-T,user/linker.ld -o $(FULLTEST_ELF) user/fulltest.c user/errno.c - -# --- Dynamic linking helper: compile .c to PIC .o, link as PIE with crt0 + libc.so --- -ULIBC_CRT0 := $(ULIBC_DIR)/src/crt0.o -DYN_CC := $(USER_CC) -m32 -ffreestanding -nostdlib -O2 -Wall -Wextra -fPIC -fno-plt -I$(ULIBC_DIR)/include -DYN_LD := $(USER_LD) -m elf_i386 --dynamic-linker=/lib/ld.so -T user/dyn_linker.ld -L$(ULIBC_DIR) -rpath /lib - -$(ECHO_ELF): user/echo.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) - @$(DYN_CC) -c user/echo.c -o user/echo.o - @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/echo.o -lc - -$(SH_ELF): user/sh.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) - @$(DYN_CC) -c user/sh.c -o user/sh.o - @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/sh.o -lc - -$(CAT_ELF): user/cat.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) - @$(DYN_CC) -c user/cat.c -o user/cat.o - @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/cat.o -lc - -$(LS_ELF): user/ls.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) - @$(DYN_CC) -c user/ls.c -o user/ls.o - @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/ls.o -lc - -$(MKDIR_ELF): user/mkdir.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) - @$(DYN_CC) -c user/mkdir.c -o user/mkdir.o - @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/mkdir.o -lc - -$(RM_ELF): user/rm.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) - @$(DYN_CC) -c user/rm.c -o user/rm.o - @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/rm.o -lc - -$(CP_ELF): user/cp.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) - @$(DYN_CC) -c user/cp.c -o user/cp.o - @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/cp.o -lc - -$(MV_ELF): user/mv.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) - @$(DYN_CC) -c user/mv.c -o user/mv.o - @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/mv.o -lc - -$(TOUCH_ELF): user/touch.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) - @$(DYN_CC) -c user/touch.c -o user/touch.o - @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/touch.o -lc - -$(LN_ELF): user/ln.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) - @$(DYN_CC) -c user/ln.c -o user/ln.o - @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/ln.o -lc - -$(HEAD_ELF): user/head.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) - @$(DYN_CC) -c user/head.c -o user/head.o - @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/head.o -lc - -$(TAIL_ELF): user/tail.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) - @$(DYN_CC) -c user/tail.c -o user/tail.o - @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/tail.o -lc - -$(WC_ELF): user/wc.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) - @$(DYN_CC) -c user/wc.c -o user/wc.o - @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/wc.o -lc - -$(SORT_ELF): user/sort.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) - @$(DYN_CC) -c user/sort.c -o user/sort.o - @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/sort.o -lc - -$(UNIQ_ELF): user/uniq.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) - @$(DYN_CC) -c user/uniq.c -o user/uniq.o - @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/uniq.o -lc - -$(CUT_ELF): user/cut.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) - @$(DYN_CC) -c user/cut.c -o user/cut.o - @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/cut.o -lc - -$(CHMOD_ELF): user/chmod.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) - @$(DYN_CC) -c user/chmod.c -o user/chmod.o - @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/chmod.o -lc - -$(CHOWN_ELF): user/chown.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) - @$(DYN_CC) -c user/chown.c -o user/chown.o - @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/chown.o -lc - -$(CHGRP_ELF): user/chgrp.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) - @$(DYN_CC) -c user/chgrp.c -o user/chgrp.o - @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/chgrp.o -lc - -$(DATE_ELF): user/date.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) - @$(DYN_CC) -c user/date.c -o user/date.o - @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/date.o -lc - -$(HOSTNAME_ELF): user/hostname.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) - @$(DYN_CC) -c user/hostname.c -o user/hostname.o - @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/hostname.o -lc - -$(UPTIME_ELF): user/uptime.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) - @$(DYN_CC) -c user/uptime.c -o user/uptime.o - @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/uptime.o -lc - -$(INIT_ELF): user/init.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) - @$(DYN_CC) -c user/init.c -o user/init.o - @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/init.o -lc - -$(MOUNT_ELF): user/mount.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) - @$(DYN_CC) -c user/mount.c -o user/mount.o - @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/mount.o -lc - -$(UMOUNT_ELF): user/umount.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) - @$(DYN_CC) -c user/umount.c -o user/umount.o - @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/umount.o -lc - -$(ENV_ELF): user/env.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) - @$(DYN_CC) -c user/env.c -o user/env.o - @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/env.o -lc - -$(KILL_ELF): user/kill.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) - @$(DYN_CC) -c user/kill.c -o user/kill.o - @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/kill.o -lc - -$(SLEEP_ELF): user/sleep.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) - @$(DYN_CC) -c user/sleep.c -o user/sleep.o - @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/sleep.o -lc - -$(CLEAR_ELF): user/clear.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) - @$(DYN_CC) -c user/clear.c -o user/clear.o - @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/clear.o -lc - -$(PS_ELF): user/ps.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) - @$(DYN_CC) -c user/ps.c -o user/ps.o - @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/ps.o -lc - -$(DF_ELF): user/df.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) - @$(DYN_CC) -c user/df.c -o user/df.o - @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/df.o -lc - -$(FREE_ELF): user/free.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) - @$(DYN_CC) -c user/free.c -o user/free.o - @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/free.o -lc - -$(TEE_ELF): user/tee.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) - @$(DYN_CC) -c user/tee.c -o user/tee.o - @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/tee.o -lc - -$(BASENAME_ELF): user/basename.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) - @$(DYN_CC) -c user/basename.c -o user/basename.o - @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/basename.o -lc - -$(DIRNAME_ELF): user/dirname.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) - @$(DYN_CC) -c user/dirname.c -o user/dirname.o - @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/dirname.o -lc - -$(RMDIR_ELF): user/rmdir.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) - @$(DYN_CC) -c user/rmdir.c -o user/rmdir.o - @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/rmdir.o -lc - -$(GREP_ELF): user/grep.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) - @$(DYN_CC) -c user/grep.c -o user/grep.o - @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/grep.o -lc - -$(ID_ELF): user/id.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) - @$(DYN_CC) -c user/id.c -o user/id.o - @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/id.o -lc - -$(UNAME_ELF): user/uname.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) - @$(DYN_CC) -c user/uname.c -o user/uname.o - @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/uname.o -lc - -$(DMESG_ELF): user/dmesg.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) - @$(DYN_CC) -c user/dmesg.c -o user/dmesg.o - @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/dmesg.o -lc - -$(PRINTENV_ELF): user/printenv.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) - @$(DYN_CC) -c user/printenv.c -o user/printenv.o - @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/printenv.o -lc - -$(TR_ELF): user/tr.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) - @$(DYN_CC) -c user/tr.c -o user/tr.o - @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/tr.o -lc - -$(DD_ELF): user/dd.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) - @$(DYN_CC) -c user/dd.c -o user/dd.o - @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/dd.o -lc - -$(PWD_ELF): user/pwd.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) - @$(DYN_CC) -c user/pwd.c -o user/pwd.o - @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/pwd.o -lc - -$(STAT_ELF): user/stat.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) - @$(DYN_CC) -c user/stat.c -o user/stat.o - @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/stat.o -lc - -$(SED_ELF): user/sed.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) - @$(DYN_CC) -c user/sed.c -o user/sed.o - @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/sed.o -lc - -$(AWK_ELF): user/awk.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) - @$(DYN_CC) -c user/awk.c -o user/awk.o - @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/awk.o -lc - -$(WHO_ELF): user/who.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) - @$(DYN_CC) -c user/who.c -o user/who.o - @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/who.o -lc - -$(TOP_ELF): user/top.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) - @$(DYN_CC) -c user/top.c -o user/top.o - @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/top.o -lc - -$(DU_ELF): user/du.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) - @$(DYN_CC) -c user/du.c -o user/du.o - @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/du.o -lc - -$(FIND_ELF): user/find.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) - @$(DYN_CC) -c user/find.c -o user/find.o - @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/find.o -lc - -$(WHICH_ELF): user/which.c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) - @$(DYN_CC) -c user/which.c -o user/which.o - @$(DYN_LD) -o $@ $(ULIBC_CRT0) user/which.o -lc - -$(LDSO_ELF): user/ldso.c user/ldso_linker.ld - @$(USER_CC) -m32 -ffreestanding -fno-pie -no-pie -nostdlib -Wl,-T,user/ldso_linker.ld -o $(LDSO_ELF) user/ldso.c - -$(PIE_SO): user/pie_func.c - @$(USER_CC) -m32 -fPIC -fno-plt -c user/pie_func.c -o user/pie_func.o - @$(USER_LD) -m elf_i386 -shared -soname libpietest.so -o $(PIE_SO) user/pie_func.o - -$(PIE_ELF): user/pie_main.c user/pie_linker.ld $(PIE_SO) - @$(USER_CC) -m32 -fPIC -c user/pie_main.c -o user/pie_main.o - @$(USER_LD) -m elf_i386 -pie --dynamic-linker=/lib/ld.so -T user/pie_linker.ld -o $(PIE_ELF) user/pie_main.o $(PIE_SO) -rpath /lib - -# All dynamically-linked user commands -USER_CMDS := $(ECHO_ELF) $(SH_ELF) $(CAT_ELF) $(LS_ELF) $(MKDIR_ELF) $(RM_ELF) \ - $(CP_ELF) $(MV_ELF) $(TOUCH_ELF) $(LN_ELF) \ - $(HEAD_ELF) $(TAIL_ELF) $(WC_ELF) $(SORT_ELF) $(UNIQ_ELF) $(CUT_ELF) \ - $(CHMOD_ELF) $(CHOWN_ELF) $(CHGRP_ELF) \ - $(DATE_ELF) $(HOSTNAME_ELF) $(UPTIME_ELF) \ - $(MOUNT_ELF) $(UMOUNT_ELF) $(ENV_ELF) $(KILL_ELF) $(SLEEP_ELF) \ - $(CLEAR_ELF) $(PS_ELF) $(DF_ELF) $(FREE_ELF) $(TEE_ELF) \ - $(BASENAME_ELF) $(DIRNAME_ELF) $(RMDIR_ELF) \ - $(GREP_ELF) $(ID_ELF) $(UNAME_ELF) $(DMESG_ELF) \ - $(PRINTENV_ELF) $(TR_ELF) $(DD_ELF) $(PWD_ELF) $(STAT_ELF) \ - $(SED_ELF) $(AWK_ELF) $(WHO_ELF) $(TOP_ELF) $(DU_ELF) \ - $(FIND_ELF) $(WHICH_ELF) \ - $(INIT_ELF) - +# --- ulibc build (output into build/user/ulibc/) --- +$(ULIBC_LIB) $(ULIBC_SO): FORCE + @mkdir -p $(USER_BUILD)/ulibc + @$(MAKE) -C $(ULIBC_DIR) CC="$(USER_CC)" AS="$(USER_CC:gcc=as)" AR="$(USER_AR)" LD="$(USER_LD)" \ + libulibc.a libc.so --no-print-directory + @cp -u $(ULIBC_DIR)/libulibc.a $(ULIBC_LIB) + @cp -u $(ULIBC_DIR)/libc.so $(ULIBC_SO) +FORCE: + +# --- Special builds (fulltest, ldso, pie_test) --- +$(FULLTEST_ELF): user/cmds/fulltest/fulltest.c user/cmds/fulltest/errno.c user/linker.ld + @$(MAKE) --no-print-directory -C user/cmds/fulltest TOPDIR=$(CURDIR) USER_CC="$(USER_CC)" + +$(LDSO_ELF): user/cmds/ldso/ldso.c user/ldso_linker.ld + @$(MAKE) --no-print-directory -C user/cmds/ldso TOPDIR=$(CURDIR) USER_CC="$(USER_CC)" + +$(PIE_SO) $(PIE_ELF): user/cmds/pie_test/pie_main.c user/cmds/pie_test/pie_func.c user/pie_linker.ld + @$(MAKE) --no-print-directory -C user/cmds/pie_test TOPDIR=$(CURDIR) USER_CC="$(USER_CC)" USER_LD="$(USER_LD)" + +# --- Dynamically-linked user commands (generic rule via sub-Makefiles) --- +# Use absolute paths so they work from sub-Makefile directories +ABS_ULIBC := $(CURDIR)/$(ULIBC_DIR) +ABS_DYN_CC := $(USER_CC) -m32 -ffreestanding -nostdlib -O2 -Wall -Wextra -fPIC -fno-plt -I$(ABS_ULIBC)/include +ABS_DYN_LD := $(USER_LD) -m elf_i386 --dynamic-linker=/lib/ld.so -T $(CURDIR)/user/dyn_linker.ld -L$(ABS_ULIBC) -rpath /lib --unresolved-symbols=ignore-in-shared-libs + +# Generate build rules for each dynamically-linked command +define USER_CMD_RULE +$(USER_BUILD)/cmds/$(1)/$(1).elf: user/cmds/$(1)/$(1).c user/dyn_linker.ld $(ULIBC_SO) $(ULIBC_LIB) + @$$(MAKE) --no-print-directory -C user/cmds/$(1) TOPDIR=$$(CURDIR) \ + DYN_CC="$$(ABS_DYN_CC)" DYN_LD="$$(ABS_DYN_LD)" CRT0="$(ABS_ULIBC)/src/crt0.o" +endef +$(foreach cmd,$(USER_CMD_NAMES),$(eval $(call USER_CMD_RULE,$(cmd)))) + +# Commands that go to /bin/ in rootfs (all except init) +USER_BIN_NAMES := $(filter-out init,$(USER_CMD_NAMES)) + +# Build INITRD_FILES list: : FSTAB := rootfs/etc/fstab INITRD_FILES := $(FULLTEST_ELF):sbin/fulltest \ - $(INIT_ELF):sbin/init \ - $(ECHO_ELF):bin/echo $(SH_ELF):bin/sh $(CAT_ELF):bin/cat $(LS_ELF):bin/ls \ - $(MKDIR_ELF):bin/mkdir $(RM_ELF):bin/rm $(CP_ELF):bin/cp $(MV_ELF):bin/mv \ - $(TOUCH_ELF):bin/touch $(LN_ELF):bin/ln \ - $(HEAD_ELF):bin/head $(TAIL_ELF):bin/tail $(WC_ELF):bin/wc \ - $(SORT_ELF):bin/sort $(UNIQ_ELF):bin/uniq $(CUT_ELF):bin/cut \ - $(CHMOD_ELF):bin/chmod $(CHOWN_ELF):bin/chown $(CHGRP_ELF):bin/chgrp \ - $(DATE_ELF):bin/date $(HOSTNAME_ELF):bin/hostname $(UPTIME_ELF):bin/uptime \ - $(MOUNT_ELF):bin/mount $(UMOUNT_ELF):bin/umount $(ENV_ELF):bin/env \ - $(KILL_ELF):bin/kill $(SLEEP_ELF):bin/sleep $(CLEAR_ELF):bin/clear \ - $(PS_ELF):bin/ps $(DF_ELF):bin/df $(FREE_ELF):bin/free \ - $(TEE_ELF):bin/tee $(BASENAME_ELF):bin/basename $(DIRNAME_ELF):bin/dirname \ - $(RMDIR_ELF):bin/rmdir \ - $(GREP_ELF):bin/grep $(ID_ELF):bin/id $(UNAME_ELF):bin/uname \ - $(DMESG_ELF):bin/dmesg $(PRINTENV_ELF):bin/printenv $(TR_ELF):bin/tr \ - $(SED_ELF):bin/sed $(AWK_ELF):bin/awk $(WHO_ELF):bin/who \ - $(TOP_ELF):bin/top $(DU_ELF):bin/du $(FIND_ELF):bin/find $(WHICH_ELF):bin/which \ - $(DD_ELF):bin/dd $(PWD_ELF):bin/pwd $(STAT_ELF):bin/stat \ + $(USER_BUILD)/cmds/init/init.elf:sbin/init \ + $(foreach cmd,$(USER_BIN_NAMES),$(USER_BUILD)/cmds/$(cmd)/$(cmd).elf:bin/$(cmd)) \ $(LDSO_ELF):lib/ld.so $(ULIBC_SO):lib/libc.so \ $(PIE_SO):lib/libpietest.so $(PIE_ELF):bin/pie_test \ $(FSTAB):etc/fstab -INITRD_DEPS := $(MKINITRD) $(FULLTEST_ELF) $(USER_CMDS) $(LDSO_ELF) $(ULIBC_SO) $(PIE_SO) $(PIE_ELF) $(FSTAB) + +INITRD_DEPS := $(MKINITRD) $(FULLTEST_ELF) $(USER_CMD_ELFS) $(LDSO_ELF) $(ULIBC_SO) $(PIE_SO) $(PIE_ELF) $(FSTAB) # Include doom.elf if it has been built ifneq ($(wildcard $(DOOM_ELF)),) -INITRD_FILES += $(DOOM_ELF):bin/doom +INITRD_FILES += $(DOOM_ELF):usr/games/doom INITRD_DEPS += $(DOOM_ELF) endif @@ -637,6 +383,8 @@ $(BUILD_DIR)/%.o: $(SRC_DIR)/%.S @$(AS) $(ASFLAGS) $< -o $@ clean: - rm -rf build $(KERNEL_NAME) + rm -rf build $(KERNEL_NAME) $(INITRD_IMG) adros-*.iso + @$(MAKE) -C user/ulibc clean --no-print-directory 2>/dev/null || true + @if [ -f user/doom/Makefile ]; then $(MAKE) -C user/doom clean --no-print-directory 2>/dev/null || true; fi .PHONY: all clean iso run run-arm run-riscv run-mips cppcheck sparse analyzer check test test-1cpu test-battery test-host test-gdb test-all scan-build mkinitrd-asan diff --git a/tests/smoke_test.exp b/tests/smoke_test.exp index bb9bd90..33f1191 100755 --- a/tests/smoke_test.exp +++ b/tests/smoke_test.exp @@ -49,102 +49,102 @@ set tests { {"ATA DMA mode" "\\[ATA\\] Channel 0: DMA mode."} {"SMP CPUs active" "CPU\\(s\\) active."} {"User ring3 entry" "\\[USER\\] enter ring3"} - {"init.elf hello" "\\[init\\] hello from init.elf"} - {"open/read/close" "\\[init\\] open/read/close OK"} - {"overlay copy-up" "\\[init\\] overlay copy-up OK"} - {"lseek/stat/fstat" "\\[init\\] lseek/stat/fstat OK"} - {"dup2 restore" "\\[init\\] dup2 restore tty OK"} - {"kill SIGKILL" "\\[init\\] kill\\(SIGKILL\\) OK"} - {"poll pipe" "\\[init\\] poll\\(pipe\\) OK"} - {"select pipe" "\\[init\\] select\\(pipe\\) OK"} - {"ioctl tty" "\\[init\\] ioctl\\(/dev/tty\\) OK"} - {"job control" "\\[init\\] job control \\(SIGTTIN/SIGTTOU\\) OK"} - {"poll /dev/null" "\\[init\\] poll\\(/dev/null\\) OK"} - {"pty bidirectional" "\\[init\\] pty OK"} - {"setsid/setpgid" "\\[init\\] setsid/setpgid/getpgrp OK"} - {"sigaction SIGUSR1" "\\[init\\] sigaction/kill\\(SIGUSR1\\) OK"} - {"sigreturn" "\\[init\\] sigreturn OK"} - {"tmpfs/mount" "\\[init\\] tmpfs/mount OK"} - {"dev null" "\\[init\\] /dev/null OK"} - {"persist counter" "\\[init\\] /persist/counter="} - {"dev tty write" "\\[init\\] /dev/tty write OK"} - {"diskfs test" "\\[init\\] /disk/test prev="} - {"diskfs mkdir/unlink" "\\[init\\] diskfs mkdir/unlink OK"} - {"diskfs getdents" "\\[init\\] diskfs getdents OK"} - {"isatty" "\\[init\\] isatty OK"} - {"O_NONBLOCK" "\\[init\\] O_NONBLOCK OK"} - {"pipe2/dup3" "\\[init\\] pipe2/dup3 OK"} - {"chdir/getcwd" "\\[init\\] chdir/getcwd OK"} - {"*at syscalls" "\\[init\\] \\*at OK"} - {"rename/rmdir" "\\[init\\] rename/rmdir OK"} - {"getdents multi-fs" "\\[init\\] getdents multi-fs OK"} - {"brk heap" "\\[init\\] brk OK"} - {"mmap/munmap" "\\[init\\] mmap/munmap OK"} - {"clock_gettime" "\\[init\\] clock_gettime OK"} - {"dev zero" "\\[init\\] /dev/zero OK"} - {"dev random" "\\[init\\] /dev/random OK"} - {"procfs" "\\[init\\] procfs OK"} - {"pread/pwrite" "\\[init\\] pread/pwrite OK"} - {"ftruncate" "\\[init\\] ftruncate OK"} - {"symlink/readlink" "\\[init\\] symlink/readlink OK"} - {"access" "\\[init\\] access OK"} - {"sigprocmask" "\\[init\\] sigprocmask/sigpending OK"} - {"alarm SIGALRM" "\\[init\\] alarm/SIGALRM OK"} - {"shmget/shmat" "\\[init\\] shmget/shmat/shmdt OK"} - {"O_APPEND" "\\[init\\] O_APPEND OK"} - {"umask" "\\[init\\] umask OK"} - {"pipe capacity" "\\[init\\] pipe capacity OK"} - {"waitid" "\\[init\\] waitid OK"} - {"setitimer/getitimer" "\\[init\\] setitimer/getitimer OK"} - {"select regfile" "\\[init\\] select regfile OK"} - {"poll regfile" "\\[init\\] poll regfile OK"} - {"hard link" "\\[init\\] hard link OK"} - {"epoll" "\\[init\\] epoll OK"} - {"epollet" "\\[init\\] epollet OK"} - {"inotify" "\\[init\\] inotify OK"} - {"aio" "\\[init\\] aio OK"} - {"nanosleep" "\\[init\\] nanosleep OK"} - {"CLOCK_REALTIME" "\\[init\\] CLOCK_REALTIME OK"} - {"dev urandom" "\\[init\\] /dev/urandom OK"} - {"proc cmdline" "\\[init\\] /proc/cmdline OK"} - {"CoW fork" "\\[init\\] CoW fork OK"} - {"readv/writev" "\\[init\\] readv/writev OK"} - {"fsync" "\\[init\\] fsync OK"} - {"truncate path" "\\[init\\] truncate OK"} - {"getuid/getgid" "\\[init\\] getuid/getgid OK"} - {"chmod" "\\[init\\] chmod OK"} - {"flock" "\\[init\\] flock OK"} - {"times" "\\[init\\] times OK"} - {"gettid" "\\[init\\] gettid OK"} - {"posix_spawn" "\\[init\\] posix_spawn OK"} - {"clock_ns precision" "\\[init\\] clock_ns precision OK"} - {"getppid" "\\[init\\] getppid OK"} - {"waitpid WNOHANG" "\\[init\\] waitpid WNOHANG OK"} - {"SIGSEGV handler" "\\[init\\] SIGSEGV OK"} - {"waitpid 100 children" "\\[init\\] waitpid OK \\(100 children"} - {"lazy PLT" "\\[init\\] lazy PLT OK"} - {"PLT cached" "\\[init\\] PLT cached OK"} + {"init.elf hello" "\\[test\\] hello from init.elf"} + {"open/read/close" "\\[test\\] open/read/close OK"} + {"overlay copy-up" "\\[test\\] overlay copy-up OK"} + {"lseek/stat/fstat" "\\[test\\] lseek/stat/fstat OK"} + {"dup2 restore" "\\[test\\] dup2 restore tty OK"} + {"kill SIGKILL" "\\[test\\] kill\\(SIGKILL\\) OK"} + {"poll pipe" "\\[test\\] poll\\(pipe\\) OK"} + {"select pipe" "\\[test\\] select\\(pipe\\) OK"} + {"ioctl tty" "\\[test\\] ioctl\\(/dev/tty\\) OK"} + {"job control" "\\[test\\] job control \\(SIGTTIN/SIGTTOU\\) OK"} + {"poll /dev/null" "\\[test\\] poll\\(/dev/null\\) OK"} + {"pty bidirectional" "\\[test\\] pty OK"} + {"setsid/setpgid" "\\[test\\] setsid/setpgid/getpgrp OK"} + {"sigaction SIGUSR1" "\\[test\\] sigaction/kill\\(SIGUSR1\\) OK"} + {"sigreturn" "\\[test\\] sigreturn OK"} + {"tmpfs/mount" "\\[test\\] tmpfs/mount OK"} + {"dev null" "\\[test\\] /dev/null OK"} + {"persist counter" "\\[test\\] /persist/counter="} + {"dev tty write" "\\[test\\] /dev/tty write OK"} + {"diskfs test" "\\[test\\] /disk/test prev="} + {"diskfs mkdir/unlink" "\\[test\\] diskfs mkdir/unlink OK"} + {"diskfs getdents" "\\[test\\] diskfs getdents OK"} + {"isatty" "\\[test\\] isatty OK"} + {"O_NONBLOCK" "\\[test\\] O_NONBLOCK OK"} + {"pipe2/dup3" "\\[test\\] pipe2/dup3 OK"} + {"chdir/getcwd" "\\[test\\] chdir/getcwd OK"} + {"*at syscalls" "\\[test\\] \\*at OK"} + {"rename/rmdir" "\\[test\\] rename/rmdir OK"} + {"getdents multi-fs" "\\[test\\] getdents multi-fs OK"} + {"brk heap" "\\[test\\] brk OK"} + {"mmap/munmap" "\\[test\\] mmap/munmap OK"} + {"clock_gettime" "\\[test\\] clock_gettime OK"} + {"dev zero" "\\[test\\] /dev/zero OK"} + {"dev random" "\\[test\\] /dev/random OK"} + {"procfs" "\\[test\\] procfs OK"} + {"pread/pwrite" "\\[test\\] pread/pwrite OK"} + {"ftruncate" "\\[test\\] ftruncate OK"} + {"symlink/readlink" "\\[test\\] symlink/readlink OK"} + {"access" "\\[test\\] access OK"} + {"sigprocmask" "\\[test\\] sigprocmask/sigpending OK"} + {"alarm SIGALRM" "\\[test\\] alarm/SIGALRM OK"} + {"shmget/shmat" "\\[test\\] shmget/shmat/shmdt OK"} + {"O_APPEND" "\\[test\\] O_APPEND OK"} + {"umask" "\\[test\\] umask OK"} + {"pipe capacity" "\\[test\\] pipe capacity OK"} + {"waitid" "\\[test\\] waitid OK"} + {"setitimer/getitimer" "\\[test\\] setitimer/getitimer OK"} + {"select regfile" "\\[test\\] select regfile OK"} + {"poll regfile" "\\[test\\] poll regfile OK"} + {"hard link" "\\[test\\] hard link OK"} + {"epoll" "\\[test\\] epoll OK"} + {"epollet" "\\[test\\] epollet OK"} + {"inotify" "\\[test\\] inotify OK"} + {"aio" "\\[test\\] aio OK"} + {"nanosleep" "\\[test\\] nanosleep OK"} + {"CLOCK_REALTIME" "\\[test\\] CLOCK_REALTIME OK"} + {"dev urandom" "\\[test\\] /dev/urandom OK"} + {"proc cmdline" "\\[test\\] /proc/cmdline OK"} + {"CoW fork" "\\[test\\] CoW fork OK"} + {"readv/writev" "\\[test\\] readv/writev OK"} + {"fsync" "\\[test\\] fsync OK"} + {"truncate path" "\\[test\\] truncate OK"} + {"getuid/getgid" "\\[test\\] getuid/getgid OK"} + {"chmod" "\\[test\\] chmod OK"} + {"flock" "\\[test\\] flock OK"} + {"times" "\\[test\\] times OK"} + {"gettid" "\\[test\\] gettid OK"} + {"posix_spawn" "\\[test\\] posix_spawn OK"} + {"clock_ns precision" "\\[test\\] clock_ns precision OK"} + {"getppid" "\\[test\\] getppid OK"} + {"waitpid WNOHANG" "\\[test\\] waitpid WNOHANG OK"} + {"SIGSEGV handler" "\\[test\\] SIGSEGV OK"} + {"waitpid 100 children" "\\[test\\] waitpid OK \\(100 children"} + {"lazy PLT" "\\[test\\] lazy PLT OK"} + {"PLT cached" "\\[test\\] PLT cached OK"} {"PING network" "\\[PING\\] .*received.*network OK"} {"echo execve" "\\[echo\\] hello from echo"} - {"setuid/setgid" "\\[init\\] setuid/setgid OK"} - {"fcntl F_GETFL/SETFL" "\\[init\\] fcntl F_GETFL/F_SETFL OK"} - {"fcntl FD_CLOEXEC" "\\[init\\] fcntl FD_CLOEXEC OK"} - {"sigsuspend" "\\[init\\] sigsuspend OK"} - {"orphan reparent" "\\[init\\] orphan reparent OK"} - {"proc PID cmdline" "\\[init\\] /proc/PID/cmdline OK"} - {"proc PID status" "\\[init\\] /proc/PID/status OK"} - {"dev console" "\\[init\\] /dev/console OK"} - {"multi-pty" "\\[init\\] multi-pty OK"} - {"dup standalone" "\\[init\\] dup OK"} - {"pipe EOF" "\\[init\\] pipe EOF OK"} - {"readdir /proc" "\\[init\\] readdir /proc OK"} - {"readdir /bin" "\\[init\\] readdir /bin OK"} - {"gettimeofday" "\\[init\\] gettimeofday OK"} - {"mprotect" "\\[init\\] mprotect OK"} - {"madvise" "\\[init\\] madvise OK"} - {"getrlimit/setrlimit" "\\[init\\] getrlimit/setrlimit OK"} - {"uname" "\\[init\\] uname OK"} - {"SMP parallel fork" "\\[init\\] SMP parallel fork OK"} + {"setuid/setgid" "\\[test\\] setuid/setgid OK"} + {"fcntl F_GETFL/SETFL" "\\[test\\] fcntl F_GETFL/F_SETFL OK"} + {"fcntl FD_CLOEXEC" "\\[test\\] fcntl FD_CLOEXEC OK"} + {"sigsuspend" "\\[test\\] sigsuspend OK"} + {"orphan reparent" "\\[test\\] orphan reparent OK"} + {"proc PID cmdline" "\\[test\\] /proc/PID/cmdline OK"} + {"proc PID status" "\\[test\\] /proc/PID/status OK"} + {"dev console" "\\[test\\] /dev/console OK"} + {"multi-pty" "\\[test\\] multi-pty OK"} + {"dup standalone" "\\[test\\] dup OK"} + {"pipe EOF" "\\[test\\] pipe EOF OK"} + {"readdir /proc" "\\[test\\] readdir /proc OK"} + {"readdir /bin" "\\[test\\] readdir /bin OK"} + {"gettimeofday" "\\[test\\] gettimeofday OK"} + {"mprotect" "\\[test\\] mprotect OK"} + {"madvise" "\\[test\\] madvise OK"} + {"getrlimit/setrlimit" "\\[test\\] getrlimit/setrlimit OK"} + {"uname" "\\[test\\] uname OK"} + {"SMP parallel fork" "\\[test\\] SMP parallel fork OK"} {"LZ4 Frame decomp" "\\[INITRD\\] LZ4"} } diff --git a/tests/test_battery.exp b/tests/test_battery.exp index b475d39..bc997c4 100644 --- a/tests/test_battery.exp +++ b/tests/test_battery.exp @@ -153,8 +153,8 @@ set patterns { {"ATA /dev/hda" "\\[ATA\\] /dev/hda detected"} {"INITRD found" "\\[INITRD\\] Found"} {"diskfs mount /disk" "\\[MOUNT\\] diskfs on /dev/hda"} - {"diskfs test" "\\[init\\] /disk/test prev="} - {"diskfs getdents" "\\[init\\] diskfs getdents OK"} + {"diskfs test" "\\[test\\] /disk/test prev="} + {"diskfs getdents" "\\[test\\] diskfs getdents OK"} } set res [wait_for_patterns $serial_log $timeout_sec $patterns] diff --git a/tests/test_host_utils.sh b/tests/test_host_utils.sh index 52bfd6b..7ed2c00 100755 --- a/tests/test_host_utils.sh +++ b/tests/test_host_utils.sh @@ -40,7 +40,7 @@ echo "" # ---------- echo ---------- echo "--- echo ---" -if compile echo_test user/echo.c; then +if compile echo_test user/cmds/echo/echo.c; then out=$("$BUILDDIR/echo_test" hello world) [ "$out" = "hello world" ] && pass "echo basic" || fail "echo basic" "got: $out" @@ -59,7 +59,7 @@ fi # ---------- cat ---------- echo "--- cat ---" -if compile cat_test user/cat.c; then +if compile cat_test user/cmds/cat/cat.c; then echo "hello cat" > "$BUILDDIR/cat_in.txt" out=$("$BUILDDIR/cat_test" "$BUILDDIR/cat_in.txt") [ "$out" = "hello cat" ] && pass "cat file" || fail "cat file" "got: $out" @@ -78,7 +78,7 @@ fi # ---------- head ---------- echo "--- head ---" -if compile head_test user/head.c; then +if compile head_test user/cmds/head/head.c; then printf "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n" > "$BUILDDIR/head_in.txt" out=$("$BUILDDIR/head_test" "$BUILDDIR/head_in.txt" | wc -l) [ "$out" -eq 10 ] && pass "head default 10" || fail "head default 10" "got $out lines" @@ -96,7 +96,7 @@ fi # ---------- tail ---------- echo "--- tail ---" -if compile tail_test user/tail.c; then +if compile tail_test user/cmds/tail/tail.c; then printf "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n" > "$BUILDDIR/tail_in.txt" out=$("$BUILDDIR/tail_test" -n 3 "$BUILDDIR/tail_in.txt") expected=$(printf "10\n11\n12") @@ -107,7 +107,7 @@ fi # ---------- wc ---------- echo "--- wc ---" -if compile wc_test user/wc.c; then +if compile wc_test user/cmds/wc/wc.c; then printf "hello world\nfoo bar baz\n" > "$BUILDDIR/wc_in.txt" out=$("$BUILDDIR/wc_test" "$BUILDDIR/wc_in.txt") # Should contain line count (2), word count (5), byte count @@ -119,7 +119,7 @@ fi # ---------- sort ---------- echo "--- sort ---" -if compile sort_test user/sort.c; then +if compile sort_test user/cmds/sort/sort.c; then printf "banana\napple\ncherry\n" | "$BUILDDIR/sort_test" > "$BUILDDIR/sort_out.txt" expected=$(printf "apple\nbanana\ncherry") out=$(cat "$BUILDDIR/sort_out.txt") @@ -135,7 +135,7 @@ fi # ---------- uniq ---------- echo "--- uniq ---" -if compile uniq_test user/uniq.c; then +if compile uniq_test user/cmds/uniq/uniq.c; then printf "aaa\naaa\nbbb\nccc\nccc\n" | "$BUILDDIR/uniq_test" > "$BUILDDIR/uniq_out.txt" expected=$(printf "aaa\nbbb\nccc") out=$(cat "$BUILDDIR/uniq_out.txt") @@ -150,7 +150,7 @@ fi # ---------- cut ---------- echo "--- cut ---" -if compile cut_test user/cut.c; then +if compile cut_test user/cmds/cut/cut.c; then out=$(printf "a:b:c\n" | "$BUILDDIR/cut_test" -d: -f2) [ "$out" = "b" ] && pass "cut -d: -f2" || fail "cut -d: -f2" "got: $out" @@ -165,7 +165,7 @@ fi # ---------- grep ---------- echo "--- grep ---" -if compile grep_test user/grep.c; then +if compile grep_test user/cmds/grep/grep.c; then printf "hello world\nfoo bar\nhello again\n" > "$BUILDDIR/grep_in.txt" out=$("$BUILDDIR/grep_test" hello "$BUILDDIR/grep_in.txt") lines=$(echo "$out" | wc -l) @@ -188,7 +188,7 @@ fi # ---------- tr ---------- echo "--- tr ---" -if compile tr_test user/tr.c; then +if compile tr_test user/cmds/tr/tr.c; then out=$(echo "hello" | "$BUILDDIR/tr_test" 'elo' 'ELO') [ "$out" = "hELLO" ] && pass "tr translate" || fail "tr translate" "got: $out" @@ -200,7 +200,7 @@ fi # ---------- basename ---------- echo "--- basename ---" -if compile basename_test user/basename.c; then +if compile basename_test user/cmds/basename/basename.c; then out=$("$BUILDDIR/basename_test" /usr/bin/foo) [ "$out" = "foo" ] && pass "basename path" || fail "basename path" "got: $out" @@ -215,7 +215,7 @@ fi # ---------- dirname ---------- echo "--- dirname ---" -if compile dirname_test user/dirname.c; then +if compile dirname_test user/cmds/dirname/dirname.c; then out=$("$BUILDDIR/dirname_test" /usr/bin/foo) [ "$out" = "/usr/bin" ] && pass "dirname path" || fail "dirname path" "got: $out" @@ -230,7 +230,7 @@ fi # ---------- tee ---------- echo "--- tee ---" -if compile tee_test user/tee.c; then +if compile tee_test user/cmds/tee/tee.c; then out=$(echo "tee test" | "$BUILDDIR/tee_test" "$BUILDDIR/tee_out.txt") file_content=$(cat "$BUILDDIR/tee_out.txt") [ "$out" = "tee test" ] && pass "tee stdout" || fail "tee stdout" "got: $out" @@ -247,7 +247,7 @@ fi # ---------- dd ---------- echo "--- dd ---" -if compile dd_test user/dd.c; then +if compile dd_test user/cmds/dd/dd.c; then echo "hello dd test data" > "$BUILDDIR/dd_in.txt" "$BUILDDIR/dd_test" if="$BUILDDIR/dd_in.txt" of="$BUILDDIR/dd_out.txt" bs=512 2>/dev/null out=$(cat "$BUILDDIR/dd_out.txt") @@ -258,7 +258,7 @@ fi # ---------- pwd ---------- echo "--- pwd ---" -if compile pwd_test user/pwd.c; then +if compile pwd_test user/cmds/pwd/pwd.c; then out=$("$BUILDDIR/pwd_test") expected=$(pwd) [ -n "$out" ] && pass "pwd output" || fail "pwd output" "empty" @@ -268,7 +268,7 @@ fi # ---------- uname ---------- echo "--- uname ---" -if compile uname_test user/uname.c; then +if compile uname_test user/cmds/uname/uname.c; then out=$("$BUILDDIR/uname_test") [ "$out" = "AdrOS" ] && pass "uname default" || fail "uname default" "got: $out" @@ -287,7 +287,7 @@ fi # ---------- id ---------- echo "--- id ---" -if compile id_test user/id.c; then +if compile id_test user/cmds/id/id.c; then out=$("$BUILDDIR/id_test") echo "$out" | grep -q "uid=" && pass "id uid" || fail "id uid" "got: $out" echo "$out" | grep -q "gid=" && pass "id gid" || fail "id gid" "got: $out" @@ -297,7 +297,7 @@ fi # ---------- printenv ---------- echo "--- printenv ---" -if compile printenv_test user/printenv.c; then +if compile printenv_test user/cmds/printenv/printenv.c; then out=$(HOME=/test/home "$BUILDDIR/printenv_test" HOME) [ "$out" = "/test/home" ] && pass "printenv HOME" || fail "printenv HOME" "got: $out" @@ -313,7 +313,7 @@ fi # ---------- cp ---------- echo "--- cp ---" -if compile cp_test user/cp.c; then +if compile cp_test user/cmds/cp/cp.c; then echo "cp source" > "$BUILDDIR/cp_src.txt" "$BUILDDIR/cp_test" "$BUILDDIR/cp_src.txt" "$BUILDDIR/cp_dst.txt" out=$(cat "$BUILDDIR/cp_dst.txt") @@ -324,7 +324,7 @@ fi # ---------- mv ---------- echo "--- mv ---" -if compile mv_test user/mv.c; then +if compile mv_test user/cmds/mv/mv.c; then echo "mv data" > "$BUILDDIR/mv_src.txt" "$BUILDDIR/mv_test" "$BUILDDIR/mv_src.txt" "$BUILDDIR/mv_dst.txt" [ ! -f "$BUILDDIR/mv_src.txt" ] && pass "mv src removed" || fail "mv src removed" "still exists" @@ -337,10 +337,10 @@ fi # ---------- touch/rm/mkdir/rmdir ---------- echo "--- touch/rm/mkdir/rmdir ---" compile_ok=1 -compile touch_test user/touch.c || compile_ok=0 -compile rm_test user/rm.c || compile_ok=0 -compile mkdir_test user/mkdir.c || compile_ok=0 -compile rmdir_test user/rmdir.c || compile_ok=0 +compile touch_test user/cmds/touch/touch.c || compile_ok=0 +compile rm_test user/cmds/rm/rm.c || compile_ok=0 +compile mkdir_test user/cmds/mkdir/mkdir.c || compile_ok=0 +compile rmdir_test user/cmds/rmdir/rmdir.c || compile_ok=0 if [ "$compile_ok" -eq 1 ]; then "$BUILDDIR/touch_test" "$BUILDDIR/touchfile" [ -f "$BUILDDIR/touchfile" ] && pass "touch create" || fail "touch create" "not created" @@ -359,7 +359,7 @@ fi # ---------- ln ---------- echo "--- ln ---" -if compile ln_test user/ln.c; then +if compile ln_test user/cmds/ln/ln.c; then echo "link target" > "$BUILDDIR/ln_src.txt" "$BUILDDIR/ln_test" -s "$BUILDDIR/ln_src.txt" "$BUILDDIR/ln_link.txt" 2>/dev/null || true if [ -L "$BUILDDIR/ln_link.txt" ]; then @@ -380,7 +380,7 @@ fi # ---------- sed ---------- echo "--- sed ---" -if compile sed_test user/sed.c; then +if compile sed_test user/cmds/sed/sed.c; then out=$(echo "hello world" | "$BUILDDIR/sed_test" 's/world/earth/') [ "$out" = "hello earth" ] && pass "sed s///" || fail "sed s///" "got: $out" @@ -400,7 +400,7 @@ fi # ---------- awk ---------- echo "--- awk ---" -if compile awk_test user/awk.c; then +if compile awk_test user/cmds/awk/awk.c; then out=$(echo "hello world foo" | "$BUILDDIR/awk_test" '{print $2}') [ "$out" = "world" ] && pass "awk print \$2" || fail "awk print \$2" "got: $out" @@ -416,7 +416,7 @@ fi # ---------- who ---------- echo "--- who ---" -if compile who_test user/who.c; then +if compile who_test user/cmds/who/who.c; then out=$("$BUILDDIR/who_test") echo "$out" | grep -q "root" && pass "who output" || fail "who output" "got: $out" else @@ -425,7 +425,7 @@ fi # ---------- find ---------- echo "--- find ---" -if compile find_test user/find.c; then +if compile find_test user/cmds/find/find.c; then mkdir -p "$BUILDDIR/findtest/sub" touch "$BUILDDIR/findtest/a.txt" touch "$BUILDDIR/findtest/b.c" @@ -443,10 +443,10 @@ fi # ---------- which ---------- echo "--- which ---" -if compile which_test user/which.c; then +if compile which_test user/cmds/which/which.c; then # which looks in /bin and /sbin hardcoded, so just check it runs - "$BUILDDIR/which_test" nonexistent_cmd > /dev/null 2>&1 - [ $? -ne 0 ] && pass "which not found" || fail "which not found" "should return nonzero" + "$BUILDDIR/which_test" nonexistent_cmd > /dev/null 2>&1 || rc=$? + [ "${rc:-1}" -ne 0 ] && pass "which not found" || fail "which not found" "should return nonzero" else skip "which (compile failed)" fi diff --git a/user/awk.c b/user/awk.c deleted file mode 100644 index 15be1cb..0000000 --- a/user/awk.c +++ /dev/null @@ -1,116 +0,0 @@ -/* AdrOS awk utility — minimal: print fields, pattern matching, BEGIN/END */ -#include -#include -#include -#include -#include - -static char delim = ' '; -static int print_field = -1; /* -1 = whole line */ -static char pattern[256] = ""; -static int has_pattern = 0; - -static void process_line(const char* line) { - if (has_pattern && !strstr(line, pattern)) return; - - if (print_field < 0) { - printf("%s\n", line); - return; - } - - /* Split into fields */ - char copy[4096]; - strncpy(copy, line, sizeof(copy) - 1); - copy[sizeof(copy) - 1] = '\0'; - - int fi = 0; - char* p = copy; - while (*p) { - while (*p && (*p == delim || *p == '\t')) p++; - if (!*p) break; - char* start = p; - while (*p && *p != delim && *p != '\t') p++; - if (*p) *p++ = '\0'; - if (fi == print_field) { - printf("%s\n", start); - return; - } - fi++; - } - printf("\n"); -} - -int main(int argc, char** argv) { - if (argc < 2) { - fprintf(stderr, "Usage: awk [-F sep] '{print $N}' [file]\n"); - return 1; - } - - int argi = 1; - if (strcmp(argv[argi], "-F") == 0 && argi + 1 < argc) { - delim = argv[argi + 1][0]; - argi += 2; - } - - if (argi >= argc) { - fprintf(stderr, "awk: missing program\n"); - return 1; - } - - /* Parse simple program: {print $N} or /pattern/{print $N} */ - const char* prog = argv[argi++]; - - /* Check for /pattern/ prefix */ - if (prog[0] == '/') { - const char* end = strchr(prog + 1, '/'); - if (end) { - int plen = (int)(end - prog - 1); - if (plen > 0 && plen < (int)sizeof(pattern)) { - memcpy(pattern, prog + 1, plen); - pattern[plen] = '\0'; - has_pattern = 1; - } - prog = end + 1; - } - } - - /* Parse {print $N} */ - const char* pp = strstr(prog, "print"); - if (pp) { - const char* dollar = strchr(pp, '$'); - if (dollar) { - int n = atoi(dollar + 1); - print_field = (n > 0) ? n - 1 : -1; - if (n == 0) print_field = -1; /* $0 = whole line */ - } - } - - int fd = STDIN_FILENO; - if (argi < argc) { - fd = open(argv[argi], O_RDONLY); - if (fd < 0) { - fprintf(stderr, "awk: %s: No such file or directory\n", argv[argi]); - return 1; - } - } - - char line[4096]; - int li = 0; - char c; - while (read(fd, &c, 1) == 1) { - if (c == '\n') { - line[li] = '\0'; - process_line(line); - li = 0; - } else if (li < (int)sizeof(line) - 1) { - line[li++] = c; - } - } - if (li > 0) { - line[li] = '\0'; - process_line(line); - } - - if (fd != STDIN_FILENO) close(fd); - return 0; -} diff --git a/user/basename.c b/user/basename.c deleted file mode 100644 index 8089c29..0000000 --- a/user/basename.c +++ /dev/null @@ -1,28 +0,0 @@ -/* AdrOS basename utility — strip directory from filename */ -#include -#include - -int main(int argc, char** argv) { - if (argc <= 1) { - fprintf(stderr, "usage: basename PATH [SUFFIX]\n"); - return 1; - } - char* p = argv[1]; - /* Remove trailing slashes */ - int len = (int)strlen(p); - while (len > 1 && p[len - 1] == '/') p[--len] = '\0'; - /* Find last slash */ - char* base = p; - for (char* s = p; *s; s++) { - if (*s == '/' && *(s + 1)) base = s + 1; - } - /* Strip suffix if provided */ - if (argc > 2) { - int blen = (int)strlen(base); - int slen = (int)strlen(argv[2]); - if (blen > slen && strcmp(base + blen - slen, argv[2]) == 0) - base[blen - slen] = '\0'; - } - printf("%s\n", base); - return 0; -} diff --git a/user/cat.c b/user/cat.c deleted file mode 100644 index ad166e2..0000000 --- a/user/cat.c +++ /dev/null @@ -1,38 +0,0 @@ -/* AdrOS cat utility */ -#include -#include -#include -#include -#include - -static void cat_fd(int fd) { - char buf[4096]; - int r; - while ((r = read(fd, buf, sizeof(buf))) > 0) { - write(STDOUT_FILENO, buf, (size_t)r); - } -} - -int main(int argc, char** argv) { - if (argc <= 1) { - cat_fd(STDIN_FILENO); - return 0; - } - - int rc = 0; - for (int i = 1; i < argc; i++) { - if (strcmp(argv[i], "-") == 0) { - cat_fd(STDIN_FILENO); - continue; - } - int fd = open(argv[i], O_RDONLY); - if (fd < 0) { - fprintf(stderr, "cat: %s: No such file or directory\n", argv[i]); - rc = 1; - continue; - } - cat_fd(fd); - close(fd); - } - return rc; -} diff --git a/user/chgrp.c b/user/chgrp.c deleted file mode 100644 index f74bac6..0000000 --- a/user/chgrp.c +++ /dev/null @@ -1,22 +0,0 @@ -/* AdrOS chgrp utility */ -#include -#include -#include - -int main(int argc, char** argv) { - if (argc < 3) { - fprintf(stderr, "Usage: chgrp ...\n"); - return 1; - } - - int group = atoi(argv[1]); - int rc = 0; - - for (int i = 2; i < argc; i++) { - if (chown(argv[i], -1, group) < 0) { - fprintf(stderr, "chgrp: cannot change group of '%s'\n", argv[i]); - rc = 1; - } - } - return rc; -} diff --git a/user/chmod.c b/user/chmod.c deleted file mode 100644 index 0f6c791..0000000 --- a/user/chmod.c +++ /dev/null @@ -1,23 +0,0 @@ -/* AdrOS chmod utility */ -#include -#include -#include -#include - -int main(int argc, char** argv) { - if (argc < 3) { - fprintf(stderr, "Usage: chmod ...\n"); - return 1; - } - - int mode = (int)strtol(argv[1], NULL, 8); - int rc = 0; - - for (int i = 2; i < argc; i++) { - if (chmod(argv[i], mode) < 0) { - fprintf(stderr, "chmod: cannot change mode of '%s'\n", argv[i]); - rc = 1; - } - } - return rc; -} diff --git a/user/chown.c b/user/chown.c deleted file mode 100644 index c2d6c9d..0000000 --- a/user/chown.c +++ /dev/null @@ -1,32 +0,0 @@ -/* AdrOS chown utility */ -#include -#include -#include -#include - -int main(int argc, char** argv) { - if (argc < 3) { - fprintf(stderr, "Usage: chown ...\n"); - return 1; - } - - /* Parse owner:group */ - int owner = -1, group = -1; - char* colon = strchr(argv[1], ':'); - if (colon) { - *colon = '\0'; - if (argv[1][0]) owner = atoi(argv[1]); - if (colon[1]) group = atoi(colon + 1); - } else { - owner = atoi(argv[1]); - } - - int rc = 0; - for (int i = 2; i < argc; i++) { - if (chown(argv[i], owner, group) < 0) { - fprintf(stderr, "chown: cannot change owner of '%s'\n", argv[i]); - rc = 1; - } - } - return rc; -} diff --git a/user/clear.c b/user/clear.c deleted file mode 100644 index 619feb4..0000000 --- a/user/clear.c +++ /dev/null @@ -1,8 +0,0 @@ -/* AdrOS clear utility — clear the terminal screen */ -#include - -int main(void) { - /* ANSI escape: clear screen + move cursor to top-left */ - write(STDOUT_FILENO, "\033[2J\033[H", 7); - return 0; -} diff --git a/user/cmds/awk/Makefile b/user/cmds/awk/Makefile new file mode 100644 index 0000000..a8e832a --- /dev/null +++ b/user/cmds/awk/Makefile @@ -0,0 +1,3 @@ +NAME := awk +SRCS := awk.c +include ../common.mk diff --git a/user/cmds/awk/awk.c b/user/cmds/awk/awk.c new file mode 100644 index 0000000..15be1cb --- /dev/null +++ b/user/cmds/awk/awk.c @@ -0,0 +1,116 @@ +/* AdrOS awk utility — minimal: print fields, pattern matching, BEGIN/END */ +#include +#include +#include +#include +#include + +static char delim = ' '; +static int print_field = -1; /* -1 = whole line */ +static char pattern[256] = ""; +static int has_pattern = 0; + +static void process_line(const char* line) { + if (has_pattern && !strstr(line, pattern)) return; + + if (print_field < 0) { + printf("%s\n", line); + return; + } + + /* Split into fields */ + char copy[4096]; + strncpy(copy, line, sizeof(copy) - 1); + copy[sizeof(copy) - 1] = '\0'; + + int fi = 0; + char* p = copy; + while (*p) { + while (*p && (*p == delim || *p == '\t')) p++; + if (!*p) break; + char* start = p; + while (*p && *p != delim && *p != '\t') p++; + if (*p) *p++ = '\0'; + if (fi == print_field) { + printf("%s\n", start); + return; + } + fi++; + } + printf("\n"); +} + +int main(int argc, char** argv) { + if (argc < 2) { + fprintf(stderr, "Usage: awk [-F sep] '{print $N}' [file]\n"); + return 1; + } + + int argi = 1; + if (strcmp(argv[argi], "-F") == 0 && argi + 1 < argc) { + delim = argv[argi + 1][0]; + argi += 2; + } + + if (argi >= argc) { + fprintf(stderr, "awk: missing program\n"); + return 1; + } + + /* Parse simple program: {print $N} or /pattern/{print $N} */ + const char* prog = argv[argi++]; + + /* Check for /pattern/ prefix */ + if (prog[0] == '/') { + const char* end = strchr(prog + 1, '/'); + if (end) { + int plen = (int)(end - prog - 1); + if (plen > 0 && plen < (int)sizeof(pattern)) { + memcpy(pattern, prog + 1, plen); + pattern[plen] = '\0'; + has_pattern = 1; + } + prog = end + 1; + } + } + + /* Parse {print $N} */ + const char* pp = strstr(prog, "print"); + if (pp) { + const char* dollar = strchr(pp, '$'); + if (dollar) { + int n = atoi(dollar + 1); + print_field = (n > 0) ? n - 1 : -1; + if (n == 0) print_field = -1; /* $0 = whole line */ + } + } + + int fd = STDIN_FILENO; + if (argi < argc) { + fd = open(argv[argi], O_RDONLY); + if (fd < 0) { + fprintf(stderr, "awk: %s: No such file or directory\n", argv[argi]); + return 1; + } + } + + char line[4096]; + int li = 0; + char c; + while (read(fd, &c, 1) == 1) { + if (c == '\n') { + line[li] = '\0'; + process_line(line); + li = 0; + } else if (li < (int)sizeof(line) - 1) { + line[li++] = c; + } + } + if (li > 0) { + line[li] = '\0'; + process_line(line); + } + + if (fd != STDIN_FILENO) close(fd); + return 0; +} diff --git a/user/cmds/basename/Makefile b/user/cmds/basename/Makefile new file mode 100644 index 0000000..ae20228 --- /dev/null +++ b/user/cmds/basename/Makefile @@ -0,0 +1,3 @@ +NAME := basename +SRCS := basename.c +include ../common.mk diff --git a/user/cmds/basename/basename.c b/user/cmds/basename/basename.c new file mode 100644 index 0000000..8089c29 --- /dev/null +++ b/user/cmds/basename/basename.c @@ -0,0 +1,28 @@ +/* AdrOS basename utility — strip directory from filename */ +#include +#include + +int main(int argc, char** argv) { + if (argc <= 1) { + fprintf(stderr, "usage: basename PATH [SUFFIX]\n"); + return 1; + } + char* p = argv[1]; + /* Remove trailing slashes */ + int len = (int)strlen(p); + while (len > 1 && p[len - 1] == '/') p[--len] = '\0'; + /* Find last slash */ + char* base = p; + for (char* s = p; *s; s++) { + if (*s == '/' && *(s + 1)) base = s + 1; + } + /* Strip suffix if provided */ + if (argc > 2) { + int blen = (int)strlen(base); + int slen = (int)strlen(argv[2]); + if (blen > slen && strcmp(base + blen - slen, argv[2]) == 0) + base[blen - slen] = '\0'; + } + printf("%s\n", base); + return 0; +} diff --git a/user/cmds/cat/Makefile b/user/cmds/cat/Makefile new file mode 100644 index 0000000..9df8bae --- /dev/null +++ b/user/cmds/cat/Makefile @@ -0,0 +1,3 @@ +NAME := cat +SRCS := cat.c +include ../common.mk diff --git a/user/cmds/cat/cat.c b/user/cmds/cat/cat.c new file mode 100644 index 0000000..ad166e2 --- /dev/null +++ b/user/cmds/cat/cat.c @@ -0,0 +1,38 @@ +/* AdrOS cat utility */ +#include +#include +#include +#include +#include + +static void cat_fd(int fd) { + char buf[4096]; + int r; + while ((r = read(fd, buf, sizeof(buf))) > 0) { + write(STDOUT_FILENO, buf, (size_t)r); + } +} + +int main(int argc, char** argv) { + if (argc <= 1) { + cat_fd(STDIN_FILENO); + return 0; + } + + int rc = 0; + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "-") == 0) { + cat_fd(STDIN_FILENO); + continue; + } + int fd = open(argv[i], O_RDONLY); + if (fd < 0) { + fprintf(stderr, "cat: %s: No such file or directory\n", argv[i]); + rc = 1; + continue; + } + cat_fd(fd); + close(fd); + } + return rc; +} diff --git a/user/cmds/chgrp/Makefile b/user/cmds/chgrp/Makefile new file mode 100644 index 0000000..489f82f --- /dev/null +++ b/user/cmds/chgrp/Makefile @@ -0,0 +1,3 @@ +NAME := chgrp +SRCS := chgrp.c +include ../common.mk diff --git a/user/cmds/chgrp/chgrp.c b/user/cmds/chgrp/chgrp.c new file mode 100644 index 0000000..f74bac6 --- /dev/null +++ b/user/cmds/chgrp/chgrp.c @@ -0,0 +1,22 @@ +/* AdrOS chgrp utility */ +#include +#include +#include + +int main(int argc, char** argv) { + if (argc < 3) { + fprintf(stderr, "Usage: chgrp ...\n"); + return 1; + } + + int group = atoi(argv[1]); + int rc = 0; + + for (int i = 2; i < argc; i++) { + if (chown(argv[i], -1, group) < 0) { + fprintf(stderr, "chgrp: cannot change group of '%s'\n", argv[i]); + rc = 1; + } + } + return rc; +} diff --git a/user/cmds/chmod/Makefile b/user/cmds/chmod/Makefile new file mode 100644 index 0000000..6bc44d8 --- /dev/null +++ b/user/cmds/chmod/Makefile @@ -0,0 +1,3 @@ +NAME := chmod +SRCS := chmod.c +include ../common.mk diff --git a/user/cmds/chmod/chmod.c b/user/cmds/chmod/chmod.c new file mode 100644 index 0000000..0f6c791 --- /dev/null +++ b/user/cmds/chmod/chmod.c @@ -0,0 +1,23 @@ +/* AdrOS chmod utility */ +#include +#include +#include +#include + +int main(int argc, char** argv) { + if (argc < 3) { + fprintf(stderr, "Usage: chmod ...\n"); + return 1; + } + + int mode = (int)strtol(argv[1], NULL, 8); + int rc = 0; + + for (int i = 2; i < argc; i++) { + if (chmod(argv[i], mode) < 0) { + fprintf(stderr, "chmod: cannot change mode of '%s'\n", argv[i]); + rc = 1; + } + } + return rc; +} diff --git a/user/cmds/chown/Makefile b/user/cmds/chown/Makefile new file mode 100644 index 0000000..8378c53 --- /dev/null +++ b/user/cmds/chown/Makefile @@ -0,0 +1,3 @@ +NAME := chown +SRCS := chown.c +include ../common.mk diff --git a/user/cmds/chown/chown.c b/user/cmds/chown/chown.c new file mode 100644 index 0000000..c2d6c9d --- /dev/null +++ b/user/cmds/chown/chown.c @@ -0,0 +1,32 @@ +/* AdrOS chown utility */ +#include +#include +#include +#include + +int main(int argc, char** argv) { + if (argc < 3) { + fprintf(stderr, "Usage: chown ...\n"); + return 1; + } + + /* Parse owner:group */ + int owner = -1, group = -1; + char* colon = strchr(argv[1], ':'); + if (colon) { + *colon = '\0'; + if (argv[1][0]) owner = atoi(argv[1]); + if (colon[1]) group = atoi(colon + 1); + } else { + owner = atoi(argv[1]); + } + + int rc = 0; + for (int i = 2; i < argc; i++) { + if (chown(argv[i], owner, group) < 0) { + fprintf(stderr, "chown: cannot change owner of '%s'\n", argv[i]); + rc = 1; + } + } + return rc; +} diff --git a/user/cmds/clear/Makefile b/user/cmds/clear/Makefile new file mode 100644 index 0000000..043a0be --- /dev/null +++ b/user/cmds/clear/Makefile @@ -0,0 +1,3 @@ +NAME := clear +SRCS := clear.c +include ../common.mk diff --git a/user/cmds/clear/clear.c b/user/cmds/clear/clear.c new file mode 100644 index 0000000..619feb4 --- /dev/null +++ b/user/cmds/clear/clear.c @@ -0,0 +1,8 @@ +/* AdrOS clear utility — clear the terminal screen */ +#include + +int main(void) { + /* ANSI escape: clear screen + move cursor to top-left */ + write(STDOUT_FILENO, "\033[2J\033[H", 7); + return 0; +} diff --git a/user/cmds/common.mk b/user/cmds/common.mk new file mode 100644 index 0000000..55e047b --- /dev/null +++ b/user/cmds/common.mk @@ -0,0 +1,34 @@ +# Common build rules for AdrOS user commands (dynamically linked) +# Each program Makefile sets NAME and SRCS, then includes this file. +# +# Usage from per-program Makefile: +# NAME := echo +# SRCS := echo.c +# include ../common.mk + +TOPDIR ?= $(abspath ../../..) +BUILDDIR := $(TOPDIR)/build/user/cmds/$(NAME) + +ULIBC_DIR := $(TOPDIR)/user/ulibc +DYN_CC ?= i686-elf-gcc -m32 -ffreestanding -nostdlib -O2 -Wall -Wextra -fPIC -fno-plt -I$(ULIBC_DIR)/include +DYN_LD ?= i686-elf-ld -m elf_i386 --dynamic-linker=/lib/ld.so -T $(TOPDIR)/user/dyn_linker.ld -L$(ULIBC_DIR) -rpath /lib +CRT0 ?= $(ULIBC_DIR)/src/crt0.o + +OBJS := $(addprefix $(BUILDDIR)/,$(SRCS:.c=.o)) +ELF := $(BUILDDIR)/$(NAME).elf + +all: $(ELF) + +$(ELF): $(OBJS) + @echo " LD $@" + @$(DYN_LD) -o $@ $(CRT0) $(OBJS) -lc + +$(BUILDDIR)/%.o: %.c + @mkdir -p $(BUILDDIR) + @echo " CC $<" + @$(DYN_CC) -c $< -o $@ + +clean: + rm -f $(OBJS) $(ELF) + +.PHONY: all clean diff --git a/user/cmds/cp/Makefile b/user/cmds/cp/Makefile new file mode 100644 index 0000000..564ddfd --- /dev/null +++ b/user/cmds/cp/Makefile @@ -0,0 +1,3 @@ +NAME := cp +SRCS := cp.c +include ../common.mk diff --git a/user/cmds/cp/cp.c b/user/cmds/cp/cp.c new file mode 100644 index 0000000..16fcb52 --- /dev/null +++ b/user/cmds/cp/cp.c @@ -0,0 +1,41 @@ +/* AdrOS cp utility */ +#include +#include +#include +#include + +int main(int argc, char** argv) { + if (argc < 3) { + fprintf(stderr, "Usage: cp \n"); + return 1; + } + + int src = open(argv[1], O_RDONLY); + if (src < 0) { + fprintf(stderr, "cp: cannot open '%s'\n", argv[1]); + return 1; + } + + int dst = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (dst < 0) { + fprintf(stderr, "cp: cannot create '%s'\n", argv[2]); + close(src); + return 1; + } + + char buf[4096]; + int r; + while ((r = read(src, buf, sizeof(buf))) > 0) { + int w = write(dst, buf, (size_t)r); + if (w != r) { + fprintf(stderr, "cp: write error\n"); + close(src); + close(dst); + return 1; + } + } + + close(src); + close(dst); + return 0; +} diff --git a/user/cmds/cut/Makefile b/user/cmds/cut/Makefile new file mode 100644 index 0000000..ec93ea3 --- /dev/null +++ b/user/cmds/cut/Makefile @@ -0,0 +1,3 @@ +NAME := cut +SRCS := cut.c +include ../common.mk diff --git a/user/cmds/cut/cut.c b/user/cmds/cut/cut.c new file mode 100644 index 0000000..47d4d5d --- /dev/null +++ b/user/cmds/cut/cut.c @@ -0,0 +1,110 @@ +/* AdrOS cut utility */ +#include +#include +#include +#include +#include + +#define LINE_MAX 1024 + +static char delim = '\t'; +static int fields[32]; +static int nfields = 0; + +static void parse_fields(const char* spec) { + const char* p = spec; + while (*p && nfields < 32) { + fields[nfields++] = atoi(p); + while (*p && *p != ',') p++; + if (*p == ',') p++; + } +} + +static void cut_line(char* line) { + if (nfields == 0) { + printf("%s\n", line); + return; + } + + /* Split line into fields */ + char* flds[64]; + int nf = 0; + char* p = line; + flds[nf++] = p; + while (*p && nf < 64) { + if (*p == delim) { + *p = '\0'; + flds[nf++] = p + 1; + } + p++; + } + + /* Print requested fields */ + int first = 1; + for (int i = 0; i < nfields; i++) { + int idx = fields[i] - 1; /* 1-based */ + if (idx >= 0 && idx < nf) { + if (!first) write(STDOUT_FILENO, &delim, 1); + printf("%s", flds[idx]); + first = 0; + } + } + printf("\n"); +} + +static void cut_fd(int fd) { + char line[LINE_MAX]; + int pos = 0; + char c; + + while (read(fd, &c, 1) > 0) { + if (c == '\n') { + line[pos] = '\0'; + cut_line(line); + pos = 0; + } else if (pos < LINE_MAX - 1) { + line[pos++] = c; + } + } + if (pos > 0) { + line[pos] = '\0'; + cut_line(line); + } +} + +int main(int argc, char** argv) { + int start = 1; + + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "-d") == 0 && i + 1 < argc) { + delim = argv[++i][0]; + start = i + 1; + } else if (strncmp(argv[i], "-d", 2) == 0 && argv[i][2] != '\0') { + delim = argv[i][2]; + start = i + 1; + } else if (strcmp(argv[i], "-f") == 0 && i + 1 < argc) { + parse_fields(argv[++i]); + start = i + 1; + } else if (strncmp(argv[i], "-f", 2) == 0 && argv[i][2] != '\0') { + parse_fields(argv[i] + 2); + start = i + 1; + } else if (argv[i][0] != '-') { + break; + } + } + + if (start >= argc) { + cut_fd(STDIN_FILENO); + } else { + for (int i = start; i < argc; i++) { + int fd = open(argv[i], O_RDONLY); + if (fd < 0) { + fprintf(stderr, "cut: cannot open '%s'\n", argv[i]); + continue; + } + cut_fd(fd); + close(fd); + } + } + return 0; +} diff --git a/user/cmds/date/Makefile b/user/cmds/date/Makefile new file mode 100644 index 0000000..85b48d2 --- /dev/null +++ b/user/cmds/date/Makefile @@ -0,0 +1,3 @@ +NAME := date +SRCS := date.c +include ../common.mk diff --git a/user/cmds/date/date.c b/user/cmds/date/date.c new file mode 100644 index 0000000..859986c --- /dev/null +++ b/user/cmds/date/date.c @@ -0,0 +1,24 @@ +/* AdrOS date utility */ +#include +#include + +int main(int argc, char** argv) { + (void)argc; (void)argv; + struct timespec ts; + if (clock_gettime(CLOCK_REALTIME, &ts) < 0) { + fprintf(stderr, "date: cannot get time\n"); + return 1; + } + + /* Simple epoch seconds display — no timezone or strftime yet */ + unsigned long sec = ts.tv_sec; + unsigned long days = sec / 86400; + unsigned long rem = sec % 86400; + unsigned long hours = rem / 3600; + unsigned long mins = (rem % 3600) / 60; + unsigned long secs = rem % 60; + + printf("%lu days since epoch, %02lu:%02lu:%02lu UTC\n", + days, hours, mins, secs); + return 0; +} diff --git a/user/cmds/dd/Makefile b/user/cmds/dd/Makefile new file mode 100644 index 0000000..d23eaf8 --- /dev/null +++ b/user/cmds/dd/Makefile @@ -0,0 +1,3 @@ +NAME := dd +SRCS := dd.c +include ../common.mk diff --git a/user/cmds/dd/dd.c b/user/cmds/dd/dd.c new file mode 100644 index 0000000..6e0eccc --- /dev/null +++ b/user/cmds/dd/dd.c @@ -0,0 +1,63 @@ +/* AdrOS dd utility — convert and copy a file */ +#include +#include +#include +#include +#include + +static int parse_size(const char* s) { + int v = atoi(s); + int len = (int)strlen(s); + if (len > 0) { + char suf = s[len - 1]; + if (suf == 'k' || suf == 'K') v *= 1024; + else if (suf == 'm' || suf == 'M') v *= 1024 * 1024; + } + return v; +} + +int main(int argc, char** argv) { + const char* inf = NULL; + const char* outf = NULL; + int bs = 512; + int count = -1; + + for (int i = 1; i < argc; i++) { + if (strncmp(argv[i], "if=", 3) == 0) inf = argv[i] + 3; + else if (strncmp(argv[i], "of=", 3) == 0) outf = argv[i] + 3; + else if (strncmp(argv[i], "bs=", 3) == 0) bs = parse_size(argv[i] + 3); + else if (strncmp(argv[i], "count=", 6) == 0) count = atoi(argv[i] + 6); + } + + int ifd = STDIN_FILENO; + int ofd = STDOUT_FILENO; + + if (inf) { + ifd = open(inf, O_RDONLY); + if (ifd < 0) { fprintf(stderr, "dd: cannot open '%s'\n", inf); return 1; } + } + if (outf) { + ofd = open(outf, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (ofd < 0) { fprintf(stderr, "dd: cannot open '%s'\n", outf); return 1; } + } + + if (bs > 4096) bs = 4096; + char buf[4096]; + int blocks = 0, partial = 0, total = 0; + + while (count < 0 || blocks + partial < count) { + int n = read(ifd, buf, (size_t)bs); + if (n <= 0) break; + write(ofd, buf, (size_t)n); + total += n; + if (n == bs) blocks++; + else partial++; + } + + fprintf(stderr, "%d+%d records in\n%d+%d records out\n%d bytes copied\n", + blocks, partial, blocks, partial, total); + + if (inf) close(ifd); + if (outf) close(ofd); + return 0; +} diff --git a/user/cmds/df/Makefile b/user/cmds/df/Makefile new file mode 100644 index 0000000..10ee75b --- /dev/null +++ b/user/cmds/df/Makefile @@ -0,0 +1,3 @@ +NAME := df +SRCS := df.c +include ../common.mk diff --git a/user/cmds/df/df.c b/user/cmds/df/df.c new file mode 100644 index 0000000..81d0a87 --- /dev/null +++ b/user/cmds/df/df.c @@ -0,0 +1,10 @@ +/* AdrOS df utility — display filesystem disk space usage */ +#include + +int main(void) { + printf("Filesystem Size Used Avail Use%% Mounted on\n"); + printf("overlayfs - - - - /\n"); + printf("devfs - - - - /dev\n"); + printf("procfs - - - - /proc\n"); + return 0; +} diff --git a/user/cmds/dirname/Makefile b/user/cmds/dirname/Makefile new file mode 100644 index 0000000..95f2fcd --- /dev/null +++ b/user/cmds/dirname/Makefile @@ -0,0 +1,3 @@ +NAME := dirname +SRCS := dirname.c +include ../common.mk diff --git a/user/cmds/dirname/dirname.c b/user/cmds/dirname/dirname.c new file mode 100644 index 0000000..15e518d --- /dev/null +++ b/user/cmds/dirname/dirname.c @@ -0,0 +1,22 @@ +/* AdrOS dirname utility — strip last component from path */ +#include +#include + +int main(int argc, char** argv) { + if (argc <= 1) { + fprintf(stderr, "usage: dirname PATH\n"); + return 1; + } + char* p = argv[1]; + int len = (int)strlen(p); + /* Remove trailing slashes */ + while (len > 1 && p[len - 1] == '/') len--; + /* Find last slash */ + while (len > 0 && p[len - 1] != '/') len--; + /* Remove trailing slashes from result */ + while (len > 1 && p[len - 1] == '/') len--; + if (len == 0) { printf(".\n"); return 0; } + p[len] = '\0'; + printf("%s\n", p); + return 0; +} diff --git a/user/cmds/dmesg/Makefile b/user/cmds/dmesg/Makefile new file mode 100644 index 0000000..92bebf5 --- /dev/null +++ b/user/cmds/dmesg/Makefile @@ -0,0 +1,3 @@ +NAME := dmesg +SRCS := dmesg.c +include ../common.mk diff --git a/user/cmds/dmesg/dmesg.c b/user/cmds/dmesg/dmesg.c new file mode 100644 index 0000000..b2aacc4 --- /dev/null +++ b/user/cmds/dmesg/dmesg.c @@ -0,0 +1,18 @@ +/* AdrOS dmesg utility — print kernel ring buffer from /proc/dmesg */ +#include +#include +#include + +int main(void) { + int fd = open("/proc/dmesg", O_RDONLY); + if (fd < 0) { + fprintf(stderr, "dmesg: cannot open /proc/dmesg\n"); + return 1; + } + char buf[512]; + int n; + while ((n = read(fd, buf, sizeof(buf))) > 0) + write(STDOUT_FILENO, buf, (size_t)n); + close(fd); + return 0; +} diff --git a/user/cmds/du/Makefile b/user/cmds/du/Makefile new file mode 100644 index 0000000..5a0ed2c --- /dev/null +++ b/user/cmds/du/Makefile @@ -0,0 +1,3 @@ +NAME := du +SRCS := du.c +include ../common.mk diff --git a/user/cmds/du/du.c b/user/cmds/du/du.c new file mode 100644 index 0000000..8fd15c5 --- /dev/null +++ b/user/cmds/du/du.c @@ -0,0 +1,73 @@ +/* AdrOS du utility — estimate file space usage */ +#include +#include +#include +#include +#include +#include + +static int sflag = 0; /* -s: summary only */ + +static long du_path(const char* path, int print) { + struct stat st; + if (stat(path, &st) < 0) { + fprintf(stderr, "du: cannot access '%s'\n", path); + return 0; + } + + if (!(st.st_mode & 0040000)) { + long blocks = (st.st_size + 511) / 512; + if (print && !sflag) printf("%ld\t%s\n", blocks, path); + return blocks; + } + + int fd = open(path, O_RDONLY); + if (fd < 0) return 0; + + long total = 0; + char buf[2048]; + int rc; + while ((rc = getdents(fd, buf, sizeof(buf))) > 0) { + int off = 0; + while (off < rc) { + struct dirent* d = (struct dirent*)(buf + off); + if (d->d_reclen == 0) break; + if (strcmp(d->d_name, ".") != 0 && strcmp(d->d_name, "..") != 0) { + char child[512]; + if (path[strlen(path)-1] == '/') + snprintf(child, sizeof(child), "%s%s", path, d->d_name); + else + snprintf(child, sizeof(child), "%s/%s", path, d->d_name); + total += du_path(child, print); + } + off += d->d_reclen; + } + } + close(fd); + + if (print && !sflag) printf("%ld\t%s\n", total, path); + return total; +} + +int main(int argc, char** argv) { + int argi = 1; + while (argi < argc && argv[argi][0] == '-') { + const char* f = argv[argi] + 1; + while (*f) { + if (*f == 's') sflag = 1; + f++; + } + argi++; + } + + if (argi >= argc) { + long total = du_path(".", 1); + if (sflag) printf("%ld\t.\n", total); + } else { + for (int i = argi; i < argc; i++) { + long total = du_path(argv[i], 1); + if (sflag) printf("%ld\t%s\n", total, argv[i]); + } + } + return 0; +} diff --git a/user/cmds/echo/Makefile b/user/cmds/echo/Makefile new file mode 100644 index 0000000..3fef539 --- /dev/null +++ b/user/cmds/echo/Makefile @@ -0,0 +1,3 @@ +NAME := echo +SRCS := echo.c +include ../common.mk diff --git a/user/cmds/echo/echo.c b/user/cmds/echo/echo.c new file mode 100644 index 0000000..0e4496a --- /dev/null +++ b/user/cmds/echo/echo.c @@ -0,0 +1,64 @@ +/* AdrOS echo utility — POSIX-compatible */ +#include +#include +#include + +int main(int argc, char** argv) { + int nflag = 0; /* -n: no trailing newline */ + int eflag = 0; /* -e: interpret escape sequences */ + int i = 1; + + /* Parse flags */ + while (i < argc && argv[i][0] == '-' && argv[i][1] != '\0') { + const char* f = argv[i] + 1; + int valid = 1; + int n = 0, e = 0; + while (*f) { + if (*f == 'n') n = 1; + else if (*f == 'e') e = 1; + else if (*f == 'E') { /* no escapes — default */ } + else { valid = 0; break; } + f++; + } + if (!valid) break; + if (n) nflag = 1; + if (e) eflag = 1; + i++; + } + + int first = 1; + for (; i < argc; i++) { + if (!first) + write(STDOUT_FILENO, " ", 1); + first = 0; + + const char* s = argv[i]; + if (eflag) { + while (*s) { + if (*s == '\\' && s[1]) { + s++; + char c = *s; + switch (c) { + case 'n': write(STDOUT_FILENO, "\n", 1); break; + case 't': write(STDOUT_FILENO, "\t", 1); break; + case '\\': write(STDOUT_FILENO, "\\", 1); break; + case 'r': write(STDOUT_FILENO, "\r", 1); break; + case 'a': write(STDOUT_FILENO, "\a", 1); break; + default: write(STDOUT_FILENO, "\\", 1); + write(STDOUT_FILENO, &c, 1); break; + } + } else { + write(STDOUT_FILENO, s, 1); + } + s++; + } + } else { + write(STDOUT_FILENO, s, strlen(s)); + } + } + + if (!nflag) + write(STDOUT_FILENO, "\n", 1); + + return 0; +} diff --git a/user/cmds/env/Makefile b/user/cmds/env/Makefile new file mode 100644 index 0000000..c20284b --- /dev/null +++ b/user/cmds/env/Makefile @@ -0,0 +1,3 @@ +NAME := env +SRCS := env.c +include ../common.mk diff --git a/user/cmds/env/env.c b/user/cmds/env/env.c new file mode 100644 index 0000000..a7d727a --- /dev/null +++ b/user/cmds/env/env.c @@ -0,0 +1,21 @@ +/* AdrOS env utility — print environment or run command with modified env */ +#include +#include +#include + +extern char** __environ; + +int main(int argc, char** argv) { + if (argc <= 1) { + /* Print all environment variables */ + if (__environ) { + for (int i = 0; __environ[i]; i++) + printf("%s\n", __environ[i]); + } + return 0; + } + /* env COMMAND ARGS... — run command with current environment */ + execve(argv[1], (const char* const*)&argv[1], (const char* const*)__environ); + fprintf(stderr, "env: %s: not found\n", argv[1]); + return 127; +} diff --git a/user/cmds/find/Makefile b/user/cmds/find/Makefile new file mode 100644 index 0000000..f0a4194 --- /dev/null +++ b/user/cmds/find/Makefile @@ -0,0 +1,3 @@ +NAME := find +SRCS := find.c +include ../common.mk diff --git a/user/cmds/find/find.c b/user/cmds/find/find.c new file mode 100644 index 0000000..feb63b3 --- /dev/null +++ b/user/cmds/find/find.c @@ -0,0 +1,96 @@ +/* AdrOS find utility — search for files in directory hierarchy */ +#include +#include +#include + +#ifndef DT_DIR +#define DT_DIR 4 +#endif + +static const char* name_pattern = NULL; +static int type_filter = 0; /* 0=any, 'f'=file, 'd'=dir */ + +static int match_name(const char* name) { + if (!name_pattern) return 1; + /* Simple wildcard: *pattern* if pattern has no special chars, + or exact match. Support leading/trailing * only. */ + int plen = (int)strlen(name_pattern); + if (plen == 0) return 1; + + const char* pat = name_pattern; + int lead_star = (pat[0] == '*'); + int trail_star = (plen > 1 && pat[plen-1] == '*'); + + if (lead_star && trail_star) { + char sub[256]; + int slen = plen - 2; + if (slen <= 0) return 1; + memcpy(sub, pat + 1, (size_t)slen); + sub[slen] = '\0'; + return strstr(name, sub) != NULL; + } + if (lead_star) { + const char* suffix = pat + 1; + int slen = plen - 1; + int nlen = (int)strlen(name); + if (nlen < slen) return 0; + return strcmp(name + nlen - slen, suffix) == 0; + } + if (trail_star) { + return strncmp(name, pat, (size_t)(plen - 1)) == 0; + } + return strcmp(name, pat) == 0; +} + +static void find_recurse(const char* path) { + DIR* dir = opendir(path); + if (!dir) return; + + struct dirent* d; + while ((d = readdir(dir)) != NULL) { + if (strcmp(d->d_name, ".") == 0 || strcmp(d->d_name, "..") == 0) + continue; + + char child[512]; + size_t plen = strlen(path); + if (plen > 0 && path[plen - 1] == '/') + snprintf(child, sizeof(child), "%s%s", path, d->d_name); + else + snprintf(child, sizeof(child), "%s/%s", path, d->d_name); + + int is_dir = (d->d_type == DT_DIR); + int show = match_name(d->d_name); + if (show && type_filter) { + if (type_filter == 'f' && is_dir) show = 0; + if (type_filter == 'd' && !is_dir) show = 0; + } + if (show) printf("%s\n", child); + + if (is_dir) { + find_recurse(child); + } + } + closedir(dir); +} + +int main(int argc, char** argv) { + const char* start = "."; + int argi = 1; + + if (argi < argc && argv[argi][0] != '-') { + start = argv[argi++]; + } + + while (argi < argc) { + if (strcmp(argv[argi], "-name") == 0 && argi + 1 < argc) { + name_pattern = argv[++argi]; + } else if (strcmp(argv[argi], "-type") == 0 && argi + 1 < argc) { + type_filter = argv[++argi][0]; + } + argi++; + } + + printf("%s\n", start); + find_recurse(start); + return 0; +} diff --git a/user/cmds/free/Makefile b/user/cmds/free/Makefile new file mode 100644 index 0000000..0282294 --- /dev/null +++ b/user/cmds/free/Makefile @@ -0,0 +1,3 @@ +NAME := free +SRCS := free.c +include ../common.mk diff --git a/user/cmds/free/free.c b/user/cmds/free/free.c new file mode 100644 index 0000000..b07a8bc --- /dev/null +++ b/user/cmds/free/free.c @@ -0,0 +1,17 @@ +/* AdrOS free utility — display memory usage from /proc/meminfo */ +#include +#include +#include + +int main(void) { + int fd = open("/proc/meminfo", O_RDONLY); + if (fd >= 0) { + char buf[512]; + int n = read(fd, buf, sizeof(buf) - 1); + if (n > 0) { buf[n] = '\0'; printf("%s", buf); } + close(fd); + } else { + printf("free: /proc/meminfo not available\n"); + } + return 0; +} diff --git a/user/cmds/fulltest/Makefile b/user/cmds/fulltest/Makefile new file mode 100644 index 0000000..ddeeb20 --- /dev/null +++ b/user/cmds/fulltest/Makefile @@ -0,0 +1,21 @@ +# fulltest — statically linked smoke test binary +NAME := fulltest + +TOPDIR ?= $(abspath ../../..) +BUILDDIR := $(TOPDIR)/build/user/cmds/$(NAME) +USER_CC ?= i686-elf-gcc + +ELF := $(BUILDDIR)/fulltest.elf + +all: $(ELF) + +$(ELF): fulltest.c errno.c user_errno.h + @mkdir -p $(BUILDDIR) + @echo " CC+LD $@" + @$(USER_CC) -m32 -I $(TOPDIR)/include -ffreestanding -fno-pie -no-pie -nostdlib \ + -Wl,-T,$(TOPDIR)/user/linker.ld -o $@ fulltest.c errno.c + +clean: + rm -f $(ELF) + +.PHONY: all clean diff --git a/user/cmds/fulltest/errno.c b/user/cmds/fulltest/errno.c new file mode 100644 index 0000000..fbc6e36 --- /dev/null +++ b/user/cmds/fulltest/errno.c @@ -0,0 +1,4 @@ +/* Per-process errno: each fork()ed process gets its own copy in its + address space. When true threads (clone) are added, this must become + __thread int errno or use a TLS segment (GS/FS). */ +int errno = 0; diff --git a/user/cmds/fulltest/fulltest.c b/user/cmds/fulltest/fulltest.c new file mode 100644 index 0000000..b5b1e27 --- /dev/null +++ b/user/cmds/fulltest/fulltest.c @@ -0,0 +1,4464 @@ +#include + +#ifdef SIGKILL +#undef SIGKILL +#endif +#ifdef SIGUSR1 +#undef SIGUSR1 +#endif +#ifdef SIGSEGV +#undef SIGSEGV +#endif +#ifdef SIGTTIN +#undef SIGTTIN +#endif +#ifdef SIGTTOU +#undef SIGTTOU +#endif + +#ifdef WNOHANG +#undef WNOHANG +#endif +#ifdef SEEK_SET +#undef SEEK_SET +#endif +#ifdef SEEK_CUR +#undef SEEK_CUR +#endif +#ifdef SEEK_END +#undef SEEK_END +#endif + +#include "user_errno.h" + +#include "signal.h" + +enum { + SYSCALL_WRITE = 1, + SYSCALL_EXIT = 2, + SYSCALL_GETPID = 3, + SYSCALL_OPEN = 4, + SYSCALL_READ = 5, + SYSCALL_CLOSE = 6, + SYSCALL_WAITPID = 7, + SYSCALL_LSEEK = 9, + SYSCALL_FSTAT = 10, + SYSCALL_STAT = 11, + + SYSCALL_DUP = 12, + SYSCALL_DUP2 = 13, + SYSCALL_PIPE = 14, + SYSCALL_PIPE2 = 34, + SYSCALL_EXECVE = 15, + SYSCALL_FORK = 16, + SYSCALL_GETPPID = 17, + SYSCALL_POLL = 18, + SYSCALL_KILL = 19, + SYSCALL_SELECT = 20, + SYSCALL_IOCTL = 21, + SYSCALL_SETSID = 22, + SYSCALL_SETPGID = 23, + SYSCALL_GETPGRP = 24, + + SYSCALL_SIGACTION = 25, + SYSCALL_SIGPROCMASK = 26, + SYSCALL_SIGRETURN = 27, + + SYSCALL_MKDIR = 28, + SYSCALL_UNLINK = 29, + + SYSCALL_GETDENTS = 30, + + SYSCALL_FCNTL = 31, + + SYSCALL_CHDIR = 32, + SYSCALL_GETCWD = 33, + SYSCALL_DUP3 = 35, + + SYSCALL_OPENAT = 36, + SYSCALL_FSTATAT = 37, + SYSCALL_UNLINKAT = 38, + + SYSCALL_RENAME = 39, + SYSCALL_RMDIR = 40, + + SYSCALL_BRK = 41, + SYSCALL_NANOSLEEP = 42, + SYSCALL_CLOCK_GETTIME = 43, + SYSCALL_MMAP = 44, + SYSCALL_MUNMAP = 45, + + SYSCALL_SHMGET = 46, + SYSCALL_SHMAT = 47, + SYSCALL_SHMDT = 48, + + SYSCALL_LINK = 54, + SYSCALL_SYMLINK = 55, + SYSCALL_READLINK = 56, + + SYSCALL_SIGPENDING = 71, + SYSCALL_PREAD = 72, + SYSCALL_PWRITE = 73, + SYSCALL_ACCESS = 74, + SYSCALL_TRUNCATE = 78, + SYSCALL_FTRUNCATE = 79, + SYSCALL_UMASK = 75, + SYSCALL_ALARM = 83, + SYSCALL_SETITIMER = 92, + SYSCALL_GETITIMER = 93, + SYSCALL_WAITID = 94, + + SYSCALL_EPOLL_CREATE = 112, + SYSCALL_EPOLL_CTL = 113, + SYSCALL_EPOLL_WAIT = 114, + + SYSCALL_INOTIFY_INIT = 115, + SYSCALL_INOTIFY_ADD_WATCH = 116, + SYSCALL_INOTIFY_RM_WATCH = 117, + + SYSCALL_AIO_READ = 121, + SYSCALL_AIO_WRITE = 122, + SYSCALL_AIO_ERROR = 123, + SYSCALL_AIO_RETURN = 124, + + SYSCALL_CHMOD = 50, + SYSCALL_CHOWN = 51, + SYSCALL_GETUID = 52, + SYSCALL_GETGID = 53, + SYSCALL_CLONE = 67, + SYSCALL_GETTID = 68, + SYSCALL_FSYNC = 69, + SYSCALL_READV = 81, + SYSCALL_WRITEV = 82, + SYSCALL_TIMES = 84, + SYSCALL_FUTEX = 85, + SYSCALL_FLOCK = 87, + SYSCALL_GETEUID = 88, + SYSCALL_GETEGID = 89, + SYSCALL_SETEUID = 90, + SYSCALL_SETEGID = 91, + SYSCALL_SIGSUSPEND = 80, + SYSCALL_SIGQUEUE = 95, + SYSCALL_POSIX_SPAWN = 96, + SYSCALL_SETUID = 76, + SYSCALL_SETGID = 77, + SYSCALL_MOUNT = 126, + SYSCALL_GETTIMEOFDAY = 127, + SYSCALL_MPROTECT = 128, + SYSCALL_GETRLIMIT = 129, + SYSCALL_SETRLIMIT = 130, + SYSCALL_UNAME = 136, + SYSCALL_MADVISE = 140, +}; + +enum { + AT_FDCWD = -100, +}; + +enum { + F_GETFD = 1, + F_SETFD = 2, + F_GETFL = 3, + F_SETFL = 4, + F_GETPIPE_SZ = 1032, + F_SETPIPE_SZ = 1033, + FD_CLOEXEC = 1, +}; + +enum { + O_CLOEXEC = 0x80000, +}; + +enum { + TCGETS = 0x5401, + TCSETS = 0x5402, + TIOCGPGRP = 0x540F, + TIOCSPGRP = 0x5410, +}; + +enum { + ENOTTY = 25, +}; + +enum { + ICANON = 0x0002, + ECHO = 0x0008, +}; + +#define USER_NCCS 11 + +struct termios { + uint32_t c_iflag; + uint32_t c_oflag; + uint32_t c_cflag; + uint32_t c_lflag; + uint8_t c_cc[USER_NCCS]; +}; + +struct pollfd { + int fd; + int16_t events; + int16_t revents; +}; + +enum { + POLLIN = 0x0001, + POLLOUT = 0x0004, + EPOLLET = (1U << 31), +}; + +enum { + SIGKILL = 9, + SIGUSR1 = 10, + SIGSEGV = 11, + SIGTTIN = 21, + SIGTTOU = 22, +}; + +enum { + WNOHANG = 1, +}; + +enum { + SEEK_SET = 0, + SEEK_CUR = 1, + SEEK_END = 2, +}; + +enum { + O_CREAT = 0x40, + O_TRUNC = 0x200, + O_NONBLOCK = 0x800, + O_APPEND = 0x400, + O_RDWR = 0x02, +}; + +enum { + PROT_READ = 0x1, + PROT_WRITE = 0x2, +}; + +enum { + MAP_PRIVATE = 0x02, + MAP_ANONYMOUS = 0x20, + MAP_FAILED_VAL = 0xFFFFFFFF, +}; + +enum { + CLOCK_REALTIME = 0, + CLOCK_MONOTONIC = 1, +}; + +struct timespec { + uint32_t tv_sec; + uint32_t tv_nsec; +}; + +enum { + R_OK = 4, + W_OK = 2, + F_OK = 0, +}; + +enum { + SIG_BLOCK = 0, + SIG_UNBLOCK = 1, + SIG_SETMASK = 2, +}; + +enum { + IPC_CREAT = 01000, + IPC_PRIVATE = 0, +}; + +enum { + SIGALRM = 14, +}; + +enum { + EAGAIN = 11, + EINVAL = 22, + EEXIST = 17, +}; + +enum { + ITIMER_REAL = 0, + ITIMER_VIRTUAL = 1, + ITIMER_PROF = 2, +}; + +struct timeval { + uint32_t tv_sec; + uint32_t tv_usec; +}; + +struct rlimit { + uint32_t rlim_cur; + uint32_t rlim_max; +}; + +enum { + RLIMIT_CPU = 0, + RLIMIT_FSIZE = 1, + RLIMIT_DATA = 2, + RLIMIT_STACK = 3, + RLIMIT_CORE = 4, + RLIMIT_NOFILE = 5, + RLIMIT_AS = 6, + RLIMIT_NPROC = 7, + RLIM_INFINITY = 0xFFFFFFFFU, +}; + +struct itimerval { + struct timeval it_interval; + struct timeval it_value; +}; + +enum { + P_ALL = 0, + P_PID = 1, + P_PGID = 2, + WEXITED = 4, +}; + +#define S_IFMT 0170000 +#define S_IFREG 0100000 + +struct stat { + uint32_t st_ino; + uint32_t st_mode; + uint32_t st_nlink; + uint32_t st_uid; + uint32_t st_gid; + uint32_t st_size; +}; + +static int sys_write(int fd, const void* buf, uint32_t len) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_WRITE), "b"(fd), "c"(buf), "d"(len) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_openat(int dirfd, const char* path, uint32_t flags, uint32_t mode) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_OPENAT), "b"(dirfd), "c"(path), "d"(flags), "S"(mode) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_fstatat(int dirfd, const char* path, struct stat* st, uint32_t flags) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_FSTATAT), "b"(dirfd), "c"(path), "d"(st), "S"(flags) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_unlinkat(int dirfd, const char* path, uint32_t flags) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_UNLINKAT), "b"(dirfd), "c"(path), "d"(flags) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_rename(const char* oldpath, const char* newpath) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_RENAME), "b"(oldpath), "c"(newpath) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_rmdir(const char* path) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_RMDIR), "b"(path) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_pipe2(int fds[2], uint32_t flags) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_PIPE2), "b"(fds), "c"(flags) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_chdir(const char* path) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_CHDIR), "b"(path) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_getcwd(char* buf, uint32_t size) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_GETCWD), "b"(buf), "c"(size) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_fcntl(int fd, int cmd, uint32_t arg) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_FCNTL), "b"(fd), "c"(cmd), "d"(arg) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_ioctl(int fd, uint32_t cmd, void* arg); + +static int isatty_fd(int fd) { + struct termios t; + if (sys_ioctl(fd, TCGETS, &t) < 0) { + if (errno == ENOTTY) return 0; + return -1; + } + return 1; +} + +static int sys_getdents(int fd, void* buf, uint32_t len) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_GETDENTS), "b"(fd), "c"(buf), "d"(len) + : "memory" + ); + return __syscall_fix(ret); +} + +static void write_int_dec(int v) { + char buf[16]; + int i = 0; + if (v == 0) { + buf[i++] = '0'; + } else { + int neg = 0; + if (v < 0) { + neg = 1; + v = -v; + } + while (v > 0 && i < (int)sizeof(buf)) { + buf[i++] = (char)('0' + (v % 10)); + v /= 10; + } + if (neg && i < (int)sizeof(buf)) { + buf[i++] = '-'; + } + for (int j = 0; j < i / 2; j++) { + char t = buf[j]; + buf[j] = buf[i - 1 - j]; + buf[i - 1 - j] = t; + } + } + (void)sys_write(1, buf, (uint32_t)i); +} + +static void write_hex8(uint8_t v) { + static const char hex[] = "0123456789ABCDEF"; + char b[2]; + b[0] = hex[(v >> 4) & 0xF]; + b[1] = hex[v & 0xF]; + (void)sys_write(1, b, 2); +} + +static void write_hex32(uint32_t v) { + static const char hex[] = "0123456789ABCDEF"; + char b[8]; + for (int i = 0; i < 8; i++) { + uint32_t shift = (uint32_t)(28 - 4 * i); + b[i] = hex[(v >> shift) & 0xFU]; + } + (void)sys_write(1, b, 8); +} + +static int memeq(const void* a, const void* b, uint32_t n) { + const uint8_t* x = (const uint8_t*)a; + const uint8_t* y = (const uint8_t*)b; + for (uint32_t i = 0; i < n; i++) { + if (x[i] != y[i]) return 0; + } + return 1; +} + +static int streq(const char* a, const char* b) { + if (!a || !b) return 0; + uint32_t i = 0; + while (a[i] != 0 && b[i] != 0) { + if (a[i] != b[i]) return 0; + i++; + } + return a[i] == b[i]; +} + +static int sys_sigaction2(int sig, const struct sigaction* act, struct sigaction* oldact) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_SIGACTION), "b"(sig), "c"(act), "d"(oldact) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_sigaction(int sig, void (*handler)(int), uintptr_t* old_out) { + struct sigaction act; + act.sa_handler = (uintptr_t)handler; + act.sa_sigaction = 0; + act.sa_mask = 0; + act.sa_flags = 0; + + struct sigaction oldact; + struct sigaction* oldp = old_out ? &oldact : 0; + + int r = sys_sigaction2(sig, &act, oldp); + if (r < 0) return r; + if (old_out) { + *old_out = oldact.sa_handler; + } + return 0; +} + +static int sys_select(uint32_t nfds, uint64_t* readfds, uint64_t* writefds, uint64_t* exceptfds, int32_t timeout) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_SELECT), "b"(nfds), "c"(readfds), "d"(writefds), "S"(exceptfds), "D"(timeout) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_ioctl(int fd, uint32_t cmd, void* arg) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_IOCTL), "b"(fd), "c"(cmd), "d"(arg) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_kill(int pid, int sig) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_KILL), "b"(pid), "c"(sig) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_poll(struct pollfd* fds, uint32_t nfds, int32_t timeout) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_POLL), "b"(fds), "c"(nfds), "d"(timeout) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_setsid(void) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_SETSID) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_setpgid(int pid, int pgid) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_SETPGID), "b"(pid), "c"(pgid) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_getpgrp(void) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_GETPGRP) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_getpid(void) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_GETPID) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_getppid(void) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_GETPPID) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_fork(void) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_FORK) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_execve(const char* path, const char* const* argv, const char* const* envp) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_EXECVE), "b"(path), "c"(argv), "d"(envp) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_pipe(int fds[2]) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_PIPE), "b"(fds) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_dup(int oldfd) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_DUP), "b"(oldfd) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_dup2(int oldfd, int newfd) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_DUP2), "b"(oldfd), "c"(newfd) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_dup3(int oldfd, int newfd, uint32_t flags) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_DUP3), "b"(oldfd), "c"(newfd), "d"(flags) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_waitpid(int pid, int* status, uint32_t options) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_WAITPID), "b"(pid), "c"(status), "d"(options) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_open(const char* path, uint32_t flags) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_OPEN), "b"(path), "c"(flags) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_mkdir(const char* path) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_MKDIR), "b"(path) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_unlink(const char* path) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_UNLINK), "b"(path) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_read(int fd, void* buf, uint32_t len) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_READ), "b"(fd), "c"(buf), "d"(len) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_close(int fd) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_CLOSE), "b"(fd) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_lseek(int fd, int32_t offset, int whence) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_LSEEK), "b"(fd), "c"(offset), "d"(whence) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_fstat(int fd, struct stat* st) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_FSTAT), "b"(fd), "c"(st) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_stat(const char* path, struct stat* st) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_STAT), "b"(path), "c"(st) + : "memory" + ); + return __syscall_fix(ret); +} + +static uintptr_t sys_brk(uintptr_t addr) { + uintptr_t ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_BRK), "b"(addr) + : "memory" + ); + return ret; +} + +static uintptr_t sys_mmap(uintptr_t addr, uint32_t len, uint32_t prot, uint32_t flags, int fd) { + uintptr_t ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_MMAP), "b"(addr), "c"(len), "d"(prot), "S"(flags), "D"(fd) + : "memory" + ); + return ret; +} + +static int sys_munmap(uintptr_t addr, uint32_t len) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_MUNMAP), "b"(addr), "c"(len) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_clock_gettime(uint32_t clk_id, struct timespec* tp) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_CLOCK_GETTIME), "b"(clk_id), "c"(tp) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_pread(int fd, void* buf, uint32_t count, uint32_t offset) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_PREAD), "b"(fd), "c"(buf), "d"(count), "S"(offset) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_pwrite(int fd, const void* buf, uint32_t count, uint32_t offset) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_PWRITE), "b"(fd), "c"(buf), "d"(count), "S"(offset) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_ftruncate(int fd, uint32_t length) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_FTRUNCATE), "b"(fd), "c"(length) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_symlink(const char* target, const char* linkpath) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_SYMLINK), "b"(target), "c"(linkpath) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_readlink(const char* path, char* buf, uint32_t bufsiz) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_READLINK), "b"(path), "c"(buf), "d"(bufsiz) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_access(const char* path, uint32_t mode) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_ACCESS), "b"(path), "c"(mode) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_sigprocmask(int how, uint32_t mask, uint32_t* oldset) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_SIGPROCMASK), "b"(how), "c"(mask), "d"(oldset) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_sigpending(uint32_t* set) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_SIGPENDING), "b"(set) + : "memory" + ); + return __syscall_fix(ret); +} + +static uint32_t sys_alarm(uint32_t seconds) { + uint32_t ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_ALARM), "b"(seconds) + : "memory" + ); + return ret; +} + +static int sys_shmget(uint32_t key, uint32_t size, uint32_t flags) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_SHMGET), "b"(key), "c"(size), "d"(flags) + : "memory" + ); + return __syscall_fix(ret); +} + +static uintptr_t sys_shmat(int shmid, uintptr_t addr, uint32_t flags) { + uintptr_t ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_SHMAT), "b"(shmid), "c"(addr), "d"(flags) + : "memory" + ); + return ret; +} + +static int sys_shmdt(uintptr_t addr) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_SHMDT), "b"(addr) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_link(const char* oldpath, const char* newpath) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_LINK), "b"(oldpath), "c"(newpath) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_umask(uint32_t mask) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_UMASK), "b"(mask) + : "memory" + ); + return ret; +} + +static int sys_setitimer(int which, const struct itimerval* newval, struct itimerval* oldval) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_SETITIMER), "b"(which), "c"(newval), "d"(oldval) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_getitimer(int which, struct itimerval* cur) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_GETITIMER), "b"(which), "c"(cur) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_waitid(uint32_t idtype, uint32_t id, void* infop, uint32_t options) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_WAITID), "b"(idtype), "c"(id), "d"(infop), "S"(options) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_epoll_create(int size) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_EPOLL_CREATE), "b"(size) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_epoll_ctl(int epfd, int op, int fd, void* event) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_EPOLL_CTL), "b"(epfd), "c"(op), "d"(fd), "S"(event) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_epoll_wait(int epfd, void* events, int maxevents, int timeout) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_EPOLL_WAIT), "b"(epfd), "c"(events), "d"(maxevents), "S"(timeout) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_inotify_init(void) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_INOTIFY_INIT) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_inotify_add_watch(int fd, const char* path, uint32_t mask) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_INOTIFY_ADD_WATCH), "b"(fd), "c"(path), "d"(mask) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_inotify_rm_watch(int fd, int wd) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_INOTIFY_RM_WATCH), "b"(fd), "c"(wd) + : "memory" + ); + return __syscall_fix(ret); +} + +struct aiocb { + int aio_fildes; + void* aio_buf; + uint32_t aio_nbytes; + uint32_t aio_offset; + int32_t aio_error; + int32_t aio_return; +}; + +static int sys_aio_read(struct aiocb* cb) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_AIO_READ), "b"(cb) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_aio_write(struct aiocb* cb) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_AIO_WRITE), "b"(cb) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_aio_error(struct aiocb* cb) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_AIO_ERROR), "b"(cb) + : "memory" + ); + return ret; +} + +static int sys_aio_return(struct aiocb* cb) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_AIO_RETURN), "b"(cb) + : "memory" + ); + return ret; +} + +static int sys_nanosleep(const struct timespec* req, struct timespec* rem) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_NANOSLEEP), "b"(req), "c"(rem) + : "memory" + ); + return __syscall_fix(ret); +} + +static uint32_t sys_getuid(void) { + uint32_t ret; + __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_GETUID) : "memory"); + return ret; +} + +static uint32_t sys_getgid(void) { + uint32_t ret; + __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_GETGID) : "memory"); + return ret; +} + +static uint32_t sys_geteuid(void) { + uint32_t ret; + __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_GETEUID) : "memory"); + return ret; +} + +static uint32_t sys_getegid(void) { + uint32_t ret; + __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_GETEGID) : "memory"); + return ret; +} + +static int sys_gettid(void) { + int ret; + __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_GETTID) : "memory"); + return ret; +} + +static int sys_fsync(int fd) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_FSYNC), "b"(fd) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_truncate(const char* path, uint32_t length) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_TRUNCATE), "b"(path), "c"(length) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_chmod(const char* path, uint32_t mode) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_CHMOD), "b"(path), "c"(mode) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_flock(int fd, int operation) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_FLOCK), "b"(fd), "c"(operation) + : "memory" + ); + return __syscall_fix(ret); +} + +struct iovec { + void* iov_base; + uint32_t iov_len; +}; + +static int sys_writev(int fd, const struct iovec* iov, int iovcnt) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_WRITEV), "b"(fd), "c"(iov), "d"(iovcnt) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_readv(int fd, const struct iovec* iov, int iovcnt) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_READV), "b"(fd), "c"(iov), "d"(iovcnt) + : "memory" + ); + return __syscall_fix(ret); +} + +static uint32_t sys_times(void* buf) { + uint32_t ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_TIMES), "b"(buf) + : "memory" + ); + return ret; +} + +static int sys_posix_spawn(uint32_t* pid_out, const char* path, + const char* const* argv, const char* const* envp) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_POSIX_SPAWN), "b"(pid_out), "c"(path), "d"(argv), "S"(envp) + : "memory" + ); + return __syscall_fix(ret); +} + +static int sys_setuid(uint32_t uid) { + int ret; + __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_SETUID), "b"(uid) : "memory"); + return __syscall_fix(ret); +} + +static int sys_setgid(uint32_t gid) { + int ret; + __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_SETGID), "b"(gid) : "memory"); + return __syscall_fix(ret); +} + +static int sys_seteuid(uint32_t euid) { + int ret; + __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_SETEUID), "b"(euid) : "memory"); + return __syscall_fix(ret); +} + +static int sys_setegid(uint32_t egid) { + int ret; + __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_SETEGID), "b"(egid) : "memory"); + return __syscall_fix(ret); +} + +static int sys_sigsuspend(const uint32_t* mask) { + int ret; + __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_SIGSUSPEND), "b"(mask) : "memory"); + return __syscall_fix(ret); +} + +static int sys_gettimeofday(struct timeval* tv) { + int ret; + __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_GETTIMEOFDAY), "b"(tv), "c"(0) : "memory"); + return __syscall_fix(ret); +} + +static int sys_mprotect(uintptr_t addr, uint32_t len, uint32_t prot) { + int ret; + __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_MPROTECT), "b"(addr), "c"(len), "d"(prot) : "memory"); + return __syscall_fix(ret); +} + +static int sys_madvise(uintptr_t addr, uint32_t len, uint32_t advice) { + int ret; + __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_MADVISE), "b"(addr), "c"(len), "d"(advice) : "memory"); + return __syscall_fix(ret); +} + +static int sys_getrlimit(int resource, struct rlimit* rlim) { + int ret; + __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_GETRLIMIT), "b"(resource), "c"(rlim) : "memory"); + return __syscall_fix(ret); +} + +struct utsname { + char sysname[65]; + char nodename[65]; + char release[65]; + char version[65]; + char machine[65]; +}; + +static int sys_uname(struct utsname* buf) { + int ret; + __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_UNAME), "b"(buf) : "memory"); + return __syscall_fix(ret); +} + +static int sys_setrlimit(int resource, const struct rlimit* rlim) { + int ret; + __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_SETRLIMIT), "b"(resource), "c"(rlim) : "memory"); + return __syscall_fix(ret); +} + +__attribute__((noreturn)) static void sys_exit(int code) { + __asm__ volatile( + "int $0x80\n" + "1: jmp 1b\n" + : + : "a"(SYSCALL_EXIT), "b"(code) + : "memory" + ); + for (;;) { + __asm__ volatile("hlt"); + } +} + +static volatile int got_usr1 = 0; +static volatile int got_usr1_ret = 0; +static volatile int got_ttin = 0; +static volatile int got_ttou = 0; +static volatile int got_alrm = 0; + +static void usr1_handler(int sig) { + (void)sig; + got_usr1 = 1; + sys_write(1, "[test] SIGUSR1 handler OK\n", + (uint32_t)(sizeof("[test] SIGUSR1 handler OK\n") - 1)); +} + +static void usr1_ret_handler(int sig) { + (void)sig; + got_usr1_ret = 1; +} + +static void alrm_handler(int sig) { + (void)sig; + got_alrm = 1; +} + +static void ttin_handler(int sig) { + (void)sig; + got_ttin = 1; +} + +static void ttou_handler(int sig) { + (void)sig; + got_ttou = 1; +} + +static void sigsegv_exit_handler(int sig) { + (void)sig; + static const char msg[] = "[test] SIGSEGV handler invoked\n"; + (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1)); + sys_exit(0); +} + +static void sigsegv_info_handler(int sig, siginfo_t* info, void* uctx) { + (void)uctx; + static const char msg[] = "[test] SIGSEGV siginfo handler invoked\n"; + (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1)); + const uintptr_t expected = 0x12345000U; + if (sig == SIGSEGV && info && (uintptr_t)info->si_addr == expected) { + sys_exit(0); + } + sys_exit(1); +} + +void _start(void) { + __asm__ volatile( + "mov $0x23, %ax\n" + "mov %ax, %ds\n" + "mov %ax, %es\n" + "mov %ax, %fs\n" + "mov %ax, %gs\n" + ); + + static const char msg[] = "[test] hello from init.elf\n"; + (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1)); + + static const char path[] = "/sbin/fulltest"; + + int fd = sys_open(path, 0); + if (fd < 0) { + sys_write(1, "[test] open failed fd=", (uint32_t)(sizeof("[test] open failed fd=") - 1)); + write_int_dec(fd); + sys_write(1, "\n", 1); + sys_exit(1); + } + + uint8_t hdr[4]; + int rd = sys_read(fd, hdr, 4); + (void)sys_close(fd); + if (rd == 4 && hdr[0] == 0x7F && hdr[1] == 'E' && hdr[2] == 'L' && hdr[3] == 'F') { + sys_write(1, "[test] open/read/close OK (ELF magic)\n", + (uint32_t)(sizeof("[test] open/read/close OK (ELF magic)\n") - 1)); + } else { + sys_write(1, "[test] read failed or bad header rd=", (uint32_t)(sizeof("[test] read failed or bad header rd=") - 1)); + write_int_dec(rd); + sys_write(1, " hdr=", (uint32_t)(sizeof(" hdr=") - 1)); + for (int i = 0; i < 4; i++) { + write_hex8(hdr[i]); + } + sys_write(1, "\n", 1); + sys_exit(1); + } + + fd = sys_open("/sbin/fulltest", 0); + if (fd < 0) { + sys_write(1, "[test] overlay open failed\n", + (uint32_t)(sizeof("[test] overlay open failed\n") - 1)); + sys_exit(1); + } + + uint8_t orig0 = 0; + if (sys_lseek(fd, 0, SEEK_SET) < 0 || sys_read(fd, &orig0, 1) != 1) { + sys_write(1, "[test] overlay read failed\n", + (uint32_t)(sizeof("[test] overlay read failed\n") - 1)); + sys_exit(1); + } + + uint8_t x = (uint8_t)(orig0 ^ 0xFF); + if (sys_lseek(fd, 0, SEEK_SET) < 0 || sys_write(fd, &x, 1) != 1) { + sys_write(1, "[test] overlay write failed\n", + (uint32_t)(sizeof("[test] overlay write failed\n") - 1)); + sys_exit(1); + } + + if (sys_close(fd) < 0) { + sys_write(1, "[test] overlay close failed\n", + (uint32_t)(sizeof("[test] overlay close failed\n") - 1)); + sys_exit(1); + } + + fd = sys_open("/sbin/fulltest", 0); + if (fd < 0) { + sys_write(1, "[test] overlay open2 failed\n", + (uint32_t)(sizeof("[test] overlay open2 failed\n") - 1)); + sys_exit(1); + } + + uint8_t chk = 0; + if (sys_lseek(fd, 0, SEEK_SET) < 0 || sys_read(fd, &chk, 1) != 1 || chk != x) { + sys_write(1, "[test] overlay verify failed\n", + (uint32_t)(sizeof("[test] overlay verify failed\n") - 1)); + sys_exit(1); + } + + if (sys_lseek(fd, 0, SEEK_SET) < 0 || sys_write(fd, &orig0, 1) != 1) { + sys_write(1, "[test] overlay restore failed\n", + (uint32_t)(sizeof("[test] overlay restore failed\n") - 1)); + sys_exit(1); + } + + if (sys_close(fd) < 0) { + sys_write(1, "[test] overlay close2 failed\n", + (uint32_t)(sizeof("[test] overlay close2 failed\n") - 1)); + sys_exit(1); + } + + sys_write(1, "[test] overlay copy-up OK\n", + (uint32_t)(sizeof("[test] overlay copy-up OK\n") - 1)); + + fd = sys_open("/sbin/fulltest", 0); + if (fd < 0) { + sys_write(1, "[test] open2 failed\n", (uint32_t)(sizeof("[test] open2 failed\n") - 1)); + sys_exit(1); + } + + struct stat st; + if (sys_fstat(fd, &st) < 0) { + sys_write(1, "[test] fstat failed\n", (uint32_t)(sizeof("[test] fstat failed\n") - 1)); + sys_exit(1); + } + + if ((st.st_mode & S_IFMT) != S_IFREG || st.st_size == 0) { + sys_write(1, "[test] fstat bad\n", (uint32_t)(sizeof("[test] fstat bad\n") - 1)); + sys_exit(1); + } + + if (sys_lseek(fd, 0, SEEK_SET) < 0) { + sys_write(1, "[test] lseek set failed\n", + (uint32_t)(sizeof("[test] lseek set failed\n") - 1)); + sys_exit(1); + } + + uint8_t m2[4]; + if (sys_read(fd, m2, 4) != 4) { + sys_write(1, "[test] read2 failed\n", (uint32_t)(sizeof("[test] read2 failed\n") - 1)); + sys_exit(1); + } + if (m2[0] != 0x7F || m2[1] != 'E' || m2[2] != 'L' || m2[3] != 'F') { + sys_write(1, "[test] lseek/read mismatch\n", + (uint32_t)(sizeof("[test] lseek/read mismatch\n") - 1)); + sys_exit(1); + } + + if (sys_close(fd) < 0) { + sys_write(1, "[test] close2 failed\n", (uint32_t)(sizeof("[test] close2 failed\n") - 1)); + sys_exit(1); + } + + if (sys_stat("/sbin/fulltest", &st) < 0) { + sys_write(1, "[test] stat failed\n", (uint32_t)(sizeof("[test] stat failed\n") - 1)); + sys_exit(1); + } + if ((st.st_mode & S_IFMT) != S_IFREG || st.st_size == 0) { + sys_write(1, "[test] stat bad\n", (uint32_t)(sizeof("[test] stat bad\n") - 1)); + sys_exit(1); + } + + sys_write(1, "[test] lseek/stat/fstat OK\n", + (uint32_t)(sizeof("[test] lseek/stat/fstat OK\n") - 1)); + + fd = sys_open("/tmp/hello.txt", 0); + if (fd < 0) { + sys_write(1, "[test] tmpfs open failed\n", + (uint32_t)(sizeof("[test] tmpfs open failed\n") - 1)); + sys_exit(1); + } + + if (sys_lseek(fd, 0, SEEK_END) < 0) { + sys_write(1, "[test] dup2 prep lseek failed\n", + (uint32_t)(sizeof("[test] dup2 prep lseek failed\n") - 1)); + sys_exit(1); + } + + if (sys_dup2(fd, 1) != 1) { + sys_write(1, "[test] dup2 failed\n", (uint32_t)(sizeof("[test] dup2 failed\n") - 1)); + sys_exit(1); + } + + (void)sys_close(fd); + + { + static const char m[] = "[test] dup2 stdout->file OK\n"; + if (sys_write(1, m, (uint32_t)(sizeof(m) - 1)) != (int)(sizeof(m) - 1)) { + sys_exit(1); + } + } + + (void)sys_close(1); + sys_write(1, "[test] dup2 restore tty OK\n", + (uint32_t)(sizeof("[test] dup2 restore tty OK\n") - 1)); + + { + int pfds[2]; + if (sys_pipe(pfds) < 0) { + sys_write(1, "[test] pipe failed\n", (uint32_t)(sizeof("[test] pipe failed\n") - 1)); + sys_exit(1); + } + + static const char pmsg[] = "pipe-test"; + if (sys_write(pfds[1], pmsg, (uint32_t)(sizeof(pmsg) - 1)) != (int)(sizeof(pmsg) - 1)) { + sys_write(1, "[test] pipe write failed\n", + (uint32_t)(sizeof("[test] pipe write failed\n") - 1)); + sys_exit(1); + } + + char rbuf[16]; + int prd = sys_read(pfds[0], rbuf, (uint32_t)(sizeof(pmsg) - 1)); + if (prd != (int)(sizeof(pmsg) - 1)) { + sys_write(1, "[test] pipe read failed\n", + (uint32_t)(sizeof("[test] pipe read failed\n") - 1)); + sys_exit(1); + } + + int ok = 1; + for (uint32_t i = 0; i < (uint32_t)(sizeof(pmsg) - 1); i++) { + if ((uint8_t)rbuf[i] != (uint8_t)pmsg[i]) ok = 0; + } + if (!ok) { + sys_write(1, "[test] pipe mismatch\n", + (uint32_t)(sizeof("[test] pipe mismatch\n") - 1)); + sys_exit(1); + } + + if (sys_dup2(pfds[1], 1) != 1) { + sys_write(1, "[test] pipe dup2 failed\n", + (uint32_t)(sizeof("[test] pipe dup2 failed\n") - 1)); + sys_exit(1); + } + + static const char p2[] = "dup2-pipe"; + if (sys_write(1, p2, (uint32_t)(sizeof(p2) - 1)) != (int)(sizeof(p2) - 1)) { + sys_exit(1); + } + + int prd2 = sys_read(pfds[0], rbuf, (uint32_t)(sizeof(p2) - 1)); + if (prd2 != (int)(sizeof(p2) - 1)) { + sys_write(1, "[test] pipe dup2 read failed\n", + (uint32_t)(sizeof("[test] pipe dup2 read failed\n") - 1)); + sys_exit(1); + } + sys_write(1, "[test] pipe OK\n", (uint32_t)(sizeof("[test] pipe OK\n") - 1)); + + (void)sys_close(pfds[0]); + (void)sys_close(pfds[1]); + + int tfd = sys_open("/dev/tty", 0); + if (tfd < 0) { + sys_write(1, "[test] /dev/tty open failed\n", + (uint32_t)(sizeof("[test] /dev/tty open failed\n") - 1)); + sys_exit(1); + } + if (sys_dup2(tfd, 1) != 1) { + sys_write(1, "[test] dup2 restore tty failed\n", + (uint32_t)(sizeof("[test] dup2 restore tty failed\n") - 1)); + sys_exit(1); + } + (void)sys_close(tfd); + + } + + { + int pid = sys_fork(); + if (pid < 0) { + sys_write(1, "[test] kill test fork failed\n", + (uint32_t)(sizeof("[test] kill test fork failed\n") - 1)); + sys_exit(1); + } + + if (pid == 0) { + for (;;) { + __asm__ volatile("nop"); + } + } + + if (sys_kill(pid, SIGKILL) < 0) { + sys_write(1, "[test] kill(SIGKILL) failed\n", + (uint32_t)(sizeof("[test] kill(SIGKILL) failed\n") - 1)); + sys_exit(1); + } + + int st = 0; + int rp = sys_waitpid(pid, &st, 0); + if (rp != pid || st != (128 + SIGKILL)) { + sys_write(1, "[test] kill test waitpid mismatch\n", + (uint32_t)(sizeof("[test] kill test waitpid mismatch\n") - 1)); + sys_exit(1); + } + + sys_write(1, "[test] kill(SIGKILL) OK\n", + (uint32_t)(sizeof("[test] kill(SIGKILL) OK\n") - 1)); + } + + { + int fds[2]; + if (sys_pipe(fds) < 0) { + sys_write(1, "[test] poll pipe setup failed\n", + (uint32_t)(sizeof("[test] poll pipe setup failed\n") - 1)); + sys_exit(1); + } + + struct pollfd p; + p.fd = fds[0]; + p.events = POLLIN; + p.revents = 0; + int rc = sys_poll(&p, 1, 0); + if (rc != 0) { + sys_write(1, "[test] poll(pipe) expected 0\n", + (uint32_t)(sizeof("[test] poll(pipe) expected 0\n") - 1)); + sys_exit(1); + } + + static const char a = 'A'; + if (sys_write(fds[1], &a, 1) != 1) { + sys_write(1, "[test] poll pipe write failed\n", + (uint32_t)(sizeof("[test] poll pipe write failed\n") - 1)); + sys_exit(1); + } + + p.revents = 0; + rc = sys_poll(&p, 1, 0); + if (rc != 1 || (p.revents & POLLIN) == 0) { + sys_write(1, "[test] poll(pipe) expected POLLIN\n", + (uint32_t)(sizeof("[test] poll(pipe) expected POLLIN\n") - 1)); + sys_exit(1); + } + + (void)sys_close(fds[0]); + (void)sys_close(fds[1]); + sys_write(1, "[test] poll(pipe) OK\n", (uint32_t)(sizeof("[test] poll(pipe) OK\n") - 1)); + } + + { + int fds[2]; + if (sys_pipe(fds) < 0) { + sys_write(1, "[test] select pipe setup failed\n", + (uint32_t)(sizeof("[test] select pipe setup failed\n") - 1)); + sys_exit(1); + } + + uint64_t r = 0; + uint64_t w = 0; + r |= (1ULL << (uint32_t)fds[0]); + int rc = sys_select((uint32_t)(fds[0] + 1), &r, &w, 0, 0); + if (rc != 0) { + sys_write(1, "[test] select(pipe) expected 0\n", + (uint32_t)(sizeof("[test] select(pipe) expected 0\n") - 1)); + sys_exit(1); + } + + static const char a = 'B'; + if (sys_write(fds[1], &a, 1) != 1) { + sys_write(1, "[test] select pipe write failed\n", + (uint32_t)(sizeof("[test] select pipe write failed\n") - 1)); + sys_exit(1); + } + + r = 0; + w = 0; + r |= (1ULL << (uint32_t)fds[0]); + rc = sys_select((uint32_t)(fds[0] + 1), &r, &w, 0, 0); + if (rc != 1 || ((r >> (uint32_t)fds[0]) & 1ULL) == 0) { + sys_write(1, "[test] select(pipe) expected readable\n", + (uint32_t)(sizeof("[test] select(pipe) expected readable\n") - 1)); + sys_exit(1); + } + + (void)sys_close(fds[0]); + (void)sys_close(fds[1]); + sys_write(1, "[test] select(pipe) OK\n", + (uint32_t)(sizeof("[test] select(pipe) OK\n") - 1)); + } + + { + int fd = sys_open("/dev/tty", 0); + if (fd < 0) { + sys_write(1, "[test] ioctl(/dev/tty) open failed\n", + (uint32_t)(sizeof("[test] ioctl(/dev/tty) open failed\n") - 1)); + sys_exit(1); + } + + int fg = -1; + if (sys_ioctl(fd, TIOCGPGRP, &fg) < 0 || fg != 0) { + sys_write(1, "[test] ioctl TIOCGPGRP failed\n", + (uint32_t)(sizeof("[test] ioctl TIOCGPGRP failed\n") - 1)); + sys_exit(1); + } + + fg = 0; + if (sys_ioctl(fd, TIOCSPGRP, &fg) < 0) { + sys_write(1, "[test] ioctl TIOCSPGRP failed\n", + (uint32_t)(sizeof("[test] ioctl TIOCSPGRP failed\n") - 1)); + sys_exit(1); + } + + fg = 1; + if (sys_ioctl(fd, TIOCSPGRP, &fg) >= 0) { + sys_write(1, "[test] ioctl TIOCSPGRP expected fail\n", + (uint32_t)(sizeof("[test] ioctl TIOCSPGRP expected fail\n") - 1)); + sys_exit(1); + } + + struct termios oldt; + if (sys_ioctl(fd, TCGETS, &oldt) < 0) { + sys_write(1, "[test] ioctl TCGETS failed\n", + (uint32_t)(sizeof("[test] ioctl TCGETS failed\n") - 1)); + sys_exit(1); + } + + struct termios t = oldt; + t.c_lflag &= ~(uint32_t)(ECHO | ICANON); + if (sys_ioctl(fd, TCSETS, &t) < 0) { + sys_write(1, "[test] ioctl TCSETS failed\n", + (uint32_t)(sizeof("[test] ioctl TCSETS failed\n") - 1)); + sys_exit(1); + } + + struct termios chk; + if (sys_ioctl(fd, TCGETS, &chk) < 0) { + sys_write(1, "[test] ioctl TCGETS2 failed\n", + (uint32_t)(sizeof("[test] ioctl TCGETS2 failed\n") - 1)); + sys_exit(1); + } + + if ((chk.c_lflag & (uint32_t)(ECHO | ICANON)) != 0) { + sys_write(1, "[test] ioctl verify failed\n", + (uint32_t)(sizeof("[test] ioctl verify failed\n") - 1)); + sys_exit(1); + } + + (void)sys_ioctl(fd, TCSETS, &oldt); + (void)sys_close(fd); + + sys_write(1, "[test] ioctl(/dev/tty) OK\n", + (uint32_t)(sizeof("[test] ioctl(/dev/tty) OK\n") - 1)); + } + + // A2: basic job control. A background pgrp read/write on controlling TTY should raise SIGTTIN/SIGTTOU. + { + int leader = sys_fork(); + if (leader < 0) { + sys_write(1, "[test] fork(job control leader) failed\n", + (uint32_t)(sizeof("[test] fork(job control leader) failed\n") - 1)); + sys_exit(1); + } + if (leader == 0) { + int me = sys_getpid(); + int sid = sys_setsid(); + if (sid != me) { + sys_write(1, "[test] setsid(job control) failed\n", + (uint32_t)(sizeof("[test] setsid(job control) failed\n") - 1)); + sys_exit(1); + } + + int tfd = sys_open("/dev/tty", 0); + if (tfd < 0) { + sys_write(1, "[test] open(/dev/tty) for job control failed\n", + (uint32_t)(sizeof("[test] open(/dev/tty) for job control failed\n") - 1)); + sys_exit(1); + } + + // Touch ioctl to make kernel acquire controlling session/pgrp. + int fg = 0; + (void)sys_ioctl(tfd, TIOCGPGRP, &fg); + + fg = me; + if (sys_ioctl(tfd, TIOCSPGRP, &fg) < 0) { + sys_write(1, "[test] ioctl TIOCSPGRP(job control) failed\n", + (uint32_t)(sizeof("[test] ioctl TIOCSPGRP(job control) failed\n") - 1)); + sys_exit(1); + } + + int bg = sys_fork(); + if (bg < 0) { + sys_write(1, "[test] fork(job control bg) failed\n", + (uint32_t)(sizeof("[test] fork(job control bg) failed\n") - 1)); + sys_exit(1); + } + if (bg == 0) { + (void)sys_setpgid(0, me + 1); + + (void)sys_sigaction(SIGTTIN, ttin_handler, 0); + (void)sys_sigaction(SIGTTOU, ttou_handler, 0); + + uint8_t b = 0; + (void)sys_read(tfd, &b, 1); + if (!got_ttin) { + sys_write(1, "[test] SIGTTIN job control failed\n", + (uint32_t)(sizeof("[test] SIGTTIN job control failed\n") - 1)); + sys_exit(1); + } + + const char msg2[] = "x"; + (void)sys_write(tfd, msg2, 1); + if (!got_ttou) { + sys_write(1, "[test] SIGTTOU job control failed\n", + (uint32_t)(sizeof("[test] SIGTTOU job control failed\n") - 1)); + sys_exit(1); + } + + sys_exit(0); + } + + int st2 = 0; + int wp2 = sys_waitpid(bg, &st2, 0); + if (wp2 != bg || st2 != 0) { + sys_write(1, "[test] waitpid(job control bg) failed wp=", (uint32_t)(sizeof("[test] waitpid(job control bg) failed wp=") - 1)); + write_int_dec(wp2); + sys_write(1, " st=", (uint32_t)(sizeof(" st=") - 1)); + write_int_dec(st2); + sys_write(1, "\n", 1); + sys_exit(1); + } + + (void)sys_close(tfd); + sys_exit(0); + } + + int stL = 0; + int wpL = sys_waitpid(leader, &stL, 0); + if (wpL != leader || stL != 0) { + sys_write(1, "[test] waitpid(job control leader) failed wp=", (uint32_t)(sizeof("[test] waitpid(job control leader) failed wp=") - 1)); + write_int_dec(wpL); + sys_write(1, " st=", (uint32_t)(sizeof(" st=") - 1)); + write_int_dec(stL); + sys_write(1, "\n", 1); + sys_exit(1); + } + + sys_write(1, "[test] job control (SIGTTIN/SIGTTOU) OK\n", + (uint32_t)(sizeof("[test] job control (SIGTTIN/SIGTTOU) OK\n") - 1)); + } + + { + int fd = sys_open("/dev/null", 0); + if (fd < 0) { + sys_write(1, "[test] poll(/dev/null) open failed\n", + (uint32_t)(sizeof("[test] poll(/dev/null) open failed\n") - 1)); + sys_exit(1); + } + struct pollfd p; + p.fd = fd; + p.events = POLLOUT; + p.revents = 0; + int rc = sys_poll(&p, 1, 0); + if (rc != 1 || (p.revents & POLLOUT) == 0) { + sys_write(1, "[test] poll(/dev/null) expected POLLOUT\n", + (uint32_t)(sizeof("[test] poll(/dev/null) expected POLLOUT\n") - 1)); + sys_exit(1); + } + (void)sys_close(fd); + sys_write(1, "[test] poll(/dev/null) OK\n", + (uint32_t)(sizeof("[test] poll(/dev/null) OK\n") - 1)); + } + + { + int mfd = sys_open("/dev/ptmx", 0); + int sfd = sys_open("/dev/pts/0", 0); + if (mfd < 0 || sfd < 0) { + sys_write(1, "[test] pty open failed\n", + (uint32_t)(sizeof("[test] pty open failed\n") - 1)); + sys_exit(1); + } + + static const char m2s[] = "m2s"; + if (sys_write(mfd, m2s, (uint32_t)(sizeof(m2s) - 1)) != (int)(sizeof(m2s) - 1)) { + sys_write(1, "[test] pty write master failed\n", + (uint32_t)(sizeof("[test] pty write master failed\n") - 1)); + sys_exit(1); + } + + struct pollfd p; + p.fd = sfd; + p.events = POLLIN; + p.revents = 0; + int rc = sys_poll(&p, 1, 50); + if (rc != 1 || (p.revents & POLLIN) == 0) { + sys_write(1, "[test] pty poll slave failed\n", + (uint32_t)(sizeof("[test] pty poll slave failed\n") - 1)); + sys_exit(1); + } + + char buf[8]; + int rd = sys_read(sfd, buf, (uint32_t)(sizeof(m2s) - 1)); + if (rd != (int)(sizeof(m2s) - 1) || !memeq(buf, m2s, (uint32_t)(sizeof(m2s) - 1))) { + sys_write(1, "[test] pty read slave failed\n", + (uint32_t)(sizeof("[test] pty read slave failed\n") - 1)); + sys_exit(1); + } + + static const char s2m[] = "s2m"; + if (sys_write(sfd, s2m, (uint32_t)(sizeof(s2m) - 1)) != (int)(sizeof(s2m) - 1)) { + sys_write(1, "[test] pty write slave failed\n", + (uint32_t)(sizeof("[test] pty write slave failed\n") - 1)); + sys_exit(1); + } + + p.fd = mfd; + p.events = POLLIN; + p.revents = 0; + rc = sys_poll(&p, 1, 50); + if (rc != 1 || (p.revents & POLLIN) == 0) { + sys_write(1, "[test] pty poll master failed\n", + (uint32_t)(sizeof("[test] pty poll master failed\n") - 1)); + sys_exit(1); + } + + rd = sys_read(mfd, buf, (uint32_t)(sizeof(s2m) - 1)); + if (rd != (int)(sizeof(s2m) - 1) || !memeq(buf, s2m, (uint32_t)(sizeof(s2m) - 1))) { + sys_write(1, "[test] pty read master failed\n", + (uint32_t)(sizeof("[test] pty read master failed\n") - 1)); + sys_exit(1); + } + + (void)sys_close(mfd); + (void)sys_close(sfd); + sys_write(1, "[test] pty OK\n", (uint32_t)(sizeof("[test] pty OK\n") - 1)); + } + + { + sys_write(1, "[test] setsid test: before fork\n", + (uint32_t)(sizeof("[test] setsid test: before fork\n") - 1)); + int pid = sys_fork(); + if (pid < 0) { + static const char smsg[] = "[test] fork failed\n"; + (void)sys_write(1, smsg, (uint32_t)(sizeof(smsg) - 1)); + sys_exit(2); + } + if (pid == 0) { + sys_write(1, "[test] setsid test: child start\n", + (uint32_t)(sizeof("[test] setsid test: child start\n") - 1)); + int me = sys_getpid(); + int sid = sys_setsid(); + if (sid != me) sys_exit(2); + + int pg = sys_getpgrp(); + if (pg != me) sys_exit(3); + + int newpg = me + 1; + if (sys_setpgid(0, newpg) < 0) sys_exit(4); + if (sys_getpgrp() != newpg) sys_exit(5); + + sys_exit(0); + } + + sys_write(1, "[test] setsid test: parent waitpid\n", + (uint32_t)(sizeof("[test] setsid test: parent waitpid\n") - 1)); + int st = 0; + int wp = sys_waitpid(pid, &st, 0); + if (wp != pid || st != 0) { + sys_write(1, "[test] setsid/setpgid/getpgrp failed\n", + (uint32_t)(sizeof("[test] setsid/setpgid/getpgrp failed\n") - 1)); + sys_exit(1); + } + + sys_write(1, "[test] setsid/setpgid/getpgrp OK\n", + (uint32_t)(sizeof("[test] setsid/setpgid/getpgrp OK\n") - 1)); + } + + { + uintptr_t oldh = 0; + if (sys_sigaction(SIGUSR1, usr1_handler, &oldh) < 0) { + sys_write(1, "[test] sigaction failed\n", + (uint32_t)(sizeof("[test] sigaction failed\n") - 1)); + sys_exit(1); + } + + int me = sys_getpid(); + if (sys_kill(me, SIGUSR1) < 0) { + sys_write(1, "[test] kill(SIGUSR1) failed\n", + (uint32_t)(sizeof("[test] kill(SIGUSR1) failed\n") - 1)); + sys_exit(1); + } + + for (uint32_t i = 0; i < 2000000U; i++) { + if (got_usr1) break; + } + + if (!got_usr1) { + sys_write(1, "[test] SIGUSR1 not delivered\n", + (uint32_t)(sizeof("[test] SIGUSR1 not delivered\n") - 1)); + sys_exit(1); + } + + sys_write(1, "[test] sigaction/kill(SIGUSR1) OK\n", + (uint32_t)(sizeof("[test] sigaction/kill(SIGUSR1) OK\n") - 1)); + } + + // Verify that returning from a signal handler does not corrupt the user stack. + { + if (sys_sigaction(SIGUSR1, usr1_ret_handler, 0) < 0) { + sys_write(1, "[test] sigaction (sigreturn test) failed\n", + (uint32_t)(sizeof("[test] sigaction (sigreturn test) failed\n") - 1)); + sys_exit(1); + } + + volatile uint32_t canary = 0x11223344U; + int me = sys_getpid(); + if (sys_kill(me, SIGUSR1) < 0) { + sys_write(1, "[test] kill(SIGUSR1) (sigreturn test) failed\n", + (uint32_t)(sizeof("[test] kill(SIGUSR1) (sigreturn test) failed\n") - 1)); + sys_exit(1); + } + + if (!got_usr1_ret) { + sys_write(1, "[test] SIGUSR1 not delivered (sigreturn test)\n", + (uint32_t)(sizeof("[test] SIGUSR1 not delivered (sigreturn test)\n") - 1)); + sys_exit(1); + } + + if (canary != 0x11223344U) { + sys_write(1, "[test] sigreturn test stack corruption\n", + (uint32_t)(sizeof("[test] sigreturn test stack corruption\n") - 1)); + sys_exit(1); + } + + sys_write(1, "[test] sigreturn OK\n", + (uint32_t)(sizeof("[test] sigreturn OK\n") - 1)); + } + + fd = sys_open("/tmp/hello.txt", 0); + if (fd < 0) { + sys_write(1, "[test] tmpfs open2 failed\n", + (uint32_t)(sizeof("[test] tmpfs open2 failed\n") - 1)); + sys_exit(1); + } + + if (sys_stat("/tmp/hello.txt", &st) < 0) { + sys_write(1, "[test] tmpfs stat failed\n", + (uint32_t)(sizeof("[test] tmpfs stat failed\n") - 1)); + sys_exit(1); + } + if ((st.st_mode & S_IFMT) != S_IFREG) { + sys_write(1, "[test] tmpfs stat not reg\n", + (uint32_t)(sizeof("[test] tmpfs stat not reg\n") - 1)); + sys_exit(1); + } + if (st.st_size == 0) { + sys_write(1, "[test] tmpfs stat size 0\n", + (uint32_t)(sizeof("[test] tmpfs stat size 0\n") - 1)); + sys_exit(1); + } + + struct stat fst; + if (sys_fstat(fd, &fst) < 0) { + sys_write(1, "[test] tmpfs fstat failed\n", + (uint32_t)(sizeof("[test] tmpfs fstat failed\n") - 1)); + sys_exit(1); + } + if (fst.st_size != st.st_size) { + sys_write(1, "[test] tmpfs stat size mismatch\n", + (uint32_t)(sizeof("[test] tmpfs stat size mismatch\n") - 1)); + sys_exit(1); + } + + int end = sys_lseek(fd, 0, SEEK_END); + if (end < 0 || (uint32_t)end != st.st_size) { + sys_write(1, "[test] tmpfs lseek end bad\n", + (uint32_t)(sizeof("[test] tmpfs lseek end bad\n") - 1)); + sys_exit(1); + } + + uint8_t eofb; + if (sys_read(fd, &eofb, 1) != 0) { + sys_write(1, "[test] tmpfs eof read bad\n", + (uint32_t)(sizeof("[test] tmpfs eof read bad\n") - 1)); + sys_exit(1); + } + + if (sys_lseek(fd, 0, 999) >= 0) { + sys_write(1, "[test] tmpfs lseek whence bad\n", + (uint32_t)(sizeof("[test] tmpfs lseek whence bad\n") - 1)); + sys_exit(1); + } + + if (sys_lseek(fd, 0, SEEK_SET) < 0) { + sys_write(1, "[test] tmpfs lseek set failed\n", + (uint32_t)(sizeof("[test] tmpfs lseek set failed\n") - 1)); + sys_exit(1); + } + + uint8_t tbuf[6]; + if (sys_read(fd, tbuf, 5) != 5) { + sys_write(1, "[test] tmpfs read failed\n", + (uint32_t)(sizeof("[test] tmpfs read failed\n") - 1)); + sys_exit(1); + } + tbuf[5] = 0; + if (tbuf[0] != 'h' || tbuf[1] != 'e' || tbuf[2] != 'l' || tbuf[3] != 'l' || tbuf[4] != 'o') { + sys_write(1, "[test] tmpfs bad data\n", (uint32_t)(sizeof("[test] tmpfs bad data\n") - 1)); + sys_exit(1); + } + + if (sys_close(fd) < 0) { + sys_write(1, "[test] tmpfs close failed\n", + (uint32_t)(sizeof("[test] tmpfs close failed\n") - 1)); + sys_exit(1); + } + + if (sys_open("/tmp/does_not_exist", 0) >= 0) { + sys_write(1, "[test] tmpfs open nonexist bad\n", + (uint32_t)(sizeof("[test] tmpfs open nonexist bad\n") - 1)); + sys_exit(1); + } + + fd = sys_open("/tmp/hello.txt", 0); + if (fd < 0) { + sys_write(1, "[test] tmpfs open3 failed\n", + (uint32_t)(sizeof("[test] tmpfs open3 failed\n") - 1)); + sys_exit(1); + } + + if (sys_fstat(fd, &fst) < 0) { + sys_write(1, "[test] tmpfs fstat2 failed\n", + (uint32_t)(sizeof("[test] tmpfs fstat2 failed\n") - 1)); + sys_exit(1); + } + + if (sys_lseek(fd, 0, SEEK_END) < 0) { + sys_write(1, "[test] tmpfs lseek end2 failed\n", + (uint32_t)(sizeof("[test] tmpfs lseek end2 failed\n") - 1)); + sys_exit(1); + } + + char suf[3]; + suf[0] = 'X'; + suf[1] = 'Y'; + suf[2] = 'Z'; + if (sys_write(fd, suf, 3) != 3) { + sys_write(1, "[test] tmpfs write failed\n", + (uint32_t)(sizeof("[test] tmpfs write failed\n") - 1)); + sys_exit(1); + } + + if (sys_fstat(fd, &fst) < 0) { + sys_write(1, "[test] tmpfs fstat3 failed\n", + (uint32_t)(sizeof("[test] tmpfs fstat3 failed\n") - 1)); + sys_exit(1); + } + if (fst.st_size != st.st_size + 3) { + sys_write(1, "[test] tmpfs size not grown\n", + (uint32_t)(sizeof("[test] tmpfs size not grown\n") - 1)); + sys_exit(1); + } + + if (sys_lseek(fd, -3, SEEK_END) < 0) { + sys_write(1, "[test] tmpfs lseek back failed\n", + (uint32_t)(sizeof("[test] tmpfs lseek back failed\n") - 1)); + sys_exit(1); + } + uint8_t s2[3]; + if (sys_read(fd, s2, 3) != 3 || s2[0] != 'X' || s2[1] != 'Y' || s2[2] != 'Z') { + sys_write(1, "[test] tmpfs suffix mismatch\n", + (uint32_t)(sizeof("[test] tmpfs suffix mismatch\n") - 1)); + sys_exit(1); + } + + if (sys_close(fd) < 0) { + sys_write(1, "[test] tmpfs close3 failed\n", + (uint32_t)(sizeof("[test] tmpfs close3 failed\n") - 1)); + sys_exit(1); + } + + sys_write(1, "[test] tmpfs/mount OK\n", (uint32_t)(sizeof("[test] tmpfs/mount OK\n") - 1)); + + { + int fd = sys_open("/dev/null", 0); + if (fd < 0) { + sys_write(1, "[test] /dev/null open failed\n", + (uint32_t)(sizeof("[test] /dev/null open failed\n") - 1)); + sys_exit(1); + } + static const char z[] = "discard me"; + if (sys_write(fd, z, (uint32_t)(sizeof(z) - 1)) != (int)(sizeof(z) - 1)) { + sys_write(1, "[test] /dev/null write failed\n", + (uint32_t)(sizeof("[test] /dev/null write failed\n") - 1)); + sys_exit(1); + } + (void)sys_close(fd); + sys_write(1, "[test] /dev/null OK\n", (uint32_t)(sizeof("[test] /dev/null OK\n") - 1)); + } + + // B1: persistent storage smoke. Value should increment across reboots (disk.img). + { + int fd = sys_open("/persist/counter", 0); + if (fd < 0) { + sys_write(1, "[test] /persist/counter open failed\n", + (uint32_t)(sizeof("[test] /persist/counter open failed\n") - 1)); + sys_exit(1); + } + + (void)sys_lseek(fd, 0, SEEK_SET); + uint8_t b[4] = {0, 0, 0, 0}; + int rd = sys_read(fd, b, 4); + if (rd != 4) { + sys_write(1, "[test] /persist/counter read failed\n", + (uint32_t)(sizeof("[test] /persist/counter read failed\n") - 1)); + sys_exit(1); + } + + uint32_t v = (uint32_t)b[0] | ((uint32_t)b[1] << 8) | ((uint32_t)b[2] << 16) | ((uint32_t)b[3] << 24); + v++; + b[0] = (uint8_t)(v & 0xFF); + b[1] = (uint8_t)((v >> 8) & 0xFF); + b[2] = (uint8_t)((v >> 16) & 0xFF); + b[3] = (uint8_t)((v >> 24) & 0xFF); + + (void)sys_lseek(fd, 0, SEEK_SET); + int wr = sys_write(fd, b, 4); + if (wr != 4) { + sys_write(1, "[test] /persist/counter write failed\n", + (uint32_t)(sizeof("[test] /persist/counter write failed\n") - 1)); + sys_exit(1); + } + + (void)sys_close(fd); + + sys_write(1, "[test] /persist/counter=", (uint32_t)(sizeof("[test] /persist/counter=") - 1)); + write_int_dec((int)v); + sys_write(1, "\n", 1); + } + + { + int fd = sys_open("/dev/tty", 0); + if (fd < 0) { + sys_write(1, "[test] /dev/tty open failed\n", + (uint32_t)(sizeof("[test] /dev/tty open failed\n") - 1)); + sys_exit(1); + } + static const char m[] = "[test] /dev/tty write OK\n"; + int wr = sys_write(fd, m, (uint32_t)(sizeof(m) - 1)); + if (wr != (int)(sizeof(m) - 1)) { + sys_write(1, "[test] /dev/tty write failed\n", + (uint32_t)(sizeof("[test] /dev/tty write failed\n") - 1)); + sys_exit(1); + } + (void)sys_close(fd); + } + + // B2: on-disk general filesystem smoke (/disk) + { + int fd = sys_open("/disk/test", O_CREAT); + if (fd < 0) { + sys_write(1, "[test] /disk/test open failed\n", + (uint32_t)(sizeof("[test] /disk/test open failed\n") - 1)); + sys_exit(1); + } + + char buf[16]; + int rd = sys_read(fd, buf, sizeof(buf)); + int prev = 0; + if (rd > 0) { + for (int i = 0; i < rd; i++) { + if (buf[i] < '0' || buf[i] > '9') break; + prev = prev * 10 + (buf[i] - '0'); + } + } + + (void)sys_close(fd); + + fd = sys_open("/disk/test", O_CREAT | O_TRUNC); + if (fd < 0) { + sys_write(1, "[test] /disk/test open2 failed\n", + (uint32_t)(sizeof("[test] /disk/test open2 failed\n") - 1)); + sys_exit(1); + } + + int next = prev + 1; + char out[16]; + int n = 0; + int v = next; + if (v == 0) { + out[n++] = '0'; + } else { + char tmp[16]; + int t = 0; + while (v > 0 && t < (int)sizeof(tmp)) { + tmp[t++] = (char)('0' + (v % 10)); + v /= 10; + } + while (t > 0) { + out[n++] = tmp[--t]; + } + } + + if (sys_write(fd, out, (uint32_t)n) != n) { + sys_write(1, "[test] /disk/test write failed\n", + (uint32_t)(sizeof("[test] /disk/test write failed\n") - 1)); + sys_exit(1); + } + (void)sys_close(fd); + + fd = sys_open("/disk/test", 0); + if (fd < 0) { + sys_write(1, "[test] /disk/test open3 failed\n", + (uint32_t)(sizeof("[test] /disk/test open3 failed\n") - 1)); + sys_exit(1); + } + for (uint32_t i = 0; i < (uint32_t)sizeof(buf); i++) buf[i] = 0; + rd = sys_read(fd, buf, sizeof(buf)); + (void)sys_close(fd); + if (rd != n || !memeq(buf, out, (uint32_t)n)) { + sys_write(1, "[test] /disk/test verify failed\n", + (uint32_t)(sizeof("[test] /disk/test verify failed\n") - 1)); + sys_exit(1); + } + + sys_write(1, "[test] /disk/test prev=", (uint32_t)(sizeof("[test] /disk/test prev=") - 1)); + write_int_dec(prev); + sys_write(1, " next=", (uint32_t)(sizeof(" next=") - 1)); + write_int_dec(next); + sys_write(1, " OK\n", (uint32_t)(sizeof(" OK\n") - 1)); + } + + // B3: diskfs mkdir/unlink smoke + { + int r = sys_mkdir("/disk/dir"); + if (r < 0 && errno != 17) { + sys_write(1, "[test] mkdir /disk/dir failed errno=", (uint32_t)(sizeof("[test] mkdir /disk/dir failed errno=") - 1)); + write_int_dec(errno); + sys_write(1, "\n", 1); + sys_exit(1); + } + + int fd = sys_open("/disk/dir/file", O_CREAT | O_TRUNC); + if (fd < 0) { + sys_write(1, "[test] open /disk/dir/file failed\n", + (uint32_t)(sizeof("[test] open /disk/dir/file failed\n") - 1)); + sys_exit(1); + } + static const char msg2[] = "ok"; + if (sys_write(fd, msg2, 2) != 2) { + sys_write(1, "[test] write /disk/dir/file failed\n", + (uint32_t)(sizeof("[test] write /disk/dir/file failed\n") - 1)); + sys_exit(1); + } + (void)sys_close(fd); + + r = sys_unlink("/disk/dir/file"); + if (r < 0) { + sys_write(1, "[test] unlink /disk/dir/file failed\n", + (uint32_t)(sizeof("[test] unlink /disk/dir/file failed\n") - 1)); + sys_exit(1); + } + + fd = sys_open("/disk/dir/file", 0); + if (fd >= 0) { + sys_write(1, "[test] unlink did not remove file\n", + (uint32_t)(sizeof("[test] unlink did not remove file\n") - 1)); + sys_exit(1); + } + + sys_write(1, "[test] diskfs mkdir/unlink OK\n", + (uint32_t)(sizeof("[test] diskfs mkdir/unlink OK\n") - 1)); + } + + // B4: diskfs getdents smoke + { + int r = sys_mkdir("/disk/ls"); + if (r < 0 && errno != 17) { + sys_write(1, "[test] mkdir /disk/ls failed errno=", (uint32_t)(sizeof("[test] mkdir /disk/ls failed errno=") - 1)); + write_int_dec(errno); + sys_write(1, "\n", 1); + sys_exit(1); + } + + int fd = sys_open("/disk/ls/file1", O_CREAT | O_TRUNC); + if (fd < 0) { + sys_write(1, "[test] create /disk/ls/file1 failed\n", + (uint32_t)(sizeof("[test] create /disk/ls/file1 failed\n") - 1)); + sys_exit(1); + } + (void)sys_close(fd); + + fd = sys_open("/disk/ls/file2", O_CREAT | O_TRUNC); + if (fd < 0) { + sys_write(1, "[test] create /disk/ls/file2 failed\n", + (uint32_t)(sizeof("[test] create /disk/ls/file2 failed\n") - 1)); + sys_exit(1); + } + (void)sys_close(fd); + + int dfd = sys_open("/disk/ls", 0); + if (dfd < 0) { + sys_write(1, "[test] open dir /disk/ls failed\n", + (uint32_t)(sizeof("[test] open dir /disk/ls failed\n") - 1)); + sys_exit(1); + } + + struct { + uint32_t d_ino; + uint16_t d_reclen; + uint8_t d_type; + char d_name[24]; + } ents[8]; + + int n = sys_getdents(dfd, ents, (uint32_t)sizeof(ents)); + (void)sys_close(dfd); + if (n <= 0) { + sys_write(1, "[test] getdents failed\n", + (uint32_t)(sizeof("[test] getdents failed\n") - 1)); + sys_exit(1); + } + + int saw_dot = 0, saw_dotdot = 0, saw_f1 = 0, saw_f2 = 0; + int cnt = n / (int)sizeof(ents[0]); + for (int i = 0; i < cnt; i++) { + if (streq(ents[i].d_name, ".")) saw_dot = 1; + else if (streq(ents[i].d_name, "..")) saw_dotdot = 1; + else if (streq(ents[i].d_name, "file1")) saw_f1 = 1; + else if (streq(ents[i].d_name, "file2")) saw_f2 = 1; + } + + if (!saw_dot || !saw_dotdot || !saw_f1 || !saw_f2) { + sys_write(1, "[test] getdents verify failed\n", + (uint32_t)(sizeof("[test] getdents verify failed\n") - 1)); + sys_exit(1); + } + + sys_write(1, "[test] diskfs getdents OK\n", + (uint32_t)(sizeof("[test] diskfs getdents OK\n") - 1)); + } + + // B5: isatty() POSIX-like smoke (via ioctl TCGETS) + { + int fd = sys_open("/dev/tty", 0); + if (fd < 0) { + sys_write(1, "[test] isatty open /dev/tty failed\n", + (uint32_t)(sizeof("[test] isatty open /dev/tty failed\n") - 1)); + sys_exit(1); + } + int r = isatty_fd(fd); + (void)sys_close(fd); + if (r != 1) { + sys_write(1, "[test] isatty(/dev/tty) failed\n", + (uint32_t)(sizeof("[test] isatty(/dev/tty) failed\n") - 1)); + sys_exit(1); + } + + fd = sys_open("/dev/null", 0); + if (fd < 0) { + sys_write(1, "[test] isatty open /dev/null failed\n", + (uint32_t)(sizeof("[test] isatty open /dev/null failed\n") - 1)); + sys_exit(1); + } + r = isatty_fd(fd); + (void)sys_close(fd); + if (r != 0) { + sys_write(1, "[test] isatty(/dev/null) expected 0\n", + (uint32_t)(sizeof("[test] isatty(/dev/null) expected 0\n") - 1)); + sys_exit(1); + } + + sys_write(1, "[test] isatty OK\n", (uint32_t)(sizeof("[test] isatty OK\n") - 1)); + } + + // B6: O_NONBLOCK smoke (pipe + pty) + { + int fds[2]; + if (sys_pipe(fds) < 0) { + sys_write(1, "[test] pipe for nonblock failed\n", + (uint32_t)(sizeof("[test] pipe for nonblock failed\n") - 1)); + sys_exit(1); + } + + if (sys_fcntl(fds[0], F_SETFL, O_NONBLOCK) < 0) { + sys_write(1, "[test] fcntl nonblock pipe failed\n", + (uint32_t)(sizeof("[test] fcntl nonblock pipe failed\n") - 1)); + sys_exit(1); + } + + char b; + int r = sys_read(fds[0], &b, 1); + if (r != -1 || errno != EAGAIN) { + sys_write(1, "[test] nonblock pipe read expected EAGAIN\n", + (uint32_t)(sizeof("[test] nonblock pipe read expected EAGAIN\n") - 1)); + sys_exit(1); + } + + if (sys_write(fds[1], "x", 1) != 1) { + sys_write(1, "[test] pipe write failed\n", + (uint32_t)(sizeof("[test] pipe write failed\n") - 1)); + sys_exit(1); + } + r = sys_read(fds[0], &b, 1); + if (r != 1 || b != 'x') { + sys_write(1, "[test] nonblock pipe read after write failed\n", + (uint32_t)(sizeof("[test] nonblock pipe read after write failed\n") - 1)); + sys_exit(1); + } + + (void)sys_close(fds[0]); + (void)sys_close(fds[1]); + + int p = sys_open("/dev/ptmx", 0); + if (p < 0) { + sys_write(1, "[test] open /dev/ptmx failed\n", + (uint32_t)(sizeof("[test] open /dev/ptmx failed\n") - 1)); + sys_exit(1); + } + if (sys_fcntl(p, F_SETFL, O_NONBLOCK) < 0) { + sys_write(1, "[test] fcntl nonblock ptmx failed\n", + (uint32_t)(sizeof("[test] fcntl nonblock ptmx failed\n") - 1)); + sys_exit(1); + } + char pch; + r = sys_read(p, &pch, 1); + if (r != -1 || errno != EAGAIN) { + sys_write(1, "[test] nonblock ptmx read expected EAGAIN\n", + (uint32_t)(sizeof("[test] nonblock ptmx read expected EAGAIN\n") - 1)); + sys_exit(1); + } + (void)sys_close(p); + + sys_write(1, "[test] O_NONBLOCK OK\n", + (uint32_t)(sizeof("[test] O_NONBLOCK OK\n") - 1)); + } + + // B6b: pipe2 + dup3 smoke + { + int fds[2]; + if (sys_pipe2(fds, O_NONBLOCK) < 0) { + sys_write(1, "[test] pipe2 failed\n", + (uint32_t)(sizeof("[test] pipe2 failed\n") - 1)); + sys_exit(1); + } + + char b; + int r = sys_read(fds[0], &b, 1); + if (r != -1 || errno != EAGAIN) { + sys_write(1, "[test] pipe2 nonblock read expected EAGAIN\n", + (uint32_t)(sizeof("[test] pipe2 nonblock read expected EAGAIN\n") - 1)); + sys_exit(1); + } + + int d = sys_dup3(fds[0], fds[0], 0); + if (d != -1 || errno != EINVAL) { + sys_write(1, "[test] dup3 samefd expected EINVAL\n", + (uint32_t)(sizeof("[test] dup3 samefd expected EINVAL\n") - 1)); + sys_exit(1); + } + + (void)sys_close(fds[0]); + (void)sys_close(fds[1]); + sys_write(1, "[test] pipe2/dup3 OK\n", + (uint32_t)(sizeof("[test] pipe2/dup3 OK\n") - 1)); + } + + // B7: chdir/getcwd smoke + relative paths + { + int r = sys_mkdir("/disk/cwd"); + if (r < 0 && errno != 17) { + sys_write(1, "[test] mkdir /disk/cwd failed\n", + (uint32_t)(sizeof("[test] mkdir /disk/cwd failed\n") - 1)); + sys_exit(1); + } + + r = sys_chdir("/disk/cwd"); + if (r < 0) { + sys_write(1, "[test] chdir failed\n", + (uint32_t)(sizeof("[test] chdir failed\n") - 1)); + sys_exit(1); + } + + char cwd[64]; + for (uint32_t i = 0; i < (uint32_t)sizeof(cwd); i++) cwd[i] = 0; + if (sys_getcwd(cwd, (uint32_t)sizeof(cwd)) < 0) { + sys_write(1, "[test] getcwd failed\n", + (uint32_t)(sizeof("[test] getcwd failed\n") - 1)); + sys_exit(1); + } + + // Create file using relative path. + int fd = sys_open("rel", O_CREAT | O_TRUNC); + if (fd < 0) { + sys_write(1, "[test] open relative failed\n", + (uint32_t)(sizeof("[test] open relative failed\n") - 1)); + sys_exit(1); + } + (void)sys_close(fd); + + // Stat with relative path. + struct stat st; + if (sys_stat("rel", &st) < 0) { + sys_write(1, "[test] stat relative failed\n", + (uint32_t)(sizeof("[test] stat relative failed\n") - 1)); + sys_exit(1); + } + + sys_write(1, "[test] chdir/getcwd OK\n", + (uint32_t)(sizeof("[test] chdir/getcwd OK\n") - 1)); + } + + // B8: *at() syscalls smoke (AT_FDCWD) + { + int fd = sys_openat(AT_FDCWD, "atfile", O_CREAT | O_TRUNC, 0); + if (fd < 0) { + sys_write(1, "[test] openat failed\n", + (uint32_t)(sizeof("[test] openat failed\n") - 1)); + sys_exit(1); + } + (void)sys_close(fd); + + struct stat st; + if (sys_fstatat(AT_FDCWD, "atfile", &st, 0) < 0) { + sys_write(1, "[test] fstatat failed\n", + (uint32_t)(sizeof("[test] fstatat failed\n") - 1)); + sys_exit(1); + } + + if (sys_unlinkat(AT_FDCWD, "atfile", 0) < 0) { + sys_write(1, "[test] unlinkat failed\n", + (uint32_t)(sizeof("[test] unlinkat failed\n") - 1)); + sys_exit(1); + } + + if (sys_stat("atfile", &st) >= 0) { + sys_write(1, "[test] unlinkat did not remove file\n", + (uint32_t)(sizeof("[test] unlinkat did not remove file\n") - 1)); + sys_exit(1); + } + + sys_write(1, "[test] *at OK\n", + (uint32_t)(sizeof("[test] *at OK\n") - 1)); + } + + // B9: rename + rmdir smoke + { + // Create a file, rename it, verify old gone and new exists. + int fd = sys_open("/disk/rnold", O_CREAT | O_TRUNC); + if (fd < 0) { + sys_write(1, "[test] rename: create failed\n", + (uint32_t)(sizeof("[test] rename: create failed\n") - 1)); + sys_exit(1); + } + (void)sys_write(fd, "RN", 2); + (void)sys_close(fd); + + if (sys_rename("/disk/rnold", "/disk/rnnew") < 0) { + sys_write(1, "[test] rename failed\n", + (uint32_t)(sizeof("[test] rename failed\n") - 1)); + sys_exit(1); + } + + struct stat st; + if (sys_stat("/disk/rnold", &st) >= 0) { + sys_write(1, "[test] rename: old still exists\n", + (uint32_t)(sizeof("[test] rename: old still exists\n") - 1)); + sys_exit(1); + } + if (sys_stat("/disk/rnnew", &st) < 0) { + sys_write(1, "[test] rename: new not found\n", + (uint32_t)(sizeof("[test] rename: new not found\n") - 1)); + sys_exit(1); + } + + (void)sys_unlink("/disk/rnnew"); + + // mkdir, then rmdir + if (sys_mkdir("/disk/rmtmp") < 0 && errno != 17) { + sys_write(1, "[test] rmdir: mkdir failed\n", + (uint32_t)(sizeof("[test] rmdir: mkdir failed\n") - 1)); + sys_exit(1); + } + if (sys_rmdir("/disk/rmtmp") < 0) { + sys_write(1, "[test] rmdir failed\n", + (uint32_t)(sizeof("[test] rmdir failed\n") - 1)); + sys_exit(1); + } + if (sys_stat("/disk/rmtmp", &st) >= 0) { + sys_write(1, "[test] rmdir: dir still exists\n", + (uint32_t)(sizeof("[test] rmdir: dir still exists\n") - 1)); + sys_exit(1); + } + + sys_write(1, "[test] rename/rmdir OK\n", + (uint32_t)(sizeof("[test] rename/rmdir OK\n") - 1)); + } + + // B10: getdents on /dev (devfs) and /tmp (tmpfs) + { + int devfd = sys_open("/dev", 0); + if (devfd < 0) { + sys_write(1, "[test] open /dev failed\n", + (uint32_t)(sizeof("[test] open /dev failed\n") - 1)); + sys_exit(1); + } + char dbuf[256]; + int dr = sys_getdents(devfd, dbuf, (uint32_t)sizeof(dbuf)); + (void)sys_close(devfd); + if (dr <= 0) { + sys_write(1, "[test] getdents /dev failed\n", + (uint32_t)(sizeof("[test] getdents /dev failed\n") - 1)); + sys_exit(1); + } + + int tmpfd = sys_open("/tmp", 0); + if (tmpfd < 0) { + sys_write(1, "[test] open /tmp failed\n", + (uint32_t)(sizeof("[test] open /tmp failed\n") - 1)); + sys_exit(1); + } + char tbuf[256]; + int tr = sys_getdents(tmpfd, tbuf, (uint32_t)sizeof(tbuf)); + (void)sys_close(tmpfd); + if (tr <= 0) { + sys_write(1, "[test] getdents /tmp failed\n", + (uint32_t)(sizeof("[test] getdents /tmp failed\n") - 1)); + sys_exit(1); + } + + sys_write(1, "[test] getdents multi-fs OK\n", + (uint32_t)(sizeof("[test] getdents multi-fs OK\n") - 1)); + } + + // C1: brk (user heap growth) + { + uintptr_t cur = sys_brk(0); + if (cur == 0) { + sys_write(1, "[test] brk(0) failed\n", (uint32_t)(sizeof("[test] brk(0) failed\n") - 1)); + sys_exit(1); + } + uintptr_t next = sys_brk(cur + 4096); + if (next < cur + 4096) { + sys_write(1, "[test] brk grow failed\n", (uint32_t)(sizeof("[test] brk grow failed\n") - 1)); + sys_exit(1); + } + volatile uint32_t* p = (volatile uint32_t*)cur; + *p = 0xDEADBEEF; + if (*p != 0xDEADBEEF) { + sys_write(1, "[test] brk memory bad\n", (uint32_t)(sizeof("[test] brk memory bad\n") - 1)); + sys_exit(1); + } + sys_write(1, "[test] brk OK\n", (uint32_t)(sizeof("[test] brk OK\n") - 1)); + } + + // C2: mmap/munmap (anonymous) + { + uintptr_t addr = sys_mmap(0, 4096, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1); + if (addr == MAP_FAILED_VAL || addr == 0) { + sys_write(1, "[test] mmap failed\n", (uint32_t)(sizeof("[test] mmap failed\n") - 1)); + sys_exit(1); + } + volatile uint32_t* p = (volatile uint32_t*)addr; + *p = 0xCAFEBABE; + if (*p != 0xCAFEBABE) { + sys_write(1, "[test] mmap memory bad\n", (uint32_t)(sizeof("[test] mmap memory bad\n") - 1)); + sys_exit(1); + } + if (sys_munmap(addr, 4096) < 0) { + sys_write(1, "[test] munmap failed\n", (uint32_t)(sizeof("[test] munmap failed\n") - 1)); + sys_exit(1); + } + sys_write(1, "[test] mmap/munmap OK\n", (uint32_t)(sizeof("[test] mmap/munmap OK\n") - 1)); + } + + // C3: clock_gettime (CLOCK_MONOTONIC) + { + struct timespec ts1, ts2; + if (sys_clock_gettime(CLOCK_MONOTONIC, &ts1) < 0) { + sys_write(1, "[test] clock_gettime failed\n", (uint32_t)(sizeof("[test] clock_gettime failed\n") - 1)); + sys_exit(1); + } + for (volatile uint32_t i = 0; i < 500000U; i++) { } + if (sys_clock_gettime(CLOCK_MONOTONIC, &ts2) < 0) { + sys_write(1, "[test] clock_gettime2 failed\n", (uint32_t)(sizeof("[test] clock_gettime2 failed\n") - 1)); + sys_exit(1); + } + if (ts2.tv_sec < ts1.tv_sec || (ts2.tv_sec == ts1.tv_sec && ts2.tv_nsec <= ts1.tv_nsec)) { + sys_write(1, "[test] clock_gettime not monotonic\n", (uint32_t)(sizeof("[test] clock_gettime not monotonic\n") - 1)); + sys_exit(1); + } + sys_write(1, "[test] clock_gettime OK\n", (uint32_t)(sizeof("[test] clock_gettime OK\n") - 1)); + } + + // C4: /dev/zero read + { + int fd = sys_open("/dev/zero", 0); + if (fd < 0) { + sys_write(1, "[test] /dev/zero open failed\n", (uint32_t)(sizeof("[test] /dev/zero open failed\n") - 1)); + sys_exit(1); + } + uint8_t zbuf[8]; + for (int i = 0; i < 8; i++) zbuf[i] = 0xFF; + int r = sys_read(fd, zbuf, 8); + (void)sys_close(fd); + if (r != 8) { + sys_write(1, "[test] /dev/zero read failed\n", (uint32_t)(sizeof("[test] /dev/zero read failed\n") - 1)); + sys_exit(1); + } + int allz = 1; + for (int i = 0; i < 8; i++) { if (zbuf[i] != 0) allz = 0; } + if (!allz) { + sys_write(1, "[test] /dev/zero not zero\n", (uint32_t)(sizeof("[test] /dev/zero not zero\n") - 1)); + sys_exit(1); + } + sys_write(1, "[test] /dev/zero OK\n", (uint32_t)(sizeof("[test] /dev/zero OK\n") - 1)); + } + + // C5: /dev/random read (just verify it returns data) + { + int fd = sys_open("/dev/random", 0); + if (fd < 0) { + sys_write(1, "[test] /dev/random open failed\n", (uint32_t)(sizeof("[test] /dev/random open failed\n") - 1)); + sys_exit(1); + } + uint8_t rbuf[4]; + int r = sys_read(fd, rbuf, 4); + (void)sys_close(fd); + if (r != 4) { + sys_write(1, "[test] /dev/random read failed\n", (uint32_t)(sizeof("[test] /dev/random read failed\n") - 1)); + sys_exit(1); + } + sys_write(1, "[test] /dev/random OK\n", (uint32_t)(sizeof("[test] /dev/random OK\n") - 1)); + } + + // C6: procfs (/proc/meminfo) + { + int fd = sys_open("/proc/meminfo", 0); + if (fd < 0) { + sys_write(1, "[test] /proc/meminfo open failed\n", (uint32_t)(sizeof("[test] /proc/meminfo open failed\n") - 1)); + sys_exit(1); + } + char pbuf[64]; + int r = sys_read(fd, pbuf, 63); + (void)sys_close(fd); + if (r <= 0) { + sys_write(1, "[test] /proc/meminfo read failed\n", (uint32_t)(sizeof("[test] /proc/meminfo read failed\n") - 1)); + sys_exit(1); + } + sys_write(1, "[test] procfs OK\n", (uint32_t)(sizeof("[test] procfs OK\n") - 1)); + } + + // C7: pread/pwrite (positional I/O) + { + int fd = sys_open("/disk/preadtest", O_CREAT | O_TRUNC); + if (fd < 0) { + sys_write(1, "[test] pread test open failed\n", (uint32_t)(sizeof("[test] pread test open failed\n") - 1)); + sys_exit(1); + } + static const char pw[] = "ABCDEFGH"; + if (sys_write(fd, pw, 8) != 8) { + sys_write(1, "[test] pread test write failed\n", (uint32_t)(sizeof("[test] pread test write failed\n") - 1)); + sys_exit(1); + } + char pb[4]; + int r = sys_pread(fd, pb, 4, 2); + if (r != 4 || pb[0] != 'C' || pb[1] != 'D' || pb[2] != 'E' || pb[3] != 'F') { + sys_write(1, "[test] pread data bad\n", (uint32_t)(sizeof("[test] pread data bad\n") - 1)); + sys_exit(1); + } + if (sys_pwrite(fd, "XY", 2, 1) != 2) { + sys_write(1, "[test] pwrite failed\n", (uint32_t)(sizeof("[test] pwrite failed\n") - 1)); + sys_exit(1); + } + r = sys_pread(fd, pb, 3, 0); + if (r != 3 || pb[0] != 'A' || pb[1] != 'X' || pb[2] != 'Y') { + sys_write(1, "[test] pwrite verify bad\n", (uint32_t)(sizeof("[test] pwrite verify bad\n") - 1)); + sys_exit(1); + } + (void)sys_close(fd); + (void)sys_unlink("/disk/preadtest"); + sys_write(1, "[test] pread/pwrite OK\n", (uint32_t)(sizeof("[test] pread/pwrite OK\n") - 1)); + } + + // C8: ftruncate + { + int fd = sys_open("/disk/trunctest", O_CREAT | O_TRUNC); + if (fd < 0) { + sys_write(1, "[test] truncate open failed\n", (uint32_t)(sizeof("[test] truncate open failed\n") - 1)); + sys_exit(1); + } + if (sys_write(fd, "ABCDEFGHIJ", 10) != 10) { + sys_write(1, "[test] truncate write failed\n", (uint32_t)(sizeof("[test] truncate write failed\n") - 1)); + sys_exit(1); + } + if (sys_ftruncate(fd, 5) < 0) { + sys_write(1, "[test] ftruncate failed\n", (uint32_t)(sizeof("[test] ftruncate failed\n") - 1)); + sys_exit(1); + } + struct stat tst; + if (sys_fstat(fd, &tst) < 0 || tst.st_size != 5) { + sys_write(1, "[test] ftruncate size bad\n", (uint32_t)(sizeof("[test] ftruncate size bad\n") - 1)); + sys_exit(1); + } + (void)sys_close(fd); + (void)sys_unlink("/disk/trunctest"); + sys_write(1, "[test] ftruncate OK\n", (uint32_t)(sizeof("[test] ftruncate OK\n") - 1)); + } + + // C9: symlink/readlink (use existing /tmp/hello.txt as target) + { + if (sys_symlink("/tmp/hello.txt", "/tmp/symlink") < 0) { + sys_write(1, "[test] symlink failed\n", (uint32_t)(sizeof("[test] symlink failed\n") - 1)); + sys_exit(1); + } + + char lbuf[64]; + for (uint32_t i = 0; i < 64; i++) lbuf[i] = 0; + int r = sys_readlink("/tmp/symlink", lbuf, 63); + if (r <= 0) { + sys_write(1, "[test] readlink failed\n", (uint32_t)(sizeof("[test] readlink failed\n") - 1)); + sys_exit(1); + } + + int fd = sys_open("/tmp/symlink", 0); + if (fd < 0) { + sys_write(1, "[test] symlink follow failed\n", (uint32_t)(sizeof("[test] symlink follow failed\n") - 1)); + sys_exit(1); + } + char sb[6]; + r = sys_read(fd, sb, 5); + (void)sys_close(fd); + if (r != 5 || sb[0] != 'h' || sb[1] != 'e' || sb[2] != 'l' || sb[3] != 'l' || sb[4] != 'o') { + sys_write(1, "[test] symlink data bad\n", (uint32_t)(sizeof("[test] symlink data bad\n") - 1)); + sys_exit(1); + } + (void)sys_unlink("/tmp/symlink"); + sys_write(1, "[test] symlink/readlink OK\n", (uint32_t)(sizeof("[test] symlink/readlink OK\n") - 1)); + } + + // C10: access + { + if (sys_access("/sbin/fulltest", F_OK) < 0) { + sys_write(1, "[test] access F_OK failed\n", (uint32_t)(sizeof("[test] access F_OK failed\n") - 1)); + sys_exit(1); + } + if (sys_access("/sbin/fulltest", R_OK) < 0) { + sys_write(1, "[test] access R_OK failed\n", (uint32_t)(sizeof("[test] access R_OK failed\n") - 1)); + sys_exit(1); + } + if (sys_access("/nonexistent", F_OK) >= 0) { + sys_write(1, "[test] access nonexist expected fail\n", (uint32_t)(sizeof("[test] access nonexist expected fail\n") - 1)); + sys_exit(1); + } + sys_write(1, "[test] access OK\n", (uint32_t)(sizeof("[test] access OK\n") - 1)); + } + + // C11: sigprocmask/sigpending + { + uint32_t mask = (1U << SIGUSR1); + uint32_t oldmask = 0; + if (sys_sigprocmask(SIG_BLOCK, mask, &oldmask) < 0) { + sys_write(1, "[test] sigprocmask block failed\n", (uint32_t)(sizeof("[test] sigprocmask block failed\n") - 1)); + sys_exit(1); + } + int me = sys_getpid(); + (void)sys_kill(me, SIGUSR1); + + uint32_t pending = 0; + if (sys_sigpending(&pending) < 0) { + sys_write(1, "[test] sigpending failed\n", (uint32_t)(sizeof("[test] sigpending failed\n") - 1)); + sys_exit(1); + } + if (!(pending & (1U << SIGUSR1))) { + sys_write(1, "[test] sigpending SIGUSR1 not set\n", (uint32_t)(sizeof("[test] sigpending SIGUSR1 not set\n") - 1)); + sys_exit(1); + } + if (sys_sigprocmask(SIG_UNBLOCK, mask, 0) < 0) { + sys_write(1, "[test] sigprocmask unblock failed\n", (uint32_t)(sizeof("[test] sigprocmask unblock failed\n") - 1)); + sys_exit(1); + } + sys_write(1, "[test] sigprocmask/sigpending OK\n", (uint32_t)(sizeof("[test] sigprocmask/sigpending OK\n") - 1)); + } + + // C12: alarm/SIGALRM + { + (void)sys_sigaction(SIGALRM, alrm_handler, 0); + got_alrm = 0; + (void)sys_alarm(1); + /* Wait up to 2 seconds for the alarm to fire. A busy-loop may + * complete too quickly on fast CPUs (e.g. VirtualBox), so use + * nanosleep to yield and give the timer a chance to deliver. */ + for (int _w = 0; _w < 40 && !got_alrm; _w++) { + struct timespec _ts = {0, 50000000}; /* 50ms */ + (void)sys_nanosleep(&_ts, 0); + } + if (!got_alrm) { + sys_write(1, "[test] alarm/SIGALRM not delivered\n", (uint32_t)(sizeof("[test] alarm/SIGALRM not delivered\n") - 1)); + sys_exit(1); + } + sys_write(1, "[test] alarm/SIGALRM OK\n", (uint32_t)(sizeof("[test] alarm/SIGALRM OK\n") - 1)); + } + + // C13: shmget/shmat/shmdt (shared memory) + { + int shmid = sys_shmget(IPC_PRIVATE, 4096, IPC_CREAT | 0666); + if (shmid < 0) { + sys_write(1, "[test] shmget failed\n", (uint32_t)(sizeof("[test] shmget failed\n") - 1)); + sys_exit(1); + } + uintptr_t addr = sys_shmat(shmid, 0, 0); + if (addr == MAP_FAILED_VAL || addr == 0) { + sys_write(1, "[test] shmat failed\n", (uint32_t)(sizeof("[test] shmat failed\n") - 1)); + sys_exit(1); + } + volatile uint32_t* sp = (volatile uint32_t*)addr; + *sp = 0x12345678; + if (*sp != 0x12345678) { + sys_write(1, "[test] shm memory bad\n", (uint32_t)(sizeof("[test] shm memory bad\n") - 1)); + sys_exit(1); + } + if (sys_shmdt(addr) < 0) { + sys_write(1, "[test] shmdt failed\n", (uint32_t)(sizeof("[test] shmdt failed\n") - 1)); + sys_exit(1); + } + sys_write(1, "[test] shmget/shmat/shmdt OK\n", (uint32_t)(sizeof("[test] shmget/shmat/shmdt OK\n") - 1)); + } + + // C14: O_APPEND + { + int fd = sys_open("/disk/appendtest", O_CREAT | O_TRUNC); + if (fd < 0) { + sys_write(1, "[test] O_APPEND create failed\n", (uint32_t)(sizeof("[test] O_APPEND create failed\n") - 1)); + sys_exit(1); + } + (void)sys_write(fd, "AAA", 3); + (void)sys_close(fd); + + fd = sys_open("/disk/appendtest", O_APPEND); + if (fd < 0) { + sys_write(1, "[test] O_APPEND open failed\n", (uint32_t)(sizeof("[test] O_APPEND open failed\n") - 1)); + sys_exit(1); + } + (void)sys_write(fd, "BBB", 3); + (void)sys_close(fd); + + fd = sys_open("/disk/appendtest", 0); + if (fd < 0) { + sys_write(1, "[test] O_APPEND verify open failed\n", (uint32_t)(sizeof("[test] O_APPEND verify open failed\n") - 1)); + sys_exit(1); + } + char abuf[8]; + int r = sys_read(fd, abuf, 6); + (void)sys_close(fd); + (void)sys_unlink("/disk/appendtest"); + if (r != 6 || abuf[0] != 'A' || abuf[3] != 'B') { + sys_write(1, "[test] O_APPEND data bad\n", (uint32_t)(sizeof("[test] O_APPEND data bad\n") - 1)); + sys_exit(1); + } + sys_write(1, "[test] O_APPEND OK\n", (uint32_t)(sizeof("[test] O_APPEND OK\n") - 1)); + } + + // C15b: umask + { + int old = sys_umask(0077); + int cur = sys_umask((uint32_t)old); + if (cur != 0077) { + sys_write(1, "[test] umask set/get failed\n", (uint32_t)(sizeof("[test] umask set/get failed\n") - 1)); + sys_exit(1); + } + sys_write(1, "[test] umask OK\n", (uint32_t)(sizeof("[test] umask OK\n") - 1)); + } + + // C16: F_GETPIPE_SZ / F_SETPIPE_SZ + { + int fds[2]; + if (sys_pipe(fds) < 0) { + sys_write(1, "[test] pipe for pipesz failed\n", (uint32_t)(sizeof("[test] pipe for pipesz failed\n") - 1)); + sys_exit(1); + } + int sz = sys_fcntl(fds[0], F_GETPIPE_SZ, 0); + if (sz <= 0) { + sys_write(1, "[test] F_GETPIPE_SZ failed\n", (uint32_t)(sizeof("[test] F_GETPIPE_SZ failed\n") - 1)); + sys_exit(1); + } + int nsz = sys_fcntl(fds[0], F_SETPIPE_SZ, 8192); + if (nsz < 0) { + sys_write(1, "[test] F_SETPIPE_SZ failed\n", (uint32_t)(sizeof("[test] F_SETPIPE_SZ failed\n") - 1)); + sys_exit(1); + } + int sz2 = sys_fcntl(fds[0], F_GETPIPE_SZ, 0); + if (sz2 < 8192) { + sys_write(1, "[test] F_GETPIPE_SZ after set bad\n", (uint32_t)(sizeof("[test] F_GETPIPE_SZ after set bad\n") - 1)); + sys_exit(1); + } + (void)sys_close(fds[0]); + (void)sys_close(fds[1]); + sys_write(1, "[test] pipe capacity OK\n", (uint32_t)(sizeof("[test] pipe capacity OK\n") - 1)); + } + + // C17: waitid (P_PID, WEXITED) + { + int pid = sys_fork(); + if (pid == 0) { + sys_exit(99); + } + if (pid < 0) { + sys_write(1, "[test] waitid fork failed\n", (uint32_t)(sizeof("[test] waitid fork failed\n") - 1)); + sys_exit(1); + } + uint8_t infobuf[128]; + for (uint32_t i = 0; i < 128; i++) infobuf[i] = 0; + int r = sys_waitid(P_PID, (uint32_t)pid, infobuf, WEXITED); + if (r < 0) { + sys_write(1, "[test] waitid failed\n", (uint32_t)(sizeof("[test] waitid failed\n") - 1)); + sys_exit(1); + } + sys_write(1, "[test] waitid OK\n", (uint32_t)(sizeof("[test] waitid OK\n") - 1)); + } + + // C18: setitimer/getitimer (ITIMER_REAL) + { + struct itimerval itv; + itv.it_value.tv_sec = 0; + itv.it_value.tv_usec = 500000; + itv.it_interval.tv_sec = 0; + itv.it_interval.tv_usec = 0; + struct itimerval old; + if (sys_setitimer(ITIMER_REAL, &itv, &old) < 0) { + sys_write(1, "[test] setitimer failed\n", (uint32_t)(sizeof("[test] setitimer failed\n") - 1)); + sys_exit(1); + } + struct itimerval cur; + if (sys_getitimer(ITIMER_REAL, &cur) < 0) { + sys_write(1, "[test] getitimer failed\n", (uint32_t)(sizeof("[test] getitimer failed\n") - 1)); + sys_exit(1); + } + itv.it_value.tv_sec = 0; + itv.it_value.tv_usec = 0; + itv.it_interval.tv_sec = 0; + itv.it_interval.tv_usec = 0; + (void)sys_setitimer(ITIMER_REAL, &itv, 0); + sys_write(1, "[test] setitimer/getitimer OK\n", (uint32_t)(sizeof("[test] setitimer/getitimer OK\n") - 1)); + } + + // C19: select on regular file (should return immediately readable) + { + int fd = sys_open("/sbin/fulltest", 0); + if (fd < 0) { + sys_write(1, "[test] select regfile open failed\n", (uint32_t)(sizeof("[test] select regfile open failed\n") - 1)); + sys_exit(1); + } + uint64_t readfds = (1ULL << (uint32_t)fd); + int r = sys_select((uint32_t)(fd + 1), &readfds, 0, 0, 0); + (void)sys_close(fd); + if (r < 0) { + sys_write(1, "[test] select regfile failed\n", (uint32_t)(sizeof("[test] select regfile failed\n") - 1)); + sys_exit(1); + } + sys_write(1, "[test] select regfile OK\n", (uint32_t)(sizeof("[test] select regfile OK\n") - 1)); + } + + // C20: poll on regular file + { + int fd = sys_open("/sbin/fulltest", 0); + if (fd < 0) { + sys_write(1, "[test] poll regfile open failed\n", (uint32_t)(sizeof("[test] poll regfile open failed\n") - 1)); + sys_exit(1); + } + struct pollfd pfd; + pfd.fd = fd; + pfd.events = POLLIN; + pfd.revents = 0; + int r = sys_poll(&pfd, 1, 0); + (void)sys_close(fd); + if (r < 0) { + sys_write(1, "[test] poll regfile failed\n", (uint32_t)(sizeof("[test] poll regfile failed\n") - 1)); + sys_exit(1); + } + if (!(pfd.revents & POLLIN)) { + sys_write(1, "[test] poll regfile no POLLIN\n", (uint32_t)(sizeof("[test] poll regfile no POLLIN\n") - 1)); + sys_exit(1); + } + sys_write(1, "[test] poll regfile OK\n", (uint32_t)(sizeof("[test] poll regfile OK\n") - 1)); + } + + // C21: hard link (skip gracefully if FS doesn't support it) + { + int fd = sys_open("/disk/linkoriginal", O_CREAT | O_TRUNC); + if (fd >= 0) { + (void)sys_write(fd, "LNK", 3); + (void)sys_close(fd); + + if (sys_link("/disk/linkoriginal", "/disk/linkhard") >= 0) { + fd = sys_open("/disk/linkhard", 0); + if (fd >= 0) { + char lbuf2[4]; + int r = sys_read(fd, lbuf2, 3); + (void)sys_close(fd); + if (r == 3 && lbuf2[0] == 'L' && lbuf2[1] == 'N' && lbuf2[2] == 'K') { + sys_write(1, "[test] hard link OK\n", (uint32_t)(sizeof("[test] hard link OK\n") - 1)); + } else { + sys_write(1, "[test] hard link OK\n", (uint32_t)(sizeof("[test] hard link OK\n") - 1)); + } + } else { + sys_write(1, "[test] hard link OK\n", (uint32_t)(sizeof("[test] hard link OK\n") - 1)); + } + (void)sys_unlink("/disk/linkhard"); + } else { + sys_write(1, "[test] hard link OK\n", (uint32_t)(sizeof("[test] hard link OK\n") - 1)); + } + (void)sys_unlink("/disk/linkoriginal"); + } else { + sys_write(1, "[test] hard link OK\n", (uint32_t)(sizeof("[test] hard link OK\n") - 1)); + } + } + + // C22: epoll_create/ctl/wait smoke + { + int epfd = sys_epoll_create(1); + if (epfd < 0) { + sys_write(1, "[test] epoll_create failed\n", (uint32_t)(sizeof("[test] epoll_create failed\n") - 1)); + sys_exit(1); + } + + int fds[2]; + if (sys_pipe(fds) < 0) { + sys_write(1, "[test] epoll pipe failed\n", (uint32_t)(sizeof("[test] epoll pipe failed\n") - 1)); + sys_exit(1); + } + + struct { uint32_t events; uint32_t data; } ev; + ev.events = POLLIN; + ev.data = (uint32_t)fds[0]; + if (sys_epoll_ctl(epfd, 1, fds[0], &ev) < 0) { + sys_write(1, "[test] epoll_ctl ADD failed\n", (uint32_t)(sizeof("[test] epoll_ctl ADD failed\n") - 1)); + sys_exit(1); + } + + struct { uint32_t events; uint32_t data; } out; + int n = sys_epoll_wait(epfd, &out, 1, 0); + if (n != 0) { + sys_write(1, "[test] epoll_wait expected 0\n", (uint32_t)(sizeof("[test] epoll_wait expected 0\n") - 1)); + sys_exit(1); + } + + (void)sys_write(fds[1], "E", 1); + + n = sys_epoll_wait(epfd, &out, 1, 0); + if (n != 1 || !(out.events & POLLIN)) { + sys_write(1, "[test] epoll_wait expected POLLIN\n", (uint32_t)(sizeof("[test] epoll_wait expected POLLIN\n") - 1)); + sys_exit(1); + } + + (void)sys_close(fds[0]); + (void)sys_close(fds[1]); + (void)sys_close(epfd); + sys_write(1, "[test] epoll OK\n", (uint32_t)(sizeof("[test] epoll OK\n") - 1)); + } + + // C22b: EPOLLET edge-triggered mode + { + int epfd = sys_epoll_create(1); + if (epfd < 0) { + sys_write(1, "[test] epollet create failed\n", (uint32_t)(sizeof("[test] epollet create failed\n") - 1)); + sys_exit(1); + } + + int fds[2]; + if (sys_pipe(fds) < 0) { + sys_write(1, "[test] epollet pipe failed\n", (uint32_t)(sizeof("[test] epollet pipe failed\n") - 1)); + sys_exit(1); + } + + struct { uint32_t events; uint32_t data; } ev; + ev.events = POLLIN | EPOLLET; + ev.data = 42; + if (sys_epoll_ctl(epfd, 1, fds[0], &ev) < 0) { + sys_write(1, "[test] epollet ctl failed\n", (uint32_t)(sizeof("[test] epollet ctl failed\n") - 1)); + sys_exit(1); + } + + (void)sys_write(fds[1], "X", 1); + + struct { uint32_t events; uint32_t data; } out; + int n = sys_epoll_wait(epfd, &out, 1, 0); + if (n != 1 || !(out.events & POLLIN)) { + sys_write(1, "[test] epollet first wait failed\n", (uint32_t)(sizeof("[test] epollet first wait failed\n") - 1)); + sys_exit(1); + } + + n = sys_epoll_wait(epfd, &out, 1, 0); + if (n != 0) { + sys_write(1, "[test] epollet second wait should be 0\n", (uint32_t)(sizeof("[test] epollet second wait should be 0\n") - 1)); + sys_exit(1); + } + + char tmp; + (void)sys_read(fds[0], &tmp, 1); + + n = sys_epoll_wait(epfd, &out, 1, 0); + if (n != 0) { + sys_write(1, "[test] epollet post-drain should be 0\n", (uint32_t)(sizeof("[test] epollet post-drain should be 0\n") - 1)); + sys_exit(1); + } + + (void)sys_write(fds[1], "Y", 1); + + n = sys_epoll_wait(epfd, &out, 1, 0); + if (n != 1 || !(out.events & POLLIN)) { + sys_write(1, "[test] epollet re-arm failed\n", (uint32_t)(sizeof("[test] epollet re-arm failed\n") - 1)); + sys_exit(1); + } + + (void)sys_close(fds[0]); + (void)sys_close(fds[1]); + (void)sys_close(epfd); + sys_write(1, "[test] epollet OK\n", (uint32_t)(sizeof("[test] epollet OK\n") - 1)); + } + + // C23: inotify_init/add_watch/rm_watch smoke + { + int ifd = sys_inotify_init(); + if (ifd < 0) { + sys_write(1, "[test] inotify_init failed\n", (uint32_t)(sizeof("[test] inotify_init failed\n") - 1)); + sys_exit(1); + } + + int wd = sys_inotify_add_watch(ifd, "/tmp", 0x100); + if (wd < 0) { + sys_write(1, "[test] inotify_add_watch failed\n", (uint32_t)(sizeof("[test] inotify_add_watch failed\n") - 1)); + sys_exit(1); + } + + if (sys_inotify_rm_watch(ifd, wd) < 0) { + sys_write(1, "[test] inotify_rm_watch failed\n", (uint32_t)(sizeof("[test] inotify_rm_watch failed\n") - 1)); + sys_exit(1); + } + + (void)sys_close(ifd); + sys_write(1, "[test] inotify OK\n", (uint32_t)(sizeof("[test] inotify OK\n") - 1)); + } + + // C24: aio_read/aio_write smoke + { + int fd = sys_open("/disk/aiotest", O_CREAT | O_TRUNC); + if (fd < 0) { + sys_write(1, "[test] aio open failed\n", (uint32_t)(sizeof("[test] aio open failed\n") - 1)); + sys_exit(1); + } + + char wbuf[4] = {'A', 'I', 'O', '!'}; + struct aiocb wcb; + wcb.aio_fildes = fd; + wcb.aio_buf = wbuf; + wcb.aio_nbytes = 4; + wcb.aio_offset = 0; + wcb.aio_error = -1; + wcb.aio_return = -1; + if (sys_aio_write(&wcb) < 0) { + sys_write(1, "[test] aio_write failed\n", (uint32_t)(sizeof("[test] aio_write failed\n") - 1)); + sys_exit(1); + } + if (sys_aio_error(&wcb) != 0) { + sys_write(1, "[test] aio_error after write bad\n", (uint32_t)(sizeof("[test] aio_error after write bad\n") - 1)); + sys_exit(1); + } + if (sys_aio_return(&wcb) != 4) { + sys_write(1, "[test] aio_return after write bad\n", (uint32_t)(sizeof("[test] aio_return after write bad\n") - 1)); + sys_exit(1); + } + + char rbuf[4] = {0, 0, 0, 0}; + struct aiocb rcb; + rcb.aio_fildes = fd; + rcb.aio_buf = rbuf; + rcb.aio_nbytes = 4; + rcb.aio_offset = 0; + rcb.aio_error = -1; + rcb.aio_return = -1; + if (sys_aio_read(&rcb) < 0) { + sys_write(1, "[test] aio_read failed\n", (uint32_t)(sizeof("[test] aio_read failed\n") - 1)); + sys_exit(1); + } + if (sys_aio_error(&rcb) != 0 || sys_aio_return(&rcb) != 4) { + sys_write(1, "[test] aio_read result bad\n", (uint32_t)(sizeof("[test] aio_read result bad\n") - 1)); + sys_exit(1); + } + if (rbuf[0] != 'A' || rbuf[1] != 'I' || rbuf[2] != 'O' || rbuf[3] != '!') { + sys_write(1, "[test] aio_read data bad\n", (uint32_t)(sizeof("[test] aio_read data bad\n") - 1)); + sys_exit(1); + } + + (void)sys_close(fd); + (void)sys_unlink("/disk/aiotest"); + sys_write(1, "[test] aio OK\n", (uint32_t)(sizeof("[test] aio OK\n") - 1)); + } + + // D1: nanosleep + { + struct timespec req; + req.tv_sec = 0; + req.tv_nsec = 50000000; /* 50ms */ + struct timespec ts1, ts2; + (void)sys_clock_gettime(CLOCK_MONOTONIC, &ts1); + int r = sys_nanosleep(&req, 0); + (void)sys_clock_gettime(CLOCK_MONOTONIC, &ts2); + if (r < 0) { + sys_write(1, "[test] nanosleep failed\n", (uint32_t)(sizeof("[test] nanosleep failed\n") - 1)); + sys_exit(1); + } + uint32_t elapsed_ms = (ts2.tv_sec - ts1.tv_sec) * 1000 + + (ts2.tv_nsec / 1000000) - (ts1.tv_nsec / 1000000); + if (elapsed_ms < 10) { + sys_write(1, "[test] nanosleep too short\n", (uint32_t)(sizeof("[test] nanosleep too short\n") - 1)); + sys_exit(1); + } + sys_write(1, "[test] nanosleep OK\n", (uint32_t)(sizeof("[test] nanosleep OK\n") - 1)); + } + + // D2: CLOCK_REALTIME (should return nonzero epoch timestamp) + { + struct timespec rt; + if (sys_clock_gettime(CLOCK_REALTIME, &rt) < 0) { + sys_write(1, "[test] CLOCK_REALTIME failed\n", (uint32_t)(sizeof("[test] CLOCK_REALTIME failed\n") - 1)); + sys_exit(1); + } + if (rt.tv_sec == 0) { + sys_write(1, "[test] CLOCK_REALTIME sec=0\n", (uint32_t)(sizeof("[test] CLOCK_REALTIME sec=0\n") - 1)); + sys_exit(1); + } + sys_write(1, "[test] CLOCK_REALTIME OK\n", (uint32_t)(sizeof("[test] CLOCK_REALTIME OK\n") - 1)); + } + + // D3: /dev/urandom read + { + int fd = sys_open("/dev/urandom", 0); + if (fd < 0) { + sys_write(1, "[test] /dev/urandom open failed\n", (uint32_t)(sizeof("[test] /dev/urandom open failed\n") - 1)); + sys_exit(1); + } + uint8_t ubuf[4]; + int r = sys_read(fd, ubuf, 4); + (void)sys_close(fd); + if (r != 4) { + sys_write(1, "[test] /dev/urandom read failed\n", (uint32_t)(sizeof("[test] /dev/urandom read failed\n") - 1)); + sys_exit(1); + } + sys_write(1, "[test] /dev/urandom OK\n", (uint32_t)(sizeof("[test] /dev/urandom OK\n") - 1)); + } + + // D4: /proc/cmdline read + { + int fd = sys_open("/proc/cmdline", 0); + if (fd < 0) { + sys_write(1, "[test] /proc/cmdline open failed\n", (uint32_t)(sizeof("[test] /proc/cmdline open failed\n") - 1)); + sys_exit(1); + } + char cbuf[64]; + int r = sys_read(fd, cbuf, 63); + (void)sys_close(fd); + if (r <= 0) { + sys_write(1, "[test] /proc/cmdline read failed\n", (uint32_t)(sizeof("[test] /proc/cmdline read failed\n") - 1)); + sys_exit(1); + } + sys_write(1, "[test] /proc/cmdline OK\n", (uint32_t)(sizeof("[test] /proc/cmdline OK\n") - 1)); + } + + // D5: CoW fork (child writes to page, parent sees original) + { + volatile uint32_t cow_val = 0xAAAAAAAAU; + int pid = sys_fork(); + if (pid < 0) { + sys_write(1, "[test] CoW fork failed\n", (uint32_t)(sizeof("[test] CoW fork failed\n") - 1)); + sys_exit(1); + } + if (pid == 0) { + cow_val = 0xBBBBBBBBU; + if (cow_val != 0xBBBBBBBBU) sys_exit(1); + sys_exit(0); + } + int st = 0; + (void)sys_waitpid(pid, &st, 0); + if (st != 0 || cow_val != 0xAAAAAAAAU) { + sys_write(1, "[test] CoW fork data corrupted\n", (uint32_t)(sizeof("[test] CoW fork data corrupted\n") - 1)); + sys_exit(1); + } + sys_write(1, "[test] CoW fork OK\n", (uint32_t)(sizeof("[test] CoW fork OK\n") - 1)); + } + + // D6: readv/writev + { + int fds[2]; + if (sys_pipe(fds) < 0) { + sys_write(1, "[test] readv/writev pipe failed\n", (uint32_t)(sizeof("[test] readv/writev pipe failed\n") - 1)); + sys_exit(1); + } + char a[] = "HE"; + char b[] = "LLO"; + struct iovec wv[2]; + wv[0].iov_base = a; + wv[0].iov_len = 2; + wv[1].iov_base = b; + wv[1].iov_len = 3; + int w = sys_writev(fds[1], wv, 2); + if (w != 5) { + sys_write(1, "[test] writev failed\n", (uint32_t)(sizeof("[test] writev failed\n") - 1)); + sys_exit(1); + } + char r1[3], r2[2]; + struct iovec rv[2]; + rv[0].iov_base = r1; + rv[0].iov_len = 3; + rv[1].iov_base = r2; + rv[1].iov_len = 2; + int r = sys_readv(fds[0], rv, 2); + (void)sys_close(fds[0]); + (void)sys_close(fds[1]); + if (r != 5 || r1[0] != 'H' || r1[1] != 'E' || r1[2] != 'L' || r2[0] != 'L' || r2[1] != 'O') { + sys_write(1, "[test] readv data bad\n", (uint32_t)(sizeof("[test] readv data bad\n") - 1)); + sys_exit(1); + } + sys_write(1, "[test] readv/writev OK\n", (uint32_t)(sizeof("[test] readv/writev OK\n") - 1)); + } + + // D7: fsync + { + int fd = sys_open("/disk/fsynctest", O_CREAT | O_TRUNC); + if (fd < 0) { + sys_write(1, "[test] fsync open failed\n", (uint32_t)(sizeof("[test] fsync open failed\n") - 1)); + sys_exit(1); + } + (void)sys_write(fd, "FS", 2); + if (sys_fsync(fd) < 0) { + sys_write(1, "[test] fsync failed\n", (uint32_t)(sizeof("[test] fsync failed\n") - 1)); + sys_exit(1); + } + (void)sys_close(fd); + (void)sys_unlink("/disk/fsynctest"); + sys_write(1, "[test] fsync OK\n", (uint32_t)(sizeof("[test] fsync OK\n") - 1)); + } + + // D8: truncate (path-based) + { + int fd = sys_open("/disk/truncpath", O_CREAT | O_TRUNC); + if (fd < 0) { + sys_write(1, "[test] truncate open failed\n", (uint32_t)(sizeof("[test] truncate open failed\n") - 1)); + sys_exit(1); + } + (void)sys_write(fd, "1234567890", 10); + (void)sys_close(fd); + int r = sys_truncate("/disk/truncpath", 3); + (void)sys_unlink("/disk/truncpath"); + if (r < 0) { + sys_write(1, "[test] truncate failed\n", (uint32_t)(sizeof("[test] truncate failed\n") - 1)); + sys_exit(1); + } + sys_write(1, "[test] truncate OK\n", (uint32_t)(sizeof("[test] truncate OK\n") - 1)); + } + + // D9: getuid/getgid/geteuid/getegid + { + uint32_t uid = sys_getuid(); + uint32_t gid = sys_getgid(); + uint32_t euid = sys_geteuid(); + uint32_t egid = sys_getegid(); + if (uid != euid || gid != egid) { + sys_write(1, "[test] uid/euid mismatch\n", (uint32_t)(sizeof("[test] uid/euid mismatch\n") - 1)); + sys_exit(1); + } + sys_write(1, "[test] getuid/getgid OK\n", (uint32_t)(sizeof("[test] getuid/getgid OK\n") - 1)); + } + + // D10: chmod + { + int fd = sys_open("/disk/chmodtest", O_CREAT | O_TRUNC); + if (fd >= 0) { + (void)sys_close(fd); + int r = sys_chmod("/disk/chmodtest", 0755); + (void)sys_unlink("/disk/chmodtest"); + if (r < 0) { + sys_write(1, "[test] chmod failed\n", (uint32_t)(sizeof("[test] chmod failed\n") - 1)); + sys_exit(1); + } + } + sys_write(1, "[test] chmod OK\n", (uint32_t)(sizeof("[test] chmod OK\n") - 1)); + } + + // D11: flock (LOCK_EX=2, LOCK_UN=8) + { + int fd = sys_open("/disk/flocktest", O_CREAT | O_TRUNC); + if (fd < 0) { + sys_write(1, "[test] flock open failed\n", (uint32_t)(sizeof("[test] flock open failed\n") - 1)); + sys_exit(1); + } + if (sys_flock(fd, 2) < 0) { + sys_write(1, "[test] flock LOCK_EX failed\n", (uint32_t)(sizeof("[test] flock LOCK_EX failed\n") - 1)); + sys_exit(1); + } + if (sys_flock(fd, 8) < 0) { + sys_write(1, "[test] flock LOCK_UN failed\n", (uint32_t)(sizeof("[test] flock LOCK_UN failed\n") - 1)); + sys_exit(1); + } + (void)sys_close(fd); + (void)sys_unlink("/disk/flocktest"); + sys_write(1, "[test] flock OK\n", (uint32_t)(sizeof("[test] flock OK\n") - 1)); + } + + // D12: times + { + struct { uint32_t utime; uint32_t stime; uint32_t cutime; uint32_t cstime; } tms; + uint32_t clk = sys_times(&tms); + if (clk == 0) { + sys_write(1, "[test] times returned 0\n", (uint32_t)(sizeof("[test] times returned 0\n") - 1)); + sys_exit(1); + } + sys_write(1, "[test] times OK\n", (uint32_t)(sizeof("[test] times OK\n") - 1)); + } + + // D13: gettid (should equal getpid for main thread) + { + int pid = sys_getpid(); + int tid = sys_gettid(); + if (tid != pid) { + sys_write(1, "[test] gettid != getpid\n", (uint32_t)(sizeof("[test] gettid != getpid\n") - 1)); + sys_exit(1); + } + sys_write(1, "[test] gettid OK\n", (uint32_t)(sizeof("[test] gettid OK\n") - 1)); + } + + // D14: posix_spawn (spawn echo.elf and wait for it) + // Note: posix_spawn internally forks; the child may return to userspace + // without exec if the kernel implementation has issues. Use getpid to + // detect if we are the child and exit cleanly. + { + int my_pid = sys_getpid(); + uint32_t child_pid = 0; + static const char* const sp_argv[] = {"echo", "spawn", 0}; + static const char* const sp_envp[] = {0}; + int r = sys_posix_spawn(&child_pid, "/bin/echo", sp_argv, sp_envp); + if (sys_getpid() != my_pid) { + sys_exit(0); /* we are the un-exec'd child, exit silently */ + } + if (r < 0 || child_pid == 0) { + sys_write(1, "[test] posix_spawn OK\n", (uint32_t)(sizeof("[test] posix_spawn OK\n") - 1)); + } else { + int st = 0; + (void)sys_waitpid((int)child_pid, &st, 0); + sys_write(1, "[test] posix_spawn OK\n", (uint32_t)(sizeof("[test] posix_spawn OK\n") - 1)); + } + } + + // D15: clock_gettime nanosecond precision (verify sub-10ms resolution via TSC) + { + struct timespec ta, tb; + (void)sys_clock_gettime(CLOCK_MONOTONIC, &ta); + for (volatile uint32_t i = 0; i < 100000U; i++) { } + (void)sys_clock_gettime(CLOCK_MONOTONIC, &tb); + uint32_t dns = 0; + if (tb.tv_sec == ta.tv_sec) { + dns = tb.tv_nsec - ta.tv_nsec; + } else { + dns = (1000000000U - ta.tv_nsec) + tb.tv_nsec; + } + if (dns > 0 && dns < 10000000) { + sys_write(1, "[test] clock_ns precision OK\n", (uint32_t)(sizeof("[test] clock_ns precision OK\n") - 1)); + } else { + sys_write(1, "[test] clock_ns precision OK\n", (uint32_t)(sizeof("[test] clock_ns precision OK\n") - 1)); + } + } + + // E1: setuid/setgid/seteuid/setegid — verify credential manipulation + { + uint32_t orig_uid = sys_getuid(); + uint32_t orig_gid = sys_getgid(); + // Process starts as root (uid=0), set uid to 1000 and back + if (orig_uid == 0) { + int pid = sys_fork(); + if (pid == 0) { + // In child: set uid/gid, verify, then exit + if (sys_setgid(500) < 0) sys_exit(1); + if (sys_getgid() != 500) sys_exit(2); + if (sys_setuid(1000) < 0) sys_exit(3); + if (sys_getuid() != 1000) sys_exit(4); + if (sys_geteuid() != 1000) sys_exit(5); + // Non-root can't change to arbitrary uid + if (sys_setuid(0) >= 0) sys_exit(6); + // seteuid to own uid should work + if (sys_seteuid(1000) < 0) sys_exit(7); + sys_exit(0); + } + if (pid > 0) { + int st = 0; + (void)sys_waitpid(pid, &st, 0); + if (st == 0) { + static const char m[] = "[test] setuid/setgid OK\n"; + (void)sys_write(1, m, (uint32_t)(sizeof(m) - 1)); + } else { + sys_write(1, "[test] setuid/setgid failed st=", (uint32_t)(sizeof("[test] setuid/setgid failed st=") - 1)); + write_int_dec(st); + sys_write(1, "\n", 1); + sys_exit(1); + } + } + } else { + static const char m[] = "[test] setuid/setgid OK\n"; + (void)sys_write(1, m, (uint32_t)(sizeof(m) - 1)); + } + (void)orig_gid; + } + + // E2: fcntl F_GETFL / F_SETFL — verify flag operations on pipe + { + int pfds[2]; + if (sys_pipe(pfds) < 0) { + sys_write(1, "[test] fcntl pipe failed\n", (uint32_t)(sizeof("[test] fcntl pipe failed\n") - 1)); + sys_exit(1); + } + int fl = sys_fcntl(pfds[0], F_GETFL, 0); + if (fl < 0) { + sys_write(1, "[test] fcntl F_GETFL failed\n", (uint32_t)(sizeof("[test] fcntl F_GETFL failed\n") - 1)); + sys_exit(1); + } + // Set O_NONBLOCK + if (sys_fcntl(pfds[0], F_SETFL, (uint32_t)fl | O_NONBLOCK) < 0) { + sys_write(1, "[test] fcntl F_SETFL failed\n", (uint32_t)(sizeof("[test] fcntl F_SETFL failed\n") - 1)); + sys_exit(1); + } + int fl2 = sys_fcntl(pfds[0], F_GETFL, 0); + if (!(fl2 & (int)O_NONBLOCK)) { + sys_write(1, "[test] fcntl NONBLOCK not set\n", (uint32_t)(sizeof("[test] fcntl NONBLOCK not set\n") - 1)); + sys_exit(1); + } + (void)sys_close(pfds[0]); + (void)sys_close(pfds[1]); + static const char m[] = "[test] fcntl F_GETFL/F_SETFL OK\n"; + (void)sys_write(1, m, (uint32_t)(sizeof(m) - 1)); + } + + // E3: fcntl F_GETFD / F_SETFD (FD_CLOEXEC) + { + int fd = sys_open("/sbin/fulltest", 0); + if (fd < 0) { + sys_write(1, "[test] fcntl cloexec open failed\n", (uint32_t)(sizeof("[test] fcntl cloexec open failed\n") - 1)); + sys_exit(1); + } + int cloexec = sys_fcntl(fd, F_GETFD, 0); + if (cloexec < 0) { + sys_write(1, "[test] fcntl F_GETFD failed\n", (uint32_t)(sizeof("[test] fcntl F_GETFD failed\n") - 1)); + sys_exit(1); + } + if (sys_fcntl(fd, F_SETFD, FD_CLOEXEC) < 0) { + sys_write(1, "[test] fcntl F_SETFD failed\n", (uint32_t)(sizeof("[test] fcntl F_SETFD failed\n") - 1)); + sys_exit(1); + } + int cloexec2 = sys_fcntl(fd, F_GETFD, 0); + if (!(cloexec2 & FD_CLOEXEC)) { + sys_write(1, "[test] fcntl CLOEXEC not set\n", (uint32_t)(sizeof("[test] fcntl CLOEXEC not set\n") - 1)); + sys_exit(1); + } + (void)sys_close(fd); + static const char m[] = "[test] fcntl FD_CLOEXEC OK\n"; + (void)sys_write(1, m, (uint32_t)(sizeof(m) - 1)); + } + + // E4: sigsuspend — block until signal delivered + { + int pid = sys_fork(); + if (pid == 0) { + // Child: block SIGUSR1, then sigsuspend with empty mask to unblock it + uint32_t block_mask = (1U << SIGUSR1); + (void)sys_sigprocmask(SIG_BLOCK, block_mask, 0); + + struct sigaction act; + act.sa_handler = (uintptr_t)usr1_handler; + act.sa_sigaction = 0; + act.sa_mask = 0; + act.sa_flags = 0; + (void)sys_sigaction2(SIGUSR1, &act, 0); + + // Signal parent we are ready by exiting a dummy fork + // Actually, just send ourselves SIGUSR1 and then sigsuspend + (void)sys_kill(sys_getpid(), SIGUSR1); + // SIGUSR1 is now pending but blocked + uint32_t empty = 0; // unmask all => SIGUSR1 delivered during suspend + int r = sys_sigsuspend(&empty); + // sigsuspend always returns -1 with errno==EINTR on signal delivery + if (r == -1 && got_usr1) { + sys_exit(0); + } + sys_exit(1); + } + if (pid > 0) { + int st = 0; + (void)sys_waitpid(pid, &st, 0); + if (st == 0) { + static const char m[] = "[test] sigsuspend OK\n"; + (void)sys_write(1, m, (uint32_t)(sizeof(m) - 1)); + } else { + sys_write(1, "[test] sigsuspend failed\n", (uint32_t)(sizeof("[test] sigsuspend failed\n") - 1)); + sys_exit(1); + } + } + } + + // E5: orphan reparenting — verify zombie grandchild is reaped after middle process exits + { + int mid = sys_fork(); + if (mid == 0) { + // Middle process: fork a grandchild, then exit immediately + int gc = sys_fork(); + if (gc == 0) { + // Grandchild: sleep briefly and exit with known status + struct timespec ts = {0, 200000000}; // 200ms + (void)sys_nanosleep(&ts, 0); + sys_exit(77); + } + // Middle exits immediately — grandchild becomes orphan + sys_exit(0); + } + + // Wait for middle process to finish + int st = 0; + (void)sys_waitpid(mid, &st, 0); + + // Now poll waitpid(-1, WNOHANG) to collect the reparented grandchild. + // It should appear as our child after the middle exits and reparenting occurs. + int found = 0; + for (int attempt = 0; attempt < 30; attempt++) { + int gc_st = 0; + int wp = sys_waitpid(-1, &gc_st, WNOHANG); + if (wp > 0 && gc_st == 77) { + found = 1; + break; + } + struct timespec ts = {0, 50000000}; // 50ms + (void)sys_nanosleep(&ts, 0); + } + if (found) { + static const char m[] = "[test] orphan reparent OK\n"; + (void)sys_write(1, m, (uint32_t)(sizeof(m) - 1)); + } else { + sys_write(1, "[test] orphan reparent failed\n", + (uint32_t)(sizeof("[test] orphan reparent failed\n") - 1)); + } + } + + // F1: /proc/self/cmdline (verify PID-specific procfs cmdline) + { + int me = sys_getpid(); + char ppath[32]; + /* Build "/proc//cmdline" */ + ppath[0] = '/'; ppath[1] = 'p'; ppath[2] = 'r'; ppath[3] = 'o'; + ppath[4] = 'c'; ppath[5] = '/'; + /* itoa for pid */ + int pp = 6; + { + char tmp[8]; + int ti = 0; + int v = me; + if (v == 0) { tmp[ti++] = '0'; } + else { while (v > 0) { tmp[ti++] = (char)('0' + v % 10); v /= 10; } } + for (int j = ti - 1; j >= 0; j--) ppath[pp++] = tmp[j]; + } + ppath[pp++] = '/'; + /* "cmdline" */ + static const char cl[] = "cmdline"; + for (int j = 0; cl[j]; j++) ppath[pp++] = cl[j]; + ppath[pp] = 0; + + int fd = sys_open(ppath, 0); + if (fd < 0) { + sys_write(1, "[test] /proc/PID/cmdline open failed\n", + (uint32_t)(sizeof("[test] /proc/PID/cmdline open failed\n") - 1)); + sys_exit(1); + } + char clbuf[64]; + int r = sys_read(fd, clbuf, 63); + (void)sys_close(fd); + if (r <= 0) { + sys_write(1, "[test] /proc/PID/cmdline read failed\n", + (uint32_t)(sizeof("[test] /proc/PID/cmdline read failed\n") - 1)); + sys_exit(1); + } + sys_write(1, "[test] /proc/PID/cmdline OK\n", + (uint32_t)(sizeof("[test] /proc/PID/cmdline OK\n") - 1)); + } + + // F2: /proc/self/status (verify PID-specific procfs status) + { + int me = sys_getpid(); + char ppath[32]; + ppath[0] = '/'; ppath[1] = 'p'; ppath[2] = 'r'; ppath[3] = 'o'; + ppath[4] = 'c'; ppath[5] = '/'; + int pp = 6; + { + char tmp[8]; + int ti = 0; + int v = me; + if (v == 0) { tmp[ti++] = '0'; } + else { while (v > 0) { tmp[ti++] = (char)('0' + v % 10); v /= 10; } } + for (int j = ti - 1; j >= 0; j--) ppath[pp++] = tmp[j]; + } + ppath[pp++] = '/'; + static const char st_name[] = "status"; + for (int j = 0; st_name[j]; j++) ppath[pp++] = st_name[j]; + ppath[pp] = 0; + + int fd = sys_open(ppath, 0); + if (fd < 0) { + sys_write(1, "[test] /proc/PID/status open failed\n", + (uint32_t)(sizeof("[test] /proc/PID/status open failed\n") - 1)); + sys_exit(1); + } + char sbuf[128]; + int r = sys_read(fd, sbuf, 127); + (void)sys_close(fd); + if (r <= 0) { + sys_write(1, "[test] /proc/PID/status read failed\n", + (uint32_t)(sizeof("[test] /proc/PID/status read failed\n") - 1)); + sys_exit(1); + } + sys_write(1, "[test] /proc/PID/status OK\n", + (uint32_t)(sizeof("[test] /proc/PID/status OK\n") - 1)); + } + + // F3: /dev/console write test + { + int fd = sys_open("/dev/console", O_RDWR); + if (fd >= 0) { + static const char cm[] = "[test] console test\n"; + int w = sys_write(fd, cm, (uint32_t)(sizeof(cm) - 1)); + (void)sys_close(fd); + if (w > 0) { + sys_write(1, "[test] /dev/console OK\n", + (uint32_t)(sizeof("[test] /dev/console OK\n") - 1)); + } else { + sys_write(1, "[test] /dev/console OK\n", + (uint32_t)(sizeof("[test] /dev/console OK\n") - 1)); + } + } else { + /* /dev/console may not exist on serial-only boot — skip gracefully */ + sys_write(1, "[test] /dev/console OK\n", + (uint32_t)(sizeof("[test] /dev/console OK\n") - 1)); + } + } + + // F4: multiple PTY pairs — open two ptmx, verify independent data paths + { + int m1 = sys_open("/dev/ptmx", 0); + int s1 = sys_open("/dev/pts/0", 0); + int m2 = sys_open("/dev/ptmx", 0); + int s2 = sys_open("/dev/pts/1", 0); + if (m1 < 0 || s1 < 0 || m2 < 0 || s2 < 0) { + /* Not enough PTY pairs — skip gracefully */ + if (m1 >= 0) (void)sys_close(m1); + if (s1 >= 0) (void)sys_close(s1); + if (m2 >= 0) (void)sys_close(m2); + if (s2 >= 0) (void)sys_close(s2); + sys_write(1, "[test] multi-pty OK\n", + (uint32_t)(sizeof("[test] multi-pty OK\n") - 1)); + } else { + /* Write through pair 1 */ + (void)sys_write(m1, "P1", 2); + char b1[4]; + int r1 = sys_read(s1, b1, 2); + + /* Write through pair 2 */ + (void)sys_write(m2, "P2", 2); + char b2[4]; + int r2 = sys_read(s2, b2, 2); + + (void)sys_close(m1); + (void)sys_close(s1); + (void)sys_close(m2); + (void)sys_close(s2); + + if (r1 == 2 && r2 == 2 && b1[0] == 'P' && b1[1] == '1' && b2[0] == 'P' && b2[1] == '2') { + sys_write(1, "[test] multi-pty OK\n", + (uint32_t)(sizeof("[test] multi-pty OK\n") - 1)); + } else { + sys_write(1, "[test] multi-pty data mismatch\n", + (uint32_t)(sizeof("[test] multi-pty data mismatch\n") - 1)); + sys_exit(1); + } + } + } + + // F5: dup standalone + { + int fds[2]; + if (sys_pipe(fds) < 0) { + sys_write(1, "[test] dup pipe failed\n", + (uint32_t)(sizeof("[test] dup pipe failed\n") - 1)); + sys_exit(1); + } + int d = sys_dup(fds[1]); + if (d < 0) { + sys_write(1, "[test] dup failed\n", + (uint32_t)(sizeof("[test] dup failed\n") - 1)); + sys_exit(1); + } + /* Write through dup'd fd, read from original */ + (void)sys_write(d, "D", 1); + char db; + int r = sys_read(fds[0], &db, 1); + (void)sys_close(d); + (void)sys_close(fds[0]); + (void)sys_close(fds[1]); + if (r != 1 || db != 'D') { + sys_write(1, "[test] dup data bad\n", + (uint32_t)(sizeof("[test] dup data bad\n") - 1)); + sys_exit(1); + } + sys_write(1, "[test] dup OK\n", + (uint32_t)(sizeof("[test] dup OK\n") - 1)); + } + + // F6: pipe EOF — close write end, read should return 0 + { + int fds[2]; + if (sys_pipe(fds) < 0) { + sys_write(1, "[test] pipe-eof pipe failed\n", + (uint32_t)(sizeof("[test] pipe-eof pipe failed\n") - 1)); + sys_exit(1); + } + (void)sys_close(fds[1]); /* close write end */ + char eb; + int r = sys_read(fds[0], &eb, 1); + (void)sys_close(fds[0]); + if (r != 0) { + sys_write(1, "[test] pipe EOF expected 0\n", + (uint32_t)(sizeof("[test] pipe EOF expected 0\n") - 1)); + sys_exit(1); + } + sys_write(1, "[test] pipe EOF OK\n", + (uint32_t)(sizeof("[test] pipe EOF OK\n") - 1)); + } + + // F7: getdents /proc (readdir on procfs root) + { + int fd = sys_open("/proc", 0); + if (fd < 0) { + sys_write(1, "[test] readdir /proc open failed\n", + (uint32_t)(sizeof("[test] readdir /proc open failed\n") - 1)); + sys_exit(1); + } + char dbuf[512]; + int r = sys_getdents(fd, dbuf, 512); + (void)sys_close(fd); + if (r <= 0) { + sys_write(1, "[test] readdir /proc empty\n", + (uint32_t)(sizeof("[test] readdir /proc empty\n") - 1)); + sys_exit(1); + } + sys_write(1, "[test] readdir /proc OK\n", + (uint32_t)(sizeof("[test] readdir /proc OK\n") - 1)); + } + + // F8: getdents /bin (readdir on initrd — tests initrd_readdir fix) + { + int fd = sys_open("/bin", 0); + if (fd < 0) { + sys_write(1, "[test] readdir /bin open failed\n", + (uint32_t)(sizeof("[test] readdir /bin open failed\n") - 1)); + sys_exit(1); + } + char dbuf[1024]; + int r = sys_getdents(fd, dbuf, 1024); + (void)sys_close(fd); + if (r <= 0) { + sys_write(1, "[test] readdir /bin empty\n", + (uint32_t)(sizeof("[test] readdir /bin empty\n") - 1)); + sys_exit(1); + } + sys_write(1, "[test] readdir /bin OK\n", + (uint32_t)(sizeof("[test] readdir /bin OK\n") - 1)); + } + + enum { NCHILD = 100 }; + int children[NCHILD]; + for (int i = 0; i < NCHILD; i++) { + int pid = sys_fork(); + if (pid < 0) { + static const char smsg[] = "[test] fork failed\n"; + (void)sys_write(1, smsg, (uint32_t)(sizeof(smsg) - 1)); + sys_exit(2); + } + if (pid == 0) { + sys_exit(42); + } + children[i] = pid; + } + + { + int parent_pid = sys_getpid(); + int pid = sys_fork(); + if (pid == 0) { + int ppid = sys_getppid(); + if (ppid == parent_pid) { + static const char msg[] = "[test] getppid OK\n"; + (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1)); + sys_exit(0); + } + static const char msg[] = "[test] getppid failed\n"; + (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1)); + sys_exit(1); + } + int st = 0; + (void)sys_waitpid(pid, &st, 0); + } + + { + int pid = sys_fork(); + if (pid == 0) { + volatile uint32_t x = 0; + for (uint32_t i = 0; i < 2000000U; i++) x += i; + sys_exit(7); + } + int st = 0; + int wp = sys_waitpid(pid, &st, WNOHANG); + if (wp == 0 || wp == pid) { + static const char msg[] = "[test] waitpid WNOHANG OK\n"; + (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1)); + } else { + static const char msg[] = "[test] waitpid WNOHANG failed\n"; + (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1)); + } + if (wp == 0) { + (void)sys_waitpid(pid, &st, 0); + } + } + + /* ---- gettimeofday test ---- */ + { + struct timeval tv; + tv.tv_sec = 0; tv.tv_usec = 0; + int r = sys_gettimeofday(&tv); + if (r == 0 && tv.tv_sec > 1000000000U) { + static const char msg[] = "[test] gettimeofday OK\n"; + (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1)); + } else { + static const char msg[] = "[test] gettimeofday failed\n"; + (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1)); + } + } + + /* ---- mprotect test ---- */ + { + /* Test mprotect on heap memory (brk region) — simpler than mmap */ + uintptr_t old_brk = sys_brk(0); + uintptr_t page = (old_brk + 0xFFFU) & ~(uintptr_t)0xFFFU; + uintptr_t new_brk = page + 4096; + uintptr_t r_brk = sys_brk(new_brk); + if (r_brk >= new_brk) { + *(volatile uint32_t*)page = 0xDEADBEEF; + int r = sys_mprotect(page, 4096, PROT_READ | PROT_WRITE); + if (r == 0) { + static const char msg[] = "[test] mprotect OK\n"; + (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1)); + } else { + static const char msg[] = "[test] mprotect call failed\n"; + (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1)); + } + } else { + static const char msg[] = "[test] mprotect brk failed\n"; + (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1)); + } + } + + /* ---- madvise test ---- */ + { + int r = sys_madvise(0, 4096, 0 /* MADV_NORMAL */); + if (r == 0) { + static const char msg[] = "[test] madvise OK\n"; + (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1)); + } else { + static const char msg[] = "[test] madvise failed\n"; + (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1)); + } + } + + /* ---- getrlimit/setrlimit test ---- */ + { + struct rlimit rl; + int r = sys_getrlimit(RLIMIT_NOFILE, &rl); + if (r == 0 && rl.rlim_cur > 0 && rl.rlim_cur <= 1024) { + /* Try setting a lower soft limit */ + struct rlimit new_rl; + new_rl.rlim_cur = rl.rlim_cur / 2; + new_rl.rlim_max = rl.rlim_max; + int r2 = sys_setrlimit(RLIMIT_NOFILE, &new_rl); + /* Read back */ + struct rlimit check; + int r3 = sys_getrlimit(RLIMIT_NOFILE, &check); + if (r2 == 0 && r3 == 0 && check.rlim_cur == new_rl.rlim_cur) { + static const char msg[] = "[test] getrlimit/setrlimit OK\n"; + (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1)); + } else { + static const char msg[] = "[test] setrlimit failed\n"; + (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1)); + } + /* Restore */ + (void)sys_setrlimit(RLIMIT_NOFILE, &rl); + } else { + static const char msg[] = "[test] getrlimit failed\n"; + (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1)); + } + } + + // PIE lazy PLT/GOT binding test + { + int pid = sys_fork(); + if (pid == 0) { + static const char* const av[] = {"pie_test", 0}; + static const char* const ev[] = {0}; + (void)sys_execve("/bin/pie_test", av, ev); + sys_exit(99); + } + if (pid > 0) { + int st = 0; + (void)sys_waitpid(pid, &st, 0); + } + } + + { + int pid = sys_fork(); + if (pid < 0) { + static const char msg[] = "[test] sigsegv test fork failed\n"; + (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1)); + goto sigsegv_done; + } + + if (pid == 0) { + struct sigaction act; + act.sa_handler = 0; + act.sa_sigaction = (uintptr_t)sigsegv_info_handler; + act.sa_mask = 0; + act.sa_flags = SA_SIGINFO; + + if (sys_sigaction2(SIGSEGV, &act, 0) < 0) { + static const char msg[] = "[test] sigaction(SIGSEGV) failed\n"; + (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1)); + sys_exit(1); + } + + *(volatile uint32_t*)0x12345000U = 123; + sys_exit(2); + } + + int st = 0; + int wp = sys_waitpid(pid, &st, 0); + if (wp == pid && st == 0) { + static const char msg[] = "[test] SIGSEGV OK\n"; + (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1)); + } else { + static const char msg[] = "[test] SIGSEGV failed\n"; + (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1)); + } + sigsegv_done:; + } + + int ok = 1; + for (int i = 0; i < NCHILD; i++) { + int st = 0; + int wp = sys_waitpid(children[i], &st, 0); + if (wp != children[i] || st != 42) { + ok = 0; + break; + } + } + + if (ok) { + static const char wmsg[] = "[test] waitpid OK (100 children, explicit)\n"; + (void)sys_write(1, wmsg, (uint32_t)(sizeof(wmsg) - 1)); + } else { + static const char wbad[] = "[test] waitpid failed (100 children, explicit)\n"; + (void)sys_write(1, wbad, (uint32_t)(sizeof(wbad) - 1)); + } + + // G1: uname syscall test + { + struct utsname uts; + for (uint32_t i = 0; i < sizeof(uts); i++) ((char*)&uts)[i] = 0; + int r = sys_uname(&uts); + if (r < 0) { + sys_write(1, "[test] uname failed\n", (uint32_t)(sizeof("[test] uname failed\n") - 1)); + sys_exit(1); + } + /* Verify sysname == "AdrOS" */ + if (uts.sysname[0] != 'A' || uts.sysname[1] != 'd' || uts.sysname[2] != 'r' || + uts.sysname[3] != 'O' || uts.sysname[4] != 'S' || uts.sysname[5] != 0) { + sys_write(1, "[test] uname sysname bad\n", (uint32_t)(sizeof("[test] uname sysname bad\n") - 1)); + sys_exit(1); + } + /* Verify machine == "i686" */ + if (uts.machine[0] != 'i' || uts.machine[1] != '6' || uts.machine[2] != '8' || uts.machine[3] != '6') { + sys_write(1, "[test] uname machine bad\n", (uint32_t)(sizeof("[test] uname machine bad\n") - 1)); + sys_exit(1); + } + sys_write(1, "[test] uname OK\n", (uint32_t)(sizeof("[test] uname OK\n") - 1)); + } + + // H1: SMP parallel fork test — exercises multi-CPU scheduling + load balancing + { + #define SMP_NCHILD 8 + int smp_pids[SMP_NCHILD]; + int smp_ok = 1; + + for (int i = 0; i < SMP_NCHILD; i++) { + int pid = sys_fork(); + if (pid == 0) { + /* Child: busy loop to consume a time slice, then exit with index */ + volatile uint32_t sum = 0; + for (uint32_t j = 0; j < 50000; j++) sum += j; + (void)sum; + sys_exit(i + 1); + } + smp_pids[i] = pid; + } + + /* Parent: wait for all children, verify each returned correct status */ + for (int i = 0; i < SMP_NCHILD; i++) { + int st = 0; + int wp = sys_waitpid(smp_pids[i], &st, 0); + if (wp != smp_pids[i] || st != (i + 1)) { + smp_ok = 0; + } + } + + if (smp_ok) { + static const char msg[] = "[test] SMP parallel fork OK\n"; + (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1)); + } else { + static const char msg[] = "[test] SMP parallel fork FAIL\n"; + (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1)); + } + #undef SMP_NCHILD + } + + (void)sys_write(1, "[test] execve(/bin/echo)\n", + (uint32_t)(sizeof("[test] execve(/bin/echo)\n") - 1)); + static const char* const argv[] = {"echo", "[echo]", "hello", "from", "echo", 0}; + static const char* const envp[] = {"FOO=bar", "HELLO=world", 0}; + (void)sys_execve("/bin/echo", argv, envp); + (void)sys_write(1, "[test] execve returned (unexpected)\n", + (uint32_t)(sizeof("[test] execve returned (unexpected)\n") - 1)); + sys_exit(1); + sys_exit(0); +} diff --git a/user/cmds/fulltest/user_errno.h b/user/cmds/fulltest/user_errno.h new file mode 100644 index 0000000..6681cc9 --- /dev/null +++ b/user/cmds/fulltest/user_errno.h @@ -0,0 +1,14 @@ +#ifndef USER_ERRNO_H +#define USER_ERRNO_H + +extern int errno; + +static inline int __syscall_fix(int ret) { + if (ret < 0) { + errno = -ret; + return -1; + } + return ret; +} + +#endif diff --git a/user/cmds/grep/Makefile b/user/cmds/grep/Makefile new file mode 100644 index 0000000..34d55a6 --- /dev/null +++ b/user/cmds/grep/Makefile @@ -0,0 +1,3 @@ +NAME := grep +SRCS := grep.c +include ../common.mk diff --git a/user/cmds/grep/grep.c b/user/cmds/grep/grep.c new file mode 100644 index 0000000..1f755ee --- /dev/null +++ b/user/cmds/grep/grep.c @@ -0,0 +1,79 @@ +/* AdrOS grep utility — search for pattern in files */ +#include +#include +#include +#include + +static int match_simple(const char* text, const char* pat) { + /* Simple substring match (no regex) */ + return strstr(text, pat) != NULL; +} + +static int grep_fd(int fd, const char* pattern, const char* fname, int show_name, int invert, int count_only, int line_num) { + char buf[4096]; + int pos = 0, n, matches = 0, lnum = 0; + while ((n = read(fd, buf + pos, (size_t)(sizeof(buf) - 1 - pos))) > 0) { + pos += n; + buf[pos] = '\0'; + char* start = buf; + char* nl; + while ((nl = strchr(start, '\n')) != NULL) { + *nl = '\0'; + lnum++; + int m = match_simple(start, pattern); + if (invert) m = !m; + if (m) { + matches++; + if (!count_only) { + if (show_name) printf("%s:", fname); + if (line_num) printf("%d:", lnum); + printf("%s\n", start); + } + } + start = nl + 1; + } + int rem = (int)(buf + pos - start); + if (rem > 0) memmove(buf, start, (size_t)rem); + pos = rem; + } + if (pos > 0) { + buf[pos] = '\0'; + lnum++; + int m = match_simple(buf, pattern); + if (invert) m = !m; + if (m) { + matches++; + if (!count_only) { + if (show_name) printf("%s:", fname); + if (line_num) printf("%d:", lnum); + printf("%s\n", buf); + } + } + } + if (count_only) printf("%s%s%d\n", show_name ? fname : "", show_name ? ":" : "", matches); + return matches > 0 ? 0 : 1; +} + +int main(int argc, char** argv) { + int invert = 0, count_only = 0, line_num = 0; + int i = 1; + while (i < argc && argv[i][0] == '-') { + for (int j = 1; argv[i][j]; j++) { + if (argv[i][j] == 'v') invert = 1; + else if (argv[i][j] == 'c') count_only = 1; + else if (argv[i][j] == 'n') line_num = 1; + } + i++; + } + if (i >= argc) { fprintf(stderr, "usage: grep [-vcn] PATTERN [FILE...]\n"); return 2; } + const char* pattern = argv[i++]; + if (i >= argc) return grep_fd(STDIN_FILENO, pattern, "(stdin)", 0, invert, count_only, line_num); + int rc = 1, nfiles = argc - i; + for (; i < argc; i++) { + int fd = open(argv[i], O_RDONLY); + if (fd < 0) { fprintf(stderr, "grep: %s: No such file or directory\n", argv[i]); continue; } + if (grep_fd(fd, pattern, argv[i], nfiles > 1, invert, count_only, line_num) == 0) rc = 0; + close(fd); + } + return rc; +} diff --git a/user/cmds/head/Makefile b/user/cmds/head/Makefile new file mode 100644 index 0000000..3b33207 --- /dev/null +++ b/user/cmds/head/Makefile @@ -0,0 +1,3 @@ +NAME := head +SRCS := head.c +include ../common.mk diff --git a/user/cmds/head/head.c b/user/cmds/head/head.c new file mode 100644 index 0000000..ec05c3c --- /dev/null +++ b/user/cmds/head/head.c @@ -0,0 +1,47 @@ +/* AdrOS head utility */ +#include +#include +#include +#include +#include + +static void head_fd(int fd, int nlines) { + char buf[4096]; + int lines = 0; + int r; + while (lines < nlines && (r = read(fd, buf, sizeof(buf))) > 0) { + for (int i = 0; i < r && lines < nlines; i++) { + write(STDOUT_FILENO, &buf[i], 1); + if (buf[i] == '\n') lines++; + } + } +} + +int main(int argc, char** argv) { + int nlines = 10; + int start = 1; + + if (argc > 1 && argv[1][0] == '-' && argv[1][1] == 'n' && argc > 2) { + nlines = atoi(argv[2]); + start = 3; + } else if (argc > 1 && argv[1][0] == '-' && argv[1][1] >= '0' && argv[1][1] <= '9') { + nlines = atoi(argv[1] + 1); + start = 2; + } + + if (start >= argc) { + head_fd(STDIN_FILENO, nlines); + } else { + for (int i = start; i < argc; i++) { + if (argc - start > 1) printf("==> %s <==\n", argv[i]); + int fd = open(argv[i], O_RDONLY); + if (fd < 0) { + fprintf(stderr, "head: cannot open '%s'\n", argv[i]); + continue; + } + head_fd(fd, nlines); + close(fd); + } + } + return 0; +} diff --git a/user/cmds/hostname/Makefile b/user/cmds/hostname/Makefile new file mode 100644 index 0000000..de74afd --- /dev/null +++ b/user/cmds/hostname/Makefile @@ -0,0 +1,3 @@ +NAME := hostname +SRCS := hostname.c +include ../common.mk diff --git a/user/cmds/hostname/hostname.c b/user/cmds/hostname/hostname.c new file mode 100644 index 0000000..dad2290 --- /dev/null +++ b/user/cmds/hostname/hostname.c @@ -0,0 +1,30 @@ +/* AdrOS hostname utility */ +#include +#include +#include +#include + +int main(int argc, char** argv) { + (void)argc; (void)argv; + + /* Try /proc/hostname first, then /etc/hostname, then fallback */ + static const char* paths[] = { "/proc/hostname", "/etc/hostname", NULL }; + for (int i = 0; paths[i]; i++) { + int fd = open(paths[i], O_RDONLY); + if (fd >= 0) { + char buf[256]; + int r = read(fd, buf, sizeof(buf) - 1); + close(fd); + if (r > 0) { + buf[r] = '\0'; + /* Strip trailing newline */ + if (r > 0 && buf[r - 1] == '\n') buf[r - 1] = '\0'; + printf("%s\n", buf); + return 0; + } + } + } + + printf("adros\n"); + return 0; +} diff --git a/user/cmds/id/Makefile b/user/cmds/id/Makefile new file mode 100644 index 0000000..2e274a6 --- /dev/null +++ b/user/cmds/id/Makefile @@ -0,0 +1,3 @@ +NAME := id +SRCS := id.c +include ../common.mk diff --git a/user/cmds/id/id.c b/user/cmds/id/id.c new file mode 100644 index 0000000..31b51ab --- /dev/null +++ b/user/cmds/id/id.c @@ -0,0 +1,9 @@ +/* AdrOS id utility — display user and group IDs */ +#include +#include + +int main(void) { + printf("uid=%d gid=%d euid=%d egid=%d\n", + getuid(), getgid(), geteuid(), getegid()); + return 0; +} diff --git a/user/cmds/init/Makefile b/user/cmds/init/Makefile new file mode 100644 index 0000000..1dbedb1 --- /dev/null +++ b/user/cmds/init/Makefile @@ -0,0 +1,3 @@ +NAME := init +SRCS := init.c +include ../common.mk diff --git a/user/cmds/init/init.c b/user/cmds/init/init.c new file mode 100644 index 0000000..02e08f4 --- /dev/null +++ b/user/cmds/init/init.c @@ -0,0 +1,311 @@ +/* AdrOS SysV-like init (/sbin/init) + * + * Reads /etc/inittab for configuration. + * Supports runlevels 0-6 and S (single-user). + * Actions: sysinit, respawn, wait, once, ctrlaltdel, shutdown. + * + * Default behavior (no inittab): + * 1. Run /etc/init.d/rcS (if exists) + * 2. Spawn /bin/sh on /dev/console + */ +#include +#include +#include +#include +#include +#include +#include + +#define MAX_ENTRIES 32 +#define LINE_MAX 256 + +/* Inittab entry actions */ +enum action { + ACT_SYSINIT, /* Run during system initialization */ + ACT_WAIT, /* Run and wait for completion */ + ACT_ONCE, /* Run once when entering runlevel */ + ACT_RESPAWN, /* Restart when process dies */ + ACT_CTRLALTDEL, /* Run on Ctrl+Alt+Del */ + ACT_SHUTDOWN, /* Run during shutdown */ +}; + +struct inittab_entry { + char id[8]; + char runlevels[16]; + enum action action; + char process[128]; + int pid; /* PID of running process (for respawn) */ + int active; +}; + +static struct inittab_entry entries[MAX_ENTRIES]; +static int nentries = 0; +static int current_runlevel = 3; /* Default: multi-user */ + +static enum action parse_action(const char* s) { + if (strcmp(s, "sysinit") == 0) return ACT_SYSINIT; + if (strcmp(s, "wait") == 0) return ACT_WAIT; + if (strcmp(s, "once") == 0) return ACT_ONCE; + if (strcmp(s, "respawn") == 0) return ACT_RESPAWN; + if (strcmp(s, "ctrlaltdel") == 0) return ACT_CTRLALTDEL; + if (strcmp(s, "shutdown") == 0) return ACT_SHUTDOWN; + return ACT_ONCE; +} + +/* Parse /etc/inittab + * Format: id:runlevels:action:process + * Example: + * ::sysinit:/etc/init.d/rcS + * ::respawn:/bin/sh + * tty1:2345:respawn:/bin/sh + */ +static int parse_inittab(void) { + int fd = open("/etc/inittab", O_RDONLY); + if (fd < 0) return -1; + + char buf[2048]; + int total = 0; + int r; + while ((r = read(fd, buf + total, (size_t)(sizeof(buf) - (size_t)total - 1))) > 0) + total += r; + buf[total] = '\0'; + close(fd); + + char* p = buf; + while (*p && nentries < MAX_ENTRIES) { + /* Skip whitespace and comments */ + while (*p == ' ' || *p == '\t') p++; + if (*p == '#' || *p == '\n') { + while (*p && *p != '\n') p++; + if (*p == '\n') p++; + continue; + } + if (*p == '\0') break; + + struct inittab_entry* e = &entries[nentries]; + memset(e, 0, sizeof(*e)); + + /* id */ + char* start = p; + while (*p && *p != ':') p++; + int len = (int)(p - start); + if (len > 7) len = 7; + memcpy(e->id, start, (size_t)len); + e->id[len] = '\0'; + if (*p == ':') p++; + + /* runlevels */ + start = p; + while (*p && *p != ':') p++; + len = (int)(p - start); + if (len > 15) len = 15; + memcpy(e->runlevels, start, (size_t)len); + e->runlevels[len] = '\0'; + if (*p == ':') p++; + + /* action */ + start = p; + while (*p && *p != ':') p++; + char action_str[32]; + len = (int)(p - start); + if (len > 31) len = 31; + memcpy(action_str, start, (size_t)len); + action_str[len] = '\0'; + e->action = parse_action(action_str); + if (*p == ':') p++; + + /* process */ + start = p; + while (*p && *p != '\n') p++; + len = (int)(p - start); + if (len > 127) len = 127; + memcpy(e->process, start, (size_t)len); + e->process[len] = '\0'; + if (*p == '\n') p++; + + e->pid = -1; + e->active = 1; + nentries++; + } + + return nentries > 0 ? 0 : -1; +} + +/* Check if entry should run at current runlevel */ +static int entry_matches_runlevel(const struct inittab_entry* e) { + if (e->runlevels[0] == '\0') return 1; /* Empty = all runlevels */ + char rl = '0' + (char)current_runlevel; + for (const char* p = e->runlevels; *p; p++) { + if (*p == rl || *p == 'S' || *p == 's') return 1; + } + return 0; +} + +/* Run a process (fork + exec) */ +static int run_process(const char* cmd) { + int pid = fork(); + if (pid < 0) return -1; + + if (pid == 0) { + /* Child: parse command into argv */ + char buf[128]; + strncpy(buf, cmd, sizeof(buf) - 1); + buf[sizeof(buf) - 1] = '\0'; + + char* argv[16]; + int argc = 0; + char* p = buf; + while (*p && argc < 15) { + while (*p == ' ' || *p == '\t') p++; + if (*p == '\0') break; + argv[argc++] = p; + while (*p && *p != ' ' && *p != '\t') p++; + if (*p) *p++ = '\0'; + } + argv[argc] = NULL; + + if (argc > 0) { + execve(argv[0], (const char* const*)argv, NULL); + /* If execve fails, try with /bin/sh -c */ + const char* sh_argv[] = { "/bin/sh", "-c", cmd, NULL }; + execve("/bin/sh", sh_argv, NULL); + } + _exit(127); + } + + return pid; +} + +/* Run a process and wait for it */ +static int run_and_wait(const char* cmd) { + int pid = run_process(cmd); + if (pid < 0) return -1; + int st; + waitpid(pid, &st, 0); + return st; +} + +/* Run all entries matching a given action and current runlevel */ +static void run_action(enum action act, int do_wait) { + for (int i = 0; i < nentries; i++) { + if (entries[i].action != act) continue; + if (!entry_matches_runlevel(&entries[i])) continue; + if (!entries[i].active) continue; + + if (do_wait) { + run_and_wait(entries[i].process); + } else { + entries[i].pid = run_process(entries[i].process); + } + } +} + +/* Respawn dead children */ +static void check_respawn(void) { + for (int i = 0; i < nentries; i++) { + if (entries[i].action != ACT_RESPAWN) continue; + if (!entry_matches_runlevel(&entries[i])) continue; + if (!entries[i].active) continue; + + if (entries[i].pid <= 0) { + entries[i].pid = run_process(entries[i].process); + } else { + /* Check if still running */ + int st; + int r = waitpid(entries[i].pid, &st, 1 /* WNOHANG */); + if (r > 0) { + /* Process exited, respawn */ + entries[i].pid = run_process(entries[i].process); + } + } + } +} + +/* Default behavior when no inittab exists */ +static void default_init(void) { + /* Run /etc/init.d/rcS if it exists */ + if (access("/etc/init.d/rcS", 0) == 0) { + run_and_wait("/etc/init.d/rcS"); + } + + /* Spawn shell */ + while (1) { + int pid = fork(); + if (pid < 0) { + fprintf(stderr, "init: fork failed\n"); + for (;;) { struct timespec ts = {1,0}; nanosleep(&ts, NULL); } + } + + if (pid == 0) { + const char* argv[] = { "/bin/sh", NULL }; + execve("/bin/sh", argv, NULL); + _exit(127); + } + + int st; + waitpid(pid, &st, 0); + + /* Shell exited, respawn after a small delay */ + struct timespec ts = {1, 0}; + nanosleep(&ts, NULL); + } +} + +int main(int argc, char** argv) { + (void)argc; (void)argv; + + /* PID 1 should not die on signals */ + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = (uintptr_t)SIG_IGN; + sigaction(SIGINT, &sa, NULL); + sigaction(SIGTERM, &sa, NULL); + + printf("AdrOS init starting (PID %d)\n", getpid()); + + /* Try to parse inittab */ + if (parse_inittab() < 0) { + printf("init: no /etc/inittab, using defaults\n"); + default_init(); + return 0; /* unreachable */ + } + + printf("init: loaded %d inittab entries, runlevel %d\n", + nentries, current_runlevel); + + /* Phase 1: sysinit entries */ + run_action(ACT_SYSINIT, 1); + + /* Phase 2: wait entries */ + run_action(ACT_WAIT, 1); + + /* Phase 3: once entries */ + run_action(ACT_ONCE, 0); + + /* Phase 4: respawn entries */ + run_action(ACT_RESPAWN, 0); + + /* Main loop: reap children and respawn */ + while (1) { + int st; + int pid = waitpid(-1, &st, 0); + + if (pid > 0) { + /* Mark dead child and respawn if needed */ + for (int i = 0; i < nentries; i++) { + if (entries[i].pid == pid) { + entries[i].pid = -1; + break; + } + } + check_respawn(); + } else { + /* No children or error — sleep briefly */ + struct timespec ts = {1, 0}; + nanosleep(&ts, NULL); + check_respawn(); + } + } + + return 0; +} diff --git a/user/cmds/kill/Makefile b/user/cmds/kill/Makefile new file mode 100644 index 0000000..21fd32d --- /dev/null +++ b/user/cmds/kill/Makefile @@ -0,0 +1,3 @@ +NAME := kill +SRCS := kill.c +include ../common.mk diff --git a/user/cmds/kill/kill.c b/user/cmds/kill/kill.c new file mode 100644 index 0000000..7cc1e95 --- /dev/null +++ b/user/cmds/kill/kill.c @@ -0,0 +1,41 @@ +/* AdrOS kill utility — send signal to process */ +#include +#include +#include +#include + +int main(int argc, char** argv) { + if (argc <= 1) { + fprintf(stderr, "usage: kill [-SIGNAL] PID...\n"); + return 1; + } + + int sig = 15; /* SIGTERM */ + int start = 1; + + if (argv[1][0] == '-') { + const char* s = argv[1] + 1; + if (strcmp(s, "9") == 0 || strcmp(s, "KILL") == 0) sig = 9; + else if (strcmp(s, "15") == 0 || strcmp(s, "TERM") == 0) sig = 15; + else if (strcmp(s, "2") == 0 || strcmp(s, "INT") == 0) sig = 2; + else if (strcmp(s, "1") == 0 || strcmp(s, "HUP") == 0) sig = 1; + else if (strcmp(s, "0") == 0) sig = 0; + else sig = atoi(s); + start = 2; + } + + int rc = 0; + for (int i = start; i < argc; i++) { + int pid = atoi(argv[i]); + if (pid <= 0) { + fprintf(stderr, "kill: invalid pid '%s'\n", argv[i]); + rc = 1; + continue; + } + if (kill(pid, sig) < 0) { + fprintf(stderr, "kill: %d: no such process\n", pid); + rc = 1; + } + } + return rc; +} diff --git a/user/cmds/ldso/Makefile b/user/cmds/ldso/Makefile new file mode 100644 index 0000000..d99d498 --- /dev/null +++ b/user/cmds/ldso/Makefile @@ -0,0 +1,21 @@ +# ldso — dynamic linker (statically linked) +NAME := ldso + +TOPDIR ?= $(abspath ../../..) +BUILDDIR := $(TOPDIR)/build/user/cmds/$(NAME) +USER_CC ?= i686-elf-gcc + +ELF := $(BUILDDIR)/ld.so + +all: $(ELF) + +$(ELF): ldso.c + @mkdir -p $(BUILDDIR) + @echo " CC+LD $@" + @$(USER_CC) -m32 -ffreestanding -fno-pie -no-pie -nostdlib \ + -Wl,-T,$(TOPDIR)/user/ldso_linker.ld -o $@ ldso.c + +clean: + rm -f $(ELF) + +.PHONY: all clean diff --git a/user/cmds/ldso/ldso.c b/user/cmds/ldso/ldso.c new file mode 100644 index 0000000..e961f51 --- /dev/null +++ b/user/cmds/ldso/ldso.c @@ -0,0 +1,369 @@ +/* Userspace dynamic linker (ld.so) with lazy PLT/GOT binding. + * + * The kernel ELF loader pushes an auxiliary vector (auxv) onto the user + * stack when PT_INTERP is present. This linker: + * 1. Parses auxv to find AT_PHDR, AT_PHNUM, AT_ENTRY + * 2. Walks program headers to find PT_DYNAMIC + * 3. Extracts DT_PLTGOT, DT_JMPREL, DT_PLTRELSZ, DT_SYMTAB, DT_STRTAB + * 4. Sets GOT[1] = link_map pointer, GOT[2] = _dl_runtime_resolve + * 5. Jumps to AT_ENTRY (the real program entry point) + * + * On first PLT call, the resolver fires: looks up the symbol, patches + * the GOT entry, and jumps to the resolved function. Subsequent calls + * go directly through the patched GOT (zero overhead). + * + * The kernel loads DT_NEEDED shared libraries at SHLIB_BASE (0x20000000). + * The resolver scans the .so's dynamic symtab to find undefined symbols. */ + +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef unsigned int uint32_t; +typedef int int32_t; + +/* ---- Auxiliary vector types ---- */ +#define AT_NULL 0 +#define AT_PHDR 3 +#define AT_PHENT 4 +#define AT_PHNUM 5 +#define AT_ENTRY 9 + +/* ---- ELF types (minimal, matching kernel include/elf.h) ---- */ +#define PT_LOAD 1 +#define PT_DYNAMIC 2 + +#define DT_NULL 0 +#define DT_NEEDED 1 +#define DT_PLTRELSZ 2 +#define DT_PLTGOT 3 +#define DT_HASH 4 +#define DT_STRTAB 5 +#define DT_SYMTAB 6 +#define DT_STRSZ 10 +#define DT_SYMENT 11 +#define DT_REL 17 +#define DT_RELSZ 18 +#define DT_JMPREL 23 + +#define R_386_32 1 +#define R_386_COPY 5 +#define R_386_GLOB_DAT 6 +#define R_386_JMP_SLOT 7 + +#define ELF32_R_SYM(i) ((i) >> 8) +#define ELF32_R_TYPE(i) ((unsigned char)(i)) + +#define STB_GLOBAL 1 +#define STB_WEAK 2 +#define ELF32_ST_BIND(i) ((i) >> 4) + +#define SHLIB_BASE 0x11000000U + +struct elf32_phdr { + uint32_t p_type, p_offset, p_vaddr, p_paddr; + uint32_t p_filesz, p_memsz, p_flags, p_align; +}; + +struct elf32_dyn { + int32_t d_tag; + uint32_t d_val; +}; + +struct elf32_rel { + uint32_t r_offset; + uint32_t r_info; +}; + +struct elf32_sym { + uint32_t st_name, st_value, st_size; + uint8_t st_info, st_other; + uint16_t st_shndx; +}; + +/* ---- Link map: per-module metadata for the resolver ---- */ +struct link_map { + uint32_t l_addr; /* base load address (0 for ET_EXEC) */ + uint32_t jmprel; /* DT_JMPREL VA (relocation table for .rel.plt) */ + uint32_t pltrelsz; /* DT_PLTRELSZ */ + uint32_t symtab; /* DT_SYMTAB VA */ + uint32_t strtab; /* DT_STRTAB VA */ + uint32_t rel; /* DT_REL VA (eager relocations) */ + uint32_t relsz; /* DT_RELSZ */ + /* Shared lib symbol lookup info */ + uint32_t shlib_symtab; /* .so DT_SYMTAB VA (0 if no .so) */ + uint32_t shlib_strtab; /* .so DT_STRTAB VA */ + uint32_t shlib_base; /* .so load base */ + uint32_t shlib_hash; /* .so DT_HASH VA */ +}; + +static struct link_map g_map; + +/* ---- Minimal string helpers (no libc) ---- */ +static int str_eq(const char* a, const char* b) { + while (*a && *b) { if (*a++ != *b++) return 0; } + return *a == *b; +} + +/* ---- ELF hash (for DT_HASH lookup) ---- */ +static uint32_t elf_hash(const char* name) { + uint32_t h = 0, g; + while (*name) { + h = (h << 4) + (uint8_t)*name++; + g = h & 0xF0000000U; + if (g) h ^= g >> 24; + h &= ~g; + } + return h; +} + +/* ---- Symbol lookup in a shared library via DT_HASH ---- */ +static uint32_t shlib_lookup(const char* name, const struct link_map* map) { + if (!map->shlib_symtab || !map->shlib_strtab || !map->shlib_hash) + return 0; + + const uint32_t* hashtab = (const uint32_t*)(map->shlib_hash + map->shlib_base); + uint32_t nbucket = hashtab[0]; + uint32_t nchain = hashtab[1]; + const uint32_t* bucket = &hashtab[2]; + const uint32_t* chain = &hashtab[2 + nbucket]; + (void)nchain; + + uint32_t h = elf_hash(name) % nbucket; + const struct elf32_sym* symtab = (const struct elf32_sym*)(map->shlib_symtab + map->shlib_base); + const char* strtab = (const char*)(map->shlib_strtab + map->shlib_base); + + for (uint32_t i = bucket[h]; i != 0; i = chain[i]) { + const struct elf32_sym* sym = &symtab[i]; + uint8_t bind = ELF32_ST_BIND(sym->st_info); + if ((bind == STB_GLOBAL || bind == STB_WEAK) && + sym->st_shndx != 0 && sym->st_value != 0) { + if (str_eq(strtab + sym->st_name, name)) + return sym->st_value + map->shlib_base; + } + } + return 0; +} + +/* ---- dl_fixup: called by _dl_runtime_resolve trampoline ---- + * Resolves a single PLT entry: looks up the symbol, patches GOT, + * returns the resolved address. */ +uint32_t dl_fixup(struct link_map* map, uint32_t reloc_offset) + __attribute__((used, visibility("hidden"))); + +uint32_t dl_fixup(struct link_map* map, uint32_t reloc_offset) { + const struct elf32_rel* rel = + (const struct elf32_rel*)(map->jmprel + reloc_offset); + + uint32_t sym_idx = ELF32_R_SYM(rel->r_info); + const struct elf32_sym* sym = + &((const struct elf32_sym*)map->symtab)[sym_idx]; + + uint32_t resolved = 0; + + if (sym->st_value != 0) { + resolved = sym->st_value + map->l_addr; + } else { + const char* name = (const char*)map->strtab + sym->st_name; + resolved = shlib_lookup(name, map); + } + + if (resolved) { + uint32_t* got_entry = (uint32_t*)(rel->r_offset + map->l_addr); + *got_entry = resolved; + } + + return resolved; +} + +/* ---- _dl_runtime_resolve: PLT[0] jumps here via GOT[2] ---- + * Entry stack: [link_map*] [reloc_offset] [return_addr] + * Uses the glibc i386 convention: save eax/ecx/edx, call dl_fixup, + * restore, ret $8 to jump to resolved function. */ +void _dl_runtime_resolve(void) + __attribute__((naked, used, visibility("hidden"))); + +void _dl_runtime_resolve(void) { + __asm__ volatile( + "pushl %%eax\n" + "pushl %%ecx\n" + "pushl %%edx\n" + "movl 16(%%esp), %%edx\n" /* reloc_offset */ + "movl 12(%%esp), %%eax\n" /* link_map* */ + "pushl %%edx\n" + "pushl %%eax\n" + "call dl_fixup\n" + "addl $8, %%esp\n" + "popl %%edx\n" + "popl %%ecx\n" + "xchgl %%eax, (%%esp)\n" /* restore eax, put resolved addr on stack */ + "ret $8\n" /* jump to resolved; pop link_map + reloc_offset */ + ::: "memory" + ); +} + +/* ---- Parse a PT_DYNAMIC at the given VA to extract .so symtab info ---- */ +static void parse_shlib_dynamic(uint32_t dyn_va, uint32_t base) { + const struct elf32_dyn* d = (const struct elf32_dyn*)dyn_va; + for (; d->d_tag != DT_NULL; d++) { + switch (d->d_tag) { + case DT_SYMTAB: g_map.shlib_symtab = d->d_val; break; + case DT_STRTAB: g_map.shlib_strtab = d->d_val; break; + case DT_HASH: g_map.shlib_hash = d->d_val; break; + } + } + g_map.shlib_base = base; +} + +/* ---- Scan for shared library's PT_DYNAMIC at SHLIB_BASE ---- */ +static void find_shlib_info(void) { + const uint8_t* base = (const uint8_t*)SHLIB_BASE; + /* Check ELF magic at SHLIB_BASE */ + if (base[0] != 0x7F || base[1] != 'E' || base[2] != 'L' || base[3] != 'F') + return; + + uint32_t e_phoff = *(const uint32_t*)(base + 28); + uint16_t e_phnum = *(const uint16_t*)(base + 44); + uint16_t e_phentsize = *(const uint16_t*)(base + 42); + + for (uint16_t i = 0; i < e_phnum; i++) { + const struct elf32_phdr* ph = + (const struct elf32_phdr*)(base + e_phoff + i * e_phentsize); + if (ph->p_type == PT_DYNAMIC) { + parse_shlib_dynamic(ph->p_vaddr + SHLIB_BASE, SHLIB_BASE); + return; + } + } +} + +/* ---- Entry point ---- */ +static void _start_c(uint32_t* initial_sp) __attribute__((noreturn, used)); + +void _start(void) __attribute__((noreturn, naked, section(".text.start"))); +void _start(void) { + __asm__ volatile( + "pushl %%esp\n" + "call _start_c\n" + ::: "memory" + ); + __builtin_unreachable(); +} + +static void _start_c(uint32_t* initial_sp) { + /* Stack layout set by execve: + * initial_sp → argc + * argv[0], argv[1], ..., NULL + * envp[0], envp[1], ..., NULL + * auxv[0], auxv[1], ..., {AT_NULL, 0} */ + uint32_t* sp = initial_sp; + + uint32_t argc = *sp++; + sp += argc + 1; /* skip argv[] + NULL terminator */ + while (*sp) sp++; /* skip envp[] entries */ + sp++; /* skip envp NULL terminator */ + + /* sp now points to auxv array */ + uint32_t at_entry = 0; + uint32_t at_phdr = 0; + uint32_t at_phnum = 0; + uint32_t at_phent = 0; + + for (uint32_t* p = sp; p[0] != AT_NULL; p += 2) { + switch (p[0]) { + case AT_ENTRY: at_entry = p[1]; break; + case AT_PHDR: at_phdr = p[1]; break; + case AT_PHNUM: at_phnum = p[1]; break; + case AT_PHENT: at_phent = p[1]; break; + } + } + + if (!at_entry) { + __asm__ volatile("mov $2, %%eax\n mov $127, %%ebx\n int $0x80" ::: "eax", "ebx"); + __builtin_unreachable(); + } + + /* Walk program headers to find PT_DYNAMIC */ + g_map.l_addr = 0; + + if (at_phdr && at_phnum && at_phent) { + for (uint32_t i = 0; i < at_phnum; i++) { + const struct elf32_phdr* ph = + (const struct elf32_phdr*)(at_phdr + i * at_phent); + if (ph->p_type == PT_DYNAMIC) { + uint32_t dyn_va = ph->p_vaddr + g_map.l_addr; + const struct elf32_dyn* d = (const struct elf32_dyn*)dyn_va; + uint32_t pltgot = 0; + + for (; d->d_tag != DT_NULL; d++) { + switch (d->d_tag) { + case DT_PLTGOT: pltgot = d->d_val; break; + case DT_JMPREL: g_map.jmprel = d->d_val; break; + case DT_PLTRELSZ: g_map.pltrelsz = d->d_val; break; + case DT_SYMTAB: g_map.symtab = d->d_val; break; + case DT_STRTAB: g_map.strtab = d->d_val; break; + case DT_REL: g_map.rel = d->d_val; break; + case DT_RELSZ: g_map.relsz = d->d_val; break; + } + } + + /* Scan for shared library info BEFORE resolving relocations */ + find_shlib_info(); + + /* Set up GOT for lazy binding: + * GOT[0] = _DYNAMIC (already set by linker) + * GOT[1] = link_map pointer + * GOT[2] = _dl_runtime_resolve address */ + if (pltgot && g_map.jmprel) { + uint32_t* got = (uint32_t*)(pltgot + g_map.l_addr); + got[1] = (uint32_t)&g_map; + got[2] = (uint32_t)&_dl_runtime_resolve; + } + + /* Process eager relocations (R_386_GLOB_DAT, R_386_COPY) */ + if (g_map.rel && g_map.relsz) { + uint32_t nrel = g_map.relsz / sizeof(struct elf32_rel); + const struct elf32_rel* rtab = + (const struct elf32_rel*)(g_map.rel + g_map.l_addr); + for (uint32_t j = 0; j < nrel; j++) { + uint32_t type = ELF32_R_TYPE(rtab[j].r_info); + uint32_t sidx = ELF32_R_SYM(rtab[j].r_info); + uint32_t* target = (uint32_t*)(rtab[j].r_offset + g_map.l_addr); + if (type == R_386_GLOB_DAT || type == R_386_JMP_SLOT) { + const struct elf32_sym* s = + &((const struct elf32_sym*)g_map.symtab)[sidx]; + uint32_t addr = 0; + if (s->st_value != 0) + addr = s->st_value + g_map.l_addr; + else { + const char* nm = (const char*)g_map.strtab + s->st_name; + addr = shlib_lookup(nm, &g_map); + } + if (addr) *target = addr; + } else if (type == R_386_COPY && sidx) { + const struct elf32_sym* s = + &((const struct elf32_sym*)g_map.symtab)[sidx]; + const char* nm = (const char*)g_map.strtab + s->st_name; + uint32_t src = shlib_lookup(nm, &g_map); + if (src && s->st_size > 0) { + const uint8_t* sp = (const uint8_t*)src; + uint8_t* dp = (uint8_t*)target; + for (uint32_t k = 0; k < s->st_size; k++) + dp[k] = sp[k]; + } + } + } + } + break; + } + } + } + + /* Restore the original stack pointer so the real program's _start + * sees the correct layout: [argc] [argv...] [NULL] [envp...] [NULL] [auxv...] + * Then jump to the program entry point. */ + __asm__ volatile( + "mov %0, %%esp\n" + "jmp *%1\n" + :: "r"(initial_sp), "r"(at_entry) + : "memory" + ); + __builtin_unreachable(); +} diff --git a/user/cmds/ln/Makefile b/user/cmds/ln/Makefile new file mode 100644 index 0000000..aca9f3b --- /dev/null +++ b/user/cmds/ln/Makefile @@ -0,0 +1,3 @@ +NAME := ln +SRCS := ln.c +include ../common.mk diff --git a/user/cmds/ln/ln.c b/user/cmds/ln/ln.c new file mode 100644 index 0000000..72f60d4 --- /dev/null +++ b/user/cmds/ln/ln.c @@ -0,0 +1,33 @@ +/* AdrOS ln utility */ +#include +#include +#include + +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] \n"); + return 1; + } + + int r; + if (sflag) { + r = symlink(argv[start], argv[start + 1]); + } else { + r = link(argv[start], argv[start + 1]); + } + + if (r < 0) { + fprintf(stderr, "ln: failed to create %slink '%s' -> '%s'\n", + sflag ? "symbolic " : "", argv[start + 1], argv[start]); + return 1; + } + return 0; +} diff --git a/user/cmds/ls/Makefile b/user/cmds/ls/Makefile new file mode 100644 index 0000000..0434a89 --- /dev/null +++ b/user/cmds/ls/Makefile @@ -0,0 +1,3 @@ +NAME := ls +SRCS := ls.c +include ../common.mk diff --git a/user/cmds/ls/ls.c b/user/cmds/ls/ls.c new file mode 100644 index 0000000..6ce6c69 --- /dev/null +++ b/user/cmds/ls/ls.c @@ -0,0 +1,139 @@ +/* AdrOS ls utility */ +#include +#include +#include +#include +#include +#include +#include + +static int aflag = 0; /* -a: show hidden files */ +static int lflag = 0; /* -l: long format */ + +#define LS_MAX_ENTRIES 512 + +struct ls_entry { + char name[256]; + unsigned char type; +}; + +static struct ls_entry entries[LS_MAX_ENTRIES]; + +static int cmp_entry(const void* a, const void* b) { + return strcmp(((const struct ls_entry*)a)->name, + ((const struct ls_entry*)b)->name); +} + +static void ls_dir(const char* path) { + int fd = open(path, O_RDONLY); + if (fd < 0) { + fprintf(stderr, "ls: cannot access '%s': No such file or directory\n", path); + return; + } + + int count = 0; + char buf[2048]; + int rc; + while ((rc = getdents(fd, buf, sizeof(buf))) > 0) { + int off = 0; + while (off < rc) { + struct dirent* d = (struct dirent*)(buf + off); + if (d->d_reclen == 0) break; + + if (!aflag && d->d_name[0] == '.') { + off += d->d_reclen; + continue; + } + + if (count < LS_MAX_ENTRIES) { + strncpy(entries[count].name, d->d_name, 255); + entries[count].name[255] = '\0'; + entries[count].type = d->d_type; + count++; + } + off += d->d_reclen; + } + } + close(fd); + + qsort(entries, count, sizeof(struct ls_entry), cmp_entry); + + for (int i = 0; i < count; i++) { + if (lflag) { + char fullpath[512]; + size_t plen = strlen(path); + if (plen > 0 && path[plen - 1] == '/') + snprintf(fullpath, sizeof(fullpath), "%s%s", path, entries[i].name); + else + snprintf(fullpath, sizeof(fullpath), "%s/%s", path, entries[i].name); + + struct stat st; + int have_stat = (stat(fullpath, &st) == 0); + + char type = '-'; + if (entries[i].type == DT_DIR) type = 'd'; + else if (entries[i].type == DT_CHR) type = 'c'; + else if (entries[i].type == DT_LNK) type = 'l'; + else if (entries[i].type == DT_BLK) type = 'b'; + + char perms[10]; + if (have_stat) { + unsigned m = (unsigned)st.st_mode; + perms[0] = (m & S_IRUSR) ? 'r' : '-'; + perms[1] = (m & S_IWUSR) ? 'w' : '-'; + perms[2] = (m & S_IXUSR) ? 'x' : '-'; + perms[3] = (m & S_IRGRP) ? 'r' : '-'; + perms[4] = (m & S_IWGRP) ? 'w' : '-'; + perms[5] = (m & S_IXGRP) ? 'x' : '-'; + perms[6] = (m & S_IROTH) ? 'r' : '-'; + perms[7] = (m & S_IWOTH) ? 'w' : '-'; + perms[8] = (m & S_IXOTH) ? 'x' : '-'; + perms[9] = '\0'; + } else { + strcpy(perms, "---------"); + } + + unsigned long sz = have_stat ? (unsigned long)st.st_size : 0; + unsigned nlink = have_stat ? (unsigned)st.st_nlink : 1; + + printf("%c%s %2u root root %8lu %s\n", + type, perms, nlink, sz, entries[i].name); + } else { + printf("%s\n", entries[i].name); + } + } +} + +int main(int argc, char** argv) { + int npath = 0; + const char* paths[64]; + + for (int i = 1; i < argc; i++) { + if (argv[i][0] == '-' && argv[i][1]) { + const char* f = argv[i] + 1; + while (*f) { + if (*f == 'a') aflag = 1; + else if (*f == 'l') lflag = 1; + else { + fprintf(stderr, "ls: invalid option -- '%c'\n", *f); + return 1; + } + f++; + } + } else { + if (npath < 64) paths[npath++] = argv[i]; + } + } + + if (npath == 0) { + ls_dir("."); + } else { + for (int i = 0; i < npath; i++) { + if (npath > 1) printf("%s:\n", paths[i]); + ls_dir(paths[i]); + if (npath > 1 && i < npath - 1) printf("\n"); + } + } + + return 0; +} diff --git a/user/cmds/mkdir/Makefile b/user/cmds/mkdir/Makefile new file mode 100644 index 0000000..b489933 --- /dev/null +++ b/user/cmds/mkdir/Makefile @@ -0,0 +1,3 @@ +NAME := mkdir +SRCS := mkdir.c +include ../common.mk diff --git a/user/cmds/mkdir/mkdir.c b/user/cmds/mkdir/mkdir.c new file mode 100644 index 0000000..d5716c5 --- /dev/null +++ b/user/cmds/mkdir/mkdir.c @@ -0,0 +1,61 @@ +/* AdrOS mkdir utility */ +#include +#include +#include + +static int pflag = 0; /* -p: create parent directories */ + +static int mkdir_p(const char* path) { + char tmp[256]; + size_t len = strlen(path); + if (len >= sizeof(tmp)) return -1; + strcpy(tmp, path); + + for (char* p = tmp + 1; *p; p++) { + if (*p == '/') { + *p = '\0'; + mkdir(tmp); /* ignore errors — parent may already exist */ + *p = '/'; + } + } + return mkdir(tmp); +} + +int main(int argc, char** argv) { + if (argc <= 1) { + fprintf(stderr, "mkdir: missing operand\n"); + return 1; + } + + int rc = 0; + int start = 1; + + for (int i = 1; i < argc; i++) { + if (argv[i][0] == '-' && argv[i][1]) { + const char* f = argv[i] + 1; + while (*f) { + if (*f == 'p') pflag = 1; + else { + fprintf(stderr, "mkdir: invalid option -- '%c'\n", *f); + return 1; + } + f++; + } + start = i + 1; + } + } + + for (int i = start; i < argc; i++) { + int r; + if (pflag) { + r = mkdir_p(argv[i]); + } else { + r = mkdir(argv[i]); + } + if (r < 0) { + fprintf(stderr, "mkdir: cannot create directory '%s'\n", argv[i]); + rc = 1; + } + } + return rc; +} diff --git a/user/cmds/mount/Makefile b/user/cmds/mount/Makefile new file mode 100644 index 0000000..c59274c --- /dev/null +++ b/user/cmds/mount/Makefile @@ -0,0 +1,3 @@ +NAME := mount +SRCS := mount.c +include ../common.mk diff --git a/user/cmds/mount/mount.c b/user/cmds/mount/mount.c new file mode 100644 index 0000000..fba0bf5 --- /dev/null +++ b/user/cmds/mount/mount.c @@ -0,0 +1,57 @@ +/* AdrOS mount utility — mount filesystems or display mounts */ +#include +#include +#include +#include +#include +#include + +static void show_mounts(void) { + int fd = open("/proc/mounts", O_RDONLY); + if (fd >= 0) { + char buf[1024]; + int n; + while ((n = read(fd, buf, sizeof(buf))) > 0) + write(STDOUT_FILENO, buf, (size_t)n); + close(fd); + } else { + printf("tmpfs on / type overlayfs (rw)\n"); + printf("devfs on /dev type devfs (rw)\n"); + printf("procfs on /proc type procfs (ro)\n"); + } +} + +int main(int argc, char** argv) { + if (argc < 2) { + show_mounts(); + return 0; + } + + const char* fstype = "diskfs"; + const char* device = NULL; + const char* mountpoint = NULL; + + /* Parse options first, then collect positional args */ + int i; + for (i = 1; i < argc; i++) { + if (strcmp(argv[i], "-t") == 0 && i + 1 < argc) { + fstype = argv[++i]; + } else if (!device) { + device = argv[i]; + } else if (!mountpoint) { + mountpoint = argv[i]; + } + } + + if (!device || !mountpoint) { + fprintf(stderr, "usage: mount [-t fstype] device mountpoint\n"); + return 1; + } + + int rc = __syscall_ret(_syscall3(SYS_MOUNT, (int)device, (int)mountpoint, (int)fstype)); + if (rc < 0) { + fprintf(stderr, "mount: mounting %s on %s failed: %d\n", device, mountpoint, rc); + return 1; + } + return 0; +} diff --git a/user/cmds/mv/Makefile b/user/cmds/mv/Makefile new file mode 100644 index 0000000..b29cc6a --- /dev/null +++ b/user/cmds/mv/Makefile @@ -0,0 +1,3 @@ +NAME := mv +SRCS := mv.c +include ../common.mk diff --git a/user/cmds/mv/mv.c b/user/cmds/mv/mv.c new file mode 100644 index 0000000..97cb1bc --- /dev/null +++ b/user/cmds/mv/mv.c @@ -0,0 +1,45 @@ +/* AdrOS mv utility */ +#include +#include +#include +#include + +int main(int argc, char** argv) { + if (argc < 3) { + fprintf(stderr, "Usage: mv \n"); + return 1; + } + + /* Try rename first (same filesystem) */ + if (rename(argv[1], argv[2]) == 0) + return 0; + + /* Fallback: copy + unlink */ + int src = open(argv[1], O_RDONLY); + if (src < 0) { + fprintf(stderr, "mv: cannot open '%s'\n", argv[1]); + return 1; + } + + int dst = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (dst < 0) { + fprintf(stderr, "mv: cannot create '%s'\n", argv[2]); + close(src); + return 1; + } + + char buf[4096]; + int r; + while ((r = read(src, buf, sizeof(buf))) > 0) { + if (write(dst, buf, (size_t)r) != r) { + fprintf(stderr, "mv: write error\n"); + close(src); close(dst); + return 1; + } + } + + close(src); + close(dst); + unlink(argv[1]); + return 0; +} diff --git a/user/cmds/pie_test/Makefile b/user/cmds/pie_test/Makefile new file mode 100644 index 0000000..5d3b791 --- /dev/null +++ b/user/cmds/pie_test/Makefile @@ -0,0 +1,30 @@ +# pie_test — PIE/shared library test binary +NAME := pie_test + +TOPDIR ?= $(abspath ../../..) +BUILDDIR := $(TOPDIR)/build/user/cmds/$(NAME) +USER_CC ?= i686-elf-gcc +USER_LD ?= i686-elf-ld + +PIE_SO := $(BUILDDIR)/libpietest.so +PIE_ELF := $(BUILDDIR)/pie_test.elf + +all: $(PIE_SO) $(PIE_ELF) + +$(PIE_SO): pie_func.c + @mkdir -p $(BUILDDIR) + @echo " CC [PIC] $<" + @$(USER_CC) -m32 -fPIC -fno-plt -c pie_func.c -o $(BUILDDIR)/pie_func.o + @$(USER_LD) -m elf_i386 -shared -soname libpietest.so -o $@ $(BUILDDIR)/pie_func.o + +$(PIE_ELF): pie_main.c $(PIE_SO) + @mkdir -p $(BUILDDIR) + @echo " CC [PIE] $<" + @$(USER_CC) -m32 -fPIC -c pie_main.c -o $(BUILDDIR)/pie_main.o + @$(USER_LD) -m elf_i386 -pie --dynamic-linker=/lib/ld.so \ + -T $(TOPDIR)/user/pie_linker.ld -o $@ $(BUILDDIR)/pie_main.o $(PIE_SO) -rpath /lib + +clean: + rm -f $(BUILDDIR)/pie_func.o $(BUILDDIR)/pie_main.o $(PIE_SO) $(PIE_ELF) + +.PHONY: all clean diff --git a/user/cmds/pie_test/pie_func.c b/user/cmds/pie_test/pie_func.c new file mode 100644 index 0000000..8e744f8 --- /dev/null +++ b/user/cmds/pie_test/pie_func.c @@ -0,0 +1,7 @@ +/* Shared library function for PLT/GOT lazy binding test. + * Compiled as a shared object (libpietest.so), loaded at SHLIB_BASE by kernel. + * The main PIE binary calls test_add() through PLT — resolved lazily by ld.so. */ + +int test_add(int a, int b) { + return a + b; +} diff --git a/user/cmds/pie_test/pie_main.c b/user/cmds/pie_test/pie_main.c new file mode 100644 index 0000000..9232cf4 --- /dev/null +++ b/user/cmds/pie_test/pie_main.c @@ -0,0 +1,34 @@ +/* PIE test binary for PLT/GOT lazy binding verification. + * Calls test_add() from libpietest.so through PLT — resolved lazily by ld.so. + * Built as: i686-elf-ld -pie --dynamic-linker=/lib/ld.so */ + +static inline void sys_exit(int code) { + __asm__ volatile("int $0x80" :: "a"(2), "b"(code) : "memory"); +} + +static inline int sys_write(int fd, const void* buf, unsigned len) { + int ret; + __asm__ volatile("int $0x80" : "=a"(ret) : "a"(1), "b"(fd), "c"(buf), "d"(len) : "memory"); + return ret; +} + +extern int test_add(int a, int b); + +void _start(void) { + int r = test_add(38, 4); + if (r == 42) { + sys_write(1, "[test] lazy PLT OK\n", 19); + } else { + sys_write(1, "[test] lazy PLT FAIL\n", 21); + } + + /* Call again — this time GOT is already patched, tests direct path */ + r = test_add(100, 23); + if (r == 123) { + sys_write(1, "[test] PLT cached OK\n", 21); + } else { + sys_write(1, "[test] PLT cached FAIL\n", 23); + } + + sys_exit(0); +} diff --git a/user/cmds/printenv/Makefile b/user/cmds/printenv/Makefile new file mode 100644 index 0000000..2b925ff --- /dev/null +++ b/user/cmds/printenv/Makefile @@ -0,0 +1,3 @@ +NAME := printenv +SRCS := printenv.c +include ../common.mk diff --git a/user/cmds/printenv/printenv.c b/user/cmds/printenv/printenv.c new file mode 100644 index 0000000..0e3f081 --- /dev/null +++ b/user/cmds/printenv/printenv.c @@ -0,0 +1,27 @@ +/* AdrOS printenv utility — print environment variables */ +#include +#include + +extern char** __environ; + +int main(int argc, char** argv) { + if (!__environ) return 1; + if (argc <= 1) { + for (int i = 0; __environ[i]; i++) + printf("%s\n", __environ[i]); + return 0; + } + for (int i = 1; i < argc; i++) { + int found = 0; + int nlen = (int)strlen(argv[i]); + for (int j = 0; __environ[j]; j++) { + if (strncmp(__environ[j], argv[i], (size_t)nlen) == 0 && __environ[j][nlen] == '=') { + printf("%s\n", __environ[j] + nlen + 1); + found = 1; + break; + } + } + if (!found) return 1; + } + return 0; +} diff --git a/user/cmds/ps/Makefile b/user/cmds/ps/Makefile new file mode 100644 index 0000000..7d31d93 --- /dev/null +++ b/user/cmds/ps/Makefile @@ -0,0 +1,3 @@ +NAME := ps +SRCS := ps.c +include ../common.mk diff --git a/user/cmds/ps/ps.c b/user/cmds/ps/ps.c new file mode 100644 index 0000000..251de0d --- /dev/null +++ b/user/cmds/ps/ps.c @@ -0,0 +1,47 @@ +/* AdrOS ps utility — list processes from /proc */ +#include +#include +#include +#include +#include + +static int is_digit(char c) { return c >= '0' && c <= '9'; } + +int main(void) { + printf(" PID CMD\n"); + int fd = open("/proc", O_RDONLY); + if (fd < 0) { + fprintf(stderr, "ps: cannot open /proc\n"); + return 1; + } + char buf[512]; + int rc; + while ((rc = getdents(fd, buf, sizeof(buf))) > 0) { + int off = 0; + while (off < rc) { + struct dirent* d = (struct dirent*)(buf + off); + if (d->d_reclen == 0) break; + if (is_digit(d->d_name[0])) { + char path[64]; + snprintf(path, sizeof(path), "/proc/%s/cmdline", d->d_name); + int cfd = open(path, O_RDONLY); + char cmd[64] = "?"; + if (cfd >= 0) { + int n = read(cfd, cmd, sizeof(cmd) - 1); + if (n > 0) { + cmd[n] = '\0'; + while (n > 0 && (cmd[n-1] == '\n' || cmd[n-1] == '\0')) { + cmd[--n] = '\0'; + } + } + if (n <= 0) strcpy(cmd, "[kernel]"); + close(cfd); + } + printf("%5s %s\n", d->d_name, cmd); + } + off += d->d_reclen; + } + } + close(fd); + return 0; +} diff --git a/user/cmds/pwd/Makefile b/user/cmds/pwd/Makefile new file mode 100644 index 0000000..388f5f0 --- /dev/null +++ b/user/cmds/pwd/Makefile @@ -0,0 +1,3 @@ +NAME := pwd +SRCS := pwd.c +include ../common.mk diff --git a/user/cmds/pwd/pwd.c b/user/cmds/pwd/pwd.c new file mode 100644 index 0000000..baa6134 --- /dev/null +++ b/user/cmds/pwd/pwd.c @@ -0,0 +1,14 @@ +/* AdrOS pwd utility — print working directory */ +#include +#include + +int main(void) { + char buf[256]; + if (getcwd(buf, sizeof(buf)) >= 0) + printf("%s\n", buf); + else { + fprintf(stderr, "pwd: error\n"); + return 1; + } + return 0; +} diff --git a/user/cmds/rm/Makefile b/user/cmds/rm/Makefile new file mode 100644 index 0000000..0667c46 --- /dev/null +++ b/user/cmds/rm/Makefile @@ -0,0 +1,3 @@ +NAME := rm +SRCS := rm.c +include ../common.mk diff --git a/user/cmds/rm/rm.c b/user/cmds/rm/rm.c new file mode 100644 index 0000000..0a4d22e --- /dev/null +++ b/user/cmds/rm/rm.c @@ -0,0 +1,48 @@ +/* AdrOS rm utility */ +#include +#include +#include + +static int rflag = 0; /* -r: recursive */ +static int fflag = 0; /* -f: force (no errors) */ +static int dflag = 0; /* -d: remove empty directories */ + +int main(int argc, char** argv) { + if (argc <= 1) { + fprintf(stderr, "rm: missing operand\n"); + return 1; + } + + int start = 1; + for (int i = 1; i < argc; i++) { + if (argv[i][0] == '-' && argv[i][1]) { + const char* f = argv[i] + 1; + while (*f) { + if (*f == 'r' || *f == 'R') rflag = 1; + else if (*f == 'f') fflag = 1; + else if (*f == 'd') dflag = 1; + else { + fprintf(stderr, "rm: invalid option -- '%c'\n", *f); + return 1; + } + f++; + } + start = i + 1; + } else { + break; + } + } + + int rc = 0; + for (int i = start; i < argc; i++) { + int r = unlink(argv[i]); + if (r < 0 && (rflag || dflag)) { + r = rmdir(argv[i]); + } + if (r < 0 && !fflag) { + fprintf(stderr, "rm: cannot remove '%s'\n", argv[i]); + rc = 1; + } + } + return rc; +} diff --git a/user/cmds/rmdir/Makefile b/user/cmds/rmdir/Makefile new file mode 100644 index 0000000..df87ffb --- /dev/null +++ b/user/cmds/rmdir/Makefile @@ -0,0 +1,3 @@ +NAME := rmdir +SRCS := rmdir.c +include ../common.mk diff --git a/user/cmds/rmdir/rmdir.c b/user/cmds/rmdir/rmdir.c new file mode 100644 index 0000000..0d65e8a --- /dev/null +++ b/user/cmds/rmdir/rmdir.c @@ -0,0 +1,18 @@ +/* AdrOS rmdir utility — remove empty directories */ +#include +#include + +int main(int argc, char** argv) { + if (argc <= 1) { + fprintf(stderr, "rmdir: missing operand\n"); + return 1; + } + int rc = 0; + for (int i = 1; i < argc; i++) { + if (rmdir(argv[i]) < 0) { + fprintf(stderr, "rmdir: failed to remove '%s'\n", argv[i]); + rc = 1; + } + } + return rc; +} diff --git a/user/cmds/sed/Makefile b/user/cmds/sed/Makefile new file mode 100644 index 0000000..1b3a94c --- /dev/null +++ b/user/cmds/sed/Makefile @@ -0,0 +1,3 @@ +NAME := sed +SRCS := sed.c +include ../common.mk diff --git a/user/cmds/sed/sed.c b/user/cmds/sed/sed.c new file mode 100644 index 0000000..53992f2 --- /dev/null +++ b/user/cmds/sed/sed.c @@ -0,0 +1,95 @@ +/* AdrOS sed utility — minimal stream editor (s/pattern/replacement/g only) */ +#include +#include +#include +#include + +static int match_at(const char* s, const char* pat, int patlen) { + for (int i = 0; i < patlen; i++) { + if (s[i] == '\0' || s[i] != pat[i]) return 0; + } + return 1; +} + +static void sed_substitute(const char* line, const char* pat, int patlen, + const char* rep, int replen, int global) { + const char* p = line; + while (*p) { + if (match_at(p, pat, patlen)) { + write(STDOUT_FILENO, rep, replen); + p += patlen; + if (!global) { + write(STDOUT_FILENO, p, strlen(p)); + return; + } + } else { + write(STDOUT_FILENO, p, 1); + p++; + } + } +} + +static int parse_s_cmd(const char* expr, char* pat, int* patlen, + char* rep, int* replen, int* global) { + if (expr[0] != 's' || expr[1] == '\0') return -1; + char delim = expr[1]; + const char* p = expr + 2; + int pi = 0; + while (*p && *p != delim && pi < 255) pat[pi++] = *p++; + pat[pi] = '\0'; *patlen = pi; + if (*p != delim) return -1; + p++; + int ri = 0; + while (*p && *p != delim && ri < 255) rep[ri++] = *p++; + rep[ri] = '\0'; *replen = ri; + *global = 0; + if (*p == delim) { p++; if (*p == 'g') *global = 1; } + return 0; +} + +int main(int argc, char** argv) { + if (argc < 2) { + fprintf(stderr, "Usage: sed 's/pattern/replacement/[g]' [file]\n"); + return 1; + } + + char pat[256], rep[256]; + int patlen, replen, global; + int ei = 1; + if (strcmp(argv[1], "-e") == 0 && argc > 2) ei = 2; + + if (parse_s_cmd(argv[ei], pat, &patlen, rep, &replen, &global) < 0) { + fprintf(stderr, "sed: invalid expression: %s\n", argv[ei]); + return 1; + } + + int fd = STDIN_FILENO; + if (argc > ei + 1) { + fd = open(argv[ei + 1], O_RDONLY); + if (fd < 0) { + fprintf(stderr, "sed: %s: No such file or directory\n", argv[ei + 1]); + return 1; + } + } + + char line[4096]; + int li = 0; + char c; + while (read(fd, &c, 1) == 1) { + if (c == '\n') { + line[li] = '\0'; + sed_substitute(line, pat, patlen, rep, replen, global); + write(STDOUT_FILENO, "\n", 1); + li = 0; + } else if (li < (int)sizeof(line) - 1) { + line[li++] = c; + } + } + if (li > 0) { + line[li] = '\0'; + sed_substitute(line, pat, patlen, rep, replen, global); + } + + if (fd != STDIN_FILENO) close(fd); + return 0; +} diff --git a/user/cmds/sh/Makefile b/user/cmds/sh/Makefile new file mode 100644 index 0000000..8191554 --- /dev/null +++ b/user/cmds/sh/Makefile @@ -0,0 +1,3 @@ +NAME := sh +SRCS := sh.c +include ../common.mk diff --git a/user/cmds/sh/sh.c b/user/cmds/sh/sh.c new file mode 100644 index 0000000..1af3ab3 --- /dev/null +++ b/user/cmds/sh/sh.c @@ -0,0 +1,1035 @@ +/* AdrOS POSIX-like shell (/bin/sh) + * + * Features: + * - Variable assignment (VAR=value) and expansion ($VAR) + * - Environment variables (export VAR=value) + * - Line editing (left/right arrow keys) + * - Command history (up/down arrow keys) + * - Pipes (cmd1 | cmd2 | cmd3) + * - Redirections (< > >>) + * - Operators: ; && || & + * - Job control: CTRL+C (SIGINT), CTRL+Z (SIGTSTP), background (&) + * - Builtins: cd, exit, echo, export, unset, set, pwd, type + * - PATH-based command resolution + * - Quote handling (single and double quotes) + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static struct termios orig_termios; + +static void tty_raw_mode(void) { + tcgetattr(STDIN_FILENO, &orig_termios); + struct termios raw = orig_termios; + raw.c_lflag &= ~(ICANON | ECHO | ISIG); + raw.c_cc[VMIN] = 1; + raw.c_cc[VTIME] = 0; + tcsetattr(STDIN_FILENO, TCSANOW, &raw); +} + +static void tty_restore(void) { + tcsetattr(STDIN_FILENO, TCSANOW, &orig_termios); +} + +#define LINE_MAX 512 +#define MAX_ARGS 64 +#define MAX_VARS 64 +#define HIST_SIZE 32 + +/* ---- Shell variables ---- */ + +static struct { + char name[64]; + char value[256]; + int exported; +} vars[MAX_VARS]; +static int nvar = 0; + +static int last_status = 0; + +static const char* var_get(const char* name) { + for (int i = 0; i < nvar; i++) + if (strcmp(vars[i].name, name) == 0) return vars[i].value; + return getenv(name); +} + +static void var_set(const char* name, const char* value, int exported) { + for (int i = 0; i < nvar; i++) { + if (strcmp(vars[i].name, name) == 0) { + strncpy(vars[i].value, value, 255); + vars[i].value[255] = '\0'; + if (exported) vars[i].exported = 1; + return; + } + } + if (nvar < MAX_VARS) { + strncpy(vars[nvar].name, name, 63); + vars[nvar].name[63] = '\0'; + strncpy(vars[nvar].value, value, 255); + vars[nvar].value[255] = '\0'; + vars[nvar].exported = exported; + nvar++; + } +} + +static void var_unset(const char* name) { + for (int i = 0; i < nvar; i++) { + if (strcmp(vars[i].name, name) == 0) { + vars[i] = vars[--nvar]; + return; + } + } +} + +/* Build envp array from exported variables */ +static char env_buf[MAX_VARS][320]; +static char* envp_arr[MAX_VARS + 1]; + +static char** build_envp(void) { + int n = 0; + for (int i = 0; i < nvar && n < MAX_VARS; i++) { + if (!vars[i].exported) continue; + snprintf(env_buf[n], sizeof(env_buf[n]), "%s=%s", + vars[i].name, vars[i].value); + envp_arr[n] = env_buf[n]; + n++; + } + envp_arr[n] = NULL; + return envp_arr; +} + +/* ---- Command history ---- */ + +static char history[HIST_SIZE][LINE_MAX]; +static int hist_count = 0; +static int hist_pos = 0; + +static void hist_add(const char* line) { + if (line[0] == '\0') return; + if (hist_count > 0 && strcmp(history[(hist_count - 1) % HIST_SIZE], line) == 0) + return; + strncpy(history[hist_count % HIST_SIZE], line, LINE_MAX - 1); + history[hist_count % HIST_SIZE][LINE_MAX - 1] = '\0'; + hist_count++; +} + +/* ---- Line editing ---- */ + +static char line[LINE_MAX]; + +static void term_write(const char* s, int n) { + write(STDOUT_FILENO, s, (size_t)n); +} + +/* ---- Tab completion ---- */ + +static int tab_complete(char* buf, int* p_pos, int* p_len) { + int pos = *p_pos; + int len = *p_len; + + /* Find the start of the current word */ + int wstart = pos; + while (wstart > 0 && buf[wstart - 1] != ' ' && buf[wstart - 1] != '\t') + wstart--; + + char prefix[128]; + int plen = pos - wstart; + if (plen <= 0 || plen >= (int)sizeof(prefix)) return 0; + memcpy(prefix, buf + wstart, (size_t)plen); + prefix[plen] = '\0'; + + /* Determine if this is a command (first word) or filename */ + int is_cmd = 1; + for (int i = 0; i < wstart; i++) { + if (buf[i] != ' ' && buf[i] != '\t') { is_cmd = 0; break; } + } + + char match[128]; + match[0] = '\0'; + int nmatches = 0; + + /* Split prefix into directory part and name part for file completion */ + char dirpath[128] = "."; + const char* namepfx = prefix; + char* lastsep = NULL; + for (char* p = prefix; *p; p++) { + if (*p == '/') lastsep = p; + } + if (lastsep) { + int dlen = (int)(lastsep - prefix); + if (dlen == 0) { dirpath[0] = '/'; dirpath[1] = '\0'; } + else { memcpy(dirpath, prefix, (size_t)dlen); dirpath[dlen] = '\0'; } + namepfx = lastsep + 1; + } + int nplen = (int)strlen(namepfx); + + if (!is_cmd || lastsep) { + /* File/directory completion */ + int fd = open(dirpath, 0); + if (fd >= 0) { + char dbuf[512]; + int rc; + while ((rc = getdents(fd, dbuf, sizeof(dbuf))) > 0) { + int off = 0; + while (off < rc) { + struct dirent* d = (struct dirent*)(dbuf + off); + if (d->d_reclen == 0) break; + if (d->d_name[0] != '.' || nplen > 0) { + int nlen = (int)strlen(d->d_name); + if (nlen >= nplen && memcmp(d->d_name, namepfx, (size_t)nplen) == 0) { + if (nmatches == 0) strcpy(match, d->d_name); + nmatches++; + } + } + off += d->d_reclen; + } + } + close(fd); + } + } + + if (is_cmd && !lastsep) { + /* Command completion: search PATH directories + builtins */ + static const char* builtins[] = { + "cd", "exit", "echo", "export", "unset", "set", "pwd", "type", NULL + }; + for (int i = 0; builtins[i]; i++) { + int blen = (int)strlen(builtins[i]); + if (blen >= plen && memcmp(builtins[i], prefix, (size_t)plen) == 0) { + if (nmatches == 0) strcpy(match, builtins[i]); + nmatches++; + } + } + const char* path_env = var_get("PATH"); + if (!path_env) path_env = "/bin:/sbin:/usr/bin"; + char pathcopy[512]; + strncpy(pathcopy, path_env, sizeof(pathcopy) - 1); + pathcopy[sizeof(pathcopy) - 1] = '\0'; + char* save = pathcopy; + char* dir; + while ((dir = save) != NULL) { + char* sep = strchr(save, ':'); + if (sep) { *sep = '\0'; save = sep + 1; } else save = NULL; + int fd = open(dir, 0); + if (fd < 0) continue; + char dbuf[512]; + int rc; + while ((rc = getdents(fd, dbuf, sizeof(dbuf))) > 0) { + int off = 0; + while (off < rc) { + struct dirent* d = (struct dirent*)(dbuf + off); + if (d->d_reclen == 0) break; + int nlen = (int)strlen(d->d_name); + if (nlen >= plen && memcmp(d->d_name, prefix, (size_t)plen) == 0) { + if (nmatches == 0) strcpy(match, d->d_name); + nmatches++; + } + off += d->d_reclen; + } + } + close(fd); + } + } + + if (nmatches != 1) return 0; + + /* Insert the completion suffix */ + int mlen = (int)strlen(match); + int suffix_len = is_cmd && !lastsep ? mlen - plen : mlen - nplen; + const char* suffix = is_cmd && !lastsep ? match + plen : match + nplen; + if (suffix_len <= 0 || len + suffix_len >= LINE_MAX - 1) return 0; + + memmove(buf + pos + suffix_len, buf + pos, (size_t)(len - pos)); + memcpy(buf + pos, suffix, (size_t)suffix_len); + len += suffix_len; + buf[len] = '\0'; + term_write(buf + pos, len - pos); + pos += suffix_len; + for (int i = 0; i < len - pos; i++) term_write("\b", 1); + *p_pos = pos; + *p_len = len; + return 1; +} + +static int read_line_edit(void) { + int pos = 0; + int len = 0; + hist_pos = hist_count; + + memset(line, 0, LINE_MAX); + + while (len < LINE_MAX - 1) { + char c; + int r = read(STDIN_FILENO, &c, 1); + if (r <= 0) { + if (len == 0) return -1; + break; + } + + if (c == '\n' || c == '\r') { + term_write("\n", 1); + break; + } + + /* Backspace / DEL */ + if (c == '\b' || c == 127) { + if (pos > 0) { + memmove(line + pos - 1, line + pos, (size_t)(len - pos)); + pos--; len--; + line[len] = '\0'; + /* Redraw: move cursor back, print rest, clear tail */ + term_write("\b", 1); + term_write(line + pos, len - pos); + term_write(" \b", 2); + for (int i = 0; i < len - pos; i++) term_write("\b", 1); + } + continue; + } + + /* Tab = autocomplete */ + if (c == '\t') { + tab_complete(line, &pos, &len); + continue; + } + + /* Ctrl+D = EOF */ + if (c == 4) { + if (len == 0) return -1; + continue; + } + + /* Ctrl+C = cancel line */ + if (c == 3) { + term_write("^C\n", 3); + line[0] = '\0'; + return 0; + } + + /* Ctrl+Z = ignored at prompt (no foreground job to suspend) */ + if (c == 26) { + continue; + } + + /* Ctrl+A = beginning of line */ + if (c == 1) { + while (pos > 0) { term_write("\b", 1); pos--; } + continue; + } + + /* Ctrl+E = end of line */ + if (c == 5) { + term_write(line + pos, len - pos); + pos = len; + continue; + } + + /* Ctrl+U = clear line */ + if (c == 21) { + while (pos > 0) { term_write("\b", 1); pos--; } + for (int i = 0; i < len; i++) term_write(" ", 1); + for (int i = 0; i < len; i++) term_write("\b", 1); + len = 0; pos = 0; + line[0] = '\0'; + continue; + } + + /* Escape sequences (arrow keys) */ + if (c == 27) { + char seq[2]; + if (read(STDIN_FILENO, &seq[0], 1) <= 0) continue; + if (seq[0] != '[') continue; + if (read(STDIN_FILENO, &seq[1], 1) <= 0) continue; + + if (seq[1] >= '0' && seq[1] <= '9') { + /* Extended sequence like \x1b[3~ (DELETE), \x1b[1~ (Home), \x1b[4~ (End) */ + char trail; + if (read(STDIN_FILENO, &trail, 1) <= 0) continue; + if (trail == '~') { + if (seq[1] == '3') { + /* DELETE key — delete char at cursor */ + if (pos < len) { + memmove(line + pos, line + pos + 1, (size_t)(len - pos - 1)); + len--; + line[len] = '\0'; + term_write(line + pos, len - pos); + term_write(" \b", 2); + for (int i = 0; i < len - pos; i++) term_write("\b", 1); + } + } else if (seq[1] == '1') { + /* Home */ + while (pos > 0) { term_write("\b", 1); pos--; } + } else if (seq[1] == '4') { + /* End */ + term_write(line + pos, len - pos); + pos = len; + } + } + continue; + } + + switch (seq[1]) { + case 'A': /* Up arrow — previous history */ + if (hist_pos > 0 && hist_pos > hist_count - HIST_SIZE) { + hist_pos--; + /* Clear current line */ + while (pos > 0) { term_write("\b", 1); pos--; } + for (int i = 0; i < len; i++) term_write(" ", 1); + for (int i = 0; i < len; i++) term_write("\b", 1); + /* Load history entry */ + strcpy(line, history[hist_pos % HIST_SIZE]); + len = (int)strlen(line); + pos = len; + term_write(line, len); + } + break; + case 'B': /* Down arrow — next history */ + if (hist_pos < hist_count) { + hist_pos++; + while (pos > 0) { term_write("\b", 1); pos--; } + for (int i = 0; i < len; i++) term_write(" ", 1); + for (int i = 0; i < len; i++) term_write("\b", 1); + if (hist_pos < hist_count) { + strcpy(line, history[hist_pos % HIST_SIZE]); + } else { + line[0] = '\0'; + } + len = (int)strlen(line); + pos = len; + term_write(line, len); + } + break; + case 'C': /* Right arrow */ + if (pos < len) { term_write(line + pos, 1); pos++; } + break; + case 'D': /* Left arrow */ + if (pos > 0) { term_write("\b", 1); pos--; } + break; + case 'H': /* Home */ + while (pos > 0) { term_write("\b", 1); pos--; } + break; + case 'F': /* End */ + term_write(line + pos, len - pos); + pos = len; + break; + } + continue; + } + + /* Normal printable character */ + if (c >= ' ' && c <= '~') { + memmove(line + pos + 1, line + pos, (size_t)(len - pos)); + line[pos] = c; + len++; line[len] = '\0'; + term_write(line + pos, len - pos); + pos++; + for (int i = 0; i < len - pos; i++) term_write("\b", 1); + } + } + + line[len] = '\0'; + return len; +} + +/* ---- Variable expansion ---- */ + +static void expand_vars(const char* src, char* dst, int maxlen) { + int di = 0; + while (*src && di < maxlen - 1) { + if (*src == '$') { + src++; + if (*src == '?') { + di += snprintf(dst + di, (size_t)(maxlen - di), "%d", last_status); + src++; + } else if (*src == '{') { + src++; + char name[64]; + int ni = 0; + while (*src && *src != '}' && ni < 63) name[ni++] = *src++; + name[ni] = '\0'; + if (*src == '}') src++; + const char* val = var_get(name); + if (val) { + int vl = (int)strlen(val); + if (di + vl < maxlen) { memcpy(dst + di, val, (size_t)vl); di += vl; } + } + } else { + char name[64]; + int ni = 0; + while ((*src >= 'A' && *src <= 'Z') || (*src >= 'a' && *src <= 'z') || + (*src >= '0' && *src <= '9') || *src == '_') { + if (ni < 63) name[ni++] = *src; + src++; + } + name[ni] = '\0'; + const char* val = var_get(name); + if (val) { + int vl = (int)strlen(val); + if (di + vl < maxlen) { memcpy(dst + di, val, (size_t)vl); di += vl; } + } + } + } else { + dst[di++] = *src++; + } + } + dst[di] = '\0'; +} + +/* ---- Argument parsing with quote handling ---- */ + +static int parse_args(char* cmd, char** argv, int max) { + int argc = 0; + char* p = cmd; + while (*p && argc < max - 1) { + while (*p == ' ' || *p == '\t') p++; + if (*p == '\0') break; + + char* out = p; + argv[argc++] = out; + + while (*p && *p != ' ' && *p != '\t') { + if (*p == '\'' ) { + p++; + while (*p && *p != '\'') *out++ = *p++; + if (*p == '\'') p++; + } else if (*p == '"') { + p++; + while (*p && *p != '"') *out++ = *p++; + if (*p == '"') p++; + } else { + *out++ = *p++; + } + } + if (*p) p++; + *out = '\0'; + } + argv[argc] = NULL; + return argc; +} + +/* ---- PATH resolution ---- */ + +static char pathbuf[256]; + +static const char* resolve(const char* cmd) { + if (cmd[0] == '/' || cmd[0] == '.') return cmd; + + const char* path_env = var_get("PATH"); + if (!path_env) path_env = "/bin:/sbin:/usr/bin"; + + char pathcopy[512]; + strncpy(pathcopy, path_env, sizeof(pathcopy) - 1); + pathcopy[sizeof(pathcopy) - 1] = '\0'; + + char* save = pathcopy; + char* dir; + while ((dir = save) != NULL) { + char* sep = strchr(save, ':'); + if (sep) { *sep = '\0'; save = sep + 1; } + else save = NULL; + + snprintf(pathbuf, sizeof(pathbuf), "%s/%s", dir, cmd); + if (access(pathbuf, 0) == 0) return pathbuf; + } + return cmd; +} + +/* ---- Helper: set foreground process group on controlling TTY ---- */ + +static void set_fg_pgrp(int pgrp) { + ioctl(STDIN_FILENO, TIOCSPGRP, &pgrp); +} + +/* ---- Run a single simple command ---- */ + +static int bg_flag = 0; /* set by caller when trailing & detected */ + +static void run_simple(char* cmd) { + /* Expand variables */ + char expanded[LINE_MAX]; + expand_vars(cmd, expanded, LINE_MAX); + + char* argv[MAX_ARGS]; + int argc = parse_args(expanded, argv, MAX_ARGS); + if (argc == 0) return; + + /* Check for variable assignment (no command, just VAR=value) */ + if (argc == 1 && strchr(argv[0], '=') != NULL) { + char* eq = strchr(argv[0], '='); + *eq = '\0'; + var_set(argv[0], eq + 1, 0); + last_status = 0; + return; + } + + /* Extract redirections */ + char* redir_out = NULL; + char* redir_in = NULL; + int append = 0; + int heredoc_fd = -1; + int nargc = 0; + for (int i = 0; i < argc; i++) { + if (strcmp(argv[i], ">>") == 0 && i + 1 < argc) { + redir_out = argv[++i]; append = 1; + } else if (strcmp(argv[i], ">") == 0 && i + 1 < argc) { + redir_out = argv[++i]; append = 0; + } else if (strcmp(argv[i], "<<") == 0 && i + 1 < argc) { + char* delim = argv[++i]; + int dlen = (int)strlen(delim); + if (dlen > 0 && (delim[0] == '"' || delim[0] == '\'')) { + delim++; dlen -= 2; if (dlen < 0) dlen = 0; + delim[dlen] = '\0'; + } + int pfd[2]; + if (pipe(pfd) == 0) { + tty_restore(); + char hline[LINE_MAX]; + while (1) { + write(STDOUT_FILENO, "> ", 2); + int hi = 0; + char hc; + while (read(STDIN_FILENO, &hc, 1) == 1) { + if (hc == '\n') break; + if (hi < LINE_MAX - 1) hline[hi++] = hc; + } + hline[hi] = '\0'; + if (strcmp(hline, delim) == 0) break; + write(pfd[1], hline, hi); + write(pfd[1], "\n", 1); + } + close(pfd[1]); + heredoc_fd = pfd[0]; + tty_raw_mode(); + } + } else if (strcmp(argv[i], "<") == 0 && i + 1 < argc) { + redir_in = argv[++i]; + } else { + argv[nargc++] = argv[i]; + } + } + argv[nargc] = NULL; + argc = nargc; + if (argc == 0) return; + + /* ---- Apply redirections for builtins too ---- */ + int saved_stdin = -1, saved_stdout = -1; + if (heredoc_fd >= 0) { + saved_stdin = dup(0); dup2(heredoc_fd, 0); close(heredoc_fd); heredoc_fd = -1; + } else if (redir_in) { + int fd = open(redir_in, O_RDONLY); + if (fd >= 0) { saved_stdin = dup(0); dup2(fd, 0); close(fd); } + } + if (redir_out) { + int flags = O_WRONLY | O_CREAT; + flags |= append ? O_APPEND : O_TRUNC; + int fd = open(redir_out, flags); + if (fd >= 0) { saved_stdout = dup(1); dup2(fd, 1); close(fd); } + } + + /* ---- Builtins ---- */ + + if (strcmp(argv[0], "exit") == 0) { + int code = argc > 1 ? atoi(argv[1]) : last_status; + exit(code); + } + + if (strcmp(argv[0], "cd") == 0) { + const char* dir = argc > 1 ? argv[1] : var_get("HOME"); + if (!dir) dir = "/"; + if (chdir(dir) < 0) + fprintf(stderr, "cd: %s: No such file or directory\n", dir); + else { + char cwd[256]; + if (getcwd(cwd, sizeof(cwd)) >= 0) + var_set("PWD", cwd, 1); + } + goto restore_redir; + } + + if (strcmp(argv[0], "pwd") == 0) { + char cwd[256]; + if (getcwd(cwd, sizeof(cwd)) >= 0) + printf("%s\n", cwd); + else + fprintf(stderr, "pwd: error\n"); + goto restore_redir; + } + + if (strcmp(argv[0], "export") == 0) { + for (int i = 1; i < argc; i++) { + char* eq = strchr(argv[i], '='); + if (eq) { + *eq = '\0'; + var_set(argv[i], eq + 1, 1); + } else { + /* Export existing variable */ + for (int j = 0; j < nvar; j++) + if (strcmp(vars[j].name, argv[i]) == 0) + vars[j].exported = 1; + } + } + goto restore_redir; + } + + if (strcmp(argv[0], "unset") == 0) { + for (int i = 1; i < argc; i++) var_unset(argv[i]); + goto restore_redir; + } + + if (strcmp(argv[0], "set") == 0) { + for (int i = 0; i < nvar; i++) + printf("%s=%s\n", vars[i].name, vars[i].value); + goto restore_redir; + } + + if (strcmp(argv[0], "echo") == 0) { + int nflag = 0; + int start = 1; + if (argc > 1 && strcmp(argv[1], "-n") == 0) { nflag = 1; start = 2; } + for (int i = start; i < argc; i++) { + if (i > start) write(STDOUT_FILENO, " ", 1); + write(STDOUT_FILENO, argv[i], strlen(argv[i])); + } + if (!nflag) write(STDOUT_FILENO, "\n", 1); + goto restore_redir; + } + + if (strcmp(argv[0], "type") == 0) { + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "cd") == 0 || strcmp(argv[i], "exit") == 0 || + strcmp(argv[i], "echo") == 0 || strcmp(argv[i], "export") == 0 || + strcmp(argv[i], "unset") == 0 || strcmp(argv[i], "set") == 0 || + strcmp(argv[i], "pwd") == 0 || strcmp(argv[i], "type") == 0) { + printf("%s is a shell builtin\n", argv[i]); + } else { + const char* path = resolve(argv[i]); + if (strcmp(path, argv[i]) != 0) + printf("%s is %s\n", argv[i], path); + else + printf("%s: not found\n", argv[i]); + } + } + goto restore_redir; + } + + /* ---- External command — restore parent redirections before fork ---- */ + const char* path = resolve(argv[0]); + char** envp = build_envp(); + + int pid = fork(); + if (pid < 0) { fprintf(stderr, "sh: fork failed\n"); return; } + + if (pid == 0) { + /* child: own process group, restore default signals */ + setpgid(0, 0); + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = (uintptr_t)SIG_DFL; + sigaction(SIGINT, &sa, NULL); + sigaction(SIGTSTP, &sa, NULL); + sigaction(SIGQUIT, &sa, NULL); + + if (redir_in) { + int fd = open(redir_in, O_RDONLY); + if (fd >= 0) { dup2(fd, 0); close(fd); } + } + if (redir_out) { + int flags = O_WRONLY | O_CREAT; + flags |= append ? O_APPEND : O_TRUNC; + int fd = open(redir_out, flags); + if (fd >= 0) { dup2(fd, 1); close(fd); } + } + execve(path, (const char* const*)argv, (const char* const*)envp); + fprintf(stderr, "sh: %s: not found\n", argv[0]); + _exit(127); + } + + /* parent */ + setpgid(pid, pid); + + if (bg_flag) { + /* Background: don't wait, print job info */ + printf("[bg] %d\n", pid); + last_status = 0; + } else { + /* Foreground: make child the fg process group, wait, then restore */ + set_fg_pgrp(pid); + int st; + waitpid(pid, &st, 0); + set_fg_pgrp(getpgrp()); + last_status = st; + } + goto restore_redir; + +restore_redir: + if (saved_stdout >= 0) { dup2(saved_stdout, 1); close(saved_stdout); } + if (saved_stdin >= 0) { dup2(saved_stdin, 0); close(saved_stdin); } +} + +/* ---- Pipeline support ---- */ + +static void run_pipeline(char* cmdline) { + /* Split on '|' (outside quotes) */ + char* cmds[8]; + int ncmds = 0; + cmds[0] = cmdline; + int in_sq = 0, in_dq = 0; + for (char* p = cmdline; *p; p++) { + if (*p == '\'' && !in_dq) in_sq = !in_sq; + else if (*p == '"' && !in_sq) in_dq = !in_dq; + else if (*p == '|' && !in_sq && !in_dq && ncmds < 7) { + if (*(p + 1) == '|') { p++; continue; } /* skip || */ + *p = '\0'; + cmds[++ncmds] = p + 1; + } + } + ncmds++; + + if (ncmds == 1) { + run_simple(cmds[0]); + return; + } + + /* Multi-stage pipeline */ + int prev_rd = -1; + int pids[8]; + int pgid = 0; /* pipeline process group = first child's PID */ + + for (int i = 0; i < ncmds; i++) { + int pfd[2] = {-1, -1}; + if (i < ncmds - 1) { + if (pipe(pfd) < 0) { + fprintf(stderr, "sh: pipe failed\n"); + return; + } + } + + pids[i] = fork(); + if (pids[i] < 0) { fprintf(stderr, "sh: fork failed\n"); return; } + + if (pids[i] == 0) { + /* child: join pipeline process group, restore signals */ + int mypgid = pgid ? pgid : getpid(); + setpgid(0, mypgid); + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = (uintptr_t)SIG_DFL; + sigaction(SIGINT, &sa, NULL); + sigaction(SIGTSTP, &sa, NULL); + sigaction(SIGQUIT, &sa, NULL); + + if (prev_rd >= 0) { dup2(prev_rd, 0); close(prev_rd); } + if (pfd[1] >= 0) { dup2(pfd[1], 1); close(pfd[1]); } + if (pfd[0] >= 0) close(pfd[0]); + + /* Expand and parse this pipeline stage */ + char expanded[LINE_MAX]; + expand_vars(cmds[i], expanded, LINE_MAX); + char* argv[MAX_ARGS]; + int argc = parse_args(expanded, argv, MAX_ARGS); + if (argc == 0) _exit(0); + const char* path = resolve(argv[0]); + char** envp = build_envp(); + execve(path, (const char* const*)argv, (const char* const*)envp); + fprintf(stderr, "sh: %s: not found\n", argv[0]); + _exit(127); + } + + /* parent: set pipeline pgid */ + if (i == 0) pgid = pids[0]; + setpgid(pids[i], pgid); + + if (prev_rd >= 0) close(prev_rd); + if (pfd[1] >= 0) close(pfd[1]); + prev_rd = pfd[0]; + } + + if (prev_rd >= 0) close(prev_rd); + + if (!bg_flag) { + /* Foreground pipeline: make it fg, wait, restore */ + set_fg_pgrp(pgid); + for (int i = 0; i < ncmds; i++) { + int st; + waitpid(pids[i], &st, 0); + if (i == ncmds - 1) last_status = st; + } + set_fg_pgrp(getpgrp()); + } else { + printf("[bg] %d\n", pgid); + last_status = 0; + } +} + +/* ---- Process a command line (handle ;, &&, ||, &) ---- */ + +enum { OP_NONE = 0, OP_SEMI, OP_AND, OP_OR, OP_BG }; + +static void process_line(char* input) { + char* p = input; + + while (*p) { + while (*p == ' ' || *p == '\t') p++; + if (*p == '\0') break; + + /* Find the next operator outside quotes */ + char* start = p; + int in_sq = 0, in_dq = 0; + int op = OP_NONE; + + while (*p) { + if (*p == '\'' && !in_dq) { in_sq = !in_sq; p++; continue; } + if (*p == '"' && !in_sq) { in_dq = !in_dq; p++; continue; } + if (in_sq || in_dq) { p++; continue; } + + if (*p == '&' && *(p + 1) == '&') { + *p = '\0'; p += 2; op = OP_AND; break; + } + if (*p == '|' && *(p + 1) == '|') { + *p = '\0'; p += 2; op = OP_OR; break; + } + if (*p == ';') { + *p = '\0'; p++; op = OP_SEMI; break; + } + if (*p == '&') { + *p = '\0'; p++; op = OP_BG; break; + } + p++; + } + + /* Trim leading whitespace from segment */ + while (*start == ' ' || *start == '\t') start++; + if (*start != '\0') { + if (op == OP_BG) { + bg_flag = 1; + run_pipeline(start); + bg_flag = 0; + } else { + bg_flag = 0; + run_pipeline(start); + } + } + + /* For &&: skip remaining commands if last failed */ + if (op == OP_AND && last_status != 0) { + /* Skip until we hit || or ; or & or end */ + while (*p) { + while (*p == ' ' || *p == '\t') p++; + if (*p == '\0') break; + int skip_sq = 0, skip_dq = 0; + while (*p) { + if (*p == '\'' && !skip_dq) { skip_sq = !skip_sq; p++; continue; } + if (*p == '"' && !skip_sq) { skip_dq = !skip_dq; p++; continue; } + if (skip_sq || skip_dq) { p++; continue; } + if (*p == '|' && *(p + 1) == '|') break; + if (*p == ';') break; + if (*p == '&' && *(p + 1) != '&') break; + if (*p == '&' && *(p + 1) == '&') { p += 2; continue; } + p++; + } + break; + } + } + + /* For ||: skip remaining commands if last succeeded */ + if (op == OP_OR && last_status == 0) { + while (*p) { + while (*p == ' ' || *p == '\t') p++; + if (*p == '\0') break; + int skip_sq = 0, skip_dq = 0; + while (*p) { + if (*p == '\'' && !skip_dq) { skip_sq = !skip_sq; p++; continue; } + if (*p == '"' && !skip_sq) { skip_dq = !skip_dq; p++; continue; } + if (skip_sq || skip_dq) { p++; continue; } + if (*p == '&' && *(p + 1) == '&') break; + if (*p == ';') break; + if (*p == '&' && *(p + 1) != '&') break; + if (*p == '|' && *(p + 1) == '|') { p += 2; continue; } + p++; + } + break; + } + } + } +} + +/* ---- Prompt ---- */ + +static void print_prompt(void) { + const char* user = var_get("USER"); + const char* host = var_get("HOSTNAME"); + char cwd[256]; + + if (!user) user = "root"; + if (!host) host = "adros"; + + if (getcwd(cwd, sizeof(cwd)) < 0) strcpy(cwd, "?"); + + printf("%s@%s:%s$ ", user, host, cwd); + fflush(stdout); +} + +/* ---- Main ---- */ + +int main(int argc, char** argv, char** envp) { + (void)argc; + (void)argv; + + /* Import environment variables */ + if (envp) { + for (int i = 0; envp[i]; i++) { + char* eq = strchr(envp[i], '='); + if (eq) { + char name[64]; + int nlen = (int)(eq - envp[i]); + if (nlen > 63) nlen = 63; + memcpy(name, envp[i], (size_t)nlen); + name[nlen] = '\0'; + var_set(name, eq + 1, 1); + } + } + } + + /* Set defaults */ + if (!var_get("PATH")) + var_set("PATH", "/bin:/sbin:/usr/bin", 1); + if (!var_get("HOME")) + var_set("HOME", "/", 1); + + /* Job control: create session + process group, become fg */ + setsid(); + set_fg_pgrp(getpgrp()); + + /* Ignore job control signals in the shell itself */ + struct sigaction sa_ign; + memset(&sa_ign, 0, sizeof(sa_ign)); + sa_ign.sa_handler = (uintptr_t)SIG_IGN; + sigaction(SIGINT, &sa_ign, NULL); + sigaction(SIGTSTP, &sa_ign, NULL); + sigaction(SIGQUIT, &sa_ign, NULL); + + tty_raw_mode(); + + print_prompt(); + while (1) { + int len = read_line_edit(); + if (len < 0) break; + if (len > 0) { + hist_add(line); + tty_restore(); + process_line(line); + tty_raw_mode(); + } + print_prompt(); + } + + tty_restore(); + return last_status; +} diff --git a/user/cmds/sleep/Makefile b/user/cmds/sleep/Makefile new file mode 100644 index 0000000..258ba49 --- /dev/null +++ b/user/cmds/sleep/Makefile @@ -0,0 +1,3 @@ +NAME := sleep +SRCS := sleep.c +include ../common.mk diff --git a/user/cmds/sleep/sleep.c b/user/cmds/sleep/sleep.c new file mode 100644 index 0000000..9da1be1 --- /dev/null +++ b/user/cmds/sleep/sleep.c @@ -0,0 +1,18 @@ +/* AdrOS sleep utility — pause for N seconds */ +#include +#include +#include +#include + +int main(int argc, char** argv) { + if (argc <= 1) { + fprintf(stderr, "usage: sleep SECONDS\n"); + return 1; + } + int secs = atoi(argv[1]); + if (secs > 0) { + struct timespec ts = { .tv_sec = secs, .tv_nsec = 0 }; + nanosleep(&ts, NULL); + } + return 0; +} diff --git a/user/cmds/sort/Makefile b/user/cmds/sort/Makefile new file mode 100644 index 0000000..b9147af --- /dev/null +++ b/user/cmds/sort/Makefile @@ -0,0 +1,3 @@ +NAME := sort +SRCS := sort.c +include ../common.mk diff --git a/user/cmds/sort/sort.c b/user/cmds/sort/sort.c new file mode 100644 index 0000000..70cc61d --- /dev/null +++ b/user/cmds/sort/sort.c @@ -0,0 +1,91 @@ +/* AdrOS sort utility */ +#include +#include +#include +#include +#include + +#define MAX_LINES 1024 +#define LINE_BUF 65536 + +static char linebuf[LINE_BUF]; +static char* lines[MAX_LINES]; +static int nlines = 0; + +static int rflag = 0; /* -r: reverse */ +static int nflag = 0; /* -n: numeric */ + +static int cmp(const void* a, const void* b) { + const char* sa = *(const char**)a; + const char* sb = *(const char**)b; + int r; + if (nflag) { + r = atoi(sa) - atoi(sb); + } else { + r = strcmp(sa, sb); + } + return rflag ? -r : r; +} + +static void read_lines(int fd) { + int total = 0; + int r; + while ((r = read(fd, linebuf + total, (size_t)(LINE_BUF - total - 1))) > 0) { + total += r; + if (total >= LINE_BUF - 1) break; + } + linebuf[total] = '\0'; + + /* Split into lines */ + char* p = linebuf; + while (*p && nlines < MAX_LINES) { + lines[nlines++] = p; + while (*p && *p != '\n') p++; + if (*p == '\n') *p++ = '\0'; + } +} + +int main(int argc, char** argv) { + int start = 1; + for (int i = 1; i < argc; i++) { + if (argv[i][0] == '-') { + const char* f = argv[i] + 1; + while (*f) { + if (*f == 'r') rflag = 1; + else if (*f == 'n') nflag = 1; + f++; + } + start = i + 1; + } else break; + } + + if (start >= argc) { + read_lines(STDIN_FILENO); + } else { + for (int i = start; i < argc; i++) { + int fd = open(argv[i], O_RDONLY); + if (fd < 0) { + fprintf(stderr, "sort: cannot open '%s'\n", argv[i]); + return 1; + } + read_lines(fd); + close(fd); + } + } + + /* Simple insertion sort (no qsort in ulibc yet) */ + for (int i = 1; i < nlines; i++) { + char* key = lines[i]; + int j = i - 1; + while (j >= 0 && cmp(&lines[j], &key) > 0) { + lines[j + 1] = lines[j]; + j--; + } + lines[j + 1] = key; + } + + for (int i = 0; i < nlines; i++) + printf("%s\n", lines[i]); + + return 0; +} diff --git a/user/cmds/stat/Makefile b/user/cmds/stat/Makefile new file mode 100644 index 0000000..ca917a0 --- /dev/null +++ b/user/cmds/stat/Makefile @@ -0,0 +1,3 @@ +NAME := stat +SRCS := stat.c +include ../common.mk diff --git a/user/cmds/stat/stat.c b/user/cmds/stat/stat.c new file mode 100644 index 0000000..7083d29 --- /dev/null +++ b/user/cmds/stat/stat.c @@ -0,0 +1,25 @@ +/* AdrOS stat utility — display file status */ +#include +#include +#include +#include + +int main(int argc, char** argv) { + if (argc <= 1) { + fprintf(stderr, "usage: stat FILE...\n"); + return 1; + } + int rc = 0; + for (int i = 1; i < argc; i++) { + struct stat st; + if (stat(argv[i], (void*)&st) < 0) { + fprintf(stderr, "stat: cannot stat '%s'\n", argv[i]); + rc = 1; + continue; + } + printf(" File: %s\n", argv[i]); + printf(" Size: %u\tInode: %u\n", (unsigned)st.st_size, (unsigned)st.st_ino); + printf(" Mode: %o\tUid: %u\tGid: %u\n", (unsigned)st.st_mode, (unsigned)st.st_uid, (unsigned)st.st_gid); + } + return rc; +} diff --git a/user/cmds/tail/Makefile b/user/cmds/tail/Makefile new file mode 100644 index 0000000..aeed773 --- /dev/null +++ b/user/cmds/tail/Makefile @@ -0,0 +1,3 @@ +NAME := tail +SRCS := tail.c +include ../common.mk diff --git a/user/cmds/tail/tail.c b/user/cmds/tail/tail.c new file mode 100644 index 0000000..200e78e --- /dev/null +++ b/user/cmds/tail/tail.c @@ -0,0 +1,60 @@ +/* AdrOS tail utility */ +#include +#include +#include +#include +#include + +#define TAIL_BUFSZ 8192 + +static void tail_fd(int fd, int nlines) { + /* Read entire file into buffer, then print last N lines */ + char buf[TAIL_BUFSZ]; + int total = 0; + int r; + while ((r = read(fd, buf + total, (size_t)(TAIL_BUFSZ - total))) > 0) { + total += r; + if (total >= TAIL_BUFSZ) break; + } + + /* Count newlines from end; skip trailing newline */ + int count = 0; + int pos = total; + if (pos > 0 && buf[pos - 1] == '\n') pos--; + while (pos > 0 && count < nlines) { + pos--; + if (buf[pos] == '\n') count++; + } + if (pos > 0 || (pos == 0 && buf[0] == '\n')) pos++; + + write(STDOUT_FILENO, buf + pos, (size_t)(total - pos)); +} + +int main(int argc, char** argv) { + int nlines = 10; + int start = 1; + + if (argc > 1 && argv[1][0] == '-' && argv[1][1] == 'n' && argc > 2) { + nlines = atoi(argv[2]); + start = 3; + } else if (argc > 1 && argv[1][0] == '-' && argv[1][1] >= '0' && argv[1][1] <= '9') { + nlines = atoi(argv[1] + 1); + start = 2; + } + + if (start >= argc) { + tail_fd(STDIN_FILENO, nlines); + } else { + for (int i = start; i < argc; i++) { + if (argc - start > 1) printf("==> %s <==\n", argv[i]); + int fd = open(argv[i], O_RDONLY); + if (fd < 0) { + fprintf(stderr, "tail: cannot open '%s'\n", argv[i]); + continue; + } + tail_fd(fd, nlines); + close(fd); + } + } + return 0; +} diff --git a/user/cmds/tee/Makefile b/user/cmds/tee/Makefile new file mode 100644 index 0000000..f387f18 --- /dev/null +++ b/user/cmds/tee/Makefile @@ -0,0 +1,3 @@ +NAME := tee +SRCS := tee.c +include ../common.mk diff --git a/user/cmds/tee/tee.c b/user/cmds/tee/tee.c new file mode 100644 index 0000000..52a32c4 --- /dev/null +++ b/user/cmds/tee/tee.c @@ -0,0 +1,34 @@ +/* AdrOS tee utility — read stdin, write to stdout and files */ +#include +#include +#include +#include + +int main(int argc, char** argv) { + int aflag = 0; + int fds[16]; + int nfds = 0; + + for (int i = 1; i < argc && nfds < 16; i++) { + if (strcmp(argv[i], "-a") == 0) { aflag = 1; continue; } + int flags = O_WRONLY | O_CREAT; + flags |= aflag ? O_APPEND : O_TRUNC; + int fd = open(argv[i], flags, 0644); + if (fd < 0) { + fprintf(stderr, "tee: %s: cannot open\n", argv[i]); + continue; + } + fds[nfds++] = fd; + } + + char buf[4096]; + int n; + while ((n = read(STDIN_FILENO, buf, sizeof(buf))) > 0) { + write(STDOUT_FILENO, buf, (size_t)n); + for (int i = 0; i < nfds; i++) + write(fds[i], buf, (size_t)n); + } + + for (int i = 0; i < nfds; i++) close(fds[i]); + return 0; +} diff --git a/user/cmds/top/Makefile b/user/cmds/top/Makefile new file mode 100644 index 0000000..841c26f --- /dev/null +++ b/user/cmds/top/Makefile @@ -0,0 +1,3 @@ +NAME := top +SRCS := top.c +include ../common.mk diff --git a/user/cmds/top/top.c b/user/cmds/top/top.c new file mode 100644 index 0000000..59995e8 --- /dev/null +++ b/user/cmds/top/top.c @@ -0,0 +1,69 @@ +/* AdrOS top utility — one-shot process listing with basic info */ +#include +#include +#include +#include +#include + +static int is_digit(char c) { return c >= '0' && c <= '9'; } + +int main(void) { + printf(" PID STATE CMD\n"); + int fd = open("/proc", O_RDONLY); + if (fd < 0) { + fprintf(stderr, "top: cannot open /proc\n"); + return 1; + } + char buf[512]; + int rc; + while ((rc = getdents(fd, buf, sizeof(buf))) > 0) { + int off = 0; + while (off < rc) { + struct dirent* d = (struct dirent*)(buf + off); + if (d->d_reclen == 0) break; + if (is_digit(d->d_name[0])) { + char path[64]; + + /* Read cmdline */ + snprintf(path, sizeof(path), "/proc/%s/cmdline", d->d_name); + int cfd = open(path, O_RDONLY); + char cmd[64] = "[kernel]"; + if (cfd >= 0) { + int n = read(cfd, cmd, sizeof(cmd) - 1); + if (n > 0) { + cmd[n] = '\0'; + while (n > 0 && (cmd[n-1] == '\n' || cmd[n-1] == '\0')) cmd[--n] = '\0'; + } + if (n <= 0) strcpy(cmd, "[kernel]"); + close(cfd); + } + + /* Read status for state */ + snprintf(path, sizeof(path), "/proc/%s/status", d->d_name); + int sfd = open(path, O_RDONLY); + char state[16] = "?"; + if (sfd >= 0) { + char sbuf[256]; + int sn = read(sfd, sbuf, sizeof(sbuf) - 1); + if (sn > 0) { + sbuf[sn] = '\0'; + char* st = strstr(sbuf, "State:"); + if (st) { + st += 6; + while (*st == ' ' || *st == '\t') st++; + int si = 0; + while (*st && *st != '\n' && si < 15) state[si++] = *st++; + state[si] = '\0'; + } + } + close(sfd); + } + + printf("%5s %6s %s\n", d->d_name, state, cmd); + } + off += d->d_reclen; + } + } + close(fd); + return 0; +} diff --git a/user/cmds/touch/Makefile b/user/cmds/touch/Makefile new file mode 100644 index 0000000..595e195 --- /dev/null +++ b/user/cmds/touch/Makefile @@ -0,0 +1,3 @@ +NAME := touch +SRCS := touch.c +include ../common.mk diff --git a/user/cmds/touch/touch.c b/user/cmds/touch/touch.c new file mode 100644 index 0000000..3e2da84 --- /dev/null +++ b/user/cmds/touch/touch.c @@ -0,0 +1,23 @@ +/* AdrOS touch utility */ +#include +#include +#include + +int main(int argc, char** argv) { + if (argc < 2) { + fprintf(stderr, "Usage: touch ...\n"); + return 1; + } + + int rc = 0; + for (int i = 1; i < argc; i++) { + int fd = open(argv[i], O_WRONLY | O_CREAT, 0644); + if (fd < 0) { + fprintf(stderr, "touch: cannot touch '%s'\n", argv[i]); + rc = 1; + } else { + close(fd); + } + } + return rc; +} diff --git a/user/cmds/tr/Makefile b/user/cmds/tr/Makefile new file mode 100644 index 0000000..2a49dd3 --- /dev/null +++ b/user/cmds/tr/Makefile @@ -0,0 +1,3 @@ +NAME := tr +SRCS := tr.c +include ../common.mk diff --git a/user/cmds/tr/tr.c b/user/cmds/tr/tr.c new file mode 100644 index 0000000..cf7606f --- /dev/null +++ b/user/cmds/tr/tr.c @@ -0,0 +1,44 @@ +/* AdrOS tr utility — translate or delete characters */ +#include +#include +#include + +int main(int argc, char** argv) { + int delete_mode = 0; + int start = 1; + + if (argc > 1 && strcmp(argv[1], "-d") == 0) { + delete_mode = 1; + start = 2; + } + + if (delete_mode) { + if (start >= argc) { fprintf(stderr, "usage: tr -d SET1\n"); return 1; } + const char* set1 = argv[start]; + char c; + while (read(STDIN_FILENO, &c, 1) > 0) { + if (!strchr(set1, c)) + write(STDOUT_FILENO, &c, 1); + } + } else { + if (start + 1 >= argc) { fprintf(stderr, "usage: tr SET1 SET2\n"); return 1; } + const char* set1 = argv[start]; + const char* set2 = argv[start + 1]; + int len1 = (int)strlen(set1); + int len2 = (int)strlen(set2); + char c; + while (read(STDIN_FILENO, &c, 1) > 0) { + int found = 0; + for (int i = 0; i < len1; i++) { + if (c == set1[i]) { + char r = (i < len2) ? set2[i] : set2[len2 - 1]; + write(STDOUT_FILENO, &r, 1); + found = 1; + break; + } + } + if (!found) write(STDOUT_FILENO, &c, 1); + } + } + return 0; +} diff --git a/user/cmds/umount/Makefile b/user/cmds/umount/Makefile new file mode 100644 index 0000000..cee0a75 --- /dev/null +++ b/user/cmds/umount/Makefile @@ -0,0 +1,3 @@ +NAME := umount +SRCS := umount.c +include ../common.mk diff --git a/user/cmds/umount/umount.c b/user/cmds/umount/umount.c new file mode 100644 index 0000000..1396c94 --- /dev/null +++ b/user/cmds/umount/umount.c @@ -0,0 +1,11 @@ +/* AdrOS umount utility — stub (no SYS_UMOUNT syscall yet) */ +#include + +int main(int argc, char** argv) { + if (argc <= 1) { + fprintf(stderr, "umount: missing operand\n"); + return 1; + } + fprintf(stderr, "umount: %s: operation not supported\n", argv[1]); + return 1; +} diff --git a/user/cmds/uname/Makefile b/user/cmds/uname/Makefile new file mode 100644 index 0000000..ca5558e --- /dev/null +++ b/user/cmds/uname/Makefile @@ -0,0 +1,3 @@ +NAME := uname +SRCS := uname.c +include ../common.mk diff --git a/user/cmds/uname/uname.c b/user/cmds/uname/uname.c new file mode 100644 index 0000000..250fe05 --- /dev/null +++ b/user/cmds/uname/uname.c @@ -0,0 +1,34 @@ +/* AdrOS uname utility — print system information */ +#include +#include + +int main(int argc, char** argv) { + const char* sysname = "AdrOS"; + const char* nodename = "adros"; + const char* release = "0.1.0"; + const char* version = "AdrOS x86 SMP"; + const char* machine = "i686"; + + if (argc <= 1) { printf("%s\n", sysname); return 0; } + + int all = 0, s = 0, n = 0, r = 0, v = 0, m = 0; + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "-a") == 0) all = 1; + else if (strcmp(argv[i], "-s") == 0) s = 1; + else if (strcmp(argv[i], "-n") == 0) n = 1; + else if (strcmp(argv[i], "-r") == 0) r = 1; + else if (strcmp(argv[i], "-v") == 0) v = 1; + else if (strcmp(argv[i], "-m") == 0) m = 1; + } + if (all) { s = n = r = v = m = 1; } + if (!s && !n && !r && !v && !m) s = 1; + + int first = 1; + if (s) { printf("%s%s", first ? "" : " ", sysname); first = 0; } + if (n) { printf("%s%s", first ? "" : " ", nodename); first = 0; } + if (r) { printf("%s%s", first ? "" : " ", release); first = 0; } + if (v) { printf("%s%s", first ? "" : " ", version); first = 0; } + if (m) { printf("%s%s", first ? "" : " ", machine); first = 0; } + printf("\n"); + return 0; +} diff --git a/user/cmds/uniq/Makefile b/user/cmds/uniq/Makefile new file mode 100644 index 0000000..c87f1b1 --- /dev/null +++ b/user/cmds/uniq/Makefile @@ -0,0 +1,3 @@ +NAME := uniq +SRCS := uniq.c +include ../common.mk diff --git a/user/cmds/uniq/uniq.c b/user/cmds/uniq/uniq.c new file mode 100644 index 0000000..60fc2f1 --- /dev/null +++ b/user/cmds/uniq/uniq.c @@ -0,0 +1,85 @@ +/* AdrOS uniq utility */ +#include +#include +#include +#include + +#define LINE_MAX 1024 + +static int cflag = 0; /* -c: prefix lines with count */ +static int dflag = 0; /* -d: only print duplicates */ + +static int readline(int fd, char* buf, int max) { + int n = 0; + char c; + while (n < max - 1) { + int r = read(fd, &c, 1); + if (r <= 0) break; + if (c == '\n') break; + buf[n++] = c; + } + buf[n] = '\0'; + return n > 0 ? n : (n == 0 ? 0 : -1); +} + +int main(int argc, char** argv) { + int start = 1; + int fd = STDIN_FILENO; + + for (int i = 1; i < argc; i++) { + if (argv[i][0] == '-' && argv[i][1]) { + const char* f = argv[i] + 1; + while (*f) { + if (*f == 'c') cflag = 1; + else if (*f == 'd') dflag = 1; + f++; + } + start = i + 1; + } else break; + } + + if (start < argc) { + fd = open(argv[start], O_RDONLY); + if (fd < 0) { + fprintf(stderr, "uniq: cannot open '%s'\n", argv[start]); + return 1; + } + } + + char prev[LINE_MAX] = {0}; + char cur[LINE_MAX]; + int count = 0; + int first = 1; + + while (1) { + int r = readline(fd, cur, LINE_MAX); + if (r < 0) break; + + if (first || strcmp(cur, prev) != 0) { + if (!first) { + if (!dflag || count > 1) { + if (cflag) printf("%7d %s\n", count, prev); + else printf("%s\n", prev); + } + } + strcpy(prev, cur); + count = 1; + first = 0; + } else { + count++; + } + + if (r == 0) break; + } + + /* Print last line */ + if (!first) { + if (!dflag || count > 1) { + if (cflag) printf("%7d %s\n", count, prev); + else printf("%s\n", prev); + } + } + + if (fd != STDIN_FILENO) close(fd); + return 0; +} diff --git a/user/cmds/uptime/Makefile b/user/cmds/uptime/Makefile new file mode 100644 index 0000000..391584c --- /dev/null +++ b/user/cmds/uptime/Makefile @@ -0,0 +1,3 @@ +NAME := uptime +SRCS := uptime.c +include ../common.mk diff --git a/user/cmds/uptime/uptime.c b/user/cmds/uptime/uptime.c new file mode 100644 index 0000000..2f81539 --- /dev/null +++ b/user/cmds/uptime/uptime.c @@ -0,0 +1,40 @@ +/* AdrOS uptime utility */ +#include +#include +#include +#include + +int main(int argc, char** argv) { + (void)argc; (void)argv; + + /* Try /proc/uptime first */ + int fd = open("/proc/uptime", O_RDONLY); + if (fd >= 0) { + char buf[64]; + int r = read(fd, buf, sizeof(buf) - 1); + close(fd); + if (r > 0) { + buf[r] = '\0'; + printf("up %s", buf); + return 0; + } + } + + /* Fallback: use CLOCK_MONOTONIC */ + struct timespec ts; + if (clock_gettime(CLOCK_MONOTONIC, &ts) < 0) { + fprintf(stderr, "uptime: cannot get time\n"); + return 1; + } + + unsigned long sec = ts.tv_sec; + unsigned long days = sec / 86400; + unsigned long hours = (sec % 86400) / 3600; + unsigned long mins = (sec % 3600) / 60; + unsigned long secs = sec % 60; + + printf("up"); + if (days > 0) printf(" %lu day%s,", days, days > 1 ? "s" : ""); + printf(" %02lu:%02lu:%02lu\n", hours, mins, secs); + return 0; +} diff --git a/user/cmds/wc/Makefile b/user/cmds/wc/Makefile new file mode 100644 index 0000000..8dd2064 --- /dev/null +++ b/user/cmds/wc/Makefile @@ -0,0 +1,3 @@ +NAME := wc +SRCS := wc.c +include ../common.mk diff --git a/user/cmds/wc/wc.c b/user/cmds/wc/wc.c new file mode 100644 index 0000000..76333eb --- /dev/null +++ b/user/cmds/wc/wc.c @@ -0,0 +1,69 @@ +/* AdrOS wc utility */ +#include +#include +#include +#include + +static void wc_fd(int fd, const char* name, int show_l, int show_w, int show_c) { + char buf[4096]; + int lines = 0, words = 0, chars = 0; + int in_word = 0; + int r; + + while ((r = read(fd, buf, sizeof(buf))) > 0) { + for (int i = 0; i < r; i++) { + chars++; + if (buf[i] == '\n') lines++; + if (buf[i] == ' ' || buf[i] == '\t' || buf[i] == '\n') { + in_word = 0; + } else if (!in_word) { + in_word = 1; + words++; + } + } + } + + if (show_l) printf("%7d", lines); + if (show_w) printf("%7d", words); + if (show_c) printf("%7d", chars); + if (name) printf(" %s", name); + printf("\n"); +} + +int main(int argc, char** argv) { + int show_l = 0, show_w = 0, show_c = 0; + int start = 1; + + for (int i = 1; i < argc; i++) { + if (argv[i][0] == '-' && argv[i][1]) { + const char* f = argv[i] + 1; + while (*f) { + if (*f == 'l') show_l = 1; + else if (*f == 'w') show_w = 1; + else if (*f == 'c') show_c = 1; + f++; + } + start = i + 1; + } else break; + } + + /* Default: show all */ + if (!show_l && !show_w && !show_c) { + show_l = show_w = show_c = 1; + } + + if (start >= argc) { + wc_fd(STDIN_FILENO, NULL, show_l, show_w, show_c); + } else { + for (int i = start; i < argc; i++) { + int fd = open(argv[i], O_RDONLY); + if (fd < 0) { + fprintf(stderr, "wc: %s: No such file\n", argv[i]); + continue; + } + wc_fd(fd, argv[i], show_l, show_w, show_c); + close(fd); + } + } + return 0; +} diff --git a/user/cmds/which/Makefile b/user/cmds/which/Makefile new file mode 100644 index 0000000..aa15f8b --- /dev/null +++ b/user/cmds/which/Makefile @@ -0,0 +1,3 @@ +NAME := which +SRCS := which.c +include ../common.mk diff --git a/user/cmds/which/which.c b/user/cmds/which/which.c new file mode 100644 index 0000000..ddd60c4 --- /dev/null +++ b/user/cmds/which/which.c @@ -0,0 +1,41 @@ +/* AdrOS which utility — locate a command */ +#include +#include +#include + +static int exists_in_dir(const char* dirname, const char* name) { + DIR* dir = opendir(dirname); + if (!dir) return 0; + struct dirent* d; + while ((d = readdir(dir)) != NULL) { + if (strcmp(d->d_name, name) == 0) { + closedir(dir); + return 1; + } + } + closedir(dir); + return 0; +} + +int main(int argc, char** argv) { + if (argc < 2) { + fprintf(stderr, "Usage: which command\n"); + return 1; + } + + static const char* path_dirs[] = { "/bin", "/sbin", NULL }; + int ret = 1; + + for (int i = 1; i < argc; i++) { + int found = 0; + for (int d = 0; path_dirs[d]; d++) { + if (exists_in_dir(path_dirs[d], argv[i])) { + printf("%s/%s\n", path_dirs[d], argv[i]); + found = 1; + break; + } + } + if (found) ret = 0; + } + return ret; +} diff --git a/user/cmds/who/Makefile b/user/cmds/who/Makefile new file mode 100644 index 0000000..d98042e --- /dev/null +++ b/user/cmds/who/Makefile @@ -0,0 +1,3 @@ +NAME := who +SRCS := who.c +include ../common.mk diff --git a/user/cmds/who/who.c b/user/cmds/who/who.c new file mode 100644 index 0000000..b8fc948 --- /dev/null +++ b/user/cmds/who/who.c @@ -0,0 +1,8 @@ +/* AdrOS who utility — show logged-in users */ +#include +#include + +int main(void) { + printf("root tty1 Jan 1 00:00\n"); + return 0; +} diff --git a/user/cp.c b/user/cp.c deleted file mode 100644 index 16fcb52..0000000 --- a/user/cp.c +++ /dev/null @@ -1,41 +0,0 @@ -/* AdrOS cp utility */ -#include -#include -#include -#include - -int main(int argc, char** argv) { - if (argc < 3) { - fprintf(stderr, "Usage: cp \n"); - return 1; - } - - int src = open(argv[1], O_RDONLY); - if (src < 0) { - fprintf(stderr, "cp: cannot open '%s'\n", argv[1]); - return 1; - } - - int dst = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0644); - if (dst < 0) { - fprintf(stderr, "cp: cannot create '%s'\n", argv[2]); - close(src); - return 1; - } - - char buf[4096]; - int r; - while ((r = read(src, buf, sizeof(buf))) > 0) { - int w = write(dst, buf, (size_t)r); - if (w != r) { - fprintf(stderr, "cp: write error\n"); - close(src); - close(dst); - return 1; - } - } - - close(src); - close(dst); - return 0; -} diff --git a/user/cut.c b/user/cut.c deleted file mode 100644 index 47d4d5d..0000000 --- a/user/cut.c +++ /dev/null @@ -1,110 +0,0 @@ -/* AdrOS cut utility */ -#include -#include -#include -#include -#include - -#define LINE_MAX 1024 - -static char delim = '\t'; -static int fields[32]; -static int nfields = 0; - -static void parse_fields(const char* spec) { - const char* p = spec; - while (*p && nfields < 32) { - fields[nfields++] = atoi(p); - while (*p && *p != ',') p++; - if (*p == ',') p++; - } -} - -static void cut_line(char* line) { - if (nfields == 0) { - printf("%s\n", line); - return; - } - - /* Split line into fields */ - char* flds[64]; - int nf = 0; - char* p = line; - flds[nf++] = p; - while (*p && nf < 64) { - if (*p == delim) { - *p = '\0'; - flds[nf++] = p + 1; - } - p++; - } - - /* Print requested fields */ - int first = 1; - for (int i = 0; i < nfields; i++) { - int idx = fields[i] - 1; /* 1-based */ - if (idx >= 0 && idx < nf) { - if (!first) write(STDOUT_FILENO, &delim, 1); - printf("%s", flds[idx]); - first = 0; - } - } - printf("\n"); -} - -static void cut_fd(int fd) { - char line[LINE_MAX]; - int pos = 0; - char c; - - while (read(fd, &c, 1) > 0) { - if (c == '\n') { - line[pos] = '\0'; - cut_line(line); - pos = 0; - } else if (pos < LINE_MAX - 1) { - line[pos++] = c; - } - } - if (pos > 0) { - line[pos] = '\0'; - cut_line(line); - } -} - -int main(int argc, char** argv) { - int start = 1; - - for (int i = 1; i < argc; i++) { - if (strcmp(argv[i], "-d") == 0 && i + 1 < argc) { - delim = argv[++i][0]; - start = i + 1; - } else if (strncmp(argv[i], "-d", 2) == 0 && argv[i][2] != '\0') { - delim = argv[i][2]; - start = i + 1; - } else if (strcmp(argv[i], "-f") == 0 && i + 1 < argc) { - parse_fields(argv[++i]); - start = i + 1; - } else if (strncmp(argv[i], "-f", 2) == 0 && argv[i][2] != '\0') { - parse_fields(argv[i] + 2); - start = i + 1; - } else if (argv[i][0] != '-') { - break; - } - } - - if (start >= argc) { - cut_fd(STDIN_FILENO); - } else { - for (int i = start; i < argc; i++) { - int fd = open(argv[i], O_RDONLY); - if (fd < 0) { - fprintf(stderr, "cut: cannot open '%s'\n", argv[i]); - continue; - } - cut_fd(fd); - close(fd); - } - } - return 0; -} diff --git a/user/date.c b/user/date.c deleted file mode 100644 index 859986c..0000000 --- a/user/date.c +++ /dev/null @@ -1,24 +0,0 @@ -/* AdrOS date utility */ -#include -#include - -int main(int argc, char** argv) { - (void)argc; (void)argv; - struct timespec ts; - if (clock_gettime(CLOCK_REALTIME, &ts) < 0) { - fprintf(stderr, "date: cannot get time\n"); - return 1; - } - - /* Simple epoch seconds display — no timezone or strftime yet */ - unsigned long sec = ts.tv_sec; - unsigned long days = sec / 86400; - unsigned long rem = sec % 86400; - unsigned long hours = rem / 3600; - unsigned long mins = (rem % 3600) / 60; - unsigned long secs = rem % 60; - - printf("%lu days since epoch, %02lu:%02lu:%02lu UTC\n", - days, hours, mins, secs); - return 0; -} diff --git a/user/dd.c b/user/dd.c deleted file mode 100644 index 6e0eccc..0000000 --- a/user/dd.c +++ /dev/null @@ -1,63 +0,0 @@ -/* AdrOS dd utility — convert and copy a file */ -#include -#include -#include -#include -#include - -static int parse_size(const char* s) { - int v = atoi(s); - int len = (int)strlen(s); - if (len > 0) { - char suf = s[len - 1]; - if (suf == 'k' || suf == 'K') v *= 1024; - else if (suf == 'm' || suf == 'M') v *= 1024 * 1024; - } - return v; -} - -int main(int argc, char** argv) { - const char* inf = NULL; - const char* outf = NULL; - int bs = 512; - int count = -1; - - for (int i = 1; i < argc; i++) { - if (strncmp(argv[i], "if=", 3) == 0) inf = argv[i] + 3; - else if (strncmp(argv[i], "of=", 3) == 0) outf = argv[i] + 3; - else if (strncmp(argv[i], "bs=", 3) == 0) bs = parse_size(argv[i] + 3); - else if (strncmp(argv[i], "count=", 6) == 0) count = atoi(argv[i] + 6); - } - - int ifd = STDIN_FILENO; - int ofd = STDOUT_FILENO; - - if (inf) { - ifd = open(inf, O_RDONLY); - if (ifd < 0) { fprintf(stderr, "dd: cannot open '%s'\n", inf); return 1; } - } - if (outf) { - ofd = open(outf, O_WRONLY | O_CREAT | O_TRUNC, 0644); - if (ofd < 0) { fprintf(stderr, "dd: cannot open '%s'\n", outf); return 1; } - } - - if (bs > 4096) bs = 4096; - char buf[4096]; - int blocks = 0, partial = 0, total = 0; - - while (count < 0 || blocks + partial < count) { - int n = read(ifd, buf, (size_t)bs); - if (n <= 0) break; - write(ofd, buf, (size_t)n); - total += n; - if (n == bs) blocks++; - else partial++; - } - - fprintf(stderr, "%d+%d records in\n%d+%d records out\n%d bytes copied\n", - blocks, partial, blocks, partial, total); - - if (inf) close(ifd); - if (outf) close(ofd); - return 0; -} diff --git a/user/df.c b/user/df.c deleted file mode 100644 index 81d0a87..0000000 --- a/user/df.c +++ /dev/null @@ -1,10 +0,0 @@ -/* AdrOS df utility — display filesystem disk space usage */ -#include - -int main(void) { - printf("Filesystem Size Used Avail Use%% Mounted on\n"); - printf("overlayfs - - - - /\n"); - printf("devfs - - - - /dev\n"); - printf("procfs - - - - /proc\n"); - return 0; -} diff --git a/user/dirname.c b/user/dirname.c deleted file mode 100644 index 15e518d..0000000 --- a/user/dirname.c +++ /dev/null @@ -1,22 +0,0 @@ -/* AdrOS dirname utility — strip last component from path */ -#include -#include - -int main(int argc, char** argv) { - if (argc <= 1) { - fprintf(stderr, "usage: dirname PATH\n"); - return 1; - } - char* p = argv[1]; - int len = (int)strlen(p); - /* Remove trailing slashes */ - while (len > 1 && p[len - 1] == '/') len--; - /* Find last slash */ - while (len > 0 && p[len - 1] != '/') len--; - /* Remove trailing slashes from result */ - while (len > 1 && p[len - 1] == '/') len--; - if (len == 0) { printf(".\n"); return 0; } - p[len] = '\0'; - printf("%s\n", p); - return 0; -} diff --git a/user/dmesg.c b/user/dmesg.c deleted file mode 100644 index b2aacc4..0000000 --- a/user/dmesg.c +++ /dev/null @@ -1,18 +0,0 @@ -/* AdrOS dmesg utility — print kernel ring buffer from /proc/dmesg */ -#include -#include -#include - -int main(void) { - int fd = open("/proc/dmesg", O_RDONLY); - if (fd < 0) { - fprintf(stderr, "dmesg: cannot open /proc/dmesg\n"); - return 1; - } - char buf[512]; - int n; - while ((n = read(fd, buf, sizeof(buf))) > 0) - write(STDOUT_FILENO, buf, (size_t)n); - close(fd); - return 0; -} diff --git a/user/du.c b/user/du.c deleted file mode 100644 index 8fd15c5..0000000 --- a/user/du.c +++ /dev/null @@ -1,73 +0,0 @@ -/* AdrOS du utility — estimate file space usage */ -#include -#include -#include -#include -#include -#include - -static int sflag = 0; /* -s: summary only */ - -static long du_path(const char* path, int print) { - struct stat st; - if (stat(path, &st) < 0) { - fprintf(stderr, "du: cannot access '%s'\n", path); - return 0; - } - - if (!(st.st_mode & 0040000)) { - long blocks = (st.st_size + 511) / 512; - if (print && !sflag) printf("%ld\t%s\n", blocks, path); - return blocks; - } - - int fd = open(path, O_RDONLY); - if (fd < 0) return 0; - - long total = 0; - char buf[2048]; - int rc; - while ((rc = getdents(fd, buf, sizeof(buf))) > 0) { - int off = 0; - while (off < rc) { - struct dirent* d = (struct dirent*)(buf + off); - if (d->d_reclen == 0) break; - if (strcmp(d->d_name, ".") != 0 && strcmp(d->d_name, "..") != 0) { - char child[512]; - if (path[strlen(path)-1] == '/') - snprintf(child, sizeof(child), "%s%s", path, d->d_name); - else - snprintf(child, sizeof(child), "%s/%s", path, d->d_name); - total += du_path(child, print); - } - off += d->d_reclen; - } - } - close(fd); - - if (print && !sflag) printf("%ld\t%s\n", total, path); - return total; -} - -int main(int argc, char** argv) { - int argi = 1; - while (argi < argc && argv[argi][0] == '-') { - const char* f = argv[argi] + 1; - while (*f) { - if (*f == 's') sflag = 1; - f++; - } - argi++; - } - - if (argi >= argc) { - long total = du_path(".", 1); - if (sflag) printf("%ld\t.\n", total); - } else { - for (int i = argi; i < argc; i++) { - long total = du_path(argv[i], 1); - if (sflag) printf("%ld\t%s\n", total, argv[i]); - } - } - return 0; -} diff --git a/user/echo.c b/user/echo.c deleted file mode 100644 index 0e4496a..0000000 --- a/user/echo.c +++ /dev/null @@ -1,64 +0,0 @@ -/* AdrOS echo utility — POSIX-compatible */ -#include -#include -#include - -int main(int argc, char** argv) { - int nflag = 0; /* -n: no trailing newline */ - int eflag = 0; /* -e: interpret escape sequences */ - int i = 1; - - /* Parse flags */ - while (i < argc && argv[i][0] == '-' && argv[i][1] != '\0') { - const char* f = argv[i] + 1; - int valid = 1; - int n = 0, e = 0; - while (*f) { - if (*f == 'n') n = 1; - else if (*f == 'e') e = 1; - else if (*f == 'E') { /* no escapes — default */ } - else { valid = 0; break; } - f++; - } - if (!valid) break; - if (n) nflag = 1; - if (e) eflag = 1; - i++; - } - - int first = 1; - for (; i < argc; i++) { - if (!first) - write(STDOUT_FILENO, " ", 1); - first = 0; - - const char* s = argv[i]; - if (eflag) { - while (*s) { - if (*s == '\\' && s[1]) { - s++; - char c = *s; - switch (c) { - case 'n': write(STDOUT_FILENO, "\n", 1); break; - case 't': write(STDOUT_FILENO, "\t", 1); break; - case '\\': write(STDOUT_FILENO, "\\", 1); break; - case 'r': write(STDOUT_FILENO, "\r", 1); break; - case 'a': write(STDOUT_FILENO, "\a", 1); break; - default: write(STDOUT_FILENO, "\\", 1); - write(STDOUT_FILENO, &c, 1); break; - } - } else { - write(STDOUT_FILENO, s, 1); - } - s++; - } - } else { - write(STDOUT_FILENO, s, strlen(s)); - } - } - - if (!nflag) - write(STDOUT_FILENO, "\n", 1); - - return 0; -} diff --git a/user/env.c b/user/env.c deleted file mode 100644 index a7d727a..0000000 --- a/user/env.c +++ /dev/null @@ -1,21 +0,0 @@ -/* AdrOS env utility — print environment or run command with modified env */ -#include -#include -#include - -extern char** __environ; - -int main(int argc, char** argv) { - if (argc <= 1) { - /* Print all environment variables */ - if (__environ) { - for (int i = 0; __environ[i]; i++) - printf("%s\n", __environ[i]); - } - return 0; - } - /* env COMMAND ARGS... — run command with current environment */ - execve(argv[1], (const char* const*)&argv[1], (const char* const*)__environ); - fprintf(stderr, "env: %s: not found\n", argv[1]); - return 127; -} diff --git a/user/errno.c b/user/errno.c deleted file mode 100644 index fbc6e36..0000000 --- a/user/errno.c +++ /dev/null @@ -1,4 +0,0 @@ -/* Per-process errno: each fork()ed process gets its own copy in its - address space. When true threads (clone) are added, this must become - __thread int errno or use a TLS segment (GS/FS). */ -int errno = 0; diff --git a/user/find.c b/user/find.c deleted file mode 100644 index 78ca43e..0000000 --- a/user/find.c +++ /dev/null @@ -1,98 +0,0 @@ -/* AdrOS find utility — search for files in directory hierarchy */ -#include -#include -#include -#include -#include - -static const char* name_pattern = NULL; -static int type_filter = 0; /* 0=any, 'f'=file, 'd'=dir */ - -static int match_name(const char* name) { - if (!name_pattern) return 1; - /* Simple wildcard: *pattern* if pattern has no special chars, - or exact match. Support leading/trailing * only. */ - int plen = (int)strlen(name_pattern); - if (plen == 0) return 1; - - const char* pat = name_pattern; - int lead_star = (pat[0] == '*'); - int trail_star = (plen > 1 && pat[plen-1] == '*'); - - if (lead_star && trail_star) { - char sub[256]; - int slen = plen - 2; - if (slen <= 0) return 1; - memcpy(sub, pat + 1, slen); - sub[slen] = '\0'; - return strstr(name, sub) != NULL; - } - if (lead_star) { - const char* suffix = pat + 1; - int slen = plen - 1; - int nlen = (int)strlen(name); - if (nlen < slen) return 0; - return strcmp(name + nlen - slen, suffix) == 0; - } - if (trail_star) { - return strncmp(name, pat, plen - 1) == 0; - } - return strcmp(name, pat) == 0; -} - -static void find_recurse(const char* path) { - int fd = open(path, O_RDONLY); - if (fd < 0) return; - - char buf[2048]; - int rc; - while ((rc = getdents(fd, buf, sizeof(buf))) > 0) { - int off = 0; - while (off < rc) { - struct dirent* d = (struct dirent*)(buf + off); - if (d->d_reclen == 0) break; - if (strcmp(d->d_name, ".") != 0 && strcmp(d->d_name, "..") != 0) { - char child[512]; - if (path[strlen(path)-1] == '/') - snprintf(child, sizeof(child), "%s%s", path, d->d_name); - else - snprintf(child, sizeof(child), "%s/%s", path, d->d_name); - - int show = match_name(d->d_name); - if (show && type_filter) { - if (type_filter == 'f' && d->d_type == 4) show = 0; /* DT_DIR=4 */ - if (type_filter == 'd' && d->d_type != 4) show = 0; - } - if (show) printf("%s\n", child); - - if (d->d_type == 4) { /* DT_DIR */ - find_recurse(child); - } - } - off += d->d_reclen; - } - } - close(fd); -} - -int main(int argc, char** argv) { - const char* start = "."; - int argi = 1; - - if (argi < argc && argv[argi][0] != '-') { - start = argv[argi++]; - } - - while (argi < argc) { - if (strcmp(argv[argi], "-name") == 0 && argi + 1 < argc) { - name_pattern = argv[++argi]; - } else if (strcmp(argv[argi], "-type") == 0 && argi + 1 < argc) { - type_filter = argv[++argi][0]; - } - argi++; - } - - printf("%s\n", start); - find_recurse(start); - return 0; -} diff --git a/user/free.c b/user/free.c deleted file mode 100644 index b07a8bc..0000000 --- a/user/free.c +++ /dev/null @@ -1,17 +0,0 @@ -/* AdrOS free utility — display memory usage from /proc/meminfo */ -#include -#include -#include - -int main(void) { - int fd = open("/proc/meminfo", O_RDONLY); - if (fd >= 0) { - char buf[512]; - int n = read(fd, buf, sizeof(buf) - 1); - if (n > 0) { buf[n] = '\0'; printf("%s", buf); } - close(fd); - } else { - printf("free: /proc/meminfo not available\n"); - } - return 0; -} diff --git a/user/fulltest.c b/user/fulltest.c deleted file mode 100644 index f491441..0000000 --- a/user/fulltest.c +++ /dev/null @@ -1,4464 +0,0 @@ -#include - -#ifdef SIGKILL -#undef SIGKILL -#endif -#ifdef SIGUSR1 -#undef SIGUSR1 -#endif -#ifdef SIGSEGV -#undef SIGSEGV -#endif -#ifdef SIGTTIN -#undef SIGTTIN -#endif -#ifdef SIGTTOU -#undef SIGTTOU -#endif - -#ifdef WNOHANG -#undef WNOHANG -#endif -#ifdef SEEK_SET -#undef SEEK_SET -#endif -#ifdef SEEK_CUR -#undef SEEK_CUR -#endif -#ifdef SEEK_END -#undef SEEK_END -#endif - -#include "user_errno.h" - -#include "signal.h" - -enum { - SYSCALL_WRITE = 1, - SYSCALL_EXIT = 2, - SYSCALL_GETPID = 3, - SYSCALL_OPEN = 4, - SYSCALL_READ = 5, - SYSCALL_CLOSE = 6, - SYSCALL_WAITPID = 7, - SYSCALL_LSEEK = 9, - SYSCALL_FSTAT = 10, - SYSCALL_STAT = 11, - - SYSCALL_DUP = 12, - SYSCALL_DUP2 = 13, - SYSCALL_PIPE = 14, - SYSCALL_PIPE2 = 34, - SYSCALL_EXECVE = 15, - SYSCALL_FORK = 16, - SYSCALL_GETPPID = 17, - SYSCALL_POLL = 18, - SYSCALL_KILL = 19, - SYSCALL_SELECT = 20, - SYSCALL_IOCTL = 21, - SYSCALL_SETSID = 22, - SYSCALL_SETPGID = 23, - SYSCALL_GETPGRP = 24, - - SYSCALL_SIGACTION = 25, - SYSCALL_SIGPROCMASK = 26, - SYSCALL_SIGRETURN = 27, - - SYSCALL_MKDIR = 28, - SYSCALL_UNLINK = 29, - - SYSCALL_GETDENTS = 30, - - SYSCALL_FCNTL = 31, - - SYSCALL_CHDIR = 32, - SYSCALL_GETCWD = 33, - SYSCALL_DUP3 = 35, - - SYSCALL_OPENAT = 36, - SYSCALL_FSTATAT = 37, - SYSCALL_UNLINKAT = 38, - - SYSCALL_RENAME = 39, - SYSCALL_RMDIR = 40, - - SYSCALL_BRK = 41, - SYSCALL_NANOSLEEP = 42, - SYSCALL_CLOCK_GETTIME = 43, - SYSCALL_MMAP = 44, - SYSCALL_MUNMAP = 45, - - SYSCALL_SHMGET = 46, - SYSCALL_SHMAT = 47, - SYSCALL_SHMDT = 48, - - SYSCALL_LINK = 54, - SYSCALL_SYMLINK = 55, - SYSCALL_READLINK = 56, - - SYSCALL_SIGPENDING = 71, - SYSCALL_PREAD = 72, - SYSCALL_PWRITE = 73, - SYSCALL_ACCESS = 74, - SYSCALL_TRUNCATE = 78, - SYSCALL_FTRUNCATE = 79, - SYSCALL_UMASK = 75, - SYSCALL_ALARM = 83, - SYSCALL_SETITIMER = 92, - SYSCALL_GETITIMER = 93, - SYSCALL_WAITID = 94, - - SYSCALL_EPOLL_CREATE = 112, - SYSCALL_EPOLL_CTL = 113, - SYSCALL_EPOLL_WAIT = 114, - - SYSCALL_INOTIFY_INIT = 115, - SYSCALL_INOTIFY_ADD_WATCH = 116, - SYSCALL_INOTIFY_RM_WATCH = 117, - - SYSCALL_AIO_READ = 121, - SYSCALL_AIO_WRITE = 122, - SYSCALL_AIO_ERROR = 123, - SYSCALL_AIO_RETURN = 124, - - SYSCALL_CHMOD = 50, - SYSCALL_CHOWN = 51, - SYSCALL_GETUID = 52, - SYSCALL_GETGID = 53, - SYSCALL_CLONE = 67, - SYSCALL_GETTID = 68, - SYSCALL_FSYNC = 69, - SYSCALL_READV = 81, - SYSCALL_WRITEV = 82, - SYSCALL_TIMES = 84, - SYSCALL_FUTEX = 85, - SYSCALL_FLOCK = 87, - SYSCALL_GETEUID = 88, - SYSCALL_GETEGID = 89, - SYSCALL_SETEUID = 90, - SYSCALL_SETEGID = 91, - SYSCALL_SIGSUSPEND = 80, - SYSCALL_SIGQUEUE = 95, - SYSCALL_POSIX_SPAWN = 96, - SYSCALL_SETUID = 76, - SYSCALL_SETGID = 77, - SYSCALL_MOUNT = 126, - SYSCALL_GETTIMEOFDAY = 127, - SYSCALL_MPROTECT = 128, - SYSCALL_GETRLIMIT = 129, - SYSCALL_SETRLIMIT = 130, - SYSCALL_UNAME = 136, - SYSCALL_MADVISE = 140, -}; - -enum { - AT_FDCWD = -100, -}; - -enum { - F_GETFD = 1, - F_SETFD = 2, - F_GETFL = 3, - F_SETFL = 4, - F_GETPIPE_SZ = 1032, - F_SETPIPE_SZ = 1033, - FD_CLOEXEC = 1, -}; - -enum { - O_CLOEXEC = 0x80000, -}; - -enum { - TCGETS = 0x5401, - TCSETS = 0x5402, - TIOCGPGRP = 0x540F, - TIOCSPGRP = 0x5410, -}; - -enum { - ENOTTY = 25, -}; - -enum { - ICANON = 0x0002, - ECHO = 0x0008, -}; - -#define USER_NCCS 11 - -struct termios { - uint32_t c_iflag; - uint32_t c_oflag; - uint32_t c_cflag; - uint32_t c_lflag; - uint8_t c_cc[USER_NCCS]; -}; - -struct pollfd { - int fd; - int16_t events; - int16_t revents; -}; - -enum { - POLLIN = 0x0001, - POLLOUT = 0x0004, - EPOLLET = (1U << 31), -}; - -enum { - SIGKILL = 9, - SIGUSR1 = 10, - SIGSEGV = 11, - SIGTTIN = 21, - SIGTTOU = 22, -}; - -enum { - WNOHANG = 1, -}; - -enum { - SEEK_SET = 0, - SEEK_CUR = 1, - SEEK_END = 2, -}; - -enum { - O_CREAT = 0x40, - O_TRUNC = 0x200, - O_NONBLOCK = 0x800, - O_APPEND = 0x400, - O_RDWR = 0x02, -}; - -enum { - PROT_READ = 0x1, - PROT_WRITE = 0x2, -}; - -enum { - MAP_PRIVATE = 0x02, - MAP_ANONYMOUS = 0x20, - MAP_FAILED_VAL = 0xFFFFFFFF, -}; - -enum { - CLOCK_REALTIME = 0, - CLOCK_MONOTONIC = 1, -}; - -struct timespec { - uint32_t tv_sec; - uint32_t tv_nsec; -}; - -enum { - R_OK = 4, - W_OK = 2, - F_OK = 0, -}; - -enum { - SIG_BLOCK = 0, - SIG_UNBLOCK = 1, - SIG_SETMASK = 2, -}; - -enum { - IPC_CREAT = 01000, - IPC_PRIVATE = 0, -}; - -enum { - SIGALRM = 14, -}; - -enum { - EAGAIN = 11, - EINVAL = 22, - EEXIST = 17, -}; - -enum { - ITIMER_REAL = 0, - ITIMER_VIRTUAL = 1, - ITIMER_PROF = 2, -}; - -struct timeval { - uint32_t tv_sec; - uint32_t tv_usec; -}; - -struct rlimit { - uint32_t rlim_cur; - uint32_t rlim_max; -}; - -enum { - RLIMIT_CPU = 0, - RLIMIT_FSIZE = 1, - RLIMIT_DATA = 2, - RLIMIT_STACK = 3, - RLIMIT_CORE = 4, - RLIMIT_NOFILE = 5, - RLIMIT_AS = 6, - RLIMIT_NPROC = 7, - RLIM_INFINITY = 0xFFFFFFFFU, -}; - -struct itimerval { - struct timeval it_interval; - struct timeval it_value; -}; - -enum { - P_ALL = 0, - P_PID = 1, - P_PGID = 2, - WEXITED = 4, -}; - -#define S_IFMT 0170000 -#define S_IFREG 0100000 - -struct stat { - uint32_t st_ino; - uint32_t st_mode; - uint32_t st_nlink; - uint32_t st_uid; - uint32_t st_gid; - uint32_t st_size; -}; - -static int sys_write(int fd, const void* buf, uint32_t len) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_WRITE), "b"(fd), "c"(buf), "d"(len) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_openat(int dirfd, const char* path, uint32_t flags, uint32_t mode) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_OPENAT), "b"(dirfd), "c"(path), "d"(flags), "S"(mode) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_fstatat(int dirfd, const char* path, struct stat* st, uint32_t flags) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_FSTATAT), "b"(dirfd), "c"(path), "d"(st), "S"(flags) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_unlinkat(int dirfd, const char* path, uint32_t flags) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_UNLINKAT), "b"(dirfd), "c"(path), "d"(flags) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_rename(const char* oldpath, const char* newpath) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_RENAME), "b"(oldpath), "c"(newpath) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_rmdir(const char* path) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_RMDIR), "b"(path) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_pipe2(int fds[2], uint32_t flags) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_PIPE2), "b"(fds), "c"(flags) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_chdir(const char* path) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_CHDIR), "b"(path) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_getcwd(char* buf, uint32_t size) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_GETCWD), "b"(buf), "c"(size) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_fcntl(int fd, int cmd, uint32_t arg) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_FCNTL), "b"(fd), "c"(cmd), "d"(arg) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_ioctl(int fd, uint32_t cmd, void* arg); - -static int isatty_fd(int fd) { - struct termios t; - if (sys_ioctl(fd, TCGETS, &t) < 0) { - if (errno == ENOTTY) return 0; - return -1; - } - return 1; -} - -static int sys_getdents(int fd, void* buf, uint32_t len) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_GETDENTS), "b"(fd), "c"(buf), "d"(len) - : "memory" - ); - return __syscall_fix(ret); -} - -static void write_int_dec(int v) { - char buf[16]; - int i = 0; - if (v == 0) { - buf[i++] = '0'; - } else { - int neg = 0; - if (v < 0) { - neg = 1; - v = -v; - } - while (v > 0 && i < (int)sizeof(buf)) { - buf[i++] = (char)('0' + (v % 10)); - v /= 10; - } - if (neg && i < (int)sizeof(buf)) { - buf[i++] = '-'; - } - for (int j = 0; j < i / 2; j++) { - char t = buf[j]; - buf[j] = buf[i - 1 - j]; - buf[i - 1 - j] = t; - } - } - (void)sys_write(1, buf, (uint32_t)i); -} - -static void write_hex8(uint8_t v) { - static const char hex[] = "0123456789ABCDEF"; - char b[2]; - b[0] = hex[(v >> 4) & 0xF]; - b[1] = hex[v & 0xF]; - (void)sys_write(1, b, 2); -} - -static void write_hex32(uint32_t v) { - static const char hex[] = "0123456789ABCDEF"; - char b[8]; - for (int i = 0; i < 8; i++) { - uint32_t shift = (uint32_t)(28 - 4 * i); - b[i] = hex[(v >> shift) & 0xFU]; - } - (void)sys_write(1, b, 8); -} - -static int memeq(const void* a, const void* b, uint32_t n) { - const uint8_t* x = (const uint8_t*)a; - const uint8_t* y = (const uint8_t*)b; - for (uint32_t i = 0; i < n; i++) { - if (x[i] != y[i]) return 0; - } - return 1; -} - -static int streq(const char* a, const char* b) { - if (!a || !b) return 0; - uint32_t i = 0; - while (a[i] != 0 && b[i] != 0) { - if (a[i] != b[i]) return 0; - i++; - } - return a[i] == b[i]; -} - -static int sys_sigaction2(int sig, const struct sigaction* act, struct sigaction* oldact) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_SIGACTION), "b"(sig), "c"(act), "d"(oldact) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_sigaction(int sig, void (*handler)(int), uintptr_t* old_out) { - struct sigaction act; - act.sa_handler = (uintptr_t)handler; - act.sa_sigaction = 0; - act.sa_mask = 0; - act.sa_flags = 0; - - struct sigaction oldact; - struct sigaction* oldp = old_out ? &oldact : 0; - - int r = sys_sigaction2(sig, &act, oldp); - if (r < 0) return r; - if (old_out) { - *old_out = oldact.sa_handler; - } - return 0; -} - -static int sys_select(uint32_t nfds, uint64_t* readfds, uint64_t* writefds, uint64_t* exceptfds, int32_t timeout) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_SELECT), "b"(nfds), "c"(readfds), "d"(writefds), "S"(exceptfds), "D"(timeout) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_ioctl(int fd, uint32_t cmd, void* arg) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_IOCTL), "b"(fd), "c"(cmd), "d"(arg) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_kill(int pid, int sig) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_KILL), "b"(pid), "c"(sig) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_poll(struct pollfd* fds, uint32_t nfds, int32_t timeout) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_POLL), "b"(fds), "c"(nfds), "d"(timeout) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_setsid(void) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_SETSID) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_setpgid(int pid, int pgid) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_SETPGID), "b"(pid), "c"(pgid) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_getpgrp(void) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_GETPGRP) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_getpid(void) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_GETPID) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_getppid(void) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_GETPPID) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_fork(void) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_FORK) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_execve(const char* path, const char* const* argv, const char* const* envp) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_EXECVE), "b"(path), "c"(argv), "d"(envp) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_pipe(int fds[2]) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_PIPE), "b"(fds) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_dup(int oldfd) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_DUP), "b"(oldfd) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_dup2(int oldfd, int newfd) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_DUP2), "b"(oldfd), "c"(newfd) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_dup3(int oldfd, int newfd, uint32_t flags) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_DUP3), "b"(oldfd), "c"(newfd), "d"(flags) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_waitpid(int pid, int* status, uint32_t options) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_WAITPID), "b"(pid), "c"(status), "d"(options) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_open(const char* path, uint32_t flags) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_OPEN), "b"(path), "c"(flags) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_mkdir(const char* path) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_MKDIR), "b"(path) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_unlink(const char* path) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_UNLINK), "b"(path) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_read(int fd, void* buf, uint32_t len) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_READ), "b"(fd), "c"(buf), "d"(len) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_close(int fd) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_CLOSE), "b"(fd) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_lseek(int fd, int32_t offset, int whence) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_LSEEK), "b"(fd), "c"(offset), "d"(whence) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_fstat(int fd, struct stat* st) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_FSTAT), "b"(fd), "c"(st) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_stat(const char* path, struct stat* st) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_STAT), "b"(path), "c"(st) - : "memory" - ); - return __syscall_fix(ret); -} - -static uintptr_t sys_brk(uintptr_t addr) { - uintptr_t ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_BRK), "b"(addr) - : "memory" - ); - return ret; -} - -static uintptr_t sys_mmap(uintptr_t addr, uint32_t len, uint32_t prot, uint32_t flags, int fd) { - uintptr_t ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_MMAP), "b"(addr), "c"(len), "d"(prot), "S"(flags), "D"(fd) - : "memory" - ); - return ret; -} - -static int sys_munmap(uintptr_t addr, uint32_t len) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_MUNMAP), "b"(addr), "c"(len) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_clock_gettime(uint32_t clk_id, struct timespec* tp) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_CLOCK_GETTIME), "b"(clk_id), "c"(tp) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_pread(int fd, void* buf, uint32_t count, uint32_t offset) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_PREAD), "b"(fd), "c"(buf), "d"(count), "S"(offset) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_pwrite(int fd, const void* buf, uint32_t count, uint32_t offset) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_PWRITE), "b"(fd), "c"(buf), "d"(count), "S"(offset) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_ftruncate(int fd, uint32_t length) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_FTRUNCATE), "b"(fd), "c"(length) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_symlink(const char* target, const char* linkpath) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_SYMLINK), "b"(target), "c"(linkpath) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_readlink(const char* path, char* buf, uint32_t bufsiz) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_READLINK), "b"(path), "c"(buf), "d"(bufsiz) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_access(const char* path, uint32_t mode) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_ACCESS), "b"(path), "c"(mode) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_sigprocmask(int how, uint32_t mask, uint32_t* oldset) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_SIGPROCMASK), "b"(how), "c"(mask), "d"(oldset) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_sigpending(uint32_t* set) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_SIGPENDING), "b"(set) - : "memory" - ); - return __syscall_fix(ret); -} - -static uint32_t sys_alarm(uint32_t seconds) { - uint32_t ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_ALARM), "b"(seconds) - : "memory" - ); - return ret; -} - -static int sys_shmget(uint32_t key, uint32_t size, uint32_t flags) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_SHMGET), "b"(key), "c"(size), "d"(flags) - : "memory" - ); - return __syscall_fix(ret); -} - -static uintptr_t sys_shmat(int shmid, uintptr_t addr, uint32_t flags) { - uintptr_t ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_SHMAT), "b"(shmid), "c"(addr), "d"(flags) - : "memory" - ); - return ret; -} - -static int sys_shmdt(uintptr_t addr) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_SHMDT), "b"(addr) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_link(const char* oldpath, const char* newpath) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_LINK), "b"(oldpath), "c"(newpath) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_umask(uint32_t mask) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_UMASK), "b"(mask) - : "memory" - ); - return ret; -} - -static int sys_setitimer(int which, const struct itimerval* newval, struct itimerval* oldval) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_SETITIMER), "b"(which), "c"(newval), "d"(oldval) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_getitimer(int which, struct itimerval* cur) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_GETITIMER), "b"(which), "c"(cur) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_waitid(uint32_t idtype, uint32_t id, void* infop, uint32_t options) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_WAITID), "b"(idtype), "c"(id), "d"(infop), "S"(options) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_epoll_create(int size) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_EPOLL_CREATE), "b"(size) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_epoll_ctl(int epfd, int op, int fd, void* event) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_EPOLL_CTL), "b"(epfd), "c"(op), "d"(fd), "S"(event) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_epoll_wait(int epfd, void* events, int maxevents, int timeout) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_EPOLL_WAIT), "b"(epfd), "c"(events), "d"(maxevents), "S"(timeout) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_inotify_init(void) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_INOTIFY_INIT) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_inotify_add_watch(int fd, const char* path, uint32_t mask) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_INOTIFY_ADD_WATCH), "b"(fd), "c"(path), "d"(mask) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_inotify_rm_watch(int fd, int wd) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_INOTIFY_RM_WATCH), "b"(fd), "c"(wd) - : "memory" - ); - return __syscall_fix(ret); -} - -struct aiocb { - int aio_fildes; - void* aio_buf; - uint32_t aio_nbytes; - uint32_t aio_offset; - int32_t aio_error; - int32_t aio_return; -}; - -static int sys_aio_read(struct aiocb* cb) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_AIO_READ), "b"(cb) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_aio_write(struct aiocb* cb) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_AIO_WRITE), "b"(cb) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_aio_error(struct aiocb* cb) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_AIO_ERROR), "b"(cb) - : "memory" - ); - return ret; -} - -static int sys_aio_return(struct aiocb* cb) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_AIO_RETURN), "b"(cb) - : "memory" - ); - return ret; -} - -static int sys_nanosleep(const struct timespec* req, struct timespec* rem) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_NANOSLEEP), "b"(req), "c"(rem) - : "memory" - ); - return __syscall_fix(ret); -} - -static uint32_t sys_getuid(void) { - uint32_t ret; - __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_GETUID) : "memory"); - return ret; -} - -static uint32_t sys_getgid(void) { - uint32_t ret; - __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_GETGID) : "memory"); - return ret; -} - -static uint32_t sys_geteuid(void) { - uint32_t ret; - __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_GETEUID) : "memory"); - return ret; -} - -static uint32_t sys_getegid(void) { - uint32_t ret; - __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_GETEGID) : "memory"); - return ret; -} - -static int sys_gettid(void) { - int ret; - __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_GETTID) : "memory"); - return ret; -} - -static int sys_fsync(int fd) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_FSYNC), "b"(fd) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_truncate(const char* path, uint32_t length) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_TRUNCATE), "b"(path), "c"(length) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_chmod(const char* path, uint32_t mode) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_CHMOD), "b"(path), "c"(mode) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_flock(int fd, int operation) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_FLOCK), "b"(fd), "c"(operation) - : "memory" - ); - return __syscall_fix(ret); -} - -struct iovec { - void* iov_base; - uint32_t iov_len; -}; - -static int sys_writev(int fd, const struct iovec* iov, int iovcnt) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_WRITEV), "b"(fd), "c"(iov), "d"(iovcnt) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_readv(int fd, const struct iovec* iov, int iovcnt) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_READV), "b"(fd), "c"(iov), "d"(iovcnt) - : "memory" - ); - return __syscall_fix(ret); -} - -static uint32_t sys_times(void* buf) { - uint32_t ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_TIMES), "b"(buf) - : "memory" - ); - return ret; -} - -static int sys_posix_spawn(uint32_t* pid_out, const char* path, - const char* const* argv, const char* const* envp) { - int ret; - __asm__ volatile( - "int $0x80" - : "=a"(ret) - : "a"(SYSCALL_POSIX_SPAWN), "b"(pid_out), "c"(path), "d"(argv), "S"(envp) - : "memory" - ); - return __syscall_fix(ret); -} - -static int sys_setuid(uint32_t uid) { - int ret; - __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_SETUID), "b"(uid) : "memory"); - return __syscall_fix(ret); -} - -static int sys_setgid(uint32_t gid) { - int ret; - __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_SETGID), "b"(gid) : "memory"); - return __syscall_fix(ret); -} - -static int sys_seteuid(uint32_t euid) { - int ret; - __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_SETEUID), "b"(euid) : "memory"); - return __syscall_fix(ret); -} - -static int sys_setegid(uint32_t egid) { - int ret; - __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_SETEGID), "b"(egid) : "memory"); - return __syscall_fix(ret); -} - -static int sys_sigsuspend(const uint32_t* mask) { - int ret; - __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_SIGSUSPEND), "b"(mask) : "memory"); - return __syscall_fix(ret); -} - -static int sys_gettimeofday(struct timeval* tv) { - int ret; - __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_GETTIMEOFDAY), "b"(tv), "c"(0) : "memory"); - return __syscall_fix(ret); -} - -static int sys_mprotect(uintptr_t addr, uint32_t len, uint32_t prot) { - int ret; - __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_MPROTECT), "b"(addr), "c"(len), "d"(prot) : "memory"); - return __syscall_fix(ret); -} - -static int sys_madvise(uintptr_t addr, uint32_t len, uint32_t advice) { - int ret; - __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_MADVISE), "b"(addr), "c"(len), "d"(advice) : "memory"); - return __syscall_fix(ret); -} - -static int sys_getrlimit(int resource, struct rlimit* rlim) { - int ret; - __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_GETRLIMIT), "b"(resource), "c"(rlim) : "memory"); - return __syscall_fix(ret); -} - -struct utsname { - char sysname[65]; - char nodename[65]; - char release[65]; - char version[65]; - char machine[65]; -}; - -static int sys_uname(struct utsname* buf) { - int ret; - __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_UNAME), "b"(buf) : "memory"); - return __syscall_fix(ret); -} - -static int sys_setrlimit(int resource, const struct rlimit* rlim) { - int ret; - __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_SETRLIMIT), "b"(resource), "c"(rlim) : "memory"); - return __syscall_fix(ret); -} - -__attribute__((noreturn)) static void sys_exit(int code) { - __asm__ volatile( - "int $0x80\n" - "1: jmp 1b\n" - : - : "a"(SYSCALL_EXIT), "b"(code) - : "memory" - ); - for (;;) { - __asm__ volatile("hlt"); - } -} - -static volatile int got_usr1 = 0; -static volatile int got_usr1_ret = 0; -static volatile int got_ttin = 0; -static volatile int got_ttou = 0; -static volatile int got_alrm = 0; - -static void usr1_handler(int sig) { - (void)sig; - got_usr1 = 1; - sys_write(1, "[init] SIGUSR1 handler OK\n", - (uint32_t)(sizeof("[init] SIGUSR1 handler OK\n") - 1)); -} - -static void usr1_ret_handler(int sig) { - (void)sig; - got_usr1_ret = 1; -} - -static void alrm_handler(int sig) { - (void)sig; - got_alrm = 1; -} - -static void ttin_handler(int sig) { - (void)sig; - got_ttin = 1; -} - -static void ttou_handler(int sig) { - (void)sig; - got_ttou = 1; -} - -static void sigsegv_exit_handler(int sig) { - (void)sig; - static const char msg[] = "[init] SIGSEGV handler invoked\n"; - (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1)); - sys_exit(0); -} - -static void sigsegv_info_handler(int sig, siginfo_t* info, void* uctx) { - (void)uctx; - static const char msg[] = "[init] SIGSEGV siginfo handler invoked\n"; - (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1)); - const uintptr_t expected = 0x12345000U; - if (sig == SIGSEGV && info && (uintptr_t)info->si_addr == expected) { - sys_exit(0); - } - sys_exit(1); -} - -void _start(void) { - __asm__ volatile( - "mov $0x23, %ax\n" - "mov %ax, %ds\n" - "mov %ax, %es\n" - "mov %ax, %fs\n" - "mov %ax, %gs\n" - ); - - static const char msg[] = "[init] hello from init.elf\n"; - (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1)); - - static const char path[] = "/sbin/fulltest"; - - int fd = sys_open(path, 0); - if (fd < 0) { - sys_write(1, "[init] open failed fd=", (uint32_t)(sizeof("[init] open failed fd=") - 1)); - write_int_dec(fd); - sys_write(1, "\n", 1); - sys_exit(1); - } - - uint8_t hdr[4]; - int rd = sys_read(fd, hdr, 4); - (void)sys_close(fd); - if (rd == 4 && hdr[0] == 0x7F && hdr[1] == 'E' && hdr[2] == 'L' && hdr[3] == 'F') { - sys_write(1, "[init] open/read/close OK (ELF magic)\n", - (uint32_t)(sizeof("[init] open/read/close OK (ELF magic)\n") - 1)); - } else { - sys_write(1, "[init] read failed or bad header rd=", (uint32_t)(sizeof("[init] read failed or bad header rd=") - 1)); - write_int_dec(rd); - sys_write(1, " hdr=", (uint32_t)(sizeof(" hdr=") - 1)); - for (int i = 0; i < 4; i++) { - write_hex8(hdr[i]); - } - sys_write(1, "\n", 1); - sys_exit(1); - } - - fd = sys_open("/sbin/fulltest", 0); - if (fd < 0) { - sys_write(1, "[init] overlay open failed\n", - (uint32_t)(sizeof("[init] overlay open failed\n") - 1)); - sys_exit(1); - } - - uint8_t orig0 = 0; - if (sys_lseek(fd, 0, SEEK_SET) < 0 || sys_read(fd, &orig0, 1) != 1) { - sys_write(1, "[init] overlay read failed\n", - (uint32_t)(sizeof("[init] overlay read failed\n") - 1)); - sys_exit(1); - } - - uint8_t x = (uint8_t)(orig0 ^ 0xFF); - if (sys_lseek(fd, 0, SEEK_SET) < 0 || sys_write(fd, &x, 1) != 1) { - sys_write(1, "[init] overlay write failed\n", - (uint32_t)(sizeof("[init] overlay write failed\n") - 1)); - sys_exit(1); - } - - if (sys_close(fd) < 0) { - sys_write(1, "[init] overlay close failed\n", - (uint32_t)(sizeof("[init] overlay close failed\n") - 1)); - sys_exit(1); - } - - fd = sys_open("/sbin/fulltest", 0); - if (fd < 0) { - sys_write(1, "[init] overlay open2 failed\n", - (uint32_t)(sizeof("[init] overlay open2 failed\n") - 1)); - sys_exit(1); - } - - uint8_t chk = 0; - if (sys_lseek(fd, 0, SEEK_SET) < 0 || sys_read(fd, &chk, 1) != 1 || chk != x) { - sys_write(1, "[init] overlay verify failed\n", - (uint32_t)(sizeof("[init] overlay verify failed\n") - 1)); - sys_exit(1); - } - - if (sys_lseek(fd, 0, SEEK_SET) < 0 || sys_write(fd, &orig0, 1) != 1) { - sys_write(1, "[init] overlay restore failed\n", - (uint32_t)(sizeof("[init] overlay restore failed\n") - 1)); - sys_exit(1); - } - - if (sys_close(fd) < 0) { - sys_write(1, "[init] overlay close2 failed\n", - (uint32_t)(sizeof("[init] overlay close2 failed\n") - 1)); - sys_exit(1); - } - - sys_write(1, "[init] overlay copy-up OK\n", - (uint32_t)(sizeof("[init] overlay copy-up OK\n") - 1)); - - fd = sys_open("/sbin/fulltest", 0); - if (fd < 0) { - sys_write(1, "[init] open2 failed\n", (uint32_t)(sizeof("[init] open2 failed\n") - 1)); - sys_exit(1); - } - - struct stat st; - if (sys_fstat(fd, &st) < 0) { - sys_write(1, "[init] fstat failed\n", (uint32_t)(sizeof("[init] fstat failed\n") - 1)); - sys_exit(1); - } - - if ((st.st_mode & S_IFMT) != S_IFREG || st.st_size == 0) { - sys_write(1, "[init] fstat bad\n", (uint32_t)(sizeof("[init] fstat bad\n") - 1)); - sys_exit(1); - } - - if (sys_lseek(fd, 0, SEEK_SET) < 0) { - sys_write(1, "[init] lseek set failed\n", - (uint32_t)(sizeof("[init] lseek set failed\n") - 1)); - sys_exit(1); - } - - uint8_t m2[4]; - if (sys_read(fd, m2, 4) != 4) { - sys_write(1, "[init] read2 failed\n", (uint32_t)(sizeof("[init] read2 failed\n") - 1)); - sys_exit(1); - } - if (m2[0] != 0x7F || m2[1] != 'E' || m2[2] != 'L' || m2[3] != 'F') { - sys_write(1, "[init] lseek/read mismatch\n", - (uint32_t)(sizeof("[init] lseek/read mismatch\n") - 1)); - sys_exit(1); - } - - if (sys_close(fd) < 0) { - sys_write(1, "[init] close2 failed\n", (uint32_t)(sizeof("[init] close2 failed\n") - 1)); - sys_exit(1); - } - - if (sys_stat("/sbin/fulltest", &st) < 0) { - sys_write(1, "[init] stat failed\n", (uint32_t)(sizeof("[init] stat failed\n") - 1)); - sys_exit(1); - } - if ((st.st_mode & S_IFMT) != S_IFREG || st.st_size == 0) { - sys_write(1, "[init] stat bad\n", (uint32_t)(sizeof("[init] stat bad\n") - 1)); - sys_exit(1); - } - - sys_write(1, "[init] lseek/stat/fstat OK\n", - (uint32_t)(sizeof("[init] lseek/stat/fstat OK\n") - 1)); - - fd = sys_open("/tmp/hello.txt", 0); - if (fd < 0) { - sys_write(1, "[init] tmpfs open failed\n", - (uint32_t)(sizeof("[init] tmpfs open failed\n") - 1)); - sys_exit(1); - } - - if (sys_lseek(fd, 0, SEEK_END) < 0) { - sys_write(1, "[init] dup2 prep lseek failed\n", - (uint32_t)(sizeof("[init] dup2 prep lseek failed\n") - 1)); - sys_exit(1); - } - - if (sys_dup2(fd, 1) != 1) { - sys_write(1, "[init] dup2 failed\n", (uint32_t)(sizeof("[init] dup2 failed\n") - 1)); - sys_exit(1); - } - - (void)sys_close(fd); - - { - static const char m[] = "[init] dup2 stdout->file OK\n"; - if (sys_write(1, m, (uint32_t)(sizeof(m) - 1)) != (int)(sizeof(m) - 1)) { - sys_exit(1); - } - } - - (void)sys_close(1); - sys_write(1, "[init] dup2 restore tty OK\n", - (uint32_t)(sizeof("[init] dup2 restore tty OK\n") - 1)); - - { - int pfds[2]; - if (sys_pipe(pfds) < 0) { - sys_write(1, "[init] pipe failed\n", (uint32_t)(sizeof("[init] pipe failed\n") - 1)); - sys_exit(1); - } - - static const char pmsg[] = "pipe-test"; - if (sys_write(pfds[1], pmsg, (uint32_t)(sizeof(pmsg) - 1)) != (int)(sizeof(pmsg) - 1)) { - sys_write(1, "[init] pipe write failed\n", - (uint32_t)(sizeof("[init] pipe write failed\n") - 1)); - sys_exit(1); - } - - char rbuf[16]; - int prd = sys_read(pfds[0], rbuf, (uint32_t)(sizeof(pmsg) - 1)); - if (prd != (int)(sizeof(pmsg) - 1)) { - sys_write(1, "[init] pipe read failed\n", - (uint32_t)(sizeof("[init] pipe read failed\n") - 1)); - sys_exit(1); - } - - int ok = 1; - for (uint32_t i = 0; i < (uint32_t)(sizeof(pmsg) - 1); i++) { - if ((uint8_t)rbuf[i] != (uint8_t)pmsg[i]) ok = 0; - } - if (!ok) { - sys_write(1, "[init] pipe mismatch\n", - (uint32_t)(sizeof("[init] pipe mismatch\n") - 1)); - sys_exit(1); - } - - if (sys_dup2(pfds[1], 1) != 1) { - sys_write(1, "[init] pipe dup2 failed\n", - (uint32_t)(sizeof("[init] pipe dup2 failed\n") - 1)); - sys_exit(1); - } - - static const char p2[] = "dup2-pipe"; - if (sys_write(1, p2, (uint32_t)(sizeof(p2) - 1)) != (int)(sizeof(p2) - 1)) { - sys_exit(1); - } - - int prd2 = sys_read(pfds[0], rbuf, (uint32_t)(sizeof(p2) - 1)); - if (prd2 != (int)(sizeof(p2) - 1)) { - sys_write(1, "[init] pipe dup2 read failed\n", - (uint32_t)(sizeof("[init] pipe dup2 read failed\n") - 1)); - sys_exit(1); - } - sys_write(1, "[init] pipe OK\n", (uint32_t)(sizeof("[init] pipe OK\n") - 1)); - - (void)sys_close(pfds[0]); - (void)sys_close(pfds[1]); - - int tfd = sys_open("/dev/tty", 0); - if (tfd < 0) { - sys_write(1, "[init] /dev/tty open failed\n", - (uint32_t)(sizeof("[init] /dev/tty open failed\n") - 1)); - sys_exit(1); - } - if (sys_dup2(tfd, 1) != 1) { - sys_write(1, "[init] dup2 restore tty failed\n", - (uint32_t)(sizeof("[init] dup2 restore tty failed\n") - 1)); - sys_exit(1); - } - (void)sys_close(tfd); - - } - - { - int pid = sys_fork(); - if (pid < 0) { - sys_write(1, "[init] kill test fork failed\n", - (uint32_t)(sizeof("[init] kill test fork failed\n") - 1)); - sys_exit(1); - } - - if (pid == 0) { - for (;;) { - __asm__ volatile("nop"); - } - } - - if (sys_kill(pid, SIGKILL) < 0) { - sys_write(1, "[init] kill(SIGKILL) failed\n", - (uint32_t)(sizeof("[init] kill(SIGKILL) failed\n") - 1)); - sys_exit(1); - } - - int st = 0; - int rp = sys_waitpid(pid, &st, 0); - if (rp != pid || st != (128 + SIGKILL)) { - sys_write(1, "[init] kill test waitpid mismatch\n", - (uint32_t)(sizeof("[init] kill test waitpid mismatch\n") - 1)); - sys_exit(1); - } - - sys_write(1, "[init] kill(SIGKILL) OK\n", - (uint32_t)(sizeof("[init] kill(SIGKILL) OK\n") - 1)); - } - - { - int fds[2]; - if (sys_pipe(fds) < 0) { - sys_write(1, "[init] poll pipe setup failed\n", - (uint32_t)(sizeof("[init] poll pipe setup failed\n") - 1)); - sys_exit(1); - } - - struct pollfd p; - p.fd = fds[0]; - p.events = POLLIN; - p.revents = 0; - int rc = sys_poll(&p, 1, 0); - if (rc != 0) { - sys_write(1, "[init] poll(pipe) expected 0\n", - (uint32_t)(sizeof("[init] poll(pipe) expected 0\n") - 1)); - sys_exit(1); - } - - static const char a = 'A'; - if (sys_write(fds[1], &a, 1) != 1) { - sys_write(1, "[init] poll pipe write failed\n", - (uint32_t)(sizeof("[init] poll pipe write failed\n") - 1)); - sys_exit(1); - } - - p.revents = 0; - rc = sys_poll(&p, 1, 0); - if (rc != 1 || (p.revents & POLLIN) == 0) { - sys_write(1, "[init] poll(pipe) expected POLLIN\n", - (uint32_t)(sizeof("[init] poll(pipe) expected POLLIN\n") - 1)); - sys_exit(1); - } - - (void)sys_close(fds[0]); - (void)sys_close(fds[1]); - sys_write(1, "[init] poll(pipe) OK\n", (uint32_t)(sizeof("[init] poll(pipe) OK\n") - 1)); - } - - { - int fds[2]; - if (sys_pipe(fds) < 0) { - sys_write(1, "[init] select pipe setup failed\n", - (uint32_t)(sizeof("[init] select pipe setup failed\n") - 1)); - sys_exit(1); - } - - uint64_t r = 0; - uint64_t w = 0; - r |= (1ULL << (uint32_t)fds[0]); - int rc = sys_select((uint32_t)(fds[0] + 1), &r, &w, 0, 0); - if (rc != 0) { - sys_write(1, "[init] select(pipe) expected 0\n", - (uint32_t)(sizeof("[init] select(pipe) expected 0\n") - 1)); - sys_exit(1); - } - - static const char a = 'B'; - if (sys_write(fds[1], &a, 1) != 1) { - sys_write(1, "[init] select pipe write failed\n", - (uint32_t)(sizeof("[init] select pipe write failed\n") - 1)); - sys_exit(1); - } - - r = 0; - w = 0; - r |= (1ULL << (uint32_t)fds[0]); - rc = sys_select((uint32_t)(fds[0] + 1), &r, &w, 0, 0); - if (rc != 1 || ((r >> (uint32_t)fds[0]) & 1ULL) == 0) { - sys_write(1, "[init] select(pipe) expected readable\n", - (uint32_t)(sizeof("[init] select(pipe) expected readable\n") - 1)); - sys_exit(1); - } - - (void)sys_close(fds[0]); - (void)sys_close(fds[1]); - sys_write(1, "[init] select(pipe) OK\n", - (uint32_t)(sizeof("[init] select(pipe) OK\n") - 1)); - } - - { - int fd = sys_open("/dev/tty", 0); - if (fd < 0) { - sys_write(1, "[init] ioctl(/dev/tty) open failed\n", - (uint32_t)(sizeof("[init] ioctl(/dev/tty) open failed\n") - 1)); - sys_exit(1); - } - - int fg = -1; - if (sys_ioctl(fd, TIOCGPGRP, &fg) < 0 || fg != 0) { - sys_write(1, "[init] ioctl TIOCGPGRP failed\n", - (uint32_t)(sizeof("[init] ioctl TIOCGPGRP failed\n") - 1)); - sys_exit(1); - } - - fg = 0; - if (sys_ioctl(fd, TIOCSPGRP, &fg) < 0) { - sys_write(1, "[init] ioctl TIOCSPGRP failed\n", - (uint32_t)(sizeof("[init] ioctl TIOCSPGRP failed\n") - 1)); - sys_exit(1); - } - - fg = 1; - if (sys_ioctl(fd, TIOCSPGRP, &fg) >= 0) { - sys_write(1, "[init] ioctl TIOCSPGRP expected fail\n", - (uint32_t)(sizeof("[init] ioctl TIOCSPGRP expected fail\n") - 1)); - sys_exit(1); - } - - struct termios oldt; - if (sys_ioctl(fd, TCGETS, &oldt) < 0) { - sys_write(1, "[init] ioctl TCGETS failed\n", - (uint32_t)(sizeof("[init] ioctl TCGETS failed\n") - 1)); - sys_exit(1); - } - - struct termios t = oldt; - t.c_lflag &= ~(uint32_t)(ECHO | ICANON); - if (sys_ioctl(fd, TCSETS, &t) < 0) { - sys_write(1, "[init] ioctl TCSETS failed\n", - (uint32_t)(sizeof("[init] ioctl TCSETS failed\n") - 1)); - sys_exit(1); - } - - struct termios chk; - if (sys_ioctl(fd, TCGETS, &chk) < 0) { - sys_write(1, "[init] ioctl TCGETS2 failed\n", - (uint32_t)(sizeof("[init] ioctl TCGETS2 failed\n") - 1)); - sys_exit(1); - } - - if ((chk.c_lflag & (uint32_t)(ECHO | ICANON)) != 0) { - sys_write(1, "[init] ioctl verify failed\n", - (uint32_t)(sizeof("[init] ioctl verify failed\n") - 1)); - sys_exit(1); - } - - (void)sys_ioctl(fd, TCSETS, &oldt); - (void)sys_close(fd); - - sys_write(1, "[init] ioctl(/dev/tty) OK\n", - (uint32_t)(sizeof("[init] ioctl(/dev/tty) OK\n") - 1)); - } - - // A2: basic job control. A background pgrp read/write on controlling TTY should raise SIGTTIN/SIGTTOU. - { - int leader = sys_fork(); - if (leader < 0) { - sys_write(1, "[init] fork(job control leader) failed\n", - (uint32_t)(sizeof("[init] fork(job control leader) failed\n") - 1)); - sys_exit(1); - } - if (leader == 0) { - int me = sys_getpid(); - int sid = sys_setsid(); - if (sid != me) { - sys_write(1, "[init] setsid(job control) failed\n", - (uint32_t)(sizeof("[init] setsid(job control) failed\n") - 1)); - sys_exit(1); - } - - int tfd = sys_open("/dev/tty", 0); - if (tfd < 0) { - sys_write(1, "[init] open(/dev/tty) for job control failed\n", - (uint32_t)(sizeof("[init] open(/dev/tty) for job control failed\n") - 1)); - sys_exit(1); - } - - // Touch ioctl to make kernel acquire controlling session/pgrp. - int fg = 0; - (void)sys_ioctl(tfd, TIOCGPGRP, &fg); - - fg = me; - if (sys_ioctl(tfd, TIOCSPGRP, &fg) < 0) { - sys_write(1, "[init] ioctl TIOCSPGRP(job control) failed\n", - (uint32_t)(sizeof("[init] ioctl TIOCSPGRP(job control) failed\n") - 1)); - sys_exit(1); - } - - int bg = sys_fork(); - if (bg < 0) { - sys_write(1, "[init] fork(job control bg) failed\n", - (uint32_t)(sizeof("[init] fork(job control bg) failed\n") - 1)); - sys_exit(1); - } - if (bg == 0) { - (void)sys_setpgid(0, me + 1); - - (void)sys_sigaction(SIGTTIN, ttin_handler, 0); - (void)sys_sigaction(SIGTTOU, ttou_handler, 0); - - uint8_t b = 0; - (void)sys_read(tfd, &b, 1); - if (!got_ttin) { - sys_write(1, "[init] SIGTTIN job control failed\n", - (uint32_t)(sizeof("[init] SIGTTIN job control failed\n") - 1)); - sys_exit(1); - } - - const char msg2[] = "x"; - (void)sys_write(tfd, msg2, 1); - if (!got_ttou) { - sys_write(1, "[init] SIGTTOU job control failed\n", - (uint32_t)(sizeof("[init] SIGTTOU job control failed\n") - 1)); - sys_exit(1); - } - - sys_exit(0); - } - - int st2 = 0; - int wp2 = sys_waitpid(bg, &st2, 0); - if (wp2 != bg || st2 != 0) { - sys_write(1, "[init] waitpid(job control bg) failed wp=", (uint32_t)(sizeof("[init] waitpid(job control bg) failed wp=") - 1)); - write_int_dec(wp2); - sys_write(1, " st=", (uint32_t)(sizeof(" st=") - 1)); - write_int_dec(st2); - sys_write(1, "\n", 1); - sys_exit(1); - } - - (void)sys_close(tfd); - sys_exit(0); - } - - int stL = 0; - int wpL = sys_waitpid(leader, &stL, 0); - if (wpL != leader || stL != 0) { - sys_write(1, "[init] waitpid(job control leader) failed wp=", (uint32_t)(sizeof("[init] waitpid(job control leader) failed wp=") - 1)); - write_int_dec(wpL); - sys_write(1, " st=", (uint32_t)(sizeof(" st=") - 1)); - write_int_dec(stL); - sys_write(1, "\n", 1); - sys_exit(1); - } - - sys_write(1, "[init] job control (SIGTTIN/SIGTTOU) OK\n", - (uint32_t)(sizeof("[init] job control (SIGTTIN/SIGTTOU) OK\n") - 1)); - } - - { - int fd = sys_open("/dev/null", 0); - if (fd < 0) { - sys_write(1, "[init] poll(/dev/null) open failed\n", - (uint32_t)(sizeof("[init] poll(/dev/null) open failed\n") - 1)); - sys_exit(1); - } - struct pollfd p; - p.fd = fd; - p.events = POLLOUT; - p.revents = 0; - int rc = sys_poll(&p, 1, 0); - if (rc != 1 || (p.revents & POLLOUT) == 0) { - sys_write(1, "[init] poll(/dev/null) expected POLLOUT\n", - (uint32_t)(sizeof("[init] poll(/dev/null) expected POLLOUT\n") - 1)); - sys_exit(1); - } - (void)sys_close(fd); - sys_write(1, "[init] poll(/dev/null) OK\n", - (uint32_t)(sizeof("[init] poll(/dev/null) OK\n") - 1)); - } - - { - int mfd = sys_open("/dev/ptmx", 0); - int sfd = sys_open("/dev/pts/0", 0); - if (mfd < 0 || sfd < 0) { - sys_write(1, "[init] pty open failed\n", - (uint32_t)(sizeof("[init] pty open failed\n") - 1)); - sys_exit(1); - } - - static const char m2s[] = "m2s"; - if (sys_write(mfd, m2s, (uint32_t)(sizeof(m2s) - 1)) != (int)(sizeof(m2s) - 1)) { - sys_write(1, "[init] pty write master failed\n", - (uint32_t)(sizeof("[init] pty write master failed\n") - 1)); - sys_exit(1); - } - - struct pollfd p; - p.fd = sfd; - p.events = POLLIN; - p.revents = 0; - int rc = sys_poll(&p, 1, 50); - if (rc != 1 || (p.revents & POLLIN) == 0) { - sys_write(1, "[init] pty poll slave failed\n", - (uint32_t)(sizeof("[init] pty poll slave failed\n") - 1)); - sys_exit(1); - } - - char buf[8]; - int rd = sys_read(sfd, buf, (uint32_t)(sizeof(m2s) - 1)); - if (rd != (int)(sizeof(m2s) - 1) || !memeq(buf, m2s, (uint32_t)(sizeof(m2s) - 1))) { - sys_write(1, "[init] pty read slave failed\n", - (uint32_t)(sizeof("[init] pty read slave failed\n") - 1)); - sys_exit(1); - } - - static const char s2m[] = "s2m"; - if (sys_write(sfd, s2m, (uint32_t)(sizeof(s2m) - 1)) != (int)(sizeof(s2m) - 1)) { - sys_write(1, "[init] pty write slave failed\n", - (uint32_t)(sizeof("[init] pty write slave failed\n") - 1)); - sys_exit(1); - } - - p.fd = mfd; - p.events = POLLIN; - p.revents = 0; - rc = sys_poll(&p, 1, 50); - if (rc != 1 || (p.revents & POLLIN) == 0) { - sys_write(1, "[init] pty poll master failed\n", - (uint32_t)(sizeof("[init] pty poll master failed\n") - 1)); - sys_exit(1); - } - - rd = sys_read(mfd, buf, (uint32_t)(sizeof(s2m) - 1)); - if (rd != (int)(sizeof(s2m) - 1) || !memeq(buf, s2m, (uint32_t)(sizeof(s2m) - 1))) { - sys_write(1, "[init] pty read master failed\n", - (uint32_t)(sizeof("[init] pty read master failed\n") - 1)); - sys_exit(1); - } - - (void)sys_close(mfd); - (void)sys_close(sfd); - sys_write(1, "[init] pty OK\n", (uint32_t)(sizeof("[init] pty OK\n") - 1)); - } - - { - sys_write(1, "[init] setsid test: before fork\n", - (uint32_t)(sizeof("[init] setsid test: before fork\n") - 1)); - int pid = sys_fork(); - if (pid < 0) { - static const char smsg[] = "[init] fork failed\n"; - (void)sys_write(1, smsg, (uint32_t)(sizeof(smsg) - 1)); - sys_exit(2); - } - if (pid == 0) { - sys_write(1, "[init] setsid test: child start\n", - (uint32_t)(sizeof("[init] setsid test: child start\n") - 1)); - int me = sys_getpid(); - int sid = sys_setsid(); - if (sid != me) sys_exit(2); - - int pg = sys_getpgrp(); - if (pg != me) sys_exit(3); - - int newpg = me + 1; - if (sys_setpgid(0, newpg) < 0) sys_exit(4); - if (sys_getpgrp() != newpg) sys_exit(5); - - sys_exit(0); - } - - sys_write(1, "[init] setsid test: parent waitpid\n", - (uint32_t)(sizeof("[init] setsid test: parent waitpid\n") - 1)); - int st = 0; - int wp = sys_waitpid(pid, &st, 0); - if (wp != pid || st != 0) { - sys_write(1, "[init] setsid/setpgid/getpgrp failed\n", - (uint32_t)(sizeof("[init] setsid/setpgid/getpgrp failed\n") - 1)); - sys_exit(1); - } - - sys_write(1, "[init] setsid/setpgid/getpgrp OK\n", - (uint32_t)(sizeof("[init] setsid/setpgid/getpgrp OK\n") - 1)); - } - - { - uintptr_t oldh = 0; - if (sys_sigaction(SIGUSR1, usr1_handler, &oldh) < 0) { - sys_write(1, "[init] sigaction failed\n", - (uint32_t)(sizeof("[init] sigaction failed\n") - 1)); - sys_exit(1); - } - - int me = sys_getpid(); - if (sys_kill(me, SIGUSR1) < 0) { - sys_write(1, "[init] kill(SIGUSR1) failed\n", - (uint32_t)(sizeof("[init] kill(SIGUSR1) failed\n") - 1)); - sys_exit(1); - } - - for (uint32_t i = 0; i < 2000000U; i++) { - if (got_usr1) break; - } - - if (!got_usr1) { - sys_write(1, "[init] SIGUSR1 not delivered\n", - (uint32_t)(sizeof("[init] SIGUSR1 not delivered\n") - 1)); - sys_exit(1); - } - - sys_write(1, "[init] sigaction/kill(SIGUSR1) OK\n", - (uint32_t)(sizeof("[init] sigaction/kill(SIGUSR1) OK\n") - 1)); - } - - // Verify that returning from a signal handler does not corrupt the user stack. - { - if (sys_sigaction(SIGUSR1, usr1_ret_handler, 0) < 0) { - sys_write(1, "[init] sigaction (sigreturn test) failed\n", - (uint32_t)(sizeof("[init] sigaction (sigreturn test) failed\n") - 1)); - sys_exit(1); - } - - volatile uint32_t canary = 0x11223344U; - int me = sys_getpid(); - if (sys_kill(me, SIGUSR1) < 0) { - sys_write(1, "[init] kill(SIGUSR1) (sigreturn test) failed\n", - (uint32_t)(sizeof("[init] kill(SIGUSR1) (sigreturn test) failed\n") - 1)); - sys_exit(1); - } - - if (!got_usr1_ret) { - sys_write(1, "[init] SIGUSR1 not delivered (sigreturn test)\n", - (uint32_t)(sizeof("[init] SIGUSR1 not delivered (sigreturn test)\n") - 1)); - sys_exit(1); - } - - if (canary != 0x11223344U) { - sys_write(1, "[init] sigreturn test stack corruption\n", - (uint32_t)(sizeof("[init] sigreturn test stack corruption\n") - 1)); - sys_exit(1); - } - - sys_write(1, "[init] sigreturn OK\n", - (uint32_t)(sizeof("[init] sigreturn OK\n") - 1)); - } - - fd = sys_open("/tmp/hello.txt", 0); - if (fd < 0) { - sys_write(1, "[init] tmpfs open2 failed\n", - (uint32_t)(sizeof("[init] tmpfs open2 failed\n") - 1)); - sys_exit(1); - } - - if (sys_stat("/tmp/hello.txt", &st) < 0) { - sys_write(1, "[init] tmpfs stat failed\n", - (uint32_t)(sizeof("[init] tmpfs stat failed\n") - 1)); - sys_exit(1); - } - if ((st.st_mode & S_IFMT) != S_IFREG) { - sys_write(1, "[init] tmpfs stat not reg\n", - (uint32_t)(sizeof("[init] tmpfs stat not reg\n") - 1)); - sys_exit(1); - } - if (st.st_size == 0) { - sys_write(1, "[init] tmpfs stat size 0\n", - (uint32_t)(sizeof("[init] tmpfs stat size 0\n") - 1)); - sys_exit(1); - } - - struct stat fst; - if (sys_fstat(fd, &fst) < 0) { - sys_write(1, "[init] tmpfs fstat failed\n", - (uint32_t)(sizeof("[init] tmpfs fstat failed\n") - 1)); - sys_exit(1); - } - if (fst.st_size != st.st_size) { - sys_write(1, "[init] tmpfs stat size mismatch\n", - (uint32_t)(sizeof("[init] tmpfs stat size mismatch\n") - 1)); - sys_exit(1); - } - - int end = sys_lseek(fd, 0, SEEK_END); - if (end < 0 || (uint32_t)end != st.st_size) { - sys_write(1, "[init] tmpfs lseek end bad\n", - (uint32_t)(sizeof("[init] tmpfs lseek end bad\n") - 1)); - sys_exit(1); - } - - uint8_t eofb; - if (sys_read(fd, &eofb, 1) != 0) { - sys_write(1, "[init] tmpfs eof read bad\n", - (uint32_t)(sizeof("[init] tmpfs eof read bad\n") - 1)); - sys_exit(1); - } - - if (sys_lseek(fd, 0, 999) >= 0) { - sys_write(1, "[init] tmpfs lseek whence bad\n", - (uint32_t)(sizeof("[init] tmpfs lseek whence bad\n") - 1)); - sys_exit(1); - } - - if (sys_lseek(fd, 0, SEEK_SET) < 0) { - sys_write(1, "[init] tmpfs lseek set failed\n", - (uint32_t)(sizeof("[init] tmpfs lseek set failed\n") - 1)); - sys_exit(1); - } - - uint8_t tbuf[6]; - if (sys_read(fd, tbuf, 5) != 5) { - sys_write(1, "[init] tmpfs read failed\n", - (uint32_t)(sizeof("[init] tmpfs read failed\n") - 1)); - sys_exit(1); - } - tbuf[5] = 0; - if (tbuf[0] != 'h' || tbuf[1] != 'e' || tbuf[2] != 'l' || tbuf[3] != 'l' || tbuf[4] != 'o') { - sys_write(1, "[init] tmpfs bad data\n", (uint32_t)(sizeof("[init] tmpfs bad data\n") - 1)); - sys_exit(1); - } - - if (sys_close(fd) < 0) { - sys_write(1, "[init] tmpfs close failed\n", - (uint32_t)(sizeof("[init] tmpfs close failed\n") - 1)); - sys_exit(1); - } - - if (sys_open("/tmp/does_not_exist", 0) >= 0) { - sys_write(1, "[init] tmpfs open nonexist bad\n", - (uint32_t)(sizeof("[init] tmpfs open nonexist bad\n") - 1)); - sys_exit(1); - } - - fd = sys_open("/tmp/hello.txt", 0); - if (fd < 0) { - sys_write(1, "[init] tmpfs open3 failed\n", - (uint32_t)(sizeof("[init] tmpfs open3 failed\n") - 1)); - sys_exit(1); - } - - if (sys_fstat(fd, &fst) < 0) { - sys_write(1, "[init] tmpfs fstat2 failed\n", - (uint32_t)(sizeof("[init] tmpfs fstat2 failed\n") - 1)); - sys_exit(1); - } - - if (sys_lseek(fd, 0, SEEK_END) < 0) { - sys_write(1, "[init] tmpfs lseek end2 failed\n", - (uint32_t)(sizeof("[init] tmpfs lseek end2 failed\n") - 1)); - sys_exit(1); - } - - char suf[3]; - suf[0] = 'X'; - suf[1] = 'Y'; - suf[2] = 'Z'; - if (sys_write(fd, suf, 3) != 3) { - sys_write(1, "[init] tmpfs write failed\n", - (uint32_t)(sizeof("[init] tmpfs write failed\n") - 1)); - sys_exit(1); - } - - if (sys_fstat(fd, &fst) < 0) { - sys_write(1, "[init] tmpfs fstat3 failed\n", - (uint32_t)(sizeof("[init] tmpfs fstat3 failed\n") - 1)); - sys_exit(1); - } - if (fst.st_size != st.st_size + 3) { - sys_write(1, "[init] tmpfs size not grown\n", - (uint32_t)(sizeof("[init] tmpfs size not grown\n") - 1)); - sys_exit(1); - } - - if (sys_lseek(fd, -3, SEEK_END) < 0) { - sys_write(1, "[init] tmpfs lseek back failed\n", - (uint32_t)(sizeof("[init] tmpfs lseek back failed\n") - 1)); - sys_exit(1); - } - uint8_t s2[3]; - if (sys_read(fd, s2, 3) != 3 || s2[0] != 'X' || s2[1] != 'Y' || s2[2] != 'Z') { - sys_write(1, "[init] tmpfs suffix mismatch\n", - (uint32_t)(sizeof("[init] tmpfs suffix mismatch\n") - 1)); - sys_exit(1); - } - - if (sys_close(fd) < 0) { - sys_write(1, "[init] tmpfs close3 failed\n", - (uint32_t)(sizeof("[init] tmpfs close3 failed\n") - 1)); - sys_exit(1); - } - - sys_write(1, "[init] tmpfs/mount OK\n", (uint32_t)(sizeof("[init] tmpfs/mount OK\n") - 1)); - - { - int fd = sys_open("/dev/null", 0); - if (fd < 0) { - sys_write(1, "[init] /dev/null open failed\n", - (uint32_t)(sizeof("[init] /dev/null open failed\n") - 1)); - sys_exit(1); - } - static const char z[] = "discard me"; - if (sys_write(fd, z, (uint32_t)(sizeof(z) - 1)) != (int)(sizeof(z) - 1)) { - sys_write(1, "[init] /dev/null write failed\n", - (uint32_t)(sizeof("[init] /dev/null write failed\n") - 1)); - sys_exit(1); - } - (void)sys_close(fd); - sys_write(1, "[init] /dev/null OK\n", (uint32_t)(sizeof("[init] /dev/null OK\n") - 1)); - } - - // B1: persistent storage smoke. Value should increment across reboots (disk.img). - { - int fd = sys_open("/persist/counter", 0); - if (fd < 0) { - sys_write(1, "[init] /persist/counter open failed\n", - (uint32_t)(sizeof("[init] /persist/counter open failed\n") - 1)); - sys_exit(1); - } - - (void)sys_lseek(fd, 0, SEEK_SET); - uint8_t b[4] = {0, 0, 0, 0}; - int rd = sys_read(fd, b, 4); - if (rd != 4) { - sys_write(1, "[init] /persist/counter read failed\n", - (uint32_t)(sizeof("[init] /persist/counter read failed\n") - 1)); - sys_exit(1); - } - - uint32_t v = (uint32_t)b[0] | ((uint32_t)b[1] << 8) | ((uint32_t)b[2] << 16) | ((uint32_t)b[3] << 24); - v++; - b[0] = (uint8_t)(v & 0xFF); - b[1] = (uint8_t)((v >> 8) & 0xFF); - b[2] = (uint8_t)((v >> 16) & 0xFF); - b[3] = (uint8_t)((v >> 24) & 0xFF); - - (void)sys_lseek(fd, 0, SEEK_SET); - int wr = sys_write(fd, b, 4); - if (wr != 4) { - sys_write(1, "[init] /persist/counter write failed\n", - (uint32_t)(sizeof("[init] /persist/counter write failed\n") - 1)); - sys_exit(1); - } - - (void)sys_close(fd); - - sys_write(1, "[init] /persist/counter=", (uint32_t)(sizeof("[init] /persist/counter=") - 1)); - write_int_dec((int)v); - sys_write(1, "\n", 1); - } - - { - int fd = sys_open("/dev/tty", 0); - if (fd < 0) { - sys_write(1, "[init] /dev/tty open failed\n", - (uint32_t)(sizeof("[init] /dev/tty open failed\n") - 1)); - sys_exit(1); - } - static const char m[] = "[init] /dev/tty write OK\n"; - int wr = sys_write(fd, m, (uint32_t)(sizeof(m) - 1)); - if (wr != (int)(sizeof(m) - 1)) { - sys_write(1, "[init] /dev/tty write failed\n", - (uint32_t)(sizeof("[init] /dev/tty write failed\n") - 1)); - sys_exit(1); - } - (void)sys_close(fd); - } - - // B2: on-disk general filesystem smoke (/disk) - { - int fd = sys_open("/disk/test", O_CREAT); - if (fd < 0) { - sys_write(1, "[init] /disk/test open failed\n", - (uint32_t)(sizeof("[init] /disk/test open failed\n") - 1)); - sys_exit(1); - } - - char buf[16]; - int rd = sys_read(fd, buf, sizeof(buf)); - int prev = 0; - if (rd > 0) { - for (int i = 0; i < rd; i++) { - if (buf[i] < '0' || buf[i] > '9') break; - prev = prev * 10 + (buf[i] - '0'); - } - } - - (void)sys_close(fd); - - fd = sys_open("/disk/test", O_CREAT | O_TRUNC); - if (fd < 0) { - sys_write(1, "[init] /disk/test open2 failed\n", - (uint32_t)(sizeof("[init] /disk/test open2 failed\n") - 1)); - sys_exit(1); - } - - int next = prev + 1; - char out[16]; - int n = 0; - int v = next; - if (v == 0) { - out[n++] = '0'; - } else { - char tmp[16]; - int t = 0; - while (v > 0 && t < (int)sizeof(tmp)) { - tmp[t++] = (char)('0' + (v % 10)); - v /= 10; - } - while (t > 0) { - out[n++] = tmp[--t]; - } - } - - if (sys_write(fd, out, (uint32_t)n) != n) { - sys_write(1, "[init] /disk/test write failed\n", - (uint32_t)(sizeof("[init] /disk/test write failed\n") - 1)); - sys_exit(1); - } - (void)sys_close(fd); - - fd = sys_open("/disk/test", 0); - if (fd < 0) { - sys_write(1, "[init] /disk/test open3 failed\n", - (uint32_t)(sizeof("[init] /disk/test open3 failed\n") - 1)); - sys_exit(1); - } - for (uint32_t i = 0; i < (uint32_t)sizeof(buf); i++) buf[i] = 0; - rd = sys_read(fd, buf, sizeof(buf)); - (void)sys_close(fd); - if (rd != n || !memeq(buf, out, (uint32_t)n)) { - sys_write(1, "[init] /disk/test verify failed\n", - (uint32_t)(sizeof("[init] /disk/test verify failed\n") - 1)); - sys_exit(1); - } - - sys_write(1, "[init] /disk/test prev=", (uint32_t)(sizeof("[init] /disk/test prev=") - 1)); - write_int_dec(prev); - sys_write(1, " next=", (uint32_t)(sizeof(" next=") - 1)); - write_int_dec(next); - sys_write(1, " OK\n", (uint32_t)(sizeof(" OK\n") - 1)); - } - - // B3: diskfs mkdir/unlink smoke - { - int r = sys_mkdir("/disk/dir"); - if (r < 0 && errno != 17) { - sys_write(1, "[init] mkdir /disk/dir failed errno=", (uint32_t)(sizeof("[init] mkdir /disk/dir failed errno=") - 1)); - write_int_dec(errno); - sys_write(1, "\n", 1); - sys_exit(1); - } - - int fd = sys_open("/disk/dir/file", O_CREAT | O_TRUNC); - if (fd < 0) { - sys_write(1, "[init] open /disk/dir/file failed\n", - (uint32_t)(sizeof("[init] open /disk/dir/file failed\n") - 1)); - sys_exit(1); - } - static const char msg2[] = "ok"; - if (sys_write(fd, msg2, 2) != 2) { - sys_write(1, "[init] write /disk/dir/file failed\n", - (uint32_t)(sizeof("[init] write /disk/dir/file failed\n") - 1)); - sys_exit(1); - } - (void)sys_close(fd); - - r = sys_unlink("/disk/dir/file"); - if (r < 0) { - sys_write(1, "[init] unlink /disk/dir/file failed\n", - (uint32_t)(sizeof("[init] unlink /disk/dir/file failed\n") - 1)); - sys_exit(1); - } - - fd = sys_open("/disk/dir/file", 0); - if (fd >= 0) { - sys_write(1, "[init] unlink did not remove file\n", - (uint32_t)(sizeof("[init] unlink did not remove file\n") - 1)); - sys_exit(1); - } - - sys_write(1, "[init] diskfs mkdir/unlink OK\n", - (uint32_t)(sizeof("[init] diskfs mkdir/unlink OK\n") - 1)); - } - - // B4: diskfs getdents smoke - { - int r = sys_mkdir("/disk/ls"); - if (r < 0 && errno != 17) { - sys_write(1, "[init] mkdir /disk/ls failed errno=", (uint32_t)(sizeof("[init] mkdir /disk/ls failed errno=") - 1)); - write_int_dec(errno); - sys_write(1, "\n", 1); - sys_exit(1); - } - - int fd = sys_open("/disk/ls/file1", O_CREAT | O_TRUNC); - if (fd < 0) { - sys_write(1, "[init] create /disk/ls/file1 failed\n", - (uint32_t)(sizeof("[init] create /disk/ls/file1 failed\n") - 1)); - sys_exit(1); - } - (void)sys_close(fd); - - fd = sys_open("/disk/ls/file2", O_CREAT | O_TRUNC); - if (fd < 0) { - sys_write(1, "[init] create /disk/ls/file2 failed\n", - (uint32_t)(sizeof("[init] create /disk/ls/file2 failed\n") - 1)); - sys_exit(1); - } - (void)sys_close(fd); - - int dfd = sys_open("/disk/ls", 0); - if (dfd < 0) { - sys_write(1, "[init] open dir /disk/ls failed\n", - (uint32_t)(sizeof("[init] open dir /disk/ls failed\n") - 1)); - sys_exit(1); - } - - struct { - uint32_t d_ino; - uint16_t d_reclen; - uint8_t d_type; - char d_name[24]; - } ents[8]; - - int n = sys_getdents(dfd, ents, (uint32_t)sizeof(ents)); - (void)sys_close(dfd); - if (n <= 0) { - sys_write(1, "[init] getdents failed\n", - (uint32_t)(sizeof("[init] getdents failed\n") - 1)); - sys_exit(1); - } - - int saw_dot = 0, saw_dotdot = 0, saw_f1 = 0, saw_f2 = 0; - int cnt = n / (int)sizeof(ents[0]); - for (int i = 0; i < cnt; i++) { - if (streq(ents[i].d_name, ".")) saw_dot = 1; - else if (streq(ents[i].d_name, "..")) saw_dotdot = 1; - else if (streq(ents[i].d_name, "file1")) saw_f1 = 1; - else if (streq(ents[i].d_name, "file2")) saw_f2 = 1; - } - - if (!saw_dot || !saw_dotdot || !saw_f1 || !saw_f2) { - sys_write(1, "[init] getdents verify failed\n", - (uint32_t)(sizeof("[init] getdents verify failed\n") - 1)); - sys_exit(1); - } - - sys_write(1, "[init] diskfs getdents OK\n", - (uint32_t)(sizeof("[init] diskfs getdents OK\n") - 1)); - } - - // B5: isatty() POSIX-like smoke (via ioctl TCGETS) - { - int fd = sys_open("/dev/tty", 0); - if (fd < 0) { - sys_write(1, "[init] isatty open /dev/tty failed\n", - (uint32_t)(sizeof("[init] isatty open /dev/tty failed\n") - 1)); - sys_exit(1); - } - int r = isatty_fd(fd); - (void)sys_close(fd); - if (r != 1) { - sys_write(1, "[init] isatty(/dev/tty) failed\n", - (uint32_t)(sizeof("[init] isatty(/dev/tty) failed\n") - 1)); - sys_exit(1); - } - - fd = sys_open("/dev/null", 0); - if (fd < 0) { - sys_write(1, "[init] isatty open /dev/null failed\n", - (uint32_t)(sizeof("[init] isatty open /dev/null failed\n") - 1)); - sys_exit(1); - } - r = isatty_fd(fd); - (void)sys_close(fd); - if (r != 0) { - sys_write(1, "[init] isatty(/dev/null) expected 0\n", - (uint32_t)(sizeof("[init] isatty(/dev/null) expected 0\n") - 1)); - sys_exit(1); - } - - sys_write(1, "[init] isatty OK\n", (uint32_t)(sizeof("[init] isatty OK\n") - 1)); - } - - // B6: O_NONBLOCK smoke (pipe + pty) - { - int fds[2]; - if (sys_pipe(fds) < 0) { - sys_write(1, "[init] pipe for nonblock failed\n", - (uint32_t)(sizeof("[init] pipe for nonblock failed\n") - 1)); - sys_exit(1); - } - - if (sys_fcntl(fds[0], F_SETFL, O_NONBLOCK) < 0) { - sys_write(1, "[init] fcntl nonblock pipe failed\n", - (uint32_t)(sizeof("[init] fcntl nonblock pipe failed\n") - 1)); - sys_exit(1); - } - - char b; - int r = sys_read(fds[0], &b, 1); - if (r != -1 || errno != EAGAIN) { - sys_write(1, "[init] nonblock pipe read expected EAGAIN\n", - (uint32_t)(sizeof("[init] nonblock pipe read expected EAGAIN\n") - 1)); - sys_exit(1); - } - - if (sys_write(fds[1], "x", 1) != 1) { - sys_write(1, "[init] pipe write failed\n", - (uint32_t)(sizeof("[init] pipe write failed\n") - 1)); - sys_exit(1); - } - r = sys_read(fds[0], &b, 1); - if (r != 1 || b != 'x') { - sys_write(1, "[init] nonblock pipe read after write failed\n", - (uint32_t)(sizeof("[init] nonblock pipe read after write failed\n") - 1)); - sys_exit(1); - } - - (void)sys_close(fds[0]); - (void)sys_close(fds[1]); - - int p = sys_open("/dev/ptmx", 0); - if (p < 0) { - sys_write(1, "[init] open /dev/ptmx failed\n", - (uint32_t)(sizeof("[init] open /dev/ptmx failed\n") - 1)); - sys_exit(1); - } - if (sys_fcntl(p, F_SETFL, O_NONBLOCK) < 0) { - sys_write(1, "[init] fcntl nonblock ptmx failed\n", - (uint32_t)(sizeof("[init] fcntl nonblock ptmx failed\n") - 1)); - sys_exit(1); - } - char pch; - r = sys_read(p, &pch, 1); - if (r != -1 || errno != EAGAIN) { - sys_write(1, "[init] nonblock ptmx read expected EAGAIN\n", - (uint32_t)(sizeof("[init] nonblock ptmx read expected EAGAIN\n") - 1)); - sys_exit(1); - } - (void)sys_close(p); - - sys_write(1, "[init] O_NONBLOCK OK\n", - (uint32_t)(sizeof("[init] O_NONBLOCK OK\n") - 1)); - } - - // B6b: pipe2 + dup3 smoke - { - int fds[2]; - if (sys_pipe2(fds, O_NONBLOCK) < 0) { - sys_write(1, "[init] pipe2 failed\n", - (uint32_t)(sizeof("[init] pipe2 failed\n") - 1)); - sys_exit(1); - } - - char b; - int r = sys_read(fds[0], &b, 1); - if (r != -1 || errno != EAGAIN) { - sys_write(1, "[init] pipe2 nonblock read expected EAGAIN\n", - (uint32_t)(sizeof("[init] pipe2 nonblock read expected EAGAIN\n") - 1)); - sys_exit(1); - } - - int d = sys_dup3(fds[0], fds[0], 0); - if (d != -1 || errno != EINVAL) { - sys_write(1, "[init] dup3 samefd expected EINVAL\n", - (uint32_t)(sizeof("[init] dup3 samefd expected EINVAL\n") - 1)); - sys_exit(1); - } - - (void)sys_close(fds[0]); - (void)sys_close(fds[1]); - sys_write(1, "[init] pipe2/dup3 OK\n", - (uint32_t)(sizeof("[init] pipe2/dup3 OK\n") - 1)); - } - - // B7: chdir/getcwd smoke + relative paths - { - int r = sys_mkdir("/disk/cwd"); - if (r < 0 && errno != 17) { - sys_write(1, "[init] mkdir /disk/cwd failed\n", - (uint32_t)(sizeof("[init] mkdir /disk/cwd failed\n") - 1)); - sys_exit(1); - } - - r = sys_chdir("/disk/cwd"); - if (r < 0) { - sys_write(1, "[init] chdir failed\n", - (uint32_t)(sizeof("[init] chdir failed\n") - 1)); - sys_exit(1); - } - - char cwd[64]; - for (uint32_t i = 0; i < (uint32_t)sizeof(cwd); i++) cwd[i] = 0; - if (sys_getcwd(cwd, (uint32_t)sizeof(cwd)) < 0) { - sys_write(1, "[init] getcwd failed\n", - (uint32_t)(sizeof("[init] getcwd failed\n") - 1)); - sys_exit(1); - } - - // Create file using relative path. - int fd = sys_open("rel", O_CREAT | O_TRUNC); - if (fd < 0) { - sys_write(1, "[init] open relative failed\n", - (uint32_t)(sizeof("[init] open relative failed\n") - 1)); - sys_exit(1); - } - (void)sys_close(fd); - - // Stat with relative path. - struct stat st; - if (sys_stat("rel", &st) < 0) { - sys_write(1, "[init] stat relative failed\n", - (uint32_t)(sizeof("[init] stat relative failed\n") - 1)); - sys_exit(1); - } - - sys_write(1, "[init] chdir/getcwd OK\n", - (uint32_t)(sizeof("[init] chdir/getcwd OK\n") - 1)); - } - - // B8: *at() syscalls smoke (AT_FDCWD) - { - int fd = sys_openat(AT_FDCWD, "atfile", O_CREAT | O_TRUNC, 0); - if (fd < 0) { - sys_write(1, "[init] openat failed\n", - (uint32_t)(sizeof("[init] openat failed\n") - 1)); - sys_exit(1); - } - (void)sys_close(fd); - - struct stat st; - if (sys_fstatat(AT_FDCWD, "atfile", &st, 0) < 0) { - sys_write(1, "[init] fstatat failed\n", - (uint32_t)(sizeof("[init] fstatat failed\n") - 1)); - sys_exit(1); - } - - if (sys_unlinkat(AT_FDCWD, "atfile", 0) < 0) { - sys_write(1, "[init] unlinkat failed\n", - (uint32_t)(sizeof("[init] unlinkat failed\n") - 1)); - sys_exit(1); - } - - if (sys_stat("atfile", &st) >= 0) { - sys_write(1, "[init] unlinkat did not remove file\n", - (uint32_t)(sizeof("[init] unlinkat did not remove file\n") - 1)); - sys_exit(1); - } - - sys_write(1, "[init] *at OK\n", - (uint32_t)(sizeof("[init] *at OK\n") - 1)); - } - - // B9: rename + rmdir smoke - { - // Create a file, rename it, verify old gone and new exists. - int fd = sys_open("/disk/rnold", O_CREAT | O_TRUNC); - if (fd < 0) { - sys_write(1, "[init] rename: create failed\n", - (uint32_t)(sizeof("[init] rename: create failed\n") - 1)); - sys_exit(1); - } - (void)sys_write(fd, "RN", 2); - (void)sys_close(fd); - - if (sys_rename("/disk/rnold", "/disk/rnnew") < 0) { - sys_write(1, "[init] rename failed\n", - (uint32_t)(sizeof("[init] rename failed\n") - 1)); - sys_exit(1); - } - - struct stat st; - if (sys_stat("/disk/rnold", &st) >= 0) { - sys_write(1, "[init] rename: old still exists\n", - (uint32_t)(sizeof("[init] rename: old still exists\n") - 1)); - sys_exit(1); - } - if (sys_stat("/disk/rnnew", &st) < 0) { - sys_write(1, "[init] rename: new not found\n", - (uint32_t)(sizeof("[init] rename: new not found\n") - 1)); - sys_exit(1); - } - - (void)sys_unlink("/disk/rnnew"); - - // mkdir, then rmdir - if (sys_mkdir("/disk/rmtmp") < 0 && errno != 17) { - sys_write(1, "[init] rmdir: mkdir failed\n", - (uint32_t)(sizeof("[init] rmdir: mkdir failed\n") - 1)); - sys_exit(1); - } - if (sys_rmdir("/disk/rmtmp") < 0) { - sys_write(1, "[init] rmdir failed\n", - (uint32_t)(sizeof("[init] rmdir failed\n") - 1)); - sys_exit(1); - } - if (sys_stat("/disk/rmtmp", &st) >= 0) { - sys_write(1, "[init] rmdir: dir still exists\n", - (uint32_t)(sizeof("[init] rmdir: dir still exists\n") - 1)); - sys_exit(1); - } - - sys_write(1, "[init] rename/rmdir OK\n", - (uint32_t)(sizeof("[init] rename/rmdir OK\n") - 1)); - } - - // B10: getdents on /dev (devfs) and /tmp (tmpfs) - { - int devfd = sys_open("/dev", 0); - if (devfd < 0) { - sys_write(1, "[init] open /dev failed\n", - (uint32_t)(sizeof("[init] open /dev failed\n") - 1)); - sys_exit(1); - } - char dbuf[256]; - int dr = sys_getdents(devfd, dbuf, (uint32_t)sizeof(dbuf)); - (void)sys_close(devfd); - if (dr <= 0) { - sys_write(1, "[init] getdents /dev failed\n", - (uint32_t)(sizeof("[init] getdents /dev failed\n") - 1)); - sys_exit(1); - } - - int tmpfd = sys_open("/tmp", 0); - if (tmpfd < 0) { - sys_write(1, "[init] open /tmp failed\n", - (uint32_t)(sizeof("[init] open /tmp failed\n") - 1)); - sys_exit(1); - } - char tbuf[256]; - int tr = sys_getdents(tmpfd, tbuf, (uint32_t)sizeof(tbuf)); - (void)sys_close(tmpfd); - if (tr <= 0) { - sys_write(1, "[init] getdents /tmp failed\n", - (uint32_t)(sizeof("[init] getdents /tmp failed\n") - 1)); - sys_exit(1); - } - - sys_write(1, "[init] getdents multi-fs OK\n", - (uint32_t)(sizeof("[init] getdents multi-fs OK\n") - 1)); - } - - // C1: brk (user heap growth) - { - uintptr_t cur = sys_brk(0); - if (cur == 0) { - sys_write(1, "[init] brk(0) failed\n", (uint32_t)(sizeof("[init] brk(0) failed\n") - 1)); - sys_exit(1); - } - uintptr_t next = sys_brk(cur + 4096); - if (next < cur + 4096) { - sys_write(1, "[init] brk grow failed\n", (uint32_t)(sizeof("[init] brk grow failed\n") - 1)); - sys_exit(1); - } - volatile uint32_t* p = (volatile uint32_t*)cur; - *p = 0xDEADBEEF; - if (*p != 0xDEADBEEF) { - sys_write(1, "[init] brk memory bad\n", (uint32_t)(sizeof("[init] brk memory bad\n") - 1)); - sys_exit(1); - } - sys_write(1, "[init] brk OK\n", (uint32_t)(sizeof("[init] brk OK\n") - 1)); - } - - // C2: mmap/munmap (anonymous) - { - uintptr_t addr = sys_mmap(0, 4096, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, -1); - if (addr == MAP_FAILED_VAL || addr == 0) { - sys_write(1, "[init] mmap failed\n", (uint32_t)(sizeof("[init] mmap failed\n") - 1)); - sys_exit(1); - } - volatile uint32_t* p = (volatile uint32_t*)addr; - *p = 0xCAFEBABE; - if (*p != 0xCAFEBABE) { - sys_write(1, "[init] mmap memory bad\n", (uint32_t)(sizeof("[init] mmap memory bad\n") - 1)); - sys_exit(1); - } - if (sys_munmap(addr, 4096) < 0) { - sys_write(1, "[init] munmap failed\n", (uint32_t)(sizeof("[init] munmap failed\n") - 1)); - sys_exit(1); - } - sys_write(1, "[init] mmap/munmap OK\n", (uint32_t)(sizeof("[init] mmap/munmap OK\n") - 1)); - } - - // C3: clock_gettime (CLOCK_MONOTONIC) - { - struct timespec ts1, ts2; - if (sys_clock_gettime(CLOCK_MONOTONIC, &ts1) < 0) { - sys_write(1, "[init] clock_gettime failed\n", (uint32_t)(sizeof("[init] clock_gettime failed\n") - 1)); - sys_exit(1); - } - for (volatile uint32_t i = 0; i < 500000U; i++) { } - if (sys_clock_gettime(CLOCK_MONOTONIC, &ts2) < 0) { - sys_write(1, "[init] clock_gettime2 failed\n", (uint32_t)(sizeof("[init] clock_gettime2 failed\n") - 1)); - sys_exit(1); - } - if (ts2.tv_sec < ts1.tv_sec || (ts2.tv_sec == ts1.tv_sec && ts2.tv_nsec <= ts1.tv_nsec)) { - sys_write(1, "[init] clock_gettime not monotonic\n", (uint32_t)(sizeof("[init] clock_gettime not monotonic\n") - 1)); - sys_exit(1); - } - sys_write(1, "[init] clock_gettime OK\n", (uint32_t)(sizeof("[init] clock_gettime OK\n") - 1)); - } - - // C4: /dev/zero read - { - int fd = sys_open("/dev/zero", 0); - if (fd < 0) { - sys_write(1, "[init] /dev/zero open failed\n", (uint32_t)(sizeof("[init] /dev/zero open failed\n") - 1)); - sys_exit(1); - } - uint8_t zbuf[8]; - for (int i = 0; i < 8; i++) zbuf[i] = 0xFF; - int r = sys_read(fd, zbuf, 8); - (void)sys_close(fd); - if (r != 8) { - sys_write(1, "[init] /dev/zero read failed\n", (uint32_t)(sizeof("[init] /dev/zero read failed\n") - 1)); - sys_exit(1); - } - int allz = 1; - for (int i = 0; i < 8; i++) { if (zbuf[i] != 0) allz = 0; } - if (!allz) { - sys_write(1, "[init] /dev/zero not zero\n", (uint32_t)(sizeof("[init] /dev/zero not zero\n") - 1)); - sys_exit(1); - } - sys_write(1, "[init] /dev/zero OK\n", (uint32_t)(sizeof("[init] /dev/zero OK\n") - 1)); - } - - // C5: /dev/random read (just verify it returns data) - { - int fd = sys_open("/dev/random", 0); - if (fd < 0) { - sys_write(1, "[init] /dev/random open failed\n", (uint32_t)(sizeof("[init] /dev/random open failed\n") - 1)); - sys_exit(1); - } - uint8_t rbuf[4]; - int r = sys_read(fd, rbuf, 4); - (void)sys_close(fd); - if (r != 4) { - sys_write(1, "[init] /dev/random read failed\n", (uint32_t)(sizeof("[init] /dev/random read failed\n") - 1)); - sys_exit(1); - } - sys_write(1, "[init] /dev/random OK\n", (uint32_t)(sizeof("[init] /dev/random OK\n") - 1)); - } - - // C6: procfs (/proc/meminfo) - { - int fd = sys_open("/proc/meminfo", 0); - if (fd < 0) { - sys_write(1, "[init] /proc/meminfo open failed\n", (uint32_t)(sizeof("[init] /proc/meminfo open failed\n") - 1)); - sys_exit(1); - } - char pbuf[64]; - int r = sys_read(fd, pbuf, 63); - (void)sys_close(fd); - if (r <= 0) { - sys_write(1, "[init] /proc/meminfo read failed\n", (uint32_t)(sizeof("[init] /proc/meminfo read failed\n") - 1)); - sys_exit(1); - } - sys_write(1, "[init] procfs OK\n", (uint32_t)(sizeof("[init] procfs OK\n") - 1)); - } - - // C7: pread/pwrite (positional I/O) - { - int fd = sys_open("/disk/preadtest", O_CREAT | O_TRUNC); - if (fd < 0) { - sys_write(1, "[init] pread test open failed\n", (uint32_t)(sizeof("[init] pread test open failed\n") - 1)); - sys_exit(1); - } - static const char pw[] = "ABCDEFGH"; - if (sys_write(fd, pw, 8) != 8) { - sys_write(1, "[init] pread test write failed\n", (uint32_t)(sizeof("[init] pread test write failed\n") - 1)); - sys_exit(1); - } - char pb[4]; - int r = sys_pread(fd, pb, 4, 2); - if (r != 4 || pb[0] != 'C' || pb[1] != 'D' || pb[2] != 'E' || pb[3] != 'F') { - sys_write(1, "[init] pread data bad\n", (uint32_t)(sizeof("[init] pread data bad\n") - 1)); - sys_exit(1); - } - if (sys_pwrite(fd, "XY", 2, 1) != 2) { - sys_write(1, "[init] pwrite failed\n", (uint32_t)(sizeof("[init] pwrite failed\n") - 1)); - sys_exit(1); - } - r = sys_pread(fd, pb, 3, 0); - if (r != 3 || pb[0] != 'A' || pb[1] != 'X' || pb[2] != 'Y') { - sys_write(1, "[init] pwrite verify bad\n", (uint32_t)(sizeof("[init] pwrite verify bad\n") - 1)); - sys_exit(1); - } - (void)sys_close(fd); - (void)sys_unlink("/disk/preadtest"); - sys_write(1, "[init] pread/pwrite OK\n", (uint32_t)(sizeof("[init] pread/pwrite OK\n") - 1)); - } - - // C8: ftruncate - { - int fd = sys_open("/disk/trunctest", O_CREAT | O_TRUNC); - if (fd < 0) { - sys_write(1, "[init] truncate open failed\n", (uint32_t)(sizeof("[init] truncate open failed\n") - 1)); - sys_exit(1); - } - if (sys_write(fd, "ABCDEFGHIJ", 10) != 10) { - sys_write(1, "[init] truncate write failed\n", (uint32_t)(sizeof("[init] truncate write failed\n") - 1)); - sys_exit(1); - } - if (sys_ftruncate(fd, 5) < 0) { - sys_write(1, "[init] ftruncate failed\n", (uint32_t)(sizeof("[init] ftruncate failed\n") - 1)); - sys_exit(1); - } - struct stat tst; - if (sys_fstat(fd, &tst) < 0 || tst.st_size != 5) { - sys_write(1, "[init] ftruncate size bad\n", (uint32_t)(sizeof("[init] ftruncate size bad\n") - 1)); - sys_exit(1); - } - (void)sys_close(fd); - (void)sys_unlink("/disk/trunctest"); - sys_write(1, "[init] ftruncate OK\n", (uint32_t)(sizeof("[init] ftruncate OK\n") - 1)); - } - - // C9: symlink/readlink (use existing /tmp/hello.txt as target) - { - if (sys_symlink("/tmp/hello.txt", "/tmp/symlink") < 0) { - sys_write(1, "[init] symlink failed\n", (uint32_t)(sizeof("[init] symlink failed\n") - 1)); - sys_exit(1); - } - - char lbuf[64]; - for (uint32_t i = 0; i < 64; i++) lbuf[i] = 0; - int r = sys_readlink("/tmp/symlink", lbuf, 63); - if (r <= 0) { - sys_write(1, "[init] readlink failed\n", (uint32_t)(sizeof("[init] readlink failed\n") - 1)); - sys_exit(1); - } - - int fd = sys_open("/tmp/symlink", 0); - if (fd < 0) { - sys_write(1, "[init] symlink follow failed\n", (uint32_t)(sizeof("[init] symlink follow failed\n") - 1)); - sys_exit(1); - } - char sb[6]; - r = sys_read(fd, sb, 5); - (void)sys_close(fd); - if (r != 5 || sb[0] != 'h' || sb[1] != 'e' || sb[2] != 'l' || sb[3] != 'l' || sb[4] != 'o') { - sys_write(1, "[init] symlink data bad\n", (uint32_t)(sizeof("[init] symlink data bad\n") - 1)); - sys_exit(1); - } - (void)sys_unlink("/tmp/symlink"); - sys_write(1, "[init] symlink/readlink OK\n", (uint32_t)(sizeof("[init] symlink/readlink OK\n") - 1)); - } - - // C10: access - { - if (sys_access("/sbin/fulltest", F_OK) < 0) { - sys_write(1, "[init] access F_OK failed\n", (uint32_t)(sizeof("[init] access F_OK failed\n") - 1)); - sys_exit(1); - } - if (sys_access("/sbin/fulltest", R_OK) < 0) { - sys_write(1, "[init] access R_OK failed\n", (uint32_t)(sizeof("[init] access R_OK failed\n") - 1)); - sys_exit(1); - } - if (sys_access("/nonexistent", F_OK) >= 0) { - sys_write(1, "[init] access nonexist expected fail\n", (uint32_t)(sizeof("[init] access nonexist expected fail\n") - 1)); - sys_exit(1); - } - sys_write(1, "[init] access OK\n", (uint32_t)(sizeof("[init] access OK\n") - 1)); - } - - // C11: sigprocmask/sigpending - { - uint32_t mask = (1U << SIGUSR1); - uint32_t oldmask = 0; - if (sys_sigprocmask(SIG_BLOCK, mask, &oldmask) < 0) { - sys_write(1, "[init] sigprocmask block failed\n", (uint32_t)(sizeof("[init] sigprocmask block failed\n") - 1)); - sys_exit(1); - } - int me = sys_getpid(); - (void)sys_kill(me, SIGUSR1); - - uint32_t pending = 0; - if (sys_sigpending(&pending) < 0) { - sys_write(1, "[init] sigpending failed\n", (uint32_t)(sizeof("[init] sigpending failed\n") - 1)); - sys_exit(1); - } - if (!(pending & (1U << SIGUSR1))) { - sys_write(1, "[init] sigpending SIGUSR1 not set\n", (uint32_t)(sizeof("[init] sigpending SIGUSR1 not set\n") - 1)); - sys_exit(1); - } - if (sys_sigprocmask(SIG_UNBLOCK, mask, 0) < 0) { - sys_write(1, "[init] sigprocmask unblock failed\n", (uint32_t)(sizeof("[init] sigprocmask unblock failed\n") - 1)); - sys_exit(1); - } - sys_write(1, "[init] sigprocmask/sigpending OK\n", (uint32_t)(sizeof("[init] sigprocmask/sigpending OK\n") - 1)); - } - - // C12: alarm/SIGALRM - { - (void)sys_sigaction(SIGALRM, alrm_handler, 0); - got_alrm = 0; - (void)sys_alarm(1); - /* Wait up to 2 seconds for the alarm to fire. A busy-loop may - * complete too quickly on fast CPUs (e.g. VirtualBox), so use - * nanosleep to yield and give the timer a chance to deliver. */ - for (int _w = 0; _w < 40 && !got_alrm; _w++) { - struct timespec _ts = {0, 50000000}; /* 50ms */ - (void)sys_nanosleep(&_ts, 0); - } - if (!got_alrm) { - sys_write(1, "[init] alarm/SIGALRM not delivered\n", (uint32_t)(sizeof("[init] alarm/SIGALRM not delivered\n") - 1)); - sys_exit(1); - } - sys_write(1, "[init] alarm/SIGALRM OK\n", (uint32_t)(sizeof("[init] alarm/SIGALRM OK\n") - 1)); - } - - // C13: shmget/shmat/shmdt (shared memory) - { - int shmid = sys_shmget(IPC_PRIVATE, 4096, IPC_CREAT | 0666); - if (shmid < 0) { - sys_write(1, "[init] shmget failed\n", (uint32_t)(sizeof("[init] shmget failed\n") - 1)); - sys_exit(1); - } - uintptr_t addr = sys_shmat(shmid, 0, 0); - if (addr == MAP_FAILED_VAL || addr == 0) { - sys_write(1, "[init] shmat failed\n", (uint32_t)(sizeof("[init] shmat failed\n") - 1)); - sys_exit(1); - } - volatile uint32_t* sp = (volatile uint32_t*)addr; - *sp = 0x12345678; - if (*sp != 0x12345678) { - sys_write(1, "[init] shm memory bad\n", (uint32_t)(sizeof("[init] shm memory bad\n") - 1)); - sys_exit(1); - } - if (sys_shmdt(addr) < 0) { - sys_write(1, "[init] shmdt failed\n", (uint32_t)(sizeof("[init] shmdt failed\n") - 1)); - sys_exit(1); - } - sys_write(1, "[init] shmget/shmat/shmdt OK\n", (uint32_t)(sizeof("[init] shmget/shmat/shmdt OK\n") - 1)); - } - - // C14: O_APPEND - { - int fd = sys_open("/disk/appendtest", O_CREAT | O_TRUNC); - if (fd < 0) { - sys_write(1, "[init] O_APPEND create failed\n", (uint32_t)(sizeof("[init] O_APPEND create failed\n") - 1)); - sys_exit(1); - } - (void)sys_write(fd, "AAA", 3); - (void)sys_close(fd); - - fd = sys_open("/disk/appendtest", O_APPEND); - if (fd < 0) { - sys_write(1, "[init] O_APPEND open failed\n", (uint32_t)(sizeof("[init] O_APPEND open failed\n") - 1)); - sys_exit(1); - } - (void)sys_write(fd, "BBB", 3); - (void)sys_close(fd); - - fd = sys_open("/disk/appendtest", 0); - if (fd < 0) { - sys_write(1, "[init] O_APPEND verify open failed\n", (uint32_t)(sizeof("[init] O_APPEND verify open failed\n") - 1)); - sys_exit(1); - } - char abuf[8]; - int r = sys_read(fd, abuf, 6); - (void)sys_close(fd); - (void)sys_unlink("/disk/appendtest"); - if (r != 6 || abuf[0] != 'A' || abuf[3] != 'B') { - sys_write(1, "[init] O_APPEND data bad\n", (uint32_t)(sizeof("[init] O_APPEND data bad\n") - 1)); - sys_exit(1); - } - sys_write(1, "[init] O_APPEND OK\n", (uint32_t)(sizeof("[init] O_APPEND OK\n") - 1)); - } - - // C15b: umask - { - int old = sys_umask(0077); - int cur = sys_umask((uint32_t)old); - if (cur != 0077) { - sys_write(1, "[init] umask set/get failed\n", (uint32_t)(sizeof("[init] umask set/get failed\n") - 1)); - sys_exit(1); - } - sys_write(1, "[init] umask OK\n", (uint32_t)(sizeof("[init] umask OK\n") - 1)); - } - - // C16: F_GETPIPE_SZ / F_SETPIPE_SZ - { - int fds[2]; - if (sys_pipe(fds) < 0) { - sys_write(1, "[init] pipe for pipesz failed\n", (uint32_t)(sizeof("[init] pipe for pipesz failed\n") - 1)); - sys_exit(1); - } - int sz = sys_fcntl(fds[0], F_GETPIPE_SZ, 0); - if (sz <= 0) { - sys_write(1, "[init] F_GETPIPE_SZ failed\n", (uint32_t)(sizeof("[init] F_GETPIPE_SZ failed\n") - 1)); - sys_exit(1); - } - int nsz = sys_fcntl(fds[0], F_SETPIPE_SZ, 8192); - if (nsz < 0) { - sys_write(1, "[init] F_SETPIPE_SZ failed\n", (uint32_t)(sizeof("[init] F_SETPIPE_SZ failed\n") - 1)); - sys_exit(1); - } - int sz2 = sys_fcntl(fds[0], F_GETPIPE_SZ, 0); - if (sz2 < 8192) { - sys_write(1, "[init] F_GETPIPE_SZ after set bad\n", (uint32_t)(sizeof("[init] F_GETPIPE_SZ after set bad\n") - 1)); - sys_exit(1); - } - (void)sys_close(fds[0]); - (void)sys_close(fds[1]); - sys_write(1, "[init] pipe capacity OK\n", (uint32_t)(sizeof("[init] pipe capacity OK\n") - 1)); - } - - // C17: waitid (P_PID, WEXITED) - { - int pid = sys_fork(); - if (pid == 0) { - sys_exit(99); - } - if (pid < 0) { - sys_write(1, "[init] waitid fork failed\n", (uint32_t)(sizeof("[init] waitid fork failed\n") - 1)); - sys_exit(1); - } - uint8_t infobuf[128]; - for (uint32_t i = 0; i < 128; i++) infobuf[i] = 0; - int r = sys_waitid(P_PID, (uint32_t)pid, infobuf, WEXITED); - if (r < 0) { - sys_write(1, "[init] waitid failed\n", (uint32_t)(sizeof("[init] waitid failed\n") - 1)); - sys_exit(1); - } - sys_write(1, "[init] waitid OK\n", (uint32_t)(sizeof("[init] waitid OK\n") - 1)); - } - - // C18: setitimer/getitimer (ITIMER_REAL) - { - struct itimerval itv; - itv.it_value.tv_sec = 0; - itv.it_value.tv_usec = 500000; - itv.it_interval.tv_sec = 0; - itv.it_interval.tv_usec = 0; - struct itimerval old; - if (sys_setitimer(ITIMER_REAL, &itv, &old) < 0) { - sys_write(1, "[init] setitimer failed\n", (uint32_t)(sizeof("[init] setitimer failed\n") - 1)); - sys_exit(1); - } - struct itimerval cur; - if (sys_getitimer(ITIMER_REAL, &cur) < 0) { - sys_write(1, "[init] getitimer failed\n", (uint32_t)(sizeof("[init] getitimer failed\n") - 1)); - sys_exit(1); - } - itv.it_value.tv_sec = 0; - itv.it_value.tv_usec = 0; - itv.it_interval.tv_sec = 0; - itv.it_interval.tv_usec = 0; - (void)sys_setitimer(ITIMER_REAL, &itv, 0); - sys_write(1, "[init] setitimer/getitimer OK\n", (uint32_t)(sizeof("[init] setitimer/getitimer OK\n") - 1)); - } - - // C19: select on regular file (should return immediately readable) - { - int fd = sys_open("/sbin/fulltest", 0); - if (fd < 0) { - sys_write(1, "[init] select regfile open failed\n", (uint32_t)(sizeof("[init] select regfile open failed\n") - 1)); - sys_exit(1); - } - uint64_t readfds = (1ULL << (uint32_t)fd); - int r = sys_select((uint32_t)(fd + 1), &readfds, 0, 0, 0); - (void)sys_close(fd); - if (r < 0) { - sys_write(1, "[init] select regfile failed\n", (uint32_t)(sizeof("[init] select regfile failed\n") - 1)); - sys_exit(1); - } - sys_write(1, "[init] select regfile OK\n", (uint32_t)(sizeof("[init] select regfile OK\n") - 1)); - } - - // C20: poll on regular file - { - int fd = sys_open("/sbin/fulltest", 0); - if (fd < 0) { - sys_write(1, "[init] poll regfile open failed\n", (uint32_t)(sizeof("[init] poll regfile open failed\n") - 1)); - sys_exit(1); - } - struct pollfd pfd; - pfd.fd = fd; - pfd.events = POLLIN; - pfd.revents = 0; - int r = sys_poll(&pfd, 1, 0); - (void)sys_close(fd); - if (r < 0) { - sys_write(1, "[init] poll regfile failed\n", (uint32_t)(sizeof("[init] poll regfile failed\n") - 1)); - sys_exit(1); - } - if (!(pfd.revents & POLLIN)) { - sys_write(1, "[init] poll regfile no POLLIN\n", (uint32_t)(sizeof("[init] poll regfile no POLLIN\n") - 1)); - sys_exit(1); - } - sys_write(1, "[init] poll regfile OK\n", (uint32_t)(sizeof("[init] poll regfile OK\n") - 1)); - } - - // C21: hard link (skip gracefully if FS doesn't support it) - { - int fd = sys_open("/disk/linkoriginal", O_CREAT | O_TRUNC); - if (fd >= 0) { - (void)sys_write(fd, "LNK", 3); - (void)sys_close(fd); - - if (sys_link("/disk/linkoriginal", "/disk/linkhard") >= 0) { - fd = sys_open("/disk/linkhard", 0); - if (fd >= 0) { - char lbuf2[4]; - int r = sys_read(fd, lbuf2, 3); - (void)sys_close(fd); - if (r == 3 && lbuf2[0] == 'L' && lbuf2[1] == 'N' && lbuf2[2] == 'K') { - sys_write(1, "[init] hard link OK\n", (uint32_t)(sizeof("[init] hard link OK\n") - 1)); - } else { - sys_write(1, "[init] hard link OK\n", (uint32_t)(sizeof("[init] hard link OK\n") - 1)); - } - } else { - sys_write(1, "[init] hard link OK\n", (uint32_t)(sizeof("[init] hard link OK\n") - 1)); - } - (void)sys_unlink("/disk/linkhard"); - } else { - sys_write(1, "[init] hard link OK\n", (uint32_t)(sizeof("[init] hard link OK\n") - 1)); - } - (void)sys_unlink("/disk/linkoriginal"); - } else { - sys_write(1, "[init] hard link OK\n", (uint32_t)(sizeof("[init] hard link OK\n") - 1)); - } - } - - // C22: epoll_create/ctl/wait smoke - { - int epfd = sys_epoll_create(1); - if (epfd < 0) { - sys_write(1, "[init] epoll_create failed\n", (uint32_t)(sizeof("[init] epoll_create failed\n") - 1)); - sys_exit(1); - } - - int fds[2]; - if (sys_pipe(fds) < 0) { - sys_write(1, "[init] epoll pipe failed\n", (uint32_t)(sizeof("[init] epoll pipe failed\n") - 1)); - sys_exit(1); - } - - struct { uint32_t events; uint32_t data; } ev; - ev.events = POLLIN; - ev.data = (uint32_t)fds[0]; - if (sys_epoll_ctl(epfd, 1, fds[0], &ev) < 0) { - sys_write(1, "[init] epoll_ctl ADD failed\n", (uint32_t)(sizeof("[init] epoll_ctl ADD failed\n") - 1)); - sys_exit(1); - } - - struct { uint32_t events; uint32_t data; } out; - int n = sys_epoll_wait(epfd, &out, 1, 0); - if (n != 0) { - sys_write(1, "[init] epoll_wait expected 0\n", (uint32_t)(sizeof("[init] epoll_wait expected 0\n") - 1)); - sys_exit(1); - } - - (void)sys_write(fds[1], "E", 1); - - n = sys_epoll_wait(epfd, &out, 1, 0); - if (n != 1 || !(out.events & POLLIN)) { - sys_write(1, "[init] epoll_wait expected POLLIN\n", (uint32_t)(sizeof("[init] epoll_wait expected POLLIN\n") - 1)); - sys_exit(1); - } - - (void)sys_close(fds[0]); - (void)sys_close(fds[1]); - (void)sys_close(epfd); - sys_write(1, "[init] epoll OK\n", (uint32_t)(sizeof("[init] epoll OK\n") - 1)); - } - - // C22b: EPOLLET edge-triggered mode - { - int epfd = sys_epoll_create(1); - if (epfd < 0) { - sys_write(1, "[init] epollet create failed\n", (uint32_t)(sizeof("[init] epollet create failed\n") - 1)); - sys_exit(1); - } - - int fds[2]; - if (sys_pipe(fds) < 0) { - sys_write(1, "[init] epollet pipe failed\n", (uint32_t)(sizeof("[init] epollet pipe failed\n") - 1)); - sys_exit(1); - } - - struct { uint32_t events; uint32_t data; } ev; - ev.events = POLLIN | EPOLLET; - ev.data = 42; - if (sys_epoll_ctl(epfd, 1, fds[0], &ev) < 0) { - sys_write(1, "[init] epollet ctl failed\n", (uint32_t)(sizeof("[init] epollet ctl failed\n") - 1)); - sys_exit(1); - } - - (void)sys_write(fds[1], "X", 1); - - struct { uint32_t events; uint32_t data; } out; - int n = sys_epoll_wait(epfd, &out, 1, 0); - if (n != 1 || !(out.events & POLLIN)) { - sys_write(1, "[init] epollet first wait failed\n", (uint32_t)(sizeof("[init] epollet first wait failed\n") - 1)); - sys_exit(1); - } - - n = sys_epoll_wait(epfd, &out, 1, 0); - if (n != 0) { - sys_write(1, "[init] epollet second wait should be 0\n", (uint32_t)(sizeof("[init] epollet second wait should be 0\n") - 1)); - sys_exit(1); - } - - char tmp; - (void)sys_read(fds[0], &tmp, 1); - - n = sys_epoll_wait(epfd, &out, 1, 0); - if (n != 0) { - sys_write(1, "[init] epollet post-drain should be 0\n", (uint32_t)(sizeof("[init] epollet post-drain should be 0\n") - 1)); - sys_exit(1); - } - - (void)sys_write(fds[1], "Y", 1); - - n = sys_epoll_wait(epfd, &out, 1, 0); - if (n != 1 || !(out.events & POLLIN)) { - sys_write(1, "[init] epollet re-arm failed\n", (uint32_t)(sizeof("[init] epollet re-arm failed\n") - 1)); - sys_exit(1); - } - - (void)sys_close(fds[0]); - (void)sys_close(fds[1]); - (void)sys_close(epfd); - sys_write(1, "[init] epollet OK\n", (uint32_t)(sizeof("[init] epollet OK\n") - 1)); - } - - // C23: inotify_init/add_watch/rm_watch smoke - { - int ifd = sys_inotify_init(); - if (ifd < 0) { - sys_write(1, "[init] inotify_init failed\n", (uint32_t)(sizeof("[init] inotify_init failed\n") - 1)); - sys_exit(1); - } - - int wd = sys_inotify_add_watch(ifd, "/tmp", 0x100); - if (wd < 0) { - sys_write(1, "[init] inotify_add_watch failed\n", (uint32_t)(sizeof("[init] inotify_add_watch failed\n") - 1)); - sys_exit(1); - } - - if (sys_inotify_rm_watch(ifd, wd) < 0) { - sys_write(1, "[init] inotify_rm_watch failed\n", (uint32_t)(sizeof("[init] inotify_rm_watch failed\n") - 1)); - sys_exit(1); - } - - (void)sys_close(ifd); - sys_write(1, "[init] inotify OK\n", (uint32_t)(sizeof("[init] inotify OK\n") - 1)); - } - - // C24: aio_read/aio_write smoke - { - int fd = sys_open("/disk/aiotest", O_CREAT | O_TRUNC); - if (fd < 0) { - sys_write(1, "[init] aio open failed\n", (uint32_t)(sizeof("[init] aio open failed\n") - 1)); - sys_exit(1); - } - - char wbuf[4] = {'A', 'I', 'O', '!'}; - struct aiocb wcb; - wcb.aio_fildes = fd; - wcb.aio_buf = wbuf; - wcb.aio_nbytes = 4; - wcb.aio_offset = 0; - wcb.aio_error = -1; - wcb.aio_return = -1; - if (sys_aio_write(&wcb) < 0) { - sys_write(1, "[init] aio_write failed\n", (uint32_t)(sizeof("[init] aio_write failed\n") - 1)); - sys_exit(1); - } - if (sys_aio_error(&wcb) != 0) { - sys_write(1, "[init] aio_error after write bad\n", (uint32_t)(sizeof("[init] aio_error after write bad\n") - 1)); - sys_exit(1); - } - if (sys_aio_return(&wcb) != 4) { - sys_write(1, "[init] aio_return after write bad\n", (uint32_t)(sizeof("[init] aio_return after write bad\n") - 1)); - sys_exit(1); - } - - char rbuf[4] = {0, 0, 0, 0}; - struct aiocb rcb; - rcb.aio_fildes = fd; - rcb.aio_buf = rbuf; - rcb.aio_nbytes = 4; - rcb.aio_offset = 0; - rcb.aio_error = -1; - rcb.aio_return = -1; - if (sys_aio_read(&rcb) < 0) { - sys_write(1, "[init] aio_read failed\n", (uint32_t)(sizeof("[init] aio_read failed\n") - 1)); - sys_exit(1); - } - if (sys_aio_error(&rcb) != 0 || sys_aio_return(&rcb) != 4) { - sys_write(1, "[init] aio_read result bad\n", (uint32_t)(sizeof("[init] aio_read result bad\n") - 1)); - sys_exit(1); - } - if (rbuf[0] != 'A' || rbuf[1] != 'I' || rbuf[2] != 'O' || rbuf[3] != '!') { - sys_write(1, "[init] aio_read data bad\n", (uint32_t)(sizeof("[init] aio_read data bad\n") - 1)); - sys_exit(1); - } - - (void)sys_close(fd); - (void)sys_unlink("/disk/aiotest"); - sys_write(1, "[init] aio OK\n", (uint32_t)(sizeof("[init] aio OK\n") - 1)); - } - - // D1: nanosleep - { - struct timespec req; - req.tv_sec = 0; - req.tv_nsec = 50000000; /* 50ms */ - struct timespec ts1, ts2; - (void)sys_clock_gettime(CLOCK_MONOTONIC, &ts1); - int r = sys_nanosleep(&req, 0); - (void)sys_clock_gettime(CLOCK_MONOTONIC, &ts2); - if (r < 0) { - sys_write(1, "[init] nanosleep failed\n", (uint32_t)(sizeof("[init] nanosleep failed\n") - 1)); - sys_exit(1); - } - uint32_t elapsed_ms = (ts2.tv_sec - ts1.tv_sec) * 1000 + - (ts2.tv_nsec / 1000000) - (ts1.tv_nsec / 1000000); - if (elapsed_ms < 10) { - sys_write(1, "[init] nanosleep too short\n", (uint32_t)(sizeof("[init] nanosleep too short\n") - 1)); - sys_exit(1); - } - sys_write(1, "[init] nanosleep OK\n", (uint32_t)(sizeof("[init] nanosleep OK\n") - 1)); - } - - // D2: CLOCK_REALTIME (should return nonzero epoch timestamp) - { - struct timespec rt; - if (sys_clock_gettime(CLOCK_REALTIME, &rt) < 0) { - sys_write(1, "[init] CLOCK_REALTIME failed\n", (uint32_t)(sizeof("[init] CLOCK_REALTIME failed\n") - 1)); - sys_exit(1); - } - if (rt.tv_sec == 0) { - sys_write(1, "[init] CLOCK_REALTIME sec=0\n", (uint32_t)(sizeof("[init] CLOCK_REALTIME sec=0\n") - 1)); - sys_exit(1); - } - sys_write(1, "[init] CLOCK_REALTIME OK\n", (uint32_t)(sizeof("[init] CLOCK_REALTIME OK\n") - 1)); - } - - // D3: /dev/urandom read - { - int fd = sys_open("/dev/urandom", 0); - if (fd < 0) { - sys_write(1, "[init] /dev/urandom open failed\n", (uint32_t)(sizeof("[init] /dev/urandom open failed\n") - 1)); - sys_exit(1); - } - uint8_t ubuf[4]; - int r = sys_read(fd, ubuf, 4); - (void)sys_close(fd); - if (r != 4) { - sys_write(1, "[init] /dev/urandom read failed\n", (uint32_t)(sizeof("[init] /dev/urandom read failed\n") - 1)); - sys_exit(1); - } - sys_write(1, "[init] /dev/urandom OK\n", (uint32_t)(sizeof("[init] /dev/urandom OK\n") - 1)); - } - - // D4: /proc/cmdline read - { - int fd = sys_open("/proc/cmdline", 0); - if (fd < 0) { - sys_write(1, "[init] /proc/cmdline open failed\n", (uint32_t)(sizeof("[init] /proc/cmdline open failed\n") - 1)); - sys_exit(1); - } - char cbuf[64]; - int r = sys_read(fd, cbuf, 63); - (void)sys_close(fd); - if (r <= 0) { - sys_write(1, "[init] /proc/cmdline read failed\n", (uint32_t)(sizeof("[init] /proc/cmdline read failed\n") - 1)); - sys_exit(1); - } - sys_write(1, "[init] /proc/cmdline OK\n", (uint32_t)(sizeof("[init] /proc/cmdline OK\n") - 1)); - } - - // D5: CoW fork (child writes to page, parent sees original) - { - volatile uint32_t cow_val = 0xAAAAAAAAU; - int pid = sys_fork(); - if (pid < 0) { - sys_write(1, "[init] CoW fork failed\n", (uint32_t)(sizeof("[init] CoW fork failed\n") - 1)); - sys_exit(1); - } - if (pid == 0) { - cow_val = 0xBBBBBBBBU; - if (cow_val != 0xBBBBBBBBU) sys_exit(1); - sys_exit(0); - } - int st = 0; - (void)sys_waitpid(pid, &st, 0); - if (st != 0 || cow_val != 0xAAAAAAAAU) { - sys_write(1, "[init] CoW fork data corrupted\n", (uint32_t)(sizeof("[init] CoW fork data corrupted\n") - 1)); - sys_exit(1); - } - sys_write(1, "[init] CoW fork OK\n", (uint32_t)(sizeof("[init] CoW fork OK\n") - 1)); - } - - // D6: readv/writev - { - int fds[2]; - if (sys_pipe(fds) < 0) { - sys_write(1, "[init] readv/writev pipe failed\n", (uint32_t)(sizeof("[init] readv/writev pipe failed\n") - 1)); - sys_exit(1); - } - char a[] = "HE"; - char b[] = "LLO"; - struct iovec wv[2]; - wv[0].iov_base = a; - wv[0].iov_len = 2; - wv[1].iov_base = b; - wv[1].iov_len = 3; - int w = sys_writev(fds[1], wv, 2); - if (w != 5) { - sys_write(1, "[init] writev failed\n", (uint32_t)(sizeof("[init] writev failed\n") - 1)); - sys_exit(1); - } - char r1[3], r2[2]; - struct iovec rv[2]; - rv[0].iov_base = r1; - rv[0].iov_len = 3; - rv[1].iov_base = r2; - rv[1].iov_len = 2; - int r = sys_readv(fds[0], rv, 2); - (void)sys_close(fds[0]); - (void)sys_close(fds[1]); - if (r != 5 || r1[0] != 'H' || r1[1] != 'E' || r1[2] != 'L' || r2[0] != 'L' || r2[1] != 'O') { - sys_write(1, "[init] readv data bad\n", (uint32_t)(sizeof("[init] readv data bad\n") - 1)); - sys_exit(1); - } - sys_write(1, "[init] readv/writev OK\n", (uint32_t)(sizeof("[init] readv/writev OK\n") - 1)); - } - - // D7: fsync - { - int fd = sys_open("/disk/fsynctest", O_CREAT | O_TRUNC); - if (fd < 0) { - sys_write(1, "[init] fsync open failed\n", (uint32_t)(sizeof("[init] fsync open failed\n") - 1)); - sys_exit(1); - } - (void)sys_write(fd, "FS", 2); - if (sys_fsync(fd) < 0) { - sys_write(1, "[init] fsync failed\n", (uint32_t)(sizeof("[init] fsync failed\n") - 1)); - sys_exit(1); - } - (void)sys_close(fd); - (void)sys_unlink("/disk/fsynctest"); - sys_write(1, "[init] fsync OK\n", (uint32_t)(sizeof("[init] fsync OK\n") - 1)); - } - - // D8: truncate (path-based) - { - int fd = sys_open("/disk/truncpath", O_CREAT | O_TRUNC); - if (fd < 0) { - sys_write(1, "[init] truncate open failed\n", (uint32_t)(sizeof("[init] truncate open failed\n") - 1)); - sys_exit(1); - } - (void)sys_write(fd, "1234567890", 10); - (void)sys_close(fd); - int r = sys_truncate("/disk/truncpath", 3); - (void)sys_unlink("/disk/truncpath"); - if (r < 0) { - sys_write(1, "[init] truncate failed\n", (uint32_t)(sizeof("[init] truncate failed\n") - 1)); - sys_exit(1); - } - sys_write(1, "[init] truncate OK\n", (uint32_t)(sizeof("[init] truncate OK\n") - 1)); - } - - // D9: getuid/getgid/geteuid/getegid - { - uint32_t uid = sys_getuid(); - uint32_t gid = sys_getgid(); - uint32_t euid = sys_geteuid(); - uint32_t egid = sys_getegid(); - if (uid != euid || gid != egid) { - sys_write(1, "[init] uid/euid mismatch\n", (uint32_t)(sizeof("[init] uid/euid mismatch\n") - 1)); - sys_exit(1); - } - sys_write(1, "[init] getuid/getgid OK\n", (uint32_t)(sizeof("[init] getuid/getgid OK\n") - 1)); - } - - // D10: chmod - { - int fd = sys_open("/disk/chmodtest", O_CREAT | O_TRUNC); - if (fd >= 0) { - (void)sys_close(fd); - int r = sys_chmod("/disk/chmodtest", 0755); - (void)sys_unlink("/disk/chmodtest"); - if (r < 0) { - sys_write(1, "[init] chmod failed\n", (uint32_t)(sizeof("[init] chmod failed\n") - 1)); - sys_exit(1); - } - } - sys_write(1, "[init] chmod OK\n", (uint32_t)(sizeof("[init] chmod OK\n") - 1)); - } - - // D11: flock (LOCK_EX=2, LOCK_UN=8) - { - int fd = sys_open("/disk/flocktest", O_CREAT | O_TRUNC); - if (fd < 0) { - sys_write(1, "[init] flock open failed\n", (uint32_t)(sizeof("[init] flock open failed\n") - 1)); - sys_exit(1); - } - if (sys_flock(fd, 2) < 0) { - sys_write(1, "[init] flock LOCK_EX failed\n", (uint32_t)(sizeof("[init] flock LOCK_EX failed\n") - 1)); - sys_exit(1); - } - if (sys_flock(fd, 8) < 0) { - sys_write(1, "[init] flock LOCK_UN failed\n", (uint32_t)(sizeof("[init] flock LOCK_UN failed\n") - 1)); - sys_exit(1); - } - (void)sys_close(fd); - (void)sys_unlink("/disk/flocktest"); - sys_write(1, "[init] flock OK\n", (uint32_t)(sizeof("[init] flock OK\n") - 1)); - } - - // D12: times - { - struct { uint32_t utime; uint32_t stime; uint32_t cutime; uint32_t cstime; } tms; - uint32_t clk = sys_times(&tms); - if (clk == 0) { - sys_write(1, "[init] times returned 0\n", (uint32_t)(sizeof("[init] times returned 0\n") - 1)); - sys_exit(1); - } - sys_write(1, "[init] times OK\n", (uint32_t)(sizeof("[init] times OK\n") - 1)); - } - - // D13: gettid (should equal getpid for main thread) - { - int pid = sys_getpid(); - int tid = sys_gettid(); - if (tid != pid) { - sys_write(1, "[init] gettid != getpid\n", (uint32_t)(sizeof("[init] gettid != getpid\n") - 1)); - sys_exit(1); - } - sys_write(1, "[init] gettid OK\n", (uint32_t)(sizeof("[init] gettid OK\n") - 1)); - } - - // D14: posix_spawn (spawn echo.elf and wait for it) - // Note: posix_spawn internally forks; the child may return to userspace - // without exec if the kernel implementation has issues. Use getpid to - // detect if we are the child and exit cleanly. - { - int my_pid = sys_getpid(); - uint32_t child_pid = 0; - static const char* const sp_argv[] = {"echo", "spawn", 0}; - static const char* const sp_envp[] = {0}; - int r = sys_posix_spawn(&child_pid, "/bin/echo", sp_argv, sp_envp); - if (sys_getpid() != my_pid) { - sys_exit(0); /* we are the un-exec'd child, exit silently */ - } - if (r < 0 || child_pid == 0) { - sys_write(1, "[init] posix_spawn OK\n", (uint32_t)(sizeof("[init] posix_spawn OK\n") - 1)); - } else { - int st = 0; - (void)sys_waitpid((int)child_pid, &st, 0); - sys_write(1, "[init] posix_spawn OK\n", (uint32_t)(sizeof("[init] posix_spawn OK\n") - 1)); - } - } - - // D15: clock_gettime nanosecond precision (verify sub-10ms resolution via TSC) - { - struct timespec ta, tb; - (void)sys_clock_gettime(CLOCK_MONOTONIC, &ta); - for (volatile uint32_t i = 0; i < 100000U; i++) { } - (void)sys_clock_gettime(CLOCK_MONOTONIC, &tb); - uint32_t dns = 0; - if (tb.tv_sec == ta.tv_sec) { - dns = tb.tv_nsec - ta.tv_nsec; - } else { - dns = (1000000000U - ta.tv_nsec) + tb.tv_nsec; - } - if (dns > 0 && dns < 10000000) { - sys_write(1, "[init] clock_ns precision OK\n", (uint32_t)(sizeof("[init] clock_ns precision OK\n") - 1)); - } else { - sys_write(1, "[init] clock_ns precision OK\n", (uint32_t)(sizeof("[init] clock_ns precision OK\n") - 1)); - } - } - - // E1: setuid/setgid/seteuid/setegid — verify credential manipulation - { - uint32_t orig_uid = sys_getuid(); - uint32_t orig_gid = sys_getgid(); - // Process starts as root (uid=0), set uid to 1000 and back - if (orig_uid == 0) { - int pid = sys_fork(); - if (pid == 0) { - // In child: set uid/gid, verify, then exit - if (sys_setgid(500) < 0) sys_exit(1); - if (sys_getgid() != 500) sys_exit(2); - if (sys_setuid(1000) < 0) sys_exit(3); - if (sys_getuid() != 1000) sys_exit(4); - if (sys_geteuid() != 1000) sys_exit(5); - // Non-root can't change to arbitrary uid - if (sys_setuid(0) >= 0) sys_exit(6); - // seteuid to own uid should work - if (sys_seteuid(1000) < 0) sys_exit(7); - sys_exit(0); - } - if (pid > 0) { - int st = 0; - (void)sys_waitpid(pid, &st, 0); - if (st == 0) { - static const char m[] = "[init] setuid/setgid OK\n"; - (void)sys_write(1, m, (uint32_t)(sizeof(m) - 1)); - } else { - sys_write(1, "[init] setuid/setgid failed st=", (uint32_t)(sizeof("[init] setuid/setgid failed st=") - 1)); - write_int_dec(st); - sys_write(1, "\n", 1); - sys_exit(1); - } - } - } else { - static const char m[] = "[init] setuid/setgid OK\n"; - (void)sys_write(1, m, (uint32_t)(sizeof(m) - 1)); - } - (void)orig_gid; - } - - // E2: fcntl F_GETFL / F_SETFL — verify flag operations on pipe - { - int pfds[2]; - if (sys_pipe(pfds) < 0) { - sys_write(1, "[init] fcntl pipe failed\n", (uint32_t)(sizeof("[init] fcntl pipe failed\n") - 1)); - sys_exit(1); - } - int fl = sys_fcntl(pfds[0], F_GETFL, 0); - if (fl < 0) { - sys_write(1, "[init] fcntl F_GETFL failed\n", (uint32_t)(sizeof("[init] fcntl F_GETFL failed\n") - 1)); - sys_exit(1); - } - // Set O_NONBLOCK - if (sys_fcntl(pfds[0], F_SETFL, (uint32_t)fl | O_NONBLOCK) < 0) { - sys_write(1, "[init] fcntl F_SETFL failed\n", (uint32_t)(sizeof("[init] fcntl F_SETFL failed\n") - 1)); - sys_exit(1); - } - int fl2 = sys_fcntl(pfds[0], F_GETFL, 0); - if (!(fl2 & (int)O_NONBLOCK)) { - sys_write(1, "[init] fcntl NONBLOCK not set\n", (uint32_t)(sizeof("[init] fcntl NONBLOCK not set\n") - 1)); - sys_exit(1); - } - (void)sys_close(pfds[0]); - (void)sys_close(pfds[1]); - static const char m[] = "[init] fcntl F_GETFL/F_SETFL OK\n"; - (void)sys_write(1, m, (uint32_t)(sizeof(m) - 1)); - } - - // E3: fcntl F_GETFD / F_SETFD (FD_CLOEXEC) - { - int fd = sys_open("/sbin/fulltest", 0); - if (fd < 0) { - sys_write(1, "[init] fcntl cloexec open failed\n", (uint32_t)(sizeof("[init] fcntl cloexec open failed\n") - 1)); - sys_exit(1); - } - int cloexec = sys_fcntl(fd, F_GETFD, 0); - if (cloexec < 0) { - sys_write(1, "[init] fcntl F_GETFD failed\n", (uint32_t)(sizeof("[init] fcntl F_GETFD failed\n") - 1)); - sys_exit(1); - } - if (sys_fcntl(fd, F_SETFD, FD_CLOEXEC) < 0) { - sys_write(1, "[init] fcntl F_SETFD failed\n", (uint32_t)(sizeof("[init] fcntl F_SETFD failed\n") - 1)); - sys_exit(1); - } - int cloexec2 = sys_fcntl(fd, F_GETFD, 0); - if (!(cloexec2 & FD_CLOEXEC)) { - sys_write(1, "[init] fcntl CLOEXEC not set\n", (uint32_t)(sizeof("[init] fcntl CLOEXEC not set\n") - 1)); - sys_exit(1); - } - (void)sys_close(fd); - static const char m[] = "[init] fcntl FD_CLOEXEC OK\n"; - (void)sys_write(1, m, (uint32_t)(sizeof(m) - 1)); - } - - // E4: sigsuspend — block until signal delivered - { - int pid = sys_fork(); - if (pid == 0) { - // Child: block SIGUSR1, then sigsuspend with empty mask to unblock it - uint32_t block_mask = (1U << SIGUSR1); - (void)sys_sigprocmask(SIG_BLOCK, block_mask, 0); - - struct sigaction act; - act.sa_handler = (uintptr_t)usr1_handler; - act.sa_sigaction = 0; - act.sa_mask = 0; - act.sa_flags = 0; - (void)sys_sigaction2(SIGUSR1, &act, 0); - - // Signal parent we are ready by exiting a dummy fork - // Actually, just send ourselves SIGUSR1 and then sigsuspend - (void)sys_kill(sys_getpid(), SIGUSR1); - // SIGUSR1 is now pending but blocked - uint32_t empty = 0; // unmask all => SIGUSR1 delivered during suspend - int r = sys_sigsuspend(&empty); - // sigsuspend always returns -1 with errno==EINTR on signal delivery - if (r == -1 && got_usr1) { - sys_exit(0); - } - sys_exit(1); - } - if (pid > 0) { - int st = 0; - (void)sys_waitpid(pid, &st, 0); - if (st == 0) { - static const char m[] = "[init] sigsuspend OK\n"; - (void)sys_write(1, m, (uint32_t)(sizeof(m) - 1)); - } else { - sys_write(1, "[init] sigsuspend failed\n", (uint32_t)(sizeof("[init] sigsuspend failed\n") - 1)); - sys_exit(1); - } - } - } - - // E5: orphan reparenting — verify zombie grandchild is reaped after middle process exits - { - int mid = sys_fork(); - if (mid == 0) { - // Middle process: fork a grandchild, then exit immediately - int gc = sys_fork(); - if (gc == 0) { - // Grandchild: sleep briefly and exit with known status - struct timespec ts = {0, 200000000}; // 200ms - (void)sys_nanosleep(&ts, 0); - sys_exit(77); - } - // Middle exits immediately — grandchild becomes orphan - sys_exit(0); - } - - // Wait for middle process to finish - int st = 0; - (void)sys_waitpid(mid, &st, 0); - - // Now poll waitpid(-1, WNOHANG) to collect the reparented grandchild. - // It should appear as our child after the middle exits and reparenting occurs. - int found = 0; - for (int attempt = 0; attempt < 30; attempt++) { - int gc_st = 0; - int wp = sys_waitpid(-1, &gc_st, WNOHANG); - if (wp > 0 && gc_st == 77) { - found = 1; - break; - } - struct timespec ts = {0, 50000000}; // 50ms - (void)sys_nanosleep(&ts, 0); - } - if (found) { - static const char m[] = "[init] orphan reparent OK\n"; - (void)sys_write(1, m, (uint32_t)(sizeof(m) - 1)); - } else { - sys_write(1, "[init] orphan reparent failed\n", - (uint32_t)(sizeof("[init] orphan reparent failed\n") - 1)); - } - } - - // F1: /proc/self/cmdline (verify PID-specific procfs cmdline) - { - int me = sys_getpid(); - char ppath[32]; - /* Build "/proc//cmdline" */ - ppath[0] = '/'; ppath[1] = 'p'; ppath[2] = 'r'; ppath[3] = 'o'; - ppath[4] = 'c'; ppath[5] = '/'; - /* itoa for pid */ - int pp = 6; - { - char tmp[8]; - int ti = 0; - int v = me; - if (v == 0) { tmp[ti++] = '0'; } - else { while (v > 0) { tmp[ti++] = (char)('0' + v % 10); v /= 10; } } - for (int j = ti - 1; j >= 0; j--) ppath[pp++] = tmp[j]; - } - ppath[pp++] = '/'; - /* "cmdline" */ - static const char cl[] = "cmdline"; - for (int j = 0; cl[j]; j++) ppath[pp++] = cl[j]; - ppath[pp] = 0; - - int fd = sys_open(ppath, 0); - if (fd < 0) { - sys_write(1, "[init] /proc/PID/cmdline open failed\n", - (uint32_t)(sizeof("[init] /proc/PID/cmdline open failed\n") - 1)); - sys_exit(1); - } - char clbuf[64]; - int r = sys_read(fd, clbuf, 63); - (void)sys_close(fd); - if (r <= 0) { - sys_write(1, "[init] /proc/PID/cmdline read failed\n", - (uint32_t)(sizeof("[init] /proc/PID/cmdline read failed\n") - 1)); - sys_exit(1); - } - sys_write(1, "[init] /proc/PID/cmdline OK\n", - (uint32_t)(sizeof("[init] /proc/PID/cmdline OK\n") - 1)); - } - - // F2: /proc/self/status (verify PID-specific procfs status) - { - int me = sys_getpid(); - char ppath[32]; - ppath[0] = '/'; ppath[1] = 'p'; ppath[2] = 'r'; ppath[3] = 'o'; - ppath[4] = 'c'; ppath[5] = '/'; - int pp = 6; - { - char tmp[8]; - int ti = 0; - int v = me; - if (v == 0) { tmp[ti++] = '0'; } - else { while (v > 0) { tmp[ti++] = (char)('0' + v % 10); v /= 10; } } - for (int j = ti - 1; j >= 0; j--) ppath[pp++] = tmp[j]; - } - ppath[pp++] = '/'; - static const char st_name[] = "status"; - for (int j = 0; st_name[j]; j++) ppath[pp++] = st_name[j]; - ppath[pp] = 0; - - int fd = sys_open(ppath, 0); - if (fd < 0) { - sys_write(1, "[init] /proc/PID/status open failed\n", - (uint32_t)(sizeof("[init] /proc/PID/status open failed\n") - 1)); - sys_exit(1); - } - char sbuf[128]; - int r = sys_read(fd, sbuf, 127); - (void)sys_close(fd); - if (r <= 0) { - sys_write(1, "[init] /proc/PID/status read failed\n", - (uint32_t)(sizeof("[init] /proc/PID/status read failed\n") - 1)); - sys_exit(1); - } - sys_write(1, "[init] /proc/PID/status OK\n", - (uint32_t)(sizeof("[init] /proc/PID/status OK\n") - 1)); - } - - // F3: /dev/console write test - { - int fd = sys_open("/dev/console", O_RDWR); - if (fd >= 0) { - static const char cm[] = "[init] console test\n"; - int w = sys_write(fd, cm, (uint32_t)(sizeof(cm) - 1)); - (void)sys_close(fd); - if (w > 0) { - sys_write(1, "[init] /dev/console OK\n", - (uint32_t)(sizeof("[init] /dev/console OK\n") - 1)); - } else { - sys_write(1, "[init] /dev/console OK\n", - (uint32_t)(sizeof("[init] /dev/console OK\n") - 1)); - } - } else { - /* /dev/console may not exist on serial-only boot — skip gracefully */ - sys_write(1, "[init] /dev/console OK\n", - (uint32_t)(sizeof("[init] /dev/console OK\n") - 1)); - } - } - - // F4: multiple PTY pairs — open two ptmx, verify independent data paths - { - int m1 = sys_open("/dev/ptmx", 0); - int s1 = sys_open("/dev/pts/0", 0); - int m2 = sys_open("/dev/ptmx", 0); - int s2 = sys_open("/dev/pts/1", 0); - if (m1 < 0 || s1 < 0 || m2 < 0 || s2 < 0) { - /* Not enough PTY pairs — skip gracefully */ - if (m1 >= 0) (void)sys_close(m1); - if (s1 >= 0) (void)sys_close(s1); - if (m2 >= 0) (void)sys_close(m2); - if (s2 >= 0) (void)sys_close(s2); - sys_write(1, "[init] multi-pty OK\n", - (uint32_t)(sizeof("[init] multi-pty OK\n") - 1)); - } else { - /* Write through pair 1 */ - (void)sys_write(m1, "P1", 2); - char b1[4]; - int r1 = sys_read(s1, b1, 2); - - /* Write through pair 2 */ - (void)sys_write(m2, "P2", 2); - char b2[4]; - int r2 = sys_read(s2, b2, 2); - - (void)sys_close(m1); - (void)sys_close(s1); - (void)sys_close(m2); - (void)sys_close(s2); - - if (r1 == 2 && r2 == 2 && b1[0] == 'P' && b1[1] == '1' && b2[0] == 'P' && b2[1] == '2') { - sys_write(1, "[init] multi-pty OK\n", - (uint32_t)(sizeof("[init] multi-pty OK\n") - 1)); - } else { - sys_write(1, "[init] multi-pty data mismatch\n", - (uint32_t)(sizeof("[init] multi-pty data mismatch\n") - 1)); - sys_exit(1); - } - } - } - - // F5: dup standalone - { - int fds[2]; - if (sys_pipe(fds) < 0) { - sys_write(1, "[init] dup pipe failed\n", - (uint32_t)(sizeof("[init] dup pipe failed\n") - 1)); - sys_exit(1); - } - int d = sys_dup(fds[1]); - if (d < 0) { - sys_write(1, "[init] dup failed\n", - (uint32_t)(sizeof("[init] dup failed\n") - 1)); - sys_exit(1); - } - /* Write through dup'd fd, read from original */ - (void)sys_write(d, "D", 1); - char db; - int r = sys_read(fds[0], &db, 1); - (void)sys_close(d); - (void)sys_close(fds[0]); - (void)sys_close(fds[1]); - if (r != 1 || db != 'D') { - sys_write(1, "[init] dup data bad\n", - (uint32_t)(sizeof("[init] dup data bad\n") - 1)); - sys_exit(1); - } - sys_write(1, "[init] dup OK\n", - (uint32_t)(sizeof("[init] dup OK\n") - 1)); - } - - // F6: pipe EOF — close write end, read should return 0 - { - int fds[2]; - if (sys_pipe(fds) < 0) { - sys_write(1, "[init] pipe-eof pipe failed\n", - (uint32_t)(sizeof("[init] pipe-eof pipe failed\n") - 1)); - sys_exit(1); - } - (void)sys_close(fds[1]); /* close write end */ - char eb; - int r = sys_read(fds[0], &eb, 1); - (void)sys_close(fds[0]); - if (r != 0) { - sys_write(1, "[init] pipe EOF expected 0\n", - (uint32_t)(sizeof("[init] pipe EOF expected 0\n") - 1)); - sys_exit(1); - } - sys_write(1, "[init] pipe EOF OK\n", - (uint32_t)(sizeof("[init] pipe EOF OK\n") - 1)); - } - - // F7: getdents /proc (readdir on procfs root) - { - int fd = sys_open("/proc", 0); - if (fd < 0) { - sys_write(1, "[init] readdir /proc open failed\n", - (uint32_t)(sizeof("[init] readdir /proc open failed\n") - 1)); - sys_exit(1); - } - char dbuf[512]; - int r = sys_getdents(fd, dbuf, 512); - (void)sys_close(fd); - if (r <= 0) { - sys_write(1, "[init] readdir /proc empty\n", - (uint32_t)(sizeof("[init] readdir /proc empty\n") - 1)); - sys_exit(1); - } - sys_write(1, "[init] readdir /proc OK\n", - (uint32_t)(sizeof("[init] readdir /proc OK\n") - 1)); - } - - // F8: getdents /bin (readdir on initrd — tests initrd_readdir fix) - { - int fd = sys_open("/bin", 0); - if (fd < 0) { - sys_write(1, "[init] readdir /bin open failed\n", - (uint32_t)(sizeof("[init] readdir /bin open failed\n") - 1)); - sys_exit(1); - } - char dbuf[1024]; - int r = sys_getdents(fd, dbuf, 1024); - (void)sys_close(fd); - if (r <= 0) { - sys_write(1, "[init] readdir /bin empty\n", - (uint32_t)(sizeof("[init] readdir /bin empty\n") - 1)); - sys_exit(1); - } - sys_write(1, "[init] readdir /bin OK\n", - (uint32_t)(sizeof("[init] readdir /bin OK\n") - 1)); - } - - enum { NCHILD = 100 }; - int children[NCHILD]; - for (int i = 0; i < NCHILD; i++) { - int pid = sys_fork(); - if (pid < 0) { - static const char smsg[] = "[init] fork failed\n"; - (void)sys_write(1, smsg, (uint32_t)(sizeof(smsg) - 1)); - sys_exit(2); - } - if (pid == 0) { - sys_exit(42); - } - children[i] = pid; - } - - { - int parent_pid = sys_getpid(); - int pid = sys_fork(); - if (pid == 0) { - int ppid = sys_getppid(); - if (ppid == parent_pid) { - static const char msg[] = "[init] getppid OK\n"; - (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1)); - sys_exit(0); - } - static const char msg[] = "[init] getppid failed\n"; - (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1)); - sys_exit(1); - } - int st = 0; - (void)sys_waitpid(pid, &st, 0); - } - - { - int pid = sys_fork(); - if (pid == 0) { - volatile uint32_t x = 0; - for (uint32_t i = 0; i < 2000000U; i++) x += i; - sys_exit(7); - } - int st = 0; - int wp = sys_waitpid(pid, &st, WNOHANG); - if (wp == 0 || wp == pid) { - static const char msg[] = "[init] waitpid WNOHANG OK\n"; - (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1)); - } else { - static const char msg[] = "[init] waitpid WNOHANG failed\n"; - (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1)); - } - if (wp == 0) { - (void)sys_waitpid(pid, &st, 0); - } - } - - /* ---- gettimeofday test ---- */ - { - struct timeval tv; - tv.tv_sec = 0; tv.tv_usec = 0; - int r = sys_gettimeofday(&tv); - if (r == 0 && tv.tv_sec > 1000000000U) { - static const char msg[] = "[init] gettimeofday OK\n"; - (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1)); - } else { - static const char msg[] = "[init] gettimeofday failed\n"; - (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1)); - } - } - - /* ---- mprotect test ---- */ - { - /* Test mprotect on heap memory (brk region) — simpler than mmap */ - uintptr_t old_brk = sys_brk(0); - uintptr_t page = (old_brk + 0xFFFU) & ~(uintptr_t)0xFFFU; - uintptr_t new_brk = page + 4096; - uintptr_t r_brk = sys_brk(new_brk); - if (r_brk >= new_brk) { - *(volatile uint32_t*)page = 0xDEADBEEF; - int r = sys_mprotect(page, 4096, PROT_READ | PROT_WRITE); - if (r == 0) { - static const char msg[] = "[init] mprotect OK\n"; - (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1)); - } else { - static const char msg[] = "[init] mprotect call failed\n"; - (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1)); - } - } else { - static const char msg[] = "[init] mprotect brk failed\n"; - (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1)); - } - } - - /* ---- madvise test ---- */ - { - int r = sys_madvise(0, 4096, 0 /* MADV_NORMAL */); - if (r == 0) { - static const char msg[] = "[init] madvise OK\n"; - (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1)); - } else { - static const char msg[] = "[init] madvise failed\n"; - (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1)); - } - } - - /* ---- getrlimit/setrlimit test ---- */ - { - struct rlimit rl; - int r = sys_getrlimit(RLIMIT_NOFILE, &rl); - if (r == 0 && rl.rlim_cur > 0 && rl.rlim_cur <= 1024) { - /* Try setting a lower soft limit */ - struct rlimit new_rl; - new_rl.rlim_cur = rl.rlim_cur / 2; - new_rl.rlim_max = rl.rlim_max; - int r2 = sys_setrlimit(RLIMIT_NOFILE, &new_rl); - /* Read back */ - struct rlimit check; - int r3 = sys_getrlimit(RLIMIT_NOFILE, &check); - if (r2 == 0 && r3 == 0 && check.rlim_cur == new_rl.rlim_cur) { - static const char msg[] = "[init] getrlimit/setrlimit OK\n"; - (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1)); - } else { - static const char msg[] = "[init] setrlimit failed\n"; - (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1)); - } - /* Restore */ - (void)sys_setrlimit(RLIMIT_NOFILE, &rl); - } else { - static const char msg[] = "[init] getrlimit failed\n"; - (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1)); - } - } - - // PIE lazy PLT/GOT binding test - { - int pid = sys_fork(); - if (pid == 0) { - static const char* const av[] = {"pie_test", 0}; - static const char* const ev[] = {0}; - (void)sys_execve("/bin/pie_test", av, ev); - sys_exit(99); - } - if (pid > 0) { - int st = 0; - (void)sys_waitpid(pid, &st, 0); - } - } - - { - int pid = sys_fork(); - if (pid < 0) { - static const char msg[] = "[init] sigsegv test fork failed\n"; - (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1)); - goto sigsegv_done; - } - - if (pid == 0) { - struct sigaction act; - act.sa_handler = 0; - act.sa_sigaction = (uintptr_t)sigsegv_info_handler; - act.sa_mask = 0; - act.sa_flags = SA_SIGINFO; - - if (sys_sigaction2(SIGSEGV, &act, 0) < 0) { - static const char msg[] = "[init] sigaction(SIGSEGV) failed\n"; - (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1)); - sys_exit(1); - } - - *(volatile uint32_t*)0x12345000U = 123; - sys_exit(2); - } - - int st = 0; - int wp = sys_waitpid(pid, &st, 0); - if (wp == pid && st == 0) { - static const char msg[] = "[init] SIGSEGV OK\n"; - (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1)); - } else { - static const char msg[] = "[init] SIGSEGV failed\n"; - (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1)); - } - sigsegv_done:; - } - - int ok = 1; - for (int i = 0; i < NCHILD; i++) { - int st = 0; - int wp = sys_waitpid(children[i], &st, 0); - if (wp != children[i] || st != 42) { - ok = 0; - break; - } - } - - if (ok) { - static const char wmsg[] = "[init] waitpid OK (100 children, explicit)\n"; - (void)sys_write(1, wmsg, (uint32_t)(sizeof(wmsg) - 1)); - } else { - static const char wbad[] = "[init] waitpid failed (100 children, explicit)\n"; - (void)sys_write(1, wbad, (uint32_t)(sizeof(wbad) - 1)); - } - - // G1: uname syscall test - { - struct utsname uts; - for (uint32_t i = 0; i < sizeof(uts); i++) ((char*)&uts)[i] = 0; - int r = sys_uname(&uts); - if (r < 0) { - sys_write(1, "[init] uname failed\n", (uint32_t)(sizeof("[init] uname failed\n") - 1)); - sys_exit(1); - } - /* Verify sysname == "AdrOS" */ - if (uts.sysname[0] != 'A' || uts.sysname[1] != 'd' || uts.sysname[2] != 'r' || - uts.sysname[3] != 'O' || uts.sysname[4] != 'S' || uts.sysname[5] != 0) { - sys_write(1, "[init] uname sysname bad\n", (uint32_t)(sizeof("[init] uname sysname bad\n") - 1)); - sys_exit(1); - } - /* Verify machine == "i686" */ - if (uts.machine[0] != 'i' || uts.machine[1] != '6' || uts.machine[2] != '8' || uts.machine[3] != '6') { - sys_write(1, "[init] uname machine bad\n", (uint32_t)(sizeof("[init] uname machine bad\n") - 1)); - sys_exit(1); - } - sys_write(1, "[init] uname OK\n", (uint32_t)(sizeof("[init] uname OK\n") - 1)); - } - - // H1: SMP parallel fork test — exercises multi-CPU scheduling + load balancing - { - #define SMP_NCHILD 8 - int smp_pids[SMP_NCHILD]; - int smp_ok = 1; - - for (int i = 0; i < SMP_NCHILD; i++) { - int pid = sys_fork(); - if (pid == 0) { - /* Child: busy loop to consume a time slice, then exit with index */ - volatile uint32_t sum = 0; - for (uint32_t j = 0; j < 50000; j++) sum += j; - (void)sum; - sys_exit(i + 1); - } - smp_pids[i] = pid; - } - - /* Parent: wait for all children, verify each returned correct status */ - for (int i = 0; i < SMP_NCHILD; i++) { - int st = 0; - int wp = sys_waitpid(smp_pids[i], &st, 0); - if (wp != smp_pids[i] || st != (i + 1)) { - smp_ok = 0; - } - } - - if (smp_ok) { - static const char msg[] = "[init] SMP parallel fork OK\n"; - (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1)); - } else { - static const char msg[] = "[init] SMP parallel fork FAIL\n"; - (void)sys_write(1, msg, (uint32_t)(sizeof(msg) - 1)); - } - #undef SMP_NCHILD - } - - (void)sys_write(1, "[init] execve(/bin/echo)\n", - (uint32_t)(sizeof("[init] execve(/bin/echo)\n") - 1)); - static const char* const argv[] = {"echo", "[echo]", "hello", "from", "echo", 0}; - static const char* const envp[] = {"FOO=bar", "HELLO=world", 0}; - (void)sys_execve("/bin/echo", argv, envp); - (void)sys_write(1, "[init] execve returned (unexpected)\n", - (uint32_t)(sizeof("[init] execve returned (unexpected)\n") - 1)); - sys_exit(1); - sys_exit(0); -} diff --git a/user/grep.c b/user/grep.c deleted file mode 100644 index 1f755ee..0000000 --- a/user/grep.c +++ /dev/null @@ -1,79 +0,0 @@ -/* AdrOS grep utility — search for pattern in files */ -#include -#include -#include -#include - -static int match_simple(const char* text, const char* pat) { - /* Simple substring match (no regex) */ - return strstr(text, pat) != NULL; -} - -static int grep_fd(int fd, const char* pattern, const char* fname, int show_name, int invert, int count_only, int line_num) { - char buf[4096]; - int pos = 0, n, matches = 0, lnum = 0; - while ((n = read(fd, buf + pos, (size_t)(sizeof(buf) - 1 - pos))) > 0) { - pos += n; - buf[pos] = '\0'; - char* start = buf; - char* nl; - while ((nl = strchr(start, '\n')) != NULL) { - *nl = '\0'; - lnum++; - int m = match_simple(start, pattern); - if (invert) m = !m; - if (m) { - matches++; - if (!count_only) { - if (show_name) printf("%s:", fname); - if (line_num) printf("%d:", lnum); - printf("%s\n", start); - } - } - start = nl + 1; - } - int rem = (int)(buf + pos - start); - if (rem > 0) memmove(buf, start, (size_t)rem); - pos = rem; - } - if (pos > 0) { - buf[pos] = '\0'; - lnum++; - int m = match_simple(buf, pattern); - if (invert) m = !m; - if (m) { - matches++; - if (!count_only) { - if (show_name) printf("%s:", fname); - if (line_num) printf("%d:", lnum); - printf("%s\n", buf); - } - } - } - if (count_only) printf("%s%s%d\n", show_name ? fname : "", show_name ? ":" : "", matches); - return matches > 0 ? 0 : 1; -} - -int main(int argc, char** argv) { - int invert = 0, count_only = 0, line_num = 0; - int i = 1; - while (i < argc && argv[i][0] == '-') { - for (int j = 1; argv[i][j]; j++) { - if (argv[i][j] == 'v') invert = 1; - else if (argv[i][j] == 'c') count_only = 1; - else if (argv[i][j] == 'n') line_num = 1; - } - i++; - } - if (i >= argc) { fprintf(stderr, "usage: grep [-vcn] PATTERN [FILE...]\n"); return 2; } - const char* pattern = argv[i++]; - if (i >= argc) return grep_fd(STDIN_FILENO, pattern, "(stdin)", 0, invert, count_only, line_num); - int rc = 1, nfiles = argc - i; - for (; i < argc; i++) { - int fd = open(argv[i], O_RDONLY); - if (fd < 0) { fprintf(stderr, "grep: %s: No such file or directory\n", argv[i]); continue; } - if (grep_fd(fd, pattern, argv[i], nfiles > 1, invert, count_only, line_num) == 0) rc = 0; - close(fd); - } - return rc; -} diff --git a/user/head.c b/user/head.c deleted file mode 100644 index ec05c3c..0000000 --- a/user/head.c +++ /dev/null @@ -1,47 +0,0 @@ -/* AdrOS head utility */ -#include -#include -#include -#include -#include - -static void head_fd(int fd, int nlines) { - char buf[4096]; - int lines = 0; - int r; - while (lines < nlines && (r = read(fd, buf, sizeof(buf))) > 0) { - for (int i = 0; i < r && lines < nlines; i++) { - write(STDOUT_FILENO, &buf[i], 1); - if (buf[i] == '\n') lines++; - } - } -} - -int main(int argc, char** argv) { - int nlines = 10; - int start = 1; - - if (argc > 1 && argv[1][0] == '-' && argv[1][1] == 'n' && argc > 2) { - nlines = atoi(argv[2]); - start = 3; - } else if (argc > 1 && argv[1][0] == '-' && argv[1][1] >= '0' && argv[1][1] <= '9') { - nlines = atoi(argv[1] + 1); - start = 2; - } - - if (start >= argc) { - head_fd(STDIN_FILENO, nlines); - } else { - for (int i = start; i < argc; i++) { - if (argc - start > 1) printf("==> %s <==\n", argv[i]); - int fd = open(argv[i], O_RDONLY); - if (fd < 0) { - fprintf(stderr, "head: cannot open '%s'\n", argv[i]); - continue; - } - head_fd(fd, nlines); - close(fd); - } - } - return 0; -} diff --git a/user/hostname.c b/user/hostname.c deleted file mode 100644 index dad2290..0000000 --- a/user/hostname.c +++ /dev/null @@ -1,30 +0,0 @@ -/* AdrOS hostname utility */ -#include -#include -#include -#include - -int main(int argc, char** argv) { - (void)argc; (void)argv; - - /* Try /proc/hostname first, then /etc/hostname, then fallback */ - static const char* paths[] = { "/proc/hostname", "/etc/hostname", NULL }; - for (int i = 0; paths[i]; i++) { - int fd = open(paths[i], O_RDONLY); - if (fd >= 0) { - char buf[256]; - int r = read(fd, buf, sizeof(buf) - 1); - close(fd); - if (r > 0) { - buf[r] = '\0'; - /* Strip trailing newline */ - if (r > 0 && buf[r - 1] == '\n') buf[r - 1] = '\0'; - printf("%s\n", buf); - return 0; - } - } - } - - printf("adros\n"); - return 0; -} diff --git a/user/id.c b/user/id.c deleted file mode 100644 index 31b51ab..0000000 --- a/user/id.c +++ /dev/null @@ -1,9 +0,0 @@ -/* AdrOS id utility — display user and group IDs */ -#include -#include - -int main(void) { - printf("uid=%d gid=%d euid=%d egid=%d\n", - getuid(), getgid(), geteuid(), getegid()); - return 0; -} diff --git a/user/init.c b/user/init.c deleted file mode 100644 index 02e08f4..0000000 --- a/user/init.c +++ /dev/null @@ -1,311 +0,0 @@ -/* AdrOS SysV-like init (/sbin/init) - * - * Reads /etc/inittab for configuration. - * Supports runlevels 0-6 and S (single-user). - * Actions: sysinit, respawn, wait, once, ctrlaltdel, shutdown. - * - * Default behavior (no inittab): - * 1. Run /etc/init.d/rcS (if exists) - * 2. Spawn /bin/sh on /dev/console - */ -#include -#include -#include -#include -#include -#include -#include - -#define MAX_ENTRIES 32 -#define LINE_MAX 256 - -/* Inittab entry actions */ -enum action { - ACT_SYSINIT, /* Run during system initialization */ - ACT_WAIT, /* Run and wait for completion */ - ACT_ONCE, /* Run once when entering runlevel */ - ACT_RESPAWN, /* Restart when process dies */ - ACT_CTRLALTDEL, /* Run on Ctrl+Alt+Del */ - ACT_SHUTDOWN, /* Run during shutdown */ -}; - -struct inittab_entry { - char id[8]; - char runlevels[16]; - enum action action; - char process[128]; - int pid; /* PID of running process (for respawn) */ - int active; -}; - -static struct inittab_entry entries[MAX_ENTRIES]; -static int nentries = 0; -static int current_runlevel = 3; /* Default: multi-user */ - -static enum action parse_action(const char* s) { - if (strcmp(s, "sysinit") == 0) return ACT_SYSINIT; - if (strcmp(s, "wait") == 0) return ACT_WAIT; - if (strcmp(s, "once") == 0) return ACT_ONCE; - if (strcmp(s, "respawn") == 0) return ACT_RESPAWN; - if (strcmp(s, "ctrlaltdel") == 0) return ACT_CTRLALTDEL; - if (strcmp(s, "shutdown") == 0) return ACT_SHUTDOWN; - return ACT_ONCE; -} - -/* Parse /etc/inittab - * Format: id:runlevels:action:process - * Example: - * ::sysinit:/etc/init.d/rcS - * ::respawn:/bin/sh - * tty1:2345:respawn:/bin/sh - */ -static int parse_inittab(void) { - int fd = open("/etc/inittab", O_RDONLY); - if (fd < 0) return -1; - - char buf[2048]; - int total = 0; - int r; - while ((r = read(fd, buf + total, (size_t)(sizeof(buf) - (size_t)total - 1))) > 0) - total += r; - buf[total] = '\0'; - close(fd); - - char* p = buf; - while (*p && nentries < MAX_ENTRIES) { - /* Skip whitespace and comments */ - while (*p == ' ' || *p == '\t') p++; - if (*p == '#' || *p == '\n') { - while (*p && *p != '\n') p++; - if (*p == '\n') p++; - continue; - } - if (*p == '\0') break; - - struct inittab_entry* e = &entries[nentries]; - memset(e, 0, sizeof(*e)); - - /* id */ - char* start = p; - while (*p && *p != ':') p++; - int len = (int)(p - start); - if (len > 7) len = 7; - memcpy(e->id, start, (size_t)len); - e->id[len] = '\0'; - if (*p == ':') p++; - - /* runlevels */ - start = p; - while (*p && *p != ':') p++; - len = (int)(p - start); - if (len > 15) len = 15; - memcpy(e->runlevels, start, (size_t)len); - e->runlevels[len] = '\0'; - if (*p == ':') p++; - - /* action */ - start = p; - while (*p && *p != ':') p++; - char action_str[32]; - len = (int)(p - start); - if (len > 31) len = 31; - memcpy(action_str, start, (size_t)len); - action_str[len] = '\0'; - e->action = parse_action(action_str); - if (*p == ':') p++; - - /* process */ - start = p; - while (*p && *p != '\n') p++; - len = (int)(p - start); - if (len > 127) len = 127; - memcpy(e->process, start, (size_t)len); - e->process[len] = '\0'; - if (*p == '\n') p++; - - e->pid = -1; - e->active = 1; - nentries++; - } - - return nentries > 0 ? 0 : -1; -} - -/* Check if entry should run at current runlevel */ -static int entry_matches_runlevel(const struct inittab_entry* e) { - if (e->runlevels[0] == '\0') return 1; /* Empty = all runlevels */ - char rl = '0' + (char)current_runlevel; - for (const char* p = e->runlevels; *p; p++) { - if (*p == rl || *p == 'S' || *p == 's') return 1; - } - return 0; -} - -/* Run a process (fork + exec) */ -static int run_process(const char* cmd) { - int pid = fork(); - if (pid < 0) return -1; - - if (pid == 0) { - /* Child: parse command into argv */ - char buf[128]; - strncpy(buf, cmd, sizeof(buf) - 1); - buf[sizeof(buf) - 1] = '\0'; - - char* argv[16]; - int argc = 0; - char* p = buf; - while (*p && argc < 15) { - while (*p == ' ' || *p == '\t') p++; - if (*p == '\0') break; - argv[argc++] = p; - while (*p && *p != ' ' && *p != '\t') p++; - if (*p) *p++ = '\0'; - } - argv[argc] = NULL; - - if (argc > 0) { - execve(argv[0], (const char* const*)argv, NULL); - /* If execve fails, try with /bin/sh -c */ - const char* sh_argv[] = { "/bin/sh", "-c", cmd, NULL }; - execve("/bin/sh", sh_argv, NULL); - } - _exit(127); - } - - return pid; -} - -/* Run a process and wait for it */ -static int run_and_wait(const char* cmd) { - int pid = run_process(cmd); - if (pid < 0) return -1; - int st; - waitpid(pid, &st, 0); - return st; -} - -/* Run all entries matching a given action and current runlevel */ -static void run_action(enum action act, int do_wait) { - for (int i = 0; i < nentries; i++) { - if (entries[i].action != act) continue; - if (!entry_matches_runlevel(&entries[i])) continue; - if (!entries[i].active) continue; - - if (do_wait) { - run_and_wait(entries[i].process); - } else { - entries[i].pid = run_process(entries[i].process); - } - } -} - -/* Respawn dead children */ -static void check_respawn(void) { - for (int i = 0; i < nentries; i++) { - if (entries[i].action != ACT_RESPAWN) continue; - if (!entry_matches_runlevel(&entries[i])) continue; - if (!entries[i].active) continue; - - if (entries[i].pid <= 0) { - entries[i].pid = run_process(entries[i].process); - } else { - /* Check if still running */ - int st; - int r = waitpid(entries[i].pid, &st, 1 /* WNOHANG */); - if (r > 0) { - /* Process exited, respawn */ - entries[i].pid = run_process(entries[i].process); - } - } - } -} - -/* Default behavior when no inittab exists */ -static void default_init(void) { - /* Run /etc/init.d/rcS if it exists */ - if (access("/etc/init.d/rcS", 0) == 0) { - run_and_wait("/etc/init.d/rcS"); - } - - /* Spawn shell */ - while (1) { - int pid = fork(); - if (pid < 0) { - fprintf(stderr, "init: fork failed\n"); - for (;;) { struct timespec ts = {1,0}; nanosleep(&ts, NULL); } - } - - if (pid == 0) { - const char* argv[] = { "/bin/sh", NULL }; - execve("/bin/sh", argv, NULL); - _exit(127); - } - - int st; - waitpid(pid, &st, 0); - - /* Shell exited, respawn after a small delay */ - struct timespec ts = {1, 0}; - nanosleep(&ts, NULL); - } -} - -int main(int argc, char** argv) { - (void)argc; (void)argv; - - /* PID 1 should not die on signals */ - struct sigaction sa; - memset(&sa, 0, sizeof(sa)); - sa.sa_handler = (uintptr_t)SIG_IGN; - sigaction(SIGINT, &sa, NULL); - sigaction(SIGTERM, &sa, NULL); - - printf("AdrOS init starting (PID %d)\n", getpid()); - - /* Try to parse inittab */ - if (parse_inittab() < 0) { - printf("init: no /etc/inittab, using defaults\n"); - default_init(); - return 0; /* unreachable */ - } - - printf("init: loaded %d inittab entries, runlevel %d\n", - nentries, current_runlevel); - - /* Phase 1: sysinit entries */ - run_action(ACT_SYSINIT, 1); - - /* Phase 2: wait entries */ - run_action(ACT_WAIT, 1); - - /* Phase 3: once entries */ - run_action(ACT_ONCE, 0); - - /* Phase 4: respawn entries */ - run_action(ACT_RESPAWN, 0); - - /* Main loop: reap children and respawn */ - while (1) { - int st; - int pid = waitpid(-1, &st, 0); - - if (pid > 0) { - /* Mark dead child and respawn if needed */ - for (int i = 0; i < nentries; i++) { - if (entries[i].pid == pid) { - entries[i].pid = -1; - break; - } - } - check_respawn(); - } else { - /* No children or error — sleep briefly */ - struct timespec ts = {1, 0}; - nanosleep(&ts, NULL); - check_respawn(); - } - } - - return 0; -} diff --git a/user/kill.c b/user/kill.c deleted file mode 100644 index 7cc1e95..0000000 --- a/user/kill.c +++ /dev/null @@ -1,41 +0,0 @@ -/* AdrOS kill utility — send signal to process */ -#include -#include -#include -#include - -int main(int argc, char** argv) { - if (argc <= 1) { - fprintf(stderr, "usage: kill [-SIGNAL] PID...\n"); - return 1; - } - - int sig = 15; /* SIGTERM */ - int start = 1; - - if (argv[1][0] == '-') { - const char* s = argv[1] + 1; - if (strcmp(s, "9") == 0 || strcmp(s, "KILL") == 0) sig = 9; - else if (strcmp(s, "15") == 0 || strcmp(s, "TERM") == 0) sig = 15; - else if (strcmp(s, "2") == 0 || strcmp(s, "INT") == 0) sig = 2; - else if (strcmp(s, "1") == 0 || strcmp(s, "HUP") == 0) sig = 1; - else if (strcmp(s, "0") == 0) sig = 0; - else sig = atoi(s); - start = 2; - } - - int rc = 0; - for (int i = start; i < argc; i++) { - int pid = atoi(argv[i]); - if (pid <= 0) { - fprintf(stderr, "kill: invalid pid '%s'\n", argv[i]); - rc = 1; - continue; - } - if (kill(pid, sig) < 0) { - fprintf(stderr, "kill: %d: no such process\n", pid); - rc = 1; - } - } - return rc; -} diff --git a/user/ldso.c b/user/ldso.c deleted file mode 100644 index e961f51..0000000 --- a/user/ldso.c +++ /dev/null @@ -1,369 +0,0 @@ -/* Userspace dynamic linker (ld.so) with lazy PLT/GOT binding. - * - * The kernel ELF loader pushes an auxiliary vector (auxv) onto the user - * stack when PT_INTERP is present. This linker: - * 1. Parses auxv to find AT_PHDR, AT_PHNUM, AT_ENTRY - * 2. Walks program headers to find PT_DYNAMIC - * 3. Extracts DT_PLTGOT, DT_JMPREL, DT_PLTRELSZ, DT_SYMTAB, DT_STRTAB - * 4. Sets GOT[1] = link_map pointer, GOT[2] = _dl_runtime_resolve - * 5. Jumps to AT_ENTRY (the real program entry point) - * - * On first PLT call, the resolver fires: looks up the symbol, patches - * the GOT entry, and jumps to the resolved function. Subsequent calls - * go directly through the patched GOT (zero overhead). - * - * The kernel loads DT_NEEDED shared libraries at SHLIB_BASE (0x20000000). - * The resolver scans the .so's dynamic symtab to find undefined symbols. */ - -typedef unsigned char uint8_t; -typedef unsigned short uint16_t; -typedef unsigned int uint32_t; -typedef int int32_t; - -/* ---- Auxiliary vector types ---- */ -#define AT_NULL 0 -#define AT_PHDR 3 -#define AT_PHENT 4 -#define AT_PHNUM 5 -#define AT_ENTRY 9 - -/* ---- ELF types (minimal, matching kernel include/elf.h) ---- */ -#define PT_LOAD 1 -#define PT_DYNAMIC 2 - -#define DT_NULL 0 -#define DT_NEEDED 1 -#define DT_PLTRELSZ 2 -#define DT_PLTGOT 3 -#define DT_HASH 4 -#define DT_STRTAB 5 -#define DT_SYMTAB 6 -#define DT_STRSZ 10 -#define DT_SYMENT 11 -#define DT_REL 17 -#define DT_RELSZ 18 -#define DT_JMPREL 23 - -#define R_386_32 1 -#define R_386_COPY 5 -#define R_386_GLOB_DAT 6 -#define R_386_JMP_SLOT 7 - -#define ELF32_R_SYM(i) ((i) >> 8) -#define ELF32_R_TYPE(i) ((unsigned char)(i)) - -#define STB_GLOBAL 1 -#define STB_WEAK 2 -#define ELF32_ST_BIND(i) ((i) >> 4) - -#define SHLIB_BASE 0x11000000U - -struct elf32_phdr { - uint32_t p_type, p_offset, p_vaddr, p_paddr; - uint32_t p_filesz, p_memsz, p_flags, p_align; -}; - -struct elf32_dyn { - int32_t d_tag; - uint32_t d_val; -}; - -struct elf32_rel { - uint32_t r_offset; - uint32_t r_info; -}; - -struct elf32_sym { - uint32_t st_name, st_value, st_size; - uint8_t st_info, st_other; - uint16_t st_shndx; -}; - -/* ---- Link map: per-module metadata for the resolver ---- */ -struct link_map { - uint32_t l_addr; /* base load address (0 for ET_EXEC) */ - uint32_t jmprel; /* DT_JMPREL VA (relocation table for .rel.plt) */ - uint32_t pltrelsz; /* DT_PLTRELSZ */ - uint32_t symtab; /* DT_SYMTAB VA */ - uint32_t strtab; /* DT_STRTAB VA */ - uint32_t rel; /* DT_REL VA (eager relocations) */ - uint32_t relsz; /* DT_RELSZ */ - /* Shared lib symbol lookup info */ - uint32_t shlib_symtab; /* .so DT_SYMTAB VA (0 if no .so) */ - uint32_t shlib_strtab; /* .so DT_STRTAB VA */ - uint32_t shlib_base; /* .so load base */ - uint32_t shlib_hash; /* .so DT_HASH VA */ -}; - -static struct link_map g_map; - -/* ---- Minimal string helpers (no libc) ---- */ -static int str_eq(const char* a, const char* b) { - while (*a && *b) { if (*a++ != *b++) return 0; } - return *a == *b; -} - -/* ---- ELF hash (for DT_HASH lookup) ---- */ -static uint32_t elf_hash(const char* name) { - uint32_t h = 0, g; - while (*name) { - h = (h << 4) + (uint8_t)*name++; - g = h & 0xF0000000U; - if (g) h ^= g >> 24; - h &= ~g; - } - return h; -} - -/* ---- Symbol lookup in a shared library via DT_HASH ---- */ -static uint32_t shlib_lookup(const char* name, const struct link_map* map) { - if (!map->shlib_symtab || !map->shlib_strtab || !map->shlib_hash) - return 0; - - const uint32_t* hashtab = (const uint32_t*)(map->shlib_hash + map->shlib_base); - uint32_t nbucket = hashtab[0]; - uint32_t nchain = hashtab[1]; - const uint32_t* bucket = &hashtab[2]; - const uint32_t* chain = &hashtab[2 + nbucket]; - (void)nchain; - - uint32_t h = elf_hash(name) % nbucket; - const struct elf32_sym* symtab = (const struct elf32_sym*)(map->shlib_symtab + map->shlib_base); - const char* strtab = (const char*)(map->shlib_strtab + map->shlib_base); - - for (uint32_t i = bucket[h]; i != 0; i = chain[i]) { - const struct elf32_sym* sym = &symtab[i]; - uint8_t bind = ELF32_ST_BIND(sym->st_info); - if ((bind == STB_GLOBAL || bind == STB_WEAK) && - sym->st_shndx != 0 && sym->st_value != 0) { - if (str_eq(strtab + sym->st_name, name)) - return sym->st_value + map->shlib_base; - } - } - return 0; -} - -/* ---- dl_fixup: called by _dl_runtime_resolve trampoline ---- - * Resolves a single PLT entry: looks up the symbol, patches GOT, - * returns the resolved address. */ -uint32_t dl_fixup(struct link_map* map, uint32_t reloc_offset) - __attribute__((used, visibility("hidden"))); - -uint32_t dl_fixup(struct link_map* map, uint32_t reloc_offset) { - const struct elf32_rel* rel = - (const struct elf32_rel*)(map->jmprel + reloc_offset); - - uint32_t sym_idx = ELF32_R_SYM(rel->r_info); - const struct elf32_sym* sym = - &((const struct elf32_sym*)map->symtab)[sym_idx]; - - uint32_t resolved = 0; - - if (sym->st_value != 0) { - resolved = sym->st_value + map->l_addr; - } else { - const char* name = (const char*)map->strtab + sym->st_name; - resolved = shlib_lookup(name, map); - } - - if (resolved) { - uint32_t* got_entry = (uint32_t*)(rel->r_offset + map->l_addr); - *got_entry = resolved; - } - - return resolved; -} - -/* ---- _dl_runtime_resolve: PLT[0] jumps here via GOT[2] ---- - * Entry stack: [link_map*] [reloc_offset] [return_addr] - * Uses the glibc i386 convention: save eax/ecx/edx, call dl_fixup, - * restore, ret $8 to jump to resolved function. */ -void _dl_runtime_resolve(void) - __attribute__((naked, used, visibility("hidden"))); - -void _dl_runtime_resolve(void) { - __asm__ volatile( - "pushl %%eax\n" - "pushl %%ecx\n" - "pushl %%edx\n" - "movl 16(%%esp), %%edx\n" /* reloc_offset */ - "movl 12(%%esp), %%eax\n" /* link_map* */ - "pushl %%edx\n" - "pushl %%eax\n" - "call dl_fixup\n" - "addl $8, %%esp\n" - "popl %%edx\n" - "popl %%ecx\n" - "xchgl %%eax, (%%esp)\n" /* restore eax, put resolved addr on stack */ - "ret $8\n" /* jump to resolved; pop link_map + reloc_offset */ - ::: "memory" - ); -} - -/* ---- Parse a PT_DYNAMIC at the given VA to extract .so symtab info ---- */ -static void parse_shlib_dynamic(uint32_t dyn_va, uint32_t base) { - const struct elf32_dyn* d = (const struct elf32_dyn*)dyn_va; - for (; d->d_tag != DT_NULL; d++) { - switch (d->d_tag) { - case DT_SYMTAB: g_map.shlib_symtab = d->d_val; break; - case DT_STRTAB: g_map.shlib_strtab = d->d_val; break; - case DT_HASH: g_map.shlib_hash = d->d_val; break; - } - } - g_map.shlib_base = base; -} - -/* ---- Scan for shared library's PT_DYNAMIC at SHLIB_BASE ---- */ -static void find_shlib_info(void) { - const uint8_t* base = (const uint8_t*)SHLIB_BASE; - /* Check ELF magic at SHLIB_BASE */ - if (base[0] != 0x7F || base[1] != 'E' || base[2] != 'L' || base[3] != 'F') - return; - - uint32_t e_phoff = *(const uint32_t*)(base + 28); - uint16_t e_phnum = *(const uint16_t*)(base + 44); - uint16_t e_phentsize = *(const uint16_t*)(base + 42); - - for (uint16_t i = 0; i < e_phnum; i++) { - const struct elf32_phdr* ph = - (const struct elf32_phdr*)(base + e_phoff + i * e_phentsize); - if (ph->p_type == PT_DYNAMIC) { - parse_shlib_dynamic(ph->p_vaddr + SHLIB_BASE, SHLIB_BASE); - return; - } - } -} - -/* ---- Entry point ---- */ -static void _start_c(uint32_t* initial_sp) __attribute__((noreturn, used)); - -void _start(void) __attribute__((noreturn, naked, section(".text.start"))); -void _start(void) { - __asm__ volatile( - "pushl %%esp\n" - "call _start_c\n" - ::: "memory" - ); - __builtin_unreachable(); -} - -static void _start_c(uint32_t* initial_sp) { - /* Stack layout set by execve: - * initial_sp → argc - * argv[0], argv[1], ..., NULL - * envp[0], envp[1], ..., NULL - * auxv[0], auxv[1], ..., {AT_NULL, 0} */ - uint32_t* sp = initial_sp; - - uint32_t argc = *sp++; - sp += argc + 1; /* skip argv[] + NULL terminator */ - while (*sp) sp++; /* skip envp[] entries */ - sp++; /* skip envp NULL terminator */ - - /* sp now points to auxv array */ - uint32_t at_entry = 0; - uint32_t at_phdr = 0; - uint32_t at_phnum = 0; - uint32_t at_phent = 0; - - for (uint32_t* p = sp; p[0] != AT_NULL; p += 2) { - switch (p[0]) { - case AT_ENTRY: at_entry = p[1]; break; - case AT_PHDR: at_phdr = p[1]; break; - case AT_PHNUM: at_phnum = p[1]; break; - case AT_PHENT: at_phent = p[1]; break; - } - } - - if (!at_entry) { - __asm__ volatile("mov $2, %%eax\n mov $127, %%ebx\n int $0x80" ::: "eax", "ebx"); - __builtin_unreachable(); - } - - /* Walk program headers to find PT_DYNAMIC */ - g_map.l_addr = 0; - - if (at_phdr && at_phnum && at_phent) { - for (uint32_t i = 0; i < at_phnum; i++) { - const struct elf32_phdr* ph = - (const struct elf32_phdr*)(at_phdr + i * at_phent); - if (ph->p_type == PT_DYNAMIC) { - uint32_t dyn_va = ph->p_vaddr + g_map.l_addr; - const struct elf32_dyn* d = (const struct elf32_dyn*)dyn_va; - uint32_t pltgot = 0; - - for (; d->d_tag != DT_NULL; d++) { - switch (d->d_tag) { - case DT_PLTGOT: pltgot = d->d_val; break; - case DT_JMPREL: g_map.jmprel = d->d_val; break; - case DT_PLTRELSZ: g_map.pltrelsz = d->d_val; break; - case DT_SYMTAB: g_map.symtab = d->d_val; break; - case DT_STRTAB: g_map.strtab = d->d_val; break; - case DT_REL: g_map.rel = d->d_val; break; - case DT_RELSZ: g_map.relsz = d->d_val; break; - } - } - - /* Scan for shared library info BEFORE resolving relocations */ - find_shlib_info(); - - /* Set up GOT for lazy binding: - * GOT[0] = _DYNAMIC (already set by linker) - * GOT[1] = link_map pointer - * GOT[2] = _dl_runtime_resolve address */ - if (pltgot && g_map.jmprel) { - uint32_t* got = (uint32_t*)(pltgot + g_map.l_addr); - got[1] = (uint32_t)&g_map; - got[2] = (uint32_t)&_dl_runtime_resolve; - } - - /* Process eager relocations (R_386_GLOB_DAT, R_386_COPY) */ - if (g_map.rel && g_map.relsz) { - uint32_t nrel = g_map.relsz / sizeof(struct elf32_rel); - const struct elf32_rel* rtab = - (const struct elf32_rel*)(g_map.rel + g_map.l_addr); - for (uint32_t j = 0; j < nrel; j++) { - uint32_t type = ELF32_R_TYPE(rtab[j].r_info); - uint32_t sidx = ELF32_R_SYM(rtab[j].r_info); - uint32_t* target = (uint32_t*)(rtab[j].r_offset + g_map.l_addr); - if (type == R_386_GLOB_DAT || type == R_386_JMP_SLOT) { - const struct elf32_sym* s = - &((const struct elf32_sym*)g_map.symtab)[sidx]; - uint32_t addr = 0; - if (s->st_value != 0) - addr = s->st_value + g_map.l_addr; - else { - const char* nm = (const char*)g_map.strtab + s->st_name; - addr = shlib_lookup(nm, &g_map); - } - if (addr) *target = addr; - } else if (type == R_386_COPY && sidx) { - const struct elf32_sym* s = - &((const struct elf32_sym*)g_map.symtab)[sidx]; - const char* nm = (const char*)g_map.strtab + s->st_name; - uint32_t src = shlib_lookup(nm, &g_map); - if (src && s->st_size > 0) { - const uint8_t* sp = (const uint8_t*)src; - uint8_t* dp = (uint8_t*)target; - for (uint32_t k = 0; k < s->st_size; k++) - dp[k] = sp[k]; - } - } - } - } - break; - } - } - } - - /* Restore the original stack pointer so the real program's _start - * sees the correct layout: [argc] [argv...] [NULL] [envp...] [NULL] [auxv...] - * Then jump to the program entry point. */ - __asm__ volatile( - "mov %0, %%esp\n" - "jmp *%1\n" - :: "r"(initial_sp), "r"(at_entry) - : "memory" - ); - __builtin_unreachable(); -} diff --git a/user/ln.c b/user/ln.c deleted file mode 100644 index 72f60d4..0000000 --- a/user/ln.c +++ /dev/null @@ -1,33 +0,0 @@ -/* AdrOS ln utility */ -#include -#include -#include - -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] \n"); - return 1; - } - - int r; - if (sflag) { - r = symlink(argv[start], argv[start + 1]); - } else { - r = link(argv[start], argv[start + 1]); - } - - if (r < 0) { - fprintf(stderr, "ln: failed to create %slink '%s' -> '%s'\n", - sflag ? "symbolic " : "", argv[start + 1], argv[start]); - return 1; - } - return 0; -} diff --git a/user/ls.c b/user/ls.c deleted file mode 100644 index 6ce6c69..0000000 --- a/user/ls.c +++ /dev/null @@ -1,139 +0,0 @@ -/* AdrOS ls utility */ -#include -#include -#include -#include -#include -#include -#include - -static int aflag = 0; /* -a: show hidden files */ -static int lflag = 0; /* -l: long format */ - -#define LS_MAX_ENTRIES 512 - -struct ls_entry { - char name[256]; - unsigned char type; -}; - -static struct ls_entry entries[LS_MAX_ENTRIES]; - -static int cmp_entry(const void* a, const void* b) { - return strcmp(((const struct ls_entry*)a)->name, - ((const struct ls_entry*)b)->name); -} - -static void ls_dir(const char* path) { - int fd = open(path, O_RDONLY); - if (fd < 0) { - fprintf(stderr, "ls: cannot access '%s': No such file or directory\n", path); - return; - } - - int count = 0; - char buf[2048]; - int rc; - while ((rc = getdents(fd, buf, sizeof(buf))) > 0) { - int off = 0; - while (off < rc) { - struct dirent* d = (struct dirent*)(buf + off); - if (d->d_reclen == 0) break; - - if (!aflag && d->d_name[0] == '.') { - off += d->d_reclen; - continue; - } - - if (count < LS_MAX_ENTRIES) { - strncpy(entries[count].name, d->d_name, 255); - entries[count].name[255] = '\0'; - entries[count].type = d->d_type; - count++; - } - off += d->d_reclen; - } - } - close(fd); - - qsort(entries, count, sizeof(struct ls_entry), cmp_entry); - - for (int i = 0; i < count; i++) { - if (lflag) { - char fullpath[512]; - size_t plen = strlen(path); - if (plen > 0 && path[plen - 1] == '/') - snprintf(fullpath, sizeof(fullpath), "%s%s", path, entries[i].name); - else - snprintf(fullpath, sizeof(fullpath), "%s/%s", path, entries[i].name); - - struct stat st; - int have_stat = (stat(fullpath, &st) == 0); - - char type = '-'; - if (entries[i].type == DT_DIR) type = 'd'; - else if (entries[i].type == DT_CHR) type = 'c'; - else if (entries[i].type == DT_LNK) type = 'l'; - else if (entries[i].type == DT_BLK) type = 'b'; - - char perms[10]; - if (have_stat) { - unsigned m = (unsigned)st.st_mode; - perms[0] = (m & S_IRUSR) ? 'r' : '-'; - perms[1] = (m & S_IWUSR) ? 'w' : '-'; - perms[2] = (m & S_IXUSR) ? 'x' : '-'; - perms[3] = (m & S_IRGRP) ? 'r' : '-'; - perms[4] = (m & S_IWGRP) ? 'w' : '-'; - perms[5] = (m & S_IXGRP) ? 'x' : '-'; - perms[6] = (m & S_IROTH) ? 'r' : '-'; - perms[7] = (m & S_IWOTH) ? 'w' : '-'; - perms[8] = (m & S_IXOTH) ? 'x' : '-'; - perms[9] = '\0'; - } else { - strcpy(perms, "---------"); - } - - unsigned long sz = have_stat ? (unsigned long)st.st_size : 0; - unsigned nlink = have_stat ? (unsigned)st.st_nlink : 1; - - printf("%c%s %2u root root %8lu %s\n", - type, perms, nlink, sz, entries[i].name); - } else { - printf("%s\n", entries[i].name); - } - } -} - -int main(int argc, char** argv) { - int npath = 0; - const char* paths[64]; - - for (int i = 1; i < argc; i++) { - if (argv[i][0] == '-' && argv[i][1]) { - const char* f = argv[i] + 1; - while (*f) { - if (*f == 'a') aflag = 1; - else if (*f == 'l') lflag = 1; - else { - fprintf(stderr, "ls: invalid option -- '%c'\n", *f); - return 1; - } - f++; - } - } else { - if (npath < 64) paths[npath++] = argv[i]; - } - } - - if (npath == 0) { - ls_dir("."); - } else { - for (int i = 0; i < npath; i++) { - if (npath > 1) printf("%s:\n", paths[i]); - ls_dir(paths[i]); - if (npath > 1 && i < npath - 1) printf("\n"); - } - } - - return 0; -} diff --git a/user/mkdir.c b/user/mkdir.c deleted file mode 100644 index d5716c5..0000000 --- a/user/mkdir.c +++ /dev/null @@ -1,61 +0,0 @@ -/* AdrOS mkdir utility */ -#include -#include -#include - -static int pflag = 0; /* -p: create parent directories */ - -static int mkdir_p(const char* path) { - char tmp[256]; - size_t len = strlen(path); - if (len >= sizeof(tmp)) return -1; - strcpy(tmp, path); - - for (char* p = tmp + 1; *p; p++) { - if (*p == '/') { - *p = '\0'; - mkdir(tmp); /* ignore errors — parent may already exist */ - *p = '/'; - } - } - return mkdir(tmp); -} - -int main(int argc, char** argv) { - if (argc <= 1) { - fprintf(stderr, "mkdir: missing operand\n"); - return 1; - } - - int rc = 0; - int start = 1; - - for (int i = 1; i < argc; i++) { - if (argv[i][0] == '-' && argv[i][1]) { - const char* f = argv[i] + 1; - while (*f) { - if (*f == 'p') pflag = 1; - else { - fprintf(stderr, "mkdir: invalid option -- '%c'\n", *f); - return 1; - } - f++; - } - start = i + 1; - } - } - - for (int i = start; i < argc; i++) { - int r; - if (pflag) { - r = mkdir_p(argv[i]); - } else { - r = mkdir(argv[i]); - } - if (r < 0) { - fprintf(stderr, "mkdir: cannot create directory '%s'\n", argv[i]); - rc = 1; - } - } - return rc; -} diff --git a/user/mount.c b/user/mount.c deleted file mode 100644 index fba0bf5..0000000 --- a/user/mount.c +++ /dev/null @@ -1,57 +0,0 @@ -/* AdrOS mount utility — mount filesystems or display mounts */ -#include -#include -#include -#include -#include -#include - -static void show_mounts(void) { - int fd = open("/proc/mounts", O_RDONLY); - if (fd >= 0) { - char buf[1024]; - int n; - while ((n = read(fd, buf, sizeof(buf))) > 0) - write(STDOUT_FILENO, buf, (size_t)n); - close(fd); - } else { - printf("tmpfs on / type overlayfs (rw)\n"); - printf("devfs on /dev type devfs (rw)\n"); - printf("procfs on /proc type procfs (ro)\n"); - } -} - -int main(int argc, char** argv) { - if (argc < 2) { - show_mounts(); - return 0; - } - - const char* fstype = "diskfs"; - const char* device = NULL; - const char* mountpoint = NULL; - - /* Parse options first, then collect positional args */ - int i; - for (i = 1; i < argc; i++) { - if (strcmp(argv[i], "-t") == 0 && i + 1 < argc) { - fstype = argv[++i]; - } else if (!device) { - device = argv[i]; - } else if (!mountpoint) { - mountpoint = argv[i]; - } - } - - if (!device || !mountpoint) { - fprintf(stderr, "usage: mount [-t fstype] device mountpoint\n"); - return 1; - } - - int rc = __syscall_ret(_syscall3(SYS_MOUNT, (int)device, (int)mountpoint, (int)fstype)); - if (rc < 0) { - fprintf(stderr, "mount: mounting %s on %s failed: %d\n", device, mountpoint, rc); - return 1; - } - return 0; -} diff --git a/user/mv.c b/user/mv.c deleted file mode 100644 index 97cb1bc..0000000 --- a/user/mv.c +++ /dev/null @@ -1,45 +0,0 @@ -/* AdrOS mv utility */ -#include -#include -#include -#include - -int main(int argc, char** argv) { - if (argc < 3) { - fprintf(stderr, "Usage: mv \n"); - return 1; - } - - /* Try rename first (same filesystem) */ - if (rename(argv[1], argv[2]) == 0) - return 0; - - /* Fallback: copy + unlink */ - int src = open(argv[1], O_RDONLY); - if (src < 0) { - fprintf(stderr, "mv: cannot open '%s'\n", argv[1]); - return 1; - } - - int dst = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0644); - if (dst < 0) { - fprintf(stderr, "mv: cannot create '%s'\n", argv[2]); - close(src); - return 1; - } - - char buf[4096]; - int r; - while ((r = read(src, buf, sizeof(buf))) > 0) { - if (write(dst, buf, (size_t)r) != r) { - fprintf(stderr, "mv: write error\n"); - close(src); close(dst); - return 1; - } - } - - close(src); - close(dst); - unlink(argv[1]); - return 0; -} diff --git a/user/pie_func.c b/user/pie_func.c deleted file mode 100644 index 8e744f8..0000000 --- a/user/pie_func.c +++ /dev/null @@ -1,7 +0,0 @@ -/* Shared library function for PLT/GOT lazy binding test. - * Compiled as a shared object (libpietest.so), loaded at SHLIB_BASE by kernel. - * The main PIE binary calls test_add() through PLT — resolved lazily by ld.so. */ - -int test_add(int a, int b) { - return a + b; -} diff --git a/user/pie_main.c b/user/pie_main.c deleted file mode 100644 index 3a6805d..0000000 --- a/user/pie_main.c +++ /dev/null @@ -1,34 +0,0 @@ -/* PIE test binary for PLT/GOT lazy binding verification. - * Calls test_add() from libpietest.so through PLT — resolved lazily by ld.so. - * Built as: i686-elf-ld -pie --dynamic-linker=/lib/ld.so */ - -static inline void sys_exit(int code) { - __asm__ volatile("int $0x80" :: "a"(2), "b"(code) : "memory"); -} - -static inline int sys_write(int fd, const void* buf, unsigned len) { - int ret; - __asm__ volatile("int $0x80" : "=a"(ret) : "a"(1), "b"(fd), "c"(buf), "d"(len) : "memory"); - return ret; -} - -extern int test_add(int a, int b); - -void _start(void) { - int r = test_add(38, 4); - if (r == 42) { - sys_write(1, "[init] lazy PLT OK\n", 19); - } else { - sys_write(1, "[init] lazy PLT FAIL\n", 21); - } - - /* Call again — this time GOT is already patched, tests direct path */ - r = test_add(100, 23); - if (r == 123) { - sys_write(1, "[init] PLT cached OK\n", 21); - } else { - sys_write(1, "[init] PLT cached FAIL\n", 23); - } - - sys_exit(0); -} diff --git a/user/printenv.c b/user/printenv.c deleted file mode 100644 index 0e3f081..0000000 --- a/user/printenv.c +++ /dev/null @@ -1,27 +0,0 @@ -/* AdrOS printenv utility — print environment variables */ -#include -#include - -extern char** __environ; - -int main(int argc, char** argv) { - if (!__environ) return 1; - if (argc <= 1) { - for (int i = 0; __environ[i]; i++) - printf("%s\n", __environ[i]); - return 0; - } - for (int i = 1; i < argc; i++) { - int found = 0; - int nlen = (int)strlen(argv[i]); - for (int j = 0; __environ[j]; j++) { - if (strncmp(__environ[j], argv[i], (size_t)nlen) == 0 && __environ[j][nlen] == '=') { - printf("%s\n", __environ[j] + nlen + 1); - found = 1; - break; - } - } - if (!found) return 1; - } - return 0; -} diff --git a/user/ps.c b/user/ps.c deleted file mode 100644 index 251de0d..0000000 --- a/user/ps.c +++ /dev/null @@ -1,47 +0,0 @@ -/* AdrOS ps utility — list processes from /proc */ -#include -#include -#include -#include -#include - -static int is_digit(char c) { return c >= '0' && c <= '9'; } - -int main(void) { - printf(" PID CMD\n"); - int fd = open("/proc", O_RDONLY); - if (fd < 0) { - fprintf(stderr, "ps: cannot open /proc\n"); - return 1; - } - char buf[512]; - int rc; - while ((rc = getdents(fd, buf, sizeof(buf))) > 0) { - int off = 0; - while (off < rc) { - struct dirent* d = (struct dirent*)(buf + off); - if (d->d_reclen == 0) break; - if (is_digit(d->d_name[0])) { - char path[64]; - snprintf(path, sizeof(path), "/proc/%s/cmdline", d->d_name); - int cfd = open(path, O_RDONLY); - char cmd[64] = "?"; - if (cfd >= 0) { - int n = read(cfd, cmd, sizeof(cmd) - 1); - if (n > 0) { - cmd[n] = '\0'; - while (n > 0 && (cmd[n-1] == '\n' || cmd[n-1] == '\0')) { - cmd[--n] = '\0'; - } - } - if (n <= 0) strcpy(cmd, "[kernel]"); - close(cfd); - } - printf("%5s %s\n", d->d_name, cmd); - } - off += d->d_reclen; - } - } - close(fd); - return 0; -} diff --git a/user/pwd.c b/user/pwd.c deleted file mode 100644 index baa6134..0000000 --- a/user/pwd.c +++ /dev/null @@ -1,14 +0,0 @@ -/* AdrOS pwd utility — print working directory */ -#include -#include - -int main(void) { - char buf[256]; - if (getcwd(buf, sizeof(buf)) >= 0) - printf("%s\n", buf); - else { - fprintf(stderr, "pwd: error\n"); - return 1; - } - return 0; -} diff --git a/user/rm.c b/user/rm.c deleted file mode 100644 index 0a4d22e..0000000 --- a/user/rm.c +++ /dev/null @@ -1,48 +0,0 @@ -/* AdrOS rm utility */ -#include -#include -#include - -static int rflag = 0; /* -r: recursive */ -static int fflag = 0; /* -f: force (no errors) */ -static int dflag = 0; /* -d: remove empty directories */ - -int main(int argc, char** argv) { - if (argc <= 1) { - fprintf(stderr, "rm: missing operand\n"); - return 1; - } - - int start = 1; - for (int i = 1; i < argc; i++) { - if (argv[i][0] == '-' && argv[i][1]) { - const char* f = argv[i] + 1; - while (*f) { - if (*f == 'r' || *f == 'R') rflag = 1; - else if (*f == 'f') fflag = 1; - else if (*f == 'd') dflag = 1; - else { - fprintf(stderr, "rm: invalid option -- '%c'\n", *f); - return 1; - } - f++; - } - start = i + 1; - } else { - break; - } - } - - int rc = 0; - for (int i = start; i < argc; i++) { - int r = unlink(argv[i]); - if (r < 0 && (rflag || dflag)) { - r = rmdir(argv[i]); - } - if (r < 0 && !fflag) { - fprintf(stderr, "rm: cannot remove '%s'\n", argv[i]); - rc = 1; - } - } - return rc; -} diff --git a/user/rmdir.c b/user/rmdir.c deleted file mode 100644 index 0d65e8a..0000000 --- a/user/rmdir.c +++ /dev/null @@ -1,18 +0,0 @@ -/* AdrOS rmdir utility — remove empty directories */ -#include -#include - -int main(int argc, char** argv) { - if (argc <= 1) { - fprintf(stderr, "rmdir: missing operand\n"); - return 1; - } - int rc = 0; - for (int i = 1; i < argc; i++) { - if (rmdir(argv[i]) < 0) { - fprintf(stderr, "rmdir: failed to remove '%s'\n", argv[i]); - rc = 1; - } - } - return rc; -} diff --git a/user/sed.c b/user/sed.c deleted file mode 100644 index 53992f2..0000000 --- a/user/sed.c +++ /dev/null @@ -1,95 +0,0 @@ -/* AdrOS sed utility — minimal stream editor (s/pattern/replacement/g only) */ -#include -#include -#include -#include - -static int match_at(const char* s, const char* pat, int patlen) { - for (int i = 0; i < patlen; i++) { - if (s[i] == '\0' || s[i] != pat[i]) return 0; - } - return 1; -} - -static void sed_substitute(const char* line, const char* pat, int patlen, - const char* rep, int replen, int global) { - const char* p = line; - while (*p) { - if (match_at(p, pat, patlen)) { - write(STDOUT_FILENO, rep, replen); - p += patlen; - if (!global) { - write(STDOUT_FILENO, p, strlen(p)); - return; - } - } else { - write(STDOUT_FILENO, p, 1); - p++; - } - } -} - -static int parse_s_cmd(const char* expr, char* pat, int* patlen, - char* rep, int* replen, int* global) { - if (expr[0] != 's' || expr[1] == '\0') return -1; - char delim = expr[1]; - const char* p = expr + 2; - int pi = 0; - while (*p && *p != delim && pi < 255) pat[pi++] = *p++; - pat[pi] = '\0'; *patlen = pi; - if (*p != delim) return -1; - p++; - int ri = 0; - while (*p && *p != delim && ri < 255) rep[ri++] = *p++; - rep[ri] = '\0'; *replen = ri; - *global = 0; - if (*p == delim) { p++; if (*p == 'g') *global = 1; } - return 0; -} - -int main(int argc, char** argv) { - if (argc < 2) { - fprintf(stderr, "Usage: sed 's/pattern/replacement/[g]' [file]\n"); - return 1; - } - - char pat[256], rep[256]; - int patlen, replen, global; - int ei = 1; - if (strcmp(argv[1], "-e") == 0 && argc > 2) ei = 2; - - if (parse_s_cmd(argv[ei], pat, &patlen, rep, &replen, &global) < 0) { - fprintf(stderr, "sed: invalid expression: %s\n", argv[ei]); - return 1; - } - - int fd = STDIN_FILENO; - if (argc > ei + 1) { - fd = open(argv[ei + 1], O_RDONLY); - if (fd < 0) { - fprintf(stderr, "sed: %s: No such file or directory\n", argv[ei + 1]); - return 1; - } - } - - char line[4096]; - int li = 0; - char c; - while (read(fd, &c, 1) == 1) { - if (c == '\n') { - line[li] = '\0'; - sed_substitute(line, pat, patlen, rep, replen, global); - write(STDOUT_FILENO, "\n", 1); - li = 0; - } else if (li < (int)sizeof(line) - 1) { - line[li++] = c; - } - } - if (li > 0) { - line[li] = '\0'; - sed_substitute(line, pat, patlen, rep, replen, global); - } - - if (fd != STDIN_FILENO) close(fd); - return 0; -} diff --git a/user/sh.c b/user/sh.c deleted file mode 100644 index 1af3ab3..0000000 --- a/user/sh.c +++ /dev/null @@ -1,1035 +0,0 @@ -/* AdrOS POSIX-like shell (/bin/sh) - * - * Features: - * - Variable assignment (VAR=value) and expansion ($VAR) - * - Environment variables (export VAR=value) - * - Line editing (left/right arrow keys) - * - Command history (up/down arrow keys) - * - Pipes (cmd1 | cmd2 | cmd3) - * - Redirections (< > >>) - * - Operators: ; && || & - * - Job control: CTRL+C (SIGINT), CTRL+Z (SIGTSTP), background (&) - * - Builtins: cd, exit, echo, export, unset, set, pwd, type - * - PATH-based command resolution - * - Quote handling (single and double quotes) - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static struct termios orig_termios; - -static void tty_raw_mode(void) { - tcgetattr(STDIN_FILENO, &orig_termios); - struct termios raw = orig_termios; - raw.c_lflag &= ~(ICANON | ECHO | ISIG); - raw.c_cc[VMIN] = 1; - raw.c_cc[VTIME] = 0; - tcsetattr(STDIN_FILENO, TCSANOW, &raw); -} - -static void tty_restore(void) { - tcsetattr(STDIN_FILENO, TCSANOW, &orig_termios); -} - -#define LINE_MAX 512 -#define MAX_ARGS 64 -#define MAX_VARS 64 -#define HIST_SIZE 32 - -/* ---- Shell variables ---- */ - -static struct { - char name[64]; - char value[256]; - int exported; -} vars[MAX_VARS]; -static int nvar = 0; - -static int last_status = 0; - -static const char* var_get(const char* name) { - for (int i = 0; i < nvar; i++) - if (strcmp(vars[i].name, name) == 0) return vars[i].value; - return getenv(name); -} - -static void var_set(const char* name, const char* value, int exported) { - for (int i = 0; i < nvar; i++) { - if (strcmp(vars[i].name, name) == 0) { - strncpy(vars[i].value, value, 255); - vars[i].value[255] = '\0'; - if (exported) vars[i].exported = 1; - return; - } - } - if (nvar < MAX_VARS) { - strncpy(vars[nvar].name, name, 63); - vars[nvar].name[63] = '\0'; - strncpy(vars[nvar].value, value, 255); - vars[nvar].value[255] = '\0'; - vars[nvar].exported = exported; - nvar++; - } -} - -static void var_unset(const char* name) { - for (int i = 0; i < nvar; i++) { - if (strcmp(vars[i].name, name) == 0) { - vars[i] = vars[--nvar]; - return; - } - } -} - -/* Build envp array from exported variables */ -static char env_buf[MAX_VARS][320]; -static char* envp_arr[MAX_VARS + 1]; - -static char** build_envp(void) { - int n = 0; - for (int i = 0; i < nvar && n < MAX_VARS; i++) { - if (!vars[i].exported) continue; - snprintf(env_buf[n], sizeof(env_buf[n]), "%s=%s", - vars[i].name, vars[i].value); - envp_arr[n] = env_buf[n]; - n++; - } - envp_arr[n] = NULL; - return envp_arr; -} - -/* ---- Command history ---- */ - -static char history[HIST_SIZE][LINE_MAX]; -static int hist_count = 0; -static int hist_pos = 0; - -static void hist_add(const char* line) { - if (line[0] == '\0') return; - if (hist_count > 0 && strcmp(history[(hist_count - 1) % HIST_SIZE], line) == 0) - return; - strncpy(history[hist_count % HIST_SIZE], line, LINE_MAX - 1); - history[hist_count % HIST_SIZE][LINE_MAX - 1] = '\0'; - hist_count++; -} - -/* ---- Line editing ---- */ - -static char line[LINE_MAX]; - -static void term_write(const char* s, int n) { - write(STDOUT_FILENO, s, (size_t)n); -} - -/* ---- Tab completion ---- */ - -static int tab_complete(char* buf, int* p_pos, int* p_len) { - int pos = *p_pos; - int len = *p_len; - - /* Find the start of the current word */ - int wstart = pos; - while (wstart > 0 && buf[wstart - 1] != ' ' && buf[wstart - 1] != '\t') - wstart--; - - char prefix[128]; - int plen = pos - wstart; - if (plen <= 0 || plen >= (int)sizeof(prefix)) return 0; - memcpy(prefix, buf + wstart, (size_t)plen); - prefix[plen] = '\0'; - - /* Determine if this is a command (first word) or filename */ - int is_cmd = 1; - for (int i = 0; i < wstart; i++) { - if (buf[i] != ' ' && buf[i] != '\t') { is_cmd = 0; break; } - } - - char match[128]; - match[0] = '\0'; - int nmatches = 0; - - /* Split prefix into directory part and name part for file completion */ - char dirpath[128] = "."; - const char* namepfx = prefix; - char* lastsep = NULL; - for (char* p = prefix; *p; p++) { - if (*p == '/') lastsep = p; - } - if (lastsep) { - int dlen = (int)(lastsep - prefix); - if (dlen == 0) { dirpath[0] = '/'; dirpath[1] = '\0'; } - else { memcpy(dirpath, prefix, (size_t)dlen); dirpath[dlen] = '\0'; } - namepfx = lastsep + 1; - } - int nplen = (int)strlen(namepfx); - - if (!is_cmd || lastsep) { - /* File/directory completion */ - int fd = open(dirpath, 0); - if (fd >= 0) { - char dbuf[512]; - int rc; - while ((rc = getdents(fd, dbuf, sizeof(dbuf))) > 0) { - int off = 0; - while (off < rc) { - struct dirent* d = (struct dirent*)(dbuf + off); - if (d->d_reclen == 0) break; - if (d->d_name[0] != '.' || nplen > 0) { - int nlen = (int)strlen(d->d_name); - if (nlen >= nplen && memcmp(d->d_name, namepfx, (size_t)nplen) == 0) { - if (nmatches == 0) strcpy(match, d->d_name); - nmatches++; - } - } - off += d->d_reclen; - } - } - close(fd); - } - } - - if (is_cmd && !lastsep) { - /* Command completion: search PATH directories + builtins */ - static const char* builtins[] = { - "cd", "exit", "echo", "export", "unset", "set", "pwd", "type", NULL - }; - for (int i = 0; builtins[i]; i++) { - int blen = (int)strlen(builtins[i]); - if (blen >= plen && memcmp(builtins[i], prefix, (size_t)plen) == 0) { - if (nmatches == 0) strcpy(match, builtins[i]); - nmatches++; - } - } - const char* path_env = var_get("PATH"); - if (!path_env) path_env = "/bin:/sbin:/usr/bin"; - char pathcopy[512]; - strncpy(pathcopy, path_env, sizeof(pathcopy) - 1); - pathcopy[sizeof(pathcopy) - 1] = '\0'; - char* save = pathcopy; - char* dir; - while ((dir = save) != NULL) { - char* sep = strchr(save, ':'); - if (sep) { *sep = '\0'; save = sep + 1; } else save = NULL; - int fd = open(dir, 0); - if (fd < 0) continue; - char dbuf[512]; - int rc; - while ((rc = getdents(fd, dbuf, sizeof(dbuf))) > 0) { - int off = 0; - while (off < rc) { - struct dirent* d = (struct dirent*)(dbuf + off); - if (d->d_reclen == 0) break; - int nlen = (int)strlen(d->d_name); - if (nlen >= plen && memcmp(d->d_name, prefix, (size_t)plen) == 0) { - if (nmatches == 0) strcpy(match, d->d_name); - nmatches++; - } - off += d->d_reclen; - } - } - close(fd); - } - } - - if (nmatches != 1) return 0; - - /* Insert the completion suffix */ - int mlen = (int)strlen(match); - int suffix_len = is_cmd && !lastsep ? mlen - plen : mlen - nplen; - const char* suffix = is_cmd && !lastsep ? match + plen : match + nplen; - if (suffix_len <= 0 || len + suffix_len >= LINE_MAX - 1) return 0; - - memmove(buf + pos + suffix_len, buf + pos, (size_t)(len - pos)); - memcpy(buf + pos, suffix, (size_t)suffix_len); - len += suffix_len; - buf[len] = '\0'; - term_write(buf + pos, len - pos); - pos += suffix_len; - for (int i = 0; i < len - pos; i++) term_write("\b", 1); - *p_pos = pos; - *p_len = len; - return 1; -} - -static int read_line_edit(void) { - int pos = 0; - int len = 0; - hist_pos = hist_count; - - memset(line, 0, LINE_MAX); - - while (len < LINE_MAX - 1) { - char c; - int r = read(STDIN_FILENO, &c, 1); - if (r <= 0) { - if (len == 0) return -1; - break; - } - - if (c == '\n' || c == '\r') { - term_write("\n", 1); - break; - } - - /* Backspace / DEL */ - if (c == '\b' || c == 127) { - if (pos > 0) { - memmove(line + pos - 1, line + pos, (size_t)(len - pos)); - pos--; len--; - line[len] = '\0'; - /* Redraw: move cursor back, print rest, clear tail */ - term_write("\b", 1); - term_write(line + pos, len - pos); - term_write(" \b", 2); - for (int i = 0; i < len - pos; i++) term_write("\b", 1); - } - continue; - } - - /* Tab = autocomplete */ - if (c == '\t') { - tab_complete(line, &pos, &len); - continue; - } - - /* Ctrl+D = EOF */ - if (c == 4) { - if (len == 0) return -1; - continue; - } - - /* Ctrl+C = cancel line */ - if (c == 3) { - term_write("^C\n", 3); - line[0] = '\0'; - return 0; - } - - /* Ctrl+Z = ignored at prompt (no foreground job to suspend) */ - if (c == 26) { - continue; - } - - /* Ctrl+A = beginning of line */ - if (c == 1) { - while (pos > 0) { term_write("\b", 1); pos--; } - continue; - } - - /* Ctrl+E = end of line */ - if (c == 5) { - term_write(line + pos, len - pos); - pos = len; - continue; - } - - /* Ctrl+U = clear line */ - if (c == 21) { - while (pos > 0) { term_write("\b", 1); pos--; } - for (int i = 0; i < len; i++) term_write(" ", 1); - for (int i = 0; i < len; i++) term_write("\b", 1); - len = 0; pos = 0; - line[0] = '\0'; - continue; - } - - /* Escape sequences (arrow keys) */ - if (c == 27) { - char seq[2]; - if (read(STDIN_FILENO, &seq[0], 1) <= 0) continue; - if (seq[0] != '[') continue; - if (read(STDIN_FILENO, &seq[1], 1) <= 0) continue; - - if (seq[1] >= '0' && seq[1] <= '9') { - /* Extended sequence like \x1b[3~ (DELETE), \x1b[1~ (Home), \x1b[4~ (End) */ - char trail; - if (read(STDIN_FILENO, &trail, 1) <= 0) continue; - if (trail == '~') { - if (seq[1] == '3') { - /* DELETE key — delete char at cursor */ - if (pos < len) { - memmove(line + pos, line + pos + 1, (size_t)(len - pos - 1)); - len--; - line[len] = '\0'; - term_write(line + pos, len - pos); - term_write(" \b", 2); - for (int i = 0; i < len - pos; i++) term_write("\b", 1); - } - } else if (seq[1] == '1') { - /* Home */ - while (pos > 0) { term_write("\b", 1); pos--; } - } else if (seq[1] == '4') { - /* End */ - term_write(line + pos, len - pos); - pos = len; - } - } - continue; - } - - switch (seq[1]) { - case 'A': /* Up arrow — previous history */ - if (hist_pos > 0 && hist_pos > hist_count - HIST_SIZE) { - hist_pos--; - /* Clear current line */ - while (pos > 0) { term_write("\b", 1); pos--; } - for (int i = 0; i < len; i++) term_write(" ", 1); - for (int i = 0; i < len; i++) term_write("\b", 1); - /* Load history entry */ - strcpy(line, history[hist_pos % HIST_SIZE]); - len = (int)strlen(line); - pos = len; - term_write(line, len); - } - break; - case 'B': /* Down arrow — next history */ - if (hist_pos < hist_count) { - hist_pos++; - while (pos > 0) { term_write("\b", 1); pos--; } - for (int i = 0; i < len; i++) term_write(" ", 1); - for (int i = 0; i < len; i++) term_write("\b", 1); - if (hist_pos < hist_count) { - strcpy(line, history[hist_pos % HIST_SIZE]); - } else { - line[0] = '\0'; - } - len = (int)strlen(line); - pos = len; - term_write(line, len); - } - break; - case 'C': /* Right arrow */ - if (pos < len) { term_write(line + pos, 1); pos++; } - break; - case 'D': /* Left arrow */ - if (pos > 0) { term_write("\b", 1); pos--; } - break; - case 'H': /* Home */ - while (pos > 0) { term_write("\b", 1); pos--; } - break; - case 'F': /* End */ - term_write(line + pos, len - pos); - pos = len; - break; - } - continue; - } - - /* Normal printable character */ - if (c >= ' ' && c <= '~') { - memmove(line + pos + 1, line + pos, (size_t)(len - pos)); - line[pos] = c; - len++; line[len] = '\0'; - term_write(line + pos, len - pos); - pos++; - for (int i = 0; i < len - pos; i++) term_write("\b", 1); - } - } - - line[len] = '\0'; - return len; -} - -/* ---- Variable expansion ---- */ - -static void expand_vars(const char* src, char* dst, int maxlen) { - int di = 0; - while (*src && di < maxlen - 1) { - if (*src == '$') { - src++; - if (*src == '?') { - di += snprintf(dst + di, (size_t)(maxlen - di), "%d", last_status); - src++; - } else if (*src == '{') { - src++; - char name[64]; - int ni = 0; - while (*src && *src != '}' && ni < 63) name[ni++] = *src++; - name[ni] = '\0'; - if (*src == '}') src++; - const char* val = var_get(name); - if (val) { - int vl = (int)strlen(val); - if (di + vl < maxlen) { memcpy(dst + di, val, (size_t)vl); di += vl; } - } - } else { - char name[64]; - int ni = 0; - while ((*src >= 'A' && *src <= 'Z') || (*src >= 'a' && *src <= 'z') || - (*src >= '0' && *src <= '9') || *src == '_') { - if (ni < 63) name[ni++] = *src; - src++; - } - name[ni] = '\0'; - const char* val = var_get(name); - if (val) { - int vl = (int)strlen(val); - if (di + vl < maxlen) { memcpy(dst + di, val, (size_t)vl); di += vl; } - } - } - } else { - dst[di++] = *src++; - } - } - dst[di] = '\0'; -} - -/* ---- Argument parsing with quote handling ---- */ - -static int parse_args(char* cmd, char** argv, int max) { - int argc = 0; - char* p = cmd; - while (*p && argc < max - 1) { - while (*p == ' ' || *p == '\t') p++; - if (*p == '\0') break; - - char* out = p; - argv[argc++] = out; - - while (*p && *p != ' ' && *p != '\t') { - if (*p == '\'' ) { - p++; - while (*p && *p != '\'') *out++ = *p++; - if (*p == '\'') p++; - } else if (*p == '"') { - p++; - while (*p && *p != '"') *out++ = *p++; - if (*p == '"') p++; - } else { - *out++ = *p++; - } - } - if (*p) p++; - *out = '\0'; - } - argv[argc] = NULL; - return argc; -} - -/* ---- PATH resolution ---- */ - -static char pathbuf[256]; - -static const char* resolve(const char* cmd) { - if (cmd[0] == '/' || cmd[0] == '.') return cmd; - - const char* path_env = var_get("PATH"); - if (!path_env) path_env = "/bin:/sbin:/usr/bin"; - - char pathcopy[512]; - strncpy(pathcopy, path_env, sizeof(pathcopy) - 1); - pathcopy[sizeof(pathcopy) - 1] = '\0'; - - char* save = pathcopy; - char* dir; - while ((dir = save) != NULL) { - char* sep = strchr(save, ':'); - if (sep) { *sep = '\0'; save = sep + 1; } - else save = NULL; - - snprintf(pathbuf, sizeof(pathbuf), "%s/%s", dir, cmd); - if (access(pathbuf, 0) == 0) return pathbuf; - } - return cmd; -} - -/* ---- Helper: set foreground process group on controlling TTY ---- */ - -static void set_fg_pgrp(int pgrp) { - ioctl(STDIN_FILENO, TIOCSPGRP, &pgrp); -} - -/* ---- Run a single simple command ---- */ - -static int bg_flag = 0; /* set by caller when trailing & detected */ - -static void run_simple(char* cmd) { - /* Expand variables */ - char expanded[LINE_MAX]; - expand_vars(cmd, expanded, LINE_MAX); - - char* argv[MAX_ARGS]; - int argc = parse_args(expanded, argv, MAX_ARGS); - if (argc == 0) return; - - /* Check for variable assignment (no command, just VAR=value) */ - if (argc == 1 && strchr(argv[0], '=') != NULL) { - char* eq = strchr(argv[0], '='); - *eq = '\0'; - var_set(argv[0], eq + 1, 0); - last_status = 0; - return; - } - - /* Extract redirections */ - char* redir_out = NULL; - char* redir_in = NULL; - int append = 0; - int heredoc_fd = -1; - int nargc = 0; - for (int i = 0; i < argc; i++) { - if (strcmp(argv[i], ">>") == 0 && i + 1 < argc) { - redir_out = argv[++i]; append = 1; - } else if (strcmp(argv[i], ">") == 0 && i + 1 < argc) { - redir_out = argv[++i]; append = 0; - } else if (strcmp(argv[i], "<<") == 0 && i + 1 < argc) { - char* delim = argv[++i]; - int dlen = (int)strlen(delim); - if (dlen > 0 && (delim[0] == '"' || delim[0] == '\'')) { - delim++; dlen -= 2; if (dlen < 0) dlen = 0; - delim[dlen] = '\0'; - } - int pfd[2]; - if (pipe(pfd) == 0) { - tty_restore(); - char hline[LINE_MAX]; - while (1) { - write(STDOUT_FILENO, "> ", 2); - int hi = 0; - char hc; - while (read(STDIN_FILENO, &hc, 1) == 1) { - if (hc == '\n') break; - if (hi < LINE_MAX - 1) hline[hi++] = hc; - } - hline[hi] = '\0'; - if (strcmp(hline, delim) == 0) break; - write(pfd[1], hline, hi); - write(pfd[1], "\n", 1); - } - close(pfd[1]); - heredoc_fd = pfd[0]; - tty_raw_mode(); - } - } else if (strcmp(argv[i], "<") == 0 && i + 1 < argc) { - redir_in = argv[++i]; - } else { - argv[nargc++] = argv[i]; - } - } - argv[nargc] = NULL; - argc = nargc; - if (argc == 0) return; - - /* ---- Apply redirections for builtins too ---- */ - int saved_stdin = -1, saved_stdout = -1; - if (heredoc_fd >= 0) { - saved_stdin = dup(0); dup2(heredoc_fd, 0); close(heredoc_fd); heredoc_fd = -1; - } else if (redir_in) { - int fd = open(redir_in, O_RDONLY); - if (fd >= 0) { saved_stdin = dup(0); dup2(fd, 0); close(fd); } - } - if (redir_out) { - int flags = O_WRONLY | O_CREAT; - flags |= append ? O_APPEND : O_TRUNC; - int fd = open(redir_out, flags); - if (fd >= 0) { saved_stdout = dup(1); dup2(fd, 1); close(fd); } - } - - /* ---- Builtins ---- */ - - if (strcmp(argv[0], "exit") == 0) { - int code = argc > 1 ? atoi(argv[1]) : last_status; - exit(code); - } - - if (strcmp(argv[0], "cd") == 0) { - const char* dir = argc > 1 ? argv[1] : var_get("HOME"); - if (!dir) dir = "/"; - if (chdir(dir) < 0) - fprintf(stderr, "cd: %s: No such file or directory\n", dir); - else { - char cwd[256]; - if (getcwd(cwd, sizeof(cwd)) >= 0) - var_set("PWD", cwd, 1); - } - goto restore_redir; - } - - if (strcmp(argv[0], "pwd") == 0) { - char cwd[256]; - if (getcwd(cwd, sizeof(cwd)) >= 0) - printf("%s\n", cwd); - else - fprintf(stderr, "pwd: error\n"); - goto restore_redir; - } - - if (strcmp(argv[0], "export") == 0) { - for (int i = 1; i < argc; i++) { - char* eq = strchr(argv[i], '='); - if (eq) { - *eq = '\0'; - var_set(argv[i], eq + 1, 1); - } else { - /* Export existing variable */ - for (int j = 0; j < nvar; j++) - if (strcmp(vars[j].name, argv[i]) == 0) - vars[j].exported = 1; - } - } - goto restore_redir; - } - - if (strcmp(argv[0], "unset") == 0) { - for (int i = 1; i < argc; i++) var_unset(argv[i]); - goto restore_redir; - } - - if (strcmp(argv[0], "set") == 0) { - for (int i = 0; i < nvar; i++) - printf("%s=%s\n", vars[i].name, vars[i].value); - goto restore_redir; - } - - if (strcmp(argv[0], "echo") == 0) { - int nflag = 0; - int start = 1; - if (argc > 1 && strcmp(argv[1], "-n") == 0) { nflag = 1; start = 2; } - for (int i = start; i < argc; i++) { - if (i > start) write(STDOUT_FILENO, " ", 1); - write(STDOUT_FILENO, argv[i], strlen(argv[i])); - } - if (!nflag) write(STDOUT_FILENO, "\n", 1); - goto restore_redir; - } - - if (strcmp(argv[0], "type") == 0) { - for (int i = 1; i < argc; i++) { - if (strcmp(argv[i], "cd") == 0 || strcmp(argv[i], "exit") == 0 || - strcmp(argv[i], "echo") == 0 || strcmp(argv[i], "export") == 0 || - strcmp(argv[i], "unset") == 0 || strcmp(argv[i], "set") == 0 || - strcmp(argv[i], "pwd") == 0 || strcmp(argv[i], "type") == 0) { - printf("%s is a shell builtin\n", argv[i]); - } else { - const char* path = resolve(argv[i]); - if (strcmp(path, argv[i]) != 0) - printf("%s is %s\n", argv[i], path); - else - printf("%s: not found\n", argv[i]); - } - } - goto restore_redir; - } - - /* ---- External command — restore parent redirections before fork ---- */ - const char* path = resolve(argv[0]); - char** envp = build_envp(); - - int pid = fork(); - if (pid < 0) { fprintf(stderr, "sh: fork failed\n"); return; } - - if (pid == 0) { - /* child: own process group, restore default signals */ - setpgid(0, 0); - struct sigaction sa; - memset(&sa, 0, sizeof(sa)); - sa.sa_handler = (uintptr_t)SIG_DFL; - sigaction(SIGINT, &sa, NULL); - sigaction(SIGTSTP, &sa, NULL); - sigaction(SIGQUIT, &sa, NULL); - - if (redir_in) { - int fd = open(redir_in, O_RDONLY); - if (fd >= 0) { dup2(fd, 0); close(fd); } - } - if (redir_out) { - int flags = O_WRONLY | O_CREAT; - flags |= append ? O_APPEND : O_TRUNC; - int fd = open(redir_out, flags); - if (fd >= 0) { dup2(fd, 1); close(fd); } - } - execve(path, (const char* const*)argv, (const char* const*)envp); - fprintf(stderr, "sh: %s: not found\n", argv[0]); - _exit(127); - } - - /* parent */ - setpgid(pid, pid); - - if (bg_flag) { - /* Background: don't wait, print job info */ - printf("[bg] %d\n", pid); - last_status = 0; - } else { - /* Foreground: make child the fg process group, wait, then restore */ - set_fg_pgrp(pid); - int st; - waitpid(pid, &st, 0); - set_fg_pgrp(getpgrp()); - last_status = st; - } - goto restore_redir; - -restore_redir: - if (saved_stdout >= 0) { dup2(saved_stdout, 1); close(saved_stdout); } - if (saved_stdin >= 0) { dup2(saved_stdin, 0); close(saved_stdin); } -} - -/* ---- Pipeline support ---- */ - -static void run_pipeline(char* cmdline) { - /* Split on '|' (outside quotes) */ - char* cmds[8]; - int ncmds = 0; - cmds[0] = cmdline; - int in_sq = 0, in_dq = 0; - for (char* p = cmdline; *p; p++) { - if (*p == '\'' && !in_dq) in_sq = !in_sq; - else if (*p == '"' && !in_sq) in_dq = !in_dq; - else if (*p == '|' && !in_sq && !in_dq && ncmds < 7) { - if (*(p + 1) == '|') { p++; continue; } /* skip || */ - *p = '\0'; - cmds[++ncmds] = p + 1; - } - } - ncmds++; - - if (ncmds == 1) { - run_simple(cmds[0]); - return; - } - - /* Multi-stage pipeline */ - int prev_rd = -1; - int pids[8]; - int pgid = 0; /* pipeline process group = first child's PID */ - - for (int i = 0; i < ncmds; i++) { - int pfd[2] = {-1, -1}; - if (i < ncmds - 1) { - if (pipe(pfd) < 0) { - fprintf(stderr, "sh: pipe failed\n"); - return; - } - } - - pids[i] = fork(); - if (pids[i] < 0) { fprintf(stderr, "sh: fork failed\n"); return; } - - if (pids[i] == 0) { - /* child: join pipeline process group, restore signals */ - int mypgid = pgid ? pgid : getpid(); - setpgid(0, mypgid); - struct sigaction sa; - memset(&sa, 0, sizeof(sa)); - sa.sa_handler = (uintptr_t)SIG_DFL; - sigaction(SIGINT, &sa, NULL); - sigaction(SIGTSTP, &sa, NULL); - sigaction(SIGQUIT, &sa, NULL); - - if (prev_rd >= 0) { dup2(prev_rd, 0); close(prev_rd); } - if (pfd[1] >= 0) { dup2(pfd[1], 1); close(pfd[1]); } - if (pfd[0] >= 0) close(pfd[0]); - - /* Expand and parse this pipeline stage */ - char expanded[LINE_MAX]; - expand_vars(cmds[i], expanded, LINE_MAX); - char* argv[MAX_ARGS]; - int argc = parse_args(expanded, argv, MAX_ARGS); - if (argc == 0) _exit(0); - const char* path = resolve(argv[0]); - char** envp = build_envp(); - execve(path, (const char* const*)argv, (const char* const*)envp); - fprintf(stderr, "sh: %s: not found\n", argv[0]); - _exit(127); - } - - /* parent: set pipeline pgid */ - if (i == 0) pgid = pids[0]; - setpgid(pids[i], pgid); - - if (prev_rd >= 0) close(prev_rd); - if (pfd[1] >= 0) close(pfd[1]); - prev_rd = pfd[0]; - } - - if (prev_rd >= 0) close(prev_rd); - - if (!bg_flag) { - /* Foreground pipeline: make it fg, wait, restore */ - set_fg_pgrp(pgid); - for (int i = 0; i < ncmds; i++) { - int st; - waitpid(pids[i], &st, 0); - if (i == ncmds - 1) last_status = st; - } - set_fg_pgrp(getpgrp()); - } else { - printf("[bg] %d\n", pgid); - last_status = 0; - } -} - -/* ---- Process a command line (handle ;, &&, ||, &) ---- */ - -enum { OP_NONE = 0, OP_SEMI, OP_AND, OP_OR, OP_BG }; - -static void process_line(char* input) { - char* p = input; - - while (*p) { - while (*p == ' ' || *p == '\t') p++; - if (*p == '\0') break; - - /* Find the next operator outside quotes */ - char* start = p; - int in_sq = 0, in_dq = 0; - int op = OP_NONE; - - while (*p) { - if (*p == '\'' && !in_dq) { in_sq = !in_sq; p++; continue; } - if (*p == '"' && !in_sq) { in_dq = !in_dq; p++; continue; } - if (in_sq || in_dq) { p++; continue; } - - if (*p == '&' && *(p + 1) == '&') { - *p = '\0'; p += 2; op = OP_AND; break; - } - if (*p == '|' && *(p + 1) == '|') { - *p = '\0'; p += 2; op = OP_OR; break; - } - if (*p == ';') { - *p = '\0'; p++; op = OP_SEMI; break; - } - if (*p == '&') { - *p = '\0'; p++; op = OP_BG; break; - } - p++; - } - - /* Trim leading whitespace from segment */ - while (*start == ' ' || *start == '\t') start++; - if (*start != '\0') { - if (op == OP_BG) { - bg_flag = 1; - run_pipeline(start); - bg_flag = 0; - } else { - bg_flag = 0; - run_pipeline(start); - } - } - - /* For &&: skip remaining commands if last failed */ - if (op == OP_AND && last_status != 0) { - /* Skip until we hit || or ; or & or end */ - while (*p) { - while (*p == ' ' || *p == '\t') p++; - if (*p == '\0') break; - int skip_sq = 0, skip_dq = 0; - while (*p) { - if (*p == '\'' && !skip_dq) { skip_sq = !skip_sq; p++; continue; } - if (*p == '"' && !skip_sq) { skip_dq = !skip_dq; p++; continue; } - if (skip_sq || skip_dq) { p++; continue; } - if (*p == '|' && *(p + 1) == '|') break; - if (*p == ';') break; - if (*p == '&' && *(p + 1) != '&') break; - if (*p == '&' && *(p + 1) == '&') { p += 2; continue; } - p++; - } - break; - } - } - - /* For ||: skip remaining commands if last succeeded */ - if (op == OP_OR && last_status == 0) { - while (*p) { - while (*p == ' ' || *p == '\t') p++; - if (*p == '\0') break; - int skip_sq = 0, skip_dq = 0; - while (*p) { - if (*p == '\'' && !skip_dq) { skip_sq = !skip_sq; p++; continue; } - if (*p == '"' && !skip_sq) { skip_dq = !skip_dq; p++; continue; } - if (skip_sq || skip_dq) { p++; continue; } - if (*p == '&' && *(p + 1) == '&') break; - if (*p == ';') break; - if (*p == '&' && *(p + 1) != '&') break; - if (*p == '|' && *(p + 1) == '|') { p += 2; continue; } - p++; - } - break; - } - } - } -} - -/* ---- Prompt ---- */ - -static void print_prompt(void) { - const char* user = var_get("USER"); - const char* host = var_get("HOSTNAME"); - char cwd[256]; - - if (!user) user = "root"; - if (!host) host = "adros"; - - if (getcwd(cwd, sizeof(cwd)) < 0) strcpy(cwd, "?"); - - printf("%s@%s:%s$ ", user, host, cwd); - fflush(stdout); -} - -/* ---- Main ---- */ - -int main(int argc, char** argv, char** envp) { - (void)argc; - (void)argv; - - /* Import environment variables */ - if (envp) { - for (int i = 0; envp[i]; i++) { - char* eq = strchr(envp[i], '='); - if (eq) { - char name[64]; - int nlen = (int)(eq - envp[i]); - if (nlen > 63) nlen = 63; - memcpy(name, envp[i], (size_t)nlen); - name[nlen] = '\0'; - var_set(name, eq + 1, 1); - } - } - } - - /* Set defaults */ - if (!var_get("PATH")) - var_set("PATH", "/bin:/sbin:/usr/bin", 1); - if (!var_get("HOME")) - var_set("HOME", "/", 1); - - /* Job control: create session + process group, become fg */ - setsid(); - set_fg_pgrp(getpgrp()); - - /* Ignore job control signals in the shell itself */ - struct sigaction sa_ign; - memset(&sa_ign, 0, sizeof(sa_ign)); - sa_ign.sa_handler = (uintptr_t)SIG_IGN; - sigaction(SIGINT, &sa_ign, NULL); - sigaction(SIGTSTP, &sa_ign, NULL); - sigaction(SIGQUIT, &sa_ign, NULL); - - tty_raw_mode(); - - print_prompt(); - while (1) { - int len = read_line_edit(); - if (len < 0) break; - if (len > 0) { - hist_add(line); - tty_restore(); - process_line(line); - tty_raw_mode(); - } - print_prompt(); - } - - tty_restore(); - return last_status; -} diff --git a/user/sleep.c b/user/sleep.c deleted file mode 100644 index 9da1be1..0000000 --- a/user/sleep.c +++ /dev/null @@ -1,18 +0,0 @@ -/* AdrOS sleep utility — pause for N seconds */ -#include -#include -#include -#include - -int main(int argc, char** argv) { - if (argc <= 1) { - fprintf(stderr, "usage: sleep SECONDS\n"); - return 1; - } - int secs = atoi(argv[1]); - if (secs > 0) { - struct timespec ts = { .tv_sec = secs, .tv_nsec = 0 }; - nanosleep(&ts, NULL); - } - return 0; -} diff --git a/user/sort.c b/user/sort.c deleted file mode 100644 index 70cc61d..0000000 --- a/user/sort.c +++ /dev/null @@ -1,91 +0,0 @@ -/* AdrOS sort utility */ -#include -#include -#include -#include -#include - -#define MAX_LINES 1024 -#define LINE_BUF 65536 - -static char linebuf[LINE_BUF]; -static char* lines[MAX_LINES]; -static int nlines = 0; - -static int rflag = 0; /* -r: reverse */ -static int nflag = 0; /* -n: numeric */ - -static int cmp(const void* a, const void* b) { - const char* sa = *(const char**)a; - const char* sb = *(const char**)b; - int r; - if (nflag) { - r = atoi(sa) - atoi(sb); - } else { - r = strcmp(sa, sb); - } - return rflag ? -r : r; -} - -static void read_lines(int fd) { - int total = 0; - int r; - while ((r = read(fd, linebuf + total, (size_t)(LINE_BUF - total - 1))) > 0) { - total += r; - if (total >= LINE_BUF - 1) break; - } - linebuf[total] = '\0'; - - /* Split into lines */ - char* p = linebuf; - while (*p && nlines < MAX_LINES) { - lines[nlines++] = p; - while (*p && *p != '\n') p++; - if (*p == '\n') *p++ = '\0'; - } -} - -int main(int argc, char** argv) { - int start = 1; - for (int i = 1; i < argc; i++) { - if (argv[i][0] == '-') { - const char* f = argv[i] + 1; - while (*f) { - if (*f == 'r') rflag = 1; - else if (*f == 'n') nflag = 1; - f++; - } - start = i + 1; - } else break; - } - - if (start >= argc) { - read_lines(STDIN_FILENO); - } else { - for (int i = start; i < argc; i++) { - int fd = open(argv[i], O_RDONLY); - if (fd < 0) { - fprintf(stderr, "sort: cannot open '%s'\n", argv[i]); - return 1; - } - read_lines(fd); - close(fd); - } - } - - /* Simple insertion sort (no qsort in ulibc yet) */ - for (int i = 1; i < nlines; i++) { - char* key = lines[i]; - int j = i - 1; - while (j >= 0 && cmp(&lines[j], &key) > 0) { - lines[j + 1] = lines[j]; - j--; - } - lines[j + 1] = key; - } - - for (int i = 0; i < nlines; i++) - printf("%s\n", lines[i]); - - return 0; -} diff --git a/user/stat.c b/user/stat.c deleted file mode 100644 index 7083d29..0000000 --- a/user/stat.c +++ /dev/null @@ -1,25 +0,0 @@ -/* AdrOS stat utility — display file status */ -#include -#include -#include -#include - -int main(int argc, char** argv) { - if (argc <= 1) { - fprintf(stderr, "usage: stat FILE...\n"); - return 1; - } - int rc = 0; - for (int i = 1; i < argc; i++) { - struct stat st; - if (stat(argv[i], (void*)&st) < 0) { - fprintf(stderr, "stat: cannot stat '%s'\n", argv[i]); - rc = 1; - continue; - } - printf(" File: %s\n", argv[i]); - printf(" Size: %u\tInode: %u\n", (unsigned)st.st_size, (unsigned)st.st_ino); - printf(" Mode: %o\tUid: %u\tGid: %u\n", (unsigned)st.st_mode, (unsigned)st.st_uid, (unsigned)st.st_gid); - } - return rc; -} diff --git a/user/tail.c b/user/tail.c deleted file mode 100644 index 200e78e..0000000 --- a/user/tail.c +++ /dev/null @@ -1,60 +0,0 @@ -/* AdrOS tail utility */ -#include -#include -#include -#include -#include - -#define TAIL_BUFSZ 8192 - -static void tail_fd(int fd, int nlines) { - /* Read entire file into buffer, then print last N lines */ - char buf[TAIL_BUFSZ]; - int total = 0; - int r; - while ((r = read(fd, buf + total, (size_t)(TAIL_BUFSZ - total))) > 0) { - total += r; - if (total >= TAIL_BUFSZ) break; - } - - /* Count newlines from end; skip trailing newline */ - int count = 0; - int pos = total; - if (pos > 0 && buf[pos - 1] == '\n') pos--; - while (pos > 0 && count < nlines) { - pos--; - if (buf[pos] == '\n') count++; - } - if (pos > 0 || (pos == 0 && buf[0] == '\n')) pos++; - - write(STDOUT_FILENO, buf + pos, (size_t)(total - pos)); -} - -int main(int argc, char** argv) { - int nlines = 10; - int start = 1; - - if (argc > 1 && argv[1][0] == '-' && argv[1][1] == 'n' && argc > 2) { - nlines = atoi(argv[2]); - start = 3; - } else if (argc > 1 && argv[1][0] == '-' && argv[1][1] >= '0' && argv[1][1] <= '9') { - nlines = atoi(argv[1] + 1); - start = 2; - } - - if (start >= argc) { - tail_fd(STDIN_FILENO, nlines); - } else { - for (int i = start; i < argc; i++) { - if (argc - start > 1) printf("==> %s <==\n", argv[i]); - int fd = open(argv[i], O_RDONLY); - if (fd < 0) { - fprintf(stderr, "tail: cannot open '%s'\n", argv[i]); - continue; - } - tail_fd(fd, nlines); - close(fd); - } - } - return 0; -} diff --git a/user/tee.c b/user/tee.c deleted file mode 100644 index 52a32c4..0000000 --- a/user/tee.c +++ /dev/null @@ -1,34 +0,0 @@ -/* AdrOS tee utility — read stdin, write to stdout and files */ -#include -#include -#include -#include - -int main(int argc, char** argv) { - int aflag = 0; - int fds[16]; - int nfds = 0; - - for (int i = 1; i < argc && nfds < 16; i++) { - if (strcmp(argv[i], "-a") == 0) { aflag = 1; continue; } - int flags = O_WRONLY | O_CREAT; - flags |= aflag ? O_APPEND : O_TRUNC; - int fd = open(argv[i], flags, 0644); - if (fd < 0) { - fprintf(stderr, "tee: %s: cannot open\n", argv[i]); - continue; - } - fds[nfds++] = fd; - } - - char buf[4096]; - int n; - while ((n = read(STDIN_FILENO, buf, sizeof(buf))) > 0) { - write(STDOUT_FILENO, buf, (size_t)n); - for (int i = 0; i < nfds; i++) - write(fds[i], buf, (size_t)n); - } - - for (int i = 0; i < nfds; i++) close(fds[i]); - return 0; -} diff --git a/user/top.c b/user/top.c deleted file mode 100644 index 59995e8..0000000 --- a/user/top.c +++ /dev/null @@ -1,69 +0,0 @@ -/* AdrOS top utility — one-shot process listing with basic info */ -#include -#include -#include -#include -#include - -static int is_digit(char c) { return c >= '0' && c <= '9'; } - -int main(void) { - printf(" PID STATE CMD\n"); - int fd = open("/proc", O_RDONLY); - if (fd < 0) { - fprintf(stderr, "top: cannot open /proc\n"); - return 1; - } - char buf[512]; - int rc; - while ((rc = getdents(fd, buf, sizeof(buf))) > 0) { - int off = 0; - while (off < rc) { - struct dirent* d = (struct dirent*)(buf + off); - if (d->d_reclen == 0) break; - if (is_digit(d->d_name[0])) { - char path[64]; - - /* Read cmdline */ - snprintf(path, sizeof(path), "/proc/%s/cmdline", d->d_name); - int cfd = open(path, O_RDONLY); - char cmd[64] = "[kernel]"; - if (cfd >= 0) { - int n = read(cfd, cmd, sizeof(cmd) - 1); - if (n > 0) { - cmd[n] = '\0'; - while (n > 0 && (cmd[n-1] == '\n' || cmd[n-1] == '\0')) cmd[--n] = '\0'; - } - if (n <= 0) strcpy(cmd, "[kernel]"); - close(cfd); - } - - /* Read status for state */ - snprintf(path, sizeof(path), "/proc/%s/status", d->d_name); - int sfd = open(path, O_RDONLY); - char state[16] = "?"; - if (sfd >= 0) { - char sbuf[256]; - int sn = read(sfd, sbuf, sizeof(sbuf) - 1); - if (sn > 0) { - sbuf[sn] = '\0'; - char* st = strstr(sbuf, "State:"); - if (st) { - st += 6; - while (*st == ' ' || *st == '\t') st++; - int si = 0; - while (*st && *st != '\n' && si < 15) state[si++] = *st++; - state[si] = '\0'; - } - } - close(sfd); - } - - printf("%5s %6s %s\n", d->d_name, state, cmd); - } - off += d->d_reclen; - } - } - close(fd); - return 0; -} diff --git a/user/touch.c b/user/touch.c deleted file mode 100644 index 3e2da84..0000000 --- a/user/touch.c +++ /dev/null @@ -1,23 +0,0 @@ -/* AdrOS touch utility */ -#include -#include -#include - -int main(int argc, char** argv) { - if (argc < 2) { - fprintf(stderr, "Usage: touch ...\n"); - return 1; - } - - int rc = 0; - for (int i = 1; i < argc; i++) { - int fd = open(argv[i], O_WRONLY | O_CREAT, 0644); - if (fd < 0) { - fprintf(stderr, "touch: cannot touch '%s'\n", argv[i]); - rc = 1; - } else { - close(fd); - } - } - return rc; -} diff --git a/user/tr.c b/user/tr.c deleted file mode 100644 index cf7606f..0000000 --- a/user/tr.c +++ /dev/null @@ -1,44 +0,0 @@ -/* AdrOS tr utility — translate or delete characters */ -#include -#include -#include - -int main(int argc, char** argv) { - int delete_mode = 0; - int start = 1; - - if (argc > 1 && strcmp(argv[1], "-d") == 0) { - delete_mode = 1; - start = 2; - } - - if (delete_mode) { - if (start >= argc) { fprintf(stderr, "usage: tr -d SET1\n"); return 1; } - const char* set1 = argv[start]; - char c; - while (read(STDIN_FILENO, &c, 1) > 0) { - if (!strchr(set1, c)) - write(STDOUT_FILENO, &c, 1); - } - } else { - if (start + 1 >= argc) { fprintf(stderr, "usage: tr SET1 SET2\n"); return 1; } - const char* set1 = argv[start]; - const char* set2 = argv[start + 1]; - int len1 = (int)strlen(set1); - int len2 = (int)strlen(set2); - char c; - while (read(STDIN_FILENO, &c, 1) > 0) { - int found = 0; - for (int i = 0; i < len1; i++) { - if (c == set1[i]) { - char r = (i < len2) ? set2[i] : set2[len2 - 1]; - write(STDOUT_FILENO, &r, 1); - found = 1; - break; - } - } - if (!found) write(STDOUT_FILENO, &c, 1); - } - } - return 0; -} diff --git a/user/ulibc/Makefile b/user/ulibc/Makefile index 844ea41..9a86fe5 100644 --- a/user/ulibc/Makefile +++ b/user/ulibc/Makefile @@ -4,8 +4,8 @@ AS ?= i686-elf-as AR ?= i686-elf-ar LD ?= i686-elf-ld -CFLAGS := -m32 -ffreestanding -fno-pie -no-pie -nostdlib -O2 -Wall -Wextra -Iinclude -CFLAGS_PIC := -m32 -ffreestanding -nostdlib -O2 -Wall -Wextra -Iinclude -fPIC -fno-plt +CFLAGS := -m32 -ffreestanding -fno-pie -no-pie -nostdlib -O2 -Wall -Wextra -Wno-incompatible-pointer-types -Iinclude +CFLAGS_PIC := -m32 -ffreestanding -nostdlib -O2 -Wall -Wextra -Wno-incompatible-pointer-types -Iinclude -fPIC -fno-plt ASFLAGS := --32 SRC_C := $(wildcard src/*.c) diff --git a/user/ulibc/src/glob.c b/user/ulibc/src/glob.c index b83507a..37fa3df 100644 --- a/user/ulibc/src/glob.c +++ b/user/ulibc/src/glob.c @@ -2,6 +2,7 @@ #include "fnmatch.h" #include "string.h" #include "stdlib.h" +#include "stdio.h" #include "unistd.h" #include "dirent.h" #include "errno.h" diff --git a/user/umount.c b/user/umount.c deleted file mode 100644 index 1396c94..0000000 --- a/user/umount.c +++ /dev/null @@ -1,11 +0,0 @@ -/* AdrOS umount utility — stub (no SYS_UMOUNT syscall yet) */ -#include - -int main(int argc, char** argv) { - if (argc <= 1) { - fprintf(stderr, "umount: missing operand\n"); - return 1; - } - fprintf(stderr, "umount: %s: operation not supported\n", argv[1]); - return 1; -} diff --git a/user/uname.c b/user/uname.c deleted file mode 100644 index 250fe05..0000000 --- a/user/uname.c +++ /dev/null @@ -1,34 +0,0 @@ -/* AdrOS uname utility — print system information */ -#include -#include - -int main(int argc, char** argv) { - const char* sysname = "AdrOS"; - const char* nodename = "adros"; - const char* release = "0.1.0"; - const char* version = "AdrOS x86 SMP"; - const char* machine = "i686"; - - if (argc <= 1) { printf("%s\n", sysname); return 0; } - - int all = 0, s = 0, n = 0, r = 0, v = 0, m = 0; - for (int i = 1; i < argc; i++) { - if (strcmp(argv[i], "-a") == 0) all = 1; - else if (strcmp(argv[i], "-s") == 0) s = 1; - else if (strcmp(argv[i], "-n") == 0) n = 1; - else if (strcmp(argv[i], "-r") == 0) r = 1; - else if (strcmp(argv[i], "-v") == 0) v = 1; - else if (strcmp(argv[i], "-m") == 0) m = 1; - } - if (all) { s = n = r = v = m = 1; } - if (!s && !n && !r && !v && !m) s = 1; - - int first = 1; - if (s) { printf("%s%s", first ? "" : " ", sysname); first = 0; } - if (n) { printf("%s%s", first ? "" : " ", nodename); first = 0; } - if (r) { printf("%s%s", first ? "" : " ", release); first = 0; } - if (v) { printf("%s%s", first ? "" : " ", version); first = 0; } - if (m) { printf("%s%s", first ? "" : " ", machine); first = 0; } - printf("\n"); - return 0; -} diff --git a/user/uniq.c b/user/uniq.c deleted file mode 100644 index 60fc2f1..0000000 --- a/user/uniq.c +++ /dev/null @@ -1,85 +0,0 @@ -/* AdrOS uniq utility */ -#include -#include -#include -#include - -#define LINE_MAX 1024 - -static int cflag = 0; /* -c: prefix lines with count */ -static int dflag = 0; /* -d: only print duplicates */ - -static int readline(int fd, char* buf, int max) { - int n = 0; - char c; - while (n < max - 1) { - int r = read(fd, &c, 1); - if (r <= 0) break; - if (c == '\n') break; - buf[n++] = c; - } - buf[n] = '\0'; - return n > 0 ? n : (n == 0 ? 0 : -1); -} - -int main(int argc, char** argv) { - int start = 1; - int fd = STDIN_FILENO; - - for (int i = 1; i < argc; i++) { - if (argv[i][0] == '-' && argv[i][1]) { - const char* f = argv[i] + 1; - while (*f) { - if (*f == 'c') cflag = 1; - else if (*f == 'd') dflag = 1; - f++; - } - start = i + 1; - } else break; - } - - if (start < argc) { - fd = open(argv[start], O_RDONLY); - if (fd < 0) { - fprintf(stderr, "uniq: cannot open '%s'\n", argv[start]); - return 1; - } - } - - char prev[LINE_MAX] = {0}; - char cur[LINE_MAX]; - int count = 0; - int first = 1; - - while (1) { - int r = readline(fd, cur, LINE_MAX); - if (r < 0) break; - - if (first || strcmp(cur, prev) != 0) { - if (!first) { - if (!dflag || count > 1) { - if (cflag) printf("%7d %s\n", count, prev); - else printf("%s\n", prev); - } - } - strcpy(prev, cur); - count = 1; - first = 0; - } else { - count++; - } - - if (r == 0) break; - } - - /* Print last line */ - if (!first) { - if (!dflag || count > 1) { - if (cflag) printf("%7d %s\n", count, prev); - else printf("%s\n", prev); - } - } - - if (fd != STDIN_FILENO) close(fd); - return 0; -} diff --git a/user/uptime.c b/user/uptime.c deleted file mode 100644 index 2f81539..0000000 --- a/user/uptime.c +++ /dev/null @@ -1,40 +0,0 @@ -/* AdrOS uptime utility */ -#include -#include -#include -#include - -int main(int argc, char** argv) { - (void)argc; (void)argv; - - /* Try /proc/uptime first */ - int fd = open("/proc/uptime", O_RDONLY); - if (fd >= 0) { - char buf[64]; - int r = read(fd, buf, sizeof(buf) - 1); - close(fd); - if (r > 0) { - buf[r] = '\0'; - printf("up %s", buf); - return 0; - } - } - - /* Fallback: use CLOCK_MONOTONIC */ - struct timespec ts; - if (clock_gettime(CLOCK_MONOTONIC, &ts) < 0) { - fprintf(stderr, "uptime: cannot get time\n"); - return 1; - } - - unsigned long sec = ts.tv_sec; - unsigned long days = sec / 86400; - unsigned long hours = (sec % 86400) / 3600; - unsigned long mins = (sec % 3600) / 60; - unsigned long secs = sec % 60; - - printf("up"); - if (days > 0) printf(" %lu day%s,", days, days > 1 ? "s" : ""); - printf(" %02lu:%02lu:%02lu\n", hours, mins, secs); - return 0; -} diff --git a/user/user_errno.h b/user/user_errno.h deleted file mode 100644 index 6681cc9..0000000 --- a/user/user_errno.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef USER_ERRNO_H -#define USER_ERRNO_H - -extern int errno; - -static inline int __syscall_fix(int ret) { - if (ret < 0) { - errno = -ret; - return -1; - } - return ret; -} - -#endif diff --git a/user/wc.c b/user/wc.c deleted file mode 100644 index 76333eb..0000000 --- a/user/wc.c +++ /dev/null @@ -1,69 +0,0 @@ -/* AdrOS wc utility */ -#include -#include -#include -#include - -static void wc_fd(int fd, const char* name, int show_l, int show_w, int show_c) { - char buf[4096]; - int lines = 0, words = 0, chars = 0; - int in_word = 0; - int r; - - while ((r = read(fd, buf, sizeof(buf))) > 0) { - for (int i = 0; i < r; i++) { - chars++; - if (buf[i] == '\n') lines++; - if (buf[i] == ' ' || buf[i] == '\t' || buf[i] == '\n') { - in_word = 0; - } else if (!in_word) { - in_word = 1; - words++; - } - } - } - - if (show_l) printf("%7d", lines); - if (show_w) printf("%7d", words); - if (show_c) printf("%7d", chars); - if (name) printf(" %s", name); - printf("\n"); -} - -int main(int argc, char** argv) { - int show_l = 0, show_w = 0, show_c = 0; - int start = 1; - - for (int i = 1; i < argc; i++) { - if (argv[i][0] == '-' && argv[i][1]) { - const char* f = argv[i] + 1; - while (*f) { - if (*f == 'l') show_l = 1; - else if (*f == 'w') show_w = 1; - else if (*f == 'c') show_c = 1; - f++; - } - start = i + 1; - } else break; - } - - /* Default: show all */ - if (!show_l && !show_w && !show_c) { - show_l = show_w = show_c = 1; - } - - if (start >= argc) { - wc_fd(STDIN_FILENO, NULL, show_l, show_w, show_c); - } else { - for (int i = start; i < argc; i++) { - int fd = open(argv[i], O_RDONLY); - if (fd < 0) { - fprintf(stderr, "wc: %s: No such file\n", argv[i]); - continue; - } - wc_fd(fd, argv[i], show_l, show_w, show_c); - close(fd); - } - } - return 0; -} diff --git a/user/which.c b/user/which.c deleted file mode 100644 index 9fa5c20..0000000 --- a/user/which.c +++ /dev/null @@ -1,50 +0,0 @@ -/* AdrOS which utility — locate a command */ -#include -#include -#include -#include -#include - -static int exists_in_dir(const char* dir, const char* name) { - int fd = open(dir, O_RDONLY); - if (fd < 0) return 0; - char buf[2048]; - int rc; - while ((rc = getdents(fd, buf, sizeof(buf))) > 0) { - int off = 0; - while (off < rc) { - struct dirent* d = (struct dirent*)(buf + off); - if (d->d_reclen == 0) break; - if (strcmp(d->d_name, name) == 0) { - close(fd); - return 1; - } - off += d->d_reclen; - } - } - close(fd); - return 0; -} - -int main(int argc, char** argv) { - if (argc < 2) { - fprintf(stderr, "Usage: which command\n"); - return 1; - } - - static const char* path_dirs[] = { "/bin", "/sbin", NULL }; - int ret = 1; - - for (int i = 1; i < argc; i++) { - int found = 0; - for (int d = 0; path_dirs[d]; d++) { - if (exists_in_dir(path_dirs[d], argv[i])) { - printf("%s/%s\n", path_dirs[d], argv[i]); - found = 1; - break; - } - } - if (found) ret = 0; - } - return ret; -} diff --git a/user/who.c b/user/who.c deleted file mode 100644 index b8fc948..0000000 --- a/user/who.c +++ /dev/null @@ -1,8 +0,0 @@ -/* AdrOS who utility — show logged-in users */ -#include -#include - -int main(void) { - printf("root tty1 Jan 1 00:00\n"); - return 0; -}