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:
+22
-26
@@ -192,41 +192,37 @@ fn reach(path: []const u8) ?Registry {
|
||||
}
|
||||
}
|
||||
|
||||
/// One vfs-protocol round trip at a backend: fixed header, inline payload, and
|
||||
/// an optional capability in each direction.
|
||||
/// One vfs-protocol round trip at a backend: the folded header, the verb's own
|
||||
/// fixed part, the name as the packet's tail, and an optional capability in each
|
||||
/// direction. Both verbs this file sends address the backend itself (target 0) —
|
||||
/// the name in the tail is what they are about.
|
||||
fn transact(
|
||||
comptime operation: vfs_protocol.Operation,
|
||||
handle: ipc.Handle,
|
||||
operation: vfs_protocol.Operation,
|
||||
payload: []const u8,
|
||||
request: vfs_protocol.Protocol.RequestOf(operation),
|
||||
name: []const u8,
|
||||
send_capability: ?ipc.Handle,
|
||||
) ?struct { reply: vfs_protocol.Reply, capability: ?ipc.Handle } {
|
||||
var request: [vfs_protocol.message_maximum]u8 = undefined;
|
||||
if (vfs_protocol.request_size + payload.len > request.len) return null;
|
||||
const header = vfs_protocol.Request{
|
||||
.operation = operation,
|
||||
.node = 0,
|
||||
.offset = 0,
|
||||
.len = @intCast(payload.len),
|
||||
.flags = 0,
|
||||
};
|
||||
@memcpy(request[0..vfs_protocol.request_size], std.mem.asBytes(&header));
|
||||
@memcpy(request[vfs_protocol.request_size..][0..payload.len], payload);
|
||||
) ?struct { status: envelope.Status, capability: ?ipc.Handle } {
|
||||
var packet: [vfs_protocol.message_maximum]u8 = undefined;
|
||||
const framed = vfs_protocol.Protocol.encodeRequest(operation, 0, request, name, &packet) orelse return null;
|
||||
|
||||
var reply: [vfs_protocol.message_maximum]u8 = undefined;
|
||||
const answer = ipc.callCap(handle, request[0 .. vfs_protocol.request_size + payload.len], &reply, send_capability) catch return null;
|
||||
if (answer.len < vfs_protocol.reply_size) return null;
|
||||
return .{
|
||||
.reply = std.mem.bytesToValue(vfs_protocol.Reply, reply[0..vfs_protocol.reply_size]),
|
||||
.capability = answer.cap,
|
||||
};
|
||||
const answer = ipc.callCap(handle, framed, &reply, send_capability) catch return null;
|
||||
const status = envelope.statusOf(reply[0..answer.len]) orelse return null;
|
||||
return .{ .status = status, .capability = answer.cap };
|
||||
}
|
||||
|
||||
/// Resolve an absolute `/protocol/...` path and take the provider's endpoint out
|
||||
/// of the open reply's capability.
|
||||
fn openPath(path: []const u8) ?ipc.Handle {
|
||||
const registry = reach(path) orelse return null;
|
||||
const answered = transact(registry.handle, .open, registry.path(), null) orelse return null;
|
||||
if (answered.reply.status != 0) return null;
|
||||
const answered = transact(.open, registry.handle, .{ .flags = 0 }, registry.path(), null) orelse return null;
|
||||
if (answered.status.status != 0) {
|
||||
// A refusal carries no channel; anything that arrived anyway would be a
|
||||
// handle-table slot spent for nothing.
|
||||
if (answered.capability) |handle| _ = ipc.close(handle);
|
||||
return null;
|
||||
}
|
||||
// The capability *is* the channel — an open that succeeds without one was
|
||||
// answered by a file backend, which does not speak protocols.
|
||||
return answered.capability;
|
||||
@@ -257,8 +253,8 @@ pub fn openEndpoint(name: []const u8) ?ipc.Handle {
|
||||
/// `-EBUSY` a live provider already holds it.
|
||||
pub fn bind(name: []const u8, endpoint: ipc.Handle) ?i32 {
|
||||
const registry = reach(root) orelse return null;
|
||||
const answered = transact(registry.handle, .bind, name, endpoint) orelse return null;
|
||||
return answered.reply.status;
|
||||
const answered = transact(.bind, registry.handle, {}, name, endpoint) orelse return null;
|
||||
return answered.status.status;
|
||||
}
|
||||
|
||||
/// How long a provider keeps offering itself before giving up. The registry is
|
||||
|
||||
Reference in New Issue
Block a user