library: five protocols speak the envelope
The folded header stops being a rule in a document and becomes the layout on the wire. Verbs number from sixteen, leaving describe, enumerate, subscribe and unsubscribe reserved and answered the same way by every provider — none of them writes a line to do it. What each protocol used to carry in a field of its own now travels in the header: a vfs node and a display layer are the packet's target, and a reply opens with a status the envelope stamps rather than one each protocol spelled for itself. Display gains the most. One forty-byte request had served eleven verbs, so attach_scanout smuggled stride through x, refresh through y and format through colour, and every coordinate crossed as a bitcast. Per-operation structs end all three: the fields have their own names and their own signs, and the tile payload grows to 224 bytes because the prefix shrank. Scanout loses a message maximum of 64 it had no business declaring — it answers calls, and the floor for a call is 256 — and virtio-gpu stops hard-coding that number at its harness. Two changes are semantic rather than notational. A directory now ends at an entry with no name, because the fixed part of a reply always travels and a zero-length reply no longer exists to mean anything. And input joins the service harness, the last loop in the tree that answered no ping and heard no terminate; its subscriber table, its pruning and its fan-out are the same code, and a shutdown now asks it to stop instead of killing it. A new conformance case reads the registry's own listing and asks every protocol it finds for its name, its version and its verb count, then offers a verb nobody defines and requires -ENOSYS — the envelope's promise, checked against providers rather than against itself. What it cannot reach in that boot it names on the serial line instead of passing quietly. Suite 110/110.
This commit is contained in:
@@ -14,8 +14,13 @@ const std = @import("std");
|
||||
const abi = @import("abi");
|
||||
const sc = @import("system-call");
|
||||
const ipc = @import("ipc");
|
||||
const envelope = @import("envelope");
|
||||
const vfs_protocol = @import("vfs-protocol");
|
||||
|
||||
/// The generated vfs contract: encode/decode for every verb, with the node id
|
||||
/// carried in the packet header's `target`.
|
||||
const Protocol = vfs_protocol.Protocol;
|
||||
|
||||
/// The kind of a filesystem node — re-exported so a caller need not import the
|
||||
/// wire protocol.
|
||||
pub const Kind = vfs_protocol.NodeKind;
|
||||
@@ -89,23 +94,24 @@ fn resolve(path: []const u8, flags: usize) ?Route {
|
||||
}
|
||||
}
|
||||
|
||||
const Result = struct { reply: vfs_protocol.Reply, payload: []u8 };
|
||||
|
||||
// One request/reply round trip: [Request header][send payload] -> backend ->
|
||||
// [Reply header][receive payload]. The receive payload lands in `out`.
|
||||
fn transact(h: ipc.Handle, request: vfs_protocol.Request, send: []const u8, out: []u8) ?Result {
|
||||
var message: [vfs_protocol.message_maximum]u8 = undefined;
|
||||
@memcpy(message[0..vfs_protocol.request_size], std.mem.asBytes(&request));
|
||||
const slen = @min(send.len, vfs_protocol.maximum_payload);
|
||||
@memcpy(message[vfs_protocol.request_size..][0..slen], send[0..slen]);
|
||||
|
||||
var rbuf: [vfs_protocol.message_maximum]u8 = undefined;
|
||||
const n = ipc.call(h, message[0 .. vfs_protocol.request_size + slen], &rbuf) catch return null;
|
||||
if (n < vfs_protocol.reply_size) return null;
|
||||
const reply = std.mem.bytesToValue(vfs_protocol.Reply, rbuf[0..vfs_protocol.reply_size]);
|
||||
const rpl = @min(n - vfs_protocol.reply_size, out.len);
|
||||
@memcpy(out[0..rpl], rbuf[vfs_protocol.reply_size..][0..rpl]);
|
||||
return .{ .reply = reply, .payload = out[0..rpl] };
|
||||
// One request/reply round trip: frame `[Header][request][tail]`, send it, and
|
||||
// hand back the whole reply packet for the caller to decode with the generated
|
||||
// helpers. A backend that refused (a negative status) reads as null, which is
|
||||
// what every caller here did with it anyway.
|
||||
fn transact(
|
||||
comptime operation: Protocol.Operation,
|
||||
handle: ipc.Handle,
|
||||
target: u64,
|
||||
request: Protocol.RequestOf(operation),
|
||||
tail: []const u8,
|
||||
reply: []u8,
|
||||
) ?[]u8 {
|
||||
var packet: [vfs_protocol.message_maximum]u8 = undefined;
|
||||
const framed = Protocol.encodeRequest(operation, target, request, tail, &packet) orelse return null;
|
||||
const n = ipc.call(handle, framed, reply) catch return null;
|
||||
const status = envelope.statusOf(reply[0..n]) orelse return null;
|
||||
if (status.status != 0) return null;
|
||||
return reply[0..n];
|
||||
}
|
||||
|
||||
/// An open file: a VFS node plus a byte cursor. Read and write advance the cursor.
|
||||
@@ -125,11 +131,13 @@ pub const File = struct {
|
||||
return n;
|
||||
};
|
||||
const want: u32 = @intCast(@min(buffer.len, vfs_protocol.maximum_payload));
|
||||
const request = vfs_protocol.Request{ .operation = .read, .node = self.node, .offset = self.offset, .len = want, .flags = 0 };
|
||||
const r = transact(h, request, &.{}, buffer) orelse return null;
|
||||
if (r.reply.status != 0) return null;
|
||||
self.offset += r.reply.len;
|
||||
return r.reply.len;
|
||||
var reply: [vfs_protocol.message_maximum]u8 = undefined;
|
||||
const answered = transact(.read, h, self.node, .{ .offset = self.offset, .len = want }, &.{}, &reply) orelse return null;
|
||||
const bytes = Protocol.replyTail(.read, answered);
|
||||
const n = @min(bytes.len, buffer.len);
|
||||
@memcpy(buffer[0..n], bytes[0..n]);
|
||||
self.offset += n;
|
||||
return n;
|
||||
}
|
||||
|
||||
/// Write `data` at the current offset; returns the count written. A single
|
||||
@@ -139,11 +147,11 @@ pub const File = struct {
|
||||
pub fn write(self: *File, data: []const u8) ?usize {
|
||||
const h = self.backend orelse return null;
|
||||
const want: u32 = @intCast(@min(data.len, vfs_protocol.maximum_payload));
|
||||
const request = vfs_protocol.Request{ .operation = .write, .node = self.node, .offset = self.offset, .len = want, .flags = 0 };
|
||||
const r = transact(h, request, data[0..want], &.{}) orelse return null;
|
||||
if (r.reply.status != 0) return null;
|
||||
self.offset += r.reply.len;
|
||||
return r.reply.len;
|
||||
var reply: [vfs_protocol.message_maximum]u8 = undefined;
|
||||
const answered = transact(.write, h, self.node, .{ .offset = self.offset, .len = want }, data[0..want], &reply) orelse return null;
|
||||
const written = Protocol.decodeReply(.write, answered) orelse return null;
|
||||
self.offset += written.count;
|
||||
return written.count;
|
||||
}
|
||||
|
||||
/// Write all of `data`, looping past the per-call payload cap. Returns the
|
||||
@@ -169,11 +177,9 @@ pub const File = struct {
|
||||
const a = fsNodeStatus(self.node) orelse return null;
|
||||
return .{ .size = a.size, .kind = if (a.kind == file_kind_directory) .directory else .regular, .mtime = a.mtime };
|
||||
};
|
||||
const request = vfs_protocol.Request{ .operation = .status, .node = self.node, .offset = 0, .len = 0, .flags = 0 };
|
||||
var buffer: [@sizeOf(vfs_protocol.FileStatus)]u8 = undefined;
|
||||
const r = transact(h, request, &.{}, &buffer) orelse return null;
|
||||
if (r.reply.status != 0 or r.payload.len < @sizeOf(vfs_protocol.FileStatus)) return null;
|
||||
const status = std.mem.bytesToValue(vfs_protocol.FileStatus, buffer[0..@sizeOf(vfs_protocol.FileStatus)]);
|
||||
var reply: [vfs_protocol.message_maximum]u8 = undefined;
|
||||
const answered = transact(.status, h, self.node, {}, &.{}, &reply) orelse return null;
|
||||
const status = Protocol.decodeReply(.status, answered) orelse return null;
|
||||
return .{ .size = status.size, .kind = kindFromWire(status.kind), .mtime = status.mtime };
|
||||
}
|
||||
|
||||
@@ -181,8 +187,8 @@ pub const File = struct {
|
||||
/// tokens are permanent — nothing to release.
|
||||
pub fn close(self: *File) void {
|
||||
const h = self.backend orelse return;
|
||||
const request = vfs_protocol.Request{ .operation = .close, .node = self.node, .offset = 0, .len = 0, .flags = 0 };
|
||||
_ = transact(h, request, &.{}, &.{});
|
||||
var reply: [vfs_protocol.message_maximum]u8 = undefined;
|
||||
_ = transact(.close, h, self.node, {}, &.{}, &reply);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -193,10 +199,10 @@ pub fn open(path: []const u8, options: OpenOptions) ?File {
|
||||
.kernel => |token| return .{ .node = token, .backend = null },
|
||||
.backend => |b| {
|
||||
const relative = route.backendPath();
|
||||
const request = vfs_protocol.Request{ .operation = .open, .node = 0, .offset = 0, .len = @intCast(relative.len), .flags = options.wireFlags() };
|
||||
const r = transact(b.handle, request, relative, &.{}) orelse return null;
|
||||
if (r.reply.status != 0) return null;
|
||||
return .{ .node = r.reply.node, .backend = b.handle };
|
||||
var reply: [vfs_protocol.message_maximum]u8 = undefined;
|
||||
const answered = transact(.open, b.handle, 0, .{ .flags = options.wireFlags() }, relative, &reply) orelse return null;
|
||||
const opened = Protocol.decodeReply(.open, answered) orelse return null;
|
||||
return .{ .node = opened.node, .backend = b.handle };
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -248,15 +254,13 @@ pub const Directory = struct {
|
||||
self.cursor += 1;
|
||||
return true;
|
||||
};
|
||||
const request = vfs_protocol.Request{ .operation = .readdir, .node = self.node, .offset = self.cursor, .len = 0, .flags = 0 };
|
||||
var buffer: [vfs_protocol.message_maximum]u8 = undefined;
|
||||
const r = transact(h, request, &.{}, &buffer) orelse return false;
|
||||
if (r.reply.status != 0 or r.reply.len == 0) return false; // error or EOF
|
||||
if (r.payload.len < vfs_protocol.directory_entry_size) return false;
|
||||
const header = std.mem.bytesToValue(vfs_protocol.DirectoryEntry, r.payload[0..vfs_protocol.directory_entry_size]);
|
||||
var reply: [vfs_protocol.message_maximum]u8 = undefined;
|
||||
const answered = transact(.readdir, h, self.node, .{ .cursor = self.cursor }, &.{}, &reply) orelse return false;
|
||||
const header = Protocol.decodeReply(.readdir, answered) orelse return false;
|
||||
if (header.name_len == 0) return false; // end of directory
|
||||
entry.kind = kindFromWire(header.kind);
|
||||
entry.size = header.size;
|
||||
const source = r.payload[vfs_protocol.directory_entry_size..];
|
||||
const source = Protocol.replyTail(.readdir, answered);
|
||||
const nlen = @min(@min(@as(usize, header.name_len), source.len), entry.name_buffer.len);
|
||||
@memcpy(entry.name_buffer[0..nlen], source[0..nlen]);
|
||||
entry.name_len = nlen;
|
||||
@@ -280,13 +284,11 @@ pub fn openDirectory(path: []const u8) ?Directory {
|
||||
// A path-based request that returns only a status (mkdir, unlink). Kernel-served
|
||||
// paths (the read-only /system) refuse mutation by construction: the resolve
|
||||
// must land on a backend.
|
||||
fn pathOperation(operation: vfs_protocol.Operation, path: []const u8) bool {
|
||||
fn pathOperation(comptime operation: Protocol.Operation, path: []const u8) bool {
|
||||
const route = resolve(path, 0) orelse return false;
|
||||
if (route != .backend) return false;
|
||||
const relative = route.backendPath();
|
||||
const request = vfs_protocol.Request{ .operation = operation, .node = 0, .offset = 0, .len = @intCast(relative.len), .flags = 0 };
|
||||
const r = transact(route.backend.handle, request, relative, &.{}) orelse return false;
|
||||
return r.reply.status == 0;
|
||||
var reply: [vfs_protocol.message_maximum]u8 = undefined;
|
||||
return transact(operation, route.backend.handle, 0, {}, route.backendPath(), &reply) != null;
|
||||
}
|
||||
|
||||
/// Create a directory at `path` (its parent must already exist). Returns true on
|
||||
@@ -336,9 +338,8 @@ pub fn rename(old_path: []const u8, new_path: []const u8) bool {
|
||||
@memcpy(payload[0..old_relative.len], old_relative);
|
||||
payload[old_relative.len] = 0;
|
||||
@memcpy(payload[old_relative.len + 1 ..][0..new_relative.len], new_relative);
|
||||
const request = vfs_protocol.Request{ .operation = .rename, .node = 0, .offset = 0, .len = @intCast(total), .flags = 0 };
|
||||
const r = transact(old_route.backend.handle, request, payload[0..total], &.{}) orelse return false;
|
||||
return r.reply.status == 0;
|
||||
var reply: [vfs_protocol.message_maximum]u8 = undefined;
|
||||
return transact(.rename, old_route.backend.handle, 0, {}, payload[0..total], &reply) != null;
|
||||
}
|
||||
|
||||
/// Mount a filesystem backend (its server endpoint) at absolute path `target`;
|
||||
|
||||
Reference in New Issue
Block a user