From 54d316a531ddb8e293711761e4f29e93d282599c Mon Sep 17 00:00:00 2001 From: zinc Date: Thu, 15 Oct 2026 02:46:57 +0330 Subject: std: File, the first user of drops: std.Host.open(path, mode) returns an owned std.File (or null) with write and read, and its drop closes the descriptor; Host.writefile is built on it; file_drop.zc, learn and readme --- README.md | 2 +- docs/LEARN.md | 2 ++ src/lib/std.zc | 38 +++++++++++++++++++++++++++++++++----- tests/programs/file_drop.zc | 19 +++++++++++++++++++ tests/test.sh | 1 + 5 files changed, 56 insertions(+), 6 deletions(-) create mode 100644 tests/programs/file_drop.zc diff --git a/README.md b/README.md index a90017d..839eac2 100644 --- a/README.md +++ b/README.md @@ -198,7 +198,7 @@ a unit is read whole and scanned for function signatures before it compiles, so the runtime api lives in the `rt` namespace and requires `include rt;`: `rt.put_c`, `rt.get_c`, `rt.eof`, `rt.put_s`, `rt.put_n`, `rt.halt`, `rt.len_w`, `rt.eq_w`, `rt.copy_w`, `rt.fill_w`, the packed-string helpers `rt.len_s`, `rt.at_s`, `rt.slice_s`, `rt.cat_s`, `rt.eq_s`, `rt.s_to_n`, `rt.n_to_s` and `rt.put_bs`, allocation and ownership helpers such as `rt.new`, `rt.new_t`, `rt.free`, `rt.gc`, `rt.memreport`, and host functions including `rt.syscall` and `rt.readfile`. their signatures and descriptions are in the runtime source comments and built-in hover table. `src/lib/std.zc` declares the `std` namespace; its static groups are called as `std.Group.name(args)`: `std.Text`, `std.Num`, `std.Host`, `std.Bytes`, `std.Arena`, and `std.Buf`. the constructors `std.arena(n)` and `std.bnew(n)` and globals such as `std.argc` and `std.argv` are plain declarations in that namespace. capture uses static functions: `std.capture.start()` begins output capture and `std.capture.end(buf)` ends it and returns the buffer. -the standard library has seven static groups: `std.capture` (output capture), `std.Text` (char* strings), `std.Num` (integer helpers), `std.Host` (environment, clock, files and raw syscalls), `std.Bytes` (packed byte strings), `std.Arena` (word arenas) and `std.Buf` (byte buffers). the array algorithms `std.array_find`, `std.array_map`, `std.array_reduce` and the others are plain functions in the same namespace. every public declaration in `src/lib/std.zc` has a comment that the language server shows on hover. `examples/std_tour.zc` calls one or two functions from each group and the array algorithms; run it with `build/bin/zy < examples/std_tour.zc`. +the standard library has seven static groups: `std.capture` (output capture), `std.Text` (char* strings), `std.Num` (integer helpers), `std.Host` (environment, clock, files and raw syscalls; `std.Host.open` returns a `std.File` whose drop closes it), `std.Bytes` (packed byte strings), `std.Arena` (word arenas) and `std.Buf` (byte buffers). the array algorithms `std.array_find`, `std.array_map`, `std.array_reduce` and the others are plain functions in the same namespace. every public declaration in `src/lib/std.zc` has a comment that the language server shows on hover. `examples/std_tour.zc` calls one or two functions from each group and the array algorithms; run it with `build/bin/zy < examples/std_tour.zc`. `std.argc` and `std.argv` provide command-line arguments to `main`; `std.envc` and `std.envp` provide environment entries in `NAME=value` form. `std.argc` is an `int`, and `std.argv` is a `char*[?]`; the environment fields have matching types. `std.argv[0]` is the program path, or `` when source comes from stdin. `std.libinit()` fills all four before `main` when the program includes `std`. Since include dependencies do not import into the caller, runtime calls also require `include rt;`: diff --git a/docs/LEARN.md b/docs/LEARN.md index fd399e3..ed12caf 100644 --- a/docs/LEARN.md +++ b/docs/LEARN.md @@ -535,6 +535,8 @@ output: `rt.syscall(op, a, b, c)` calls the runtime interface to the vm's syscall gate. the runtime defines names such as `SYS_OPEN`, `SYS_READ`, `SYS_WRITE`, `SYS_GETENV`, and `SYS_TIME`. unsupported operations return -1. `std.Host.now()` wraps the clock request. +`std.Host.open(path, mode)` opens a file (0 to read, 1 to write) and returns an owned `std.File`, or null. `f.write(s)` and `f.read(buf, n)` go through its descriptor, and its drop closes it: the file is closed when its owner's block ends, when it is freed, or when the collector reclaims it, with no `close` to call. + `std.Host.env(name, dst, n)` writes an environment value into a word-string buffer and returns its length, or -1 when it is unset. the library also supplies `std.argc`, `std.argv`, `std.envc`, and `std.envp`; a program read on stdin has `` as its first argument. gate calls must be made from ordinary code, outside an `o3` region. [example source](../examples/learn/21-host.zc): diff --git a/src/lib/std.zc b/src/lib/std.zc index 772b887..745a921 100644 --- a/src/lib/std.zc +++ b/src/lib/std.zc @@ -344,6 +344,24 @@ struct Num { // are its arguments. Host.raw fills it on each call. int rawblk[7]; +// an open file, from Host.open. its drop closes the descriptor, so the file is +// closed when its owner's block ends, when it is freed, or when the collector +// reclaims it (docs/SPEC.md 6.15). called as f.write(s), f.read(buf, n). +struct File { + int fd; + // writes a string. + // s: the text to write + // returns: how many bytes were written, negative on an error + int write(char* s) { return rt.syscall(rt.SYS_WRITE, this.fd, s, rt.len_w(s)); } + // reads bytes, one to a word. + // buf: where they go + // n: the most to read + // returns: how many were read, 0 at the end of the file, negative on an error + int read(char* buf, int n) { return rt.syscall(rt.SYS_READ, this.fd, buf, n); } + // the file dies: close its descriptor + void drop() { rt.syscall(rt.SYS_CLOSE, this.fd, 0, 0); } +} + // calls into the host that runs the vm: the environment, the clock, files and // raw system calls. called as std.Host.name(args). struct Host { @@ -373,17 +391,27 @@ struct Host { // returns: the time in seconds since 1970 static int now() { return rt.syscall(rt.SYS_TIME, 0, 0, 0); } + // opens a file. the result owns the descriptor: its drop closes it (see File). + // path: the file's name + // mode: 0 to read, 1 to write (the file is replaced, or created) + // returns: the open file, or null if it could not be opened + static File*.u open(char* path, int mode) { + int fd = rt.syscall(rt.SYS_OPEN, path, mode, 0); + if (fd < 0) return 0; + File* f = rt.new(File); + f.fd = fd; + return f; + } + // writes a string to a file, replacing what was there. the file is created if // it is missing. // path: the file's name // s: the text to write // returns: how many bytes were written, -1 if the file could not be opened static int writefile(char* path, char* s) { - int fd = rt.syscall(rt.SYS_OPEN, path, 1, 0), n; - if (fd < 0) return -1; - n = rt.syscall(rt.SYS_WRITE, fd, s, rt.len_w(s)); - rt.syscall(rt.SYS_CLOSE, fd, 0, 0); - return n; + File* f = Host.open(path, 1); + if (!f) return -1; + return f.write(s); // f's drop closes the file after the write } } diff --git a/tests/programs/file_drop.zc b/tests/programs/file_drop.zc new file mode 100644 index 0000000..0c0738a --- /dev/null +++ b/tests/programs/file_drop.zc @@ -0,0 +1,19 @@ +namespace; +// std.File: Host.open gives an owned file whose drop closes the descriptor at its block end (a write to it then writes nothing) +include rt, std; +fn main() { + int fd = 0 - 1; + { + std.File* f = std.Host.open("/tmp/zc-file-drop.txt", 1); + fd = f.fd; + rt.put_n(f.write("hello")); rt.put_c(' '); + rt.put_n(rt.syscall(rt.SYS_WRITE, fd, "!", 1)); rt.put_c(' '); + } + rt.put_n(rt.syscall(rt.SYS_WRITE, fd, "?", 1) <= 0); rt.put_c(' '); + char* s = rt.readfile("/tmp/zc-file-drop.txt"); + rt.put_s(s); rt.put_c(' '); + rt.free(s); + std.File* g = std.Host.open("/tmp/zc-no-such-dir/x", 1); + rt.put_n(g == 0); rt.put_c(10); + return 0; +} diff --git a/tests/test.sh b/tests/test.sh index 6dfecf1..70e12b9 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -166,6 +166,7 @@ hello world!42 256 v" "$l a full capture is still terminated inside its buffer" chk "$($vm < tests/programs/sys.zc)" "2 zy time" "$l syscalls: write, read, time" + chk "$($vm < tests/programs/file_drop.zc)" "5 1 1 hello! 1" "$l std.File: its drop closes the descriptor at block end" chk "$($vm < tests/programs/memleak.zc)" $'leak: line 8 1 words\n1' "$l memdebug names a live allocation" chk "$($vm < tests/programs/uaf.zc)" $'zc: line 10: warning: use after free: this storage\'s lifetime ended on line 9\nzy: use after free from line 7\n at :10 in main' "$l memdebug: use after free" chk "$($vm < tests/programs/uaf_in.zc)" $'zy: use after free from line 7\n at :11 in main' "$l memdebug: use after free through an interior pointer" -- cgit v1.2.3