aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md2
-rw-r--r--docs/LEARN.md2
-rw-r--r--src/lib/std.zc38
-rw-r--r--tests/programs/file_drop.zc19
-rwxr-xr-xtests/test.sh1
5 files changed, 56 insertions, 6 deletions
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 `<eval>` 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 `<eval>` 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 <stdin>:10 in main' "$l memdebug: use after free"
chk "$($vm < tests/programs/uaf_in.zc)" $'zy: use after free from line 7\n at <stdin>:11 in main' "$l memdebug: use after free through an interior pointer"