| ... | ... | @@ -60,7 +60,22 @@ pub const IO_Uring = struct { |
| 60 | 60 | assert(p.resv[2] == 0); |
| 61 | 61 | |
| 62 | 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 | 79 | const fd = @intCast(i32, res); |
| 65 | 80 | assert(fd >= 0); |
| 66 | 81 | errdefer os.close(fd); |
| ... | ... | @@ -75,7 +90,7 @@ pub const IO_Uring = struct { |
| 75 | 90 | // We do not support the double mmap() done before 5.4, because we want to keep the |
| 76 | 91 | // init/deinit mmap paths simple and because io_uring has had many bug fixes even since 5.4. |
| 77 | 92 | if ((p.features & linux.IORING_FEAT_SINGLE_MMAP) == 0) { |
| 78 | | return error.UnsupportedKernel; |
| 93 | return error.SystemOutdated; |
| 79 | 94 | } |
| 80 | 95 | |
| 81 | 96 | // Check that the kernel has actually set params and that "impossible is nothing". |
| ... | ... | @@ -172,7 +187,31 @@ pub const IO_Uring = struct { |
| 172 | 187 | fn enter(self: *IO_Uring, to_submit: u32, min_complete: u32, flags: u32) !u32 { |
| 173 | 188 | assert(self.fd >= 0); |
| 174 | 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 | 215 | return @truncate(u32, res); |
| 177 | 216 | } |
| 178 | 217 | |
| ... | ... | @@ -479,7 +518,25 @@ pub const IO_Uring = struct { |
| 479 | 518 | @ptrCast(*const c_void, fds.ptr), |
| 480 | 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 | 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 | 548 | pub fn unregister_files(self: *IO_Uring) !void { |
| 492 | 549 | assert(self.fd >= 0); |
| 493 | 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 | 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 | 671 | test "queue_nop" { |
| 619 | 672 | if (builtin.os.tag != .linux) return error.SkipZigTest; |
| 620 | 673 | |
| 621 | | var ring = IO_Uring.init(1, 0) catch |err| { |
| 622 | | if (err == error.UnsupportedKernel) return error.SkipZigTest; |
| 623 | | return err; |
| 674 | var ring = IO_Uring.init(1, 0) catch |err| switch (err) { |
| 675 | error.SystemOutdated => return error.SkipZigTest, |
| 676 | error.PermissionDenied => return error.SkipZigTest, |
| 677 | else => return err |
| 624 | 678 | }; |
| 625 | 679 | defer { |
| 626 | 680 | ring.deinit(); |
| ... | ... | @@ -684,9 +738,10 @@ test "queue_nop" { |
| 684 | 738 | test "queue_readv" { |
| 685 | 739 | if (builtin.os.tag != .linux) return error.SkipZigTest; |
| 686 | 740 | |
| 687 | | var ring = IO_Uring.init(1, 0) catch |err| { |
| 688 | | if (err == error.UnsupportedKernel) return error.SkipZigTest; |
| 689 | | return err; |
| 741 | var ring = IO_Uring.init(1, 0) catch |err| switch (err) { |
| 742 | error.SystemOutdated => return error.SkipZigTest, |
| 743 | error.PermissionDenied => return error.SkipZigTest, |
| 744 | else => return err |
| 690 | 745 | }; |
| 691 | 746 | defer ring.deinit(); |
| 692 | 747 | |
| ... | ... | @@ -725,9 +780,10 @@ test "queue_readv" { |
| 725 | 780 | test "queue_writev/queue_fsync" { |
| 726 | 781 | if (builtin.os.tag != .linux) return error.SkipZigTest; |
| 727 | 782 | |
| 728 | | var ring = IO_Uring.init(2, 0) catch |err| { |
| 729 | | if (err == error.UnsupportedKernel) return error.SkipZigTest; |
| 730 | | return err; |
| 783 | var ring = IO_Uring.init(2, 0) catch |err| switch (err) { |
| 784 | error.SystemOutdated => return error.SkipZigTest, |
| 785 | error.PermissionDenied => return error.SkipZigTest, |
| 786 | else => return err |
| 731 | 787 | }; |
| 732 | 788 | defer ring.deinit(); |
| 733 | 789 | |
| ... | ... | @@ -769,9 +825,10 @@ test "queue_writev/queue_fsync" { |
| 769 | 825 | test "queue_write/queue_read" { |
| 770 | 826 | if (builtin.os.tag != .linux) return error.SkipZigTest; |
| 771 | 827 | |
| 772 | | var ring = IO_Uring.init(2, 0) catch |err| { |
| 773 | | if (err == error.UnsupportedKernel) return error.SkipZigTest; |
| 774 | | return err; |
| 828 | var ring = IO_Uring.init(2, 0) catch |err| switch (err) { |
| 829 | error.SystemOutdated => return error.SkipZigTest, |
| 830 | error.PermissionDenied => return error.SkipZigTest, |
| 831 | else => return err |
| 775 | 832 | }; |
| 776 | 833 | defer ring.deinit(); |
| 777 | 834 | |