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:
+23
-23
@@ -13,7 +13,7 @@ const std = @import("std");
|
||||
const runtime = @import("runtime");
|
||||
const engine = @import("engine.zig");
|
||||
const on_disk = @import("on-disk.zig");
|
||||
const protocol = runtime.vfs_protocol;
|
||||
const vfs_protocol = @import("vfs-protocol");
|
||||
const dma = runtime.dma;
|
||||
|
||||
const mount_point = "/mnt/usb";
|
||||
@@ -69,11 +69,11 @@ fn openAt(id: u64) ?*OpenNode {
|
||||
return if (o.used) o else null;
|
||||
}
|
||||
|
||||
fn writeReply(out: []u8, reply: protocol.Reply, payload: []const u8) usize {
|
||||
@memcpy(out[0..protocol.reply_size], std.mem.asBytes(&reply));
|
||||
const n = @min(payload.len, out.len - protocol.reply_size);
|
||||
@memcpy(out[protocol.reply_size..][0..n], payload[0..n]);
|
||||
return protocol.reply_size + n;
|
||||
fn writeReply(out: []u8, reply: vfs_protocol.Reply, payload: []const u8) usize {
|
||||
@memcpy(out[0..vfs_protocol.reply_size], std.mem.asBytes(&reply));
|
||||
const n = @min(payload.len, out.len - vfs_protocol.reply_size);
|
||||
@memcpy(out[vfs_protocol.reply_size..][0..n], payload[0..n]);
|
||||
return vfs_protocol.reply_size + n;
|
||||
}
|
||||
|
||||
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 {
|
||||
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 parent = filesystem.resolve(split.parent) orelse return fail(out);
|
||||
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);
|
||||
// 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).
|
||||
if (flags & protocol.truncate != 0 and !resolved.is_directory) {
|
||||
if (flags & vfs_protocol.truncate != 0 and !resolved.is_directory) {
|
||||
filesystem.truncate(&resolved);
|
||||
}
|
||||
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 {
|
||||
_ = capability;
|
||||
if (!mounted) return fail(out); // storage not up (yet): fail politely, clients retry
|
||||
if (message.len < protocol.request_size) return fail(out);
|
||||
const request = std.mem.bytesToValue(protocol.Request, message[0..protocol.request_size]);
|
||||
const payload = message[protocol.request_size..];
|
||||
if (message.len < vfs_protocol.request_size) return fail(out);
|
||||
const request = std.mem.bytesToValue(vfs_protocol.Request, message[0..vfs_protocol.request_size]);
|
||||
const payload = message[vfs_protocol.request_size..];
|
||||
|
||||
// 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).
|
||||
@@ -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),
|
||||
.read => {
|
||||
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 n = filesystem.readFile(o.node, @intCast(request.offset), buffer[0..want]);
|
||||
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 => {
|
||||
const o = openAt(request.node) orelse return fail(out);
|
||||
const kind: 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 };
|
||||
return writeReply(out, .{ .status = 0, .len = @sizeOf(protocol.FileStatus) }, std.mem.asBytes(&status));
|
||||
const kind: vfs_protocol.NodeKind = if (o.node.is_directory) .directory else .regular;
|
||||
const status = vfs_protocol.FileStatus{ .size = o.node.size, .kind = @intFromEnum(kind), .mtime = o.node.mtime };
|
||||
return writeReply(out, .{ .status = 0, .len = @sizeOf(vfs_protocol.FileStatus) }, std.mem.asBytes(&status));
|
||||
},
|
||||
.readdir => {
|
||||
const o = openAt(request.node) orelse return fail(out);
|
||||
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 kind: 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 };
|
||||
var buffer: [protocol.maximum_payload]u8 = undefined;
|
||||
@memcpy(buffer[0..protocol.directory_entry_size], std.mem.asBytes(&header));
|
||||
const nlen = @min(listing.name_len, buffer.len - protocol.directory_entry_size);
|
||||
@memcpy(buffer[protocol.directory_entry_size..][0..nlen], listing.name_buffer[0..nlen]);
|
||||
const total = protocol.directory_entry_size + nlen;
|
||||
const kind: vfs_protocol.NodeKind = if (listing.is_directory) .directory else .regular;
|
||||
const header = vfs_protocol.DirectoryEntry{ .kind = @intFromEnum(kind), .name_len = @intCast(listing.name_len), .size = listing.size };
|
||||
var buffer: [vfs_protocol.maximum_payload]u8 = undefined;
|
||||
@memcpy(buffer[0..vfs_protocol.directory_entry_size], std.mem.asBytes(&header));
|
||||
const nlen = @min(listing.name_len, buffer.len - vfs_protocol.directory_entry_size);
|
||||
@memcpy(buffer[vfs_protocol.directory_entry_size..][0..nlen], listing.name_buffer[0..nlen]);
|
||||
const total = vfs_protocol.directory_entry_size + nlen;
|
||||
return writeReply(out, .{ .status = 0, .len = @intCast(total) }, buffer[0..total]);
|
||||
},
|
||||
.close => {
|
||||
@@ -287,7 +287,7 @@ fn onMessage(message: []const u8, out: []u8, sender: u32, capability: ?runtime.i
|
||||
}
|
||||
|
||||
pub fn main() void {
|
||||
runtime.service.run(protocol.message_maximum, .{
|
||||
runtime.service.run(vfs_protocol.message_maximum, .{
|
||||
.service = .fat,
|
||||
.init = initialise,
|
||||
.on_message = onMessage,
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
//! The VFS wire protocol — the message format spoken between a client (via the file
|
||||
//! API) and the user-space VFS server over IPC. A request is a fixed `Request` header
|
||||
//! followed by an inline payload (a path, or write bytes); a reply is a fixed `Reply`
|
||||
//! header followed by an inline payload (read bytes, or a FileStatus). Everything fits
|
||||
//! in one IPC message (<= ipc MESSAGE_MAXIMUM = 256 bytes).
|
||||
//!
|
||||
//! This is a danos-native contract, so it uses danos names throughout. The client
|
||||
//! side is `runtime.fs` (library/runtime/fs.zig), which programs use directly.
|
||||
//!
|
||||
//! This is user-space only — the kernel knows nothing of files or paths; it only moves the bytes.
|
||||
//! Shared by library/runtime/fs.zig (client) and system/services/vfs/vfs.zig (server).
|
||||
|
||||
pub const Operation = enum(u32) {
|
||||
open, // open(path) -> node id
|
||||
close, // close(node)
|
||||
read, // read(node, offset, len) -> bytes
|
||||
write, // write(node, offset, bytes) -> count
|
||||
status, // status(node) -> FileStatus
|
||||
// Appended for the mount router (M5). Values stay stable, so existing clients
|
||||
// and the flat-ramfs tests are unaffected.
|
||||
readdir, // readdir(dir_node, cursor=offset) -> one DirectoryEntry (len==0 => EOF)
|
||||
mount, // mount(prefix payload, capability = backend endpoint)
|
||||
unmount, // unmount(prefix payload)
|
||||
// Appended for filesystem mutation (Phase 2). Path-based (the path is the
|
||||
// payload); a mounted backend handles them, the flat ramfs refuses them.
|
||||
mkdir, // mkdir(path payload) -> status
|
||||
unlink, // unlink(path payload) -> status
|
||||
// rename: the payload is the old path, a single 0x00 separator, then the new
|
||||
// path. Same-directory rename only (the router requires both under one mount).
|
||||
rename, // rename(old\0new payload) -> status
|
||||
};
|
||||
|
||||
/// The type of a filesystem node, aligned to the FSH file-type table
|
||||
/// (docs/danos-file-system-hierarchy-FSH.md). Fills `FileStatus.kind` and
|
||||
/// `DirectoryEntry.kind`; `regular = 0` keeps the historical hardcoded value.
|
||||
pub const NodeKind = enum(u32) {
|
||||
regular = 0,
|
||||
directory = 1,
|
||||
character_device = 2,
|
||||
block_device = 3,
|
||||
symbolic_link = 4,
|
||||
fifo = 5,
|
||||
socket = 6,
|
||||
};
|
||||
|
||||
/// One directory entry, returned by `readdir`: a fixed header followed inline in
|
||||
/// the reply payload by `name_len` bytes of name. A zero-length reply is EOF.
|
||||
pub const DirectoryEntry = extern struct {
|
||||
kind: u32, // a NodeKind
|
||||
name_len: u32,
|
||||
size: u64,
|
||||
};
|
||||
|
||||
pub const directory_entry_size: usize = @sizeOf(DirectoryEntry);
|
||||
|
||||
/// Request header. `node` is the server-side open-file id (from a prior open);
|
||||
/// for `open` the path is the payload and `len` is its length. `offset`/`len`
|
||||
/// carry the read/write position and count.
|
||||
pub const Request = extern struct {
|
||||
operation: Operation,
|
||||
node: u64,
|
||||
offset: u64,
|
||||
len: u32,
|
||||
flags: u32,
|
||||
};
|
||||
|
||||
/// Reply header. `status` is 0 on success or a negative errno; `node` is the new
|
||||
/// open-file id (for `open`); `len` is the payload length (bytes read, or the
|
||||
/// FileStatus size).
|
||||
pub const Reply = extern struct {
|
||||
status: i32,
|
||||
_padding: u32 = 0,
|
||||
node: u64 = 0,
|
||||
len: u32 = 0,
|
||||
_padding2: u32 = 0,
|
||||
};
|
||||
|
||||
/// A file's metadata (the danos-native answer to a `status` request). The POSIX
|
||||
/// layer maps this onto `struct stat`.
|
||||
pub const FileStatus = extern struct {
|
||||
size: u64,
|
||||
kind: u32,
|
||||
_padding: u32 = 0,
|
||||
/// Modification time — Unix epoch seconds, UTC. 0 if the backend has none (the
|
||||
/// flat ramfs). Filled from the FAT directory entry's write date/time.
|
||||
mtime: u64 = 0,
|
||||
};
|
||||
|
||||
pub const message_maximum: usize = 256;
|
||||
pub const request_size: usize = @sizeOf(Request);
|
||||
pub const reply_size: usize = @sizeOf(Reply);
|
||||
/// Largest inline payload that still fits one IPC message alongside a header.
|
||||
pub const maximum_payload: usize = message_maximum - request_size;
|
||||
|
||||
/// Open flags (danos-native; `runtime.fs.OpenOptions` maps its booleans onto these).
|
||||
pub const create: u32 = 1;
|
||||
/// Open a directory (for readdir) rather than a file. A mounted backend uses
|
||||
/// this to open a directory node; the flat ramfs ignores it.
|
||||
pub const directory: u32 = 2;
|
||||
/// Truncate the file to zero length on open (O_TRUNC): replace its contents rather
|
||||
/// than overwriting in place, so a shorter new file leaves no stale tail. A mounted
|
||||
/// backend frees the old cluster chain; the flat ramfs ignores it.
|
||||
pub const truncate: u32 = 4;
|
||||
|
||||
test "protocol struct sizes and node kinds" {
|
||||
const std = @import("std");
|
||||
try std.testing.expectEqual(@as(u32, 0), @intFromEnum(NodeKind.regular));
|
||||
try std.testing.expectEqual(@as(u32, 1), @intFromEnum(NodeKind.directory));
|
||||
try std.testing.expectEqual(@as(usize, 16), @sizeOf(DirectoryEntry));
|
||||
// The appended operations keep the original values.
|
||||
try std.testing.expectEqual(@as(u32, 0), @intFromEnum(Operation.open));
|
||||
try std.testing.expectEqual(@as(u32, 4), @intFromEnum(Operation.status));
|
||||
try std.testing.expectEqual(@as(u32, 5), @intFromEnum(Operation.readdir));
|
||||
}
|
||||
Reference in New Issue
Block a user