diff --git a/examples/typephp-os/Makefile b/examples/typephp-os/Makefile index 0954cee4..ce882726 100644 --- a/examples/typephp-os/Makefile +++ b/examples/typephp-os/Makefile @@ -7,19 +7,25 @@ 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 systest +USER_COMMANDS := ls cd date uname pwd cat echo write touch mkdir rm rmdir mv fault vmfault wrfault memtest systest builtins USER_PROGRAMS := sh $(USER_COMMANDS) USER_SH_ELF := $(BUILD)/sh.elf USER_COMMAND_ELFS := $(addprefix $(BUILD)/,$(addsuffix .elf,$(USER_COMMANDS))) USER_CRT0_OBJECT := $(OBJECT_DIR)/user-crt0.o -USER_LIBC_OBJECTS := $(OBJECT_DIR)/user-libc.o $(OBJECT_DIR)/user-syscall.o +USER_LIBC_OBJECTS := $(OBJECT_DIR)/user-libc.o $(OBJECT_DIR)/user-dirent.o \ + $(OBJECT_DIR)/user-fcntl.o $(OBJECT_DIR)/user-syscall.o NANO_USER_ELF := $(BUILD)/tnhello.elf TYPEPHP_OS_LIBRARY := $(BUILD)/libtypephp-os.a TYPEPHP_OS_LIBRARY_OBJECT_DIR := $(BUILD)/libtypephp-os +COMPILER_RT_LIBRARY := $(BUILD)/libcompiler-rt-builtins.a +COMPILER_RT_OBJECT_DIR := $(BUILD)/compiler-rt-builtins PHP_NANO_DIR := $(or $(realpath ../../vendor/swoole/php-nano),$(realpath ../../../php-nano)) PHPX_DIR := $(or $(realpath ../../vendor/swoole/phpx),$(realpath ../../../phpx)) OPENLIBM_SOURCES := $(shell sed -n 's|^\(.*\.c\)$$|thirdparty/openlibm/\1|p' tools/openlibm-files.txt) -TYPEPHP_OS_LIBRARY_C_SOURCES := user/runtime/posix.c kernel/core/abi/libc.c $(OPENLIBM_SOURCES) +COMPILER_RT_SOURCES := $(shell awk '$$2 ~ /\.c$$/ { print "thirdparty/compiler-rt/" $$2 }' tools/compiler-rt-builtins-files.sha256) +COMPILER_RT_OBJECTS := $(patsubst thirdparty/compiler-rt/lib/builtins/%.c,$(COMPILER_RT_OBJECT_DIR)/%.o,$(COMPILER_RT_SOURCES)) +TYPEPHP_OS_LIBRARY_C_SOURCES := user/runtime/posix.c user/dirent.c user/fcntl.c \ + kernel/core/abi/libc.c $(OPENLIBM_SOURCES) TYPEPHP_OS_LIBRARY_CXX_SOURCES := user/runtime/host.cc kernel/core/abi/cxx.cpp TYPEPHP_OS_LIBRARY_ASM_SOURCES := user/runtime/crt0.S user/runtime/syscall.S \ kernel/core/abi/unimplemented.S @@ -30,7 +36,7 @@ TYPEPHP_OS_LIBRARY_OBJECTS := \ $(patsubst %.S,$(TYPEPHP_OS_LIBRARY_OBJECT_DIR)/%.o,$(TYPEPHP_OS_LIBRARY_ASM_SOURCES)) TYPEPHP_OS_LIBRARY_DEPFILES := $(addsuffix .d,$(TYPEPHP_OS_LIBRARY_OBJECTS)) NANO_USER_SOURCES := user/nano/hello.php user/nano/hello.yml user/runtime/linker.ld \ - $(TYPEPHP_OS_LIBRARY) + $(TYPEPHP_OS_LIBRARY) $(COMPILER_RT_LIBRARY) BOOTFLAGS := -m32 -O2 -ffreestanding -fno-pic -fno-pie \ -fno-stack-protector -Wall -Wextra @@ -64,14 +70,19 @@ TYPEPHP_OS_RUNTIME_ASMFLAGS := -m64 -ffreestanding -fno-pic -fno-pie \ -fno-stack-protector -mno-red-zone -mcmodel=small ARFLAGS := rcs -.PHONY: all thirdparty payload objects user-runtime nano-user disk run test clean +.PHONY: all thirdparty thirdparty-smoke compiler-rt-builtins payload objects user-runtime nano-user disk run test clean all: $(KERNEL) $(DISK) thirdparty: ./tools/fetch-thirdparty.sh -payload: thirdparty objects +thirdparty-smoke: thirdparty compiler-rt-builtins + ./tools/test-thirdparty.sh + +compiler-rt-builtins: $(COMPILER_RT_LIBRARY) + +payload: thirdparty objects $(COMPILER_RT_LIBRARY) cd ../.. && ./bin/tpc.php --nano examples/typephp-os/project.yml # Startup sources are built explicitly by Make. The 64-bit entry object is @@ -115,11 +126,26 @@ $(USER_CRT0_OBJECT): user/crt0.S kernel/core/abi/typephp_os_syscall.h | $(OBJECT $(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-dirent.o: user/dirent.c $(USER_HEADERS) kernel/core/abi/typephp_os_syscall.h | $(OBJECT_DIR) + gcc $(USER_CFLAGS) -c $< -o $@ + +$(OBJECT_DIR)/user-fcntl.o: user/fcntl.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) gcc $(USER_CFLAGS) -c $< -o $@ $(OPENLIBM_SOURCES): | thirdparty +$(COMPILER_RT_SOURCES): | thirdparty + +$(COMPILER_RT_OBJECT_DIR)/%.o: thirdparty/compiler-rt/lib/builtins/%.c Makefile + @mkdir -p $(@D) + gcc $(USER_CFLAGS) -std=c11 -Ithirdparty/compiler-rt/lib/builtins -c $< -o $@ + +$(COMPILER_RT_LIBRARY): $(COMPILER_RT_OBJECTS) | $(BUILD) + $(AR) $(ARFLAGS) $@ $^ + $(TYPEPHP_OS_LIBRARY_OBJECT_DIR)/%.o: %.c Makefile @mkdir -p $(@D) gcc $(TYPEPHP_OS_RUNTIME_CFLAGS) -MMD -MP -MF $@.d -c $< -o $@ @@ -142,11 +168,11 @@ $(TYPEPHP_OS_LIBRARY): $(TYPEPHP_OS_LIBRARY_OBJECTS) | $(BUILD) $(NANO_USER_ELF): $(NANO_USER_SOURCES) | thirdparty $(BUILD) cd ../.. && ./bin/tpc.php --nano examples/typephp-os/user/nano/hello.yml -$(USER_SH_ELF): $(OBJECT_DIR)/sh.o $(USER_CRT0_OBJECT) $(USER_LIBC_OBJECTS) user/linker.ld Makefile | $(BUILD) - ld $(USER_LDFLAGS) --defsym USER_BASE=0x40000000 -o $@ $(filter %.o,$^) +$(USER_SH_ELF): $(OBJECT_DIR)/sh.o $(USER_CRT0_OBJECT) $(USER_LIBC_OBJECTS) $(COMPILER_RT_LIBRARY) user/linker.ld Makefile | $(BUILD) + ld $(USER_LDFLAGS) --defsym USER_BASE=0x40000000 -o $@ $(filter %.o,$^) $(COMPILER_RT_LIBRARY) -$(USER_COMMAND_ELFS): $(BUILD)/%.elf: $(OBJECT_DIR)/%.o $(USER_CRT0_OBJECT) $(USER_LIBC_OBJECTS) user/linker.ld Makefile | $(BUILD) - ld $(USER_LDFLAGS) --defsym USER_BASE=0x41000000 -o $@ $(filter %.o,$^) +$(USER_COMMAND_ELFS): $(BUILD)/%.elf: $(OBJECT_DIR)/%.o $(USER_CRT0_OBJECT) $(USER_LIBC_OBJECTS) $(COMPILER_RT_LIBRARY) user/linker.ld Makefile | $(BUILD) + ld $(USER_LDFLAGS) --defsym USER_BASE=0x41000000 -o $@ $(filter %.o,$^) $(COMPILER_RT_LIBRARY) $(KERNEL): payload objcopy -O binary $(PAYLOAD_ELF) $(PAYLOAD_BIN) diff --git a/examples/typephp-os/README.md b/examples/typephp-os/README.md index 74049608..fbe98203 100644 --- a/examples/typephp-os/README.md +++ b/examples/typephp-os/README.md @@ -163,6 +163,10 @@ The QEMU smoke test currently verifies: - 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; +- Linux-compatible directory descriptors and `getdents64`, exposed to C and + PHP Nano through `opendir`, `readdir`, `rewinddir`, and `closedir`; +- the first `fcntl` subset (`F_GETFD`, `F_SETFD`, `F_GETFL`, `F_SETFL`), + including tracked `FD_CLOEXEC`/`O_NONBLOCK` state and effective `O_APPEND`; - 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; @@ -201,8 +205,10 @@ An `int 0x80` boundary currently provides synchronous `read`, `write`, `close`, `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 +anonymous private `mmap`, `mprotect`, `munmap`, `getdents64`, the initial +`fcntl` flag operations, and private +spawn and same-directory rename operations. The former private directory-list +syscall has been removed; `ls` and PHP Nano use the standard directory ABI. 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`. diff --git a/examples/typephp-os/ROADMAP.md b/examples/typephp-os/ROADMAP.md index aba65bb5..0cb45cbd 100644 --- a/examples/typephp-os/ROADMAP.md +++ b/examples/typephp-os/ROADMAP.md @@ -58,7 +58,12 @@ behavior. delivery, and independent freestanding C shell/command programs. The first Linux-compatible file syscalls cover `openat`, `read`, `write`, `lseek`, `close`, `mkdir`, `rmdir`, `unlink`, the stat/access families, synchronous - persistence, and file truncation; `cat`, `write`, `touch`, and the + persistence, file truncation, directory descriptors, and `getdents64`; + `ls` now uses the standard buffered `dirent` API rather than a private + directory-list syscall. Flag-oriented `fcntl` operations preserve + close-on-exec, append, and nonblocking descriptor state, with append + enforced by the write path; descriptor + duplication and record locking remain future work. `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 diff --git a/examples/typephp-os/kernel/core/abi/typephp_os_abi.h b/examples/typephp-os/kernel/core/abi/typephp_os_abi.h index 4c15f814..ff09297b 100644 --- a/examples/typephp-os/kernel/core/abi/typephp_os_abi.h +++ b/examples/typephp-os/kernel/core/abi/typephp_os_abi.h @@ -40,7 +40,7 @@ void phpx_no_exception_abort(const char *message); * boundary. The filesystem implementations continue to live in TypePHP. */ void typephp_os_process_start(void); long typephp_os_fs_path_type(const char *path); -long typephp_os_fs_list(const char *path, char *buffer, size_t capacity); +long typephp_os_fs_getdents64(int fd, void *buffer, size_t capacity); long typephp_os_console_read(void *buffer, size_t size); #ifdef __cplusplus 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 fea72c2c..628cef87 100644 --- a/examples/typephp-os/kernel/core/abi/typephp_os_syscall.h +++ b/examples/typephp-os/kernel/core/abi/typephp_os_syscall.h @@ -20,6 +20,7 @@ enum typephp_os_syscall_number { TYPEPHP_SYS_GETPID = 39, TYPEPHP_SYS_EXIT = 60, TYPEPHP_SYS_UNAME = 63, + TYPEPHP_SYS_FCNTL = 72, TYPEPHP_SYS_FSYNC = 74, TYPEPHP_SYS_FDATASYNC = 75, TYPEPHP_SYS_TRUNCATE = 76, @@ -37,6 +38,7 @@ enum typephp_os_syscall_number { TYPEPHP_SYS_GETPPID = 110, TYPEPHP_SYS_GETTID = 186, TYPEPHP_SYS_TIME = 201, + TYPEPHP_SYS_GETDENTS64 = 217, TYPEPHP_SYS_CLOCK_GETTIME = 228, TYPEPHP_SYS_CLOCK_GETRES = 229, TYPEPHP_SYS_EXIT_GROUP = 231, @@ -44,7 +46,6 @@ enum typephp_os_syscall_number { 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 d7cd78f1..46a59206 100644 --- a/examples/typephp-os/kernel/core/abi/unimplemented.S +++ b/examples/typephp-os/kernel/core/abi/unimplemented.S @@ -19,7 +19,6 @@ TYPEPHP_OS_UNIMPLEMENTED chmod TYPEPHP_OS_UNIMPLEMENTED chown -TYPEPHP_OS_UNIMPLEMENTED fcntl TYPEPHP_OS_UNIMPLEMENTED fdopen TYPEPHP_OS_UNIMPLEMENTED feof TYPEPHP_OS_UNIMPLEMENTED fileno diff --git a/examples/typephp-os/kernel/core/filesystem_abi.cc b/examples/typephp-os/kernel/core/filesystem_abi.cc index bd86e780..e4f6b9d5 100644 --- a/examples/typephp-os/kernel/core/filesystem_abi.cc +++ b/examples/typephp-os/kernel/core/filesystem_abi.cc @@ -147,6 +147,63 @@ struct DirectoryState { explicit DirectoryState(php::Str value) : entries(std::move(value)) {} }; +enum { + DIRECTORY_FD_BEGIN = 64, + DIRECTORY_FD_COUNT = 8, + LINUX_O_ACCMODE = 3, + LINUX_O_CREAT = 64, + LINUX_O_TRUNC = 512, + LINUX_O_APPEND = 1024, + LINUX_O_NONBLOCK = 2048, + LINUX_O_DIRECTORY = 65536, + LINUX_O_CLOEXEC = 524288, + LINUX_F_GETFD = 1, + LINUX_F_SETFD = 2, + LINUX_F_GETFL = 3, + LINUX_F_SETFL = 4, + LINUX_FD_CLOEXEC = 1, +}; + +DirectoryState *open_directories[DIRECTORY_FD_COUNT]{}; +int descriptor_status_flags[DIRECTORY_FD_BEGIN + DIRECTORY_FD_COUNT]{}; +int descriptor_fd_flags[DIRECTORY_FD_BEGIN + DIRECTORY_FD_COUNT]{}; + +DirectoryState *directory_state(int fd) +{ + if (fd < DIRECTORY_FD_BEGIN + || fd >= DIRECTORY_FD_BEGIN + DIRECTORY_FD_COUNT) { + return nullptr; + } + return open_directories[fd - DIRECTORY_FD_BEGIN]; +} + +int open_directory(php::Str path, int flags) +{ + for (int index = 0; index < DIRECTORY_FD_COUNT; ++index) { + if (open_directories[index] == nullptr) { + open_directories[index] = new DirectoryState(fs_entries(std::move(path))); + const int fd = DIRECTORY_FD_BEGIN + index; + descriptor_status_flags[fd] = O_RDONLY | LINUX_O_DIRECTORY; + descriptor_fd_flags[fd] = + (flags & LINUX_O_CLOEXEC) != 0 ? LINUX_FD_CLOEXEC : 0; + return fd; + } + } + errno = EMFILE; + return -1; +} + +struct LinuxDirent64 { + std::uint64_t inode; + std::int64_t offset; + std::uint16_t record_length; + std::uint8_t type; + char name[1]; +}; + +static_assert(offsetof(LinuxDirent64, name) == 19, + "Linux x86-64 dirent64 prefix layout changed"); + } // namespace php::Bool php_kernel_fs_install(php::Object filesystem) @@ -163,29 +220,33 @@ extern "C" long typephp_os_fs_path_type(const char *path) return static_cast(fs_type(php::Str(path))); } -extern "C" long typephp_os_fs_list(const char *path, char *buffer, size_t capacity) -{ - if (path == nullptr || (buffer == nullptr && capacity != 0)) { - return -EFAULT; - } - if (fs_type(php::Str(path)) != 2) { - return -ENOTDIR; - } - php::Str entries = fs_entries(php::Str(path)); - const size_t size = entries.length() < capacity ? entries.length() : capacity; - if (size != 0) { - std::memcpy(buffer, entries.data(), size); - } - return static_cast(size); -} - extern "C" int open(const char *path, int flags, ...) { if (path == nullptr) { errno = EFAULT; return -1; } - return posix_result(fs_open(php::Str(path), flags)); + const php::Int type = fs_type(php::Str(path)); + if (type == 2) { + if ((flags & LINUX_O_ACCMODE) != O_RDONLY + || (flags & (LINUX_O_CREAT | LINUX_O_TRUNC | LINUX_O_APPEND)) != 0) { + errno = EISDIR; + return -1; + } + return open_directory(php::Str(path), flags); + } + if ((flags & LINUX_O_DIRECTORY) != 0) { + errno = type == 0 ? ENOENT : ENOTDIR; + return -1; + } + const int fd = posix_result(fs_open(php::Str(path), flags)); + if (fd >= 0 && fd < DIRECTORY_FD_BEGIN) { + descriptor_status_flags[fd] = flags + & (LINUX_O_ACCMODE | LINUX_O_APPEND | LINUX_O_NONBLOCK); + descriptor_fd_flags[fd] = + (flags & LINUX_O_CLOEXEC) != 0 ? LINUX_FD_CLOEXEC : 0; + } + return fd; } extern "C" int close(int fd) @@ -193,7 +254,19 @@ extern "C" int close(int fd) if (fd >= 0 && fd <= 2) { return 0; } - return posix_result(fs_close(fd)); + if (DirectoryState *state = directory_state(fd)) { + delete state; + open_directories[fd - DIRECTORY_FD_BEGIN] = nullptr; + descriptor_status_flags[fd] = 0; + descriptor_fd_flags[fd] = 0; + return 0; + } + const int result = posix_result(fs_close(fd)); + if (result == 0 && fd >= 0 && fd < DIRECTORY_FD_BEGIN) { + descriptor_status_flags[fd] = 0; + descriptor_fd_flags[fd] = 0; + } + return result; } extern "C" ssize_t read(int fd, void *buffer, size_t count) @@ -205,6 +278,10 @@ extern "C" ssize_t read(int fd, void *buffer, size_t count) if (fd == STDIN_FILENO) { return static_cast(typephp_os_console_read(buffer, count)); } + if (directory_state(fd) != nullptr) { + errno = EISDIR; + return -1; + } const php::Int size = fs_fd_size(fd); if (size < 0) { errno = static_cast(-size); @@ -225,22 +302,112 @@ extern "C" ssize_t write(int fd, const void *buffer, size_t count) typephp_os_write(static_cast(buffer), count); return static_cast(count); } + if (directory_state(fd) != nullptr) { + errno = EISDIR; + return -1; + } + if (fd >= 0 && fd < DIRECTORY_FD_BEGIN + && (descriptor_status_flags[fd] & LINUX_O_APPEND) != 0) { + const php::Int result = fs_seek(fd, 0, SEEK_END); + if (result < 0) { + errno = static_cast(-result); + return -1; + } + } return static_cast(posix_result(fs_write( fd, php::Str(static_cast(buffer), count)))); } +extern "C" int fcntl(int fd, int command, ...) +{ + int argument = 0; + const bool valid = fd >= 0 && fd <= STDERR_FILENO + ? true + : directory_state(fd) != nullptr || fs_fd_size(fd) >= 0; + if (!valid) { + errno = EBADF; + return -1; + } + if (command == LINUX_F_SETFD || command == LINUX_F_SETFL) { + va_list arguments; + va_start(arguments, command); + argument = va_arg(arguments, int); + va_end(arguments); + } + switch (command) { + case LINUX_F_GETFD: + return fd < static_cast(sizeof(descriptor_fd_flags) + / sizeof(descriptor_fd_flags[0])) ? descriptor_fd_flags[fd] : 0; + case LINUX_F_SETFD: + if ((argument & ~LINUX_FD_CLOEXEC) != 0) { + errno = EINVAL; + return -1; + } + descriptor_fd_flags[fd] = argument; + return 0; + case LINUX_F_GETFL: + if (fd == STDIN_FILENO) { + return O_RDONLY; + } + if (fd == STDOUT_FILENO || fd == STDERR_FILENO) { + return O_WRONLY; + } + return descriptor_status_flags[fd]; + case LINUX_F_SETFL: + if ((argument & ~(LINUX_O_APPEND | LINUX_O_NONBLOCK)) != 0) { + errno = EINVAL; + return -1; + } + if (fd > STDERR_FILENO) { + descriptor_status_flags[fd] = + (descriptor_status_flags[fd] + & ~(LINUX_O_APPEND | LINUX_O_NONBLOCK)) + | argument; + } + return 0; + default: + errno = EINVAL; + return -1; + } +} + extern "C" off_t lseek(int fd, off_t offset, int whence) { + if (DirectoryState *state = directory_state(fd)) { + off_t position = offset; + if (whence == SEEK_CUR) { + position = static_cast(state->offset) + offset; + } else if (whence == SEEK_END) { + position = static_cast(state->entries.length()) + offset; + } else if (whence != SEEK_SET) { + errno = EINVAL; + return -1; + } + if (position < 0 + || static_cast(position) > state->entries.length()) { + errno = EINVAL; + return -1; + } + state->offset = static_cast(position); + return position; + } return posix_offset(fs_seek(fd, offset, whence)); } extern "C" int fsync(int fd) { + if (directory_state(fd) != nullptr) { + return 0; + } return posix_result(fs_flush(fd)); } extern "C" int ftruncate(int fd, off_t size) { + if (directory_state(fd) != nullptr) { + errno = EISDIR; + return -1; + } return posix_result(fs_truncate(fd, size)); } @@ -281,6 +448,10 @@ extern "C" int fstat(int fd, struct stat *value) value->st_mode = S_IFCHR | 0666; return 0; } + if (directory_state(fd) != nullptr) { + fill_stat(value, 2, 0); + return 0; + } const php::Int size = fs_fd_size(fd); if (size < 0) { errno = static_cast(-size); @@ -290,6 +461,52 @@ extern "C" int fstat(int fd, struct stat *value) return 0; } +extern "C" long typephp_os_fs_getdents64( + int fd, void *buffer, std::size_t capacity) +{ + DirectoryState *state = directory_state(fd); + auto *output = static_cast(buffer); + std::size_t written = 0; + if (state == nullptr) { + return -ENOTDIR; + } + if (buffer == nullptr && capacity != 0) { + return -EFAULT; + } + while (state->offset < state->entries.length()) { + const std::size_t start = state->offset; + std::size_t length = 0; + while (start + length < state->entries.length() + && state->entries.data()[start + length] != '\n') { + ++length; + } + const std::size_t next = start + length + + (start + length < state->entries.length() ? 1u : 0u); + const std::size_t record_size = + (offsetof(LinuxDirent64, name) + length + 1u + 7u) & ~std::size_t{7u}; + if (record_size > capacity - written) { + if (written == 0) { + return -EINVAL; + } + break; + } + auto *record = reinterpret_cast(output + written); + std::memset(record, 0, record_size); + record->inode = start + 1u; + record->offset = static_cast(next); + record->record_length = static_cast(record_size); + record->type = (length == 1 && state->entries.data()[start] == '.') + || (length == 2 && state->entries.data()[start] == '.' + && state->entries.data()[start + 1u] == '.') + ? DT_DIR : DT_UNKNOWN; + std::memcpy(record->name, state->entries.data() + start, length); + record->name[length] = '\0'; + state->offset = next; + written += record_size; + } + return static_cast(written); +} + extern "C" int mkdir(const char *path, mode_t mode) { (void) mode; diff --git a/examples/typephp-os/kernel/core/process.c b/examples/typephp-os/kernel/core/process.c index 311c6330..57bbaf59 100644 --- a/examples/typephp-os/kernel/core/process.c +++ b/examples/typephp-os/kernel/core/process.c @@ -541,22 +541,6 @@ static long syscall_chdir(const char *path) return 0; } -static long syscall_readdir(const char *path, char *buffer, size_t capacity) -{ - char resolved[USER_PATH_MAX]; - const char *directory = foreground_process.cwd; - if (path != 0) { - if (!resolved_path(path, resolved, sizeof(resolved))) { - return -ENOENT; - } - directory = resolved; - } - if (!user_buffer(buffer, capacity)) { - return -EFAULT; - } - return typephp_os_fs_list(directory, buffer, capacity); -} - static int resolved_path(const char *path, char *resolved, size_t capacity) { size_t length = bounded_user_string(path, capacity); @@ -1175,6 +1159,9 @@ long typephp_os_syscall_dispatch(syscall_frame *frame) case TYPEPHP_SYS_GETPID: case TYPEPHP_SYS_GETTID: return (long) foreground_process.pid; + case TYPEPHP_SYS_FCNTL: + return posix_syscall_result(fcntl( + (int) frame->rdi, (int) frame->rsi, (int) frame->rdx)); case TYPEPHP_SYS_SPAWN: return syscall_spawn(frame, (const char *const *) frame->rdi); case TYPEPHP_SYS_GETCWD: @@ -1224,6 +1211,15 @@ long typephp_os_syscall_dispatch(syscall_frame *frame) } case TYPEPHP_SYS_TIME: return syscall_time((long *) frame->rdi); + case TYPEPHP_SYS_GETDENTS64: + if (frame->rdx == 0) { + return -EINVAL; + } + if (!user_buffer((void *) frame->rsi, frame->rdx)) { + return -EFAULT; + } + return typephp_os_fs_getdents64((int) frame->rdi, + (void *) frame->rsi, frame->rdx); case TYPEPHP_SYS_GETTIMEOFDAY: if (frame->rsi != 0) { return -EINVAL; @@ -1242,9 +1238,6 @@ long typephp_os_syscall_dispatch(syscall_frame *frame) 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); diff --git a/examples/typephp-os/project.yml b/examples/typephp-os/project.yml index e30bff94..693a8e30 100644 --- a/examples/typephp-os/project.yml +++ b/examples/typephp-os/project.yml @@ -5,6 +5,10 @@ sources: - thirdparty/openlibm/src objects: - build/objects/kernel64-entry.o +link-paths: + - build +link-libs: + - compiler-rt-builtins build-dir: build/generated output: build/kernel64.elf cxx-std: c++17 diff --git a/examples/typephp-os/tools/compiler-rt-builtins-files.sha256 b/examples/typephp-os/tools/compiler-rt-builtins-files.sha256 new file mode 100644 index 00000000..c7afe0bc --- /dev/null +++ b/examples/typephp-os/tools/compiler-rt-builtins-files.sha256 @@ -0,0 +1,15 @@ +1a8f1058753f1ba890de984e48f0242a3a5c29a6a8f2ed9fd813f36985387e8d LICENSE.TXT +ec5175561075845b7b16a92ad5ecdfd42a284de2736dd07e4ca7011b75c37709 lib/builtins/int_endianness.h +a5735bf1bfacecd6cf5785ed19ae62a981ff2f0923c38caa99f4af47f2be57b5 lib/builtins/int_lib.h +198552b25faf8a75b077ea145591cd7f6762b7f2712321b18f5e38fee8cc2a1e lib/builtins/int_types.h +63d06198ab720a75c20bfd84be895c2746581b129168bae6201e7384bbb0473b lib/builtins/int_util.h +833906405d418f0f421d6155acc5abf79d9902ed37a44903d01c95ff55d3a010 lib/builtins/int_div_impl.inc +39afba4710d9b24c9c27cccdc8e18a18db10ce3616dc60cdcfa494f0a49a3a60 lib/builtins/ashlti3.c +a6da2dc32167fbf369bbb0d185daeaf1b23aa13fda72f832e2097926c9fb2d74 lib/builtins/ashrti3.c +89da91e31e33c3c62128c32ed1c740856a413c17ac1e7cd75702da37d1cf9fa4 lib/builtins/lshrti3.c +b8392be2b070cda2a01bc33c7912e06ab83ce9a1ec7995fe0acbe44540cf92ae lib/builtins/divti3.c +23b5f5af57a8a3ccdab785eec6e486bffef932cee87ff1152b3beedaa6296414 lib/builtins/modti3.c +b2e133764668e78119f7c76facbb11f0b4b7c0c6b279fb0b97a589da11d1d060 lib/builtins/multi3.c +c0b20f06f6e0e8a6b5423ca6674cf0c57219d07554f6f35c9ecc36bf28915355 lib/builtins/udivti3.c +2fb3eb297e006000935452cb34622b389c5ce39af6e1416b4a11398b78142251 lib/builtins/udivmodti4.c +3d88088419d87b486858a4bf54ff08209944ae526c77b8dc5e0c0b6b8001c1e9 lib/builtins/umodti3.c diff --git a/examples/typephp-os/tools/fetch-compiler-rt-builtins.sh b/examples/typephp-os/tools/fetch-compiler-rt-builtins.sh new file mode 100755 index 00000000..9ff5817e --- /dev/null +++ b/examples/typephp-os/tools/fetch-compiler-rt-builtins.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env sh + +set -eu + +project_dir=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +version=23.1.1 +tag="llvmorg-${version}" +target="${project_dir}/thirdparty/compiler-rt" +marker="${target}/.typephp-source-${version}" +manifest="${project_dir}/tools/compiler-rt-builtins-files.sha256" +base_url="https://raw.githubusercontent.com/llvm/llvm-project/${tag}/compiler-rt" + +if [ -f "${marker}" ] && [ -f "${target}/lib/builtins/udivti3.c" ]; then + exit 0 +fi + +temporary=$(mktemp -d "${TMPDIR:-/tmp}/typephp-os-compiler-rt.XXXXXX") +trap 'rm -rf "${temporary}"' EXIT HUP INT TERM +staging="${temporary}/installed" + +echo "Downloading compiler-rt builtins ${version}" +while read -r expected relative; do + [ -n "${relative}" ] || continue + destination="${staging}/${relative}" + mkdir -p "$(dirname -- "${destination}")" + curl -L --fail --retry 3 -o "${destination}" "${base_url}/${relative}" + printf '%s %s\n' "${expected}" "${destination}" | sha256sum -c - +done < "${manifest}" + +mkdir -p "${project_dir}/thirdparty" +rm -rf "${target}" +mv "${staging}" "${target}" +printf '%s\n' "${tag}" > "${marker}" +echo "Installed compiler-rt builtins ${version} in ${target}" diff --git a/examples/typephp-os/tools/fetch-mlibc.sh b/examples/typephp-os/tools/fetch-mlibc.sh new file mode 100755 index 00000000..6bb9d7d7 --- /dev/null +++ b/examples/typephp-os/tools/fetch-mlibc.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env sh + +set -eu + +project_dir=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +version=7.0.0 +commit=7c2a178142625cc9852e59a1a090468c61a62d3b +archive_sha256=56d62366a6a15bc3fd8741bebcb311ca543553f7b734b1e780aeae33691bd621 +target="${project_dir}/thirdparty/mlibc" +marker="${target}/.typephp-source-${version}" + +if [ -f "${marker}" ] && [ -f "${target}/meson.build" ]; then + exit 0 +fi + +temporary=$(mktemp -d "${TMPDIR:-/tmp}/typephp-os-mlibc.XXXXXX") +trap 'rm -rf "${temporary}"' EXIT HUP INT TERM +archive="${temporary}/mlibc.tar.gz" +staging="${temporary}/installed" + +echo "Downloading mlibc v${version} (${commit})" +curl -L --fail --retry 3 -o "${archive}" \ + "https://github.com/managarm/mlibc/archive/refs/tags/v${version}.tar.gz" +printf '%s %s\n' "${archive_sha256}" "${archive}" | sha256sum -c - +mkdir -p "${staging}" +tar -xzf "${archive}" -C "${staging}" --strip-components=1 +test -f "${staging}/LICENSE" + +mkdir -p "${project_dir}/thirdparty" +rm -rf "${target}" +mv "${staging}" "${target}" +printf '%s\n' "${commit}" > "${marker}" +echo "Installed mlibc v${version} in ${target}" diff --git a/examples/typephp-os/tools/fetch-openlibm.sh b/examples/typephp-os/tools/fetch-openlibm.sh new file mode 100755 index 00000000..1b8b714d --- /dev/null +++ b/examples/typephp-os/tools/fetch-openlibm.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env sh + +set -eu + +project_dir=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +version=0.8.7 +archive_sha256=e328a1d59b94748b111e022bca6a9d2fc0481fb57d23c87d90f394b559d4f062 +target="${project_dir}/thirdparty/openlibm" +marker="${target}/.typephp-source-${version}" +manifest="${project_dir}/tools/openlibm-files.txt" + +if [ -f "${marker}" ] && [ -f "${target}/src/e_sqrt.c" ]; then + exit 0 +fi + +temporary=$(mktemp -d "${TMPDIR:-/tmp}/typephp-os-openlibm.XXXXXX") +trap 'rm -rf "${temporary}"' EXIT HUP INT TERM +archive="${temporary}/openlibm.tar.gz" +source_dir="${temporary}/openlibm-${version}" +staging="${temporary}/installed" + +echo "Downloading OpenLibm v${version}" +curl -L --fail --retry 3 \ + -o "${archive}" \ + "https://github.com/JuliaMath/openlibm/archive/refs/tags/v${version}.tar.gz" +echo "${archive_sha256} ${archive}" | sha256sum -c - +tar -xzf "${archive}" -C "${temporary}" + +while IFS= read -r relative; do + [ -n "${relative}" ] || continue + mkdir -p "${staging}/$(dirname -- "${relative}")" + cp "${source_dir}/${relative}" "${staging}/${relative}" +done < "${manifest}" + +mkdir -p "${project_dir}/thirdparty" +rm -rf "${target}" +mv "${staging}" "${target}" +printf '%s\n' "${archive_sha256}" > "${marker}" +echo "Installed OpenLibm v${version} in ${target}" diff --git a/examples/typephp-os/tools/fetch-thirdparty.sh b/examples/typephp-os/tools/fetch-thirdparty.sh index 6ca8857c..e5b466bd 100755 --- a/examples/typephp-os/tools/fetch-thirdparty.sh +++ b/examples/typephp-os/tools/fetch-thirdparty.sh @@ -2,38 +2,9 @@ set -eu -project_dir=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) -version=0.8.7 -archive_sha256=e328a1d59b94748b111e022bca6a9d2fc0481fb57d23c87d90f394b559d4f062 -target="${project_dir}/thirdparty/openlibm" -marker="${target}/.typephp-source-${version}" -manifest="${project_dir}/tools/openlibm-files.txt" +tools_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) -if [ -f "${marker}" ] && [ -f "${target}/src/e_sqrt.c" ]; then - exit 0 -fi - -temporary=$(mktemp -d "${TMPDIR:-/tmp}/typephp-os-thirdparty.XXXXXX") -trap 'rm -rf "${temporary}"' EXIT HUP INT TERM -archive="${temporary}/openlibm.tar.gz" -source_dir="${temporary}/openlibm-${version}" -staging="${temporary}/installed" - -echo "Downloading OpenLibm v${version}" -curl -L --fail --retry 3 \ - -o "${archive}" \ - "https://github.com/JuliaMath/openlibm/archive/refs/tags/v${version}.tar.gz" -echo "${archive_sha256} ${archive}" | sha256sum -c - -tar -xzf "${archive}" -C "${temporary}" - -while IFS= read -r relative; do - [ -n "${relative}" ] || continue - mkdir -p "${staging}/$(dirname -- "${relative}")" - cp "${source_dir}/${relative}" "${staging}/${relative}" -done < "${manifest}" - -mkdir -p "${project_dir}/thirdparty" -rm -rf "${target}" -mv "${staging}" "${target}" -printf '%s\n' "${archive_sha256}" > "${marker}" -echo "Installed OpenLibm v${version} in ${target}" +"${tools_dir}/fetch-openlibm.sh" +"${tools_dir}/fetch-compiler-rt-builtins.sh" +"${tools_dir}/fetch-mlibc.sh" +"${tools_dir}/fetch-toybox.sh" diff --git a/examples/typephp-os/tools/fetch-toybox.sh b/examples/typephp-os/tools/fetch-toybox.sh new file mode 100755 index 00000000..92c8a817 --- /dev/null +++ b/examples/typephp-os/tools/fetch-toybox.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env sh + +set -eu + +project_dir=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +version=0.8.14 +commit=b7ec52ac35e075caffca5d330995d44e8dbfc8c3 +archive_sha256=827e4cdfd69f5da973e00e2a59b30b3c9857fb7fae74c362fd0b4f96be7929b0 +target="${project_dir}/thirdparty/toybox" +marker="${target}/.typephp-source-${version}" + +if [ -f "${marker}" ] && [ -f "${target}/Makefile" ]; then + exit 0 +fi + +temporary=$(mktemp -d "${TMPDIR:-/tmp}/typephp-os-toybox.XXXXXX") +trap 'rm -rf "${temporary}"' EXIT HUP INT TERM +archive="${temporary}/toybox.tar.gz" +staging="${temporary}/installed" + +echo "Downloading Toybox ${version} (${commit})" +curl -L --fail --retry 3 -o "${archive}" \ + "https://landley.net/toybox/downloads/toybox-${version}.tar.gz" +printf '%s %s\n' "${archive_sha256}" "${archive}" | sha256sum -c - +mkdir -p "${staging}" +tar -xzf "${archive}" -C "${staging}" --strip-components=1 +test -f "${staging}/LICENSE" + +mkdir -p "${project_dir}/thirdparty" +rm -rf "${target}" +mv "${staging}" "${target}" +printf '%s\n' "${commit}" > "${marker}" +echo "Installed Toybox ${version} in ${target}" diff --git a/examples/typephp-os/tools/mlibc-demo-cross.ini b/examples/typephp-os/tools/mlibc-demo-cross.ini new file mode 100644 index 00000000..49a20260 --- /dev/null +++ b/examples/typephp-os/tools/mlibc-demo-cross.ini @@ -0,0 +1,14 @@ +[binaries] +c = 'gcc' +cpp = 'g++' +ar = 'ar' +strip = 'strip' + +[host_machine] +system = 'demo' +cpu_family = 'x86_64' +cpu = 'x86_64' +endian = 'little' + +[properties] +needs_exe_wrapper = true diff --git a/examples/typephp-os/tools/mlibc-headers-smoke.c b/examples/typephp-os/tools/mlibc-headers-smoke.c new file mode 100644 index 00000000..16248be9 --- /dev/null +++ b/examples/typephp-os/tools/mlibc-headers-smoke.c @@ -0,0 +1,26 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +_Static_assert(sizeof(uintptr_t) == 8, "TypePHP-OS requires a 64-bit mlibc ABI"); + +static void typecheck(void) +{ + struct dirent entry; + struct stat status; + struct timespec timestamp; + struct utsname identity; + + (void) entry; + (void) status; + (void) timestamp; + (void) identity; +} diff --git a/examples/typephp-os/tools/test-qemu.sh b/examples/typephp-os/tools/test-qemu.sh index 9c426675..ba453cd1 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\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 + <<< $'\ndate\nuname\nuname -a\necho Hello TypePHP userspace\nhello Dynamically loaded\ntnhello alpha beta\nsystest\nbuiltins\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,7 +51,7 @@ 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, systest, fault, vmfault, wrfault" "${log}" +grep -q "Commands: ls, cd, pwd, date, uname, cat, echo, write, touch, mkdir, rm, rmdir, mv, memtest, systest, builtins, 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 -Fq $'TypePHP-OS\r' "${log}" grep -Fq $'TypePHP-OS typephp-os 0.1 TypePHP Nano user mode x86_64\r' "${log}" @@ -65,11 +65,12 @@ 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 +if [[ $(grep -c '^bool(true)' "${log}") -lt 3 ]]; then + echo "the Nano smoke program did not complete its file and directory checks" >&2 exit 1 fi grep -q '^basic syscalls: OK' "${log}" +grep -q '^compiler-rt builtins: 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/tools/test-thirdparty.sh b/examples/typephp-os/tools/test-thirdparty.sh new file mode 100755 index 00000000..e2adcbb6 --- /dev/null +++ b/examples/typephp-os/tools/test-thirdparty.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash + +set -euo pipefail + +project_dir=$(cd -- "$(dirname -- "$0")/.." && pwd) +build_dir="${project_dir}/build/thirdparty-smoke" +meson_command=${MESON:-meson} + +if [[ ! -f "${project_dir}/build/libcompiler-rt-builtins.a" ]]; then + echo "compiler-rt archive is missing; run make compiler-rt-builtins first" >&2 + exit 1 +fi +for symbol in __udivti3 __udivmodti4 __divti3 __modti3 __multi3; do + if ! nm -g --defined-only "${project_dir}/build/libcompiler-rt-builtins.a" \ + | grep -qE "[[:space:]]${symbol}$"; then + echo "compiler-rt archive does not define ${symbol}" >&2 + exit 1 + fi +done +echo "compiler-rt builtins archive: OK" + +if ! command -v "${meson_command}" >/dev/null 2>&1; then + echo "Meson 1.3 or newer is required for the mlibc headers probe" >&2 + exit 1 +fi + +mlibc_build="${build_dir}/mlibc-build" +mlibc_sysroot="${build_dir}/mlibc-sysroot" +rm -rf "${mlibc_build}" "${mlibc_sysroot}" +"${meson_command}" setup \ + "${mlibc_build}" "${project_dir}/thirdparty/mlibc" \ + --cross-file "${project_dir}/tools/mlibc-demo-cross.ini" \ + --prefix=/usr \ + -Dheaders_only=true \ + -Dposix_option=enabled \ + -Dlinux_option=disabled \ + -Dglibc_option=disabled \ + -Dbsd_option=disabled +DESTDIR="${mlibc_sysroot}" "${meson_command}" install -C "${mlibc_build}" +compiler_headers=$(gcc -print-file-name=include) +gcc -std=c11 -ffreestanding -fsyntax-only -nostdinc \ + -isystem "${mlibc_sysroot}/usr/include" \ + -isystem "${compiler_headers}" \ + "${project_dir}/tools/mlibc-headers-smoke.c" +echo "mlibc public-header ABI probe: OK" + +toybox_source="${project_dir}/thirdparty/toybox" +toybox_config="${build_dir}/toybox.config" +mkdir -p "${build_dir}" +KCONFIG_ALLCONFIG="${project_dir}/tools/toybox-miniconfig" \ +KCONFIG_CONFIG="${toybox_config}" \ +CFLAGS="-O2 -fno-stack-protector -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0" \ + make -C "${toybox_source}" allnoconfig toybox + +test "$("${toybox_source}/toybox" | tr ' ' '\n' | sed '/^$/d' | sort | tr '\n' ' ')" \ + = "cat date echo pwd uname " +test "$("${toybox_source}/toybox" echo 'Toybox smoke: OK')" = "Toybox smoke: OK" +"${toybox_source}/toybox" date -u +%Y-%m-%d >/dev/null +"${toybox_source}/toybox" uname -s >/dev/null + +toybox_symbols="${build_dir}/toybox-undefined-symbols.txt" +nm -u "${toybox_source}/generated/unstripped/toybox" \ + | awk '{ print $NF }' | sed 's/@.*//' | sort -u > "${toybox_symbols}" +if grep -Eq '^(fork|vfork|execv|execve|execvp|posix_spawn)$' "${toybox_symbols}"; then + echo "the selected Toybox applets unexpectedly require process creation" >&2 + exit 1 +fi +echo "Toybox selected applets build: OK" +if grep -qx ioctl "${toybox_symbols}"; then + echo "Toybox install: deferred (the common runtime still references unsupported ioctl)" +else + echo "Toybox install: ABI audit no longer reports ioctl; reassess target linking" +fi +echo "Toybox ABI report: ${toybox_symbols}" diff --git a/examples/typephp-os/tools/toybox-miniconfig b/examples/typephp-os/tools/toybox-miniconfig new file mode 100644 index 00000000..a84cec99 --- /dev/null +++ b/examples/typephp-os/tools/toybox-miniconfig @@ -0,0 +1,8 @@ +CONFIG_TOYBOX=y +CONFIG_TOYBOX_HELP=y +# CONFIG_TOYBOX_FORK is not set +CONFIG_CAT=y +CONFIG_DATE=y +CONFIG_ECHO=y +CONFIG_PWD=y +CONFIG_UNAME=y diff --git a/examples/typephp-os/user/README.md b/examples/typephp-os/user/README.md index 2d062ce6..2e159864 100644 --- a/examples/typephp-os/user/README.md +++ b/examples/typephp-os/user/README.md @@ -35,8 +35,11 @@ The current bootstrap libc provides `syscall`, `read`, `write`, `openat`, `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 +`mprotect`, `munmap`, `opendir`, `fdopendir`, `readdir`, `rewinddir`, +`closedir`, `dirfd`, the `F_GETFD`/`F_SETFD`/`F_GETFL`/`F_SETFL` subset of +`fcntl`, `strlen`, `strerror`, `perror`, and `_exit` with +libc-compatible C signatures. Directory streams are backed by Linux x86-64 +`getdents64`; no TypePHP-OS-private directory syscall is exposed. 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. @@ -64,8 +67,9 @@ 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` +The kernel additionally accepts Linux x86-64 `newfstatat`, `faccessat`, +`getdents64`, the initial flag-only `fcntl` subset, 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. diff --git a/examples/typephp-os/user/builtins.c b/examples/typephp-os/user/builtins.c new file mode 100644 index 00000000..26a5f5fd --- /dev/null +++ b/examples/typephp-os/user/builtins.c @@ -0,0 +1,44 @@ +#include +#include + +typedef unsigned __int128 uint128_t; +typedef __int128 int128_t; + +static int fail(void) +{ + static const char message[] = "compiler-rt builtins: failed\n"; + (void) write(STDERR_FILENO, message, sizeof(message) - 1); + return 1; +} + +int main(int argc, char **argv) +{ + volatile uint128_t unsigned_numerator = ((uint128_t) 1 << 100) + 123456789u; + volatile uint128_t unsigned_divisor = ((uint128_t) 1 << 37) + 19u; + volatile int128_t signed_numerator = -(((int128_t) 1 << 99) + 7654321); + volatile int128_t signed_divisor = ((int128_t) 1 << 35) + 11; + uint128_t quotient; + uint128_t remainder; + int128_t signed_quotient; + int128_t signed_remainder; + (void) argc; + (void) argv; + + quotient = unsigned_numerator / unsigned_divisor; + remainder = unsigned_numerator % unsigned_divisor; + if (quotient * unsigned_divisor + remainder != unsigned_numerator + || remainder >= unsigned_divisor) { + return fail(); + } + + signed_quotient = signed_numerator / signed_divisor; + signed_remainder = signed_numerator % signed_divisor; + if (signed_quotient * signed_divisor + signed_remainder != signed_numerator + || signed_remainder > 0) { + return fail(); + } + + (void) write(STDOUT_FILENO, "compiler-rt builtins: OK\n", + sizeof("compiler-rt builtins: OK\n") - 1); + return 0; +} diff --git a/examples/typephp-os/user/dirent.c b/examples/typephp-os/user/dirent.c new file mode 100644 index 00000000..16f6db00 --- /dev/null +++ b/examples/typephp-os/user/dirent.c @@ -0,0 +1,171 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +/* glibc hides this GNU extension when php-nano requests POSIX.1-2008 only. */ +long syscall(long number, ...); + +#ifndef O_DIRECTORY +#define O_DIRECTORY 65536 +#endif + +#ifndef O_CLOEXEC +#define O_CLOEXEC 524288 +#endif + +enum { + TYPEPHP_DIRECTORY_STREAMS = 4, + TYPEPHP_DIRECTORY_BUFFER_SIZE = 1024, +}; + +typedef struct { + int used; + int fd; + size_t offset; + size_t length; + unsigned char buffer[TYPEPHP_DIRECTORY_BUFFER_SIZE]; + struct dirent entry; +} typephp_directory_stream; + +static typephp_directory_stream directory_streams[TYPEPHP_DIRECTORY_STREAMS]; + +static typephp_directory_stream *directory_state(DIR *directory) +{ + const uintptr_t address = (uintptr_t) directory; + const uintptr_t begin = (uintptr_t) directory_streams; + const uintptr_t end = (uintptr_t) (directory_streams + + TYPEPHP_DIRECTORY_STREAMS); + typephp_directory_stream *state; + if (address < begin || address >= end + || (address - begin) % sizeof(typephp_directory_stream) != 0) { + errno = EBADF; + return NULL; + } + state = (typephp_directory_stream *) directory; + if (!state->used) { + errno = EBADF; + return NULL; + } + return state; +} + +DIR *fdopendir(int fd) +{ + struct stat status; + size_t index; + if (fstat(fd, &status) != 0) { + return NULL; + } + if (!S_ISDIR(status.st_mode)) { + errno = ENOTDIR; + return NULL; + } + for (index = 0; index < TYPEPHP_DIRECTORY_STREAMS; ++index) { + typephp_directory_stream *state = &directory_streams[index]; + if (!state->used) { + state->used = 1; + state->fd = fd; + state->offset = 0; + state->length = 0; + return (DIR *) state; + } + } + errno = EMFILE; + return NULL; +} + +DIR *opendir(const char *path) +{ + int fd = open(path, O_RDONLY | O_DIRECTORY | O_CLOEXEC); + DIR *directory; + if (fd < 0) { + return NULL; + } + directory = fdopendir(fd); + if (directory == NULL) { + int saved_errno = errno; + (void) close(fd); + errno = saved_errno; + } + return directory; +} + +struct dirent *readdir(DIR *directory) +{ + typephp_directory_stream *state = directory_state(directory); + struct dirent *record; + size_t name_capacity; + size_t name_length = 0; + if (state == NULL) { + return NULL; + } + if (state->offset == state->length) { + long length = syscall(SYS_getdents64, state->fd, + state->buffer, sizeof(state->buffer)); + if (length <= 0) { + return NULL; + } + state->offset = 0; + state->length = (size_t) length; + } + record = (struct dirent *) (state->buffer + state->offset); + if (record->d_reclen < offsetof(struct dirent, d_name) + 1 + || record->d_reclen > state->length - state->offset) { + errno = EIO; + return NULL; + } + name_capacity = record->d_reclen - offsetof(struct dirent, d_name); + while (name_length < name_capacity + && record->d_name[name_length] != '\0') { + ++name_length; + } + if (name_length == name_capacity + || name_length >= sizeof(state->entry.d_name)) { + errno = EIO; + return NULL; + } + state->entry.d_ino = record->d_ino; + state->entry.d_off = record->d_off; + state->entry.d_reclen = record->d_reclen; + state->entry.d_type = record->d_type; + for (size_t index = 0; index <= name_length; ++index) { + state->entry.d_name[index] = record->d_name[index]; + } + state->offset += record->d_reclen; + return &state->entry; +} + +int closedir(DIR *directory) +{ + typephp_directory_stream *state = directory_state(directory); + int result; + if (state == NULL) { + return -1; + } + result = close(state->fd); + state->used = 0; + state->fd = -1; + state->offset = 0; + state->length = 0; + return result; +} + +void rewinddir(DIR *directory) +{ + typephp_directory_stream *state = directory_state(directory); + if (state != NULL && lseek(state->fd, 0, SEEK_SET) >= 0) { + state->offset = 0; + state->length = 0; + } +} + +int dirfd(DIR *directory) +{ + typephp_directory_stream *state = directory_state(directory); + return state == NULL ? -1 : state->fd; +} diff --git a/examples/typephp-os/user/fcntl.c b/examples/typephp-os/user/fcntl.c new file mode 100644 index 00000000..6f0b36c1 --- /dev/null +++ b/examples/typephp-os/user/fcntl.c @@ -0,0 +1,18 @@ +#include +#include +#include + +/* glibc hides this GNU extension when php-nano requests POSIX.1-2008 only. */ +long syscall(long number, ...); + +int fcntl(int fd, int command, ...) +{ + long argument = 0; + if (command == F_SETFD || command == F_SETFL) { + va_list arguments; + va_start(arguments, command); + argument = va_arg(arguments, int); + va_end(arguments); + } + return (int) syscall(SYS_fcntl, fd, command, argument); +} diff --git a/examples/typephp-os/user/include/dirent.h b/examples/typephp-os/user/include/dirent.h new file mode 100644 index 00000000..8b47296e --- /dev/null +++ b/examples/typephp-os/user/include/dirent.h @@ -0,0 +1,35 @@ +#ifndef TYPEPHP_OS_USER_DIRENT_H +#define TYPEPHP_OS_USER_DIRENT_H + +#include + +/* Linux x86-64 dirent64 layout. getdents64() returns variable-length records; + * readdir() exposes the same prefix through this conventional fixed-size + * userspace view. */ +struct dirent { + ino_t d_ino; + off_t d_off; + unsigned short d_reclen; + unsigned char d_type; + char d_name[256]; +}; + +#define DT_UNKNOWN 0 +#define DT_FIFO 1 +#define DT_CHR 2 +#define DT_DIR 4 +#define DT_BLK 6 +#define DT_REG 8 +#define DT_LNK 10 +#define DT_SOCK 12 + +typedef struct __typephp_dir_stream DIR; + +DIR *opendir(const char *path); +DIR *fdopendir(int fd); +struct dirent *readdir(DIR *directory); +int closedir(DIR *directory); +void rewinddir(DIR *directory); +int dirfd(DIR *directory); + +#endif diff --git a/examples/typephp-os/user/include/fcntl.h b/examples/typephp-os/user/include/fcntl.h index a3650783..574795f6 100644 --- a/examples/typephp-os/user/include/fcntl.h +++ b/examples/typephp-os/user/include/fcntl.h @@ -7,6 +7,16 @@ #define O_CREAT 64 #define O_TRUNC 512 #define O_APPEND 1024 +#define O_NONBLOCK 2048 +#define O_DIRECTORY 65536 +#define O_CLOEXEC 524288 + +#define F_GETFD 1 +#define F_SETFD 2 +#define F_GETFL 3 +#define F_SETFL 4 + +#define FD_CLOEXEC 1 #define AT_FDCWD (-100) #define AT_SYMLINK_NOFOLLOW 0x100 @@ -14,5 +24,6 @@ int open(const char *path, int flags, ...); int openat(int directory_fd, const char *path, int flags, ...); +int fcntl(int fd, int command, ...); #endif diff --git a/examples/typephp-os/user/include/sys/syscall.h b/examples/typephp-os/user/include/sys/syscall.h index efeb701f..5945304b 100644 --- a/examples/typephp-os/user/include/sys/syscall.h +++ b/examples/typephp-os/user/include/sys/syscall.h @@ -18,6 +18,7 @@ #define SYS_getpid TYPEPHP_SYS_GETPID #define SYS_exit TYPEPHP_SYS_EXIT #define SYS_uname TYPEPHP_SYS_UNAME +#define SYS_fcntl TYPEPHP_SYS_FCNTL #define SYS_fsync TYPEPHP_SYS_FSYNC #define SYS_fdatasync TYPEPHP_SYS_FDATASYNC #define SYS_truncate TYPEPHP_SYS_TRUNCATE @@ -35,6 +36,7 @@ #define SYS_getppid TYPEPHP_SYS_GETPPID #define SYS_gettid TYPEPHP_SYS_GETTID #define SYS_time TYPEPHP_SYS_TIME +#define SYS_getdents64 TYPEPHP_SYS_GETDENTS64 #define SYS_clock_gettime TYPEPHP_SYS_CLOCK_GETTIME #define SYS_clock_getres TYPEPHP_SYS_CLOCK_GETRES #define SYS_exit_group TYPEPHP_SYS_EXIT_GROUP @@ -45,7 +47,6 @@ /* Private bootstrap services; these deliberately do not claim Linux syscall * numbers or semantics. */ #define SYS_typephp_spawn TYPEPHP_SYS_SPAWN -#define SYS_typephp_listdir TYPEPHP_SYS_LISTDIR #define SYS_typephp_rename TYPEPHP_SYS_RENAME long syscall(long number, ...); diff --git a/examples/typephp-os/user/ls.c b/examples/typephp-os/user/ls.c index 7c2ba1fc..7163f8b5 100644 --- a/examples/typephp-os/user/ls.c +++ b/examples/typephp-os/user/ls.c @@ -1,16 +1,27 @@ -#include +#include +#include #include int main(int argc, char **argv) { - char entries[512]; - const char *path = argc > 1 ? argv[1] : 0; - long length = syscall(SYS_typephp_listdir, path, entries, sizeof(entries)); - if (length < 0) { + const char *path = argc > 1 ? argv[1] : "."; + DIR *directory = opendir(path); + struct dirent *entry; + if (directory == NULL) { (void) write(STDERR_FILENO, "ls: cannot read directory\n", sizeof("ls: cannot read directory\n") - 1); return 1; } - (void) write(STDOUT_FILENO, entries, (size_t) length); + while ((entry = readdir(directory)) != NULL) { + size_t length = 0; + while (entry->d_name[length] != '\0') { + ++length; + } + (void) write(STDOUT_FILENO, entry->d_name, length); + (void) write(STDOUT_FILENO, "\n", 1); + } + if (closedir(directory) != 0) { + return 1; + } return 0; } diff --git a/examples/typephp-os/user/nano/hello.php b/examples/typephp-os/user/nano/hello.php index 9e9a5ed8..947c1166 100644 --- a/examples/typephp-os/user/nano/hello.php +++ b/examples/typephp-os/user/nano/hello.php @@ -12,4 +12,5 @@ function main(int $argc, array $argv): void var_dump(php_uname()); var_dump(file_exists('/HELLO.TXT')); var_dump(filesize('/HELLO.TXT') > 0); + var_dump(scandir('/') !== false); } diff --git a/examples/typephp-os/user/nano/hello.yml b/examples/typephp-os/user/nano/hello.yml index 2721e355..d4f26af4 100644 --- a/examples/typephp-os/user/nano/hello.yml +++ b/examples/typephp-os/user/nano/hello.yml @@ -56,3 +56,4 @@ link-paths: - ../../build link-libs: - typephp-os + - compiler-rt-builtins diff --git a/examples/typephp-os/user/runtime/README.md b/examples/typephp-os/user/runtime/README.md index f43f13ec..947e0c7c 100644 --- a/examples/typephp-os/user/runtime/README.md +++ b/examples/typephp-os/user/runtime/README.md @@ -25,7 +25,7 @@ link-libs: ``` The archive owns the userspace `_start`, PHP Nano host entry, syscall bridge, -POSIX/libc and C++ ABI shims, and math implementation. Application projects +POSIX/libc and C++ ABI shims, buffered `dirent` implementation, and math implementation. Application projects 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. diff --git a/examples/typephp-os/user/runtime/posix.c b/examples/typephp-os/user/runtime/posix.c index cf4260f8..d69e9195 100644 --- a/examples/typephp-os/user/runtime/posix.c +++ b/examples/typephp-os/user/runtime/posix.c @@ -1,5 +1,4 @@ #include -#include #include #include #include @@ -17,18 +16,6 @@ long syscall(long number, ...); extern char **environ; -static __attribute__((noreturn)) void unsupported(const char *name) -{ - static const char prefix[] = "unsupported TypePHP-OS user ABI: "; - (void) syscall(TYPEPHP_SYS_WRITE, STDERR_FILENO, prefix, sizeof(prefix) - 1); - (void) syscall(TYPEPHP_SYS_WRITE, STDERR_FILENO, name, strlen(name)); - (void) syscall(TYPEPHP_SYS_WRITE, STDERR_FILENO, "\n", 1); - (void) syscall(TYPEPHP_SYS_EXIT, 126); - for (;;) { - __asm__ volatile("pause"); - } -} - ssize_t read(int fd, void *buffer, size_t count) { return (ssize_t) syscall(TYPEPHP_SYS_READ, fd, buffer, count); @@ -196,30 +183,6 @@ int faccessat(int directory_fd, const char *path, int mode, int flags) TYPEPHP_SYS_FACCESSAT, directory_fd, path, mode, flags); } -DIR *opendir(const char *path) -{ - (void) path; - unsupported("opendir"); -} - -struct dirent *readdir(DIR *directory) -{ - (void) directory; - unsupported("readdir"); -} - -int closedir(DIR *directory) -{ - (void) directory; - unsupported("closedir"); -} - -void rewinddir(DIR *directory) -{ - (void) directory; - unsupported("rewinddir"); -} - int fsync(int fd) { return (int) syscall(TYPEPHP_SYS_FSYNC, fd); diff --git a/examples/typephp-os/user/sh.c b/examples/typephp-os/user/sh.c index f08a871b..fc550008 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, 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); + "Commands: ls, cd, pwd, date, uname, cat, echo, write, touch, mkdir, rm, rmdir, mv, memtest, systest, builtins, fault, vmfault, wrfault\n", + sizeof("Commands: ls, cd, pwd, date, uname, cat, echo, write, touch, mkdir, rm, rmdir, mv, memtest, systest, builtins, 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 index f62d5a43..9b4cb418 100644 --- a/examples/typephp-os/user/systest.c +++ b/examples/typephp-os/user/systest.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -19,6 +20,15 @@ static int fail(const char *operation) return 1; } +static int same_string(const char *left, const char *right) +{ + while (*left != '\0' && *left == *right) { + ++left; + ++right; + } + return *left == *right; +} + int main(int argc, char **argv) { static const char path[] = "/SYS.TMP"; @@ -28,6 +38,9 @@ int main(int argc, char **argv) struct timespec realtime; struct timespec monotonic; struct timespec resolution; + DIR *directory; + struct dirent *entry; + int found_hello = 0; int fd; (void) argc; (void) argv; @@ -43,8 +56,20 @@ int main(int argc, char **argv) (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 + if (fd < 0 + || write(fd, payload, sizeof(payload) - 1) != sizeof(payload) - 1) { + return fail("descriptor write"); + } + if ((fcntl(fd, F_GETFL) & 3) != O_RDWR + || fcntl(fd, F_SETFD, FD_CLOEXEC) != 0 + || fcntl(fd, F_GETFD) != FD_CLOEXEC + || fcntl(fd, F_SETFL, O_APPEND) != 0 + || lseek(fd, 0, SEEK_SET) != 0 + || write(fd, "Z", 1) != 1 + || fstat(fd, &info) != 0 || info.st_size != 7) { + return fail("fcntl/append"); + } + if (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"); @@ -54,6 +79,23 @@ int main(int argc, char **argv) return fail("path truncate"); } + directory = opendir("/"); + if (directory == NULL || fstat(dirfd(directory), &info) != 0 + || !S_ISDIR(info.st_mode)) { + return fail("opendir/fstat"); + } + while ((entry = readdir(directory)) != NULL) { + if (same_string(entry->d_name, "HELLO.TXT")) { + found_hello = 1; + } + } + rewinddir(directory); + entry = readdir(directory); + if (!found_hello || entry == NULL || !same_string(entry->d_name, ".") + || closedir(directory) != 0) { + return fail("getdents64/rewinddir"); + } + if (getpid() != 2 || gettid() != 2 || getppid() != 1 || getuid() != 0 || geteuid() != 0 || getgid() != 0 || getegid() != 0) { return fail("single-task identity");