1const builtin = @import("builtin");
2
3const std = @import("../../../std.zig");
4const Io = std.Io;
5const mem = std.mem;
6const assert = std.debug.assert;
7const testing = std.testing;
8const linux = std.os.linux;
9
10const IoUring = std.os.linux.IoUring;
11const BufferGroup = IoUring.BufferGroup;
12
13const posix = std.posix;
14const iovec = posix.iovec;
15const iovec_const = posix.iovec_const;
16
17comptime {
18 assert(builtin.os.tag == .linux);
19}
20
21test "structs/offsets/entries" {
22 try testing.expectEqual(@as(usize, 120), @sizeOf(linux.io_uring_params));
23 try testing.expectEqual(@as(usize, 64), @sizeOf(linux.io_uring_sqe));
24 try testing.expectEqual(@as(usize, 16), @sizeOf(linux.io_uring_cqe));
25
26 try testing.expectEqual(0, linux.IORING_OFF_SQ_RING);
27 try testing.expectEqual(0x8000000, linux.IORING_OFF_CQ_RING);
28 try testing.expectEqual(0x10000000, linux.IORING_OFF_SQES);
29
30 try testing.expectError(error.EntriesZero, IoUring.init(0, 0));
31 try testing.expectError(error.EntriesNotPowerOfTwo, IoUring.init(3, 0));
32}
33
34test "nop" {
35 var ring = IoUring.init(1, 0) catch |err| switch (err) {
36 error.SystemOutdated => return error.SkipZigTest,
37 error.PermissionDenied => return error.SkipZigTest,
38 else => return err,
39 };
40 defer {
41 ring.deinit();
42 testing.expectEqual(@as(linux.fd_t, -1), ring.fd) catch @panic("test failed");
43 }
44
45 const sqe = try ring.nop(0xaaaaaaaa);
46 try testing.expectEqual(linux.io_uring_sqe{
47 .opcode = .NOP,
48 .flags = 0,
49 .ioprio = 0,
50 .fd = 0,
51 .off = 0,
52 .addr = 0,
53 .len = 0,
54 .rw_flags = 0,
55 .user_data = 0xaaaaaaaa,
56 .buf_index = 0,
57 .personality = 0,
58 .splice_fd_in = 0,
59 .addr3 = 0,
60 .resv = 0,
61 }, sqe.*);
62
63 try testing.expectEqual(@as(u32, 0), ring.sq.sqe_head);
64 try testing.expectEqual(@as(u32, 1), ring.sq.sqe_tail);
65 try testing.expectEqual(@as(u32, 0), ring.sq.tail.*);
66 try testing.expectEqual(@as(u32, 0), ring.cq.head.*);
67 try testing.expectEqual(@as(u32, 1), ring.sq_ready());
68 try testing.expectEqual(@as(u32, 0), ring.cq_ready());
69
70 try testing.expectEqual(@as(u32, 1), try ring.submit());
71 try testing.expectEqual(@as(u32, 1), ring.sq.sqe_head);
72 try testing.expectEqual(@as(u32, 1), ring.sq.sqe_tail);
73 try testing.expectEqual(@as(u32, 1), ring.sq.tail.*);
74 try testing.expectEqual(@as(u32, 0), ring.cq.head.*);
75 try testing.expectEqual(@as(u32, 0), ring.sq_ready());
76
77 try testing.expectEqual(linux.io_uring_cqe{
78 .user_data = 0xaaaaaaaa,
79 .res = 0,
80 .flags = 0,
81 }, try ring.copy_cqe());
82 try testing.expectEqual(@as(u32, 1), ring.cq.head.*);
83 try testing.expectEqual(@as(u32, 0), ring.cq_ready());
84
85 const sqe_barrier = try ring.nop(0xbbbbbbbb);
86 sqe_barrier.flags |= linux.IOSQE_IO_DRAIN;
87 try testing.expectEqual(@as(u32, 1), try ring.submit());
88 try testing.expectEqual(linux.io_uring_cqe{
89 .user_data = 0xbbbbbbbb,
90 .res = 0,
91 .flags = 0,
92 }, try ring.copy_cqe());
93 try testing.expectEqual(@as(u32, 2), ring.sq.sqe_head);
94 try testing.expectEqual(@as(u32, 2), ring.sq.sqe_tail);
95 try testing.expectEqual(@as(u32, 2), ring.sq.tail.*);
96 try testing.expectEqual(@as(u32, 2), ring.cq.head.*);
97}
98
99test "readv" {
100 const io = testing.io;
101
102 var ring = IoUring.init(1, 0) catch |err| switch (err) {
103 error.SystemOutdated => return error.SkipZigTest,
104 error.PermissionDenied => return error.SkipZigTest,
105 else => return err,
106 };
107 defer ring.deinit();
108
109 const file = try Io.Dir.openFileAbsolute(io, "/dev/zero", .{});
110 defer file.close(io);
111
112 // Linux Kernel 5.4 supports IORING_REGISTER_FILES but not sparse fd sets (i.e. an fd of -1).
113 // Linux Kernel 5.5 adds support for sparse fd sets.
114 // Compare:
115 // https://github.com/torvalds/linux/blob/v5.4/fs/io_uring.c#L3119-L3124 vs
116 // https://github.com/torvalds/linux/blob/v5.8/fs/io_uring.c#L6687-L6691
117 // We therefore avoid stressing sparse fd sets here:
118 var registered_fds: [1]linux.fd_t = .{0};
119 const fd_index = 0;
120 registered_fds[fd_index] = file.handle;
121 try ring.register_files(registered_fds[0..]);
122
123 var buffer: [128]u8 = @splat(42);
124 var iovecs = [_]iovec{iovec{ .base = &buffer, .len = buffer.len }};
125 const sqe = try ring.read(0xcccccccc, fd_index, .{ .iovecs = iovecs[0..] }, 0);
126 try testing.expectEqual(linux.IORING_OP.READV, sqe.opcode);
127 sqe.flags |= linux.IOSQE_FIXED_FILE;
128
129 try testing.expectError(error.SubmissionQueueFull, ring.nop(0));
130 try testing.expectEqual(@as(u32, 1), try ring.submit());
131 try testing.expectEqual(linux.io_uring_cqe{
132 .user_data = 0xcccccccc,
133 .res = buffer.len,
134 .flags = 0,
135 }, try ring.copy_cqe());
136 try testing.expectEqualSlices(u8, &@as([buffer.len]u8, @splat(0)), buffer[0..]);
137
138 try ring.unregister_files();
139}
140
141test "writev/fsync/readv" {
142 const io = testing.io;
143
144 var ring = IoUring.init(4, 0) catch |err| switch (err) {
145 error.SystemOutdated => return error.SkipZigTest,
146 error.PermissionDenied => return error.SkipZigTest,
147 else => return err,
148 };
149 defer ring.deinit();
150
151 var tmp = std.testing.tmpDir(.{});
152 defer tmp.cleanup();
153
154 const path = "test_io_uring_writev_fsync_readv";
155 const file = try tmp.dir.createFile(io, path, .{ .read = true });
156 defer file.close(io);
157 const fd = file.handle;
158
159 const buffer_write: [128]u8 = @splat(42);
160 const iovecs_write = [_]iovec_const{
161 iovec_const{ .base = &buffer_write, .len = buffer_write.len },
162 };
163 var buffer_read: [128]u8 = @splat(0);
164 var iovecs_read = [_]iovec{
165 iovec{ .base = &buffer_read, .len = buffer_read.len },
166 };
167
168 const sqe_writev = try ring.writev(0xdddddddd, fd, iovecs_write[0..], 17);
169 try testing.expectEqual(linux.IORING_OP.WRITEV, sqe_writev.opcode);
170 try testing.expectEqual(@as(u64, 17), sqe_writev.off);
171 sqe_writev.flags |= linux.IOSQE_IO_LINK;
172
173 const sqe_fsync = try ring.fsync(0xeeeeeeee, fd, 0);
174 try testing.expectEqual(linux.IORING_OP.FSYNC, sqe_fsync.opcode);
175 try testing.expectEqual(fd, sqe_fsync.fd);
176 sqe_fsync.flags |= linux.IOSQE_IO_LINK;
177
178 const sqe_readv = try ring.read(0xffffffff, fd, .{ .iovecs = iovecs_read[0..] }, 17);
179 try testing.expectEqual(linux.IORING_OP.READV, sqe_readv.opcode);
180 try testing.expectEqual(@as(u64, 17), sqe_readv.off);
181
182 try testing.expectEqual(@as(u32, 3), ring.sq_ready());
183 try testing.expectEqual(@as(u32, 3), try ring.submit_and_wait(3));
184 try testing.expectEqual(@as(u32, 0), ring.sq_ready());
185 try testing.expectEqual(@as(u32, 3), ring.cq_ready());
186
187 try testing.expectEqual(linux.io_uring_cqe{
188 .user_data = 0xdddddddd,
189 .res = buffer_write.len,
190 .flags = 0,
191 }, try ring.copy_cqe());
192 try testing.expectEqual(@as(u32, 2), ring.cq_ready());
193
194 try testing.expectEqual(linux.io_uring_cqe{
195 .user_data = 0xeeeeeeee,
196 .res = 0,
197 .flags = 0,
198 }, try ring.copy_cqe());
199 try testing.expectEqual(@as(u32, 1), ring.cq_ready());
200
201 try testing.expectEqual(linux.io_uring_cqe{
202 .user_data = 0xffffffff,
203 .res = buffer_read.len,
204 .flags = 0,
205 }, try ring.copy_cqe());
206 try testing.expectEqual(@as(u32, 0), ring.cq_ready());
207
208 try testing.expectEqualSlices(u8, buffer_write[0..], buffer_read[0..]);
209}
210
211test "write/read" {
212 const io = testing.io;
213
214 var ring = IoUring.init(2, 0) catch |err| switch (err) {
215 error.SystemOutdated => return error.SkipZigTest,
216 error.PermissionDenied => return error.SkipZigTest,
217 else => return err,
218 };
219 defer ring.deinit();
220
221 var tmp = std.testing.tmpDir(.{});
222 defer tmp.cleanup();
223 const path = "test_io_uring_write_read";
224 const file = try tmp.dir.createFile(io, path, .{ .read = true });
225 defer file.close(io);
226 const fd = file.handle;
227
228 const buffer_write: [20]u8 = @splat(97);
229 var buffer_read: [20]u8 = @splat(98);
230 const sqe_write = try ring.write(0x11111111, fd, buffer_write[0..], 10);
231 try testing.expectEqual(linux.IORING_OP.WRITE, sqe_write.opcode);
232 try testing.expectEqual(@as(u64, 10), sqe_write.off);
233 sqe_write.flags |= linux.IOSQE_IO_LINK;
234 const sqe_read = try ring.read(0x22222222, fd, .{ .buffer = buffer_read[0..] }, 10);
235 try testing.expectEqual(linux.IORING_OP.READ, sqe_read.opcode);
236 try testing.expectEqual(@as(u64, 10), sqe_read.off);
237 try testing.expectEqual(@as(u32, 2), try ring.submit());
238
239 const cqe_write = try ring.copy_cqe();
240 const cqe_read = try ring.copy_cqe();
241 // Prior to Linux Kernel 5.6 this is the only way to test for read/write support:
242 // https://lwn.net/Articles/809820/
243 if (cqe_write.err() == .INVAL) return error.SkipZigTest;
244 if (cqe_read.err() == .INVAL) return error.SkipZigTest;
245 try testing.expectEqual(linux.io_uring_cqe{
246 .user_data = 0x11111111,
247 .res = buffer_write.len,
248 .flags = 0,
249 }, cqe_write);
250 try testing.expectEqual(linux.io_uring_cqe{
251 .user_data = 0x22222222,
252 .res = buffer_read.len,
253 .flags = 0,
254 }, cqe_read);
255 try testing.expectEqualSlices(u8, buffer_write[0..], buffer_read[0..]);
256}
257
258test "splice/read" {
259 const io = testing.io;
260
261 var ring = IoUring.init(4, 0) catch |err| switch (err) {
262 error.SystemOutdated => return error.SkipZigTest,
263 error.PermissionDenied => return error.SkipZigTest,
264 else => return err,
265 };
266 defer ring.deinit();
267
268 var tmp = std.testing.tmpDir(.{});
269 const path_src = "test_io_uring_splice_src";
270 const file_src = try tmp.dir.createFile(io, path_src, .{ .read = true });
271 defer file_src.close(io);
272 const fd_src = file_src.handle;
273
274 const path_dst = "test_io_uring_splice_dst";
275 const file_dst = try tmp.dir.createFile(io, path_dst, .{ .read = true });
276 defer file_dst.close(io);
277 const fd_dst = file_dst.handle;
278
279 const buffer_write: [20]u8 = @splat(97);
280 var buffer_read: [20]u8 = @splat(98);
281 try file_src.writeStreamingAll(io, &buffer_write);
282
283 const fds = try std.Io.Threaded.pipe2(.{});
284 const pipe_offset: u64 = std.math.maxInt(u64);
285
286 const sqe_splice_to_pipe = try ring.splice(0x11111111, fd_src, 0, fds[1], pipe_offset, buffer_write.len);
287 try testing.expectEqual(linux.IORING_OP.SPLICE, sqe_splice_to_pipe.opcode);
288 try testing.expectEqual(@as(u64, 0), sqe_splice_to_pipe.addr);
289 try testing.expectEqual(pipe_offset, sqe_splice_to_pipe.off);
290 sqe_splice_to_pipe.flags |= linux.IOSQE_IO_LINK;
291
292 const sqe_splice_from_pipe = try ring.splice(0x22222222, fds[0], pipe_offset, fd_dst, 10, buffer_write.len);
293 try testing.expectEqual(linux.IORING_OP.SPLICE, sqe_splice_from_pipe.opcode);
294 try testing.expectEqual(pipe_offset, sqe_splice_from_pipe.addr);
295 try testing.expectEqual(@as(u64, 10), sqe_splice_from_pipe.off);
296 sqe_splice_from_pipe.flags |= linux.IOSQE_IO_LINK;
297
298 const sqe_read = try ring.read(0x33333333, fd_dst, .{ .buffer = buffer_read[0..] }, 10);
299 try testing.expectEqual(linux.IORING_OP.READ, sqe_read.opcode);
300 try testing.expectEqual(@as(u64, 10), sqe_read.off);
301 try testing.expectEqual(@as(u32, 3), try ring.submit());
302
303 const cqe_splice_to_pipe = try ring.copy_cqe();
304 const cqe_splice_from_pipe = try ring.copy_cqe();
305 const cqe_read = try ring.copy_cqe();
306 // Prior to Linux Kernel 5.6 this is the only way to test for splice/read support:
307 // https://lwn.net/Articles/809820/
308 if (cqe_splice_to_pipe.err() == .INVAL) return error.SkipZigTest;
309 if (cqe_splice_from_pipe.err() == .INVAL) return error.SkipZigTest;
310 if (cqe_read.err() == .INVAL) return error.SkipZigTest;
311 try testing.expectEqual(linux.io_uring_cqe{
312 .user_data = 0x11111111,
313 .res = buffer_write.len,
314 .flags = 0,
315 }, cqe_splice_to_pipe);
316 try testing.expectEqual(linux.io_uring_cqe{
317 .user_data = 0x22222222,
318 .res = buffer_write.len,
319 .flags = 0,
320 }, cqe_splice_from_pipe);
321 try testing.expectEqual(linux.io_uring_cqe{
322 .user_data = 0x33333333,
323 .res = buffer_read.len,
324 .flags = 0,
325 }, cqe_read);
326 try testing.expectEqualSlices(u8, buffer_write[0..], buffer_read[0..]);
327}
328
329test "write_fixed/read_fixed" {
330 const io = testing.io;
331
332 var ring = IoUring.init(2, 0) catch |err| switch (err) {
333 error.SystemOutdated => return error.SkipZigTest,
334 error.PermissionDenied => return error.SkipZigTest,
335 else => return err,
336 };
337 defer ring.deinit();
338
339 var tmp = std.testing.tmpDir(.{});
340 defer tmp.cleanup();
341
342 const path = "test_io_uring_write_read_fixed";
343 const file = try tmp.dir.createFile(io, path, .{ .read = true });
344 defer file.close(io);
345 const fd = file.handle;
346
347 var raw_buffers: [2][11]u8 = undefined;
348 // First buffer will be written to the file.
349 @memset(&raw_buffers[0], 'z');
350 raw_buffers[0][0.."foobar".len].* = "foobar".*;
351
352 var buffers = [2]iovec{
353 .{ .base = &raw_buffers[0], .len = raw_buffers[0].len },
354 .{ .base = &raw_buffers[1], .len = raw_buffers[1].len },
355 };
356 ring.register_buffers(&buffers) catch |err| switch (err) {
357 error.SystemResources => {
358 // See https://github.com/ziglang/zig/issues/15362
359 return error.SkipZigTest;
360 },
361 else => |e| return e,
362 };
363
364 const sqe_write = try ring.write_fixed(0x45454545, fd, &buffers[0], 3, 0);
365 try testing.expectEqual(linux.IORING_OP.WRITE_FIXED, sqe_write.opcode);
366 try testing.expectEqual(@as(u64, 3), sqe_write.off);
367 sqe_write.flags |= linux.IOSQE_IO_LINK;
368
369 const sqe_read = try ring.read_fixed(0x12121212, fd, &buffers[1], 0, 1);
370 try testing.expectEqual(linux.IORING_OP.READ_FIXED, sqe_read.opcode);
371 try testing.expectEqual(@as(u64, 0), sqe_read.off);
372
373 try testing.expectEqual(@as(u32, 2), try ring.submit());
374
375 const cqe_write = try ring.copy_cqe();
376 const cqe_read = try ring.copy_cqe();
377
378 try testing.expectEqual(linux.io_uring_cqe{
379 .user_data = 0x45454545,
380 .res = @as(i32, @intCast(buffers[0].len)),
381 .flags = 0,
382 }, cqe_write);
383 try testing.expectEqual(linux.io_uring_cqe{
384 .user_data = 0x12121212,
385 .res = @as(i32, @intCast(buffers[1].len)),
386 .flags = 0,
387 }, cqe_read);
388
389 try testing.expectEqualSlices(u8, "\x00\x00\x00", buffers[1].base[0..3]);
390 try testing.expectEqualSlices(u8, "foobar", buffers[1].base[3..9]);
391 try testing.expectEqualSlices(u8, "zz", buffers[1].base[9..11]);
392}
393
394test "openat" {
395 var ring = IoUring.init(1, 0) catch |err| switch (err) {
396 error.SystemOutdated => return error.SkipZigTest,
397 error.PermissionDenied => return error.SkipZigTest,
398 else => return err,
399 };
400 defer ring.deinit();
401
402 var tmp = std.testing.tmpDir(.{});
403 defer tmp.cleanup();
404
405 const path = "test_io_uring_openat";
406
407 // Workaround for LLVM bug: https://github.com/ziglang/zig/issues/12014
408 const path_addr = if (builtin.zig_backend == .stage2_llvm) p: {
409 var workaround = path;
410 _ = &workaround;
411 break :p @intFromPtr(workaround);
412 } else @intFromPtr(path);
413
414 const flags: linux.O = .{ .CLOEXEC = true, .ACCMODE = .RDWR, .CREAT = true };
415 const mode: posix.mode_t = 0o666;
416 const sqe_openat = try ring.openat(0x33333333, tmp.dir.handle, path, flags, mode);
417 try testing.expectEqual(linux.io_uring_sqe{
418 .opcode = .OPENAT,
419 .flags = 0,
420 .ioprio = 0,
421 .fd = tmp.dir.handle,
422 .off = 0,
423 .addr = path_addr,
424 .len = mode,
425 .rw_flags = @bitCast(flags),
426 .user_data = 0x33333333,
427 .buf_index = 0,
428 .personality = 0,
429 .splice_fd_in = 0,
430 .addr3 = 0,
431 .resv = 0,
432 }, sqe_openat.*);
433 try testing.expectEqual(@as(u32, 1), try ring.submit());
434
435 const cqe_openat = try ring.copy_cqe();
436 try testing.expectEqual(@as(u64, 0x33333333), cqe_openat.user_data);
437 if (cqe_openat.err() == .INVAL) return error.SkipZigTest;
438 if (cqe_openat.err() == .BADF) return error.SkipZigTest;
439 if (cqe_openat.res <= 0) std.debug.print("\ncqe_openat.res={}\n", .{cqe_openat.res});
440 try testing.expect(cqe_openat.res > 0);
441 try testing.expectEqual(@as(u32, 0), cqe_openat.flags);
442
443 _ = linux.close(cqe_openat.res);
444}
445
446test "close" {
447 const io = testing.io;
448
449 var ring = IoUring.init(1, 0) catch |err| switch (err) {
450 error.SystemOutdated => return error.SkipZigTest,
451 error.PermissionDenied => return error.SkipZigTest,
452 else => return err,
453 };
454 defer ring.deinit();
455
456 var tmp = std.testing.tmpDir(.{});
457 defer tmp.cleanup();
458
459 const path = "test_io_uring_close";
460 const file = try tmp.dir.createFile(io, path, .{});
461 errdefer file.close(io);
462
463 const sqe_close = try ring.close(0x44444444, file.handle);
464 try testing.expectEqual(linux.IORING_OP.CLOSE, sqe_close.opcode);
465 try testing.expectEqual(file.handle, sqe_close.fd);
466 try testing.expectEqual(@as(u32, 1), try ring.submit());
467
468 const cqe_close = try ring.copy_cqe();
469 if (cqe_close.err() == .INVAL) return error.SkipZigTest;
470 try testing.expectEqual(linux.io_uring_cqe{
471 .user_data = 0x44444444,
472 .res = 0,
473 .flags = 0,
474 }, cqe_close);
475}
476
477test "accept/connect/send/recv" {
478 var ring = IoUring.init(16, 0) catch |err| switch (err) {
479 error.SystemOutdated => return error.SkipZigTest,
480 error.PermissionDenied => return error.SkipZigTest,
481 else => return err,
482 };
483 defer ring.deinit();
484
485 const socket_test_harness = try createSocketTestHarness(&ring);
486 defer socket_test_harness.close();
487
488 const buffer_send = [_]u8{ 1, 0, 1, 0, 1, 0, 1, 0, 1, 0 };
489 var buffer_recv = [_]u8{ 0, 1, 0, 1, 0 };
490
491 const sqe_send = try ring.send(0xeeeeeeee, socket_test_harness.client, buffer_send[0..], 0);
492 sqe_send.flags |= linux.IOSQE_IO_LINK;
493 _ = try ring.recv(0xffffffff, socket_test_harness.server, .{ .buffer = buffer_recv[0..] }, 0);
494 try testing.expectEqual(@as(u32, 2), try ring.submit());
495
496 const cqe_send = try ring.copy_cqe();
497 if (cqe_send.err() == .INVAL) return error.SkipZigTest;
498 try testing.expectEqual(linux.io_uring_cqe{
499 .user_data = 0xeeeeeeee,
500 .res = buffer_send.len,
501 .flags = 0,
502 }, cqe_send);
503
504 const cqe_recv = try ring.copy_cqe();
505 if (cqe_recv.err() == .INVAL) return error.SkipZigTest;
506 try testing.expectEqual(linux.io_uring_cqe{
507 .user_data = 0xffffffff,
508 .res = buffer_recv.len,
509 // ignore IORING_CQE_F_SOCK_NONEMPTY since it is only set on some systems
510 .flags = cqe_recv.flags & linux.IORING_CQE_F_SOCK_NONEMPTY,
511 }, cqe_recv);
512
513 try testing.expectEqualSlices(u8, buffer_send[0..buffer_recv.len], buffer_recv[0..]);
514}
515
516test "sendmsg/recvmsg" {
517 var ring = IoUring.init(2, 0) catch |err| switch (err) {
518 error.SystemOutdated => return error.SkipZigTest,
519 error.PermissionDenied => return error.SkipZigTest,
520 else => return err,
521 };
522 defer ring.deinit();
523
524 var address_server: linux.sockaddr.in = .{
525 .port = 0,
526 .addr = @as(*align(1) const u32, @ptrCast(
527 &@as([4]u8, .{ 127, 0, 0, 1 }),
528 )).*,
529 };
530
531 const server = try socket(address_server.family, posix.SOCK.DGRAM, 0);
532 defer _ = linux.close(server);
533 try posix.setsockopt(server, posix.SOL.SOCKET, posix.SO.REUSEPORT, &mem.toBytes(@as(c_int, 1)));
534 try posix.setsockopt(server, posix.SOL.SOCKET, posix.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1)));
535 try bind(server, addrAny(&address_server), @sizeOf(linux.sockaddr.in));
536
537 // set address_server to the OS-chosen IP/port.
538 var slen: posix.socklen_t = @sizeOf(linux.sockaddr.in);
539 try getsockname(server, addrAny(&address_server), &slen);
540
541 const client = try socket(address_server.family, posix.SOCK.DGRAM, 0);
542 defer _ = linux.close(client);
543
544 const buffer_send: [128]u8 = @splat(42);
545 const iovecs_send = [_]iovec_const{
546 iovec_const{ .base = &buffer_send, .len = buffer_send.len },
547 };
548 const msg_send: linux.msghdr_const = .{
549 .name = addrAny(&address_server),
550 .namelen = @sizeOf(linux.sockaddr.in),
551 .iov = &iovecs_send,
552 .iovlen = 1,
553 .control = null,
554 .controllen = 0,
555 .flags = 0,
556 };
557 const sqe_sendmsg = try ring.sendmsg(0x11111111, client, &msg_send, 0);
558 sqe_sendmsg.flags |= linux.IOSQE_IO_LINK;
559 try testing.expectEqual(linux.IORING_OP.SENDMSG, sqe_sendmsg.opcode);
560 try testing.expectEqual(client, sqe_sendmsg.fd);
561
562 var buffer_recv: [128]u8 = @splat(0);
563 var iovecs_recv = [_]iovec{
564 iovec{ .base = &buffer_recv, .len = buffer_recv.len },
565 };
566 var address_recv: linux.sockaddr.in = .{
567 .port = 0,
568 .addr = 0,
569 };
570 var msg_recv: linux.msghdr = .{
571 .name = addrAny(&address_recv),
572 .namelen = @sizeOf(linux.sockaddr.in),
573 .iov = &iovecs_recv,
574 .iovlen = 1,
575 .control = null,
576 .controllen = 0,
577 .flags = 0,
578 };
579 const sqe_recvmsg = try ring.recvmsg(0x22222222, server, &msg_recv, 0);
580 try testing.expectEqual(linux.IORING_OP.RECVMSG, sqe_recvmsg.opcode);
581 try testing.expectEqual(server, sqe_recvmsg.fd);
582
583 try testing.expectEqual(@as(u32, 2), ring.sq_ready());
584 try testing.expectEqual(@as(u32, 2), try ring.submit_and_wait(2));
585 try testing.expectEqual(@as(u32, 0), ring.sq_ready());
586 try testing.expectEqual(@as(u32, 2), ring.cq_ready());
587
588 const cqe_sendmsg = try ring.copy_cqe();
589 if (cqe_sendmsg.res == -@as(i32, @backingInt(linux.E.INVAL))) return error.SkipZigTest;
590 try testing.expectEqual(linux.io_uring_cqe{
591 .user_data = 0x11111111,
592 .res = buffer_send.len,
593 .flags = 0,
594 }, cqe_sendmsg);
595
596 const cqe_recvmsg = try ring.copy_cqe();
597 if (cqe_recvmsg.res == -@as(i32, @backingInt(linux.E.INVAL))) return error.SkipZigTest;
598 try testing.expectEqual(linux.io_uring_cqe{
599 .user_data = 0x22222222,
600 .res = buffer_recv.len,
601 // ignore IORING_CQE_F_SOCK_NONEMPTY since it is set non-deterministically
602 .flags = cqe_recvmsg.flags & linux.IORING_CQE_F_SOCK_NONEMPTY,
603 }, cqe_recvmsg);
604
605 try testing.expectEqualSlices(u8, buffer_send[0..buffer_recv.len], buffer_recv[0..]);
606}
607
608test "timeout (after a relative time)" {
609 const io = testing.io;
610
611 var ring = IoUring.init(1, 0) catch |err| switch (err) {
612 error.SystemOutdated => return error.SkipZigTest,
613 error.PermissionDenied => return error.SkipZigTest,
614 else => return err,
615 };
616 defer ring.deinit();
617
618 const ms = 10;
619 const margin = 5;
620 const ts: linux.kernel_timespec = .{ .sec = 0, .nsec = ms * std.time.ns_per_ms };
621
622 const started = std.Io.Clock.awake.now(io);
623 const sqe = try ring.timeout(0x55555555, &ts, 0, 0);
624 try testing.expectEqual(linux.IORING_OP.TIMEOUT, sqe.opcode);
625 try testing.expectEqual(@as(u32, 1), try ring.submit());
626 const cqe = try ring.copy_cqe();
627 const stopped = std.Io.Clock.awake.now(io);
628
629 try testing.expectEqual(linux.io_uring_cqe{
630 .user_data = 0x55555555,
631 .res = -@as(i32, @backingInt(linux.E.TIME)),
632 .flags = 0,
633 }, cqe);
634
635 // Tests should not depend on timings: skip test if outside margin.
636 const ms_elapsed = started.durationTo(stopped).toMilliseconds();
637 if (ms_elapsed > margin) return error.SkipZigTest;
638}
639
640test "timeout (after a number of completions)" {
641 var ring = IoUring.init(2, 0) catch |err| switch (err) {
642 error.SystemOutdated => return error.SkipZigTest,
643 error.PermissionDenied => return error.SkipZigTest,
644 else => return err,
645 };
646 defer ring.deinit();
647
648 const ts: linux.kernel_timespec = .{ .sec = 3, .nsec = 0 };
649 const count_completions: u64 = 1;
650 const sqe_timeout = try ring.timeout(0x66666666, &ts, count_completions, 0);
651 try testing.expectEqual(linux.IORING_OP.TIMEOUT, sqe_timeout.opcode);
652 try testing.expectEqual(count_completions, sqe_timeout.off);
653 _ = try ring.nop(0x77777777);
654 try testing.expectEqual(@as(u32, 2), try ring.submit());
655
656 const cqe_nop = try ring.copy_cqe();
657 try testing.expectEqual(linux.io_uring_cqe{
658 .user_data = 0x77777777,
659 .res = 0,
660 .flags = 0,
661 }, cqe_nop);
662
663 const cqe_timeout = try ring.copy_cqe();
664 try testing.expectEqual(linux.io_uring_cqe{
665 .user_data = 0x66666666,
666 .res = 0,
667 .flags = 0,
668 }, cqe_timeout);
669}
670
671test "timeout_remove" {
672 var ring = IoUring.init(2, 0) catch |err| switch (err) {
673 error.SystemOutdated => return error.SkipZigTest,
674 error.PermissionDenied => return error.SkipZigTest,
675 else => return err,
676 };
677 defer ring.deinit();
678
679 const ts: linux.kernel_timespec = .{ .sec = 3, .nsec = 0 };
680 const sqe_timeout = try ring.timeout(0x88888888, &ts, 0, 0);
681 try testing.expectEqual(linux.IORING_OP.TIMEOUT, sqe_timeout.opcode);
682 try testing.expectEqual(@as(u64, 0x88888888), sqe_timeout.user_data);
683
684 const sqe_timeout_remove = try ring.timeout_remove(0x99999999, 0x88888888, 0);
685 try testing.expectEqual(linux.IORING_OP.TIMEOUT_REMOVE, sqe_timeout_remove.opcode);
686 try testing.expectEqual(@as(u64, 0x88888888), sqe_timeout_remove.addr);
687 try testing.expectEqual(@as(u64, 0x99999999), sqe_timeout_remove.user_data);
688
689 try testing.expectEqual(@as(u32, 2), try ring.submit());
690
691 // The order in which the CQE arrive is not clearly documented and it changed with kernel 5.18:
692 // * kernel 5.10 gives user data 0x88888888 first, 0x99999999 second
693 // * kernel 5.18 gives user data 0x99999999 first, 0x88888888 second
694
695 var cqes: [2]linux.io_uring_cqe = undefined;
696 cqes[0] = try ring.copy_cqe();
697 cqes[1] = try ring.copy_cqe();
698
699 for (cqes) |cqe| {
700 // IORING_OP_TIMEOUT_REMOVE is not supported by this kernel version:
701 // Timeout remove operations set the fd to -1, which results in EBADF before EINVAL.
702 // We use IORING_FEAT_RW_CUR_POS as a safety check here to make sure we are at least pre-5.6.
703 // We don't want to skip this test for newer kernels.
704 if (cqe.user_data == 0x99999999 and
705 cqe.err() == .BADF and
706 (ring.features & linux.IORING_FEAT_RW_CUR_POS) == 0)
707 {
708 return error.SkipZigTest;
709 }
710
711 try testing.expect(cqe.user_data == 0x88888888 or cqe.user_data == 0x99999999);
712
713 if (cqe.user_data == 0x88888888) {
714 try testing.expectEqual(linux.io_uring_cqe{
715 .user_data = 0x88888888,
716 .res = -@as(i32, @backingInt(linux.E.CANCELED)),
717 .flags = 0,
718 }, cqe);
719 } else if (cqe.user_data == 0x99999999) {
720 try testing.expectEqual(linux.io_uring_cqe{
721 .user_data = 0x99999999,
722 .res = 0,
723 .flags = 0,
724 }, cqe);
725 }
726 }
727}
728
729test "accept/connect/recv/link_timeout" {
730 var ring = IoUring.init(16, 0) catch |err| switch (err) {
731 error.SystemOutdated => return error.SkipZigTest,
732 error.PermissionDenied => return error.SkipZigTest,
733 else => return err,
734 };
735 defer ring.deinit();
736
737 const socket_test_harness = try createSocketTestHarness(&ring);
738 defer socket_test_harness.close();
739
740 var buffer_recv = [_]u8{ 0, 1, 0, 1, 0 };
741
742 const sqe_recv = try ring.recv(0xffffffff, socket_test_harness.server, .{ .buffer = buffer_recv[0..] }, 0);
743 sqe_recv.flags |= linux.IOSQE_IO_LINK;
744
745 const ts: linux.kernel_timespec = .{ .sec = 0, .nsec = std.time.ns_per_ms };
746 _ = try ring.link_timeout(0x22222222, &ts, 0);
747
748 const nr_wait = try ring.submit();
749 try testing.expectEqual(@as(u32, 2), nr_wait);
750
751 var i: usize = 0;
752 while (i < nr_wait) : (i += 1) {
753 const cqe = try ring.copy_cqe();
754 switch (cqe.user_data) {
755 0xffffffff => {
756 if (cqe.res != -@as(i32, @backingInt(linux.E.INTR)) and
757 cqe.res != -@as(i32, @backingInt(linux.E.CANCELED)))
758 {
759 std.debug.print("Req 0x{x} got {d}\n", .{ cqe.user_data, cqe.res });
760 try testing.expect(false);
761 }
762 },
763 0x22222222 => {
764 if (cqe.res != -@as(i32, @backingInt(linux.E.ALREADY)) and
765 cqe.res != -@as(i32, @backingInt(linux.E.TIME)))
766 {
767 std.debug.print("Req 0x{x} got {d}\n", .{ cqe.user_data, cqe.res });
768 try testing.expect(false);
769 }
770 },
771 else => @panic("should not happen"),
772 }
773 }
774}
775
776test "fallocate" {
777 const io = testing.io;
778
779 var ring = IoUring.init(1, 0) catch |err| switch (err) {
780 error.SystemOutdated => return error.SkipZigTest,
781 error.PermissionDenied => return error.SkipZigTest,
782 else => return err,
783 };
784 defer ring.deinit();
785
786 var tmp = std.testing.tmpDir(.{});
787 defer tmp.cleanup();
788
789 const path = "test_io_uring_fallocate";
790 const file = try tmp.dir.createFile(io, path, .{});
791 defer file.close(io);
792
793 try testing.expectEqual(@as(u64, 0), (try file.stat(io)).size);
794
795 const len: u64 = 65536;
796 const sqe = try ring.fallocate(0xaaaaaaaa, file.handle, 0, 0, len);
797 try testing.expectEqual(linux.IORING_OP.FALLOCATE, sqe.opcode);
798 try testing.expectEqual(file.handle, sqe.fd);
799 try testing.expectEqual(@as(u32, 1), try ring.submit());
800
801 const cqe = try ring.copy_cqe();
802 switch (cqe.err()) {
803 .SUCCESS => {},
804 // This kernel's io_uring does not yet implement fallocate():
805 .INVAL => return error.SkipZigTest,
806 // This kernel does not implement fallocate():
807 .NOSYS => return error.SkipZigTest,
808 // The filesystem containing the file referred to by fd does not support this operation;
809 // or the mode is not supported by the filesystem containing the file referred to by fd:
810 .OPNOTSUPP => return error.SkipZigTest,
811 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
812 }
813 try testing.expectEqual(linux.io_uring_cqe{
814 .user_data = 0xaaaaaaaa,
815 .res = 0,
816 .flags = 0,
817 }, cqe);
818
819 try testing.expectEqual(len, (try file.stat(io)).size);
820}
821
822test "statx" {
823 const io = testing.io;
824
825 var ring = IoUring.init(1, 0) catch |err| switch (err) {
826 error.SystemOutdated => return error.SkipZigTest,
827 error.PermissionDenied => return error.SkipZigTest,
828 else => return err,
829 };
830 defer ring.deinit();
831
832 var tmp = std.testing.tmpDir(.{});
833 defer tmp.cleanup();
834 const path = "test_io_uring_statx";
835 const file = try tmp.dir.createFile(io, path, .{});
836 defer file.close(io);
837
838 try testing.expectEqual(@as(u64, 0), (try file.stat(io)).size);
839
840 try file.writeStreamingAll(io, "foobar");
841
842 var buf: linux.Statx = undefined;
843 const sqe = try ring.statx(
844 0xaaaaaaaa,
845 tmp.dir.handle,
846 path,
847 0,
848 .{ .SIZE = true },
849 &buf,
850 );
851 try testing.expectEqual(linux.IORING_OP.STATX, sqe.opcode);
852 try testing.expectEqual(@as(i32, tmp.dir.handle), sqe.fd);
853 try testing.expectEqual(@as(u32, 1), try ring.submit());
854
855 const cqe = try ring.copy_cqe();
856 switch (cqe.err()) {
857 .SUCCESS => {},
858 // This kernel's io_uring does not yet implement statx():
859 .INVAL => return error.SkipZigTest,
860 // This kernel does not implement statx():
861 .NOSYS => return error.SkipZigTest,
862 // The filesystem containing the file referred to by fd does not support this operation;
863 // or the mode is not supported by the filesystem containing the file referred to by fd:
864 .OPNOTSUPP => return error.SkipZigTest,
865 // not supported on older kernels (5.4)
866 .BADF => return error.SkipZigTest,
867 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
868 }
869 try testing.expectEqual(linux.io_uring_cqe{
870 .user_data = 0xaaaaaaaa,
871 .res = 0,
872 .flags = 0,
873 }, cqe);
874
875 try testing.expect(buf.mask.SIZE);
876 try testing.expectEqual(@as(u64, 6), buf.size);
877}
878
879test "accept/connect/recv/cancel" {
880 var ring = IoUring.init(16, 0) catch |err| switch (err) {
881 error.SystemOutdated => return error.SkipZigTest,
882 error.PermissionDenied => return error.SkipZigTest,
883 else => return err,
884 };
885 defer ring.deinit();
886
887 const socket_test_harness = try createSocketTestHarness(&ring);
888 defer socket_test_harness.close();
889
890 var buffer_recv = [_]u8{ 0, 1, 0, 1, 0 };
891
892 _ = try ring.recv(0xffffffff, socket_test_harness.server, .{ .buffer = buffer_recv[0..] }, 0);
893 try testing.expectEqual(@as(u32, 1), try ring.submit());
894
895 const sqe_cancel = try ring.cancel(0x99999999, 0xffffffff, 0);
896 try testing.expectEqual(linux.IORING_OP.ASYNC_CANCEL, sqe_cancel.opcode);
897 try testing.expectEqual(@as(u64, 0xffffffff), sqe_cancel.addr);
898 try testing.expectEqual(@as(u64, 0x99999999), sqe_cancel.user_data);
899 try testing.expectEqual(@as(u32, 1), try ring.submit());
900
901 var cqe_recv = try ring.copy_cqe();
902 if (cqe_recv.err() == .INVAL) return error.SkipZigTest;
903 var cqe_cancel = try ring.copy_cqe();
904 if (cqe_cancel.err() == .INVAL) return error.SkipZigTest;
905
906 // The recv/cancel CQEs may arrive in any order, the recv CQE will sometimes come first:
907 if (cqe_recv.user_data == 0x99999999 and cqe_cancel.user_data == 0xffffffff) {
908 const a = cqe_recv;
909 const b = cqe_cancel;
910 cqe_recv = b;
911 cqe_cancel = a;
912 }
913
914 try testing.expectEqual(linux.io_uring_cqe{
915 .user_data = 0xffffffff,
916 .res = -@as(i32, @backingInt(linux.E.CANCELED)),
917 .flags = 0,
918 }, cqe_recv);
919
920 try testing.expectEqual(linux.io_uring_cqe{
921 .user_data = 0x99999999,
922 .res = 0,
923 .flags = 0,
924 }, cqe_cancel);
925}
926
927test "register_files_update" {
928 const io = testing.io;
929
930 var ring = IoUring.init(1, 0) catch |err| switch (err) {
931 error.SystemOutdated => return error.SkipZigTest,
932 error.PermissionDenied => return error.SkipZigTest,
933 else => return err,
934 };
935 defer ring.deinit();
936
937 const file = try Io.Dir.openFileAbsolute(io, "/dev/zero", .{});
938 defer file.close(io);
939
940 var registered_fds: [2]linux.fd_t = @splat(0);
941 const fd_index = 0;
942 const fd_index2 = 1;
943 registered_fds[fd_index] = file.handle;
944 registered_fds[fd_index2] = -1;
945
946 ring.register_files(registered_fds[0..]) catch |err| switch (err) {
947 // Happens when the kernel doesn't support sparse entry (-1) in the file descriptors array.
948 error.FileDescriptorInvalid => return error.SkipZigTest,
949 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
950 };
951
952 // Test IORING_REGISTER_FILES_UPDATE
953 // Only available since Linux 5.5
954
955 const file2 = try Io.Dir.openFileAbsolute(io, "/dev/zero", .{});
956 defer file2.close(io);
957
958 registered_fds[fd_index] = file2.handle;
959 registered_fds[fd_index2] = -1;
960 try ring.register_files_update(0, registered_fds[0..]);
961
962 var buffer: [128]u8 = @splat(42);
963 {
964 const sqe = try ring.read(0xcccccccc, fd_index, .{ .buffer = &buffer }, 0);
965 try testing.expectEqual(linux.IORING_OP.READ, sqe.opcode);
966 sqe.flags |= linux.IOSQE_FIXED_FILE;
967
968 try testing.expectEqual(@as(u32, 1), try ring.submit());
969 try testing.expectEqual(linux.io_uring_cqe{
970 .user_data = 0xcccccccc,
971 .res = buffer.len,
972 .flags = 0,
973 }, try ring.copy_cqe());
974 try testing.expectEqualSlices(u8, &@as([buffer.len]u8, @splat(0)), buffer[0..]);
975 }
976
977 // Test with a non-zero offset
978
979 registered_fds[fd_index] = -1;
980 registered_fds[fd_index2] = -1;
981 try ring.register_files_update(1, registered_fds[1..]);
982
983 {
984 // Next read should still work since fd_index in the registered file descriptors hasn't been updated yet.
985 const sqe = try ring.read(0xcccccccc, fd_index, .{ .buffer = &buffer }, 0);
986 try testing.expectEqual(linux.IORING_OP.READ, sqe.opcode);
987 sqe.flags |= linux.IOSQE_FIXED_FILE;
988
989 try testing.expectEqual(@as(u32, 1), try ring.submit());
990 try testing.expectEqual(linux.io_uring_cqe{
991 .user_data = 0xcccccccc,
992 .res = buffer.len,
993 .flags = 0,
994 }, try ring.copy_cqe());
995 try testing.expectEqualSlices(u8, &@as([buffer.len]u8, @splat(0)), buffer[0..]);
996 }
997
998 try ring.register_files_update(0, registered_fds[0..]);
999
1000 {
1001 // Now this should fail since both fds are sparse (-1)
1002 const sqe = try ring.read(0xcccccccc, fd_index, .{ .buffer = &buffer }, 0);
1003 try testing.expectEqual(linux.IORING_OP.READ, sqe.opcode);
1004 sqe.flags |= linux.IOSQE_FIXED_FILE;
1005
1006 try testing.expectEqual(@as(u32, 1), try ring.submit());
1007 const cqe = try ring.copy_cqe();
1008 try testing.expectEqual(linux.E.BADF, cqe.err());
1009 }
1010
1011 try ring.unregister_files();
1012}
1013
1014test "shutdown" {
1015 var ring = IoUring.init(16, 0) catch |err| switch (err) {
1016 error.SystemOutdated => return error.SkipZigTest,
1017 error.PermissionDenied => return error.SkipZigTest,
1018 else => return err,
1019 };
1020 defer ring.deinit();
1021
1022 var address: linux.sockaddr.in = .{
1023 .port = 0,
1024 .addr = @as(*align(1) const u32, @ptrCast(
1025 &@as([4]u8, .{ 127, 0, 0, 1 }),
1026 )).*,
1027 };
1028
1029 // Socket bound, expect shutdown to work
1030 {
1031 const server = try socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
1032 defer _ = linux.close(server);
1033 try posix.setsockopt(server, posix.SOL.SOCKET, posix.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1)));
1034 try bind(server, addrAny(&address), @sizeOf(linux.sockaddr.in));
1035 try listen(server, 1);
1036
1037 // set address to the OS-chosen IP/port.
1038 var slen: posix.socklen_t = @sizeOf(linux.sockaddr.in);
1039 try getsockname(server, addrAny(&address), &slen);
1040
1041 const shutdown_sqe = try ring.shutdown(0x445445445, server, linux.SHUT.RD);
1042 try testing.expectEqual(linux.IORING_OP.SHUTDOWN, shutdown_sqe.opcode);
1043 try testing.expectEqual(@as(i32, server), shutdown_sqe.fd);
1044
1045 try testing.expectEqual(@as(u32, 1), try ring.submit());
1046
1047 const cqe = try ring.copy_cqe();
1048 switch (cqe.err()) {
1049 .SUCCESS => {},
1050 // This kernel's io_uring does not yet implement shutdown (kernel version < 5.11)
1051 .INVAL => return error.SkipZigTest,
1052 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
1053 }
1054
1055 try testing.expectEqual(linux.io_uring_cqe{
1056 .user_data = 0x445445445,
1057 .res = 0,
1058 .flags = 0,
1059 }, cqe);
1060 }
1061
1062 // Socket not bound, expect to fail with ENOTCONN
1063 {
1064 const server = try socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
1065 defer _ = linux.close(server);
1066
1067 const shutdown_sqe = ring.shutdown(0x445445445, server, linux.SHUT.RD) catch |err| switch (err) {
1068 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
1069 };
1070 try testing.expectEqual(linux.IORING_OP.SHUTDOWN, shutdown_sqe.opcode);
1071 try testing.expectEqual(@as(i32, server), shutdown_sqe.fd);
1072
1073 try testing.expectEqual(@as(u32, 1), try ring.submit());
1074
1075 const cqe = try ring.copy_cqe();
1076 try testing.expectEqual(@as(u64, 0x445445445), cqe.user_data);
1077 try testing.expectEqual(linux.E.NOTCONN, cqe.err());
1078 }
1079}
1080
1081test "renameat" {
1082 const io = testing.io;
1083
1084 var ring = IoUring.init(1, 0) catch |err| switch (err) {
1085 error.SystemOutdated => return error.SkipZigTest,
1086 error.PermissionDenied => return error.SkipZigTest,
1087 else => return err,
1088 };
1089 defer ring.deinit();
1090
1091 const old_path = "test_io_uring_renameat_old";
1092 const new_path = "test_io_uring_renameat_new";
1093
1094 var tmp = std.testing.tmpDir(.{});
1095 defer tmp.cleanup();
1096
1097 // Write old file with data
1098
1099 const old_file = try tmp.dir.createFile(io, old_path, .{});
1100 defer old_file.close(io);
1101 try old_file.writeStreamingAll(io, "hello");
1102
1103 // Submit renameat
1104
1105 const sqe = try ring.renameat(
1106 0x12121212,
1107 tmp.dir.handle,
1108 old_path,
1109 tmp.dir.handle,
1110 new_path,
1111 0,
1112 );
1113 try testing.expectEqual(linux.IORING_OP.RENAMEAT, sqe.opcode);
1114 try testing.expectEqual(@as(i32, tmp.dir.handle), sqe.fd);
1115 try testing.expectEqual(@as(i32, tmp.dir.handle), @as(i32, @bitCast(sqe.len)));
1116 try testing.expectEqual(@as(u32, 1), try ring.submit());
1117
1118 const cqe = try ring.copy_cqe();
1119 switch (cqe.err()) {
1120 .SUCCESS => {},
1121 // This kernel's io_uring does not yet implement renameat (kernel version < 5.11)
1122 .BADF, .INVAL => return error.SkipZigTest,
1123 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
1124 }
1125 try testing.expectEqual(linux.io_uring_cqe{
1126 .user_data = 0x12121212,
1127 .res = 0,
1128 .flags = 0,
1129 }, cqe);
1130
1131 // Validate that the old file doesn't exist anymore
1132 try testing.expectError(error.FileNotFound, tmp.dir.openFile(io, old_path, .{}));
1133
1134 // Validate that the new file exists with the proper content
1135 var new_file_data: [16]u8 = undefined;
1136 try testing.expectEqualStrings("hello", try tmp.dir.readFile(io, new_path, &new_file_data));
1137}
1138
1139test "unlinkat" {
1140 const io = testing.io;
1141
1142 var ring = IoUring.init(1, 0) catch |err| switch (err) {
1143 error.SystemOutdated => return error.SkipZigTest,
1144 error.PermissionDenied => return error.SkipZigTest,
1145 else => return err,
1146 };
1147 defer ring.deinit();
1148
1149 const path = "test_io_uring_unlinkat";
1150
1151 var tmp = std.testing.tmpDir(.{});
1152 defer tmp.cleanup();
1153
1154 // Write old file with data
1155
1156 const file = try tmp.dir.createFile(io, path, .{});
1157 defer file.close(io);
1158
1159 // Submit unlinkat
1160
1161 const sqe = try ring.unlinkat(
1162 0x12121212,
1163 tmp.dir.handle,
1164 path,
1165 0,
1166 );
1167 try testing.expectEqual(linux.IORING_OP.UNLINKAT, sqe.opcode);
1168 try testing.expectEqual(@as(i32, tmp.dir.handle), sqe.fd);
1169 try testing.expectEqual(@as(u32, 1), try ring.submit());
1170
1171 const cqe = try ring.copy_cqe();
1172 switch (cqe.err()) {
1173 .SUCCESS => {},
1174 // This kernel's io_uring does not yet implement unlinkat (kernel version < 5.11)
1175 .BADF, .INVAL => return error.SkipZigTest,
1176 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
1177 }
1178 try testing.expectEqual(linux.io_uring_cqe{
1179 .user_data = 0x12121212,
1180 .res = 0,
1181 .flags = 0,
1182 }, cqe);
1183
1184 // Validate that the file doesn't exist anymore
1185 _ = tmp.dir.openFile(io, path, .{}) catch |err| switch (err) {
1186 error.FileNotFound => {},
1187 else => std.debug.panic("unexpected error: {}", .{err}),
1188 };
1189}
1190
1191test "mkdirat" {
1192 const io = testing.io;
1193
1194 var ring = IoUring.init(1, 0) catch |err| switch (err) {
1195 error.SystemOutdated => return error.SkipZigTest,
1196 error.PermissionDenied => return error.SkipZigTest,
1197 else => return err,
1198 };
1199 defer ring.deinit();
1200
1201 var tmp = std.testing.tmpDir(.{});
1202 defer tmp.cleanup();
1203
1204 const path = "test_io_uring_mkdirat";
1205
1206 // Submit mkdirat
1207
1208 const sqe = try ring.mkdirat(
1209 0x12121212,
1210 tmp.dir.handle,
1211 path,
1212 0o0755,
1213 );
1214 try testing.expectEqual(linux.IORING_OP.MKDIRAT, sqe.opcode);
1215 try testing.expectEqual(@as(i32, tmp.dir.handle), sqe.fd);
1216 try testing.expectEqual(@as(u32, 1), try ring.submit());
1217
1218 const cqe = try ring.copy_cqe();
1219 switch (cqe.err()) {
1220 .SUCCESS => {},
1221 // This kernel's io_uring does not yet implement mkdirat (kernel version < 5.15)
1222 .BADF, .INVAL => return error.SkipZigTest,
1223 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
1224 }
1225 try testing.expectEqual(linux.io_uring_cqe{
1226 .user_data = 0x12121212,
1227 .res = 0,
1228 .flags = 0,
1229 }, cqe);
1230
1231 // Validate that the directory exist
1232 _ = try tmp.dir.openDir(io, path, .{});
1233}
1234
1235test "symlinkat" {
1236 const io = testing.io;
1237
1238 var ring = IoUring.init(1, 0) catch |err| switch (err) {
1239 error.SystemOutdated => return error.SkipZigTest,
1240 error.PermissionDenied => return error.SkipZigTest,
1241 else => return err,
1242 };
1243 defer ring.deinit();
1244
1245 var tmp = std.testing.tmpDir(.{});
1246 defer tmp.cleanup();
1247
1248 const path = "test_io_uring_symlinkat";
1249 const link_path = "test_io_uring_symlinkat_link";
1250
1251 const file = try tmp.dir.createFile(io, path, .{});
1252 defer file.close(io);
1253
1254 // Submit symlinkat
1255
1256 const sqe = try ring.symlinkat(
1257 0x12121212,
1258 path,
1259 tmp.dir.handle,
1260 link_path,
1261 );
1262 try testing.expectEqual(linux.IORING_OP.SYMLINKAT, sqe.opcode);
1263 try testing.expectEqual(@as(i32, tmp.dir.handle), sqe.fd);
1264 try testing.expectEqual(@as(u32, 1), try ring.submit());
1265
1266 const cqe = try ring.copy_cqe();
1267 switch (cqe.err()) {
1268 .SUCCESS => {},
1269 // This kernel's io_uring does not yet implement symlinkat (kernel version < 5.15)
1270 .BADF, .INVAL => return error.SkipZigTest,
1271 // Can occur on certain filesystems (seen on CIFS)
1272 .OPNOTSUPP => return error.SkipZigTest,
1273 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
1274 }
1275 try testing.expectEqual(linux.io_uring_cqe{
1276 .user_data = 0x12121212,
1277 .res = 0,
1278 .flags = 0,
1279 }, cqe);
1280
1281 // Validate that the symlink exist
1282 _ = try tmp.dir.openFile(io, link_path, .{});
1283}
1284
1285test "linkat" {
1286 const io = testing.io;
1287
1288 var ring = IoUring.init(1, 0) catch |err| switch (err) {
1289 error.SystemOutdated => return error.SkipZigTest,
1290 error.PermissionDenied => return error.SkipZigTest,
1291 else => return err,
1292 };
1293 defer ring.deinit();
1294
1295 var tmp = std.testing.tmpDir(.{});
1296 defer tmp.cleanup();
1297
1298 const first_path = "test_io_uring_linkat_first";
1299 const second_path = "test_io_uring_linkat_second";
1300
1301 // Write file with data
1302
1303 const first_file = try tmp.dir.createFile(io, first_path, .{});
1304 defer first_file.close(io);
1305 try first_file.writeStreamingAll(io, "hello");
1306
1307 // Submit linkat
1308
1309 const sqe = try ring.linkat(
1310 0x12121212,
1311 tmp.dir.handle,
1312 first_path,
1313 tmp.dir.handle,
1314 second_path,
1315 0,
1316 );
1317 try testing.expectEqual(linux.IORING_OP.LINKAT, sqe.opcode);
1318 try testing.expectEqual(@as(i32, tmp.dir.handle), sqe.fd);
1319 try testing.expectEqual(@as(i32, tmp.dir.handle), @as(i32, @bitCast(sqe.len)));
1320 try testing.expectEqual(@as(u32, 1), try ring.submit());
1321
1322 const cqe = try ring.copy_cqe();
1323 switch (cqe.err()) {
1324 .SUCCESS => {},
1325 // This kernel's io_uring does not yet implement linkat (kernel version < 5.15)
1326 .BADF, .INVAL => return error.SkipZigTest,
1327 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
1328 }
1329 try testing.expectEqual(linux.io_uring_cqe{
1330 .user_data = 0x12121212,
1331 .res = 0,
1332 .flags = 0,
1333 }, cqe);
1334
1335 // Validate the second file
1336 var second_file_data: [16]u8 = undefined;
1337 try testing.expectEqualStrings("hello", try tmp.dir.readFile(io, second_path, &second_file_data));
1338}
1339
1340test "provide_buffers: read" {
1341 const io = testing.io;
1342
1343 var ring = IoUring.init(1, 0) catch |err| switch (err) {
1344 error.SystemOutdated => return error.SkipZigTest,
1345 error.PermissionDenied => return error.SkipZigTest,
1346 else => return err,
1347 };
1348 defer ring.deinit();
1349
1350 const file = try Io.Dir.openFileAbsolute(io, "/dev/zero", .{});
1351 defer file.close(io);
1352
1353 const group_id = 1337;
1354 const buffer_id = 0;
1355
1356 const buffer_len = 128;
1357
1358 var buffers: [4][buffer_len]u8 = undefined;
1359
1360 // Provide 4 buffers
1361
1362 {
1363 const sqe = try ring.provide_buffers(0xcccccccc, @as([*]u8, @ptrCast(&buffers)), buffer_len, buffers.len, group_id, buffer_id);
1364 try testing.expectEqual(linux.IORING_OP.PROVIDE_BUFFERS, sqe.opcode);
1365 try testing.expectEqual(@as(i32, buffers.len), sqe.fd);
1366 try testing.expectEqual(@as(u32, buffers[0].len), sqe.len);
1367 try testing.expectEqual(@as(u16, group_id), sqe.buf_index);
1368 try testing.expectEqual(@as(u32, 1), try ring.submit());
1369
1370 const cqe = try ring.copy_cqe();
1371 switch (cqe.err()) {
1372 // Happens when the kernel is < 5.7
1373 .INVAL, .BADF => return error.SkipZigTest,
1374 .SUCCESS => {},
1375 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
1376 }
1377 try testing.expectEqual(@as(u64, 0xcccccccc), cqe.user_data);
1378 }
1379
1380 // Do 4 reads which should consume all buffers
1381
1382 var i: usize = 0;
1383 while (i < buffers.len) : (i += 1) {
1384 const sqe = try ring.read(0xdededede, file.handle, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0);
1385 try testing.expectEqual(linux.IORING_OP.READ, sqe.opcode);
1386 try testing.expectEqual(@as(i32, file.handle), sqe.fd);
1387 try testing.expectEqual(@as(u64, 0), sqe.addr);
1388 try testing.expectEqual(@as(u32, buffer_len), sqe.len);
1389 try testing.expectEqual(@as(u16, group_id), sqe.buf_index);
1390 try testing.expectEqual(@as(u32, 1), try ring.submit());
1391
1392 const cqe = try ring.copy_cqe();
1393 switch (cqe.err()) {
1394 .SUCCESS => {},
1395 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
1396 }
1397
1398 try testing.expect(cqe.flags & linux.IORING_CQE_F_BUFFER == linux.IORING_CQE_F_BUFFER);
1399 const used_buffer_id = cqe.flags >> 16;
1400 try testing.expect(used_buffer_id >= 0 and used_buffer_id <= 3);
1401 try testing.expectEqual(@as(i32, buffer_len), cqe.res);
1402
1403 try testing.expectEqual(@as(u64, 0xdededede), cqe.user_data);
1404 try testing.expectEqualSlices(u8, &@as([buffer_len]u8, @splat(0)), buffers[used_buffer_id][0..@as(usize, @intCast(cqe.res))]);
1405 }
1406
1407 // This read should fail
1408
1409 {
1410 const sqe = try ring.read(0xdfdfdfdf, file.handle, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0);
1411 try testing.expectEqual(linux.IORING_OP.READ, sqe.opcode);
1412 try testing.expectEqual(@as(i32, file.handle), sqe.fd);
1413 try testing.expectEqual(@as(u64, 0), sqe.addr);
1414 try testing.expectEqual(@as(u32, buffer_len), sqe.len);
1415 try testing.expectEqual(@as(u16, group_id), sqe.buf_index);
1416 try testing.expectEqual(@as(u32, 1), try ring.submit());
1417
1418 const cqe = try ring.copy_cqe();
1419 switch (cqe.err()) {
1420 // Expected
1421 .NOBUFS => {},
1422 .SUCCESS => std.debug.panic("unexpected success", .{}),
1423 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
1424 }
1425 try testing.expectEqual(@as(u64, 0xdfdfdfdf), cqe.user_data);
1426 }
1427
1428 // Provide 1 buffer again
1429
1430 // Deliberately put something we don't expect in the buffers
1431 @memset(mem.sliceAsBytes(&buffers), 42);
1432
1433 const reprovided_buffer_id = 2;
1434
1435 {
1436 _ = try ring.provide_buffers(0xabababab, @as([*]u8, @ptrCast(&buffers[reprovided_buffer_id])), buffer_len, 1, group_id, reprovided_buffer_id);
1437 try testing.expectEqual(@as(u32, 1), try ring.submit());
1438
1439 const cqe = try ring.copy_cqe();
1440 switch (cqe.err()) {
1441 .SUCCESS => {},
1442 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
1443 }
1444 }
1445
1446 // Final read which should work
1447
1448 {
1449 const sqe = try ring.read(0xdfdfdfdf, file.handle, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0);
1450 try testing.expectEqual(linux.IORING_OP.READ, sqe.opcode);
1451 try testing.expectEqual(@as(i32, file.handle), sqe.fd);
1452 try testing.expectEqual(@as(u64, 0), sqe.addr);
1453 try testing.expectEqual(@as(u32, buffer_len), sqe.len);
1454 try testing.expectEqual(@as(u16, group_id), sqe.buf_index);
1455 try testing.expectEqual(@as(u32, 1), try ring.submit());
1456
1457 const cqe = try ring.copy_cqe();
1458 switch (cqe.err()) {
1459 .SUCCESS => {},
1460 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
1461 }
1462
1463 try testing.expect(cqe.flags & linux.IORING_CQE_F_BUFFER == linux.IORING_CQE_F_BUFFER);
1464 const used_buffer_id = cqe.flags >> 16;
1465 try testing.expectEqual(used_buffer_id, reprovided_buffer_id);
1466 try testing.expectEqual(@as(i32, buffer_len), cqe.res);
1467 try testing.expectEqual(@as(u64, 0xdfdfdfdf), cqe.user_data);
1468 try testing.expectEqualSlices(u8, &@as([buffer_len]u8, @splat(0)), buffers[used_buffer_id][0..@as(usize, @intCast(cqe.res))]);
1469 }
1470}
1471
1472test "remove_buffers" {
1473 const io = testing.io;
1474
1475 var ring = IoUring.init(1, 0) catch |err| switch (err) {
1476 error.SystemOutdated => return error.SkipZigTest,
1477 error.PermissionDenied => return error.SkipZigTest,
1478 else => return err,
1479 };
1480 defer ring.deinit();
1481
1482 const file = try Io.Dir.openFileAbsolute(io, "/dev/zero", .{});
1483 defer file.close(io);
1484
1485 const group_id = 1337;
1486 const buffer_id = 0;
1487
1488 const buffer_len = 128;
1489
1490 var buffers: [4][buffer_len]u8 = undefined;
1491
1492 // Provide 4 buffers
1493
1494 {
1495 _ = try ring.provide_buffers(0xcccccccc, @as([*]u8, @ptrCast(&buffers)), buffer_len, buffers.len, group_id, buffer_id);
1496 try testing.expectEqual(@as(u32, 1), try ring.submit());
1497
1498 const cqe = try ring.copy_cqe();
1499 switch (cqe.err()) {
1500 .INVAL, .BADF => return error.SkipZigTest,
1501 .SUCCESS => {},
1502 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
1503 }
1504 try testing.expectEqual(@as(u64, 0xcccccccc), cqe.user_data);
1505 }
1506
1507 // Remove 3 buffers
1508
1509 {
1510 const sqe = try ring.remove_buffers(0xbababababa, 3, group_id);
1511 try testing.expectEqual(linux.IORING_OP.REMOVE_BUFFERS, sqe.opcode);
1512 try testing.expectEqual(@as(i32, 3), sqe.fd);
1513 try testing.expectEqual(@as(u64, 0), sqe.addr);
1514 try testing.expectEqual(@as(u16, group_id), sqe.buf_index);
1515 try testing.expectEqual(@as(u32, 1), try ring.submit());
1516
1517 const cqe = try ring.copy_cqe();
1518 switch (cqe.err()) {
1519 .SUCCESS => {},
1520 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
1521 }
1522 try testing.expectEqual(@as(u64, 0xbababababa), cqe.user_data);
1523 }
1524
1525 // This read should work
1526
1527 {
1528 _ = try ring.read(0xdfdfdfdf, file.handle, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0);
1529 try testing.expectEqual(@as(u32, 1), try ring.submit());
1530
1531 const cqe = try ring.copy_cqe();
1532 switch (cqe.err()) {
1533 .SUCCESS => {},
1534 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
1535 }
1536
1537 try testing.expect(cqe.flags & linux.IORING_CQE_F_BUFFER == linux.IORING_CQE_F_BUFFER);
1538 const used_buffer_id = cqe.flags >> 16;
1539 try testing.expect(used_buffer_id >= 0 and used_buffer_id < 4);
1540 try testing.expectEqual(@as(i32, buffer_len), cqe.res);
1541 try testing.expectEqual(@as(u64, 0xdfdfdfdf), cqe.user_data);
1542 try testing.expectEqualSlices(u8, &@as([buffer_len]u8, @splat(0)), buffers[used_buffer_id][0..@as(usize, @intCast(cqe.res))]);
1543 }
1544
1545 // Final read should _not_ work
1546
1547 {
1548 _ = try ring.read(0xdfdfdfdf, file.handle, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0);
1549 try testing.expectEqual(@as(u32, 1), try ring.submit());
1550
1551 const cqe = try ring.copy_cqe();
1552 switch (cqe.err()) {
1553 // Expected
1554 .NOBUFS => {},
1555 .SUCCESS => std.debug.panic("unexpected success", .{}),
1556 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
1557 }
1558 }
1559}
1560
1561test "provide_buffers: accept/connect/send/recv" {
1562 var ring = IoUring.init(16, 0) catch |err| switch (err) {
1563 error.SystemOutdated => return error.SkipZigTest,
1564 error.PermissionDenied => return error.SkipZigTest,
1565 else => return err,
1566 };
1567 defer ring.deinit();
1568
1569 const group_id = 1337;
1570 const buffer_id = 0;
1571
1572 const buffer_len = 128;
1573 var buffers: [4][buffer_len]u8 = undefined;
1574
1575 // Provide 4 buffers
1576
1577 {
1578 const sqe = try ring.provide_buffers(0xcccccccc, @as([*]u8, @ptrCast(&buffers)), buffer_len, buffers.len, group_id, buffer_id);
1579 try testing.expectEqual(linux.IORING_OP.PROVIDE_BUFFERS, sqe.opcode);
1580 try testing.expectEqual(@as(i32, buffers.len), sqe.fd);
1581 try testing.expectEqual(@as(u32, buffer_len), sqe.len);
1582 try testing.expectEqual(@as(u16, group_id), sqe.buf_index);
1583 try testing.expectEqual(@as(u32, 1), try ring.submit());
1584
1585 const cqe = try ring.copy_cqe();
1586 switch (cqe.err()) {
1587 // Happens when the kernel is < 5.7
1588 .INVAL => return error.SkipZigTest,
1589 // Happens on the kernel 5.4
1590 .BADF => return error.SkipZigTest,
1591 .SUCCESS => {},
1592 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
1593 }
1594 try testing.expectEqual(@as(u64, 0xcccccccc), cqe.user_data);
1595 }
1596
1597 const socket_test_harness = try createSocketTestHarness(&ring);
1598 defer socket_test_harness.close();
1599
1600 // Do 4 send on the socket
1601
1602 {
1603 var i: usize = 0;
1604 while (i < buffers.len) : (i += 1) {
1605 _ = try ring.send(0xdeaddead, socket_test_harness.server, &@as([buffer_len]u8, @splat('z')), 0);
1606 try testing.expectEqual(@as(u32, 1), try ring.submit());
1607 }
1608
1609 var cqes: [4]linux.io_uring_cqe = undefined;
1610 try testing.expectEqual(@as(u32, 4), try ring.copy_cqes(&cqes, 4));
1611 }
1612
1613 // Do 4 recv which should consume all buffers
1614
1615 // Deliberately put something we don't expect in the buffers
1616 @memset(mem.sliceAsBytes(&buffers), 1);
1617
1618 var i: usize = 0;
1619 while (i < buffers.len) : (i += 1) {
1620 const sqe = try ring.recv(0xdededede, socket_test_harness.client, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0);
1621 try testing.expectEqual(linux.IORING_OP.RECV, sqe.opcode);
1622 try testing.expectEqual(@as(i32, socket_test_harness.client), sqe.fd);
1623 try testing.expectEqual(@as(u64, 0), sqe.addr);
1624 try testing.expectEqual(@as(u32, buffer_len), sqe.len);
1625 try testing.expectEqual(@as(u16, group_id), sqe.buf_index);
1626 try testing.expectEqual(@as(u32, 0), sqe.rw_flags);
1627 try testing.expectEqual(@as(u32, linux.IOSQE_BUFFER_SELECT), sqe.flags);
1628 try testing.expectEqual(@as(u32, 1), try ring.submit());
1629
1630 const cqe = try ring.copy_cqe();
1631 switch (cqe.err()) {
1632 .SUCCESS => {},
1633 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
1634 }
1635
1636 try testing.expect(cqe.flags & linux.IORING_CQE_F_BUFFER == linux.IORING_CQE_F_BUFFER);
1637 const used_buffer_id = cqe.flags >> 16;
1638 try testing.expect(used_buffer_id >= 0 and used_buffer_id <= 3);
1639 try testing.expectEqual(@as(i32, buffer_len), cqe.res);
1640
1641 try testing.expectEqual(@as(u64, 0xdededede), cqe.user_data);
1642 const buffer = buffers[used_buffer_id][0..@as(usize, @intCast(cqe.res))];
1643 try testing.expectEqualSlices(u8, &@as([buffer_len]u8, @splat('z')), buffer);
1644 }
1645
1646 // This recv should fail
1647
1648 {
1649 const sqe = try ring.recv(0xdfdfdfdf, socket_test_harness.client, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0);
1650 try testing.expectEqual(linux.IORING_OP.RECV, sqe.opcode);
1651 try testing.expectEqual(@as(i32, socket_test_harness.client), sqe.fd);
1652 try testing.expectEqual(@as(u64, 0), sqe.addr);
1653 try testing.expectEqual(@as(u32, buffer_len), sqe.len);
1654 try testing.expectEqual(@as(u16, group_id), sqe.buf_index);
1655 try testing.expectEqual(@as(u32, 0), sqe.rw_flags);
1656 try testing.expectEqual(@as(u32, linux.IOSQE_BUFFER_SELECT), sqe.flags);
1657 try testing.expectEqual(@as(u32, 1), try ring.submit());
1658
1659 const cqe = try ring.copy_cqe();
1660 switch (cqe.err()) {
1661 // Expected
1662 .NOBUFS => {},
1663 .SUCCESS => std.debug.panic("unexpected success", .{}),
1664 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
1665 }
1666 try testing.expectEqual(@as(u64, 0xdfdfdfdf), cqe.user_data);
1667 }
1668
1669 // Provide 1 buffer again
1670
1671 const reprovided_buffer_id = 2;
1672
1673 {
1674 _ = try ring.provide_buffers(0xabababab, @as([*]u8, @ptrCast(&buffers[reprovided_buffer_id])), buffer_len, 1, group_id, reprovided_buffer_id);
1675 try testing.expectEqual(@as(u32, 1), try ring.submit());
1676
1677 const cqe = try ring.copy_cqe();
1678 switch (cqe.err()) {
1679 .SUCCESS => {},
1680 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
1681 }
1682 }
1683
1684 // Redo 1 send on the server socket
1685
1686 {
1687 _ = try ring.send(0xdeaddead, socket_test_harness.server, &@as([buffer_len]u8, @splat('w')), 0);
1688 try testing.expectEqual(@as(u32, 1), try ring.submit());
1689
1690 _ = try ring.copy_cqe();
1691 }
1692
1693 // Final recv which should work
1694
1695 // Deliberately put something we don't expect in the buffers
1696 @memset(mem.sliceAsBytes(&buffers), 1);
1697
1698 {
1699 const sqe = try ring.recv(0xdfdfdfdf, socket_test_harness.client, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0);
1700 try testing.expectEqual(linux.IORING_OP.RECV, sqe.opcode);
1701 try testing.expectEqual(@as(i32, socket_test_harness.client), sqe.fd);
1702 try testing.expectEqual(@as(u64, 0), sqe.addr);
1703 try testing.expectEqual(@as(u32, buffer_len), sqe.len);
1704 try testing.expectEqual(@as(u16, group_id), sqe.buf_index);
1705 try testing.expectEqual(@as(u32, 0), sqe.rw_flags);
1706 try testing.expectEqual(@as(u32, linux.IOSQE_BUFFER_SELECT), sqe.flags);
1707 try testing.expectEqual(@as(u32, 1), try ring.submit());
1708
1709 const cqe = try ring.copy_cqe();
1710 switch (cqe.err()) {
1711 .SUCCESS => {},
1712 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
1713 }
1714
1715 try testing.expect(cqe.flags & linux.IORING_CQE_F_BUFFER == linux.IORING_CQE_F_BUFFER);
1716 const used_buffer_id = cqe.flags >> 16;
1717 try testing.expectEqual(used_buffer_id, reprovided_buffer_id);
1718 try testing.expectEqual(@as(i32, buffer_len), cqe.res);
1719 try testing.expectEqual(@as(u64, 0xdfdfdfdf), cqe.user_data);
1720 const buffer = buffers[used_buffer_id][0..@as(usize, @intCast(cqe.res))];
1721 try testing.expectEqualSlices(u8, &@as([buffer_len]u8, @splat('w')), buffer);
1722 }
1723}
1724
1725test "accept multishot" {
1726 var ring = IoUring.init(16, 0) catch |err| switch (err) {
1727 error.SystemOutdated => return error.SkipZigTest,
1728 error.PermissionDenied => return error.SkipZigTest,
1729 else => return err,
1730 };
1731 defer ring.deinit();
1732
1733 var address: linux.sockaddr.in = .{
1734 .port = 0,
1735 .addr = @as(*align(1) const u32, @ptrCast(
1736 &@as([4]u8, .{ 127, 0, 0, 1 }),
1737 )).*,
1738 };
1739 const listener_socket = try createListenerSocket(&address);
1740 defer _ = linux.close(listener_socket);
1741
1742 // submit multishot accept operation
1743 var addr: posix.sockaddr = undefined;
1744 var addr_len: posix.socklen_t = @sizeOf(@TypeOf(addr));
1745 const userdata: u64 = 0xaaaaaaaa;
1746 _ = try ring.accept_multishot(userdata, listener_socket, &addr, &addr_len, 0);
1747 try testing.expectEqual(@as(u32, 1), try ring.submit());
1748
1749 var nr: usize = 4; // number of clients to connect
1750 while (nr > 0) : (nr -= 1) {
1751 // connect client
1752 const client = try socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
1753 errdefer _ = linux.close(client);
1754 try connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in));
1755
1756 // test accept completion
1757 var cqe = try ring.copy_cqe();
1758 if (cqe.err() == .INVAL) return error.SkipZigTest;
1759 try testing.expect(cqe.res > 0);
1760 try testing.expect(cqe.user_data == userdata);
1761 try testing.expect(cqe.flags & linux.IORING_CQE_F_MORE > 0); // more flag is set
1762
1763 _ = linux.close(client);
1764 }
1765}
1766
1767test "accept/connect/send_zc/recv" {
1768 var ring = IoUring.init(16, 0) catch |err| switch (err) {
1769 error.SystemOutdated => return error.SkipZigTest,
1770 error.PermissionDenied => return error.SkipZigTest,
1771 else => return err,
1772 };
1773 defer ring.deinit();
1774
1775 const probe = ring.get_probe() catch return error.SkipZigTest;
1776 const ops_not_supported = !probe.is_supported(.ACCEPT) or
1777 !probe.is_supported(.CONNECT) or
1778 !probe.is_supported(.SEND_ZC) or
1779 !probe.is_supported(.RECV);
1780 if (ops_not_supported) return error.SkipZigTest;
1781
1782 const socket_test_harness = try createSocketTestHarness(&ring);
1783 defer socket_test_harness.close();
1784
1785 const buffer_send: [15]u8 = .{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe };
1786 var buffer_recv: [10]u8 = @splat(0);
1787
1788 // zero-copy send
1789 const sqe_send = try ring.send_zc(0xeeeeeeee, socket_test_harness.client, buffer_send[0..], 0, 0);
1790 sqe_send.flags |= linux.IOSQE_IO_LINK;
1791 _ = try ring.recv(0xffffffff, socket_test_harness.server, .{ .buffer = buffer_recv[0..] }, 0);
1792 try testing.expectEqual(@as(u32, 2), try ring.submit());
1793
1794 var cqe_send = try ring.copy_cqe();
1795 // First completion of zero-copy send.
1796 // IORING_CQE_F_MORE, means that there
1797 // will be a second completion event / notification for the
1798 // request, with the user_data field set to the same value.
1799 // buffer_send must be keep alive until second cqe.
1800 try testing.expectEqual(linux.io_uring_cqe{
1801 .user_data = 0xeeeeeeee,
1802 .res = buffer_send.len,
1803 .flags = linux.IORING_CQE_F_MORE,
1804 }, cqe_send);
1805
1806 cqe_send, const cqe_recv = brk: {
1807 const cqe1 = try ring.copy_cqe();
1808 const cqe2 = try ring.copy_cqe();
1809 break :brk if (cqe1.user_data == 0xeeeeeeee) .{ cqe1, cqe2 } else .{ cqe2, cqe1 };
1810 };
1811
1812 try testing.expectEqual(linux.io_uring_cqe{
1813 .user_data = 0xffffffff,
1814 .res = buffer_recv.len,
1815 .flags = cqe_recv.flags & linux.IORING_CQE_F_SOCK_NONEMPTY,
1816 }, cqe_recv);
1817 try testing.expectEqualSlices(u8, buffer_send[0..buffer_recv.len], buffer_recv[0..]);
1818
1819 // Second completion of zero-copy send.
1820 // IORING_CQE_F_NOTIF in flags signals that kernel is done with send_buffer
1821 try testing.expectEqual(linux.io_uring_cqe{
1822 .user_data = 0xeeeeeeee,
1823 .res = 0,
1824 .flags = linux.IORING_CQE_F_NOTIF,
1825 }, cqe_send);
1826}
1827
1828test "accept_direct" {
1829 var ring = IoUring.init(1, 0) catch |err| switch (err) {
1830 error.SystemOutdated => return error.SkipZigTest,
1831 error.PermissionDenied => return error.SkipZigTest,
1832 else => return err,
1833 };
1834 defer ring.deinit();
1835
1836 const probe = ring.get_probe() catch return error.SkipZigTest;
1837 if (!probe.is_supported(.ACCEPT)) return error.SkipZigTest;
1838
1839 var address: linux.sockaddr.in = .{
1840 .port = 0,
1841 .addr = @as(*align(1) const u32, @ptrCast(
1842 &@as([4]u8, .{ 127, 0, 0, 1 }),
1843 )).*,
1844 };
1845
1846 // register direct file descriptors
1847 var registered_fds: [2]linux.fd_t = @splat(-1);
1848 try ring.register_files(registered_fds[0..]);
1849
1850 const listener_socket = try createListenerSocket(&address);
1851 defer _ = linux.close(listener_socket);
1852
1853 const accept_userdata: u64 = 0xaaaaaaaa;
1854 const read_userdata: u64 = 0xbbbbbbbb;
1855 const data = [_]u8{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe };
1856
1857 for (0..2) |_| {
1858 for (registered_fds, 0..) |_, i| {
1859 var buffer_recv: [16]u8 = @splat(0);
1860 const buffer_send: []const u8 = data[0 .. data.len - i]; // make it different at each loop
1861
1862 // submit accept, will chose registered fd and return index in cqe
1863 _ = try ring.accept_direct(accept_userdata, listener_socket, null, null, 0);
1864 try testing.expectEqual(@as(u32, 1), try ring.submit());
1865
1866 // connect
1867 const client = try socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
1868 try connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in));
1869 defer _ = linux.close(client);
1870
1871 // accept completion
1872 const cqe_accept = try ring.copy_cqe();
1873 try testing.expectEqual(posix.E.SUCCESS, cqe_accept.err());
1874 const fd_index = cqe_accept.res;
1875 try testing.expect(fd_index < registered_fds.len);
1876 try testing.expect(cqe_accept.user_data == accept_userdata);
1877
1878 // send data
1879 _ = try send(client, buffer_send, 0);
1880
1881 // Example of how to use registered fd:
1882 // Submit receive to fixed file returned by accept (fd_index).
1883 // Fd field is set to registered file index, returned by accept.
1884 // Flag linux.IOSQE_FIXED_FILE must be set.
1885 const recv_sqe = try ring.recv(read_userdata, fd_index, .{ .buffer = &buffer_recv }, 0);
1886 recv_sqe.flags |= linux.IOSQE_FIXED_FILE;
1887 try testing.expectEqual(@as(u32, 1), try ring.submit());
1888
1889 // accept receive
1890 const recv_cqe = try ring.copy_cqe();
1891 try testing.expect(recv_cqe.user_data == read_userdata);
1892 try testing.expect(recv_cqe.res == buffer_send.len);
1893 try testing.expectEqualSlices(u8, buffer_send, buffer_recv[0..buffer_send.len]);
1894 }
1895 // no more available fds, accept will get NFILE error
1896 {
1897 // submit accept
1898 _ = try ring.accept_direct(accept_userdata, listener_socket, null, null, 0);
1899 try testing.expectEqual(@as(u32, 1), try ring.submit());
1900 // connect
1901 const client = try socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
1902 try connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in));
1903 defer _ = linux.close(client);
1904 // completion with error
1905 const cqe_accept = try ring.copy_cqe();
1906 try testing.expect(cqe_accept.user_data == accept_userdata);
1907 try testing.expectEqual(posix.E.NFILE, cqe_accept.err());
1908 }
1909 // return file descriptors to kernel
1910 try ring.register_files_update(0, registered_fds[0..]);
1911 }
1912 try ring.unregister_files();
1913}
1914
1915test "accept_multishot_direct" {
1916 var ring = IoUring.init(1, 0) catch |err| switch (err) {
1917 error.SystemOutdated => return error.SkipZigTest,
1918 error.PermissionDenied => return error.SkipZigTest,
1919 else => return err,
1920 };
1921 defer ring.deinit();
1922
1923 const probe = ring.get_probe() catch return error.SkipZigTest;
1924 if (!probe.is_supported(.ACCEPT)) return error.SkipZigTest;
1925
1926 var address: linux.sockaddr.in = .{
1927 .port = 0,
1928 .addr = @as(*align(1) const u32, @ptrCast(
1929 &@as([4]u8, .{ 127, 0, 0, 1 }),
1930 )).*,
1931 };
1932
1933 var registered_fds: [2]linux.fd_t = @splat(-1);
1934 try ring.register_files(registered_fds[0..]);
1935
1936 const listener_socket = try createListenerSocket(&address);
1937 defer _ = linux.close(listener_socket);
1938
1939 const accept_userdata: u64 = 0xaaaaaaaa;
1940
1941 for (0..2) |_| {
1942 // submit multishot accept
1943 // Will chose registered fd and return index of the selected registered file in cqe.
1944 _ = try ring.accept_multishot_direct(accept_userdata, listener_socket, null, null, 0);
1945 try testing.expectEqual(@as(u32, 1), try ring.submit());
1946
1947 for (registered_fds) |_| {
1948 // connect
1949 const client = try socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
1950 try connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in));
1951 defer _ = linux.close(client);
1952
1953 // accept completion
1954 const cqe_accept = try ring.copy_cqe();
1955 const fd_index = cqe_accept.res;
1956 try testing.expect(fd_index < registered_fds.len);
1957 try testing.expect(cqe_accept.user_data == accept_userdata);
1958 try testing.expect(cqe_accept.flags & linux.IORING_CQE_F_MORE > 0); // has more is set
1959 }
1960 // No more available fds, accept will get NFILE error.
1961 // Multishot is terminated (more flag is not set).
1962 {
1963 // connect
1964 const client = try socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
1965 try connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in));
1966 defer _ = linux.close(client);
1967 // completion with error
1968 const cqe_accept = try ring.copy_cqe();
1969 try testing.expect(cqe_accept.user_data == accept_userdata);
1970 try testing.expectEqual(posix.E.NFILE, cqe_accept.err());
1971 try testing.expect(cqe_accept.flags & linux.IORING_CQE_F_MORE == 0); // has more is not set
1972 }
1973 // return file descriptors to kernel
1974 try ring.register_files_update(0, registered_fds[0..]);
1975 }
1976 try ring.unregister_files();
1977}
1978
1979test "socket" {
1980 var ring = IoUring.init(1, 0) catch |err| switch (err) {
1981 error.SystemOutdated => return error.SkipZigTest,
1982 error.PermissionDenied => return error.SkipZigTest,
1983 else => return err,
1984 };
1985 defer ring.deinit();
1986
1987 const probe = ring.get_probe() catch return error.SkipZigTest;
1988 if (!probe.is_supported(.SOCKET)) return error.SkipZigTest;
1989
1990 // prepare, submit socket operation
1991 _ = try ring.socket(0, linux.AF.INET, posix.SOCK.STREAM, 0, 0);
1992 try testing.expectEqual(@as(u32, 1), try ring.submit());
1993
1994 // test completion
1995 var cqe = try ring.copy_cqe();
1996 try testing.expectEqual(posix.E.SUCCESS, cqe.err());
1997 const fd: linux.fd_t = @intCast(cqe.res);
1998 try testing.expect(fd > 2);
1999
2000 _ = linux.close(fd);
2001}
2002
2003test "socket_direct/socket_direct_alloc/close_direct" {
2004 var ring = IoUring.init(2, 0) catch |err| switch (err) {
2005 error.SystemOutdated => return error.SkipZigTest,
2006 error.PermissionDenied => return error.SkipZigTest,
2007 else => return err,
2008 };
2009 defer ring.deinit();
2010
2011 const probe = ring.get_probe() catch return error.SkipZigTest;
2012 if (!probe.is_supported(.SOCKET) or !probe.is_supported(.CLOSE)) return error.SkipZigTest;
2013
2014 var registered_fds: [3]linux.fd_t = @splat(-1);
2015 try ring.register_files(registered_fds[0..]);
2016
2017 // create socket in registered file descriptor at index 0 (last param)
2018 _ = try ring.socket_direct(0, linux.AF.INET, posix.SOCK.STREAM, 0, 0, 0);
2019 try testing.expectEqual(@as(u32, 1), try ring.submit());
2020 var cqe_socket = try ring.copy_cqe();
2021 try testing.expectEqual(posix.E.SUCCESS, cqe_socket.err());
2022 try testing.expect(cqe_socket.res == 0);
2023
2024 // create socket in registered file descriptor at index 1 (last param)
2025 _ = try ring.socket_direct(0, linux.AF.INET, posix.SOCK.STREAM, 0, 0, 1);
2026 try testing.expectEqual(@as(u32, 1), try ring.submit());
2027 cqe_socket = try ring.copy_cqe();
2028 try testing.expectEqual(posix.E.SUCCESS, cqe_socket.err());
2029 try testing.expect(cqe_socket.res == 0); // res is 0 when index is specified
2030
2031 // create socket in kernel chosen file descriptor index (_alloc version)
2032 // completion res has index from registered files
2033 _ = try ring.socket_direct_alloc(0, linux.AF.INET, posix.SOCK.STREAM, 0, 0);
2034 try testing.expectEqual(@as(u32, 1), try ring.submit());
2035 cqe_socket = try ring.copy_cqe();
2036 try testing.expectEqual(posix.E.SUCCESS, cqe_socket.err());
2037 try testing.expect(cqe_socket.res == 2); // returns registered file index
2038
2039 // use sockets from registered_fds in connect operation
2040 var address: linux.sockaddr.in = .{
2041 .port = 0,
2042 .addr = @as(*align(1) const u32, @ptrCast(
2043 &@as([4]u8, .{ 127, 0, 0, 1 }),
2044 )).*,
2045 };
2046 const listener_socket = try createListenerSocket(&address);
2047 defer _ = linux.close(listener_socket);
2048 const accept_userdata: u64 = 0xaaaaaaaa;
2049 const connect_userdata: u64 = 0xbbbbbbbb;
2050 const close_userdata: u64 = 0xcccccccc;
2051 for (registered_fds, 0..) |_, fd_index| {
2052 // prepare accept
2053 _ = try ring.accept(accept_userdata, listener_socket, null, null, 0);
2054 // prepare connect with fixed socket
2055 const connect_sqe = try ring.connect(connect_userdata, @intCast(fd_index), addrAny(&address), @sizeOf(linux.sockaddr.in));
2056 connect_sqe.flags |= linux.IOSQE_FIXED_FILE; // fd is fixed file index
2057 // submit both
2058 try testing.expectEqual(@as(u32, 2), try ring.submit());
2059 // get completions
2060 var cqe_connect = try ring.copy_cqe();
2061 var cqe_accept = try ring.copy_cqe();
2062 // ignore order
2063 if (cqe_connect.user_data == accept_userdata and cqe_accept.user_data == connect_userdata) {
2064 const a = cqe_accept;
2065 const b = cqe_connect;
2066 cqe_accept = b;
2067 cqe_connect = a;
2068 }
2069 // test connect completion
2070 try testing.expect(cqe_connect.user_data == connect_userdata);
2071 try testing.expectEqual(posix.E.SUCCESS, cqe_connect.err());
2072 // test accept completion
2073 try testing.expect(cqe_accept.user_data == accept_userdata);
2074 try testing.expectEqual(posix.E.SUCCESS, cqe_accept.err());
2075
2076 // submit and test close_direct
2077 _ = try ring.close_direct(close_userdata, @intCast(fd_index));
2078 try testing.expectEqual(@as(u32, 1), try ring.submit());
2079 var cqe_close = try ring.copy_cqe();
2080 try testing.expect(cqe_close.user_data == close_userdata);
2081 try testing.expectEqual(posix.E.SUCCESS, cqe_close.err());
2082 }
2083
2084 try ring.unregister_files();
2085}
2086
2087test "openat_direct/close_direct" {
2088 var ring = IoUring.init(2, 0) catch |err| switch (err) {
2089 error.SystemOutdated => return error.SkipZigTest,
2090 error.PermissionDenied => return error.SkipZigTest,
2091 else => return err,
2092 };
2093 defer ring.deinit();
2094
2095 const probe = ring.get_probe() catch return error.SkipZigTest;
2096 if (!probe.is_supported(.OPENAT) or !probe.is_supported(.CLOSE)) return error.SkipZigTest;
2097
2098 var registered_fds: [3]linux.fd_t = @splat(-1);
2099 try ring.register_files(registered_fds[0..]);
2100
2101 var tmp = std.testing.tmpDir(.{});
2102 defer tmp.cleanup();
2103 const path = "test_io_uring_close_direct";
2104 const flags: linux.O = .{ .ACCMODE = .RDWR, .CREAT = true };
2105 const mode: posix.mode_t = 0o666;
2106 const user_data: u64 = 0;
2107
2108 // use registered file at index 0 (last param)
2109 _ = try ring.openat_direct(user_data, tmp.dir.handle, path, flags, mode, 0);
2110 try testing.expectEqual(@as(u32, 1), try ring.submit());
2111 var cqe = try ring.copy_cqe();
2112 try testing.expectEqual(posix.E.SUCCESS, cqe.err());
2113 try testing.expect(cqe.res == 0);
2114
2115 // use registered file at index 1
2116 _ = try ring.openat_direct(user_data, tmp.dir.handle, path, flags, mode, 1);
2117 try testing.expectEqual(@as(u32, 1), try ring.submit());
2118 cqe = try ring.copy_cqe();
2119 try testing.expectEqual(posix.E.SUCCESS, cqe.err());
2120 try testing.expect(cqe.res == 0); // res is 0 when we specify index
2121
2122 // let kernel choose registered file index
2123 _ = try ring.openat_direct(user_data, tmp.dir.handle, path, flags, mode, linux.IORING_FILE_INDEX_ALLOC);
2124 try testing.expectEqual(@as(u32, 1), try ring.submit());
2125 cqe = try ring.copy_cqe();
2126 try testing.expectEqual(posix.E.SUCCESS, cqe.err());
2127 try testing.expect(cqe.res == 2); // chosen index is in res
2128
2129 // close all open file descriptors
2130 for (registered_fds, 0..) |_, fd_index| {
2131 _ = try ring.close_direct(user_data, @intCast(fd_index));
2132 try testing.expectEqual(@as(u32, 1), try ring.submit());
2133 var cqe_close = try ring.copy_cqe();
2134 try testing.expectEqual(posix.E.SUCCESS, cqe_close.err());
2135 }
2136 try ring.unregister_files();
2137}
2138
2139test "ring mapped buffers recv" {
2140 var ring = IoUring.init(16, 0) catch |err| switch (err) {
2141 error.SystemOutdated => return error.SkipZigTest,
2142 error.PermissionDenied => return error.SkipZigTest,
2143 else => return err,
2144 };
2145 defer ring.deinit();
2146
2147 // init buffer group
2148 const group_id: u16 = 1; // buffers group id
2149 const buffers_count: u16 = 2; // number of buffers in buffer group
2150 const buffer_size: usize = 4; // size of each buffer in group
2151 var buf_grp = BufferGroup.init(
2152 &ring,
2153 testing.allocator,
2154 group_id,
2155 buffer_size,
2156 buffers_count,
2157 ) catch |err| switch (err) {
2158 // kernel older than 5.19
2159 error.ArgumentsInvalid => return error.SkipZigTest,
2160 else => return err,
2161 };
2162 defer buf_grp.deinit(testing.allocator);
2163
2164 // create client/server fds
2165 const fds = try createSocketTestHarness(&ring);
2166 defer fds.close();
2167
2168 // for random user_data in sqe/cqe
2169 var Rnd = std.Random.DefaultPrng.init(std.testing.random_seed);
2170 var rnd = Rnd.random();
2171
2172 var round: usize = 4; // repeat send/recv cycle round times
2173 while (round > 0) : (round -= 1) {
2174 // client sends data
2175 const data = [_]u8{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe };
2176 {
2177 const user_data = rnd.int(u64);
2178 _ = try ring.send(user_data, fds.client, data[0..], 0);
2179 try testing.expectEqual(@as(u32, 1), try ring.submit());
2180 const cqe_send = try ring.copy_cqe();
2181 if (cqe_send.err() == .INVAL) return error.SkipZigTest;
2182 try testing.expectEqual(linux.io_uring_cqe{ .user_data = user_data, .res = data.len, .flags = 0 }, cqe_send);
2183 }
2184 var pos: usize = 0;
2185
2186 // read first chunk
2187 const cqe1 = try buf_grp_recv_submit_get_cqe(&ring, &buf_grp, fds.server, rnd.int(u64));
2188 var buf = try buf_grp.get(cqe1);
2189 try testing.expectEqualSlices(u8, data[pos..][0..buf.len], buf);
2190 pos += buf.len;
2191 // second chunk
2192 const cqe2 = try buf_grp_recv_submit_get_cqe(&ring, &buf_grp, fds.server, rnd.int(u64));
2193 buf = try buf_grp.get(cqe2);
2194 try testing.expectEqualSlices(u8, data[pos..][0..buf.len], buf);
2195 pos += buf.len;
2196
2197 // both buffers provided to the kernel are used so we get error
2198 // 'no more buffers', until we put buffers to the kernel
2199 {
2200 const user_data = rnd.int(u64);
2201 _ = try buf_grp.recv(user_data, fds.server, 0);
2202 try testing.expectEqual(@as(u32, 1), try ring.submit());
2203 const cqe = try ring.copy_cqe();
2204 try testing.expectEqual(user_data, cqe.user_data);
2205 try testing.expect(cqe.res < 0); // fail
2206 try testing.expectEqual(posix.E.NOBUFS, cqe.err());
2207 try testing.expect(cqe.flags & linux.IORING_CQE_F_BUFFER == 0); // IORING_CQE_F_BUFFER flags is set on success only
2208 try testing.expectError(error.NoBufferSelected, cqe.buffer_id());
2209 }
2210
2211 // put buffers back to the kernel
2212 try buf_grp.put(cqe1);
2213 try buf_grp.put(cqe2);
2214
2215 // read remaining data
2216 while (pos < data.len) {
2217 const cqe = try buf_grp_recv_submit_get_cqe(&ring, &buf_grp, fds.server, rnd.int(u64));
2218 buf = try buf_grp.get(cqe);
2219 try testing.expectEqualSlices(u8, data[pos..][0..buf.len], buf);
2220 pos += buf.len;
2221 try buf_grp.put(cqe);
2222 }
2223 }
2224}
2225
2226test "ring mapped buffers multishot recv" {
2227 var ring = IoUring.init(16, 0) catch |err| switch (err) {
2228 error.SystemOutdated => return error.SkipZigTest,
2229 error.PermissionDenied => return error.SkipZigTest,
2230 else => return err,
2231 };
2232 defer ring.deinit();
2233
2234 // init buffer group
2235 const group_id: u16 = 1; // buffers group id
2236 const buffers_count: u16 = 2; // number of buffers in buffer group
2237 const buffer_size: usize = 4; // size of each buffer in group
2238 var buf_grp = BufferGroup.init(
2239 &ring,
2240 testing.allocator,
2241 group_id,
2242 buffer_size,
2243 buffers_count,
2244 ) catch |err| switch (err) {
2245 // kernel older than 5.19
2246 error.ArgumentsInvalid => return error.SkipZigTest,
2247 else => return err,
2248 };
2249 defer buf_grp.deinit(testing.allocator);
2250
2251 // create client/server fds
2252 const fds = try createSocketTestHarness(&ring);
2253 defer fds.close();
2254
2255 // for random user_data in sqe/cqe
2256 var Rnd = std.Random.DefaultPrng.init(std.testing.random_seed);
2257 var rnd = Rnd.random();
2258
2259 var round: usize = 4; // repeat send/recv cycle round times
2260 while (round > 0) : (round -= 1) {
2261 // client sends data
2262 const data = [_]u8{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf };
2263 {
2264 const user_data = rnd.int(u64);
2265 _ = try ring.send(user_data, fds.client, data[0..], 0);
2266 try testing.expectEqual(@as(u32, 1), try ring.submit());
2267 const cqe_send = try ring.copy_cqe();
2268 if (cqe_send.err() == .INVAL) return error.SkipZigTest;
2269 try testing.expectEqual(linux.io_uring_cqe{ .user_data = user_data, .res = data.len, .flags = 0 }, cqe_send);
2270 }
2271
2272 // start multishot recv
2273 var recv_user_data = rnd.int(u64);
2274 _ = try buf_grp.recv_multishot(recv_user_data, fds.server, 0);
2275 try testing.expectEqual(@as(u32, 1), try ring.submit()); // submit
2276
2277 // server reads data into provided buffers
2278 // there are 2 buffers of size 4, so each read gets only chunk of data
2279 // we read four chunks of 4, 4, 4, 4 bytes each
2280 var chunk: []const u8 = data[0..buffer_size]; // first chunk
2281 const cqe1 = try expect_buf_grp_cqe(&ring, &buf_grp, recv_user_data, chunk);
2282 try testing.expect(cqe1.flags & linux.IORING_CQE_F_MORE > 0);
2283
2284 chunk = data[buffer_size .. buffer_size * 2]; // second chunk
2285 const cqe2 = try expect_buf_grp_cqe(&ring, &buf_grp, recv_user_data, chunk);
2286 try testing.expect(cqe2.flags & linux.IORING_CQE_F_MORE > 0);
2287
2288 // both buffers provided to the kernel are used so we get error
2289 // 'no more buffers', until we put buffers to the kernel
2290 {
2291 const cqe = try ring.copy_cqe();
2292 try testing.expectEqual(recv_user_data, cqe.user_data);
2293 try testing.expect(cqe.res < 0); // fail
2294 try testing.expectEqual(posix.E.NOBUFS, cqe.err());
2295 try testing.expect(cqe.flags & linux.IORING_CQE_F_BUFFER == 0); // IORING_CQE_F_BUFFER flags is set on success only
2296 // has more is not set
2297 // indicates that multishot is finished
2298 try testing.expect(cqe.flags & linux.IORING_CQE_F_MORE == 0);
2299 try testing.expectError(error.NoBufferSelected, cqe.buffer_id());
2300 }
2301
2302 // put buffers back to the kernel
2303 try buf_grp.put(cqe1);
2304 try buf_grp.put(cqe2);
2305
2306 // restart multishot
2307 recv_user_data = rnd.int(u64);
2308 _ = try buf_grp.recv_multishot(recv_user_data, fds.server, 0);
2309 try testing.expectEqual(@as(u32, 1), try ring.submit()); // submit
2310
2311 chunk = data[buffer_size * 2 .. buffer_size * 3]; // third chunk
2312 const cqe3 = try expect_buf_grp_cqe(&ring, &buf_grp, recv_user_data, chunk);
2313 try testing.expect(cqe3.flags & linux.IORING_CQE_F_MORE > 0);
2314 try buf_grp.put(cqe3);
2315
2316 chunk = data[buffer_size * 3 ..]; // last chunk
2317 const cqe4 = try expect_buf_grp_cqe(&ring, &buf_grp, recv_user_data, chunk);
2318 try testing.expect(cqe4.flags & linux.IORING_CQE_F_MORE > 0);
2319 try buf_grp.put(cqe4);
2320
2321 // cancel pending multishot recv operation
2322 {
2323 const cancel_user_data = rnd.int(u64);
2324 _ = try ring.cancel(cancel_user_data, recv_user_data, 0);
2325 try testing.expectEqual(@as(u32, 1), try ring.submit());
2326
2327 // expect completion of cancel operation and completion of recv operation
2328 var cqe_cancel = try ring.copy_cqe();
2329 if (cqe_cancel.err() == .INVAL) return error.SkipZigTest;
2330 var cqe_recv = try ring.copy_cqe();
2331 if (cqe_recv.err() == .INVAL) return error.SkipZigTest;
2332
2333 // don't depend on order of completions
2334 if (cqe_cancel.user_data == recv_user_data and cqe_recv.user_data == cancel_user_data) {
2335 const a = cqe_cancel;
2336 const b = cqe_recv;
2337 cqe_cancel = b;
2338 cqe_recv = a;
2339 }
2340
2341 // Note on different kernel results:
2342 // on older kernel (tested with v6.0.16, v6.1.57, v6.2.12, v6.4.16)
2343 // cqe_cancel.err() == .NOENT
2344 // cqe_recv.err() == .NOBUFS
2345 // on kernel (tested with v6.5.0, v6.5.7)
2346 // cqe_cancel.err() == .SUCCESS
2347 // cqe_recv.err() == .CANCELED
2348 // Upstream reference: https://github.com/axboe/liburing/issues/984
2349
2350 // cancel operation is success (or NOENT on older kernels)
2351 try testing.expectEqual(cancel_user_data, cqe_cancel.user_data);
2352 try testing.expect(cqe_cancel.err() == .NOENT or cqe_cancel.err() == .SUCCESS);
2353
2354 // recv operation is failed with err CANCELED (or NOBUFS on older kernels)
2355 try testing.expectEqual(recv_user_data, cqe_recv.user_data);
2356 try testing.expect(cqe_recv.res < 0);
2357 try testing.expect(cqe_recv.err() == .NOBUFS or cqe_recv.err() == .CANCELED);
2358 try testing.expect(cqe_recv.flags & linux.IORING_CQE_F_MORE == 0);
2359 }
2360 }
2361}
2362
2363test "copy_cqes with wrapping sq.cqes buffer" {
2364 var ring = IoUring.init(2, 0) catch |err| switch (err) {
2365 error.SystemOutdated => return error.SkipZigTest,
2366 error.PermissionDenied => return error.SkipZigTest,
2367 else => return err,
2368 };
2369 defer ring.deinit();
2370
2371 try testing.expectEqual(2, ring.sq.sqes.len);
2372 try testing.expectEqual(4, ring.cq.cqes.len);
2373
2374 // submit 2 entries, receive 2 completions
2375 var cqes: [8]linux.io_uring_cqe = undefined;
2376 {
2377 for (0..2) |_| {
2378 const sqe = try ring.get_sqe();
2379 sqe.prep_timeout(&.{ .sec = 0, .nsec = 10000 }, 0, 0);
2380 try testing.expect(try ring.submit() == 1);
2381 }
2382 var cqe_count: u32 = 0;
2383 while (cqe_count < 2) {
2384 cqe_count += try ring.copy_cqes(&cqes, 2 - cqe_count);
2385 }
2386 }
2387
2388 try testing.expectEqual(2, ring.cq.head.*);
2389
2390 // sq.sqes len is 4, starting at position 2
2391 // every 4 entries submit wraps completion buffer
2392 // we are reading ring.cq.cqes at indexes 2,3,0,1
2393 for (1..1024) |i| {
2394 for (0..4) |_| {
2395 const sqe = try ring.get_sqe();
2396 sqe.prep_timeout(&.{ .sec = 0, .nsec = 10000 }, 0, 0);
2397 try testing.expect(try ring.submit() == 1);
2398 }
2399 var cqe_count: u32 = 0;
2400 while (cqe_count < 4) {
2401 cqe_count += try ring.copy_cqes(&cqes, 4 - cqe_count);
2402 }
2403 try testing.expectEqual(4, cqe_count);
2404 try testing.expectEqual(2 + 4 * i, ring.cq.head.*);
2405 }
2406}
2407
2408test "bind/listen/connect" {
2409 if (builtin.cpu.arch == .s390x) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/25956
2410
2411 var ring = IoUring.init(4, 0) catch |err| switch (err) {
2412 error.SystemOutdated => return error.SkipZigTest,
2413 error.PermissionDenied => return error.SkipZigTest,
2414 else => return err,
2415 };
2416 defer ring.deinit();
2417
2418 const probe = ring.get_probe() catch return error.SkipZigTest;
2419 // LISTEN is higher required operation
2420 if (!probe.is_supported(.LISTEN)) return error.SkipZigTest;
2421
2422 var addr: linux.sockaddr.in = .{
2423 .port = 0,
2424 .addr = @as(*align(1) const u32, @ptrCast(
2425 &@as([4]u8, .{ 127, 0, 0, 1 }),
2426 )).*,
2427 };
2428 const proto: u32 = if (addr.family == linux.AF.UNIX) 0 else linux.IPPROTO.TCP;
2429
2430 const listen_fd = brk: {
2431 // Create socket
2432 _ = try ring.socket(1, addr.family, linux.SOCK.STREAM | linux.SOCK.CLOEXEC, proto, 0);
2433 try testing.expectEqual(1, try ring.submit());
2434 var cqe = try ring.copy_cqe();
2435 try testing.expectEqual(1, cqe.user_data);
2436 try testing.expectEqual(posix.E.SUCCESS, cqe.err());
2437 const listen_fd: linux.fd_t = @intCast(cqe.res);
2438 try testing.expect(listen_fd > 2);
2439
2440 // Prepare: set socket option * 2, bind, listen
2441 var optval: u32 = 1;
2442 (try ring.setsockopt(2, listen_fd, linux.SOL.SOCKET, linux.SO.REUSEADDR, mem.asBytes(&optval))).link_next();
2443 (try ring.setsockopt(3, listen_fd, linux.SOL.SOCKET, linux.SO.REUSEPORT, mem.asBytes(&optval))).link_next();
2444 (try ring.bind(4, listen_fd, addrAny(&addr), @sizeOf(linux.sockaddr.in), 0)).link_next();
2445 _ = try ring.listen(5, listen_fd, 1, 0);
2446 // Submit 4 operations
2447 try testing.expectEqual(4, try ring.submit());
2448 // Expect all to succeed
2449 for (2..6) |user_data| {
2450 cqe = try ring.copy_cqe();
2451 try testing.expectEqual(user_data, cqe.user_data);
2452 try testing.expectEqual(posix.E.SUCCESS, cqe.err());
2453 }
2454
2455 // Check that socket option is set
2456 optval = 0;
2457 _ = try ring.getsockopt(5, listen_fd, linux.SOL.SOCKET, linux.SO.REUSEADDR, mem.asBytes(&optval));
2458 try testing.expectEqual(1, try ring.submit());
2459 cqe = try ring.copy_cqe();
2460 try testing.expectEqual(5, cqe.user_data);
2461 try testing.expectEqual(posix.E.SUCCESS, cqe.err());
2462 try testing.expectEqual(1, optval);
2463
2464 // Read system assigned port into addr
2465 var addr_len: posix.socklen_t = @sizeOf(linux.sockaddr.in);
2466 try getsockname(listen_fd, addrAny(&addr), &addr_len);
2467
2468 break :brk listen_fd;
2469 };
2470
2471 const connect_fd = brk: {
2472 // Create connect socket
2473 _ = try ring.socket(6, addr.family, linux.SOCK.STREAM | linux.SOCK.CLOEXEC, proto, 0);
2474 try testing.expectEqual(1, try ring.submit());
2475 const cqe = try ring.copy_cqe();
2476 try testing.expectEqual(6, cqe.user_data);
2477 try testing.expectEqual(posix.E.SUCCESS, cqe.err());
2478 // Get connect socket fd
2479 const connect_fd: linux.fd_t = @intCast(cqe.res);
2480 try testing.expect(connect_fd > 2 and connect_fd != listen_fd);
2481 break :brk connect_fd;
2482 };
2483
2484 // Prepare accept/connect operations
2485 _ = try ring.accept(7, listen_fd, null, null, 0);
2486 _ = try ring.connect(8, connect_fd, addrAny(&addr), @sizeOf(linux.sockaddr.in));
2487 try testing.expectEqual(2, try ring.submit());
2488 // Get listener accepted socket
2489 var accept_fd: posix.socket_t = 0;
2490 for (0..2) |_| {
2491 const cqe = try ring.copy_cqe();
2492 try testing.expectEqual(posix.E.SUCCESS, cqe.err());
2493 if (cqe.user_data == 7) {
2494 accept_fd = @intCast(cqe.res);
2495 } else {
2496 try testing.expectEqual(8, cqe.user_data);
2497 }
2498 }
2499 try testing.expect(accept_fd > 2 and accept_fd != listen_fd and accept_fd != connect_fd);
2500
2501 // Communicate
2502 try testSendRecv(&ring, connect_fd, accept_fd);
2503 try testSendRecv(&ring, accept_fd, connect_fd);
2504
2505 // Shutdown and close all sockets
2506 for ([_]posix.socket_t{ connect_fd, accept_fd, listen_fd }) |fd| {
2507 (try ring.shutdown(9, fd, posix.SHUT.RDWR)).link_next();
2508 _ = try ring.close(10, fd);
2509 try testing.expectEqual(2, try ring.submit());
2510 for (0..2) |i| {
2511 const cqe = try ring.copy_cqe();
2512 try testing.expectEqual(posix.E.SUCCESS, cqe.err());
2513 try testing.expectEqual(9 + i, cqe.user_data);
2514 }
2515 }
2516}
2517
2518// Prepare, submit recv and get cqe using buffer group.
2519fn buf_grp_recv_submit_get_cqe(
2520 ring: *IoUring,
2521 buf_grp: *BufferGroup,
2522 fd: linux.fd_t,
2523 user_data: u64,
2524) !linux.io_uring_cqe {
2525 // prepare and submit recv
2526 const sqe = try buf_grp.recv(user_data, fd, 0);
2527 try testing.expect(sqe.flags & linux.IOSQE_BUFFER_SELECT == linux.IOSQE_BUFFER_SELECT);
2528 try testing.expect(sqe.buf_index == buf_grp.group_id);
2529 try testing.expectEqual(@as(u32, 1), try ring.submit()); // submit
2530 // get cqe, expect success
2531 const cqe = try ring.copy_cqe();
2532 try testing.expectEqual(user_data, cqe.user_data);
2533 try testing.expect(cqe.res >= 0); // success
2534 try testing.expectEqual(posix.E.SUCCESS, cqe.err());
2535 try testing.expect(cqe.flags & linux.IORING_CQE_F_BUFFER == linux.IORING_CQE_F_BUFFER); // IORING_CQE_F_BUFFER flag is set
2536
2537 return cqe;
2538}
2539
2540fn expect_buf_grp_cqe(
2541 ring: *IoUring,
2542 buf_grp: *BufferGroup,
2543 user_data: u64,
2544 expected: []const u8,
2545) !linux.io_uring_cqe {
2546 // get cqe
2547 const cqe = try ring.copy_cqe();
2548 try testing.expectEqual(user_data, cqe.user_data);
2549 try testing.expect(cqe.res >= 0); // success
2550 try testing.expect(cqe.flags & linux.IORING_CQE_F_BUFFER == linux.IORING_CQE_F_BUFFER); // IORING_CQE_F_BUFFER flag is set
2551 try testing.expectEqual(expected.len, @as(usize, @intCast(cqe.res)));
2552 try testing.expectEqual(posix.E.SUCCESS, cqe.err());
2553
2554 // get buffer from pool
2555 const buffer_id = try cqe.buffer_id();
2556 const len = @as(usize, @intCast(cqe.res));
2557 const buf = buf_grp.get_by_id(buffer_id)[0..len];
2558 try testing.expectEqualSlices(u8, expected, buf);
2559
2560 return cqe;
2561}
2562
2563fn testSendRecv(ring: *IoUring, send_fd: posix.socket_t, recv_fd: posix.socket_t) !void {
2564 const buffer_send: []const u8 = comptime buf: {
2565 const part = "0123456789abcdf";
2566 const repeated: [10][part.len]u8 = @splat(part.*);
2567 break :buf @ptrCast(&repeated);
2568 };
2569 var buffer_recv: [buffer_send.len * 2]u8 = undefined;
2570
2571 // 2 sends
2572 _ = try ring.send(1, send_fd, buffer_send, linux.MSG.WAITALL);
2573 _ = try ring.send(2, send_fd, buffer_send, linux.MSG.WAITALL);
2574 try testing.expectEqual(2, try ring.submit());
2575 for (0..2) |i| {
2576 const cqe = try ring.copy_cqe();
2577 try testing.expectEqual(1 + i, cqe.user_data);
2578 try testing.expectEqual(posix.E.SUCCESS, cqe.err());
2579 try testing.expectEqual(buffer_send.len, @as(usize, @intCast(cqe.res)));
2580 }
2581
2582 // receive
2583 var recv_len: usize = 0;
2584 while (recv_len < buffer_send.len * 2) {
2585 _ = try ring.recv(3, recv_fd, .{ .buffer = buffer_recv[recv_len..] }, 0);
2586 try testing.expectEqual(1, try ring.submit());
2587 const cqe = try ring.copy_cqe();
2588 try testing.expectEqual(3, cqe.user_data);
2589 try testing.expectEqual(posix.E.SUCCESS, cqe.err());
2590 recv_len += @intCast(cqe.res);
2591 }
2592
2593 // inspect recv buffer
2594 try testing.expectEqualSlices(u8, buffer_send, buffer_recv[0..buffer_send.len]);
2595 try testing.expectEqualSlices(u8, buffer_send, buffer_recv[buffer_send.len..]);
2596}
2597
2598/// Used for testing server/client interactions.
2599pub const SocketTestHarness = struct {
2600 listener: posix.socket_t,
2601 server: posix.socket_t,
2602 client: posix.socket_t,
2603
2604 pub fn close(self: SocketTestHarness) void {
2605 _ = linux.close(self.client);
2606 _ = linux.close(self.listener);
2607 }
2608};
2609
2610pub fn createSocketTestHarness(ring: *IoUring) !SocketTestHarness {
2611 // Create a TCP server socket
2612 var address: linux.sockaddr.in = .{
2613 .port = 0,
2614 .addr = @as(*align(1) const u32, @ptrCast(
2615 &@as([4]u8, .{ 127, 0, 0, 1 }),
2616 )).*,
2617 };
2618 const listener_socket = try createListenerSocket(&address);
2619 errdefer _ = linux.close(listener_socket);
2620
2621 // Submit 1 accept
2622 var accept_addr: posix.sockaddr = undefined;
2623 var accept_addr_len: posix.socklen_t = @sizeOf(@TypeOf(accept_addr));
2624 _ = try ring.accept(0xaaaaaaaa, listener_socket, &accept_addr, &accept_addr_len, 0);
2625
2626 // Create a TCP client socket
2627 const client = try socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
2628 errdefer _ = linux.close(client);
2629 _ = try ring.connect(0xcccccccc, client, addrAny(&address), @sizeOf(linux.sockaddr.in));
2630
2631 try testing.expectEqual(@as(u32, 2), try ring.submit());
2632
2633 var cqe_accept = try ring.copy_cqe();
2634 if (cqe_accept.err() == .INVAL) return error.SkipZigTest;
2635 var cqe_connect = try ring.copy_cqe();
2636 if (cqe_connect.err() == .INVAL) return error.SkipZigTest;
2637
2638 // The accept/connect CQEs may arrive in any order, the connect CQE will sometimes come first:
2639 if (cqe_accept.user_data == 0xcccccccc and cqe_connect.user_data == 0xaaaaaaaa) {
2640 const a = cqe_accept;
2641 const b = cqe_connect;
2642 cqe_accept = b;
2643 cqe_connect = a;
2644 }
2645
2646 try testing.expectEqual(@as(u64, 0xaaaaaaaa), cqe_accept.user_data);
2647 if (cqe_accept.res <= 0) std.debug.print("\ncqe_accept.res={}\n", .{cqe_accept.res});
2648 try testing.expect(cqe_accept.res > 0);
2649 try testing.expectEqual(@as(u32, 0), cqe_accept.flags);
2650 try testing.expectEqual(linux.io_uring_cqe{
2651 .user_data = 0xcccccccc,
2652 .res = 0,
2653 .flags = 0,
2654 }, cqe_connect);
2655
2656 // All good
2657
2658 return .{
2659 .listener = listener_socket,
2660 .server = cqe_accept.res,
2661 .client = client,
2662 };
2663}
2664
2665fn createListenerSocket(address: *linux.sockaddr.in) !posix.socket_t {
2666 const kernel_backlog = 1;
2667 const listener_socket = try socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
2668 errdefer _ = linux.close(listener_socket);
2669
2670 try posix.setsockopt(listener_socket, posix.SOL.SOCKET, posix.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1)));
2671 try bind(listener_socket, addrAny(address), @sizeOf(linux.sockaddr.in));
2672 try listen(listener_socket, kernel_backlog);
2673
2674 // set address to the OS-chosen IP/port.
2675 var slen: posix.socklen_t = @sizeOf(linux.sockaddr.in);
2676 try getsockname(listener_socket, addrAny(address), &slen);
2677
2678 return listener_socket;
2679}
2680
2681fn addrAny(addr: *linux.sockaddr.in) *linux.sockaddr {
2682 return @ptrCast(addr);
2683}
2684
2685fn socket(domain: u32, socket_type: u32, protocol: u32) !posix.socket_t {
2686 const rc = posix.system.socket(domain, socket_type, protocol);
2687 switch (posix.errno(rc)) {
2688 .SUCCESS => return @intCast(rc),
2689 else => return error.SocketCreationFailure,
2690 }
2691}
2692
2693fn bind(sock: posix.socket_t, addr: *const posix.sockaddr, len: posix.socklen_t) !void {
2694 switch (posix.errno(posix.system.bind(sock, addr, len))) {
2695 .SUCCESS => return,
2696 else => return error.BindFailure,
2697 }
2698}
2699
2700fn listen(sock: posix.socket_t, backlog: u31) !void {
2701 switch (posix.errno(posix.system.listen(sock, backlog))) {
2702 .SUCCESS => return,
2703 else => return error.ListenFailure,
2704 }
2705}
2706
2707fn getsockname(sock: posix.socket_t, addr: *posix.sockaddr, addrlen: *posix.socklen_t) !void {
2708 switch (posix.errno(posix.system.getsockname(sock, addr, addrlen))) {
2709 .SUCCESS => return,
2710 else => return error.GetSockNameFailure,
2711 }
2712}
2713
2714fn send(sockfd: posix.socket_t, buf: []const u8, flags: u32) !usize {
2715 const rc = posix.system.sendto(sockfd, buf.ptr, buf.len, flags, null, 0);
2716 switch (posix.errno(rc)) {
2717 .SUCCESS => return @intCast(rc),
2718 else => return error.SendFailed,
2719 }
2720}
2721
2722fn connect(sock: posix.socket_t, sock_addr: *const posix.sockaddr, len: posix.socklen_t) !void {
2723 while (true) switch (posix.errno(posix.system.connect(sock, sock_addr, len))) {
2724 .SUCCESS => return,
2725 .INTR => continue,
2726 else => return error.ConnectFailed,
2727 };
2728}