From ee69e75f4f3b68572c81a2e25d6b111741212212 Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Fri, 11 Sep 2026 16:06:53 +0800 Subject: [PATCH] feat(kernel): implement comprehensive POSIX syscall support for TypePHP-OS - Added Linux-compatible file metadata syscalls (stat, lstat, fstat, newfstatat) - Implemented access control checks with faccessat and related flag definitions - Added file persistence operations (fsync, fdatasync, truncate, ftruncate) - Integrated clock syscalls (gettimeofday, clock_gettime, clock_getres) - Added process identity queries (getpid, getppid, gettid, getuid, getgid) - Changed exit mechanism from exit(60) to exit_group(231) for consistency - Updated crt0.S with Linux x86-64 exit_group ABI compatibility - Extended fcntl.h with AT_SYMLINK_NOFOLLOW and AT_EACCESS constants - Added complete syscalls implementation in user/libc.c and runtime/posix.c - Implemented kernel-side syscall handlers in process.c with proper validation - Added systest utility to verify file metadata, persistence and identity calls - Updated Makefile dependencies to track user header changes - Enhanced README documentation for new --- examples/typephp-os/Makefile | 7 +- examples/typephp-os/README.md | 25 ++- examples/typephp-os/ROADMAP.md | 13 +- examples/typephp-os/kernel/core/abi/libc.c | 13 ++ .../kernel/core/abi/typephp_os_syscall.h | 21 ++ .../kernel/core/abi/unimplemented.S | 2 - examples/typephp-os/kernel/core/process.c | 179 +++++++++++++++++- examples/typephp-os/project.yml | 1 - examples/typephp-os/tools/test-qemu.sh | 13 +- examples/typephp-os/user/README.md | 28 ++- examples/typephp-os/user/include/fcntl.h | 2 + examples/typephp-os/user/include/sys/stat.h | 34 +++- .../typephp-os/user/include/sys/syscall.h | 21 ++ examples/typephp-os/user/include/sys/time.h | 13 ++ examples/typephp-os/user/include/sys/types.h | 20 ++ examples/typephp-os/user/include/time.h | 12 +- examples/typephp-os/user/include/unistd.h | 22 ++- examples/typephp-os/user/libc.c | 103 +++++++++- examples/typephp-os/user/nano/hello.php | 2 + examples/typephp-os/user/nano/hello.yml | 1 - examples/typephp-os/user/runtime/README.md | 6 +- examples/typephp-os/user/runtime/crt0.S | 3 +- examples/typephp-os/user/runtime/posix.c | 95 +++++++--- examples/typephp-os/user/sh.c | 4 +- examples/typephp-os/user/systest.c | 73 +++++++ examples/typephp-os/user/uname.c | 20 +- 26 files changed, 667 insertions(+), 66 deletions(-) create mode 100644 examples/typephp-os/user/include/sys/time.h create mode 100644 examples/typephp-os/user/include/sys/types.h create mode 100644 examples/typephp-os/user/systest.c diff --git a/examples/typephp-os/Makefile b/examples/typephp-os/Makefile index ccbcad8e..0954cee4 100644 --- a/examples/typephp-os/Makefile +++ b/examples/typephp-os/Makefile @@ -7,7 +7,7 @@ PAYLOAD_BIN := $(BUILD)/kernel64.bin PAYLOAD_OBJECT := $(OBJECT_DIR)/kernel64-payload.o KERNEL := $(BUILD)/typephp-os.elf DISK := $(BUILD)/typephp-os.img -USER_COMMANDS := ls cd date uname pwd cat echo write touch mkdir rm rmdir mv fault vmfault wrfault memtest +USER_COMMANDS := ls cd date uname pwd cat echo write touch mkdir rm rmdir mv fault vmfault wrfault memtest systest USER_PROGRAMS := sh $(USER_COMMANDS) USER_SH_ELF := $(BUILD)/sh.elf USER_COMMAND_ELFS := $(addprefix $(BUILD)/,$(addsuffix .elf,$(USER_COMMANDS))) @@ -38,6 +38,7 @@ KERNEL64_BOOTFLAGS := -m64 -O2 -ffreestanding -fno-builtin -fno-pic -fno-pie \ -fno-stack-protector -mno-red-zone -mcmodel=small -Wall -Wextra USER_CFLAGS := -m64 -O2 -ffreestanding -fno-builtin -fno-pic -fno-pie \ -fno-stack-protector -mno-red-zone -Wall -Wextra -Iuser/include -Ikernel/core/abi +USER_HEADERS := $(shell find user/include -type f) USER_LDFLAGS := -m elf_x86_64 -nostdlib -static -T user/linker.ld TYPEPHP_OS_RUNTIME_DEFINES := -DTYPEPHP_NATIVE=1 -DPHP_NANO=1 -DPHPX_NANO=1 \ -D_POSIX_C_SOURCE=200809L -DTYPEPHP_NO_MAIN=1 -DPHPX_NO_EXCEPTION=1 \ @@ -105,13 +106,13 @@ $(BOOTSTRAP_OBJECT): boot/boot.S | $(OBJECT_DIR) $(KERNEL64_ENTRY_OBJECT): boot/kernel64-entry.S | $(OBJECT_DIR) gcc $(KERNEL64_BOOTFLAGS) -c $< -o $@ -$(OBJECT_DIR)/%.o: user/%.c kernel/core/abi/typephp_os_syscall.h | $(OBJECT_DIR) +$(OBJECT_DIR)/%.o: user/%.c $(USER_HEADERS) kernel/core/abi/typephp_os_syscall.h | $(OBJECT_DIR) gcc $(USER_CFLAGS) -c $< -o $@ $(USER_CRT0_OBJECT): user/crt0.S kernel/core/abi/typephp_os_syscall.h | $(OBJECT_DIR) gcc $(USER_CFLAGS) -c $< -o $@ -$(OBJECT_DIR)/user-libc.o: user/libc.c kernel/core/abi/typephp_os_syscall.h | $(OBJECT_DIR) +$(OBJECT_DIR)/user-libc.o: user/libc.c $(USER_HEADERS) kernel/core/abi/typephp_os_syscall.h | $(OBJECT_DIR) gcc $(USER_CFLAGS) -c $< -o $@ $(OBJECT_DIR)/user-syscall.o: user/syscall.S kernel/core/abi/typephp_os_syscall.h | $(OBJECT_DIR) diff --git a/examples/typephp-os/README.md b/examples/typephp-os/README.md index 6569ff02..74049608 100644 --- a/examples/typephp-os/README.md +++ b/examples/typephp-os/README.md @@ -143,6 +143,8 @@ The QEMU smoke test currently verifies: `std::vector`; - OpenLibm implementations of the ordinary double-precision math ABI; - RTC-derived UTC time exposed to userspace through `time()`; +- Linux-compatible `uname(2)` shared by the `uname` command and PHP's + `php_uname()` implementation; - ATA PIO sector I/O, a fixed 128-sector LRU read/write-through cache, and a TypePHP FAT16 implementation with DOS 8.3 files, nested traversal and mutation, including automatic directory-chain growth; @@ -158,6 +160,11 @@ The QEMU smoke test currently verifies: `RX`/`RW` segment permissions; - Linux-numbered `brk`, anonymous private `mmap`, `mprotect`, and `munmap` calls backed by recyclable physical pages; +- Linux-compatible file metadata and persistence calls (`stat`, `lstat`, + `fstat`, `newfstatat`, `access`, `faccessat`, `fsync`, `fdatasync`, + `truncate`, and `ftruncate`), exercised from both C and PHP Nano; +- single-task PID/TID and root UID/GID queries plus `gettimeofday`, + `clock_gettime`, `clock_getres`, and `exit_group`; - dynamic command discovery and safe rejection of malformed ELF files; - recovery from invalid opcodes, cross-address-space reads, and writes to read-only mappings without losing the resident shell; @@ -191,9 +198,11 @@ supervisor-only. The CPU has write protection and no-execute enabled, and the loader applies the final ELF `PF_W` and `PF_X` permissions after copying each segment. An `int 0x80` boundary currently provides synchronous `read`, `write`, `close`, -`lseek`, `openat`, `exit`, `getcwd`, `chdir`, `mkdir`, `rmdir`, `unlink`, -`time`, `brk`, anonymous private `mmap`, `mprotect`, `munmap`, and private -spawn, directory-list, and same-directory rename operations. Standard +`lseek`, `openat`, `exit`/`exit_group`, `getcwd`, `chdir`, `mkdir`, `rmdir`, +`unlink`, file stat/access/persistence/truncation families, fixed identity +queries, `time`, `gettimeofday`, `clock_gettime`, `clock_getres`, `brk`, +anonymous private `mmap`, `mprotect`, `munmap`, and private spawn, +directory-list, and same-directory rename operations. Standard input and output are backed by QEMU's COM1 serial console. Syscall numbers are shared by the kernel and userspace through `typephp_os_syscall.h`. @@ -216,7 +225,10 @@ user context runs at a time: while a command is active, the kernel keeps the shell register frame and restores it when the command calls `exit`. Because this is a deliberately single-task model, the working directory is a session-global property; a successful `cd` therefore remains visible after -control returns to the shell. +control returns to the shell. `fork`, `clone`, `execve`, `wait`, pipes, +threads, scheduling signals, and job control are intentional non-goals. The +private synchronous shell launch syscall must not be exposed as POSIX +`execve()` semantics. `TNHELLO.ELF` demonstrates the intended TypePHP userspace path. It is built by the ordinary tpc Nano pipeline, links the complete php-nano and PHPX manifests, @@ -253,6 +265,7 @@ mkdir TMP rm NOTE.TXT rmdir TMP mv OLD.TXT NEW.TXT +systest cd DOCS pwd ls @@ -264,7 +277,9 @@ invalid opcode, an attempted read from the shell's virtual address, and an attempted write to a read-only `mmap()` page. The kernel reports each Ring-3 exception, destroys the faulty address space, and restores the shell. The `memtest` command exercises `sbrk()`, anonymous mapping, protection changes, -and unmapping. User exceptions for divide errors, breakpoints, bounds, invalid +and unmapping. `systest` covers file metadata, persistence/truncation, fixed +single-task identities, and wall/monotonic clock ABIs. User exceptions for +divide errors, breakpoints, bounds, invalid opcodes, invalid TSS/segments, stack faults, general-protection faults, and page faults have IDT entries. A fault raised in Ring 0 still causes an immediate kernel panic. diff --git a/examples/typephp-os/ROADMAP.md b/examples/typephp-os/ROADMAP.md index 1fa3da54..aba65bb5 100644 --- a/examples/typephp-os/ROADMAP.md +++ b/examples/typephp-os/ROADMAP.md @@ -24,6 +24,10 @@ behavior. packaging and ELF/binary conversion happen after tpc emits the 64-bit ELF. - Every completed milestone must pass the serial-output QEMU smoke test and leave the 64-bit payload with no undefined symbols. +- TypePHP-OS is a single-task system by design. `fork`, `clone`, `execve`, + `wait`, pipes, threads, scheduling signals, and job control will not be + implemented. The shell may only launch one synchronous foreground ELF + through the private TypePHP-OS service. ## Milestones @@ -46,14 +50,15 @@ behavior. read/write-through cache now avoids repeated ATA PIO reads while preserving synchronous persistence. A general VFS page cache, cross-directory rename, replacement semantics, and long filenames are next. -6. **Single-task userspace — fourth slice complete.** Disk-backed ELF64 +6. **Single-task userspace — fifth slice complete.** Disk-backed ELF64 validation/loading from FAT16, GDT/TSS, Ring-3 entry, synchronous `int 0x80` system calls, COM1 standard I/O, saved parent context, shared syscall definitions, complete `argv[]` delivery, and independent freestanding C shell/command programs. The first Linux-compatible file syscalls cover `openat`, `read`, `write`, `lseek`, - `close`, `mkdir`, `rmdir`, and `unlink`; `cat`, `write`, `touch`, and the + `close`, `mkdir`, `rmdir`, `unlink`, the stat/access families, synchronous + persistence, and file truncation; `cat`, `write`, `touch`, and the directory commands exercise persistent FAT16 changes. The `mv` command exposes same-parent rename through a private syscall until full Linux rename semantics are implemented. The resident shell @@ -74,6 +79,10 @@ behavior. reports TypePHP-OS. Shared TypePHP userspace support is compiled once into `libtypephp-os.a`, allowing multiple tpc projects to link the same runtime platform archive. Unavailable ABI functions remain explicit panic stubs. + Single-task identity calls report PID/TID 1 for the resident shell, 2 for + its synchronous foreground command, and root UID/GID. Linux-compatible + wall/monotonic clock structures are exposed at the current one-second RTC + resolution. The `systest` ELF exercises this complete syscall slice. Syscall evolution follows the glibc migration rules in `user/README.md`. 7. **Native Class memory.** Exercise Wren GC through Zend MM and verify tracing of PHPX fields under sustained allocation. diff --git a/examples/typephp-os/kernel/core/abi/libc.c b/examples/typephp-os/kernel/core/abi/libc.c index 086c8d5c..8e35bf15 100644 --- a/examples/typephp-os/kernel/core/abi/libc.c +++ b/examples/typephp-os/kernel/core/abi/libc.c @@ -50,6 +50,19 @@ void *malloc(size_t size); int posix_memalign(void **result, size_t alignment, size_t size); int vsnprintf(char *buffer, size_t size, const char *format, va_list args); +/* php-src's file metadata helpers query the current identity even in the + * freestanding kernel. The weak defaults model TypePHP-OS's sole root user; + * a userspace libc can override them with syscall-backed strong symbols. */ +__attribute__((weak)) unsigned int getuid(void) +{ + return 0; +} + +__attribute__((weak)) unsigned int getgid(void) +{ + return 0; +} + int *__errno_location(void) { return &typephp_os_errno; diff --git a/examples/typephp-os/kernel/core/abi/typephp_os_syscall.h b/examples/typephp-os/kernel/core/abi/typephp_os_syscall.h index 412a0970..fea72c2c 100644 --- a/examples/typephp-os/kernel/core/abi/typephp_os_syscall.h +++ b/examples/typephp-os/kernel/core/abi/typephp_os_syscall.h @@ -8,20 +8,41 @@ enum typephp_os_syscall_number { TYPEPHP_SYS_READ = 0, TYPEPHP_SYS_WRITE = 1, TYPEPHP_SYS_CLOSE = 3, + TYPEPHP_SYS_STAT = 4, + TYPEPHP_SYS_FSTAT = 5, + TYPEPHP_SYS_LSTAT = 6, TYPEPHP_SYS_LSEEK = 8, TYPEPHP_SYS_MMAP = 9, TYPEPHP_SYS_MPROTECT = 10, TYPEPHP_SYS_MUNMAP = 11, TYPEPHP_SYS_BRK = 12, + TYPEPHP_SYS_ACCESS = 21, + TYPEPHP_SYS_GETPID = 39, TYPEPHP_SYS_EXIT = 60, TYPEPHP_SYS_UNAME = 63, + TYPEPHP_SYS_FSYNC = 74, + TYPEPHP_SYS_FDATASYNC = 75, + TYPEPHP_SYS_TRUNCATE = 76, + TYPEPHP_SYS_FTRUNCATE = 77, TYPEPHP_SYS_GETCWD = 79, TYPEPHP_SYS_CHDIR = 80, TYPEPHP_SYS_MKDIR = 83, TYPEPHP_SYS_RMDIR = 84, TYPEPHP_SYS_UNLINK = 87, + TYPEPHP_SYS_GETTIMEOFDAY = 96, + TYPEPHP_SYS_GETUID = 102, + TYPEPHP_SYS_GETGID = 104, + TYPEPHP_SYS_GETEUID = 107, + TYPEPHP_SYS_GETEGID = 108, + TYPEPHP_SYS_GETPPID = 110, + TYPEPHP_SYS_GETTID = 186, TYPEPHP_SYS_TIME = 201, + TYPEPHP_SYS_CLOCK_GETTIME = 228, + TYPEPHP_SYS_CLOCK_GETRES = 229, + TYPEPHP_SYS_EXIT_GROUP = 231, TYPEPHP_SYS_OPENAT = 257, + TYPEPHP_SYS_NEWFSTATAT = 262, + TYPEPHP_SYS_FACCESSAT = 269, TYPEPHP_SYS_SPAWN = 0x54500001, TYPEPHP_SYS_LISTDIR = 0x54500002, TYPEPHP_SYS_RENAME = 0x54500003, diff --git a/examples/typephp-os/kernel/core/abi/unimplemented.S b/examples/typephp-os/kernel/core/abi/unimplemented.S index 7de3a5d2..d7cd78f1 100644 --- a/examples/typephp-os/kernel/core/abi/unimplemented.S +++ b/examples/typephp-os/kernel/core/abi/unimplemented.S @@ -29,13 +29,11 @@ TYPEPHP_OS_UNIMPLEMENTED fclose TYPEPHP_OS_UNIMPLEMENTED fread TYPEPHP_OS_UNIMPLEMENTED fseek TYPEPHP_OS_UNIMPLEMENTED ftell -TYPEPHP_OS_UNIMPLEMENTED getgid TYPEPHP_OS_UNIMPLEMENTED getgrnam TYPEPHP_OS_UNIMPLEMENTED getgroups TYPEPHP_OS_UNIMPLEMENTED getpwnam TYPEPHP_OS_UNIMPLEMENTED getpwnam_r TYPEPHP_OS_UNIMPLEMENTED getpwuid_r -TYPEPHP_OS_UNIMPLEMENTED getuid TYPEPHP_OS_UNIMPLEMENTED gnu_dev_major TYPEPHP_OS_UNIMPLEMENTED gnu_dev_minor TYPEPHP_OS_UNIMPLEMENTED mkstemp diff --git a/examples/typephp-os/kernel/core/process.c b/examples/typephp-os/kernel/core/process.c index 63e12f9a..311c6330 100644 --- a/examples/typephp-os/kernel/core/process.c +++ b/examples/typephp-os/kernel/core/process.c @@ -49,6 +49,16 @@ typedef struct { char domainname[UTSNAME_LENGTH]; } user_utsname; +typedef struct { + long seconds; + long nanoseconds; +} user_timespec; + +typedef struct { + long seconds; + long microseconds; +} user_timeval; + enum { ELF_PT_LOAD = 1, ELF_PF_X = 1, @@ -632,18 +642,133 @@ static long syscall_openat(long directory_fd, const char *path, int flags, int m return posix_syscall_result(fd); } +static long syscall_stat_path(const char *path, struct stat *result, int no_follow) +{ + char resolved[USER_PATH_MAX]; + if (!user_buffer(result, sizeof(*result))) { + return -EFAULT; + } + if (!resolved_path(path, resolved, sizeof(resolved))) { + return -EFAULT; + } + return posix_syscall_result(no_follow + ? lstat(resolved, result) : stat(resolved, result)); +} + +static long syscall_fstat(int fd, struct stat *result) +{ + if (!user_buffer(result, sizeof(*result))) { + return -EFAULT; + } + return posix_syscall_result(fstat(fd, result)); +} + +static long syscall_newfstatat( + int directory_fd, const char *path, struct stat *result, int flags) +{ + enum { AT_SYMLINK_NOFOLLOW_VALUE = 0x100 }; + if ((flags & ~AT_SYMLINK_NOFOLLOW_VALUE) != 0) { + return -EINVAL; + } + if (directory_fd != AT_FDCWD && (path == 0 || path[0] != '/')) { + return -EBADF; + } + return syscall_stat_path(path, result, + (flags & AT_SYMLINK_NOFOLLOW_VALUE) != 0); +} + +static long syscall_access_path( + int directory_fd, const char *path, int mode, int flags) +{ + enum { AT_EACCESS_VALUE = 0x200 }; + char resolved[USER_PATH_MAX]; + struct stat value; + if ((mode & ~7) != 0 || (flags & ~AT_EACCESS_VALUE) != 0) { + return -EINVAL; + } + if (directory_fd != AT_FDCWD && (path == 0 || path[0] != '/')) { + return -EBADF; + } + if (!resolved_path(path, resolved, sizeof(resolved))) { + return -EFAULT; + } + if (stat(resolved, &value) < 0) { + return -errno; + } + /* One root identity, no ACLs. FAT16 files are readable and writable; + * directories additionally carry an executable/search bit. */ + if ((mode & 4) != 0 && (value.st_mode & 0444) == 0) { + return -EACCES; + } + if ((mode & 2) != 0 && (value.st_mode & 0222) == 0) { + return -EACCES; + } + if ((mode & 1) != 0 && (value.st_mode & 0111) == 0) { + return -EACCES; + } + return 0; +} + +static long syscall_truncate(const char *path, off_t length) +{ + char resolved[USER_PATH_MAX]; + int fd; + int result; + if (length < 0) { + return -EINVAL; + } + if (!resolved_path(path, resolved, sizeof(resolved))) { + return -EFAULT; + } + fd = open(resolved, O_WRONLY); + if (fd < 0) { + return -errno; + } + result = ftruncate(fd, length); + if (close(fd) < 0 && result == 0) { + result = -1; + } + return posix_syscall_result(result); +} + static long syscall_time(long *result) { long seconds = typephp_os_time_seconds(); if (result != 0) { if (!user_buffer(result, sizeof(*result))) { - return -1; + return -EFAULT; } *result = seconds; } return seconds; } +static long syscall_gettimeofday(user_timeval *result) +{ + if (!user_buffer(result, sizeof(*result))) { + return -EFAULT; + } + result->seconds = typephp_os_time_seconds(); + result->microseconds = 0; + return 0; +} + +static long syscall_clock(int clock_id, user_timespec *result, int resolution) +{ + if (clock_id != 0 && clock_id != 1) { + return -EINVAL; + } + if (resolution && result == 0) { + return 0; + } + if (!user_buffer(result, sizeof(*result))) { + return -EFAULT; + } + result->seconds = resolution ? 1 : typephp_os_time_seconds(); + result->nanoseconds = 0; + return 0; +} + static long syscall_uname(user_utsname *result) { static const user_utsname identity = { @@ -1024,6 +1149,14 @@ long typephp_os_syscall_dispatch(syscall_frame *frame) (int) frame->rdi, (const void *) frame->rsi, frame->rdx)); case TYPEPHP_SYS_CLOSE: return posix_syscall_result(close((int) frame->rdi)); + case TYPEPHP_SYS_STAT: + return syscall_stat_path((const char *) frame->rdi, + (struct stat *) frame->rsi, 0); + case TYPEPHP_SYS_FSTAT: + return syscall_fstat((int) frame->rdi, (struct stat *) frame->rsi); + case TYPEPHP_SYS_LSTAT: + return syscall_stat_path((const char *) frame->rdi, + (struct stat *) frame->rsi, 1); case TYPEPHP_SYS_LSEEK: return posix_syscall_result(lseek( (int) frame->rdi, (off_t) frame->rsi, (int) frame->rdx)); @@ -1036,6 +1169,12 @@ long typephp_os_syscall_dispatch(syscall_frame *frame) return syscall_munmap(frame->rdi, frame->rsi); case TYPEPHP_SYS_BRK: return syscall_brk(frame->rdi); + case TYPEPHP_SYS_ACCESS: + return syscall_access_path(AT_FDCWD, (const char *) frame->rdi, + (int) frame->rsi, 0); + case TYPEPHP_SYS_GETPID: + case TYPEPHP_SYS_GETTID: + return (long) foreground_process.pid; case TYPEPHP_SYS_SPAWN: return syscall_spawn(frame, (const char *const *) frame->rdi); case TYPEPHP_SYS_GETCWD: @@ -1063,6 +1202,17 @@ long typephp_os_syscall_dispatch(syscall_frame *frame) } return posix_syscall_result(unlink(resolved)); } + case TYPEPHP_SYS_FSYNC: + case TYPEPHP_SYS_FDATASYNC: + return posix_syscall_result(fsync((int) frame->rdi)); + case TYPEPHP_SYS_TRUNCATE: + return syscall_truncate((const char *) frame->rdi, (off_t) frame->rsi); + case TYPEPHP_SYS_FTRUNCATE: + if ((off_t) frame->rsi < 0) { + return -EINVAL; + } + return posix_syscall_result(ftruncate( + (int) frame->rdi, (off_t) frame->rsi)); case TYPEPHP_SYS_RENAME: { char old_path[USER_PATH_MAX]; char new_path[USER_PATH_MAX]; @@ -1074,13 +1224,40 @@ long typephp_os_syscall_dispatch(syscall_frame *frame) } case TYPEPHP_SYS_TIME: return syscall_time((long *) frame->rdi); + case TYPEPHP_SYS_GETTIMEOFDAY: + if (frame->rsi != 0) { + return -EINVAL; + } + return syscall_gettimeofday((user_timeval *) frame->rdi); + case TYPEPHP_SYS_GETUID: + case TYPEPHP_SYS_GETGID: + case TYPEPHP_SYS_GETEUID: + case TYPEPHP_SYS_GETEGID: + return 0; + case TYPEPHP_SYS_GETPPID: + return foreground_process.parent_waiting ? 1 : 0; + case TYPEPHP_SYS_CLOCK_GETTIME: + return syscall_clock((int) frame->rdi, + (user_timespec *) frame->rsi, 0); + case TYPEPHP_SYS_CLOCK_GETRES: + return syscall_clock((int) frame->rdi, + (user_timespec *) frame->rsi, 1); case TYPEPHP_SYS_LISTDIR: return syscall_readdir((const char *) frame->rdi, (char *) frame->rsi, frame->rdx); case TYPEPHP_SYS_OPENAT: return syscall_openat((long) frame->rdi, (const char *) frame->rsi, (int) frame->rdx, (int) frame->r10); + case TYPEPHP_SYS_NEWFSTATAT: + return syscall_newfstatat((int) frame->rdi, + (const char *) frame->rsi, (struct stat *) frame->rdx, + (int) frame->r10); + case TYPEPHP_SYS_FACCESSAT: + return syscall_access_path((int) frame->rdi, + (const char *) frame->rsi, (int) frame->rdx, + (int) frame->r10); case TYPEPHP_SYS_EXIT: + case TYPEPHP_SYS_EXIT_GROUP: return syscall_exit(frame, (long) frame->rdi); case TYPEPHP_SYS_UNAME: return syscall_uname((user_utsname *) frame->rdi); diff --git a/examples/typephp-os/project.yml b/examples/typephp-os/project.yml index 99fe2262..e30bff94 100644 --- a/examples/typephp-os/project.yml +++ b/examples/typephp-os/project.yml @@ -53,4 +53,3 @@ ld-flags: - -Wl,-m,elf_x86_64 - -Wl,--build-id=none - -Wl,-T,examples/typephp-os/boot/kernel64.ld - - -lgcc diff --git a/examples/typephp-os/tools/test-qemu.sh b/examples/typephp-os/tools/test-qemu.sh index db0cf07f..9c426675 100755 --- a/examples/typephp-os/tools/test-qemu.sh +++ b/examples/typephp-os/tools/test-qemu.sh @@ -21,7 +21,7 @@ if timeout 35 qemu-system-x86_64 \ -no-shutdown \ -device isa-debug-exit,iobase=0xf4,iosize=0x04 \ >"${log}" 2>&1 \ - <<< $'\ndate\nuname\nuname -a\necho Hello TypePHP userspace\nhello Dynamically loaded\ntnhello alpha beta\nmissing\nbad\nls /BIN\ncat HELLO.TXT\ntouch /EXPAND/F62.TXT\nls /EXPAND\nwrite NOTE.TXT Hello from Ring 3\ncat NOTE.TXT\ntouch EMPTY.TXT\nmkdir TMP\nls\nrm NOTE.TXT\ncat NOTE.TXT\nrmdir TMP\ncd TMP\nmkdir WORK\ncd WORK\nwrite NOTE.TXT Nested directory write\ncat NOTE.TXT\nmkdir SUB\ncd SUB\nwrite DEEP.TXT Deep directory write\nmv DEEP.TXT MOVED.TXT\ncat MOVED.TXT\ncat DEEP.TXT\npwd\ncd ..\nrmdir SUB\nrm SUB/MOVED.TXT\nrmdir SUB\nls\nrm NOTE.TXT\ncd ..\nrmdir WORK\ncd WORK\nmemtest\nfault\nvmfault\nwrfault\ndate\npwd\ncd BIN\npwd\nls\ncd ..\ncd DOCS\npwd\nls\ncd ..\nls\n'; then + <<< $'\ndate\nuname\nuname -a\necho Hello TypePHP userspace\nhello Dynamically loaded\ntnhello alpha beta\nsystest\nmissing\nbad\nls /BIN\ncat HELLO.TXT\ntouch /EXPAND/F62.TXT\nls /EXPAND\nwrite NOTE.TXT Hello from Ring 3\ncat NOTE.TXT\ntouch EMPTY.TXT\nmkdir TMP\nls\nrm NOTE.TXT\ncat NOTE.TXT\nrmdir TMP\ncd TMP\nmkdir WORK\ncd WORK\nwrite NOTE.TXT Nested directory write\ncat NOTE.TXT\nmkdir SUB\ncd SUB\nwrite DEEP.TXT Deep directory write\nmv DEEP.TXT MOVED.TXT\ncat MOVED.TXT\ncat DEEP.TXT\npwd\ncd ..\nrmdir SUB\nrm SUB/MOVED.TXT\nrmdir SUB\nls\nrm NOTE.TXT\ncd ..\nrmdir WORK\ncd WORK\nmemtest\nfault\nvmfault\nwrfault\ndate\npwd\ncd BIN\npwd\nls\ncd ..\ncd DOCS\npwd\nls\ncd ..\nls\n'; then status=0 else status=$? @@ -51,10 +51,10 @@ grep -q "Prime list: 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, grep -q "Process 1: sh.elf (Ring 3)" "${log}" grep -q "TypePHP-OS user shell" "${log}" grep -q "Ring 3 confirmed" "${log}" -grep -q "Commands: ls, cd, pwd, date, uname, cat, echo, write, touch, mkdir, rm, rmdir, mv, memtest, fault, vmfault, wrfault" "${log}" +grep -q "Commands: ls, cd, pwd, date, uname, cat, echo, write, touch, mkdir, rm, rmdir, mv, memtest, systest, fault, vmfault, wrfault" "${log}" grep -Eq '^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2} UTC' "${log}" -grep -q '^TypePHP-OS$' "${log}" -grep -q '^TypePHP-OS typephp-os 0.1 TypePHP Nano user mode x86_64$' "${log}" +grep -Fq $'TypePHP-OS\r' "${log}" +grep -Fq $'TypePHP-OS typephp-os 0.1 TypePHP Nano user mode x86_64\r' "${log}" grep -q '^Hello TypePHP userspace' "${log}" grep -q '^Dynamically loaded' "${log}" grep -Fq 'Hello World!string(6) "8.4.14"' "${log}" @@ -65,6 +65,11 @@ grep -Fq 'string(7) "tnhello"' "${log}" grep -Fq 'string(5) "alpha"' "${log}" grep -Fq 'string(4) "beta"' "${log}" grep -Fq 'string(55) "TypePHP-OS typephp-os 0.1 TypePHP Nano user mode x86_64"' "${log}" +if [[ $(grep -c '^bool(true)' "${log}") -lt 2 ]]; then + echo "the Nano smoke program did not complete both file metadata checks" >&2 + exit 1 +fi +grep -q '^basic syscalls: OK' "${log}" if grep -q 'unsupported TypePHP-OS user ABI' "${log}"; then echo "the Nano smoke program reached an unsupported userspace ABI" >&2 exit 1 diff --git a/examples/typephp-os/user/README.md b/examples/typephp-os/user/README.md index 98002134..2d062ce6 100644 --- a/examples/typephp-os/user/README.md +++ b/examples/typephp-os/user/README.md @@ -18,17 +18,25 @@ The following rules are normative for this directory: 4. Keep OS-specific calls behind the userspace ABI layer. Application and command code should gradually stop including raw syscall helpers as libc coverage grows. -5. New kernel services should first consider the interfaces glibc needs: - process startup, memory mapping, files and directories, clocks, TLS, - signals, and process lifecycle. Socket support remains out of scope for the - current system. -6. Freestanding test programs remain libc-free only as a bootstrap constraint; +5. TypePHP-OS is permanently a single-task system. It does not implement + `fork`, `clone`, `execve`, `wait`, pipes, job control, threads, or signals + for scheduling. The private shell launch service synchronously replaces + the active command address space and restores the resident shell on exit; + it is not a public POSIX process-creation API. +6. New kernel services should first consider the remaining interfaces needed + by a static libc and PHP Nano: memory mapping, files and directories, + clocks, TLS, terminal I/O, and single-task lifecycle. Socket support also + remains out of scope. +7. Freestanding test programs remain libc-free only as a bootstrap constraint; it is not the final userspace programming model. The current bootstrap libc provides `syscall`, `read`, `write`, `openat`, `open`, `close`, `lseek`, `getcwd`, `chdir`, `mkdir`, `rmdir`, `unlink`, -`rename`, `time`, `uname`, `brk`, `sbrk`, `mmap`, `mprotect`, `munmap`, `strlen`, -`strerror`, `perror`, and `_exit` with libc-compatible C signatures. It also +`rename`, `stat`, `lstat`, `fstat`, `access`, `fsync`, `fdatasync`, `truncate`, +`ftruncate`, `time`, `gettimeofday`, `clock_gettime`, `clock_getres`, `uname`, +`getpid`, `getppid`, `gettid`, the root UID/GID queries, `brk`, `sbrk`, `mmap`, +`mprotect`, `munmap`, `strlen`, `strerror`, `perror`, and `_exit` with +libc-compatible C signatures. It also translates kernel `-errno` results into `-1` plus the single-task userspace `errno`. This list is a migration layer, not a reason to create project-specific variants of standard functions. @@ -56,6 +64,12 @@ mappings, shared mappings, fixed mappings, remapping, and demand paging are not implemented yet. `brk()` and `mmap()` eagerly allocate zero-filled pages; `munmap()` and process teardown return them to the physical-page pool. +The kernel additionally accepts Linux x86-64 `newfstatat`, `faccessat`, and +`exit_group`. File metadata uses the Linux x86-64 144-byte `struct stat` +layout. FAT16 currently has no owners, ACLs, symlinks, executable file bit, or +sub-second timestamps: UID/GID are always root, ordinary files are `0666`, +directories are `0777`, and clock resolution is one second. + `rename()` currently accepts only source and destination paths with the same parent directory and does not replace an existing entry. Its transport uses a private TypePHP-OS syscall number until the kernel implements the complete diff --git a/examples/typephp-os/user/include/fcntl.h b/examples/typephp-os/user/include/fcntl.h index cb848232..a3650783 100644 --- a/examples/typephp-os/user/include/fcntl.h +++ b/examples/typephp-os/user/include/fcntl.h @@ -9,6 +9,8 @@ #define O_APPEND 1024 #define AT_FDCWD (-100) +#define AT_SYMLINK_NOFOLLOW 0x100 +#define AT_EACCESS 0x200 int open(const char *path, int flags, ...); int openat(int directory_fd, const char *path, int flags, ...); diff --git a/examples/typephp-os/user/include/sys/stat.h b/examples/typephp-os/user/include/sys/stat.h index 3e112819..63d0b336 100644 --- a/examples/typephp-os/user/include/sys/stat.h +++ b/examples/typephp-os/user/include/sys/stat.h @@ -1,8 +1,40 @@ #ifndef TYPEPHP_OS_USER_SYS_STAT_H #define TYPEPHP_OS_USER_SYS_STAT_H -typedef unsigned int mode_t; +#include +#include + +struct stat { + dev_t st_dev; + ino_t st_ino; + nlink_t st_nlink; + mode_t st_mode; + uid_t st_uid; + gid_t st_gid; + int __pad0; + dev_t st_rdev; + off_t st_size; + blksize_t st_blksize; + blkcnt_t st_blocks; + struct timespec st_atim; + struct timespec st_mtim; + struct timespec st_ctim; + long __reserved[3]; +}; + +#define S_IFMT 0170000 +#define S_IFREG 0100000 +#define S_IFDIR 0040000 +#define S_IFCHR 0020000 + +#define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG) +#define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) +#define S_ISCHR(mode) (((mode) & S_IFMT) == S_IFCHR) int mkdir(const char *path, mode_t mode); +int stat(const char *path, struct stat *value); +int lstat(const char *path, struct stat *value); +int fstat(int fd, struct stat *value); +int fstatat(int directory_fd, const char *path, struct stat *value, int flags); #endif diff --git a/examples/typephp-os/user/include/sys/syscall.h b/examples/typephp-os/user/include/sys/syscall.h index 2b07e5c9..efeb701f 100644 --- a/examples/typephp-os/user/include/sys/syscall.h +++ b/examples/typephp-os/user/include/sys/syscall.h @@ -6,20 +6,41 @@ #define SYS_read TYPEPHP_SYS_READ #define SYS_write TYPEPHP_SYS_WRITE #define SYS_close TYPEPHP_SYS_CLOSE +#define SYS_stat TYPEPHP_SYS_STAT +#define SYS_fstat TYPEPHP_SYS_FSTAT +#define SYS_lstat TYPEPHP_SYS_LSTAT #define SYS_lseek TYPEPHP_SYS_LSEEK #define SYS_mmap TYPEPHP_SYS_MMAP #define SYS_mprotect TYPEPHP_SYS_MPROTECT #define SYS_munmap TYPEPHP_SYS_MUNMAP #define SYS_brk TYPEPHP_SYS_BRK +#define SYS_access TYPEPHP_SYS_ACCESS +#define SYS_getpid TYPEPHP_SYS_GETPID #define SYS_exit TYPEPHP_SYS_EXIT #define SYS_uname TYPEPHP_SYS_UNAME +#define SYS_fsync TYPEPHP_SYS_FSYNC +#define SYS_fdatasync TYPEPHP_SYS_FDATASYNC +#define SYS_truncate TYPEPHP_SYS_TRUNCATE +#define SYS_ftruncate TYPEPHP_SYS_FTRUNCATE #define SYS_getcwd TYPEPHP_SYS_GETCWD #define SYS_chdir TYPEPHP_SYS_CHDIR #define SYS_mkdir TYPEPHP_SYS_MKDIR #define SYS_rmdir TYPEPHP_SYS_RMDIR #define SYS_unlink TYPEPHP_SYS_UNLINK +#define SYS_gettimeofday TYPEPHP_SYS_GETTIMEOFDAY +#define SYS_getuid TYPEPHP_SYS_GETUID +#define SYS_getgid TYPEPHP_SYS_GETGID +#define SYS_geteuid TYPEPHP_SYS_GETEUID +#define SYS_getegid TYPEPHP_SYS_GETEGID +#define SYS_getppid TYPEPHP_SYS_GETPPID +#define SYS_gettid TYPEPHP_SYS_GETTID #define SYS_time TYPEPHP_SYS_TIME +#define SYS_clock_gettime TYPEPHP_SYS_CLOCK_GETTIME +#define SYS_clock_getres TYPEPHP_SYS_CLOCK_GETRES +#define SYS_exit_group TYPEPHP_SYS_EXIT_GROUP #define SYS_openat TYPEPHP_SYS_OPENAT +#define SYS_newfstatat TYPEPHP_SYS_NEWFSTATAT +#define SYS_faccessat TYPEPHP_SYS_FACCESSAT /* Private bootstrap services; these deliberately do not claim Linux syscall * numbers or semantics. */ diff --git a/examples/typephp-os/user/include/sys/time.h b/examples/typephp-os/user/include/sys/time.h new file mode 100644 index 00000000..a0a6bab2 --- /dev/null +++ b/examples/typephp-os/user/include/sys/time.h @@ -0,0 +1,13 @@ +#ifndef TYPEPHP_OS_USER_SYS_TIME_H +#define TYPEPHP_OS_USER_SYS_TIME_H + +#include + +struct timeval { + time_t tv_sec; + long tv_usec; +}; + +int gettimeofday(struct timeval *value, void *timezone); + +#endif diff --git a/examples/typephp-os/user/include/sys/types.h b/examples/typephp-os/user/include/sys/types.h new file mode 100644 index 00000000..8af15558 --- /dev/null +++ b/examples/typephp-os/user/include/sys/types.h @@ -0,0 +1,20 @@ +#ifndef TYPEPHP_OS_USER_SYS_TYPES_H +#define TYPEPHP_OS_USER_SYS_TYPES_H + +/* Linux x86-64 ABI-compatible scalar types. Keeping this layout stable makes + * the userspace boundary suitable for a future glibc port. */ +typedef unsigned long dev_t; +typedef unsigned long ino_t; +typedef unsigned long nlink_t; +typedef unsigned int mode_t; +typedef unsigned int uid_t; +typedef unsigned int gid_t; +typedef int pid_t; +typedef long off_t; +typedef long blksize_t; +typedef long blkcnt_t; +typedef long ssize_t; +typedef long time_t; +typedef int clockid_t; + +#endif diff --git a/examples/typephp-os/user/include/time.h b/examples/typephp-os/user/include/time.h index 118d2c60..13282d30 100644 --- a/examples/typephp-os/user/include/time.h +++ b/examples/typephp-os/user/include/time.h @@ -1,8 +1,18 @@ #ifndef TYPEPHP_OS_USER_TIME_H #define TYPEPHP_OS_USER_TIME_H -typedef long time_t; +#include + +#define CLOCK_REALTIME 0 +#define CLOCK_MONOTONIC 1 + +struct timespec { + time_t tv_sec; + long tv_nsec; +}; time_t time(time_t *result); +int clock_gettime(clockid_t clock_id, struct timespec *value); +int clock_getres(clockid_t clock_id, struct timespec *value); #endif diff --git a/examples/typephp-os/user/include/unistd.h b/examples/typephp-os/user/include/unistd.h index bf654365..b7d64c6a 100644 --- a/examples/typephp-os/user/include/unistd.h +++ b/examples/typephp-os/user/include/unistd.h @@ -3,9 +3,7 @@ #include #include - -typedef long ssize_t; -typedef long off_t; +#include #define STDIN_FILENO 0 #define STDOUT_FILENO 1 @@ -19,6 +17,19 @@ int chdir(const char *path); char *getcwd(char *buffer, size_t size); int unlink(const char *path); int rmdir(const char *path); +int access(const char *path, int mode); +int faccessat(int directory_fd, const char *path, int mode, int flags); +int fsync(int fd); +int fdatasync(int fd); +int truncate(const char *path, off_t length); +int ftruncate(int fd, off_t length); +pid_t getpid(void); +pid_t getppid(void); +pid_t gettid(void); +uid_t getuid(void); +uid_t geteuid(void); +gid_t getgid(void); +gid_t getegid(void); int brk(void *address); void *sbrk(intptr_t increment); void _exit(int status) __attribute__((noreturn)); @@ -27,4 +38,9 @@ void _exit(int status) __attribute__((noreturn)); #define SEEK_CUR 1 #define SEEK_END 2 +#define F_OK 0 +#define X_OK 1 +#define W_OK 2 +#define R_OK 4 + #endif diff --git a/examples/typephp-os/user/libc.c b/examples/typephp-os/user/libc.c index 576d336c..199f9e59 100644 --- a/examples/typephp-os/user/libc.c +++ b/examples/typephp-os/user/libc.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -127,6 +128,56 @@ int rename(const char *old_path, const char *new_path) return (int) syscall(SYS_typephp_rename, old_path, new_path); } +int access(const char *path, int mode) +{ + return (int) syscall(SYS_access, path, mode); +} + +int stat(const char *path, struct stat *value) +{ + return (int) syscall(SYS_stat, path, value); +} + +int lstat(const char *path, struct stat *value) +{ + return (int) syscall(SYS_lstat, path, value); +} + +int fstat(int fd, struct stat *value) +{ + return (int) syscall(SYS_fstat, fd, value); +} + +int fstatat(int directory_fd, const char *path, struct stat *value, int flags) +{ + return (int) syscall(SYS_newfstatat, directory_fd, path, value, flags); +} + +int faccessat(int directory_fd, const char *path, int mode, int flags) +{ + return (int) syscall(SYS_faccessat, directory_fd, path, mode, flags); +} + +int fsync(int fd) +{ + return (int) syscall(SYS_fsync, fd); +} + +int fdatasync(int fd) +{ + return (int) syscall(SYS_fdatasync, fd); +} + +int truncate(const char *path, off_t length) +{ + return (int) syscall(SYS_truncate, path, length); +} + +int ftruncate(int fd, off_t length) +{ + return (int) syscall(SYS_ftruncate, fd, length); +} + int brk(void *address) { long result = syscall(SYS_brk, address); @@ -180,6 +231,56 @@ time_t time(time_t *result) return (time_t) syscall(SYS_time, result); } +int gettimeofday(struct timeval *value, void *timezone) +{ + return (int) syscall(SYS_gettimeofday, value, timezone); +} + +int clock_gettime(clockid_t clock_id, struct timespec *value) +{ + return (int) syscall(SYS_clock_gettime, clock_id, value); +} + +int clock_getres(clockid_t clock_id, struct timespec *value) +{ + return (int) syscall(SYS_clock_getres, clock_id, value); +} + +pid_t getpid(void) +{ + return (pid_t) syscall(SYS_getpid); +} + +pid_t getppid(void) +{ + return (pid_t) syscall(SYS_getppid); +} + +pid_t gettid(void) +{ + return (pid_t) syscall(SYS_gettid); +} + +uid_t getuid(void) +{ + return (uid_t) syscall(SYS_getuid); +} + +uid_t geteuid(void) +{ + return (uid_t) syscall(SYS_geteuid); +} + +gid_t getgid(void) +{ + return (gid_t) syscall(SYS_getgid); +} + +gid_t getegid(void) +{ + return (gid_t) syscall(SYS_getegid); +} + int uname(struct utsname *value) { return (int) syscall(SYS_uname, value); @@ -187,7 +288,7 @@ int uname(struct utsname *value) void _exit(int status) { - (void) syscall(SYS_exit, status); + (void) syscall(SYS_exit_group, status); for (;;) { __asm__ volatile("pause"); } diff --git a/examples/typephp-os/user/nano/hello.php b/examples/typephp-os/user/nano/hello.php index 6cb80d1b..9e9a5ed8 100644 --- a/examples/typephp-os/user/nano/hello.php +++ b/examples/typephp-os/user/nano/hello.php @@ -10,4 +10,6 @@ function main(int $argc, array $argv): void var_dump($argv); var_dump(date('Y-m-d H:i:s', time())); var_dump(php_uname()); + var_dump(file_exists('/HELLO.TXT')); + var_dump(filesize('/HELLO.TXT') > 0); } diff --git a/examples/typephp-os/user/nano/hello.yml b/examples/typephp-os/user/nano/hello.yml index 605cc6b8..2721e355 100644 --- a/examples/typephp-os/user/nano/hello.yml +++ b/examples/typephp-os/user/nano/hello.yml @@ -56,4 +56,3 @@ link-paths: - ../../build link-libs: - typephp-os - - gcc diff --git a/examples/typephp-os/user/runtime/README.md b/examples/typephp-os/user/runtime/README.md index d43c1d93..f43f13ec 100644 --- a/examples/typephp-os/user/runtime/README.md +++ b/examples/typephp-os/user/runtime/README.md @@ -22,7 +22,6 @@ link-paths: - ../../build link-libs: - typephp-os - - gcc ``` The archive owns the userspace `_start`, PHP Nano host entry, syscall bridge, @@ -31,6 +30,11 @@ must not compile private copies of these files. This allows any number of projects under `user/` to reuse one platform archive and keeps their generated object directories independent. +No compiler runtime library is currently required on x86-64; both the kernel +and Nano userspace links are audited for undefined symbols. Add selected +compiler-rt builtins only if generated code introduces an explicit helper +symbol in the future. + The archive and each consumer must use the same freestanding ABI flags and PHP Nano/PHPX feature definitions. Those shared settings are maintained in the top-level Makefile and the consumer project file respectively. diff --git a/examples/typephp-os/user/runtime/crt0.S b/examples/typephp-os/user/runtime/crt0.S index 09da672b..cfa5166a 100644 --- a/examples/typephp-os/user/runtime/crt0.S +++ b/examples/typephp-os/user/runtime/crt0.S @@ -15,7 +15,8 @@ _start: call main mov %eax, %edi - mov $60, %eax + # Linux x86-64 exit_group(2); TypePHP-OS keeps the same number and ABI. + mov $231, %eax int $0x80 ud2 .size _start, . - _start diff --git a/examples/typephp-os/user/runtime/posix.c b/examples/typephp-os/user/runtime/posix.c index d0d9b5cc..cf4260f8 100644 --- a/examples/typephp-os/user/runtime/posix.c +++ b/examples/typephp-os/user/runtime/posix.c @@ -120,18 +120,17 @@ time_t time(time_t *result) int gettimeofday(struct timeval *value, void *timezone) { - (void) timezone; - value->tv_sec = time(NULL); - value->tv_usec = 0; - return 0; + return (int) syscall(TYPEPHP_SYS_GETTIMEOFDAY, value, timezone); } int clock_gettime(clockid_t clock_id, struct timespec *value) { - (void) clock_id; - value->tv_sec = time(NULL); - value->tv_nsec = 0; - return 0; + return (int) syscall(TYPEPHP_SYS_CLOCK_GETTIME, clock_id, value); +} + +int clock_getres(clockid_t clock_id, struct timespec *value) +{ + return (int) syscall(TYPEPHP_SYS_CLOCK_GETRES, clock_id, value); } unsigned int sleep(unsigned int seconds) @@ -167,30 +166,34 @@ int rename(const char *old_path, const char *new_path) int access(const char *path, int mode) { - (void) path; - (void) mode; - unsupported("access"); + return (int) syscall(TYPEPHP_SYS_ACCESS, path, mode); } int stat(const char *path, struct stat *value) { - (void) path; - (void) value; - unsupported("stat"); + return (int) syscall(TYPEPHP_SYS_STAT, path, value); } int lstat(const char *path, struct stat *value) { - (void) path; - (void) value; - unsupported("lstat"); + return (int) syscall(TYPEPHP_SYS_LSTAT, path, value); } int fstat(int fd, struct stat *value) { - (void) fd; - (void) value; - unsupported("fstat"); + return (int) syscall(TYPEPHP_SYS_FSTAT, fd, value); +} + +int fstatat(int directory_fd, const char *path, struct stat *value, int flags) +{ + return (int) syscall( + TYPEPHP_SYS_NEWFSTATAT, directory_fd, path, value, flags); +} + +int faccessat(int directory_fd, const char *path, int mode, int flags) +{ + return (int) syscall( + TYPEPHP_SYS_FACCESSAT, directory_fd, path, mode, flags); } DIR *opendir(const char *path) @@ -219,13 +222,55 @@ void rewinddir(DIR *directory) int fsync(int fd) { - (void) fd; - unsupported("fsync"); + return (int) syscall(TYPEPHP_SYS_FSYNC, fd); +} + +int fdatasync(int fd) +{ + return (int) syscall(TYPEPHP_SYS_FDATASYNC, fd); +} + +int truncate(const char *path, off_t length) +{ + return (int) syscall(TYPEPHP_SYS_TRUNCATE, path, length); } int ftruncate(int fd, off_t length) { - (void) fd; - (void) length; - unsupported("ftruncate"); + return (int) syscall(TYPEPHP_SYS_FTRUNCATE, fd, length); +} + +pid_t getpid(void) +{ + return (pid_t) syscall(TYPEPHP_SYS_GETPID); +} + +pid_t getppid(void) +{ + return (pid_t) syscall(TYPEPHP_SYS_GETPPID); +} + +pid_t gettid(void) +{ + return (pid_t) syscall(TYPEPHP_SYS_GETTID); +} + +uid_t getuid(void) +{ + return (uid_t) syscall(TYPEPHP_SYS_GETUID); +} + +uid_t geteuid(void) +{ + return (uid_t) syscall(TYPEPHP_SYS_GETEUID); +} + +gid_t getgid(void) +{ + return (gid_t) syscall(TYPEPHP_SYS_GETGID); +} + +gid_t getegid(void) +{ + return (gid_t) syscall(TYPEPHP_SYS_GETEGID); } diff --git a/examples/typephp-os/user/sh.c b/examples/typephp-os/user/sh.c index 02133518..f08a871b 100644 --- a/examples/typephp-os/user/sh.c +++ b/examples/typephp-os/user/sh.c @@ -61,8 +61,8 @@ int main(int argc, char **argv) (void) write(STDOUT_FILENO, "Ring 3 confirmed\n", sizeof("Ring 3 confirmed\n") - 1); (void) write(STDOUT_FILENO, - "Commands: ls, cd, pwd, date, uname, cat, echo, write, touch, mkdir, rm, rmdir, mv, memtest, fault, vmfault, wrfault\n", - sizeof("Commands: ls, cd, pwd, date, uname, cat, echo, write, touch, mkdir, rm, rmdir, mv, memtest, fault, vmfault, wrfault\n") - 1); + "Commands: ls, cd, pwd, date, uname, cat, echo, write, touch, mkdir, rm, rmdir, mv, memtest, systest, fault, vmfault, wrfault\n", + sizeof("Commands: ls, cd, pwd, date, uname, cat, echo, write, touch, mkdir, rm, rmdir, mv, memtest, systest, fault, vmfault, wrfault\n") - 1); for (;;) { char *arguments[9]; int argument_count = 0; diff --git a/examples/typephp-os/user/systest.c b/examples/typephp-os/user/systest.c new file mode 100644 index 00000000..f62d5a43 --- /dev/null +++ b/examples/typephp-os/user/systest.c @@ -0,0 +1,73 @@ +#include +#include +#include +#include +#include +#include + +_Static_assert(sizeof(struct stat) == 144, "Linux x86-64 stat ABI changed"); + +static int fail(const char *operation) +{ + size_t length = 0; + while (operation[length] != '\0') { + ++length; + } + (void) write(STDERR_FILENO, "systest: ", sizeof("systest: ") - 1); + (void) write(STDERR_FILENO, operation, length); + (void) write(STDERR_FILENO, " failed\n", sizeof(" failed\n") - 1); + return 1; +} + +int main(int argc, char **argv) +{ + static const char path[] = "/SYS.TMP"; + static const char payload[] = "abcdef"; + struct stat info; + struct timeval wall; + struct timespec realtime; + struct timespec monotonic; + struct timespec resolution; + int fd; + (void) argc; + (void) argv; + + if (stat("/HELLO.TXT", &info) != 0 || !S_ISREG(info.st_mode) + || info.st_size <= 0 || access("/HELLO.TXT", R_OK) != 0) { + return fail("stat/access"); + } + if (fstatat(AT_FDCWD, "/HELLO.TXT", &info, AT_SYMLINK_NOFOLLOW) != 0 + || faccessat(AT_FDCWD, "/HELLO.TXT", R_OK, 0) != 0) { + return fail("*at metadata"); + } + + (void) unlink(path); + fd = open(path, O_CREAT | O_TRUNC | O_RDWR, 0666); + if (fd < 0 || write(fd, payload, sizeof(payload) - 1) != sizeof(payload) - 1 + || fsync(fd) != 0 || ftruncate(fd, 3) != 0 + || fstat(fd, &info) != 0 || info.st_size != 3 + || fdatasync(fd) != 0 || close(fd) != 0) { + return fail("descriptor persistence"); + } + if (truncate(path, 1) != 0 || lstat(path, &info) != 0 + || info.st_size != 1 || unlink(path) != 0) { + return fail("path truncate"); + } + + if (getpid() != 2 || gettid() != 2 || getppid() != 1 + || getuid() != 0 || geteuid() != 0 || getgid() != 0 || getegid() != 0) { + return fail("single-task identity"); + } + if (gettimeofday(&wall, NULL) != 0 || wall.tv_sec <= 0 + || clock_gettime(CLOCK_REALTIME, &realtime) != 0 + || clock_gettime(CLOCK_MONOTONIC, &monotonic) != 0 + || clock_getres(CLOCK_MONOTONIC, &resolution) != 0 + || realtime.tv_sec <= 0 || monotonic.tv_sec <= 0 + || resolution.tv_sec != 1 || resolution.tv_nsec != 0) { + return fail("clock ABI"); + } + + (void) write(STDOUT_FILENO, "basic syscalls: OK\n", + sizeof("basic syscalls: OK\n") - 1); + return 0; +} diff --git a/examples/typephp-os/user/uname.c b/examples/typephp-os/user/uname.c index 38c9ef98..a0385543 100644 --- a/examples/typephp-os/user/uname.c +++ b/examples/typephp-os/user/uname.c @@ -66,11 +66,21 @@ int main(int argc, char **argv) sizeof("uname: system information unavailable\n") - 1); return 1; } - if ((fields & FIELD_SYSNAME) != 0) print_field(identity.sysname, &first); - if ((fields & FIELD_NODENAME) != 0) print_field(identity.nodename, &first); - if ((fields & FIELD_RELEASE) != 0) print_field(identity.release, &first); - if ((fields & FIELD_VERSION) != 0) print_field(identity.version, &first); - if ((fields & FIELD_MACHINE) != 0) print_field(identity.machine, &first); + if ((fields & FIELD_SYSNAME) != 0) { + print_field(identity.sysname, &first); + } + if ((fields & FIELD_NODENAME) != 0) { + print_field(identity.nodename, &first); + } + if ((fields & FIELD_RELEASE) != 0) { + print_field(identity.release, &first); + } + if ((fields & FIELD_VERSION) != 0) { + print_field(identity.version, &first); + } + if ((fields & FIELD_MACHINE) != 0) { + print_field(identity.machine, &first); + } (void) write(STDOUT_FILENO, "\n", 1); return 0; }