//! 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("runtime").heap; pub const SEEK_SET = unistd.SEEK_SET; pub const SEEK_CURRENT = unistd.SEEK_CURRENT; 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(buffer: []u8, size: usize, nmemb: usize, f: *FILE) usize { const total = size * nmemb; if (total == 0) return 0; const n = unistd.read(f.fd, buffer[0..@min(buffer.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_CURRENT); } 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.