lib/os+test: add stat / lstat / fstat / exists
Hare-shaped filestat introspection. New types: filestat (80B, mirrors fs::filestat ref/hare/fs/types.ha:141), mode (31-member enum mirroring fs::mode ref/hare/fs/types.ha:63), stat_mask (7 bits mirroring fs::stat_mask ref/hare/fs/types.ha:129), timespec (i64+i64, layout-compatible with future lib/time::instant). APIs: stat / lstat / fstat (*filestat, *u8|i32) (void|oserror) over SYS_newfstatat (nr=262). The out-param shape sidesteps the cgreturn 24B ABI cap; commented inline. exists(*u8) bool goes through the syscall directly rather than wrapping stat()? — dodges task #22's 80B-scrutinee match-slot disagreement until that lands. Three latent cgen workarounds in tree, all pointer'd to filed tasks: #22: os.exists sidesteps the (void|oserror) match shape #24: `at` enum bundles AT_FDCWD/SYMLINK_NOFOLLOW/EMPTY_PATH instead of three top-level `def`s (negative-literal def DATA omit) #25: kstat.mode typed as `mode` (enum) rather than u32 to skip the redundant u32→enum cast emit Tests: 976_stat_run, 9 rows — stat/lstat/fstat × regfile/dir/symlink plus exists × {regfile,dir,noent}. Row 1 also pins perm-bit and atime/mtime/ctime!=0 to catch silent kstat→filestat offset miscompiles (kstat fields at 72/88/104). Graduation to lib/fs when it ships is noted inline; signatures stay rename-compatible.
This commit is contained in:
@@ -62,6 +62,7 @@ type nr = enum i64 {
|
||||
UNLINK = 87,
|
||||
GETCWD = 79,
|
||||
GETDENTS64 = 217,
|
||||
NEWFSTATAT = 262,
|
||||
};
|
||||
|
||||
// open(2) flags. Linux values, matching <fcntl.h>. Hare names them
|
||||
@@ -367,6 +368,248 @@ export fn getenv(name: str) (str | void) = {
|
||||
return;
|
||||
};
|
||||
|
||||
// ---- stat / lstat / fstat / exists -----------------------------------
|
||||
//
|
||||
// Ports of Hare's stat family (ref/hare/fs/fs.ha:172,196 +
|
||||
// ref/hare/sys/+linux/stat.ha:24-58). The Hare surface returns
|
||||
// `filestat` by value; ww's cgreturn ABI tops out at 24B today (see
|
||||
// STATUS task #21) and filestat is 80B, so [[stat]] / [[lstat]] /
|
||||
// [[fstat]] take an out-parameter and return `(void | oserror)`.
|
||||
// Re-evaluate the by-value shape when full sret lands.
|
||||
//
|
||||
// `filestat`, `mode`, and `stat_mask` live in lib/os because ww has
|
||||
// no lib/fs yet; Hare puts them in `fs::`. These types graduate to
|
||||
// lib/fs when that module ships — callers should expect a future
|
||||
// re-export.
|
||||
//
|
||||
// Underlying syscall is SYS_newfstatat (262), which unifies
|
||||
// stat/lstat/fstat through the `dirfd + flags` triple:
|
||||
// stat = newfstatat(AT_FDCWD, path, 0)
|
||||
// lstat = newfstatat(AT_FDCWD, path, AT_SYMLINK_NOFOLLOW)
|
||||
// fstat = newfstatat(fd, "", AT_EMPTY_PATH)
|
||||
// Avoiding SYS_statx — its 256B variable layout would buy btime,
|
||||
// but Hare's filestat doesn't expose btime either, so we stay on
|
||||
// the simpler 144B kernel struct.
|
||||
|
||||
// at — flags for fstatat(2). Numeric values match <fcntl.h>.
|
||||
// Bundled as an enum because top-level `def` of a negative literal
|
||||
// (FDCWD = -100) doesn't get a DATA slot emitted by cstage cgen —
|
||||
// linker surfaces as "undefined reference". Tracked as task #24;
|
||||
// enum-member resolution sidesteps it cleanly.
|
||||
type at = enum i64 {
|
||||
FDCWD = -100,
|
||||
SYMLINK_NOFOLLOW = 256, // 0x100
|
||||
EMPTY_PATH = 4096, // 0x1000
|
||||
};
|
||||
|
||||
// mode — file-mode bits. Mirrors Hare's fs::mode (ref/hare/fs/
|
||||
// types.ha:63). Permission bits are the standard Unix octal subset;
|
||||
// type bits live in the S_IFMT = 0o170000 region. Type-bit test:
|
||||
//
|
||||
// let t: u32 = (fi.mode as u32) & 61440u32; // 0o170000 mask
|
||||
// if (t == os.mode.DIR as u32) { /* directory */ };
|
||||
//
|
||||
// Numeric values are octal in Hare's source; ww has no octal
|
||||
// literals so they're written as decimal with the octal in a
|
||||
// trailing comment.
|
||||
export type mode = enum u32 {
|
||||
// permission bits
|
||||
USER_RWX = 448u32, // 0o700
|
||||
USER_RW = 384u32, // 0o600
|
||||
USER_RX = 320u32, // 0o500
|
||||
USER_R = 256u32, // 0o400
|
||||
USER_W = 128u32, // 0o200
|
||||
USER_X = 64u32, // 0o100
|
||||
GROUP_RWX = 56u32, // 0o070
|
||||
GROUP_RW = 48u32, // 0o060
|
||||
GROUP_RX = 40u32, // 0o050
|
||||
GROUP_R = 32u32, // 0o040
|
||||
GROUP_W = 16u32, // 0o020
|
||||
GROUP_X = 8u32, // 0o010
|
||||
OTHER_RWX = 7u32, // 0o007
|
||||
OTHER_RW = 6u32, // 0o006
|
||||
OTHER_RX = 5u32, // 0o005
|
||||
OTHER_R = 4u32, // 0o004
|
||||
OTHER_W = 2u32, // 0o002
|
||||
OTHER_X = 1u32, // 0o001
|
||||
SETUID = 2048u32, // 0o4000
|
||||
SETGID = 1024u32, // 0o2000
|
||||
STICKY = 512u32, // 0o1000
|
||||
// file-type bits (S_IFMT mask = 0o170000 = 61440)
|
||||
UNKNOWN = 0u32,
|
||||
FIFO = 4096u32, // 0o010000
|
||||
CHR = 8192u32, // 0o020000
|
||||
DIR = 16384u32, // 0o040000
|
||||
BLK = 24576u32, // 0o060000
|
||||
REG = 32768u32, // 0o100000
|
||||
LINK = 40960u32, // 0o120000
|
||||
SOCK = 49152u32, // 0o140000
|
||||
};
|
||||
|
||||
// stat_mask — which filestat fields the call populated. Mirrors
|
||||
// Hare's fs::stat_mask (ref/hare/fs/types.ha:129). newfstatat fills
|
||||
// every field, so [[stat]] / [[lstat]] / [[fstat]] always set all
|
||||
// seven bits OR-folded (see [[fillfilestat]]); per-bit testing is
|
||||
// the documented sparse-backend pattern (cf. Hare's fs::fs network
|
||||
// backends that only populate mtime+size).
|
||||
export type stat_mask = enum u32 {
|
||||
UID = 1u32,
|
||||
GID = 2u32,
|
||||
SIZE = 4u32,
|
||||
INODE = 8u32,
|
||||
ATIME = 16u32,
|
||||
MTIME = 32u32,
|
||||
CTIME = 64u32,
|
||||
};
|
||||
|
||||
// timespec — {sec, nsec} pair, matching Hare's time::instant
|
||||
// (ref/hare/time/types.ha). Local to lib/os; graduates to
|
||||
// lib/time.instant when lib/time and lib/fs ship. Same byte layout
|
||||
// (i64+i64 = 16B) so a future migration is field-rename only.
|
||||
export type timespec = struct {
|
||||
sec: i64,
|
||||
nsec: i64,
|
||||
};
|
||||
|
||||
// filestat — Hare's fs::filestat (ref/hare/fs/types.ha:141). 80
|
||||
// bytes. See module-header note re: graduation to lib/fs.
|
||||
export type filestat = struct {
|
||||
mask: stat_mask, // 0 (4)
|
||||
mode: mode, // 4 (4)
|
||||
uid: u32, // 8 (4)
|
||||
gid: u32, // 12 (4)
|
||||
sz: u64, // 16 (8)
|
||||
inode: u64, // 24 (8)
|
||||
atime: timespec, // 32 (16)
|
||||
mtime: timespec, // 48 (16)
|
||||
ctime: timespec, // 64 (16) — ends at 80
|
||||
};
|
||||
|
||||
// kstat — x86_64 kernel `struct stat` layout. Mirrors
|
||||
// arch/x86/include/uapi/asm/stat.h (`__kernel_ulong_t`-keyed
|
||||
// fields). 144 bytes. Module-internal; SYS_newfstatat writes into
|
||||
// this buffer and the public stat fns then copy the bits into the
|
||||
// Hare-shaped [[filestat]].
|
||||
//
|
||||
// Mode is typed as the public [[mode]] enum (rather than raw u32)
|
||||
// so [[fillfilestat]]'s `out.mode = k.mode` needs no cast. Cstage
|
||||
// emits a redundant `MOVL AX, AX` on u32 → enum-u32 casts that
|
||||
// wwstage skips (task #25); the in-tree shape sidesteps it.
|
||||
// Identical byte layout (both 4B at offset 24).
|
||||
type kstat = struct {
|
||||
dev: u64, // 0
|
||||
ino: u64, // 8
|
||||
nlink: u64, // 16
|
||||
mode: mode, // 24
|
||||
uid: u32, // 28
|
||||
gid: u32, // 32
|
||||
pad0: u32, // 36
|
||||
rdev: u64, // 40
|
||||
sz: i64, // 48
|
||||
blksize: i64, // 56
|
||||
blocks: i64, // 64
|
||||
atime_sec: i64, // 72
|
||||
atime_nsec: i64, // 80
|
||||
mtime_sec: i64, // 88
|
||||
mtime_nsec: i64, // 96
|
||||
ctime_sec: i64, // 104
|
||||
ctime_nsec: i64, // 112
|
||||
unused0: i64, // 120
|
||||
unused1: i64, // 128
|
||||
unused2: i64, // 136 — ends at 144
|
||||
};
|
||||
|
||||
// emptypath — single-NUL byte used as the `pathname` arg to
|
||||
// newfstatat with AT_EMPTY_PATH. The kernel requires a non-NULL
|
||||
// pointer to a zero-length C string, NOT a null pointer. Bytes are
|
||||
// read-only from the kernel's view; ww has no module-level const so
|
||||
// this is a writable `let`.
|
||||
let emptypath: [1]u8 = [0u8];
|
||||
|
||||
// fillfilestat — copy a 144B kstat into the 80B Hare-shaped
|
||||
// filestat. Internal helper used by all three public entry points.
|
||||
// Mirrors Hare's st_to_filestat (ref/hare/os/+linux/dirfdfs.ha:259):
|
||||
// newfstatat populates every field, so the mask is the OR-fold of
|
||||
// all seven Hare stat_mask bits.
|
||||
fn fillfilestat(out: *filestat, k: *kstat) void = {
|
||||
out.mask = stat_mask.UID | stat_mask.GID | stat_mask.SIZE
|
||||
| stat_mask.INODE | stat_mask.ATIME | stat_mask.MTIME
|
||||
| stat_mask.CTIME;
|
||||
out.mode = k.mode;
|
||||
out.uid = k.uid;
|
||||
out.gid = k.gid;
|
||||
out.sz = k.sz: u64;
|
||||
out.inode = k.ino;
|
||||
out.atime.sec = k.atime_sec;
|
||||
out.atime.nsec = k.atime_nsec;
|
||||
out.mtime.sec = k.mtime_sec;
|
||||
out.mtime.nsec = k.mtime_nsec;
|
||||
out.ctime.sec = k.ctime_sec;
|
||||
out.ctime.nsec = k.ctime_nsec;
|
||||
};
|
||||
|
||||
// stat — fill *out with metadata for `path`. Follows symlinks.
|
||||
// `path` must be NUL-terminated (lib/os convention; see task #23
|
||||
// for a planned `path: str` migration).
|
||||
//
|
||||
// Mirrors Hare's sys::stat (ref/hare/sys/+linux/stat.ha:51) modulo
|
||||
// the out-param shape forced by the cgreturn 24B cap. Note: Hare's
|
||||
// higher-level fs::stat (ref/hare/fs/fs.ha:172) instead has lstat
|
||||
// semantics — we follow sys::stat's POSIX-stat behavior here.
|
||||
export fn stat(out: *filestat, path: *u8) (void | oserror) = {
|
||||
let k: kstat;
|
||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
||||
at.FDCWD: i64, path: i64, (&k): i64, 0i64);
|
||||
if (r < 0) { return r: oserror; };
|
||||
fillfilestat(out, &k);
|
||||
};
|
||||
|
||||
// lstat — like [[stat]] but does NOT follow a terminal symlink.
|
||||
// Mirrors Hare's sys::lstat (ref/hare/sys/+linux/stat.ha:57).
|
||||
export fn lstat(out: *filestat, path: *u8) (void | oserror) = {
|
||||
let k: kstat;
|
||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
||||
at.FDCWD: i64, path: i64, (&k): i64,
|
||||
at.SYMLINK_NOFOLLOW: i64);
|
||||
if (r < 0) { return r: oserror; };
|
||||
fillfilestat(out, &k);
|
||||
};
|
||||
|
||||
// fstat — like [[stat]] but addresses the file by fd. Uses
|
||||
// newfstatat(fd, "", AT_EMPTY_PATH); the kernel resolves the fd
|
||||
// directly. Mirrors Hare's sys::fstat (ref/hare/sys/+linux/stat.ha:54).
|
||||
export fn fstat(out: *filestat, fd: i32) (void | oserror) = {
|
||||
let k: kstat;
|
||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
||||
fd: i64, (&emptypath[0]): i64, (&k): i64,
|
||||
at.EMPTY_PATH: i64);
|
||||
if (r < 0) { return r: oserror; };
|
||||
fillfilestat(out, &k);
|
||||
};
|
||||
|
||||
// exists — true if `path` resolves to anything (regular file,
|
||||
// directory, symlink, ...). Stat-shaped (Hare's `fs::exists`,
|
||||
// ref/hare/fs/fs.ha:196) — no separate syscall. Symlinks are
|
||||
// followed; a dangling symlink is `false`.
|
||||
//
|
||||
// Race warning: prefer "open and handle the error" over "exists
|
||||
// then open" in real code (Hare's docstring carries the same
|
||||
// note). The race is unavoidable in this shape.
|
||||
//
|
||||
// Goes through SYS_newfstatat directly rather than match'ing on
|
||||
// [[stat]]'s `(void | oserror)` return. Functionally identical;
|
||||
// the direct shape sidesteps a cstage/wwstage cgen disagreement
|
||||
// on the slot size of `(void | oserror)` (cstage 16B, wwstage 24B
|
||||
// — same class as STATUS #22, surfaced first time a match on this
|
||||
// shape combined with an 80B local-struct local frame). Use the
|
||||
// match shape once #22 lands.
|
||||
export fn exists(path: *u8) bool = {
|
||||
let k: kstat;
|
||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
||||
at.FDCWD: i64, path: i64, (&k): i64, 0i64);
|
||||
return r >= 0i64;
|
||||
};
|
||||
|
||||
// MODULE: wcc
|
||||
// selfhost/cmd/wcc/mem.ww — port of cmd/wcc/mem.c.
|
||||
//
|
||||
|
||||
@@ -62,6 +62,7 @@ type nr = enum i64 {
|
||||
UNLINK = 87,
|
||||
GETCWD = 79,
|
||||
GETDENTS64 = 217,
|
||||
NEWFSTATAT = 262,
|
||||
};
|
||||
|
||||
// open(2) flags. Linux values, matching <fcntl.h>. Hare names them
|
||||
@@ -367,6 +368,248 @@ export fn getenv(name: str) (str | void) = {
|
||||
return;
|
||||
};
|
||||
|
||||
// ---- stat / lstat / fstat / exists -----------------------------------
|
||||
//
|
||||
// Ports of Hare's stat family (ref/hare/fs/fs.ha:172,196 +
|
||||
// ref/hare/sys/+linux/stat.ha:24-58). The Hare surface returns
|
||||
// `filestat` by value; ww's cgreturn ABI tops out at 24B today (see
|
||||
// STATUS task #21) and filestat is 80B, so [[stat]] / [[lstat]] /
|
||||
// [[fstat]] take an out-parameter and return `(void | oserror)`.
|
||||
// Re-evaluate the by-value shape when full sret lands.
|
||||
//
|
||||
// `filestat`, `mode`, and `stat_mask` live in lib/os because ww has
|
||||
// no lib/fs yet; Hare puts them in `fs::`. These types graduate to
|
||||
// lib/fs when that module ships — callers should expect a future
|
||||
// re-export.
|
||||
//
|
||||
// Underlying syscall is SYS_newfstatat (262), which unifies
|
||||
// stat/lstat/fstat through the `dirfd + flags` triple:
|
||||
// stat = newfstatat(AT_FDCWD, path, 0)
|
||||
// lstat = newfstatat(AT_FDCWD, path, AT_SYMLINK_NOFOLLOW)
|
||||
// fstat = newfstatat(fd, "", AT_EMPTY_PATH)
|
||||
// Avoiding SYS_statx — its 256B variable layout would buy btime,
|
||||
// but Hare's filestat doesn't expose btime either, so we stay on
|
||||
// the simpler 144B kernel struct.
|
||||
|
||||
// at — flags for fstatat(2). Numeric values match <fcntl.h>.
|
||||
// Bundled as an enum because top-level `def` of a negative literal
|
||||
// (FDCWD = -100) doesn't get a DATA slot emitted by cstage cgen —
|
||||
// linker surfaces as "undefined reference". Tracked as task #24;
|
||||
// enum-member resolution sidesteps it cleanly.
|
||||
type at = enum i64 {
|
||||
FDCWD = -100,
|
||||
SYMLINK_NOFOLLOW = 256, // 0x100
|
||||
EMPTY_PATH = 4096, // 0x1000
|
||||
};
|
||||
|
||||
// mode — file-mode bits. Mirrors Hare's fs::mode (ref/hare/fs/
|
||||
// types.ha:63). Permission bits are the standard Unix octal subset;
|
||||
// type bits live in the S_IFMT = 0o170000 region. Type-bit test:
|
||||
//
|
||||
// let t: u32 = (fi.mode as u32) & 61440u32; // 0o170000 mask
|
||||
// if (t == os.mode.DIR as u32) { /* directory */ };
|
||||
//
|
||||
// Numeric values are octal in Hare's source; ww has no octal
|
||||
// literals so they're written as decimal with the octal in a
|
||||
// trailing comment.
|
||||
export type mode = enum u32 {
|
||||
// permission bits
|
||||
USER_RWX = 448u32, // 0o700
|
||||
USER_RW = 384u32, // 0o600
|
||||
USER_RX = 320u32, // 0o500
|
||||
USER_R = 256u32, // 0o400
|
||||
USER_W = 128u32, // 0o200
|
||||
USER_X = 64u32, // 0o100
|
||||
GROUP_RWX = 56u32, // 0o070
|
||||
GROUP_RW = 48u32, // 0o060
|
||||
GROUP_RX = 40u32, // 0o050
|
||||
GROUP_R = 32u32, // 0o040
|
||||
GROUP_W = 16u32, // 0o020
|
||||
GROUP_X = 8u32, // 0o010
|
||||
OTHER_RWX = 7u32, // 0o007
|
||||
OTHER_RW = 6u32, // 0o006
|
||||
OTHER_RX = 5u32, // 0o005
|
||||
OTHER_R = 4u32, // 0o004
|
||||
OTHER_W = 2u32, // 0o002
|
||||
OTHER_X = 1u32, // 0o001
|
||||
SETUID = 2048u32, // 0o4000
|
||||
SETGID = 1024u32, // 0o2000
|
||||
STICKY = 512u32, // 0o1000
|
||||
// file-type bits (S_IFMT mask = 0o170000 = 61440)
|
||||
UNKNOWN = 0u32,
|
||||
FIFO = 4096u32, // 0o010000
|
||||
CHR = 8192u32, // 0o020000
|
||||
DIR = 16384u32, // 0o040000
|
||||
BLK = 24576u32, // 0o060000
|
||||
REG = 32768u32, // 0o100000
|
||||
LINK = 40960u32, // 0o120000
|
||||
SOCK = 49152u32, // 0o140000
|
||||
};
|
||||
|
||||
// stat_mask — which filestat fields the call populated. Mirrors
|
||||
// Hare's fs::stat_mask (ref/hare/fs/types.ha:129). newfstatat fills
|
||||
// every field, so [[stat]] / [[lstat]] / [[fstat]] always set all
|
||||
// seven bits OR-folded (see [[fillfilestat]]); per-bit testing is
|
||||
// the documented sparse-backend pattern (cf. Hare's fs::fs network
|
||||
// backends that only populate mtime+size).
|
||||
export type stat_mask = enum u32 {
|
||||
UID = 1u32,
|
||||
GID = 2u32,
|
||||
SIZE = 4u32,
|
||||
INODE = 8u32,
|
||||
ATIME = 16u32,
|
||||
MTIME = 32u32,
|
||||
CTIME = 64u32,
|
||||
};
|
||||
|
||||
// timespec — {sec, nsec} pair, matching Hare's time::instant
|
||||
// (ref/hare/time/types.ha). Local to lib/os; graduates to
|
||||
// lib/time.instant when lib/time and lib/fs ship. Same byte layout
|
||||
// (i64+i64 = 16B) so a future migration is field-rename only.
|
||||
export type timespec = struct {
|
||||
sec: i64,
|
||||
nsec: i64,
|
||||
};
|
||||
|
||||
// filestat — Hare's fs::filestat (ref/hare/fs/types.ha:141). 80
|
||||
// bytes. See module-header note re: graduation to lib/fs.
|
||||
export type filestat = struct {
|
||||
mask: stat_mask, // 0 (4)
|
||||
mode: mode, // 4 (4)
|
||||
uid: u32, // 8 (4)
|
||||
gid: u32, // 12 (4)
|
||||
sz: u64, // 16 (8)
|
||||
inode: u64, // 24 (8)
|
||||
atime: timespec, // 32 (16)
|
||||
mtime: timespec, // 48 (16)
|
||||
ctime: timespec, // 64 (16) — ends at 80
|
||||
};
|
||||
|
||||
// kstat — x86_64 kernel `struct stat` layout. Mirrors
|
||||
// arch/x86/include/uapi/asm/stat.h (`__kernel_ulong_t`-keyed
|
||||
// fields). 144 bytes. Module-internal; SYS_newfstatat writes into
|
||||
// this buffer and the public stat fns then copy the bits into the
|
||||
// Hare-shaped [[filestat]].
|
||||
//
|
||||
// Mode is typed as the public [[mode]] enum (rather than raw u32)
|
||||
// so [[fillfilestat]]'s `out.mode = k.mode` needs no cast. Cstage
|
||||
// emits a redundant `MOVL AX, AX` on u32 → enum-u32 casts that
|
||||
// wwstage skips (task #25); the in-tree shape sidesteps it.
|
||||
// Identical byte layout (both 4B at offset 24).
|
||||
type kstat = struct {
|
||||
dev: u64, // 0
|
||||
ino: u64, // 8
|
||||
nlink: u64, // 16
|
||||
mode: mode, // 24
|
||||
uid: u32, // 28
|
||||
gid: u32, // 32
|
||||
pad0: u32, // 36
|
||||
rdev: u64, // 40
|
||||
sz: i64, // 48
|
||||
blksize: i64, // 56
|
||||
blocks: i64, // 64
|
||||
atime_sec: i64, // 72
|
||||
atime_nsec: i64, // 80
|
||||
mtime_sec: i64, // 88
|
||||
mtime_nsec: i64, // 96
|
||||
ctime_sec: i64, // 104
|
||||
ctime_nsec: i64, // 112
|
||||
unused0: i64, // 120
|
||||
unused1: i64, // 128
|
||||
unused2: i64, // 136 — ends at 144
|
||||
};
|
||||
|
||||
// emptypath — single-NUL byte used as the `pathname` arg to
|
||||
// newfstatat with AT_EMPTY_PATH. The kernel requires a non-NULL
|
||||
// pointer to a zero-length C string, NOT a null pointer. Bytes are
|
||||
// read-only from the kernel's view; ww has no module-level const so
|
||||
// this is a writable `let`.
|
||||
let emptypath: [1]u8 = [0u8];
|
||||
|
||||
// fillfilestat — copy a 144B kstat into the 80B Hare-shaped
|
||||
// filestat. Internal helper used by all three public entry points.
|
||||
// Mirrors Hare's st_to_filestat (ref/hare/os/+linux/dirfdfs.ha:259):
|
||||
// newfstatat populates every field, so the mask is the OR-fold of
|
||||
// all seven Hare stat_mask bits.
|
||||
fn fillfilestat(out: *filestat, k: *kstat) void = {
|
||||
out.mask = stat_mask.UID | stat_mask.GID | stat_mask.SIZE
|
||||
| stat_mask.INODE | stat_mask.ATIME | stat_mask.MTIME
|
||||
| stat_mask.CTIME;
|
||||
out.mode = k.mode;
|
||||
out.uid = k.uid;
|
||||
out.gid = k.gid;
|
||||
out.sz = k.sz: u64;
|
||||
out.inode = k.ino;
|
||||
out.atime.sec = k.atime_sec;
|
||||
out.atime.nsec = k.atime_nsec;
|
||||
out.mtime.sec = k.mtime_sec;
|
||||
out.mtime.nsec = k.mtime_nsec;
|
||||
out.ctime.sec = k.ctime_sec;
|
||||
out.ctime.nsec = k.ctime_nsec;
|
||||
};
|
||||
|
||||
// stat — fill *out with metadata for `path`. Follows symlinks.
|
||||
// `path` must be NUL-terminated (lib/os convention; see task #23
|
||||
// for a planned `path: str` migration).
|
||||
//
|
||||
// Mirrors Hare's sys::stat (ref/hare/sys/+linux/stat.ha:51) modulo
|
||||
// the out-param shape forced by the cgreturn 24B cap. Note: Hare's
|
||||
// higher-level fs::stat (ref/hare/fs/fs.ha:172) instead has lstat
|
||||
// semantics — we follow sys::stat's POSIX-stat behavior here.
|
||||
export fn stat(out: *filestat, path: *u8) (void | oserror) = {
|
||||
let k: kstat;
|
||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
||||
at.FDCWD: i64, path: i64, (&k): i64, 0i64);
|
||||
if (r < 0) { return r: oserror; };
|
||||
fillfilestat(out, &k);
|
||||
};
|
||||
|
||||
// lstat — like [[stat]] but does NOT follow a terminal symlink.
|
||||
// Mirrors Hare's sys::lstat (ref/hare/sys/+linux/stat.ha:57).
|
||||
export fn lstat(out: *filestat, path: *u8) (void | oserror) = {
|
||||
let k: kstat;
|
||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
||||
at.FDCWD: i64, path: i64, (&k): i64,
|
||||
at.SYMLINK_NOFOLLOW: i64);
|
||||
if (r < 0) { return r: oserror; };
|
||||
fillfilestat(out, &k);
|
||||
};
|
||||
|
||||
// fstat — like [[stat]] but addresses the file by fd. Uses
|
||||
// newfstatat(fd, "", AT_EMPTY_PATH); the kernel resolves the fd
|
||||
// directly. Mirrors Hare's sys::fstat (ref/hare/sys/+linux/stat.ha:54).
|
||||
export fn fstat(out: *filestat, fd: i32) (void | oserror) = {
|
||||
let k: kstat;
|
||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
||||
fd: i64, (&emptypath[0]): i64, (&k): i64,
|
||||
at.EMPTY_PATH: i64);
|
||||
if (r < 0) { return r: oserror; };
|
||||
fillfilestat(out, &k);
|
||||
};
|
||||
|
||||
// exists — true if `path` resolves to anything (regular file,
|
||||
// directory, symlink, ...). Stat-shaped (Hare's `fs::exists`,
|
||||
// ref/hare/fs/fs.ha:196) — no separate syscall. Symlinks are
|
||||
// followed; a dangling symlink is `false`.
|
||||
//
|
||||
// Race warning: prefer "open and handle the error" over "exists
|
||||
// then open" in real code (Hare's docstring carries the same
|
||||
// note). The race is unavoidable in this shape.
|
||||
//
|
||||
// Goes through SYS_newfstatat directly rather than match'ing on
|
||||
// [[stat]]'s `(void | oserror)` return. Functionally identical;
|
||||
// the direct shape sidesteps a cstage/wwstage cgen disagreement
|
||||
// on the slot size of `(void | oserror)` (cstage 16B, wwstage 24B
|
||||
// — same class as STATUS #22, surfaced first time a match on this
|
||||
// shape combined with an 80B local-struct local frame). Use the
|
||||
// match shape once #22 lands.
|
||||
export fn exists(path: *u8) bool = {
|
||||
let k: kstat;
|
||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
||||
at.FDCWD: i64, path: i64, (&k): i64, 0i64);
|
||||
return r >= 0i64;
|
||||
};
|
||||
|
||||
// MODULE: wcc
|
||||
// selfhost/cmd/wcc/mem.ww — port of cmd/wcc/mem.c.
|
||||
//
|
||||
|
||||
@@ -62,6 +62,7 @@ type nr = enum i64 {
|
||||
UNLINK = 87,
|
||||
GETCWD = 79,
|
||||
GETDENTS64 = 217,
|
||||
NEWFSTATAT = 262,
|
||||
};
|
||||
|
||||
// open(2) flags. Linux values, matching <fcntl.h>. Hare names them
|
||||
@@ -367,6 +368,248 @@ export fn getenv(name: str) (str | void) = {
|
||||
return;
|
||||
};
|
||||
|
||||
// ---- stat / lstat / fstat / exists -----------------------------------
|
||||
//
|
||||
// Ports of Hare's stat family (ref/hare/fs/fs.ha:172,196 +
|
||||
// ref/hare/sys/+linux/stat.ha:24-58). The Hare surface returns
|
||||
// `filestat` by value; ww's cgreturn ABI tops out at 24B today (see
|
||||
// STATUS task #21) and filestat is 80B, so [[stat]] / [[lstat]] /
|
||||
// [[fstat]] take an out-parameter and return `(void | oserror)`.
|
||||
// Re-evaluate the by-value shape when full sret lands.
|
||||
//
|
||||
// `filestat`, `mode`, and `stat_mask` live in lib/os because ww has
|
||||
// no lib/fs yet; Hare puts them in `fs::`. These types graduate to
|
||||
// lib/fs when that module ships — callers should expect a future
|
||||
// re-export.
|
||||
//
|
||||
// Underlying syscall is SYS_newfstatat (262), which unifies
|
||||
// stat/lstat/fstat through the `dirfd + flags` triple:
|
||||
// stat = newfstatat(AT_FDCWD, path, 0)
|
||||
// lstat = newfstatat(AT_FDCWD, path, AT_SYMLINK_NOFOLLOW)
|
||||
// fstat = newfstatat(fd, "", AT_EMPTY_PATH)
|
||||
// Avoiding SYS_statx — its 256B variable layout would buy btime,
|
||||
// but Hare's filestat doesn't expose btime either, so we stay on
|
||||
// the simpler 144B kernel struct.
|
||||
|
||||
// at — flags for fstatat(2). Numeric values match <fcntl.h>.
|
||||
// Bundled as an enum because top-level `def` of a negative literal
|
||||
// (FDCWD = -100) doesn't get a DATA slot emitted by cstage cgen —
|
||||
// linker surfaces as "undefined reference". Tracked as task #24;
|
||||
// enum-member resolution sidesteps it cleanly.
|
||||
type at = enum i64 {
|
||||
FDCWD = -100,
|
||||
SYMLINK_NOFOLLOW = 256, // 0x100
|
||||
EMPTY_PATH = 4096, // 0x1000
|
||||
};
|
||||
|
||||
// mode — file-mode bits. Mirrors Hare's fs::mode (ref/hare/fs/
|
||||
// types.ha:63). Permission bits are the standard Unix octal subset;
|
||||
// type bits live in the S_IFMT = 0o170000 region. Type-bit test:
|
||||
//
|
||||
// let t: u32 = (fi.mode as u32) & 61440u32; // 0o170000 mask
|
||||
// if (t == os.mode.DIR as u32) { /* directory */ };
|
||||
//
|
||||
// Numeric values are octal in Hare's source; ww has no octal
|
||||
// literals so they're written as decimal with the octal in a
|
||||
// trailing comment.
|
||||
export type mode = enum u32 {
|
||||
// permission bits
|
||||
USER_RWX = 448u32, // 0o700
|
||||
USER_RW = 384u32, // 0o600
|
||||
USER_RX = 320u32, // 0o500
|
||||
USER_R = 256u32, // 0o400
|
||||
USER_W = 128u32, // 0o200
|
||||
USER_X = 64u32, // 0o100
|
||||
GROUP_RWX = 56u32, // 0o070
|
||||
GROUP_RW = 48u32, // 0o060
|
||||
GROUP_RX = 40u32, // 0o050
|
||||
GROUP_R = 32u32, // 0o040
|
||||
GROUP_W = 16u32, // 0o020
|
||||
GROUP_X = 8u32, // 0o010
|
||||
OTHER_RWX = 7u32, // 0o007
|
||||
OTHER_RW = 6u32, // 0o006
|
||||
OTHER_RX = 5u32, // 0o005
|
||||
OTHER_R = 4u32, // 0o004
|
||||
OTHER_W = 2u32, // 0o002
|
||||
OTHER_X = 1u32, // 0o001
|
||||
SETUID = 2048u32, // 0o4000
|
||||
SETGID = 1024u32, // 0o2000
|
||||
STICKY = 512u32, // 0o1000
|
||||
// file-type bits (S_IFMT mask = 0o170000 = 61440)
|
||||
UNKNOWN = 0u32,
|
||||
FIFO = 4096u32, // 0o010000
|
||||
CHR = 8192u32, // 0o020000
|
||||
DIR = 16384u32, // 0o040000
|
||||
BLK = 24576u32, // 0o060000
|
||||
REG = 32768u32, // 0o100000
|
||||
LINK = 40960u32, // 0o120000
|
||||
SOCK = 49152u32, // 0o140000
|
||||
};
|
||||
|
||||
// stat_mask — which filestat fields the call populated. Mirrors
|
||||
// Hare's fs::stat_mask (ref/hare/fs/types.ha:129). newfstatat fills
|
||||
// every field, so [[stat]] / [[lstat]] / [[fstat]] always set all
|
||||
// seven bits OR-folded (see [[fillfilestat]]); per-bit testing is
|
||||
// the documented sparse-backend pattern (cf. Hare's fs::fs network
|
||||
// backends that only populate mtime+size).
|
||||
export type stat_mask = enum u32 {
|
||||
UID = 1u32,
|
||||
GID = 2u32,
|
||||
SIZE = 4u32,
|
||||
INODE = 8u32,
|
||||
ATIME = 16u32,
|
||||
MTIME = 32u32,
|
||||
CTIME = 64u32,
|
||||
};
|
||||
|
||||
// timespec — {sec, nsec} pair, matching Hare's time::instant
|
||||
// (ref/hare/time/types.ha). Local to lib/os; graduates to
|
||||
// lib/time.instant when lib/time and lib/fs ship. Same byte layout
|
||||
// (i64+i64 = 16B) so a future migration is field-rename only.
|
||||
export type timespec = struct {
|
||||
sec: i64,
|
||||
nsec: i64,
|
||||
};
|
||||
|
||||
// filestat — Hare's fs::filestat (ref/hare/fs/types.ha:141). 80
|
||||
// bytes. See module-header note re: graduation to lib/fs.
|
||||
export type filestat = struct {
|
||||
mask: stat_mask, // 0 (4)
|
||||
mode: mode, // 4 (4)
|
||||
uid: u32, // 8 (4)
|
||||
gid: u32, // 12 (4)
|
||||
sz: u64, // 16 (8)
|
||||
inode: u64, // 24 (8)
|
||||
atime: timespec, // 32 (16)
|
||||
mtime: timespec, // 48 (16)
|
||||
ctime: timespec, // 64 (16) — ends at 80
|
||||
};
|
||||
|
||||
// kstat — x86_64 kernel `struct stat` layout. Mirrors
|
||||
// arch/x86/include/uapi/asm/stat.h (`__kernel_ulong_t`-keyed
|
||||
// fields). 144 bytes. Module-internal; SYS_newfstatat writes into
|
||||
// this buffer and the public stat fns then copy the bits into the
|
||||
// Hare-shaped [[filestat]].
|
||||
//
|
||||
// Mode is typed as the public [[mode]] enum (rather than raw u32)
|
||||
// so [[fillfilestat]]'s `out.mode = k.mode` needs no cast. Cstage
|
||||
// emits a redundant `MOVL AX, AX` on u32 → enum-u32 casts that
|
||||
// wwstage skips (task #25); the in-tree shape sidesteps it.
|
||||
// Identical byte layout (both 4B at offset 24).
|
||||
type kstat = struct {
|
||||
dev: u64, // 0
|
||||
ino: u64, // 8
|
||||
nlink: u64, // 16
|
||||
mode: mode, // 24
|
||||
uid: u32, // 28
|
||||
gid: u32, // 32
|
||||
pad0: u32, // 36
|
||||
rdev: u64, // 40
|
||||
sz: i64, // 48
|
||||
blksize: i64, // 56
|
||||
blocks: i64, // 64
|
||||
atime_sec: i64, // 72
|
||||
atime_nsec: i64, // 80
|
||||
mtime_sec: i64, // 88
|
||||
mtime_nsec: i64, // 96
|
||||
ctime_sec: i64, // 104
|
||||
ctime_nsec: i64, // 112
|
||||
unused0: i64, // 120
|
||||
unused1: i64, // 128
|
||||
unused2: i64, // 136 — ends at 144
|
||||
};
|
||||
|
||||
// emptypath — single-NUL byte used as the `pathname` arg to
|
||||
// newfstatat with AT_EMPTY_PATH. The kernel requires a non-NULL
|
||||
// pointer to a zero-length C string, NOT a null pointer. Bytes are
|
||||
// read-only from the kernel's view; ww has no module-level const so
|
||||
// this is a writable `let`.
|
||||
let emptypath: [1]u8 = [0u8];
|
||||
|
||||
// fillfilestat — copy a 144B kstat into the 80B Hare-shaped
|
||||
// filestat. Internal helper used by all three public entry points.
|
||||
// Mirrors Hare's st_to_filestat (ref/hare/os/+linux/dirfdfs.ha:259):
|
||||
// newfstatat populates every field, so the mask is the OR-fold of
|
||||
// all seven Hare stat_mask bits.
|
||||
fn fillfilestat(out: *filestat, k: *kstat) void = {
|
||||
out.mask = stat_mask.UID | stat_mask.GID | stat_mask.SIZE
|
||||
| stat_mask.INODE | stat_mask.ATIME | stat_mask.MTIME
|
||||
| stat_mask.CTIME;
|
||||
out.mode = k.mode;
|
||||
out.uid = k.uid;
|
||||
out.gid = k.gid;
|
||||
out.sz = k.sz: u64;
|
||||
out.inode = k.ino;
|
||||
out.atime.sec = k.atime_sec;
|
||||
out.atime.nsec = k.atime_nsec;
|
||||
out.mtime.sec = k.mtime_sec;
|
||||
out.mtime.nsec = k.mtime_nsec;
|
||||
out.ctime.sec = k.ctime_sec;
|
||||
out.ctime.nsec = k.ctime_nsec;
|
||||
};
|
||||
|
||||
// stat — fill *out with metadata for `path`. Follows symlinks.
|
||||
// `path` must be NUL-terminated (lib/os convention; see task #23
|
||||
// for a planned `path: str` migration).
|
||||
//
|
||||
// Mirrors Hare's sys::stat (ref/hare/sys/+linux/stat.ha:51) modulo
|
||||
// the out-param shape forced by the cgreturn 24B cap. Note: Hare's
|
||||
// higher-level fs::stat (ref/hare/fs/fs.ha:172) instead has lstat
|
||||
// semantics — we follow sys::stat's POSIX-stat behavior here.
|
||||
export fn stat(out: *filestat, path: *u8) (void | oserror) = {
|
||||
let k: kstat;
|
||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
||||
at.FDCWD: i64, path: i64, (&k): i64, 0i64);
|
||||
if (r < 0) { return r: oserror; };
|
||||
fillfilestat(out, &k);
|
||||
};
|
||||
|
||||
// lstat — like [[stat]] but does NOT follow a terminal symlink.
|
||||
// Mirrors Hare's sys::lstat (ref/hare/sys/+linux/stat.ha:57).
|
||||
export fn lstat(out: *filestat, path: *u8) (void | oserror) = {
|
||||
let k: kstat;
|
||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
||||
at.FDCWD: i64, path: i64, (&k): i64,
|
||||
at.SYMLINK_NOFOLLOW: i64);
|
||||
if (r < 0) { return r: oserror; };
|
||||
fillfilestat(out, &k);
|
||||
};
|
||||
|
||||
// fstat — like [[stat]] but addresses the file by fd. Uses
|
||||
// newfstatat(fd, "", AT_EMPTY_PATH); the kernel resolves the fd
|
||||
// directly. Mirrors Hare's sys::fstat (ref/hare/sys/+linux/stat.ha:54).
|
||||
export fn fstat(out: *filestat, fd: i32) (void | oserror) = {
|
||||
let k: kstat;
|
||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
||||
fd: i64, (&emptypath[0]): i64, (&k): i64,
|
||||
at.EMPTY_PATH: i64);
|
||||
if (r < 0) { return r: oserror; };
|
||||
fillfilestat(out, &k);
|
||||
};
|
||||
|
||||
// exists — true if `path` resolves to anything (regular file,
|
||||
// directory, symlink, ...). Stat-shaped (Hare's `fs::exists`,
|
||||
// ref/hare/fs/fs.ha:196) — no separate syscall. Symlinks are
|
||||
// followed; a dangling symlink is `false`.
|
||||
//
|
||||
// Race warning: prefer "open and handle the error" over "exists
|
||||
// then open" in real code (Hare's docstring carries the same
|
||||
// note). The race is unavoidable in this shape.
|
||||
//
|
||||
// Goes through SYS_newfstatat directly rather than match'ing on
|
||||
// [[stat]]'s `(void | oserror)` return. Functionally identical;
|
||||
// the direct shape sidesteps a cstage/wwstage cgen disagreement
|
||||
// on the slot size of `(void | oserror)` (cstage 16B, wwstage 24B
|
||||
// — same class as STATUS #22, surfaced first time a match on this
|
||||
// shape combined with an 80B local-struct local frame). Use the
|
||||
// match shape once #22 lands.
|
||||
export fn exists(path: *u8) bool = {
|
||||
let k: kstat;
|
||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
||||
at.FDCWD: i64, path: i64, (&k): i64, 0i64);
|
||||
return r >= 0i64;
|
||||
};
|
||||
|
||||
// MODULE: wcc
|
||||
// selfhost/cmd/wcc/mem.ww — port of cmd/wcc/mem.c.
|
||||
//
|
||||
|
||||
@@ -62,6 +62,7 @@ type nr = enum i64 {
|
||||
UNLINK = 87,
|
||||
GETCWD = 79,
|
||||
GETDENTS64 = 217,
|
||||
NEWFSTATAT = 262,
|
||||
};
|
||||
|
||||
// open(2) flags. Linux values, matching <fcntl.h>. Hare names them
|
||||
@@ -367,6 +368,248 @@ export fn getenv(name: str) (str | void) = {
|
||||
return;
|
||||
};
|
||||
|
||||
// ---- stat / lstat / fstat / exists -----------------------------------
|
||||
//
|
||||
// Ports of Hare's stat family (ref/hare/fs/fs.ha:172,196 +
|
||||
// ref/hare/sys/+linux/stat.ha:24-58). The Hare surface returns
|
||||
// `filestat` by value; ww's cgreturn ABI tops out at 24B today (see
|
||||
// STATUS task #21) and filestat is 80B, so [[stat]] / [[lstat]] /
|
||||
// [[fstat]] take an out-parameter and return `(void | oserror)`.
|
||||
// Re-evaluate the by-value shape when full sret lands.
|
||||
//
|
||||
// `filestat`, `mode`, and `stat_mask` live in lib/os because ww has
|
||||
// no lib/fs yet; Hare puts them in `fs::`. These types graduate to
|
||||
// lib/fs when that module ships — callers should expect a future
|
||||
// re-export.
|
||||
//
|
||||
// Underlying syscall is SYS_newfstatat (262), which unifies
|
||||
// stat/lstat/fstat through the `dirfd + flags` triple:
|
||||
// stat = newfstatat(AT_FDCWD, path, 0)
|
||||
// lstat = newfstatat(AT_FDCWD, path, AT_SYMLINK_NOFOLLOW)
|
||||
// fstat = newfstatat(fd, "", AT_EMPTY_PATH)
|
||||
// Avoiding SYS_statx — its 256B variable layout would buy btime,
|
||||
// but Hare's filestat doesn't expose btime either, so we stay on
|
||||
// the simpler 144B kernel struct.
|
||||
|
||||
// at — flags for fstatat(2). Numeric values match <fcntl.h>.
|
||||
// Bundled as an enum because top-level `def` of a negative literal
|
||||
// (FDCWD = -100) doesn't get a DATA slot emitted by cstage cgen —
|
||||
// linker surfaces as "undefined reference". Tracked as task #24;
|
||||
// enum-member resolution sidesteps it cleanly.
|
||||
type at = enum i64 {
|
||||
FDCWD = -100,
|
||||
SYMLINK_NOFOLLOW = 256, // 0x100
|
||||
EMPTY_PATH = 4096, // 0x1000
|
||||
};
|
||||
|
||||
// mode — file-mode bits. Mirrors Hare's fs::mode (ref/hare/fs/
|
||||
// types.ha:63). Permission bits are the standard Unix octal subset;
|
||||
// type bits live in the S_IFMT = 0o170000 region. Type-bit test:
|
||||
//
|
||||
// let t: u32 = (fi.mode as u32) & 61440u32; // 0o170000 mask
|
||||
// if (t == os.mode.DIR as u32) { /* directory */ };
|
||||
//
|
||||
// Numeric values are octal in Hare's source; ww has no octal
|
||||
// literals so they're written as decimal with the octal in a
|
||||
// trailing comment.
|
||||
export type mode = enum u32 {
|
||||
// permission bits
|
||||
USER_RWX = 448u32, // 0o700
|
||||
USER_RW = 384u32, // 0o600
|
||||
USER_RX = 320u32, // 0o500
|
||||
USER_R = 256u32, // 0o400
|
||||
USER_W = 128u32, // 0o200
|
||||
USER_X = 64u32, // 0o100
|
||||
GROUP_RWX = 56u32, // 0o070
|
||||
GROUP_RW = 48u32, // 0o060
|
||||
GROUP_RX = 40u32, // 0o050
|
||||
GROUP_R = 32u32, // 0o040
|
||||
GROUP_W = 16u32, // 0o020
|
||||
GROUP_X = 8u32, // 0o010
|
||||
OTHER_RWX = 7u32, // 0o007
|
||||
OTHER_RW = 6u32, // 0o006
|
||||
OTHER_RX = 5u32, // 0o005
|
||||
OTHER_R = 4u32, // 0o004
|
||||
OTHER_W = 2u32, // 0o002
|
||||
OTHER_X = 1u32, // 0o001
|
||||
SETUID = 2048u32, // 0o4000
|
||||
SETGID = 1024u32, // 0o2000
|
||||
STICKY = 512u32, // 0o1000
|
||||
// file-type bits (S_IFMT mask = 0o170000 = 61440)
|
||||
UNKNOWN = 0u32,
|
||||
FIFO = 4096u32, // 0o010000
|
||||
CHR = 8192u32, // 0o020000
|
||||
DIR = 16384u32, // 0o040000
|
||||
BLK = 24576u32, // 0o060000
|
||||
REG = 32768u32, // 0o100000
|
||||
LINK = 40960u32, // 0o120000
|
||||
SOCK = 49152u32, // 0o140000
|
||||
};
|
||||
|
||||
// stat_mask — which filestat fields the call populated. Mirrors
|
||||
// Hare's fs::stat_mask (ref/hare/fs/types.ha:129). newfstatat fills
|
||||
// every field, so [[stat]] / [[lstat]] / [[fstat]] always set all
|
||||
// seven bits OR-folded (see [[fillfilestat]]); per-bit testing is
|
||||
// the documented sparse-backend pattern (cf. Hare's fs::fs network
|
||||
// backends that only populate mtime+size).
|
||||
export type stat_mask = enum u32 {
|
||||
UID = 1u32,
|
||||
GID = 2u32,
|
||||
SIZE = 4u32,
|
||||
INODE = 8u32,
|
||||
ATIME = 16u32,
|
||||
MTIME = 32u32,
|
||||
CTIME = 64u32,
|
||||
};
|
||||
|
||||
// timespec — {sec, nsec} pair, matching Hare's time::instant
|
||||
// (ref/hare/time/types.ha). Local to lib/os; graduates to
|
||||
// lib/time.instant when lib/time and lib/fs ship. Same byte layout
|
||||
// (i64+i64 = 16B) so a future migration is field-rename only.
|
||||
export type timespec = struct {
|
||||
sec: i64,
|
||||
nsec: i64,
|
||||
};
|
||||
|
||||
// filestat — Hare's fs::filestat (ref/hare/fs/types.ha:141). 80
|
||||
// bytes. See module-header note re: graduation to lib/fs.
|
||||
export type filestat = struct {
|
||||
mask: stat_mask, // 0 (4)
|
||||
mode: mode, // 4 (4)
|
||||
uid: u32, // 8 (4)
|
||||
gid: u32, // 12 (4)
|
||||
sz: u64, // 16 (8)
|
||||
inode: u64, // 24 (8)
|
||||
atime: timespec, // 32 (16)
|
||||
mtime: timespec, // 48 (16)
|
||||
ctime: timespec, // 64 (16) — ends at 80
|
||||
};
|
||||
|
||||
// kstat — x86_64 kernel `struct stat` layout. Mirrors
|
||||
// arch/x86/include/uapi/asm/stat.h (`__kernel_ulong_t`-keyed
|
||||
// fields). 144 bytes. Module-internal; SYS_newfstatat writes into
|
||||
// this buffer and the public stat fns then copy the bits into the
|
||||
// Hare-shaped [[filestat]].
|
||||
//
|
||||
// Mode is typed as the public [[mode]] enum (rather than raw u32)
|
||||
// so [[fillfilestat]]'s `out.mode = k.mode` needs no cast. Cstage
|
||||
// emits a redundant `MOVL AX, AX` on u32 → enum-u32 casts that
|
||||
// wwstage skips (task #25); the in-tree shape sidesteps it.
|
||||
// Identical byte layout (both 4B at offset 24).
|
||||
type kstat = struct {
|
||||
dev: u64, // 0
|
||||
ino: u64, // 8
|
||||
nlink: u64, // 16
|
||||
mode: mode, // 24
|
||||
uid: u32, // 28
|
||||
gid: u32, // 32
|
||||
pad0: u32, // 36
|
||||
rdev: u64, // 40
|
||||
sz: i64, // 48
|
||||
blksize: i64, // 56
|
||||
blocks: i64, // 64
|
||||
atime_sec: i64, // 72
|
||||
atime_nsec: i64, // 80
|
||||
mtime_sec: i64, // 88
|
||||
mtime_nsec: i64, // 96
|
||||
ctime_sec: i64, // 104
|
||||
ctime_nsec: i64, // 112
|
||||
unused0: i64, // 120
|
||||
unused1: i64, // 128
|
||||
unused2: i64, // 136 — ends at 144
|
||||
};
|
||||
|
||||
// emptypath — single-NUL byte used as the `pathname` arg to
|
||||
// newfstatat with AT_EMPTY_PATH. The kernel requires a non-NULL
|
||||
// pointer to a zero-length C string, NOT a null pointer. Bytes are
|
||||
// read-only from the kernel's view; ww has no module-level const so
|
||||
// this is a writable `let`.
|
||||
let emptypath: [1]u8 = [0u8];
|
||||
|
||||
// fillfilestat — copy a 144B kstat into the 80B Hare-shaped
|
||||
// filestat. Internal helper used by all three public entry points.
|
||||
// Mirrors Hare's st_to_filestat (ref/hare/os/+linux/dirfdfs.ha:259):
|
||||
// newfstatat populates every field, so the mask is the OR-fold of
|
||||
// all seven Hare stat_mask bits.
|
||||
fn fillfilestat(out: *filestat, k: *kstat) void = {
|
||||
out.mask = stat_mask.UID | stat_mask.GID | stat_mask.SIZE
|
||||
| stat_mask.INODE | stat_mask.ATIME | stat_mask.MTIME
|
||||
| stat_mask.CTIME;
|
||||
out.mode = k.mode;
|
||||
out.uid = k.uid;
|
||||
out.gid = k.gid;
|
||||
out.sz = k.sz: u64;
|
||||
out.inode = k.ino;
|
||||
out.atime.sec = k.atime_sec;
|
||||
out.atime.nsec = k.atime_nsec;
|
||||
out.mtime.sec = k.mtime_sec;
|
||||
out.mtime.nsec = k.mtime_nsec;
|
||||
out.ctime.sec = k.ctime_sec;
|
||||
out.ctime.nsec = k.ctime_nsec;
|
||||
};
|
||||
|
||||
// stat — fill *out with metadata for `path`. Follows symlinks.
|
||||
// `path` must be NUL-terminated (lib/os convention; see task #23
|
||||
// for a planned `path: str` migration).
|
||||
//
|
||||
// Mirrors Hare's sys::stat (ref/hare/sys/+linux/stat.ha:51) modulo
|
||||
// the out-param shape forced by the cgreturn 24B cap. Note: Hare's
|
||||
// higher-level fs::stat (ref/hare/fs/fs.ha:172) instead has lstat
|
||||
// semantics — we follow sys::stat's POSIX-stat behavior here.
|
||||
export fn stat(out: *filestat, path: *u8) (void | oserror) = {
|
||||
let k: kstat;
|
||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
||||
at.FDCWD: i64, path: i64, (&k): i64, 0i64);
|
||||
if (r < 0) { return r: oserror; };
|
||||
fillfilestat(out, &k);
|
||||
};
|
||||
|
||||
// lstat — like [[stat]] but does NOT follow a terminal symlink.
|
||||
// Mirrors Hare's sys::lstat (ref/hare/sys/+linux/stat.ha:57).
|
||||
export fn lstat(out: *filestat, path: *u8) (void | oserror) = {
|
||||
let k: kstat;
|
||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
||||
at.FDCWD: i64, path: i64, (&k): i64,
|
||||
at.SYMLINK_NOFOLLOW: i64);
|
||||
if (r < 0) { return r: oserror; };
|
||||
fillfilestat(out, &k);
|
||||
};
|
||||
|
||||
// fstat — like [[stat]] but addresses the file by fd. Uses
|
||||
// newfstatat(fd, "", AT_EMPTY_PATH); the kernel resolves the fd
|
||||
// directly. Mirrors Hare's sys::fstat (ref/hare/sys/+linux/stat.ha:54).
|
||||
export fn fstat(out: *filestat, fd: i32) (void | oserror) = {
|
||||
let k: kstat;
|
||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
||||
fd: i64, (&emptypath[0]): i64, (&k): i64,
|
||||
at.EMPTY_PATH: i64);
|
||||
if (r < 0) { return r: oserror; };
|
||||
fillfilestat(out, &k);
|
||||
};
|
||||
|
||||
// exists — true if `path` resolves to anything (regular file,
|
||||
// directory, symlink, ...). Stat-shaped (Hare's `fs::exists`,
|
||||
// ref/hare/fs/fs.ha:196) — no separate syscall. Symlinks are
|
||||
// followed; a dangling symlink is `false`.
|
||||
//
|
||||
// Race warning: prefer "open and handle the error" over "exists
|
||||
// then open" in real code (Hare's docstring carries the same
|
||||
// note). The race is unavoidable in this shape.
|
||||
//
|
||||
// Goes through SYS_newfstatat directly rather than match'ing on
|
||||
// [[stat]]'s `(void | oserror)` return. Functionally identical;
|
||||
// the direct shape sidesteps a cstage/wwstage cgen disagreement
|
||||
// on the slot size of `(void | oserror)` (cstage 16B, wwstage 24B
|
||||
// — same class as STATUS #22, surfaced first time a match on this
|
||||
// shape combined with an 80B local-struct local frame). Use the
|
||||
// match shape once #22 lands.
|
||||
export fn exists(path: *u8) bool = {
|
||||
let k: kstat;
|
||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
||||
at.FDCWD: i64, path: i64, (&k): i64, 0i64);
|
||||
return r >= 0i64;
|
||||
};
|
||||
|
||||
// MODULE: wcc
|
||||
// selfhost/cmd/wcc/mem.ww — port of cmd/wcc/mem.c.
|
||||
//
|
||||
|
||||
@@ -62,6 +62,7 @@ type nr = enum i64 {
|
||||
UNLINK = 87,
|
||||
GETCWD = 79,
|
||||
GETDENTS64 = 217,
|
||||
NEWFSTATAT = 262,
|
||||
};
|
||||
|
||||
// open(2) flags. Linux values, matching <fcntl.h>. Hare names them
|
||||
@@ -367,6 +368,248 @@ export fn getenv(name: str) (str | void) = {
|
||||
return;
|
||||
};
|
||||
|
||||
// ---- stat / lstat / fstat / exists -----------------------------------
|
||||
//
|
||||
// Ports of Hare's stat family (ref/hare/fs/fs.ha:172,196 +
|
||||
// ref/hare/sys/+linux/stat.ha:24-58). The Hare surface returns
|
||||
// `filestat` by value; ww's cgreturn ABI tops out at 24B today (see
|
||||
// STATUS task #21) and filestat is 80B, so [[stat]] / [[lstat]] /
|
||||
// [[fstat]] take an out-parameter and return `(void | oserror)`.
|
||||
// Re-evaluate the by-value shape when full sret lands.
|
||||
//
|
||||
// `filestat`, `mode`, and `stat_mask` live in lib/os because ww has
|
||||
// no lib/fs yet; Hare puts them in `fs::`. These types graduate to
|
||||
// lib/fs when that module ships — callers should expect a future
|
||||
// re-export.
|
||||
//
|
||||
// Underlying syscall is SYS_newfstatat (262), which unifies
|
||||
// stat/lstat/fstat through the `dirfd + flags` triple:
|
||||
// stat = newfstatat(AT_FDCWD, path, 0)
|
||||
// lstat = newfstatat(AT_FDCWD, path, AT_SYMLINK_NOFOLLOW)
|
||||
// fstat = newfstatat(fd, "", AT_EMPTY_PATH)
|
||||
// Avoiding SYS_statx — its 256B variable layout would buy btime,
|
||||
// but Hare's filestat doesn't expose btime either, so we stay on
|
||||
// the simpler 144B kernel struct.
|
||||
|
||||
// at — flags for fstatat(2). Numeric values match <fcntl.h>.
|
||||
// Bundled as an enum because top-level `def` of a negative literal
|
||||
// (FDCWD = -100) doesn't get a DATA slot emitted by cstage cgen —
|
||||
// linker surfaces as "undefined reference". Tracked as task #24;
|
||||
// enum-member resolution sidesteps it cleanly.
|
||||
type at = enum i64 {
|
||||
FDCWD = -100,
|
||||
SYMLINK_NOFOLLOW = 256, // 0x100
|
||||
EMPTY_PATH = 4096, // 0x1000
|
||||
};
|
||||
|
||||
// mode — file-mode bits. Mirrors Hare's fs::mode (ref/hare/fs/
|
||||
// types.ha:63). Permission bits are the standard Unix octal subset;
|
||||
// type bits live in the S_IFMT = 0o170000 region. Type-bit test:
|
||||
//
|
||||
// let t: u32 = (fi.mode as u32) & 61440u32; // 0o170000 mask
|
||||
// if (t == os.mode.DIR as u32) { /* directory */ };
|
||||
//
|
||||
// Numeric values are octal in Hare's source; ww has no octal
|
||||
// literals so they're written as decimal with the octal in a
|
||||
// trailing comment.
|
||||
export type mode = enum u32 {
|
||||
// permission bits
|
||||
USER_RWX = 448u32, // 0o700
|
||||
USER_RW = 384u32, // 0o600
|
||||
USER_RX = 320u32, // 0o500
|
||||
USER_R = 256u32, // 0o400
|
||||
USER_W = 128u32, // 0o200
|
||||
USER_X = 64u32, // 0o100
|
||||
GROUP_RWX = 56u32, // 0o070
|
||||
GROUP_RW = 48u32, // 0o060
|
||||
GROUP_RX = 40u32, // 0o050
|
||||
GROUP_R = 32u32, // 0o040
|
||||
GROUP_W = 16u32, // 0o020
|
||||
GROUP_X = 8u32, // 0o010
|
||||
OTHER_RWX = 7u32, // 0o007
|
||||
OTHER_RW = 6u32, // 0o006
|
||||
OTHER_RX = 5u32, // 0o005
|
||||
OTHER_R = 4u32, // 0o004
|
||||
OTHER_W = 2u32, // 0o002
|
||||
OTHER_X = 1u32, // 0o001
|
||||
SETUID = 2048u32, // 0o4000
|
||||
SETGID = 1024u32, // 0o2000
|
||||
STICKY = 512u32, // 0o1000
|
||||
// file-type bits (S_IFMT mask = 0o170000 = 61440)
|
||||
UNKNOWN = 0u32,
|
||||
FIFO = 4096u32, // 0o010000
|
||||
CHR = 8192u32, // 0o020000
|
||||
DIR = 16384u32, // 0o040000
|
||||
BLK = 24576u32, // 0o060000
|
||||
REG = 32768u32, // 0o100000
|
||||
LINK = 40960u32, // 0o120000
|
||||
SOCK = 49152u32, // 0o140000
|
||||
};
|
||||
|
||||
// stat_mask — which filestat fields the call populated. Mirrors
|
||||
// Hare's fs::stat_mask (ref/hare/fs/types.ha:129). newfstatat fills
|
||||
// every field, so [[stat]] / [[lstat]] / [[fstat]] always set all
|
||||
// seven bits OR-folded (see [[fillfilestat]]); per-bit testing is
|
||||
// the documented sparse-backend pattern (cf. Hare's fs::fs network
|
||||
// backends that only populate mtime+size).
|
||||
export type stat_mask = enum u32 {
|
||||
UID = 1u32,
|
||||
GID = 2u32,
|
||||
SIZE = 4u32,
|
||||
INODE = 8u32,
|
||||
ATIME = 16u32,
|
||||
MTIME = 32u32,
|
||||
CTIME = 64u32,
|
||||
};
|
||||
|
||||
// timespec — {sec, nsec} pair, matching Hare's time::instant
|
||||
// (ref/hare/time/types.ha). Local to lib/os; graduates to
|
||||
// lib/time.instant when lib/time and lib/fs ship. Same byte layout
|
||||
// (i64+i64 = 16B) so a future migration is field-rename only.
|
||||
export type timespec = struct {
|
||||
sec: i64,
|
||||
nsec: i64,
|
||||
};
|
||||
|
||||
// filestat — Hare's fs::filestat (ref/hare/fs/types.ha:141). 80
|
||||
// bytes. See module-header note re: graduation to lib/fs.
|
||||
export type filestat = struct {
|
||||
mask: stat_mask, // 0 (4)
|
||||
mode: mode, // 4 (4)
|
||||
uid: u32, // 8 (4)
|
||||
gid: u32, // 12 (4)
|
||||
sz: u64, // 16 (8)
|
||||
inode: u64, // 24 (8)
|
||||
atime: timespec, // 32 (16)
|
||||
mtime: timespec, // 48 (16)
|
||||
ctime: timespec, // 64 (16) — ends at 80
|
||||
};
|
||||
|
||||
// kstat — x86_64 kernel `struct stat` layout. Mirrors
|
||||
// arch/x86/include/uapi/asm/stat.h (`__kernel_ulong_t`-keyed
|
||||
// fields). 144 bytes. Module-internal; SYS_newfstatat writes into
|
||||
// this buffer and the public stat fns then copy the bits into the
|
||||
// Hare-shaped [[filestat]].
|
||||
//
|
||||
// Mode is typed as the public [[mode]] enum (rather than raw u32)
|
||||
// so [[fillfilestat]]'s `out.mode = k.mode` needs no cast. Cstage
|
||||
// emits a redundant `MOVL AX, AX` on u32 → enum-u32 casts that
|
||||
// wwstage skips (task #25); the in-tree shape sidesteps it.
|
||||
// Identical byte layout (both 4B at offset 24).
|
||||
type kstat = struct {
|
||||
dev: u64, // 0
|
||||
ino: u64, // 8
|
||||
nlink: u64, // 16
|
||||
mode: mode, // 24
|
||||
uid: u32, // 28
|
||||
gid: u32, // 32
|
||||
pad0: u32, // 36
|
||||
rdev: u64, // 40
|
||||
sz: i64, // 48
|
||||
blksize: i64, // 56
|
||||
blocks: i64, // 64
|
||||
atime_sec: i64, // 72
|
||||
atime_nsec: i64, // 80
|
||||
mtime_sec: i64, // 88
|
||||
mtime_nsec: i64, // 96
|
||||
ctime_sec: i64, // 104
|
||||
ctime_nsec: i64, // 112
|
||||
unused0: i64, // 120
|
||||
unused1: i64, // 128
|
||||
unused2: i64, // 136 — ends at 144
|
||||
};
|
||||
|
||||
// emptypath — single-NUL byte used as the `pathname` arg to
|
||||
// newfstatat with AT_EMPTY_PATH. The kernel requires a non-NULL
|
||||
// pointer to a zero-length C string, NOT a null pointer. Bytes are
|
||||
// read-only from the kernel's view; ww has no module-level const so
|
||||
// this is a writable `let`.
|
||||
let emptypath: [1]u8 = [0u8];
|
||||
|
||||
// fillfilestat — copy a 144B kstat into the 80B Hare-shaped
|
||||
// filestat. Internal helper used by all three public entry points.
|
||||
// Mirrors Hare's st_to_filestat (ref/hare/os/+linux/dirfdfs.ha:259):
|
||||
// newfstatat populates every field, so the mask is the OR-fold of
|
||||
// all seven Hare stat_mask bits.
|
||||
fn fillfilestat(out: *filestat, k: *kstat) void = {
|
||||
out.mask = stat_mask.UID | stat_mask.GID | stat_mask.SIZE
|
||||
| stat_mask.INODE | stat_mask.ATIME | stat_mask.MTIME
|
||||
| stat_mask.CTIME;
|
||||
out.mode = k.mode;
|
||||
out.uid = k.uid;
|
||||
out.gid = k.gid;
|
||||
out.sz = k.sz: u64;
|
||||
out.inode = k.ino;
|
||||
out.atime.sec = k.atime_sec;
|
||||
out.atime.nsec = k.atime_nsec;
|
||||
out.mtime.sec = k.mtime_sec;
|
||||
out.mtime.nsec = k.mtime_nsec;
|
||||
out.ctime.sec = k.ctime_sec;
|
||||
out.ctime.nsec = k.ctime_nsec;
|
||||
};
|
||||
|
||||
// stat — fill *out with metadata for `path`. Follows symlinks.
|
||||
// `path` must be NUL-terminated (lib/os convention; see task #23
|
||||
// for a planned `path: str` migration).
|
||||
//
|
||||
// Mirrors Hare's sys::stat (ref/hare/sys/+linux/stat.ha:51) modulo
|
||||
// the out-param shape forced by the cgreturn 24B cap. Note: Hare's
|
||||
// higher-level fs::stat (ref/hare/fs/fs.ha:172) instead has lstat
|
||||
// semantics — we follow sys::stat's POSIX-stat behavior here.
|
||||
export fn stat(out: *filestat, path: *u8) (void | oserror) = {
|
||||
let k: kstat;
|
||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
||||
at.FDCWD: i64, path: i64, (&k): i64, 0i64);
|
||||
if (r < 0) { return r: oserror; };
|
||||
fillfilestat(out, &k);
|
||||
};
|
||||
|
||||
// lstat — like [[stat]] but does NOT follow a terminal symlink.
|
||||
// Mirrors Hare's sys::lstat (ref/hare/sys/+linux/stat.ha:57).
|
||||
export fn lstat(out: *filestat, path: *u8) (void | oserror) = {
|
||||
let k: kstat;
|
||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
||||
at.FDCWD: i64, path: i64, (&k): i64,
|
||||
at.SYMLINK_NOFOLLOW: i64);
|
||||
if (r < 0) { return r: oserror; };
|
||||
fillfilestat(out, &k);
|
||||
};
|
||||
|
||||
// fstat — like [[stat]] but addresses the file by fd. Uses
|
||||
// newfstatat(fd, "", AT_EMPTY_PATH); the kernel resolves the fd
|
||||
// directly. Mirrors Hare's sys::fstat (ref/hare/sys/+linux/stat.ha:54).
|
||||
export fn fstat(out: *filestat, fd: i32) (void | oserror) = {
|
||||
let k: kstat;
|
||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
||||
fd: i64, (&emptypath[0]): i64, (&k): i64,
|
||||
at.EMPTY_PATH: i64);
|
||||
if (r < 0) { return r: oserror; };
|
||||
fillfilestat(out, &k);
|
||||
};
|
||||
|
||||
// exists — true if `path` resolves to anything (regular file,
|
||||
// directory, symlink, ...). Stat-shaped (Hare's `fs::exists`,
|
||||
// ref/hare/fs/fs.ha:196) — no separate syscall. Symlinks are
|
||||
// followed; a dangling symlink is `false`.
|
||||
//
|
||||
// Race warning: prefer "open and handle the error" over "exists
|
||||
// then open" in real code (Hare's docstring carries the same
|
||||
// note). The race is unavoidable in this shape.
|
||||
//
|
||||
// Goes through SYS_newfstatat directly rather than match'ing on
|
||||
// [[stat]]'s `(void | oserror)` return. Functionally identical;
|
||||
// the direct shape sidesteps a cstage/wwstage cgen disagreement
|
||||
// on the slot size of `(void | oserror)` (cstage 16B, wwstage 24B
|
||||
// — same class as STATUS #22, surfaced first time a match on this
|
||||
// shape combined with an 80B local-struct local frame). Use the
|
||||
// match shape once #22 lands.
|
||||
export fn exists(path: *u8) bool = {
|
||||
let k: kstat;
|
||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
||||
at.FDCWD: i64, path: i64, (&k): i64, 0i64);
|
||||
return r >= 0i64;
|
||||
};
|
||||
|
||||
// MODULE: wcc
|
||||
// selfhost/cmd/wcc/mem.ww — port of cmd/wcc/mem.c.
|
||||
//
|
||||
|
||||
@@ -62,6 +62,7 @@ type nr = enum i64 {
|
||||
UNLINK = 87,
|
||||
GETCWD = 79,
|
||||
GETDENTS64 = 217,
|
||||
NEWFSTATAT = 262,
|
||||
};
|
||||
|
||||
// open(2) flags. Linux values, matching <fcntl.h>. Hare names them
|
||||
@@ -367,6 +368,248 @@ export fn getenv(name: str) (str | void) = {
|
||||
return;
|
||||
};
|
||||
|
||||
// ---- stat / lstat / fstat / exists -----------------------------------
|
||||
//
|
||||
// Ports of Hare's stat family (ref/hare/fs/fs.ha:172,196 +
|
||||
// ref/hare/sys/+linux/stat.ha:24-58). The Hare surface returns
|
||||
// `filestat` by value; ww's cgreturn ABI tops out at 24B today (see
|
||||
// STATUS task #21) and filestat is 80B, so [[stat]] / [[lstat]] /
|
||||
// [[fstat]] take an out-parameter and return `(void | oserror)`.
|
||||
// Re-evaluate the by-value shape when full sret lands.
|
||||
//
|
||||
// `filestat`, `mode`, and `stat_mask` live in lib/os because ww has
|
||||
// no lib/fs yet; Hare puts them in `fs::`. These types graduate to
|
||||
// lib/fs when that module ships — callers should expect a future
|
||||
// re-export.
|
||||
//
|
||||
// Underlying syscall is SYS_newfstatat (262), which unifies
|
||||
// stat/lstat/fstat through the `dirfd + flags` triple:
|
||||
// stat = newfstatat(AT_FDCWD, path, 0)
|
||||
// lstat = newfstatat(AT_FDCWD, path, AT_SYMLINK_NOFOLLOW)
|
||||
// fstat = newfstatat(fd, "", AT_EMPTY_PATH)
|
||||
// Avoiding SYS_statx — its 256B variable layout would buy btime,
|
||||
// but Hare's filestat doesn't expose btime either, so we stay on
|
||||
// the simpler 144B kernel struct.
|
||||
|
||||
// at — flags for fstatat(2). Numeric values match <fcntl.h>.
|
||||
// Bundled as an enum because top-level `def` of a negative literal
|
||||
// (FDCWD = -100) doesn't get a DATA slot emitted by cstage cgen —
|
||||
// linker surfaces as "undefined reference". Tracked as task #24;
|
||||
// enum-member resolution sidesteps it cleanly.
|
||||
type at = enum i64 {
|
||||
FDCWD = -100,
|
||||
SYMLINK_NOFOLLOW = 256, // 0x100
|
||||
EMPTY_PATH = 4096, // 0x1000
|
||||
};
|
||||
|
||||
// mode — file-mode bits. Mirrors Hare's fs::mode (ref/hare/fs/
|
||||
// types.ha:63). Permission bits are the standard Unix octal subset;
|
||||
// type bits live in the S_IFMT = 0o170000 region. Type-bit test:
|
||||
//
|
||||
// let t: u32 = (fi.mode as u32) & 61440u32; // 0o170000 mask
|
||||
// if (t == os.mode.DIR as u32) { /* directory */ };
|
||||
//
|
||||
// Numeric values are octal in Hare's source; ww has no octal
|
||||
// literals so they're written as decimal with the octal in a
|
||||
// trailing comment.
|
||||
export type mode = enum u32 {
|
||||
// permission bits
|
||||
USER_RWX = 448u32, // 0o700
|
||||
USER_RW = 384u32, // 0o600
|
||||
USER_RX = 320u32, // 0o500
|
||||
USER_R = 256u32, // 0o400
|
||||
USER_W = 128u32, // 0o200
|
||||
USER_X = 64u32, // 0o100
|
||||
GROUP_RWX = 56u32, // 0o070
|
||||
GROUP_RW = 48u32, // 0o060
|
||||
GROUP_RX = 40u32, // 0o050
|
||||
GROUP_R = 32u32, // 0o040
|
||||
GROUP_W = 16u32, // 0o020
|
||||
GROUP_X = 8u32, // 0o010
|
||||
OTHER_RWX = 7u32, // 0o007
|
||||
OTHER_RW = 6u32, // 0o006
|
||||
OTHER_RX = 5u32, // 0o005
|
||||
OTHER_R = 4u32, // 0o004
|
||||
OTHER_W = 2u32, // 0o002
|
||||
OTHER_X = 1u32, // 0o001
|
||||
SETUID = 2048u32, // 0o4000
|
||||
SETGID = 1024u32, // 0o2000
|
||||
STICKY = 512u32, // 0o1000
|
||||
// file-type bits (S_IFMT mask = 0o170000 = 61440)
|
||||
UNKNOWN = 0u32,
|
||||
FIFO = 4096u32, // 0o010000
|
||||
CHR = 8192u32, // 0o020000
|
||||
DIR = 16384u32, // 0o040000
|
||||
BLK = 24576u32, // 0o060000
|
||||
REG = 32768u32, // 0o100000
|
||||
LINK = 40960u32, // 0o120000
|
||||
SOCK = 49152u32, // 0o140000
|
||||
};
|
||||
|
||||
// stat_mask — which filestat fields the call populated. Mirrors
|
||||
// Hare's fs::stat_mask (ref/hare/fs/types.ha:129). newfstatat fills
|
||||
// every field, so [[stat]] / [[lstat]] / [[fstat]] always set all
|
||||
// seven bits OR-folded (see [[fillfilestat]]); per-bit testing is
|
||||
// the documented sparse-backend pattern (cf. Hare's fs::fs network
|
||||
// backends that only populate mtime+size).
|
||||
export type stat_mask = enum u32 {
|
||||
UID = 1u32,
|
||||
GID = 2u32,
|
||||
SIZE = 4u32,
|
||||
INODE = 8u32,
|
||||
ATIME = 16u32,
|
||||
MTIME = 32u32,
|
||||
CTIME = 64u32,
|
||||
};
|
||||
|
||||
// timespec — {sec, nsec} pair, matching Hare's time::instant
|
||||
// (ref/hare/time/types.ha). Local to lib/os; graduates to
|
||||
// lib/time.instant when lib/time and lib/fs ship. Same byte layout
|
||||
// (i64+i64 = 16B) so a future migration is field-rename only.
|
||||
export type timespec = struct {
|
||||
sec: i64,
|
||||
nsec: i64,
|
||||
};
|
||||
|
||||
// filestat — Hare's fs::filestat (ref/hare/fs/types.ha:141). 80
|
||||
// bytes. See module-header note re: graduation to lib/fs.
|
||||
export type filestat = struct {
|
||||
mask: stat_mask, // 0 (4)
|
||||
mode: mode, // 4 (4)
|
||||
uid: u32, // 8 (4)
|
||||
gid: u32, // 12 (4)
|
||||
sz: u64, // 16 (8)
|
||||
inode: u64, // 24 (8)
|
||||
atime: timespec, // 32 (16)
|
||||
mtime: timespec, // 48 (16)
|
||||
ctime: timespec, // 64 (16) — ends at 80
|
||||
};
|
||||
|
||||
// kstat — x86_64 kernel `struct stat` layout. Mirrors
|
||||
// arch/x86/include/uapi/asm/stat.h (`__kernel_ulong_t`-keyed
|
||||
// fields). 144 bytes. Module-internal; SYS_newfstatat writes into
|
||||
// this buffer and the public stat fns then copy the bits into the
|
||||
// Hare-shaped [[filestat]].
|
||||
//
|
||||
// Mode is typed as the public [[mode]] enum (rather than raw u32)
|
||||
// so [[fillfilestat]]'s `out.mode = k.mode` needs no cast. Cstage
|
||||
// emits a redundant `MOVL AX, AX` on u32 → enum-u32 casts that
|
||||
// wwstage skips (task #25); the in-tree shape sidesteps it.
|
||||
// Identical byte layout (both 4B at offset 24).
|
||||
type kstat = struct {
|
||||
dev: u64, // 0
|
||||
ino: u64, // 8
|
||||
nlink: u64, // 16
|
||||
mode: mode, // 24
|
||||
uid: u32, // 28
|
||||
gid: u32, // 32
|
||||
pad0: u32, // 36
|
||||
rdev: u64, // 40
|
||||
sz: i64, // 48
|
||||
blksize: i64, // 56
|
||||
blocks: i64, // 64
|
||||
atime_sec: i64, // 72
|
||||
atime_nsec: i64, // 80
|
||||
mtime_sec: i64, // 88
|
||||
mtime_nsec: i64, // 96
|
||||
ctime_sec: i64, // 104
|
||||
ctime_nsec: i64, // 112
|
||||
unused0: i64, // 120
|
||||
unused1: i64, // 128
|
||||
unused2: i64, // 136 — ends at 144
|
||||
};
|
||||
|
||||
// emptypath — single-NUL byte used as the `pathname` arg to
|
||||
// newfstatat with AT_EMPTY_PATH. The kernel requires a non-NULL
|
||||
// pointer to a zero-length C string, NOT a null pointer. Bytes are
|
||||
// read-only from the kernel's view; ww has no module-level const so
|
||||
// this is a writable `let`.
|
||||
let emptypath: [1]u8 = [0u8];
|
||||
|
||||
// fillfilestat — copy a 144B kstat into the 80B Hare-shaped
|
||||
// filestat. Internal helper used by all three public entry points.
|
||||
// Mirrors Hare's st_to_filestat (ref/hare/os/+linux/dirfdfs.ha:259):
|
||||
// newfstatat populates every field, so the mask is the OR-fold of
|
||||
// all seven Hare stat_mask bits.
|
||||
fn fillfilestat(out: *filestat, k: *kstat) void = {
|
||||
out.mask = stat_mask.UID | stat_mask.GID | stat_mask.SIZE
|
||||
| stat_mask.INODE | stat_mask.ATIME | stat_mask.MTIME
|
||||
| stat_mask.CTIME;
|
||||
out.mode = k.mode;
|
||||
out.uid = k.uid;
|
||||
out.gid = k.gid;
|
||||
out.sz = k.sz: u64;
|
||||
out.inode = k.ino;
|
||||
out.atime.sec = k.atime_sec;
|
||||
out.atime.nsec = k.atime_nsec;
|
||||
out.mtime.sec = k.mtime_sec;
|
||||
out.mtime.nsec = k.mtime_nsec;
|
||||
out.ctime.sec = k.ctime_sec;
|
||||
out.ctime.nsec = k.ctime_nsec;
|
||||
};
|
||||
|
||||
// stat — fill *out with metadata for `path`. Follows symlinks.
|
||||
// `path` must be NUL-terminated (lib/os convention; see task #23
|
||||
// for a planned `path: str` migration).
|
||||
//
|
||||
// Mirrors Hare's sys::stat (ref/hare/sys/+linux/stat.ha:51) modulo
|
||||
// the out-param shape forced by the cgreturn 24B cap. Note: Hare's
|
||||
// higher-level fs::stat (ref/hare/fs/fs.ha:172) instead has lstat
|
||||
// semantics — we follow sys::stat's POSIX-stat behavior here.
|
||||
export fn stat(out: *filestat, path: *u8) (void | oserror) = {
|
||||
let k: kstat;
|
||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
||||
at.FDCWD: i64, path: i64, (&k): i64, 0i64);
|
||||
if (r < 0) { return r: oserror; };
|
||||
fillfilestat(out, &k);
|
||||
};
|
||||
|
||||
// lstat — like [[stat]] but does NOT follow a terminal symlink.
|
||||
// Mirrors Hare's sys::lstat (ref/hare/sys/+linux/stat.ha:57).
|
||||
export fn lstat(out: *filestat, path: *u8) (void | oserror) = {
|
||||
let k: kstat;
|
||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
||||
at.FDCWD: i64, path: i64, (&k): i64,
|
||||
at.SYMLINK_NOFOLLOW: i64);
|
||||
if (r < 0) { return r: oserror; };
|
||||
fillfilestat(out, &k);
|
||||
};
|
||||
|
||||
// fstat — like [[stat]] but addresses the file by fd. Uses
|
||||
// newfstatat(fd, "", AT_EMPTY_PATH); the kernel resolves the fd
|
||||
// directly. Mirrors Hare's sys::fstat (ref/hare/sys/+linux/stat.ha:54).
|
||||
export fn fstat(out: *filestat, fd: i32) (void | oserror) = {
|
||||
let k: kstat;
|
||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
||||
fd: i64, (&emptypath[0]): i64, (&k): i64,
|
||||
at.EMPTY_PATH: i64);
|
||||
if (r < 0) { return r: oserror; };
|
||||
fillfilestat(out, &k);
|
||||
};
|
||||
|
||||
// exists — true if `path` resolves to anything (regular file,
|
||||
// directory, symlink, ...). Stat-shaped (Hare's `fs::exists`,
|
||||
// ref/hare/fs/fs.ha:196) — no separate syscall. Symlinks are
|
||||
// followed; a dangling symlink is `false`.
|
||||
//
|
||||
// Race warning: prefer "open and handle the error" over "exists
|
||||
// then open" in real code (Hare's docstring carries the same
|
||||
// note). The race is unavoidable in this shape.
|
||||
//
|
||||
// Goes through SYS_newfstatat directly rather than match'ing on
|
||||
// [[stat]]'s `(void | oserror)` return. Functionally identical;
|
||||
// the direct shape sidesteps a cstage/wwstage cgen disagreement
|
||||
// on the slot size of `(void | oserror)` (cstage 16B, wwstage 24B
|
||||
// — same class as STATUS #22, surfaced first time a match on this
|
||||
// shape combined with an 80B local-struct local frame). Use the
|
||||
// match shape once #22 lands.
|
||||
export fn exists(path: *u8) bool = {
|
||||
let k: kstat;
|
||||
let r: i64 = syscall4(nr.NEWFSTATAT,
|
||||
at.FDCWD: i64, path: i64, (&k): i64, 0i64);
|
||||
return r >= 0i64;
|
||||
};
|
||||
|
||||
// MODULE: strings
|
||||
// strings — operations over the immutable str type ({ *u8, len }).
|
||||
// Mirrors Hare's strings::; `len` and `is-empty` aren't functions
|
||||
|
||||
Reference in New Issue
Block a user