| ... | @@ -60,7 +60,22 @@ pub const IO_Uring = struct { | ... | @@ -60,7 +60,22 @@ pub const IO_Uring = struct { |
| 60 | assert(p.resv[2] == 0); | 60 | assert(p.resv[2] == 0); |
| 61 | | 61 | |
| 62 | const res = linux.io_uring_setup(entries, p); | 62 | const res = linux.io_uring_setup(entries, p); |
| 63 | try check_errno(res); | 63 | switch (linux.getErrno(res)) { |
| | 64 | 0 => {}, |
| | 65 | linux.EFAULT => return error.ParamsOutsideAccessibleAddressSpace, |
| | 66 | // The resv array contains non-zero data, p.flags contains an unsupported flag, |
| | 67 | // entries out of bounds, IORING_SETUP_SQ_AFF was specified without IORING_SETUP_SQPOLL, |
| | 68 | // or IORING_SETUP_CQSIZE was specified but io_uring_params.cq_entries was invalid: |
| | 69 | linux.EINVAL => return error.ArgumentsInvalid, |
| | 70 | linux.EMFILE => return error.ProcessFdQuotaExceeded, |
| | 71 | linux.ENFILE => return error.SystemFdQuotaExceeded, |
| | 72 | linux.ENOMEM => return error.SystemResources, |
| | 73 | // IORING_SETUP_SQPOLL was specified but effective user ID lacks sufficient privileges, |
| | 74 | // or a container seccomp policy prohibits io_uring syscalls: |
| | 75 | linux.EPERM => return error.PermissionDenied, |
| | 76 | linux.ENOSYS => return error.SystemOutdated, |
| | 77 | else => |errno| return os.unexpectedErrno(errno) |
| | 78 | } |
| 64 | const fd = @intCast(i32, res); | 79 | const fd = @intCast(i32, res); |
| 65 | assert(fd >= 0); | 80 | assert(fd >= 0); |
| 66 | errdefer os.close(fd); | 81 | errdefer os.close(fd); |
| ... | @@ -75,7 +90,7 @@ pub const IO_Uring = struct { | ... | @@ -75,7 +90,7 @@ pub const IO_Uring = struct { |
| 75 | // We do not support the double mmap() done before 5.4, because we want to keep the | 90 | // We do not support the double mmap() done before 5.4, because we want to keep the |
| 76 | // init/deinit mmap paths simple and because io_uring has had many bug fixes even since 5.4. | 91 | // init/deinit mmap paths simple and because io_uring has had many bug fixes even since 5.4. |
| 77 | if ((p.features & linux.IORING_FEAT_SINGLE_MMAP) == 0) { | 92 | if ((p.features & linux.IORING_FEAT_SINGLE_MMAP) == 0) { |
| 78 | return error.UnsupportedKernel; | 93 | return error.SystemOutdated; |
| 79 | } | 94 | } |
| 80 | | 95 | |
| 81 | // Check that the kernel has actually set params and that "impossible is nothing". | 96 | // Check that the kernel has actually set params and that "impossible is nothing". |
| ... | @@ -172,7 +187,31 @@ pub const IO_Uring = struct { | ... | @@ -172,7 +187,31 @@ pub const IO_Uring = struct { |
| 172 | fn enter(self: *IO_Uring, to_submit: u32, min_complete: u32, flags: u32) !u32 { | 187 | fn enter(self: *IO_Uring, to_submit: u32, min_complete: u32, flags: u32) !u32 { |
| 173 | assert(self.fd >= 0); | 188 | assert(self.fd >= 0); |
| 174 | const res = linux.io_uring_enter(self.fd, to_submit, min_complete, flags, null); | 189 | const res = linux.io_uring_enter(self.fd, to_submit, min_complete, flags, null); |
| 175 | try check_errno(res); | 190 | switch (linux.getErrno(res)) { |
| | 191 | 0 => {}, |
| | 192 | // The kernel was unable to allocate memory or ran out of resources for the request. |
| | 193 | // The application should wait for some completions and try again: |
| | 194 | linux.EAGAIN => return error.SystemResources, |
| | 195 | // The application attempted to overcommit the number of requests it can have pending. |
| | 196 | // The application should wait for some completions and try again: |
| | 197 | linux.EBUSY => return error.CompletionQueueOvercommitted, |
| | 198 | // The SQE `fd` is invalid, or IOSQE_FIXED_FILE was set but no files were registered: |
| | 199 | linux.EBADF => return error.FileDescriptorInvalid, |
| | 200 | // The buffer is outside the process' accessible address space, or IORING_OP_READ_FIXED |
| | 201 | // or IORING_OP_WRITE_FIXED was specified but no buffers were registered, or the range |
| | 202 | // described by `addr` and `len` is not within the buffer registered at `buf_index`: |
| | 203 | linux.EFAULT => return error.BufferInvalid, |
| | 204 | // The SQE is invalid, or valid but the ring was setup with IORING_SETUP_IOPOLL: |
| | 205 | linux.EINVAL => return error.SubmissionQueueEntryInvalid, |
| | 206 | linux.ENXIO => return error.RingShuttingDown, |
| | 207 | // The kernel believes our `self.fd` does not refer to an io_uring instance, |
| | 208 | // or the opcode is valid but not supported by this kernel (more likely): |
| | 209 | linux.EOPNOTSUPP => return error.OpcodeNotSupported, |
| | 210 | // The operation was interrupted by a delivery of a signal before it could complete. |
| | 211 | // This can happen while waiting for events with IORING_ENTER_GETEVENTS: |
| | 212 | linux.EINTR => return error.SignalInterrupt, |
| | 213 | else => |errno| return os.unexpectedErrno(errno) |
| | 214 | } |
| 176 | return @truncate(u32, res); | 215 | return @truncate(u32, res); |
| 177 | } | 216 | } |
| 178 | | 217 | |
| ... | @@ -479,7 +518,25 @@ pub const IO_Uring = struct { | ... | @@ -479,7 +518,25 @@ pub const IO_Uring = struct { |
| 479 | @ptrCast(*const c_void, fds.ptr), | 518 | @ptrCast(*const c_void, fds.ptr), |
| 480 | @truncate(u32, fds.len) | 519 | @truncate(u32, fds.len) |
| 481 | ); | 520 | ); |
| 482 | try check_errno(res); | 521 | switch (linux.getErrno(res)) { |
| | 522 | 0 => {}, |
| | 523 | // One or more fds in the array are invalid, or the kernel does not support sparse sets: |
| | 524 | linux.EBADF => return error.FileDescriptorInvalid, |
| | 525 | linux.EBUSY => return error.FilesAlreadyRegistered, |
| | 526 | linux.EINVAL => return error.FilesEmpty, |
| | 527 | // Adding `nr_args` file references would exceed the maximum allowed number of files the |
| | 528 | // user is allowed to have according to the per-user RLIMIT_NOFILE resource limit and |
| | 529 | // the CAP_SYS_RESOURCE capability is not set, or `nr_args` exceeds the maximum allowed |
| | 530 | // for a fixed file set (older kernels have a limit of 1024 files vs 64K files): |
| | 531 | linux.EMFILE => return error.UserFdQuotaExceeded, |
| | 532 | // Insufficient kernel resources, or the caller had a non-zero RLIMIT_MEMLOCK soft |
| | 533 | // resource limit but tried to lock more memory than the limit permitted (not enforced |
| | 534 | // when the process is privileged with CAP_IPC_LOCK): |
| | 535 | linux.ENOMEM => return error.SystemResources, |
| | 536 | // Attempt to register files on a ring already registering files or being torn down: |
| | 537 | linux.ENXIO => return error.RingShuttingDownOrAlreadyRegisteringFiles, |
| | 538 | else => |errno| return os.unexpectedErrno(errno) |
| | 539 | } |
| 483 | } | 540 | } |
| 484 | | 541 | |
| 485 | /// Changes the semantics of the SQE's `fd` to refer to a pre-registered file descriptor. | 542 | /// Changes the semantics of the SQE's `fd` to refer to a pre-registered file descriptor. |
| ... | @@ -491,7 +548,11 @@ pub const IO_Uring = struct { | ... | @@ -491,7 +548,11 @@ pub const IO_Uring = struct { |
| 491 | pub fn unregister_files(self: *IO_Uring) !void { | 548 | pub fn unregister_files(self: *IO_Uring) !void { |
| 492 | assert(self.fd >= 0); | 549 | assert(self.fd >= 0); |
| 493 | const res = linux.io_uring_register(self.fd, .UNREGISTER_FILES, null, 0); | 550 | const res = linux.io_uring_register(self.fd, .UNREGISTER_FILES, null, 0); |
| 494 | try check_errno(res); | 551 | switch (linux.getErrno(res)) { |
| | 552 | 0 => {}, |
| | 553 | linux.ENXIO => return error.FilesNotRegistered, |
| | 554 | else => |errno| return os.unexpectedErrno(errno) |
| | 555 | } |
| 495 | } | 556 | } |
| 496 | }; | 557 | }; |
| 497 | | 558 | |
| ... | @@ -607,20 +668,13 @@ pub const CompletionQueue = struct { | ... | @@ -607,20 +668,13 @@ pub const CompletionQueue = struct { |
| 607 | } | 668 | } |
| 608 | }; | 669 | }; |
| 609 | | 670 | |
| 610 | inline fn check_errno(res: usize) !void { | | |
| 611 | switch (linux.getErrno(res)) { | | |
| 612 | 0 => return, | | |
| 613 | linux.ENOSYS => return error.UnsupportedKernel, | | |
| 614 | else => |errno| return os.unexpectedErrno(errno) | | |
| 615 | } | | |
| 616 | } | | |
| 617 | | | |
| 618 | test "queue_nop" { | 671 | test "queue_nop" { |
| 619 | if (builtin.os.tag != .linux) return error.SkipZigTest; | 672 | if (builtin.os.tag != .linux) return error.SkipZigTest; |
| 620 | | 673 | |
| 621 | var ring = IO_Uring.init(1, 0) catch |err| { | 674 | var ring = IO_Uring.init(1, 0) catch |err| switch (err) { |
| 622 | if (err == error.UnsupportedKernel) return error.SkipZigTest; | 675 | error.SystemOutdated => return error.SkipZigTest, |
| 623 | return err; | 676 | error.PermissionDenied => return error.SkipZigTest, |
| | 677 | else => return err |
| 624 | }; | 678 | }; |
| 625 | defer { | 679 | defer { |
| 626 | ring.deinit(); | 680 | ring.deinit(); |
| ... | @@ -684,9 +738,10 @@ test "queue_nop" { | ... | @@ -684,9 +738,10 @@ test "queue_nop" { |
| 684 | test "queue_readv" { | 738 | test "queue_readv" { |
| 685 | if (builtin.os.tag != .linux) return error.SkipZigTest; | 739 | if (builtin.os.tag != .linux) return error.SkipZigTest; |
| 686 | | 740 | |
| 687 | var ring = IO_Uring.init(1, 0) catch |err| { | 741 | var ring = IO_Uring.init(1, 0) catch |err| switch (err) { |
| 688 | if (err == error.UnsupportedKernel) return error.SkipZigTest; | 742 | error.SystemOutdated => return error.SkipZigTest, |
| 689 | return err; | 743 | error.PermissionDenied => return error.SkipZigTest, |
| | 744 | else => return err |
| 690 | }; | 745 | }; |
| 691 | defer ring.deinit(); | 746 | defer ring.deinit(); |
| 692 | | 747 | |
| ... | @@ -725,9 +780,10 @@ test "queue_readv" { | ... | @@ -725,9 +780,10 @@ test "queue_readv" { |
| 725 | test "queue_writev/queue_fsync" { | 780 | test "queue_writev/queue_fsync" { |
| 726 | if (builtin.os.tag != .linux) return error.SkipZigTest; | 781 | if (builtin.os.tag != .linux) return error.SkipZigTest; |
| 727 | | 782 | |
| 728 | var ring = IO_Uring.init(2, 0) catch |err| { | 783 | var ring = IO_Uring.init(2, 0) catch |err| switch (err) { |
| 729 | if (err == error.UnsupportedKernel) return error.SkipZigTest; | 784 | error.SystemOutdated => return error.SkipZigTest, |
| 730 | return err; | 785 | error.PermissionDenied => return error.SkipZigTest, |
| | 786 | else => return err |
| 731 | }; | 787 | }; |
| 732 | defer ring.deinit(); | 788 | defer ring.deinit(); |
| 733 | | 789 | |
| ... | @@ -769,9 +825,10 @@ test "queue_writev/queue_fsync" { | ... | @@ -769,9 +825,10 @@ test "queue_writev/queue_fsync" { |
| 769 | test "queue_write/queue_read" { | 825 | test "queue_write/queue_read" { |
| 770 | if (builtin.os.tag != .linux) return error.SkipZigTest; | 826 | if (builtin.os.tag != .linux) return error.SkipZigTest; |
| 771 | | 827 | |
| 772 | var ring = IO_Uring.init(2, 0) catch |err| { | 828 | var ring = IO_Uring.init(2, 0) catch |err| switch (err) { |
| 773 | if (err == error.UnsupportedKernel) return error.SkipZigTest; | 829 | error.SystemOutdated => return error.SkipZigTest, |
| 774 | return err; | 830 | error.PermissionDenied => return error.SkipZigTest, |
| | 831 | else => return err |
| 775 | }; | 832 | }; |
| 776 | defer ring.deinit(); | 833 | defer ring.deinit(); |
| 777 | | 834 | |