From 05c89fbbb140254510fa812dce1a35c289bec4ca Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 17 Apr 2026 06:46:35 -0700 Subject: [PATCH] Zig 0.16 Compatibility This makes libxev work with Zig 0.16. **Fair warning that this is messy.** The goal was to keep libxev working with minimal external API changes. That means we don't play nicely with `std.Io` yet and I'm not 100% sure what the path forward is for that since in many ways libxev is an alternate implementation but std.Io doesn't have full coverage for our functionality so we can't simply switch to it either. In cases wher we need an Io here, I use the global single threaded Io which preserves the Zig 0.15 behavior. Like I said, the goal is to get people who use libxev (including me) upgraded to 0.16, not to fully adapt to the new idioms. Particularly nasty is the large amount of shims we need in `src/posix.zig` and `src/windows.zig` to address removed APIs from Zig. --- .github/workflows/test.yml | 2 +- README.md | 21 +- build.zig | 94 +++-- build.zig.zon | 2 +- examples/_basic.zig | 1 - flake.lock | 37 +- flake.nix | 2 +- src/ThreadPool.zig | 10 +- src/backend/epoll.zig | 225 ++++++---- src/backend/io_uring.zig | 67 +-- src/backend/iocp.zig | 121 ++++-- src/backend/kqueue.zig | 265 +++++++----- src/backend/wasi_poll.zig | 10 +- src/bench/async1.zig | 13 +- src/bench/async2.zig | 4 +- src/bench/async4.zig | 4 +- src/bench/async8.zig | 4 +- src/bench/async_pummel_1.zig | 15 +- src/bench/async_pummel_2.zig | 4 +- src/bench/async_pummel_4.zig | 4 +- src/bench/async_pummel_8.zig | 4 +- src/bench/million-timers.zig | 34 +- src/bench/ping-pongs.zig | 77 ++-- src/bench/ping-udp1.zig | 21 +- src/bench/udp_pummel_1v1.zig | 19 +- src/dynamic.zig | 57 ++- src/linux/timerfd.zig | 3 +- src/posix.zig | 694 ++++++++++++++++++++++++++++++ src/watcher/async.zig | 37 +- src/watcher/file.zig | 73 ++-- src/watcher/process.zig | 48 ++- src/watcher/stream.zig | 5 +- src/watcher/tcp.zig | 82 ++-- src/watcher/udp.zig | 93 ++-- src/windows.zig | 792 ++++++++++++++++++++++++++++++----- 35 files changed, 2226 insertions(+), 718 deletions(-) create mode 100644 src/posix.zig 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), }; } }