Commit 01f20c7f48

Vincent Rischmann <vincent@rischmann.fr>
2021-09-19 21:10:49
io_uring: implement read_fixed/write_fixed
1 parent 8c86043
Changed files (1)
lib
std
os
lib/std/os/linux/io_uring.zig
@@ -404,6 +404,25 @@ pub const IO_Uring = struct {
         return sqe;
     }
 
+    /// Queues (but does not submit) an SQE to perform a IORING_OP_READ_FIXED.
+    /// The `buffer` provided must be registered with the kernel by calling `register_buffers` first.
+    /// The `buffer_index` must be the same as its index in the array provided to `register_buffers`.
+    ///
+    /// Returns a pointer to the SQE so that you can further modify the SQE for advanced use cases.
+    pub fn read_fixed(
+        self: *IO_Uring,
+        user_data: u64,
+        fd: os.fd_t,
+        buffer: *os.iovec,
+        offset: u64,
+        buffer_index: u16,
+    ) !*io_uring_sqe {
+        const sqe = try self.get_sqe();
+        io_uring_prep_read_fixed(sqe, fd, buffer, offset, buffer_index);
+        sqe.user_data = user_data;
+        return sqe;
+    }
+
     /// Queues (but does not submit) an SQE to perform a `pwritev()`.
     /// Returns a pointer to the SQE so that you can further modify the SQE for advanced use cases.
     /// For example, if you want to do a `pwritev2()` then set `rw_flags` on the returned SQE.
@@ -421,6 +440,25 @@ pub const IO_Uring = struct {
         return sqe;
     }
 
+    /// Queues (but does not submit) an SQE to perform a IORING_OP_WRITE_FIXED.
+    /// The `buffer` provided must be registered with the kernel by calling `register_buffers` first.
+    /// The `buffer_index` must be the same as its index in the array provided to `register_buffers`.
+    ///
+    /// Returns a pointer to the SQE so that you can further modify the SQE for advanced use cases.
+    pub fn write_fixed(
+        self: *IO_Uring,
+        user_data: u64,
+        fd: os.fd_t,
+        buffer: *os.iovec,
+        offset: u64,
+        buffer_index: u16,
+    ) !*io_uring_sqe {
+        const sqe = try self.get_sqe();
+        io_uring_prep_write_fixed(sqe, fd, buffer, offset, buffer_index);
+        sqe.user_data = user_data;
+        return sqe;
+    }
+
     /// Queues (but does not submit) an SQE to perform an `accept4(2)` on a socket.
     /// Returns a pointer to the SQE.
     pub fn accept(
@@ -674,6 +712,29 @@ pub const IO_Uring = struct {
         try handle_registration_result(res);
     }
 
+    /// Registers an array of buffers for use with `read_fixed` and `write_fixed`.
+    pub fn register_buffers(self: *IO_Uring, buffers: []const os.iovec) !void {
+        assert(self.fd >= 0);
+        const res = linux.io_uring_register(
+            self.fd,
+            .REGISTER_BUFFERS,
+            buffers.ptr,
+            @intCast(u32, buffers.len),
+        );
+        try handle_registration_result(res);
+    }
+
+    /// Unregister the registered buffers.
+    pub fn unregister_buffers(self: *IO_Uring) !void {
+        assert(self.fd >= 0);
+        const res = linux.io_uring_register(self.fd, .UNREGISTER_BUFFERS, null, 0);
+        switch (linux.getErrno(res)) {
+            .SUCCESS => {},
+            .NXIO => return error.BuffersNotRegistered,
+            else => |errno| return os.unexpectedErrno(errno),
+        }
+    }
+
     fn handle_registration_result(res: usize) !void {
         switch (linux.getErrno(res)) {
             .SUCCESS => {},
@@ -905,6 +966,16 @@ pub fn io_uring_prep_writev(
     io_uring_prep_rw(.WRITEV, sqe, fd, @ptrToInt(iovecs.ptr), iovecs.len, offset);
 }
 
+pub fn io_uring_prep_read_fixed(sqe: *io_uring_sqe, fd: os.fd_t, buffer: *os.iovec, offset: u64, buffer_index: u16) void {
+    io_uring_prep_rw(.READ_FIXED, sqe, fd, @ptrToInt(buffer.iov_base), buffer.iov_len, offset);
+    sqe.buf_index = buffer_index;
+}
+
+pub fn io_uring_prep_write_fixed(sqe: *io_uring_sqe, fd: os.fd_t, buffer: *os.iovec, offset: u64, buffer_index: u16) void {
+    io_uring_prep_rw(.WRITE_FIXED, sqe, fd, @ptrToInt(buffer.iov_base), buffer.iov_len, offset);
+    sqe.buf_index = buffer_index;
+}
+
 pub fn io_uring_prep_accept(
     sqe: *io_uring_sqe,
     fd: os.fd_t,
@@ -1282,6 +1353,63 @@ test "write/read" {
     try testing.expectEqualSlices(u8, buffer_write[0..], buffer_read[0..]);
 }
 
+test "write_fixed/read_fixed" {
+    if (builtin.os.tag != .linux) return error.SkipZigTest;
+
+    var ring = IO_Uring.init(2, 0) catch |err| switch (err) {
+        error.SystemOutdated => return error.SkipZigTest,
+        error.PermissionDenied => return error.SkipZigTest,
+        else => return err,
+    };
+    defer ring.deinit();
+
+    const path = "test_io_uring_write_read_fixed";
+    const file = try std.fs.cwd().createFile(path, .{ .read = true, .truncate = true });
+    defer file.close();
+    defer std.fs.cwd().deleteFile(path) catch {};
+    const fd = file.handle;
+
+    var raw_buffers: [2][11]u8 = undefined;
+    // First buffer will be written to the file.
+    std.mem.set(u8, &raw_buffers[0], 'z');
+    std.mem.copy(u8, &raw_buffers[0], "foobar");
+
+    var buffers = [2]os.iovec{
+        .{ .iov_base = &raw_buffers[0], .iov_len = raw_buffers[0].len },
+        .{ .iov_base = &raw_buffers[1], .iov_len = raw_buffers[1].len },
+    };
+    try ring.register_buffers(&buffers);
+
+    const sqe_write = try ring.write_fixed(0x45454545, fd, &buffers[0], 3, 0);
+    try testing.expectEqual(linux.IORING_OP.WRITE_FIXED, sqe_write.opcode);
+    try testing.expectEqual(@as(u64, 3), sqe_write.off);
+    sqe_write.flags |= linux.IOSQE_IO_LINK;
+
+    const sqe_read = try ring.read_fixed(0x12121212, fd, &buffers[1], 0, 1);
+    try testing.expectEqual(linux.IORING_OP.READ_FIXED, sqe_read.opcode);
+    try testing.expectEqual(@as(u64, 0), sqe_read.off);
+
+    try testing.expectEqual(@as(u32, 2), try ring.submit());
+
+    const cqe_write = try ring.copy_cqe();
+    const cqe_read = try ring.copy_cqe();
+
+    try testing.expectEqual(linux.io_uring_cqe{
+        .user_data = 0x45454545,
+        .res = @intCast(i32, buffers[0].iov_len),
+        .flags = 0,
+    }, cqe_write);
+    try testing.expectEqual(linux.io_uring_cqe{
+        .user_data = 0x12121212,
+        .res = @intCast(i32, buffers[1].iov_len),
+        .flags = 0,
+    }, cqe_read);
+
+    try testing.expectEqualSlices(u8, "\x00\x00\x00", buffers[1].iov_base[0..3]);
+    try testing.expectEqualSlices(u8, "foobar", buffers[1].iov_base[3..9]);
+    try testing.expectEqualSlices(u8, "zz", buffers[1].iov_base[9..11]);
+}
+
 test "openat" {
     if (builtin.os.tag != .linux) return error.SkipZigTest;