summaryrefslogtreecommitdiff
path: root/9ns/src/fuse.zig
diff options
context:
space:
mode:
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));
}