From ca8d99fb39657c22e56a817f42f6f25ef2d29285 Mon Sep 17 00:00:00 2001 From: Jan Philipp Hafer Date: Sat, 25 Feb 2023 12:48:32 +0100 Subject: [PATCH] std.os: spring-cleanup and specify organization - Alphabetically sort things, where reasonable. - Document, that **only** non-portable posix things belong into posix.zig * If there is a portable abstraction, do not offer one in posix.zig * Reason: Prevent useless abstractions and needless strong coupling. - Move posix-only functions into posix.zig, which have either incompatible or more extensive execution semantics than their counterparts and can be grouped into * File permission system * Process management * Memory management * IPC * Signaling Work on #6600. --- lib/std/Thread.zig | 6 +- lib/std/child_process.zig | 45 +- lib/std/crypto/tlcsprng.zig | 6 +- lib/std/debug.zig | 20 +- lib/std/dynamic_library.zig | 14 +- lib/std/fs.zig | 8 +- lib/std/fs/file.zig | 12 +- lib/std/heap/PageAllocator.zig | 6 +- lib/std/os.zig | 859 ++---------------------- lib/std/os/linux/io_uring.zig | 12 +- lib/std/os/linux/tls.zig | 2 +- lib/std/os/posix.zig | 830 +++++++++++++++++++++++ lib/std/os/posix_spawn.zig | 2 +- lib/std/os/test.zig | 90 +-- lib/std/process.zig | 4 +- lib/std/start.zig | 2 +- lib/std/zig/system/NativeTargetInfo.zig | 4 +- src/link/Wasm.zig | 2 +- src/main.zig | 11 +- test/standalone/sigpipe/breakpipe.zig | 2 +- test/standalone/sigpipe/build.zig | 2 +- 21 files changed, 997 insertions(+), 942 deletions(-) create mode 100644 lib/std/os/posix.zig diff --git a/lib/std/Thread.zig b/lib/std/Thread.zig index 27f7fa5030e9..fd6a68829752 100644 --- a/lib/std/Thread.zig +++ b/lib/std/Thread.zig @@ -946,7 +946,7 @@ 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 = os.posix.mmap( null, map_bytes, os.PROT.NONE, @@ -962,7 +962,7 @@ const LinuxThreadImpl = struct { else => |e| return e, }; assert(mapped.len >= map_bytes); - errdefer os.munmap(mapped); + errdefer os.posix.munmap(mapped); // map everything but the guard page as read/write os.mprotect( @@ -1035,7 +1035,7 @@ const LinuxThreadImpl = struct { } fn join(self: Impl) void { - defer os.munmap(self.thread.mapped); + defer os.posix.munmap(self.thread.mapped); var spin: u8 = 10; while (true) { diff --git a/lib/std/child_process.zig b/lib/std/child_process.zig index dba92ab9986b..63bbd5e5ab7f 100644 --- a/lib/std/child_process.zig +++ b/lib/std/child_process.zig @@ -5,6 +5,7 @@ const unicode = std.unicode; const io = std.io; const fs = std.fs; const os = std.os; +const posix = os.posix; const process = std.process; const File = std.fs.File; const windows = os.windows; @@ -70,7 +71,7 @@ pub const ChildProcess = struct { /// Darwin-only. Start child process in suspended state as if SIGSTOP was sent. start_suspended: bool = false, - pub const Arg0Expand = os.Arg0Expand; + pub const Arg0Expand = os.posix.Arg0Expand; pub const SpawnError = error{ OutOfMemory, @@ -86,8 +87,8 @@ 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.posix.ExecveError || + os.posix.SetIdError || os.ChangeCurDirError || windows.CreateProcessError || windows.WaitForSingleObjectError || @@ -184,7 +185,7 @@ pub const ChildProcess = struct { self.cleanupStreams(); return term; } - try os.kill(self.id, os.SIG.TERM); + try posix.kill(self.pid, os.SIG.TERM); try self.waitUnwrapped(); return self.term.?; } @@ -314,7 +315,7 @@ pub const ChildProcess = struct { return term; } - try self.waitUnwrapped(); + try self.waitUnwrappedPosix(); return self.term.?; } @@ -336,11 +337,11 @@ pub const ChildProcess = struct { return result; } - fn waitUnwrapped(self: *ChildProcess) !void { - const res: os.WaitPidResult = if (comptime builtin.target.isDarwin()) + fn waitUnwrappedPosix(self: *ChildProcess) !void { + const res: os.posix.WaitPidResult = if (comptime builtin.target.isDarwin()) try os.posix_spawn.waitpid(self.id, 0) else - os.waitpid(self.id, 0); + os.posix.waitpid(self.id, 0); const status = res.status; self.cleanupStreams(); self.handleWaitResult(status); @@ -418,13 +419,13 @@ pub const ChildProcess = struct { fn spawnMacos(self: *ChildProcess) SpawnError!void { const pipe_flags = if (io.is_async) os.O.NONBLOCK else 0; - const stdin_pipe = if (self.stdin_behavior == StdIo.Pipe) try os.pipe2(pipe_flags) else undefined; + const stdin_pipe = if (self.stdin_behavior == StdIo.Pipe) try os.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 os.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 os.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); @@ -533,17 +534,17 @@ pub const ChildProcess = struct { fn spawnPosix(self: *ChildProcess) SpawnError!void { const pipe_flags = if (io.is_async) os.O.NONBLOCK else 0; - const stdin_pipe = if (self.stdin_behavior == StdIo.Pipe) try os.pipe2(pipe_flags) else undefined; + const stdin_pipe = if (self.stdin_behavior == StdIo.Pipe) try os.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 os.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 os.posix.pipe2(pipe_flags) else undefined; errdefer if (self.stderr_behavior == StdIo.Pipe) { destroyPipe(stderr_pipe); }; @@ -608,12 +609,12 @@ pub const ChildProcess = struct { // end with eventfd break :blk [2]os.fd_t{ fd, fd }; } else { - break :blk try os.pipe2(os.O.CLOEXEC); + break :blk try os.posix.pipe2(os.O.CLOEXEC); } }; errdefer destroyPipe(err_pipe); - const pid_result = try os.fork(); + const pid_result = try os.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); @@ -640,16 +641,16 @@ pub const ChildProcess = struct { } if (self.gid) |gid| { - os.setregid(gid, gid) catch |err| forkChildErrReport(err_pipe[1], err); + os.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); + os.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 => os.posix.execvpeZ_expandArg0(.expand, argv_buf.ptr[0].?, argv_buf.ptr, envp), + .no_expand => os.posix.execvpeZ_expandArg0(.no_expand, argv_buf.ptr[0].?, argv_buf.ptr, envp), }; forkChildErrReport(err_pipe[1], err); } @@ -955,10 +956,10 @@ pub const ChildProcess = struct { 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), + .Pipe => try os.posix.dup2(pipe_fd, std_fileno), .Close => os.close(std_fileno), .Inherit => {}, - .Ignore => try os.dup2(dev_null_fd, std_fileno), + .Ignore => try os.posix.dup2(dev_null_fd, std_fileno), } } }; diff --git a/lib/std/crypto/tlcsprng.zig b/lib/std/crypto/tlcsprng.zig index 5a09e20f0716..109f04fb17e0 100644 --- a/lib/std/crypto/tlcsprng.zig +++ b/lib/std/crypto/tlcsprng.zig @@ -75,7 +75,7 @@ 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 = os.posix.mmap( null, @sizeOf(Context), os.PROT.READ | os.PROT.WRITE, @@ -111,11 +111,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 (os.posix.madvise(wipe_mem.ptr, 0, 0xffffffff)) |_| { break :wof; } else |_| {} - if (os.madvise(wipe_mem.ptr, wipe_mem.len, os.MADV.WIPEONFORK)) |_| { + if (os.posix.madvise(wipe_mem.ptr, wipe_mem.len, os.MADV.WIPEONFORK)) |_| { return initAndFill(buffer); } else |_| {} } diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 97acf81af60a..2732ee2b39cf 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -482,9 +482,9 @@ pub const StackIterator = struct { if (native_os != .windows) { if (native_os != .wasi) { - os.msync(aligned_memory, os.MSF.ASYNC) catch |err| { + os.posix.msync(aligned_memory, os.MSF.ASYNC) catch |err| { switch (err) { - os.MSyncError.UnmappedMemory => { + os.posix.MSyncError.UnmappedMemory => { return false; }, else => unreachable, @@ -1222,7 +1222,7 @@ 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 os.posix.mmap( null, file_len, os.PROT.READ, @@ -1230,7 +1230,7 @@ fn mapWholeFile(file: File) ![]align(mem.page_size) const u8 { file.handle, 0, ); - errdefer os.munmap(mapped_mem); + errdefer os.posix.munmap(mapped_mem); return mapped_mem; } @@ -1491,7 +1491,7 @@ pub const ModuleDebugInfo = switch (native_os) { } self.ofiles.deinit(); allocator.free(self.symbols); - os.munmap(self.mapped_memory); + os.posix.munmap(self.mapped_memory); } fn loadOFile(self: *@This(), allocator: mem.Allocator, o_file_path: []const u8) !OFileInfo { @@ -1798,7 +1798,7 @@ pub const ModuleDebugInfo = switch (native_os) { fn deinit(self: *@This(), allocator: mem.Allocator) void { self.dwarf.deinit(allocator); - os.munmap(self.mapped_memory); + os.posix.munmap(self.mapped_memory); } pub fn getSymbolAtAddress(self: *@This(), allocator: mem.Allocator, address: usize) !SymbolInfo { @@ -1880,10 +1880,10 @@ 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); + try os.posix.sigaction(os.SIG.SEGV, act, null); + try os.posix.sigaction(os.SIG.ILL, act, null); + try os.posix.sigaction(os.SIG.BUS, act, null); + try os.posix.sigaction(os.SIG.FPE, act, null); } /// Attaches a global SIGSEGV handler which calls @panic("segmentation fault"); diff --git a/lib/std/dynamic_library.zig b/lib/std/dynamic_library.zig index 2ab798dcd78c..c560690b27c9 100644 --- a/lib/std/dynamic_library.zig +++ b/lib/std/dynamic_library.zig @@ -123,7 +123,7 @@ pub const ElfDynLib = struct { // 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 os.posix.mmap( null, mem.alignForward(size, mem.page_size), os.PROT.READ, @@ -131,7 +131,7 @@ pub const ElfDynLib = struct { fd, 0, ); - defer os.munmap(file_bytes); + defer os.posix.munmap(file_bytes); const eh = @ptrCast(*elf.Ehdr, file_bytes.ptr); if (!mem.eql(u8, eh.e_ident[0..4], elf.MAGIC)) return error.NotElfFile; @@ -161,7 +161,7 @@ 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 os.posix.mmap( null, virt_addr_end, os.PROT.NONE, @@ -169,7 +169,7 @@ pub const ElfDynLib = struct { -1, 0, ); - errdefer os.munmap(all_loaded_mem); + errdefer os.posix.munmap(all_loaded_mem); const base = @ptrToInt(all_loaded_mem.ptr); @@ -193,7 +193,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 os.posix.mmap( ptr, extended_memsz, prot, @@ -202,7 +202,7 @@ pub const ElfDynLib = struct { ph.p_offset - extra_bytes, ); } else { - const sect_mem = try os.mmap( + const sect_mem = try os.posix.mmap( ptr, extended_memsz, prot, @@ -256,7 +256,7 @@ pub const ElfDynLib = struct { /// Trusts the file pub fn close(self: *ElfDynLib) void { - os.munmap(self.memory); + os.posix.munmap(self.memory); self.* = undefined; } diff --git a/lib/std/fs.zig b/lib/std/fs.zig index def433270003..3dd822c2ff60 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -1194,7 +1194,7 @@ pub const Dir = struct { } if (has_flock_open_flags and flags.lock_nonblocking) { - var fl_flags = os.fcntl(fd, os.F.GETFL, 0) catch |err| switch (err) { + var fl_flags = os.posix.fcntl(fd, os.F.GETFL, 0) catch |err| switch (err) { error.FileBusy => unreachable, error.Locked => unreachable, error.PermissionDenied => unreachable, @@ -1203,7 +1203,7 @@ pub const Dir = struct { else => |e| return e, }; fl_flags &= ~@as(usize, os.O.NONBLOCK); - _ = os.fcntl(fd, os.F.SETFL, fl_flags) catch |err| switch (err) { + _ = os.posix.fcntl(fd, os.F.SETFL, fl_flags) catch |err| switch (err) { error.FileBusy => unreachable, error.Locked => unreachable, error.PermissionDenied => unreachable, @@ -1350,7 +1350,7 @@ pub const Dir = struct { } if (has_flock_open_flags and flags.lock_nonblocking) { - var fl_flags = os.fcntl(fd, os.F.GETFL, 0) catch |err| switch (err) { + var fl_flags = os.posix.fcntl(fd, os.F.GETFL, 0) catch |err| switch (err) { error.FileBusy => unreachable, error.Locked => unreachable, error.PermissionDenied => unreachable, @@ -1359,7 +1359,7 @@ pub const Dir = struct { else => |e| return e, }; fl_flags &= ~@as(usize, os.O.NONBLOCK); - _ = os.fcntl(fd, os.F.SETFL, fl_flags) catch |err| switch (err) { + _ = os.posix.fcntl(fd, os.F.SETFL, fl_flags) catch |err| switch (err) { error.FileBusy => unreachable, error.Locked => unreachable, error.PermissionDenied => unreachable, diff --git a/lib/std/fs/file.zig b/lib/std/fs/file.zig index bf93a6123931..6dcdd2d2ddd4 100644 --- a/lib/std/fs/file.zig +++ b/lib/std/fs/file.zig @@ -399,17 +399,17 @@ pub const File = struct { return Stat.fromSystem(st); } - pub const ChmodError = std.os.FChmodError; + pub const ChmodError = std.os.posix.FChmodError; /// Changes the mode of the file. /// 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 chmod(self: File, new_mode: Mode) ChmodError!void { - try os.fchmod(self.handle, new_mode); + try os.posix.fchmod(self.handle, new_mode); } - pub const ChownError = std.os.FChownError; + pub const ChownError = std.os.posix.FChownError; /// Changes the owner and group of the file. /// The process must have the correct privileges in order to do this @@ -417,7 +417,7 @@ pub const File = struct { /// 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 chown(self: File, owner: ?Uid, group: ?Gid) ChownError!void { - try os.fchown(self.handle, owner, group); + try os.posix.fchown(self.handle, owner, group); } /// Cross-platform representation of permissions on a file. @@ -899,7 +899,7 @@ pub const File = struct { }; } - pub const UpdateTimesError = os.FutimensError || windows.SetFileTimeError; + pub const UpdateTimesError = os.posix.FutimensError || windows.SetFileTimeError; /// The underlying file system may have a different granularity than nanoseconds, /// and therefore this function cannot guarantee any precision will be stored. @@ -928,7 +928,7 @@ pub const File = struct { .tv_nsec = math.cast(isize, @mod(mtime, std.time.ns_per_s)) orelse maxInt(isize), }, }; - try os.futimens(self.handle, ×); + try os.posix.futimens(self.handle, ×); } /// Reads all the bytes from the current position to the end of the file. diff --git a/lib/std/heap/PageAllocator.zig b/lib/std/heap/PageAllocator.zig index 2c8146caf3b7..4334e0e43e2d 100644 --- a/lib/std/heap/PageAllocator.zig +++ b/lib/std/heap/PageAllocator.zig @@ -31,7 +31,7 @@ fn alloc(_: *anyopaque, n: usize, log2_align: u8, ra: usize) ?[*]u8 { } const hint = @atomicLoad(@TypeOf(std.heap.next_mmap_addr_hint), &std.heap.next_mmap_addr_hint, .Unordered); - const slice = os.mmap( + const slice = os.posix.mmap( hint, aligned_len, os.PROT.READ | os.PROT.WRITE, @@ -87,7 +87,7 @@ fn resize( if (new_size_aligned < buf_aligned_len) { const ptr = @alignCast(mem.page_size, buf_unaligned.ptr + new_size_aligned); // TODO: if the next_mmap_addr_hint is within the unmapped range, update it - os.munmap(ptr[0 .. buf_aligned_len - new_size_aligned]); + os.posix.munmap(ptr[0 .. buf_aligned_len - new_size_aligned]); return true; } @@ -105,6 +105,6 @@ fn free(_: *anyopaque, slice: []u8, log2_buf_align: u8, return_address: usize) v } else { const buf_aligned_len = mem.alignForward(slice.len, mem.page_size); const ptr = @alignCast(mem.page_size, slice.ptr); - os.munmap(ptr[0..buf_aligned_len]); + os.posix.munmap(ptr[0..buf_aligned_len]); } } diff --git a/lib/std/os.zig b/lib/std/os.zig index 6c680e9a3815..2c6f44ed5045 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -6,6 +6,10 @@ //! for UTF-16LE encoding. //! * Where operating systems share APIs, e.g. POSIX, these thin wrappers provide //! cross platform abstracting. +//! - If operating systems API semantics can be unified without significant +//! drawback, then only the most portable abstraction or wrapper is provided. +//! This usually means, that there is no POSIX function, if Windows and POSIX +//! API can be used with the same semantics. //! * When there exists a corresponding libc function and linking libc, the libc //! implementation is used. Exceptions are made for known buggy areas of libc. //! On Linux libc can be side-stepped by using `std.os.linux` directly. @@ -15,14 +19,14 @@ //! in general EINTR is handled by trying again. const root = @import("root"); -const std = @import("std.zig"); const builtin = @import("builtin"); +const std = @import("std.zig"); +const dl = @import("dynamic_library.zig"); const assert = std.debug.assert; const math = std.math; const mem = std.mem; const elf = std.elf; const fs = std.fs; -const dl = @import("dynamic_library.zig"); const MAX_PATH_BYTES = std.fs.MAX_PATH_BYTES; const is_windows = builtin.os.tag == .windows; const Allocator = std.mem.Allocator; @@ -30,35 +34,37 @@ const Preopen = std.fs.wasi.Preopen; const PreopenList = std.fs.wasi.PreopenList; pub const darwin = @import("os/darwin.zig"); +pub const linux = @import("os/linux.zig"); +pub const plan9 = @import("os/plan9.zig"); +pub const uefi = @import("os/uefi.zig"); +pub const wasi = @import("os/wasi.zig"); +pub const windows = @import("os/windows.zig"); + +pub const posix = @import("os/posix.zig"); // posix +pub const posix_spawn = @import("os/posix_spawn.zig"); // posix +pub const ptrace = @import("os/ptrace.zig"); // posix 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 linux = @import("os/linux.zig"); -pub const plan9 = @import("os/plan9.zig"); -pub const uefi = @import("os/uefi.zig"); -pub const wasi = @import("os/wasi.zig"); -pub const windows = @import("os/windows.zig"); -pub const posix_spawn = @import("os/posix_spawn.zig"); -pub const ptrace = @import("os/ptrace.zig"); comptime { assert(@import("std") == std); // std lib tests require --zig-lib-dir } test { + _ = @import("os/test.zig"); _ = darwin; _ = linux; + _ = wasi; + _ = windows; if (builtin.os.tag == .uefi) { _ = uefi; } - _ = wasi; - _ = windows; - _ = posix_spawn; - _ = @import("os/test.zig"); + _ = posix; } /// Applications can override the `system` API layer in their root source file. @@ -70,8 +76,8 @@ else if (builtin.link_libc or is_windows) std.c else switch (builtin.os.tag) { .linux => linux, - .wasi => wasi, .uefi => uefi, + .wasi => wasi, else => struct {}, }; @@ -98,10 +104,10 @@ 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 MSF = system.MSF; pub const MSG = system.MSG; pub const NAME_MAX = system.NAME_MAX; pub const O = switch (builtin.os.tag) { @@ -122,7 +128,6 @@ 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; @@ -136,10 +141,11 @@ pub const STDOUT_FILENO = system.STDOUT_FILENO; pub const SYS = system.SYS; pub const Sigaction = system.Sigaction; pub const Stat = system.Stat; -pub const TCSA = system.TCSA; pub const TCP = system.TCP; +pub const TCSA = system.TCSA; pub const VDSO = system.VDSO; pub const W = system.W; +pub const _SC = system._SC; pub const addrinfo = system.addrinfo; pub const blkcnt_t = system.blkcnt_t; pub const blksize_t = system.blksize_t; @@ -151,6 +157,7 @@ pub const empty_sigset = system.empty_sigset; pub const fd_t = system.fd_t; pub const fdflags_t = system.fdflags_t; pub const fdstat_t = system.fdstat_t; +pub const file_obj = system.file_obj; pub const gid_t = system.gid_t; pub const ifreq = system.ifreq; pub const ino_t = system.ino_t; @@ -165,10 +172,9 @@ pub const off_t = system.off_t; pub const oflags_t = system.oflags_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 port_t = system.port_t; pub const rights_t = system.rights_t; pub const rlim_t = system.rlim_t; pub const rlimit = system.rlimit; @@ -267,7 +273,7 @@ pub fn maybeIgnoreSigpipe() void { .mask = empty_sigset, .flags = 0, }; - sigaction(SIG.PIPE, &act, null) catch |err| + posix.sigaction(SIG.PIPE, &act, null) catch |err| std.debug.panic("failed to install noop SIGPIPE handler with '{s}'", .{@errorName(err)}); } } @@ -307,116 +313,6 @@ pub fn close(fd: fd_t) void { } } -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); - - 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), - } - } -} - -const FChmodAtError = FChmodError || error{ - NameTooLong, -}; - -pub 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"); - - 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, - .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 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 { - if (builtin.os.tag == .windows or builtin.os.tag == .wasi) - @compileError("Unsupported OS"); - - while (true) { - const res = system.fchown(fd, owner orelse @as(u32, 0) -% 1, group orelse @as(u32, 0) -% 1); - - switch (system.getErrno(res)) { - .SUCCESS => return, - .INTR => continue, - .BADF => unreachable, // Can be reached if the fd refers to a non-iterable directory. - - .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; @@ -432,6 +328,7 @@ pub const RebootCommand = switch (builtin.os.tag) { SW_SUSPEND: void, KEXEC: void, }, + // TODO darwin else => @compileError("Unsupported OS"), }; @@ -564,7 +461,7 @@ pub fn abort() noreturn { raise(SIG.ABRT) catch {}; // Disable all signal handlers. - sigprocmask(SIG.BLOCK, &linux.all_mask, null); + posix.sigprocmask(SIG.BLOCK, &linux.all_mask, null); // Only one thread may proceed to the rest of abort(). if (!builtin.single_threaded) { @@ -580,14 +477,14 @@ pub fn abort() noreturn { .mask = empty_sigset, .flags = 0, }; - sigaction(SIG.ABRT, &sigact, null) catch |err| switch (err) { + posix.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); + posix.sigprocmask(SIG.UNBLOCK, &sigabrtmask, null); // Beyond this point should be unreachable. @intToPtr(*allowzero volatile u8, 0).* = 0; @@ -602,6 +499,9 @@ pub fn abort() noreturn { pub const RaiseError = UnexpectedError; +/// Send signal to current process. +/// Note: Support varies. For example Windows only supports SIGABRT, SIGFPE, +/// SIGILL, SIGINT, SIGSEGV and SIGTERM. pub fn raise(sig: u8) RaiseError!void { if (builtin.link_libc) { switch (errno(system.raise(sig))) { @@ -613,13 +513,13 @@ pub fn raise(sig: u8) RaiseError!void { if (builtin.os.tag == .linux) { var set: sigset_t = undefined; // block application signals - sigprocmask(SIG.BLOCK, &linux.app_mask, &set); + posix.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); + posix.sigprocmask(SIG.SETMASK, &set, null); switch (errno(rc)) { .SUCCESS => return, @@ -630,18 +530,6 @@ pub fn raise(sig: u8) RaiseError!void { @compileError("std.os.raise unimplemented for this target"); } -pub const KillError = error{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 => unreachable, // always a race condition - else => |err| return unexpectedErrno(err), - } -} - /// Exits the program cleanly with the specified status code. pub fn exit(status: u8) noreturn { if (builtin.link_libc) { @@ -1759,152 +1647,7 @@ pub fn openatW(dir_fd: fd_t, file_path_w: []const u16, flags: u32, mode: mode_t) }; } -pub fn dup(old_fd: fd_t) !fd_t { - const rc = system.dup(old_fd); - return switch (errno(rc)) { - .SUCCESS => return @intCast(fd_t, 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, .solaris => 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.tokenize(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; - mem.copy(u8, &path_buf, search_path); - path_buf[search_path.len] = '/'; - mem.copy(u8, path_buf[search_path.len + 1 ..], 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`. +/// Get an environment variable. See also `getenvZ`. pub fn getenv(key: []const u8) ?[]const u8 { if (builtin.link_libc) { var small_key_buf: [64]u8 = undefined; @@ -3123,71 +2866,6 @@ pub fn readlinkatZ(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) Read } } -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) { @@ -3992,32 +3670,6 @@ pub fn getsockoptError(sockfd: fd_t) ConnectError!void { } } -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. If you spawned your child process using `posix_spawn` method, -/// use `std.os.posix_spawn.waitpid` instead. -pub fn waitpid(pid: pid_t, flags: u32) WaitPidResult { - const Status = if (builtin.link_libc) c_int else u32; - var status: Status = undefined; - while (true) { - const rc = system.waitpid(pid, &status, if (builtin.link_libc) @intCast(c_int, flags) else flags); - switch (errno(rc)) { - .SUCCESS => return .{ - .pid = @intCast(pid_t, rc), - .status = @bitCast(u32, 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, @@ -4299,110 +3951,6 @@ pub fn mprotect(memory: []align(mem.page_size) u8, protection: u32) MProtectErro } } -pub const ForkError = error{SystemResources} || UnexpectedError; - -pub fn fork() ForkError!pid_t { - const rc = system.fork(); - switch (errno(rc)) { - .SUCCESS => return @intCast(pid_t, 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 `O.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: u32, - fd: fd_t, - offset: u64, -) MMapError![]align(mem.page_size) u8 { - const mmap_sym = if (builtin.os.tag == .linux and builtin.link_libc) - system.mmap64 - else - system.mmap; - - const ioffset = @bitCast(i64, offset); // the OS treats this as unsigned - const rc = mmap_sym(ptr, length, prot, flags, fd, ioffset); - const err = if (builtin.link_libc) blk: { - if (rc != std.c.MAP.FAILED) return @ptrCast([*]align(mem.page_size) u8, @alignCast(mem.page_size, rc))[0..length]; - break :blk @intToEnum(E, system._errno().*); - } else blk: { - const err = errno(rc); - if (err == .SUCCESS) return @intToPtr([*]align(mem.page_size) u8, 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, @@ -4584,75 +4132,6 @@ pub fn faccessatW(dirfd: fd_t, sub_path_w: [*:0]const u16, mode: u32, flags: u32 } } -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: u32) 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), - } - } - - var fds: [2]fd_t = try pipe(); - errdefer { - close(fds[0]); - close(fds[1]); - } - - if (flags == 0) - return fds; - - // O.CLOEXEC is special, it's a file descriptor flag and must be set using - // F.SETFD. - if (flags & O.CLOEXEC != 0) { - 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 = flags & ~@as(u32, O.CLOEXEC); - // 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, @@ -4914,41 +4393,12 @@ pub fn lseek_CUR_get(fd: fd_t) SeekError!u64 { } } -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(usize, 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) { + var fd_flags = posix.fcntl(sock, F.GETFD, 0) catch |err| switch (err) { error.FileBusy => unreachable, error.Locked => unreachable, error.PermissionDenied => unreachable, @@ -4957,7 +4407,7 @@ fn setSockFlags(sock: socket_t, flags: u32) !void { else => |e| return e, }; fd_flags |= FD_CLOEXEC; - _ = fcntl(sock, F.SETFD, fd_flags) catch |err| switch (err) { + _ = posix.fcntl(sock, F.SETFD, fd_flags) catch |err| switch (err) { error.FileBusy => unreachable, error.Locked => unreachable, error.PermissionDenied => unreachable, @@ -4980,7 +4430,7 @@ fn setSockFlags(sock: socket_t, flags: u32) !void { } } } else { - var fl_flags = fcntl(sock, F.GETFL, 0) catch |err| switch (err) { + var fl_flags = posix.fcntl(sock, F.GETFL, 0) catch |err| switch (err) { error.FileBusy => unreachable, error.Locked => unreachable, error.PermissionDenied => unreachable, @@ -4989,7 +4439,7 @@ fn setSockFlags(sock: socket_t, flags: u32) !void { else => |e| return e, }; fl_flags |= O.NONBLOCK; - _ = fcntl(sock, F.SETFL, fl_flags) catch |err| switch (err) { + _ = posix.fcntl(sock, F.SETFL, fl_flags) catch |err| switch (err) { error.FileBusy => unreachable, error.Locked => unreachable, error.PermissionDenied => unreachable, @@ -5292,6 +4742,7 @@ pub fn getFdPath(fd: fd_t, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 { /// Spurious wakeups are possible and no precision of timing is guaranteed. pub fn nanosleep(seconds: u64, nanoseconds: u64) void { + // TODO unify with windows NtDelayExecution var req = timespec{ .tv_sec = math.cast(isize, seconds) orelse math.maxInt(isize), .tv_nsec = math.cast(isize, nanoseconds) orelse math.maxInt(isize), @@ -5513,99 +4964,6 @@ pub fn unexpectedErrno(err: E) UnexpectedError { 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(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, wasi.FILESTAT_SET_ATIM | wasi.FILESTAT_SET_MTIM)) { - .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 { @@ -5619,7 +4977,7 @@ pub fn gethostname(name_buffer: *[HOST_NAME_MAX]u8) GetHostNameError![]u8 { } } if (builtin.os.tag == .linux) { - const uts = uname(); + const uts = posix.uname(); const hostname = mem.sliceTo(&uts.nodename, 0); mem.copy(u8, name_buffer, hostname); return name_buffer[0..hostname.len]; @@ -5628,15 +4986,6 @@ pub fn gethostname(name_buffer: *[HOST_NAME_MAX]u8) GetHostNameError![]u8 { @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, @@ -6702,48 +6051,6 @@ pub fn memfd_create(name: []const u8, flags: u32) !fd_t { 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 TermiosGetError = error{NotATerminal} || 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 IoCtl_SIOCGIFINDEX_Error = error{ FileSystem, InterfaceNotFound, @@ -6890,88 +6197,6 @@ pub fn prctl(option: PR, args: anytype) PrctlError!u31 { } } -pub const GetrlimitError = UnexpectedError; - -pub fn getrlimit(resource: rlimit_resource) GetrlimitError!rlimit { - const getrlimit_sym = if (builtin.os.tag == .linux and builtin.link_libc) - 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 (builtin.os.tag == .linux and builtin.link_libc) - 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 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), diff --git a/lib/std/os/linux/io_uring.zig b/lib/std/os/linux/io_uring.zig index 61bf39105f2f..a836e6540c68 100644 --- a/lib/std/os/linux/io_uring.zig +++ b/lib/std/os/linux/io_uring.zig @@ -1097,7 +1097,7 @@ pub const SubmissionQueue = struct { 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 os.posix.mmap( null, size, os.PROT.READ | os.PROT.WRITE, @@ -1105,13 +1105,13 @@ pub const SubmissionQueue = struct { fd, linux.IORING_OFF_SQ_RING, ); - errdefer os.munmap(mmap); + errdefer os.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 os.posix.mmap( null, size_sqes, os.PROT.READ | os.PROT.WRITE, @@ -1119,7 +1119,7 @@ pub const SubmissionQueue = struct { fd, linux.IORING_OFF_SQES, ); - errdefer os.munmap(mmap_sqes); + errdefer os.posix.munmap(mmap_sqes); assert(mmap_sqes.len == size_sqes); const array = @ptrCast([*]u32, @alignCast(@alignOf(u32), &mmap[p.sq_off.array])); @@ -1144,8 +1144,8 @@ pub const SubmissionQueue = struct { } pub fn deinit(self: *SubmissionQueue) void { - os.munmap(self.mmap_sqes); - os.munmap(self.mmap); + os.posix.munmap(self.mmap_sqes); + os.posix.munmap(self.mmap); } }; diff --git a/lib/std/os/linux/tls.zig b/lib/std/os/linux/tls.zig index cffdec042448..15d09f12ab06 100644 --- a/lib/std/os/linux/tls.zig +++ b/lib/std/os/linux/tls.zig @@ -318,7 +318,7 @@ 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 = os.posix.mmap( null, tls_image.alloc_size + tls_image.alloc_align - 1, os.PROT.READ | os.PROT.WRITE, diff --git a/lib/std/os/posix.zig b/lib/std/os/posix.zig new file mode 100644 index 000000000000..e439904c8741 --- /dev/null +++ b/lib/std/os/posix.zig @@ -0,0 +1,830 @@ +//! This file contains posix abstractions and wrappers around +//! OS-specific APIs for conforming platforms, whether libc is or is not linked. +//! The purpose of this file is to have error handling for APIs which +//! don't support comparable semantics on non-posix systems like Windows. +//! Reasons are: +//! - Fundamental flaws (signaling, process management) +//! - More extensive execution semantics (permission system, memory management, ipc) +//! With above restrictions, the same goals as for os.zig apply. + +const std = @import("std"); +const builtin = @import("builtin"); +const fs = std.fs; +const math = std.math; +const mem = std.mem; +const os = std.os; + +const wasi = os.wasi; + +const UnexpectedError = os.UnexpectedError; +const errno = os.errno; +const fd_t = os.fd_t; +const gid_t = os.gid_t; +const mode_t = os.mode_t; +const pid_t = os.pid_t; +const rlimit = os.rlimit; +const rlimit_resource = os.rlimit_resource; +const rusage = os.rusage; +const sigset_t = os.sigset_t; +const stack_t = os.stack_t; +const system = os.system; +const termios = os.termios; +const timespec = os.timespec; +const uid_t = os.uid_t; +const utsname = os.utsname; +const Sigaction = os.Sigaction; +const E = os.E; +const F = os.F; +const FD_CLOEXEC = os.FD_CLOEXEC; +const O = os.O; +const TCSA = os.TCSA; +const unexpectedErrno = os.unexpectedErrno; + +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 (builtin.os.tag == .windows or builtin.os.tag == .wasi) + @compileError("Unsupported OS"); + + while (true) { + const res = system.fchmod(fd, mode); + + switch (system.getErrno(res)) { + .SUCCESS => return, + .INTR => continue, + .BADF => unreachable, // Can be reached if the fd refers to a non-iterable directory. + + .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), + } + } +} + +const FChmodAtError = FChmodError || error{ + NameTooLong, +}; + +pub 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"); + + const path_c = try os.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, + .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 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 { + if (builtin.os.tag == .windows or builtin.os.tag == .wasi) + @compileError("Unsupported OS"); + + while (true) { + const res = system.fchown(fd, owner orelse @as(u32, 0) -% 1, group orelse @as(u32, 0) -% 1); + + switch (system.getErrno(res)) { + .SUCCESS => return, + .INTR => continue, + .BADF => unreachable, // Can be reached if the fd refers to a non-iterable directory. + + .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 KillError = error{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 => unreachable, // always a race condition + else => |err| return unexpectedErrno(err), + } +} + +/// Duplicate file descriptor. +pub fn dup(old_fd: fd_t) !fd_t { + const rc = system.dup(old_fd); + return switch (errno(rc)) { + .SUCCESS => return @intCast(fd_t, rc), + .MFILE => error.ProcessFdQuotaExceeded, + .BADF => unreachable, // invalid file descriptor + else => |err| return unexpectedErrno(err), + }; +} + +/// Assign file descriptor. +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, .solaris => 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 = os.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: [fs.MAX_PATH_BYTES]u8 = undefined; + var it = mem.tokenize(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; + mem.copy(u8, &path_buf, search_path); + path_buf[search_path.len] = '/'; + mem.copy(u8, path_buf[search_path.len + 1 ..], 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); +} + +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), + } +} + +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. If you spawned your child process using `posix_spawn` method, +/// use `std.os.posix_spawn.waitpid` instead. +pub fn waitpid(pid: pid_t, flags: u32) WaitPidResult { + const Status = if (builtin.link_libc) c_int else u32; + var status: Status = undefined; + while (true) { + const rc = system.waitpid(pid, &status, if (builtin.link_libc) @intCast(c_int, flags) else flags); + switch (errno(rc)) { + .SUCCESS => return .{ + .pid = @intCast(pid_t, rc), + .status = @bitCast(u32, 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 ForkError = error{SystemResources} || UnexpectedError; + +pub fn fork() ForkError!pid_t { + const rc = system.fork(); + switch (errno(rc)) { + .SUCCESS => return @intCast(pid_t, 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 `O.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: u32, + fd: fd_t, + offset: u64, +) MMapError![]align(mem.page_size) u8 { + const mmap_sym = if (builtin.os.tag == .linux and builtin.link_libc) + system.mmap64 + else + system.mmap; + + const ioffset = @bitCast(i64, offset); // the OS treats this as unsigned + const rc = mmap_sym(ptr, length, prot, flags, fd, ioffset); + const err = if (builtin.link_libc) blk: { + if (rc != std.c.MAP.FAILED) return @ptrCast([*]align(mem.page_size) u8, @alignCast(mem.page_size, rc))[0..length]; + break :blk @intToEnum(E, system._errno().*); + } else blk: { + const err = errno(rc); + if (err == .SUCCESS) return @intToPtr([*]align(mem.page_size) u8, 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() + .NOMEM => return error.OutOfMemory, + .MFILE => return error.ProcessFdQuotaExceeded, + .NFILE => return error.SystemFdQuotaExceeded, + 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 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: u32) 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), + } + } + + var fds: [2]fd_t = try pipe(); + errdefer { + os.close(fds[0]); + os.close(fds[1]); + } + + if (flags == 0) + return fds; + + // O.CLOEXEC is special, it's a file descriptor flag and must be set using + // F.SETFD. + if (flags & O.CLOEXEC != 0) { + 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 = flags & ~@as(u32, O.CLOEXEC); + // 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 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(usize, 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 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(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, wasi.FILESTAT_SET_ATIM | wasi.FILESTAT_SET_MTIM)) { + .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 fn uname() utsname { + var uts: utsname = undefined; + switch (errno(system.uname(&uts))) { + .SUCCESS => return uts, + .FAULT => unreachable, + else => unreachable, + } +} + +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 TermiosGetError = error{NotATerminal} || 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 GetrlimitError = UnexpectedError; + +pub fn getrlimit(resource: rlimit_resource) GetrlimitError!rlimit { + const getrlimit_sym = if (builtin.os.tag == .linux and builtin.link_libc) + 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 (builtin.os.tag == .linux and builtin.link_libc) + 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 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), + } +} diff --git a/lib/std/os/posix_spawn.zig b/lib/std/os/posix_spawn.zig index 32904a94236f..0b3859a22a50 100644 --- a/lib/std/os/posix_spawn.zig +++ b/lib/std/os/posix_spawn.zig @@ -10,7 +10,7 @@ const pid_t = system.pid_t; const unexpectedErrno = os.unexpectedErrno; const UnexpectedError = os.UnexpectedError; const toPosixPath = os.toPosixPath; -const WaitPidResult = os.WaitPidResult; +const WaitPidResult = os.posix.WaitPidResult; pub usingnamespace posix_spawn; diff --git a/lib/std/os/test.zig b/lib/std/os/test.zig index 7a5fc0de33cd..1566f8f99b68 100644 --- a/lib/std/os/test.zig +++ b/lib/std/os/test.zig @@ -434,11 +434,11 @@ test "sigaltstack" { if (native_os == .windows or native_os == .wasi) return error.SkipZigTest; var st: os.stack_t = undefined; - try os.sigaltstack(null, &st); + try os.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, os.posix.sigaltstack(&st, null)); } // If the type is not available use void to avoid erroring out when `iter_fn` is @@ -506,7 +506,7 @@ test "pipe" { if (native_os == .windows or native_os == .wasi) return error.SkipZigTest; - var fds = try os.pipe(); + var fds = try os.posix.pipe(); try expect((try os.write(fds[1], "hello")) == 5); var buf: [16]u8 = undefined; try expect((try os.read(fds[0], buf[0..])) == 5); @@ -555,7 +555,7 @@ test "mmap" { // Simple mmap() call with non page-aligned size { - const data = try os.mmap( + const data = try os.posix.mmap( null, 1234, os.PROT.READ | os.PROT.WRITE, @@ -563,7 +563,7 @@ test "mmap" { -1, 0, ); - defer os.munmap(data); + defer os.posix.munmap(data); try testing.expectEqual(@as(usize, 1234), data.len); @@ -597,7 +597,7 @@ test "mmap" { const file = try tmp.dir.openFile(test_out_file, .{}); defer file.close(); - const data = try os.mmap( + const data = try os.posix.mmap( null, alloc_size, os.PROT.READ, @@ -605,7 +605,7 @@ test "mmap" { file.handle, 0, ); - defer os.munmap(data); + defer os.posix.munmap(data); var mem_stream = io.fixedBufferStream(data); const stream = mem_stream.reader(); @@ -621,7 +621,7 @@ test "mmap" { const file = try tmp.dir.openFile(test_out_file, .{}); defer file.close(); - const data = try os.mmap( + const data = try os.posix.mmap( null, alloc_size / 2, os.PROT.READ, @@ -629,7 +629,7 @@ test "mmap" { file.handle, alloc_size / 2, ); - defer os.munmap(data); + defer os.posix.munmap(data); var mem_stream = io.fixedBufferStream(data); const stream = mem_stream.reader(); @@ -668,17 +668,17 @@ test "fcntl" { // Note: The test assumes createFile opens the file with O.CLOEXEC { - const flags = try os.fcntl(file.handle, os.F.GETFD, 0); + const flags = try os.posix.fcntl(file.handle, os.F.GETFD, 0); try expect((flags & os.FD_CLOEXEC) != 0); } { - _ = try os.fcntl(file.handle, os.F.SETFD, 0); - const flags = try os.fcntl(file.handle, os.F.GETFD, 0); + _ = try os.posix.fcntl(file.handle, os.F.SETFD, 0); + const flags = try os.posix.fcntl(file.handle, os.F.GETFD, 0); try expect((flags & os.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 os.posix.fcntl(file.handle, os.F.SETFD, os.FD_CLOEXEC); + const flags = try os.posix.fcntl(file.handle, os.F.GETFD, 0); try expect((flags & os.FD_CLOEXEC) != 0); } } @@ -736,7 +736,7 @@ test "getrlimit and setrlimit" { inline for (std.meta.fields(os.rlimit_resource)) |field| { const resource = @intToEnum(os.rlimit_resource, field.value); - const limit = try os.getrlimit(resource); + const limit = try os.posix.getrlimit(resource); // On 32 bit MIPS musl includes a fix which changes limits greater than -1UL/2 to RLIM_INFINITY. // See http://git.musl-libc.org/cgit/musl/commit/src/misc/getrlimit.c?id=8258014fd1e34e942a549c88c7e022a00445c352 @@ -745,10 +745,10 @@ test "getrlimit and setrlimit" { // 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); + try os.posix.setrlimit(resource, limit); } } else { - try os.setrlimit(resource, limit); + try os.posix.setrlimit(resource, limit); } } } @@ -807,10 +807,10 @@ test "sigaction" { var old_sa: os.Sigaction = undefined; // Install the new signal handler. - try os.sigaction(os.SIG.USR1, &sa, null); + try os.posix.sigaction(os.SIG.USR1, &sa, null); // Check that we can read it back correctly. - try os.sigaction(os.SIG.USR1, null, &old_sa); + try os.posix.sigaction(os.SIG.USR1, null, &old_sa); try testing.expectEqual(&S.handler, old_sa.handler.sigaction.?); try testing.expect((old_sa.flags & os.SA.SIGINFO) != 0); @@ -819,26 +819,26 @@ test "sigaction" { 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 os.posix.sigaction(os.SIG.USR1, null, &old_sa); try testing.expectEqual(os.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.posix.sigaction(os.SIG.USR1, &sa, null); try os.raise(os.SIG.USR1); try testing.expect(S.handler_called_count == 2); // Now set the signal to ignored sa.handler = .{ .handler = os.SIG.IGN }; sa.flags = 0; - try os.sigaction(os.SIG.USR1, &sa, null); + try os.posix.sigaction(os.SIG.USR1, &sa, null); // Re-raise to ensure handler is actually ignored try os.raise(os.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 os.posix.sigaction(os.SIG.USR1, null, &old_sa); try testing.expectEqual(os.SIG.IGN, old_sa.handler.handler.?); } @@ -855,13 +855,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 std.os.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 std.os.posix.dup2(file.handle, new_fd); var dup2ed = std.fs.File{ .handle = new_fd }; defer dup2ed.close(); try dup2ed.writeAll("dup2"); @@ -909,27 +909,27 @@ 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, @ptrToInt(&struct_flock)); + var struct_flock = std.mem.zeroInit(std.os.Flock, .{ .type = std.os.F.WRLCK }); + _ = try std.os.posix.fcntl(fd, std.os.F.SETLK, @ptrToInt(&struct_flock)); struct_flock.start = 1; - struct_flock.type = os.F.RDLCK; - _ = try os.fcntl(fd, os.F.SETLK, @ptrToInt(&struct_flock)); + struct_flock.type = std.os.F.RDLCK; + _ = try std.os.posix.fcntl(fd, std.os.F.SETLK, @ptrToInt(&struct_flock)); // Check the locks in a child process: - const pid = try os.fork(); + const pid = try std.os.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, @ptrToInt(&struct_flock))); + struct_flock.type = std.os.F.WRLCK; + try expectError(error.Locked, std.os.posix.fcntl(fd, std.os.F.SETLK, @ptrToInt(&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, @ptrToInt(&struct_flock)); + struct_flock.type = std.os.F.RDLCK; + _ = try std.os.posix.fcntl(fd, std.os.F.SETLK, @ptrToInt(&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, @ptrToInt(&struct_flock)); + struct_flock.type = std.os.F.WRLCK; + _ = try std.os.posix.fcntl(fd, std.os.F.SETLKW, @ptrToInt(&struct_flock)); // child exits without continuing: os.exit(0); } else { @@ -937,18 +937,18 @@ test "POSIX file locking with fcntl" { 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, @ptrToInt(&struct_flock))); + struct_flock.type = std.os.F.WRLCK; + try expectError(error.DeadLock, std.os.posix.fcntl(fd, std.os.F.SETLKW, @ptrToInt(&struct_flock))); // parent releases exclusive lock: struct_flock.start = 0; - struct_flock.type = os.F.UNLCK; - _ = try os.fcntl(fd, os.F.SETLK, @ptrToInt(&struct_flock)); + struct_flock.type = std.os.F.UNLCK; + _ = try std.os.posix.fcntl(fd, std.os.F.SETLK, @ptrToInt(&struct_flock)); // parent releases shared lock: struct_flock.start = 1; - struct_flock.type = os.F.UNLCK; - _ = try os.fcntl(fd, os.F.SETLK, @ptrToInt(&struct_flock)); + struct_flock.type = std.os.F.UNLCK; + _ = try std.os.posix.fcntl(fd, std.os.F.SETLK, @ptrToInt(&struct_flock)); // parent waits for child: - const result = os.waitpid(pid, 0); + const result = std.os.posix.waitpid(pid, 0); try expect(result.status == 0 * 256); } } @@ -1189,10 +1189,10 @@ test "fchmodat smoke test" { var tmp = tmpDir(.{}); defer tmp.cleanup(); - try expectError(error.FileNotFound, os.fchmodat(tmp.dir.fd, "foo.txt", 0o666, 0)); + try expectError(error.FileNotFound, os.posix.fchmodat(tmp.dir.fd, "foo.txt", 0o666, 0)); const fd = try os.openat(tmp.dir.fd, "foo.txt", os.O.RDWR | os.O.CREAT | os.O.EXCL, 0o666); os.close(fd); - try os.fchmodat(tmp.dir.fd, "foo.txt", 0o755, 0); + try os.posix.fchmodat(tmp.dir.fd, "foo.txt", 0o755, 0); const st = try os.fstatat(tmp.dir.fd, "foo.txt", 0); try expectEqual(@as(os.mode_t, 0o755), st.mode & 0b111_111_111); } diff --git a/lib/std/process.zig b/lib/std/process.zig index eff29e86fa09..6308b94f78a7 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -1115,7 +1115,7 @@ pub const can_spawn = switch (builtin.os.tag) { else => true, }; -pub const ExecvError = std.os.ExecveError || error{OutOfMemory}; +pub const ExecvError = std.os.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. @@ -1167,5 +1167,5 @@ pub fn execve( } }; - return os.execvpeZ_expandArg0(.no_expand, argv_buf.ptr[0].?, argv_buf.ptr, envp); + return os.posix.execvpeZ_expandArg0(.no_expand, argv_buf.ptr[0].?, argv_buf.ptr, envp); } diff --git a/lib/std/start.zig b/lib/std/start.zig index 6edebde122e8..1d9ab28ce69c 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -471,7 +471,7 @@ fn expandStackSize(phdrs: []elf.Phdr) void { const wanted_stack_size = phdr.p_memsz; assert(wanted_stack_size % std.mem.page_size == 0); - std.os.setrlimit(.STACK, .{ + std.os.posix.setrlimit(.STACK, .{ .cur = wanted_stack_size, .max = wanted_stack_size, }) catch { diff --git a/lib/std/zig/system/NativeTargetInfo.zig b/lib/std/zig/system/NativeTargetInfo.zig index dbbebb43c936..e1372a11f785 100644 --- a/lib/std/zig/system/NativeTargetInfo.zig +++ b/lib/std/zig/system/NativeTargetInfo.zig @@ -39,7 +39,7 @@ pub fn detect(cross_target: CrossTarget) DetectError!NativeTargetInfo { if (cross_target.os_tag == null) { switch (builtin.target.os.tag) { .linux => { - const uts = std.os.uname(); + const uts = std.os.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. @@ -53,7 +53,7 @@ pub fn detect(cross_target: CrossTarget) DetectError!NativeTargetInfo { } }, .solaris => { - const uts = std.os.uname(); + const uts = std.os.posix.uname(); const release = mem.sliceTo(&uts.release, 0); if (std.builtin.Version.parse(release)) |ver| { os.version_range.semver.min = ver; diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index b5c9ffa9917f..d2a36b619c25 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -3950,7 +3950,7 @@ fn linkWithLLD(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) ! // 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. - try std.os.fchmodat(fs.cwd().fd, full_out_path, 0o744, 0); + try std.os.posix.fchmodat(fs.cwd().fd, full_out_path, 0o744, 0); } } diff --git a/src/main.zig b/src/main.zig index b134b7183e23..6ef785efc1cc 100644 --- a/src/main.zig +++ b/src/main.zig @@ -5284,9 +5284,8 @@ fn parseCodeModel(arg: []const u8) std.builtin.CodeModel { /// 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; - var lim = posix.getrlimit(.NOFILE) catch return; // Oh well; we tried. + var lim = std.os.posix.getrlimit(.NOFILE) catch return; // Oh well; we tried. if (comptime builtin.target.isDarwin()) { // On Darwin, `NOFILE` is bounded by a hardcoded value `OPEN_MAX`. // According to the man pages for setrlimit(): @@ -5298,17 +5297,17 @@ fn gimmeMoreOfThoseSweetSweetFileDescriptors() void { if (lim.cur == lim.max) return; // Do a binary search for the limit. - var min: posix.rlim_t = lim.cur; - var max: posix.rlim_t = 1 << 20; + var min: std.os.rlim_t = lim.cur; + var max: std.os.rlim_t = 1 << 20; // But if there's a defined upper bound, don't search, just set it. - if (lim.max != posix.RLIM.INFINITY) { + if (lim.max != std.os.RLIM.INFINITY) { min = lim.max; max = lim.max; } while (true) { lim.cur = min + @divTrunc(max - min, 2); // on freebsd rlim_t is signed - if (posix.setrlimit(.NOFILE, lim)) |_| { + if (std.os.posix.setrlimit(.NOFILE, lim)) |_| { min = lim.cur; } else |_| { max = lim.cur; diff --git a/test/standalone/sigpipe/breakpipe.zig b/test/standalone/sigpipe/breakpipe.zig index 3623451db56d..4f50f177c2af 100644 --- a/test/standalone/sigpipe/breakpipe.zig +++ b/test/standalone/sigpipe/breakpipe.zig @@ -8,7 +8,7 @@ pub const std_options = if (build_options.keep_sigpipe) struct { }; pub fn main() !void { - const pipe = try std.os.pipe(); + const pipe = try std.os.posix.pipe(); std.os.close(pipe[0]); _ = std.os.write(pipe[1], "a") catch |err| switch (err) { error.BrokenPipe => { diff --git a/test/standalone/sigpipe/build.zig b/test/standalone/sigpipe/build.zig index 763df5fe462f..6a2da2998db3 100644 --- a/test/standalone/sigpipe/build.zig +++ b/test/standalone/sigpipe/build.zig @@ -12,7 +12,7 @@ pub fn build(b: *std.build.Builder) !void { .mask = os.empty_sigset, .flags = 0, }; - try os.sigaction(os.SIG.PIPE, &act, null); + try os.posix.sigaction(os.SIG.PIPE, &act, null); } for ([_]bool{ false, true }) |keep_sigpipe| {