Add network features for typephp os and support curl+openssl

master
韩天峰 4 weeks ago
parent a1782233ca
commit 453543e0c8
  1. 3
      examples/typephp-os/.gitignore
  2. 43
      examples/typephp-os/Makefile
  3. 29
      examples/typephp-os/README.md
  4. 35
      examples/typephp-os/THIRDPARTY.md
  5. 4
      examples/typephp-os/boot/kernel64-entry.S
  6. 17
      examples/typephp-os/composer.json
  7. 179
      examples/typephp-os/kernel/core/abi/libc.c
  8. 13
      examples/typephp-os/kernel/core/abi/typephp_os_syscall.h
  9. 3
      examples/typephp-os/kernel/core/abi/unimplemented.S
  10. 23
      examples/typephp-os/kernel/core/net/arch/cc.h
  11. 326
      examples/typephp-os/kernel/core/net/e1000.c
  12. 12
      examples/typephp-os/kernel/core/net/e1000.h
  13. 70
      examples/typephp-os/kernel/core/net/lwipopts.h
  14. 209
      examples/typephp-os/kernel/core/net/network.c
  15. 12
      examples/typephp-os/kernel/core/net/network.h
  16. 183
      examples/typephp-os/kernel/core/process.c
  17. 648
      examples/typephp-os/kernel/core/socket.c
  18. 37
      examples/typephp-os/kernel/core/socket.h
  19. 42
      examples/typephp-os/kernel/core/vm.c
  20. 1
      examples/typephp-os/kernel/core/vm.h
  21. 30
      examples/typephp-os/project.yml
  22. 42
      examples/typephp-os/run.sh
  23. 135
      examples/typephp-os/tools/build-network-thirdparty.sh
  24. 30
      examples/typephp-os/tools/fetch-curl.sh
  25. 32
      examples/typephp-os/tools/fetch-lwip.sh
  26. 31
      examples/typephp-os/tools/fetch-nghttp2.sh
  27. 31
      examples/typephp-os/tools/fetch-openssl.sh
  28. 4
      examples/typephp-os/tools/fetch-thirdparty.sh
  29. 37
      examples/typephp-os/tools/test-qemu.sh
  30. 93
      examples/typephp-os/user/cmd/nettest.c
  31. 4
      examples/typephp-os/user/cmd/sh.c
  32. 14
      examples/typephp-os/user/cmd/systest.c
  33. 16
      examples/typephp-os/user/include/arpa/inet.h
  34. 27
      examples/typephp-os/user/include/errno.h
  35. 40
      examples/typephp-os/user/include/netdb.h
  36. 50
      examples/typephp-os/user/include/netinet/in.h
  37. 23
      examples/typephp-os/user/include/poll.h
  38. 11
      examples/typephp-os/user/include/string.h
  39. 14
      examples/typephp-os/user/include/sys/random.h
  40. 66
      examples/typephp-os/user/include/sys/socket.h
  41. 13
      examples/typephp-os/user/include/sys/syscall.h
  42. 5
      examples/typephp-os/user/nano/free.yml
  43. 5
      examples/typephp-os/user/nano/hello.yml
  44. 36
      examples/typephp-os/user/nano/https.php
  45. 92
      examples/typephp-os/user/nano/https.yml
  46. 4
      examples/typephp-os/user/runtime/crt0.S
  47. 351
      examples/typephp-os/user/runtime/glibc.c
  48. 48
      examples/typephp-os/user/runtime/host.cc
  49. 379
      examples/typephp-os/user/runtime/network.c
  50. 24
      examples/typephp-os/user/runtime/posix.c
  51. 29
      examples/typephp-os/user/runtime/start.c
  52. 207
      examples/typephp-os/user/runtime/stdio.c
  53. 18
      phpunit/src/CompilationStatisticsTest.php
  54. 13
      src/Build/NanoExtensionSelector.php
  55. 26
      src/Build/NanoSourceComposer.php
  56. 1
      src/CompilerBase.php

@ -1,3 +1,6 @@
/build/
*.o
/thirdparty/
/vendor
/.idea
/composer.lock

@ -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

@ -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

@ -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: <https://github.com/lwip-tcpip/lwip>
- 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: <https://github.com/JuliaMath/openlibm>

@ -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

@ -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"
}
}

@ -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;

@ -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

@ -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

@ -0,0 +1,23 @@
#ifndef TYPEPHP_OS_LWIP_ARCH_CC_H
#define TYPEPHP_OS_LWIP_ARCH_CC_H
#include <stdint.h>
#include <limits.h>
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

@ -0,0 +1,326 @@
/* Intel 82540EM (QEMU e1000) driver using its indirect I/O BAR. */
#include "e1000.h"
#include "../vm.h"
#include <stdint.h>
#include <string.h>
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;
}

@ -0,0 +1,12 @@
#ifndef TYPEPHP_OS_E1000_H
#define TYPEPHP_OS_E1000_H
#include <stddef.h>
#include <stdint.h>
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

@ -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

@ -0,0 +1,209 @@
#include "network.h"
#include "e1000.h"
#include <stdint.h>
#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 <errno.h>
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;
}

@ -0,0 +1,12 @@
#ifndef TYPEPHP_OS_NETWORK_H
#define TYPEPHP_OS_NETWORK_H
#include <stdint.h>
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

@ -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 <errno.h>
#include <fcntl.h>
@ -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;

@ -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 <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <string.h>
#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]);
}
}
}

@ -0,0 +1,37 @@
#ifndef TYPEPHP_OS_KERNEL_SOCKET_H
#define TYPEPHP_OS_KERNEL_SOCKET_H
#include <stddef.h>
#include <stdint.h>
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

@ -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");

@ -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(

@ -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

@ -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 \
"$@"

@ -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}"

@ -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}"

@ -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}"

@ -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}"

@ -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}"

@ -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"

@ -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

@ -0,0 +1,93 @@
#include <errno.h>
#include <netdb.h>
#include <netinet/in.h>
#include <string.h>
#include <sys/random.h>
#include <sys/socket.h>
#include <unistd.h>
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;
}

@ -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;

@ -3,6 +3,7 @@
#include <stddef.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <time.h>
@ -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;

@ -0,0 +1,16 @@
#ifndef TYPEPHP_OS_USER_ARPA_INET_H
#define TYPEPHP_OS_USER_ARPA_INET_H
#include <netinet/in.h>
#include <sys/socket.h>
#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

@ -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

@ -0,0 +1,40 @@
#ifndef TYPEPHP_OS_USER_NETDB_H
#define TYPEPHP_OS_USER_NETDB_H
#include <sys/socket.h>
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

@ -0,0 +1,50 @@
#ifndef TYPEPHP_OS_USER_NETINET_IN_H
#define TYPEPHP_OS_USER_NETINET_IN_H
#include <stdint.h>
#include <sys/socket.h>
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

@ -0,0 +1,23 @@
#ifndef TYPEPHP_OS_USER_POLL_H
#define TYPEPHP_OS_USER_POLL_H
#include <sys/types.h>
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

@ -4,6 +4,17 @@
#include <stddef.h>
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

@ -0,0 +1,14 @@
#ifndef TYPEPHP_OS_USER_SYS_RANDOM_H
#define TYPEPHP_OS_USER_SYS_RANDOM_H
#include <stddef.h>
#include <sys/types.h>
#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

@ -0,0 +1,66 @@
#ifndef TYPEPHP_OS_USER_SYS_SOCKET_H
#define TYPEPHP_OS_USER_SYS_SOCKET_H
#include <stddef.h>
#include <stdint.h>
#include <sys/types.h>
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

@ -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, ...);

@ -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

@ -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

@ -0,0 +1,36 @@
<?php
function main(int $argc, array $argv): void
{
$url = 'https://httpcan.org/get';
$handle = curl_init($url);
if ($handle === false) {
echo "curl_init failed\n";
return;
}
curl_setopt($handle, CURLOPT_RETURNTRANSFER, true);
curl_setopt($handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2TLS);
curl_setopt($handle, CURLOPT_NOSIGNAL, true);
curl_setopt($handle, CURLOPT_CAINFO, '/CACERT.PEM');
curl_setopt($handle, CURLOPT_CONNECTTIMEOUT, 10);
curl_setopt($handle, CURLOPT_TIMEOUT, 20);
curl_setopt($handle, CURLOPT_USERAGENT, 'TypePHP-OS/0.1');
$body = curl_exec($handle);
if ($body === false) {
echo 'HTTPS request failed: ', curl_error($handle), "\n";
return;
}
$status = curl_getinfo($handle, CURLINFO_RESPONSE_CODE);
$version = curl_getinfo($handle, CURLINFO_HTTP_VERSION);
$curlVersion = curl_version();
$digest = openssl_digest('TypePHP-OS', 'sha256');
echo 'GET ', $url, "\n";
echo 'HTTP status: ', (string) $status, "\n";
echo 'HTTP version: ', $version === CURL_HTTP_VERSION_2_0 ? '2' : (string) $version, "\n";
echo 'TLS backend: ', (string) $curlVersion['ssl_version'], "\n";
echo 'OpenSSL SHA-256: ', (string) $digest, "\n";
echo $body, "\n";
}

@ -0,0 +1,92 @@
name: typephp_os_user_https
mode: bin
sources:
- https.php
build-dir: ../../build/generated-user-https
output: ../../build/https.elf
cxx-std: c++17
include-paths:
- ../../kernel/core/abi
- ../../thirdparty/openlibm/include
- ../../thirdparty/openlibm/src
- ../../thirdparty/openlibm/amd64
- ../../build/network-thirdparty/install/include
defines:
- _DEFAULT_SOURCE=1
- HAVE_REALLOCARRAY=1
- HAVE_SYS_SOCKET_H=1
- HAVE_NETINET_IN_H=1
- HAVE_POLL=1
- HAVE_POLL_H=1
- HAVE_GETADDRINFO=1
- HAVE_GAI_STRERROR=1
- HAVE_INET_PTON=1
- HAVE_SHUTDOWN=1
- HAVE_STRUCT_TM_TM_GMTOFF=1
- TYPEPHP_NO_MAIN=1
- PHPX_NO_EXCEPTION=1
- PHPX_NO_RTTI=1
- PHP_NANO_NO_LIBC=1
- HAVE_SYS_UTSNAME_H=1
- HAVE_SYS_TYPES_H=1
- HAVE_SYS_TIME_H=1
- HAVE_UNISTD_H=1
- HAVE_TLS12=1
- HAVE_TLS13=1
- HAVE_TLS_ALPN=1
- HAVE_TLS_SNI=1
- CURL_STATICLIB=1
- __NO_CTYPE=1
- ZEND_MM_ERROR=0
- ZEND_MM_CUSTOM=0
- ASSEMBLER=1
- __BSD_VISIBLE=1
cxx-flags:
- -O2
- -U_FORTIFY_SOURCE
- -D_FORTIFY_SOURCE=0
- -ffreestanding
- -fno-builtin
- -fno-stack-protector
- -fno-pic
- -fno-pie
- -m64
- -mno-red-zone
- -mcmodel=small
- -fno-rtti
- -fno-threadsafe-statics
c-flags:
- -O2
- -U_FORTIFY_SOURCE
- -D_FORTIFY_SOURCE=0
- -ffreestanding
- -fno-builtin
- -fno-stack-protector
- -fno-pic
- -fno-pie
- -m64
- -mno-red-zone
- -mcmodel=small
asm-flags:
- -m64
- -mno-red-zone
- -mcmodel=small
ld-flags:
- -nostdlib
- -no-pie
- -Wl,-m,elf_x86_64
- -Wl,--build-id=none
- -Wl,-T,user/runtime/linker.ld
- -Wl,--defsym,USER_BASE=0x41000000
link-paths:
- ../../build/network-thirdparty/install/lib
- ../../build
link-libs:
- curl
- nghttp2
- ssl
- crypto
- ssl
- crypto
- typephp-os
- compiler-rt-builtins

@ -3,7 +3,7 @@
.global _start
.type _start, @function
.extern main
.extern typephp_os_runtime_start
_start:
# Linux-compatible process entry stack:
# argc, argv[], NULL, envp[], NULL, auxv[]
@ -12,7 +12,7 @@ _start:
lea 8(%rsp), %rsi
lea 16(%rsp,%rdi,8), %rdx
and $-16, %rsp
call main
call typephp_os_runtime_start
mov %eax, %edi
# Linux x86-64 exit_group(2); TypePHP-OS keeps the same number and ABI.

@ -0,0 +1,351 @@
/*
+----------------------------------------------------------------------+
| TypePHP OS |
+----------------------------------------------------------------------+
| Small glibc ABI surface required by hosted-built static libraries. |
| SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
*/
#include <errno.h>
#include <math.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
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;
}

@ -2,8 +2,10 @@
#include <typephp_os_abi.h>
#include <cstdint>
#include <cstddef>
#include <cstring>
#include <sys/syscall.h>
#include <sys/random.h>
#include <time.h>
#include <unistd.h>
@ -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<unsigned char *>(bytes);
while (size != 0) {
const auto result = getrandom(output, size, 0);
if (result <= 0) {
return FAILURE;
}
output += result;
size -= static_cast<std::size_t>(result);
}
return SUCCESS;
}
extern "C" std::uint64_t php_nano_host_random_seed()
{
const auto begin = static_cast<std::uintptr_t>(syscall(SYS_brk, 0));
const auto end = begin + arena_size;
if (begin == static_cast<std::uintptr_t>(-1)
|| end < begin
|| static_cast<std::uintptr_t>(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<void *>(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) {

@ -0,0 +1,379 @@
#include <arpa/inet.h>
#include <errno.h>
#include <netdb.h>
#include <netinet/in.h>
#include <poll.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <sys/time.h>
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";
}
}

@ -4,6 +4,7 @@
#include <stdint.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/random.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/time.h>
@ -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);

@ -0,0 +1,29 @@
#include <stdint.h>
#include <stddef.h>
#include <sys/syscall.h>
#include <typephp_os_abi.h>
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);
}

@ -0,0 +1,207 @@
/* Minimal descriptor-backed stdio used by OpenSSL certificate loading. */
#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
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);
}

@ -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

@ -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;
}
}

@ -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},";

@ -327,7 +327,6 @@ class CompilerBase implements PropertyAccessContext
'pcntl_',
'posix_',
'socket_',
'curl_',
'ftp_',
'opcache_',
];

Loading…
Cancel
Save