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:
@@ -114,7 +114,9 @@ fn view() str = {
|
||||
// behaviour Hare's lookup uses (`fmt::fatalf` on the HOME branch).
|
||||
// Centralised so both branches in [[lookup]] share the error path.
|
||||
fn ensure() void = {
|
||||
match (os.mkdirs(&pathbuf[0], MODE_0755)) {
|
||||
let pv: str;
|
||||
pv.ptr = &pathbuf[0]; pv.len = pathlen;
|
||||
match (os.mkdirs(pv, MODE_0755)) {
|
||||
case void => {};
|
||||
case let _e: os.oserror => rtabort("dirs: mkdirs failed");
|
||||
};
|
||||
|
||||
161
lib/os/os.ww
161
lib/os/os.ww
@@ -87,6 +87,36 @@ export fn exit(code: i32) void = {
|
||||
syscall1(nr.EXIT, code: i64);
|
||||
};
|
||||
|
||||
// PATH_MAX / pathbuf / kpath — port of Hare's ref/hare/sys/+linux/
|
||||
// syscalls.ha:25,27,29-55. Hare's `path` accepts a sum `(str |
|
||||
// []u8 | *const u8)`; ww's lib/os public surface narrows to `str`
|
||||
// (the Hare-faithful surface at ref/hare/os/os.ha:37,47,50 etc).
|
||||
// Internally, [[kpath]] copies the `str` bytes into a single
|
||||
// module-level [[pathbuf]] scratch slot and NUL-terminates so the
|
||||
// raw Linux syscalls (which require C strings) see a valid
|
||||
// terminator. Same precedent as Hare's static `pathbuf`.
|
||||
//
|
||||
// Non-reentrant: one buffer, every [[stat]] / [[open]] / etc.
|
||||
// rewrites it. Same caveat as strconv's `*tos` family (overwritten
|
||||
// on next call). Caller must NOT hold a kpath-returned pointer
|
||||
// across another lib/os path call. Graduates when ww grows a
|
||||
// thread story.
|
||||
//
|
||||
// `nil`-as-overflow over `(*u8 | oserror)`: wwstage over-allocates
|
||||
// 1-word-payload tagged returns to 24B (cstage emits 16B).
|
||||
// Task #9; revert at task #10 when fixed. Repro at
|
||||
// .ai/probe_tagged_return_pointer_payload.ww.
|
||||
export def PATH_MAX: i32 = 4096;
|
||||
let pathbuf: [4096]u8;
|
||||
|
||||
fn kpath(p: str) *u8 = {
|
||||
if (p.len + 1 >= PATH_MAX) { return nil: *u8; }; // ENAMETOOLONG
|
||||
let i: i32 = 0;
|
||||
for (i < p.len) { pathbuf[i] = p[i]; i += 1; };
|
||||
pathbuf[p.len] = 0u8;
|
||||
return &pathbuf[0];
|
||||
};
|
||||
|
||||
// Raw, non-fallible primitives. These return Linux's int conventions
|
||||
// (negative = -errno, non-negative = bytes/fd/etc). Callers wanting a
|
||||
// Hare-style fallible API use the wrappers below.
|
||||
@@ -125,15 +155,17 @@ export fn trywrite(fd: i32, buf: *u8, n: u64) (i64 | oserror) = {
|
||||
return r;
|
||||
};
|
||||
|
||||
// open — Linux open(2). Path must be NUL-terminated; callers using ww
|
||||
// `str` must ensure the bytes are followed by a 0 byte (literals are,
|
||||
// arena-copied paths usually are by construction). Returns -errno on
|
||||
// failure, fd otherwise. Higher-level callers prefer `tryopen`.
|
||||
export fn open(path: *u8, flags: flag, mode: i32) i32 = {
|
||||
return syscall3(nr.OPEN, path: i64, (flags as i32): i64, mode: i64): i32;
|
||||
// open — Linux open(2). Returns -errno on failure, fd otherwise.
|
||||
// Higher-level callers prefer `tryopen`. Mirrors Hare's os::open
|
||||
// (ref/hare/os/os.ha:117); kpath lands the bytes in pathbuf.
|
||||
// Returns -ENAMETOOLONG (-36) if the path overflows PATH_MAX.
|
||||
export fn open(path: str, flags: flag, mode: i32) i32 = {
|
||||
let p: *u8 = kpath(path);
|
||||
if (p == nil: *u8) { return -36i32; }; // ENAMETOOLONG
|
||||
return syscall3(nr.OPEN, p: i64, (flags as i32): i64, mode: i64): i32;
|
||||
};
|
||||
|
||||
export fn tryopen(path: *u8, flags: flag, mode: i32) (i32 | oserror) = {
|
||||
export fn tryopen(path: str, flags: flag, mode: i32) (i32 | oserror) = {
|
||||
let fd: i32 = open(path, flags, mode);
|
||||
if (fd < 0) { return fd: i64: oserror; };
|
||||
return fd;
|
||||
@@ -193,26 +225,37 @@ export fn writeall(fd: i32, buf: *u8, n: u64) (i64 | oserror) = {
|
||||
|
||||
// access(2): returns 0 if the file is reachable, negative errno
|
||||
// otherwise. mode is the bitset described in <unistd.h> (F_OK=0).
|
||||
export fn access(path: *u8, mode: i32) i32 = {
|
||||
return syscall2(nr.ACCESS, path: i64, mode: i64): i32;
|
||||
// Mirrors Hare's os::access (ref/hare/os/+linux/fs.ha:access).
|
||||
// Returns -ENAMETOOLONG (-36) if the path overflows PATH_MAX.
|
||||
export fn access(path: str, mode: i32) i32 = {
|
||||
let p: *u8 = kpath(path);
|
||||
if (p == nil: *u8) { return -36i32; };
|
||||
return syscall2(nr.ACCESS, p: i64, mode: i64): i32;
|
||||
};
|
||||
|
||||
// remove — unlink(2). Hare name; the underlying syscall is unlink(2).
|
||||
export fn remove(path: *u8) i32 = {
|
||||
return syscall1(nr.UNLINK, path: i64): i32;
|
||||
// remove — unlink(2). Mirrors Hare's os::remove
|
||||
// (ref/hare/os/os.ha:12).
|
||||
export fn remove(path: str) i32 = {
|
||||
let p: *u8 = kpath(path);
|
||||
if (p == nil: *u8) { return -36i32; };
|
||||
return syscall1(nr.UNLINK, p: i64): i32;
|
||||
};
|
||||
|
||||
// mkdir — mkdir(2). Path must be NUL-terminated. Mode is the unix
|
||||
// permission bitset (e.g. 0o700). Returns 0 on success, negative
|
||||
// errno otherwise. Hare name (os::mkdir).
|
||||
export fn mkdir(path: *u8, mode: i32) i32 = {
|
||||
return syscall2(nr.MKDIR, path: i64, mode: i64): i32;
|
||||
// mkdir — mkdir(2). Mode is the unix permission bitset (e.g. 0o700).
|
||||
// Returns 0 on success, negative errno otherwise. Mirrors Hare's
|
||||
// os::mkdir (ref/hare/os/os.ha:50).
|
||||
export fn mkdir(path: str, mode: i32) i32 = {
|
||||
let p: *u8 = kpath(path);
|
||||
if (p == nil: *u8) { return -36i32; };
|
||||
return syscall2(nr.MKDIR, p: i64, mode: i64): i32;
|
||||
};
|
||||
|
||||
// rmdir — rmdir(2). Path must be NUL-terminated. Returns 0 on
|
||||
// success, negative errno otherwise. Hare name (os::rmdir).
|
||||
export fn rmdir(path: *u8) i32 = {
|
||||
return syscall1(nr.RMDIR, path: i64): i32;
|
||||
// rmdir — rmdir(2). Mirrors Hare's os::rmdir
|
||||
// (ref/hare/os/os.ha:58).
|
||||
export fn rmdir(path: str) i32 = {
|
||||
let p: *u8 = kpath(path);
|
||||
if (p == nil: *u8) { return -36i32; };
|
||||
return syscall1(nr.RMDIR, p: i64): i32;
|
||||
};
|
||||
|
||||
// mkdirs — recursive mkdir. Creates `path` and any non-existent
|
||||
@@ -220,29 +263,28 @@ export fn rmdir(path: *u8) i32 = {
|
||||
// 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; };
|
||||
// Mirrors Hare's os::mkdirs (ref/hare/os/os.ha:54). The in-place
|
||||
// '/' → NUL splice walks the kpath-loaded [[pathbuf]] directly
|
||||
// instead of recursing through [[mkdir]] — re-entering kpath would
|
||||
// clobber the buffer mid-walk (single static slot, see kpath's
|
||||
// non-reentrancy note above).
|
||||
export fn mkdirs(path: str, mode: i32) (void | oserror) = {
|
||||
let cp: *u8 = kpath(path);
|
||||
if (cp == nil: *u8) { return -36i64: oserror; };
|
||||
let n: i32 = path.len;
|
||||
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.
|
||||
// raw MKDIR syscall on pathbuf, 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 (pathbuf[i] == 47u8) { // '/'
|
||||
pathbuf[i] = 0u8;
|
||||
let r: i32 = syscall2(nr.MKDIR,
|
||||
(&pathbuf[0]): i64, mode: i64): i32;
|
||||
pathbuf[i] = 47u8;
|
||||
if (r < 0) {
|
||||
if (r != -17) { return r: i64: oserror; };
|
||||
};
|
||||
@@ -250,8 +292,8 @@ export fn mkdirs(path: *u8, mode: i32) (void | oserror) = {
|
||||
i += 1;
|
||||
};
|
||||
|
||||
// mkdir the full path.
|
||||
let r: i32 = mkdir(path, mode);
|
||||
let r: i32 = syscall2(nr.MKDIR,
|
||||
(&pathbuf[0]): i64, mode: i64): i32;
|
||||
if (r < 0) {
|
||||
if (r != -17) { return r: i64: oserror; };
|
||||
};
|
||||
@@ -269,9 +311,14 @@ export fn fork() i32 = {
|
||||
return syscall0(nr.FORK): i32;
|
||||
};
|
||||
|
||||
// execve(2): on success, does not return.
|
||||
export fn execve(path: *u8, argv: **u8, envp: **u8) i32 = {
|
||||
return syscall3(nr.EXECVE, path: i64, argv: i64, envp: i64): i32;
|
||||
// execve(2): on success, does not return. Mirrors Hare's
|
||||
// os::exec::exec path arg (str). argv/envp stay `**u8` — the
|
||||
// kernel takes a NUL-pointer-terminated table of NUL-terminated
|
||||
// C strings, a different shape from a path.
|
||||
export fn execve(path: str, argv: **u8, envp: **u8) i32 = {
|
||||
let p: *u8 = kpath(path);
|
||||
if (p == nil: *u8) { return -36i32; };
|
||||
return syscall3(nr.EXECVE, p: i64, argv: i64, envp: i64): i32;
|
||||
};
|
||||
|
||||
// wait4(2): wait for `pid` (or any child if -1), store status in
|
||||
@@ -544,27 +591,31 @@ fn fillfilestat(out: *filestat, k: *kstat) void = {
|
||||
};
|
||||
|
||||
// stat — fill *out with metadata for `path`. Follows symlinks.
|
||||
// `path` must be NUL-terminated (lib/os convention; see task #23
|
||||
// for a planned `path: str` migration).
|
||||
// Returns ENAMETOOLONG (-36) as `oserror` if the path overflows
|
||||
// PATH_MAX.
|
||||
//
|
||||
// Mirrors Hare's sys::stat (ref/hare/sys/+linux/stat.ha:51) modulo
|
||||
// the out-param shape forced by the cgreturn 24B cap. Note: Hare's
|
||||
// higher-level fs::stat (ref/hare/fs/fs.ha:172) instead has lstat
|
||||
// semantics — we follow sys::stat's POSIX-stat behavior here.
|
||||
export fn stat(out: *filestat, path: *u8) (void | oserror) = {
|
||||
export fn stat(out: *filestat, path: str) (void | oserror) = {
|
||||
let cp: *u8 = kpath(path);
|
||||
if (cp == nil: *u8) { return -36i64: oserror; };
|
||||
let k: kstat;
|
||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
||||
AT_FDCWD: i64, path: i64, (&k): i64, 0i64);
|
||||
AT_FDCWD: i64, cp: i64, (&k): i64, 0i64);
|
||||
if (r < 0) { return r: oserror; };
|
||||
fillfilestat(out, &k);
|
||||
};
|
||||
|
||||
// lstat — like [[stat]] but does NOT follow a terminal symlink.
|
||||
// Mirrors Hare's sys::lstat (ref/hare/sys/+linux/stat.ha:57).
|
||||
export fn lstat(out: *filestat, path: *u8) (void | oserror) = {
|
||||
export fn lstat(out: *filestat, path: str) (void | oserror) = {
|
||||
let cp: *u8 = kpath(path);
|
||||
if (cp == nil: *u8) { return -36i64: oserror; };
|
||||
let k: kstat;
|
||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
||||
AT_FDCWD: i64, path: i64, (&k): i64,
|
||||
AT_FDCWD: i64, cp: i64, (&k): i64,
|
||||
AT_SYMLINK_NOFOLLOW: i64);
|
||||
if (r < 0) { return r: oserror; };
|
||||
fillfilestat(out, &k);
|
||||
@@ -585,7 +636,9 @@ export fn fstat(out: *filestat, fd: i32) (void | oserror) = {
|
||||
// exists — true if `path` resolves to anything (regular file,
|
||||
// directory, symlink, ...). Stat-shaped (Hare's `fs::exists`,
|
||||
// ref/hare/fs/fs.ha:196) — no separate syscall. Symlinks are
|
||||
// followed; a dangling symlink is `false`.
|
||||
// followed; a dangling symlink is `false`. ENAMETOOLONG is
|
||||
// swallowed as `false` — Hare's os::exists doc says "true if a
|
||||
// node exists at the given path, or false if not."
|
||||
//
|
||||
// Race warning: prefer "open and handle the error" over "exists
|
||||
// then open" in real code (Hare's docstring carries the same
|
||||
@@ -598,9 +651,11 @@ export fn fstat(out: *filestat, fd: i32) (void | oserror) = {
|
||||
// — same class as STATUS #22, surfaced first time a match on this
|
||||
// shape combined with an 80B local-struct local frame). Use the
|
||||
// match shape once #22 lands.
|
||||
export fn exists(path: *u8) bool = {
|
||||
export fn exists(path: str) bool = {
|
||||
let cp: *u8 = kpath(path);
|
||||
if (cp == nil: *u8) { return false; };
|
||||
let k: kstat;
|
||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
||||
AT_FDCWD: i64, path: i64, (&k): i64, 0i64);
|
||||
AT_FDCWD: i64, cp: i64, (&k): i64, 0i64);
|
||||
return r >= 0i64;
|
||||
};
|
||||
|
||||
@@ -56,7 +56,7 @@ fn istype(m: os.mode, t: os.mode) bool = {
|
||||
| os.stat_mask.SIZE | os.stat_mask.INODE
|
||||
| os.stat_mask.ATIME | os.stat_mask.MTIME
|
||||
| os.stat_mask.CTIME) as u32;
|
||||
match (os.stat(&fi, p.ptr)) {
|
||||
match (os.stat(&fi, p)) {
|
||||
case let e: os.oserror => fail();
|
||||
case void => {
|
||||
if ((fi.mask as u32) != wantmask) { fail(); };
|
||||
@@ -87,7 +87,7 @@ fn istype(m: os.mode, t: os.mode) bool = {
|
||||
@test fn test_stat_subdir() void = {
|
||||
let p = envpath("WW_TEST_STAT_SUBDIR");
|
||||
let fi: os.filestat;
|
||||
match (os.stat(&fi, p.ptr)) {
|
||||
match (os.stat(&fi, p)) {
|
||||
case let e: os.oserror => fail();
|
||||
case void => {
|
||||
if (!istype(fi.mode, os.mode.DIR)) { fail(); };
|
||||
@@ -100,7 +100,7 @@ fn istype(m: os.mode, t: os.mode) bool = {
|
||||
@test fn test_stat_noent() void = {
|
||||
let p = envpath("WW_TEST_STAT_NOENT");
|
||||
let fi: os.filestat;
|
||||
match (os.stat(&fi, p.ptr)) {
|
||||
match (os.stat(&fi, p)) {
|
||||
case void => fail();
|
||||
case let e: os.oserror => {
|
||||
// ENOENT = 2 → raw errno is -2.
|
||||
@@ -117,7 +117,7 @@ fn istype(m: os.mode, t: os.mode) bool = {
|
||||
@test fn test_stat_symlink_follow() void = {
|
||||
let p = envpath("WW_TEST_STAT_SYMLINK");
|
||||
let fi: os.filestat;
|
||||
match (os.stat(&fi, p.ptr)) {
|
||||
match (os.stat(&fi, p)) {
|
||||
case let e: os.oserror => fail();
|
||||
case void => {
|
||||
if (!istype(fi.mode, os.mode.REG)) { fail(); };
|
||||
@@ -129,7 +129,7 @@ fn istype(m: os.mode, t: os.mode) bool = {
|
||||
@test fn test_lstat_symlink_nofollow() void = {
|
||||
let p = envpath("WW_TEST_STAT_SYMLINK");
|
||||
let fi: os.filestat;
|
||||
match (os.lstat(&fi, p.ptr)) {
|
||||
match (os.lstat(&fi, p)) {
|
||||
case let e: os.oserror => fail();
|
||||
case void => {
|
||||
if (!istype(fi.mode, os.mode.LINK)) { fail(); };
|
||||
@@ -141,7 +141,7 @@ fn istype(m: os.mode, t: os.mode) bool = {
|
||||
|
||||
@test fn test_fstat_regfile() void = {
|
||||
let p = envpath("WW_TEST_STAT_REGFILE");
|
||||
let fd: i32 = os.open(p.ptr, os.flag.RDONLY, 0i32);
|
||||
let fd: i32 = os.open(p, os.flag.RDONLY, 0i32);
|
||||
if (fd < 0) { fail(); };
|
||||
let fi: os.filestat;
|
||||
match (os.fstat(&fi, fd)) {
|
||||
@@ -158,17 +158,53 @@ fn istype(m: os.mode, t: os.mode) bool = {
|
||||
|
||||
@test fn test_exists_regfile() void = {
|
||||
let p = envpath("WW_TEST_STAT_REGFILE");
|
||||
if (!os.exists(p.ptr)) { fail(); };
|
||||
if (!os.exists(p)) { fail(); };
|
||||
};
|
||||
|
||||
@test fn test_exists_subdir() void = {
|
||||
let p = envpath("WW_TEST_STAT_SUBDIR");
|
||||
if (!os.exists(p.ptr)) { fail(); };
|
||||
if (!os.exists(p)) { fail(); };
|
||||
};
|
||||
|
||||
@test fn test_exists_noent() void = {
|
||||
let p = envpath("WW_TEST_STAT_NOENT");
|
||||
if (os.exists(p.ptr)) { fail(); };
|
||||
if (os.exists(p)) { fail(); };
|
||||
};
|
||||
|
||||
// ---- ENAMETOOLONG: kpath rejects paths >= PATH_MAX -------------------
|
||||
//
|
||||
// kpath copies into a single [PATH_MAX]u8 buffer and reserves one byte
|
||||
// for the NUL terminator (`p.len + 1 >= PATH_MAX` → reject). The
|
||||
// rejection surfaces as `oserror = -36` (ENAMETOOLONG) on (... |
|
||||
// oserror)-returning wrappers, and as `false` on [[os.exists]]
|
||||
// (Hare's os::exists doc: "true if a node exists at the given path,
|
||||
// or false if not.").
|
||||
|
||||
let bigbuf: [4200]u8;
|
||||
|
||||
fn makebig(n: i32) str = {
|
||||
let i: i32 = 0;
|
||||
for (i < n) { bigbuf[i] = 97u8; i += 1; }; // 'a'
|
||||
let r: str;
|
||||
r.ptr = &bigbuf[0];
|
||||
r.len = n;
|
||||
return r;
|
||||
};
|
||||
|
||||
@test fn test_stat_toolong() void = {
|
||||
let bp = makebig(os.PATH_MAX);
|
||||
let fi: os.filestat;
|
||||
match (os.stat(&fi, bp)) {
|
||||
case void => fail();
|
||||
case let e: os.oserror => {
|
||||
if ((e: i64) != -36i64) { fail(); }; // ENAMETOOLONG
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@test fn test_exists_toolong() void = {
|
||||
let bp = makebig(os.PATH_MAX);
|
||||
if (os.exists(bp)) { fail(); };
|
||||
};
|
||||
|
||||
export fn main() i32 = {
|
||||
@@ -181,5 +217,7 @@ export fn main() i32 = {
|
||||
signalled = 7; test_exists_regfile();
|
||||
signalled = 8; test_exists_subdir();
|
||||
signalled = 9; test_exists_noent();
|
||||
signalled = 10; test_stat_toolong();
|
||||
signalled = 11; test_exists_toolong();
|
||||
return 0;
|
||||
};
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -88,6 +88,36 @@ export fn exit(code: i32) void = {
|
||||
syscall1(nr.EXIT, code: i64);
|
||||
};
|
||||
|
||||
// PATH_MAX / pathbuf / kpath — port of Hare's ref/hare/sys/+linux/
|
||||
// syscalls.ha:25,27,29-55. Hare's `path` accepts a sum `(str |
|
||||
// []u8 | *const u8)`; ww's lib/os public surface narrows to `str`
|
||||
// (the Hare-faithful surface at ref/hare/os/os.ha:37,47,50 etc).
|
||||
// Internally, [[kpath]] copies the `str` bytes into a single
|
||||
// module-level [[pathbuf]] scratch slot and NUL-terminates so the
|
||||
// raw Linux syscalls (which require C strings) see a valid
|
||||
// terminator. Same precedent as Hare's static `pathbuf`.
|
||||
//
|
||||
// Non-reentrant: one buffer, every [[stat]] / [[open]] / etc.
|
||||
// rewrites it. Same caveat as strconv's `*tos` family (overwritten
|
||||
// on next call). Caller must NOT hold a kpath-returned pointer
|
||||
// across another lib/os path call. Graduates when ww grows a
|
||||
// thread story.
|
||||
//
|
||||
// `nil`-as-overflow over `(*u8 | oserror)`: wwstage over-allocates
|
||||
// 1-word-payload tagged returns to 24B (cstage emits 16B).
|
||||
// Task #9; revert at task #10 when fixed. Repro at
|
||||
// .ai/probe_tagged_return_pointer_payload.ww.
|
||||
export def PATH_MAX: i32 = 4096;
|
||||
let pathbuf: [4096]u8;
|
||||
|
||||
fn kpath(p: str) *u8 = {
|
||||
if (p.len + 1 >= PATH_MAX) { return nil: *u8; }; // ENAMETOOLONG
|
||||
let i: i32 = 0;
|
||||
for (i < p.len) { pathbuf[i] = p[i]; i += 1; };
|
||||
pathbuf[p.len] = 0u8;
|
||||
return &pathbuf[0];
|
||||
};
|
||||
|
||||
// Raw, non-fallible primitives. These return Linux's int conventions
|
||||
// (negative = -errno, non-negative = bytes/fd/etc). Callers wanting a
|
||||
// Hare-style fallible API use the wrappers below.
|
||||
@@ -126,15 +156,17 @@ export fn trywrite(fd: i32, buf: *u8, n: u64) (i64 | oserror) = {
|
||||
return r;
|
||||
};
|
||||
|
||||
// open — Linux open(2). Path must be NUL-terminated; callers using ww
|
||||
// `str` must ensure the bytes are followed by a 0 byte (literals are,
|
||||
// arena-copied paths usually are by construction). Returns -errno on
|
||||
// failure, fd otherwise. Higher-level callers prefer `tryopen`.
|
||||
export fn open(path: *u8, flags: flag, mode: i32) i32 = {
|
||||
return syscall3(nr.OPEN, path: i64, (flags as i32): i64, mode: i64): i32;
|
||||
// open — Linux open(2). Returns -errno on failure, fd otherwise.
|
||||
// Higher-level callers prefer `tryopen`. Mirrors Hare's os::open
|
||||
// (ref/hare/os/os.ha:117); kpath lands the bytes in pathbuf.
|
||||
// Returns -ENAMETOOLONG (-36) if the path overflows PATH_MAX.
|
||||
export fn open(path: str, flags: flag, mode: i32) i32 = {
|
||||
let p: *u8 = kpath(path);
|
||||
if (p == nil: *u8) { return -36i32; }; // ENAMETOOLONG
|
||||
return syscall3(nr.OPEN, p: i64, (flags as i32): i64, mode: i64): i32;
|
||||
};
|
||||
|
||||
export fn tryopen(path: *u8, flags: flag, mode: i32) (i32 | oserror) = {
|
||||
export fn tryopen(path: str, flags: flag, mode: i32) (i32 | oserror) = {
|
||||
let fd: i32 = open(path, flags, mode);
|
||||
if (fd < 0) { return fd: i64: oserror; };
|
||||
return fd;
|
||||
@@ -194,26 +226,37 @@ export fn writeall(fd: i32, buf: *u8, n: u64) (i64 | oserror) = {
|
||||
|
||||
// access(2): returns 0 if the file is reachable, negative errno
|
||||
// otherwise. mode is the bitset described in <unistd.h> (F_OK=0).
|
||||
export fn access(path: *u8, mode: i32) i32 = {
|
||||
return syscall2(nr.ACCESS, path: i64, mode: i64): i32;
|
||||
// Mirrors Hare's os::access (ref/hare/os/+linux/fs.ha:access).
|
||||
// Returns -ENAMETOOLONG (-36) if the path overflows PATH_MAX.
|
||||
export fn access(path: str, mode: i32) i32 = {
|
||||
let p: *u8 = kpath(path);
|
||||
if (p == nil: *u8) { return -36i32; };
|
||||
return syscall2(nr.ACCESS, p: i64, mode: i64): i32;
|
||||
};
|
||||
|
||||
// remove — unlink(2). Hare name; the underlying syscall is unlink(2).
|
||||
export fn remove(path: *u8) i32 = {
|
||||
return syscall1(nr.UNLINK, path: i64): i32;
|
||||
// remove — unlink(2). Mirrors Hare's os::remove
|
||||
// (ref/hare/os/os.ha:12).
|
||||
export fn remove(path: str) i32 = {
|
||||
let p: *u8 = kpath(path);
|
||||
if (p == nil: *u8) { return -36i32; };
|
||||
return syscall1(nr.UNLINK, p: i64): i32;
|
||||
};
|
||||
|
||||
// mkdir — mkdir(2). Path must be NUL-terminated. Mode is the unix
|
||||
// permission bitset (e.g. 0o700). Returns 0 on success, negative
|
||||
// errno otherwise. Hare name (os::mkdir).
|
||||
export fn mkdir(path: *u8, mode: i32) i32 = {
|
||||
return syscall2(nr.MKDIR, path: i64, mode: i64): i32;
|
||||
// mkdir — mkdir(2). Mode is the unix permission bitset (e.g. 0o700).
|
||||
// Returns 0 on success, negative errno otherwise. Mirrors Hare's
|
||||
// os::mkdir (ref/hare/os/os.ha:50).
|
||||
export fn mkdir(path: str, mode: i32) i32 = {
|
||||
let p: *u8 = kpath(path);
|
||||
if (p == nil: *u8) { return -36i32; };
|
||||
return syscall2(nr.MKDIR, p: i64, mode: i64): i32;
|
||||
};
|
||||
|
||||
// rmdir — rmdir(2). Path must be NUL-terminated. Returns 0 on
|
||||
// success, negative errno otherwise. Hare name (os::rmdir).
|
||||
export fn rmdir(path: *u8) i32 = {
|
||||
return syscall1(nr.RMDIR, path: i64): i32;
|
||||
// rmdir — rmdir(2). Mirrors Hare's os::rmdir
|
||||
// (ref/hare/os/os.ha:58).
|
||||
export fn rmdir(path: str) i32 = {
|
||||
let p: *u8 = kpath(path);
|
||||
if (p == nil: *u8) { return -36i32; };
|
||||
return syscall1(nr.RMDIR, p: i64): i32;
|
||||
};
|
||||
|
||||
// mkdirs — recursive mkdir. Creates `path` and any non-existent
|
||||
@@ -221,29 +264,28 @@ export fn rmdir(path: *u8) i32 = {
|
||||
// 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; };
|
||||
// Mirrors Hare's os::mkdirs (ref/hare/os/os.ha:54). The in-place
|
||||
// '/' → NUL splice walks the kpath-loaded [[pathbuf]] directly
|
||||
// instead of recursing through [[mkdir]] — re-entering kpath would
|
||||
// clobber the buffer mid-walk (single static slot, see kpath's
|
||||
// non-reentrancy note above).
|
||||
export fn mkdirs(path: str, mode: i32) (void | oserror) = {
|
||||
let cp: *u8 = kpath(path);
|
||||
if (cp == nil: *u8) { return -36i64: oserror; };
|
||||
let n: i32 = path.len;
|
||||
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.
|
||||
// raw MKDIR syscall on pathbuf, 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 (pathbuf[i] == 47u8) { // '/'
|
||||
pathbuf[i] = 0u8;
|
||||
let r: i32 = syscall2(nr.MKDIR,
|
||||
(&pathbuf[0]): i64, mode: i64): i32;
|
||||
pathbuf[i] = 47u8;
|
||||
if (r < 0) {
|
||||
if (r != -17) { return r: i64: oserror; };
|
||||
};
|
||||
@@ -251,8 +293,8 @@ export fn mkdirs(path: *u8, mode: i32) (void | oserror) = {
|
||||
i += 1;
|
||||
};
|
||||
|
||||
// mkdir the full path.
|
||||
let r: i32 = mkdir(path, mode);
|
||||
let r: i32 = syscall2(nr.MKDIR,
|
||||
(&pathbuf[0]): i64, mode: i64): i32;
|
||||
if (r < 0) {
|
||||
if (r != -17) { return r: i64: oserror; };
|
||||
};
|
||||
@@ -270,9 +312,14 @@ export fn fork() i32 = {
|
||||
return syscall0(nr.FORK): i32;
|
||||
};
|
||||
|
||||
// execve(2): on success, does not return.
|
||||
export fn execve(path: *u8, argv: **u8, envp: **u8) i32 = {
|
||||
return syscall3(nr.EXECVE, path: i64, argv: i64, envp: i64): i32;
|
||||
// execve(2): on success, does not return. Mirrors Hare's
|
||||
// os::exec::exec path arg (str). argv/envp stay `**u8` — the
|
||||
// kernel takes a NUL-pointer-terminated table of NUL-terminated
|
||||
// C strings, a different shape from a path.
|
||||
export fn execve(path: str, argv: **u8, envp: **u8) i32 = {
|
||||
let p: *u8 = kpath(path);
|
||||
if (p == nil: *u8) { return -36i32; };
|
||||
return syscall3(nr.EXECVE, p: i64, argv: i64, envp: i64): i32;
|
||||
};
|
||||
|
||||
// wait4(2): wait for `pid` (or any child if -1), store status in
|
||||
@@ -545,27 +592,31 @@ fn fillfilestat(out: *filestat, k: *kstat) void = {
|
||||
};
|
||||
|
||||
// stat — fill *out with metadata for `path`. Follows symlinks.
|
||||
// `path` must be NUL-terminated (lib/os convention; see task #23
|
||||
// for a planned `path: str` migration).
|
||||
// Returns ENAMETOOLONG (-36) as `oserror` if the path overflows
|
||||
// PATH_MAX.
|
||||
//
|
||||
// Mirrors Hare's sys::stat (ref/hare/sys/+linux/stat.ha:51) modulo
|
||||
// the out-param shape forced by the cgreturn 24B cap. Note: Hare's
|
||||
// higher-level fs::stat (ref/hare/fs/fs.ha:172) instead has lstat
|
||||
// semantics — we follow sys::stat's POSIX-stat behavior here.
|
||||
export fn stat(out: *filestat, path: *u8) (void | oserror) = {
|
||||
export fn stat(out: *filestat, path: str) (void | oserror) = {
|
||||
let cp: *u8 = kpath(path);
|
||||
if (cp == nil: *u8) { return -36i64: oserror; };
|
||||
let k: kstat;
|
||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
||||
AT_FDCWD: i64, path: i64, (&k): i64, 0i64);
|
||||
AT_FDCWD: i64, cp: i64, (&k): i64, 0i64);
|
||||
if (r < 0) { return r: oserror; };
|
||||
fillfilestat(out, &k);
|
||||
};
|
||||
|
||||
// lstat — like [[stat]] but does NOT follow a terminal symlink.
|
||||
// Mirrors Hare's sys::lstat (ref/hare/sys/+linux/stat.ha:57).
|
||||
export fn lstat(out: *filestat, path: *u8) (void | oserror) = {
|
||||
export fn lstat(out: *filestat, path: str) (void | oserror) = {
|
||||
let cp: *u8 = kpath(path);
|
||||
if (cp == nil: *u8) { return -36i64: oserror; };
|
||||
let k: kstat;
|
||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
||||
AT_FDCWD: i64, path: i64, (&k): i64,
|
||||
AT_FDCWD: i64, cp: i64, (&k): i64,
|
||||
AT_SYMLINK_NOFOLLOW: i64);
|
||||
if (r < 0) { return r: oserror; };
|
||||
fillfilestat(out, &k);
|
||||
@@ -586,7 +637,9 @@ export fn fstat(out: *filestat, fd: i32) (void | oserror) = {
|
||||
// exists — true if `path` resolves to anything (regular file,
|
||||
// directory, symlink, ...). Stat-shaped (Hare's `fs::exists`,
|
||||
// ref/hare/fs/fs.ha:196) — no separate syscall. Symlinks are
|
||||
// followed; a dangling symlink is `false`.
|
||||
// followed; a dangling symlink is `false`. ENAMETOOLONG is
|
||||
// swallowed as `false` — Hare's os::exists doc says "true if a
|
||||
// node exists at the given path, or false if not."
|
||||
//
|
||||
// Race warning: prefer "open and handle the error" over "exists
|
||||
// then open" in real code (Hare's docstring carries the same
|
||||
@@ -599,10 +652,12 @@ export fn fstat(out: *filestat, fd: i32) (void | oserror) = {
|
||||
// — same class as STATUS #22, surfaced first time a match on this
|
||||
// shape combined with an 80B local-struct local frame). Use the
|
||||
// match shape once #22 lands.
|
||||
export fn exists(path: *u8) bool = {
|
||||
export fn exists(path: str) bool = {
|
||||
let cp: *u8 = kpath(path);
|
||||
if (cp == nil: *u8) { return false; };
|
||||
let k: kstat;
|
||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
||||
AT_FDCWD: i64, path: i64, (&k): i64, 0i64);
|
||||
AT_FDCWD: i64, cp: i64, (&k): i64, 0i64);
|
||||
return r >= 0i64;
|
||||
};
|
||||
|
||||
@@ -2896,9 +2951,18 @@ fn cstrlen(p: *u8) u64 = {
|
||||
return n;
|
||||
};
|
||||
|
||||
// pathstr — view a NUL-terminated *u8 as a str. Bridges argv/arena
|
||||
// callers to lib/os entrypoints (str post-task-#23).
|
||||
fn pathstr(p: *u8) str = {
|
||||
let r: str;
|
||||
r.ptr = p;
|
||||
r.len = cstrlen(p): i32;
|
||||
return r;
|
||||
};
|
||||
|
||||
// Slurp the whole file into a fresh buffer.
|
||||
fn slurp(path: *u8) (*u8, u64) = {
|
||||
let fd: i32 = os.open(path, os.flag.RDONLY, 0i32);
|
||||
let fd: i32 = os.open(pathstr(path), os.flag.RDONLY, 0i32);
|
||||
if (fd < 0) { return nil, 0u64; };
|
||||
let szr: (i64 | os.oserror) = os.filesize(fd);
|
||||
let n: i64 = 0i64;
|
||||
@@ -2974,7 +3038,7 @@ export fn main(argc: i32, argv: **u8) i32 = {
|
||||
if (encode(&s) != 0) { return 1; };
|
||||
|
||||
// Open output for write.
|
||||
let fd: i32 = os.open(out, os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC, 420i32); // 0o644
|
||||
let fd: i32 = os.open(pathstr(out), os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC, 420i32); // 0o644
|
||||
if (fd < 0) {
|
||||
os.write(2, "w6a: cannot open output\n".ptr, 23u64);
|
||||
return 1;
|
||||
|
||||
@@ -30,9 +30,18 @@ fn cstrlen(p: *u8) u64 = {
|
||||
return n;
|
||||
};
|
||||
|
||||
// pathstr — view a NUL-terminated *u8 as a str. Bridges argv/arena
|
||||
// callers to lib/os entrypoints (str post-task-#23).
|
||||
fn pathstr(p: *u8) str = {
|
||||
let r: str;
|
||||
r.ptr = p;
|
||||
r.len = cstrlen(p): i32;
|
||||
return r;
|
||||
};
|
||||
|
||||
// Slurp the whole file into a fresh buffer.
|
||||
fn slurp(path: *u8) (*u8, u64) = {
|
||||
let fd: i32 = os.open(path, os.flag.RDONLY, 0i32);
|
||||
let fd: i32 = os.open(pathstr(path), os.flag.RDONLY, 0i32);
|
||||
if (fd < 0) { return nil, 0u64; };
|
||||
let szr: (i64 | os.oserror) = os.filesize(fd);
|
||||
let n: i64 = 0i64;
|
||||
@@ -108,7 +117,7 @@ export fn main(argc: i32, argv: **u8) i32 = {
|
||||
if (encode(&s) != 0) { return 1; };
|
||||
|
||||
// Open output for write.
|
||||
let fd: i32 = os.open(out, os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC, 420i32); // 0o644
|
||||
let fd: i32 = os.open(pathstr(out), os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC, 420i32); // 0o644
|
||||
if (fd < 0) {
|
||||
os.write(2, "w6a: cannot open output\n".ptr, 23u64);
|
||||
return 1;
|
||||
|
||||
@@ -88,6 +88,36 @@ export fn exit(code: i32) void = {
|
||||
syscall1(nr.EXIT, code: i64);
|
||||
};
|
||||
|
||||
// PATH_MAX / pathbuf / kpath — port of Hare's ref/hare/sys/+linux/
|
||||
// syscalls.ha:25,27,29-55. Hare's `path` accepts a sum `(str |
|
||||
// []u8 | *const u8)`; ww's lib/os public surface narrows to `str`
|
||||
// (the Hare-faithful surface at ref/hare/os/os.ha:37,47,50 etc).
|
||||
// Internally, [[kpath]] copies the `str` bytes into a single
|
||||
// module-level [[pathbuf]] scratch slot and NUL-terminates so the
|
||||
// raw Linux syscalls (which require C strings) see a valid
|
||||
// terminator. Same precedent as Hare's static `pathbuf`.
|
||||
//
|
||||
// Non-reentrant: one buffer, every [[stat]] / [[open]] / etc.
|
||||
// rewrites it. Same caveat as strconv's `*tos` family (overwritten
|
||||
// on next call). Caller must NOT hold a kpath-returned pointer
|
||||
// across another lib/os path call. Graduates when ww grows a
|
||||
// thread story.
|
||||
//
|
||||
// `nil`-as-overflow over `(*u8 | oserror)`: wwstage over-allocates
|
||||
// 1-word-payload tagged returns to 24B (cstage emits 16B).
|
||||
// Task #9; revert at task #10 when fixed. Repro at
|
||||
// .ai/probe_tagged_return_pointer_payload.ww.
|
||||
export def PATH_MAX: i32 = 4096;
|
||||
let pathbuf: [4096]u8;
|
||||
|
||||
fn kpath(p: str) *u8 = {
|
||||
if (p.len + 1 >= PATH_MAX) { return nil: *u8; }; // ENAMETOOLONG
|
||||
let i: i32 = 0;
|
||||
for (i < p.len) { pathbuf[i] = p[i]; i += 1; };
|
||||
pathbuf[p.len] = 0u8;
|
||||
return &pathbuf[0];
|
||||
};
|
||||
|
||||
// Raw, non-fallible primitives. These return Linux's int conventions
|
||||
// (negative = -errno, non-negative = bytes/fd/etc). Callers wanting a
|
||||
// Hare-style fallible API use the wrappers below.
|
||||
@@ -126,15 +156,17 @@ export fn trywrite(fd: i32, buf: *u8, n: u64) (i64 | oserror) = {
|
||||
return r;
|
||||
};
|
||||
|
||||
// open — Linux open(2). Path must be NUL-terminated; callers using ww
|
||||
// `str` must ensure the bytes are followed by a 0 byte (literals are,
|
||||
// arena-copied paths usually are by construction). Returns -errno on
|
||||
// failure, fd otherwise. Higher-level callers prefer `tryopen`.
|
||||
export fn open(path: *u8, flags: flag, mode: i32) i32 = {
|
||||
return syscall3(nr.OPEN, path: i64, (flags as i32): i64, mode: i64): i32;
|
||||
// open — Linux open(2). Returns -errno on failure, fd otherwise.
|
||||
// Higher-level callers prefer `tryopen`. Mirrors Hare's os::open
|
||||
// (ref/hare/os/os.ha:117); kpath lands the bytes in pathbuf.
|
||||
// Returns -ENAMETOOLONG (-36) if the path overflows PATH_MAX.
|
||||
export fn open(path: str, flags: flag, mode: i32) i32 = {
|
||||
let p: *u8 = kpath(path);
|
||||
if (p == nil: *u8) { return -36i32; }; // ENAMETOOLONG
|
||||
return syscall3(nr.OPEN, p: i64, (flags as i32): i64, mode: i64): i32;
|
||||
};
|
||||
|
||||
export fn tryopen(path: *u8, flags: flag, mode: i32) (i32 | oserror) = {
|
||||
export fn tryopen(path: str, flags: flag, mode: i32) (i32 | oserror) = {
|
||||
let fd: i32 = open(path, flags, mode);
|
||||
if (fd < 0) { return fd: i64: oserror; };
|
||||
return fd;
|
||||
@@ -194,26 +226,37 @@ export fn writeall(fd: i32, buf: *u8, n: u64) (i64 | oserror) = {
|
||||
|
||||
// access(2): returns 0 if the file is reachable, negative errno
|
||||
// otherwise. mode is the bitset described in <unistd.h> (F_OK=0).
|
||||
export fn access(path: *u8, mode: i32) i32 = {
|
||||
return syscall2(nr.ACCESS, path: i64, mode: i64): i32;
|
||||
// Mirrors Hare's os::access (ref/hare/os/+linux/fs.ha:access).
|
||||
// Returns -ENAMETOOLONG (-36) if the path overflows PATH_MAX.
|
||||
export fn access(path: str, mode: i32) i32 = {
|
||||
let p: *u8 = kpath(path);
|
||||
if (p == nil: *u8) { return -36i32; };
|
||||
return syscall2(nr.ACCESS, p: i64, mode: i64): i32;
|
||||
};
|
||||
|
||||
// remove — unlink(2). Hare name; the underlying syscall is unlink(2).
|
||||
export fn remove(path: *u8) i32 = {
|
||||
return syscall1(nr.UNLINK, path: i64): i32;
|
||||
// remove — unlink(2). Mirrors Hare's os::remove
|
||||
// (ref/hare/os/os.ha:12).
|
||||
export fn remove(path: str) i32 = {
|
||||
let p: *u8 = kpath(path);
|
||||
if (p == nil: *u8) { return -36i32; };
|
||||
return syscall1(nr.UNLINK, p: i64): i32;
|
||||
};
|
||||
|
||||
// mkdir — mkdir(2). Path must be NUL-terminated. Mode is the unix
|
||||
// permission bitset (e.g. 0o700). Returns 0 on success, negative
|
||||
// errno otherwise. Hare name (os::mkdir).
|
||||
export fn mkdir(path: *u8, mode: i32) i32 = {
|
||||
return syscall2(nr.MKDIR, path: i64, mode: i64): i32;
|
||||
// mkdir — mkdir(2). Mode is the unix permission bitset (e.g. 0o700).
|
||||
// Returns 0 on success, negative errno otherwise. Mirrors Hare's
|
||||
// os::mkdir (ref/hare/os/os.ha:50).
|
||||
export fn mkdir(path: str, mode: i32) i32 = {
|
||||
let p: *u8 = kpath(path);
|
||||
if (p == nil: *u8) { return -36i32; };
|
||||
return syscall2(nr.MKDIR, p: i64, mode: i64): i32;
|
||||
};
|
||||
|
||||
// rmdir — rmdir(2). Path must be NUL-terminated. Returns 0 on
|
||||
// success, negative errno otherwise. Hare name (os::rmdir).
|
||||
export fn rmdir(path: *u8) i32 = {
|
||||
return syscall1(nr.RMDIR, path: i64): i32;
|
||||
// rmdir — rmdir(2). Mirrors Hare's os::rmdir
|
||||
// (ref/hare/os/os.ha:58).
|
||||
export fn rmdir(path: str) i32 = {
|
||||
let p: *u8 = kpath(path);
|
||||
if (p == nil: *u8) { return -36i32; };
|
||||
return syscall1(nr.RMDIR, p: i64): i32;
|
||||
};
|
||||
|
||||
// mkdirs — recursive mkdir. Creates `path` and any non-existent
|
||||
@@ -221,29 +264,28 @@ export fn rmdir(path: *u8) i32 = {
|
||||
// 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; };
|
||||
// Mirrors Hare's os::mkdirs (ref/hare/os/os.ha:54). The in-place
|
||||
// '/' → NUL splice walks the kpath-loaded [[pathbuf]] directly
|
||||
// instead of recursing through [[mkdir]] — re-entering kpath would
|
||||
// clobber the buffer mid-walk (single static slot, see kpath's
|
||||
// non-reentrancy note above).
|
||||
export fn mkdirs(path: str, mode: i32) (void | oserror) = {
|
||||
let cp: *u8 = kpath(path);
|
||||
if (cp == nil: *u8) { return -36i64: oserror; };
|
||||
let n: i32 = path.len;
|
||||
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.
|
||||
// raw MKDIR syscall on pathbuf, 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 (pathbuf[i] == 47u8) { // '/'
|
||||
pathbuf[i] = 0u8;
|
||||
let r: i32 = syscall2(nr.MKDIR,
|
||||
(&pathbuf[0]): i64, mode: i64): i32;
|
||||
pathbuf[i] = 47u8;
|
||||
if (r < 0) {
|
||||
if (r != -17) { return r: i64: oserror; };
|
||||
};
|
||||
@@ -251,8 +293,8 @@ export fn mkdirs(path: *u8, mode: i32) (void | oserror) = {
|
||||
i += 1;
|
||||
};
|
||||
|
||||
// mkdir the full path.
|
||||
let r: i32 = mkdir(path, mode);
|
||||
let r: i32 = syscall2(nr.MKDIR,
|
||||
(&pathbuf[0]): i64, mode: i64): i32;
|
||||
if (r < 0) {
|
||||
if (r != -17) { return r: i64: oserror; };
|
||||
};
|
||||
@@ -270,9 +312,14 @@ export fn fork() i32 = {
|
||||
return syscall0(nr.FORK): i32;
|
||||
};
|
||||
|
||||
// execve(2): on success, does not return.
|
||||
export fn execve(path: *u8, argv: **u8, envp: **u8) i32 = {
|
||||
return syscall3(nr.EXECVE, path: i64, argv: i64, envp: i64): i32;
|
||||
// execve(2): on success, does not return. Mirrors Hare's
|
||||
// os::exec::exec path arg (str). argv/envp stay `**u8` — the
|
||||
// kernel takes a NUL-pointer-terminated table of NUL-terminated
|
||||
// C strings, a different shape from a path.
|
||||
export fn execve(path: str, argv: **u8, envp: **u8) i32 = {
|
||||
let p: *u8 = kpath(path);
|
||||
if (p == nil: *u8) { return -36i32; };
|
||||
return syscall3(nr.EXECVE, p: i64, argv: i64, envp: i64): i32;
|
||||
};
|
||||
|
||||
// wait4(2): wait for `pid` (or any child if -1), store status in
|
||||
@@ -545,27 +592,31 @@ fn fillfilestat(out: *filestat, k: *kstat) void = {
|
||||
};
|
||||
|
||||
// stat — fill *out with metadata for `path`. Follows symlinks.
|
||||
// `path` must be NUL-terminated (lib/os convention; see task #23
|
||||
// for a planned `path: str` migration).
|
||||
// Returns ENAMETOOLONG (-36) as `oserror` if the path overflows
|
||||
// PATH_MAX.
|
||||
//
|
||||
// Mirrors Hare's sys::stat (ref/hare/sys/+linux/stat.ha:51) modulo
|
||||
// the out-param shape forced by the cgreturn 24B cap. Note: Hare's
|
||||
// higher-level fs::stat (ref/hare/fs/fs.ha:172) instead has lstat
|
||||
// semantics — we follow sys::stat's POSIX-stat behavior here.
|
||||
export fn stat(out: *filestat, path: *u8) (void | oserror) = {
|
||||
export fn stat(out: *filestat, path: str) (void | oserror) = {
|
||||
let cp: *u8 = kpath(path);
|
||||
if (cp == nil: *u8) { return -36i64: oserror; };
|
||||
let k: kstat;
|
||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
||||
AT_FDCWD: i64, path: i64, (&k): i64, 0i64);
|
||||
AT_FDCWD: i64, cp: i64, (&k): i64, 0i64);
|
||||
if (r < 0) { return r: oserror; };
|
||||
fillfilestat(out, &k);
|
||||
};
|
||||
|
||||
// lstat — like [[stat]] but does NOT follow a terminal symlink.
|
||||
// Mirrors Hare's sys::lstat (ref/hare/sys/+linux/stat.ha:57).
|
||||
export fn lstat(out: *filestat, path: *u8) (void | oserror) = {
|
||||
export fn lstat(out: *filestat, path: str) (void | oserror) = {
|
||||
let cp: *u8 = kpath(path);
|
||||
if (cp == nil: *u8) { return -36i64: oserror; };
|
||||
let k: kstat;
|
||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
||||
AT_FDCWD: i64, path: i64, (&k): i64,
|
||||
AT_FDCWD: i64, cp: i64, (&k): i64,
|
||||
AT_SYMLINK_NOFOLLOW: i64);
|
||||
if (r < 0) { return r: oserror; };
|
||||
fillfilestat(out, &k);
|
||||
@@ -586,7 +637,9 @@ export fn fstat(out: *filestat, fd: i32) (void | oserror) = {
|
||||
// exists — true if `path` resolves to anything (regular file,
|
||||
// directory, symlink, ...). Stat-shaped (Hare's `fs::exists`,
|
||||
// ref/hare/fs/fs.ha:196) — no separate syscall. Symlinks are
|
||||
// followed; a dangling symlink is `false`.
|
||||
// followed; a dangling symlink is `false`. ENAMETOOLONG is
|
||||
// swallowed as `false` — Hare's os::exists doc says "true if a
|
||||
// node exists at the given path, or false if not."
|
||||
//
|
||||
// Race warning: prefer "open and handle the error" over "exists
|
||||
// then open" in real code (Hare's docstring carries the same
|
||||
@@ -599,10 +652,12 @@ export fn fstat(out: *filestat, fd: i32) (void | oserror) = {
|
||||
// — same class as STATUS #22, surfaced first time a match on this
|
||||
// shape combined with an 80B local-struct local frame). Use the
|
||||
// match shape once #22 lands.
|
||||
export fn exists(path: *u8) bool = {
|
||||
export fn exists(path: str) bool = {
|
||||
let cp: *u8 = kpath(path);
|
||||
if (cp == nil: *u8) { return false; };
|
||||
let k: kstat;
|
||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
||||
AT_FDCWD: i64, path: i64, (&k): i64, 0i64);
|
||||
AT_FDCWD: i64, cp: i64, (&k): i64, 0i64);
|
||||
return r >= 0i64;
|
||||
};
|
||||
|
||||
@@ -19129,8 +19184,18 @@ fn cstrlen(p: *u8) u64 = {
|
||||
return n;
|
||||
};
|
||||
|
||||
// pathstr — view a NUL-terminated *u8 as a str. lib/os entrypoints
|
||||
// take str post-task-#23; this bridges call sites that still hold
|
||||
// C-string paths (argv entries, arena-allocated buffers).
|
||||
fn pathstr(p: *u8) str = {
|
||||
let r: str;
|
||||
r.ptr = p;
|
||||
r.len = cstrlen(p): i32;
|
||||
return r;
|
||||
};
|
||||
|
||||
fn slurp(path: *u8) (*u8, u64) = {
|
||||
let fd: i32 = os.open(path, os.flag.RDONLY, 0i32);
|
||||
let fd: i32 = os.open(pathstr(path), os.flag.RDONLY, 0i32);
|
||||
if (fd < 0) { return nil, 0u64; };
|
||||
let szr: (i64 | os.oserror) = os.filesize(fd);
|
||||
let n: i64 = 0i64;
|
||||
@@ -19196,7 +19261,7 @@ export fn main(argc: i32, argv: **u8) i32 = {
|
||||
// cgen.ww writes directly to fd 1; dup2 lets us reuse it without
|
||||
// threading a file descriptor through the emit helpers.
|
||||
if (out != nil) {
|
||||
let ofd: i32 = os.open(out,
|
||||
let ofd: i32 = os.open(pathstr(out),
|
||||
os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC, 420i32); // 0o644
|
||||
if (ofd < 0) {
|
||||
os.write(2, "w6c: cannot open output\n".ptr, 23u64);
|
||||
|
||||
@@ -39,8 +39,18 @@ fn cstrlen(p: *u8) u64 = {
|
||||
return n;
|
||||
};
|
||||
|
||||
// pathstr — view a NUL-terminated *u8 as a str. lib/os entrypoints
|
||||
// take str post-task-#23; this bridges call sites that still hold
|
||||
// C-string paths (argv entries, arena-allocated buffers).
|
||||
fn pathstr(p: *u8) str = {
|
||||
let r: str;
|
||||
r.ptr = p;
|
||||
r.len = cstrlen(p): i32;
|
||||
return r;
|
||||
};
|
||||
|
||||
fn slurp(path: *u8) (*u8, u64) = {
|
||||
let fd: i32 = os.open(path, os.flag.RDONLY, 0i32);
|
||||
let fd: i32 = os.open(pathstr(path), os.flag.RDONLY, 0i32);
|
||||
if (fd < 0) { return nil, 0u64; };
|
||||
let szr: (i64 | os.oserror) = os.filesize(fd);
|
||||
let n: i64 = 0i64;
|
||||
@@ -106,7 +116,7 @@ export fn main(argc: i32, argv: **u8) i32 = {
|
||||
// cgen.ww writes directly to fd 1; dup2 lets us reuse it without
|
||||
// threading a file descriptor through the emit helpers.
|
||||
if (out != nil) {
|
||||
let ofd: i32 = os.open(out,
|
||||
let ofd: i32 = os.open(pathstr(out),
|
||||
os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC, 420i32); // 0o644
|
||||
if (ofd < 0) {
|
||||
os.write(2, "w6c: cannot open output\n".ptr, 23u64);
|
||||
|
||||
@@ -124,7 +124,7 @@ fn dbasename(p: *u8) *u8 = {
|
||||
// ---- file slurp --------------------------------------------------------
|
||||
|
||||
fn slurpso(path: *u8) (*u8, u64) = {
|
||||
let fd: i32 = os.open(path, os.flag.RDONLY, 0i32);
|
||||
let fd: i32 = os.open(pathstr(path), os.flag.RDONLY, 0i32);
|
||||
if (fd < 0) { return nil, 0u64; };
|
||||
let szr: (i64 | os.oserror) = os.filesize(fd);
|
||||
let n: i64 = 0i64;
|
||||
|
||||
@@ -88,6 +88,36 @@ export fn exit(code: i32) void = {
|
||||
syscall1(nr.EXIT, code: i64);
|
||||
};
|
||||
|
||||
// PATH_MAX / pathbuf / kpath — port of Hare's ref/hare/sys/+linux/
|
||||
// syscalls.ha:25,27,29-55. Hare's `path` accepts a sum `(str |
|
||||
// []u8 | *const u8)`; ww's lib/os public surface narrows to `str`
|
||||
// (the Hare-faithful surface at ref/hare/os/os.ha:37,47,50 etc).
|
||||
// Internally, [[kpath]] copies the `str` bytes into a single
|
||||
// module-level [[pathbuf]] scratch slot and NUL-terminates so the
|
||||
// raw Linux syscalls (which require C strings) see a valid
|
||||
// terminator. Same precedent as Hare's static `pathbuf`.
|
||||
//
|
||||
// Non-reentrant: one buffer, every [[stat]] / [[open]] / etc.
|
||||
// rewrites it. Same caveat as strconv's `*tos` family (overwritten
|
||||
// on next call). Caller must NOT hold a kpath-returned pointer
|
||||
// across another lib/os path call. Graduates when ww grows a
|
||||
// thread story.
|
||||
//
|
||||
// `nil`-as-overflow over `(*u8 | oserror)`: wwstage over-allocates
|
||||
// 1-word-payload tagged returns to 24B (cstage emits 16B).
|
||||
// Task #9; revert at task #10 when fixed. Repro at
|
||||
// .ai/probe_tagged_return_pointer_payload.ww.
|
||||
export def PATH_MAX: i32 = 4096;
|
||||
let pathbuf: [4096]u8;
|
||||
|
||||
fn kpath(p: str) *u8 = {
|
||||
if (p.len + 1 >= PATH_MAX) { return nil: *u8; }; // ENAMETOOLONG
|
||||
let i: i32 = 0;
|
||||
for (i < p.len) { pathbuf[i] = p[i]; i += 1; };
|
||||
pathbuf[p.len] = 0u8;
|
||||
return &pathbuf[0];
|
||||
};
|
||||
|
||||
// Raw, non-fallible primitives. These return Linux's int conventions
|
||||
// (negative = -errno, non-negative = bytes/fd/etc). Callers wanting a
|
||||
// Hare-style fallible API use the wrappers below.
|
||||
@@ -126,15 +156,17 @@ export fn trywrite(fd: i32, buf: *u8, n: u64) (i64 | oserror) = {
|
||||
return r;
|
||||
};
|
||||
|
||||
// open — Linux open(2). Path must be NUL-terminated; callers using ww
|
||||
// `str` must ensure the bytes are followed by a 0 byte (literals are,
|
||||
// arena-copied paths usually are by construction). Returns -errno on
|
||||
// failure, fd otherwise. Higher-level callers prefer `tryopen`.
|
||||
export fn open(path: *u8, flags: flag, mode: i32) i32 = {
|
||||
return syscall3(nr.OPEN, path: i64, (flags as i32): i64, mode: i64): i32;
|
||||
// open — Linux open(2). Returns -errno on failure, fd otherwise.
|
||||
// Higher-level callers prefer `tryopen`. Mirrors Hare's os::open
|
||||
// (ref/hare/os/os.ha:117); kpath lands the bytes in pathbuf.
|
||||
// Returns -ENAMETOOLONG (-36) if the path overflows PATH_MAX.
|
||||
export fn open(path: str, flags: flag, mode: i32) i32 = {
|
||||
let p: *u8 = kpath(path);
|
||||
if (p == nil: *u8) { return -36i32; }; // ENAMETOOLONG
|
||||
return syscall3(nr.OPEN, p: i64, (flags as i32): i64, mode: i64): i32;
|
||||
};
|
||||
|
||||
export fn tryopen(path: *u8, flags: flag, mode: i32) (i32 | oserror) = {
|
||||
export fn tryopen(path: str, flags: flag, mode: i32) (i32 | oserror) = {
|
||||
let fd: i32 = open(path, flags, mode);
|
||||
if (fd < 0) { return fd: i64: oserror; };
|
||||
return fd;
|
||||
@@ -194,26 +226,37 @@ export fn writeall(fd: i32, buf: *u8, n: u64) (i64 | oserror) = {
|
||||
|
||||
// access(2): returns 0 if the file is reachable, negative errno
|
||||
// otherwise. mode is the bitset described in <unistd.h> (F_OK=0).
|
||||
export fn access(path: *u8, mode: i32) i32 = {
|
||||
return syscall2(nr.ACCESS, path: i64, mode: i64): i32;
|
||||
// Mirrors Hare's os::access (ref/hare/os/+linux/fs.ha:access).
|
||||
// Returns -ENAMETOOLONG (-36) if the path overflows PATH_MAX.
|
||||
export fn access(path: str, mode: i32) i32 = {
|
||||
let p: *u8 = kpath(path);
|
||||
if (p == nil: *u8) { return -36i32; };
|
||||
return syscall2(nr.ACCESS, p: i64, mode: i64): i32;
|
||||
};
|
||||
|
||||
// remove — unlink(2). Hare name; the underlying syscall is unlink(2).
|
||||
export fn remove(path: *u8) i32 = {
|
||||
return syscall1(nr.UNLINK, path: i64): i32;
|
||||
// remove — unlink(2). Mirrors Hare's os::remove
|
||||
// (ref/hare/os/os.ha:12).
|
||||
export fn remove(path: str) i32 = {
|
||||
let p: *u8 = kpath(path);
|
||||
if (p == nil: *u8) { return -36i32; };
|
||||
return syscall1(nr.UNLINK, p: i64): i32;
|
||||
};
|
||||
|
||||
// mkdir — mkdir(2). Path must be NUL-terminated. Mode is the unix
|
||||
// permission bitset (e.g. 0o700). Returns 0 on success, negative
|
||||
// errno otherwise. Hare name (os::mkdir).
|
||||
export fn mkdir(path: *u8, mode: i32) i32 = {
|
||||
return syscall2(nr.MKDIR, path: i64, mode: i64): i32;
|
||||
// mkdir — mkdir(2). Mode is the unix permission bitset (e.g. 0o700).
|
||||
// Returns 0 on success, negative errno otherwise. Mirrors Hare's
|
||||
// os::mkdir (ref/hare/os/os.ha:50).
|
||||
export fn mkdir(path: str, mode: i32) i32 = {
|
||||
let p: *u8 = kpath(path);
|
||||
if (p == nil: *u8) { return -36i32; };
|
||||
return syscall2(nr.MKDIR, p: i64, mode: i64): i32;
|
||||
};
|
||||
|
||||
// rmdir — rmdir(2). Path must be NUL-terminated. Returns 0 on
|
||||
// success, negative errno otherwise. Hare name (os::rmdir).
|
||||
export fn rmdir(path: *u8) i32 = {
|
||||
return syscall1(nr.RMDIR, path: i64): i32;
|
||||
// rmdir — rmdir(2). Mirrors Hare's os::rmdir
|
||||
// (ref/hare/os/os.ha:58).
|
||||
export fn rmdir(path: str) i32 = {
|
||||
let p: *u8 = kpath(path);
|
||||
if (p == nil: *u8) { return -36i32; };
|
||||
return syscall1(nr.RMDIR, p: i64): i32;
|
||||
};
|
||||
|
||||
// mkdirs — recursive mkdir. Creates `path` and any non-existent
|
||||
@@ -221,29 +264,28 @@ export fn rmdir(path: *u8) i32 = {
|
||||
// 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; };
|
||||
// Mirrors Hare's os::mkdirs (ref/hare/os/os.ha:54). The in-place
|
||||
// '/' → NUL splice walks the kpath-loaded [[pathbuf]] directly
|
||||
// instead of recursing through [[mkdir]] — re-entering kpath would
|
||||
// clobber the buffer mid-walk (single static slot, see kpath's
|
||||
// non-reentrancy note above).
|
||||
export fn mkdirs(path: str, mode: i32) (void | oserror) = {
|
||||
let cp: *u8 = kpath(path);
|
||||
if (cp == nil: *u8) { return -36i64: oserror; };
|
||||
let n: i32 = path.len;
|
||||
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.
|
||||
// raw MKDIR syscall on pathbuf, 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 (pathbuf[i] == 47u8) { // '/'
|
||||
pathbuf[i] = 0u8;
|
||||
let r: i32 = syscall2(nr.MKDIR,
|
||||
(&pathbuf[0]): i64, mode: i64): i32;
|
||||
pathbuf[i] = 47u8;
|
||||
if (r < 0) {
|
||||
if (r != -17) { return r: i64: oserror; };
|
||||
};
|
||||
@@ -251,8 +293,8 @@ export fn mkdirs(path: *u8, mode: i32) (void | oserror) = {
|
||||
i += 1;
|
||||
};
|
||||
|
||||
// mkdir the full path.
|
||||
let r: i32 = mkdir(path, mode);
|
||||
let r: i32 = syscall2(nr.MKDIR,
|
||||
(&pathbuf[0]): i64, mode: i64): i32;
|
||||
if (r < 0) {
|
||||
if (r != -17) { return r: i64: oserror; };
|
||||
};
|
||||
@@ -270,9 +312,14 @@ export fn fork() i32 = {
|
||||
return syscall0(nr.FORK): i32;
|
||||
};
|
||||
|
||||
// execve(2): on success, does not return.
|
||||
export fn execve(path: *u8, argv: **u8, envp: **u8) i32 = {
|
||||
return syscall3(nr.EXECVE, path: i64, argv: i64, envp: i64): i32;
|
||||
// execve(2): on success, does not return. Mirrors Hare's
|
||||
// os::exec::exec path arg (str). argv/envp stay `**u8` — the
|
||||
// kernel takes a NUL-pointer-terminated table of NUL-terminated
|
||||
// C strings, a different shape from a path.
|
||||
export fn execve(path: str, argv: **u8, envp: **u8) i32 = {
|
||||
let p: *u8 = kpath(path);
|
||||
if (p == nil: *u8) { return -36i32; };
|
||||
return syscall3(nr.EXECVE, p: i64, argv: i64, envp: i64): i32;
|
||||
};
|
||||
|
||||
// wait4(2): wait for `pid` (or any child if -1), store status in
|
||||
@@ -545,27 +592,31 @@ fn fillfilestat(out: *filestat, k: *kstat) void = {
|
||||
};
|
||||
|
||||
// stat — fill *out with metadata for `path`. Follows symlinks.
|
||||
// `path` must be NUL-terminated (lib/os convention; see task #23
|
||||
// for a planned `path: str` migration).
|
||||
// Returns ENAMETOOLONG (-36) as `oserror` if the path overflows
|
||||
// PATH_MAX.
|
||||
//
|
||||
// Mirrors Hare's sys::stat (ref/hare/sys/+linux/stat.ha:51) modulo
|
||||
// the out-param shape forced by the cgreturn 24B cap. Note: Hare's
|
||||
// higher-level fs::stat (ref/hare/fs/fs.ha:172) instead has lstat
|
||||
// semantics — we follow sys::stat's POSIX-stat behavior here.
|
||||
export fn stat(out: *filestat, path: *u8) (void | oserror) = {
|
||||
export fn stat(out: *filestat, path: str) (void | oserror) = {
|
||||
let cp: *u8 = kpath(path);
|
||||
if (cp == nil: *u8) { return -36i64: oserror; };
|
||||
let k: kstat;
|
||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
||||
AT_FDCWD: i64, path: i64, (&k): i64, 0i64);
|
||||
AT_FDCWD: i64, cp: i64, (&k): i64, 0i64);
|
||||
if (r < 0) { return r: oserror; };
|
||||
fillfilestat(out, &k);
|
||||
};
|
||||
|
||||
// lstat — like [[stat]] but does NOT follow a terminal symlink.
|
||||
// Mirrors Hare's sys::lstat (ref/hare/sys/+linux/stat.ha:57).
|
||||
export fn lstat(out: *filestat, path: *u8) (void | oserror) = {
|
||||
export fn lstat(out: *filestat, path: str) (void | oserror) = {
|
||||
let cp: *u8 = kpath(path);
|
||||
if (cp == nil: *u8) { return -36i64: oserror; };
|
||||
let k: kstat;
|
||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
||||
AT_FDCWD: i64, path: i64, (&k): i64,
|
||||
AT_FDCWD: i64, cp: i64, (&k): i64,
|
||||
AT_SYMLINK_NOFOLLOW: i64);
|
||||
if (r < 0) { return r: oserror; };
|
||||
fillfilestat(out, &k);
|
||||
@@ -586,7 +637,9 @@ export fn fstat(out: *filestat, fd: i32) (void | oserror) = {
|
||||
// exists — true if `path` resolves to anything (regular file,
|
||||
// directory, symlink, ...). Stat-shaped (Hare's `fs::exists`,
|
||||
// ref/hare/fs/fs.ha:196) — no separate syscall. Symlinks are
|
||||
// followed; a dangling symlink is `false`.
|
||||
// followed; a dangling symlink is `false`. ENAMETOOLONG is
|
||||
// swallowed as `false` — Hare's os::exists doc says "true if a
|
||||
// node exists at the given path, or false if not."
|
||||
//
|
||||
// Race warning: prefer "open and handle the error" over "exists
|
||||
// then open" in real code (Hare's docstring carries the same
|
||||
@@ -599,10 +652,12 @@ export fn fstat(out: *filestat, fd: i32) (void | oserror) = {
|
||||
// — same class as STATUS #22, surfaced first time a match on this
|
||||
// shape combined with an 80B local-struct local frame). Use the
|
||||
// match shape once #22 lands.
|
||||
export fn exists(path: *u8) bool = {
|
||||
export fn exists(path: str) bool = {
|
||||
let cp: *u8 = kpath(path);
|
||||
if (cp == nil: *u8) { return false; };
|
||||
let k: kstat;
|
||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
||||
AT_FDCWD: i64, path: i64, (&k): i64, 0i64);
|
||||
AT_FDCWD: i64, cp: i64, (&k): i64, 0i64);
|
||||
return r >= 0i64;
|
||||
};
|
||||
|
||||
@@ -906,7 +961,7 @@ def RELA_ADDEND: u64 = 16u64;
|
||||
// ---- file slurp --------------------------------------------------------
|
||||
|
||||
fn slurp(path: *u8) (*u8, u64) = {
|
||||
let fd: i32 = os.open(path, os.flag.RDONLY, 0i32);
|
||||
let fd: i32 = os.open(pathstr(path), os.flag.RDONLY, 0i32);
|
||||
if (fd < 0) { return nil, 0u64; };
|
||||
let szr: (i64 | os.oserror) = os.filesize(fd);
|
||||
let n: i64 = 0i64;
|
||||
@@ -983,6 +1038,16 @@ fn cstrlen(p: *u8) u64 = {
|
||||
return n;
|
||||
};
|
||||
|
||||
// pathstr — view a NUL-terminated *u8 as a str. Bridges argv/arena
|
||||
// callers to lib/os entrypoints (str post-task-#23). Shared with
|
||||
// main.ww and dyn.ww via the w6l bundle.
|
||||
fn pathstr(p: *u8) str = {
|
||||
let r: str;
|
||||
r.ptr = p;
|
||||
r.len = cstrlen(p): i32;
|
||||
return r;
|
||||
};
|
||||
|
||||
fn cstreq(p: *u8, lit: str) bool = {
|
||||
let n: u64 = lit.len: u64;
|
||||
let i: u64 = 0u64;
|
||||
@@ -1556,7 +1621,7 @@ fn dbasename(p: *u8) *u8 = {
|
||||
// ---- file slurp --------------------------------------------------------
|
||||
|
||||
fn slurpso(path: *u8) (*u8, u64) = {
|
||||
let fd: i32 = os.open(path, os.flag.RDONLY, 0i32);
|
||||
let fd: i32 = os.open(pathstr(path), os.flag.RDONLY, 0i32);
|
||||
if (fd < 0) { return nil, 0u64; };
|
||||
let szr: (i64 | os.oserror) = os.filesize(fd);
|
||||
let n: i64 = 0i64;
|
||||
@@ -3010,7 +3075,7 @@ fn buildpathv(dst: *u8, dir: *u8, name: *u8, v: u64) u64 = {
|
||||
|
||||
// islinkable: read first 8 bytes; require !<arch>\n or \x7fELF.
|
||||
fn islinkable(path: *u8) bool = {
|
||||
let fd: i32 = os.open(path, os.flag.RDONLY, 0i32);
|
||||
let fd: i32 = os.open(pathstr(path), os.flag.RDONLY, 0i32);
|
||||
if (fd < 0) { return false; };
|
||||
let mp: *u8 = os.alloc(8u64): *u8;
|
||||
let n: i64 = os.read(fd, mp, 8u64);
|
||||
@@ -3079,7 +3144,7 @@ fn resolvelib(a: *arena, name: *u8, libdirs: **u8, nlibdirs: i32) *u8 = {
|
||||
|
||||
// Read first 20 bytes; return 1 for ET_DYN .so, 0 for ar/.o.
|
||||
fn isso(path: *u8) i32 = {
|
||||
let fd: i32 = os.open(path, os.flag.RDONLY, 0i32);
|
||||
let fd: i32 = os.open(pathstr(path), os.flag.RDONLY, 0i32);
|
||||
if (fd < 0) { return 0; };
|
||||
let mp: *u8 = os.alloc(20u64): *u8;
|
||||
let n: i64 = os.read(fd, mp, 20u64);
|
||||
@@ -3226,7 +3291,7 @@ export fn main(argc: i32, argv: **u8) i32 = {
|
||||
};
|
||||
|
||||
let flags: os.flag = os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC;
|
||||
let fd: i32 = os.open(outpath, flags, 493i32); // 0o755
|
||||
let fd: i32 = os.open(pathstr(outpath), flags, 493i32); // 0o755
|
||||
if (fd < 0) {
|
||||
os.write(2, "w6l: cannot open output\n".ptr, 23u64);
|
||||
return 1;
|
||||
|
||||
@@ -99,7 +99,7 @@ fn buildpathv(dst: *u8, dir: *u8, name: *u8, v: u64) u64 = {
|
||||
|
||||
// islinkable: read first 8 bytes; require !<arch>\n or \x7fELF.
|
||||
fn islinkable(path: *u8) bool = {
|
||||
let fd: i32 = os.open(path, os.flag.RDONLY, 0i32);
|
||||
let fd: i32 = os.open(pathstr(path), os.flag.RDONLY, 0i32);
|
||||
if (fd < 0) { return false; };
|
||||
let mp: *u8 = os.alloc(8u64): *u8;
|
||||
let n: i64 = os.read(fd, mp, 8u64);
|
||||
@@ -168,7 +168,7 @@ fn resolvelib(a: *arena, name: *u8, libdirs: **u8, nlibdirs: i32) *u8 = {
|
||||
|
||||
// Read first 20 bytes; return 1 for ET_DYN .so, 0 for ar/.o.
|
||||
fn isso(path: *u8) i32 = {
|
||||
let fd: i32 = os.open(path, os.flag.RDONLY, 0i32);
|
||||
let fd: i32 = os.open(pathstr(path), os.flag.RDONLY, 0i32);
|
||||
if (fd < 0) { return 0; };
|
||||
let mp: *u8 = os.alloc(20u64): *u8;
|
||||
let n: i64 = os.read(fd, mp, 20u64);
|
||||
@@ -315,7 +315,7 @@ export fn main(argc: i32, argv: **u8) i32 = {
|
||||
};
|
||||
|
||||
let flags: os.flag = os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC;
|
||||
let fd: i32 = os.open(outpath, flags, 493i32); // 0o755
|
||||
let fd: i32 = os.open(pathstr(outpath), flags, 493i32); // 0o755
|
||||
if (fd < 0) {
|
||||
os.write(2, "w6l: cannot open output\n".ptr, 23u64);
|
||||
return 1;
|
||||
|
||||
@@ -76,7 +76,7 @@ def RELA_ADDEND: u64 = 16u64;
|
||||
// ---- file slurp --------------------------------------------------------
|
||||
|
||||
fn slurp(path: *u8) (*u8, u64) = {
|
||||
let fd: i32 = os.open(path, os.flag.RDONLY, 0i32);
|
||||
let fd: i32 = os.open(pathstr(path), os.flag.RDONLY, 0i32);
|
||||
if (fd < 0) { return nil, 0u64; };
|
||||
let szr: (i64 | os.oserror) = os.filesize(fd);
|
||||
let n: i64 = 0i64;
|
||||
@@ -153,6 +153,16 @@ fn cstrlen(p: *u8) u64 = {
|
||||
return n;
|
||||
};
|
||||
|
||||
// pathstr — view a NUL-terminated *u8 as a str. Bridges argv/arena
|
||||
// callers to lib/os entrypoints (str post-task-#23). Shared with
|
||||
// main.ww and dyn.ww via the w6l bundle.
|
||||
fn pathstr(p: *u8) str = {
|
||||
let r: str;
|
||||
r.ptr = p;
|
||||
r.len = cstrlen(p): i32;
|
||||
return r;
|
||||
};
|
||||
|
||||
fn cstreq(p: *u8, lit: str) bool = {
|
||||
let n: u64 = lit.len: u64;
|
||||
let i: u64 = 0u64;
|
||||
|
||||
@@ -88,6 +88,36 @@ export fn exit(code: i32) void = {
|
||||
syscall1(nr.EXIT, code: i64);
|
||||
};
|
||||
|
||||
// PATH_MAX / pathbuf / kpath — port of Hare's ref/hare/sys/+linux/
|
||||
// syscalls.ha:25,27,29-55. Hare's `path` accepts a sum `(str |
|
||||
// []u8 | *const u8)`; ww's lib/os public surface narrows to `str`
|
||||
// (the Hare-faithful surface at ref/hare/os/os.ha:37,47,50 etc).
|
||||
// Internally, [[kpath]] copies the `str` bytes into a single
|
||||
// module-level [[pathbuf]] scratch slot and NUL-terminates so the
|
||||
// raw Linux syscalls (which require C strings) see a valid
|
||||
// terminator. Same precedent as Hare's static `pathbuf`.
|
||||
//
|
||||
// Non-reentrant: one buffer, every [[stat]] / [[open]] / etc.
|
||||
// rewrites it. Same caveat as strconv's `*tos` family (overwritten
|
||||
// on next call). Caller must NOT hold a kpath-returned pointer
|
||||
// across another lib/os path call. Graduates when ww grows a
|
||||
// thread story.
|
||||
//
|
||||
// `nil`-as-overflow over `(*u8 | oserror)`: wwstage over-allocates
|
||||
// 1-word-payload tagged returns to 24B (cstage emits 16B).
|
||||
// Task #9; revert at task #10 when fixed. Repro at
|
||||
// .ai/probe_tagged_return_pointer_payload.ww.
|
||||
export def PATH_MAX: i32 = 4096;
|
||||
let pathbuf: [4096]u8;
|
||||
|
||||
fn kpath(p: str) *u8 = {
|
||||
if (p.len + 1 >= PATH_MAX) { return nil: *u8; }; // ENAMETOOLONG
|
||||
let i: i32 = 0;
|
||||
for (i < p.len) { pathbuf[i] = p[i]; i += 1; };
|
||||
pathbuf[p.len] = 0u8;
|
||||
return &pathbuf[0];
|
||||
};
|
||||
|
||||
// Raw, non-fallible primitives. These return Linux's int conventions
|
||||
// (negative = -errno, non-negative = bytes/fd/etc). Callers wanting a
|
||||
// Hare-style fallible API use the wrappers below.
|
||||
@@ -126,15 +156,17 @@ export fn trywrite(fd: i32, buf: *u8, n: u64) (i64 | oserror) = {
|
||||
return r;
|
||||
};
|
||||
|
||||
// open — Linux open(2). Path must be NUL-terminated; callers using ww
|
||||
// `str` must ensure the bytes are followed by a 0 byte (literals are,
|
||||
// arena-copied paths usually are by construction). Returns -errno on
|
||||
// failure, fd otherwise. Higher-level callers prefer `tryopen`.
|
||||
export fn open(path: *u8, flags: flag, mode: i32) i32 = {
|
||||
return syscall3(nr.OPEN, path: i64, (flags as i32): i64, mode: i64): i32;
|
||||
// open — Linux open(2). Returns -errno on failure, fd otherwise.
|
||||
// Higher-level callers prefer `tryopen`. Mirrors Hare's os::open
|
||||
// (ref/hare/os/os.ha:117); kpath lands the bytes in pathbuf.
|
||||
// Returns -ENAMETOOLONG (-36) if the path overflows PATH_MAX.
|
||||
export fn open(path: str, flags: flag, mode: i32) i32 = {
|
||||
let p: *u8 = kpath(path);
|
||||
if (p == nil: *u8) { return -36i32; }; // ENAMETOOLONG
|
||||
return syscall3(nr.OPEN, p: i64, (flags as i32): i64, mode: i64): i32;
|
||||
};
|
||||
|
||||
export fn tryopen(path: *u8, flags: flag, mode: i32) (i32 | oserror) = {
|
||||
export fn tryopen(path: str, flags: flag, mode: i32) (i32 | oserror) = {
|
||||
let fd: i32 = open(path, flags, mode);
|
||||
if (fd < 0) { return fd: i64: oserror; };
|
||||
return fd;
|
||||
@@ -194,26 +226,37 @@ export fn writeall(fd: i32, buf: *u8, n: u64) (i64 | oserror) = {
|
||||
|
||||
// access(2): returns 0 if the file is reachable, negative errno
|
||||
// otherwise. mode is the bitset described in <unistd.h> (F_OK=0).
|
||||
export fn access(path: *u8, mode: i32) i32 = {
|
||||
return syscall2(nr.ACCESS, path: i64, mode: i64): i32;
|
||||
// Mirrors Hare's os::access (ref/hare/os/+linux/fs.ha:access).
|
||||
// Returns -ENAMETOOLONG (-36) if the path overflows PATH_MAX.
|
||||
export fn access(path: str, mode: i32) i32 = {
|
||||
let p: *u8 = kpath(path);
|
||||
if (p == nil: *u8) { return -36i32; };
|
||||
return syscall2(nr.ACCESS, p: i64, mode: i64): i32;
|
||||
};
|
||||
|
||||
// remove — unlink(2). Hare name; the underlying syscall is unlink(2).
|
||||
export fn remove(path: *u8) i32 = {
|
||||
return syscall1(nr.UNLINK, path: i64): i32;
|
||||
// remove — unlink(2). Mirrors Hare's os::remove
|
||||
// (ref/hare/os/os.ha:12).
|
||||
export fn remove(path: str) i32 = {
|
||||
let p: *u8 = kpath(path);
|
||||
if (p == nil: *u8) { return -36i32; };
|
||||
return syscall1(nr.UNLINK, p: i64): i32;
|
||||
};
|
||||
|
||||
// mkdir — mkdir(2). Path must be NUL-terminated. Mode is the unix
|
||||
// permission bitset (e.g. 0o700). Returns 0 on success, negative
|
||||
// errno otherwise. Hare name (os::mkdir).
|
||||
export fn mkdir(path: *u8, mode: i32) i32 = {
|
||||
return syscall2(nr.MKDIR, path: i64, mode: i64): i32;
|
||||
// mkdir — mkdir(2). Mode is the unix permission bitset (e.g. 0o700).
|
||||
// Returns 0 on success, negative errno otherwise. Mirrors Hare's
|
||||
// os::mkdir (ref/hare/os/os.ha:50).
|
||||
export fn mkdir(path: str, mode: i32) i32 = {
|
||||
let p: *u8 = kpath(path);
|
||||
if (p == nil: *u8) { return -36i32; };
|
||||
return syscall2(nr.MKDIR, p: i64, mode: i64): i32;
|
||||
};
|
||||
|
||||
// rmdir — rmdir(2). Path must be NUL-terminated. Returns 0 on
|
||||
// success, negative errno otherwise. Hare name (os::rmdir).
|
||||
export fn rmdir(path: *u8) i32 = {
|
||||
return syscall1(nr.RMDIR, path: i64): i32;
|
||||
// rmdir — rmdir(2). Mirrors Hare's os::rmdir
|
||||
// (ref/hare/os/os.ha:58).
|
||||
export fn rmdir(path: str) i32 = {
|
||||
let p: *u8 = kpath(path);
|
||||
if (p == nil: *u8) { return -36i32; };
|
||||
return syscall1(nr.RMDIR, p: i64): i32;
|
||||
};
|
||||
|
||||
// mkdirs — recursive mkdir. Creates `path` and any non-existent
|
||||
@@ -221,29 +264,28 @@ export fn rmdir(path: *u8) i32 = {
|
||||
// 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; };
|
||||
// Mirrors Hare's os::mkdirs (ref/hare/os/os.ha:54). The in-place
|
||||
// '/' → NUL splice walks the kpath-loaded [[pathbuf]] directly
|
||||
// instead of recursing through [[mkdir]] — re-entering kpath would
|
||||
// clobber the buffer mid-walk (single static slot, see kpath's
|
||||
// non-reentrancy note above).
|
||||
export fn mkdirs(path: str, mode: i32) (void | oserror) = {
|
||||
let cp: *u8 = kpath(path);
|
||||
if (cp == nil: *u8) { return -36i64: oserror; };
|
||||
let n: i32 = path.len;
|
||||
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.
|
||||
// raw MKDIR syscall on pathbuf, 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 (pathbuf[i] == 47u8) { // '/'
|
||||
pathbuf[i] = 0u8;
|
||||
let r: i32 = syscall2(nr.MKDIR,
|
||||
(&pathbuf[0]): i64, mode: i64): i32;
|
||||
pathbuf[i] = 47u8;
|
||||
if (r < 0) {
|
||||
if (r != -17) { return r: i64: oserror; };
|
||||
};
|
||||
@@ -251,8 +293,8 @@ export fn mkdirs(path: *u8, mode: i32) (void | oserror) = {
|
||||
i += 1;
|
||||
};
|
||||
|
||||
// mkdir the full path.
|
||||
let r: i32 = mkdir(path, mode);
|
||||
let r: i32 = syscall2(nr.MKDIR,
|
||||
(&pathbuf[0]): i64, mode: i64): i32;
|
||||
if (r < 0) {
|
||||
if (r != -17) { return r: i64: oserror; };
|
||||
};
|
||||
@@ -270,9 +312,14 @@ export fn fork() i32 = {
|
||||
return syscall0(nr.FORK): i32;
|
||||
};
|
||||
|
||||
// execve(2): on success, does not return.
|
||||
export fn execve(path: *u8, argv: **u8, envp: **u8) i32 = {
|
||||
return syscall3(nr.EXECVE, path: i64, argv: i64, envp: i64): i32;
|
||||
// execve(2): on success, does not return. Mirrors Hare's
|
||||
// os::exec::exec path arg (str). argv/envp stay `**u8` — the
|
||||
// kernel takes a NUL-pointer-terminated table of NUL-terminated
|
||||
// C strings, a different shape from a path.
|
||||
export fn execve(path: str, argv: **u8, envp: **u8) i32 = {
|
||||
let p: *u8 = kpath(path);
|
||||
if (p == nil: *u8) { return -36i32; };
|
||||
return syscall3(nr.EXECVE, p: i64, argv: i64, envp: i64): i32;
|
||||
};
|
||||
|
||||
// wait4(2): wait for `pid` (or any child if -1), store status in
|
||||
@@ -545,27 +592,31 @@ fn fillfilestat(out: *filestat, k: *kstat) void = {
|
||||
};
|
||||
|
||||
// stat — fill *out with metadata for `path`. Follows symlinks.
|
||||
// `path` must be NUL-terminated (lib/os convention; see task #23
|
||||
// for a planned `path: str` migration).
|
||||
// Returns ENAMETOOLONG (-36) as `oserror` if the path overflows
|
||||
// PATH_MAX.
|
||||
//
|
||||
// Mirrors Hare's sys::stat (ref/hare/sys/+linux/stat.ha:51) modulo
|
||||
// the out-param shape forced by the cgreturn 24B cap. Note: Hare's
|
||||
// higher-level fs::stat (ref/hare/fs/fs.ha:172) instead has lstat
|
||||
// semantics — we follow sys::stat's POSIX-stat behavior here.
|
||||
export fn stat(out: *filestat, path: *u8) (void | oserror) = {
|
||||
export fn stat(out: *filestat, path: str) (void | oserror) = {
|
||||
let cp: *u8 = kpath(path);
|
||||
if (cp == nil: *u8) { return -36i64: oserror; };
|
||||
let k: kstat;
|
||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
||||
AT_FDCWD: i64, path: i64, (&k): i64, 0i64);
|
||||
AT_FDCWD: i64, cp: i64, (&k): i64, 0i64);
|
||||
if (r < 0) { return r: oserror; };
|
||||
fillfilestat(out, &k);
|
||||
};
|
||||
|
||||
// lstat — like [[stat]] but does NOT follow a terminal symlink.
|
||||
// Mirrors Hare's sys::lstat (ref/hare/sys/+linux/stat.ha:57).
|
||||
export fn lstat(out: *filestat, path: *u8) (void | oserror) = {
|
||||
export fn lstat(out: *filestat, path: str) (void | oserror) = {
|
||||
let cp: *u8 = kpath(path);
|
||||
if (cp == nil: *u8) { return -36i64: oserror; };
|
||||
let k: kstat;
|
||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
||||
AT_FDCWD: i64, path: i64, (&k): i64,
|
||||
AT_FDCWD: i64, cp: i64, (&k): i64,
|
||||
AT_SYMLINK_NOFOLLOW: i64);
|
||||
if (r < 0) { return r: oserror; };
|
||||
fillfilestat(out, &k);
|
||||
@@ -586,7 +637,9 @@ export fn fstat(out: *filestat, fd: i32) (void | oserror) = {
|
||||
// exists — true if `path` resolves to anything (regular file,
|
||||
// directory, symlink, ...). Stat-shaped (Hare's `fs::exists`,
|
||||
// ref/hare/fs/fs.ha:196) — no separate syscall. Symlinks are
|
||||
// followed; a dangling symlink is `false`.
|
||||
// followed; a dangling symlink is `false`. ENAMETOOLONG is
|
||||
// swallowed as `false` — Hare's os::exists doc says "true if a
|
||||
// node exists at the given path, or false if not."
|
||||
//
|
||||
// Race warning: prefer "open and handle the error" over "exists
|
||||
// then open" in real code (Hare's docstring carries the same
|
||||
@@ -599,10 +652,12 @@ export fn fstat(out: *filestat, fd: i32) (void | oserror) = {
|
||||
// — same class as STATUS #22, surfaced first time a match on this
|
||||
// shape combined with an 80B local-struct local frame). Use the
|
||||
// match shape once #22 lands.
|
||||
export fn exists(path: *u8) bool = {
|
||||
export fn exists(path: str) bool = {
|
||||
let cp: *u8 = kpath(path);
|
||||
if (cp == nil: *u8) { return false; };
|
||||
let k: kstat;
|
||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
||||
AT_FDCWD: i64, path: i64, (&k): i64, 0i64);
|
||||
AT_FDCWD: i64, cp: i64, (&k): i64, 0i64);
|
||||
return r >= 0i64;
|
||||
};
|
||||
|
||||
@@ -744,6 +799,16 @@ fn cstrlen(p: *u8) u64 = {
|
||||
return n;
|
||||
};
|
||||
|
||||
// pathstr — view a NUL-terminated *u8 as a str. Bridges the
|
||||
// driver's argv/arena *u8 paths to lib/os entrypoints (str
|
||||
// post-task-#23).
|
||||
fn pathstr(p: *u8) str = {
|
||||
let r: str;
|
||||
r.ptr = p;
|
||||
r.len = cstrlen(p): i32;
|
||||
return r;
|
||||
};
|
||||
|
||||
fn cstreq(a: *u8, b: *u8) bool = {
|
||||
let i: u64 = 0u64;
|
||||
for (a[i] == b[i]) {
|
||||
@@ -873,7 +938,7 @@ fn procrun(path: *u8, argv: **u8) i32 = {
|
||||
return -1;
|
||||
};
|
||||
if (pid == 0) {
|
||||
os.execve(path, argv, nil: **u8);
|
||||
os.execve(pathstr(path), argv, nil: **u8);
|
||||
os.write(2, "ww: execve failed\n".ptr, 18u64);
|
||||
os.exit(127);
|
||||
};
|
||||
@@ -951,7 +1016,7 @@ fn locatein(a: *arena, dir: *u8, dirlen: u64, name: *u8, namelen: u64) *u8 = {
|
||||
buf[off] = 119u8; off += 1u64; // 'w'
|
||||
buf[off] = 119u8; off += 1u64; // 'w'
|
||||
buf[off] = 0u8;
|
||||
if (os.access(buf, 0i32) == 0) { return buf; };
|
||||
if (os.access(pathstr(buf), 0i32) == 0) { return buf; };
|
||||
|
||||
// candidate 2: <dir>/<name>/<name>.ww
|
||||
let buf2: *u8 = amalloc(a, PATH_MAX): *u8;
|
||||
@@ -971,7 +1036,7 @@ fn locatein(a: *arena, dir: *u8, dirlen: u64, name: *u8, namelen: u64) *u8 = {
|
||||
buf2[off] = 119u8; off += 1u64;
|
||||
buf2[off] = 119u8; off += 1u64;
|
||||
buf2[off] = 0u8;
|
||||
if (os.access(buf2, 0i32) == 0) { return buf2; };
|
||||
if (os.access(pathstr(buf2), 0i32) == 0) { return buf2; };
|
||||
return nil;
|
||||
};
|
||||
|
||||
@@ -998,7 +1063,7 @@ fn locateimport(a: *arena, dirs: *u8, name: *u8, namelen: u64) *u8 = {
|
||||
// ---- file slurp -------------------------------------------------------
|
||||
|
||||
fn slurp(pathcs: *u8) (*u8, u64) = {
|
||||
let fd: i32 = os.open(pathcs, os.flag.RDONLY, 0i32);
|
||||
let fd: i32 = os.open(pathstr(pathcs), os.flag.RDONLY, 0i32);
|
||||
if (fd < 0) { return nil, 0u64; };
|
||||
let szr: (i64 | os.oserror) = os.filesize(fd);
|
||||
let n: i64 = 0i64;
|
||||
@@ -1283,7 +1348,7 @@ fn buildone(selfdir: *u8, src: *u8, out: *u8, incs: *u8, lf: *lflags) i32 = {
|
||||
};
|
||||
|
||||
// Step 1: expand `use`s into the combined file.
|
||||
let cf: i32 = os.open(combined, os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC, 420i32); // 0o644
|
||||
let cf: i32 = os.open(pathstr(combined), os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC, 420i32); // 0o644
|
||||
if (cf < 0) {
|
||||
os.write(2, "ww: cannot open combined\n".ptr, 25u64);
|
||||
return 1;
|
||||
@@ -1461,7 +1526,7 @@ fn resolvemodule(a: *arena, selfdir: *u8, name: *u8, incs: *u8) *u8 = {
|
||||
|
||||
// (1) literal .ww file that exists
|
||||
if (cstrendswithlit(name, ".ww")) {
|
||||
if (os.access(name, 0i32) == 0) {
|
||||
if (os.access(pathstr(name), 0i32) == 0) {
|
||||
return arenadupcstr(a, name, nlen);
|
||||
};
|
||||
};
|
||||
@@ -1479,7 +1544,7 @@ fn resolvemodule(a: *arena, selfdir: *u8, name: *u8, incs: *u8) *u8 = {
|
||||
dot[0] = 46u8; dot[1] = 0u8;
|
||||
let probe: *u8 = builddirmodulepath(a, dot, 1u64,
|
||||
cwd + bo, blen);
|
||||
if (os.access(probe, 0i32) == 0) { return probe; };
|
||||
if (os.access(pathstr(probe), 0i32) == 0) { return probe; };
|
||||
return nil;
|
||||
};
|
||||
};
|
||||
@@ -1488,7 +1553,7 @@ fn resolvemodule(a: *arena, selfdir: *u8, name: *u8, incs: *u8) *u8 = {
|
||||
let bo: u64 = basenameoff(name, nlen);
|
||||
let probe: *u8 = builddirmodulepath(a, name, nlen,
|
||||
name + bo, nlen - bo);
|
||||
if (os.access(probe, 0i32) == 0) { return probe; };
|
||||
if (os.access(pathstr(probe), 0i32) == 0) { return probe; };
|
||||
|
||||
// (4) search path lookup
|
||||
let search: *u8 = buildsearchpath(a, selfdir, incs);
|
||||
@@ -1779,7 +1844,7 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
lf.libs = libs;
|
||||
lf.nlibs = nlibs;
|
||||
if (buildone(selfdir, resolved, tmp, incs, &lf) != 0) {
|
||||
os.remove(tmp);
|
||||
os.remove(pathstr(tmp));
|
||||
return 1;
|
||||
};
|
||||
|
||||
@@ -1796,7 +1861,7 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
};
|
||||
execargv[nextra + 1] = nil;
|
||||
let rc: i32 = procrun(tmp, execargv);
|
||||
os.remove(tmp);
|
||||
os.remove(pathstr(tmp));
|
||||
return rc;
|
||||
};
|
||||
|
||||
@@ -1811,19 +1876,19 @@ fn runsingletest(selfdir: *u8, src: *u8) i32 = {
|
||||
let tmp: *u8 = os.alloc(PATH_MAX): *u8;
|
||||
makeruntmp(tmp);
|
||||
if (buildone(selfdir, src, tmp, "\0".ptr, nil) != 0) {
|
||||
os.remove(tmp);
|
||||
os.remove(pathstr(tmp));
|
||||
return 1;
|
||||
};
|
||||
let execargv: **u8 = os.alloc(16u64): **u8;
|
||||
execargv[0] = tmp;
|
||||
execargv[1] = nil;
|
||||
let rc: i32 = procrun(tmp, execargv);
|
||||
os.remove(tmp);
|
||||
os.remove(pathstr(tmp));
|
||||
return rc;
|
||||
};
|
||||
|
||||
fn rundirtests(selfdir: *u8, dir: *u8) i32 = {
|
||||
let fd: i32 = os.open(dir, os.flag.RDONLY, 0i32);
|
||||
let fd: i32 = os.open(pathstr(dir), os.flag.RDONLY, 0i32);
|
||||
if (fd < 0) {
|
||||
os.write(2, "ww test: cannot open directory\n".ptr, 31u64);
|
||||
return 1;
|
||||
@@ -1883,7 +1948,7 @@ fn rundirtests(selfdir: *u8, dir: *u8) i32 = {
|
||||
os.write(2, "\n".ptr, 1u64);
|
||||
};
|
||||
};
|
||||
os.remove(tmp);
|
||||
os.remove(pathstr(tmp));
|
||||
};
|
||||
off += reclen;
|
||||
};
|
||||
@@ -1907,7 +1972,7 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
|
||||
// single-file mode: literal *.ww that exists
|
||||
if (cstrendswithlit(target, ".ww")) {
|
||||
if (os.access(target, 0i32) == 0) {
|
||||
if (os.access(pathstr(target), 0i32) == 0) {
|
||||
return runsingletest(selfdir, target);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -27,6 +27,16 @@ fn cstrlen(p: *u8) u64 = {
|
||||
return n;
|
||||
};
|
||||
|
||||
// pathstr — view a NUL-terminated *u8 as a str. Bridges the
|
||||
// driver's argv/arena *u8 paths to lib/os entrypoints (str
|
||||
// post-task-#23).
|
||||
fn pathstr(p: *u8) str = {
|
||||
let r: str;
|
||||
r.ptr = p;
|
||||
r.len = cstrlen(p): i32;
|
||||
return r;
|
||||
};
|
||||
|
||||
fn cstreq(a: *u8, b: *u8) bool = {
|
||||
let i: u64 = 0u64;
|
||||
for (a[i] == b[i]) {
|
||||
@@ -156,7 +166,7 @@ fn procrun(path: *u8, argv: **u8) i32 = {
|
||||
return -1;
|
||||
};
|
||||
if (pid == 0) {
|
||||
os.execve(path, argv, nil: **u8);
|
||||
os.execve(pathstr(path), argv, nil: **u8);
|
||||
os.write(2, "ww: execve failed\n".ptr, 18u64);
|
||||
os.exit(127);
|
||||
};
|
||||
@@ -234,7 +244,7 @@ fn locatein(a: *arena, dir: *u8, dirlen: u64, name: *u8, namelen: u64) *u8 = {
|
||||
buf[off] = 119u8; off += 1u64; // 'w'
|
||||
buf[off] = 119u8; off += 1u64; // 'w'
|
||||
buf[off] = 0u8;
|
||||
if (os.access(buf, 0i32) == 0) { return buf; };
|
||||
if (os.access(pathstr(buf), 0i32) == 0) { return buf; };
|
||||
|
||||
// candidate 2: <dir>/<name>/<name>.ww
|
||||
let buf2: *u8 = amalloc(a, PATH_MAX): *u8;
|
||||
@@ -254,7 +264,7 @@ fn locatein(a: *arena, dir: *u8, dirlen: u64, name: *u8, namelen: u64) *u8 = {
|
||||
buf2[off] = 119u8; off += 1u64;
|
||||
buf2[off] = 119u8; off += 1u64;
|
||||
buf2[off] = 0u8;
|
||||
if (os.access(buf2, 0i32) == 0) { return buf2; };
|
||||
if (os.access(pathstr(buf2), 0i32) == 0) { return buf2; };
|
||||
return nil;
|
||||
};
|
||||
|
||||
@@ -281,7 +291,7 @@ fn locateimport(a: *arena, dirs: *u8, name: *u8, namelen: u64) *u8 = {
|
||||
// ---- file slurp -------------------------------------------------------
|
||||
|
||||
fn slurp(pathcs: *u8) (*u8, u64) = {
|
||||
let fd: i32 = os.open(pathcs, os.flag.RDONLY, 0i32);
|
||||
let fd: i32 = os.open(pathstr(pathcs), os.flag.RDONLY, 0i32);
|
||||
if (fd < 0) { return nil, 0u64; };
|
||||
let szr: (i64 | os.oserror) = os.filesize(fd);
|
||||
let n: i64 = 0i64;
|
||||
@@ -566,7 +576,7 @@ fn buildone(selfdir: *u8, src: *u8, out: *u8, incs: *u8, lf: *lflags) i32 = {
|
||||
};
|
||||
|
||||
// Step 1: expand `use`s into the combined file.
|
||||
let cf: i32 = os.open(combined, os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC, 420i32); // 0o644
|
||||
let cf: i32 = os.open(pathstr(combined), os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC, 420i32); // 0o644
|
||||
if (cf < 0) {
|
||||
os.write(2, "ww: cannot open combined\n".ptr, 25u64);
|
||||
return 1;
|
||||
@@ -744,7 +754,7 @@ fn resolvemodule(a: *arena, selfdir: *u8, name: *u8, incs: *u8) *u8 = {
|
||||
|
||||
// (1) literal .ww file that exists
|
||||
if (cstrendswithlit(name, ".ww")) {
|
||||
if (os.access(name, 0i32) == 0) {
|
||||
if (os.access(pathstr(name), 0i32) == 0) {
|
||||
return arenadupcstr(a, name, nlen);
|
||||
};
|
||||
};
|
||||
@@ -762,7 +772,7 @@ fn resolvemodule(a: *arena, selfdir: *u8, name: *u8, incs: *u8) *u8 = {
|
||||
dot[0] = 46u8; dot[1] = 0u8;
|
||||
let probe: *u8 = builddirmodulepath(a, dot, 1u64,
|
||||
cwd + bo, blen);
|
||||
if (os.access(probe, 0i32) == 0) { return probe; };
|
||||
if (os.access(pathstr(probe), 0i32) == 0) { return probe; };
|
||||
return nil;
|
||||
};
|
||||
};
|
||||
@@ -771,7 +781,7 @@ fn resolvemodule(a: *arena, selfdir: *u8, name: *u8, incs: *u8) *u8 = {
|
||||
let bo: u64 = basenameoff(name, nlen);
|
||||
let probe: *u8 = builddirmodulepath(a, name, nlen,
|
||||
name + bo, nlen - bo);
|
||||
if (os.access(probe, 0i32) == 0) { return probe; };
|
||||
if (os.access(pathstr(probe), 0i32) == 0) { return probe; };
|
||||
|
||||
// (4) search path lookup
|
||||
let search: *u8 = buildsearchpath(a, selfdir, incs);
|
||||
@@ -1062,7 +1072,7 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
lf.libs = libs;
|
||||
lf.nlibs = nlibs;
|
||||
if (buildone(selfdir, resolved, tmp, incs, &lf) != 0) {
|
||||
os.remove(tmp);
|
||||
os.remove(pathstr(tmp));
|
||||
return 1;
|
||||
};
|
||||
|
||||
@@ -1079,7 +1089,7 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
};
|
||||
execargv[nextra + 1] = nil;
|
||||
let rc: i32 = procrun(tmp, execargv);
|
||||
os.remove(tmp);
|
||||
os.remove(pathstr(tmp));
|
||||
return rc;
|
||||
};
|
||||
|
||||
@@ -1094,19 +1104,19 @@ fn runsingletest(selfdir: *u8, src: *u8) i32 = {
|
||||
let tmp: *u8 = os.alloc(PATH_MAX): *u8;
|
||||
makeruntmp(tmp);
|
||||
if (buildone(selfdir, src, tmp, "\0".ptr, nil) != 0) {
|
||||
os.remove(tmp);
|
||||
os.remove(pathstr(tmp));
|
||||
return 1;
|
||||
};
|
||||
let execargv: **u8 = os.alloc(16u64): **u8;
|
||||
execargv[0] = tmp;
|
||||
execargv[1] = nil;
|
||||
let rc: i32 = procrun(tmp, execargv);
|
||||
os.remove(tmp);
|
||||
os.remove(pathstr(tmp));
|
||||
return rc;
|
||||
};
|
||||
|
||||
fn rundirtests(selfdir: *u8, dir: *u8) i32 = {
|
||||
let fd: i32 = os.open(dir, os.flag.RDONLY, 0i32);
|
||||
let fd: i32 = os.open(pathstr(dir), os.flag.RDONLY, 0i32);
|
||||
if (fd < 0) {
|
||||
os.write(2, "ww test: cannot open directory\n".ptr, 31u64);
|
||||
return 1;
|
||||
@@ -1166,7 +1176,7 @@ fn rundirtests(selfdir: *u8, dir: *u8) i32 = {
|
||||
os.write(2, "\n".ptr, 1u64);
|
||||
};
|
||||
};
|
||||
os.remove(tmp);
|
||||
os.remove(pathstr(tmp));
|
||||
};
|
||||
off += reclen;
|
||||
};
|
||||
@@ -1190,7 +1200,7 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
|
||||
// single-file mode: literal *.ww that exists
|
||||
if (cstrendswithlit(target, ".ww")) {
|
||||
if (os.access(target, 0i32) == 0) {
|
||||
if (os.access(pathstr(target), 0i32) == 0) {
|
||||
return runsingletest(selfdir, target);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -88,6 +88,36 @@ export fn exit(code: i32) void = {
|
||||
syscall1(nr.EXIT, code: i64);
|
||||
};
|
||||
|
||||
// PATH_MAX / pathbuf / kpath — port of Hare's ref/hare/sys/+linux/
|
||||
// syscalls.ha:25,27,29-55. Hare's `path` accepts a sum `(str |
|
||||
// []u8 | *const u8)`; ww's lib/os public surface narrows to `str`
|
||||
// (the Hare-faithful surface at ref/hare/os/os.ha:37,47,50 etc).
|
||||
// Internally, [[kpath]] copies the `str` bytes into a single
|
||||
// module-level [[pathbuf]] scratch slot and NUL-terminates so the
|
||||
// raw Linux syscalls (which require C strings) see a valid
|
||||
// terminator. Same precedent as Hare's static `pathbuf`.
|
||||
//
|
||||
// Non-reentrant: one buffer, every [[stat]] / [[open]] / etc.
|
||||
// rewrites it. Same caveat as strconv's `*tos` family (overwritten
|
||||
// on next call). Caller must NOT hold a kpath-returned pointer
|
||||
// across another lib/os path call. Graduates when ww grows a
|
||||
// thread story.
|
||||
//
|
||||
// `nil`-as-overflow over `(*u8 | oserror)`: wwstage over-allocates
|
||||
// 1-word-payload tagged returns to 24B (cstage emits 16B).
|
||||
// Task #9; revert at task #10 when fixed. Repro at
|
||||
// .ai/probe_tagged_return_pointer_payload.ww.
|
||||
export def PATH_MAX: i32 = 4096;
|
||||
let pathbuf: [4096]u8;
|
||||
|
||||
fn kpath(p: str) *u8 = {
|
||||
if (p.len + 1 >= PATH_MAX) { return nil: *u8; }; // ENAMETOOLONG
|
||||
let i: i32 = 0;
|
||||
for (i < p.len) { pathbuf[i] = p[i]; i += 1; };
|
||||
pathbuf[p.len] = 0u8;
|
||||
return &pathbuf[0];
|
||||
};
|
||||
|
||||
// Raw, non-fallible primitives. These return Linux's int conventions
|
||||
// (negative = -errno, non-negative = bytes/fd/etc). Callers wanting a
|
||||
// Hare-style fallible API use the wrappers below.
|
||||
@@ -126,15 +156,17 @@ export fn trywrite(fd: i32, buf: *u8, n: u64) (i64 | oserror) = {
|
||||
return r;
|
||||
};
|
||||
|
||||
// open — Linux open(2). Path must be NUL-terminated; callers using ww
|
||||
// `str` must ensure the bytes are followed by a 0 byte (literals are,
|
||||
// arena-copied paths usually are by construction). Returns -errno on
|
||||
// failure, fd otherwise. Higher-level callers prefer `tryopen`.
|
||||
export fn open(path: *u8, flags: flag, mode: i32) i32 = {
|
||||
return syscall3(nr.OPEN, path: i64, (flags as i32): i64, mode: i64): i32;
|
||||
// open — Linux open(2). Returns -errno on failure, fd otherwise.
|
||||
// Higher-level callers prefer `tryopen`. Mirrors Hare's os::open
|
||||
// (ref/hare/os/os.ha:117); kpath lands the bytes in pathbuf.
|
||||
// Returns -ENAMETOOLONG (-36) if the path overflows PATH_MAX.
|
||||
export fn open(path: str, flags: flag, mode: i32) i32 = {
|
||||
let p: *u8 = kpath(path);
|
||||
if (p == nil: *u8) { return -36i32; }; // ENAMETOOLONG
|
||||
return syscall3(nr.OPEN, p: i64, (flags as i32): i64, mode: i64): i32;
|
||||
};
|
||||
|
||||
export fn tryopen(path: *u8, flags: flag, mode: i32) (i32 | oserror) = {
|
||||
export fn tryopen(path: str, flags: flag, mode: i32) (i32 | oserror) = {
|
||||
let fd: i32 = open(path, flags, mode);
|
||||
if (fd < 0) { return fd: i64: oserror; };
|
||||
return fd;
|
||||
@@ -194,26 +226,37 @@ export fn writeall(fd: i32, buf: *u8, n: u64) (i64 | oserror) = {
|
||||
|
||||
// access(2): returns 0 if the file is reachable, negative errno
|
||||
// otherwise. mode is the bitset described in <unistd.h> (F_OK=0).
|
||||
export fn access(path: *u8, mode: i32) i32 = {
|
||||
return syscall2(nr.ACCESS, path: i64, mode: i64): i32;
|
||||
// Mirrors Hare's os::access (ref/hare/os/+linux/fs.ha:access).
|
||||
// Returns -ENAMETOOLONG (-36) if the path overflows PATH_MAX.
|
||||
export fn access(path: str, mode: i32) i32 = {
|
||||
let p: *u8 = kpath(path);
|
||||
if (p == nil: *u8) { return -36i32; };
|
||||
return syscall2(nr.ACCESS, p: i64, mode: i64): i32;
|
||||
};
|
||||
|
||||
// remove — unlink(2). Hare name; the underlying syscall is unlink(2).
|
||||
export fn remove(path: *u8) i32 = {
|
||||
return syscall1(nr.UNLINK, path: i64): i32;
|
||||
// remove — unlink(2). Mirrors Hare's os::remove
|
||||
// (ref/hare/os/os.ha:12).
|
||||
export fn remove(path: str) i32 = {
|
||||
let p: *u8 = kpath(path);
|
||||
if (p == nil: *u8) { return -36i32; };
|
||||
return syscall1(nr.UNLINK, p: i64): i32;
|
||||
};
|
||||
|
||||
// mkdir — mkdir(2). Path must be NUL-terminated. Mode is the unix
|
||||
// permission bitset (e.g. 0o700). Returns 0 on success, negative
|
||||
// errno otherwise. Hare name (os::mkdir).
|
||||
export fn mkdir(path: *u8, mode: i32) i32 = {
|
||||
return syscall2(nr.MKDIR, path: i64, mode: i64): i32;
|
||||
// mkdir — mkdir(2). Mode is the unix permission bitset (e.g. 0o700).
|
||||
// Returns 0 on success, negative errno otherwise. Mirrors Hare's
|
||||
// os::mkdir (ref/hare/os/os.ha:50).
|
||||
export fn mkdir(path: str, mode: i32) i32 = {
|
||||
let p: *u8 = kpath(path);
|
||||
if (p == nil: *u8) { return -36i32; };
|
||||
return syscall2(nr.MKDIR, p: i64, mode: i64): i32;
|
||||
};
|
||||
|
||||
// rmdir — rmdir(2). Path must be NUL-terminated. Returns 0 on
|
||||
// success, negative errno otherwise. Hare name (os::rmdir).
|
||||
export fn rmdir(path: *u8) i32 = {
|
||||
return syscall1(nr.RMDIR, path: i64): i32;
|
||||
// rmdir — rmdir(2). Mirrors Hare's os::rmdir
|
||||
// (ref/hare/os/os.ha:58).
|
||||
export fn rmdir(path: str) i32 = {
|
||||
let p: *u8 = kpath(path);
|
||||
if (p == nil: *u8) { return -36i32; };
|
||||
return syscall1(nr.RMDIR, p: i64): i32;
|
||||
};
|
||||
|
||||
// mkdirs — recursive mkdir. Creates `path` and any non-existent
|
||||
@@ -221,29 +264,28 @@ export fn rmdir(path: *u8) i32 = {
|
||||
// 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; };
|
||||
// Mirrors Hare's os::mkdirs (ref/hare/os/os.ha:54). The in-place
|
||||
// '/' → NUL splice walks the kpath-loaded [[pathbuf]] directly
|
||||
// instead of recursing through [[mkdir]] — re-entering kpath would
|
||||
// clobber the buffer mid-walk (single static slot, see kpath's
|
||||
// non-reentrancy note above).
|
||||
export fn mkdirs(path: str, mode: i32) (void | oserror) = {
|
||||
let cp: *u8 = kpath(path);
|
||||
if (cp == nil: *u8) { return -36i64: oserror; };
|
||||
let n: i32 = path.len;
|
||||
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.
|
||||
// raw MKDIR syscall on pathbuf, 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 (pathbuf[i] == 47u8) { // '/'
|
||||
pathbuf[i] = 0u8;
|
||||
let r: i32 = syscall2(nr.MKDIR,
|
||||
(&pathbuf[0]): i64, mode: i64): i32;
|
||||
pathbuf[i] = 47u8;
|
||||
if (r < 0) {
|
||||
if (r != -17) { return r: i64: oserror; };
|
||||
};
|
||||
@@ -251,8 +293,8 @@ export fn mkdirs(path: *u8, mode: i32) (void | oserror) = {
|
||||
i += 1;
|
||||
};
|
||||
|
||||
// mkdir the full path.
|
||||
let r: i32 = mkdir(path, mode);
|
||||
let r: i32 = syscall2(nr.MKDIR,
|
||||
(&pathbuf[0]): i64, mode: i64): i32;
|
||||
if (r < 0) {
|
||||
if (r != -17) { return r: i64: oserror; };
|
||||
};
|
||||
@@ -270,9 +312,14 @@ export fn fork() i32 = {
|
||||
return syscall0(nr.FORK): i32;
|
||||
};
|
||||
|
||||
// execve(2): on success, does not return.
|
||||
export fn execve(path: *u8, argv: **u8, envp: **u8) i32 = {
|
||||
return syscall3(nr.EXECVE, path: i64, argv: i64, envp: i64): i32;
|
||||
// execve(2): on success, does not return. Mirrors Hare's
|
||||
// os::exec::exec path arg (str). argv/envp stay `**u8` — the
|
||||
// kernel takes a NUL-pointer-terminated table of NUL-terminated
|
||||
// C strings, a different shape from a path.
|
||||
export fn execve(path: str, argv: **u8, envp: **u8) i32 = {
|
||||
let p: *u8 = kpath(path);
|
||||
if (p == nil: *u8) { return -36i32; };
|
||||
return syscall3(nr.EXECVE, p: i64, argv: i64, envp: i64): i32;
|
||||
};
|
||||
|
||||
// wait4(2): wait for `pid` (or any child if -1), store status in
|
||||
@@ -545,27 +592,31 @@ fn fillfilestat(out: *filestat, k: *kstat) void = {
|
||||
};
|
||||
|
||||
// stat — fill *out with metadata for `path`. Follows symlinks.
|
||||
// `path` must be NUL-terminated (lib/os convention; see task #23
|
||||
// for a planned `path: str` migration).
|
||||
// Returns ENAMETOOLONG (-36) as `oserror` if the path overflows
|
||||
// PATH_MAX.
|
||||
//
|
||||
// Mirrors Hare's sys::stat (ref/hare/sys/+linux/stat.ha:51) modulo
|
||||
// the out-param shape forced by the cgreturn 24B cap. Note: Hare's
|
||||
// higher-level fs::stat (ref/hare/fs/fs.ha:172) instead has lstat
|
||||
// semantics — we follow sys::stat's POSIX-stat behavior here.
|
||||
export fn stat(out: *filestat, path: *u8) (void | oserror) = {
|
||||
export fn stat(out: *filestat, path: str) (void | oserror) = {
|
||||
let cp: *u8 = kpath(path);
|
||||
if (cp == nil: *u8) { return -36i64: oserror; };
|
||||
let k: kstat;
|
||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
||||
AT_FDCWD: i64, path: i64, (&k): i64, 0i64);
|
||||
AT_FDCWD: i64, cp: i64, (&k): i64, 0i64);
|
||||
if (r < 0) { return r: oserror; };
|
||||
fillfilestat(out, &k);
|
||||
};
|
||||
|
||||
// lstat — like [[stat]] but does NOT follow a terminal symlink.
|
||||
// Mirrors Hare's sys::lstat (ref/hare/sys/+linux/stat.ha:57).
|
||||
export fn lstat(out: *filestat, path: *u8) (void | oserror) = {
|
||||
export fn lstat(out: *filestat, path: str) (void | oserror) = {
|
||||
let cp: *u8 = kpath(path);
|
||||
if (cp == nil: *u8) { return -36i64: oserror; };
|
||||
let k: kstat;
|
||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
||||
AT_FDCWD: i64, path: i64, (&k): i64,
|
||||
AT_FDCWD: i64, cp: i64, (&k): i64,
|
||||
AT_SYMLINK_NOFOLLOW: i64);
|
||||
if (r < 0) { return r: oserror; };
|
||||
fillfilestat(out, &k);
|
||||
@@ -586,7 +637,9 @@ export fn fstat(out: *filestat, fd: i32) (void | oserror) = {
|
||||
// exists — true if `path` resolves to anything (regular file,
|
||||
// directory, symlink, ...). Stat-shaped (Hare's `fs::exists`,
|
||||
// ref/hare/fs/fs.ha:196) — no separate syscall. Symlinks are
|
||||
// followed; a dangling symlink is `false`.
|
||||
// followed; a dangling symlink is `false`. ENAMETOOLONG is
|
||||
// swallowed as `false` — Hare's os::exists doc says "true if a
|
||||
// node exists at the given path, or false if not."
|
||||
//
|
||||
// Race warning: prefer "open and handle the error" over "exists
|
||||
// then open" in real code (Hare's docstring carries the same
|
||||
@@ -599,10 +652,12 @@ export fn fstat(out: *filestat, fd: i32) (void | oserror) = {
|
||||
// — same class as STATUS #22, surfaced first time a match on this
|
||||
// shape combined with an 80B local-struct local frame). Use the
|
||||
// match shape once #22 lands.
|
||||
export fn exists(path: *u8) bool = {
|
||||
export fn exists(path: str) bool = {
|
||||
let cp: *u8 = kpath(path);
|
||||
if (cp == nil: *u8) { return false; };
|
||||
let k: kstat;
|
||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
||||
AT_FDCWD: i64, path: i64, (&k): i64, 0i64);
|
||||
AT_FDCWD: i64, cp: i64, (&k): i64, 0i64);
|
||||
return r >= 0i64;
|
||||
};
|
||||
|
||||
@@ -19164,7 +19219,7 @@ export fn main(argc: i32, argv: **u8) i32 = {
|
||||
return 2;
|
||||
};
|
||||
|
||||
let fdorerr: (i32 | os.oserror) = os.tryopen(path, os.flag.RDONLY, 0i32);
|
||||
let fdorerr: (i32 | os.oserror) = os.tryopen(argstr(path), os.flag.RDONLY, 0i32);
|
||||
let fd: i32 = -1;
|
||||
match (fdorerr) {
|
||||
case let v: i32 => fd = v;
|
||||
|
||||
@@ -74,7 +74,7 @@ export fn main(argc: i32, argv: **u8) i32 = {
|
||||
return 2;
|
||||
};
|
||||
|
||||
let fdorerr: (i32 | os.oserror) = os.tryopen(path, os.flag.RDONLY, 0i32);
|
||||
let fdorerr: (i32 | os.oserror) = os.tryopen(argstr(path), os.flag.RDONLY, 0i32);
|
||||
let fd: i32 = -1;
|
||||
match (fdorerr) {
|
||||
case let v: i32 => fd = v;
|
||||
|
||||
@@ -88,6 +88,36 @@ export fn exit(code: i32) void = {
|
||||
syscall1(nr.EXIT, code: i64);
|
||||
};
|
||||
|
||||
// PATH_MAX / pathbuf / kpath — port of Hare's ref/hare/sys/+linux/
|
||||
// syscalls.ha:25,27,29-55. Hare's `path` accepts a sum `(str |
|
||||
// []u8 | *const u8)`; ww's lib/os public surface narrows to `str`
|
||||
// (the Hare-faithful surface at ref/hare/os/os.ha:37,47,50 etc).
|
||||
// Internally, [[kpath]] copies the `str` bytes into a single
|
||||
// module-level [[pathbuf]] scratch slot and NUL-terminates so the
|
||||
// raw Linux syscalls (which require C strings) see a valid
|
||||
// terminator. Same precedent as Hare's static `pathbuf`.
|
||||
//
|
||||
// Non-reentrant: one buffer, every [[stat]] / [[open]] / etc.
|
||||
// rewrites it. Same caveat as strconv's `*tos` family (overwritten
|
||||
// on next call). Caller must NOT hold a kpath-returned pointer
|
||||
// across another lib/os path call. Graduates when ww grows a
|
||||
// thread story.
|
||||
//
|
||||
// `nil`-as-overflow over `(*u8 | oserror)`: wwstage over-allocates
|
||||
// 1-word-payload tagged returns to 24B (cstage emits 16B).
|
||||
// Task #9; revert at task #10 when fixed. Repro at
|
||||
// .ai/probe_tagged_return_pointer_payload.ww.
|
||||
export def PATH_MAX: i32 = 4096;
|
||||
let pathbuf: [4096]u8;
|
||||
|
||||
fn kpath(p: str) *u8 = {
|
||||
if (p.len + 1 >= PATH_MAX) { return nil: *u8; }; // ENAMETOOLONG
|
||||
let i: i32 = 0;
|
||||
for (i < p.len) { pathbuf[i] = p[i]; i += 1; };
|
||||
pathbuf[p.len] = 0u8;
|
||||
return &pathbuf[0];
|
||||
};
|
||||
|
||||
// Raw, non-fallible primitives. These return Linux's int conventions
|
||||
// (negative = -errno, non-negative = bytes/fd/etc). Callers wanting a
|
||||
// Hare-style fallible API use the wrappers below.
|
||||
@@ -126,15 +156,17 @@ export fn trywrite(fd: i32, buf: *u8, n: u64) (i64 | oserror) = {
|
||||
return r;
|
||||
};
|
||||
|
||||
// open — Linux open(2). Path must be NUL-terminated; callers using ww
|
||||
// `str` must ensure the bytes are followed by a 0 byte (literals are,
|
||||
// arena-copied paths usually are by construction). Returns -errno on
|
||||
// failure, fd otherwise. Higher-level callers prefer `tryopen`.
|
||||
export fn open(path: *u8, flags: flag, mode: i32) i32 = {
|
||||
return syscall3(nr.OPEN, path: i64, (flags as i32): i64, mode: i64): i32;
|
||||
// open — Linux open(2). Returns -errno on failure, fd otherwise.
|
||||
// Higher-level callers prefer `tryopen`. Mirrors Hare's os::open
|
||||
// (ref/hare/os/os.ha:117); kpath lands the bytes in pathbuf.
|
||||
// Returns -ENAMETOOLONG (-36) if the path overflows PATH_MAX.
|
||||
export fn open(path: str, flags: flag, mode: i32) i32 = {
|
||||
let p: *u8 = kpath(path);
|
||||
if (p == nil: *u8) { return -36i32; }; // ENAMETOOLONG
|
||||
return syscall3(nr.OPEN, p: i64, (flags as i32): i64, mode: i64): i32;
|
||||
};
|
||||
|
||||
export fn tryopen(path: *u8, flags: flag, mode: i32) (i32 | oserror) = {
|
||||
export fn tryopen(path: str, flags: flag, mode: i32) (i32 | oserror) = {
|
||||
let fd: i32 = open(path, flags, mode);
|
||||
if (fd < 0) { return fd: i64: oserror; };
|
||||
return fd;
|
||||
@@ -194,26 +226,37 @@ export fn writeall(fd: i32, buf: *u8, n: u64) (i64 | oserror) = {
|
||||
|
||||
// access(2): returns 0 if the file is reachable, negative errno
|
||||
// otherwise. mode is the bitset described in <unistd.h> (F_OK=0).
|
||||
export fn access(path: *u8, mode: i32) i32 = {
|
||||
return syscall2(nr.ACCESS, path: i64, mode: i64): i32;
|
||||
// Mirrors Hare's os::access (ref/hare/os/+linux/fs.ha:access).
|
||||
// Returns -ENAMETOOLONG (-36) if the path overflows PATH_MAX.
|
||||
export fn access(path: str, mode: i32) i32 = {
|
||||
let p: *u8 = kpath(path);
|
||||
if (p == nil: *u8) { return -36i32; };
|
||||
return syscall2(nr.ACCESS, p: i64, mode: i64): i32;
|
||||
};
|
||||
|
||||
// remove — unlink(2). Hare name; the underlying syscall is unlink(2).
|
||||
export fn remove(path: *u8) i32 = {
|
||||
return syscall1(nr.UNLINK, path: i64): i32;
|
||||
// remove — unlink(2). Mirrors Hare's os::remove
|
||||
// (ref/hare/os/os.ha:12).
|
||||
export fn remove(path: str) i32 = {
|
||||
let p: *u8 = kpath(path);
|
||||
if (p == nil: *u8) { return -36i32; };
|
||||
return syscall1(nr.UNLINK, p: i64): i32;
|
||||
};
|
||||
|
||||
// mkdir — mkdir(2). Path must be NUL-terminated. Mode is the unix
|
||||
// permission bitset (e.g. 0o700). Returns 0 on success, negative
|
||||
// errno otherwise. Hare name (os::mkdir).
|
||||
export fn mkdir(path: *u8, mode: i32) i32 = {
|
||||
return syscall2(nr.MKDIR, path: i64, mode: i64): i32;
|
||||
// mkdir — mkdir(2). Mode is the unix permission bitset (e.g. 0o700).
|
||||
// Returns 0 on success, negative errno otherwise. Mirrors Hare's
|
||||
// os::mkdir (ref/hare/os/os.ha:50).
|
||||
export fn mkdir(path: str, mode: i32) i32 = {
|
||||
let p: *u8 = kpath(path);
|
||||
if (p == nil: *u8) { return -36i32; };
|
||||
return syscall2(nr.MKDIR, p: i64, mode: i64): i32;
|
||||
};
|
||||
|
||||
// rmdir — rmdir(2). Path must be NUL-terminated. Returns 0 on
|
||||
// success, negative errno otherwise. Hare name (os::rmdir).
|
||||
export fn rmdir(path: *u8) i32 = {
|
||||
return syscall1(nr.RMDIR, path: i64): i32;
|
||||
// rmdir — rmdir(2). Mirrors Hare's os::rmdir
|
||||
// (ref/hare/os/os.ha:58).
|
||||
export fn rmdir(path: str) i32 = {
|
||||
let p: *u8 = kpath(path);
|
||||
if (p == nil: *u8) { return -36i32; };
|
||||
return syscall1(nr.RMDIR, p: i64): i32;
|
||||
};
|
||||
|
||||
// mkdirs — recursive mkdir. Creates `path` and any non-existent
|
||||
@@ -221,29 +264,28 @@ export fn rmdir(path: *u8) i32 = {
|
||||
// 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; };
|
||||
// Mirrors Hare's os::mkdirs (ref/hare/os/os.ha:54). The in-place
|
||||
// '/' → NUL splice walks the kpath-loaded [[pathbuf]] directly
|
||||
// instead of recursing through [[mkdir]] — re-entering kpath would
|
||||
// clobber the buffer mid-walk (single static slot, see kpath's
|
||||
// non-reentrancy note above).
|
||||
export fn mkdirs(path: str, mode: i32) (void | oserror) = {
|
||||
let cp: *u8 = kpath(path);
|
||||
if (cp == nil: *u8) { return -36i64: oserror; };
|
||||
let n: i32 = path.len;
|
||||
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.
|
||||
// raw MKDIR syscall on pathbuf, 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 (pathbuf[i] == 47u8) { // '/'
|
||||
pathbuf[i] = 0u8;
|
||||
let r: i32 = syscall2(nr.MKDIR,
|
||||
(&pathbuf[0]): i64, mode: i64): i32;
|
||||
pathbuf[i] = 47u8;
|
||||
if (r < 0) {
|
||||
if (r != -17) { return r: i64: oserror; };
|
||||
};
|
||||
@@ -251,8 +293,8 @@ export fn mkdirs(path: *u8, mode: i32) (void | oserror) = {
|
||||
i += 1;
|
||||
};
|
||||
|
||||
// mkdir the full path.
|
||||
let r: i32 = mkdir(path, mode);
|
||||
let r: i32 = syscall2(nr.MKDIR,
|
||||
(&pathbuf[0]): i64, mode: i64): i32;
|
||||
if (r < 0) {
|
||||
if (r != -17) { return r: i64: oserror; };
|
||||
};
|
||||
@@ -270,9 +312,14 @@ export fn fork() i32 = {
|
||||
return syscall0(nr.FORK): i32;
|
||||
};
|
||||
|
||||
// execve(2): on success, does not return.
|
||||
export fn execve(path: *u8, argv: **u8, envp: **u8) i32 = {
|
||||
return syscall3(nr.EXECVE, path: i64, argv: i64, envp: i64): i32;
|
||||
// execve(2): on success, does not return. Mirrors Hare's
|
||||
// os::exec::exec path arg (str). argv/envp stay `**u8` — the
|
||||
// kernel takes a NUL-pointer-terminated table of NUL-terminated
|
||||
// C strings, a different shape from a path.
|
||||
export fn execve(path: str, argv: **u8, envp: **u8) i32 = {
|
||||
let p: *u8 = kpath(path);
|
||||
if (p == nil: *u8) { return -36i32; };
|
||||
return syscall3(nr.EXECVE, p: i64, argv: i64, envp: i64): i32;
|
||||
};
|
||||
|
||||
// wait4(2): wait for `pid` (or any child if -1), store status in
|
||||
@@ -545,27 +592,31 @@ fn fillfilestat(out: *filestat, k: *kstat) void = {
|
||||
};
|
||||
|
||||
// stat — fill *out with metadata for `path`. Follows symlinks.
|
||||
// `path` must be NUL-terminated (lib/os convention; see task #23
|
||||
// for a planned `path: str` migration).
|
||||
// Returns ENAMETOOLONG (-36) as `oserror` if the path overflows
|
||||
// PATH_MAX.
|
||||
//
|
||||
// Mirrors Hare's sys::stat (ref/hare/sys/+linux/stat.ha:51) modulo
|
||||
// the out-param shape forced by the cgreturn 24B cap. Note: Hare's
|
||||
// higher-level fs::stat (ref/hare/fs/fs.ha:172) instead has lstat
|
||||
// semantics — we follow sys::stat's POSIX-stat behavior here.
|
||||
export fn stat(out: *filestat, path: *u8) (void | oserror) = {
|
||||
export fn stat(out: *filestat, path: str) (void | oserror) = {
|
||||
let cp: *u8 = kpath(path);
|
||||
if (cp == nil: *u8) { return -36i64: oserror; };
|
||||
let k: kstat;
|
||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
||||
AT_FDCWD: i64, path: i64, (&k): i64, 0i64);
|
||||
AT_FDCWD: i64, cp: i64, (&k): i64, 0i64);
|
||||
if (r < 0) { return r: oserror; };
|
||||
fillfilestat(out, &k);
|
||||
};
|
||||
|
||||
// lstat — like [[stat]] but does NOT follow a terminal symlink.
|
||||
// Mirrors Hare's sys::lstat (ref/hare/sys/+linux/stat.ha:57).
|
||||
export fn lstat(out: *filestat, path: *u8) (void | oserror) = {
|
||||
export fn lstat(out: *filestat, path: str) (void | oserror) = {
|
||||
let cp: *u8 = kpath(path);
|
||||
if (cp == nil: *u8) { return -36i64: oserror; };
|
||||
let k: kstat;
|
||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
||||
AT_FDCWD: i64, path: i64, (&k): i64,
|
||||
AT_FDCWD: i64, cp: i64, (&k): i64,
|
||||
AT_SYMLINK_NOFOLLOW: i64);
|
||||
if (r < 0) { return r: oserror; };
|
||||
fillfilestat(out, &k);
|
||||
@@ -586,7 +637,9 @@ export fn fstat(out: *filestat, fd: i32) (void | oserror) = {
|
||||
// exists — true if `path` resolves to anything (regular file,
|
||||
// directory, symlink, ...). Stat-shaped (Hare's `fs::exists`,
|
||||
// ref/hare/fs/fs.ha:196) — no separate syscall. Symlinks are
|
||||
// followed; a dangling symlink is `false`.
|
||||
// followed; a dangling symlink is `false`. ENAMETOOLONG is
|
||||
// swallowed as `false` — Hare's os::exists doc says "true if a
|
||||
// node exists at the given path, or false if not."
|
||||
//
|
||||
// Race warning: prefer "open and handle the error" over "exists
|
||||
// then open" in real code (Hare's docstring carries the same
|
||||
@@ -599,10 +652,12 @@ export fn fstat(out: *filestat, fd: i32) (void | oserror) = {
|
||||
// — same class as STATUS #22, surfaced first time a match on this
|
||||
// shape combined with an 80B local-struct local frame). Use the
|
||||
// match shape once #22 lands.
|
||||
export fn exists(path: *u8) bool = {
|
||||
export fn exists(path: str) bool = {
|
||||
let cp: *u8 = kpath(path);
|
||||
if (cp == nil: *u8) { return false; };
|
||||
let k: kstat;
|
||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
||||
AT_FDCWD: i64, path: i64, (&k): i64, 0i64);
|
||||
AT_FDCWD: i64, cp: i64, (&k): i64, 0i64);
|
||||
return r >= 0i64;
|
||||
};
|
||||
|
||||
@@ -1513,7 +1568,7 @@ export fn main() i32 = {
|
||||
// that step. RDONLY is 0; passing the literal keeps the call
|
||||
// site standalone-compilable to byte-identical asm on both
|
||||
// compilers.
|
||||
let fd: i32 = os.open(path.ptr, 0, 0i32);
|
||||
let fd: i32 = os.open(path, 0, 0i32);
|
||||
if (fd < 0) { return 21; };
|
||||
let rbuf: [128]u8;
|
||||
// Use raw os.read here (single syscall, plain i64) instead of
|
||||
|
||||
@@ -154,7 +154,7 @@ export fn main() i32 = {
|
||||
// that step. RDONLY is 0; passing the literal keeps the call
|
||||
// site standalone-compilable to byte-identical asm on both
|
||||
// compilers.
|
||||
let fd: i32 = os.open(path.ptr, 0, 0i32);
|
||||
let fd: i32 = os.open(path, 0, 0i32);
|
||||
if (fd < 0) { return 21; };
|
||||
let rbuf: [128]u8;
|
||||
// Use raw os.read here (single syscall, plain i64) instead of
|
||||
|
||||
Reference in New Issue
Block a user