8 Commits
Author SHA1 Message Date
velzieandClaude Opus 5.5 a6e7c17f9e Restructure: hosts/, page/, ports/, site/, toolchain/, wine/, scripts/
The tree by what each part is, behind the top-level Makefile:
- shims/ -> hosts/ (the browser, kernel, native and Wayland hosts)
- web/ -> page/ (the page's source, build and serve), ports/mesa,
  ports/xwayland and ports/tint (the ports the page loads), site/ (static
  sites and the eject page)
- tools/gexcc -> toolchain/gexcc, with the sysroot and busybox builds from
  tools/wine-wasm in toolchain/sysroot and toolchain/busybox, and one
  toolchain/pins (musl, the LP64 musl, LLVM, busybox)
- tools/wine-wasm -> wine/: dist.sh (was wine64-dist.sh), wine.pins (64-bit
  Wine, and the 32-bit tree its wineserver builds from), libs.pins (the Unix
  libraries), the 32-bit-only pieces in wine/legacy32 (its own pins and
  legacy.mk, which wine/Makefile includes)
- tools/*.py and tools/*.sh -> scripts/ (the GL generators in scripts/gl)
- wine32/src.sh is scripts/fetch-pinned.sh, which takes the trees the old
  script marked as its own

References follow the moves: paths from the tree's root by the map, paths
from a script's own directory by where their target went.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_e0aa53aa95eb4b86910519b4d14812a5
2026-11-04 17:30:24 -05:00
velzieandClaude Opus 5.5 54eea80307 runtime: Filesystem is abstract and its subclass is where the files' contents are kept: MemoryFilesystem (the kernel's memory, with an optional max), OPFSFilesystem (a block store in the origin private file system behind a bounded cache), or a page's own store
The kernel's block store (gex_kernel::vfs::blocks): one store of 64 KiB
blocks addressed by offset, a write-back cache of `cache` MiB, files of
16 KiB or less inline. The host reaches it through `block_io`: an
OPFSFilesystem's store Worker with a sync access handle (the single-thread
build's system Worker holds the handle itself), or a page's own store's
read/write/size/truncate/flush served from the page through the slots
(the default build only; the single-thread build refuses it at boot).
Every count a sync access handle returns is checked: a short write or an
error code is EIO, a grow past the quota ENOSPC.

`new Filesystem(sys)` throws, as does a subclass that leaves its store's
methods out. Every caller now chooses: the tests MemoryFilesystem,
runProgram and app.js `start()` the `filesystem` option (default memory,
gex-eject's "start" may set it, eject-site's site.json records it).
MemoryFilesystem's max is the kernel's TMPFS_MAX: a reservation under it
doubles only while that fits, and a file's room is freed when the file
goes. The store is emptied at boot: nothing is kept across loads yet.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_e0aa53aa95eb4b86910519b4d14812a5
2026-11-02 17:54:37 -05:00
velzieandClaude Opus 5.5 68ad6154fd runtime: new System({ arch }), and the runtime loads while the tree does
A page that knows its programs' architecture (`'x86'` or `'aarch64'`, as
describe() names them) now says so, and boot starts the kernel, shim and
seed loading before it waits for the filesystem's layers: the
single-thread build's system Worker gets the switches and architecture at
once (`prepare`, the first half of its `start`), the default build fetches
the kernel. Without it the architecture is read from the tree as before,
once its layers are in. The preset (run-program.js) passes the page's.

Timed in Chromium on worldmachine, 20 alternated rounds each, with the
single-thread package:
- a page that adds a zstd tar by URL, boots and runs `sh -c 'echo hi;
  exit 3'` (the tar fetched and decoded while the runtime loads): median
  210.0 ms before, 195.5 ms after;
- harness.mjs `program`, whose files are in memory before the system is
  made, so nothing is left to overlap: median 179.0 ms before, 177.0 after.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_381c5a280a87404da0f8d9994180b091
2026-11-02 12:35:47 -05:00
velzieandClaude Opus 5.5 c2190c487a gex-kernel: a program's O_CREAT mode is exact (0 makes 0000), and fstat says a file's mode
The commit before made mode 0 the kernel's own "no mode", so a program's
open(O_CREAT, 0), or one whose mode less its umask is 0, got 0644. A
program's open now goes through its own entry point, FileSystem::open_mode
(Namespace::open_mode), and a file it creates gets exactly the mode
computed from its call. The kernel's own opens keep FileSystem::open, whose
created files take the filesystem's default. openat, openat2's and
O_TMPFILE's files, and the page's k_fs_open (Filesystem.writeFile's mode),
go by open_mode. O_TMPFILE's mode is no longer 0600 in place of 0.

fstat of an open tmpfs file said 0644 whatever its mode. It now says the
mode its name has, by any name it has now, or the one its last name had
when an unlink took it (FileNode::gone_mode). unlink also forgets the
name's mode, so a file made at that name later is a new one, not one with
the old file's mode.

core/basic: open(O_CREAT, 0) gives 0000, 0666 under umask 022 gives 0644,
and fstat matches stat after a chmod (rt-probe creat, chmod). Unit tests
check the exact mode, the kernel's default, and fstat through a chmod, a
rename and an unlink.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
2026-10-31 01:34:08 -04:00
velzieandClaude Opus 5.5 4a3eb76e21 gex-kernel: a file O_CREAT makes takes the open's mode, less the caller's umask
The root filesystem's open ignored its mode: every file a program or the
page created was 0644, whatever the open asked for and whatever the umask.
`umask 077; echo > f` made a world-readable file. The page's
Filesystem.writeFile(path, data, { mode: 0o755 }) made a script nobody could
run, which is the README's xdg-open example. openat now hands the mode less
the umask to the filesystem, and TmpFs sets it on a file it creates. A file
that already exists keeps its own. Mode 0 is an internal caller's "no mode"
and still gets the default.

Left as it was: fstat on an open tmpfs file answers 0644 whatever the
file's mode (TmpHandle::metadata).

core/basic checks a page script made 0755 that runs, one written over that
keeps its mode, and a shell's files under umask 077 and 0. A tmpfs unit test
checks the create and the existing cases.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
2026-10-31 01:15:15 -04:00
velzieandClaude Opus 5.5 ba18e5ebfe runtime: run() ends with the program, not with the last holder of its pipes
System.run waited for end of file on the program's stdout and stderr. A
daemon the program started inherits them: under a persistent Wine server
(the page's `wineserver -f -p`), wineboot's services, plugplay and
winedevice keep stderr open for good (Wine gives a detached process
/dev/null for stdin and stdout only), so run never returned although the
program had exited. wine/server's hello_args timed out at 240 s this way.

run's pipes now end for the page once the program has exited and they
hold nothing more. The program's writes are in the pipe before its exit is
reported, so its output is all there. What a daemon writes afterwards is
read and dropped to the pipe's real end, so it neither blocks on a full
pipe nor gets EPIPE. spawn's streams keep their end-of-file meaning.

core/basic checks a shell that leaves a writing daemon behind.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
2026-10-30 23:59:34 -04:00
velzieandClaude Opus 5.5 dafb8e9b9b test: core/basic sends ^C once sleep runs, and waits for the echo's output, not the line's echo
The fixed half second was too short on a busy machine: ^C came before the
sleep, the terminal's echo of `echo after-ctrl-c` passed the check, and
`exit 5` waited for the sleep's 100 s.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
2026-10-30 15:36:40 -04:00
velzieandClaude Opus 5.5 59c2c07ac2 test: one runner for gex's tests, on a browser and a Node host
test/run.mjs runs test/GROUP/NAME.test.js modules, with no HTML. A browser
test runs in a page of its own, in a fresh context of one headless Chromium
under vite; a node test runs in a forked Node process. Each test runs on the
hosts its `meta` allows and for each --arch. It prints one JSON line per test,
host and arch (schema in test/README.md) and exits 0 only when all are ok.
Images are made once into the work directory (fixtures/images.mjs) and kept
while their inputs are unchanged; a test whose image is missing is skipped,
which fails the run unless --allow-skip is given.

The runtime's pages move in as the core, windows, gl and image groups.
Their .html pages, lib.js and the old run.mjs go. The pointer-lock page's
scene-compositor variant is a test of its own. A missing WebGPU adapter or
Mesa is now a skip, not an ok. basic, init, mount and gamepad also run on
node. persist waits 30 s, not 10, for its first save under a loaded machine.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
2026-10-30 14:14:26 -04:00