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).
|
// behaviour Hare's lookup uses (`fmt::fatalf` on the HOME branch).
|
||||||
// Centralised so both branches in [[lookup]] share the error path.
|
// Centralised so both branches in [[lookup]] share the error path.
|
||||||
fn ensure() void = {
|
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 void => {};
|
||||||
case let _e: os.oserror => rtabort("dirs: mkdirs failed");
|
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);
|
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
|
// Raw, non-fallible primitives. These return Linux's int conventions
|
||||||
// (negative = -errno, non-negative = bytes/fd/etc). Callers wanting a
|
// (negative = -errno, non-negative = bytes/fd/etc). Callers wanting a
|
||||||
// Hare-style fallible API use the wrappers below.
|
// 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;
|
return r;
|
||||||
};
|
};
|
||||||
|
|
||||||
// open — Linux open(2). Path must be NUL-terminated; callers using ww
|
// open — Linux open(2). Returns -errno on failure, fd otherwise.
|
||||||
// `str` must ensure the bytes are followed by a 0 byte (literals are,
|
// Higher-level callers prefer `tryopen`. Mirrors Hare's os::open
|
||||||
// arena-copied paths usually are by construction). Returns -errno on
|
// (ref/hare/os/os.ha:117); kpath lands the bytes in pathbuf.
|
||||||
// failure, fd otherwise. Higher-level callers prefer `tryopen`.
|
// Returns -ENAMETOOLONG (-36) if the path overflows PATH_MAX.
|
||||||
export fn open(path: *u8, flags: flag, mode: i32) i32 = {
|
export fn open(path: str, flags: flag, mode: i32) i32 = {
|
||||||
return syscall3(nr.OPEN, path: i64, (flags as i32): i64, mode: i64): 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);
|
let fd: i32 = open(path, flags, mode);
|
||||||
if (fd < 0) { return fd: i64: oserror; };
|
if (fd < 0) { return fd: i64: oserror; };
|
||||||
return fd;
|
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
|
// access(2): returns 0 if the file is reachable, negative errno
|
||||||
// otherwise. mode is the bitset described in <unistd.h> (F_OK=0).
|
// otherwise. mode is the bitset described in <unistd.h> (F_OK=0).
|
||||||
export fn access(path: *u8, mode: i32) i32 = {
|
// Mirrors Hare's os::access (ref/hare/os/+linux/fs.ha:access).
|
||||||
return syscall2(nr.ACCESS, path: i64, mode: i64): i32;
|
// 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).
|
// remove — unlink(2). Mirrors Hare's os::remove
|
||||||
export fn remove(path: *u8) i32 = {
|
// (ref/hare/os/os.ha:12).
|
||||||
return syscall1(nr.UNLINK, path: i64): i32;
|
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
|
// mkdir — mkdir(2). Mode is the unix permission bitset (e.g. 0o700).
|
||||||
// permission bitset (e.g. 0o700). Returns 0 on success, negative
|
// Returns 0 on success, negative errno otherwise. Mirrors Hare's
|
||||||
// errno otherwise. Hare name (os::mkdir).
|
// os::mkdir (ref/hare/os/os.ha:50).
|
||||||
export fn mkdir(path: *u8, mode: i32) i32 = {
|
export fn mkdir(path: str, mode: i32) i32 = {
|
||||||
return syscall2(nr.MKDIR, path: i64, mode: i64): 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
|
// rmdir — rmdir(2). Mirrors Hare's os::rmdir
|
||||||
// success, negative errno otherwise. Hare name (os::rmdir).
|
// (ref/hare/os/os.ha:58).
|
||||||
export fn rmdir(path: *u8) i32 = {
|
export fn rmdir(path: str) i32 = {
|
||||||
return syscall1(nr.RMDIR, path: i64): 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
|
// 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);
|
// accepted (matches Hare's `errors::exists` skip in os::mkdirs);
|
||||||
// any other syscall failure surfaces as `oserror`.
|
// any other syscall failure surfaces as `oserror`.
|
||||||
//
|
//
|
||||||
// `path` must be NUL-terminated AND its bytes must be writable —
|
// Mirrors Hare's os::mkdirs (ref/hare/os/os.ha:54). The in-place
|
||||||
// mkdirs temporarily replaces '/' separators with NUL while
|
// '/' → NUL splice walks the kpath-loaded [[pathbuf]] directly
|
||||||
// invoking [[mkdir]] on each prefix, then restores them. Pointing
|
// instead of recursing through [[mkdir]] — re-entering kpath would
|
||||||
// `path` at a string literal will segfault. Callers hold the bytes
|
// clobber the buffer mid-walk (single static slot, see kpath's
|
||||||
// in a writable buffer (rt_alloc'd, a static `[N]u8`, etc.) — same
|
// non-reentrancy note above).
|
||||||
// precedent as [[temp.named]]'s pathbuf.
|
export fn mkdirs(path: str, mode: i32) (void | oserror) = {
|
||||||
//
|
let cp: *u8 = kpath(path);
|
||||||
// Mirrors Hare's os::mkdirs (recursive variant of os::mkdir).
|
if (cp == nil: *u8) { return -36i64: oserror; };
|
||||||
export fn mkdirs(path: *u8, mode: i32) (void | oserror) = {
|
let n: i32 = path.len;
|
||||||
// Find the path length (excluding trailing NUL).
|
|
||||||
let n: i32 = 0;
|
|
||||||
for (path[n] != 0u8) { n += 1; };
|
|
||||||
if (n == 0) { return; };
|
if (n == 0) { return; };
|
||||||
|
|
||||||
// Walk forward; at each '/' boundary, NUL-terminate the prefix,
|
// Walk forward; at each '/' boundary, NUL-terminate the prefix,
|
||||||
// mkdir it, restore the slash, continue. Skip index 0 so a
|
// raw MKDIR syscall on pathbuf, restore the slash, continue.
|
||||||
// leading '/' on absolute paths doesn't trigger an empty mkdir.
|
// Skip index 0 so a leading '/' on absolute paths doesn't
|
||||||
|
// trigger an empty mkdir.
|
||||||
let i: i32 = 1;
|
let i: i32 = 1;
|
||||||
for (i < n) {
|
for (i < n) {
|
||||||
if (path[i] == 47u8) { // '/'
|
if (pathbuf[i] == 47u8) { // '/'
|
||||||
path[i] = 0u8;
|
pathbuf[i] = 0u8;
|
||||||
let r: i32 = mkdir(path, mode);
|
let r: i32 = syscall2(nr.MKDIR,
|
||||||
path[i] = 47u8;
|
(&pathbuf[0]): i64, mode: i64): i32;
|
||||||
|
pathbuf[i] = 47u8;
|
||||||
if (r < 0) {
|
if (r < 0) {
|
||||||
if (r != -17) { return r: i64: oserror; };
|
if (r != -17) { return r: i64: oserror; };
|
||||||
};
|
};
|
||||||
@@ -250,8 +292,8 @@ export fn mkdirs(path: *u8, mode: i32) (void | oserror) = {
|
|||||||
i += 1;
|
i += 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
// mkdir the full path.
|
let r: i32 = syscall2(nr.MKDIR,
|
||||||
let r: i32 = mkdir(path, mode);
|
(&pathbuf[0]): i64, mode: i64): i32;
|
||||||
if (r < 0) {
|
if (r < 0) {
|
||||||
if (r != -17) { return r: i64: oserror; };
|
if (r != -17) { return r: i64: oserror; };
|
||||||
};
|
};
|
||||||
@@ -269,9 +311,14 @@ export fn fork() i32 = {
|
|||||||
return syscall0(nr.FORK): i32;
|
return syscall0(nr.FORK): i32;
|
||||||
};
|
};
|
||||||
|
|
||||||
// execve(2): on success, does not return.
|
// execve(2): on success, does not return. Mirrors Hare's
|
||||||
export fn execve(path: *u8, argv: **u8, envp: **u8) i32 = {
|
// os::exec::exec path arg (str). argv/envp stay `**u8` — the
|
||||||
return syscall3(nr.EXECVE, path: i64, argv: i64, envp: i64): i32;
|
// 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
|
// 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.
|
// stat — fill *out with metadata for `path`. Follows symlinks.
|
||||||
// `path` must be NUL-terminated (lib/os convention; see task #23
|
// Returns ENAMETOOLONG (-36) as `oserror` if the path overflows
|
||||||
// for a planned `path: str` migration).
|
// PATH_MAX.
|
||||||
//
|
//
|
||||||
// Mirrors Hare's sys::stat (ref/hare/sys/+linux/stat.ha:51) modulo
|
// 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
|
// 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
|
// higher-level fs::stat (ref/hare/fs/fs.ha:172) instead has lstat
|
||||||
// semantics — we follow sys::stat's POSIX-stat behavior here.
|
// 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 k: kstat;
|
||||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
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; };
|
if (r < 0) { return r: oserror; };
|
||||||
fillfilestat(out, &k);
|
fillfilestat(out, &k);
|
||||||
};
|
};
|
||||||
|
|
||||||
// lstat — like [[stat]] but does NOT follow a terminal symlink.
|
// lstat — like [[stat]] but does NOT follow a terminal symlink.
|
||||||
// Mirrors Hare's sys::lstat (ref/hare/sys/+linux/stat.ha:57).
|
// 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 k: kstat;
|
||||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
let r: i64 = syscall4(nr.NEWFSTATAT,
|
||||||
AT_FDCWD: i64, path: i64, (&k): i64,
|
AT_FDCWD: i64, cp: i64, (&k): i64,
|
||||||
AT_SYMLINK_NOFOLLOW: i64);
|
AT_SYMLINK_NOFOLLOW: i64);
|
||||||
if (r < 0) { return r: oserror; };
|
if (r < 0) { return r: oserror; };
|
||||||
fillfilestat(out, &k);
|
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,
|
// exists — true if `path` resolves to anything (regular file,
|
||||||
// directory, symlink, ...). Stat-shaped (Hare's `fs::exists`,
|
// directory, symlink, ...). Stat-shaped (Hare's `fs::exists`,
|
||||||
// ref/hare/fs/fs.ha:196) — no separate syscall. Symlinks are
|
// 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
|
// Race warning: prefer "open and handle the error" over "exists
|
||||||
// then open" in real code (Hare's docstring carries the same
|
// 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
|
// — same class as STATUS #22, surfaced first time a match on this
|
||||||
// shape combined with an 80B local-struct local frame). Use the
|
// shape combined with an 80B local-struct local frame). Use the
|
||||||
// match shape once #22 lands.
|
// 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 k: kstat;
|
||||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
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;
|
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.SIZE | os.stat_mask.INODE
|
||||||
| os.stat_mask.ATIME | os.stat_mask.MTIME
|
| os.stat_mask.ATIME | os.stat_mask.MTIME
|
||||||
| os.stat_mask.CTIME) as u32;
|
| os.stat_mask.CTIME) as u32;
|
||||||
match (os.stat(&fi, p.ptr)) {
|
match (os.stat(&fi, p)) {
|
||||||
case let e: os.oserror => fail();
|
case let e: os.oserror => fail();
|
||||||
case void => {
|
case void => {
|
||||||
if ((fi.mask as u32) != wantmask) { fail(); };
|
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 = {
|
@test fn test_stat_subdir() void = {
|
||||||
let p = envpath("WW_TEST_STAT_SUBDIR");
|
let p = envpath("WW_TEST_STAT_SUBDIR");
|
||||||
let fi: os.filestat;
|
let fi: os.filestat;
|
||||||
match (os.stat(&fi, p.ptr)) {
|
match (os.stat(&fi, p)) {
|
||||||
case let e: os.oserror => fail();
|
case let e: os.oserror => fail();
|
||||||
case void => {
|
case void => {
|
||||||
if (!istype(fi.mode, os.mode.DIR)) { fail(); };
|
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 = {
|
@test fn test_stat_noent() void = {
|
||||||
let p = envpath("WW_TEST_STAT_NOENT");
|
let p = envpath("WW_TEST_STAT_NOENT");
|
||||||
let fi: os.filestat;
|
let fi: os.filestat;
|
||||||
match (os.stat(&fi, p.ptr)) {
|
match (os.stat(&fi, p)) {
|
||||||
case void => fail();
|
case void => fail();
|
||||||
case let e: os.oserror => {
|
case let e: os.oserror => {
|
||||||
// ENOENT = 2 → raw errno is -2.
|
// 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 = {
|
@test fn test_stat_symlink_follow() void = {
|
||||||
let p = envpath("WW_TEST_STAT_SYMLINK");
|
let p = envpath("WW_TEST_STAT_SYMLINK");
|
||||||
let fi: os.filestat;
|
let fi: os.filestat;
|
||||||
match (os.stat(&fi, p.ptr)) {
|
match (os.stat(&fi, p)) {
|
||||||
case let e: os.oserror => fail();
|
case let e: os.oserror => fail();
|
||||||
case void => {
|
case void => {
|
||||||
if (!istype(fi.mode, os.mode.REG)) { fail(); };
|
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 = {
|
@test fn test_lstat_symlink_nofollow() void = {
|
||||||
let p = envpath("WW_TEST_STAT_SYMLINK");
|
let p = envpath("WW_TEST_STAT_SYMLINK");
|
||||||
let fi: os.filestat;
|
let fi: os.filestat;
|
||||||
match (os.lstat(&fi, p.ptr)) {
|
match (os.lstat(&fi, p)) {
|
||||||
case let e: os.oserror => fail();
|
case let e: os.oserror => fail();
|
||||||
case void => {
|
case void => {
|
||||||
if (!istype(fi.mode, os.mode.LINK)) { fail(); };
|
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 = {
|
@test fn test_fstat_regfile() void = {
|
||||||
let p = envpath("WW_TEST_STAT_REGFILE");
|
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(); };
|
if (fd < 0) { fail(); };
|
||||||
let fi: os.filestat;
|
let fi: os.filestat;
|
||||||
match (os.fstat(&fi, fd)) {
|
match (os.fstat(&fi, fd)) {
|
||||||
@@ -158,17 +158,53 @@ fn istype(m: os.mode, t: os.mode) bool = {
|
|||||||
|
|
||||||
@test fn test_exists_regfile() void = {
|
@test fn test_exists_regfile() void = {
|
||||||
let p = envpath("WW_TEST_STAT_REGFILE");
|
let p = envpath("WW_TEST_STAT_REGFILE");
|
||||||
if (!os.exists(p.ptr)) { fail(); };
|
if (!os.exists(p)) { fail(); };
|
||||||
};
|
};
|
||||||
|
|
||||||
@test fn test_exists_subdir() void = {
|
@test fn test_exists_subdir() void = {
|
||||||
let p = envpath("WW_TEST_STAT_SUBDIR");
|
let p = envpath("WW_TEST_STAT_SUBDIR");
|
||||||
if (!os.exists(p.ptr)) { fail(); };
|
if (!os.exists(p)) { fail(); };
|
||||||
};
|
};
|
||||||
|
|
||||||
@test fn test_exists_noent() void = {
|
@test fn test_exists_noent() void = {
|
||||||
let p = envpath("WW_TEST_STAT_NOENT");
|
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 = {
|
export fn main() i32 = {
|
||||||
@@ -181,5 +217,7 @@ export fn main() i32 = {
|
|||||||
signalled = 7; test_exists_regfile();
|
signalled = 7; test_exists_regfile();
|
||||||
signalled = 8; test_exists_subdir();
|
signalled = 8; test_exists_subdir();
|
||||||
signalled = 9; test_exists_noent();
|
signalled = 9; test_exists_noent();
|
||||||
|
signalled = 10; test_stat_toolong();
|
||||||
|
signalled = 11; test_exists_toolong();
|
||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -186,7 +186,9 @@ export fn named(outfd: *i32, outpath: *str,
|
|||||||
};
|
};
|
||||||
for (true) {
|
for (true) {
|
||||||
pathlen = makenamed(dir);
|
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) {
|
if (fd >= 0) {
|
||||||
*outfd = fd;
|
*outfd = fd;
|
||||||
let p: str;
|
let p: str;
|
||||||
@@ -227,7 +229,9 @@ export fn file(iomode: mode, perm: i32) (i32 | os.oserror) = {
|
|||||||
export fn dir() str = {
|
export fn dir() str = {
|
||||||
for (true) {
|
for (true) {
|
||||||
pathlen = makedir();
|
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) {
|
if (r >= 0) {
|
||||||
let p: str;
|
let p: str;
|
||||||
p.ptr = &pathbuf[0];
|
p.ptr = &pathbuf[0];
|
||||||
|
|||||||
@@ -94,13 +94,13 @@ fn streq(a: str, b: str) bool = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// File exists pre-cleanup.
|
// 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.close(fd) != 0) { fail(); };
|
||||||
if (os.remove(p.ptr) != 0) { fail(); };
|
if (os.remove(p) != 0) { fail(); };
|
||||||
|
|
||||||
// Cleanup landed.
|
// Cleanup landed.
|
||||||
if (os.access(p.ptr, 0i32) == 0) { fail(); };
|
if (os.access(p, 0i32) == 0) { fail(); };
|
||||||
|
|
||||||
i += 1;
|
i += 1;
|
||||||
};
|
};
|
||||||
@@ -156,10 +156,10 @@ fn strslice(p: str, lo: i32, hi: i32) str = {
|
|||||||
if (fd1 == fd2) { fail(); };
|
if (fd1 == fd2) { fail(); };
|
||||||
|
|
||||||
if (os.close(fd2) != 0) { 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.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
|
// 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(); };
|
if (d.ptr[d.len] != 0u8) { fail(); };
|
||||||
|
|
||||||
// Dir exists.
|
// 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:
|
// Snapshot the dir path into a local NUL-terminated buffer:
|
||||||
// we'll need it after os.open() (the child create) leaves
|
// the os.* path entrypoints now copy through lib/os.pathbuf
|
||||||
// the buffer alone, but it's good hygiene given future
|
// (kpath), so d's view into temp.pathbuf is safe; the
|
||||||
// helpers might share pathbuf.
|
// snapshot still buys robustness against future helpers
|
||||||
|
// that might share temp's buffer.
|
||||||
let dsnap: [128]u8;
|
let dsnap: [128]u8;
|
||||||
let dlen: i32 = d.len;
|
let dlen: i32 = d.len;
|
||||||
let s: i32 = 0;
|
let s: i32 = 0;
|
||||||
for (s < d.len) { dsnap[s] = d[s]; s += 1; };
|
for (s < d.len) { dsnap[s] = d[s]; s += 1; };
|
||||||
dsnap[d.len] = 0u8;
|
dsnap[d.len] = 0u8;
|
||||||
|
let dview: str;
|
||||||
|
dview.ptr = &dsnap[0]; dview.len = dlen;
|
||||||
|
|
||||||
if (childn[i] > 0) {
|
if (childn[i] > 0) {
|
||||||
// Build "<d>/x\0" in a local buffer.
|
// 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] = 47u8; off += 1; // '/'
|
||||||
cbuf[off] = 120u8; off += 1; // 'x'
|
cbuf[off] = 120u8; off += 1; // 'x'
|
||||||
cbuf[off] = 0u8;
|
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 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(); };
|
if (fd < 0) { fail(); };
|
||||||
let payload: [3]u8;
|
let payload: [3]u8;
|
||||||
payload[0] = 88u8; payload[1] = 89u8; payload[2] = 90u8; // "XYZ"
|
payload[0] = 88u8; payload[1] = 89u8; payload[2] = 90u8; // "XYZ"
|
||||||
let wr: i64 = os.write(fd, &payload[0], 3u64);
|
let wr: i64 = os.write(fd, &payload[0], 3u64);
|
||||||
if (wr != 3i64) { fail(); };
|
if (wr != 3i64) { fail(); };
|
||||||
if (os.close(fd) != 0) { 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).
|
// touched between dir() and here).
|
||||||
if (os.rmdir(&dsnap[0]) != 0) { fail(); };
|
if (os.rmdir(dview) != 0) { fail(); };
|
||||||
|
|
||||||
// Cleanup landed.
|
// Cleanup landed.
|
||||||
if (os.access(&dsnap[0], 0i32) == 0) { fail(); };
|
if (os.access(dview, 0i32) == 0) { fail(); };
|
||||||
|
|
||||||
i += 1;
|
i += 1;
|
||||||
};
|
};
|
||||||
@@ -249,8 +254,8 @@ fn strslice(p: str, lo: i32, hi: i32) str = {
|
|||||||
if (streq(strslice(d2, 0, d2.len), dsnap)) { fail(); };
|
if (streq(strslice(d2, 0, d2.len), dsnap)) { fail(); };
|
||||||
|
|
||||||
// Cleanup both, using snap for d1's old contents.
|
// Cleanup both, using snap for d1's old contents.
|
||||||
if (os.rmdir(d2.ptr) != 0) { fail(); };
|
if (os.rmdir(d2) != 0) { fail(); };
|
||||||
if (os.rmdir(&snap[0]) != 0) { fail(); };
|
if (os.rmdir(dsnap) != 0) { fail(); };
|
||||||
};
|
};
|
||||||
|
|
||||||
export fn main() i32 = {
|
export fn main() i32 = {
|
||||||
|
|||||||
@@ -88,6 +88,36 @@ export fn exit(code: i32) void = {
|
|||||||
syscall1(nr.EXIT, code: i64);
|
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
|
// Raw, non-fallible primitives. These return Linux's int conventions
|
||||||
// (negative = -errno, non-negative = bytes/fd/etc). Callers wanting a
|
// (negative = -errno, non-negative = bytes/fd/etc). Callers wanting a
|
||||||
// Hare-style fallible API use the wrappers below.
|
// 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;
|
return r;
|
||||||
};
|
};
|
||||||
|
|
||||||
// open — Linux open(2). Path must be NUL-terminated; callers using ww
|
// open — Linux open(2). Returns -errno on failure, fd otherwise.
|
||||||
// `str` must ensure the bytes are followed by a 0 byte (literals are,
|
// Higher-level callers prefer `tryopen`. Mirrors Hare's os::open
|
||||||
// arena-copied paths usually are by construction). Returns -errno on
|
// (ref/hare/os/os.ha:117); kpath lands the bytes in pathbuf.
|
||||||
// failure, fd otherwise. Higher-level callers prefer `tryopen`.
|
// Returns -ENAMETOOLONG (-36) if the path overflows PATH_MAX.
|
||||||
export fn open(path: *u8, flags: flag, mode: i32) i32 = {
|
export fn open(path: str, flags: flag, mode: i32) i32 = {
|
||||||
return syscall3(nr.OPEN, path: i64, (flags as i32): i64, mode: i64): 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);
|
let fd: i32 = open(path, flags, mode);
|
||||||
if (fd < 0) { return fd: i64: oserror; };
|
if (fd < 0) { return fd: i64: oserror; };
|
||||||
return fd;
|
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
|
// access(2): returns 0 if the file is reachable, negative errno
|
||||||
// otherwise. mode is the bitset described in <unistd.h> (F_OK=0).
|
// otherwise. mode is the bitset described in <unistd.h> (F_OK=0).
|
||||||
export fn access(path: *u8, mode: i32) i32 = {
|
// Mirrors Hare's os::access (ref/hare/os/+linux/fs.ha:access).
|
||||||
return syscall2(nr.ACCESS, path: i64, mode: i64): i32;
|
// 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).
|
// remove — unlink(2). Mirrors Hare's os::remove
|
||||||
export fn remove(path: *u8) i32 = {
|
// (ref/hare/os/os.ha:12).
|
||||||
return syscall1(nr.UNLINK, path: i64): i32;
|
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
|
// mkdir — mkdir(2). Mode is the unix permission bitset (e.g. 0o700).
|
||||||
// permission bitset (e.g. 0o700). Returns 0 on success, negative
|
// Returns 0 on success, negative errno otherwise. Mirrors Hare's
|
||||||
// errno otherwise. Hare name (os::mkdir).
|
// os::mkdir (ref/hare/os/os.ha:50).
|
||||||
export fn mkdir(path: *u8, mode: i32) i32 = {
|
export fn mkdir(path: str, mode: i32) i32 = {
|
||||||
return syscall2(nr.MKDIR, path: i64, mode: i64): 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
|
// rmdir — rmdir(2). Mirrors Hare's os::rmdir
|
||||||
// success, negative errno otherwise. Hare name (os::rmdir).
|
// (ref/hare/os/os.ha:58).
|
||||||
export fn rmdir(path: *u8) i32 = {
|
export fn rmdir(path: str) i32 = {
|
||||||
return syscall1(nr.RMDIR, path: i64): 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
|
// 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);
|
// accepted (matches Hare's `errors::exists` skip in os::mkdirs);
|
||||||
// any other syscall failure surfaces as `oserror`.
|
// any other syscall failure surfaces as `oserror`.
|
||||||
//
|
//
|
||||||
// `path` must be NUL-terminated AND its bytes must be writable —
|
// Mirrors Hare's os::mkdirs (ref/hare/os/os.ha:54). The in-place
|
||||||
// mkdirs temporarily replaces '/' separators with NUL while
|
// '/' → NUL splice walks the kpath-loaded [[pathbuf]] directly
|
||||||
// invoking [[mkdir]] on each prefix, then restores them. Pointing
|
// instead of recursing through [[mkdir]] — re-entering kpath would
|
||||||
// `path` at a string literal will segfault. Callers hold the bytes
|
// clobber the buffer mid-walk (single static slot, see kpath's
|
||||||
// in a writable buffer (rt_alloc'd, a static `[N]u8`, etc.) — same
|
// non-reentrancy note above).
|
||||||
// precedent as [[temp.named]]'s pathbuf.
|
export fn mkdirs(path: str, mode: i32) (void | oserror) = {
|
||||||
//
|
let cp: *u8 = kpath(path);
|
||||||
// Mirrors Hare's os::mkdirs (recursive variant of os::mkdir).
|
if (cp == nil: *u8) { return -36i64: oserror; };
|
||||||
export fn mkdirs(path: *u8, mode: i32) (void | oserror) = {
|
let n: i32 = path.len;
|
||||||
// Find the path length (excluding trailing NUL).
|
|
||||||
let n: i32 = 0;
|
|
||||||
for (path[n] != 0u8) { n += 1; };
|
|
||||||
if (n == 0) { return; };
|
if (n == 0) { return; };
|
||||||
|
|
||||||
// Walk forward; at each '/' boundary, NUL-terminate the prefix,
|
// Walk forward; at each '/' boundary, NUL-terminate the prefix,
|
||||||
// mkdir it, restore the slash, continue. Skip index 0 so a
|
// raw MKDIR syscall on pathbuf, restore the slash, continue.
|
||||||
// leading '/' on absolute paths doesn't trigger an empty mkdir.
|
// Skip index 0 so a leading '/' on absolute paths doesn't
|
||||||
|
// trigger an empty mkdir.
|
||||||
let i: i32 = 1;
|
let i: i32 = 1;
|
||||||
for (i < n) {
|
for (i < n) {
|
||||||
if (path[i] == 47u8) { // '/'
|
if (pathbuf[i] == 47u8) { // '/'
|
||||||
path[i] = 0u8;
|
pathbuf[i] = 0u8;
|
||||||
let r: i32 = mkdir(path, mode);
|
let r: i32 = syscall2(nr.MKDIR,
|
||||||
path[i] = 47u8;
|
(&pathbuf[0]): i64, mode: i64): i32;
|
||||||
|
pathbuf[i] = 47u8;
|
||||||
if (r < 0) {
|
if (r < 0) {
|
||||||
if (r != -17) { return r: i64: oserror; };
|
if (r != -17) { return r: i64: oserror; };
|
||||||
};
|
};
|
||||||
@@ -251,8 +293,8 @@ export fn mkdirs(path: *u8, mode: i32) (void | oserror) = {
|
|||||||
i += 1;
|
i += 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
// mkdir the full path.
|
let r: i32 = syscall2(nr.MKDIR,
|
||||||
let r: i32 = mkdir(path, mode);
|
(&pathbuf[0]): i64, mode: i64): i32;
|
||||||
if (r < 0) {
|
if (r < 0) {
|
||||||
if (r != -17) { return r: i64: oserror; };
|
if (r != -17) { return r: i64: oserror; };
|
||||||
};
|
};
|
||||||
@@ -270,9 +312,14 @@ export fn fork() i32 = {
|
|||||||
return syscall0(nr.FORK): i32;
|
return syscall0(nr.FORK): i32;
|
||||||
};
|
};
|
||||||
|
|
||||||
// execve(2): on success, does not return.
|
// execve(2): on success, does not return. Mirrors Hare's
|
||||||
export fn execve(path: *u8, argv: **u8, envp: **u8) i32 = {
|
// os::exec::exec path arg (str). argv/envp stay `**u8` — the
|
||||||
return syscall3(nr.EXECVE, path: i64, argv: i64, envp: i64): i32;
|
// 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
|
// 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.
|
// stat — fill *out with metadata for `path`. Follows symlinks.
|
||||||
// `path` must be NUL-terminated (lib/os convention; see task #23
|
// Returns ENAMETOOLONG (-36) as `oserror` if the path overflows
|
||||||
// for a planned `path: str` migration).
|
// PATH_MAX.
|
||||||
//
|
//
|
||||||
// Mirrors Hare's sys::stat (ref/hare/sys/+linux/stat.ha:51) modulo
|
// 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
|
// 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
|
// higher-level fs::stat (ref/hare/fs/fs.ha:172) instead has lstat
|
||||||
// semantics — we follow sys::stat's POSIX-stat behavior here.
|
// 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 k: kstat;
|
||||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
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; };
|
if (r < 0) { return r: oserror; };
|
||||||
fillfilestat(out, &k);
|
fillfilestat(out, &k);
|
||||||
};
|
};
|
||||||
|
|
||||||
// lstat — like [[stat]] but does NOT follow a terminal symlink.
|
// lstat — like [[stat]] but does NOT follow a terminal symlink.
|
||||||
// Mirrors Hare's sys::lstat (ref/hare/sys/+linux/stat.ha:57).
|
// 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 k: kstat;
|
||||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
let r: i64 = syscall4(nr.NEWFSTATAT,
|
||||||
AT_FDCWD: i64, path: i64, (&k): i64,
|
AT_FDCWD: i64, cp: i64, (&k): i64,
|
||||||
AT_SYMLINK_NOFOLLOW: i64);
|
AT_SYMLINK_NOFOLLOW: i64);
|
||||||
if (r < 0) { return r: oserror; };
|
if (r < 0) { return r: oserror; };
|
||||||
fillfilestat(out, &k);
|
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,
|
// exists — true if `path` resolves to anything (regular file,
|
||||||
// directory, symlink, ...). Stat-shaped (Hare's `fs::exists`,
|
// directory, symlink, ...). Stat-shaped (Hare's `fs::exists`,
|
||||||
// ref/hare/fs/fs.ha:196) — no separate syscall. Symlinks are
|
// 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
|
// Race warning: prefer "open and handle the error" over "exists
|
||||||
// then open" in real code (Hare's docstring carries the same
|
// 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
|
// — same class as STATUS #22, surfaced first time a match on this
|
||||||
// shape combined with an 80B local-struct local frame). Use the
|
// shape combined with an 80B local-struct local frame). Use the
|
||||||
// match shape once #22 lands.
|
// 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 k: kstat;
|
||||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
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;
|
return r >= 0i64;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -2896,9 +2951,18 @@ fn cstrlen(p: *u8) u64 = {
|
|||||||
return n;
|
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.
|
// Slurp the whole file into a fresh buffer.
|
||||||
fn slurp(path: *u8) (*u8, u64) = {
|
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; };
|
if (fd < 0) { return nil, 0u64; };
|
||||||
let szr: (i64 | os.oserror) = os.filesize(fd);
|
let szr: (i64 | os.oserror) = os.filesize(fd);
|
||||||
let n: i64 = 0i64;
|
let n: i64 = 0i64;
|
||||||
@@ -2974,7 +3038,7 @@ export fn main(argc: i32, argv: **u8) i32 = {
|
|||||||
if (encode(&s) != 0) { return 1; };
|
if (encode(&s) != 0) { return 1; };
|
||||||
|
|
||||||
// Open output for write.
|
// 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) {
|
if (fd < 0) {
|
||||||
os.write(2, "w6a: cannot open output\n".ptr, 23u64);
|
os.write(2, "w6a: cannot open output\n".ptr, 23u64);
|
||||||
return 1;
|
return 1;
|
||||||
|
|||||||
@@ -30,9 +30,18 @@ fn cstrlen(p: *u8) u64 = {
|
|||||||
return n;
|
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.
|
// Slurp the whole file into a fresh buffer.
|
||||||
fn slurp(path: *u8) (*u8, u64) = {
|
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; };
|
if (fd < 0) { return nil, 0u64; };
|
||||||
let szr: (i64 | os.oserror) = os.filesize(fd);
|
let szr: (i64 | os.oserror) = os.filesize(fd);
|
||||||
let n: i64 = 0i64;
|
let n: i64 = 0i64;
|
||||||
@@ -108,7 +117,7 @@ export fn main(argc: i32, argv: **u8) i32 = {
|
|||||||
if (encode(&s) != 0) { return 1; };
|
if (encode(&s) != 0) { return 1; };
|
||||||
|
|
||||||
// Open output for write.
|
// 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) {
|
if (fd < 0) {
|
||||||
os.write(2, "w6a: cannot open output\n".ptr, 23u64);
|
os.write(2, "w6a: cannot open output\n".ptr, 23u64);
|
||||||
return 1;
|
return 1;
|
||||||
|
|||||||
@@ -88,6 +88,36 @@ export fn exit(code: i32) void = {
|
|||||||
syscall1(nr.EXIT, code: i64);
|
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
|
// Raw, non-fallible primitives. These return Linux's int conventions
|
||||||
// (negative = -errno, non-negative = bytes/fd/etc). Callers wanting a
|
// (negative = -errno, non-negative = bytes/fd/etc). Callers wanting a
|
||||||
// Hare-style fallible API use the wrappers below.
|
// 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;
|
return r;
|
||||||
};
|
};
|
||||||
|
|
||||||
// open — Linux open(2). Path must be NUL-terminated; callers using ww
|
// open — Linux open(2). Returns -errno on failure, fd otherwise.
|
||||||
// `str` must ensure the bytes are followed by a 0 byte (literals are,
|
// Higher-level callers prefer `tryopen`. Mirrors Hare's os::open
|
||||||
// arena-copied paths usually are by construction). Returns -errno on
|
// (ref/hare/os/os.ha:117); kpath lands the bytes in pathbuf.
|
||||||
// failure, fd otherwise. Higher-level callers prefer `tryopen`.
|
// Returns -ENAMETOOLONG (-36) if the path overflows PATH_MAX.
|
||||||
export fn open(path: *u8, flags: flag, mode: i32) i32 = {
|
export fn open(path: str, flags: flag, mode: i32) i32 = {
|
||||||
return syscall3(nr.OPEN, path: i64, (flags as i32): i64, mode: i64): 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);
|
let fd: i32 = open(path, flags, mode);
|
||||||
if (fd < 0) { return fd: i64: oserror; };
|
if (fd < 0) { return fd: i64: oserror; };
|
||||||
return fd;
|
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
|
// access(2): returns 0 if the file is reachable, negative errno
|
||||||
// otherwise. mode is the bitset described in <unistd.h> (F_OK=0).
|
// otherwise. mode is the bitset described in <unistd.h> (F_OK=0).
|
||||||
export fn access(path: *u8, mode: i32) i32 = {
|
// Mirrors Hare's os::access (ref/hare/os/+linux/fs.ha:access).
|
||||||
return syscall2(nr.ACCESS, path: i64, mode: i64): i32;
|
// 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).
|
// remove — unlink(2). Mirrors Hare's os::remove
|
||||||
export fn remove(path: *u8) i32 = {
|
// (ref/hare/os/os.ha:12).
|
||||||
return syscall1(nr.UNLINK, path: i64): i32;
|
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
|
// mkdir — mkdir(2). Mode is the unix permission bitset (e.g. 0o700).
|
||||||
// permission bitset (e.g. 0o700). Returns 0 on success, negative
|
// Returns 0 on success, negative errno otherwise. Mirrors Hare's
|
||||||
// errno otherwise. Hare name (os::mkdir).
|
// os::mkdir (ref/hare/os/os.ha:50).
|
||||||
export fn mkdir(path: *u8, mode: i32) i32 = {
|
export fn mkdir(path: str, mode: i32) i32 = {
|
||||||
return syscall2(nr.MKDIR, path: i64, mode: i64): 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
|
// rmdir — rmdir(2). Mirrors Hare's os::rmdir
|
||||||
// success, negative errno otherwise. Hare name (os::rmdir).
|
// (ref/hare/os/os.ha:58).
|
||||||
export fn rmdir(path: *u8) i32 = {
|
export fn rmdir(path: str) i32 = {
|
||||||
return syscall1(nr.RMDIR, path: i64): 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
|
// 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);
|
// accepted (matches Hare's `errors::exists` skip in os::mkdirs);
|
||||||
// any other syscall failure surfaces as `oserror`.
|
// any other syscall failure surfaces as `oserror`.
|
||||||
//
|
//
|
||||||
// `path` must be NUL-terminated AND its bytes must be writable —
|
// Mirrors Hare's os::mkdirs (ref/hare/os/os.ha:54). The in-place
|
||||||
// mkdirs temporarily replaces '/' separators with NUL while
|
// '/' → NUL splice walks the kpath-loaded [[pathbuf]] directly
|
||||||
// invoking [[mkdir]] on each prefix, then restores them. Pointing
|
// instead of recursing through [[mkdir]] — re-entering kpath would
|
||||||
// `path` at a string literal will segfault. Callers hold the bytes
|
// clobber the buffer mid-walk (single static slot, see kpath's
|
||||||
// in a writable buffer (rt_alloc'd, a static `[N]u8`, etc.) — same
|
// non-reentrancy note above).
|
||||||
// precedent as [[temp.named]]'s pathbuf.
|
export fn mkdirs(path: str, mode: i32) (void | oserror) = {
|
||||||
//
|
let cp: *u8 = kpath(path);
|
||||||
// Mirrors Hare's os::mkdirs (recursive variant of os::mkdir).
|
if (cp == nil: *u8) { return -36i64: oserror; };
|
||||||
export fn mkdirs(path: *u8, mode: i32) (void | oserror) = {
|
let n: i32 = path.len;
|
||||||
// Find the path length (excluding trailing NUL).
|
|
||||||
let n: i32 = 0;
|
|
||||||
for (path[n] != 0u8) { n += 1; };
|
|
||||||
if (n == 0) { return; };
|
if (n == 0) { return; };
|
||||||
|
|
||||||
// Walk forward; at each '/' boundary, NUL-terminate the prefix,
|
// Walk forward; at each '/' boundary, NUL-terminate the prefix,
|
||||||
// mkdir it, restore the slash, continue. Skip index 0 so a
|
// raw MKDIR syscall on pathbuf, restore the slash, continue.
|
||||||
// leading '/' on absolute paths doesn't trigger an empty mkdir.
|
// Skip index 0 so a leading '/' on absolute paths doesn't
|
||||||
|
// trigger an empty mkdir.
|
||||||
let i: i32 = 1;
|
let i: i32 = 1;
|
||||||
for (i < n) {
|
for (i < n) {
|
||||||
if (path[i] == 47u8) { // '/'
|
if (pathbuf[i] == 47u8) { // '/'
|
||||||
path[i] = 0u8;
|
pathbuf[i] = 0u8;
|
||||||
let r: i32 = mkdir(path, mode);
|
let r: i32 = syscall2(nr.MKDIR,
|
||||||
path[i] = 47u8;
|
(&pathbuf[0]): i64, mode: i64): i32;
|
||||||
|
pathbuf[i] = 47u8;
|
||||||
if (r < 0) {
|
if (r < 0) {
|
||||||
if (r != -17) { return r: i64: oserror; };
|
if (r != -17) { return r: i64: oserror; };
|
||||||
};
|
};
|
||||||
@@ -251,8 +293,8 @@ export fn mkdirs(path: *u8, mode: i32) (void | oserror) = {
|
|||||||
i += 1;
|
i += 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
// mkdir the full path.
|
let r: i32 = syscall2(nr.MKDIR,
|
||||||
let r: i32 = mkdir(path, mode);
|
(&pathbuf[0]): i64, mode: i64): i32;
|
||||||
if (r < 0) {
|
if (r < 0) {
|
||||||
if (r != -17) { return r: i64: oserror; };
|
if (r != -17) { return r: i64: oserror; };
|
||||||
};
|
};
|
||||||
@@ -270,9 +312,14 @@ export fn fork() i32 = {
|
|||||||
return syscall0(nr.FORK): i32;
|
return syscall0(nr.FORK): i32;
|
||||||
};
|
};
|
||||||
|
|
||||||
// execve(2): on success, does not return.
|
// execve(2): on success, does not return. Mirrors Hare's
|
||||||
export fn execve(path: *u8, argv: **u8, envp: **u8) i32 = {
|
// os::exec::exec path arg (str). argv/envp stay `**u8` — the
|
||||||
return syscall3(nr.EXECVE, path: i64, argv: i64, envp: i64): i32;
|
// 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
|
// 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.
|
// stat — fill *out with metadata for `path`. Follows symlinks.
|
||||||
// `path` must be NUL-terminated (lib/os convention; see task #23
|
// Returns ENAMETOOLONG (-36) as `oserror` if the path overflows
|
||||||
// for a planned `path: str` migration).
|
// PATH_MAX.
|
||||||
//
|
//
|
||||||
// Mirrors Hare's sys::stat (ref/hare/sys/+linux/stat.ha:51) modulo
|
// 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
|
// 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
|
// higher-level fs::stat (ref/hare/fs/fs.ha:172) instead has lstat
|
||||||
// semantics — we follow sys::stat's POSIX-stat behavior here.
|
// 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 k: kstat;
|
||||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
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; };
|
if (r < 0) { return r: oserror; };
|
||||||
fillfilestat(out, &k);
|
fillfilestat(out, &k);
|
||||||
};
|
};
|
||||||
|
|
||||||
// lstat — like [[stat]] but does NOT follow a terminal symlink.
|
// lstat — like [[stat]] but does NOT follow a terminal symlink.
|
||||||
// Mirrors Hare's sys::lstat (ref/hare/sys/+linux/stat.ha:57).
|
// 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 k: kstat;
|
||||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
let r: i64 = syscall4(nr.NEWFSTATAT,
|
||||||
AT_FDCWD: i64, path: i64, (&k): i64,
|
AT_FDCWD: i64, cp: i64, (&k): i64,
|
||||||
AT_SYMLINK_NOFOLLOW: i64);
|
AT_SYMLINK_NOFOLLOW: i64);
|
||||||
if (r < 0) { return r: oserror; };
|
if (r < 0) { return r: oserror; };
|
||||||
fillfilestat(out, &k);
|
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,
|
// exists — true if `path` resolves to anything (regular file,
|
||||||
// directory, symlink, ...). Stat-shaped (Hare's `fs::exists`,
|
// directory, symlink, ...). Stat-shaped (Hare's `fs::exists`,
|
||||||
// ref/hare/fs/fs.ha:196) — no separate syscall. Symlinks are
|
// 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
|
// Race warning: prefer "open and handle the error" over "exists
|
||||||
// then open" in real code (Hare's docstring carries the same
|
// 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
|
// — same class as STATUS #22, surfaced first time a match on this
|
||||||
// shape combined with an 80B local-struct local frame). Use the
|
// shape combined with an 80B local-struct local frame). Use the
|
||||||
// match shape once #22 lands.
|
// 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 k: kstat;
|
||||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
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;
|
return r >= 0i64;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -19129,8 +19184,18 @@ fn cstrlen(p: *u8) u64 = {
|
|||||||
return n;
|
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) = {
|
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; };
|
if (fd < 0) { return nil, 0u64; };
|
||||||
let szr: (i64 | os.oserror) = os.filesize(fd);
|
let szr: (i64 | os.oserror) = os.filesize(fd);
|
||||||
let n: i64 = 0i64;
|
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
|
// cgen.ww writes directly to fd 1; dup2 lets us reuse it without
|
||||||
// threading a file descriptor through the emit helpers.
|
// threading a file descriptor through the emit helpers.
|
||||||
if (out != nil) {
|
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
|
os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC, 420i32); // 0o644
|
||||||
if (ofd < 0) {
|
if (ofd < 0) {
|
||||||
os.write(2, "w6c: cannot open output\n".ptr, 23u64);
|
os.write(2, "w6c: cannot open output\n".ptr, 23u64);
|
||||||
|
|||||||
@@ -39,8 +39,18 @@ fn cstrlen(p: *u8) u64 = {
|
|||||||
return n;
|
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) = {
|
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; };
|
if (fd < 0) { return nil, 0u64; };
|
||||||
let szr: (i64 | os.oserror) = os.filesize(fd);
|
let szr: (i64 | os.oserror) = os.filesize(fd);
|
||||||
let n: i64 = 0i64;
|
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
|
// cgen.ww writes directly to fd 1; dup2 lets us reuse it without
|
||||||
// threading a file descriptor through the emit helpers.
|
// threading a file descriptor through the emit helpers.
|
||||||
if (out != nil) {
|
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
|
os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC, 420i32); // 0o644
|
||||||
if (ofd < 0) {
|
if (ofd < 0) {
|
||||||
os.write(2, "w6c: cannot open output\n".ptr, 23u64);
|
os.write(2, "w6c: cannot open output\n".ptr, 23u64);
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ fn dbasename(p: *u8) *u8 = {
|
|||||||
// ---- file slurp --------------------------------------------------------
|
// ---- file slurp --------------------------------------------------------
|
||||||
|
|
||||||
fn slurpso(path: *u8) (*u8, u64) = {
|
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; };
|
if (fd < 0) { return nil, 0u64; };
|
||||||
let szr: (i64 | os.oserror) = os.filesize(fd);
|
let szr: (i64 | os.oserror) = os.filesize(fd);
|
||||||
let n: i64 = 0i64;
|
let n: i64 = 0i64;
|
||||||
|
|||||||
@@ -88,6 +88,36 @@ export fn exit(code: i32) void = {
|
|||||||
syscall1(nr.EXIT, code: i64);
|
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
|
// Raw, non-fallible primitives. These return Linux's int conventions
|
||||||
// (negative = -errno, non-negative = bytes/fd/etc). Callers wanting a
|
// (negative = -errno, non-negative = bytes/fd/etc). Callers wanting a
|
||||||
// Hare-style fallible API use the wrappers below.
|
// 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;
|
return r;
|
||||||
};
|
};
|
||||||
|
|
||||||
// open — Linux open(2). Path must be NUL-terminated; callers using ww
|
// open — Linux open(2). Returns -errno on failure, fd otherwise.
|
||||||
// `str` must ensure the bytes are followed by a 0 byte (literals are,
|
// Higher-level callers prefer `tryopen`. Mirrors Hare's os::open
|
||||||
// arena-copied paths usually are by construction). Returns -errno on
|
// (ref/hare/os/os.ha:117); kpath lands the bytes in pathbuf.
|
||||||
// failure, fd otherwise. Higher-level callers prefer `tryopen`.
|
// Returns -ENAMETOOLONG (-36) if the path overflows PATH_MAX.
|
||||||
export fn open(path: *u8, flags: flag, mode: i32) i32 = {
|
export fn open(path: str, flags: flag, mode: i32) i32 = {
|
||||||
return syscall3(nr.OPEN, path: i64, (flags as i32): i64, mode: i64): 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);
|
let fd: i32 = open(path, flags, mode);
|
||||||
if (fd < 0) { return fd: i64: oserror; };
|
if (fd < 0) { return fd: i64: oserror; };
|
||||||
return fd;
|
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
|
// access(2): returns 0 if the file is reachable, negative errno
|
||||||
// otherwise. mode is the bitset described in <unistd.h> (F_OK=0).
|
// otherwise. mode is the bitset described in <unistd.h> (F_OK=0).
|
||||||
export fn access(path: *u8, mode: i32) i32 = {
|
// Mirrors Hare's os::access (ref/hare/os/+linux/fs.ha:access).
|
||||||
return syscall2(nr.ACCESS, path: i64, mode: i64): i32;
|
// 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).
|
// remove — unlink(2). Mirrors Hare's os::remove
|
||||||
export fn remove(path: *u8) i32 = {
|
// (ref/hare/os/os.ha:12).
|
||||||
return syscall1(nr.UNLINK, path: i64): i32;
|
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
|
// mkdir — mkdir(2). Mode is the unix permission bitset (e.g. 0o700).
|
||||||
// permission bitset (e.g. 0o700). Returns 0 on success, negative
|
// Returns 0 on success, negative errno otherwise. Mirrors Hare's
|
||||||
// errno otherwise. Hare name (os::mkdir).
|
// os::mkdir (ref/hare/os/os.ha:50).
|
||||||
export fn mkdir(path: *u8, mode: i32) i32 = {
|
export fn mkdir(path: str, mode: i32) i32 = {
|
||||||
return syscall2(nr.MKDIR, path: i64, mode: i64): 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
|
// rmdir — rmdir(2). Mirrors Hare's os::rmdir
|
||||||
// success, negative errno otherwise. Hare name (os::rmdir).
|
// (ref/hare/os/os.ha:58).
|
||||||
export fn rmdir(path: *u8) i32 = {
|
export fn rmdir(path: str) i32 = {
|
||||||
return syscall1(nr.RMDIR, path: i64): 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
|
// 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);
|
// accepted (matches Hare's `errors::exists` skip in os::mkdirs);
|
||||||
// any other syscall failure surfaces as `oserror`.
|
// any other syscall failure surfaces as `oserror`.
|
||||||
//
|
//
|
||||||
// `path` must be NUL-terminated AND its bytes must be writable —
|
// Mirrors Hare's os::mkdirs (ref/hare/os/os.ha:54). The in-place
|
||||||
// mkdirs temporarily replaces '/' separators with NUL while
|
// '/' → NUL splice walks the kpath-loaded [[pathbuf]] directly
|
||||||
// invoking [[mkdir]] on each prefix, then restores them. Pointing
|
// instead of recursing through [[mkdir]] — re-entering kpath would
|
||||||
// `path` at a string literal will segfault. Callers hold the bytes
|
// clobber the buffer mid-walk (single static slot, see kpath's
|
||||||
// in a writable buffer (rt_alloc'd, a static `[N]u8`, etc.) — same
|
// non-reentrancy note above).
|
||||||
// precedent as [[temp.named]]'s pathbuf.
|
export fn mkdirs(path: str, mode: i32) (void | oserror) = {
|
||||||
//
|
let cp: *u8 = kpath(path);
|
||||||
// Mirrors Hare's os::mkdirs (recursive variant of os::mkdir).
|
if (cp == nil: *u8) { return -36i64: oserror; };
|
||||||
export fn mkdirs(path: *u8, mode: i32) (void | oserror) = {
|
let n: i32 = path.len;
|
||||||
// Find the path length (excluding trailing NUL).
|
|
||||||
let n: i32 = 0;
|
|
||||||
for (path[n] != 0u8) { n += 1; };
|
|
||||||
if (n == 0) { return; };
|
if (n == 0) { return; };
|
||||||
|
|
||||||
// Walk forward; at each '/' boundary, NUL-terminate the prefix,
|
// Walk forward; at each '/' boundary, NUL-terminate the prefix,
|
||||||
// mkdir it, restore the slash, continue. Skip index 0 so a
|
// raw MKDIR syscall on pathbuf, restore the slash, continue.
|
||||||
// leading '/' on absolute paths doesn't trigger an empty mkdir.
|
// Skip index 0 so a leading '/' on absolute paths doesn't
|
||||||
|
// trigger an empty mkdir.
|
||||||
let i: i32 = 1;
|
let i: i32 = 1;
|
||||||
for (i < n) {
|
for (i < n) {
|
||||||
if (path[i] == 47u8) { // '/'
|
if (pathbuf[i] == 47u8) { // '/'
|
||||||
path[i] = 0u8;
|
pathbuf[i] = 0u8;
|
||||||
let r: i32 = mkdir(path, mode);
|
let r: i32 = syscall2(nr.MKDIR,
|
||||||
path[i] = 47u8;
|
(&pathbuf[0]): i64, mode: i64): i32;
|
||||||
|
pathbuf[i] = 47u8;
|
||||||
if (r < 0) {
|
if (r < 0) {
|
||||||
if (r != -17) { return r: i64: oserror; };
|
if (r != -17) { return r: i64: oserror; };
|
||||||
};
|
};
|
||||||
@@ -251,8 +293,8 @@ export fn mkdirs(path: *u8, mode: i32) (void | oserror) = {
|
|||||||
i += 1;
|
i += 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
// mkdir the full path.
|
let r: i32 = syscall2(nr.MKDIR,
|
||||||
let r: i32 = mkdir(path, mode);
|
(&pathbuf[0]): i64, mode: i64): i32;
|
||||||
if (r < 0) {
|
if (r < 0) {
|
||||||
if (r != -17) { return r: i64: oserror; };
|
if (r != -17) { return r: i64: oserror; };
|
||||||
};
|
};
|
||||||
@@ -270,9 +312,14 @@ export fn fork() i32 = {
|
|||||||
return syscall0(nr.FORK): i32;
|
return syscall0(nr.FORK): i32;
|
||||||
};
|
};
|
||||||
|
|
||||||
// execve(2): on success, does not return.
|
// execve(2): on success, does not return. Mirrors Hare's
|
||||||
export fn execve(path: *u8, argv: **u8, envp: **u8) i32 = {
|
// os::exec::exec path arg (str). argv/envp stay `**u8` — the
|
||||||
return syscall3(nr.EXECVE, path: i64, argv: i64, envp: i64): i32;
|
// 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
|
// 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.
|
// stat — fill *out with metadata for `path`. Follows symlinks.
|
||||||
// `path` must be NUL-terminated (lib/os convention; see task #23
|
// Returns ENAMETOOLONG (-36) as `oserror` if the path overflows
|
||||||
// for a planned `path: str` migration).
|
// PATH_MAX.
|
||||||
//
|
//
|
||||||
// Mirrors Hare's sys::stat (ref/hare/sys/+linux/stat.ha:51) modulo
|
// 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
|
// 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
|
// higher-level fs::stat (ref/hare/fs/fs.ha:172) instead has lstat
|
||||||
// semantics — we follow sys::stat's POSIX-stat behavior here.
|
// 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 k: kstat;
|
||||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
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; };
|
if (r < 0) { return r: oserror; };
|
||||||
fillfilestat(out, &k);
|
fillfilestat(out, &k);
|
||||||
};
|
};
|
||||||
|
|
||||||
// lstat — like [[stat]] but does NOT follow a terminal symlink.
|
// lstat — like [[stat]] but does NOT follow a terminal symlink.
|
||||||
// Mirrors Hare's sys::lstat (ref/hare/sys/+linux/stat.ha:57).
|
// 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 k: kstat;
|
||||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
let r: i64 = syscall4(nr.NEWFSTATAT,
|
||||||
AT_FDCWD: i64, path: i64, (&k): i64,
|
AT_FDCWD: i64, cp: i64, (&k): i64,
|
||||||
AT_SYMLINK_NOFOLLOW: i64);
|
AT_SYMLINK_NOFOLLOW: i64);
|
||||||
if (r < 0) { return r: oserror; };
|
if (r < 0) { return r: oserror; };
|
||||||
fillfilestat(out, &k);
|
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,
|
// exists — true if `path` resolves to anything (regular file,
|
||||||
// directory, symlink, ...). Stat-shaped (Hare's `fs::exists`,
|
// directory, symlink, ...). Stat-shaped (Hare's `fs::exists`,
|
||||||
// ref/hare/fs/fs.ha:196) — no separate syscall. Symlinks are
|
// 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
|
// Race warning: prefer "open and handle the error" over "exists
|
||||||
// then open" in real code (Hare's docstring carries the same
|
// 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
|
// — same class as STATUS #22, surfaced first time a match on this
|
||||||
// shape combined with an 80B local-struct local frame). Use the
|
// shape combined with an 80B local-struct local frame). Use the
|
||||||
// match shape once #22 lands.
|
// 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 k: kstat;
|
||||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
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;
|
return r >= 0i64;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -906,7 +961,7 @@ def RELA_ADDEND: u64 = 16u64;
|
|||||||
// ---- file slurp --------------------------------------------------------
|
// ---- file slurp --------------------------------------------------------
|
||||||
|
|
||||||
fn slurp(path: *u8) (*u8, u64) = {
|
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; };
|
if (fd < 0) { return nil, 0u64; };
|
||||||
let szr: (i64 | os.oserror) = os.filesize(fd);
|
let szr: (i64 | os.oserror) = os.filesize(fd);
|
||||||
let n: i64 = 0i64;
|
let n: i64 = 0i64;
|
||||||
@@ -983,6 +1038,16 @@ fn cstrlen(p: *u8) u64 = {
|
|||||||
return n;
|
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 = {
|
fn cstreq(p: *u8, lit: str) bool = {
|
||||||
let n: u64 = lit.len: u64;
|
let n: u64 = lit.len: u64;
|
||||||
let i: u64 = 0u64;
|
let i: u64 = 0u64;
|
||||||
@@ -1556,7 +1621,7 @@ fn dbasename(p: *u8) *u8 = {
|
|||||||
// ---- file slurp --------------------------------------------------------
|
// ---- file slurp --------------------------------------------------------
|
||||||
|
|
||||||
fn slurpso(path: *u8) (*u8, u64) = {
|
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; };
|
if (fd < 0) { return nil, 0u64; };
|
||||||
let szr: (i64 | os.oserror) = os.filesize(fd);
|
let szr: (i64 | os.oserror) = os.filesize(fd);
|
||||||
let n: i64 = 0i64;
|
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.
|
// islinkable: read first 8 bytes; require !<arch>\n or \x7fELF.
|
||||||
fn islinkable(path: *u8) bool = {
|
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; };
|
if (fd < 0) { return false; };
|
||||||
let mp: *u8 = os.alloc(8u64): *u8;
|
let mp: *u8 = os.alloc(8u64): *u8;
|
||||||
let n: i64 = os.read(fd, mp, 8u64);
|
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.
|
// Read first 20 bytes; return 1 for ET_DYN .so, 0 for ar/.o.
|
||||||
fn isso(path: *u8) i32 = {
|
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; };
|
if (fd < 0) { return 0; };
|
||||||
let mp: *u8 = os.alloc(20u64): *u8;
|
let mp: *u8 = os.alloc(20u64): *u8;
|
||||||
let n: i64 = os.read(fd, mp, 20u64);
|
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 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) {
|
if (fd < 0) {
|
||||||
os.write(2, "w6l: cannot open output\n".ptr, 23u64);
|
os.write(2, "w6l: cannot open output\n".ptr, 23u64);
|
||||||
return 1;
|
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.
|
// islinkable: read first 8 bytes; require !<arch>\n or \x7fELF.
|
||||||
fn islinkable(path: *u8) bool = {
|
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; };
|
if (fd < 0) { return false; };
|
||||||
let mp: *u8 = os.alloc(8u64): *u8;
|
let mp: *u8 = os.alloc(8u64): *u8;
|
||||||
let n: i64 = os.read(fd, mp, 8u64);
|
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.
|
// Read first 20 bytes; return 1 for ET_DYN .so, 0 for ar/.o.
|
||||||
fn isso(path: *u8) i32 = {
|
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; };
|
if (fd < 0) { return 0; };
|
||||||
let mp: *u8 = os.alloc(20u64): *u8;
|
let mp: *u8 = os.alloc(20u64): *u8;
|
||||||
let n: i64 = os.read(fd, mp, 20u64);
|
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 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) {
|
if (fd < 0) {
|
||||||
os.write(2, "w6l: cannot open output\n".ptr, 23u64);
|
os.write(2, "w6l: cannot open output\n".ptr, 23u64);
|
||||||
return 1;
|
return 1;
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ def RELA_ADDEND: u64 = 16u64;
|
|||||||
// ---- file slurp --------------------------------------------------------
|
// ---- file slurp --------------------------------------------------------
|
||||||
|
|
||||||
fn slurp(path: *u8) (*u8, u64) = {
|
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; };
|
if (fd < 0) { return nil, 0u64; };
|
||||||
let szr: (i64 | os.oserror) = os.filesize(fd);
|
let szr: (i64 | os.oserror) = os.filesize(fd);
|
||||||
let n: i64 = 0i64;
|
let n: i64 = 0i64;
|
||||||
@@ -153,6 +153,16 @@ fn cstrlen(p: *u8) u64 = {
|
|||||||
return n;
|
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 = {
|
fn cstreq(p: *u8, lit: str) bool = {
|
||||||
let n: u64 = lit.len: u64;
|
let n: u64 = lit.len: u64;
|
||||||
let i: u64 = 0u64;
|
let i: u64 = 0u64;
|
||||||
|
|||||||
@@ -88,6 +88,36 @@ export fn exit(code: i32) void = {
|
|||||||
syscall1(nr.EXIT, code: i64);
|
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
|
// Raw, non-fallible primitives. These return Linux's int conventions
|
||||||
// (negative = -errno, non-negative = bytes/fd/etc). Callers wanting a
|
// (negative = -errno, non-negative = bytes/fd/etc). Callers wanting a
|
||||||
// Hare-style fallible API use the wrappers below.
|
// 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;
|
return r;
|
||||||
};
|
};
|
||||||
|
|
||||||
// open — Linux open(2). Path must be NUL-terminated; callers using ww
|
// open — Linux open(2). Returns -errno on failure, fd otherwise.
|
||||||
// `str` must ensure the bytes are followed by a 0 byte (literals are,
|
// Higher-level callers prefer `tryopen`. Mirrors Hare's os::open
|
||||||
// arena-copied paths usually are by construction). Returns -errno on
|
// (ref/hare/os/os.ha:117); kpath lands the bytes in pathbuf.
|
||||||
// failure, fd otherwise. Higher-level callers prefer `tryopen`.
|
// Returns -ENAMETOOLONG (-36) if the path overflows PATH_MAX.
|
||||||
export fn open(path: *u8, flags: flag, mode: i32) i32 = {
|
export fn open(path: str, flags: flag, mode: i32) i32 = {
|
||||||
return syscall3(nr.OPEN, path: i64, (flags as i32): i64, mode: i64): 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);
|
let fd: i32 = open(path, flags, mode);
|
||||||
if (fd < 0) { return fd: i64: oserror; };
|
if (fd < 0) { return fd: i64: oserror; };
|
||||||
return fd;
|
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
|
// access(2): returns 0 if the file is reachable, negative errno
|
||||||
// otherwise. mode is the bitset described in <unistd.h> (F_OK=0).
|
// otherwise. mode is the bitset described in <unistd.h> (F_OK=0).
|
||||||
export fn access(path: *u8, mode: i32) i32 = {
|
// Mirrors Hare's os::access (ref/hare/os/+linux/fs.ha:access).
|
||||||
return syscall2(nr.ACCESS, path: i64, mode: i64): i32;
|
// 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).
|
// remove — unlink(2). Mirrors Hare's os::remove
|
||||||
export fn remove(path: *u8) i32 = {
|
// (ref/hare/os/os.ha:12).
|
||||||
return syscall1(nr.UNLINK, path: i64): i32;
|
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
|
// mkdir — mkdir(2). Mode is the unix permission bitset (e.g. 0o700).
|
||||||
// permission bitset (e.g. 0o700). Returns 0 on success, negative
|
// Returns 0 on success, negative errno otherwise. Mirrors Hare's
|
||||||
// errno otherwise. Hare name (os::mkdir).
|
// os::mkdir (ref/hare/os/os.ha:50).
|
||||||
export fn mkdir(path: *u8, mode: i32) i32 = {
|
export fn mkdir(path: str, mode: i32) i32 = {
|
||||||
return syscall2(nr.MKDIR, path: i64, mode: i64): 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
|
// rmdir — rmdir(2). Mirrors Hare's os::rmdir
|
||||||
// success, negative errno otherwise. Hare name (os::rmdir).
|
// (ref/hare/os/os.ha:58).
|
||||||
export fn rmdir(path: *u8) i32 = {
|
export fn rmdir(path: str) i32 = {
|
||||||
return syscall1(nr.RMDIR, path: i64): 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
|
// 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);
|
// accepted (matches Hare's `errors::exists` skip in os::mkdirs);
|
||||||
// any other syscall failure surfaces as `oserror`.
|
// any other syscall failure surfaces as `oserror`.
|
||||||
//
|
//
|
||||||
// `path` must be NUL-terminated AND its bytes must be writable —
|
// Mirrors Hare's os::mkdirs (ref/hare/os/os.ha:54). The in-place
|
||||||
// mkdirs temporarily replaces '/' separators with NUL while
|
// '/' → NUL splice walks the kpath-loaded [[pathbuf]] directly
|
||||||
// invoking [[mkdir]] on each prefix, then restores them. Pointing
|
// instead of recursing through [[mkdir]] — re-entering kpath would
|
||||||
// `path` at a string literal will segfault. Callers hold the bytes
|
// clobber the buffer mid-walk (single static slot, see kpath's
|
||||||
// in a writable buffer (rt_alloc'd, a static `[N]u8`, etc.) — same
|
// non-reentrancy note above).
|
||||||
// precedent as [[temp.named]]'s pathbuf.
|
export fn mkdirs(path: str, mode: i32) (void | oserror) = {
|
||||||
//
|
let cp: *u8 = kpath(path);
|
||||||
// Mirrors Hare's os::mkdirs (recursive variant of os::mkdir).
|
if (cp == nil: *u8) { return -36i64: oserror; };
|
||||||
export fn mkdirs(path: *u8, mode: i32) (void | oserror) = {
|
let n: i32 = path.len;
|
||||||
// Find the path length (excluding trailing NUL).
|
|
||||||
let n: i32 = 0;
|
|
||||||
for (path[n] != 0u8) { n += 1; };
|
|
||||||
if (n == 0) { return; };
|
if (n == 0) { return; };
|
||||||
|
|
||||||
// Walk forward; at each '/' boundary, NUL-terminate the prefix,
|
// Walk forward; at each '/' boundary, NUL-terminate the prefix,
|
||||||
// mkdir it, restore the slash, continue. Skip index 0 so a
|
// raw MKDIR syscall on pathbuf, restore the slash, continue.
|
||||||
// leading '/' on absolute paths doesn't trigger an empty mkdir.
|
// Skip index 0 so a leading '/' on absolute paths doesn't
|
||||||
|
// trigger an empty mkdir.
|
||||||
let i: i32 = 1;
|
let i: i32 = 1;
|
||||||
for (i < n) {
|
for (i < n) {
|
||||||
if (path[i] == 47u8) { // '/'
|
if (pathbuf[i] == 47u8) { // '/'
|
||||||
path[i] = 0u8;
|
pathbuf[i] = 0u8;
|
||||||
let r: i32 = mkdir(path, mode);
|
let r: i32 = syscall2(nr.MKDIR,
|
||||||
path[i] = 47u8;
|
(&pathbuf[0]): i64, mode: i64): i32;
|
||||||
|
pathbuf[i] = 47u8;
|
||||||
if (r < 0) {
|
if (r < 0) {
|
||||||
if (r != -17) { return r: i64: oserror; };
|
if (r != -17) { return r: i64: oserror; };
|
||||||
};
|
};
|
||||||
@@ -251,8 +293,8 @@ export fn mkdirs(path: *u8, mode: i32) (void | oserror) = {
|
|||||||
i += 1;
|
i += 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
// mkdir the full path.
|
let r: i32 = syscall2(nr.MKDIR,
|
||||||
let r: i32 = mkdir(path, mode);
|
(&pathbuf[0]): i64, mode: i64): i32;
|
||||||
if (r < 0) {
|
if (r < 0) {
|
||||||
if (r != -17) { return r: i64: oserror; };
|
if (r != -17) { return r: i64: oserror; };
|
||||||
};
|
};
|
||||||
@@ -270,9 +312,14 @@ export fn fork() i32 = {
|
|||||||
return syscall0(nr.FORK): i32;
|
return syscall0(nr.FORK): i32;
|
||||||
};
|
};
|
||||||
|
|
||||||
// execve(2): on success, does not return.
|
// execve(2): on success, does not return. Mirrors Hare's
|
||||||
export fn execve(path: *u8, argv: **u8, envp: **u8) i32 = {
|
// os::exec::exec path arg (str). argv/envp stay `**u8` — the
|
||||||
return syscall3(nr.EXECVE, path: i64, argv: i64, envp: i64): i32;
|
// 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
|
// 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.
|
// stat — fill *out with metadata for `path`. Follows symlinks.
|
||||||
// `path` must be NUL-terminated (lib/os convention; see task #23
|
// Returns ENAMETOOLONG (-36) as `oserror` if the path overflows
|
||||||
// for a planned `path: str` migration).
|
// PATH_MAX.
|
||||||
//
|
//
|
||||||
// Mirrors Hare's sys::stat (ref/hare/sys/+linux/stat.ha:51) modulo
|
// 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
|
// 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
|
// higher-level fs::stat (ref/hare/fs/fs.ha:172) instead has lstat
|
||||||
// semantics — we follow sys::stat's POSIX-stat behavior here.
|
// 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 k: kstat;
|
||||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
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; };
|
if (r < 0) { return r: oserror; };
|
||||||
fillfilestat(out, &k);
|
fillfilestat(out, &k);
|
||||||
};
|
};
|
||||||
|
|
||||||
// lstat — like [[stat]] but does NOT follow a terminal symlink.
|
// lstat — like [[stat]] but does NOT follow a terminal symlink.
|
||||||
// Mirrors Hare's sys::lstat (ref/hare/sys/+linux/stat.ha:57).
|
// 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 k: kstat;
|
||||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
let r: i64 = syscall4(nr.NEWFSTATAT,
|
||||||
AT_FDCWD: i64, path: i64, (&k): i64,
|
AT_FDCWD: i64, cp: i64, (&k): i64,
|
||||||
AT_SYMLINK_NOFOLLOW: i64);
|
AT_SYMLINK_NOFOLLOW: i64);
|
||||||
if (r < 0) { return r: oserror; };
|
if (r < 0) { return r: oserror; };
|
||||||
fillfilestat(out, &k);
|
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,
|
// exists — true if `path` resolves to anything (regular file,
|
||||||
// directory, symlink, ...). Stat-shaped (Hare's `fs::exists`,
|
// directory, symlink, ...). Stat-shaped (Hare's `fs::exists`,
|
||||||
// ref/hare/fs/fs.ha:196) — no separate syscall. Symlinks are
|
// 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
|
// Race warning: prefer "open and handle the error" over "exists
|
||||||
// then open" in real code (Hare's docstring carries the same
|
// 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
|
// — same class as STATUS #22, surfaced first time a match on this
|
||||||
// shape combined with an 80B local-struct local frame). Use the
|
// shape combined with an 80B local-struct local frame). Use the
|
||||||
// match shape once #22 lands.
|
// 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 k: kstat;
|
||||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
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;
|
return r >= 0i64;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -744,6 +799,16 @@ fn cstrlen(p: *u8) u64 = {
|
|||||||
return n;
|
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 = {
|
fn cstreq(a: *u8, b: *u8) bool = {
|
||||||
let i: u64 = 0u64;
|
let i: u64 = 0u64;
|
||||||
for (a[i] == b[i]) {
|
for (a[i] == b[i]) {
|
||||||
@@ -873,7 +938,7 @@ fn procrun(path: *u8, argv: **u8) i32 = {
|
|||||||
return -1;
|
return -1;
|
||||||
};
|
};
|
||||||
if (pid == 0) {
|
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.write(2, "ww: execve failed\n".ptr, 18u64);
|
||||||
os.exit(127);
|
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] = 119u8; off += 1u64; // 'w'
|
buf[off] = 119u8; off += 1u64; // 'w'
|
||||||
buf[off] = 0u8;
|
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
|
// candidate 2: <dir>/<name>/<name>.ww
|
||||||
let buf2: *u8 = amalloc(a, PATH_MAX): *u8;
|
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] = 119u8; off += 1u64;
|
buf2[off] = 119u8; off += 1u64;
|
||||||
buf2[off] = 0u8;
|
buf2[off] = 0u8;
|
||||||
if (os.access(buf2, 0i32) == 0) { return buf2; };
|
if (os.access(pathstr(buf2), 0i32) == 0) { return buf2; };
|
||||||
return nil;
|
return nil;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -998,7 +1063,7 @@ fn locateimport(a: *arena, dirs: *u8, name: *u8, namelen: u64) *u8 = {
|
|||||||
// ---- file slurp -------------------------------------------------------
|
// ---- file slurp -------------------------------------------------------
|
||||||
|
|
||||||
fn slurp(pathcs: *u8) (*u8, u64) = {
|
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; };
|
if (fd < 0) { return nil, 0u64; };
|
||||||
let szr: (i64 | os.oserror) = os.filesize(fd);
|
let szr: (i64 | os.oserror) = os.filesize(fd);
|
||||||
let n: i64 = 0i64;
|
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.
|
// 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) {
|
if (cf < 0) {
|
||||||
os.write(2, "ww: cannot open combined\n".ptr, 25u64);
|
os.write(2, "ww: cannot open combined\n".ptr, 25u64);
|
||||||
return 1;
|
return 1;
|
||||||
@@ -1461,7 +1526,7 @@ fn resolvemodule(a: *arena, selfdir: *u8, name: *u8, incs: *u8) *u8 = {
|
|||||||
|
|
||||||
// (1) literal .ww file that exists
|
// (1) literal .ww file that exists
|
||||||
if (cstrendswithlit(name, ".ww")) {
|
if (cstrendswithlit(name, ".ww")) {
|
||||||
if (os.access(name, 0i32) == 0) {
|
if (os.access(pathstr(name), 0i32) == 0) {
|
||||||
return arenadupcstr(a, name, nlen);
|
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;
|
dot[0] = 46u8; dot[1] = 0u8;
|
||||||
let probe: *u8 = builddirmodulepath(a, dot, 1u64,
|
let probe: *u8 = builddirmodulepath(a, dot, 1u64,
|
||||||
cwd + bo, blen);
|
cwd + bo, blen);
|
||||||
if (os.access(probe, 0i32) == 0) { return probe; };
|
if (os.access(pathstr(probe), 0i32) == 0) { return probe; };
|
||||||
return nil;
|
return nil;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -1488,7 +1553,7 @@ fn resolvemodule(a: *arena, selfdir: *u8, name: *u8, incs: *u8) *u8 = {
|
|||||||
let bo: u64 = basenameoff(name, nlen);
|
let bo: u64 = basenameoff(name, nlen);
|
||||||
let probe: *u8 = builddirmodulepath(a, name, nlen,
|
let probe: *u8 = builddirmodulepath(a, name, nlen,
|
||||||
name + bo, nlen - bo);
|
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
|
// (4) search path lookup
|
||||||
let search: *u8 = buildsearchpath(a, selfdir, incs);
|
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.libs = libs;
|
||||||
lf.nlibs = nlibs;
|
lf.nlibs = nlibs;
|
||||||
if (buildone(selfdir, resolved, tmp, incs, &lf) != 0) {
|
if (buildone(selfdir, resolved, tmp, incs, &lf) != 0) {
|
||||||
os.remove(tmp);
|
os.remove(pathstr(tmp));
|
||||||
return 1;
|
return 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1796,7 +1861,7 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
|||||||
};
|
};
|
||||||
execargv[nextra + 1] = nil;
|
execargv[nextra + 1] = nil;
|
||||||
let rc: i32 = procrun(tmp, execargv);
|
let rc: i32 = procrun(tmp, execargv);
|
||||||
os.remove(tmp);
|
os.remove(pathstr(tmp));
|
||||||
return rc;
|
return rc;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1811,19 +1876,19 @@ fn runsingletest(selfdir: *u8, src: *u8) i32 = {
|
|||||||
let tmp: *u8 = os.alloc(PATH_MAX): *u8;
|
let tmp: *u8 = os.alloc(PATH_MAX): *u8;
|
||||||
makeruntmp(tmp);
|
makeruntmp(tmp);
|
||||||
if (buildone(selfdir, src, tmp, "\0".ptr, nil) != 0) {
|
if (buildone(selfdir, src, tmp, "\0".ptr, nil) != 0) {
|
||||||
os.remove(tmp);
|
os.remove(pathstr(tmp));
|
||||||
return 1;
|
return 1;
|
||||||
};
|
};
|
||||||
let execargv: **u8 = os.alloc(16u64): **u8;
|
let execargv: **u8 = os.alloc(16u64): **u8;
|
||||||
execargv[0] = tmp;
|
execargv[0] = tmp;
|
||||||
execargv[1] = nil;
|
execargv[1] = nil;
|
||||||
let rc: i32 = procrun(tmp, execargv);
|
let rc: i32 = procrun(tmp, execargv);
|
||||||
os.remove(tmp);
|
os.remove(pathstr(tmp));
|
||||||
return rc;
|
return rc;
|
||||||
};
|
};
|
||||||
|
|
||||||
fn rundirtests(selfdir: *u8, dir: *u8) i32 = {
|
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) {
|
if (fd < 0) {
|
||||||
os.write(2, "ww test: cannot open directory\n".ptr, 31u64);
|
os.write(2, "ww test: cannot open directory\n".ptr, 31u64);
|
||||||
return 1;
|
return 1;
|
||||||
@@ -1883,7 +1948,7 @@ fn rundirtests(selfdir: *u8, dir: *u8) i32 = {
|
|||||||
os.write(2, "\n".ptr, 1u64);
|
os.write(2, "\n".ptr, 1u64);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
os.remove(tmp);
|
os.remove(pathstr(tmp));
|
||||||
};
|
};
|
||||||
off += reclen;
|
off += reclen;
|
||||||
};
|
};
|
||||||
@@ -1907,7 +1972,7 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
|||||||
|
|
||||||
// single-file mode: literal *.ww that exists
|
// single-file mode: literal *.ww that exists
|
||||||
if (cstrendswithlit(target, ".ww")) {
|
if (cstrendswithlit(target, ".ww")) {
|
||||||
if (os.access(target, 0i32) == 0) {
|
if (os.access(pathstr(target), 0i32) == 0) {
|
||||||
return runsingletest(selfdir, target);
|
return runsingletest(selfdir, target);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -27,6 +27,16 @@ fn cstrlen(p: *u8) u64 = {
|
|||||||
return n;
|
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 = {
|
fn cstreq(a: *u8, b: *u8) bool = {
|
||||||
let i: u64 = 0u64;
|
let i: u64 = 0u64;
|
||||||
for (a[i] == b[i]) {
|
for (a[i] == b[i]) {
|
||||||
@@ -156,7 +166,7 @@ fn procrun(path: *u8, argv: **u8) i32 = {
|
|||||||
return -1;
|
return -1;
|
||||||
};
|
};
|
||||||
if (pid == 0) {
|
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.write(2, "ww: execve failed\n".ptr, 18u64);
|
||||||
os.exit(127);
|
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] = 119u8; off += 1u64; // 'w'
|
buf[off] = 119u8; off += 1u64; // 'w'
|
||||||
buf[off] = 0u8;
|
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
|
// candidate 2: <dir>/<name>/<name>.ww
|
||||||
let buf2: *u8 = amalloc(a, PATH_MAX): *u8;
|
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] = 119u8; off += 1u64;
|
buf2[off] = 119u8; off += 1u64;
|
||||||
buf2[off] = 0u8;
|
buf2[off] = 0u8;
|
||||||
if (os.access(buf2, 0i32) == 0) { return buf2; };
|
if (os.access(pathstr(buf2), 0i32) == 0) { return buf2; };
|
||||||
return nil;
|
return nil;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -281,7 +291,7 @@ fn locateimport(a: *arena, dirs: *u8, name: *u8, namelen: u64) *u8 = {
|
|||||||
// ---- file slurp -------------------------------------------------------
|
// ---- file slurp -------------------------------------------------------
|
||||||
|
|
||||||
fn slurp(pathcs: *u8) (*u8, u64) = {
|
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; };
|
if (fd < 0) { return nil, 0u64; };
|
||||||
let szr: (i64 | os.oserror) = os.filesize(fd);
|
let szr: (i64 | os.oserror) = os.filesize(fd);
|
||||||
let n: i64 = 0i64;
|
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.
|
// 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) {
|
if (cf < 0) {
|
||||||
os.write(2, "ww: cannot open combined\n".ptr, 25u64);
|
os.write(2, "ww: cannot open combined\n".ptr, 25u64);
|
||||||
return 1;
|
return 1;
|
||||||
@@ -744,7 +754,7 @@ fn resolvemodule(a: *arena, selfdir: *u8, name: *u8, incs: *u8) *u8 = {
|
|||||||
|
|
||||||
// (1) literal .ww file that exists
|
// (1) literal .ww file that exists
|
||||||
if (cstrendswithlit(name, ".ww")) {
|
if (cstrendswithlit(name, ".ww")) {
|
||||||
if (os.access(name, 0i32) == 0) {
|
if (os.access(pathstr(name), 0i32) == 0) {
|
||||||
return arenadupcstr(a, name, nlen);
|
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;
|
dot[0] = 46u8; dot[1] = 0u8;
|
||||||
let probe: *u8 = builddirmodulepath(a, dot, 1u64,
|
let probe: *u8 = builddirmodulepath(a, dot, 1u64,
|
||||||
cwd + bo, blen);
|
cwd + bo, blen);
|
||||||
if (os.access(probe, 0i32) == 0) { return probe; };
|
if (os.access(pathstr(probe), 0i32) == 0) { return probe; };
|
||||||
return nil;
|
return nil;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -771,7 +781,7 @@ fn resolvemodule(a: *arena, selfdir: *u8, name: *u8, incs: *u8) *u8 = {
|
|||||||
let bo: u64 = basenameoff(name, nlen);
|
let bo: u64 = basenameoff(name, nlen);
|
||||||
let probe: *u8 = builddirmodulepath(a, name, nlen,
|
let probe: *u8 = builddirmodulepath(a, name, nlen,
|
||||||
name + bo, nlen - bo);
|
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
|
// (4) search path lookup
|
||||||
let search: *u8 = buildsearchpath(a, selfdir, incs);
|
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.libs = libs;
|
||||||
lf.nlibs = nlibs;
|
lf.nlibs = nlibs;
|
||||||
if (buildone(selfdir, resolved, tmp, incs, &lf) != 0) {
|
if (buildone(selfdir, resolved, tmp, incs, &lf) != 0) {
|
||||||
os.remove(tmp);
|
os.remove(pathstr(tmp));
|
||||||
return 1;
|
return 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1079,7 +1089,7 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
|||||||
};
|
};
|
||||||
execargv[nextra + 1] = nil;
|
execargv[nextra + 1] = nil;
|
||||||
let rc: i32 = procrun(tmp, execargv);
|
let rc: i32 = procrun(tmp, execargv);
|
||||||
os.remove(tmp);
|
os.remove(pathstr(tmp));
|
||||||
return rc;
|
return rc;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1094,19 +1104,19 @@ fn runsingletest(selfdir: *u8, src: *u8) i32 = {
|
|||||||
let tmp: *u8 = os.alloc(PATH_MAX): *u8;
|
let tmp: *u8 = os.alloc(PATH_MAX): *u8;
|
||||||
makeruntmp(tmp);
|
makeruntmp(tmp);
|
||||||
if (buildone(selfdir, src, tmp, "\0".ptr, nil) != 0) {
|
if (buildone(selfdir, src, tmp, "\0".ptr, nil) != 0) {
|
||||||
os.remove(tmp);
|
os.remove(pathstr(tmp));
|
||||||
return 1;
|
return 1;
|
||||||
};
|
};
|
||||||
let execargv: **u8 = os.alloc(16u64): **u8;
|
let execargv: **u8 = os.alloc(16u64): **u8;
|
||||||
execargv[0] = tmp;
|
execargv[0] = tmp;
|
||||||
execargv[1] = nil;
|
execargv[1] = nil;
|
||||||
let rc: i32 = procrun(tmp, execargv);
|
let rc: i32 = procrun(tmp, execargv);
|
||||||
os.remove(tmp);
|
os.remove(pathstr(tmp));
|
||||||
return rc;
|
return rc;
|
||||||
};
|
};
|
||||||
|
|
||||||
fn rundirtests(selfdir: *u8, dir: *u8) i32 = {
|
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) {
|
if (fd < 0) {
|
||||||
os.write(2, "ww test: cannot open directory\n".ptr, 31u64);
|
os.write(2, "ww test: cannot open directory\n".ptr, 31u64);
|
||||||
return 1;
|
return 1;
|
||||||
@@ -1166,7 +1176,7 @@ fn rundirtests(selfdir: *u8, dir: *u8) i32 = {
|
|||||||
os.write(2, "\n".ptr, 1u64);
|
os.write(2, "\n".ptr, 1u64);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
os.remove(tmp);
|
os.remove(pathstr(tmp));
|
||||||
};
|
};
|
||||||
off += reclen;
|
off += reclen;
|
||||||
};
|
};
|
||||||
@@ -1190,7 +1200,7 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
|||||||
|
|
||||||
// single-file mode: literal *.ww that exists
|
// single-file mode: literal *.ww that exists
|
||||||
if (cstrendswithlit(target, ".ww")) {
|
if (cstrendswithlit(target, ".ww")) {
|
||||||
if (os.access(target, 0i32) == 0) {
|
if (os.access(pathstr(target), 0i32) == 0) {
|
||||||
return runsingletest(selfdir, target);
|
return runsingletest(selfdir, target);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -88,6 +88,36 @@ export fn exit(code: i32) void = {
|
|||||||
syscall1(nr.EXIT, code: i64);
|
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
|
// Raw, non-fallible primitives. These return Linux's int conventions
|
||||||
// (negative = -errno, non-negative = bytes/fd/etc). Callers wanting a
|
// (negative = -errno, non-negative = bytes/fd/etc). Callers wanting a
|
||||||
// Hare-style fallible API use the wrappers below.
|
// 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;
|
return r;
|
||||||
};
|
};
|
||||||
|
|
||||||
// open — Linux open(2). Path must be NUL-terminated; callers using ww
|
// open — Linux open(2). Returns -errno on failure, fd otherwise.
|
||||||
// `str` must ensure the bytes are followed by a 0 byte (literals are,
|
// Higher-level callers prefer `tryopen`. Mirrors Hare's os::open
|
||||||
// arena-copied paths usually are by construction). Returns -errno on
|
// (ref/hare/os/os.ha:117); kpath lands the bytes in pathbuf.
|
||||||
// failure, fd otherwise. Higher-level callers prefer `tryopen`.
|
// Returns -ENAMETOOLONG (-36) if the path overflows PATH_MAX.
|
||||||
export fn open(path: *u8, flags: flag, mode: i32) i32 = {
|
export fn open(path: str, flags: flag, mode: i32) i32 = {
|
||||||
return syscall3(nr.OPEN, path: i64, (flags as i32): i64, mode: i64): 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);
|
let fd: i32 = open(path, flags, mode);
|
||||||
if (fd < 0) { return fd: i64: oserror; };
|
if (fd < 0) { return fd: i64: oserror; };
|
||||||
return fd;
|
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
|
// access(2): returns 0 if the file is reachable, negative errno
|
||||||
// otherwise. mode is the bitset described in <unistd.h> (F_OK=0).
|
// otherwise. mode is the bitset described in <unistd.h> (F_OK=0).
|
||||||
export fn access(path: *u8, mode: i32) i32 = {
|
// Mirrors Hare's os::access (ref/hare/os/+linux/fs.ha:access).
|
||||||
return syscall2(nr.ACCESS, path: i64, mode: i64): i32;
|
// 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).
|
// remove — unlink(2). Mirrors Hare's os::remove
|
||||||
export fn remove(path: *u8) i32 = {
|
// (ref/hare/os/os.ha:12).
|
||||||
return syscall1(nr.UNLINK, path: i64): i32;
|
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
|
// mkdir — mkdir(2). Mode is the unix permission bitset (e.g. 0o700).
|
||||||
// permission bitset (e.g. 0o700). Returns 0 on success, negative
|
// Returns 0 on success, negative errno otherwise. Mirrors Hare's
|
||||||
// errno otherwise. Hare name (os::mkdir).
|
// os::mkdir (ref/hare/os/os.ha:50).
|
||||||
export fn mkdir(path: *u8, mode: i32) i32 = {
|
export fn mkdir(path: str, mode: i32) i32 = {
|
||||||
return syscall2(nr.MKDIR, path: i64, mode: i64): 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
|
// rmdir — rmdir(2). Mirrors Hare's os::rmdir
|
||||||
// success, negative errno otherwise. Hare name (os::rmdir).
|
// (ref/hare/os/os.ha:58).
|
||||||
export fn rmdir(path: *u8) i32 = {
|
export fn rmdir(path: str) i32 = {
|
||||||
return syscall1(nr.RMDIR, path: i64): 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
|
// 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);
|
// accepted (matches Hare's `errors::exists` skip in os::mkdirs);
|
||||||
// any other syscall failure surfaces as `oserror`.
|
// any other syscall failure surfaces as `oserror`.
|
||||||
//
|
//
|
||||||
// `path` must be NUL-terminated AND its bytes must be writable —
|
// Mirrors Hare's os::mkdirs (ref/hare/os/os.ha:54). The in-place
|
||||||
// mkdirs temporarily replaces '/' separators with NUL while
|
// '/' → NUL splice walks the kpath-loaded [[pathbuf]] directly
|
||||||
// invoking [[mkdir]] on each prefix, then restores them. Pointing
|
// instead of recursing through [[mkdir]] — re-entering kpath would
|
||||||
// `path` at a string literal will segfault. Callers hold the bytes
|
// clobber the buffer mid-walk (single static slot, see kpath's
|
||||||
// in a writable buffer (rt_alloc'd, a static `[N]u8`, etc.) — same
|
// non-reentrancy note above).
|
||||||
// precedent as [[temp.named]]'s pathbuf.
|
export fn mkdirs(path: str, mode: i32) (void | oserror) = {
|
||||||
//
|
let cp: *u8 = kpath(path);
|
||||||
// Mirrors Hare's os::mkdirs (recursive variant of os::mkdir).
|
if (cp == nil: *u8) { return -36i64: oserror; };
|
||||||
export fn mkdirs(path: *u8, mode: i32) (void | oserror) = {
|
let n: i32 = path.len;
|
||||||
// Find the path length (excluding trailing NUL).
|
|
||||||
let n: i32 = 0;
|
|
||||||
for (path[n] != 0u8) { n += 1; };
|
|
||||||
if (n == 0) { return; };
|
if (n == 0) { return; };
|
||||||
|
|
||||||
// Walk forward; at each '/' boundary, NUL-terminate the prefix,
|
// Walk forward; at each '/' boundary, NUL-terminate the prefix,
|
||||||
// mkdir it, restore the slash, continue. Skip index 0 so a
|
// raw MKDIR syscall on pathbuf, restore the slash, continue.
|
||||||
// leading '/' on absolute paths doesn't trigger an empty mkdir.
|
// Skip index 0 so a leading '/' on absolute paths doesn't
|
||||||
|
// trigger an empty mkdir.
|
||||||
let i: i32 = 1;
|
let i: i32 = 1;
|
||||||
for (i < n) {
|
for (i < n) {
|
||||||
if (path[i] == 47u8) { // '/'
|
if (pathbuf[i] == 47u8) { // '/'
|
||||||
path[i] = 0u8;
|
pathbuf[i] = 0u8;
|
||||||
let r: i32 = mkdir(path, mode);
|
let r: i32 = syscall2(nr.MKDIR,
|
||||||
path[i] = 47u8;
|
(&pathbuf[0]): i64, mode: i64): i32;
|
||||||
|
pathbuf[i] = 47u8;
|
||||||
if (r < 0) {
|
if (r < 0) {
|
||||||
if (r != -17) { return r: i64: oserror; };
|
if (r != -17) { return r: i64: oserror; };
|
||||||
};
|
};
|
||||||
@@ -251,8 +293,8 @@ export fn mkdirs(path: *u8, mode: i32) (void | oserror) = {
|
|||||||
i += 1;
|
i += 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
// mkdir the full path.
|
let r: i32 = syscall2(nr.MKDIR,
|
||||||
let r: i32 = mkdir(path, mode);
|
(&pathbuf[0]): i64, mode: i64): i32;
|
||||||
if (r < 0) {
|
if (r < 0) {
|
||||||
if (r != -17) { return r: i64: oserror; };
|
if (r != -17) { return r: i64: oserror; };
|
||||||
};
|
};
|
||||||
@@ -270,9 +312,14 @@ export fn fork() i32 = {
|
|||||||
return syscall0(nr.FORK): i32;
|
return syscall0(nr.FORK): i32;
|
||||||
};
|
};
|
||||||
|
|
||||||
// execve(2): on success, does not return.
|
// execve(2): on success, does not return. Mirrors Hare's
|
||||||
export fn execve(path: *u8, argv: **u8, envp: **u8) i32 = {
|
// os::exec::exec path arg (str). argv/envp stay `**u8` — the
|
||||||
return syscall3(nr.EXECVE, path: i64, argv: i64, envp: i64): i32;
|
// 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
|
// 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.
|
// stat — fill *out with metadata for `path`. Follows symlinks.
|
||||||
// `path` must be NUL-terminated (lib/os convention; see task #23
|
// Returns ENAMETOOLONG (-36) as `oserror` if the path overflows
|
||||||
// for a planned `path: str` migration).
|
// PATH_MAX.
|
||||||
//
|
//
|
||||||
// Mirrors Hare's sys::stat (ref/hare/sys/+linux/stat.ha:51) modulo
|
// 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
|
// 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
|
// higher-level fs::stat (ref/hare/fs/fs.ha:172) instead has lstat
|
||||||
// semantics — we follow sys::stat's POSIX-stat behavior here.
|
// 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 k: kstat;
|
||||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
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; };
|
if (r < 0) { return r: oserror; };
|
||||||
fillfilestat(out, &k);
|
fillfilestat(out, &k);
|
||||||
};
|
};
|
||||||
|
|
||||||
// lstat — like [[stat]] but does NOT follow a terminal symlink.
|
// lstat — like [[stat]] but does NOT follow a terminal symlink.
|
||||||
// Mirrors Hare's sys::lstat (ref/hare/sys/+linux/stat.ha:57).
|
// 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 k: kstat;
|
||||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
let r: i64 = syscall4(nr.NEWFSTATAT,
|
||||||
AT_FDCWD: i64, path: i64, (&k): i64,
|
AT_FDCWD: i64, cp: i64, (&k): i64,
|
||||||
AT_SYMLINK_NOFOLLOW: i64);
|
AT_SYMLINK_NOFOLLOW: i64);
|
||||||
if (r < 0) { return r: oserror; };
|
if (r < 0) { return r: oserror; };
|
||||||
fillfilestat(out, &k);
|
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,
|
// exists — true if `path` resolves to anything (regular file,
|
||||||
// directory, symlink, ...). Stat-shaped (Hare's `fs::exists`,
|
// directory, symlink, ...). Stat-shaped (Hare's `fs::exists`,
|
||||||
// ref/hare/fs/fs.ha:196) — no separate syscall. Symlinks are
|
// 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
|
// Race warning: prefer "open and handle the error" over "exists
|
||||||
// then open" in real code (Hare's docstring carries the same
|
// 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
|
// — same class as STATUS #22, surfaced first time a match on this
|
||||||
// shape combined with an 80B local-struct local frame). Use the
|
// shape combined with an 80B local-struct local frame). Use the
|
||||||
// match shape once #22 lands.
|
// 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 k: kstat;
|
||||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
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;
|
return r >= 0i64;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -19164,7 +19219,7 @@ export fn main(argc: i32, argv: **u8) i32 = {
|
|||||||
return 2;
|
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;
|
let fd: i32 = -1;
|
||||||
match (fdorerr) {
|
match (fdorerr) {
|
||||||
case let v: i32 => fd = v;
|
case let v: i32 => fd = v;
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ export fn main(argc: i32, argv: **u8) i32 = {
|
|||||||
return 2;
|
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;
|
let fd: i32 = -1;
|
||||||
match (fdorerr) {
|
match (fdorerr) {
|
||||||
case let v: i32 => fd = v;
|
case let v: i32 => fd = v;
|
||||||
|
|||||||
@@ -88,6 +88,36 @@ export fn exit(code: i32) void = {
|
|||||||
syscall1(nr.EXIT, code: i64);
|
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
|
// Raw, non-fallible primitives. These return Linux's int conventions
|
||||||
// (negative = -errno, non-negative = bytes/fd/etc). Callers wanting a
|
// (negative = -errno, non-negative = bytes/fd/etc). Callers wanting a
|
||||||
// Hare-style fallible API use the wrappers below.
|
// 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;
|
return r;
|
||||||
};
|
};
|
||||||
|
|
||||||
// open — Linux open(2). Path must be NUL-terminated; callers using ww
|
// open — Linux open(2). Returns -errno on failure, fd otherwise.
|
||||||
// `str` must ensure the bytes are followed by a 0 byte (literals are,
|
// Higher-level callers prefer `tryopen`. Mirrors Hare's os::open
|
||||||
// arena-copied paths usually are by construction). Returns -errno on
|
// (ref/hare/os/os.ha:117); kpath lands the bytes in pathbuf.
|
||||||
// failure, fd otherwise. Higher-level callers prefer `tryopen`.
|
// Returns -ENAMETOOLONG (-36) if the path overflows PATH_MAX.
|
||||||
export fn open(path: *u8, flags: flag, mode: i32) i32 = {
|
export fn open(path: str, flags: flag, mode: i32) i32 = {
|
||||||
return syscall3(nr.OPEN, path: i64, (flags as i32): i64, mode: i64): 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);
|
let fd: i32 = open(path, flags, mode);
|
||||||
if (fd < 0) { return fd: i64: oserror; };
|
if (fd < 0) { return fd: i64: oserror; };
|
||||||
return fd;
|
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
|
// access(2): returns 0 if the file is reachable, negative errno
|
||||||
// otherwise. mode is the bitset described in <unistd.h> (F_OK=0).
|
// otherwise. mode is the bitset described in <unistd.h> (F_OK=0).
|
||||||
export fn access(path: *u8, mode: i32) i32 = {
|
// Mirrors Hare's os::access (ref/hare/os/+linux/fs.ha:access).
|
||||||
return syscall2(nr.ACCESS, path: i64, mode: i64): i32;
|
// 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).
|
// remove — unlink(2). Mirrors Hare's os::remove
|
||||||
export fn remove(path: *u8) i32 = {
|
// (ref/hare/os/os.ha:12).
|
||||||
return syscall1(nr.UNLINK, path: i64): i32;
|
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
|
// mkdir — mkdir(2). Mode is the unix permission bitset (e.g. 0o700).
|
||||||
// permission bitset (e.g. 0o700). Returns 0 on success, negative
|
// Returns 0 on success, negative errno otherwise. Mirrors Hare's
|
||||||
// errno otherwise. Hare name (os::mkdir).
|
// os::mkdir (ref/hare/os/os.ha:50).
|
||||||
export fn mkdir(path: *u8, mode: i32) i32 = {
|
export fn mkdir(path: str, mode: i32) i32 = {
|
||||||
return syscall2(nr.MKDIR, path: i64, mode: i64): 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
|
// rmdir — rmdir(2). Mirrors Hare's os::rmdir
|
||||||
// success, negative errno otherwise. Hare name (os::rmdir).
|
// (ref/hare/os/os.ha:58).
|
||||||
export fn rmdir(path: *u8) i32 = {
|
export fn rmdir(path: str) i32 = {
|
||||||
return syscall1(nr.RMDIR, path: i64): 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
|
// 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);
|
// accepted (matches Hare's `errors::exists` skip in os::mkdirs);
|
||||||
// any other syscall failure surfaces as `oserror`.
|
// any other syscall failure surfaces as `oserror`.
|
||||||
//
|
//
|
||||||
// `path` must be NUL-terminated AND its bytes must be writable —
|
// Mirrors Hare's os::mkdirs (ref/hare/os/os.ha:54). The in-place
|
||||||
// mkdirs temporarily replaces '/' separators with NUL while
|
// '/' → NUL splice walks the kpath-loaded [[pathbuf]] directly
|
||||||
// invoking [[mkdir]] on each prefix, then restores them. Pointing
|
// instead of recursing through [[mkdir]] — re-entering kpath would
|
||||||
// `path` at a string literal will segfault. Callers hold the bytes
|
// clobber the buffer mid-walk (single static slot, see kpath's
|
||||||
// in a writable buffer (rt_alloc'd, a static `[N]u8`, etc.) — same
|
// non-reentrancy note above).
|
||||||
// precedent as [[temp.named]]'s pathbuf.
|
export fn mkdirs(path: str, mode: i32) (void | oserror) = {
|
||||||
//
|
let cp: *u8 = kpath(path);
|
||||||
// Mirrors Hare's os::mkdirs (recursive variant of os::mkdir).
|
if (cp == nil: *u8) { return -36i64: oserror; };
|
||||||
export fn mkdirs(path: *u8, mode: i32) (void | oserror) = {
|
let n: i32 = path.len;
|
||||||
// Find the path length (excluding trailing NUL).
|
|
||||||
let n: i32 = 0;
|
|
||||||
for (path[n] != 0u8) { n += 1; };
|
|
||||||
if (n == 0) { return; };
|
if (n == 0) { return; };
|
||||||
|
|
||||||
// Walk forward; at each '/' boundary, NUL-terminate the prefix,
|
// Walk forward; at each '/' boundary, NUL-terminate the prefix,
|
||||||
// mkdir it, restore the slash, continue. Skip index 0 so a
|
// raw MKDIR syscall on pathbuf, restore the slash, continue.
|
||||||
// leading '/' on absolute paths doesn't trigger an empty mkdir.
|
// Skip index 0 so a leading '/' on absolute paths doesn't
|
||||||
|
// trigger an empty mkdir.
|
||||||
let i: i32 = 1;
|
let i: i32 = 1;
|
||||||
for (i < n) {
|
for (i < n) {
|
||||||
if (path[i] == 47u8) { // '/'
|
if (pathbuf[i] == 47u8) { // '/'
|
||||||
path[i] = 0u8;
|
pathbuf[i] = 0u8;
|
||||||
let r: i32 = mkdir(path, mode);
|
let r: i32 = syscall2(nr.MKDIR,
|
||||||
path[i] = 47u8;
|
(&pathbuf[0]): i64, mode: i64): i32;
|
||||||
|
pathbuf[i] = 47u8;
|
||||||
if (r < 0) {
|
if (r < 0) {
|
||||||
if (r != -17) { return r: i64: oserror; };
|
if (r != -17) { return r: i64: oserror; };
|
||||||
};
|
};
|
||||||
@@ -251,8 +293,8 @@ export fn mkdirs(path: *u8, mode: i32) (void | oserror) = {
|
|||||||
i += 1;
|
i += 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
// mkdir the full path.
|
let r: i32 = syscall2(nr.MKDIR,
|
||||||
let r: i32 = mkdir(path, mode);
|
(&pathbuf[0]): i64, mode: i64): i32;
|
||||||
if (r < 0) {
|
if (r < 0) {
|
||||||
if (r != -17) { return r: i64: oserror; };
|
if (r != -17) { return r: i64: oserror; };
|
||||||
};
|
};
|
||||||
@@ -270,9 +312,14 @@ export fn fork() i32 = {
|
|||||||
return syscall0(nr.FORK): i32;
|
return syscall0(nr.FORK): i32;
|
||||||
};
|
};
|
||||||
|
|
||||||
// execve(2): on success, does not return.
|
// execve(2): on success, does not return. Mirrors Hare's
|
||||||
export fn execve(path: *u8, argv: **u8, envp: **u8) i32 = {
|
// os::exec::exec path arg (str). argv/envp stay `**u8` — the
|
||||||
return syscall3(nr.EXECVE, path: i64, argv: i64, envp: i64): i32;
|
// 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
|
// 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.
|
// stat — fill *out with metadata for `path`. Follows symlinks.
|
||||||
// `path` must be NUL-terminated (lib/os convention; see task #23
|
// Returns ENAMETOOLONG (-36) as `oserror` if the path overflows
|
||||||
// for a planned `path: str` migration).
|
// PATH_MAX.
|
||||||
//
|
//
|
||||||
// Mirrors Hare's sys::stat (ref/hare/sys/+linux/stat.ha:51) modulo
|
// 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
|
// 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
|
// higher-level fs::stat (ref/hare/fs/fs.ha:172) instead has lstat
|
||||||
// semantics — we follow sys::stat's POSIX-stat behavior here.
|
// 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 k: kstat;
|
||||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
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; };
|
if (r < 0) { return r: oserror; };
|
||||||
fillfilestat(out, &k);
|
fillfilestat(out, &k);
|
||||||
};
|
};
|
||||||
|
|
||||||
// lstat — like [[stat]] but does NOT follow a terminal symlink.
|
// lstat — like [[stat]] but does NOT follow a terminal symlink.
|
||||||
// Mirrors Hare's sys::lstat (ref/hare/sys/+linux/stat.ha:57).
|
// 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 k: kstat;
|
||||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
let r: i64 = syscall4(nr.NEWFSTATAT,
|
||||||
AT_FDCWD: i64, path: i64, (&k): i64,
|
AT_FDCWD: i64, cp: i64, (&k): i64,
|
||||||
AT_SYMLINK_NOFOLLOW: i64);
|
AT_SYMLINK_NOFOLLOW: i64);
|
||||||
if (r < 0) { return r: oserror; };
|
if (r < 0) { return r: oserror; };
|
||||||
fillfilestat(out, &k);
|
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,
|
// exists — true if `path` resolves to anything (regular file,
|
||||||
// directory, symlink, ...). Stat-shaped (Hare's `fs::exists`,
|
// directory, symlink, ...). Stat-shaped (Hare's `fs::exists`,
|
||||||
// ref/hare/fs/fs.ha:196) — no separate syscall. Symlinks are
|
// 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
|
// Race warning: prefer "open and handle the error" over "exists
|
||||||
// then open" in real code (Hare's docstring carries the same
|
// 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
|
// — same class as STATUS #22, surfaced first time a match on this
|
||||||
// shape combined with an 80B local-struct local frame). Use the
|
// shape combined with an 80B local-struct local frame). Use the
|
||||||
// match shape once #22 lands.
|
// 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 k: kstat;
|
||||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
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;
|
return r >= 0i64;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1513,7 +1568,7 @@ export fn main() i32 = {
|
|||||||
// that step. RDONLY is 0; passing the literal keeps the call
|
// that step. RDONLY is 0; passing the literal keeps the call
|
||||||
// site standalone-compilable to byte-identical asm on both
|
// site standalone-compilable to byte-identical asm on both
|
||||||
// compilers.
|
// compilers.
|
||||||
let fd: i32 = os.open(path.ptr, 0, 0i32);
|
let fd: i32 = os.open(path, 0, 0i32);
|
||||||
if (fd < 0) { return 21; };
|
if (fd < 0) { return 21; };
|
||||||
let rbuf: [128]u8;
|
let rbuf: [128]u8;
|
||||||
// Use raw os.read here (single syscall, plain i64) instead of
|
// 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
|
// that step. RDONLY is 0; passing the literal keeps the call
|
||||||
// site standalone-compilable to byte-identical asm on both
|
// site standalone-compilable to byte-identical asm on both
|
||||||
// compilers.
|
// compilers.
|
||||||
let fd: i32 = os.open(path.ptr, 0, 0i32);
|
let fd: i32 = os.open(path, 0, 0i32);
|
||||||
if (fd < 0) { return 21; };
|
if (fd < 0) { return 21; };
|
||||||
let rbuf: [128]u8;
|
let rbuf: [128]u8;
|
||||||
// Use raw os.read here (single syscall, plain i64) instead of
|
// Use raw os.read here (single syscall, plain i64) instead of
|
||||||
|
|||||||
Reference in New Issue
Block a user