os: getenvs() []str + single-walker getenv (Hare env surface)
Mirror Hare's os env surface: getenvs() builds an owned []str of NAME=VALUE entries from the rt_envp table (platform_environ.ha:41), and getenv iterates it (environ.ha:32) so there is exactly one env walker. strings.dup is unusable here -- lib/strings imports os, so os importing strings would cycle; the owned copy is inlined (dup.ha:7). test_getenvs_entries pins the []str shape non-vacuously.
This commit is contained in:
109
lib/os/os.ww
109
lib/os/os.ww
@@ -437,47 +437,98 @@ export fn getdents64(fd: i32, buf: *u8, n: u64) i64 = {
|
|||||||
@symbol("rt_argc") fn rtargc() i64;
|
@symbol("rt_argc") fn rtargc() i64;
|
||||||
@symbol("rt_argv") fn rtargv() **u8;
|
@symbol("rt_argv") fn rtargv() **u8;
|
||||||
|
|
||||||
|
// envpbuilt / envpcache — build-once cache for [[getenvs]], the single
|
||||||
|
// env walker. drew ruling (task #17, shared with [[args]]): an explicit
|
||||||
|
// `built` sentinel, NOT len==0 overloading — an empty environment is a
|
||||||
|
// legitimate len-0 state, so a len check would re-walk every call.
|
||||||
|
let envpbuilt: bool = false;
|
||||||
|
let envpcache: []str;
|
||||||
|
|
||||||
|
// getenvs — the environment as an owned `[]str` of "NAME=VALUE" entries.
|
||||||
|
// Mirrors ref/hare/os/+linux/platform_environ.ha:41: lazy-build a
|
||||||
|
// file-private cache by walking the NUL-pointer-terminated rt_envp table,
|
||||||
|
// duping each C string into an owned str. Second call returns the cache.
|
||||||
|
// [[getenv]] borrows into this slice, so there is exactly one env walker.
|
||||||
|
//
|
||||||
|
// DIVERGENCE (Hare-fidelity): Hare's getenvs uses strings::dup, but ww's
|
||||||
|
// strings imports os (os.alloc is ww's allocator — a divergence from
|
||||||
|
// Hare's rt::malloc-direct strings), so os importing strings would be an
|
||||||
|
// import cycle. The owned copy is inlined here — same alloc + byte-copy
|
||||||
|
// as ref/hare/strings/dup.ha:7.
|
||||||
|
export fn getenvs() []str = {
|
||||||
|
if (envpbuilt) { return envpcache; };
|
||||||
|
let tab: **u8 = rtenvp();
|
||||||
|
let count: i32 = 0;
|
||||||
|
for (tab[count] != nil: *u8) { count += 1; };
|
||||||
|
// alloc([], 0) is rt_malloc(0) = an mmap of 0 bytes (-EINVAL); the
|
||||||
|
// empty environment carries the {nil,0,0} shape directly (mirrors
|
||||||
|
// lib/strings dupall's empty bypass).
|
||||||
|
let r: []str;
|
||||||
|
r.ptr = nil: *str;
|
||||||
|
r.len = 0;
|
||||||
|
r.cap = 0;
|
||||||
|
if (count != 0) {
|
||||||
|
let acc: []str = alloc([], count: u64)!;
|
||||||
|
let i: i32 = 0;
|
||||||
|
for (i < count) {
|
||||||
|
let entry: *u8 = tab[i];
|
||||||
|
let n: i32 = 0;
|
||||||
|
for (entry[n] != 0u8) { n += 1; };
|
||||||
|
let buf: []u8 = alloc([], n: u64)!;
|
||||||
|
buf.len = n;
|
||||||
|
let j: i32 = 0;
|
||||||
|
for (j < n) { buf[j] = entry[j]; j += 1; };
|
||||||
|
let s: str;
|
||||||
|
s.ptr = buf.ptr;
|
||||||
|
s.len = n;
|
||||||
|
append(acc, s);
|
||||||
|
i += 1;
|
||||||
|
};
|
||||||
|
r = acc;
|
||||||
|
};
|
||||||
|
envpcache = r;
|
||||||
|
envpbuilt = true;
|
||||||
|
return r;
|
||||||
|
};
|
||||||
|
|
||||||
// getenv — POSIX getenv. Returns a borrowed `str` view over the value
|
// getenv — POSIX getenv. Returns a borrowed `str` view over the value
|
||||||
// bytes of the named environment variable, or void if the name is not
|
// bytes of the named environment variable, or void if the name is not
|
||||||
// present. The view is valid for the process lifetime — the bytes
|
// present. The view is valid for the process lifetime — the bytes
|
||||||
// live in the kernel-supplied envp table at process entry. A future
|
// live in the owned []str built by [[getenvs]] (borrow-source change,
|
||||||
// `setenv` (separate task) that grows the table behind the scenes
|
// task #28: was the raw rt_envp table; now the single duped walker). A
|
||||||
// would invalidate prior views; v1 has no setenv, so callers can
|
// future `setenv` that grows the table behind the scenes would
|
||||||
// hold the view indefinitely.
|
// 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
|
// Mirrors Hare's os::getenv (ref/hare/os/environ.ha:32): iterate
|
||||||
// on missing). Hare also ships os::getenv (`(str | void)`) and
|
// [[getenvs]], "name=" prefix-match each entry, return the value tail.
|
||||||
// os::mustenv (panic-on-missing); ww collapses to the single
|
// Hare also ships os::tryenv (default-on-missing) and os::mustenv
|
||||||
// `(str | void)` form for now — consumers wanting "must" semantics
|
// (panic-on-missing); ww collapses to the single `(str | void)` form for
|
||||||
// abort at the call site.
|
// now — consumers wanting "must" semantics abort at the call site.
|
||||||
//
|
//
|
||||||
// Algorithm: walk the NUL-pointer-terminated `environ` table doing a
|
// NUL inside `name` would never match a real env var (env var names
|
||||||
// "name=" prefix match against each entry, byte-wise. NUL inside
|
// cannot contain '\0'), so we don't filter — POSIX puts that
|
||||||
// `name` would never match a real env var (env var names cannot
|
// responsibility on the caller.
|
||||||
// contain '\0'), so we don't filter — POSIX puts that responsibility
|
|
||||||
// on the caller.
|
|
||||||
export fn getenv(name: str) (str | void) = {
|
export fn getenv(name: str) (str | void) = {
|
||||||
let envp: **u8 = rtenvp();
|
let env: []str = getenvs();
|
||||||
let i: i32 = 0;
|
let i: i32 = 0;
|
||||||
for (true) {
|
for (i < env.len) {
|
||||||
let entry: *u8 = envp[i];
|
let ent: str = env[i];
|
||||||
if (entry == nil: *u8) { return; };
|
|
||||||
let j: i32 = 0;
|
|
||||||
let matched: bool = true;
|
let matched: bool = true;
|
||||||
|
let j: i32 = 0;
|
||||||
for (j < name.len) {
|
for (j < name.len) {
|
||||||
if (entry[j] == 0u8) { matched = false; break; };
|
if (j >= ent.len) { matched = false; break; };
|
||||||
if (entry[j] != name[j]) { matched = false; break; };
|
if (ent[j] != name[j]) { matched = false; break; };
|
||||||
j += 1;
|
j += 1;
|
||||||
};
|
};
|
||||||
if (matched) {
|
if (matched) {
|
||||||
if (entry[name.len] == '=') {
|
if (name.len < ent.len) {
|
||||||
let val: *u8 = entry + ((name.len + 1): u64);
|
if (ent[name.len] == '=') {
|
||||||
let n: i32 = 0;
|
let val: str;
|
||||||
for (val[n] != 0u8) { n += 1; };
|
val.ptr = ent.ptr + ((name.len + 1): u64);
|
||||||
let r: str;
|
val.len = ent.len - (name.len + 1);
|
||||||
r.ptr = val;
|
return val;
|
||||||
r.len = n;
|
};
|
||||||
return r;
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
i += 1;
|
i += 1;
|
||||||
|
|||||||
@@ -91,6 +91,34 @@ fn streq(a: str, b: str) bool = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ---- getenvs: direct non-vacuous coverage of the []str shape --------
|
||||||
|
//
|
||||||
|
// getenvs() is public API (task #28) and the single env walker getenv
|
||||||
|
// routes through. getenv's cases exercise the walk transitively, but
|
||||||
|
// none asserts the []str RETURN shape — its count or "NAME=VALUE" entry
|
||||||
|
// text. This row pins both: the slice must be non-empty, and every
|
||||||
|
// expected entry must appear verbatim (name, '=', value duped intact).
|
||||||
|
// Fails if getenvs miscounts (len 0 → the inner search finds nothing)
|
||||||
|
// or dups the wrong bytes (off-by-one truncation → no streq match).
|
||||||
|
// The "WW_TEST_EMPTY=" row also pins the empty-value entry text.
|
||||||
|
|
||||||
|
@test fn test_getenvs_entries() void = {
|
||||||
|
let env: []str = os.getenvs();
|
||||||
|
assert(!(env.len == 0));
|
||||||
|
let want: [2]str = ["WW_TEST_GETENV=hello-world", "WW_TEST_EMPTY="];
|
||||||
|
let w: i32 = 0;
|
||||||
|
for (w < 2) {
|
||||||
|
let found: bool = false;
|
||||||
|
let i: i32 = 0;
|
||||||
|
for (i < env.len) {
|
||||||
|
if (streq(env[i], want[w])) { found = true; };
|
||||||
|
i += 1;
|
||||||
|
};
|
||||||
|
assert(found);
|
||||||
|
w += 1;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
// ---- alloc/free: mmap-backed runtime allocator ----------------------
|
// ---- alloc/free: mmap-backed runtime allocator ----------------------
|
||||||
//
|
//
|
||||||
// Direct round-trip. Write-then-read-back proves the returned page is
|
// Direct round-trip. Write-then-read-back proves the returned page is
|
||||||
|
|||||||
Reference in New Issue
Block a user