| ... | @@ -724,6 +724,37 @@ pub const IO_Uring = struct { | ... | @@ -724,6 +724,37 @@ pub const IO_Uring = struct { |
| 724 | try handle_registration_result(res); | 724 | try handle_registration_result(res); |
| 725 | } | 725 | } |
| 726 | | 726 | |
| | 727 | /// Updates registered file descriptors. |
| | 728 | /// |
| | 729 | /// Updates are applied starting at the provided offset in the original file descriptors slice. |
| | 730 | /// There are three kind of updates: |
| | 731 | /// * turning a sparse entry (where the fd is -1) into a real one |
| | 732 | /// * removing an existing entry (set the fd to -1) |
| | 733 | /// * replacing an existing entry with a new fd |
| | 734 | /// Adding new file descriptors must be done with `register_files`. |
| | 735 | pub fn register_files_update(self: *IO_Uring, offset: u32, fds: []const os.fd_t) !void { |
| | 736 | assert(self.fd >= 0); |
| | 737 | |
| | 738 | const FilesUpdate = struct { |
| | 739 | offset: u32, |
| | 740 | resv: u32, |
| | 741 | fds: u64 align(8), |
| | 742 | }; |
| | 743 | var update = FilesUpdate{ |
| | 744 | .offset = offset, |
| | 745 | .resv = @as(u32, 0), |
| | 746 | .fds = @as(u64, @ptrToInt(fds.ptr)), |
| | 747 | }; |
| | 748 | |
| | 749 | const res = linux.io_uring_register( |
| | 750 | self.fd, |
| | 751 | .REGISTER_FILES_UPDATE, |
| | 752 | @ptrCast(*const c_void, &update), |
| | 753 | @intCast(u32, fds.len), |
| | 754 | ); |
| | 755 | try handle_registration_result(res); |
| | 756 | } |
| | 757 | |
| 727 | /// Registers the file descriptor for an eventfd that will be notified of completion events on | 758 | /// Registers the file descriptor for an eventfd that will be notified of completion events on |
| 728 | /// an io_uring instance. | 759 | /// an io_uring instance. |
| 729 | /// Only a single a eventfd can be registered at any given point in time. | 760 | /// Only a single a eventfd can be registered at any given point in time. |
| ... | @@ -1949,3 +1980,90 @@ test "accept/connect/recv/cancel" { | ... | @@ -1949,3 +1980,90 @@ test "accept/connect/recv/cancel" { |
| 1949 | .flags = 0, | 1980 | .flags = 0, |
| 1950 | }, cqe_cancel); | 1981 | }, cqe_cancel); |
| 1951 | } | 1982 | } |
| | 1983 | |
| | 1984 | test "register_files_update" { |
| | 1985 | if (builtin.os.tag != .linux) return error.SkipZigTest; |
| | 1986 | |
| | 1987 | var ring = IO_Uring.init(1, 0) catch |err| switch (err) { |
| | 1988 | error.SystemOutdated => return error.SkipZigTest, |
| | 1989 | error.PermissionDenied => return error.SkipZigTest, |
| | 1990 | else => return err, |
| | 1991 | }; |
| | 1992 | defer ring.deinit(); |
| | 1993 | |
| | 1994 | const fd = try os.openZ("/dev/zero", os.O.RDONLY | os.O.CLOEXEC, 0); |
| | 1995 | defer os.close(fd); |
| | 1996 | |
| | 1997 | var registered_fds = [_]os.fd_t{0} ** 2; |
| | 1998 | const fd_index = 0; |
| | 1999 | const fd_index2 = 1; |
| | 2000 | registered_fds[fd_index] = fd; |
| | 2001 | registered_fds[fd_index2] = -1; |
| | 2002 | |
| | 2003 | ring.register_files(registered_fds[0..]) catch |err| switch (err) { |
| | 2004 | // Happens when the kernel doesn't support sparse entry (-1) in the file descriptors array. |
| | 2005 | error.FileDescriptorInvalid => return error.SkipZigTest, |
| | 2006 | else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), |
| | 2007 | }; |
| | 2008 | |
| | 2009 | // Test IORING_REGISTER_FILES_UPDATE |
| | 2010 | // Only available since Linux 5.5 |
| | 2011 | |
| | 2012 | const fd2 = try os.openZ("/dev/zero", os.O.RDONLY | os.O.CLOEXEC, 0); |
| | 2013 | defer os.close(fd2); |
| | 2014 | |
| | 2015 | registered_fds[fd_index] = fd2; |
| | 2016 | registered_fds[fd_index2] = -1; |
| | 2017 | try ring.register_files_update(0, registered_fds[0..]); |
| | 2018 | |
| | 2019 | var buffer = [_]u8{42} ** 128; |
| | 2020 | { |
| | 2021 | const sqe = try ring.read(0xcccccccc, fd_index, &buffer, 0); |
| | 2022 | try testing.expectEqual(linux.IORING_OP.READ, sqe.opcode); |
| | 2023 | sqe.flags |= linux.IOSQE_FIXED_FILE; |
| | 2024 | |
| | 2025 | try testing.expectEqual(@as(u32, 1), try ring.submit()); |
| | 2026 | try testing.expectEqual(linux.io_uring_cqe{ |
| | 2027 | .user_data = 0xcccccccc, |
| | 2028 | .res = buffer.len, |
| | 2029 | .flags = 0, |
| | 2030 | }, try ring.copy_cqe()); |
| | 2031 | try testing.expectEqualSlices(u8, &([_]u8{0} ** buffer.len), buffer[0..]); |
| | 2032 | } |
| | 2033 | |
| | 2034 | // Test with a non-zero offset |
| | 2035 | |
| | 2036 | registered_fds[fd_index] = -1; |
| | 2037 | registered_fds[fd_index2] = -1; |
| | 2038 | try ring.register_files_update(1, registered_fds[1..]); |
| | 2039 | |
| | 2040 | { |
| | 2041 | // Next read should still work since fd_index in the registered file descriptors hasn't been updated yet. |
| | 2042 | const sqe = try ring.read(0xcccccccc, fd_index, &buffer, 0); |
| | 2043 | try testing.expectEqual(linux.IORING_OP.READ, sqe.opcode); |
| | 2044 | sqe.flags |= linux.IOSQE_FIXED_FILE; |
| | 2045 | |
| | 2046 | try testing.expectEqual(@as(u32, 1), try ring.submit()); |
| | 2047 | try testing.expectEqual(linux.io_uring_cqe{ |
| | 2048 | .user_data = 0xcccccccc, |
| | 2049 | .res = buffer.len, |
| | 2050 | .flags = 0, |
| | 2051 | }, try ring.copy_cqe()); |
| | 2052 | try testing.expectEqualSlices(u8, &([_]u8{0} ** buffer.len), buffer[0..]); |
| | 2053 | } |
| | 2054 | |
| | 2055 | try ring.register_files_update(0, registered_fds[0..]); |
| | 2056 | |
| | 2057 | { |
| | 2058 | // Now this should fail since both fds are sparse (-1) |
| | 2059 | const sqe = try ring.read(0xcccccccc, fd_index, &buffer, 0); |
| | 2060 | try testing.expectEqual(linux.IORING_OP.READ, sqe.opcode); |
| | 2061 | sqe.flags |= linux.IOSQE_FIXED_FILE; |
| | 2062 | |
| | 2063 | try testing.expectEqual(@as(u32, 1), try ring.submit()); |
| | 2064 | const cqe = try ring.copy_cqe(); |
| | 2065 | try testing.expectEqual(os.linux.E.BADF, cqe.err()); |
| | 2066 | } |
| | 2067 | |
| | 2068 | try ring.unregister_files(); |
| | 2069 | } |