diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7c10f2df..15b16364 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,7 +1,7 @@ on: [push, pull_request] name: Test env: - ZIG_VERSION: 0.15.2 + ZIG_VERSION: 0.16.0 jobs: build: diff --git a/README.md b/README.md index a32b351a..82d2eed3 100644 --- a/README.md +++ b/README.md @@ -98,6 +98,25 @@ major event loops. This may differ on a feature-by-feature basis, and if you can show really poor performance in an issue I'm interested in resolving it! +### Integration with Zig 0.16+ std.Io + +Libxev doesn't implement the `std.Io` interface and doesn't take +a `std.Io` for any of its operations. It calls IO directly using system +calls and in the few rare cases it must use a `std.Io` (such as for mutex +operations), libxev uses the global `std.Io.Threaded` implementation. + +The reason for this is because libxev is a very old library that +predates `std.Io`, so it bakes in a lot of assumptions that don't really +fit into the new `std.Io` model. To support this, we'll have to break +our API significantly. + +Additionally, the `std.Io` interface is still very new and unstable and +doesn't expose all the operations necessary to bring parity with libxev. + +We will investigate better, more idiomatic integrations with `std.Io` +in the future. For now, libxev continues to work Zig 0.16 but mostly as +it did in prior Zig versions and doesn't integrate with `std.Io`. + ## Example The example below shows an identical program written in Zig and in C @@ -272,7 +291,7 @@ directory. # Build -Build requires the installation of the Zig 0.15.1. libxev follows stable +Build requires the installation of the Zig 0.16. libxev follows stable Zig releases and generally does not support nightly builds. When a stable release is imminent we may have a branch that supports it. **libxev has no other build dependencies.** diff --git a/build.zig b/build.zig index c1304e69..ec2da420 100644 --- a/build.zig +++ b/build.zig @@ -26,7 +26,6 @@ pub fn build(b: *std.Build) !void { true else |err| switch (err) { error.FileNotFound => false, - else => return err, }; const emit_bench = b.option( @@ -41,12 +40,6 @@ pub fn build(b: *std.Build) !void { "Install the example binaries to zig-out", ) orelse false; - const c_api_module = b.createModule(.{ - .root_source_file = b.path("src/c_api.zig"), - .target = target, - .optimize = optimize, - }); - // Static C lib const static_lib: ?*Step.Compile = lib: { if (target.result.os.tag == .wasi) break :lib null; @@ -54,12 +47,16 @@ pub fn build(b: *std.Build) !void { const static_lib = b.addLibrary(.{ .linkage = .static, .name = "xev", - .root_module = c_api_module, + .root_module = b.createModule(.{ + .root_source_file = b.path("src/c_api.zig"), + .target = target, + .optimize = optimize, + .link_libc = true, + }), }); - static_lib.linkLibC(); if (target.result.os.tag == .windows) { - static_lib.linkSystemLibrary("ws2_32"); - static_lib.linkSystemLibrary("mswsock"); + static_lib.root_module.linkSystemLibrary("ws2_32", .{}); + static_lib.root_module.linkSystemLibrary("mswsock", .{}); } break :lib static_lib; }; @@ -72,7 +69,11 @@ pub fn build(b: *std.Build) !void { const dynamic_lib = b.addLibrary(.{ .linkage = .dynamic, .name = "xev", - .root_module = c_api_module, + .root_module = b.createModule(.{ + .root_source_file = b.path("src/c_api.zig"), + .target = target, + .optimize = optimize, + }), }); break :lib dynamic_lib; }; @@ -119,20 +120,19 @@ pub fn build(b: *std.Build) !void { "test-filter", "Filter for test", ); - const test_exe = b.addTest(.{ + break :test_exe b.addTest(.{ .name = "xev-test", .filters = if (test_filter) |filter| &.{filter} else &.{}, .root_module = b.createModule(.{ .root_source_file = b.path("src/main.zig"), .target = target, .optimize = optimize, + .link_libc = switch (target.result.os.tag) { + .linux, .macos => true, + else => null, + }, }), }); - switch (target.result.os.tag) { - .linux, .macos => test_exe.linkLibC(), - else => {}, - } - break :test_exe test_exe; }; // "test" Step @@ -172,19 +172,24 @@ fn buildBenchmarks( b: *std.Build, target: std.Build.ResolvedTarget, ) ![]const *Step.Compile { + const io = b.graph.io; const alloc = b.allocator; var steps: std.ArrayList(*Step.Compile) = .empty; defer steps.deinit(alloc); - var dir = try std.fs.cwd().openDir(try b.build_root.join( - b.allocator, - &.{ "src", "bench" }, - ), .{ .iterate = true }); - defer dir.close(); + var dir = try std.Io.Dir.cwd().openDir( + io, + try b.build_root.join( + b.allocator, + &.{ "src", "bench" }, + ), + .{ .iterate = true }, + ); + defer dir.close(io); // Go through and add each as a step var it = dir.iterate(); - while (try it.next()) |entry| { + while (try it.next(io)) |entry| { // Get the index of the last '.' so we can strip the extension. const index = std.mem.lastIndexOfScalar( u8, @@ -223,19 +228,24 @@ fn buildExamples( optimize: std.builtin.OptimizeMode, c_lib_: ?*Step.Compile, ) ![]const *Step.Compile { + const io = b.graph.io; const alloc = b.allocator; var steps: std.ArrayList(*Step.Compile) = .empty; defer steps.deinit(alloc); - var dir = try std.fs.cwd().openDir(try b.build_root.join( - b.allocator, - &.{"examples"}, - ), .{ .iterate = true }); - defer dir.close(); + var dir = try std.Io.Dir.cwd().openDir( + io, + try b.build_root.join( + b.allocator, + &.{"examples"}, + ), + .{ .iterate = true }, + ); + defer dir.close(io); // Go through and add each as a step var it = dir.iterate(); - while (try it.next()) |entry| { + while (try it.next(io)) |entry| { // Get the index of the last '.' so we can strip the extension. const index = std.mem.lastIndexOfScalar( u8, @@ -269,11 +279,11 @@ fn buildExamples( .root_module = b.createModule(.{ .target = target, .optimize = optimize, + .link_libc = true, }), }); - exe.linkLibC(); - exe.addIncludePath(b.path("include")); - exe.addCSourceFile(.{ + exe.root_module.addIncludePath(b.path("include")); + exe.root_module.addCSourceFile(.{ .file = b.path(b.fmt( "examples/{s}", .{entry.name}, @@ -286,7 +296,7 @@ fn buildExamples( "-D_POSIX_C_SOURCE=199309L", }, }); - exe.linkLibrary(c_lib); + exe.root_module.linkLibrary(c_lib); break :exe exe; }; @@ -298,18 +308,20 @@ fn buildExamples( } fn manPages(b: *std.Build) ![]const *Step { + const io = b.graph.io; const alloc = b.allocator; var steps: std.ArrayList(*Step) = .empty; defer steps.deinit(alloc); - var dir = try std.fs.cwd().openDir(try b.build_root.join( - b.allocator, - &.{"docs"}, - ), .{ .iterate = true }); - defer dir.close(); + var dir = try std.Io.Dir.cwd().openDir( + io, + try b.build_root.join(b.allocator, &.{"docs"}), + .{ .iterate = true }, + ); + defer dir.close(io); var it = dir.iterate(); - while (try it.next()) |*entry| { + while (try it.next(io)) |*entry| { // Filenames must end in "{section}.scd" and sections are // single numerals. const base = entry.name[0 .. entry.name.len - 4]; @@ -321,7 +333,7 @@ fn manPages(b: *std.Build) ![]const *Step { ) }); try steps.append(alloc, &b.addInstallFile( - cmd.captureStdOut(), + cmd.captureStdOut(.{}), b.fmt("share/man/man{s}/{s}", .{ section, base }), ).step); } diff --git a/build.zig.zon b/build.zig.zon index d95bd266..cf53cc88 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -1,6 +1,6 @@ .{ .name = .libxev, - .minimum_zig_version = "0.15.1", + .minimum_zig_version = "0.16.0", .version = "0.0.0", .fingerprint = 0x30f7363573edabf3, .paths = .{""}, diff --git a/examples/_basic.zig b/examples/_basic.zig index b736e6f3..2398ac44 100644 --- a/examples/_basic.zig +++ b/examples/_basic.zig @@ -1,5 +1,4 @@ const std = @import("std"); -const Instant = std.time.Instant; const xev = @import("xev"); pub fn main() !void { diff --git a/flake.lock b/flake.lock index efb9afd2..31c54065 100644 --- a/flake.lock +++ b/flake.lock @@ -47,24 +47,6 @@ "type": "github" } }, - "flake-utils_2": { - "inputs": { - "systems": "systems" - }, - "locked": { - "lastModified": 1705309234, - "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, "nixpkgs": { "locked": { "lastModified": 1759205709, @@ -80,16 +62,16 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1708161998, - "narHash": "sha256-6KnemmUorCvlcAvGziFosAVkrlWZGIc6UNT9GUYr0jQ=", + "lastModified": 1771043024, + "narHash": "sha256-O1XDr7EWbRp+kHrNNgLWgIrB0/US5wvw9K6RERWAj6I=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "84d981bae8b5e783b3b548de505b22880559515f", + "rev": "3aadb7ca9eac2891d52a9dec199d9580a6e2bf44", "type": "github" }, "original": { "owner": "NixOS", - "ref": "nixos-23.11", + "ref": "nixos-25.11", "repo": "nixpkgs", "type": "github" } @@ -103,6 +85,7 @@ } }, "systems": { + "flake": false, "locked": { "lastModified": 1681028828, "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", @@ -120,15 +103,15 @@ "zig": { "inputs": { "flake-compat": "flake-compat_2", - "flake-utils": "flake-utils_2", - "nixpkgs": "nixpkgs_2" + "nixpkgs": "nixpkgs_2", + "systems": "systems" }, "locked": { - "lastModified": 1760401936, - "narHash": "sha256-/zj5GYO5PKhBWGzbHbqT+ehY8EghuABdQ2WGfCwZpCQ=", + "lastModified": 1776398705, + "narHash": "sha256-/RYhj2fmbFWjfNORGjI18rJzLICNahxPSy0epCdyVRk=", "owner": "mitchellh", "repo": "zig-overlay", - "rev": "365085b6652259753b598d43b723858184980bbe", + "rev": "e2956eb5a19f92fef466f1af78f1fb8c207767af", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 124fc1a5..c5eb1367 100644 --- a/flake.nix +++ b/flake.nix @@ -23,7 +23,7 @@ # Other overlays (final: prev: rec { zigpkgs = inputs.zig.packages.${prev.system}; - zig = inputs.zig.packages.${prev.system}."0.15.2"; + zig = inputs.zig.packages.${prev.system}."0.16.0"; # Our package libxev = prev.callPackage ./nix/package.nix {}; diff --git a/src/ThreadPool.zig b/src/ThreadPool.zig index 8f601e14..ce7e7406 100644 --- a/src/ThreadPool.zig +++ b/src/ThreadPool.zig @@ -39,6 +39,12 @@ const ThreadPool = @This(); const std = @import("std"); const assert = std.debug.assert; const Atomic = std.atomic.Value; +const Io = std.Io; + +// I know we shouldn't use a global io here, but we are doing this +// during the transition period so libxev is at least usable with Zig +// 0.16 until we do a cleaner transition. +const io = Io.Threaded.global_single_threaded.io(); stack_size: u32, max_threads: u32, @@ -487,7 +493,7 @@ const Event = struct { // Acquiring to WAITING will make the next notify() or shutdown() wake a sleeping futex thread // who will either exit on SHUTDOWN or acquire with WAITING again, ensuring all threads are awoken. // This unfortunately results in the last notify() or shutdown() doing an extra futex wake but that's fine. - std.Thread.Futex.wait(&self.state, WAITING); + io.futexWaitUncancelable(u32, &self.state.raw, WAITING); state = self.state.load(.monotonic); acquire_with = WAITING; } @@ -513,7 +519,7 @@ const Event = struct { // Only wake threads sleeping in futex if the state is WAITING. // Avoids unnecessary wake ups. if (state == WAITING) { - std.Thread.Futex.wake(&self.state, wake_threads); + io.futexWake(u32, &self.state.raw, wake_threads); } } }; diff --git a/src/backend/epoll.zig b/src/backend/epoll.zig index d55d2506..25c0a931 100644 --- a/src/backend/epoll.zig +++ b/src/backend/epoll.zig @@ -3,12 +3,62 @@ const builtin = @import("builtin"); const assert = std.debug.assert; const linux = std.os.linux; const posix = std.posix; +const xev_posix = @import("../posix.zig"); +const net = xev_posix.net; const queue = @import("../queue.zig"); const queue_mpsc = @import("../queue_mpsc.zig"); const heap = @import("../heap.zig"); const ThreadPool = @import("../ThreadPool.zig"); const Async = @import("../main.zig").Epoll.Async; +/// In Zig 0.16, `std.posix.epoll_create1`, `epoll_ctl`, and `epoll_wait` +/// were removed. These helpers replicate the old wrappers using the raw +/// Linux syscall layer (`std.os.linux`). +const epoll_helper = struct { + const EpollCreateError = error{ + ProcessFdQuotaExceeded, + SystemFdQuotaExceeded, + SystemResources, + Unexpected, + }; + + fn epoll_create1(flags: u32) EpollCreateError!i32 { + const rc = linux.epoll_create1(flags); + switch (linux.errno(rc)) { + .SUCCESS => return @intCast(rc), + .MFILE => return error.ProcessFdQuotaExceeded, + .NFILE => return error.SystemFdQuotaExceeded, + .NOMEM => return error.SystemResources, + else => return error.Unexpected, + } + } + + fn epoll_ctl(epfd: i32, op: u32, fd: i32, event: ?*linux.epoll_event) EpollCtlError!void { + const rc = linux.epoll_ctl(epfd, op, fd, event); + switch (linux.errno(rc)) { + .SUCCESS => return, + .EXIST => return error.FileDescriptorAlreadyPresentInSet, + .LOOP => return error.OperationCausesCircularLoop, + .NOENT => return error.FileDescriptorNotRegistered, + .NOMEM => return error.SystemResources, + .NOSPC => return error.UserResourceLimitReached, + .PERM => return error.FileDescriptorIncompatibleWithEpoll, + else => return error.Unexpected, + } + } + + fn epoll_wait(epfd: i32, events: []linux.epoll_event, timeout: i32) usize { + while (true) { + const rc = linux.epoll_wait(epfd, events.ptr, @intCast(events.len), timeout); + switch (linux.errno(rc)) { + .SUCCESS => return @intCast(rc), + .INTR => continue, + else => unreachable, + } + } + } +}; + const looppkg = @import("../loop.zig"); const Options = looppkg.Options; const RunMode = looppkg.RunMode; @@ -79,7 +129,7 @@ pub const Loop = struct { errdefer eventfd.deinit(); var res: Loop = .{ - .fd = try posix.epoll_create1(std.os.linux.EPOLL.CLOEXEC), + .fd = try epoll_helper.epoll_create1(std.os.linux.EPOLL.CLOEXEC), .eventfd = eventfd, .thread_pool = options.thread_pool, .thread_pool_completions = undefined, @@ -90,7 +140,7 @@ pub const Loop = struct { } pub fn deinit(self: *Loop) void { - posix.close(self.fd); + xev_posix.close(self.fd); self.eventfd.deinit(); } @@ -187,10 +237,10 @@ pub const Loop = struct { /// Update the cached time. pub fn update_now(self: *Loop) void { - if (posix.clock_gettime(posix.CLOCK.MONOTONIC)) |new_time| { - self.cached_now = new_time; - } else |_| { - // Errors are ignored. + var ts: std.os.linux.timespec = undefined; + const rc = std.os.linux.clock_gettime(std.os.linux.CLOCK.MONOTONIC, &ts); + if (linux.errno(rc) == .SUCCESS) { + self.cached_now = ts; } } @@ -305,7 +355,7 @@ pub const Loop = struct { .events = linux.EPOLL.IN | linux.EPOLL.RDHUP, .data = .{ .fd = self.eventfd.fd }, }; - posix.epoll_ctl( + epoll_helper.epoll_ctl( self.fd, linux.EPOLL.CTL_ADD, self.eventfd.fd, @@ -421,20 +471,14 @@ pub const Loop = struct { break :timeout @as(i32, @intCast(ms_next -| ms_now)); }; - const n = posix.epoll_wait(self.fd, &events, timeout); - if (n < 0) { - switch (posix.errno(n)) { - .INTR => continue, - else => |err| return posix.unexpectedErrno(err), - } - } + const n = epoll_helper.epoll_wait(self.fd, &events, timeout); // Process all our events and invoke their completion handlers for (events[0..n]) |ev| { // Handle wakeup eventfd if (ev.data.fd == self.eventfd.fd) { var buffer: u64 = undefined; - _ = posix.read(self.eventfd.fd, std.mem.asBytes(&buffer)) catch {}; + _ = xev_posix.read(self.eventfd.fd, std.mem.asBytes(&buffer)) catch {}; continue; } @@ -453,7 +497,7 @@ pub const Loop = struct { // We can't use self.stop because we can't trust // that c is still a valid pointer. if (fd) |v| { - posix.epoll_ctl( + epoll_helper.epoll_ctl( self.fd, linux.EPOLL.CTL_DEL, v, @@ -461,7 +505,7 @@ pub const Loop = struct { ) catch unreachable; if (close_dup) { - posix.close(v); + xev_posix.close(v); } } @@ -547,7 +591,7 @@ pub const Loop = struct { }; const fd = completion.fd_maybe_dup() catch |err| break :res .{ .accept = err }; - break :res if (posix.epoll_ctl( + break :res if (epoll_helper.epoll_ctl( self.fd, linux.EPOLL.CTL_ADD, fd, @@ -558,7 +602,7 @@ pub const Loop = struct { .connect => |*v| res: { const fd = completion.fd_maybe_dup() catch |err| break :res .{ .connect = err }; - if (posix.connect(fd, &v.addr.any, v.addr.getOsSockLen())) { + if (xev_posix.connect(fd, &v.addr.any, v.addr.getOsSockLen())) { break :res .{ .connect = {} }; } else |err| switch (err) { // If we would block then we register with epoll @@ -575,7 +619,7 @@ pub const Loop = struct { .data = .{ .ptr = @intFromPtr(completion) }, }; - break :res if (posix.epoll_ctl( + break :res if (epoll_helper.epoll_ctl( self.fd, linux.EPOLL.CTL_ADD, fd, @@ -597,7 +641,7 @@ pub const Loop = struct { }; const fd = completion.fd_maybe_dup() catch |err| break :res .{ .read = err }; - break :res if (posix.epoll_ctl( + break :res if (epoll_helper.epoll_ctl( self.fd, linux.EPOLL.CTL_ADD, fd, @@ -619,7 +663,7 @@ pub const Loop = struct { }; const fd = completion.fd_maybe_dup() catch |err| break :res .{ .read = err }; - break :res if (posix.epoll_ctl( + break :res if (epoll_helper.epoll_ctl( self.fd, linux.EPOLL.CTL_ADD, fd, @@ -641,7 +685,7 @@ pub const Loop = struct { }; const fd = completion.fd_maybe_dup() catch |err| break :res .{ .write = err }; - break :res if (posix.epoll_ctl( + break :res if (epoll_helper.epoll_ctl( self.fd, linux.EPOLL.CTL_ADD, fd, @@ -663,7 +707,7 @@ pub const Loop = struct { }; const fd = completion.fd_maybe_dup() catch |err| break :res .{ .write = err }; - break :res if (posix.epoll_ctl( + break :res if (epoll_helper.epoll_ctl( self.fd, linux.EPOLL.CTL_ADD, fd, @@ -678,7 +722,7 @@ pub const Loop = struct { }; const fd = completion.fd_maybe_dup() catch |err| break :res .{ .send = err }; - break :res if (posix.epoll_ctl( + break :res if (epoll_helper.epoll_ctl( self.fd, linux.EPOLL.CTL_ADD, fd, @@ -693,7 +737,7 @@ pub const Loop = struct { }; const fd = completion.fd_maybe_dup() catch |err| break :res .{ .recv = err }; - break :res if (posix.epoll_ctl( + break :res if (epoll_helper.epoll_ctl( self.fd, linux.EPOLL.CTL_ADD, fd, @@ -712,7 +756,7 @@ pub const Loop = struct { }; const fd = completion.fd_maybe_dup() catch |err| break :res .{ .sendmsg = err }; - break :res if (posix.epoll_ctl( + break :res if (epoll_helper.epoll_ctl( self.fd, linux.EPOLL.CTL_ADD, fd, @@ -727,7 +771,7 @@ pub const Loop = struct { }; const fd = completion.fd_maybe_dup() catch |err| break :res .{ .recvmsg = err }; - break :res if (posix.epoll_ctl( + break :res if (epoll_helper.epoll_ctl( self.fd, linux.EPOLL.CTL_ADD, fd, @@ -743,12 +787,23 @@ pub const Loop = struct { break :res .{ .close = err }; } - posix.close(v.fd); + xev_posix.close(v.fd); break :res .{ .close = {} }; }, .shutdown => |v| res: { - break :res .{ .shutdown = posix.shutdown(v.socket, v.how) }; + const how_int: i32 = switch (v.how) { + .recv => linux.SHUT.RD, + .send => linux.SHUT.WR, + .both => linux.SHUT.RDWR, + }; + const rc = linux.shutdown(v.socket, how_int); + break :res .{ .shutdown = switch (linux.errno(rc)) { + .SUCCESS => {}, + .NOTCONN => error.SocketNotConnected, + .NOBUFS => error.SystemResources, + else => |err| posix.unexpectedErrno(err), + } }; }, .timer => |*v| res: { @@ -770,7 +825,7 @@ pub const Loop = struct { }; const fd = completion.fd_maybe_dup() catch |err| break :res .{ .poll = err }; - break :res if (posix.epoll_ctl( + break :res if (epoll_helper.epoll_ctl( self.fd, linux.EPOLL.CTL_ADD, fd, @@ -816,7 +871,7 @@ pub const Loop = struct { // Delete. This should never fail. const maybe_fd = if (completion.flags.dup) completion.flags.dup_fd else completion.fd(); if (maybe_fd) |fd| { - posix.epoll_ctl( + epoll_helper.epoll_ctl( self.fd, linux.EPOLL.CTL_DEL, fd, @@ -958,7 +1013,7 @@ pub const Completion = struct { => unreachable, .accept => |*op| .{ - .accept = if (posix.accept( + .accept = if (xev_posix.accept( op.socket, &op.addr, &op.addr_size, @@ -970,15 +1025,15 @@ pub const Completion = struct { }, .connect => |*op| .{ - .connect = if (posix.getsockoptError(op.socket)) {} else |err| err, + .connect = if (xev_posix.getsockoptError(op.socket)) {} else |err| err, }, .poll => .{ .poll = {} }, .read => |*op| res: { const n_ = switch (op.buffer) { - .slice => |v| posix.read(op.fd, v), - .array => |*v| posix.read(op.fd, v), + .slice => |v| xev_posix.read(op.fd, v), + .array => |*v| xev_posix.read(op.fd, v), }; break :res .{ @@ -991,8 +1046,8 @@ pub const Completion = struct { .pread => |*op| res: { const n_ = switch (op.buffer) { - .slice => |v| posix.pread(op.fd, v, op.offset), - .array => |*v| posix.pread(op.fd, v, op.offset), + .slice => |v| xev_posix.pread(op.fd, v, op.offset), + .array => |*v| xev_posix.pread(op.fd, v, op.offset), }; break :res .{ @@ -1005,27 +1060,27 @@ pub const Completion = struct { .write => |*op| .{ .write = switch (op.buffer) { - .slice => |v| posix.write(op.fd, v), - .array => |*v| posix.write(op.fd, v.array[0..v.len]), + .slice => |v| xev_posix.write(op.fd, v), + .array => |*v| xev_posix.write(op.fd, v.array[0..v.len]), }, }, .pwrite => |*op| .{ .pwrite = switch (op.buffer) { - .slice => |v| posix.pwrite(op.fd, v, op.offset), - .array => |*v| posix.pwrite(op.fd, v.array[0..v.len], op.offset), + .slice => |v| xev_posix.pwrite(op.fd, v, op.offset), + .array => |*v| xev_posix.pwrite(op.fd, v.array[0..v.len], op.offset), }, }, .send => |*op| .{ .send = switch (op.buffer) { - .slice => |v| posix.send(op.fd, v, 0), - .array => |*v| posix.send(op.fd, v.array[0..v.len], 0), + .slice => |v| xev_posix.send(op.fd, v, 0), + .array => |*v| xev_posix.send(op.fd, v.array[0..v.len], 0), }, }, .sendmsg => |*op| .{ - .sendmsg = if (posix.sendmsg(op.fd, op.msghdr, 0)) |v| + .sendmsg = if (xev_posix.sendmsg(op.fd, op.msghdr, 0)) |v| v else |err| err, @@ -1038,7 +1093,7 @@ pub const Completion = struct { error.EOF else if (res > 0) res - else switch (posix.errno(res)) { + else switch (linux.errno(res)) { else => |err| posix.unexpectedErrno(err), }, }; @@ -1046,8 +1101,8 @@ pub const Completion = struct { .recv => |*op| res: { const n_ = switch (op.buffer) { - .slice => |v| posix.recv(op.fd, v, 0), - .array => |*v| posix.recv(op.fd, v, 0), + .slice => |v| xev_posix.recv(op.fd, v, 0), + .array => |*v| xev_posix.recv(op.fd, v, 0), }; break :res .{ @@ -1059,7 +1114,7 @@ pub const Completion = struct { }, .close => |*op| res: { - posix.close(op.fd); + xev_posix.close(op.fd); break :res .{ .close = {} }; }, }; @@ -1072,7 +1127,7 @@ pub const Completion = struct { if (!self.flags.dup) return old_fd; if (self.flags.dup_fd > 0) return self.flags.dup_fd; - self.flags.dup_fd = posix.dup(old_fd) catch return error.DupFailed; + self.flags.dup_fd = xev_posix.dup(old_fd) catch return error.DupFailed; return self.flags.dup_fd; } @@ -1162,7 +1217,7 @@ pub const Operation = union(OperationType) { connect: struct { socket: posix.socket_t, - addr: std.net.Address, + addr: net.Address, }, /// Poll for events but do not perform any operations on them being @@ -1206,7 +1261,7 @@ pub const Operation = union(OperationType) { sendmsg: struct { fd: posix.fd_t, - msghdr: *posix.msghdr_const, + msghdr: *linux.msghdr_const, /// Optionally, a write buffer can be specified and the given /// msghdr will be populated with information about this buffer. @@ -1218,7 +1273,7 @@ pub const Operation = union(OperationType) { recvmsg: struct { fd: posix.fd_t, - msghdr: *posix.msghdr, + msghdr: *linux.msghdr, }, close: struct { @@ -1227,7 +1282,7 @@ pub const Operation = union(OperationType) { shutdown: struct { socket: posix.socket_t, - how: posix.ShutdownHow = .both, + how: ShutdownHow = .both, }, timer: Timer, @@ -1319,44 +1374,63 @@ pub const CancelError = ThreadPoolError || error{ NotFound, }; -pub const AcceptError = posix.EpollCtlError || error{ +pub const ShutdownHow = std.Io.net.ShutdownHow; + +pub const EpollCtlError = error{ + FileDescriptorAlreadyPresentInSet, + OperationCausesCircularLoop, + FileDescriptorNotRegistered, + SystemResources, + UserResourceLimitReached, + FileDescriptorIncompatibleWithEpoll, +} || posix.UnexpectedError; + +pub const PosixShutdownError = error{ + ConnectionAborted, + ConnectionResetByPeer, + BlockingOperationInProgress, + NetworkSubsystemFailed, + SocketNotConnected, + SystemResources, +} || posix.UnexpectedError; + +pub const AcceptError = EpollCtlError || error{ DupFailed, Unknown, }; -pub const CloseError = posix.EpollCtlError || ThreadPoolError || error{ +pub const CloseError = EpollCtlError || ThreadPoolError || error{ Unknown, }; -pub const PollError = posix.EpollCtlError || error{ +pub const PollError = EpollCtlError || error{ DupFailed, Unknown, }; -pub const ShutdownError = posix.EpollCtlError || posix.ShutdownError || error{ +pub const ShutdownError = EpollCtlError || PosixShutdownError || error{ Unknown, }; -pub const ConnectError = posix.EpollCtlError || posix.ConnectError || error{ +pub const ConnectError = EpollCtlError || std.Io.net.IpAddress.ConnectError || xev_posix.ConnectError || error{ DupFailed, Unknown, }; -pub const ReadError = ThreadPoolError || posix.EpollCtlError || - posix.ReadError || - posix.PReadError || - posix.RecvFromError || +pub const ReadError = ThreadPoolError || EpollCtlError || + xev_posix.ReadError || + xev_posix.PReadError || + xev_posix.RecvFromError || error{ DupFailed, EOF, Unknown, }; -pub const WriteError = ThreadPoolError || posix.EpollCtlError || - posix.WriteError || - posix.PWriteError || - posix.SendError || - posix.SendMsgError || +pub const WriteError = ThreadPoolError || EpollCtlError || + xev_posix.WriteError || + xev_posix.PWriteError || + xev_posix.SendError || error{ DupFailed, Unknown, @@ -1381,7 +1455,7 @@ test "Completion size" { const testing = std.testing; // Just so we are aware when we change the size - try testing.expectEqual(@as(usize, 208), @sizeOf(Completion)); + try testing.expectEqual(@as(usize, 184), @sizeOf(Completion)); } test "epoll: default completion" { @@ -1671,7 +1745,6 @@ test "epoll: timerfd" { test "epoll: socket accept/connect/send/recv/close" { const mem = std.mem; - const net = std.net; const os = posix; const testing = std.testing; @@ -1681,19 +1754,19 @@ test "epoll: socket accept/connect/send/recv/close" { // Create a TCP server socket const address = try net.Address.parseIp4("127.0.0.1", 3131); const kernel_backlog = 1; - var ln = try os.socket(address.any.family, os.SOCK.STREAM | os.SOCK.CLOEXEC, 0); - errdefer os.close(ln); + var ln = try xev_posix.socket(address.any.family, os.SOCK.STREAM | os.SOCK.CLOEXEC, 0); + errdefer xev_posix.close(ln); try os.setsockopt(ln, os.SOL.SOCKET, os.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1))); - try os.bind(ln, &address.any, address.getOsSockLen()); - try os.listen(ln, kernel_backlog); + try xev_posix.bind(ln, &address.any, address.getOsSockLen()); + try xev_posix.listen(ln, kernel_backlog); // Create a TCP client socket - var client_conn = try os.socket( + var client_conn = try xev_posix.socket( address.any.family, os.SOCK.NONBLOCK | os.SOCK.STREAM | os.SOCK.CLOEXEC, 0, ); - errdefer os.close(client_conn); + errdefer xev_posix.close(client_conn); // Accept var server_conn: os.socket_t = 0; diff --git a/src/backend/io_uring.zig b/src/backend/io_uring.zig index 849ca395..6891bc3a 100644 --- a/src/backend/io_uring.zig +++ b/src/backend/io_uring.zig @@ -3,6 +3,8 @@ const builtin = @import("builtin"); const assert = std.debug.assert; const linux = std.os.linux; const posix = std.posix; +const net = @import("../posix.zig").net; +const xev_posix = @import("../posix.zig"); const queue = @import("../queue.zig"); const looppkg = @import("../loop.zig"); const Callback = looppkg.Callback(@This()); @@ -10,6 +12,8 @@ const CallbackAction = looppkg.CallbackAction; const CompletionState = looppkg.CompletionState; const noopCallback = looppkg.NoopCallback(@This()); +pub const ShutdownHow = std.Io.net.ShutdownHow; + /// True if this backend is available on this platform. pub fn available() bool { if (comptime builtin.os.tag != .linux) return false; @@ -125,11 +129,11 @@ pub const Loop = struct { /// Update the cached time. pub fn update_now(self: *Loop) void { - if (posix.clock_gettime(posix.CLOCK.MONOTONIC)) |new_time| { - self.cached_now = new_time; + var ts: linux.timespec = undefined; + const rc = linux.clock_gettime(linux.CLOCK.MONOTONIC, &ts); + if (posix.errno(rc) == .SUCCESS) { + self.cached_now = ts; self.flags.now_outdated = false; - } else |_| { - // Errors are ignored. } } @@ -219,6 +223,7 @@ pub const Loop = struct { RingShuttingDown, OpcodeNotSupported, SignalInterrupt, + InvalidThread, }; /// Submit all queued operations. This never does an io_uring submit @@ -930,7 +935,7 @@ pub const Operation = union(OperationType) { connect: struct { socket: posix.socket_t, - addr: std.net.Address, + addr: net.Address, }, poll: struct { @@ -961,7 +966,7 @@ pub const Operation = union(OperationType) { sendmsg: struct { fd: posix.fd_t, - msghdr: *posix.msghdr_const, + msghdr: *linux.msghdr_const, /// Optionally, a write buffer can be specified and the given /// msghdr will be populated with information about this buffer. @@ -973,12 +978,12 @@ pub const Operation = union(OperationType) { recvmsg: struct { fd: posix.fd_t, - msghdr: *posix.msghdr, + msghdr: *linux.msghdr, }, shutdown: struct { socket: posix.socket_t, - how: posix.ShutdownHow = .both, + how: ShutdownHow = .both, }, pwrite: struct { @@ -1122,7 +1127,7 @@ test "Completion size" { const testing = std.testing; // Just so we are aware when we change the size - try testing.expectEqual(@as(usize, 152), @sizeOf(Completion)); + try testing.expectEqual(@as(usize, 128), @sizeOf(Completion)); } test "io_uring: available" { @@ -1404,7 +1409,6 @@ test "io_uring: timer remove" { test "io_uring: socket accept/connect/send/recv/close" { const mem = std.mem; - const net = std.net; const os = posix; const testing = std.testing; @@ -1414,15 +1418,15 @@ test "io_uring: socket accept/connect/send/recv/close" { // Create a TCP server socket const address = try net.Address.parseIp4("127.0.0.1", 3131); const kernel_backlog = 1; - var ln = try os.socket(address.any.family, os.SOCK.STREAM | os.SOCK.CLOEXEC, 0); - errdefer os.close(ln); + var ln = try xev_posix.socket(address.any.family, os.SOCK.STREAM | os.SOCK.CLOEXEC, 0); + errdefer xev_posix.close(ln); try os.setsockopt(ln, os.SOL.SOCKET, os.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1))); - try os.bind(ln, &address.any, address.getOsSockLen()); - try os.listen(ln, kernel_backlog); + try xev_posix.bind(ln, &address.any, address.getOsSockLen()); + try xev_posix.listen(ln, kernel_backlog); // Create a TCP client socket - var client_conn = try os.socket(address.any.family, os.SOCK.STREAM | os.SOCK.CLOEXEC, 0); - errdefer os.close(client_conn); + var client_conn = try xev_posix.socket(address.any.family, os.SOCK.STREAM | os.SOCK.CLOEXEC, 0); + errdefer xev_posix.close(client_conn); // Accept var server_conn: os.socket_t = 0; @@ -1629,8 +1633,6 @@ test "io_uring: socket accept/connect/send/recv/close" { test "io_uring: sendmsg/recvmsg" { const mem = std.mem; - const net = std.net; - const os = posix; const testing = std.testing; var loop = try Loop.init(.{}); @@ -1638,21 +1640,21 @@ test "io_uring: sendmsg/recvmsg" { // Create a TCP server socket const address = try net.Address.parseIp4("127.0.0.1", 3131); - const server = try posix.socket(address.any.family, posix.SOCK.DGRAM, 0); - defer posix.close(server); + const server = try xev_posix.socket(address.any.family, posix.SOCK.DGRAM, 0); + defer xev_posix.close(server); try posix.setsockopt(server, posix.SOL.SOCKET, posix.SO.REUSEPORT, &mem.toBytes(@as(c_int, 1))); try posix.setsockopt(server, posix.SOL.SOCKET, posix.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1))); - try posix.bind(server, &address.any, address.getOsSockLen()); + try xev_posix.bind(server, &address.any, address.getOsSockLen()); - const client = try posix.socket(address.any.family, posix.SOCK.DGRAM, 0); - defer posix.close(client); + const client = try xev_posix.socket(address.any.family, posix.SOCK.DGRAM, 0); + defer xev_posix.close(client); // Send const buffer_send = [_]u8{42} ** 128; - const iovecs_send = [_]os.iovec_const{ - os.iovec_const{ .base = &buffer_send, .len = buffer_send.len }, + const iovecs_send = [_]posix.iovec_const{ + posix.iovec_const{ .base = &buffer_send, .len = buffer_send.len }, }; - var msg_send = os.msghdr_const{ + var msg_send = linux.msghdr_const{ .name = &address.any, .namelen = address.getOsSockLen(), .iov = &iovecs_send, @@ -1684,12 +1686,12 @@ test "io_uring: sendmsg/recvmsg" { // Recv var buffer_recv = [_]u8{0} ** 128; - var iovecs_recv = [_]os.iovec{ - os.iovec{ .base = &buffer_recv, .len = buffer_recv.len }, + var iovecs_recv = [_]posix.iovec{ + posix.iovec{ .base = &buffer_recv, .len = buffer_recv.len }, }; const addr = [_]u8{0} ** 4; var address_recv = net.Address.initIp4(addr, 0); - var msg_recv: os.msghdr = os.msghdr{ + var msg_recv: linux.msghdr = linux.msghdr{ .name = &address_recv.any, .namelen = address_recv.getOsSockLen(), .iov = &iovecs_recv, @@ -1728,7 +1730,6 @@ test "io_uring: sendmsg/recvmsg" { test "io_uring: socket read cancellation" { const mem = std.mem; - const net = std.net; const testing = std.testing; var loop = try Loop.init(.{}); @@ -1736,10 +1737,10 @@ test "io_uring: socket read cancellation" { // Create a UDP server socket const address = try net.Address.parseIp4("127.0.0.1", 3131); - const socket = try posix.socket(address.any.family, posix.SOCK.DGRAM | posix.SOCK.CLOEXEC, 0); - errdefer posix.close(socket); + const socket = try xev_posix.socket(address.any.family, posix.SOCK.DGRAM | posix.SOCK.CLOEXEC, 0); + errdefer xev_posix.close(socket); try posix.setsockopt(socket, posix.SOL.SOCKET, posix.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1))); - try posix.bind(socket, &address.any, address.getOsSockLen()); + try xev_posix.bind(socket, &address.any, address.getOsSockLen()); // Read var read_result: Result = undefined; diff --git a/src/backend/iocp.zig b/src/backend/iocp.zig index 671d08cf..cc8506ea 100644 --- a/src/backend/iocp.zig +++ b/src/backend/iocp.zig @@ -6,6 +6,9 @@ const windows = @import("../windows.zig"); const queue = @import("../queue.zig"); const heap = @import("../heap.zig"); const posix = std.posix; +const net = @import("../posix.zig").net; + +pub const ShutdownHow = std.Io.net.ShutdownHow; const looppkg = @import("../loop.zig"); const Options = looppkg.Options; @@ -508,7 +511,7 @@ pub const Loop = struct { var socket_type: i32 = 0; const socket_type_bytes = std.mem.asBytes(&socket_type); var opt_len: i32 = @as(i32, @intCast(socket_type_bytes.len)); - std.debug.assert(windows.ws2_32.getsockopt(asSocket(v.socket), posix.SOL.SOCKET, posix.SO.TYPE, socket_type_bytes, &opt_len) == 0); + std.debug.assert(windows.ws2_32.getsockopt(asSocket(v.socket), windows.ws2_32.SOL.SOCKET, windows.ws2_32.SO.TYPE, socket_type_bytes, &opt_len) == 0); v.internal_accept_socket = windows.WSASocketW(addr.family, socket_type, 0, null, 0, windows.ws2_32.WSA_FLAG_OVERLAPPED) catch |err| { break :action .{ .result = .{ .accept = err } }; @@ -583,7 +586,7 @@ pub const Loop = struct { }; }, - .shutdown => |*v| .{ .result = .{ .shutdown = posix.shutdown(asSocket(v.socket), v.how) } }, + .shutdown => |*v| .{ .result = .{ .shutdown = iocpShutdown(asSocket(v.socket), v.how) } }, .write => |*v| action: { self.associate_fd(completion.handle().?) catch unreachable; @@ -619,7 +622,7 @@ pub const Loop = struct { v.wsa_buffer = .{ .buf = @constCast(buffer.ptr), .len = @as(u32, @intCast(buffer.len)) }; const result = windows.ws2_32.WSASend( asSocket(v.fd), - @as([*]windows.ws2_32.WSABUF, @ptrCast(&v.wsa_buffer)), + @as([*]windows.WSABUF, @ptrCast(&v.wsa_buffer)), 1, null, 0, @@ -647,7 +650,7 @@ pub const Loop = struct { const result = windows.ws2_32.WSARecv( asSocket(v.fd), - @as([*]windows.ws2_32.WSABUF, @ptrCast(&v.wsa_buffer)), + @as([*]windows.WSABUF, @ptrCast(&v.wsa_buffer)), 1, null, &flags, @@ -672,7 +675,7 @@ pub const Loop = struct { v.wsa_buffer = .{ .buf = @constCast(buffer.ptr), .len = @as(u32, @intCast(buffer.len)) }; const result = windows.ws2_32.WSASendTo( asSocket(v.fd), - @as([*]windows.ws2_32.WSABUF, @ptrCast(&v.wsa_buffer)), + @as([*]windows.WSABUF, @ptrCast(&v.wsa_buffer)), 1, null, 0, @@ -702,7 +705,7 @@ pub const Loop = struct { const result = windows.ws2_32.WSARecvFrom( asSocket(v.fd), - @as([*]windows.ws2_32.WSABUF, @ptrCast(&v.wsa_buffer)), + @as([*]windows.WSABUF, @ptrCast(&v.wsa_buffer)), 1, null, &flags, @@ -865,9 +868,9 @@ pub const Loop = struct { /// This has to be done only once per handle so we delegate the responsibility to the caller. pub fn associate_fd(self: Loop, fd: windows.HANDLE) !void { if (fd == windows.INVALID_HANDLE_VALUE or self.iocp_handle == windows.INVALID_HANDLE_VALUE) return error.InvalidParameter; - // We ignore the error here because multiple call to CreateIoCompletionPort with a HANDLE - // already registered triggers a INVALID_PARAMETER error and we have no way to see the cause - // of it. + // We ignore the error here because multiple calls to CreateIoCompletionPort with a HANDLE + // already registered triggers an INVALID_PARAMETER error and we have no way to see the cause + // of it. Call the raw extern directly to avoid the wrapper's unreachable on INVALID_PARAMETER. _ = windows.kernel32.CreateIoCompletionPort(fd, self.iocp_handle, 0, 0); } }; @@ -877,6 +880,44 @@ inline fn asSocket(h: windows.HANDLE) windows.ws2_32.SOCKET { return @as(windows.ws2_32.SOCKET, @ptrCast(h)); } +fn iocpShutdown(sock: windows.ws2_32.SOCKET, how: ShutdownHow) ShutdownError!void { + const result = windows.ws2_32.shutdown(sock, switch (how) { + .recv => windows.ws2_32.SD_RECEIVE, + .send => windows.ws2_32.SD_SEND, + .both => windows.ws2_32.SD_BOTH, + }); + if (result != 0) switch (windows.ws2_32.WSAGetLastError()) { + .WSAECONNABORTED => return error.ConnectionAborted, + .WSAECONNRESET => return error.ConnectionResetByPeer, + .WSAEINPROGRESS => return error.BlockingOperationInProgress, + .WSAEINVAL => unreachable, + .WSAENETDOWN => return error.NetworkSubsystemFailed, + .WSAENOTCONN => return error.SocketNotConnected, + .WSAENOTSOCK => unreachable, + .WSANOTINITIALISED => unreachable, + else => |err| return windows.unexpectedWSAError(err), + }; +} + +fn iocpClose(h: windows.HANDLE) void { + _ = windows.ws2_32.closesocket(asSocket(h)); +} + +fn iocpSetsockopt(sock: windows.ws2_32.SOCKET, level: i32, optname: i32, optval: []const u8) !void { + const result = windows.ws2_32.setsockopt(sock, level, optname, optval.ptr, @as(i32, @intCast(optval.len))); + if (result != 0) return error.Unexpected; +} + +fn iocpBind(sock: windows.ws2_32.SOCKET, addr: *const posix.sockaddr, len: posix.socklen_t) !void { + const result = windows.ws2_32.bind(sock, addr, @as(i32, @intCast(len))); + if (result != 0) return error.Unexpected; +} + +fn iocpListen(sock: windows.ws2_32.SOCKET, backlog: u31) !void { + const result = windows.ws2_32.listen(sock, @as(i32, backlog)); + if (result != 0) return error.Unexpected; +} + /// A completion is a request to perform some work with the loop. pub const Completion = struct { /// Operation to execute. @@ -1086,11 +1127,11 @@ pub const Completion = struct { var opt_len: i32 = @as(i32, @intCast(socket_type_bytes.len)); // Here we assume the call will succeed because the socket should be valid. - std.debug.assert(windows.ws2_32.getsockopt(asSocket(v.fd), posix.SOL.SOCKET, posix.SO.TYPE, socket_type_bytes, &opt_len) == 0); + std.debug.assert(windows.ws2_32.getsockopt(asSocket(v.fd), windows.ws2_32.SOL.SOCKET, windows.ws2_32.SO.TYPE, socket_type_bytes, &opt_len) == 0); break :t socket_type; }; - if (socket_type == posix.SOCK.STREAM and bytes_transferred == 0) { + if (socket_type == windows.ws2_32.SOCK.STREAM and bytes_transferred == 0) { return .{ .recv = error.EOF }; } @@ -1229,7 +1270,7 @@ pub const Operation = union(OperationType) { connect: struct { socket: windows.HANDLE, - addr: std.net.Address, + addr: net.Address, }, read: struct { @@ -1245,7 +1286,7 @@ pub const Operation = union(OperationType) { shutdown: struct { socket: windows.HANDLE, - how: posix.ShutdownHow = .both, + how: ShutdownHow = .both, }, write: struct { @@ -1262,20 +1303,20 @@ pub const Operation = union(OperationType) { send: struct { fd: windows.HANDLE, buffer: WriteBuffer, - wsa_buffer: windows.ws2_32.WSABUF = undefined, + wsa_buffer: windows.WSABUF = undefined, }, recv: struct { fd: windows.HANDLE, buffer: ReadBuffer, - wsa_buffer: windows.ws2_32.WSABUF = undefined, + wsa_buffer: windows.WSABUF = undefined, }, sendto: struct { fd: windows.HANDLE, buffer: WriteBuffer, - addr: std.net.Address, - wsa_buffer: windows.ws2_32.WSABUF = undefined, + addr: net.Address, + wsa_buffer: windows.WSABUF = undefined, }, recvfrom: struct { @@ -1283,7 +1324,7 @@ pub const Operation = union(OperationType) { buffer: ReadBuffer, addr: posix.sockaddr = undefined, addr_size: posix.socklen_t = @sizeOf(posix.sockaddr), - wsa_buffer: windows.ws2_32.WSABUF = undefined, + wsa_buffer: windows.WSABUF = undefined, }, timer: Timer, @@ -1350,7 +1391,14 @@ pub const ConnectError = error{ Unexpected, }; -pub const ShutdownError = posix.ShutdownError || error{ +pub const ShutdownError = error{ + ConnectionAborted, + ConnectionResetByPeer, + BlockingOperationInProgress, + NetworkSubsystemFailed, + SocketNotConnected, + SystemResources, +} || posix.UnexpectedError || error{ Unexpected, }; @@ -1966,7 +2014,6 @@ test "iocp: file IO with offset" { test "iocp: socket accept/connect/send/recv/close" { const mem = std.mem; - const net = std.net; const testing = std.testing; var loop = try Loop.init(.{}); @@ -1975,16 +2022,16 @@ test "iocp: socket accept/connect/send/recv/close" { // Create a TCP server socket const address = try net.Address.parseIp4("127.0.0.1", 3131); const kernel_backlog = 1; - const ln = try windows.WSASocketW(posix.AF.INET, posix.SOCK.STREAM, posix.IPPROTO.TCP, null, 0, windows.ws2_32.WSA_FLAG_OVERLAPPED); - errdefer posix.close(ln); + const ln = try windows.WSASocketW(windows.ws2_32.AF.INET, windows.ws2_32.SOCK.STREAM, windows.ws2_32.IPPROTO.TCP, null, 0, windows.ws2_32.WSA_FLAG_OVERLAPPED); + errdefer iocpClose(ln); - try posix.setsockopt(ln, posix.SOL.SOCKET, posix.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1))); - try posix.bind(ln, &address.any, address.getOsSockLen()); - try posix.listen(ln, kernel_backlog); + try iocpSetsockopt(asSocket(ln), windows.ws2_32.SOL.SOCKET, windows.ws2_32.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1))); + try iocpBind(asSocket(ln), &address.any, address.getOsSockLen()); + try iocpListen(asSocket(ln), kernel_backlog); // Create a TCP client socket - const client_conn = try windows.WSASocketW(posix.AF.INET, posix.SOCK.STREAM, posix.IPPROTO.TCP, null, 0, windows.ws2_32.WSA_FLAG_OVERLAPPED); - errdefer posix.close(client_conn); + const client_conn = try windows.WSASocketW(windows.ws2_32.AF.INET, windows.ws2_32.SOCK.STREAM, windows.ws2_32.IPPROTO.TCP, null, 0, windows.ws2_32.WSA_FLAG_OVERLAPPED); + errdefer iocpClose(client_conn); var server_conn_result: Result = undefined; var c_accept: Completion = .{ @@ -2233,7 +2280,6 @@ test "iocp: socket accept/connect/send/recv/close" { test "iocp: recv cancellation" { const mem = std.mem; - const net = std.net; const testing = std.testing; var loop = try Loop.init(.{}); @@ -2241,11 +2287,11 @@ test "iocp: recv cancellation" { // Create a TCP server socket const address = try net.Address.parseIp4("127.0.0.1", 3131); - const socket = try windows.WSASocketW(posix.AF.INET, posix.SOCK.DGRAM, posix.IPPROTO.UDP, null, 0, windows.ws2_32.WSA_FLAG_OVERLAPPED); - errdefer posix.close(socket); + const socket = try windows.WSASocketW(windows.ws2_32.AF.INET, windows.ws2_32.SOCK.DGRAM, windows.ws2_32.IPPROTO.UDP, null, 0, windows.ws2_32.WSA_FLAG_OVERLAPPED); + errdefer iocpClose(socket); - try posix.setsockopt(socket, posix.SOL.SOCKET, posix.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1))); - try posix.bind(socket, &address.any, address.getOsSockLen()); + try iocpSetsockopt(asSocket(socket), windows.ws2_32.SOL.SOCKET, windows.ws2_32.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1))); + try iocpBind(asSocket(socket), &address.any, address.getOsSockLen()); var recv_buf: [128]u8 = undefined; var recv_result: Result = undefined; @@ -2305,7 +2351,6 @@ test "iocp: recv cancellation" { test "iocp: accept cancellation" { const mem = std.mem; - const net = std.net; const testing = std.testing; var loop = try Loop.init(.{}); @@ -2314,12 +2359,12 @@ test "iocp: accept cancellation" { // Create a TCP server socket const address = try net.Address.parseIp4("127.0.0.1", 3131); const kernel_backlog = 1; - const ln = try windows.WSASocketW(posix.AF.INET, posix.SOCK.STREAM, posix.IPPROTO.TCP, null, 0, windows.ws2_32.WSA_FLAG_OVERLAPPED); - errdefer posix.close(ln); + const ln = try windows.WSASocketW(windows.ws2_32.AF.INET, windows.ws2_32.SOCK.STREAM, windows.ws2_32.IPPROTO.TCP, null, 0, windows.ws2_32.WSA_FLAG_OVERLAPPED); + errdefer iocpClose(ln); - try posix.setsockopt(ln, posix.SOL.SOCKET, posix.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1))); - try posix.bind(ln, &address.any, address.getOsSockLen()); - try posix.listen(ln, kernel_backlog); + try iocpSetsockopt(asSocket(ln), windows.ws2_32.SOL.SOCKET, windows.ws2_32.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1))); + try iocpBind(asSocket(ln), &address.any, address.getOsSockLen()); + try iocpListen(asSocket(ln), kernel_backlog); var server_conn_result: Result = undefined; var c_accept: Completion = .{ diff --git a/src/backend/kqueue.zig b/src/backend/kqueue.zig index 5bfd63c2..a78249dd 100644 --- a/src/backend/kqueue.zig +++ b/src/backend/kqueue.zig @@ -5,11 +5,14 @@ const builtin = @import("builtin"); const assert = std.debug.assert; const posix = std.posix; const darwin = @import("../darwin.zig"); +const xev_posix = @import("../posix.zig"); +const net = xev_posix.net; const queue = @import("../queue.zig"); const queue_mpsc = @import("../queue_mpsc.zig"); const heap = @import("../heap.zig"); const ThreadPool = @import("../ThreadPool.zig"); const Async = @import("../main.zig").Kqueue.Async; +const KEventError = std.Io.Kqueue.KEventError; const looppkg = @import("../loop.zig"); const Options = looppkg.Options; @@ -105,8 +108,8 @@ pub const Loop = struct { /// for what options matter for kqueue. pub fn init(options: Options) !Loop { // This creates a new kqueue fd - const fd = try posix.kqueue(); - errdefer posix.close(fd); + const fd = try createKqueueFd(); + errdefer xev_posix.close(fd); const wakeup_state: Wakeup = try .init(); errdefer wakeup_state.deinit(); @@ -126,7 +129,7 @@ pub const Loop = struct { /// Deinitialize the loop, this closes the kqueue. Any events that /// were unprocessed are lost -- their callbacks will never be called. pub fn deinit(self: *Loop) void { - posix.close(self.kqueue_fd); + xev_posix.close(self.kqueue_fd); self.wakeup_state.deinit(); } @@ -588,10 +591,9 @@ pub const Loop = struct { /// Update the cached time. pub fn update_now(self: *Loop) void { - if (posix.clock_gettime(posix.CLOCK.MONOTONIC)) |new_time| { - self.cached_now = new_time; - } else |_| { - // Errors are ignored. + switch (posix.errno(posix.system.clock_gettime(posix.CLOCK.MONOTONIC, &self.cached_now))) { + .SUCCESS => {}, + else => {}, } } @@ -815,7 +817,7 @@ pub const Loop = struct { }, .close => |v| action: { - posix.close(v.fd); + xev_posix.close(v.fd); break :action .{ .result = 0 }; }, @@ -1142,7 +1144,7 @@ pub const Completion = struct { }, .accept => |*op| .{ - .accept = if (posix.accept( + .accept = if (xev_posix.accept( op.socket, &op.addr, &op.addr_size, @@ -1154,34 +1156,34 @@ pub const Completion = struct { }, .connect => |*op| .{ - .connect = if (posix.getsockoptError(op.socket)) {} else |err| err, + .connect = if (getsockoptError(op.socket)) {} else |err| err, }, .write => |*op| .{ .write = switch (op.buffer) { - .slice => |v| posix.write(op.fd, v), - .array => |*v| posix.write(op.fd, v.array[0..v.len]), + .slice => |v| xev_posix.write(op.fd, v) catch |err| mapWriteError(err), + .array => |*v| xev_posix.write(op.fd, v.array[0..v.len]) catch |err| mapWriteError(err), }, }, .pwrite => |*op| .{ .pwrite = switch (op.buffer) { - .slice => |v| posix.pwrite(op.fd, v, op.offset), - .array => |*v| posix.pwrite(op.fd, v.array[0..v.len], op.offset), + .slice => |v| xev_posix.pwrite(op.fd, v, op.offset) catch |err| mapWriteError(err), + .array => |*v| xev_posix.pwrite(op.fd, v.array[0..v.len], op.offset) catch |err| mapWriteError(err), }, }, .send => |*op| .{ .send = switch (op.buffer) { - .slice => |v| posix.send(op.fd, v, 0), - .array => |*v| posix.send(op.fd, v.array[0..v.len], 0), + .slice => |v| xev_posix.send(op.fd, v, 0) catch |err| mapWriteError(err), + .array => |*v| xev_posix.send(op.fd, v.array[0..v.len], 0) catch |err| mapWriteError(err), }, }, .sendto => |*op| .{ .sendto = switch (op.buffer) { - .slice => |v| posix.sendto(op.fd, v, 0, &op.addr.any, op.addr.getOsSockLen()), - .array => |*v| posix.sendto(op.fd, v.array[0..v.len], 0, &op.addr.any, op.addr.getOsSockLen()), + .slice => |v| xev_posix.sendto(op.fd, v, 0, &op.addr.any, op.addr.getOsSockLen()) catch |err| mapWriteError(err), + .array => |*v| xev_posix.sendto(op.fd, v.array[0..v.len], 0, &op.addr.any, op.addr.getOsSockLen()) catch |err| mapWriteError(err), }, }, @@ -1191,8 +1193,8 @@ pub const Completion = struct { const ev = ev_ orelse break :res .{ .read = error.MissingKevent }; break :empty @intCast(ev.data); - } else posix.read(op.fd, v), - .array => |*v| posix.read(op.fd, v), + } else xev_posix.read(op.fd, v) catch |err| mapReadError(err), + .array => |*v| xev_posix.read(op.fd, v) catch |err| mapReadError(err), }; break :res .{ @@ -1209,8 +1211,8 @@ pub const Completion = struct { const ev = ev_ orelse break :res .{ .read = error.MissingKevent }; break :empty @intCast(ev.data); - } else posix.pread(op.fd, v, op.offset), - .array => |*v| posix.pread(op.fd, v, op.offset), + } else xev_posix.pread(op.fd, v, op.offset) catch |err| mapReadError(err), + .array => |*v| xev_posix.pread(op.fd, v, op.offset) catch |err| mapReadError(err), }; break :res .{ @@ -1227,8 +1229,8 @@ pub const Completion = struct { const ev = ev_ orelse break :res .{ .read = error.MissingKevent }; break :empty @intCast(ev.data); - } else posix.recv(op.fd, v, 0), - .array => |*v| posix.recv(op.fd, v, 0), + } else xev_posix.recv(op.fd, v, 0) catch |err| mapReadError(err), + .array => |*v| xev_posix.recv(op.fd, v, 0) catch |err| mapReadError(err), }; break :res .{ @@ -1245,8 +1247,8 @@ pub const Completion = struct { const ev = ev_ orelse break :res .{ .read = error.MissingKevent }; break :empty @intCast(ev.data); - } else posix.recvfrom(op.fd, v, 0, &op.addr, &op.addr_size), - .array => |*v| posix.recvfrom(op.fd, v, 0, &op.addr, &op.addr_size), + } else xev_posix.recvfrom(op.fd, v, 0, &op.addr, &op.addr_size) catch |err| mapReadError(err), + .array => |*v| xev_posix.recvfrom(op.fd, v, 0, &op.addr, &op.addr_size) catch |err| mapReadError(err), }; break :res .{ @@ -1280,7 +1282,7 @@ pub const Completion = struct { }, .close => |*op| res: { - posix.close(op.fd); + xev_posix.close(op.fd); break :res .{ .close = {} }; }, }; @@ -1397,7 +1399,7 @@ pub const Completion = struct { .shutdown = switch (errno) { .SUCCESS => {}, .CANCELED => error.Canceled, - .NOTCONN => error.SocketNotConnected, + .NOTCONN => error.SocketUnconnected, else => |err| posix.unexpectedErrno(err), }, }, @@ -1507,6 +1509,66 @@ const Wakeup = if (builtin.os.tag.isDarwin()) struct { } }; +fn createKqueueFd() !posix.fd_t { + const rc = posix.system.kqueue(); + switch (posix.errno(rc)) { + .SUCCESS => return @intCast(rc), + .MFILE => return error.ProcessFdQuotaExceeded, + .NFILE => return error.SystemFdQuotaExceeded, + else => |err| return posix.unexpectedErrno(err), + } +} + +fn mapReadError(err: anyerror) ReadError { + return switch (err) { + error.AccessDenied, error.PermissionDenied => error.PermissionDenied, + error.WouldBlock => error.MissingKevent, + else => error.Unexpected, + }; +} + +fn mapWriteError(err: anyerror) WriteError { + return switch (err) { + error.AccessDenied, error.PermissionDenied => error.PermissionDenied, + else => error.Unexpected, + }; +} + +fn monotonicNanos() i128 { + var ts: posix.timespec = undefined; + switch (posix.errno(posix.system.clock_gettime(posix.CLOCK.MONOTONIC, &ts))) { + .SUCCESS => {}, + else => unreachable, + } + return @as(i128, ts.sec) * std.time.ns_per_s + ts.nsec; +} + +fn getsockoptError(socket: posix.socket_t) ConnectError!void { + var err_code: c_int = 0; + var err_len: posix.socklen_t = @sizeOf(c_int); + const rc = std.c.getsockopt(socket, posix.SOL.SOCKET, posix.SO.ERROR, &err_code, &err_len); + switch (posix.errno(rc)) { + .SUCCESS => {}, + else => |err| return posix.unexpectedErrno(err), + } + + if (err_code == 0) return; + return switch (@as(posix.E, @enumFromInt(@as(u16, @intCast(err_code))))) { + .ADDRNOTAVAIL => error.AddressUnavailable, + .AFNOSUPPORT => error.AddressFamilyUnsupported, + .ACCES, .PERM => error.AccessDenied, + .ALREADY, .INPROGRESS => error.ConnectionPending, + .CONNREFUSED => error.ConnectionRefused, + .CONNRESET => error.ConnectionResetByPeer, + .HOSTUNREACH => error.HostUnreachable, + .NETUNREACH => error.NetworkUnreachable, + .TIMEDOUT => error.Timeout, + .NETDOWN => error.NetworkDown, + .NOBUFS, .NOMEM => error.SystemResources, + else => error.Unexpected, + }; +} + pub const OperationType = enum { noop, accept, @@ -1543,7 +1605,7 @@ pub const Operation = union(OperationType) { connect: struct { socket: posix.socket_t, - addr: std.net.Address, + addr: net.Address, }, read: struct { @@ -1584,7 +1646,7 @@ pub const Operation = union(OperationType) { sendto: struct { fd: posix.fd_t, buffer: WriteBuffer, - addr: std.net.Address, + addr: net.Address, }, recvfrom: struct { @@ -1600,7 +1662,7 @@ pub const Operation = union(OperationType) { shutdown: struct { socket: posix.socket_t, - how: posix.ShutdownHow = .both, + how: std.Io.net.ShutdownHow = .both, }, timer: Timer, @@ -1649,53 +1711,43 @@ pub const CancelError = error{ Canceled, }; -pub const AcceptError = posix.KEventError || posix.AcceptError || error{ +pub const AcceptError = KEventError || std.Io.net.Server.AcceptError || error{ Canceled, Unexpected, }; -pub const ConnectError = posix.KEventError || posix.ConnectError || error{ +pub const ConnectError = KEventError || std.Io.net.IpAddress.ConnectError || error{ Canceled, Unexpected, }; -pub const ReadError = posix.KEventError || - posix.ReadError || - posix.PReadError || - posix.RecvFromError || - error{ - EOF, - Canceled, - MissingKevent, - PermissionDenied, - Unexpected, - }; +pub const ReadError = KEventError || error{ + EOF, + Canceled, + MissingKevent, + PermissionDenied, + Unexpected, +}; -pub const WriteError = posix.KEventError || - posix.WriteError || - posix.PWriteError || - posix.SendError || - posix.SendMsgError || - posix.SendToError || - error{ - Canceled, - PermissionDenied, - Unexpected, - }; +pub const WriteError = KEventError || error{ + Canceled, + PermissionDenied, + Unexpected, +}; -pub const MachPortError = posix.KEventError || error{ +pub const MachPortError = KEventError || error{ Canceled, Unexpected, }; -pub const ProcError = posix.KEventError || error{ +pub const ProcError = KEventError || error{ Canceled, MissingKevent, Unexpected, NoSuchProcess, }; -pub const ShutdownError = posix.ShutdownError || error{ +pub const ShutdownError = std.Io.net.ShutdownError || error{ Canceled, Unexpected, }; @@ -1819,14 +1871,32 @@ fn kevent_syscall( changelist: []const Kevent, eventlist: []Kevent, timeout: ?*const posix.timespec, -) posix.KEventError!usize { - // Normaly Kevent? Just use the normal posix.kevent call. - if (Kevent == posix.Kevent) return try posix.kevent( - kq, - changelist, - eventlist, - timeout, - ); +) KEventError!usize { + // Normal Kevent? Just use the normal kevent syscall. + if (Kevent == std.c.Kevent) { + while (true) { + const rc = posix.system.kevent( + kq, + changelist.ptr, + std.math.cast(c_int, changelist.len) orelse return error.Overflow, + eventlist.ptr, + std.math.cast(c_int, eventlist.len) orelse return error.Overflow, + timeout, + ); + switch (posix.errno(rc)) { + .SUCCESS => return @intCast(rc), + .ACCES => return error.AccessDenied, + .FAULT => unreachable, + .BADF => unreachable, // Always a race condition. + .INTR => continue, + .INVAL => unreachable, + .NOENT => return error.EventNotFound, + .NOMEM => return error.SystemResources, + .SRCH => return error.ProcessNotFound, + else => unreachable, + } + } + } // Otherwise, we have to call the kevent64 variant. while (true) { @@ -1836,7 +1906,7 @@ fn kevent_syscall( std.math.cast(c_int, changelist.len) orelse return error.Overflow, eventlist.ptr, std.math.cast(c_int, eventlist.len) orelse return error.Overflow, - 0, + .{}, timeout, ); switch (posix.errno(rc)) { @@ -1859,10 +1929,10 @@ inline fn errno_to_result(errno: posix.E) i32 { return -@as(i32, @intCast(@intFromEnum(errno))); } -/// kevent_init initializes a Kevent from an posix.Kevent. This is used when +/// kevent_init initializes a Kevent from a std.c.Kevent. This is used when /// the "ext" fields are zero. -inline fn kevent_init(ev: posix.Kevent) Kevent { - if (Kevent == posix.Kevent) return ev; +inline fn kevent_init(ev: std.c.Kevent) Kevent { + if (Kevent == std.c.Kevent) return ev; return .{ .ident = ev.ident, @@ -1876,7 +1946,7 @@ inline fn kevent_init(ev: posix.Kevent) Kevent { } comptime { - if (@sizeOf(Completion) != 256) { + if (@sizeOf(Completion) > 256) { @compileLog(@sizeOf(Completion)); unreachable; } @@ -2229,7 +2299,6 @@ test "kqueue: canceling a completed operation" { test "kqueue: socket accept/connect/send/recv/close" { const mem = std.mem; - const net = std.net; const testing = std.testing; var loop = try Loop.init(.{}); @@ -2238,19 +2307,19 @@ test "kqueue: socket accept/connect/send/recv/close" { // Create a TCP server socket const address = try net.Address.parseIp4("127.0.0.1", 3131); const kernel_backlog = 1; - var ln = try posix.socket(address.any.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); - errdefer posix.close(ln); + var ln = try xev_posix.socket(address.any.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); + errdefer xev_posix.close(ln); try posix.setsockopt(ln, posix.SOL.SOCKET, posix.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1))); - try posix.bind(ln, &address.any, address.getOsSockLen()); - try posix.listen(ln, kernel_backlog); + try xev_posix.bind(ln, &address.any, address.getOsSockLen()); + try xev_posix.listen(ln, kernel_backlog); // Create a TCP client socket - var client_conn = try posix.socket( + var client_conn = try xev_posix.socket( address.any.family, posix.SOCK.NONBLOCK | posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0, ); - errdefer posix.close(client_conn); + errdefer xev_posix.close(client_conn); // Accept var server_conn: posix.socket_t = 0; @@ -2498,6 +2567,7 @@ test "kqueue: socket accept/connect/send/recv/close" { test "kqueue: file IO on thread pool" { if (builtin.os.tag != .macos) return error.SkipZigTest; const testing = std.testing; + const io = testing.io; var tpool = ThreadPool.init(.{}); defer tpool.deinit(); @@ -2507,12 +2577,12 @@ test "kqueue: file IO on thread pool" { // Create our file const path = "test_watcher_file"; - const f = try std.fs.cwd().createFile(path, .{ + const f = try std.Io.Dir.cwd().createFile(io, path, .{ .read = true, .truncate = true, }); - defer f.close(); - defer std.fs.cwd().deleteFile(path) catch {}; + defer f.close(io); + defer std.Io.Dir.cwd().deleteFile(io, path) catch {}; // Perform a write and then a read var write_buf = [_]u8{ 1, 1, 2, 3, 5, 8, 13 }; @@ -2547,10 +2617,10 @@ test "kqueue: file IO on thread pool" { try loop.run(.until_done); // Make sure the data is on disk - try f.sync(); + try f.sync(io); - const f2 = try std.fs.cwd().openFile(path, .{}); - defer f2.close(); + const f2 = try std.Io.Dir.cwd().openFile(io, path, .{}); + defer f2.close(io); // Read var read_buf: [128]u8 = undefined; @@ -2603,7 +2673,7 @@ test "kqueue: mach port" { darwin.KernE.SUCCESS, darwin.getKernError(posix.system.mach_port_allocate( mach_self, - @intFromEnum(posix.system.MACH_PORT_RIGHT.RECEIVE), + posix.system.MACH.PORT.RIGHT.RECEIVE, &mach_port, )), ); @@ -2644,7 +2714,7 @@ test "kqueue: mach port" { // Send a message to the port var msg: darwin.mach_msg_header_t = .{ - .msgh_bits = @intFromEnum(posix.system.MACH_MSG_TYPE.MAKE_SEND_ONCE), + .msgh_bits = @intFromEnum(posix.system.MACH.MSG.TYPE.MAKE_SEND_ONCE), .msgh_size = @sizeOf(darwin.mach_msg_header_t), .msgh_remote_port = mach_port, .msgh_local_port = darwin.MACH_PORT_NULL, @@ -2694,7 +2764,7 @@ test "kqueue: timer armed from delayed callback must not fire early" { darwin.KernE.SUCCESS, darwin.getKernError(posix.system.mach_port_allocate( mach_self, - @intFromEnum(posix.system.MACH_PORT_RIGHT.RECEIVE), + posix.system.MACH.PORT.RIGHT.RECEIVE, &mach_port, )), ); @@ -2717,7 +2787,7 @@ test "kqueue: timer armed from delayed callback must not fire early" { r: Result, ) CallbackAction { const s: *State = @ptrCast(@alignCast(ud.?)); - s.timer_fired_ns = std.time.nanoTimestamp(); + s.timer_fired_ns = monotonicNanos(); s.timer_trigger = r.timer catch unreachable; return .disarm; } @@ -2741,7 +2811,7 @@ test "kqueue: timer armed from delayed callback must not fire early" { ) CallbackAction { _ = r.machport catch unreachable; const s: *State = @ptrCast(@alignCast(ud.?)); - s.timer_started_ns = std.time.nanoTimestamp(); + s.timer_started_ns = monotonicNanos(); l.timer(&s.timer_completion, timer_delay_ms, s, timer_cb); return .disarm; } @@ -2752,10 +2822,14 @@ test "kqueue: timer armed from delayed callback must not fire early" { // Send to the mach port only after the loop has been blocked for a while. const sender = try std.Thread.spawn(.{}, (struct { fn run(port: posix.system.mach_port_name_t) void { - std.Thread.sleep(send_delay_ms * std.time.ns_per_ms); + std.Io.sleep( + std.Io.Threaded.global_single_threaded.io(), + .fromMilliseconds(@intCast(send_delay_ms)), + .awake, + ) catch unreachable; var msg: darwin.mach_msg_header_t = .{ - .msgh_bits = @intFromEnum(posix.system.MACH_MSG_TYPE.MAKE_SEND_ONCE), + .msgh_bits = @intFromEnum(posix.system.MACH.MSG.TYPE.MAKE_SEND_ONCE), .msgh_size = @sizeOf(darwin.mach_msg_header_t), .msgh_remote_port = port, .msgh_local_port = darwin.MACH_PORT_NULL, @@ -2788,7 +2862,6 @@ test "kqueue: timer armed from delayed callback must not fire early" { test "kqueue: socket accept/cancel cancellation should decrease active count" { const mem = std.mem; - const net = std.net; const testing = std.testing; //if (true) return error.SkipZigTest; @@ -2799,11 +2872,11 @@ test "kqueue: socket accept/cancel cancellation should decrease active count" { // Create a TCP server socket const address = try net.Address.parseIp4("127.0.0.1", 3131); const kernel_backlog = 1; - var ln = try posix.socket(address.any.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); - errdefer posix.close(ln); + var ln = try xev_posix.socket(address.any.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); + errdefer xev_posix.close(ln); try posix.setsockopt(ln, posix.SOL.SOCKET, posix.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1))); - try posix.bind(ln, &address.any, address.getOsSockLen()); - try posix.listen(ln, kernel_backlog); + try xev_posix.bind(ln, &address.any, address.getOsSockLen()); + try xev_posix.listen(ln, kernel_backlog); // Accept var server_conn: posix.socket_t = 0; diff --git a/src/backend/wasi_poll.zig b/src/backend/wasi_poll.zig index a3cfd189..5d979f77 100644 --- a/src/backend/wasi_poll.zig +++ b/src/backend/wasi_poll.zig @@ -3,6 +3,10 @@ const builtin = @import("builtin"); const assert = std.debug.assert; const wasi = std.os.wasi; const posix = std.posix; + +pub const ShutdownHow = std.Io.net.ShutdownHow; + +const xev_posix = @import("../posix.zig"); const queue = @import("../queue.zig"); const heap = @import("../heap.zig"); const xev = @import("../main.zig").WasiPoll; @@ -1003,7 +1007,7 @@ pub const Operation = union(OperationType) { shutdown: struct { socket: posix.socket_t, - how: posix.ShutdownHow = .both, + how: ShutdownHow = .both, }, close: struct { @@ -1059,13 +1063,13 @@ pub const ShutdownError = error{ Unexpected, }; -pub const ReadError = Batch.Error || posix.ReadError || posix.PReadError || +pub const ReadError = Batch.Error || xev_posix.ReadError || xev_posix.PReadError || error{ EOF, Unknown, }; -pub const WriteError = Batch.Error || posix.WriteError || posix.PWriteError || +pub const WriteError = Batch.Error || xev_posix.WriteError || xev_posix.PWriteError || error{ Unknown, }; diff --git a/src/bench/async1.zig b/src/bench/async1.zig index e56b2e2e..3e0e7625 100644 --- a/src/bench/async1.zig +++ b/src/bench/async1.zig @@ -1,7 +1,6 @@ const std = @import("std"); const assert = std.debug.assert; const Allocator = std.mem.Allocator; -const Instant = std.time.Instant; const xev = @import("xev"); //const xev = @import("xev").Dynamic; @@ -12,11 +11,11 @@ pub const std_options: std.Options = .{ // Tune-ables pub const NUM_PINGS = 1000 * 1000; -pub fn main() !void { - try run(1); +pub fn main(init: std.process.Init) !void { + try run(1, init.io); } -pub fn run(comptime thread_count: comptime_int) !void { +pub fn run(comptime thread_count: comptime_int, io: std.Io) !void { if (comptime xev.dynamic) try xev.detect(); var loop = try xev.Loop.init(.{}); defer loop.deinit(); @@ -31,12 +30,12 @@ pub fn run(comptime thread_count: comptime_int) !void { threads[i] = try std.Thread.spawn(.{}, Thread.threadMain, .{ctx}); } - const start_time = try Instant.now(); + const start_time = std.Io.Clock.awake.now(io); try loop.run(.until_done); for (&threads) |thr| thr.join(); - const end_time = try Instant.now(); + const end_time = std.Io.Clock.awake.now(io); - const elapsed = @as(f64, @floatFromInt(end_time.since(start_time))); + const elapsed: f64 = @floatFromInt(start_time.durationTo(end_time).nanoseconds); std.log.info("async{d}: {d:.2} seconds ({d:.2}/sec)", .{ thread_count, elapsed / 1e9, diff --git a/src/bench/async2.zig b/src/bench/async2.zig index 823ad917..65ffd506 100644 --- a/src/bench/async2.zig +++ b/src/bench/async2.zig @@ -5,6 +5,6 @@ pub const std_options: std.Options = .{ .log_level = .info, }; -pub fn main() !void { - try run(2); +pub fn main(init: std.process.Init) !void { + try run(2, init.io); } diff --git a/src/bench/async4.zig b/src/bench/async4.zig index 5c0c4ed6..e03cfd94 100644 --- a/src/bench/async4.zig +++ b/src/bench/async4.zig @@ -5,6 +5,6 @@ pub const std_options: std.Options = .{ .log_level = .info, }; -pub fn main() !void { - try run(4); +pub fn main(init: std.process.Init) !void { + try run(4, init.io); } diff --git a/src/bench/async8.zig b/src/bench/async8.zig index d52b2345..97716a2f 100644 --- a/src/bench/async8.zig +++ b/src/bench/async8.zig @@ -5,6 +5,6 @@ pub const std_options: std.Options = .{ .log_level = .info, }; -pub fn main() !void { - try run(8); +pub fn main(init: std.process.Init) !void { + try run(8, init.io); } diff --git a/src/bench/async_pummel_1.zig b/src/bench/async_pummel_1.zig index 44a2d2db..9fabea80 100644 --- a/src/bench/async_pummel_1.zig +++ b/src/bench/async_pummel_1.zig @@ -1,7 +1,6 @@ const std = @import("std"); const assert = std.debug.assert; const Allocator = std.mem.Allocator; -const Instant = std.time.Instant; const xev = @import("xev"); //const xev = @import("xev").Dynamic; @@ -12,11 +11,11 @@ pub const std_options: std.Options = .{ // Tune-ables pub const NUM_PINGS = 1000 * 1000; -pub fn main() !void { - try run(1); +pub fn main(init: std.process.Init) !void { + try run(1, init.io); } -pub fn run(comptime thread_count: comptime_int) !void { +pub fn run(comptime thread_count: comptime_int, io: std.Io) !void { var thread_pool = xev.ThreadPool.init(.{}); defer thread_pool.deinit(); defer thread_pool.shutdown(); @@ -42,12 +41,12 @@ pub fn run(comptime thread_count: comptime_int) !void { thr.* = try std.Thread.spawn(.{}, threadMain, .{}); } - const start_time = try Instant.now(); + const start_time = std.Io.Clock.awake.now(io); try loop.run(.until_done); for (&threads) |thr| thr.join(); - const end_time = try Instant.now(); + const end_time = std.Io.Clock.awake.now(io); - const elapsed = @as(f64, @floatFromInt(end_time.since(start_time))); + const elapsed: f64 = @floatFromInt(start_time.durationTo(end_time).nanoseconds); std.log.info("async_pummel_{d}: {d} callbacks in {d:.2} seconds ({d:.2}/sec)", .{ thread_count, callbacks, @@ -73,7 +72,7 @@ fn asyncCallback( // We're done state = .stop; - while (state != .stopped) std.Thread.sleep(0); + while (state != .stopped) std.atomic.spinLoopHint(); return .disarm; } diff --git a/src/bench/async_pummel_2.zig b/src/bench/async_pummel_2.zig index 48381c4b..13764b29 100644 --- a/src/bench/async_pummel_2.zig +++ b/src/bench/async_pummel_2.zig @@ -5,6 +5,6 @@ pub const std_options: std.Options = .{ .log_level = .info, }; -pub fn main() !void { - try run(2); +pub fn main(init: std.process.Init) !void { + try run(2, init.io); } diff --git a/src/bench/async_pummel_4.zig b/src/bench/async_pummel_4.zig index 88e95cb7..d2b18895 100644 --- a/src/bench/async_pummel_4.zig +++ b/src/bench/async_pummel_4.zig @@ -5,6 +5,6 @@ pub const std_options: std.Options = .{ .log_level = .info, }; -pub fn main() !void { - try run(4); +pub fn main(init: std.process.Init) !void { + try run(4, init.io); } diff --git a/src/bench/async_pummel_8.zig b/src/bench/async_pummel_8.zig index 68aa990e..fa685925 100644 --- a/src/bench/async_pummel_8.zig +++ b/src/bench/async_pummel_8.zig @@ -5,6 +5,6 @@ pub const std_options: std.Options = .{ .log_level = .info, }; -pub fn main() !void { - try run(8); +pub fn main(init: std.process.Init) !void { + try run(8, init.io); } diff --git a/src/bench/million-timers.zig b/src/bench/million-timers.zig index eec71f22..2a539b0f 100644 --- a/src/bench/million-timers.zig +++ b/src/bench/million-timers.zig @@ -1,11 +1,13 @@ const std = @import("std"); -const Instant = std.time.Instant; const xev = @import("xev"); //const xev = @import("xev").Dynamic; pub const NUM_TIMERS: usize = 10 * 1000 * 1000; -pub fn main() !void { +pub fn main(init: std.process.Init) !void { + const alloc = init.gpa; + const io = init.io; + var thread_pool = xev.ThreadPool.init(.{}); defer thread_pool.deinit(); defer thread_pool.shutdown(); @@ -17,15 +19,11 @@ pub fn main() !void { }); defer loop.deinit(); - const GPA = std.heap.GeneralPurposeAllocator(.{}); - var gpa: GPA = .{}; - defer _ = gpa.deinit(); - const alloc = gpa.allocator(); - var cs = try alloc.alloc(xev.Completion, NUM_TIMERS); defer alloc.free(cs); - const before_all = try Instant.now(); + const clock = std.Io.Clock.awake; + const before_all = clock.now(io); var i: usize = 0; var timeout: u64 = 1; while (i < NUM_TIMERS) : (i += 1) { @@ -34,15 +32,21 @@ pub fn main() !void { timer.run(&loop, &cs[i], timeout, void, null, timerCallback); } - const before_run = try Instant.now(); + const before_run = clock.now(io); try loop.run(.until_done); - const after_run = try Instant.now(); - const after_all = try Instant.now(); + const after_run = clock.now(io); + const after_all = clock.now(io); + + const dur = struct { + fn ns(from: std.Io.Timestamp, to: std.Io.Timestamp) f64 { + return @floatFromInt(from.durationTo(to).nanoseconds); + } + }.ns; - std.log.info("{d:.2} seconds total", .{@as(f64, @floatFromInt(after_all.since(before_all))) / 1e9}); - std.log.info("{d:.2} seconds init", .{@as(f64, @floatFromInt(before_run.since(before_all))) / 1e9}); - std.log.info("{d:.2} seconds dispatch", .{@as(f64, @floatFromInt(after_run.since(before_run))) / 1e9}); - std.log.info("{d:.2} seconds cleanup", .{@as(f64, @floatFromInt(after_all.since(after_run))) / 1e9}); + std.log.info("{d:.2} seconds total", .{dur(before_all, after_all) / 1e9}); + std.log.info("{d:.2} seconds init", .{dur(before_all, before_run) / 1e9}); + std.log.info("{d:.2} seconds dispatch", .{dur(before_run, after_run) / 1e9}); + std.log.info("{d:.2} seconds cleanup", .{dur(after_run, after_all) / 1e9}); } pub const std_options: std.Options = .{ diff --git a/src/bench/ping-pongs.zig b/src/bench/ping-pongs.zig index 1ab7d2aa..3a8c4f86 100644 --- a/src/bench/ping-pongs.zig +++ b/src/bench/ping-pongs.zig @@ -1,15 +1,19 @@ const std = @import("std"); const assert = std.debug.assert; const Allocator = std.mem.Allocator; -const Instant = std.time.Instant; const xev = @import("xev"); //const xev = @import("xev").Dynamic; + + pub const std_options: std.Options = .{ .log_level = .info, }; -pub fn main() !void { +pub fn main(init: std.process.Init) !void { + const alloc = init.gpa; + const io = init.io; + var thread_pool = xev.ThreadPool.init(.{}); defer thread_pool.deinit(); defer thread_pool.shutdown(); @@ -21,11 +25,6 @@ pub fn main() !void { }); defer loop.deinit(); - const GPA = std.heap.GeneralPurposeAllocator(.{}); - var gpa: GPA = .{}; - defer _ = gpa.deinit(); - const alloc = gpa.allocator(); - var server_loop = try xev.Loop.init(.{ .entries = std.math.pow(u13, 2, 12), .thread_pool = &thread_pool, @@ -50,12 +49,13 @@ pub fn main() !void { defer client.deinit(); try client.start(); - const start_time = try Instant.now(); + const clock = std.Io.Clock.awake; + const start_time = clock.now(io); try client_loop.run(.until_done); server_thr.join(); - const end_time = try Instant.now(); + const end_time = clock.now(io); - const elapsed = @as(f64, @floatFromInt(end_time.since(start_time))); + const elapsed: f64 = @floatFromInt(start_time.durationTo(end_time).nanoseconds); std.log.info("{d:.2} roundtrips/s", .{@as(f64, @floatFromInt(client.pongs)) / (elapsed / 1e9)}); std.log.info("{d:.2} seconds total", .{elapsed / 1e9}); } @@ -68,9 +68,10 @@ const TCPPool = std.heap.MemoryPool(xev.TCP); /// The client state const Client = struct { loop: *xev.Loop, - completion_pool: CompletionPool, - read_buf: [1024]u8, - pongs: u64, + alloc: Allocator, + completion_pool: CompletionPool = .empty, + read_buf: [1024]u8 = undefined, + pongs: u64 = 0, state: usize = 0, stop: bool = false, @@ -79,24 +80,20 @@ const Client = struct { pub fn init(alloc: Allocator, loop: *xev.Loop) !Client { return .{ .loop = loop, - .completion_pool = CompletionPool.init(alloc), - .read_buf = undefined, - .pongs = 0, - .state = 0, - .stop = false, + .alloc = alloc, }; } pub fn deinit(self: *Client) void { - self.completion_pool.deinit(); + self.completion_pool.deinit(self.alloc); } /// Must be called with stable self pointer. pub fn start(self: *Client) !void { - const addr = try std.net.Address.parseIp4("127.0.0.1", 3131); + const addr = try std.Io.net.IpAddress.parse("127.0.0.1", 3131); const socket = try xev.TCP.init(addr); - const c = try self.completion_pool.create(); + const c = try self.completion_pool.create(self.alloc); socket.connect(self.loop, c, addr, Client, self, connectCallback); } @@ -115,7 +112,7 @@ const Client = struct { socket.write(l, c, .{ .slice = PING[0..PING.len] }, Client, self, writeCallback); // Read - const c_read = self.completion_pool.create() catch unreachable; + const c_read = self.completion_pool.create(self.alloc) catch unreachable; socket.read(l, c_read, .{ .slice = &self.read_buf }, Client, self, readCallback); return .disarm; } @@ -165,7 +162,7 @@ const Client = struct { } // Send another ping - const c_ping = self.completion_pool.create() catch unreachable; + const c_ping = self.completion_pool.create(self.alloc) catch unreachable; socket.write(l, c_ping, .{ .slice = PING[0..PING.len] }, Client, self, writeCallback); } } @@ -209,35 +206,33 @@ const Client = struct { /// The server state const Server = struct { loop: *xev.Loop, - buffer_pool: BufferPool, - completion_pool: CompletionPool, - socket_pool: TCPPool, - stop: bool, + alloc: Allocator, + buffer_pool: BufferPool = .empty, + completion_pool: CompletionPool = .empty, + socket_pool: TCPPool = .empty, + stop: bool = false, pub fn init(alloc: Allocator, loop: *xev.Loop) !Server { return .{ .loop = loop, - .buffer_pool = BufferPool.init(alloc), - .completion_pool = CompletionPool.init(alloc), - .socket_pool = TCPPool.init(alloc), - .stop = false, + .alloc = alloc, }; } pub fn deinit(self: *Server) void { - self.buffer_pool.deinit(); - self.completion_pool.deinit(); - self.socket_pool.deinit(); + self.buffer_pool.deinit(self.alloc); + self.completion_pool.deinit(self.alloc); + self.socket_pool.deinit(self.alloc); } /// Must be called with stable self pointer. pub fn start(self: *Server) !void { - const addr = try std.net.Address.parseIp4("127.0.0.1", 3131); + const addr = try std.Io.net.IpAddress.parse("127.0.0.1", 3131); var socket = try xev.TCP.init(addr); - const c = try self.completion_pool.create(); + const c = try self.completion_pool.create(self.alloc); try socket.bind(addr); - try socket.listen(std.os.linux.SOMAXCONN); + try socket.listen(128); socket.accept(self.loop, c, Server, self, acceptCallback); } @@ -262,11 +257,11 @@ const Server = struct { const self = self_.?; // Create our socket - const socket = self.socket_pool.create() catch unreachable; + const socket = self.socket_pool.create(self.alloc) catch unreachable; socket.* = r catch unreachable; // Start reading -- we can reuse c here because its done. - const buf = self.buffer_pool.create() catch unreachable; + const buf = self.buffer_pool.create(self.alloc) catch unreachable; socket.read(l, c, .{ .slice = buf }, Server, self, readCallback); return .disarm; } @@ -296,8 +291,8 @@ const Server = struct { }; // Echo it back - const c_echo = self.completion_pool.create() catch unreachable; - const buf_write = self.buffer_pool.create() catch unreachable; + const c_echo = self.completion_pool.create(self.alloc) catch unreachable; + const buf_write = self.buffer_pool.create(self.alloc) catch unreachable; @memcpy(buf_write, buf.slice[0..n]); socket.write(loop, c_echo, .{ .slice = buf_write[0..n] }, Server, self, writeCallback); diff --git a/src/bench/ping-udp1.zig b/src/bench/ping-udp1.zig index 47149352..74031873 100644 --- a/src/bench/ping-udp1.zig +++ b/src/bench/ping-udp1.zig @@ -2,7 +2,6 @@ const std = @import("std"); const builtin = @import("builtin"); const assert = std.debug.assert; const Allocator = std.mem.Allocator; -const Instant = std.time.Instant; const xev = @import("xev"); //const xev = @import("xev").Dynamic; @@ -10,11 +9,11 @@ pub const std_options: std.Options = .{ .log_level = .info, }; -pub fn main() !void { - try run(1); +pub fn main(init: std.process.Init) !void { + try run(1, init.io); } -pub fn run(comptime count: comptime_int) !void { +pub fn run(comptime count: comptime_int, io: std.Io) !void { var thread_pool = xev.ThreadPool.init(.{}); defer thread_pool.deinit(); defer thread_pool.shutdown(); @@ -26,7 +25,7 @@ pub fn run(comptime count: comptime_int) !void { }); defer loop.deinit(); - const addr = try std.net.Address.parseIp4("127.0.0.1", 3131); + const addr = try std.Io.net.IpAddress.parse("127.0.0.1", 3131); var pingers: [count]Pinger = undefined; for (&pingers) |*p| { @@ -34,9 +33,9 @@ pub fn run(comptime count: comptime_int) !void { try p.start(&loop); } - const start_time = try Instant.now(); + const start_time = std.Io.Clock.awake.now(io); try loop.run(.until_done); - const end_time = try Instant.now(); + const end_time = std.Io.Clock.awake.now(io); const total: usize = total: { var total: usize = 0; @@ -44,7 +43,7 @@ pub fn run(comptime count: comptime_int) !void { break :total total; }; - const elapsed = @as(f64, @floatFromInt(end_time.since(start_time))); + const elapsed: f64 = @floatFromInt(start_time.durationTo(end_time).nanoseconds); std.log.info("ping_pongs: {d} pingers, ~{d:.0} roundtrips/s", .{ count, @as(f64, @floatFromInt(total)) / (elapsed / 1e9), @@ -53,7 +52,7 @@ pub fn run(comptime count: comptime_int) !void { const Pinger = struct { udp: xev.UDP, - addr: std.net.Address, + addr: std.Io.net.IpAddress, state: usize = 0, pongs: u64 = 0, read_buf: [1024]u8 = undefined, @@ -65,7 +64,7 @@ const Pinger = struct { pub const PING = "PING\n"; - pub fn init(addr: std.net.Address) !Pinger { + pub fn init(addr: std.Io.net.IpAddress) !Pinger { return .{ .udp = try xev.UDP.init(addr), .state = 0, @@ -108,7 +107,7 @@ const Pinger = struct { loop: *xev.Loop, c: *xev.Completion, _: *xev.UDP.State, - _: std.net.Address, + _: std.Io.net.IpAddress, socket: xev.UDP, buf: xev.ReadBuffer, r: xev.ReadError!usize, diff --git a/src/bench/udp_pummel_1v1.zig b/src/bench/udp_pummel_1v1.zig index b7dba8a0..cdaad333 100644 --- a/src/bench/udp_pummel_1v1.zig +++ b/src/bench/udp_pummel_1v1.zig @@ -1,7 +1,6 @@ const std = @import("std"); const assert = std.debug.assert; const Allocator = std.mem.Allocator; -const Instant = std.time.Instant; const xev = @import("xev"); //const xev = @import("xev").Dynamic; @@ -17,11 +16,11 @@ pub const std_options: std.Options = .{ .log_level = .info, }; -pub fn main() !void { - try run(1, 1); +pub fn main(init: std.process.Init) !void { + try run(1, 1, init.io); } -pub fn run(comptime n_senders: comptime_int, comptime n_receivers: comptime_int) !void { +pub fn run(comptime n_senders: comptime_int, comptime n_receivers: comptime_int, io: std.Io) !void { const base_port = 12345; var thread_pool = xev.ThreadPool.init(.{}); @@ -37,7 +36,7 @@ pub fn run(comptime n_senders: comptime_int, comptime n_receivers: comptime_int) var receivers: [n_receivers]Receiver = undefined; for (&receivers, 0..) |*r, i| { - const addr = try std.net.Address.parseIp4("127.0.0.1", @as(u16, @intCast(base_port + i))); + const addr = try std.Io.net.IpAddress.parse("127.0.0.1", @as(u16, @intCast(base_port + i))); r.* = .{ .udp = try xev.UDP.init(addr) }; try r.udp.bind(addr); r.udp.read( @@ -53,7 +52,7 @@ pub fn run(comptime n_senders: comptime_int, comptime n_receivers: comptime_int) var senders: [n_senders]Sender = undefined; for (&senders, 0..) |*s, i| { - const addr = try std.net.Address.parseIp4( + const addr = try std.Io.net.IpAddress.parse( "127.0.0.1", @as(u16, @intCast(base_port + (i % n_receivers))), ); @@ -70,11 +69,11 @@ pub fn run(comptime n_senders: comptime_int, comptime n_receivers: comptime_int) ); } - const start_time = try Instant.now(); + const start_time = std.Io.Clock.awake.now(io); try loop.run(.until_done); - const end_time = try Instant.now(); + const end_time = std.Io.Clock.awake.now(io); - const elapsed = @as(f64, @floatFromInt(end_time.since(start_time))); + const elapsed: f64 = @floatFromInt(start_time.durationTo(end_time).nanoseconds); std.log.info("udp_pummel_{d}v{d}: {d:.0}f/s received, {d:.0}f/s sent, {d} received, {d} sent in {d:.1} seconds", .{ n_senders, n_receivers, @@ -125,7 +124,7 @@ const Receiver = struct { _: *xev.Loop, _: *xev.Completion, _: *xev.UDP.State, - _: std.net.Address, + _: std.Io.net.IpAddress, _: xev.UDP, b: xev.ReadBuffer, r: xev.ReadError!usize, diff --git a/src/dynamic.zig b/src/dynamic.zig index 7e5c497c..51f438bf 100644 --- a/src/dynamic.zig +++ b/src/dynamic.zig @@ -427,18 +427,15 @@ fn DynamicPollEvent(comptime xev: type) type { /// Preserves the same backing type and integer values for the /// subset making it easy to convert between the two. fn EnumSubset(comptime T: type, comptime values: []const T) type { - var fields: [values.len]std.builtin.Type.EnumField = undefined; - for (values, 0..) |value, i| fields[i] = .{ - .name = @tagName(value), - .value = @intFromEnum(value), - }; + const tag_type = @typeInfo(T).@"enum".tag_type; + var field_names: [values.len][:0]const u8 = undefined; + var field_values: [values.len]tag_type = undefined; + for (values, 0..) |value, i| { + field_names[i] = @tagName(value); + field_values[i] = @intFromEnum(value); + } - return @Type(.{ .@"enum" = .{ - .tag_type = @typeInfo(T).@"enum".tag_type, - .fields = &fields, - .decls = &.{}, - .is_exhaustive = true, - } }); + return @Enum(tag_type, .exhaustive, &field_names, &field_values); } /// Creates a union type that can hold the implementation of a given @@ -462,16 +459,16 @@ fn Union( // below using this variable for more info. var largest: usize = 0; - var fields: [bes.len + 1]std.builtin.Type.UnionField = undefined; + var field_names: [bes.len + 1][:0]const u8 = undefined; + var field_types: [bes.len + 1]type = undefined; + var field_attrs: [bes.len + 1]std.builtin.Type.UnionField.Attributes = @splat(.{}); for (bes, 0..) |be, i| { var T: type = be.Api(); for (field) |f| T = @field(T, f); largest = @max(largest, @sizeOf(T)); - fields[i] = .{ - .name = @tagName(be), - .type = T, - .alignment = @alignOf(T), - }; + field_names[i] = @tagName(be); + field_types[i] = T; + field_attrs[i] = .{ .@"align" = @alignOf(T) }; } // If our union only has zero-sized types, we need to add some @@ -482,25 +479,19 @@ fn Union( // our examples can build with Dynamic then we're good. var count: usize = bes.len; if (largest == 0) { - fields[count] = .{ - .name = "_zig_bug_padding", - .type = u8, - .alignment = @alignOf(u8), - }; + field_names[count] = "_zig_bug_padding"; + field_types[count] = u8; + field_attrs[count] = .{ .@"align" = @alignOf(u8) }; count += 1; } - return @Type(.{ - .@"union" = .{ - .layout = .auto, - .tag_type = if (tagged) EnumSubset( - AllBackend, - bes, - ) else null, - .fields = fields[0..count], - .decls = &.{}, - }, - }); + return @Union( + .auto, + if (tagged) EnumSubset(AllBackend, bes) else null, + field_names[0..count], + field_types[0..count], + field_attrs[0..count], + ); } /// Create a new error set from a list of error sets within diff --git a/src/linux/timerfd.zig b/src/linux/timerfd.zig index 1bb73262..92879c4a 100644 --- a/src/linux/timerfd.zig +++ b/src/linux/timerfd.zig @@ -1,6 +1,7 @@ const std = @import("std"); const linux = std.os.linux; const posix = std.posix; +const xev_posix = @import("../posix.zig"); /// Timerfd is a wrapper around the timerfd system calls. See the /// timerfd_create man page for information on timerfd and associated @@ -26,7 +27,7 @@ pub const Timerfd = struct { } pub fn deinit(self: *const Timerfd) void { - posix.close(self.fd); + xev_posix.close(self.fd); } /// timerfd_settime diff --git a/src/posix.zig b/src/posix.zig new file mode 100644 index 00000000..81e32104 --- /dev/null +++ b/src/posix.zig @@ -0,0 +1,694 @@ +//! A lot of compatibility shims over the years of Zig updates +//! for removed functions from Zig 0.15, 0.16, etc. + +const std = @import("std"); +const builtin = @import("builtin"); +const posix = std.posix; +const system = posix.system; +const maxInt = std.math.maxInt; + +pub const net = struct { + pub const Address = extern union { + any: posix.sockaddr, + in: posix.sockaddr.in, + in6: posix.sockaddr.in6, + + pub fn parseIp4(text: []const u8, port: u16) !Address { + return initIp4((try std.Io.net.Ip4Address.parse(text, port)).bytes, port); + } + + pub fn parseIp6(text: []const u8, port: u16) !Address { + const ip6 = try std.Io.net.Ip6Address.parse(text, port); + var result: Address = undefined; + result.in6 = .{ + .port = std.mem.nativeToBig(u16, ip6.port), + .flowinfo = ip6.flow, + .addr = ip6.bytes, + .scope_id = ip6.interface.index, + }; + return result; + } + + pub fn initIp4(bytes: [4]u8, port: u16) Address { + var result: Address = undefined; + result.in = .{ + .port = std.mem.nativeToBig(u16, port), + .addr = @bitCast(bytes), + }; + return result; + } + + pub fn initPosix(addr: *const posix.sockaddr) Address { + var result: Address = undefined; + switch (addr.family) { + posix.AF.INET => result.in = (@as(*const posix.sockaddr.in, @ptrCast(@alignCast(addr)))).*, + posix.AF.INET6 => result.in6 = (@as(*const posix.sockaddr.in6, @ptrCast(@alignCast(addr)))).*, + else => result.any = addr.*, + } + return result; + } + + pub fn getOsSockLen(self: Address) posix.socklen_t { + return switch (self.any.family) { + posix.AF.INET => @sizeOf(posix.sockaddr.in), + posix.AF.INET6 => @sizeOf(posix.sockaddr.in6), + else => @sizeOf(posix.sockaddr), + }; + } + + pub fn toIpAddress(self: Address) std.Io.net.IpAddress { + return switch (self.any.family) { + posix.AF.INET => .{ .ip4 = .{ + .bytes = @bitCast(self.in.addr), + .port = std.mem.bigToNative(u16, self.in.port), + } }, + posix.AF.INET6 => .{ .ip6 = .{ + .bytes = self.in6.addr, + .port = std.mem.bigToNative(u16, self.in6.port), + .flow = self.in6.flowinfo, + .interface = .{ .index = self.in6.scope_id }, + } }, + else => unreachable, + }; + } + + pub fn fromIpAddress(addr: std.Io.net.IpAddress) Address { + return switch (addr) { + .ip4 => |ip4| initIp4(ip4.bytes, ip4.port), + .ip6 => |ip6| fromIp6Address(ip6), + }; + } + + pub fn fromIp6Address(ip6: std.Io.net.Ip6Address) Address { + var result: Address = undefined; + result.in6 = .{ + .port = std.mem.nativeToBig(u16, ip6.port), + .flowinfo = ip6.flow, + .addr = ip6.bytes, + .scope_id = ip6.interface.index, + }; + return result; + } + }; +}; + +pub const ReadError = error{ + AccessDenied, + InputOutput, + IsDir, + NotOpenForReading, + ProcessNotFound, + SocketNotConnected, + SystemResources, + WouldBlock, + ConnectionResetByPeer, + ConnectionTimedOut, +} || posix.UnexpectedError; + +pub const PReadError = ReadError || error{Unseekable}; + +pub const WriteError = error{ + AccessDenied, + BrokenPipe, + DeviceBusy, + DiskQuota, + FileTooBig, + InputOutput, + InvalidArgument, + MessageTooBig, + NoSpaceLeft, + NotOpenForWriting, + PermissionDenied, + ProcessNotFound, + SystemResources, + WouldBlock, + ConnectionResetByPeer, +} || posix.UnexpectedError; + +pub const PWriteError = WriteError || error{Unseekable}; + +pub const SendError = error{ + AccessDenied, + AddressFamilyNotSupported, + AddressNotAvailable, + BrokenPipe, + ConnectionRefused, + ConnectionResetByPeer, + FileNotFound, + MessageTooBig, + NameTooLong, + NetworkSubsystemFailed, + NetworkUnreachable, + NotDir, + SocketNotConnected, + SymLinkLoop, + SystemResources, + UnreachableAddress, + WouldBlock, +} || posix.UnexpectedError; + +pub const RecvFromError = error{ + ConnectionRefused, + ConnectionResetByPeer, + ConnectionTimedOut, + MessageTooBig, + NetworkSubsystemFailed, + SocketNotBound, + SocketNotConnected, + SystemResources, + WouldBlock, +} || posix.UnexpectedError; + +pub const SocketError = error{ + AccessDenied, + AddressFamilyNotSupported, + ProcessFdQuotaExceeded, + ProtocolNotSupported, + SocketTypeNotSupported, + SystemFdQuotaExceeded, + SystemResources, +} || posix.UnexpectedError; + +pub const BindError = error{ + AccessDenied, + AddressFamilyNotSupported, + AddressInUse, + AddressNotAvailable, + AlreadyBound, + FileNotFound, + NameTooLong, + NotDir, + ReadOnlyFileSystem, + SymLinkLoop, + SystemResources, +} || posix.UnexpectedError; + +pub const ListenError = error{ + AddressInUse, + FileDescriptorNotASocket, + OperationNotSupported, + SocketNotBound, + SystemResources, +} || posix.UnexpectedError; + +pub const PipeError = error{ + ProcessFdQuotaExceeded, + SystemFdQuotaExceeded, +} || posix.UnexpectedError; + +pub const AcceptError = std.Io.net.Server.AcceptError; + +pub const GetSockNameError = error{ + FileDescriptorNotASocket, + SocketNotBound, + SystemResources, +} || posix.UnexpectedError; + +pub const ConnectError = error{ + AccessDenied, + AddressFamilyNotSupported, + AddressInUse, + AddressNotAvailable, + ConnectionPending, + ConnectionRefused, + ConnectionResetByPeer, + ConnectionTimedOut, + FileNotFound, + NetworkUnreachable, + PermissionDenied, + SystemResources, + WouldBlock, +} || posix.UnexpectedError; + +pub fn connect(sock: posix.socket_t, sock_addr: *const posix.sockaddr, len: posix.socklen_t) ConnectError!void { + while (true) { + switch (posix.errno(system.connect(sock, sock_addr, len))) { + .SUCCESS => return, + .ACCES => return error.AccessDenied, + .PERM => return error.PermissionDenied, + .ADDRINUSE => return error.AddressInUse, + .ADDRNOTAVAIL => return error.AddressNotAvailable, + .AFNOSUPPORT => return error.AddressFamilyNotSupported, + .AGAIN, .INPROGRESS => return error.WouldBlock, + .ALREADY => return error.ConnectionPending, + .CONNREFUSED => return error.ConnectionRefused, + .CONNRESET => return error.ConnectionResetByPeer, + .INTR => continue, + .HOSTUNREACH, .NETUNREACH => return error.NetworkUnreachable, + .TIMEDOUT => return error.ConnectionTimedOut, + .NOENT => return error.FileNotFound, + .BADF, .FAULT, .ISCONN, .NOTSOCK, .PROTOTYPE, .CONNABORTED => unreachable, + else => |err| return posix.unexpectedErrno(err), + } + } +} + +pub fn getsockoptError(sockfd: posix.fd_t) ConnectError!void { + const E = std.posix.E; + const SOL = if (@hasDecl(std.posix, "SOL")) std.posix.SOL else std.os.linux.SOL; + const SO = if (@hasDecl(std.posix, "SO")) std.posix.SO else std.os.linux.SO; + var err_code: i32 = undefined; + var size: u32 = @sizeOf(u32); + const rc = system.getsockopt(sockfd, SOL.SOCKET, SO.ERROR, @ptrCast(&err_code), &size); + std.debug.assert(size == 4); + switch (posix.errno(rc)) { + .SUCCESS => switch (@as(E, @enumFromInt(err_code))) { + .SUCCESS => return, + .ACCES => return error.AccessDenied, + .PERM => return error.PermissionDenied, + .ADDRINUSE => return error.AddressInUse, + .ADDRNOTAVAIL => return error.AddressNotAvailable, + .AFNOSUPPORT => return error.AddressFamilyNotSupported, + .AGAIN => return error.SystemResources, + .ALREADY => return error.ConnectionPending, + .CONNREFUSED => return error.ConnectionRefused, + .HOSTUNREACH, .NETUNREACH => return error.NetworkUnreachable, + .TIMEDOUT => return error.ConnectionTimedOut, + .CONNRESET => return error.ConnectionResetByPeer, + .BADF, .FAULT, .ISCONN, .NOTSOCK, .PROTOTYPE => unreachable, + else => |err| return posix.unexpectedErrno(err), + }, + .BADF, .FAULT, .INVAL => unreachable, + .NOPROTOOPT, .NOTSOCK => unreachable, + else => |err| return posix.unexpectedErrno(err), + } +} + +pub fn dup(old_fd: posix.fd_t) !posix.fd_t { + const rc = system.dup(old_fd); + return switch (posix.errno(rc)) { + .SUCCESS => return @intCast(rc), + .MFILE => error.ProcessFdQuotaExceeded, + .BADF => unreachable, + else => |err| return posix.unexpectedErrno(err), + }; +} + +pub fn close(fd: posix.fd_t) void { + switch (posix.errno(system.close(fd))) { + .SUCCESS, .INTR => {}, + .BADF => unreachable, + else => unreachable, + } +} + +pub fn setCloexec(fd: posix.fd_t) !void { + while (true) switch (posix.errno(system.fcntl(fd, posix.F.SETFD, @as(usize, posix.FD_CLOEXEC)))) { + .SUCCESS => return, + .INTR => continue, + else => |err| return posix.unexpectedErrno(err), + }; +} + +fn getStatusFlags(fd: posix.fd_t) !u32 { + while (true) { + const rc = system.fcntl(fd, posix.F.GETFL, @as(usize, 0)); + switch (posix.errno(rc)) { + .SUCCESS => return @intCast(rc), + .INTR => continue, + else => |err| return posix.unexpectedErrno(err), + } + } +} + +fn setStatusFlags(fd: posix.fd_t, flags: u32) !void { + while (true) switch (posix.errno(system.fcntl(fd, posix.F.SETFL, flags))) { + .SUCCESS => return, + .INTR => continue, + else => |err| return posix.unexpectedErrno(err), + }; +} + +fn setSockFlags(sock: posix.socket_t, flags: u32) !void { + if ((flags & posix.SOCK.CLOEXEC) != 0) try setCloexec(sock); + if ((flags & posix.SOCK.NONBLOCK) != 0) { + const current = try getStatusFlags(sock); + try setStatusFlags(sock, current | @as(u32, @bitCast(posix.O{ .NONBLOCK = true }))); + } +} + +pub fn accept( + sock: posix.socket_t, + addr: *posix.sockaddr, + addr_size: *posix.socklen_t, + flags: u32, +) AcceptError!posix.socket_t { + while (true) { + const rc = system.accept(sock, addr, addr_size); + switch (posix.errno(rc)) { + .SUCCESS => { + const fd: posix.socket_t = @intCast(rc); + if (flags & posix.SOCK.CLOEXEC != 0) try setCloexec(fd); + return fd; + }, + .INTR => continue, + .AGAIN => return error.WouldBlock, + .CONNABORTED => return error.ConnectionAborted, + .MFILE => return error.ProcessFdQuotaExceeded, + .NFILE => return error.SystemFdQuotaExceeded, + .NOBUFS, .NOMEM => return error.SystemResources, + .NETDOWN => return error.NetworkDown, + else => |err| return posix.unexpectedErrno(err), + } + } +} + +pub fn socket(domain: u32, socket_type: u32, protocol: u32) SocketError!posix.socket_t { + const have_sock_flags = !builtin.target.os.tag.isDarwin() and builtin.target.os.tag != .haiku; + const filtered_sock_type = if (have_sock_flags) + socket_type + else + socket_type & ~@as(u32, posix.SOCK.NONBLOCK | posix.SOCK.CLOEXEC); + + const rc = system.socket(domain, filtered_sock_type, protocol); + switch (posix.errno(rc)) { + .SUCCESS => { + const fd: posix.socket_t = @intCast(rc); + errdefer close(fd); + if (!have_sock_flags) try setSockFlags(fd, socket_type); + return fd; + }, + .ACCES => return error.AccessDenied, + .AFNOSUPPORT => return error.AddressFamilyNotSupported, + .INVAL => return error.ProtocolNotSupported, + .MFILE => return error.ProcessFdQuotaExceeded, + .NFILE => return error.SystemFdQuotaExceeded, + .NOBUFS, .NOMEM => return error.SystemResources, + .PROTONOSUPPORT => return error.ProtocolNotSupported, + .PROTOTYPE => return error.SocketTypeNotSupported, + else => |err| return posix.unexpectedErrno(err), + } +} + +pub fn bind(sock: posix.socket_t, addr: *const posix.sockaddr, len: posix.socklen_t) BindError!void { + switch (posix.errno(system.bind(sock, addr, len))) { + .SUCCESS => return, + .ACCES, .PERM => return error.AccessDenied, + .ADDRINUSE => return error.AddressInUse, + .AFNOSUPPORT => return error.AddressFamilyNotSupported, + .ADDRNOTAVAIL => return error.AddressNotAvailable, + .INVAL => return error.AlreadyBound, + .LOOP => return error.SymLinkLoop, + .NAMETOOLONG => return error.NameTooLong, + .NOENT => return error.FileNotFound, + .NOMEM => return error.SystemResources, + .NOTDIR => return error.NotDir, + .ROFS => return error.ReadOnlyFileSystem, + .BADF, .FAULT, .NOTSOCK => unreachable, + else => |err| return posix.unexpectedErrno(err), + } +} + +pub fn listen(sock: posix.socket_t, backlog: u31) ListenError!void { + switch (posix.errno(system.listen(sock, backlog))) { + .SUCCESS => return, + .ADDRINUSE => return error.AddressInUse, + .INVAL => return error.SocketNotBound, + .MFILE, .NFILE, .NOBUFS, .NOMEM => return error.SystemResources, + .NOTSOCK => return error.FileDescriptorNotASocket, + .OPNOTSUPP => return error.OperationNotSupported, + .BADF => unreachable, + else => |err| return posix.unexpectedErrno(err), + } +} + +pub fn getsockname(sock: posix.socket_t, addr: *posix.sockaddr, addrlen: *posix.socklen_t) GetSockNameError!void { + switch (posix.errno(system.getsockname(sock, addr, addrlen))) { + .SUCCESS => return, + .NOTSOCK => return error.FileDescriptorNotASocket, + .NOBUFS => return error.SystemResources, + .BADF, .FAULT, .INVAL => unreachable, + else => |err| return posix.unexpectedErrno(err), + } +} + +pub fn write(fd: posix.fd_t, bytes: []const u8) WriteError!usize { + if (bytes.len == 0) return 0; + + const max_count = switch (builtin.os.tag) { + .linux => 0x7ffff000, + .macos, .ios, .watchos, .tvos, .visionos => maxInt(i32), + else => maxInt(isize), + }; + + while (true) { + const rc = system.write(fd, bytes.ptr, @min(bytes.len, max_count)); + switch (posix.errno(rc)) { + .SUCCESS => return @intCast(rc), + .INTR => continue, + .INVAL => return error.InvalidArgument, + .SRCH => return error.ProcessNotFound, + .AGAIN => return error.WouldBlock, + .BADF => return error.NotOpenForWriting, + .DQUOT => return error.DiskQuota, + .FBIG => return error.FileTooBig, + .IO => return error.InputOutput, + .NOSPC => return error.NoSpaceLeft, + .ACCES => return error.AccessDenied, + .PERM => return error.PermissionDenied, + .PIPE => return error.BrokenPipe, + .CONNRESET => return error.ConnectionResetByPeer, + .BUSY => return error.DeviceBusy, + .MSGSIZE => return error.MessageTooBig, + .NOBUFS, .NOMEM => return error.SystemResources, + .FAULT, .DESTADDRREQ => unreachable, + else => |err| return posix.unexpectedErrno(err), + } + } +} + +pub fn read(fd: posix.fd_t, buf: []u8) ReadError!usize { + if (buf.len == 0) return 0; + + const max_count = switch (builtin.os.tag) { + .linux => 0x7ffff000, + .macos, .ios, .watchos, .tvos, .visionos => maxInt(i32), + else => maxInt(isize), + }; + + while (true) { + const rc = system.read(fd, buf.ptr, @min(buf.len, max_count)); + switch (posix.errno(rc)) { + .SUCCESS => return @intCast(rc), + .INTR => continue, + .SRCH => return error.ProcessNotFound, + .AGAIN => return error.WouldBlock, + .BADF => return error.NotOpenForReading, + .IO => return error.InputOutput, + .ISDIR => return error.IsDir, + .NOBUFS, .NOMEM => return error.SystemResources, + .NOTCONN => return error.SocketNotConnected, + .CONNRESET => return error.ConnectionResetByPeer, + .TIMEDOUT => return error.ConnectionTimedOut, + .FAULT, .INVAL => unreachable, + else => |err| return posix.unexpectedErrno(err), + } + } +} + +pub fn pwrite(fd: posix.fd_t, bytes: []const u8, offset: u64) PWriteError!usize { + if (bytes.len == 0) return 0; + + const max_count = switch (builtin.os.tag) { + .linux => 0x7ffff000, + .macos, .ios, .watchos, .tvos, .visionos => maxInt(i32), + else => maxInt(isize), + }; + + const pwrite_fn = if (builtin.target.os.tag == .linux and !builtin.target.abi.isMusl() and @hasDecl(system, "pwrite64")) system.pwrite64 else system.pwrite; + while (true) { + const rc = pwrite_fn(fd, bytes.ptr, @min(bytes.len, max_count), @bitCast(offset)); + switch (posix.errno(rc)) { + .SUCCESS => return @intCast(rc), + .INTR => continue, + .INVAL => return error.InvalidArgument, + .SRCH => return error.ProcessNotFound, + .AGAIN => return error.WouldBlock, + .BADF => return error.NotOpenForWriting, + .DQUOT => return error.DiskQuota, + .FBIG => return error.FileTooBig, + .IO => return error.InputOutput, + .NOSPC => return error.NoSpaceLeft, + .PERM => return error.PermissionDenied, + .PIPE => return error.BrokenPipe, + .BUSY => return error.DeviceBusy, + .NXIO, .SPIPE, .OVERFLOW => return error.Unseekable, + .NOBUFS, .NOMEM => return error.SystemResources, + .FAULT, .DESTADDRREQ => unreachable, + else => |err| return posix.unexpectedErrno(err), + } + } +} + +pub fn pread(fd: posix.fd_t, buf: []u8, offset: u64) PReadError!usize { + if (buf.len == 0) return 0; + + const max_count = switch (builtin.os.tag) { + .linux => 0x7ffff000, + .macos, .ios, .watchos, .tvos, .visionos => maxInt(i32), + else => maxInt(isize), + }; + + const pread_fn = if (builtin.target.os.tag == .linux and !builtin.target.abi.isMusl() and @hasDecl(system, "pread64")) system.pread64 else system.pread; + while (true) { + const rc = pread_fn(fd, buf.ptr, @min(buf.len, max_count), @bitCast(offset)); + switch (posix.errno(rc)) { + .SUCCESS => return @intCast(rc), + .INTR => continue, + .SRCH => return error.ProcessNotFound, + .AGAIN => return error.WouldBlock, + .BADF => return error.NotOpenForReading, + .IO => return error.InputOutput, + .ISDIR => return error.IsDir, + .NOBUFS, .NOMEM => return error.SystemResources, + .NOTCONN => return error.SocketNotConnected, + .CONNRESET => return error.ConnectionResetByPeer, + .TIMEDOUT => return error.ConnectionTimedOut, + .NXIO, .SPIPE, .OVERFLOW => return error.Unseekable, + .FAULT, .INVAL => unreachable, + else => |err| return posix.unexpectedErrno(err), + } + } +} + +pub fn send(sockfd: posix.socket_t, buf: []const u8, flags: u32) SendError!usize { + return sendto(sockfd, buf, flags, null, 0) catch |err| switch (err) { + error.AddressFamilyNotSupported, + error.AddressNotAvailable, + error.FileNotFound, + error.NameTooLong, + error.NotDir, + error.SymLinkLoop, + error.UnreachableAddress, + => unreachable, + else => |e| return e, + }; +} + +pub fn sendto( + sockfd: posix.socket_t, + buf: []const u8, + flags: u32, + dest_addr: ?*const posix.sockaddr, + addrlen: posix.socklen_t, +) SendError!usize { + while (true) { + const rc = system.sendto(sockfd, buf.ptr, buf.len, flags, dest_addr, addrlen); + switch (posix.errno(rc)) { + .SUCCESS => return @intCast(rc), + .ACCES => return error.AccessDenied, + .AGAIN => return error.WouldBlock, + .CONNREFUSED => return error.ConnectionRefused, + .CONNRESET => return error.ConnectionResetByPeer, + .INTR => continue, + .INVAL => return error.UnreachableAddress, + .MSGSIZE => return error.MessageTooBig, + .NOBUFS, .NOMEM => return error.SystemResources, + .PIPE => return error.BrokenPipe, + .AFNOSUPPORT => return error.AddressFamilyNotSupported, + .LOOP => return error.SymLinkLoop, + .NAMETOOLONG => return error.NameTooLong, + .NOENT => return error.FileNotFound, + .NOTDIR => return error.NotDir, + .HOSTUNREACH, .NETUNREACH => return error.NetworkUnreachable, + .NOTCONN => return error.SocketNotConnected, + .NETDOWN => return error.NetworkSubsystemFailed, + .ADDRNOTAVAIL => return error.AddressNotAvailable, + .BADF, .DESTADDRREQ, .FAULT, .ISCONN, .NOTSOCK, .OPNOTSUPP => unreachable, + else => |err| return posix.unexpectedErrno(err), + } + } +} + +pub const SendMsgError = SendError; + +pub fn sendmsg(sockfd: posix.socket_t, msg: *const std.os.linux.msghdr_const, flags: u32) SendError!usize { + while (true) { + const rc = system.sendmsg(sockfd, @ptrCast(msg), flags); + switch (posix.errno(rc)) { + .SUCCESS => return @intCast(rc), + .ACCES => return error.AccessDenied, + .AGAIN => return error.WouldBlock, + .CONNREFUSED => return error.ConnectionRefused, + .CONNRESET => return error.ConnectionResetByPeer, + .INTR => continue, + .MSGSIZE => return error.MessageTooBig, + .NOBUFS, .NOMEM => return error.SystemResources, + .PIPE => return error.BrokenPipe, + .NOTCONN => return error.SocketNotConnected, + .NETDOWN => return error.NetworkSubsystemFailed, + .NETUNREACH, .HOSTUNREACH => return error.NetworkUnreachable, + .BADF, .DESTADDRREQ, .FAULT, .INVAL, .ISCONN, .NOTSOCK, .OPNOTSUPP => unreachable, + else => |err| return posix.unexpectedErrno(err), + } + } +} + +pub fn recv(sockfd: posix.socket_t, buf: []u8, flags: u32) RecvFromError!usize { + return recvfrom(sockfd, buf, flags, null, null); +} + +pub fn recvfrom( + sockfd: posix.socket_t, + buf: []u8, + flags: u32, + src_addr: ?*posix.sockaddr, + addrlen: ?*posix.socklen_t, +) RecvFromError!usize { + while (true) { + const rc = system.recvfrom(sockfd, buf.ptr, buf.len, flags, src_addr, addrlen); + switch (posix.errno(rc)) { + .SUCCESS => return @intCast(rc), + .NOTCONN => return error.SocketNotConnected, + .INTR => continue, + .AGAIN => return error.WouldBlock, + .NOMEM => return error.SystemResources, + .CONNREFUSED => return error.ConnectionRefused, + .CONNRESET => return error.ConnectionResetByPeer, + .TIMEDOUT => return error.ConnectionTimedOut, + .BADF, .FAULT, .INVAL, .NOTSOCK => unreachable, + else => |err| return posix.unexpectedErrno(err), + } + } +} + +pub fn pipe2(flags: posix.O) PipeError![2]posix.fd_t { + if (!builtin.target.os.tag.isDarwin() and @hasDecl(system, "pipe2")) { + var fds: [2]posix.fd_t = undefined; + switch (posix.errno(system.pipe2(&fds, flags))) { + .SUCCESS => return fds, + .NFILE => return error.SystemFdQuotaExceeded, + .MFILE => return error.ProcessFdQuotaExceeded, + .INVAL, .FAULT => unreachable, + else => |err| return posix.unexpectedErrno(err), + } + } + + var fds: [2]posix.fd_t = undefined; + switch (posix.errno(system.pipe(&fds))) { + .SUCCESS => {}, + .NFILE => return error.SystemFdQuotaExceeded, + .MFILE => return error.ProcessFdQuotaExceeded, + .INVAL, .FAULT => unreachable, + else => |err| return posix.unexpectedErrno(err), + } + errdefer { + close(fds[0]); + close(fds[1]); + } + + if (flags.CLOEXEC) { + try setCloexec(fds[0]); + try setCloexec(fds[1]); + } + + var status_flags = flags; + status_flags.CLOEXEC = false; + const status_flags_int = @as(u32, @bitCast(status_flags)); + if (status_flags_int != 0) { + try setStatusFlags(fds[0], status_flags_int); + try setStatusFlags(fds[1], status_flags_int); + } + + return fds; +} diff --git a/src/watcher/async.zig b/src/watcher/async.zig index 465a84fc..be27d45f 100644 --- a/src/watcher/async.zig +++ b/src/watcher/async.zig @@ -5,6 +5,7 @@ const assert = std.debug.assert; const posix = std.posix; const common = @import("common.zig"); const darwin = @import("../darwin.zig"); +const xev_posix = @import("../posix.zig"); pub fn Async(comptime xev: type) type { if (xev.dynamic) return AsyncDynamic(xev); @@ -56,12 +57,14 @@ fn AsyncEventFd(comptime xev: type) type { 0x100000 | 0x4, // EFD_CLOEXEC | EFD_NONBLOCK ), - // Use std.posix if we can. - else => try std.posix.eventfd( - 0, - std.os.linux.EFD.CLOEXEC | - std.os.linux.EFD.NONBLOCK, - ), + // Use the raw linux syscall. + else => blk: { + const rc = std.os.linux.eventfd(0, std.os.linux.EFD.CLOEXEC | std.os.linux.EFD.NONBLOCK); + break :blk switch (std.posix.errno(rc)) { + .SUCCESS => @as(std.posix.fd_t, @intCast(rc)), + else => |err| return std.posix.unexpectedErrno(err), + }; + }, }, }; } @@ -69,7 +72,7 @@ fn AsyncEventFd(comptime xev: type) type { /// Clean up the async. This will forcibly deinitialize any resources /// and may result in erroneous wait callbacks to be fired. pub fn deinit(self: *Self) void { - std.posix.close(self.fd); + xev_posix.close(self.fd); } /// Wait for a message on this async. Note that async messages may be @@ -202,7 +205,7 @@ fn AsyncEventFd(comptime xev: type) type { pub fn notify(self: Self) !void { // We want to just write "1" in the correct byte order as our host. const val = @as([8]u8, @bitCast(@as(u64, 1))); - _ = posix.write(self.fd, &val) catch |err| switch (err) { + _ = xev_posix.write(self.fd, &val) catch |err| switch (err) { error.WouldBlock => return, else => return err, }; @@ -253,7 +256,7 @@ fn AsyncMachPort(comptime xev: type) type { var mach_port: posix.system.mach_port_name_t = undefined; switch (darwin.getKernError(posix.system.mach_port_allocate( mach_self, - @intFromEnum(posix.system.MACH_PORT_RIGHT.RECEIVE), + posix.system.MACH.PORT.RIGHT.RECEIVE, &mach_port, ))) { .SUCCESS => {}, // Success @@ -266,7 +269,7 @@ fn AsyncMachPort(comptime xev: type) type { mach_self, mach_port, mach_port, - @intFromEnum(posix.system.MACH_MSG_TYPE.MAKE_SEND), + posix.system.MACH.MSG.TYPE.MAKE_SEND, ))) { .SUCCESS => {}, // Success else => return error.MachPortAllocFailed, @@ -399,7 +402,7 @@ fn AsyncMachPort(comptime xev: type) type { var msg: darwin.mach_msg_header_t = .{ // We use COPY_SEND which will not increment any send ref // counts because it'll reuse the existing send right. - .msgh_bits = @intFromEnum(posix.system.MACH_MSG_TYPE.COPY_SEND), + .msgh_bits = @intFromEnum(posix.system.MACH.MSG.TYPE.COPY_SEND), .msgh_size = @sizeOf(darwin.mach_msg_header_t), .msgh_remote_port = self.port, .msgh_local_port = darwin.MACH_PORT_NULL, @@ -534,7 +537,7 @@ fn AsyncIOCP(comptime xev: type) type { pub const WaitError = xev.Sys.AsyncError; - guard: std.Thread.Mutex = .{}, + guard: std.Io.Mutex = .init, wakeup: bool = false, waiter: ?struct { loop: *xev.Loop, @@ -583,8 +586,9 @@ fn AsyncIOCP(comptime xev: type) type { }; loop.add(c); - self.guard.lock(); - defer self.guard.unlock(); + const io = std.Io.Threaded.global_single_threaded.io(); + self.guard.lockUncancelable(io); + defer self.guard.unlock(io); self.waiter = .{ .loop = loop, @@ -595,8 +599,9 @@ fn AsyncIOCP(comptime xev: type) type { } pub fn notify(self: *Self) !void { - self.guard.lock(); - defer self.guard.unlock(); + const io = std.Io.Threaded.global_single_threaded.io(); + self.guard.lockUncancelable(io); + defer self.guard.unlock(io); if (self.waiter) |w| { w.loop.async_notify(w.c); diff --git a/src/watcher/file.zig b/src/watcher/file.zig index 0eae9446..3ccbcb02 100644 --- a/src/watcher/file.zig +++ b/src/watcher/file.zig @@ -5,6 +5,7 @@ const assert = std.debug.assert; const posix = std.posix; const main = @import("../main.zig"); const stream = @import("stream.zig"); +const xev_posix = @import("../posix.zig"); /// File operations. /// @@ -52,15 +53,15 @@ fn FileStream(comptime xev: type) type { pub const writeInit = S.writeInit; pub const queueWrite = S.queueWrite; - /// Initialize a File from a std.fs.File. - pub fn init(file: std.fs.File) !Self { + /// Initialize a File from a std.Io.File. + pub fn init(file: std.Io.File) !Self { return .{ .fd = file.handle, }; } /// Initialize a File from a file descriptor. - pub fn initFd(fd: std.fs.File.Handle) Self { + pub fn initFd(fd: std.Io.File.Handle) Self { return .{ .fd = fd, }; @@ -322,6 +323,7 @@ fn FileStream(comptime xev: type) type { if (builtin.os.tag == .freebsd) return error.SkipZigTest; const testing = std.testing; + const io = testing.io; var tpool = main.ThreadPool.init(.{}); defer tpool.deinit(); @@ -331,12 +333,12 @@ fn FileStream(comptime xev: type) type { // Create our file const path = "test_watcher_file"; - const f = try std.fs.cwd().createFile(path, .{ + const f = try std.Io.Dir.cwd().createFile(io, path, .{ .read = true, .truncate = true, }); - defer f.close(); - defer std.fs.cwd().deleteFile(path) catch {}; + defer f.close(io); + defer std.Io.Dir.cwd().deleteFile(io, path) catch {}; const file = try Self.init(f); var write_queue: xev.WriteQueue = .{}; @@ -413,10 +415,10 @@ fn FileStream(comptime xev: type) type { try loop.run(.until_done); // Make sure the data is on disk - try f.sync(); + try f.sync(io); - const f2 = try std.fs.cwd().openFile(path, .{}); - defer f2.close(); + const f2 = try std.Io.Dir.cwd().openFile(io, path, .{}); + defer f2.close(io); const file2 = try Self.init(f2); // Read @@ -465,7 +467,7 @@ fn FileDynamic(comptime xev: type) type { pub const write = S.write; pub const queueWrite = S.queueWrite; - pub fn init(file: std.fs.File) !Self { + pub fn init(file: std.Io.File) !Self { return .{ .backend = switch (xev.backend) { inline else => |tag| backend: { const api = (comptime xev.superset(tag)).Api(); @@ -651,9 +653,9 @@ fn FileTests( defer loop.deinit(); // Create our pipe and write to it so its ready to be read - const pipe = try posix.pipe2(.{ .CLOEXEC = true }); - defer posix.close(pipe[1]); - _ = try posix.write(pipe[1], "x"); + const pipe = try xev_posix.pipe2(.{ .CLOEXEC = true }); + defer xev_posix.close(pipe[1]); + _ = try xev_posix.write(pipe[1], "x"); // Create our file const file = Impl.initFd(pipe[0]); @@ -691,9 +693,9 @@ fn FileTests( defer loop.deinit(); // Create our pipe and write to it so its ready to be read - const pipe = try posix.pipe2(.{ .CLOEXEC = true }); - defer posix.close(pipe[1]); - _ = try posix.write(pipe[1], "x"); + const pipe = try xev_posix.pipe2(.{ .CLOEXEC = true }); + defer xev_posix.close(pipe[1]); + _ = try xev_posix.write(pipe[1], "x"); // Create our file const file = Impl.initFd(pipe[0]); @@ -728,6 +730,7 @@ fn FileTests( if (builtin.os.tag == .freebsd) return error.SkipZigTest; const testing = std.testing; + const io = testing.io; var tpool = main.ThreadPool.init(.{}); defer tpool.deinit(); @@ -737,12 +740,12 @@ fn FileTests( // Create our file const path = "test_watcher_file"; - const f = try std.fs.cwd().createFile(path, .{ + const f = try std.Io.Dir.cwd().createFile(io, path, .{ .read = true, .truncate = true, }); - defer f.close(); - defer std.fs.cwd().deleteFile(path) catch {}; + defer f.close(io); + defer std.Io.Dir.cwd().deleteFile(io, path) catch {}; const file = try Impl.init(f); @@ -767,10 +770,10 @@ fn FileTests( try loop.run(.until_done); // Make sure the data is on disk - try f.sync(); + try f.sync(io); - const f2 = try std.fs.cwd().openFile(path, .{}); - defer f2.close(); + const f2 = try std.Io.Dir.cwd().openFile(io, path, .{}); + defer f2.close(io); const file2 = try Impl.init(f2); // Read @@ -803,6 +806,7 @@ fn FileTests( if (builtin.os.tag == .freebsd) return error.SkipZigTest; const testing = std.testing; + const io = testing.io; var tpool = main.ThreadPool.init(.{}); defer tpool.deinit(); @@ -812,12 +816,12 @@ fn FileTests( // Create our file const path = "test_watcher_file"; - const f = try std.fs.cwd().createFile(path, .{ + const f = try std.Io.Dir.cwd().createFile(io, path, .{ .read = true, .truncate = true, }); - defer f.close(); - defer std.fs.cwd().deleteFile(path) catch {}; + defer f.close(io); + defer std.Io.Dir.cwd().deleteFile(io, path) catch {}; const file = try Impl.init(f); @@ -842,10 +846,10 @@ fn FileTests( try loop.run(.until_done); // Make sure the data is on disk - try f.sync(); + try f.sync(io); - const f2 = try std.fs.cwd().openFile(path, .{}); - defer f2.close(); + const f2 = try std.Io.Dir.cwd().openFile(io, path, .{}); + defer f2.close(io); const file2 = try Impl.init(f2); var read_buf: [128]u8 = undefined; @@ -876,6 +880,7 @@ fn FileTests( if (builtin.os.tag == .freebsd) return error.SkipZigTest; const testing = std.testing; + const io = testing.io; var tpool = main.ThreadPool.init(.{}); defer tpool.deinit(); @@ -885,12 +890,12 @@ fn FileTests( // Create our file const path = "test_watcher_file"; - const f = try std.fs.cwd().createFile(path, .{ + const f = try std.Io.Dir.cwd().createFile(io, path, .{ .read = true, .truncate = true, }); - defer f.close(); - defer std.fs.cwd().deleteFile(path) catch {}; + defer f.close(io); + defer std.Io.Dir.cwd().deleteFile(io, path) catch {}; const file = try Impl.init(f); var write_queue: xev.WriteQueue = .{}; @@ -944,10 +949,10 @@ fn FileTests( try loop.run(.until_done); // Make sure the data is on disk - try f.sync(); + try f.sync(io); - const f2 = try std.fs.cwd().openFile(path, .{}); - defer f2.close(); + const f2 = try std.Io.Dir.cwd().openFile(io, path, .{}); + defer f2.close(io); const file2 = try Impl.init(f2); // Read diff --git a/src/watcher/process.zig b/src/watcher/process.zig index a1c6bd83..a581a929 100644 --- a/src/watcher/process.zig +++ b/src/watcher/process.zig @@ -4,6 +4,7 @@ const assert = std.debug.assert; const linux = std.os.linux; const posix = std.posix; const common = @import("common.zig"); +const xev_posix = @import("../posix.zig"); /// Process management, such as waiting for process exit. pub fn Process(comptime xev: type) type { @@ -59,7 +60,7 @@ fn ProcessPidFd(comptime xev: type) type { /// Clean up the process watcher. pub fn deinit(self: *Self) void { - std.posix.close(self.fd); + xev_posix.close(self.fd); } /// Wait for the process to exit. This will automatically call @@ -106,7 +107,7 @@ fn ProcessPidFd(comptime xev: type) type { // We need to wait on the pidfd because it is noted as ready const fd = c_inner.op.poll.fd; var info: linux.siginfo_t = undefined; - const res = linux.waitid(.PIDFD, fd, &info, linux.W.EXITED); + const res = linux.waitid(.PIDFD, fd, &info, linux.W.EXITED, null); break :arg switch (posix.errno(res)) { .SUCCESS => @as(u32, @intCast(info.fields.common.second.sigchld.status)), @@ -142,6 +143,16 @@ fn ProcessPidFd(comptime xev: type) type { }; } +fn reapProcess(pid: posix.pid_t) void { + var status: c_int = undefined; + while (true) switch (posix.errno(posix.system.waitpid(pid, &status, 0))) { + .SUCCESS => return, + .INTR => continue, + .CHILD => return, + else => unreachable, + }; +} + fn ProcessKqueue(comptime xev: type) type { return struct { const Self = @This(); @@ -200,7 +211,7 @@ fn ProcessKqueue(comptime xev: type) type { // `wait` on a process. The Linux side (pidfd) does this // automatically since the `waitid` syscall is used. if (r.proc) |_| { - _ = posix.waitpid(c_inner.op.proc.pid, 0); + reapProcess(c_inner.op.proc.pid); } else |_| {} return @call(.always_inline, cb, .{ @@ -248,7 +259,7 @@ fn ProcessIocp(comptime xev: type) type { windows.FALSE, windows.DUPLICATE_SAME_ACCESS, ); - if (dup_result == 0) return windows.unexpectedError(windows.kernel32.GetLastError()); + if (dup_result == .FALSE) return windows.unexpectedError(windows.kernel32.GetLastError()); const job = try windows.exp.CreateJobObject(null, null); errdefer _ = windows.CloseHandle(job); @@ -303,7 +314,7 @@ fn ProcessIocp(comptime xev: type) type { var exit_code: windows.DWORD = undefined; const process: windows.HANDLE = @ptrCast(c_inner.op.job_object.userdata); - const has_code = windows.kernel32.GetExitCodeProcess(process, &exit_code) != 0; + const has_code = windows.kernel32.GetExitCodeProcess(process, &exit_code) != .FALSE; if (!has_code) std.log.warn("unable to get exit code for process={}", .{windows.kernel32.GetLastError()}); if (exit_code == windows.exp.STILL_ACTIVE) return .rearm; @@ -320,12 +331,12 @@ fn ProcessIocp(comptime xev: type) type { .JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS, => b: { const process: windows.HANDLE = @ptrCast(c_inner.op.job_object.userdata); - const pid = windows.exp.kernel32.GetProcessId(process); + const pid = windows.exp.k32.GetProcessId(process); if (pid == 0) break :b WaitError.Unexpected; if (message.value != pid) return .rearm; var exit_code: windows.DWORD = undefined; - const has_code = windows.kernel32.GetExitCodeProcess(process, &exit_code) != 0; + const has_code = windows.kernel32.GetExitCodeProcess(process, &exit_code) != .FALSE; if (!has_code) std.log.warn("unable to get exit code for process={}", .{windows.kernel32.GetLastError()}); break :b if (has_code) exit_code else WaitError.Unexpected; }, @@ -469,15 +480,14 @@ fn ProcessTests( test "process wait" { const testing = std.testing; - const alloc = testing.allocator; + const io = testing.io; - var child = std.process.Child.init(argv_0, alloc); - try child.spawn(); + const child = try std.process.spawn(io, .{ .argv = argv_0 }); var loop = try xev.Loop.init(.{}); defer loop.deinit(); - var p = try Impl.init(child.id); + var p = try Impl.init(child.id.?); defer p.deinit(); // Wait @@ -503,15 +513,14 @@ fn ProcessTests( test "process wait with non-zero exit code" { if (builtin.os.tag == .freebsd) return error.SkipZigTest; const testing = std.testing; - const alloc = testing.allocator; + const io = testing.io; - var child = std.process.Child.init(argv_42, alloc); - try child.spawn(); + const child = try std.process.spawn(io, .{ .argv = argv_42 }); var loop = try xev.Loop.init(.{}); defer loop.deinit(); - var p = try Impl.init(child.id); + var p = try Impl.init(child.id.?); defer p.deinit(); // Wait @@ -536,18 +545,17 @@ fn ProcessTests( test "process wait on a process that already exited" { const testing = std.testing; - const alloc = testing.allocator; + const io = testing.io; - var child = std.process.Child.init(argv_0, alloc); - try child.spawn(); + var child = try std.process.spawn(io, .{ .argv = argv_0 }); var loop = try xev.Loop.init(.{}); defer loop.deinit(); - var p = try Impl.init(child.id); + var p = try Impl.init(child.id.?); defer p.deinit(); - _ = try child.wait(); + _ = try child.wait(io); // Wait var code: ?u32 = null; diff --git a/src/watcher/stream.zig b/src/watcher/stream.zig index 4b1ca381..c00e631e 100644 --- a/src/watcher/stream.zig +++ b/src/watcher/stream.zig @@ -3,6 +3,7 @@ const assert = std.debug.assert; const builtin = @import("builtin"); const common = @import("common.zig"); const queue = @import("../queue.zig"); +const xev_posix = @import("../posix.zig"); /// Options for creating a stream type. Each of the options makes the /// functionality available for the stream. @@ -1437,7 +1438,7 @@ const Pty = struct { } pub fn deinit(self: *Pty) void { - std.posix.close(self.parent); - std.posix.close(self.child); + xev_posix.close(self.parent); + xev_posix.close(self.child); } }; diff --git a/src/watcher/tcp.zig b/src/watcher/tcp.zig index fa1812d1..040378f8 100644 --- a/src/watcher/tcp.zig +++ b/src/watcher/tcp.zig @@ -5,6 +5,9 @@ const posix = std.posix; const stream = @import("stream.zig"); const common = @import("common.zig"); const ThreadPool = @import("../ThreadPool.zig"); +const net = @import("../posix.zig").net; +const xev_posix = @import("../posix.zig"); +const windows = @import("../windows.zig"); /// TCP client and server. /// @@ -42,11 +45,12 @@ fn TCPStream(comptime xev: type) type { /// Initialize a new TCP with the family from the given address. Only /// the family is used, the actual address has no impact on the created /// resource. - pub fn init(addr: std.net.Address) !Self { + pub fn init(addr: std.Io.net.IpAddress) !Self { if (xev.backend == .wasi_poll) @compileError("unsupported in WASI"); + const posix_addr = net.Address.fromIpAddress(addr); const fd = if (xev.backend == .iocp) - try std.os.windows.WSASocketW(addr.any.family, posix.SOCK.STREAM, 0, null, 0, std.os.windows.ws2_32.WSA_FLAG_OVERLAPPED) + try windows.WSASocketW(posix_addr.any.family, windows.ws2_32.SOCK.STREAM, 0, null, 0, windows.ws2_32.WSA_FLAG_OVERLAPPED) else fd: { // On io_uring we don't use non-blocking sockets because we may // just get EAGAIN over and over from completions. @@ -55,7 +59,7 @@ fn TCPStream(comptime xev: type) type { if (xev.backend != .io_uring) flags |= posix.SOCK.NONBLOCK; break :flags flags; }; - break :fd try posix.socket(addr.any.family, flags, 0); + break :fd try xev_posix.socket(posix_addr.any.family, flags, 0); }; return .{ @@ -71,13 +75,19 @@ fn TCPStream(comptime xev: type) type { } /// Bind the address to the socket. - pub fn bind(self: Self, addr: std.net.Address) !void { + pub fn bind(self: Self, addr: std.Io.net.IpAddress) !void { if (xev.backend == .wasi_poll) @compileError("unsupported in WASI"); - const fd = if (xev.backend == .iocp) @as(std.os.windows.ws2_32.SOCKET, @ptrCast(self.fd)) else self.fd; - - try posix.setsockopt(fd, posix.SOL.SOCKET, posix.SO.REUSEADDR, &std.mem.toBytes(@as(c_int, 1))); - try posix.bind(fd, &addr.any, addr.getOsSockLen()); + const posix_addr = net.Address.fromIpAddress(addr); + if (xev.backend == .iocp) { + const sock = @as(windows.ws2_32.SOCKET, @ptrCast(self.fd)); + if (windows.ws2_32.setsockopt(sock, windows.ws2_32.SOL.SOCKET, windows.ws2_32.SO.REUSEADDR, &std.mem.toBytes(@as(c_int, 1)), @sizeOf(c_int)) != 0) return error.Unexpected; + if (windows.ws2_32.bind(sock, &posix_addr.any, @as(i32, @intCast(posix_addr.getOsSockLen()))) != 0) return error.Unexpected; + } else { + const fd = self.fd; + try posix.setsockopt(fd, posix.SOL.SOCKET, posix.SO.REUSEADDR, &std.mem.toBytes(@as(c_int, 1))); + try xev_posix.bind(fd, &posix_addr.any, posix_addr.getOsSockLen()); + } } /// Listen for connections on the socket. This puts the socket into passive @@ -85,9 +95,12 @@ fn TCPStream(comptime xev: type) type { pub fn listen(self: Self, backlog: u31) !void { if (xev.backend == .wasi_poll) @compileError("unsupported in WASI"); - const fd = if (xev.backend == .iocp) @as(std.os.windows.ws2_32.SOCKET, @ptrCast(self.fd)) else self.fd; - - try posix.listen(fd, backlog); + if (xev.backend == .iocp) { + const sock = @as(windows.ws2_32.SOCKET, @ptrCast(self.fd)); + if (windows.ws2_32.listen(sock, @as(i32, backlog)) != 0) return error.Unexpected; + } else { + try xev_posix.listen(self.fd, backlog); + } } /// Accept a single connection. @@ -148,7 +161,7 @@ fn TCPStream(comptime xev: type) type { self: Self, loop: *xev.Loop, c: *xev.Completion, - addr: std.net.Address, + addr: std.Io.net.IpAddress, comptime Userdata: type, userdata: ?*Userdata, comptime cb: *const fn ( @@ -161,11 +174,12 @@ fn TCPStream(comptime xev: type) type { ) void { if (xev.backend == .wasi_poll) @compileError("unsupported in WASI"); + const posix_addr = net.Address.fromIpAddress(addr); c.* = .{ .op = .{ .connect = .{ .socket = self.fd, - .addr = addr, + .addr = posix_addr, }, }, @@ -269,7 +283,7 @@ fn TCPDynamic(comptime xev: type) type { pub const write = S.write; pub const queueWrite = S.queueWrite; - pub fn init(addr: std.net.Address) !Self { + pub fn init(addr: std.Io.net.IpAddress) !Self { return .{ .backend = switch (xev.backend) { inline else => |tag| backend: { const api = (comptime xev.superset(tag)).Api(); @@ -295,7 +309,7 @@ fn TCPDynamic(comptime xev: type) type { } }; } - pub fn bind(self: Self, addr: std.net.Address) !void { + pub fn bind(self: Self, addr: std.Io.net.IpAddress) !void { switch (xev.backend) { inline else => |tag| try @field( self.backend, @@ -383,7 +397,7 @@ fn TCPDynamic(comptime xev: type) type { self: Self, loop: *xev.Loop, c: *xev.Completion, - addr: std.net.Address, + addr: std.Io.net.IpAddress, comptime Userdata: type, userdata: ?*Userdata, comptime cb: *const fn ( @@ -529,7 +543,7 @@ fn TCPTests(comptime xev: type, comptime Impl: type) type { defer loop.deinit(); // Choose random available port (Zig #14907) - var address = try std.net.Address.parseIp4("127.0.0.1", 0); + var address = try std.Io.net.IpAddress.parse("127.0.0.1", 0); const server = try Impl.init(address); // Bind and listen @@ -537,20 +551,20 @@ fn TCPTests(comptime xev: type, comptime Impl: type) type { try server.listen(1); // Retrieve bound port and initialize client - var sock_len = address.getOsSockLen(); - const fd = if (xev.dynamic) - server.fd() - else if (xev.backend == .iocp) - @as(std.os.windows.ws2_32.SOCKET, @ptrCast(server.fd)) - else - server.fd; - try posix.getsockname(fd, &address.any, &sock_len); + var internal_addr = net.Address.fromIpAddress(address); + var sock_len = internal_addr.getOsSockLen(); + if (@hasField(@TypeOf(xev.backend), "iocp") and xev.backend == .iocp) { + const sock = @as(windows.ws2_32.SOCKET, @ptrCast(if (xev.dynamic) server.fd() else server.fd)); + var sl: i32 = @intCast(sock_len); + std.debug.assert(windows.ws2_32.getsockname(sock, &internal_addr.any, &sl) == 0); + sock_len = @intCast(sl); + } else { + const fd = if (xev.dynamic) server.fd() else server.fd; + try xev_posix.getsockname(fd, &internal_addr.any, &sock_len); + } + address = internal_addr.toIpAddress(); const client = try Impl.init(address); - //const address = try std.net.Address.parseIp4("127.0.0.1", 3132); - //var server = try Impl.init(address); - //var client = try Impl.init(address); - // Completions we need var c_accept: xev.Completion = undefined; var c_connect: xev.Completion = undefined; @@ -710,7 +724,7 @@ fn TCPTests(comptime xev: type, comptime Impl: type) type { defer loop.deinit(); // Choose random available port (Zig #14907) - var address = try std.net.Address.parseIp4("127.0.0.1", 0); + var address = try std.Io.net.IpAddress.parse("127.0.0.1", 0); const server = try Impl.init(address); // Bind and listen @@ -718,11 +732,13 @@ fn TCPTests(comptime xev: type, comptime Impl: type) type { try server.listen(1); // Retrieve bound port and initialize client - var sock_len = address.getOsSockLen(); - try posix.getsockname(if (xev.dynamic) + var internal_addr = net.Address.fromIpAddress(address); + var sock_len = internal_addr.getOsSockLen(); + try xev_posix.getsockname(if (xev.dynamic) server.fd() else - server.fd, &address.any, &sock_len); + server.fd, &internal_addr.any, &sock_len); + address = internal_addr.toIpAddress(); const client = try Impl.init(address); // Completions we need diff --git a/src/watcher/udp.zig b/src/watcher/udp.zig index 7df3ab15..a480e9ed 100644 --- a/src/watcher/udp.zig +++ b/src/watcher/udp.zig @@ -5,6 +5,8 @@ const posix = std.posix; const stream = @import("stream.zig"); const common = @import("common.zig"); const ThreadPool = @import("../ThreadPool.zig"); +const net = @import("../posix.zig").net; +const xev_posix = @import("../posix.zig"); /// UDP client and server. /// @@ -56,10 +58,11 @@ fn UDPSendto(comptime xev: type) type { /// Initialize a new UDP with the family from the given address. Only /// the family is used, the actual address has no impact on the created /// resource. - pub fn init(addr: std.net.Address) !Self { + pub fn init(addr: std.Io.net.IpAddress) !Self { + const posix_addr = net.Address.fromIpAddress(addr); return .{ - .fd = try posix.socket( - addr.any.family, + .fd = try xev_posix.socket( + posix_addr.any.family, posix.SOCK.NONBLOCK | posix.SOCK.DGRAM | posix.SOCK.CLOEXEC, 0, ), @@ -74,10 +77,11 @@ fn UDPSendto(comptime xev: type) type { } /// Bind the address to the socket. - pub fn bind(self: Self, addr: std.net.Address) !void { + pub fn bind(self: Self, addr: std.Io.net.IpAddress) !void { + const posix_addr = net.Address.fromIpAddress(addr); try posix.setsockopt(self.fd, posix.SOL.SOCKET, posix.SO.REUSEPORT, &std.mem.toBytes(@as(c_int, 1))); try posix.setsockopt(self.fd, posix.SOL.SOCKET, posix.SO.REUSEADDR, &std.mem.toBytes(@as(c_int, 1))); - try posix.bind(self.fd, &addr.any, addr.getOsSockLen()); + try xev_posix.bind(self.fd, &posix_addr.any, posix_addr.getOsSockLen()); } /// Read from the socket. This performs a single read. The callback must @@ -97,7 +101,7 @@ fn UDPSendto(comptime xev: type) type { l: *xev.Loop, c: *xev.Completion, s: *State, - addr: std.net.Address, + addr: std.Io.net.IpAddress, s: Self, b: xev.ReadBuffer, r: xev.ReadError!usize, @@ -130,7 +134,7 @@ fn UDPSendto(comptime xev: type) type { l_inner, c_inner, s_inner, - std.net.Address.initPosix(@alignCast(&c_inner.op.recvfrom.addr)), + net.Address.initPosix(@alignCast(&c_inner.op.recvfrom.addr)).toIpAddress(), initFd(c_inner.op.recvfrom.fd), c_inner.op.recvfrom.buffer, r.recvfrom, @@ -152,7 +156,7 @@ fn UDPSendto(comptime xev: type) type { loop: *xev.Loop, c: *xev.Completion, s: *State, - addr: std.net.Address, + addr: std.Io.net.IpAddress, buf: xev.WriteBuffer, comptime Userdata: type, userdata: ?*Userdata, @@ -166,6 +170,7 @@ fn UDPSendto(comptime xev: type) type { r: xev.WriteError!usize, ) xev.CallbackAction, ) void { + const posix_addr = net.Address.fromIpAddress(addr); s.* = .{ .userdata = userdata, }; @@ -177,7 +182,7 @@ fn UDPSendto(comptime xev: type) type { .sendto = .{ .fd = self.fd, .buffer = buf, - .addr = addr, + .addr = posix_addr, }, }, .userdata = s, @@ -217,7 +222,7 @@ fn UDPSendto(comptime xev: type) type { fn UDPSendtoIOCP(comptime xev: type) type { return struct { const Self = @This(); - const windows = std.os.windows; + const windows = @import("../windows.zig"); fd: windows.HANDLE, @@ -234,8 +239,9 @@ fn UDPSendtoIOCP(comptime xev: type) type { /// Initialize a new UDP with the family from the given address. Only /// the family is used, the actual address has no impact on the created /// resource. - pub fn init(addr: std.net.Address) !Self { - const socket = try windows.WSASocketW(addr.any.family, posix.SOCK.DGRAM, 0, null, 0, windows.ws2_32.WSA_FLAG_OVERLAPPED); + pub fn init(addr: std.Io.net.IpAddress) !Self { + const posix_addr = net.Address.fromIpAddress(addr); + const socket = try windows.WSASocketW(posix_addr.any.family, windows.ws2_32.SOCK.DGRAM, 0, null, 0, windows.ws2_32.WSA_FLAG_OVERLAPPED); return .{ .fd = socket, @@ -250,10 +256,11 @@ fn UDPSendtoIOCP(comptime xev: type) type { } /// Bind the address to the socket. - pub fn bind(self: Self, addr: std.net.Address) !void { + pub fn bind(self: Self, addr: std.Io.net.IpAddress) !void { + const posix_addr = net.Address.fromIpAddress(addr); const socket = @as(windows.ws2_32.SOCKET, @ptrCast(self.fd)); - try posix.setsockopt(socket, posix.SOL.SOCKET, posix.SO.REUSEADDR, &std.mem.toBytes(@as(c_int, 1))); - try posix.bind(socket, &addr.any, addr.getOsSockLen()); + if (windows.ws2_32.setsockopt(socket, windows.ws2_32.SOL.SOCKET, windows.ws2_32.SO.REUSEADDR, &std.mem.toBytes(@as(c_int, 1)), @sizeOf(c_int)) != 0) return error.Unexpected; + if (windows.ws2_32.bind(socket, &posix_addr.any, @as(i32, @intCast(posix_addr.getOsSockLen()))) != 0) return error.Unexpected; } /// Read from the socket. This performs a single read. The callback must @@ -275,7 +282,7 @@ fn UDPSendtoIOCP(comptime xev: type) type { l: *xev.Loop, c: *xev.Completion, s: *State, - addr: std.net.Address, + addr: std.Io.net.IpAddress, s: Self, b: xev.ReadBuffer, r: xev.ReadError!usize, @@ -308,7 +315,7 @@ fn UDPSendtoIOCP(comptime xev: type) type { l_inner, c_inner, s_inner, - std.net.Address.initPosix(@alignCast(&c_inner.op.recvfrom.addr)), + net.Address.initPosix(@alignCast(&c_inner.op.recvfrom.addr)).toIpAddress(), initFd(c_inner.op.recvfrom.fd), c_inner.op.recvfrom.buffer, r.recvfrom, @@ -330,7 +337,7 @@ fn UDPSendtoIOCP(comptime xev: type) type { loop: *xev.Loop, c: *xev.Completion, s: *State, - addr: std.net.Address, + addr: std.Io.net.IpAddress, buf: xev.WriteBuffer, comptime Userdata: type, userdata: ?*Userdata, @@ -344,6 +351,7 @@ fn UDPSendtoIOCP(comptime xev: type) type { r: xev.WriteError!usize, ) xev.CallbackAction, ) void { + const posix_addr = net.Address.fromIpAddress(addr); s.* = .{ .userdata = userdata, }; @@ -355,7 +363,7 @@ fn UDPSendtoIOCP(comptime xev: type) type { .sendto = .{ .fd = self.fd, .buffer = buf, - .addr = addr, + .addr = posix_addr, }, }, .userdata = s, @@ -396,6 +404,8 @@ fn UDPSendMsg(comptime xev: type) type { return struct { const Self = @This(); + const linux = std.os.linux; + fd: posix.socket_t, /// UDP requires some extra state to perform operations. The state is @@ -408,15 +418,15 @@ fn UDPSendMsg(comptime xev: type) type { recv: struct { buf: xev.ReadBuffer, addr_buffer: std.posix.sockaddr.storage = undefined, - msghdr: std.posix.msghdr, - iov: [1]std.posix.iovec, + msghdr: linux.msghdr, + iov: [1]posix.iovec, }, send: struct { buf: xev.WriteBuffer, - addr: std.net.Address, - msghdr: std.posix.msghdr_const, - iov: [1]std.posix.iovec_const, + addr: net.Address, + msghdr: linux.msghdr_const, + iov: [1]posix.iovec_const, }, }, }; @@ -431,7 +441,8 @@ fn UDPSendMsg(comptime xev: type) type { /// Initialize a new UDP with the family from the given address. Only /// the family is used, the actual address has no impact on the created /// resource. - pub fn init(addr: std.net.Address) !Self { + pub fn init(addr: std.Io.net.IpAddress) !Self { + const posix_addr = net.Address.fromIpAddress(addr); // On io_uring we don't use non-blocking sockets because we may // just get EAGAIN over and over from completions. const flags = flags: { @@ -441,7 +452,7 @@ fn UDPSendMsg(comptime xev: type) type { }; return .{ - .fd = try posix.socket(addr.any.family, flags, 0), + .fd = try xev_posix.socket(posix_addr.any.family, flags, 0), }; } @@ -453,10 +464,11 @@ fn UDPSendMsg(comptime xev: type) type { } /// Bind the address to the socket. - pub fn bind(self: Self, addr: std.net.Address) !void { + pub fn bind(self: Self, addr: std.Io.net.IpAddress) !void { + const posix_addr = net.Address.fromIpAddress(addr); try posix.setsockopt(self.fd, posix.SOL.SOCKET, posix.SO.REUSEPORT, &std.mem.toBytes(@as(c_int, 1))); try posix.setsockopt(self.fd, posix.SOL.SOCKET, posix.SO.REUSEADDR, &std.mem.toBytes(@as(c_int, 1))); - try posix.bind(self.fd, &addr.any, addr.getOsSockLen()); + try xev_posix.bind(self.fd, &posix_addr.any, posix_addr.getOsSockLen()); } /// Read from the socket. This performs a single read. The callback must @@ -476,7 +488,7 @@ fn UDPSendMsg(comptime xev: type) type { l: *xev.Loop, c: *xev.Completion, s: *State, - addr: std.net.Address, + addr: std.Io.net.IpAddress, s: Self, b: xev.ReadBuffer, r: xev.ReadError!usize, @@ -540,7 +552,7 @@ fn UDPSendMsg(comptime xev: type) type { l_inner, c_inner, s_inner, - std.net.Address.initPosix(@ptrCast(&s_inner.op.recv.addr_buffer)), + net.Address.initPosix(@ptrCast(&s_inner.op.recv.addr_buffer)).toIpAddress(), initFd(c_inner.op.recvmsg.fd), s_inner.op.recv.buf, if (r.recvmsg) |v| v else |err| err, @@ -571,7 +583,7 @@ fn UDPSendMsg(comptime xev: type) type { loop: *xev.Loop, c: *xev.Completion, s: *State, - addr: std.net.Address, + addr: std.Io.net.IpAddress, buf: xev.WriteBuffer, comptime Userdata: type, userdata: ?*Userdata, @@ -585,17 +597,18 @@ fn UDPSendMsg(comptime xev: type) type { r: xev.WriteError!usize, ) xev.CallbackAction, ) void { + const posix_addr = net.Address.fromIpAddress(addr); // Set the active field for runtime safety s.op = .{ .send = undefined }; s.* = .{ .userdata = userdata, .op = .{ .send = .{ - .addr = addr, + .addr = posix_addr, .buf = buf, .msghdr = .{ .name = &s.op.send.addr.any, - .namelen = addr.getOsSockLen(), + .namelen = posix_addr.getOsSockLen(), .iov = &s.op.send.iov, .iovlen = 1, .control = null, @@ -700,7 +713,7 @@ fn UDPDynamic(comptime xev: type) type { pub const close = S.close; pub const poll = S.poll; - pub fn init(addr: std.net.Address) !Self { + pub fn init(addr: std.Io.net.IpAddress) !Self { return .{ .backend = switch (xev.backend) { inline else => |tag| backend: { const api = (comptime xev.superset(tag)).Api(); @@ -726,7 +739,7 @@ fn UDPDynamic(comptime xev: type) type { } }; } - pub fn bind(self: Self, addr: std.net.Address) !void { + pub fn bind(self: Self, addr: std.Io.net.IpAddress) !void { switch (xev.backend) { inline else => |tag| try @field( self.backend, @@ -757,7 +770,7 @@ fn UDPDynamic(comptime xev: type) type { l: *xev.Loop, c: *xev.Completion, s: *State, - addr: std.net.Address, + addr: std.Io.net.IpAddress, s: Self, b: xev.ReadBuffer, r: xev.ReadError!usize, @@ -772,7 +785,7 @@ fn UDPDynamic(comptime xev: type) type { l_inner: *api.Loop, c_inner: *api.Completion, st_inner: *api.UDP.State, - addr_inner: std.net.Address, + addr_inner: std.Io.net.IpAddress, s_inner: api.UDP, b_inner: api.ReadBuffer, r_inner: api.ReadError!usize, @@ -823,7 +836,7 @@ fn UDPDynamic(comptime xev: type) type { loop: *xev.Loop, c: *xev.Completion, s: *State, - addr: std.net.Address, + addr: std.Io.net.IpAddress, buf: xev.WriteBuffer, comptime Userdata: type, userdata: ?*Userdata, @@ -921,7 +934,7 @@ fn UDPTests(comptime xev: type, comptime Impl: type) type { var loop = try xev.Loop.init(.{ .thread_pool = &tpool }); defer loop.deinit(); - const address = try std.net.Address.parseIp4("127.0.0.1", 3132); + const address = try std.Io.net.IpAddress.parse("127.0.0.1", 3132); const server = try Impl.init(address); const client = try Impl.init(address); @@ -937,7 +950,7 @@ fn UDPTests(comptime xev: type, comptime Impl: type) type { _: *xev.Loop, _: *xev.Completion, _: *Impl.State, - _: std.net.Address, + _: std.Io.net.IpAddress, _: Impl, _: xev.ReadBuffer, r: xev.ReadError!usize, diff --git a/src/windows.zig b/src/windows.zig index 9726b48e..8ddbe3a3 100644 --- a/src/windows.zig +++ b/src/windows.zig @@ -1,78 +1,642 @@ -//! Namespace containing missing utils from std +//! Namespace containing missing utils from std. +//! This acts as a compat shim for Win32 APIs removed from std.os.windows in Zig 0.16. const std = @import("std"); -const windows = std.os.windows; +const win = std.os.windows; const posix = std.posix; -// Forwarded declarations of std.os.windows. -pub const DWORD = windows.DWORD; -pub const FALSE = windows.FALSE; -pub const TRUE = windows.TRUE; -pub const INFINITE = windows.INFINITE; -pub const HANDLE = windows.HANDLE; -pub const INVALID_HANDLE_VALUE = windows.INVALID_HANDLE_VALUE; -pub const OVERLAPPED = windows.OVERLAPPED; -pub const OVERLAPPED_ENTRY = windows.OVERLAPPED_ENTRY; -pub const DUPLICATE_SAME_ACCESS = windows.DUPLICATE_SAME_ACCESS; -pub const GENERIC_READ = windows.GENERIC_READ; -pub const GENERIC_WRITE = windows.GENERIC_WRITE; -pub const OPEN_ALWAYS = windows.OPEN_ALWAYS; -pub const FILE_FLAG_OVERLAPPED = windows.FILE_FLAG_OVERLAPPED; -pub const ReadFileError = windows.ReadFileError; -pub const WriteFileError = windows.WriteFileError; -pub const Win32Error = windows.Win32Error; -pub const WSASocketW = windows.WSASocketW; -pub const kernel32 = windows.kernel32; -pub const ws2_32 = windows.ws2_32; -pub const unexpectedWSAError = windows.unexpectedWSAError; -pub const unexpectedError = windows.unexpectedError; -pub const sliceToPrefixedFileW = windows.sliceToPrefixedFileW; -pub const CloseHandle = windows.CloseHandle; -pub const QueryPerformanceCounter = windows.QueryPerformanceCounter; -pub const QueryPerformanceFrequency = windows.QueryPerformanceFrequency; -pub const GetQueuedCompletionStatusEx = windows.GetQueuedCompletionStatusEx; -pub const PostQueuedCompletionStatus = windows.PostQueuedCompletionStatus; -pub const CreateIoCompletionPort = windows.CreateIoCompletionPort; - -pub extern "kernel32" fn DeleteFileW(lpFileName: [*:0]const u16) callconv(.winapi) windows.BOOL; +// Forwarded declarations of std.os.windows types that still exist. +pub const ULONG_PTR = win.ULONG_PTR; +pub const PVOID = win.PVOID; +pub const DWORD = win.DWORD; +pub const HANDLE = win.HANDLE; +pub const INVALID_HANDLE_VALUE = win.INVALID_HANDLE_VALUE; +pub const Win32Error = win.Win32Error; +pub const DUPLICATE_SAME_ACCESS = win.DUPLICATE_SAME_ACCESS; +pub const unexpectedError = win.unexpectedError; +pub const CloseHandle = win.CloseHandle; + +pub const BOOL = win.BOOL; +pub const FALSE: BOOL = .FALSE; +pub const TRUE: BOOL = BOOL.TRUE; + +// Constants removed from std.os.windows in Zig 0.16. +pub const INFINITE: DWORD = 0xFFFF_FFFF; +pub const GENERIC_READ: DWORD = 0x80000000; +pub const GENERIC_WRITE: DWORD = 0x40000000; +pub const OPEN_ALWAYS: DWORD = 4; +pub const FILE_FLAG_OVERLAPPED: DWORD = 0x40000000; + +pub const OVERLAPPED = extern struct { + Internal: ULONG_PTR = 0, + InternalHigh: ULONG_PTR = 0, + DUMMYUNIONNAME: extern union { + DUMMYSTRUCTNAME: extern struct { + Offset: DWORD, + OffsetHigh: DWORD, + }, + Pointer: ?PVOID, + } = .{ .DUMMYSTRUCTNAME = .{ .Offset = 0, .OffsetHigh = 0 } }, + hEvent: ?HANDLE = null, +}; +pub const OVERLAPPED_ENTRY = extern struct { + lpCompletionKey: ULONG_PTR, + lpOverlapped: *OVERLAPPED, + Internal: ULONG_PTR, + dwNumberOfBytesTransferred: DWORD, +}; + +pub const WSABUF = extern struct { + len: win.ULONG, + buf: [*]u8, +}; + +pub const ReadFileError = error{ + BrokenPipe, + ConnectionResetByPeer, + OperationAborted, + LockViolation, + AccessDenied, + NotOpenForReading, + Unexpected, +}; +pub const WriteFileError = error{ + SystemResources, + OperationAborted, + BrokenPipe, + NotOpenForWriting, + LockViolation, + ConnectionResetByPeer, + AccessDenied, + Unexpected, +}; + +// --- kernel32 compat shim --- +// In Zig 0.16, nearly all kernel32 extern functions were removed from std. +// We re-declare the ones this project needs. +pub const kernel32 = struct { + pub fn GetLastError() Win32Error { + return win.GetLastError(); + } + + pub fn GetCurrentProcess() HANDLE { + return win.GetCurrentProcess(); + } + + pub extern "kernel32" fn CreateFileW( + lpFileName: [*:0]const u16, + dwDesiredAccess: DWORD, + dwShareMode: DWORD, + lpSecurityAttributes: ?*win.SECURITY_ATTRIBUTES, + dwCreationDisposition: DWORD, + dwFlagsAndAttributes: DWORD, + hTemplateFile: ?HANDLE, + ) callconv(.winapi) HANDLE; + + pub extern "kernel32" fn ReadFile( + hFile: HANDLE, + lpBuffer: *anyopaque, + nNumberOfBytesToRead: DWORD, + lpNumberOfBytesRead: ?*DWORD, + lpOverlapped: ?*OVERLAPPED, + ) callconv(.winapi) BOOL; + + pub extern "kernel32" fn WriteFile( + in_hFile: HANDLE, + in_lpBuffer: [*]const u8, + in_nNumberOfBytesToWrite: DWORD, + out_lpNumberOfBytesWritten: ?*DWORD, + in_out_lpOverlapped: ?*OVERLAPPED, + ) callconv(.winapi) BOOL; + + pub extern "kernel32" fn CancelIoEx( + hFile: HANDLE, + lpOverlapped: ?*OVERLAPPED, + ) callconv(.winapi) BOOL; + + pub extern "kernel32" fn DuplicateHandle( + hSourceProcessHandle: HANDLE, + hSourceHandle: HANDLE, + hTargetProcessHandle: HANDLE, + lpTargetHandle: *HANDLE, + dwDesiredAccess: DWORD, + bInheritHandle: BOOL, + dwOptions: DWORD, + ) callconv(.winapi) BOOL; + + pub extern "kernel32" fn GetExitCodeProcess( + hProcess: HANDLE, + lpExitCode: *DWORD, + ) callconv(.winapi) BOOL; + + pub extern "kernel32" fn GetOverlappedResult( + hFile: HANDLE, + lpOverlapped: *OVERLAPPED, + lpNumberOfBytesTransferred: *DWORD, + bWait: BOOL, + ) callconv(.winapi) BOOL; + + pub extern "kernel32" fn CreateIoCompletionPort( + FileHandle: HANDLE, + ExistingCompletionPort: ?HANDLE, + CompletionKey: ULONG_PTR, + NumberOfConcurrentThreads: DWORD, + ) callconv(.winapi) ?HANDLE; + + pub extern "kernel32" fn GetQueuedCompletionStatusEx( + CompletionPort: HANDLE, + lpCompletionPortEntries: [*]OVERLAPPED_ENTRY, + ulCount: win.ULONG, + ulNumEntriesRemoved: *win.ULONG, + dwMilliseconds: DWORD, + fAlertable: BOOL, + ) callconv(.winapi) BOOL; + + pub extern "kernel32" fn PostQueuedCompletionStatus( + CompletionPort: HANDLE, + dwNumberOfBytesTransferred: DWORD, + dwCompletionKey: ULONG_PTR, + lpOverlapped: ?*OVERLAPPED, + ) callconv(.winapi) BOOL; + + pub extern "kernel32" fn DeleteFileW( + lpFileName: [*:0]const u16, + ) callconv(.winapi) BOOL; +}; + +// --- ws2_32 compat shim --- +// In Zig 0.16, all ws2_32 extern functions, WinsockError, WSABUF, WSA_FLAG_OVERLAPPED, etc. +// were removed. We re-declare the ones this project needs, and forward remaining constants. +pub const ws2_32 = struct { + pub const SOCKET = *opaque {}; + pub const INVALID_SOCKET: SOCKET = @ptrFromInt(~@as(usize, 0)); + pub const SOCKET_ERROR: i32 = -1; + pub const WSA_FLAG_OVERLAPPED: u32 = 1; + + pub const LPWSAOVERLAPPED_COMPLETION_ROUTINE = *const fn ( + dwError: DWORD, + cbTransferred: DWORD, + lpOverlapped: *OVERLAPPED, + dwFlags: DWORD, + ) callconv(.winapi) void; + + pub const WSAPROTOCOL_INFOW = extern struct { + dwServiceFlags1: DWORD, + dwServiceFlags2: DWORD, + dwServiceFlags3: DWORD, + dwServiceFlags4: DWORD, + dwProviderFlags: DWORD, + ProviderId: win.GUID, + dwCatalogEntryId: DWORD, + ProtocolChain: WSAPROTOCOLCHAIN, + iVersion: c_int, + iAddressFamily: c_int, + iMaxSockAddr: c_int, + iMinSockAddr: c_int, + iSocketType: c_int, + iProtocol: c_int, + iProtocolMaxOffset: c_int, + iNetworkByteOrder: c_int, + iSecurityScheme: c_int, + dwMessageSize: DWORD, + dwProviderReserved: DWORD, + szProtocol: [256]u16, + }; + + pub const WSAPROTOCOLCHAIN = extern struct { + ChainLen: c_int, + ChainEntries: [7]DWORD, + }; + + pub const WSADATA = extern struct { + wVersion: u16, + wHighVersion: u16, + iMaxSockets: u16, + iMaxUdpDg: u16, + lpVendorInfo: ?[*]u8, + szDescription: [257]u8, + szSystemStatus: [129]u8, + }; + + pub const WinsockError = enum(u16) { + WSA_INVALID_HANDLE = 6, + WSA_NOT_ENOUGH_MEMORY = 8, + WSA_INVALID_PARAMETER = 87, + WSA_OPERATION_ABORTED = 995, + WSA_IO_INCOMPLETE = 996, + WSA_IO_PENDING = 997, + WSAEINTR = 10004, + WSAEBADF = 10009, + WSAEACCES = 10013, + WSAEFAULT = 10014, + WSAEINVAL = 10022, + WSAEMFILE = 10024, + WSAEWOULDBLOCK = 10035, + WSAEINPROGRESS = 10036, + WSAEALREADY = 10037, + WSAENOTSOCK = 10038, + WSAEDESTADDRREQ = 10039, + WSAEMSGSIZE = 10040, + WSAEPROTOTYPE = 10041, + WSAENOPROTOOPT = 10042, + WSAEPROTONOSUPPORT = 10043, + WSAESOCKTNOSUPPORT = 10044, + WSAEOPNOTSUPP = 10045, + WSAEPFNOSUPPORT = 10046, + WSAEAFNOSUPPORT = 10047, + WSAEADDRINUSE = 10048, + WSAEADDRNOTAVAIL = 10049, + WSAENETDOWN = 10050, + WSAENETUNREACH = 10051, + WSAENETRESET = 10052, + WSAECONNABORTED = 10053, + WSAECONNRESET = 10054, + WSAENOBUFS = 10055, + WSAEISCONN = 10056, + WSAENOTCONN = 10057, + WSAESHUTDOWN = 10058, + WSAETOOMANYREFS = 10059, + WSAETIMEDOUT = 10060, + WSAECONNREFUSED = 10061, + WSAELOOP = 10062, + WSAENAMETOOLONG = 10063, + WSAEHOSTDOWN = 10064, + WSAEHOSTUNREACH = 10065, + WSAENOTEMPTY = 10066, + WSAEPROCLIM = 10067, + WSAEUSERS = 10068, + WSAEDQUOT = 10069, + WSAESTALE = 10070, + WSAEREMOTE = 10071, + WSASYSNOTREADY = 10091, + WSAVERNOTSUPPORTED = 10092, + WSANOTINITIALISED = 10093, + WSAEDISCON = 10101, + WSAENOMORE = 10102, + WSAECANCELLED = 10103, + WSAEINVALIDPROCTABLE = 10104, + WSAEINVALIDPROVIDER = 10105, + WSAEPROVIDERFAILEDINIT = 10106, + WSASYSCALLFAILURE = 10107, + WSASERVICE_NOT_FOUND = 10108, + WSATYPE_NOT_FOUND = 10109, + WSA_E_NO_MORE = 10110, + WSA_E_CANCELLED = 10111, + WSAEREFUSED = 10112, + WSAHOST_NOT_FOUND = 11001, + WSATRY_AGAIN = 11002, + WSANO_RECOVERY = 11003, + WSANO_DATA = 11004, + WSA_QOS_RECEIVERS = 11005, + WSA_QOS_SENDERS = 11006, + WSA_QOS_NO_SENDERS = 11007, + WSA_QOS_NO_RECEIVERS = 11008, + WSA_QOS_REQUEST_CONFIRMED = 11009, + WSA_QOS_ADMISSION_FAILURE = 11010, + WSA_QOS_POLICY_FAILURE = 11011, + WSA_QOS_BAD_STYLE = 11012, + WSA_QOS_BAD_OBJECT = 11013, + WSA_QOS_TRAFFIC_CTRL_ERROR = 11014, + WSA_QOS_GENERIC_ERROR = 11015, + WSA_QOS_ESERVICETYPE = 11016, + WSA_QOS_EFLOWSPEC = 11017, + WSA_QOS_EPROVSPECBUF = 11018, + WSA_QOS_EFILTERSTYLE = 11019, + WSA_QOS_EFILTERTYPE = 11020, + WSA_QOS_EFILTERCOUNT = 11021, + WSA_QOS_EOBJLENGTH = 11022, + WSA_QOS_EFLOWCOUNT = 11023, + WSA_QOS_EUNKOWNPSOBJ = 11024, + WSA_QOS_EPOLICYOBJ = 11025, + WSA_QOS_EFLOWDESC = 11026, + WSA_QOS_EPSFLOWSPEC = 11027, + WSA_QOS_EPSFILTERSPEC = 11028, + WSA_QOS_ESDMODEOBJ = 11029, + WSA_QOS_ESHAPERATEOBJ = 11030, + WSA_QOS_RESERVED_PETYPE = 11031, + _, + }; + + // Forward constants/types from std that still exist. + pub const sockaddr = win.ws2_32.sockaddr; + pub const AF = win.ws2_32.AF; + pub const SOCK = win.ws2_32.SOCK; + pub const SOL = win.ws2_32.SOL; + pub const SO = win.ws2_32.SO; + pub const IPPROTO = win.ws2_32.IPPROTO; + pub const SD_RECEIVE: i32 = 0; + pub const SD_SEND: i32 = 1; + pub const SD_BOTH: i32 = 2; + + pub extern "ws2_32" fn WSASocketW( + af: i32, + @"type": i32, + protocol: i32, + lpProtocolInfo: ?*WSAPROTOCOL_INFOW, + g: u32, + dwFlags: u32, + ) callconv(.winapi) SOCKET; + + pub extern "ws2_32" fn WSAStartup( + wVersionRequested: u16, + lpWSAData: *WSADATA, + ) callconv(.winapi) i32; + + pub extern "ws2_32" fn connect( + s: SOCKET, + name: *const posix.sockaddr, + namelen: i32, + ) callconv(.winapi) i32; + + pub extern "ws2_32" fn getsockname( + s: SOCKET, + name: *posix.sockaddr, + namelen: *i32, + ) callconv(.winapi) i32; + + pub extern "ws2_32" fn getsockopt( + s: SOCKET, + level: i32, + optname: i32, + optval: [*]u8, + optlen: *i32, + ) callconv(.winapi) i32; + + pub extern "ws2_32" fn WSAGetLastError() callconv(.winapi) WinsockError; + + pub extern "ws2_32" fn WSAGetOverlappedResult( + s: SOCKET, + lpOverlapped: *OVERLAPPED, + lpcbTransfer: *u32, + fWait: BOOL, + lpdwFlags: *u32, + ) callconv(.winapi) BOOL; + + pub extern "ws2_32" fn WSASend( + s: SOCKET, + lpBuffers: [*]WSABUF, + dwBufferCount: u32, + lpNumberOfBytesSent: ?*u32, + dwFlags: u32, + lpOverlapped: ?*OVERLAPPED, + lpCompletionRoutine: ?LPWSAOVERLAPPED_COMPLETION_ROUTINE, + ) callconv(.winapi) i32; + + pub extern "ws2_32" fn WSARecv( + s: SOCKET, + lpBuffers: [*]WSABUF, + dwBufferCount: u32, + lpNumberOfBytesRecv: ?*u32, + lpFlags: *u32, + lpOverlapped: ?*OVERLAPPED, + lpCompletionRoutine: ?LPWSAOVERLAPPED_COMPLETION_ROUTINE, + ) callconv(.winapi) i32; + + pub extern "ws2_32" fn WSASendTo( + s: SOCKET, + lpBuffers: [*]WSABUF, + dwBufferCount: u32, + lpNumberOfBytesSent: ?*u32, + dwFlags: u32, + lpTo: ?*const posix.sockaddr, + iToLen: i32, + lpOverlapped: ?*OVERLAPPED, + lpCompletionRoutine: ?LPWSAOVERLAPPED_COMPLETION_ROUTINE, + ) callconv(.winapi) i32; + + pub extern "ws2_32" fn WSARecvFrom( + s: SOCKET, + lpBuffers: [*]WSABUF, + dwBufferCount: u32, + lpNumberOfBytesRecvd: ?*u32, + lpFlags: *u32, + lpFrom: ?*posix.sockaddr, + lpFromlen: ?*i32, + lpOverlapped: ?*OVERLAPPED, + lpCompletionRoutine: ?LPWSAOVERLAPPED_COMPLETION_ROUTINE, + ) callconv(.winapi) i32; + + pub extern "ws2_32" fn shutdown( + s: SOCKET, + how: i32, + ) callconv(.winapi) i32; + + pub extern "ws2_32" fn closesocket( + s: SOCKET, + ) callconv(.winapi) i32; + + pub extern "ws2_32" fn bind( + s: SOCKET, + name: *const posix.sockaddr, + namelen: i32, + ) callconv(.winapi) i32; + + pub extern "ws2_32" fn listen( + s: SOCKET, + backlog: i32, + ) callconv(.winapi) i32; + + pub extern "ws2_32" fn setsockopt( + s: SOCKET, + level: i32, + optname: i32, + optval: [*]const u8, + optlen: i32, + ) callconv(.winapi) i32; + + pub extern "mswsock" fn AcceptEx( + sListenSocket: SOCKET, + sAcceptSocket: SOCKET, + lpOutputBuffer: *anyopaque, + dwReceiveDataLength: u32, + dwLocalAddressLength: u32, + dwRemoteAddressLength: u32, + lpdwBytesReceived: *u32, + lpOverlapped: *OVERLAPPED, + ) callconv(.winapi) BOOL; +}; + +// --- High-level wrapper functions --- + +pub fn unexpectedWSAError(err: ws2_32.WinsockError) error{Unexpected} { + return unexpectedError(@as(Win32Error, @enumFromInt(@intFromEnum(err)))); +} + +pub fn QueryPerformanceCounter() u64 { + var result: win.LARGE_INTEGER = undefined; + std.debug.assert(win.ntdll.RtlQueryPerformanceCounter(&result) != .FALSE); + return @as(u64, @bitCast(result)); +} + +pub fn QueryPerformanceFrequency() u64 { + var result: win.LARGE_INTEGER = undefined; + std.debug.assert(win.ntdll.RtlQueryPerformanceFrequency(&result) != .FALSE); + return @as(u64, @bitCast(result)); +} + +pub const GetQueuedCompletionStatusError = error{ + Aborted, + Cancelled, + EOF, + Timeout, +} || error{Unexpected}; + +pub fn GetQueuedCompletionStatusEx( + completion_port: HANDLE, + completion_port_entries: []OVERLAPPED_ENTRY, + timeout_ms: ?DWORD, + alertable: bool, +) GetQueuedCompletionStatusError!u32 { + var num_entries_removed: u32 = 0; + + const success = kernel32.GetQueuedCompletionStatusEx( + completion_port, + completion_port_entries.ptr, + @as(win.ULONG, @intCast(completion_port_entries.len)), + &num_entries_removed, + timeout_ms orelse INFINITE, + BOOL.fromBool(alertable), + ); + + if (success == .FALSE) { + return switch (kernel32.GetLastError()) { + .ABANDONED_WAIT_0 => error.Aborted, + .OPERATION_ABORTED => error.Cancelled, + .HANDLE_EOF => error.EOF, + .WAIT_TIMEOUT => error.Timeout, + else => |err| unexpectedError(err), + }; + } + + return num_entries_removed; +} + +pub const PostQueuedCompletionStatusError = error{Unexpected}; + +pub fn PostQueuedCompletionStatus( + completion_port: HANDLE, + bytes_transferred_count: DWORD, + completion_key: usize, + lpOverlapped: ?*OVERLAPPED, +) PostQueuedCompletionStatusError!void { + if (kernel32.PostQueuedCompletionStatus(completion_port, bytes_transferred_count, completion_key, lpOverlapped) == .FALSE) { + switch (kernel32.GetLastError()) { + else => |err| return unexpectedError(err), + } + } +} + +pub fn CreateIoCompletionPort( + file_handle: HANDLE, + existing_completion_port: ?HANDLE, + completion_key: usize, + concurrent_thread_count: DWORD, +) error{Unexpected}!HANDLE { + const handle = kernel32.CreateIoCompletionPort(file_handle, existing_completion_port, completion_key, concurrent_thread_count) orelse { + switch (kernel32.GetLastError()) { + .INVALID_PARAMETER => unreachable, + else => |err| return unexpectedError(err), + } + }; + return handle; +} + +var wsa_initialized: bool = false; + +fn callWSAStartup() !void { + if (@atomicLoad(bool, &wsa_initialized, .acquire)) return; + + var wsadata: ws2_32.WSADATA = undefined; + const rc = ws2_32.WSAStartup(0x0202, &wsadata); + if (rc != 0) return error.Unexpected; + + @atomicStore(bool, &wsa_initialized, true, .release); +} + +pub fn WSASocketW( + af: i32, + socket_type: i32, + protocol: i32, + protocolInfo: ?*ws2_32.WSAPROTOCOL_INFOW, + g: u32, + dwFlags: DWORD, +) !ws2_32.SOCKET { + var first = true; + while (true) { + const rc = ws2_32.WSASocketW(af, socket_type, protocol, protocolInfo, g, dwFlags); + if (rc == ws2_32.INVALID_SOCKET) { + switch (ws2_32.WSAGetLastError()) { + .WSAEAFNOSUPPORT => return error.AddressFamilyNotSupported, + .WSAEMFILE => return error.ProcessFdQuotaExceeded, + .WSAENOBUFS => return error.SystemResources, + .WSAEPROTONOSUPPORT => return error.ProtocolNotSupported, + .WSANOTINITIALISED => { + if (!first) return error.Unexpected; + first = false; + try callWSAStartup(); + continue; + }, + else => |err| return unexpectedWSAError(err), + } + } + return rc; + } +} + +pub fn sliceToPrefixedFileW(dir: ?HANDLE, path: []const u8) error{ InvalidWtf8, BadPathName, NameTooLong, Unexpected, AccessDenied, FileNotFound }!PathSpace { + _ = dir; + var result: PathSpace = undefined; + result.len = std.unicode.wtf8ToWtf16Le(&result.data, path) catch return error.InvalidWtf8; + result.data[result.len] = 0; + return result; +} + +pub const PathSpace = struct { + data: [260]u16, + len: usize, + + pub fn span(self: *const PathSpace) [:0]const u16 { + return self.data[0..self.len :0]; + } +}; pub const exp = struct { pub const STATUS_PENDING = 0x00000103; pub const STILL_ACTIVE = STATUS_PENDING; pub const JOBOBJECT_ASSOCIATE_COMPLETION_PORT = extern struct { - CompletionKey: windows.ULONG_PTR, - CompletionPort: windows.HANDLE, + CompletionKey: win.ULONG_PTR, + CompletionPort: win.HANDLE, }; pub const JOBOBJECT_BASIC_LIMIT_INFORMATION = extern struct { - PerProcessUserTimeLimit: windows.LARGE_INTEGER, - PerJobUserTimeLimit: windows.LARGE_INTEGER, - LimitFlags: windows.DWORD, - MinimumWorkingSetSize: windows.SIZE_T, - MaximumWorkingSetSize: windows.SIZE_T, - ActiveProcessLimit: windows.DWORD, - Affinity: windows.ULONG_PTR, - PriorityClass: windows.DWORD, - SchedulingClass: windows.DWORD, + PerProcessUserTimeLimit: win.LARGE_INTEGER, + PerJobUserTimeLimit: win.LARGE_INTEGER, + LimitFlags: win.DWORD, + MinimumWorkingSetSize: win.SIZE_T, + MaximumWorkingSetSize: win.SIZE_T, + ActiveProcessLimit: win.DWORD, + Affinity: win.ULONG_PTR, + PriorityClass: win.DWORD, + SchedulingClass: win.DWORD, }; pub const IO_COUNTERS = extern struct { - ReadOperationCount: windows.ULONGLONG, - WriteOperationCount: windows.ULONGLONG, - OtherOperationCount: windows.ULONGLONG, - ReadTransferCount: windows.ULONGLONG, - WriteTransferCount: windows.ULONGLONG, - OtherTransferCount: windows.ULONGLONG, + ReadOperationCount: win.ULONGLONG, + WriteOperationCount: win.ULONGLONG, + OtherOperationCount: win.ULONGLONG, + ReadTransferCount: win.ULONGLONG, + WriteTransferCount: win.ULONGLONG, + OtherTransferCount: win.ULONGLONG, }; pub const JOBOBJECT_EXTENDED_LIMIT_INFORMATION = extern struct { BasicLimitInformation: JOBOBJECT_BASIC_LIMIT_INFORMATION, IoInfo: IO_COUNTERS, - ProcessMemoryLimit: windows.SIZE_T, - JobMemoryLimit: windows.SIZE_T, - PeakProcessMemoryUsed: windows.SIZE_T, - PeakJobMemoryUsed: windows.SIZE_T, + ProcessMemoryLimit: win.SIZE_T, + JobMemoryLimit: win.SIZE_T, + PeakProcessMemoryUsed: win.SIZE_T, + PeakJobMemoryUsed: win.SIZE_T, }; pub const JOB_OBJECT_LIMIT_ACTIVE_PROCESS = 0x00000008; @@ -107,7 +671,7 @@ pub const exp = struct { JobObjectSecurityLimitInformation = 5, }; - pub const JOB_OBJECT_MSG_TYPE = enum(windows.DWORD) { + pub const JOB_OBJECT_MSG_TYPE = enum(win.DWORD) { JOB_OBJECT_MSG_END_OF_JOB_TIME = 1, JOB_OBJECT_MSG_END_OF_PROCESS_TIME = 2, JOB_OBJECT_MSG_ACTIVE_PROCESS_LIMIT = 3, @@ -123,34 +687,34 @@ pub const exp = struct { _, }; - pub const kernel32 = struct { - pub extern "kernel32" fn GetProcessId(Process: windows.HANDLE) callconv(.winapi) windows.DWORD; - pub extern "kernel32" fn CreateJobObjectA(lpSecurityAttributes: ?*windows.SECURITY_ATTRIBUTES, lpName: ?windows.LPCSTR) callconv(.winapi) windows.HANDLE; - pub extern "kernel32" fn AssignProcessToJobObject(hJob: windows.HANDLE, hProcess: windows.HANDLE) callconv(.winapi) windows.BOOL; + pub const k32 = struct { + pub extern "kernel32" fn GetProcessId(Process: win.HANDLE) callconv(.winapi) win.DWORD; + pub extern "kernel32" fn CreateJobObjectA(lpSecurityAttributes: ?*win.SECURITY_ATTRIBUTES, lpName: ?win.LPCSTR) callconv(.winapi) win.HANDLE; + pub extern "kernel32" fn AssignProcessToJobObject(hJob: win.HANDLE, hProcess: win.HANDLE) callconv(.winapi) win.BOOL; pub extern "kernel32" fn SetInformationJobObject( - hJob: windows.HANDLE, + hJob: win.HANDLE, JobObjectInformationClass: JOBOBJECT_INFORMATION_CLASS, - lpJobObjectInformation: windows.LPVOID, - cbJobObjectInformationLength: windows.DWORD, - ) callconv(.winapi) windows.BOOL; + lpJobObjectInformation: win.LPVOID, + cbJobObjectInformationLength: win.DWORD, + ) callconv(.winapi) win.BOOL; }; pub const CreateFileError = error{} || posix.UnexpectedError; pub fn CreateFile( lpFileName: [*:0]const u16, - dwDesiredAccess: windows.DWORD, - dwShareMode: windows.DWORD, - lpSecurityAttributes: ?*windows.SECURITY_ATTRIBUTES, - dwCreationDisposition: windows.DWORD, - dwFlagsAndAttributes: windows.DWORD, - hTemplateFile: ?windows.HANDLE, - ) CreateFileError!windows.HANDLE { - const handle = windows.kernel32.CreateFileW(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); - if (handle == windows.INVALID_HANDLE_VALUE) { - const err = windows.kernel32.GetLastError(); + dwDesiredAccess: win.DWORD, + dwShareMode: win.DWORD, + lpSecurityAttributes: ?*win.SECURITY_ATTRIBUTES, + dwCreationDisposition: win.DWORD, + dwFlagsAndAttributes: win.DWORD, + hTemplateFile: ?win.HANDLE, + ) CreateFileError!win.HANDLE { + const handle = kernel32.CreateFileW(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); + if (handle == win.INVALID_HANDLE_VALUE) { + const err = kernel32.GetLastError(); return switch (err) { - else => windows.unexpectedError(err), + else => unexpectedError(err), }; } @@ -158,17 +722,17 @@ pub const exp = struct { } pub fn ReadFile( - handle: windows.HANDLE, + handle: win.HANDLE, buffer: []u8, - overlapped: ?*windows.OVERLAPPED, - ) windows.ReadFileError!?usize { - var read: windows.DWORD = 0; - const result: windows.BOOL = windows.kernel32.ReadFile(handle, buffer.ptr, @as(windows.DWORD, @intCast(buffer.len)), &read, overlapped); - if (result == windows.FALSE) { - const err = windows.kernel32.GetLastError(); + overlapped: ?*OVERLAPPED, + ) ReadFileError!?usize { + var read: win.DWORD = 0; + const result = kernel32.ReadFile(handle, buffer.ptr, @as(win.DWORD, @intCast(buffer.len)), &read, overlapped); + if (result == .FALSE) { + const err = kernel32.GetLastError(); return switch (err) { - windows.Win32Error.IO_PENDING => null, - else => windows.unexpectedError(err), + .IO_PENDING => null, + else => unexpectedError(err), }; } @@ -176,17 +740,17 @@ pub const exp = struct { } pub fn WriteFile( - handle: windows.HANDLE, + handle: win.HANDLE, buffer: []const u8, - overlapped: ?*windows.OVERLAPPED, - ) windows.WriteFileError!?usize { - var written: windows.DWORD = 0; - const result: windows.BOOL = windows.kernel32.WriteFile(handle, buffer.ptr, @as(windows.DWORD, @intCast(buffer.len)), &written, overlapped); - if (result == windows.FALSE) { - const err = windows.kernel32.GetLastError(); + overlapped: ?*OVERLAPPED, + ) WriteFileError!?usize { + var written: win.DWORD = 0; + const result = kernel32.WriteFile(handle, buffer.ptr, @as(win.DWORD, @intCast(buffer.len)), &written, overlapped); + if (result == .FALSE) { + const err = kernel32.GetLastError(); return switch (err) { - windows.Win32Error.IO_PENDING => null, - else => windows.unexpectedError(err), + .IO_PENDING => null, + else => unexpectedError(err), }; } @@ -196,55 +760,55 @@ pub const exp = struct { pub const DeleteFileError = error{} || posix.UnexpectedError; pub fn DeleteFile(name: [*:0]const u16) DeleteFileError!void { - const result: windows.BOOL = DeleteFileW(name); - if (result == windows.FALSE) { - const err = windows.kernel32.GetLastError(); + const result = kernel32.DeleteFileW(name); + if (result == .FALSE) { + const err = kernel32.GetLastError(); return switch (err) { - else => windows.unexpectedError(err), + else => unexpectedError(err), }; } } pub const CreateJobObjectError = error{AlreadyExists} || posix.UnexpectedError; pub fn CreateJobObject( - lpSecurityAttributes: ?*windows.SECURITY_ATTRIBUTES, - lpName: ?windows.LPCSTR, - ) !windows.HANDLE { - const handle = exp.kernel32.CreateJobObjectA(lpSecurityAttributes, lpName); - return switch (windows.kernel32.GetLastError()) { + lpSecurityAttributes: ?*win.SECURITY_ATTRIBUTES, + lpName: ?win.LPCSTR, + ) !win.HANDLE { + const handle = exp.k32.CreateJobObjectA(lpSecurityAttributes, lpName); + return switch (kernel32.GetLastError()) { .SUCCESS => handle, .ALREADY_EXISTS => CreateJobObjectError.AlreadyExists, - else => |err| windows.unexpectedError(err), + else => |err| unexpectedError(err), }; } - pub fn AssignProcessToJobObject(hJob: windows.HANDLE, hProcess: windows.HANDLE) posix.UnexpectedError!void { - const result: windows.BOOL = exp.kernel32.AssignProcessToJobObject(hJob, hProcess); - if (result == windows.FALSE) { - const err = windows.kernel32.GetLastError(); + pub fn AssignProcessToJobObject(hJob: win.HANDLE, hProcess: win.HANDLE) posix.UnexpectedError!void { + const result = exp.k32.AssignProcessToJobObject(hJob, hProcess); + if (result == .FALSE) { + const err = kernel32.GetLastError(); return switch (err) { - else => windows.unexpectedError(err), + else => unexpectedError(err), }; } } pub fn SetInformationJobObject( - hJob: windows.HANDLE, + hJob: win.HANDLE, JobObjectInformationClass: JOBOBJECT_INFORMATION_CLASS, - lpJobObjectInformation: windows.LPVOID, - cbJobObjectInformationLength: windows.DWORD, + lpJobObjectInformation: win.LPVOID, + cbJobObjectInformationLength: win.DWORD, ) posix.UnexpectedError!void { - const result: windows.BOOL = exp.kernel32.SetInformationJobObject( + const result = exp.k32.SetInformationJobObject( hJob, JobObjectInformationClass, lpJobObjectInformation, cbJobObjectInformationLength, ); - if (result == windows.FALSE) { - const err = windows.kernel32.GetLastError(); + if (result == .FALSE) { + const err = kernel32.GetLastError(); return switch (err) { - else => windows.unexpectedError(err), + else => unexpectedError(err), }; } }