diff --git a/CMakeLists.txt b/CMakeLists.txt index 3da41d632116..c4ad62677288 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -286,7 +286,6 @@ set(ZIG_STAGE2_SOURCES "${CMAKE_SOURCE_DIR}/lib/std/multi_array_list.zig" "${CMAKE_SOURCE_DIR}/lib/std/os.zig" "${CMAKE_SOURCE_DIR}/lib/std/os/linux.zig" - "${CMAKE_SOURCE_DIR}/lib/std/os/linux/errno/generic.zig" "${CMAKE_SOURCE_DIR}/lib/std/os/linux/x86_64.zig" "${CMAKE_SOURCE_DIR}/lib/std/os/linux.zig" "${CMAKE_SOURCE_DIR}/lib/std/os/linux/IoUring.zig" diff --git a/build.zig b/build.zig index 243df2967adb..f661bd6887be 100644 --- a/build.zig +++ b/build.zig @@ -882,7 +882,7 @@ fn findConfigH(b: *std.Build, config_h_path_option: ?[]const u8) ?[]const u8 { return path; } else |_| { std.log.err("Could not open provided config.h: \"{s}\"", .{path}); - std.os.exit(1); + std.process.exit(1); } } diff --git a/lib/compiler/objcopy.zig b/lib/compiler/objcopy.zig index 67926be3354f..7772b8f2906d 100644 --- a/lib/compiler/objcopy.zig +++ b/lib/compiler/objcopy.zig @@ -1285,7 +1285,7 @@ const ElfFileHelper = struct { for (consolidated.items) |cmd| { switch (cmd) { .write_data => |data| { - var iovec = [_]std.os.iovec_const{.{ .iov_base = data.data.ptr, .iov_len = data.data.len }}; + var iovec = [_]std.posix.iovec_const{.{ .iov_base = data.data.ptr, .iov_len = data.data.len }}; try out_file.pwritevAll(&iovec, data.out_offset); }, .copy_range => |range| { diff --git a/lib/std/Random/benchmark.zig b/lib/std/Random/benchmark.zig index f3ea46818127..ad76742f2253 100644 --- a/lib/std/Random/benchmark.zig +++ b/lib/std/Random/benchmark.zig @@ -144,7 +144,7 @@ pub fn main() !void { i += 1; if (i == args.len) { usage(); - std.os.exit(1); + std.process.exit(1); } filter = args[i]; @@ -152,7 +152,7 @@ pub fn main() !void { i += 1; if (i == args.len) { usage(); - std.os.exit(1); + std.process.exit(1); } const c = try std.fmt.parseUnsigned(usize, args[i], 10); @@ -170,7 +170,7 @@ pub fn main() !void { return; } else { usage(); - std.os.exit(1); + std.process.exit(1); } } diff --git a/lib/std/Thread.zig b/lib/std/Thread.zig index e89518333124..abdeb00360e9 100644 --- a/lib/std/Thread.zig +++ b/lib/std/Thread.zig @@ -5,9 +5,11 @@ const std = @import("std.zig"); const builtin = @import("builtin"); const math = std.math; -const os = std.os; const assert = std.debug.assert; const target = builtin.target; +const native_os = builtin.os.tag; +const posix = std.posix; +const windows = std.os.windows; pub const Futex = @import("Thread/Futex.zig"); pub const ResetEvent = @import("Thread/ResetEvent.zig"); @@ -18,23 +20,23 @@ pub const RwLock = @import("Thread/RwLock.zig"); pub const Pool = @import("Thread/Pool.zig"); pub const WaitGroup = @import("Thread/WaitGroup.zig"); -pub const use_pthreads = target.os.tag != .windows and target.os.tag != .wasi and builtin.link_libc; +pub const use_pthreads = native_os != .windows and native_os != .wasi and builtin.link_libc; const Thread = @This(); -const Impl = if (target.os.tag == .windows) +const Impl = if (native_os == .windows) WindowsThreadImpl else if (use_pthreads) PosixThreadImpl -else if (target.os.tag == .linux) +else if (native_os == .linux) LinuxThreadImpl -else if (target.os.tag == .wasi) +else if (native_os == .wasi) WasiThreadImpl else UnsupportedImpl; impl: Impl, -pub const max_name_len = switch (target.os.tag) { +pub const max_name_len = switch (native_os) { .linux => 15, .windows => 31, .macos, .ios, .watchos, .tvos => 63, @@ -50,7 +52,7 @@ pub const SetNameError = error{ NameTooLong, Unsupported, Unexpected, -} || os.PrctlError || os.WriteError || std.fs.File.OpenError || std.fmt.BufPrintError; +} || posix.PrctlError || posix.WriteError || std.fs.File.OpenError || std.fmt.BufPrintError; pub fn setName(self: Thread, name: []const u8) SetNameError!void { if (name.len > max_name_len) return error.NameTooLong; @@ -62,21 +64,21 @@ pub fn setName(self: Thread, name: []const u8) SetNameError!void { break :blk name_buf[0..name.len :0]; }; - switch (target.os.tag) { + switch (native_os) { .linux => if (use_pthreads) { if (self.getHandle() == std.c.pthread_self()) { // Set the name of the calling thread (no thread id required). - const err = try os.prctl(.SET_NAME, .{@intFromPtr(name_with_terminator.ptr)}); - switch (@as(os.E, @enumFromInt(err))) { + const err = try posix.prctl(.SET_NAME, .{@intFromPtr(name_with_terminator.ptr)}); + switch (@as(posix.E, @enumFromInt(err))) { .SUCCESS => return, - else => |e| return os.unexpectedErrno(e), + else => |e| return posix.unexpectedErrno(e), } } else { const err = std.c.pthread_setname_np(self.getHandle(), name_with_terminator.ptr); switch (err) { .SUCCESS => return, .RANGE => unreachable, - else => |e| return os.unexpectedErrno(e), + else => |e| return posix.unexpectedErrno(e), } } } else { @@ -95,21 +97,21 @@ pub fn setName(self: Thread, name: []const u8) SetNameError!void { const byte_len = math.cast(c_ushort, len * 2) orelse return error.NameTooLong; // Note: NT allocates its own copy, no use-after-free here. - const unicode_string = os.windows.UNICODE_STRING{ + const unicode_string = windows.UNICODE_STRING{ .Length = byte_len, .MaximumLength = byte_len, .Buffer = &buf, }; - switch (os.windows.ntdll.NtSetInformationThread( + switch (windows.ntdll.NtSetInformationThread( self.getHandle(), .ThreadNameInformation, &unicode_string, - @sizeOf(os.windows.UNICODE_STRING), + @sizeOf(windows.UNICODE_STRING), )) { .SUCCESS => return, .NOT_IMPLEMENTED => return error.Unsupported, - else => |err| return os.windows.unexpectedStatus(err), + else => |err| return windows.unexpectedStatus(err), } }, .macos, .ios, .watchos, .tvos => if (use_pthreads) { @@ -119,7 +121,7 @@ pub fn setName(self: Thread, name: []const u8) SetNameError!void { const err = std.c.pthread_setname_np(name_with_terminator.ptr); switch (err) { .SUCCESS => return, - else => |e| return os.unexpectedErrno(e), + else => |e| return posix.unexpectedErrno(e), } }, .netbsd, .solaris, .illumos => if (use_pthreads) { @@ -129,7 +131,7 @@ pub fn setName(self: Thread, name: []const u8) SetNameError!void { .INVAL => unreachable, .SRCH => unreachable, .NOMEM => unreachable, - else => |e| return os.unexpectedErrno(e), + else => |e| return posix.unexpectedErrno(e), } }, .freebsd, .openbsd => if (use_pthreads) { @@ -148,7 +150,7 @@ pub fn setName(self: Thread, name: []const u8) SetNameError!void { .FAULT => unreachable, .NAMETOOLONG => unreachable, // already checked .SRCH => unreachable, - else => |e| return os.unexpectedErrno(e), + else => |e| return posix.unexpectedErrno(e), } }, else => {}, @@ -159,7 +161,7 @@ pub fn setName(self: Thread, name: []const u8) SetNameError!void { pub const GetNameError = error{ Unsupported, Unexpected, -} || os.PrctlError || os.ReadError || std.fs.File.OpenError || std.fmt.BufPrintError; +} || posix.PrctlError || posix.ReadError || std.fs.File.OpenError || std.fmt.BufPrintError; /// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). /// On other platforms, the result is an opaque sequence of bytes with no particular encoding. @@ -167,21 +169,21 @@ pub fn getName(self: Thread, buffer_ptr: *[max_name_len:0]u8) GetNameError!?[]co buffer_ptr[max_name_len] = 0; var buffer: [:0]u8 = buffer_ptr; - switch (target.os.tag) { + switch (native_os) { .linux => if (use_pthreads) { if (self.getHandle() == std.c.pthread_self()) { // Get the name of the calling thread (no thread id required). - const err = try os.prctl(.GET_NAME, .{@intFromPtr(buffer.ptr)}); - switch (@as(os.E, @enumFromInt(err))) { + const err = try posix.prctl(.GET_NAME, .{@intFromPtr(buffer.ptr)}); + switch (@as(posix.E, @enumFromInt(err))) { .SUCCESS => return std.mem.sliceTo(buffer, 0), - else => |e| return os.unexpectedErrno(e), + else => |e| return posix.unexpectedErrno(e), } } else { const err = std.c.pthread_getname_np(self.getHandle(), buffer.ptr, max_name_len + 1); switch (err) { .SUCCESS => return std.mem.sliceTo(buffer, 0), .RANGE => unreachable, - else => |e| return os.unexpectedErrno(e), + else => |e| return posix.unexpectedErrno(e), } } } else { @@ -196,10 +198,10 @@ pub fn getName(self: Thread, buffer_ptr: *[max_name_len:0]u8) GetNameError!?[]co return if (data_len >= 1) buffer[0 .. data_len - 1] else null; }, .windows => { - const buf_capacity = @sizeOf(os.windows.UNICODE_STRING) + (@sizeOf(u16) * max_name_len); - var buf: [buf_capacity]u8 align(@alignOf(os.windows.UNICODE_STRING)) = undefined; + const buf_capacity = @sizeOf(windows.UNICODE_STRING) + (@sizeOf(u16) * max_name_len); + var buf: [buf_capacity]u8 align(@alignOf(windows.UNICODE_STRING)) = undefined; - switch (os.windows.ntdll.NtQueryInformationThread( + switch (windows.ntdll.NtQueryInformationThread( self.getHandle(), .ThreadNameInformation, &buf, @@ -207,12 +209,12 @@ pub fn getName(self: Thread, buffer_ptr: *[max_name_len:0]u8) GetNameError!?[]co null, )) { .SUCCESS => { - const string = @as(*const os.windows.UNICODE_STRING, @ptrCast(&buf)); + const string = @as(*const windows.UNICODE_STRING, @ptrCast(&buf)); const len = std.unicode.wtf16LeToWtf8(buffer, string.Buffer.?[0 .. string.Length / 2]); return if (len > 0) buffer[0..len] else null; }, .NOT_IMPLEMENTED => return error.Unsupported, - else => |err| return os.windows.unexpectedStatus(err), + else => |err| return windows.unexpectedStatus(err), } }, .macos, .ios, .watchos, .tvos => if (use_pthreads) { @@ -220,7 +222,7 @@ pub fn getName(self: Thread, buffer_ptr: *[max_name_len:0]u8) GetNameError!?[]co switch (err) { .SUCCESS => return std.mem.sliceTo(buffer, 0), .SRCH => unreachable, - else => |e| return os.unexpectedErrno(e), + else => |e| return posix.unexpectedErrno(e), } }, .netbsd, .solaris, .illumos => if (use_pthreads) { @@ -229,7 +231,7 @@ pub fn getName(self: Thread, buffer_ptr: *[max_name_len:0]u8) GetNameError!?[]co .SUCCESS => return std.mem.sliceTo(buffer, 0), .INVAL => unreachable, .SRCH => unreachable, - else => |e| return os.unexpectedErrno(e), + else => |e| return posix.unexpectedErrno(e), } }, .freebsd, .openbsd => if (use_pthreads) { @@ -246,7 +248,7 @@ pub fn getName(self: Thread, buffer_ptr: *[max_name_len:0]u8) GetNameError!?[]co .INVAL => unreachable, .FAULT => unreachable, .SRCH => unreachable, - else => |e| return os.unexpectedErrno(e), + else => |e| return posix.unexpectedErrno(e), } }, else => {}, @@ -255,7 +257,7 @@ pub fn getName(self: Thread, buffer_ptr: *[max_name_len:0]u8) GetNameError!?[]co } /// Represents an ID per thread guaranteed to be unique only within a process. -pub const Id = switch (target.os.tag) { +pub const Id = switch (native_os) { .linux, .dragonfly, .netbsd, @@ -265,7 +267,7 @@ pub const Id = switch (target.os.tag) { .wasi, => u32, .macos, .ios, .watchos, .tvos => u64, - .windows => os.windows.DWORD, + .windows => windows.DWORD, else => usize, }; @@ -368,13 +370,13 @@ pub const YieldError = error{ /// Yields the current thread potentially allowing other threads to run. pub fn yield() YieldError!void { - if (builtin.os.tag == .windows) { + if (native_os == .windows) { // The return value has to do with how many other threads there are; it is not // an error condition on Windows. - _ = os.windows.kernel32.SwitchToThread(); + _ = windows.kernel32.SwitchToThread(); return; } - switch (os.errno(os.system.sched_yield())) { + switch (posix.errno(posix.system.sched_yield())) { .SUCCESS => return, .NOSYS => return error.SystemCannotYield, else => return error.SystemCannotYield, @@ -390,7 +392,7 @@ const Completion = std.atomic.Value(enum(u8) { /// Used by the Thread implementations to call the spawned function with the arguments. fn callFn(comptime f: anytype, args: anytype) switch (Impl) { - WindowsThreadImpl => std.os.windows.DWORD, + WindowsThreadImpl => windows.DWORD, LinuxThreadImpl => u8, PosixThreadImpl => ?*anyopaque, else => unreachable, @@ -470,13 +472,11 @@ const UnsupportedImpl = struct { fn unsupported(unused: anytype) noreturn { _ = unused; - @compileError("Unsupported operating system " ++ @tagName(target.os.tag)); + @compileError("Unsupported operating system " ++ @tagName(native_os)); } }; const WindowsThreadImpl = struct { - const windows = os.windows; - pub const ThreadHandle = windows.HANDLE; fn getCurrentId() windows.DWORD { @@ -584,7 +584,7 @@ const PosixThreadImpl = struct { pub const ThreadHandle = c.pthread_t; fn getCurrentId() Id { - switch (target.os.tag) { + switch (native_os) { .linux => { return LinuxThreadImpl.getCurrentId(); }, @@ -616,15 +616,15 @@ const PosixThreadImpl = struct { } fn getCpuCount() !usize { - switch (target.os.tag) { + switch (native_os) { .linux => { return LinuxThreadImpl.getCpuCount(); }, .openbsd => { var count: c_int = undefined; var count_size: usize = @sizeOf(c_int); - const mib = [_]c_int{ os.CTL.HW, os.system.HW.NCPUONLINE }; - os.sysctl(&mib, &count, &count_size, null, 0) catch |err| switch (err) { + const mib = [_]c_int{ std.c.CTL.HW, std.c.HW.NCPUONLINE }; + std.c.sysctl(&mib, &count, &count_size, null, 0) catch |err| switch (err) { error.NameTooLong, error.UnknownName => unreachable, else => |e| return e, }; @@ -634,25 +634,25 @@ const PosixThreadImpl = struct { // The "proper" way to get the cpu count would be to query // /dev/kstat via ioctls, and traverse a linked list for each // cpu. - const rc = c.sysconf(os._SC.NPROCESSORS_ONLN); - return switch (os.errno(rc)) { + const rc = c.sysconf(std.c._SC.NPROCESSORS_ONLN); + return switch (posix.errno(rc)) { .SUCCESS => @as(usize, @intCast(rc)), - else => |err| os.unexpectedErrno(err), + else => |err| posix.unexpectedErrno(err), }; }, .haiku => { - var system_info: os.system.system_info = undefined; - const rc = os.system.get_system_info(&system_info); // always returns B_OK - return switch (os.errno(rc)) { + var system_info: std.c.system_info = undefined; + const rc = std.c.get_system_info(&system_info); // always returns B_OK + return switch (posix.errno(rc)) { .SUCCESS => @as(usize, @intCast(system_info.cpu_count)), - else => |err| os.unexpectedErrno(err), + else => |err| posix.unexpectedErrno(err), }; }, else => { var count: c_int = undefined; var count_len: usize = @sizeOf(c_int); const name = if (comptime target.isDarwin()) "hw.logicalcpu" else "hw.ncpu"; - os.sysctlbynameZ(name, &count, &count_len, null, 0) catch |err| switch (err) { + posix.sysctlbynameZ(name, &count, &count_len, null, 0) catch |err| switch (err) { error.NameTooLong, error.UnknownName => unreachable, else => |e| return e, }; @@ -699,7 +699,7 @@ const PosixThreadImpl = struct { .AGAIN => return error.SystemResources, .PERM => unreachable, .INVAL => unreachable, - else => |err| return os.unexpectedErrno(err), + else => |err| return posix.unexpectedErrno(err), } } @@ -1013,7 +1013,7 @@ const WasiThreadImpl = struct { }; const LinuxThreadImpl = struct { - const linux = os.linux; + const linux = std.os.linux; pub const ThreadHandle = i32; @@ -1028,9 +1028,9 @@ const LinuxThreadImpl = struct { } fn getCpuCount() !usize { - const cpu_set = try os.sched_getaffinity(0); + const cpu_set = try posix.sched_getaffinity(0); // TODO: should not need this usize cast - return @as(usize, os.CPU_COUNT(cpu_set)); + return @as(usize, posix.CPU_COUNT(cpu_set)); } thread: *ThreadCompletion, @@ -1228,10 +1228,10 @@ const LinuxThreadImpl = struct { // map all memory needed without read/write permissions // to avoid committing the whole region right away // anonymous mapping ensures file descriptor limits are not exceeded - const mapped = os.mmap( + const mapped = posix.mmap( null, map_bytes, - os.PROT.NONE, + posix.PROT.NONE, .{ .TYPE = .PRIVATE, .ANONYMOUS = true }, -1, 0, @@ -1244,24 +1244,24 @@ const LinuxThreadImpl = struct { else => |e| return e, }; assert(mapped.len >= map_bytes); - errdefer os.munmap(mapped); + errdefer posix.munmap(mapped); // map everything but the guard page as read/write - os.mprotect( + posix.mprotect( @alignCast(mapped[guard_offset..]), - os.PROT.READ | os.PROT.WRITE, + posix.PROT.READ | posix.PROT.WRITE, ) catch |err| switch (err) { error.AccessDenied => unreachable, else => |e| return e, }; // Prepare the TLS segment and prepare a user_desc struct when needed on x86 - var tls_ptr = os.linux.tls.prepareTLS(mapped[tls_offset..]); - var user_desc: if (target.cpu.arch == .x86) os.linux.user_desc else void = undefined; + var tls_ptr = linux.tls.prepareTLS(mapped[tls_offset..]); + var user_desc: if (target.cpu.arch == .x86) linux.user_desc else void = undefined; if (target.cpu.arch == .x86) { defer tls_ptr = @intFromPtr(&user_desc); user_desc = .{ - .entry_number = os.linux.tls.tls_image.gdt_entry_number, + .entry_number = linux.tls.tls_image.gdt_entry_number, .base_addr = tls_ptr, .limit = 0xfffff, .flags = .{ @@ -1286,7 +1286,7 @@ const LinuxThreadImpl = struct { linux.CLONE.PARENT_SETTID | linux.CLONE.CHILD_CLEARTID | linux.CLONE.SIGHAND | linux.CLONE.SYSVSEM | linux.CLONE.SETTLS; - switch (linux.getErrno(linux.clone( + switch (linux.E.init(linux.clone( Instance.entryFn, @intFromPtr(&mapped[stack_offset]), flags, @@ -1302,7 +1302,7 @@ const LinuxThreadImpl = struct { .NOSPC => unreachable, .PERM => unreachable, .USERS => unreachable, - else => |err| return os.unexpectedErrno(err), + else => |err| return posix.unexpectedErrno(err), } } @@ -1319,7 +1319,7 @@ const LinuxThreadImpl = struct { } fn join(self: Impl) void { - defer os.munmap(self.thread.mapped); + defer posix.munmap(self.thread.mapped); var spin: u8 = 10; while (true) { @@ -1334,7 +1334,7 @@ const LinuxThreadImpl = struct { continue; } - switch (linux.getErrno(linux.futex_wait( + switch (linux.E.init(linux.futex_wait( &self.thread.child_tid.raw, linux.FUTEX.WAIT, tid, @@ -1383,7 +1383,7 @@ test "setName, getName" { // Wait for the main thread to have set the thread field in the context. ctx.start_wait_event.wait(); - switch (target.os.tag) { + switch (native_os) { .windows => testThreadName(&ctx.thread) catch |err| switch (err) { error.Unsupported => return error.SkipZigTest, else => return err, @@ -1406,7 +1406,7 @@ test "setName, getName" { context.start_wait_event.set(); context.test_done_event.wait(); - switch (target.os.tag) { + switch (native_os) { .macos, .ios, .watchos, .tvos => { const res = thread.setName("foobar"); try std.testing.expectError(error.Unsupported, res); diff --git a/lib/std/Thread/Futex.zig b/lib/std/Thread/Futex.zig index 764d3f13e123..2a81a4a5352e 100644 --- a/lib/std/Thread/Futex.zig +++ b/lib/std/Thread/Futex.zig @@ -1,13 +1,20 @@ -//! Futex is a mechanism used to block (`wait`) and unblock (`wake`) threads using a 32bit memory address as hints. -//! Blocking a thread is acknowledged only if the 32bit memory address is equal to a given value. -//! This check helps avoid block/unblock deadlocks which occur if a `wake()` happens before a `wait()`. -//! Using Futex, other Thread synchronization primitives can be built which efficiently wait for cross-thread events or signals. +//! A mechanism used to block (`wait`) and unblock (`wake`) threads using a +//! 32bit memory address as hints. +//! +//! Blocking a thread is acknowledged only if the 32bit memory address is equal +//! to a given value. This check helps avoid block/unblock deadlocks which +//! occur if a `wake()` happens before a `wait()`. +//! +//! Using Futex, other Thread synchronization primitives can be built which +//! efficiently wait for cross-thread events or signals. const std = @import("../std.zig"); const builtin = @import("builtin"); const Futex = @This(); +const windows = std.os.windows; +const linux = std.os.linux; +const c = std.c; -const os = std.os; const assert = std.debug.assert; const testing = std.testing; const atomic = std.atomic; @@ -124,18 +131,18 @@ const SingleThreadedImpl = struct { // as it's generally already a linked target and is autoloaded into all processes anyway. const WindowsImpl = struct { fn wait(ptr: *const atomic.Value(u32), expect: u32, timeout: ?u64) error{Timeout}!void { - var timeout_value: os.windows.LARGE_INTEGER = undefined; - var timeout_ptr: ?*const os.windows.LARGE_INTEGER = null; + var timeout_value: windows.LARGE_INTEGER = undefined; + var timeout_ptr: ?*const windows.LARGE_INTEGER = null; // NTDLL functions work with time in units of 100 nanoseconds. // Positive values are absolute deadlines while negative values are relative durations. if (timeout) |delay| { - timeout_value = @as(os.windows.LARGE_INTEGER, @intCast(delay / 100)); + timeout_value = @as(windows.LARGE_INTEGER, @intCast(delay / 100)); timeout_value = -timeout_value; timeout_ptr = &timeout_value; } - const rc = os.windows.ntdll.RtlWaitOnAddress( + const rc = windows.ntdll.RtlWaitOnAddress( ptr, &expect, @sizeOf(@TypeOf(expect)), @@ -157,8 +164,8 @@ const WindowsImpl = struct { assert(max_waiters != 0); switch (max_waiters) { - 1 => os.windows.ntdll.RtlWakeAddressSingle(address), - else => os.windows.ntdll.RtlWakeAddressAll(address), + 1 => windows.ntdll.RtlWakeAddressSingle(address), + else => windows.ntdll.RtlWakeAddressAll(address), } } }; @@ -189,10 +196,10 @@ const DarwinImpl = struct { var timeout_overflowed = false; const addr: *const anyopaque = ptr; - const flags = os.darwin.UL_COMPARE_AND_WAIT | os.darwin.ULF_NO_ERRNO; + const flags = c.UL_COMPARE_AND_WAIT | c.ULF_NO_ERRNO; const status = blk: { if (supports_ulock_wait2) { - break :blk os.darwin.__ulock_wait2(flags, addr, expect, timeout_ns, 0); + break :blk c.__ulock_wait2(flags, addr, expect, timeout_ns, 0); } const timeout_us = std.math.cast(u32, timeout_ns / std.time.ns_per_us) orelse overflow: { @@ -200,11 +207,11 @@ const DarwinImpl = struct { break :overflow std.math.maxInt(u32); }; - break :blk os.darwin.__ulock_wait(flags, addr, expect, timeout_us); + break :blk c.__ulock_wait(flags, addr, expect, timeout_us); }; if (status >= 0) return; - switch (@as(std.os.E, @enumFromInt(-status))) { + switch (@as(c.E, @enumFromInt(-status))) { // Wait was interrupted by the OS or other spurious signalling. .INTR => {}, // Address of the futex was paged out. This is unlikely, but possible in theory, and @@ -221,17 +228,17 @@ const DarwinImpl = struct { } fn wake(ptr: *const atomic.Value(u32), max_waiters: u32) void { - var flags: u32 = os.darwin.UL_COMPARE_AND_WAIT | os.darwin.ULF_NO_ERRNO; + var flags: u32 = c.UL_COMPARE_AND_WAIT | c.ULF_NO_ERRNO; if (max_waiters > 1) { - flags |= os.darwin.ULF_WAKE_ALL; + flags |= c.ULF_WAKE_ALL; } while (true) { const addr: *const anyopaque = ptr; - const status = os.darwin.__ulock_wake(flags, addr, 0); + const status = c.__ulock_wake(flags, addr, 0); if (status >= 0) return; - switch (@as(std.os.E, @enumFromInt(-status))) { + switch (@as(c.E, @enumFromInt(-status))) { .INTR => continue, // spurious wake() .FAULT => unreachable, // __ulock_wake doesn't generate EFAULT according to darwin pthread_cond_t .NOENT => return, // nothing was woken up @@ -245,20 +252,20 @@ const DarwinImpl = struct { // https://man7.org/linux/man-pages/man2/futex.2.html const LinuxImpl = struct { fn wait(ptr: *const atomic.Value(u32), expect: u32, timeout: ?u64) error{Timeout}!void { - var ts: os.timespec = undefined; + var ts: linux.timespec = undefined; if (timeout) |timeout_ns| { ts.tv_sec = @as(@TypeOf(ts.tv_sec), @intCast(timeout_ns / std.time.ns_per_s)); ts.tv_nsec = @as(@TypeOf(ts.tv_nsec), @intCast(timeout_ns % std.time.ns_per_s)); } - const rc = os.linux.futex_wait( + const rc = linux.futex_wait( @as(*const i32, @ptrCast(&ptr.raw)), - os.linux.FUTEX.PRIVATE_FLAG | os.linux.FUTEX.WAIT, + linux.FUTEX.PRIVATE_FLAG | linux.FUTEX.WAIT, @as(i32, @bitCast(expect)), if (timeout != null) &ts else null, ); - switch (os.linux.getErrno(rc)) { + switch (linux.E.init(rc)) { .SUCCESS => {}, // notified by `wake()` .INTR => {}, // spurious wakeup .AGAIN => {}, // ptr.* != expect @@ -273,13 +280,13 @@ const LinuxImpl = struct { } fn wake(ptr: *const atomic.Value(u32), max_waiters: u32) void { - const rc = os.linux.futex_wake( + const rc = linux.futex_wake( @as(*const i32, @ptrCast(&ptr.raw)), - os.linux.FUTEX.PRIVATE_FLAG | os.linux.FUTEX.WAKE, + linux.FUTEX.PRIVATE_FLAG | linux.FUTEX.WAKE, std.math.cast(i32, max_waiters) orelse std.math.maxInt(i32), ); - switch (os.linux.getErrno(rc)) { + switch (linux.E.init(rc)) { .SUCCESS => {}, // successful wake up .INVAL => {}, // invalid futex_wait() on ptr done elsewhere .FAULT => {}, // pointer became invalid while doing the wake @@ -292,28 +299,28 @@ const LinuxImpl = struct { const FreebsdImpl = struct { fn wait(ptr: *const atomic.Value(u32), expect: u32, timeout: ?u64) error{Timeout}!void { var tm_size: usize = 0; - var tm: os.freebsd._umtx_time = undefined; - var tm_ptr: ?*const os.freebsd._umtx_time = null; + var tm: c._umtx_time = undefined; + var tm_ptr: ?*const c._umtx_time = null; if (timeout) |timeout_ns| { tm_ptr = &tm; tm_size = @sizeOf(@TypeOf(tm)); tm._flags = 0; // use relative time not UMTX_ABSTIME - tm._clockid = os.CLOCK.MONOTONIC; + tm._clockid = c.CLOCK.MONOTONIC; tm._timeout.tv_sec = @as(@TypeOf(tm._timeout.tv_sec), @intCast(timeout_ns / std.time.ns_per_s)); tm._timeout.tv_nsec = @as(@TypeOf(tm._timeout.tv_nsec), @intCast(timeout_ns % std.time.ns_per_s)); } - const rc = os.freebsd._umtx_op( + const rc = c._umtx_op( @intFromPtr(&ptr.raw), - @intFromEnum(os.freebsd.UMTX_OP.WAIT_UINT_PRIVATE), + @intFromEnum(c.UMTX_OP.WAIT_UINT_PRIVATE), @as(c_ulong, expect), tm_size, @intFromPtr(tm_ptr), ); - switch (os.errno(rc)) { + switch (std.posix.errno(rc)) { .SUCCESS => {}, .FAULT => unreachable, // one of the args points to invalid memory .INVAL => unreachable, // arguments should be correct @@ -327,15 +334,15 @@ const FreebsdImpl = struct { } fn wake(ptr: *const atomic.Value(u32), max_waiters: u32) void { - const rc = os.freebsd._umtx_op( + const rc = c._umtx_op( @intFromPtr(&ptr.raw), - @intFromEnum(os.freebsd.UMTX_OP.WAKE_PRIVATE), + @intFromEnum(c.UMTX_OP.WAKE_PRIVATE), @as(c_ulong, max_waiters), 0, // there is no timeout struct 0, // there is no timeout struct pointer ); - switch (os.errno(rc)) { + switch (std.posix.errno(rc)) { .SUCCESS => {}, .FAULT => {}, // it's ok if the ptr doesn't point to valid memory .INVAL => unreachable, // arguments should be correct @@ -347,21 +354,21 @@ const FreebsdImpl = struct { // https://man.openbsd.org/futex.2 const OpenbsdImpl = struct { fn wait(ptr: *const atomic.Value(u32), expect: u32, timeout: ?u64) error{Timeout}!void { - var ts: os.timespec = undefined; + var ts: c.timespec = undefined; if (timeout) |timeout_ns| { ts.tv_sec = @as(@TypeOf(ts.tv_sec), @intCast(timeout_ns / std.time.ns_per_s)); ts.tv_nsec = @as(@TypeOf(ts.tv_nsec), @intCast(timeout_ns % std.time.ns_per_s)); } - const rc = os.openbsd.futex( + const rc = c.futex( @as(*const volatile u32, @ptrCast(&ptr.raw)), - os.openbsd.FUTEX_WAIT | os.openbsd.FUTEX_PRIVATE_FLAG, + c.FUTEX_WAIT | c.FUTEX_PRIVATE_FLAG, @as(c_int, @bitCast(expect)), if (timeout != null) &ts else null, null, // FUTEX_WAIT takes no requeue address ); - switch (os.errno(rc)) { + switch (std.posix.errno(rc)) { .SUCCESS => {}, // woken up by wake .NOSYS => unreachable, // the futex operation shouldn't be invalid .FAULT => unreachable, // ptr was invalid @@ -378,9 +385,9 @@ const OpenbsdImpl = struct { } fn wake(ptr: *const atomic.Value(u32), max_waiters: u32) void { - const rc = os.openbsd.futex( + const rc = c.futex( @as(*const volatile u32, @ptrCast(&ptr.raw)), - os.openbsd.FUTEX_WAKE | os.openbsd.FUTEX_PRIVATE_FLAG, + c.FUTEX_WAKE | c.FUTEX_PRIVATE_FLAG, std.math.cast(c_int, max_waiters) orelse std.math.maxInt(c_int), null, // FUTEX_WAKE takes no timeout ptr null, // FUTEX_WAKE takes no requeue address @@ -415,9 +422,9 @@ const DragonflyImpl = struct { const value = @as(c_int, @bitCast(expect)); const addr = @as(*const volatile c_int, @ptrCast(&ptr.raw)); - const rc = os.dragonfly.umtx_sleep(addr, value, timeout_us); + const rc = c.umtx_sleep(addr, value, timeout_us); - switch (os.errno(rc)) { + switch (std.posix.errno(rc)) { .SUCCESS => {}, .BUSY => {}, // ptr != expect .AGAIN => { // maybe timed out, or paged out, or hit 2s kernel refresh @@ -444,7 +451,7 @@ const DragonflyImpl = struct { // > umtx_wakeup() will generally return 0 unless the address is bad. // We are fine with the address being bad (e.g. for Semaphore.post() where Semaphore.wait() frees the Semaphore) const addr = @as(*const volatile c_int, @ptrCast(&ptr.raw)); - _ = os.dragonfly.umtx_wakeup(addr, to_wake); + _ = c.umtx_wakeup(addr, to_wake); } }; @@ -496,8 +503,8 @@ const WasmImpl = struct { /// https://go.dev/src/runtime/sema.go const PosixImpl = struct { const Event = struct { - cond: std.c.pthread_cond_t, - mutex: std.c.pthread_mutex_t, + cond: c.pthread_cond_t, + mutex: c.pthread_mutex_t, state: enum { empty, waiting, notified }, fn init(self: *Event) void { @@ -509,18 +516,18 @@ const PosixImpl = struct { fn deinit(self: *Event) void { // Some platforms reportedly give EINVAL for statically initialized pthread types. - const rc = std.c.pthread_cond_destroy(&self.cond); + const rc = c.pthread_cond_destroy(&self.cond); assert(rc == .SUCCESS or rc == .INVAL); - const rm = std.c.pthread_mutex_destroy(&self.mutex); + const rm = c.pthread_mutex_destroy(&self.mutex); assert(rm == .SUCCESS or rm == .INVAL); self.* = undefined; } fn wait(self: *Event, timeout: ?u64) error{Timeout}!void { - assert(std.c.pthread_mutex_lock(&self.mutex) == .SUCCESS); - defer assert(std.c.pthread_mutex_unlock(&self.mutex) == .SUCCESS); + assert(c.pthread_mutex_lock(&self.mutex) == .SUCCESS); + defer assert(c.pthread_mutex_unlock(&self.mutex) == .SUCCESS); // Early return if the event was already set. if (self.state == .notified) { @@ -530,9 +537,9 @@ const PosixImpl = struct { // Compute the absolute timeout if one was specified. // POSIX requires that REALTIME is used by default for the pthread timedwait functions. // This can be changed with pthread_condattr_setclock, but it's an extension and may not be available everywhere. - var ts: os.timespec = undefined; + var ts: c.timespec = undefined; if (timeout) |timeout_ns| { - os.clock_gettime(os.CLOCK.REALTIME, &ts) catch unreachable; + c.clock_gettime(c.CLOCK.REALTIME, &ts) catch unreachable; ts.tv_sec +|= @as(@TypeOf(ts.tv_sec), @intCast(timeout_ns / std.time.ns_per_s)); ts.tv_nsec += @as(@TypeOf(ts.tv_nsec), @intCast(timeout_ns % std.time.ns_per_s)); @@ -549,8 +556,8 @@ const PosixImpl = struct { while (true) { // Block using either pthread_cond_wait or pthread_cond_timewait if there's an absolute timeout. const rc = blk: { - if (timeout == null) break :blk std.c.pthread_cond_wait(&self.cond, &self.mutex); - break :blk std.c.pthread_cond_timedwait(&self.cond, &self.mutex, &ts); + if (timeout == null) break :blk c.pthread_cond_wait(&self.cond, &self.mutex); + break :blk c.pthread_cond_timedwait(&self.cond, &self.mutex, &ts); }; // After waking up, check if the event was set. @@ -574,8 +581,8 @@ const PosixImpl = struct { } fn set(self: *Event) void { - assert(std.c.pthread_mutex_lock(&self.mutex) == .SUCCESS); - defer assert(std.c.pthread_mutex_unlock(&self.mutex) == .SUCCESS); + assert(c.pthread_mutex_lock(&self.mutex) == .SUCCESS); + defer assert(c.pthread_mutex_unlock(&self.mutex) == .SUCCESS); // Make sure that multiple calls to set() were not done on the same Event. const old_state = self.state; @@ -586,7 +593,7 @@ const PosixImpl = struct { // the condition variable once it observes the new state, potentially causing a UAF if done unlocked. self.state = .notified; if (old_state == .waiting) { - assert(std.c.pthread_cond_signal(&self.cond) == .SUCCESS); + assert(c.pthread_cond_signal(&self.cond) == .SUCCESS); } } }; @@ -732,7 +739,7 @@ const PosixImpl = struct { }; const Bucket = struct { - mutex: std.c.pthread_mutex_t align(atomic.cache_line) = .{}, + mutex: c.pthread_mutex_t align(atomic.cache_line) = .{}, pending: atomic.Value(usize) = atomic.Value(usize).init(0), treap: Treap = .{}, @@ -798,8 +805,8 @@ const PosixImpl = struct { var waiter: Waiter = undefined; { - assert(std.c.pthread_mutex_lock(&bucket.mutex) == .SUCCESS); - defer assert(std.c.pthread_mutex_unlock(&bucket.mutex) == .SUCCESS); + assert(c.pthread_mutex_lock(&bucket.mutex) == .SUCCESS); + defer assert(c.pthread_mutex_unlock(&bucket.mutex) == .SUCCESS); cancelled = ptr.load(.monotonic) != expect; if (cancelled) { @@ -821,8 +828,8 @@ const PosixImpl = struct { // If we return early without waiting, the waiter on the stack would be invalidated and the wake() thread risks a UAF. defer if (!cancelled) waiter.event.wait(null) catch unreachable; - assert(std.c.pthread_mutex_lock(&bucket.mutex) == .SUCCESS); - defer assert(std.c.pthread_mutex_unlock(&bucket.mutex) == .SUCCESS); + assert(c.pthread_mutex_lock(&bucket.mutex) == .SUCCESS); + defer assert(c.pthread_mutex_unlock(&bucket.mutex) == .SUCCESS); cancelled = WaitQueue.tryRemove(&bucket.treap, address, &waiter); if (cancelled) { @@ -871,8 +878,8 @@ const PosixImpl = struct { } }; - assert(std.c.pthread_mutex_lock(&bucket.mutex) == .SUCCESS); - defer assert(std.c.pthread_mutex_unlock(&bucket.mutex) == .SUCCESS); + assert(c.pthread_mutex_lock(&bucket.mutex) == .SUCCESS); + defer assert(c.pthread_mutex_unlock(&bucket.mutex) == .SUCCESS); // Another pending check again to avoid the WaitQueue lookup if not necessary. if (bucket.pending.load(.monotonic) > 0) { diff --git a/lib/std/Thread/Mutex.zig b/lib/std/Thread/Mutex.zig index 67472ffd9ce0..b6d3d6fb84e0 100644 --- a/lib/std/Thread/Mutex.zig +++ b/lib/std/Thread/Mutex.zig @@ -23,7 +23,6 @@ const std = @import("../std.zig"); const builtin = @import("builtin"); const Mutex = @This(); -const os = std.os; const assert = std.debug.assert; const testing = std.testing; const Thread = std.Thread; @@ -117,36 +116,40 @@ const SingleThreadedImpl = struct { // SRWLOCK on windows is almost always faster than Futex solution. // It also implements an efficient Condition with requeue support for us. const WindowsImpl = struct { - srwlock: os.windows.SRWLOCK = .{}, + srwlock: windows.SRWLOCK = .{}, fn tryLock(self: *@This()) bool { - return os.windows.kernel32.TryAcquireSRWLockExclusive(&self.srwlock) != os.windows.FALSE; + return windows.kernel32.TryAcquireSRWLockExclusive(&self.srwlock) != windows.FALSE; } fn lock(self: *@This()) void { - os.windows.kernel32.AcquireSRWLockExclusive(&self.srwlock); + windows.kernel32.AcquireSRWLockExclusive(&self.srwlock); } fn unlock(self: *@This()) void { - os.windows.kernel32.ReleaseSRWLockExclusive(&self.srwlock); + windows.kernel32.ReleaseSRWLockExclusive(&self.srwlock); } + + const windows = std.os.windows; }; // os_unfair_lock on darwin supports priority inheritance and is generally faster than Futex solutions. const DarwinImpl = struct { - oul: os.darwin.os_unfair_lock = .{}, + oul: c.os_unfair_lock = .{}, fn tryLock(self: *@This()) bool { - return os.darwin.os_unfair_lock_trylock(&self.oul); + return c.os_unfair_lock_trylock(&self.oul); } fn lock(self: *@This()) void { - os.darwin.os_unfair_lock_lock(&self.oul); + c.os_unfair_lock_lock(&self.oul); } fn unlock(self: *@This()) void { - os.darwin.os_unfair_lock_unlock(&self.oul); + c.os_unfair_lock_unlock(&self.oul); } + + const c = std.c; }; const FutexImpl = struct { diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig index 0238d35c7d9d..6f6e0c97fc0f 100644 --- a/lib/std/builtin.zig +++ b/lib/std/builtin.zig @@ -782,7 +782,7 @@ pub fn default_panic(msg: []const u8, error_return_trace: ?*StackTrace, ret_addr }, .wasi => { std.debug.print("{s}", .{msg}); - std.os.abort(); + std.posix.abort(); }, .uefi => { const uefi = std.os.uefi; @@ -830,9 +830,9 @@ pub fn default_panic(msg: []const u8, error_return_trace: ?*StackTrace, ret_addr } // Didn't have boot_services, just fallback to whatever. - std.os.abort(); + std.posix.abort(); }, - .cuda, .amdhsa => std.os.abort(), + .cuda, .amdhsa => std.posix.abort(), .plan9 => { var status: [std.os.plan9.ERRMAX]u8 = undefined; const len = @min(msg.len, status.len - 1); diff --git a/lib/std/c.zig b/lib/std/c.zig index 7f8f46ce4d65..a7468efd7d9f 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -2,12 +2,13 @@ const std = @import("std"); const builtin = @import("builtin"); const c = @This(); const page_size = std.mem.page_size; -const iovec = std.os.iovec; -const iovec_const = std.os.iovec_const; +const iovec = std.posix.iovec; +const iovec_const = std.posix.iovec_const; const wasi = @import("c/wasi.zig"); const native_abi = builtin.abi; const native_arch = builtin.cpu.arch; const native_os = builtin.os.tag; +const linux = std.os.linux; /// If not linking libc, returns false. /// If linking musl libc, returns true. @@ -208,7 +209,7 @@ pub const pthread_rwlock_t = switch (native_os) { }; pub const AT = switch (native_os) { - .linux => std.os.linux.AT, + .linux => linux.AT, .windows => struct { /// Remove directory instead of unlinking file pub const REMOVEDIR = 0x200; @@ -326,9 +327,9 @@ pub const AT = switch (native_os) { }; pub const O = switch (native_os) { - .linux => std.os.linux.O, + .linux => linux.O, .emscripten => packed struct(u32) { - ACCMODE: std.os.ACCMODE = .RDONLY, + ACCMODE: std.posix.ACCMODE = .RDONLY, _2: u4 = 0, CREAT: bool = false, EXCL: bool = false, @@ -369,7 +370,7 @@ pub const O = switch (native_os) { _: u3 = 0, }, .solaris, .illumos => packed struct(u32) { - ACCMODE: std.os.ACCMODE = .RDONLY, + ACCMODE: std.posix.ACCMODE = .RDONLY, NDELAY: bool = false, APPEND: bool = false, SYNC: bool = false, @@ -396,7 +397,7 @@ pub const O = switch (native_os) { _: u6 = 0, }, .netbsd => packed struct(u32) { - ACCMODE: std.os.ACCMODE = .RDONLY, + ACCMODE: std.posix.ACCMODE = .RDONLY, NONBLOCK: bool = false, APPEND: bool = false, SHLOCK: bool = false, @@ -420,7 +421,7 @@ pub const O = switch (native_os) { _: u8 = 0, }, .openbsd => packed struct(u32) { - ACCMODE: std.os.ACCMODE = .RDONLY, + ACCMODE: std.posix.ACCMODE = .RDONLY, NONBLOCK: bool = false, APPEND: bool = false, SHLOCK: bool = false, @@ -438,7 +439,7 @@ pub const O = switch (native_os) { _: u14 = 0, }, .haiku => packed struct(u32) { - ACCMODE: std.os.ACCMODE = .RDONLY, + ACCMODE: std.posix.ACCMODE = .RDONLY, _2: u4 = 0, CLOEXEC: bool = false, NONBLOCK: bool = false, @@ -458,7 +459,7 @@ pub const O = switch (native_os) { _: u10 = 0, }, .macos, .ios, .tvos, .watchos => packed struct(u32) { - ACCMODE: std.os.ACCMODE = .RDONLY, + ACCMODE: std.posix.ACCMODE = .RDONLY, NONBLOCK: bool = false, APPEND: bool = false, SHLOCK: bool = false, @@ -485,7 +486,7 @@ pub const O = switch (native_os) { POPUP: bool = false, }, .dragonfly => packed struct(u32) { - ACCMODE: std.os.ACCMODE = .RDONLY, + ACCMODE: std.posix.ACCMODE = .RDONLY, NONBLOCK: bool = false, APPEND: bool = false, SHLOCK: bool = false, @@ -511,7 +512,7 @@ pub const O = switch (native_os) { _: u4 = 0, }, .freebsd => packed struct(u32) { - ACCMODE: std.os.ACCMODE = .RDONLY, + ACCMODE: std.posix.ACCMODE = .RDONLY, NONBLOCK: bool = false, APPEND: bool = false, SHLOCK: bool = false, @@ -538,7 +539,7 @@ pub const O = switch (native_os) { }; pub const MAP = switch (native_os) { - .linux => std.os.linux.MAP, + .linux => linux.MAP, .emscripten => packed struct(u32) { TYPE: enum(u4) { SHARED = 0x01, @@ -683,7 +684,7 @@ pub const cc_t = u8; /// Indices into the `cc` array in the `termios` struct. pub const V = switch (native_os) { - .linux => std.os.linux.V, + .linux => linux.V, .macos, .ios, .tvos, .watchos, .netbsd, .openbsd => enum { EOF, EOL, @@ -782,7 +783,7 @@ pub const V = switch (native_os) { }; pub const NCCS = switch (native_os) { - .linux => std.os.linux.NCCS, + .linux => linux.NCCS, .macos, .ios, .tvos, .watchos, .freebsd, .kfreebsd, .netbsd, .openbsd, .dragonfly => 20, .haiku => 11, .solaris, .illumos => 19, @@ -791,7 +792,7 @@ pub const NCCS = switch (native_os) { }; pub const termios = switch (native_os) { - .linux => std.os.linux.termios, + .linux => linux.termios, .macos, .ios, .tvos, .watchos => extern struct { iflag: tc_iflag_t, oflag: tc_oflag_t, @@ -841,7 +842,7 @@ pub const termios = switch (native_os) { }; pub const tc_iflag_t = switch (native_os) { - .linux => std.os.linux.tc_iflag_t, + .linux => linux.tc_iflag_t, .macos, .ios, .tvos, .watchos => packed struct(u64) { IGNBRK: bool = false, BRKINT: bool = false, @@ -951,7 +952,7 @@ pub const tc_iflag_t = switch (native_os) { }; pub const tc_oflag_t = switch (native_os) { - .linux => std.os.linux.tc_oflag_t, + .linux => linux.tc_oflag_t, .macos, .ios, .tvos, .watchos => packed struct(u64) { OPOST: bool = false, ONLCR: bool = false, @@ -1042,13 +1043,13 @@ pub const tc_oflag_t = switch (native_os) { }; pub const CSIZE = switch (native_os) { - .linux => std.os.linux.CSIZE, + .linux => linux.CSIZE, .haiku => enum(u1) { CS7, CS8 }, else => enum(u2) { CS5, CS6, CS7, CS8 }, }; pub const tc_cflag_t = switch (native_os) { - .linux => std.os.linux.tc_cflag_t, + .linux => linux.tc_cflag_t, .macos, .ios, .tvos, .watchos => packed struct(u64) { CIGNORE: bool = false, _1: u5 = 0, @@ -1184,7 +1185,7 @@ pub const tc_cflag_t = switch (native_os) { }; pub const tc_lflag_t = switch (native_os) { - .linux => std.os.linux.tc_lflag_t, + .linux => linux.tc_lflag_t, .macos, .ios, .tvos, .watchos => packed struct(u64) { ECHOKE: bool = false, ECHOE: bool = false, @@ -1310,7 +1311,7 @@ pub const tc_lflag_t = switch (native_os) { }; pub const speed_t = switch (native_os) { - .linux => std.os.linux.speed_t, + .linux => linux.speed_t, .macos, .ios, .tvos, .watchos, .openbsd => enum(u64) { B0 = 0, B50 = 50, @@ -1605,14 +1606,6 @@ pub const stat = switch (native_os) { else => private.stat, }; -pub fn getErrno(rc: anytype) c.E { - if (rc == -1) { - return @enumFromInt(c._errno().*); - } else { - return .SUCCESS; - } -} - pub extern "c" var environ: [*:null]?[*:0]u8; pub extern "c" fn fopen(noalias filename: [*:0]const u8, noalias modes: [*:0]const u8) ?*FILE; @@ -1905,10 +1898,10 @@ pub extern "c" fn if_nametoindex([*:0]const u8) c_int; pub const getcontext = if (builtin.target.isAndroid()) @compileError("android bionic libc does not implement getcontext") else if (native_os == .linux and builtin.target.isMusl()) - std.os.linux.getcontext + linux.getcontext else struct { - extern fn getcontext(ucp: *std.os.ucontext_t) c_int; + extern fn getcontext(ucp: *std.posix.ucontext_t) c_int; }.getcontext; pub const max_align_t = if (native_abi == .msvc) diff --git a/lib/std/c/darwin.zig b/lib/std/c/darwin.zig index 8442ac9fe00c..521aca507eef 100644 --- a/lib/std/c/darwin.zig +++ b/lib/std/c/darwin.zig @@ -4,7 +4,7 @@ const assert = std.debug.assert; const macho = std.macho; const native_arch = builtin.target.cpu.arch; const maxInt = std.math.maxInt; -const iovec_const = std.os.iovec_const; +const iovec_const = std.posix.iovec_const; pub const aarch64 = @import("darwin/aarch64.zig"); pub const x86_64 = @import("darwin/x86_64.zig"); @@ -2826,237 +2826,12 @@ pub extern "c" fn posix_spawnp( env: [*:null]?[*:0]const u8, ) c_int; -pub const PosixSpawn = struct { - const errno = std.os.errno; - const unexpectedErrno = std.os.unexpectedErrno; - - pub const Error = error{ - SystemResources, - InvalidFileDescriptor, - NameTooLong, - TooBig, - PermissionDenied, - InputOutput, - FileSystem, - FileNotFound, - InvalidExe, - NotDir, - FileBusy, - /// Returned when the child fails to execute either in the pre-exec() initialization step, or - /// when exec(3) is invoked. - ChildExecFailed, - } || std.os.UnexpectedError; - - pub const Attr = struct { - attr: posix_spawnattr_t, - - pub fn init() Error!Attr { - var attr: posix_spawnattr_t = undefined; - switch (errno(posix_spawnattr_init(&attr))) { - .SUCCESS => return Attr{ .attr = attr }, - .NOMEM => return error.SystemResources, - .INVAL => unreachable, - else => |err| return unexpectedErrno(err), - } - } - - pub fn deinit(self: *Attr) void { - defer self.* = undefined; - switch (errno(posix_spawnattr_destroy(&self.attr))) { - .SUCCESS => return, - .INVAL => unreachable, // Invalid parameters. - else => unreachable, - } - } - - pub fn get(self: Attr) Error!u16 { - var flags: c_short = undefined; - switch (errno(posix_spawnattr_getflags(&self.attr, &flags))) { - .SUCCESS => return @as(u16, @bitCast(flags)), - .INVAL => unreachable, - else => |err| return unexpectedErrno(err), - } - } - - pub fn set(self: *Attr, flags: u16) Error!void { - switch (errno(posix_spawnattr_setflags(&self.attr, @as(c_short, @bitCast(flags))))) { - .SUCCESS => return, - .INVAL => unreachable, - else => |err| return unexpectedErrno(err), - } - } - }; - - pub const Actions = struct { - actions: posix_spawn_file_actions_t, - - pub fn init() Error!Actions { - var actions: posix_spawn_file_actions_t = undefined; - switch (errno(posix_spawn_file_actions_init(&actions))) { - .SUCCESS => return Actions{ .actions = actions }, - .NOMEM => return error.SystemResources, - .INVAL => unreachable, - else => |err| return unexpectedErrno(err), - } - } - - pub fn deinit(self: *Actions) void { - defer self.* = undefined; - switch (errno(posix_spawn_file_actions_destroy(&self.actions))) { - .SUCCESS => return, - .INVAL => unreachable, // Invalid parameters. - else => unreachable, - } - } - - pub fn open(self: *Actions, fd: fd_t, path: []const u8, flags: u32, mode: mode_t) Error!void { - const posix_path = try std.os.toPosixPath(path); - return self.openZ(fd, &posix_path, flags, mode); - } - - pub fn openZ(self: *Actions, fd: fd_t, path: [*:0]const u8, flags: u32, mode: mode_t) Error!void { - switch (errno(posix_spawn_file_actions_addopen(&self.actions, fd, path, @as(c_int, @bitCast(flags)), mode))) { - .SUCCESS => return, - .BADF => return error.InvalidFileDescriptor, - .NOMEM => return error.SystemResources, - .NAMETOOLONG => return error.NameTooLong, - .INVAL => unreachable, // the value of file actions is invalid - else => |err| return unexpectedErrno(err), - } - } - - pub fn close(self: *Actions, fd: fd_t) Error!void { - switch (errno(posix_spawn_file_actions_addclose(&self.actions, fd))) { - .SUCCESS => return, - .BADF => return error.InvalidFileDescriptor, - .NOMEM => return error.SystemResources, - .INVAL => unreachable, // the value of file actions is invalid - .NAMETOOLONG => unreachable, - else => |err| return unexpectedErrno(err), - } - } - - pub fn dup2(self: *Actions, fd: fd_t, newfd: fd_t) Error!void { - switch (errno(posix_spawn_file_actions_adddup2(&self.actions, fd, newfd))) { - .SUCCESS => return, - .BADF => return error.InvalidFileDescriptor, - .NOMEM => return error.SystemResources, - .INVAL => unreachable, // the value of file actions is invalid - .NAMETOOLONG => unreachable, - else => |err| return unexpectedErrno(err), - } - } - - pub fn inherit(self: *Actions, fd: fd_t) Error!void { - switch (errno(posix_spawn_file_actions_addinherit_np(&self.actions, fd))) { - .SUCCESS => return, - .BADF => return error.InvalidFileDescriptor, - .NOMEM => return error.SystemResources, - .INVAL => unreachable, // the value of file actions is invalid - .NAMETOOLONG => unreachable, - else => |err| return unexpectedErrno(err), - } - } - - pub fn chdir(self: *Actions, path: []const u8) Error!void { - const posix_path = try std.os.toPosixPath(path); - return self.chdirZ(&posix_path); - } - - pub fn chdirZ(self: *Actions, path: [*:0]const u8) Error!void { - switch (errno(posix_spawn_file_actions_addchdir_np(&self.actions, path))) { - .SUCCESS => return, - .NOMEM => return error.SystemResources, - .NAMETOOLONG => return error.NameTooLong, - .BADF => unreachable, - .INVAL => unreachable, // the value of file actions is invalid - else => |err| return unexpectedErrno(err), - } - } - - pub fn fchdir(self: *Actions, fd: fd_t) Error!void { - switch (errno(posix_spawn_file_actions_addfchdir_np(&self.actions, fd))) { - .SUCCESS => return, - .BADF => return error.InvalidFileDescriptor, - .NOMEM => return error.SystemResources, - .INVAL => unreachable, // the value of file actions is invalid - .NAMETOOLONG => unreachable, - else => |err| return unexpectedErrno(err), - } - } - }; - - pub fn spawn( - path: []const u8, - actions: ?Actions, - attr: ?Attr, - argv: [*:null]?[*:0]const u8, - envp: [*:null]?[*:0]const u8, - ) Error!pid_t { - const posix_path = try std.os.toPosixPath(path); - return spawnZ(&posix_path, actions, attr, argv, envp); - } - - pub fn spawnZ( - path: [*:0]const u8, - actions: ?Actions, - attr: ?Attr, - argv: [*:null]?[*:0]const u8, - envp: [*:null]?[*:0]const u8, - ) Error!pid_t { - var pid: pid_t = undefined; - switch (errno(posix_spawn( - &pid, - path, - if (actions) |a| &a.actions else null, - if (attr) |a| &a.attr else null, - argv, - envp, - ))) { - .SUCCESS => return pid, - .@"2BIG" => return error.TooBig, - .NOMEM => return error.SystemResources, - .BADF => return error.InvalidFileDescriptor, - .ACCES => return error.PermissionDenied, - .IO => return error.InputOutput, - .LOOP => return error.FileSystem, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOEXEC => return error.InvalidExe, - .NOTDIR => return error.NotDir, - .TXTBSY => return error.FileBusy, - .BADARCH => return error.InvalidExe, - .BADEXEC => return error.InvalidExe, - .FAULT => unreachable, - .INVAL => unreachable, - else => |err| return unexpectedErrno(err), - } - } - - pub fn waitpid(pid: pid_t, flags: u32) Error!std.os.WaitPidResult { - var status: c_int = undefined; - while (true) { - const rc = waitpid(pid, &status, @as(c_int, @intCast(flags))); - switch (errno(rc)) { - .SUCCESS => return std.os.WaitPidResult{ - .pid = @as(pid_t, @intCast(rc)), - .status = @as(u32, @bitCast(status)), - }, - .INTR => continue, - .CHILD => return error.ChildExecFailed, - .INVAL => unreachable, // Invalid flags. - else => unreachable, - } - } - } -}; - pub fn getKernError(err: kern_return_t) KernE { return @as(KernE, @enumFromInt(@as(u32, @truncate(@as(usize, @intCast(err)))))); } -pub fn unexpectedKernError(err: KernE) std.os.UnexpectedError { - if (std.os.unexpected_error_tracing) { +pub fn unexpectedKernError(err: KernE) std.posix.UnexpectedError { + if (std.posix.unexpected_error_tracing) { std.debug.print("unexpected error: {d}\n", .{@intFromEnum(err)}); std.debug.dumpCurrentStackTrace(null); } @@ -3067,7 +2842,7 @@ pub const MachError = error{ /// Not enough permissions held to perform the requested kernel /// call. PermissionDenied, -} || std.os.UnexpectedError; +} || std.posix.UnexpectedError; pub const MachTask = extern struct { port: mach_port_name_t, @@ -3076,8 +2851,8 @@ pub const MachTask = extern struct { return self.port != TASK_NULL; } - pub fn pidForTask(self: MachTask) MachError!std.os.pid_t { - var pid: std.os.pid_t = undefined; + pub fn pidForTask(self: MachTask) MachError!std.c.pid_t { + var pid: std.c.pid_t = undefined; switch (getKernError(pid_for_task(self.port, &pid))) { .SUCCESS => return pid, .FAILURE => return error.PermissionDenied, @@ -3517,7 +3292,7 @@ pub const MachThread = extern struct { } }; -pub fn machTaskForPid(pid: std.os.pid_t) MachError!MachTask { +pub fn machTaskForPid(pid: std.c.pid_t) MachError!MachTask { var port: mach_port_name_t = undefined; switch (getKernError(task_for_pid(mach_task_self(), pid, &port))) { .SUCCESS => {}, diff --git a/lib/std/c/dragonfly.zig b/lib/std/c/dragonfly.zig index 183a81bba2a2..c265fd0ff27f 100644 --- a/lib/std/c/dragonfly.zig +++ b/lib/std/c/dragonfly.zig @@ -2,7 +2,7 @@ const builtin = @import("builtin"); const std = @import("../std.zig"); const assert = std.debug.assert; const maxInt = std.math.maxInt; -const iovec = std.os.iovec; +const iovec = std.posix.iovec; extern "c" threadlocal var errno: c_int; pub fn _errno() *c_int { diff --git a/lib/std/c/freebsd.zig b/lib/std/c/freebsd.zig index a89ca30968fc..6d0b88e3c35b 100644 --- a/lib/std/c/freebsd.zig +++ b/lib/std/c/freebsd.zig @@ -2,8 +2,8 @@ const std = @import("../std.zig"); const assert = std.debug.assert; const builtin = @import("builtin"); const maxInt = std.math.maxInt; -const iovec = std.os.iovec; -const iovec_const = std.os.iovec_const; +const iovec = std.posix.iovec; +const iovec_const = std.posix.iovec_const; extern "c" fn __error() *c_int; pub const _errno = __error; diff --git a/lib/std/c/haiku.zig b/lib/std/c/haiku.zig index 12b5201acd86..58bd55d17ea2 100644 --- a/lib/std/c/haiku.zig +++ b/lib/std/c/haiku.zig @@ -2,8 +2,8 @@ const std = @import("../std.zig"); const assert = std.debug.assert; const builtin = @import("builtin"); const maxInt = std.math.maxInt; -const iovec = std.os.iovec; -const iovec_const = std.os.iovec_const; +const iovec = std.posix.iovec; +const iovec_const = std.posix.iovec_const; extern "c" fn _errnop() *c_int; diff --git a/lib/std/c/linux.zig b/lib/std/c/linux.zig index 03c90e5760dd..1a3c63451502 100644 --- a/lib/std/c/linux.zig +++ b/lib/std/c/linux.zig @@ -3,8 +3,8 @@ const builtin = @import("builtin"); const native_abi = builtin.abi; const native_arch = builtin.cpu.arch; const linux = std.os.linux; -const iovec = std.os.iovec; -const iovec_const = std.os.iovec_const; +const iovec = std.posix.iovec; +const iovec_const = std.posix.iovec_const; const FILE = std.c.FILE; pub const AF = linux.AF; diff --git a/lib/std/c/netbsd.zig b/lib/std/c/netbsd.zig index c06857787ae4..fb2aefdb44b2 100644 --- a/lib/std/c/netbsd.zig +++ b/lib/std/c/netbsd.zig @@ -2,8 +2,8 @@ const std = @import("../std.zig"); const assert = std.debug.assert; const builtin = @import("builtin"); const maxInt = std.math.maxInt; -const iovec = std.os.iovec; -const iovec_const = std.os.iovec_const; +const iovec = std.posix.iovec; +const iovec_const = std.posix.iovec_const; const timezone = std.c.timezone; const rusage = std.c.rusage; diff --git a/lib/std/c/openbsd.zig b/lib/std/c/openbsd.zig index 4fd450cd5ce4..e97798b0e794 100644 --- a/lib/std/c/openbsd.zig +++ b/lib/std/c/openbsd.zig @@ -2,8 +2,8 @@ const std = @import("../std.zig"); const assert = std.debug.assert; const maxInt = std.math.maxInt; const builtin = @import("builtin"); -const iovec = std.os.iovec; -const iovec_const = std.os.iovec_const; +const iovec = std.posix.iovec; +const iovec_const = std.posix.iovec_const; extern "c" fn __errno() *c_int; pub const _errno = __errno; diff --git a/lib/std/c/solaris.zig b/lib/std/c/solaris.zig index 838b6985cc08..31900c83728b 100644 --- a/lib/std/c/solaris.zig +++ b/lib/std/c/solaris.zig @@ -2,8 +2,8 @@ const std = @import("../std.zig"); const assert = std.debug.assert; const builtin = @import("builtin"); const maxInt = std.math.maxInt; -const iovec = std.os.iovec; -const iovec_const = std.os.iovec_const; +const iovec = std.posix.iovec; +const iovec_const = std.posix.iovec_const; const timezone = std.c.timezone; extern "c" fn ___errno() *c_int; diff --git a/lib/std/child_process.zig b/lib/std/child_process.zig index 85fa3d294efc..c9db06d40b9d 100644 --- a/lib/std/child_process.zig +++ b/lib/std/child_process.zig @@ -3,30 +3,31 @@ const builtin = @import("builtin"); const unicode = std.unicode; const io = std.io; const fs = std.fs; -const os = std.os; const process = std.process; const File = std.fs.File; -const windows = os.windows; -const linux = os.linux; +const windows = std.os.windows; +const linux = std.os.linux; +const posix = std.posix; const mem = std.mem; const math = std.math; const debug = std.debug; const EnvMap = process.EnvMap; const maxInt = std.math.maxInt; const assert = std.debug.assert; +const native_os = builtin.os.tag; pub const ChildProcess = struct { - pub const Id = switch (builtin.os.tag) { + pub const Id = switch (native_os) { .windows => windows.HANDLE, .wasi => void, - else => os.pid_t, + else => posix.pid_t, }; /// Available after calling `spawn()`. This becomes `undefined` after calling `wait()`. /// On Windows this is the hProcess. /// On POSIX this is the pid. id: Id, - thread_handle: if (builtin.os.tag == .windows) windows.HANDLE else void, + thread_handle: if (native_os == .windows) windows.HANDLE else void, allocator: mem.Allocator, @@ -46,10 +47,10 @@ pub const ChildProcess = struct { stderr_behavior: StdIo, /// Set to change the user id when spawning the child process. - uid: if (builtin.os.tag == .windows or builtin.os.tag == .wasi) void else ?os.uid_t, + uid: if (native_os == .windows or native_os == .wasi) void else ?posix.uid_t, /// Set to change the group id when spawning the child process. - gid: if (builtin.os.tag == .windows or builtin.os.tag == .wasi) void else ?os.gid_t, + gid: if (native_os == .windows or native_os == .wasi) void else ?posix.gid_t, /// Set to change the current working directory when spawning the child process. cwd: ?[]const u8, @@ -58,7 +59,7 @@ pub const ChildProcess = struct { /// Once that is done, `cwd` will be deprecated in favor of this field. cwd_dir: ?fs.Dir = null, - err_pipe: ?if (builtin.os.tag == .windows) void else [2]os.fd_t, + err_pipe: ?if (native_os == .windows) void else [2]posix.fd_t, expand_arg0: Arg0Expand, @@ -87,7 +88,7 @@ pub const ChildProcess = struct { /// Returns the peak resident set size of the child process, in bytes, /// if available. pub inline fn getMaxRss(rus: ResourceUsageStatistics) ?usize { - switch (builtin.os.tag) { + switch (native_os) { .linux => { if (rus.rusage) |ru| { return @as(usize, @intCast(ru.maxrss)) * 1024; @@ -114,14 +115,14 @@ pub const ChildProcess = struct { } } - const rusage_init = switch (builtin.os.tag) { - .linux, .macos, .ios => @as(?std.os.rusage, null), + const rusage_init = switch (native_os) { + .linux, .macos, .ios => @as(?posix.rusage, null), .windows => @as(?windows.VM_COUNTERS, null), else => {}, }; }; - pub const Arg0Expand = os.Arg0Expand; + pub const Arg0Expand = posix.Arg0Expand; pub const SpawnError = error{ OutOfMemory, @@ -136,9 +137,9 @@ pub const ChildProcess = struct { /// Windows-only. `cwd` was provided, but the path did not exist when spawning the child process. CurrentWorkingDirectoryUnlinked, } || - os.ExecveError || - os.SetIdError || - os.ChangeCurDirError || + posix.ExecveError || + posix.SetIdError || + posix.ChangeCurDirError || windows.CreateProcessError || windows.GetProcessMemoryInfoError || windows.WaitForSingleObjectError; @@ -168,8 +169,8 @@ pub const ChildProcess = struct { .term = null, .env_map = null, .cwd = null, - .uid = if (builtin.os.tag == .windows or builtin.os.tag == .wasi) {} else null, - .gid = if (builtin.os.tag == .windows or builtin.os.tag == .wasi) {} else null, + .uid = if (native_os == .windows or native_os == .wasi) {} else null, + .gid = if (native_os == .windows or native_os == .wasi) {} else null, .stdin = null, .stdout = null, .stderr = null, @@ -193,7 +194,7 @@ pub const ChildProcess = struct { @compileError("the target operating system cannot spawn processes"); } - if (builtin.os.tag == .windows) { + if (native_os == .windows) { return self.spawnWindows(); } else { return self.spawnPosix(); @@ -207,7 +208,7 @@ pub const ChildProcess = struct { /// Forcibly terminates child process and then cleans up all resources. pub fn kill(self: *ChildProcess) !Term { - if (builtin.os.tag == .windows) { + if (native_os == .windows) { return self.killWindows(1); } else { return self.killPosix(); @@ -241,7 +242,7 @@ pub const ChildProcess = struct { self.cleanupStreams(); return term; } - os.kill(self.id, os.SIG.TERM) catch |err| switch (err) { + posix.kill(self.id, posix.SIG.TERM) catch |err| switch (err) { error.ProcessNotFound => return error.AlreadyTerminated, else => return err, }; @@ -251,7 +252,7 @@ pub const ChildProcess = struct { /// Blocks until child process terminates and then cleans up all resources. pub fn wait(self: *ChildProcess) !Term { - const term = if (builtin.os.tag == .windows) + const term = if (native_os == .windows) try self.waitWindows() else try self.waitPosix(); @@ -318,7 +319,7 @@ pub const ChildProcess = struct { stderr.* = fifoToOwnedArrayList(poller.fifo(.stderr)); } - pub const RunError = os.GetCwdError || os.ReadError || SpawnError || os.PollError || error{ + pub const RunError = posix.GetCwdError || posix.ReadError || SpawnError || posix.PollError || error{ StdoutStreamTooLong, StderrStreamTooLong, }; @@ -396,19 +397,19 @@ pub const ChildProcess = struct { self.resource_usage_statistics.rusage = try windows.GetProcessMemoryInfo(self.id); } - os.close(self.id); - os.close(self.thread_handle); + posix.close(self.id); + posix.close(self.thread_handle); self.cleanupStreams(); return result; } fn waitUnwrapped(self: *ChildProcess) !void { - const res: os.WaitPidResult = res: { + const res: posix.WaitPidResult = res: { if (self.request_resource_usage_statistics) { - switch (builtin.os.tag) { + switch (native_os) { .linux, .macos, .ios => { - var ru: std.os.rusage = undefined; - const res = os.wait4(self.id, 0, &ru); + var ru: posix.rusage = undefined; + const res = posix.wait4(self.id, 0, &ru); self.resource_usage_statistics.rusage = ru; break :res res; }, @@ -416,7 +417,7 @@ pub const ChildProcess = struct { } } - break :res os.waitpid(self.id, 0); + break :res posix.waitpid(self.id, 0); }; const status = res.status; self.cleanupStreams(); @@ -446,20 +447,20 @@ pub const ChildProcess = struct { if (self.err_pipe) |err_pipe| { defer destroyPipe(err_pipe); - if (builtin.os.tag == .linux) { - var fd = [1]std.os.pollfd{std.os.pollfd{ + if (native_os == .linux) { + var fd = [1]posix.pollfd{posix.pollfd{ .fd = err_pipe[0], - .events = std.os.POLL.IN, + .events = posix.POLL.IN, .revents = undefined, }}; // Check if the eventfd buffer stores a non-zero value by polling // it, that's the error code returned by the child process. - _ = std.os.poll(&fd, 0) catch unreachable; + _ = posix.poll(&fd, 0) catch unreachable; // According to eventfd(2) the descriptor is readable if the counter // has a value greater than 0 - if ((fd[0].revents & std.os.POLL.IN) != 0) { + if ((fd[0].revents & posix.POLL.IN) != 0) { const err_int = try readIntFd(err_pipe[0]); return @as(SpawnError, @errorCast(@errorFromInt(err_int))); } @@ -483,36 +484,36 @@ pub const ChildProcess = struct { } fn statusToTerm(status: u32) Term { - return if (os.W.IFEXITED(status)) - Term{ .Exited = os.W.EXITSTATUS(status) } - else if (os.W.IFSIGNALED(status)) - Term{ .Signal = os.W.TERMSIG(status) } - else if (os.W.IFSTOPPED(status)) - Term{ .Stopped = os.W.STOPSIG(status) } + return if (posix.W.IFEXITED(status)) + Term{ .Exited = posix.W.EXITSTATUS(status) } + else if (posix.W.IFSIGNALED(status)) + Term{ .Signal = posix.W.TERMSIG(status) } + else if (posix.W.IFSTOPPED(status)) + Term{ .Stopped = posix.W.STOPSIG(status) } else Term{ .Unknown = status }; } fn spawnPosix(self: *ChildProcess) SpawnError!void { - const pipe_flags: os.O = .{}; - const stdin_pipe = if (self.stdin_behavior == StdIo.Pipe) try os.pipe2(pipe_flags) else undefined; + const pipe_flags: posix.O = .{}; + const stdin_pipe = if (self.stdin_behavior == StdIo.Pipe) try posix.pipe2(pipe_flags) else undefined; errdefer if (self.stdin_behavior == StdIo.Pipe) { destroyPipe(stdin_pipe); }; - const stdout_pipe = if (self.stdout_behavior == StdIo.Pipe) try os.pipe2(pipe_flags) else undefined; + const stdout_pipe = if (self.stdout_behavior == StdIo.Pipe) try posix.pipe2(pipe_flags) else undefined; errdefer if (self.stdout_behavior == StdIo.Pipe) { destroyPipe(stdout_pipe); }; - const stderr_pipe = if (self.stderr_behavior == StdIo.Pipe) try os.pipe2(pipe_flags) else undefined; + const stderr_pipe = if (self.stderr_behavior == StdIo.Pipe) try posix.pipe2(pipe_flags) else undefined; errdefer if (self.stderr_behavior == StdIo.Pipe) { destroyPipe(stderr_pipe); }; const any_ignore = (self.stdin_behavior == StdIo.Ignore or self.stdout_behavior == StdIo.Ignore or self.stderr_behavior == StdIo.Ignore); const dev_null_fd = if (any_ignore) - os.openZ("/dev/null", .{ .ACCMODE = .RDWR }, 0) catch |err| switch (err) { + posix.openZ("/dev/null", .{ .ACCMODE = .RDWR }, 0) catch |err| switch (err) { error.PathAlreadyExists => unreachable, error.NoSpaceLeft => unreachable, error.FileTooBig => unreachable, @@ -526,7 +527,7 @@ pub const ChildProcess = struct { else undefined; defer { - if (any_ignore) os.close(dev_null_fd); + if (any_ignore) posix.close(dev_null_fd); } var arena_allocator = std.heap.ArenaAllocator.init(self.allocator); @@ -554,7 +555,7 @@ pub const ChildProcess = struct { } else if (builtin.output_mode == .Exe) { // Then we have Zig start code and this works. // TODO type-safety for null-termination of `os.environ`. - break :m @as([*:null]const ?[*:0]const u8, @ptrCast(os.environ.ptr)); + break :m @as([*:null]const ?[*:0]const u8, @ptrCast(std.os.environ.ptr)); } else { // TODO come up with a solution for this. @compileError("missing std lib enhancement: ChildProcess implementation has no way to collect the environment variables to forward to the child process"); @@ -564,60 +565,60 @@ pub const ChildProcess = struct { // This pipe is used to communicate errors between the time of fork // and execve from the child process to the parent process. const err_pipe = blk: { - if (builtin.os.tag == .linux) { - const fd = try os.eventfd(0, linux.EFD.CLOEXEC); + if (native_os == .linux) { + const fd = try posix.eventfd(0, linux.EFD.CLOEXEC); // There's no distinction between the readable and the writeable // end with eventfd - break :blk [2]os.fd_t{ fd, fd }; + break :blk [2]posix.fd_t{ fd, fd }; } else { - break :blk try os.pipe2(.{ .CLOEXEC = true }); + break :blk try posix.pipe2(.{ .CLOEXEC = true }); } }; errdefer destroyPipe(err_pipe); - const pid_result = try os.fork(); + const pid_result = try posix.fork(); if (pid_result == 0) { // we are the child - setUpChildIo(self.stdin_behavior, stdin_pipe[0], os.STDIN_FILENO, dev_null_fd) catch |err| forkChildErrReport(err_pipe[1], err); - setUpChildIo(self.stdout_behavior, stdout_pipe[1], os.STDOUT_FILENO, dev_null_fd) catch |err| forkChildErrReport(err_pipe[1], err); - setUpChildIo(self.stderr_behavior, stderr_pipe[1], os.STDERR_FILENO, dev_null_fd) catch |err| forkChildErrReport(err_pipe[1], err); + setUpChildIo(self.stdin_behavior, stdin_pipe[0], posix.STDIN_FILENO, dev_null_fd) catch |err| forkChildErrReport(err_pipe[1], err); + setUpChildIo(self.stdout_behavior, stdout_pipe[1], posix.STDOUT_FILENO, dev_null_fd) catch |err| forkChildErrReport(err_pipe[1], err); + setUpChildIo(self.stderr_behavior, stderr_pipe[1], posix.STDERR_FILENO, dev_null_fd) catch |err| forkChildErrReport(err_pipe[1], err); if (self.stdin_behavior == .Pipe) { - os.close(stdin_pipe[0]); - os.close(stdin_pipe[1]); + posix.close(stdin_pipe[0]); + posix.close(stdin_pipe[1]); } if (self.stdout_behavior == .Pipe) { - os.close(stdout_pipe[0]); - os.close(stdout_pipe[1]); + posix.close(stdout_pipe[0]); + posix.close(stdout_pipe[1]); } if (self.stderr_behavior == .Pipe) { - os.close(stderr_pipe[0]); - os.close(stderr_pipe[1]); + posix.close(stderr_pipe[0]); + posix.close(stderr_pipe[1]); } if (self.cwd_dir) |cwd| { - os.fchdir(cwd.fd) catch |err| forkChildErrReport(err_pipe[1], err); + posix.fchdir(cwd.fd) catch |err| forkChildErrReport(err_pipe[1], err); } else if (self.cwd) |cwd| { - os.chdir(cwd) catch |err| forkChildErrReport(err_pipe[1], err); + posix.chdir(cwd) catch |err| forkChildErrReport(err_pipe[1], err); } if (self.gid) |gid| { - os.setregid(gid, gid) catch |err| forkChildErrReport(err_pipe[1], err); + posix.setregid(gid, gid) catch |err| forkChildErrReport(err_pipe[1], err); } if (self.uid) |uid| { - os.setreuid(uid, uid) catch |err| forkChildErrReport(err_pipe[1], err); + posix.setreuid(uid, uid) catch |err| forkChildErrReport(err_pipe[1], err); } const err = switch (self.expand_arg0) { - .expand => os.execvpeZ_expandArg0(.expand, argv_buf.ptr[0].?, argv_buf.ptr, envp), - .no_expand => os.execvpeZ_expandArg0(.no_expand, argv_buf.ptr[0].?, argv_buf.ptr, envp), + .expand => posix.execvpeZ_expandArg0(.expand, argv_buf.ptr[0].?, argv_buf.ptr, envp), + .no_expand => posix.execvpeZ_expandArg0(.no_expand, argv_buf.ptr[0].?, argv_buf.ptr, envp), }; forkChildErrReport(err_pipe[1], err); } // we are the parent - const pid = @as(i32, @intCast(pid_result)); + const pid: i32 = @intCast(pid_result); if (self.stdin_behavior == StdIo.Pipe) { self.stdin = File{ .handle = stdin_pipe[1] }; } else { @@ -639,13 +640,13 @@ pub const ChildProcess = struct { self.term = null; if (self.stdin_behavior == StdIo.Pipe) { - os.close(stdin_pipe[0]); + posix.close(stdin_pipe[0]); } if (self.stdout_behavior == StdIo.Pipe) { - os.close(stdout_pipe[1]); + posix.close(stdout_pipe[1]); } if (self.stderr_behavior == StdIo.Pipe) { - os.close(stderr_pipe[1]); + posix.close(stderr_pipe[1]); } } @@ -679,7 +680,7 @@ pub const ChildProcess = struct { else undefined; defer { - if (any_ignore) os.close(nul_handle); + if (any_ignore) posix.close(nul_handle); } var g_hChildStd_IN_Rd: ?windows.HANDLE = null; @@ -821,8 +822,8 @@ pub const ChildProcess = struct { defer self.allocator.free(cmd_line_w); run: { - const PATH: [:0]const u16 = std.os.getenvW(unicode.utf8ToUtf16LeStringLiteral("PATH")) orelse &[_:0]u16{}; - const PATHEXT: [:0]const u16 = std.os.getenvW(unicode.utf8ToUtf16LeStringLiteral("PATHEXT")) orelse &[_:0]u16{}; + const PATH: [:0]const u16 = std.process.getenvW(unicode.utf8ToUtf16LeStringLiteral("PATH")) orelse &[_:0]u16{}; + const PATHEXT: [:0]const u16 = std.process.getenvW(unicode.utf8ToUtf16LeStringLiteral("PATHEXT")) orelse &[_:0]u16{}; var app_buf = std.ArrayListUnmanaged(u16){}; defer app_buf.deinit(self.allocator); @@ -905,22 +906,22 @@ pub const ChildProcess = struct { self.term = null; if (self.stdin_behavior == StdIo.Pipe) { - os.close(g_hChildStd_IN_Rd.?); + posix.close(g_hChildStd_IN_Rd.?); } if (self.stderr_behavior == StdIo.Pipe) { - os.close(g_hChildStd_ERR_Wr.?); + posix.close(g_hChildStd_ERR_Wr.?); } if (self.stdout_behavior == StdIo.Pipe) { - os.close(g_hChildStd_OUT_Wr.?); + posix.close(g_hChildStd_OUT_Wr.?); } } fn setUpChildIo(stdio: StdIo, pipe_fd: i32, std_fileno: i32, dev_null_fd: i32) !void { switch (stdio) { - .Pipe => try os.dup2(pipe_fd, std_fileno), - .Close => os.close(std_fileno), + .Pipe => try posix.dup2(pipe_fd, std_fileno), + .Close => posix.close(std_fileno), .Inherit => {}, - .Ignore => try os.dup2(dev_null_fd, std_fileno), + .Ignore => try posix.dup2(dev_null_fd, std_fileno), } } }; @@ -987,7 +988,7 @@ fn windowsCreateProcessPathExt( // This 2048 is arbitrary, we just want it to be large enough to get multiple FILE_DIRECTORY_INFORMATION entries // returned per NtQueryDirectoryFile call. - var file_information_buf: [2048]u8 align(@alignOf(os.windows.FILE_DIRECTORY_INFORMATION)) = undefined; + var file_information_buf: [2048]u8 align(@alignOf(windows.FILE_DIRECTORY_INFORMATION)) = undefined; const file_info_maximum_single_entry_size = @sizeOf(windows.FILE_DIRECTORY_INFORMATION) + (windows.NAME_MAX * 2); if (file_information_buf.len < file_info_maximum_single_entry_size) { @compileError("file_information_buf must be large enough to contain at least one maximum size FILE_DIRECTORY_INFORMATION entry"); @@ -1391,8 +1392,8 @@ fn testArgvToCommandLineWindows(argv: []const []const u8, expected_cmd_line: []c } fn windowsDestroyPipe(rd: ?windows.HANDLE, wr: ?windows.HANDLE) void { - if (rd) |h| os.close(h); - if (wr) |h| os.close(h); + if (rd) |h| posix.close(h); + if (wr) |h| posix.close(h); } fn windowsMakePipeIn(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const windows.SECURITY_ATTRIBUTES) !void { @@ -1443,7 +1444,7 @@ fn windowsMakeAsyncPipe(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *cons else => |err| return windows.unexpectedError(err), } } - errdefer os.close(read_handle); + errdefer posix.close(read_handle); var sattr_copy = sattr.*; const write_handle = windows.kernel32.CreateFileW( @@ -1460,7 +1461,7 @@ fn windowsMakeAsyncPipe(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *cons else => |err| return windows.unexpectedError(err), } } - errdefer os.close(write_handle); + errdefer posix.close(write_handle); try windows.SetHandleInformation(read_handle, windows.HANDLE_FLAG_INHERIT, 0); @@ -1468,9 +1469,9 @@ fn windowsMakeAsyncPipe(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *cons wr.* = write_handle; } -fn destroyPipe(pipe: [2]os.fd_t) void { - os.close(pipe[0]); - if (pipe[0] != pipe[1]) os.close(pipe[1]); +fn destroyPipe(pipe: [2]posix.fd_t) void { + posix.close(pipe[0]); + if (pipe[0] != pipe[1]) posix.close(pipe[1]); } // Child of fork calls this to report an error to the fork parent. @@ -1485,7 +1486,7 @@ fn forkChildErrReport(fd: i32, err: ChildProcess.SpawnError) noreturn { // The _exit(2) function does nothing but make the exit syscall, unlike exit(3) std.c._exit(1); } - os.exit(1); + posix.exit(1); } const ErrInt = std.meta.Int(.unsigned, @sizeOf(anyerror) * 8); diff --git a/lib/std/crypto/Certificate/Bundle.zig b/lib/std/crypto/Certificate/Bundle.zig index b1c3cfee2f90..d1c0a9682cca 100644 --- a/lib/std/crypto/Certificate/Bundle.zig +++ b/lib/std/crypto/Certificate/Bundle.zig @@ -125,7 +125,7 @@ fn rescanBSD(cb: *Bundle, gpa: Allocator, cert_file_path: []const u8) RescanBSDE cb.bytes.shrinkAndFree(gpa, cb.bytes.items.len); } -const RescanWindowsError = Allocator.Error || ParseCertError || std.os.UnexpectedError || error{FileNotFound}; +const RescanWindowsError = Allocator.Error || ParseCertError || std.posix.UnexpectedError || error{FileNotFound}; fn rescanWindows(cb: *Bundle, gpa: Allocator) RescanWindowsError!void { cb.bytes.clearRetainingCapacity(); diff --git a/lib/std/crypto/Certificate/Bundle/macos.zig b/lib/std/crypto/Certificate/Bundle/macos.zig index e4bbfecb7e31..61e3339b3323 100644 --- a/lib/std/crypto/Certificate/Bundle/macos.zig +++ b/lib/std/crypto/Certificate/Bundle/macos.zig @@ -42,7 +42,7 @@ pub fn rescanMac(cb: *Bundle, gpa: Allocator) RescanMacError!void { const table_header = try reader.readStructEndian(TableHeader, .big); - if (@as(std.os.darwin.cssm.DB_RECORDTYPE, @enumFromInt(table_header.table_id)) != .X509_CERTIFICATE) { + if (@as(std.c.cssm.DB_RECORDTYPE, @enumFromInt(table_header.table_id)) != .X509_CERTIFICATE) { continue; } diff --git a/lib/std/crypto/tlcsprng.zig b/lib/std/crypto/tlcsprng.zig index 973a0f8bb4fc..9293887a5988 100644 --- a/lib/std/crypto/tlcsprng.zig +++ b/lib/std/crypto/tlcsprng.zig @@ -6,7 +6,8 @@ const std = @import("std"); const builtin = @import("builtin"); const mem = std.mem; -const os = std.os; +const native_os = builtin.os.tag; +const posix = std.posix; /// We use this as a layer of indirection because global const pointers cannot /// point to thread-local variables. @@ -15,7 +16,7 @@ pub const interface = std.Random{ .fillFn = tlsCsprngFill, }; -const os_has_fork = switch (builtin.os.tag) { +const os_has_fork = switch (native_os) { .dragonfly, .freebsd, .ios, @@ -41,7 +42,7 @@ const maybe_have_wipe_on_fork = builtin.os.isAtLeast(.linux, .{ .minor = 14, .patch = 0, }) orelse true; -const is_haiku = builtin.os.tag == .haiku; +const is_haiku = native_os == .haiku; const Rng = std.Random.DefaultCsprng; @@ -79,10 +80,10 @@ fn tlsCsprngFill(_: *anyopaque, buffer: []u8) void { if (want_fork_safety and maybe_have_wipe_on_fork or is_haiku) { // Allocate a per-process page, madvise operates with page // granularity. - wipe_mem = os.mmap( + wipe_mem = posix.mmap( null, @sizeOf(Context), - os.PROT.READ | os.PROT.WRITE, + posix.PROT.READ | posix.PROT.WRITE, .{ .TYPE = .PRIVATE, .ANONYMOUS = true }, -1, 0, @@ -115,11 +116,11 @@ fn tlsCsprngFill(_: *anyopaque, buffer: []u8) void { // Qemu user-mode emulation ignores any valid/invalid madvise // hint and returns success. Check if this is the case by // passing bogus parameters, we expect EINVAL as result. - if (os.madvise(wipe_mem.ptr, 0, 0xffffffff)) |_| { + if (posix.madvise(wipe_mem.ptr, 0, 0xffffffff)) |_| { break :wof; } else |_| {} - if (os.madvise(wipe_mem.ptr, wipe_mem.len, os.MADV.WIPEONFORK)) |_| { + if (posix.madvise(wipe_mem.ptr, wipe_mem.len, posix.MADV.WIPEONFORK)) |_| { return initAndFill(buffer); } else |_| {} } @@ -164,7 +165,7 @@ fn fillWithCsprng(buffer: []u8) void { } pub fn defaultRandomSeed(buffer: []u8) void { - os.getrandom(buffer) catch @panic("getrandom() failed to provide entropy"); + posix.getrandom(buffer) catch @panic("getrandom() failed to provide entropy"); } fn initAndFill(buffer: []u8) void { diff --git a/lib/std/crypto/tls/Client.zig b/lib/std/crypto/tls/Client.zig index f07cfe781031..682c1ffe0ebb 100644 --- a/lib/std/crypto/tls/Client.zig +++ b/lib/std/crypto/tls/Client.zig @@ -62,7 +62,7 @@ pub const StreamInterface = struct { /// The `iovecs` parameter is mutable because so that function may to /// mutate the fields in order to handle partial reads from the underlying /// stream layer. - pub fn readv(this: @This(), iovecs: []std.os.iovec) ReadError!usize { + pub fn readv(this: @This(), iovecs: []std.posix.iovec) ReadError!usize { _ = .{ this, iovecs }; @panic("unimplemented"); } @@ -72,7 +72,7 @@ pub const StreamInterface = struct { /// Returns the number of bytes read, which may be less than the buffer /// space provided. A short read does not indicate end-of-stream. - pub fn writev(this: @This(), iovecs: []const std.os.iovec_const) WriteError!usize { + pub fn writev(this: @This(), iovecs: []const std.posix.iovec_const) WriteError!usize { _ = .{ this, iovecs }; @panic("unimplemented"); } @@ -81,7 +81,7 @@ pub const StreamInterface = struct { /// space provided, indicating end-of-stream. /// The `iovecs` parameter is mutable in case this function needs to mutate /// the fields in order to handle partial writes from the underlying layer. - pub fn writevAll(this: @This(), iovecs: []std.os.iovec_const) WriteError!usize { + pub fn writevAll(this: @This(), iovecs: []std.posix.iovec_const) WriteError!usize { // This can be implemented in terms of writev, or specialized if desired. _ = .{ this, iovecs }; @panic("unimplemented"); @@ -215,7 +215,7 @@ pub fn init(stream: anytype, ca_bundle: Certificate.Bundle, host: []const u8) In } ++ int2(@intCast(out_handshake.len + host_len)) ++ out_handshake; { - var iovecs = [_]std.os.iovec_const{ + var iovecs = [_]std.posix.iovec_const{ .{ .iov_base = &plaintext_header, .iov_len = plaintext_header.len, @@ -677,7 +677,7 @@ pub fn init(stream: anytype, ca_bundle: Certificate.Bundle, host: []const u8) In P.AEAD.encrypt(ciphertext, auth_tag, &out_cleartext, ad, nonce, p.client_handshake_key); const both_msgs = client_change_cipher_spec_msg ++ finished_msg; - var both_msgs_vec = [_]std.os.iovec_const{.{ + var both_msgs_vec = [_]std.posix.iovec_const{.{ .iov_base = &both_msgs, .iov_len = both_msgs.len, }}; @@ -755,7 +755,7 @@ pub fn writeAllEnd(c: *Client, stream: anytype, bytes: []const u8, end: bool) !v /// TLS session, or a truncation attack. pub fn writeEnd(c: *Client, stream: anytype, bytes: []const u8, end: bool) !usize { var ciphertext_buf: [tls.max_ciphertext_record_len * 4]u8 = undefined; - var iovecs_buf: [6]std.os.iovec_const = undefined; + var iovecs_buf: [6]std.posix.iovec_const = undefined; var prepared = prepareCiphertextRecord(c, &iovecs_buf, &ciphertext_buf, bytes, .application_data); if (end) { prepared.iovec_end += prepareCiphertextRecord( @@ -796,7 +796,7 @@ pub fn writeEnd(c: *Client, stream: anytype, bytes: []const u8, end: bool) !usiz fn prepareCiphertextRecord( c: *Client, - iovecs: []std.os.iovec_const, + iovecs: []std.posix.iovec_const, ciphertext_buf: []u8, bytes: []const u8, inner_content_type: tls.ContentType, @@ -885,7 +885,7 @@ pub fn eof(c: Client) bool { /// If the number read is less than `len` it means the stream reached the end. /// Reaching the end of the stream is not an error condition. pub fn readAtLeast(c: *Client, stream: anytype, buffer: []u8, len: usize) !usize { - var iovecs = [1]std.os.iovec{.{ .iov_base = buffer.ptr, .iov_len = buffer.len }}; + var iovecs = [1]std.posix.iovec{.{ .iov_base = buffer.ptr, .iov_len = buffer.len }}; return readvAtLeast(c, stream, &iovecs, len); } @@ -908,7 +908,7 @@ pub fn readAll(c: *Client, stream: anytype, buffer: []u8) !usize { /// stream is not an error condition. /// The `iovecs` parameter is mutable because this function needs to mutate the fields in /// order to handle partial reads from the underlying stream layer. -pub fn readv(c: *Client, stream: anytype, iovecs: []std.os.iovec) !usize { +pub fn readv(c: *Client, stream: anytype, iovecs: []std.posix.iovec) !usize { return readvAtLeast(c, stream, iovecs, 1); } @@ -919,7 +919,7 @@ pub fn readv(c: *Client, stream: anytype, iovecs: []std.os.iovec) !usize { /// Reaching the end of the stream is not an error condition. /// The `iovecs` parameter is mutable because this function needs to mutate the fields in /// order to handle partial reads from the underlying stream layer. -pub fn readvAtLeast(c: *Client, stream: anytype, iovecs: []std.os.iovec, len: usize) !usize { +pub fn readvAtLeast(c: *Client, stream: anytype, iovecs: []std.posix.iovec, len: usize) !usize { if (c.eof()) return 0; var off_i: usize = 0; @@ -945,7 +945,7 @@ pub fn readvAtLeast(c: *Client, stream: anytype, iovecs: []std.os.iovec, len: us /// function asserts that `eof()` is `false`. /// See `readv` for a higher level function that has the same, familiar API as /// other read functions, such as `std.fs.File.read`. -pub fn readvAdvanced(c: *Client, stream: anytype, iovecs: []const std.os.iovec) !usize { +pub fn readvAdvanced(c: *Client, stream: anytype, iovecs: []const std.posix.iovec) !usize { var vp: VecPut = .{ .iovecs = iovecs }; // Give away the buffered cleartext we have, if any. @@ -998,7 +998,7 @@ pub fn readvAdvanced(c: *Client, stream: anytype, iovecs: []const std.os.iovec) c.partial_cleartext_idx = 0; const first_iov = c.partially_read_buffer[c.partial_ciphertext_end..]; - var ask_iovecs_buf: [2]std.os.iovec = .{ + var ask_iovecs_buf: [2]std.posix.iovec = .{ .{ .iov_base = first_iov.ptr, .iov_len = first_iov.len, @@ -1352,7 +1352,7 @@ fn SchemeEddsa(comptime scheme: tls.SignatureScheme) type { /// Abstraction for sending multiple byte buffers to a slice of iovecs. const VecPut = struct { - iovecs: []const std.os.iovec, + iovecs: []const std.posix.iovec, idx: usize = 0, off: usize = 0, total: usize = 0, @@ -1413,7 +1413,7 @@ const VecPut = struct { }; /// Limit iovecs to a specific byte size. -fn limitVecs(iovecs: []std.os.iovec, len: usize) []std.os.iovec { +fn limitVecs(iovecs: []std.posix.iovec, len: usize) []std.posix.iovec { var bytes_left: usize = len; for (iovecs, 0..) |*iovec, vec_i| { if (bytes_left <= iovec.iov_len) { diff --git a/lib/std/debug.zig b/lib/std/debug.zig index f190e7fee230..7baa02fe7422 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -3,7 +3,7 @@ const builtin = @import("builtin"); const math = std.math; const mem = std.mem; const io = std.io; -const os = std.os; +const posix = std.posix; const fs = std.fs; const testing = std.testing; const elf = std.elf; @@ -34,7 +34,7 @@ pub const sys_can_stack_trace = switch (builtin.cpu.arch) { // "Non-Emscripten WebAssembly hasn't implemented __builtin_return_address". .wasm32, .wasm64, - => builtin.os.tag == .emscripten, + => native_os == .emscripten, // `@returnAddress()` is unsupported in LLVM 13. .bpfel, @@ -192,8 +192,8 @@ pub fn dumpCurrentStackTrace(start_addr: ?usize) void { } } -pub const have_ucontext = @hasDecl(os.system, "ucontext_t") and - (builtin.os.tag != .linux or switch (builtin.cpu.arch) { +pub const have_ucontext = @hasDecl(posix.system, "ucontext_t") and + (native_os != .linux or switch (builtin.cpu.arch) { .mips, .mipsel, .mips64, .mips64el, .riscv64 => false, else => true, }); @@ -203,9 +203,9 @@ pub const have_ucontext = @hasDecl(os.system, "ucontext_t") and /// use internal pointers within this structure. To make a copy, use `copyContext`. pub const ThreadContext = blk: { if (native_os == .windows) { - break :blk std.os.windows.CONTEXT; + break :blk windows.CONTEXT; } else if (have_ucontext) { - break :blk os.ucontext_t; + break :blk posix.ucontext_t; } else { break :blk void; } @@ -228,9 +228,9 @@ pub fn relocateContext(context: *ThreadContext) void { }; } -pub const have_getcontext = @hasDecl(os.system, "getcontext") and - builtin.os.tag != .openbsd and - (builtin.os.tag != .linux or switch (builtin.cpu.arch) { +pub const have_getcontext = @hasDecl(posix.system, "getcontext") and + native_os != .openbsd and + (native_os != .linux or switch (builtin.cpu.arch) { .x86, .x86_64, => true, @@ -249,7 +249,7 @@ pub inline fn getContext(context: *ThreadContext) bool { return true; } - const result = have_getcontext and os.system.getcontext(context) == 0; + const result = have_getcontext and posix.system.getcontext(context) == 0; if (native_os == .macos) { assert(context.mcsize == @sizeOf(std.c.mcontext_t)); @@ -470,12 +470,12 @@ pub fn panicImpl(trace: ?*const std.builtin.StackTrace, first_trace_addr: ?usize const stderr = io.getStdErr().writer(); if (builtin.single_threaded) { - stderr.print("panic: ", .{}) catch os.abort(); + stderr.print("panic: ", .{}) catch posix.abort(); } else { const current_thread_id = std.Thread.getCurrentId(); - stderr.print("thread {} panic: ", .{current_thread_id}) catch os.abort(); + stderr.print("thread {} panic: ", .{current_thread_id}) catch posix.abort(); } - stderr.print("{s}\n", .{msg}) catch os.abort(); + stderr.print("{s}\n", .{msg}) catch posix.abort(); if (trace) |t| { dumpStackTrace(t.*); } @@ -491,14 +491,14 @@ pub fn panicImpl(trace: ?*const std.builtin.StackTrace, first_trace_addr: ?usize // we're still holding the mutex but that's fine as we're going to // call abort() const stderr = io.getStdErr().writer(); - stderr.print("Panicked during a panic. Aborting.\n", .{}) catch os.abort(); + stderr.print("Panicked during a panic. Aborting.\n", .{}) catch posix.abort(); }, else => { // Panicked while printing "Panicked during a panic." }, }; - os.abort(); + posix.abort(); } /// Must be called only after adding 1 to `panicking`. There are three callsites. @@ -584,7 +584,7 @@ pub const StackIterator = struct { }; } - pub fn initWithContext(first_address: ?usize, debug_info: *DebugInfo, context: *const os.ucontext_t) !StackIterator { + pub fn initWithContext(first_address: ?usize, debug_info: *DebugInfo, context: *const posix.ucontext_t) !StackIterator { // The implementation of DWARF unwinding on aarch64-macos is not complete. However, Apple mandates that // the frame pointer register is always used, so on this platform we can safely use the FP-based unwinder. if (comptime builtin.target.isDarwin() and native_arch == .aarch64) { @@ -668,12 +668,11 @@ pub const StackIterator = struct { const aligned_memory = @as([*]align(mem.page_size) u8, @ptrFromInt(aligned_address))[0..mem.page_size]; if (native_os == .windows) { - const w = os.windows; - var memory_info: w.MEMORY_BASIC_INFORMATION = undefined; + var memory_info: windows.MEMORY_BASIC_INFORMATION = undefined; // The only error this function can throw is ERROR_INVALID_PARAMETER. // supply an address that invalid i'll be thrown. - const rc = w.VirtualQuery(aligned_memory, &memory_info, aligned_memory.len) catch { + const rc = windows.VirtualQuery(aligned_memory, &memory_info, aligned_memory.len) catch { return false; }; @@ -683,17 +682,15 @@ pub const StackIterator = struct { } // Free pages cannot be read, they are unmapped - if (memory_info.State == w.MEM_FREE) { + if (memory_info.State == windows.MEM_FREE) { return false; } return true; - } else if (@hasDecl(os.system, "msync") and native_os != .wasi and native_os != .emscripten) { - os.msync(aligned_memory, os.MSF.ASYNC) catch |err| { + } else if (@hasDecl(posix.system, "msync") and native_os != .wasi and native_os != .emscripten) { + posix.msync(aligned_memory, posix.MSF.ASYNC) catch |err| { switch (err) { - os.MSyncError.UnmappedMemory => { - return false; - }, + error.UnmappedMemory => return false, else => unreachable, } }; @@ -1296,7 +1293,7 @@ pub fn readElfDebugInfo( } var cwd_buf: [fs.MAX_PATH_BYTES]u8 = undefined; - const cwd_path = os.realpath(".", &cwd_buf) catch break :blk; + const cwd_path = posix.realpath(".", &cwd_buf) catch break :blk; // // for (global_debug_directories) |global_directory| { @@ -1651,15 +1648,15 @@ fn mapWholeFile(file: File) ![]align(mem.page_size) const u8 { defer file.close(); const file_len = math.cast(usize, try file.getEndPos()) orelse math.maxInt(usize); - const mapped_mem = try os.mmap( + const mapped_mem = try posix.mmap( null, file_len, - os.PROT.READ, + posix.PROT.READ, .{ .TYPE = .SHARED }, file.handle, 0, ); - errdefer os.munmap(mapped_mem); + errdefer posix.munmap(mapped_mem); return mapped_mem; } @@ -1997,8 +1994,8 @@ pub const DebugInfo = struct { } = .{ .address = address }; const CtxTy = @TypeOf(ctx); - if (os.dl_iterate_phdr(&ctx, error{Found}, struct { - fn callback(info: *os.dl_phdr_info, size: usize, context: *CtxTy) !void { + if (posix.dl_iterate_phdr(&ctx, error{Found}, struct { + fn callback(info: *posix.dl_phdr_info, size: usize, context: *CtxTy) !void { _ = size; if (context.address < info.dlpi_addr) return; const phdrs = info.dlpi_phdr[0..info.dlpi_phnum]; @@ -2036,8 +2033,8 @@ pub const DebugInfo = struct { } = .{ .address = address }; const CtxTy = @TypeOf(ctx); - if (os.dl_iterate_phdr(&ctx, error{Found}, struct { - fn callback(info: *os.dl_phdr_info, size: usize, context: *CtxTy) !void { + if (posix.dl_iterate_phdr(&ctx, error{Found}, struct { + fn callback(info: *posix.dl_phdr_info, size: usize, context: *CtxTy) !void { _ = size; // The base address is too high if (context.address < info.dlpi_addr) @@ -2159,7 +2156,7 @@ pub const ModuleDebugInfo = switch (native_os) { } self.ofiles.deinit(); allocator.free(self.symbols); - os.munmap(self.mapped_memory); + posix.munmap(self.mapped_memory); } fn loadOFile(self: *@This(), allocator: mem.Allocator, o_file_path: []const u8) !*OFileInfo { @@ -2433,8 +2430,8 @@ pub const ModuleDebugInfo = switch (native_os) { pub fn deinit(self: *@This(), allocator: mem.Allocator) void { self.dwarf.deinit(allocator); - os.munmap(self.mapped_memory); - if (self.external_mapped_memory) |m| os.munmap(m); + posix.munmap(self.mapped_memory); + if (self.external_mapped_memory) |m| posix.munmap(m); } pub fn getSymbolAtAddress(self: *@This(), allocator: mem.Allocator, address: usize) !SymbolInfo { @@ -2514,7 +2511,7 @@ pub const have_segfault_handling_support = switch (native_os) { .windows, => true, - .freebsd, .openbsd => @hasDecl(os.system, "ucontext_t"), + .freebsd, .openbsd => @hasDecl(std.c, "ucontext_t"), else => false, }; @@ -2529,11 +2526,11 @@ pub fn maybeEnableSegfaultHandler() void { var windows_segfault_handle: ?windows.HANDLE = null; -pub fn updateSegfaultHandler(act: ?*const os.Sigaction) error{OperationNotSupported}!void { - try os.sigaction(os.SIG.SEGV, act, null); - try os.sigaction(os.SIG.ILL, act, null); - try os.sigaction(os.SIG.BUS, act, null); - try os.sigaction(os.SIG.FPE, act, null); +pub fn updateSegfaultHandler(act: ?*const posix.Sigaction) error{OperationNotSupported}!void { + try posix.sigaction(posix.SIG.SEGV, act, null); + try posix.sigaction(posix.SIG.ILL, act, null); + try posix.sigaction(posix.SIG.BUS, act, null); + try posix.sigaction(posix.SIG.FPE, act, null); } /// Attaches a global SIGSEGV handler which calls `@panic("segmentation fault");` @@ -2545,10 +2542,10 @@ pub fn attachSegfaultHandler() void { windows_segfault_handle = windows.kernel32.AddVectoredExceptionHandler(0, handleSegfaultWindows); return; } - var act = os.Sigaction{ + var act = posix.Sigaction{ .handler = .{ .sigaction = handleSegfaultPosix }, - .mask = os.empty_sigset, - .flags = (os.SA.SIGINFO | os.SA.RESTART | os.SA.RESETHAND), + .mask = posix.empty_sigset, + .flags = (posix.SA.SIGINFO | posix.SA.RESTART | posix.SA.RESETHAND), }; updateSegfaultHandler(&act) catch { @@ -2564,16 +2561,16 @@ fn resetSegfaultHandler() void { } return; } - var act = os.Sigaction{ - .handler = .{ .handler = os.SIG.DFL }, - .mask = os.empty_sigset, + var act = posix.Sigaction{ + .handler = .{ .handler = posix.SIG.DFL }, + .mask = posix.empty_sigset, .flags = 0, }; // To avoid a double-panic, do nothing if an error happens here. updateSegfaultHandler(&act) catch {}; } -fn handleSegfaultPosix(sig: i32, info: *const os.siginfo_t, ctx_ptr: ?*const anyopaque) callconv(.C) noreturn { +fn handleSegfaultPosix(sig: i32, info: *const posix.siginfo_t, ctx_ptr: ?*const anyopaque) callconv(.C) noreturn { // Reset to the default handler so that if a segfault happens in this handler it will crash // the process. Also when this handler returns, the original instruction will be repeated // and the resulting segfault will crash the process rather than continually dump stack traces. @@ -2612,13 +2609,13 @@ fn handleSegfaultPosix(sig: i32, info: *const os.siginfo_t, ctx_ptr: ?*const any // We cannot allow the signal handler to return because when it runs the original instruction // again, the memory may be mapped and undefined behavior would occur rather than repeating // the segfault. So we simply abort here. - os.abort(); + posix.abort(); } fn dumpSegfaultInfoPosix(sig: i32, code: i32, addr: usize, ctx_ptr: ?*const anyopaque) void { const stderr = io.getStdErr().writer(); _ = switch (sig) { - os.SIG.SEGV => if (native_arch == .x86_64 and native_os == .linux and code == 128) // SI_KERNEL + posix.SIG.SEGV => if (native_arch == .x86_64 and native_os == .linux and code == 128) // SI_KERNEL // x86_64 doesn't have a full 64-bit virtual address space. // Addresses outside of that address space are non-canonical // and the CPU won't provide the faulting address to us. @@ -2629,11 +2626,11 @@ fn dumpSegfaultInfoPosix(sig: i32, code: i32, addr: usize, ctx_ptr: ?*const anyo stderr.print("General protection exception (no address available)\n", .{}) else stderr.print("Segmentation fault at address 0x{x}\n", .{addr}), - os.SIG.ILL => stderr.print("Illegal instruction at address 0x{x}\n", .{addr}), - os.SIG.BUS => stderr.print("Bus error at address 0x{x}\n", .{addr}), - os.SIG.FPE => stderr.print("Arithmetic exception at address 0x{x}\n", .{addr}), + posix.SIG.ILL => stderr.print("Illegal instruction at address 0x{x}\n", .{addr}), + posix.SIG.BUS => stderr.print("Bus error at address 0x{x}\n", .{addr}), + posix.SIG.FPE => stderr.print("Arithmetic exception at address 0x{x}\n", .{addr}), else => unreachable, - } catch os.abort(); + } catch posix.abort(); switch (native_arch) { .x86, @@ -2641,7 +2638,7 @@ fn dumpSegfaultInfoPosix(sig: i32, code: i32, addr: usize, ctx_ptr: ?*const anyo .arm, .aarch64, => { - const ctx: *const os.ucontext_t = @ptrCast(@alignCast(ctx_ptr)); + const ctx: *const posix.ucontext_t = @ptrCast(@alignCast(ctx_ptr)); dumpStackTraceFromBase(ctx); }, else => {}, @@ -2684,7 +2681,7 @@ fn handleSegfaultWindowsExtra( dumpSegfaultInfoWindows(info, msg, label); }, }; - os.abort(); + posix.abort(); } else { switch (msg) { 0 => panicImpl(null, exception_address, "{s}", label.?), @@ -2707,7 +2704,7 @@ fn dumpSegfaultInfoWindows(info: *windows.EXCEPTION_POINTERS, msg: u8, label: ?[ 1 => stderr.print("Segmentation fault at address 0x{x}\n", .{info.ExceptionRecord.ExceptionInformation[1]}), 2 => stderr.print("Illegal instruction at address 0x{x}\n", .{info.ContextRecord.getRegs().ip}), else => unreachable, - } catch os.abort(); + } catch posix.abort(); dumpStackTraceFromBase(info.ContextRecord); } @@ -2722,9 +2719,9 @@ pub fn dumpStackPointerAddr(prefix: []const u8) void { test "manage resources correctly" { if (builtin.strip_debug_info) return error.SkipZigTest; - if (builtin.os.tag == .wasi) return error.SkipZigTest; + if (native_os == .wasi) return error.SkipZigTest; - if (builtin.os.tag == .windows) { + if (native_os == .windows) { // https://github.com/ziglang/zig/issues/13963 return error.SkipZigTest; } diff --git a/lib/std/dwarf.zig b/lib/std/dwarf.zig index b59f53569182..a0f5a7f0a85f 100644 --- a/lib/std/dwarf.zig +++ b/lib/std/dwarf.zig @@ -2188,12 +2188,12 @@ pub fn openDwarfDebugInfo(di: *DwarfInfo, allocator: mem.Allocator) !void { /// This function is to make it handy to comment out the return and make it /// into a crash when working on this file. fn badDwarf() error{InvalidDebugInfo} { - //std.os.abort(); // can be handy to uncomment when working on this file + //if (true) @panic("badDwarf"); // can be handy to uncomment when working on this file return error.InvalidDebugInfo; } fn missingDwarf() error{MissingDebugInfo} { - //std.os.abort(); // can be handy to uncomment when working on this file + //if (true) @panic("missingDwarf"); // can be handy to uncomment when working on this file return error.MissingDebugInfo; } diff --git a/lib/std/dwarf/abi.zig b/lib/std/dwarf/abi.zig index 7130341e8dac..75ff3d1e9e0e 100644 --- a/lib/std/dwarf/abi.zig +++ b/lib/std/dwarf/abi.zig @@ -1,7 +1,8 @@ const builtin = @import("builtin"); const std = @import("../std.zig"); -const os = std.os; const mem = std.mem; +const native_os = builtin.os.tag; +const posix = std.posix; pub fn supportsUnwinding(target: std.Target) bool { return switch (target.cpu.arch) { @@ -138,7 +139,7 @@ pub fn regBytes( reg_number: u8, reg_context: ?RegisterContext, ) AbiError!RegBytesReturnType(@TypeOf(thread_context_ptr)) { - if (builtin.os.tag == .windows) { + if (native_os == .windows) { return switch (builtin.cpu.arch) { .x86 => switch (reg_number) { 0 => mem.asBytes(&thread_context_ptr.Eax), @@ -193,61 +194,61 @@ pub fn regBytes( const ucontext_ptr = thread_context_ptr; return switch (builtin.cpu.arch) { - .x86 => switch (builtin.os.tag) { + .x86 => switch (native_os) { .linux, .netbsd, .solaris, .illumos => switch (reg_number) { - 0 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.EAX]), - 1 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.ECX]), - 2 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.EDX]), - 3 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.EBX]), + 0 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.EAX]), + 1 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.ECX]), + 2 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.EDX]), + 3 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.EBX]), 4...5 => if (reg_context) |r| bytes: { if (reg_number == 4) { break :bytes if (r.eh_frame and r.is_macho) - mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.EBP]) + mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.EBP]) else - mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.ESP]); + mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.ESP]); } else { break :bytes if (r.eh_frame and r.is_macho) - mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.ESP]) + mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.ESP]) else - mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.EBP]); + mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.EBP]); } } else error.RegisterContextRequired, - 6 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.ESI]), - 7 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.EDI]), - 8 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.EIP]), - 9 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.EFL]), - 10 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.CS]), - 11 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.SS]), - 12 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.DS]), - 13 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.ES]), - 14 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.FS]), - 15 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.GS]), + 6 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.ESI]), + 7 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.EDI]), + 8 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.EIP]), + 9 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.EFL]), + 10 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.CS]), + 11 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.SS]), + 12 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.DS]), + 13 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.ES]), + 14 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.FS]), + 15 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.GS]), 16...23 => error.InvalidRegister, // TODO: Support loading ST0-ST7 from mcontext.fpregs 32...39 => error.InvalidRegister, // TODO: Support loading XMM0-XMM7 from mcontext.fpregs else => error.InvalidRegister, }, else => error.UnimplementedOs, }, - .x86_64 => switch (builtin.os.tag) { + .x86_64 => switch (native_os) { .linux, .solaris, .illumos => switch (reg_number) { - 0 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.RAX]), - 1 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.RDX]), - 2 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.RCX]), - 3 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.RBX]), - 4 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.RSI]), - 5 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.RDI]), - 6 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.RBP]), - 7 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.RSP]), - 8 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.R8]), - 9 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.R9]), - 10 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.R10]), - 11 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.R11]), - 12 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.R12]), - 13 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.R13]), - 14 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.R14]), - 15 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.R15]), - 16 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.RIP]), - 17...32 => |i| if (builtin.os.tag.isSolarish()) + 0 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RAX]), + 1 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RDX]), + 2 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RCX]), + 3 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RBX]), + 4 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RSI]), + 5 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RDI]), + 6 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RBP]), + 7 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RSP]), + 8 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R8]), + 9 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R9]), + 10 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R10]), + 11 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R11]), + 12 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R12]), + 13 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R13]), + 14 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R14]), + 15 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R15]), + 16 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RIP]), + 17...32 => |i| if (native_os.isSolarish()) mem.asBytes(&ucontext_ptr.mcontext.fpregs.chip_state.xmm[i - 17]) else mem.asBytes(&ucontext_ptr.mcontext.fpregs.xmm[i - 17]), @@ -317,7 +318,7 @@ pub fn regBytes( }, else => error.UnimplementedOs, }, - .arm => switch (builtin.os.tag) { + .arm => switch (native_os) { .linux => switch (reg_number) { 0 => mem.asBytes(&ucontext_ptr.mcontext.arm_r0), 1 => mem.asBytes(&ucontext_ptr.mcontext.arm_r1), @@ -340,7 +341,7 @@ pub fn regBytes( }, else => error.UnimplementedOs, }, - .aarch64 => switch (builtin.os.tag) { + .aarch64 => switch (native_os) { .macos, .ios => switch (reg_number) { 0...28 => mem.asBytes(&ucontext_ptr.mcontext.ss.regs[reg_number]), 29 => mem.asBytes(&ucontext_ptr.mcontext.ss.fp), diff --git a/lib/std/dynamic_library.zig b/lib/std/dynamic_library.zig index 7a31c39f4457..a6ef8ac6c73f 100644 --- a/lib/std/dynamic_library.zig +++ b/lib/std/dynamic_library.zig @@ -1,16 +1,16 @@ const std = @import("std.zig"); const builtin = @import("builtin"); const mem = std.mem; -const os = std.os; const testing = std.testing; const elf = std.elf; const windows = std.os.windows; -const system = std.os.system; +const native_os = builtin.os.tag; +const posix = std.posix; /// Cross-platform dynamic library loading and symbol lookup. /// Platform-specific functionality is available through the `inner` field. pub const DynLib = struct { - const InnerType = switch (builtin.os.tag) { + const InnerType = switch (native_os) { .linux => if (!builtin.link_libc or builtin.abi == .musl and builtin.link_mode == .static) ElfDynLib else @@ -125,7 +125,7 @@ pub fn linkmap_iterator(phdrs: []elf.Phdr) error{InvalidExe}!LinkMap.Iterator { pub const ElfDynLib = struct { strings: [*:0]u8, syms: [*]elf.Sym, - hashtab: [*]os.Elf_Symndx, + hashtab: [*]posix.Elf_Symndx, versym: ?[*]u16, verdef: ?*elf.Verdef, memory: []align(mem.page_size) u8, @@ -138,27 +138,27 @@ pub const ElfDynLib = struct { ElfStringSectionNotFound, ElfSymSectionNotFound, ElfHashTableNotFound, - } || os.OpenError || os.MMapError; + } || posix.OpenError || posix.MMapError; /// Trusts the file. Malicious file will be able to execute arbitrary code. pub fn open(path: []const u8) Error!ElfDynLib { - const fd = try os.open(path, .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0); - defer os.close(fd); + const fd = try posix.open(path, .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0); + defer posix.close(fd); - const stat = try os.fstat(fd); + const stat = try posix.fstat(fd); const size = std.math.cast(usize, stat.size) orelse return error.FileTooBig; // This one is to read the ELF info. We do more mmapping later // corresponding to the actual LOAD sections. - const file_bytes = try os.mmap( + const file_bytes = try posix.mmap( null, mem.alignForward(usize, size, mem.page_size), - os.PROT.READ, + posix.PROT.READ, .{ .TYPE = .PRIVATE }, fd, 0, ); - defer os.munmap(file_bytes); + defer posix.munmap(file_bytes); const eh = @as(*elf.Ehdr, @ptrCast(file_bytes.ptr)); if (!mem.eql(u8, eh.e_ident[0..4], elf.MAGIC)) return error.NotElfFile; @@ -188,15 +188,15 @@ pub const ElfDynLib = struct { const dynv = maybe_dynv orelse return error.MissingDynamicLinkingInformation; // Reserve the entire range (with no permissions) so that we can do MAP.FIXED below. - const all_loaded_mem = try os.mmap( + const all_loaded_mem = try posix.mmap( null, virt_addr_end, - os.PROT.NONE, + posix.PROT.NONE, .{ .TYPE = .PRIVATE, .ANONYMOUS = true }, -1, 0, ); - errdefer os.munmap(all_loaded_mem); + errdefer posix.munmap(all_loaded_mem); const base = @intFromPtr(all_loaded_mem.ptr); @@ -220,7 +220,7 @@ pub const ElfDynLib = struct { const prot = elfToMmapProt(ph.p_flags); if ((ph.p_flags & elf.PF_W) == 0) { // If it does not need write access, it can be mapped from the fd. - _ = try os.mmap( + _ = try posix.mmap( ptr, extended_memsz, prot, @@ -229,7 +229,7 @@ pub const ElfDynLib = struct { ph.p_offset - extra_bytes, ); } else { - const sect_mem = try os.mmap( + const sect_mem = try posix.mmap( ptr, extended_memsz, prot, @@ -247,7 +247,7 @@ pub const ElfDynLib = struct { var maybe_strings: ?[*:0]u8 = null; var maybe_syms: ?[*]elf.Sym = null; - var maybe_hashtab: ?[*]os.Elf_Symndx = null; + var maybe_hashtab: ?[*]posix.Elf_Symndx = null; var maybe_versym: ?[*]u16 = null; var maybe_verdef: ?*elf.Verdef = null; @@ -258,7 +258,7 @@ pub const ElfDynLib = struct { switch (dynv[i]) { elf.DT_STRTAB => maybe_strings = @as([*:0]u8, @ptrFromInt(p)), elf.DT_SYMTAB => maybe_syms = @as([*]elf.Sym, @ptrFromInt(p)), - elf.DT_HASH => maybe_hashtab = @as([*]os.Elf_Symndx, @ptrFromInt(p)), + elf.DT_HASH => maybe_hashtab = @as([*]posix.Elf_Symndx, @ptrFromInt(p)), elf.DT_VERSYM => maybe_versym = @as([*]u16, @ptrFromInt(p)), elf.DT_VERDEF => maybe_verdef = @as(*elf.Verdef, @ptrFromInt(p)), else => {}, @@ -283,7 +283,7 @@ pub const ElfDynLib = struct { /// Trusts the file pub fn close(self: *ElfDynLib) void { - os.munmap(self.memory); + posix.munmap(self.memory); self.* = undefined; } @@ -320,10 +320,10 @@ pub const ElfDynLib = struct { } fn elfToMmapProt(elf_prot: u64) u32 { - var result: u32 = os.PROT.NONE; - if ((elf_prot & elf.PF_R) != 0) result |= os.PROT.READ; - if ((elf_prot & elf.PF_W) != 0) result |= os.PROT.WRITE; - if ((elf_prot & elf.PF_X) != 0) result |= os.PROT.EXEC; + var result: u32 = posix.PROT.NONE; + if ((elf_prot & elf.PF_R) != 0) result |= posix.PROT.READ; + if ((elf_prot & elf.PF_W) != 0) result |= posix.PROT.WRITE; + if ((elf_prot & elf.PF_X) != 0) result |= posix.PROT.EXEC; return result; } }; @@ -343,7 +343,7 @@ fn checkver(def_arg: *elf.Verdef, vsym_arg: i32, vername: []const u8, strings: [ } test "ElfDynLib" { - if (builtin.os.tag != .linux) { + if (native_os != .linux) { return error.SkipZigTest; } @@ -419,20 +419,20 @@ pub const DlDynLib = struct { handle: *anyopaque, pub fn open(path: []const u8) Error!DlDynLib { - const path_c = try os.toPosixPath(path); + const path_c = try posix.toPosixPath(path); return openZ(&path_c); } pub fn openZ(path_c: [*:0]const u8) Error!DlDynLib { return .{ - .handle = system.dlopen(path_c, system.RTLD.LAZY) orelse { + .handle = std.c.dlopen(path_c, std.c.RTLD.LAZY) orelse { return error.FileNotFound; }, }; } pub fn close(self: *DlDynLib) void { - switch (std.os.errno(system.dlclose(self.handle))) { + switch (posix.errno(std.c.dlclose(self.handle))) { .SUCCESS => return, else => unreachable, } @@ -442,7 +442,7 @@ pub const DlDynLib = struct { pub fn lookup(self: *DlDynLib, comptime T: type, name: [:0]const u8) ?T { // dlsym (and other dl-functions) secretly take shadow parameter - return address on stack // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66826 - if (@call(.never_tail, system.dlsym, .{ self.handle, name.ptr })) |symbol| { + if (@call(.never_tail, std.c.dlsym, .{ self.handle, name.ptr })) |symbol| { return @as(T, @ptrCast(@alignCast(symbol))); } else { return null; @@ -453,12 +453,12 @@ pub const DlDynLib = struct { /// Returns human readable string describing most recent error than occurred from `lookup` /// or `null` if no error has occurred since initialization or when `getError` was last called. pub fn getError() ?[:0]const u8 { - return mem.span(system.dlerror()); + return mem.span(std.c.dlerror()); } }; test "dynamic_library" { - const libname = switch (builtin.os.tag) { + const libname = switch (native_os) { .linux, .freebsd, .openbsd, .solaris, .illumos => "invalid_so.so", .windows => "invalid_dll.dll", .macos, .tvos, .watchos, .ios => "invalid_dylib.dylib", diff --git a/lib/std/fs.zig b/lib/std/fs.zig index 5c6e8c9c3af0..06b9cf3e122d 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -3,21 +3,23 @@ const std = @import("std.zig"); const builtin = @import("builtin"); const root = @import("root"); -const os = std.os; const mem = std.mem; const base64 = std.base64; const crypto = std.crypto; const Allocator = std.mem.Allocator; const assert = std.debug.assert; +const native_os = builtin.os.tag; +const posix = std.posix; +const windows = std.os.windows; -const is_darwin = builtin.os.tag.isDarwin(); +const is_darwin = native_os.isDarwin(); pub const AtomicFile = @import("fs/AtomicFile.zig"); pub const Dir = @import("fs/Dir.zig"); pub const File = @import("fs/File.zig"); pub const path = @import("fs/path.zig"); -pub const has_executable_bit = switch (builtin.os.tag) { +pub const has_executable_bit = switch (native_os) { .windows, .wasi => false, else => true, }; @@ -26,36 +28,43 @@ pub const wasi = @import("fs/wasi.zig"); // TODO audit these APIs with respect to Dir and absolute paths -pub const realpath = os.realpath; -pub const realpathZ = os.realpathZ; -pub const realpathW = os.realpathW; +pub const realpath = posix.realpath; +pub const realpathZ = posix.realpathZ; +pub const realpathW = posix.realpathW; pub const getAppDataDir = @import("fs/get_app_data_dir.zig").getAppDataDir; pub const GetAppDataDirError = @import("fs/get_app_data_dir.zig").GetAppDataDirError; -/// This represents the maximum size of a `[]u8` file path that the -/// operating system will accept. Paths, including those returned from file -/// system operations, may be longer than this length, but such paths cannot -/// be successfully passed back in other file system operations. However, -/// all path components returned by file system operations are assumed to -/// fit into a `u8` array of this length. +/// Deprecated: use `max_path_bytes`. +pub const MAX_PATH_BYTES = max_path_bytes; + +/// The maximum length of a file path that the operating system will accept. +/// +/// Paths, including those returned from file system operations, may be longer +/// than this length, but such paths cannot be successfully passed back in +/// other file system operations. However, all path components returned by file +/// system operations are assumed to fit into a `u8` array of this length. +/// /// The byte count includes room for a null sentinel byte. -/// On Windows, `[]u8` file paths are encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). -/// On WASI, `[]u8` file paths are encoded as valid UTF-8. -/// On other platforms, `[]u8` file paths are opaque sequences of bytes with no particular encoding. -pub const MAX_PATH_BYTES = switch (builtin.os.tag) { - .linux, .macos, .ios, .freebsd, .openbsd, .netbsd, .dragonfly, .haiku, .solaris, .illumos, .plan9, .emscripten => os.PATH_MAX, +/// +/// * On Windows, `[]u8` file paths are encoded as +/// [WTF-8](https://simonsapin.github.io/wtf-8/). +/// * On WASI, `[]u8` file paths are encoded as valid UTF-8. +/// * On other platforms, `[]u8` file paths are opaque sequences of bytes with +/// no particular encoding. +pub const max_path_bytes = switch (native_os) { + .linux, .macos, .ios, .freebsd, .openbsd, .netbsd, .dragonfly, .haiku, .solaris, .illumos, .plan9, .emscripten => posix.PATH_MAX, // Each WTF-16LE code unit may be expanded to 3 WTF-8 bytes. // If it would require 4 WTF-8 bytes, then there would be a surrogate // pair in the WTF-16LE, and we (over)account 3 bytes for it that way. // +1 for the null byte at the end, which can be encoded in 1 byte. - .windows => os.windows.PATH_MAX_WIDE * 3 + 1, + .windows => windows.PATH_MAX_WIDE * 3 + 1, // TODO work out what a reasonable value we should use here .wasi => 4096, else => if (@hasDecl(root, "os") and @hasDecl(root.os, "PATH_MAX")) root.os.PATH_MAX else - @compileError("PATH_MAX not implemented for " ++ @tagName(builtin.os.tag)), + @compileError("PATH_MAX not implemented for " ++ @tagName(native_os)), }; /// This represents the maximum size of a `[]u8` file name component that @@ -66,22 +75,22 @@ pub const MAX_PATH_BYTES = switch (builtin.os.tag) { /// On Windows, `[]u8` file name components are encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). /// On WASI, file name components are encoded as valid UTF-8. /// On other platforms, `[]u8` components are an opaque sequence of bytes with no particular encoding. -pub const MAX_NAME_BYTES = switch (builtin.os.tag) { - .linux, .macos, .ios, .freebsd, .openbsd, .netbsd, .dragonfly, .solaris, .illumos => os.NAME_MAX, +pub const MAX_NAME_BYTES = switch (native_os) { + .linux, .macos, .ios, .freebsd, .openbsd, .netbsd, .dragonfly, .solaris, .illumos => posix.NAME_MAX, // Haiku's NAME_MAX includes the null terminator, so subtract one. - .haiku => os.NAME_MAX - 1, + .haiku => posix.NAME_MAX - 1, // Each WTF-16LE character may be expanded to 3 WTF-8 bytes. // If it would require 4 WTF-8 bytes, then there would be a surrogate // pair in the WTF-16LE, and we (over)account 3 bytes for it that way. - .windows => os.windows.NAME_MAX * 3, + .windows => windows.NAME_MAX * 3, // For WASI, the MAX_NAME will depend on the host OS, so it needs to be // as large as the largest MAX_NAME_BYTES (Windows) in order to work on any host OS. // TODO determine if this is a reasonable approach - .wasi => os.windows.NAME_MAX * 3, + .wasi => windows.NAME_MAX * 3, else => if (@hasDecl(root, "os") and @hasDecl(root.os, "NAME_MAX")) root.os.NAME_MAX else - @compileError("NAME_MAX not implemented for " ++ @tagName(builtin.os.tag)), + @compileError("NAME_MAX not implemented for " ++ @tagName(native_os)), }; pub const base64_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".*; @@ -167,19 +176,19 @@ pub fn copyFileAbsolute( /// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding. pub fn makeDirAbsolute(absolute_path: []const u8) !void { assert(path.isAbsolute(absolute_path)); - return os.mkdir(absolute_path, Dir.default_mode); + return posix.mkdir(absolute_path, Dir.default_mode); } /// Same as `makeDirAbsolute` except the parameter is null-terminated. pub fn makeDirAbsoluteZ(absolute_path_z: [*:0]const u8) !void { assert(path.isAbsoluteZ(absolute_path_z)); - return os.mkdirZ(absolute_path_z, Dir.default_mode); + return posix.mkdirZ(absolute_path_z, Dir.default_mode); } /// Same as `makeDirAbsolute` except the parameter is a null-terminated WTF-16 LE-encoded string. pub fn makeDirAbsoluteW(absolute_path_w: [*:0]const u16) !void { assert(path.isAbsoluteWindowsW(absolute_path_w)); - return os.mkdirW(absolute_path_w, Dir.default_mode); + return posix.mkdirW(absolute_path_w, Dir.default_mode); } /// Same as `Dir.deleteDir` except the path is absolute. @@ -188,19 +197,19 @@ pub fn makeDirAbsoluteW(absolute_path_w: [*:0]const u16) !void { /// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding. pub fn deleteDirAbsolute(dir_path: []const u8) !void { assert(path.isAbsolute(dir_path)); - return os.rmdir(dir_path); + return posix.rmdir(dir_path); } /// Same as `deleteDirAbsolute` except the path parameter is null-terminated. pub fn deleteDirAbsoluteZ(dir_path: [*:0]const u8) !void { assert(path.isAbsoluteZ(dir_path)); - return os.rmdirZ(dir_path); + return posix.rmdirZ(dir_path); } /// Same as `deleteDirAbsolute` except the path parameter is WTF-16 and target OS is assumed Windows. pub fn deleteDirAbsoluteW(dir_path: [*:0]const u16) !void { assert(path.isAbsoluteWindowsW(dir_path)); - return os.rmdirW(dir_path); + return posix.rmdirW(dir_path); } /// Same as `Dir.rename` except the paths are absolute. @@ -210,49 +219,49 @@ pub fn deleteDirAbsoluteW(dir_path: [*:0]const u16) !void { pub fn renameAbsolute(old_path: []const u8, new_path: []const u8) !void { assert(path.isAbsolute(old_path)); assert(path.isAbsolute(new_path)); - return os.rename(old_path, new_path); + return posix.rename(old_path, new_path); } /// Same as `renameAbsolute` except the path parameters are null-terminated. pub fn renameAbsoluteZ(old_path: [*:0]const u8, new_path: [*:0]const u8) !void { assert(path.isAbsoluteZ(old_path)); assert(path.isAbsoluteZ(new_path)); - return os.renameZ(old_path, new_path); + return posix.renameZ(old_path, new_path); } /// Same as `renameAbsolute` except the path parameters are WTF-16 and target OS is assumed Windows. pub fn renameAbsoluteW(old_path: [*:0]const u16, new_path: [*:0]const u16) !void { assert(path.isAbsoluteWindowsW(old_path)); assert(path.isAbsoluteWindowsW(new_path)); - return os.renameW(old_path, new_path); + return posix.renameW(old_path, new_path); } /// Same as `Dir.rename`, except `new_sub_path` is relative to `new_dir` pub fn rename(old_dir: Dir, old_sub_path: []const u8, new_dir: Dir, new_sub_path: []const u8) !void { - return os.renameat(old_dir.fd, old_sub_path, new_dir.fd, new_sub_path); + return posix.renameat(old_dir.fd, old_sub_path, new_dir.fd, new_sub_path); } /// Same as `rename` except the parameters are null-terminated. pub fn renameZ(old_dir: Dir, old_sub_path_z: [*:0]const u8, new_dir: Dir, new_sub_path_z: [*:0]const u8) !void { - return os.renameatZ(old_dir.fd, old_sub_path_z, new_dir.fd, new_sub_path_z); + return posix.renameatZ(old_dir.fd, old_sub_path_z, new_dir.fd, new_sub_path_z); } /// Same as `rename` except the parameters are WTF16LE, NT prefixed. /// This function is Windows-only. pub fn renameW(old_dir: Dir, old_sub_path_w: []const u16, new_dir: Dir, new_sub_path_w: []const u16) !void { - return os.renameatW(old_dir.fd, old_sub_path_w, new_dir.fd, new_sub_path_w); + return posix.renameatW(old_dir.fd, old_sub_path_w, new_dir.fd, new_sub_path_w); } /// Returns a handle to the current working directory. It is not opened with iteration capability. /// Closing the returned `Dir` is checked illegal behavior. Iterating over the result is illegal behavior. /// On POSIX targets, this function is comptime-callable. pub fn cwd() Dir { - if (builtin.os.tag == .windows) { - return .{ .fd = os.windows.peb().ProcessParameters.CurrentDirectory.Handle }; - } else if (builtin.os.tag == .wasi) { + if (native_os == .windows) { + return .{ .fd = windows.peb().ProcessParameters.CurrentDirectory.Handle }; + } else if (native_os == .wasi) { return .{ .fd = std.options.wasiCwd() }; } else { - return .{ .fd = os.AT.FDCWD }; + return .{ .fd = posix.AT.FDCWD }; } } @@ -412,20 +421,20 @@ pub fn deleteTreeAbsolute(absolute_path: []const u8) !void { /// On other platforms, `pathname` is an opaque sequence of bytes with no particular encoding. pub fn readLinkAbsolute(pathname: []const u8, buffer: *[MAX_PATH_BYTES]u8) ![]u8 { assert(path.isAbsolute(pathname)); - return os.readlink(pathname, buffer); + return posix.readlink(pathname, buffer); } /// Windows-only. Same as `readlinkW`, except the path parameter is null-terminated, WTF16 /// encoded. pub fn readlinkAbsoluteW(pathname_w: [*:0]const u16, buffer: *[MAX_PATH_BYTES]u8) ![]u8 { assert(path.isAbsoluteWindowsW(pathname_w)); - return os.readlinkW(pathname_w, buffer); + return posix.readlinkW(pathname_w, buffer); } /// Same as `readLink`, except the path parameter is null-terminated. pub fn readLinkAbsoluteZ(pathname_c: [*:0]const u8, buffer: *[MAX_PATH_BYTES]u8) ![]u8 { assert(path.isAbsoluteZ(pathname_c)); - return os.readlinkZ(pathname_c, buffer); + return posix.readlinkZ(pathname_c, buffer); } /// Creates a symbolic link named `sym_link_path` which contains the string `target_path`. @@ -443,12 +452,12 @@ pub fn symLinkAbsolute( ) !void { assert(path.isAbsolute(target_path)); assert(path.isAbsolute(sym_link_path)); - if (builtin.os.tag == .windows) { - const target_path_w = try os.windows.sliceToPrefixedFileW(null, target_path); - const sym_link_path_w = try os.windows.sliceToPrefixedFileW(null, sym_link_path); - return os.windows.CreateSymbolicLink(null, sym_link_path_w.span(), target_path_w.span(), flags.is_directory); + if (native_os == .windows) { + const target_path_w = try windows.sliceToPrefixedFileW(null, target_path); + const sym_link_path_w = try windows.sliceToPrefixedFileW(null, sym_link_path); + return windows.CreateSymbolicLink(null, sym_link_path_w.span(), target_path_w.span(), flags.is_directory); } - return os.symlink(target_path, sym_link_path); + return posix.symlink(target_path, sym_link_path); } /// Windows-only. Same as `symLinkAbsolute` except the parameters are null-terminated, WTF16 LE encoded. @@ -462,7 +471,7 @@ pub fn symLinkAbsoluteW( ) !void { assert(path.isAbsoluteWindowsWTF16(target_path_w)); assert(path.isAbsoluteWindowsWTF16(sym_link_path_w)); - return os.windows.CreateSymbolicLink(null, sym_link_path_w, target_path_w, flags.is_directory); + return windows.CreateSymbolicLink(null, sym_link_path_w, target_path_w, flags.is_directory); } /// Same as `symLinkAbsolute` except the parameters are null-terminated pointers. @@ -474,27 +483,27 @@ pub fn symLinkAbsoluteZ( ) !void { assert(path.isAbsoluteZ(target_path_c)); assert(path.isAbsoluteZ(sym_link_path_c)); - if (builtin.os.tag == .windows) { - const target_path_w = try os.windows.cStrToPrefixedFileW(null, target_path_c); - const sym_link_path_w = try os.windows.cStrToPrefixedFileW(null, sym_link_path_c); - return os.windows.CreateSymbolicLink(null, sym_link_path_w.span(), target_path_w.span(), flags.is_directory); + if (native_os == .windows) { + const target_path_w = try windows.cStrToPrefixedFileW(null, target_path_c); + const sym_link_path_w = try windows.cStrToPrefixedFileW(null, sym_link_path_c); + return windows.CreateSymbolicLink(null, sym_link_path_w.span(), target_path_w.span(), flags.is_directory); } - return os.symlinkZ(target_path_c, sym_link_path_c); + return posix.symlinkZ(target_path_c, sym_link_path_c); } -pub const OpenSelfExeError = os.OpenError || SelfExePathError || os.FlockError; +pub const OpenSelfExeError = posix.OpenError || SelfExePathError || posix.FlockError; pub fn openSelfExe(flags: File.OpenFlags) OpenSelfExeError!File { - if (builtin.os.tag == .linux) { + if (native_os == .linux) { return openFileAbsoluteZ("/proc/self/exe", flags); } - if (builtin.os.tag == .windows) { + if (native_os == .windows) { // If ImagePathName is a symlink, then it will contain the path of the symlink, // not the path that the symlink points to. However, because we are opening // the file, we can let the openFileW call follow the symlink for us. - const image_path_unicode_string = &os.windows.peb().ProcessParameters.ImagePathName; + const image_path_unicode_string = &windows.peb().ProcessParameters.ImagePathName; const image_path_name = image_path_unicode_string.Buffer.?[0 .. image_path_unicode_string.Length / 2 :0]; - const prefixed_path_w = try os.windows.wToPrefixedFileW(null, image_path_name); + const prefixed_path_w = try windows.wToPrefixedFileW(null, image_path_name); return cwd().openFileW(prefixed_path_w.span(), flags); } // Use of MAX_PATH_BYTES here is valid as the resulting path is immediately @@ -505,7 +514,7 @@ pub fn openSelfExe(flags: File.OpenFlags) OpenSelfExeError!File { return openFileAbsoluteZ(buf[0..self_exe_path.len :0].ptr, flags); } -// This is os.ReadLinkError || os.RealPathError with impossible errors excluded +// This is `posix.ReadLinkError || posix.RealPathError` with impossible errors excluded pub const SelfExePathError = error{ FileNotFound, AccessDenied, @@ -542,7 +551,7 @@ pub const SelfExePathError = error{ /// On Windows, the volume does not contain a recognized file system. File /// system drivers might not be loaded, or the volume may be corrupt. UnrecognizedVolume, -} || os.SysCtlError; +} || posix.SysCtlError; /// `selfExePath` except allocates the result on the heap. /// Caller owns returned memory. @@ -580,7 +589,7 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 { if (rc != 0) return error.NameTooLong; var real_path_buf: [MAX_PATH_BYTES]u8 = undefined; - const real_path = std.os.realpathZ(&symlink_path_buf, &real_path_buf) catch |err| switch (err) { + const real_path = std.posix.realpathZ(&symlink_path_buf, &real_path_buf) catch |err| switch (err) { error.InvalidWtf8 => unreachable, // Windows-only error.NetworkNotFound => unreachable, // Windows-only else => |e| return e, @@ -590,15 +599,15 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 { @memcpy(result, real_path); return result; } - switch (builtin.os.tag) { - .linux => return os.readlinkZ("/proc/self/exe", out_buffer) catch |err| switch (err) { + switch (native_os) { + .linux => return posix.readlinkZ("/proc/self/exe", out_buffer) catch |err| switch (err) { error.InvalidUtf8 => unreachable, // WASI-only error.InvalidWtf8 => unreachable, // Windows-only error.UnsupportedReparsePointType => unreachable, // Windows-only error.NetworkNotFound => unreachable, // Windows-only else => |e| return e, }, - .solaris, .illumos => return os.readlinkZ("/proc/self/path/a.out", out_buffer) catch |err| switch (err) { + .solaris, .illumos => return posix.readlinkZ("/proc/self/path/a.out", out_buffer) catch |err| switch (err) { error.InvalidUtf8 => unreachable, // WASI-only error.InvalidWtf8 => unreachable, // Windows-only error.UnsupportedReparsePointType => unreachable, // Windows-only @@ -606,29 +615,29 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 { else => |e| return e, }, .freebsd, .dragonfly => { - var mib = [4]c_int{ os.CTL.KERN, os.KERN.PROC, os.KERN.PROC_PATHNAME, -1 }; + var mib = [4]c_int{ posix.CTL.KERN, posix.KERN.PROC, posix.KERN.PROC_PATHNAME, -1 }; var out_len: usize = out_buffer.len; - try os.sysctl(&mib, out_buffer.ptr, &out_len, null, 0); + try posix.sysctl(&mib, out_buffer.ptr, &out_len, null, 0); // TODO could this slice from 0 to out_len instead? return mem.sliceTo(out_buffer, 0); }, .netbsd => { - var mib = [4]c_int{ os.CTL.KERN, os.KERN.PROC_ARGS, -1, os.KERN.PROC_PATHNAME }; + var mib = [4]c_int{ posix.CTL.KERN, posix.KERN.PROC_ARGS, -1, posix.KERN.PROC_PATHNAME }; var out_len: usize = out_buffer.len; - try os.sysctl(&mib, out_buffer.ptr, &out_len, null, 0); + try posix.sysctl(&mib, out_buffer.ptr, &out_len, null, 0); // TODO could this slice from 0 to out_len instead? return mem.sliceTo(out_buffer, 0); }, .openbsd, .haiku => { // OpenBSD doesn't support getting the path of a running process, so try to guess it - if (os.argv.len == 0) + if (std.os.argv.len == 0) return error.FileNotFound; - const argv0 = mem.span(os.argv[0]); + const argv0 = mem.span(std.os.argv[0]); if (mem.indexOf(u8, argv0, "/") != null) { // argv[0] is a path (relative or absolute): use realpath(3) directly var real_path_buf: [MAX_PATH_BYTES]u8 = undefined; - const real_path = os.realpathZ(os.argv[0], &real_path_buf) catch |err| switch (err) { + const real_path = posix.realpathZ(std.os.argv[0], &real_path_buf) catch |err| switch (err) { error.InvalidWtf8 => unreachable, // Windows-only error.NetworkNotFound => unreachable, // Windows-only else => |e| return e, @@ -640,17 +649,17 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 { return result; } else if (argv0.len != 0) { // argv[0] is not empty (and not a path): search it inside PATH - const PATH = std.os.getenvZ("PATH") orelse return error.FileNotFound; + const PATH = posix.getenvZ("PATH") orelse return error.FileNotFound; var path_it = mem.tokenizeScalar(u8, PATH, path.delimiter); while (path_it.next()) |a_path| { var resolved_path_buf: [MAX_PATH_BYTES - 1:0]u8 = undefined; const resolved_path = std.fmt.bufPrintZ(&resolved_path_buf, "{s}/{s}", .{ a_path, - os.argv[0], + std.os.argv[0], }) catch continue; var real_path_buf: [MAX_PATH_BYTES]u8 = undefined; - if (os.realpathZ(resolved_path, &real_path_buf)) |real_path| { + if (posix.realpathZ(resolved_path, &real_path_buf)) |real_path| { // found a file, and hope it is the right file if (real_path.len > out_buffer.len) return error.NameTooLong; @@ -663,13 +672,13 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 { return error.FileNotFound; }, .windows => { - const image_path_unicode_string = &os.windows.peb().ProcessParameters.ImagePathName; + const image_path_unicode_string = &windows.peb().ProcessParameters.ImagePathName; const image_path_name = image_path_unicode_string.Buffer.?[0 .. image_path_unicode_string.Length / 2 :0]; // If ImagePathName is a symlink, then it will contain the path of the // symlink, not the path that the symlink points to. We want the path // that the symlink points to, though, so we need to get the realpath. - const pathname_w = try os.windows.wToPrefixedFileW(null, image_path_name); + const pathname_w = try windows.wToPrefixedFileW(null, image_path_name); return std.fs.cwd().realpathW(pathname_w.span(), out_buffer) catch |err| switch (err) { error.InvalidWtf8 => unreachable, else => |e| return e, @@ -718,11 +727,11 @@ pub fn realpathAlloc(allocator: Allocator, pathname: []const u8) ![]u8 { // paths. musl supports passing NULL but restricts the output to PATH_MAX // anyway. var buf: [MAX_PATH_BYTES]u8 = undefined; - return allocator.dupe(u8, try os.realpath(pathname, &buf)); + return allocator.dupe(u8, try posix.realpath(pathname, &buf)); } test { - if (builtin.os.tag != .wasi) { + if (native_os != .wasi) { _ = &makeDirAbsolute; _ = &makeDirAbsoluteZ; _ = ©FileAbsolute; diff --git a/lib/std/fs/AtomicFile.zig b/lib/std/fs/AtomicFile.zig index c95ae9bcf2bb..17a17f899345 100644 --- a/lib/std/fs/AtomicFile.zig +++ b/lib/std/fs/AtomicFile.zig @@ -85,5 +85,4 @@ const File = std.fs.File; const Dir = std.fs.Dir; const fs = std.fs; const assert = std.debug.assert; -// https://github.com/ziglang/zig/issues/5019 -const posix = std.os; +const posix = std.posix; diff --git a/lib/std/fs/Dir.zig b/lib/std/fs/Dir.zig index 11fbc13c41dc..e6777e2c0984 100644 --- a/lib/std/fs/Dir.zig +++ b/lib/std/fs/Dir.zig @@ -18,7 +18,7 @@ const IteratorError = error{ InvalidUtf8, } || posix.UnexpectedError; -pub const Iterator = switch (builtin.os.tag) { +pub const Iterator = switch (native_os) { .macos, .ios, .freebsd, .netbsd, .dragonfly, .openbsd, .solaris, .illumos => struct { dir: Dir, seek: i64, @@ -34,7 +34,7 @@ pub const Iterator = switch (builtin.os.tag) { /// Memory such as file names referenced in this returned entry becomes invalid /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized. pub fn next(self: *Self) Error!?Entry { - switch (builtin.os.tag) { + switch (native_os) { .macos, .ios => return self.nextDarwin(), .freebsd, .netbsd, .dragonfly, .openbsd => return self.nextBsd(), .solaris, .illumos => return self.nextSolaris(), @@ -183,7 +183,7 @@ pub const Iterator = switch (builtin.os.tag) { const name = @as([*]u8, @ptrCast(&bsd_entry.name))[0..bsd_entry.namlen]; - const skip_zero_fileno = switch (builtin.os.tag) { + const skip_zero_fileno = switch (native_os) { // fileno=0 is used to mark invalid entries or deleted files. .openbsd, .netbsd => true, else => false, @@ -315,7 +315,7 @@ pub const Iterator = switch (builtin.os.tag) { dir: Dir, // The if guard is solely there to prevent compile errors from missing `linux.dirent64` // definition when compiling for other OSes. It doesn't do anything when compiling for Linux. - buf: [1024]u8 align(if (builtin.os.tag != .linux) 1 else @alignOf(linux.dirent64)), + buf: [1024]u8 align(if (native_os != .linux) 1 else @alignOf(linux.dirent64)), index: usize, end_index: usize, first_iter: bool, @@ -348,7 +348,7 @@ pub const Iterator = switch (builtin.os.tag) { self.first_iter = false; } const rc = linux.getdents64(self.dir.fd, &self.buf, self.buf.len); - switch (linux.getErrno(rc)) { + switch (linux.E.init(rc)) { .SUCCESS => {}, .BADF => unreachable, // Dir is invalid or was opened without iteration ability .FAULT => unreachable, @@ -398,7 +398,7 @@ pub const Iterator = switch (builtin.os.tag) { }, .windows => struct { dir: Dir, - buf: [1024]u8 align(@alignOf(std.os.windows.FILE_BOTH_DIR_INFORMATION)), + buf: [1024]u8 align(@alignOf(windows.FILE_BOTH_DIR_INFORMATION)), index: usize, end_index: usize, first_iter: bool, @@ -411,8 +411,8 @@ pub const Iterator = switch (builtin.os.tag) { /// Memory such as file names referenced in this returned entry becomes invalid /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized. pub fn next(self: *Self) Error!?Entry { + const w = windows; while (true) { - const w = std.os.windows; if (self.index >= self.end_index) { var io: w.IO_STATUS_BLOCK = undefined; const rc = w.ntdll.NtQueryDirectoryFile( @@ -582,7 +582,7 @@ pub fn iterateAssumeFirstIteration(self: Dir) Iterator { } fn iterateImpl(self: Dir, first_iter_start_value: bool) Iterator { - switch (builtin.os.tag) { + switch (native_os) { .macos, .ios, .freebsd, @@ -770,11 +770,11 @@ pub fn close(self: *Dir) void { /// On WASI, `sub_path` should be encoded as valid UTF-8. /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. pub fn openFile(self: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File { - if (builtin.os.tag == .windows) { - const path_w = try std.os.windows.sliceToPrefixedFileW(self.fd, sub_path); + if (native_os == .windows) { + const path_w = try windows.sliceToPrefixedFileW(self.fd, sub_path); return self.openFileW(path_w.span(), flags); } - if (builtin.os.tag == .wasi) { + if (native_os == .wasi) { var base: std.os.wasi.rights_t = .{}; if (flags.isRead()) { base.FD_READ = true; @@ -803,9 +803,9 @@ pub fn openFile(self: Dir, sub_path: []const u8, flags: File.OpenFlags) File.Ope /// Same as `openFile` but the path parameter is null-terminated. pub fn openFileZ(self: Dir, sub_path: [*:0]const u8, flags: File.OpenFlags) File.OpenError!File { - switch (builtin.os.tag) { + switch (native_os) { .windows => { - const path_w = try std.os.windows.cStrToPrefixedFileW(self.fd, sub_path); + const path_w = try windows.cStrToPrefixedFileW(self.fd, sub_path); return self.openFileW(path_w.span(), flags); }, .wasi => { @@ -884,7 +884,7 @@ pub fn openFileZ(self: Dir, sub_path: [*:0]const u8, flags: File.OpenFlags) File /// Same as `openFile` but Windows-only and the path parameter is /// [WTF-16](https://simonsapin.github.io/wtf-8/#potentially-ill-formed-utf-16) encoded. pub fn openFileW(self: Dir, sub_path_w: []const u16, flags: File.OpenFlags) File.OpenError!File { - const w = std.os.windows; + const w = windows; const file: File = .{ .handle = try w.OpenFile(sub_path_w, .{ .dir = self.fd, @@ -925,11 +925,11 @@ pub fn openFileW(self: Dir, sub_path_w: []const u16, flags: File.OpenFlags) File /// On WASI, `sub_path` should be encoded as valid UTF-8. /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. pub fn createFile(self: Dir, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File { - if (builtin.os.tag == .windows) { - const path_w = try std.os.windows.sliceToPrefixedFileW(self.fd, sub_path); + if (native_os == .windows) { + const path_w = try windows.sliceToPrefixedFileW(self.fd, sub_path); return self.createFileW(path_w.span(), flags); } - if (builtin.os.tag == .wasi) { + if (native_os == .wasi) { return .{ .handle = try posix.openatWasi(self.fd, sub_path, .{}, .{ .CREAT = true, @@ -957,9 +957,9 @@ pub fn createFile(self: Dir, sub_path: []const u8, flags: File.CreateFlags) File /// Same as `createFile` but the path parameter is null-terminated. pub fn createFileZ(self: Dir, sub_path_c: [*:0]const u8, flags: File.CreateFlags) File.OpenError!File { - switch (builtin.os.tag) { + switch (native_os) { .windows => { - const path_w = try std.os.windows.cStrToPrefixedFileW(self.fd, sub_path_c); + const path_w = try windows.cStrToPrefixedFileW(self.fd, sub_path_c); return self.createFileW(path_w.span(), flags); }, .wasi => { @@ -968,7 +968,7 @@ pub fn createFileZ(self: Dir, sub_path_c: [*:0]const u8, flags: File.CreateFlags else => {}, } - var os_flags: std.os.O = .{ + var os_flags: posix.O = .{ .ACCMODE = if (flags.read) .RDWR else .WRONLY, .CREAT = true, .TRUNC = flags.truncate, @@ -1032,7 +1032,7 @@ pub fn createFileZ(self: Dir, sub_path_c: [*:0]const u8, flags: File.CreateFlags /// Same as `createFile` but Windows-only and the path parameter is /// [WTF-16](https://simonsapin.github.io/wtf-8/#potentially-ill-formed-utf-16) encoded. pub fn createFileW(self: Dir, sub_path_w: []const u16, flags: File.CreateFlags) File.OpenError!File { - const w = std.os.windows; + const w = windows; const read_flag = if (flags.read) @as(u32, w.GENERIC_READ) else 0; const file: File = .{ .handle = try w.OpenFile(sub_path_w, .{ @@ -1145,7 +1145,7 @@ pub fn makePath(self: Dir, sub_path: []const u8) !void { /// have been modified regardless. /// `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). fn makeOpenPathAccessMaskW(self: Dir, sub_path: []const u8, access_mask: u32, no_follow: bool) OpenError!Dir { - const w = std.os.windows; + const w = windows; var it = try fs.path.componentIterator(sub_path); // If there are no components in the path, then create a dummy component with the full path. var component = it.last() orelse fs.path.NativeComponentIterator.Component{ @@ -1180,9 +1180,9 @@ fn makeOpenPathAccessMaskW(self: Dir, sub_path: []const u8, access_mask: u32, no /// On WASI, `sub_path` should be encoded as valid UTF-8. /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. pub fn makeOpenPath(self: Dir, sub_path: []const u8, open_dir_options: OpenDirOptions) !Dir { - return switch (builtin.os.tag) { + return switch (native_os) { .windows => { - const w = std.os.windows; + const w = windows; const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA | w.SYNCHRONIZE | w.FILE_TRAVERSE | (if (open_dir_options.iterate) w.FILE_LIST_DIRECTORY else @as(u32, 0)); @@ -1215,11 +1215,11 @@ pub const RealPathError = posix.RealPathError; /// Currently supported hosts are: Linux, macOS, and Windows. /// See also `Dir.realpathZ`, `Dir.realpathW`, and `Dir.realpathAlloc`. pub fn realpath(self: Dir, pathname: []const u8, out_buffer: []u8) RealPathError![]u8 { - if (builtin.os.tag == .wasi) { + if (native_os == .wasi) { @compileError("realpath is not available on WASI"); } - if (builtin.os.tag == .windows) { - const pathname_w = try std.os.windows.sliceToPrefixedFileW(self.fd, pathname); + if (native_os == .windows) { + const pathname_w = try windows.sliceToPrefixedFileW(self.fd, pathname); return self.realpathW(pathname_w.span(), out_buffer); } const pathname_c = try posix.toPosixPath(pathname); @@ -1229,12 +1229,12 @@ pub fn realpath(self: Dir, pathname: []const u8, out_buffer: []u8) RealPathError /// Same as `Dir.realpath` except `pathname` is null-terminated. /// See also `Dir.realpath`, `realpathZ`. pub fn realpathZ(self: Dir, pathname: [*:0]const u8, out_buffer: []u8) RealPathError![]u8 { - if (builtin.os.tag == .windows) { - const pathname_w = try posix.windows.cStrToPrefixedFileW(self.fd, pathname); + if (native_os == .windows) { + const pathname_w = try windows.cStrToPrefixedFileW(self.fd, pathname); return self.realpathW(pathname_w.span(), out_buffer); } - const flags: posix.O = switch (builtin.os.tag) { + const flags: posix.O = switch (native_os) { .linux => .{ .NONBLOCK = true, .CLOEXEC = true, @@ -1255,14 +1255,8 @@ pub fn realpathZ(self: Dir, pathname: [*:0]const u8, out_buffer: []u8) RealPathE }; defer posix.close(fd); - // Use of MAX_PATH_BYTES here is valid as the realpath function does not - // have a variant that takes an arbitrary-size buffer. - // TODO(#4812): Consider reimplementing realpath or using the POSIX.1-2008 - // NULL out parameter (GNU's canonicalize_file_name) to handle overelong - // paths. musl supports passing NULL but restricts the output to PATH_MAX - // anyway. var buffer: [fs.MAX_PATH_BYTES]u8 = undefined; - const out_path = try posix.getFdPath(fd, &buffer); + const out_path = try std.os.getFdPath(fd, &buffer); if (out_path.len > out_buffer.len) { return error.NameTooLong; @@ -1277,7 +1271,7 @@ pub fn realpathZ(self: Dir, pathname: [*:0]const u8, out_buffer: []u8) RealPathE /// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). /// See also `Dir.realpath`, `realpathW`. pub fn realpathW(self: Dir, pathname: []const u16, out_buffer: []u8) RealPathError![]u8 { - const w = std.os.windows; + const w = windows; const access_mask = w.GENERIC_READ | w.SYNCHRONIZE; const share_access = w.FILE_SHARE_READ; @@ -1331,16 +1325,16 @@ pub fn realpathAlloc(self: Dir, allocator: Allocator, pathname: []const u8) Real /// Not all targets support this. For example, WASI does not have the concept /// of a current working directory. pub fn setAsCwd(self: Dir) !void { - if (builtin.os.tag == .wasi) { + if (native_os == .wasi) { @compileError("changing cwd is not currently possible in WASI"); } - if (builtin.os.tag == .windows) { - var dir_path_buffer: [std.os.windows.PATH_MAX_WIDE]u16 = undefined; - const dir_path = try std.os.windows.GetFinalPathNameByHandle(self.fd, .{}, &dir_path_buffer); + if (native_os == .windows) { + var dir_path_buffer: [windows.PATH_MAX_WIDE]u16 = undefined; + const dir_path = try windows.GetFinalPathNameByHandle(self.fd, .{}, &dir_path_buffer); if (builtin.link_libc) { return posix.chdirW(dir_path); } - return std.os.windows.SetCurrentDirectory(dir_path); + return windows.SetCurrentDirectory(dir_path); } try posix.fchdir(self.fd); } @@ -1368,9 +1362,9 @@ pub const OpenDirOptions = struct { /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. /// Asserts that the path parameter has no null bytes. pub fn openDir(self: Dir, sub_path: []const u8, args: OpenDirOptions) OpenError!Dir { - switch (builtin.os.tag) { + switch (native_os) { .windows => { - const sub_path_w = try posix.windows.sliceToPrefixedFileW(self.fd, sub_path); + const sub_path_w = try windows.sliceToPrefixedFileW(self.fd, sub_path); return self.openDirW(sub_path_w.span().ptr, args); }, .wasi => { @@ -1427,9 +1421,9 @@ pub fn openDir(self: Dir, sub_path: []const u8, args: OpenDirOptions) OpenError! /// Same as `openDir` except the parameter is null-terminated. pub fn openDirZ(self: Dir, sub_path_c: [*:0]const u8, args: OpenDirOptions) OpenError!Dir { - switch (builtin.os.tag) { + switch (native_os) { .windows => { - const sub_path_w = try std.os.windows.cStrToPrefixedFileW(self.fd, sub_path_c); + const sub_path_w = try windows.cStrToPrefixedFileW(self.fd, sub_path_c); return self.openDirW(sub_path_w.span().ptr, args); }, .wasi => { @@ -1453,7 +1447,7 @@ pub fn openDirZ(self: Dir, sub_path_c: [*:0]const u8, args: OpenDirOptions) Open /// Same as `openDir` except the path parameter is WTF-16 LE encoded, NT-prefixed. /// This function asserts the target OS is Windows. pub fn openDirW(self: Dir, sub_path_w: [*:0]const u16, args: OpenDirOptions) OpenError!Dir { - const w = std.os.windows; + const w = windows; // TODO remove some of these flags if args.access_sub_paths is false const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA | w.SYNCHRONIZE | w.FILE_TRAVERSE; @@ -1487,7 +1481,7 @@ const MakeOpenDirAccessMaskWOptions = struct { }; fn makeOpenDirAccessMaskW(self: Dir, sub_path_w: [*:0]const u16, access_mask: u32, flags: MakeOpenDirAccessMaskWOptions) OpenError!Dir { - const w = std.os.windows; + const w = windows; var result = Dir{ .fd = undefined, @@ -1545,10 +1539,10 @@ pub const DeleteFileError = posix.UnlinkError; /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. /// Asserts that the path parameter has no null bytes. pub fn deleteFile(self: Dir, sub_path: []const u8) DeleteFileError!void { - if (builtin.os.tag == .windows) { - const sub_path_w = try std.os.windows.sliceToPrefixedFileW(self.fd, sub_path); + if (native_os == .windows) { + const sub_path_w = try windows.sliceToPrefixedFileW(self.fd, sub_path); return self.deleteFileW(sub_path_w.span()); - } else if (builtin.os.tag == .wasi and !builtin.link_libc) { + } else if (native_os == .wasi and !builtin.link_libc) { posix.unlinkat(self.fd, sub_path, 0) catch |err| switch (err) { error.DirNotEmpty => unreachable, // not passing AT.REMOVEDIR else => |e| return e, @@ -1563,7 +1557,7 @@ pub fn deleteFile(self: Dir, sub_path: []const u8) DeleteFileError!void { pub fn deleteFileZ(self: Dir, sub_path_c: [*:0]const u8) DeleteFileError!void { posix.unlinkatZ(self.fd, sub_path_c, 0) catch |err| switch (err) { error.DirNotEmpty => unreachable, // not passing AT.REMOVEDIR - error.AccessDenied => |e| switch (builtin.os.tag) { + error.AccessDenied => |e| switch (native_os) { // non-Linux POSIX systems return EPERM when trying to delete a directory, so // we need to handle that case specifically and translate the error .macos, .ios, .freebsd, .netbsd, .dragonfly, .openbsd, .solaris, .illumos => { @@ -1615,10 +1609,10 @@ pub const DeleteDirError = error{ /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. /// Asserts that the path parameter has no null bytes. pub fn deleteDir(self: Dir, sub_path: []const u8) DeleteDirError!void { - if (builtin.os.tag == .windows) { - const sub_path_w = try std.os.windows.sliceToPrefixedFileW(self.fd, sub_path); + if (native_os == .windows) { + const sub_path_w = try windows.sliceToPrefixedFileW(self.fd, sub_path); return self.deleteDirW(sub_path_w.span()); - } else if (builtin.os.tag == .wasi and !builtin.link_libc) { + } else if (native_os == .wasi and !builtin.link_libc) { posix.unlinkat(self.fd, sub_path, posix.AT.REMOVEDIR) catch |err| switch (err) { error.IsDir => unreachable, // not possible since we pass AT.REMOVEDIR else => |e| return e, @@ -1691,15 +1685,15 @@ pub fn symLink( sym_link_path: []const u8, flags: SymLinkFlags, ) !void { - if (builtin.os.tag == .wasi and !builtin.link_libc) { + if (native_os == .wasi and !builtin.link_libc) { return self.symLinkWasi(target_path, sym_link_path, flags); } - if (builtin.os.tag == .windows) { + if (native_os == .windows) { // Target path does not use sliceToPrefixedFileW because certain paths // are handled differently when creating a symlink than they would be // when converting to an NT namespaced path. CreateSymbolicLink in // symLinkW will handle the necessary conversion. - var target_path_w: std.os.windows.PathSpace = undefined; + var target_path_w: windows.PathSpace = undefined; target_path_w.len = try std.unicode.wtf8ToWtf16Le(&target_path_w.data, target_path); target_path_w.data[target_path_w.len] = 0; // However, we need to canonicalize any path separators to `\`, since if @@ -1711,7 +1705,7 @@ pub fn symLink( mem.nativeToLittle(u16, '\\'), ); - const sym_link_path_w = try std.os.windows.sliceToPrefixedFileW(self.fd, sym_link_path); + const sym_link_path_w = try windows.sliceToPrefixedFileW(self.fd, sym_link_path); return self.symLinkW(target_path_w.span(), sym_link_path_w.span(), flags); } const target_path_c = try posix.toPosixPath(target_path); @@ -1736,9 +1730,9 @@ pub fn symLinkZ( sym_link_path_c: [*:0]const u8, flags: SymLinkFlags, ) !void { - if (builtin.os.tag == .windows) { - const target_path_w = try std.os.windows.cStrToPrefixedFileW(self.fd, target_path_c); - const sym_link_path_w = try std.os.windows.cStrToPrefixedFileW(self.fd, sym_link_path_c); + if (native_os == .windows) { + const target_path_w = try windows.cStrToPrefixedFileW(self.fd, target_path_c); + const sym_link_path_w = try windows.cStrToPrefixedFileW(self.fd, sym_link_path_c); return self.symLinkW(target_path_w.span(), sym_link_path_w.span(), flags); } return posix.symlinkatZ(target_path_c, self.fd, sym_link_path_c); @@ -1756,7 +1750,7 @@ pub fn symLinkW( sym_link_path_w: []const u16, flags: SymLinkFlags, ) !void { - return std.os.windows.CreateSymbolicLink(self.fd, sym_link_path_w, target_path_w, flags.is_directory); + return windows.CreateSymbolicLink(self.fd, sym_link_path_w, target_path_w, flags.is_directory); } pub const ReadLinkError = posix.ReadLinkError; @@ -1768,11 +1762,11 @@ pub const ReadLinkError = posix.ReadLinkError; /// On WASI, `sub_path` should be encoded as valid UTF-8. /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. pub fn readLink(self: Dir, sub_path: []const u8, buffer: []u8) ReadLinkError![]u8 { - if (builtin.os.tag == .wasi and !builtin.link_libc) { + if (native_os == .wasi and !builtin.link_libc) { return self.readLinkWasi(sub_path, buffer); } - if (builtin.os.tag == .windows) { - const sub_path_w = try std.os.windows.sliceToPrefixedFileW(self.fd, sub_path); + if (native_os == .windows) { + const sub_path_w = try windows.sliceToPrefixedFileW(self.fd, sub_path); return self.readLinkW(sub_path_w.span(), buffer); } const sub_path_c = try posix.toPosixPath(sub_path); @@ -1786,8 +1780,8 @@ pub fn readLinkWasi(self: Dir, sub_path: []const u8, buffer: []u8) ![]u8 { /// Same as `readLink`, except the `sub_path_c` parameter is null-terminated. pub fn readLinkZ(self: Dir, sub_path_c: [*:0]const u8, buffer: []u8) ![]u8 { - if (builtin.os.tag == .windows) { - const sub_path_w = try std.os.windows.cStrToPrefixedFileW(self.fd, sub_path_c); + if (native_os == .windows) { + const sub_path_w = try windows.cStrToPrefixedFileW(self.fd, sub_path_c); return self.readLinkW(sub_path_w.span(), buffer); } return posix.readlinkatZ(self.fd, sub_path_c, buffer); @@ -1796,7 +1790,7 @@ pub fn readLinkZ(self: Dir, sub_path_c: [*:0]const u8, buffer: []u8) ![]u8 { /// Windows-only. Same as `readLink` except the pathname parameter /// is WTF16 LE encoded. pub fn readLinkW(self: Dir, sub_path_w: []const u16, buffer: []u8) ![]u8 { - return std.os.windows.ReadLink(self.fd, sub_path_w, buffer); + return windows.ReadLink(self.fd, sub_path_w, buffer); } /// Read all of file contents using a preallocated buffer. @@ -2319,8 +2313,8 @@ pub const AccessError = posix.AccessError; /// For example, instead of testing if a file exists and then opening it, just /// open it and handle the error for file not found. pub fn access(self: Dir, sub_path: []const u8, flags: File.OpenFlags) AccessError!void { - if (builtin.os.tag == .windows) { - const sub_path_w = std.os.windows.sliceToPrefixedFileW(self.fd, sub_path) catch |err| switch (err) { + if (native_os == .windows) { + const sub_path_w = windows.sliceToPrefixedFileW(self.fd, sub_path) catch |err| switch (err) { error.AccessDenied => return error.PermissionDenied, else => |e| return e, }; @@ -2332,8 +2326,8 @@ pub fn access(self: Dir, sub_path: []const u8, flags: File.OpenFlags) AccessErro /// Same as `access` except the path parameter is null-terminated. pub fn accessZ(self: Dir, sub_path: [*:0]const u8, flags: File.OpenFlags) AccessError!void { - if (builtin.os.tag == .windows) { - const sub_path_w = std.os.windows.cStrToPrefixedFileW(self.fd, sub_path) catch |err| switch (err) { + if (native_os == .windows) { + const sub_path_w = windows.cStrToPrefixedFileW(self.fd, sub_path) catch |err| switch (err) { error.AccessDenied => return error.PermissionDenied, else => |e| return e, }; @@ -2355,7 +2349,7 @@ pub fn accessZ(self: Dir, sub_path: [*:0]const u8, flags: File.OpenFlags) Access /// TODO currently this ignores `flags`. pub fn accessW(self: Dir, sub_path_w: [*:0]const u16, flags: File.OpenFlags) AccessError!void { _ = flags; - return posix.faccessatW(self.fd, sub_path_w, 0, 0); + return posix.faccessatW(self.fd, sub_path_w); } pub const CopyFileOptions = struct { @@ -2473,7 +2467,7 @@ fn copy_file(fd_in: posix.fd_t, fd_out: posix.fd_t, maybe_size: ?u64) CopyFileRa } } - if (builtin.os.tag == .linux) { + if (native_os == .linux) { // Try copy_file_range first as that works at the FS level and is the // most efficient method (if available). var offset: u64 = 0; @@ -2555,12 +2549,12 @@ pub const StatFileError = File.OpenError || File.StatError || posix.FStatAtError /// On WASI, `sub_path` should be encoded as valid UTF-8. /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. pub fn statFile(self: Dir, sub_path: []const u8) StatFileError!Stat { - if (builtin.os.tag == .windows) { + if (native_os == .windows) { var file = try self.openFile(sub_path, .{}); defer file.close(); return file.stat(); } - if (builtin.os.tag == .wasi and !builtin.link_libc) { + if (native_os == .wasi and !builtin.link_libc) { const st = try posix.fstatat_wasi(self.fd, sub_path, .{ .SYMLINK_FOLLOW = true }); return Stat.fromWasi(st); } @@ -2617,9 +2611,10 @@ const builtin = @import("builtin"); const std = @import("../std.zig"); const File = std.fs.File; const AtomicFile = std.fs.AtomicFile; -// https://github.com/ziglang/zig/issues/5019 -const posix = std.os; +const posix = std.posix; const mem = std.mem; const fs = std.fs; const Allocator = std.mem.Allocator; const assert = std.debug.assert; +const windows = std.os.windows; +const native_os = builtin.os.tag; diff --git a/lib/std/fs/File.zig b/lib/std/fs/File.zig index 669f1b72e33f..8e1583653670 100644 --- a/lib/std/fs/File.zig +++ b/lib/std/fs/File.zig @@ -193,6 +193,58 @@ pub fn isTty(self: File) bool { return posix.isatty(self.handle); } +pub fn isCygwinPty(file: File) bool { + if (builtin.os.tag != .windows) return false; + + const handle = file.handle; + + // If this is a MSYS2/cygwin pty, then it will be a named pipe with a name in one of these formats: + // msys-[...]-ptyN-[...] + // cygwin-[...]-ptyN-[...] + // + // Example: msys-1888ae32e00d56aa-pty0-to-master + + // First, just check that the handle is a named pipe. + // This allows us to avoid the more costly NtQueryInformationFile call + // for handles that aren't named pipes. + { + var io_status: windows.IO_STATUS_BLOCK = undefined; + var device_info: windows.FILE_FS_DEVICE_INFORMATION = undefined; + const rc = windows.ntdll.NtQueryVolumeInformationFile(handle, &io_status, &device_info, @sizeOf(windows.FILE_FS_DEVICE_INFORMATION), .FileFsDeviceInformation); + switch (rc) { + .SUCCESS => {}, + else => return false, + } + if (device_info.DeviceType != windows.FILE_DEVICE_NAMED_PIPE) return false; + } + + const name_bytes_offset = @offsetOf(windows.FILE_NAME_INFO, "FileName"); + // `NAME_MAX` UTF-16 code units (2 bytes each) + // This buffer may not be long enough to handle *all* possible paths + // (PATH_MAX_WIDE would be necessary for that), but because we only care + // about certain paths and we know they must be within a reasonable length, + // we can use this smaller buffer and just return false on any error from + // NtQueryInformationFile. + const num_name_bytes = windows.MAX_PATH * 2; + var name_info_bytes align(@alignOf(windows.FILE_NAME_INFO)) = [_]u8{0} ** (name_bytes_offset + num_name_bytes); + + var io_status_block: windows.IO_STATUS_BLOCK = undefined; + const rc = windows.ntdll.NtQueryInformationFile(handle, &io_status_block, &name_info_bytes, @intCast(name_info_bytes.len), .FileNameInformation); + switch (rc) { + .SUCCESS => {}, + .INVALID_PARAMETER => unreachable, + else => return false, + } + + const name_info: *const windows.FILE_NAME_INFO = @ptrCast(&name_info_bytes); + const name_bytes = name_info_bytes[name_bytes_offset .. name_bytes_offset + name_info.FileNameLength]; + const name_wide = std.mem.bytesAsSlice(u16, name_bytes); + // The name we get from NtQueryInformationFile will be prefixed with a '\', e.g. \msys-1888ae32e00d56aa-pty0-to-master + return (std.mem.startsWith(u16, name_wide, &[_]u16{ '\\', 'm', 's', 'y', 's', '-' }) or + std.mem.startsWith(u16, name_wide, &[_]u16{ '\\', 'c', 'y', 'g', 'w', 'i', 'n', '-' })) and + std.mem.indexOf(u16, name_wide, &[_]u16{ '-', 'p', 't', 'y' }) != null; +} + /// Test whether ANSI escape codes will be treated as such. pub fn supportsAnsiEscapeCodes(self: File) bool { if (builtin.os.tag == .windows) { @@ -201,7 +253,7 @@ pub fn supportsAnsiEscapeCodes(self: File) bool { if (console_mode & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) return true; } - return posix.isCygwinPty(self.handle); + return self.isCygwinPty(); } if (builtin.os.tag == .wasi) { // WASI sanitizes stdout when fd is a tty so ANSI escape codes @@ -1634,8 +1686,7 @@ const File = @This(); const std = @import("../std.zig"); const builtin = @import("builtin"); const Allocator = std.mem.Allocator; -// https://github.com/ziglang/zig/issues/5019 -const posix = std.os; +const posix = std.posix; const io = std.io; const math = std.math; const assert = std.debug.assert; diff --git a/lib/std/fs/get_app_data_dir.zig b/lib/std/fs/get_app_data_dir.zig index 4fcd17efe2ae..9c8429119934 100644 --- a/lib/std/fs/get_app_data_dir.zig +++ b/lib/std/fs/get_app_data_dir.zig @@ -3,7 +3,8 @@ const builtin = @import("builtin"); const unicode = std.unicode; const mem = std.mem; const fs = std.fs; -const os = std.os; +const native_os = builtin.os.tag; +const posix = std.posix; pub const GetAppDataDirError = error{ OutOfMemory, @@ -13,7 +14,7 @@ pub const GetAppDataDirError = error{ /// Caller owns returned memory. /// TODO determine if we can remove the allocator requirement pub fn getAppDataDir(allocator: mem.Allocator, appname: []const u8) GetAppDataDirError![]u8 { - switch (builtin.os.tag) { + switch (native_os) { .windows => { const local_app_data_dir = std.process.getEnvVarOwned(allocator, "LOCALAPPDATA") catch |err| switch (err) { error.OutOfMemory => |e| return e, @@ -23,18 +24,18 @@ pub fn getAppDataDir(allocator: mem.Allocator, appname: []const u8) GetAppDataDi return fs.path.join(allocator, &[_][]const u8{ local_app_data_dir, appname }); }, .macos => { - const home_dir = os.getenv("HOME") orelse { + const home_dir = posix.getenv("HOME") orelse { // TODO look in /etc/passwd return error.AppDataDirUnavailable; }; return fs.path.join(allocator, &[_][]const u8{ home_dir, "Library", "Application Support", appname }); }, .linux, .freebsd, .netbsd, .dragonfly, .openbsd, .solaris, .illumos => { - if (os.getenv("XDG_DATA_HOME")) |xdg| { + if (posix.getenv("XDG_DATA_HOME")) |xdg| { return fs.path.join(allocator, &[_][]const u8{ xdg, appname }); } - const home_dir = os.getenv("HOME") orelse { + const home_dir = posix.getenv("HOME") orelse { // TODO look in /etc/passwd return error.AppDataDirUnavailable; }; @@ -48,7 +49,7 @@ pub fn getAppDataDir(allocator: mem.Allocator, appname: []const u8) GetAppDataDi } // TODO look into directory_which const be_user_settings = 0xbbe; - const rc = os.system.find_directory(be_user_settings, -1, true, dir_path_ptr, 1); + const rc = std.c.find_directory(be_user_settings, -1, true, dir_path_ptr, 1); const settings_dir = try allocator.dupeZ(u8, mem.sliceTo(dir_path_ptr, 0)); defer allocator.free(settings_dir); switch (rc) { @@ -61,7 +62,7 @@ pub fn getAppDataDir(allocator: mem.Allocator, appname: []const u8) GetAppDataDi } test "getAppDataDir" { - if (builtin.os.tag == .wasi) return error.SkipZigTest; + if (native_os == .wasi) return error.SkipZigTest; // We can't actually validate the result const dir = getAppDataDir(std.testing.allocator, "zig") catch return; diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 740e0655c5b4..3aa932cf0147 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -1,10 +1,12 @@ const std = @import("../std.zig"); const builtin = @import("builtin"); const testing = std.testing; -const os = std.os; const fs = std.fs; const mem = std.mem; const wasi = std.os.wasi; +const native_os = builtin.os.tag; +const windows = std.os.windows; +const posix = std.posix; const ArenaAllocator = std.heap.ArenaAllocator; const Dir = std.fs.Dir; @@ -25,7 +27,7 @@ const PathType = enum { }; } - pub const TransformError = std.os.RealPathError || error{OutOfMemory}; + pub const TransformError = posix.RealPathError || error{OutOfMemory}; pub const TransformFn = fn (allocator: mem.Allocator, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8; pub fn getTransformFn(comptime path_type: PathType) TransformFn { @@ -42,7 +44,7 @@ const PathType = enum { // The final path may not actually exist which would cause realpath to fail. // So instead, we get the path of the dir and join it with the relative path. var fd_path_buf: [fs.MAX_PATH_BYTES]u8 = undefined; - const dir_path = try os.getFdPath(dir.fd, &fd_path_buf); + const dir_path = try std.os.getFdPath(dir.fd, &fd_path_buf); return fs.path.joinZ(allocator, &.{ dir_path, relative_path }); } }.transform, @@ -51,8 +53,8 @@ const PathType = enum { // Any drive absolute path (C:\foo) can be converted into a UNC path by // using '127.0.0.1' as the server name and '$' as the share name. var fd_path_buf: [fs.MAX_PATH_BYTES]u8 = undefined; - const dir_path = try os.getFdPath(dir.fd, &fd_path_buf); - const windows_path_type = std.os.windows.getUnprefixedPathType(u8, dir_path); + const dir_path = try std.os.getFdPath(dir.fd, &fd_path_buf); + const windows_path_type = windows.getUnprefixedPathType(u8, dir_path); switch (windows_path_type) { .unc_absolute => return fs.path.joinZ(allocator, &.{ dir_path, relative_path }), .drive_absolute => { @@ -102,7 +104,7 @@ const TestContext = struct { pub fn transformPath(self: *TestContext, relative_path: [:0]const u8) ![:0]const u8 { const allocator = self.arena.allocator(); const transformed_path = try self.transform_fn(allocator, self.dir, relative_path); - if (builtin.os.tag == .windows) { + if (native_os == .windows) { const transformed_sep_path = try allocator.dupeZ(u8, transformed_path); std.mem.replaceScalar(u8, transformed_sep_path, switch (self.path_sep) { '/' => '\\', @@ -119,7 +121,7 @@ const TestContext = struct { /// If path separators are replaced, then the result is allocated by the /// TestContext's arena and will be free'd during `TestContext.deinit`. pub fn toCanonicalPathSep(self: *TestContext, path: [:0]const u8) ![:0]const u8 { - if (builtin.os.tag == .windows) { + if (native_os == .windows) { const allocator = self.arena.allocator(); const transformed_sep_path = try allocator.dupeZ(u8, path); std.mem.replaceScalar(u8, transformed_sep_path, '/', '\\'); @@ -157,7 +159,7 @@ fn testWithPathTypeIfSupported(comptime path_type: PathType, comptime path_sep: fn setupSymlink(dir: Dir, target: []const u8, link: []const u8, flags: SymLinkFlags) !void { return dir.symLink(target, link, flags) catch |err| switch (err) { // Symlink requires admin privileges on windows, so this test can legitimately fail. - error.AccessDenied => if (builtin.os.tag == .windows) return error.SkipZigTest else return err, + error.AccessDenied => if (native_os == .windows) return error.SkipZigTest else return err, else => return err, }; } @@ -166,7 +168,7 @@ fn setupSymlink(dir: Dir, target: []const u8, link: []const u8, flags: SymLinkFl // AccessDenied, then make the test failure silent (it is not a Zig failure). fn setupSymlinkAbsolute(target: []const u8, link: []const u8, flags: SymLinkFlags) !void { return fs.symLinkAbsolute(target, link, flags) catch |err| switch (err) { - error.AccessDenied => if (builtin.os.tag == .windows) return error.SkipZigTest else return err, + error.AccessDenied => if (native_os == .windows) return error.SkipZigTest else return err, else => return err, }; } @@ -232,60 +234,58 @@ test "File.stat on a File that is a symlink returns Kind.sym_link" { var symlink = switch (builtin.target.os.tag) { .windows => windows_symlink: { - const w = std.os.windows; - - const sub_path_w = try std.os.windows.cStrToPrefixedFileW(ctx.dir.fd, "symlink"); + const sub_path_w = try windows.cStrToPrefixedFileW(ctx.dir.fd, "symlink"); var result = Dir{ .fd = undefined, }; const path_len_bytes = @as(u16, @intCast(sub_path_w.span().len * 2)); - var nt_name = w.UNICODE_STRING{ + var nt_name = windows.UNICODE_STRING{ .Length = path_len_bytes, .MaximumLength = path_len_bytes, .Buffer = @constCast(&sub_path_w.data), }; - var attr = w.OBJECT_ATTRIBUTES{ - .Length = @sizeOf(w.OBJECT_ATTRIBUTES), + var attr = windows.OBJECT_ATTRIBUTES{ + .Length = @sizeOf(windows.OBJECT_ATTRIBUTES), .RootDirectory = if (fs.path.isAbsoluteWindowsW(sub_path_w.span())) null else ctx.dir.fd, .Attributes = 0, .ObjectName = &nt_name, .SecurityDescriptor = null, .SecurityQualityOfService = null, }; - var io: w.IO_STATUS_BLOCK = undefined; - const rc = w.ntdll.NtCreateFile( + var io: windows.IO_STATUS_BLOCK = undefined; + const rc = windows.ntdll.NtCreateFile( &result.fd, - w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA | w.SYNCHRONIZE | w.FILE_TRAVERSE, + windows.STANDARD_RIGHTS_READ | windows.FILE_READ_ATTRIBUTES | windows.FILE_READ_EA | windows.SYNCHRONIZE | windows.FILE_TRAVERSE, &attr, &io, null, - w.FILE_ATTRIBUTE_NORMAL, - w.FILE_SHARE_READ | w.FILE_SHARE_WRITE, - w.FILE_OPEN, + windows.FILE_ATTRIBUTE_NORMAL, + windows.FILE_SHARE_READ | windows.FILE_SHARE_WRITE, + windows.FILE_OPEN, // FILE_OPEN_REPARSE_POINT is the important thing here - w.FILE_OPEN_REPARSE_POINT | w.FILE_DIRECTORY_FILE | w.FILE_SYNCHRONOUS_IO_NONALERT | w.FILE_OPEN_FOR_BACKUP_INTENT, + windows.FILE_OPEN_REPARSE_POINT | windows.FILE_DIRECTORY_FILE | windows.FILE_SYNCHRONOUS_IO_NONALERT | windows.FILE_OPEN_FOR_BACKUP_INTENT, null, 0, ); switch (rc) { .SUCCESS => break :windows_symlink result, - else => return w.unexpectedStatus(rc), + else => return windows.unexpectedStatus(rc), } }, .linux => linux_symlink: { - const sub_path_c = try os.toPosixPath("symlink"); + const sub_path_c = try posix.toPosixPath("symlink"); // the O_NOFOLLOW | O_PATH combination can obtain a fd to a symlink // note that if O_DIRECTORY is set, then this will error with ENOTDIR - const flags: os.O = .{ + const flags: posix.O = .{ .NOFOLLOW = true, .PATH = true, .ACCMODE = .RDONLY, .CLOEXEC = true, }; - const fd = try os.openatZ(ctx.dir.fd, &sub_path_c, flags, 0); + const fd = try posix.openatZ(ctx.dir.fd, &sub_path_c, flags, 0); break :linux_symlink Dir{ .fd = fd }; }, else => unreachable, @@ -315,7 +315,7 @@ test "openDir" { } test "accessAbsolute" { - if (builtin.os.tag == .wasi) return error.SkipZigTest; + if (native_os == .wasi) return error.SkipZigTest; var tmp = tmpDir(.{}); defer tmp.cleanup(); @@ -333,7 +333,7 @@ test "accessAbsolute" { } test "openDirAbsolute" { - if (builtin.os.tag == .wasi) return error.SkipZigTest; + if (native_os == .wasi) return error.SkipZigTest; var tmp = tmpDir(.{}); defer tmp.cleanup(); @@ -361,14 +361,14 @@ test "openDirAbsolute" { } test "openDir cwd parent '..'" { - if (builtin.os.tag == .wasi) return error.SkipZigTest; + if (native_os == .wasi) return error.SkipZigTest; var dir = try fs.cwd().openDir("..", .{}); defer dir.close(); } test "openDir non-cwd parent '..'" { - switch (builtin.os.tag) { + switch (native_os) { .wasi, .netbsd, .openbsd => return error.SkipZigTest, else => {}, } @@ -392,7 +392,7 @@ test "openDir non-cwd parent '..'" { } test "readLinkAbsolute" { - if (builtin.os.tag == .wasi) return error.SkipZigTest; + if (native_os == .wasi) return error.SkipZigTest; var tmp = tmpDir(.{}); defer tmp.cleanup(); @@ -587,7 +587,7 @@ test "Dir.Iterator but dir is deleted during iteration" { try std.testing.expect(entry == null); // On Linux, we can opt-in to receiving a more specific error by calling `nextLinux` - if (builtin.os.tag == .linux) { + if (native_os == .linux) { try std.testing.expectError(error.DirNotFound, iterator.nextLinux()); } } @@ -744,7 +744,7 @@ test "directory operations on files" { test "file operations on directories" { // TODO: fix this test on FreeBSD. https://github.com/ziglang/zig/issues/1759 - if (builtin.os.tag == .freebsd) return error.SkipZigTest; + if (native_os == .freebsd) return error.SkipZigTest; try testWithAllSupportedPathTypes(struct { fn impl(ctx: *TestContext) !void { @@ -754,7 +754,7 @@ test "file operations on directories" { try testing.expectError(error.IsDir, ctx.dir.createFile(test_dir_name, .{})); try testing.expectError(error.IsDir, ctx.dir.deleteFile(test_dir_name)); - switch (builtin.os.tag) { + switch (native_os) { // no error when reading a directory. .dragonfly, .netbsd => {}, // Currently, WASI will return error.Unexpected (via ENOTCAPABLE) when attempting fd_read on a directory handle. @@ -895,7 +895,7 @@ test "Dir.rename directories" { test "Dir.rename directory onto empty dir" { // TODO: Fix on Windows, see https://github.com/ziglang/zig/issues/6364 - if (builtin.os.tag == .windows) return error.SkipZigTest; + if (native_os == .windows) return error.SkipZigTest; try testWithAllSupportedPathTypes(struct { fn impl(ctx: *TestContext) !void { @@ -916,7 +916,7 @@ test "Dir.rename directory onto empty dir" { test "Dir.rename directory onto non-empty dir" { // TODO: Fix on Windows, see https://github.com/ziglang/zig/issues/6364 - if (builtin.os.tag == .windows) return error.SkipZigTest; + if (native_os == .windows) return error.SkipZigTest; try testWithAllSupportedPathTypes(struct { fn impl(ctx: *TestContext) !void { @@ -942,7 +942,7 @@ test "Dir.rename directory onto non-empty dir" { test "Dir.rename file <-> dir" { // TODO: Fix on Windows, see https://github.com/ziglang/zig/issues/6364 - if (builtin.os.tag == .windows) return error.SkipZigTest; + if (native_os == .windows) return error.SkipZigTest; try testWithAllSupportedPathTypes(struct { fn impl(ctx: *TestContext) !void { @@ -979,7 +979,7 @@ test "rename" { } test "renameAbsolute" { - if (builtin.os.tag == .wasi) return error.SkipZigTest; + if (native_os == .wasi) return error.SkipZigTest; var tmp_dir = tmpDir(.{}); defer tmp_dir.cleanup(); @@ -1032,14 +1032,14 @@ test "renameAbsolute" { } test "openSelfExe" { - if (builtin.os.tag == .wasi) return error.SkipZigTest; + if (native_os == .wasi) return error.SkipZigTest; const self_exe_file = try std.fs.openSelfExe(.{}); self_exe_file.close(); } test "selfExePath" { - if (builtin.os.tag == .wasi) return error.SkipZigTest; + if (native_os == .wasi) return error.SkipZigTest; var buf: [fs.MAX_PATH_BYTES]u8 = undefined; const buf_self_exe_path = try std.fs.selfExePath(&buf); @@ -1120,7 +1120,7 @@ test "makePath, put some files in it, deleteTreeMinStackSize" { } test "makePath in a directory that no longer exists" { - if (builtin.os.tag == .windows) return error.SkipZigTest; // Windows returns FileBusy if attempting to remove an open dir + if (native_os == .windows) return error.SkipZigTest; // Windows returns FileBusy if attempting to remove an open dir var tmp = tmpDir(.{}); defer tmp.cleanup(); @@ -1182,7 +1182,7 @@ test "makepath relative walks" { try tmp.dir.makePath(relPath); // How .. is handled is different on Windows than non-Windows - switch (builtin.os.tag) { + switch (native_os) { .windows => { // On Windows, .. is resolved before passing the path to NtCreateFile, // meaning everything except `first/C` drops out. @@ -1248,12 +1248,12 @@ test "max file name component lengths" { var tmp = tmpDir(.{ .iterate = true }); defer tmp.cleanup(); - if (builtin.os.tag == .windows) { + if (native_os == .windows) { // U+FFFF is the character with the largest code point that is encoded as a single // UTF-16 code unit, so Windows allows for NAME_MAX of them. - const maxed_windows_filename = ("\u{FFFF}".*) ** std.os.windows.NAME_MAX; + const maxed_windows_filename = ("\u{FFFF}".*) ** windows.NAME_MAX; try testFilenameLimits(tmp.dir, &maxed_windows_filename); - } else if (builtin.os.tag == .wasi) { + } else if (native_os == .wasi) { // On WASI, the maxed filename depends on the host OS, so in order for this test to // work on any host, we need to use a length that will work for all platforms // (i.e. the minimum MAX_NAME_BYTES of all supported platforms). @@ -1274,7 +1274,7 @@ test "writev, readv" { var buf1: [line1.len]u8 = undefined; var buf2: [line2.len]u8 = undefined; - var write_vecs = [_]std.os.iovec_const{ + var write_vecs = [_]posix.iovec_const{ .{ .iov_base = line1, .iov_len = line1.len, @@ -1284,7 +1284,7 @@ test "writev, readv" { .iov_len = line2.len, }, }; - var read_vecs = [_]std.os.iovec{ + var read_vecs = [_]posix.iovec{ .{ .iov_base = &buf2, .iov_len = buf2.len, @@ -1316,7 +1316,7 @@ test "pwritev, preadv" { var buf1: [line1.len]u8 = undefined; var buf2: [line2.len]u8 = undefined; - var write_vecs = [_]std.os.iovec_const{ + var write_vecs = [_]posix.iovec_const{ .{ .iov_base = line1, .iov_len = line1.len, @@ -1326,7 +1326,7 @@ test "pwritev, preadv" { .iov_len = line2.len, }, }; - var read_vecs = [_]std.os.iovec{ + var read_vecs = [_]posix.iovec{ .{ .iov_base = &buf2, .iov_len = buf2.len, @@ -1376,7 +1376,7 @@ test "sendfile" { const line1 = "line1\n"; const line2 = "second line\n"; - var vecs = [_]std.os.iovec_const{ + var vecs = [_]posix.iovec_const{ .{ .iov_base = line1, .iov_len = line1.len, @@ -1399,7 +1399,7 @@ test "sendfile" { const header2 = "second header\n"; const trailer1 = "trailer1\n"; const trailer2 = "second trailer\n"; - var hdtr = [_]std.os.iovec_const{ + var hdtr = [_]posix.iovec_const{ .{ .iov_base = header1, .iov_len = header1.len, @@ -1510,7 +1510,7 @@ test "AtomicFile" { } test "open file with exclusive nonblocking lock twice" { - if (builtin.os.tag == .wasi) return error.SkipZigTest; + if (native_os == .wasi) return error.SkipZigTest; try testWithAllSupportedPathTypes(struct { fn impl(ctx: *TestContext) !void { @@ -1526,7 +1526,7 @@ test "open file with exclusive nonblocking lock twice" { } test "open file with shared and exclusive nonblocking lock" { - if (builtin.os.tag == .wasi) return error.SkipZigTest; + if (native_os == .wasi) return error.SkipZigTest; try testWithAllSupportedPathTypes(struct { fn impl(ctx: *TestContext) !void { @@ -1542,7 +1542,7 @@ test "open file with shared and exclusive nonblocking lock" { } test "open file with exclusive and shared nonblocking lock" { - if (builtin.os.tag == .wasi) return error.SkipZigTest; + if (native_os == .wasi) return error.SkipZigTest; try testWithAllSupportedPathTypes(struct { fn impl(ctx: *TestContext) !void { @@ -1601,7 +1601,7 @@ test "open file with exclusive lock twice, make sure second lock waits" { } test "open file with exclusive nonblocking lock twice (absolute paths)" { - if (builtin.os.tag == .wasi) return error.SkipZigTest; + if (native_os == .wasi) return error.SkipZigTest; var random_bytes: [12]u8 = undefined; std.crypto.random.bytes(&random_bytes); @@ -1634,7 +1634,7 @@ test "open file with exclusive nonblocking lock twice (absolute paths)" { } test "walker" { - if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest; + if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest; var tmp = tmpDir(.{ .iterate = true }); defer tmp.cleanup(); @@ -1687,7 +1687,7 @@ test "walker" { } test "walker without fully iterating" { - if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest; + if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest; var tmp = tmpDir(.{ .iterate = true }); defer tmp.cleanup(); @@ -1710,9 +1710,9 @@ test "walker without fully iterating" { } test "'.' and '..' in fs.Dir functions" { - if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest; + if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest; - if (builtin.os.tag == .windows and builtin.cpu.arch == .aarch64) { + if (native_os == .windows and builtin.cpu.arch == .aarch64) { // https://github.com/ziglang/zig/issues/17134 return error.SkipZigTest; } @@ -1750,7 +1750,7 @@ test "'.' and '..' in fs.Dir functions" { } test "'.' and '..' in absolute functions" { - if (builtin.os.tag == .wasi) return error.SkipZigTest; + if (native_os == .wasi) return error.SkipZigTest; var tmp = tmpDir(.{}); defer tmp.cleanup(); @@ -1794,7 +1794,7 @@ test "'.' and '..' in absolute functions" { } test "chmod" { - if (builtin.os.tag == .windows or builtin.os.tag == .wasi) + if (native_os == .windows or native_os == .wasi) return error.SkipZigTest; var tmp = tmpDir(.{}); @@ -1816,7 +1816,7 @@ test "chmod" { } test "chown" { - if (builtin.os.tag == .windows or builtin.os.tag == .wasi) + if (native_os == .windows or native_os == .wasi) return error.SkipZigTest; var tmp = tmpDir(.{}); @@ -1849,7 +1849,7 @@ test "File.Metadata" { } test "File.Permissions" { - if (builtin.os.tag == .wasi) + if (native_os == .wasi) return error.SkipZigTest; var tmp = tmpDir(.{}); @@ -1875,7 +1875,7 @@ test "File.Permissions" { } test "File.PermissionsUnix" { - if (builtin.os.tag == .windows or builtin.os.tag == .wasi) + if (native_os == .windows or native_os == .wasi) return error.SkipZigTest; var tmp = tmpDir(.{}); @@ -1910,7 +1910,7 @@ test "File.PermissionsUnix" { } test "delete a read-only file on windows" { - if (builtin.os.tag != .windows) + if (native_os != .windows) return error.SkipZigTest; var tmp = testing.tmpDir(.{}); @@ -1941,7 +1941,7 @@ test "delete a read-only file on windows" { } test "delete a setAsCwd directory on Windows" { - if (builtin.os.tag != .windows) return error.SkipZigTest; + if (native_os != .windows) return error.SkipZigTest; var tmp = tmpDir(.{}); // Set tmp dir as current working directory. @@ -1956,7 +1956,7 @@ test "delete a setAsCwd directory on Windows" { } test "invalid UTF-8/WTF-8 paths" { - const expected_err = switch (builtin.os.tag) { + const expected_err = switch (native_os) { .wasi => error.InvalidUtf8, .windows => error.InvalidWtf8, else => return error.SkipZigTest, @@ -1993,13 +1993,13 @@ test "invalid UTF-8/WTF-8 paths" { try testing.expectError(expected_err, ctx.dir.symLink(invalid_path, invalid_path, .{})); try testing.expectError(expected_err, ctx.dir.symLinkZ(invalid_path, invalid_path, .{})); - if (builtin.os.tag == .wasi) { + if (native_os == .wasi) { try testing.expectError(expected_err, ctx.dir.symLinkWasi(invalid_path, invalid_path, .{})); } try testing.expectError(expected_err, ctx.dir.readLink(invalid_path, &[_]u8{})); try testing.expectError(expected_err, ctx.dir.readLinkZ(invalid_path, &[_]u8{})); - if (builtin.os.tag == .wasi) { + if (native_os == .wasi) { try testing.expectError(expected_err, ctx.dir.readLinkWasi(invalid_path, &[_]u8{})); } @@ -2023,7 +2023,7 @@ test "invalid UTF-8/WTF-8 paths" { try testing.expectError(expected_err, ctx.dir.statFile(invalid_path)); - if (builtin.os.tag != .wasi) { + if (native_os != .wasi) { try testing.expectError(expected_err, ctx.dir.realpath(invalid_path, &[_]u8{})); try testing.expectError(expected_err, ctx.dir.realpathZ(invalid_path, &[_]u8{})); try testing.expectError(expected_err, ctx.dir.realpathAlloc(testing.allocator, invalid_path)); @@ -2032,7 +2032,7 @@ test "invalid UTF-8/WTF-8 paths" { try testing.expectError(expected_err, fs.rename(ctx.dir, invalid_path, ctx.dir, invalid_path)); try testing.expectError(expected_err, fs.renameZ(ctx.dir, invalid_path, ctx.dir, invalid_path)); - if (builtin.os.tag != .wasi and ctx.path_type != .relative) { + if (native_os != .wasi and ctx.path_type != .relative) { try testing.expectError(expected_err, fs.updateFileAbsolute(invalid_path, invalid_path, .{})); try testing.expectError(expected_err, fs.copyFileAbsolute(invalid_path, invalid_path, .{})); try testing.expectError(expected_err, fs.makeDirAbsolute(invalid_path)); diff --git a/lib/std/hash/benchmark.zig b/lib/std/hash/benchmark.zig index 35e96a655baf..518b4ca99e6f 100644 --- a/lib/std/hash/benchmark.zig +++ b/lib/std/hash/benchmark.zig @@ -367,7 +367,7 @@ pub fn main() !void { i += 1; if (i == args.len) { usage(); - std.os.exit(1); + std.process.exit(1); } seed = try std.fmt.parseUnsigned(u32, args[i], 10); @@ -376,7 +376,7 @@ pub fn main() !void { i += 1; if (i == args.len) { usage(); - std.os.exit(1); + std.process.exit(1); } filter = args[i]; @@ -384,7 +384,7 @@ pub fn main() !void { i += 1; if (i == args.len) { usage(); - std.os.exit(1); + std.process.exit(1); } const c = try std.fmt.parseUnsigned(usize, args[i], 10); @@ -393,13 +393,13 @@ pub fn main() !void { i += 1; if (i == args.len) { usage(); - std.os.exit(1); + std.process.exit(1); } key_size = try std.fmt.parseUnsigned(usize, args[i], 10); if (key_size.? > block_size) { try stdout.print("key_size cannot exceed block size of {}\n", .{block_size}); - std.os.exit(1); + std.process.exit(1); } } else if (std.mem.eql(u8, args[i], "--iterative-only")) { test_iterative_only = true; @@ -410,7 +410,7 @@ pub fn main() !void { return; } else { usage(); - std.os.exit(1); + std.process.exit(1); } } diff --git a/lib/std/heap.zig b/lib/std/heap.zig index e49b3c1e5c8f..9b99f7e1d9af 100644 --- a/lib/std/heap.zig +++ b/lib/std/heap.zig @@ -4,9 +4,9 @@ const root = @import("root"); const assert = std.debug.assert; const testing = std.testing; const mem = std.mem; -const os = std.os; const c = std.c; const Allocator = std.mem.Allocator; +const windows = std.os.windows; pub const LoggingAllocator = @import("heap/logging_allocator.zig").LoggingAllocator; pub const loggingAllocator = @import("heap/logging_allocator.zig").loggingAllocator; @@ -263,7 +263,7 @@ pub const HeapAllocator = switch (builtin.os.tag) { .windows => struct { heap_handle: ?HeapHandle, - const HeapHandle = os.windows.HANDLE; + const HeapHandle = windows.HANDLE; pub fn init() HeapAllocator { return HeapAllocator{ @@ -284,7 +284,7 @@ pub const HeapAllocator = switch (builtin.os.tag) { pub fn deinit(self: *HeapAllocator) void { if (self.heap_handle) |heap_handle| { - os.windows.HeapDestroy(heap_handle); + windows.HeapDestroy(heap_handle); } } @@ -305,13 +305,13 @@ pub const HeapAllocator = switch (builtin.os.tag) { const amt = n + ptr_align - 1 + @sizeOf(usize); const optional_heap_handle = @atomicLoad(?HeapHandle, &self.heap_handle, .seq_cst); const heap_handle = optional_heap_handle orelse blk: { - const options = if (builtin.single_threaded) os.windows.HEAP_NO_SERIALIZE else 0; - const hh = os.windows.kernel32.HeapCreate(options, amt, 0) orelse return null; + const options = if (builtin.single_threaded) windows.HEAP_NO_SERIALIZE else 0; + const hh = windows.kernel32.HeapCreate(options, amt, 0) orelse return null; const other_hh = @cmpxchgStrong(?HeapHandle, &self.heap_handle, null, hh, .seq_cst, .seq_cst) orelse break :blk hh; - os.windows.HeapDestroy(hh); + windows.HeapDestroy(hh); break :blk other_hh.?; // can't be null because of the cmpxchg }; - const ptr = os.windows.kernel32.HeapAlloc(heap_handle, 0, amt) orelse return null; + const ptr = windows.kernel32.HeapAlloc(heap_handle, 0, amt) orelse return null; const root_addr = @intFromPtr(ptr); const aligned_addr = mem.alignForward(usize, root_addr, ptr_align); const buf = @as([*]u8, @ptrFromInt(aligned_addr))[0..n]; @@ -333,9 +333,9 @@ pub const HeapAllocator = switch (builtin.os.tag) { const root_addr = getRecordPtr(buf).*; const align_offset = @intFromPtr(buf.ptr) - root_addr; const amt = align_offset + new_size + @sizeOf(usize); - const new_ptr = os.windows.kernel32.HeapReAlloc( + const new_ptr = windows.kernel32.HeapReAlloc( self.heap_handle.?, - os.windows.HEAP_REALLOC_IN_PLACE_ONLY, + windows.HEAP_REALLOC_IN_PLACE_ONLY, @as(*anyopaque, @ptrFromInt(root_addr)), amt, ) orelse return false; @@ -353,7 +353,7 @@ pub const HeapAllocator = switch (builtin.os.tag) { _ = log2_buf_align; _ = return_address; const self: *HeapAllocator = @ptrCast(@alignCast(ctx)); - os.windows.HeapFree(self.heap_handle.?, 0, @as(*anyopaque, @ptrFromInt(getRecordPtr(buf).*))); + windows.HeapFree(self.heap_handle.?, 0, @as(*anyopaque, @ptrFromInt(getRecordPtr(buf).*))); } }, else => @compileError("Unsupported OS"), diff --git a/lib/std/heap/PageAllocator.zig b/lib/std/heap/PageAllocator.zig index 5f8c506f24c7..67cafe48d99d 100644 --- a/lib/std/heap/PageAllocator.zig +++ b/lib/std/heap/PageAllocator.zig @@ -2,9 +2,11 @@ const std = @import("../std.zig"); const builtin = @import("builtin"); const Allocator = std.mem.Allocator; const mem = std.mem; -const os = std.os; const maxInt = std.math.maxInt; const assert = std.debug.assert; +const native_os = builtin.os.tag; +const windows = std.os.windows; +const posix = std.posix; pub const vtable = Allocator.VTable{ .alloc = alloc, @@ -19,22 +21,21 @@ fn alloc(_: *anyopaque, n: usize, log2_align: u8, ra: usize) ?[*]u8 { if (n > maxInt(usize) - (mem.page_size - 1)) return null; const aligned_len = mem.alignForward(usize, n, mem.page_size); - if (builtin.os.tag == .windows) { - const w = os.windows; - const addr = w.VirtualAlloc( + if (native_os == .windows) { + const addr = windows.VirtualAlloc( null, aligned_len, - w.MEM_COMMIT | w.MEM_RESERVE, - w.PAGE_READWRITE, + windows.MEM_COMMIT | windows.MEM_RESERVE, + windows.PAGE_READWRITE, ) catch return null; return @ptrCast(addr); } const hint = @atomicLoad(@TypeOf(std.heap.next_mmap_addr_hint), &std.heap.next_mmap_addr_hint, .unordered); - const slice = os.mmap( + const slice = posix.mmap( hint, aligned_len, - os.PROT.READ | os.PROT.WRITE, + posix.PROT.READ | posix.PROT.WRITE, .{ .TYPE = .PRIVATE, .ANONYMOUS = true }, -1, 0, @@ -56,8 +57,7 @@ fn resize( _ = return_address; const new_size_aligned = mem.alignForward(usize, new_size, mem.page_size); - if (builtin.os.tag == .windows) { - const w = os.windows; + if (native_os == .windows) { if (new_size <= buf_unaligned.len) { const base_addr = @intFromPtr(buf_unaligned.ptr); const old_addr_end = base_addr + buf_unaligned.len; @@ -65,10 +65,10 @@ fn resize( if (old_addr_end > new_addr_end) { // For shrinking that is not releasing, we will only // decommit the pages not needed anymore. - w.VirtualFree( + windows.VirtualFree( @as(*anyopaque, @ptrFromInt(new_addr_end)), old_addr_end - new_addr_end, - w.MEM_DECOMMIT, + windows.MEM_DECOMMIT, ); } return true; @@ -87,7 +87,7 @@ fn resize( if (new_size_aligned < buf_aligned_len) { const ptr = buf_unaligned.ptr + new_size_aligned; // TODO: if the next_mmap_addr_hint is within the unmapped range, update it - os.munmap(@alignCast(ptr[0 .. buf_aligned_len - new_size_aligned])); + posix.munmap(@alignCast(ptr[0 .. buf_aligned_len - new_size_aligned])); return true; } @@ -100,10 +100,10 @@ fn free(_: *anyopaque, slice: []u8, log2_buf_align: u8, return_address: usize) v _ = log2_buf_align; _ = return_address; - if (builtin.os.tag == .windows) { - os.windows.VirtualFree(slice.ptr, 0, os.windows.MEM_RELEASE); + if (native_os == .windows) { + windows.VirtualFree(slice.ptr, 0, windows.MEM_RELEASE); } else { const buf_aligned_len = mem.alignForward(usize, slice.len, mem.page_size); - os.munmap(@alignCast(slice.ptr[0..buf_aligned_len])); + posix.munmap(@alignCast(slice.ptr[0..buf_aligned_len])); } } diff --git a/lib/std/http/Client.zig b/lib/std/http/Client.zig index 339afdb96e91..0e70b839b4e0 100644 --- a/lib/std/http/Client.zig +++ b/lib/std/http/Client.zig @@ -220,7 +220,7 @@ pub const Connection = struct { pub const Protocol = enum { plain, tls }; - pub fn readvDirectTls(conn: *Connection, buffers: []std.os.iovec) ReadError!usize { + pub fn readvDirectTls(conn: *Connection, buffers: []std.posix.iovec) ReadError!usize { return conn.tls_client.readv(conn.stream, buffers) catch |err| { // https://github.com/ziglang/zig/issues/2473 if (mem.startsWith(u8, @errorName(err), "TlsAlert")) return error.TlsAlert; @@ -234,7 +234,7 @@ pub const Connection = struct { }; } - pub fn readvDirect(conn: *Connection, buffers: []std.os.iovec) ReadError!usize { + pub fn readvDirect(conn: *Connection, buffers: []std.posix.iovec) ReadError!usize { if (conn.protocol == .tls) { if (disable_tls) unreachable; @@ -252,7 +252,7 @@ pub const Connection = struct { pub fn fill(conn: *Connection) ReadError!void { if (conn.read_end != conn.read_start) return; - var iovecs = [1]std.os.iovec{ + var iovecs = [1]std.posix.iovec{ .{ .iov_base = &conn.read_buf, .iov_len = conn.read_buf.len }, }; const nread = try conn.readvDirect(&iovecs); @@ -288,7 +288,7 @@ pub const Connection = struct { return available_read; } - var iovecs = [2]std.os.iovec{ + var iovecs = [2]std.posix.iovec{ .{ .iov_base = buffer.ptr, .iov_len = buffer.len }, .{ .iov_base = &conn.read_buf, .iov_len = conn.read_buf.len }, }; @@ -1387,7 +1387,7 @@ pub fn connectTcp(client: *Client, host: []const u8, port: u16, protocol: Connec return &conn.data; } -pub const ConnectUnixError = Allocator.Error || std.os.SocketError || error{NameTooLong} || std.os.ConnectError; +pub const ConnectUnixError = Allocator.Error || std.posix.SocketError || error{NameTooLong} || std.posix.ConnectError; /// Connect to `path` as a unix domain socket. This will reuse a connection if one is already open. /// diff --git a/lib/std/io.zig b/lib/std/io.zig index df220e24898a..2fc70ca2dbf8 100644 --- a/lib/std/io.zig +++ b/lib/std/io.zig @@ -2,74 +2,76 @@ const std = @import("std.zig"); const builtin = @import("builtin"); const root = @import("root"); const c = std.c; +const is_windows = builtin.os.tag == .windows; +const windows = std.os.windows; +const posix = std.posix; const math = std.math; const assert = std.debug.assert; -const os = std.os; const fs = std.fs; const mem = std.mem; const meta = std.meta; const File = std.fs.File; const Allocator = std.mem.Allocator; -fn getStdOutHandle() os.fd_t { - if (builtin.os.tag == .windows) { +fn getStdOutHandle() posix.fd_t { + if (is_windows) { if (builtin.zig_backend == .stage2_aarch64) { // TODO: this is just a temporary workaround until we advance aarch64 backend further along. - return os.windows.GetStdHandle(os.windows.STD_OUTPUT_HANDLE) catch os.windows.INVALID_HANDLE_VALUE; + return windows.GetStdHandle(windows.STD_OUTPUT_HANDLE) catch windows.INVALID_HANDLE_VALUE; } - return os.windows.peb().ProcessParameters.hStdOutput; + return windows.peb().ProcessParameters.hStdOutput; } if (@hasDecl(root, "os") and @hasDecl(root.os, "io") and @hasDecl(root.os.io, "getStdOutHandle")) { return root.os.io.getStdOutHandle(); } - return os.STDOUT_FILENO; + return posix.STDOUT_FILENO; } pub fn getStdOut() File { - return File{ .handle = getStdOutHandle() }; + return .{ .handle = getStdOutHandle() }; } -fn getStdErrHandle() os.fd_t { - if (builtin.os.tag == .windows) { +fn getStdErrHandle() posix.fd_t { + if (is_windows) { if (builtin.zig_backend == .stage2_aarch64) { // TODO: this is just a temporary workaround until we advance aarch64 backend further along. - return os.windows.GetStdHandle(os.windows.STD_ERROR_HANDLE) catch os.windows.INVALID_HANDLE_VALUE; + return windows.GetStdHandle(windows.STD_ERROR_HANDLE) catch windows.INVALID_HANDLE_VALUE; } - return os.windows.peb().ProcessParameters.hStdError; + return windows.peb().ProcessParameters.hStdError; } if (@hasDecl(root, "os") and @hasDecl(root.os, "io") and @hasDecl(root.os.io, "getStdErrHandle")) { return root.os.io.getStdErrHandle(); } - return os.STDERR_FILENO; + return posix.STDERR_FILENO; } pub fn getStdErr() File { - return File{ .handle = getStdErrHandle() }; + return .{ .handle = getStdErrHandle() }; } -fn getStdInHandle() os.fd_t { - if (builtin.os.tag == .windows) { +fn getStdInHandle() posix.fd_t { + if (is_windows) { if (builtin.zig_backend == .stage2_aarch64) { // TODO: this is just a temporary workaround until we advance aarch64 backend further along. - return os.windows.GetStdHandle(os.windows.STD_INPUT_HANDLE) catch os.windows.INVALID_HANDLE_VALUE; + return windows.GetStdHandle(windows.STD_INPUT_HANDLE) catch windows.INVALID_HANDLE_VALUE; } - return os.windows.peb().ProcessParameters.hStdInput; + return windows.peb().ProcessParameters.hStdInput; } if (@hasDecl(root, "os") and @hasDecl(root.os, "io") and @hasDecl(root.os.io, "getStdInHandle")) { return root.os.io.getStdInHandle(); } - return os.STDIN_FILENO; + return posix.STDIN_FILENO; } pub fn getStdIn() File { - return File{ .handle = getStdInHandle() }; + return .{ .handle = getStdInHandle() }; } pub fn GenericReader( @@ -434,10 +436,10 @@ pub fn poll( const enum_fields = @typeInfo(StreamEnum).Enum.fields; var result: Poller(StreamEnum) = undefined; - if (builtin.os.tag == .windows) result.windows = .{ + if (is_windows) result.windows = .{ .first_read_done = false, - .overlapped = [1]os.windows.OVERLAPPED{ - mem.zeroes(os.windows.OVERLAPPED), + .overlapped = [1]windows.OVERLAPPED{ + mem.zeroes(windows.OVERLAPPED), } ** enum_fields.len, .active = .{ .count = 0, @@ -453,12 +455,12 @@ pub fn poll( .head = 0, .count = 0, }; - if (builtin.os.tag == .windows) { + if (is_windows) { result.windows.active.handles_buf[i] = @field(files, enum_fields[i].name).handle; } else { result.poll_fds[i] = .{ .fd = @field(files, enum_fields[i].name).handle, - .events = os.POLL.IN, + .events = posix.POLL.IN, .revents = undefined, }; } @@ -471,16 +473,16 @@ pub const PollFifo = std.fifo.LinearFifo(u8, .Dynamic); pub fn Poller(comptime StreamEnum: type) type { return struct { const enum_fields = @typeInfo(StreamEnum).Enum.fields; - const PollFd = if (builtin.os.tag == .windows) void else std.os.pollfd; + const PollFd = if (is_windows) void else posix.pollfd; fifos: [enum_fields.len]PollFifo, poll_fds: [enum_fields.len]PollFd, - windows: if (builtin.os.tag == .windows) struct { + windows: if (is_windows) struct { first_read_done: bool, - overlapped: [enum_fields.len]os.windows.OVERLAPPED, + overlapped: [enum_fields.len]windows.OVERLAPPED, active: struct { count: math.IntFittingRange(0, enum_fields.len), - handles_buf: [enum_fields.len]os.windows.HANDLE, + handles_buf: [enum_fields.len]windows.HANDLE, stream_map: [enum_fields.len]StreamEnum, pub fn removeAt(self: *@This(), index: u32) void { @@ -497,10 +499,10 @@ pub fn Poller(comptime StreamEnum: type) type { const Self = @This(); pub fn deinit(self: *Self) void { - if (builtin.os.tag == .windows) { + if (is_windows) { // cancel any pending IO to prevent clobbering OVERLAPPED value for (self.windows.active.handles_buf[0..self.windows.active.count]) |h| { - _ = os.windows.kernel32.CancelIo(h); + _ = windows.kernel32.CancelIo(h); } } inline for (&self.fifos) |*q| q.deinit(); @@ -508,7 +510,7 @@ pub fn Poller(comptime StreamEnum: type) type { } pub fn poll(self: *Self) !bool { - if (builtin.os.tag == .windows) { + if (is_windows) { return pollWindows(self, null); } else { return pollPosix(self, null); @@ -516,7 +518,7 @@ pub fn Poller(comptime StreamEnum: type) type { } pub fn pollTimeout(self: *Self, nanoseconds: u64) !bool { - if (builtin.os.tag == .windows) { + if (is_windows) { return pollWindows(self, nanoseconds); } else { return pollPosix(self, nanoseconds); @@ -554,39 +556,39 @@ pub fn Poller(comptime StreamEnum: type) type { while (true) { if (self.windows.active.count == 0) return false; - const status = os.windows.kernel32.WaitForMultipleObjects( + const status = windows.kernel32.WaitForMultipleObjects( self.windows.active.count, &self.windows.active.handles_buf, 0, if (nanoseconds) |ns| - @min(std.math.cast(u32, ns / std.time.ns_per_ms) orelse (os.windows.INFINITE - 1), os.windows.INFINITE - 1) + @min(std.math.cast(u32, ns / std.time.ns_per_ms) orelse (windows.INFINITE - 1), windows.INFINITE - 1) else - os.windows.INFINITE, + windows.INFINITE, ); - if (status == os.windows.WAIT_FAILED) - return os.windows.unexpectedError(os.windows.kernel32.GetLastError()); - if (status == os.windows.WAIT_TIMEOUT) + if (status == windows.WAIT_FAILED) + return windows.unexpectedError(windows.kernel32.GetLastError()); + if (status == windows.WAIT_TIMEOUT) return true; - if (status < os.windows.WAIT_OBJECT_0 or status > os.windows.WAIT_OBJECT_0 + enum_fields.len - 1) + if (status < windows.WAIT_OBJECT_0 or status > windows.WAIT_OBJECT_0 + enum_fields.len - 1) unreachable; - const active_idx = status - os.windows.WAIT_OBJECT_0; + const active_idx = status - windows.WAIT_OBJECT_0; const handle = self.windows.active.handles_buf[active_idx]; const stream_idx = @intFromEnum(self.windows.active.stream_map[active_idx]); var read_bytes: u32 = undefined; - if (0 == os.windows.kernel32.GetOverlappedResult( + if (0 == windows.kernel32.GetOverlappedResult( handle, &self.windows.overlapped[stream_idx], &read_bytes, 0, - )) switch (os.windows.kernel32.GetLastError()) { + )) switch (windows.kernel32.GetLastError()) { .BROKEN_PIPE => { self.windows.active.removeAt(active_idx); continue; }, - else => |err| return os.windows.unexpectedError(err), + else => |err| return windows.unexpectedError(err), }; self.fifos[stream_idx].update(read_bytes); @@ -611,9 +613,9 @@ pub fn Poller(comptime StreamEnum: type) type { // allocate grows exponentially. const bump_amt = 512; - const err_mask = os.POLL.ERR | os.POLL.NVAL | os.POLL.HUP; + const err_mask = posix.POLL.ERR | posix.POLL.NVAL | posix.POLL.HUP; - const events_len = try os.poll(&self.poll_fds, if (nanoseconds) |ns| + const events_len = try posix.poll(&self.poll_fds, if (nanoseconds) |ns| std.math.cast(i32, ns / std.time.ns_per_ms) orelse std.math.maxInt(i32) else -1); @@ -629,9 +631,9 @@ pub fn Poller(comptime StreamEnum: type) type { // conditions. // It's still possible to read after a POLL.HUP is received, // always check if there's some data waiting to be read first. - if (poll_fd.revents & os.POLL.IN != 0) { + if (poll_fd.revents & posix.POLL.IN != 0) { const buf = try q.writableWithSize(bump_amt); - const amt = try os.read(poll_fd.fd, buf); + const amt = try posix.read(poll_fd.fd, buf); q.update(amt); if (amt == 0) { // Remove the fd when the EOF condition is met. @@ -652,19 +654,19 @@ pub fn Poller(comptime StreamEnum: type) type { } fn windowsAsyncRead( - handle: os.windows.HANDLE, - overlapped: *os.windows.OVERLAPPED, + handle: windows.HANDLE, + overlapped: *windows.OVERLAPPED, fifo: *PollFifo, bump_amt: usize, ) !enum { pending, closed } { while (true) { const buf = try fifo.writableWithSize(bump_amt); var read_bytes: u32 = undefined; - const read_result = os.windows.kernel32.ReadFile(handle, buf.ptr, math.cast(u32, buf.len) orelse math.maxInt(u32), &read_bytes, overlapped); - if (read_result == 0) return switch (os.windows.kernel32.GetLastError()) { + const read_result = windows.kernel32.ReadFile(handle, buf.ptr, math.cast(u32, buf.len) orelse math.maxInt(u32), &read_bytes, overlapped); + if (read_result == 0) return switch (windows.kernel32.GetLastError()) { .IO_PENDING => .pending, .BROKEN_PIPE => .closed, - else => |err| os.windows.unexpectedError(err), + else => |err| windows.unexpectedError(err), }; fifo.update(read_bytes); } diff --git a/lib/std/io/c_writer.zig b/lib/std/io/c_writer.zig index ee87a28dc6c7..cbe4e7834d0f 100644 --- a/lib/std/io/c_writer.zig +++ b/lib/std/io/c_writer.zig @@ -2,7 +2,6 @@ const std = @import("../std.zig"); const builtin = @import("builtin"); const io = std.io; const testing = std.testing; -const os = std.os; pub const CWriter = io.Writer(*std.c.FILE, std.fs.File.WriteError, cWriterWrite); @@ -13,7 +12,7 @@ pub fn cWriter(c_file: *std.c.FILE) CWriter { fn cWriterWrite(c_file: *std.c.FILE, bytes: []const u8) std.fs.File.WriteError!usize { const amt_written = std.c.fwrite(bytes.ptr, 1, bytes.len, c_file); if (amt_written >= 0) return amt_written; - switch (@as(os.E, @enumFromInt(std.c._errno().*))) { + switch (@as(std.c.E, @enumFromInt(std.c._errno().*))) { .SUCCESS => unreachable, .INVAL => unreachable, .FAULT => unreachable, @@ -26,11 +25,11 @@ fn cWriterWrite(c_file: *std.c.FILE, bytes: []const u8) std.fs.File.WriteError!u .NOSPC => return error.NoSpaceLeft, .PERM => return error.AccessDenied, .PIPE => return error.BrokenPipe, - else => |err| return os.unexpectedErrno(err), + else => |err| return std.posix.unexpectedErrno(err), } } -test "C Writer" { +test cWriter { if (!builtin.link_libc or builtin.os.tag == .wasi) return error.SkipZigTest; const filename = "tmp_io_test_file.txt"; diff --git a/lib/std/net.zig b/lib/std/net.zig index e68adc4207ff..b12fb1932d8f 100644 --- a/lib/std/net.zig +++ b/lib/std/net.zig @@ -5,15 +5,16 @@ const builtin = @import("builtin"); const assert = std.debug.assert; const net = @This(); const mem = std.mem; -const os = std.os; const posix = std.posix; const fs = std.fs; const io = std.io; const native_endian = builtin.target.cpu.arch.endian(); +const native_os = builtin.os.tag; +const windows = std.os.windows; // Windows 10 added support for unix sockets in build 17063, redstone 4 is the // first release to support them. -pub const has_unix_sockets = switch (builtin.os.tag) { +pub const has_unix_sockets = switch (native_os) { .windows => builtin.os.version_range.windows.isAtLeast(.win10_rs4) orelse false, else => true, }; @@ -28,14 +29,14 @@ pub const IPParseError = error{ pub const IPv4ParseError = IPParseError || error{NonCanonical}; pub const IPv6ParseError = IPParseError || error{InvalidIpv4Mapping}; -pub const IPv6InterfaceError = os.SocketError || os.IoCtl_SIOCGIFINDEX_Error || error{NameTooLong}; +pub const IPv6InterfaceError = posix.SocketError || posix.IoCtl_SIOCGIFINDEX_Error || error{NameTooLong}; pub const IPv6ResolveError = IPv6ParseError || IPv6InterfaceError; pub const Address = extern union { - any: os.sockaddr, + any: posix.sockaddr, in: Ip4Address, in6: Ip6Address, - un: if (has_unix_sockets) os.sockaddr.un else void, + un: if (has_unix_sockets) posix.sockaddr.un else void, /// Parse the given IP address string into an Address value. /// It is recommended to use `resolveIp` instead, to handle @@ -85,38 +86,38 @@ pub const Address = extern union { return error.InvalidIPAddressFormat; } - pub fn parseExpectingFamily(name: []const u8, family: os.sa_family_t, port: u16) !Address { + pub fn parseExpectingFamily(name: []const u8, family: posix.sa_family_t, port: u16) !Address { switch (family) { - os.AF.INET => return parseIp4(name, port), - os.AF.INET6 => return parseIp6(name, port), - os.AF.UNSPEC => return parseIp(name, port), + posix.AF.INET => return parseIp4(name, port), + posix.AF.INET6 => return parseIp6(name, port), + posix.AF.UNSPEC => return parseIp(name, port), else => unreachable, } } pub fn parseIp6(buf: []const u8, port: u16) IPv6ParseError!Address { - return Address{ .in6 = try Ip6Address.parse(buf, port) }; + return .{ .in6 = try Ip6Address.parse(buf, port) }; } pub fn resolveIp6(buf: []const u8, port: u16) IPv6ResolveError!Address { - return Address{ .in6 = try Ip6Address.resolve(buf, port) }; + return .{ .in6 = try Ip6Address.resolve(buf, port) }; } pub fn parseIp4(buf: []const u8, port: u16) IPv4ParseError!Address { - return Address{ .in = try Ip4Address.parse(buf, port) }; + return .{ .in = try Ip4Address.parse(buf, port) }; } pub fn initIp4(addr: [4]u8, port: u16) Address { - return Address{ .in = Ip4Address.init(addr, port) }; + return .{ .in = Ip4Address.init(addr, port) }; } pub fn initIp6(addr: [16]u8, port: u16, flowinfo: u32, scope_id: u32) Address { - return Address{ .in6 = Ip6Address.init(addr, port, flowinfo, scope_id) }; + return .{ .in6 = Ip6Address.init(addr, port, flowinfo, scope_id) }; } pub fn initUnix(path: []const u8) !Address { - var sock_addr = os.sockaddr.un{ - .family = os.AF.UNIX, + var sock_addr = posix.sockaddr.un{ + .family = posix.AF.UNIX, .path = undefined, }; @@ -133,8 +134,8 @@ pub const Address = extern union { /// Asserts that the address is ip4 or ip6. pub fn getPort(self: Address) u16 { return switch (self.any.family) { - os.AF.INET => self.in.getPort(), - os.AF.INET6 => self.in6.getPort(), + posix.AF.INET => self.in.getPort(), + posix.AF.INET6 => self.in6.getPort(), else => unreachable, }; } @@ -143,8 +144,8 @@ pub const Address = extern union { /// Asserts that the address is ip4 or ip6. pub fn setPort(self: *Address, port: u16) void { switch (self.any.family) { - os.AF.INET => self.in.setPort(port), - os.AF.INET6 => self.in6.setPort(port), + posix.AF.INET => self.in.setPort(port), + posix.AF.INET6 => self.in6.setPort(port), else => unreachable, } } @@ -152,10 +153,10 @@ pub const Address = extern union { /// Asserts that `addr` is an IP address. /// This function will read past the end of the pointer, with a size depending /// on the address family. - pub fn initPosix(addr: *align(4) const os.sockaddr) Address { + pub fn initPosix(addr: *align(4) const posix.sockaddr) Address { switch (addr.family) { - os.AF.INET => return Address{ .in = Ip4Address{ .sa = @as(*const os.sockaddr.in, @ptrCast(addr)).* } }, - os.AF.INET6 => return Address{ .in6 = Ip6Address{ .sa = @as(*const os.sockaddr.in6, @ptrCast(addr)).* } }, + posix.AF.INET => return Address{ .in = Ip4Address{ .sa = @as(*const posix.sockaddr.in, @ptrCast(addr)).* } }, + posix.AF.INET6 => return Address{ .in6 = Ip6Address{ .sa = @as(*const posix.sockaddr.in6, @ptrCast(addr)).* } }, else => unreachable, } } @@ -168,9 +169,9 @@ pub const Address = extern union { ) !void { if (fmt.len != 0) std.fmt.invalidFmtError(fmt, self); switch (self.any.family) { - os.AF.INET => try self.in.format(fmt, options, out_stream), - os.AF.INET6 => try self.in6.format(fmt, options, out_stream), - os.AF.UNIX => { + posix.AF.INET => try self.in.format(fmt, options, out_stream), + posix.AF.INET6 => try self.in6.format(fmt, options, out_stream), + posix.AF.UNIX => { if (!has_unix_sockets) { unreachable; } @@ -187,11 +188,11 @@ pub const Address = extern union { return mem.eql(u8, a_bytes, b_bytes); } - pub fn getOsSockLen(self: Address) os.socklen_t { + pub fn getOsSockLen(self: Address) posix.socklen_t { switch (self.any.family) { - os.AF.INET => return self.in.getOsSockLen(), - os.AF.INET6 => return self.in6.getOsSockLen(), - os.AF.UNIX => { + posix.AF.INET => return self.in.getOsSockLen(), + posix.AF.INET6 => return self.in6.getOsSockLen(), + posix.AF.UNIX => { if (!has_unix_sockets) { unreachable; } @@ -204,7 +205,7 @@ pub const Address = extern union { // provide the full buffer size (e.g. getsockname, getpeername, recvfrom, accept). // // To access the path, std.mem.sliceTo(&address.un.path, 0) should be used. - return @as(os.socklen_t, @intCast(@sizeOf(os.sockaddr.un))); + return @as(posix.socklen_t, @intCast(@sizeOf(posix.sockaddr.un))); }, else => unreachable, @@ -247,7 +248,7 @@ pub const Address = extern union { posix.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1)), ); - switch (builtin.os.tag) { + switch (native_os) { .windows => {}, else => try posix.setsockopt( sockfd, @@ -267,7 +268,7 @@ pub const Address = extern union { }; pub const Ip4Address = extern struct { - sa: os.sockaddr.in, + sa: posix.sockaddr.in, pub fn parse(buf: []const u8, port: u16) IPv4ParseError!Ip4Address { var result = Ip4Address{ @@ -330,7 +331,7 @@ pub const Ip4Address = extern struct { pub fn init(addr: [4]u8, port: u16) Ip4Address { return Ip4Address{ - .sa = os.sockaddr.in{ + .sa = posix.sockaddr.in{ .port = mem.nativeToBig(u16, port), .addr = @as(*align(1) const u32, @ptrCast(&addr)).*, }, @@ -367,21 +368,21 @@ pub const Ip4Address = extern struct { }); } - pub fn getOsSockLen(self: Ip4Address) os.socklen_t { + pub fn getOsSockLen(self: Ip4Address) posix.socklen_t { _ = self; - return @sizeOf(os.sockaddr.in); + return @sizeOf(posix.sockaddr.in); } }; pub const Ip6Address = extern struct { - sa: os.sockaddr.in6, + sa: posix.sockaddr.in6, /// Parse a given IPv6 address string into an Address. /// Assumes the Scope ID of the address is fully numeric. /// For non-numeric addresses, see `resolveIp6`. pub fn parse(buf: []const u8, port: u16) IPv6ParseError!Ip6Address { var result = Ip6Address{ - .sa = os.sockaddr.in6{ + .sa = posix.sockaddr.in6{ .scope_id = 0, .port = mem.nativeToBig(u16, port), .flowinfo = 0, @@ -499,7 +500,7 @@ pub const Ip6Address = extern struct { pub fn resolve(buf: []const u8, port: u16) IPv6ResolveError!Ip6Address { // TODO: Unify the implementations of resolveIp6 and parseIp6. var result = Ip6Address{ - .sa = os.sockaddr.in6{ + .sa = posix.sockaddr.in6{ .scope_id = 0, .port = mem.nativeToBig(u16, port), .flowinfo = 0, @@ -516,7 +517,7 @@ pub const Ip6Address = extern struct { var abbrv = false; var scope_id = false; - var scope_id_value: [os.IFNAMESIZE - 1]u8 = undefined; + var scope_id_value: [posix.IFNAMESIZE - 1]u8 = undefined; var scope_id_index: usize = 0; for (buf, 0..) |c, i| { @@ -632,7 +633,7 @@ pub const Ip6Address = extern struct { pub fn init(addr: [16]u8, port: u16, flowinfo: u32, scope_id: u32) Ip6Address { return Ip6Address{ - .sa = os.sockaddr.in6{ + .sa = posix.sockaddr.in6{ .addr = addr, .port = mem.nativeToBig(u16, port), .flowinfo = flowinfo, @@ -702,51 +703,51 @@ pub const Ip6Address = extern struct { try std.fmt.format(out_stream, "]:{}", .{port}); } - pub fn getOsSockLen(self: Ip6Address) os.socklen_t { + pub fn getOsSockLen(self: Ip6Address) posix.socklen_t { _ = self; - return @sizeOf(os.sockaddr.in6); + return @sizeOf(posix.sockaddr.in6); } }; pub fn connectUnixSocket(path: []const u8) !Stream { const opt_non_block = 0; - const sockfd = try os.socket( - os.AF.UNIX, - os.SOCK.STREAM | os.SOCK.CLOEXEC | opt_non_block, + const sockfd = try posix.socket( + posix.AF.UNIX, + posix.SOCK.STREAM | posix.SOCK.CLOEXEC | opt_non_block, 0, ); errdefer Stream.close(.{ .handle = sockfd }); var addr = try std.net.Address.initUnix(path); - try os.connect(sockfd, &addr.any, addr.getOsSockLen()); + try posix.connect(sockfd, &addr.any, addr.getOsSockLen()); - return Stream{ .handle = sockfd }; + return .{ .handle = sockfd }; } fn if_nametoindex(name: []const u8) IPv6InterfaceError!u32 { - if (builtin.target.os.tag == .linux) { - var ifr: os.ifreq = undefined; - const sockfd = try os.socket(os.AF.UNIX, os.SOCK.DGRAM | os.SOCK.CLOEXEC, 0); + if (native_os == .linux) { + var ifr: posix.ifreq = undefined; + const sockfd = try posix.socket(posix.AF.UNIX, posix.SOCK.DGRAM | posix.SOCK.CLOEXEC, 0); defer Stream.close(.{ .handle = sockfd }); @memcpy(ifr.ifrn.name[0..name.len], name); ifr.ifrn.name[name.len] = 0; // TODO investigate if this needs to be integrated with evented I/O. - try os.ioctl_SIOCGIFINDEX(sockfd, &ifr); + try posix.ioctl_SIOCGIFINDEX(sockfd, &ifr); - return @as(u32, @bitCast(ifr.ifru.ivalue)); + return @bitCast(ifr.ifru.ivalue); } - if (comptime builtin.target.os.tag.isDarwin()) { - if (name.len >= os.IFNAMESIZE) + if (native_os.isDarwin()) { + if (name.len >= posix.IFNAMESIZE) return error.NameTooLong; - var if_name: [os.IFNAMESIZE:0]u8 = undefined; + var if_name: [posix.IFNAMESIZE:0]u8 = undefined; @memcpy(if_name[0..name.len], name); if_name[name.len] = 0; const if_slice = if_name[0..name.len :0]; - const index = os.system.if_nametoindex(if_slice); + const index = std.c.if_nametoindex(if_slice); if (index == 0) return error.InterfaceNotFound; return @as(u32, @bitCast(index)); @@ -786,24 +787,24 @@ pub fn tcpConnectToHost(allocator: mem.Allocator, name: []const u8, port: u16) T else => return err, }; } - return std.os.ConnectError.ConnectionRefused; + return posix.ConnectError.ConnectionRefused; } -pub const TcpConnectToAddressError = std.os.SocketError || std.os.ConnectError; +pub const TcpConnectToAddressError = posix.SocketError || posix.ConnectError; pub fn tcpConnectToAddress(address: Address) TcpConnectToAddressError!Stream { const nonblock = 0; - const sock_flags = os.SOCK.STREAM | nonblock | - (if (builtin.target.os.tag == .windows) 0 else os.SOCK.CLOEXEC); - const sockfd = try os.socket(address.any.family, sock_flags, os.IPPROTO.TCP); + const sock_flags = posix.SOCK.STREAM | nonblock | + (if (native_os == .windows) 0 else posix.SOCK.CLOEXEC); + const sockfd = try posix.socket(address.any.family, sock_flags, posix.IPPROTO.TCP); errdefer Stream.close(.{ .handle = sockfd }); - try os.connect(sockfd, &address.any, address.getOsSockLen()); + try posix.connect(sockfd, &address.any, address.getOsSockLen()); return Stream{ .handle = sockfd }; } -const GetAddressListError = std.mem.Allocator.Error || std.fs.File.OpenError || std.fs.File.ReadError || std.os.SocketError || std.os.BindError || std.os.SetSockOptError || error{ +const GetAddressListError = std.mem.Allocator.Error || std.fs.File.OpenError || std.fs.File.ReadError || posix.SocketError || posix.BindError || posix.SetSockOptError || error{ // TODO: break this up into error sets from the various underlying functions TemporaryNameServerFailure, @@ -844,30 +845,30 @@ pub fn getAddressList(allocator: mem.Allocator, name: []const u8, port: u16) Get const arena = result.arena.allocator(); errdefer result.deinit(); - if (builtin.target.os.tag == .windows) { + if (native_os == .windows) { const name_c = try allocator.dupeZ(u8, name); defer allocator.free(name_c); const port_c = try std.fmt.allocPrintZ(allocator, "{}", .{port}); defer allocator.free(port_c); - const ws2_32 = os.windows.ws2_32; - const hints = os.addrinfo{ + const ws2_32 = windows.ws2_32; + const hints = posix.addrinfo{ .flags = ws2_32.AI.NUMERICSERV, - .family = os.AF.UNSPEC, - .socktype = os.SOCK.STREAM, - .protocol = os.IPPROTO.TCP, + .family = posix.AF.UNSPEC, + .socktype = posix.SOCK.STREAM, + .protocol = posix.IPPROTO.TCP, .canonname = null, .addr = null, .addrlen = 0, .next = null, }; - var res: ?*os.addrinfo = null; + var res: ?*posix.addrinfo = null; var first = true; while (true) { const rc = ws2_32.getaddrinfo(name_c.ptr, port_c.ptr, &hints, &res); - switch (@as(os.windows.ws2_32.WinsockError, @enumFromInt(@as(u16, @intCast(rc))))) { - @as(os.windows.ws2_32.WinsockError, @enumFromInt(0)) => break, + switch (@as(windows.ws2_32.WinsockError, @enumFromInt(@as(u16, @intCast(rc))))) { + @as(windows.ws2_32.WinsockError, @enumFromInt(0)) => break, .WSATRY_AGAIN => return error.TemporaryNameServerFailure, .WSANO_RECOVERY => return error.NameServerFailure, .WSAEAFNOSUPPORT => return error.AddressFamilyNotSupported, @@ -879,10 +880,10 @@ pub fn getAddressList(allocator: mem.Allocator, name: []const u8, port: u16) Get .WSANOTINITIALISED => { if (!first) return error.Unexpected; first = false; - try os.windows.callWSAStartup(); + try windows.callWSAStartup(); continue; }, - else => |err| return os.windows.unexpectedWSAError(err), + else => |err| return windows.unexpectedWSAError(err), } } defer ws2_32.freeaddrinfo(res); @@ -923,18 +924,18 @@ pub fn getAddressList(allocator: mem.Allocator, name: []const u8, port: u16) Get const port_c = try std.fmt.allocPrintZ(allocator, "{}", .{port}); defer allocator.free(port_c); - const sys = if (builtin.target.os.tag == .windows) os.windows.ws2_32 else os.system; - const hints = os.addrinfo{ + const sys = if (native_os == .windows) windows.ws2_32 else posix.system; + const hints = posix.addrinfo{ .flags = sys.AI.NUMERICSERV, - .family = os.AF.UNSPEC, - .socktype = os.SOCK.STREAM, - .protocol = os.IPPROTO.TCP, + .family = posix.AF.UNSPEC, + .socktype = posix.SOCK.STREAM, + .protocol = posix.IPPROTO.TCP, .canonname = null, .addr = null, .addrlen = 0, .next = null, }; - var res: ?*os.addrinfo = null; + var res: ?*posix.addrinfo = null; switch (sys.getaddrinfo(name_c.ptr, port_c.ptr, &hints, &res)) { @as(sys.EAI, @enumFromInt(0)) => {}, .ADDRFAMILY => return error.HostLacksNetworkAddresses, @@ -947,8 +948,8 @@ pub fn getAddressList(allocator: mem.Allocator, name: []const u8, port: u16) Get .NONAME => return error.UnknownHostName, .SERVICE => return error.ServiceUnavailable, .SOCKTYPE => unreachable, // Invalid socket type requested in hints - .SYSTEM => switch (os.errno(-1)) { - else => |e| return os.unexpectedErrno(e), + .SYSTEM => switch (posix.errno(-1)) { + else => |e| return posix.unexpectedErrno(e), }, else => unreachable, } @@ -983,9 +984,9 @@ pub fn getAddressList(allocator: mem.Allocator, name: []const u8, port: u16) Get return result; } - if (builtin.target.os.tag == .linux) { + if (native_os == .linux) { const flags = std.c.AI.NUMERICSERV; - const family = os.AF.UNSPEC; + const family = posix.AF.UNSPEC; var lookup_addrs = std.ArrayList(LookupAddr).init(allocator); defer lookup_addrs.deinit(); @@ -1026,7 +1027,7 @@ fn linuxLookupName( addrs: *std.ArrayList(LookupAddr), canon: *std.ArrayList(u8), opt_name: ?[]const u8, - family: os.sa_family_t, + family: posix.sa_family_t, flags: u32, port: u16, ) !void { @@ -1066,9 +1067,9 @@ fn linuxLookupName( // No further processing is needed if there are fewer than 2 // results or if there are only IPv4 results. - if (addrs.items.len == 1 or family == os.AF.INET) return; + if (addrs.items.len == 1 or family == posix.AF.INET) return; const all_ip4 = for (addrs.items) |addr| { - if (addr.addr.any.family != os.AF.INET) break false; + if (addr.addr.any.family != posix.AF.INET) break false; } else true; if (all_ip4) return; @@ -1081,42 +1082,42 @@ fn linuxLookupName( // A more idiomatic "ziggy" implementation would be welcome. for (addrs.items, 0..) |*addr, i| { var key: i32 = 0; - var sa6: os.sockaddr.in6 = undefined; - @memset(@as([*]u8, @ptrCast(&sa6))[0..@sizeOf(os.sockaddr.in6)], 0); - var da6 = os.sockaddr.in6{ - .family = os.AF.INET6, + var sa6: posix.sockaddr.in6 = undefined; + @memset(@as([*]u8, @ptrCast(&sa6))[0..@sizeOf(posix.sockaddr.in6)], 0); + var da6 = posix.sockaddr.in6{ + .family = posix.AF.INET6, .scope_id = addr.addr.in6.sa.scope_id, .port = 65535, .flowinfo = 0, .addr = [1]u8{0} ** 16, }; - var sa4: os.sockaddr.in = undefined; - @memset(@as([*]u8, @ptrCast(&sa4))[0..@sizeOf(os.sockaddr.in)], 0); - var da4 = os.sockaddr.in{ - .family = os.AF.INET, + var sa4: posix.sockaddr.in = undefined; + @memset(@as([*]u8, @ptrCast(&sa4))[0..@sizeOf(posix.sockaddr.in)], 0); + var da4 = posix.sockaddr.in{ + .family = posix.AF.INET, .port = 65535, .addr = 0, .zero = [1]u8{0} ** 8, }; - var sa: *align(4) os.sockaddr = undefined; - var da: *align(4) os.sockaddr = undefined; - var salen: os.socklen_t = undefined; - var dalen: os.socklen_t = undefined; - if (addr.addr.any.family == os.AF.INET6) { + var sa: *align(4) posix.sockaddr = undefined; + var da: *align(4) posix.sockaddr = undefined; + var salen: posix.socklen_t = undefined; + var dalen: posix.socklen_t = undefined; + if (addr.addr.any.family == posix.AF.INET6) { da6.addr = addr.addr.in6.sa.addr; da = @ptrCast(&da6); - dalen = @sizeOf(os.sockaddr.in6); + dalen = @sizeOf(posix.sockaddr.in6); sa = @ptrCast(&sa6); - salen = @sizeOf(os.sockaddr.in6); + salen = @sizeOf(posix.sockaddr.in6); } else { sa6.addr[0..12].* = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff".*; da6.addr[0..12].* = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff".*; mem.writeInt(u32, da6.addr[12..], addr.addr.in.sa.addr, native_endian); da4.addr = addr.addr.in.sa.addr; da = @ptrCast(&da4); - dalen = @sizeOf(os.sockaddr.in); + dalen = @sizeOf(posix.sockaddr.in); sa = @ptrCast(&sa4); - salen = @sizeOf(os.sockaddr.in); + salen = @sizeOf(posix.sockaddr.in); } const dpolicy = policyOf(da6.addr); const dscope: i32 = scopeOf(da6.addr); @@ -1124,13 +1125,13 @@ fn linuxLookupName( const dprec: i32 = dpolicy.prec; const MAXADDRS = 3; var prefixlen: i32 = 0; - const sock_flags = os.SOCK.DGRAM | os.SOCK.CLOEXEC; - if (os.socket(addr.addr.any.family, sock_flags, os.IPPROTO.UDP)) |fd| syscalls: { + const sock_flags = posix.SOCK.DGRAM | posix.SOCK.CLOEXEC; + if (posix.socket(addr.addr.any.family, sock_flags, posix.IPPROTO.UDP)) |fd| syscalls: { defer Stream.close(.{ .handle = fd }); - os.connect(fd, da, dalen) catch break :syscalls; + posix.connect(fd, da, dalen) catch break :syscalls; key |= DAS_USABLE; - os.getsockname(fd, sa, &salen) catch break :syscalls; - if (addr.addr.any.family == os.AF.INET) { + posix.getsockname(fd, sa, &salen) catch break :syscalls; + if (addr.addr.any.family == posix.AF.INET) { mem.writeInt(u32, sa6.addr[12..16], sa4.addr, native_endian); } if (dscope == @as(i32, scopeOf(sa6.addr))) key |= DAS_MATCHINGSCOPE; @@ -1267,28 +1268,28 @@ fn addrCmpLessThan(context: void, b: LookupAddr, a: LookupAddr) bool { fn linuxLookupNameFromNull( addrs: *std.ArrayList(LookupAddr), - family: os.sa_family_t, + family: posix.sa_family_t, flags: u32, port: u16, ) !void { if ((flags & std.c.AI.PASSIVE) != 0) { - if (family != os.AF.INET6) { + if (family != posix.AF.INET6) { (try addrs.addOne()).* = LookupAddr{ .addr = Address.initIp4([1]u8{0} ** 4, port), }; } - if (family != os.AF.INET) { + if (family != posix.AF.INET) { (try addrs.addOne()).* = LookupAddr{ .addr = Address.initIp6([1]u8{0} ** 16, port, 0, 0), }; } } else { - if (family != os.AF.INET6) { + if (family != posix.AF.INET6) { (try addrs.addOne()).* = LookupAddr{ .addr = Address.initIp4([4]u8{ 127, 0, 0, 1 }, port), }; } - if (family != os.AF.INET) { + if (family != posix.AF.INET) { (try addrs.addOne()).* = LookupAddr{ .addr = Address.initIp6(([1]u8{0} ** 15) ++ [1]u8{1}, port, 0, 0), }; @@ -1300,7 +1301,7 @@ fn linuxLookupNameFromHosts( addrs: *std.ArrayList(LookupAddr), canon: *std.ArrayList(u8), name: []const u8, - family: os.sa_family_t, + family: posix.sa_family_t, port: u16, ) !void { const file = fs.openFileAbsoluteZ("/etc/hosts", .{}) catch |err| switch (err) { @@ -1374,7 +1375,7 @@ fn linuxLookupNameFromDnsSearch( addrs: *std.ArrayList(LookupAddr), canon: *std.ArrayList(u8), name: []const u8, - family: os.sa_family_t, + family: posix.sa_family_t, port: u16, ) !void { var rc: ResolvConf = undefined; @@ -1429,7 +1430,7 @@ fn linuxLookupNameFromDns( addrs: *std.ArrayList(LookupAddr), canon: *std.ArrayList(u8), name: []const u8, - family: os.sa_family_t, + family: posix.sa_family_t, rc: ResolvConf, port: u16, ) !void { @@ -1439,12 +1440,12 @@ fn linuxLookupNameFromDns( .port = port, }; const AfRr = struct { - af: os.sa_family_t, + af: posix.sa_family_t, rr: u8, }; const afrrs = [_]AfRr{ - AfRr{ .af = os.AF.INET6, .rr = os.RR.A }, - AfRr{ .af = os.AF.INET, .rr = os.RR.AAAA }, + AfRr{ .af = posix.AF.INET6, .rr = posix.RR.A }, + AfRr{ .af = posix.AF.INET, .rr = posix.RR.AAAA }, }; var qbuf: [2][280]u8 = undefined; var abuf: [2][512]u8 = undefined; @@ -1454,7 +1455,7 @@ fn linuxLookupNameFromDns( for (afrrs) |afrr| { if (family != afrr.af) { - const len = os.res_mkquery(0, name, 1, afrr.rr, &[_]u8{}, null, &qbuf[nq]); + const len = posix.res_mkquery(0, name, 1, afrr.rr, &[_]u8{}, null, &qbuf[nq]); qp[nq] = qbuf[nq][0..len]; nq += 1; } @@ -1582,8 +1583,8 @@ fn resMSendRc( const timeout = 1000 * rc.timeout; const attempts = rc.attempts; - var sl: os.socklen_t = @sizeOf(os.sockaddr.in); - var family: os.sa_family_t = os.AF.INET; + var sl: posix.socklen_t = @sizeOf(posix.sockaddr.in); + var family: posix.sa_family_t = posix.AF.INET; var ns_list = std.ArrayList(Address).init(rc.ns.allocator); defer ns_list.deinit(); @@ -1594,18 +1595,18 @@ fn resMSendRc( for (rc.ns.items, 0..) |iplit, i| { ns[i] = iplit.addr; assert(ns[i].getPort() == 53); - if (iplit.addr.any.family != os.AF.INET) { - family = os.AF.INET6; + if (iplit.addr.any.family != posix.AF.INET) { + family = posix.AF.INET6; } } - const flags = os.SOCK.DGRAM | os.SOCK.CLOEXEC | os.SOCK.NONBLOCK; - const fd = os.socket(family, flags, 0) catch |err| switch (err) { + const flags = posix.SOCK.DGRAM | posix.SOCK.CLOEXEC | posix.SOCK.NONBLOCK; + const fd = posix.socket(family, flags, 0) catch |err| switch (err) { error.AddressFamilyNotSupported => blk: { // Handle case where system lacks IPv6 support - if (family == os.AF.INET6) { - family = os.AF.INET; - break :blk try os.socket(os.AF.INET, flags, 0); + if (family == posix.AF.INET6) { + family = posix.AF.INET; + break :blk try posix.socket(posix.AF.INET, flags, 0); } return err; }, @@ -1618,33 +1619,33 @@ fn resMSendRc( // packet which is up to the caller to interpret. // Convert any IPv4 addresses in a mixed environment to v4-mapped - if (family == os.AF.INET6) { - try os.setsockopt( + if (family == posix.AF.INET6) { + try posix.setsockopt( fd, - os.SOL.IPV6, - os.linux.IPV6.V6ONLY, + posix.SOL.IPV6, + std.os.linux.IPV6.V6ONLY, &mem.toBytes(@as(c_int, 0)), ); for (0..ns.len) |i| { - if (ns[i].any.family != os.AF.INET) continue; + if (ns[i].any.family != posix.AF.INET) continue; mem.writeInt(u32, ns[i].in6.sa.addr[12..], ns[i].in.sa.addr, native_endian); ns[i].in6.sa.addr[0..12].* = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff".*; - ns[i].any.family = os.AF.INET6; + ns[i].any.family = posix.AF.INET6; ns[i].in6.sa.flowinfo = 0; ns[i].in6.sa.scope_id = 0; } - sl = @sizeOf(os.sockaddr.in6); + sl = @sizeOf(posix.sockaddr.in6); } // Get local address and open/bind a socket var sa: Address = undefined; @memset(@as([*]u8, @ptrCast(&sa))[0..@sizeOf(Address)], 0); sa.any.family = family; - try os.bind(fd, &sa.any, sl); + try posix.bind(fd, &sa.any, sl); - var pfd = [1]os.pollfd{os.pollfd{ + var pfd = [1]posix.pollfd{posix.pollfd{ .fd = fd, - .events = os.POLL.IN, + .events = posix.POLL.IN, .revents = undefined, }}; const retry_interval = timeout / attempts; @@ -1663,7 +1664,7 @@ fn resMSendRc( if (answers[i].len == 0) { var j: usize = 0; while (j < ns.len) : (j += 1) { - _ = os.sendto(fd, queries[i], os.MSG.NOSIGNAL, &ns[j].any, sl) catch undefined; + _ = posix.sendto(fd, queries[i], posix.MSG.NOSIGNAL, &ns[j].any, sl) catch undefined; } } } @@ -1673,12 +1674,12 @@ fn resMSendRc( // Wait for a response, or until time to retry const clamped_timeout = @min(@as(u31, std.math.maxInt(u31)), t1 + retry_interval - t2); - const nevents = os.poll(&pfd, clamped_timeout) catch 0; + const nevents = posix.poll(&pfd, clamped_timeout) catch 0; if (nevents == 0) continue; while (true) { var sl_copy = sl; - const rlen = os.recvfrom(fd, answer_bufs[next], 0, &sa.any, &sl_copy) catch break; + const rlen = posix.recvfrom(fd, answer_bufs[next], 0, &sa.any, &sl_copy) catch break; // Ignore non-identifiable packets if (rlen < 4) continue; @@ -1704,7 +1705,7 @@ fn resMSendRc( 0, 3 => {}, 2 => if (servfail_retry != 0) { servfail_retry -= 1; - _ = os.sendto(fd, queries[i], os.MSG.NOSIGNAL, &ns[j].any, sl) catch undefined; + _ = posix.sendto(fd, queries[i], posix.MSG.NOSIGNAL, &ns[j].any, sl) catch undefined; }, else => continue, } @@ -1758,24 +1759,24 @@ fn dnsParse( fn dnsParseCallback(ctx: dpc_ctx, rr: u8, data: []const u8, packet: []const u8) !void { switch (rr) { - os.RR.A => { + posix.RR.A => { if (data.len != 4) return error.InvalidDnsARecord; const new_addr = try ctx.addrs.addOne(); new_addr.* = LookupAddr{ .addr = Address.initIp4(data[0..4].*, ctx.port), }; }, - os.RR.AAAA => { + posix.RR.AAAA => { if (data.len != 16) return error.InvalidDnsAAAARecord; const new_addr = try ctx.addrs.addOne(); new_addr.* = LookupAddr{ .addr = Address.initIp6(data[0..16].*, ctx.port, 0, 0), }; }, - os.RR.CNAME => { + posix.RR.CNAME => { var tmp: [256]u8 = undefined; // Returns len of compressed name. strlen to get canon name. - _ = try os.dn_expand(packet, data, &tmp); + _ = try posix.dn_expand(packet, data, &tmp); const canon_name = mem.sliceTo(&tmp, 0); if (isValidHostName(canon_name)) { ctx.canon.items.len = 0; @@ -1792,14 +1793,14 @@ pub const Stream = struct { handle: posix.socket_t, pub fn close(s: Stream) void { - switch (builtin.os.tag) { - .windows => std.os.windows.closesocket(s.handle) catch unreachable, + switch (native_os) { + .windows => windows.closesocket(s.handle) catch unreachable, else => posix.close(s.handle), } } - pub const ReadError = os.ReadError; - pub const WriteError = os.WriteError; + pub const ReadError = posix.ReadError; + pub const WriteError = posix.WriteError; pub const Reader = io.Reader(Stream, ReadError, read); pub const Writer = io.Writer(Stream, WriteError, write); @@ -1813,22 +1814,22 @@ pub const Stream = struct { } pub fn read(self: Stream, buffer: []u8) ReadError!usize { - if (builtin.os.tag == .windows) { - return os.windows.ReadFile(self.handle, buffer, null); + if (native_os == .windows) { + return windows.ReadFile(self.handle, buffer, null); } - return os.read(self.handle, buffer); + return posix.read(self.handle, buffer); } - pub fn readv(s: Stream, iovecs: []const os.iovec) ReadError!usize { - if (builtin.os.tag == .windows) { + pub fn readv(s: Stream, iovecs: []const posix.iovec) ReadError!usize { + if (native_os == .windows) { // TODO improve this to use ReadFileScatter if (iovecs.len == 0) return @as(usize, 0); const first = iovecs[0]; - return os.windows.ReadFile(s.handle, first.iov_base[0..first.iov_len], null); + return windows.ReadFile(s.handle, first.iov_base[0..first.iov_len], null); } - return os.readv(s.handle, iovecs); + return posix.readv(s.handle, iovecs); } /// Returns the number of bytes read. If the number read is smaller than @@ -1858,11 +1859,11 @@ pub const Stream = struct { /// file system thread instead of non-blocking. It needs to be reworked to properly /// use non-blocking I/O. pub fn write(self: Stream, buffer: []const u8) WriteError!usize { - if (builtin.os.tag == .windows) { - return os.windows.WriteFile(self.handle, buffer, null); + if (native_os == .windows) { + return windows.WriteFile(self.handle, buffer, null); } - return os.write(self.handle, buffer); + return posix.write(self.handle, buffer); } pub fn writeAll(self: Stream, bytes: []const u8) WriteError!void { @@ -1874,15 +1875,15 @@ pub const Stream = struct { /// See https://github.com/ziglang/zig/issues/7699 /// See equivalent function: `std.fs.File.writev`. - pub fn writev(self: Stream, iovecs: []const os.iovec_const) WriteError!usize { - return os.writev(self.handle, iovecs); + pub fn writev(self: Stream, iovecs: []const posix.iovec_const) WriteError!usize { + return posix.writev(self.handle, iovecs); } /// The `iovecs` parameter is mutable because this function needs to mutate the fields in /// order to handle partial writes from the underlying OS layer. /// See https://github.com/ziglang/zig/issues/7699 /// See equivalent function: `std.fs.File.writevAll`. - pub fn writevAll(self: Stream, iovecs: []os.iovec_const) WriteError!void { + pub fn writevAll(self: Stream, iovecs: []posix.iovec_const) WriteError!void { if (iovecs.len == 0) return; var i: usize = 0; diff --git a/lib/std/os.zig b/lib/std/os.zig index 2138f5cd20a8..9d322ed9bde4 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -11,8 +11,6 @@ //! On Linux libc can be side-stepped by using `std.os.linux` directly. //! * For Windows, this file represents the API that libc would provide for //! Windows. For thin wrappers around Windows-specific APIs, see `std.os.windows`. -//! Note: The Zig standard library does not support POSIX thread cancellation, and -//! in general EINTR is handled by trying again. const root = @import("root"); const std = @import("std.zig"); @@ -25,14 +23,6 @@ const fs = std.fs; const dl = @import("dynamic_library.zig"); const MAX_PATH_BYTES = std.fs.MAX_PATH_BYTES; -pub const darwin = std.c; -pub const dragonfly = std.c; -pub const freebsd = std.c; -pub const haiku = std.c; -pub const netbsd = std.c; -pub const openbsd = std.c; -pub const solaris = std.c; -pub const illumos = std.c; pub const linux = @import("os/linux.zig"); pub const plan9 = @import("os/plan9.zig"); pub const uefi = @import("os/uefi.zig"); @@ -40,214 +30,15 @@ pub const wasi = @import("os/wasi.zig"); pub const emscripten = @import("os/emscripten.zig"); pub const windows = @import("os/windows.zig"); -comptime { - assert(@import("std") == std); // std lib tests require --zig-lib-dir -} - test { - _ = darwin; _ = linux; if (builtin.os.tag == .uefi) { _ = uefi; } _ = wasi; _ = windows; - - _ = @import("os/test.zig"); } -/// Applications can override the `system` API layer in their root source file. -/// Otherwise, when linking libc, this is the C API. -/// When not linking libc, it is the OS-specific system interface. -pub const system = if (@hasDecl(root, "os") and @hasDecl(root.os, "system") and root.os != @This()) - root.os.system -else if (use_libc) - std.c -else switch (builtin.os.tag) { - .linux => linux, - .plan9 => plan9, - .uefi => uefi, - else => struct {}, -}; - -/// Whether to use libc for the POSIX API layer. -const use_libc = builtin.link_libc or switch (builtin.os.tag) { - .windows, .wasi => true, - else => false, -}; - -pub const AF = system.AF; -pub const AF_SUN = system.AF_SUN; -pub const ARCH = system.ARCH; -pub const AT = system.AT; -pub const AT_SUN = system.AT_SUN; -pub const CLOCK = system.CLOCK; -pub const CPU_COUNT = system.CPU_COUNT; -pub const CTL = system.CTL; -pub const DT = system.DT; -pub const E = system.E; -pub const Elf_Symndx = system.Elf_Symndx; -pub const F = system.F; -pub const FD_CLOEXEC = system.FD_CLOEXEC; -pub const Flock = system.Flock; -pub const HOST_NAME_MAX = system.HOST_NAME_MAX; -pub const HW = system.HW; -pub const IFNAMESIZE = system.IFNAMESIZE; -pub const IOV_MAX = system.IOV_MAX; -pub const IPPROTO = system.IPPROTO; -pub const KERN = system.KERN; -pub const Kevent = system.Kevent; -pub const LOCK = system.LOCK; -pub const MADV = system.MADV; -pub const MAP = system.MAP; -pub const MSF = system.MSF; -pub const MAX_ADDR_LEN = system.MAX_ADDR_LEN; -pub const MFD = system.MFD; -pub const MMAP2_UNIT = system.MMAP2_UNIT; -pub const MSG = system.MSG; -pub const NAME_MAX = system.NAME_MAX; -pub const O = system.O; -pub const PATH_MAX = system.PATH_MAX; -pub const POLL = system.POLL; -pub const POSIX_FADV = system.POSIX_FADV; -pub const PR = system.PR; -pub const PROT = system.PROT; -pub const REG = system.REG; -pub const RLIM = system.RLIM; -pub const RR = system.RR; -pub const S = system.S; -pub const SA = system.SA; -pub const SC = system.SC; -pub const _SC = system._SC; -pub const SEEK = system.SEEK; -pub const SHUT = system.SHUT; -pub const SIG = system.SIG; -pub const SIOCGIFINDEX = system.SIOCGIFINDEX; -pub const SO = system.SO; -pub const SOCK = system.SOCK; -pub const SOL = system.SOL; -pub const STDERR_FILENO = system.STDERR_FILENO; -pub const STDIN_FILENO = system.STDIN_FILENO; -pub const STDOUT_FILENO = system.STDOUT_FILENO; -pub const SYS = system.SYS; -pub const Sigaction = system.Sigaction; -pub const Stat = system.Stat; -pub const T = system.T; -pub const TCSA = system.TCSA; -pub const TCP = system.TCP; -pub const VDSO = system.VDSO; -pub const W = system.W; -pub const addrinfo = system.addrinfo; -pub const blkcnt_t = system.blkcnt_t; -pub const blksize_t = system.blksize_t; -pub const clock_t = system.clock_t; -pub const cpu_set_t = system.cpu_set_t; -pub const dev_t = system.dev_t; -pub const dl_phdr_info = system.dl_phdr_info; -pub const empty_sigset = system.empty_sigset; -pub const filled_sigset = system.filled_sigset; -pub const fd_t = system.fd_t; -pub const gid_t = system.gid_t; -pub const ifreq = system.ifreq; -pub const ino_t = system.ino_t; -pub const mcontext_t = system.mcontext_t; -pub const mode_t = system.mode_t; -pub const msghdr = system.msghdr; -pub const msghdr_const = system.msghdr_const; -pub const nfds_t = system.nfds_t; -pub const nlink_t = system.nlink_t; -pub const off_t = system.off_t; -pub const pid_t = system.pid_t; -pub const pollfd = system.pollfd; -pub const port_t = system.port_t; -pub const port_event = system.port_event; -pub const port_notify = system.port_notify; -pub const file_obj = system.file_obj; -pub const rlim_t = system.rlim_t; -pub const rlimit = system.rlimit; -pub const rlimit_resource = system.rlimit_resource; -pub const rusage = system.rusage; -pub const sa_family_t = system.sa_family_t; -pub const siginfo_t = system.siginfo_t; -pub const sigset_t = system.sigset_t; -pub const sockaddr = system.sockaddr; -pub const socklen_t = system.socklen_t; -pub const stack_t = system.stack_t; -pub const time_t = system.time_t; -pub const timespec = system.timespec; -pub const timestamp_t = system.timestamp_t; -pub const timeval = system.timeval; -pub const timezone = system.timezone; -pub const ucontext_t = system.ucontext_t; -pub const uid_t = system.uid_t; -pub const user_desc = system.user_desc; -pub const utsname = system.utsname; -pub const winsize = system.winsize; - -pub const termios = system.termios; -pub const CSIZE = system.CSIZE; -pub const NCCS = system.NCCS; -pub const cc_t = system.cc_t; -pub const V = system.V; -pub const speed_t = system.speed_t; -pub const tc_iflag_t = system.tc_iflag_t; -pub const tc_oflag_t = system.tc_oflag_t; -pub const tc_cflag_t = system.tc_cflag_t; -pub const tc_lflag_t = system.tc_lflag_t; - -pub const F_OK = system.F_OK; -pub const R_OK = system.R_OK; -pub const W_OK = system.W_OK; -pub const X_OK = system.X_OK; - -pub const iovec = extern struct { - iov_base: [*]u8, - iov_len: usize, -}; - -pub const iovec_const = extern struct { - iov_base: [*]const u8, - iov_len: usize, -}; - -pub const ACCMODE = enum(u2) { - RDONLY = 0, - WRONLY = 1, - RDWR = 2, -}; - -pub const LOG = struct { - /// system is unusable - pub const EMERG = 0; - /// action must be taken immediately - pub const ALERT = 1; - /// critical conditions - pub const CRIT = 2; - /// error conditions - pub const ERR = 3; - /// warning conditions - pub const WARNING = 4; - /// normal but significant condition - pub const NOTICE = 5; - /// informational - pub const INFO = 6; - /// debug-level messages - pub const DEBUG = 7; -}; - -/// An fd-relative file path -/// -/// This is currently only used for WASI-specific functionality, but the concept -/// is the same as the dirfd/pathname pairs in the `*at(...)` POSIX functions. -pub const RelativePathWasi = struct { - /// Handle to directory - dir_fd: fd_t, - /// Path to resource within `dir_fd`. - relative_path: []const u8, -}; - -pub const socket_t = if (builtin.os.tag == .windows) windows.ws2_32.SOCKET else fd_t; - /// See also `getenv`. Populated by startup code before main(). /// TODO this is a footgun because the value will be undefined when using `zig build-lib`. /// https://github.com/ziglang/zig/issues/4524 @@ -262,7433 +53,184 @@ pub var argv: [][*:0]u8 = if (builtin.link_libc) undefined else switch (builtin. else => undefined, }; -pub const have_sigpipe_support = @hasDecl(@This(), "SIG") and @hasDecl(SIG, "PIPE"); - -fn noopSigHandler(_: c_int) callconv(.C) void {} - -/// On default executed by posix startup code before main(), if SIGPIPE is supported. -pub fn maybeIgnoreSigpipe() void { - if (have_sigpipe_support and !std.options.keep_sigpipe) { - const act = Sigaction{ - // We set handler to a noop function instead of SIG.IGN so we don't leak our - // signal disposition to a child process - .handler = .{ .handler = noopSigHandler }, - .mask = empty_sigset, - .flags = 0, - }; - sigaction(SIG.PIPE, &act, null) catch |err| - std.debug.panic("failed to install noop SIGPIPE handler with '{s}'", .{@errorName(err)}); - } -} - -/// To obtain errno, call this function with the return value of the -/// system function call. For some systems this will obtain the value directly -/// from the return code; for others it will use a thread-local errno variable. -/// Therefore, this function only returns a well-defined value when it is called -/// directly after the system function call which one wants to learn the errno -/// value of. -pub const errno = system.getErrno; - -/// Closes the file descriptor. -/// This function is not capable of returning any indication of failure. An -/// application which wants to ensure writes have succeeded before closing -/// must call `fsync` before `close`. -/// Note: The Zig standard library does not support POSIX thread cancellation. -pub fn close(fd: fd_t) void { - if (builtin.os.tag == .windows) { - return windows.CloseHandle(fd); - } - if (builtin.os.tag == .wasi and !builtin.link_libc) { - _ = wasi.fd_close(fd); +/// Call from Windows-specific code if you already have a WTF-16LE encoded, null terminated string. +/// Otherwise use `access` or `accessZ`. +pub fn accessW(path: [*:0]const u16) windows.GetFileAttributesError!void { + const ret = try windows.GetFileAttributesW(path); + if (ret != windows.INVALID_FILE_ATTRIBUTES) { return; } - if (builtin.target.isDarwin()) { - // This avoids the EINTR problem. - switch (darwin.getErrno(darwin.@"close$NOCANCEL"(fd))) { - .BADF => unreachable, // Always a race condition. - else => return, - } - } - switch (errno(system.close(fd))) { - .BADF => unreachable, // Always a race condition. - .INTR => return, // This is still a success. See https://github.com/ziglang/zig/issues/2425 - else => return, + switch (windows.kernel32.GetLastError()) { + .FILE_NOT_FOUND => return error.FileNotFound, + .PATH_NOT_FOUND => return error.FileNotFound, + .ACCESS_DENIED => return error.PermissionDenied, + else => |err| return windows.unexpectedError(err), } } -pub const FChmodError = error{ - AccessDenied, - InputOutput, - SymLinkLoop, - FileNotFound, - SystemResources, - ReadOnlyFileSystem, -} || UnexpectedError; - -/// Changes the mode of the file referred to by the file descriptor. -/// The process must have the correct privileges in order to do this -/// successfully, or must have the effective user ID matching the owner -/// of the file. -pub fn fchmod(fd: fd_t, mode: mode_t) FChmodError!void { - if (!std.fs.has_executable_bit) @compileError("fchmod unsupported by target OS"); - - while (true) { - const res = system.fchmod(fd, mode); +pub fn isGetFdPathSupportedOnTarget(os: std.Target.Os) bool { + return switch (os.tag) { + .windows, + .macos, + .ios, + .watchos, + .tvos, + .linux, + .solaris, + .illumos, + .freebsd, + => true, - switch (system.getErrno(res)) { - .SUCCESS => return, - .INTR => continue, - .BADF => unreachable, - .FAULT => unreachable, - .INVAL => unreachable, - .ACCES => return error.AccessDenied, - .IO => return error.InputOutput, - .LOOP => return error.SymLinkLoop, - .NOENT => return error.FileNotFound, - .NOMEM => return error.SystemResources, - .NOTDIR => return error.FileNotFound, - .PERM => return error.AccessDenied, - .ROFS => return error.ReadOnlyFileSystem, - else => |err| return unexpectedErrno(err), - } - } + .dragonfly => os.version_range.semver.max.order(.{ .major = 6, .minor = 0, .patch = 0 }) != .lt, + .netbsd => os.version_range.semver.max.order(.{ .major = 10, .minor = 0, .patch = 0 }) != .lt, + else => false, + }; } -const FChmodAtError = FChmodError || error{ - /// A component of `path` exceeded `NAME_MAX`, or the entire path exceeded - /// `PATH_MAX`. - NameTooLong, - /// `path` resolves to a symbolic link, and `AT.SYMLINK_NOFOLLOW` was set - /// in `flags`. This error only occurs on Linux, where changing the mode of - /// a symbolic link has no meaning and can cause undefined behaviour on - /// certain filesystems. - /// - /// The procfs fallback was used but procfs was not mounted. - OperationNotSupported, - /// The procfs fallback was used but the process exceeded its open file - /// limit. - ProcessFdQuotaExceeded, - /// The procfs fallback was used but the system exceeded it open file limit. - SystemFdQuotaExceeded, -}; - -var has_fchmodat2_syscall = std.atomic.Value(bool).init(true); - -/// Changes the `mode` of `path` relative to the directory referred to by -/// `dirfd`. The process must have the correct privileges in order to do this -/// successfully, or must have the effective user ID matching the owner of the -/// file. +/// Return canonical path of handle `fd`. /// -/// On Linux the `fchmodat2` syscall will be used if available, otherwise a -/// workaround using procfs will be employed. Changing the mode of a symbolic -/// link with `AT.SYMLINK_NOFOLLOW` set will also return -/// `OperationNotSupported`, as: +/// This function is very host-specific and is not universally supported by all hosts. +/// For example, while it generally works on Linux, macOS, FreeBSD or Windows, it is +/// unsupported on WASI. /// -/// 1. Permissions on the link are ignored when resolving its target. -/// 2. This operation has been known to invoke undefined behaviour across -/// different filesystems[1]. +/// * On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// * On other platforms, the result is an opaque sequence of bytes with no particular encoding. /// -/// [1]: https://sourceware.org/legacy-ml/libc-alpha/2020-02/msg00467.html. -pub inline fn fchmodat(dirfd: fd_t, path: []const u8, mode: mode_t, flags: u32) FChmodAtError!void { - if (!std.fs.has_executable_bit) @compileError("fchmodat unsupported by target OS"); - - // No special handling for linux is needed if we can use the libc fallback - // or `flags` is empty. Glibc only added the fallback in 2.32. - const skip_fchmodat_fallback = builtin.os.tag != .linux or - std.c.versionCheck(.{ .major = 2, .minor = 32, .patch = 0 }) or - flags == 0; - - // This function is marked inline so that when flags is comptime-known, - // skip_fchmodat_fallback will be comptime-known true. - if (skip_fchmodat_fallback) - return fchmodat1(dirfd, path, mode, flags); - - return fchmodat2(dirfd, path, mode, flags); -} - -fn fchmodat1(dirfd: fd_t, path: []const u8, mode: mode_t, flags: u32) FChmodAtError!void { - const path_c = try toPosixPath(path); - while (true) { - const res = system.fchmodat(dirfd, &path_c, mode, flags); - switch (system.getErrno(res)) { - .SUCCESS => return, - .INTR => continue, - .BADF => unreachable, - .FAULT => unreachable, - .INVAL => unreachable, - .ACCES => return error.AccessDenied, - .IO => return error.InputOutput, - .LOOP => return error.SymLinkLoop, - .MFILE => return error.ProcessFdQuotaExceeded, - .NAMETOOLONG => return error.NameTooLong, - .NFILE => return error.SystemFdQuotaExceeded, - .NOENT => return error.FileNotFound, - .NOTDIR => return error.FileNotFound, - .NOMEM => return error.SystemResources, - .OPNOTSUPP => return error.OperationNotSupported, - .PERM => return error.AccessDenied, - .ROFS => return error.ReadOnlyFileSystem, - else => |err| return unexpectedErrno(err), - } - } -} - -fn fchmodat2(dirfd: fd_t, path: []const u8, mode: mode_t, flags: u32) FChmodAtError!void { - const path_c = try toPosixPath(path); - const use_fchmodat2 = (builtin.os.isAtLeast(.linux, .{ .major = 6, .minor = 6, .patch = 0 }) orelse false) and - has_fchmodat2_syscall.load(.monotonic); - while (use_fchmodat2) { - // Later on this should be changed to `system.fchmodat2` - // when the musl/glibc add a wrapper. - const res = linux.fchmodat2(dirfd, &path_c, mode, flags); - switch (linux.getErrno(res)) { - .SUCCESS => return, - .INTR => continue, - .BADF => unreachable, - .FAULT => unreachable, - .INVAL => unreachable, - .ACCES => return error.AccessDenied, - .IO => return error.InputOutput, - .LOOP => return error.SymLinkLoop, - .NOENT => return error.FileNotFound, - .NOMEM => return error.SystemResources, - .NOTDIR => return error.FileNotFound, - .OPNOTSUPP => return error.OperationNotSupported, - .PERM => return error.AccessDenied, - .ROFS => return error.ReadOnlyFileSystem, - - .NOSYS => { // Use fallback. - has_fchmodat2_syscall.store(false, .monotonic); - break; - }, - else => |err| return unexpectedErrno(err), - } - } - - // Fallback to changing permissions using procfs: - // - // 1. Open `path` as a `PATH` descriptor. - // 2. Stat the fd and check if it isn't a symbolic link. - // 3. Generate the procfs reference to the fd via `/proc/self/fd/{fd}`. - // 4. Pass the procfs path to `chmod` with the `mode`. - var pathfd: fd_t = undefined; - while (true) { - const rc = system.openat(dirfd, &path_c, .{ .PATH = true, .NOFOLLOW = true, .CLOEXEC = true }, @as(mode_t, 0)); - switch (system.getErrno(rc)) { - .SUCCESS => { - pathfd = @intCast(rc); - break; - }, - .INTR => continue, - .FAULT => unreachable, - .INVAL => unreachable, - .ACCES => return error.AccessDenied, - .PERM => return error.AccessDenied, - .LOOP => return error.SymLinkLoop, - .MFILE => return error.ProcessFdQuotaExceeded, - .NAMETOOLONG => return error.NameTooLong, - .NFILE => return error.SystemFdQuotaExceeded, - .NOENT => return error.FileNotFound, - .NOMEM => return error.SystemResources, - else => |err| return unexpectedErrno(err), - } - } - defer close(pathfd); - - const stat = fstatatZ(pathfd, "", AT.EMPTY_PATH) catch |err| switch (err) { - error.NameTooLong => unreachable, - error.FileNotFound => unreachable, - error.InvalidUtf8 => unreachable, - else => |e| return e, - }; - if ((stat.mode & S.IFMT) == S.IFLNK) - return error.OperationNotSupported; - - var procfs_buf: ["/proc/self/fd/-2147483648\x00".len]u8 = undefined; - const proc_path = std.fmt.bufPrintZ(procfs_buf[0..], "/proc/self/fd/{d}", .{pathfd}) catch unreachable; - while (true) { - const res = system.chmod(proc_path, mode); - switch (system.getErrno(res)) { - // Getting NOENT here means that procfs isn't mounted. - .NOENT => return error.OperationNotSupported, - - .SUCCESS => return, - .INTR => continue, - .BADF => unreachable, - .FAULT => unreachable, - .INVAL => unreachable, - .ACCES => return error.AccessDenied, - .IO => return error.InputOutput, - .LOOP => return error.SymLinkLoop, - .NOMEM => return error.SystemResources, - .NOTDIR => return error.FileNotFound, - .PERM => return error.AccessDenied, - .ROFS => return error.ReadOnlyFileSystem, - else => |err| return unexpectedErrno(err), - } +/// Calling this function is usually a bug. +pub fn getFdPath(fd: std.posix.fd_t, out_buffer: *[MAX_PATH_BYTES]u8) std.posix.RealPathError![]u8 { + const posix = std.posix; + if (!comptime isGetFdPathSupportedOnTarget(builtin.os)) { + @compileError("querying for canonical path of a handle is unsupported on this host"); } -} - -pub const FChownError = error{ - AccessDenied, - InputOutput, - SymLinkLoop, - FileNotFound, - SystemResources, - ReadOnlyFileSystem, -} || UnexpectedError; - -/// Changes the owner and group of the file referred to by the file descriptor. -/// The process must have the correct privileges in order to do this -/// successfully. The group may be changed by the owner of the directory to -/// any group of which the owner is a member. If the owner or group is -/// specified as `null`, the ID is not changed. -pub fn fchown(fd: fd_t, owner: ?uid_t, group: ?gid_t) FChownError!void { switch (builtin.os.tag) { - .windows, .wasi => @compileError("Unsupported OS"), - else => {}, - } - - while (true) { - const res = system.fchown(fd, owner orelse ~@as(uid_t, 0), group orelse ~@as(gid_t, 0)); - - switch (system.getErrno(res)) { - .SUCCESS => return, - .INTR => continue, - .BADF => unreachable, // Can be reached if the fd refers to a directory opened without `OpenDirOptions{ .iterate = true }` - - .FAULT => unreachable, - .INVAL => unreachable, - .ACCES => return error.AccessDenied, - .IO => return error.InputOutput, - .LOOP => return error.SymLinkLoop, - .NOENT => return error.FileNotFound, - .NOMEM => return error.SystemResources, - .NOTDIR => return error.FileNotFound, - .PERM => return error.AccessDenied, - .ROFS => return error.ReadOnlyFileSystem, - else => |err| return unexpectedErrno(err), - } - } -} - -pub const RebootError = error{ - PermissionDenied, -} || UnexpectedError; - -pub const RebootCommand = switch (builtin.os.tag) { - .linux => union(linux.LINUX_REBOOT.CMD) { - RESTART: void, - HALT: void, - CAD_ON: void, - CAD_OFF: void, - POWER_OFF: void, - RESTART2: [*:0]const u8, - SW_SUSPEND: void, - KEXEC: void, - }, - else => @compileError("Unsupported OS"), -}; + .windows => { + var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined; + const wide_slice = try windows.GetFinalPathNameByHandle(fd, .{}, wide_buf[0..]); -pub fn reboot(cmd: RebootCommand) RebootError!void { - switch (builtin.os.tag) { - .linux => { - switch (system.getErrno(linux.reboot( - .MAGIC1, - .MAGIC2, - cmd, - switch (cmd) { - .RESTART2 => |s| s, - else => null, - }, - ))) { + const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice); + return out_buffer[0..end_index]; + }, + .macos, .ios, .watchos, .tvos => { + // On macOS, we can use F.GETPATH fcntl command to query the OS for + // the path to the file descriptor. + @memset(out_buffer[0..MAX_PATH_BYTES], 0); + switch (posix.errno(posix.system.fcntl(fd, posix.F.GETPATH, out_buffer))) { .SUCCESS => {}, - .PERM => return error.PermissionDenied, - else => |err| return std.os.unexpectedErrno(err), - } - switch (cmd) { - .CAD_OFF => {}, - .CAD_ON => {}, - .SW_SUSPEND => {}, - - .HALT => unreachable, - .KEXEC => unreachable, - .POWER_OFF => unreachable, - .RESTART => unreachable, - .RESTART2 => unreachable, + .BADF => return error.FileNotFound, + .NOSPC => return error.NameTooLong, + // TODO man pages for fcntl on macOS don't really tell you what + // errno values to expect when command is F.GETPATH... + else => |err| return posix.unexpectedErrno(err), } + const len = mem.indexOfScalar(u8, out_buffer[0..], 0) orelse MAX_PATH_BYTES; + return out_buffer[0..len]; }, - else => @compileError("Unsupported OS"), - } -} - -pub const GetRandomError = OpenError; + .linux => { + var procfs_buf: ["/proc/self/fd/-2147483648\x00".len]u8 = undefined; + const proc_path = std.fmt.bufPrintZ(procfs_buf[0..], "/proc/self/fd/{d}", .{fd}) catch unreachable; -/// Obtain a series of random bytes. These bytes can be used to seed user-space -/// random number generators or for cryptographic purposes. -/// When linking against libc, this calls the -/// appropriate OS-specific library call. Otherwise it uses the zig standard -/// library implementation. -pub fn getrandom(buffer: []u8) GetRandomError!void { - if (builtin.os.tag == .windows) { - return windows.RtlGenRandom(buffer); - } - if (builtin.os.tag == .linux or builtin.os.tag == .freebsd) { - var buf = buffer; - const use_c = builtin.os.tag != .linux or - std.c.versionCheck(std.SemanticVersion{ .major = 2, .minor = 25, .patch = 0 }); + const target = posix.readlinkZ(proc_path, out_buffer) catch |err| { + switch (err) { + error.NotLink => unreachable, + error.BadPathName => unreachable, + error.InvalidUtf8 => unreachable, // WASI-only + error.InvalidWtf8 => unreachable, // Windows-only + error.UnsupportedReparsePointType => unreachable, // Windows-only + error.NetworkNotFound => unreachable, // Windows-only + else => |e| return e, + } + }; + return target; + }, + .solaris, .illumos => { + var procfs_buf: ["/proc/self/path/-2147483648\x00".len]u8 = undefined; + const proc_path = std.fmt.bufPrintZ(procfs_buf[0..], "/proc/self/path/{d}", .{fd}) catch unreachable; - while (buf.len != 0) { - const num_read: usize, const err = if (use_c) res: { - const rc = std.c.getrandom(buf.ptr, buf.len, 0); - break :res .{ - @bitCast(rc), - std.c.getErrno(rc), + const target = posix.readlinkZ(proc_path, out_buffer) catch |err| switch (err) { + error.UnsupportedReparsePointType => unreachable, + error.NotLink => unreachable, + else => |e| return e, + }; + return target; + }, + .freebsd => { + if (comptime builtin.os.isAtLeast(.freebsd, .{ .major = 13, .minor = 0, .patch = 0 }) orelse false) { + var kfile: std.c.kinfo_file = undefined; + kfile.structsize = std.c.KINFO_FILE_SIZE; + switch (posix.errno(std.c.fcntl(fd, std.c.F.KINFO, @intFromPtr(&kfile)))) { + .SUCCESS => {}, + .BADF => return error.FileNotFound, + else => |err| return posix.unexpectedErrno(err), + } + const len = mem.indexOfScalar(u8, &kfile.path, 0) orelse MAX_PATH_BYTES; + if (len == 0) return error.NameTooLong; + const result = out_buffer[0..len]; + @memcpy(result, kfile.path[0..len]); + return result; + } else { + // This fallback implementation reimplements libutil's `kinfo_getfile()`. + // The motivation is to avoid linking -lutil when building zig or general + // user executables. + var mib = [4]c_int{ posix.CTL.KERN, posix.KERN.PROC, posix.KERN.PROC_FILEDESC, std.c.getpid() }; + var len: usize = undefined; + posix.sysctl(&mib, null, &len, null, 0) catch |err| switch (err) { + error.PermissionDenied => unreachable, + error.SystemResources => return error.SystemResources, + error.NameTooLong => unreachable, + error.UnknownName => unreachable, + else => return error.Unexpected, }; - } else res: { - const rc = linux.getrandom(buf.ptr, buf.len, 0); - break :res .{ - rc, - linux.getErrno(rc), + len = len * 4 / 3; + const buf = std.heap.c_allocator.alloc(u8, len) catch return error.SystemResources; + defer std.heap.c_allocator.free(buf); + len = buf.len; + posix.sysctl(&mib, &buf[0], &len, null, 0) catch |err| switch (err) { + error.PermissionDenied => unreachable, + error.SystemResources => return error.SystemResources, + error.NameTooLong => unreachable, + error.UnknownName => unreachable, + else => return error.Unexpected, }; - }; - - switch (err) { - .SUCCESS => buf = buf[num_read..], - .INVAL => unreachable, - .FAULT => unreachable, - .INTR => continue, - .NOSYS => return getRandomBytesDevURandom(buf), - else => return unexpectedErrno(err), + var i: usize = 0; + while (i < len) { + const kf: *align(1) std.c.kinfo_file = @ptrCast(&buf[i]); + if (kf.fd == fd) { + len = mem.indexOfScalar(u8, &kf.path, 0) orelse MAX_PATH_BYTES; + if (len == 0) return error.NameTooLong; + const result = out_buffer[0..len]; + @memcpy(result, kf.path[0..len]); + return result; + } + i += @intCast(kf.structsize); + } + return error.FileNotFound; } - } - return; - } - if (builtin.os.tag == .emscripten) { - const err = std.c.getErrno(std.c.getentropy(buffer.ptr, buffer.len)); - switch (err) { - .SUCCESS => return, - else => return unexpectedErrno(err), - } - } - switch (builtin.os.tag) { - .netbsd, .openbsd, .macos, .ios, .tvos, .watchos => { - system.arc4random_buf(buffer.ptr, buffer.len); - return; }, - .wasi => switch (wasi.random_get(buffer.ptr, buffer.len)) { - .SUCCESS => return, - else => |err| return unexpectedErrno(err), + .dragonfly => { + @memset(out_buffer[0..MAX_PATH_BYTES], 0); + switch (posix.errno(std.c.fcntl(fd, posix.F.GETPATH, out_buffer))) { + .SUCCESS => {}, + .BADF => return error.FileNotFound, + .RANGE => return error.NameTooLong, + else => |err| return posix.unexpectedErrno(err), + } + const len = mem.indexOfScalar(u8, out_buffer[0..], 0) orelse MAX_PATH_BYTES; + return out_buffer[0..len]; + }, + .netbsd => { + @memset(out_buffer[0..MAX_PATH_BYTES], 0); + switch (posix.errno(std.c.fcntl(fd, posix.F.GETPATH, out_buffer))) { + .SUCCESS => {}, + .ACCES => return error.AccessDenied, + .BADF => return error.FileNotFound, + .NOENT => return error.FileNotFound, + .NOMEM => return error.SystemResources, + .RANGE => return error.NameTooLong, + else => |err| return posix.unexpectedErrno(err), + } + const len = mem.indexOfScalar(u8, out_buffer[0..], 0) orelse MAX_PATH_BYTES; + return out_buffer[0..len]; }, - else => return getRandomBytesDevURandom(buffer), - } -} - -fn getRandomBytesDevURandom(buf: []u8) !void { - const fd = try openZ("/dev/urandom", .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0); - defer close(fd); - - const st = try fstat(fd); - if (!S.ISCHR(st.mode)) { - return error.NoDevice; - } - - const file = std.fs.File{ .handle = fd }; - const stream = file.reader(); - stream.readNoEof(buf) catch return error.Unexpected; -} - -/// Causes abnormal process termination. -/// If linking against libc, this calls the abort() libc function. Otherwise -/// it raises SIGABRT followed by SIGKILL and finally lo -/// Invokes the current signal handler for SIGABRT, if any. -pub fn abort() noreturn { - @setCold(true); - // MSVCRT abort() sometimes opens a popup window which is undesirable, so - // even when linking libc on Windows we use our own abort implementation. - // See https://github.com/ziglang/zig/issues/2071 for more details. - if (builtin.os.tag == .windows) { - if (builtin.mode == .Debug) { - @breakpoint(); - } - windows.kernel32.ExitProcess(3); - } - if (!builtin.link_libc and builtin.os.tag == .linux) { - // The Linux man page says that the libc abort() function - // "first unblocks the SIGABRT signal", but this is a footgun - // for user-defined signal handlers that want to restore some state in - // some program sections and crash in others. - // So, the user-installed SIGABRT handler is run, if present. - raise(SIG.ABRT) catch {}; - - // Disable all signal handlers. - sigprocmask(SIG.BLOCK, &linux.all_mask, null); - - // Only one thread may proceed to the rest of abort(). - if (!builtin.single_threaded) { - const global = struct { - var abort_entered: bool = false; - }; - while (@cmpxchgWeak(bool, &global.abort_entered, false, true, .seq_cst, .seq_cst)) |_| {} - } - - // Install default handler so that the tkill below will terminate. - const sigact = Sigaction{ - .handler = .{ .handler = SIG.DFL }, - .mask = empty_sigset, - .flags = 0, - }; - sigaction(SIG.ABRT, &sigact, null) catch |err| switch (err) { - error.OperationNotSupported => unreachable, - }; - - _ = linux.tkill(linux.gettid(), SIG.ABRT); - - const sigabrtmask: linux.sigset_t = [_]u32{0} ** 31 ++ [_]u32{1 << (SIG.ABRT - 1)}; - sigprocmask(SIG.UNBLOCK, &sigabrtmask, null); - - // Beyond this point should be unreachable. - @as(*allowzero volatile u8, @ptrFromInt(0)).* = 0; - raise(SIG.KILL) catch {}; - exit(127); // Pid 1 might not be signalled in some containers. - } - switch (builtin.os.tag) { - .uefi, .wasi, .emscripten, .cuda, .amdhsa => @trap(), - else => system.abort(), - } -} - -pub const RaiseError = UnexpectedError; - -pub fn raise(sig: u8) RaiseError!void { - if (builtin.link_libc) { - switch (errno(system.raise(sig))) { - .SUCCESS => return, - else => |err| return unexpectedErrno(err), - } - } - - if (builtin.os.tag == .linux) { - var set: sigset_t = undefined; - // block application signals - sigprocmask(SIG.BLOCK, &linux.app_mask, &set); - - const tid = linux.gettid(); - const rc = linux.tkill(tid, sig); - - // restore signal mask - sigprocmask(SIG.SETMASK, &set, null); - - switch (errno(rc)) { - .SUCCESS => return, - else => |err| return unexpectedErrno(err), - } - } - - @compileError("std.os.raise unimplemented for this target"); -} - -pub const KillError = error{ ProcessNotFound, PermissionDenied } || UnexpectedError; - -pub fn kill(pid: pid_t, sig: u8) KillError!void { - switch (errno(system.kill(pid, sig))) { - .SUCCESS => return, - .INVAL => unreachable, // invalid signal - .PERM => return error.PermissionDenied, - .SRCH => return error.ProcessNotFound, - else => |err| return unexpectedErrno(err), - } -} - -/// Exits the program cleanly with the specified status code. -pub fn exit(status: u8) noreturn { - if (builtin.link_libc) { - system.exit(status); - } - if (builtin.os.tag == .windows) { - windows.kernel32.ExitProcess(status); - } - if (builtin.os.tag == .wasi) { - wasi.proc_exit(status); - } - if (builtin.os.tag == .linux and !builtin.single_threaded) { - linux.exit_group(status); - } - if (builtin.os.tag == .uefi) { - // exit() is only available if exitBootServices() has not been called yet. - // This call to exit should not fail, so we don't care about its return value. - if (uefi.system_table.boot_services) |bs| { - _ = bs.exit(uefi.handle, @enumFromInt(status), 0, null); - } - // If we can't exit, reboot the system instead. - uefi.system_table.runtime_services.resetSystem(.ResetCold, @enumFromInt(status), 0, null); + else => unreachable, // made unreachable by isGetFdPathSupportedOnTarget above } - system.exit(status); } - -pub const ReadError = error{ - InputOutput, - SystemResources, - IsDir, - OperationAborted, - BrokenPipe, - ConnectionResetByPeer, - ConnectionTimedOut, - NotOpenForReading, - SocketNotConnected, - - /// This error occurs when no global event loop is configured, - /// and reading from the file descriptor would block. - WouldBlock, - - /// In WASI, this error occurs when the file descriptor does - /// not hold the required rights to read from it. - AccessDenied, -} || UnexpectedError; - -/// Returns the number of bytes that were read, which can be less than -/// buf.len. If 0 bytes were read, that means EOF. -/// If `fd` is opened in non blocking mode, the function will return error.WouldBlock -/// when EAGAIN is received. -/// -/// Linux has a limit on how many bytes may be transferred in one `read` 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 `read` man page. -/// The limit on Darwin is `0x7fffffff`, trying to read more than that returns EINVAL. -/// The corresponding POSIX limit is `math.maxInt(isize)`. -pub fn read(fd: fd_t, buf: []u8) ReadError!usize { - if (buf.len == 0) return 0; - if (builtin.os.tag == .windows) { - return windows.ReadFile(fd, buf, null); - } - if (builtin.os.tag == .wasi and !builtin.link_libc) { - const iovs = [1]iovec{iovec{ - .iov_base = buf.ptr, - .iov_len = buf.len, - }}; - - var nread: usize = undefined; - switch (wasi.fd_read(fd, &iovs, iovs.len, &nread)) { - .SUCCESS => return nread, - .INTR => unreachable, - .INVAL => unreachable, - .FAULT => unreachable, - .AGAIN => unreachable, - .BADF => return error.NotOpenForReading, // Can be a race condition. - .IO => return error.InputOutput, - .ISDIR => return error.IsDir, - .NOBUFS => return error.SystemResources, - .NOMEM => return error.SystemResources, - .NOTCONN => return error.SocketNotConnected, - .CONNRESET => return error.ConnectionResetByPeer, - .TIMEDOUT => return error.ConnectionTimedOut, - .NOTCAPABLE => return error.AccessDenied, - else => |err| return unexpectedErrno(err), - } - } - - // Prevents EINVAL. - const max_count = switch (builtin.os.tag) { - .linux => 0x7ffff000, - .macos, .ios, .watchos, .tvos => math.maxInt(i32), - else => math.maxInt(isize), - }; - while (true) { - const rc = system.read(fd, buf.ptr, @min(buf.len, max_count)); - switch (errno(rc)) { - .SUCCESS => return @intCast(rc), - .INTR => continue, - .INVAL => unreachable, - .FAULT => unreachable, - .AGAIN => return error.WouldBlock, - .BADF => return error.NotOpenForReading, // Can be a race condition. - .IO => return error.InputOutput, - .ISDIR => return error.IsDir, - .NOBUFS => return error.SystemResources, - .NOMEM => return error.SystemResources, - .NOTCONN => return error.SocketNotConnected, - .CONNRESET => return error.ConnectionResetByPeer, - .TIMEDOUT => return error.ConnectionTimedOut, - else => |err| return unexpectedErrno(err), - } - } -} - -/// Number of bytes read is returned. Upon reading end-of-file, zero is returned. -/// -/// 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. -/// -/// This operation is non-atomic on the following systems: -/// * Windows -/// On these systems, the read races with concurrent writes to the same file descriptor. -/// -/// This function assumes that all vectors, including zero-length vectors, have -/// a pointer within the address space of the application. -pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize { - if (builtin.os.tag == .windows) { - // TODO improve this to use ReadFileScatter - if (iov.len == 0) return 0; - const first = iov[0]; - return read(fd, first.iov_base[0..first.iov_len]); - } - if (builtin.os.tag == .wasi and !builtin.link_libc) { - var nread: usize = undefined; - switch (wasi.fd_read(fd, iov.ptr, iov.len, &nread)) { - .SUCCESS => return nread, - .INTR => unreachable, - .INVAL => unreachable, - .FAULT => unreachable, - .AGAIN => unreachable, // currently not support in WASI - .BADF => return error.NotOpenForReading, // can be a race condition - .IO => return error.InputOutput, - .ISDIR => return error.IsDir, - .NOBUFS => return error.SystemResources, - .NOMEM => return error.SystemResources, - .NOTCONN => return error.SocketNotConnected, - .CONNRESET => return error.ConnectionResetByPeer, - .TIMEDOUT => return error.ConnectionTimedOut, - .NOTCAPABLE => return error.AccessDenied, - else => |err| return unexpectedErrno(err), - } - } - - while (true) { - const rc = system.readv(fd, iov.ptr, @min(iov.len, IOV_MAX)); - switch (errno(rc)) { - .SUCCESS => return @intCast(rc), - .INTR => continue, - .INVAL => unreachable, - .FAULT => unreachable, - .AGAIN => return error.WouldBlock, - .BADF => return error.NotOpenForReading, // can be a race condition - .IO => return error.InputOutput, - .ISDIR => return error.IsDir, - .NOBUFS => return error.SystemResources, - .NOMEM => return error.SystemResources, - .NOTCONN => return error.SocketNotConnected, - .CONNRESET => return error.ConnectionResetByPeer, - .TIMEDOUT => return error.ConnectionTimedOut, - else => |err| return unexpectedErrno(err), - } - } -} - -pub const PReadError = ReadError || error{Unseekable}; - -/// Number of bytes read is returned. Upon reading end-of-file, zero is returned. -/// -/// Retries when interrupted by a signal. -/// -/// 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 `pread` 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 `read` man page. -/// The limit on Darwin is `0x7fffffff`, trying to read more than that returns EINVAL. -/// The corresponding POSIX limit is `math.maxInt(isize)`. -pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize { - if (buf.len == 0) return 0; - if (builtin.os.tag == .windows) { - return windows.ReadFile(fd, buf, offset); - } - if (builtin.os.tag == .wasi and !builtin.link_libc) { - const iovs = [1]iovec{iovec{ - .iov_base = buf.ptr, - .iov_len = buf.len, - }}; - - var nread: usize = undefined; - switch (wasi.fd_pread(fd, &iovs, iovs.len, offset, &nread)) { - .SUCCESS => return nread, - .INTR => unreachable, - .INVAL => unreachable, - .FAULT => unreachable, - .AGAIN => unreachable, - .BADF => return error.NotOpenForReading, // Can be a race condition. - .IO => return error.InputOutput, - .ISDIR => return error.IsDir, - .NOBUFS => return error.SystemResources, - .NOMEM => return error.SystemResources, - .NOTCONN => return error.SocketNotConnected, - .CONNRESET => return error.ConnectionResetByPeer, - .TIMEDOUT => return error.ConnectionTimedOut, - .NXIO => return error.Unseekable, - .SPIPE => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .NOTCAPABLE => return error.AccessDenied, - else => |err| return unexpectedErrno(err), - } - } - - // Prevent EINVAL. - const max_count = switch (builtin.os.tag) { - .linux => 0x7ffff000, - .macos, .ios, .watchos, .tvos => math.maxInt(i32), - else => math.maxInt(isize), - }; - - const pread_sym = if (lfs64_abi) system.pread64 else system.pread; - while (true) { - const rc = pread_sym(fd, buf.ptr, @min(buf.len, max_count), @bitCast(offset)); - switch (errno(rc)) { - .SUCCESS => return @intCast(rc), - .INTR => continue, - .INVAL => unreachable, - .FAULT => unreachable, - .AGAIN => return error.WouldBlock, - .BADF => return error.NotOpenForReading, // Can be a race condition. - .IO => return error.InputOutput, - .ISDIR => return error.IsDir, - .NOBUFS => return error.SystemResources, - .NOMEM => return error.SystemResources, - .NOTCONN => return error.SocketNotConnected, - .CONNRESET => return error.ConnectionResetByPeer, - .TIMEDOUT => return error.ConnectionTimedOut, - .NXIO => return error.Unseekable, - .SPIPE => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - else => |err| return unexpectedErrno(err), - } - } -} - -pub const TruncateError = error{ - FileTooBig, - InputOutput, - FileBusy, - - /// In WASI, this error occurs when the file descriptor does - /// not hold the required rights to call `ftruncate` on it. - AccessDenied, -} || UnexpectedError; - -pub fn ftruncate(fd: fd_t, length: u64) TruncateError!void { - if (builtin.os.tag == .windows) { - var io_status_block: windows.IO_STATUS_BLOCK = undefined; - var eof_info = windows.FILE_END_OF_FILE_INFORMATION{ - .EndOfFile = @bitCast(length), - }; - - const rc = windows.ntdll.NtSetInformationFile( - fd, - &io_status_block, - &eof_info, - @sizeOf(windows.FILE_END_OF_FILE_INFORMATION), - .FileEndOfFileInformation, - ); - - switch (rc) { - .SUCCESS => return, - .INVALID_HANDLE => unreachable, // Handle not open for writing - .ACCESS_DENIED => return error.AccessDenied, - else => return windows.unexpectedStatus(rc), - } - } - if (builtin.os.tag == .wasi and !builtin.link_libc) { - switch (wasi.fd_filestat_set_size(fd, length)) { - .SUCCESS => return, - .INTR => unreachable, - .FBIG => return error.FileTooBig, - .IO => return error.InputOutput, - .PERM => return error.AccessDenied, - .TXTBSY => return error.FileBusy, - .BADF => unreachable, // Handle not open for writing - .INVAL => unreachable, // Handle not open for writing - .NOTCAPABLE => return error.AccessDenied, - else => |err| return unexpectedErrno(err), - } - } - - const ftruncate_sym = if (lfs64_abi) system.ftruncate64 else system.ftruncate; - while (true) { - switch (errno(ftruncate_sym(fd, @bitCast(length)))) { - .SUCCESS => return, - .INTR => continue, - .FBIG => return error.FileTooBig, - .IO => return error.InputOutput, - .PERM => return error.AccessDenied, - .TXTBSY => return error.FileBusy, - .BADF => unreachable, // Handle not open for writing - .INVAL => unreachable, // Handle not open for writing - else => |err| return unexpectedErrno(err), - } - } -} - -/// Number of bytes read is returned. Upon reading end-of-file, zero is returned. -/// -/// Retries when interrupted by a signal. -/// -/// 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. -/// -/// This operation is non-atomic on the following systems: -/// * Darwin -/// * Windows -/// On these systems, the read races with concurrent writes to the same file descriptor. -pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) PReadError!usize { - const have_pread_but_not_preadv = switch (builtin.os.tag) { - .windows, .macos, .ios, .watchos, .tvos, .haiku => true, - else => false, - }; - if (have_pread_but_not_preadv) { - // We could loop here; but proper usage of `preadv` must handle partial reads anyway. - // So we simply read into the first vector only. - if (iov.len == 0) return 0; - const first = iov[0]; - return pread(fd, first.iov_base[0..first.iov_len], offset); - } - if (builtin.os.tag == .wasi and !builtin.link_libc) { - var nread: usize = undefined; - switch (wasi.fd_pread(fd, iov.ptr, iov.len, offset, &nread)) { - .SUCCESS => return nread, - .INTR => unreachable, - .INVAL => unreachable, - .FAULT => unreachable, - .AGAIN => unreachable, - .BADF => return error.NotOpenForReading, // can be a race condition - .IO => return error.InputOutput, - .ISDIR => return error.IsDir, - .NOBUFS => return error.SystemResources, - .NOMEM => return error.SystemResources, - .NOTCONN => return error.SocketNotConnected, - .CONNRESET => return error.ConnectionResetByPeer, - .TIMEDOUT => return error.ConnectionTimedOut, - .NXIO => return error.Unseekable, - .SPIPE => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .NOTCAPABLE => return error.AccessDenied, - else => |err| return unexpectedErrno(err), - } - } - - const preadv_sym = if (lfs64_abi) system.preadv64 else system.preadv; - while (true) { - const rc = preadv_sym(fd, iov.ptr, @min(iov.len, IOV_MAX), @bitCast(offset)); - switch (errno(rc)) { - .SUCCESS => return @bitCast(rc), - .INTR => continue, - .INVAL => unreachable, - .FAULT => unreachable, - .AGAIN => return error.WouldBlock, - .BADF => return error.NotOpenForReading, // can be a race condition - .IO => return error.InputOutput, - .ISDIR => return error.IsDir, - .NOBUFS => return error.SystemResources, - .NOMEM => return error.SystemResources, - .NOTCONN => return error.SocketNotConnected, - .CONNRESET => return error.ConnectionResetByPeer, - .TIMEDOUT => return error.ConnectionTimedOut, - .NXIO => return error.Unseekable, - .SPIPE => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - else => |err| return unexpectedErrno(err), - } - } -} - -pub const WriteError = error{ - DiskQuota, - FileTooBig, - InputOutput, - NoSpaceLeft, - DeviceBusy, - InvalidArgument, - - /// In WASI, this error may occur when the file descriptor does - /// not hold the required rights to write to it. - AccessDenied, - BrokenPipe, - SystemResources, - OperationAborted, - 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, -} || 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 `math.maxInt(isize)`. -pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize { - if (bytes.len == 0) return 0; - if (builtin.os.tag == .windows) { - return windows.WriteFile(fd, bytes, null); - } - - if (builtin.os.tag == .wasi and !builtin.link_libc) { - const ciovs = [_]iovec_const{iovec_const{ - .iov_base = bytes.ptr, - .iov_len = bytes.len, - }}; - var nwritten: usize = undefined; - switch (wasi.fd_write(fd, &ciovs, ciovs.len, &nwritten)) { - .SUCCESS => return nwritten, - .INTR => unreachable, - .INVAL => unreachable, - .FAULT => unreachable, - .AGAIN => unreachable, - .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, - .PERM => return error.AccessDenied, - .PIPE => return error.BrokenPipe, - .NOTCAPABLE => return error.AccessDenied, - else => |err| return unexpectedErrno(err), - } - } - - const max_count = switch (builtin.os.tag) { - .linux => 0x7ffff000, - .macos, .ios, .watchos, .tvos => math.maxInt(i32), - else => math.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, - .PERM => return error.AccessDenied, - .PIPE => return error.BrokenPipe, - .CONNRESET => return error.ConnectionResetByPeer, - .BUSY => return error.DeviceBusy, - else => |err| return unexpectedErrno(err), - } - } -} - -/// Write multiple buffers 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 bytes than supplied. 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. -/// -/// If `iov.len` is larger than `IOV_MAX`, a partial write will occur. -/// -/// This function assumes that all vectors, including zero-length vectors, have -/// a pointer within the address space of the application. -pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!usize { - if (builtin.os.tag == .windows) { - // TODO improve this to use WriteFileScatter - if (iov.len == 0) return 0; - const first = iov[0]; - return write(fd, first.iov_base[0..first.iov_len]); - } - if (builtin.os.tag == .wasi and !builtin.link_libc) { - var nwritten: usize = undefined; - switch (wasi.fd_write(fd, iov.ptr, iov.len, &nwritten)) { - .SUCCESS => return nwritten, - .INTR => unreachable, - .INVAL => unreachable, - .FAULT => unreachable, - .AGAIN => unreachable, - .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, - .PERM => return error.AccessDenied, - .PIPE => return error.BrokenPipe, - .NOTCAPABLE => return error.AccessDenied, - else => |err| return unexpectedErrno(err), - } - } - - while (true) { - const rc = system.writev(fd, iov.ptr, @min(iov.len, IOV_MAX)); - 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, - .PERM => return error.AccessDenied, - .PIPE => return error.BrokenPipe, - .CONNRESET => return error.ConnectionResetByPeer, - .BUSY => return error.DeviceBusy, - else => |err| return unexpectedErrno(err), - } - } -} - -pub const PWriteError = WriteError || error{Unseekable}; - -/// Write to a file descriptor, with a position offset. -/// 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 bytes than supplied. 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 `pwrite` 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 write more than that returns EINVAL. -/// The corresponding POSIX limit is `math.maxInt(isize)`. -pub fn pwrite(fd: fd_t, bytes: []const u8, offset: u64) PWriteError!usize { - if (bytes.len == 0) return 0; - if (builtin.os.tag == .windows) { - return windows.WriteFile(fd, bytes, offset); - } - if (builtin.os.tag == .wasi and !builtin.link_libc) { - const ciovs = [1]iovec_const{iovec_const{ - .iov_base = bytes.ptr, - .iov_len = bytes.len, - }}; - - var nwritten: usize = undefined; - switch (wasi.fd_pwrite(fd, &ciovs, ciovs.len, offset, &nwritten)) { - .SUCCESS => return nwritten, - .INTR => unreachable, - .INVAL => unreachable, - .FAULT => unreachable, - .AGAIN => unreachable, - .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, - .PERM => return error.AccessDenied, - .PIPE => return error.BrokenPipe, - .NXIO => return error.Unseekable, - .SPIPE => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .NOTCAPABLE => return error.AccessDenied, - else => |err| return unexpectedErrno(err), - } - } - - // Prevent EINVAL. - const max_count = switch (builtin.os.tag) { - .linux => 0x7ffff000, - .macos, .ios, .watchos, .tvos => math.maxInt(i32), - else => math.maxInt(isize), - }; - - const pwrite_sym = if (lfs64_abi) system.pwrite64 else system.pwrite; - while (true) { - const rc = pwrite_sym(fd, bytes.ptr, @min(bytes.len, max_count), @bitCast(offset)); - 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, - .PERM => return error.AccessDenied, - .PIPE => return error.BrokenPipe, - .NXIO => return error.Unseekable, - .SPIPE => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .BUSY => return error.DeviceBusy, - else => |err| return unexpectedErrno(err), - } - } -} - -/// Write multiple buffers to a file descriptor, with a position offset. -/// 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). -/// -/// If `fd` is opened in non blocking mode, the function will -/// return error.WouldBlock when EAGAIN is received. -/// -/// The following systems do not have this syscall, and will return partial writes if more than one -/// vector is provided: -/// * Darwin -/// * Windows -/// -/// If `iov.len` is larger than `IOV_MAX`, a partial write will occur. -pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) PWriteError!usize { - const have_pwrite_but_not_pwritev = switch (builtin.os.tag) { - .windows, .macos, .ios, .watchos, .tvos, .haiku => true, - else => false, - }; - - if (have_pwrite_but_not_pwritev) { - // We could loop here; but proper usage of `pwritev` must handle partial writes anyway. - // So we simply write the first vector only. - if (iov.len == 0) return 0; - const first = iov[0]; - return pwrite(fd, first.iov_base[0..first.iov_len], offset); - } - if (builtin.os.tag == .wasi and !builtin.link_libc) { - var nwritten: usize = undefined; - switch (wasi.fd_pwrite(fd, iov.ptr, iov.len, offset, &nwritten)) { - .SUCCESS => return nwritten, - .INTR => unreachable, - .INVAL => unreachable, - .FAULT => unreachable, - .AGAIN => unreachable, - .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, - .PERM => return error.AccessDenied, - .PIPE => return error.BrokenPipe, - .NXIO => return error.Unseekable, - .SPIPE => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .NOTCAPABLE => return error.AccessDenied, - else => |err| return unexpectedErrno(err), - } - } - - const pwritev_sym = if (lfs64_abi) system.pwritev64 else system.pwritev; - while (true) { - const rc = pwritev_sym(fd, iov.ptr, @min(iov.len, IOV_MAX), @bitCast(offset)); - 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, - .PERM => return error.AccessDenied, - .PIPE => return error.BrokenPipe, - .NXIO => return error.Unseekable, - .SPIPE => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .BUSY => return error.DeviceBusy, - else => |err| return unexpectedErrno(err), - } - } -} - -pub const OpenError = error{ - /// In WASI, this error may occur when the file descriptor does - /// not hold the required rights to open a new resource relative to it. - AccessDenied, - SymLinkLoop, - ProcessFdQuotaExceeded, - SystemFdQuotaExceeded, - NoDevice, - FileNotFound, - - /// The path exceeded `MAX_PATH_BYTES` bytes. - NameTooLong, - - /// Insufficient kernel memory was available, or - /// the named file is a FIFO and per-user hard limit on - /// memory allocation for pipes has been reached. - SystemResources, - - /// The file is too large to be opened. This error is unreachable - /// for 64-bit targets, as well as when opening directories. - FileTooBig, - - /// The path refers to directory but the `DIRECTORY` flag was not provided. - IsDir, - - /// A new path cannot be created because the device has no room for the new file. - /// This error is only reachable when the `CREAT` flag is provided. - NoSpaceLeft, - - /// A component used as a directory in the path was not, in fact, a directory, or - /// `DIRECTORY` was specified and the path was not a directory. - NotDir, - - /// The path already exists and the `CREAT` and `EXCL` flags were provided. - PathAlreadyExists, - DeviceBusy, - - /// The underlying filesystem does not support file locks - FileLocksNotSupported, - - /// Path contains characters that are disallowed by the underlying filesystem. - BadPathName, - - /// WASI-only; file paths must be valid UTF-8. - InvalidUtf8, - - /// Windows-only; file paths provided by the user must be valid WTF-8. - /// https://simonsapin.github.io/wtf-8/ - InvalidWtf8, - - /// On Windows, `\\server` or `\\server\share` was not found. - NetworkNotFound, - - /// One of these three things: - /// * pathname refers to an executable image which is currently being - /// executed and write access was requested. - /// * pathname refers to a file that is currently in use as a swap - /// file, and the O_TRUNC flag was specified. - /// * pathname refers to a file that is currently being read by the - /// kernel (e.g., for module/firmware loading), and write access was - /// requested. - FileBusy, - - WouldBlock, -} || UnexpectedError; - -/// Open and possibly create a file. Keeps trying if it gets interrupted. -/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). -/// 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 (builtin.os.tag == .windows) { - @compileError("Windows does not support POSIX; use Windows-specific API or cross-platform std.fs API"); - } else if (builtin.os.tag == .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://simonsapin.github.io/wtf-8/). -/// 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 (builtin.os.tag == .windows) { - @compileError("Windows does not support POSIX; use Windows-specific API or cross-platform std.fs API"); - } else if (builtin.os.tag == .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 => unreachable, - .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, - .NOMEM => return error.SystemResources, - .NOSPC => return error.NoSpaceLeft, - .NOTDIR => return error.NotDir, - .PERM => return error.AccessDenied, - .EXIST => return error.PathAlreadyExists, - .BUSY => return error.DeviceBusy, - 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://simonsapin.github.io/wtf-8/). -/// 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 `openatZ`. -pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: O, mode: mode_t) OpenError!fd_t { - if (builtin.os.tag == .windows) { - @compileError("Windows does not support POSIX; use Windows-specific API or cross-platform std.fs API"); - } else if (builtin.os.tag == .wasi and !builtin.link_libc) { - // `mode` is ignored on WASI, which does not support unix-style file permissions - const opts = try openOptionsFromFlagsWasi(flags); - const fd = try openatWasi( - dir_fd, - file_path, - opts.lookup_flags, - opts.oflags, - opts.fs_flags, - opts.fs_rights_base, - opts.fs_rights_inheriting, - ); - errdefer close(fd); - - if (flags.write) { - const info = try fstat_wasi(fd); - if (info.filetype == .DIRECTORY) - return error.IsDir; - } - - return fd; - } - const file_path_c = try toPosixPath(file_path); - return openatZ(dir_fd, &file_path_c, flags, mode); -} - -pub const CommonOpenFlags = packed struct { - ACCMODE: ACCMODE = .RDONLY, - CREAT: bool = false, - EXCL: bool = false, - LARGEFILE: bool = false, - DIRECTORY: bool = false, - CLOEXEC: bool = false, - NONBLOCK: bool = false, - - pub fn lower(cof: CommonOpenFlags) O { - if (builtin.os.tag == .wasi) return .{ - .read = cof.ACCMODE != .WRONLY, - .write = cof.ACCMODE != .RDONLY, - .CREAT = cof.CREAT, - .EXCL = cof.EXCL, - .DIRECTORY = cof.DIRECTORY, - .NONBLOCK = cof.NONBLOCK, - }; - var result: O = .{ - .ACCMODE = cof.ACCMODE, - .CREAT = cof.CREAT, - .EXCL = cof.EXCL, - .DIRECTORY = cof.DIRECTORY, - .NONBLOCK = cof.NONBLOCK, - .CLOEXEC = cof.CLOEXEC, - }; - if (@hasField(O, "LARGEFILE")) result.LARGEFILE = cof.LARGEFILE; - return result; - } -}; - -/// A struct to contain all lookup/rights flags accepted by `wasi.path_open` -const WasiOpenOptions = struct { - oflags: wasi.oflags_t, - lookup_flags: wasi.lookupflags_t, - fs_rights_base: wasi.rights_t, - fs_rights_inheriting: wasi.rights_t, - fs_flags: wasi.fdflags_t, -}; - -/// Compute rights + flags corresponding to the provided POSIX access mode. -fn openOptionsFromFlagsWasi(oflag: O) OpenError!WasiOpenOptions { - const w = std.os.wasi; - - // Next, calculate the read/write rights to request, depending on the - // provided POSIX access mode - var rights: w.rights_t = .{}; - if (oflag.read) { - rights.FD_READ = true; - rights.FD_READDIR = true; - } - if (oflag.write) { - rights.FD_DATASYNC = true; - rights.FD_WRITE = true; - rights.FD_ALLOCATE = true; - rights.FD_FILESTAT_SET_SIZE = true; - } - - // https://github.com/ziglang/zig/issues/18882 - const flag_bits: u32 = @bitCast(oflag); - const oflags_int: u16 = @as(u12, @truncate(flag_bits >> 12)); - const fs_flags_int: u16 = @as(u12, @truncate(flag_bits)); - - return .{ - // https://github.com/ziglang/zig/issues/18882 - .oflags = @bitCast(oflags_int), - .lookup_flags = .{ - .SYMLINK_FOLLOW = !oflag.NOFOLLOW, - }, - .fs_rights_base = rights, - .fs_rights_inheriting = rights, - // https://github.com/ziglang/zig/issues/18882 - .fs_flags = @bitCast(fs_flags_int), - }; -} - -/// Open and possibly create a file in WASI. -pub fn openatWasi( - dir_fd: fd_t, - file_path: []const u8, - lookup_flags: wasi.lookupflags_t, - oflags: wasi.oflags_t, - fdflags: wasi.fdflags_t, - base: wasi.rights_t, - inheriting: wasi.rights_t, -) OpenError!fd_t { - while (true) { - var fd: fd_t = undefined; - switch (wasi.path_open(dir_fd, lookup_flags, file_path.ptr, file_path.len, oflags, base, inheriting, fdflags, &fd)) { - .SUCCESS => return fd, - .INTR => continue, - - .FAULT => unreachable, - .INVAL => unreachable, - .BADF => unreachable, - .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, - .NOMEM => return error.SystemResources, - .NOSPC => return error.NoSpaceLeft, - .NOTDIR => return error.NotDir, - .PERM => return error.AccessDenied, - .EXIST => return error.PathAlreadyExists, - .BUSY => return error.DeviceBusy, - .NOTCAPABLE => return error.AccessDenied, - .ILSEQ => return error.InvalidUtf8, - 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://simonsapin.github.io/wtf-8/). -/// 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 `openat`. -pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: O, mode: mode_t) OpenError!fd_t { - if (builtin.os.tag == .windows) { - @compileError("Windows does not support POSIX; use Windows-specific API or cross-platform std.fs API"); - } else if (builtin.os.tag == .wasi and !builtin.link_libc) { - return openat(dir_fd, mem.sliceTo(file_path, 0), flags, mode); - } - - const openat_sym = if (lfs64_abi) system.openat64 else system.openat; - while (true) { - const rc = openat_sym(dir_fd, file_path, flags, mode); - switch (errno(rc)) { - .SUCCESS => return @intCast(rc), - .INTR => continue, - - .FAULT => unreachable, - .INVAL => unreachable, - .BADF => unreachable, - .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, - .NOMEM => return error.SystemResources, - .NOSPC => return error.NoSpaceLeft, - .NOTDIR => return error.NotDir, - .PERM => return error.AccessDenied, - .EXIST => return error.PathAlreadyExists, - .BUSY => return error.DeviceBusy, - .OPNOTSUPP => return error.FileLocksNotSupported, - .AGAIN => return error.WouldBlock, - .TXTBSY => return error.FileBusy, - else => |err| return unexpectedErrno(err), - } - } -} - -pub fn dup(old_fd: fd_t) !fd_t { - const rc = system.dup(old_fd); - return switch (errno(rc)) { - .SUCCESS => return @intCast(rc), - .MFILE => error.ProcessFdQuotaExceeded, - .BADF => unreachable, // invalid file descriptor - else => |err| return unexpectedErrno(err), - }; -} - -pub fn dup2(old_fd: fd_t, new_fd: fd_t) !void { - while (true) { - switch (errno(system.dup2(old_fd, new_fd))) { - .SUCCESS => return, - .BUSY, .INTR => continue, - .MFILE => return error.ProcessFdQuotaExceeded, - .INVAL => unreachable, // invalid parameters passed to dup2 - .BADF => unreachable, // invalid file descriptor - else => |err| return unexpectedErrno(err), - } - } -} - -pub const ExecveError = error{ - SystemResources, - AccessDenied, - InvalidExe, - FileSystem, - IsDir, - FileNotFound, - NotDir, - FileBusy, - ProcessFdQuotaExceeded, - SystemFdQuotaExceeded, - NameTooLong, -} || UnexpectedError; - -/// This function ignores PATH environment variable. See `execvpeZ` for that. -pub fn execveZ( - path: [*:0]const u8, - child_argv: [*:null]const ?[*:0]const u8, - envp: [*:null]const ?[*:0]const u8, -) ExecveError { - switch (errno(system.execve(path, child_argv, envp))) { - .SUCCESS => unreachable, - .FAULT => unreachable, - .@"2BIG" => return error.SystemResources, - .MFILE => return error.ProcessFdQuotaExceeded, - .NAMETOOLONG => return error.NameTooLong, - .NFILE => return error.SystemFdQuotaExceeded, - .NOMEM => return error.SystemResources, - .ACCES => return error.AccessDenied, - .PERM => return error.AccessDenied, - .INVAL => return error.InvalidExe, - .NOEXEC => return error.InvalidExe, - .IO => return error.FileSystem, - .LOOP => return error.FileSystem, - .ISDIR => return error.IsDir, - .NOENT => return error.FileNotFound, - .NOTDIR => return error.NotDir, - .TXTBSY => return error.FileBusy, - else => |err| switch (builtin.os.tag) { - .macos, .ios, .tvos, .watchos => switch (err) { - .BADEXEC => return error.InvalidExe, - .BADARCH => return error.InvalidExe, - else => return unexpectedErrno(err), - }, - .linux => switch (err) { - .LIBBAD => return error.InvalidExe, - else => return unexpectedErrno(err), - }, - else => return unexpectedErrno(err), - }, - } -} - -pub const Arg0Expand = enum { - expand, - no_expand, -}; - -/// Like `execvpeZ` except if `arg0_expand` is `.expand`, then `argv` is mutable, -/// and `argv[0]` is expanded to be the same absolute path that is passed to the execve syscall. -/// If this function returns with an error, `argv[0]` will be restored to the value it was when it was passed in. -pub fn execvpeZ_expandArg0( - comptime arg0_expand: Arg0Expand, - file: [*:0]const u8, - child_argv: switch (arg0_expand) { - .expand => [*:null]?[*:0]const u8, - .no_expand => [*:null]const ?[*:0]const u8, - }, - envp: [*:null]const ?[*:0]const u8, -) ExecveError { - const file_slice = mem.sliceTo(file, 0); - if (mem.indexOfScalar(u8, file_slice, '/') != null) return execveZ(file, child_argv, envp); - - const PATH = getenvZ("PATH") orelse "/usr/local/bin:/bin/:/usr/bin"; - // Use of MAX_PATH_BYTES here is valid as the path_buf will be passed - // directly to the operating system in execveZ. - var path_buf: [MAX_PATH_BYTES]u8 = undefined; - var it = mem.tokenizeScalar(u8, PATH, ':'); - var seen_eacces = false; - var err: ExecveError = error.FileNotFound; - - // In case of expanding arg0 we must put it back if we return with an error. - const prev_arg0 = child_argv[0]; - defer switch (arg0_expand) { - .expand => child_argv[0] = prev_arg0, - .no_expand => {}, - }; - - while (it.next()) |search_path| { - const path_len = search_path.len + file_slice.len + 1; - if (path_buf.len < path_len + 1) return error.NameTooLong; - @memcpy(path_buf[0..search_path.len], search_path); - path_buf[search_path.len] = '/'; - @memcpy(path_buf[search_path.len + 1 ..][0..file_slice.len], file_slice); - path_buf[path_len] = 0; - const full_path = path_buf[0..path_len :0].ptr; - switch (arg0_expand) { - .expand => child_argv[0] = full_path, - .no_expand => {}, - } - err = execveZ(full_path, child_argv, envp); - switch (err) { - error.AccessDenied => seen_eacces = true, - error.FileNotFound, error.NotDir => {}, - else => |e| return e, - } - } - if (seen_eacces) return error.AccessDenied; - return err; -} - -/// This function also uses the PATH environment variable to get the full path to the executable. -/// If `file` is an absolute path, this is the same as `execveZ`. -pub fn execvpeZ( - file: [*:0]const u8, - argv_ptr: [*:null]const ?[*:0]const u8, - envp: [*:null]const ?[*:0]const u8, -) ExecveError { - return execvpeZ_expandArg0(.no_expand, file, argv_ptr, envp); -} - -/// Get an environment variable. -/// See also `getenvZ`. -pub fn getenv(key: []const u8) ?[:0]const u8 { - if (builtin.os.tag == .windows) { - @compileError("std.os.getenv is unavailable for Windows because environment strings are in WTF-16 format. See std.process.getEnvVarOwned for a cross-platform API or std.os.getenvW for a Windows-specific API."); - } - if (builtin.link_libc) { - var ptr = std.c.environ; - while (ptr[0]) |line| : (ptr += 1) { - var line_i: usize = 0; - while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {} - const this_key = line[0..line_i]; - - if (!mem.eql(u8, this_key, key)) continue; - - return mem.sliceTo(line + line_i + 1, 0); - } - return null; - } - if (builtin.os.tag == .wasi) { - @compileError("std.os.getenv is unavailable for WASI. See std.process.getEnvMap or std.process.getEnvVarOwned for a cross-platform API."); - } - // The simplified start logic doesn't populate environ. - if (std.start.simplified_logic) return null; - // TODO see https://github.com/ziglang/zig/issues/4524 - for (environ) |ptr| { - var line_i: usize = 0; - while (ptr[line_i] != 0 and ptr[line_i] != '=') : (line_i += 1) {} - const this_key = ptr[0..line_i]; - if (!mem.eql(u8, key, this_key)) continue; - - return mem.sliceTo(ptr + line_i + 1, 0); - } - return null; -} - -/// Get an environment variable with a null-terminated name. -/// See also `getenv`. -pub fn getenvZ(key: [*:0]const u8) ?[:0]const u8 { - if (builtin.link_libc) { - const value = system.getenv(key) orelse return null; - return mem.sliceTo(value, 0); - } - if (builtin.os.tag == .windows) { - @compileError("std.os.getenvZ is unavailable for Windows because environment string is in WTF-16 format. See std.process.getEnvVarOwned for cross-platform API or std.os.getenvW for Windows-specific API."); - } - return getenv(mem.sliceTo(key, 0)); -} - -/// Windows-only. Get an environment variable with a null-terminated, WTF-16 encoded name. -/// See also `getenv`. -/// This function performs a Unicode-aware case-insensitive lookup using RtlEqualUnicodeString. -pub fn getenvW(key: [*:0]const u16) ?[:0]const u16 { - if (builtin.os.tag != .windows) { - @compileError("std.os.getenvW is a Windows-only API"); - } - const key_slice = mem.sliceTo(key, 0); - const ptr = windows.peb().ProcessParameters.Environment; - var i: usize = 0; - while (ptr[i] != 0) { - const key_start = i; - - // There are some special environment variables that start with =, - // so we need a special case to not treat = as a key/value separator - // if it's the first character. - // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133 - if (ptr[key_start] == '=') i += 1; - - while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {} - const this_key = ptr[key_start..i]; - - if (ptr[i] == '=') i += 1; - - const value_start = i; - while (ptr[i] != 0) : (i += 1) {} - const this_value = ptr[value_start..i :0]; - - if (windows.eqlIgnoreCaseWTF16(key_slice, this_key)) { - return this_value; - } - - i += 1; // skip over null byte - } - return null; -} - -pub const GetCwdError = error{ - NameTooLong, - CurrentWorkingDirectoryUnlinked, -} || UnexpectedError; - -/// The result is a slice of out_buffer, indexed from 0. -pub fn getcwd(out_buffer: []u8) GetCwdError![]u8 { - if (builtin.os.tag == .windows) { - return windows.GetCurrentDirectory(out_buffer); - } else if (builtin.os.tag == .wasi and !builtin.link_libc) { - const path = "."; - if (out_buffer.len < path.len) return error.NameTooLong; - const result = out_buffer[0..path.len]; - @memcpy(result, path); - return result; - } - - const err: E = if (builtin.link_libc) err: { - const c_err = if (std.c.getcwd(out_buffer.ptr, out_buffer.len)) |_| 0 else std.c._errno().*; - break :err @enumFromInt(c_err); - } else err: { - break :err errno(system.getcwd(out_buffer.ptr, out_buffer.len)); - }; - switch (err) { - .SUCCESS => return mem.sliceTo(out_buffer, 0), - .FAULT => unreachable, - .INVAL => unreachable, - .NOENT => return error.CurrentWorkingDirectoryUnlinked, - .RANGE => return error.NameTooLong, - else => return unexpectedErrno(err), - } -} - -pub const SymLinkError = error{ - /// In WASI, this error may occur when the file descriptor does - /// not hold the required rights to create a new symbolic link relative to it. - AccessDenied, - DiskQuota, - PathAlreadyExists, - FileSystem, - SymLinkLoop, - FileNotFound, - SystemResources, - NoSpaceLeft, - ReadOnlyFileSystem, - NotDir, - NameTooLong, - - /// WASI-only; file paths must be valid UTF-8. - InvalidUtf8, - - /// Windows-only; file paths provided by the user must be valid WTF-8. - /// https://simonsapin.github.io/wtf-8/ - InvalidWtf8, - - BadPathName, -} || UnexpectedError; - -/// Creates a symbolic link named `sym_link_path` which contains the string `target_path`. -/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent -/// one; the latter case is known as a dangling link. -/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). -/// On WASI, both paths should be encoded as valid UTF-8. -/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. -/// If `sym_link_path` exists, it will not be overwritten. -/// See also `symlinkZ. -pub fn symlink(target_path: []const u8, sym_link_path: []const u8) SymLinkError!void { - if (builtin.os.tag == .windows) { - @compileError("symlink is not supported on Windows; use std.os.windows.CreateSymbolicLink instead"); - } else if (builtin.os.tag == .wasi and !builtin.link_libc) { - return symlinkat(target_path, wasi.AT.FDCWD, sym_link_path); - } - const target_path_c = try toPosixPath(target_path); - const sym_link_path_c = try toPosixPath(sym_link_path); - return symlinkZ(&target_path_c, &sym_link_path_c); -} - -/// This is the same as `symlink` except the parameters are null-terminated pointers. -/// See also `symlink`. -pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLinkError!void { - if (builtin.os.tag == .windows) { - @compileError("symlink is not supported on Windows; use std.os.windows.CreateSymbolicLink instead"); - } else if (builtin.os.tag == .wasi and !builtin.link_libc) { - return symlinkatZ(target_path, fs.cwd().fd, sym_link_path); - } - switch (errno(system.symlink(target_path, sym_link_path))) { - .SUCCESS => return, - .FAULT => unreachable, - .INVAL => unreachable, - .ACCES => return error.AccessDenied, - .PERM => return error.AccessDenied, - .DQUOT => return error.DiskQuota, - .EXIST => return error.PathAlreadyExists, - .IO => return error.FileSystem, - .LOOP => return error.SymLinkLoop, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOTDIR => return error.NotDir, - .NOMEM => return error.SystemResources, - .NOSPC => return error.NoSpaceLeft, - .ROFS => return error.ReadOnlyFileSystem, - .ILSEQ => |err| if (builtin.os.tag == .wasi) - return error.InvalidUtf8 - else - return unexpectedErrno(err), - else => |err| return unexpectedErrno(err), - } -} - -/// Similar to `symlink`, however, creates a symbolic link named `sym_link_path` which contains the string -/// `target_path` **relative** to `newdirfd` directory handle. -/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent -/// one; the latter case is known as a dangling link. -/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). -/// On WASI, both paths should be encoded as valid UTF-8. -/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. -/// If `sym_link_path` exists, it will not be overwritten. -/// See also `symlinkatWasi`, `symlinkatZ` and `symlinkatW`. -pub fn symlinkat(target_path: []const u8, newdirfd: fd_t, sym_link_path: []const u8) SymLinkError!void { - if (builtin.os.tag == .windows) { - @compileError("symlinkat is not supported on Windows; use std.os.windows.CreateSymbolicLink instead"); - } else if (builtin.os.tag == .wasi and !builtin.link_libc) { - return symlinkatWasi(target_path, newdirfd, sym_link_path); - } - const target_path_c = try toPosixPath(target_path); - const sym_link_path_c = try toPosixPath(sym_link_path); - return symlinkatZ(&target_path_c, newdirfd, &sym_link_path_c); -} - -/// WASI-only. The same as `symlinkat` but targeting WASI. -/// See also `symlinkat`. -pub fn symlinkatWasi(target_path: []const u8, newdirfd: fd_t, sym_link_path: []const u8) SymLinkError!void { - switch (wasi.path_symlink(target_path.ptr, target_path.len, newdirfd, sym_link_path.ptr, sym_link_path.len)) { - .SUCCESS => {}, - .FAULT => unreachable, - .INVAL => unreachable, - .BADF => unreachable, - .ACCES => return error.AccessDenied, - .PERM => return error.AccessDenied, - .DQUOT => return error.DiskQuota, - .EXIST => return error.PathAlreadyExists, - .IO => return error.FileSystem, - .LOOP => return error.SymLinkLoop, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOTDIR => return error.NotDir, - .NOMEM => return error.SystemResources, - .NOSPC => return error.NoSpaceLeft, - .ROFS => return error.ReadOnlyFileSystem, - .NOTCAPABLE => return error.AccessDenied, - .ILSEQ => return error.InvalidUtf8, - else => |err| return unexpectedErrno(err), - } -} - -/// The same as `symlinkat` except the parameters are null-terminated pointers. -/// See also `symlinkat`. -pub fn symlinkatZ(target_path: [*:0]const u8, newdirfd: fd_t, sym_link_path: [*:0]const u8) SymLinkError!void { - if (builtin.os.tag == .windows) { - @compileError("symlinkat is not supported on Windows; use std.os.windows.CreateSymbolicLink instead"); - } else if (builtin.os.tag == .wasi and !builtin.link_libc) { - return symlinkat(mem.sliceTo(target_path, 0), newdirfd, mem.sliceTo(sym_link_path, 0)); - } - switch (errno(system.symlinkat(target_path, newdirfd, sym_link_path))) { - .SUCCESS => return, - .FAULT => unreachable, - .INVAL => unreachable, - .ACCES => return error.AccessDenied, - .PERM => return error.AccessDenied, - .DQUOT => return error.DiskQuota, - .EXIST => return error.PathAlreadyExists, - .IO => return error.FileSystem, - .LOOP => return error.SymLinkLoop, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOTDIR => return error.NotDir, - .NOMEM => return error.SystemResources, - .NOSPC => return error.NoSpaceLeft, - .ROFS => return error.ReadOnlyFileSystem, - .ILSEQ => |err| if (builtin.os.tag == .wasi) - return error.InvalidUtf8 - else - return unexpectedErrno(err), - else => |err| return unexpectedErrno(err), - } -} - -pub const LinkError = UnexpectedError || error{ - AccessDenied, - DiskQuota, - PathAlreadyExists, - FileSystem, - SymLinkLoop, - LinkQuotaExceeded, - NameTooLong, - FileNotFound, - SystemResources, - NoSpaceLeft, - ReadOnlyFileSystem, - NotSameFileSystem, - - /// WASI-only; file paths must be valid UTF-8. - InvalidUtf8, -}; - -/// On WASI, both paths should be encoded as valid UTF-8. -/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. -pub fn linkZ(oldpath: [*:0]const u8, newpath: [*:0]const u8, flags: i32) LinkError!void { - if (builtin.os.tag == .wasi and !builtin.link_libc) { - return link(mem.sliceTo(oldpath, 0), mem.sliceTo(newpath, 0), flags); - } - switch (errno(system.link(oldpath, newpath, flags))) { - .SUCCESS => return, - .ACCES => return error.AccessDenied, - .DQUOT => return error.DiskQuota, - .EXIST => return error.PathAlreadyExists, - .FAULT => unreachable, - .IO => return error.FileSystem, - .LOOP => return error.SymLinkLoop, - .MLINK => return error.LinkQuotaExceeded, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOMEM => return error.SystemResources, - .NOSPC => return error.NoSpaceLeft, - .PERM => return error.AccessDenied, - .ROFS => return error.ReadOnlyFileSystem, - .XDEV => return error.NotSameFileSystem, - .INVAL => unreachable, - .ILSEQ => |err| if (builtin.os.tag == .wasi) - return error.InvalidUtf8 - else - return unexpectedErrno(err), - else => |err| return unexpectedErrno(err), - } -} - -/// On WASI, both paths should be encoded as valid UTF-8. -/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. -pub fn link(oldpath: []const u8, newpath: []const u8, flags: i32) LinkError!void { - if (builtin.os.tag == .wasi and !builtin.link_libc) { - return linkat(wasi.AT.FDCWD, oldpath, wasi.AT.FDCWD, newpath, flags) catch |err| switch (err) { - error.NotDir => unreachable, // link() does not support directories - else => |e| return e, - }; - } - const old = try toPosixPath(oldpath); - const new = try toPosixPath(newpath); - return try linkZ(&old, &new, flags); -} - -pub const LinkatError = LinkError || error{NotDir}; - -/// On WASI, both paths should be encoded as valid UTF-8. -/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. -pub fn linkatZ( - olddir: fd_t, - oldpath: [*:0]const u8, - newdir: fd_t, - newpath: [*:0]const u8, - flags: i32, -) LinkatError!void { - if (builtin.os.tag == .wasi and !builtin.link_libc) { - return linkat(olddir, mem.sliceTo(oldpath, 0), newdir, mem.sliceTo(newpath, 0), flags); - } - switch (errno(system.linkat(olddir, oldpath, newdir, newpath, flags))) { - .SUCCESS => return, - .ACCES => return error.AccessDenied, - .DQUOT => return error.DiskQuota, - .EXIST => return error.PathAlreadyExists, - .FAULT => unreachable, - .IO => return error.FileSystem, - .LOOP => return error.SymLinkLoop, - .MLINK => return error.LinkQuotaExceeded, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOMEM => return error.SystemResources, - .NOSPC => return error.NoSpaceLeft, - .NOTDIR => return error.NotDir, - .PERM => return error.AccessDenied, - .ROFS => return error.ReadOnlyFileSystem, - .XDEV => return error.NotSameFileSystem, - .INVAL => unreachable, - .ILSEQ => |err| if (builtin.os.tag == .wasi) - return error.InvalidUtf8 - else - return unexpectedErrno(err), - else => |err| return unexpectedErrno(err), - } -} - -/// On WASI, both paths should be encoded as valid UTF-8. -/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. -pub fn linkat( - olddir: fd_t, - oldpath: []const u8, - newdir: fd_t, - newpath: []const u8, - flags: i32, -) LinkatError!void { - if (builtin.os.tag == .wasi and !builtin.link_libc) { - const old: RelativePathWasi = .{ .dir_fd = olddir, .relative_path = oldpath }; - const new: RelativePathWasi = .{ .dir_fd = newdir, .relative_path = newpath }; - const old_flags: wasi.lookupflags_t = .{ - .SYMLINK_FOLLOW = (flags & AT.SYMLINK_FOLLOW) != 0, - }; - switch (wasi.path_link( - old.dir_fd, - old_flags, - old.relative_path.ptr, - old.relative_path.len, - new.dir_fd, - new.relative_path.ptr, - new.relative_path.len, - )) { - .SUCCESS => return, - .ACCES => return error.AccessDenied, - .DQUOT => return error.DiskQuota, - .EXIST => return error.PathAlreadyExists, - .FAULT => unreachable, - .IO => return error.FileSystem, - .LOOP => return error.SymLinkLoop, - .MLINK => return error.LinkQuotaExceeded, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOMEM => return error.SystemResources, - .NOSPC => return error.NoSpaceLeft, - .NOTDIR => return error.NotDir, - .PERM => return error.AccessDenied, - .ROFS => return error.ReadOnlyFileSystem, - .XDEV => return error.NotSameFileSystem, - .INVAL => unreachable, - .ILSEQ => return error.InvalidUtf8, - else => |err| return unexpectedErrno(err), - } - } - const old = try toPosixPath(oldpath); - const new = try toPosixPath(newpath); - return try linkatZ(olddir, &old, newdir, &new, flags); -} - -pub const UnlinkError = error{ - FileNotFound, - - /// In WASI, this error may occur when the file descriptor does - /// not hold the required rights to unlink a resource by path relative to it. - AccessDenied, - FileBusy, - FileSystem, - IsDir, - SymLinkLoop, - NameTooLong, - NotDir, - SystemResources, - ReadOnlyFileSystem, - - /// WASI-only; file paths must be valid UTF-8. - InvalidUtf8, - - /// Windows-only; file paths provided by the user must be valid WTF-8. - /// https://simonsapin.github.io/wtf-8/ - InvalidWtf8, - - /// On Windows, file paths cannot contain these characters: - /// '/', '*', '?', '"', '<', '>', '|' - BadPathName, - - /// On Windows, `\\server` or `\\server\share` was not found. - NetworkNotFound, -} || UnexpectedError; - -/// Delete a name and possibly the file it refers to. -/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). -/// 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 `unlinkZ`. -pub fn unlink(file_path: []const u8) UnlinkError!void { - if (builtin.os.tag == .wasi and !builtin.link_libc) { - return unlinkat(wasi.AT.FDCWD, file_path, 0) catch |err| switch (err) { - error.DirNotEmpty => unreachable, // only occurs when targeting directories - else => |e| return e, - }; - } else if (builtin.os.tag == .windows) { - const file_path_w = try windows.sliceToPrefixedFileW(null, file_path); - return unlinkW(file_path_w.span()); - } else { - const file_path_c = try toPosixPath(file_path); - return unlinkZ(&file_path_c); - } -} - -/// Same as `unlink` except the parameter is null terminated. -pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void { - if (builtin.os.tag == .windows) { - const file_path_w = try windows.cStrToPrefixedFileW(null, file_path); - return unlinkW(file_path_w.span()); - } else if (builtin.os.tag == .wasi and !builtin.link_libc) { - return unlink(mem.sliceTo(file_path, 0)); - } - switch (errno(system.unlink(file_path))) { - .SUCCESS => return, - .ACCES => return error.AccessDenied, - .PERM => return error.AccessDenied, - .BUSY => return error.FileBusy, - .FAULT => unreachable, - .INVAL => unreachable, - .IO => return error.FileSystem, - .ISDIR => return error.IsDir, - .LOOP => return error.SymLinkLoop, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOTDIR => return error.NotDir, - .NOMEM => return error.SystemResources, - .ROFS => return error.ReadOnlyFileSystem, - .ILSEQ => |err| if (builtin.os.tag == .wasi) - return error.InvalidUtf8 - else - return unexpectedErrno(err), - else => |err| return unexpectedErrno(err), - } -} - -/// Windows-only. Same as `unlink` except the parameter is null-terminated, WTF16 LE encoded. -pub fn unlinkW(file_path_w: []const u16) UnlinkError!void { - windows.DeleteFile(file_path_w, .{ .dir = std.fs.cwd().fd }) catch |err| switch (err) { - error.DirNotEmpty => unreachable, // we're not passing .remove_dir = true - else => |e| return e, - }; -} - -pub const UnlinkatError = UnlinkError || error{ - /// When passing `AT.REMOVEDIR`, this error occurs when the named directory is not empty. - DirNotEmpty, -}; - -/// Delete a file name and possibly the file it refers to, based on an open directory handle. -/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). -/// 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. -/// Asserts that the path parameter has no null bytes. -pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void { - if (builtin.os.tag == .windows) { - const file_path_w = try windows.sliceToPrefixedFileW(dirfd, file_path); - return unlinkatW(dirfd, file_path_w.span(), flags); - } else if (builtin.os.tag == .wasi and !builtin.link_libc) { - return unlinkatWasi(dirfd, file_path, flags); - } else { - const file_path_c = try toPosixPath(file_path); - return unlinkatZ(dirfd, &file_path_c, flags); - } -} - -/// WASI-only. Same as `unlinkat` but targeting WASI. -/// See also `unlinkat`. -pub fn unlinkatWasi(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void { - const remove_dir = (flags & AT.REMOVEDIR) != 0; - const res = if (remove_dir) - wasi.path_remove_directory(dirfd, file_path.ptr, file_path.len) - else - wasi.path_unlink_file(dirfd, file_path.ptr, file_path.len); - switch (res) { - .SUCCESS => return, - .ACCES => return error.AccessDenied, - .PERM => return error.AccessDenied, - .BUSY => return error.FileBusy, - .FAULT => unreachable, - .IO => return error.FileSystem, - .ISDIR => return error.IsDir, - .LOOP => return error.SymLinkLoop, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOTDIR => return error.NotDir, - .NOMEM => return error.SystemResources, - .ROFS => return error.ReadOnlyFileSystem, - .NOTEMPTY => return error.DirNotEmpty, - .NOTCAPABLE => return error.AccessDenied, - .ILSEQ => return error.InvalidUtf8, - - .INVAL => unreachable, // invalid flags, or pathname has . as last component - .BADF => unreachable, // always a race condition - - else => |err| return unexpectedErrno(err), - } -} - -/// Same as `unlinkat` but `file_path` is a null-terminated string. -pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatError!void { - if (builtin.os.tag == .windows) { - const file_path_w = try windows.cStrToPrefixedFileW(dirfd, file_path_c); - return unlinkatW(dirfd, file_path_w.span(), flags); - } else if (builtin.os.tag == .wasi and !builtin.link_libc) { - return unlinkat(dirfd, mem.sliceTo(file_path_c, 0), flags); - } - switch (errno(system.unlinkat(dirfd, file_path_c, flags))) { - .SUCCESS => return, - .ACCES => return error.AccessDenied, - .PERM => return error.AccessDenied, - .BUSY => return error.FileBusy, - .FAULT => unreachable, - .IO => return error.FileSystem, - .ISDIR => return error.IsDir, - .LOOP => return error.SymLinkLoop, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOTDIR => return error.NotDir, - .NOMEM => return error.SystemResources, - .ROFS => return error.ReadOnlyFileSystem, - .EXIST => return error.DirNotEmpty, - .NOTEMPTY => return error.DirNotEmpty, - .ILSEQ => |err| if (builtin.os.tag == .wasi) - return error.InvalidUtf8 - else - return unexpectedErrno(err), - - .INVAL => unreachable, // invalid flags, or pathname has . as last component - .BADF => unreachable, // always a race condition - - else => |err| return unexpectedErrno(err), - } -} - -/// Same as `unlinkat` but `sub_path_w` is WTF16LE, NT prefixed. Windows only. -pub fn unlinkatW(dirfd: fd_t, sub_path_w: []const u16, flags: u32) UnlinkatError!void { - const remove_dir = (flags & AT.REMOVEDIR) != 0; - return windows.DeleteFile(sub_path_w, .{ .dir = dirfd, .remove_dir = remove_dir }); -} - -pub const RenameError = error{ - /// In WASI, this error may occur when the file descriptor does - /// not hold the required rights to rename a resource by path relative to it. - /// - /// On Windows, this error may be returned instead of PathAlreadyExists when - /// renaming a directory over an existing directory. - AccessDenied, - FileBusy, - DiskQuota, - IsDir, - SymLinkLoop, - LinkQuotaExceeded, - NameTooLong, - FileNotFound, - NotDir, - SystemResources, - NoSpaceLeft, - PathAlreadyExists, - ReadOnlyFileSystem, - RenameAcrossMountPoints, - /// WASI-only; file paths must be valid UTF-8. - InvalidUtf8, - /// Windows-only; file paths provided by the user must be valid WTF-8. - /// https://simonsapin.github.io/wtf-8/ - InvalidWtf8, - BadPathName, - NoDevice, - SharingViolation, - PipeBusy, - /// On Windows, `\\server` or `\\server\share` was not found. - NetworkNotFound, - /// On Windows, antivirus software is enabled by default. It can be - /// disabled, but Windows Update sometimes ignores the user's preference - /// and re-enables it. When enabled, antivirus software on Windows - /// intercepts file system operations and makes them significantly slower - /// in addition to possibly failing with this error code. - AntivirusInterference, -} || UnexpectedError; - -/// Change the name or location of a file. -/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). -/// On WASI, both paths should be encoded as valid UTF-8. -/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. -pub fn rename(old_path: []const u8, new_path: []const u8) RenameError!void { - if (builtin.os.tag == .wasi and !builtin.link_libc) { - return renameat(wasi.AT.FDCWD, old_path, wasi.AT.FDCWD, new_path); - } else if (builtin.os.tag == .windows) { - const old_path_w = try windows.sliceToPrefixedFileW(null, old_path); - const new_path_w = try windows.sliceToPrefixedFileW(null, new_path); - return renameW(old_path_w.span().ptr, new_path_w.span().ptr); - } else { - const old_path_c = try toPosixPath(old_path); - const new_path_c = try toPosixPath(new_path); - return renameZ(&old_path_c, &new_path_c); - } -} - -/// Same as `rename` except the parameters are null-terminated. -pub fn renameZ(old_path: [*:0]const u8, new_path: [*:0]const u8) RenameError!void { - if (builtin.os.tag == .windows) { - const old_path_w = try windows.cStrToPrefixedFileW(null, old_path); - const new_path_w = try windows.cStrToPrefixedFileW(null, new_path); - return renameW(old_path_w.span().ptr, new_path_w.span().ptr); - } else if (builtin.os.tag == .wasi and !builtin.link_libc) { - return rename(mem.sliceTo(old_path, 0), mem.sliceTo(new_path, 0)); - } - switch (errno(system.rename(old_path, new_path))) { - .SUCCESS => return, - .ACCES => return error.AccessDenied, - .PERM => return error.AccessDenied, - .BUSY => return error.FileBusy, - .DQUOT => return error.DiskQuota, - .FAULT => unreachable, - .INVAL => unreachable, - .ISDIR => return error.IsDir, - .LOOP => return error.SymLinkLoop, - .MLINK => return error.LinkQuotaExceeded, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOTDIR => return error.NotDir, - .NOMEM => return error.SystemResources, - .NOSPC => return error.NoSpaceLeft, - .EXIST => return error.PathAlreadyExists, - .NOTEMPTY => return error.PathAlreadyExists, - .ROFS => return error.ReadOnlyFileSystem, - .XDEV => return error.RenameAcrossMountPoints, - .ILSEQ => |err| if (builtin.os.tag == .wasi) - return error.InvalidUtf8 - else - return unexpectedErrno(err), - else => |err| return unexpectedErrno(err), - } -} - -/// Same as `rename` except the parameters are null-terminated and WTF16LE encoded. -/// Assumes target is Windows. -pub fn renameW(old_path: [*:0]const u16, new_path: [*:0]const u16) RenameError!void { - const flags = windows.MOVEFILE_REPLACE_EXISTING | windows.MOVEFILE_WRITE_THROUGH; - return windows.MoveFileExW(old_path, new_path, flags); -} - -/// Change the name or location of a file based on an open directory handle. -/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). -/// On WASI, both paths should be encoded as valid UTF-8. -/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. -pub fn renameat( - old_dir_fd: fd_t, - old_path: []const u8, - new_dir_fd: fd_t, - new_path: []const u8, -) RenameError!void { - if (builtin.os.tag == .windows) { - const old_path_w = try windows.sliceToPrefixedFileW(old_dir_fd, old_path); - const new_path_w = try windows.sliceToPrefixedFileW(new_dir_fd, new_path); - return renameatW(old_dir_fd, old_path_w.span(), new_dir_fd, new_path_w.span(), windows.TRUE); - } else if (builtin.os.tag == .wasi and !builtin.link_libc) { - const old: RelativePathWasi = .{ .dir_fd = old_dir_fd, .relative_path = old_path }; - const new: RelativePathWasi = .{ .dir_fd = new_dir_fd, .relative_path = new_path }; - return renameatWasi(old, new); - } else { - const old_path_c = try toPosixPath(old_path); - const new_path_c = try toPosixPath(new_path); - return renameatZ(old_dir_fd, &old_path_c, new_dir_fd, &new_path_c); - } -} - -/// WASI-only. Same as `renameat` expect targeting WASI. -/// See also `renameat`. -pub fn renameatWasi(old: RelativePathWasi, new: RelativePathWasi) RenameError!void { - switch (wasi.path_rename(old.dir_fd, old.relative_path.ptr, old.relative_path.len, new.dir_fd, new.relative_path.ptr, new.relative_path.len)) { - .SUCCESS => return, - .ACCES => return error.AccessDenied, - .PERM => return error.AccessDenied, - .BUSY => return error.FileBusy, - .DQUOT => return error.DiskQuota, - .FAULT => unreachable, - .INVAL => unreachable, - .ISDIR => return error.IsDir, - .LOOP => return error.SymLinkLoop, - .MLINK => return error.LinkQuotaExceeded, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOTDIR => return error.NotDir, - .NOMEM => return error.SystemResources, - .NOSPC => return error.NoSpaceLeft, - .EXIST => return error.PathAlreadyExists, - .NOTEMPTY => return error.PathAlreadyExists, - .ROFS => return error.ReadOnlyFileSystem, - .XDEV => return error.RenameAcrossMountPoints, - .NOTCAPABLE => return error.AccessDenied, - .ILSEQ => return error.InvalidUtf8, - else => |err| return unexpectedErrno(err), - } -} - -/// Same as `renameat` except the parameters are null-terminated. -pub fn renameatZ( - old_dir_fd: fd_t, - old_path: [*:0]const u8, - new_dir_fd: fd_t, - new_path: [*:0]const u8, -) RenameError!void { - if (builtin.os.tag == .windows) { - const old_path_w = try windows.cStrToPrefixedFileW(old_dir_fd, old_path); - const new_path_w = try windows.cStrToPrefixedFileW(new_dir_fd, new_path); - return renameatW(old_dir_fd, old_path_w.span(), new_dir_fd, new_path_w.span(), windows.TRUE); - } else if (builtin.os.tag == .wasi and !builtin.link_libc) { - return renameat(old_dir_fd, mem.sliceTo(old_path, 0), new_dir_fd, mem.sliceTo(new_path, 0)); - } - - switch (errno(system.renameat(old_dir_fd, old_path, new_dir_fd, new_path))) { - .SUCCESS => return, - .ACCES => return error.AccessDenied, - .PERM => return error.AccessDenied, - .BUSY => return error.FileBusy, - .DQUOT => return error.DiskQuota, - .FAULT => unreachable, - .INVAL => unreachable, - .ISDIR => return error.IsDir, - .LOOP => return error.SymLinkLoop, - .MLINK => return error.LinkQuotaExceeded, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOTDIR => return error.NotDir, - .NOMEM => return error.SystemResources, - .NOSPC => return error.NoSpaceLeft, - .EXIST => return error.PathAlreadyExists, - .NOTEMPTY => return error.PathAlreadyExists, - .ROFS => return error.ReadOnlyFileSystem, - .XDEV => return error.RenameAcrossMountPoints, - .ILSEQ => |err| if (builtin.os.tag == .wasi) - return error.InvalidUtf8 - else - return unexpectedErrno(err), - else => |err| return unexpectedErrno(err), - } -} - -/// Same as `renameat` but Windows-only and the path parameters are -/// [WTF-16](https://simonsapin.github.io/wtf-8/#potentially-ill-formed-utf-16) encoded. -pub fn renameatW( - old_dir_fd: fd_t, - old_path_w: []const u16, - new_dir_fd: fd_t, - new_path_w: []const u16, - ReplaceIfExists: windows.BOOLEAN, -) RenameError!void { - const src_fd = windows.OpenFile(old_path_w, .{ - .dir = old_dir_fd, - .access_mask = windows.SYNCHRONIZE | windows.GENERIC_WRITE | windows.DELETE, - .creation = windows.FILE_OPEN, - .filter = .any, // This function is supposed to rename both files and directories. - .follow_symlinks = false, - }) catch |err| switch (err) { - error.WouldBlock => unreachable, // Not possible without `.share_access_nonblocking = true`. - else => |e| return e, - }; - defer windows.CloseHandle(src_fd); - - var need_fallback = true; - var rc: windows.NTSTATUS = undefined; - // FILE_RENAME_INFORMATION_EX and FILE_RENAME_POSIX_SEMANTICS require >= win10_rs1, - // but FILE_RENAME_IGNORE_READONLY_ATTRIBUTE requires >= win10_rs5. We check >= rs5 here - // so that we only use POSIX_SEMANTICS when we know IGNORE_READONLY_ATTRIBUTE will also be - // supported in order to avoid either (1) using a redundant call that we can know in advance will return - // STATUS_NOT_SUPPORTED or (2) only setting IGNORE_READONLY_ATTRIBUTE when >= rs5 - // and therefore having different behavior when the Windows version is >= rs1 but < rs5. - if (builtin.target.os.isAtLeast(.windows, .win10_rs5) orelse false) { - const struct_buf_len = @sizeOf(windows.FILE_RENAME_INFORMATION_EX) + (MAX_PATH_BYTES - 1); - var rename_info_buf: [struct_buf_len]u8 align(@alignOf(windows.FILE_RENAME_INFORMATION_EX)) = undefined; - const struct_len = @sizeOf(windows.FILE_RENAME_INFORMATION_EX) - 1 + new_path_w.len * 2; - if (struct_len > struct_buf_len) return error.NameTooLong; - - const rename_info: *windows.FILE_RENAME_INFORMATION_EX = @ptrCast(&rename_info_buf); - var io_status_block: windows.IO_STATUS_BLOCK = undefined; - - var flags: windows.ULONG = windows.FILE_RENAME_POSIX_SEMANTICS | windows.FILE_RENAME_IGNORE_READONLY_ATTRIBUTE; - if (ReplaceIfExists == windows.TRUE) flags |= windows.FILE_RENAME_REPLACE_IF_EXISTS; - rename_info.* = .{ - .Flags = flags, - .RootDirectory = if (std.fs.path.isAbsoluteWindowsWTF16(new_path_w)) null else new_dir_fd, - .FileNameLength = @intCast(new_path_w.len * 2), // already checked error.NameTooLong - .FileName = undefined, - }; - @memcpy((&rename_info.FileName).ptr, new_path_w); - rc = windows.ntdll.NtSetInformationFile( - src_fd, - &io_status_block, - rename_info, - @intCast(struct_len), // already checked for error.NameTooLong - .FileRenameInformationEx, - ); - switch (rc) { - .SUCCESS => return, - // INVALID_PARAMETER here means that the filesystem does not support FileRenameInformationEx - .INVALID_PARAMETER => {}, - .DIRECTORY_NOT_EMPTY => return error.PathAlreadyExists, - .FILE_IS_A_DIRECTORY => return error.IsDir, - .NOT_A_DIRECTORY => return error.NotDir, - // For all other statuses, fall down to the switch below to handle them. - else => need_fallback = false, - } - } - - if (need_fallback) { - const struct_buf_len = @sizeOf(windows.FILE_RENAME_INFORMATION) + (MAX_PATH_BYTES - 1); - var rename_info_buf: [struct_buf_len]u8 align(@alignOf(windows.FILE_RENAME_INFORMATION)) = undefined; - const struct_len = @sizeOf(windows.FILE_RENAME_INFORMATION) - 1 + new_path_w.len * 2; - if (struct_len > struct_buf_len) return error.NameTooLong; - - const rename_info: *windows.FILE_RENAME_INFORMATION = @ptrCast(&rename_info_buf); - var io_status_block: windows.IO_STATUS_BLOCK = undefined; - - rename_info.* = .{ - .Flags = ReplaceIfExists, - .RootDirectory = if (std.fs.path.isAbsoluteWindowsWTF16(new_path_w)) null else new_dir_fd, - .FileNameLength = @intCast(new_path_w.len * 2), // already checked error.NameTooLong - .FileName = undefined, - }; - @memcpy((&rename_info.FileName).ptr, new_path_w); - - rc = - windows.ntdll.NtSetInformationFile( - src_fd, - &io_status_block, - rename_info, - @intCast(struct_len), // already checked for error.NameTooLong - .FileRenameInformation, - ); - } - - switch (rc) { - .SUCCESS => {}, - .INVALID_HANDLE => unreachable, - .INVALID_PARAMETER => unreachable, - .OBJECT_PATH_SYNTAX_BAD => unreachable, - .ACCESS_DENIED => return error.AccessDenied, - .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, - .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, - .NOT_SAME_DEVICE => return error.RenameAcrossMountPoints, - .OBJECT_NAME_COLLISION => return error.PathAlreadyExists, - else => return windows.unexpectedStatus(rc), - } -} - -/// On Windows, `sub_dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). -/// On WASI, `sub_dir_path` should be encoded as valid UTF-8. -/// On other platforms, `sub_dir_path` is an opaque sequence of bytes with no particular encoding. -pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!void { - if (builtin.os.tag == .windows) { - const sub_dir_path_w = try windows.sliceToPrefixedFileW(dir_fd, sub_dir_path); - return mkdiratW(dir_fd, sub_dir_path_w.span(), mode); - } else if (builtin.os.tag == .wasi and !builtin.link_libc) { - return mkdiratWasi(dir_fd, sub_dir_path, mode); - } else { - const sub_dir_path_c = try toPosixPath(sub_dir_path); - return mkdiratZ(dir_fd, &sub_dir_path_c, mode); - } -} - -pub fn mkdiratWasi(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!void { - _ = mode; - switch (wasi.path_create_directory(dir_fd, sub_dir_path.ptr, sub_dir_path.len)) { - .SUCCESS => return, - .ACCES => return error.AccessDenied, - .BADF => unreachable, - .PERM => return error.AccessDenied, - .DQUOT => return error.DiskQuota, - .EXIST => return error.PathAlreadyExists, - .FAULT => unreachable, - .LOOP => return error.SymLinkLoop, - .MLINK => return error.LinkQuotaExceeded, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOMEM => return error.SystemResources, - .NOSPC => return error.NoSpaceLeft, - .NOTDIR => return error.NotDir, - .ROFS => return error.ReadOnlyFileSystem, - .NOTCAPABLE => return error.AccessDenied, - .ILSEQ => return error.InvalidUtf8, - else => |err| return unexpectedErrno(err), - } -} - -/// Same as `mkdirat` except the parameters are null-terminated. -pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirError!void { - if (builtin.os.tag == .windows) { - const sub_dir_path_w = try windows.cStrToPrefixedFileW(dir_fd, sub_dir_path); - return mkdiratW(dir_fd, sub_dir_path_w.span(), mode); - } else if (builtin.os.tag == .wasi and !builtin.link_libc) { - return mkdirat(dir_fd, mem.sliceTo(sub_dir_path, 0), mode); - } - switch (errno(system.mkdirat(dir_fd, sub_dir_path, mode))) { - .SUCCESS => return, - .ACCES => return error.AccessDenied, - .BADF => unreachable, - .PERM => return error.AccessDenied, - .DQUOT => return error.DiskQuota, - .EXIST => return error.PathAlreadyExists, - .FAULT => unreachable, - .LOOP => return error.SymLinkLoop, - .MLINK => return error.LinkQuotaExceeded, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOMEM => return error.SystemResources, - .NOSPC => return error.NoSpaceLeft, - .NOTDIR => return error.NotDir, - .ROFS => return error.ReadOnlyFileSystem, - // dragonfly: when dir_fd is unlinked from filesystem - .NOTCONN => return error.FileNotFound, - .ILSEQ => |err| if (builtin.os.tag == .wasi) - return error.InvalidUtf8 - else - return unexpectedErrno(err), - else => |err| return unexpectedErrno(err), - } -} - -/// Windows-only. Same as `mkdirat` except the parameter WTF16 LE encoded. -pub fn mkdiratW(dir_fd: fd_t, sub_path_w: []const u16, mode: u32) MakeDirError!void { - _ = mode; - const sub_dir_handle = windows.OpenFile(sub_path_w, .{ - .dir = dir_fd, - .access_mask = windows.GENERIC_READ | windows.SYNCHRONIZE, - .creation = windows.FILE_CREATE, - .filter = .dir_only, - }) catch |err| switch (err) { - error.IsDir => return error.Unexpected, - error.PipeBusy => return error.Unexpected, - error.WouldBlock => return error.Unexpected, - error.AntivirusInterference => return error.Unexpected, - else => |e| return e, - }; - windows.CloseHandle(sub_dir_handle); -} - -pub const MakeDirError = error{ - /// In WASI, this error may occur when the file descriptor does - /// not hold the required rights to create a new directory relative to it. - AccessDenied, - DiskQuota, - PathAlreadyExists, - SymLinkLoop, - LinkQuotaExceeded, - NameTooLong, - FileNotFound, - SystemResources, - NoSpaceLeft, - NotDir, - ReadOnlyFileSystem, - /// WASI-only; file paths must be valid UTF-8. - InvalidUtf8, - /// Windows-only; file paths provided by the user must be valid WTF-8. - /// https://simonsapin.github.io/wtf-8/ - InvalidWtf8, - BadPathName, - NoDevice, - /// On Windows, `\\server` or `\\server\share` was not found. - NetworkNotFound, -} || UnexpectedError; - -/// Create a directory. -/// `mode` is ignored on Windows and WASI. -/// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). -/// On WASI, `dir_path` should be encoded as valid UTF-8. -/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding. -pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void { - if (builtin.os.tag == .wasi and !builtin.link_libc) { - return mkdirat(wasi.AT.FDCWD, dir_path, mode); - } else if (builtin.os.tag == .windows) { - const dir_path_w = try windows.sliceToPrefixedFileW(null, dir_path); - return mkdirW(dir_path_w.span(), mode); - } else { - const dir_path_c = try toPosixPath(dir_path); - return mkdirZ(&dir_path_c, mode); - } -} - -/// Same as `mkdir` but the parameter is null-terminated. -/// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). -/// On WASI, `dir_path` should be encoded as valid UTF-8. -/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding. -pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void { - if (builtin.os.tag == .windows) { - const dir_path_w = try windows.cStrToPrefixedFileW(null, dir_path); - return mkdirW(dir_path_w.span(), mode); - } else if (builtin.os.tag == .wasi and !builtin.link_libc) { - return mkdir(mem.sliceTo(dir_path, 0), mode); - } - switch (errno(system.mkdir(dir_path, mode))) { - .SUCCESS => return, - .ACCES => return error.AccessDenied, - .PERM => return error.AccessDenied, - .DQUOT => return error.DiskQuota, - .EXIST => return error.PathAlreadyExists, - .FAULT => unreachable, - .LOOP => return error.SymLinkLoop, - .MLINK => return error.LinkQuotaExceeded, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOMEM => return error.SystemResources, - .NOSPC => return error.NoSpaceLeft, - .NOTDIR => return error.NotDir, - .ROFS => return error.ReadOnlyFileSystem, - .ILSEQ => |err| if (builtin.os.tag == .wasi) - return error.InvalidUtf8 - else - return unexpectedErrno(err), - else => |err| return unexpectedErrno(err), - } -} - -/// Windows-only. Same as `mkdir` but the parameters is WTF16LE encoded. -pub fn mkdirW(dir_path_w: []const u16, mode: u32) MakeDirError!void { - _ = mode; - const sub_dir_handle = windows.OpenFile(dir_path_w, .{ - .dir = std.fs.cwd().fd, - .access_mask = windows.GENERIC_READ | windows.SYNCHRONIZE, - .creation = windows.FILE_CREATE, - .filter = .dir_only, - }) catch |err| switch (err) { - error.IsDir => return error.Unexpected, - error.PipeBusy => return error.Unexpected, - error.WouldBlock => return error.Unexpected, - error.AntivirusInterference => return error.Unexpected, - else => |e| return e, - }; - windows.CloseHandle(sub_dir_handle); -} - -pub const DeleteDirError = error{ - AccessDenied, - FileBusy, - SymLinkLoop, - NameTooLong, - FileNotFound, - SystemResources, - NotDir, - DirNotEmpty, - ReadOnlyFileSystem, - /// WASI-only; file paths must be valid UTF-8. - InvalidUtf8, - /// Windows-only; file paths provided by the user must be valid WTF-8. - /// https://simonsapin.github.io/wtf-8/ - InvalidWtf8, - BadPathName, - /// On Windows, `\\server` or `\\server\share` was not found. - NetworkNotFound, -} || UnexpectedError; - -/// Deletes an empty directory. -/// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). -/// On WASI, `dir_path` should be encoded as valid UTF-8. -/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding. -pub fn rmdir(dir_path: []const u8) DeleteDirError!void { - if (builtin.os.tag == .wasi and !builtin.link_libc) { - return unlinkat(wasi.AT.FDCWD, dir_path, AT.REMOVEDIR) catch |err| switch (err) { - error.FileSystem => unreachable, // only occurs when targeting files - error.IsDir => unreachable, // only occurs when targeting files - else => |e| return e, - }; - } else if (builtin.os.tag == .windows) { - const dir_path_w = try windows.sliceToPrefixedFileW(null, dir_path); - return rmdirW(dir_path_w.span()); - } else { - const dir_path_c = try toPosixPath(dir_path); - return rmdirZ(&dir_path_c); - } -} - -/// Same as `rmdir` except the parameter is null-terminated. -/// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). -/// On WASI, `dir_path` should be encoded as valid UTF-8. -/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding. -pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void { - if (builtin.os.tag == .windows) { - const dir_path_w = try windows.cStrToPrefixedFileW(null, dir_path); - return rmdirW(dir_path_w.span()); - } else if (builtin.os.tag == .wasi and !builtin.link_libc) { - return rmdir(mem.sliceTo(dir_path, 0)); - } - switch (errno(system.rmdir(dir_path))) { - .SUCCESS => return, - .ACCES => return error.AccessDenied, - .PERM => return error.AccessDenied, - .BUSY => return error.FileBusy, - .FAULT => unreachable, - .INVAL => return error.BadPathName, - .LOOP => return error.SymLinkLoop, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOMEM => return error.SystemResources, - .NOTDIR => return error.NotDir, - .EXIST => return error.DirNotEmpty, - .NOTEMPTY => return error.DirNotEmpty, - .ROFS => return error.ReadOnlyFileSystem, - .ILSEQ => |err| if (builtin.os.tag == .wasi) - return error.InvalidUtf8 - else - return unexpectedErrno(err), - else => |err| return unexpectedErrno(err), - } -} - -/// Windows-only. Same as `rmdir` except the parameter is WTF-16 LE encoded. -pub fn rmdirW(dir_path_w: []const u16) DeleteDirError!void { - return windows.DeleteFile(dir_path_w, .{ .dir = std.fs.cwd().fd, .remove_dir = true }) catch |err| switch (err) { - error.IsDir => unreachable, - else => |e| return e, - }; -} - -pub const ChangeCurDirError = error{ - AccessDenied, - FileSystem, - SymLinkLoop, - NameTooLong, - FileNotFound, - SystemResources, - NotDir, - BadPathName, - /// WASI-only; file paths must be valid UTF-8. - InvalidUtf8, - /// Windows-only; file paths provided by the user must be valid WTF-8. - /// https://simonsapin.github.io/wtf-8/ - InvalidWtf8, -} || UnexpectedError; - -/// Changes the current working directory of the calling process. -/// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). -/// On WASI, `dir_path` should be encoded as valid UTF-8. -/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding. -pub fn chdir(dir_path: []const u8) ChangeCurDirError!void { - if (builtin.os.tag == .wasi and !builtin.link_libc) { - @compileError("WASI does not support os.chdir"); - } else if (builtin.os.tag == .windows) { - var wtf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined; - const len = try std.unicode.wtf8ToWtf16Le(wtf16_dir_path[0..], dir_path); - if (len > wtf16_dir_path.len) return error.NameTooLong; - return chdirW(wtf16_dir_path[0..len]); - } else { - const dir_path_c = try toPosixPath(dir_path); - return chdirZ(&dir_path_c); - } -} - -/// Same as `chdir` except the parameter is null-terminated. -/// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). -/// On WASI, `dir_path` should be encoded as valid UTF-8. -/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding. -pub fn chdirZ(dir_path: [*:0]const u8) ChangeCurDirError!void { - if (builtin.os.tag == .windows) { - var wtf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined; - const len = try std.unicode.wtf8ToWtf16Le(wtf16_dir_path[0..], mem.span(dir_path)); - if (len > wtf16_dir_path.len) return error.NameTooLong; - return chdirW(wtf16_dir_path[0..len]); - } else if (builtin.os.tag == .wasi and !builtin.link_libc) { - return chdir(mem.span(dir_path)); - } - switch (errno(system.chdir(dir_path))) { - .SUCCESS => return, - .ACCES => return error.AccessDenied, - .FAULT => unreachable, - .IO => return error.FileSystem, - .LOOP => return error.SymLinkLoop, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOMEM => return error.SystemResources, - .NOTDIR => return error.NotDir, - .ILSEQ => |err| if (builtin.os.tag == .wasi) - return error.InvalidUtf8 - else - return unexpectedErrno(err), - else => |err| return unexpectedErrno(err), - } -} - -/// Windows-only. Same as `chdir` except the parameter is WTF16 LE encoded. -pub fn chdirW(dir_path: []const u16) ChangeCurDirError!void { - windows.SetCurrentDirectory(dir_path) catch |err| switch (err) { - error.NoDevice => return error.FileSystem, - else => |e| return e, - }; -} - -pub const FchdirError = error{ - AccessDenied, - NotDir, - FileSystem, -} || UnexpectedError; - -pub fn fchdir(dirfd: fd_t) FchdirError!void { - if (dirfd == AT.FDCWD) return; - while (true) { - switch (errno(system.fchdir(dirfd))) { - .SUCCESS => return, - .ACCES => return error.AccessDenied, - .BADF => unreachable, - .NOTDIR => return error.NotDir, - .INTR => continue, - .IO => return error.FileSystem, - else => |err| return unexpectedErrno(err), - } - } -} - -pub const ReadLinkError = error{ - /// In WASI, this error may occur when the file descriptor does - /// not hold the required rights to read value of a symbolic link relative to it. - AccessDenied, - FileSystem, - SymLinkLoop, - NameTooLong, - FileNotFound, - SystemResources, - NotLink, - NotDir, - /// WASI-only; file paths must be valid UTF-8. - InvalidUtf8, - /// Windows-only; file paths provided by the user must be valid WTF-8. - /// https://simonsapin.github.io/wtf-8/ - InvalidWtf8, - BadPathName, - /// Windows-only. This error may occur if the opened reparse point is - /// of unsupported type. - UnsupportedReparsePointType, - /// On Windows, `\\server` or `\\server\share` was not found. - NetworkNotFound, -} || UnexpectedError; - -/// Read value of a symbolic link. -/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). -/// 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. -/// The return value is a slice of `out_buffer` from index 0. -/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). -/// On WASI, the result is encoded as UTF-8. -/// On other platforms, the result is an opaque sequence of bytes with no particular encoding. -pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 { - if (builtin.os.tag == .wasi and !builtin.link_libc) { - return readlinkat(wasi.AT.FDCWD, file_path, out_buffer); - } else if (builtin.os.tag == .windows) { - const file_path_w = try windows.sliceToPrefixedFileW(null, file_path); - return readlinkW(file_path_w.span(), out_buffer); - } else { - const file_path_c = try toPosixPath(file_path); - return readlinkZ(&file_path_c, out_buffer); - } -} - -/// Windows-only. Same as `readlink` except `file_path` is WTF16 LE encoded. -/// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). -/// See also `readlinkZ`. -pub fn readlinkW(file_path: []const u16, out_buffer: []u8) ReadLinkError![]u8 { - return windows.ReadLink(std.fs.cwd().fd, file_path, out_buffer); -} - -/// Same as `readlink` except `file_path` is null-terminated. -pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 { - if (builtin.os.tag == .windows) { - const file_path_w = try windows.cStrToPrefixedFileW(null, file_path); - return readlinkW(file_path_w.span(), out_buffer); - } else if (builtin.os.tag == .wasi and !builtin.link_libc) { - return readlink(mem.sliceTo(file_path, 0), out_buffer); - } - const rc = system.readlink(file_path, out_buffer.ptr, out_buffer.len); - switch (errno(rc)) { - .SUCCESS => return out_buffer[0..@bitCast(rc)], - .ACCES => return error.AccessDenied, - .FAULT => unreachable, - .INVAL => return error.NotLink, - .IO => return error.FileSystem, - .LOOP => return error.SymLinkLoop, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOMEM => return error.SystemResources, - .NOTDIR => return error.NotDir, - .ILSEQ => |err| if (builtin.os.tag == .wasi) - return error.InvalidUtf8 - else - return unexpectedErrno(err), - else => |err| return unexpectedErrno(err), - } -} - -/// Similar to `readlink` except reads value of a symbolink link **relative** to `dirfd` directory handle. -/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). -/// 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. -/// The return value is a slice of `out_buffer` from index 0. -/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). -/// On WASI, the result is encoded as UTF-8. -/// On other platforms, the result is an opaque sequence of bytes with no particular encoding. -/// See also `readlinkatWasi`, `realinkatZ` and `realinkatW`. -pub fn readlinkat(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 { - if (builtin.os.tag == .wasi and !builtin.link_libc) { - return readlinkatWasi(dirfd, file_path, out_buffer); - } - if (builtin.os.tag == .windows) { - const file_path_w = try windows.sliceToPrefixedFileW(dirfd, file_path); - return readlinkatW(dirfd, file_path_w.span(), out_buffer); - } - const file_path_c = try toPosixPath(file_path); - return readlinkatZ(dirfd, &file_path_c, out_buffer); -} - -/// WASI-only. Same as `readlinkat` but targets WASI. -/// See also `readlinkat`. -pub fn readlinkatWasi(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 { - var bufused: usize = undefined; - switch (wasi.path_readlink(dirfd, file_path.ptr, file_path.len, out_buffer.ptr, out_buffer.len, &bufused)) { - .SUCCESS => return out_buffer[0..bufused], - .ACCES => return error.AccessDenied, - .FAULT => unreachable, - .INVAL => return error.NotLink, - .IO => return error.FileSystem, - .LOOP => return error.SymLinkLoop, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOMEM => return error.SystemResources, - .NOTDIR => return error.NotDir, - .NOTCAPABLE => return error.AccessDenied, - .ILSEQ => return error.InvalidUtf8, - else => |err| return unexpectedErrno(err), - } -} - -/// Windows-only. Same as `readlinkat` except `file_path` is null-terminated, WTF16 LE encoded. -/// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). -/// See also `readlinkat`. -pub fn readlinkatW(dirfd: fd_t, file_path: []const u16, out_buffer: []u8) ReadLinkError![]u8 { - return windows.ReadLink(dirfd, file_path, out_buffer); -} - -/// Same as `readlinkat` except `file_path` is null-terminated. -/// See also `readlinkat`. -pub fn readlinkatZ(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 { - if (builtin.os.tag == .windows) { - const file_path_w = try windows.cStrToPrefixedFileW(dirfd, file_path); - return readlinkatW(dirfd, file_path_w.span(), out_buffer); - } else if (builtin.os.tag == .wasi and !builtin.link_libc) { - return readlinkat(dirfd, mem.sliceTo(file_path, 0), out_buffer); - } - const rc = system.readlinkat(dirfd, file_path, out_buffer.ptr, out_buffer.len); - switch (errno(rc)) { - .SUCCESS => return out_buffer[0..@bitCast(rc)], - .ACCES => return error.AccessDenied, - .FAULT => unreachable, - .INVAL => return error.NotLink, - .IO => return error.FileSystem, - .LOOP => return error.SymLinkLoop, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOMEM => return error.SystemResources, - .NOTDIR => return error.NotDir, - .ILSEQ => |err| if (builtin.os.tag == .wasi) - return error.InvalidUtf8 - else - return unexpectedErrno(err), - else => |err| return unexpectedErrno(err), - } -} - -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 setreuid(ruid: uid_t, euid: uid_t) SetIdError!void { - switch (errno(system.setreuid(ruid, euid))) { - .SUCCESS => return, - .AGAIN => return error.ResourceLimitReached, - .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 setregid(rgid: gid_t, egid: gid_t) SetIdError!void { - switch (errno(system.setregid(rgid, egid))) { - .SUCCESS => return, - .AGAIN => return error.ResourceLimitReached, - .INVAL => return error.InvalidUserId, - .PERM => return error.PermissionDenied, - else => |err| return unexpectedErrno(err), - } -} - -/// Test whether a file descriptor refers to a terminal. -pub fn isatty(handle: fd_t) bool { - if (builtin.os.tag == .windows) { - if (isCygwinPty(handle)) - return true; - - var out: windows.DWORD = undefined; - return windows.kernel32.GetConsoleMode(handle, &out) != 0; - } - if (builtin.link_libc) { - return system.isatty(handle) != 0; - } - if (builtin.os.tag == .wasi) { - var statbuf: wasi.fdstat_t = undefined; - const err = wasi.fd_fdstat_get(handle, &statbuf); - if (err != .SUCCESS) - return false; - - // A tty is a character device that we can't seek or tell on. - if (statbuf.fs_filetype != .CHARACTER_DEVICE) - return false; - if (statbuf.fs_rights_base.FD_SEEK or statbuf.fs_rights_base.FD_TELL) - return false; - - return true; - } - if (builtin.os.tag == .linux) { - while (true) { - var wsz: linux.winsize = undefined; - const fd: usize = @bitCast(@as(isize, handle)); - const rc = linux.syscall3(.ioctl, fd, linux.T.IOCGWINSZ, @intFromPtr(&wsz)); - switch (linux.getErrno(rc)) { - .SUCCESS => return true, - .INTR => continue, - else => return false, - } - } - } - return system.isatty(handle) != 0; -} - -pub fn isCygwinPty(handle: fd_t) bool { - if (builtin.os.tag != .windows) return false; - - // If this is a MSYS2/cygwin pty, then it will be a named pipe with a name in one of these formats: - // msys-[...]-ptyN-[...] - // cygwin-[...]-ptyN-[...] - // - // Example: msys-1888ae32e00d56aa-pty0-to-master - - // First, just check that the handle is a named pipe. - // This allows us to avoid the more costly NtQueryInformationFile call - // for handles that aren't named pipes. - { - var io_status: windows.IO_STATUS_BLOCK = undefined; - var device_info: windows.FILE_FS_DEVICE_INFORMATION = undefined; - const rc = windows.ntdll.NtQueryVolumeInformationFile(handle, &io_status, &device_info, @sizeOf(windows.FILE_FS_DEVICE_INFORMATION), .FileFsDeviceInformation); - switch (rc) { - .SUCCESS => {}, - else => return false, - } - if (device_info.DeviceType != windows.FILE_DEVICE_NAMED_PIPE) return false; - } - - const name_bytes_offset = @offsetOf(windows.FILE_NAME_INFO, "FileName"); - // `NAME_MAX` UTF-16 code units (2 bytes each) - // Note: This buffer may not be long enough to handle *all* possible paths (PATH_MAX_WIDE would be necessary for that), - // but because we only care about certain paths and we know they must be within a reasonable length, - // we can use this smaller buffer and just return false on any error from NtQueryInformationFile. - const num_name_bytes = windows.MAX_PATH * 2; - var name_info_bytes align(@alignOf(windows.FILE_NAME_INFO)) = [_]u8{0} ** (name_bytes_offset + num_name_bytes); - - var io_status_block: windows.IO_STATUS_BLOCK = undefined; - const rc = windows.ntdll.NtQueryInformationFile(handle, &io_status_block, &name_info_bytes, @intCast(name_info_bytes.len), .FileNameInformation); - switch (rc) { - .SUCCESS => {}, - .INVALID_PARAMETER => unreachable, - else => return false, - } - - const name_info: *const windows.FILE_NAME_INFO = @ptrCast(&name_info_bytes); - const name_bytes = name_info_bytes[name_bytes_offset .. name_bytes_offset + name_info.FileNameLength]; - const name_wide = mem.bytesAsSlice(u16, name_bytes); - // Note: The name we get from NtQueryInformationFile will be prefixed with a '\', e.g. \msys-1888ae32e00d56aa-pty0-to-master - return (mem.startsWith(u16, name_wide, &[_]u16{ '\\', 'm', 's', 'y', 's', '-' }) or - mem.startsWith(u16, name_wide, &[_]u16{ '\\', 'c', 'y', 'g', 'w', 'i', 'n', '-' })) and - mem.indexOf(u16, name_wide, &[_]u16{ '-', 'p', 't', 'y' }) != null; -} - -pub const SocketError = error{ - /// Permission to create a socket of the specified type and/or - /// pro‐tocol is denied. - PermissionDenied, - - /// The implementation does not support the specified address family. - AddressFamilyNotSupported, - - /// Unknown protocol, or protocol family not available. - ProtocolFamilyNotAvailable, - - /// 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, - - /// Insufficient memory is available. The socket cannot be created until sufficient - /// resources are freed. - SystemResources, - - /// The protocol type or the specified protocol is not supported within this domain. - ProtocolNotSupported, - - /// The socket type is not supported by the protocol. - SocketTypeNotSupported, -} || UnexpectedError; - -pub fn socket(domain: u32, socket_type: u32, protocol: u32) SocketError!socket_t { - if (builtin.os.tag == .windows) { - // NOTE: windows translates the SOCK.NONBLOCK/SOCK.CLOEXEC flags into - // windows-analagous operations - const filtered_sock_type = socket_type & ~@as(u32, SOCK.NONBLOCK | SOCK.CLOEXEC); - const flags: u32 = if ((socket_type & SOCK.CLOEXEC) != 0) - windows.ws2_32.WSA_FLAG_NO_HANDLE_INHERIT - else - 0; - const rc = try windows.WSASocketW( - @bitCast(domain), - @bitCast(filtered_sock_type), - @bitCast(protocol), - null, - 0, - flags, - ); - errdefer windows.closesocket(rc) catch unreachable; - if ((socket_type & SOCK.NONBLOCK) != 0) { - var mode: c_ulong = 1; // nonblocking - if (windows.ws2_32.SOCKET_ERROR == windows.ws2_32.ioctlsocket(rc, windows.ws2_32.FIONBIO, &mode)) { - switch (windows.ws2_32.WSAGetLastError()) { - // have not identified any error codes that should be handled yet - else => unreachable, - } - } - } - return rc; - } - - const have_sock_flags = !builtin.target.isDarwin(); - 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.PermissionDenied, - .AFNOSUPPORT => return error.AddressFamilyNotSupported, - .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 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. - NetworkSubsystemFailed, - - /// The socket is not connected (connection-oriented sockets only). - SocketNotConnected, - 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 (builtin.os.tag == .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()) { - .WSAECONNABORTED => return error.ConnectionAborted, - .WSAECONNRESET => return error.ConnectionResetByPeer, - .WSAEINPROGRESS => return error.BlockingOperationInProgress, - .WSAEINVAL => unreachable, - .WSAENETDOWN => return error.NetworkSubsystemFailed, - .WSAENOTCONN => return error.SocketNotConnected, - .WSAENOTSOCK => unreachable, - .WSANOTINITIALISED => unreachable, - else => |err| return windows.unexpectedWSAError(err), - }; - } 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.SocketNotConnected, - .NOTSOCK => unreachable, - .NOBUFS => return error.SystemResources, - else => |err| return unexpectedErrno(err), - } - } -} - -pub const BindError = error{ - /// The address is protected, and the user is not the superuser. - /// For UNIX domain sockets: Search permission is denied on a component - /// of the path prefix. - AccessDenied, - - /// The given address is already in use, or in the case of Internet domain sockets, - /// The port number was specified as zero in the socket - /// address structure, but, upon attempting to bind 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 ip(7). - AddressInUse, - - /// A nonexistent interface was requested or the requested address was not local. - AddressNotAvailable, - - /// The address is not valid for the address family of socket. - AddressFamilyNotSupported, - - /// Too many symbolic links were encountered in resolving addr. - SymLinkLoop, - - /// addr is too long. - NameTooLong, - - /// A component in the directory prefix of the socket pathname does not exist. - FileNotFound, - - /// Insufficient kernel memory was available. - SystemResources, - - /// A component of the path prefix is not a directory. - NotDir, - - /// The socket inode would reside on a read-only filesystem. - ReadOnlyFileSystem, - - /// The network subsystem has failed. - NetworkSubsystemFailed, - - FileDescriptorNotASocket, - - AlreadyBound, -} || UnexpectedError; - -/// addr is `*const T` where T is one of the sockaddr -pub fn bind(sock: socket_t, addr: *const sockaddr, len: socklen_t) BindError!void { - if (builtin.os.tag == .windows) { - const rc = windows.bind(sock, addr, len); - if (rc == windows.ws2_32.SOCKET_ERROR) { - switch (windows.ws2_32.WSAGetLastError()) { - .WSANOTINITIALISED => unreachable, // not initialized WSA - .WSAEACCES => return error.AccessDenied, - .WSAEADDRINUSE => return error.AddressInUse, - .WSAEADDRNOTAVAIL => return error.AddressNotAvailable, - .WSAENOTSOCK => return error.FileDescriptorNotASocket, - .WSAEFAULT => unreachable, // invalid pointers - .WSAEINVAL => return error.AlreadyBound, - .WSAENOBUFS => return error.SystemResources, - .WSAENETDOWN => return error.NetworkSubsystemFailed, - else => |err| return windows.unexpectedWSAError(err), - } - unreachable; - } - return; - } 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.AddressFamilyNotSupported, - .ADDRNOTAVAIL => return error.AddressNotAvailable, - .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{ - /// Another socket is already listening on the same port. - /// For Internet domain 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). - AddressInUse, - - /// The file descriptor sockfd does not refer to a socket. - FileDescriptorNotASocket, - - /// The socket is not of a type that supports the listen() operation. - OperationNotSupported, - - /// The network subsystem has failed. - NetworkSubsystemFailed, - - /// Ran out of system resources - /// On Windows it can either run out of socket descriptors or buffer space - SystemResources, - - /// Already connected - AlreadyConnected, - - /// Socket has not been bound yet - SocketNotBound, -} || UnexpectedError; - -pub fn listen(sock: socket_t, backlog: u31) ListenError!void { - if (builtin.os.tag == .windows) { - const rc = windows.listen(sock, backlog); - if (rc == windows.ws2_32.SOCKET_ERROR) { - switch (windows.ws2_32.WSAGetLastError()) { - .WSANOTINITIALISED => unreachable, // not initialized WSA - .WSAENETDOWN => return error.NetworkSubsystemFailed, - .WSAEADDRINUSE => return error.AddressInUse, - .WSAEISCONN => return error.AlreadyConnected, - .WSAEINVAL => return error.SocketNotBound, - .WSAEMFILE, .WSAENOBUFS => return error.SystemResources, - .WSAENOTSOCK => return error.FileDescriptorNotASocket, - .WSAEOPNOTSUPP => return error.OperationNotSupported, - .WSAEINPROGRESS => unreachable, - else => |err| return windows.unexpectedWSAError(err), - } - } - return; - } 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.OperationNotSupported, - else => |err| return unexpectedErrno(err), - } - } -} - -pub const AcceptError = error{ - ConnectionAborted, - - /// The file descriptor sockfd does not refer to a socket. - FileDescriptorNotASocket, - - /// 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, - - /// Not enough free memory. This often means that the memory allocation is limited - /// by the socket buffer limits, not by the system memory. - SystemResources, - - /// Socket is not listening for new connections. - SocketNotListening, - - ProtocolFailure, - - /// Firewall rules forbid connection. - BlockedByFirewall, - - /// This error occurs when no global event loop is configured, - /// and accepting from the socket would block. - WouldBlock, - - /// An incoming connection was indicated, but was subsequently terminated by the - /// remote peer prior to accepting the call. - ConnectionResetByPeer, - - /// The network subsystem has failed. - NetworkSubsystemFailed, - - /// The referenced socket is not a type that supports connection-oriented service. - OperationNotSupported, -} || UnexpectedError; - -/// Accept a connection on a socket. -/// If `sockfd` is opened in non blocking mode, the function will -/// return error.WouldBlock when EAGAIN is received. -pub fn accept( - /// This argument is a socket that has been created with `socket`, bound to a local address - /// with `bind`, and is listening for connections after a `listen`. - sock: socket_t, - /// This argument is a pointer to a sockaddr structure. This structure is filled in with the - /// address of the peer socket, as known to the communications layer. The exact format of the - /// address returned addr is determined by the socket's address family (see `socket` and the - /// respective protocol man pages). - addr: ?*sockaddr, - /// This argument is a value-result argument: the caller must initialize it to contain the - /// size (in bytes) of the structure pointed to by addr; on return it will contain the actual size - /// of the peer address. - /// - /// The returned address is truncated if the buffer provided is too small; in this case, `addr_size` - /// will return a value greater than was supplied to the call. - addr_size: ?*socklen_t, - /// The following values can be bitwise ORed in flags to obtain different behavior: - /// * `SOCK.NONBLOCK` - Set the `NONBLOCK` file status flag on the open file description (see `open`) - /// referred to by the new file descriptor. Using this flag saves extra calls to `fcntl` to achieve - /// the same result. - /// * `SOCK.CLOEXEC` - Set the close-on-exec (`FD_CLOEXEC`) flag on the new file descriptor. See the - /// description of the `CLOEXEC` flag in `open` for reasons why this may be useful. - flags: u32, -) AcceptError!socket_t { - const have_accept4 = !(builtin.target.isDarwin() or builtin.os.tag == .windows); - 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 if (builtin.os.tag == .windows) - windows.accept(sock, addr, addr_size) - else - system.accept(sock, addr, addr_size); - - if (builtin.os.tag == .windows) { - if (rc == windows.ws2_32.INVALID_SOCKET) { - switch (windows.ws2_32.WSAGetLastError()) { - .WSANOTINITIALISED => unreachable, // not initialized WSA - .WSAECONNRESET => return error.ConnectionResetByPeer, - .WSAEFAULT => unreachable, - .WSAEINVAL => return error.SocketNotListening, - .WSAEMFILE => return error.ProcessFdQuotaExceeded, - .WSAENETDOWN => return error.NetworkSubsystemFailed, - .WSAENOBUFS => return error.FileDescriptorNotASocket, - .WSAEOPNOTSUPP => return error.OperationNotSupported, - .WSAEWOULDBLOCK => return error.WouldBlock, - else => |err| return windows.unexpectedWSAError(err), - } - } else { - break rc; - } - } 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 (builtin.os.tag) { - .windows => windows.closesocket(accepted_sock) catch unreachable, - else => close(accepted_sock), - }; - if (!have_accept4) { - try setSockFlags(accepted_sock, flags); - } - return accepted_sock; -} - -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: ?*linux.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: []linux.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, - SystemFdQuotaExceeded, -} || UnexpectedError; - -pub fn eventfd(initval: u32, flags: u32) EventFdError!i32 { - const rc = system.eventfd(initval, flags); - switch (errno(rc)) { - .SUCCESS => return @intCast(rc), - else => |err| return unexpectedErrno(err), - - .INVAL => unreachable, // invalid parameters - .MFILE => return error.ProcessFdQuotaExceeded, - .NFILE => return error.SystemFdQuotaExceeded, - .NODEV => return error.SystemResources, - .NOMEM => return error.SystemResources, - } -} - -pub const GetSockNameError = error{ - /// Insufficient resources were available in the system to perform the operation. - SystemResources, - - /// The network subsystem has failed. - NetworkSubsystemFailed, - - /// Socket hasn't been bound yet - SocketNotBound, - - FileDescriptorNotASocket, -} || UnexpectedError; - -pub fn getsockname(sock: socket_t, addr: *sockaddr, addrlen: *socklen_t) GetSockNameError!void { - if (builtin.os.tag == .windows) { - const rc = windows.getsockname(sock, addr, addrlen); - if (rc == windows.ws2_32.SOCKET_ERROR) { - switch (windows.ws2_32.WSAGetLastError()) { - .WSANOTINITIALISED => unreachable, - .WSAENETDOWN => return error.NetworkSubsystemFailed, - .WSAEFAULT => unreachable, // addr or addrlen have invalid pointers or addrlen points to an incorrect value - .WSAENOTSOCK => return error.FileDescriptorNotASocket, - .WSAEINVAL => 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 (builtin.os.tag == .windows) { - const rc = windows.getpeername(sock, addr, addrlen); - if (rc == windows.ws2_32.SOCKET_ERROR) { - switch (windows.ws2_32.WSAGetLastError()) { - .WSANOTINITIALISED => unreachable, - .WSAENETDOWN => return error.NetworkSubsystemFailed, - .WSAEFAULT => unreachable, // addr or addrlen have invalid pointers or addrlen points to an incorrect value - .WSAENOTSOCK => return error.FileDescriptorNotASocket, - .WSAEINVAL => return error.SocketNotBound, - else => |err| return windows.unexpectedWSAError(err), - } - } - return; - } else { - const rc = system.getpeername(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 const ConnectError = error{ - /// For UNIX domain sockets, which are identified by pathname: Write permission is denied on the socket - /// file, or search permission is denied for one of the directories in the path prefix. - /// or - /// The user tried to connect to a broadcast address without having the socket broadcast flag enabled or - /// the connection request failed because of a local firewall rule. - PermissionDenied, - - /// Local address is already in use. - AddressInUse, - - /// (Internet domain 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). - AddressNotAvailable, - - /// The passed address didn't have the correct address family in its sa_family field. - AddressFamilyNotSupported, - - /// Insufficient entries in the routing cache. - SystemResources, - - /// A connect() on a stream socket found no one listening on the remote address. - ConnectionRefused, - - /// Network is unreachable. - NetworkUnreachable, - - /// Timeout while attempting connection. The server may be too busy to accept new connections. Note - /// that for IP sockets the timeout may be very long when syncookies are enabled on the server. - ConnectionTimedOut, - - /// This error occurs when no global event loop is configured, - /// and connecting to the socket would block. - WouldBlock, - - /// The given path for the unix socket does not exist. - FileNotFound, - - /// Connection was reset by peer before connect could complete. - ConnectionResetByPeer, - - /// Socket is non-blocking and already has a pending connection in progress. - ConnectionPending, -} || UnexpectedError; - -/// Initiate a connection on a socket. -/// If `sockfd` is opened in non blocking mode, the function will -/// return error.WouldBlock when EAGAIN or EINPROGRESS is received. -pub fn connect(sock: socket_t, sock_addr: *const sockaddr, len: socklen_t) ConnectError!void { - if (builtin.os.tag == .windows) { - const rc = windows.ws2_32.connect(sock, sock_addr, @intCast(len)); - if (rc == 0) return; - switch (windows.ws2_32.WSAGetLastError()) { - .WSAEADDRINUSE => return error.AddressInUse, - .WSAEADDRNOTAVAIL => return error.AddressNotAvailable, - .WSAECONNREFUSED => return error.ConnectionRefused, - .WSAECONNRESET => return error.ConnectionResetByPeer, - .WSAETIMEDOUT => return error.ConnectionTimedOut, - .WSAEHOSTUNREACH, // TODO: should we return NetworkUnreachable in this case as well? - .WSAENETUNREACH, - => return error.NetworkUnreachable, - .WSAEFAULT => unreachable, - .WSAEINVAL => unreachable, - .WSAEISCONN => unreachable, - .WSAENOTSOCK => unreachable, - .WSAEWOULDBLOCK => return error.WouldBlock, - .WSAEACCES => unreachable, - .WSAENOBUFS => return error.SystemResources, - .WSAEAFNOSUPPORT => return error.AddressFamilyNotSupported, - else => |err| return windows.unexpectedWSAError(err), - } - return; - } - - while (true) { - switch (errno(system.connect(sock, sock_addr, len))) { - .SUCCESS => return, - .ACCES => return error.PermissionDenied, - .PERM => return error.PermissionDenied, - .ADDRINUSE => return error.AddressInUse, - .ADDRNOTAVAIL => return error.AddressNotAvailable, - .AFNOSUPPORT => return error.AddressFamilyNotSupported, - .AGAIN, .INPROGRESS => return error.WouldBlock, - .ALREADY => return error.ConnectionPending, - .BADF => unreachable, // sockfd is not a valid open file descriptor. - .CONNREFUSED => return error.ConnectionRefused, - .CONNRESET => return error.ConnectionResetByPeer, - .FAULT => unreachable, // The socket structure address is outside the user's address space. - .INTR => continue, - .ISCONN => unreachable, // 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.ConnectionTimedOut, - .NOENT => return error.FileNotFound, // Returned when socket is AF.UNIX and the given path does not exist. - .CONNABORTED => unreachable, // Tried to reuse socket that previously received error.ConnectionRefused. - 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.PermissionDenied, - .PERM => return error.PermissionDenied, - .ADDRINUSE => return error.AddressInUse, - .ADDRNOTAVAIL => return error.AddressNotAvailable, - .AFNOSUPPORT => return error.AddressFamilyNotSupported, - .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 => unreachable, // 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.ConnectionTimedOut, - .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 WaitPidResult = struct { - pid: pid_t, - status: u32, -}; - -/// Use this version of the `waitpid` wrapper if you spawned your child process using explicit -/// `fork` and `execve` method. -pub fn waitpid(pid: pid_t, flags: u32) WaitPidResult { - var status: if (builtin.link_libc) c_int else u32 = undefined; - while (true) { - const rc = system.waitpid(pid, &status, @intCast(flags)); - switch (errno(rc)) { - .SUCCESS => return .{ - .pid = @intCast(rc), - .status = @bitCast(status), - }, - .INTR => continue, - .CHILD => unreachable, // The process specified does not exist. It would be a race condition to handle this error. - .INVAL => unreachable, // Invalid flags. - else => unreachable, - } - } -} - -pub fn wait4(pid: pid_t, flags: u32, ru: ?*rusage) WaitPidResult { - var status: if (builtin.link_libc) c_int else u32 = undefined; - while (true) { - const rc = system.wait4(pid, &status, @intCast(flags), ru); - switch (errno(rc)) { - .SUCCESS => return .{ - .pid = @intCast(rc), - .status = @bitCast(status), - }, - .INTR => continue, - .CHILD => unreachable, // The process specified does not exist. It would be a race condition to handle this error. - .INVAL => unreachable, // Invalid flags. - else => unreachable, - } - } -} - -pub const FStatError = error{ - SystemResources, - - /// In WASI, this error may occur when the file descriptor does - /// not hold the required rights to get its filestat information. - AccessDenied, -} || UnexpectedError; - -/// Return information about a file descriptor. -pub fn fstat(fd: fd_t) FStatError!Stat { - if (builtin.os.tag == .wasi and !builtin.link_libc) { - return Stat.fromFilestat(try fstat_wasi(fd)); - } - if (builtin.os.tag == .windows) { - @compileError("fstat is not yet implemented on Windows"); - } - - const fstat_sym = if (lfs64_abi) system.fstat64 else system.fstat; - var stat = mem.zeroes(Stat); - switch (errno(fstat_sym(fd, &stat))) { - .SUCCESS => return stat, - .INVAL => unreachable, - .BADF => unreachable, // Always a race condition. - .NOMEM => return error.SystemResources, - .ACCES => return error.AccessDenied, - else => |err| return unexpectedErrno(err), - } -} - -pub fn fstat_wasi(fd: fd_t) FStatError!wasi.filestat_t { - var stat: wasi.filestat_t = undefined; - switch (wasi.fd_filestat_get(fd, &stat)) { - .SUCCESS => return stat, - .INVAL => unreachable, - .BADF => unreachable, // Always a race condition. - .NOMEM => return error.SystemResources, - .ACCES => return error.AccessDenied, - .NOTCAPABLE => return error.AccessDenied, - else => |err| return unexpectedErrno(err), - } -} - -pub const FStatAtError = FStatError || error{ - NameTooLong, - FileNotFound, - SymLinkLoop, - /// WASI-only; file paths must be valid UTF-8. - InvalidUtf8, -}; - -/// 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` and `fstatat_wasi`. -pub fn fstatat(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!Stat { - if (builtin.os.tag == .wasi and !builtin.link_libc) { - const filestat = try fstatat_wasi(dirfd, pathname, .{ - .SYMLINK_FOLLOW = (flags & AT.SYMLINK_NOFOLLOW) == 0, - }); - return Stat.fromFilestat(filestat); - } else if (builtin.os.tag == .windows) { - @compileError("fstatat is not yet implemented on Windows"); - } else { - const pathname_c = try toPosixPath(pathname); - return fstatatZ(dirfd, &pathname_c, flags); - } -} - -/// WASI-only. Same as `fstatat` but targeting WASI. -/// `pathname` should be encoded as valid UTF-8. -/// See also `fstatat`. -pub fn fstatat_wasi(dirfd: fd_t, pathname: []const u8, flags: wasi.lookupflags_t) FStatAtError!wasi.filestat_t { - var stat: wasi.filestat_t = undefined; - switch (wasi.path_filestat_get(dirfd, flags, pathname.ptr, pathname.len, &stat)) { - .SUCCESS => return stat, - .INVAL => unreachable, - .BADF => unreachable, // Always a race condition. - .NOMEM => return error.SystemResources, - .ACCES => return error.AccessDenied, - .FAULT => unreachable, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOTDIR => return error.FileNotFound, - .NOTCAPABLE => return error.AccessDenied, - .ILSEQ => return error.InvalidUtf8, - else => |err| return unexpectedErrno(err), - } -} - -/// 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 (builtin.os.tag == .wasi and !builtin.link_libc) { - const filestat = try fstatat_wasi(dirfd, mem.sliceTo(pathname, 0), .{ - .SYMLINK_FOLLOW = (flags & AT.SYMLINK_NOFOLLOW) == 0, - }); - return Stat.fromFilestat(filestat); - } - - const fstatat_sym = if (lfs64_abi) system.fstatat64 else system.fstatat; - var stat = mem.zeroes(Stat); - switch (errno(fstatat_sym(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.AccessDenied, - .FAULT => unreachable, - .NAMETOOLONG => return error.NameTooLong, - .LOOP => return error.SymLinkLoop, - .NOENT => return error.FileNotFound, - .NOTDIR => return error.FileNotFound, - .ILSEQ => |err| if (builtin.os.tag == .wasi) - return error.InvalidUtf8 - else - return unexpectedErrno(err), - else => |err| return unexpectedErrno(err), - } -} - -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, - - /// 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, - math.cast(c_int, changelist.len) orelse return error.Overflow, - eventlist.ptr, - math.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, - SystemResources, -} || UnexpectedError; - -/// initialize an inotify instance -pub fn inotify_init1(flags: u32) INotifyInitError!i32 { - const rc = system.inotify_init1(flags); - switch (errno(rc)) { - .SUCCESS => return @intCast(rc), - .INVAL => unreachable, - .MFILE => return error.ProcessFdQuotaExceeded, - .NFILE => return error.SystemFdQuotaExceeded, - .NOMEM => return error.SystemResources, - else => |err| return unexpectedErrno(err), - } -} - -pub const INotifyAddWatchError = error{ - AccessDenied, - NameTooLong, - FileNotFound, - SystemResources, - UserResourceLimitReached, - NotDir, - WatchAlreadyExists, -} || UnexpectedError; - -/// add a watch to an initialized inotify instance -pub fn inotify_add_watch(inotify_fd: i32, pathname: []const u8, mask: u32) INotifyAddWatchError!i32 { - const pathname_c = try toPosixPath(pathname); - return inotify_add_watchZ(inotify_fd, &pathname_c, mask); -} - -/// Same as `inotify_add_watch` except pathname is null-terminated. -pub fn inotify_add_watchZ(inotify_fd: i32, pathname: [*:0]const u8, mask: u32) INotifyAddWatchError!i32 { - const rc = system.inotify_add_watch(inotify_fd, pathname, mask); - switch (errno(rc)) { - .SUCCESS => return @intCast(rc), - .ACCES => return error.AccessDenied, - .BADF => unreachable, - .FAULT => unreachable, - .INVAL => unreachable, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOMEM => return error.SystemResources, - .NOSPC => return error.UserResourceLimitReached, - .NOTDIR => return error.NotDir, - .EXIST => return error.WatchAlreadyExists, - else => |err| return unexpectedErrno(err), - } -} - -/// remove an existing watch from an inotify instance -pub fn inotify_rm_watch(inotify_fd: i32, wd: i32) void { - switch (errno(system.inotify_rm_watch(inotify_fd, wd))) { - .SUCCESS => return, - .BADF => unreachable, - .INVAL => unreachable, - else => unreachable, - } -} - -pub const FanotifyInitError = error{ - ProcessFdQuotaExceeded, - SystemFdQuotaExceeded, - SystemResources, - OperationNotSupported, - PermissionDenied, -} || UnexpectedError; - -pub fn fanotify_init(flags: u32, event_f_flags: u32) FanotifyInitError!i32 { - const rc = system.fanotify_init(flags, event_f_flags); - switch (errno(rc)) { - .SUCCESS => return @intCast(rc), - .INVAL => unreachable, - .MFILE => return error.ProcessFdQuotaExceeded, - .NFILE => return error.SystemFdQuotaExceeded, - .NOMEM => return error.SystemResources, - .NOSYS => return error.OperationNotSupported, - .PERM => return error.PermissionDenied, - else => |err| return unexpectedErrno(err), - } -} - -pub const FanotifyMarkError = error{ - MarkAlreadyExists, - IsDir, - NotAssociatedWithFileSystem, - FileNotFound, - SystemResources, - UserMarkQuotaExceeded, - NotImplemented, - NotDir, - OperationNotSupported, - PermissionDenied, - NotSameFileSystem, - NameTooLong, -} || UnexpectedError; - -pub fn fanotify_mark(fanotify_fd: i32, flags: u32, mask: u64, dirfd: i32, pathname: ?[]const u8) FanotifyMarkError!void { - if (pathname) |path| { - const path_c = try toPosixPath(path); - return fanotify_markZ(fanotify_fd, flags, mask, dirfd, &path_c); - } - - return fanotify_markZ(fanotify_fd, flags, mask, dirfd, null); -} - -pub fn fanotify_markZ(fanotify_fd: i32, flags: u32, mask: u64, dirfd: i32, pathname: ?[*:0]const u8) FanotifyMarkError!void { - const rc = system.fanotify_mark(fanotify_fd, flags, mask, dirfd, pathname); - switch (errno(rc)) { - .SUCCESS => return, - .BADF => unreachable, - .EXIST => return error.MarkAlreadyExists, - .INVAL => unreachable, - .ISDIR => return error.IsDir, - .NODEV => return error.NotAssociatedWithFileSystem, - .NOENT => return error.FileNotFound, - .NOMEM => return error.SystemResources, - .NOSPC => return error.UserMarkQuotaExceeded, - .NOSYS => return error.NotImplemented, - .NOTDIR => return error.NotDir, - .OPNOTSUPP => return error.OperationNotSupported, - .PERM => return error.PermissionDenied, - .XDEV => return error.NotSameFileSystem, - else => |err| return unexpectedErrno(err), - } -} - -pub const MProtectError = error{ - /// The memory cannot be given the specified access. This can happen, for example, if you - /// mmap(2) a file to which you have read-only access, then ask mprotect() to mark it - /// PROT_WRITE. - AccessDenied, - - /// Changing the protection of a memory region would result in the total number of map‐ - /// pings with distinct attributes (e.g., read versus read/write protection) exceeding the - /// allowed maximum. (For example, making the protection of a range PROT_READ in the mid‐ - /// dle of a region currently protected as PROT_READ|PROT_WRITE would result in three map‐ - /// pings: two read/write mappings at each end and a read-only mapping in the middle.) - OutOfMemory, -} || UnexpectedError; - -/// `memory.len` must be page-aligned. -pub fn mprotect(memory: []align(mem.page_size) u8, protection: u32) MProtectError!void { - assert(mem.isAligned(memory.len, mem.page_size)); - if (builtin.os.tag == .windows) { - const win_prot: windows.DWORD = switch (@as(u3, @truncate(protection))) { - 0b000 => windows.PAGE_NOACCESS, - 0b001 => windows.PAGE_READONLY, - 0b010 => unreachable, // +w -r not allowed - 0b011 => windows.PAGE_READWRITE, - 0b100 => windows.PAGE_EXECUTE, - 0b101 => windows.PAGE_EXECUTE_READ, - 0b110 => unreachable, // +w -r not allowed - 0b111 => windows.PAGE_EXECUTE_READWRITE, - }; - var old: windows.DWORD = undefined; - windows.VirtualProtect(memory.ptr, memory.len, win_prot, &old) catch |err| switch (err) { - error.InvalidAddress => return error.AccessDenied, - error.Unexpected => return error.Unexpected, - }; - } else { - switch (errno(system.mprotect(memory.ptr, memory.len, protection))) { - .SUCCESS => return, - .INVAL => unreachable, - .ACCES => return error.AccessDenied, - .NOMEM => return error.OutOfMemory, - else => |err| return unexpectedErrno(err), - } - } -} - -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, - - /// A file descriptor refers to a non-regular file. Or a file mapping was requested, - /// but the file descriptor is not open for reading. Or `MAP.SHARED` was requested - /// and `PROT_WRITE` is set, but the file descriptor is not open in `RDWR` mode. - /// Or `PROT_WRITE` is set, but the file is append-only. - AccessDenied, - - /// The `prot` argument asks for `PROT_EXEC` but the mapped area belongs to a file on - /// a filesystem that was mounted no-exec. - PermissionDenied, - LockedMemoryLimitExceeded, - ProcessFdQuotaExceeded, - SystemFdQuotaExceeded, - OutOfMemory, -} || UnexpectedError; - -/// Map files or devices into memory. -/// `length` does not need to be aligned. -/// Use of a mapped region can result in these signals: -/// * SIGSEGV - Attempted write into a region mapped as read-only. -/// * SIGBUS - Attempted access to a portion of the buffer that does not correspond to the file -pub fn mmap( - ptr: ?[*]align(mem.page_size) u8, - length: usize, - prot: u32, - flags: system.MAP, - fd: fd_t, - offset: u64, -) MMapError![]align(mem.page_size) u8 { - const mmap_sym = if (lfs64_abi) system.mmap64 else system.mmap; - const rc = mmap_sym(ptr, length, prot, @bitCast(flags), fd, @bitCast(offset)); - const err: E = if (builtin.link_libc) blk: { - if (rc != std.c.MAP_FAILED) return @as([*]align(mem.page_size) u8, @ptrCast(@alignCast(rc)))[0..length]; - break :blk @enumFromInt(system._errno().*); - } else blk: { - const err = errno(rc); - if (err == .SUCCESS) return @as([*]align(mem.page_size) u8, @ptrFromInt(rc))[0..length]; - break :blk err; - }; - switch (err) { - .SUCCESS => unreachable, - .TXTBSY => return error.AccessDenied, - .ACCES => return error.AccessDenied, - .PERM => return error.PermissionDenied, - .AGAIN => return error.LockedMemoryLimitExceeded, - .BADF => unreachable, // Always a race condition. - .OVERFLOW => unreachable, // The number of pages used for length + offset would overflow. - .NODEV => return error.MemoryMappingNotSupported, - .INVAL => unreachable, // Invalid parameters to mmap() - .MFILE => return error.ProcessFdQuotaExceeded, - .NFILE => return error.SystemFdQuotaExceeded, - .NOMEM => return error.OutOfMemory, - else => return unexpectedErrno(err), - } -} - -/// Deletes the mappings for the specified address range, causing -/// further references to addresses within the range to generate invalid memory references. -/// Note that while POSIX allows unmapping a region in the middle of an existing mapping, -/// Zig's munmap function does not, for two reasons: -/// * It violates the Zig principle that resource deallocation must succeed. -/// * The Windows function, VirtualFree, has this restriction. -pub fn munmap(memory: []align(mem.page_size) const u8) void { - switch (errno(system.munmap(memory.ptr, memory.len))) { - .SUCCESS => return, - .INVAL => unreachable, // Invalid parameters. - .NOMEM => unreachable, // Attempted to unmap a region in the middle of an existing mapping. - else => unreachable, - } -} - -pub const MSyncError = error{ - UnmappedMemory, -} || UnexpectedError; - -pub fn msync(memory: []align(mem.page_size) u8, flags: i32) MSyncError!void { - switch (errno(system.msync(memory.ptr, memory.len, flags))) { - .SUCCESS => return, - .NOMEM => return error.UnmappedMemory, // Unsuccessful, provided pointer does not point mapped memory - .INVAL => unreachable, // Invalid parameters. - else => unreachable, - } -} - -pub const AccessError = error{ - PermissionDenied, - FileNotFound, - NameTooLong, - InputOutput, - SystemResources, - BadPathName, - FileBusy, - SymLinkLoop, - ReadOnlyFileSystem, - /// WASI-only; file paths must be valid UTF-8. - InvalidUtf8, - /// Windows-only; file paths provided by the user must be valid WTF-8. - /// https://simonsapin.github.io/wtf-8/ - InvalidWtf8, -} || UnexpectedError; - -/// check user's permissions for a file -/// On Windows, `path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). -/// On WASI, `path` should be encoded as valid UTF-8. -/// On other platforms, `path` is an opaque sequence of bytes with no particular encoding. -/// TODO currently this assumes `mode` is `F.OK` on Windows. -pub fn access(path: []const u8, mode: u32) AccessError!void { - if (builtin.os.tag == .windows) { - const path_w = windows.sliceToPrefixedFileW(null, path) catch |err| switch (err) { - error.AccessDenied => return error.PermissionDenied, - else => |e| return e, - }; - _ = try windows.GetFileAttributesW(path_w.span().ptr); - return; - } else if (builtin.os.tag == .wasi and !builtin.link_libc) { - return faccessat(wasi.AT.FDCWD, path, mode, 0); - } - const path_c = try toPosixPath(path); - return accessZ(&path_c, mode); -} - -/// Same as `access` except `path` is null-terminated. -pub fn accessZ(path: [*:0]const u8, mode: u32) AccessError!void { - if (builtin.os.tag == .windows) { - const path_w = windows.cStrToPrefixedFileW(null, path) catch |err| switch (err) { - error.AccessDenied => return error.PermissionDenied, - else => |e| return e, - }; - _ = try windows.GetFileAttributesW(path_w.span().ptr); - return; - } else if (builtin.os.tag == .wasi and !builtin.link_libc) { - return access(mem.sliceTo(path, 0), mode); - } - switch (errno(system.access(path, mode))) { - .SUCCESS => return, - .ACCES => return error.PermissionDenied, - .ROFS => return error.ReadOnlyFileSystem, - .LOOP => return error.SymLinkLoop, - .TXTBSY => return error.FileBusy, - .NOTDIR => return error.FileNotFound, - .NOENT => return error.FileNotFound, - .NAMETOOLONG => return error.NameTooLong, - .INVAL => unreachable, - .FAULT => unreachable, - .IO => return error.InputOutput, - .NOMEM => return error.SystemResources, - .ILSEQ => |err| if (builtin.os.tag == .wasi) - return error.InvalidUtf8 - else - return unexpectedErrno(err), - else => |err| return unexpectedErrno(err), - } -} - -/// Call from Windows-specific code if you already have a WTF-16LE encoded, null terminated string. -/// Otherwise use `access` or `accessZ`. -/// TODO currently this ignores `mode`. -pub fn accessW(path: [*:0]const u16, mode: u32) windows.GetFileAttributesError!void { - _ = mode; - const ret = try windows.GetFileAttributesW(path); - if (ret != windows.INVALID_FILE_ATTRIBUTES) { - return; - } - switch (windows.kernel32.GetLastError()) { - .FILE_NOT_FOUND => return error.FileNotFound, - .PATH_NOT_FOUND => return error.FileNotFound, - .ACCESS_DENIED => return error.PermissionDenied, - else => |err| return windows.unexpectedError(err), - } -} - -/// Check user's permissions for a file, based on an open directory handle. -/// On Windows, `path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). -/// On WASI, `path` should be encoded as valid UTF-8. -/// On other platforms, `path` is an opaque sequence of bytes with no particular encoding. -/// TODO currently this ignores `mode` and `flags` on Windows. -pub fn faccessat(dirfd: fd_t, path: []const u8, mode: u32, flags: u32) AccessError!void { - if (builtin.os.tag == .windows) { - const path_w = try windows.sliceToPrefixedFileW(dirfd, path); - return faccessatW(dirfd, path_w.span().ptr, mode, flags); - } else if (builtin.os.tag == .wasi and !builtin.link_libc) { - const resolved: RelativePathWasi = .{ .dir_fd = dirfd, .relative_path = path }; - - const st = blk: { - break :blk fstatat_wasi(dirfd, path, .{ - .SYMLINK_FOLLOW = (flags & AT.SYMLINK_NOFOLLOW) == 0, - }); - } catch |err| switch (err) { - error.AccessDenied => return error.PermissionDenied, - else => |e| return e, - }; - - if (mode != F_OK) { - var directory: wasi.fdstat_t = undefined; - if (wasi.fd_fdstat_get(resolved.dir_fd, &directory) != .SUCCESS) { - return error.PermissionDenied; - } - - var rights: wasi.rights_t = .{}; - if (mode & R_OK != 0) { - if (st.filetype == .DIRECTORY) { - rights.FD_READDIR = true; - } else { - rights.FD_READ = true; - } - } - if (mode & W_OK != 0) { - rights.FD_WRITE = true; - } - // No validation for X_OK - - // https://github.com/ziglang/zig/issues/18882 - const rights_int: u64 = @bitCast(rights); - const inheriting_int: u64 = @bitCast(directory.fs_rights_inheriting); - if ((rights_int & inheriting_int) != rights_int) { - return error.PermissionDenied; - } - } - return; - } - const path_c = try toPosixPath(path); - return faccessatZ(dirfd, &path_c, mode, flags); -} - -/// Same as `faccessat` except the path parameter is null-terminated. -pub fn faccessatZ(dirfd: fd_t, path: [*:0]const u8, mode: u32, flags: u32) AccessError!void { - if (builtin.os.tag == .windows) { - const path_w = try windows.cStrToPrefixedFileW(dirfd, path); - return faccessatW(dirfd, path_w.span().ptr, mode, flags); - } else if (builtin.os.tag == .wasi and !builtin.link_libc) { - return faccessat(dirfd, mem.sliceTo(path, 0), mode, flags); - } - switch (errno(system.faccessat(dirfd, path, mode, flags))) { - .SUCCESS => return, - .ACCES => return error.PermissionDenied, - .ROFS => return error.ReadOnlyFileSystem, - .LOOP => return error.SymLinkLoop, - .TXTBSY => return error.FileBusy, - .NOTDIR => return error.FileNotFound, - .NOENT => return error.FileNotFound, - .NAMETOOLONG => return error.NameTooLong, - .INVAL => unreachable, - .FAULT => unreachable, - .IO => return error.InputOutput, - .NOMEM => return error.SystemResources, - .ILSEQ => |err| if (builtin.os.tag == .wasi) - return error.InvalidUtf8 - else - return unexpectedErrno(err), - else => |err| return unexpectedErrno(err), - } -} - -/// Same as `faccessat` except asserts the target is Windows and the path parameter -/// is NtDll-prefixed, null-terminated, WTF-16 encoded. -/// TODO currently this ignores `mode` and `flags` -pub fn faccessatW(dirfd: fd_t, sub_path_w: [*:0]const u16, mode: u32, flags: u32) AccessError!void { - _ = mode; - _ = flags; - if (sub_path_w[0] == '.' and sub_path_w[1] == 0) { - return; - } - if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) { - return; - } - - const path_len_bytes = math.cast(u16, mem.sliceTo(sub_path_w, 0).len * 2) orelse return error.NameTooLong; - var nt_name = windows.UNICODE_STRING{ - .Length = path_len_bytes, - .MaximumLength = path_len_bytes, - .Buffer = @constCast(sub_path_w), - }; - var attr = windows.OBJECT_ATTRIBUTES{ - .Length = @sizeOf(windows.OBJECT_ATTRIBUTES), - .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w)) null else dirfd, - .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here. - .ObjectName = &nt_name, - .SecurityDescriptor = null, - .SecurityQualityOfService = null, - }; - var basic_info: windows.FILE_BASIC_INFORMATION = undefined; - switch (windows.ntdll.NtQueryAttributesFile(&attr, &basic_info)) { - .SUCCESS => return, - .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, - .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, - .OBJECT_NAME_INVALID => unreachable, - .INVALID_PARAMETER => unreachable, - .ACCESS_DENIED => return error.PermissionDenied, - .OBJECT_PATH_SYNTAX_BAD => unreachable, - else => |rc| return windows.unexpectedStatus(rc), - } -} - -pub const PipeError = error{ - SystemFdQuotaExceeded, - ProcessFdQuotaExceeded, -} || UnexpectedError; - -/// Creates a unidirectional data channel that can be used for interprocess communication. -pub fn pipe() PipeError![2]fd_t { - var fds: [2]fd_t = undefined; - switch (errno(system.pipe(&fds))) { - .SUCCESS => return fds, - .INVAL => unreachable, // Invalid parameters to pipe() - .FAULT => unreachable, // Invalid fds pointer - .NFILE => return error.SystemFdQuotaExceeded, - .MFILE => return error.ProcessFdQuotaExceeded, - else => |err| return unexpectedErrno(err), - } -} - -pub fn pipe2(flags: O) PipeError![2]fd_t { - if (@hasDecl(system, "pipe2")) { - var fds: [2]fd_t = undefined; - switch (errno(system.pipe2(&fds, flags))) { - .SUCCESS => return fds, - .INVAL => unreachable, // Invalid flags - .FAULT => unreachable, // Invalid fds pointer - .NFILE => return error.SystemFdQuotaExceeded, - .MFILE => return error.ProcessFdQuotaExceeded, - else => |err| return unexpectedErrno(err), - } - } - - const fds: [2]fd_t = try pipe(); - errdefer { - close(fds[0]); - close(fds[1]); - } - - // https://github.com/ziglang/zig/issues/18882 - if (@as(u32, @bitCast(flags)) == 0) - return fds; - - // CLOEXEC is special, it's a file descriptor flag and must be set using - // F.SETFD. - if (flags.CLOEXEC) { - for (fds) |fd| { - switch (errno(system.fcntl(fd, F.SETFD, @as(u32, FD_CLOEXEC)))) { - .SUCCESS => {}, - .INVAL => unreachable, // Invalid flags - .BADF => unreachable, // Always a race condition - else => |err| return unexpectedErrno(err), - } - } - } - - const new_flags: u32 = f: { - var new_flags = flags; - new_flags.CLOEXEC = false; - break :f @bitCast(new_flags); - }; - // Set every other flag affecting the file status using F.SETFL. - if (new_flags != 0) { - for (fds) |fd| { - switch (errno(system.fcntl(fd, F.SETFL, new_flags))) { - .SUCCESS => {}, - .INVAL => unreachable, // Invalid flags - .BADF => unreachable, // Always a race condition - else => |err| return unexpectedErrno(err), - } - } - } - - return fds; -} - -pub const SysCtlError = error{ - PermissionDenied, - SystemResources, - NameTooLong, - UnknownName, -} || UnexpectedError; - -pub fn sysctl( - name: []const c_int, - oldp: ?*anyopaque, - oldlenp: ?*usize, - newp: ?*anyopaque, - newlen: usize, -) SysCtlError!void { - if (builtin.os.tag == .wasi) { - @panic("unsupported"); // TODO should be compile error, not panic - } - if (builtin.os.tag == .haiku) { - @panic("unsupported"); // TODO should be compile error, not panic - } - - const name_len = math.cast(c_uint, name.len) orelse return error.NameTooLong; - switch (errno(system.sysctl(name.ptr, name_len, oldp, oldlenp, newp, newlen))) { - .SUCCESS => return, - .FAULT => unreachable, - .PERM => return error.PermissionDenied, - .NOMEM => return error.SystemResources, - .NOENT => return error.UnknownName, - else => |err| return unexpectedErrno(err), - } -} - -pub fn sysctlbynameZ( - name: [*:0]const u8, - oldp: ?*anyopaque, - oldlenp: ?*usize, - newp: ?*anyopaque, - newlen: usize, -) SysCtlError!void { - if (builtin.os.tag == .wasi) { - @panic("unsupported"); // TODO should be compile error, not panic - } - if (builtin.os.tag == .haiku) { - @panic("unsupported"); // TODO should be compile error, not panic - } - - switch (errno(system.sysctlbyname(name, oldp, oldlenp, newp, newlen))) { - .SUCCESS => return, - .FAULT => unreachable, - .PERM => return error.PermissionDenied, - .NOMEM => return error.SystemResources, - .NOENT => return error.UnknownName, - else => |err| return unexpectedErrno(err), - } -} - -pub fn gettimeofday(tv: ?*timeval, tz: ?*timezone) void { - switch (errno(system.gettimeofday(tv, tz))) { - .SUCCESS => return, - .INVAL => unreachable, - else => unreachable, - } -} - -pub const SeekError = error{ - Unseekable, - - /// In WASI, this error may occur when the file descriptor does - /// not hold the required rights to seek on it. - AccessDenied, -} || UnexpectedError; - -/// Repositions read/write file offset relative to the beginning. -pub fn lseek_SET(fd: fd_t, offset: u64) SeekError!void { - if (builtin.os.tag == .linux and !builtin.link_libc and @sizeOf(usize) == 4) { - var result: u64 = undefined; - switch (errno(system.llseek(fd, offset, &result, SEEK.SET))) { - .SUCCESS => return, - .BADF => unreachable, // always a race condition - .INVAL => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .SPIPE => return error.Unseekable, - .NXIO => return error.Unseekable, - else => |err| return unexpectedErrno(err), - } - } - if (builtin.os.tag == .windows) { - return windows.SetFilePointerEx_BEGIN(fd, offset); - } - if (builtin.os.tag == .wasi and !builtin.link_libc) { - var new_offset: wasi.filesize_t = undefined; - switch (wasi.fd_seek(fd, @bitCast(offset), .SET, &new_offset)) { - .SUCCESS => return, - .BADF => unreachable, // always a race condition - .INVAL => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .SPIPE => return error.Unseekable, - .NXIO => return error.Unseekable, - .NOTCAPABLE => return error.AccessDenied, - else => |err| return unexpectedErrno(err), - } - } - - const lseek_sym = if (lfs64_abi) system.lseek64 else system.lseek; - switch (errno(lseek_sym(fd, @bitCast(offset), SEEK.SET))) { - .SUCCESS => return, - .BADF => unreachable, // always a race condition - .INVAL => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .SPIPE => return error.Unseekable, - .NXIO => return error.Unseekable, - else => |err| return unexpectedErrno(err), - } -} - -/// Repositions read/write file offset relative to the current offset. -pub fn lseek_CUR(fd: fd_t, offset: i64) SeekError!void { - if (builtin.os.tag == .linux and !builtin.link_libc and @sizeOf(usize) == 4) { - var result: u64 = undefined; - switch (errno(system.llseek(fd, @bitCast(offset), &result, SEEK.CUR))) { - .SUCCESS => return, - .BADF => unreachable, // always a race condition - .INVAL => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .SPIPE => return error.Unseekable, - .NXIO => return error.Unseekable, - else => |err| return unexpectedErrno(err), - } - } - if (builtin.os.tag == .windows) { - return windows.SetFilePointerEx_CURRENT(fd, offset); - } - if (builtin.os.tag == .wasi and !builtin.link_libc) { - var new_offset: wasi.filesize_t = undefined; - switch (wasi.fd_seek(fd, offset, .CUR, &new_offset)) { - .SUCCESS => return, - .BADF => unreachable, // always a race condition - .INVAL => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .SPIPE => return error.Unseekable, - .NXIO => return error.Unseekable, - .NOTCAPABLE => return error.AccessDenied, - else => |err| return unexpectedErrno(err), - } - } - const lseek_sym = if (lfs64_abi) system.lseek64 else system.lseek; - switch (errno(lseek_sym(fd, @bitCast(offset), SEEK.CUR))) { - .SUCCESS => return, - .BADF => unreachable, // always a race condition - .INVAL => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .SPIPE => return error.Unseekable, - .NXIO => return error.Unseekable, - else => |err| return unexpectedErrno(err), - } -} - -/// Repositions read/write file offset relative to the end. -pub fn lseek_END(fd: fd_t, offset: i64) SeekError!void { - if (builtin.os.tag == .linux and !builtin.link_libc and @sizeOf(usize) == 4) { - var result: u64 = undefined; - switch (errno(system.llseek(fd, @bitCast(offset), &result, SEEK.END))) { - .SUCCESS => return, - .BADF => unreachable, // always a race condition - .INVAL => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .SPIPE => return error.Unseekable, - .NXIO => return error.Unseekable, - else => |err| return unexpectedErrno(err), - } - } - if (builtin.os.tag == .windows) { - return windows.SetFilePointerEx_END(fd, offset); - } - if (builtin.os.tag == .wasi and !builtin.link_libc) { - var new_offset: wasi.filesize_t = undefined; - switch (wasi.fd_seek(fd, offset, .END, &new_offset)) { - .SUCCESS => return, - .BADF => unreachable, // always a race condition - .INVAL => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .SPIPE => return error.Unseekable, - .NXIO => return error.Unseekable, - .NOTCAPABLE => return error.AccessDenied, - else => |err| return unexpectedErrno(err), - } - } - const lseek_sym = if (lfs64_abi) system.lseek64 else system.lseek; - switch (errno(lseek_sym(fd, @bitCast(offset), SEEK.END))) { - .SUCCESS => return, - .BADF => unreachable, // always a race condition - .INVAL => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .SPIPE => return error.Unseekable, - .NXIO => return error.Unseekable, - else => |err| return unexpectedErrno(err), - } -} - -/// Returns the read/write file offset relative to the beginning. -pub fn lseek_CUR_get(fd: fd_t) SeekError!u64 { - if (builtin.os.tag == .linux and !builtin.link_libc and @sizeOf(usize) == 4) { - var result: u64 = undefined; - switch (errno(system.llseek(fd, 0, &result, SEEK.CUR))) { - .SUCCESS => return result, - .BADF => unreachable, // always a race condition - .INVAL => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .SPIPE => return error.Unseekable, - .NXIO => return error.Unseekable, - else => |err| return unexpectedErrno(err), - } - } - if (builtin.os.tag == .windows) { - return windows.SetFilePointerEx_CURRENT_get(fd); - } - if (builtin.os.tag == .wasi and !builtin.link_libc) { - var new_offset: wasi.filesize_t = undefined; - switch (wasi.fd_seek(fd, 0, .CUR, &new_offset)) { - .SUCCESS => return new_offset, - .BADF => unreachable, // always a race condition - .INVAL => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .SPIPE => return error.Unseekable, - .NXIO => return error.Unseekable, - .NOTCAPABLE => return error.AccessDenied, - else => |err| return unexpectedErrno(err), - } - } - const lseek_sym = if (lfs64_abi) system.lseek64 else system.lseek; - const rc = lseek_sym(fd, 0, SEEK.CUR); - switch (errno(rc)) { - .SUCCESS => return @bitCast(rc), - .BADF => unreachable, // always a race condition - .INVAL => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .SPIPE => return error.Unseekable, - .NXIO => return error.Unseekable, - else => |err| return unexpectedErrno(err), - } -} - -pub const FcntlError = error{ - PermissionDenied, - FileBusy, - ProcessFdQuotaExceeded, - Locked, - DeadLock, - LockedRegionLimitExceeded, -} || UnexpectedError; - -pub fn fcntl(fd: fd_t, cmd: i32, arg: usize) FcntlError!usize { - while (true) { - const rc = system.fcntl(fd, cmd, arg); - switch (errno(rc)) { - .SUCCESS => return @intCast(rc), - .INTR => continue, - .AGAIN, .ACCES => return error.Locked, - .BADF => unreachable, - .BUSY => return error.FileBusy, - .INVAL => unreachable, // invalid parameters - .PERM => return error.PermissionDenied, - .MFILE => return error.ProcessFdQuotaExceeded, - .NOTDIR => unreachable, // invalid parameter - .DEADLK => return error.DeadLock, - .NOLCK => return error.LockedRegionLimitExceeded, - else => |err| return unexpectedErrno(err), - } - } -} - -fn setSockFlags(sock: socket_t, flags: u32) !void { - if ((flags & SOCK.CLOEXEC) != 0) { - if (builtin.os.tag == .windows) { - // TODO: Find out if this is supported for sockets - } else { - var fd_flags = fcntl(sock, F.GETFD, 0) catch |err| switch (err) { - error.FileBusy => unreachable, - error.Locked => unreachable, - error.PermissionDenied => unreachable, - error.DeadLock => unreachable, - error.LockedRegionLimitExceeded => unreachable, - else => |e| return e, - }; - fd_flags |= FD_CLOEXEC; - _ = fcntl(sock, F.SETFD, fd_flags) catch |err| switch (err) { - error.FileBusy => unreachable, - error.Locked => unreachable, - error.PermissionDenied => unreachable, - error.DeadLock => unreachable, - error.LockedRegionLimitExceeded => unreachable, - else => |e| return e, - }; - } - } - if ((flags & SOCK.NONBLOCK) != 0) { - if (builtin.os.tag == .windows) { - var mode: c_ulong = 1; - if (windows.ws2_32.ioctlsocket(sock, windows.ws2_32.FIONBIO, &mode) == windows.ws2_32.SOCKET_ERROR) { - switch (windows.ws2_32.WSAGetLastError()) { - .WSANOTINITIALISED => unreachable, - .WSAENETDOWN => return error.NetworkSubsystemFailed, - .WSAENOTSOCK => return error.FileDescriptorNotASocket, - // TODO: handle more errors - else => |err| return windows.unexpectedWSAError(err), - } - } - } else { - var fl_flags = fcntl(sock, F.GETFL, 0) catch |err| switch (err) { - error.FileBusy => unreachable, - error.Locked => unreachable, - error.PermissionDenied => unreachable, - error.DeadLock => unreachable, - error.LockedRegionLimitExceeded => unreachable, - else => |e| return e, - }; - fl_flags |= 1 << @bitOffsetOf(O, "NONBLOCK"); - _ = fcntl(sock, F.SETFL, fl_flags) catch |err| switch (err) { - error.FileBusy => unreachable, - error.Locked => unreachable, - error.PermissionDenied => unreachable, - error.DeadLock => unreachable, - error.LockedRegionLimitExceeded => unreachable, - else => |e| return e, - }; - } - } -} - -pub const FlockError = error{ - WouldBlock, - - /// The kernel ran out of memory for allocating file locks - SystemResources, - - /// The underlying filesystem does not support file locks - FileLocksNotSupported, -} || UnexpectedError; - -/// Depending on the operating system `flock` may or may not interact with -/// `fcntl` locks made by other processes. -pub fn flock(fd: fd_t, operation: i32) FlockError!void { - while (true) { - const rc = system.flock(fd, operation); - switch (errno(rc)) { - .SUCCESS => return, - .BADF => unreachable, - .INTR => continue, - .INVAL => unreachable, // invalid parameters - .NOLCK => return error.SystemResources, - .AGAIN => return error.WouldBlock, // TODO: integrate with async instead of just returning an error - .OPNOTSUPP => return error.FileLocksNotSupported, - else => |err| return unexpectedErrno(err), - } - } -} - -pub const RealPathError = error{ - FileNotFound, - AccessDenied, - NameTooLong, - NotSupported, - NotDir, - SymLinkLoop, - InputOutput, - FileTooBig, - IsDir, - ProcessFdQuotaExceeded, - SystemFdQuotaExceeded, - NoDevice, - SystemResources, - NoSpaceLeft, - FileSystem, - BadPathName, - DeviceBusy, - - SharingViolation, - PipeBusy, - - /// Windows-only; file paths provided by the user must be valid WTF-8. - /// https://simonsapin.github.io/wtf-8/ - InvalidWtf8, - - /// On Windows, `\\server` or `\\server\share` was not found. - NetworkNotFound, - - PathAlreadyExists, - - /// On Windows, antivirus software is enabled by default. It can be - /// disabled, but Windows Update sometimes ignores the user's preference - /// and re-enables it. When enabled, antivirus software on Windows - /// intercepts file system operations and makes them significantly slower - /// in addition to possibly failing with this error code. - AntivirusInterference, - - /// On Windows, the volume does not contain a recognized file system. File - /// system drivers might not be loaded, or the volume may be corrupt. - UnrecognizedVolume, -} || UnexpectedError; - -/// Return the canonicalized absolute pathname. -/// Expands all symbolic links and resolves references to `.`, `..`, and -/// extra `/` characters in `pathname`. -/// On Windows, `pathname` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). -/// On other platforms, `pathname` is an opaque sequence of bytes with no particular encoding. -/// The return value is a slice of `out_buffer`, but not necessarily from the beginning. -/// See also `realpathZ` and `realpathW`. -/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). -/// On other platforms, the result is an opaque sequence of bytes with no particular encoding. -/// Calling this function is usually a bug. -pub fn realpath(pathname: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 { - if (builtin.os.tag == .windows) { - const pathname_w = try windows.sliceToPrefixedFileW(null, pathname); - return realpathW(pathname_w.span(), out_buffer); - } else if (builtin.os.tag == .wasi and !builtin.link_libc) { - @compileError("WASI does not support os.realpath"); - } - const pathname_c = try toPosixPath(pathname); - return realpathZ(&pathname_c, out_buffer); -} - -/// Same as `realpath` except `pathname` is null-terminated. -/// Calling this function is usually a bug. -pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 { - if (builtin.os.tag == .windows) { - const pathname_w = try windows.cStrToPrefixedFileW(null, pathname); - return realpathW(pathname_w.span(), out_buffer); - } else if (builtin.os.tag == .wasi and !builtin.link_libc) { - return realpath(mem.sliceTo(pathname, 0), out_buffer); - } - if (!builtin.link_libc) { - const flags: O = switch (builtin.os.tag) { - .linux => .{ - .NONBLOCK = true, - .CLOEXEC = true, - .PATH = true, - }, - else => .{ - .NONBLOCK = true, - .CLOEXEC = true, - }, - }; - const fd = openZ(pathname, flags, 0) catch |err| switch (err) { - error.FileLocksNotSupported => unreachable, - error.WouldBlock => unreachable, - error.FileBusy => unreachable, // not asking for write permissions - error.InvalidUtf8 => unreachable, // WASI-only - else => |e| return e, - }; - defer close(fd); - - return getFdPath(fd, out_buffer); - } - const result_path = std.c.realpath(pathname, out_buffer) orelse switch (@as(E, @enumFromInt(std.c._errno().*))) { - .SUCCESS => unreachable, - .INVAL => unreachable, - .BADF => unreachable, - .FAULT => unreachable, - .ACCES => return error.AccessDenied, - .NOENT => return error.FileNotFound, - .OPNOTSUPP => return error.NotSupported, - .NOTDIR => return error.NotDir, - .NAMETOOLONG => return error.NameTooLong, - .LOOP => return error.SymLinkLoop, - .IO => return error.InputOutput, - else => |err| return unexpectedErrno(err), - }; - return mem.sliceTo(result_path, 0); -} - -/// Same as `realpath` except `pathname` is WTF16LE-encoded. -/// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). -/// Calling this function is usually a bug. -pub fn realpathW(pathname: []const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 { - const w = windows; - - const dir = std.fs.cwd().fd; - const access_mask = w.GENERIC_READ | w.SYNCHRONIZE; - const share_access = w.FILE_SHARE_READ; - const creation = w.FILE_OPEN; - const h_file = blk: { - const res = w.OpenFile(pathname, .{ - .dir = dir, - .access_mask = access_mask, - .share_access = share_access, - .creation = creation, - .filter = .any, - }) catch |err| switch (err) { - error.WouldBlock => unreachable, - else => |e| return e, - }; - break :blk res; - }; - defer w.CloseHandle(h_file); - - return getFdPath(h_file, out_buffer); -} - -pub fn isGetFdPathSupportedOnTarget(os: std.Target.Os) bool { - return switch (os.tag) { - .windows, - .macos, - .ios, - .watchos, - .tvos, - .linux, - .solaris, - .illumos, - .freebsd, - => true, - - .dragonfly => os.version_range.semver.max.order(.{ .major = 6, .minor = 0, .patch = 0 }) != .lt, - .netbsd => os.version_range.semver.max.order(.{ .major = 10, .minor = 0, .patch = 0 }) != .lt, - else => false, - }; -} - -/// Return canonical path of handle `fd`. -/// This function is very host-specific and is not universally supported by all hosts. -/// For example, while it generally works on Linux, macOS, FreeBSD or Windows, it is -/// unsupported on WASI. -/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). -/// On other platforms, the result is an opaque sequence of bytes with no particular encoding. -/// Calling this function is usually a bug. -pub fn getFdPath(fd: fd_t, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 { - if (!comptime isGetFdPathSupportedOnTarget(builtin.os)) { - @compileError("querying for canonical path of a handle is unsupported on this host"); - } - switch (builtin.os.tag) { - .windows => { - var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined; - const wide_slice = try windows.GetFinalPathNameByHandle(fd, .{}, wide_buf[0..]); - - const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice); - return out_buffer[0..end_index]; - }, - .macos, .ios, .watchos, .tvos => { - // On macOS, we can use F.GETPATH fcntl command to query the OS for - // the path to the file descriptor. - @memset(out_buffer[0..MAX_PATH_BYTES], 0); - switch (errno(system.fcntl(fd, F.GETPATH, out_buffer))) { - .SUCCESS => {}, - .BADF => return error.FileNotFound, - .NOSPC => return error.NameTooLong, - // TODO man pages for fcntl on macOS don't really tell you what - // errno values to expect when command is F.GETPATH... - else => |err| return unexpectedErrno(err), - } - const len = mem.indexOfScalar(u8, out_buffer[0..], 0) orelse MAX_PATH_BYTES; - return out_buffer[0..len]; - }, - .linux => { - var procfs_buf: ["/proc/self/fd/-2147483648\x00".len]u8 = undefined; - const proc_path = std.fmt.bufPrintZ(procfs_buf[0..], "/proc/self/fd/{d}", .{fd}) catch unreachable; - - const target = readlinkZ(proc_path, out_buffer) catch |err| { - switch (err) { - error.NotLink => unreachable, - error.BadPathName => unreachable, - error.InvalidUtf8 => unreachable, // WASI-only - error.InvalidWtf8 => unreachable, // Windows-only - error.UnsupportedReparsePointType => unreachable, // Windows-only - error.NetworkNotFound => unreachable, // Windows-only - else => |e| return e, - } - }; - return target; - }, - .solaris, .illumos => { - var procfs_buf: ["/proc/self/path/-2147483648\x00".len]u8 = undefined; - const proc_path = std.fmt.bufPrintZ(procfs_buf[0..], "/proc/self/path/{d}", .{fd}) catch unreachable; - - const target = readlinkZ(proc_path, out_buffer) catch |err| switch (err) { - error.UnsupportedReparsePointType => unreachable, - error.NotLink => unreachable, - else => |e| return e, - }; - return target; - }, - .freebsd => { - if (comptime builtin.os.isAtLeast(.freebsd, .{ .major = 13, .minor = 0, .patch = 0 }) orelse false) { - var kfile: system.kinfo_file = undefined; - kfile.structsize = system.KINFO_FILE_SIZE; - switch (errno(system.fcntl(fd, system.F.KINFO, @intFromPtr(&kfile)))) { - .SUCCESS => {}, - .BADF => return error.FileNotFound, - else => |err| return unexpectedErrno(err), - } - const len = mem.indexOfScalar(u8, &kfile.path, 0) orelse MAX_PATH_BYTES; - if (len == 0) return error.NameTooLong; - const result = out_buffer[0..len]; - @memcpy(result, kfile.path[0..len]); - return result; - } else { - // This fallback implementation reimplements libutil's `kinfo_getfile()`. - // The motivation is to avoid linking -lutil when building zig or general - // user executables. - var mib = [4]c_int{ CTL.KERN, KERN.PROC, KERN.PROC_FILEDESC, system.getpid() }; - var len: usize = undefined; - sysctl(&mib, null, &len, null, 0) catch |err| switch (err) { - error.PermissionDenied => unreachable, - error.SystemResources => return error.SystemResources, - error.NameTooLong => unreachable, - error.UnknownName => unreachable, - else => return error.Unexpected, - }; - len = len * 4 / 3; - const buf = std.heap.c_allocator.alloc(u8, len) catch return error.SystemResources; - defer std.heap.c_allocator.free(buf); - len = buf.len; - sysctl(&mib, &buf[0], &len, null, 0) catch |err| switch (err) { - error.PermissionDenied => unreachable, - error.SystemResources => return error.SystemResources, - error.NameTooLong => unreachable, - error.UnknownName => unreachable, - else => return error.Unexpected, - }; - var i: usize = 0; - while (i < len) { - const kf: *align(1) system.kinfo_file = @ptrCast(&buf[i]); - if (kf.fd == fd) { - len = mem.indexOfScalar(u8, &kf.path, 0) orelse MAX_PATH_BYTES; - if (len == 0) return error.NameTooLong; - const result = out_buffer[0..len]; - @memcpy(result, kf.path[0..len]); - return result; - } - i += @intCast(kf.structsize); - } - return error.FileNotFound; - } - }, - .dragonfly => { - @memset(out_buffer[0..MAX_PATH_BYTES], 0); - switch (errno(system.fcntl(fd, F.GETPATH, out_buffer))) { - .SUCCESS => {}, - .BADF => return error.FileNotFound, - .RANGE => return error.NameTooLong, - else => |err| return unexpectedErrno(err), - } - const len = mem.indexOfScalar(u8, out_buffer[0..], 0) orelse MAX_PATH_BYTES; - return out_buffer[0..len]; - }, - .netbsd => { - @memset(out_buffer[0..MAX_PATH_BYTES], 0); - switch (errno(system.fcntl(fd, F.GETPATH, out_buffer))) { - .SUCCESS => {}, - .ACCES => return error.AccessDenied, - .BADF => return error.FileNotFound, - .NOENT => return error.FileNotFound, - .NOMEM => return error.SystemResources, - .RANGE => return error.NameTooLong, - else => |err| return unexpectedErrno(err), - } - const len = mem.indexOfScalar(u8, out_buffer[0..], 0) orelse MAX_PATH_BYTES; - return out_buffer[0..len]; - }, - else => unreachable, // made unreachable by isGetFdPathSupportedOnTarget above - } -} - -/// Spurious wakeups are possible and no precision of timing is guaranteed. -pub fn nanosleep(seconds: u64, nanoseconds: u64) void { - var req = timespec{ - .tv_sec = math.cast(isize, seconds) orelse math.maxInt(isize), - .tv_nsec = math.cast(isize, nanoseconds) orelse math.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 dl_iterate_phdr( - context: anytype, - comptime Error: type, - comptime callback: fn (info: *dl_phdr_info, size: usize, context: @TypeOf(context)) Error!void, -) Error!void { - const Context = @TypeOf(context); - - switch (builtin.object_format) { - .elf, .c => {}, - else => @compileError("dl_iterate_phdr is not available for this target"), - } - - if (builtin.link_libc) { - switch (system.dl_iterate_phdr(struct { - fn callbackC(info: *dl_phdr_info, size: usize, data: ?*anyopaque) callconv(.C) c_int { - const context_ptr: *const Context = @ptrCast(@alignCast(data)); - callback(info, size, context_ptr.*) catch |err| return @intFromError(err); - return 0; - } - }.callbackC, @ptrCast(@constCast(&context)))) { - 0 => return, - else => |err| return @as(Error, @errorCast(@errorFromInt(@as(std.meta.Int(.unsigned, @bitSizeOf(anyerror)), @intCast(err))))), - } - } - - const elf_base = std.process.getBaseAddress(); - const ehdr: *elf.Ehdr = @ptrFromInt(elf_base); - // Make sure the base address points to an ELF image. - assert(mem.eql(u8, ehdr.e_ident[0..4], elf.MAGIC)); - const n_phdr = ehdr.e_phnum; - const phdrs = (@as([*]elf.Phdr, @ptrFromInt(elf_base + ehdr.e_phoff)))[0..n_phdr]; - - var it = dl.linkmap_iterator(phdrs) catch unreachable; - - // The executable has no dynamic link segment, create a single entry for - // the whole ELF image. - if (it.end()) { - // Find the base address for the ELF image, if this is a PIE the value - // is non-zero. - const base_address = for (phdrs) |*phdr| { - if (phdr.p_type == elf.PT_PHDR) { - break @intFromPtr(phdrs.ptr) - phdr.p_vaddr; - // We could try computing the difference between _DYNAMIC and - // the p_vaddr of the PT_DYNAMIC section, but using the phdr is - // good enough (Is it?). - } - } else unreachable; - - var info = dl_phdr_info{ - .dlpi_addr = base_address, - .dlpi_name = "/proc/self/exe", - .dlpi_phdr = phdrs.ptr, - .dlpi_phnum = ehdr.e_phnum, - }; - - return callback(&info, @sizeOf(dl_phdr_info), context); - } - - // Last return value from the callback function. - while (it.next()) |entry| { - var dlpi_phdr: [*]elf.Phdr = undefined; - var dlpi_phnum: u16 = undefined; - - if (entry.l_addr != 0) { - const elf_header: *elf.Ehdr = @ptrFromInt(entry.l_addr); - dlpi_phdr = @ptrFromInt(entry.l_addr + elf_header.e_phoff); - dlpi_phnum = elf_header.e_phnum; - } else { - // This is the running ELF image - dlpi_phdr = @ptrFromInt(elf_base + ehdr.e_phoff); - dlpi_phnum = ehdr.e_phnum; - } - - var info = dl_phdr_info{ - .dlpi_addr = entry.l_addr, - .dlpi_name = entry.l_name, - .dlpi_phdr = dlpi_phdr, - .dlpi_phnum = dlpi_phnum, - }; - - try callback(&info, @sizeOf(dl_phdr_info), context); - } -} - -pub const ClockGetTimeError = error{UnsupportedClock} || UnexpectedError; - -/// TODO: change this to return the timespec as a return value -/// TODO: look into making clk_id an enum -pub fn clock_gettime(clk_id: i32, tp: *timespec) ClockGetTimeError!void { - if (builtin.os.tag == .wasi and !builtin.link_libc) { - var ts: timestamp_t = undefined; - switch (system.clock_time_get(@bitCast(clk_id), 1, &ts)) { - .SUCCESS => { - tp.* = .{ - .tv_sec = @intCast(ts / std.time.ns_per_s), - .tv_nsec = @intCast(ts % std.time.ns_per_s), - }; - }, - .INVAL => return error.UnsupportedClock, - else => |err| return unexpectedErrno(err), - } - return; - } - if (builtin.os.tag == .windows) { - if (clk_id == CLOCK.REALTIME) { - var ft: windows.FILETIME = undefined; - windows.kernel32.GetSystemTimeAsFileTime(&ft); - // FileTime has a granularity of 100 nanoseconds and uses the NTFS/Windows epoch. - const ft64 = (@as(u64, ft.dwHighDateTime) << 32) | ft.dwLowDateTime; - const ft_per_s = std.time.ns_per_s / 100; - tp.* = .{ - .tv_sec = @as(i64, @intCast(ft64 / ft_per_s)) + std.time.epoch.windows, - .tv_nsec = @as(c_long, @intCast(ft64 % ft_per_s)) * 100, - }; - return; - } else { - // TODO POSIX implementation of CLOCK.MONOTONIC on Windows. - return error.UnsupportedClock; - } - } - - switch (errno(system.clock_gettime(clk_id, tp))) { - .SUCCESS => return, - .FAULT => unreachable, - .INVAL => return error.UnsupportedClock, - else => |err| return unexpectedErrno(err), - } -} - -pub fn clock_getres(clk_id: i32, res: *timespec) ClockGetTimeError!void { - if (builtin.os.tag == .wasi and !builtin.link_libc) { - var ts: timestamp_t = undefined; - switch (system.clock_res_get(@bitCast(clk_id), &ts)) { - .SUCCESS => res.* = .{ - .tv_sec = @intCast(ts / std.time.ns_per_s), - .tv_nsec = @intCast(ts % std.time.ns_per_s), - }, - .INVAL => return error.UnsupportedClock, - else => |err| return unexpectedErrno(err), - } - return; - } - - switch (errno(system.clock_getres(clk_id, res))) { - .SUCCESS => return, - .FAULT => unreachable, - .INVAL => return error.UnsupportedClock, - else => |err| return unexpectedErrno(err), - } -} - -pub const SchedGetAffinityError = error{PermissionDenied} || UnexpectedError; - -pub fn sched_getaffinity(pid: pid_t) SchedGetAffinityError!cpu_set_t { - var set: cpu_set_t = undefined; - switch (errno(system.sched_getaffinity(pid, @sizeOf(cpu_set_t), &set))) { - .SUCCESS => return set, - .FAULT => unreachable, - .INVAL => unreachable, - .SRCH => unreachable, - .PERM => return error.PermissionDenied, - else => |err| return unexpectedErrno(err), - } -} - -/// Used to convert a slice to a null terminated slice on the stack. -/// TODO https://github.com/ziglang/zig/issues/287 -pub fn toPosixPath(file_path: []const u8) error{NameTooLong}![MAX_PATH_BYTES - 1:0]u8 { - if (std.debug.runtime_safety) assert(std.mem.indexOfScalar(u8, file_path, 0) == null); - var path_with_null: [MAX_PATH_BYTES - 1:0]u8 = undefined; - // >= rather than > to make room for the null byte - if (file_path.len >= MAX_PATH_BYTES) return error.NameTooLong; - @memcpy(path_with_null[0..file_path.len], file_path); - path_with_null[file_path.len] = 0; - return path_with_null; -} - -/// Whether or not error.Unexpected will print its value and a stack trace. -/// if this happens the fix is to add the error code to the corresponding -/// switch expression, possibly introduce a new error in the error set, and -/// send a patch to Zig. -pub const unexpected_error_tracing = builtin.zig_backend == .stage2_llvm and builtin.mode == .Debug; - -pub const UnexpectedError = error{ - /// The Operating System returned an undocumented error code. - /// This error is in theory not possible, but it would be better - /// to handle this error than to invoke undefined behavior. - Unexpected, -}; - -/// Call this when you made a syscall or something that sets errno -/// and you get an unexpected error. -pub fn unexpectedErrno(err: E) UnexpectedError { - if (unexpected_error_tracing) { - std.debug.print("unexpected errno: {d}\n", .{@intFromEnum(err)}); - std.debug.dumpCurrentStackTrace(null); - } - return error.Unexpected; -} - -pub const SigaltstackError = error{ - /// The supplied stack size was less than MINSIGSTKSZ. - SizeTooSmall, - - /// Attempted to change the signal stack while it was active. - PermissionDenied, -} || UnexpectedError; - -pub fn sigaltstack(ss: ?*stack_t, old_ss: ?*stack_t) SigaltstackError!void { - switch (errno(system.sigaltstack(ss, old_ss))) { - .SUCCESS => return, - .FAULT => unreachable, - .INVAL => unreachable, - .NOMEM => return error.SizeTooSmall, - .PERM => return error.PermissionDenied, - else => |err| return unexpectedErrno(err), - } -} - -/// Examine and change a signal action. -pub fn sigaction(sig: u6, noalias act: ?*const Sigaction, noalias oact: ?*Sigaction) error{OperationNotSupported}!void { - switch (errno(system.sigaction(sig, act, oact))) { - .SUCCESS => return, - .INVAL, .NOSYS => return error.OperationNotSupported, - else => unreachable, - } -} - -/// Sets the thread signal mask. -pub fn sigprocmask(flags: u32, noalias set: ?*const sigset_t, noalias oldset: ?*sigset_t) void { - switch (errno(system.sigprocmask(@bitCast(flags), set, oldset))) { - .SUCCESS => return, - .FAULT => unreachable, - .INVAL => unreachable, - else => unreachable, - } -} - -pub const FutimensError = error{ - /// times is NULL, or both tv_nsec values are UTIME_NOW, and either: - /// * the effective user ID of the caller does not match the owner - /// of the file, the caller does not have write access to the - /// file, and the caller is not privileged (Linux: does not have - /// either the CAP_FOWNER or the CAP_DAC_OVERRIDE capability); - /// or, - /// * the file is marked immutable (see chattr(1)). - AccessDenied, - - /// The caller attempted to change one or both timestamps to a value - /// other than the current time, or to change one of the timestamps - /// to the current time while leaving the other timestamp unchanged, - /// (i.e., times is not NULL, neither tv_nsec field is UTIME_NOW, - /// and neither tv_nsec field is UTIME_OMIT) and either: - /// * the caller's effective user ID does not match the owner of - /// file, and the caller is not privileged (Linux: does not have - /// the CAP_FOWNER capability); or, - /// * the file is marked append-only or immutable (see chattr(1)). - PermissionDenied, - - ReadOnlyFileSystem, -} || UnexpectedError; - -pub fn futimens(fd: fd_t, times: *const [2]timespec) FutimensError!void { - if (builtin.os.tag == .wasi and !builtin.link_libc) { - // TODO WASI encodes `wasi.fstflags` to signify magic values - // similar to UTIME_NOW and UTIME_OMIT. Currently, we ignore - // this here, but we should really handle it somehow. - const atim = times[0].toTimestamp(); - const mtim = times[1].toTimestamp(); - switch (wasi.fd_filestat_set_times(fd, atim, mtim, .{ - .ATIM = true, - .MTIM = true, - })) { - .SUCCESS => return, - .ACCES => return error.AccessDenied, - .PERM => return error.PermissionDenied, - .BADF => unreachable, // always a race condition - .FAULT => unreachable, - .INVAL => unreachable, - .ROFS => return error.ReadOnlyFileSystem, - else => |err| return unexpectedErrno(err), - } - } - - switch (errno(system.futimens(fd, times))) { - .SUCCESS => return, - .ACCES => return error.AccessDenied, - .PERM => return error.PermissionDenied, - .BADF => unreachable, // always a race condition - .FAULT => unreachable, - .INVAL => unreachable, - .ROFS => return error.ReadOnlyFileSystem, - else => |err| return unexpectedErrno(err), - } -} - -pub const GetHostNameError = error{PermissionDenied} || UnexpectedError; - -pub fn gethostname(name_buffer: *[HOST_NAME_MAX]u8) GetHostNameError![]u8 { - if (builtin.link_libc) { - switch (errno(system.gethostname(name_buffer, name_buffer.len))) { - .SUCCESS => return mem.sliceTo(name_buffer, 0), - .FAULT => unreachable, - .NAMETOOLONG => unreachable, // HOST_NAME_MAX prevents this - .PERM => return error.PermissionDenied, - else => |err| return unexpectedErrno(err), - } - } - if (builtin.os.tag == .linux) { - const uts = uname(); - const hostname = mem.sliceTo(&uts.nodename, 0); - const result = name_buffer[0..hostname.len]; - @memcpy(result, hostname); - return result; - } - - @compileError("TODO implement gethostname for this OS"); -} - -pub fn uname() utsname { - var uts: utsname = undefined; - switch (errno(system.uname(&uts))) { - .SUCCESS => return uts, - .FAULT => unreachable, - else => unreachable, - } -} - -pub fn res_mkquery( - op: u4, - dname: []const u8, - class: u8, - ty: u8, - data: []const u8, - newrr: ?[*]const u8, - buf: []u8, -) usize { - _ = data; - _ = newrr; - // This implementation is ported from musl libc. - // A more idiomatic "ziggy" implementation would be welcome. - var name = dname; - if (mem.endsWith(u8, name, ".")) name.len -= 1; - assert(name.len <= 253); - const n = 17 + name.len + @intFromBool(name.len != 0); - - // Construct query template - ID will be filled later - var q: [280]u8 = undefined; - @memset(q[0..n], 0); - q[2] = @as(u8, op) * 8 + 1; - q[5] = 1; - @memcpy(q[13..][0..name.len], name); - var i: usize = 13; - var j: usize = undefined; - while (q[i] != 0) : (i = j + 1) { - j = i; - while (q[j] != 0 and q[j] != '.') : (j += 1) {} - // TODO determine the circumstances for this and whether or - // not this should be an error. - if (j - i - 1 > 62) unreachable; - q[i - 1] = @intCast(j - i); - } - q[i + 1] = ty; - q[i + 3] = class; - - // Make a reasonably unpredictable id - var ts: timespec = undefined; - clock_gettime(CLOCK.REALTIME, &ts) catch {}; - const UInt = std.meta.Int(.unsigned, @bitSizeOf(@TypeOf(ts.tv_nsec))); - const unsec: UInt = @bitCast(ts.tv_nsec); - const id: u32 = @truncate(unsec + unsec / 65536); - q[0] = @truncate(id / 256); - q[1] = @truncate(id); - - @memcpy(buf[0..n], q[0..n]); - return n; -} - -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. - MessageTooBig, - - /// 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. - NetworkSubsystemFailed, -} || UnexpectedError; - -pub const SendMsgError = SendError || error{ - /// The passed address didn't have the correct address family in its sa_family field. - AddressFamilyNotSupported, - - /// 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). - SocketNotConnected, - AddressNotAvailable, -}; - -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 (builtin.os.tag == .windows) { - if (rc == windows.ws2_32.SOCKET_ERROR) { - switch (windows.ws2_32.WSAGetLastError()) { - .WSAEACCES => return error.AccessDenied, - .WSAEADDRNOTAVAIL => return error.AddressNotAvailable, - .WSAECONNRESET => return error.ConnectionResetByPeer, - .WSAEMSGSIZE => return error.MessageTooBig, - .WSAENOBUFS => return error.SystemResources, - .WSAENOTSOCK => return error.FileDescriptorNotASocket, - .WSAEAFNOSUPPORT => return error.AddressFamilyNotSupported, - .WSAEDESTADDRREQ => unreachable, // A destination address is required. - .WSAEFAULT => unreachable, // The lpBuffers, lpTo, lpOverlapped, lpNumberOfBytesSent, or lpCompletionRoutine parameters are not part of the user address space, or the lpTo parameter is too small. - .WSAEHOSTUNREACH => return error.NetworkUnreachable, - // TODO: WSAEINPROGRESS, WSAEINTR - .WSAEINVAL => unreachable, - .WSAENETDOWN => return error.NetworkSubsystemFailed, - .WSAENETRESET => return error.ConnectionResetByPeer, - .WSAENETUNREACH => return error.NetworkUnreachable, - .WSAENOTCONN => return error.SocketNotConnected, - .WSAESHUTDOWN => 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. - .WSAEWOULDBLOCK => return error.WouldBlock, - .WSANOTINITIALISED => 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.MessageTooBig, - .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.AddressFamilyNotSupported, - .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.SocketNotConnected, - .NETDOWN => return error.NetworkSubsystemFailed, - else => |err| return unexpectedErrno(err), - } - } - } -} - -pub const SendToError = SendMsgError || error{ - /// The destination address is not reachable by the bound address. - UnreachableAddress, -}; - -/// 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.MessageTooBig` 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 (builtin.os.tag == .windows) { - switch (windows.sendto(sockfd, buf.ptr, buf.len, flags, dest_addr, addrlen)) { - windows.ws2_32.SOCKET_ERROR => switch (windows.ws2_32.WSAGetLastError()) { - .WSAEACCES => return error.AccessDenied, - .WSAEADDRNOTAVAIL => return error.AddressNotAvailable, - .WSAECONNRESET => return error.ConnectionResetByPeer, - .WSAEMSGSIZE => return error.MessageTooBig, - .WSAENOBUFS => return error.SystemResources, - .WSAENOTSOCK => return error.FileDescriptorNotASocket, - .WSAEAFNOSUPPORT => return error.AddressFamilyNotSupported, - .WSAEDESTADDRREQ => unreachable, // A destination address is required. - .WSAEFAULT => unreachable, // The lpBuffers, lpTo, lpOverlapped, lpNumberOfBytesSent, or lpCompletionRoutine parameters are not part of the user address space, or the lpTo parameter is too small. - .WSAEHOSTUNREACH => return error.NetworkUnreachable, - // TODO: WSAEINPROGRESS, WSAEINTR - .WSAEINVAL => unreachable, - .WSAENETDOWN => return error.NetworkSubsystemFailed, - .WSAENETRESET => return error.ConnectionResetByPeer, - .WSAENETUNREACH => return error.NetworkUnreachable, - .WSAENOTCONN => return error.SocketNotConnected, - .WSAESHUTDOWN => 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. - .WSAEWOULDBLOCK => return error.WouldBlock, - .WSANOTINITIALISED => 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 - .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.MessageTooBig, - .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.AddressFamilyNotSupported, - .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.SocketNotConnected, - .NETDOWN => return error.NetworkSubsystemFailed, - 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.AddressFamilyNotSupported => unreachable, - error.SymLinkLoop => unreachable, - error.NameTooLong => unreachable, - error.FileNotFound => unreachable, - error.NotDir => unreachable, - error.NetworkUnreachable => unreachable, - error.AddressNotAvailable => unreachable, - error.SocketNotConnected => unreachable, - error.UnreachableAddress => unreachable, - else => |e| return e, - }; -} - -pub const SendFileError = PReadError || WriteError || SendError; - -fn count_iovec_bytes(iovs: []const iovec_const) usize { - var count: usize = 0; - for (iovs) |iov| { - count += iov.iov_len; - } - return count; -} - -/// Transfer data between file descriptors, with optional headers and trailers. -/// Returns the number of bytes written, which can be zero. -/// -/// The `sendfile` call copies `in_len` bytes from one file descriptor to another. When possible, -/// this is done within the operating system kernel, which can provide better performance -/// characteristics than transferring data from kernel to user space and back, such as with -/// `read` and `write` calls. When `in_len` is `0`, it means to copy until the end of the input file has been -/// reached. Note, however, that partial writes are still possible in this case. -/// -/// `in_fd` must be a file descriptor opened for reading, and `out_fd` must be a file descriptor -/// opened for writing. They may be any kind of file descriptor; however, if `in_fd` is not a regular -/// file system file, it may cause this function to fall back to calling `read` and `write`, in which case -/// atomicity guarantees no longer apply. -/// -/// Copying begins reading at `in_offset`. The input file descriptor seek position is ignored and not updated. -/// If the output file descriptor has a seek position, it is updated as bytes are written. When -/// `in_offset` is past the end of the input file, it successfully reads 0 bytes. -/// -/// `flags` has different meanings per operating system; refer to the respective man pages. -/// -/// These systems support atomically sending everything, including headers and trailers: -/// * macOS -/// * FreeBSD -/// -/// These systems support in-kernel data copying, but headers and trailers are not sent atomically: -/// * Linux -/// -/// Other systems fall back to calling `read` / `write`. -/// -/// Linux has a limit on how many bytes may be transferred in one `sendfile` 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 `sendfile` man page. -/// The limit on Darwin is `0x7fffffff`, trying to write more than that returns EINVAL. -/// The corresponding POSIX limit on this is `math.maxInt(isize)`. -pub fn sendfile( - out_fd: fd_t, - in_fd: fd_t, - in_offset: u64, - in_len: u64, - headers: []const iovec_const, - trailers: []const iovec_const, - flags: u32, -) SendFileError!usize { - var header_done = false; - var total_written: usize = 0; - - // Prevents EOVERFLOW. - const size_t = std.meta.Int(.unsigned, @typeInfo(usize).Int.bits - 1); - const max_count = switch (builtin.os.tag) { - .linux => 0x7ffff000, - .macos, .ios, .watchos, .tvos => math.maxInt(i32), - else => math.maxInt(size_t), - }; - - switch (builtin.os.tag) { - .linux => sf: { - // sendfile() first appeared in Linux 2.2, glibc 2.1. - const call_sf = comptime if (builtin.link_libc) - std.c.versionCheck(.{ .major = 2, .minor = 1, .patch = 0 }) - else - builtin.os.version_range.linux.range.max.order(.{ .major = 2, .minor = 2, .patch = 0 }) != .lt; - if (!call_sf) break :sf; - - if (headers.len != 0) { - const amt = try writev(out_fd, headers); - total_written += amt; - if (amt < count_iovec_bytes(headers)) return total_written; - header_done = true; - } - - // Here we match BSD behavior, making a zero count value send as many bytes as possible. - const adjusted_count = if (in_len == 0) max_count else @min(in_len, max_count); - - const sendfile_sym = if (lfs64_abi) system.sendfile64 else system.sendfile; - while (true) { - var offset: off_t = @bitCast(in_offset); - const rc = sendfile_sym(out_fd, in_fd, &offset, adjusted_count); - switch (errno(rc)) { - .SUCCESS => { - const amt: usize = @bitCast(rc); - total_written += amt; - if (in_len == 0 and amt == 0) { - // We have detected EOF from `in_fd`. - break; - } else if (amt < in_len) { - return total_written; - } else { - break; - } - }, - - .BADF => unreachable, // Always a race condition. - .FAULT => unreachable, // Segmentation fault. - .OVERFLOW => unreachable, // We avoid passing too large of a `count`. - .NOTCONN => return error.BrokenPipe, // `out_fd` is an unconnected socket - - .INVAL, .NOSYS => { - // EINVAL could be any of the following situations: - // * Descriptor is not valid or locked - // * an mmap(2)-like operation is not available for in_fd - // * count is negative - // * out_fd has the APPEND flag set - // Because of the "mmap(2)-like operation" possibility, we fall back to doing read/write - // manually, the same as ENOSYS. - break :sf; - }, - .AGAIN => return error.WouldBlock, - .IO => return error.InputOutput, - .PIPE => return error.BrokenPipe, - .NOMEM => return error.SystemResources, - .NXIO => return error.Unseekable, - .SPIPE => return error.Unseekable, - else => |err| { - unexpectedErrno(err) catch {}; - break :sf; - }, - } - } - - if (trailers.len != 0) { - total_written += try writev(out_fd, trailers); - } - - return total_written; - }, - .freebsd => sf: { - var hdtr_data: std.c.sf_hdtr = undefined; - var hdtr: ?*std.c.sf_hdtr = null; - if (headers.len != 0 or trailers.len != 0) { - // Here we carefully avoid `@intCast` by returning partial writes when - // too many io vectors are provided. - const hdr_cnt = math.cast(u31, headers.len) orelse math.maxInt(u31); - if (headers.len > hdr_cnt) return writev(out_fd, headers); - - const trl_cnt = math.cast(u31, trailers.len) orelse math.maxInt(u31); - - hdtr_data = std.c.sf_hdtr{ - .headers = headers.ptr, - .hdr_cnt = hdr_cnt, - .trailers = trailers.ptr, - .trl_cnt = trl_cnt, - }; - hdtr = &hdtr_data; - } - - while (true) { - var sbytes: off_t = undefined; - const err = errno(system.sendfile(in_fd, out_fd, @bitCast(in_offset), @min(in_len, max_count), hdtr, &sbytes, flags)); - const amt: usize = @bitCast(sbytes); - switch (err) { - .SUCCESS => return amt, - - .BADF => unreachable, // Always a race condition. - .FAULT => unreachable, // Segmentation fault. - .NOTCONN => return error.BrokenPipe, // `out_fd` is an unconnected socket - - .INVAL, .OPNOTSUPP, .NOTSOCK, .NOSYS => { - // EINVAL could be any of the following situations: - // * The fd argument is not a regular file. - // * The s argument is not a SOCK.STREAM type socket. - // * The offset argument is negative. - // Because of some of these possibilities, we fall back to doing read/write - // manually, the same as ENOSYS. - break :sf; - }, - - .INTR => if (amt != 0) return amt else continue, - - .AGAIN => if (amt != 0) { - return amt; - } else { - return error.WouldBlock; - }, - - .BUSY => if (amt != 0) { - return amt; - } else { - return error.WouldBlock; - }, - - .IO => return error.InputOutput, - .NOBUFS => return error.SystemResources, - .PIPE => return error.BrokenPipe, - - else => { - unexpectedErrno(err) catch {}; - if (amt != 0) { - return amt; - } else { - break :sf; - } - }, - } - } - }, - .macos, .ios, .tvos, .watchos => sf: { - var hdtr_data: std.c.sf_hdtr = undefined; - var hdtr: ?*std.c.sf_hdtr = null; - if (headers.len != 0 or trailers.len != 0) { - // Here we carefully avoid `@intCast` by returning partial writes when - // too many io vectors are provided. - const hdr_cnt = math.cast(u31, headers.len) orelse math.maxInt(u31); - if (headers.len > hdr_cnt) return writev(out_fd, headers); - - const trl_cnt = math.cast(u31, trailers.len) orelse math.maxInt(u31); - - hdtr_data = std.c.sf_hdtr{ - .headers = headers.ptr, - .hdr_cnt = hdr_cnt, - .trailers = trailers.ptr, - .trl_cnt = trl_cnt, - }; - hdtr = &hdtr_data; - } - - while (true) { - var sbytes: off_t = @min(in_len, max_count); - const err = errno(system.sendfile(in_fd, out_fd, @bitCast(in_offset), &sbytes, hdtr, flags)); - const amt: usize = @bitCast(sbytes); - switch (err) { - .SUCCESS => return amt, - - .BADF => unreachable, // Always a race condition. - .FAULT => unreachable, // Segmentation fault. - .INVAL => unreachable, - .NOTCONN => return error.BrokenPipe, // `out_fd` is an unconnected socket - - .OPNOTSUPP, .NOTSOCK, .NOSYS => break :sf, - - .INTR => if (amt != 0) return amt else continue, - - .AGAIN => if (amt != 0) { - return amt; - } else { - return error.WouldBlock; - }, - - .IO => return error.InputOutput, - .PIPE => return error.BrokenPipe, - - else => { - unexpectedErrno(err) catch {}; - if (amt != 0) { - return amt; - } else { - break :sf; - } - }, - } - } - }, - else => {}, // fall back to read/write - } - - if (headers.len != 0 and !header_done) { - const amt = try writev(out_fd, headers); - total_written += amt; - if (amt < count_iovec_bytes(headers)) return total_written; - } - - rw: { - var buf: [8 * 4096]u8 = undefined; - // Here we match BSD behavior, making a zero count value send as many bytes as possible. - const adjusted_count = if (in_len == 0) buf.len else @min(buf.len, in_len); - const amt_read = try pread(in_fd, buf[0..adjusted_count], in_offset); - if (amt_read == 0) { - if (in_len == 0) { - // We have detected EOF from `in_fd`. - break :rw; - } else { - return total_written; - } - } - const amt_written = try write(out_fd, buf[0..amt_read]); - total_written += amt_written; - if (amt_written < in_len or in_len == 0) return total_written; - } - - if (trailers.len != 0) { - total_written += try writev(out_fd, trailers); - } - - return total_written; -} - -pub const CopyFileRangeError = error{ - FileTooBig, - InputOutput, - /// `fd_in` is not open for reading; or `fd_out` is not open for writing; - /// or the `APPEND` flag is set for `fd_out`. - FilesOpenedWithWrongFlags, - IsDir, - OutOfMemory, - NoSpaceLeft, - Unseekable, - PermissionDenied, - SwapFile, - CorruptedData, -} || PReadError || PWriteError || UnexpectedError; - -var has_copy_file_range_syscall = std.atomic.Value(bool).init(true); - -/// Transfer data between file descriptors at specified offsets. -/// Returns the number of bytes written, which can less than requested. -/// -/// The `copy_file_range` call copies `len` bytes from one file descriptor to another. When possible, -/// this is done within the operating system kernel, which can provide better performance -/// characteristics than transferring data from kernel to user space and back, such as with -/// `pread` and `pwrite` calls. -/// -/// `fd_in` must be a file descriptor opened for reading, and `fd_out` must be a file descriptor -/// opened for writing. They may be any kind of file descriptor; however, if `fd_in` is not a regular -/// file system file, it may cause this function to fall back to calling `pread` and `pwrite`, in which case -/// atomicity guarantees no longer apply. -/// -/// If `fd_in` and `fd_out` are the same, source and target ranges must not overlap. -/// The file descriptor seek positions are ignored and not updated. -/// When `off_in` is past the end of the input file, it successfully reads 0 bytes. -/// -/// `flags` has different meanings per operating system; refer to the respective man pages. -/// -/// These systems support in-kernel data copying: -/// * Linux 4.5 (cross-filesystem 5.3) -/// * FreeBSD 13.0 -/// -/// Other systems fall back to calling `pread` / `pwrite`. -/// -/// Maximum offsets on Linux and FreeBSD are `math.maxInt(i64)`. -pub fn copy_file_range(fd_in: fd_t, off_in: u64, fd_out: fd_t, off_out: u64, len: usize, flags: u32) CopyFileRangeError!usize { - if ((comptime builtin.os.isAtLeast(.freebsd, .{ .major = 13, .minor = 0, .patch = 0 }) orelse false) or - ((comptime builtin.os.isAtLeast(.linux, .{ .major = 4, .minor = 5, .patch = 0 }) orelse false and - std.c.versionCheck(.{ .major = 2, .minor = 27, .patch = 0 })) and - has_copy_file_range_syscall.load(.monotonic))) - { - var off_in_copy: i64 = @bitCast(off_in); - var off_out_copy: i64 = @bitCast(off_out); - - while (true) { - const rc = system.copy_file_range(fd_in, &off_in_copy, fd_out, &off_out_copy, len, flags); - if (builtin.os.tag == .freebsd) { - switch (system.getErrno(rc)) { - .SUCCESS => return @intCast(rc), - .BADF => return error.FilesOpenedWithWrongFlags, - .FBIG => return error.FileTooBig, - .IO => return error.InputOutput, - .ISDIR => return error.IsDir, - .NOSPC => return error.NoSpaceLeft, - .INVAL => break, // these may not be regular files, try fallback - .INTEGRITY => return error.CorruptedData, - .INTR => continue, - else => |err| return unexpectedErrno(err), - } - } else { // assume linux - switch (system.getErrno(rc)) { - .SUCCESS => return @intCast(rc), - .BADF => return error.FilesOpenedWithWrongFlags, - .FBIG => return error.FileTooBig, - .IO => return error.InputOutput, - .ISDIR => return error.IsDir, - .NOSPC => return error.NoSpaceLeft, - .INVAL => break, // these may not be regular files, try fallback - .NOMEM => return error.OutOfMemory, - .OVERFLOW => return error.Unseekable, - .PERM => return error.PermissionDenied, - .TXTBSY => return error.SwapFile, - .XDEV => break, // support for cross-filesystem copy added in Linux 5.3, use fallback - .NOSYS => { // syscall added in Linux 4.5, use fallback - has_copy_file_range_syscall.store(false, .monotonic); - break; - }, - else => |err| return unexpectedErrno(err), - } - } - } - } - - var buf: [8 * 4096]u8 = undefined; - const amt_read = try pread(fd_in, buf[0..@min(buf.len, len)], off_in); - if (amt_read == 0) return 0; - return pwrite(fd_out, buf[0..amt_read], off_out); -} - -pub const PollError = error{ - /// The network subsystem has failed. - NetworkSubsystemFailed, - - /// The kernel had no space to allocate file descriptor tables. - SystemResources, -} || UnexpectedError; - -pub fn poll(fds: []pollfd, timeout: i32) PollError!usize { - while (true) { - const fds_count = math.cast(nfds_t, fds.len) orelse return error.SystemResources; - const rc = system.poll(fds.ptr, fds_count, timeout); - if (builtin.os.tag == .windows) { - if (rc == windows.ws2_32.SOCKET_ERROR) { - switch (windows.ws2_32.WSAGetLastError()) { - .WSANOTINITIALISED => unreachable, - .WSAENETDOWN => return error.NetworkSubsystemFailed, - .WSAENOBUFS => return error.SystemResources, - // TODO: handle more errors - else => |err| return windows.unexpectedWSAError(err), - } - } else { - return @intCast(rc); - } - } else { - switch (errno(rc)) { - .SUCCESS => return @intCast(rc), - .FAULT => unreachable, - .INTR => continue, - .INVAL => unreachable, - .NOMEM => return error.SystemResources, - else => |err| return unexpectedErrno(err), - } - } - unreachable; - } -} - -pub const PPollError = error{ - /// The operation was interrupted by a delivery of a signal before it could complete. - SignalInterrupt, - - /// The kernel had no space to allocate file descriptor tables. - SystemResources, -} || UnexpectedError; - -pub fn ppoll(fds: []pollfd, timeout: ?*const timespec, mask: ?*const sigset_t) PPollError!usize { - var ts: timespec = undefined; - var ts_ptr: ?*timespec = null; - if (timeout) |timeout_ns| { - ts_ptr = &ts; - ts = timeout_ns.*; - } - const fds_count = math.cast(nfds_t, fds.len) orelse return error.SystemResources; - const rc = system.ppoll(fds.ptr, fds_count, ts_ptr, mask); - switch (errno(rc)) { - .SUCCESS => return @intCast(rc), - .FAULT => unreachable, - .INTR => return error.SignalInterrupt, - .INVAL => unreachable, - .NOMEM => return error.SystemResources, - else => |err| return unexpectedErrno(err), - } -} - -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, - ConnectionTimedOut, - - /// The socket has not been bound. - SocketNotBound, - - /// The UDP message was too big for the buffer and part of it has been discarded - MessageTooBig, - - /// The network subsystem has failed. - NetworkSubsystemFailed, - - /// The socket is not connected (connection-oriented sockets only). - SocketNotConnected, -} || 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 (builtin.os.tag == .windows) { - if (rc == windows.ws2_32.SOCKET_ERROR) { - switch (windows.ws2_32.WSAGetLastError()) { - .WSANOTINITIALISED => unreachable, - .WSAECONNRESET => return error.ConnectionResetByPeer, - .WSAEINVAL => return error.SocketNotBound, - .WSAEMSGSIZE => return error.MessageTooBig, - .WSAENETDOWN => return error.NetworkSubsystemFailed, - .WSAENOTCONN => return error.SocketNotConnected, - .WSAEWOULDBLOCK => return error.WouldBlock, - .WSAETIMEDOUT => return error.ConnectionTimedOut, - // 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.SocketNotConnected, - .NOTSOCK => unreachable, - .INTR => continue, - .AGAIN => return error.WouldBlock, - .NOMEM => return error.SystemResources, - .CONNREFUSED => return error.ConnectionRefused, - .CONNRESET => return error.ConnectionResetByPeer, - .TIMEDOUT => return error.ConnectionTimedOut, - else => |err| return unexpectedErrno(err), - } - } - } -} - -pub const DnExpandError = error{InvalidDnsPacket}; - -pub fn dn_expand( - msg: []const u8, - comp_dn: []const u8, - exp_dn: []u8, -) DnExpandError!usize { - // This implementation is ported from musl libc. - // A more idiomatic "ziggy" implementation would be welcome. - var p = comp_dn.ptr; - var len: usize = std.math.maxInt(usize); - const end = msg.ptr + msg.len; - if (p == end or exp_dn.len == 0) return error.InvalidDnsPacket; - var dest = exp_dn.ptr; - const dend = dest + @min(exp_dn.len, 254); - // detect reference loop using an iteration counter - var i: usize = 0; - while (i < msg.len) : (i += 2) { - // loop invariants: p= msg.len) return error.InvalidDnsPacket; - p = msg.ptr + j; - } else if (p[0] != 0) { - if (dest != exp_dn.ptr) { - dest[0] = '.'; - dest += 1; - } - var j = p[0]; - p += 1; - if (j >= @intFromPtr(end) - @intFromPtr(p) or j >= @intFromPtr(dend) - @intFromPtr(dest)) { - return error.InvalidDnsPacket; - } - while (j != 0) { - j -= 1; - dest[0] = p[0]; - dest += 1; - p += 1; - } - } else { - dest[0] = 0; - if (len == std.math.maxInt(usize)) len = @intFromPtr(p) + 1 - @intFromPtr(comp_dn.ptr); - return len; - } - } - return error.InvalidDnsPacket; -} - -pub const SetSockOptError = error{ - /// The socket is already connected, and a specified option cannot be set while the socket is connected. - AlreadyConnected, - - /// The option is not supported by the protocol. - InvalidProtocolOption, - - /// The send and receive timeout values are too big to fit into the timeout fields in the socket structure. - TimeoutTooBig, - - /// Insufficient resources are available in the system to complete the call. - SystemResources, - - // Setting the socket option requires more elevated permissions. - PermissionDenied, - - NetworkSubsystemFailed, - FileDescriptorNotASocket, - SocketNotBound, - NoDevice, -} || UnexpectedError; - -/// Set a socket's options. -pub fn setsockopt(fd: socket_t, level: u32, optname: u32, opt: []const u8) SetSockOptError!void { - if (builtin.os.tag == .windows) { - const rc = windows.ws2_32.setsockopt(fd, @intCast(level), @intCast(optname), opt.ptr, @intCast(opt.len)); - if (rc == windows.ws2_32.SOCKET_ERROR) { - switch (windows.ws2_32.WSAGetLastError()) { - .WSANOTINITIALISED => unreachable, - .WSAENETDOWN => return error.NetworkSubsystemFailed, - .WSAEFAULT => unreachable, - .WSAENOTSOCK => return error.FileDescriptorNotASocket, - .WSAEINVAL => return error.SocketNotBound, - else => |err| return windows.unexpectedWSAError(err), - } - } - return; - } else { - switch (errno(system.setsockopt(fd, level, optname, opt.ptr, @intCast(opt.len)))) { - .SUCCESS => {}, - .BADF => unreachable, // always a race condition - .NOTSOCK => unreachable, // always a race condition - .INVAL => unreachable, - .FAULT => unreachable, - .DOM => return error.TimeoutTooBig, - .ISCONN => return error.AlreadyConnected, - .NOPROTOOPT => return error.InvalidProtocolOption, - .NOMEM => return error.SystemResources, - .NOBUFS => return error.SystemResources, - .PERM => return error.PermissionDenied, - .NODEV => return error.NoDevice, - else => |err| return unexpectedErrno(err), - } - } -} - -pub const MemFdCreateError = error{ - SystemFdQuotaExceeded, - ProcessFdQuotaExceeded, - OutOfMemory, - - /// memfd_create is available in Linux 3.17 and later. This error is returned - /// for older kernel versions. - SystemOutdated, -} || UnexpectedError; - -pub fn memfd_createZ(name: [*:0]const u8, flags: u32) MemFdCreateError!fd_t { - switch (builtin.os.tag) { - .linux => { - // memfd_create is available only in glibc versions starting with 2.27. - const use_c = std.c.versionCheck(.{ .major = 2, .minor = 27, .patch = 0 }); - const sys = if (use_c) std.c else linux; - const getErrno = if (use_c) std.c.getErrno else linux.getErrno; - const rc = sys.memfd_create(name, flags); - switch (getErrno(rc)) { - .SUCCESS => return @intCast(rc), - .FAULT => unreachable, // name has invalid memory - .INVAL => unreachable, // name/flags are faulty - .NFILE => return error.SystemFdQuotaExceeded, - .MFILE => return error.ProcessFdQuotaExceeded, - .NOMEM => return error.OutOfMemory, - .NOSYS => return error.SystemOutdated, - else => |err| return unexpectedErrno(err), - } - }, - .freebsd => { - if (comptime builtin.os.version_range.semver.max.order(.{ .major = 13, .minor = 0, .patch = 0 }) == .lt) - @compileError("memfd_create is unavailable on FreeBSD < 13.0"); - const rc = system.memfd_create(name, flags); - switch (errno(rc)) { - .SUCCESS => return rc, - .BADF => unreachable, // name argument NULL - .INVAL => unreachable, // name too long or invalid/unsupported flags. - .MFILE => return error.ProcessFdQuotaExceeded, - .NFILE => return error.SystemFdQuotaExceeded, - .NOSYS => return error.SystemOutdated, - else => |err| return unexpectedErrno(err), - } - }, - else => @compileError("target OS does not support memfd_create()"), - } -} - -pub const MFD_NAME_PREFIX = "memfd:"; -pub const MFD_MAX_NAME_LEN = NAME_MAX - MFD_NAME_PREFIX.len; -fn toMemFdPath(name: []const u8) ![MFD_MAX_NAME_LEN:0]u8 { - var path_with_null: [MFD_MAX_NAME_LEN:0]u8 = undefined; - // >= rather than > to make room for the null byte - if (name.len >= MFD_MAX_NAME_LEN) return error.NameTooLong; - @memcpy(path_with_null[0..name.len], name); - path_with_null[name.len] = 0; - return path_with_null; -} - -pub fn memfd_create(name: []const u8, flags: u32) !fd_t { - const name_t = try toMemFdPath(name); - return memfd_createZ(&name_t, flags); -} - -pub fn getrusage(who: i32) rusage { - var result: rusage = undefined; - const rc = system.getrusage(who, &result); - switch (errno(rc)) { - .SUCCESS => return result, - .INVAL => unreachable, - .FAULT => unreachable, - else => unreachable, - } -} - -pub const TIOCError = error{NotATerminal}; - -pub const TermiosGetError = TIOCError || UnexpectedError; - -pub fn tcgetattr(handle: fd_t) TermiosGetError!termios { - while (true) { - var term: termios = undefined; - switch (errno(system.tcgetattr(handle, &term))) { - .SUCCESS => return term, - .INTR => continue, - .BADF => unreachable, - .NOTTY => return error.NotATerminal, - else => |err| return unexpectedErrno(err), - } - } -} - -pub const TermiosSetError = TermiosGetError || error{ProcessOrphaned}; - -pub fn tcsetattr(handle: fd_t, optional_action: TCSA, termios_p: termios) TermiosSetError!void { - while (true) { - switch (errno(system.tcsetattr(handle, optional_action, &termios_p))) { - .SUCCESS => return, - .BADF => unreachable, - .INTR => continue, - .INVAL => unreachable, - .NOTTY => return error.NotATerminal, - .IO => return error.ProcessOrphaned, - else => |err| return unexpectedErrno(err), - } - } -} - -pub const TermioGetPgrpError = TIOCError || UnexpectedError; - -/// Returns the process group ID for the TTY associated with the given handle. -pub fn tcgetpgrp(handle: fd_t) TermioGetPgrpError!pid_t { - while (true) { - var pgrp: pid_t = undefined; - switch (errno(system.tcgetpgrp(handle, &pgrp))) { - .SUCCESS => return pgrp, - .BADF => unreachable, - .INVAL => unreachable, - .INTR => continue, - .NOTTY => return error.NotATerminal, - else => |err| return unexpectedErrno(err), - } - } -} - -pub const TermioSetPgrpError = TermioGetPgrpError || error{NotAPgrpMember}; - -/// Sets the controlling process group ID for given TTY. -/// handle must be valid fd_t to a TTY associated with calling process. -/// pgrp must be a valid process group, and the calling process must be a member -/// of that group. -pub fn tcsetpgrp(handle: fd_t, pgrp: pid_t) TermioSetPgrpError!void { - while (true) { - switch (errno(system.tcsetpgrp(handle, &pgrp))) { - .SUCCESS => return, - .BADF => unreachable, - .INVAL => unreachable, - .INTR => continue, - .NOTTY => return error.NotATerminal, - .PERM => return TermioSetPgrpError.NotAPgrpMember, - else => |err| return unexpectedErrno(err), - } - } -} - -pub const IoCtl_SIOCGIFINDEX_Error = error{ - FileSystem, - InterfaceNotFound, -} || UnexpectedError; - -pub fn ioctl_SIOCGIFINDEX(fd: fd_t, ifr: *ifreq) IoCtl_SIOCGIFINDEX_Error!void { - while (true) { - switch (errno(system.ioctl(fd, SIOCGIFINDEX, @intFromPtr(ifr)))) { - .SUCCESS => return, - .INVAL => unreachable, // Bad parameters. - .NOTTY => unreachable, - .NXIO => unreachable, - .BADF => unreachable, // Always a race condition. - .FAULT => unreachable, // Bad pointer parameter. - .INTR => continue, - .IO => return error.FileSystem, - .NODEV => return error.InterfaceNotFound, - 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)) { - .SUCCESS => return @intCast(rc), - .BADF, .INVAL => unreachable, - .NFILE => return error.SystemFdQuotaExceeded, - .NOMEM => return error.SystemResources, - .MFILE => return error.ProcessResources, - .NODEV => return error.InodeMountFail, - .NOSYS => return error.SystemOutdated, - else => |err| return unexpectedErrno(err), - } -} - -pub const SyncError = error{ - InputOutput, - NoSpaceLeft, - DiskQuota, - AccessDenied, -} || UnexpectedError; - -/// Write all pending file contents and metadata modifications to all filesystems. -pub fn sync() void { - system.sync(); -} - -/// Write all pending file contents and metadata modifications to the filesystem which contains the specified file. -pub fn syncfs(fd: fd_t) SyncError!void { - const rc = system.syncfs(fd); - switch (errno(rc)) { - .SUCCESS => return, - .BADF, .INVAL, .ROFS => unreachable, - .IO => return error.InputOutput, - .NOSPC => return error.NoSpaceLeft, - .DQUOT => return error.DiskQuota, - else => |err| return unexpectedErrno(err), - } -} - -/// Write all pending file contents and metadata modifications for the specified file descriptor to the underlying filesystem. -pub fn fsync(fd: fd_t) SyncError!void { - if (builtin.os.tag == .windows) { - if (windows.kernel32.FlushFileBuffers(fd) != 0) - return; - switch (windows.kernel32.GetLastError()) { - .SUCCESS => return, - .INVALID_HANDLE => unreachable, - .ACCESS_DENIED => return error.AccessDenied, // a sync was performed but the system couldn't update the access time - .UNEXP_NET_ERR => return error.InputOutput, - else => return error.InputOutput, - } - } - const rc = system.fsync(fd); - switch (errno(rc)) { - .SUCCESS => return, - .BADF, .INVAL, .ROFS => unreachable, - .IO => return error.InputOutput, - .NOSPC => return error.NoSpaceLeft, - .DQUOT => return error.DiskQuota, - else => |err| return unexpectedErrno(err), - } -} - -/// Write all pending file contents for the specified file descriptor to the underlying filesystem, but not necessarily the metadata. -pub fn fdatasync(fd: fd_t) SyncError!void { - if (builtin.os.tag == .windows) { - return fsync(fd) catch |err| switch (err) { - SyncError.AccessDenied => return, // fdatasync doesn't promise that the access time was synced - else => return err, - }; - } - const rc = system.fdatasync(fd); - switch (errno(rc)) { - .SUCCESS => return, - .BADF, .INVAL, .ROFS => unreachable, - .IO => return error.InputOutput, - .NOSPC => return error.NoSpaceLeft, - .DQUOT => return error.DiskQuota, - else => |err| return unexpectedErrno(err), - } -} - -pub const PrctlError = error{ - /// Can only occur with PR_SET_SECCOMP/SECCOMP_MODE_FILTER or - /// PR_SET_MM/PR_SET_MM_EXE_FILE - AccessDenied, - /// Can only occur with PR_SET_MM/PR_SET_MM_EXE_FILE - InvalidFileDescriptor, - InvalidAddress, - /// Can only occur with PR_SET_SPECULATION_CTRL, PR_MPX_ENABLE_MANAGEMENT, - /// or PR_MPX_DISABLE_MANAGEMENT - UnsupportedFeature, - /// Can only occur with PR_SET_FP_MODE - OperationNotSupported, - PermissionDenied, -} || UnexpectedError; - -pub fn prctl(option: PR, args: anytype) PrctlError!u31 { - if (@typeInfo(@TypeOf(args)) != .Struct) - @compileError("Expected tuple or struct argument, found " ++ @typeName(@TypeOf(args))); - if (args.len > 4) - @compileError("prctl takes a maximum of 4 optional arguments"); - - var buf: [4]usize = undefined; - { - comptime var i = 0; - inline while (i < args.len) : (i += 1) buf[i] = args[i]; - } - - const rc = system.prctl(@intFromEnum(option), buf[0], buf[1], buf[2], buf[3]); - switch (errno(rc)) { - .SUCCESS => return @intCast(rc), - .ACCES => return error.AccessDenied, - .BADF => return error.InvalidFileDescriptor, - .FAULT => return error.InvalidAddress, - .INVAL => unreachable, - .NODEV, .NXIO => return error.UnsupportedFeature, - .OPNOTSUPP => return error.OperationNotSupported, - .PERM, .BUSY => return error.PermissionDenied, - .RANGE => unreachable, - else => |err| return unexpectedErrno(err), - } -} - -pub const GetrlimitError = UnexpectedError; - -pub fn getrlimit(resource: rlimit_resource) GetrlimitError!rlimit { - const getrlimit_sym = if (lfs64_abi) system.getrlimit64 else system.getrlimit; - - var limits: rlimit = undefined; - switch (errno(getrlimit_sym(resource, &limits))) { - .SUCCESS => return limits, - .FAULT => unreachable, // bogus pointer - .INVAL => unreachable, - else => |err| return unexpectedErrno(err), - } -} - -pub const SetrlimitError = error{ PermissionDenied, LimitTooBig } || UnexpectedError; - -pub fn setrlimit(resource: rlimit_resource, limits: rlimit) SetrlimitError!void { - const setrlimit_sym = if (lfs64_abi) system.setrlimit64 else system.setrlimit; - - switch (errno(setrlimit_sym(resource, &limits))) { - .SUCCESS => return, - .FAULT => unreachable, // bogus pointer - .INVAL => return error.LimitTooBig, // this could also mean "invalid resource", but that would be unreachable - .PERM => return error.PermissionDenied, - else => |err| return unexpectedErrno(err), - } -} - -pub const MincoreError = error{ - /// A kernel resource was temporarily unavailable. - SystemResources, - /// vec points to an invalid address. - InvalidAddress, - /// addr is not page-aligned. - InvalidSyscall, - /// One of the following: - /// * length is greater than user space TASK_SIZE - addr - /// * addr + length contains unmapped memory - OutOfMemory, - /// The mincore syscall is not available on this version and configuration - /// of this UNIX-like kernel. - MincoreUnavailable, -} || UnexpectedError; - -/// Determine whether pages are resident in memory. -pub fn mincore(ptr: [*]align(mem.page_size) u8, length: usize, vec: [*]u8) MincoreError!void { - return switch (errno(system.mincore(ptr, length, vec))) { - .SUCCESS => {}, - .AGAIN => error.SystemResources, - .FAULT => error.InvalidAddress, - .INVAL => error.InvalidSyscall, - .NOMEM => error.OutOfMemory, - .NOSYS => error.MincoreUnavailable, - else => |err| unexpectedErrno(err), - }; -} - -pub const MadviseError = error{ - /// advice is MADV.REMOVE, but the specified address range is not a shared writable mapping. - AccessDenied, - /// advice is MADV.HWPOISON, but the caller does not have the CAP_SYS_ADMIN capability. - PermissionDenied, - /// A kernel resource was temporarily unavailable. - SystemResources, - /// One of the following: - /// * addr is not page-aligned or length is negative - /// * advice is not valid - /// * advice is MADV.DONTNEED or MADV.REMOVE and the specified address range - /// includes locked, Huge TLB pages, or VM_PFNMAP pages. - /// * advice is MADV.MERGEABLE or MADV.UNMERGEABLE, but the kernel was not - /// configured with CONFIG_KSM. - /// * advice is MADV.FREE or MADV.WIPEONFORK but the specified address range - /// includes file, Huge TLB, MAP.SHARED, or VM_PFNMAP ranges. - InvalidSyscall, - /// (for MADV.WILLNEED) Paging in this area would exceed the process's - /// maximum resident set size. - WouldExceedMaximumResidentSetSize, - /// One of the following: - /// * (for MADV.WILLNEED) Not enough memory: paging in failed. - /// * Addresses in the specified range are not currently mapped, or - /// are outside the address space of the process. - OutOfMemory, - /// The madvise syscall is not available on this version and configuration - /// of the Linux kernel. - MadviseUnavailable, - /// The operating system returned an undocumented error code. - Unexpected, -}; - -/// Give advice about use of memory. -/// This syscall is optional and is sometimes configured to be disabled. -pub fn madvise(ptr: [*]align(mem.page_size) u8, length: usize, advice: u32) MadviseError!void { - switch (errno(system.madvise(ptr, length, advice))) { - .SUCCESS => return, - .ACCES => return error.AccessDenied, - .AGAIN => return error.SystemResources, - .BADF => unreachable, // The map exists, but the area maps something that isn't a file. - .INVAL => return error.InvalidSyscall, - .IO => return error.WouldExceedMaximumResidentSetSize, - .NOMEM => return error.OutOfMemory, - .NOSYS => return error.MadviseUnavailable, - else => |err| return unexpectedErrno(err), - } -} - -pub const PerfEventOpenError = error{ - /// Returned if the perf_event_attr size value is too small (smaller - /// than PERF_ATTR_SIZE_VER0), too big (larger than the page size), - /// or larger than the kernel supports and the extra bytes are not - /// zero. When E2BIG is returned, the perf_event_attr size field is - /// overwritten by the kernel to be the size of the structure it was - /// expecting. - TooBig, - /// Returned when the requested event requires CAP_SYS_ADMIN permis‐ - /// sions (or a more permissive perf_event paranoid setting). Some - /// common cases where an unprivileged process may encounter this - /// error: attaching to a process owned by a different user; moni‐ - /// toring all processes on a given CPU (i.e., specifying the pid - /// argument as -1); and not setting exclude_kernel when the para‐ - /// noid setting requires it. - /// Also: - /// Returned on many (but not all) architectures when an unsupported - /// exclude_hv, exclude_idle, exclude_user, or exclude_kernel set‐ - /// ting is specified. - /// It can also happen, as with EACCES, when the requested event re‐ - /// quires CAP_SYS_ADMIN permissions (or a more permissive - /// perf_event paranoid setting). This includes setting a break‐ - /// point on a kernel address, and (since Linux 3.13) setting a ker‐ - /// nel function-trace tracepoint. - PermissionDenied, - /// Returned if another event already has exclusive access to the - /// PMU. - DeviceBusy, - /// Each opened event uses one file descriptor. If a large number - /// of events are opened, the per-process limit on the number of - /// open file descriptors will be reached, and no more events can be - /// created. - ProcessResources, - EventRequiresUnsupportedCpuFeature, - /// Returned if you try to add more breakpoint - /// events than supported by the hardware. - TooManyBreakpoints, - /// Returned if PERF_SAMPLE_STACK_USER is set in sample_type and it - /// is not supported by hardware. - SampleStackNotSupported, - /// Returned if an event requiring a specific hardware feature is - /// requested but there is no hardware support. This includes re‐ - /// questing low-skid events if not supported, branch tracing if it - /// is not available, sampling if no PMU interrupt is available, and - /// branch stacks for software events. - EventNotSupported, - /// Returned if PERF_SAMPLE_CALLCHAIN is requested and sam‐ - /// ple_max_stack is larger than the maximum specified in - /// /proc/sys/kernel/perf_event_max_stack. - SampleMaxStackOverflow, - /// Returned if attempting to attach to a process that does not exist. - ProcessNotFound, -} || UnexpectedError; - -pub fn perf_event_open( - attr: *linux.perf_event_attr, - pid: pid_t, - cpu: i32, - group_fd: fd_t, - flags: usize, -) PerfEventOpenError!fd_t { - const rc = linux.perf_event_open(attr, pid, cpu, group_fd, flags); - switch (errno(rc)) { - .SUCCESS => return @intCast(rc), - .@"2BIG" => return error.TooBig, - .ACCES => return error.PermissionDenied, - .BADF => unreachable, // group_fd file descriptor is not valid. - .BUSY => return error.DeviceBusy, - .FAULT => unreachable, // Segmentation fault. - .INVAL => unreachable, // Bad attr settings. - .INTR => unreachable, // Mixed perf and ftrace handling for a uprobe. - .MFILE => return error.ProcessResources, - .NODEV => return error.EventRequiresUnsupportedCpuFeature, - .NOENT => unreachable, // Invalid type setting. - .NOSPC => return error.TooManyBreakpoints, - .NOSYS => return error.SampleStackNotSupported, - .OPNOTSUPP => return error.EventNotSupported, - .OVERFLOW => return error.SampleMaxStackOverflow, - .PERM => return error.PermissionDenied, - .SRCH => return error.ProcessNotFound, - else => |err| return unexpectedErrno(err), - } -} - -pub const TimerFdCreateError = error{ - AccessDenied, - ProcessFdQuotaExceeded, - SystemFdQuotaExceeded, - NoDevice, - SystemResources, -} || UnexpectedError; - -pub const TimerFdGetError = error{InvalidHandle} || UnexpectedError; -pub const TimerFdSetError = TimerFdGetError || error{Canceled}; - -pub fn timerfd_create(clokid: i32, flags: linux.TFD) TimerFdCreateError!fd_t { - const rc = linux.timerfd_create(clokid, flags); - return switch (errno(rc)) { - .SUCCESS => @intCast(rc), - .INVAL => unreachable, - .MFILE => return error.ProcessFdQuotaExceeded, - .NFILE => return error.SystemFdQuotaExceeded, - .NODEV => return error.NoDevice, - .NOMEM => return error.SystemResources, - .PERM => return error.AccessDenied, - else => |err| return unexpectedErrno(err), - }; -} - -pub fn timerfd_settime( - fd: i32, - flags: linux.TFD.TIMER, - new_value: *const linux.itimerspec, - old_value: ?*linux.itimerspec, -) TimerFdSetError!void { - const rc = linux.timerfd_settime(fd, flags, new_value, old_value); - return switch (errno(rc)) { - .SUCCESS => {}, - .BADF => error.InvalidHandle, - .FAULT => unreachable, - .INVAL => unreachable, - .CANCELED => error.Canceled, - else => |err| return unexpectedErrno(err), - }; -} - -pub fn timerfd_gettime(fd: i32) TimerFdGetError!linux.itimerspec { - var curr_value: linux.itimerspec = undefined; - const rc = linux.timerfd_gettime(fd, &curr_value); - return switch (errno(rc)) { - .SUCCESS => return curr_value, - .BADF => error.InvalidHandle, - .FAULT => unreachable, - .INVAL => unreachable, - else => |err| return unexpectedErrno(err), - }; -} - -pub const PtraceError = error{ - DeviceBusy, - InputOutput, - ProcessNotFound, - PermissionDenied, -} || UnexpectedError; - -pub fn ptrace(request: u32, pid: pid_t, addr: usize, signal: usize) PtraceError!void { - if (builtin.os.tag == .windows or builtin.os.tag == .wasi) - @compileError("Unsupported OS"); - - return switch (builtin.os.tag) { - .linux => switch (errno(linux.ptrace(request, pid, addr, signal, 0))) { - .SUCCESS => {}, - .SRCH => error.ProcessNotFound, - .FAULT => unreachable, - .INVAL => unreachable, - .IO => return error.InputOutput, - .PERM => error.PermissionDenied, - .BUSY => error.DeviceBusy, - else => |err| return unexpectedErrno(err), - }, - - .macos, .ios, .tvos, .watchos => switch (errno(darwin.ptrace( - @intCast(request), - pid, - @ptrFromInt(addr), - @intCast(signal), - ))) { - .SUCCESS => {}, - .SRCH => error.ProcessNotFound, - .INVAL => unreachable, - .PERM => error.PermissionDenied, - .BUSY => error.DeviceBusy, - else => |err| return unexpectedErrno(err), - }, - - else => switch (errno(system.ptrace(request, pid, addr, signal))) { - .SUCCESS => {}, - .SRCH => error.ProcessNotFound, - .INVAL => unreachable, - .PERM => error.PermissionDenied, - .BUSY => error.DeviceBusy, - else => |err| return unexpectedErrno(err), - }, - }; -} - -const lfs64_abi = builtin.os.tag == .linux and builtin.link_libc and builtin.abi.isGnu(); diff --git a/lib/std/os/emscripten.zig b/lib/std/os/emscripten.zig index 924b2dd0b21d..a37b2b1f5c91 100644 --- a/lib/std/os/emscripten.zig +++ b/lib/std/os/emscripten.zig @@ -1,8 +1,8 @@ const std = @import("std"); const builtin = @import("builtin"); const wasi = std.os.wasi; -const iovec = std.os.iovec; -const iovec_const = std.os.iovec_const; +const iovec = std.posix.iovec; +const iovec_const = std.posix.iovec_const; const c = std.c; pub const FILE = c.FILE; diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index a5cdab18ce81..ba0ffb452f44 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -18,9 +18,9 @@ const is_mips = native_arch.isMIPS(); const is_ppc = native_arch.isPPC(); const is_ppc64 = native_arch.isPPC64(); const is_sparc = native_arch.isSPARC(); -const iovec = std.os.iovec; -const iovec_const = std.os.iovec_const; -const ACCMODE = std.os.ACCMODE; +const iovec = std.posix.iovec; +const iovec_const = std.posix.iovec_const; +const ACCMODE = std.posix.ACCMODE; test { if (builtin.os.tag == .linux) { @@ -451,10 +451,11 @@ fn splitValue64(val: i64) [2]u32 { } /// Get the errno from a syscall return value, or 0 for no error. -pub fn getErrno(r: usize) E { - const signed_r = @as(isize, @bitCast(r)); +/// The public API is exposed via the `E` namespace. +fn errnoFromSyscall(r: usize) E { + const signed_r: isize = @bitCast(r); const int = if (signed_r > -4096 and signed_r < 0) -signed_r else 0; - return @as(E, @enumFromInt(int)); + return @enumFromInt(int); } pub fn dup(old: i32) usize { @@ -1561,7 +1562,7 @@ pub fn sigaction(sig: u6, noalias act: ?*const Sigaction, noalias oact: ?*Sigact .sparc, .sparc64 => syscall5(.rt_sigaction, sig, ksa_arg, oldksa_arg, @intFromPtr(ksa.restorer), mask_size), else => syscall4(.rt_sigaction, sig, ksa_arg, oldksa_arg, mask_size), }; - if (getErrno(result) != .SUCCESS) return result; + if (E.init(result) != .SUCCESS) return result; if (oact) |old| { old.handler.handler = oldksa.handler; @@ -1648,12 +1649,12 @@ pub fn sendmmsg(fd: i32, msgvec: [*]mmsghdr_const, vlen: u32, flags: u32) usize if (next_unsent < i) { const batch_size = i - next_unsent; const r = syscall4(.sendmmsg, @as(usize, @bitCast(@as(isize, fd))), @intFromPtr(&msgvec[next_unsent]), batch_size, flags); - if (getErrno(r) != 0) return next_unsent; + if (E.init(r) != 0) return next_unsent; if (r < batch_size) return next_unsent + r; } // send current message as own packet const r = sendmsg(fd, &msg.msg_hdr, flags); - if (getErrno(r) != 0) return r; + if (E.init(r) != 0) return r; // Linux limits the total bytes sent by sendmsg to INT_MAX, so this cast is safe. msg.msg_len = @as(u32, @intCast(r)); next_unsent = i + 1; @@ -1665,7 +1666,7 @@ pub fn sendmmsg(fd: i32, msgvec: [*]mmsghdr_const, vlen: u32, flags: u32) usize if (next_unsent < kvlen or next_unsent == 0) { // want to make sure at least one syscall occurs (e.g. to trigger MSG.EOR) const batch_size = kvlen - next_unsent; const r = syscall4(.sendmmsg, @as(usize, @bitCast(@as(isize, fd))), @intFromPtr(&msgvec[next_unsent]), batch_size, flags); - if (getErrno(r) != 0) return r; + if (E.init(r) != 0) return r; return next_unsent + r; } return kvlen; @@ -2263,13 +2264,609 @@ pub fn map_shadow_stack(addr: u64, size: u64, flags: u32) usize { } pub const E = switch (native_arch) { - .mips, .mipsel => @import("linux/errno/mips.zig").E, - .sparc, .sparcel, .sparc64 => @import("linux/errno/sparc.zig").E, - else => @import("linux/errno/generic.zig").E, + .mips, .mipsel => enum(i32) { + /// No error occurred. + SUCCESS = 0, + + PERM = 1, + NOENT = 2, + SRCH = 3, + INTR = 4, + IO = 5, + NXIO = 6, + @"2BIG" = 7, + NOEXEC = 8, + BADF = 9, + CHILD = 10, + /// Also used for WOULDBLOCK. + AGAIN = 11, + NOMEM = 12, + ACCES = 13, + FAULT = 14, + NOTBLK = 15, + BUSY = 16, + EXIST = 17, + XDEV = 18, + NODEV = 19, + NOTDIR = 20, + ISDIR = 21, + INVAL = 22, + NFILE = 23, + MFILE = 24, + NOTTY = 25, + TXTBSY = 26, + FBIG = 27, + NOSPC = 28, + SPIPE = 29, + ROFS = 30, + MLINK = 31, + PIPE = 32, + DOM = 33, + RANGE = 34, + + NOMSG = 35, + IDRM = 36, + CHRNG = 37, + L2NSYNC = 38, + L3HLT = 39, + L3RST = 40, + LNRNG = 41, + UNATCH = 42, + NOCSI = 43, + L2HLT = 44, + DEADLK = 45, + NOLCK = 46, + BADE = 50, + BADR = 51, + XFULL = 52, + NOANO = 53, + BADRQC = 54, + BADSLT = 55, + DEADLOCK = 56, + BFONT = 59, + NOSTR = 60, + NODATA = 61, + TIME = 62, + NOSR = 63, + NONET = 64, + NOPKG = 65, + REMOTE = 66, + NOLINK = 67, + ADV = 68, + SRMNT = 69, + COMM = 70, + PROTO = 71, + DOTDOT = 73, + MULTIHOP = 74, + BADMSG = 77, + NAMETOOLONG = 78, + OVERFLOW = 79, + NOTUNIQ = 80, + BADFD = 81, + REMCHG = 82, + LIBACC = 83, + LIBBAD = 84, + LIBSCN = 85, + LIBMAX = 86, + LIBEXEC = 87, + ILSEQ = 88, + NOSYS = 89, + LOOP = 90, + RESTART = 91, + STRPIPE = 92, + NOTEMPTY = 93, + USERS = 94, + NOTSOCK = 95, + DESTADDRREQ = 96, + MSGSIZE = 97, + PROTOTYPE = 98, + NOPROTOOPT = 99, + PROTONOSUPPORT = 120, + SOCKTNOSUPPORT = 121, + OPNOTSUPP = 122, + PFNOSUPPORT = 123, + AFNOSUPPORT = 124, + ADDRINUSE = 125, + ADDRNOTAVAIL = 126, + NETDOWN = 127, + NETUNREACH = 128, + NETRESET = 129, + CONNABORTED = 130, + CONNRESET = 131, + NOBUFS = 132, + ISCONN = 133, + NOTCONN = 134, + UCLEAN = 135, + NOTNAM = 137, + NAVAIL = 138, + ISNAM = 139, + REMOTEIO = 140, + SHUTDOWN = 143, + TOOMANYREFS = 144, + TIMEDOUT = 145, + CONNREFUSED = 146, + HOSTDOWN = 147, + HOSTUNREACH = 148, + ALREADY = 149, + INPROGRESS = 150, + STALE = 151, + CANCELED = 158, + NOMEDIUM = 159, + MEDIUMTYPE = 160, + NOKEY = 161, + KEYEXPIRED = 162, + KEYREVOKED = 163, + KEYREJECTED = 164, + OWNERDEAD = 165, + NOTRECOVERABLE = 166, + RFKILL = 167, + HWPOISON = 168, + DQUOT = 1133, + _, + + pub const init = errnoFromSyscall; + }, + .sparc, .sparcel, .sparc64 => enum(i32) { + /// No error occurred. + SUCCESS = 0, + + PERM = 1, + NOENT = 2, + SRCH = 3, + INTR = 4, + IO = 5, + NXIO = 6, + @"2BIG" = 7, + NOEXEC = 8, + BADF = 9, + CHILD = 10, + /// Also used for WOULDBLOCK + AGAIN = 11, + NOMEM = 12, + ACCES = 13, + FAULT = 14, + NOTBLK = 15, + BUSY = 16, + EXIST = 17, + XDEV = 18, + NODEV = 19, + NOTDIR = 20, + ISDIR = 21, + INVAL = 22, + NFILE = 23, + MFILE = 24, + NOTTY = 25, + TXTBSY = 26, + FBIG = 27, + NOSPC = 28, + SPIPE = 29, + ROFS = 30, + MLINK = 31, + PIPE = 32, + DOM = 33, + RANGE = 34, + + INPROGRESS = 36, + ALREADY = 37, + NOTSOCK = 38, + DESTADDRREQ = 39, + MSGSIZE = 40, + PROTOTYPE = 41, + NOPROTOOPT = 42, + PROTONOSUPPORT = 43, + SOCKTNOSUPPORT = 44, + /// Also used for NOTSUP + OPNOTSUPP = 45, + PFNOSUPPORT = 46, + AFNOSUPPORT = 47, + ADDRINUSE = 48, + ADDRNOTAVAIL = 49, + NETDOWN = 50, + NETUNREACH = 51, + NETRESET = 52, + CONNABORTED = 53, + CONNRESET = 54, + NOBUFS = 55, + ISCONN = 56, + NOTCONN = 57, + SHUTDOWN = 58, + TOOMANYREFS = 59, + TIMEDOUT = 60, + CONNREFUSED = 61, + LOOP = 62, + NAMETOOLONG = 63, + HOSTDOWN = 64, + HOSTUNREACH = 65, + NOTEMPTY = 66, + PROCLIM = 67, + USERS = 68, + DQUOT = 69, + STALE = 70, + REMOTE = 71, + NOSTR = 72, + TIME = 73, + NOSR = 74, + NOMSG = 75, + BADMSG = 76, + IDRM = 77, + DEADLK = 78, + NOLCK = 79, + NONET = 80, + RREMOTE = 81, + NOLINK = 82, + ADV = 83, + SRMNT = 84, + COMM = 85, + PROTO = 86, + MULTIHOP = 87, + DOTDOT = 88, + REMCHG = 89, + NOSYS = 90, + STRPIPE = 91, + OVERFLOW = 92, + BADFD = 93, + CHRNG = 94, + L2NSYNC = 95, + L3HLT = 96, + L3RST = 97, + LNRNG = 98, + UNATCH = 99, + NOCSI = 100, + L2HLT = 101, + BADE = 102, + BADR = 103, + XFULL = 104, + NOANO = 105, + BADRQC = 106, + BADSLT = 107, + DEADLOCK = 108, + BFONT = 109, + LIBEXEC = 110, + NODATA = 111, + LIBBAD = 112, + NOPKG = 113, + LIBACC = 114, + NOTUNIQ = 115, + RESTART = 116, + UCLEAN = 117, + NOTNAM = 118, + NAVAIL = 119, + ISNAM = 120, + REMOTEIO = 121, + ILSEQ = 122, + LIBMAX = 123, + LIBSCN = 124, + NOMEDIUM = 125, + MEDIUMTYPE = 126, + CANCELED = 127, + NOKEY = 128, + KEYEXPIRED = 129, + KEYREVOKED = 130, + KEYREJECTED = 131, + OWNERDEAD = 132, + NOTRECOVERABLE = 133, + RFKILL = 134, + HWPOISON = 135, + _, + + pub const init = errnoFromSyscall; + }, + else => enum(u16) { + /// No error occurred. + /// Same code used for `NSROK`. + SUCCESS = 0, + /// Operation not permitted + PERM = 1, + /// No such file or directory + NOENT = 2, + /// No such process + SRCH = 3, + /// Interrupted system call + INTR = 4, + /// I/O error + IO = 5, + /// No such device or address + NXIO = 6, + /// Arg list too long + @"2BIG" = 7, + /// Exec format error + NOEXEC = 8, + /// Bad file number + BADF = 9, + /// No child processes + CHILD = 10, + /// Try again + /// Also means: WOULDBLOCK: operation would block + AGAIN = 11, + /// Out of memory + NOMEM = 12, + /// Permission denied + ACCES = 13, + /// Bad address + FAULT = 14, + /// Block device required + NOTBLK = 15, + /// Device or resource busy + BUSY = 16, + /// File exists + EXIST = 17, + /// Cross-device link + XDEV = 18, + /// No such device + NODEV = 19, + /// Not a directory + NOTDIR = 20, + /// Is a directory + ISDIR = 21, + /// Invalid argument + INVAL = 22, + /// File table overflow + NFILE = 23, + /// Too many open files + MFILE = 24, + /// Not a typewriter + NOTTY = 25, + /// Text file busy + TXTBSY = 26, + /// File too large + FBIG = 27, + /// No space left on device + NOSPC = 28, + /// Illegal seek + SPIPE = 29, + /// Read-only file system + ROFS = 30, + /// Too many links + MLINK = 31, + /// Broken pipe + PIPE = 32, + /// Math argument out of domain of func + DOM = 33, + /// Math result not representable + RANGE = 34, + /// Resource deadlock would occur + DEADLK = 35, + /// File name too long + NAMETOOLONG = 36, + /// No record locks available + NOLCK = 37, + /// Function not implemented + NOSYS = 38, + /// Directory not empty + NOTEMPTY = 39, + /// Too many symbolic links encountered + LOOP = 40, + /// No message of desired type + NOMSG = 42, + /// Identifier removed + IDRM = 43, + /// Channel number out of range + CHRNG = 44, + /// Level 2 not synchronized + L2NSYNC = 45, + /// Level 3 halted + L3HLT = 46, + /// Level 3 reset + L3RST = 47, + /// Link number out of range + LNRNG = 48, + /// Protocol driver not attached + UNATCH = 49, + /// No CSI structure available + NOCSI = 50, + /// Level 2 halted + L2HLT = 51, + /// Invalid exchange + BADE = 52, + /// Invalid request descriptor + BADR = 53, + /// Exchange full + XFULL = 54, + /// No anode + NOANO = 55, + /// Invalid request code + BADRQC = 56, + /// Invalid slot + BADSLT = 57, + /// Bad font file format + BFONT = 59, + /// Device not a stream + NOSTR = 60, + /// No data available + NODATA = 61, + /// Timer expired + TIME = 62, + /// Out of streams resources + NOSR = 63, + /// Machine is not on the network + NONET = 64, + /// Package not installed + NOPKG = 65, + /// Object is remote + REMOTE = 66, + /// Link has been severed + NOLINK = 67, + /// Advertise error + ADV = 68, + /// Srmount error + SRMNT = 69, + /// Communication error on send + COMM = 70, + /// Protocol error + PROTO = 71, + /// Multihop attempted + MULTIHOP = 72, + /// RFS specific error + DOTDOT = 73, + /// Not a data message + BADMSG = 74, + /// Value too large for defined data type + OVERFLOW = 75, + /// Name not unique on network + NOTUNIQ = 76, + /// File descriptor in bad state + BADFD = 77, + /// Remote address changed + REMCHG = 78, + /// Can not access a needed shared library + LIBACC = 79, + /// Accessing a corrupted shared library + LIBBAD = 80, + /// .lib section in a.out corrupted + LIBSCN = 81, + /// Attempting to link in too many shared libraries + LIBMAX = 82, + /// Cannot exec a shared library directly + LIBEXEC = 83, + /// Illegal byte sequence + ILSEQ = 84, + /// Interrupted system call should be restarted + RESTART = 85, + /// Streams pipe error + STRPIPE = 86, + /// Too many users + USERS = 87, + /// Socket operation on non-socket + NOTSOCK = 88, + /// Destination address required + DESTADDRREQ = 89, + /// Message too long + MSGSIZE = 90, + /// Protocol wrong type for socket + PROTOTYPE = 91, + /// Protocol not available + NOPROTOOPT = 92, + /// Protocol not supported + PROTONOSUPPORT = 93, + /// Socket type not supported + SOCKTNOSUPPORT = 94, + /// Operation not supported on transport endpoint + /// This code also means `NOTSUP`. + OPNOTSUPP = 95, + /// Protocol family not supported + PFNOSUPPORT = 96, + /// Address family not supported by protocol + AFNOSUPPORT = 97, + /// Address already in use + ADDRINUSE = 98, + /// Cannot assign requested address + ADDRNOTAVAIL = 99, + /// Network is down + NETDOWN = 100, + /// Network is unreachable + NETUNREACH = 101, + /// Network dropped connection because of reset + NETRESET = 102, + /// Software caused connection abort + CONNABORTED = 103, + /// Connection reset by peer + CONNRESET = 104, + /// No buffer space available + NOBUFS = 105, + /// Transport endpoint is already connected + ISCONN = 106, + /// Transport endpoint is not connected + NOTCONN = 107, + /// Cannot send after transport endpoint shutdown + SHUTDOWN = 108, + /// Too many references: cannot splice + TOOMANYREFS = 109, + /// Connection timed out + TIMEDOUT = 110, + /// Connection refused + CONNREFUSED = 111, + /// Host is down + HOSTDOWN = 112, + /// No route to host + HOSTUNREACH = 113, + /// Operation already in progress + ALREADY = 114, + /// Operation now in progress + INPROGRESS = 115, + /// Stale NFS file handle + STALE = 116, + /// Structure needs cleaning + UCLEAN = 117, + /// Not a XENIX named type file + NOTNAM = 118, + /// No XENIX semaphores available + NAVAIL = 119, + /// Is a named type file + ISNAM = 120, + /// Remote I/O error + REMOTEIO = 121, + /// Quota exceeded + DQUOT = 122, + /// No medium found + NOMEDIUM = 123, + /// Wrong medium type + MEDIUMTYPE = 124, + /// Operation canceled + CANCELED = 125, + /// Required key not available + NOKEY = 126, + /// Key has expired + KEYEXPIRED = 127, + /// Key has been revoked + KEYREVOKED = 128, + /// Key was rejected by service + KEYREJECTED = 129, + // for robust mutexes + /// Owner died + OWNERDEAD = 130, + /// State not recoverable + NOTRECOVERABLE = 131, + /// Operation not possible due to RF-kill + RFKILL = 132, + /// Memory page has hardware error + HWPOISON = 133, + // nameserver query return codes + /// DNS server returned answer with no data + NSRNODATA = 160, + /// DNS server claims query was misformatted + NSRFORMERR = 161, + /// DNS server returned general failure + NSRSERVFAIL = 162, + /// Domain name not found + NSRNOTFOUND = 163, + /// DNS server does not implement requested operation + NSRNOTIMP = 164, + /// DNS server refused query + NSRREFUSED = 165, + /// Misformatted DNS query + NSRBADQUERY = 166, + /// Misformatted domain name + NSRBADNAME = 167, + /// Unsupported address family + NSRBADFAMILY = 168, + /// Misformatted DNS reply + NSRBADRESP = 169, + /// Could not contact DNS servers + NSRCONNREFUSED = 170, + /// Timeout while contacting DNS servers + NSRTIMEOUT = 171, + /// End of file + NSROF = 172, + /// Error reading file + NSRFILE = 173, + /// Out of memory + NSRNOMEM = 174, + /// Application terminated lookup + NSRDESTRUCTION = 175, + /// Domain name is too long + NSRQUERYDOMAINTOOLONG = 176, + /// Domain name is too long + NSRCNAMELOOP = 177, + + _, + + pub const init = errnoFromSyscall; + }, }; pub const pid_t = i32; pub const fd_t = i32; +pub const socket_t = i32; pub const uid_t = u32; pub const gid_t = u32; pub const clock_t = isize; diff --git a/lib/std/os/linux/IoUring.zig b/lib/std/os/linux/IoUring.zig index 9e9957c6974a..0e3c4ce33858 100644 --- a/lib/std/os/linux/IoUring.zig +++ b/lib/std/os/linux/IoUring.zig @@ -4,12 +4,12 @@ const builtin = @import("builtin"); const assert = std.debug.assert; const mem = std.mem; const net = std.net; -const os = std.os; const posix = std.posix; -const linux = os.linux; +const linux = std.os.linux; const testing = std.testing; +const is_linux = builtin.os.tag == .linux; -fd: os.fd_t = -1, +fd: posix.fd_t = -1, sq: SubmissionQueue, cq: CompletionQueue, flags: u32, @@ -45,7 +45,7 @@ pub fn init_params(entries: u16, p: *linux.io_uring_params) !IoUring { assert(p.resv[2] == 0); const res = linux.io_uring_setup(entries, p); - switch (linux.getErrno(res)) { + switch (linux.E.init(res)) { .SUCCESS => {}, .FAULT => return error.ParamsOutsideAccessibleAddressSpace, // The resv array contains non-zero data, p.flags contains an unsupported flag, @@ -59,11 +59,11 @@ pub fn init_params(entries: u16, p: *linux.io_uring_params) !IoUring { // or a container seccomp policy prohibits io_uring syscalls: .PERM => return error.PermissionDenied, .NOSYS => return error.SystemOutdated, - else => |errno| return os.unexpectedErrno(errno), + else => |errno| return posix.unexpectedErrno(errno), } - const fd = @as(os.fd_t, @intCast(res)); + const fd = @as(posix.fd_t, @intCast(res)); assert(fd >= 0); - errdefer os.close(fd); + errdefer posix.close(fd); // Kernel versions 5.4 and up use only one mmap() for the submission and completion queues. // This is not an optional feature for us... if the kernel does it, we have to do it. @@ -121,7 +121,7 @@ pub fn deinit(self: *IoUring) void { // The mmaps depend on the fd, so the order of these calls is important: self.cq.deinit(); self.sq.deinit(); - os.close(self.fd); + posix.close(self.fd); self.fd = -1; } @@ -174,7 +174,7 @@ pub fn submit_and_wait(self: *IoUring, wait_nr: u32) !u32 { pub fn enter(self: *IoUring, to_submit: u32, min_complete: u32, flags: u32) !u32 { assert(self.fd >= 0); const res = linux.io_uring_enter(self.fd, to_submit, min_complete, flags, null); - switch (linux.getErrno(res)) { + switch (linux.E.init(res)) { .SUCCESS => {}, // The kernel was unable to allocate memory or ran out of resources for the request. // The application should wait for some completions and try again: @@ -200,7 +200,7 @@ pub fn enter(self: *IoUring, to_submit: u32, min_complete: u32, flags: u32) !u32 // The operation was interrupted by a delivery of a signal before it could complete. // This can happen while waiting for events with IORING_ENTER_GETEVENTS: .INTR => return error.SignalInterrupt, - else => |errno| return os.unexpectedErrno(errno), + else => |errno| return posix.unexpectedErrno(errno), } return @as(u32, @intCast(res)); } @@ -344,7 +344,7 @@ pub fn cq_advance(self: *IoUring, count: u32) void { /// apply to the write, since the fsync may complete before the write is issued to the disk. /// You should preferably use `link_with_next_sqe()` on a write's SQE to link it with an fsync, /// or else insert a full write barrier using `drain_previous_sqes()` when queueing an fsync. -pub fn fsync(self: *IoUring, user_data: u64, fd: os.fd_t, flags: u32) !*linux.io_uring_sqe { +pub fn fsync(self: *IoUring, user_data: u64, fd: posix.fd_t, flags: u32) !*linux.io_uring_sqe { const sqe = try self.get_sqe(); sqe.prep_fsync(fd, flags); sqe.user_data = user_data; @@ -369,7 +369,7 @@ pub const ReadBuffer = union(enum) { buffer: []u8, /// io_uring will read directly into these buffers using readv. - iovecs: []const os.iovec, + iovecs: []const posix.iovec, /// io_uring will select a buffer that has previously been provided with `provide_buffers`. /// The buffer group reference by `group_id` must contain at least one buffer for the read to work. @@ -389,7 +389,7 @@ pub const ReadBuffer = union(enum) { pub fn read( self: *IoUring, user_data: u64, - fd: os.fd_t, + fd: posix.fd_t, buffer: ReadBuffer, offset: u64, ) !*linux.io_uring_sqe { @@ -412,7 +412,7 @@ pub fn read( pub fn write( self: *IoUring, user_data: u64, - fd: os.fd_t, + fd: posix.fd_t, buffer: []const u8, offset: u64, ) !*linux.io_uring_sqe { @@ -436,7 +436,7 @@ pub fn write( /// See https://github.com/axboe/liburing/issues/291 /// /// Returns a pointer to the SQE so that you can further modify the SQE for advanced use cases. -pub fn splice(self: *IoUring, user_data: u64, fd_in: os.fd_t, off_in: u64, fd_out: os.fd_t, off_out: u64, len: usize) !*linux.io_uring_sqe { +pub fn splice(self: *IoUring, user_data: u64, fd_in: posix.fd_t, off_in: u64, fd_out: posix.fd_t, off_out: u64, len: usize) !*linux.io_uring_sqe { const sqe = try self.get_sqe(); sqe.prep_splice(fd_in, off_in, fd_out, off_out, len); sqe.user_data = user_data; @@ -451,8 +451,8 @@ pub fn splice(self: *IoUring, user_data: u64, fd_in: os.fd_t, off_in: u64, fd_ou pub fn read_fixed( self: *IoUring, user_data: u64, - fd: os.fd_t, - buffer: *os.iovec, + fd: posix.fd_t, + buffer: *posix.iovec, offset: u64, buffer_index: u16, ) !*linux.io_uring_sqe { @@ -469,8 +469,8 @@ pub fn read_fixed( pub fn writev( self: *IoUring, user_data: u64, - fd: os.fd_t, - iovecs: []const os.iovec_const, + fd: posix.fd_t, + iovecs: []const posix.iovec_const, offset: u64, ) !*linux.io_uring_sqe { const sqe = try self.get_sqe(); @@ -487,8 +487,8 @@ pub fn writev( pub fn write_fixed( self: *IoUring, user_data: u64, - fd: os.fd_t, - buffer: *os.iovec, + fd: posix.fd_t, + buffer: *posix.iovec, offset: u64, buffer_index: u16, ) !*linux.io_uring_sqe { @@ -504,9 +504,9 @@ pub fn write_fixed( pub fn accept( self: *IoUring, user_data: u64, - fd: os.fd_t, - addr: ?*os.sockaddr, - addrlen: ?*os.socklen_t, + fd: posix.fd_t, + addr: ?*posix.sockaddr, + addrlen: ?*posix.socklen_t, flags: u32, ) !*linux.io_uring_sqe { const sqe = try self.get_sqe(); @@ -526,9 +526,9 @@ pub fn accept( pub fn accept_multishot( self: *IoUring, user_data: u64, - fd: os.fd_t, - addr: ?*os.sockaddr, - addrlen: ?*os.socklen_t, + fd: posix.fd_t, + addr: ?*posix.sockaddr, + addrlen: ?*posix.socklen_t, flags: u32, ) !*linux.io_uring_sqe { const sqe = try self.get_sqe(); @@ -551,9 +551,9 @@ pub fn accept_multishot( pub fn accept_direct( self: *IoUring, user_data: u64, - fd: os.fd_t, - addr: ?*os.sockaddr, - addrlen: ?*os.socklen_t, + fd: posix.fd_t, + addr: ?*posix.sockaddr, + addrlen: ?*posix.socklen_t, flags: u32, ) !*linux.io_uring_sqe { const sqe = try self.get_sqe(); @@ -567,9 +567,9 @@ pub fn accept_direct( pub fn accept_multishot_direct( self: *IoUring, user_data: u64, - fd: os.fd_t, - addr: ?*os.sockaddr, - addrlen: ?*os.socklen_t, + fd: posix.fd_t, + addr: ?*posix.sockaddr, + addrlen: ?*posix.socklen_t, flags: u32, ) !*linux.io_uring_sqe { const sqe = try self.get_sqe(); @@ -583,9 +583,9 @@ pub fn accept_multishot_direct( pub fn connect( self: *IoUring, user_data: u64, - fd: os.fd_t, - addr: *const os.sockaddr, - addrlen: os.socklen_t, + fd: posix.fd_t, + addr: *const posix.sockaddr, + addrlen: posix.socklen_t, ) !*linux.io_uring_sqe { const sqe = try self.get_sqe(); sqe.prep_connect(fd, addr, addrlen); @@ -598,8 +598,8 @@ pub fn connect( pub fn epoll_ctl( self: *IoUring, user_data: u64, - epfd: os.fd_t, - fd: os.fd_t, + epfd: posix.fd_t, + fd: posix.fd_t, op: u32, ev: ?*linux.epoll_event, ) !*linux.io_uring_sqe { @@ -629,7 +629,7 @@ pub const RecvBuffer = union(enum) { pub fn recv( self: *IoUring, user_data: u64, - fd: os.fd_t, + fd: posix.fd_t, buffer: RecvBuffer, flags: u32, ) !*linux.io_uring_sqe { @@ -653,7 +653,7 @@ pub fn recv( pub fn send( self: *IoUring, user_data: u64, - fd: os.fd_t, + fd: posix.fd_t, buffer: []const u8, flags: u32, ) !*linux.io_uring_sqe { @@ -681,7 +681,7 @@ pub fn send( pub fn send_zc( self: *IoUring, user_data: u64, - fd: os.fd_t, + fd: posix.fd_t, buffer: []const u8, send_flags: u32, zc_flags: u16, @@ -698,7 +698,7 @@ pub fn send_zc( pub fn send_zc_fixed( self: *IoUring, user_data: u64, - fd: os.fd_t, + fd: posix.fd_t, buffer: []const u8, send_flags: u32, zc_flags: u16, @@ -716,8 +716,8 @@ pub fn send_zc_fixed( pub fn recvmsg( self: *IoUring, user_data: u64, - fd: os.fd_t, - msg: *os.msghdr, + fd: posix.fd_t, + msg: *posix.msghdr, flags: u32, ) !*linux.io_uring_sqe { const sqe = try self.get_sqe(); @@ -732,8 +732,8 @@ pub fn recvmsg( pub fn sendmsg( self: *IoUring, user_data: u64, - fd: os.fd_t, - msg: *const os.msghdr_const, + fd: posix.fd_t, + msg: *const posix.msghdr_const, flags: u32, ) !*linux.io_uring_sqe { const sqe = try self.get_sqe(); @@ -748,8 +748,8 @@ pub fn sendmsg( pub fn sendmsg_zc( self: *IoUring, user_data: u64, - fd: os.fd_t, - msg: *const os.msghdr_const, + fd: posix.fd_t, + msg: *const posix.msghdr_const, flags: u32, ) !*linux.io_uring_sqe { const sqe = try self.get_sqe(); @@ -764,10 +764,10 @@ pub fn sendmsg_zc( pub fn openat( self: *IoUring, user_data: u64, - fd: os.fd_t, + fd: posix.fd_t, path: [*:0]const u8, flags: linux.O, - mode: os.mode_t, + mode: posix.mode_t, ) !*linux.io_uring_sqe { const sqe = try self.get_sqe(); sqe.prep_openat(fd, path, flags, mode); @@ -789,10 +789,10 @@ pub fn openat( pub fn openat_direct( self: *IoUring, user_data: u64, - fd: os.fd_t, + fd: posix.fd_t, path: [*:0]const u8, flags: linux.O, - mode: os.mode_t, + mode: posix.mode_t, file_index: u32, ) !*linux.io_uring_sqe { const sqe = try self.get_sqe(); @@ -804,7 +804,7 @@ pub fn openat_direct( /// Queues (but does not submit) an SQE to perform a `close(2)`. /// Returns a pointer to the SQE. /// Available since 5.6. -pub fn close(self: *IoUring, user_data: u64, fd: os.fd_t) !*linux.io_uring_sqe { +pub fn close(self: *IoUring, user_data: u64, fd: posix.fd_t) !*linux.io_uring_sqe { const sqe = try self.get_sqe(); sqe.prep_close(fd); sqe.user_data = user_data; @@ -836,7 +836,7 @@ pub fn close_direct(self: *IoUring, user_data: u64, file_index: u32) !*linux.io_ pub fn timeout( self: *IoUring, user_data: u64, - ts: *const os.linux.kernel_timespec, + ts: *const linux.kernel_timespec, count: u32, flags: u32, ) !*linux.io_uring_sqe { @@ -885,7 +885,7 @@ pub fn timeout_remove( pub fn link_timeout( self: *IoUring, user_data: u64, - ts: *const os.linux.kernel_timespec, + ts: *const linux.kernel_timespec, flags: u32, ) !*linux.io_uring_sqe { const sqe = try self.get_sqe(); @@ -899,7 +899,7 @@ pub fn link_timeout( pub fn poll_add( self: *IoUring, user_data: u64, - fd: os.fd_t, + fd: posix.fd_t, poll_mask: u32, ) !*linux.io_uring_sqe { const sqe = try self.get_sqe(); @@ -942,7 +942,7 @@ pub fn poll_update( pub fn fallocate( self: *IoUring, user_data: u64, - fd: os.fd_t, + fd: posix.fd_t, mode: i32, offset: u64, len: u64, @@ -958,7 +958,7 @@ pub fn fallocate( pub fn statx( self: *IoUring, user_data: u64, - fd: os.fd_t, + fd: posix.fd_t, path: [:0]const u8, flags: u32, mask: u32, @@ -997,7 +997,7 @@ pub fn cancel( pub fn shutdown( self: *IoUring, user_data: u64, - sockfd: os.socket_t, + sockfd: posix.socket_t, how: u32, ) !*linux.io_uring_sqe { const sqe = try self.get_sqe(); @@ -1011,9 +1011,9 @@ pub fn shutdown( pub fn renameat( self: *IoUring, user_data: u64, - old_dir_fd: os.fd_t, + old_dir_fd: posix.fd_t, old_path: [*:0]const u8, - new_dir_fd: os.fd_t, + new_dir_fd: posix.fd_t, new_path: [*:0]const u8, flags: u32, ) !*linux.io_uring_sqe { @@ -1028,7 +1028,7 @@ pub fn renameat( pub fn unlinkat( self: *IoUring, user_data: u64, - dir_fd: os.fd_t, + dir_fd: posix.fd_t, path: [*:0]const u8, flags: u32, ) !*linux.io_uring_sqe { @@ -1043,9 +1043,9 @@ pub fn unlinkat( pub fn mkdirat( self: *IoUring, user_data: u64, - dir_fd: os.fd_t, + dir_fd: posix.fd_t, path: [*:0]const u8, - mode: os.mode_t, + mode: posix.mode_t, ) !*linux.io_uring_sqe { const sqe = try self.get_sqe(); sqe.prep_mkdirat(dir_fd, path, mode); @@ -1059,7 +1059,7 @@ pub fn symlinkat( self: *IoUring, user_data: u64, target: [*:0]const u8, - new_dir_fd: os.fd_t, + new_dir_fd: posix.fd_t, link_path: [*:0]const u8, ) !*linux.io_uring_sqe { const sqe = try self.get_sqe(); @@ -1073,9 +1073,9 @@ pub fn symlinkat( pub fn linkat( self: *IoUring, user_data: u64, - old_dir_fd: os.fd_t, + old_dir_fd: posix.fd_t, old_path: [*:0]const u8, - new_dir_fd: os.fd_t, + new_dir_fd: posix.fd_t, new_path: [*:0]const u8, flags: u32, ) !*linux.io_uring_sqe { @@ -1147,7 +1147,7 @@ pub fn waitid( /// Registering file descriptors will wait for the ring to idle. /// Files are automatically unregistered by the kernel when the ring is torn down. /// An application need unregister only if it wants to register a new array of file descriptors. -pub fn register_files(self: *IoUring, fds: []const os.fd_t) !void { +pub fn register_files(self: *IoUring, fds: []const posix.fd_t) !void { assert(self.fd >= 0); const res = linux.io_uring_register( self.fd, @@ -1166,7 +1166,7 @@ pub fn register_files(self: *IoUring, fds: []const os.fd_t) !void { /// * removing an existing entry (set the fd to -1) /// * replacing an existing entry with a new fd /// Adding new file descriptors must be done with `register_files`. -pub fn register_files_update(self: *IoUring, offset: u32, fds: []const os.fd_t) !void { +pub fn register_files_update(self: *IoUring, offset: u32, fds: []const posix.fd_t) !void { assert(self.fd >= 0); const FilesUpdate = extern struct { @@ -1192,7 +1192,7 @@ pub fn register_files_update(self: *IoUring, offset: u32, fds: []const os.fd_t) /// Registers the file descriptor for an eventfd that will be notified of completion events on /// an io_uring instance. /// Only a single a eventfd can be registered at any given point in time. -pub fn register_eventfd(self: *IoUring, fd: os.fd_t) !void { +pub fn register_eventfd(self: *IoUring, fd: posix.fd_t) !void { assert(self.fd >= 0); const res = linux.io_uring_register( self.fd, @@ -1207,7 +1207,7 @@ pub fn register_eventfd(self: *IoUring, fd: os.fd_t) !void { /// an io_uring instance. Notifications are only posted for events that complete in an async manner. /// This means that events that complete inline while being submitted do not trigger a notification event. /// Only a single eventfd can be registered at any given point in time. -pub fn register_eventfd_async(self: *IoUring, fd: os.fd_t) !void { +pub fn register_eventfd_async(self: *IoUring, fd: posix.fd_t) !void { assert(self.fd >= 0); const res = linux.io_uring_register( self.fd, @@ -1231,7 +1231,7 @@ pub fn unregister_eventfd(self: *IoUring) !void { } /// Registers an array of buffers for use with `read_fixed` and `write_fixed`. -pub fn register_buffers(self: *IoUring, buffers: []const os.iovec) !void { +pub fn register_buffers(self: *IoUring, buffers: []const posix.iovec) !void { assert(self.fd >= 0); const res = linux.io_uring_register( self.fd, @@ -1246,15 +1246,15 @@ pub fn register_buffers(self: *IoUring, buffers: []const os.iovec) !void { pub fn unregister_buffers(self: *IoUring) !void { assert(self.fd >= 0); const res = linux.io_uring_register(self.fd, .UNREGISTER_BUFFERS, null, 0); - switch (linux.getErrno(res)) { + switch (linux.E.init(res)) { .SUCCESS => {}, .NXIO => return error.BuffersNotRegistered, - else => |errno| return os.unexpectedErrno(errno), + else => |errno| return posix.unexpectedErrno(errno), } } fn handle_registration_result(res: usize) !void { - switch (linux.getErrno(res)) { + switch (linux.E.init(res)) { .SUCCESS => {}, // One or more fds in the array are invalid, or the kernel does not support sparse sets: .BADF => return error.FileDescriptorInvalid, @@ -1271,7 +1271,7 @@ fn handle_registration_result(res: usize) !void { .NOMEM => return error.SystemResources, // Attempt to register files on a ring already registering files or being torn down: .NXIO => return error.RingShuttingDownOrAlreadyRegisteringFiles, - else => |errno| return os.unexpectedErrno(errno), + else => |errno| return posix.unexpectedErrno(errno), } } @@ -1279,10 +1279,10 @@ fn handle_registration_result(res: usize) !void { pub fn unregister_files(self: *IoUring) !void { assert(self.fd >= 0); const res = linux.io_uring_register(self.fd, .UNREGISTER_FILES, null, 0); - switch (linux.getErrno(res)) { + switch (linux.E.init(res)) { .SUCCESS => {}, .NXIO => return error.FilesNotRegistered, - else => |errno| return os.unexpectedErrno(errno), + else => |errno| return posix.unexpectedErrno(errno), } } @@ -1355,36 +1355,36 @@ pub const SubmissionQueue = struct { sqe_head: u32 = 0, sqe_tail: u32 = 0, - pub fn init(fd: os.fd_t, p: linux.io_uring_params) !SubmissionQueue { + pub fn init(fd: posix.fd_t, p: linux.io_uring_params) !SubmissionQueue { assert(fd >= 0); assert((p.features & linux.IORING_FEAT_SINGLE_MMAP) != 0); const size = @max( p.sq_off.array + p.sq_entries * @sizeOf(u32), p.cq_off.cqes + p.cq_entries * @sizeOf(linux.io_uring_cqe), ); - const mmap = try os.mmap( + const mmap = try posix.mmap( null, size, - os.PROT.READ | os.PROT.WRITE, + posix.PROT.READ | posix.PROT.WRITE, .{ .TYPE = .SHARED, .POPULATE = true }, fd, linux.IORING_OFF_SQ_RING, ); - errdefer os.munmap(mmap); + errdefer posix.munmap(mmap); assert(mmap.len == size); // The motivation for the `sqes` and `array` indirection is to make it possible for the // application to preallocate static linux.io_uring_sqe entries and then replay them when needed. const size_sqes = p.sq_entries * @sizeOf(linux.io_uring_sqe); - const mmap_sqes = try os.mmap( + const mmap_sqes = try posix.mmap( null, size_sqes, - os.PROT.READ | os.PROT.WRITE, + posix.PROT.READ | posix.PROT.WRITE, .{ .TYPE = .SHARED, .POPULATE = true }, fd, linux.IORING_OFF_SQES, ); - errdefer os.munmap(mmap_sqes); + errdefer posix.munmap(mmap_sqes); assert(mmap_sqes.len == size_sqes); const array: [*]u32 = @ptrCast(@alignCast(&mmap[p.sq_off.array])); @@ -1406,8 +1406,8 @@ pub const SubmissionQueue = struct { } pub fn deinit(self: *SubmissionQueue) void { - os.munmap(self.mmap_sqes); - os.munmap(self.mmap); + posix.munmap(self.mmap_sqes); + posix.munmap(self.mmap); } }; @@ -1418,7 +1418,7 @@ pub const CompletionQueue = struct { overflow: *u32, cqes: []linux.io_uring_cqe, - pub fn init(fd: os.fd_t, p: linux.io_uring_params, sq: SubmissionQueue) !CompletionQueue { + pub fn init(fd: posix.fd_t, p: linux.io_uring_params, sq: SubmissionQueue) !CompletionQueue { assert(fd >= 0); assert((p.features & linux.IORING_FEAT_SINGLE_MMAP) != 0); const mmap = sq.mmap; @@ -1506,7 +1506,7 @@ pub const BufferGroup = struct { } // Prepare recv operation which will select buffer from this group. - pub fn recv(self: *BufferGroup, user_data: u64, fd: os.fd_t, flags: u32) !*linux.io_uring_sqe { + pub fn recv(self: *BufferGroup, user_data: u64, fd: posix.fd_t, flags: u32) !*linux.io_uring_sqe { var sqe = try self.ring.get_sqe(); sqe.prep_rw(.RECV, fd, 0, 0, 0); sqe.rw_flags = flags; @@ -1517,7 +1517,7 @@ pub const BufferGroup = struct { } // Prepare multishot recv operation which will select buffer from this group. - pub fn recv_multishot(self: *BufferGroup, user_data: u64, fd: os.fd_t, flags: u32) !*linux.io_uring_sqe { + pub fn recv_multishot(self: *BufferGroup, user_data: u64, fd: posix.fd_t, flags: u32) !*linux.io_uring_sqe { var sqe = try self.recv(user_data, fd, flags); sqe.ioprio |= linux.IORING_RECV_MULTISHOT; return sqe; @@ -1559,20 +1559,20 @@ pub const BufferGroup = struct { /// `fd` is IO_Uring.fd for which the provided buffer ring is being registered. /// `entries` is the number of entries requested in the buffer ring, must be power of 2. /// `group_id` is the chosen buffer group ID, unique in IO_Uring. -pub fn setup_buf_ring(fd: os.fd_t, entries: u16, group_id: u16) !*align(mem.page_size) linux.io_uring_buf_ring { +pub fn setup_buf_ring(fd: posix.fd_t, entries: u16, group_id: u16) !*align(mem.page_size) linux.io_uring_buf_ring { if (entries == 0 or entries > 1 << 15) return error.EntriesNotInRange; if (!std.math.isPowerOfTwo(entries)) return error.EntriesNotPowerOfTwo; const mmap_size = entries * @sizeOf(linux.io_uring_buf); - const mmap = try os.mmap( + const mmap = try posix.mmap( null, mmap_size, - os.PROT.READ | os.PROT.WRITE, + posix.PROT.READ | posix.PROT.WRITE, .{ .TYPE = .PRIVATE, .ANONYMOUS = true }, -1, 0, ); - errdefer os.munmap(mmap); + errdefer posix.munmap(mmap); assert(mmap.len == mmap_size); const br: *align(mem.page_size) linux.io_uring_buf_ring = @ptrCast(mmap.ptr); @@ -1580,7 +1580,7 @@ pub fn setup_buf_ring(fd: os.fd_t, entries: u16, group_id: u16) !*align(mem.page return br; } -fn register_buf_ring(fd: os.fd_t, addr: u64, entries: u32, group_id: u16) !void { +fn register_buf_ring(fd: posix.fd_t, addr: u64, entries: u32, group_id: u16) !void { var reg = mem.zeroInit(linux.io_uring_buf_reg, .{ .ring_addr = addr, .ring_entries = entries, @@ -1595,7 +1595,7 @@ fn register_buf_ring(fd: os.fd_t, addr: u64, entries: u32, group_id: u16) !void try handle_register_buf_ring_result(res); } -fn unregister_buf_ring(fd: os.fd_t, group_id: u16) !void { +fn unregister_buf_ring(fd: posix.fd_t, group_id: u16) !void { var reg = mem.zeroInit(linux.io_uring_buf_reg, .{ .bgid = group_id, }); @@ -1609,20 +1609,20 @@ fn unregister_buf_ring(fd: os.fd_t, group_id: u16) !void { } fn handle_register_buf_ring_result(res: usize) !void { - switch (linux.getErrno(res)) { + switch (linux.E.init(res)) { .SUCCESS => {}, .INVAL => return error.ArgumentsInvalid, - else => |errno| return os.unexpectedErrno(errno), + else => |errno| return posix.unexpectedErrno(errno), } } // Unregisters a previously registered shared buffer ring, returned from io_uring_setup_buf_ring. -pub fn free_buf_ring(fd: os.fd_t, br: *align(mem.page_size) linux.io_uring_buf_ring, entries: u32, group_id: u16) void { +pub fn free_buf_ring(fd: posix.fd_t, br: *align(mem.page_size) linux.io_uring_buf_ring, entries: u32, group_id: u16) void { unregister_buf_ring(fd, group_id) catch {}; var mmap: []align(mem.page_size) u8 = undefined; mmap.ptr = @ptrCast(br); mmap.len = entries * @sizeOf(linux.io_uring_buf); - os.munmap(mmap); + posix.munmap(mmap); } /// Initialises `br` so that it is ready to be used. @@ -1664,7 +1664,7 @@ pub fn buf_ring_advance(br: *linux.io_uring_buf_ring, count: u16) void { } test "structs/offsets/entries" { - if (builtin.os.tag != .linux) return error.SkipZigTest; + if (!is_linux) return error.SkipZigTest; try testing.expectEqual(@as(usize, 120), @sizeOf(linux.io_uring_params)); try testing.expectEqual(@as(usize, 64), @sizeOf(linux.io_uring_sqe)); @@ -1679,7 +1679,7 @@ test "structs/offsets/entries" { } test "nop" { - if (builtin.os.tag != .linux) return error.SkipZigTest; + if (!is_linux) return error.SkipZigTest; var ring = IoUring.init(1, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, @@ -1688,7 +1688,7 @@ test "nop" { }; defer { ring.deinit(); - testing.expectEqual(@as(os.fd_t, -1), ring.fd) catch @panic("test failed"); + testing.expectEqual(@as(posix.fd_t, -1), ring.fd) catch @panic("test failed"); } const sqe = try ring.nop(0xaaaaaaaa); @@ -1746,7 +1746,7 @@ test "nop" { } test "readv" { - if (builtin.os.tag != .linux) return error.SkipZigTest; + if (!is_linux) return error.SkipZigTest; var ring = IoUring.init(1, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, @@ -1755,8 +1755,8 @@ test "readv" { }; defer ring.deinit(); - const fd = try os.openZ("/dev/zero", .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0); - defer os.close(fd); + const fd = try posix.openZ("/dev/zero", .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0); + defer posix.close(fd); // Linux Kernel 5.4 supports IORING_REGISTER_FILES but not sparse fd sets (i.e. an fd of -1). // Linux Kernel 5.5 adds support for sparse fd sets. @@ -1764,13 +1764,13 @@ test "readv" { // https://github.com/torvalds/linux/blob/v5.4/fs/io_uring.c#L3119-L3124 vs // https://github.com/torvalds/linux/blob/v5.8/fs/io_uring.c#L6687-L6691 // We therefore avoid stressing sparse fd sets here: - var registered_fds = [_]os.fd_t{0} ** 1; + var registered_fds = [_]posix.fd_t{0} ** 1; const fd_index = 0; registered_fds[fd_index] = fd; try ring.register_files(registered_fds[0..]); var buffer = [_]u8{42} ** 128; - var iovecs = [_]os.iovec{os.iovec{ .iov_base = &buffer, .iov_len = buffer.len }}; + var iovecs = [_]posix.iovec{posix.iovec{ .iov_base = &buffer, .iov_len = buffer.len }}; const sqe = try ring.read(0xcccccccc, fd_index, .{ .iovecs = iovecs[0..] }, 0); try testing.expectEqual(linux.IORING_OP.READV, sqe.opcode); sqe.flags |= linux.IOSQE_FIXED_FILE; @@ -1788,7 +1788,7 @@ test "readv" { } test "writev/fsync/readv" { - if (builtin.os.tag != .linux) return error.SkipZigTest; + if (!is_linux) return error.SkipZigTest; var ring = IoUring.init(4, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, @@ -1806,12 +1806,12 @@ test "writev/fsync/readv" { const fd = file.handle; const buffer_write = [_]u8{42} ** 128; - const iovecs_write = [_]os.iovec_const{ - os.iovec_const{ .iov_base = &buffer_write, .iov_len = buffer_write.len }, + const iovecs_write = [_]posix.iovec_const{ + posix.iovec_const{ .iov_base = &buffer_write, .iov_len = buffer_write.len }, }; var buffer_read = [_]u8{0} ** 128; - var iovecs_read = [_]os.iovec{ - os.iovec{ .iov_base = &buffer_read, .iov_len = buffer_read.len }, + var iovecs_read = [_]posix.iovec{ + posix.iovec{ .iov_base = &buffer_read, .iov_len = buffer_read.len }, }; const sqe_writev = try ring.writev(0xdddddddd, fd, iovecs_write[0..], 17); @@ -1858,7 +1858,7 @@ test "writev/fsync/readv" { } test "write/read" { - if (builtin.os.tag != .linux) return error.SkipZigTest; + if (!is_linux) return error.SkipZigTest; var ring = IoUring.init(2, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, @@ -1905,7 +1905,7 @@ test "write/read" { } test "splice/read" { - if (builtin.os.tag != .linux) return error.SkipZigTest; + if (!is_linux) return error.SkipZigTest; var ring = IoUring.init(4, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, @@ -1929,7 +1929,7 @@ test "splice/read" { var buffer_read = [_]u8{98} ** 20; _ = try file_src.write(&buffer_write); - const fds = try os.pipe(); + const fds = try posix.pipe(); const pipe_offset: u64 = std.math.maxInt(u64); const sqe_splice_to_pipe = try ring.splice(0x11111111, fd_src, 0, fds[1], pipe_offset, buffer_write.len); @@ -1976,7 +1976,7 @@ test "splice/read" { } test "write_fixed/read_fixed" { - if (builtin.os.tag != .linux) return error.SkipZigTest; + if (!is_linux) return error.SkipZigTest; var ring = IoUring.init(2, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, @@ -1998,7 +1998,7 @@ test "write_fixed/read_fixed" { @memset(&raw_buffers[0], 'z'); raw_buffers[0][0.."foobar".len].* = "foobar".*; - var buffers = [2]os.iovec{ + var buffers = [2]posix.iovec{ .{ .iov_base = &raw_buffers[0], .iov_len = raw_buffers[0].len }, .{ .iov_base = &raw_buffers[1], .iov_len = raw_buffers[1].len }, }; @@ -2041,7 +2041,7 @@ test "write_fixed/read_fixed" { } test "openat" { - if (builtin.os.tag != .linux) return error.SkipZigTest; + if (!is_linux) return error.SkipZigTest; var ring = IoUring.init(1, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, @@ -2063,7 +2063,7 @@ test "openat" { } else @intFromPtr(path); const flags: linux.O = .{ .CLOEXEC = true, .ACCMODE = .RDWR, .CREAT = true }; - const mode: os.mode_t = 0o666; + const mode: posix.mode_t = 0o666; const sqe_openat = try ring.openat(0x33333333, tmp.dir.fd, path, flags, mode); try testing.expectEqual(linux.io_uring_sqe{ .opcode = .OPENAT, @@ -2091,11 +2091,11 @@ test "openat" { try testing.expect(cqe_openat.res > 0); try testing.expectEqual(@as(u32, 0), cqe_openat.flags); - os.close(cqe_openat.res); + posix.close(cqe_openat.res); } test "close" { - if (builtin.os.tag != .linux) return error.SkipZigTest; + if (!is_linux) return error.SkipZigTest; var ring = IoUring.init(1, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, @@ -2126,7 +2126,7 @@ test "close" { } test "accept/connect/send/recv" { - if (builtin.os.tag != .linux) return error.SkipZigTest; + if (!is_linux) return error.SkipZigTest; var ring = IoUring.init(16, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, @@ -2167,7 +2167,7 @@ test "accept/connect/send/recv" { } test "sendmsg/recvmsg" { - if (builtin.os.tag != .linux) return error.SkipZigTest; + if (!is_linux) return error.SkipZigTest; var ring = IoUring.init(2, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, @@ -2178,24 +2178,24 @@ test "sendmsg/recvmsg" { var address_server = try net.Address.parseIp4("127.0.0.1", 0); - const server = try os.socket(address_server.any.family, os.SOCK.DGRAM, 0); - defer os.close(server); - try os.setsockopt(server, os.SOL.SOCKET, os.SO.REUSEPORT, &mem.toBytes(@as(c_int, 1))); - try os.setsockopt(server, os.SOL.SOCKET, os.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1))); - try os.bind(server, &address_server.any, address_server.getOsSockLen()); + const server = try posix.socket(address_server.any.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))); + try posix.bind(server, &address_server.any, address_server.getOsSockLen()); // set address_server to the OS-chosen IP/port. - var slen: os.socklen_t = address_server.getOsSockLen(); - try os.getsockname(server, &address_server.any, &slen); + var slen: posix.socklen_t = address_server.getOsSockLen(); + try posix.getsockname(server, &address_server.any, &slen); - const client = try os.socket(address_server.any.family, os.SOCK.DGRAM, 0); - defer os.close(client); + const client = try posix.socket(address_server.any.family, posix.SOCK.DGRAM, 0); + defer posix.close(client); const buffer_send = [_]u8{42} ** 128; - const iovecs_send = [_]os.iovec_const{ - os.iovec_const{ .iov_base = &buffer_send, .iov_len = buffer_send.len }, + const iovecs_send = [_]posix.iovec_const{ + posix.iovec_const{ .iov_base = &buffer_send, .iov_len = buffer_send.len }, }; - const msg_send = os.msghdr_const{ + const msg_send: posix.msghdr_const = .{ .name = &address_server.any, .namelen = address_server.getOsSockLen(), .iov = &iovecs_send, @@ -2210,12 +2210,12 @@ test "sendmsg/recvmsg" { try testing.expectEqual(client, sqe_sendmsg.fd); var buffer_recv = [_]u8{0} ** 128; - var iovecs_recv = [_]os.iovec{ - os.iovec{ .iov_base = &buffer_recv, .iov_len = buffer_recv.len }, + var iovecs_recv = [_]posix.iovec{ + posix.iovec{ .iov_base = &buffer_recv, .iov_len = buffer_recv.len }, }; const addr = [_]u8{0} ** 4; var address_recv = net.Address.initIp4(addr, 0); - var msg_recv: os.msghdr = os.msghdr{ + var msg_recv: posix.msghdr = .{ .name = &address_recv.any, .namelen = address_recv.getOsSockLen(), .iov = &iovecs_recv, @@ -2254,7 +2254,7 @@ test "sendmsg/recvmsg" { } test "timeout (after a relative time)" { - if (builtin.os.tag != .linux) return error.SkipZigTest; + if (!is_linux) return error.SkipZigTest; var ring = IoUring.init(1, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, @@ -2265,7 +2265,7 @@ test "timeout (after a relative time)" { const ms = 10; const margin = 5; - const ts = os.linux.kernel_timespec{ .tv_sec = 0, .tv_nsec = ms * 1000000 }; + const ts: linux.kernel_timespec = .{ .tv_sec = 0, .tv_nsec = ms * 1000000 }; const started = std.time.milliTimestamp(); const sqe = try ring.timeout(0x55555555, &ts, 0, 0); @@ -2285,7 +2285,7 @@ test "timeout (after a relative time)" { } test "timeout (after a number of completions)" { - if (builtin.os.tag != .linux) return error.SkipZigTest; + if (!is_linux) return error.SkipZigTest; var ring = IoUring.init(2, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, @@ -2294,7 +2294,7 @@ test "timeout (after a number of completions)" { }; defer ring.deinit(); - const ts = os.linux.kernel_timespec{ .tv_sec = 3, .tv_nsec = 0 }; + const ts: linux.kernel_timespec = .{ .tv_sec = 3, .tv_nsec = 0 }; const count_completions: u64 = 1; const sqe_timeout = try ring.timeout(0x66666666, &ts, count_completions, 0); try testing.expectEqual(linux.IORING_OP.TIMEOUT, sqe_timeout.opcode); @@ -2318,7 +2318,7 @@ test "timeout (after a number of completions)" { } test "timeout_remove" { - if (builtin.os.tag != .linux) return error.SkipZigTest; + if (!is_linux) return error.SkipZigTest; var ring = IoUring.init(2, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, @@ -2327,7 +2327,7 @@ test "timeout_remove" { }; defer ring.deinit(); - const ts = os.linux.kernel_timespec{ .tv_sec = 3, .tv_nsec = 0 }; + const ts: linux.kernel_timespec = .{ .tv_sec = 3, .tv_nsec = 0 }; const sqe_timeout = try ring.timeout(0x88888888, &ts, 0, 0); try testing.expectEqual(linux.IORING_OP.TIMEOUT, sqe_timeout.opcode); try testing.expectEqual(@as(u64, 0x88888888), sqe_timeout.user_data); @@ -2343,7 +2343,7 @@ test "timeout_remove" { // * kernel 5.10 gives user data 0x88888888 first, 0x99999999 second // * kernel 5.18 gives user data 0x99999999 first, 0x88888888 second - var cqes: [2]os.linux.io_uring_cqe = undefined; + var cqes: [2]linux.io_uring_cqe = undefined; cqes[0] = try ring.copy_cqe(); cqes[1] = try ring.copy_cqe(); @@ -2378,7 +2378,7 @@ test "timeout_remove" { } test "accept/connect/recv/link_timeout" { - if (builtin.os.tag != .linux) return error.SkipZigTest; + if (!is_linux) return error.SkipZigTest; var ring = IoUring.init(16, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, @@ -2395,7 +2395,7 @@ test "accept/connect/recv/link_timeout" { const sqe_recv = try ring.recv(0xffffffff, socket_test_harness.server, .{ .buffer = buffer_recv[0..] }, 0); sqe_recv.flags |= linux.IOSQE_IO_LINK; - const ts = os.linux.kernel_timespec{ .tv_sec = 0, .tv_nsec = 1000000 }; + const ts = linux.kernel_timespec{ .tv_sec = 0, .tv_nsec = 1000000 }; _ = try ring.link_timeout(0x22222222, &ts, 0); const nr_wait = try ring.submit(); @@ -2427,7 +2427,7 @@ test "accept/connect/recv/link_timeout" { } test "fallocate" { - if (builtin.os.tag != .linux) return error.SkipZigTest; + if (!is_linux) return error.SkipZigTest; var ring = IoUring.init(1, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, @@ -2473,7 +2473,7 @@ test "fallocate" { } test "statx" { - if (builtin.os.tag != .linux) return error.SkipZigTest; + if (!is_linux) return error.SkipZigTest; var ring = IoUring.init(1, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, @@ -2525,12 +2525,12 @@ test "statx" { .flags = 0, }, cqe); - try testing.expect(buf.mask & os.linux.STATX_SIZE == os.linux.STATX_SIZE); + try testing.expect(buf.mask & linux.STATX_SIZE == linux.STATX_SIZE); try testing.expectEqual(@as(u64, 6), buf.size); } test "accept/connect/recv/cancel" { - if (builtin.os.tag != .linux) return error.SkipZigTest; + if (!is_linux) return error.SkipZigTest; var ring = IoUring.init(16, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, @@ -2580,7 +2580,7 @@ test "accept/connect/recv/cancel" { } test "register_files_update" { - if (builtin.os.tag != .linux) return error.SkipZigTest; + if (!is_linux) return error.SkipZigTest; var ring = IoUring.init(1, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, @@ -2589,10 +2589,10 @@ test "register_files_update" { }; defer ring.deinit(); - const fd = try os.openZ("/dev/zero", .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0); - defer os.close(fd); + const fd = try posix.openZ("/dev/zero", .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0); + defer posix.close(fd); - var registered_fds = [_]os.fd_t{0} ** 2; + var registered_fds = [_]posix.fd_t{0} ** 2; const fd_index = 0; const fd_index2 = 1; registered_fds[fd_index] = fd; @@ -2607,8 +2607,8 @@ test "register_files_update" { // Test IORING_REGISTER_FILES_UPDATE // Only available since Linux 5.5 - const fd2 = try os.openZ("/dev/zero", .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0); - defer os.close(fd2); + const fd2 = try posix.openZ("/dev/zero", .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0); + defer posix.close(fd2); registered_fds[fd_index] = fd2; registered_fds[fd_index2] = -1; @@ -2660,14 +2660,14 @@ test "register_files_update" { try testing.expectEqual(@as(u32, 1), try ring.submit()); const cqe = try ring.copy_cqe(); - try testing.expectEqual(os.linux.E.BADF, cqe.err()); + try testing.expectEqual(linux.E.BADF, cqe.err()); } try ring.unregister_files(); } test "shutdown" { - if (builtin.os.tag != .linux) return error.SkipZigTest; + if (!is_linux) return error.SkipZigTest; var ring = IoUring.init(16, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, @@ -2680,17 +2680,17 @@ test "shutdown" { // Socket bound, expect shutdown to work { - const server = try os.socket(address.any.family, os.SOCK.STREAM | os.SOCK.CLOEXEC, 0); - defer os.close(server); - try os.setsockopt(server, os.SOL.SOCKET, os.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1))); - try os.bind(server, &address.any, address.getOsSockLen()); - try os.listen(server, 1); + const server = try posix.socket(address.any.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, &address.any, address.getOsSockLen()); + try posix.listen(server, 1); // set address to the OS-chosen IP/port. - var slen: os.socklen_t = address.getOsSockLen(); - try os.getsockname(server, &address.any, &slen); + var slen: posix.socklen_t = address.getOsSockLen(); + try posix.getsockname(server, &address.any, &slen); - const shutdown_sqe = try ring.shutdown(0x445445445, server, os.linux.SHUT.RD); + const shutdown_sqe = try ring.shutdown(0x445445445, server, linux.SHUT.RD); try testing.expectEqual(linux.IORING_OP.SHUTDOWN, shutdown_sqe.opcode); try testing.expectEqual(@as(i32, server), shutdown_sqe.fd); @@ -2713,10 +2713,10 @@ test "shutdown" { // Socket not bound, expect to fail with ENOTCONN { - const server = try os.socket(address.any.family, os.SOCK.STREAM | os.SOCK.CLOEXEC, 0); - defer os.close(server); + const server = try posix.socket(address.any.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); + defer posix.close(server); - const shutdown_sqe = ring.shutdown(0x445445445, server, os.linux.SHUT.RD) catch |err| switch (err) { + const shutdown_sqe = ring.shutdown(0x445445445, server, linux.SHUT.RD) catch |err| switch (err) { else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), }; try testing.expectEqual(linux.IORING_OP.SHUTDOWN, shutdown_sqe.opcode); @@ -2726,12 +2726,12 @@ test "shutdown" { const cqe = try ring.copy_cqe(); try testing.expectEqual(@as(u64, 0x445445445), cqe.user_data); - try testing.expectEqual(os.linux.E.NOTCONN, cqe.err()); + try testing.expectEqual(linux.E.NOTCONN, cqe.err()); } } test "renameat" { - if (builtin.os.tag != .linux) return error.SkipZigTest; + if (!is_linux) return error.SkipZigTest; var ring = IoUring.init(1, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, @@ -2800,7 +2800,7 @@ test "renameat" { } test "unlinkat" { - if (builtin.os.tag != .linux) return error.SkipZigTest; + if (!is_linux) return error.SkipZigTest; var ring = IoUring.init(1, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, @@ -2852,7 +2852,7 @@ test "unlinkat" { } test "mkdirat" { - if (builtin.os.tag != .linux) return error.SkipZigTest; + if (!is_linux) return error.SkipZigTest; var ring = IoUring.init(1, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, @@ -2896,7 +2896,7 @@ test "mkdirat" { } test "symlinkat" { - if (builtin.os.tag != .linux) return error.SkipZigTest; + if (!is_linux) return error.SkipZigTest; var ring = IoUring.init(1, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, @@ -2944,7 +2944,7 @@ test "symlinkat" { } test "linkat" { - if (builtin.os.tag != .linux) return error.SkipZigTest; + if (!is_linux) return error.SkipZigTest; var ring = IoUring.init(1, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, @@ -3003,7 +3003,7 @@ test "linkat" { } test "provide_buffers: read" { - if (builtin.os.tag != .linux) return error.SkipZigTest; + if (!is_linux) return error.SkipZigTest; var ring = IoUring.init(1, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, @@ -3012,8 +3012,8 @@ test "provide_buffers: read" { }; defer ring.deinit(); - const fd = try os.openZ("/dev/zero", .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0); - defer os.close(fd); + const fd = try posix.openZ("/dev/zero", .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0); + defer posix.close(fd); const group_id = 1337; const buffer_id = 0; @@ -3135,7 +3135,7 @@ test "provide_buffers: read" { } test "remove_buffers" { - if (builtin.os.tag != .linux) return error.SkipZigTest; + if (!is_linux) return error.SkipZigTest; var ring = IoUring.init(1, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, @@ -3144,8 +3144,8 @@ test "remove_buffers" { }; defer ring.deinit(); - const fd = try os.openZ("/dev/zero", .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0); - defer os.close(fd); + const fd = try posix.openZ("/dev/zero", .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0); + defer posix.close(fd); const group_id = 1337; const buffer_id = 0; @@ -3224,7 +3224,7 @@ test "remove_buffers" { } test "provide_buffers: accept/connect/send/recv" { - if (builtin.os.tag != .linux) return error.SkipZigTest; + if (!is_linux) return error.SkipZigTest; var ring = IoUring.init(16, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, @@ -3391,9 +3391,9 @@ test "provide_buffers: accept/connect/send/recv" { /// Used for testing server/client interactions. const SocketTestHarness = struct { - listener: os.socket_t, - server: os.socket_t, - client: os.socket_t, + listener: posix.socket_t, + server: posix.socket_t, + client: posix.socket_t, fn close(self: SocketTestHarness) void { posix.close(self.client); @@ -3408,12 +3408,12 @@ fn createSocketTestHarness(ring: *IoUring) !SocketTestHarness { errdefer posix.close(listener_socket); // Submit 1 accept - var accept_addr: os.sockaddr = undefined; - var accept_addr_len: os.socklen_t = @sizeOf(@TypeOf(accept_addr)); + var accept_addr: posix.sockaddr = undefined; + var accept_addr_len: posix.socklen_t = @sizeOf(@TypeOf(accept_addr)); _ = try ring.accept(0xaaaaaaaa, listener_socket, &accept_addr, &accept_addr_len, 0); // Create a TCP client socket - const client = try os.socket(address.any.family, os.SOCK.STREAM | os.SOCK.CLOEXEC, 0); + const client = try posix.socket(address.any.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); errdefer posix.close(client); _ = try ring.connect(0xcccccccc, client, &address.any, address.getOsSockLen()); @@ -3451,24 +3451,24 @@ fn createSocketTestHarness(ring: *IoUring) !SocketTestHarness { }; } -fn createListenerSocket(address: *net.Address) !os.socket_t { +fn createListenerSocket(address: *net.Address) !posix.socket_t { const kernel_backlog = 1; - const listener_socket = try os.socket(address.any.family, os.SOCK.STREAM | os.SOCK.CLOEXEC, 0); + const listener_socket = try posix.socket(address.any.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); errdefer posix.close(listener_socket); - try os.setsockopt(listener_socket, os.SOL.SOCKET, os.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1))); - try os.bind(listener_socket, &address.any, address.getOsSockLen()); - try os.listen(listener_socket, kernel_backlog); + try posix.setsockopt(listener_socket, posix.SOL.SOCKET, posix.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1))); + try posix.bind(listener_socket, &address.any, address.getOsSockLen()); + try posix.listen(listener_socket, kernel_backlog); // set address to the OS-chosen IP/port. - var slen: os.socklen_t = address.getOsSockLen(); - try os.getsockname(listener_socket, &address.any, &slen); + var slen: posix.socklen_t = address.getOsSockLen(); + try posix.getsockname(listener_socket, &address.any, &slen); return listener_socket; } test "accept multishot" { - if (builtin.os.tag != .linux) return error.SkipZigTest; + if (!is_linux) return error.SkipZigTest; var ring = IoUring.init(16, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, @@ -3482,8 +3482,8 @@ test "accept multishot" { defer posix.close(listener_socket); // submit multishot accept operation - var addr: os.sockaddr = undefined; - var addr_len: os.socklen_t = @sizeOf(@TypeOf(addr)); + var addr: posix.sockaddr = undefined; + var addr_len: posix.socklen_t = @sizeOf(@TypeOf(addr)); const userdata: u64 = 0xaaaaaaaa; _ = try ring.accept_multishot(userdata, listener_socket, &addr, &addr_len, 0); try testing.expectEqual(@as(u32, 1), try ring.submit()); @@ -3491,9 +3491,9 @@ test "accept multishot" { var nr: usize = 4; // number of clients to connect while (nr > 0) : (nr -= 1) { // connect client - const client = try os.socket(address.any.family, os.SOCK.STREAM | os.SOCK.CLOEXEC, 0); + const client = try posix.socket(address.any.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); errdefer posix.close(client); - try os.connect(client, &address.any, address.getOsSockLen()); + try posix.connect(client, &address.any, address.getOsSockLen()); // test accept completion var cqe = try ring.copy_cqe(); @@ -3571,7 +3571,7 @@ test "accept_direct" { var address = try net.Address.parseIp4("127.0.0.1", 0); // register direct file descriptors - var registered_fds = [_]os.fd_t{-1} ** 2; + var registered_fds = [_]posix.fd_t{-1} ** 2; try ring.register_files(registered_fds[0..]); const listener_socket = try createListenerSocket(&address); @@ -3591,19 +3591,19 @@ test "accept_direct" { try testing.expectEqual(@as(u32, 1), try ring.submit()); // connect - const client = try os.socket(address.any.family, os.SOCK.STREAM | os.SOCK.CLOEXEC, 0); - try os.connect(client, &address.any, address.getOsSockLen()); + const client = try posix.socket(address.any.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); + try posix.connect(client, &address.any, address.getOsSockLen()); defer posix.close(client); // accept completion const cqe_accept = try ring.copy_cqe(); - try testing.expectEqual(os.E.SUCCESS, cqe_accept.err()); + try testing.expectEqual(posix.E.SUCCESS, cqe_accept.err()); const fd_index = cqe_accept.res; try testing.expect(fd_index < registered_fds.len); try testing.expect(cqe_accept.user_data == accept_userdata); // send data - _ = try os.send(client, buffer_send, 0); + _ = try posix.send(client, buffer_send, 0); // Example of how to use registered fd: // Submit receive to fixed file returned by accept (fd_index). @@ -3625,13 +3625,13 @@ 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 os.socket(address.any.family, os.SOCK.STREAM | os.SOCK.CLOEXEC, 0); - try os.connect(client, &address.any, address.getOsSockLen()); + const client = try posix.socket(address.any.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); + try posix.connect(client, &address.any, address.getOsSockLen()); defer posix.close(client); // completion with error const cqe_accept = try ring.copy_cqe(); try testing.expect(cqe_accept.user_data == accept_userdata); - try testing.expectEqual(os.E.NFILE, cqe_accept.err()); + try testing.expectEqual(posix.E.NFILE, cqe_accept.err()); } // return file descriptors to kernel try ring.register_files_update(0, registered_fds[0..]); @@ -3651,7 +3651,7 @@ test "accept_multishot_direct" { var address = try net.Address.parseIp4("127.0.0.1", 0); - var registered_fds = [_]os.fd_t{-1} ** 2; + var registered_fds = [_]posix.fd_t{-1} ** 2; try ring.register_files(registered_fds[0..]); const listener_socket = try createListenerSocket(&address); @@ -3667,8 +3667,8 @@ test "accept_multishot_direct" { for (registered_fds) |_| { // connect - const client = try os.socket(address.any.family, os.SOCK.STREAM | os.SOCK.CLOEXEC, 0); - try os.connect(client, &address.any, address.getOsSockLen()); + const client = try posix.socket(address.any.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); + try posix.connect(client, &address.any, address.getOsSockLen()); defer posix.close(client); // accept completion @@ -3682,13 +3682,13 @@ test "accept_multishot_direct" { // Multishot is terminated (more flag is not set). { // connect - const client = try os.socket(address.any.family, os.SOCK.STREAM | os.SOCK.CLOEXEC, 0); - try os.connect(client, &address.any, address.getOsSockLen()); + const client = try posix.socket(address.any.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); + try posix.connect(client, &address.any, address.getOsSockLen()); defer posix.close(client); // completion with error const cqe_accept = try ring.copy_cqe(); try testing.expect(cqe_accept.user_data == accept_userdata); - try testing.expectEqual(os.E.NFILE, cqe_accept.err()); + try testing.expectEqual(posix.E.NFILE, cqe_accept.err()); try testing.expect(cqe_accept.flags & linux.IORING_CQE_F_MORE == 0); // has more is not set } // return file descriptors to kernel @@ -3708,16 +3708,16 @@ test "socket" { defer ring.deinit(); // prepare, submit socket operation - _ = try ring.socket(0, linux.AF.INET, os.SOCK.STREAM, 0, 0); + _ = try ring.socket(0, linux.AF.INET, posix.SOCK.STREAM, 0, 0); try testing.expectEqual(@as(u32, 1), try ring.submit()); // test completion var cqe = try ring.copy_cqe(); - try testing.expectEqual(os.E.SUCCESS, cqe.err()); - const fd: os.fd_t = @intCast(cqe.res); + try testing.expectEqual(posix.E.SUCCESS, cqe.err()); + const fd: posix.fd_t = @intCast(cqe.res); try testing.expect(fd > 2); - os.close(fd); + posix.close(fd); } test "socket_direct/socket_direct_alloc/close_direct" { @@ -3730,29 +3730,29 @@ test "socket_direct/socket_direct_alloc/close_direct" { }; defer ring.deinit(); - var registered_fds = [_]os.fd_t{-1} ** 3; + var registered_fds = [_]posix.fd_t{-1} ** 3; try ring.register_files(registered_fds[0..]); // create socket in registered file descriptor at index 0 (last param) - _ = try ring.socket_direct(0, linux.AF.INET, os.SOCK.STREAM, 0, 0, 0); + _ = try ring.socket_direct(0, linux.AF.INET, posix.SOCK.STREAM, 0, 0, 0); try testing.expectEqual(@as(u32, 1), try ring.submit()); var cqe_socket = try ring.copy_cqe(); - try testing.expectEqual(os.E.SUCCESS, cqe_socket.err()); + try testing.expectEqual(posix.E.SUCCESS, cqe_socket.err()); try testing.expect(cqe_socket.res == 0); // create socket in registered file descriptor at index 1 (last param) - _ = try ring.socket_direct(0, linux.AF.INET, os.SOCK.STREAM, 0, 0, 1); + _ = try ring.socket_direct(0, linux.AF.INET, posix.SOCK.STREAM, 0, 0, 1); try testing.expectEqual(@as(u32, 1), try ring.submit()); cqe_socket = try ring.copy_cqe(); - try testing.expectEqual(os.E.SUCCESS, cqe_socket.err()); + try testing.expectEqual(posix.E.SUCCESS, cqe_socket.err()); try testing.expect(cqe_socket.res == 0); // res is 0 when index is specified // create socket in kernel chosen file descriptor index (_alloc version) // completion res has index from registered files - _ = try ring.socket_direct_alloc(0, linux.AF.INET, os.SOCK.STREAM, 0, 0); + _ = try ring.socket_direct_alloc(0, linux.AF.INET, posix.SOCK.STREAM, 0, 0); try testing.expectEqual(@as(u32, 1), try ring.submit()); cqe_socket = try ring.copy_cqe(); - try testing.expectEqual(os.E.SUCCESS, cqe_socket.err()); + try testing.expectEqual(posix.E.SUCCESS, cqe_socket.err()); try testing.expect(cqe_socket.res == 2); // returns registered file index // use sockets from registered_fds in connect operation @@ -3782,17 +3782,17 @@ test "socket_direct/socket_direct_alloc/close_direct" { } // test connect completion try testing.expect(cqe_connect.user_data == connect_userdata); - try testing.expectEqual(os.E.SUCCESS, cqe_connect.err()); + try testing.expectEqual(posix.E.SUCCESS, cqe_connect.err()); // test accept completion try testing.expect(cqe_accept.user_data == accept_userdata); - try testing.expectEqual(os.E.SUCCESS, cqe_accept.err()); + try testing.expectEqual(posix.E.SUCCESS, cqe_accept.err()); // submit and test close_direct _ = try ring.close_direct(close_userdata, @intCast(fd_index)); try testing.expectEqual(@as(u32, 1), try ring.submit()); var cqe_close = try ring.copy_cqe(); try testing.expect(cqe_close.user_data == close_userdata); - try testing.expectEqual(os.E.SUCCESS, cqe_close.err()); + try testing.expectEqual(posix.E.SUCCESS, cqe_close.err()); } try ring.unregister_files(); @@ -3808,35 +3808,35 @@ test "openat_direct/close_direct" { }; defer ring.deinit(); - var registered_fds = [_]os.fd_t{-1} ** 3; + var registered_fds = [_]posix.fd_t{-1} ** 3; try ring.register_files(registered_fds[0..]); var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); const path = "test_io_uring_close_direct"; const flags: linux.O = .{ .ACCMODE = .RDWR, .CREAT = true }; - const mode: os.mode_t = 0o666; + const mode: posix.mode_t = 0o666; const user_data: u64 = 0; // use registered file at index 0 (last param) _ = try ring.openat_direct(user_data, tmp.dir.fd, path, flags, mode, 0); try testing.expectEqual(@as(u32, 1), try ring.submit()); var cqe = try ring.copy_cqe(); - try testing.expectEqual(os.E.SUCCESS, cqe.err()); + try testing.expectEqual(posix.E.SUCCESS, cqe.err()); try testing.expect(cqe.res == 0); // use registered file at index 1 _ = try ring.openat_direct(user_data, tmp.dir.fd, path, flags, mode, 1); try testing.expectEqual(@as(u32, 1), try ring.submit()); cqe = try ring.copy_cqe(); - try testing.expectEqual(os.E.SUCCESS, cqe.err()); + try testing.expectEqual(posix.E.SUCCESS, cqe.err()); try testing.expect(cqe.res == 0); // res is 0 when we specify index // let kernel choose registered file index _ = try ring.openat_direct(user_data, tmp.dir.fd, path, flags, mode, linux.IORING_FILE_INDEX_ALLOC); try testing.expectEqual(@as(u32, 1), try ring.submit()); cqe = try ring.copy_cqe(); - try testing.expectEqual(os.E.SUCCESS, cqe.err()); + try testing.expectEqual(posix.E.SUCCESS, cqe.err()); try testing.expect(cqe.res == 2); // chosen index is in res // close all open file descriptors @@ -3844,7 +3844,7 @@ test "openat_direct/close_direct" { _ = try ring.close_direct(user_data, @intCast(fd_index)); try testing.expectEqual(@as(u32, 1), try ring.submit()); var cqe_close = try ring.copy_cqe(); - try testing.expectEqual(os.E.SUCCESS, cqe_close.err()); + try testing.expectEqual(posix.E.SUCCESS, cqe_close.err()); } try ring.unregister_files(); } @@ -3859,13 +3859,13 @@ test "waitid" { }; defer ring.deinit(); - const pid = try os.fork(); + const pid = try posix.fork(); if (pid == 0) { - os.exit(7); + posix.exit(7); } - var siginfo: os.siginfo_t = undefined; - _ = try ring.waitid(0, .PID, pid, &siginfo, os.W.EXITED, 0); + var siginfo: posix.siginfo_t = undefined; + _ = try ring.waitid(0, .PID, pid, &siginfo, posix.W.EXITED, 0); try testing.expectEqual(1, try ring.submit()); @@ -3877,13 +3877,13 @@ test "waitid" { /// For use in tests. Returns SkipZigTest if kernel version is less than required. inline fn skipKernelLessThan(required: std.SemanticVersion) !void { - if (builtin.os.tag != .linux) return error.SkipZigTest; + if (!is_linux) return error.SkipZigTest; var uts: linux.utsname = undefined; const res = linux.uname(&uts); - switch (linux.getErrno(res)) { + switch (linux.E.init(res)) { .SUCCESS => {}, - else => |errno| return os.unexpectedErrno(errno), + else => |errno| return posix.unexpectedErrno(errno), } const release = mem.sliceTo(&uts.release, 0); @@ -3893,7 +3893,7 @@ inline fn skipKernelLessThan(required: std.SemanticVersion) !void { } test BufferGroup { - if (builtin.os.tag != .linux) return error.SkipZigTest; + if (!is_linux) return error.SkipZigTest; // Init IoUring var ring = IoUring.init(16, 0) catch |err| switch (err) { @@ -3948,7 +3948,7 @@ test BufferGroup { const cqe = try ring.copy_cqe(); try testing.expectEqual(2, cqe.user_data); // matches submitted user_data try testing.expect(cqe.res >= 0); // success - try testing.expectEqual(os.E.SUCCESS, cqe.err()); + try testing.expectEqual(posix.E.SUCCESS, cqe.err()); try testing.expectEqual(data.len, @as(usize, @intCast(cqe.res))); // cqe.res holds received data len // Read buffer_id and used buffer len from cqe @@ -3963,7 +3963,7 @@ test BufferGroup { } test "ring mapped buffers recv" { - if (builtin.os.tag != .linux) return error.SkipZigTest; + if (!is_linux) return error.SkipZigTest; var ring = IoUring.init(16, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, @@ -4029,7 +4029,7 @@ test "ring mapped buffers recv" { const cqe = try ring.copy_cqe(); try testing.expectEqual(user_data, cqe.user_data); try testing.expect(cqe.res < 0); // fail - try testing.expectEqual(os.E.NOBUFS, cqe.err()); + try testing.expectEqual(posix.E.NOBUFS, cqe.err()); try testing.expect(cqe.flags & linux.IORING_CQE_F_BUFFER == 0); // IORING_CQE_F_BUFFER flags is set on success only try testing.expectError(error.NoBufferSelected, cqe.buffer_id()); } @@ -4049,7 +4049,7 @@ test "ring mapped buffers recv" { } test "ring mapped buffers multishot recv" { - if (builtin.os.tag != .linux) return error.SkipZigTest; + if (!is_linux) return error.SkipZigTest; var ring = IoUring.init(16, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, @@ -4120,7 +4120,7 @@ test "ring mapped buffers multishot recv" { const cqe = try ring.copy_cqe(); try testing.expectEqual(recv_user_data, cqe.user_data); try testing.expect(cqe.res < 0); // fail - try testing.expectEqual(os.E.NOBUFS, cqe.err()); + try testing.expectEqual(posix.E.NOBUFS, cqe.err()); try testing.expect(cqe.flags & linux.IORING_CQE_F_BUFFER == 0); // IORING_CQE_F_BUFFER flags is set on success only // has more is not set // indicates that multishot is finished @@ -4194,7 +4194,7 @@ test "ring mapped buffers multishot recv" { fn expect_buf_grp_recv( ring: *IoUring, buf_grp: *BufferGroup, - fd: os.fd_t, + fd: posix.fd_t, user_data: u64, expected: []const u8, ) !u16 { @@ -4220,7 +4220,7 @@ fn expect_buf_grp_cqe( try testing.expect(cqe.res >= 0); // success try testing.expect(cqe.flags & linux.IORING_CQE_F_BUFFER == linux.IORING_CQE_F_BUFFER); // IORING_CQE_F_BUFFER flag is set try testing.expectEqual(expected.len, @as(usize, @intCast(cqe.res))); - try testing.expectEqual(os.E.SUCCESS, cqe.err()); + try testing.expectEqual(posix.E.SUCCESS, cqe.err()); // get buffer from pool const buffer_id = try cqe.buffer_id(); diff --git a/lib/std/os/linux/arm-eabi.zig b/lib/std/os/linux/arm-eabi.zig index 68575c33442b..e5893057255d 100644 --- a/lib/std/os/linux/arm-eabi.zig +++ b/lib/std/os/linux/arm-eabi.zig @@ -2,8 +2,8 @@ const std = @import("../../std.zig"); const maxInt = std.math.maxInt; const linux = std.os.linux; const SYS = linux.SYS; -const iovec = std.os.iovec; -const iovec_const = std.os.iovec_const; +const iovec = std.posix.iovec; +const iovec_const = std.posix.iovec_const; const socklen_t = linux.socklen_t; const stack_t = linux.stack_t; const sigset_t = linux.sigset_t; diff --git a/lib/std/os/linux/arm64.zig b/lib/std/os/linux/arm64.zig index f2331c130908..fd1db3400494 100644 --- a/lib/std/os/linux/arm64.zig +++ b/lib/std/os/linux/arm64.zig @@ -4,8 +4,8 @@ const linux = std.os.linux; const SYS = linux.SYS; const socklen_t = linux.socklen_t; const sockaddr = linux.sockaddr; -const iovec = std.os.iovec; -const iovec_const = std.os.iovec_const; +const iovec = std.posix.iovec; +const iovec_const = std.posix.iovec_const; const uid_t = linux.uid_t; const gid_t = linux.gid_t; const pid_t = linux.pid_t; diff --git a/lib/std/os/linux/bpf.zig b/lib/std/os/linux/bpf.zig index 145fe3212d23..5e02485b152d 100644 --- a/lib/std/os/linux/bpf.zig +++ b/lib/std/os/linux/bpf.zig @@ -1,5 +1,5 @@ const std = @import("../../std.zig"); -const errno = getErrno; +const errno = linux.E.init; const unexpectedErrno = std.os.unexpectedErrno; const expectEqual = std.testing.expectEqual; const expectError = std.testing.expectError; @@ -8,7 +8,6 @@ const expect = std.testing.expect; const linux = std.os.linux; const fd_t = linux.fd_t; const pid_t = linux.pid_t; -const getErrno = linux.getErrno; pub const btf = @import("bpf/btf.zig"); pub const kern = @import("bpf/kern.zig"); diff --git a/lib/std/os/linux/errno/generic.zig b/lib/std/os/linux/errno/generic.zig deleted file mode 100644 index 730c71a5a2f3..000000000000 --- a/lib/std/os/linux/errno/generic.zig +++ /dev/null @@ -1,460 +0,0 @@ -pub const E = enum(u16) { - /// No error occurred. - /// Same code used for `NSROK`. - SUCCESS = 0, - - /// Operation not permitted - PERM = 1, - - /// No such file or directory - NOENT = 2, - - /// No such process - SRCH = 3, - - /// Interrupted system call - INTR = 4, - - /// I/O error - IO = 5, - - /// No such device or address - NXIO = 6, - - /// Arg list too long - @"2BIG" = 7, - - /// Exec format error - NOEXEC = 8, - - /// Bad file number - BADF = 9, - - /// No child processes - CHILD = 10, - - /// Try again - /// Also means: WOULDBLOCK: operation would block - AGAIN = 11, - - /// Out of memory - NOMEM = 12, - - /// Permission denied - ACCES = 13, - - /// Bad address - FAULT = 14, - - /// Block device required - NOTBLK = 15, - - /// Device or resource busy - BUSY = 16, - - /// File exists - EXIST = 17, - - /// Cross-device link - XDEV = 18, - - /// No such device - NODEV = 19, - - /// Not a directory - NOTDIR = 20, - - /// Is a directory - ISDIR = 21, - - /// Invalid argument - INVAL = 22, - - /// File table overflow - NFILE = 23, - - /// Too many open files - MFILE = 24, - - /// Not a typewriter - NOTTY = 25, - - /// Text file busy - TXTBSY = 26, - - /// File too large - FBIG = 27, - - /// No space left on device - NOSPC = 28, - - /// Illegal seek - SPIPE = 29, - - /// Read-only file system - ROFS = 30, - - /// Too many links - MLINK = 31, - - /// Broken pipe - PIPE = 32, - - /// Math argument out of domain of func - DOM = 33, - - /// Math result not representable - RANGE = 34, - - /// Resource deadlock would occur - DEADLK = 35, - - /// File name too long - NAMETOOLONG = 36, - - /// No record locks available - NOLCK = 37, - - /// Function not implemented - NOSYS = 38, - - /// Directory not empty - NOTEMPTY = 39, - - /// Too many symbolic links encountered - LOOP = 40, - - /// No message of desired type - NOMSG = 42, - - /// Identifier removed - IDRM = 43, - - /// Channel number out of range - CHRNG = 44, - - /// Level 2 not synchronized - L2NSYNC = 45, - - /// Level 3 halted - L3HLT = 46, - - /// Level 3 reset - L3RST = 47, - - /// Link number out of range - LNRNG = 48, - - /// Protocol driver not attached - UNATCH = 49, - - /// No CSI structure available - NOCSI = 50, - - /// Level 2 halted - L2HLT = 51, - - /// Invalid exchange - BADE = 52, - - /// Invalid request descriptor - BADR = 53, - - /// Exchange full - XFULL = 54, - - /// No anode - NOANO = 55, - - /// Invalid request code - BADRQC = 56, - - /// Invalid slot - BADSLT = 57, - - /// Bad font file format - BFONT = 59, - - /// Device not a stream - NOSTR = 60, - - /// No data available - NODATA = 61, - - /// Timer expired - TIME = 62, - - /// Out of streams resources - NOSR = 63, - - /// Machine is not on the network - NONET = 64, - - /// Package not installed - NOPKG = 65, - - /// Object is remote - REMOTE = 66, - - /// Link has been severed - NOLINK = 67, - - /// Advertise error - ADV = 68, - - /// Srmount error - SRMNT = 69, - - /// Communication error on send - COMM = 70, - - /// Protocol error - PROTO = 71, - - /// Multihop attempted - MULTIHOP = 72, - - /// RFS specific error - DOTDOT = 73, - - /// Not a data message - BADMSG = 74, - - /// Value too large for defined data type - OVERFLOW = 75, - - /// Name not unique on network - NOTUNIQ = 76, - - /// File descriptor in bad state - BADFD = 77, - - /// Remote address changed - REMCHG = 78, - - /// Can not access a needed shared library - LIBACC = 79, - - /// Accessing a corrupted shared library - LIBBAD = 80, - - /// .lib section in a.out corrupted - LIBSCN = 81, - - /// Attempting to link in too many shared libraries - LIBMAX = 82, - - /// Cannot exec a shared library directly - LIBEXEC = 83, - - /// Illegal byte sequence - ILSEQ = 84, - - /// Interrupted system call should be restarted - RESTART = 85, - - /// Streams pipe error - STRPIPE = 86, - - /// Too many users - USERS = 87, - - /// Socket operation on non-socket - NOTSOCK = 88, - - /// Destination address required - DESTADDRREQ = 89, - - /// Message too long - MSGSIZE = 90, - - /// Protocol wrong type for socket - PROTOTYPE = 91, - - /// Protocol not available - NOPROTOOPT = 92, - - /// Protocol not supported - PROTONOSUPPORT = 93, - - /// Socket type not supported - SOCKTNOSUPPORT = 94, - - /// Operation not supported on transport endpoint - /// This code also means `NOTSUP`. - OPNOTSUPP = 95, - - /// Protocol family not supported - PFNOSUPPORT = 96, - - /// Address family not supported by protocol - AFNOSUPPORT = 97, - - /// Address already in use - ADDRINUSE = 98, - - /// Cannot assign requested address - ADDRNOTAVAIL = 99, - - /// Network is down - NETDOWN = 100, - - /// Network is unreachable - NETUNREACH = 101, - - /// Network dropped connection because of reset - NETRESET = 102, - - /// Software caused connection abort - CONNABORTED = 103, - - /// Connection reset by peer - CONNRESET = 104, - - /// No buffer space available - NOBUFS = 105, - - /// Transport endpoint is already connected - ISCONN = 106, - - /// Transport endpoint is not connected - NOTCONN = 107, - - /// Cannot send after transport endpoint shutdown - SHUTDOWN = 108, - - /// Too many references: cannot splice - TOOMANYREFS = 109, - - /// Connection timed out - TIMEDOUT = 110, - - /// Connection refused - CONNREFUSED = 111, - - /// Host is down - HOSTDOWN = 112, - - /// No route to host - HOSTUNREACH = 113, - - /// Operation already in progress - ALREADY = 114, - - /// Operation now in progress - INPROGRESS = 115, - - /// Stale NFS file handle - STALE = 116, - - /// Structure needs cleaning - UCLEAN = 117, - - /// Not a XENIX named type file - NOTNAM = 118, - - /// No XENIX semaphores available - NAVAIL = 119, - - /// Is a named type file - ISNAM = 120, - - /// Remote I/O error - REMOTEIO = 121, - - /// Quota exceeded - DQUOT = 122, - - /// No medium found - NOMEDIUM = 123, - - /// Wrong medium type - MEDIUMTYPE = 124, - - /// Operation canceled - CANCELED = 125, - - /// Required key not available - NOKEY = 126, - - /// Key has expired - KEYEXPIRED = 127, - - /// Key has been revoked - KEYREVOKED = 128, - - /// Key was rejected by service - KEYREJECTED = 129, - - // for robust mutexes - - /// Owner died - OWNERDEAD = 130, - - /// State not recoverable - NOTRECOVERABLE = 131, - - /// Operation not possible due to RF-kill - RFKILL = 132, - - /// Memory page has hardware error - HWPOISON = 133, - - // nameserver query return codes - - /// DNS server returned answer with no data - NSRNODATA = 160, - - /// DNS server claims query was misformatted - NSRFORMERR = 161, - - /// DNS server returned general failure - NSRSERVFAIL = 162, - - /// Domain name not found - NSRNOTFOUND = 163, - - /// DNS server does not implement requested operation - NSRNOTIMP = 164, - - /// DNS server refused query - NSRREFUSED = 165, - - /// Misformatted DNS query - NSRBADQUERY = 166, - - /// Misformatted domain name - NSRBADNAME = 167, - - /// Unsupported address family - NSRBADFAMILY = 168, - - /// Misformatted DNS reply - NSRBADRESP = 169, - - /// Could not contact DNS servers - NSRCONNREFUSED = 170, - - /// Timeout while contacting DNS servers - NSRTIMEOUT = 171, - - /// End of file - NSROF = 172, - - /// Error reading file - NSRFILE = 173, - - /// Out of memory - NSRNOMEM = 174, - - /// Application terminated lookup - NSRDESTRUCTION = 175, - - /// Domain name is too long - NSRQUERYDOMAINTOOLONG = 176, - - /// Domain name is too long - NSRCNAMELOOP = 177, - - _, -}; diff --git a/lib/std/os/linux/errno/mips.zig b/lib/std/os/linux/errno/mips.zig deleted file mode 100644 index 39fb9f71eaec..000000000000 --- a/lib/std/os/linux/errno/mips.zig +++ /dev/null @@ -1,141 +0,0 @@ -//! These are MIPS ABI compatible. -pub const E = enum(i32) { - /// No error occurred. - SUCCESS = 0, - - PERM = 1, - NOENT = 2, - SRCH = 3, - INTR = 4, - IO = 5, - NXIO = 6, - @"2BIG" = 7, - NOEXEC = 8, - BADF = 9, - CHILD = 10, - /// Also used for WOULDBLOCK. - AGAIN = 11, - NOMEM = 12, - ACCES = 13, - FAULT = 14, - NOTBLK = 15, - BUSY = 16, - EXIST = 17, - XDEV = 18, - NODEV = 19, - NOTDIR = 20, - ISDIR = 21, - INVAL = 22, - NFILE = 23, - MFILE = 24, - NOTTY = 25, - TXTBSY = 26, - FBIG = 27, - NOSPC = 28, - SPIPE = 29, - ROFS = 30, - MLINK = 31, - PIPE = 32, - DOM = 33, - RANGE = 34, - - NOMSG = 35, - IDRM = 36, - CHRNG = 37, - L2NSYNC = 38, - L3HLT = 39, - L3RST = 40, - LNRNG = 41, - UNATCH = 42, - NOCSI = 43, - L2HLT = 44, - DEADLK = 45, - NOLCK = 46, - BADE = 50, - BADR = 51, - XFULL = 52, - NOANO = 53, - BADRQC = 54, - BADSLT = 55, - DEADLOCK = 56, - BFONT = 59, - NOSTR = 60, - NODATA = 61, - TIME = 62, - NOSR = 63, - NONET = 64, - NOPKG = 65, - REMOTE = 66, - NOLINK = 67, - ADV = 68, - SRMNT = 69, - COMM = 70, - PROTO = 71, - DOTDOT = 73, - MULTIHOP = 74, - BADMSG = 77, - NAMETOOLONG = 78, - OVERFLOW = 79, - NOTUNIQ = 80, - BADFD = 81, - REMCHG = 82, - LIBACC = 83, - LIBBAD = 84, - LIBSCN = 85, - LIBMAX = 86, - LIBEXEC = 87, - ILSEQ = 88, - NOSYS = 89, - LOOP = 90, - RESTART = 91, - STRPIPE = 92, - NOTEMPTY = 93, - USERS = 94, - NOTSOCK = 95, - DESTADDRREQ = 96, - MSGSIZE = 97, - PROTOTYPE = 98, - NOPROTOOPT = 99, - PROTONOSUPPORT = 120, - SOCKTNOSUPPORT = 121, - OPNOTSUPP = 122, - PFNOSUPPORT = 123, - AFNOSUPPORT = 124, - ADDRINUSE = 125, - ADDRNOTAVAIL = 126, - NETDOWN = 127, - NETUNREACH = 128, - NETRESET = 129, - CONNABORTED = 130, - CONNRESET = 131, - NOBUFS = 132, - ISCONN = 133, - NOTCONN = 134, - UCLEAN = 135, - NOTNAM = 137, - NAVAIL = 138, - ISNAM = 139, - REMOTEIO = 140, - SHUTDOWN = 143, - TOOMANYREFS = 144, - TIMEDOUT = 145, - CONNREFUSED = 146, - HOSTDOWN = 147, - HOSTUNREACH = 148, - ALREADY = 149, - INPROGRESS = 150, - STALE = 151, - CANCELED = 158, - NOMEDIUM = 159, - MEDIUMTYPE = 160, - NOKEY = 161, - KEYEXPIRED = 162, - KEYREVOKED = 163, - KEYREJECTED = 164, - OWNERDEAD = 165, - NOTRECOVERABLE = 166, - RFKILL = 167, - HWPOISON = 168, - DQUOT = 1133, - _, -}; diff --git a/lib/std/os/linux/errno/sparc.zig b/lib/std/os/linux/errno/sparc.zig deleted file mode 100644 index c4ab65f34a5a..000000000000 --- a/lib/std/os/linux/errno/sparc.zig +++ /dev/null @@ -1,144 +0,0 @@ -//! These match the SunOS error numbering scheme. -pub const E = enum(i32) { - /// No error occurred. - SUCCESS = 0, - - PERM = 1, - NOENT = 2, - SRCH = 3, - INTR = 4, - IO = 5, - NXIO = 6, - @"2BIG" = 7, - NOEXEC = 8, - BADF = 9, - CHILD = 10, - /// Also used for WOULDBLOCK - AGAIN = 11, - NOMEM = 12, - ACCES = 13, - FAULT = 14, - NOTBLK = 15, - BUSY = 16, - EXIST = 17, - XDEV = 18, - NODEV = 19, - NOTDIR = 20, - ISDIR = 21, - INVAL = 22, - NFILE = 23, - MFILE = 24, - NOTTY = 25, - TXTBSY = 26, - FBIG = 27, - NOSPC = 28, - SPIPE = 29, - ROFS = 30, - MLINK = 31, - PIPE = 32, - DOM = 33, - RANGE = 34, - - INPROGRESS = 36, - ALREADY = 37, - NOTSOCK = 38, - DESTADDRREQ = 39, - MSGSIZE = 40, - PROTOTYPE = 41, - NOPROTOOPT = 42, - PROTONOSUPPORT = 43, - SOCKTNOSUPPORT = 44, - /// Also used for NOTSUP - OPNOTSUPP = 45, - PFNOSUPPORT = 46, - AFNOSUPPORT = 47, - ADDRINUSE = 48, - ADDRNOTAVAIL = 49, - NETDOWN = 50, - NETUNREACH = 51, - NETRESET = 52, - CONNABORTED = 53, - CONNRESET = 54, - NOBUFS = 55, - ISCONN = 56, - NOTCONN = 57, - SHUTDOWN = 58, - TOOMANYREFS = 59, - TIMEDOUT = 60, - CONNREFUSED = 61, - LOOP = 62, - NAMETOOLONG = 63, - HOSTDOWN = 64, - HOSTUNREACH = 65, - NOTEMPTY = 66, - PROCLIM = 67, - USERS = 68, - DQUOT = 69, - STALE = 70, - REMOTE = 71, - NOSTR = 72, - TIME = 73, - NOSR = 74, - NOMSG = 75, - BADMSG = 76, - IDRM = 77, - DEADLK = 78, - NOLCK = 79, - NONET = 80, - RREMOTE = 81, - NOLINK = 82, - ADV = 83, - SRMNT = 84, - COMM = 85, - PROTO = 86, - MULTIHOP = 87, - DOTDOT = 88, - REMCHG = 89, - NOSYS = 90, - STRPIPE = 91, - OVERFLOW = 92, - BADFD = 93, - CHRNG = 94, - L2NSYNC = 95, - L3HLT = 96, - L3RST = 97, - LNRNG = 98, - UNATCH = 99, - NOCSI = 100, - L2HLT = 101, - BADE = 102, - BADR = 103, - XFULL = 104, - NOANO = 105, - BADRQC = 106, - BADSLT = 107, - DEADLOCK = 108, - BFONT = 109, - LIBEXEC = 110, - NODATA = 111, - LIBBAD = 112, - NOPKG = 113, - LIBACC = 114, - NOTUNIQ = 115, - RESTART = 116, - UCLEAN = 117, - NOTNAM = 118, - NAVAIL = 119, - ISNAM = 120, - REMOTEIO = 121, - ILSEQ = 122, - LIBMAX = 123, - LIBSCN = 124, - NOMEDIUM = 125, - MEDIUMTYPE = 126, - CANCELED = 127, - NOKEY = 128, - KEYEXPIRED = 129, - KEYREVOKED = 130, - KEYREJECTED = 131, - OWNERDEAD = 132, - NOTRECOVERABLE = 133, - RFKILL = 134, - HWPOISON = 135, - _, -}; diff --git a/lib/std/os/linux/io_uring_sqe.zig b/lib/std/os/linux/io_uring_sqe.zig index 73ee1956878c..7306cf8eedfa 100644 --- a/lib/std/os/linux/io_uring_sqe.zig +++ b/lib/std/os/linux/io_uring_sqe.zig @@ -2,8 +2,7 @@ //! Split into its own file to compartmentalize the initialization methods. const std = @import("../../std.zig"); -const os = std.os; -const linux = os.linux; +const linux = std.os.linux; pub const io_uring_sqe = extern struct { opcode: linux.IORING_OP, @@ -40,7 +39,7 @@ pub const io_uring_sqe = extern struct { }; } - pub fn prep_fsync(sqe: *linux.io_uring_sqe, fd: os.fd_t, flags: u32) void { + pub fn prep_fsync(sqe: *linux.io_uring_sqe, fd: linux.fd_t, flags: u32) void { sqe.* = .{ .opcode = .FSYNC, .flags = 0, @@ -62,7 +61,7 @@ pub const io_uring_sqe = extern struct { pub fn prep_rw( sqe: *linux.io_uring_sqe, op: linux.IORING_OP, - fd: os.fd_t, + fd: linux.fd_t, addr: u64, len: usize, offset: u64, @@ -85,15 +84,15 @@ pub const io_uring_sqe = extern struct { }; } - pub fn prep_read(sqe: *linux.io_uring_sqe, fd: os.fd_t, buffer: []u8, offset: u64) void { + pub fn prep_read(sqe: *linux.io_uring_sqe, fd: linux.fd_t, buffer: []u8, offset: u64) void { sqe.prep_rw(.READ, fd, @intFromPtr(buffer.ptr), buffer.len, offset); } - pub fn prep_write(sqe: *linux.io_uring_sqe, fd: os.fd_t, buffer: []const u8, offset: u64) void { + pub fn prep_write(sqe: *linux.io_uring_sqe, fd: linux.fd_t, buffer: []const u8, offset: u64) void { sqe.prep_rw(.WRITE, fd, @intFromPtr(buffer.ptr), buffer.len, offset); } - pub fn prep_splice(sqe: *linux.io_uring_sqe, fd_in: os.fd_t, off_in: u64, fd_out: os.fd_t, off_out: u64, len: usize) void { + pub fn prep_splice(sqe: *linux.io_uring_sqe, fd_in: linux.fd_t, off_in: u64, fd_out: linux.fd_t, off_out: u64, len: usize) void { sqe.prep_rw(.SPLICE, fd_out, undefined, len, off_out); sqe.addr = off_in; sqe.splice_fd_in = fd_in; @@ -101,8 +100,8 @@ pub const io_uring_sqe = extern struct { pub fn prep_readv( sqe: *linux.io_uring_sqe, - fd: os.fd_t, - iovecs: []const os.iovec, + fd: linux.fd_t, + iovecs: []const std.posix.iovec, offset: u64, ) void { sqe.prep_rw(.READV, fd, @intFromPtr(iovecs.ptr), iovecs.len, offset); @@ -110,28 +109,28 @@ pub const io_uring_sqe = extern struct { pub fn prep_writev( sqe: *linux.io_uring_sqe, - fd: os.fd_t, - iovecs: []const os.iovec_const, + fd: linux.fd_t, + iovecs: []const std.posix.iovec_const, offset: u64, ) void { sqe.prep_rw(.WRITEV, fd, @intFromPtr(iovecs.ptr), iovecs.len, offset); } - pub fn prep_read_fixed(sqe: *linux.io_uring_sqe, fd: os.fd_t, buffer: *os.iovec, offset: u64, buffer_index: u16) void { + pub fn prep_read_fixed(sqe: *linux.io_uring_sqe, fd: linux.fd_t, buffer: *std.posix.iovec, offset: u64, buffer_index: u16) void { sqe.prep_rw(.READ_FIXED, fd, @intFromPtr(buffer.iov_base), buffer.iov_len, offset); sqe.buf_index = buffer_index; } - pub fn prep_write_fixed(sqe: *linux.io_uring_sqe, fd: os.fd_t, buffer: *os.iovec, offset: u64, buffer_index: u16) void { + pub fn prep_write_fixed(sqe: *linux.io_uring_sqe, fd: linux.fd_t, buffer: *std.posix.iovec, offset: u64, buffer_index: u16) void { sqe.prep_rw(.WRITE_FIXED, fd, @intFromPtr(buffer.iov_base), buffer.iov_len, offset); sqe.buf_index = buffer_index; } pub fn prep_accept( sqe: *linux.io_uring_sqe, - fd: os.fd_t, - addr: ?*os.sockaddr, - addrlen: ?*os.socklen_t, + fd: linux.fd_t, + addr: ?*linux.sockaddr, + addrlen: ?*linux.socklen_t, flags: u32, ) void { // `addr` holds a pointer to `sockaddr`, and `addr2` holds a pointer to socklen_t`. @@ -142,9 +141,9 @@ pub const io_uring_sqe = extern struct { pub fn prep_accept_direct( sqe: *linux.io_uring_sqe, - fd: os.fd_t, - addr: ?*os.sockaddr, - addrlen: ?*os.socklen_t, + fd: linux.fd_t, + addr: ?*linux.sockaddr, + addrlen: ?*linux.socklen_t, flags: u32, file_index: u32, ) void { @@ -154,9 +153,9 @@ pub const io_uring_sqe = extern struct { pub fn prep_multishot_accept_direct( sqe: *linux.io_uring_sqe, - fd: os.fd_t, - addr: ?*os.sockaddr, - addrlen: ?*os.socklen_t, + fd: linux.fd_t, + addr: ?*linux.sockaddr, + addrlen: ?*linux.socklen_t, flags: u32, ) void { prep_multishot_accept(sqe, fd, addr, addrlen, flags); @@ -177,9 +176,9 @@ pub const io_uring_sqe = extern struct { pub fn prep_connect( sqe: *linux.io_uring_sqe, - fd: os.fd_t, - addr: *const os.sockaddr, - addrlen: os.socklen_t, + fd: linux.fd_t, + addr: *const linux.sockaddr, + addrlen: linux.socklen_t, ) void { // `addrlen` maps to `sqe.off` (u64) instead of `sqe.len` (which is only a u32). sqe.prep_rw(.CONNECT, fd, @intFromPtr(addr), 0, addrlen); @@ -187,22 +186,22 @@ pub const io_uring_sqe = extern struct { pub fn prep_epoll_ctl( sqe: *linux.io_uring_sqe, - epfd: os.fd_t, - fd: os.fd_t, + epfd: linux.fd_t, + fd: linux.fd_t, op: u32, ev: ?*linux.epoll_event, ) void { sqe.prep_rw(.EPOLL_CTL, epfd, @intFromPtr(ev), op, @intCast(fd)); } - pub fn prep_recv(sqe: *linux.io_uring_sqe, fd: os.fd_t, buffer: []u8, flags: u32) void { + pub fn prep_recv(sqe: *linux.io_uring_sqe, fd: linux.fd_t, buffer: []u8, flags: u32) void { sqe.prep_rw(.RECV, fd, @intFromPtr(buffer.ptr), buffer.len, 0); sqe.rw_flags = flags; } pub fn prep_recv_multishot( sqe: *linux.io_uring_sqe, - fd: os.fd_t, + fd: linux.fd_t, buffer: []u8, flags: u32, ) void { @@ -212,8 +211,8 @@ pub const io_uring_sqe = extern struct { pub fn prep_recvmsg( sqe: *linux.io_uring_sqe, - fd: os.fd_t, - msg: *os.msghdr, + fd: linux.fd_t, + msg: *linux.msghdr, flags: u32, ) void { sqe.prep_rw(.RECVMSG, fd, @intFromPtr(msg), 1, 0); @@ -222,26 +221,26 @@ pub const io_uring_sqe = extern struct { pub fn prep_recvmsg_multishot( sqe: *linux.io_uring_sqe, - fd: os.fd_t, - msg: *os.msghdr, + fd: linux.fd_t, + msg: *linux.msghdr, flags: u32, ) void { sqe.prep_recvmsg(fd, msg, flags); sqe.ioprio |= linux.IORING_RECV_MULTISHOT; } - pub fn prep_send(sqe: *linux.io_uring_sqe, fd: os.fd_t, buffer: []const u8, flags: u32) void { + pub fn prep_send(sqe: *linux.io_uring_sqe, fd: linux.fd_t, buffer: []const u8, flags: u32) void { sqe.prep_rw(.SEND, fd, @intFromPtr(buffer.ptr), buffer.len, 0); sqe.rw_flags = flags; } - pub fn prep_send_zc(sqe: *linux.io_uring_sqe, fd: os.fd_t, buffer: []const u8, flags: u32, zc_flags: u16) void { + pub fn prep_send_zc(sqe: *linux.io_uring_sqe, fd: linux.fd_t, buffer: []const u8, flags: u32, zc_flags: u16) void { sqe.prep_rw(.SEND_ZC, fd, @intFromPtr(buffer.ptr), buffer.len, 0); sqe.rw_flags = flags; sqe.ioprio = zc_flags; } - pub fn prep_send_zc_fixed(sqe: *linux.io_uring_sqe, fd: os.fd_t, buffer: []const u8, flags: u32, zc_flags: u16, buf_index: u16) void { + pub fn prep_send_zc_fixed(sqe: *linux.io_uring_sqe, fd: linux.fd_t, buffer: []const u8, flags: u32, zc_flags: u16, buf_index: u16) void { prep_send_zc(sqe, fd, buffer, flags, zc_flags); sqe.ioprio |= linux.IORING_RECVSEND_FIXED_BUF; sqe.buf_index = buf_index; @@ -249,8 +248,8 @@ pub const io_uring_sqe = extern struct { pub fn prep_sendmsg_zc( sqe: *linux.io_uring_sqe, - fd: os.fd_t, - msg: *const os.msghdr_const, + fd: linux.fd_t, + msg: *const linux.msghdr_const, flags: u32, ) void { prep_sendmsg(sqe, fd, msg, flags); @@ -259,8 +258,8 @@ pub const io_uring_sqe = extern struct { pub fn prep_sendmsg( sqe: *linux.io_uring_sqe, - fd: os.fd_t, - msg: *const os.msghdr_const, + fd: linux.fd_t, + msg: *const linux.msghdr_const, flags: u32, ) void { sqe.prep_rw(.SENDMSG, fd, @intFromPtr(msg), 1, 0); @@ -269,10 +268,10 @@ pub const io_uring_sqe = extern struct { pub fn prep_openat( sqe: *linux.io_uring_sqe, - fd: os.fd_t, + fd: linux.fd_t, path: [*:0]const u8, flags: linux.O, - mode: os.mode_t, + mode: linux.mode_t, ) void { sqe.prep_rw(.OPENAT, fd, @intFromPtr(path), mode, 0); sqe.rw_flags = @bitCast(flags); @@ -280,17 +279,17 @@ pub const io_uring_sqe = extern struct { pub fn prep_openat_direct( sqe: *linux.io_uring_sqe, - fd: os.fd_t, + fd: linux.fd_t, path: [*:0]const u8, flags: linux.O, - mode: os.mode_t, + mode: linux.mode_t, file_index: u32, ) void { prep_openat(sqe, fd, path, flags, mode); __io_uring_set_target_fixed_file(sqe, file_index); } - pub fn prep_close(sqe: *linux.io_uring_sqe, fd: os.fd_t) void { + pub fn prep_close(sqe: *linux.io_uring_sqe, fd: linux.fd_t) void { sqe.* = .{ .opcode = .CLOSE, .flags = 0, @@ -316,7 +315,7 @@ pub const io_uring_sqe = extern struct { pub fn prep_timeout( sqe: *linux.io_uring_sqe, - ts: *const os.linux.kernel_timespec, + ts: *const linux.kernel_timespec, count: u32, flags: u32, ) void { @@ -345,7 +344,7 @@ pub const io_uring_sqe = extern struct { pub fn prep_link_timeout( sqe: *linux.io_uring_sqe, - ts: *const os.linux.kernel_timespec, + ts: *const linux.kernel_timespec, flags: u32, ) void { sqe.prep_rw(.LINK_TIMEOUT, -1, @intFromPtr(ts), 1, 0); @@ -354,7 +353,7 @@ pub const io_uring_sqe = extern struct { pub fn prep_poll_add( sqe: *linux.io_uring_sqe, - fd: os.fd_t, + fd: linux.fd_t, poll_mask: u32, ) void { sqe.prep_rw(.POLL_ADD, fd, @intFromPtr(@as(?*anyopaque, null)), 0, 0); @@ -393,7 +392,7 @@ pub const io_uring_sqe = extern struct { pub fn prep_fallocate( sqe: *linux.io_uring_sqe, - fd: os.fd_t, + fd: linux.fd_t, mode: i32, offset: u64, len: u64, @@ -418,7 +417,7 @@ pub const io_uring_sqe = extern struct { pub fn prep_statx( sqe: *linux.io_uring_sqe, - fd: os.fd_t, + fd: linux.fd_t, path: [*:0]const u8, flags: u32, mask: u32, @@ -439,7 +438,7 @@ pub const io_uring_sqe = extern struct { pub fn prep_shutdown( sqe: *linux.io_uring_sqe, - sockfd: os.socket_t, + sockfd: linux.socket_t, how: u32, ) void { sqe.prep_rw(.SHUTDOWN, sockfd, 0, how, 0); @@ -447,9 +446,9 @@ pub const io_uring_sqe = extern struct { pub fn prep_renameat( sqe: *linux.io_uring_sqe, - old_dir_fd: os.fd_t, + old_dir_fd: linux.fd_t, old_path: [*:0]const u8, - new_dir_fd: os.fd_t, + new_dir_fd: linux.fd_t, new_path: [*:0]const u8, flags: u32, ) void { @@ -466,7 +465,7 @@ pub const io_uring_sqe = extern struct { pub fn prep_unlinkat( sqe: *linux.io_uring_sqe, - dir_fd: os.fd_t, + dir_fd: linux.fd_t, path: [*:0]const u8, flags: u32, ) void { @@ -476,9 +475,9 @@ pub const io_uring_sqe = extern struct { pub fn prep_mkdirat( sqe: *linux.io_uring_sqe, - dir_fd: os.fd_t, + dir_fd: linux.fd_t, path: [*:0]const u8, - mode: os.mode_t, + mode: linux.mode_t, ) void { sqe.prep_rw(.MKDIRAT, dir_fd, @intFromPtr(path), mode, 0); } @@ -486,7 +485,7 @@ pub const io_uring_sqe = extern struct { pub fn prep_symlinkat( sqe: *linux.io_uring_sqe, target: [*:0]const u8, - new_dir_fd: os.fd_t, + new_dir_fd: linux.fd_t, link_path: [*:0]const u8, ) void { sqe.prep_rw( @@ -500,9 +499,9 @@ pub const io_uring_sqe = extern struct { pub fn prep_linkat( sqe: *linux.io_uring_sqe, - old_dir_fd: os.fd_t, + old_dir_fd: linux.fd_t, old_path: [*:0]const u8, - new_dir_fd: os.fd_t, + new_dir_fd: linux.fd_t, new_path: [*:0]const u8, flags: u32, ) void { @@ -541,9 +540,9 @@ pub const io_uring_sqe = extern struct { pub fn prep_multishot_accept( sqe: *linux.io_uring_sqe, - fd: os.fd_t, - addr: ?*os.sockaddr, - addrlen: ?*os.socklen_t, + fd: linux.fd_t, + addr: ?*linux.sockaddr, + addrlen: ?*linux.socklen_t, flags: u32, ) void { prep_accept(sqe, fd, addr, addrlen, flags); diff --git a/lib/std/os/linux/mips.zig b/lib/std/os/linux/mips.zig index 896757f1f6c5..b0f33894c896 100644 --- a/lib/std/os/linux/mips.zig +++ b/lib/std/os/linux/mips.zig @@ -3,8 +3,8 @@ const maxInt = std.math.maxInt; const linux = std.os.linux; const SYS = linux.SYS; const socklen_t = linux.socklen_t; -const iovec = std.os.iovec; -const iovec_const = std.os.iovec_const; +const iovec = std.posix.iovec; +const iovec_const = std.posix.iovec_const; const uid_t = linux.uid_t; const gid_t = linux.gid_t; const pid_t = linux.pid_t; diff --git a/lib/std/os/linux/mips64.zig b/lib/std/os/linux/mips64.zig index 4a34f30dd96d..c1b352328bc9 100644 --- a/lib/std/os/linux/mips64.zig +++ b/lib/std/os/linux/mips64.zig @@ -3,8 +3,8 @@ const maxInt = std.math.maxInt; const linux = std.os.linux; const SYS = linux.SYS; const socklen_t = linux.socklen_t; -const iovec = std.os.iovec; -const iovec_const = std.os.iovec_const; +const iovec = std.posix.iovec; +const iovec_const = std.posix.iovec_const; const uid_t = linux.uid_t; const gid_t = linux.gid_t; const pid_t = linux.pid_t; diff --git a/lib/std/os/linux/powerpc.zig b/lib/std/os/linux/powerpc.zig index 4d13e90166a1..af7c8f0c06f9 100644 --- a/lib/std/os/linux/powerpc.zig +++ b/lib/std/os/linux/powerpc.zig @@ -3,8 +3,8 @@ const maxInt = std.math.maxInt; const linux = std.os.linux; const SYS = linux.SYS; const socklen_t = linux.socklen_t; -const iovec = std.os.iovec; -const iovec_const = std.os.iovec_const; +const iovec = std.posix.iovec; +const iovec_const = std.posix.iovec_const; const uid_t = linux.uid_t; const gid_t = linux.gid_t; const pid_t = linux.pid_t; diff --git a/lib/std/os/linux/powerpc64.zig b/lib/std/os/linux/powerpc64.zig index c81ef382c2d7..9a17fb9a35de 100644 --- a/lib/std/os/linux/powerpc64.zig +++ b/lib/std/os/linux/powerpc64.zig @@ -3,8 +3,8 @@ const maxInt = std.math.maxInt; const linux = std.os.linux; const SYS = linux.SYS; const socklen_t = linux.socklen_t; -const iovec = std.os.iovec; -const iovec_const = std.os.iovec_const; +const iovec = std.posix.iovec; +const iovec_const = std.posix.iovec_const; const uid_t = linux.uid_t; const gid_t = linux.gid_t; const pid_t = linux.pid_t; diff --git a/lib/std/os/linux/riscv64.zig b/lib/std/os/linux/riscv64.zig index c23fc5e4dfe2..88b5c70fbc32 100644 --- a/lib/std/os/linux/riscv64.zig +++ b/lib/std/os/linux/riscv64.zig @@ -1,6 +1,6 @@ const std = @import("../../std.zig"); -const iovec = std.os.iovec; -const iovec_const = std.os.iovec_const; +const iovec = std.posix.iovec; +const iovec_const = std.posix.iovec_const; const linux = std.os.linux; const SYS = linux.SYS; const uid_t = std.os.linux.uid_t; diff --git a/lib/std/os/linux/sparc64.zig b/lib/std/os/linux/sparc64.zig index 0a344e2bf47b..796c6e090558 100644 --- a/lib/std/os/linux/sparc64.zig +++ b/lib/std/os/linux/sparc64.zig @@ -10,8 +10,8 @@ const linux = std.os.linux; const SYS = linux.SYS; const sockaddr = linux.sockaddr; const socklen_t = linux.socklen_t; -const iovec = std.os.iovec; -const iovec_const = std.os.iovec_const; +const iovec = std.posix.iovec; +const iovec_const = std.posix.iovec_const; const timespec = linux.timespec; pub fn syscall_pipe(fd: *[2]i32) usize { diff --git a/lib/std/os/linux/test.zig b/lib/std/os/linux/test.zig index 2224224c5dea..5aa8a565cfd1 100644 --- a/lib/std/os/linux/test.zig +++ b/lib/std/os/linux/test.zig @@ -18,7 +18,7 @@ test "fallocate" { try expect((try file.stat()).size == 0); const len: i64 = 65536; - switch (linux.getErrno(linux.fallocate(file.handle, 0, 0, len))) { + switch (linux.E.init(linux.fallocate(file.handle, 0, 0, len))) { .SUCCESS => {}, .NOSYS => return error.SkipZigTest, .OPNOTSUPP => return error.SkipZigTest, @@ -34,11 +34,11 @@ test "getpid" { test "timer" { const epoll_fd = linux.epoll_create(); - var err: linux.E = linux.getErrno(epoll_fd); + var err: linux.E = linux.E.init(epoll_fd); try expect(err == .SUCCESS); const timer_fd = linux.timerfd_create(linux.CLOCK.MONOTONIC, .{}); - try expect(linux.getErrno(timer_fd) == .SUCCESS); + try expect(linux.E.init(timer_fd) == .SUCCESS); const time_interval = linux.timespec{ .tv_sec = 0, @@ -50,7 +50,7 @@ test "timer" { .it_value = time_interval, }; - err = linux.getErrno(linux.timerfd_settime(@as(i32, @intCast(timer_fd)), .{}, &new_time, null)); + err = linux.E.init(linux.timerfd_settime(@as(i32, @intCast(timer_fd)), .{}, &new_time, null)); try expect(err == .SUCCESS); var event = linux.epoll_event{ @@ -58,13 +58,13 @@ test "timer" { .data = linux.epoll_data{ .ptr = 0 }, }; - err = linux.getErrno(linux.epoll_ctl(@as(i32, @intCast(epoll_fd)), linux.EPOLL.CTL_ADD, @as(i32, @intCast(timer_fd)), &event)); + err = linux.E.init(linux.epoll_ctl(@as(i32, @intCast(epoll_fd)), linux.EPOLL.CTL_ADD, @as(i32, @intCast(timer_fd)), &event)); try expect(err == .SUCCESS); const events_one: linux.epoll_event = undefined; var events = [_]linux.epoll_event{events_one} ** 8; - err = linux.getErrno(linux.epoll_wait(@as(i32, @intCast(epoll_fd)), &events, 8, -1)); + err = linux.E.init(linux.epoll_wait(@as(i32, @intCast(epoll_fd)), &events, 8, -1)); try expect(err == .SUCCESS); } @@ -77,7 +77,7 @@ test "statx" { defer file.close(); var statx_buf: linux.Statx = undefined; - switch (linux.getErrno(linux.statx(file.handle, "", linux.AT.EMPTY_PATH, linux.STATX_BASIC_STATS, &statx_buf))) { + switch (linux.E.init(linux.statx(file.handle, "", linux.AT.EMPTY_PATH, linux.STATX_BASIC_STATS, &statx_buf))) { .SUCCESS => {}, // The statx syscall was only introduced in linux 4.11 .NOSYS => return error.SkipZigTest, @@ -85,7 +85,7 @@ test "statx" { } var stat_buf: linux.Stat = undefined; - switch (linux.getErrno(linux.fstatat(file.handle, "", &stat_buf, linux.AT.EMPTY_PATH))) { + switch (linux.E.init(linux.fstatat(file.handle, "", &stat_buf, linux.AT.EMPTY_PATH))) { .SUCCESS => {}, else => unreachable, } diff --git a/lib/std/os/linux/tls.zig b/lib/std/os/linux/tls.zig index 6973d1ea2e0e..82dc3bf75997 100644 --- a/lib/std/os/linux/tls.zig +++ b/lib/std/os/linux/tls.zig @@ -1,10 +1,11 @@ const std = @import("std"); -const os = std.os; const mem = std.mem; const elf = std.elf; const math = std.math; const assert = std.debug.assert; const native_arch = @import("builtin").cpu.arch; +const linux = std.os.linux; +const posix = std.posix; // This file implements the two TLS variants [1] used by ELF-based systems. // @@ -111,7 +112,7 @@ pub var tls_image: TLSImage = undefined; pub fn setThreadPointer(addr: usize) void { switch (native_arch) { .x86 => { - var user_desc = std.os.linux.user_desc{ + var user_desc: linux.user_desc = .{ .entry_number = tls_image.gdt_entry_number, .base_addr = addr, .limit = 0xfffff, @@ -124,7 +125,7 @@ pub fn setThreadPointer(addr: usize) void { .useable = 1, }, }; - const rc = std.os.linux.syscall1(.set_thread_area, @intFromPtr(&user_desc)); + const rc = linux.syscall1(.set_thread_area, @intFromPtr(&user_desc)); assert(rc == 0); const gdt_entry_number = user_desc.entry_number; @@ -137,7 +138,7 @@ pub fn setThreadPointer(addr: usize) void { ); }, .x86_64 => { - const rc = std.os.linux.syscall2(.arch_prctl, std.os.linux.ARCH.SET_FS, addr); + const rc = linux.syscall2(.arch_prctl, linux.ARCH.SET_FS, addr); assert(rc == 0); }, .aarch64, .aarch64_be => { @@ -148,7 +149,7 @@ pub fn setThreadPointer(addr: usize) void { ); }, .arm, .thumb => { - const rc = std.os.linux.syscall1(.set_tls, addr); + const rc = linux.syscall1(.set_tls, addr); assert(rc == 0); }, .riscv64 => { @@ -159,7 +160,7 @@ pub fn setThreadPointer(addr: usize) void { ); }, .mips, .mipsel, .mips64, .mips64el => { - const rc = std.os.linux.syscall1(.set_thread_area, addr); + const rc = linux.syscall1(.set_thread_area, addr); assert(rc == 0); }, .powerpc, .powerpcle => { @@ -320,14 +321,14 @@ pub fn initStaticTLS(phdrs: []elf.Phdr) void { break :blk main_thread_tls_buffer[0..tls_image.alloc_size]; } - const alloc_tls_area = os.mmap( + const alloc_tls_area = posix.mmap( null, tls_image.alloc_size + tls_image.alloc_align - 1, - os.PROT.READ | os.PROT.WRITE, + posix.PROT.READ | posix.PROT.WRITE, .{ .TYPE = .PRIVATE, .ANONYMOUS = true }, -1, 0, - ) catch os.abort(); + ) catch posix.abort(); // Make sure the slice is correctly aligned. const begin_addr = @intFromPtr(alloc_tls_area.ptr); diff --git a/lib/std/os/linux/vdso.zig b/lib/std/os/linux/vdso.zig index 50e7ce1dfddc..4e62679b26e2 100644 --- a/lib/std/os/linux/vdso.zig +++ b/lib/std/os/linux/vdso.zig @@ -5,7 +5,7 @@ const mem = std.mem; const maxInt = std.math.maxInt; pub fn lookup(vername: []const u8, name: []const u8) usize { - const vdso_addr = std.os.system.getauxval(std.elf.AT_SYSINFO_EHDR); + const vdso_addr = linux.getauxval(std.elf.AT_SYSINFO_EHDR); if (vdso_addr == 0) return 0; const eh = @as(*elf.Ehdr, @ptrFromInt(vdso_addr)); diff --git a/lib/std/os/linux/x86.zig b/lib/std/os/linux/x86.zig index 44ee45d31666..52a0215922ce 100644 --- a/lib/std/os/linux/x86.zig +++ b/lib/std/os/linux/x86.zig @@ -3,8 +3,8 @@ const maxInt = std.math.maxInt; const linux = std.os.linux; const SYS = linux.SYS; const socklen_t = linux.socklen_t; -const iovec = std.os.iovec; -const iovec_const = std.os.iovec_const; +const iovec = std.posix.iovec; +const iovec_const = std.posix.iovec_const; const uid_t = linux.uid_t; const gid_t = linux.gid_t; const pid_t = linux.pid_t; diff --git a/lib/std/os/linux/x86_64.zig b/lib/std/os/linux/x86_64.zig index 2d69d539aee5..b21e56c0e5f6 100644 --- a/lib/std/os/linux/x86_64.zig +++ b/lib/std/os/linux/x86_64.zig @@ -2,8 +2,8 @@ const std = @import("../../std.zig"); const maxInt = std.math.maxInt; const linux = std.os.linux; const SYS = linux.SYS; -const iovec = std.os.iovec; -const iovec_const = std.os.iovec_const; +const iovec = std.posix.iovec; +const iovec_const = std.posix.iovec_const; const pid_t = linux.pid_t; const uid_t = linux.uid_t; diff --git a/lib/std/os/plan9.zig b/lib/std/os/plan9.zig index 354e05e5706c..a01e6f59eb8b 100644 --- a/lib/std/os/plan9.zig +++ b/lib/std/os/plan9.zig @@ -11,13 +11,96 @@ pub const syscall_bits = switch (builtin.cpu.arch) { .x86_64 => @import("plan9/x86_64.zig"), else => @compileError("more plan9 syscall implementations (needs more inline asm in stage2"), }; -pub const E = @import("plan9/errno.zig").E; -/// Get the errno from a syscall return value, or 0 for no error. -pub fn getErrno(r: usize) E { - const signed_r = @as(isize, @bitCast(r)); - const int = if (signed_r > -4096 and signed_r < 0) -signed_r else 0; - return @as(E, @enumFromInt(int)); -} +/// Ported from /sys/include/ape/errno.h +pub const E = enum(u16) { + SUCCESS = 0, + DOM = 1000, + RANGE = 1001, + PLAN9 = 1002, + + @"2BIG" = 1, + ACCES = 2, + AGAIN = 3, + // WOULDBLOCK = 3, // TODO errno.h has 2 names for 3 + BADF = 4, + BUSY = 5, + CHILD = 6, + DEADLK = 7, + EXIST = 8, + FAULT = 9, + FBIG = 10, + INTR = 11, + INVAL = 12, + IO = 13, + ISDIR = 14, + MFILE = 15, + MLINK = 16, + NAMETOOLONG = 17, + NFILE = 18, + NODEV = 19, + NOENT = 20, + NOEXEC = 21, + NOLCK = 22, + NOMEM = 23, + NOSPC = 24, + NOSYS = 25, + NOTDIR = 26, + NOTEMPTY = 27, + NOTTY = 28, + NXIO = 29, + PERM = 30, + PIPE = 31, + ROFS = 32, + SPIPE = 33, + SRCH = 34, + XDEV = 35, + + // bsd networking software + NOTSOCK = 36, + PROTONOSUPPORT = 37, + // PROTOTYPE = 37, // TODO errno.h has two names for 37 + CONNREFUSED = 38, + AFNOSUPPORT = 39, + NOBUFS = 40, + OPNOTSUPP = 41, + ADDRINUSE = 42, + DESTADDRREQ = 43, + MSGSIZE = 44, + NOPROTOOPT = 45, + SOCKTNOSUPPORT = 46, + PFNOSUPPORT = 47, + ADDRNOTAVAIL = 48, + NETDOWN = 49, + NETUNREACH = 50, + NETRESET = 51, + CONNABORTED = 52, + ISCONN = 53, + NOTCONN = 54, + SHUTDOWN = 55, + TOOMANYREFS = 56, + TIMEDOUT = 57, + HOSTDOWN = 58, + HOSTUNREACH = 59, + GREG = 60, + + // These added in 1003.1b-1993 + CANCELED = 61, + INPROGRESS = 62, + + // We just add these to be compatible with std.os, which uses them, + // They should never get used. + DQUOT, + CONNRESET, + OVERFLOW, + LOOP, + TXTBSY, + + pub fn init(r: usize) E { + const signed_r: isize = @bitCast(r); + const int = if (signed_r > -4096 and signed_r < 0) -signed_r else 0; + return @enumFromInt(int); + } +}; // The max bytes that can be in the errstr buff pub const ERRMAX = 128; var errstr_buf: [ERRMAX]u8 = undefined; diff --git a/lib/std/os/plan9/errno.zig b/lib/std/os/plan9/errno.zig deleted file mode 100644 index 47a232e67c0e..000000000000 --- a/lib/std/os/plan9/errno.zig +++ /dev/null @@ -1,84 +0,0 @@ -//! Ported from /sys/include/ape/errno.h -pub const E = enum(u16) { - SUCCESS = 0, - DOM = 1000, - RANGE = 1001, - PLAN9 = 1002, - - @"2BIG" = 1, - ACCES = 2, - AGAIN = 3, - // WOULDBLOCK = 3, // TODO errno.h has 2 names for 3 - BADF = 4, - BUSY = 5, - CHILD = 6, - DEADLK = 7, - EXIST = 8, - FAULT = 9, - FBIG = 10, - INTR = 11, - INVAL = 12, - IO = 13, - ISDIR = 14, - MFILE = 15, - MLINK = 16, - NAMETOOLONG = 17, - NFILE = 18, - NODEV = 19, - NOENT = 20, - NOEXEC = 21, - NOLCK = 22, - NOMEM = 23, - NOSPC = 24, - NOSYS = 25, - NOTDIR = 26, - NOTEMPTY = 27, - NOTTY = 28, - NXIO = 29, - PERM = 30, - PIPE = 31, - ROFS = 32, - SPIPE = 33, - SRCH = 34, - XDEV = 35, - - // bsd networking software - NOTSOCK = 36, - PROTONOSUPPORT = 37, - // PROTOTYPE = 37, // TODO errno.h has two names for 37 - CONNREFUSED = 38, - AFNOSUPPORT = 39, - NOBUFS = 40, - OPNOTSUPP = 41, - ADDRINUSE = 42, - DESTADDRREQ = 43, - MSGSIZE = 44, - NOPROTOOPT = 45, - SOCKTNOSUPPORT = 46, - PFNOSUPPORT = 47, - ADDRNOTAVAIL = 48, - NETDOWN = 49, - NETUNREACH = 50, - NETRESET = 51, - CONNABORTED = 52, - ISCONN = 53, - NOTCONN = 54, - SHUTDOWN = 55, - TOOMANYREFS = 56, - TIMEDOUT = 57, - HOSTDOWN = 58, - HOSTUNREACH = 59, - GREG = 60, - - // These added in 1003.1b-1993 - CANCELED = 61, - INPROGRESS = 62, - - // We just add these to be compatible with std.os, which uses them, - // They should never get used. - DQUOT, - CONNRESET, - OVERFLOW, - LOOP, - TXTBSY, -}; diff --git a/lib/std/os/wasi.zig b/lib/std/os/wasi.zig index 016ce38a9fcb..d0f8bdbb661a 100644 --- a/lib/std/os/wasi.zig +++ b/lib/std/os/wasi.zig @@ -17,8 +17,8 @@ comptime { // assert(@alignOf(u64) == 8); } -pub const iovec_t = std.os.iovec; -pub const ciovec_t = std.os.iovec_const; +pub const iovec_t = std.posix.iovec; +pub const ciovec_t = std.posix.iovec_const; pub extern "wasi_snapshot_preview1" fn args_get(argv: [*][*:0]u8, argv_buf: [*]u8) errno_t; pub extern "wasi_snapshot_preview1" fn args_sizes_get(argc: *usize, argv_buf_size: *usize) errno_t; diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index dec54026096f..6ea7611fd773 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -11,6 +11,7 @@ const assert = std.debug.assert; const math = std.math; const maxInt = std.math.maxInt; const native_arch = builtin.cpu.arch; +const UnexpectedError = std.posix.UnexpectedError; test { if (builtin.os.tag == .windows) { @@ -547,7 +548,7 @@ pub const GetQueuedCompletionStatusError = error{ Cancelled, EOF, Timeout, -} || std.os.UnexpectedError; +} || UnexpectedError; pub fn GetQueuedCompletionStatusEx( completion_port: HANDLE, @@ -1701,7 +1702,7 @@ pub fn VirtualProtectEx(handle: HANDLE, addr: ?LPVOID, size: SIZE_T, new_prot: D .SUCCESS => return old_prot, .INVALID_ADDRESS => return error.InvalidAddress, // TODO: map errors - else => |rc| return std.os.windows.unexpectedStatus(rc), + else => |rc| return unexpectedStatus(rc), } } @@ -1946,7 +1947,7 @@ pub fn SetFileTime( pub const LockFileError = error{ SystemResources, WouldBlock, -} || std.os.UnexpectedError; +} || UnexpectedError; pub fn LockFile( FileHandle: HANDLE, @@ -1983,7 +1984,7 @@ pub fn LockFile( pub const UnlockFileError = error{ RangeNotLocked, -} || std.os.UnexpectedError; +} || UnexpectedError; pub fn UnlockFile( FileHandle: HANDLE, @@ -2672,8 +2673,8 @@ pub fn loadWinsockExtensionFunction(comptime T: type, sock: ws2_32.SOCKET, guid: /// Call this when you made a windows DLL call or something that does SetLastError /// and you get an unexpected error. -pub fn unexpectedError(err: Win32Error) std.os.UnexpectedError { - if (std.os.unexpected_error_tracing) { +pub fn unexpectedError(err: Win32Error) UnexpectedError { + if (std.posix.unexpected_error_tracing) { // 614 is the length of the longest windows error description var buf_wstr: [614]WCHAR = undefined; const len = kernel32.FormatMessageW( @@ -2694,14 +2695,14 @@ pub fn unexpectedError(err: Win32Error) std.os.UnexpectedError { return error.Unexpected; } -pub fn unexpectedWSAError(err: ws2_32.WinsockError) std.os.UnexpectedError { +pub fn unexpectedWSAError(err: ws2_32.WinsockError) UnexpectedError { return unexpectedError(@as(Win32Error, @enumFromInt(@intFromEnum(err)))); } /// Call this when you made a windows NtDll call /// and you get an unexpected status. -pub fn unexpectedStatus(status: NTSTATUS) std.os.UnexpectedError { - if (std.os.unexpected_error_tracing) { +pub fn unexpectedStatus(status: NTSTATUS) UnexpectedError { + if (std.posix.unexpected_error_tracing) { std.debug.print("error.Unexpected NTSTATUS=0x{x}\n", .{@intFromEnum(status)}); std.debug.dumpCurrentStackTrace(@returnAddress()); } @@ -4246,7 +4247,7 @@ pub const KNONVOLATILE_CONTEXT_POINTERS = switch (native_arch) { pub const EXCEPTION_POINTERS = extern struct { ExceptionRecord: *EXCEPTION_RECORD, - ContextRecord: *std.os.windows.CONTEXT, + ContextRecord: *CONTEXT, }; pub const VECTORED_EXCEPTION_HANDLER = *const fn (ExceptionInfo: *EXCEPTION_POINTERS) callconv(WINAPI) c_long; diff --git a/lib/std/os/windows/test.zig b/lib/std/os/windows/test.zig index 64d554c8360a..c9f6ccbce005 100644 --- a/lib/std/os/windows/test.zig +++ b/lib/std/os/windows/test.zig @@ -245,7 +245,7 @@ test "loadWinsockExtensionFunction" { const LPFN_CONNECTEX = *const fn ( Socket: windows.ws2_32.SOCKET, SockAddr: *const windows.ws2_32.sockaddr, - SockLen: std.os.socklen_t, + SockLen: std.posix.socklen_t, SendBuf: ?*const anyopaque, SendBufLen: windows.DWORD, BytesSent: *windows.DWORD, @@ -254,7 +254,7 @@ test "loadWinsockExtensionFunction" { _ = windows.loadWinsockExtensionFunction( LPFN_CONNECTEX, - try std.os.socket(std.os.AF.INET, std.os.SOCK.DGRAM, 0), + try std.posix.socket(std.posix.AF.INET, std.posix.SOCK.DGRAM, 0), windows.ws2_32.WSAID_CONNECTEX, ) catch |err| switch (err) { error.OperationNotSupported => unreachable, diff --git a/lib/std/pdb.zig b/lib/std/pdb.zig index 9640ec35697b..ece1cc63dc92 100644 --- a/lib/std/pdb.zig +++ b/lib/std/pdb.zig @@ -2,7 +2,6 @@ const std = @import("std.zig"); const io = std.io; const math = std.math; const mem = std.mem; -const os = std.os; const coff = std.coff; const fs = std.fs; const File = std.fs.File; diff --git a/lib/std/posix.zig b/lib/std/posix.zig new file mode 100644 index 000000000000..a5f8641ed6f7 --- /dev/null +++ b/lib/std/posix.zig @@ -0,0 +1,7364 @@ +//! POSIX API layer. +//! +//! This is more cross platform than using OS-specific APIs, however, it is +//! lower-level and less portable than other namespaces such as `std.fs` and +//! `std.process`. +//! +//! These APIs are generally lowered to libc function calls if and only if libc +//! is linked. Most operating systems other than Windows, Linux, and WASI +//! require always linking libc because they use it as the stable syscall ABI. +//! +//! Operating systems that are not POSIX-compliant are sometimes supported by +//! this API layer; sometimes not. Generally, an implementation will be +//! provided only if such implementation is straightforward on that operating +//! system. Otherwise, programmers are expected to use OS-specific logic to +//! deal with the exception. + +const builtin = @import("builtin"); +const root = @import("root"); +const std = @import("std.zig"); +const mem = std.mem; +const fs = std.fs; +const max_path_bytes = fs.MAX_PATH_BYTES; +const maxInt = std.math.maxInt; +const cast = std.math.cast; +const assert = std.debug.assert; +const native_os = builtin.os.tag; + +test { + _ = @import("posix/test.zig"); +} + +/// Whether to use libc for the POSIX API layer. +const use_libc = builtin.link_libc or switch (native_os) { + .windows, .wasi => true, + else => false, +}; + +const linux = std.os.linux; +const windows = std.os.windows; +const wasi = std.os.wasi; + +/// Applications can override the `system` API layer in their root source file. +/// Otherwise, when linking libc, this is the C API. +/// When not linking libc, it is the OS-specific system interface. +pub const system = if (@hasDecl(root, "os") and @hasDecl(root.os, "system") and root.os != @This()) + root.os.system +else if (use_libc) + std.c +else switch (native_os) { + .linux => linux, + .plan9 => std.os.plan9, + else => struct {}, +}; + +pub const AF = system.AF; +pub const AF_SUN = system.AF_SUN; +pub const ARCH = system.ARCH; +pub const AT = system.AT; +pub const AT_SUN = system.AT_SUN; +pub const CLOCK = system.CLOCK; +pub const CPU_COUNT = system.CPU_COUNT; +pub const CTL = system.CTL; +pub const DT = system.DT; +pub const E = system.E; +pub const Elf_Symndx = system.Elf_Symndx; +pub const F = system.F; +pub const FD_CLOEXEC = system.FD_CLOEXEC; +pub const Flock = system.Flock; +pub const HOST_NAME_MAX = system.HOST_NAME_MAX; +pub const HW = system.HW; +pub const IFNAMESIZE = system.IFNAMESIZE; +pub const IOV_MAX = system.IOV_MAX; +pub const IPPROTO = system.IPPROTO; +pub const KERN = system.KERN; +pub const Kevent = system.Kevent; +pub const LOCK = system.LOCK; +pub const MADV = system.MADV; +pub const MAP = system.MAP; +pub const MSF = system.MSF; +pub const MAX_ADDR_LEN = system.MAX_ADDR_LEN; +pub const MFD = system.MFD; +pub const MMAP2_UNIT = system.MMAP2_UNIT; +pub const MSG = system.MSG; +pub const NAME_MAX = system.NAME_MAX; +pub const O = system.O; +pub const PATH_MAX = system.PATH_MAX; +pub const POLL = system.POLL; +pub const POSIX_FADV = system.POSIX_FADV; +pub const PR = system.PR; +pub const PROT = system.PROT; +pub const REG = system.REG; +pub const RLIM = system.RLIM; +pub const RR = system.RR; +pub const S = system.S; +pub const SA = system.SA; +pub const SC = system.SC; +pub const _SC = system._SC; +pub const SEEK = system.SEEK; +pub const SHUT = system.SHUT; +pub const SIG = system.SIG; +pub const SIOCGIFINDEX = system.SIOCGIFINDEX; +pub const SO = system.SO; +pub const SOCK = system.SOCK; +pub const SOL = system.SOL; +pub const STDERR_FILENO = system.STDERR_FILENO; +pub const STDIN_FILENO = system.STDIN_FILENO; +pub const STDOUT_FILENO = system.STDOUT_FILENO; +pub const SYS = system.SYS; +pub const Sigaction = system.Sigaction; +pub const Stat = system.Stat; +pub const T = system.T; +pub const TCSA = system.TCSA; +pub const TCP = system.TCP; +pub const VDSO = system.VDSO; +pub const W = system.W; +pub const addrinfo = system.addrinfo; +pub const blkcnt_t = system.blkcnt_t; +pub const blksize_t = system.blksize_t; +pub const clock_t = system.clock_t; +pub const cpu_set_t = system.cpu_set_t; +pub const dev_t = system.dev_t; +pub const dl_phdr_info = system.dl_phdr_info; +pub const empty_sigset = system.empty_sigset; +pub const filled_sigset = system.filled_sigset; +pub const fd_t = system.fd_t; +pub const gid_t = system.gid_t; +pub const ifreq = system.ifreq; +pub const ino_t = system.ino_t; +pub const mcontext_t = system.mcontext_t; +pub const mode_t = system.mode_t; +pub const msghdr = system.msghdr; +pub const msghdr_const = system.msghdr_const; +pub const nfds_t = system.nfds_t; +pub const nlink_t = system.nlink_t; +pub const off_t = system.off_t; +pub const pid_t = system.pid_t; +pub const pollfd = system.pollfd; +pub const port_t = system.port_t; +pub const port_event = system.port_event; +pub const port_notify = system.port_notify; +pub const file_obj = system.file_obj; +pub const rlim_t = system.rlim_t; +pub const rlimit = system.rlimit; +pub const rlimit_resource = system.rlimit_resource; +pub const rusage = system.rusage; +pub const sa_family_t = system.sa_family_t; +pub const siginfo_t = system.siginfo_t; +pub const sigset_t = system.sigset_t; +pub const sockaddr = system.sockaddr; +pub const socklen_t = system.socklen_t; +pub const stack_t = system.stack_t; +pub const time_t = system.time_t; +pub const timespec = system.timespec; +pub const timestamp_t = system.timestamp_t; +pub const timeval = system.timeval; +pub const timezone = system.timezone; +pub const ucontext_t = system.ucontext_t; +pub const uid_t = system.uid_t; +pub const user_desc = system.user_desc; +pub const utsname = system.utsname; +pub const winsize = system.winsize; + +pub const termios = system.termios; +pub const CSIZE = system.CSIZE; +pub const NCCS = system.NCCS; +pub const cc_t = system.cc_t; +pub const V = system.V; +pub const speed_t = system.speed_t; +pub const tc_iflag_t = system.tc_iflag_t; +pub const tc_oflag_t = system.tc_oflag_t; +pub const tc_cflag_t = system.tc_cflag_t; +pub const tc_lflag_t = system.tc_lflag_t; + +pub const F_OK = system.F_OK; +pub const R_OK = system.R_OK; +pub const W_OK = system.W_OK; +pub const X_OK = system.X_OK; + +pub const iovec = extern struct { + iov_base: [*]u8, + iov_len: usize, +}; + +pub const iovec_const = extern struct { + iov_base: [*]const u8, + iov_len: usize, +}; + +pub const ACCMODE = enum(u2) { + RDONLY = 0, + WRONLY = 1, + RDWR = 2, +}; + +pub const LOG = struct { + /// system is unusable + pub const EMERG = 0; + /// action must be taken immediately + pub const ALERT = 1; + /// critical conditions + pub const CRIT = 2; + /// error conditions + pub const ERR = 3; + /// warning conditions + pub const WARNING = 4; + /// normal but significant condition + pub const NOTICE = 5; + /// informational + pub const INFO = 6; + /// debug-level messages + pub const DEBUG = 7; +}; + +pub const socket_t = if (native_os == .windows) windows.ws2_32.SOCKET else fd_t; + +/// Obtains errno from the return value of a system function call. +/// +/// For some systems this will obtain the value directly from the syscall return value; +/// for others it will use a thread-local errno variable. Therefore, this +/// function only returns a well-defined value when it is called directly after +/// the system function call whose errno value is intended to be observed. +pub fn errno(rc: anytype) E { + if (use_libc) { + return if (rc == -1) @enumFromInt(std.c._errno().*) else .SUCCESS; + } + const signed: isize = @bitCast(rc); + const int = if (signed > -4096 and signed < 0) -signed else 0; + return @enumFromInt(int); +} + +/// Closes the file descriptor. +/// +/// This function is not capable of returning any indication of failure. An +/// application which wants to ensure writes have succeeded before closing must +/// call `fsync` before `close`. +/// +/// The Zig standard library does not support POSIX thread cancellation. +pub fn close(fd: fd_t) void { + if (native_os == .windows) { + return windows.CloseHandle(fd); + } + if (native_os == .wasi and !builtin.link_libc) { + _ = std.os.wasi.fd_close(fd); + return; + } + if (builtin.target.isDarwin()) { + // This avoids the EINTR problem. + switch (errno(std.c.@"close$NOCANCEL"(fd))) { + .BADF => unreachable, // Always a race condition. + else => return, + } + } + switch (errno(system.close(fd))) { + .BADF => unreachable, // Always a race condition. + .INTR => return, // This is still a success. See https://github.com/ziglang/zig/issues/2425 + else => return, + } +} + +pub const FChmodError = error{ + AccessDenied, + InputOutput, + SymLinkLoop, + FileNotFound, + SystemResources, + ReadOnlyFileSystem, +} || UnexpectedError; + +/// Changes the mode of the file referred to by the file descriptor. +/// +/// The process must have the correct privileges in order to do this +/// successfully, or must have the effective user ID matching the owner +/// of the file. +pub fn fchmod(fd: fd_t, mode: mode_t) FChmodError!void { + if (!fs.has_executable_bit) @compileError("fchmod unsupported by target OS"); + + while (true) { + const res = system.fchmod(fd, mode); + switch (errno(res)) { + .SUCCESS => return, + .INTR => continue, + .BADF => unreachable, + .FAULT => unreachable, + .INVAL => unreachable, + .ACCES => return error.AccessDenied, + .IO => return error.InputOutput, + .LOOP => return error.SymLinkLoop, + .NOENT => return error.FileNotFound, + .NOMEM => return error.SystemResources, + .NOTDIR => return error.FileNotFound, + .PERM => return error.AccessDenied, + .ROFS => return error.ReadOnlyFileSystem, + else => |err| return unexpectedErrno(err), + } + } +} + +pub const FChmodAtError = FChmodError || error{ + /// A component of `path` exceeded `NAME_MAX`, or the entire path exceeded + /// `PATH_MAX`. + NameTooLong, + /// `path` resolves to a symbolic link, and `AT.SYMLINK_NOFOLLOW` was set + /// in `flags`. This error only occurs on Linux, where changing the mode of + /// a symbolic link has no meaning and can cause undefined behaviour on + /// certain filesystems. + /// + /// The procfs fallback was used but procfs was not mounted. + OperationNotSupported, + /// The procfs fallback was used but the process exceeded its open file + /// limit. + ProcessFdQuotaExceeded, + /// The procfs fallback was used but the system exceeded it open file limit. + SystemFdQuotaExceeded, +}; + +/// Changes the `mode` of `path` relative to the directory referred to by +/// `dirfd`. The process must have the correct privileges in order to do this +/// successfully, or must have the effective user ID matching the owner of the +/// file. +/// +/// On Linux the `fchmodat2` syscall will be used if available, otherwise a +/// workaround using procfs will be employed. Changing the mode of a symbolic +/// link with `AT.SYMLINK_NOFOLLOW` set will also return +/// `OperationNotSupported`, as: +/// +/// 1. Permissions on the link are ignored when resolving its target. +/// 2. This operation has been known to invoke undefined behaviour across +/// different filesystems[1]. +/// +/// [1]: https://sourceware.org/legacy-ml/libc-alpha/2020-02/msg00467.html. +pub inline fn fchmodat(dirfd: fd_t, path: []const u8, mode: mode_t, flags: u32) FChmodAtError!void { + if (!fs.has_executable_bit) @compileError("fchmodat unsupported by target OS"); + + // No special handling for linux is needed if we can use the libc fallback + // or `flags` is empty. Glibc only added the fallback in 2.32. + const skip_fchmodat_fallback = native_os != .linux or + std.c.versionCheck(.{ .major = 2, .minor = 32, .patch = 0 }) or + flags == 0; + + // This function is marked inline so that when flags is comptime-known, + // skip_fchmodat_fallback will be comptime-known true. + if (skip_fchmodat_fallback) + return fchmodat1(dirfd, path, mode, flags); + + return fchmodat2(dirfd, path, mode, flags); +} + +fn fchmodat1(dirfd: fd_t, path: []const u8, mode: mode_t, flags: u32) FChmodAtError!void { + const path_c = try toPosixPath(path); + while (true) { + const res = system.fchmodat(dirfd, &path_c, mode, flags); + switch (errno(res)) { + .SUCCESS => return, + .INTR => continue, + .BADF => unreachable, + .FAULT => unreachable, + .INVAL => unreachable, + .ACCES => return error.AccessDenied, + .IO => return error.InputOutput, + .LOOP => return error.SymLinkLoop, + .MFILE => return error.ProcessFdQuotaExceeded, + .NAMETOOLONG => return error.NameTooLong, + .NFILE => return error.SystemFdQuotaExceeded, + .NOENT => return error.FileNotFound, + .NOTDIR => return error.FileNotFound, + .NOMEM => return error.SystemResources, + .OPNOTSUPP => return error.OperationNotSupported, + .PERM => return error.AccessDenied, + .ROFS => return error.ReadOnlyFileSystem, + else => |err| return unexpectedErrno(err), + } + } +} + +fn fchmodat2(dirfd: fd_t, path: []const u8, mode: mode_t, flags: u32) FChmodAtError!void { + const global = struct { + var has_fchmodat2: bool = true; + }; + const path_c = try toPosixPath(path); + const use_fchmodat2 = (builtin.os.isAtLeast(.linux, .{ .major = 6, .minor = 6, .patch = 0 }) orelse false) and + @atomicLoad(bool, &global.has_fchmodat2, .monotonic); + while (use_fchmodat2) { + // Later on this should be changed to `system.fchmodat2` + // when the musl/glibc add a wrapper. + const res = linux.fchmodat2(dirfd, &path_c, mode, flags); + switch (E.init(res)) { + .SUCCESS => return, + .INTR => continue, + .BADF => unreachable, + .FAULT => unreachable, + .INVAL => unreachable, + .ACCES => return error.AccessDenied, + .IO => return error.InputOutput, + .LOOP => return error.SymLinkLoop, + .NOENT => return error.FileNotFound, + .NOMEM => return error.SystemResources, + .NOTDIR => return error.FileNotFound, + .OPNOTSUPP => return error.OperationNotSupported, + .PERM => return error.AccessDenied, + .ROFS => return error.ReadOnlyFileSystem, + + .NOSYS => { + @atomicStore(bool, &global.has_fchmodat2, false, .monotonic); + break; + }, + else => |err| return unexpectedErrno(err), + } + } + + // Fallback to changing permissions using procfs: + // + // 1. Open `path` as a `PATH` descriptor. + // 2. Stat the fd and check if it isn't a symbolic link. + // 3. Generate the procfs reference to the fd via `/proc/self/fd/{fd}`. + // 4. Pass the procfs path to `chmod` with the `mode`. + var pathfd: fd_t = undefined; + while (true) { + const rc = system.openat(dirfd, &path_c, .{ .PATH = true, .NOFOLLOW = true, .CLOEXEC = true }, @as(mode_t, 0)); + switch (errno(rc)) { + .SUCCESS => { + pathfd = @intCast(rc); + break; + }, + .INTR => continue, + .FAULT => unreachable, + .INVAL => unreachable, + .ACCES => return error.AccessDenied, + .PERM => return error.AccessDenied, + .LOOP => return error.SymLinkLoop, + .MFILE => return error.ProcessFdQuotaExceeded, + .NAMETOOLONG => return error.NameTooLong, + .NFILE => return error.SystemFdQuotaExceeded, + .NOENT => return error.FileNotFound, + .NOMEM => return error.SystemResources, + else => |err| return unexpectedErrno(err), + } + } + defer close(pathfd); + + const stat = fstatatZ(pathfd, "", AT.EMPTY_PATH) catch |err| switch (err) { + error.NameTooLong => unreachable, + error.FileNotFound => unreachable, + error.InvalidUtf8 => unreachable, + else => |e| return e, + }; + if ((stat.mode & S.IFMT) == S.IFLNK) + return error.OperationNotSupported; + + var procfs_buf: ["/proc/self/fd/-2147483648\x00".len]u8 = undefined; + const proc_path = std.fmt.bufPrintZ(procfs_buf[0..], "/proc/self/fd/{d}", .{pathfd}) catch unreachable; + while (true) { + const res = system.chmod(proc_path, mode); + switch (errno(res)) { + // Getting NOENT here means that procfs isn't mounted. + .NOENT => return error.OperationNotSupported, + + .SUCCESS => return, + .INTR => continue, + .BADF => unreachable, + .FAULT => unreachable, + .INVAL => unreachable, + .ACCES => return error.AccessDenied, + .IO => return error.InputOutput, + .LOOP => return error.SymLinkLoop, + .NOMEM => return error.SystemResources, + .NOTDIR => return error.FileNotFound, + .PERM => return error.AccessDenied, + .ROFS => return error.ReadOnlyFileSystem, + else => |err| return unexpectedErrno(err), + } + } +} + +pub const FChownError = error{ + AccessDenied, + InputOutput, + SymLinkLoop, + FileNotFound, + SystemResources, + ReadOnlyFileSystem, +} || UnexpectedError; + +/// Changes the owner and group of the file referred to by the file descriptor. +/// The process must have the correct privileges in order to do this +/// successfully. The group may be changed by the owner of the directory to +/// any group of which the owner is a member. If the owner or group is +/// specified as `null`, the ID is not changed. +pub fn fchown(fd: fd_t, owner: ?uid_t, group: ?gid_t) FChownError!void { + switch (native_os) { + .windows, .wasi => @compileError("Unsupported OS"), + else => {}, + } + + while (true) { + const res = system.fchown(fd, owner orelse ~@as(uid_t, 0), group orelse ~@as(gid_t, 0)); + + switch (errno(res)) { + .SUCCESS => return, + .INTR => continue, + .BADF => unreachable, // Can be reached if the fd refers to a directory opened without `OpenDirOptions{ .iterate = true }` + + .FAULT => unreachable, + .INVAL => unreachable, + .ACCES => return error.AccessDenied, + .IO => return error.InputOutput, + .LOOP => return error.SymLinkLoop, + .NOENT => return error.FileNotFound, + .NOMEM => return error.SystemResources, + .NOTDIR => return error.FileNotFound, + .PERM => return error.AccessDenied, + .ROFS => return error.ReadOnlyFileSystem, + else => |err| return unexpectedErrno(err), + } + } +} + +pub const RebootError = error{ + PermissionDenied, +} || UnexpectedError; + +pub const RebootCommand = switch (native_os) { + .linux => union(linux.LINUX_REBOOT.CMD) { + RESTART: void, + HALT: void, + CAD_ON: void, + CAD_OFF: void, + POWER_OFF: void, + RESTART2: [*:0]const u8, + SW_SUSPEND: void, + KEXEC: void, + }, + else => @compileError("Unsupported OS"), +}; + +pub fn reboot(cmd: RebootCommand) RebootError!void { + switch (native_os) { + .linux => { + switch (linux.E.init(linux.reboot( + .MAGIC1, + .MAGIC2, + cmd, + switch (cmd) { + .RESTART2 => |s| s, + else => null, + }, + ))) { + .SUCCESS => {}, + .PERM => return error.PermissionDenied, + else => |err| return std.os.unexpectedErrno(err), + } + switch (cmd) { + .CAD_OFF => {}, + .CAD_ON => {}, + .SW_SUSPEND => {}, + + .HALT => unreachable, + .KEXEC => unreachable, + .POWER_OFF => unreachable, + .RESTART => unreachable, + .RESTART2 => unreachable, + } + }, + else => @compileError("Unsupported OS"), + } +} + +pub const GetRandomError = OpenError; + +/// Obtain a series of random bytes. These bytes can be used to seed user-space +/// random number generators or for cryptographic purposes. +/// When linking against libc, this calls the +/// appropriate OS-specific library call. Otherwise it uses the zig standard +/// library implementation. +pub fn getrandom(buffer: []u8) GetRandomError!void { + if (native_os == .windows) { + return windows.RtlGenRandom(buffer); + } + if (native_os == .linux or native_os == .freebsd) { + var buf = buffer; + const use_c = native_os != .linux or + std.c.versionCheck(std.SemanticVersion{ .major = 2, .minor = 25, .patch = 0 }); + + while (buf.len != 0) { + const num_read: usize, const err = if (use_c) res: { + const rc = std.c.getrandom(buf.ptr, buf.len, 0); + break :res .{ @bitCast(rc), errno(rc) }; + } else res: { + const rc = linux.getrandom(buf.ptr, buf.len, 0); + break :res .{ rc, linux.E.init(rc) }; + }; + + switch (err) { + .SUCCESS => buf = buf[num_read..], + .INVAL => unreachable, + .FAULT => unreachable, + .INTR => continue, + .NOSYS => return getRandomBytesDevURandom(buf), + else => return unexpectedErrno(err), + } + } + return; + } + if (native_os == .emscripten) { + const err = errno(std.c.getentropy(buffer.ptr, buffer.len)); + switch (err) { + .SUCCESS => return, + else => return unexpectedErrno(err), + } + } + switch (native_os) { + .netbsd, .openbsd, .macos, .ios, .tvos, .watchos => { + system.arc4random_buf(buffer.ptr, buffer.len); + return; + }, + .wasi => switch (wasi.random_get(buffer.ptr, buffer.len)) { + .SUCCESS => return, + else => |err| return unexpectedErrno(err), + }, + else => return getRandomBytesDevURandom(buffer), + } +} + +fn getRandomBytesDevURandom(buf: []u8) !void { + const fd = try openZ("/dev/urandom", .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0); + defer close(fd); + + const st = try fstat(fd); + if (!S.ISCHR(st.mode)) { + return error.NoDevice; + } + + const file: fs.File = .{ .handle = fd }; + const stream = file.reader(); + stream.readNoEof(buf) catch return error.Unexpected; +} + +/// Causes abnormal process termination. +/// If linking against libc, this calls the abort() libc function. Otherwise +/// it raises SIGABRT followed by SIGKILL and finally lo +/// Invokes the current signal handler for SIGABRT, if any. +pub fn abort() noreturn { + @setCold(true); + // MSVCRT abort() sometimes opens a popup window which is undesirable, so + // even when linking libc on Windows we use our own abort implementation. + // See https://github.com/ziglang/zig/issues/2071 for more details. + if (native_os == .windows) { + if (builtin.mode == .Debug) { + @breakpoint(); + } + windows.kernel32.ExitProcess(3); + } + if (!builtin.link_libc and native_os == .linux) { + // The Linux man page says that the libc abort() function + // "first unblocks the SIGABRT signal", but this is a footgun + // for user-defined signal handlers that want to restore some state in + // some program sections and crash in others. + // So, the user-installed SIGABRT handler is run, if present. + raise(SIG.ABRT) catch {}; + + // Disable all signal handlers. + sigprocmask(SIG.BLOCK, &linux.all_mask, null); + + // Only one thread may proceed to the rest of abort(). + if (!builtin.single_threaded) { + const global = struct { + var abort_entered: bool = false; + }; + while (@cmpxchgWeak(bool, &global.abort_entered, false, true, .seq_cst, .seq_cst)) |_| {} + } + + // Install default handler so that the tkill below will terminate. + const sigact = Sigaction{ + .handler = .{ .handler = SIG.DFL }, + .mask = empty_sigset, + .flags = 0, + }; + sigaction(SIG.ABRT, &sigact, null) catch |err| switch (err) { + error.OperationNotSupported => unreachable, + }; + + _ = linux.tkill(linux.gettid(), SIG.ABRT); + + const sigabrtmask: linux.sigset_t = [_]u32{0} ** 31 ++ [_]u32{1 << (SIG.ABRT - 1)}; + sigprocmask(SIG.UNBLOCK, &sigabrtmask, null); + + // Beyond this point should be unreachable. + @as(*allowzero volatile u8, @ptrFromInt(0)).* = 0; + raise(SIG.KILL) catch {}; + exit(127); // Pid 1 might not be signalled in some containers. + } + switch (native_os) { + .uefi, .wasi, .emscripten, .cuda, .amdhsa => @trap(), + else => system.abort(), + } +} + +pub const RaiseError = UnexpectedError; + +pub fn raise(sig: u8) RaiseError!void { + if (builtin.link_libc) { + switch (errno(system.raise(sig))) { + .SUCCESS => return, + else => |err| return unexpectedErrno(err), + } + } + + if (native_os == .linux) { + var set: sigset_t = undefined; + // block application signals + sigprocmask(SIG.BLOCK, &linux.app_mask, &set); + + const tid = linux.gettid(); + const rc = linux.tkill(tid, sig); + + // restore signal mask + sigprocmask(SIG.SETMASK, &set, null); + + switch (errno(rc)) { + .SUCCESS => return, + else => |err| return unexpectedErrno(err), + } + } + + @compileError("std.os.raise unimplemented for this target"); +} + +pub const KillError = error{ ProcessNotFound, PermissionDenied } || UnexpectedError; + +pub fn kill(pid: pid_t, sig: u8) KillError!void { + switch (errno(system.kill(pid, sig))) { + .SUCCESS => return, + .INVAL => unreachable, // invalid signal + .PERM => return error.PermissionDenied, + .SRCH => return error.ProcessNotFound, + else => |err| return unexpectedErrno(err), + } +} + +/// Exits all threads of the program with the specified status code. +pub fn exit(status: u8) noreturn { + if (builtin.link_libc) { + std.c.exit(status); + } + if (native_os == .windows) { + windows.kernel32.ExitProcess(status); + } + if (native_os == .wasi) { + wasi.proc_exit(status); + } + if (native_os == .linux and !builtin.single_threaded) { + linux.exit_group(status); + } + if (native_os == .uefi) { + const uefi = std.os.uefi; + // exit() is only available if exitBootServices() has not been called yet. + // This call to exit should not fail, so we don't care about its return value. + if (uefi.system_table.boot_services) |bs| { + _ = bs.exit(uefi.handle, @enumFromInt(status), 0, null); + } + // If we can't exit, reboot the system instead. + uefi.system_table.runtime_services.resetSystem(.ResetCold, @enumFromInt(status), 0, null); + } + system.exit(status); +} + +pub const ReadError = error{ + InputOutput, + SystemResources, + IsDir, + OperationAborted, + BrokenPipe, + ConnectionResetByPeer, + ConnectionTimedOut, + NotOpenForReading, + SocketNotConnected, + + /// This error occurs when no global event loop is configured, + /// and reading from the file descriptor would block. + WouldBlock, + + /// In WASI, this error occurs when the file descriptor does + /// not hold the required rights to read from it. + AccessDenied, +} || UnexpectedError; + +/// Returns the number of bytes that were read, which can be less than +/// buf.len. If 0 bytes were read, that means EOF. +/// If `fd` is opened in non blocking mode, the function will return error.WouldBlock +/// when EAGAIN is received. +/// +/// Linux has a limit on how many bytes may be transferred in one `read` 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 `read` 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 read(fd: fd_t, buf: []u8) ReadError!usize { + if (buf.len == 0) return 0; + if (native_os == .windows) { + return windows.ReadFile(fd, buf, null); + } + if (native_os == .wasi and !builtin.link_libc) { + const iovs = [1]iovec{iovec{ + .iov_base = buf.ptr, + .iov_len = buf.len, + }}; + + var nread: usize = undefined; + switch (wasi.fd_read(fd, &iovs, iovs.len, &nread)) { + .SUCCESS => return nread, + .INTR => unreachable, + .INVAL => unreachable, + .FAULT => unreachable, + .AGAIN => unreachable, + .BADF => return error.NotOpenForReading, // Can be a race condition. + .IO => return error.InputOutput, + .ISDIR => return error.IsDir, + .NOBUFS => return error.SystemResources, + .NOMEM => return error.SystemResources, + .NOTCONN => return error.SocketNotConnected, + .CONNRESET => return error.ConnectionResetByPeer, + .TIMEDOUT => return error.ConnectionTimedOut, + .NOTCAPABLE => return error.AccessDenied, + else => |err| return unexpectedErrno(err), + } + } + + // Prevents EINVAL. + const max_count = switch (native_os) { + .linux => 0x7ffff000, + .macos, .ios, .watchos, .tvos => maxInt(i32), + else => maxInt(isize), + }; + while (true) { + const rc = system.read(fd, buf.ptr, @min(buf.len, max_count)); + switch (errno(rc)) { + .SUCCESS => return @intCast(rc), + .INTR => continue, + .INVAL => unreachable, + .FAULT => unreachable, + .AGAIN => return error.WouldBlock, + .BADF => return error.NotOpenForReading, // Can be a race condition. + .IO => return error.InputOutput, + .ISDIR => return error.IsDir, + .NOBUFS => return error.SystemResources, + .NOMEM => return error.SystemResources, + .NOTCONN => return error.SocketNotConnected, + .CONNRESET => return error.ConnectionResetByPeer, + .TIMEDOUT => return error.ConnectionTimedOut, + else => |err| return unexpectedErrno(err), + } + } +} + +/// Number of bytes read is returned. Upon reading end-of-file, zero is returned. +/// +/// 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. +/// +/// This operation is non-atomic on the following systems: +/// * Windows +/// On these systems, the read races with concurrent writes to the same file descriptor. +/// +/// This function assumes that all vectors, including zero-length vectors, have +/// a pointer within the address space of the application. +pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize { + if (native_os == .windows) { + // TODO improve this to use ReadFileScatter + if (iov.len == 0) return 0; + const first = iov[0]; + return read(fd, first.iov_base[0..first.iov_len]); + } + if (native_os == .wasi and !builtin.link_libc) { + var nread: usize = undefined; + switch (wasi.fd_read(fd, iov.ptr, iov.len, &nread)) { + .SUCCESS => return nread, + .INTR => unreachable, + .INVAL => unreachable, + .FAULT => unreachable, + .AGAIN => unreachable, // currently not support in WASI + .BADF => return error.NotOpenForReading, // can be a race condition + .IO => return error.InputOutput, + .ISDIR => return error.IsDir, + .NOBUFS => return error.SystemResources, + .NOMEM => return error.SystemResources, + .NOTCONN => return error.SocketNotConnected, + .CONNRESET => return error.ConnectionResetByPeer, + .TIMEDOUT => return error.ConnectionTimedOut, + .NOTCAPABLE => return error.AccessDenied, + else => |err| return unexpectedErrno(err), + } + } + + while (true) { + const rc = system.readv(fd, iov.ptr, @min(iov.len, IOV_MAX)); + switch (errno(rc)) { + .SUCCESS => return @intCast(rc), + .INTR => continue, + .INVAL => unreachable, + .FAULT => unreachable, + .AGAIN => return error.WouldBlock, + .BADF => return error.NotOpenForReading, // can be a race condition + .IO => return error.InputOutput, + .ISDIR => return error.IsDir, + .NOBUFS => return error.SystemResources, + .NOMEM => return error.SystemResources, + .NOTCONN => return error.SocketNotConnected, + .CONNRESET => return error.ConnectionResetByPeer, + .TIMEDOUT => return error.ConnectionTimedOut, + else => |err| return unexpectedErrno(err), + } + } +} + +pub const PReadError = ReadError || error{Unseekable}; + +/// Number of bytes read is returned. Upon reading end-of-file, zero is returned. +/// +/// Retries when interrupted by a signal. +/// +/// 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 `pread` 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 `read` 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 pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize { + if (buf.len == 0) return 0; + if (native_os == .windows) { + return windows.ReadFile(fd, buf, offset); + } + if (native_os == .wasi and !builtin.link_libc) { + const iovs = [1]iovec{iovec{ + .iov_base = buf.ptr, + .iov_len = buf.len, + }}; + + var nread: usize = undefined; + switch (wasi.fd_pread(fd, &iovs, iovs.len, offset, &nread)) { + .SUCCESS => return nread, + .INTR => unreachable, + .INVAL => unreachable, + .FAULT => unreachable, + .AGAIN => unreachable, + .BADF => return error.NotOpenForReading, // Can be a race condition. + .IO => return error.InputOutput, + .ISDIR => return error.IsDir, + .NOBUFS => return error.SystemResources, + .NOMEM => return error.SystemResources, + .NOTCONN => return error.SocketNotConnected, + .CONNRESET => return error.ConnectionResetByPeer, + .TIMEDOUT => return error.ConnectionTimedOut, + .NXIO => return error.Unseekable, + .SPIPE => return error.Unseekable, + .OVERFLOW => return error.Unseekable, + .NOTCAPABLE => return error.AccessDenied, + else => |err| return unexpectedErrno(err), + } + } + + // Prevent EINVAL. + const max_count = switch (native_os) { + .linux => 0x7ffff000, + .macos, .ios, .watchos, .tvos => maxInt(i32), + else => maxInt(isize), + }; + + const pread_sym = if (lfs64_abi) system.pread64 else system.pread; + while (true) { + const rc = pread_sym(fd, buf.ptr, @min(buf.len, max_count), @bitCast(offset)); + switch (errno(rc)) { + .SUCCESS => return @intCast(rc), + .INTR => continue, + .INVAL => unreachable, + .FAULT => unreachable, + .AGAIN => return error.WouldBlock, + .BADF => return error.NotOpenForReading, // Can be a race condition. + .IO => return error.InputOutput, + .ISDIR => return error.IsDir, + .NOBUFS => return error.SystemResources, + .NOMEM => return error.SystemResources, + .NOTCONN => return error.SocketNotConnected, + .CONNRESET => return error.ConnectionResetByPeer, + .TIMEDOUT => return error.ConnectionTimedOut, + .NXIO => return error.Unseekable, + .SPIPE => return error.Unseekable, + .OVERFLOW => return error.Unseekable, + else => |err| return unexpectedErrno(err), + } + } +} + +pub const TruncateError = error{ + FileTooBig, + InputOutput, + FileBusy, + + /// In WASI, this error occurs when the file descriptor does + /// not hold the required rights to call `ftruncate` on it. + AccessDenied, +} || UnexpectedError; + +pub fn ftruncate(fd: fd_t, length: u64) TruncateError!void { + if (native_os == .windows) { + var io_status_block: windows.IO_STATUS_BLOCK = undefined; + var eof_info = windows.FILE_END_OF_FILE_INFORMATION{ + .EndOfFile = @bitCast(length), + }; + + const rc = windows.ntdll.NtSetInformationFile( + fd, + &io_status_block, + &eof_info, + @sizeOf(windows.FILE_END_OF_FILE_INFORMATION), + .FileEndOfFileInformation, + ); + + switch (rc) { + .SUCCESS => return, + .INVALID_HANDLE => unreachable, // Handle not open for writing + .ACCESS_DENIED => return error.AccessDenied, + else => return windows.unexpectedStatus(rc), + } + } + if (native_os == .wasi and !builtin.link_libc) { + switch (wasi.fd_filestat_set_size(fd, length)) { + .SUCCESS => return, + .INTR => unreachable, + .FBIG => return error.FileTooBig, + .IO => return error.InputOutput, + .PERM => return error.AccessDenied, + .TXTBSY => return error.FileBusy, + .BADF => unreachable, // Handle not open for writing + .INVAL => unreachable, // Handle not open for writing + .NOTCAPABLE => return error.AccessDenied, + else => |err| return unexpectedErrno(err), + } + } + + const ftruncate_sym = if (lfs64_abi) system.ftruncate64 else system.ftruncate; + while (true) { + switch (errno(ftruncate_sym(fd, @bitCast(length)))) { + .SUCCESS => return, + .INTR => continue, + .FBIG => return error.FileTooBig, + .IO => return error.InputOutput, + .PERM => return error.AccessDenied, + .TXTBSY => return error.FileBusy, + .BADF => unreachable, // Handle not open for writing + .INVAL => unreachable, // Handle not open for writing + else => |err| return unexpectedErrno(err), + } + } +} + +/// Number of bytes read is returned. Upon reading end-of-file, zero is returned. +/// +/// Retries when interrupted by a signal. +/// +/// 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. +/// +/// This operation is non-atomic on the following systems: +/// * Darwin +/// * Windows +/// On these systems, the read races with concurrent writes to the same file descriptor. +pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) PReadError!usize { + const have_pread_but_not_preadv = switch (native_os) { + .windows, .macos, .ios, .watchos, .tvos, .haiku => true, + else => false, + }; + if (have_pread_but_not_preadv) { + // We could loop here; but proper usage of `preadv` must handle partial reads anyway. + // So we simply read into the first vector only. + if (iov.len == 0) return 0; + const first = iov[0]; + return pread(fd, first.iov_base[0..first.iov_len], offset); + } + if (native_os == .wasi and !builtin.link_libc) { + var nread: usize = undefined; + switch (wasi.fd_pread(fd, iov.ptr, iov.len, offset, &nread)) { + .SUCCESS => return nread, + .INTR => unreachable, + .INVAL => unreachable, + .FAULT => unreachable, + .AGAIN => unreachable, + .BADF => return error.NotOpenForReading, // can be a race condition + .IO => return error.InputOutput, + .ISDIR => return error.IsDir, + .NOBUFS => return error.SystemResources, + .NOMEM => return error.SystemResources, + .NOTCONN => return error.SocketNotConnected, + .CONNRESET => return error.ConnectionResetByPeer, + .TIMEDOUT => return error.ConnectionTimedOut, + .NXIO => return error.Unseekable, + .SPIPE => return error.Unseekable, + .OVERFLOW => return error.Unseekable, + .NOTCAPABLE => return error.AccessDenied, + else => |err| return unexpectedErrno(err), + } + } + + const preadv_sym = if (lfs64_abi) system.preadv64 else system.preadv; + while (true) { + const rc = preadv_sym(fd, iov.ptr, @min(iov.len, IOV_MAX), @bitCast(offset)); + switch (errno(rc)) { + .SUCCESS => return @bitCast(rc), + .INTR => continue, + .INVAL => unreachable, + .FAULT => unreachable, + .AGAIN => return error.WouldBlock, + .BADF => return error.NotOpenForReading, // can be a race condition + .IO => return error.InputOutput, + .ISDIR => return error.IsDir, + .NOBUFS => return error.SystemResources, + .NOMEM => return error.SystemResources, + .NOTCONN => return error.SocketNotConnected, + .CONNRESET => return error.ConnectionResetByPeer, + .TIMEDOUT => return error.ConnectionTimedOut, + .NXIO => return error.Unseekable, + .SPIPE => return error.Unseekable, + .OVERFLOW => return error.Unseekable, + else => |err| return unexpectedErrno(err), + } + } +} + +pub const WriteError = error{ + DiskQuota, + FileTooBig, + InputOutput, + NoSpaceLeft, + DeviceBusy, + InvalidArgument, + + /// In WASI, this error may occur when the file descriptor does + /// not hold the required rights to write to it. + AccessDenied, + BrokenPipe, + SystemResources, + OperationAborted, + 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, +} || 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) { + return windows.WriteFile(fd, bytes, null); + } + + if (native_os == .wasi and !builtin.link_libc) { + const ciovs = [_]iovec_const{iovec_const{ + .iov_base = bytes.ptr, + .iov_len = bytes.len, + }}; + var nwritten: usize = undefined; + switch (wasi.fd_write(fd, &ciovs, ciovs.len, &nwritten)) { + .SUCCESS => return nwritten, + .INTR => unreachable, + .INVAL => unreachable, + .FAULT => unreachable, + .AGAIN => unreachable, + .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, + .PERM => return error.AccessDenied, + .PIPE => return error.BrokenPipe, + .NOTCAPABLE => return error.AccessDenied, + else => |err| return unexpectedErrno(err), + } + } + + const max_count = switch (native_os) { + .linux => 0x7ffff000, + .macos, .ios, .watchos, .tvos => 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, + .PERM => return error.AccessDenied, + .PIPE => return error.BrokenPipe, + .CONNRESET => return error.ConnectionResetByPeer, + .BUSY => return error.DeviceBusy, + else => |err| return unexpectedErrno(err), + } + } +} + +/// Write multiple buffers 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 bytes than supplied. 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. +/// +/// If `iov.len` is larger than `IOV_MAX`, a partial write will occur. +/// +/// This function assumes that all vectors, including zero-length vectors, have +/// a pointer within the address space of the application. +pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!usize { + if (native_os == .windows) { + // TODO improve this to use WriteFileScatter + if (iov.len == 0) return 0; + const first = iov[0]; + return write(fd, first.iov_base[0..first.iov_len]); + } + if (native_os == .wasi and !builtin.link_libc) { + var nwritten: usize = undefined; + switch (wasi.fd_write(fd, iov.ptr, iov.len, &nwritten)) { + .SUCCESS => return nwritten, + .INTR => unreachable, + .INVAL => unreachable, + .FAULT => unreachable, + .AGAIN => unreachable, + .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, + .PERM => return error.AccessDenied, + .PIPE => return error.BrokenPipe, + .NOTCAPABLE => return error.AccessDenied, + else => |err| return unexpectedErrno(err), + } + } + + while (true) { + const rc = system.writev(fd, iov.ptr, @min(iov.len, IOV_MAX)); + 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, + .PERM => return error.AccessDenied, + .PIPE => return error.BrokenPipe, + .CONNRESET => return error.ConnectionResetByPeer, + .BUSY => return error.DeviceBusy, + else => |err| return unexpectedErrno(err), + } + } +} + +pub const PWriteError = WriteError || error{Unseekable}; + +/// Write to a file descriptor, with a position offset. +/// 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 bytes than supplied. 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 `pwrite` 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 write more than that returns EINVAL. +/// The corresponding POSIX limit is `maxInt(isize)`. +pub fn pwrite(fd: fd_t, bytes: []const u8, offset: u64) PWriteError!usize { + if (bytes.len == 0) return 0; + if (native_os == .windows) { + return windows.WriteFile(fd, bytes, offset); + } + if (native_os == .wasi and !builtin.link_libc) { + const ciovs = [1]iovec_const{iovec_const{ + .iov_base = bytes.ptr, + .iov_len = bytes.len, + }}; + + var nwritten: usize = undefined; + switch (wasi.fd_pwrite(fd, &ciovs, ciovs.len, offset, &nwritten)) { + .SUCCESS => return nwritten, + .INTR => unreachable, + .INVAL => unreachable, + .FAULT => unreachable, + .AGAIN => unreachable, + .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, + .PERM => return error.AccessDenied, + .PIPE => return error.BrokenPipe, + .NXIO => return error.Unseekable, + .SPIPE => return error.Unseekable, + .OVERFLOW => return error.Unseekable, + .NOTCAPABLE => return error.AccessDenied, + else => |err| return unexpectedErrno(err), + } + } + + // Prevent EINVAL. + const max_count = switch (native_os) { + .linux => 0x7ffff000, + .macos, .ios, .watchos, .tvos => maxInt(i32), + else => maxInt(isize), + }; + + const pwrite_sym = if (lfs64_abi) system.pwrite64 else system.pwrite; + while (true) { + const rc = pwrite_sym(fd, bytes.ptr, @min(bytes.len, max_count), @bitCast(offset)); + 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, + .PERM => return error.AccessDenied, + .PIPE => return error.BrokenPipe, + .NXIO => return error.Unseekable, + .SPIPE => return error.Unseekable, + .OVERFLOW => return error.Unseekable, + .BUSY => return error.DeviceBusy, + else => |err| return unexpectedErrno(err), + } + } +} + +/// Write multiple buffers to a file descriptor, with a position offset. +/// 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). +/// +/// If `fd` is opened in non blocking mode, the function will +/// return error.WouldBlock when EAGAIN is received. +/// +/// The following systems do not have this syscall, and will return partial writes if more than one +/// vector is provided: +/// * Darwin +/// * Windows +/// +/// If `iov.len` is larger than `IOV_MAX`, a partial write will occur. +pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) PWriteError!usize { + const have_pwrite_but_not_pwritev = switch (native_os) { + .windows, .macos, .ios, .watchos, .tvos, .haiku => true, + else => false, + }; + + if (have_pwrite_but_not_pwritev) { + // We could loop here; but proper usage of `pwritev` must handle partial writes anyway. + // So we simply write the first vector only. + if (iov.len == 0) return 0; + const first = iov[0]; + return pwrite(fd, first.iov_base[0..first.iov_len], offset); + } + if (native_os == .wasi and !builtin.link_libc) { + var nwritten: usize = undefined; + switch (wasi.fd_pwrite(fd, iov.ptr, iov.len, offset, &nwritten)) { + .SUCCESS => return nwritten, + .INTR => unreachable, + .INVAL => unreachable, + .FAULT => unreachable, + .AGAIN => unreachable, + .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, + .PERM => return error.AccessDenied, + .PIPE => return error.BrokenPipe, + .NXIO => return error.Unseekable, + .SPIPE => return error.Unseekable, + .OVERFLOW => return error.Unseekable, + .NOTCAPABLE => return error.AccessDenied, + else => |err| return unexpectedErrno(err), + } + } + + const pwritev_sym = if (lfs64_abi) system.pwritev64 else system.pwritev; + while (true) { + const rc = pwritev_sym(fd, iov.ptr, @min(iov.len, IOV_MAX), @bitCast(offset)); + 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, + .PERM => return error.AccessDenied, + .PIPE => return error.BrokenPipe, + .NXIO => return error.Unseekable, + .SPIPE => return error.Unseekable, + .OVERFLOW => return error.Unseekable, + .BUSY => return error.DeviceBusy, + else => |err| return unexpectedErrno(err), + } + } +} + +pub const OpenError = error{ + /// In WASI, this error may occur when the file descriptor does + /// not hold the required rights to open a new resource relative to it. + AccessDenied, + SymLinkLoop, + ProcessFdQuotaExceeded, + SystemFdQuotaExceeded, + NoDevice, + FileNotFound, + + /// The path exceeded `max_path_bytes` bytes. + NameTooLong, + + /// Insufficient kernel memory was available, or + /// the named file is a FIFO and per-user hard limit on + /// memory allocation for pipes has been reached. + SystemResources, + + /// The file is too large to be opened. This error is unreachable + /// for 64-bit targets, as well as when opening directories. + FileTooBig, + + /// The path refers to directory but the `DIRECTORY` flag was not provided. + IsDir, + + /// A new path cannot be created because the device has no room for the new file. + /// This error is only reachable when the `CREAT` flag is provided. + NoSpaceLeft, + + /// A component used as a directory in the path was not, in fact, a directory, or + /// `DIRECTORY` was specified and the path was not a directory. + NotDir, + + /// The path already exists and the `CREAT` and `EXCL` flags were provided. + PathAlreadyExists, + DeviceBusy, + + /// The underlying filesystem does not support file locks + FileLocksNotSupported, + + /// Path contains characters that are disallowed by the underlying filesystem. + BadPathName, + + /// WASI-only; file paths must be valid UTF-8. + InvalidUtf8, + + /// Windows-only; file paths provided by the user must be valid WTF-8. + /// https://simonsapin.github.io/wtf-8/ + InvalidWtf8, + + /// On Windows, `\\server` or `\\server\share` was not found. + NetworkNotFound, + + /// One of these three things: + /// * pathname refers to an executable image which is currently being + /// executed and write access was requested. + /// * pathname refers to a file that is currently in use as a swap + /// file, and the O_TRUNC flag was specified. + /// * pathname refers to a file that is currently being read by the + /// kernel (e.g., for module/firmware loading), and write access was + /// requested. + FileBusy, + + WouldBlock, +} || UnexpectedError; + +/// Open and possibly create a file. Keeps trying if it gets interrupted. +/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// 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://simonsapin.github.io/wtf-8/). +/// 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 => unreachable, + .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, + .NOMEM => return error.SystemResources, + .NOSPC => return error.NoSpaceLeft, + .NOTDIR => return error.NotDir, + .PERM => return error.AccessDenied, + .EXIST => return error.PathAlreadyExists, + .BUSY => return error.DeviceBusy, + 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://simonsapin.github.io/wtf-8/). +/// 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 `openatZ`. +pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: O, mode: 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) { + // `mode` is ignored on WASI, which does not support unix-style file permissions + const opts = try openOptionsFromFlagsWasi(flags); + const fd = try openatWasi( + dir_fd, + file_path, + opts.lookup_flags, + opts.oflags, + opts.fs_flags, + opts.fs_rights_base, + opts.fs_rights_inheriting, + ); + errdefer close(fd); + + if (flags.write) { + const info = try fstat_wasi(fd); + if (info.filetype == .DIRECTORY) + return error.IsDir; + } + + return fd; + } + const file_path_c = try toPosixPath(file_path); + return openatZ(dir_fd, &file_path_c, flags, mode); +} + +/// Open and possibly create a file in WASI. +pub fn openatWasi( + dir_fd: fd_t, + file_path: []const u8, + lookup_flags: wasi.lookupflags_t, + oflags: wasi.oflags_t, + fdflags: wasi.fdflags_t, + base: wasi.rights_t, + inheriting: wasi.rights_t, +) OpenError!fd_t { + while (true) { + var fd: fd_t = undefined; + switch (wasi.path_open(dir_fd, lookup_flags, file_path.ptr, file_path.len, oflags, base, inheriting, fdflags, &fd)) { + .SUCCESS => return fd, + .INTR => continue, + + .FAULT => unreachable, + .INVAL => unreachable, + .BADF => unreachable, + .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, + .NOMEM => return error.SystemResources, + .NOSPC => return error.NoSpaceLeft, + .NOTDIR => return error.NotDir, + .PERM => return error.AccessDenied, + .EXIST => return error.PathAlreadyExists, + .BUSY => return error.DeviceBusy, + .NOTCAPABLE => return error.AccessDenied, + .ILSEQ => return error.InvalidUtf8, + else => |err| return unexpectedErrno(err), + } + } +} + +/// A struct to contain all lookup/rights flags accepted by `wasi.path_open` +const WasiOpenOptions = struct { + oflags: wasi.oflags_t, + lookup_flags: wasi.lookupflags_t, + fs_rights_base: wasi.rights_t, + fs_rights_inheriting: wasi.rights_t, + fs_flags: wasi.fdflags_t, +}; + +/// Compute rights + flags corresponding to the provided POSIX access mode. +fn openOptionsFromFlagsWasi(oflag: O) OpenError!WasiOpenOptions { + const w = std.os.wasi; + + // Next, calculate the read/write rights to request, depending on the + // provided POSIX access mode + var rights: w.rights_t = .{}; + if (oflag.read) { + rights.FD_READ = true; + rights.FD_READDIR = true; + } + if (oflag.write) { + rights.FD_DATASYNC = true; + rights.FD_WRITE = true; + rights.FD_ALLOCATE = true; + rights.FD_FILESTAT_SET_SIZE = true; + } + + // https://github.com/ziglang/zig/issues/18882 + const flag_bits: u32 = @bitCast(oflag); + const oflags_int: u16 = @as(u12, @truncate(flag_bits >> 12)); + const fs_flags_int: u16 = @as(u12, @truncate(flag_bits)); + + return .{ + // https://github.com/ziglang/zig/issues/18882 + .oflags = @bitCast(oflags_int), + .lookup_flags = .{ + .SYMLINK_FOLLOW = !oflag.NOFOLLOW, + }, + .fs_rights_base = rights, + .fs_rights_inheriting = rights, + // https://github.com/ziglang/zig/issues/18882 + .fs_flags = @bitCast(fs_flags_int), + }; +} + +/// 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://simonsapin.github.io/wtf-8/). +/// 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 `openat`. +pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: O, mode: 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(dir_fd, mem.sliceTo(file_path, 0), flags, mode); + } + + const openat_sym = if (lfs64_abi) system.openat64 else system.openat; + while (true) { + const rc = openat_sym(dir_fd, file_path, flags, mode); + switch (errno(rc)) { + .SUCCESS => return @intCast(rc), + .INTR => continue, + + .FAULT => unreachable, + .INVAL => unreachable, + .BADF => unreachable, + .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, + .NOMEM => return error.SystemResources, + .NOSPC => return error.NoSpaceLeft, + .NOTDIR => return error.NotDir, + .PERM => return error.AccessDenied, + .EXIST => return error.PathAlreadyExists, + .BUSY => return error.DeviceBusy, + .OPNOTSUPP => return error.FileLocksNotSupported, + .AGAIN => return error.WouldBlock, + .TXTBSY => return error.FileBusy, + else => |err| return unexpectedErrno(err), + } + } +} + +pub fn dup(old_fd: fd_t) !fd_t { + const rc = system.dup(old_fd); + return switch (errno(rc)) { + .SUCCESS => return @intCast(rc), + .MFILE => error.ProcessFdQuotaExceeded, + .BADF => unreachable, // invalid file descriptor + else => |err| return unexpectedErrno(err), + }; +} + +pub fn dup2(old_fd: fd_t, new_fd: fd_t) !void { + while (true) { + switch (errno(system.dup2(old_fd, new_fd))) { + .SUCCESS => return, + .BUSY, .INTR => continue, + .MFILE => return error.ProcessFdQuotaExceeded, + .INVAL => unreachable, // invalid parameters passed to dup2 + .BADF => unreachable, // invalid file descriptor + else => |err| return unexpectedErrno(err), + } + } +} + +pub const ExecveError = error{ + SystemResources, + AccessDenied, + InvalidExe, + FileSystem, + IsDir, + FileNotFound, + NotDir, + FileBusy, + ProcessFdQuotaExceeded, + SystemFdQuotaExceeded, + NameTooLong, +} || UnexpectedError; + +/// This function ignores PATH environment variable. See `execvpeZ` for that. +pub fn execveZ( + path: [*:0]const u8, + child_argv: [*:null]const ?[*:0]const u8, + envp: [*:null]const ?[*:0]const u8, +) ExecveError { + switch (errno(system.execve(path, child_argv, envp))) { + .SUCCESS => unreachable, + .FAULT => unreachable, + .@"2BIG" => return error.SystemResources, + .MFILE => return error.ProcessFdQuotaExceeded, + .NAMETOOLONG => return error.NameTooLong, + .NFILE => return error.SystemFdQuotaExceeded, + .NOMEM => return error.SystemResources, + .ACCES => return error.AccessDenied, + .PERM => return error.AccessDenied, + .INVAL => return error.InvalidExe, + .NOEXEC => return error.InvalidExe, + .IO => return error.FileSystem, + .LOOP => return error.FileSystem, + .ISDIR => return error.IsDir, + .NOENT => return error.FileNotFound, + .NOTDIR => return error.NotDir, + .TXTBSY => return error.FileBusy, + else => |err| switch (native_os) { + .macos, .ios, .tvos, .watchos => switch (err) { + .BADEXEC => return error.InvalidExe, + .BADARCH => return error.InvalidExe, + else => return unexpectedErrno(err), + }, + .linux => switch (err) { + .LIBBAD => return error.InvalidExe, + else => return unexpectedErrno(err), + }, + else => return unexpectedErrno(err), + }, + } +} + +pub const Arg0Expand = enum { + expand, + no_expand, +}; + +/// Like `execvpeZ` except if `arg0_expand` is `.expand`, then `argv` is mutable, +/// and `argv[0]` is expanded to be the same absolute path that is passed to the execve syscall. +/// If this function returns with an error, `argv[0]` will be restored to the value it was when it was passed in. +pub fn execvpeZ_expandArg0( + comptime arg0_expand: Arg0Expand, + file: [*:0]const u8, + child_argv: switch (arg0_expand) { + .expand => [*:null]?[*:0]const u8, + .no_expand => [*:null]const ?[*:0]const u8, + }, + envp: [*:null]const ?[*:0]const u8, +) ExecveError { + const file_slice = mem.sliceTo(file, 0); + if (mem.indexOfScalar(u8, file_slice, '/') != null) return execveZ(file, child_argv, envp); + + const PATH = getenvZ("PATH") orelse "/usr/local/bin:/bin/:/usr/bin"; + // Use of PATH_MAX here is valid as the path_buf will be passed + // directly to the operating system in execveZ. + var path_buf: [PATH_MAX]u8 = undefined; + var it = mem.tokenizeScalar(u8, PATH, ':'); + var seen_eacces = false; + var err: ExecveError = error.FileNotFound; + + // In case of expanding arg0 we must put it back if we return with an error. + const prev_arg0 = child_argv[0]; + defer switch (arg0_expand) { + .expand => child_argv[0] = prev_arg0, + .no_expand => {}, + }; + + while (it.next()) |search_path| { + const path_len = search_path.len + file_slice.len + 1; + if (path_buf.len < path_len + 1) return error.NameTooLong; + @memcpy(path_buf[0..search_path.len], search_path); + path_buf[search_path.len] = '/'; + @memcpy(path_buf[search_path.len + 1 ..][0..file_slice.len], file_slice); + path_buf[path_len] = 0; + const full_path = path_buf[0..path_len :0].ptr; + switch (arg0_expand) { + .expand => child_argv[0] = full_path, + .no_expand => {}, + } + err = execveZ(full_path, child_argv, envp); + switch (err) { + error.AccessDenied => seen_eacces = true, + error.FileNotFound, error.NotDir => {}, + else => |e| return e, + } + } + if (seen_eacces) return error.AccessDenied; + return err; +} + +/// This function also uses the PATH environment variable to get the full path to the executable. +/// If `file` is an absolute path, this is the same as `execveZ`. +pub fn execvpeZ( + file: [*:0]const u8, + argv_ptr: [*:null]const ?[*:0]const u8, + envp: [*:null]const ?[*:0]const u8, +) ExecveError { + return execvpeZ_expandArg0(.no_expand, file, argv_ptr, envp); +} + +/// Get an environment variable. +/// See also `getenvZ`. +pub fn getenv(key: []const u8) ?[:0]const u8 { + if (native_os == .windows) { + @compileError("std.os.getenv is unavailable for Windows because environment strings are in WTF-16 format. See std.process.getEnvVarOwned for a cross-platform API or std.process.getenvW for a Windows-specific API."); + } + if (builtin.link_libc) { + var ptr = std.c.environ; + while (ptr[0]) |line| : (ptr += 1) { + var line_i: usize = 0; + while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {} + const this_key = line[0..line_i]; + + if (!mem.eql(u8, this_key, key)) continue; + + return mem.sliceTo(line + line_i + 1, 0); + } + return null; + } + if (native_os == .wasi) { + @compileError("std.os.getenv is unavailable for WASI. See std.process.getEnvMap or std.process.getEnvVarOwned for a cross-platform API."); + } + // The simplified start logic doesn't populate environ. + if (std.start.simplified_logic) return null; + // TODO see https://github.com/ziglang/zig/issues/4524 + for (std.os.environ) |ptr| { + var line_i: usize = 0; + while (ptr[line_i] != 0 and ptr[line_i] != '=') : (line_i += 1) {} + const this_key = ptr[0..line_i]; + if (!mem.eql(u8, key, this_key)) continue; + + return mem.sliceTo(ptr + line_i + 1, 0); + } + return null; +} + +/// Get an environment variable with a null-terminated name. +/// See also `getenv`. +pub fn getenvZ(key: [*:0]const u8) ?[:0]const u8 { + if (builtin.link_libc) { + const value = system.getenv(key) orelse return null; + return mem.sliceTo(value, 0); + } + if (native_os == .windows) { + @compileError("std.os.getenvZ is unavailable for Windows because environment string is in WTF-16 format. See std.process.getEnvVarOwned for cross-platform API or std.process.getenvW for Windows-specific API."); + } + return getenv(mem.sliceTo(key, 0)); +} + +pub const GetCwdError = error{ + NameTooLong, + CurrentWorkingDirectoryUnlinked, +} || UnexpectedError; + +/// The result is a slice of out_buffer, indexed from 0. +pub fn getcwd(out_buffer: []u8) GetCwdError![]u8 { + if (native_os == .windows) { + return windows.GetCurrentDirectory(out_buffer); + } else if (native_os == .wasi and !builtin.link_libc) { + const path = "."; + if (out_buffer.len < path.len) return error.NameTooLong; + const result = out_buffer[0..path.len]; + @memcpy(result, path); + return result; + } + + const err: E = if (builtin.link_libc) err: { + const c_err = if (std.c.getcwd(out_buffer.ptr, out_buffer.len)) |_| 0 else std.c._errno().*; + break :err @enumFromInt(c_err); + } else err: { + break :err errno(system.getcwd(out_buffer.ptr, out_buffer.len)); + }; + switch (err) { + .SUCCESS => return mem.sliceTo(out_buffer, 0), + .FAULT => unreachable, + .INVAL => unreachable, + .NOENT => return error.CurrentWorkingDirectoryUnlinked, + .RANGE => return error.NameTooLong, + else => return unexpectedErrno(err), + } +} + +pub const SymLinkError = error{ + /// In WASI, this error may occur when the file descriptor does + /// not hold the required rights to create a new symbolic link relative to it. + AccessDenied, + DiskQuota, + PathAlreadyExists, + FileSystem, + SymLinkLoop, + FileNotFound, + SystemResources, + NoSpaceLeft, + ReadOnlyFileSystem, + NotDir, + NameTooLong, + + /// WASI-only; file paths must be valid UTF-8. + InvalidUtf8, + + /// Windows-only; file paths provided by the user must be valid WTF-8. + /// https://simonsapin.github.io/wtf-8/ + InvalidWtf8, + + BadPathName, +} || UnexpectedError; + +/// Creates a symbolic link named `sym_link_path` which contains the string `target_path`. +/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent +/// one; the latter case is known as a dangling link. +/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On WASI, both paths should be encoded as valid UTF-8. +/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. +/// If `sym_link_path` exists, it will not be overwritten. +/// See also `symlinkZ. +pub fn symlink(target_path: []const u8, sym_link_path: []const u8) SymLinkError!void { + if (native_os == .windows) { + @compileError("symlink is not supported on Windows; use std.os.windows.CreateSymbolicLink instead"); + } else if (native_os == .wasi and !builtin.link_libc) { + return symlinkat(target_path, wasi.AT.FDCWD, sym_link_path); + } + const target_path_c = try toPosixPath(target_path); + const sym_link_path_c = try toPosixPath(sym_link_path); + return symlinkZ(&target_path_c, &sym_link_path_c); +} + +/// This is the same as `symlink` except the parameters are null-terminated pointers. +/// See also `symlink`. +pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLinkError!void { + if (native_os == .windows) { + @compileError("symlink is not supported on Windows; use std.os.windows.CreateSymbolicLink instead"); + } else if (native_os == .wasi and !builtin.link_libc) { + return symlinkatZ(target_path, fs.cwd().fd, sym_link_path); + } + switch (errno(system.symlink(target_path, sym_link_path))) { + .SUCCESS => return, + .FAULT => unreachable, + .INVAL => unreachable, + .ACCES => return error.AccessDenied, + .PERM => return error.AccessDenied, + .DQUOT => return error.DiskQuota, + .EXIST => return error.PathAlreadyExists, + .IO => return error.FileSystem, + .LOOP => return error.SymLinkLoop, + .NAMETOOLONG => return error.NameTooLong, + .NOENT => return error.FileNotFound, + .NOTDIR => return error.NotDir, + .NOMEM => return error.SystemResources, + .NOSPC => return error.NoSpaceLeft, + .ROFS => return error.ReadOnlyFileSystem, + .ILSEQ => |err| if (native_os == .wasi) + return error.InvalidUtf8 + else + return unexpectedErrno(err), + else => |err| return unexpectedErrno(err), + } +} + +/// Similar to `symlink`, however, creates a symbolic link named `sym_link_path` which contains the string +/// `target_path` **relative** to `newdirfd` directory handle. +/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent +/// one; the latter case is known as a dangling link. +/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On WASI, both paths should be encoded as valid UTF-8. +/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. +/// If `sym_link_path` exists, it will not be overwritten. +/// See also `symlinkatWasi`, `symlinkatZ` and `symlinkatW`. +pub fn symlinkat(target_path: []const u8, newdirfd: fd_t, sym_link_path: []const u8) SymLinkError!void { + if (native_os == .windows) { + @compileError("symlinkat is not supported on Windows; use std.os.windows.CreateSymbolicLink instead"); + } else if (native_os == .wasi and !builtin.link_libc) { + return symlinkatWasi(target_path, newdirfd, sym_link_path); + } + const target_path_c = try toPosixPath(target_path); + const sym_link_path_c = try toPosixPath(sym_link_path); + return symlinkatZ(&target_path_c, newdirfd, &sym_link_path_c); +} + +/// WASI-only. The same as `symlinkat` but targeting WASI. +/// See also `symlinkat`. +pub fn symlinkatWasi(target_path: []const u8, newdirfd: fd_t, sym_link_path: []const u8) SymLinkError!void { + switch (wasi.path_symlink(target_path.ptr, target_path.len, newdirfd, sym_link_path.ptr, sym_link_path.len)) { + .SUCCESS => {}, + .FAULT => unreachable, + .INVAL => unreachable, + .BADF => unreachable, + .ACCES => return error.AccessDenied, + .PERM => return error.AccessDenied, + .DQUOT => return error.DiskQuota, + .EXIST => return error.PathAlreadyExists, + .IO => return error.FileSystem, + .LOOP => return error.SymLinkLoop, + .NAMETOOLONG => return error.NameTooLong, + .NOENT => return error.FileNotFound, + .NOTDIR => return error.NotDir, + .NOMEM => return error.SystemResources, + .NOSPC => return error.NoSpaceLeft, + .ROFS => return error.ReadOnlyFileSystem, + .NOTCAPABLE => return error.AccessDenied, + .ILSEQ => return error.InvalidUtf8, + else => |err| return unexpectedErrno(err), + } +} + +/// The same as `symlinkat` except the parameters are null-terminated pointers. +/// See also `symlinkat`. +pub fn symlinkatZ(target_path: [*:0]const u8, newdirfd: fd_t, sym_link_path: [*:0]const u8) SymLinkError!void { + if (native_os == .windows) { + @compileError("symlinkat is not supported on Windows; use std.os.windows.CreateSymbolicLink instead"); + } else if (native_os == .wasi and !builtin.link_libc) { + return symlinkat(mem.sliceTo(target_path, 0), newdirfd, mem.sliceTo(sym_link_path, 0)); + } + switch (errno(system.symlinkat(target_path, newdirfd, sym_link_path))) { + .SUCCESS => return, + .FAULT => unreachable, + .INVAL => unreachable, + .ACCES => return error.AccessDenied, + .PERM => return error.AccessDenied, + .DQUOT => return error.DiskQuota, + .EXIST => return error.PathAlreadyExists, + .IO => return error.FileSystem, + .LOOP => return error.SymLinkLoop, + .NAMETOOLONG => return error.NameTooLong, + .NOENT => return error.FileNotFound, + .NOTDIR => return error.NotDir, + .NOMEM => return error.SystemResources, + .NOSPC => return error.NoSpaceLeft, + .ROFS => return error.ReadOnlyFileSystem, + .ILSEQ => |err| if (native_os == .wasi) + return error.InvalidUtf8 + else + return unexpectedErrno(err), + else => |err| return unexpectedErrno(err), + } +} + +pub const LinkError = UnexpectedError || error{ + AccessDenied, + DiskQuota, + PathAlreadyExists, + FileSystem, + SymLinkLoop, + LinkQuotaExceeded, + NameTooLong, + FileNotFound, + SystemResources, + NoSpaceLeft, + ReadOnlyFileSystem, + NotSameFileSystem, + + /// WASI-only; file paths must be valid UTF-8. + InvalidUtf8, +}; + +/// On WASI, both paths should be encoded as valid UTF-8. +/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. +pub fn linkZ(oldpath: [*:0]const u8, newpath: [*:0]const u8, flags: i32) LinkError!void { + if (native_os == .wasi and !builtin.link_libc) { + return link(mem.sliceTo(oldpath, 0), mem.sliceTo(newpath, 0), flags); + } + switch (errno(system.link(oldpath, newpath, flags))) { + .SUCCESS => return, + .ACCES => return error.AccessDenied, + .DQUOT => return error.DiskQuota, + .EXIST => return error.PathAlreadyExists, + .FAULT => unreachable, + .IO => return error.FileSystem, + .LOOP => return error.SymLinkLoop, + .MLINK => return error.LinkQuotaExceeded, + .NAMETOOLONG => return error.NameTooLong, + .NOENT => return error.FileNotFound, + .NOMEM => return error.SystemResources, + .NOSPC => return error.NoSpaceLeft, + .PERM => return error.AccessDenied, + .ROFS => return error.ReadOnlyFileSystem, + .XDEV => return error.NotSameFileSystem, + .INVAL => unreachable, + .ILSEQ => |err| if (native_os == .wasi) + return error.InvalidUtf8 + else + return unexpectedErrno(err), + else => |err| return unexpectedErrno(err), + } +} + +/// On WASI, both paths should be encoded as valid UTF-8. +/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. +pub fn link(oldpath: []const u8, newpath: []const u8, flags: i32) LinkError!void { + if (native_os == .wasi and !builtin.link_libc) { + return linkat(wasi.AT.FDCWD, oldpath, wasi.AT.FDCWD, newpath, flags) catch |err| switch (err) { + error.NotDir => unreachable, // link() does not support directories + else => |e| return e, + }; + } + const old = try toPosixPath(oldpath); + const new = try toPosixPath(newpath); + return try linkZ(&old, &new, flags); +} + +pub const LinkatError = LinkError || error{NotDir}; + +/// On WASI, both paths should be encoded as valid UTF-8. +/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. +pub fn linkatZ( + olddir: fd_t, + oldpath: [*:0]const u8, + newdir: fd_t, + newpath: [*:0]const u8, + flags: i32, +) LinkatError!void { + if (native_os == .wasi and !builtin.link_libc) { + return linkat(olddir, mem.sliceTo(oldpath, 0), newdir, mem.sliceTo(newpath, 0), flags); + } + switch (errno(system.linkat(olddir, oldpath, newdir, newpath, flags))) { + .SUCCESS => return, + .ACCES => return error.AccessDenied, + .DQUOT => return error.DiskQuota, + .EXIST => return error.PathAlreadyExists, + .FAULT => unreachable, + .IO => return error.FileSystem, + .LOOP => return error.SymLinkLoop, + .MLINK => return error.LinkQuotaExceeded, + .NAMETOOLONG => return error.NameTooLong, + .NOENT => return error.FileNotFound, + .NOMEM => return error.SystemResources, + .NOSPC => return error.NoSpaceLeft, + .NOTDIR => return error.NotDir, + .PERM => return error.AccessDenied, + .ROFS => return error.ReadOnlyFileSystem, + .XDEV => return error.NotSameFileSystem, + .INVAL => unreachable, + .ILSEQ => |err| if (native_os == .wasi) + return error.InvalidUtf8 + else + return unexpectedErrno(err), + else => |err| return unexpectedErrno(err), + } +} + +/// On WASI, both paths should be encoded as valid UTF-8. +/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. +pub fn linkat( + olddir: fd_t, + oldpath: []const u8, + newdir: fd_t, + newpath: []const u8, + flags: i32, +) LinkatError!void { + if (native_os == .wasi and !builtin.link_libc) { + const old: RelativePathWasi = .{ .dir_fd = olddir, .relative_path = oldpath }; + const new: RelativePathWasi = .{ .dir_fd = newdir, .relative_path = newpath }; + const old_flags: wasi.lookupflags_t = .{ + .SYMLINK_FOLLOW = (flags & AT.SYMLINK_FOLLOW) != 0, + }; + switch (wasi.path_link( + old.dir_fd, + old_flags, + old.relative_path.ptr, + old.relative_path.len, + new.dir_fd, + new.relative_path.ptr, + new.relative_path.len, + )) { + .SUCCESS => return, + .ACCES => return error.AccessDenied, + .DQUOT => return error.DiskQuota, + .EXIST => return error.PathAlreadyExists, + .FAULT => unreachable, + .IO => return error.FileSystem, + .LOOP => return error.SymLinkLoop, + .MLINK => return error.LinkQuotaExceeded, + .NAMETOOLONG => return error.NameTooLong, + .NOENT => return error.FileNotFound, + .NOMEM => return error.SystemResources, + .NOSPC => return error.NoSpaceLeft, + .NOTDIR => return error.NotDir, + .PERM => return error.AccessDenied, + .ROFS => return error.ReadOnlyFileSystem, + .XDEV => return error.NotSameFileSystem, + .INVAL => unreachable, + .ILSEQ => return error.InvalidUtf8, + else => |err| return unexpectedErrno(err), + } + } + const old = try toPosixPath(oldpath); + const new = try toPosixPath(newpath); + return try linkatZ(olddir, &old, newdir, &new, flags); +} + +pub const UnlinkError = error{ + FileNotFound, + + /// In WASI, this error may occur when the file descriptor does + /// not hold the required rights to unlink a resource by path relative to it. + AccessDenied, + FileBusy, + FileSystem, + IsDir, + SymLinkLoop, + NameTooLong, + NotDir, + SystemResources, + ReadOnlyFileSystem, + + /// WASI-only; file paths must be valid UTF-8. + InvalidUtf8, + + /// Windows-only; file paths provided by the user must be valid WTF-8. + /// https://simonsapin.github.io/wtf-8/ + InvalidWtf8, + + /// On Windows, file paths cannot contain these characters: + /// '/', '*', '?', '"', '<', '>', '|' + BadPathName, + + /// On Windows, `\\server` or `\\server\share` was not found. + NetworkNotFound, +} || UnexpectedError; + +/// Delete a name and possibly the file it refers to. +/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// 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 `unlinkZ`. +pub fn unlink(file_path: []const u8) UnlinkError!void { + if (native_os == .wasi and !builtin.link_libc) { + return unlinkat(wasi.AT.FDCWD, file_path, 0) catch |err| switch (err) { + error.DirNotEmpty => unreachable, // only occurs when targeting directories + else => |e| return e, + }; + } else if (native_os == .windows) { + const file_path_w = try windows.sliceToPrefixedFileW(null, file_path); + return unlinkW(file_path_w.span()); + } else { + const file_path_c = try toPosixPath(file_path); + return unlinkZ(&file_path_c); + } +} + +/// Same as `unlink` except the parameter is null terminated. +pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void { + if (native_os == .windows) { + const file_path_w = try windows.cStrToPrefixedFileW(null, file_path); + return unlinkW(file_path_w.span()); + } else if (native_os == .wasi and !builtin.link_libc) { + return unlink(mem.sliceTo(file_path, 0)); + } + switch (errno(system.unlink(file_path))) { + .SUCCESS => return, + .ACCES => return error.AccessDenied, + .PERM => return error.AccessDenied, + .BUSY => return error.FileBusy, + .FAULT => unreachable, + .INVAL => unreachable, + .IO => return error.FileSystem, + .ISDIR => return error.IsDir, + .LOOP => return error.SymLinkLoop, + .NAMETOOLONG => return error.NameTooLong, + .NOENT => return error.FileNotFound, + .NOTDIR => return error.NotDir, + .NOMEM => return error.SystemResources, + .ROFS => return error.ReadOnlyFileSystem, + .ILSEQ => |err| if (native_os == .wasi) + return error.InvalidUtf8 + else + return unexpectedErrno(err), + else => |err| return unexpectedErrno(err), + } +} + +/// Windows-only. Same as `unlink` except the parameter is null-terminated, WTF16 LE encoded. +pub fn unlinkW(file_path_w: []const u16) UnlinkError!void { + windows.DeleteFile(file_path_w, .{ .dir = fs.cwd().fd }) catch |err| switch (err) { + error.DirNotEmpty => unreachable, // we're not passing .remove_dir = true + else => |e| return e, + }; +} + +pub const UnlinkatError = UnlinkError || error{ + /// When passing `AT.REMOVEDIR`, this error occurs when the named directory is not empty. + DirNotEmpty, +}; + +/// Delete a file name and possibly the file it refers to, based on an open directory handle. +/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// 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. +/// Asserts that the path parameter has no null bytes. +pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void { + if (native_os == .windows) { + const file_path_w = try windows.sliceToPrefixedFileW(dirfd, file_path); + return unlinkatW(dirfd, file_path_w.span(), flags); + } else if (native_os == .wasi and !builtin.link_libc) { + return unlinkatWasi(dirfd, file_path, flags); + } else { + const file_path_c = try toPosixPath(file_path); + return unlinkatZ(dirfd, &file_path_c, flags); + } +} + +/// WASI-only. Same as `unlinkat` but targeting WASI. +/// See also `unlinkat`. +pub fn unlinkatWasi(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void { + const remove_dir = (flags & AT.REMOVEDIR) != 0; + const res = if (remove_dir) + wasi.path_remove_directory(dirfd, file_path.ptr, file_path.len) + else + wasi.path_unlink_file(dirfd, file_path.ptr, file_path.len); + switch (res) { + .SUCCESS => return, + .ACCES => return error.AccessDenied, + .PERM => return error.AccessDenied, + .BUSY => return error.FileBusy, + .FAULT => unreachable, + .IO => return error.FileSystem, + .ISDIR => return error.IsDir, + .LOOP => return error.SymLinkLoop, + .NAMETOOLONG => return error.NameTooLong, + .NOENT => return error.FileNotFound, + .NOTDIR => return error.NotDir, + .NOMEM => return error.SystemResources, + .ROFS => return error.ReadOnlyFileSystem, + .NOTEMPTY => return error.DirNotEmpty, + .NOTCAPABLE => return error.AccessDenied, + .ILSEQ => return error.InvalidUtf8, + + .INVAL => unreachable, // invalid flags, or pathname has . as last component + .BADF => unreachable, // always a race condition + + else => |err| return unexpectedErrno(err), + } +} + +/// Same as `unlinkat` but `file_path` is a null-terminated string. +pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatError!void { + if (native_os == .windows) { + const file_path_w = try windows.cStrToPrefixedFileW(dirfd, file_path_c); + return unlinkatW(dirfd, file_path_w.span(), flags); + } else if (native_os == .wasi and !builtin.link_libc) { + return unlinkat(dirfd, mem.sliceTo(file_path_c, 0), flags); + } + switch (errno(system.unlinkat(dirfd, file_path_c, flags))) { + .SUCCESS => return, + .ACCES => return error.AccessDenied, + .PERM => return error.AccessDenied, + .BUSY => return error.FileBusy, + .FAULT => unreachable, + .IO => return error.FileSystem, + .ISDIR => return error.IsDir, + .LOOP => return error.SymLinkLoop, + .NAMETOOLONG => return error.NameTooLong, + .NOENT => return error.FileNotFound, + .NOTDIR => return error.NotDir, + .NOMEM => return error.SystemResources, + .ROFS => return error.ReadOnlyFileSystem, + .EXIST => return error.DirNotEmpty, + .NOTEMPTY => return error.DirNotEmpty, + .ILSEQ => |err| if (native_os == .wasi) + return error.InvalidUtf8 + else + return unexpectedErrno(err), + + .INVAL => unreachable, // invalid flags, or pathname has . as last component + .BADF => unreachable, // always a race condition + + else => |err| return unexpectedErrno(err), + } +} + +/// Same as `unlinkat` but `sub_path_w` is WTF16LE, NT prefixed. Windows only. +pub fn unlinkatW(dirfd: fd_t, sub_path_w: []const u16, flags: u32) UnlinkatError!void { + const remove_dir = (flags & AT.REMOVEDIR) != 0; + return windows.DeleteFile(sub_path_w, .{ .dir = dirfd, .remove_dir = remove_dir }); +} + +pub const RenameError = error{ + /// In WASI, this error may occur when the file descriptor does + /// not hold the required rights to rename a resource by path relative to it. + /// + /// On Windows, this error may be returned instead of PathAlreadyExists when + /// renaming a directory over an existing directory. + AccessDenied, + FileBusy, + DiskQuota, + IsDir, + SymLinkLoop, + LinkQuotaExceeded, + NameTooLong, + FileNotFound, + NotDir, + SystemResources, + NoSpaceLeft, + PathAlreadyExists, + ReadOnlyFileSystem, + RenameAcrossMountPoints, + /// WASI-only; file paths must be valid UTF-8. + InvalidUtf8, + /// Windows-only; file paths provided by the user must be valid WTF-8. + /// https://simonsapin.github.io/wtf-8/ + InvalidWtf8, + BadPathName, + NoDevice, + SharingViolation, + PipeBusy, + /// On Windows, `\\server` or `\\server\share` was not found. + NetworkNotFound, + /// On Windows, antivirus software is enabled by default. It can be + /// disabled, but Windows Update sometimes ignores the user's preference + /// and re-enables it. When enabled, antivirus software on Windows + /// intercepts file system operations and makes them significantly slower + /// in addition to possibly failing with this error code. + AntivirusInterference, +} || UnexpectedError; + +/// Change the name or location of a file. +/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On WASI, both paths should be encoded as valid UTF-8. +/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. +pub fn rename(old_path: []const u8, new_path: []const u8) RenameError!void { + if (native_os == .wasi and !builtin.link_libc) { + return renameat(wasi.AT.FDCWD, old_path, wasi.AT.FDCWD, new_path); + } else if (native_os == .windows) { + const old_path_w = try windows.sliceToPrefixedFileW(null, old_path); + const new_path_w = try windows.sliceToPrefixedFileW(null, new_path); + return renameW(old_path_w.span().ptr, new_path_w.span().ptr); + } else { + const old_path_c = try toPosixPath(old_path); + const new_path_c = try toPosixPath(new_path); + return renameZ(&old_path_c, &new_path_c); + } +} + +/// Same as `rename` except the parameters are null-terminated. +pub fn renameZ(old_path: [*:0]const u8, new_path: [*:0]const u8) RenameError!void { + if (native_os == .windows) { + const old_path_w = try windows.cStrToPrefixedFileW(null, old_path); + const new_path_w = try windows.cStrToPrefixedFileW(null, new_path); + return renameW(old_path_w.span().ptr, new_path_w.span().ptr); + } else if (native_os == .wasi and !builtin.link_libc) { + return rename(mem.sliceTo(old_path, 0), mem.sliceTo(new_path, 0)); + } + switch (errno(system.rename(old_path, new_path))) { + .SUCCESS => return, + .ACCES => return error.AccessDenied, + .PERM => return error.AccessDenied, + .BUSY => return error.FileBusy, + .DQUOT => return error.DiskQuota, + .FAULT => unreachable, + .INVAL => unreachable, + .ISDIR => return error.IsDir, + .LOOP => return error.SymLinkLoop, + .MLINK => return error.LinkQuotaExceeded, + .NAMETOOLONG => return error.NameTooLong, + .NOENT => return error.FileNotFound, + .NOTDIR => return error.NotDir, + .NOMEM => return error.SystemResources, + .NOSPC => return error.NoSpaceLeft, + .EXIST => return error.PathAlreadyExists, + .NOTEMPTY => return error.PathAlreadyExists, + .ROFS => return error.ReadOnlyFileSystem, + .XDEV => return error.RenameAcrossMountPoints, + .ILSEQ => |err| if (native_os == .wasi) + return error.InvalidUtf8 + else + return unexpectedErrno(err), + else => |err| return unexpectedErrno(err), + } +} + +/// Same as `rename` except the parameters are null-terminated and WTF16LE encoded. +/// Assumes target is Windows. +pub fn renameW(old_path: [*:0]const u16, new_path: [*:0]const u16) RenameError!void { + const flags = windows.MOVEFILE_REPLACE_EXISTING | windows.MOVEFILE_WRITE_THROUGH; + return windows.MoveFileExW(old_path, new_path, flags); +} + +/// Change the name or location of a file based on an open directory handle. +/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On WASI, both paths should be encoded as valid UTF-8. +/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. +pub fn renameat( + old_dir_fd: fd_t, + old_path: []const u8, + new_dir_fd: fd_t, + new_path: []const u8, +) RenameError!void { + if (native_os == .windows) { + const old_path_w = try windows.sliceToPrefixedFileW(old_dir_fd, old_path); + const new_path_w = try windows.sliceToPrefixedFileW(new_dir_fd, new_path); + return renameatW(old_dir_fd, old_path_w.span(), new_dir_fd, new_path_w.span(), windows.TRUE); + } else if (native_os == .wasi and !builtin.link_libc) { + const old: RelativePathWasi = .{ .dir_fd = old_dir_fd, .relative_path = old_path }; + const new: RelativePathWasi = .{ .dir_fd = new_dir_fd, .relative_path = new_path }; + return renameatWasi(old, new); + } else { + const old_path_c = try toPosixPath(old_path); + const new_path_c = try toPosixPath(new_path); + return renameatZ(old_dir_fd, &old_path_c, new_dir_fd, &new_path_c); + } +} + +/// WASI-only. Same as `renameat` expect targeting WASI. +/// See also `renameat`. +fn renameatWasi(old: RelativePathWasi, new: RelativePathWasi) RenameError!void { + switch (wasi.path_rename(old.dir_fd, old.relative_path.ptr, old.relative_path.len, new.dir_fd, new.relative_path.ptr, new.relative_path.len)) { + .SUCCESS => return, + .ACCES => return error.AccessDenied, + .PERM => return error.AccessDenied, + .BUSY => return error.FileBusy, + .DQUOT => return error.DiskQuota, + .FAULT => unreachable, + .INVAL => unreachable, + .ISDIR => return error.IsDir, + .LOOP => return error.SymLinkLoop, + .MLINK => return error.LinkQuotaExceeded, + .NAMETOOLONG => return error.NameTooLong, + .NOENT => return error.FileNotFound, + .NOTDIR => return error.NotDir, + .NOMEM => return error.SystemResources, + .NOSPC => return error.NoSpaceLeft, + .EXIST => return error.PathAlreadyExists, + .NOTEMPTY => return error.PathAlreadyExists, + .ROFS => return error.ReadOnlyFileSystem, + .XDEV => return error.RenameAcrossMountPoints, + .NOTCAPABLE => return error.AccessDenied, + .ILSEQ => return error.InvalidUtf8, + else => |err| return unexpectedErrno(err), + } +} + +/// An fd-relative file path +/// +/// This is currently only used for WASI-specific functionality, but the concept +/// is the same as the dirfd/pathname pairs in the `*at(...)` POSIX functions. +const RelativePathWasi = struct { + /// Handle to directory + dir_fd: fd_t, + /// Path to resource within `dir_fd`. + relative_path: []const u8, +}; + +/// Same as `renameat` except the parameters are null-terminated. +pub fn renameatZ( + old_dir_fd: fd_t, + old_path: [*:0]const u8, + new_dir_fd: fd_t, + new_path: [*:0]const u8, +) RenameError!void { + if (native_os == .windows) { + const old_path_w = try windows.cStrToPrefixedFileW(old_dir_fd, old_path); + const new_path_w = try windows.cStrToPrefixedFileW(new_dir_fd, new_path); + return renameatW(old_dir_fd, old_path_w.span(), new_dir_fd, new_path_w.span(), windows.TRUE); + } else if (native_os == .wasi and !builtin.link_libc) { + return renameat(old_dir_fd, mem.sliceTo(old_path, 0), new_dir_fd, mem.sliceTo(new_path, 0)); + } + + switch (errno(system.renameat(old_dir_fd, old_path, new_dir_fd, new_path))) { + .SUCCESS => return, + .ACCES => return error.AccessDenied, + .PERM => return error.AccessDenied, + .BUSY => return error.FileBusy, + .DQUOT => return error.DiskQuota, + .FAULT => unreachable, + .INVAL => unreachable, + .ISDIR => return error.IsDir, + .LOOP => return error.SymLinkLoop, + .MLINK => return error.LinkQuotaExceeded, + .NAMETOOLONG => return error.NameTooLong, + .NOENT => return error.FileNotFound, + .NOTDIR => return error.NotDir, + .NOMEM => return error.SystemResources, + .NOSPC => return error.NoSpaceLeft, + .EXIST => return error.PathAlreadyExists, + .NOTEMPTY => return error.PathAlreadyExists, + .ROFS => return error.ReadOnlyFileSystem, + .XDEV => return error.RenameAcrossMountPoints, + .ILSEQ => |err| if (native_os == .wasi) + return error.InvalidUtf8 + else + return unexpectedErrno(err), + else => |err| return unexpectedErrno(err), + } +} + +/// Same as `renameat` but Windows-only and the path parameters are +/// [WTF-16](https://simonsapin.github.io/wtf-8/#potentially-ill-formed-utf-16) encoded. +pub fn renameatW( + old_dir_fd: fd_t, + old_path_w: []const u16, + new_dir_fd: fd_t, + new_path_w: []const u16, + ReplaceIfExists: windows.BOOLEAN, +) RenameError!void { + const src_fd = windows.OpenFile(old_path_w, .{ + .dir = old_dir_fd, + .access_mask = windows.SYNCHRONIZE | windows.GENERIC_WRITE | windows.DELETE, + .creation = windows.FILE_OPEN, + .filter = .any, // This function is supposed to rename both files and directories. + .follow_symlinks = false, + }) catch |err| switch (err) { + error.WouldBlock => unreachable, // Not possible without `.share_access_nonblocking = true`. + else => |e| return e, + }; + defer windows.CloseHandle(src_fd); + + var need_fallback = true; + var rc: windows.NTSTATUS = undefined; + // FILE_RENAME_INFORMATION_EX and FILE_RENAME_POSIX_SEMANTICS require >= win10_rs1, + // but FILE_RENAME_IGNORE_READONLY_ATTRIBUTE requires >= win10_rs5. We check >= rs5 here + // so that we only use POSIX_SEMANTICS when we know IGNORE_READONLY_ATTRIBUTE will also be + // supported in order to avoid either (1) using a redundant call that we can know in advance will return + // STATUS_NOT_SUPPORTED or (2) only setting IGNORE_READONLY_ATTRIBUTE when >= rs5 + // and therefore having different behavior when the Windows version is >= rs1 but < rs5. + if (builtin.target.os.isAtLeast(.windows, .win10_rs5) orelse false) { + const struct_buf_len = @sizeOf(windows.FILE_RENAME_INFORMATION_EX) + (max_path_bytes - 1); + var rename_info_buf: [struct_buf_len]u8 align(@alignOf(windows.FILE_RENAME_INFORMATION_EX)) = undefined; + const struct_len = @sizeOf(windows.FILE_RENAME_INFORMATION_EX) - 1 + new_path_w.len * 2; + if (struct_len > struct_buf_len) return error.NameTooLong; + + const rename_info: *windows.FILE_RENAME_INFORMATION_EX = @ptrCast(&rename_info_buf); + var io_status_block: windows.IO_STATUS_BLOCK = undefined; + + var flags: windows.ULONG = windows.FILE_RENAME_POSIX_SEMANTICS | windows.FILE_RENAME_IGNORE_READONLY_ATTRIBUTE; + if (ReplaceIfExists == windows.TRUE) flags |= windows.FILE_RENAME_REPLACE_IF_EXISTS; + rename_info.* = .{ + .Flags = flags, + .RootDirectory = if (fs.path.isAbsoluteWindowsWTF16(new_path_w)) null else new_dir_fd, + .FileNameLength = @intCast(new_path_w.len * 2), // already checked error.NameTooLong + .FileName = undefined, + }; + @memcpy((&rename_info.FileName).ptr, new_path_w); + rc = windows.ntdll.NtSetInformationFile( + src_fd, + &io_status_block, + rename_info, + @intCast(struct_len), // already checked for error.NameTooLong + .FileRenameInformationEx, + ); + switch (rc) { + .SUCCESS => return, + // INVALID_PARAMETER here means that the filesystem does not support FileRenameInformationEx + .INVALID_PARAMETER => {}, + .DIRECTORY_NOT_EMPTY => return error.PathAlreadyExists, + .FILE_IS_A_DIRECTORY => return error.IsDir, + .NOT_A_DIRECTORY => return error.NotDir, + // For all other statuses, fall down to the switch below to handle them. + else => need_fallback = false, + } + } + + if (need_fallback) { + const struct_buf_len = @sizeOf(windows.FILE_RENAME_INFORMATION) + (max_path_bytes - 1); + var rename_info_buf: [struct_buf_len]u8 align(@alignOf(windows.FILE_RENAME_INFORMATION)) = undefined; + const struct_len = @sizeOf(windows.FILE_RENAME_INFORMATION) - 1 + new_path_w.len * 2; + if (struct_len > struct_buf_len) return error.NameTooLong; + + const rename_info: *windows.FILE_RENAME_INFORMATION = @ptrCast(&rename_info_buf); + var io_status_block: windows.IO_STATUS_BLOCK = undefined; + + rename_info.* = .{ + .Flags = ReplaceIfExists, + .RootDirectory = if (fs.path.isAbsoluteWindowsWTF16(new_path_w)) null else new_dir_fd, + .FileNameLength = @intCast(new_path_w.len * 2), // already checked error.NameTooLong + .FileName = undefined, + }; + @memcpy((&rename_info.FileName).ptr, new_path_w); + + rc = + windows.ntdll.NtSetInformationFile( + src_fd, + &io_status_block, + rename_info, + @intCast(struct_len), // already checked for error.NameTooLong + .FileRenameInformation, + ); + } + + switch (rc) { + .SUCCESS => {}, + .INVALID_HANDLE => unreachable, + .INVALID_PARAMETER => unreachable, + .OBJECT_PATH_SYNTAX_BAD => unreachable, + .ACCESS_DENIED => return error.AccessDenied, + .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, + .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, + .NOT_SAME_DEVICE => return error.RenameAcrossMountPoints, + .OBJECT_NAME_COLLISION => return error.PathAlreadyExists, + else => return windows.unexpectedStatus(rc), + } +} + +/// On Windows, `sub_dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On WASI, `sub_dir_path` should be encoded as valid UTF-8. +/// On other platforms, `sub_dir_path` is an opaque sequence of bytes with no particular encoding. +pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!void { + if (native_os == .windows) { + const sub_dir_path_w = try windows.sliceToPrefixedFileW(dir_fd, sub_dir_path); + return mkdiratW(dir_fd, sub_dir_path_w.span(), mode); + } else if (native_os == .wasi and !builtin.link_libc) { + return mkdiratWasi(dir_fd, sub_dir_path, mode); + } else { + const sub_dir_path_c = try toPosixPath(sub_dir_path); + return mkdiratZ(dir_fd, &sub_dir_path_c, mode); + } +} + +pub fn mkdiratWasi(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!void { + _ = mode; + switch (wasi.path_create_directory(dir_fd, sub_dir_path.ptr, sub_dir_path.len)) { + .SUCCESS => return, + .ACCES => return error.AccessDenied, + .BADF => unreachable, + .PERM => return error.AccessDenied, + .DQUOT => return error.DiskQuota, + .EXIST => return error.PathAlreadyExists, + .FAULT => unreachable, + .LOOP => return error.SymLinkLoop, + .MLINK => return error.LinkQuotaExceeded, + .NAMETOOLONG => return error.NameTooLong, + .NOENT => return error.FileNotFound, + .NOMEM => return error.SystemResources, + .NOSPC => return error.NoSpaceLeft, + .NOTDIR => return error.NotDir, + .ROFS => return error.ReadOnlyFileSystem, + .NOTCAPABLE => return error.AccessDenied, + .ILSEQ => return error.InvalidUtf8, + else => |err| return unexpectedErrno(err), + } +} + +/// Same as `mkdirat` except the parameters are null-terminated. +pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirError!void { + if (native_os == .windows) { + const sub_dir_path_w = try windows.cStrToPrefixedFileW(dir_fd, sub_dir_path); + return mkdiratW(dir_fd, sub_dir_path_w.span(), mode); + } else if (native_os == .wasi and !builtin.link_libc) { + return mkdirat(dir_fd, mem.sliceTo(sub_dir_path, 0), mode); + } + switch (errno(system.mkdirat(dir_fd, sub_dir_path, mode))) { + .SUCCESS => return, + .ACCES => return error.AccessDenied, + .BADF => unreachable, + .PERM => return error.AccessDenied, + .DQUOT => return error.DiskQuota, + .EXIST => return error.PathAlreadyExists, + .FAULT => unreachable, + .LOOP => return error.SymLinkLoop, + .MLINK => return error.LinkQuotaExceeded, + .NAMETOOLONG => return error.NameTooLong, + .NOENT => return error.FileNotFound, + .NOMEM => return error.SystemResources, + .NOSPC => return error.NoSpaceLeft, + .NOTDIR => return error.NotDir, + .ROFS => return error.ReadOnlyFileSystem, + // dragonfly: when dir_fd is unlinked from filesystem + .NOTCONN => return error.FileNotFound, + .ILSEQ => |err| if (native_os == .wasi) + return error.InvalidUtf8 + else + return unexpectedErrno(err), + else => |err| return unexpectedErrno(err), + } +} + +/// Windows-only. Same as `mkdirat` except the parameter WTF16 LE encoded. +pub fn mkdiratW(dir_fd: fd_t, sub_path_w: []const u16, mode: u32) MakeDirError!void { + _ = mode; + const sub_dir_handle = windows.OpenFile(sub_path_w, .{ + .dir = dir_fd, + .access_mask = windows.GENERIC_READ | windows.SYNCHRONIZE, + .creation = windows.FILE_CREATE, + .filter = .dir_only, + }) catch |err| switch (err) { + error.IsDir => return error.Unexpected, + error.PipeBusy => return error.Unexpected, + error.WouldBlock => return error.Unexpected, + error.AntivirusInterference => return error.Unexpected, + else => |e| return e, + }; + windows.CloseHandle(sub_dir_handle); +} + +pub const MakeDirError = error{ + /// In WASI, this error may occur when the file descriptor does + /// not hold the required rights to create a new directory relative to it. + AccessDenied, + DiskQuota, + PathAlreadyExists, + SymLinkLoop, + LinkQuotaExceeded, + NameTooLong, + FileNotFound, + SystemResources, + NoSpaceLeft, + NotDir, + ReadOnlyFileSystem, + /// WASI-only; file paths must be valid UTF-8. + InvalidUtf8, + /// Windows-only; file paths provided by the user must be valid WTF-8. + /// https://simonsapin.github.io/wtf-8/ + InvalidWtf8, + BadPathName, + NoDevice, + /// On Windows, `\\server` or `\\server\share` was not found. + NetworkNotFound, +} || UnexpectedError; + +/// Create a directory. +/// `mode` is ignored on Windows and WASI. +/// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On WASI, `dir_path` should be encoded as valid UTF-8. +/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding. +pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void { + if (native_os == .wasi and !builtin.link_libc) { + return mkdirat(wasi.AT.FDCWD, dir_path, mode); + } else if (native_os == .windows) { + const dir_path_w = try windows.sliceToPrefixedFileW(null, dir_path); + return mkdirW(dir_path_w.span(), mode); + } else { + const dir_path_c = try toPosixPath(dir_path); + return mkdirZ(&dir_path_c, mode); + } +} + +/// Same as `mkdir` but the parameter is null-terminated. +/// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On WASI, `dir_path` should be encoded as valid UTF-8. +/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding. +pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void { + if (native_os == .windows) { + const dir_path_w = try windows.cStrToPrefixedFileW(null, dir_path); + return mkdirW(dir_path_w.span(), mode); + } else if (native_os == .wasi and !builtin.link_libc) { + return mkdir(mem.sliceTo(dir_path, 0), mode); + } + switch (errno(system.mkdir(dir_path, mode))) { + .SUCCESS => return, + .ACCES => return error.AccessDenied, + .PERM => return error.AccessDenied, + .DQUOT => return error.DiskQuota, + .EXIST => return error.PathAlreadyExists, + .FAULT => unreachable, + .LOOP => return error.SymLinkLoop, + .MLINK => return error.LinkQuotaExceeded, + .NAMETOOLONG => return error.NameTooLong, + .NOENT => return error.FileNotFound, + .NOMEM => return error.SystemResources, + .NOSPC => return error.NoSpaceLeft, + .NOTDIR => return error.NotDir, + .ROFS => return error.ReadOnlyFileSystem, + .ILSEQ => |err| if (native_os == .wasi) + return error.InvalidUtf8 + else + return unexpectedErrno(err), + else => |err| return unexpectedErrno(err), + } +} + +/// Windows-only. Same as `mkdir` but the parameters is WTF16LE encoded. +pub fn mkdirW(dir_path_w: []const u16, mode: u32) MakeDirError!void { + _ = mode; + const sub_dir_handle = windows.OpenFile(dir_path_w, .{ + .dir = fs.cwd().fd, + .access_mask = windows.GENERIC_READ | windows.SYNCHRONIZE, + .creation = windows.FILE_CREATE, + .filter = .dir_only, + }) catch |err| switch (err) { + error.IsDir => return error.Unexpected, + error.PipeBusy => return error.Unexpected, + error.WouldBlock => return error.Unexpected, + error.AntivirusInterference => return error.Unexpected, + else => |e| return e, + }; + windows.CloseHandle(sub_dir_handle); +} + +pub const DeleteDirError = error{ + AccessDenied, + FileBusy, + SymLinkLoop, + NameTooLong, + FileNotFound, + SystemResources, + NotDir, + DirNotEmpty, + ReadOnlyFileSystem, + /// WASI-only; file paths must be valid UTF-8. + InvalidUtf8, + /// Windows-only; file paths provided by the user must be valid WTF-8. + /// https://simonsapin.github.io/wtf-8/ + InvalidWtf8, + BadPathName, + /// On Windows, `\\server` or `\\server\share` was not found. + NetworkNotFound, +} || UnexpectedError; + +/// Deletes an empty directory. +/// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On WASI, `dir_path` should be encoded as valid UTF-8. +/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding. +pub fn rmdir(dir_path: []const u8) DeleteDirError!void { + if (native_os == .wasi and !builtin.link_libc) { + return unlinkat(wasi.AT.FDCWD, dir_path, AT.REMOVEDIR) catch |err| switch (err) { + error.FileSystem => unreachable, // only occurs when targeting files + error.IsDir => unreachable, // only occurs when targeting files + else => |e| return e, + }; + } else if (native_os == .windows) { + const dir_path_w = try windows.sliceToPrefixedFileW(null, dir_path); + return rmdirW(dir_path_w.span()); + } else { + const dir_path_c = try toPosixPath(dir_path); + return rmdirZ(&dir_path_c); + } +} + +/// Same as `rmdir` except the parameter is null-terminated. +/// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On WASI, `dir_path` should be encoded as valid UTF-8. +/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding. +pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void { + if (native_os == .windows) { + const dir_path_w = try windows.cStrToPrefixedFileW(null, dir_path); + return rmdirW(dir_path_w.span()); + } else if (native_os == .wasi and !builtin.link_libc) { + return rmdir(mem.sliceTo(dir_path, 0)); + } + switch (errno(system.rmdir(dir_path))) { + .SUCCESS => return, + .ACCES => return error.AccessDenied, + .PERM => return error.AccessDenied, + .BUSY => return error.FileBusy, + .FAULT => unreachable, + .INVAL => return error.BadPathName, + .LOOP => return error.SymLinkLoop, + .NAMETOOLONG => return error.NameTooLong, + .NOENT => return error.FileNotFound, + .NOMEM => return error.SystemResources, + .NOTDIR => return error.NotDir, + .EXIST => return error.DirNotEmpty, + .NOTEMPTY => return error.DirNotEmpty, + .ROFS => return error.ReadOnlyFileSystem, + .ILSEQ => |err| if (native_os == .wasi) + return error.InvalidUtf8 + else + return unexpectedErrno(err), + else => |err| return unexpectedErrno(err), + } +} + +/// Windows-only. Same as `rmdir` except the parameter is WTF-16 LE encoded. +pub fn rmdirW(dir_path_w: []const u16) DeleteDirError!void { + return windows.DeleteFile(dir_path_w, .{ .dir = fs.cwd().fd, .remove_dir = true }) catch |err| switch (err) { + error.IsDir => unreachable, + else => |e| return e, + }; +} + +pub const ChangeCurDirError = error{ + AccessDenied, + FileSystem, + SymLinkLoop, + NameTooLong, + FileNotFound, + SystemResources, + NotDir, + BadPathName, + /// WASI-only; file paths must be valid UTF-8. + InvalidUtf8, + /// Windows-only; file paths provided by the user must be valid WTF-8. + /// https://simonsapin.github.io/wtf-8/ + InvalidWtf8, +} || UnexpectedError; + +/// Changes the current working directory of the calling process. +/// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On WASI, `dir_path` should be encoded as valid UTF-8. +/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding. +pub fn chdir(dir_path: []const u8) ChangeCurDirError!void { + if (native_os == .wasi and !builtin.link_libc) { + @compileError("WASI does not support os.chdir"); + } else if (native_os == .windows) { + var wtf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined; + const len = try std.unicode.wtf8ToWtf16Le(wtf16_dir_path[0..], dir_path); + if (len > wtf16_dir_path.len) return error.NameTooLong; + return chdirW(wtf16_dir_path[0..len]); + } else { + const dir_path_c = try toPosixPath(dir_path); + return chdirZ(&dir_path_c); + } +} + +/// Same as `chdir` except the parameter is null-terminated. +/// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On WASI, `dir_path` should be encoded as valid UTF-8. +/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding. +pub fn chdirZ(dir_path: [*:0]const u8) ChangeCurDirError!void { + if (native_os == .windows) { + var wtf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined; + const len = try std.unicode.wtf8ToWtf16Le(wtf16_dir_path[0..], mem.span(dir_path)); + if (len > wtf16_dir_path.len) return error.NameTooLong; + return chdirW(wtf16_dir_path[0..len]); + } else if (native_os == .wasi and !builtin.link_libc) { + return chdir(mem.span(dir_path)); + } + switch (errno(system.chdir(dir_path))) { + .SUCCESS => return, + .ACCES => return error.AccessDenied, + .FAULT => unreachable, + .IO => return error.FileSystem, + .LOOP => return error.SymLinkLoop, + .NAMETOOLONG => return error.NameTooLong, + .NOENT => return error.FileNotFound, + .NOMEM => return error.SystemResources, + .NOTDIR => return error.NotDir, + .ILSEQ => |err| if (native_os == .wasi) + return error.InvalidUtf8 + else + return unexpectedErrno(err), + else => |err| return unexpectedErrno(err), + } +} + +/// Windows-only. Same as `chdir` except the parameter is WTF16 LE encoded. +pub fn chdirW(dir_path: []const u16) ChangeCurDirError!void { + windows.SetCurrentDirectory(dir_path) catch |err| switch (err) { + error.NoDevice => return error.FileSystem, + else => |e| return e, + }; +} + +pub const FchdirError = error{ + AccessDenied, + NotDir, + FileSystem, +} || UnexpectedError; + +pub fn fchdir(dirfd: fd_t) FchdirError!void { + if (dirfd == AT.FDCWD) return; + while (true) { + switch (errno(system.fchdir(dirfd))) { + .SUCCESS => return, + .ACCES => return error.AccessDenied, + .BADF => unreachable, + .NOTDIR => return error.NotDir, + .INTR => continue, + .IO => return error.FileSystem, + else => |err| return unexpectedErrno(err), + } + } +} + +pub const ReadLinkError = error{ + /// In WASI, this error may occur when the file descriptor does + /// not hold the required rights to read value of a symbolic link relative to it. + AccessDenied, + FileSystem, + SymLinkLoop, + NameTooLong, + FileNotFound, + SystemResources, + NotLink, + NotDir, + /// WASI-only; file paths must be valid UTF-8. + InvalidUtf8, + /// Windows-only; file paths provided by the user must be valid WTF-8. + /// https://simonsapin.github.io/wtf-8/ + InvalidWtf8, + BadPathName, + /// Windows-only. This error may occur if the opened reparse point is + /// of unsupported type. + UnsupportedReparsePointType, + /// On Windows, `\\server` or `\\server\share` was not found. + NetworkNotFound, +} || UnexpectedError; + +/// Read value of a symbolic link. +/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// 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. +/// The return value is a slice of `out_buffer` from index 0. +/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On WASI, the result is encoded as UTF-8. +/// On other platforms, the result is an opaque sequence of bytes with no particular encoding. +pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 { + if (native_os == .wasi and !builtin.link_libc) { + return readlinkat(wasi.AT.FDCWD, file_path, out_buffer); + } else if (native_os == .windows) { + const file_path_w = try windows.sliceToPrefixedFileW(null, file_path); + return readlinkW(file_path_w.span(), out_buffer); + } else { + const file_path_c = try toPosixPath(file_path); + return readlinkZ(&file_path_c, out_buffer); + } +} + +/// Windows-only. Same as `readlink` except `file_path` is WTF16 LE encoded. +/// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// See also `readlinkZ`. +pub fn readlinkW(file_path: []const u16, out_buffer: []u8) ReadLinkError![]u8 { + return windows.ReadLink(fs.cwd().fd, file_path, out_buffer); +} + +/// Same as `readlink` except `file_path` is null-terminated. +pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 { + if (native_os == .windows) { + const file_path_w = try windows.cStrToPrefixedFileW(null, file_path); + return readlinkW(file_path_w.span(), out_buffer); + } else if (native_os == .wasi and !builtin.link_libc) { + return readlink(mem.sliceTo(file_path, 0), out_buffer); + } + const rc = system.readlink(file_path, out_buffer.ptr, out_buffer.len); + switch (errno(rc)) { + .SUCCESS => return out_buffer[0..@bitCast(rc)], + .ACCES => return error.AccessDenied, + .FAULT => unreachable, + .INVAL => return error.NotLink, + .IO => return error.FileSystem, + .LOOP => return error.SymLinkLoop, + .NAMETOOLONG => return error.NameTooLong, + .NOENT => return error.FileNotFound, + .NOMEM => return error.SystemResources, + .NOTDIR => return error.NotDir, + .ILSEQ => |err| if (native_os == .wasi) + return error.InvalidUtf8 + else + return unexpectedErrno(err), + else => |err| return unexpectedErrno(err), + } +} + +/// Similar to `readlink` except reads value of a symbolink link **relative** to `dirfd` directory handle. +/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// 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. +/// The return value is a slice of `out_buffer` from index 0. +/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On WASI, the result is encoded as UTF-8. +/// On other platforms, the result is an opaque sequence of bytes with no particular encoding. +/// See also `readlinkatWasi`, `realinkatZ` and `realinkatW`. +pub fn readlinkat(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 { + if (native_os == .wasi and !builtin.link_libc) { + return readlinkatWasi(dirfd, file_path, out_buffer); + } + if (native_os == .windows) { + const file_path_w = try windows.sliceToPrefixedFileW(dirfd, file_path); + return readlinkatW(dirfd, file_path_w.span(), out_buffer); + } + const file_path_c = try toPosixPath(file_path); + return readlinkatZ(dirfd, &file_path_c, out_buffer); +} + +/// WASI-only. Same as `readlinkat` but targets WASI. +/// See also `readlinkat`. +pub fn readlinkatWasi(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 { + var bufused: usize = undefined; + switch (wasi.path_readlink(dirfd, file_path.ptr, file_path.len, out_buffer.ptr, out_buffer.len, &bufused)) { + .SUCCESS => return out_buffer[0..bufused], + .ACCES => return error.AccessDenied, + .FAULT => unreachable, + .INVAL => return error.NotLink, + .IO => return error.FileSystem, + .LOOP => return error.SymLinkLoop, + .NAMETOOLONG => return error.NameTooLong, + .NOENT => return error.FileNotFound, + .NOMEM => return error.SystemResources, + .NOTDIR => return error.NotDir, + .NOTCAPABLE => return error.AccessDenied, + .ILSEQ => return error.InvalidUtf8, + else => |err| return unexpectedErrno(err), + } +} + +/// Windows-only. Same as `readlinkat` except `file_path` is null-terminated, WTF16 LE encoded. +/// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// See also `readlinkat`. +pub fn readlinkatW(dirfd: fd_t, file_path: []const u16, out_buffer: []u8) ReadLinkError![]u8 { + return windows.ReadLink(dirfd, file_path, out_buffer); +} + +/// Same as `readlinkat` except `file_path` is null-terminated. +/// See also `readlinkat`. +pub fn readlinkatZ(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 { + if (native_os == .windows) { + const file_path_w = try windows.cStrToPrefixedFileW(dirfd, file_path); + return readlinkatW(dirfd, file_path_w.span(), out_buffer); + } else if (native_os == .wasi and !builtin.link_libc) { + return readlinkat(dirfd, mem.sliceTo(file_path, 0), out_buffer); + } + const rc = system.readlinkat(dirfd, file_path, out_buffer.ptr, out_buffer.len); + switch (errno(rc)) { + .SUCCESS => return out_buffer[0..@bitCast(rc)], + .ACCES => return error.AccessDenied, + .FAULT => unreachable, + .INVAL => return error.NotLink, + .IO => return error.FileSystem, + .LOOP => return error.SymLinkLoop, + .NAMETOOLONG => return error.NameTooLong, + .NOENT => return error.FileNotFound, + .NOMEM => return error.SystemResources, + .NOTDIR => return error.NotDir, + .ILSEQ => |err| if (native_os == .wasi) + return error.InvalidUtf8 + else + return unexpectedErrno(err), + else => |err| return unexpectedErrno(err), + } +} + +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 setreuid(ruid: uid_t, euid: uid_t) SetIdError!void { + switch (errno(system.setreuid(ruid, euid))) { + .SUCCESS => return, + .AGAIN => return error.ResourceLimitReached, + .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 setregid(rgid: gid_t, egid: gid_t) SetIdError!void { + switch (errno(system.setregid(rgid, egid))) { + .SUCCESS => return, + .AGAIN => return error.ResourceLimitReached, + .INVAL => return error.InvalidUserId, + .PERM => return error.PermissionDenied, + else => |err| return unexpectedErrno(err), + } +} + +/// Test whether a file descriptor refers to a terminal. +pub fn isatty(handle: fd_t) bool { + if (native_os == .windows) { + if (fs.File.isCygwinPty(.{ .handle = handle })) + return true; + + var out: windows.DWORD = undefined; + return windows.kernel32.GetConsoleMode(handle, &out) != 0; + } + if (builtin.link_libc) { + return system.isatty(handle) != 0; + } + if (native_os == .wasi) { + var statbuf: wasi.fdstat_t = undefined; + const err = wasi.fd_fdstat_get(handle, &statbuf); + if (err != .SUCCESS) + return false; + + // A tty is a character device that we can't seek or tell on. + if (statbuf.fs_filetype != .CHARACTER_DEVICE) + return false; + if (statbuf.fs_rights_base.FD_SEEK or statbuf.fs_rights_base.FD_TELL) + return false; + + return true; + } + if (native_os == .linux) { + while (true) { + var wsz: linux.winsize = undefined; + const fd: usize = @bitCast(@as(isize, handle)); + const rc = linux.syscall3(.ioctl, fd, linux.T.IOCGWINSZ, @intFromPtr(&wsz)); + switch (linux.E.init(rc)) { + .SUCCESS => return true, + .INTR => continue, + else => return false, + } + } + } + return system.isatty(handle) != 0; +} + +pub const SocketError = error{ + /// Permission to create a socket of the specified type and/or + /// pro‐tocol is denied. + PermissionDenied, + + /// The implementation does not support the specified address family. + AddressFamilyNotSupported, + + /// Unknown protocol, or protocol family not available. + ProtocolFamilyNotAvailable, + + /// 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, + + /// Insufficient memory is available. The socket cannot be created until sufficient + /// resources are freed. + SystemResources, + + /// The protocol type or the specified protocol is not supported within this domain. + ProtocolNotSupported, + + /// The socket type is not supported by the protocol. + SocketTypeNotSupported, +} || UnexpectedError; + +pub fn socket(domain: u32, socket_type: u32, protocol: u32) SocketError!socket_t { + if (native_os == .windows) { + // NOTE: windows translates the SOCK.NONBLOCK/SOCK.CLOEXEC flags into + // windows-analagous operations + const filtered_sock_type = socket_type & ~@as(u32, SOCK.NONBLOCK | SOCK.CLOEXEC); + const flags: u32 = if ((socket_type & SOCK.CLOEXEC) != 0) + windows.ws2_32.WSA_FLAG_NO_HANDLE_INHERIT + else + 0; + const rc = try windows.WSASocketW( + @bitCast(domain), + @bitCast(filtered_sock_type), + @bitCast(protocol), + null, + 0, + flags, + ); + errdefer windows.closesocket(rc) catch unreachable; + if ((socket_type & SOCK.NONBLOCK) != 0) { + var mode: c_ulong = 1; // nonblocking + if (windows.ws2_32.SOCKET_ERROR == windows.ws2_32.ioctlsocket(rc, windows.ws2_32.FIONBIO, &mode)) { + switch (windows.ws2_32.WSAGetLastError()) { + // have not identified any error codes that should be handled yet + else => unreachable, + } + } + } + return rc; + } + + const have_sock_flags = !builtin.target.isDarwin(); + 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.PermissionDenied, + .AFNOSUPPORT => return error.AddressFamilyNotSupported, + .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 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. + NetworkSubsystemFailed, + + /// The socket is not connected (connection-oriented sockets only). + SocketNotConnected, + 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()) { + .WSAECONNABORTED => return error.ConnectionAborted, + .WSAECONNRESET => return error.ConnectionResetByPeer, + .WSAEINPROGRESS => return error.BlockingOperationInProgress, + .WSAEINVAL => unreachable, + .WSAENETDOWN => return error.NetworkSubsystemFailed, + .WSAENOTCONN => return error.SocketNotConnected, + .WSAENOTSOCK => unreachable, + .WSANOTINITIALISED => unreachable, + else => |err| return windows.unexpectedWSAError(err), + }; + } 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.SocketNotConnected, + .NOTSOCK => unreachable, + .NOBUFS => return error.SystemResources, + else => |err| return unexpectedErrno(err), + } + } +} + +pub const BindError = error{ + /// The address is protected, and the user is not the superuser. + /// For UNIX domain sockets: Search permission is denied on a component + /// of the path prefix. + AccessDenied, + + /// The given address is already in use, or in the case of Internet domain sockets, + /// The port number was specified as zero in the socket + /// address structure, but, upon attempting to bind 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 ip(7). + AddressInUse, + + /// A nonexistent interface was requested or the requested address was not local. + AddressNotAvailable, + + /// The address is not valid for the address family of socket. + AddressFamilyNotSupported, + + /// Too many symbolic links were encountered in resolving addr. + SymLinkLoop, + + /// addr is too long. + NameTooLong, + + /// A component in the directory prefix of the socket pathname does not exist. + FileNotFound, + + /// Insufficient kernel memory was available. + SystemResources, + + /// A component of the path prefix is not a directory. + NotDir, + + /// The socket inode would reside on a read-only filesystem. + ReadOnlyFileSystem, + + /// The network subsystem has failed. + NetworkSubsystemFailed, + + FileDescriptorNotASocket, + + AlreadyBound, +} || UnexpectedError; + +/// addr is `*const T` where T is one of the sockaddr +pub fn bind(sock: socket_t, addr: *const sockaddr, len: socklen_t) BindError!void { + if (native_os == .windows) { + const rc = windows.bind(sock, addr, len); + if (rc == windows.ws2_32.SOCKET_ERROR) { + switch (windows.ws2_32.WSAGetLastError()) { + .WSANOTINITIALISED => unreachable, // not initialized WSA + .WSAEACCES => return error.AccessDenied, + .WSAEADDRINUSE => return error.AddressInUse, + .WSAEADDRNOTAVAIL => return error.AddressNotAvailable, + .WSAENOTSOCK => return error.FileDescriptorNotASocket, + .WSAEFAULT => unreachable, // invalid pointers + .WSAEINVAL => return error.AlreadyBound, + .WSAENOBUFS => return error.SystemResources, + .WSAENETDOWN => return error.NetworkSubsystemFailed, + else => |err| return windows.unexpectedWSAError(err), + } + unreachable; + } + return; + } 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.AddressFamilyNotSupported, + .ADDRNOTAVAIL => return error.AddressNotAvailable, + .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{ + /// Another socket is already listening on the same port. + /// For Internet domain 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). + AddressInUse, + + /// The file descriptor sockfd does not refer to a socket. + FileDescriptorNotASocket, + + /// The socket is not of a type that supports the listen() operation. + OperationNotSupported, + + /// The network subsystem has failed. + NetworkSubsystemFailed, + + /// Ran out of system resources + /// On Windows it can either run out of socket descriptors or buffer space + SystemResources, + + /// Already connected + AlreadyConnected, + + /// Socket has not been bound yet + SocketNotBound, +} || UnexpectedError; + +pub fn listen(sock: socket_t, backlog: u31) ListenError!void { + if (native_os == .windows) { + const rc = windows.listen(sock, backlog); + if (rc == windows.ws2_32.SOCKET_ERROR) { + switch (windows.ws2_32.WSAGetLastError()) { + .WSANOTINITIALISED => unreachable, // not initialized WSA + .WSAENETDOWN => return error.NetworkSubsystemFailed, + .WSAEADDRINUSE => return error.AddressInUse, + .WSAEISCONN => return error.AlreadyConnected, + .WSAEINVAL => return error.SocketNotBound, + .WSAEMFILE, .WSAENOBUFS => return error.SystemResources, + .WSAENOTSOCK => return error.FileDescriptorNotASocket, + .WSAEOPNOTSUPP => return error.OperationNotSupported, + .WSAEINPROGRESS => unreachable, + else => |err| return windows.unexpectedWSAError(err), + } + } + return; + } 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.OperationNotSupported, + else => |err| return unexpectedErrno(err), + } + } +} + +pub const AcceptError = error{ + ConnectionAborted, + + /// The file descriptor sockfd does not refer to a socket. + FileDescriptorNotASocket, + + /// 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, + + /// Not enough free memory. This often means that the memory allocation is limited + /// by the socket buffer limits, not by the system memory. + SystemResources, + + /// Socket is not listening for new connections. + SocketNotListening, + + ProtocolFailure, + + /// Firewall rules forbid connection. + BlockedByFirewall, + + /// This error occurs when no global event loop is configured, + /// and accepting from the socket would block. + WouldBlock, + + /// An incoming connection was indicated, but was subsequently terminated by the + /// remote peer prior to accepting the call. + ConnectionResetByPeer, + + /// The network subsystem has failed. + NetworkSubsystemFailed, + + /// The referenced socket is not a type that supports connection-oriented service. + OperationNotSupported, +} || UnexpectedError; + +/// Accept a connection on a socket. +/// If `sockfd` is opened in non blocking mode, the function will +/// return error.WouldBlock when EAGAIN is received. +pub fn accept( + /// This argument is a socket that has been created with `socket`, bound to a local address + /// with `bind`, and is listening for connections after a `listen`. + sock: socket_t, + /// This argument is a pointer to a sockaddr structure. This structure is filled in with the + /// address of the peer socket, as known to the communications layer. The exact format of the + /// address returned addr is determined by the socket's address family (see `socket` and the + /// respective protocol man pages). + addr: ?*sockaddr, + /// This argument is a value-result argument: the caller must initialize it to contain the + /// size (in bytes) of the structure pointed to by addr; on return it will contain the actual size + /// of the peer address. + /// + /// The returned address is truncated if the buffer provided is too small; in this case, `addr_size` + /// will return a value greater than was supplied to the call. + addr_size: ?*socklen_t, + /// The following values can be bitwise ORed in flags to obtain different behavior: + /// * `SOCK.NONBLOCK` - Set the `NONBLOCK` file status flag on the open file description (see `open`) + /// referred to by the new file descriptor. Using this flag saves extra calls to `fcntl` to achieve + /// the same result. + /// * `SOCK.CLOEXEC` - Set the close-on-exec (`FD_CLOEXEC`) flag on the new file descriptor. See the + /// description of the `CLOEXEC` flag in `open` for reasons why this may be useful. + flags: u32, +) AcceptError!socket_t { + const have_accept4 = !(builtin.target.isDarwin() or native_os == .windows); + 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 if (native_os == .windows) + windows.accept(sock, addr, addr_size) + else + system.accept(sock, addr, addr_size); + + if (native_os == .windows) { + if (rc == windows.ws2_32.INVALID_SOCKET) { + switch (windows.ws2_32.WSAGetLastError()) { + .WSANOTINITIALISED => unreachable, // not initialized WSA + .WSAECONNRESET => return error.ConnectionResetByPeer, + .WSAEFAULT => unreachable, + .WSAEINVAL => return error.SocketNotListening, + .WSAEMFILE => return error.ProcessFdQuotaExceeded, + .WSAENETDOWN => return error.NetworkSubsystemFailed, + .WSAENOBUFS => return error.FileDescriptorNotASocket, + .WSAEOPNOTSUPP => return error.OperationNotSupported, + .WSAEWOULDBLOCK => return error.WouldBlock, + else => |err| return windows.unexpectedWSAError(err), + } + } else { + break rc; + } + } 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) { + // TODO: Find out if this is supported for sockets + } else { + var fd_flags = fcntl(sock, F.GETFD, 0) catch |err| switch (err) { + error.FileBusy => unreachable, + error.Locked => unreachable, + error.PermissionDenied => unreachable, + error.DeadLock => unreachable, + error.LockedRegionLimitExceeded => unreachable, + else => |e| return e, + }; + fd_flags |= FD_CLOEXEC; + _ = fcntl(sock, F.SETFD, fd_flags) catch |err| switch (err) { + error.FileBusy => unreachable, + error.Locked => unreachable, + error.PermissionDenied => unreachable, + error.DeadLock => unreachable, + error.LockedRegionLimitExceeded => unreachable, + else => |e| return e, + }; + } + } + if ((flags & SOCK.NONBLOCK) != 0) { + if (native_os == .windows) { + var mode: c_ulong = 1; + if (windows.ws2_32.ioctlsocket(sock, windows.ws2_32.FIONBIO, &mode) == windows.ws2_32.SOCKET_ERROR) { + switch (windows.ws2_32.WSAGetLastError()) { + .WSANOTINITIALISED => unreachable, + .WSAENETDOWN => return error.NetworkSubsystemFailed, + .WSAENOTSOCK => return error.FileDescriptorNotASocket, + // TODO: handle more errors + else => |err| return windows.unexpectedWSAError(err), + } + } + } else { + var fl_flags = fcntl(sock, F.GETFL, 0) catch |err| switch (err) { + error.FileBusy => unreachable, + error.Locked => unreachable, + error.PermissionDenied => unreachable, + error.DeadLock => unreachable, + error.LockedRegionLimitExceeded => unreachable, + else => |e| return e, + }; + fl_flags |= 1 << @bitOffsetOf(O, "NONBLOCK"); + _ = fcntl(sock, F.SETFL, fl_flags) catch |err| switch (err) { + error.FileBusy => unreachable, + error.Locked => unreachable, + error.PermissionDenied => unreachable, + error.DeadLock => unreachable, + error.LockedRegionLimitExceeded => unreachable, + else => |e| return e, + }; + } + } +} + +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: ?*linux.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: []linux.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, + SystemFdQuotaExceeded, +} || UnexpectedError; + +pub fn eventfd(initval: u32, flags: u32) EventFdError!i32 { + const rc = system.eventfd(initval, flags); + switch (errno(rc)) { + .SUCCESS => return @intCast(rc), + else => |err| return unexpectedErrno(err), + + .INVAL => unreachable, // invalid parameters + .MFILE => return error.ProcessFdQuotaExceeded, + .NFILE => return error.SystemFdQuotaExceeded, + .NODEV => return error.SystemResources, + .NOMEM => return error.SystemResources, + } +} + +pub const GetSockNameError = error{ + /// Insufficient resources were available in the system to perform the operation. + SystemResources, + + /// The network subsystem has failed. + NetworkSubsystemFailed, + + /// Socket hasn't been bound yet + SocketNotBound, + + 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()) { + .WSANOTINITIALISED => unreachable, + .WSAENETDOWN => return error.NetworkSubsystemFailed, + .WSAEFAULT => unreachable, // addr or addrlen have invalid pointers or addrlen points to an incorrect value + .WSAENOTSOCK => return error.FileDescriptorNotASocket, + .WSAEINVAL => 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); + if (rc == windows.ws2_32.SOCKET_ERROR) { + switch (windows.ws2_32.WSAGetLastError()) { + .WSANOTINITIALISED => unreachable, + .WSAENETDOWN => return error.NetworkSubsystemFailed, + .WSAEFAULT => unreachable, // addr or addrlen have invalid pointers or addrlen points to an incorrect value + .WSAENOTSOCK => return error.FileDescriptorNotASocket, + .WSAEINVAL => return error.SocketNotBound, + else => |err| return windows.unexpectedWSAError(err), + } + } + return; + } else { + const rc = system.getpeername(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 const ConnectError = error{ + /// For UNIX domain sockets, which are identified by pathname: Write permission is denied on the socket + /// file, or search permission is denied for one of the directories in the path prefix. + /// or + /// The user tried to connect to a broadcast address without having the socket broadcast flag enabled or + /// the connection request failed because of a local firewall rule. + PermissionDenied, + + /// Local address is already in use. + AddressInUse, + + /// (Internet domain 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). + AddressNotAvailable, + + /// The passed address didn't have the correct address family in its sa_family field. + AddressFamilyNotSupported, + + /// Insufficient entries in the routing cache. + SystemResources, + + /// A connect() on a stream socket found no one listening on the remote address. + ConnectionRefused, + + /// Network is unreachable. + NetworkUnreachable, + + /// Timeout while attempting connection. The server may be too busy to accept new connections. Note + /// that for IP sockets the timeout may be very long when syncookies are enabled on the server. + ConnectionTimedOut, + + /// This error occurs when no global event loop is configured, + /// and connecting to the socket would block. + WouldBlock, + + /// The given path for the unix socket does not exist. + FileNotFound, + + /// Connection was reset by peer before connect could complete. + ConnectionResetByPeer, + + /// Socket is non-blocking and already has a pending connection in progress. + ConnectionPending, +} || UnexpectedError; + +/// Initiate a connection on a socket. +/// If `sockfd` is opened in non blocking mode, the function will +/// return error.WouldBlock when EAGAIN or EINPROGRESS is received. +pub fn connect(sock: socket_t, sock_addr: *const sockaddr, len: socklen_t) ConnectError!void { + if (native_os == .windows) { + const rc = windows.ws2_32.connect(sock, sock_addr, @intCast(len)); + if (rc == 0) return; + switch (windows.ws2_32.WSAGetLastError()) { + .WSAEADDRINUSE => return error.AddressInUse, + .WSAEADDRNOTAVAIL => return error.AddressNotAvailable, + .WSAECONNREFUSED => return error.ConnectionRefused, + .WSAECONNRESET => return error.ConnectionResetByPeer, + .WSAETIMEDOUT => return error.ConnectionTimedOut, + .WSAEHOSTUNREACH, // TODO: should we return NetworkUnreachable in this case as well? + .WSAENETUNREACH, + => return error.NetworkUnreachable, + .WSAEFAULT => unreachable, + .WSAEINVAL => unreachable, + .WSAEISCONN => unreachable, + .WSAENOTSOCK => unreachable, + .WSAEWOULDBLOCK => return error.WouldBlock, + .WSAEACCES => unreachable, + .WSAENOBUFS => return error.SystemResources, + .WSAEAFNOSUPPORT => return error.AddressFamilyNotSupported, + else => |err| return windows.unexpectedWSAError(err), + } + return; + } + + while (true) { + switch (errno(system.connect(sock, sock_addr, len))) { + .SUCCESS => return, + .ACCES => return error.PermissionDenied, + .PERM => return error.PermissionDenied, + .ADDRINUSE => return error.AddressInUse, + .ADDRNOTAVAIL => return error.AddressNotAvailable, + .AFNOSUPPORT => return error.AddressFamilyNotSupported, + .AGAIN, .INPROGRESS => return error.WouldBlock, + .ALREADY => return error.ConnectionPending, + .BADF => unreachable, // sockfd is not a valid open file descriptor. + .CONNREFUSED => return error.ConnectionRefused, + .CONNRESET => return error.ConnectionResetByPeer, + .FAULT => unreachable, // The socket structure address is outside the user's address space. + .INTR => continue, + .ISCONN => unreachable, // 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.ConnectionTimedOut, + .NOENT => return error.FileNotFound, // Returned when socket is AF.UNIX and the given path does not exist. + .CONNABORTED => unreachable, // Tried to reuse socket that previously received error.ConnectionRefused. + 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.PermissionDenied, + .PERM => return error.PermissionDenied, + .ADDRINUSE => return error.AddressInUse, + .ADDRNOTAVAIL => return error.AddressNotAvailable, + .AFNOSUPPORT => return error.AddressFamilyNotSupported, + .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 => unreachable, // 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.ConnectionTimedOut, + .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 WaitPidResult = struct { + pid: pid_t, + status: u32, +}; + +/// Use this version of the `waitpid` wrapper if you spawned your child process using explicit +/// `fork` and `execve` method. +pub fn waitpid(pid: pid_t, flags: u32) WaitPidResult { + var status: if (builtin.link_libc) c_int else u32 = undefined; + while (true) { + const rc = system.waitpid(pid, &status, @intCast(flags)); + switch (errno(rc)) { + .SUCCESS => return .{ + .pid = @intCast(rc), + .status = @bitCast(status), + }, + .INTR => continue, + .CHILD => unreachable, // The process specified does not exist. It would be a race condition to handle this error. + .INVAL => unreachable, // Invalid flags. + else => unreachable, + } + } +} + +pub fn wait4(pid: pid_t, flags: u32, ru: ?*rusage) WaitPidResult { + var status: if (builtin.link_libc) c_int else u32 = undefined; + while (true) { + const rc = system.wait4(pid, &status, @intCast(flags), ru); + switch (errno(rc)) { + .SUCCESS => return .{ + .pid = @intCast(rc), + .status = @bitCast(status), + }, + .INTR => continue, + .CHILD => unreachable, // The process specified does not exist. It would be a race condition to handle this error. + .INVAL => unreachable, // Invalid flags. + else => unreachable, + } + } +} + +pub const FStatError = error{ + SystemResources, + + /// In WASI, this error may occur when the file descriptor does + /// not hold the required rights to get its filestat information. + AccessDenied, +} || UnexpectedError; + +/// Return information about a file descriptor. +pub fn fstat(fd: fd_t) FStatError!Stat { + if (native_os == .wasi and !builtin.link_libc) { + return Stat.fromFilestat(try fstat_wasi(fd)); + } + if (native_os == .windows) { + @compileError("fstat is not yet implemented on Windows"); + } + + const fstat_sym = if (lfs64_abi) system.fstat64 else system.fstat; + var stat = mem.zeroes(Stat); + switch (errno(fstat_sym(fd, &stat))) { + .SUCCESS => return stat, + .INVAL => unreachable, + .BADF => unreachable, // Always a race condition. + .NOMEM => return error.SystemResources, + .ACCES => return error.AccessDenied, + else => |err| return unexpectedErrno(err), + } +} + +fn fstat_wasi(fd: fd_t) FStatError!wasi.filestat_t { + var stat: wasi.filestat_t = undefined; + switch (wasi.fd_filestat_get(fd, &stat)) { + .SUCCESS => return stat, + .INVAL => unreachable, + .BADF => unreachable, // Always a race condition. + .NOMEM => return error.SystemResources, + .ACCES => return error.AccessDenied, + .NOTCAPABLE => return error.AccessDenied, + else => |err| return unexpectedErrno(err), + } +} + +pub const FStatAtError = FStatError || error{ + NameTooLong, + FileNotFound, + SymLinkLoop, + /// WASI-only; file paths must be valid UTF-8. + InvalidUtf8, +}; + +/// 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` and `fstatat_wasi`. +pub fn fstatat(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!Stat { + if (native_os == .wasi and !builtin.link_libc) { + const filestat = try fstatat_wasi(dirfd, pathname, .{ + .SYMLINK_FOLLOW = (flags & AT.SYMLINK_NOFOLLOW) == 0, + }); + return Stat.fromFilestat(filestat); + } 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) { + const filestat = try fstatat_wasi(dirfd, mem.sliceTo(pathname, 0), .{ + .SYMLINK_FOLLOW = (flags & AT.SYMLINK_NOFOLLOW) == 0, + }); + return Stat.fromFilestat(filestat); + } + + const fstatat_sym = if (lfs64_abi) system.fstatat64 else system.fstatat; + var stat = mem.zeroes(Stat); + switch (errno(fstatat_sym(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.AccessDenied, + .FAULT => unreachable, + .NAMETOOLONG => return error.NameTooLong, + .LOOP => return error.SymLinkLoop, + .NOENT => return error.FileNotFound, + .NOTDIR => return error.FileNotFound, + .ILSEQ => |err| if (native_os == .wasi) + return error.InvalidUtf8 + else + return unexpectedErrno(err), + else => |err| return unexpectedErrno(err), + } +} + +/// WASI-only. Same as `fstatat` but targeting WASI. +/// `pathname` should be encoded as valid UTF-8. +/// See also `fstatat`. +fn fstatat_wasi(dirfd: fd_t, pathname: []const u8, flags: wasi.lookupflags_t) FStatAtError!wasi.filestat_t { + var stat: wasi.filestat_t = undefined; + switch (wasi.path_filestat_get(dirfd, flags, pathname.ptr, pathname.len, &stat)) { + .SUCCESS => return stat, + .INVAL => unreachable, + .BADF => unreachable, // Always a race condition. + .NOMEM => return error.SystemResources, + .ACCES => return error.AccessDenied, + .FAULT => unreachable, + .NAMETOOLONG => return error.NameTooLong, + .NOENT => return error.FileNotFound, + .NOTDIR => return error.FileNotFound, + .NOTCAPABLE => return error.AccessDenied, + .ILSEQ => return error.InvalidUtf8, + else => |err| return unexpectedErrno(err), + } +} + +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, + + /// 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, + SystemResources, +} || UnexpectedError; + +/// initialize an inotify instance +pub fn inotify_init1(flags: u32) INotifyInitError!i32 { + const rc = system.inotify_init1(flags); + switch (errno(rc)) { + .SUCCESS => return @intCast(rc), + .INVAL => unreachable, + .MFILE => return error.ProcessFdQuotaExceeded, + .NFILE => return error.SystemFdQuotaExceeded, + .NOMEM => return error.SystemResources, + else => |err| return unexpectedErrno(err), + } +} + +pub const INotifyAddWatchError = error{ + AccessDenied, + NameTooLong, + FileNotFound, + SystemResources, + UserResourceLimitReached, + NotDir, + WatchAlreadyExists, +} || UnexpectedError; + +/// add a watch to an initialized inotify instance +pub fn inotify_add_watch(inotify_fd: i32, pathname: []const u8, mask: u32) INotifyAddWatchError!i32 { + const pathname_c = try toPosixPath(pathname); + return inotify_add_watchZ(inotify_fd, &pathname_c, mask); +} + +/// Same as `inotify_add_watch` except pathname is null-terminated. +pub fn inotify_add_watchZ(inotify_fd: i32, pathname: [*:0]const u8, mask: u32) INotifyAddWatchError!i32 { + const rc = system.inotify_add_watch(inotify_fd, pathname, mask); + switch (errno(rc)) { + .SUCCESS => return @intCast(rc), + .ACCES => return error.AccessDenied, + .BADF => unreachable, + .FAULT => unreachable, + .INVAL => unreachable, + .NAMETOOLONG => return error.NameTooLong, + .NOENT => return error.FileNotFound, + .NOMEM => return error.SystemResources, + .NOSPC => return error.UserResourceLimitReached, + .NOTDIR => return error.NotDir, + .EXIST => return error.WatchAlreadyExists, + else => |err| return unexpectedErrno(err), + } +} + +/// remove an existing watch from an inotify instance +pub fn inotify_rm_watch(inotify_fd: i32, wd: i32) void { + switch (errno(system.inotify_rm_watch(inotify_fd, wd))) { + .SUCCESS => return, + .BADF => unreachable, + .INVAL => unreachable, + else => unreachable, + } +} + +pub const FanotifyInitError = error{ + ProcessFdQuotaExceeded, + SystemFdQuotaExceeded, + SystemResources, + OperationNotSupported, + PermissionDenied, +} || UnexpectedError; + +pub fn fanotify_init(flags: u32, event_f_flags: u32) FanotifyInitError!i32 { + const rc = system.fanotify_init(flags, event_f_flags); + switch (errno(rc)) { + .SUCCESS => return @intCast(rc), + .INVAL => unreachable, + .MFILE => return error.ProcessFdQuotaExceeded, + .NFILE => return error.SystemFdQuotaExceeded, + .NOMEM => return error.SystemResources, + .NOSYS => return error.OperationNotSupported, + .PERM => return error.PermissionDenied, + else => |err| return unexpectedErrno(err), + } +} + +pub const FanotifyMarkError = error{ + MarkAlreadyExists, + IsDir, + NotAssociatedWithFileSystem, + FileNotFound, + SystemResources, + UserMarkQuotaExceeded, + NotImplemented, + NotDir, + OperationNotSupported, + PermissionDenied, + NotSameFileSystem, + NameTooLong, +} || UnexpectedError; + +pub fn fanotify_mark(fanotify_fd: i32, flags: u32, mask: u64, dirfd: i32, pathname: ?[]const u8) FanotifyMarkError!void { + if (pathname) |path| { + const path_c = try toPosixPath(path); + return fanotify_markZ(fanotify_fd, flags, mask, dirfd, &path_c); + } + + return fanotify_markZ(fanotify_fd, flags, mask, dirfd, null); +} + +pub fn fanotify_markZ(fanotify_fd: i32, flags: u32, mask: u64, dirfd: i32, pathname: ?[*:0]const u8) FanotifyMarkError!void { + const rc = system.fanotify_mark(fanotify_fd, flags, mask, dirfd, pathname); + switch (errno(rc)) { + .SUCCESS => return, + .BADF => unreachable, + .EXIST => return error.MarkAlreadyExists, + .INVAL => unreachable, + .ISDIR => return error.IsDir, + .NODEV => return error.NotAssociatedWithFileSystem, + .NOENT => return error.FileNotFound, + .NOMEM => return error.SystemResources, + .NOSPC => return error.UserMarkQuotaExceeded, + .NOSYS => return error.NotImplemented, + .NOTDIR => return error.NotDir, + .OPNOTSUPP => return error.OperationNotSupported, + .PERM => return error.PermissionDenied, + .XDEV => return error.NotSameFileSystem, + else => |err| return unexpectedErrno(err), + } +} + +pub const MProtectError = error{ + /// The memory cannot be given the specified access. This can happen, for example, if you + /// mmap(2) a file to which you have read-only access, then ask mprotect() to mark it + /// PROT_WRITE. + AccessDenied, + + /// Changing the protection of a memory region would result in the total number of map‐ + /// pings with distinct attributes (e.g., read versus read/write protection) exceeding the + /// allowed maximum. (For example, making the protection of a range PROT_READ in the mid‐ + /// dle of a region currently protected as PROT_READ|PROT_WRITE would result in three map‐ + /// pings: two read/write mappings at each end and a read-only mapping in the middle.) + OutOfMemory, +} || UnexpectedError; + +/// `memory.len` must be page-aligned. +pub fn mprotect(memory: []align(mem.page_size) u8, protection: u32) MProtectError!void { + assert(mem.isAligned(memory.len, mem.page_size)); + if (native_os == .windows) { + const win_prot: windows.DWORD = switch (@as(u3, @truncate(protection))) { + 0b000 => windows.PAGE_NOACCESS, + 0b001 => windows.PAGE_READONLY, + 0b010 => unreachable, // +w -r not allowed + 0b011 => windows.PAGE_READWRITE, + 0b100 => windows.PAGE_EXECUTE, + 0b101 => windows.PAGE_EXECUTE_READ, + 0b110 => unreachable, // +w -r not allowed + 0b111 => windows.PAGE_EXECUTE_READWRITE, + }; + var old: windows.DWORD = undefined; + windows.VirtualProtect(memory.ptr, memory.len, win_prot, &old) catch |err| switch (err) { + error.InvalidAddress => return error.AccessDenied, + error.Unexpected => return error.Unexpected, + }; + } else { + switch (errno(system.mprotect(memory.ptr, memory.len, protection))) { + .SUCCESS => return, + .INVAL => unreachable, + .ACCES => return error.AccessDenied, + .NOMEM => return error.OutOfMemory, + else => |err| return unexpectedErrno(err), + } + } +} + +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, + + /// A file descriptor refers to a non-regular file. Or a file mapping was requested, + /// but the file descriptor is not open for reading. Or `MAP.SHARED` was requested + /// and `PROT_WRITE` is set, but the file descriptor is not open in `RDWR` mode. + /// Or `PROT_WRITE` is set, but the file is append-only. + AccessDenied, + + /// The `prot` argument asks for `PROT_EXEC` but the mapped area belongs to a file on + /// a filesystem that was mounted no-exec. + PermissionDenied, + LockedMemoryLimitExceeded, + ProcessFdQuotaExceeded, + SystemFdQuotaExceeded, + OutOfMemory, +} || UnexpectedError; + +/// Map files or devices into memory. +/// `length` does not need to be aligned. +/// Use of a mapped region can result in these signals: +/// * SIGSEGV - Attempted write into a region mapped as read-only. +/// * SIGBUS - Attempted access to a portion of the buffer that does not correspond to the file +pub fn mmap( + ptr: ?[*]align(mem.page_size) u8, + length: usize, + prot: u32, + flags: system.MAP, + fd: fd_t, + offset: u64, +) MMapError![]align(mem.page_size) u8 { + const mmap_sym = if (lfs64_abi) system.mmap64 else system.mmap; + const rc = mmap_sym(ptr, length, prot, @bitCast(flags), fd, @bitCast(offset)); + const err: E = if (builtin.link_libc) blk: { + if (rc != std.c.MAP_FAILED) return @as([*]align(mem.page_size) u8, @ptrCast(@alignCast(rc)))[0..length]; + break :blk @enumFromInt(system._errno().*); + } else blk: { + const err = errno(rc); + if (err == .SUCCESS) return @as([*]align(mem.page_size) u8, @ptrFromInt(rc))[0..length]; + break :blk err; + }; + switch (err) { + .SUCCESS => unreachable, + .TXTBSY => return error.AccessDenied, + .ACCES => return error.AccessDenied, + .PERM => return error.PermissionDenied, + .AGAIN => return error.LockedMemoryLimitExceeded, + .BADF => unreachable, // Always a race condition. + .OVERFLOW => unreachable, // The number of pages used for length + offset would overflow. + .NODEV => return error.MemoryMappingNotSupported, + .INVAL => unreachable, // Invalid parameters to mmap() + .MFILE => return error.ProcessFdQuotaExceeded, + .NFILE => return error.SystemFdQuotaExceeded, + .NOMEM => return error.OutOfMemory, + else => return unexpectedErrno(err), + } +} + +/// Deletes the mappings for the specified address range, causing +/// further references to addresses within the range to generate invalid memory references. +/// Note that while POSIX allows unmapping a region in the middle of an existing mapping, +/// Zig's munmap function does not, for two reasons: +/// * It violates the Zig principle that resource deallocation must succeed. +/// * The Windows function, VirtualFree, has this restriction. +pub fn munmap(memory: []align(mem.page_size) const u8) void { + switch (errno(system.munmap(memory.ptr, memory.len))) { + .SUCCESS => return, + .INVAL => unreachable, // Invalid parameters. + .NOMEM => unreachable, // Attempted to unmap a region in the middle of an existing mapping. + else => unreachable, + } +} + +pub const MSyncError = error{ + UnmappedMemory, +} || UnexpectedError; + +pub fn msync(memory: []align(mem.page_size) u8, flags: i32) MSyncError!void { + switch (errno(system.msync(memory.ptr, memory.len, flags))) { + .SUCCESS => return, + .NOMEM => return error.UnmappedMemory, // Unsuccessful, provided pointer does not point mapped memory + .INVAL => unreachable, // Invalid parameters. + else => unreachable, + } +} + +pub const AccessError = error{ + PermissionDenied, + FileNotFound, + NameTooLong, + InputOutput, + SystemResources, + BadPathName, + FileBusy, + SymLinkLoop, + ReadOnlyFileSystem, + /// WASI-only; file paths must be valid UTF-8. + InvalidUtf8, + /// Windows-only; file paths provided by the user must be valid WTF-8. + /// https://simonsapin.github.io/wtf-8/ + InvalidWtf8, +} || UnexpectedError; + +/// check user's permissions for a file +/// +/// * On Windows, asserts `path` is valid [WTF-8](https://simonsapin.github.io/wtf-8/). +/// * On WASI, invalid UTF-8 passed to `path` causes `error.InvalidUtf8`. +/// * On other platforms, `path` is an opaque sequence of bytes with no particular encoding. +/// +/// On Windows, `mode` is ignored. This is a POSIX API that is only partially supported by +/// Windows. See `fs` for the cross-platform file system API. +pub fn access(path: []const u8, mode: u32) AccessError!void { + if (native_os == .windows) { + const path_w = windows.sliceToPrefixedFileW(null, path) catch |err| switch (err) { + error.AccessDenied => return error.PermissionDenied, + else => |e| return e, + }; + _ = try windows.GetFileAttributesW(path_w.span().ptr); + return; + } else if (native_os == .wasi and !builtin.link_libc) { + return faccessat(wasi.AT.FDCWD, path, mode, 0); + } + const path_c = try toPosixPath(path); + return accessZ(&path_c, mode); +} + +/// Same as `access` except `path` is null-terminated. +pub fn accessZ(path: [*:0]const u8, mode: u32) AccessError!void { + if (native_os == .windows) { + const path_w = windows.cStrToPrefixedFileW(null, path) catch |err| switch (err) { + error.AccessDenied => return error.PermissionDenied, + else => |e| return e, + }; + _ = try windows.GetFileAttributesW(path_w.span().ptr); + return; + } else if (native_os == .wasi and !builtin.link_libc) { + return access(mem.sliceTo(path, 0), mode); + } + switch (errno(system.access(path, mode))) { + .SUCCESS => return, + .ACCES => return error.PermissionDenied, + .ROFS => return error.ReadOnlyFileSystem, + .LOOP => return error.SymLinkLoop, + .TXTBSY => return error.FileBusy, + .NOTDIR => return error.FileNotFound, + .NOENT => return error.FileNotFound, + .NAMETOOLONG => return error.NameTooLong, + .INVAL => unreachable, + .FAULT => unreachable, + .IO => return error.InputOutput, + .NOMEM => return error.SystemResources, + .ILSEQ => |err| if (native_os == .wasi) + return error.InvalidUtf8 + else + return unexpectedErrno(err), + else => |err| return unexpectedErrno(err), + } +} + +/// Check user's permissions for a file, based on an open directory handle. +/// +/// * On Windows, asserts `path` is valid [WTF-8](https://simonsapin.github.io/wtf-8/). +/// * On WASI, invalid UTF-8 passed to `path` causes `error.InvalidUtf8`. +/// * On other platforms, `path` is an opaque sequence of bytes with no particular encoding. +/// +/// On Windows, `mode` is ignored. This is a POSIX API that is only partially supported by +/// Windows. See `fs` for the cross-platform file system API. +pub fn faccessat(dirfd: fd_t, path: []const u8, mode: u32, flags: u32) AccessError!void { + if (native_os == .windows) { + const path_w = try windows.sliceToPrefixedFileW(dirfd, path); + return faccessatW(dirfd, path_w.span().ptr); + } else if (native_os == .wasi and !builtin.link_libc) { + const resolved: RelativePathWasi = .{ .dir_fd = dirfd, .relative_path = path }; + + const st = blk: { + break :blk fstatat_wasi(dirfd, path, .{ + .SYMLINK_FOLLOW = (flags & AT.SYMLINK_NOFOLLOW) == 0, + }); + } catch |err| switch (err) { + error.AccessDenied => return error.PermissionDenied, + else => |e| return e, + }; + + if (mode != F_OK) { + var directory: wasi.fdstat_t = undefined; + if (wasi.fd_fdstat_get(resolved.dir_fd, &directory) != .SUCCESS) { + return error.PermissionDenied; + } + + var rights: wasi.rights_t = .{}; + if (mode & R_OK != 0) { + if (st.filetype == .DIRECTORY) { + rights.FD_READDIR = true; + } else { + rights.FD_READ = true; + } + } + if (mode & W_OK != 0) { + rights.FD_WRITE = true; + } + // No validation for X_OK + + // https://github.com/ziglang/zig/issues/18882 + const rights_int: u64 = @bitCast(rights); + const inheriting_int: u64 = @bitCast(directory.fs_rights_inheriting); + if ((rights_int & inheriting_int) != rights_int) { + return error.PermissionDenied; + } + } + return; + } + const path_c = try toPosixPath(path); + return faccessatZ(dirfd, &path_c, mode, flags); +} + +/// Same as `faccessat` except the path parameter is null-terminated. +pub fn faccessatZ(dirfd: fd_t, path: [*:0]const u8, mode: u32, flags: u32) AccessError!void { + if (native_os == .windows) { + const path_w = try windows.cStrToPrefixedFileW(dirfd, path); + return faccessatW(dirfd, path_w.span().ptr); + } else if (native_os == .wasi and !builtin.link_libc) { + return faccessat(dirfd, mem.sliceTo(path, 0), mode, flags); + } + switch (errno(system.faccessat(dirfd, path, mode, flags))) { + .SUCCESS => return, + .ACCES => return error.PermissionDenied, + .ROFS => return error.ReadOnlyFileSystem, + .LOOP => return error.SymLinkLoop, + .TXTBSY => return error.FileBusy, + .NOTDIR => return error.FileNotFound, + .NOENT => return error.FileNotFound, + .NAMETOOLONG => return error.NameTooLong, + .INVAL => unreachable, + .FAULT => unreachable, + .IO => return error.InputOutput, + .NOMEM => return error.SystemResources, + .ILSEQ => |err| if (native_os == .wasi) + return error.InvalidUtf8 + else + return unexpectedErrno(err), + else => |err| return unexpectedErrno(err), + } +} + +/// Same as `faccessat` except asserts the target is Windows and the path parameter +/// is NtDll-prefixed, null-terminated, WTF-16 encoded. +pub fn faccessatW(dirfd: fd_t, sub_path_w: [*:0]const u16) AccessError!void { + if (sub_path_w[0] == '.' and sub_path_w[1] == 0) { + return; + } + if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) { + return; + } + + const path_len_bytes = cast(u16, mem.sliceTo(sub_path_w, 0).len * 2) orelse return error.NameTooLong; + var nt_name = windows.UNICODE_STRING{ + .Length = path_len_bytes, + .MaximumLength = path_len_bytes, + .Buffer = @constCast(sub_path_w), + }; + var attr = windows.OBJECT_ATTRIBUTES{ + .Length = @sizeOf(windows.OBJECT_ATTRIBUTES), + .RootDirectory = if (fs.path.isAbsoluteWindowsW(sub_path_w)) null else dirfd, + .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here. + .ObjectName = &nt_name, + .SecurityDescriptor = null, + .SecurityQualityOfService = null, + }; + var basic_info: windows.FILE_BASIC_INFORMATION = undefined; + switch (windows.ntdll.NtQueryAttributesFile(&attr, &basic_info)) { + .SUCCESS => return, + .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, + .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, + .OBJECT_NAME_INVALID => unreachable, + .INVALID_PARAMETER => unreachable, + .ACCESS_DENIED => return error.PermissionDenied, + .OBJECT_PATH_SYNTAX_BAD => unreachable, + else => |rc| return windows.unexpectedStatus(rc), + } +} + +pub const PipeError = error{ + SystemFdQuotaExceeded, + ProcessFdQuotaExceeded, +} || UnexpectedError; + +/// Creates a unidirectional data channel that can be used for interprocess communication. +pub fn pipe() PipeError![2]fd_t { + var fds: [2]fd_t = undefined; + switch (errno(system.pipe(&fds))) { + .SUCCESS => return fds, + .INVAL => unreachable, // Invalid parameters to pipe() + .FAULT => unreachable, // Invalid fds pointer + .NFILE => return error.SystemFdQuotaExceeded, + .MFILE => return error.ProcessFdQuotaExceeded, + else => |err| return unexpectedErrno(err), + } +} + +pub fn pipe2(flags: O) PipeError![2]fd_t { + // https://github.com/ziglang/zig/issues/19352 + if (@hasDecl(system, "pipe2")) { + var fds: [2]fd_t = undefined; + switch (errno(system.pipe2(&fds, flags))) { + .SUCCESS => return fds, + .INVAL => unreachable, // Invalid flags + .FAULT => unreachable, // Invalid fds pointer + .NFILE => return error.SystemFdQuotaExceeded, + .MFILE => return error.ProcessFdQuotaExceeded, + else => |err| return unexpectedErrno(err), + } + } + + const fds: [2]fd_t = try pipe(); + errdefer { + close(fds[0]); + close(fds[1]); + } + + // https://github.com/ziglang/zig/issues/18882 + if (@as(u32, @bitCast(flags)) == 0) + return fds; + + // CLOEXEC is special, it's a file descriptor flag and must be set using + // F.SETFD. + if (flags.CLOEXEC) { + for (fds) |fd| { + switch (errno(system.fcntl(fd, F.SETFD, @as(u32, FD_CLOEXEC)))) { + .SUCCESS => {}, + .INVAL => unreachable, // Invalid flags + .BADF => unreachable, // Always a race condition + else => |err| return unexpectedErrno(err), + } + } + } + + const new_flags: u32 = f: { + var new_flags = flags; + new_flags.CLOEXEC = false; + break :f @bitCast(new_flags); + }; + // Set every other flag affecting the file status using F.SETFL. + if (new_flags != 0) { + for (fds) |fd| { + switch (errno(system.fcntl(fd, F.SETFL, new_flags))) { + .SUCCESS => {}, + .INVAL => unreachable, // Invalid flags + .BADF => unreachable, // Always a race condition + else => |err| return unexpectedErrno(err), + } + } + } + + return fds; +} + +pub const SysCtlError = error{ + PermissionDenied, + SystemResources, + NameTooLong, + UnknownName, +} || UnexpectedError; + +pub fn sysctl( + name: []const c_int, + oldp: ?*anyopaque, + oldlenp: ?*usize, + newp: ?*anyopaque, + newlen: usize, +) SysCtlError!void { + if (native_os == .wasi) { + @panic("unsupported"); // TODO should be compile error, not panic + } + if (native_os == .haiku) { + @panic("unsupported"); // TODO should be compile error, not panic + } + + const name_len = cast(c_uint, name.len) orelse return error.NameTooLong; + switch (errno(system.sysctl(name.ptr, name_len, oldp, oldlenp, newp, newlen))) { + .SUCCESS => return, + .FAULT => unreachable, + .PERM => return error.PermissionDenied, + .NOMEM => return error.SystemResources, + .NOENT => return error.UnknownName, + else => |err| return unexpectedErrno(err), + } +} + +pub fn sysctlbynameZ( + name: [*:0]const u8, + oldp: ?*anyopaque, + oldlenp: ?*usize, + newp: ?*anyopaque, + newlen: usize, +) SysCtlError!void { + if (native_os == .wasi) { + @panic("unsupported"); // TODO should be compile error, not panic + } + if (native_os == .haiku) { + @panic("unsupported"); // TODO should be compile error, not panic + } + + switch (errno(system.sysctlbyname(name, oldp, oldlenp, newp, newlen))) { + .SUCCESS => return, + .FAULT => unreachable, + .PERM => return error.PermissionDenied, + .NOMEM => return error.SystemResources, + .NOENT => return error.UnknownName, + else => |err| return unexpectedErrno(err), + } +} + +pub fn gettimeofday(tv: ?*timeval, tz: ?*timezone) void { + switch (errno(system.gettimeofday(tv, tz))) { + .SUCCESS => return, + .INVAL => unreachable, + else => unreachable, + } +} + +pub const SeekError = error{ + Unseekable, + + /// In WASI, this error may occur when the file descriptor does + /// not hold the required rights to seek on it. + AccessDenied, +} || UnexpectedError; + +/// Repositions read/write file offset relative to the beginning. +pub fn lseek_SET(fd: fd_t, offset: u64) SeekError!void { + if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) { + var result: u64 = undefined; + switch (errno(system.llseek(fd, offset, &result, SEEK.SET))) { + .SUCCESS => return, + .BADF => unreachable, // always a race condition + .INVAL => return error.Unseekable, + .OVERFLOW => return error.Unseekable, + .SPIPE => return error.Unseekable, + .NXIO => return error.Unseekable, + else => |err| return unexpectedErrno(err), + } + } + if (native_os == .windows) { + return windows.SetFilePointerEx_BEGIN(fd, offset); + } + if (native_os == .wasi and !builtin.link_libc) { + var new_offset: wasi.filesize_t = undefined; + switch (wasi.fd_seek(fd, @bitCast(offset), .SET, &new_offset)) { + .SUCCESS => return, + .BADF => unreachable, // always a race condition + .INVAL => return error.Unseekable, + .OVERFLOW => return error.Unseekable, + .SPIPE => return error.Unseekable, + .NXIO => return error.Unseekable, + .NOTCAPABLE => return error.AccessDenied, + else => |err| return unexpectedErrno(err), + } + } + + const lseek_sym = if (lfs64_abi) system.lseek64 else system.lseek; + switch (errno(lseek_sym(fd, @bitCast(offset), SEEK.SET))) { + .SUCCESS => return, + .BADF => unreachable, // always a race condition + .INVAL => return error.Unseekable, + .OVERFLOW => return error.Unseekable, + .SPIPE => return error.Unseekable, + .NXIO => return error.Unseekable, + else => |err| return unexpectedErrno(err), + } +} + +/// Repositions read/write file offset relative to the current offset. +pub fn lseek_CUR(fd: fd_t, offset: i64) SeekError!void { + if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) { + var result: u64 = undefined; + switch (errno(system.llseek(fd, @bitCast(offset), &result, SEEK.CUR))) { + .SUCCESS => return, + .BADF => unreachable, // always a race condition + .INVAL => return error.Unseekable, + .OVERFLOW => return error.Unseekable, + .SPIPE => return error.Unseekable, + .NXIO => return error.Unseekable, + else => |err| return unexpectedErrno(err), + } + } + if (native_os == .windows) { + return windows.SetFilePointerEx_CURRENT(fd, offset); + } + if (native_os == .wasi and !builtin.link_libc) { + var new_offset: wasi.filesize_t = undefined; + switch (wasi.fd_seek(fd, offset, .CUR, &new_offset)) { + .SUCCESS => return, + .BADF => unreachable, // always a race condition + .INVAL => return error.Unseekable, + .OVERFLOW => return error.Unseekable, + .SPIPE => return error.Unseekable, + .NXIO => return error.Unseekable, + .NOTCAPABLE => return error.AccessDenied, + else => |err| return unexpectedErrno(err), + } + } + const lseek_sym = if (lfs64_abi) system.lseek64 else system.lseek; + switch (errno(lseek_sym(fd, @bitCast(offset), SEEK.CUR))) { + .SUCCESS => return, + .BADF => unreachable, // always a race condition + .INVAL => return error.Unseekable, + .OVERFLOW => return error.Unseekable, + .SPIPE => return error.Unseekable, + .NXIO => return error.Unseekable, + else => |err| return unexpectedErrno(err), + } +} + +/// Repositions read/write file offset relative to the end. +pub fn lseek_END(fd: fd_t, offset: i64) SeekError!void { + if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) { + var result: u64 = undefined; + switch (errno(system.llseek(fd, @bitCast(offset), &result, SEEK.END))) { + .SUCCESS => return, + .BADF => unreachable, // always a race condition + .INVAL => return error.Unseekable, + .OVERFLOW => return error.Unseekable, + .SPIPE => return error.Unseekable, + .NXIO => return error.Unseekable, + else => |err| return unexpectedErrno(err), + } + } + if (native_os == .windows) { + return windows.SetFilePointerEx_END(fd, offset); + } + if (native_os == .wasi and !builtin.link_libc) { + var new_offset: wasi.filesize_t = undefined; + switch (wasi.fd_seek(fd, offset, .END, &new_offset)) { + .SUCCESS => return, + .BADF => unreachable, // always a race condition + .INVAL => return error.Unseekable, + .OVERFLOW => return error.Unseekable, + .SPIPE => return error.Unseekable, + .NXIO => return error.Unseekable, + .NOTCAPABLE => return error.AccessDenied, + else => |err| return unexpectedErrno(err), + } + } + const lseek_sym = if (lfs64_abi) system.lseek64 else system.lseek; + switch (errno(lseek_sym(fd, @bitCast(offset), SEEK.END))) { + .SUCCESS => return, + .BADF => unreachable, // always a race condition + .INVAL => return error.Unseekable, + .OVERFLOW => return error.Unseekable, + .SPIPE => return error.Unseekable, + .NXIO => return error.Unseekable, + else => |err| return unexpectedErrno(err), + } +} + +/// Returns the read/write file offset relative to the beginning. +pub fn lseek_CUR_get(fd: fd_t) SeekError!u64 { + if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) { + var result: u64 = undefined; + switch (errno(system.llseek(fd, 0, &result, SEEK.CUR))) { + .SUCCESS => return result, + .BADF => unreachable, // always a race condition + .INVAL => return error.Unseekable, + .OVERFLOW => return error.Unseekable, + .SPIPE => return error.Unseekable, + .NXIO => return error.Unseekable, + else => |err| return unexpectedErrno(err), + } + } + if (native_os == .windows) { + return windows.SetFilePointerEx_CURRENT_get(fd); + } + if (native_os == .wasi and !builtin.link_libc) { + var new_offset: wasi.filesize_t = undefined; + switch (wasi.fd_seek(fd, 0, .CUR, &new_offset)) { + .SUCCESS => return new_offset, + .BADF => unreachable, // always a race condition + .INVAL => return error.Unseekable, + .OVERFLOW => return error.Unseekable, + .SPIPE => return error.Unseekable, + .NXIO => return error.Unseekable, + .NOTCAPABLE => return error.AccessDenied, + else => |err| return unexpectedErrno(err), + } + } + const lseek_sym = if (lfs64_abi) system.lseek64 else system.lseek; + const rc = lseek_sym(fd, 0, SEEK.CUR); + switch (errno(rc)) { + .SUCCESS => return @bitCast(rc), + .BADF => unreachable, // always a race condition + .INVAL => return error.Unseekable, + .OVERFLOW => return error.Unseekable, + .SPIPE => return error.Unseekable, + .NXIO => return error.Unseekable, + else => |err| return unexpectedErrno(err), + } +} + +pub const FcntlError = error{ + PermissionDenied, + FileBusy, + ProcessFdQuotaExceeded, + Locked, + DeadLock, + LockedRegionLimitExceeded, +} || UnexpectedError; + +pub fn fcntl(fd: fd_t, cmd: i32, arg: usize) FcntlError!usize { + while (true) { + const rc = system.fcntl(fd, cmd, arg); + switch (errno(rc)) { + .SUCCESS => return @intCast(rc), + .INTR => continue, + .AGAIN, .ACCES => return error.Locked, + .BADF => unreachable, + .BUSY => return error.FileBusy, + .INVAL => unreachable, // invalid parameters + .PERM => return error.PermissionDenied, + .MFILE => return error.ProcessFdQuotaExceeded, + .NOTDIR => unreachable, // invalid parameter + .DEADLK => return error.DeadLock, + .NOLCK => return error.LockedRegionLimitExceeded, + else => |err| return unexpectedErrno(err), + } + } +} + +pub const FlockError = error{ + WouldBlock, + + /// The kernel ran out of memory for allocating file locks + SystemResources, + + /// The underlying filesystem does not support file locks + FileLocksNotSupported, +} || UnexpectedError; + +/// Depending on the operating system `flock` may or may not interact with +/// `fcntl` locks made by other processes. +pub fn flock(fd: fd_t, operation: i32) FlockError!void { + while (true) { + const rc = system.flock(fd, operation); + switch (errno(rc)) { + .SUCCESS => return, + .BADF => unreachable, + .INTR => continue, + .INVAL => unreachable, // invalid parameters + .NOLCK => return error.SystemResources, + .AGAIN => return error.WouldBlock, // TODO: integrate with async instead of just returning an error + .OPNOTSUPP => return error.FileLocksNotSupported, + else => |err| return unexpectedErrno(err), + } + } +} + +pub const RealPathError = error{ + FileNotFound, + AccessDenied, + NameTooLong, + NotSupported, + NotDir, + SymLinkLoop, + InputOutput, + FileTooBig, + IsDir, + ProcessFdQuotaExceeded, + SystemFdQuotaExceeded, + NoDevice, + SystemResources, + NoSpaceLeft, + FileSystem, + BadPathName, + DeviceBusy, + + SharingViolation, + PipeBusy, + + /// Windows-only; file paths provided by the user must be valid WTF-8. + /// https://simonsapin.github.io/wtf-8/ + InvalidWtf8, + + /// On Windows, `\\server` or `\\server\share` was not found. + NetworkNotFound, + + PathAlreadyExists, + + /// On Windows, antivirus software is enabled by default. It can be + /// disabled, but Windows Update sometimes ignores the user's preference + /// and re-enables it. When enabled, antivirus software on Windows + /// intercepts file system operations and makes them significantly slower + /// in addition to possibly failing with this error code. + AntivirusInterference, + + /// On Windows, the volume does not contain a recognized file system. File + /// system drivers might not be loaded, or the volume may be corrupt. + UnrecognizedVolume, +} || UnexpectedError; + +/// Return the canonicalized absolute pathname. +/// +/// Expands all symbolic links and resolves references to `.`, `..`, and +/// extra `/` characters in `pathname`. +/// +/// On Windows, `pathname` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// +/// On other platforms, `pathname` is an opaque sequence of bytes with no particular encoding. +/// +/// The return value is a slice of `out_buffer`, but not necessarily from the beginning. +/// +/// See also `realpathZ` and `realpathW`. +/// +/// * On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// * On other platforms, the result is an opaque sequence of bytes with no particular encoding. +/// +/// Calling this function is usually a bug. +pub fn realpath(pathname: []const u8, out_buffer: *[max_path_bytes]u8) RealPathError![]u8 { + if (native_os == .windows) { + const pathname_w = try windows.sliceToPrefixedFileW(null, pathname); + return realpathW(pathname_w.span(), out_buffer); + } else if (native_os == .wasi and !builtin.link_libc) { + @compileError("WASI does not support os.realpath"); + } + const pathname_c = try toPosixPath(pathname); + return realpathZ(&pathname_c, out_buffer); +} + +/// Same as `realpath` except `pathname` is null-terminated. +/// +/// Calling this function is usually a bug. +pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[max_path_bytes]u8) RealPathError![]u8 { + if (native_os == .windows) { + const pathname_w = try windows.cStrToPrefixedFileW(null, pathname); + return realpathW(pathname_w.span(), out_buffer); + } else if (native_os == .wasi and !builtin.link_libc) { + return realpath(mem.sliceTo(pathname, 0), out_buffer); + } + if (!builtin.link_libc) { + const flags: O = switch (native_os) { + .linux => .{ + .NONBLOCK = true, + .CLOEXEC = true, + .PATH = true, + }, + else => .{ + .NONBLOCK = true, + .CLOEXEC = true, + }, + }; + const fd = openZ(pathname, flags, 0) catch |err| switch (err) { + error.FileLocksNotSupported => unreachable, + error.WouldBlock => unreachable, + error.FileBusy => unreachable, // not asking for write permissions + error.InvalidUtf8 => unreachable, // WASI-only + else => |e| return e, + }; + defer close(fd); + + return std.os.getFdPath(fd, out_buffer); + } + const result_path = std.c.realpath(pathname, out_buffer) orelse switch (@as(E, @enumFromInt(std.c._errno().*))) { + .SUCCESS => unreachable, + .INVAL => unreachable, + .BADF => unreachable, + .FAULT => unreachable, + .ACCES => return error.AccessDenied, + .NOENT => return error.FileNotFound, + .OPNOTSUPP => return error.NotSupported, + .NOTDIR => return error.NotDir, + .NAMETOOLONG => return error.NameTooLong, + .LOOP => return error.SymLinkLoop, + .IO => return error.InputOutput, + else => |err| return unexpectedErrno(err), + }; + return mem.sliceTo(result_path, 0); +} + +/// Same as `realpath` except `pathname` is WTF16LE-encoded. +/// +/// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// +/// Calling this function is usually a bug. +pub fn realpathW(pathname: []const u16, out_buffer: *[max_path_bytes]u8) RealPathError![]u8 { + const w = windows; + + const dir = fs.cwd().fd; + const access_mask = w.GENERIC_READ | w.SYNCHRONIZE; + const share_access = w.FILE_SHARE_READ; + const creation = w.FILE_OPEN; + const h_file = blk: { + const res = w.OpenFile(pathname, .{ + .dir = dir, + .access_mask = access_mask, + .share_access = share_access, + .creation = creation, + .filter = .any, + }) catch |err| switch (err) { + error.WouldBlock => unreachable, + else => |e| return e, + }; + break :blk res; + }; + defer w.CloseHandle(h_file); + + return std.os.getFdPath(h_file, out_buffer); +} + +/// Spurious wakeups are possible and no precision of timing is guaranteed. +pub fn nanosleep(seconds: u64, nanoseconds: u64) void { + var req = timespec{ + .tv_sec = cast(isize, seconds) orelse maxInt(isize), + .tv_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 dl_iterate_phdr( + context: anytype, + comptime Error: type, + comptime callback: fn (info: *dl_phdr_info, size: usize, context: @TypeOf(context)) Error!void, +) Error!void { + const Context = @TypeOf(context); + const elf = std.elf; + const dl = @import("dynamic_library.zig"); + + switch (builtin.object_format) { + .elf, .c => {}, + else => @compileError("dl_iterate_phdr is not available for this target"), + } + + if (builtin.link_libc) { + switch (system.dl_iterate_phdr(struct { + fn callbackC(info: *dl_phdr_info, size: usize, data: ?*anyopaque) callconv(.C) c_int { + const context_ptr: *const Context = @ptrCast(@alignCast(data)); + callback(info, size, context_ptr.*) catch |err| return @intFromError(err); + return 0; + } + }.callbackC, @ptrCast(@constCast(&context)))) { + 0 => return, + else => |err| return @as(Error, @errorCast(@errorFromInt(@as(std.meta.Int(.unsigned, @bitSizeOf(anyerror)), @intCast(err))))), + } + } + + const elf_base = std.process.getBaseAddress(); + const ehdr: *elf.Ehdr = @ptrFromInt(elf_base); + // Make sure the base address points to an ELF image. + assert(mem.eql(u8, ehdr.e_ident[0..4], elf.MAGIC)); + const n_phdr = ehdr.e_phnum; + const phdrs = (@as([*]elf.Phdr, @ptrFromInt(elf_base + ehdr.e_phoff)))[0..n_phdr]; + + var it = dl.linkmap_iterator(phdrs) catch unreachable; + + // The executable has no dynamic link segment, create a single entry for + // the whole ELF image. + if (it.end()) { + // Find the base address for the ELF image, if this is a PIE the value + // is non-zero. + const base_address = for (phdrs) |*phdr| { + if (phdr.p_type == elf.PT_PHDR) { + break @intFromPtr(phdrs.ptr) - phdr.p_vaddr; + // We could try computing the difference between _DYNAMIC and + // the p_vaddr of the PT_DYNAMIC section, but using the phdr is + // good enough (Is it?). + } + } else unreachable; + + var info = dl_phdr_info{ + .dlpi_addr = base_address, + .dlpi_name = "/proc/self/exe", + .dlpi_phdr = phdrs.ptr, + .dlpi_phnum = ehdr.e_phnum, + }; + + return callback(&info, @sizeOf(dl_phdr_info), context); + } + + // Last return value from the callback function. + while (it.next()) |entry| { + var dlpi_phdr: [*]elf.Phdr = undefined; + var dlpi_phnum: u16 = undefined; + + if (entry.l_addr != 0) { + const elf_header: *elf.Ehdr = @ptrFromInt(entry.l_addr); + dlpi_phdr = @ptrFromInt(entry.l_addr + elf_header.e_phoff); + dlpi_phnum = elf_header.e_phnum; + } else { + // This is the running ELF image + dlpi_phdr = @ptrFromInt(elf_base + ehdr.e_phoff); + dlpi_phnum = ehdr.e_phnum; + } + + var info = dl_phdr_info{ + .dlpi_addr = entry.l_addr, + .dlpi_name = entry.l_name, + .dlpi_phdr = dlpi_phdr, + .dlpi_phnum = dlpi_phnum, + }; + + try callback(&info, @sizeOf(dl_phdr_info), context); + } +} + +pub const ClockGetTimeError = error{UnsupportedClock} || UnexpectedError; + +/// TODO: change this to return the timespec as a return value +/// TODO: look into making clk_id an enum +pub fn clock_gettime(clk_id: i32, tp: *timespec) ClockGetTimeError!void { + if (native_os == .wasi and !builtin.link_libc) { + var ts: timestamp_t = undefined; + switch (system.clock_time_get(@bitCast(clk_id), 1, &ts)) { + .SUCCESS => { + tp.* = .{ + .tv_sec = @intCast(ts / std.time.ns_per_s), + .tv_nsec = @intCast(ts % std.time.ns_per_s), + }; + }, + .INVAL => return error.UnsupportedClock, + else => |err| return unexpectedErrno(err), + } + return; + } + if (native_os == .windows) { + if (clk_id == CLOCK.REALTIME) { + var ft: windows.FILETIME = undefined; + windows.kernel32.GetSystemTimeAsFileTime(&ft); + // FileTime has a granularity of 100 nanoseconds and uses the NTFS/Windows epoch. + const ft64 = (@as(u64, ft.dwHighDateTime) << 32) | ft.dwLowDateTime; + const ft_per_s = std.time.ns_per_s / 100; + tp.* = .{ + .tv_sec = @as(i64, @intCast(ft64 / ft_per_s)) + std.time.epoch.windows, + .tv_nsec = @as(c_long, @intCast(ft64 % ft_per_s)) * 100, + }; + return; + } else { + // TODO POSIX implementation of CLOCK.MONOTONIC on Windows. + return error.UnsupportedClock; + } + } + + switch (errno(system.clock_gettime(clk_id, tp))) { + .SUCCESS => return, + .FAULT => unreachable, + .INVAL => return error.UnsupportedClock, + else => |err| return unexpectedErrno(err), + } +} + +pub fn clock_getres(clk_id: i32, res: *timespec) ClockGetTimeError!void { + if (native_os == .wasi and !builtin.link_libc) { + var ts: timestamp_t = undefined; + switch (system.clock_res_get(@bitCast(clk_id), &ts)) { + .SUCCESS => res.* = .{ + .tv_sec = @intCast(ts / std.time.ns_per_s), + .tv_nsec = @intCast(ts % std.time.ns_per_s), + }, + .INVAL => return error.UnsupportedClock, + else => |err| return unexpectedErrno(err), + } + return; + } + + switch (errno(system.clock_getres(clk_id, res))) { + .SUCCESS => return, + .FAULT => unreachable, + .INVAL => return error.UnsupportedClock, + else => |err| return unexpectedErrno(err), + } +} + +pub const SchedGetAffinityError = error{PermissionDenied} || UnexpectedError; + +pub fn sched_getaffinity(pid: pid_t) SchedGetAffinityError!cpu_set_t { + var set: cpu_set_t = undefined; + switch (errno(system.sched_getaffinity(pid, @sizeOf(cpu_set_t), &set))) { + .SUCCESS => return set, + .FAULT => unreachable, + .INVAL => unreachable, + .SRCH => unreachable, + .PERM => return error.PermissionDenied, + else => |err| return unexpectedErrno(err), + } +} + +pub const SigaltstackError = error{ + /// The supplied stack size was less than MINSIGSTKSZ. + SizeTooSmall, + + /// Attempted to change the signal stack while it was active. + PermissionDenied, +} || UnexpectedError; + +pub fn sigaltstack(ss: ?*stack_t, old_ss: ?*stack_t) SigaltstackError!void { + switch (errno(system.sigaltstack(ss, old_ss))) { + .SUCCESS => return, + .FAULT => unreachable, + .INVAL => unreachable, + .NOMEM => return error.SizeTooSmall, + .PERM => return error.PermissionDenied, + else => |err| return unexpectedErrno(err), + } +} + +/// Examine and change a signal action. +pub fn sigaction(sig: u6, noalias act: ?*const Sigaction, noalias oact: ?*Sigaction) error{OperationNotSupported}!void { + switch (errno(system.sigaction(sig, act, oact))) { + .SUCCESS => return, + .INVAL, .NOSYS => return error.OperationNotSupported, + else => unreachable, + } +} + +/// Sets the thread signal mask. +pub fn sigprocmask(flags: u32, noalias set: ?*const sigset_t, noalias oldset: ?*sigset_t) void { + switch (errno(system.sigprocmask(@bitCast(flags), set, oldset))) { + .SUCCESS => return, + .FAULT => unreachable, + .INVAL => unreachable, + else => unreachable, + } +} + +pub const FutimensError = error{ + /// times is NULL, or both tv_nsec values are UTIME_NOW, and either: + /// * the effective user ID of the caller does not match the owner + /// of the file, the caller does not have write access to the + /// file, and the caller is not privileged (Linux: does not have + /// either the CAP_FOWNER or the CAP_DAC_OVERRIDE capability); + /// or, + /// * the file is marked immutable (see chattr(1)). + AccessDenied, + + /// The caller attempted to change one or both timestamps to a value + /// other than the current time, or to change one of the timestamps + /// to the current time while leaving the other timestamp unchanged, + /// (i.e., times is not NULL, neither tv_nsec field is UTIME_NOW, + /// and neither tv_nsec field is UTIME_OMIT) and either: + /// * the caller's effective user ID does not match the owner of + /// file, and the caller is not privileged (Linux: does not have + /// the CAP_FOWNER capability); or, + /// * the file is marked append-only or immutable (see chattr(1)). + PermissionDenied, + + ReadOnlyFileSystem, +} || UnexpectedError; + +pub fn futimens(fd: fd_t, times: *const [2]timespec) FutimensError!void { + if (native_os == .wasi and !builtin.link_libc) { + // TODO WASI encodes `wasi.fstflags` to signify magic values + // similar to UTIME_NOW and UTIME_OMIT. Currently, we ignore + // this here, but we should really handle it somehow. + const atim = times[0].toTimestamp(); + const mtim = times[1].toTimestamp(); + switch (wasi.fd_filestat_set_times(fd, atim, mtim, .{ + .ATIM = true, + .MTIM = true, + })) { + .SUCCESS => return, + .ACCES => return error.AccessDenied, + .PERM => return error.PermissionDenied, + .BADF => unreachable, // always a race condition + .FAULT => unreachable, + .INVAL => unreachable, + .ROFS => return error.ReadOnlyFileSystem, + else => |err| return unexpectedErrno(err), + } + } + + switch (errno(system.futimens(fd, times))) { + .SUCCESS => return, + .ACCES => return error.AccessDenied, + .PERM => return error.PermissionDenied, + .BADF => unreachable, // always a race condition + .FAULT => unreachable, + .INVAL => unreachable, + .ROFS => return error.ReadOnlyFileSystem, + else => |err| return unexpectedErrno(err), + } +} + +pub const GetHostNameError = error{PermissionDenied} || UnexpectedError; + +pub fn gethostname(name_buffer: *[HOST_NAME_MAX]u8) GetHostNameError![]u8 { + if (builtin.link_libc) { + switch (errno(system.gethostname(name_buffer, name_buffer.len))) { + .SUCCESS => return mem.sliceTo(name_buffer, 0), + .FAULT => unreachable, + .NAMETOOLONG => unreachable, // HOST_NAME_MAX prevents this + .PERM => return error.PermissionDenied, + else => |err| return unexpectedErrno(err), + } + } + if (native_os == .linux) { + const uts = uname(); + const hostname = mem.sliceTo(&uts.nodename, 0); + const result = name_buffer[0..hostname.len]; + @memcpy(result, hostname); + return result; + } + + @compileError("TODO implement gethostname for this OS"); +} + +pub fn uname() utsname { + var uts: utsname = undefined; + switch (errno(system.uname(&uts))) { + .SUCCESS => return uts, + .FAULT => unreachable, + else => unreachable, + } +} + +pub fn res_mkquery( + op: u4, + dname: []const u8, + class: u8, + ty: u8, + data: []const u8, + newrr: ?[*]const u8, + buf: []u8, +) usize { + _ = data; + _ = newrr; + // This implementation is ported from musl libc. + // A more idiomatic "ziggy" implementation would be welcome. + var name = dname; + if (mem.endsWith(u8, name, ".")) name.len -= 1; + assert(name.len <= 253); + const n = 17 + name.len + @intFromBool(name.len != 0); + + // Construct query template - ID will be filled later + var q: [280]u8 = undefined; + @memset(q[0..n], 0); + q[2] = @as(u8, op) * 8 + 1; + q[5] = 1; + @memcpy(q[13..][0..name.len], name); + var i: usize = 13; + var j: usize = undefined; + while (q[i] != 0) : (i = j + 1) { + j = i; + while (q[j] != 0 and q[j] != '.') : (j += 1) {} + // TODO determine the circumstances for this and whether or + // not this should be an error. + if (j - i - 1 > 62) unreachable; + q[i - 1] = @intCast(j - i); + } + q[i + 1] = ty; + q[i + 3] = class; + + // Make a reasonably unpredictable id + var ts: timespec = undefined; + clock_gettime(CLOCK.REALTIME, &ts) catch {}; + const UInt = std.meta.Int(.unsigned, @bitSizeOf(@TypeOf(ts.tv_nsec))); + const unsec: UInt = @bitCast(ts.tv_nsec); + const id: u32 = @truncate(unsec + unsec / 65536); + q[0] = @truncate(id / 256); + q[1] = @truncate(id); + + @memcpy(buf[0..n], q[0..n]); + return n; +} + +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. + MessageTooBig, + + /// 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. + NetworkSubsystemFailed, +} || UnexpectedError; + +pub const SendMsgError = SendError || error{ + /// The passed address didn't have the correct address family in its sa_family field. + AddressFamilyNotSupported, + + /// 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). + SocketNotConnected, + AddressNotAvailable, +}; + +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()) { + .WSAEACCES => return error.AccessDenied, + .WSAEADDRNOTAVAIL => return error.AddressNotAvailable, + .WSAECONNRESET => return error.ConnectionResetByPeer, + .WSAEMSGSIZE => return error.MessageTooBig, + .WSAENOBUFS => return error.SystemResources, + .WSAENOTSOCK => return error.FileDescriptorNotASocket, + .WSAEAFNOSUPPORT => return error.AddressFamilyNotSupported, + .WSAEDESTADDRREQ => unreachable, // A destination address is required. + .WSAEFAULT => unreachable, // The lpBuffers, lpTo, lpOverlapped, lpNumberOfBytesSent, or lpCompletionRoutine parameters are not part of the user address space, or the lpTo parameter is too small. + .WSAEHOSTUNREACH => return error.NetworkUnreachable, + // TODO: WSAEINPROGRESS, WSAEINTR + .WSAEINVAL => unreachable, + .WSAENETDOWN => return error.NetworkSubsystemFailed, + .WSAENETRESET => return error.ConnectionResetByPeer, + .WSAENETUNREACH => return error.NetworkUnreachable, + .WSAENOTCONN => return error.SocketNotConnected, + .WSAESHUTDOWN => 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. + .WSAEWOULDBLOCK => return error.WouldBlock, + .WSANOTINITIALISED => 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.MessageTooBig, + .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.AddressFamilyNotSupported, + .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.SocketNotConnected, + .NETDOWN => return error.NetworkSubsystemFailed, + else => |err| return unexpectedErrno(err), + } + } + } +} + +pub const SendToError = SendMsgError || error{ + /// The destination address is not reachable by the bound address. + UnreachableAddress, +}; + +/// 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.MessageTooBig` 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()) { + .WSAEACCES => return error.AccessDenied, + .WSAEADDRNOTAVAIL => return error.AddressNotAvailable, + .WSAECONNRESET => return error.ConnectionResetByPeer, + .WSAEMSGSIZE => return error.MessageTooBig, + .WSAENOBUFS => return error.SystemResources, + .WSAENOTSOCK => return error.FileDescriptorNotASocket, + .WSAEAFNOSUPPORT => return error.AddressFamilyNotSupported, + .WSAEDESTADDRREQ => unreachable, // A destination address is required. + .WSAEFAULT => unreachable, // The lpBuffers, lpTo, lpOverlapped, lpNumberOfBytesSent, or lpCompletionRoutine parameters are not part of the user address space, or the lpTo parameter is too small. + .WSAEHOSTUNREACH => return error.NetworkUnreachable, + // TODO: WSAEINPROGRESS, WSAEINTR + .WSAEINVAL => unreachable, + .WSAENETDOWN => return error.NetworkSubsystemFailed, + .WSAENETRESET => return error.ConnectionResetByPeer, + .WSAENETUNREACH => return error.NetworkUnreachable, + .WSAENOTCONN => return error.SocketNotConnected, + .WSAESHUTDOWN => 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. + .WSAEWOULDBLOCK => return error.WouldBlock, + .WSANOTINITIALISED => 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 + .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.MessageTooBig, + .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.AddressFamilyNotSupported, + .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.SocketNotConnected, + .NETDOWN => return error.NetworkSubsystemFailed, + 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.AddressFamilyNotSupported => unreachable, + error.SymLinkLoop => unreachable, + error.NameTooLong => unreachable, + error.FileNotFound => unreachable, + error.NotDir => unreachable, + error.NetworkUnreachable => unreachable, + error.AddressNotAvailable => unreachable, + error.SocketNotConnected => unreachable, + error.UnreachableAddress => unreachable, + else => |e| return e, + }; +} + +pub const SendFileError = PReadError || WriteError || SendError; + +/// Transfer data between file descriptors, with optional headers and trailers. +/// +/// Returns the number of bytes written, which can be zero. +/// +/// The `sendfile` call copies `in_len` bytes from one file descriptor to another. When possible, +/// this is done within the operating system kernel, which can provide better performance +/// characteristics than transferring data from kernel to user space and back, such as with +/// `read` and `write` calls. When `in_len` is `0`, it means to copy until the end of the input file has been +/// reached. Note, however, that partial writes are still possible in this case. +/// +/// `in_fd` must be a file descriptor opened for reading, and `out_fd` must be a file descriptor +/// opened for writing. They may be any kind of file descriptor; however, if `in_fd` is not a regular +/// file system file, it may cause this function to fall back to calling `read` and `write`, in which case +/// atomicity guarantees no longer apply. +/// +/// Copying begins reading at `in_offset`. The input file descriptor seek position is ignored and not updated. +/// If the output file descriptor has a seek position, it is updated as bytes are written. When +/// `in_offset` is past the end of the input file, it successfully reads 0 bytes. +/// +/// `flags` has different meanings per operating system; refer to the respective man pages. +/// +/// These systems support atomically sending everything, including headers and trailers: +/// * macOS +/// * FreeBSD +/// +/// These systems support in-kernel data copying, but headers and trailers are not sent atomically: +/// * Linux +/// +/// Other systems fall back to calling `read` / `write`. +/// +/// Linux has a limit on how many bytes may be transferred in one `sendfile` 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 `sendfile` man page. +/// The limit on Darwin is `0x7fffffff`, trying to write more than that returns EINVAL. +/// The corresponding POSIX limit on this is `maxInt(isize)`. +pub fn sendfile( + out_fd: fd_t, + in_fd: fd_t, + in_offset: u64, + in_len: u64, + headers: []const iovec_const, + trailers: []const iovec_const, + flags: u32, +) SendFileError!usize { + var header_done = false; + var total_written: usize = 0; + + // Prevents EOVERFLOW. + const size_t = std.meta.Int(.unsigned, @typeInfo(usize).Int.bits - 1); + const max_count = switch (native_os) { + .linux => 0x7ffff000, + .macos, .ios, .watchos, .tvos => maxInt(i32), + else => maxInt(size_t), + }; + + switch (native_os) { + .linux => sf: { + // sendfile() first appeared in Linux 2.2, glibc 2.1. + const call_sf = comptime if (builtin.link_libc) + std.c.versionCheck(.{ .major = 2, .minor = 1, .patch = 0 }) + else + builtin.os.version_range.linux.range.max.order(.{ .major = 2, .minor = 2, .patch = 0 }) != .lt; + if (!call_sf) break :sf; + + if (headers.len != 0) { + const amt = try writev(out_fd, headers); + total_written += amt; + if (amt < count_iovec_bytes(headers)) return total_written; + header_done = true; + } + + // Here we match BSD behavior, making a zero count value send as many bytes as possible. + const adjusted_count = if (in_len == 0) max_count else @min(in_len, max_count); + + const sendfile_sym = if (lfs64_abi) system.sendfile64 else system.sendfile; + while (true) { + var offset: off_t = @bitCast(in_offset); + const rc = sendfile_sym(out_fd, in_fd, &offset, adjusted_count); + switch (errno(rc)) { + .SUCCESS => { + const amt: usize = @bitCast(rc); + total_written += amt; + if (in_len == 0 and amt == 0) { + // We have detected EOF from `in_fd`. + break; + } else if (amt < in_len) { + return total_written; + } else { + break; + } + }, + + .BADF => unreachable, // Always a race condition. + .FAULT => unreachable, // Segmentation fault. + .OVERFLOW => unreachable, // We avoid passing too large of a `count`. + .NOTCONN => return error.BrokenPipe, // `out_fd` is an unconnected socket + + .INVAL, .NOSYS => { + // EINVAL could be any of the following situations: + // * Descriptor is not valid or locked + // * an mmap(2)-like operation is not available for in_fd + // * count is negative + // * out_fd has the APPEND flag set + // Because of the "mmap(2)-like operation" possibility, we fall back to doing read/write + // manually, the same as ENOSYS. + break :sf; + }, + .AGAIN => return error.WouldBlock, + .IO => return error.InputOutput, + .PIPE => return error.BrokenPipe, + .NOMEM => return error.SystemResources, + .NXIO => return error.Unseekable, + .SPIPE => return error.Unseekable, + else => |err| { + unexpectedErrno(err) catch {}; + break :sf; + }, + } + } + + if (trailers.len != 0) { + total_written += try writev(out_fd, trailers); + } + + return total_written; + }, + .freebsd => sf: { + var hdtr_data: std.c.sf_hdtr = undefined; + var hdtr: ?*std.c.sf_hdtr = null; + if (headers.len != 0 or trailers.len != 0) { + // Here we carefully avoid `@intCast` by returning partial writes when + // too many io vectors are provided. + const hdr_cnt = cast(u31, headers.len) orelse maxInt(u31); + if (headers.len > hdr_cnt) return writev(out_fd, headers); + + const trl_cnt = cast(u31, trailers.len) orelse maxInt(u31); + + hdtr_data = std.c.sf_hdtr{ + .headers = headers.ptr, + .hdr_cnt = hdr_cnt, + .trailers = trailers.ptr, + .trl_cnt = trl_cnt, + }; + hdtr = &hdtr_data; + } + + while (true) { + var sbytes: off_t = undefined; + const err = errno(system.sendfile(in_fd, out_fd, @bitCast(in_offset), @min(in_len, max_count), hdtr, &sbytes, flags)); + const amt: usize = @bitCast(sbytes); + switch (err) { + .SUCCESS => return amt, + + .BADF => unreachable, // Always a race condition. + .FAULT => unreachable, // Segmentation fault. + .NOTCONN => return error.BrokenPipe, // `out_fd` is an unconnected socket + + .INVAL, .OPNOTSUPP, .NOTSOCK, .NOSYS => { + // EINVAL could be any of the following situations: + // * The fd argument is not a regular file. + // * The s argument is not a SOCK.STREAM type socket. + // * The offset argument is negative. + // Because of some of these possibilities, we fall back to doing read/write + // manually, the same as ENOSYS. + break :sf; + }, + + .INTR => if (amt != 0) return amt else continue, + + .AGAIN => if (amt != 0) { + return amt; + } else { + return error.WouldBlock; + }, + + .BUSY => if (amt != 0) { + return amt; + } else { + return error.WouldBlock; + }, + + .IO => return error.InputOutput, + .NOBUFS => return error.SystemResources, + .PIPE => return error.BrokenPipe, + + else => { + unexpectedErrno(err) catch {}; + if (amt != 0) { + return amt; + } else { + break :sf; + } + }, + } + } + }, + .macos, .ios, .tvos, .watchos => sf: { + var hdtr_data: std.c.sf_hdtr = undefined; + var hdtr: ?*std.c.sf_hdtr = null; + if (headers.len != 0 or trailers.len != 0) { + // Here we carefully avoid `@intCast` by returning partial writes when + // too many io vectors are provided. + const hdr_cnt = cast(u31, headers.len) orelse maxInt(u31); + if (headers.len > hdr_cnt) return writev(out_fd, headers); + + const trl_cnt = cast(u31, trailers.len) orelse maxInt(u31); + + hdtr_data = std.c.sf_hdtr{ + .headers = headers.ptr, + .hdr_cnt = hdr_cnt, + .trailers = trailers.ptr, + .trl_cnt = trl_cnt, + }; + hdtr = &hdtr_data; + } + + while (true) { + var sbytes: off_t = @min(in_len, max_count); + const err = errno(system.sendfile(in_fd, out_fd, @bitCast(in_offset), &sbytes, hdtr, flags)); + const amt: usize = @bitCast(sbytes); + switch (err) { + .SUCCESS => return amt, + + .BADF => unreachable, // Always a race condition. + .FAULT => unreachable, // Segmentation fault. + .INVAL => unreachable, + .NOTCONN => return error.BrokenPipe, // `out_fd` is an unconnected socket + + .OPNOTSUPP, .NOTSOCK, .NOSYS => break :sf, + + .INTR => if (amt != 0) return amt else continue, + + .AGAIN => if (amt != 0) { + return amt; + } else { + return error.WouldBlock; + }, + + .IO => return error.InputOutput, + .PIPE => return error.BrokenPipe, + + else => { + unexpectedErrno(err) catch {}; + if (amt != 0) { + return amt; + } else { + break :sf; + } + }, + } + } + }, + else => {}, // fall back to read/write + } + + if (headers.len != 0 and !header_done) { + const amt = try writev(out_fd, headers); + total_written += amt; + if (amt < count_iovec_bytes(headers)) return total_written; + } + + rw: { + var buf: [8 * 4096]u8 = undefined; + // Here we match BSD behavior, making a zero count value send as many bytes as possible. + const adjusted_count = if (in_len == 0) buf.len else @min(buf.len, in_len); + const amt_read = try pread(in_fd, buf[0..adjusted_count], in_offset); + if (amt_read == 0) { + if (in_len == 0) { + // We have detected EOF from `in_fd`. + break :rw; + } else { + return total_written; + } + } + const amt_written = try write(out_fd, buf[0..amt_read]); + total_written += amt_written; + if (amt_written < in_len or in_len == 0) return total_written; + } + + if (trailers.len != 0) { + total_written += try writev(out_fd, trailers); + } + + return total_written; +} + +fn count_iovec_bytes(iovs: []const iovec_const) usize { + var count: usize = 0; + for (iovs) |iov| { + count += iov.iov_len; + } + return count; +} + +pub const CopyFileRangeError = error{ + FileTooBig, + InputOutput, + /// `fd_in` is not open for reading; or `fd_out` is not open for writing; + /// or the `APPEND` flag is set for `fd_out`. + FilesOpenedWithWrongFlags, + IsDir, + OutOfMemory, + NoSpaceLeft, + Unseekable, + PermissionDenied, + SwapFile, + CorruptedData, +} || PReadError || PWriteError || UnexpectedError; + +/// Transfer data between file descriptors at specified offsets. +/// +/// Returns the number of bytes written, which can less than requested. +/// +/// The `copy_file_range` call copies `len` bytes from one file descriptor to another. When possible, +/// this is done within the operating system kernel, which can provide better performance +/// characteristics than transferring data from kernel to user space and back, such as with +/// `pread` and `pwrite` calls. +/// +/// `fd_in` must be a file descriptor opened for reading, and `fd_out` must be a file descriptor +/// opened for writing. They may be any kind of file descriptor; however, if `fd_in` is not a regular +/// file system file, it may cause this function to fall back to calling `pread` and `pwrite`, in which case +/// atomicity guarantees no longer apply. +/// +/// If `fd_in` and `fd_out` are the same, source and target ranges must not overlap. +/// The file descriptor seek positions are ignored and not updated. +/// When `off_in` is past the end of the input file, it successfully reads 0 bytes. +/// +/// `flags` has different meanings per operating system; refer to the respective man pages. +/// +/// These systems support in-kernel data copying: +/// * Linux 4.5 (cross-filesystem 5.3) +/// * FreeBSD 13.0 +/// +/// Other systems fall back to calling `pread` / `pwrite`. +/// +/// Maximum offsets on Linux and FreeBSD are `maxInt(i64)`. +pub fn copy_file_range(fd_in: fd_t, off_in: u64, fd_out: fd_t, off_out: u64, len: usize, flags: u32) CopyFileRangeError!usize { + const global = struct { + var has_copy_file_range = true; + }; + + if ((comptime builtin.os.isAtLeast(.freebsd, .{ .major = 13, .minor = 0, .patch = 0 }) orelse false) or + ((comptime builtin.os.isAtLeast(.linux, .{ .major = 4, .minor = 5, .patch = 0 }) orelse false and + std.c.versionCheck(.{ .major = 2, .minor = 27, .patch = 0 })) and + @atomicLoad(bool, &global.has_copy_file_range, .monotonic))) + { + var off_in_copy: i64 = @bitCast(off_in); + var off_out_copy: i64 = @bitCast(off_out); + + while (true) { + const rc = system.copy_file_range(fd_in, &off_in_copy, fd_out, &off_out_copy, len, flags); + if (native_os == .freebsd) { + switch (errno(rc)) { + .SUCCESS => return @intCast(rc), + .BADF => return error.FilesOpenedWithWrongFlags, + .FBIG => return error.FileTooBig, + .IO => return error.InputOutput, + .ISDIR => return error.IsDir, + .NOSPC => return error.NoSpaceLeft, + .INVAL => break, // these may not be regular files, try fallback + .INTEGRITY => return error.CorruptedData, + .INTR => continue, + else => |err| return unexpectedErrno(err), + } + } else { // assume linux + switch (errno(rc)) { + .SUCCESS => return @intCast(rc), + .BADF => return error.FilesOpenedWithWrongFlags, + .FBIG => return error.FileTooBig, + .IO => return error.InputOutput, + .ISDIR => return error.IsDir, + .NOSPC => return error.NoSpaceLeft, + .INVAL => break, // these may not be regular files, try fallback + .NOMEM => return error.OutOfMemory, + .OVERFLOW => return error.Unseekable, + .PERM => return error.PermissionDenied, + .TXTBSY => return error.SwapFile, + .XDEV => break, // support for cross-filesystem copy added in Linux 5.3, use fallback + .NOSYS => { + @atomicStore(bool, &global.has_copy_file_range, false, .monotonic); + break; + }, + else => |err| return unexpectedErrno(err), + } + } + } + } + + var buf: [8 * 4096]u8 = undefined; + const amt_read = try pread(fd_in, buf[0..@min(buf.len, len)], off_in); + if (amt_read == 0) return 0; + return pwrite(fd_out, buf[0..amt_read], off_out); +} + +pub const PollError = error{ + /// The network subsystem has failed. + NetworkSubsystemFailed, + + /// The kernel had no space to allocate file descriptor tables. + SystemResources, +} || UnexpectedError; + +pub fn poll(fds: []pollfd, timeout: i32) PollError!usize { + while (true) { + const fds_count = cast(nfds_t, fds.len) orelse return error.SystemResources; + const rc = system.poll(fds.ptr, fds_count, timeout); + if (native_os == .windows) { + if (rc == windows.ws2_32.SOCKET_ERROR) { + switch (windows.ws2_32.WSAGetLastError()) { + .WSANOTINITIALISED => unreachable, + .WSAENETDOWN => return error.NetworkSubsystemFailed, + .WSAENOBUFS => return error.SystemResources, + // TODO: handle more errors + else => |err| return windows.unexpectedWSAError(err), + } + } else { + return @intCast(rc); + } + } else { + switch (errno(rc)) { + .SUCCESS => return @intCast(rc), + .FAULT => unreachable, + .INTR => continue, + .INVAL => unreachable, + .NOMEM => return error.SystemResources, + else => |err| return unexpectedErrno(err), + } + } + unreachable; + } +} + +pub const PPollError = error{ + /// The operation was interrupted by a delivery of a signal before it could complete. + SignalInterrupt, + + /// The kernel had no space to allocate file descriptor tables. + SystemResources, +} || UnexpectedError; + +pub fn ppoll(fds: []pollfd, timeout: ?*const timespec, mask: ?*const sigset_t) PPollError!usize { + var ts: timespec = undefined; + var ts_ptr: ?*timespec = null; + if (timeout) |timeout_ns| { + ts_ptr = &ts; + ts = timeout_ns.*; + } + const fds_count = cast(nfds_t, fds.len) orelse return error.SystemResources; + const rc = system.ppoll(fds.ptr, fds_count, ts_ptr, mask); + switch (errno(rc)) { + .SUCCESS => return @intCast(rc), + .FAULT => unreachable, + .INTR => return error.SignalInterrupt, + .INVAL => unreachable, + .NOMEM => return error.SystemResources, + else => |err| return unexpectedErrno(err), + } +} + +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, + ConnectionTimedOut, + + /// The socket has not been bound. + SocketNotBound, + + /// The UDP message was too big for the buffer and part of it has been discarded + MessageTooBig, + + /// The network subsystem has failed. + NetworkSubsystemFailed, + + /// The socket is not connected (connection-oriented sockets only). + SocketNotConnected, +} || 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()) { + .WSANOTINITIALISED => unreachable, + .WSAECONNRESET => return error.ConnectionResetByPeer, + .WSAEINVAL => return error.SocketNotBound, + .WSAEMSGSIZE => return error.MessageTooBig, + .WSAENETDOWN => return error.NetworkSubsystemFailed, + .WSAENOTCONN => return error.SocketNotConnected, + .WSAEWOULDBLOCK => return error.WouldBlock, + .WSAETIMEDOUT => return error.ConnectionTimedOut, + // 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.SocketNotConnected, + .NOTSOCK => unreachable, + .INTR => continue, + .AGAIN => return error.WouldBlock, + .NOMEM => return error.SystemResources, + .CONNREFUSED => return error.ConnectionRefused, + .CONNRESET => return error.ConnectionResetByPeer, + .TIMEDOUT => return error.ConnectionTimedOut, + else => |err| return unexpectedErrno(err), + } + } + } +} + +pub const DnExpandError = error{InvalidDnsPacket}; + +pub fn dn_expand( + msg: []const u8, + comp_dn: []const u8, + exp_dn: []u8, +) DnExpandError!usize { + // This implementation is ported from musl libc. + // A more idiomatic "ziggy" implementation would be welcome. + var p = comp_dn.ptr; + var len: usize = maxInt(usize); + const end = msg.ptr + msg.len; + if (p == end or exp_dn.len == 0) return error.InvalidDnsPacket; + var dest = exp_dn.ptr; + const dend = dest + @min(exp_dn.len, 254); + // detect reference loop using an iteration counter + var i: usize = 0; + while (i < msg.len) : (i += 2) { + // loop invariants: p= msg.len) return error.InvalidDnsPacket; + p = msg.ptr + j; + } else if (p[0] != 0) { + if (dest != exp_dn.ptr) { + dest[0] = '.'; + dest += 1; + } + var j = p[0]; + p += 1; + if (j >= @intFromPtr(end) - @intFromPtr(p) or j >= @intFromPtr(dend) - @intFromPtr(dest)) { + return error.InvalidDnsPacket; + } + while (j != 0) { + j -= 1; + dest[0] = p[0]; + dest += 1; + p += 1; + } + } else { + dest[0] = 0; + if (len == maxInt(usize)) len = @intFromPtr(p) + 1 - @intFromPtr(comp_dn.ptr); + return len; + } + } + return error.InvalidDnsPacket; +} + +pub const SetSockOptError = error{ + /// The socket is already connected, and a specified option cannot be set while the socket is connected. + AlreadyConnected, + + /// The option is not supported by the protocol. + InvalidProtocolOption, + + /// The send and receive timeout values are too big to fit into the timeout fields in the socket structure. + TimeoutTooBig, + + /// Insufficient resources are available in the system to complete the call. + SystemResources, + + // Setting the socket option requires more elevated permissions. + PermissionDenied, + + NetworkSubsystemFailed, + FileDescriptorNotASocket, + SocketNotBound, + NoDevice, +} || UnexpectedError; + +/// Set a socket's options. +pub fn setsockopt(fd: socket_t, level: u32, optname: u32, opt: []const u8) SetSockOptError!void { + if (native_os == .windows) { + const rc = windows.ws2_32.setsockopt(fd, @intCast(level), @intCast(optname), opt.ptr, @intCast(opt.len)); + if (rc == windows.ws2_32.SOCKET_ERROR) { + switch (windows.ws2_32.WSAGetLastError()) { + .WSANOTINITIALISED => unreachable, + .WSAENETDOWN => return error.NetworkSubsystemFailed, + .WSAEFAULT => unreachable, + .WSAENOTSOCK => return error.FileDescriptorNotASocket, + .WSAEINVAL => return error.SocketNotBound, + else => |err| return windows.unexpectedWSAError(err), + } + } + return; + } else { + switch (errno(system.setsockopt(fd, level, optname, opt.ptr, @intCast(opt.len)))) { + .SUCCESS => {}, + .BADF => unreachable, // always a race condition + .NOTSOCK => unreachable, // always a race condition + .INVAL => unreachable, + .FAULT => unreachable, + .DOM => return error.TimeoutTooBig, + .ISCONN => return error.AlreadyConnected, + .NOPROTOOPT => return error.InvalidProtocolOption, + .NOMEM => return error.SystemResources, + .NOBUFS => return error.SystemResources, + .PERM => return error.PermissionDenied, + .NODEV => return error.NoDevice, + else => |err| return unexpectedErrno(err), + } + } +} + +pub const MemFdCreateError = error{ + SystemFdQuotaExceeded, + ProcessFdQuotaExceeded, + OutOfMemory, + /// Either the name provided exceeded `NAME_MAX`, or invalid flags were passed. + NameTooLong, + + /// memfd_create is available in Linux 3.17 and later. This error is returned + /// for older kernel versions. + SystemOutdated, +} || UnexpectedError; + +pub fn memfd_createZ(name: [*:0]const u8, flags: u32) MemFdCreateError!fd_t { + switch (native_os) { + .linux => { + // memfd_create is available only in glibc versions starting with 2.27. + const use_c = std.c.versionCheck(.{ .major = 2, .minor = 27, .patch = 0 }); + const sys = if (use_c) std.c else linux; + const rc = sys.memfd_create(name, flags); + switch (errno(rc)) { + .SUCCESS => return @intCast(rc), + .FAULT => unreachable, // name has invalid memory + .INVAL => return error.NameTooLong, // or, program has a bug and flags are faulty + .NFILE => return error.SystemFdQuotaExceeded, + .MFILE => return error.ProcessFdQuotaExceeded, + .NOMEM => return error.OutOfMemory, + .NOSYS => return error.SystemOutdated, + else => |err| return unexpectedErrno(err), + } + }, + .freebsd => { + if (comptime builtin.os.version_range.semver.max.order(.{ .major = 13, .minor = 0, .patch = 0 }) == .lt) + @compileError("memfd_create is unavailable on FreeBSD < 13.0"); + const rc = system.memfd_create(name, flags); + switch (errno(rc)) { + .SUCCESS => return rc, + .BADF => unreachable, // name argument NULL + .INVAL => unreachable, // name too long or invalid/unsupported flags. + .MFILE => return error.ProcessFdQuotaExceeded, + .NFILE => return error.SystemFdQuotaExceeded, + .NOSYS => return error.SystemOutdated, + else => |err| return unexpectedErrno(err), + } + }, + else => @compileError("target OS does not support memfd_create()"), + } +} + +pub fn memfd_create(name: []const u8, flags: u32) MemFdCreateError!fd_t { + var buffer: [NAME_MAX - "memfd:".len - 1:0]u8 = undefined; + if (name.len > buffer.len) return error.NameTooLong; + @memcpy(buffer[0..name.len], name); + buffer[name.len] = 0; + return memfd_createZ(&buffer, flags); +} + +pub fn getrusage(who: i32) rusage { + var result: rusage = undefined; + const rc = system.getrusage(who, &result); + switch (errno(rc)) { + .SUCCESS => return result, + .INVAL => unreachable, + .FAULT => unreachable, + else => unreachable, + } +} + +pub const TIOCError = error{NotATerminal}; + +pub const TermiosGetError = TIOCError || UnexpectedError; + +pub fn tcgetattr(handle: fd_t) TermiosGetError!termios { + while (true) { + var term: termios = undefined; + switch (errno(system.tcgetattr(handle, &term))) { + .SUCCESS => return term, + .INTR => continue, + .BADF => unreachable, + .NOTTY => return error.NotATerminal, + else => |err| return unexpectedErrno(err), + } + } +} + +pub const TermiosSetError = TermiosGetError || error{ProcessOrphaned}; + +pub fn tcsetattr(handle: fd_t, optional_action: TCSA, termios_p: termios) TermiosSetError!void { + while (true) { + switch (errno(system.tcsetattr(handle, optional_action, &termios_p))) { + .SUCCESS => return, + .BADF => unreachable, + .INTR => continue, + .INVAL => unreachable, + .NOTTY => return error.NotATerminal, + .IO => return error.ProcessOrphaned, + else => |err| return unexpectedErrno(err), + } + } +} + +pub const TermioGetPgrpError = TIOCError || UnexpectedError; + +/// Returns the process group ID for the TTY associated with the given handle. +pub fn tcgetpgrp(handle: fd_t) TermioGetPgrpError!pid_t { + while (true) { + var pgrp: pid_t = undefined; + switch (errno(system.tcgetpgrp(handle, &pgrp))) { + .SUCCESS => return pgrp, + .BADF => unreachable, + .INVAL => unreachable, + .INTR => continue, + .NOTTY => return error.NotATerminal, + else => |err| return unexpectedErrno(err), + } + } +} + +pub const TermioSetPgrpError = TermioGetPgrpError || error{NotAPgrpMember}; + +/// Sets the controlling process group ID for given TTY. +/// handle must be valid fd_t to a TTY associated with calling process. +/// pgrp must be a valid process group, and the calling process must be a member +/// of that group. +pub fn tcsetpgrp(handle: fd_t, pgrp: pid_t) TermioSetPgrpError!void { + while (true) { + switch (errno(system.tcsetpgrp(handle, &pgrp))) { + .SUCCESS => return, + .BADF => unreachable, + .INVAL => unreachable, + .INTR => continue, + .NOTTY => return error.NotATerminal, + .PERM => return TermioSetPgrpError.NotAPgrpMember, + 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)) { + .SUCCESS => return @intCast(rc), + .BADF, .INVAL => unreachable, + .NFILE => return error.SystemFdQuotaExceeded, + .NOMEM => return error.SystemResources, + .MFILE => return error.ProcessResources, + .NODEV => return error.InodeMountFail, + .NOSYS => return error.SystemOutdated, + else => |err| return unexpectedErrno(err), + } +} + +pub const SyncError = error{ + InputOutput, + NoSpaceLeft, + DiskQuota, + AccessDenied, +} || UnexpectedError; + +/// Write all pending file contents and metadata modifications to all filesystems. +pub fn sync() void { + system.sync(); +} + +/// Write all pending file contents and metadata modifications to the filesystem which contains the specified file. +pub fn syncfs(fd: fd_t) SyncError!void { + const rc = system.syncfs(fd); + switch (errno(rc)) { + .SUCCESS => return, + .BADF, .INVAL, .ROFS => unreachable, + .IO => return error.InputOutput, + .NOSPC => return error.NoSpaceLeft, + .DQUOT => return error.DiskQuota, + else => |err| return unexpectedErrno(err), + } +} + +/// Write all pending file contents and metadata modifications for the specified file descriptor to the underlying filesystem. +pub fn fsync(fd: fd_t) SyncError!void { + if (native_os == .windows) { + if (windows.kernel32.FlushFileBuffers(fd) != 0) + return; + switch (windows.kernel32.GetLastError()) { + .SUCCESS => return, + .INVALID_HANDLE => unreachable, + .ACCESS_DENIED => return error.AccessDenied, // a sync was performed but the system couldn't update the access time + .UNEXP_NET_ERR => return error.InputOutput, + else => return error.InputOutput, + } + } + const rc = system.fsync(fd); + switch (errno(rc)) { + .SUCCESS => return, + .BADF, .INVAL, .ROFS => unreachable, + .IO => return error.InputOutput, + .NOSPC => return error.NoSpaceLeft, + .DQUOT => return error.DiskQuota, + else => |err| return unexpectedErrno(err), + } +} + +/// Write all pending file contents for the specified file descriptor to the underlying filesystem, but not necessarily the metadata. +pub fn fdatasync(fd: fd_t) SyncError!void { + if (native_os == .windows) { + return fsync(fd) catch |err| switch (err) { + SyncError.AccessDenied => return, // fdatasync doesn't promise that the access time was synced + else => return err, + }; + } + const rc = system.fdatasync(fd); + switch (errno(rc)) { + .SUCCESS => return, + .BADF, .INVAL, .ROFS => unreachable, + .IO => return error.InputOutput, + .NOSPC => return error.NoSpaceLeft, + .DQUOT => return error.DiskQuota, + else => |err| return unexpectedErrno(err), + } +} + +pub const PrctlError = error{ + /// Can only occur with PR_SET_SECCOMP/SECCOMP_MODE_FILTER or + /// PR_SET_MM/PR_SET_MM_EXE_FILE + AccessDenied, + /// Can only occur with PR_SET_MM/PR_SET_MM_EXE_FILE + InvalidFileDescriptor, + InvalidAddress, + /// Can only occur with PR_SET_SPECULATION_CTRL, PR_MPX_ENABLE_MANAGEMENT, + /// or PR_MPX_DISABLE_MANAGEMENT + UnsupportedFeature, + /// Can only occur with PR_SET_FP_MODE + OperationNotSupported, + PermissionDenied, +} || UnexpectedError; + +pub fn prctl(option: PR, args: anytype) PrctlError!u31 { + if (@typeInfo(@TypeOf(args)) != .Struct) + @compileError("Expected tuple or struct argument, found " ++ @typeName(@TypeOf(args))); + if (args.len > 4) + @compileError("prctl takes a maximum of 4 optional arguments"); + + var buf: [4]usize = undefined; + { + comptime var i = 0; + inline while (i < args.len) : (i += 1) buf[i] = args[i]; + } + + const rc = system.prctl(@intFromEnum(option), buf[0], buf[1], buf[2], buf[3]); + switch (errno(rc)) { + .SUCCESS => return @intCast(rc), + .ACCES => return error.AccessDenied, + .BADF => return error.InvalidFileDescriptor, + .FAULT => return error.InvalidAddress, + .INVAL => unreachable, + .NODEV, .NXIO => return error.UnsupportedFeature, + .OPNOTSUPP => return error.OperationNotSupported, + .PERM, .BUSY => return error.PermissionDenied, + .RANGE => unreachable, + else => |err| return unexpectedErrno(err), + } +} + +pub const GetrlimitError = UnexpectedError; + +pub fn getrlimit(resource: rlimit_resource) GetrlimitError!rlimit { + const getrlimit_sym = if (lfs64_abi) system.getrlimit64 else system.getrlimit; + + var limits: rlimit = undefined; + switch (errno(getrlimit_sym(resource, &limits))) { + .SUCCESS => return limits, + .FAULT => unreachable, // bogus pointer + .INVAL => unreachable, + else => |err| return unexpectedErrno(err), + } +} + +pub const SetrlimitError = error{ PermissionDenied, LimitTooBig } || UnexpectedError; + +pub fn setrlimit(resource: rlimit_resource, limits: rlimit) SetrlimitError!void { + const setrlimit_sym = if (lfs64_abi) system.setrlimit64 else system.setrlimit; + + switch (errno(setrlimit_sym(resource, &limits))) { + .SUCCESS => return, + .FAULT => unreachable, // bogus pointer + .INVAL => return error.LimitTooBig, // this could also mean "invalid resource", but that would be unreachable + .PERM => return error.PermissionDenied, + else => |err| return unexpectedErrno(err), + } +} + +pub const MincoreError = error{ + /// A kernel resource was temporarily unavailable. + SystemResources, + /// vec points to an invalid address. + InvalidAddress, + /// addr is not page-aligned. + InvalidSyscall, + /// One of the following: + /// * length is greater than user space TASK_SIZE - addr + /// * addr + length contains unmapped memory + OutOfMemory, + /// The mincore syscall is not available on this version and configuration + /// of this UNIX-like kernel. + MincoreUnavailable, +} || UnexpectedError; + +/// Determine whether pages are resident in memory. +pub fn mincore(ptr: [*]align(mem.page_size) u8, length: usize, vec: [*]u8) MincoreError!void { + return switch (errno(system.mincore(ptr, length, vec))) { + .SUCCESS => {}, + .AGAIN => error.SystemResources, + .FAULT => error.InvalidAddress, + .INVAL => error.InvalidSyscall, + .NOMEM => error.OutOfMemory, + .NOSYS => error.MincoreUnavailable, + else => |err| unexpectedErrno(err), + }; +} + +pub const MadviseError = error{ + /// advice is MADV.REMOVE, but the specified address range is not a shared writable mapping. + AccessDenied, + /// advice is MADV.HWPOISON, but the caller does not have the CAP_SYS_ADMIN capability. + PermissionDenied, + /// A kernel resource was temporarily unavailable. + SystemResources, + /// One of the following: + /// * addr is not page-aligned or length is negative + /// * advice is not valid + /// * advice is MADV.DONTNEED or MADV.REMOVE and the specified address range + /// includes locked, Huge TLB pages, or VM_PFNMAP pages. + /// * advice is MADV.MERGEABLE or MADV.UNMERGEABLE, but the kernel was not + /// configured with CONFIG_KSM. + /// * advice is MADV.FREE or MADV.WIPEONFORK but the specified address range + /// includes file, Huge TLB, MAP.SHARED, or VM_PFNMAP ranges. + InvalidSyscall, + /// (for MADV.WILLNEED) Paging in this area would exceed the process's + /// maximum resident set size. + WouldExceedMaximumResidentSetSize, + /// One of the following: + /// * (for MADV.WILLNEED) Not enough memory: paging in failed. + /// * Addresses in the specified range are not currently mapped, or + /// are outside the address space of the process. + OutOfMemory, + /// The madvise syscall is not available on this version and configuration + /// of the Linux kernel. + MadviseUnavailable, + /// The operating system returned an undocumented error code. + Unexpected, +}; + +/// Give advice about use of memory. +/// This syscall is optional and is sometimes configured to be disabled. +pub fn madvise(ptr: [*]align(mem.page_size) u8, length: usize, advice: u32) MadviseError!void { + switch (errno(system.madvise(ptr, length, advice))) { + .SUCCESS => return, + .ACCES => return error.AccessDenied, + .AGAIN => return error.SystemResources, + .BADF => unreachable, // The map exists, but the area maps something that isn't a file. + .INVAL => return error.InvalidSyscall, + .IO => return error.WouldExceedMaximumResidentSetSize, + .NOMEM => return error.OutOfMemory, + .NOSYS => return error.MadviseUnavailable, + else => |err| return unexpectedErrno(err), + } +} + +pub const PerfEventOpenError = error{ + /// Returned if the perf_event_attr size value is too small (smaller + /// than PERF_ATTR_SIZE_VER0), too big (larger than the page size), + /// or larger than the kernel supports and the extra bytes are not + /// zero. When E2BIG is returned, the perf_event_attr size field is + /// overwritten by the kernel to be the size of the structure it was + /// expecting. + TooBig, + /// Returned when the requested event requires CAP_SYS_ADMIN permis‐ + /// sions (or a more permissive perf_event paranoid setting). Some + /// common cases where an unprivileged process may encounter this + /// error: attaching to a process owned by a different user; moni‐ + /// toring all processes on a given CPU (i.e., specifying the pid + /// argument as -1); and not setting exclude_kernel when the para‐ + /// noid setting requires it. + /// Also: + /// Returned on many (but not all) architectures when an unsupported + /// exclude_hv, exclude_idle, exclude_user, or exclude_kernel set‐ + /// ting is specified. + /// It can also happen, as with EACCES, when the requested event re‐ + /// quires CAP_SYS_ADMIN permissions (or a more permissive + /// perf_event paranoid setting). This includes setting a break‐ + /// point on a kernel address, and (since Linux 3.13) setting a ker‐ + /// nel function-trace tracepoint. + PermissionDenied, + /// Returned if another event already has exclusive access to the + /// PMU. + DeviceBusy, + /// Each opened event uses one file descriptor. If a large number + /// of events are opened, the per-process limit on the number of + /// open file descriptors will be reached, and no more events can be + /// created. + ProcessResources, + EventRequiresUnsupportedCpuFeature, + /// Returned if you try to add more breakpoint + /// events than supported by the hardware. + TooManyBreakpoints, + /// Returned if PERF_SAMPLE_STACK_USER is set in sample_type and it + /// is not supported by hardware. + SampleStackNotSupported, + /// Returned if an event requiring a specific hardware feature is + /// requested but there is no hardware support. This includes re‐ + /// questing low-skid events if not supported, branch tracing if it + /// is not available, sampling if no PMU interrupt is available, and + /// branch stacks for software events. + EventNotSupported, + /// Returned if PERF_SAMPLE_CALLCHAIN is requested and sam‐ + /// ple_max_stack is larger than the maximum specified in + /// /proc/sys/kernel/perf_event_max_stack. + SampleMaxStackOverflow, + /// Returned if attempting to attach to a process that does not exist. + ProcessNotFound, +} || UnexpectedError; + +pub fn perf_event_open( + attr: *linux.perf_event_attr, + pid: pid_t, + cpu: i32, + group_fd: fd_t, + flags: usize, +) PerfEventOpenError!fd_t { + const rc = linux.perf_event_open(attr, pid, cpu, group_fd, flags); + switch (errno(rc)) { + .SUCCESS => return @intCast(rc), + .@"2BIG" => return error.TooBig, + .ACCES => return error.PermissionDenied, + .BADF => unreachable, // group_fd file descriptor is not valid. + .BUSY => return error.DeviceBusy, + .FAULT => unreachable, // Segmentation fault. + .INVAL => unreachable, // Bad attr settings. + .INTR => unreachable, // Mixed perf and ftrace handling for a uprobe. + .MFILE => return error.ProcessResources, + .NODEV => return error.EventRequiresUnsupportedCpuFeature, + .NOENT => unreachable, // Invalid type setting. + .NOSPC => return error.TooManyBreakpoints, + .NOSYS => return error.SampleStackNotSupported, + .OPNOTSUPP => return error.EventNotSupported, + .OVERFLOW => return error.SampleMaxStackOverflow, + .PERM => return error.PermissionDenied, + .SRCH => return error.ProcessNotFound, + else => |err| return unexpectedErrno(err), + } +} + +pub const TimerFdCreateError = error{ + AccessDenied, + ProcessFdQuotaExceeded, + SystemFdQuotaExceeded, + NoDevice, + SystemResources, +} || UnexpectedError; + +pub const TimerFdGetError = error{InvalidHandle} || UnexpectedError; +pub const TimerFdSetError = TimerFdGetError || error{Canceled}; + +pub fn timerfd_create(clokid: i32, flags: linux.TFD) TimerFdCreateError!fd_t { + const rc = linux.timerfd_create(clokid, flags); + return switch (errno(rc)) { + .SUCCESS => @intCast(rc), + .INVAL => unreachable, + .MFILE => return error.ProcessFdQuotaExceeded, + .NFILE => return error.SystemFdQuotaExceeded, + .NODEV => return error.NoDevice, + .NOMEM => return error.SystemResources, + .PERM => return error.AccessDenied, + else => |err| return unexpectedErrno(err), + }; +} + +pub fn timerfd_settime( + fd: i32, + flags: linux.TFD.TIMER, + new_value: *const linux.itimerspec, + old_value: ?*linux.itimerspec, +) TimerFdSetError!void { + const rc = linux.timerfd_settime(fd, flags, new_value, old_value); + return switch (errno(rc)) { + .SUCCESS => {}, + .BADF => error.InvalidHandle, + .FAULT => unreachable, + .INVAL => unreachable, + .CANCELED => error.Canceled, + else => |err| return unexpectedErrno(err), + }; +} + +pub fn timerfd_gettime(fd: i32) TimerFdGetError!linux.itimerspec { + var curr_value: linux.itimerspec = undefined; + const rc = linux.timerfd_gettime(fd, &curr_value); + return switch (errno(rc)) { + .SUCCESS => return curr_value, + .BADF => error.InvalidHandle, + .FAULT => unreachable, + .INVAL => unreachable, + else => |err| return unexpectedErrno(err), + }; +} + +pub const PtraceError = error{ + DeviceBusy, + InputOutput, + ProcessNotFound, + PermissionDenied, +} || UnexpectedError; + +pub fn ptrace(request: u32, pid: pid_t, addr: usize, signal: usize) PtraceError!void { + if (native_os == .windows or native_os == .wasi) + @compileError("Unsupported OS"); + + return switch (native_os) { + .linux => switch (errno(linux.ptrace(request, pid, addr, signal, 0))) { + .SUCCESS => {}, + .SRCH => error.ProcessNotFound, + .FAULT => unreachable, + .INVAL => unreachable, + .IO => return error.InputOutput, + .PERM => error.PermissionDenied, + .BUSY => error.DeviceBusy, + else => |err| return unexpectedErrno(err), + }, + + .macos, .ios, .tvos, .watchos => switch (errno(std.c.ptrace( + @intCast(request), + pid, + @ptrFromInt(addr), + @intCast(signal), + ))) { + .SUCCESS => {}, + .SRCH => error.ProcessNotFound, + .INVAL => unreachable, + .PERM => error.PermissionDenied, + .BUSY => error.DeviceBusy, + else => |err| return unexpectedErrno(err), + }, + + else => switch (errno(system.ptrace(request, pid, addr, signal))) { + .SUCCESS => {}, + .SRCH => error.ProcessNotFound, + .INVAL => unreachable, + .PERM => error.PermissionDenied, + .BUSY => error.DeviceBusy, + else => |err| return unexpectedErrno(err), + }, + }; +} + +pub const IoCtl_SIOCGIFINDEX_Error = error{ + FileSystem, + InterfaceNotFound, +} || UnexpectedError; + +pub fn ioctl_SIOCGIFINDEX(fd: fd_t, ifr: *ifreq) IoCtl_SIOCGIFINDEX_Error!void { + while (true) { + switch (errno(system.ioctl(fd, SIOCGIFINDEX, @intFromPtr(ifr)))) { + .SUCCESS => return, + .INVAL => unreachable, // Bad parameters. + .NOTTY => unreachable, + .NXIO => unreachable, + .BADF => unreachable, // Always a race condition. + .FAULT => unreachable, // Bad pointer parameter. + .INTR => continue, + .IO => return error.FileSystem, + .NODEV => return error.InterfaceNotFound, + else => |err| return unexpectedErrno(err), + } + } +} + +const lfs64_abi = native_os == .linux and builtin.link_libc and builtin.abi.isGnu(); + +/// Whether or not `error.Unexpected` will print its value and a stack trace. +/// +/// If this happens the fix is to add the error code to the corresponding +/// switch expression, possibly introduce a new error in the error set, and +/// send a patch to Zig. +pub const unexpected_error_tracing = builtin.zig_backend == .stage2_llvm and builtin.mode == .Debug; + +pub const UnexpectedError = error{ + /// The Operating System returned an undocumented error code. + /// + /// This error is in theory not possible, but it would be better + /// to handle this error than to invoke undefined behavior. + /// + /// When this error code is observed, it usually means the Zig Standard + /// Library needs a small patch to add the error code to the error set for + /// the respective function. + Unexpected, +}; + +/// Call this when you made a syscall or something that sets errno +/// and you get an unexpected error. +pub fn unexpectedErrno(err: E) UnexpectedError { + if (unexpected_error_tracing) { + std.debug.print("unexpected errno: {d}\n", .{@intFromEnum(err)}); + std.debug.dumpCurrentStackTrace(null); + } + return error.Unexpected; +} + +/// Used to convert a slice to a null terminated slice on the stack. +pub fn toPosixPath(file_path: []const u8) error{NameTooLong}![PATH_MAX - 1:0]u8 { + if (std.debug.runtime_safety) assert(mem.indexOfScalar(u8, file_path, 0) == null); + var path_with_null: [PATH_MAX - 1:0]u8 = undefined; + // >= rather than > to make room for the null byte + if (file_path.len >= PATH_MAX) return error.NameTooLong; + @memcpy(path_with_null[0..file_path.len], file_path); + path_with_null[file_path.len] = 0; + return path_with_null; +} diff --git a/lib/std/os/test.zig b/lib/std/posix/test.zig similarity index 72% rename from lib/std/os/test.zig rename to lib/std/posix/test.zig index d9f05d94ef93..1020bef4b7b0 100644 --- a/lib/std/os/test.zig +++ b/lib/std/posix/test.zig @@ -1,5 +1,5 @@ const std = @import("../std.zig"); -const os = std.os; +const posix = std.posix; const testing = std.testing; const expect = testing.expect; const expectEqual = testing.expectEqual; @@ -10,6 +10,7 @@ const mem = std.mem; const elf = std.elf; const File = std.fs.File; const Thread = std.Thread; +const linux = std.os.linux; const a = std.testing.allocator; @@ -31,26 +32,26 @@ test "chdir smoke test" { // Get current working directory path var old_cwd_buf: [fs.MAX_PATH_BYTES]u8 = undefined; - const old_cwd = try os.getcwd(old_cwd_buf[0..]); + const old_cwd = try posix.getcwd(old_cwd_buf[0..]); { // Firstly, changing to itself should have no effect - try os.chdir(old_cwd); + try posix.chdir(old_cwd); var new_cwd_buf: [fs.MAX_PATH_BYTES]u8 = undefined; - const new_cwd = try os.getcwd(new_cwd_buf[0..]); + const new_cwd = try posix.getcwd(new_cwd_buf[0..]); try expect(mem.eql(u8, old_cwd, new_cwd)); } // Next, change current working directory to one level above if (native_os != .wasi) { // WASI does not support navigating outside of Preopens const parent = fs.path.dirname(old_cwd) orelse unreachable; // old_cwd should be absolute - try os.chdir(parent); + try posix.chdir(parent); // Restore cwd because process may have other tests that do not tolerate chdir. - defer os.chdir(old_cwd) catch unreachable; + defer posix.chdir(old_cwd) catch unreachable; var new_cwd_buf: [fs.MAX_PATH_BYTES]u8 = undefined; - const new_cwd = try os.getcwd(new_cwd_buf[0..]); + const new_cwd = try posix.getcwd(new_cwd_buf[0..]); try expect(mem.eql(u8, parent, new_cwd)); } @@ -65,10 +66,10 @@ test "chdir smoke test" { var tmp_dir = try fs.cwd().makeOpenPath("zig-test-tmp", .{}); // Change current working directory to tmp directory - try os.chdir("zig-test-tmp"); + try posix.chdir("zig-test-tmp"); var new_cwd_buf: [fs.MAX_PATH_BYTES]u8 = undefined; - const new_cwd = try os.getcwd(new_cwd_buf[0..]); + const new_cwd = try posix.getcwd(new_cwd_buf[0..]); // On Windows, fs.path.resolve returns an uppercase drive letter, but the drive letter returned by getcwd may be lowercase var resolved_cwd_buf: [fs.MAX_PATH_BYTES]u8 = undefined; @@ -80,7 +81,7 @@ test "chdir smoke test" { // Restore cwd because process may have other tests that do not tolerate chdir. tmp_dir.close(); - os.chdir(old_cwd) catch unreachable; + posix.chdir(old_cwd) catch unreachable; try fs.cwd().deleteDir("zig-test-tmp"); } } @@ -105,39 +106,39 @@ test "open smoke test" { }; var file_path: []u8 = undefined; - var fd: os.fd_t = undefined; - const mode: os.mode_t = if (native_os == .windows) 0 else 0o666; + var fd: posix.fd_t = undefined; + const mode: posix.mode_t = if (native_os == .windows) 0 else 0o666; // Create some file using `open`. file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" }); - fd = try os.open(file_path, .{ .ACCMODE = .RDWR, .CREAT = true, .EXCL = true }, mode); - os.close(fd); + fd = try posix.open(file_path, .{ .ACCMODE = .RDWR, .CREAT = true, .EXCL = true }, mode); + posix.close(fd); // Try this again with the same flags. This op should fail with error.PathAlreadyExists. file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" }); - try expectError(error.PathAlreadyExists, os.open(file_path, .{ .ACCMODE = .RDWR, .CREAT = true, .EXCL = true }, mode)); + try expectError(error.PathAlreadyExists, posix.open(file_path, .{ .ACCMODE = .RDWR, .CREAT = true, .EXCL = true }, mode)); // Try opening without `EXCL` flag. file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" }); - fd = try os.open(file_path, .{ .ACCMODE = .RDWR, .CREAT = true }, mode); - os.close(fd); + fd = try posix.open(file_path, .{ .ACCMODE = .RDWR, .CREAT = true }, mode); + posix.close(fd); // Try opening as a directory which should fail. file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" }); - try expectError(error.NotDir, os.open(file_path, .{ .ACCMODE = .RDWR, .DIRECTORY = true }, mode)); + try expectError(error.NotDir, posix.open(file_path, .{ .ACCMODE = .RDWR, .DIRECTORY = true }, mode)); // Create some directory file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_dir" }); - try os.mkdir(file_path, mode); + try posix.mkdir(file_path, mode); // Open dir using `open` file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_dir" }); - fd = try os.open(file_path, .{ .ACCMODE = .RDONLY, .DIRECTORY = true }, mode); - os.close(fd); + fd = try posix.open(file_path, .{ .ACCMODE = .RDONLY, .DIRECTORY = true }, mode); + posix.close(fd); // Try opening as file which should fail. file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_dir" }); - try expectError(error.IsDir, os.open(file_path, .{ .ACCMODE = .RDWR }, mode)); + try expectError(error.IsDir, posix.open(file_path, .{ .ACCMODE = .RDWR }, mode)); } test "openat smoke test" { @@ -149,49 +150,49 @@ test "openat smoke test" { var tmp = tmpDir(.{}); defer tmp.cleanup(); - var fd: os.fd_t = undefined; - const mode: os.mode_t = if (native_os == .windows) 0 else 0o666; + var fd: posix.fd_t = undefined; + const mode: posix.mode_t = if (native_os == .windows) 0 else 0o666; // Create some file using `openat`. - fd = try os.openat(tmp.dir.fd, "some_file", os.CommonOpenFlags.lower(.{ + fd = try posix.openat(tmp.dir.fd, "some_file", CommonOpenFlags.lower(.{ .ACCMODE = .RDWR, .CREAT = true, .EXCL = true, }), mode); - os.close(fd); + posix.close(fd); // Try this again with the same flags. This op should fail with error.PathAlreadyExists. - try expectError(error.PathAlreadyExists, os.openat(tmp.dir.fd, "some_file", os.CommonOpenFlags.lower(.{ + try expectError(error.PathAlreadyExists, posix.openat(tmp.dir.fd, "some_file", CommonOpenFlags.lower(.{ .ACCMODE = .RDWR, .CREAT = true, .EXCL = true, }), mode)); // Try opening without `EXCL` flag. - fd = try os.openat(tmp.dir.fd, "some_file", os.CommonOpenFlags.lower(.{ + fd = try posix.openat(tmp.dir.fd, "some_file", CommonOpenFlags.lower(.{ .ACCMODE = .RDWR, .CREAT = true, }), mode); - os.close(fd); + posix.close(fd); // Try opening as a directory which should fail. - try expectError(error.NotDir, os.openat(tmp.dir.fd, "some_file", os.CommonOpenFlags.lower(.{ + try expectError(error.NotDir, posix.openat(tmp.dir.fd, "some_file", CommonOpenFlags.lower(.{ .ACCMODE = .RDWR, .DIRECTORY = true, }), mode)); // Create some directory - try os.mkdirat(tmp.dir.fd, "some_dir", mode); + try posix.mkdirat(tmp.dir.fd, "some_dir", mode); // Open dir using `open` - fd = try os.openat(tmp.dir.fd, "some_dir", os.CommonOpenFlags.lower(.{ + fd = try posix.openat(tmp.dir.fd, "some_dir", CommonOpenFlags.lower(.{ .ACCMODE = .RDONLY, .DIRECTORY = true, }), mode); - os.close(fd); + posix.close(fd); // Try opening as file which should fail. - try expectError(error.IsDir, os.openat(tmp.dir.fd, "some_dir", os.CommonOpenFlags.lower(.{ + try expectError(error.IsDir, posix.openat(tmp.dir.fd, "some_dir", CommonOpenFlags.lower(.{ .ACCMODE = .RDWR, }), mode)); } @@ -211,7 +212,7 @@ test "symlink with relative paths" { try cwd.writeFile("file.txt", "nonsense"); if (native_os == .windows) { - os.windows.CreateSymbolicLink( + std.os.windows.CreateSymbolicLink( cwd.fd, &[_]u16{ 's', 'y', 'm', 'l', 'i', 'n', 'k', 'e', 'd' }, &[_:0]u16{ 'f', 'i', 'l', 'e', '.', 't', 'x', 't' }, @@ -226,11 +227,11 @@ test "symlink with relative paths" { else => return err, }; } else { - try os.symlink("file.txt", "symlinked"); + try posix.symlink("file.txt", "symlinked"); } var buffer: [fs.MAX_PATH_BYTES]u8 = undefined; - const given = try os.readlink("symlinked", buffer[0..]); + const given = try posix.readlink("symlinked", buffer[0..]); try expect(mem.eql(u8, "file.txt", given)); try cwd.deleteFile("file.txt"); @@ -247,7 +248,7 @@ test "readlink on Windows" { fn testReadlink(target_path: []const u8, symlink_path: []const u8) !void { var buffer: [fs.MAX_PATH_BYTES]u8 = undefined; - const given = try os.readlink(symlink_path, buffer[0..]); + const given = try posix.readlink(symlink_path, buffer[0..]); try expect(mem.eql(u8, target_path, given)); } @@ -268,7 +269,7 @@ test "link with relative paths" { cwd.deleteFile("new.txt") catch {}; try cwd.writeFile("example.txt", "example"); - try os.link("example.txt", "new.txt", 0); + try posix.link("example.txt", "new.txt", 0); const efd = try cwd.openFile("example.txt", .{}); defer efd.close(); @@ -277,17 +278,17 @@ test "link with relative paths" { defer nfd.close(); { - const estat = try os.fstat(efd.handle); - const nstat = try os.fstat(nfd.handle); + const estat = try posix.fstat(efd.handle); + const nstat = try posix.fstat(nfd.handle); try testing.expectEqual(estat.ino, nstat.ino); try testing.expectEqual(@as(@TypeOf(nstat.nlink), 2), nstat.nlink); } - try os.unlink("new.txt"); + try posix.unlink("new.txt"); { - const estat = try os.fstat(efd.handle); + const estat = try posix.fstat(efd.handle); try testing.expectEqual(@as(@TypeOf(estat.nlink), 1), estat.nlink); } @@ -312,7 +313,7 @@ test "linkat with different directories" { tmp.dir.deleteFile("new.txt") catch {}; try cwd.writeFile("example.txt", "example"); - try os.linkat(cwd.fd, "example.txt", tmp.dir.fd, "new.txt", 0); + try posix.linkat(cwd.fd, "example.txt", tmp.dir.fd, "new.txt", 0); const efd = try cwd.openFile("example.txt", .{}); defer efd.close(); @@ -321,17 +322,17 @@ test "linkat with different directories" { { defer nfd.close(); - const estat = try os.fstat(efd.handle); - const nstat = try os.fstat(nfd.handle); + const estat = try posix.fstat(efd.handle); + const nstat = try posix.fstat(nfd.handle); try testing.expectEqual(estat.ino, nstat.ino); try testing.expectEqual(@as(@TypeOf(nstat.nlink), 2), nstat.nlink); } - try os.unlinkat(tmp.dir.fd, "new.txt", 0); + try posix.unlinkat(tmp.dir.fd, "new.txt", 0); { - const estat = try os.fstat(efd.handle); + const estat = try posix.fstat(efd.handle); try testing.expectEqual(@as(@TypeOf(estat.nlink), 1), estat.nlink); } @@ -351,12 +352,12 @@ test "fstatat" { // fetch file's info on the opened fd directly const file = try tmp.dir.openFile("file.txt", .{}); - const stat = try os.fstat(file.handle); + const stat = try posix.fstat(file.handle); defer file.close(); // now repeat but using `fstatat` instead - const flags = if (native_os == .wasi) 0x0 else os.AT.SYMLINK_NOFOLLOW; - const statat = try os.fstatat(tmp.dir.fd, "file.txt", flags); + const flags = if (native_os == .wasi) 0x0 else posix.AT.SYMLINK_NOFOLLOW; + const statat = try posix.fstatat(tmp.dir.fd, "file.txt", flags); try expectEqual(stat, statat); } @@ -369,7 +370,7 @@ test "readlinkat" { // create a symbolic link if (native_os == .windows) { - os.windows.CreateSymbolicLink( + std.os.windows.CreateSymbolicLink( tmp.dir.fd, &[_]u16{ 'l', 'i', 'n', 'k' }, &[_:0]u16{ 'f', 'i', 'l', 'e', '.', 't', 'x', 't' }, @@ -380,12 +381,12 @@ test "readlinkat" { else => return err, }; } else { - try os.symlinkat("file.txt", tmp.dir.fd, "link"); + try posix.symlinkat("file.txt", tmp.dir.fd, "link"); } // read the link var buffer: [fs.MAX_PATH_BYTES]u8 = undefined; - const read_link = try os.readlinkat(tmp.dir.fd, "link", buffer[0..]); + const read_link = try posix.readlinkat(tmp.dir.fd, "link", buffer[0..]); try expect(mem.eql(u8, "file.txt", read_link)); } @@ -456,8 +457,8 @@ fn testTls() !void { test "getrandom" { var buf_a: [50]u8 = undefined; var buf_b: [50]u8 = undefined; - try os.getrandom(&buf_a); - try os.getrandom(&buf_b); + try posix.getrandom(&buf_a); + try posix.getrandom(&buf_b); // If this test fails the chance is significantly higher that there is a bug than // that two sets of 50 bytes were equal. try expect(!mem.eql(u8, &buf_a, &buf_b)); @@ -466,23 +467,23 @@ test "getrandom" { test "getcwd" { // at least call it so it gets compiled var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; - _ = os.getcwd(&buf) catch undefined; + _ = posix.getcwd(&buf) catch undefined; } test "sigaltstack" { if (native_os == .windows or native_os == .wasi) return error.SkipZigTest; - var st: os.stack_t = undefined; - try os.sigaltstack(null, &st); + var st: posix.stack_t = undefined; + try posix.sigaltstack(null, &st); // Setting a stack size less than MINSIGSTKSZ returns ENOMEM st.flags = 0; st.size = 1; - try testing.expectError(error.SizeTooSmall, os.sigaltstack(&st, null)); + try testing.expectError(error.SizeTooSmall, posix.sigaltstack(&st, null)); } // If the type is not available use void to avoid erroring out when `iter_fn` is // analyzed -const dl_phdr_info = if (@hasDecl(os.system, "dl_phdr_info")) os.dl_phdr_info else anyopaque; +const dl_phdr_info = if (@hasDecl(posix.system, "dl_phdr_info")) posix.dl_phdr_info else anyopaque; const IterFnError = error{ MissingPtLoadSegment, @@ -527,7 +528,7 @@ test "dl_iterate_phdr" { if (builtin.object_format != .elf) return error.SkipZigTest; var counter: usize = 0; - try os.dl_iterate_phdr(&counter, IterFnError, iter_fn); + try posix.dl_iterate_phdr(&counter, IterFnError, iter_fn); try expect(counter != 0); } @@ -535,8 +536,8 @@ test "gethostname" { if (native_os == .windows or native_os == .wasi) return error.SkipZigTest; - var buf: [os.HOST_NAME_MAX]u8 = undefined; - const hostname = try os.gethostname(&buf); + var buf: [posix.HOST_NAME_MAX]u8 = undefined; + const hostname = try posix.gethostname(&buf); try expect(hostname.len != 0); } @@ -544,13 +545,13 @@ test "pipe" { if (native_os == .windows or native_os == .wasi) return error.SkipZigTest; - const fds = try os.pipe(); - try expect((try os.write(fds[1], "hello")) == 5); + const fds = try posix.pipe(); + try expect((try posix.write(fds[1], "hello")) == 5); var buf: [16]u8 = undefined; - try expect((try os.read(fds[0], buf[0..])) == 5); + try expect((try posix.read(fds[0], buf[0..])) == 5); try testing.expectEqualSlices(u8, buf[0..5], "hello"); - os.close(fds[1]); - os.close(fds[0]); + posix.close(fds[1]); + posix.close(fds[0]); } test "argsAlloc" { @@ -569,17 +570,17 @@ test "memfd_create" { else => return error.SkipZigTest, } - const fd = os.memfd_create("test", 0) catch |err| switch (err) { + const fd = posix.memfd_create("test", 0) catch |err| switch (err) { // Related: https://github.com/ziglang/zig/issues/4019 error.SystemOutdated => return error.SkipZigTest, else => |e| return e, }; - defer os.close(fd); - try expect((try os.write(fd, "test")) == 4); - try os.lseek_SET(fd, 0); + defer posix.close(fd); + try expect((try posix.write(fd, "test")) == 4); + try posix.lseek_SET(fd, 0); var buf: [10]u8 = undefined; - const bytes_read = try os.read(fd, &buf); + const bytes_read = try posix.read(fd, &buf); try expect(bytes_read == 4); try expect(mem.eql(u8, buf[0..4], "test")); } @@ -593,15 +594,15 @@ test "mmap" { // Simple mmap() call with non page-aligned size { - const data = try os.mmap( + const data = try posix.mmap( null, 1234, - os.PROT.READ | os.PROT.WRITE, + posix.PROT.READ | posix.PROT.WRITE, .{ .TYPE = .PRIVATE, .ANONYMOUS = true }, -1, 0, ); - defer os.munmap(data); + defer posix.munmap(data); try testing.expectEqual(@as(usize, 1234), data.len); @@ -635,15 +636,15 @@ test "mmap" { const file = try tmp.dir.openFile(test_out_file, .{}); defer file.close(); - const data = try os.mmap( + const data = try posix.mmap( null, alloc_size, - os.PROT.READ, + posix.PROT.READ, .{ .TYPE = .PRIVATE }, file.handle, 0, ); - defer os.munmap(data); + defer posix.munmap(data); var mem_stream = io.fixedBufferStream(data); const stream = mem_stream.reader(); @@ -659,15 +660,15 @@ test "mmap" { const file = try tmp.dir.openFile(test_out_file, .{}); defer file.close(); - const data = try os.mmap( + const data = try posix.mmap( null, alloc_size / 2, - os.PROT.READ, + posix.PROT.READ, .{ .TYPE = .PRIVATE }, file.handle, alloc_size / 2, ); - defer os.munmap(data); + defer posix.munmap(data); var mem_stream = io.fixedBufferStream(data); const stream = mem_stream.reader(); @@ -683,14 +684,14 @@ test "mmap" { test "getenv" { if (native_os == .wasi and !builtin.link_libc) { - // std.os.getenv is not supported on WASI due to the need of allocation + // std.posix.getenv is not supported on WASI due to the need of allocation return error.SkipZigTest; } if (native_os == .windows) { - try expect(os.getenvW(&[_:0]u16{ 'B', 'O', 'G', 'U', 'S', 0x11, 0x22, 0x33, 0x44, 0x55 }) == null); + try expect(std.process.getenvW(&[_:0]u16{ 'B', 'O', 'G', 'U', 'S', 0x11, 0x22, 0x33, 0x44, 0x55 }) == null); } else { - try expect(os.getenvZ("BOGUSDOESNOTEXISTENVVAR") == null); + try expect(posix.getenvZ("BOGUSDOESNOTEXISTENVVAR") == null); } } @@ -711,18 +712,18 @@ test "fcntl" { // Note: The test assumes createFile opens the file with CLOEXEC { - const flags = try os.fcntl(file.handle, os.F.GETFD, 0); - try expect((flags & os.FD_CLOEXEC) != 0); + const flags = try posix.fcntl(file.handle, posix.F.GETFD, 0); + try expect((flags & posix.FD_CLOEXEC) != 0); } { - _ = try os.fcntl(file.handle, os.F.SETFD, 0); - const flags = try os.fcntl(file.handle, os.F.GETFD, 0); - try expect((flags & os.FD_CLOEXEC) == 0); + _ = try posix.fcntl(file.handle, posix.F.SETFD, 0); + const flags = try posix.fcntl(file.handle, posix.F.GETFD, 0); + try expect((flags & posix.FD_CLOEXEC) == 0); } { - _ = try os.fcntl(file.handle, os.F.SETFD, os.FD_CLOEXEC); - const flags = try os.fcntl(file.handle, os.F.GETFD, 0); - try expect((flags & os.FD_CLOEXEC) != 0); + _ = try posix.fcntl(file.handle, posix.F.SETFD, posix.FD_CLOEXEC); + const flags = try posix.fcntl(file.handle, posix.F.GETFD, 0); + try expect((flags & posix.FD_CLOEXEC) != 0); } } @@ -731,7 +732,7 @@ test "signalfd" { .linux, .solaris, .illumos => {}, else => return error.SkipZigTest, } - _ = &os.signalfd; + _ = &posix.signalfd; } test "sync" { @@ -748,8 +749,8 @@ test "sync" { tmp.dir.deleteFile(test_out_file) catch {}; } - os.sync(); - try os.syncfs(file.handle); + posix.sync(); + try posix.syncfs(file.handle); } test "fsync" { @@ -768,23 +769,23 @@ test "fsync" { tmp.dir.deleteFile(test_out_file) catch {}; } - try os.fsync(file.handle); - try os.fdatasync(file.handle); + try posix.fsync(file.handle); + try posix.fdatasync(file.handle); } test "getrlimit and setrlimit" { - if (!@hasDecl(os.system, "rlimit")) { + if (!@hasDecl(posix.system, "rlimit")) { return error.SkipZigTest; } - inline for (std.meta.fields(os.rlimit_resource)) |field| { - const resource = @as(os.rlimit_resource, @enumFromInt(field.value)); - const limit = try os.getrlimit(resource); + inline for (std.meta.fields(posix.rlimit_resource)) |field| { + const resource = @as(posix.rlimit_resource, @enumFromInt(field.value)); + const limit = try posix.getrlimit(resource); // XNU kernel does not support RLIMIT_STACK if a custom stack is active, // which looks to always be the case. EINVAL is returned. // See https://github.com/apple-oss-distributions/xnu/blob/5e3eaea39dcf651e66cb99ba7d70e32cc4a99587/bsd/kern/kern_resource.c#L1173 - if (builtin.os.tag.isDarwin() and resource == .STACK) { + if (native_os.isDarwin() and resource == .STACK) { continue; } @@ -794,11 +795,11 @@ test "getrlimit and setrlimit" { // This happens for example if RLIMIT_MEMLOCK is bigger than ~2GiB. // In that case the following the limit would be RLIM_INFINITY and the following setrlimit fails with EPERM. if (comptime builtin.cpu.arch.isMIPS() and builtin.link_libc) { - if (limit.cur != os.linux.RLIM.INFINITY) { - try os.setrlimit(resource, limit); + if (limit.cur != linux.RLIM.INFINITY) { + try posix.setrlimit(resource, limit); } } else { - try os.setrlimit(resource, limit); + try posix.setrlimit(resource, limit); } } } @@ -807,15 +808,15 @@ test "shutdown socket" { if (native_os == .wasi) return error.SkipZigTest; if (native_os == .windows) { - _ = try os.windows.WSAStartup(2, 2); + _ = try std.os.windows.WSAStartup(2, 2); } defer { if (native_os == .windows) { - os.windows.WSACleanup() catch unreachable; + std.os.windows.WSACleanup() catch unreachable; } } - const sock = try os.socket(os.AF.INET, os.SOCK.STREAM, 0); - os.shutdown(sock, .both) catch |err| switch (err) { + const sock = try posix.socket(posix.AF.INET, posix.SOCK.STREAM, 0); + posix.shutdown(sock, .both) catch |err| switch (err) { error.SocketNotConnected => {}, else => |e| return e, }; @@ -838,63 +839,63 @@ test "sigaction" { const S = struct { var handler_called_count: u32 = 0; - fn handler(sig: i32, info: *const os.siginfo_t, ctx_ptr: ?*const anyopaque) callconv(.C) void { + fn handler(sig: i32, info: *const posix.siginfo_t, ctx_ptr: ?*const anyopaque) callconv(.C) void { _ = ctx_ptr; // Check that we received the correct signal. switch (native_os) { .netbsd => { - if (sig == os.SIG.USR1 and sig == info.info.signo) + if (sig == posix.SIG.USR1 and sig == info.info.signo) handler_called_count += 1; }, else => { - if (sig == os.SIG.USR1 and sig == info.signo) + if (sig == posix.SIG.USR1 and sig == info.signo) handler_called_count += 1; }, } } }; - var sa = os.Sigaction{ + var sa: posix.Sigaction = .{ .handler = .{ .sigaction = &S.handler }, - .mask = os.empty_sigset, - .flags = os.SA.SIGINFO | os.SA.RESETHAND, + .mask = posix.empty_sigset, + .flags = posix.SA.SIGINFO | posix.SA.RESETHAND, }; - var old_sa: os.Sigaction = undefined; + var old_sa: posix.Sigaction = undefined; // Install the new signal handler. - try os.sigaction(os.SIG.USR1, &sa, null); + try posix.sigaction(posix.SIG.USR1, &sa, null); // Check that we can read it back correctly. - try os.sigaction(os.SIG.USR1, null, &old_sa); + try posix.sigaction(posix.SIG.USR1, null, &old_sa); try testing.expectEqual(&S.handler, old_sa.handler.sigaction.?); - try testing.expect((old_sa.flags & os.SA.SIGINFO) != 0); + try testing.expect((old_sa.flags & posix.SA.SIGINFO) != 0); // Invoke the handler. - try os.raise(os.SIG.USR1); + try posix.raise(posix.SIG.USR1); try testing.expect(S.handler_called_count == 1); // Check if passing RESETHAND correctly reset the handler to SIG_DFL - try os.sigaction(os.SIG.USR1, null, &old_sa); - try testing.expectEqual(os.SIG.DFL, old_sa.handler.handler); + try posix.sigaction(posix.SIG.USR1, null, &old_sa); + try testing.expectEqual(posix.SIG.DFL, old_sa.handler.handler); // Reinstall the signal w/o RESETHAND and re-raise - sa.flags = os.SA.SIGINFO; - try os.sigaction(os.SIG.USR1, &sa, null); - try os.raise(os.SIG.USR1); + sa.flags = posix.SA.SIGINFO; + try posix.sigaction(posix.SIG.USR1, &sa, null); + try posix.raise(posix.SIG.USR1); try testing.expect(S.handler_called_count == 2); // Now set the signal to ignored - sa.handler = .{ .handler = os.SIG.IGN }; + sa.handler = .{ .handler = posix.SIG.IGN }; sa.flags = 0; - try os.sigaction(os.SIG.USR1, &sa, null); + try posix.sigaction(posix.SIG.USR1, &sa, null); // Re-raise to ensure handler is actually ignored - try os.raise(os.SIG.USR1); + try posix.raise(posix.SIG.USR1); try testing.expect(S.handler_called_count == 2); // Ensure that ignored state is returned when querying - try os.sigaction(os.SIG.USR1, null, &old_sa); - try testing.expectEqual(os.SIG.IGN, old_sa.handler.handler.?); + try posix.sigaction(posix.SIG.USR1, null, &old_sa); + try testing.expectEqual(posix.SIG.IGN, old_sa.handler.handler.?); } test "dup & dup2" { @@ -910,13 +911,13 @@ test "dup & dup2" { var file = try tmp.dir.createFile("os_dup_test", .{}); defer file.close(); - var duped = std.fs.File{ .handle = try os.dup(file.handle) }; + var duped = std.fs.File{ .handle = try posix.dup(file.handle) }; defer duped.close(); try duped.writeAll("dup"); // Tests aren't run in parallel so using the next fd shouldn't be an issue. const new_fd = duped.handle + 1; - try os.dup2(file.handle, new_fd); + try posix.dup2(file.handle, new_fd); var dup2ed = std.fs.File{ .handle = new_fd }; defer dup2ed.close(); try dup2ed.writeAll("dup2"); @@ -938,9 +939,9 @@ test "writev longer than IOV_MAX" { var file = try tmp.dir.createFile("pwritev", .{}); defer file.close(); - const iovecs = [_]os.iovec_const{.{ .iov_base = "a", .iov_len = 1 }} ** (os.IOV_MAX + 1); + const iovecs = [_]posix.iovec_const{.{ .iov_base = "a", .iov_len = 1 }} ** (posix.IOV_MAX + 1); const amt = try file.writev(&iovecs); - try testing.expectEqual(@as(usize, os.IOV_MAX), amt); + try testing.expectEqual(@as(usize, posix.IOV_MAX), amt); } test "POSIX file locking with fcntl" { @@ -964,46 +965,46 @@ test "POSIX file locking with fcntl" { const fd = file.handle; // Place an exclusive lock on the first byte, and a shared lock on the second byte: - var struct_flock = std.mem.zeroInit(os.Flock, .{ .type = os.F.WRLCK }); - _ = try os.fcntl(fd, os.F.SETLK, @intFromPtr(&struct_flock)); + var struct_flock = std.mem.zeroInit(posix.Flock, .{ .type = posix.F.WRLCK }); + _ = try posix.fcntl(fd, posix.F.SETLK, @intFromPtr(&struct_flock)); struct_flock.start = 1; - struct_flock.type = os.F.RDLCK; - _ = try os.fcntl(fd, os.F.SETLK, @intFromPtr(&struct_flock)); + struct_flock.type = posix.F.RDLCK; + _ = try posix.fcntl(fd, posix.F.SETLK, @intFromPtr(&struct_flock)); // Check the locks in a child process: - const pid = try os.fork(); + const pid = try posix.fork(); if (pid == 0) { // child expects be denied the exclusive lock: struct_flock.start = 0; - struct_flock.type = os.F.WRLCK; - try expectError(error.Locked, os.fcntl(fd, os.F.SETLK, @intFromPtr(&struct_flock))); + struct_flock.type = posix.F.WRLCK; + try expectError(error.Locked, posix.fcntl(fd, posix.F.SETLK, @intFromPtr(&struct_flock))); // child expects to get the shared lock: struct_flock.start = 1; - struct_flock.type = os.F.RDLCK; - _ = try os.fcntl(fd, os.F.SETLK, @intFromPtr(&struct_flock)); + struct_flock.type = posix.F.RDLCK; + _ = try posix.fcntl(fd, posix.F.SETLK, @intFromPtr(&struct_flock)); // child waits for the exclusive lock in order to test deadlock: struct_flock.start = 0; - struct_flock.type = os.F.WRLCK; - _ = try os.fcntl(fd, os.F.SETLKW, @intFromPtr(&struct_flock)); + struct_flock.type = posix.F.WRLCK; + _ = try posix.fcntl(fd, posix.F.SETLKW, @intFromPtr(&struct_flock)); // child exits without continuing: - os.exit(0); + posix.exit(0); } else { // parent waits for child to get shared lock: std.time.sleep(1 * std.time.ns_per_ms); // parent expects deadlock when attempting to upgrade the shared lock to exclusive: struct_flock.start = 1; - struct_flock.type = os.F.WRLCK; - try expectError(error.DeadLock, os.fcntl(fd, os.F.SETLKW, @intFromPtr(&struct_flock))); + struct_flock.type = posix.F.WRLCK; + try expectError(error.DeadLock, posix.fcntl(fd, posix.F.SETLKW, @intFromPtr(&struct_flock))); // parent releases exclusive lock: struct_flock.start = 0; - struct_flock.type = os.F.UNLCK; - _ = try os.fcntl(fd, os.F.SETLK, @intFromPtr(&struct_flock)); + struct_flock.type = posix.F.UNLCK; + _ = try posix.fcntl(fd, posix.F.SETLK, @intFromPtr(&struct_flock)); // parent releases shared lock: struct_flock.start = 1; - struct_flock.type = os.F.UNLCK; - _ = try os.fcntl(fd, os.F.SETLK, @intFromPtr(&struct_flock)); + struct_flock.type = posix.F.UNLCK; + _ = try posix.fcntl(fd, posix.F.SETLK, @intFromPtr(&struct_flock)); // parent waits for child: - const result = os.waitpid(pid, 0); + const result = posix.waitpid(pid, 0); try expect(result.status == 0 * 256); } } @@ -1026,43 +1027,43 @@ test "rename smoke test" { }; var file_path: []u8 = undefined; - var fd: os.fd_t = undefined; - const mode: os.mode_t = if (native_os == .windows) 0 else 0o666; + var fd: posix.fd_t = undefined; + const mode: posix.mode_t = if (native_os == .windows) 0 else 0o666; // Create some file using `open`. file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" }); - fd = try os.open(file_path, .{ .ACCMODE = .RDWR, .CREAT = true, .EXCL = true }, mode); - os.close(fd); + fd = try posix.open(file_path, .{ .ACCMODE = .RDWR, .CREAT = true, .EXCL = true }, mode); + posix.close(fd); // Rename the file var new_file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_other_file" }); - try os.rename(file_path, new_file_path); + try posix.rename(file_path, new_file_path); // Try opening renamed file file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_other_file" }); - fd = try os.open(file_path, .{ .ACCMODE = .RDWR }, mode); - os.close(fd); + fd = try posix.open(file_path, .{ .ACCMODE = .RDWR }, mode); + posix.close(fd); // Try opening original file - should fail with error.FileNotFound file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" }); - try expectError(error.FileNotFound, os.open(file_path, .{ .ACCMODE = .RDWR }, mode)); + try expectError(error.FileNotFound, posix.open(file_path, .{ .ACCMODE = .RDWR }, mode)); // Create some directory file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_dir" }); - try os.mkdir(file_path, mode); + try posix.mkdir(file_path, mode); // Rename the directory new_file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_other_dir" }); - try os.rename(file_path, new_file_path); + try posix.rename(file_path, new_file_path); // Try opening renamed directory file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_other_dir" }); - fd = try os.open(file_path, .{ .ACCMODE = .RDONLY, .DIRECTORY = true }, mode); - os.close(fd); + fd = try posix.open(file_path, .{ .ACCMODE = .RDONLY, .DIRECTORY = true }, mode); + posix.close(fd); // Try opening original directory - should fail with error.FileNotFound file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_dir" }); - try expectError(error.FileNotFound, os.open(file_path, .{ .ACCMODE = .RDONLY, .DIRECTORY = true }, mode)); + try expectError(error.FileNotFound, posix.open(file_path, .{ .ACCMODE = .RDONLY, .DIRECTORY = true }, mode)); } test "access smoke test" { @@ -1083,50 +1084,49 @@ test "access smoke test" { }; var file_path: []u8 = undefined; - var fd: os.fd_t = undefined; - const mode: os.mode_t = if (native_os == .windows) 0 else 0o666; + var fd: posix.fd_t = undefined; + const mode: posix.mode_t = if (native_os == .windows) 0 else 0o666; // Create some file using `open`. file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" }); - fd = try os.open(file_path, .{ .ACCMODE = .RDWR, .CREAT = true, .EXCL = true }, mode); - os.close(fd); + fd = try posix.open(file_path, .{ .ACCMODE = .RDWR, .CREAT = true, .EXCL = true }, mode); + posix.close(fd); // Try to access() the file file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" }); - if (builtin.os.tag == .windows) { - try os.access(file_path, os.F_OK); + if (native_os == .windows) { + try posix.access(file_path, posix.F_OK); } else { - try os.access(file_path, os.F_OK | os.W_OK | os.R_OK); + try posix.access(file_path, posix.F_OK | posix.W_OK | posix.R_OK); } // Try to access() a non-existent file - should fail with error.FileNotFound file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_other_file" }); - try expectError(error.FileNotFound, os.access(file_path, os.F_OK)); + try expectError(error.FileNotFound, posix.access(file_path, posix.F_OK)); // Create some directory file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_dir" }); - try os.mkdir(file_path, mode); + try posix.mkdir(file_path, mode); // Try to access() the directory file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_dir" }); - try os.access(file_path, os.F_OK); + try posix.access(file_path, posix.F_OK); } test "timerfd" { if (native_os != .linux) return error.SkipZigTest; - const linux = os.linux; - const tfd = try os.timerfd_create(linux.CLOCK.MONOTONIC, .{ .CLOEXEC = true }); - defer os.close(tfd); + const tfd = try posix.timerfd_create(linux.CLOCK.MONOTONIC, .{ .CLOEXEC = true }); + defer posix.close(tfd); - // Fire event 10_000_000ns = 10ms after the os.timerfd_settime call. + // Fire event 10_000_000ns = 10ms after the posix.timerfd_settime call. var sit: linux.itimerspec = .{ .it_interval = .{ .tv_sec = 0, .tv_nsec = 0 }, .it_value = .{ .tv_sec = 0, .tv_nsec = 10 * (1000 * 1000) } }; - try os.timerfd_settime(tfd, .{}, &sit, null); + try posix.timerfd_settime(tfd, .{}, &sit, null); - var fds: [1]os.pollfd = .{.{ .fd = tfd, .events = os.linux.POLL.IN, .revents = 0 }}; - try expectEqual(@as(usize, 1), try os.poll(&fds, -1)); // -1 => infinite waiting + var fds: [1]posix.pollfd = .{.{ .fd = tfd, .events = linux.POLL.IN, .revents = 0 }}; + try expectEqual(@as(usize, 1), try posix.poll(&fds, -1)); // -1 => infinite waiting - const git = try os.timerfd_gettime(tfd); + const git = try posix.timerfd_gettime(tfd); const expect_disarmed_timer: linux.itimerspec = .{ .it_interval = .{ .tv_sec = 0, .tv_nsec = 0 }, .it_value = .{ .tv_sec = 0, .tv_nsec = 0 } }; try expectEqual(expect_disarmed_timer, git); } @@ -1138,7 +1138,7 @@ test "isatty" { var file = try tmp.dir.createFile("foo", .{}); defer file.close(); - try expectEqual(os.isatty(file.handle), false); + try expectEqual(posix.isatty(file.handle), false); } test "read with empty buffer" { @@ -1163,7 +1163,7 @@ test "read with empty buffer" { const bytes = try allocator.alloc(u8, 0); - _ = try os.read(file.handle, bytes); + _ = try posix.read(file.handle, bytes); } test "pread with empty buffer" { @@ -1188,7 +1188,7 @@ test "pread with empty buffer" { const bytes = try allocator.alloc(u8, 0); - _ = try os.pread(file.handle, bytes, 0); + _ = try posix.pread(file.handle, bytes, 0); } test "write with empty buffer" { @@ -1213,7 +1213,7 @@ test "write with empty buffer" { const bytes = try allocator.alloc(u8, 0); - _ = try os.write(file.handle, bytes); + _ = try posix.write(file.handle, bytes); } test "pwrite with empty buffer" { @@ -1238,11 +1238,11 @@ test "pwrite with empty buffer" { const bytes = try allocator.alloc(u8, 0); - _ = try os.pwrite(file.handle, bytes, 0); + _ = try posix.pwrite(file.handle, bytes, 0); } -fn expectMode(dir: os.fd_t, file: []const u8, mode: os.mode_t) !void { - const st = try os.fstatat(dir, file, os.AT.SYMLINK_NOFOLLOW); +fn expectMode(dir: posix.fd_t, file: []const u8, mode: posix.mode_t) !void { + const st = try posix.fstatat(dir, file, posix.AT.SYMLINK_NOFOLLOW); try expectEqual(mode, st.mode & 0b111_111_111); } @@ -1252,31 +1252,31 @@ test "fchmodat smoke test" { var tmp = tmpDir(.{}); defer tmp.cleanup(); - try expectError(error.FileNotFound, os.fchmodat(tmp.dir.fd, "regfile", 0o666, 0)); - const fd = try os.openat( + try expectError(error.FileNotFound, posix.fchmodat(tmp.dir.fd, "regfile", 0o666, 0)); + const fd = try posix.openat( tmp.dir.fd, "regfile", .{ .ACCMODE = .WRONLY, .CREAT = true, .EXCL = true, .TRUNC = true }, 0o644, ); - os.close(fd); - try os.symlinkat("regfile", tmp.dir.fd, "symlink"); + posix.close(fd); + try posix.symlinkat("regfile", tmp.dir.fd, "symlink"); const sym_mode = blk: { - const st = try os.fstatat(tmp.dir.fd, "symlink", os.AT.SYMLINK_NOFOLLOW); + const st = try posix.fstatat(tmp.dir.fd, "symlink", posix.AT.SYMLINK_NOFOLLOW); break :blk st.mode & 0b111_111_111; }; - try os.fchmodat(tmp.dir.fd, "regfile", 0o640, 0); + try posix.fchmodat(tmp.dir.fd, "regfile", 0o640, 0); try expectMode(tmp.dir.fd, "regfile", 0o640); - try os.fchmodat(tmp.dir.fd, "regfile", 0o600, os.AT.SYMLINK_NOFOLLOW); + try posix.fchmodat(tmp.dir.fd, "regfile", 0o600, posix.AT.SYMLINK_NOFOLLOW); try expectMode(tmp.dir.fd, "regfile", 0o600); - try os.fchmodat(tmp.dir.fd, "symlink", 0o640, 0); + try posix.fchmodat(tmp.dir.fd, "symlink", 0o640, 0); try expectMode(tmp.dir.fd, "regfile", 0o640); try expectMode(tmp.dir.fd, "symlink", sym_mode); var test_link = true; - os.fchmodat(tmp.dir.fd, "symlink", 0o600, os.AT.SYMLINK_NOFOLLOW) catch |err| switch (err) { + posix.fchmodat(tmp.dir.fd, "symlink", 0o600, posix.AT.SYMLINK_NOFOLLOW) catch |err| switch (err) { error.OperationNotSupported => test_link = false, else => |e| return e, }; @@ -1284,3 +1284,34 @@ test "fchmodat smoke test" { try expectMode(tmp.dir.fd, "symlink", 0o600); try expectMode(tmp.dir.fd, "regfile", 0o640); } + +const CommonOpenFlags = packed struct { + ACCMODE: posix.ACCMODE = .RDONLY, + CREAT: bool = false, + EXCL: bool = false, + LARGEFILE: bool = false, + DIRECTORY: bool = false, + CLOEXEC: bool = false, + NONBLOCK: bool = false, + + pub fn lower(cof: CommonOpenFlags) posix.O { + if (native_os == .wasi) return .{ + .read = cof.ACCMODE != .WRONLY, + .write = cof.ACCMODE != .RDONLY, + .CREAT = cof.CREAT, + .EXCL = cof.EXCL, + .DIRECTORY = cof.DIRECTORY, + .NONBLOCK = cof.NONBLOCK, + }; + var result: posix.O = .{ + .ACCMODE = cof.ACCMODE, + .CREAT = cof.CREAT, + .EXCL = cof.EXCL, + .DIRECTORY = cof.DIRECTORY, + .NONBLOCK = cof.NONBLOCK, + .CLOEXEC = cof.CLOEXEC, + }; + if (@hasField(posix.O, "LARGEFILE")) result.LARGEFILE = cof.LARGEFILE; + return result; + } +}; diff --git a/lib/std/process.zig b/lib/std/process.zig index 5360a96521e1..b72c1963f572 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -1,6 +1,5 @@ const std = @import("std.zig"); const builtin = @import("builtin"); -const os = std.os; const fs = std.fs; const mem = std.mem; const math = std.math; @@ -8,20 +7,27 @@ const Allocator = mem.Allocator; const assert = std.debug.assert; const testing = std.testing; const child_process = @import("child_process.zig"); +const native_os = builtin.os.tag; +const posix = std.posix; +const windows = std.os.windows; pub const Child = child_process.ChildProcess; -pub const abort = os.abort; -pub const exit = os.exit; -pub const changeCurDir = os.chdir; -pub const changeCurDirC = os.chdirC; +pub const abort = posix.abort; +pub const exit = posix.exit; +pub const changeCurDir = posix.chdir; +pub const changeCurDirC = posix.chdirC; + +pub const GetCwdError = posix.GetCwdError; /// The result is a slice of `out_buffer`, from index `0`. /// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). /// On other platforms, the result is an opaque sequence of bytes with no particular encoding. pub fn getCwd(out_buffer: []u8) ![]u8 { - return os.getcwd(out_buffer); + return posix.getcwd(out_buffer); } +pub const GetCwdAllocError = Allocator.Error || posix.GetCwdError; + /// Caller must free the returned memory. /// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). /// On other platforms, the result is an opaque sequence of bytes with no particular encoding. @@ -34,7 +40,7 @@ pub fn getCwdAlloc(allocator: Allocator) ![]u8 { var current_buf: []u8 = &stack_buf; while (true) { - if (os.getcwd(current_buf)) |slice| { + if (posix.getcwd(current_buf)) |slice| { return allocator.dupe(u8, slice); } else |err| switch (err) { error.NameTooLong => { @@ -51,7 +57,7 @@ pub fn getCwdAlloc(allocator: Allocator) ![]u8 { } test getCwdAlloc { - if (builtin.os.tag == .wasi) return error.SkipZigTest; + if (native_os == .wasi) return error.SkipZigTest; const cwd = try getCwdAlloc(testing.allocator); testing.allocator.free(cwd); @@ -72,13 +78,13 @@ pub const EnvMap = struct { pub const EnvNameHashContext = struct { fn upcase(c: u21) u21 { if (c <= std.math.maxInt(u16)) - return std.os.windows.ntdll.RtlUpcaseUnicodeChar(@as(u16, @intCast(c))); + return windows.ntdll.RtlUpcaseUnicodeChar(@as(u16, @intCast(c))); return c; } pub fn hash(self: @This(), s: []const u8) u64 { _ = self; - if (builtin.os.tag == .windows) { + if (native_os == .windows) { var h = std.hash.Wyhash.init(0); var it = std.unicode.Wtf8View.initUnchecked(s).iterator(); while (it.nextCodepoint()) |cp| { @@ -96,7 +102,7 @@ pub const EnvMap = struct { pub fn eql(self: @This(), a: []const u8, b: []const u8) bool { _ = self; - if (builtin.os.tag == .windows) { + if (native_os == .windows) { var it_a = std.unicode.Wtf8View.initUnchecked(a).iterator(); var it_b = std.unicode.Wtf8View.initUnchecked(b).iterator(); while (true) { @@ -228,7 +234,7 @@ test "EnvMap" { try testing.expectEqual(@as(EnvMap.Size, 2), env.count()); // case insensitivity on Windows only - if (builtin.os.tag == .windows) { + if (native_os == .windows) { try testing.expectEqualStrings("1", env.get("something_New_aNd_LONGER").?); } else { try testing.expect(null == env.get("something_New_aNd_LONGER")); @@ -248,7 +254,7 @@ test "EnvMap" { try testing.expectEqual(@as(EnvMap.Size, 1), env.count()); - if (builtin.os.tag == .windows) { + if (native_os == .windows) { // test Unicode case-insensitivity on Windows try env.put("КИРиллИЦА", "something else"); try testing.expectEqualStrings("something else", env.get("кириллица").?); @@ -279,8 +285,8 @@ pub fn getEnvMap(allocator: Allocator) GetEnvMapError!EnvMap { var result = EnvMap.init(allocator); errdefer result.deinit(); - if (builtin.os.tag == .windows) { - const ptr = os.windows.peb().ProcessParameters.Environment; + if (native_os == .windows) { + const ptr = windows.peb().ProcessParameters.Environment; var i: usize = 0; while (ptr[i] != 0) { @@ -310,13 +316,13 @@ pub fn getEnvMap(allocator: Allocator) GetEnvMapError!EnvMap { try result.putMove(key, value); } return result; - } else if (builtin.os.tag == .wasi and !builtin.link_libc) { + } else if (native_os == .wasi and !builtin.link_libc) { var environ_count: usize = undefined; var environ_buf_size: usize = undefined; - const environ_sizes_get_ret = os.wasi.environ_sizes_get(&environ_count, &environ_buf_size); + const environ_sizes_get_ret = std.os.wasi.environ_sizes_get(&environ_count, &environ_buf_size); if (environ_sizes_get_ret != .SUCCESS) { - return os.unexpectedErrno(environ_sizes_get_ret); + return posix.unexpectedErrno(environ_sizes_get_ret); } if (environ_count == 0) { @@ -328,9 +334,9 @@ pub fn getEnvMap(allocator: Allocator) GetEnvMapError!EnvMap { const environ_buf = try allocator.alloc(u8, environ_buf_size); defer allocator.free(environ_buf); - const environ_get_ret = os.wasi.environ_get(environ.ptr, environ_buf.ptr); + const environ_get_ret = std.os.wasi.environ_get(environ.ptr, environ_buf.ptr); if (environ_get_ret != .SUCCESS) { - return os.unexpectedErrno(environ_get_ret); + return posix.unexpectedErrno(environ_get_ret); } for (environ) |env| { @@ -356,7 +362,7 @@ pub fn getEnvMap(allocator: Allocator) GetEnvMapError!EnvMap { } return result; } else { - for (os.environ) |line| { + for (std.os.environ) |line| { var line_i: usize = 0; while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {} const key = line[0..line_i]; @@ -391,37 +397,37 @@ pub const GetEnvVarOwnedError = error{ /// On Windows, the value is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). /// On other platforms, the value is an opaque sequence of bytes with no particular encoding. pub fn getEnvVarOwned(allocator: Allocator, key: []const u8) GetEnvVarOwnedError![]u8 { - if (builtin.os.tag == .windows) { + if (native_os == .windows) { const result_w = blk: { var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator); const stack_allocator = stack_alloc.get(); const key_w = try std.unicode.wtf8ToWtf16LeAllocZ(stack_allocator, key); defer stack_allocator.free(key_w); - break :blk std.os.getenvW(key_w) orelse return error.EnvironmentVariableNotFound; + break :blk getenvW(key_w) orelse return error.EnvironmentVariableNotFound; }; // wtf16LeToWtf8Alloc can only fail with OutOfMemory return std.unicode.wtf16LeToWtf8Alloc(allocator, result_w); - } else if (builtin.os.tag == .wasi and !builtin.link_libc) { + } else if (native_os == .wasi and !builtin.link_libc) { var envmap = getEnvMap(allocator) catch return error.OutOfMemory; defer envmap.deinit(); const val = envmap.get(key) orelse return error.EnvironmentVariableNotFound; return allocator.dupe(u8, val); } else { - const result = os.getenv(key) orelse return error.EnvironmentVariableNotFound; + const result = posix.getenv(key) orelse return error.EnvironmentVariableNotFound; return allocator.dupe(u8, result); } } /// On Windows, `key` must be valid UTF-8. pub fn hasEnvVarConstant(comptime key: []const u8) bool { - if (builtin.os.tag == .windows) { + if (native_os == .windows) { const key_w = comptime std.unicode.utf8ToUtf16LeStringLiteral(key); - return std.os.getenvW(key_w) != null; - } else if (builtin.os.tag == .wasi and !builtin.link_libc) { + return getenvW(key_w) != null; + } else if (native_os == .wasi and !builtin.link_libc) { @compileError("hasEnvVarConstant is not supported for WASI without libc"); } else { - return os.getenv(key) != null; + return posix.getenv(key) != null; } } @@ -436,19 +442,63 @@ pub const HasEnvVarError = error{ /// On Windows, if `key` is not valid [WTF-8](https://simonsapin.github.io/wtf-8/), /// then `error.InvalidWtf8` is returned. pub fn hasEnvVar(allocator: Allocator, key: []const u8) HasEnvVarError!bool { - if (builtin.os.tag == .windows) { + if (native_os == .windows) { var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator); const stack_allocator = stack_alloc.get(); const key_w = try std.unicode.wtf8ToWtf16LeAllocZ(stack_allocator, key); defer stack_allocator.free(key_w); - return std.os.getenvW(key_w) != null; - } else if (builtin.os.tag == .wasi and !builtin.link_libc) { + return getenvW(key_w) != null; + } else if (native_os == .wasi and !builtin.link_libc) { var envmap = getEnvMap(allocator) catch return error.OutOfMemory; defer envmap.deinit(); return envmap.getPtr(key) != null; } else { - return os.getenv(key) != null; + return posix.getenv(key) != null; + } +} + +/// Windows-only. Get an environment variable with a null-terminated, WTF-16 encoded name. +/// +/// This function performs a Unicode-aware case-insensitive lookup using RtlEqualUnicodeString. +/// +/// See also: +/// * `std.posix.getenv` +/// * `getEnvMap` +/// * `getEnvVarOwned` +/// * `hasEnvVarConstant` +/// * `hasEnvVar` +pub fn getenvW(key: [*:0]const u16) ?[:0]const u16 { + if (native_os != .windows) { + @compileError("Windows-only"); + } + const key_slice = mem.sliceTo(key, 0); + const ptr = windows.peb().ProcessParameters.Environment; + var i: usize = 0; + while (ptr[i] != 0) { + const key_start = i; + + // There are some special environment variables that start with =, + // so we need a special case to not treat = as a key/value separator + // if it's the first character. + // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133 + if (ptr[key_start] == '=') i += 1; + + while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {} + const this_key = ptr[key_start..i]; + + if (ptr[i] == '=') i += 1; + + const value_start = i; + while (ptr[i] != 0) : (i += 1) {} + const this_value = ptr[value_start..i :0]; + + if (windows.eqlIgnoreCaseWTF16(key_slice, this_key)) { + return this_value; + } + + i += 1; // skip over null byte } + return null; } test getEnvVarOwned { @@ -459,7 +509,7 @@ test getEnvVarOwned { } test hasEnvVarConstant { - if (builtin.os.tag == .wasi and !builtin.link_libc) return error.SkipZigTest; + if (native_os == .wasi and !builtin.link_libc) return error.SkipZigTest; try testing.expect(!hasEnvVarConstant("BADENV")); } @@ -478,14 +528,14 @@ pub const ArgIteratorPosix = struct { pub fn init() ArgIteratorPosix { return ArgIteratorPosix{ .index = 0, - .count = os.argv.len, + .count = std.os.argv.len, }; } pub fn next(self: *ArgIteratorPosix) ?[:0]const u8 { if (self.index == self.count) return null; - const s = os.argv[self.index]; + const s = std.os.argv[self.index]; self.index += 1; return mem.sliceTo(s, 0); } @@ -503,7 +553,7 @@ pub const ArgIteratorWasi = struct { index: usize, args: [][:0]u8, - pub const InitError = error{OutOfMemory} || os.UnexpectedError; + pub const InitError = error{OutOfMemory} || posix.UnexpectedError; /// You must call deinit to free the internal buffer of the /// iterator after you are done. @@ -517,13 +567,12 @@ pub const ArgIteratorWasi = struct { } fn internalInit(allocator: Allocator) InitError![][:0]u8 { - const w = os.wasi; var count: usize = undefined; var buf_size: usize = undefined; - switch (w.args_sizes_get(&count, &buf_size)) { + switch (std.os.wasi.args_sizes_get(&count, &buf_size)) { .SUCCESS => {}, - else => |err| return os.unexpectedErrno(err), + else => |err| return posix.unexpectedErrno(err), } if (count == 0) { @@ -535,9 +584,9 @@ pub const ArgIteratorWasi = struct { const argv_buf = try allocator.alloc(u8, buf_size); - switch (w.args_get(argv.ptr, argv_buf.ptr)) { + switch (std.os.wasi.args_get(argv.ptr, argv_buf.ptr)) { .SUCCESS => {}, - else => |err| return os.unexpectedErrno(err), + else => |err| return posix.unexpectedErrno(err), } var result_args = try allocator.alloc([:0]u8, count); @@ -1007,7 +1056,7 @@ pub fn ArgIteratorGeneral(comptime options: ArgIteratorGeneralOptions) type { /// Cross-platform command line argument iterator. pub const ArgIterator = struct { - const InnerType = switch (builtin.os.tag) { + const InnerType = switch (native_os) { .windows => ArgIteratorWindows, .wasi => if (builtin.link_libc) ArgIteratorPosix else ArgIteratorWasi, else => ArgIteratorPosix, @@ -1018,10 +1067,10 @@ pub const ArgIterator = struct { /// Initialize the args iterator. Consider using initWithAllocator() instead /// for cross-platform compatibility. pub fn init() ArgIterator { - if (builtin.os.tag == .wasi) { + if (native_os == .wasi) { @compileError("In WASI, use initWithAllocator instead."); } - if (builtin.os.tag == .windows) { + if (native_os == .windows) { @compileError("In Windows, use initWithAllocator instead."); } @@ -1032,11 +1081,11 @@ pub const ArgIterator = struct { /// You must deinitialize iterator's internal buffers by calling `deinit` when done. pub fn initWithAllocator(allocator: Allocator) InitError!ArgIterator { - if (builtin.os.tag == .wasi and !builtin.link_libc) { + if (native_os == .wasi and !builtin.link_libc) { return ArgIterator{ .inner = try InnerType.init(allocator) }; } - if (builtin.os.tag == .windows) { - const cmd_line_w = os.windows.kernel32.GetCommandLineW(); + if (native_os == .windows) { + const cmd_line_w = windows.kernel32.GetCommandLineW(); return ArgIterator{ .inner = try InnerType.init(allocator, cmd_line_w) }; } @@ -1061,11 +1110,11 @@ pub const ArgIterator = struct { /// was created with `initWithAllocator` function. pub fn deinit(self: *ArgIterator) void { // Unless we're targeting WASI or Windows, this is a no-op. - if (builtin.os.tag == .wasi and !builtin.link_libc) { + if (native_os == .wasi and !builtin.link_libc) { self.inner.deinit(); } - if (builtin.os.tag == .windows) { + if (native_os == .windows) { self.inner.deinit(); } } @@ -1334,13 +1383,13 @@ fn testResponseFileCmdLine(input_cmd_line: []const u8, expected_args: []const [] } pub const UserInfo = struct { - uid: os.uid_t, - gid: os.gid_t, + uid: posix.uid_t, + gid: posix.gid_t, }; /// POSIX function which gets a uid from username. pub fn getUserInfo(name: []const u8) !UserInfo { - return switch (builtin.os.tag) { + return switch (native_os) { .linux, .macos, .watchos, @@ -1376,8 +1425,8 @@ pub fn posixGetUserInfo(name: []const u8) !UserInfo { var buf: [std.mem.page_size]u8 = undefined; var name_index: usize = 0; var state = State.Start; - var uid: os.uid_t = 0; - var gid: os.gid_t = 0; + var uid: posix.uid_t = 0; + var gid: posix.gid_t = 0; while (true) { const amt_read = try reader.read(buf[0..]); @@ -1462,36 +1511,36 @@ pub fn posixGetUserInfo(name: []const u8) !UserInfo { } pub fn getBaseAddress() usize { - switch (builtin.os.tag) { + switch (native_os) { .linux => { - const base = os.system.getauxval(std.elf.AT_BASE); + const base = std.os.linux.getauxval(std.elf.AT_BASE); if (base != 0) { return base; } - const phdr = os.system.getauxval(std.elf.AT_PHDR); + const phdr = std.os.linux.getauxval(std.elf.AT_PHDR); return phdr - @sizeOf(std.elf.Ehdr); }, .macos, .freebsd, .netbsd => { return @intFromPtr(&std.c._mh_execute_header); }, - .windows => return @intFromPtr(os.windows.kernel32.GetModuleHandleW(null)), + .windows => return @intFromPtr(windows.kernel32.GetModuleHandleW(null)), else => @compileError("Unsupported OS"), } } /// Tells whether calling the `execv` or `execve` functions will be a compile error. -pub const can_execv = switch (builtin.os.tag) { +pub const can_execv = switch (native_os) { .windows, .haiku, .wasi => false, else => true, }; /// Tells whether spawning child processes is supported (e.g. via ChildProcess) -pub const can_spawn = switch (builtin.os.tag) { +pub const can_spawn = switch (native_os) { .wasi, .watchos, .tvos => false, else => true, }; -pub const ExecvError = std.os.ExecveError || error{OutOfMemory}; +pub const ExecvError = std.posix.ExecveError || error{OutOfMemory}; /// Replaces the current process image with the executed process. /// This function must allocate memory to add a null terminating bytes on path and each arg. @@ -1500,7 +1549,7 @@ pub const ExecvError = std.os.ExecveError || error{OutOfMemory}; /// `argv[0]` is the executable path. /// This function also uses the PATH environment variable to get the full path to the executable. /// Due to the heap-allocation, it is illegal to call this function in a fork() child. -/// For that use case, use the `std.os` functions directly. +/// For that use case, use the `std.posix` functions directly. pub fn execv(allocator: Allocator, argv: []const []const u8) ExecvError { return execve(allocator, argv, null); } @@ -1512,7 +1561,7 @@ pub fn execv(allocator: Allocator, argv: []const []const u8) ExecvError { /// `argv[0]` is the executable path. /// This function also uses the PATH environment variable to get the full path to the executable. /// Due to the heap-allocation, it is illegal to call this function in a fork() child. -/// For that use case, use the `std.os` functions directly. +/// For that use case, use the `std.posix` functions directly. pub fn execve( allocator: Allocator, argv: []const []const u8, @@ -1536,14 +1585,14 @@ pub fn execve( } else if (builtin.output_mode == .Exe) { // Then we have Zig start code and this works. // TODO type-safety for null-termination of `os.environ`. - break :m @as([*:null]const ?[*:0]const u8, @ptrCast(os.environ.ptr)); + break :m @as([*:null]const ?[*:0]const u8, @ptrCast(std.os.environ.ptr)); } else { // TODO come up with a solution for this. @compileError("missing std lib enhancement: std.process.execv implementation has no way to collect the environment variables to forward to the child process"); } }; - return os.execvpeZ_expandArg0(.no_expand, argv_buf.ptr[0].?, argv_buf.ptr, envp); + return posix.execvpeZ_expandArg0(.no_expand, argv_buf.ptr[0].?, argv_buf.ptr, envp); } pub const TotalSystemMemoryError = error{ @@ -1555,14 +1604,14 @@ pub const TotalSystemMemoryError = error{ /// and Linux's /proc/meminfo reporting more memory when /// using QEMU user mode emulation. pub fn totalSystemMemory() TotalSystemMemoryError!u64 { - switch (builtin.os.tag) { + switch (native_os) { .linux => { return totalSystemMemoryLinux() catch return error.UnknownTotalSystemMemory; }, .freebsd => { var physmem: c_ulong = undefined; var len: usize = @sizeOf(c_ulong); - os.sysctlbynameZ("hw.physmem", &physmem, &len, null, 0) catch |err| switch (err) { + posix.sysctlbynameZ("hw.physmem", &physmem, &len, null, 0) catch |err| switch (err) { error.NameTooLong, error.UnknownName => unreachable, else => return error.UnknownTotalSystemMemory, }; @@ -1570,12 +1619,12 @@ pub fn totalSystemMemory() TotalSystemMemoryError!u64 { }, .openbsd => { const mib: [2]c_int = [_]c_int{ - std.os.CTL.HW, - std.os.HW.PHYSMEM64, + posix.CTL.HW, + posix.HW.PHYSMEM64, }; var physmem: i64 = undefined; var len: usize = @sizeOf(@TypeOf(physmem)); - std.os.sysctl(&mib, &physmem, &len, null, 0) catch |err| switch (err) { + posix.sysctl(&mib, &physmem, &len, null, 0) catch |err| switch (err) { error.NameTooLong => unreachable, // constant, known good value error.PermissionDenied => unreachable, // only when setting values, error.SystemResources => unreachable, // memory already on the stack @@ -1586,11 +1635,11 @@ pub fn totalSystemMemory() TotalSystemMemoryError!u64 { return @as(u64, @bitCast(physmem)); }, .windows => { - var sbi: std.os.windows.SYSTEM_BASIC_INFORMATION = undefined; - const rc = std.os.windows.ntdll.NtQuerySystemInformation( + var sbi: windows.SYSTEM_BASIC_INFORMATION = undefined; + const rc = windows.ntdll.NtQuerySystemInformation( .SystemBasicInformation, &sbi, - @sizeOf(std.os.windows.SYSTEM_BASIC_INFORMATION), + @sizeOf(windows.SYSTEM_BASIC_INFORMATION), null, ); if (rc != .SUCCESS) { diff --git a/lib/std/start.zig b/lib/std/start.zig index 49695a3a25b6..3a2b0714f745 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -407,7 +407,7 @@ fn posixCallMainAndExit() callconv(.C) noreturn { // FIXME: Make __aeabi_read_tp call the kernel helper kuser_get_tls // For the time being use a simple abort instead of a @panic call to // keep the binary bloat under control. - std.os.abort(); + std.posix.abort(); } } @@ -422,7 +422,7 @@ fn posixCallMainAndExit() callconv(.C) noreturn { expandStackSize(phdrs); } - std.os.exit(callMainWithArgs(argc, argv, envp)); + std.posix.exit(callMainWithArgs(argc, argv, envp)); } fn expandStackSize(phdrs: []elf.Phdr) void { @@ -432,13 +432,13 @@ fn expandStackSize(phdrs: []elf.Phdr) void { assert(phdr.p_memsz % std.mem.page_size == 0); // Silently fail if we are unable to get limits. - const limits = std.os.getrlimit(.STACK) catch break; + const limits = std.posix.getrlimit(.STACK) catch break; // Clamp to limits.max . const wanted_stack_size = @min(phdr.p_memsz, limits.max); if (wanted_stack_size > limits.cur) { - std.os.setrlimit(.STACK, .{ + std.posix.setrlimit(.STACK, .{ .cur = wanted_stack_size, .max = limits.max, }) catch { @@ -464,7 +464,7 @@ inline fn callMainWithArgs(argc: usize, argv: [*][*:0]u8, envp: [][*:0]u8) u8 { std.os.environ = envp; std.debug.maybeEnableSegfaultHandler(); - std.os.maybeIgnoreSigpipe(); + maybeIgnoreSigpipe(); return callMain(); } @@ -563,3 +563,38 @@ pub fn call_wWinMain() std.os.windows.INT { // second parameter hPrevInstance, MSDN: "This parameter is always NULL" return root.wWinMain(hInstance, null, lpCmdLine, nCmdShow); } + +fn maybeIgnoreSigpipe() void { + const have_sigpipe_support = switch (builtin.os.tag) { + .linux, + .plan9, + .solaris, + .netbsd, + .openbsd, + .haiku, + .macos, + .ios, + .watchos, + .tvos, + .dragonfly, + .freebsd, + => true, + + else => false, + }; + + if (have_sigpipe_support and !std.options.keep_sigpipe) { + const posix = std.posix; + const act: posix.Sigaction = .{ + // Set handler to a noop function instead of `SIG.IGN` to prevent + // leaking signal disposition to a child process. + .handler = .{ .handler = noopSigHandler }, + .mask = posix.empty_sigset, + .flags = 0, + }; + posix.sigaction(posix.SIG.PIPE, &act, null) catch |err| + std.debug.panic("failed to set noop SIGPIPE handler: {s}", .{@errorName(err)}); + } +} + +fn noopSigHandler(_: c_int) callconv(.C) void {} diff --git a/lib/std/std.zig b/lib/std/std.zig index 557b320c244e..8aa12ff31a47 100644 --- a/lib/std/std.zig +++ b/lib/std/std.zig @@ -85,12 +85,11 @@ pub const math = @import("math.zig"); pub const mem = @import("mem.zig"); pub const meta = @import("meta.zig"); pub const net = @import("net.zig"); -pub const posix = @import("os.zig"); -/// Non-portable Operating System-specific API. pub const os = @import("os.zig"); pub const once = @import("once.zig").once; pub const packed_int_array = @import("packed_int_array.zig"); pub const pdb = @import("pdb.zig"); +pub const posix = @import("posix.zig"); pub const process = @import("process.zig"); /// Deprecated: use `Random` instead. pub const rand = Random; @@ -170,3 +169,7 @@ comptime { test { testing.refAllDecls(@This()); } + +comptime { + debug.assert(@import("std") == @This()); // std lib tests require --zig-lib-dir +} diff --git a/lib/std/time.zig b/lib/std/time.zig index 425e028e01aa..3a4be673cc4a 100644 --- a/lib/std/time.zig +++ b/lib/std/time.zig @@ -2,8 +2,9 @@ const std = @import("std.zig"); const builtin = @import("builtin"); const assert = std.debug.assert; const testing = std.testing; -const os = std.os; const math = std.math; +const windows = std.os.windows; +const posix = std.posix; pub const epoch = @import("time/epoch.zig"); @@ -11,8 +12,8 @@ pub const epoch = @import("time/epoch.zig"); pub fn sleep(nanoseconds: u64) void { if (builtin.os.tag == .windows) { const big_ms_from_ns = nanoseconds / ns_per_ms; - const ms = math.cast(os.windows.DWORD, big_ms_from_ns) orelse math.maxInt(os.windows.DWORD); - os.windows.kernel32.Sleep(ms); + const ms = math.cast(windows.DWORD, big_ms_from_ns) orelse math.maxInt(windows.DWORD); + windows.kernel32.Sleep(ms); return; } @@ -40,7 +41,7 @@ pub fn sleep(nanoseconds: u64) void { } if (builtin.os.tag == .uefi) { - const boot_services = os.uefi.system_table.boot_services.?; + const boot_services = std.os.uefi.system_table.boot_services.?; const us_from_ns = nanoseconds / ns_per_us; const us = math.cast(usize, us_from_ns) orelse math.maxInt(usize); _ = boot_services.stall(us); @@ -49,7 +50,7 @@ pub fn sleep(nanoseconds: u64) void { const s = nanoseconds / ns_per_s; const ns = nanoseconds % ns_per_s; - std.os.nanosleep(s, ns); + posix.nanosleep(s, ns); } test "sleep" { @@ -60,7 +61,7 @@ test "sleep" { /// Precision of timing depends on the hardware and operating system. /// The return value is signed because it is possible to have a date that is /// before the epoch. -/// See `std.os.clock_gettime` for a POSIX timestamp. +/// See `posix.clock_gettime` for a POSIX timestamp. pub fn timestamp() i64 { return @divFloor(milliTimestamp(), ms_per_s); } @@ -69,7 +70,7 @@ pub fn timestamp() i64 { /// Precision of timing depends on the hardware and operating system. /// The return value is signed because it is possible to have a date that is /// before the epoch. -/// See `std.os.clock_gettime` for a POSIX timestamp. +/// See `posix.clock_gettime` for a POSIX timestamp. pub fn milliTimestamp() i64 { return @as(i64, @intCast(@divFloor(nanoTimestamp(), ns_per_ms))); } @@ -78,7 +79,7 @@ pub fn milliTimestamp() i64 { /// Precision of timing depends on the hardware and operating system. /// The return value is signed because it is possible to have a date that is /// before the epoch. -/// See `std.os.clock_gettime` for a POSIX timestamp. +/// See `posix.clock_gettime` for a POSIX timestamp. pub fn microTimestamp() i64 { return @as(i64, @intCast(@divFloor(nanoTimestamp(), ns_per_us))); } @@ -88,21 +89,21 @@ pub fn microTimestamp() i64 { /// On Windows this has a maximum granularity of 100 nanoseconds. /// The return value is signed because it is possible to have a date that is /// before the epoch. -/// See `std.os.clock_gettime` for a POSIX timestamp. +/// See `posix.clock_gettime` for a POSIX timestamp. pub fn nanoTimestamp() i128 { switch (builtin.os.tag) { .windows => { // FileTime has a granularity of 100 nanoseconds and uses the NTFS/Windows epoch, // which is 1601-01-01. const epoch_adj = epoch.windows * (ns_per_s / 100); - var ft: os.windows.FILETIME = undefined; - os.windows.kernel32.GetSystemTimeAsFileTime(&ft); + var ft: windows.FILETIME = undefined; + windows.kernel32.GetSystemTimeAsFileTime(&ft); const ft64 = (@as(u64, ft.dwHighDateTime) << 32) | ft.dwLowDateTime; return @as(i128, @as(i64, @bitCast(ft64)) + epoch_adj) * 100; }, .wasi => { - var ns: os.wasi.timestamp_t = undefined; - const err = os.wasi.clock_time_get(.REALTIME, 1, &ns); + var ns: std.os.wasi.timestamp_t = undefined; + const err = std.os.wasi.clock_time_get(.REALTIME, 1, &ns); assert(err == .SUCCESS); return ns; }, @@ -113,8 +114,8 @@ pub fn nanoTimestamp() i128 { return value.toEpoch(); }, else => { - var ts: os.timespec = undefined; - os.clock_gettime(os.CLOCK.REALTIME, &ts) catch |err| switch (err) { + var ts: posix.timespec = undefined; + posix.clock_gettime(posix.CLOCK.REALTIME, &ts) catch |err| switch (err) { error.UnsupportedClock, error.Unexpected => return 0, // "Precision of timing depends on hardware and OS". }; return (@as(i128, ts.tv_sec) * ns_per_s) + ts.tv_nsec; @@ -172,7 +173,7 @@ pub const s_per_week = s_per_day * 7; /// It also tries to be monotonic, but this is not a guarantee due to OS/hardware bugs. /// If you need monotonic readings for elapsed time, consider `Timer` instead. pub const Instant = struct { - timestamp: if (is_posix) os.timespec else u64, + timestamp: if (is_posix) posix.timespec else u64, // true if we should use clock_gettime() const is_posix = switch (builtin.os.tag) { @@ -188,11 +189,11 @@ pub const Instant = struct { const clock_id = switch (builtin.os.tag) { .windows => { // QPC on windows doesn't fail on >= XP/2000 and includes time suspended. - return Instant{ .timestamp = os.windows.QueryPerformanceCounter() }; + return Instant{ .timestamp = windows.QueryPerformanceCounter() }; }, .wasi => { - var ns: os.wasi.timestamp_t = undefined; - const rc = os.wasi.clock_time_get(.MONOTONIC, 1, &ns); + var ns: std.os.wasi.timestamp_t = undefined; + const rc = std.os.wasi.clock_time_get(.MONOTONIC, 1, &ns); if (rc != .SUCCESS) return error.Unsupported; return .{ .timestamp = ns }; }, @@ -204,21 +205,21 @@ pub const Instant = struct { }, // On darwin, use UPTIME_RAW instead of MONOTONIC as it ticks while // suspended. - .macos, .ios, .tvos, .watchos => os.CLOCK.UPTIME_RAW, + .macos, .ios, .tvos, .watchos => posix.CLOCK.UPTIME_RAW, // On freebsd derivatives, use MONOTONIC_FAST as currently there's // no precision tradeoff. - .freebsd, .dragonfly => os.CLOCK.MONOTONIC_FAST, + .freebsd, .dragonfly => posix.CLOCK.MONOTONIC_FAST, // On linux, use BOOTTIME instead of MONOTONIC as it ticks while // suspended. - .linux => os.CLOCK.BOOTTIME, + .linux => posix.CLOCK.BOOTTIME, // On other posix systems, MONOTONIC is generally the fastest and // ticks while suspended. - else => os.CLOCK.MONOTONIC, + else => posix.CLOCK.MONOTONIC, }; - var ts: os.timespec = undefined; - os.clock_gettime(clock_id, &ts) catch return error.Unsupported; - return Instant{ .timestamp = ts }; + var ts: posix.timespec = undefined; + posix.clock_gettime(clock_id, &ts) catch return error.Unsupported; + return .{ .timestamp = ts }; } /// Quickly compares two instances between each other. @@ -245,7 +246,7 @@ pub const Instant = struct { // https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/ns-ntddk-kuser_shared_data // https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntexapi_x/kuser_shared_data/index.htm const qpc = self.timestamp - earlier.timestamp; - const qpf = os.windows.QueryPerformanceFrequency(); + const qpf = windows.QueryPerformanceFrequency(); // 10Mhz (1 qpc tick every 100ns) is a common enough QPF value that we can optimize on it. // https://github.com/microsoft/STL/blob/785143a0c73f030238ef618890fd4d6ae2b3a3a0/stl/inc/chrono#L694-L701 diff --git a/lib/std/zig.zig b/lib/std/zig.zig index ce446e59b606..0b2b29eebfa3 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -1002,7 +1002,7 @@ pub const EnvVar = enum { } pub fn getPosix(comptime ev: EnvVar) ?[:0]const u8 { - return std.os.getenvZ(@tagName(ev)); + return std.posix.getenvZ(@tagName(ev)); } }; diff --git a/lib/std/zig/Server.zig b/lib/std/zig/Server.zig index d112adf45f5c..bf5fdba231b2 100644 --- a/lib/std/zig/Server.zig +++ b/lib/std/zig/Server.zig @@ -146,7 +146,7 @@ pub fn serveMessage( header: OutMessage.Header, bufs: []const []const u8, ) !void { - var iovecs: [10]std.os.iovec_const = undefined; + var iovecs: [10]std.posix.iovec_const = undefined; const header_le = bswap(header); iovecs[0] = .{ .iov_base = @as([*]const u8, @ptrCast(&header_le)), diff --git a/lib/std/zig/system.zig b/lib/std/zig/system.zig index 3599488dca14..b7a5284be9f0 100644 --- a/lib/std/zig/system.zig +++ b/lib/std/zig/system.zig @@ -168,7 +168,7 @@ pub fn resolveTargetQuery(query: Target.Query) DetectError!Target { if (query.os_tag == null) { switch (builtin.target.os.tag) { .linux => { - const uts = std.os.uname(); + const uts = posix.uname(); const release = mem.sliceTo(&uts.release, 0); // The release field sometimes has a weird format, // `Version.parse` will attempt to find some meaningful interpretation. @@ -181,7 +181,7 @@ pub fn resolveTargetQuery(query: Target.Query) DetectError!Target { } }, .solaris, .illumos => { - const uts = std.os.uname(); + const uts = posix.uname(); const release = mem.sliceTo(&uts.release, 0); if (std.SemanticVersion.parse(release)) |ver| { os.version_range.semver.min = ver; @@ -206,7 +206,7 @@ pub fn resolveTargetQuery(query: Target.Query) DetectError!Target { var value: u32 = undefined; var len: usize = @sizeOf(@TypeOf(value)); - std.os.sysctlbynameZ(key, &value, &len, null, 0) catch |err| switch (err) { + posix.sysctlbynameZ(key, &value, &len, null, 0) catch |err| switch (err) { error.NameTooLong => unreachable, // constant, known good value error.PermissionDenied => unreachable, // only when setting values, error.SystemResources => unreachable, // memory already on the stack @@ -257,15 +257,15 @@ pub fn resolveTargetQuery(query: Target.Query) DetectError!Target { }, .openbsd => { const mib: [2]c_int = [_]c_int{ - std.os.CTL.KERN, - std.os.KERN.OSRELEASE, + posix.CTL.KERN, + posix.KERN.OSRELEASE, }; var buf: [64]u8 = undefined; // consider that sysctl result includes null-termination // reserve 1 byte to ensure we never overflow when appending ".0" var len: usize = buf.len - 1; - std.os.sysctl(&mib, &buf, &len, null, 0) catch |err| switch (err) { + posix.sysctl(&mib, &buf, &len, null, 0) catch |err| switch (err) { error.NameTooLong => unreachable, // constant, known good value error.PermissionDenied => unreachable, // only when setting values, error.SystemResources => unreachable, // memory already on the stack @@ -636,8 +636,8 @@ pub fn abiAndDynamicLinkerFromFile( // So far, no luck. Next we try to see if the information is // present in the symlink data for the dynamic linker path. - var link_buf: [std.os.PATH_MAX]u8 = undefined; - const link_name = std.os.readlink(dl_path, &link_buf) catch |err| switch (err) { + var link_buf: [posix.PATH_MAX]u8 = undefined; + const link_name = posix.readlink(dl_path, &link_buf) catch |err| switch (err) { error.NameTooLong => unreachable, error.InvalidUtf8 => unreachable, // WASI only error.InvalidWtf8 => unreachable, // Windows only @@ -670,7 +670,7 @@ pub fn abiAndDynamicLinkerFromFile( // Nothing worked so far. Finally we fall back to hard-coded search paths. // Some distros such as Debian keep their libc.so.6 in `/lib/$triple/`. - var path_buf: [std.os.PATH_MAX]u8 = undefined; + var path_buf: [posix.PATH_MAX]u8 = undefined; var index: usize = 0; const prefix = "/lib/"; const cpu_arch = @tagName(result.cpu.arch); @@ -1138,6 +1138,7 @@ const fs = std.fs; const assert = std.debug.assert; const Target = std.Target; const native_endian = builtin.cpu.arch.endian(); +const posix = std.posix; test { _ = NativePaths; diff --git a/lib/std/zig/system/NativePaths.zig b/lib/std/zig/system/NativePaths.zig index 1d3ce10d9bd8..9d9ab22812fb 100644 --- a/lib/std/zig/system/NativePaths.zig +++ b/lib/std/zig/system/NativePaths.zig @@ -137,21 +137,21 @@ pub fn detect(arena: Allocator, native_target: std.Target) !NativePaths { // variables to search for headers and libraries. // We use os.getenv here since this part won't be executed on // windows, to get rid of unnecessary error handling. - if (std.os.getenv("C_INCLUDE_PATH")) |c_include_path| { + if (std.posix.getenv("C_INCLUDE_PATH")) |c_include_path| { var it = mem.tokenizeScalar(u8, c_include_path, ':'); while (it.next()) |dir| { try self.addIncludeDir(dir); } } - if (std.os.getenv("CPLUS_INCLUDE_PATH")) |cplus_include_path| { + if (std.posix.getenv("CPLUS_INCLUDE_PATH")) |cplus_include_path| { var it = mem.tokenizeScalar(u8, cplus_include_path, ':'); while (it.next()) |dir| { try self.addIncludeDir(dir); } } - if (std.os.getenv("LIBRARY_PATH")) |library_path| { + if (std.posix.getenv("LIBRARY_PATH")) |library_path| { var it = mem.tokenizeScalar(u8, library_path, ':'); while (it.next()) |dir| { try self.addLibDir(dir); diff --git a/lib/std/zig/system/darwin/macos.zig b/lib/std/zig/system/darwin/macos.zig index c4feb7a35b26..3b27c2f8cfe3 100644 --- a/lib/std/zig/system/darwin/macos.zig +++ b/lib/std/zig/system/darwin/macos.zig @@ -3,7 +3,6 @@ const builtin = @import("builtin"); const assert = std.debug.assert; const mem = std.mem; const testing = std.testing; -const os = std.os; const Target = std.Target; @@ -397,7 +396,7 @@ test "detect" { pub fn detectNativeCpuAndFeatures() ?Target.Cpu { var cpu_family: std.c.CPUFAMILY = undefined; var len: usize = @sizeOf(std.c.CPUFAMILY); - os.sysctlbynameZ("hw.cpufamily", &cpu_family, &len, null, 0) catch |err| switch (err) { + std.posix.sysctlbynameZ("hw.cpufamily", &cpu_family, &len, null, 0) catch |err| switch (err) { error.NameTooLong => unreachable, // constant, known good value error.PermissionDenied => unreachable, // only when setting values, error.SystemResources => unreachable, // memory already on the stack diff --git a/src/Compilation.zig b/src/Compilation.zig index 97b7e448dbb1..232c3d32a6d3 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -1137,7 +1137,7 @@ fn addModuleTableToCacheHash( root_mod: *Package.Module, main_mod: *Package.Module, hash_type: union(enum) { path_bytes, files: *Cache.Manifest }, -) (error{OutOfMemory} || std.os.GetCwdError)!void { +) (error{OutOfMemory} || std.process.GetCwdError)!void { var seen_table: std.AutoArrayHashMapUnmanaged(*Package.Module, void) = .{}; defer seen_table.deinit(gpa); @@ -2741,7 +2741,7 @@ const Header = extern struct { /// saved, such as the target and most CLI flags. A cache hit will only occur /// when subsequent compiler invocations use the same set of flags. pub fn saveState(comp: *Compilation) !void { - var bufs_list: [19]std.os.iovec_const = undefined; + var bufs_list: [19]std.posix.iovec_const = undefined; var bufs_len: usize = 0; const lf = comp.bin_file orelse return; @@ -2808,7 +2808,7 @@ pub fn saveState(comp: *Compilation) !void { try af.finish(); } -fn addBuf(bufs_list: []std.os.iovec_const, bufs_len: *usize, buf: []const u8) void { +fn addBuf(bufs_list: []std.posix.iovec_const, bufs_len: *usize, buf: []const u8) void { const i = bufs_len.*; bufs_len.* = i + 1; bufs_list[i] = .{ @@ -3791,7 +3791,7 @@ fn docsCopyFallible(comp: *Compilation) anyerror!void { break :p padding_buffer[0..n]; }; - var header_and_trailer: [2]std.os.iovec_const = .{ + var header_and_trailer: [2]std.posix.iovec_const = .{ .{ .iov_base = header_bytes.ptr, .iov_len = header_bytes.len }, .{ .iov_base = padding.ptr, .iov_len = padding.len }, }; diff --git a/src/DarwinPosixSpawn.zig b/src/DarwinPosixSpawn.zig new file mode 100644 index 000000000000..336848353e6b --- /dev/null +++ b/src/DarwinPosixSpawn.zig @@ -0,0 +1,224 @@ +const errno = std.posix.errno; +const unexpectedErrno = std.posix.unexpectedErrno; + +pub const Error = error{ + SystemResources, + InvalidFileDescriptor, + NameTooLong, + TooBig, + PermissionDenied, + InputOutput, + FileSystem, + FileNotFound, + InvalidExe, + NotDir, + FileBusy, + /// Returned when the child fails to execute either in the pre-exec() initialization step, or + /// when exec(3) is invoked. + ChildExecFailed, +} || std.posix.UnexpectedError; + +pub const Attr = struct { + attr: std.c.posix_spawnattr_t, + + pub fn init() Error!Attr { + var attr: std.c.posix_spawnattr_t = undefined; + switch (errno(std.c.posix_spawnattr_init(&attr))) { + .SUCCESS => return Attr{ .attr = attr }, + .NOMEM => return error.SystemResources, + .INVAL => unreachable, + else => |err| return unexpectedErrno(err), + } + } + + pub fn deinit(self: *Attr) void { + defer self.* = undefined; + switch (errno(std.c.posix_spawnattr_destroy(&self.attr))) { + .SUCCESS => return, + .INVAL => unreachable, // Invalid parameters. + else => unreachable, + } + } + + pub fn get(self: Attr) Error!u16 { + var flags: c_short = undefined; + switch (errno(std.c.posix_spawnattr_getflags(&self.attr, &flags))) { + .SUCCESS => return @as(u16, @bitCast(flags)), + .INVAL => unreachable, + else => |err| return unexpectedErrno(err), + } + } + + pub fn set(self: *Attr, flags: u16) Error!void { + switch (errno(std.c.posix_spawnattr_setflags(&self.attr, @as(c_short, @bitCast(flags))))) { + .SUCCESS => return, + .INVAL => unreachable, + else => |err| return unexpectedErrno(err), + } + } +}; + +pub const Actions = struct { + actions: std.c.posix_spawn_file_actions_t, + + pub fn init() Error!Actions { + var actions: std.c.posix_spawn_file_actions_t = undefined; + switch (errno(std.c.posix_spawn_file_actions_init(&actions))) { + .SUCCESS => return Actions{ .actions = actions }, + .NOMEM => return error.SystemResources, + .INVAL => unreachable, + else => |err| return unexpectedErrno(err), + } + } + + pub fn deinit(self: *Actions) void { + defer self.* = undefined; + switch (errno(std.c.posix_spawn_file_actions_destroy(&self.actions))) { + .SUCCESS => return, + .INVAL => unreachable, // Invalid parameters. + else => unreachable, + } + } + + pub fn open(self: *Actions, fd: std.c.fd_t, path: []const u8, flags: u32, mode: std.c.mode_t) Error!void { + const posix_path = try std.os.toPosixPath(path); + return self.openZ(fd, &posix_path, flags, mode); + } + + pub fn openZ(self: *Actions, fd: std.c.fd_t, path: [*:0]const u8, flags: u32, mode: std.c.mode_t) Error!void { + switch (errno(std.c.posix_spawn_file_actions_addopen(&self.actions, fd, path, @as(c_int, @bitCast(flags)), mode))) { + .SUCCESS => return, + .BADF => return error.InvalidFileDescriptor, + .NOMEM => return error.SystemResources, + .NAMETOOLONG => return error.NameTooLong, + .INVAL => unreachable, // the value of file actions is invalid + else => |err| return unexpectedErrno(err), + } + } + + pub fn close(self: *Actions, fd: std.c.fd_t) Error!void { + switch (errno(std.c.posix_spawn_file_actions_addclose(&self.actions, fd))) { + .SUCCESS => return, + .BADF => return error.InvalidFileDescriptor, + .NOMEM => return error.SystemResources, + .INVAL => unreachable, // the value of file actions is invalid + .NAMETOOLONG => unreachable, + else => |err| return unexpectedErrno(err), + } + } + + pub fn dup2(self: *Actions, fd: std.c.fd_t, newfd: std.c.fd_t) Error!void { + switch (errno(std.c.posix_spawn_file_actions_adddup2(&self.actions, fd, newfd))) { + .SUCCESS => return, + .BADF => return error.InvalidFileDescriptor, + .NOMEM => return error.SystemResources, + .INVAL => unreachable, // the value of file actions is invalid + .NAMETOOLONG => unreachable, + else => |err| return unexpectedErrno(err), + } + } + + pub fn inherit(self: *Actions, fd: std.c.fd_t) Error!void { + switch (errno(std.c.posix_spawn_file_actions_addinherit_np(&self.actions, fd))) { + .SUCCESS => return, + .BADF => return error.InvalidFileDescriptor, + .NOMEM => return error.SystemResources, + .INVAL => unreachable, // the value of file actions is invalid + .NAMETOOLONG => unreachable, + else => |err| return unexpectedErrno(err), + } + } + + pub fn chdir(self: *Actions, path: []const u8) Error!void { + const posix_path = try std.os.toPosixPath(path); + return self.chdirZ(&posix_path); + } + + pub fn chdirZ(self: *Actions, path: [*:0]const u8) Error!void { + switch (errno(std.c.posix_spawn_file_actions_addchdir_np(&self.actions, path))) { + .SUCCESS => return, + .NOMEM => return error.SystemResources, + .NAMETOOLONG => return error.NameTooLong, + .BADF => unreachable, + .INVAL => unreachable, // the value of file actions is invalid + else => |err| return unexpectedErrno(err), + } + } + + pub fn fchdir(self: *Actions, fd: std.c.fd_t) Error!void { + switch (errno(std.c.posix_spawn_file_actions_addfchdir_np(&self.actions, fd))) { + .SUCCESS => return, + .BADF => return error.InvalidFileDescriptor, + .NOMEM => return error.SystemResources, + .INVAL => unreachable, // the value of file actions is invalid + .NAMETOOLONG => unreachable, + else => |err| return unexpectedErrno(err), + } + } +}; + +pub fn spawn( + path: []const u8, + actions: ?Actions, + attr: ?Attr, + argv: [*:null]?[*:0]const u8, + envp: [*:null]?[*:0]const u8, +) Error!std.c.pid_t { + const posix_path = try std.os.toPosixPath(path); + return spawnZ(&posix_path, actions, attr, argv, envp); +} + +pub fn spawnZ( + path: [*:0]const u8, + actions: ?Actions, + attr: ?Attr, + argv: [*:null]?[*:0]const u8, + envp: [*:null]?[*:0]const u8, +) Error!std.c.pid_t { + var pid: std.c.pid_t = undefined; + switch (errno(std.c.posix_spawn( + &pid, + path, + if (actions) |a| &a.actions else null, + if (attr) |a| &a.attr else null, + argv, + envp, + ))) { + .SUCCESS => return pid, + .@"2BIG" => return error.TooBig, + .NOMEM => return error.SystemResources, + .BADF => return error.InvalidFileDescriptor, + .ACCES => return error.PermissionDenied, + .IO => return error.InputOutput, + .LOOP => return error.FileSystem, + .NAMETOOLONG => return error.NameTooLong, + .NOENT => return error.FileNotFound, + .NOEXEC => return error.InvalidExe, + .NOTDIR => return error.NotDir, + .TXTBSY => return error.FileBusy, + .BADARCH => return error.InvalidExe, + .BADEXEC => return error.InvalidExe, + .FAULT => unreachable, + .INVAL => unreachable, + else => |err| return unexpectedErrno(err), + } +} + +pub fn waitpid(pid: std.c.pid_t, flags: u32) Error!std.os.WaitPidResult { + var status: c_int = undefined; + while (true) { + const rc = waitpid(pid, &status, @as(c_int, @intCast(flags))); + switch (errno(rc)) { + .SUCCESS => return std.os.WaitPidResult{ + .pid = @as(std.c.pid_t, @intCast(rc)), + .status = @as(u32, @bitCast(status)), + }, + .INTR => continue, + .CHILD => return error.ChildExecFailed, + .INVAL => unreachable, // Invalid flags. + else => unreachable, + } + } +} + +const std = @import("std"); diff --git a/src/Module.zig b/src/Module.zig index 34b9510ea622..237a0dd6ada0 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -2396,7 +2396,7 @@ pub fn astGenFile(mod: *Module, file: *File) !void { .stat_inode = stat.inode, .stat_mtime = stat.mtime, }; - var iovecs = [_]std.os.iovec_const{ + var iovecs = [_]std.posix.iovec_const{ .{ .iov_base = @as([*]const u8, @ptrCast(&header)), .iov_len = @sizeOf(Zir.Header), @@ -2484,7 +2484,7 @@ fn loadZirCacheBody(gpa: Allocator, header: Zir.Header, cache_file: std.fs.File) else @as([*]u8, @ptrCast(zir.instructions.items(.data).ptr)); - var iovecs = [_]std.os.iovec{ + var iovecs = [_]std.posix.iovec{ .{ .iov_base = @as([*]u8, @ptrCast(zir.instructions.items(.tag).ptr)), .iov_len = header.instructions_len, diff --git a/src/Package/Fetch.zig b/src/Package/Fetch.zig index 93a6868603c6..a40bb539f78b 100644 --- a/src/Package/Fetch.zig +++ b/src/Package/Fetch.zig @@ -482,7 +482,7 @@ fn runResource( // Compute the package hash based on the remaining files in the temporary // directory. - if (builtin.os.tag == .linux and f.job_queue.work_around_btrfs_bug) { + if (native_os == .linux and f.job_queue.work_around_btrfs_bug) { // https://github.com/ziglang/zig/issues/17095 tmp_directory.handle.close(); tmp_directory.handle = cache_root.handle.makeOpenPath(tmp_dir_sub_path, .{ @@ -1153,11 +1153,7 @@ fn unpackTarball(f: *Fetch, out_dir: fs.Dir, reader: anytype) RunError!void { std.tar.pipeToFileSystem(out_dir, reader, .{ .diagnostics = &diagnostics, .strip_components = 1, - // TODO: we would like to set this to executable_bit_only, but two - // things need to happen before that: - // 1. the tar implementation needs to support it - // 2. the hashing algorithm here needs to support detecting the is_executable - // bit on Windows from the ACLs (see the isExecutable function). + // https://github.com/ziglang/zig/issues/17463 .mode_mode = .ignore, .exclude_empty_directories = true, }) catch |err| return f.fail(f.location_tok, try eb.printString( @@ -1542,6 +1538,8 @@ fn hashFileFallible(dir: fs.Dir, hashed_file: *HashedFile) HashedFile.Error!void .file => { var file = try dir.openFile(hashed_file.fs_path, .{}); defer file.close(); + // When implementing https://github.com/ziglang/zig/issues/17463 + // this will change to hard-coded `false`. hasher.update(&.{ 0, @intFromBool(try isExecutable(file)) }); while (true) { const bytes_read = try file.read(&buf); @@ -1568,15 +1566,17 @@ fn deleteFileFallible(dir: fs.Dir, deleted_file: *DeletedFile) DeletedFile.Error } fn isExecutable(file: fs.File) !bool { - if (builtin.os.tag == .windows) { - // TODO check the ACL on Windows. + // When implementing https://github.com/ziglang/zig/issues/17463 + // this function will not check the mode but instead check if the file is an ELF + // file or has a shebang line. + if (native_os == .windows) { // Until this is implemented, this could be a false negative on // Windows, which is why we do not yet set executable_bit_only above // when unpacking the tarball. return false; } else { const stat = try file.stat(); - return (stat.mode & std.os.S.IXUSR) != 0; + return (stat.mode & std.posix.S.IXUSR) != 0; } } @@ -1694,6 +1694,7 @@ const git = @import("Fetch/git.zig"); const Package = @import("../Package.zig"); const Manifest = Package.Manifest; const ErrorBundle = std.zig.ErrorBundle; +const native_os = builtin.os.tag; test { _ = Filter; diff --git a/src/codegen/spirv/Module.zig b/src/codegen/spirv/Module.zig index 61ef36162f0f..2f3a3fa083e5 100644 --- a/src/codegen/spirv/Module.zig +++ b/src/codegen/spirv/Module.zig @@ -478,7 +478,7 @@ pub fn flush(self: *Module, file: std.fs.File, target: std.Target) !void { } } else { // miscompiles with x86_64 backend - var iovc_buffers: [buffers.len]std.os.iovec_const = undefined; + var iovc_buffers: [buffers.len]std.posix.iovec_const = undefined; var file_size: u64 = 0; for (&iovc_buffers, 0..) |*iovc, i| { // Note, since spir-v supports both little and big endian we can ignore byte order here and diff --git a/src/crash_report.zig b/src/crash_report.zig index d05a950a469f..f33bef78e75c 100644 --- a/src/crash_report.zig +++ b/src/crash_report.zig @@ -2,9 +2,10 @@ const std = @import("std"); const builtin = @import("builtin"); const build_options = @import("build_options"); const debug = std.debug; -const os = std.os; const io = std.io; const print_zir = @import("print_zir.zig"); +const windows = std.os.windows; +const posix = std.posix; const native_os = builtin.os.tag; const Module = @import("Module.zig"); @@ -156,14 +157,14 @@ pub fn attachSegfaultHandler() void { if (!debug.have_segfault_handling_support) { @compileError("segfault handler not supported for this target"); } - if (builtin.os.tag == .windows) { - _ = os.windows.kernel32.AddVectoredExceptionHandler(0, handleSegfaultWindows); + if (native_os == .windows) { + _ = windows.kernel32.AddVectoredExceptionHandler(0, handleSegfaultWindows); return; } - var act = os.Sigaction{ + var act: posix.Sigaction = .{ .handler = .{ .sigaction = handleSegfaultPosix }, - .mask = os.empty_sigset, - .flags = (os.SA.SIGINFO | os.SA.RESTART | os.SA.RESETHAND), + .mask = posix.empty_sigset, + .flags = (posix.SA.SIGINFO | posix.SA.RESTART | posix.SA.RESETHAND), }; debug.updateSegfaultHandler(&act) catch { @@ -171,11 +172,11 @@ pub fn attachSegfaultHandler() void { }; } -fn handleSegfaultPosix(sig: i32, info: *const os.siginfo_t, ctx_ptr: ?*const anyopaque) callconv(.C) noreturn { +fn handleSegfaultPosix(sig: i32, info: *const posix.siginfo_t, ctx_ptr: ?*const anyopaque) callconv(.C) noreturn { // TODO: use alarm() here to prevent infinite loops PanicSwitch.preDispatch(); - const addr = switch (builtin.os.tag) { + const addr = switch (native_os) { .linux => @intFromPtr(info.fields.sigfault.addr), .freebsd, .macos => @intFromPtr(info.addr), .netbsd => @intFromPtr(info.info.reason.fault.addr), @@ -186,9 +187,9 @@ fn handleSegfaultPosix(sig: i32, info: *const os.siginfo_t, ctx_ptr: ?*const any var err_buffer: [128]u8 = undefined; const error_msg = switch (sig) { - os.SIG.SEGV => std.fmt.bufPrint(&err_buffer, "Segmentation fault at address 0x{x}", .{addr}) catch "Segmentation fault", - os.SIG.ILL => std.fmt.bufPrint(&err_buffer, "Illegal instruction at address 0x{x}", .{addr}) catch "Illegal instruction", - os.SIG.BUS => std.fmt.bufPrint(&err_buffer, "Bus error at address 0x{x}", .{addr}) catch "Bus error", + posix.SIG.SEGV => std.fmt.bufPrint(&err_buffer, "Segmentation fault at address 0x{x}", .{addr}) catch "Segmentation fault", + posix.SIG.ILL => std.fmt.bufPrint(&err_buffer, "Illegal instruction at address 0x{x}", .{addr}) catch "Illegal instruction", + posix.SIG.BUS => std.fmt.bufPrint(&err_buffer, "Bus error at address 0x{x}", .{addr}) catch "Bus error", else => std.fmt.bufPrint(&err_buffer, "Unknown error (signal {}) at address 0x{x}", .{ sig, addr }) catch "Unknown error", }; @@ -210,20 +211,20 @@ const WindowsSegfaultMessage = union(enum) { illegal_instruction: void, }; -fn handleSegfaultWindows(info: *os.windows.EXCEPTION_POINTERS) callconv(os.windows.WINAPI) c_long { +fn handleSegfaultWindows(info: *windows.EXCEPTION_POINTERS) callconv(windows.WINAPI) c_long { switch (info.ExceptionRecord.ExceptionCode) { - os.windows.EXCEPTION_DATATYPE_MISALIGNMENT => handleSegfaultWindowsExtra(info, .{ .literal = "Unaligned Memory Access" }), - os.windows.EXCEPTION_ACCESS_VIOLATION => handleSegfaultWindowsExtra(info, .segfault), - os.windows.EXCEPTION_ILLEGAL_INSTRUCTION => handleSegfaultWindowsExtra(info, .illegal_instruction), - os.windows.EXCEPTION_STACK_OVERFLOW => handleSegfaultWindowsExtra(info, .{ .literal = "Stack Overflow" }), - else => return os.windows.EXCEPTION_CONTINUE_SEARCH, + windows.EXCEPTION_DATATYPE_MISALIGNMENT => handleSegfaultWindowsExtra(info, .{ .literal = "Unaligned Memory Access" }), + windows.EXCEPTION_ACCESS_VIOLATION => handleSegfaultWindowsExtra(info, .segfault), + windows.EXCEPTION_ILLEGAL_INSTRUCTION => handleSegfaultWindowsExtra(info, .illegal_instruction), + windows.EXCEPTION_STACK_OVERFLOW => handleSegfaultWindowsExtra(info, .{ .literal = "Stack Overflow" }), + else => return windows.EXCEPTION_CONTINUE_SEARCH, } } -fn handleSegfaultWindowsExtra(info: *os.windows.EXCEPTION_POINTERS, comptime msg: WindowsSegfaultMessage) noreturn { +fn handleSegfaultWindowsExtra(info: *windows.EXCEPTION_POINTERS, comptime msg: WindowsSegfaultMessage) noreturn { PanicSwitch.preDispatch(); - const stack_ctx = if (@hasDecl(os.windows, "CONTEXT")) + const stack_ctx = if (@hasDecl(windows, "CONTEXT")) StackContext{ .exception = info.ContextRecord } else ctx: { const addr = @intFromPtr(info.ExceptionRecord.ExceptionAddress); @@ -488,7 +489,7 @@ const PanicSwitch = struct { } noinline fn abort() noreturn { - os.abort(); + std.process.abort(); } inline fn goTo(comptime func: anytype, args: anytype) noreturn { diff --git a/src/link.zig b/src/link.zig index 631768a4de19..6034d8df49ee 100644 --- a/src/link.zig +++ b/src/link.zig @@ -254,7 +254,7 @@ pub const File = struct { try emit.directory.handle.copyFile(emit.sub_path, emit.directory.handle, tmp_sub_path, .{}); try emit.directory.handle.rename(tmp_sub_path, emit.sub_path); switch (builtin.os.tag) { - .linux => std.os.ptrace(std.os.linux.PTRACE.ATTACH, pid, 0, 0) catch |err| { + .linux => std.posix.ptrace(std.os.linux.PTRACE.ATTACH, pid, 0, 0) catch |err| { log.warn("ptrace failure: {s}", .{@errorName(err)}); }, .macos => base.cast(MachO).?.ptraceAttach(pid) catch |err| { @@ -305,7 +305,7 @@ pub const File = struct { if (base.child_pid) |pid| { switch (builtin.os.tag) { - .linux => std.os.ptrace(std.os.linux.PTRACE.DETACH, pid, 0, 0) catch |err| { + .linux => std.posix.ptrace(std.os.linux.PTRACE.DETACH, pid, 0, 0) catch |err| { log.warn("ptrace failure: {s}", .{@errorName(err)}); }, else => return error.HotSwapUnavailableOnHostOperatingSystem, diff --git a/src/link/C.zig b/src/link/C.zig index 62051be03ca2..59ebed800b77 100644 --- a/src/link/C.zig +++ b/src/link/C.zig @@ -518,7 +518,7 @@ const Flush = struct { asm_buf: std.ArrayListUnmanaged(u8) = .{}, /// We collect a list of buffers to write, and write them all at once with pwritev 😎 - all_buffers: std.ArrayListUnmanaged(std.os.iovec_const) = .{}, + all_buffers: std.ArrayListUnmanaged(std.posix.iovec_const) = .{}, /// Keeps track of the total bytes of `all_buffers`. file_size: u64 = 0, @@ -752,7 +752,7 @@ pub fn flushEmitH(module: *Module) !void { // We collect a list of buffers to write, and write them all at once with pwritev 😎 const num_buffers = emit_h.decl_table.count() + 1; - var all_buffers = try std.ArrayList(std.os.iovec_const).initCapacity(module.gpa, num_buffers); + var all_buffers = try std.ArrayList(std.posix.iovec_const).initCapacity(module.gpa, num_buffers); defer all_buffers.deinit(); var file_size: u64 = zig_h.len; diff --git a/src/link/Dwarf.zig b/src/link/Dwarf.zig index 9b6ff5d2437f..26eb536c0814 100644 --- a/src/link/Dwarf.zig +++ b/src/link/Dwarf.zig @@ -2118,7 +2118,7 @@ fn pwriteDbgLineNops( const page_of_nops = [1]u8{DW.LNS.negate_stmt} ** 4096; const three_byte_nop = [3]u8{ DW.LNS.advance_pc, 0b1000_0000, 0 }; - var vecs: [512]std.os.iovec_const = undefined; + var vecs: [512]std.posix.iovec_const = undefined; var vec_index: usize = 0; { var padding_left = prev_padding_size; @@ -2235,7 +2235,7 @@ fn pwriteDbgInfoNops( defer tracy.end(); const page_of_nops = [1]u8{@intFromEnum(AbbrevCode.padding)} ** 4096; - var vecs: [32]std.os.iovec_const = undefined; + var vecs: [32]std.posix.iovec_const = undefined; var vec_index: usize = 0; { var padding_left = prev_padding_size; @@ -2807,10 +2807,10 @@ fn genIncludeDirsAndFileNames(self: *Dwarf, arena: Allocator) !struct { const full_path = try dif.mod.root.joinString(arena, dif.sub_file_path); const dir_path = std.fs.path.dirname(full_path) orelse "."; const sub_file_path = std.fs.path.basename(full_path); - // TODO re-investigate if realpath is needed here + // https://github.com/ziglang/zig/issues/19353 var buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined; const resolved = if (!std.fs.path.isAbsolute(dir_path)) - std.os.realpath(dir_path, &buffer) catch dir_path + std.posix.realpath(dir_path, &buffer) catch dir_path else dir_path; diff --git a/src/link/Elf.zig b/src/link/Elf.zig index e6182b43332a..f0d3f89a7fc0 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -422,7 +422,7 @@ pub fn createEmpty( const index: File.Index = @intCast(try self.files.addOne(gpa)); self.files.set(index, .{ .zig_object = .{ .index = index, - .path = try std.fmt.allocPrint(arena, "{s}.o", .{std.fs.path.stem( + .path = try std.fmt.allocPrint(arena, "{s}.o", .{fs.path.stem( zcu.main_mod.root_src_path, )}), } }); @@ -1673,7 +1673,7 @@ pub const ParseError = error{ NotSupported, InvalidCharacter, UnknownFileType, -} || LdScript.Error || std.os.AccessError || std.os.SeekError || std.fs.File.OpenError || std.fs.File.ReadError; +} || LdScript.Error || fs.Dir.AccessError || fs.File.SeekError || fs.File.OpenError || fs.File.ReadError; pub fn parsePositional(self: *Elf, path: []const u8, must_link: bool) ParseError!void { const tracy = trace(@src()); @@ -1703,7 +1703,7 @@ fn parseObject(self: *Elf, path: []const u8) ParseError!void { defer tracy.end(); const gpa = self.base.comp.gpa; - const handle = try std.fs.cwd().openFile(path, .{}); + const handle = try fs.cwd().openFile(path, .{}); const fh = try self.addFileHandle(handle); const index = @as(File.Index, @intCast(try self.files.addOne(gpa))); @@ -1723,7 +1723,7 @@ fn parseArchive(self: *Elf, path: []const u8, must_link: bool) ParseError!void { defer tracy.end(); const gpa = self.base.comp.gpa; - const handle = try std.fs.cwd().openFile(path, .{}); + const handle = try fs.cwd().openFile(path, .{}); const fh = try self.addFileHandle(handle); var archive = Archive{}; @@ -1749,7 +1749,7 @@ fn parseSharedObject(self: *Elf, lib: SystemLib) ParseError!void { defer tracy.end(); const gpa = self.base.comp.gpa; - const handle = try std.fs.cwd().openFile(lib.path, .{}); + const handle = try fs.cwd().openFile(lib.path, .{}); defer handle.close(); const index = @as(File.Index, @intCast(try self.files.addOne(gpa))); @@ -1770,7 +1770,7 @@ fn parseLdScript(self: *Elf, lib: SystemLib) ParseError!void { defer tracy.end(); const gpa = self.base.comp.gpa; - const in_file = try std.fs.cwd().openFile(lib.path, .{}); + const in_file = try fs.cwd().openFile(lib.path, .{}); defer in_file.close(); const data = try in_file.readToEndAlloc(gpa, std.math.maxInt(u32)); defer gpa.free(data); @@ -5468,7 +5468,7 @@ pub fn file(self: *Elf, index: File.Index) ?File { }; } -pub fn addFileHandle(self: *Elf, handle: std.fs.File) !File.HandleIndex { +pub fn addFileHandle(self: *Elf, handle: fs.File) !File.HandleIndex { const gpa = self.base.comp.gpa; const index: File.HandleIndex = @intCast(self.file_handles.items.len); const fh = try self.file_handles.addOne(gpa); @@ -6045,7 +6045,7 @@ fn fmtDumpState( } /// Caller owns the memory. -pub fn preadAllAlloc(allocator: Allocator, handle: std.fs.File, offset: u64, size: u64) ![]u8 { +pub fn preadAllAlloc(allocator: Allocator, handle: fs.File, offset: u64, size: u64) ![]u8 { const buffer = try allocator.alloc(u8, math.cast(usize, size) orelse return error.Overflow); errdefer allocator.free(buffer); const amt = try handle.preadAll(buffer, offset); diff --git a/src/link/Elf/ZigObject.zig b/src/link/Elf/ZigObject.zig index 28b8acb51fb7..4c86bb3a894d 100644 --- a/src/link/Elf/ZigObject.zig +++ b/src/link/Elf/ZigObject.zig @@ -965,16 +965,16 @@ fn updateDeclCode( if (elf_file.base.child_pid) |pid| { switch (builtin.os.tag) { .linux => { - var code_vec: [1]std.os.iovec_const = .{.{ + var code_vec: [1]std.posix.iovec_const = .{.{ .iov_base = code.ptr, .iov_len = code.len, }}; - var remote_vec: [1]std.os.iovec_const = .{.{ + var remote_vec: [1]std.posix.iovec_const = .{.{ .iov_base = @as([*]u8, @ptrFromInt(@as(usize, @intCast(sym.address(.{}, elf_file))))), .iov_len = code.len, }}; const rc = std.os.linux.process_vm_writev(pid, &code_vec, &remote_vec, 0); - switch (std.os.errno(rc)) { + switch (std.os.linux.E.init(rc)) { .SUCCESS => assert(rc == code.len), else => |errno| log.warn("process_vm_writev failure: {s}", .{@tagName(errno)}), } diff --git a/src/link/Elf/synthetic_sections.zig b/src/link/Elf/synthetic_sections.zig index b55e5de4328a..0e7cb9054551 100644 --- a/src/link/Elf/synthetic_sections.zig +++ b/src/link/Elf/synthetic_sections.zig @@ -317,16 +317,16 @@ pub const ZigGotSection = struct { if (elf_file.base.child_pid) |pid| { switch (builtin.os.tag) { .linux => { - var local_vec: [1]std.os.iovec_const = .{.{ + var local_vec: [1]std.posix.iovec_const = .{.{ .iov_base = &buf, .iov_len = buf.len, }}; - var remote_vec: [1]std.os.iovec_const = .{.{ + var remote_vec: [1]std.posix.iovec_const = .{.{ .iov_base = @as([*]u8, @ptrFromInt(@as(usize, @intCast(vaddr)))), .iov_len = buf.len, }}; const rc = std.os.linux.process_vm_writev(pid, &local_vec, &remote_vec, 0); - switch (std.os.errno(rc)) { + switch (std.os.linux.E.init(rc)) { .SUCCESS => assert(rc == buf.len), else => |errno| log.warn("process_vm_writev failure: {s}", .{@tagName(errno)}), } diff --git a/src/link/MachO.zig b/src/link/MachO.zig index cc488b75418b..6731f88e43ca 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -258,7 +258,7 @@ pub fn createEmpty( const index: File.Index = @intCast(try self.files.addOne(gpa)); self.files.set(index, .{ .zig_object = .{ .index = index, - .path = try std.fmt.allocPrint(arena, "{s}.o", .{std.fs.path.stem( + .path = try std.fmt.allocPrint(arena, "{s}.o", .{fs.path.stem( zcu.main_mod.root_src_path, )}), } }); @@ -843,7 +843,7 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void { } for (self.frameworks) |framework| { - const name = std.fs.path.stem(framework.path); + const name = fs.path.stem(framework.path); const arg = if (framework.needed) try std.fmt.allocPrint(arena, "-needed_framework {s}", .{name}) else if (framework.weak) @@ -917,7 +917,7 @@ pub const ParseError = error{ NotSupported, Unhandled, UnknownFileType, -} || std.os.SeekError || std.fs.File.OpenError || std.fs.File.ReadError || tapi.TapiError; +} || fs.File.SeekError || fs.File.OpenError || fs.File.ReadError || tapi.TapiError; pub fn parsePositional(self: *MachO, path: []const u8, must_link: bool) ParseError!void { const tracy = trace(@src()); @@ -956,7 +956,7 @@ fn parseObject(self: *MachO, path: []const u8) ParseError!void { defer tracy.end(); const gpa = self.base.comp.gpa; - const file = try std.fs.cwd().openFile(path, .{}); + const file = try fs.cwd().openFile(path, .{}); const handle = try self.addFileHandle(file); const mtime: u64 = mtime: { const stat = file.stat() catch break :mtime 0; @@ -992,7 +992,7 @@ fn parseArchive(self: *MachO, lib: SystemLib, must_link: bool, fat_arch: ?fat.Ar const gpa = self.base.comp.gpa; - const file = try std.fs.cwd().openFile(lib.path, .{}); + const file = try fs.cwd().openFile(lib.path, .{}); const handle = try self.addFileHandle(file); var archive = Archive{}; @@ -1029,7 +1029,7 @@ fn parseDylib(self: *MachO, lib: SystemLib, explicit: bool, fat_arch: ?fat.Arch) const gpa = self.base.comp.gpa; - const file = try std.fs.cwd().openFile(lib.path, .{}); + const file = try fs.cwd().openFile(lib.path, .{}); defer file.close(); const index = @as(File.Index, @intCast(try self.files.addOne(gpa))); @@ -1054,7 +1054,7 @@ fn parseTbd(self: *MachO, lib: SystemLib, explicit: bool) ParseError!File.Index defer tracy.end(); const gpa = self.base.comp.gpa; - const file = try std.fs.cwd().openFile(lib.path, .{}); + const file = try fs.cwd().openFile(lib.path, .{}); defer file.close(); var lib_stub = LibStub.loadFromFile(gpa, file) catch return error.MalformedTbd; // TODO actually handle different errors @@ -1080,10 +1080,10 @@ fn parseTbd(self: *MachO, lib: SystemLib, explicit: bool) ParseError!File.Index /// image unless overriden by -no_implicit_dylibs. fn isHoisted(self: *MachO, install_name: []const u8) bool { if (self.no_implicit_dylibs) return true; - if (std.fs.path.dirname(install_name)) |dirname| { + if (fs.path.dirname(install_name)) |dirname| { if (mem.startsWith(u8, dirname, "/usr/lib")) return true; if (eatPrefix(dirname, "/System/Library/Frameworks/")) |path| { - const basename = std.fs.path.basename(install_name); + const basename = fs.path.basename(install_name); if (mem.indexOfScalar(u8, path, '.')) |index| { if (mem.eql(u8, basename, path[0..index])) return true; } @@ -1105,7 +1105,7 @@ fn accessLibPath( test_path.clearRetainingCapacity(); try test_path.writer().print("{s}" ++ sep ++ "lib{s}{s}", .{ search_dir, name, ext }); try checked_paths.append(try arena.dupe(u8, test_path.items)); - std.fs.cwd().access(test_path.items, .{}) catch |err| switch (err) { + fs.cwd().access(test_path.items, .{}) catch |err| switch (err) { error.FileNotFound => continue, else => |e| return e, }; @@ -1133,7 +1133,7 @@ fn accessFrameworkPath( ext, }); try checked_paths.append(try arena.dupe(u8, test_path.items)); - std.fs.cwd().access(test_path.items, .{}) catch |err| switch (err) { + fs.cwd().access(test_path.items, .{}) catch |err| switch (err) { error.FileNotFound => continue, else => |e| return e, }; @@ -1181,7 +1181,7 @@ fn parseDependentDylibs(self: *MachO) !void { const full_path = full_path: { { - const stem = std.fs.path.stem(id.name); + const stem = fs.path.stem(id.name); // Framework for (framework_dirs) |dir| { @@ -1197,18 +1197,18 @@ fn parseDependentDylibs(self: *MachO) !void { } } - if (std.fs.path.isAbsolute(id.name)) { - const existing_ext = std.fs.path.extension(id.name); + if (fs.path.isAbsolute(id.name)) { + const existing_ext = fs.path.extension(id.name); const path = if (existing_ext.len > 0) id.name[0 .. id.name.len - existing_ext.len] else id.name; for (&[_][]const u8{ ".tbd", ".dylib", "" }) |ext| { test_path.clearRetainingCapacity(); if (self.base.comp.sysroot) |root| { - try test_path.writer().print("{s}" ++ std.fs.path.sep_str ++ "{s}{s}", .{ root, path, ext }); + try test_path.writer().print("{s}" ++ fs.path.sep_str ++ "{s}{s}", .{ root, path, ext }); } else { try test_path.writer().print("{s}{s}", .{ path, ext }); } try checked_paths.append(try arena.dupe(u8, test_path.items)); - std.fs.cwd().access(test_path.items, .{}) catch |err| switch (err) { + fs.cwd().access(test_path.items, .{}) catch |err| switch (err) { error.FileNotFound => continue, else => |e| return e, }; @@ -1220,10 +1220,10 @@ fn parseDependentDylibs(self: *MachO) !void { const dylib = self.getFile(dylib_index).?.dylib; for (self.getFile(dylib.umbrella).?.dylib.rpaths.keys()) |rpath| { const prefix = eatPrefix(rpath, "@loader_path/") orelse rpath; - const rel_path = try std.fs.path.join(arena, &.{ prefix, path }); + const rel_path = try fs.path.join(arena, &.{ prefix, path }); try checked_paths.append(rel_path); - var buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined; - const full_path = std.fs.realpath(rel_path, &buffer) catch continue; + var buffer: [fs.MAX_PATH_BYTES]u8 = undefined; + const full_path = fs.realpath(rel_path, &buffer) catch continue; break :full_path try arena.dupe(u8, full_path); } } else if (eatPrefix(id.name, "@loader_path/")) |_| { @@ -1235,8 +1235,8 @@ fn parseDependentDylibs(self: *MachO) !void { } try checked_paths.append(try arena.dupe(u8, id.name)); - var buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined; - if (std.fs.realpath(id.name, &buffer)) |full_path| { + var buffer: [fs.MAX_PATH_BYTES]u8 = undefined; + if (fs.realpath(id.name, &buffer)) |full_path| { break :full_path try arena.dupe(u8, full_path); } else |_| { try self.reportMissingDependencyError( @@ -3651,7 +3651,7 @@ pub fn getTarget(self: MachO) std.Target { /// into a new inode, remove the original file, and rename the copy to match /// the original file. This is super messy, but there doesn't seem any other /// way to please the XNU. -pub fn invalidateKernelCache(dir: std.fs.Dir, sub_path: []const u8) !void { +pub fn invalidateKernelCache(dir: fs.Dir, sub_path: []const u8) !void { if (comptime builtin.target.isDarwin() and builtin.target.cpu.arch == .aarch64) { try dir.copyFile(sub_path, dir, sub_path, .{}); } @@ -3839,7 +3839,7 @@ pub fn getInternalObject(self: *MachO) ?*InternalObject { return self.getFile(index).?.internal; } -pub fn addFileHandle(self: *MachO, file: std.fs.File) !File.HandleIndex { +pub fn addFileHandle(self: *MachO, file: fs.File) !File.HandleIndex { const gpa = self.base.comp.gpa; const index: File.HandleIndex = @intCast(self.file_handles.items.len); const fh = try self.file_handles.addOne(gpa); @@ -4530,7 +4530,7 @@ fn inferSdkVersion(comp: *Compilation, sdk_layout: SdkLayout) ?std.SemanticVersi const sdk_dir = switch (sdk_layout) { .sdk => comp.sysroot.?, - .vendored => std.fs.path.join(arena, &.{ comp.zig_lib_directory.path.?, "libc", "darwin" }) catch return null, + .vendored => fs.path.join(arena, &.{ comp.zig_lib_directory.path.?, "libc", "darwin" }) catch return null, }; if (readSdkVersionFromSettings(arena, sdk_dir)) |ver| { return parseSdkVersion(ver); @@ -4541,7 +4541,7 @@ fn inferSdkVersion(comp: *Compilation, sdk_layout: SdkLayout) ?std.SemanticVersi } // infer from pathname - const stem = std.fs.path.stem(sdk_dir); + const stem = fs.path.stem(sdk_dir); const start = for (stem, 0..) |c, i| { if (std.ascii.isDigit(c)) break i; } else stem.len; @@ -4556,8 +4556,8 @@ fn inferSdkVersion(comp: *Compilation, sdk_layout: SdkLayout) ?std.SemanticVersi // Use property `MinimalDisplayName` to determine version. // The file/property is also available with vendored libc. fn readSdkVersionFromSettings(arena: Allocator, dir: []const u8) ![]const u8 { - const sdk_path = try std.fs.path.join(arena, &.{ dir, "SDKSettings.json" }); - const contents = try std.fs.cwd().readFileAlloc(arena, sdk_path, std.math.maxInt(u16)); + const sdk_path = try fs.path.join(arena, &.{ dir, "SDKSettings.json" }); + const contents = try fs.cwd().readFileAlloc(arena, sdk_path, std.math.maxInt(u16)); const parsed = try std.json.parseFromSlice(std.json.Value, arena, contents, .{}); if (parsed.value.object.get("MinimalDisplayName")) |ver| return ver.string; return error.SdkVersionFailure; diff --git a/src/link/Plan9.zig b/src/link/Plan9.zig index be68465af76b..e14fc18a55d0 100644 --- a/src/link/Plan9.zig +++ b/src/link/Plan9.zig @@ -368,7 +368,7 @@ fn putFn(self: *Plan9, decl_index: InternPool.DeclIndex, out: FnDeclOutput) !voi // getting the full file path var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; const full_path = try std.fs.path.join(arena, &.{ - file.mod.root.root_dir.path orelse try std.os.getcwd(&buf), + file.mod.root.root_dir.path orelse try std.posix.getcwd(&buf), file.mod.root.sub_path, file.sub_file_path, }); @@ -722,7 +722,7 @@ pub fn flushModule(self: *Plan9, arena: Allocator, prog_node: *std.Progress.Node defer gpa.free(got_table); // + 4 for header, got, symbols, linecountinfo - var iovecs = try gpa.alloc(std.os.iovec_const, self.atomCount() + 4 - self.externCount()); + var iovecs = try gpa.alloc(std.posix.iovec_const, self.atomCount() + 4 - self.externCount()); defer gpa.free(iovecs); const file = self.base.file.?; diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 128cec5b6e20..c8fea56c1630 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -3046,7 +3046,7 @@ fn writeToFile( } // finally, write the entire binary into the file. - var iovec = [_]std.os.iovec_const{.{ + var iovec = [_]std.posix.iovec_const{.{ .iov_base = binary_bytes.items.ptr, .iov_len = binary_bytes.items.len, }}; @@ -3709,7 +3709,7 @@ fn linkWithLLD(wasm: *Wasm, arena: Allocator, prog_node: *std.Progress.Node) !vo // report a nice error here with the file path if it fails instead of // just returning the error code. // chmod does not interact with umask, so we use a conservative -rwxr--r-- here. - std.os.fchmodat(fs.cwd().fd, full_out_path, 0o744, 0) catch |err| switch (err) { + std.posix.fchmodat(fs.cwd().fd, full_out_path, 0o744, 0) catch |err| switch (err) { error.OperationNotSupported => unreachable, // Not a symlink. else => |e| return e, }; diff --git a/src/link/Wasm/Archive.zig b/src/link/Wasm/Archive.zig index 028ef95726ee..a9acf2ca0364 100644 --- a/src/link/Wasm/Archive.zig +++ b/src/link/Wasm/Archive.zig @@ -193,7 +193,7 @@ pub fn parseObject(archive: Archive, wasm_file: *const Wasm, file_offset: u32) ! const object_name = try archive.parseName(header); const name = name: { var buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined; - const path = try std.os.realpath(archive.name, &buffer); + const path = try std.posix.realpath(archive.name, &buffer); break :name try std.fmt.allocPrint(gpa, "{s}({s})", .{ path, object_name }); }; defer gpa.free(name); diff --git a/src/main.zig b/src/main.zig index db76f7605ca6..afdfb49c48ed 100644 --- a/src/main.zig +++ b/src/main.zig @@ -48,7 +48,7 @@ pub const panic = crash_report.panic; var wasi_preopens: fs.wasi.Preopens = undefined; pub fn wasi_cwd() std.os.wasi.fd_t { // Expect the first preopen to be current working directory. - const cwd_fd: std.os.fd_t = 3; + const cwd_fd: std.posix.fd_t = 3; assert(mem.eql(u8, wasi_preopens.names[cwd_fd], ".")); return cwd_fd; } @@ -222,7 +222,7 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { fatal("expected command argument", .{}); } - if (process.can_execv and std.os.getenvZ("ZIG_IS_DETECTING_LIBC_PATHS") != null) { + if (process.can_execv and std.posix.getenvZ("ZIG_IS_DETECTING_LIBC_PATHS") != null) { // In this case we have accidentally invoked ourselves as "the system C compiler" // to figure out where libc is installed. This is essentially infinite recursion // via child process execution due to the CC environment variable pointing to Zig. @@ -4434,16 +4434,16 @@ fn runOrTestHotSwap( switch (builtin.target.os.tag) { .macos, .ios, .tvos, .watchos => { - const PosixSpawn = std.os.darwin.PosixSpawn; + const PosixSpawn = @import("DarwinPosixSpawn.zig"); var attr = try PosixSpawn.Attr.init(); defer attr.deinit(); // ASLR is probably a good default for better debugging experience/programming // with hot-code updates in mind. However, we can also make it work with ASLR on. - const flags: u16 = std.os.darwin.POSIX_SPAWN.SETSIGDEF | - std.os.darwin.POSIX_SPAWN.SETSIGMASK | - std.os.darwin.POSIX_SPAWN.DISABLE_ASLR; + const flags: u16 = std.c.POSIX_SPAWN.SETSIGDEF | + std.c.POSIX_SPAWN.SETSIGMASK | + std.c.POSIX_SPAWN.DISABLE_ASLR; try attr.set(flags); var arena_allocator = std.heap.ArenaAllocator.init(gpa); @@ -5985,8 +5985,8 @@ fn parseCodeModel(arg: []const u8) std.builtin.CodeModel { /// garbage collector to run concurrently to zig processes, and to allow multiple /// zig processes to run concurrently with each other, without clobbering each other. fn gimmeMoreOfThoseSweetSweetFileDescriptors() void { - if (!@hasDecl(std.os.system, "rlimit")) return; - const posix = std.os; + const posix = std.posix; + if (!@hasDecl(posix, "rlimit")) return; var lim = posix.getrlimit(.NOFILE) catch return; // Oh well; we tried. if (comptime builtin.target.isDarwin()) {