From fbe0df4e68efd7ffc6a82a9d6abb9990a21ca103 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Wed, 13 May 2026 17:18:59 +0900 Subject: [PATCH] lib: add temp + os.mkdir/rmdir/EXCL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit temp mirrors Hare's temp: file, named, dir. file() routes through named() and discards the path (no O_TMPFILE yet). Path randomizer uses inline SplitMix64 seeded from getpid + O_EXCL retry (Hare uses crypto::random which we don't ship). named() takes out-pointers for fd + path — return shape gated on tasks #5 and #11. Caller closes and removes; no defer in ww. os gains mkdir, rmdir, flag.EXCL — straight ports of ref/hare/os. selfhost combined files cascade; 995_self_rebuild byte-identity holds. --- lib/os/os.ww | 16 ++ lib/temp/temp.ww | 243 +++++++++++++++++++++++++ lib/temp/temptest.ww | 262 +++++++++++++++++++++++++++ selfhost/cmd/w6a/main.combined.ww | 16 ++ selfhost/cmd/w6c/main.combined.ww | 16 ++ selfhost/cmd/w6l/main.combined.ww | 16 ++ selfhost/cmd/ww/main.combined.ww | 16 ++ selfhost/cmd/wwdump/main.combined.ww | 16 ++ selfhost/test/smoke.combined.ww | 16 ++ 9 files changed, 617 insertions(+) create mode 100644 lib/temp/temp.ww create mode 100644 lib/temp/temptest.ww 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;