From 453543e0c89e78297e60b89e1540eab15a06717a Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Mon, 14 Sep 2026 10:40:41 +0800 Subject: [PATCH] Add network features for typephp os and support curl+openssl --- examples/typephp-os/.gitignore | 3 + examples/typephp-os/Makefile | 43 +- examples/typephp-os/README.md | 29 +- examples/typephp-os/THIRDPARTY.md | 35 + examples/typephp-os/boot/kernel64-entry.S | 4 + examples/typephp-os/composer.json | 17 + examples/typephp-os/kernel/core/abi/libc.c | 179 ++++- .../kernel/core/abi/typephp_os_syscall.h | 13 + .../kernel/core/abi/unimplemented.S | 3 + examples/typephp-os/kernel/core/net/arch/cc.h | 23 + examples/typephp-os/kernel/core/net/e1000.c | 326 +++++++++ examples/typephp-os/kernel/core/net/e1000.h | 12 + .../typephp-os/kernel/core/net/lwipopts.h | 70 ++ examples/typephp-os/kernel/core/net/network.c | 209 ++++++ examples/typephp-os/kernel/core/net/network.h | 12 + examples/typephp-os/kernel/core/process.c | 183 +++++ examples/typephp-os/kernel/core/socket.c | 648 ++++++++++++++++++ examples/typephp-os/kernel/core/socket.h | 37 + examples/typephp-os/kernel/core/vm.c | 42 ++ examples/typephp-os/kernel/core/vm.h | 1 + examples/typephp-os/project.yml | 30 +- examples/typephp-os/run.sh | 42 ++ .../tools/build-network-thirdparty.sh | 135 ++++ examples/typephp-os/tools/fetch-curl.sh | 30 + examples/typephp-os/tools/fetch-lwip.sh | 32 + examples/typephp-os/tools/fetch-nghttp2.sh | 31 + examples/typephp-os/tools/fetch-openssl.sh | 31 + examples/typephp-os/tools/fetch-thirdparty.sh | 4 + examples/typephp-os/tools/test-qemu.sh | 37 +- examples/typephp-os/user/cmd/nettest.c | 93 +++ examples/typephp-os/user/cmd/sh.c | 4 +- examples/typephp-os/user/cmd/systest.c | 14 + examples/typephp-os/user/include/arpa/inet.h | 16 + examples/typephp-os/user/include/errno.h | 27 + examples/typephp-os/user/include/netdb.h | 40 ++ examples/typephp-os/user/include/netinet/in.h | 50 ++ examples/typephp-os/user/include/poll.h | 23 + examples/typephp-os/user/include/string.h | 11 + examples/typephp-os/user/include/sys/random.h | 14 + examples/typephp-os/user/include/sys/socket.h | 66 ++ .../typephp-os/user/include/sys/syscall.h | 13 + examples/typephp-os/user/nano/free.yml | 5 +- examples/typephp-os/user/nano/hello.yml | 5 +- examples/typephp-os/user/nano/https.php | 36 + examples/typephp-os/user/nano/https.yml | 92 +++ examples/typephp-os/user/runtime/crt0.S | 4 +- examples/typephp-os/user/runtime/glibc.c | 351 ++++++++++ examples/typephp-os/user/runtime/host.cc | 48 +- examples/typephp-os/user/runtime/network.c | 379 ++++++++++ examples/typephp-os/user/runtime/posix.c | 24 + examples/typephp-os/user/runtime/start.c | 29 + examples/typephp-os/user/runtime/stdio.c | 207 ++++++ phpunit/src/CompilationStatisticsTest.php | 18 +- src/Build/NanoExtensionSelector.php | 13 +- src/Build/NanoSourceComposer.php | 26 +- src/CompilerBase.php | 1 - 56 files changed, 3821 insertions(+), 49 deletions(-) create mode 100644 examples/typephp-os/composer.json create mode 100644 examples/typephp-os/kernel/core/net/arch/cc.h create mode 100644 examples/typephp-os/kernel/core/net/e1000.c create mode 100644 examples/typephp-os/kernel/core/net/e1000.h create mode 100644 examples/typephp-os/kernel/core/net/lwipopts.h create mode 100644 examples/typephp-os/kernel/core/net/network.c create mode 100644 examples/typephp-os/kernel/core/net/network.h create mode 100644 examples/typephp-os/kernel/core/socket.c create mode 100644 examples/typephp-os/kernel/core/socket.h create mode 100755 examples/typephp-os/run.sh create mode 100755 examples/typephp-os/tools/build-network-thirdparty.sh create mode 100755 examples/typephp-os/tools/fetch-curl.sh create mode 100755 examples/typephp-os/tools/fetch-lwip.sh create mode 100755 examples/typephp-os/tools/fetch-nghttp2.sh create mode 100755 examples/typephp-os/tools/fetch-openssl.sh create mode 100644 examples/typephp-os/user/cmd/nettest.c create mode 100644 examples/typephp-os/user/include/arpa/inet.h create mode 100644 examples/typephp-os/user/include/netdb.h create mode 100644 examples/typephp-os/user/include/netinet/in.h create mode 100644 examples/typephp-os/user/include/poll.h create mode 100644 examples/typephp-os/user/include/sys/random.h create mode 100644 examples/typephp-os/user/include/sys/socket.h create mode 100644 examples/typephp-os/user/nano/https.php create mode 100644 examples/typephp-os/user/nano/https.yml create mode 100644 examples/typephp-os/user/runtime/glibc.c create mode 100644 examples/typephp-os/user/runtime/network.c create mode 100644 examples/typephp-os/user/runtime/start.c create mode 100644 examples/typephp-os/user/runtime/stdio.c diff --git a/examples/typephp-os/.gitignore b/examples/typephp-os/.gitignore index f6f12142..1e5c49c2 100644 --- a/examples/typephp-os/.gitignore +++ b/examples/typephp-os/.gitignore @@ -1,3 +1,6 @@ /build/ *.o /thirdparty/ +/vendor +/.idea +/composer.lock \ No newline at end of file diff --git a/examples/typephp-os/Makefile b/examples/typephp-os/Makefile index eab5b2ac..e4d078cd 100644 --- a/examples/typephp-os/Makefile +++ b/examples/typephp-os/Makefile @@ -7,12 +7,14 @@ 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 builtins +USER_COMMANDS := ls cd date uname pwd cat echo write touch mkdir rm rmdir mv fault vmfault wrfault memtest systest nettest builtins USER_PROGRAMS := sh $(USER_COMMANDS) USER_SH_ELF := $(BUILD)/sh.elf USER_COMMAND_ELFS := $(addprefix $(BUILD)/,$(addsuffix .elf,$(USER_COMMANDS))) NANO_USER_ELF := $(BUILD)/tnhello.elf TYPEPHP_FREE_ELF := $(BUILD)/free.elf +TYPEPHP_HTTPS_ELF := $(BUILD)/https.elf +CA_BUNDLE ?= /etc/ssl/certs/ca-certificates.crt TYPEPHP_OS_LIBRARY := $(BUILD)/libtypephp-os.a TYPEPHP_OS_LIBRARY_OBJECT_DIR := $(BUILD)/libtypephp-os COMPILER_RT_LIBRARY := $(BUILD)/libcompiler-rt-builtins.a @@ -23,7 +25,9 @@ OPENLIBM_SOURCES := $(shell sed -n 's|^\(.*\.c\)$$|thirdparty/openlibm/\1|p' too 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/runtime/dirent.c \ - user/runtime/fcntl.c user/runtime/ioctl.c kernel/core/abi/error.c kernel/core/abi/string.c \ + user/runtime/fcntl.c user/runtime/ioctl.c user/runtime/network.c \ + user/runtime/glibc.c user/runtime/stdio.c user/runtime/start.c \ + kernel/core/abi/error.c kernel/core/abi/string.c \ user/runtime/memory.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 \ @@ -39,6 +43,9 @@ NANO_USER_SOURCES := user/nano/hello.php user/nano/hello.yml user/runtime/linker TYPEPHP_FREE_SOURCES := user/nano/free.php user/nano/memory.stub.php \ user/nano/free.cc user/nano/free.yml user/runtime/linker.ld \ $(TYPEPHP_OS_LIBRARY) $(COMPILER_RT_LIBRARY) +TYPEPHP_HTTPS_SOURCES := user/nano/https.php user/nano/https.yml \ + user/runtime/linker.ld $(TYPEPHP_OS_LIBRARY) $(COMPILER_RT_LIBRARY) +TPC := ./vendor/bin/tpc.php BOOTFLAGS := -m32 -O2 -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 -ffreestanding -fno-pic -fno-pie \ -fno-stack-protector -Wall -Wextra @@ -84,7 +91,7 @@ define STRIP_ELF objcopy --add-gnu-debuglink=$(1).debug $(1) endef -.PHONY: all thirdparty thirdparty-smoke compiler-rt-builtins payload objects user-runtime nano-user disk run test clean +.PHONY: all thirdparty thirdparty-smoke network-thirdparty-smoke compiler-rt-builtins payload objects user-runtime nano-user disk run test clean all: $(KERNEL) $(DISK) @@ -94,10 +101,13 @@ thirdparty: thirdparty-smoke: thirdparty compiler-rt-builtins ./tools/test-thirdparty.sh +network-thirdparty-smoke: + ./tools/build-network-thirdparty.sh + compiler-rt-builtins: $(COMPILER_RT_LIBRARY) payload: thirdparty objects $(COMPILER_RT_LIBRARY) - cd ../.. && ./bin/tpc.php --nano examples/typephp-os/project.yml + $(TPC) --nano project.yml $(call STRIP_ELF,$(PAYLOAD_ELF)) # Startup sources are built explicitly by Make. The 64-bit entry object is @@ -107,11 +117,11 @@ objects: $(BOOTSTRAP_OBJECT) $(KERNEL64_ENTRY_OBJECT) user-runtime: $(TYPEPHP_OS_LIBRARY) -nano-user: $(TYPEPHP_OS_LIBRARY) $(NANO_USER_ELF) $(TYPEPHP_FREE_ELF) +nano-user: $(TYPEPHP_OS_LIBRARY) $(NANO_USER_ELF) $(TYPEPHP_FREE_ELF) $(TYPEPHP_HTTPS_ELF) disk: $(DISK) -$(DISK): $(USER_SH_ELF) $(USER_COMMAND_ELFS) $(NANO_USER_ELF) $(TYPEPHP_FREE_ELF) | $(BUILD) +$(DISK): $(USER_SH_ELF) $(USER_COMMAND_ELFS) $(NANO_USER_ELF) $(TYPEPHP_FREE_ELF) $(TYPEPHP_HTTPS_ELF) | $(BUILD) truncate -s 32M $@ mkfs.fat -F 16 -n TYPEPHPOS $@ mmd -i $@ ::/BIN @@ -120,6 +130,8 @@ $(DISK): $(USER_SH_ELF) $(USER_COMMAND_ELFS) $(NANO_USER_ELF) $(TYPEPHP_FREE_ELF done mcopy -o -i $@ $(NANO_USER_ELF) ::/BIN/TNHELLO.ELF mcopy -o -i $@ $(TYPEPHP_FREE_ELF) ::/BIN/FREE.ELF + mcopy -o -i $@ $(TYPEPHP_HTTPS_ELF) ::/BIN/HTTPS.ELF + mcopy -o -i $@ $(CA_BUNDLE) ::/CACERT.PEM $(BUILD): mkdir -p $@ @@ -151,6 +163,10 @@ $(TYPEPHP_OS_LIBRARY_OBJECT_DIR)/%.o: %.c Makefile @mkdir -p $(@D) gcc $(TYPEPHP_OS_RUNTIME_CFLAGS) -MMD -MP -MF $@.d -c $< -o $@ +$(TYPEPHP_OS_LIBRARY_OBJECT_DIR)/user/runtime/network.o: user/runtime/network.c Makefile $(USER_HEADERS) + @mkdir -p $(@D) + gcc -Iuser/include $(TYPEPHP_OS_RUNTIME_CFLAGS) -MMD -MP -MF $@.d -c $< -o $@ + $(TYPEPHP_OS_LIBRARY_OBJECT_DIR)/%.o: %.cc Makefile @mkdir -p $(@D) g++ $(TYPEPHP_OS_RUNTIME_CXXFLAGS) -MMD -MP -MF $@.d -c $< -o $@ @@ -167,11 +183,15 @@ $(TYPEPHP_OS_LIBRARY): $(TYPEPHP_OS_LIBRARY_OBJECTS) | $(BUILD) $(AR) $(ARFLAGS) $@ $^ $(NANO_USER_ELF): $(NANO_USER_SOURCES) | thirdparty $(BUILD) - cd ../.. && ./bin/tpc.php --nano examples/typephp-os/user/nano/hello.yml + $(TPC) --nano user/nano/hello.yml $(call STRIP_ELF,$@) $(TYPEPHP_FREE_ELF): $(TYPEPHP_FREE_SOURCES) | thirdparty $(BUILD) - cd ../.. && ./bin/tpc.php --nano examples/typephp-os/user/nano/free.yml + $(TPC) --nano user/nano/free.yml + $(call STRIP_ELF,$@) + +$(TYPEPHP_HTTPS_ELF): $(TYPEPHP_HTTPS_SOURCES) | network-thirdparty-smoke thirdparty $(BUILD) + $(TPC) --nano user/nano/https.yml $(call STRIP_ELF,$@) $(USER_SH_ELF): $(OBJECT_DIR)/sh.o $(TYPEPHP_OS_LIBRARY) $(COMPILER_RT_LIBRARY) user/runtime/linker.ld Makefile | $(BUILD) @@ -194,11 +214,12 @@ $(KERNEL): payload $(call STRIP_ELF,$@) run: all - qemu-system-x86_64 -m 512M -kernel $(KERNEL) \ + qemu-system-x86_64 -cpu max -m 512M -kernel $(KERNEL) \ -drive file=$(DISK),format=raw,if=ide,index=0 \ + -netdev user,id=net0 -device e1000,netdev=net0 \ -display none -serial stdio -monitor none -no-reboot -no-shutdown -test: $(KERNEL) $(USER_SH_ELF) $(USER_COMMAND_ELFS) $(NANO_USER_ELF) $(TYPEPHP_FREE_ELF) +test: $(KERNEL) $(USER_SH_ELF) $(USER_COMMAND_ELFS) $(NANO_USER_ELF) $(TYPEPHP_FREE_ELF) $(TYPEPHP_HTTPS_ELF) truncate -s 32M $(DISK) mkfs.fat -F 16 -n TYPEPHPOS $(DISK) mmd -i $(DISK) ::/BIN @@ -207,6 +228,8 @@ test: $(KERNEL) $(USER_SH_ELF) $(USER_COMMAND_ELFS) $(NANO_USER_ELF) $(TYPEPHP_F done mcopy -o -i $(DISK) $(NANO_USER_ELF) ::/BIN/TNHELLO.ELF mcopy -o -i $(DISK) $(TYPEPHP_FREE_ELF) ::/BIN/FREE.ELF + mcopy -o -i $(DISK) $(TYPEPHP_HTTPS_ELF) ::/BIN/HTTPS.ELF + mcopy -o -i $(DISK) $(CA_BUNDLE) ::/CACERT.PEM # An alias not known while linking the kernel verifies disk-based discovery. mcopy -o -i $(DISK) $(BUILD)/echo.elf ::/BIN/hello.elf mcopy -o -i $(DISK) tools/invalid-elf.txt ::/BIN/bad.elf diff --git a/examples/typephp-os/README.md b/examples/typephp-os/README.md index bc7e8871..0c31b220 100644 --- a/examples/typephp-os/README.md +++ b/examples/typephp-os/README.md @@ -40,7 +40,9 @@ project owns the freestanding boundary under `kernel/core/abi`: implemented C/POSIX and C++ ABI functions live there, while APIs required for linking but not implemented by the kernel are exported as panic stubs. Consequently an unsupported operation fails immediately with its ABI symbol instead of -silently returning fabricated data. Sockets are outside the current scope. +silently returning fabricated data. Networking is split deliberately: the +kernel owns the Ethernet and TCP/IP data path, while HTTP and TLS libraries +remain ordinary userspace dependencies. On a fresh checkout, run `examples/typephp-os/tools/fetch-thirdparty.sh` before invoking tpc directly; `make payload` performs both steps automatically. @@ -106,6 +108,10 @@ To build and audit the restricted hosted Toybox applet set, run `make thirdparty-smoke`. This is an integration audit, not part of the boot image. +Run `make network-thirdparty-smoke` to build and audit the pinned static +OpenSSL/nghttp2/curl stack. Its curl protocol list must contain only HTTP and +HTTPS, and its feature list must contain HTTP2. + The Makefile compiles the startup sources under `boot/` directly. Its 256 KiB early kernel stack is kept separate from the adjacent identity-map tables. The 32-bit Multiboot bootstrap cannot participate in the 64-bit payload link; the 64-bit @@ -131,6 +137,7 @@ To invoke QEMU manually: qemu-system-x86_64 -m 512M \ -kernel examples/typephp-os/build/typephp-os.elf \ -drive file=examples/typephp-os/build/typephp-os.img,format=raw,if=ide,index=0 \ + -netdev user,id=net0 -device e1000,netdev=net0 \ -display none -serial stdio -monitor none -no-reboot -no-shutdown ``` @@ -181,6 +188,10 @@ The QEMU smoke test currently verifies: `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`; +- a PCI/MMIO Intel 82540EM driver, lwIP raw IPv4/TCP data path, and + Linux-numbered socket/connect/send/recv/shutdown/name/options/poll ABI; +- a Ring-3 TCP/HTTP smoke command that reaches a host service through QEMU's + NAT network without placing an HTTP implementation in the kernel; - 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`, @@ -208,8 +219,11 @@ performs its own runtime initialization. Rename currently stays within one parent directory; cross-directory rename, replacement semantics, long filenames, timestamps, permissions, and a general block-device layer remain future work. -Network sockets, dynamic module loading, `include`/`require`/`eval`, and PHP -APIs that execute host commands remain unavailable. +DNS name resolution, TLS entropy integration, IPv6, UDP userspace sockets, +listening/server sockets, dynamic module loading, `include`/`require`/`eval`, +and PHP APIs that execute host commands remain unavailable. The first network +milestone provides outbound IPv4 TCP; OpenSSL/nghttp2/curl are not yet linked +into a TypePHP-OS user ELF. ## Single-task userspace @@ -227,7 +241,10 @@ The native x86-64 `SYSCALL` boundary provides synchronous `read`, `write`, `clos queries, `time`, `gettimeofday`, `clock_gettime`, `clock_getres`, `nanosleep`, `brk`, anonymous private `mmap`, `mprotect`, `munmap`, `getdents64`, the initial `fcntl` flag operations, fixed-console `ioctl(TIOCGWINSZ)`, and private -spawn and same-directory rename operations. The former private directory-list +spawn and same-directory rename operations. The network subset adds +`socket`, `connect`, `sendto`, `recvfrom`, `shutdown`, socket-name and option +queries, and `poll`; all are Linux-numbered userspace calls backed by the +kernel's lwIP raw TCP control blocks. 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`. @@ -308,6 +325,7 @@ rm NOTE.TXT rmdir TMP mv OLD.TXT NEW.TXT systest +nettest cd DOCS pwd ls @@ -320,7 +338,8 @@ 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. `systest` covers file metadata, persistence/truncation, fixed -single-task identities, and wall/monotonic clock ABIs. User exceptions for +single-task identities, wall/monotonic clock ABIs, and socket descriptor +flags. `nettest` performs a real TCP HTTP request to the QEMU host. 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 diff --git a/examples/typephp-os/THIRDPARTY.md b/examples/typephp-os/THIRDPARTY.md index bbb9098a..0c2dc7d8 100644 --- a/examples/typephp-os/THIRDPARTY.md +++ b/examples/typephp-os/THIRDPARTY.md @@ -26,11 +26,46 @@ Run the upstream integration probes separately: ```shell make thirdparty-smoke +make network-thirdparty-smoke ``` This target builds a deliberately small hosted Toybox and audits its undefined ABI. It is not installed into the TypePHP-OS disk image by this probe. +The network probe builds a static, protocol-minimal dependency stack under +`build/network-thirdparty/install`. It verifies that libcurl exposes exactly +`HTTP` and `HTTPS`, with HTTP/2 supplied by nghttp2. This hosted build locks +down the upstream feature selection while the same sources are being ported to +the freestanding TypePHP-OS userspace ABI. + +## lwIP + +- Upstream: +- Version: `STABLE-2_2_1_RELEASE` +- Archive SHA-256: `ce0b7461c0ad9602c376f0bf07c5eb7253b48c7bf66f011c6bf3e2a96731c539` + +The complete release tree is fetched, but `project.yml` selects only the raw +IPv4/TCP, ARP, ICMP, UDP/DNS, timeout, and Ethernet sources used by the kernel. +The lwIP socket-compatibility and netconn layers are not compiled. + +## HTTPS and HTTP/2 userspace stack + +- OpenSSL `3.5.8` LTS, archive SHA-256 + `a8f84a39918ec6415ce765d9b429d313ba97b8143169c172e734b9514464f5b2` +- nghttp2 `1.70.0`, archive SHA-256 + `e05cb1388eaca3830aded4ccf20044b6e1ac1a61411dcca11b0437c4285c8bc2` +- curl `8.22.0`, archive SHA-256 + `f7ef3ae8a22e521f289803fe93543eb64c329b58aa73a9e224dfd915a2a5f4f7` + +All three are static userspace libraries. OpenSSL keeps TLS 1.2/1.3, X.509, +and the modern algorithms needed for public HTTPS servers, while omitting its +apps, tests, dynamic modules, legacy provider, QUIC/DTLS, and old TLS protocol +versions. nghttp2 builds only its C library. Only libcurl is built; the curl +command-line tool is excluded. libcurl enables only HTTP/HTTPS and +HTTP/2; FTP, FILE, IPFS, mail, LDAP, SMB, MQTT, WebSocket, proxy, compression, +IDN, PSL, and other optional dependencies are disabled. Redirect handling, +cookies, MIME, headers, and ordinary HTTP authentication remain available. + ## OpenLibm - Upstream: diff --git a/examples/typephp-os/boot/kernel64-entry.S b/examples/typephp-os/boot/kernel64-entry.S index 2977db18..a5e5abfb 100644 --- a/examples/typephp-os/boot/kernel64-entry.S +++ b/examples/typephp-os/boot/kernel64-entry.S @@ -20,7 +20,11 @@ kernel64_entry: mov %r12, %rdi mov %r13, %rsi + # The Multiboot bootstrap calls this entry point, so RSP is 8 mod 16 here. + # Align it before calling C code as required by the x86-64 System V ABI. + sub $8, %rsp call kernel_entry + add $8, %rsp halt64: cli diff --git a/examples/typephp-os/composer.json b/examples/typephp-os/composer.json new file mode 100644 index 00000000..d135926b --- /dev/null +++ b/examples/typephp-os/composer.json @@ -0,0 +1,17 @@ +{ + "name": "swoole/typephp-os", + "description": "TypePHP-OS experimental system image", + "type": "project", + "license": "BSD-3-Clause", + "minimum-stability": "dev", + "prefer-stable": true, + "repositories": [ + {"type": "path", "url": "../..", "options": {"symlink": true}}, + {"type": "path", "url": "../../../phpx", "options": {"symlink": true}}, + {"type": "path", "url": "../../../php-nano", "options": {"symlink": true}} + ], + "require": { + "swoole/typephp": "dev-master", + "swoole/php-nano": "dev-master" + } +} diff --git a/examples/typephp-os/kernel/core/abi/libc.c b/examples/typephp-os/kernel/core/abi/libc.c index 9fb52bf1..7707a07d 100644 --- a/examples/typephp-os/kernel/core/abi/libc.c +++ b/examples/typephp-os/kernel/core/abi/libc.c @@ -130,7 +130,7 @@ __attribute__((weak, noreturn)) void typephp_os_panic(const char *message) } } -int php_nano_host_random_bytes(void *bytes, size_t size) +__attribute__((weak)) int php_nano_host_random_bytes(void *bytes, size_t size) { static uint64_t state = UINT64_C(0x9e3779b97f4a7c15); unsigned char *output = (unsigned char *) bytes; @@ -143,7 +143,7 @@ int php_nano_host_random_bytes(void *bytes, size_t size) return 0; } -uint64_t php_nano_host_random_seed(void) +__attribute__((weak)) uint64_t php_nano_host_random_seed(void) { uint64_t seed = 0; (void) php_nano_host_random_bytes(&seed, sizeof(seed)); @@ -443,6 +443,57 @@ char *strcat(char *destination, const char *source) return result; } +char *strcpy(char *destination, const char *source) +{ + char *result = destination; + while ((*destination++ = *source++) != '\0') { + } + return result; +} + +char *strncpy(char *destination, const char *source, size_t size) +{ + char *result = destination; + while (size != 0 && *source != '\0') { + *destination++ = *source++; + --size; + } + while (size-- != 0) { + *destination++ = '\0'; + } + return result; +} + +size_t strspn(const char *string, const char *accept) +{ + const char *cursor = string; + while (*cursor != '\0' && strchr(accept, *cursor) != 0) { + ++cursor; + } + return (size_t) (cursor - string); +} + +size_t strcspn(const char *string, const char *reject) +{ + const char *cursor = string; + while (*cursor != '\0' && strchr(reject, *cursor) == 0) { + ++cursor; + } + return (size_t) (cursor - string); +} + +void *memrchr(const void *memory, int character, size_t size) +{ + const unsigned char *cursor = (const unsigned char *) memory + size; + const unsigned char value = (unsigned char) character; + while (size-- != 0) { + if (*--cursor == value) { + return (void *) cursor; + } + } + return 0; +} + char *strpbrk(const char *string, const char *characters) { for (; *string != '\0'; ++string) { @@ -495,6 +546,11 @@ unsigned long long strtoull(const char *string, char **end, int base) return value; } +unsigned long strtoul(const char *string, char **end, int base) +{ + return (unsigned long) strtoull(string, end, base); +} + long long strtoll(const char *string, char **end, int base) { while (isspace((unsigned char) *string)) { @@ -633,7 +689,115 @@ long long llabs(long long value) return value < 0 ? -value : value; } -int ap_php_vsnprintf(char *buffer, size_t size, const char *format, va_list args); +static void format_byte(char *buffer, size_t size, size_t *length, char byte) +{ + if (*length + 1 < size) { + buffer[*length] = byte; + } + ++*length; +} + +static void format_unsigned(char *buffer, size_t size, size_t *length, + unsigned long long value, unsigned int base) +{ + static const char digits[] = "0123456789abcdef"; + char reversed[32]; + size_t count = 0; + do { + reversed[count++] = digits[value % base]; + value /= base; + } while (value != 0); + while (count != 0) { + format_byte(buffer, size, length, reversed[--count]); + } +} + +/* php-nano's main/snprintf.c provides the strong, fully compatible version. + * Plain C user programs only need this small freestanding fallback. */ +__attribute__((weak)) int ap_php_vsnprintf( + char *buffer, size_t size, const char *format, va_list args) +{ + size_t length = 0; + while (*format != '\0') { + int long_count = 0; + int size_value = 0; + if (*format != '%') { + format_byte(buffer, size, &length, *format++); + continue; + } + ++format; + if (*format == '%') { + format_byte(buffer, size, &length, *format++); + continue; + } + while (*format == '-' || *format == '+' || *format == ' ' + || *format == '#' || *format == '0' + || (*format >= '1' && *format <= '9') || *format == '.') { + ++format; + } + while (*format == 'l') { + ++long_count; + ++format; + } + if (*format == 'z') { + size_value = 1; + ++format; + } + switch (*format++) { + case 's': { + const char *string = va_arg(args, const char *); + if (string == 0) { + string = "(null)"; + } + while (*string != '\0') { + format_byte(buffer, size, &length, *string++); + } + break; + } + case 'c': + format_byte(buffer, size, &length, (char) va_arg(args, int)); + break; + case 'd': + case 'i': { + long long value = long_count >= 2 ? va_arg(args, long long) + : (long_count == 1 ? va_arg(args, long) : va_arg(args, int)); + if (value < 0) { + format_byte(buffer, size, &length, '-'); + format_unsigned(buffer, size, &length, + (unsigned long long) (-(value + 1)) + 1, 10); + } else { + format_unsigned(buffer, size, &length, + (unsigned long long) value, 10); + } + break; + } + case 'u': + case 'x': { + const char conversion = format[-1]; + unsigned long long value = size_value ? va_arg(args, size_t) + : (long_count >= 2 ? va_arg(args, unsigned long long) + : (long_count == 1 ? va_arg(args, unsigned long) + : va_arg(args, unsigned int))); + format_unsigned(buffer, size, &length, value, + conversion == 'x' ? 16 : 10); + break; + } + case 'p': + format_byte(buffer, size, &length, '0'); + format_byte(buffer, size, &length, 'x'); + format_unsigned(buffer, size, &length, + (uintptr_t) va_arg(args, void *), 16); + break; + default: + format_byte(buffer, size, &length, '?'); + break; + } + } + if (size != 0) { + buffer[length < size ? length : size - 1] = '\0'; + } + return length > INT32_MAX ? INT32_MAX : (int) length; +} int vsnprintf(char *buffer, size_t size, const char *format, va_list args) { @@ -788,6 +952,15 @@ void *realloc(void *pointer, size_t size) return replacement; } +void *reallocarray(void *pointer, size_t count, size_t size) +{ + if (count != 0 && size > SIZE_MAX / count) { + errno = ENOMEM; + return 0; + } + return realloc(pointer, count * size); +} + int posix_memalign(void **result, size_t alignment, size_t size) { size_t index; 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 56665f42..fe06b464 100644 --- a/examples/typephp-os/kernel/core/abi/typephp_os_syscall.h +++ b/examples/typephp-os/kernel/core/abi/typephp_os_syscall.h @@ -11,6 +11,7 @@ enum typephp_os_syscall_number { TYPEPHP_SYS_STAT = 4, TYPEPHP_SYS_FSTAT = 5, TYPEPHP_SYS_LSTAT = 6, + TYPEPHP_SYS_POLL = 7, TYPEPHP_SYS_LSEEK = 8, TYPEPHP_SYS_MMAP = 9, TYPEPHP_SYS_MPROTECT = 10, @@ -19,6 +20,16 @@ enum typephp_os_syscall_number { TYPEPHP_SYS_IOCTL = 16, TYPEPHP_SYS_ACCESS = 21, TYPEPHP_SYS_NANOSLEEP = 35, + TYPEPHP_SYS_SOCKET = 41, + TYPEPHP_SYS_CONNECT = 42, + TYPEPHP_SYS_SENDTO = 44, + TYPEPHP_SYS_RECVFROM = 45, + TYPEPHP_SYS_SHUTDOWN = 48, + TYPEPHP_SYS_BIND = 49, + TYPEPHP_SYS_GETSOCKNAME = 51, + TYPEPHP_SYS_GETPEERNAME = 52, + TYPEPHP_SYS_SETSOCKOPT = 54, + TYPEPHP_SYS_GETSOCKOPT = 55, TYPEPHP_SYS_GETPID = 39, TYPEPHP_SYS_EXIT = 60, TYPEPHP_SYS_UNAME = 63, @@ -47,9 +58,11 @@ enum typephp_os_syscall_number { TYPEPHP_SYS_OPENAT = 257, TYPEPHP_SYS_NEWFSTATAT = 262, TYPEPHP_SYS_FACCESSAT = 269, + TYPEPHP_SYS_GETRANDOM = 318, TYPEPHP_SYS_SPAWN = 0x54500001, TYPEPHP_SYS_RENAME = 0x54500003, TYPEPHP_SYS_MEMORY_INFO = 0x54500004, + TYPEPHP_SYS_DNS_RESOLVE_IPV4 = 0x54500005, }; #endif diff --git a/examples/typephp-os/kernel/core/abi/unimplemented.S b/examples/typephp-os/kernel/core/abi/unimplemented.S index bea46a3e..5da2d54a 100644 --- a/examples/typephp-os/kernel/core/abi/unimplemented.S +++ b/examples/typephp-os/kernel/core/abi/unimplemented.S @@ -7,6 +7,7 @@ * any accidental runtime use fails loudly instead of returning fake data. */ .macro TYPEPHP_OS_UNIMPLEMENTED symbol .global \symbol + .weak \symbol .type \symbol, @function \symbol: lea .Lmessage\@(%rip), %rdi @@ -19,6 +20,8 @@ TYPEPHP_OS_UNIMPLEMENTED chmod TYPEPHP_OS_UNIMPLEMENTED chown +TYPEPHP_OS_UNIMPLEMENTED accept +TYPEPHP_OS_UNIMPLEMENTED listen TYPEPHP_OS_UNIMPLEMENTED fdopen TYPEPHP_OS_UNIMPLEMENTED feof TYPEPHP_OS_UNIMPLEMENTED fileno diff --git a/examples/typephp-os/kernel/core/net/arch/cc.h b/examples/typephp-os/kernel/core/net/arch/cc.h new file mode 100644 index 00000000..158f7b75 --- /dev/null +++ b/examples/typephp-os/kernel/core/net/arch/cc.h @@ -0,0 +1,23 @@ +#ifndef TYPEPHP_OS_LWIP_ARCH_CC_H +#define TYPEPHP_OS_LWIP_ARCH_CC_H + +#include +#include + +typedef long ssize_t; +#ifndef SSIZE_MAX +#define SSIZE_MAX LONG_MAX +#endif + +#define BYTE_ORDER LITTLE_ENDIAN +#define LWIP_NO_UNISTD_H 1 +#define LWIP_NO_CTYPE_H 1 +#define LWIP_PLATFORM_DIAG(message) do { } while (0) + +void typephp_network_assert(const char *message); +uint32_t typephp_network_random_u32(void); + +#define LWIP_PLATFORM_ASSERT(message) typephp_network_assert(message) +#define LWIP_RAND() typephp_network_random_u32() + +#endif diff --git a/examples/typephp-os/kernel/core/net/e1000.c b/examples/typephp-os/kernel/core/net/e1000.c new file mode 100644 index 00000000..2f23e9a4 --- /dev/null +++ b/examples/typephp-os/kernel/core/net/e1000.c @@ -0,0 +1,326 @@ +/* Intel 82540EM (QEMU e1000) driver using its indirect I/O BAR. */ + +#include "e1000.h" +#include "../vm.h" + +#include +#include + +enum { + E1000_VENDOR = 0x8086, + E1000_DEVICE = 0x100e, + PCI_ADDRESS = 0xcf8, + PCI_DATA = 0xcfc, + PCI_COMMAND = 0x04, + PCI_BAR_BEGIN = 0x10, + PCI_BAR_END = 0x28, + PCI_COMMAND_MEMORY = 2, + PCI_COMMAND_BUS_MASTER = 4, + REG_CTRL = 0x0000, + REG_STATUS = 0x0008, + REG_EERD = 0x0014, + REG_ICR = 0x00c0, + REG_IMC = 0x00d8, + REG_RCTL = 0x0100, + REG_TCTL = 0x0400, + REG_TIPG = 0x0410, + REG_RDBAL = 0x2800, + REG_RDBAH = 0x2804, + REG_RDLEN = 0x2808, + REG_RDH = 0x2810, + REG_RDT = 0x2818, + REG_TDBAL = 0x3800, + REG_TDBAH = 0x3804, + REG_TDLEN = 0x3808, + REG_TDH = 0x3810, + REG_TDT = 0x3818, + REG_RAL = 0x5400, + REG_RAH = 0x5404, + CTRL_RST = 1u << 26, + CTRL_SLU = 1u << 6, + EERD_START = 1, + EERD_DONE = 1u << 4, + RCTL_EN = 1u << 1, + RCTL_BAM = 1u << 15, + RCTL_SECRC = 1u << 26, + TCTL_EN = 1u << 1, + TCTL_PSP = 1u << 3, + RX_DESCRIPTOR_DONE = 1, + TX_DESCRIPTOR_DONE = 1, + TX_COMMAND_EOP = 1, + TX_COMMAND_IFCS = 1u << 1, + TX_COMMAND_RS = 1u << 3, + DESCRIPTOR_COUNT = 256, + BUFFER_SIZE = 2048, +}; + +typedef struct __attribute__((packed)) { + uint64_t address; + uint16_t length; + uint16_t checksum; + uint8_t status; + uint8_t errors; + uint16_t special; +} rx_descriptor; + +typedef struct __attribute__((packed)) { + uint64_t address; + uint16_t length; + uint8_t checksum_offset; + uint8_t command; + uint8_t status; + uint8_t checksum_start; + uint16_t special; +} tx_descriptor; + +static rx_descriptor rx_descriptors[DESCRIPTOR_COUNT] + __attribute__((aligned(4096))); +static tx_descriptor tx_descriptors[DESCRIPTOR_COUNT] + __attribute__((aligned(4096))); +static uint8_t rx_buffers[DESCRIPTOR_COUNT][BUFFER_SIZE] + __attribute__((aligned(4096))); +static uint8_t tx_buffers[DESCRIPTOR_COUNT][BUFFER_SIZE] + __attribute__((aligned(4096))); +static uint8_t mac_address[6]; +static volatile uint8_t *mmio_base; +static uint16_t rx_index; +static uint16_t tx_index; +static int initialized; + +static inline void outl(uint16_t port, uint32_t value) +{ + __asm__ volatile("outl %0, %1" : : "a"(value), "Nd"(port)); +} + +static inline uint32_t inl(uint16_t port) +{ + uint32_t value; + __asm__ volatile("inl %1, %0" : "=a"(value) : "Nd"(port)); + return value; +} + +static uint32_t pci_read32( + uint8_t bus, uint8_t device, uint8_t function, uint8_t offset) +{ + const uint32_t address = UINT32_C(0x80000000) + | ((uint32_t) bus << 16u) | ((uint32_t) device << 11u) + | ((uint32_t) function << 8u) | (offset & 0xfcu); + outl(PCI_ADDRESS, address); + return inl(PCI_DATA); +} + +static void pci_write32(uint8_t bus, uint8_t device, uint8_t function, + uint8_t offset, uint32_t value) +{ + const uint32_t address = UINT32_C(0x80000000) + | ((uint32_t) bus << 16u) | ((uint32_t) device << 11u) + | ((uint32_t) function << 8u) | (offset & 0xfcu); + outl(PCI_ADDRESS, address); + outl(PCI_DATA, value); +} + +static uint32_t register_read(uint32_t offset) +{ + return *(volatile uint32_t *) (mmio_base + offset); +} + +static void register_write(uint32_t offset, uint32_t value) +{ + *(volatile uint32_t *) (mmio_base + offset) = value; +} + +static int read_eeprom_word(uint8_t address, uint16_t *value) +{ + register_write(REG_EERD, ((uint32_t) address << 8u) | EERD_START); + for (unsigned int attempt = 0; attempt < 1000000u; ++attempt) { + const uint32_t result = register_read(REG_EERD); + if ((result & EERD_DONE) != 0) { + *value = (uint16_t) (result >> 16u); + return 1; + } + __asm__ volatile("pause"); + } + return 0; +} + +static int find_device(uint8_t *found_bus, uint8_t *found_device, + uint8_t *found_function) +{ + for (uint16_t bus = 0; bus < 256; ++bus) { + for (uint8_t device = 0; device < 32; ++device) { + for (uint8_t function = 0; function < 8; ++function) { + const uint32_t identity = pci_read32( + (uint8_t) bus, device, function, 0); + if ((identity & 0xffffu) == E1000_VENDOR + && (identity >> 16u) == E1000_DEVICE) { + *found_bus = (uint8_t) bus; + *found_device = device; + *found_function = function; + return 1; + } + if (function == 0 + && (pci_read32((uint8_t) bus, device, 0, 0x0c) + & UINT32_C(0x00800000)) == 0) { + break; + } + } + } + } + return 0; +} + +int typephp_e1000_init(void) +{ + uint8_t bus; + uint8_t device; + uint8_t function; + if (!find_device(&bus, &device, &function)) { + return 0; + } + uint64_t memory_base = 0; + for (uint8_t offset = PCI_BAR_BEGIN; offset < PCI_BAR_END; offset += 4) { + const uint32_t bar = pci_read32(bus, device, function, offset); + if (bar != 0 && bar != UINT32_MAX && (bar & 1u) == 0) { + memory_base = bar & UINT32_C(0xfffffff0); + if ((bar & 6u) == 4u && offset + 4 < PCI_BAR_END) { + memory_base |= (uint64_t) pci_read32( + bus, device, function, (uint8_t) (offset + 4)) << 32u; + } + break; + } + } + if (memory_base == 0) { + return 0; + } + mmio_base = (volatile uint8_t *) typephp_vm_map_mmio( + memory_base, UINT64_C(128) * 1024u); + if (mmio_base == 0) { + return 0; + } + uint32_t command = pci_read32(bus, device, function, PCI_COMMAND); + pci_write32(bus, device, function, PCI_COMMAND, + command | PCI_COMMAND_MEMORY | PCI_COMMAND_BUS_MASTER); + + register_write(REG_IMC, UINT32_MAX); + register_write(REG_CTRL, register_read(REG_CTRL) | CTRL_RST); + for (volatile unsigned int delay = 0; delay < 1000000u; ++delay) { + __asm__ volatile("pause"); + } + register_write(REG_IMC, UINT32_MAX); + (void) register_read(REG_ICR); + register_write(REG_CTRL, register_read(REG_CTRL) | CTRL_SLU); + + uint32_t ral = register_read(REG_RAL); + uint32_t rah = register_read(REG_RAH); + if ((rah & UINT32_C(0x80000000)) != 0) { + mac_address[0] = (uint8_t) ral; + mac_address[1] = (uint8_t) (ral >> 8u); + mac_address[2] = (uint8_t) (ral >> 16u); + mac_address[3] = (uint8_t) (ral >> 24u); + mac_address[4] = (uint8_t) rah; + mac_address[5] = (uint8_t) (rah >> 8u); + } else { + uint16_t word[3]; + if (!read_eeprom_word(0, &word[0]) + || !read_eeprom_word(1, &word[1]) + || !read_eeprom_word(2, &word[2])) { + return 0; + } + for (unsigned int index = 0; index < 3; ++index) { + mac_address[index * 2] = (uint8_t) word[index]; + mac_address[index * 2 + 1] = (uint8_t) (word[index] >> 8u); + } + ral = (uint32_t) mac_address[0] + | ((uint32_t) mac_address[1] << 8u) + | ((uint32_t) mac_address[2] << 16u) + | ((uint32_t) mac_address[3] << 24u); + rah = (uint32_t) mac_address[4] + | ((uint32_t) mac_address[5] << 8u) + | UINT32_C(0x80000000); + register_write(REG_RAL, ral); + register_write(REG_RAH, rah); + } + + memset(rx_descriptors, 0, sizeof(rx_descriptors)); + memset(tx_descriptors, 0, sizeof(tx_descriptors)); + for (unsigned int index = 0; index < DESCRIPTOR_COUNT; ++index) { + rx_descriptors[index].address = + (uint64_t) (uintptr_t) rx_buffers[index]; + tx_descriptors[index].address = + (uint64_t) (uintptr_t) tx_buffers[index]; + tx_descriptors[index].status = TX_DESCRIPTOR_DONE; + } + rx_index = 0; + tx_index = 0; + + const uint64_t rx_address = (uint64_t) (uintptr_t) rx_descriptors; + register_write(REG_RDBAL, (uint32_t) rx_address); + register_write(REG_RDBAH, (uint32_t) (rx_address >> 32u)); + register_write(REG_RDLEN, sizeof(rx_descriptors)); + register_write(REG_RDH, 0); + register_write(REG_RDT, DESCRIPTOR_COUNT - 1u); + register_write(REG_RCTL, RCTL_EN | RCTL_BAM | RCTL_SECRC); + + const uint64_t tx_address = (uint64_t) (uintptr_t) tx_descriptors; + register_write(REG_TDBAL, (uint32_t) tx_address); + register_write(REG_TDBAH, (uint32_t) (tx_address >> 32u)); + register_write(REG_TDLEN, sizeof(tx_descriptors)); + register_write(REG_TDH, 0); + register_write(REG_TDT, 0); + register_write(REG_TCTL, + TCTL_EN | TCTL_PSP | (0x10u << 4u) | (0x40u << 12u)); + register_write(REG_TIPG, UINT32_C(0x0060200a)); + initialized = (register_read(REG_STATUS) & 2u) != 0; + return initialized; +} + +const uint8_t *typephp_e1000_mac(void) +{ + return mac_address; +} + +int typephp_e1000_transmit(const void *data, size_t length) +{ + if (!initialized || data == 0 || length == 0 || length > BUFFER_SIZE) { + return 0; + } + tx_descriptor *descriptor = &tx_descriptors[tx_index]; + unsigned int attempts = 0; + while ((descriptor->status & TX_DESCRIPTOR_DONE) == 0) { + if (++attempts == 10000000u) { + return 0; + } + __asm__ volatile("pause"); + } + memcpy(tx_buffers[tx_index], data, length); + descriptor->length = (uint16_t) length; + descriptor->command = TX_COMMAND_EOP | TX_COMMAND_IFCS | TX_COMMAND_RS; + descriptor->status = 0; + __asm__ volatile("mfence" : : : "memory"); + tx_index = (uint16_t) ((tx_index + 1u) % DESCRIPTOR_COUNT); + register_write(REG_TDT, tx_index); + return 1; +} + +int typephp_e1000_receive(void *data, size_t capacity, size_t *length) +{ + rx_descriptor *descriptor; + if (!initialized || data == 0 || length == 0) { + return 0; + } + descriptor = &rx_descriptors[rx_index]; + if ((descriptor->status & RX_DESCRIPTOR_DONE) == 0) { + return 0; + } + *length = descriptor->length; + if (descriptor->errors != 0 || *length == 0 || *length > capacity) { + *length = 0; + } else { + memcpy(data, rx_buffers[rx_index], *length); + } + descriptor->status = 0; + __asm__ volatile("mfence" : : : "memory"); + register_write(REG_RDT, rx_index); + rx_index = (uint16_t) ((rx_index + 1u) % DESCRIPTOR_COUNT); + return *length != 0; +} diff --git a/examples/typephp-os/kernel/core/net/e1000.h b/examples/typephp-os/kernel/core/net/e1000.h new file mode 100644 index 00000000..ec269544 --- /dev/null +++ b/examples/typephp-os/kernel/core/net/e1000.h @@ -0,0 +1,12 @@ +#ifndef TYPEPHP_OS_E1000_H +#define TYPEPHP_OS_E1000_H + +#include +#include + +int typephp_e1000_init(void); +const uint8_t *typephp_e1000_mac(void); +int typephp_e1000_transmit(const void *data, size_t length); +int typephp_e1000_receive(void *data, size_t capacity, size_t *length); + +#endif diff --git a/examples/typephp-os/kernel/core/net/lwipopts.h b/examples/typephp-os/kernel/core/net/lwipopts.h new file mode 100644 index 00000000..8b4bbb3a --- /dev/null +++ b/examples/typephp-os/kernel/core/net/lwipopts.h @@ -0,0 +1,70 @@ +#ifndef TYPEPHP_OS_LWIPOPTS_H +#define TYPEPHP_OS_LWIPOPTS_H + +/* The network stack targets a desktop-class x86-64 machine. These values are + * deliberately sized for throughput and concurrent libcurl handles rather + * than for a microcontroller RAM budget. */ +#define NO_SYS 1 +#define SYS_LIGHTWEIGHT_PROT 0 +#define LWIP_NETCONN 0 +#define LWIP_SOCKET 0 + +#define LWIP_IPV4 1 +#define LWIP_IPV6 0 +#define LWIP_ARP 1 +#define LWIP_ETHERNET 1 +#define LWIP_ICMP 1 +#define LWIP_RAW 0 +#define LWIP_UDP 1 +#define LWIP_TCP 1 +#define LWIP_DNS 1 +#define LWIP_DHCP 0 +#define LWIP_AUTOIP 0 +#define IP_FORWARD 0 +#define IP_REASSEMBLY 1 +#define IP_FRAG 1 + +#define MEM_ALIGNMENT 16 +#define MEM_SIZE (16 * 1024 * 1024) +#define MEMP_NUM_PBUF 2048 +#define MEMP_NUM_UDP_PCB 64 +#define MEMP_NUM_TCP_PCB 256 +#define MEMP_NUM_TCP_PCB_LISTEN 0 +#define MEMP_NUM_TCP_SEG 4096 +#define MEMP_NUM_SYS_TIMEOUT 128 +#define PBUF_POOL_SIZE 1024 +#define PBUF_POOL_BUFSIZE 2048 + +#define TCP_MSS 1460 +#define TCP_SND_BUF (1024 * 1024) +#define TCP_SND_QUEUELEN (4 * TCP_SND_BUF / TCP_MSS) +#define TCP_SNDLOWAT (65535 - 4 * TCP_MSS - 1) +#define TCP_WND (1024 * 1024) +#define LWIP_WND_SCALE 1 +#define TCP_RCV_SCALE 5 +#define TCP_QUEUE_OOSEQ 1 +#define TCP_OVERSIZE TCP_MSS +#define TCP_LISTEN_BACKLOG 0 + +#define ARP_TABLE_SIZE 256 +#define ARP_QUEUEING 1 +#define DNS_TABLE_SIZE 32 +#define DNS_MAX_NAME_LENGTH 256 +#define DNS_MAX_SERVERS 2 +#define LWIP_DNS_SECURE 7 +#define LWIP_RANDOMIZE_INITIAL_LOCAL_PORTS 1 + +#define LWIP_CHECKSUM_CTRL_PER_NETIF 0 +#define CHECKSUM_GEN_IP 1 +#define CHECKSUM_GEN_UDP 1 +#define CHECKSUM_GEN_TCP 1 +#define CHECKSUM_CHECK_IP 1 +#define CHECKSUM_CHECK_UDP 1 +#define CHECKSUM_CHECK_TCP 1 + +#define LWIP_STATS 0 +#define LWIP_DEBUG 0 +#define LWIP_TIMERS 1 +#define LWIP_TIMERS_CUSTOM 0 + +#endif diff --git a/examples/typephp-os/kernel/core/net/network.c b/examples/typephp-os/kernel/core/net/network.c new file mode 100644 index 00000000..8ad46346 --- /dev/null +++ b/examples/typephp-os/kernel/core/net/network.c @@ -0,0 +1,209 @@ +#include "network.h" +#include "e1000.h" + +#include + +#include "lwip/dns.h" +#include "lwip/etharp.h" +#include "lwip/init.h" +#include "lwip/netif.h" +#include "lwip/pbuf.h" +#include "lwip/timeouts.h" +#include "netif/ethernet.h" + +#include + +extern void typephp_os_panic(const char *message); +extern uint64_t typephp_os_timer_milliseconds(void); + +static struct netif primary_netif; +static int ready; +static volatile unsigned long dns_generation; +static volatile int dns_result; +static uint32_t dns_address; + +enum { + DNS_PENDING = 0, + DNS_RESOLVED = 1, + DNS_FAILED = 2, + DNS_ATTEMPTS = 3, + DNS_ATTEMPT_TIMEOUT_MS = 5000, +}; + +void typephp_network_assert(const char *message) +{ + (void) message; + typephp_os_panic("lwIP assertion failed\n"); +} + +uint32_t typephp_network_random_u32(void) +{ + uint32_t low; + uint32_t high; + __asm__ volatile("rdtsc" : "=a"(low), "=d"(high)); + low ^= high + (uint32_t) typephp_network_milliseconds(); + low ^= low << 13u; + low ^= low >> 17u; + low ^= low << 5u; + return low; +} + +uint64_t typephp_network_milliseconds(void) +{ + return typephp_os_timer_milliseconds(); +} + +uint32_t sys_now(void) +{ + return (uint32_t) typephp_network_milliseconds(); +} + +static err_t link_output(struct netif *netif, struct pbuf *packet) +{ + uint8_t frame[2048]; + (void) netif; + if (packet->tot_len > sizeof(frame) + || pbuf_copy_partial(packet, frame, packet->tot_len, 0) + != packet->tot_len + || !typephp_e1000_transmit(frame, packet->tot_len)) { + return ERR_IF; + } + return ERR_OK; +} + +static err_t interface_init(struct netif *netif) +{ + const uint8_t *mac = typephp_e1000_mac(); + netif->name[0] = 'e'; + netif->name[1] = '0'; + netif->output = etharp_output; + netif->linkoutput = link_output; + netif->mtu = 1500; + netif->hwaddr_len = 6; + for (unsigned int index = 0; index < 6; ++index) { + netif->hwaddr[index] = mac[index]; + } + netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP + | NETIF_FLAG_ETHERNET | NETIF_FLAG_LINK_UP; + return ERR_OK; +} + +int typephp_network_init(void) +{ + ip4_addr_t address; + ip4_addr_t netmask; + ip4_addr_t gateway; + ip_addr_t dns_server; + if (ready) { + return 1; + } + if (!typephp_e1000_init()) { + return 0; + } + lwip_init(); + IP4_ADDR(&address, 10, 0, 2, 15); + IP4_ADDR(&netmask, 255, 255, 255, 0); + IP4_ADDR(&gateway, 10, 0, 2, 2); + if (netif_add(&primary_netif, &address, &netmask, &gateway, + 0, interface_init, ethernet_input) == 0) { + return 0; + } + netif_set_default(&primary_netif); + netif_set_up(&primary_netif); + netif_set_link_up(&primary_netif); + IP_ADDR4(&dns_server, 10, 0, 2, 3); + dns_setserver(0, &dns_server); + ready = 1; + return 1; +} + +int typephp_network_ready(void) +{ + return ready; +} + +void typephp_network_poll(void) +{ + uint8_t frame[2048]; + size_t length; + if (!ready) { + return; + } + while (typephp_e1000_receive(frame, sizeof(frame), &length)) { + struct pbuf *packet = pbuf_alloc(PBUF_RAW, (u16_t) length, PBUF_POOL); + if (packet != 0) { + if (pbuf_take(packet, frame, length) == ERR_OK + && primary_netif.input(packet, &primary_netif) == ERR_OK) { + continue; + } + pbuf_free(packet); + } + } + sys_check_timeouts(); +} + +static void dns_callback(const char *hostname, const ip_addr_t *address, + void *argument) +{ + const unsigned long generation = (unsigned long) (uintptr_t) argument; + (void) hostname; + if (generation != dns_generation || dns_result != DNS_PENDING) { + return; + } + if (address == 0 || !IP_IS_V4(address)) { + dns_result = DNS_FAILED; + return; + } + dns_address = ip_2_ip4(address)->addr; + dns_result = DNS_RESOLVED; +} + +long typephp_network_resolve_ipv4(const char *hostname, uint32_t *address) +{ + ip_addr_t immediate; + int attempt; + long result = -EHOSTUNREACH; + if (!ready) { + return -ENETDOWN; + } + if (hostname == 0 || hostname[0] == '\0' || address == 0) { + return -EINVAL; + } + + /* QEMU user networking can reject the first DNS request while its + * virtual link is still settling. Keep that transient out of userspace; + * later lookups normally hit lwIP's cache. */ + for (attempt = 0; attempt < DNS_ATTEMPTS; ++attempt) { + const uint64_t started = typephp_network_milliseconds(); + const unsigned long generation = ++dns_generation; + const err_t error = dns_gethostbyname(hostname, &immediate, + dns_callback, (void *) (uintptr_t) generation); + dns_result = DNS_PENDING; + if (error == ERR_OK) { + if (!IP_IS_V4(&immediate)) { + return -EAFNOSUPPORT; + } + *address = ip_2_ip4(&immediate)->addr; + return 0; + } + if (error == ERR_INPROGRESS) { + while (dns_result == DNS_PENDING + && typephp_network_milliseconds() - started + < DNS_ATTEMPT_TIMEOUT_MS) { + typephp_network_poll(); + __asm__ volatile("sti; hlt; cli" : : : "memory"); + } + if (dns_result == DNS_RESOLVED) { + *address = dns_address; + return 0; + } + result = dns_result == DNS_FAILED + ? -EHOSTUNREACH : -ETIMEDOUT; + } else { + result = error == ERR_MEM ? -ENOMEM : -EHOSTUNREACH; + } + ++dns_generation; + typephp_network_poll(); + } + return result; +} diff --git a/examples/typephp-os/kernel/core/net/network.h b/examples/typephp-os/kernel/core/net/network.h new file mode 100644 index 00000000..2adb56d5 --- /dev/null +++ b/examples/typephp-os/kernel/core/net/network.h @@ -0,0 +1,12 @@ +#ifndef TYPEPHP_OS_NETWORK_H +#define TYPEPHP_OS_NETWORK_H + +#include + +int typephp_network_init(void); +int typephp_network_ready(void); +void typephp_network_poll(void); +uint64_t typephp_network_milliseconds(void); +long typephp_network_resolve_ipv4(const char *hostname, uint32_t *address); + +#endif diff --git a/examples/typephp-os/kernel/core/process.c b/examples/typephp-os/kernel/core/process.c index 8361cf73..d0ad974f 100644 --- a/examples/typephp-os/kernel/core/process.c +++ b/examples/typephp-os/kernel/core/process.c @@ -11,6 +11,8 @@ #include "typephp_os_memory.h" #include "typephp_os_syscall.h" #include "vm.h" +#include "socket.h" +#include "net/network.h" #include #include @@ -241,6 +243,11 @@ uint64_t typephp_os_syscall_user_rsp; static process_state foreground_process = {.pid = 1, .cwd = "/"}; static volatile uint64_t timer_ticks; +uint64_t typephp_os_timer_milliseconds(void) +{ + return timer_ticks * (1000u / TIMER_FREQUENCY); +} + enum { IA32_EFER = 0xc0000080u, IA32_STAR = 0xc0000081u, @@ -352,6 +359,61 @@ static size_t bounded_user_string(const char *string, size_t maximum) return length < maximum && user_buffer(string + length, 1) ? length : (size_t) -1; } +static int cpu_has_rdrand(void) +{ + uint32_t eax; + uint32_t ebx; + uint32_t ecx; + uint32_t edx; + __asm__ volatile("cpuid" + : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx) + : "a"(1u), "c"(0u)); + (void) eax; + (void) ebx; + (void) edx; + return (ecx & (1u << 30u)) != 0; +} + +static int cpu_random_u64(uint64_t *value) +{ + unsigned char valid; + int attempt; + for (attempt = 0; attempt < 16; ++attempt) { + __asm__ volatile("rdrand %0; setc %1" + : "=r"(*value), "=qm"(valid)); + if (valid) { + return 1; + } + __asm__ volatile("pause"); + } + return 0; +} + +static long syscall_getrandom(void *buffer, size_t size, unsigned int flags) +{ + unsigned char *output = (unsigned char *) buffer; + if ((flags & ~1u) != 0) { + return -EINVAL; + } + if (!user_buffer(buffer, size)) { + return -EFAULT; + } + if (!cpu_has_rdrand()) { + return -ENOSYS; + } + while (size != 0) { + uint64_t random; + const size_t chunk = size < sizeof(random) ? size : sizeof(random); + if (!cpu_random_u64(&random)) { + return -EAGAIN; + } + memcpy(output, &random, chunk); + output += chunk; + size -= chunk; + } + return (long) (output - (unsigned char *) buffer); +} + static void install_tss_descriptor(uint64_t base, uint32_t limit) { gdt[5] = ((uint64_t) (limit & 0xffffu)) @@ -1208,6 +1270,7 @@ static long syscall_exit(syscall_frame *frame, long status) typephp_os_panic("shell process exited\n"); } child_address_space = foreground_process.address_space; + typephp_socket_close_owner(foreground_process.pid); memcpy(frame, &foreground_process.parent_frame, sizeof(*frame)); foreground_process.address_space = foreground_process.parent_address_space; foreground_process.program_break = foreground_process.parent_program_break; @@ -1272,6 +1335,7 @@ static void restore_shell_after_fault(exception_frame *frame) uint64_t child_address_space = foreground_process.address_space; syscall_frame *parent = &foreground_process.parent_frame; /* The general-register prefixes of both frame formats are identical. */ + typephp_socket_close_owner(foreground_process.pid); memcpy(frame, parent, offsetof(syscall_frame, rip)); frame->rax = (uint64_t) -EIO; frame->rip = parent->rip; @@ -1352,6 +1416,10 @@ long typephp_os_syscall_dispatch(syscall_frame *frame) if (frame->rdi <= STDERR_FILENO) { return -EBADF; } + if (typephp_socket_is_fd((int) frame->rdi)) { + return typephp_socket_read( + (int) frame->rdi, (void *) frame->rsi, frame->rdx); + } return posix_syscall_result(read( (int) frame->rdi, (void *) frame->rsi, frame->rdx)); case TYPEPHP_SYS_WRITE: @@ -1365,9 +1433,16 @@ long typephp_os_syscall_dispatch(syscall_frame *frame) if (frame->rdi == STDIN_FILENO) { return -EBADF; } + if (typephp_socket_is_fd((int) frame->rdi)) { + return typephp_socket_write( + (int) frame->rdi, (const void *) frame->rsi, frame->rdx); + } return posix_syscall_result(write( (int) frame->rdi, (const void *) frame->rsi, frame->rdx)); case TYPEPHP_SYS_CLOSE: + if (typephp_socket_is_fd((int) frame->rdi)) { + return typephp_socket_close((int) frame->rdi); + } return posix_syscall_result(close((int) frame->rdi)); case TYPEPHP_SYS_STAT: return syscall_stat_path((const char *) frame->rdi, @@ -1402,8 +1477,91 @@ long typephp_os_syscall_dispatch(syscall_frame *frame) case TYPEPHP_SYS_GETTID: return (long) foreground_process.pid; case TYPEPHP_SYS_FCNTL: + if (typephp_socket_is_fd((int) frame->rdi)) { + return typephp_socket_fcntl( + (int) frame->rdi, (int) frame->rsi, (long) frame->rdx); + } return posix_syscall_result(fcntl( (int) frame->rdi, (int) frame->rsi, (int) frame->rdx)); + case TYPEPHP_SYS_SOCKET: + return typephp_socket_open(foreground_process.pid, + (int) frame->rdi, (int) frame->rsi, (int) frame->rdx); + case TYPEPHP_SYS_CONNECT: + if (!user_buffer((const void *) frame->rsi, frame->rdx)) { + return -EFAULT; + } + return typephp_socket_connect((int) frame->rdi, + (const void *) frame->rsi, (typephp_socklen_t) frame->rdx); + case TYPEPHP_SYS_BIND: + if (!user_buffer((const void *) frame->rsi, frame->rdx)) { + return -EFAULT; + } + return typephp_socket_bind((int) frame->rdi, + (const void *) frame->rsi, (typephp_socklen_t) frame->rdx); + case TYPEPHP_SYS_SENDTO: + if (!user_buffer((const void *) frame->rsi, frame->rdx) + || (frame->r8 != 0 + && !user_buffer((const void *) frame->r8, frame->r9))) { + return -EFAULT; + } + return typephp_socket_sendto((int) frame->rdi, + (const void *) frame->rsi, frame->rdx, (int) frame->r10, + (const void *) frame->r8, (typephp_socklen_t) frame->r9); + case TYPEPHP_SYS_RECVFROM: + if (!user_buffer((void *) frame->rsi, frame->rdx) + || (frame->r8 != 0 + && (!user_buffer((void *) frame->r8, sizeof(uint16_t)) + || frame->r9 == 0 + || !user_buffer((void *) frame->r9, + sizeof(typephp_socklen_t))))) { + return -EFAULT; + } + return typephp_socket_recvfrom((int) frame->rdi, + (void *) frame->rsi, frame->rdx, (int) frame->r10, + (void *) frame->r8, (typephp_socklen_t *) frame->r9); + case TYPEPHP_SYS_SHUTDOWN: + return typephp_socket_shutdown((int) frame->rdi, (int) frame->rsi); + case TYPEPHP_SYS_GETSOCKNAME: + case TYPEPHP_SYS_GETPEERNAME: + if (frame->rdx == 0 + || !user_buffer((void *) frame->rdx, sizeof(typephp_socklen_t))) { + return -EFAULT; + } + if (frame->rsi != 0 + && !user_buffer((void *) frame->rsi, + *(typephp_socklen_t *) frame->rdx)) { + return -EFAULT; + } + return typephp_socket_getname((int) frame->rdi, + (void *) frame->rsi, (typephp_socklen_t *) frame->rdx, + frame->rax == TYPEPHP_SYS_GETPEERNAME); + case TYPEPHP_SYS_SETSOCKOPT: + if (!user_buffer((const void *) frame->r10, frame->r8)) { + return -EFAULT; + } + return typephp_socket_setsockopt((int) frame->rdi, + (int) frame->rsi, (int) frame->rdx, + (const void *) frame->r10, (typephp_socklen_t) frame->r8); + case TYPEPHP_SYS_GETSOCKOPT: + if (frame->r8 == 0 + || !user_buffer((void *) frame->r8, sizeof(typephp_socklen_t))) { + return -EFAULT; + } + if (!user_buffer((void *) frame->r10, + *(typephp_socklen_t *) frame->r8)) { + return -EFAULT; + } + return typephp_socket_getsockopt((int) frame->rdi, + (int) frame->rsi, (int) frame->rdx, + (void *) frame->r10, (typephp_socklen_t *) frame->r8); + case TYPEPHP_SYS_POLL: + if (frame->rsi > 1024 + || !user_buffer((void *) frame->rdi, + frame->rsi * sizeof(typephp_pollfd))) { + return -EFAULT; + } + return typephp_socket_poll((typephp_pollfd *) frame->rdi, + frame->rsi, (int) frame->rdx); case TYPEPHP_SYS_SPAWN: return syscall_spawn(frame, (const char *const *) frame->rdi); case TYPEPHP_SYS_GETCWD: @@ -1491,6 +1649,24 @@ long typephp_os_syscall_dispatch(syscall_frame *frame) return syscall_access_path((int) frame->rdi, (const char *) frame->rsi, (int) frame->rdx, (int) frame->r10); + case TYPEPHP_SYS_GETRANDOM: + return syscall_getrandom((void *) frame->rdi, frame->rsi, + (unsigned int) frame->rdx); + case TYPEPHP_SYS_DNS_RESOLVE_IPV4: { + char hostname[256]; + size_t length; + if (!user_buffer((void *) frame->rsi, sizeof(uint32_t))) { + return -EFAULT; + } + length = bounded_user_string((const char *) frame->rdi, + sizeof(hostname)); + if (length == (size_t) -1) { + return -EFAULT; + } + memcpy(hostname, (const void *) frame->rdi, length + 1u); + return typephp_network_resolve_ipv4(hostname, + (uint32_t *) frame->rsi); + } case TYPEPHP_SYS_EXIT: case TYPEPHP_SYS_EXIT_GROUP: return syscall_exit(frame, (long) frame->rdi); @@ -1522,6 +1698,13 @@ void typephp_os_process_start(void) panic("unable to load /BIN/SH.ELF\n"); } install_descriptor_tables(); + if (typephp_network_init()) { + typephp_os_write("Network: e1000/lwIP IPv4 ready\n", + sizeof("Network: e1000/lwIP IPv4 ready\n") - 1); + } else { + typephp_os_write("Network: no supported adapter\n", + sizeof("Network: no supported adapter\n") - 1); + } foreground_process.running = 1; foreground_process.address_space = address_space; foreground_process.minimum_break = image_end; diff --git a/examples/typephp-os/kernel/core/socket.c b/examples/typephp-os/kernel/core/socket.c new file mode 100644 index 00000000..0fe2b65d --- /dev/null +++ b/examples/typephp-os/kernel/core/socket.c @@ -0,0 +1,648 @@ +/* + +----------------------------------------------------------------------+ + | TypePHP OS | + +----------------------------------------------------------------------+ + | Linux-compatible userspace sockets backed by kernel-owned lwIP TCP. | + | SPDX-License-Identifier: BSD-3-Clause | + +----------------------------------------------------------------------+ +*/ + +#include "socket.h" +#include "net/network.h" + +#include +#include +#include +#include + +#include "lwip/ip_addr.h" +#include "lwip/pbuf.h" +#include "lwip/tcp.h" + +enum { + SOCKET_FD_BEGIN = 128, + SOCKET_COUNT = 256, + LINUX_AF_INET = 2, + LINUX_SOCK_STREAM = 1, + LINUX_SOCK_TYPE_MASK = 15, + LINUX_SOCK_NONBLOCK = 04000, + LINUX_SOCK_CLOEXEC = 02000000, + LINUX_IPPROTO_TCP = 6, + LINUX_SOL_SOCKET = 1, + LINUX_SO_ERROR = 4, + LINUX_SO_KEEPALIVE = 9, + LINUX_SO_RCVBUF = 8, + LINUX_SO_SNDBUF = 7, + LINUX_TCP_NODELAY = 1, + LINUX_F_GETFD = 1, + LINUX_F_SETFD = 2, + LINUX_F_GETFL = 3, + LINUX_F_SETFL = 4, + LINUX_FD_CLOEXEC = 1, + LINUX_O_RDWR = 2, + LINUX_O_NONBLOCK = 04000, + LINUX_MSG_PEEK = 2, + LINUX_MSG_DONTWAIT = 0x40, + LINUX_MSG_NOSIGNAL = 0x4000, + LINUX_MSG_MORE = 0x8000, + LINUX_POLLIN = 0x001, + LINUX_POLLOUT = 0x004, + LINUX_POLLERR = 0x008, + LINUX_POLLHUP = 0x010, + LINUX_POLLNVAL = 0x020, + SOCKET_NEW = 0, + SOCKET_CONNECTING = 1, + SOCKET_CONNECTED = 2, + SOCKET_PEER_CLOSED = 3, + SOCKET_FAILED = 4, +}; + +typedef struct __attribute__((packed)) { + uint16_t family; + uint16_t port; + uint32_t address; + uint8_t zero[8]; +} linux_sockaddr_in; + +typedef struct { + int allocated; + int domain; + int type; + int protocol; + int status_flags; + int descriptor_flags; + int pending_error; + int state; + unsigned long owner; + struct tcp_pcb *pcb; + struct pbuf *received; +} kernel_socket; + +static kernel_socket sockets[SOCKET_COUNT]; + +static kernel_socket *socket_for_fd(int fd) +{ + if (fd < SOCKET_FD_BEGIN || fd >= SOCKET_FD_BEGIN + SOCKET_COUNT) { + return 0; + } + kernel_socket *socket = &sockets[fd - SOCKET_FD_BEGIN]; + return socket->allocated ? socket : 0; +} + +static int network_errno(err_t error) +{ + switch (error) { + case ERR_OK: return 0; + case ERR_MEM: return ENOMEM; + case ERR_BUF: return ENOBUFS; + case ERR_TIMEOUT: return ETIMEDOUT; + case ERR_RTE: return ENETUNREACH; + case ERR_INPROGRESS: return EINPROGRESS; + case ERR_VAL: return EINVAL; + case ERR_WOULDBLOCK: return EAGAIN; + case ERR_USE: return EADDRINUSE; + case ERR_ALREADY: return EALREADY; + case ERR_ISCONN: return EISCONN; + case ERR_ABRT: return ECONNABORTED; + case ERR_RST: return ECONNRESET; + case ERR_CLSD: return ENOTCONN; + case ERR_CONN: return ENOTCONN; + case ERR_ARG: return EINVAL; + case ERR_IF: + default: + return ENETDOWN; + } +} + +static void wait_for_network_event(void) +{ + typephp_network_poll(); + __asm__ volatile("sti; hlt; cli" : : : "memory"); + typephp_network_poll(); +} + +static err_t connected_callback(void *argument, struct tcp_pcb *pcb, err_t error) +{ + kernel_socket *socket = (kernel_socket *) argument; + (void) pcb; + if (error == ERR_OK) { + socket->state = SOCKET_CONNECTED; + socket->pending_error = 0; + } else { + socket->state = SOCKET_FAILED; + socket->pending_error = network_errno(error); + } + return ERR_OK; +} + +static err_t received_callback(void *argument, struct tcp_pcb *pcb, + struct pbuf *packet, err_t error) +{ + kernel_socket *socket = (kernel_socket *) argument; + (void) pcb; + if (error != ERR_OK) { + if (packet != 0) { + pbuf_free(packet); + } + socket->pending_error = network_errno(error); + return ERR_OK; + } + if (packet == 0) { + socket->state = SOCKET_PEER_CLOSED; + return ERR_OK; + } + if (socket->received == 0) { + socket->received = packet; + } else { + pbuf_cat(socket->received, packet); + } + return ERR_OK; +} + +static void error_callback(void *argument, err_t error) +{ + kernel_socket *socket = (kernel_socket *) argument; + socket->pcb = 0; + socket->state = SOCKET_FAILED; + socket->pending_error = network_errno(error); +} + +static void install_callbacks(kernel_socket *socket) +{ + tcp_arg(socket->pcb, socket); + tcp_recv(socket->pcb, received_callback); + tcp_err(socket->pcb, error_callback); +} + +static void release_socket(kernel_socket *socket) +{ + if (socket->pcb != 0) { + tcp_arg(socket->pcb, 0); + tcp_recv(socket->pcb, 0); + tcp_err(socket->pcb, 0); + if (tcp_close(socket->pcb) != ERR_OK) { + tcp_abort(socket->pcb); + } + } + if (socket->received != 0) { + pbuf_free(socket->received); + } + memset(socket, 0, sizeof(*socket)); +} + +int typephp_socket_is_fd(int fd) +{ + return socket_for_fd(fd) != 0; +} + +long typephp_socket_open(unsigned long owner, int domain, int type, int protocol) +{ + const int base_type = type & LINUX_SOCK_TYPE_MASK; + const int flags = type & ~LINUX_SOCK_TYPE_MASK; + if (domain != LINUX_AF_INET) { + return -EAFNOSUPPORT; + } + if (base_type != LINUX_SOCK_STREAM) { + return -ESOCKTNOSUPPORT; + } + if ((flags & ~(LINUX_SOCK_NONBLOCK | LINUX_SOCK_CLOEXEC)) != 0) { + return -EINVAL; + } + if (protocol != 0 && protocol != LINUX_IPPROTO_TCP) { + return -EPROTONOSUPPORT; + } + if (!typephp_network_ready()) { + return -ENETDOWN; + } + for (int index = 0; index < SOCKET_COUNT; ++index) { + if (!sockets[index].allocated) { + struct tcp_pcb *pcb = tcp_new_ip_type(IPADDR_TYPE_V4); + if (pcb == 0) { + return -ENOBUFS; + } + kernel_socket *socket = &sockets[index]; + memset(socket, 0, sizeof(*socket)); + socket->allocated = 1; + socket->domain = domain; + socket->type = base_type; + socket->protocol = LINUX_IPPROTO_TCP; + socket->status_flags = LINUX_O_RDWR + | ((flags & LINUX_SOCK_NONBLOCK) != 0 ? LINUX_O_NONBLOCK : 0); + socket->descriptor_flags = + (flags & LINUX_SOCK_CLOEXEC) != 0 ? LINUX_FD_CLOEXEC : 0; + socket->owner = owner; + socket->state = SOCKET_NEW; + socket->pcb = pcb; + install_callbacks(socket); + return SOCKET_FD_BEGIN + index; + } + } + return -EMFILE; +} + +long typephp_socket_close(int fd) +{ + kernel_socket *socket = socket_for_fd(fd); + if (socket == 0) { + return -EBADF; + } + release_socket(socket); + return 0; +} + +long typephp_socket_read(int fd, void *buffer, size_t length) +{ + return typephp_socket_recvfrom(fd, buffer, length, 0, 0, 0); +} + +long typephp_socket_write(int fd, const void *buffer, size_t length) +{ + return typephp_socket_sendto(fd, buffer, length, 0, 0, 0); +} + +long typephp_socket_connect(int fd, const void *address, typephp_socklen_t length) +{ + kernel_socket *socket = socket_for_fd(fd); + const linux_sockaddr_in *remote = (const linux_sockaddr_in *) address; + ip_addr_t remote_address; + err_t error; + if (socket == 0) { + return -EBADF; + } + if (address == 0 || length < sizeof(*remote)) { + return -EINVAL; + } + if (remote->family != LINUX_AF_INET) { + return -EAFNOSUPPORT; + } + if (socket->state == SOCKET_CONNECTING) { + return -EALREADY; + } + if (socket->state == SOCKET_CONNECTED) { + return -EISCONN; + } + if (socket->state != SOCKET_NEW || socket->pcb == 0) { + return -ENOTCONN; + } + IP_ADDR4(&remote_address, + (uint8_t) remote->address, + (uint8_t) (remote->address >> 8u), + (uint8_t) (remote->address >> 16u), + (uint8_t) (remote->address >> 24u)); + socket->state = SOCKET_CONNECTING; + socket->pending_error = 0; + error = tcp_connect(socket->pcb, &remote_address, + lwip_ntohs(remote->port), connected_callback); + if (error != ERR_OK) { + socket->state = SOCKET_FAILED; + socket->pending_error = network_errno(error); + return -socket->pending_error; + } + if ((socket->status_flags & LINUX_O_NONBLOCK) != 0) { + return -EINPROGRESS; + } + while (socket->state == SOCKET_CONNECTING) { + wait_for_network_event(); + } + return socket->state == SOCKET_CONNECTED ? 0 + : -(socket->pending_error != 0 ? socket->pending_error : ECONNREFUSED); +} + +long typephp_socket_bind(int fd, const void *address, typephp_socklen_t length) +{ + kernel_socket *socket = socket_for_fd(fd); + const linux_sockaddr_in *local = (const linux_sockaddr_in *) address; + ip_addr_t local_address; + err_t error; + if (socket == 0) { + return -EBADF; + } + if (address == 0 || length < sizeof(*local)) { + return -EINVAL; + } + if (local->family != LINUX_AF_INET) { + return -EAFNOSUPPORT; + } + if (socket->state != SOCKET_NEW || socket->pcb == 0) { + return -EINVAL; + } + IP_ADDR4(&local_address, + (uint8_t) local->address, + (uint8_t) (local->address >> 8u), + (uint8_t) (local->address >> 16u), + (uint8_t) (local->address >> 24u)); + error = tcp_bind(socket->pcb, + local->address == 0 ? IP_ANY_TYPE : &local_address, + lwip_ntohs(local->port)); + return error == ERR_OK ? 0 : -network_errno(error); +} + +long typephp_socket_sendto(int fd, const void *buffer, size_t length, int flags, + const void *address, typephp_socklen_t address_length) +{ + kernel_socket *socket = socket_for_fd(fd); + const uint8_t *input = (const uint8_t *) buffer; + size_t written = 0; + (void) address; + (void) address_length; + if (socket == 0) { + return -EBADF; + } + if ((flags & ~(LINUX_MSG_DONTWAIT | LINUX_MSG_NOSIGNAL | LINUX_MSG_MORE)) != 0) { + return -EOPNOTSUPP; + } + if (buffer == 0 && length != 0) { + return -EFAULT; + } + if (socket->state != SOCKET_CONNECTED || socket->pcb == 0) { + return -ENOTCONN; + } + while (written < length) { + size_t chunk = length - written; + const size_t available = tcp_sndbuf(socket->pcb); + if (chunk > UINT16_MAX) { + chunk = UINT16_MAX; + } + if (chunk > available) { + chunk = available; + } + if (chunk == 0) { + if ((socket->status_flags & LINUX_O_NONBLOCK) != 0 + || (flags & LINUX_MSG_DONTWAIT) != 0) { + return written != 0 ? (long) written : -EAGAIN; + } + wait_for_network_event(); + if (socket->state != SOCKET_CONNECTED || socket->pcb == 0) { + return written != 0 ? (long) written : -ENOTCONN; + } + continue; + } + err_t error = tcp_write(socket->pcb, input + written, + (u16_t) chunk, TCP_WRITE_FLAG_COPY); + if (error == ERR_MEM) { + if ((socket->status_flags & LINUX_O_NONBLOCK) != 0 + || (flags & LINUX_MSG_DONTWAIT) != 0) { + return written != 0 ? (long) written : -EAGAIN; + } + wait_for_network_event(); + continue; + } + if (error != ERR_OK) { + return written != 0 ? (long) written : -network_errno(error); + } + written += chunk; + (void) tcp_output(socket->pcb); + typephp_network_poll(); + } + return (long) written; +} + +long typephp_socket_recvfrom(int fd, void *buffer, size_t length, int flags, + void *address, typephp_socklen_t *address_length) +{ + kernel_socket *socket = socket_for_fd(fd); + uint8_t *output = (uint8_t *) buffer; + size_t copied = 0; + (void) address; + (void) address_length; + if (socket == 0) { + return -EBADF; + } + if ((flags & ~(LINUX_MSG_PEEK | LINUX_MSG_DONTWAIT)) != 0) { + return -EOPNOTSUPP; + } + if (buffer == 0 && length != 0) { + return -EFAULT; + } + if (length == 0) { + return 0; + } + while (socket->received == 0) { + if (socket->state == SOCKET_PEER_CLOSED) { + return 0; + } + if (socket->state == SOCKET_FAILED) { + return -(socket->pending_error != 0 + ? socket->pending_error : ECONNRESET); + } + if (socket->state != SOCKET_CONNECTED || socket->pcb == 0) { + return -ENOTCONN; + } + if ((socket->status_flags & LINUX_O_NONBLOCK) != 0 + || (flags & LINUX_MSG_DONTWAIT) != 0) { + return -EAGAIN; + } + wait_for_network_event(); + } + copied = length < socket->received->tot_len + ? length : socket->received->tot_len; + if (pbuf_copy_partial(socket->received, output, copied, 0) != copied) { + return -EIO; + } + if ((flags & LINUX_MSG_PEEK) == 0) { + size_t consumed = copied; + while (consumed != 0) { + const u16_t part = consumed > UINT16_MAX + ? UINT16_MAX : (u16_t) consumed; + socket->received = pbuf_free_header(socket->received, part); + tcp_recved(socket->pcb, part); + consumed -= part; + } + } + return (long) copied; +} + +long typephp_socket_shutdown(int fd, int how) +{ + kernel_socket *socket = socket_for_fd(fd); + err_t error; + if (socket == 0) { + return -EBADF; + } + if (how < 0 || how > 2) { + return -EINVAL; + } + if (socket->pcb == 0 + || (socket->state != SOCKET_CONNECTED + && socket->state != SOCKET_PEER_CLOSED)) { + return -ENOTCONN; + } + error = tcp_shutdown(socket->pcb, how != 1, how != 0); + return error == ERR_OK ? 0 : -network_errno(error); +} + +long typephp_socket_getname(int fd, void *address, typephp_socklen_t *length, + int peer) +{ + kernel_socket *socket = socket_for_fd(fd); + linux_sockaddr_in result; + const ip_addr_t *ip; + uint16_t port; + if (socket == 0) { + return -EBADF; + } + if (socket->pcb == 0 || (peer && socket->state != SOCKET_CONNECTED)) { + return -ENOTCONN; + } + if (address == 0 || length == 0 || *length < sizeof(result)) { + return -EINVAL; + } + memset(&result, 0, sizeof(result)); + result.family = LINUX_AF_INET; + ip = peer ? &socket->pcb->remote_ip : &socket->pcb->local_ip; + port = peer ? socket->pcb->remote_port : socket->pcb->local_port; + result.address = ip_2_ip4(ip)->addr; + result.port = lwip_htons(port); + memcpy(address, &result, sizeof(result)); + *length = sizeof(result); + return 0; +} + +long typephp_socket_setsockopt(int fd, int level, int option, + const void *value, typephp_socklen_t length) +{ + kernel_socket *socket = socket_for_fd(fd); + int enabled; + if (socket == 0) { + return -EBADF; + } + if (value == 0 || length < sizeof(int) || socket->pcb == 0) { + return -EINVAL; + } + enabled = *(const int *) value; + if (level == LINUX_SOL_SOCKET && option == LINUX_SO_KEEPALIVE) { + if (enabled) { + ip_set_option(socket->pcb, SOF_KEEPALIVE); + } else { + ip_reset_option(socket->pcb, SOF_KEEPALIVE); + } + return 0; + } + if (level == LINUX_IPPROTO_TCP && option == LINUX_TCP_NODELAY) { + if (enabled) { + tcp_nagle_disable(socket->pcb); + } else { + tcp_nagle_enable(socket->pcb); + } + return 0; + } + return -ENOPROTOOPT; +} + +long typephp_socket_getsockopt(int fd, int level, int option, + void *value, typephp_socklen_t *length) +{ + kernel_socket *socket = socket_for_fd(fd); + int result; + if (socket == 0) { + return -EBADF; + } + if (value == 0 || length == 0 || *length < sizeof(int)) { + return -EINVAL; + } + if (level == LINUX_SOL_SOCKET && option == LINUX_SO_ERROR) { + result = socket->pending_error; + socket->pending_error = 0; + } else if (level == LINUX_SOL_SOCKET && option == LINUX_SO_KEEPALIVE) { + result = socket->pcb != 0 && ip_get_option(socket->pcb, SOF_KEEPALIVE); + } else if (level == LINUX_SOL_SOCKET && option == LINUX_SO_RCVBUF) { + result = TCP_WND; + } else if (level == LINUX_SOL_SOCKET && option == LINUX_SO_SNDBUF) { + result = TCP_SND_BUF; + } else if (level == LINUX_IPPROTO_TCP && option == LINUX_TCP_NODELAY) { + result = socket->pcb != 0 && tcp_nagle_disabled(socket->pcb); + } else { + return -ENOPROTOOPT; + } + *(int *) value = result; + *length = sizeof(int); + return 0; +} + +long typephp_socket_fcntl(int fd, int command, long argument) +{ + kernel_socket *socket = socket_for_fd(fd); + if (socket == 0) { + return -EBADF; + } + switch (command) { + case LINUX_F_GETFD: + return socket->descriptor_flags; + case LINUX_F_SETFD: + if ((argument & ~LINUX_FD_CLOEXEC) != 0) { + return -EINVAL; + } + socket->descriptor_flags = (int) argument; + return 0; + case LINUX_F_GETFL: + return socket->status_flags; + case LINUX_F_SETFL: + if ((argument & ~LINUX_O_NONBLOCK) != 0) { + return -EINVAL; + } + socket->status_flags = (socket->status_flags & ~LINUX_O_NONBLOCK) + | (int) argument; + return 0; + default: + return -EINVAL; + } +} + +static long poll_once(typephp_pollfd *fds, size_t count) +{ + long ready = 0; + for (size_t index = 0; index < count; ++index) { + kernel_socket *socket = socket_for_fd(fds[index].fd); + fds[index].revents = 0; + if (socket == 0) { + fds[index].revents = LINUX_POLLNVAL; + } else { + if (socket->pending_error != 0 || socket->state == SOCKET_FAILED) { + fds[index].revents |= LINUX_POLLERR; + } + if ((fds[index].events & LINUX_POLLIN) != 0 + && (socket->received != 0 + || socket->state == SOCKET_PEER_CLOSED)) { + fds[index].revents |= LINUX_POLLIN; + } + if ((fds[index].events & LINUX_POLLOUT) != 0 + && socket->state == SOCKET_CONNECTED && socket->pcb != 0 + && tcp_sndbuf(socket->pcb) != 0) { + fds[index].revents |= LINUX_POLLOUT; + } + if (socket->state == SOCKET_PEER_CLOSED) { + fds[index].revents |= LINUX_POLLHUP; + } + } + if (fds[index].revents != 0) { + ++ready; + } + } + return ready; +} + +long typephp_socket_poll(typephp_pollfd *fds, size_t count, int timeout) +{ + const uint64_t start = typephp_network_milliseconds(); + for (;;) { + typephp_network_poll(); + long ready = poll_once(fds, count); + if (ready != 0 || timeout == 0) { + return ready; + } + if (timeout > 0 + && typephp_network_milliseconds() - start >= (uint64_t) timeout) { + return 0; + } + wait_for_network_event(); + } +} + +void typephp_socket_close_owner(unsigned long owner) +{ + for (int index = 0; index < SOCKET_COUNT; ++index) { + if (sockets[index].allocated && sockets[index].owner == owner) { + release_socket(&sockets[index]); + } + } +} diff --git a/examples/typephp-os/kernel/core/socket.h b/examples/typephp-os/kernel/core/socket.h new file mode 100644 index 00000000..2c477a33 --- /dev/null +++ b/examples/typephp-os/kernel/core/socket.h @@ -0,0 +1,37 @@ +#ifndef TYPEPHP_OS_KERNEL_SOCKET_H +#define TYPEPHP_OS_KERNEL_SOCKET_H + +#include +#include + +typedef uint32_t typephp_socklen_t; + +typedef struct { + int fd; + int16_t events; + int16_t revents; +} typephp_pollfd; + +int typephp_socket_is_fd(int fd); +long typephp_socket_open(unsigned long owner, int domain, int type, int protocol); +long typephp_socket_close(int fd); +long typephp_socket_read(int fd, void *buffer, size_t length); +long typephp_socket_write(int fd, const void *buffer, size_t length); +long typephp_socket_connect(int fd, const void *address, typephp_socklen_t length); +long typephp_socket_bind(int fd, const void *address, typephp_socklen_t length); +long typephp_socket_sendto(int fd, const void *buffer, size_t length, int flags, + const void *address, typephp_socklen_t address_length); +long typephp_socket_recvfrom(int fd, void *buffer, size_t length, int flags, + void *address, typephp_socklen_t *address_length); +long typephp_socket_shutdown(int fd, int how); +long typephp_socket_getname(int fd, void *address, typephp_socklen_t *length, + int peer); +long typephp_socket_setsockopt(int fd, int level, int option, + const void *value, typephp_socklen_t length); +long typephp_socket_getsockopt(int fd, int level, int option, + void *value, typephp_socklen_t *length); +long typephp_socket_fcntl(int fd, int command, long argument); +long typephp_socket_poll(typephp_pollfd *fds, size_t count, int timeout); +void typephp_socket_close_owner(unsigned long owner); + +#endif diff --git a/examples/typephp-os/kernel/core/vm.c b/examples/typephp-os/kernel/core/vm.c index 8f3ce8cf..106ebe1a 100644 --- a/examples/typephp-os/kernel/core/vm.c +++ b/examples/typephp-os/kernel/core/vm.c @@ -16,10 +16,12 @@ enum { PAGE_PRESENT = 1, PAGE_WRITE = 2, PAGE_USER = 4, + PAGE_CACHE_DISABLE = 16, PAGE_HUGE = 128, PAGE_OWNED = 512, USER_FIRST_PDE = 0, USER_LAST_PDE = 127, + MMIO_PDP_INDEX = 4, }; #define PAGE_NX (UINT64_C(1) << 63) @@ -30,6 +32,12 @@ enum { extern uint64_t physical_page_allocate(void); extern void physical_page_free(uint64_t page); +static uint64_t mmio_pd_page; +static uint64_t mmio_physical_base; + +#define MMIO_VIRTUAL_BASE UINT64_C(0x100000000) +#define HUGE_PAGE_SIZE UINT64_C(0x200000) + static uint64_t *page_table(uint64_t physical) { return (uint64_t *) (uintptr_t) (physical & PAGE_ADDRESS); @@ -85,6 +93,9 @@ uint64_t typephp_vm_create(void) pml4[0] = pdp_page | PAGE_PRESENT | PAGE_WRITE | PAGE_USER; pdp[0] = kernel_pd_page | PAGE_PRESENT | PAGE_WRITE; pdp[1] = user_pd_page | PAGE_PRESENT | PAGE_WRITE | PAGE_USER; + if (mmio_pd_page != 0) { + pdp[MMIO_PDP_INDEX] = mmio_pd_page | PAGE_PRESENT | PAGE_WRITE; + } for (uint64_t index = 0; index < 512; ++index) { kernel_pd[index] = index * UINT64_C(0x200000) | PAGE_PRESENT | PAGE_WRITE | PAGE_HUGE; @@ -92,6 +103,37 @@ uint64_t typephp_vm_create(void) return pml4_page; } +void *typephp_vm_map_mmio(uint64_t physical_address, uint64_t size) +{ + const uint64_t physical_base = physical_address & ~(HUGE_PAGE_SIZE - 1); + const uint64_t offset = physical_address - physical_base; + if (size == 0 || size > HUGE_PAGE_SIZE || offset > HUGE_PAGE_SIZE - size) { + return 0; + } + if (mmio_pd_page == 0) { + mmio_pd_page = physical_page_allocate(); + if (mmio_pd_page == 0) { + return 0; + } + memset(page_table(mmio_pd_page), 0, PAGE_SIZE); + mmio_physical_base = physical_base; + page_table(mmio_pd_page)[0] = physical_base | PAGE_PRESENT + | PAGE_WRITE | PAGE_CACHE_DISABLE | PAGE_HUGE; + } else if (mmio_physical_base != physical_base) { + return 0; + } + + const uint64_t address_space = typephp_vm_current(); + uint64_t *pml4 = page_table(address_space); + if ((pml4[0] & PAGE_PRESENT) == 0) { + return 0; + } + uint64_t *pdp = page_table(pml4[0]); + pdp[MMIO_PDP_INDEX] = mmio_pd_page | PAGE_PRESENT | PAGE_WRITE; + typephp_vm_activate(address_space); + return (void *) (uintptr_t) (MMIO_VIRTUAL_BASE + offset); +} + void typephp_vm_activate(uint64_t address_space) { __asm__ volatile("mov %0, %%cr3" : : "r"(address_space) : "memory"); diff --git a/examples/typephp-os/kernel/core/vm.h b/examples/typephp-os/kernel/core/vm.h index 0cbc10f8..380ef9b7 100644 --- a/examples/typephp-os/kernel/core/vm.h +++ b/examples/typephp-os/kernel/core/vm.h @@ -14,6 +14,7 @@ uint64_t typephp_vm_create(void); void typephp_vm_destroy(uint64_t address_space); void typephp_vm_activate(uint64_t address_space); uint64_t typephp_vm_current(void); +void *typephp_vm_map_mmio(uint64_t physical_address, uint64_t size); int typephp_vm_map_user( uint64_t address_space, uint64_t address, uint64_t size, unsigned int flags); int typephp_vm_user_range( diff --git a/examples/typephp-os/project.yml b/examples/typephp-os/project.yml index 8bf993c7..38f91a03 100644 --- a/examples/typephp-os/project.yml +++ b/examples/typephp-os/project.yml @@ -3,6 +3,29 @@ mode: bin sources: - kernel - thirdparty/openlibm/src + - thirdparty/lwip/src/core/init.c + - thirdparty/lwip/src/core/def.c + - thirdparty/lwip/src/core/dns.c + - thirdparty/lwip/src/core/inet_chksum.c + - thirdparty/lwip/src/core/ip.c + - thirdparty/lwip/src/core/mem.c + - thirdparty/lwip/src/core/memp.c + - thirdparty/lwip/src/core/netif.c + - thirdparty/lwip/src/core/pbuf.c + - thirdparty/lwip/src/core/stats.c + - thirdparty/lwip/src/core/sys.c + - thirdparty/lwip/src/core/timeouts.c + - thirdparty/lwip/src/core/tcp.c + - thirdparty/lwip/src/core/tcp_in.c + - thirdparty/lwip/src/core/tcp_out.c + - thirdparty/lwip/src/core/udp.c + - thirdparty/lwip/src/core/ipv4/acd.c + - thirdparty/lwip/src/core/ipv4/etharp.c + - thirdparty/lwip/src/core/ipv4/icmp.c + - thirdparty/lwip/src/core/ipv4/ip4.c + - thirdparty/lwip/src/core/ipv4/ip4_addr.c + - thirdparty/lwip/src/core/ipv4/ip4_frag.c + - thirdparty/lwip/src/netif/ethernet.c objects: - build/objects/kernel64-entry.o link-paths: @@ -14,6 +37,8 @@ output: build/kernel64.elf cxx-std: c++17 include-paths: - kernel/core/abi + - kernel/core/net + - thirdparty/lwip/src/include - thirdparty/openlibm/include - thirdparty/openlibm/src - thirdparty/openlibm/amd64 @@ -22,6 +47,9 @@ defines: - PHPX_NO_EXCEPTION=1 - PHPX_NO_RTTI=1 - PHP_NANO_NO_LIBC=1 + - HAVE_SYS_SOCKET_H=1 + - HAVE_NETINET_IN_H=1 + - HAVE_SHUTDOWN=1 - __NO_CTYPE=1 - ZEND_MM_ERROR=0 - ZEND_MM_CUSTOM=0 @@ -62,4 +90,4 @@ ld-flags: - -no-pie - -Wl,-m,elf_x86_64 - -Wl,--build-id=none - - -Wl,-T,examples/typephp-os/boot/kernel64.ld + - -Wl,-T,boot/kernel64.ld diff --git a/examples/typephp-os/run.sh b/examples/typephp-os/run.sh new file mode 100755 index 00000000..1ea11284 --- /dev/null +++ b/examples/typephp-os/run.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash + +set -euo pipefail + +project_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd) +kernel="${project_dir}/build/typephp-os.elf" +disk="${project_dir}/build/typephp-os.img" + +if ! command -v qemu-system-x86_64 >/dev/null 2>&1; then + echo "run.sh: qemu-system-x86_64 is not installed" >&2 + exit 1 +fi + +if [[ ! -r /dev/kvm || ! -w /dev/kvm ]]; then + echo "run.sh: /dev/kvm is unavailable or is not accessible by the current user" >&2 + exit 1 +fi + +if [[ ! -f "${kernel}" ]]; then + echo "run.sh: kernel image not found: ${kernel}" >&2 + exit 1 +fi + +if [[ ! -f "${disk}" ]]; then + echo "run.sh: disk image not found: ${disk}" >&2 + exit 1 +fi + +qemu-system-x86_64 \ + -enable-kvm \ + -cpu host \ + -m 512M \ + -kernel "${kernel}" \ + -drive "file=${disk},format=raw,if=ide,index=0" \ + -netdev user,id=net0 \ + -device e1000,netdev=net0 \ + -display none \ + -serial stdio \ + -monitor none \ + -no-reboot \ + -no-shutdown \ + "$@" diff --git a/examples/typephp-os/tools/build-network-thirdparty.sh b/examples/typephp-os/tools/build-network-thirdparty.sh new file mode 100755 index 00000000..5c841376 --- /dev/null +++ b/examples/typephp-os/tools/build-network-thirdparty.sh @@ -0,0 +1,135 @@ +#!/usr/bin/env bash + +set -euo pipefail + +project_dir=$(cd -- "$(dirname -- "$0")/.." && pwd) +build_root="${project_dir}/build/network-thirdparty" +install_root="${build_root}/install" +jobs=${JOBS:-4} +build_revision=5 +build_stamp="${build_root}/.typephp-os-network-v${build_revision}" +common_cflags="-O2 -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 -ffreestanding -fno-builtin -fno-stack-protector -fno-pic -fno-pie -ffunction-sections -fdata-sections" + +"${project_dir}/tools/fetch-openssl.sh" +"${project_dir}/tools/fetch-nghttp2.sh" +"${project_dir}/tools/fetch-curl.sh" + +mkdir -p "${build_root}/openssl" "${build_root}/nghttp2" \ + "${build_root}/curl" "${install_root}" + +# Configure probes describe the build host, not TypePHP-OS. Reconfigure all +# three libraries together whenever the freestanding profile changes so stale +# hosted objects can never leak glibc fortify, pthread, or signal dependencies +# into a user-mode ELF. +if [[ ! -f "${build_stamp}" ]]; then + if [[ -f "${build_root}/openssl/Makefile" ]]; then + make -C "${build_root}/openssl" clean + fi + if [[ -f "${build_root}/nghttp2/Makefile" ]]; then + make -C "${build_root}/nghttp2" clean + fi + if [[ -f "${build_root}/curl/Makefile" ]]; then + make -C "${build_root}/curl" clean + fi + find "${install_root}" -type f -delete +fi + +# TLS 1.2/1.3 and X.509 remain complete. Command-line tools, tests, dynamic +# loading, historical protocol versions, datagram TLS, and unrelated protocol +# facilities are excluded from the static TypePHP-OS network SDK. +if [[ ! -f "${install_root}/lib64/libssl.a" \ + && ! -f "${install_root}/lib/libssl.a" ]]; then + cd "${build_root}/openssl" + "${project_dir}/thirdparty/openssl/Configure" linux-x86_64 \ + --prefix="${install_root}" --libdir=lib \ + no-shared no-apps no-tests no-docs no-dso no-module no-engine \ + no-legacy no-quic no-dtls no-dtls1 no-dtls1_2 \ + no-ssl3 no-tls1 no-tls1_1 no-cmp no-ocsp no-srp no-ts \ + no-threads no-ui-console \ + -DOPENSSL_RAND_SEED_DEVRANDOM_SHM_ID=-1 \ + ${common_cflags} + make -j"${jobs}" build_sw + make install_sw +fi + +if [[ ! -f "${install_root}/lib/libnghttp2.a" ]]; then + cd "${build_root}/nghttp2" + "${project_dir}/thirdparty/nghttp2/configure" \ + --prefix="${install_root}" --libdir="${install_root}/lib" \ + --disable-shared --enable-static --enable-lib-only \ + --disable-threads --disable-failmalloc \ + CFLAGS="${common_cflags}" LDFLAGS="-no-pie" + make -j"${jobs}" + make install +fi + +if [[ ! -f "${install_root}/lib/libcurl.a" ]]; then + cd "${build_root}/curl" + PKG_CONFIG_PATH="${install_root}/lib/pkgconfig" \ + "${project_dir}/thirdparty/curl/configure" \ + --prefix="${install_root}" --libdir="${install_root}/lib" \ + --disable-shared --enable-static --enable-symbol-hiding \ + --enable-http \ + --disable-ftp --disable-file --disable-ipfs \ + --disable-ldap --disable-ldaps --disable-rtsp \ + --disable-dict --disable-telnet --disable-tftp \ + --disable-pop3 --disable-imap --disable-smb --disable-smtp \ + --disable-gopher --disable-mqtt \ + --disable-proxy --disable-doh --disable-websockets \ + --disable-unix-sockets --disable-socketpair \ + --disable-httpsrr --disable-ech --disable-proxy-http3 \ + --disable-alt-svc --disable-hsts --disable-netrc \ + --disable-manual --disable-docs --disable-libcurl-option \ + --disable-cookies --disable-dateparse --disable-progress-meter \ + --disable-get-easy-options --disable-headers-api --disable-verbose \ + --disable-threaded-resolver --disable-ipv6 \ + --with-openssl="${install_root}" \ + --with-nghttp2="${install_root}" \ + --without-zlib --without-brotli --without-zstd \ + --without-libpsl --without-libidn2 --without-libssh2 \ + --without-librtmp --without-gssapi \ + ac_cv_header_pthread_h=no ac_cv_func_pthread_create=no \ + ac_cv_func_alarm=no ac_cv_func_sigaction=no \ + curl_cv_func_alarm=no curl_cv_func_sigaction=no \ + curl_cv_func_signal=no \ + CFLAGS="${common_cflags}" \ + LDFLAGS="-no-pie -Wl,--gc-sections" + # Autoconf executed on Linux reports signal APIs that TypePHP-OS + # deliberately does not expose. CURLOPT_NOSIGNAL is always used by the + # user runtime, and removing these generated feature defines also removes + # unreachable alarm/sigsetjmp ABI dependencies from libcurl. + sed -i \ + -e 's/^#define HAVE_ALARM 1$/\/\* #undef HAVE_ALARM \*\//' \ + -e 's/^#define HAVE_SIGACTION 1$/\/\* #undef HAVE_SIGACTION \*\//' \ + -e 's/^#define HAVE_SIGNAL 1$/\/\* #undef HAVE_SIGNAL \*\//' \ + "${build_root}/curl/lib/curl_config.h" + # Build libcurl only. The curl command-line executable is intentionally + # outside the TypePHP-OS SDK. + make -C lib -j"${jobs}" + make -C lib install + make -C include install + mkdir -p "${install_root}/bin" "${install_root}/lib/pkgconfig" + install -m 755 curl-config "${install_root}/bin/curl-config" + install -m 644 libcurl.pc "${install_root}/lib/pkgconfig/libcurl.pc" +fi + +touch "${build_stamp}" + +protocols=$("${build_root}/curl/curl-config" --protocols \ + | tr '[:upper:]' '[:lower:]' | sort -u | tr '\n' ' ') +if [[ "${protocols}" != "http https " ]]; then + echo "unexpected libcurl protocol set: ${protocols}" >&2 + exit 1 +fi + +features=$("${build_root}/curl/curl-config" --features | tr '\n' ' ') +if [[ " ${features} " != *" HTTP2 "* ]]; then + echo "minimal libcurl was built without HTTP/2: ${features}" >&2 + exit 1 +fi + +echo "OpenSSL static TLS library: OK" +echo "nghttp2 static library: OK" +echo "libcurl protocols: HTTP HTTPS" +echo "libcurl HTTP/2: OK" +echo "Network SDK prefix: ${install_root}" diff --git a/examples/typephp-os/tools/fetch-curl.sh b/examples/typephp-os/tools/fetch-curl.sh new file mode 100755 index 00000000..ef9f87fe --- /dev/null +++ b/examples/typephp-os/tools/fetch-curl.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env sh + +set -eu + +project_dir=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +version=8.22.0 +archive_sha256=f7ef3ae8a22e521f289803fe93543eb64c329b58aa73a9e224dfd915a2a5f4f7 +target="${project_dir}/thirdparty/curl" +marker="${target}/.typephp-source-${version}" + +if [ -f "${marker}" ] && [ -f "${target}/lib/http.c" ]; then + exit 0 +fi + +temporary=$(mktemp -d "${TMPDIR:-/tmp}/typephp-os-curl.XXXXXX") +trap 'rm -rf "${temporary}"' EXIT HUP INT TERM +archive="${temporary}/curl.tar.xz" +source_dir="${temporary}/curl-${version}" + +echo "Downloading curl ${version}" +curl -L --fail --retry 3 --silent --show-error \ + -o "${archive}" "https://curl.se/download/curl-${version}.tar.xz" +echo "${archive_sha256} ${archive}" | sha256sum -c - +tar -xJf "${archive}" -C "${temporary}" + +mkdir -p "${project_dir}/thirdparty" +rm -rf "${target}" +mv "${source_dir}" "${target}" +printf '%s\n' "${archive_sha256}" > "${marker}" +echo "Installed curl ${version} in ${target}" diff --git a/examples/typephp-os/tools/fetch-lwip.sh b/examples/typephp-os/tools/fetch-lwip.sh new file mode 100755 index 00000000..814174db --- /dev/null +++ b/examples/typephp-os/tools/fetch-lwip.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env sh + +set -eu + +project_dir=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +version=2.2.1 +tag=STABLE-2_2_1_RELEASE +archive_sha256=ce0b7461c0ad9602c376f0bf07c5eb7253b48c7bf66f011c6bf3e2a96731c539 +target="${project_dir}/thirdparty/lwip" +marker="${target}/.typephp-source-${version}" + +if [ -f "${marker}" ] && [ -f "${target}/src/core/tcp.c" ]; then + exit 0 +fi + +temporary=$(mktemp -d "${TMPDIR:-/tmp}/typephp-os-lwip.XXXXXX") +trap 'rm -rf "${temporary}"' EXIT HUP INT TERM +archive="${temporary}/lwip.tar.gz" +source_dir="${temporary}/lwip-${tag}" + +echo "Downloading lwIP ${version}" +curl -L --fail --retry 3 --silent --show-error \ + -o "${archive}" \ + "https://github.com/lwip-tcpip/lwip/archive/refs/tags/${tag}.tar.gz" +echo "${archive_sha256} ${archive}" | sha256sum -c - +tar -xzf "${archive}" -C "${temporary}" + +mkdir -p "${project_dir}/thirdparty" +rm -rf "${target}" +mv "${source_dir}" "${target}" +printf '%s\n' "${archive_sha256}" > "${marker}" +echo "Installed lwIP ${version} in ${target}" diff --git a/examples/typephp-os/tools/fetch-nghttp2.sh b/examples/typephp-os/tools/fetch-nghttp2.sh new file mode 100755 index 00000000..7adeb5f5 --- /dev/null +++ b/examples/typephp-os/tools/fetch-nghttp2.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env sh + +set -eu + +project_dir=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +version=1.70.0 +archive_sha256=e05cb1388eaca3830aded4ccf20044b6e1ac1a61411dcca11b0437c4285c8bc2 +target="${project_dir}/thirdparty/nghttp2" +marker="${target}/.typephp-source-${version}" + +if [ -f "${marker}" ] && [ -f "${target}/lib/nghttp2_session.c" ]; then + exit 0 +fi + +temporary=$(mktemp -d "${TMPDIR:-/tmp}/typephp-os-nghttp2.XXXXXX") +trap 'rm -rf "${temporary}"' EXIT HUP INT TERM +archive="${temporary}/nghttp2.tar.xz" +source_dir="${temporary}/nghttp2-${version}" + +echo "Downloading nghttp2 ${version}" +curl -L --fail --retry 3 --silent --show-error \ + -o "${archive}" \ + "https://github.com/nghttp2/nghttp2/releases/download/v${version}/nghttp2-${version}.tar.xz" +echo "${archive_sha256} ${archive}" | sha256sum -c - +tar -xJf "${archive}" -C "${temporary}" + +mkdir -p "${project_dir}/thirdparty" +rm -rf "${target}" +mv "${source_dir}" "${target}" +printf '%s\n' "${archive_sha256}" > "${marker}" +echo "Installed nghttp2 ${version} in ${target}" diff --git a/examples/typephp-os/tools/fetch-openssl.sh b/examples/typephp-os/tools/fetch-openssl.sh new file mode 100755 index 00000000..8ec5184a --- /dev/null +++ b/examples/typephp-os/tools/fetch-openssl.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env sh + +set -eu + +project_dir=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +version=3.5.8 +archive_sha256=a8f84a39918ec6415ce765d9b429d313ba97b8143169c172e734b9514464f5b2 +target="${project_dir}/thirdparty/openssl" +marker="${target}/.typephp-source-${version}" + +if [ -f "${marker}" ] && [ -f "${target}/Configure" ]; then + exit 0 +fi + +temporary=$(mktemp -d "${TMPDIR:-/tmp}/typephp-os-openssl.XXXXXX") +trap 'rm -rf "${temporary}"' EXIT HUP INT TERM +archive="${temporary}/openssl.tar.gz" +source_dir="${temporary}/openssl-${version}" + +echo "Downloading OpenSSL ${version} LTS" +curl -L --fail --retry 3 --silent --show-error \ + -o "${archive}" \ + "https://github.com/openssl/openssl/releases/download/openssl-${version}/openssl-${version}.tar.gz" +echo "${archive_sha256} ${archive}" | sha256sum -c - +tar -xzf "${archive}" -C "${temporary}" + +mkdir -p "${project_dir}/thirdparty" +rm -rf "${target}" +mv "${source_dir}" "${target}" +printf '%s\n' "${archive_sha256}" > "${marker}" +echo "Installed OpenSSL ${version} in ${target}" diff --git a/examples/typephp-os/tools/fetch-thirdparty.sh b/examples/typephp-os/tools/fetch-thirdparty.sh index 09b32814..55630e9e 100755 --- a/examples/typephp-os/tools/fetch-thirdparty.sh +++ b/examples/typephp-os/tools/fetch-thirdparty.sh @@ -7,3 +7,7 @@ tools_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) "${tools_dir}/fetch-openlibm.sh" "${tools_dir}/fetch-compiler-rt-builtins.sh" "${tools_dir}/fetch-toybox.sh" +"${tools_dir}/fetch-lwip.sh" +"${tools_dir}/fetch-openssl.sh" +"${tools_dir}/fetch-nghttp2.sh" +"${tools_dir}/fetch-curl.sh" diff --git a/examples/typephp-os/tools/test-qemu.sh b/examples/typephp-os/tools/test-qemu.sh index ecc348cf..233f79af 100755 --- a/examples/typephp-os/tools/test-qemu.sh +++ b/examples/typephp-os/tools/test-qemu.sh @@ -7,13 +7,32 @@ disk=${2:?FAT16 disk image is required} log=${3:?log path is required} qemu_disk=$(mktemp /tmp/typephp-os-qemu.XXXXXX.img) -trap 'rm -f "${qemu_disk}"' EXIT +http_log="${log}.http-server" +python3 -m http.server 18080 --bind 127.0.0.1 \ + --directory "$(dirname -- "$0")" >"${http_log}" 2>&1 & +http_pid=$! +cleanup() { + kill "${http_pid}" 2>/dev/null || true + wait "${http_pid}" 2>/dev/null || true + rm -f "${qemu_disk}" +} +trap cleanup EXIT cp "${disk}" "${qemu_disk}" -if timeout 35 qemu-system-x86_64 \ - -m 512M \ +sleep 0.2 +if ! kill -0 "${http_pid}" 2>/dev/null; then + cat "${http_log}" + echo "failed to start the host HTTP smoke server" >&2 + exit 1 +fi + +if timeout 60 qemu-system-x86_64 \ + -cpu max \ + -m 512M \ -kernel "${kernel}" \ -drive file="${qemu_disk}",format=raw,if=ide,index=0 \ + -netdev user,id=net0 \ + -device e1000,netdev=net0 \ -display none \ -serial stdio \ -monitor none \ @@ -21,7 +40,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\nfree\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 + <<< $'\ndate\nuname\nuname -a\nfree\necho Hello TypePHP userspace\nhello Dynamically loaded\ntnhello alpha beta\nsystest\nnettest\nbuiltins\nhttps\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 +70,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, free, cat, echo, write, touch, mkdir, rm, rmdir, mv, memtest, systest, builtins, fault, vmfault, wrfault" "${log}" +grep -q "Commands: ls, cd, pwd, date, uname, free, cat, echo, write, touch, mkdir, rm, rmdir, mv, memtest, systest, nettest, builtins, tnhello, https, 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}" @@ -75,6 +94,14 @@ if [[ $(grep -c '^bool(true)' "${log}") -lt 3 ]]; then exit 1 fi grep -q '^basic syscalls: OK' "${log}" +grep -q '^TCP HTTP socket: OK' "${log}" +grep -q '^lwIP DNS: OK' "${log}" +grep -q '^CSPRNG: OK' "${log}" +grep -q '^GET https://httpcan.org/get' "${log}" +grep -q '^HTTP status: 200' "${log}" +grep -q '^HTTP version: 2' "${log}" +grep -q '^TLS backend: OpenSSL/' "${log}" +grep -q '^OpenSSL SHA-256: ' "${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 diff --git a/examples/typephp-os/user/cmd/nettest.c b/examples/typephp-os/user/cmd/nettest.c new file mode 100644 index 00000000..1257ca55 --- /dev/null +++ b/examples/typephp-os/user/cmd/nettest.c @@ -0,0 +1,93 @@ +#include +#include +#include +#include +#include +#include +#include + +static int fail(const char *operation) +{ + (void) write(STDERR_FILENO, "nettest: ", sizeof("nettest: ") - 1); + (void) write(STDERR_FILENO, operation, strlen(operation)); + (void) write(STDERR_FILENO, ": ", 2); + (void) write(STDERR_FILENO, strerror(errno), strlen(strerror(errno))); + (void) write(STDERR_FILENO, "\n", 1); + return 1; +} + +int main(void) +{ + static const char request[] = + "GET /invalid-elf.txt HTTP/1.0\r\n" + "Host: 10.0.2.2\r\n" + "Connection: close\r\n\r\n"; + struct sockaddr_in server = { + AF_INET, + htons(18080), + { htonl(0x0a000202u) }, + { 0, 0, 0, 0, 0, 0, 0, 0 }, + }; + char response[64]; + unsigned char random[32]; + struct addrinfo hints = {0}; + struct addrinfo *resolved = 0; + unsigned char random_bits = 0; + int index; + + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + if (getaddrinfo("httpcan.org", "443", &hints, &resolved) != 0 + || resolved == 0 || resolved->ai_addr == 0) { + (void) write(STDERR_FILENO, "nettest: DNS resolution failed\n", + sizeof("nettest: DNS resolution failed\n") - 1); + return 1; + } + freeaddrinfo(resolved); + (void) write(STDOUT_FILENO, "lwIP DNS: OK\n", + sizeof("lwIP DNS: OK\n") - 1); + + if (getrandom(random, sizeof(random), 0) != sizeof(random)) { + return fail("getrandom"); + } + for (index = 0; index < (int) sizeof(random); ++index) { + random_bits |= random[index]; + } + if (random_bits == 0) { + (void) write(STDERR_FILENO, "nettest: empty random output\n", + sizeof("nettest: empty random output\n") - 1); + return 1; + } + (void) write(STDOUT_FILENO, "CSPRNG: OK\n", + sizeof("CSPRNG: OK\n") - 1); + + int fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd < 0) { + return fail("socket"); + } + if (connect(fd, (const struct sockaddr *) &server, sizeof(server)) < 0) { + (void) close(fd); + return fail("connect"); + } + if (send(fd, request, sizeof(request) - 1, MSG_NOSIGNAL) + != (ssize_t) (sizeof(request) - 1)) { + (void) close(fd); + return fail("send"); + } + ssize_t received = recv(fd, response, sizeof(response), 0); + if (received < 12) { + (void) close(fd); + return fail("recv"); + } + (void) close(fd); + if (response[0] != 'H' || response[1] != 'T' || response[2] != 'T' + || response[3] != 'P' || response[4] != '/' + || response[5] != '1' || response[6] != '.') { + (void) write(STDERR_FILENO, "nettest: invalid HTTP response\n", + sizeof("nettest: invalid HTTP response\n") - 1); + return 1; + } + (void) write(STDOUT_FILENO, "TCP HTTP socket: OK\n", + sizeof("TCP HTTP socket: OK\n") - 1); + return 0; +} diff --git a/examples/typephp-os/user/cmd/sh.c b/examples/typephp-os/user/cmd/sh.c index 31384775..4900b3c0 100644 --- a/examples/typephp-os/user/cmd/sh.c +++ b/examples/typephp-os/user/cmd/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, free, cat, echo, write, touch, mkdir, rm, rmdir, mv, memtest, systest, builtins, fault, vmfault, wrfault\n", - sizeof("Commands: ls, cd, pwd, date, uname, free, cat, echo, write, touch, mkdir, rm, rmdir, mv, memtest, systest, builtins, fault, vmfault, wrfault\n") - 1); + "Commands: ls, cd, pwd, date, uname, free, cat, echo, write, touch, mkdir, rm, rmdir, mv, memtest, systest, nettest, builtins, tnhello, https, fault, vmfault, wrfault\n", + sizeof("Commands: ls, cd, pwd, date, uname, free, cat, echo, write, touch, mkdir, rm, rmdir, mv, memtest, systest, nettest, builtins, tnhello, https, fault, vmfault, wrfault\n") - 1); for (;;) { char *arguments[9]; int argument_count = 0; diff --git a/examples/typephp-os/user/cmd/systest.c b/examples/typephp-os/user/cmd/systest.c index 7f7ac154..e34e4477 100644 --- a/examples/typephp-os/user/cmd/systest.c +++ b/examples/typephp-os/user/cmd/systest.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -47,6 +48,7 @@ int main(int argc, char **argv) struct dirent *entry; int found_hello = 0; int fd; + int socket_fd; (void) argc; (void) argv; @@ -128,6 +130,18 @@ int main(int argc, char **argv) return fail("interruptible nanosleep"); } + if (socket(AF_INET, SOCK_RAW, 0) != -1 || errno != ESOCKTNOSUPPORT) { + return fail("raw socket rejection"); + } + socket_fd = socket(AF_INET, + SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0); + if (socket_fd < 0 + || (fcntl(socket_fd, F_GETFL) & O_NONBLOCK) == 0 + || fcntl(socket_fd, F_GETFD) != FD_CLOEXEC + || close(socket_fd) != 0) { + return fail("TCP socket descriptor ABI"); + } + (void) write(STDOUT_FILENO, "basic syscalls: OK\n", sizeof("basic syscalls: OK\n") - 1); return 0; diff --git a/examples/typephp-os/user/include/arpa/inet.h b/examples/typephp-os/user/include/arpa/inet.h new file mode 100644 index 00000000..71b2ee94 --- /dev/null +++ b/examples/typephp-os/user/include/arpa/inet.h @@ -0,0 +1,16 @@ +#ifndef TYPEPHP_OS_USER_ARPA_INET_H +#define TYPEPHP_OS_USER_ARPA_INET_H + +#include +#include + +#define INET_ADDRSTRLEN 16 +#define INET6_ADDRSTRLEN 46 + +int inet_aton(const char *text, struct in_addr *address); +in_addr_t inet_addr(const char *text); +int inet_pton(int family, const char *text, void *address); +const char *inet_ntop(int family, const void *address, char *text, + socklen_t length); + +#endif diff --git a/examples/typephp-os/user/include/errno.h b/examples/typephp-os/user/include/errno.h index d816f0d0..d4e6e459 100644 --- a/examples/typephp-os/user/include/errno.h +++ b/examples/typephp-os/user/include/errno.h @@ -19,8 +19,35 @@ int *__errno_location(void); #define ENOTTY 25 #define EROFS 30 #define ERANGE 34 +#define ENOSPC 28 +#define EAGAIN 11 +#define EWOULDBLOCK EAGAIN +#define EPIPE 32 #define ENAMETOOLONG 36 #define ENOSYS 38 #define ENOTEMPTY 39 +#define EINPROGRESS 115 +#define EALREADY 114 +#define ENOTSOCK 88 +#define EDESTADDRREQ 89 +#define EMSGSIZE 90 +#define EPROTOTYPE 91 +#define ENOPROTOOPT 92 +#define EPROTONOSUPPORT 93 +#define ESOCKTNOSUPPORT 94 +#define EOPNOTSUPP 95 +#define EAFNOSUPPORT 97 +#define EADDRINUSE 98 +#define EADDRNOTAVAIL 99 +#define ENETDOWN 100 +#define ENETUNREACH 101 +#define ECONNABORTED 103 +#define ECONNRESET 104 +#define ENOBUFS 105 +#define EISCONN 106 +#define ENOTCONN 107 +#define ETIMEDOUT 110 +#define ECONNREFUSED 111 +#define EHOSTUNREACH 113 #endif diff --git a/examples/typephp-os/user/include/netdb.h b/examples/typephp-os/user/include/netdb.h new file mode 100644 index 00000000..92c9052e --- /dev/null +++ b/examples/typephp-os/user/include/netdb.h @@ -0,0 +1,40 @@ +#ifndef TYPEPHP_OS_USER_NETDB_H +#define TYPEPHP_OS_USER_NETDB_H + +#include + +struct addrinfo { + int ai_flags; + int ai_family; + int ai_socktype; + int ai_protocol; + socklen_t ai_addrlen; + struct sockaddr *ai_addr; + char *ai_canonname; + struct addrinfo *ai_next; +}; + +#define AI_PASSIVE 0x0001 +#define AI_CANONNAME 0x0002 +#define AI_NUMERICHOST 0x0004 +#define AI_V4MAPPED 0x0008 +#define AI_ALL 0x0010 +#define AI_ADDRCONFIG 0x0020 +#define AI_NUMERICSERV 0x0400 + +#define EAI_BADFLAGS -1 +#define EAI_NONAME -2 +#define EAI_AGAIN -3 +#define EAI_FAIL -4 +#define EAI_FAMILY -6 +#define EAI_SOCKTYPE -7 +#define EAI_SERVICE -8 +#define EAI_MEMORY -10 +#define EAI_SYSTEM -11 + +int getaddrinfo(const char *node, const char *service, + const struct addrinfo *hints, struct addrinfo **result); +void freeaddrinfo(struct addrinfo *result); +const char *gai_strerror(int error); + +#endif diff --git a/examples/typephp-os/user/include/netinet/in.h b/examples/typephp-os/user/include/netinet/in.h new file mode 100644 index 00000000..7334b2f5 --- /dev/null +++ b/examples/typephp-os/user/include/netinet/in.h @@ -0,0 +1,50 @@ +#ifndef TYPEPHP_OS_USER_NETINET_IN_H +#define TYPEPHP_OS_USER_NETINET_IN_H + +#include +#include + +typedef uint16_t in_port_t; +typedef uint32_t in_addr_t; + +struct in_addr { + in_addr_t s_addr; +}; + +struct sockaddr_in { + sa_family_t sin_family; + in_port_t sin_port; + struct in_addr sin_addr; + unsigned char sin_zero[8]; +}; + +#define IPPROTO_IP 0 +#define IPPROTO_TCP 6 + +#define INADDR_ANY ((in_addr_t) 0x00000000) +#define INADDR_LOOPBACK ((in_addr_t) 0x0100007f) +#define INADDR_NONE ((in_addr_t) 0xffffffff) + +#define TCP_NODELAY 1 + +static inline uint16_t htons(uint16_t value) +{ + return __builtin_bswap16(value); +} + +static inline uint16_t ntohs(uint16_t value) +{ + return __builtin_bswap16(value); +} + +static inline uint32_t htonl(uint32_t value) +{ + return __builtin_bswap32(value); +} + +static inline uint32_t ntohl(uint32_t value) +{ + return __builtin_bswap32(value); +} + +#endif diff --git a/examples/typephp-os/user/include/poll.h b/examples/typephp-os/user/include/poll.h new file mode 100644 index 00000000..8bad76ed --- /dev/null +++ b/examples/typephp-os/user/include/poll.h @@ -0,0 +1,23 @@ +#ifndef TYPEPHP_OS_USER_POLL_H +#define TYPEPHP_OS_USER_POLL_H + +#include + +typedef unsigned long nfds_t; + +struct pollfd { + int fd; + short events; + short revents; +}; + +#define POLLIN 0x001 +#define POLLPRI 0x002 +#define POLLOUT 0x004 +#define POLLERR 0x008 +#define POLLHUP 0x010 +#define POLLNVAL 0x020 + +int poll(struct pollfd *fds, nfds_t count, int timeout); + +#endif diff --git a/examples/typephp-os/user/include/string.h b/examples/typephp-os/user/include/string.h index 2dac42a9..777a7266 100644 --- a/examples/typephp-os/user/include/string.h +++ b/examples/typephp-os/user/include/string.h @@ -4,6 +4,17 @@ #include size_t strlen(const char *string); +void *memcpy(void *destination, const void *source, size_t size); +void *memmove(void *destination, const void *source, size_t size); +void *memset(void *destination, int value, size_t size); +int memcmp(const void *left, const void *right, size_t size); +int strcmp(const char *left, const char *right); +int strncmp(const char *left, const char *right, size_t size); +char *strcpy(char *destination, const char *source); +char *strncpy(char *destination, const char *source, size_t size); +char *strchr(const char *string, int character); +char *strrchr(const char *string, int character); +char *strstr(const char *haystack, const char *needle); char *strerror(int error); #endif diff --git a/examples/typephp-os/user/include/sys/random.h b/examples/typephp-os/user/include/sys/random.h new file mode 100644 index 00000000..bf50542e --- /dev/null +++ b/examples/typephp-os/user/include/sys/random.h @@ -0,0 +1,14 @@ +#ifndef TYPEPHP_OS_USER_SYS_RANDOM_H +#define TYPEPHP_OS_USER_SYS_RANDOM_H + +#include +#include + +#define GRND_NONBLOCK 0x0001 +#define GRND_RANDOM 0x0002 +#define GRND_INSECURE 0x0004 + +ssize_t getrandom(void *buffer, size_t length, unsigned int flags); +int getentropy(void *buffer, size_t length); + +#endif diff --git a/examples/typephp-os/user/include/sys/socket.h b/examples/typephp-os/user/include/sys/socket.h new file mode 100644 index 00000000..0fa9a521 --- /dev/null +++ b/examples/typephp-os/user/include/sys/socket.h @@ -0,0 +1,66 @@ +#ifndef TYPEPHP_OS_USER_SYS_SOCKET_H +#define TYPEPHP_OS_USER_SYS_SOCKET_H + +#include +#include +#include + +typedef uint32_t socklen_t; +typedef uint16_t sa_family_t; + +struct sockaddr { + sa_family_t sa_family; + char sa_data[14]; +}; + +struct iovec { + void *iov_base; + size_t iov_len; +}; + +#define AF_UNSPEC 0 +#define AF_UNIX 1 +#define AF_INET 2 +#define AF_INET6 10 + +#define PF_UNSPEC AF_UNSPEC +#define PF_UNIX AF_UNIX +#define PF_INET AF_INET +#define PF_INET6 AF_INET6 + +#define SOCK_STREAM 1 +#define SOCK_DGRAM 2 +#define SOCK_RAW 3 +#define SOCK_NONBLOCK 04000 +#define SOCK_CLOEXEC 02000000 + +#define SOL_SOCKET 1 +#define SO_ERROR 4 +#define SO_KEEPALIVE 9 +#define SO_RCVBUF 8 +#define SO_SNDBUF 7 + +#define MSG_PEEK 2 +#define MSG_DONTWAIT 0x40 +#define MSG_NOSIGNAL 0x4000 + +#define SHUT_RD 0 +#define SHUT_WR 1 +#define SHUT_RDWR 2 + +int socket(int domain, int type, int protocol); +int connect(int fd, const struct sockaddr *address, socklen_t length); +int bind(int fd, const struct sockaddr *address, socklen_t length); +ssize_t send(int fd, const void *buffer, size_t length, int flags); +ssize_t recv(int fd, void *buffer, size_t length, int flags); +ssize_t sendto(int fd, const void *buffer, size_t length, int flags, + const struct sockaddr *address, socklen_t address_length); +ssize_t recvfrom(int fd, void *buffer, size_t length, int flags, + struct sockaddr *address, socklen_t *address_length); +int shutdown(int fd, int how); +int getsockname(int fd, struct sockaddr *address, socklen_t *length); +int getpeername(int fd, struct sockaddr *address, socklen_t *length); +int setsockopt(int fd, int level, int option, const void *value, socklen_t length); +int getsockopt(int fd, int level, int option, void *value, socklen_t *length); + +#endif diff --git a/examples/typephp-os/user/include/sys/syscall.h b/examples/typephp-os/user/include/sys/syscall.h index 01f2ae06..43ad6f29 100644 --- a/examples/typephp-os/user/include/sys/syscall.h +++ b/examples/typephp-os/user/include/sys/syscall.h @@ -9,6 +9,7 @@ #define SYS_stat TYPEPHP_SYS_STAT #define SYS_fstat TYPEPHP_SYS_FSTAT #define SYS_lstat TYPEPHP_SYS_LSTAT +#define SYS_poll TYPEPHP_SYS_POLL #define SYS_lseek TYPEPHP_SYS_LSEEK #define SYS_mmap TYPEPHP_SYS_MMAP #define SYS_mprotect TYPEPHP_SYS_MPROTECT @@ -17,6 +18,16 @@ #define SYS_ioctl TYPEPHP_SYS_IOCTL #define SYS_access TYPEPHP_SYS_ACCESS #define SYS_nanosleep TYPEPHP_SYS_NANOSLEEP +#define SYS_socket TYPEPHP_SYS_SOCKET +#define SYS_connect TYPEPHP_SYS_CONNECT +#define SYS_bind TYPEPHP_SYS_BIND +#define SYS_sendto TYPEPHP_SYS_SENDTO +#define SYS_recvfrom TYPEPHP_SYS_RECVFROM +#define SYS_shutdown TYPEPHP_SYS_SHUTDOWN +#define SYS_getsockname TYPEPHP_SYS_GETSOCKNAME +#define SYS_getpeername TYPEPHP_SYS_GETPEERNAME +#define SYS_setsockopt TYPEPHP_SYS_SETSOCKOPT +#define SYS_getsockopt TYPEPHP_SYS_GETSOCKOPT #define SYS_getpid TYPEPHP_SYS_GETPID #define SYS_exit TYPEPHP_SYS_EXIT #define SYS_uname TYPEPHP_SYS_UNAME @@ -45,12 +56,14 @@ #define SYS_openat TYPEPHP_SYS_OPENAT #define SYS_newfstatat TYPEPHP_SYS_NEWFSTATAT #define SYS_faccessat TYPEPHP_SYS_FACCESSAT +#define SYS_getrandom TYPEPHP_SYS_GETRANDOM /* Private bootstrap services; these deliberately do not claim Linux syscall * numbers or semantics. */ #define SYS_typephp_spawn TYPEPHP_SYS_SPAWN #define SYS_typephp_rename TYPEPHP_SYS_RENAME #define SYS_typephp_memory_info TYPEPHP_SYS_MEMORY_INFO +#define SYS_typephp_dns_resolve_ipv4 TYPEPHP_SYS_DNS_RESOLVE_IPV4 long syscall(long number, ...); diff --git a/examples/typephp-os/user/nano/free.yml b/examples/typephp-os/user/nano/free.yml index a4738114..dc1d90c5 100644 --- a/examples/typephp-os/user/nano/free.yml +++ b/examples/typephp-os/user/nano/free.yml @@ -18,6 +18,9 @@ defines: - PHPX_NO_RTTI=1 - PHP_NANO_NO_LIBC=1 - HAVE_SYS_UTSNAME_H=1 + - HAVE_SYS_SOCKET_H=1 + - HAVE_NETINET_IN_H=1 + - HAVE_SHUTDOWN=1 - __NO_CTYPE=1 - ZEND_MM_ERROR=0 - ZEND_MM_CUSTOM=0 @@ -58,7 +61,7 @@ ld-flags: - -no-pie - -Wl,-m,elf_x86_64 - -Wl,--build-id=none - - -Wl,-T,examples/typephp-os/user/runtime/linker.ld + - -Wl,-T,user/runtime/linker.ld - -Wl,--defsym,USER_BASE=0x41000000 link-paths: - ../../build diff --git a/examples/typephp-os/user/nano/hello.yml b/examples/typephp-os/user/nano/hello.yml index 63c23577..8a128ba2 100644 --- a/examples/typephp-os/user/nano/hello.yml +++ b/examples/typephp-os/user/nano/hello.yml @@ -16,6 +16,9 @@ defines: - PHPX_NO_RTTI=1 - PHP_NANO_NO_LIBC=1 - HAVE_SYS_UTSNAME_H=1 + - HAVE_SYS_SOCKET_H=1 + - HAVE_NETINET_IN_H=1 + - HAVE_SHUTDOWN=1 - __NO_CTYPE=1 - ZEND_MM_ERROR=0 - ZEND_MM_CUSTOM=0 @@ -56,7 +59,7 @@ ld-flags: - -no-pie - -Wl,-m,elf_x86_64 - -Wl,--build-id=none - - -Wl,-T,examples/typephp-os/user/runtime/linker.ld + - -Wl,-T,user/runtime/linker.ld - -Wl,--defsym,USER_BASE=0x41000000 link-paths: - ../../build diff --git a/examples/typephp-os/user/nano/https.php b/examples/typephp-os/user/nano/https.php new file mode 100644 index 00000000..816cb512 --- /dev/null +++ b/examples/typephp-os/user/nano/https.php @@ -0,0 +1,36 @@ + +#include +#include +#include +#include +#include +#include +#include +#include + +static unsigned short ctype_flags[384]; +static int ctype_tolower[384]; +static int ctype_ready; + +static void init_ctype_tables(void) +{ + int value; + if (ctype_ready) { + return; + } + for (value = -128; value < 256; ++value) { + const unsigned char byte = (unsigned char) value; + unsigned short flags = 0; + ctype_tolower[value + 128] = value; + if (byte >= 'A' && byte <= 'Z') { + flags |= 0x0100 | 0x0400 | 0x0008; + ctype_tolower[value + 128] = byte + ('a' - 'A'); + } else if (byte >= 'a' && byte <= 'z') { + flags |= 0x0200 | 0x0400 | 0x0008; + } + if (byte >= '0' && byte <= '9') { + flags |= 0x0800 | 0x1000 | 0x0008; + } else if ((byte >= 'a' && byte <= 'f') + || (byte >= 'A' && byte <= 'F')) { + flags |= 0x1000; + } + if (byte == ' ' || (byte >= '\t' && byte <= '\r')) { + flags |= 0x2000; + } + if (byte == ' ' || byte == '\t') { + flags |= 0x0001; + } + if (byte < 0x20 || byte == 0x7f) { + flags |= 0x0002; + } + if (byte >= 0x20 && byte < 0x7f) { + flags |= 0x4000; + } + if (byte > 0x20 && byte < 0x7f) { + flags |= 0x8000; + } + if (byte > 0x20 && byte < 0x7f + && !(byte >= '0' && byte <= '9') + && !(byte >= 'A' && byte <= 'Z') + && !(byte >= 'a' && byte <= 'z')) { + flags |= 0x0004; + } + ctype_flags[value + 128] = flags; + } + ctype_ready = 1; +} + +const unsigned short **__ctype_b_loc(void) +{ + static const unsigned short *table; + init_ctype_tables(); + table = ctype_flags + 128; + return &table; +} + +const int **__ctype_tolower_loc(void) +{ + static const int *table; + init_ctype_tables(); + table = ctype_tolower + 128; + return &table; +} + +int __popcountdi2(uint64_t value) +{ + int count = 0; + while (value != 0) { + value &= value - 1; + ++count; + } + return count; +} + +char *secure_getenv(const char *name) +{ + return getenv(name); +} + +int atexit(void (*function)(void)) +{ + /* A user image is discarded as one unit. OpenSSL cleanup registration is + * therefore successful without retaining process-exit callbacks. */ + (void) function; + return 0; +} + +void explicit_bzero(void *memory, size_t size) +{ + volatile unsigned char *cursor = (volatile unsigned char *) memory; + while (size-- != 0) { + *cursor++ = 0; + } +} + +void *shmat(int id, const void *address, int flags) +{ + (void) id; + (void) address; + (void) flags; + errno = ENOSYS; + return (void *) -1; +} + +int shmdt(const void *address) +{ + (void) address; + errno = ENOSYS; + return -1; +} + +int shmget(int key, size_t size, int flags) +{ + (void) key; + (void) size; + (void) flags; + errno = ENOSYS; + return -1; +} + +static int leap_year(int year) +{ + return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0; +} + +struct tm *gmtime_r(const time_t *timer, struct tm *result) +{ + static const int month_days[] = { + 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, + }; + int64_t seconds; + int64_t days; + int year = 1970; + int month = 0; + int days_in_month; + if (timer == 0 || result == 0 || *timer < 0) { + errno = EINVAL; + return 0; + } + seconds = (int64_t) *timer; + days = seconds / 86400; + result->tm_sec = (int) (seconds % 60); + result->tm_min = (int) ((seconds / 60) % 60); + result->tm_hour = (int) ((seconds / 3600) % 24); + result->tm_wday = (int) ((days + 4) % 7); + while (days >= (leap_year(year) ? 366 : 365)) { + days -= leap_year(year) ? 366 : 365; + ++year; + } + result->tm_yday = (int) days; + while (month < 12) { + days_in_month = month_days[month] + + (month == 1 && leap_year(year) ? 1 : 0); + if (days < days_in_month) { + break; + } + days -= days_in_month; + ++month; + } + result->tm_year = year - 1900; + result->tm_mon = month; + result->tm_mday = (int) days + 1; + result->tm_isdst = 0; + return result; +} + +struct tm *gmtime(const time_t *timer) +{ + static struct tm result; + return gmtime_r(timer, &result); +} + +/* TypePHP-OS currently uses UTC as its only timezone. This civil-date + * conversion follows the proleptic Gregorian calendar and normalizes the + * caller's struct tm through gmtime_r(). */ +time_t mktime(struct tm *value) +{ + int year; + unsigned int month; + int64_t era; + unsigned int year_of_era; + unsigned int day_of_year; + unsigned int day_of_era; + int64_t days; + int64_t seconds; + time_t result; + if (value == 0 || value->tm_mon < 0 || value->tm_mon > 11 + || value->tm_mday < 1 || value->tm_mday > 31 + || value->tm_hour < 0 || value->tm_hour > 23 + || value->tm_min < 0 || value->tm_min > 59 + || value->tm_sec < 0 || value->tm_sec > 60) { + errno = EINVAL; + return (time_t) -1; + } + year = value->tm_year + 1900; + month = (unsigned int) value->tm_mon + 1u; + year -= month <= 2u; + era = (year >= 0 ? year : year - 399) / 400; + year_of_era = (unsigned int) (year - era * 400); + day_of_year = (153u * (month > 2u ? month - 3u : month + 9u) + + 2u) / 5u + (unsigned int) value->tm_mday - 1u; + day_of_era = year_of_era * 365u + year_of_era / 4u + - year_of_era / 100u + day_of_year; + days = era * 146097 + (int64_t) day_of_era - 719468; + seconds = days * 86400 + value->tm_hour * 3600 + + value->tm_min * 60 + value->tm_sec; + result = (time_t) seconds; + if (seconds >= 0) { + (void) gmtime_r(&result, value); + } + return result; +} + +float floorf(float value) +{ + return (float) floor((double) value); +} + +char *realpath(const char *path, char *resolved) +{ + char cwd[256]; + size_t path_size; + size_t cwd_size = 0; + if (path == 0 || *path == '\0') { + errno = ENOENT; + return 0; + } + path_size = strlen(path); + if (path[0] != '/') { + if (getcwd(cwd, sizeof(cwd)) == 0) { + return 0; + } + cwd_size = strlen(cwd); + } + if (resolved == 0) { + resolved = (char *) malloc(cwd_size + (cwd_size > 1 ? 1 : 0) + + path_size + 1); + if (resolved == 0) { + errno = ENOMEM; + return 0; + } + } + if (cwd_size != 0) { + memcpy(resolved, cwd, cwd_size); + if (cwd_size > 1) { + resolved[cwd_size++] = '/'; + } + } + memcpy(resolved + cwd_size, path, path_size + 1); + return resolved; +} + +int typephp_os_sscanf(const char *input, const char *format, ...) + __asm__("__isoc99_sscanf"); + +int typephp_os_sscanf(const char *input, const char *format, ...) +{ + va_list arguments; + int assigned = 0; + va_start(arguments, format); + while (*format != '\0') { + if (*format != '%') { + if (*input++ != *format++) { + break; + } + continue; + } + ++format; + if (*format == 'd' || *format == 'u') { + char *end = 0; + const int signed_value = *format == 'd'; + unsigned long value = strtoul(input, &end, 10); + if (end == input) { + break; + } + if (signed_value) { + *va_arg(arguments, int *) = (int) value; + } else { + *va_arg(arguments, unsigned int *) = (unsigned int) value; + } + input = end; + ++format; + ++assigned; + continue; + } + break; + } + va_end(arguments); + return assigned; +} + +int typephp_os_xpg_strerror_r(int error, char *buffer, size_t size) + __asm__("__xpg_strerror_r"); + +int typephp_os_xpg_strerror_r(int error, char *buffer, size_t size) +{ + const char *message = strerror(error); + const size_t length = strlen(message); + if (size == 0 || length >= size) { + return ERANGE; + } + memcpy(buffer, message, length + 1); + return 0; +} + +char *typephp_os_gnu_strerror_r(int error, char *buffer, size_t size) + __asm__("strerror_r"); + +char *typephp_os_gnu_strerror_r(int error, char *buffer, size_t size) +{ + return typephp_os_xpg_strerror_r(error, buffer, size) == 0 + ? buffer : strerror(error); +} + +char *__xpg_basename(char *path) +{ + char *last = path; + if (path == 0 || *path == '\0') { + return "."; + } + while (*path != '\0') { + if (*path == '/' && path[1] != '\0') { + last = path + 1; + } + ++path; + } + return last; +} diff --git a/examples/typephp-os/user/runtime/host.cc b/examples/typephp-os/user/runtime/host.cc index d3ffd455..87be3605 100644 --- a/examples/typephp-os/user/runtime/host.cc +++ b/examples/typephp-os/user/runtime/host.cc @@ -2,8 +2,10 @@ #include #include +#include #include #include +#include #include #include @@ -12,11 +14,6 @@ extern "C" char **environ; namespace { -/* Zend MM acquires aligned 2 MiB chunks while PHPX's native object GC also - * keeps process-lifetime metadata. A 48 MiB arena leaves enough headroom for - * the complete Nano module startup in the current 512 MiB QEMU machine. */ -constexpr std::uintptr_t arena_size = 48u * 1024u * 1024u; - [[noreturn]] void raw_exit(int status) { (void) syscall(SYS_exit, status); @@ -27,6 +24,16 @@ constexpr std::uintptr_t arena_size = 48u * 1024u * 1024u; } // namespace +/* Override the lightweight C runtime's default arena. Zend MM acquires + * aligned 2 MiB chunks while PHPX's native object GC also keeps + * process-lifetime metadata. */ +extern "C" { +std::size_t typephp_os_runtime_arena_size() +{ + return 48u * 1024u * 1024u; +} +} + extern "C" void typephp_os_write(const char *data, size_t size) { (void) syscall(SYS_write, STDOUT_FILENO, data, size); @@ -75,16 +82,31 @@ extern "C" void php_nano_host_sleep( } } -int main(int argc, char **argv) +extern "C" zend_result php_nano_host_random_bytes(void *bytes, std::size_t size) +{ + auto *output = static_cast(bytes); + while (size != 0) { + const auto result = getrandom(output, size, 0); + if (result <= 0) { + return FAILURE; + } + output += result; + size -= static_cast(result); + } + return SUCCESS; +} + +extern "C" std::uint64_t php_nano_host_random_seed() { - const auto begin = static_cast(syscall(SYS_brk, 0)); - const auto end = begin + arena_size; - if (begin == static_cast(-1) - || end < begin - || static_cast(syscall(SYS_brk, end)) != end) { - typephp_os_panic("unable to allocate the Nano runtime arena"); + std::uint64_t seed = 0; + if (php_nano_host_random_bytes(&seed, sizeof(seed)) != SUCCESS) { + typephp_os_panic("unable to obtain secure random bytes"); } - typephp_os_memory_init(reinterpret_cast(begin), arena_size); + return seed; +} + +int main(int argc, char **argv) +{ environ = argv + argc + 1; php_nano_set_cli_arguments(argc, argv); if (php_nano_startup_composer_extensions() != SUCCESS) { diff --git a/examples/typephp-os/user/runtime/network.c b/examples/typephp-os/user/runtime/network.c new file mode 100644 index 00000000..28159d2e --- /dev/null +++ b/examples/typephp-os/user/runtime/network.c @@ -0,0 +1,379 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +long syscall(long number, ...); + +int socket(int domain, int type, int protocol) +{ + return (int) syscall(SYS_socket, domain, type, protocol); +} + +int connect(int fd, const struct sockaddr *address, socklen_t length) +{ + return (int) syscall(SYS_connect, fd, address, length); +} + +int bind(int fd, const struct sockaddr *address, socklen_t length) +{ + return (int) syscall(SYS_bind, fd, address, length); +} + +ssize_t sendto(int fd, const void *buffer, size_t length, int flags, + const struct sockaddr *address, socklen_t address_length) +{ + return (ssize_t) syscall(SYS_sendto, fd, buffer, length, flags, + address, address_length); +} + +ssize_t recvfrom(int fd, void *buffer, size_t length, int flags, + struct sockaddr *address, socklen_t *address_length) +{ + return (ssize_t) syscall(SYS_recvfrom, fd, buffer, length, flags, + address, address_length); +} + +ssize_t send(int fd, const void *buffer, size_t length, int flags) +{ + return sendto(fd, buffer, length, flags, 0, 0); +} + +ssize_t recv(int fd, void *buffer, size_t length, int flags) +{ + return recvfrom(fd, buffer, length, flags, 0, 0); +} + +int shutdown(int fd, int how) +{ + return (int) syscall(SYS_shutdown, fd, how); +} + +int getsockname(int fd, struct sockaddr *address, socklen_t *length) +{ + return (int) syscall(SYS_getsockname, fd, address, length); +} + +int getpeername(int fd, struct sockaddr *address, socklen_t *length) +{ + return (int) syscall(SYS_getpeername, fd, address, length); +} + +int setsockopt(int fd, int level, int option, const void *value, socklen_t length) +{ + return (int) syscall(SYS_setsockopt, fd, level, option, value, length); +} + +int getsockopt(int fd, int level, int option, void *value, socklen_t *length) +{ + return (int) syscall(SYS_getsockopt, fd, level, option, value, length); +} + +int poll(struct pollfd *fds, nfds_t count, int timeout) +{ + return (int) syscall(SYS_poll, fds, count, timeout); +} + +int typephp_os_select(int count, void *read_set_pointer, void *write_set_pointer, + void *except_set_pointer, struct timeval *timeout) __asm__("select"); + +int typephp_os_select(int count, void *read_set_pointer, void *write_set_pointer, + void *except_set_pointer, struct timeval *timeout) +{ + enum { BITS_PER_WORD = (int) (sizeof(unsigned long) * 8), MAX_FDS = 1024 }; + unsigned long *read_set = (unsigned long *) read_set_pointer; + unsigned long *write_set = (unsigned long *) write_set_pointer; + unsigned long *except_set = (unsigned long *) except_set_pointer; + struct pollfd fds[MAX_FDS]; + int fd_map[MAX_FDS]; + int poll_count = 0; + int milliseconds = -1; + int ready; + int fd; + if (count < 0 || count > MAX_FDS) { + errno = EINVAL; + return -1; + } + if (timeout != 0) { + int64_t value = (int64_t) timeout->tv_sec * 1000 + + (timeout->tv_usec + 999) / 1000; + milliseconds = value > INT32_MAX ? INT32_MAX : (int) value; + } + for (fd = 0; fd < count; ++fd) { + const unsigned long mask = 1ul << (fd % BITS_PER_WORD); + short events = 0; + if (read_set != 0 && (read_set[fd / BITS_PER_WORD] & mask) != 0) { + events |= POLLIN; + } + if (write_set != 0 && (write_set[fd / BITS_PER_WORD] & mask) != 0) { + events |= POLLOUT; + } + if (except_set != 0 && (except_set[fd / BITS_PER_WORD] & mask) != 0) { + events |= POLLERR; + } + if (events != 0) { + fds[poll_count].fd = fd; + fds[poll_count].events = events; + fds[poll_count].revents = 0; + fd_map[poll_count++] = fd; + } + } + if (read_set != 0) { + memset(read_set, 0, ((size_t) count + BITS_PER_WORD - 1) / BITS_PER_WORD + * sizeof(unsigned long)); + } + if (write_set != 0) { + memset(write_set, 0, ((size_t) count + BITS_PER_WORD - 1) / BITS_PER_WORD + * sizeof(unsigned long)); + } + if (except_set != 0) { + memset(except_set, 0, ((size_t) count + BITS_PER_WORD - 1) / BITS_PER_WORD + * sizeof(unsigned long)); + } + ready = poll(fds, (nfds_t) poll_count, milliseconds); + if (ready <= 0) { + return ready; + } + ready = 0; + for (fd = 0; fd < poll_count; ++fd) { + const int selected = fd_map[fd]; + const unsigned long mask = 1ul << (selected % BITS_PER_WORD); + int marked = 0; + if (read_set != 0 && (fds[fd].revents & (POLLIN | POLLHUP)) != 0) { + read_set[selected / BITS_PER_WORD] |= mask; + marked = 1; + } + if (write_set != 0 && (fds[fd].revents & POLLOUT) != 0) { + write_set[selected / BITS_PER_WORD] |= mask; + marked = 1; + } + if (except_set != 0 && (fds[fd].revents & (POLLERR | POLLNVAL)) != 0) { + except_set[selected / BITS_PER_WORD] |= mask; + marked = 1; + } + ready += marked; + } + return ready; +} + +static int parse_ipv4(const char *text, uint32_t *address) +{ + uint32_t octets[4] = {0, 0, 0, 0}; + unsigned int part = 0; + int digits = 0; + if (text == 0 || *text == '\0') { + return 0; + } + while (*text != '\0') { + if (*text >= '0' && *text <= '9') { + octets[part] = octets[part] * 10u + (unsigned int) (*text - '0'); + if (++digits > 3 || octets[part] > 255u) { + return 0; + } + } else if (*text == '.' && digits != 0 && part < 3) { + ++part; + digits = 0; + } else { + return 0; + } + ++text; + } + if (part != 3 || digits == 0) { + return 0; + } + *address = octets[0] | (octets[1] << 8u) + | (octets[2] << 16u) | (octets[3] << 24u); + return 1; +} + +int inet_pton(int family, const char *text, void *address) +{ + uint32_t value; + if (family != AF_INET) { + errno = EAFNOSUPPORT; + return -1; + } + if (address == 0 || !parse_ipv4(text, &value)) { + return 0; + } + *(uint32_t *) address = value; + return 1; +} + +int inet_aton(const char *text, struct in_addr *address) +{ + return address != 0 && inet_pton(AF_INET, text, address) == 1; +} + +in_addr_t inet_addr(const char *text) +{ + in_addr_t address; + return parse_ipv4(text, &address) ? address : INADDR_NONE; +} + +static char *append_octet(char *output, unsigned int value) +{ + if (value >= 100) { + *output++ = (char) ('0' + value / 100u); + value %= 100u; + *output++ = (char) ('0' + value / 10u); + } else if (value >= 10) { + *output++ = (char) ('0' + value / 10u); + } + *output++ = (char) ('0' + value % 10u); + return output; +} + +const char *inet_ntop(int family, const void *address, char *text, + socklen_t length) +{ + char buffer[INET_ADDRSTRLEN]; + char *output = buffer; + const unsigned char *bytes = (const unsigned char *) address; + unsigned int index; + if (family != AF_INET) { + errno = EAFNOSUPPORT; + return 0; + } + if (address == 0 || text == 0) { + errno = EFAULT; + return 0; + } + for (index = 0; index < 4; ++index) { + output = append_octet(output, bytes[index]); + if (index != 3) { + *output++ = '.'; + } + } + *output = '\0'; + if ((size_t) (output - buffer + 1) > length) { + errno = ENOSPC; + return 0; + } + memcpy(text, buffer, (size_t) (output - buffer + 1)); + return text; +} + +static int service_port(const char *service, uint16_t *port) +{ + unsigned int value = 0; + if (service == 0) { + *port = 0; + return 1; + } + if (strcmp(service, "http") == 0) { + *port = 80; + return 1; + } + if (strcmp(service, "https") == 0) { + *port = 443; + return 1; + } + if (*service == '\0') { + return 0; + } + while (*service != '\0') { + if (*service < '0' || *service > '9') { + return 0; + } + value = value * 10u + (unsigned int) (*service++ - '0'); + if (value > 65535u) { + return 0; + } + } + *port = (uint16_t) value; + return 1; +} + +int getaddrinfo(const char *node, const char *service, + const struct addrinfo *hints, struct addrinfo **result) +{ + struct addrinfo *entry; + struct sockaddr_in *address; + uint16_t port; + uint32_t ipv4; + int flags = hints != 0 ? hints->ai_flags : 0; + int family = hints != 0 ? hints->ai_family : AF_UNSPEC; + int socktype = hints != 0 ? hints->ai_socktype : 0; + int protocol = hints != 0 ? hints->ai_protocol : 0; + const int supported_flags = AI_PASSIVE | AI_CANONNAME | AI_NUMERICHOST + | AI_ADDRCONFIG | AI_NUMERICSERV; + if (result == 0) { + return EAI_FAIL; + } + *result = 0; + if ((flags & ~supported_flags) != 0) { + return EAI_BADFLAGS; + } + if (family != AF_UNSPEC && family != AF_INET) { + return EAI_FAMILY; + } + if (socktype != 0 && socktype != SOCK_STREAM) { + return EAI_SOCKTYPE; + } + if (protocol != 0 && protocol != IPPROTO_TCP) { + return EAI_SERVICE; + } + if (!service_port(service, &port)) { + return EAI_SERVICE; + } + if (node == 0) { + ipv4 = (flags & AI_PASSIVE) != 0 ? INADDR_ANY : INADDR_LOOPBACK; + } else if (!parse_ipv4(node, &ipv4)) { + long status; + if ((flags & AI_NUMERICHOST) != 0) { + return EAI_NONAME; + } + status = syscall(SYS_typephp_dns_resolve_ipv4, node, &ipv4); + if (status != 0) { + return errno == ETIMEDOUT || errno == EAGAIN ? EAI_AGAIN : EAI_NONAME; + } + } + entry = (struct addrinfo *) calloc(1, + sizeof(*entry) + sizeof(*address)); + if (entry == 0) { + return EAI_MEMORY; + } + address = (struct sockaddr_in *) (entry + 1); + address->sin_family = AF_INET; + address->sin_port = htons(port); + address->sin_addr.s_addr = ipv4; + entry->ai_flags = flags; + entry->ai_family = AF_INET; + entry->ai_socktype = socktype != 0 ? socktype : SOCK_STREAM; + entry->ai_protocol = protocol != 0 ? protocol : IPPROTO_TCP; + entry->ai_addrlen = sizeof(*address); + entry->ai_addr = (struct sockaddr *) address; + entry->ai_next = 0; + *result = entry; + return 0; +} + +void freeaddrinfo(struct addrinfo *result) +{ + free(result); +} + +const char *gai_strerror(int error) +{ + switch (error) { + case 0: return "Success"; + case EAI_BADFLAGS: return "Invalid flags"; + case EAI_NONAME: return "Name or service not known"; + case EAI_AGAIN: return "Temporary failure in name resolution"; + case EAI_FAIL: return "Non-recoverable name resolution failure"; + case EAI_FAMILY: return "Address family not supported"; + case EAI_SOCKTYPE: return "Socket type not supported"; + case EAI_SERVICE: return "Service not supported"; + case EAI_MEMORY: return "Memory allocation failure"; + case EAI_SYSTEM: return "System error"; + default: return "Unknown address resolution error"; + } +} diff --git a/examples/typephp-os/user/runtime/posix.c b/examples/typephp-os/user/runtime/posix.c index 28602ae5..f016363e 100644 --- a/examples/typephp-os/user/runtime/posix.c +++ b/examples/typephp-os/user/runtime/posix.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -154,6 +155,29 @@ int nanosleep(const struct timespec *duration, struct timespec *remaining) return (int) syscall(TYPEPHP_SYS_NANOSLEEP, duration, remaining); } +ssize_t getrandom(void *buffer, size_t length, unsigned int flags) +{ + return (ssize_t) syscall(TYPEPHP_SYS_GETRANDOM, buffer, length, flags); +} + +int getentropy(void *buffer, size_t length) +{ + unsigned char *output = (unsigned char *) buffer; + if (length > 256) { + errno = EIO; + return -1; + } + while (length != 0) { + ssize_t result = getrandom(output, length, 0); + if (result <= 0) { + return -1; + } + output += result; + length -= (size_t) result; + } + return 0; +} + int uname(struct utsname *value) { return (int) syscall(TYPEPHP_SYS_UNAME, value); diff --git a/examples/typephp-os/user/runtime/start.c b/examples/typephp-os/user/runtime/start.c new file mode 100644 index 00000000..4f8710fe --- /dev/null +++ b/examples/typephp-os/user/runtime/start.c @@ -0,0 +1,29 @@ +#include +#include +#include + +#include + +long syscall(long number, ...); +int main(int argc, char **argv); + +/* Small C utilities only need a modest libc arena. A heavier runtime can + * override this weak definition without giving C and TypePHP programs + * separate crt0 implementations. */ +__attribute__((weak, noinline)) size_t typephp_os_runtime_arena_size(void) +{ + return 2u * 1024u * 1024u; +} + +int typephp_os_runtime_start(int argc, char **argv) +{ + const size_t arena_size = typephp_os_runtime_arena_size(); + const uintptr_t begin = (uintptr_t) syscall(SYS_brk, 0); + const uintptr_t end = begin + arena_size; + if (begin == UINTPTR_MAX || end < begin + || (uintptr_t) syscall(SYS_brk, end) != end) { + typephp_os_panic("unable to allocate the userspace runtime arena"); + } + typephp_os_memory_init((void *) begin, arena_size); + return main(argc, argv); +} diff --git a/examples/typephp-os/user/runtime/stdio.c b/examples/typephp-os/user/runtime/stdio.c new file mode 100644 index 00000000..6db1172b --- /dev/null +++ b/examples/typephp-os/user/runtime/stdio.c @@ -0,0 +1,207 @@ +/* Minimal descriptor-backed stdio used by OpenSSL certificate loading. */ + +#include +#include +#include +#include +#include +#include + +typedef struct { + uint32_t magic; + int fd; + int error; + int eof; +} typephp_file; + +enum { TYPEPHP_FILE_MAGIC = 0x54504649u }; + +static typephp_file *private_file(FILE *stream) +{ + typephp_file *file = (typephp_file *) stream; + return file != 0 && file->magic == TYPEPHP_FILE_MAGIC ? file : 0; +} + +static int mode_flags(const char *mode) +{ + int flags; + if (mode == 0 || *mode == '\0') { + return -1; + } + flags = *mode == 'r' ? O_RDONLY + : (*mode == 'w' ? O_WRONLY | O_CREAT | O_TRUNC + : (*mode == 'a' ? O_WRONLY | O_CREAT | O_APPEND : -1)); + if (flags >= 0 && mode[1] == '+') { + flags = (flags & ~(O_RDONLY | O_WRONLY)) | O_RDWR; + } + return flags; +} + +FILE *fopen(const char *path, const char *mode) +{ + const int flags = mode_flags(mode); + typephp_file *file; + int fd; + if (flags < 0) { + errno = EINVAL; + return 0; + } + fd = open(path, flags, 0666); + if (fd < 0) { + return 0; + } + file = (typephp_file *) malloc(sizeof(*file)); + if (file == 0) { + close(fd); + errno = ENOMEM; + return 0; + } + file->magic = TYPEPHP_FILE_MAGIC; + file->fd = fd; + file->error = 0; + file->eof = 0; + return (FILE *) file; +} + +FILE *fopen64(const char *path, const char *mode) +{ + return fopen(path, mode); +} + +int fclose(FILE *stream) +{ + typephp_file *file = private_file(stream); + int status; + if (file == 0) { + errno = EBADF; + return EOF; + } + status = close(file->fd); + file->magic = 0; + free(file); + return status; +} + +size_t fread(void *buffer, size_t size, size_t count, FILE *stream) +{ + typephp_file *file = private_file(stream); + size_t bytes; + ssize_t received; + if (file == 0 || (size != 0 && count > SIZE_MAX / size)) { + errno = EINVAL; + return 0; + } + bytes = size * count; + received = read(file->fd, buffer, bytes); + if (received < 0) { + file->error = 1; + return 0; + } + if ((size_t) received < bytes) { + file->eof = 1; + } + return size == 0 ? 0 : (size_t) received / size; +} + +char *fgets(char *buffer, int size, FILE *stream) +{ + typephp_file *file = private_file(stream); + int offset = 0; + if (file == 0 || buffer == 0 || size <= 0) { + errno = EINVAL; + return 0; + } + while (offset + 1 < size) { + ssize_t received = read(file->fd, buffer + offset, 1); + if (received < 0) { + file->error = 1; + return offset == 0 ? 0 : buffer; + } + if (received == 0) { + file->eof = 1; + break; + } + if (buffer[offset++] == '\n') { + break; + } + } + if (offset == 0) { + return 0; + } + buffer[offset] = '\0'; + return buffer; +} + +int ferror(FILE *stream) +{ + typephp_file *file = private_file(stream); + return file != 0 ? file->error : 1; +} + +int feof(FILE *stream) +{ + typephp_file *file = private_file(stream); + return file != 0 ? file->eof : 0; +} + +void clearerr(FILE *stream) +{ + typephp_file *file = private_file(stream); + if (file != 0) { + file->error = 0; + file->eof = 0; + } +} + +int fseeko(FILE *stream, off_t offset, int whence) +{ + typephp_file *file = private_file(stream); + if (file == 0) { + errno = EBADF; + return -1; + } + file->eof = 0; + return lseek(file->fd, offset, whence) < 0 ? -1 : 0; +} + +off_t ftello(FILE *stream) +{ + typephp_file *file = private_file(stream); + if (file == 0) { + errno = EBADF; + return -1; + } + return lseek(file->fd, 0, SEEK_CUR); +} + +int fseek(FILE *stream, long offset, int whence) +{ + return fseeko(stream, (off_t) offset, whence); +} + +long ftell(FILE *stream) +{ + return (long) ftello(stream); +} + +void rewind(FILE *stream) +{ + (void) fseeko(stream, 0, SEEK_SET); + clearerr(stream); +} + +void setbuf(FILE *stream, char *buffer) +{ + (void) stream; + (void) buffer; +} + +size_t __fread_chk(void *buffer, size_t buffer_size, size_t size, + size_t count, FILE *stream) +{ + if (size != 0 && count > buffer_size / size) { + errno = ERANGE; + return 0; + } + return fread(buffer, size, count, stream); +} diff --git a/phpunit/src/CompilationStatisticsTest.php b/phpunit/src/CompilationStatisticsTest.php index 77f9fc6d..d6f2cf8a 100644 --- a/phpunit/src/CompilationStatisticsTest.php +++ b/phpunit/src/CompilationStatisticsTest.php @@ -63,17 +63,31 @@ final class CompilationStatisticsTest extends BaseTest self::assertSame([], $selection->features); } - public function testDynamicFunctionCallFallsBackToAllExtensions(): void + public function testDynamicFunctionCallFallsBackToBuiltInExtensionsOnly(): void { $statistics = new CompilationStatistics(); $statistics->begin(); $statistics->record(CompilationStatistics::DYNAMIC_CAPABILITIES, 'function-call'); $statistics->finish(); - $selection = (new NanoExtensionSelector())->select($statistics, ['json', 'standard']); + $selection = (new NanoExtensionSelector())->select( + $statistics, + ['curl', 'json', 'openssl', 'standard'], + ); self::assertTrue($selection->completeFallback); self::assertSame(['json', 'standard'], $selection->extensions); + + $composition = (new NanoSourceComposer())->compose( + sys_get_temp_dir() . '/typephp-nano-dynamic-fallback-test', + 'dynamic_fallback_test', + false, + $statistics, + ); + self::assertFalse($this->containsSource($composition['packageSources'], 'main/network.c')); + self::assertFalse($this->containsSource($composition['packageSources'], 'main/streams/xp_socket.c')); + self::assertFalse($this->containsSource($composition['packageSources'], 'ext/curl/interface.c')); + self::assertFalse($this->containsSource($composition['packageSources'], 'ext/openssl/openssl.c')); } public function testDateRemainsWholeWhileStandardUsesFineGrainedFeatures(): void diff --git a/src/Build/NanoExtensionSelector.php b/src/Build/NanoExtensionSelector.php index 057a2475..0f061184 100644 --- a/src/Build/NanoExtensionSelector.php +++ b/src/Build/NanoExtensionSelector.php @@ -248,8 +248,14 @@ final class NanoExtensionSelector $dynamic = array_keys($statistics->get(CompilationStatistics::DYNAMIC_CAPABILITIES)); if ($dynamic !== []) { + // Dynamic calls can only imply the built-in Nano surface. Optional + // php-src and PIE extensions must still be selected by a statically + // observed symbol (or, in the future, explicit project metadata). + // Otherwise adding an extension to php-nano silently bloats every + // program that contains any dynamic call. + $builtIn = array_intersect_key($available, self::EXTENSION_NAMES); return new NanoExtensionSelection( - array_keys($available), + array_keys($builtIn), true, [], array_map( @@ -396,6 +402,9 @@ final class NanoExtensionSelector if (!is_string($extension)) { return null; } - return self::EXTENSION_NAMES[strtolower($extension)] ?? null; + $extension = strtolower($extension); + return preg_match('/^[a-z][a-z0-9_]*$/', $extension) === 1 + ? $extension + : null; } } diff --git a/src/Build/NanoSourceComposer.php b/src/Build/NanoSourceComposer.php index e6d8bb9a..b107a277 100644 --- a/src/Build/NanoSourceComposer.php +++ b/src/Build/NanoSourceComposer.php @@ -42,9 +42,21 @@ final class NanoSourceComposer } $packages = array_values($packagesByName); + $availableExtensions = []; + foreach ($packages as $package) { + if ($package->extensionName !== null) { + $availableExtensions[] = $package->extensionName; + } + foreach ($package->components as $component) { + $availableExtensions[] = $component->extension; + } + } $selection = $statistics === null ? null - : (new NanoExtensionSelector())->select($statistics); + : (new NanoExtensionSelector())->select( + $statistics, + array_values(array_unique($availableExtensions)), + ); $activeComponents = $selection === null ? null : $this->selectComponents($packages, $selection); @@ -178,6 +190,18 @@ final class NanoSourceComposer } } foreach ($extensions as $extension) { + if ($activeComponents !== null && $extension->components !== []) { + $packageActive = false; + foreach ($extension->components as $component) { + if (isset($activeComponents[$extension->name][$component->name])) { + $packageActive = true; + break; + } + } + if (!$packageActive) { + continue; + } + } $moduleEntry = $extension->extensionModuleEntry; $declarations[$moduleEntry] = "extern \"C\" zend_module_entry {$moduleEntry};"; $entries[$moduleEntry] = " &{$moduleEntry},"; diff --git a/src/CompilerBase.php b/src/CompilerBase.php index 38b708ef..99718d8e 100644 --- a/src/CompilerBase.php +++ b/src/CompilerBase.php @@ -327,7 +327,6 @@ class CompilerBase implements PropertyAccessContext 'pcntl_', 'posix_', 'socket_', - 'curl_', 'ftp_', 'opcache_', ];