diff options
Diffstat (limited to '9ns/src/fuse.zig')
| -rw-r--r-- | 9ns/src/fuse.zig | 56 |
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)); } |
