summaryrefslogtreecommitdiff
path: root/9ns/src/fuse.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-19 23:55:47 -0300
committerGabriel Schneider <[email protected]>2026-09-19 23:55:47 -0300
commit3e9f8805f293f622bb885cf849b5ce47dc062ad1 (patch)
tree0f31107fd20e7a9aa065826c891d2d326efc2309 /9ns/src/fuse.zig
parentba996acfcad1698adbf4a1834fe50e73b1c6cab9 (diff)
downloadcloud9-3e9f8805f293f622bb885cf849b5ce47dc062ad1.tar.gz
cloud9-3e9f8805f293f622bb885cf849b5ce47dc062ad1.zip
9ns: --name and /mnt/9p/<name> mounts, qid.path as inode number, interrupts as Tflush
- --name NAME (default derived from the transport: socket basename, tcp-IP-PORT, spawned command, fdN) mounts at /mnt/9p/<name>; --mount still overrides. ensureMountpoint walks down and creates missing components, shadowing the deepest unwritable ancestor. NINE_MOUNT is the only exported variable. - The inode number reported to the kernel is the 9P qid.path for every node, root included; a server handing qid.path 1 to a file (Pardes /self) no longer collides with the root. - FUSE_INTERRUPT for the request in flight becomes Tflush; a blocked read returns EINTR when the server answers the flush, chunked transfers return short counts, other requests arriving meanwhile are stashed and served next. Servers ignoring Tflush still block until they answer. - 9ns-test now covers nine/bridge/fuse; new adv_bridge_interrupt suite (28); 9ns-itest grows to 88 checks. Co-Authored-By: Claude Fable 5.1 <[email protected]>
Diffstat (limited to '9ns/src/fuse.zig')
-rw-r--r--9ns/src/fuse.zig56
1 files changed, 42 insertions, 14 deletions
diff --git a/9ns/src/fuse.zig b/9ns/src/fuse.zig
index 682b3d5..216e616 100644
--- a/9ns/src/fuse.zig
+++ b/9ns/src/fuse.zig
@@ -314,7 +314,7 @@ comptime {
// Request / reply helpers
// ---------------------------------------------------------------------------
-pub const Error = error{ Protocol, Io, TooManyPayloads };
+pub const Error = error{ Protocol, Io, TooManyPayloads, Retry };
pub const Request = struct {
header: InHeader,
@@ -327,22 +327,42 @@ pub const Request = struct {
/// `max_write + 4096` bytes and 8-byte aligned so `body()` can view it.
pub fn readRequest(fd: i32, buf: []u8) Error!?Request {
while (true) {
- const rc = linux.read(fd, buf.ptr, buf.len);
- switch (linux.errno(rc)) {
- .SUCCESS => {
- const n: usize = rc;
- if (n < @sizeOf(InHeader)) return error.Protocol;
- const header = std.mem.bytesToValue(InHeader, buf[0..@sizeOf(InHeader)]);
- if (header.len != n) return error.Protocol;
- return .{ .header = header, .body = buf[@sizeOf(InHeader)..n] };
- },
- .INTR, .AGAIN, .NOENT => continue,
- .NODEV => return null,
- else => return error.Io,
- }
+ return readRequestOnce(fd, buf) catch |e| switch (e) {
+ error.Retry => continue,
+ else => return e,
+ };
+ }
+}
+
+/// One `read(2)` attempt: like `readRequest` but EINTR/EAGAIN/ENOENT surface as
+/// `error.Retry` instead of being retried, so a caller that only reads after
+/// `poll` (or on a non-blocking fd) never blocks in here.
+pub fn readRequestOnce(fd: i32, buf: []u8) Error!?Request {
+ const rc = linux.read(fd, buf.ptr, buf.len);
+ switch (linux.errno(rc)) {
+ .SUCCESS => {
+ const n: usize = rc;
+ if (n < @sizeOf(InHeader)) return error.Protocol;
+ const header = std.mem.bytesToValue(InHeader, buf[0..@sizeOf(InHeader)]);
+ if (header.len != n) return error.Protocol;
+ return .{ .header = header, .body = buf[@sizeOf(InHeader)..n] };
+ },
+ .INTR, .AGAIN, .NOENT => return error.Retry,
+ .NODEV => return null,
+ else => return error.Io,
}
}
+/// Sets O_NONBLOCK on `fd` so that a read after `poll` cannot block when the
+/// kernel withdrew the request in between (a killed waiter, for instance).
+pub fn setNonblocking(fd: i32) Error!void {
+ const cur = linux.fcntl(fd, linux.F.GETFL, 0);
+ if (linux.errno(cur) != .SUCCESS) return error.Io;
+ const nonblock: u32 = @bitCast(linux.O{ .NONBLOCK = true });
+ const rc = linux.fcntl(fd, linux.F.SETFL, @as(usize, cur) | nonblock);
+ if (linux.errno(rc) != .SUCCESS) return error.Io;
+}
+
/// Maximum number of payload slices a single `reply` can carry.
pub const max_payloads = 7;
@@ -650,4 +670,12 @@ test "readRequest parses one request from a pipe and rejects bad lengths" {
std.mem.bytesAsValue(InHeader, bad[0..40]).len = 40;
try testing.expectEqual(@as(usize, 48), linux.write(fds[1], &bad, bad.len));
try testing.expectError(error.Protocol, readRequest(fds[0], &buf));
+
+ // Non-blocking and empty: readRequestOnce reports Retry instead of waiting.
+ try setNonblocking(fds[0]);
+ try testing.expectError(error.Retry, readRequestOnce(fds[0], &buf));
+ try testing.expectEqual(@as(usize, 48), linux.write(fds[1], &wire, wire.len));
+ const again = (try readRequestOnce(fds[0], &buf)) orelse return error.Io;
+ try testing.expectEqual(@as(u64, 3), again.header.unique);
+ try testing.expectError(error.Retry, readRequestOnce(fds[0], &buf));
}