vfs: the root moves into the kernel — resolve + redirect cutover
runtime.fs now routes every path through fs_resolve: kernel-served /system nodes are read via fs_node (tokens, no open state); everything under a userspace mount goes straight to the owning backend's endpoint with the kernel-rewritten mount-relative path — one syscall of naming, then the unchanged vfs-protocol rendezvous, public API untouched. mkdir/ unlink/rename resolve-then-forward (rename checks both paths land on the SAME backend); mount is the fs_mount syscall. The fat server mounts twice — /mnt/usb from the volume root and /var from its /var subtree — so the logger now writes the FHS path /var/log/<boot-stamp>/... and swapping the persistent medium later touches only fat's two mount calls. With clients holding fat's node ids directly, fat records each handle's owner, checks it, and sweeps a dead client's handles via the published exit events (the old router's pattern, now where the state actually lives). The userspace vfs server and its router die; ServiceId.vfs=1 stays reserved-retired; protocol.zig moves to system/vfs-protocol.zig (the wire contract is backend-only now). vfs-test becomes the ring-3 proof of the kernel VFS (own-binary ELF magic through /system, read-only refusals, listing); vfs-client-death becomes the fat sweep test over the full storage chain, with a ring-scanning check (the last-write buffer is too racy under a chattering tree).
This commit is contained in:
@@ -32,6 +32,10 @@ pub const klog_record_magic = abi.klog_record_magic;
|
||||
pub const klog_flag_truncated = abi.klog_flag_truncated;
|
||||
pub const klog_maximum_message = abi.klog_maximum_message;
|
||||
pub const maximum_process_name = abi.maximum_process_name;
|
||||
pub const FileAttributes = abi.FileAttributes;
|
||||
pub const DirectoryEntryHeader = abi.DirectoryEntryHeader;
|
||||
pub const file_kind_regular = abi.file_kind_regular;
|
||||
pub const file_kind_directory = abi.file_kind_directory;
|
||||
|
||||
/// Write raw bytes to the kernel log (bring-up/panic diagnostics; ordinary
|
||||
/// output goes through std.log -> writeRecord). The kernel stamps the record
|
||||
@@ -100,6 +104,72 @@ pub fn klogStatus() ?KlogStatus {
|
||||
return status;
|
||||
}
|
||||
|
||||
/// Where fs_resolve routed a path: served by the kernel (a permanent node
|
||||
/// token for fs_node) or by a userspace filesystem backend (an endpoint handle
|
||||
/// plus the rewritten mount-relative path, returned in the caller's buffer).
|
||||
pub const FsRoute = union(enum) {
|
||||
kernel: u64,
|
||||
backend: struct { handle: usize, path_len: usize },
|
||||
};
|
||||
|
||||
/// Route `path` through the kernel VFS. For a backend route the rewritten
|
||||
/// mount-relative path lands in `out` (behind a kernel-written length prefix,
|
||||
/// already stripped here: out[0..path_len] is the path).
|
||||
pub fn fsResolve(path: []const u8, flags: usize, out: []u8) ?FsRoute {
|
||||
var rax: usize = undefined;
|
||||
var rdx: usize = flags; // in: flags (arg #3); out: node token / backend handle
|
||||
asm volatile ("syscall"
|
||||
: [rax] "={rax}" (rax),
|
||||
[rdx] "+{rdx}" (rdx),
|
||||
: [n] "{rax}" (@intFromEnum(abi.SystemCall.fs_resolve)),
|
||||
[a0] "{rdi}" (@intFromPtr(path.ptr)),
|
||||
[a1] "{rsi}" (path.len),
|
||||
[a3] "{r10}" (@intFromPtr(out.ptr)),
|
||||
[a4] "{r8}" (out.len),
|
||||
: .{ .rcx = true, .r11 = true, .memory = true });
|
||||
if (@as(isize, @bitCast(rax)) < 0) return null;
|
||||
if (rax == abi.fs_route_kernel) return .{ .kernel = rdx };
|
||||
if (rax != abi.fs_route_backend) return null;
|
||||
const path_len = @as(usize, out[0]) | (@as(usize, out[1]) << 8);
|
||||
if (path_len + 2 > out.len) return null;
|
||||
std.mem.copyForwards(u8, out[0..path_len], out[2..][0..path_len]);
|
||||
return .{ .backend = .{ .handle = rdx, .path_len = path_len } };
|
||||
}
|
||||
|
||||
/// Read `out.len` bytes of a kernel-served node at `offset` (fs_node read).
|
||||
pub fn fsNodeRead(node_token: u64, offset: u64, out: []u8) ?usize {
|
||||
const r = sc.systemCall5(.fs_node, abi.fs_node_read, node_token, offset, @intFromPtr(out.ptr), out.len);
|
||||
if (@as(isize, @bitCast(r)) < 0) return null;
|
||||
return r;
|
||||
}
|
||||
|
||||
/// A kernel-served node's metadata (fs_node status).
|
||||
pub fn fsNodeStatus(node_token: u64) ?abi.FileAttributes {
|
||||
var attributes: abi.FileAttributes = undefined;
|
||||
const r = sc.systemCall5(.fs_node, abi.fs_node_status, node_token, 0, @intFromPtr(&attributes), @sizeOf(abi.FileAttributes));
|
||||
if (@as(isize, @bitCast(r)) < 0) return null;
|
||||
return attributes;
|
||||
}
|
||||
|
||||
/// The `cursor`th child of a kernel-served directory (fs_node readdir): fills
|
||||
/// `out` with [DirectoryEntryHeader][name]; returns total bytes (0 = end).
|
||||
pub fn fsNodeReaddir(node_token: u64, cursor: u64, out: []u8) ?usize {
|
||||
const r = sc.systemCall5(.fs_node, abi.fs_node_readdir, node_token, cursor, @intFromPtr(out.ptr), out.len);
|
||||
if (@as(isize, @bitCast(r)) < 0) return null;
|
||||
return r;
|
||||
}
|
||||
|
||||
/// Mount a userspace filesystem's endpoint at `prefix`, with an optional
|
||||
/// backend-side `rewrite` prefix ("" = none). Possession of the endpoint
|
||||
/// handle is the capability.
|
||||
pub fn fsMount(prefix: []const u8, backend: usize, rewrite: []const u8) bool {
|
||||
return sc.systemCall5(.fs_mount, @intFromPtr(prefix.ptr), prefix.len, backend, @intFromPtr(rewrite.ptr), rewrite.len) == 0;
|
||||
}
|
||||
|
||||
pub fn fsUnmount(prefix: []const u8) bool {
|
||||
return sc.systemCall2(.fs_unmount, @intFromPtr(prefix.ptr), prefix.len) == 0;
|
||||
}
|
||||
|
||||
/// End the process. Never returns.
|
||||
pub fn exit(code: usize) noreturn {
|
||||
_ = sc.systemCall1(.exit, code);
|
||||
|
||||
Reference in New Issue
Block a user