From 846cfc5578bef34b8e413feffa59bdafea72ecf8 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Fri, 15 May 2026 17:17:53 +0900 Subject: [PATCH] selfhost+test: regen combined.ww after os.mkdirs Auto-regen of derived files; lib/os.mkdirs landed in 19aa66a but the selfhost combined.ww snapshots that fold lib/os in were not regened in that commit. Catching them up now so the next make doesn't fight the tree. No source change; make test 61/61. --- selfhost/cmd/w6a/main.combined.ww | 103 +++++++++++++++++++++++++++ selfhost/cmd/w6c/main.combined.ww | 103 +++++++++++++++++++++++++++ selfhost/cmd/w6l/main.combined.ww | 103 +++++++++++++++++++++++++++ selfhost/cmd/ww/main.combined.ww | 103 +++++++++++++++++++++++++++ selfhost/cmd/wwdump/main.combined.ww | 103 +++++++++++++++++++++++++++ selfhost/test/smoke.combined.ww | 103 +++++++++++++++++++++++++++ 6 files changed, 618 insertions(+) diff --git a/selfhost/cmd/w6a/main.combined.ww b/selfhost/cmd/w6a/main.combined.ww index bfe9600e..9cf59285 100644 --- a/selfhost/cmd/w6a/main.combined.ww +++ b/selfhost/cmd/w6a/main.combined.ww @@ -194,6 +194,49 @@ export fn rmdir(path: *u8) i32 = { return syscall1(nr.RMDIR, path: i64): i32; }; +// mkdirs — recursive mkdir. Creates `path` and any non-existent +// parent directories with the given mode. EEXIST is silently +// accepted (matches Hare's `errors::exists` skip in os::mkdirs); +// any other syscall failure surfaces as `oserror`. +// +// `path` must be NUL-terminated AND its bytes must be writable — +// mkdirs temporarily replaces '/' separators with NUL while +// invoking [[mkdir]] on each prefix, then restores them. Pointing +// `path` at a string literal will segfault. Callers hold the bytes +// in a writable buffer (rt_alloc'd, a static `[N]u8`, etc.) — same +// precedent as [[temp.named]]'s pathbuf. +// +// Mirrors Hare's os::mkdirs (recursive variant of os::mkdir). +export fn mkdirs(path: *u8, mode: i32) (void | oserror) = { + // Find the path length (excluding trailing NUL). + let n: i32 = 0; + for (path[n] != 0u8) { n += 1; }; + if (n == 0) { return; }; + + // Walk forward; at each '/' boundary, NUL-terminate the prefix, + // mkdir it, restore the slash, continue. Skip index 0 so a + // leading '/' on absolute paths doesn't trigger an empty mkdir. + let i: i32 = 1; + for (i < n) { + if (path[i] == 47u8) { // '/' + path[i] = 0u8; + let r: i32 = mkdir(path, mode); + path[i] = 47u8; + if (r < 0) { + if (r != -17) { return r: i64: oserror; }; + }; + }; + i += 1; + }; + + // mkdir the full path. + let r: i32 = mkdir(path, mode); + if (r < 0) { + if (r != -17) { return r: i64: oserror; }; + }; + return; +}; + // getpid(2). Used by the driver to mint unique scratch paths. export fn getpid() i32 = { return syscall0(nr.GETPID): i32; @@ -243,6 +286,66 @@ export fn getdents64(fd: i32, buf: *u8, n: u64) i64 = { return syscall3(nr.GETDENTS64, fd: i64, buf: i64, n: i64); }; +// ---- environment ------------------------------------------------------ + +// rt_envp — runtime-side getter. rt/start.s captures envp into a DATAW +// slot before calling main; this binding lifts the captured pointer +// into ww. Same FFI shape as rt_syscall / rt_alloc / rt_abort: a TEXT +// symbol the linker resolves. The returned `**u8` is a NUL-terminated +// table of `*u8` entries, each pointing at a NUL-terminated +// "NAME=VALUE" byte sequence. +// +// We don't expose `rtenvp` directly; [[getenv]] is the only consumer. +@symbol("rt_envp") fn rtenvp() **u8; + +// getenv — POSIX getenv. Returns a borrowed `str` view over the value +// bytes of the named environment variable, or void if the name is not +// present. The view is valid for the process lifetime — the bytes +// live in the kernel-supplied envp table at process entry. A future +// `setenv` (separate task) that grows the table behind the scenes +// would invalidate prior views; v1 has no setenv, so callers can +// hold the view indefinitely. +// +// Mirrors Hare's os::tryenv shape (returns void rather than panicking +// on missing). Hare also ships os::getenv (`(str | void)`) and +// os::mustenv (panic-on-missing); ww collapses to the single +// `(str | void)` form for now — consumers wanting "must" semantics +// abort at the call site. +// +// Algorithm: walk the NUL-pointer-terminated `environ` table doing a +// "name=" prefix match against each entry, byte-wise. NUL inside +// `name` would never match a real env var (env var names cannot +// contain '\0'), so we don't filter — POSIX puts that responsibility +// on the caller. +export fn getenv(name: str) (str | void) = { + let envp: **u8 = rtenvp(); + let i: i32 = 0; + for (true) { + let entry: *u8 = envp[i]; + if (entry == nil: *u8) { return; }; + let j: i32 = 0; + let matched: bool = true; + for (j < name.len) { + if (entry[j] == 0u8) { matched = false; break; }; + if (entry[j] != name[j]) { matched = false; break; }; + j += 1; + }; + if (matched) { + if (entry[name.len] == 61u8) { // '=' + let val: *u8 = entry + ((name.len + 1): u64); + let n: i32 = 0; + for (val[n] != 0u8) { n += 1; }; + let r: str; + r.ptr = val; + r.len = n; + return r; + }; + }; + i += 1; + }; + return; +}; + // MODULE: wcc // selfhost/cmd/wcc/mem.ww — port of cmd/wcc/mem.c. // diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 438832c8..f452ef14 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -194,6 +194,49 @@ export fn rmdir(path: *u8) i32 = { return syscall1(nr.RMDIR, path: i64): i32; }; +// mkdirs — recursive mkdir. Creates `path` and any non-existent +// parent directories with the given mode. EEXIST is silently +// accepted (matches Hare's `errors::exists` skip in os::mkdirs); +// any other syscall failure surfaces as `oserror`. +// +// `path` must be NUL-terminated AND its bytes must be writable — +// mkdirs temporarily replaces '/' separators with NUL while +// invoking [[mkdir]] on each prefix, then restores them. Pointing +// `path` at a string literal will segfault. Callers hold the bytes +// in a writable buffer (rt_alloc'd, a static `[N]u8`, etc.) — same +// precedent as [[temp.named]]'s pathbuf. +// +// Mirrors Hare's os::mkdirs (recursive variant of os::mkdir). +export fn mkdirs(path: *u8, mode: i32) (void | oserror) = { + // Find the path length (excluding trailing NUL). + let n: i32 = 0; + for (path[n] != 0u8) { n += 1; }; + if (n == 0) { return; }; + + // Walk forward; at each '/' boundary, NUL-terminate the prefix, + // mkdir it, restore the slash, continue. Skip index 0 so a + // leading '/' on absolute paths doesn't trigger an empty mkdir. + let i: i32 = 1; + for (i < n) { + if (path[i] == 47u8) { // '/' + path[i] = 0u8; + let r: i32 = mkdir(path, mode); + path[i] = 47u8; + if (r < 0) { + if (r != -17) { return r: i64: oserror; }; + }; + }; + i += 1; + }; + + // mkdir the full path. + let r: i32 = mkdir(path, mode); + if (r < 0) { + if (r != -17) { return r: i64: oserror; }; + }; + return; +}; + // getpid(2). Used by the driver to mint unique scratch paths. export fn getpid() i32 = { return syscall0(nr.GETPID): i32; @@ -243,6 +286,66 @@ export fn getdents64(fd: i32, buf: *u8, n: u64) i64 = { return syscall3(nr.GETDENTS64, fd: i64, buf: i64, n: i64); }; +// ---- environment ------------------------------------------------------ + +// rt_envp — runtime-side getter. rt/start.s captures envp into a DATAW +// slot before calling main; this binding lifts the captured pointer +// into ww. Same FFI shape as rt_syscall / rt_alloc / rt_abort: a TEXT +// symbol the linker resolves. The returned `**u8` is a NUL-terminated +// table of `*u8` entries, each pointing at a NUL-terminated +// "NAME=VALUE" byte sequence. +// +// We don't expose `rtenvp` directly; [[getenv]] is the only consumer. +@symbol("rt_envp") fn rtenvp() **u8; + +// getenv — POSIX getenv. Returns a borrowed `str` view over the value +// bytes of the named environment variable, or void if the name is not +// present. The view is valid for the process lifetime — the bytes +// live in the kernel-supplied envp table at process entry. A future +// `setenv` (separate task) that grows the table behind the scenes +// would invalidate prior views; v1 has no setenv, so callers can +// hold the view indefinitely. +// +// Mirrors Hare's os::tryenv shape (returns void rather than panicking +// on missing). Hare also ships os::getenv (`(str | void)`) and +// os::mustenv (panic-on-missing); ww collapses to the single +// `(str | void)` form for now — consumers wanting "must" semantics +// abort at the call site. +// +// Algorithm: walk the NUL-pointer-terminated `environ` table doing a +// "name=" prefix match against each entry, byte-wise. NUL inside +// `name` would never match a real env var (env var names cannot +// contain '\0'), so we don't filter — POSIX puts that responsibility +// on the caller. +export fn getenv(name: str) (str | void) = { + let envp: **u8 = rtenvp(); + let i: i32 = 0; + for (true) { + let entry: *u8 = envp[i]; + if (entry == nil: *u8) { return; }; + let j: i32 = 0; + let matched: bool = true; + for (j < name.len) { + if (entry[j] == 0u8) { matched = false; break; }; + if (entry[j] != name[j]) { matched = false; break; }; + j += 1; + }; + if (matched) { + if (entry[name.len] == 61u8) { // '=' + let val: *u8 = entry + ((name.len + 1): u64); + let n: i32 = 0; + for (val[n] != 0u8) { n += 1; }; + let r: str; + r.ptr = val; + r.len = n; + return r; + }; + }; + i += 1; + }; + return; +}; + // MODULE: wcc // selfhost/cmd/wcc/mem.ww — port of cmd/wcc/mem.c. // diff --git a/selfhost/cmd/w6l/main.combined.ww b/selfhost/cmd/w6l/main.combined.ww index 73497c8c..fd3f9baf 100644 --- a/selfhost/cmd/w6l/main.combined.ww +++ b/selfhost/cmd/w6l/main.combined.ww @@ -194,6 +194,49 @@ export fn rmdir(path: *u8) i32 = { return syscall1(nr.RMDIR, path: i64): i32; }; +// mkdirs — recursive mkdir. Creates `path` and any non-existent +// parent directories with the given mode. EEXIST is silently +// accepted (matches Hare's `errors::exists` skip in os::mkdirs); +// any other syscall failure surfaces as `oserror`. +// +// `path` must be NUL-terminated AND its bytes must be writable — +// mkdirs temporarily replaces '/' separators with NUL while +// invoking [[mkdir]] on each prefix, then restores them. Pointing +// `path` at a string literal will segfault. Callers hold the bytes +// in a writable buffer (rt_alloc'd, a static `[N]u8`, etc.) — same +// precedent as [[temp.named]]'s pathbuf. +// +// Mirrors Hare's os::mkdirs (recursive variant of os::mkdir). +export fn mkdirs(path: *u8, mode: i32) (void | oserror) = { + // Find the path length (excluding trailing NUL). + let n: i32 = 0; + for (path[n] != 0u8) { n += 1; }; + if (n == 0) { return; }; + + // Walk forward; at each '/' boundary, NUL-terminate the prefix, + // mkdir it, restore the slash, continue. Skip index 0 so a + // leading '/' on absolute paths doesn't trigger an empty mkdir. + let i: i32 = 1; + for (i < n) { + if (path[i] == 47u8) { // '/' + path[i] = 0u8; + let r: i32 = mkdir(path, mode); + path[i] = 47u8; + if (r < 0) { + if (r != -17) { return r: i64: oserror; }; + }; + }; + i += 1; + }; + + // mkdir the full path. + let r: i32 = mkdir(path, mode); + if (r < 0) { + if (r != -17) { return r: i64: oserror; }; + }; + return; +}; + // getpid(2). Used by the driver to mint unique scratch paths. export fn getpid() i32 = { return syscall0(nr.GETPID): i32; @@ -243,6 +286,66 @@ export fn getdents64(fd: i32, buf: *u8, n: u64) i64 = { return syscall3(nr.GETDENTS64, fd: i64, buf: i64, n: i64); }; +// ---- environment ------------------------------------------------------ + +// rt_envp — runtime-side getter. rt/start.s captures envp into a DATAW +// slot before calling main; this binding lifts the captured pointer +// into ww. Same FFI shape as rt_syscall / rt_alloc / rt_abort: a TEXT +// symbol the linker resolves. The returned `**u8` is a NUL-terminated +// table of `*u8` entries, each pointing at a NUL-terminated +// "NAME=VALUE" byte sequence. +// +// We don't expose `rtenvp` directly; [[getenv]] is the only consumer. +@symbol("rt_envp") fn rtenvp() **u8; + +// getenv — POSIX getenv. Returns a borrowed `str` view over the value +// bytes of the named environment variable, or void if the name is not +// present. The view is valid for the process lifetime — the bytes +// live in the kernel-supplied envp table at process entry. A future +// `setenv` (separate task) that grows the table behind the scenes +// would invalidate prior views; v1 has no setenv, so callers can +// hold the view indefinitely. +// +// Mirrors Hare's os::tryenv shape (returns void rather than panicking +// on missing). Hare also ships os::getenv (`(str | void)`) and +// os::mustenv (panic-on-missing); ww collapses to the single +// `(str | void)` form for now — consumers wanting "must" semantics +// abort at the call site. +// +// Algorithm: walk the NUL-pointer-terminated `environ` table doing a +// "name=" prefix match against each entry, byte-wise. NUL inside +// `name` would never match a real env var (env var names cannot +// contain '\0'), so we don't filter — POSIX puts that responsibility +// on the caller. +export fn getenv(name: str) (str | void) = { + let envp: **u8 = rtenvp(); + let i: i32 = 0; + for (true) { + let entry: *u8 = envp[i]; + if (entry == nil: *u8) { return; }; + let j: i32 = 0; + let matched: bool = true; + for (j < name.len) { + if (entry[j] == 0u8) { matched = false; break; }; + if (entry[j] != name[j]) { matched = false; break; }; + j += 1; + }; + if (matched) { + if (entry[name.len] == 61u8) { // '=' + let val: *u8 = entry + ((name.len + 1): u64); + let n: i32 = 0; + for (val[n] != 0u8) { n += 1; }; + let r: str; + r.ptr = val; + r.len = n; + return r; + }; + }; + i += 1; + }; + return; +}; + // MODULE: wcc // selfhost/cmd/wcc/mem.ww — port of cmd/wcc/mem.c. // diff --git a/selfhost/cmd/ww/main.combined.ww b/selfhost/cmd/ww/main.combined.ww index cad71d88..c38dfb3c 100644 --- a/selfhost/cmd/ww/main.combined.ww +++ b/selfhost/cmd/ww/main.combined.ww @@ -194,6 +194,49 @@ export fn rmdir(path: *u8) i32 = { return syscall1(nr.RMDIR, path: i64): i32; }; +// mkdirs — recursive mkdir. Creates `path` and any non-existent +// parent directories with the given mode. EEXIST is silently +// accepted (matches Hare's `errors::exists` skip in os::mkdirs); +// any other syscall failure surfaces as `oserror`. +// +// `path` must be NUL-terminated AND its bytes must be writable — +// mkdirs temporarily replaces '/' separators with NUL while +// invoking [[mkdir]] on each prefix, then restores them. Pointing +// `path` at a string literal will segfault. Callers hold the bytes +// in a writable buffer (rt_alloc'd, a static `[N]u8`, etc.) — same +// precedent as [[temp.named]]'s pathbuf. +// +// Mirrors Hare's os::mkdirs (recursive variant of os::mkdir). +export fn mkdirs(path: *u8, mode: i32) (void | oserror) = { + // Find the path length (excluding trailing NUL). + let n: i32 = 0; + for (path[n] != 0u8) { n += 1; }; + if (n == 0) { return; }; + + // Walk forward; at each '/' boundary, NUL-terminate the prefix, + // mkdir it, restore the slash, continue. Skip index 0 so a + // leading '/' on absolute paths doesn't trigger an empty mkdir. + let i: i32 = 1; + for (i < n) { + if (path[i] == 47u8) { // '/' + path[i] = 0u8; + let r: i32 = mkdir(path, mode); + path[i] = 47u8; + if (r < 0) { + if (r != -17) { return r: i64: oserror; }; + }; + }; + i += 1; + }; + + // mkdir the full path. + let r: i32 = mkdir(path, mode); + if (r < 0) { + if (r != -17) { return r: i64: oserror; }; + }; + return; +}; + // getpid(2). Used by the driver to mint unique scratch paths. export fn getpid() i32 = { return syscall0(nr.GETPID): i32; @@ -243,6 +286,66 @@ export fn getdents64(fd: i32, buf: *u8, n: u64) i64 = { return syscall3(nr.GETDENTS64, fd: i64, buf: i64, n: i64); }; +// ---- environment ------------------------------------------------------ + +// rt_envp — runtime-side getter. rt/start.s captures envp into a DATAW +// slot before calling main; this binding lifts the captured pointer +// into ww. Same FFI shape as rt_syscall / rt_alloc / rt_abort: a TEXT +// symbol the linker resolves. The returned `**u8` is a NUL-terminated +// table of `*u8` entries, each pointing at a NUL-terminated +// "NAME=VALUE" byte sequence. +// +// We don't expose `rtenvp` directly; [[getenv]] is the only consumer. +@symbol("rt_envp") fn rtenvp() **u8; + +// getenv — POSIX getenv. Returns a borrowed `str` view over the value +// bytes of the named environment variable, or void if the name is not +// present. The view is valid for the process lifetime — the bytes +// live in the kernel-supplied envp table at process entry. A future +// `setenv` (separate task) that grows the table behind the scenes +// would invalidate prior views; v1 has no setenv, so callers can +// hold the view indefinitely. +// +// Mirrors Hare's os::tryenv shape (returns void rather than panicking +// on missing). Hare also ships os::getenv (`(str | void)`) and +// os::mustenv (panic-on-missing); ww collapses to the single +// `(str | void)` form for now — consumers wanting "must" semantics +// abort at the call site. +// +// Algorithm: walk the NUL-pointer-terminated `environ` table doing a +// "name=" prefix match against each entry, byte-wise. NUL inside +// `name` would never match a real env var (env var names cannot +// contain '\0'), so we don't filter — POSIX puts that responsibility +// on the caller. +export fn getenv(name: str) (str | void) = { + let envp: **u8 = rtenvp(); + let i: i32 = 0; + for (true) { + let entry: *u8 = envp[i]; + if (entry == nil: *u8) { return; }; + let j: i32 = 0; + let matched: bool = true; + for (j < name.len) { + if (entry[j] == 0u8) { matched = false; break; }; + if (entry[j] != name[j]) { matched = false; break; }; + j += 1; + }; + if (matched) { + if (entry[name.len] == 61u8) { // '=' + let val: *u8 = entry + ((name.len + 1): u64); + let n: i32 = 0; + for (val[n] != 0u8) { n += 1; }; + let r: str; + r.ptr = val; + r.len = n; + return r; + }; + }; + i += 1; + }; + return; +}; + // MODULE: wcc // selfhost/cmd/wcc/mem.ww — port of cmd/wcc/mem.c. // diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index b9744939..7962cfbd 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -194,6 +194,49 @@ export fn rmdir(path: *u8) i32 = { return syscall1(nr.RMDIR, path: i64): i32; }; +// mkdirs — recursive mkdir. Creates `path` and any non-existent +// parent directories with the given mode. EEXIST is silently +// accepted (matches Hare's `errors::exists` skip in os::mkdirs); +// any other syscall failure surfaces as `oserror`. +// +// `path` must be NUL-terminated AND its bytes must be writable — +// mkdirs temporarily replaces '/' separators with NUL while +// invoking [[mkdir]] on each prefix, then restores them. Pointing +// `path` at a string literal will segfault. Callers hold the bytes +// in a writable buffer (rt_alloc'd, a static `[N]u8`, etc.) — same +// precedent as [[temp.named]]'s pathbuf. +// +// Mirrors Hare's os::mkdirs (recursive variant of os::mkdir). +export fn mkdirs(path: *u8, mode: i32) (void | oserror) = { + // Find the path length (excluding trailing NUL). + let n: i32 = 0; + for (path[n] != 0u8) { n += 1; }; + if (n == 0) { return; }; + + // Walk forward; at each '/' boundary, NUL-terminate the prefix, + // mkdir it, restore the slash, continue. Skip index 0 so a + // leading '/' on absolute paths doesn't trigger an empty mkdir. + let i: i32 = 1; + for (i < n) { + if (path[i] == 47u8) { // '/' + path[i] = 0u8; + let r: i32 = mkdir(path, mode); + path[i] = 47u8; + if (r < 0) { + if (r != -17) { return r: i64: oserror; }; + }; + }; + i += 1; + }; + + // mkdir the full path. + let r: i32 = mkdir(path, mode); + if (r < 0) { + if (r != -17) { return r: i64: oserror; }; + }; + return; +}; + // getpid(2). Used by the driver to mint unique scratch paths. export fn getpid() i32 = { return syscall0(nr.GETPID): i32; @@ -243,6 +286,66 @@ export fn getdents64(fd: i32, buf: *u8, n: u64) i64 = { return syscall3(nr.GETDENTS64, fd: i64, buf: i64, n: i64); }; +// ---- environment ------------------------------------------------------ + +// rt_envp — runtime-side getter. rt/start.s captures envp into a DATAW +// slot before calling main; this binding lifts the captured pointer +// into ww. Same FFI shape as rt_syscall / rt_alloc / rt_abort: a TEXT +// symbol the linker resolves. The returned `**u8` is a NUL-terminated +// table of `*u8` entries, each pointing at a NUL-terminated +// "NAME=VALUE" byte sequence. +// +// We don't expose `rtenvp` directly; [[getenv]] is the only consumer. +@symbol("rt_envp") fn rtenvp() **u8; + +// getenv — POSIX getenv. Returns a borrowed `str` view over the value +// bytes of the named environment variable, or void if the name is not +// present. The view is valid for the process lifetime — the bytes +// live in the kernel-supplied envp table at process entry. A future +// `setenv` (separate task) that grows the table behind the scenes +// would invalidate prior views; v1 has no setenv, so callers can +// hold the view indefinitely. +// +// Mirrors Hare's os::tryenv shape (returns void rather than panicking +// on missing). Hare also ships os::getenv (`(str | void)`) and +// os::mustenv (panic-on-missing); ww collapses to the single +// `(str | void)` form for now — consumers wanting "must" semantics +// abort at the call site. +// +// Algorithm: walk the NUL-pointer-terminated `environ` table doing a +// "name=" prefix match against each entry, byte-wise. NUL inside +// `name` would never match a real env var (env var names cannot +// contain '\0'), so we don't filter — POSIX puts that responsibility +// on the caller. +export fn getenv(name: str) (str | void) = { + let envp: **u8 = rtenvp(); + let i: i32 = 0; + for (true) { + let entry: *u8 = envp[i]; + if (entry == nil: *u8) { return; }; + let j: i32 = 0; + let matched: bool = true; + for (j < name.len) { + if (entry[j] == 0u8) { matched = false; break; }; + if (entry[j] != name[j]) { matched = false; break; }; + j += 1; + }; + if (matched) { + if (entry[name.len] == 61u8) { // '=' + let val: *u8 = entry + ((name.len + 1): u64); + let n: i32 = 0; + for (val[n] != 0u8) { n += 1; }; + let r: str; + r.ptr = val; + r.len = n; + return r; + }; + }; + i += 1; + }; + return; +}; + // MODULE: wcc // selfhost/cmd/wcc/mem.ww — port of cmd/wcc/mem.c. // diff --git a/selfhost/test/smoke.combined.ww b/selfhost/test/smoke.combined.ww index 7068cd6e..e4582fec 100644 --- a/selfhost/test/smoke.combined.ww +++ b/selfhost/test/smoke.combined.ww @@ -194,6 +194,49 @@ export fn rmdir(path: *u8) i32 = { return syscall1(nr.RMDIR, path: i64): i32; }; +// mkdirs — recursive mkdir. Creates `path` and any non-existent +// parent directories with the given mode. EEXIST is silently +// accepted (matches Hare's `errors::exists` skip in os::mkdirs); +// any other syscall failure surfaces as `oserror`. +// +// `path` must be NUL-terminated AND its bytes must be writable — +// mkdirs temporarily replaces '/' separators with NUL while +// invoking [[mkdir]] on each prefix, then restores them. Pointing +// `path` at a string literal will segfault. Callers hold the bytes +// in a writable buffer (rt_alloc'd, a static `[N]u8`, etc.) — same +// precedent as [[temp.named]]'s pathbuf. +// +// Mirrors Hare's os::mkdirs (recursive variant of os::mkdir). +export fn mkdirs(path: *u8, mode: i32) (void | oserror) = { + // Find the path length (excluding trailing NUL). + let n: i32 = 0; + for (path[n] != 0u8) { n += 1; }; + if (n == 0) { return; }; + + // Walk forward; at each '/' boundary, NUL-terminate the prefix, + // mkdir it, restore the slash, continue. Skip index 0 so a + // leading '/' on absolute paths doesn't trigger an empty mkdir. + let i: i32 = 1; + for (i < n) { + if (path[i] == 47u8) { // '/' + path[i] = 0u8; + let r: i32 = mkdir(path, mode); + path[i] = 47u8; + if (r < 0) { + if (r != -17) { return r: i64: oserror; }; + }; + }; + i += 1; + }; + + // mkdir the full path. + let r: i32 = mkdir(path, mode); + if (r < 0) { + if (r != -17) { return r: i64: oserror; }; + }; + return; +}; + // getpid(2). Used by the driver to mint unique scratch paths. export fn getpid() i32 = { return syscall0(nr.GETPID): i32; @@ -243,6 +286,66 @@ export fn getdents64(fd: i32, buf: *u8, n: u64) i64 = { return syscall3(nr.GETDENTS64, fd: i64, buf: i64, n: i64); }; +// ---- environment ------------------------------------------------------ + +// rt_envp — runtime-side getter. rt/start.s captures envp into a DATAW +// slot before calling main; this binding lifts the captured pointer +// into ww. Same FFI shape as rt_syscall / rt_alloc / rt_abort: a TEXT +// symbol the linker resolves. The returned `**u8` is a NUL-terminated +// table of `*u8` entries, each pointing at a NUL-terminated +// "NAME=VALUE" byte sequence. +// +// We don't expose `rtenvp` directly; [[getenv]] is the only consumer. +@symbol("rt_envp") fn rtenvp() **u8; + +// getenv — POSIX getenv. Returns a borrowed `str` view over the value +// bytes of the named environment variable, or void if the name is not +// present. The view is valid for the process lifetime — the bytes +// live in the kernel-supplied envp table at process entry. A future +// `setenv` (separate task) that grows the table behind the scenes +// would invalidate prior views; v1 has no setenv, so callers can +// hold the view indefinitely. +// +// Mirrors Hare's os::tryenv shape (returns void rather than panicking +// on missing). Hare also ships os::getenv (`(str | void)`) and +// os::mustenv (panic-on-missing); ww collapses to the single +// `(str | void)` form for now — consumers wanting "must" semantics +// abort at the call site. +// +// Algorithm: walk the NUL-pointer-terminated `environ` table doing a +// "name=" prefix match against each entry, byte-wise. NUL inside +// `name` would never match a real env var (env var names cannot +// contain '\0'), so we don't filter — POSIX puts that responsibility +// on the caller. +export fn getenv(name: str) (str | void) = { + let envp: **u8 = rtenvp(); + let i: i32 = 0; + for (true) { + let entry: *u8 = envp[i]; + if (entry == nil: *u8) { return; }; + let j: i32 = 0; + let matched: bool = true; + for (j < name.len) { + if (entry[j] == 0u8) { matched = false; break; }; + if (entry[j] != name[j]) { matched = false; break; }; + j += 1; + }; + if (matched) { + if (entry[name.len] == 61u8) { // '=' + let val: *u8 = entry + ((name.len + 1): u64); + let n: i32 = 0; + for (val[n] != 0u8) { n += 1; }; + let r: str; + r.ptr = val; + r.len = n; + return r; + }; + }; + i += 1; + }; + return; +}; + // MODULE: strings // strings — operations over the immutable str type ({ *u8, len }). // Mirrors Hare's strings::; `len` and `is-empty` aren't functions