From c44e12dcd309ae4f18903d66e262c07f0a318296 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 7 Jan 2026 12:09:09 -0800 Subject: [PATCH 01/23] std: find a better home for the "preopens" concept --- doc/langref/wasi_preopens.zig | 8 ++-- lib/std/fs.zig | 1 - lib/std/fs/wasi.zig | 55 ------------------------- lib/std/os/wasi.zig | 5 ++- lib/std/process.zig | 5 +++ lib/std/process/Preopens.zig | 75 +++++++++++++++++++++++++++++++++++ lib/std/start.zig | 4 ++ src/Compilation.zig | 16 ++++---- src/main.zig | 14 +++---- src/print_env.zig | 7 +--- 10 files changed, 106 insertions(+), 84 deletions(-) delete mode 100644 lib/std/fs/wasi.zig create mode 100644 lib/std/process/Preopens.zig diff --git a/doc/langref/wasi_preopens.zig b/doc/langref/wasi_preopens.zig index 99ab36f31483d318a3be9c254a4927427d2e4ca4..423e79f3c928621db7e88a02d74e82bbab60dbeb 100644 --- a/doc/langref/wasi_preopens.zig +++ b/doc/langref/wasi_preopens.zig @@ -1,10 +1,8 @@ const std = @import("std"); -pub fn main(init: std.process.Init) !void { - const preopens = try std.fs.wasi.preopensAlloc(init.arena.allocator()); - - for (preopens.names, 0..) |preopen, i| { - std.debug.print("{d}: {s}\n", .{ i, preopen }); +pub fn main(init: std.process.Init) void { + for (init.preopens.map.keys(), 0..) |preopen, i| { + std.log.info("{d}: {s}", .{ i, preopen }); } } diff --git a/lib/std/fs.zig b/lib/std/fs.zig index 4d149dbbda40ef20498b74e5776848e48f795e4c..93c2a9a0ae8495275b230a29f63fc26c0e8fa701 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -4,7 +4,6 @@ const std = @import("std.zig"); /// Deprecated, use `std.Io.Dir.path`. pub const path = @import("fs/path.zig"); -pub const wasi = @import("fs/wasi.zig"); pub const base64_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".*; diff --git a/lib/std/fs/wasi.zig b/lib/std/fs/wasi.zig deleted file mode 100644 index e17a852a9bb7b6754839b8dcc249afd3a5efbb9d..0000000000000000000000000000000000000000 --- a/lib/std/fs/wasi.zig +++ /dev/null @@ -1,55 +0,0 @@ -const std = @import("std"); -const builtin = @import("builtin"); -const mem = std.mem; -const math = std.math; -const fs = std.fs; -const assert = std.debug.assert; -const Allocator = mem.Allocator; -const wasi = std.os.wasi; -const fd_t = wasi.fd_t; -const prestat_t = wasi.prestat_t; - -pub const Preopens = struct { - // Indexed by file descriptor number. - names: []const []const u8, - - pub fn find(p: Preopens, name: []const u8) ?std.posix.fd_t { - for (p.names, 0..) |elem_name, i| { - if (mem.eql(u8, elem_name, name)) { - return @intCast(i); - } - } - return null; - } -}; - -pub fn preopensAlloc(gpa: Allocator) Allocator.Error!Preopens { - var names: std.ArrayList([]const u8) = .empty; - defer names.deinit(gpa); - - try names.ensureUnusedCapacity(gpa, 3); - - names.appendAssumeCapacity("stdin"); // 0 - names.appendAssumeCapacity("stdout"); // 1 - names.appendAssumeCapacity("stderr"); // 2 - while (true) { - const fd = @as(wasi.fd_t, @intCast(names.items.len)); - var prestat: prestat_t = undefined; - switch (wasi.fd_prestat_get(fd, &prestat)) { - .SUCCESS => {}, - .OPNOTSUPP, .BADF => return .{ .names = try names.toOwnedSlice(gpa) }, - else => @panic("fd_prestat_get: unexpected error"), - } - try names.ensureUnusedCapacity(gpa, 1); - // This length does not include a null byte. Let's keep it this way to - // gently encourage WASI implementations to behave properly. - const name_len = prestat.u.dir.pr_name_len; - const name = try gpa.alloc(u8, name_len); - errdefer gpa.free(name); - switch (wasi.fd_prestat_dir_name(fd, name.ptr, name.len)) { - .SUCCESS => {}, - else => @panic("fd_prestat_dir_name: unexpected error"), - } - names.appendAssumeCapacity(name); - } -} diff --git a/lib/std/os/wasi.zig b/lib/std/os/wasi.zig index caef010683b99f36d482901a6b2d09e86b702ab3..87e1bc59c2e7a1c3af4c3e3410d80313da8644dc 100644 --- a/lib/std/os/wasi.zig +++ b/lib/std/os/wasi.zig @@ -288,8 +288,9 @@ pub const oflags_t = packed struct(u16) { _: u12 = 0, }; -pub const preopentype_t = u8; -pub const PREOPENTYPE_DIR: preopentype_t = 0; +pub const preopentype_t = enum(u8) { + DIR = 0, +}; pub const prestat_t = extern struct { pr_type: preopentype_t, diff --git a/lib/std/process.zig b/lib/std/process.zig index d8db82cc0582821c4c2b9a6243ca32149905491a..0936116bf02b1feb451094db5f4b33176dd55b5f 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -18,6 +18,7 @@ const max_path_bytes = std.fs.max_path_bytes; pub const Child = @import("process/Child.zig"); pub const Args = @import("process/Args.zig"); pub const Environ = @import("process/Environ.zig"); +pub const Preopens = @import("process/Preopens.zig"); /// This is the global, process-wide protection to coordinate stderr writes. /// @@ -48,6 +49,10 @@ pub const Init = struct { io: Io, /// Environment variables, initialized with `gpa`. Not threadsafe. environ_map: *Environ.Map, + /// Named files that have been provided by the parent process. This is + /// mainly useful on WASI, but can be used on other systems to mimic the + /// behavior with respect to stdio. + preopens: Preopens, /// Alternative to `Init` as the first parameter of the main function. pub const Minimal = struct { diff --git a/lib/std/process/Preopens.zig b/lib/std/process/Preopens.zig new file mode 100644 index 0000000000000000000000000000000000000000..8223c29f83fadce644c49c874e0039d9e090ed96 --- /dev/null +++ b/lib/std/process/Preopens.zig @@ -0,0 +1,75 @@ +const Preopens = @This(); + +const builtin = @import("builtin"); +const native_os = builtin.os.tag; + +const std = @import("../std.zig"); +const Io = std.Io; +const Allocator = std.mem.Allocator; + +map: Map, + +pub const empty: Preopens = switch (native_os) { + .wasi => .{ .map = .empty }, + else => .{ .map = {} }, +}; + +pub const Map = switch (native_os) { + // Indexed by file descriptor number. + .wasi => std.StringArrayHashMapUnmanaged(void), + else => void, +}; + +pub const Resource = union(enum) { + file: Io.File, + dir: Io.Dir, +}; + +pub fn get(p: *const Preopens, name: []const u8) ?Resource { + switch (native_os) { + .wasi => { + const index = p.map.getIndex(name) orelse return null; + if (index <= 2) return .{ .file = .{ .handle = @intCast(index) } }; + return .{ .dir = .{ .handle = @intCast(index) } }; + }, + else => { + if (std.mem.eql(u8, name, "stdin")) return .{ .file = .stdin() }; + if (std.mem.eql(u8, name, "stdout")) return .{ .file = .stdout() }; + if (std.mem.eql(u8, name, "stderr")) return .{ .file = .stderr() }; + return null; + }, + } +} + +pub const InitError = Allocator.Error || error{Unexpected}; + +pub fn init(arena: Allocator) InitError!Preopens { + if (native_os != .wasi) return .{ .map = {} }; + const wasi = std.os.wasi; + var map: Map = .empty; + + try map.ensureUnusedCapacity(arena, 3); + + map.putAssumeCapacityNoClobber("stdin", {}); // 0 + map.putAssumeCapacityNoClobber("stdout", {}); // 1 + map.putAssumeCapacityNoClobber("stderr", {}); // 2 + while (true) { + const fd: wasi.fd_t = @intCast(map.entries.len); + var prestat: wasi.prestat_t = undefined; + switch (wasi.fd_prestat_get(fd, &prestat)) { + .SUCCESS => {}, + .OPNOTSUPP, .BADF => return .{ .map = map }, + else => return error.Unexpected, + } + try map.ensureUnusedCapacity(arena, 1); + // This length does not include a null byte. Let's keep it this way to + // gently encourage WASI implementations to behave properly. + const name_len = prestat.u.dir.pr_name_len; + const name = try arena.alloc(u8, name_len); + switch (wasi.fd_prestat_dir_name(fd, name.ptr, name.len)) { + .SUCCESS => {}, + else => return error.Unexpected, + } + map.putAssumeCapacityNoClobber(name, {}); + } +} diff --git a/lib/std/start.zig b/lib/std/start.zig index 8d2e2a9df2df2ff95459d6a9d5cfd695d24aa1d7..685fda36353fbf77f3224b4c787af3b9a3f80626 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -708,6 +708,9 @@ inline fn callMain(args: std.process.Args.Vector, environ: std.process.Environ.B std.process.fatal("failed to parse environment variables: {t}", .{err}); defer environ_map.deinit(); + const preopens = std.process.Preopens.init(arena_allocator.allocator()) catch |err| + std.process.fatal("failed to init preopens: {t}", .{err}); + return wrapMain(root.main(.{ .minimal = .{ .args = .{ .vector = args }, @@ -717,6 +720,7 @@ inline fn callMain(args: std.process.Args.Vector, environ: std.process.Environ.B .gpa = gpa, .io = threaded.io(), .environ_map = &environ_map, + .preopens = preopens, })); } diff --git a/src/Compilation.zig b/src/Compilation.zig index 5d369594c88330b509cc1625b05c5bd9af4d6f4c..d617f0a0e5f22446023cd8996a0678691afc507d 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -758,10 +758,7 @@ pub const Directories = struct { search, global, }, - wasi_preopens: switch (builtin.target.os.tag) { - .wasi => fs.wasi.Preopens, - else => void, - }, + preopens: std.process.Preopens, self_exe_path: switch (builtin.target.os.tag) { .wasi => void, else => []const u8, @@ -776,7 +773,7 @@ pub const Directories = struct { const zig_lib: Cache.Directory = d: { if (override_zig_lib) |path| break :d openUnresolved(arena, io, cwd, path, .@"zig lib"); - if (wasi) break :d openWasiPreopen(wasi_preopens, "/lib"); + if (wasi) break :d getPreopen(preopens, "/lib"); break :d introspect.findZigLibDirFromSelfExe(arena, io, cwd, self_exe_path) catch |err| { fatal("unable to find zig installation directory '{s}': {t}", .{ self_exe_path, err }); }; @@ -784,7 +781,7 @@ pub const Directories = struct { const global_cache: Cache.Directory = d: { if (override_global_cache) |path| break :d openUnresolved(arena, io, cwd, path, .@"global cache"); - if (wasi) break :d openWasiPreopen(wasi_preopens, "/cache"); + if (wasi) break :d getPreopen(preopens, "/cache"); const path = introspect.resolveGlobalCacheDir(arena, environ_map) catch |err| { fatal("unable to resolve zig cache directory: {t}", .{err}); }; @@ -817,11 +814,12 @@ pub const Directories = struct { .local_cache = local_cache, }; } - fn openWasiPreopen(preopens: fs.wasi.Preopens, name: []const u8) Cache.Directory { + fn getPreopen(preopens: std.process.Preopens, name: []const u8) Cache.Directory { return .{ .path = if (std.mem.eql(u8, name, ".")) null else name, - .handle = .{ - .handle = preopens.find(name) orelse fatal("WASI preopen not found: '{s}'", .{name}), + .handle = switch (preopens.get(name) orelse fatal("preopen not found: '{s}'", .{name})) { + .file => fatal("preopen {s} is not a directory", .{name}), + .dir => |d| d, }, }; } diff --git a/src/main.zig b/src/main.zig index 97a4e53c2f6e4e70d22741674b89eb9a88a6ac89..1da2c76a1a1526cb9c5cdbfa22102f038edaf706 100644 --- a/src/main.zig +++ b/src/main.zig @@ -55,11 +55,11 @@ pub const std_options_cwd = if (native_os == .wasi) wasi_cwd else null; pub const panic = crash_report.panic; pub const debug = crash_report.debug; -var wasi_preopens: fs.wasi.Preopens = undefined; +var preopens: std.process.Preopens = .empty; pub fn wasi_cwd() Io.Dir { // Expect the first preopen to be current working directory. const cwd_fd: std.posix.fd_t = 3; - assert(mem.eql(u8, wasi_preopens.names[cwd_fd], ".")); + assert(mem.eql(u8, preopens.map.keys()[cwd_fd], ".")); return .{ .handle = cwd_fd }; } @@ -210,7 +210,7 @@ pub fn main(init: std.process.Init.Minimal) anyerror!void { } if (native_os == .wasi) { - wasi_preopens = try fs.wasi.preopensAlloc(arena); + preopens = try .init(arena); } return mainArgs(gpa, arena, io, args, &environ_map); @@ -360,7 +360,7 @@ fn mainArgs( io, &stdout_writer.interface, args, - if (native_os == .wasi) wasi_preopens, + preopens, &host, environ_map, ); @@ -3107,7 +3107,7 @@ fn buildOutputType( else => .search, }; }, - if (native_os == .wasi) wasi_preopens, + preopens, self_exe_path, environ_map, ); @@ -5141,7 +5141,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8, if (override_local_cache_dir) |d| break :path d; break :path try build_root.directory.join(arena, &.{introspect.default_local_zig_cache_basename}); } }, - {}, + .empty, self_exe_path, environ_map, ); @@ -5556,7 +5556,7 @@ fn jitCmd( override_lib_dir, override_global_cache_dir, .global, - if (native_os == .wasi) wasi_preopens, + preopens, self_exe_path, environ_map, ); diff --git a/src/print_env.zig b/src/print_env.zig index ac0578852e5f9d43c925ee4894904cd78062e826..163648321e41dd6c8368c6e0874fcffd13fe183c 100644 --- a/src/print_env.zig +++ b/src/print_env.zig @@ -14,10 +14,7 @@ pub fn cmdEnv( io: Io, out: *std.Io.Writer, args: []const []const u8, - wasi_preopens: switch (builtin.target.os.tag) { - .wasi => std.fs.wasi.Preopens, - else => void, - }, + preopens: std.process.Preopens, host: *const std.Target, environ_map: *std.process.Environ.Map, ) !void { @@ -37,7 +34,7 @@ pub fn cmdEnv( override_lib_dir, override_global_cache_dir, .global, - if (builtin.target.os.tag == .wasi) wasi_preopens, + preopens, if (builtin.target.os.tag != .wasi) self_exe_path, environ_map, ); -- 2.54.0 From 7248b4a4e437223a0c826dfbcb76b9835fce16d0 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 7 Jan 2026 12:44:17 -0800 Subject: [PATCH 02/23] std.fs: deprecate base64 APIs 100% of std.fs is now deprecated. --- lib/compiler/aro/aro/Driver.zig | 4 ++-- lib/std/Io/net/test.zig | 4 ++-- lib/std/fs.zig | 15 ++++++--------- lib/std/fs/test.zig | 4 ++-- lib/std/testing.zig | 4 ++-- 5 files changed, 14 insertions(+), 17 deletions(-) diff --git a/lib/compiler/aro/aro/Driver.zig b/lib/compiler/aro/aro/Driver.zig index 154b4d56a51c757168adb302c3d509e3492bff8b..5c356dc5b1300dfc2edad8a2d8cc3adc9862f6f7 100644 --- a/lib/compiler/aro/aro/Driver.zig +++ b/lib/compiler/aro/aro/Driver.zig @@ -1219,12 +1219,12 @@ pub fn getDepFileName(d: *Driver, source: Source, buf: *[std.fs.max_name_bytes]u fn getRandomFilename(d: *Driver, buf: *[std.fs.max_name_bytes]u8, extension: []const u8) ![]const u8 { const io = d.comp.io; const random_bytes_count = 12; - const sub_path_len = comptime std.fs.base64_encoder.calcSize(random_bytes_count); + const sub_path_len = comptime std.base64.url_safe.Encoder.calcSize(random_bytes_count); var random_bytes: [random_bytes_count]u8 = undefined; io.random(&random_bytes); var random_name: [sub_path_len]u8 = undefined; - _ = std.fs.base64_encoder.encode(&random_name, &random_bytes); + _ = std.base64.url_safe.Encoder.encode(&random_name, &random_bytes); const fmt_template = "/tmp/{s}{s}"; const fmt_args = .{ diff --git a/lib/std/Io/net/test.zig b/lib/std/Io/net/test.zig index 75f72d3aff02a23f2483dc2256d49f3fb7cc2f47..7904bc4641170ee448af9319f4804d21b439d4ca 100644 --- a/lib/std/Io/net/test.zig +++ b/lib/std/Io/net/test.zig @@ -310,11 +310,11 @@ test "listen on a unix socket, send bytes, receive bytes" { fn generateFileName(io: Io, base_name: []const u8) ![]const u8 { const random_bytes_count = 12; - const sub_path_len = comptime std.fs.base64_encoder.calcSize(random_bytes_count); + const sub_path_len = comptime std.base64.url_safe.Encoder.calcSize(random_bytes_count); var random_bytes: [12]u8 = undefined; io.random(&random_bytes); var sub_path: [sub_path_len]u8 = undefined; - _ = std.fs.base64_encoder.encode(&sub_path, &random_bytes); + _ = std.base64.url_safe.Encoder.encode(&sub_path, &random_bytes); return std.fmt.allocPrint(testing.allocator, "{s}-{s}", .{ sub_path[0..], base_name }); } diff --git a/lib/std/fs.zig b/lib/std/fs.zig index 93c2a9a0ae8495275b230a29f63fc26c0e8fa701..a127da1696d3ed3e134abd96dc078131ac727733 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -4,15 +4,12 @@ const std = @import("std.zig"); /// Deprecated, use `std.Io.Dir.path`. pub const path = @import("fs/path.zig"); - -pub const base64_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".*; - -/// Base64 encoder, replacing the standard `+/` with `-_` so that it can be used in a file name on any filesystem. -pub const base64_encoder = std.base64.Base64Encoder.init(base64_alphabet, null); - -/// Base64 decoder, replacing the standard `+/` with `-_` so that it can be used in a file name on any filesystem. -pub const base64_decoder = std.base64.Base64Decoder.init(base64_alphabet, null); - +/// Deprecated, use `std.base64.url_safe_alphabet_chars`. +pub const base64_alphabet = std.base64.url_safe_alphabet_chars; +/// Deprecated, use `std.base64.url_safe.Encoder`. +pub const base64_encoder = std.base64.url_safe.Encoder; +/// Deprecated, use `std.base64.url_safe.Decoder`. +pub const base64_decoder = std.base64.url_safe.Decoder; /// Deprecated, use `std.Io.Dir.max_path_bytes`. pub const max_path_bytes = std.Io.Dir.max_path_bytes; /// Deprecated, use `std.Io.Dir.max_name_bytes`. diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index a584fa347718bd349c229ecf99bf28f6f8abac82..a9fdab1ae0cb38dccb10145af148c689aeaf0036 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -1777,8 +1777,8 @@ test "open file with exclusive nonblocking lock twice (absolute paths)" { var random_bytes: [12]u8 = undefined; io.random(&random_bytes); - var random_b64: [std.fs.base64_encoder.calcSize(random_bytes.len)]u8 = undefined; - _ = std.fs.base64_encoder.encode(&random_b64, &random_bytes); + var random_b64: [std.base64.url_safe.Encoder.calcSize(random_bytes.len)]u8 = undefined; + _ = std.base64.url_safe.Encoder.encode(&random_b64, &random_bytes); const sub_path = random_b64 ++ "-zig-test-absolute-paths.txt"; diff --git a/lib/std/testing.zig b/lib/std/testing.zig index 113886966372ee706197525c788a7ddc287c5898..03e1d06c1894c3a01ae60b21d1c007ce41b0fa9d 100644 --- a/lib/std/testing.zig +++ b/lib/std/testing.zig @@ -618,7 +618,7 @@ pub const TmpDir = struct { sub_path: [sub_path_len]u8, const random_bytes_count = 12; - const sub_path_len = std.fs.base64_encoder.calcSize(random_bytes_count); + const sub_path_len = std.base64.url_safe.Encoder.calcSize(random_bytes_count); pub fn cleanup(self: *TmpDir) void { self.dir.close(io); @@ -633,7 +633,7 @@ pub fn tmpDir(opts: Io.Dir.OpenOptions) TmpDir { var random_bytes: [TmpDir.random_bytes_count]u8 = undefined; io.random(&random_bytes); var sub_path: [TmpDir.sub_path_len]u8 = undefined; - _ = std.fs.base64_encoder.encode(&sub_path, &random_bytes); + _ = std.base64.url_safe.Encoder.encode(&sub_path, &random_bytes); const cwd = Io.Dir.cwd(); var cache_dir = cwd.createDirPathOpen(io, ".zig-cache", .{}) catch -- 2.54.0 From 520397b48fac7fd0c107257c57f2cca4f0a40a98 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 7 Jan 2026 13:32:20 -0800 Subject: [PATCH 03/23] tests: delete redundant cases These were originally supposed to be incremental test cases but have long since been made redundant. --- test/cases/function_pointers.zig | 30 ------------------------------ test/cases/print_u32s.zig | 28 ---------------------------- 2 files changed, 58 deletions(-) delete mode 100644 test/cases/function_pointers.zig delete mode 100644 test/cases/print_u32s.zig diff --git a/test/cases/function_pointers.zig b/test/cases/function_pointers.zig deleted file mode 100644 index 546eef72a4802907e78c7132a08c70fd46e94795..0000000000000000000000000000000000000000 --- a/test/cases/function_pointers.zig +++ /dev/null @@ -1,30 +0,0 @@ -const std = @import("std"); - -const PrintFn = *const fn () void; - -pub fn main() void { - var printFn: PrintFn = stopSayingThat; - var i: u32 = 0; - while (i < 4) : (i += 1) printFn(); - - printFn = moveEveryZig; - printFn(); -} - -fn stopSayingThat() void { - _ = std.posix.write(1, "Hello, my name is Inigo Montoya; you killed my father, prepare to die.\n") catch {}; -} - -fn moveEveryZig() void { - _ = std.posix.write(1, "All your codebase are belong to us\n") catch {}; -} - -// run -// target=x86_64-macos -// -// Hello, my name is Inigo Montoya; you killed my father, prepare to die. -// Hello, my name is Inigo Montoya; you killed my father, prepare to die. -// Hello, my name is Inigo Montoya; you killed my father, prepare to die. -// Hello, my name is Inigo Montoya; you killed my father, prepare to die. -// All your codebase are belong to us -// diff --git a/test/cases/print_u32s.zig b/test/cases/print_u32s.zig deleted file mode 100644 index 0c73361278c884932a54522c234f29ca741a63e5..0000000000000000000000000000000000000000 --- a/test/cases/print_u32s.zig +++ /dev/null @@ -1,28 +0,0 @@ -const std = @import("std"); - -pub fn main() void { - printNumberHex(0x00000000); - printNumberHex(0xaaaaaaaa); - printNumberHex(0xdeadbeef); - printNumberHex(0x31415926); -} - -fn printNumberHex(x: u32) void { - const digit_chars = "0123456789abcdef"; - var i: u5 = 28; - while (true) : (i -= 4) { - const digit = (x >> i) & 0xf; - _ = std.posix.write(1, &.{digit_chars[digit]}) catch {}; - if (i == 0) break; - } - _ = std.posix.write(1, "\n") catch {}; -} - -// run -// target=x86_64-macos -// -// 00000000 -// aaaaaaaa -// deadbeef -// 31415926 -// -- 2.54.0 From 2f372b3dc00c60a625e1cc3518fa1bda3429de59 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 7 Jan 2026 13:54:02 -0800 Subject: [PATCH 04/23] goodbye posix.write see #6600 --- lib/std/Thread.zig | 2 +- lib/std/posix.zig | 97 ------------------------------------------ lib/std/posix/test.zig | 13 ++++-- 3 files changed, 10 insertions(+), 102 deletions(-) diff --git a/lib/std/Thread.zig b/lib/std/Thread.zig index 1c361506b72e3eb1a8c6125642b68ba1e6fba51a..c3b86d5b6ac95d2b7b047045584f17e78b16bfb6 100644 --- a/lib/std/Thread.zig +++ b/lib/std/Thread.zig @@ -175,7 +175,7 @@ pub const SetNameError = error{ Unsupported, Unexpected, InvalidWtf8, -} || posix.PrctlError || posix.WriteError || Io.File.OpenError || std.fmt.BufPrintError; +} || posix.PrctlError || Io.File.Writer.Error || Io.File.OpenError || std.fmt.BufPrintError; pub fn setName(self: Thread, io: Io, name: []const u8) SetNameError!void { if (name.len > max_name_len) return error.NameTooLong; diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 60938818df2a5450a627a1f3247131b896223848..150287538a9223f4ddba705e092faf94ed1d3a17 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -446,103 +446,6 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize { } } -pub const WriteError = error{ - DiskQuota, - FileTooBig, - InputOutput, - NoSpaceLeft, - DeviceBusy, - InvalidArgument, - - /// File descriptor does not hold the required rights to write to it. - AccessDenied, - PermissionDenied, - BrokenPipe, - SystemResources, - Canceled, - NotOpenForWriting, - - /// The process cannot access the file because another process has locked - /// a portion of the file. Windows-only. - LockViolation, - - /// This error occurs when no global event loop is configured, - /// and reading from the file descriptor would block. - WouldBlock, - - /// Connection reset by peer. - ConnectionResetByPeer, - - /// This error occurs in Linux if the process being written to - /// no longer exists. - ProcessNotFound, - /// This error occurs when a device gets disconnected before or mid-flush - /// while it's being written to - errno(6): No such device or address. - NoDevice, - - /// The socket type requires that message be sent atomically, and the size of the message - /// to be sent made this impossible. The message is not transmitted. - MessageOversize, -} || UnexpectedError; - -/// Write to a file descriptor. -/// Retries when interrupted by a signal. -/// Returns the number of bytes written. If nonzero bytes were supplied, this will be nonzero. -/// -/// Note that a successful write() may transfer fewer than count bytes. Such partial writes can -/// occur for various reasons; for example, because there was insufficient space on the disk -/// device to write all of the requested bytes, or because a blocked write() to a socket, pipe, or -/// similar was interrupted by a signal handler after it had transferred some, but before it had -/// transferred all of the requested bytes. In the event of a partial write, the caller can make -/// another write() call to transfer the remaining bytes. The subsequent call will either -/// transfer further bytes or may result in an error (e.g., if the disk is now full). -/// -/// For POSIX systems, if `fd` is opened in non blocking mode, the function will -/// return error.WouldBlock when EAGAIN is received. -/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are -/// used to perform the I/O. `error.WouldBlock` is not possible on Windows. -/// -/// Linux has a limit on how many bytes may be transferred in one `write` call, which is `0x7ffff000` -/// on both 64-bit and 32-bit systems. This is due to using a signed C int as the return value, as -/// well as stuffing the errno codes into the last `4096` values. This is noted on the `write` man page. -/// The limit on Darwin is `0x7fffffff`, trying to read more than that returns EINVAL. -/// The corresponding POSIX limit is `maxInt(isize)`. -pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize { - if (bytes.len == 0) return 0; - if (native_os == .windows) @compileError("unsupported OS"); - if (native_os == .wasi) @compileError("unsupported OS"); - - const max_count = switch (native_os) { - .linux => 0x7ffff000, - .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => maxInt(i32), - else => maxInt(isize), - }; - while (true) { - const rc = system.write(fd, bytes.ptr, @min(bytes.len, max_count)); - switch (errno(rc)) { - .SUCCESS => return @intCast(rc), - .INTR => continue, - .INVAL => return error.InvalidArgument, - .FAULT => unreachable, - .AGAIN => return error.WouldBlock, - .BADF => return error.NotOpenForWriting, // can be a race condition. - .DESTADDRREQ => unreachable, // `connect` was never called. - .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, - .NXIO => return error.NoDevice, - .MSGSIZE => return error.MessageOversize, - else => |err| return unexpectedErrno(err), - } - } -} - pub const OpenError = std.Io.File.OpenError || error{WouldBlock}; /// Open and possibly create a file. Keeps trying if it gets interrupted. diff --git a/lib/std/posix/test.zig b/lib/std/posix/test.zig index 47341ea525fa9138ec77efe848bf9c5251d7ad51..a0b0c92ce53dc7d1abfa2b1c091315e8718e893a 100644 --- a/lib/std/posix/test.zig +++ b/lib/std/posix/test.zig @@ -121,13 +121,18 @@ test "pipe" { if (native_os == .windows or native_os == .wasi) return error.SkipZigTest; + const io = testing.io; + const fds = try std.Io.Threaded.pipe2(.{}); - try expect((try posix.write(fds[1], "hello")) == 5); + const out: Io.File = .{ .handle = fds[0] }; + const in: Io.File = .{ .handle = fds[1] }; + try in.writeStreamingAll(io, "hello"); var buf: [16]u8 = undefined; - try expect((try posix.read(fds[0], buf[0..])) == 5); + try expect((try out.readStreaming(io, &.{&buf})) == 5); + try expectEqualSlices(u8, buf[0..5], "hello"); - posix.close(fds[1]); - posix.close(fds[0]); + out.close(io); + in.close(io); } test "memfd_create" { -- 2.54.0 From 213ef953462341b44c52adc0696ab3fbc60e82b4 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 7 Jan 2026 14:12:34 -0800 Subject: [PATCH 05/23] goodbye posix.open see #6600 --- lib/std/dynamic_library.zig | 38 ++++++++------------ lib/std/os/linux/IoUring/test.zig | 42 ++++++++++++---------- lib/std/posix.zig | 60 ------------------------------- lib/std/posix/test.zig | 20 ++++++----- 4 files changed, 51 insertions(+), 109 deletions(-) diff --git a/lib/std/dynamic_library.zig b/lib/std/dynamic_library.zig index 05a2562e29b0312ec9089b7b1bf2349a5d671a08..1d4c8b965fdb7b06a3721545d3eee3b2c1a10e00 100644 --- a/lib/std/dynamic_library.zig +++ b/lib/std/dynamic_library.zig @@ -148,7 +148,7 @@ const ElfDynLibError = error{ ElfHashTableNotFound, Canceled, Streaming, -} || posix.OpenError || posix.MMapError; +} || Io.File.OpenError || posix.MMapError; pub const ElfDynLib = struct { strings: [*:0]u8, @@ -177,27 +177,20 @@ pub const ElfDynLib = struct { return parent; } - fn resolveFromSearchPath(io: Io, search_path: []const u8, file_name: []const u8, delim: u8) ?posix.fd_t { + fn resolveFromSearchPath(io: Io, search_path: []const u8, file_name: []const u8, delim: u8) ?Io.File { var paths = std.mem.tokenizeScalar(u8, search_path, delim); while (paths.next()) |p| { var dir = openPath(io, p) catch continue; defer dir.close(io); - const fd = posix.openat(dir.handle, file_name, .{ - .ACCMODE = .RDONLY, - .CLOEXEC = true, - }, 0) catch continue; - return fd; + return dir.openFile(io, file_name, .{}) catch continue; } return null; } - fn resolveFromParent(io: Io, dir_path: []const u8, file_name: []const u8) ?posix.fd_t { + fn resolveFromParent(io: Io, dir_path: []const u8, file_name: []const u8) ?Io.File { var dir = Io.Dir.cwd().openDir(io, dir_path, .{}) catch return null; defer dir.close(io); - return posix.openat(dir.handle, file_name, .{ - .ACCMODE = .RDONLY, - .CLOEXEC = true, - }, 0) catch null; + return dir.openFile(io, file_name, .{}) catch null; } // This implements enough to be able to load system libraries in general @@ -205,10 +198,10 @@ pub const ElfDynLib = struct { // - DT_RPATH of the calling binary is not used as a search path // - DT_RUNPATH of the calling binary is not used as a search path // - /etc/ld.so.cache is not read - fn resolveFromName(io: Io, path_or_name: []const u8, LD_LIBRARY_PATH: ?[]const u8) !posix.fd_t { + fn resolveFromName(io: Io, path_or_name: []const u8, LD_LIBRARY_PATH: ?[]const u8) !Io.File { // If filename contains a slash ("/"), then it is interpreted as a (relative or absolute) pathname if (std.mem.findScalarPos(u8, path_or_name, 0, '/')) |_| { - return posix.open(path_or_name, .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0); + return Io.Dir.cwd().openFile(io, path_or_name, .{}); } // Only read LD_LIBRARY_PATH if the binary is not setuid/setgid @@ -216,15 +209,15 @@ pub const ElfDynLib = struct { std.os.linux.getegid() == std.os.linux.getgid()) { if (LD_LIBRARY_PATH) |ld_library_path| { - if (resolveFromSearchPath(io, ld_library_path, path_or_name, ':')) |fd| { - return fd; + if (resolveFromSearchPath(io, ld_library_path, path_or_name, ':')) |file| { + return file; } } } // Lastly the directories /lib and /usr/lib are searched (in this exact order) - if (resolveFromParent(io, "/lib", path_or_name)) |fd| return fd; - if (resolveFromParent(io, "/usr/lib", path_or_name)) |fd| return fd; + if (resolveFromParent(io, "/lib", path_or_name)) |file| return file; + if (resolveFromParent(io, "/usr/lib", path_or_name)) |file| return file; return error.FileNotFound; } @@ -232,10 +225,9 @@ pub const ElfDynLib = struct { pub fn open(path: []const u8, LD_LIBRARY_PATH: ?[]const u8) Error!ElfDynLib { const io = std.Options.debug_io; - const fd = try resolveFromName(io, path, LD_LIBRARY_PATH); - defer posix.close(fd); + const file = try resolveFromName(io, path, LD_LIBRARY_PATH); + defer file.close(io); - const file: Io.File = .{ .handle = fd }; const stat = try file.stat(io); const size = std.math.cast(usize, stat.size) orelse return error.FileTooBig; @@ -248,7 +240,7 @@ pub const ElfDynLib = struct { mem.alignForward(usize, size, page_size), posix.PROT.READ, .{ .TYPE = .PRIVATE }, - fd, + file.handle, 0, ); defer posix.munmap(file_bytes); @@ -318,7 +310,7 @@ pub const ElfDynLib = struct { extended_memsz, prot, .{ .TYPE = .PRIVATE, .FIXED = true }, - fd, + file.handle, ph.p_offset - extra_bytes, ); } else { diff --git a/lib/std/os/linux/IoUring/test.zig b/lib/std/os/linux/IoUring/test.zig index 899a6dae6402f0e38598d85a6d38868c05d4ba83..88e5db88c40078c91f67dc138049c92da4b823ee 100644 --- a/lib/std/os/linux/IoUring/test.zig +++ b/lib/std/os/linux/IoUring/test.zig @@ -932,6 +932,8 @@ test "accept/connect/recv/cancel" { } test "register_files_update" { + const io = testing.io; + var ring = IoUring.init(1, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, error.PermissionDenied => return error.SkipZigTest, @@ -939,13 +941,13 @@ test "register_files_update" { }; defer ring.deinit(); - const fd = try posix.openZ("/dev/zero", .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0); - defer posix.close(fd); + const file = try Io.Dir.openFileAbsolute(io, "/dev/zero", .{}); + defer file.close(io); var registered_fds = [_]linux.fd_t{0} ** 2; const fd_index = 0; const fd_index2 = 1; - registered_fds[fd_index] = fd; + registered_fds[fd_index] = file.handle; registered_fds[fd_index2] = -1; ring.register_files(registered_fds[0..]) catch |err| switch (err) { @@ -957,10 +959,10 @@ test "register_files_update" { // Test IORING_REGISTER_FILES_UPDATE // Only available since Linux 5.5 - const fd2 = try posix.openZ("/dev/zero", .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0); - defer posix.close(fd2); + const file2 = try Io.Dir.openFileAbsolute(io, "/dev/zero", .{}); + defer file2.close(io); - registered_fds[fd_index] = fd2; + registered_fds[fd_index] = file2.handle; registered_fds[fd_index2] = -1; try ring.register_files_update(0, registered_fds[0..]); @@ -1339,6 +1341,8 @@ test "linkat" { } test "provide_buffers: read" { + const io = testing.io; + var ring = IoUring.init(1, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, error.PermissionDenied => return error.SkipZigTest, @@ -1346,8 +1350,8 @@ test "provide_buffers: read" { }; defer ring.deinit(); - const fd = try posix.openZ("/dev/zero", .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0); - defer posix.close(fd); + const file = try Io.Dir.openFileAbsolute(io, "/dev/zero", .{}); + defer file.close(io); const group_id = 1337; const buffer_id = 0; @@ -1380,9 +1384,9 @@ test "provide_buffers: read" { var i: usize = 0; while (i < buffers.len) : (i += 1) { - const sqe = try ring.read(0xdededede, fd, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0); + const sqe = try ring.read(0xdededede, file.handle, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0); try testing.expectEqual(linux.IORING_OP.READ, sqe.opcode); - try testing.expectEqual(@as(i32, fd), sqe.fd); + try testing.expectEqual(@as(i32, file.handle), sqe.fd); try testing.expectEqual(@as(u64, 0), sqe.addr); try testing.expectEqual(@as(u32, buffer_len), sqe.len); try testing.expectEqual(@as(u16, group_id), sqe.buf_index); @@ -1406,9 +1410,9 @@ test "provide_buffers: read" { // This read should fail { - const sqe = try ring.read(0xdfdfdfdf, fd, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0); + const sqe = try ring.read(0xdfdfdfdf, file.handle, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0); try testing.expectEqual(linux.IORING_OP.READ, sqe.opcode); - try testing.expectEqual(@as(i32, fd), sqe.fd); + try testing.expectEqual(@as(i32, file.handle), sqe.fd); try testing.expectEqual(@as(u64, 0), sqe.addr); try testing.expectEqual(@as(u32, buffer_len), sqe.len); try testing.expectEqual(@as(u16, group_id), sqe.buf_index); @@ -1445,9 +1449,9 @@ test "provide_buffers: read" { // Final read which should work { - const sqe = try ring.read(0xdfdfdfdf, fd, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0); + const sqe = try ring.read(0xdfdfdfdf, file.handle, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0); try testing.expectEqual(linux.IORING_OP.READ, sqe.opcode); - try testing.expectEqual(@as(i32, fd), sqe.fd); + try testing.expectEqual(@as(i32, file.handle), sqe.fd); try testing.expectEqual(@as(u64, 0), sqe.addr); try testing.expectEqual(@as(u32, buffer_len), sqe.len); try testing.expectEqual(@as(u16, group_id), sqe.buf_index); @@ -1469,6 +1473,8 @@ test "provide_buffers: read" { } test "remove_buffers" { + const io = testing.io; + var ring = IoUring.init(1, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, error.PermissionDenied => return error.SkipZigTest, @@ -1476,8 +1482,8 @@ test "remove_buffers" { }; defer ring.deinit(); - const fd = try posix.openZ("/dev/zero", .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0); - defer posix.close(fd); + const file = try Io.Dir.openFileAbsolute(io, "/dev/zero", .{}); + defer file.close(io); const group_id = 1337; const buffer_id = 0; @@ -1522,7 +1528,7 @@ test "remove_buffers" { // This read should work { - _ = try ring.read(0xdfdfdfdf, fd, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0); + _ = try ring.read(0xdfdfdfdf, file.handle, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0); try testing.expectEqual(@as(u32, 1), try ring.submit()); const cqe = try ring.copy_cqe(); @@ -1542,7 +1548,7 @@ test "remove_buffers" { // Final read should _not_ work { - _ = try ring.read(0xdfdfdfdf, fd, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0); + _ = try ring.read(0xdfdfdfdf, file.handle, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0); try testing.expectEqual(@as(u32, 1), try ring.submit()); const cqe = try ring.copy_cqe(); diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 150287538a9223f4ddba705e092faf94ed1d3a17..84619317aa8fd1b79c8f5f0ae92e1de6915927da 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -448,66 +448,6 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize { pub const OpenError = std.Io.File.OpenError || error{WouldBlock}; -/// Open and possibly create a file. Keeps trying if it gets interrupted. -/// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On WASI, `file_path` should be encoded as valid UTF-8. -/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding. -/// See also `openZ`. -pub fn open(file_path: []const u8, flags: O, perm: mode_t) OpenError!fd_t { - if (native_os == .windows) { - @compileError("Windows does not support POSIX; use Windows-specific API or cross-platform std.fs API"); - } else if (native_os == .wasi and !builtin.link_libc) { - return openat(AT.FDCWD, file_path, flags, perm); - } - const file_path_c = try toPosixPath(file_path); - return openZ(&file_path_c, flags, perm); -} - -/// Open and possibly create a file. Keeps trying if it gets interrupted. -/// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On WASI, `file_path` should be encoded as valid UTF-8. -/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding. -/// See also `open`. -pub fn openZ(file_path: [*:0]const u8, flags: O, perm: mode_t) OpenError!fd_t { - if (native_os == .windows) { - @compileError("Windows does not support POSIX; use Windows-specific API or cross-platform std.fs API"); - } else if (native_os == .wasi and !builtin.link_libc) { - return open(mem.sliceTo(file_path, 0), flags, perm); - } - - const open_sym = if (lfs64_abi) system.open64 else system.open; - while (true) { - const rc = open_sym(file_path, flags, perm); - switch (errno(rc)) { - .SUCCESS => return @intCast(rc), - .INTR => continue, - - .FAULT => unreachable, - .INVAL => return error.BadPathName, - .ACCES => return error.AccessDenied, - .FBIG => return error.FileTooBig, - .OVERFLOW => return error.FileTooBig, - .ISDIR => return error.IsDir, - .LOOP => return error.SymLinkLoop, - .MFILE => return error.ProcessFdQuotaExceeded, - .NAMETOOLONG => return error.NameTooLong, - .NFILE => return error.SystemFdQuotaExceeded, - .NODEV => return error.NoDevice, - .NOENT => return error.FileNotFound, - // Can happen on Linux when opening procfs files. - .SRCH => return error.FileNotFound, - .NOMEM => return error.SystemResources, - .NOSPC => return error.NoSpaceLeft, - .NOTDIR => return error.NotDir, - .PERM => return error.PermissionDenied, - .EXIST => return error.PathAlreadyExists, - .BUSY => return error.DeviceBusy, - .ILSEQ => return error.BadPathName, - else => |err| return unexpectedErrno(err), - } - } -} - /// Open and possibly create a file. Keeps trying if it gets interrupted. /// `file_path` is relative to the open directory handle `dir_fd`. /// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). diff --git a/lib/std/posix/test.zig b/lib/std/posix/test.zig index a0b0c92ce53dc7d1abfa2b1c091315e8718e893a..31ed23211eb3ed89701bd6b9c96dd927bbc552a1 100644 --- a/lib/std/posix/test.zig +++ b/lib/std/posix/test.zig @@ -463,8 +463,12 @@ test "rename smoke test" { // Create some file using `open`. const file_path = try Dir.path.join(gpa, &.{ base_path, "some_file" }); defer gpa.free(file_path); - const fd = try posix.open(file_path, .{ .ACCMODE = .RDWR, .CREAT = true, .EXCL = true }, mode); - posix.close(fd); + const file = try Io.Dir.cwd().createFile(io, file_path, .{ + .read = true, + .exclusive = true, + .permissions = .fromMode(mode), + }); + file.close(io); // Rename the file const new_file_path = try Dir.path.join(gpa, &.{ base_path, "some_other_file" }); @@ -476,15 +480,15 @@ test "rename smoke test" { // Try opening renamed file const file_path = try Dir.path.join(gpa, &.{ base_path, "some_other_file" }); defer gpa.free(file_path); - const fd = try posix.open(file_path, .{ .ACCMODE = .RDWR }, mode); - posix.close(fd); + const file = try Io.Dir.cwd().openFile(io, file_path, .{ .mode = .read_write }); + file.close(io); } { // Try opening original file - should fail with error.FileNotFound const file_path = try Dir.path.join(gpa, &.{ base_path, "some_file" }); defer gpa.free(file_path); - try expectError(error.FileNotFound, posix.open(file_path, .{ .ACCMODE = .RDWR }, mode)); + try expectError(error.FileNotFound, Io.Dir.cwd().openFile(io, file_path, .{ .mode = .read_write })); } { @@ -503,15 +507,15 @@ test "rename smoke test" { // Try opening renamed directory const file_path = try Dir.path.join(gpa, &.{ base_path, "some_other_dir" }); defer gpa.free(file_path); - const fd = try posix.open(file_path, .{ .ACCMODE = .RDONLY, .DIRECTORY = true }, mode); - posix.close(fd); + const dir = try Io.Dir.cwd().openDir(io, file_path, .{}); + dir.close(io); } { // Try opening original directory - should fail with error.FileNotFound const file_path = try Dir.path.join(gpa, &.{ base_path, "some_dir" }); defer gpa.free(file_path); - try expectError(error.FileNotFound, posix.open(file_path, .{ .ACCMODE = .RDONLY, .DIRECTORY = true }, mode)); + try expectError(error.FileNotFound, Io.Dir.cwd().openDir(io, file_path, .{})); } } -- 2.54.0 From ceae9600e3ce7003907c08af82142242ca3e294c Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 7 Jan 2026 14:25:29 -0800 Subject: [PATCH 06/23] std.posix: remove setuid, seteuid, setgid, setegid, getuid, etc applications and libraries should reach for the lower level APIs instead --- lib/std/posix.zig | 75 ------------------------------------------ lib/std/posix/test.zig | 8 ++--- 2 files changed, 4 insertions(+), 79 deletions(-) diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 84619317aa8fd1b79c8f5f0ae92e1de6915927da..532711f1b42846fc24c031d52c16db7d80cf9792 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -551,67 +551,6 @@ pub fn getcwd(out_buffer: []u8) GetCwdError![]u8 { } } -pub const SetEidError = error{ - InvalidUserId, - PermissionDenied, -} || UnexpectedError; - -pub const SetIdError = error{ResourceLimitReached} || SetEidError; - -pub fn setuid(uid: uid_t) SetIdError!void { - switch (errno(system.setuid(uid))) { - .SUCCESS => return, - .AGAIN => return error.ResourceLimitReached, - .INVAL => return error.InvalidUserId, - .PERM => return error.PermissionDenied, - else => |err| return unexpectedErrno(err), - } -} - -pub fn seteuid(uid: uid_t) SetEidError!void { - switch (errno(system.seteuid(uid))) { - .SUCCESS => return, - .INVAL => return error.InvalidUserId, - .PERM => return error.PermissionDenied, - else => |err| return unexpectedErrno(err), - } -} - -pub fn setgid(gid: gid_t) SetIdError!void { - switch (errno(system.setgid(gid))) { - .SUCCESS => return, - .AGAIN => return error.ResourceLimitReached, - .INVAL => return error.InvalidUserId, - .PERM => return error.PermissionDenied, - else => |err| return unexpectedErrno(err), - } -} - -pub fn setegid(uid: uid_t) SetEidError!void { - switch (errno(system.setegid(uid))) { - .SUCCESS => return, - .INVAL => return error.InvalidUserId, - .PERM => return error.PermissionDenied, - else => |err| return unexpectedErrno(err), - } -} - -pub fn getuid() uid_t { - return system.getuid(); -} - -pub fn geteuid() uid_t { - return system.geteuid(); -} - -pub fn getgid() gid_t { - return system.getgid(); -} - -pub fn getegid() gid_t { - return system.getegid(); -} - pub const SocketError = error{ /// Permission to create a socket of the specified type and/or /// pro‐tocol is denied. @@ -2800,20 +2739,6 @@ pub fn tcsetpgrp(handle: fd_t, pgrp: pid_t) TermioSetPgrpError!void { } } -pub const SetSidError = error{ - /// The calling process is already a process group leader, or the process group ID of a process other than the calling process matches the process ID of the calling process. - PermissionDenied, -} || UnexpectedError; - -pub fn setsid() SetSidError!pid_t { - const rc = system.setsid(); - switch (errno(rc)) { - .SUCCESS => return @intCast(rc), - .PERM => return error.PermissionDenied, - else => |err| return unexpectedErrno(err), - } -} - pub fn signalfd(fd: fd_t, mask: *const sigset_t, flags: u32) !fd_t { const rc = system.signalfd(fd, mask, flags); switch (errno(rc)) { diff --git a/lib/std/posix/test.zig b/lib/std/posix/test.zig index 31ed23211eb3ed89701bd6b9c96dd927bbc552a1..d2e34c0556daaf2c00612b82a7c6e08afec13113 100644 --- a/lib/std/posix/test.zig +++ b/lib/std/posix/test.zig @@ -35,14 +35,14 @@ test "check WASI CWD" { test "getuid" { if (native_os == .windows or native_os == .wasi) return error.SkipZigTest; - _ = posix.getuid(); - _ = posix.geteuid(); + _ = posix.system.getuid(); + _ = posix.system.geteuid(); } test "getgid" { if (native_os == .windows or native_os == .wasi) return error.SkipZigTest; - _ = posix.getgid(); - _ = posix.getegid(); + _ = posix.system.getgid(); + _ = posix.system.getegid(); } test "sigaltstack" { -- 2.54.0 From d10a730480fef4d35c951881948baed991a803de Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 7 Jan 2026 14:35:51 -0800 Subject: [PATCH 07/23] goodbye posix.socket see #6600 --- lib/std/os/linux/IoUring/test.zig | 30 +++++++++++++++++++----------- lib/std/posix.zig | 29 ----------------------------- 2 files changed, 19 insertions(+), 40 deletions(-) diff --git a/lib/std/os/linux/IoUring/test.zig b/lib/std/os/linux/IoUring/test.zig index 88e5db88c40078c91f67dc138049c92da4b823ee..766786547137d25d42e4b5692d6e426d6e438aed 100644 --- a/lib/std/os/linux/IoUring/test.zig +++ b/lib/std/os/linux/IoUring/test.zig @@ -529,7 +529,7 @@ test "sendmsg/recvmsg" { .addr = @bitCast([4]u8{ 127, 0, 0, 1 }), }; - const server = try posix.socket(address_server.family, posix.SOCK.DGRAM, 0); + const server = try socket(address_server.family, posix.SOCK.DGRAM, 0); defer 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))); @@ -539,7 +539,7 @@ test "sendmsg/recvmsg" { var slen: posix.socklen_t = @sizeOf(linux.sockaddr.in); try posix.getsockname(server, addrAny(&address_server), &slen); - const client = try posix.socket(address_server.family, posix.SOCK.DGRAM, 0); + const client = try socket(address_server.family, posix.SOCK.DGRAM, 0); defer posix.close(client); const buffer_send = [_]u8{42} ** 128; @@ -1033,7 +1033,7 @@ test "shutdown" { // Socket bound, expect shutdown to work { - const server = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); + const server = try socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); defer posix.close(server); try posix.setsockopt(server, posix.SOL.SOCKET, posix.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1))); try posix.bind(server, addrAny(&address), @sizeOf(linux.sockaddr.in)); @@ -1066,7 +1066,7 @@ test "shutdown" { // Socket not bound, expect to fail with ENOTCONN { - const server = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); + const server = try socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); defer posix.close(server); const shutdown_sqe = ring.shutdown(0x445445445, server, linux.SHUT.RD) catch |err| switch (err) { @@ -1753,7 +1753,7 @@ test "accept multishot" { var nr: usize = 4; // number of clients to connect while (nr > 0) : (nr -= 1) { // connect client - const client = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); + const client = try socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); errdefer posix.close(client); try posix.connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in)); @@ -1862,7 +1862,7 @@ test "accept_direct" { try testing.expectEqual(@as(u32, 1), try ring.submit()); // connect - const client = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); + const client = try socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); try posix.connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in)); defer posix.close(client); @@ -1896,7 +1896,7 @@ test "accept_direct" { _ = try ring.accept_direct(accept_userdata, listener_socket, null, null, 0); try testing.expectEqual(@as(u32, 1), try ring.submit()); // connect - const client = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); + const client = try socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); try posix.connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in)); defer posix.close(client); // completion with error @@ -1946,7 +1946,7 @@ test "accept_multishot_direct" { for (registered_fds) |_| { // connect - const client = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); + const client = try socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); try posix.connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in)); defer posix.close(client); @@ -1961,7 +1961,7 @@ test "accept_multishot_direct" { // Multishot is terminated (more flag is not set). { // connect - const client = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); + const client = try socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); try posix.connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in)); defer posix.close(client); // completion with error @@ -2617,7 +2617,7 @@ pub fn createSocketTestHarness(ring: *IoUring) !SocketTestHarness { _ = try ring.accept(0xaaaaaaaa, listener_socket, &accept_addr, &accept_addr_len, 0); // Create a TCP client socket - const client = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); + const client = try socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); errdefer posix.close(client); _ = try ring.connect(0xcccccccc, client, addrAny(&address), @sizeOf(linux.sockaddr.in)); @@ -2657,7 +2657,7 @@ pub fn createSocketTestHarness(ring: *IoUring) !SocketTestHarness { fn createListenerSocket(address: *linux.sockaddr.in) !posix.socket_t { const kernel_backlog = 1; - const listener_socket = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); + const listener_socket = try socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); errdefer posix.close(listener_socket); try posix.setsockopt(listener_socket, posix.SOL.SOCKET, posix.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1))); @@ -2695,3 +2695,11 @@ inline fn skipKernelLessThan(required: std.SemanticVersion) !void { fn addrAny(addr: *linux.sockaddr.in) *linux.sockaddr { return @ptrCast(addr); } + +fn socket(domain: u32, socket_type: u32, protocol: u32) !posix.socket_t { + const rc = posix.system.socket(domain, socket_type, protocol); + switch (posix.errno(rc)) { + .SUCCESS => return @intCast(rc), + else => return error.SocketCreationFailure, + } +} diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 532711f1b42846fc24c031d52c16db7d80cf9792..bca811641336e374dd6d4d6e578623f3ca792844 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -579,35 +579,6 @@ pub const SocketError = error{ SocketTypeNotSupported, } || UnexpectedError; -pub fn socket(domain: u32, socket_type: u32, protocol: u32) SocketError!socket_t { - const have_sock_flags = !builtin.target.os.tag.isDarwin() and native_os != .haiku; - const filtered_sock_type = if (!have_sock_flags) - socket_type & ~@as(u32, SOCK.NONBLOCK | SOCK.CLOEXEC) - else - socket_type; - const rc = system.socket(domain, filtered_sock_type, protocol); - switch (errno(rc)) { - .SUCCESS => { - const fd: fd_t = @intCast(rc); - errdefer close(fd); - if (!have_sock_flags) { - try setSockFlags(fd, socket_type); - } - return fd; - }, - .ACCES => return error.AccessDenied, - .AFNOSUPPORT => return error.AddressFamilyUnsupported, - .INVAL => return error.ProtocolFamilyNotAvailable, - .MFILE => return error.ProcessFdQuotaExceeded, - .NFILE => return error.SystemFdQuotaExceeded, - .NOBUFS => return error.SystemResources, - .NOMEM => return error.SystemResources, - .PROTONOSUPPORT => return error.ProtocolNotSupported, - .PROTOTYPE => return error.SocketTypeNotSupported, - else => |err| return unexpectedErrno(err), - } -} - pub fn socketpair(domain: u32, socket_type: u32, protocol: u32) SocketError![2]socket_t { // Note to the future: we could provide a shim here for e.g. windows which // creates a listening socket, then creates a second socket and connects it -- 2.54.0 From ed4dfdcff0d3affb950f5277c2303407d002135a Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 7 Jan 2026 14:38:31 -0800 Subject: [PATCH 08/23] goodbye posix.shutdown see #6600 --- lib/std/posix.zig | 54 ----------------------------------------------- 1 file changed, 54 deletions(-) diff --git a/lib/std/posix.zig b/lib/std/posix.zig index bca811641336e374dd6d4d6e578623f3ca792844..984ba3811e1867f4540aab1e0408802773efb453 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -619,60 +619,6 @@ pub fn socketpair(domain: u32, socket_type: u32, protocol: u32) SocketError![2]s } } -pub const ShutdownError = error{ - ConnectionAborted, - - /// Connection was reset by peer, application should close socket as it is no longer usable. - ConnectionResetByPeer, - BlockingOperationInProgress, - - /// The network subsystem has failed. - NetworkDown, - - /// The socket is not connected (connection-oriented sockets only). - SocketUnconnected, - SystemResources, -} || UnexpectedError; - -pub const ShutdownHow = enum { recv, send, both }; - -/// Shutdown socket send/receive operations -pub fn shutdown(sock: socket_t, how: ShutdownHow) ShutdownError!void { - if (native_os == .windows) { - 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 (0 != result) switch (windows.ws2_32.WSAGetLastError()) { - .ECONNABORTED => return error.ConnectionAborted, - .ECONNRESET => return error.ConnectionResetByPeer, - .EINPROGRESS => return error.BlockingOperationInProgress, - .EINVAL => unreachable, - .ENETDOWN => return error.NetworkDown, - .ENOTCONN => return error.SocketUnconnected, - .ENOTSOCK => unreachable, - .NOTINITIALISED => unreachable, - else => |err| return windows.unexpectedWSAError(err), - }; - } else { - const rc = system.shutdown(sock, switch (how) { - .recv => SHUT.RD, - .send => SHUT.WR, - .both => SHUT.RDWR, - }); - switch (errno(rc)) { - .SUCCESS => return, - .BADF => unreachable, - .INVAL => unreachable, - .NOTCONN => return error.SocketUnconnected, - .NOTSOCK => unreachable, - .NOBUFS => return error.SystemResources, - else => |err| return unexpectedErrno(err), - } - } -} - pub const BindError = error{ SymLinkLoop, NameTooLong, -- 2.54.0 From 4a49546f59db2f90f6f4caaae1e9abccba5f6600 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 7 Jan 2026 14:40:18 -0800 Subject: [PATCH 09/23] goodbye posix.bind see #6600 --- lib/std/os/linux/IoUring/test.zig | 13 ++++++++--- lib/std/posix.zig | 36 ------------------------------- 2 files changed, 10 insertions(+), 39 deletions(-) diff --git a/lib/std/os/linux/IoUring/test.zig b/lib/std/os/linux/IoUring/test.zig index 766786547137d25d42e4b5692d6e426d6e438aed..03477fb88201361995cb8e0eaed4fb09cff2445a 100644 --- a/lib/std/os/linux/IoUring/test.zig +++ b/lib/std/os/linux/IoUring/test.zig @@ -533,7 +533,7 @@ test "sendmsg/recvmsg" { defer 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, addrAny(&address_server), @sizeOf(linux.sockaddr.in)); + try bind(server, addrAny(&address_server), @sizeOf(linux.sockaddr.in)); // set address_server to the OS-chosen IP/port. var slen: posix.socklen_t = @sizeOf(linux.sockaddr.in); @@ -1036,7 +1036,7 @@ test "shutdown" { const server = try socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); defer posix.close(server); try posix.setsockopt(server, posix.SOL.SOCKET, posix.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1))); - try posix.bind(server, addrAny(&address), @sizeOf(linux.sockaddr.in)); + try bind(server, addrAny(&address), @sizeOf(linux.sockaddr.in)); try posix.listen(server, 1); // set address to the OS-chosen IP/port. @@ -2661,7 +2661,7 @@ fn createListenerSocket(address: *linux.sockaddr.in) !posix.socket_t { errdefer posix.close(listener_socket); try posix.setsockopt(listener_socket, posix.SOL.SOCKET, posix.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1))); - try posix.bind(listener_socket, addrAny(address), @sizeOf(linux.sockaddr.in)); + try bind(listener_socket, addrAny(address), @sizeOf(linux.sockaddr.in)); try posix.listen(listener_socket, kernel_backlog); // set address to the OS-chosen IP/port. @@ -2703,3 +2703,10 @@ fn socket(domain: u32, socket_type: u32, protocol: u32) !posix.socket_t { else => return error.SocketCreationFailure, } } + +fn bind(sock: posix.socket_t, addr: *const posix.sockaddr, len: posix.socklen_t) !void { + switch (posix.errno(posix.system.bind(sock, addr, len))) { + .SUCCESS => return, + else => return error.BindFailure, + } +} diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 984ba3811e1867f4540aab1e0408802773efb453..69a26ee2022fd3ad2d97eb68298cab6ddcb62bd5 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -619,42 +619,6 @@ pub fn socketpair(domain: u32, socket_type: u32, protocol: u32) SocketError![2]s } } -pub const BindError = error{ - SymLinkLoop, - NameTooLong, - FileNotFound, - NotDir, - ReadOnlyFileSystem, - AccessDenied, -} || std.Io.net.IpAddress.BindError; - -pub fn bind(sock: socket_t, addr: *const sockaddr, len: socklen_t) BindError!void { - if (native_os == .windows) { - @compileError("use std.Io instead"); - } else { - const rc = system.bind(sock, addr, len); - switch (errno(rc)) { - .SUCCESS => return, - .ACCES, .PERM => return error.AccessDenied, - .ADDRINUSE => return error.AddressInUse, - .BADF => unreachable, // always a race condition if this error is returned - .INVAL => unreachable, // invalid parameters - .NOTSOCK => unreachable, // invalid `sockfd` - .AFNOSUPPORT => return error.AddressFamilyUnsupported, - .ADDRNOTAVAIL => return error.AddressUnavailable, - .FAULT => unreachable, // invalid `addr` pointer - .LOOP => return error.SymLinkLoop, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOMEM => return error.SystemResources, - .NOTDIR => return error.NotDir, - .ROFS => return error.ReadOnlyFileSystem, - else => |err| return unexpectedErrno(err), - } - } - unreachable; -} - pub const ListenError = error{ FileDescriptorNotASocket, OperationUnsupported, -- 2.54.0 From c89df809b9ae2dd0c36071544aced320120d4a9d Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 7 Jan 2026 14:41:38 -0800 Subject: [PATCH 10/23] goodbye posix.listen see #6600 --- lib/std/os/linux/IoUring/test.zig | 11 +++++++++-- lib/std/posix.zig | 21 --------------------- 2 files changed, 9 insertions(+), 23 deletions(-) diff --git a/lib/std/os/linux/IoUring/test.zig b/lib/std/os/linux/IoUring/test.zig index 03477fb88201361995cb8e0eaed4fb09cff2445a..adebad9180d22a3d11eee98c548f9c99bbf1db6d 100644 --- a/lib/std/os/linux/IoUring/test.zig +++ b/lib/std/os/linux/IoUring/test.zig @@ -1037,7 +1037,7 @@ test "shutdown" { defer posix.close(server); try posix.setsockopt(server, posix.SOL.SOCKET, posix.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1))); try bind(server, addrAny(&address), @sizeOf(linux.sockaddr.in)); - try posix.listen(server, 1); + try listen(server, 1); // set address to the OS-chosen IP/port. var slen: posix.socklen_t = @sizeOf(linux.sockaddr.in); @@ -2662,7 +2662,7 @@ fn createListenerSocket(address: *linux.sockaddr.in) !posix.socket_t { try posix.setsockopt(listener_socket, posix.SOL.SOCKET, posix.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1))); try bind(listener_socket, addrAny(address), @sizeOf(linux.sockaddr.in)); - try posix.listen(listener_socket, kernel_backlog); + try listen(listener_socket, kernel_backlog); // set address to the OS-chosen IP/port. var slen: posix.socklen_t = @sizeOf(linux.sockaddr.in); @@ -2710,3 +2710,10 @@ fn bind(sock: posix.socket_t, addr: *const posix.sockaddr, len: posix.socklen_t) else => return error.BindFailure, } } + +fn listen(sock: posix.socket_t, backlog: u31) !void { + switch (posix.errno(posix.system.listen(sock, backlog))) { + .SUCCESS => return, + else => return error.ListenFailure, + } +} diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 69a26ee2022fd3ad2d97eb68298cab6ddcb62bd5..6fbad51263446b14a7b9c300e3cda01e8616d82d 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -619,27 +619,6 @@ pub fn socketpair(domain: u32, socket_type: u32, protocol: u32) SocketError![2]s } } -pub const ListenError = error{ - FileDescriptorNotASocket, - OperationUnsupported, -} || std.Io.net.IpAddress.ListenError || std.Io.net.UnixAddress.ListenError; - -pub fn listen(sock: socket_t, backlog: u31) ListenError!void { - if (native_os == .windows) { - @compileError("use std.Io instead"); - } else { - const rc = system.listen(sock, backlog); - switch (errno(rc)) { - .SUCCESS => return, - .ADDRINUSE => return error.AddressInUse, - .BADF => unreachable, - .NOTSOCK => return error.FileDescriptorNotASocket, - .OPNOTSUPP => return error.OperationUnsupported, - else => |err| return unexpectedErrno(err), - } - } -} - pub const AcceptError = std.Io.net.Server.AcceptError; pub fn accept( -- 2.54.0 From 55ad03e261712c99d6318b6021cc21304d38ae6b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 7 Jan 2026 14:42:26 -0800 Subject: [PATCH 11/23] goodbye posix.accept see #6600 --- lib/std/posix.zig | 51 ----------------------------------------------- 1 file changed, 51 deletions(-) diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 6fbad51263446b14a7b9c300e3cda01e8616d82d..20613b7c1d366afa1e56e60011b00ffccdfb78a2 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -619,57 +619,6 @@ pub fn socketpair(domain: u32, socket_type: u32, protocol: u32) SocketError![2]s } } -pub const AcceptError = std.Io.net.Server.AcceptError; - -pub fn accept( - sock: socket_t, - addr: ?*sockaddr, - addr_size: ?*socklen_t, - flags: u32, -) AcceptError!socket_t { - const have_accept4 = !(builtin.target.os.tag.isDarwin() or native_os == .windows or native_os == .haiku); - assert(0 == (flags & ~@as(u32, SOCK.NONBLOCK | SOCK.CLOEXEC))); // Unsupported flag(s) - - const accepted_sock: socket_t = while (true) { - const rc = if (have_accept4) - system.accept4(sock, addr, addr_size, flags) - else - system.accept(sock, addr, addr_size); - - if (native_os == .windows) { - @compileError("use std.Io instead"); - } else { - switch (errno(rc)) { - .SUCCESS => break @intCast(rc), - .INTR => continue, - .AGAIN => return error.WouldBlock, - .BADF => unreachable, // always a race condition - .CONNABORTED => return error.ConnectionAborted, - .FAULT => unreachable, - .INVAL => return error.SocketNotListening, - .NOTSOCK => unreachable, - .MFILE => return error.ProcessFdQuotaExceeded, - .NFILE => return error.SystemFdQuotaExceeded, - .NOBUFS => return error.SystemResources, - .NOMEM => return error.SystemResources, - .OPNOTSUPP => unreachable, - .PROTO => return error.ProtocolFailure, - .PERM => return error.BlockedByFirewall, - else => |err| return unexpectedErrno(err), - } - } - }; - - errdefer switch (native_os) { - .windows => windows.closesocket(accepted_sock) catch unreachable, - else => close(accepted_sock), - }; - if (!have_accept4) { - try setSockFlags(accepted_sock, flags); - } - return accepted_sock; -} - fn setSockFlags(sock: socket_t, flags: u32) !void { if ((flags & SOCK.CLOEXEC) != 0) { if (native_os == .windows) { -- 2.54.0 From 1b43f27a91cfa2722e256f561b7e08f163913933 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 7 Jan 2026 14:43:37 -0800 Subject: [PATCH 12/23] std.posix: delete epoll APIs see #6600 --- lib/std/posix.zig | 88 ----------------------------------------------- 1 file changed, 88 deletions(-) diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 20613b7c1d366afa1e56e60011b00ffccdfb78a2..ce6e6de15a4c82345f3a4bce99843a2b1c7228e2 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -677,94 +677,6 @@ fn setSockFlags(sock: socket_t, flags: u32) !void { } } -pub const EpollCreateError = error{ - /// The per-user limit on the number of epoll instances imposed by - /// /proc/sys/fs/epoll/max_user_instances was encountered. See epoll(7) for further - /// details. - /// Or, The per-process limit on the number of open file descriptors has been reached. - ProcessFdQuotaExceeded, - - /// The system-wide limit on the total number of open files has been reached. - SystemFdQuotaExceeded, - - /// There was insufficient memory to create the kernel object. - SystemResources, -} || UnexpectedError; - -pub fn epoll_create1(flags: u32) EpollCreateError!i32 { - const rc = system.epoll_create1(flags); - switch (errno(rc)) { - .SUCCESS => return @intCast(rc), - else => |err| return unexpectedErrno(err), - - .INVAL => unreachable, - .MFILE => return error.ProcessFdQuotaExceeded, - .NFILE => return error.SystemFdQuotaExceeded, - .NOMEM => return error.SystemResources, - } -} - -pub const EpollCtlError = error{ - /// op was EPOLL_CTL_ADD, and the supplied file descriptor fd is already registered - /// with this epoll instance. - FileDescriptorAlreadyPresentInSet, - - /// fd refers to an epoll instance and this EPOLL_CTL_ADD operation would result in a - /// circular loop of epoll instances monitoring one another. - OperationCausesCircularLoop, - - /// op was EPOLL_CTL_MOD or EPOLL_CTL_DEL, and fd is not registered with this epoll - /// instance. - FileDescriptorNotRegistered, - - /// There was insufficient memory to handle the requested op control operation. - SystemResources, - - /// The limit imposed by /proc/sys/fs/epoll/max_user_watches was encountered while - /// trying to register (EPOLL_CTL_ADD) a new file descriptor on an epoll instance. - /// See epoll(7) for further details. - UserResourceLimitReached, - - /// The target file fd does not support epoll. This error can occur if fd refers to, - /// for example, a regular file or a directory. - FileDescriptorIncompatibleWithEpoll, -} || UnexpectedError; - -pub fn epoll_ctl(epfd: i32, op: u32, fd: i32, event: ?*system.epoll_event) EpollCtlError!void { - const rc = system.epoll_ctl(epfd, op, fd, event); - switch (errno(rc)) { - .SUCCESS => return, - else => |err| return unexpectedErrno(err), - - .BADF => unreachable, // always a race condition if this happens - .EXIST => return error.FileDescriptorAlreadyPresentInSet, - .INVAL => unreachable, - .LOOP => return error.OperationCausesCircularLoop, - .NOENT => return error.FileDescriptorNotRegistered, - .NOMEM => return error.SystemResources, - .NOSPC => return error.UserResourceLimitReached, - .PERM => return error.FileDescriptorIncompatibleWithEpoll, - } -} - -/// Waits for an I/O event on an epoll file descriptor. -/// Returns the number of file descriptors ready for the requested I/O, -/// or zero if no file descriptor became ready during the requested timeout milliseconds. -pub fn epoll_wait(epfd: i32, events: []system.epoll_event, timeout: i32) usize { - while (true) { - // TODO get rid of the @intCast - const rc = system.epoll_wait(epfd, events.ptr, @intCast(events.len), timeout); - switch (errno(rc)) { - .SUCCESS => return @intCast(rc), - .INTR => continue, - .BADF => unreachable, - .FAULT => unreachable, - .INVAL => unreachable, - else => unreachable, - } - } -} - pub const EventFdError = error{ SystemResources, ProcessFdQuotaExceeded, -- 2.54.0 From 02c260dd06988f6acd8a1cbfbacb306d9c367ced Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 7 Jan 2026 14:45:09 -0800 Subject: [PATCH 13/23] goodbye posix.getsockname see #6600 --- lib/std/os/linux/IoUring/test.zig | 15 +++++++++++---- lib/std/posix.zig | 29 ----------------------------- 2 files changed, 11 insertions(+), 33 deletions(-) diff --git a/lib/std/os/linux/IoUring/test.zig b/lib/std/os/linux/IoUring/test.zig index adebad9180d22a3d11eee98c548f9c99bbf1db6d..fb0013b9d1e1a286a0bca39406878a9ce9f11f11 100644 --- a/lib/std/os/linux/IoUring/test.zig +++ b/lib/std/os/linux/IoUring/test.zig @@ -537,7 +537,7 @@ test "sendmsg/recvmsg" { // set address_server to the OS-chosen IP/port. var slen: posix.socklen_t = @sizeOf(linux.sockaddr.in); - try posix.getsockname(server, addrAny(&address_server), &slen); + try getsockname(server, addrAny(&address_server), &slen); const client = try socket(address_server.family, posix.SOCK.DGRAM, 0); defer posix.close(client); @@ -1041,7 +1041,7 @@ test "shutdown" { // set address to the OS-chosen IP/port. var slen: posix.socklen_t = @sizeOf(linux.sockaddr.in); - try posix.getsockname(server, addrAny(&address), &slen); + try getsockname(server, addrAny(&address), &slen); const shutdown_sqe = try ring.shutdown(0x445445445, server, linux.SHUT.RD); try testing.expectEqual(linux.IORING_OP.SHUTDOWN, shutdown_sqe.opcode); @@ -2462,7 +2462,7 @@ test "bind/listen/connect" { // Read system assigned port into addr var addr_len: posix.socklen_t = @sizeOf(linux.sockaddr.in); - try posix.getsockname(listen_fd, addrAny(&addr), &addr_len); + try getsockname(listen_fd, addrAny(&addr), &addr_len); break :brk listen_fd; }; @@ -2666,7 +2666,7 @@ fn createListenerSocket(address: *linux.sockaddr.in) !posix.socket_t { // set address to the OS-chosen IP/port. var slen: posix.socklen_t = @sizeOf(linux.sockaddr.in); - try posix.getsockname(listener_socket, addrAny(address), &slen); + try getsockname(listener_socket, addrAny(address), &slen); return listener_socket; } @@ -2717,3 +2717,10 @@ fn listen(sock: posix.socket_t, backlog: u31) !void { else => return error.ListenFailure, } } + +fn getsockname(sock: posix.socket_t, addr: *posix.sockaddr, addrlen: *posix.socklen_t) !void { + switch (posix.errno(posix.system.getsockname(sock, addr, addrlen))) { + .SUCCESS => return, + else => return error.GetSockNameFailure, + } +} diff --git a/lib/std/posix.zig b/lib/std/posix.zig index ce6e6de15a4c82345f3a4bce99843a2b1c7228e2..373b38be374f097eccb60e1d6c81a93b9b895979 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -710,35 +710,6 @@ pub const GetSockNameError = error{ FileDescriptorNotASocket, } || UnexpectedError; -pub fn getsockname(sock: socket_t, addr: *sockaddr, addrlen: *socklen_t) GetSockNameError!void { - if (native_os == .windows) { - const rc = windows.getsockname(sock, addr, addrlen); - if (rc == windows.ws2_32.SOCKET_ERROR) { - switch (windows.ws2_32.WSAGetLastError()) { - .NOTINITIALISED => unreachable, - .ENETDOWN => return error.NetworkDown, - .EFAULT => unreachable, // addr or addrlen have invalid pointers or addrlen points to an incorrect value - .ENOTSOCK => return error.FileDescriptorNotASocket, - .EINVAL => return error.SocketNotBound, - else => |err| return windows.unexpectedWSAError(err), - } - } - return; - } else { - const rc = system.getsockname(sock, addr, addrlen); - switch (errno(rc)) { - .SUCCESS => return, - else => |err| return unexpectedErrno(err), - - .BADF => unreachable, // always a race condition - .FAULT => unreachable, - .INVAL => unreachable, // invalid parameters - .NOTSOCK => return error.FileDescriptorNotASocket, - .NOBUFS => return error.SystemResources, - } - } -} - pub fn getpeername(sock: socket_t, addr: *sockaddr, addrlen: *socklen_t) GetSockNameError!void { if (native_os == .windows) { const rc = windows.getpeername(sock, addr, addrlen); -- 2.54.0 From 829afe98d16d72a9a3af3ae30622ce6576c8b27f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 7 Jan 2026 14:49:00 -0800 Subject: [PATCH 14/23] std.posix: remove getsockopt, getsockoptError see #6600 --- lib/std/posix.zig | 65 ----------------------------------------------- 1 file changed, 65 deletions(-) diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 373b38be374f097eccb60e1d6c81a93b9b895979..03bf830a4781ac9d79ca5a100d283b23143318be 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -773,71 +773,6 @@ pub fn connect(sock: socket_t, sock_addr: *const sockaddr, len: socklen_t) Conne } } -pub const GetSockOptError = error{ - /// The calling process does not have the appropriate privileges. - AccessDenied, - - /// The option is not supported by the protocol. - InvalidProtocolOption, - - /// Insufficient resources are available in the system to complete the call. - SystemResources, -} || UnexpectedError; - -pub fn getsockopt(fd: socket_t, level: i32, optname: u32, opt: []u8) GetSockOptError!void { - var len: socklen_t = @intCast(opt.len); - switch (errno(system.getsockopt(fd, level, optname, opt.ptr, &len))) { - .SUCCESS => { - std.debug.assert(len == opt.len); - }, - .BADF => unreachable, - .NOTSOCK => unreachable, - .INVAL => unreachable, - .FAULT => unreachable, - .NOPROTOOPT => return error.InvalidProtocolOption, - .NOMEM => return error.SystemResources, - .NOBUFS => return error.SystemResources, - .ACCES => return error.AccessDenied, - else => |err| return unexpectedErrno(err), - } -} - -pub fn getsockoptError(sockfd: fd_t) ConnectError!void { - var err_code: i32 = undefined; - var size: u32 = @sizeOf(u32); - const rc = system.getsockopt(sockfd, SOL.SOCKET, SO.ERROR, @ptrCast(&err_code), &size); - assert(size == 4); - switch (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.AddressUnavailable, - .AFNOSUPPORT => return error.AddressFamilyUnsupported, - .AGAIN => return error.SystemResources, - .ALREADY => return error.ConnectionPending, - .BADF => unreachable, // sockfd is not a valid open file descriptor. - .CONNREFUSED => return error.ConnectionRefused, - .FAULT => unreachable, // The socket structure address is outside the user's address space. - .ISCONN => return error.AlreadyConnected, // The socket is already connected. - .HOSTUNREACH => return error.NetworkUnreachable, - .NETUNREACH => return error.NetworkUnreachable, - .NOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket. - .PROTOTYPE => unreachable, // The socket type does not support the requested communications protocol. - .TIMEDOUT => return error.Timeout, - .CONNRESET => return error.ConnectionResetByPeer, - else => |err| return unexpectedErrno(err), - }, - .BADF => unreachable, // The argument sockfd is not a valid file descriptor. - .FAULT => unreachable, // The address pointed to by optval or optlen is not in a valid part of the process address space. - .INVAL => unreachable, - .NOPROTOOPT => unreachable, // The option is unknown at the level indicated. - .NOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket. - else => |err| return unexpectedErrno(err), - } -} - pub const FStatError = std.Io.File.StatError; /// Return information about a file descriptor. -- 2.54.0 From be0a4dc299c71dd916817ab6c181b8a441f22ddd Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 7 Jan 2026 14:50:25 -0800 Subject: [PATCH 15/23] goodbye posix.fstatat see #6600 --- lib/std/posix.zig | 48 ----------------------------------------------- 1 file changed, 48 deletions(-) diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 03bf830a4781ac9d79ca5a100d283b23143318be..331fa3b1e2aade5bf3b56c58009f8014c1e0b04f 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -792,54 +792,6 @@ pub fn fstat(fd: fd_t) FStatError!Stat { } } -pub const FStatAtError = FStatError || error{ - NameTooLong, - FileNotFound, - SymLinkLoop, - BadPathName, -}; - -/// Similar to `fstat`, but returns stat of a resource pointed to by `pathname` -/// which is relative to `dirfd` handle. -/// On WASI, `pathname` should be encoded as valid UTF-8. -/// On other platforms, `pathname` is an opaque sequence of bytes with no particular encoding. -/// See also `fstatatZ`. -pub fn fstatat(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!Stat { - if (native_os == .wasi and !builtin.link_libc) { - @compileError("use std.Io instead"); - } else if (native_os == .windows) { - @compileError("fstatat is not yet implemented on Windows"); - } else { - const pathname_c = try toPosixPath(pathname); - return fstatatZ(dirfd, &pathname_c, flags); - } -} - -/// Same as `fstatat` but `pathname` is null-terminated. -/// See also `fstatat`. -pub fn fstatatZ(dirfd: fd_t, pathname: [*:0]const u8, flags: u32) FStatAtError!Stat { - if (native_os == .wasi and !builtin.link_libc) { - @compileError("use std.Io instead"); - } - - var stat = mem.zeroes(Stat); - switch (errno(system.fstatat(dirfd, pathname, &stat, flags))) { - .SUCCESS => return stat, - .INVAL => unreachable, - .BADF => unreachable, // Always a race condition. - .NOMEM => return error.SystemResources, - .ACCES => return error.AccessDenied, - .PERM => return error.PermissionDenied, - .FAULT => unreachable, - .NAMETOOLONG => return error.NameTooLong, - .LOOP => return error.SymLinkLoop, - .NOENT => return error.FileNotFound, - .NOTDIR => return error.FileNotFound, - .ILSEQ => return error.BadPathName, - else => |err| return unexpectedErrno(err), - } -} - pub const KQueueError = error{ /// The per-process limit on the number of open file descriptors has been reached. ProcessFdQuotaExceeded, -- 2.54.0 From 2c6304efc798d8a3773ae126661902b3a3214854 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 7 Jan 2026 14:56:13 -0800 Subject: [PATCH 16/23] std: move posix.kqueue to Io.Kqueue.createFileDescriptor --- lib/std/Build/Watch.zig | 4 +++- lib/std/Io/Kqueue.zig | 24 ++++++++++++++++++++++-- lib/std/posix.zig | 18 ------------------ 3 files changed, 25 insertions(+), 21 deletions(-) diff --git a/lib/std/Build/Watch.zig b/lib/std/Build/Watch.zig index 0a0178a018b535f5d06c11275c87753a4a6aef2d..fb80d5d1d560116c41b9b710dddd033859b33b5c 100644 --- a/lib/std/Build/Watch.zig +++ b/lib/std/Build/Watch.zig @@ -1,5 +1,7 @@ const builtin = @import("builtin"); + const std = @import("../std.zig"); +const Io = std.Io; const Step = std.Build.Step; const Allocator = std.mem.Allocator; const assert = std.debug.assert; @@ -666,7 +668,7 @@ const Os = switch (builtin.os.tag) { .dir_table = .{}, .dir_count = 0, .os = .{ - .kq_fd = try posix.kqueue(), + .kq_fd = try Io.Kqueue.createFileDescriptor(), .handles = .empty, }, .generation = 0, diff --git a/lib/std/Io/Kqueue.zig b/lib/std/Io/Kqueue.zig index df9fa1dee68a9deaf03e5a7e074848b7554b7e45..c1230a1ce63b0c969489eed28fb0ffd5c6ea7d95 100644 --- a/lib/std/Io/Kqueue.zig +++ b/lib/std/Io/Kqueue.zig @@ -157,8 +157,11 @@ pub const InitOptions = struct { n_threads: ?usize = null, }; +pub const InitError = Allocator.Error || CreateFileDescriptorError; + pub fn init(k: *Kqueue, gpa: Allocator, options: InitOptions) !void { assert(options.n_threads != 0); + const n_threads = @max(1, options.n_threads orelse std.Thread.getCpuCount() catch 1); const threads_size = n_threads * @sizeOf(Thread); const idle_stack_end_offset = std.mem.alignForward(usize, threads_size + idle_stack_size, std.heap.page_size_max); @@ -204,7 +207,7 @@ pub fn init(k: *Kqueue, gpa: Allocator, options: InitOptions) !void { }, .current_context = &main_fiber.context, .ready_queue = null, - .kq_fd = try posix.kqueue(), + .kq_fd = try createFileDescriptor(), .idle_search_index = 1, .steal_ready_search_index = 1, .wait_queues = .empty, @@ -231,6 +234,23 @@ pub fn deinit(k: *Kqueue) void { k.* = undefined; } +pub const CreateFileDescriptorError = error{ + /// The per-process limit on the number of open file descriptors has been reached. + ProcessFdQuotaExceeded, + /// The system-wide limit on the total number of open files has been reached. + SystemFdQuotaExceeded, +} || Io.Unexpected; + +pub fn createFileDescriptor() CreateFileDescriptorError!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 findReadyFiber(k: *Kqueue, thread: *Thread) ?*Fiber { if (@atomicRmw(?*Fiber, &thread.ready_queue, .Xchg, Fiber.finished, .acquire)) |ready_fiber| { @atomicStore(?*Fiber, &thread.ready_queue, ready_fiber.queue_next, .release); @@ -334,7 +354,7 @@ fn schedule(k: *Kqueue, thread: *Thread, ready_queue: Fiber.Queue) void { .idle_context = undefined, .current_context = &new_thread.idle_context, .ready_queue = ready_queue.head, - .kq_fd = posix.kqueue() catch |err| { + .kq_fd = createFileDescriptor() catch |err| { @atomicStore(u32, &k.threads.reserved, new_thread_index, .release); // no more access to `thread` after giving up reservation std.log.warn("unable to create worker thread due to kqueue init failure: {t}", .{err}); diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 331fa3b1e2aade5bf3b56c58009f8014c1e0b04f..603af9fb458246e7da35ff273addff7ba4b8bbca 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -792,24 +792,6 @@ pub fn fstat(fd: fd_t) FStatError!Stat { } } -pub const KQueueError = error{ - /// The per-process limit on the number of open file descriptors has been reached. - ProcessFdQuotaExceeded, - - /// The system-wide limit on the total number of open files has been reached. - SystemFdQuotaExceeded, -} || UnexpectedError; - -pub fn kqueue() KQueueError!i32 { - const rc = system.kqueue(); - switch (errno(rc)) { - .SUCCESS => return @intCast(rc), - .MFILE => return error.ProcessFdQuotaExceeded, - .NFILE => return error.SystemFdQuotaExceeded, - else => |err| return unexpectedErrno(err), - } -} - pub const KEventError = error{ /// The process does not have permission to register a filter. AccessDenied, -- 2.54.0 From 3961fe3de9713e3e618551cd96873a7b3948a2b2 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 7 Jan 2026 15:00:01 -0800 Subject: [PATCH 17/23] std: move posix.kevent to Io.Kqueue --- lib/std/Build/Watch.zig | 8 +++--- lib/std/Io/Kqueue.zig | 61 +++++++++++++++++++++++++++++++++++++---- lib/std/posix.zig | 48 -------------------------------- 3 files changed, 59 insertions(+), 58 deletions(-) diff --git a/lib/std/Build/Watch.zig b/lib/std/Build/Watch.zig index fb80d5d1d560116c41b9b710dddd033859b33b5c..8e8ef732a6b72724c7b15feb028fe4fe0515ff72 100644 --- a/lib/std/Build/Watch.zig +++ b/lib/std/Build/Watch.zig @@ -699,7 +699,7 @@ const Os = switch (builtin.os.tag) { .data = 0, .udata = gop.index, }}; - _ = try posix.kevent(w.os.kq_fd, &changes, &.{}, null); + _ = try Io.Kqueue.kevent(w.os.kq_fd, &changes, &.{}, null); assert(handles.len == gop.index); try handles.append(gpa, .{ .rs = .{}, @@ -789,7 +789,7 @@ const Os = switch (builtin.os.tag) { }, }; const filtered_changes = if (i == handles.len - 1) changes[0..1] else &changes; - _ = try posix.kevent(w.os.kq_fd, filtered_changes, &.{}, null); + _ = try Io.Kqueue.kevent(w.os.kq_fd, filtered_changes, &.{}, null); if (path.sub_path.len != 0) posix.close(dir_fd); w.dir_table.swapRemoveAt(i); @@ -803,13 +803,13 @@ const Os = switch (builtin.os.tag) { fn wait(w: *Watch, gpa: Allocator, timeout: Timeout) !WaitResult { var timespec_buffer: posix.timespec = undefined; var event_buffer: [100]posix.Kevent = undefined; - var n = try posix.kevent(w.os.kq_fd, &.{}, &event_buffer, timeout.toTimespec(×pec_buffer)); + var n = try Io.Kqueue.kevent(w.os.kq_fd, &.{}, &event_buffer, timeout.toTimespec(×pec_buffer)); if (n == 0) return .timeout; const reaction_sets = w.os.handles.items(.rs); var any_dirty = markDirtySteps(gpa, reaction_sets, event_buffer[0..n], false); timespec_buffer = .{ .sec = 0, .nsec = 0 }; while (n == event_buffer.len) { - n = try posix.kevent(w.os.kq_fd, &.{}, &event_buffer, ×pec_buffer); + n = try Io.Kqueue.kevent(w.os.kq_fd, &.{}, &event_buffer, ×pec_buffer); if (n == 0) break; any_dirty = markDirtySteps(gpa, reaction_sets, event_buffer[0..n], any_dirty); } diff --git a/lib/std/Io/Kqueue.zig b/lib/std/Io/Kqueue.zig index c1230a1ce63b0c969489eed28fb0ffd5c6ea7d95..11932ba21293e049990efc70e1acf7faaff1b1c9 100644 --- a/lib/std/Io/Kqueue.zig +++ b/lib/std/Io/Kqueue.zig @@ -334,7 +334,10 @@ fn schedule(k: *Kqueue, thread: *Thread, ready_queue: Fiber.Queue) void { }, }; // If an error occurs it only pessimises scheduling. - _ = posix.kevent(idle_search_thread.kq_fd, &changes, &.{}, null) catch {}; + _ = kevent(idle_search_thread.kq_fd, &changes, &.{}, null) catch |err| { + // TODO handle EINTR for cancellation purposes + @panic(@errorName(err)); // TODO + }; return; } spawn_thread: { @@ -429,9 +432,9 @@ fn idle(k: *Kqueue, thread: *Thread) void { k.yield(ready_fiber, .nothing); maybe_ready_fiber = null; } - const n = posix.kevent(thread.kq_fd, &.{}, &events_buffer, null) catch |err| { + const n = kevent(thread.kq_fd, &.{}, &events_buffer, null) catch |err| { // TODO handle EINTR for cancellation purposes - @panic(@errorName(err)); + @panic(@errorName(err)); // TODO }; var maybe_ready_queue: ?Fiber.Queue = null; for (events_buffer[0..n]) |event| switch (@as(Completion.UserData, @enumFromInt(event.udata))) { @@ -598,8 +601,9 @@ const SwitchMessage = struct { .udata = @intFromEnum(Completion.UserData.exit), }, }; - _ = posix.kevent(each_thread.kq_fd, &changes, &.{}, null) catch |err| { - @panic(@errorName(err)); + _ = kevent(each_thread.kq_fd, &changes, &.{}, null) catch |err| { + // TODO handle EINTR for cancellation purposes + @panic(@errorName(err)); // TODO }; }, } @@ -1538,7 +1542,8 @@ fn netRead(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net.Strea .udata = @intFromPtr(fiber), }, }; - assert(0 == (posix.kevent(thread.kq_fd, &changes, &.{}, null) catch |err| { + assert(0 == (kevent(thread.kq_fd, &changes, &.{}, null) catch |err| { + // TODO handle EINTR for cancellation purposes @panic(@errorName(err)); // TODO })); } @@ -1774,3 +1779,47 @@ const Condition = struct { wake: Io.Condition.Wake, }, }; + +pub const KEventError = error{ + /// The process does not have permission to register a filter. + AccessDenied, + /// The event could not be found to be modified or deleted. + EventNotFound, + /// No memory was available to register the event. + SystemResources, + /// The specified process to attach to does not exist. + ProcessNotFound, + /// changelist or eventlist had too many items on it. + /// TODO remove this possibility + Overflow, +}; + +pub fn kevent( + kq: i32, + changelist: []const posix.Kevent, + eventlist: []posix.Kevent, + timeout: ?*const posix.timespec, +) KEventError!usize { + 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, // TODO use error.Unexpected for these + .BADF => unreachable, // Always a race condition. + .INTR => continue, // TODO handle cancelation + .INVAL => unreachable, + .NOENT => return error.EventNotFound, + .NOMEM => return error.SystemResources, + .SRCH => return error.ProcessNotFound, + else => unreachable, + } + } +} diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 603af9fb458246e7da35ff273addff7ba4b8bbca..e69ab2a75c7b215ce3d87f32e67bbbd3de4143c3 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -792,54 +792,6 @@ pub fn fstat(fd: fd_t) FStatError!Stat { } } -pub const KEventError = error{ - /// The process does not have permission to register a filter. - AccessDenied, - - /// The event could not be found to be modified or deleted. - EventNotFound, - - /// No memory was available to register the event. - SystemResources, - - /// The specified process to attach to does not exist. - ProcessNotFound, - - /// changelist or eventlist had too many items on it. - /// TODO remove this possibility - Overflow, -}; - -pub fn kevent( - kq: i32, - changelist: []const Kevent, - eventlist: []Kevent, - timeout: ?*const timespec, -) KEventError!usize { - while (true) { - const rc = system.kevent( - kq, - changelist.ptr, - cast(c_int, changelist.len) orelse return error.Overflow, - eventlist.ptr, - cast(c_int, eventlist.len) orelse return error.Overflow, - timeout, - ); - switch (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, - } - } -} - pub const INotifyInitError = error{ ProcessFdQuotaExceeded, SystemFdQuotaExceeded, -- 2.54.0 From 45b931a23fb9a1c68c8de181ac02d39f51f7712b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 7 Jan 2026 15:01:37 -0800 Subject: [PATCH 18/23] goodbye posix.fork see #6600 nobody should be using fork anyway, especially Redis --- lib/std/posix.zig | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/lib/std/posix.zig b/lib/std/posix.zig index e69ab2a75c7b215ce3d87f32e67bbbd3de4143c3..bba202423e5e516cae7eafe7efd6bccb208145ec 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -1046,18 +1046,6 @@ pub fn mprotect(memory: []align(page_size_min) u8, protection: u32) MProtectErro } } -pub const ForkError = error{SystemResources} || UnexpectedError; - -pub fn fork() ForkError!pid_t { - const rc = system.fork(); - switch (errno(rc)) { - .SUCCESS => return @intCast(rc), - .AGAIN => return error.SystemResources, - .NOMEM => return error.SystemResources, - else => |err| return unexpectedErrno(err), - } -} - pub const MMapError = error{ /// The underlying filesystem of the specified file does not support memory mapping. MemoryMappingNotSupported, -- 2.54.0 From 791baefff2c0130dadbce7fd5abc69ec7df8cc53 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 7 Jan 2026 15:02:27 -0800 Subject: [PATCH 19/23] goodbye posix.nanosleep see #6600 --- lib/std/posix.zig | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/lib/std/posix.zig b/lib/std/posix.zig index bba202423e5e516cae7eafe7efd6bccb208145ec..f3976f69097c2e8ba4d28b71002dd8bb75f2fa59 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -1274,31 +1274,6 @@ pub fn fcntl(fd: fd_t, cmd: i32, arg: usize) FcntlError!usize { } } -/// Spurious wakeups are possible and no precision of timing is guaranteed. -pub fn nanosleep(seconds: u64, nanoseconds: u64) void { - var req = timespec{ - .sec = cast(isize, seconds) orelse maxInt(isize), - .nsec = cast(isize, nanoseconds) orelse maxInt(isize), - }; - var rem: timespec = undefined; - while (true) { - switch (errno(system.nanosleep(&req, &rem))) { - .FAULT => unreachable, - .INVAL => { - // Sometimes Darwin returns EINVAL for no reason. - // We treat it as a spurious wakeup. - return; - }, - .INTR => { - req = rem; - continue; - }, - // This prong handles success as well as unexpected errors. - else => return, - } - } -} - pub fn getSelfPhdrs() []std.elf.ElfN.Phdr { const getauxval = if (builtin.link_libc) std.c.getauxval else std.os.linux.getauxval; assert(getauxval(std.elf.AT_PHENT) == @sizeOf(std.elf.ElfN.Phdr)); -- 2.54.0 From d96d7353387e10a2306f05d98955ac22db6e89e7 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 7 Jan 2026 15:06:04 -0800 Subject: [PATCH 20/23] posix: remove send, sendto, sendmsg see #6600 --- lib/std/os/linux/IoUring/test.zig | 10 +- lib/std/posix.zig | 283 ------------------------------ 2 files changed, 9 insertions(+), 284 deletions(-) diff --git a/lib/std/os/linux/IoUring/test.zig b/lib/std/os/linux/IoUring/test.zig index fb0013b9d1e1a286a0bca39406878a9ce9f11f11..c924fb3d2a5f124c40aae20e1b8b9b5178e0d225 100644 --- a/lib/std/os/linux/IoUring/test.zig +++ b/lib/std/os/linux/IoUring/test.zig @@ -1874,7 +1874,7 @@ test "accept_direct" { try testing.expect(cqe_accept.user_data == accept_userdata); // send data - _ = try posix.send(client, buffer_send, 0); + _ = try send(client, buffer_send, 0); // Example of how to use registered fd: // Submit receive to fixed file returned by accept (fd_index). @@ -2724,3 +2724,11 @@ fn getsockname(sock: posix.socket_t, addr: *posix.sockaddr, addrlen: *posix.sock else => return error.GetSockNameFailure, } } + +fn send(sockfd: posix.socket_t, buf: []const u8, flags: u32) !usize { + const rc = posix.system.sendto(sockfd, buf.ptr, buf.len, flags, null, 0); + switch (posix.errno(rc)) { + .SUCCESS => return @intCast(rc), + else => return error.SendFailed, + } +} diff --git a/lib/std/posix.zig b/lib/std/posix.zig index f3976f69097c2e8ba4d28b71002dd8bb75f2fa59..986f3de9da4a610d3e05c89a1087f22d68eb437e 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -1545,289 +1545,6 @@ pub fn uname() utsname { } } -pub const SendError = error{ - /// (For UNIX domain sockets, which are identified by pathname) Write permission is denied - /// on the destination socket file, or search permission is denied for one of the - /// directories the path prefix. (See path_resolution(7).) - /// (For UDP sockets) An attempt was made to send to a network/broadcast address as though - /// it was a unicast address. - AccessDenied, - - /// The socket is marked nonblocking and the requested operation would block, and - /// there is no global event loop configured. - /// It's also possible to get this error under the following condition: - /// (Internet domain datagram sockets) The socket referred to by sockfd had not previously - /// been bound to an address and, upon attempting to bind it to an ephemeral port, it was - /// determined that all port numbers in the ephemeral port range are currently in use. See - /// the discussion of /proc/sys/net/ipv4/ip_local_port_range in ip(7). - WouldBlock, - - /// Another Fast Open is already in progress. - FastOpenAlreadyInProgress, - - /// Connection reset by peer. - ConnectionResetByPeer, - - /// The socket type requires that message be sent atomically, and the size of the message - /// to be sent made this impossible. The message is not transmitted. - MessageOversize, - - /// The output queue for a network interface was full. This generally indicates that the - /// interface has stopped sending, but may be caused by transient congestion. (Normally, - /// this does not occur in Linux. Packets are just silently dropped when a device queue - /// overflows.) - /// This is also caused when there is not enough kernel memory available. - SystemResources, - - /// The local end has been shut down on a connection oriented socket. In this case, the - /// process will also receive a SIGPIPE unless MSG.NOSIGNAL is set. - BrokenPipe, - - FileDescriptorNotASocket, - - /// Network is unreachable. - NetworkUnreachable, - - /// The local network interface used to reach the destination is down. - NetworkDown, - - /// The destination address is not listening. - ConnectionRefused, -} || UnexpectedError; - -pub const SendMsgError = SendError || error{ - /// The passed address didn't have the correct address family in its sa_family field. - AddressFamilyUnsupported, - - /// Returned when socket is AF.UNIX and the given path has a symlink loop. - SymLinkLoop, - - /// Returned when socket is AF.UNIX and the given path length exceeds `max_path_bytes` bytes. - NameTooLong, - - /// Returned when socket is AF.UNIX and the given path does not point to an existing file. - FileNotFound, - NotDir, - - /// The socket is not connected (connection-oriented sockets only). - SocketUnconnected, - AddressUnavailable, -}; - -pub fn sendmsg( - /// The file descriptor of the sending socket. - sockfd: socket_t, - /// Message header and iovecs - msg: *const msghdr_const, - flags: u32, -) SendMsgError!usize { - while (true) { - const rc = system.sendmsg(sockfd, msg, flags); - if (native_os == .windows) { - if (rc == windows.ws2_32.SOCKET_ERROR) { - switch (windows.ws2_32.WSAGetLastError()) { - .EACCES => return error.AccessDenied, - .EADDRNOTAVAIL => return error.AddressUnavailable, - .ECONNRESET => return error.ConnectionResetByPeer, - .EMSGSIZE => return error.MessageOversize, - .ENOBUFS => return error.SystemResources, - .ENOTSOCK => return error.FileDescriptorNotASocket, - .EAFNOSUPPORT => return error.AddressFamilyUnsupported, - .EDESTADDRREQ => unreachable, // A destination address is required. - .EFAULT => unreachable, // The lpBuffers, lpTo, lpOverlapped, lpNumberOfBytesSent, or lpCompletionRoutine parameters are not part of the user address space, or the lpTo parameter is too small. - .EHOSTUNREACH => return error.NetworkUnreachable, - // TODO: EINPROGRESS, EINTR - .EINVAL => unreachable, - .ENETDOWN => return error.NetworkDown, - .ENETRESET => return error.ConnectionResetByPeer, - .ENETUNREACH => return error.NetworkUnreachable, - .ENOTCONN => return error.SocketUnconnected, - .ESHUTDOWN => unreachable, // The socket has been shut down; it is not possible to WSASendTo on a socket after shutdown has been invoked with how set to SD_SEND or SD_BOTH. - .EWOULDBLOCK => return error.WouldBlock, - .NOTINITIALISED => unreachable, // A successful WSAStartup call must occur before using this function. - else => |err| return windows.unexpectedWSAError(err), - } - } else { - return @intCast(rc); - } - } else { - switch (errno(rc)) { - .SUCCESS => return @intCast(rc), - - .ACCES => return error.AccessDenied, - .AGAIN => return error.WouldBlock, - .ALREADY => return error.FastOpenAlreadyInProgress, - .BADF => unreachable, // always a race condition - .CONNRESET => return error.ConnectionResetByPeer, - .DESTADDRREQ => unreachable, // The socket is not connection-mode, and no peer address is set. - .FAULT => unreachable, // An invalid user space address was specified for an argument. - .INTR => continue, - .INVAL => unreachable, // Invalid argument passed. - .ISCONN => unreachable, // connection-mode socket was connected already but a recipient was specified - .MSGSIZE => return error.MessageOversize, - .NOBUFS => return error.SystemResources, - .NOMEM => return error.SystemResources, - .NOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket. - .OPNOTSUPP => unreachable, // Some bit in the flags argument is inappropriate for the socket type. - .PIPE => return error.BrokenPipe, - .AFNOSUPPORT => return error.AddressFamilyUnsupported, - .LOOP => return error.SymLinkLoop, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOTDIR => return error.NotDir, - .HOSTUNREACH => return error.NetworkUnreachable, - .NETUNREACH => return error.NetworkUnreachable, - .NOTCONN => return error.SocketUnconnected, - .NETDOWN => return error.NetworkDown, - else => |err| return unexpectedErrno(err), - } - } - } -} - -pub const SendToError = SendMsgError || error{ - /// The destination address is not reachable by the bound address. - UnreachableAddress, - /// The destination address is not listening. - ConnectionRefused, -}; - -/// Transmit a message to another socket. -/// -/// The `sendto` call may be used only when the socket is in a connected state (so that the intended -/// recipient is known). The following call -/// -/// send(sockfd, buf, len, flags); -/// -/// is equivalent to -/// -/// sendto(sockfd, buf, len, flags, NULL, 0); -/// -/// If sendto() is used on a connection-mode (`SOCK.STREAM`, `SOCK.SEQPACKET`) socket, the arguments -/// `dest_addr` and `addrlen` are asserted to be `null` and `0` respectively, and asserted -/// that the socket was actually connected. -/// Otherwise, the address of the target is given by `dest_addr` with `addrlen` specifying its size. -/// -/// If the message is too long to pass atomically through the underlying protocol, -/// `SendError.MessageOversize` is returned, and the message is not transmitted. -/// -/// There is no indication of failure to deliver. -/// -/// When the message does not fit into the send buffer of the socket, `sendto` normally blocks, -/// unless the socket has been placed in nonblocking I/O mode. In nonblocking mode it would fail -/// with `SendError.WouldBlock`. The `select` call may be used to determine when it is -/// possible to send more data. -pub fn sendto( - /// The file descriptor of the sending socket. - sockfd: socket_t, - /// Message to send. - buf: []const u8, - flags: u32, - dest_addr: ?*const sockaddr, - addrlen: socklen_t, -) SendToError!usize { - if (native_os == .windows) { - switch (windows.sendto(sockfd, buf.ptr, buf.len, flags, dest_addr, addrlen)) { - windows.ws2_32.SOCKET_ERROR => switch (windows.ws2_32.WSAGetLastError()) { - .EACCES => return error.AccessDenied, - .EADDRNOTAVAIL => return error.AddressUnavailable, - .ECONNRESET => return error.ConnectionResetByPeer, - .EMSGSIZE => return error.MessageOversize, - .ENOBUFS => return error.SystemResources, - .ENOTSOCK => return error.FileDescriptorNotASocket, - .EAFNOSUPPORT => return error.AddressFamilyUnsupported, - .EDESTADDRREQ => unreachable, // A destination address is required. - .EFAULT => unreachable, // The lpBuffers, lpTo, lpOverlapped, lpNumberOfBytesSent, or lpCompletionRoutine parameters are not part of the user address space, or the lpTo parameter is too small. - .EHOSTUNREACH => return error.NetworkUnreachable, - // TODO: EINPROGRESS, EINTR - .EINVAL => unreachable, - .ENETDOWN => return error.NetworkDown, - .ENETRESET => return error.ConnectionResetByPeer, - .ENETUNREACH => return error.NetworkUnreachable, - .ENOTCONN => return error.SocketUnconnected, - .ESHUTDOWN => unreachable, // The socket has been shut down; it is not possible to WSASendTo on a socket after shutdown has been invoked with how set to SD_SEND or SD_BOTH. - .EWOULDBLOCK => return error.WouldBlock, - .NOTINITIALISED => unreachable, // A successful WSAStartup call must occur before using this function. - else => |err| return windows.unexpectedWSAError(err), - }, - else => |rc| return @intCast(rc), - } - } - while (true) { - const rc = system.sendto(sockfd, buf.ptr, buf.len, flags, dest_addr, addrlen); - switch (errno(rc)) { - .SUCCESS => return @intCast(rc), - - .ACCES => return error.AccessDenied, - .AGAIN => return error.WouldBlock, - .ALREADY => return error.FastOpenAlreadyInProgress, - .BADF => unreachable, // always a race condition - .CONNREFUSED => return error.ConnectionRefused, - .CONNRESET => return error.ConnectionResetByPeer, - .DESTADDRREQ => unreachable, // The socket is not connection-mode, and no peer address is set. - .FAULT => unreachable, // An invalid user space address was specified for an argument. - .INTR => continue, - .INVAL => return error.UnreachableAddress, - .ISCONN => unreachable, // connection-mode socket was connected already but a recipient was specified - .MSGSIZE => return error.MessageOversize, - .NOBUFS => return error.SystemResources, - .NOMEM => return error.SystemResources, - .NOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket. - .OPNOTSUPP => unreachable, // Some bit in the flags argument is inappropriate for the socket type. - .PIPE => return error.BrokenPipe, - .AFNOSUPPORT => return error.AddressFamilyUnsupported, - .LOOP => return error.SymLinkLoop, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOTDIR => return error.NotDir, - .HOSTUNREACH => return error.NetworkUnreachable, - .NETUNREACH => return error.NetworkUnreachable, - .NOTCONN => return error.SocketUnconnected, - .NETDOWN => return error.NetworkDown, - else => |err| return unexpectedErrno(err), - } - } -} - -/// Transmit a message to another socket. -/// -/// The `send` call may be used only when the socket is in a connected state (so that the intended -/// recipient is known). The only difference between `send` and `write` is the presence of -/// flags. With a zero flags argument, `send` is equivalent to `write`. Also, the following -/// call -/// -/// send(sockfd, buf, len, flags); -/// -/// is equivalent to -/// -/// sendto(sockfd, buf, len, flags, NULL, 0); -/// -/// There is no indication of failure to deliver. -/// -/// When the message does not fit into the send buffer of the socket, `send` normally blocks, -/// unless the socket has been placed in nonblocking I/O mode. In nonblocking mode it would fail -/// with `SendError.WouldBlock`. The `select` call may be used to determine when it is -/// possible to send more data. -pub fn send( - /// The file descriptor of the sending socket. - sockfd: socket_t, - buf: []const u8, - flags: u32, -) SendError!usize { - return sendto(sockfd, buf, flags, null, 0) catch |err| switch (err) { - error.AddressFamilyUnsupported => unreachable, - error.SymLinkLoop => unreachable, - error.NameTooLong => unreachable, - error.FileNotFound => unreachable, - error.NotDir => unreachable, - error.NetworkUnreachable => unreachable, - error.AddressUnavailable => unreachable, - error.SocketUnconnected => unreachable, - error.UnreachableAddress => unreachable, - else => |e| return e, - }; -} - pub const PollError = error{ /// The network subsystem has failed. NetworkDown, -- 2.54.0 From cbd75b484f783e4b388ef4b0fb3662fb4c43bbfa Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 7 Jan 2026 15:08:00 -0800 Subject: [PATCH 21/23] std.posix: remove recvfrom, recvmsg see #6600 --- lib/std/posix.zig | 133 ---------------------------------------------- 1 file changed, 133 deletions(-) diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 986f3de9da4a610d3e05c89a1087f22d68eb437e..e883223b93cca3c804cb137dde29df5b49a5716a 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -1608,139 +1608,6 @@ pub fn ppoll(fds: []pollfd, timeout: ?*const timespec, mask: ?*const sigset_t) P } } -pub const RecvFromError = error{ - /// The socket is marked nonblocking and the requested operation would block, and - /// there is no global event loop configured. - WouldBlock, - - /// A remote host refused to allow the network connection, typically because it is not - /// running the requested service. - ConnectionRefused, - - /// Could not allocate kernel memory. - SystemResources, - - ConnectionResetByPeer, - Timeout, - - /// The socket has not been bound. - SocketNotBound, - - /// The UDP message was too big for the buffer and part of it has been discarded - MessageOversize, - - /// The network subsystem has failed. - NetworkDown, - - /// The socket is not connected (connection-oriented sockets only). - SocketUnconnected, - - /// The other end closed the socket unexpectedly or a read is executed on a shut down socket - BrokenPipe, -} || UnexpectedError; - -pub fn recv(sock: socket_t, buf: []u8, flags: u32) RecvFromError!usize { - return recvfrom(sock, buf, flags, null, null); -} - -/// If `sockfd` is opened in non blocking mode, the function will -/// return error.WouldBlock when EAGAIN is received. -pub fn recvfrom( - sockfd: socket_t, - buf: []u8, - flags: u32, - src_addr: ?*sockaddr, - addrlen: ?*socklen_t, -) RecvFromError!usize { - while (true) { - const rc = system.recvfrom(sockfd, buf.ptr, buf.len, flags, src_addr, addrlen); - if (native_os == .windows) { - if (rc == windows.ws2_32.SOCKET_ERROR) { - switch (windows.ws2_32.WSAGetLastError()) { - .NOTINITIALISED => unreachable, - .ECONNRESET => return error.ConnectionResetByPeer, - .EINVAL => return error.SocketNotBound, - .EMSGSIZE => return error.MessageOversize, - .ENETDOWN => return error.NetworkDown, - .ENOTCONN => return error.SocketUnconnected, - .EWOULDBLOCK => return error.WouldBlock, - .ETIMEDOUT => return error.Timeout, - // TODO: handle more errors - else => |err| return windows.unexpectedWSAError(err), - } - } else { - return @intCast(rc); - } - } else { - switch (errno(rc)) { - .SUCCESS => return @intCast(rc), - .BADF => unreachable, // always a race condition - .FAULT => unreachable, - .INVAL => unreachable, - .NOTCONN => return error.SocketUnconnected, - .NOTSOCK => unreachable, - .INTR => continue, - .AGAIN => return error.WouldBlock, - .NOMEM => return error.SystemResources, - .CONNREFUSED => return error.ConnectionRefused, - .CONNRESET => return error.ConnectionResetByPeer, - .TIMEDOUT => return error.Timeout, - .PIPE => return error.BrokenPipe, - else => |err| return unexpectedErrno(err), - } - } - } -} - -pub const RecvMsgError = RecvFromError || error{ - /// Reception of SCM_RIGHTS fds via ancillary data in msg.control would - /// exceed some system limit (generally this is retryable by trying to - /// receive fewer fds or closing some existing fds) - SystemFdQuotaExceeded, - - /// Reception of SCM_RIGHTS fds via ancillary data in msg.control would - /// exceed some process limit (generally this is retryable by trying to - /// receive fewer fds, closing some existing fds, or changing the ulimit) - ProcessFdQuotaExceeded, -}; - -/// If `sockfd` is opened in non blocking mode, the function will -/// return error.WouldBlock when EAGAIN is received. -pub fn recvmsg( - /// The file descriptor of the sending socket. - sockfd: socket_t, - /// Message header and iovecs - msg: *msghdr, - flags: u32, -) RecvMsgError!usize { - if (@TypeOf(system.recvmsg) == void) - @compileError("recvmsg() not supported on this OS"); - while (true) { - const rc = system.recvmsg(sockfd, msg, flags); - switch (errno(rc)) { - .SUCCESS => return @intCast(rc), - .AGAIN => return error.WouldBlock, - .BADF => unreachable, // always a race condition - .NFILE => return error.SystemFdQuotaExceeded, - .MFILE => return error.ProcessFdQuotaExceeded, - .INTR => continue, - .FAULT => unreachable, // An invalid user space address was specified for an argument. - .INVAL => unreachable, // Invalid argument passed. - .ISCONN => unreachable, // connection-mode socket was connected already but a recipient was specified - .NOBUFS => return error.SystemResources, - .NOMEM => return error.SystemResources, - .NOTCONN => return error.SocketUnconnected, - .NOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket. - .MSGSIZE => return error.MessageOversize, - .PIPE => return error.BrokenPipe, - .OPNOTSUPP => unreachable, // Some bit in the flags argument is inappropriate for the socket type. - .CONNRESET => return error.ConnectionResetByPeer, - .NETDOWN => return error.NetworkDown, - else => |err| return unexpectedErrno(err), - } - } -} - pub const SetSockOptError = error{ /// The socket is already connected, and a specified option cannot be set while the socket is connected. AlreadyConnected, -- 2.54.0 From c0092f5394b918f4a4407b73820574d18edfa1dd Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 7 Jan 2026 15:52:34 -0800 Subject: [PATCH 22/23] std.Io: expose Kqueue and IoUring directly --- lib/std/Io.zig | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 5796ee7eb030afc74f3488996ad16576322f56bc..3440614b5b0e730bd07af7533c6a0b64eeedbcfc 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -540,18 +540,20 @@ test { const Io = @This(); +pub const Threaded = @import("Io/Threaded.zig"); pub const Evented = switch (builtin.os.tag) { .linux => switch (builtin.cpu.arch) { - .x86_64, .aarch64 => @import("Io/IoUring.zig"), + .x86_64, .aarch64 => IoUring, else => void, // context-switching code not implemented yet }, .dragonfly, .freebsd, .netbsd, .openbsd, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => switch (builtin.cpu.arch) { - .x86_64, .aarch64 => @import("Io/Kqueue.zig"), + .x86_64, .aarch64 => Kqueue, else => void, // context-switching code not implemented yet }, else => void, }; -pub const Threaded = @import("Io/Threaded.zig"); +pub const Kqueue = @import("Io/Kqueue.zig"); +pub const IoUring = @import("Io/IoUring.zig"); pub const net = @import("Io/net.zig"); userdata: ?*anyopaque, -- 2.54.0 From ce890060350a848590a7371f11316c2040a89a2b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 7 Jan 2026 18:00:36 -0800 Subject: [PATCH 23/23] std.Io.Kqueue: fix bitrot --- lib/std/Io.zig | 2 +- lib/std/Io/Kqueue.zig | 270 ++++++++++++++---------------------------- 2 files changed, 91 insertions(+), 181 deletions(-) diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 3440614b5b0e730bd07af7533c6a0b64eeedbcfc..5164b75404080bd5cff2fc05da370db710900809 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -1358,7 +1358,7 @@ pub const Mutex = extern struct { pub const init: Mutex = .{ .state = .init(.unlocked) }; - const State = enum(u32) { + pub const State = enum(u32) { unlocked, locked_once, contended, diff --git a/lib/std/Io/Kqueue.zig b/lib/std/Io/Kqueue.zig index 11932ba21293e049990efc70e1acf7faaff1b1c9..f998d5cef8b41461ad47cbcbe31595acbc557927 100644 --- a/lib/std/Io/Kqueue.zig +++ b/lib/std/Io/Kqueue.zig @@ -239,7 +239,7 @@ pub const CreateFileDescriptorError = error{ ProcessFdQuotaExceeded, /// The system-wide limit on the total number of open files has been reached. SystemFdQuotaExceeded, -} || Io.Unexpected; +} || Io.UnexpectedError; pub fn createFileDescriptor() CreateFileDescriptorError!posix.fd_t { const rc = posix.system.kqueue(); @@ -494,14 +494,6 @@ const SwitchMessage = struct { recycle: *Fiber, register_awaiter: *?*Fiber, register_select: []const *Io.AnyFuture, - mutex_lock: struct { - prev_state: Io.Mutex.State, - mutex: *Io.Mutex, - }, - condition_wait: struct { - cond: *Io.Condition, - mutex: *Io.Mutex, - }, exit, }; @@ -537,59 +529,6 @@ const SwitchMessage = struct { } } }, - .mutex_lock => |mutex_lock| { - const prev_fiber: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.prev)); - assert(prev_fiber.queue_next == null); - var prev_state = mutex_lock.prev_state; - while (switch (prev_state) { - else => next_state: { - prev_fiber.queue_next = @ptrFromInt(@intFromEnum(prev_state)); - break :next_state @cmpxchgWeak( - Io.Mutex.State, - &mutex_lock.mutex.state, - prev_state, - @enumFromInt(@intFromPtr(prev_fiber)), - .release, - .acquire, - ); - }, - .unlocked => @cmpxchgWeak( - Io.Mutex.State, - &mutex_lock.mutex.state, - .unlocked, - .locked_once, - .acquire, - .acquire, - ) orelse { - prev_fiber.queue_next = null; - k.schedule(thread, .{ .head = prev_fiber, .tail = prev_fiber }); - return; - }, - }) |next_state| prev_state = next_state; - }, - .condition_wait => |condition_wait| { - const prev_fiber: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.prev)); - assert(prev_fiber.queue_next == null); - const cond_impl = prev_fiber.resultPointer(Condition); - cond_impl.* = .{ - .tail = prev_fiber, - .event = .queued, - }; - if (@cmpxchgStrong( - ?*Fiber, - @as(*?*Fiber, @ptrCast(&condition_wait.cond.state)), - null, - prev_fiber, - .release, - .acquire, - )) |waiting_fiber| { - const waiting_cond_impl = waiting_fiber.?.resultPointer(Condition); - assert(waiting_cond_impl.tail.queue_next == null); - waiting_cond_impl.tail.queue_next = prev_fiber; - waiting_cond_impl.tail = prev_fiber; - } - condition_wait.mutex.unlock(k.io()); - }, .exit => for (k.threads.allocated[0..@atomicLoad(u32, &k.threads.active, .acquire)]) |*each_thread| { const changes = [_]posix.Kevent{ .{ @@ -878,21 +817,13 @@ pub fn io(k: *Kqueue) Io { .concurrent = concurrent, .await = await, .cancel = cancel, - .cancelRequested = cancelRequested, .select = select, .groupAsync = groupAsync, - .groupWait = groupWait, + .groupConcurrent = groupConcurrent, + .groupAwait = groupAwait, .groupCancel = groupCancel, - .mutexLock = mutexLock, - .mutexLockUncancelable = mutexLockUncancelable, - .mutexUnlock = mutexUnlock, - - .conditionWait = conditionWait, - .conditionWaitUncancelable = conditionWaitUncancelable, - .conditionWake = conditionWake, - .dirCreateDir = dirCreateDir, .dirCreateDirPath = dirCreateDirPath, .dirCreateDirPathOpen = dirCreateDirPathOpen, @@ -912,7 +843,6 @@ pub fn io(k: *Kqueue) Io { .fileReadPositional = fileReadPositional, .fileSeekBy = fileSeekBy, .fileSeekTo = fileSeekTo, - .openExecutable = openExecutable, .now = now, .sleep = sleep, @@ -1037,25 +967,41 @@ fn cancelRequested(userdata: ?*anyopaque) bool { fn groupAsync( userdata: ?*anyopaque, - group: *Io.Group, + type_erased: *Io.Group, context: []const u8, - context_alignment: std.mem.Alignment, - start: *const fn (*Io.Group, context: *const anyopaque) void, + context_alignment: Alignment, + start: *const fn (context: *const anyopaque) Io.Cancelable!void, ) void { const k: *Kqueue = @ptrCast(@alignCast(userdata)); _ = k; - _ = group; + _ = type_erased; _ = context; _ = context_alignment; _ = start; @panic("TODO"); } -fn groupWait(userdata: ?*anyopaque, group: *Io.Group, token: *anyopaque) void { +fn groupConcurrent( + userdata: ?*anyopaque, + type_erased: *Io.Group, + context: []const u8, + context_alignment: Alignment, + start: *const fn (context: *const anyopaque) Io.Cancelable!void, +) Io.ConcurrentError!void { const k: *Kqueue = @ptrCast(@alignCast(userdata)); _ = k; - _ = group; - _ = token; + _ = type_erased; + _ = context; + _ = context_alignment; + _ = start; + @panic("TODO"); +} + +fn groupAwait(userdata: ?*anyopaque, type_erased: *Io.Group, initial_token: *anyopaque) Io.Cancelable!void { + const k: *Kqueue = @ptrCast(@alignCast(userdata)); + _ = k; + _ = type_erased; + _ = initial_token; @panic("TODO"); } @@ -1074,102 +1020,58 @@ fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) Io.Cancelable!u @panic("TODO"); } -fn mutexLock(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mutex) Io.Cancelable!void { - const k: *Kqueue = @ptrCast(@alignCast(userdata)); - _ = k; - _ = prev_state; - _ = mutex; - @panic("TODO"); -} -fn mutexLockUncancelable(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mutex) void { - const k: *Kqueue = @ptrCast(@alignCast(userdata)); - _ = k; - _ = prev_state; - _ = mutex; - @panic("TODO"); -} -fn mutexUnlock(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mutex) void { - const k: *Kqueue = @ptrCast(@alignCast(userdata)); - _ = k; - _ = prev_state; - _ = mutex; - @panic("TODO"); -} - -fn conditionWait(userdata: ?*anyopaque, cond: *Io.Condition, mutex: *Io.Mutex) Io.Cancelable!void { - const k: *Kqueue = @ptrCast(@alignCast(userdata)); - k.yield(null, .{ .condition_wait = .{ .cond = cond, .mutex = mutex } }); - const thread = Thread.current(); - const fiber = thread.currentFiber(); - const cond_impl = fiber.resultPointer(Condition); - try mutex.lock(k.io()); - switch (cond_impl.event) { - .queued => {}, - .wake => |wake| if (fiber.queue_next) |next_fiber| switch (wake) { - .one => if (@cmpxchgStrong( - ?*Fiber, - @as(*?*Fiber, @ptrCast(&cond.state)), - null, - next_fiber, - .release, - .acquire, - )) |old_fiber| { - const old_cond_impl = old_fiber.?.resultPointer(Condition); - assert(old_cond_impl.tail.queue_next == null); - old_cond_impl.tail.queue_next = next_fiber; - old_cond_impl.tail = cond_impl.tail; - }, - .all => k.schedule(thread, .{ .head = next_fiber, .tail = cond_impl.tail }), - }, - } - fiber.queue_next = null; -} - -fn conditionWaitUncancelable(userdata: ?*anyopaque, cond: *Io.Condition, mutex: *Io.Mutex) void { - const k: *Kqueue = @ptrCast(@alignCast(userdata)); - _ = k; - _ = cond; - _ = mutex; - @panic("TODO"); -} -fn conditionWake(userdata: ?*anyopaque, cond: *Io.Condition, wake: Io.Condition.Wake) void { - const k: *Kqueue = @ptrCast(@alignCast(userdata)); - const waiting_fiber = @atomicRmw(?*Fiber, @as(*?*Fiber, @ptrCast(&cond.state)), .Xchg, null, .acquire) orelse return; - waiting_fiber.resultPointer(Condition).event = .{ .wake = wake }; - k.yield(waiting_fiber, .reschedule); -} - -fn dirCreateDir(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, mode: Dir.Mode) Dir.CreateDirError!void { +fn dirCreateDir(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: Dir.Permissions) Dir.CreateDirError!void { const k: *Kqueue = @ptrCast(@alignCast(userdata)); _ = k; _ = dir; _ = sub_path; - _ = mode; + _ = permissions; @panic("TODO"); } -fn dirCreateDirPath(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, mode: Dir.Mode) Dir.CreateDirError!void { + +fn dirCreateDirPath( + userdata: ?*anyopaque, + dir: Dir, + sub_path: []const u8, + permissions: Dir.Permissions, +) Dir.CreateDirPathError!Dir.CreatePathStatus { const k: *Kqueue = @ptrCast(@alignCast(userdata)); _ = k; _ = dir; _ = sub_path; - _ = mode; + _ = permissions; @panic("TODO"); } -fn dirCreateDirPathOpen(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, options: Dir.OpenOptions) Dir.CreateDirPathOpenError!Dir { + +fn dirCreateDirPathOpen( + userdata: ?*anyopaque, + dir: Dir, + sub_path: []const u8, + permissions: Dir.Permissions, + options: Dir.OpenOptions, +) Dir.CreateDirPathOpenError!Dir { const k: *Kqueue = @ptrCast(@alignCast(userdata)); _ = k; _ = dir; _ = sub_path; + _ = permissions; _ = options; @panic("TODO"); } + fn dirStat(userdata: ?*anyopaque, dir: Dir) Dir.StatError!Dir.Stat { const k: *Kqueue = @ptrCast(@alignCast(userdata)); _ = k; _ = dir; @panic("TODO"); } -fn dirStatFile(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, options: Dir.StatPathOptions) Dir.StatFileError!File.Stat { + +fn dirStatFile( + userdata: ?*anyopaque, + dir: Dir, + sub_path: []const u8, + options: Dir.StatFileOptions, +) Dir.StatFileError!File.Stat { const k: *Kqueue = @ptrCast(@alignCast(userdata)); _ = k; _ = dir; @@ -1209,10 +1111,10 @@ fn dirOpenDir(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, options: Di _ = options; @panic("TODO"); } -fn dirClose(userdata: ?*anyopaque, dir: Dir) void { +fn dirClose(userdata: ?*anyopaque, dirs: []const Dir) void { const k: *Kqueue = @ptrCast(@alignCast(userdata)); _ = k; - _ = dir; + _ = dirs; @panic("TODO"); } fn fileStat(userdata: ?*anyopaque, file: File) File.StatError!File.Stat { @@ -1221,35 +1123,57 @@ fn fileStat(userdata: ?*anyopaque, file: File) File.StatError!File.Stat { _ = file; @panic("TODO"); } -fn fileClose(userdata: ?*anyopaque, file: File) void { + +fn fileClose(userdata: ?*anyopaque, files: []const File) void { const k: *Kqueue = @ptrCast(@alignCast(userdata)); _ = k; - _ = file; + _ = files; @panic("TODO"); } -fn fileWriteStreaming(userdata: ?*anyopaque, file: File, buffer: [][]const u8) File.WriteStreamingError!usize { + +fn fileWriteStreaming( + userdata: ?*anyopaque, + file: File, + header: []const u8, + data: []const []const u8, + splat: usize, +) File.Writer.Error!usize { const k: *Kqueue = @ptrCast(@alignCast(userdata)); _ = k; _ = file; - _ = buffer; + _ = header; + _ = data; + _ = splat; @panic("TODO"); } -fn fileWritePositional(userdata: ?*anyopaque, file: File, buffer: [][]const u8, offset: u64) File.WritePositionalError!usize { + +fn fileWritePositional( + userdata: ?*anyopaque, + file: File, + header: []const u8, + data: []const []const u8, + splat: usize, + offset: u64, +) File.WritePositionalError!usize { const k: *Kqueue = @ptrCast(@alignCast(userdata)); _ = k; _ = file; - _ = buffer; + _ = header; + _ = data; + _ = splat; _ = offset; @panic("TODO"); } -fn fileReadStreaming(userdata: ?*anyopaque, file: File, data: [][]u8) File.Reader.Error!usize { + +fn fileReadStreaming(userdata: ?*anyopaque, file: File, data: []const []u8) File.Reader.Error!usize { const k: *Kqueue = @ptrCast(@alignCast(userdata)); _ = k; _ = file; _ = data; @panic("TODO"); } -fn fileReadPositional(userdata: ?*anyopaque, file: File, data: [][]u8, offset: u64) File.ReadPositionalError!usize { + +fn fileReadPositional(userdata: ?*anyopaque, file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize { const k: *Kqueue = @ptrCast(@alignCast(userdata)); _ = k; _ = file; @@ -1271,12 +1195,6 @@ fn fileSeekTo(userdata: ?*anyopaque, file: File, absolute_offset: u64) File.Seek _ = absolute_offset; @panic("TODO"); } -fn openExecutable(userdata: ?*anyopaque, file: File.OpenFlags) File.OpenExecutableError!File { - const k: *Kqueue = @ptrCast(@alignCast(userdata)); - _ = k; - _ = file; - @panic("TODO"); -} fn now(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp { const k: *Kqueue = @ptrCast(@alignCast(userdata)); @@ -1576,10 +1494,10 @@ fn netWrite(userdata: ?*anyopaque, dest: net.Socket.Handle, header: []const u8, @panic("TODO"); } -fn netClose(userdata: ?*anyopaque, handle: net.Socket.Handle) void { +fn netClose(userdata: ?*anyopaque, handles: []const net.Socket.Handle) void { const k: *Kqueue = @ptrCast(@alignCast(userdata)); _ = k; - _ = handle; + _ = handles; @panic("TODO"); } @@ -1611,13 +1529,13 @@ fn netInterfaceName(userdata: ?*anyopaque, interface: net.Interface) net.Interfa fn netLookup( userdata: ?*anyopaque, host_name: net.HostName, - result: *Io.Queue(net.HostName.LookupResult), + resolved: *Io.Queue(net.HostName.LookupResult), options: net.HostName.LookupOptions, -) void { +) net.HostName.LookupError!void { const k: *Kqueue = @ptrCast(@alignCast(userdata)); _ = k; _ = host_name; - _ = result; + _ = resolved; _ = options; @panic("TODO"); } @@ -1772,14 +1690,6 @@ fn checkCancel(k: *Kqueue) error{Canceled}!void { if (cancelRequested(k)) return error.Canceled; } -const Condition = struct { - tail: *Fiber, - event: union(enum) { - queued, - wake: Io.Condition.Wake, - }, -}; - pub const KEventError = error{ /// The process does not have permission to register a filter. AccessDenied, -- 2.54.0