diff --git a/lib/os/os.ww b/lib/os/os.ww
index f123389d..a597b8a3 100644
--- a/lib/os/os.ww
+++ b/lib/os/os.ww
@@ -35,6 +35,8 @@ type nr = enum i64 {
EXECVE = 59,
EXIT = 60,
WAIT4 = 61,
+ MKDIR = 83,
+ RMDIR = 84,
UNLINK = 87,
GETCWD = 79,
GETDENTS64 = 217,
@@ -48,6 +50,7 @@ export type flag = enum i32 {
WRONLY = 1,
RDWR = 2,
CREATE = 64, // 0x40
+ EXCL = 128, // 0x80 — pair with CREATE to fail on existing path
TRUNC = 512, // 0x200
};
@@ -177,6 +180,19 @@ export fn remove(path: *u8) i32 = {
return syscall1(nr.UNLINK, path: i64): i32;
};
+// mkdir — mkdir(2). Path must be NUL-terminated. Mode is the unix
+// permission bitset (e.g. 0o700). Returns 0 on success, negative
+// errno otherwise. Hare name (os::mkdir).
+export fn mkdir(path: *u8, mode: i32) i32 = {
+ return syscall2(nr.MKDIR, path: i64, mode: i64): i32;
+};
+
+// rmdir — rmdir(2). Path must be NUL-terminated. Returns 0 on
+// success, negative errno otherwise. Hare name (os::rmdir).
+export fn rmdir(path: *u8) i32 = {
+ return syscall1(nr.RMDIR, path: i64): i32;
+};
+
// getpid(2). Used by the driver to mint unique scratch paths.
export fn getpid() i32 = {
return syscall0(nr.GETPID): i32;
diff --git a/lib/temp/temp.ww b/lib/temp/temp.ww
new file mode 100644
index 00000000..7a1a3064
--- /dev/null
+++ b/lib/temp/temp.ww
@@ -0,0 +1,243 @@
+// temp — process-local temporary files and directories.
+//
+// Mirrors Hare's temp:: at ref/hare/temp/+linux.ha; drops underscores
+// per plan 9 style and the layers Hare wires on top of raw os: there
+// is no fs::fs filesystem handle, no io::file abstraction, no defer
+// hook for auto-cleanup. Callers MUST [[os.close]] the returned fd
+// and [[os.remove]] / [[os.rmdir]] the returned path themselves; the
+// absence of defer is a deliberate ergonomic divergence drew-devault
+// was explicit about — do not invent auto-cleanup wrappers.
+//
+// Subset divergence from Hare's +linux.ha:
+//
+// * No TMPDIR env probe. lib/os doesn't expose getenv yet, so
+// [[file]] and [[dir]] are pinned to "/tmp". Graduate to a
+// `gettmpdir` that mirrors Hare's os::tryenv lookup when
+// lib/os.getenv lands.
+// * No O_TMPFILE fast path in [[file]]. We always go through
+// [[named]] and discard the returned path, matching Hare's
+// +freebsd.ha branch (and the +linux.ha fallback). The on-disk
+// entry leaks until the caller of [[named]] removes it; [[file]]
+// callers cannot remove it because they no longer hold the path.
+// Lift to a real O_TMPFILE branch when lib/os gains a TMPFILE
+// flag.
+// * [[named]] writes its (fd, path) result through out-pointers
+// instead of returning Hare's `(io::file, str) | fs::error`
+// 2-tuple. ww's cgen drops 32B return-by-value (task #5) and
+// mis-sizes the (i32, str) tuple (task #11), so we mirror
+// memio's / getopt's out-pointer shape until both close.
+// * Hare's `fs: *fs::fs` parameter is dropped — lib/os hands out
+// raw fds, there is no virtual-filesystem indirection.
+// * `iomode: mode` is our own subset of Hare's io::mode — only
+// WRITE and RDWR, the two values Hare's temp asserts on.
+// * `perm: i32` is the raw Linux mode_t bitset rather than
+// Hare's fs::mode enum; matches what [[os.open]] takes.
+// * No PRNG seeding from crypto::random. Hare reads /dev/urandom
+// (via crypto::random::buffer); ww doesn't ship crypto::random
+// yet, so we seed an inline SplitMix64 from [[os.getpid]] and
+// rely on O_EXCL retry for collision avoidance. Adopt a real
+// entropy source when crypto::random is ported.
+// * The path stored in pathbuf is NUL-terminated for direct
+// handoff to syscalls; the returned str view excludes the NUL.
+// Hare's path::buffer carries no terminator.
+//
+// GRADUATE-IN-ONE-GO WARNING (lib/CLAUDE.md policy). Once tasks #5
+// and #11 close and lib/os exposes a real io::file abstraction plus
+// getenv, [[named]] graduates to Hare's value-returning
+// `(io::file, str) | fs::error` shape with a `fs: *fs::fs` first
+// arg, [[file]] grows an O_TMPFILE fast path, and [[gettmpdir]]
+// consults $TMPDIR. The old out-pointer surface disappears in the
+// same commit — callers MUST NOT bake it into themselves.
+//
+// Caller cleanup template (no defer yet):
+//
+// let fd: i32; let p: str;
+// match (temp.named(&fd, &p, "/tmp", temp.mode.RDWR, 384i32)) {
+// case void => { /* use fd */ os.close(fd); os.remove(p.ptr); };
+// case let e: os.oserror => { /* error */ };
+// };
+//
+// let d: str = temp.dir();
+// /* populate d ... */
+// os.rmdir(d.ptr);
+
+use os;
+
+// mode — temp's io flavour. Hare exposes io::mode {READ, WRITE,
+// RDWR}; temp asserts iomode must be WRITE or RDWR, so we ship just
+// those two. Numeric values match the corresponding [[os.flag]]
+// bits so the OR with CREATE/EXCL composes correctly.
+export type mode = enum i32 {
+ WRITE = 1, // os.flag.WRONLY
+ RDWR = 2, // os.flag.RDWR
+};
+
+// pathbuf — shared scratch path for [[named]] and [[dir]]. Sized
+// for any caller-supplied directory <= 233 bytes plus our 22-byte
+// "/temp.<16-hex-digits>\0" suffix; [[named]] returns ENAMETOOLONG
+// past that. Hare uses a path::buffer of PATH_MAX bytes; this
+// collapses to a single static slot because lib/path doesn't yet
+// ship a path::buffer type. NUL byte at `pathbuf[pathlen]` lets the
+// bytes be handed straight to [[os.open]] / [[os.mkdir]] /
+// [[os.remove]] / [[os.rmdir]].
+let pathbuf: [256]u8;
+let pathlen: i32 = 0;
+
+// rng* — SplitMix64 state. Lazily seeded on first call from
+// [[os.getpid]]; collisions are caught by the O_EXCL retry loops in
+// [[named]] and [[dir]]. Inline rather than `use math.random;` to
+// keep temp's import surface minimal and avoid pulling random's
+// module-level scope into temp's namespace.
+let rngstate: u64 = 0u64;
+let rnginit: i32 = 0;
+
+fn seedrng() void = {
+ if (rnginit == 0) {
+ rngstate = (os.getpid(): u64) ^ 0x9E3779B97F4A7C15u64;
+ rnginit = 1;
+ };
+};
+
+fn nextrand() u64 = {
+ seedrng();
+ rngstate = rngstate + 0x9E3779B97F4A7C15u64;
+ let a: u64 = rngstate;
+ a = (a ^ (a >> 30u64)) * 0xBF58476D1CE4E5B9u64;
+ a = (a ^ (a >> 27u64)) * 0x94D049BB133111EBu64;
+ return a ^ (a >> 31u64);
+};
+
+// gettmpdir — Hare's get_tmpdir(). Hardcoded /tmp until lib/os
+// exposes getenv; documented in the file header.
+fn gettmpdir() str = { return "/tmp"; };
+
+// puts — copy `s` into pathbuf starting at `off`. Caps writes
+// against pathbuf's capacity so a long caller-supplied dir can't
+// run off the end. Returns the new offset.
+fn puts(off: i32, s: str) i32 = {
+ let i: i32 = 0;
+ for (i < s.len) {
+ if (off + i >= 255) { break; };
+ pathbuf[off + i] = s[i];
+ i += 1;
+ };
+ return off + i;
+};
+
+// puthex — 16 lowercase hex digits of `v` into pathbuf[off..off+16].
+fn puthex(off: i32, v: u64) i32 = {
+ let hex: str = "0123456789abcdef";
+ let i: i32 = 0;
+ for (i < 16) {
+ let shift: u64 = ((15 - i): u64) * 4u64;
+ let nib: i32 = ((v >> shift) & 0xFu64): i32;
+ if (off + i < 255) { pathbuf[off + i] = hex[nib]; };
+ i += 1;
+ };
+ return off + 16;
+};
+
+// makenamed — `
/temp.\0` into pathbuf. Returns the path
+// length (excluding NUL).
+fn makenamed(dir: str) i32 = {
+ let off: i32 = 0;
+ off = puts(off, dir);
+ pathbuf[off] = 47u8; off += 1; // '/'
+ off = puts(off, "temp.");
+ off = puthex(off, nextrand());
+ pathbuf[off] = 0u8;
+ return off;
+};
+
+// makedir — `/\0` into pathbuf. Returns path length
+// (excluding NUL).
+fn makedir() i32 = {
+ let off: i32 = 0;
+ off = puts(off, gettmpdir());
+ pathbuf[off] = 47u8; off += 1;
+ off = puthex(off, nextrand());
+ pathbuf[off] = 0u8;
+ return off;
+};
+
+@symbol("rt_abort") fn rtabort(msg: str) void;
+
+// named — create a fresh `/temp.` with O_EXCL. On success
+// `*outfd` receives the fd and `*outpath` receives a borrowed view
+// into a module-level path buffer; the buffer is overwritten on
+// every subsequent [[named]] or [[dir]] call. Caller MUST close
+// *outfd and [[os.remove]] *outpath when done.
+//
+// Returns -ENAMETOOLONG (-36) as `os.oserror` if `dir.len > 233`:
+// pathbuf is 256B, the suffix "/temp.<16-hex>\0" needs 22 bytes,
+// leaving 234 for `dir` (0..233 inclusive). Without this guard
+// [[makenamed]]'s final NUL-store would write past pathbuf's end.
+//
+// Mirrors Hare's temp::named (minus the fs::fs first arg and the
+// 2-tuple return — see file header).
+export fn named(outfd: *i32, outpath: *str,
+ dir: str, iomode: mode, perm: i32) (void | os.oserror) = {
+ if (dir.len > 233) { return -36i64: os.oserror; };
+ let flags: os.flag = os.flag.CREATE | os.flag.EXCL;
+ if (iomode == mode.RDWR) {
+ flags = flags | os.flag.RDWR;
+ } else {
+ flags = flags | os.flag.WRONLY;
+ };
+ for (true) {
+ pathlen = makenamed(dir);
+ let fd: i32 = os.open(&pathbuf[0], flags, perm);
+ if (fd >= 0) {
+ *outfd = fd;
+ let p: str;
+ p.ptr = &pathbuf[0];
+ p.len = pathlen;
+ *outpath = p;
+ return;
+ };
+ let e: i64 = fd: i64;
+ if (e != -17i64) { return e: os.oserror; }; // -EEXIST → retry
+ };
+ return;
+};
+
+// file — create a temporary file under [[gettmpdir]] and return the
+// fd. The on-disk path is discarded; without O_TMPFILE the entry
+// persists until removed externally. Matches Hare's +freebsd.ha
+// branch (and the +linux.ha fallback path). Caller MUST close.
+//
+// Mirrors Hare's temp::file.
+export fn file(iomode: mode, perm: i32) (i32 | os.oserror) = {
+ let fd: i32 = 0;
+ let p: str;
+ match (named(&fd, &p, gettmpdir(), iomode, perm)) {
+ case void => return fd;
+ case let e: os.oserror => return e;
+ };
+};
+
+// dir — create a fresh temporary directory under [[gettmpdir]] with
+// mode 0o700. Returns a borrowed view of the static path buffer;
+// the buffer is overwritten on every subsequent [[named]] or
+// [[dir]] call. Caller MUST [[os.rmdir]] the path when done.
+// Aborts on syscall failure other than EEXIST, mirroring Hare's
+// `abort("Could not create temp directory")`.
+//
+// Mirrors Hare's temp::dir.
+export fn dir() str = {
+ for (true) {
+ pathlen = makedir();
+ let r: i32 = os.mkdir(&pathbuf[0], 448i32); // 0o700
+ if (r >= 0) {
+ let p: str;
+ p.ptr = &pathbuf[0];
+ p.len = pathlen;
+ return p;
+ };
+ if (r != -17) { rtabort("temp.dir: mkdir failed"); }; // EEXIST → retry
+ };
+ let p: str;
+ p.ptr = &pathbuf[0];
+ p.len = pathlen;
+ return p;
+};
diff --git a/lib/temp/temptest.ww b/lib/temp/temptest.ww
new file mode 100644
index 00000000..d1720a47
--- /dev/null
+++ b/lib/temp/temptest.ww
@@ -0,0 +1,262 @@
+// temptest — exercises lib/temp. Run with
+// `out/bin/ww build lib/temp/temptest.ww && ./temptest`.
+//
+// Every @test enumerates parallel `[N]T` arrays of inputs and
+// expectations, then iterates one body across them. Parallel arrays
+// (rather than `[N]struct{...}`) sidestep the cstage cgen's chained
+// `arr[i].field` store gap (task #6).
+//
+// Cleanup is the tests' responsibility — temp ships no defer hook
+// (deliberate divergence from Hare; see lib/temp/temp.ww header).
+// Each test [[os.close]]s every fd it opens and [[os.remove]] /
+// [[os.rmdir]]s every path it returns, so a regression here would
+// leave detritus under /tmp. `ls /tmp` before/after each run should
+// match.
+
+use os;
+use temp;
+
+// Direct exit(2) binding rather than mixing `use io;` and `use os;` —
+// they share read/write/close names under the driver's flat-scope
+// concat (task #7). We don't need lib/io here at all (raw fd ops
+// via os.read/os.write/os.close suffice), but the binding shape
+// mirrors memio/getopt for parity.
+fn doexit(code: i32) void = { os.exit(code); };
+
+// signalled — bumped by main before each test so a failing exit
+// code pinpoints the offending case.
+let signalled: i32 = 0;
+
+fn fail() void = { doexit(signalled + 10); };
+
+fn streq(a: str, b: str) bool = {
+ if (a.len != b.len) { return false; };
+ let i: i32 = 0;
+ for (i < a.len) {
+ if (a[i] != b[i]) { return false; };
+ i += 1;
+ };
+ return true;
+};
+
+// ---- namedroundtrip: write + read-back across payload sizes ------------
+
+@test fn namedroundtrip() void = {
+ // (payload size, fill byte). Row 0 covers the zero-byte edge.
+ let sz: [4]i32;
+ let fill: [4]u8;
+ sz[0]=0; fill[0]=0u8;
+ sz[1]=1; fill[1]=33u8; // '!'
+ sz[2]=4; fill[2]=65u8; // 'A'
+ sz[3]=64; fill[3]=90u8; // 'Z'
+
+ let i: i32 = 0;
+ for (i < 4) {
+ let fd: i32 = 0;
+ let p: str;
+ match (temp.named(&fd, &p, "/tmp", temp.mode.RDWR, 384i32)) { // 0o600
+ case void => {};
+ case let e: os.oserror => fail();
+ };
+ if (fd < 0) { fail(); };
+ if (p.len < 10) { fail(); }; // at minimum "/tmp/temp.<1 hex>"
+
+ // Path bytes must start with "/tmp/temp." and live in temp's
+ // static buffer (NUL-terminated for syscall handoff).
+ if (p[0] != 47u8) { fail(); }; // '/'
+ if (!streq(strslice(p, 0, 10), "/tmp/temp.")) { fail(); };
+ if (p.ptr[p.len] != 0u8) { fail(); };
+
+ // Build fill payload, write it, lseek to 0, read it back.
+ let wbuf: [64]u8;
+ let k: i32 = 0;
+ for (k < sz[i]) { wbuf[k] = fill[i]; k += 1; };
+ if (sz[i] > 0) {
+ let wr: i64 = os.write(fd, &wbuf[0], sz[i]: u64);
+ if (wr != sz[i]: i64) { fail(); };
+ };
+
+ let r: i64 = os.lseek(fd, 0i64, os.whence.SET);
+ if (r != 0i64) { fail(); };
+
+ let rbuf: [64]u8;
+ let z: i32 = 0;
+ for (z < 64) { rbuf[z] = 0u8; z += 1; };
+ let rd: i64 = 0i64;
+ if (sz[i] > 0) {
+ rd = os.read(fd, &rbuf[0], sz[i]: u64);
+ if (rd != sz[i]: i64) { fail(); };
+ };
+ let k2: i32 = 0;
+ for (k2 < sz[i]) {
+ if (rbuf[k2] != fill[i]) { fail(); };
+ k2 += 1;
+ };
+
+ // File exists pre-cleanup.
+ if (os.access(p.ptr, 0i32) != 0) { fail(); };
+
+ if (os.close(fd) != 0) { fail(); };
+ if (os.remove(p.ptr) != 0) { fail(); };
+
+ // Cleanup landed.
+ if (os.access(p.ptr, 0i32) == 0) { fail(); };
+
+ i += 1;
+ };
+};
+
+// strslice — borrow `p[lo:hi]`. Inline because lib/strings.sub
+// returns an allocated copy in some shapes; here we want a view.
+fn strslice(p: str, lo: i32, hi: i32) str = {
+ let r: str;
+ r.ptr = p.ptr + (lo: u64);
+ r.len = hi - lo;
+ return r;
+};
+
+// ---- namedoverwrite: static buffer is reused across calls --------------
+//
+// Hare docs: "The name is statically allocated, and will be
+// overwritten on subsequent calls." Match that contract — the second
+// named() call lands in the same buffer, so p1.ptr == p2.ptr.
+@test fn namedoverwrite() void = {
+ let fd1: i32 = 0;
+ let p1: str;
+ match (temp.named(&fd1, &p1, "/tmp", temp.mode.WRITE, 384i32)) {
+ case void => {};
+ case let e: os.oserror => fail();
+ };
+
+ // Snapshot p1's bytes BEFORE the second call clobbers the buffer,
+ // so we can compare p2 against the original p1 content and
+ // remove() the first file after closing it.
+ let snap: [128]u8;
+ let snaplen: i32 = p1.len;
+ let i: i32 = 0;
+ for (i < p1.len) { snap[i] = p1[i]; i += 1; };
+ snap[p1.len] = 0u8;
+ let psnap: str;
+ psnap.ptr = &snap[0];
+ psnap.len = snaplen;
+
+ let fd2: i32 = 0;
+ let p2: str;
+ match (temp.named(&fd2, &p2, "/tmp", temp.mode.WRITE, 384i32)) {
+ case void => {};
+ case let e: os.oserror => fail();
+ };
+
+ // Same buffer (Hare docs: "overwritten on subsequent calls"),
+ // distinct path bytes (random suffix differs).
+ if (p1.ptr != p2.ptr) { fail(); };
+ if (streq(strslice(p2, 0, p2.len), psnap)) { fail(); };
+
+ // Both fds are distinct.
+ if (fd1 == fd2) { fail(); };
+
+ if (os.close(fd2) != 0) { fail(); };
+ if (os.remove(p2.ptr) != 0) { fail(); };
+
+ if (os.close(fd1) != 0) { fail(); };
+ if (os.remove(&snap[0]) != 0) { fail(); };
+};
+
+// NOTE: temp.file() has no test of its own. The function is a thin
+// wrapper around temp.named() that DROPS the returned path, and the
+// on-disk entry would leak until external cleanup (no O_TMPFILE in
+// lib/os yet — see lib/temp/temp.ww header). Hare's +freebsd.ha
+// has the same leak; the Hare +linux.ha fallback path does too.
+// Re-add a file() test once O_TMPFILE lands and the leak goes away.
+// Coverage for the underlying open+create+EXCL path lives in
+// [[namedroundtrip]] / [[namedoverwrite]].
+
+// ---- dirlifecycle: empty dir, then dir + one child file ----------------
+
+@test fn dirlifecycle() void = {
+ // (child count). Row 0: empty dir. Row 1: dir + one file.
+ let childn: [2]i32;
+ childn[0] = 0;
+ childn[1] = 1;
+
+ let i: i32 = 0;
+ for (i < 2) {
+ let d: str = temp.dir();
+ if (d.len < 6) { fail(); };
+ if (!streq(strslice(d, 0, 5), "/tmp/")) { fail(); };
+ if (d.ptr[d.len] != 0u8) { fail(); };
+
+ // Dir exists.
+ if (os.access(d.ptr, 0i32) != 0) { fail(); };
+
+ // Snapshot the dir path into a local NUL-terminated buffer:
+ // we'll need it after os.open() (the child create) leaves
+ // the buffer alone, but it's good hygiene given future
+ // helpers might share pathbuf.
+ let dsnap: [128]u8;
+ let dlen: i32 = d.len;
+ let s: i32 = 0;
+ for (s < d.len) { dsnap[s] = d[s]; s += 1; };
+ dsnap[d.len] = 0u8;
+
+ if (childn[i] > 0) {
+ // Build "/x\0" in a local buffer.
+ let cbuf: [144]u8;
+ let off: i32 = 0;
+ let j: i32 = 0;
+ for (j < dlen) { cbuf[off] = dsnap[j]; off += 1; j += 1; };
+ cbuf[off] = 47u8; off += 1; // '/'
+ cbuf[off] = 120u8; off += 1; // 'x'
+ cbuf[off] = 0u8;
+ let cflags: os.flag = os.flag.WRONLY | os.flag.CREATE | os.flag.EXCL;
+ let fd: i32 = os.open(&cbuf[0], cflags, 384i32);
+ if (fd < 0) { fail(); };
+ let payload: [3]u8;
+ payload[0] = 88u8; payload[1] = 89u8; payload[2] = 90u8; // "XYZ"
+ let wr: i64 = os.write(fd, &payload[0], 3u64);
+ if (wr != 3i64) { fail(); };
+ if (os.close(fd) != 0) { fail(); };
+ if (os.remove(&cbuf[0]) != 0) { fail(); };
+ };
+
+ // Rmdir uses dsnap (more robust if the static buffer were
+ // touched between dir() and here).
+ if (os.rmdir(&dsnap[0]) != 0) { fail(); };
+
+ // Cleanup landed.
+ if (os.access(&dsnap[0], 0i32) == 0) { fail(); };
+
+ i += 1;
+ };
+};
+
+// ---- diruniqueness: two dir() calls produce different paths ------------
+
+@test fn diruniqueness() void = {
+ let d1: str = temp.dir();
+ let snap: [128]u8;
+ let snaplen: i32 = d1.len;
+ let i: i32 = 0;
+ for (i < d1.len) { snap[i] = d1[i]; i += 1; };
+ snap[d1.len] = 0u8;
+
+ let dsnap: str;
+ dsnap.ptr = &snap[0];
+ dsnap.len = snaplen;
+
+ let d2: str = temp.dir();
+ if (d1.ptr != d2.ptr) { fail(); }; // same static buffer
+ if (streq(strslice(d2, 0, d2.len), dsnap)) { fail(); };
+
+ // Cleanup both, using snap for d1's old contents.
+ if (os.rmdir(d2.ptr) != 0) { fail(); };
+ if (os.rmdir(&snap[0]) != 0) { fail(); };
+};
+
+export fn main() i32 = {
+ signalled = 1; namedroundtrip();
+ signalled = 2; namedoverwrite();
+ signalled = 3; dirlifecycle();
+ signalled = 4; diruniqueness();
+ return 0;
+};
diff --git a/selfhost/cmd/w6a/main.combined.ww b/selfhost/cmd/w6a/main.combined.ww
index 35e1df87..d6decfb5 100644
--- a/selfhost/cmd/w6a/main.combined.ww
+++ b/selfhost/cmd/w6a/main.combined.ww
@@ -36,6 +36,8 @@ type nr = enum i64 {
EXECVE = 59,
EXIT = 60,
WAIT4 = 61,
+ MKDIR = 83,
+ RMDIR = 84,
UNLINK = 87,
GETCWD = 79,
GETDENTS64 = 217,
@@ -49,6 +51,7 @@ export type flag = enum i32 {
WRONLY = 1,
RDWR = 2,
CREATE = 64, // 0x40
+ EXCL = 128, // 0x80 — pair with CREATE to fail on existing path
TRUNC = 512, // 0x200
};
@@ -178,6 +181,19 @@ export fn remove(path: *u8) i32 = {
return syscall1(nr.UNLINK, path: i64): i32;
};
+// mkdir — mkdir(2). Path must be NUL-terminated. Mode is the unix
+// permission bitset (e.g. 0o700). Returns 0 on success, negative
+// errno otherwise. Hare name (os::mkdir).
+export fn mkdir(path: *u8, mode: i32) i32 = {
+ return syscall2(nr.MKDIR, path: i64, mode: i64): i32;
+};
+
+// rmdir — rmdir(2). Path must be NUL-terminated. Returns 0 on
+// success, negative errno otherwise. Hare name (os::rmdir).
+export fn rmdir(path: *u8) i32 = {
+ return syscall1(nr.RMDIR, path: i64): i32;
+};
+
// getpid(2). Used by the driver to mint unique scratch paths.
export fn getpid() i32 = {
return syscall0(nr.GETPID): i32;
diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww
index 955513ef..f8771749 100644
--- a/selfhost/cmd/w6c/main.combined.ww
+++ b/selfhost/cmd/w6c/main.combined.ww
@@ -36,6 +36,8 @@ type nr = enum i64 {
EXECVE = 59,
EXIT = 60,
WAIT4 = 61,
+ MKDIR = 83,
+ RMDIR = 84,
UNLINK = 87,
GETCWD = 79,
GETDENTS64 = 217,
@@ -49,6 +51,7 @@ export type flag = enum i32 {
WRONLY = 1,
RDWR = 2,
CREATE = 64, // 0x40
+ EXCL = 128, // 0x80 — pair with CREATE to fail on existing path
TRUNC = 512, // 0x200
};
@@ -178,6 +181,19 @@ export fn remove(path: *u8) i32 = {
return syscall1(nr.UNLINK, path: i64): i32;
};
+// mkdir — mkdir(2). Path must be NUL-terminated. Mode is the unix
+// permission bitset (e.g. 0o700). Returns 0 on success, negative
+// errno otherwise. Hare name (os::mkdir).
+export fn mkdir(path: *u8, mode: i32) i32 = {
+ return syscall2(nr.MKDIR, path: i64, mode: i64): i32;
+};
+
+// rmdir — rmdir(2). Path must be NUL-terminated. Returns 0 on
+// success, negative errno otherwise. Hare name (os::rmdir).
+export fn rmdir(path: *u8) i32 = {
+ return syscall1(nr.RMDIR, path: i64): i32;
+};
+
// getpid(2). Used by the driver to mint unique scratch paths.
export fn getpid() i32 = {
return syscall0(nr.GETPID): i32;
diff --git a/selfhost/cmd/w6l/main.combined.ww b/selfhost/cmd/w6l/main.combined.ww
index dc1671f9..73497c8c 100644
--- a/selfhost/cmd/w6l/main.combined.ww
+++ b/selfhost/cmd/w6l/main.combined.ww
@@ -36,6 +36,8 @@ type nr = enum i64 {
EXECVE = 59,
EXIT = 60,
WAIT4 = 61,
+ MKDIR = 83,
+ RMDIR = 84,
UNLINK = 87,
GETCWD = 79,
GETDENTS64 = 217,
@@ -49,6 +51,7 @@ export type flag = enum i32 {
WRONLY = 1,
RDWR = 2,
CREATE = 64, // 0x40
+ EXCL = 128, // 0x80 — pair with CREATE to fail on existing path
TRUNC = 512, // 0x200
};
@@ -178,6 +181,19 @@ export fn remove(path: *u8) i32 = {
return syscall1(nr.UNLINK, path: i64): i32;
};
+// mkdir — mkdir(2). Path must be NUL-terminated. Mode is the unix
+// permission bitset (e.g. 0o700). Returns 0 on success, negative
+// errno otherwise. Hare name (os::mkdir).
+export fn mkdir(path: *u8, mode: i32) i32 = {
+ return syscall2(nr.MKDIR, path: i64, mode: i64): i32;
+};
+
+// rmdir — rmdir(2). Path must be NUL-terminated. Returns 0 on
+// success, negative errno otherwise. Hare name (os::rmdir).
+export fn rmdir(path: *u8) i32 = {
+ return syscall1(nr.RMDIR, path: i64): i32;
+};
+
// getpid(2). Used by the driver to mint unique scratch paths.
export fn getpid() i32 = {
return syscall0(nr.GETPID): i32;
diff --git a/selfhost/cmd/ww/main.combined.ww b/selfhost/cmd/ww/main.combined.ww
index de180c9f..54cdfc43 100644
--- a/selfhost/cmd/ww/main.combined.ww
+++ b/selfhost/cmd/ww/main.combined.ww
@@ -36,6 +36,8 @@ type nr = enum i64 {
EXECVE = 59,
EXIT = 60,
WAIT4 = 61,
+ MKDIR = 83,
+ RMDIR = 84,
UNLINK = 87,
GETCWD = 79,
GETDENTS64 = 217,
@@ -49,6 +51,7 @@ export type flag = enum i32 {
WRONLY = 1,
RDWR = 2,
CREATE = 64, // 0x40
+ EXCL = 128, // 0x80 — pair with CREATE to fail on existing path
TRUNC = 512, // 0x200
};
@@ -178,6 +181,19 @@ export fn remove(path: *u8) i32 = {
return syscall1(nr.UNLINK, path: i64): i32;
};
+// mkdir — mkdir(2). Path must be NUL-terminated. Mode is the unix
+// permission bitset (e.g. 0o700). Returns 0 on success, negative
+// errno otherwise. Hare name (os::mkdir).
+export fn mkdir(path: *u8, mode: i32) i32 = {
+ return syscall2(nr.MKDIR, path: i64, mode: i64): i32;
+};
+
+// rmdir — rmdir(2). Path must be NUL-terminated. Returns 0 on
+// success, negative errno otherwise. Hare name (os::rmdir).
+export fn rmdir(path: *u8) i32 = {
+ return syscall1(nr.RMDIR, path: i64): i32;
+};
+
// getpid(2). Used by the driver to mint unique scratch paths.
export fn getpid() i32 = {
return syscall0(nr.GETPID): i32;
diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww
index 6b2a9157..b382a864 100644
--- a/selfhost/cmd/wwdump/main.combined.ww
+++ b/selfhost/cmd/wwdump/main.combined.ww
@@ -36,6 +36,8 @@ type nr = enum i64 {
EXECVE = 59,
EXIT = 60,
WAIT4 = 61,
+ MKDIR = 83,
+ RMDIR = 84,
UNLINK = 87,
GETCWD = 79,
GETDENTS64 = 217,
@@ -49,6 +51,7 @@ export type flag = enum i32 {
WRONLY = 1,
RDWR = 2,
CREATE = 64, // 0x40
+ EXCL = 128, // 0x80 — pair with CREATE to fail on existing path
TRUNC = 512, // 0x200
};
@@ -178,6 +181,19 @@ export fn remove(path: *u8) i32 = {
return syscall1(nr.UNLINK, path: i64): i32;
};
+// mkdir — mkdir(2). Path must be NUL-terminated. Mode is the unix
+// permission bitset (e.g. 0o700). Returns 0 on success, negative
+// errno otherwise. Hare name (os::mkdir).
+export fn mkdir(path: *u8, mode: i32) i32 = {
+ return syscall2(nr.MKDIR, path: i64, mode: i64): i32;
+};
+
+// rmdir — rmdir(2). Path must be NUL-terminated. Returns 0 on
+// success, negative errno otherwise. Hare name (os::rmdir).
+export fn rmdir(path: *u8) i32 = {
+ return syscall1(nr.RMDIR, path: i64): i32;
+};
+
// getpid(2). Used by the driver to mint unique scratch paths.
export fn getpid() i32 = {
return syscall0(nr.GETPID): i32;
diff --git a/selfhost/test/smoke.combined.ww b/selfhost/test/smoke.combined.ww
index 70021f7b..fa83f16d 100644
--- a/selfhost/test/smoke.combined.ww
+++ b/selfhost/test/smoke.combined.ww
@@ -36,6 +36,8 @@ type nr = enum i64 {
EXECVE = 59,
EXIT = 60,
WAIT4 = 61,
+ MKDIR = 83,
+ RMDIR = 84,
UNLINK = 87,
GETCWD = 79,
GETDENTS64 = 217,
@@ -49,6 +51,7 @@ export type flag = enum i32 {
WRONLY = 1,
RDWR = 2,
CREATE = 64, // 0x40
+ EXCL = 128, // 0x80 — pair with CREATE to fail on existing path
TRUNC = 512, // 0x200
};
@@ -178,6 +181,19 @@ export fn remove(path: *u8) i32 = {
return syscall1(nr.UNLINK, path: i64): i32;
};
+// mkdir — mkdir(2). Path must be NUL-terminated. Mode is the unix
+// permission bitset (e.g. 0o700). Returns 0 on success, negative
+// errno otherwise. Hare name (os::mkdir).
+export fn mkdir(path: *u8, mode: i32) i32 = {
+ return syscall2(nr.MKDIR, path: i64, mode: i64): i32;
+};
+
+// rmdir — rmdir(2). Path must be NUL-terminated. Returns 0 on
+// success, negative errno otherwise. Hare name (os::rmdir).
+export fn rmdir(path: *u8) i32 = {
+ return syscall1(nr.RMDIR, path: i64): i32;
+};
+
// getpid(2). Used by the driver to mint unique scratch paths.
export fn getpid() i32 = {
return syscall0(nr.GETPID): i32;