reorg: move vfs-protocol to library/protocol + direct import

vfs-protocol -> library/protocol/vfs/vfs-protocol.zig. fat, its only consumer,
now imports the module directly (const vfs_protocol = @import("vfs-protocol"))
instead of through runtime.vfs_protocol, and the runtime re-export is deleted.
The runtime's own client (runtime.fs) still imports the module by name.

zig build + test green; vfs, fat-mount pass.
This commit is contained in:
Daniel Samson
2026-07-22 20:52:09 +01:00
parent f90dc6c121
commit 25cc4d610e
4 changed files with 26 additions and 27 deletions
+3 -2
View File
@@ -368,7 +368,7 @@ pub fn build(b: *std.Build) void {
// is the first "protocol module" (see docs/driver-model.md); usb/block will // is the first "protocol module" (see docs/driver-model.md); usb/block will
// expose theirs the same way. // expose theirs the same way.
const vfs_protocol_module = b.addModule("vfs-protocol", .{ const vfs_protocol_module = b.addModule("vfs-protocol", .{
.root_source_file = b.path("system/vfs-protocol.zig"), .root_source_file = b.path("library/protocol/vfs/vfs-protocol.zig"),
}); });
// The input wire protocol: the input service's public interface, exposed as its own // The input wire protocol: the input service's public interface, exposed as its own
@@ -572,6 +572,7 @@ pub fn build(b: *std.Build) void {
// The FAT filesystem server: mounts the block device and serves it into the VFS // The FAT filesystem server: mounts the block device and serves it into the VFS
// at /mnt/usb. Its engine (engine.zig / on-disk.zig) is imported relatively. // at /mnt/usb. Its engine (engine.zig / on-disk.zig) is imported relatively.
const fat_exe = addUserBinary(b, kernel_target, runtime_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "fat", "system/services/fat/fat.zig"); const fat_exe = addUserBinary(b, kernel_target, runtime_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "fat", "system/services/fat/fat.zig");
programModule(fat_exe).addImport("vfs-protocol", vfs_protocol_module);
// Threaded: the display runs a mouse-listener thread alongside its compositor loop // Threaded: the display runs a mouse-listener thread alongside its compositor loop
// (docs/threading.md, docs/display.md), so it opts into real atomics/TLS. // (docs/threading.md, docs/display.md), so it opts into real atomics/TLS.
const display_exe = addThreadedUserBinary(b, kernel_target, runtime_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "display", "system/services/display/display.zig"); const display_exe = addThreadedUserBinary(b, kernel_target, runtime_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "display", "system/services/display/display.zig");
@@ -957,7 +958,7 @@ pub fn build(b: *std.Build) void {
"system/drivers/usb-hid/hid-report.zig", // HID boot-report keyboard/mouse decode "system/drivers/usb-hid/hid-report.zig", // HID boot-report keyboard/mouse decode
"system/drivers/usb-storage/bulk-only-transport.zig", // CBW/CSW wrapper sizes "system/drivers/usb-storage/bulk-only-transport.zig", // CBW/CSW wrapper sizes
"system/drivers/usb-storage/scsi.zig", // SCSI CDB encodings (big-endian) "system/drivers/usb-storage/scsi.zig", // SCSI CDB encodings (big-endian)
"system/vfs-protocol.zig", // NodeKind / DirectoryEntry sizes + op values "library/protocol/vfs/vfs-protocol.zig", // NodeKind / DirectoryEntry sizes + op values
"system/services/fat/on-disk.zig", // FAT on-disk struct sizes + type detection "system/services/fat/on-disk.zig", // FAT on-disk struct sizes + type detection
"system/services/fat/engine.zig", // FAT read/write over a RAM-backed image "system/services/fat/engine.zig", // FAT read/write over a RAM-backed image
"system/services/display/compositor.zig", // Rect math + fill/composite/blit-tile "system/services/display/compositor.zig", // Rect math + fill/composite/blit-tile
-2
View File
@@ -18,8 +18,6 @@ pub const time = @import("time.zig");
pub const heap = @import("heap.zig"); pub const heap = @import("heap.zig");
pub const ipc = @import("ipc.zig"); pub const ipc = @import("ipc.zig");
pub const start = @import("start.zig"); pub const start = @import("start.zig");
/// The VFS wire protocol (shared with the VFS server).
pub const vfs_protocol = @import("vfs-protocol");
/// The device-manager protocol: hello + tree reports (docs/device-manager.md). /// The device-manager protocol: hello + tree reports (docs/device-manager.md).
pub const device_manager_protocol = @import("device-manager-protocol"); pub const device_manager_protocol = @import("device-manager-protocol");
+23 -23
View File
@@ -13,7 +13,7 @@ const std = @import("std");
const runtime = @import("runtime"); const runtime = @import("runtime");
const engine = @import("engine.zig"); const engine = @import("engine.zig");
const on_disk = @import("on-disk.zig"); const on_disk = @import("on-disk.zig");
const protocol = runtime.vfs_protocol; const vfs_protocol = @import("vfs-protocol");
const dma = runtime.dma; const dma = runtime.dma;
const mount_point = "/mnt/usb"; const mount_point = "/mnt/usb";
@@ -69,11 +69,11 @@ fn openAt(id: u64) ?*OpenNode {
return if (o.used) o else null; return if (o.used) o else null;
} }
fn writeReply(out: []u8, reply: protocol.Reply, payload: []const u8) usize { fn writeReply(out: []u8, reply: vfs_protocol.Reply, payload: []const u8) usize {
@memcpy(out[0..protocol.reply_size], std.mem.asBytes(&reply)); @memcpy(out[0..vfs_protocol.reply_size], std.mem.asBytes(&reply));
const n = @min(payload.len, out.len - protocol.reply_size); const n = @min(payload.len, out.len - vfs_protocol.reply_size);
@memcpy(out[protocol.reply_size..][0..n], payload[0..n]); @memcpy(out[vfs_protocol.reply_size..][0..n], payload[0..n]);
return protocol.reply_size + n; return vfs_protocol.reply_size + n;
} }
fn fail(out: []u8) usize { fn fail(out: []u8) usize {
@@ -183,7 +183,7 @@ fn splitParent(path: []const u8) ParentLeaf {
fn handleOpen(out: []u8, path: []const u8, flags: u32, sender: u32) usize { fn handleOpen(out: []u8, path: []const u8, flags: u32, sender: u32) usize {
var node = filesystem.resolve(path); var node = filesystem.resolve(path);
if (node == null and flags & protocol.create != 0) { if (node == null and flags & vfs_protocol.create != 0) {
const split = splitParent(path); const split = splitParent(path);
const parent = filesystem.resolve(split.parent) orelse return fail(out); const parent = filesystem.resolve(split.parent) orelse return fail(out);
node = filesystem.createFile(parent, split.leaf); node = filesystem.createFile(parent, split.leaf);
@@ -191,7 +191,7 @@ fn handleOpen(out: []u8, path: []const u8, flags: u32, sender: u32) usize {
var resolved = node orelse return fail(out); var resolved = node orelse return fail(out);
// O_TRUNC: replace an existing file's contents rather than overwriting in place // O_TRUNC: replace an existing file's contents rather than overwriting in place
// (frees the old chain, so a shorter rewrite leaves no stale tail). // (frees the old chain, so a shorter rewrite leaves no stale tail).
if (flags & protocol.truncate != 0 and !resolved.is_directory) { if (flags & vfs_protocol.truncate != 0 and !resolved.is_directory) {
filesystem.truncate(&resolved); filesystem.truncate(&resolved);
} }
const index = allocOpen() orelse return fail(out); const index = allocOpen() orelse return fail(out);
@@ -202,9 +202,9 @@ fn handleOpen(out: []u8, path: []const u8, flags: u32, sender: u32) usize {
fn onMessage(message: []const u8, out: []u8, sender: u32, capability: ?runtime.ipc.Handle) usize { fn onMessage(message: []const u8, out: []u8, sender: u32, capability: ?runtime.ipc.Handle) usize {
_ = capability; _ = capability;
if (!mounted) return fail(out); // storage not up (yet): fail politely, clients retry if (!mounted) return fail(out); // storage not up (yet): fail politely, clients retry
if (message.len < protocol.request_size) return fail(out); if (message.len < vfs_protocol.request_size) return fail(out);
const request = std.mem.bytesToValue(protocol.Request, message[0..protocol.request_size]); const request = std.mem.bytesToValue(vfs_protocol.Request, message[0..vfs_protocol.request_size]);
const payload = message[protocol.request_size..]; const payload = message[vfs_protocol.request_size..];
// Stamp create/write with the current wall-clock time (mtime). Cheap, and it // Stamp create/write with the current wall-clock time (mtime). Cheap, and it
// keeps the engine pure (it takes the time as data, not a syscall). // keeps the engine pure (it takes the time as data, not a syscall).
@@ -214,7 +214,7 @@ fn onMessage(message: []const u8, out: []u8, sender: u32, capability: ?runtime.i
.open => return handleOpen(out, payload[0..@min(payload.len, request.len)], request.flags, sender), .open => return handleOpen(out, payload[0..@min(payload.len, request.len)], request.flags, sender),
.read => { .read => {
const o = openAt(request.node) orelse return fail(out); const o = openAt(request.node) orelse return fail(out);
var buffer: [protocol.maximum_payload]u8 = undefined; var buffer: [vfs_protocol.maximum_payload]u8 = undefined;
const want = @min(@as(usize, request.len), buffer.len); const want = @min(@as(usize, request.len), buffer.len);
const n = filesystem.readFile(o.node, @intCast(request.offset), buffer[0..want]); const n = filesystem.readFile(o.node, @intCast(request.offset), buffer[0..want]);
return writeReply(out, .{ .status = 0, .len = @intCast(n) }, buffer[0..n]); return writeReply(out, .{ .status = 0, .len = @intCast(n) }, buffer[0..n]);
@@ -227,21 +227,21 @@ fn onMessage(message: []const u8, out: []u8, sender: u32, capability: ?runtime.i
}, },
.status => { .status => {
const o = openAt(request.node) orelse return fail(out); const o = openAt(request.node) orelse return fail(out);
const kind: protocol.NodeKind = if (o.node.is_directory) .directory else .regular; const kind: vfs_protocol.NodeKind = if (o.node.is_directory) .directory else .regular;
const status = protocol.FileStatus{ .size = o.node.size, .kind = @intFromEnum(kind), .mtime = o.node.mtime }; const status = vfs_protocol.FileStatus{ .size = o.node.size, .kind = @intFromEnum(kind), .mtime = o.node.mtime };
return writeReply(out, .{ .status = 0, .len = @sizeOf(protocol.FileStatus) }, std.mem.asBytes(&status)); return writeReply(out, .{ .status = 0, .len = @sizeOf(vfs_protocol.FileStatus) }, std.mem.asBytes(&status));
}, },
.readdir => { .readdir => {
const o = openAt(request.node) orelse return fail(out); const o = openAt(request.node) orelse return fail(out);
if (!o.node.is_directory) return writeReply(out, .{ .status = 0, .len = 0 }, &.{}); if (!o.node.is_directory) return writeReply(out, .{ .status = 0, .len = 0 }, &.{});
const listing = filesystem.listEntry(o.node, @intCast(request.offset)) orelse return writeReply(out, .{ .status = 0, .len = 0 }, &.{}); const listing = filesystem.listEntry(o.node, @intCast(request.offset)) orelse return writeReply(out, .{ .status = 0, .len = 0 }, &.{});
const kind: protocol.NodeKind = if (listing.is_directory) .directory else .regular; const kind: vfs_protocol.NodeKind = if (listing.is_directory) .directory else .regular;
const header = protocol.DirectoryEntry{ .kind = @intFromEnum(kind), .name_len = @intCast(listing.name_len), .size = listing.size }; const header = vfs_protocol.DirectoryEntry{ .kind = @intFromEnum(kind), .name_len = @intCast(listing.name_len), .size = listing.size };
var buffer: [protocol.maximum_payload]u8 = undefined; var buffer: [vfs_protocol.maximum_payload]u8 = undefined;
@memcpy(buffer[0..protocol.directory_entry_size], std.mem.asBytes(&header)); @memcpy(buffer[0..vfs_protocol.directory_entry_size], std.mem.asBytes(&header));
const nlen = @min(listing.name_len, buffer.len - protocol.directory_entry_size); const nlen = @min(listing.name_len, buffer.len - vfs_protocol.directory_entry_size);
@memcpy(buffer[protocol.directory_entry_size..][0..nlen], listing.name_buffer[0..nlen]); @memcpy(buffer[vfs_protocol.directory_entry_size..][0..nlen], listing.name_buffer[0..nlen]);
const total = protocol.directory_entry_size + nlen; const total = vfs_protocol.directory_entry_size + nlen;
return writeReply(out, .{ .status = 0, .len = @intCast(total) }, buffer[0..total]); return writeReply(out, .{ .status = 0, .len = @intCast(total) }, buffer[0..total]);
}, },
.close => { .close => {
@@ -287,7 +287,7 @@ fn onMessage(message: []const u8, out: []u8, sender: u32, capability: ?runtime.i
} }
pub fn main() void { pub fn main() void {
runtime.service.run(protocol.message_maximum, .{ runtime.service.run(vfs_protocol.message_maximum, .{
.service = .fat, .service = .fat,
.init = initialise, .init = initialise,
.on_message = onMessage, .on_message = onMessage,