//! POSIX-style file API for user programs — the low level under C stdio. Files //! are named objects served by the user-space VFS server (system/services/vfs/vfs.zig); each //! call marshals a request, IPC_Calls the VFS, and unmarshals the reply. The //! kernel knows nothing of files or fds — the fd table lives here, per process. const std = @import("std"); const protocol = @import("vfs-protocol"); const ipc = @import("ipc.zig"); const danos = @import("danos"); pub const O_CREAT = protocol.O_CREAT; pub const SEEK_SET: u32 = 0; pub const SEEK_CURRENT: u32 = 1; pub const SEEK_END: u32 = 2; // Resolve (and cache) the VFS server endpoint, looked up by well-known id. var vfs_handle: usize = 0; var vfs_resolved = false; fn vfs() ?usize { if (!vfs_resolved) { vfs_handle = ipc.lookup(.vfs) orelse return null; vfs_resolved = true; } return vfs_handle; } const maximum_fds = 32; const Fd = struct { used: bool = false, node: u64 = 0, offset: u64 = 0 }; var fds = [_]Fd{.{}} ** maximum_fds; fn allocFd() ?usize { for (&fds, 0..) |*f, i| { if (!f.used) { f.* = .{ .used = true }; return i; } } return null; } const Result = struct { reply: protocol.Reply, payload: []u8 }; /// One request/reply round trip: [Request header][send payload] -> VFS -> /// [Reply header][receive payload]. The receive payload is written into `out`. fn transact(request: protocol.Request, send: []const u8, out: []u8) ?Result { const h = vfs() orelse return null; var message: [protocol.message_maximum]u8 = undefined; @memcpy(message[0..protocol.request_size], std.mem.asBytes(&request)); const slen = @min(send.len, protocol.maximum_payload); @memcpy(message[protocol.request_size..][0..slen], send[0..slen]); var rbuf: [protocol.message_maximum]u8 = undefined; const n = ipc.call(h, message[0 .. protocol.request_size + slen], &rbuf) catch return null; if (n < protocol.reply_size) return null; const reply = std.mem.bytesToValue(protocol.Reply, rbuf[0..protocol.reply_size]); const rpl = @min(n - protocol.reply_size, out.len); @memcpy(out[0..rpl], rbuf[protocol.reply_size..][0..rpl]); return .{ .reply = reply, .payload = out[0..rpl] }; } /// Open (or create, with O_CREAT) `path`; returns an fd or -1. pub fn open(path: []const u8, flags: u32) i32 { const fd = allocFd() orelse return -1; const request = protocol.Request{ .operation = .open, .node = 0, .offset = 0, .len = @intCast(path.len), .flags = flags }; const r = transact(request, path, &.{}) orelse { fds[fd].used = false; return -1; }; if (r.reply.status != 0) { fds[fd].used = false; return -1; } fds[fd] = .{ .used = true, .node = r.reply.node, .offset = 0 }; return @intCast(fd); } fn fdPtr(fd: i32) ?*Fd { if (fd < 0 or fd >= maximum_fds) return null; const f = &fds[@intCast(fd)]; return if (f.used) f else null; } /// Read up to `buffer.len` bytes at the current offset; returns the count or -1. pub fn read(fd: i32, buffer: []u8) isize { const f = fdPtr(fd) orelse return -1; const want: u32 = @intCast(@min(buffer.len, protocol.maximum_payload)); const request = protocol.Request{ .operation = .read, .node = f.node, .offset = f.offset, .len = want, .flags = 0 }; const r = transact(request, &.{}, buffer) orelse return -1; if (r.reply.status != 0) return -1; f.offset += r.reply.len; return @intCast(r.reply.len); } /// Write `data` at the current offset; returns the count or -1. pub fn write(fd: i32, data: []const u8) isize { const f = fdPtr(fd) orelse return -1; const want: u32 = @intCast(@min(data.len, protocol.maximum_payload)); const request = protocol.Request{ .operation = .write, .node = f.node, .offset = f.offset, .len = want, .flags = 0 }; const r = transact(request, data[0..want], &.{}) orelse return -1; if (r.reply.status != 0) return -1; f.offset += r.reply.len; return @intCast(r.reply.len); } /// Reposition the fd's offset. Returns the new offset or -1. (SEEK_END needs the /// file size, which `stat` provides; handled by fetching it here.) pub fn lseek(fd: i32, off: i64, whence: u32) i64 { const f = fdPtr(fd) orelse return -1; const base: i64 = switch (whence) { SEEK_SET => 0, SEEK_CURRENT => @intCast(f.offset), SEEK_END => blk: { const request = protocol.Request{ .operation = .stat, .node = f.node, .offset = 0, .len = 0, .flags = 0 }; var sbuf: [@sizeOf(protocol.Stat)]u8 = undefined; const r = transact(request, &.{}, &sbuf) orelse return -1; if (r.reply.status != 0 or r.payload.len < @sizeOf(protocol.Stat)) return -1; const st = std.mem.bytesToValue(protocol.Stat, sbuf[0..@sizeOf(protocol.Stat)]); break :blk @intCast(st.size); }, else => return -1, }; const pos = base + off; if (pos < 0) return -1; f.offset = @intCast(pos); return pos; } /// Stat `path`. Returns 0 or -1. pub fn stat(path: []const u8, out: *protocol.Stat) i32 { // Open, stat by node, close — simple and enough for now. const fd = open(path, 0); if (fd < 0) return -1; defer close(fd); const f = fdPtr(fd).?; const request = protocol.Request{ .operation = .stat, .node = f.node, .offset = 0, .len = 0, .flags = 0 }; var sbuf: [@sizeOf(protocol.Stat)]u8 = undefined; const r = transact(request, &.{}, &sbuf) orelse return -1; if (r.reply.status != 0 or r.payload.len < @sizeOf(protocol.Stat)) return -1; out.* = std.mem.bytesToValue(protocol.Stat, sbuf[0..@sizeOf(protocol.Stat)]); return 0; } /// Close an fd (best effort — tells the VFS to release the open file). pub fn close(fd: i32) void { const f = fdPtr(fd) orelse return; const request = protocol.Request{ .operation = .close, .node = f.node, .offset = 0, .len = 0, .flags = 0 }; _ = transact(request, &.{}, &.{}); f.used = false; }