116 lines
3.2 KiB
Zig
116 lines
3.2 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("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.
|