lib/os+selfhost: *u8→str path migration (#23)

Path-shaped entrypoints now take str: open, tryopen, access, remove,
mkdir, rmdir, mkdirs, stat, lstat, exists, execve (path arg only).
Each cites its Hare source (ref/hare/os/*.ha, ref/hare/sys/+linux/
*.ha).

New internal kpath(str) *u8 copies into module-level pathbuf: [4096]u8
and NUL-terminates; mirrors ref/hare/sys/+linux/syscalls.ha:25,53.
Non-reentrant — graduates with thread story. mkdirs flattens to one
kpath at entry then walks pathbuf invoking raw SYS_mkdir to avoid
nested kpath clobber.

One Hare divergence at kpath: ships *u8 with nil ENAMETOOLONG sentinel
instead of (*const u8 | errno). Reason: wwstage over-allocates
1-word-payload tagged returns to 24B (cstage emits 16B); filed as
follow-up. Repro at .ai/probe_tagged_return_pointer_payload.ww;
graduates when fix lands.

Each selfhost cmd grew a private pathstr(*u8) str (cstrlen + bs) for
remaining *u8 path sites; w6l shares via obj.ww. Probe 7 in smoke
updated.

Tests 975/976/981 cover migrated entrypoints; 976 extended with two
ENAMETOOLONG rows (-36 for stat, false for exists).
This commit is contained in:
2026-05-16 23:36:41 +09:00
parent 08ac5149e8
commit deaa777eb8
19 changed files with 963 additions and 451 deletions

View File

@@ -186,7 +186,9 @@ export fn named(outfd: *i32, outpath: *str,
};
for (true) {
pathlen = makenamed(dir);
let fd: i32 = os.open(&pathbuf[0], flags, perm);
let bs: str;
bs.ptr = &pathbuf[0]; bs.len = pathlen;
let fd: i32 = os.open(bs, flags, perm);
if (fd >= 0) {
*outfd = fd;
let p: str;
@@ -227,7 +229,9 @@ export fn file(iomode: mode, perm: i32) (i32 | os.oserror) = {
export fn dir() str = {
for (true) {
pathlen = makedir();
let r: i32 = os.mkdir(&pathbuf[0], 448i32); // 0o700
let bs: str;
bs.ptr = &pathbuf[0]; bs.len = pathlen;
let r: i32 = os.mkdir(bs, 448i32); // 0o700
if (r >= 0) {
let p: str;
p.ptr = &pathbuf[0];

View File

@@ -94,13 +94,13 @@ fn streq(a: str, b: str) bool = {
};
// File exists pre-cleanup.
if (os.access(p.ptr, 0i32) != 0) { fail(); };
if (os.access(p, 0i32) != 0) { fail(); };
if (os.close(fd) != 0) { fail(); };
if (os.remove(p.ptr) != 0) { fail(); };
if (os.remove(p) != 0) { fail(); };
// Cleanup landed.
if (os.access(p.ptr, 0i32) == 0) { fail(); };
if (os.access(p, 0i32) == 0) { fail(); };
i += 1;
};
@@ -156,10 +156,10 @@ fn strslice(p: str, lo: i32, hi: i32) str = {
if (fd1 == fd2) { fail(); };
if (os.close(fd2) != 0) { fail(); };
if (os.remove(p2.ptr) != 0) { fail(); };
if (os.remove(p2) != 0) { fail(); };
if (os.close(fd1) != 0) { fail(); };
if (os.remove(&snap[0]) != 0) { fail(); };
if (os.remove(psnap) != 0) { fail(); };
};
// NOTE: temp.file() has no test of its own. The function is a thin
@@ -187,17 +187,20 @@ fn strslice(p: str, lo: i32, hi: i32) str = {
if (d.ptr[d.len] != 0u8) { fail(); };
// Dir exists.
if (os.access(d.ptr, 0i32) != 0) { fail(); };
if (os.access(d, 0i32) != 0) { fail(); };
// Snapshot the dir path into a local NUL-terminated buffer:
// we'll need it after os.open() (the child create) leaves
// the buffer alone, but it's good hygiene given future
// helpers might share pathbuf.
// the os.* path entrypoints now copy through lib/os.pathbuf
// (kpath), so d's view into temp.pathbuf is safe; the
// snapshot still buys robustness against future helpers
// that might share temp's buffer.
let dsnap: [128]u8;
let dlen: i32 = d.len;
let s: i32 = 0;
for (s < d.len) { dsnap[s] = d[s]; s += 1; };
dsnap[d.len] = 0u8;
let dview: str;
dview.ptr = &dsnap[0]; dview.len = dlen;
if (childn[i] > 0) {
// Build "<d>/x\0" in a local buffer.
@@ -208,23 +211,25 @@ fn strslice(p: str, lo: i32, hi: i32) str = {
cbuf[off] = 47u8; off += 1; // '/'
cbuf[off] = 120u8; off += 1; // 'x'
cbuf[off] = 0u8;
let cview: str;
cview.ptr = &cbuf[0]; cview.len = off;
let cflags: os.flag = os.flag.WRONLY | os.flag.CREATE | os.flag.EXCL;
let fd: i32 = os.open(&cbuf[0], cflags, 384i32);
let fd: i32 = os.open(cview, cflags, 384i32);
if (fd < 0) { fail(); };
let payload: [3]u8;
payload[0] = 88u8; payload[1] = 89u8; payload[2] = 90u8; // "XYZ"
let wr: i64 = os.write(fd, &payload[0], 3u64);
if (wr != 3i64) { fail(); };
if (os.close(fd) != 0) { fail(); };
if (os.remove(&cbuf[0]) != 0) { fail(); };
if (os.remove(cview) != 0) { fail(); };
};
// Rmdir uses dsnap (more robust if the static buffer were
// Rmdir uses dview (more robust if the static buffer were
// touched between dir() and here).
if (os.rmdir(&dsnap[0]) != 0) { fail(); };
if (os.rmdir(dview) != 0) { fail(); };
// Cleanup landed.
if (os.access(&dsnap[0], 0i32) == 0) { fail(); };
if (os.access(dview, 0i32) == 0) { fail(); };
i += 1;
};
@@ -249,8 +254,8 @@ fn strslice(p: str, lo: i32, hi: i32) str = {
if (streq(strslice(d2, 0, d2.len), dsnap)) { fail(); };
// Cleanup both, using snap for d1's old contents.
if (os.rmdir(d2.ptr) != 0) { fail(); };
if (os.rmdir(&snap[0]) != 0) { fail(); };
if (os.rmdir(d2) != 0) { fail(); };
if (os.rmdir(dsnap) != 0) { fail(); };
};
export fn main() i32 = {