Files
danos/lib/stdio.zig
T
Daniel Samson b0f894f50c M9: user-space VFS server + client file API (open/read/write/stat + stdio)
The payoff milestone: files are served by a user-space process, reached over
IPC — the kernel never sees a path or an fd.

- lib/vfs_proto.zig: the VFS wire protocol (Op, Request/Reply fixed header +
  inline payload, Stat), shared by client and server; one message <= MSG_MAX.
- lib/ipc.zig: replyWait() — the server-side dual-return stub (length in rax,
  badge in rdx via a "+{rdx}" read-write operand), deferred from M7.
- sbin/vfs.zig: the real VFS server — an in-heap ramfs (open creates a node)
  with an IPC_ReplyWait dispatch loop serving open/read/write/stat/close.
  Registers its endpoint under the well-known vfs id at startup.
- lib/unistd.zig: POSIX-style client API in rt — a per-process fd table +
  open/close/read/write/lseek/stat, each an IPC_Call to the VFS. The kernel
  knows nothing of fds; the table lives here.
- lib/stdio.zig: C stdio over unistd — FILE + fopen/fclose/fread/fwrite/
  fseek/ftell/rewind/feof/ferror/fputs/fputc/fgetc (unbuffered for now).
- sbin/vfstest.zig: a client that opens/writes/seeks/reads a file and only
  heartbeats "vfstest: ok" if the round trip matched. Packed in the initrd.
- New `vfs` test drives it end to end. initrd test relaxed to generic
  liveness. Suite 31/31.
2026-07-09 07:48:24 +01:00

116 lines
3.1 KiB
Zig

//! A small C stdio layer over the POSIX-style file API (unistd.zig). Unbuffered
//! for now — each fread/fwrite is one VFS round trip; an internal buffer (fewer
//! IPC calls) is a later optimisation. Both a Zig-callable API and `extern "C"`
//! symbols are provided, so Zig and future C programs share it.
const std = @import("std");
const unistd = @import("unistd.zig");
const heap = @import("heap.zig");
pub const SEEK_SET = unistd.SEEK_SET;
pub const SEEK_CUR = unistd.SEEK_CUR;
pub const SEEK_END = unistd.SEEK_END;
/// A C `FILE`: an fd plus sticky end-of-file / error flags. Allocated on the
/// heap; `fclose` frees it.
pub const FILE = extern struct {
fd: i32,
eof: c_int = 0,
err: c_int = 0,
};
fn flagsFor(mode: []const u8) u32 {
if (mode.len == 0) return 0;
return switch (mode[0]) {
'w', 'a' => unistd.O_CREAT,
else => 0,
};
}
/// Open `path` in `mode` ("r"/"w"/"a", '+' ignored for now). Returns null on error.
pub fn fopen(path: []const u8, mode: []const u8) ?*FILE {
const fd = unistd.open(path, flagsFor(mode));
if (fd < 0) return null;
const f = heap.allocator().create(FILE) catch {
unistd.close(fd);
return null;
};
f.* = .{ .fd = fd };
if (mode.len > 0 and mode[0] == 'a') _ = unistd.lseek(fd, 0, unistd.SEEK_END);
return f;
}
pub fn fclose(f: *FILE) c_int {
unistd.close(f.fd);
heap.allocator().destroy(f);
return 0;
}
/// Read `size*nmemb` bytes; returns the number of whole items read.
pub fn fread(buf: []u8, size: usize, nmemb: usize, f: *FILE) usize {
const total = size * nmemb;
if (total == 0) return 0;
const n = unistd.read(f.fd, buf[0..@min(buf.len, total)]);
if (n <= 0) {
f.eof = 1;
return 0;
}
return @as(usize, @intCast(n)) / size;
}
/// Write `size*nmemb` bytes; returns the number of whole items written.
pub fn fwrite(data: []const u8, size: usize, nmemb: usize, f: *FILE) usize {
const total = @min(data.len, size * nmemb);
if (total == 0) return 0;
const n = unistd.write(f.fd, data[0..total]);
if (n <= 0) {
f.err = 1;
return 0;
}
return @as(usize, @intCast(n)) / size;
}
pub fn fseek(f: *FILE, off: i64, whence: u32) c_int {
f.eof = 0;
return if (unistd.lseek(f.fd, off, whence) < 0) -1 else 0;
}
pub fn ftell(f: *FILE) i64 {
return unistd.lseek(f.fd, 0, unistd.SEEK_CUR);
}
pub fn rewind(f: *FILE) void {
_ = fseek(f, 0, SEEK_SET);
}
pub fn feof(f: *FILE) c_int {
return f.eof;
}
pub fn ferror(f: *FILE) c_int {
return f.err;
}
pub fn fputs(s: []const u8, f: *FILE) c_int {
return if (unistd.write(f.fd, s) < 0) -1 else 0;
}
pub fn fputc(c: u8, f: *FILE) c_int {
const b = [_]u8{c};
return if (unistd.write(f.fd, &b) == 1) c else -1;
}
pub fn fgetc(f: *FILE) c_int {
var b: [1]u8 = undefined;
const n = unistd.read(f.fd, &b);
if (n <= 0) {
f.eof = 1;
return -1; // EOF
}
return b[0];
}
// Real `extern "C"` symbols (fopen/fread/fseek/...) — with a C-string signature
// distinct from the Zig slice API above — land with the first C program, wired
// via @export so they don't collide with these Zig names.