aboutsummaryrefslogtreecommitdiff
path: root/lib/std/posix.zig
diff options
context:
space:
mode:
Diffstat (limited to 'lib/std/posix.zig')
-rw-r--r--lib/std/posix.zig7364
1 files changed, 7364 insertions, 0 deletions
diff --git a/lib/std/posix.zig b/lib/std/posix.zig
new file mode 100644
index 0000000000..a5f8641ed6
--- /dev/null
+++ b/lib/std/posix.zig
@@ -0,0 +1,7364 @@
+//! POSIX API layer.
+//!
+//! This is more cross platform than using OS-specific APIs, however, it is
+//! lower-level and less portable than other namespaces such as `std.fs` and
+//! `std.process`.
+//!
+//! These APIs are generally lowered to libc function calls if and only if libc
+//! is linked. Most operating systems other than Windows, Linux, and WASI
+//! require always linking libc because they use it as the stable syscall ABI.
+//!
+//! Operating systems that are not POSIX-compliant are sometimes supported by
+//! this API layer; sometimes not. Generally, an implementation will be
+//! provided only if such implementation is straightforward on that operating
+//! system. Otherwise, programmers are expected to use OS-specific logic to
+//! deal with the exception.
+
+const builtin = @import("builtin");
+const root = @import("root");
+const std = @import("std.zig");
+const mem = std.mem;
+const fs = std.fs;
+const max_path_bytes = fs.MAX_PATH_BYTES;
+const maxInt = std.math.maxInt;
+const cast = std.math.cast;
+const assert = std.debug.assert;
+const native_os = builtin.os.tag;
+
+test {
+ _ = @import("posix/test.zig");
+}
+
+/// Whether to use libc for the POSIX API layer.
+const use_libc = builtin.link_libc or switch (native_os) {
+ .windows, .wasi => true,
+ else => false,
+};
+
+const linux = std.os.linux;
+const windows = std.os.windows;
+const wasi = std.os.wasi;
+
+/// Applications can override the `system` API layer in their root source file.
+/// Otherwise, when linking libc, this is the C API.
+/// When not linking libc, it is the OS-specific system interface.
+pub const system = if (@hasDecl(root, "os") and @hasDecl(root.os, "system") and root.os != @This())
+ root.os.system
+else if (use_libc)
+ std.c
+else switch (native_os) {
+ .linux => linux,
+ .plan9 => std.os.plan9,
+ else => struct {},
+};
+
+pub const AF = system.AF;
+pub const AF_SUN = system.AF_SUN;
+pub const ARCH = system.ARCH;
+pub const AT = system.AT;
+pub const AT_SUN = system.AT_SUN;
+pub const CLOCK = system.CLOCK;
+pub const CPU_COUNT = system.CPU_COUNT;
+pub const CTL = system.CTL;
+pub const DT = system.DT;
+pub const E = system.E;
+pub const Elf_Symndx = system.Elf_Symndx;
+pub const F = system.F;
+pub const FD_CLOEXEC = system.FD_CLOEXEC;
+pub const Flock = system.Flock;
+pub const HOST_NAME_MAX = system.HOST_NAME_MAX;
+pub const HW = system.HW;
+pub const IFNAMESIZE = system.IFNAMESIZE;
+pub const IOV_MAX = system.IOV_MAX;
+pub const IPPROTO = system.IPPROTO;
+pub const KERN = system.KERN;
+pub const Kevent = system.Kevent;
+pub const LOCK = system.LOCK;
+pub const MADV = system.MADV;
+pub const MAP = system.MAP;
+pub const MSF = system.MSF;
+pub const MAX_ADDR_LEN = system.MAX_ADDR_LEN;
+pub const MFD = system.MFD;
+pub const MMAP2_UNIT = system.MMAP2_UNIT;
+pub const MSG = system.MSG;
+pub const NAME_MAX = system.NAME_MAX;
+pub const O = system.O;
+pub const PATH_MAX = system.PATH_MAX;
+pub const POLL = system.POLL;
+pub const POSIX_FADV = system.POSIX_FADV;
+pub const PR = system.PR;
+pub const PROT = system.PROT;
+pub const REG = system.REG;
+pub const RLIM = system.RLIM;
+pub const RR = system.RR;
+pub const S = system.S;
+pub const SA = system.SA;
+pub const SC = system.SC;
+pub const _SC = system._SC;
+pub const SEEK = system.SEEK;
+pub const SHUT = system.SHUT;
+pub const SIG = system.SIG;
+pub const SIOCGIFINDEX = system.SIOCGIFINDEX;
+pub const SO = system.SO;
+pub const SOCK = system.SOCK;
+pub const SOL = system.SOL;
+pub const STDERR_FILENO = system.STDERR_FILENO;
+pub const STDIN_FILENO = system.STDIN_FILENO;
+pub const STDOUT_FILENO = system.STDOUT_FILENO;
+pub const SYS = system.SYS;
+pub const Sigaction = system.Sigaction;
+pub const Stat = system.Stat;
+pub const T = system.T;
+pub const TCSA = system.TCSA;
+pub const TCP = system.TCP;
+pub const VDSO = system.VDSO;
+pub const W = system.W;
+pub const addrinfo = system.addrinfo;
+pub const blkcnt_t = system.blkcnt_t;
+pub const blksize_t = system.blksize_t;
+pub const clock_t = system.clock_t;
+pub const cpu_set_t = system.cpu_set_t;
+pub const dev_t = system.dev_t;
+pub const dl_phdr_info = system.dl_phdr_info;
+pub const empty_sigset = system.empty_sigset;
+pub const filled_sigset = system.filled_sigset;
+pub const fd_t = system.fd_t;
+pub const gid_t = system.gid_t;
+pub const ifreq = system.ifreq;
+pub const ino_t = system.ino_t;
+pub const mcontext_t = system.mcontext_t;
+pub const mode_t = system.mode_t;
+pub const msghdr = system.msghdr;
+pub const msghdr_const = system.msghdr_const;
+pub const nfds_t = system.nfds_t;
+pub const nlink_t = system.nlink_t;
+pub const off_t = system.off_t;
+pub const pid_t = system.pid_t;
+pub const pollfd = system.pollfd;
+pub const port_t = system.port_t;
+pub const port_event = system.port_event;
+pub const port_notify = system.port_notify;
+pub const file_obj = system.file_obj;
+pub const rlim_t = system.rlim_t;
+pub const rlimit = system.rlimit;
+pub const rlimit_resource = system.rlimit_resource;
+pub const rusage = system.rusage;
+pub const sa_family_t = system.sa_family_t;
+pub const siginfo_t = system.siginfo_t;
+pub const sigset_t = system.sigset_t;
+pub const sockaddr = system.sockaddr;
+pub const socklen_t = system.socklen_t;
+pub const stack_t = system.stack_t;
+pub const time_t = system.time_t;
+pub const timespec = system.timespec;
+pub const timestamp_t = system.timestamp_t;
+pub const timeval = system.timeval;
+pub const timezone = system.timezone;
+pub const ucontext_t = system.ucontext_t;
+pub const uid_t = system.uid_t;
+pub const user_desc = system.user_desc;
+pub const utsname = system.utsname;
+pub const winsize = system.winsize;
+
+pub const termios = system.termios;
+pub const CSIZE = system.CSIZE;
+pub const NCCS = system.NCCS;
+pub const cc_t = system.cc_t;
+pub const V = system.V;
+pub const speed_t = system.speed_t;
+pub const tc_iflag_t = system.tc_iflag_t;
+pub const tc_oflag_t = system.tc_oflag_t;
+pub const tc_cflag_t = system.tc_cflag_t;
+pub const tc_lflag_t = system.tc_lflag_t;
+
+pub const F_OK = system.F_OK;
+pub const R_OK = system.R_OK;
+pub const W_OK = system.W_OK;
+pub const X_OK = system.X_OK;
+
+pub const iovec = extern struct {
+ iov_base: [*]u8,
+ iov_len: usize,
+};
+
+pub const iovec_const = extern struct {
+ iov_base: [*]const u8,
+ iov_len: usize,
+};
+
+pub const ACCMODE = enum(u2) {
+ RDONLY = 0,
+ WRONLY = 1,
+ RDWR = 2,
+};
+
+pub const LOG = struct {
+ /// system is unusable
+ pub const EMERG = 0;
+ /// action must be taken immediately
+ pub const ALERT = 1;
+ /// critical conditions
+ pub const CRIT = 2;
+ /// error conditions
+ pub const ERR = 3;
+ /// warning conditions
+ pub const WARNING = 4;
+ /// normal but significant condition
+ pub const NOTICE = 5;
+ /// informational
+ pub const INFO = 6;
+ /// debug-level messages
+ pub const DEBUG = 7;
+};
+
+pub const socket_t = if (native_os == .windows) windows.ws2_32.SOCKET else fd_t;
+
+/// Obtains errno from the return value of a system function call.
+///
+/// For some systems this will obtain the value directly from the syscall return value;
+/// for others it will use a thread-local errno variable. Therefore, this
+/// function only returns a well-defined value when it is called directly after
+/// the system function call whose errno value is intended to be observed.
+pub fn errno(rc: anytype) E {
+ if (use_libc) {
+ return if (rc == -1) @enumFromInt(std.c._errno().*) else .SUCCESS;
+ }
+ const signed: isize = @bitCast(rc);
+ const int = if (signed > -4096 and signed < 0) -signed else 0;
+ return @enumFromInt(int);
+}
+
+/// Closes the file descriptor.
+///
+/// This function is not capable of returning any indication of failure. An
+/// application which wants to ensure writes have succeeded before closing must
+/// call `fsync` before `close`.
+///
+/// The Zig standard library does not support POSIX thread cancellation.
+pub fn close(fd: fd_t) void {
+ if (native_os == .windows) {
+ return windows.CloseHandle(fd);
+ }
+ if (native_os == .wasi and !builtin.link_libc) {
+ _ = std.os.wasi.fd_close(fd);
+ return;
+ }
+ if (builtin.target.isDarwin()) {
+ // This avoids the EINTR problem.
+ switch (errno(std.c.@"close$NOCANCEL"(fd))) {
+ .BADF => unreachable, // Always a race condition.
+ else => return,
+ }
+ }
+ switch (errno(system.close(fd))) {
+ .BADF => unreachable, // Always a race condition.
+ .INTR => return, // This is still a success. See https://github.com/ziglang/zig/issues/2425
+ else => return,
+ }
+}
+
+pub const FChmodError = error{
+ AccessDenied,
+ InputOutput,
+ SymLinkLoop,
+ FileNotFound,
+ SystemResources,
+ ReadOnlyFileSystem,
+} || UnexpectedError;
+
+/// Changes the mode of the file referred to by the file descriptor.
+///
+/// The process must have the correct privileges in order to do this
+/// successfully, or must have the effective user ID matching the owner
+/// of the file.
+pub fn fchmod(fd: fd_t, mode: mode_t) FChmodError!void {
+ if (!fs.has_executable_bit) @compileError("fchmod unsupported by target OS");
+
+ while (true) {
+ const res = system.fchmod(fd, mode);
+ switch (errno(res)) {
+ .SUCCESS => return,
+ .INTR => continue,
+ .BADF => unreachable,
+ .FAULT => unreachable,
+ .INVAL => unreachable,
+ .ACCES => return error.AccessDenied,
+ .IO => return error.InputOutput,
+ .LOOP => return error.SymLinkLoop,
+ .NOENT => return error.FileNotFound,
+ .NOMEM => return error.SystemResources,
+ .NOTDIR => return error.FileNotFound,
+ .PERM => return error.AccessDenied,
+ .ROFS => return error.ReadOnlyFileSystem,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+}
+
+pub const FChmodAtError = FChmodError || error{
+ /// A component of `path` exceeded `NAME_MAX`, or the entire path exceeded
+ /// `PATH_MAX`.
+ NameTooLong,
+ /// `path` resolves to a symbolic link, and `AT.SYMLINK_NOFOLLOW` was set
+ /// in `flags`. This error only occurs on Linux, where changing the mode of
+ /// a symbolic link has no meaning and can cause undefined behaviour on
+ /// certain filesystems.
+ ///
+ /// The procfs fallback was used but procfs was not mounted.
+ OperationNotSupported,
+ /// The procfs fallback was used but the process exceeded its open file
+ /// limit.
+ ProcessFdQuotaExceeded,
+ /// The procfs fallback was used but the system exceeded it open file limit.
+ SystemFdQuotaExceeded,
+};
+
+/// Changes the `mode` of `path` relative to the directory referred to by
+/// `dirfd`. The process must have the correct privileges in order to do this
+/// successfully, or must have the effective user ID matching the owner of the
+/// file.
+///
+/// On Linux the `fchmodat2` syscall will be used if available, otherwise a
+/// workaround using procfs will be employed. Changing the mode of a symbolic
+/// link with `AT.SYMLINK_NOFOLLOW` set will also return
+/// `OperationNotSupported`, as:
+///
+/// 1. Permissions on the link are ignored when resolving its target.
+/// 2. This operation has been known to invoke undefined behaviour across
+/// different filesystems[1].
+///
+/// [1]: https://sourceware.org/legacy-ml/libc-alpha/2020-02/msg00467.html.
+pub inline fn fchmodat(dirfd: fd_t, path: []const u8, mode: mode_t, flags: u32) FChmodAtError!void {
+ if (!fs.has_executable_bit) @compileError("fchmodat unsupported by target OS");
+
+ // No special handling for linux is needed if we can use the libc fallback
+ // or `flags` is empty. Glibc only added the fallback in 2.32.
+ const skip_fchmodat_fallback = native_os != .linux or
+ std.c.versionCheck(.{ .major = 2, .minor = 32, .patch = 0 }) or
+ flags == 0;
+
+ // This function is marked inline so that when flags is comptime-known,
+ // skip_fchmodat_fallback will be comptime-known true.
+ if (skip_fchmodat_fallback)
+ return fchmodat1(dirfd, path, mode, flags);
+
+ return fchmodat2(dirfd, path, mode, flags);
+}
+
+fn fchmodat1(dirfd: fd_t, path: []const u8, mode: mode_t, flags: u32) FChmodAtError!void {
+ const path_c = try toPosixPath(path);
+ while (true) {
+ const res = system.fchmodat(dirfd, &path_c, mode, flags);
+ switch (errno(res)) {
+ .SUCCESS => return,
+ .INTR => continue,
+ .BADF => unreachable,
+ .FAULT => unreachable,
+ .INVAL => unreachable,
+ .ACCES => return error.AccessDenied,
+ .IO => return error.InputOutput,
+ .LOOP => return error.SymLinkLoop,
+ .MFILE => return error.ProcessFdQuotaExceeded,
+ .NAMETOOLONG => return error.NameTooLong,
+ .NFILE => return error.SystemFdQuotaExceeded,
+ .NOENT => return error.FileNotFound,
+ .NOTDIR => return error.FileNotFound,
+ .NOMEM => return error.SystemResources,
+ .OPNOTSUPP => return error.OperationNotSupported,
+ .PERM => return error.AccessDenied,
+ .ROFS => return error.ReadOnlyFileSystem,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+}
+
+fn fchmodat2(dirfd: fd_t, path: []const u8, mode: mode_t, flags: u32) FChmodAtError!void {
+ const global = struct {
+ var has_fchmodat2: bool = true;
+ };
+ const path_c = try toPosixPath(path);
+ const use_fchmodat2 = (builtin.os.isAtLeast(.linux, .{ .major = 6, .minor = 6, .patch = 0 }) orelse false) and
+ @atomicLoad(bool, &global.has_fchmodat2, .monotonic);
+ while (use_fchmodat2) {
+ // Later on this should be changed to `system.fchmodat2`
+ // when the musl/glibc add a wrapper.
+ const res = linux.fchmodat2(dirfd, &path_c, mode, flags);
+ switch (E.init(res)) {
+ .SUCCESS => return,
+ .INTR => continue,
+ .BADF => unreachable,
+ .FAULT => unreachable,
+ .INVAL => unreachable,
+ .ACCES => return error.AccessDenied,
+ .IO => return error.InputOutput,
+ .LOOP => return error.SymLinkLoop,
+ .NOENT => return error.FileNotFound,
+ .NOMEM => return error.SystemResources,
+ .NOTDIR => return error.FileNotFound,
+ .OPNOTSUPP => return error.OperationNotSupported,
+ .PERM => return error.AccessDenied,
+ .ROFS => return error.ReadOnlyFileSystem,
+
+ .NOSYS => {
+ @atomicStore(bool, &global.has_fchmodat2, false, .monotonic);
+ break;
+ },
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+
+ // Fallback to changing permissions using procfs:
+ //
+ // 1. Open `path` as a `PATH` descriptor.
+ // 2. Stat the fd and check if it isn't a symbolic link.
+ // 3. Generate the procfs reference to the fd via `/proc/self/fd/{fd}`.
+ // 4. Pass the procfs path to `chmod` with the `mode`.
+ var pathfd: fd_t = undefined;
+ while (true) {
+ const rc = system.openat(dirfd, &path_c, .{ .PATH = true, .NOFOLLOW = true, .CLOEXEC = true }, @as(mode_t, 0));
+ switch (errno(rc)) {
+ .SUCCESS => {
+ pathfd = @intCast(rc);
+ break;
+ },
+ .INTR => continue,
+ .FAULT => unreachable,
+ .INVAL => unreachable,
+ .ACCES => return error.AccessDenied,
+ .PERM => return error.AccessDenied,
+ .LOOP => return error.SymLinkLoop,
+ .MFILE => return error.ProcessFdQuotaExceeded,
+ .NAMETOOLONG => return error.NameTooLong,
+ .NFILE => return error.SystemFdQuotaExceeded,
+ .NOENT => return error.FileNotFound,
+ .NOMEM => return error.SystemResources,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+ defer close(pathfd);
+
+ const stat = fstatatZ(pathfd, "", AT.EMPTY_PATH) catch |err| switch (err) {
+ error.NameTooLong => unreachable,
+ error.FileNotFound => unreachable,
+ error.InvalidUtf8 => unreachable,
+ else => |e| return e,
+ };
+ if ((stat.mode & S.IFMT) == S.IFLNK)
+ return error.OperationNotSupported;
+
+ var procfs_buf: ["/proc/self/fd/-2147483648\x00".len]u8 = undefined;
+ const proc_path = std.fmt.bufPrintZ(procfs_buf[0..], "/proc/self/fd/{d}", .{pathfd}) catch unreachable;
+ while (true) {
+ const res = system.chmod(proc_path, mode);
+ switch (errno(res)) {
+ // Getting NOENT here means that procfs isn't mounted.
+ .NOENT => return error.OperationNotSupported,
+
+ .SUCCESS => return,
+ .INTR => continue,
+ .BADF => unreachable,
+ .FAULT => unreachable,
+ .INVAL => unreachable,
+ .ACCES => return error.AccessDenied,
+ .IO => return error.InputOutput,
+ .LOOP => return error.SymLinkLoop,
+ .NOMEM => return error.SystemResources,
+ .NOTDIR => return error.FileNotFound,
+ .PERM => return error.AccessDenied,
+ .ROFS => return error.ReadOnlyFileSystem,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+}
+
+pub const FChownError = error{
+ AccessDenied,
+ InputOutput,
+ SymLinkLoop,
+ FileNotFound,
+ SystemResources,
+ ReadOnlyFileSystem,
+} || UnexpectedError;
+
+/// Changes the owner and group of the file referred to by the file descriptor.
+/// The process must have the correct privileges in order to do this
+/// successfully. The group may be changed by the owner of the directory to
+/// any group of which the owner is a member. If the owner or group is
+/// specified as `null`, the ID is not changed.
+pub fn fchown(fd: fd_t, owner: ?uid_t, group: ?gid_t) FChownError!void {
+ switch (native_os) {
+ .windows, .wasi => @compileError("Unsupported OS"),
+ else => {},
+ }
+
+ while (true) {
+ const res = system.fchown(fd, owner orelse ~@as(uid_t, 0), group orelse ~@as(gid_t, 0));
+
+ switch (errno(res)) {
+ .SUCCESS => return,
+ .INTR => continue,
+ .BADF => unreachable, // Can be reached if the fd refers to a directory opened without `OpenDirOptions{ .iterate = true }`
+
+ .FAULT => unreachable,
+ .INVAL => unreachable,
+ .ACCES => return error.AccessDenied,
+ .IO => return error.InputOutput,
+ .LOOP => return error.SymLinkLoop,
+ .NOENT => return error.FileNotFound,
+ .NOMEM => return error.SystemResources,
+ .NOTDIR => return error.FileNotFound,
+ .PERM => return error.AccessDenied,
+ .ROFS => return error.ReadOnlyFileSystem,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+}
+
+pub const RebootError = error{
+ PermissionDenied,
+} || UnexpectedError;
+
+pub const RebootCommand = switch (native_os) {
+ .linux => union(linux.LINUX_REBOOT.CMD) {
+ RESTART: void,
+ HALT: void,
+ CAD_ON: void,
+ CAD_OFF: void,
+ POWER_OFF: void,
+ RESTART2: [*:0]const u8,
+ SW_SUSPEND: void,
+ KEXEC: void,
+ },
+ else => @compileError("Unsupported OS"),
+};
+
+pub fn reboot(cmd: RebootCommand) RebootError!void {
+ switch (native_os) {
+ .linux => {
+ switch (linux.E.init(linux.reboot(
+ .MAGIC1,
+ .MAGIC2,
+ cmd,
+ switch (cmd) {
+ .RESTART2 => |s| s,
+ else => null,
+ },
+ ))) {
+ .SUCCESS => {},
+ .PERM => return error.PermissionDenied,
+ else => |err| return std.os.unexpectedErrno(err),
+ }
+ switch (cmd) {
+ .CAD_OFF => {},
+ .CAD_ON => {},
+ .SW_SUSPEND => {},
+
+ .HALT => unreachable,
+ .KEXEC => unreachable,
+ .POWER_OFF => unreachable,
+ .RESTART => unreachable,
+ .RESTART2 => unreachable,
+ }
+ },
+ else => @compileError("Unsupported OS"),
+ }
+}
+
+pub const GetRandomError = OpenError;
+
+/// Obtain a series of random bytes. These bytes can be used to seed user-space
+/// random number generators or for cryptographic purposes.
+/// When linking against libc, this calls the
+/// appropriate OS-specific library call. Otherwise it uses the zig standard
+/// library implementation.
+pub fn getrandom(buffer: []u8) GetRandomError!void {
+ if (native_os == .windows) {
+ return windows.RtlGenRandom(buffer);
+ }
+ if (native_os == .linux or native_os == .freebsd) {
+ var buf = buffer;
+ const use_c = native_os != .linux or
+ std.c.versionCheck(std.SemanticVersion{ .major = 2, .minor = 25, .patch = 0 });
+
+ while (buf.len != 0) {
+ const num_read: usize, const err = if (use_c) res: {
+ const rc = std.c.getrandom(buf.ptr, buf.len, 0);
+ break :res .{ @bitCast(rc), errno(rc) };
+ } else res: {
+ const rc = linux.getrandom(buf.ptr, buf.len, 0);
+ break :res .{ rc, linux.E.init(rc) };
+ };
+
+ switch (err) {
+ .SUCCESS => buf = buf[num_read..],
+ .INVAL => unreachable,
+ .FAULT => unreachable,
+ .INTR => continue,
+ .NOSYS => return getRandomBytesDevURandom(buf),
+ else => return unexpectedErrno(err),
+ }
+ }
+ return;
+ }
+ if (native_os == .emscripten) {
+ const err = errno(std.c.getentropy(buffer.ptr, buffer.len));
+ switch (err) {
+ .SUCCESS => return,
+ else => return unexpectedErrno(err),
+ }
+ }
+ switch (native_os) {
+ .netbsd, .openbsd, .macos, .ios, .tvos, .watchos => {
+ system.arc4random_buf(buffer.ptr, buffer.len);
+ return;
+ },
+ .wasi => switch (wasi.random_get(buffer.ptr, buffer.len)) {
+ .SUCCESS => return,
+ else => |err| return unexpectedErrno(err),
+ },
+ else => return getRandomBytesDevURandom(buffer),
+ }
+}
+
+fn getRandomBytesDevURandom(buf: []u8) !void {
+ const fd = try openZ("/dev/urandom", .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0);
+ defer close(fd);
+
+ const st = try fstat(fd);
+ if (!S.ISCHR(st.mode)) {
+ return error.NoDevice;
+ }
+
+ const file: fs.File = .{ .handle = fd };
+ const stream = file.reader();
+ stream.readNoEof(buf) catch return error.Unexpected;
+}
+
+/// Causes abnormal process termination.
+/// If linking against libc, this calls the abort() libc function. Otherwise
+/// it raises SIGABRT followed by SIGKILL and finally lo
+/// Invokes the current signal handler for SIGABRT, if any.
+pub fn abort() noreturn {
+ @setCold(true);
+ // MSVCRT abort() sometimes opens a popup window which is undesirable, so
+ // even when linking libc on Windows we use our own abort implementation.
+ // See https://github.com/ziglang/zig/issues/2071 for more details.
+ if (native_os == .windows) {
+ if (builtin.mode == .Debug) {
+ @breakpoint();
+ }
+ windows.kernel32.ExitProcess(3);
+ }
+ if (!builtin.link_libc and native_os == .linux) {
+ // The Linux man page says that the libc abort() function
+ // "first unblocks the SIGABRT signal", but this is a footgun
+ // for user-defined signal handlers that want to restore some state in
+ // some program sections and crash in others.
+ // So, the user-installed SIGABRT handler is run, if present.
+ raise(SIG.ABRT) catch {};
+
+ // Disable all signal handlers.
+ sigprocmask(SIG.BLOCK, &linux.all_mask, null);
+
+ // Only one thread may proceed to the rest of abort().
+ if (!builtin.single_threaded) {
+ const global = struct {
+ var abort_entered: bool = false;
+ };
+ while (@cmpxchgWeak(bool, &global.abort_entered, false, true, .seq_cst, .seq_cst)) |_| {}
+ }
+
+ // Install default handler so that the tkill below will terminate.
+ const sigact = Sigaction{
+ .handler = .{ .handler = SIG.DFL },
+ .mask = empty_sigset,
+ .flags = 0,
+ };
+ sigaction(SIG.ABRT, &sigact, null) catch |err| switch (err) {
+ error.OperationNotSupported => unreachable,
+ };
+
+ _ = linux.tkill(linux.gettid(), SIG.ABRT);
+
+ const sigabrtmask: linux.sigset_t = [_]u32{0} ** 31 ++ [_]u32{1 << (SIG.ABRT - 1)};
+ sigprocmask(SIG.UNBLOCK, &sigabrtmask, null);
+
+ // Beyond this point should be unreachable.
+ @as(*allowzero volatile u8, @ptrFromInt(0)).* = 0;
+ raise(SIG.KILL) catch {};
+ exit(127); // Pid 1 might not be signalled in some containers.
+ }
+ switch (native_os) {
+ .uefi, .wasi, .emscripten, .cuda, .amdhsa => @trap(),
+ else => system.abort(),
+ }
+}
+
+pub const RaiseError = UnexpectedError;
+
+pub fn raise(sig: u8) RaiseError!void {
+ if (builtin.link_libc) {
+ switch (errno(system.raise(sig))) {
+ .SUCCESS => return,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+
+ if (native_os == .linux) {
+ var set: sigset_t = undefined;
+ // block application signals
+ sigprocmask(SIG.BLOCK, &linux.app_mask, &set);
+
+ const tid = linux.gettid();
+ const rc = linux.tkill(tid, sig);
+
+ // restore signal mask
+ sigprocmask(SIG.SETMASK, &set, null);
+
+ switch (errno(rc)) {
+ .SUCCESS => return,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+
+ @compileError("std.os.raise unimplemented for this target");
+}
+
+pub const KillError = error{ ProcessNotFound, PermissionDenied } || UnexpectedError;
+
+pub fn kill(pid: pid_t, sig: u8) KillError!void {
+ switch (errno(system.kill(pid, sig))) {
+ .SUCCESS => return,
+ .INVAL => unreachable, // invalid signal
+ .PERM => return error.PermissionDenied,
+ .SRCH => return error.ProcessNotFound,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+/// Exits all threads of the program with the specified status code.
+pub fn exit(status: u8) noreturn {
+ if (builtin.link_libc) {
+ std.c.exit(status);
+ }
+ if (native_os == .windows) {
+ windows.kernel32.ExitProcess(status);
+ }
+ if (native_os == .wasi) {
+ wasi.proc_exit(status);
+ }
+ if (native_os == .linux and !builtin.single_threaded) {
+ linux.exit_group(status);
+ }
+ if (native_os == .uefi) {
+ const uefi = std.os.uefi;
+ // exit() is only available if exitBootServices() has not been called yet.
+ // This call to exit should not fail, so we don't care about its return value.
+ if (uefi.system_table.boot_services) |bs| {
+ _ = bs.exit(uefi.handle, @enumFromInt(status), 0, null);
+ }
+ // If we can't exit, reboot the system instead.
+ uefi.system_table.runtime_services.resetSystem(.ResetCold, @enumFromInt(status), 0, null);
+ }
+ system.exit(status);
+}
+
+pub const ReadError = error{
+ InputOutput,
+ SystemResources,
+ IsDir,
+ OperationAborted,
+ BrokenPipe,
+ ConnectionResetByPeer,
+ ConnectionTimedOut,
+ NotOpenForReading,
+ SocketNotConnected,
+
+ /// This error occurs when no global event loop is configured,
+ /// and reading from the file descriptor would block.
+ WouldBlock,
+
+ /// In WASI, this error occurs when the file descriptor does
+ /// not hold the required rights to read from it.
+ AccessDenied,
+} || UnexpectedError;
+
+/// Returns the number of bytes that were read, which can be less than
+/// buf.len. If 0 bytes were read, that means EOF.
+/// If `fd` is opened in non blocking mode, the function will return error.WouldBlock
+/// when EAGAIN is received.
+///
+/// Linux has a limit on how many bytes may be transferred in one `read` call, which is `0x7ffff000`
+/// on both 64-bit and 32-bit systems. This is due to using a signed C int as the return value, as
+/// well as stuffing the errno codes into the last `4096` values. This is noted on the `read` man page.
+/// The limit on Darwin is `0x7fffffff`, trying to read more than that returns EINVAL.
+/// The corresponding POSIX limit is `maxInt(isize)`.
+pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
+ if (buf.len == 0) return 0;
+ if (native_os == .windows) {
+ return windows.ReadFile(fd, buf, null);
+ }
+ if (native_os == .wasi and !builtin.link_libc) {
+ const iovs = [1]iovec{iovec{
+ .iov_base = buf.ptr,
+ .iov_len = buf.len,
+ }};
+
+ var nread: usize = undefined;
+ switch (wasi.fd_read(fd, &iovs, iovs.len, &nread)) {
+ .SUCCESS => return nread,
+ .INTR => unreachable,
+ .INVAL => unreachable,
+ .FAULT => unreachable,
+ .AGAIN => unreachable,
+ .BADF => return error.NotOpenForReading, // Can be a race condition.
+ .IO => return error.InputOutput,
+ .ISDIR => return error.IsDir,
+ .NOBUFS => return error.SystemResources,
+ .NOMEM => return error.SystemResources,
+ .NOTCONN => return error.SocketNotConnected,
+ .CONNRESET => return error.ConnectionResetByPeer,
+ .TIMEDOUT => return error.ConnectionTimedOut,
+ .NOTCAPABLE => return error.AccessDenied,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+
+ // Prevents EINVAL.
+ const max_count = switch (native_os) {
+ .linux => 0x7ffff000,
+ .macos, .ios, .watchos, .tvos => maxInt(i32),
+ else => maxInt(isize),
+ };
+ while (true) {
+ const rc = system.read(fd, buf.ptr, @min(buf.len, max_count));
+ switch (errno(rc)) {
+ .SUCCESS => return @intCast(rc),
+ .INTR => continue,
+ .INVAL => unreachable,
+ .FAULT => unreachable,
+ .AGAIN => return error.WouldBlock,
+ .BADF => return error.NotOpenForReading, // Can be a race condition.
+ .IO => return error.InputOutput,
+ .ISDIR => return error.IsDir,
+ .NOBUFS => return error.SystemResources,
+ .NOMEM => return error.SystemResources,
+ .NOTCONN => return error.SocketNotConnected,
+ .CONNRESET => return error.ConnectionResetByPeer,
+ .TIMEDOUT => return error.ConnectionTimedOut,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+}
+
+/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.
+///
+/// For POSIX systems, if `fd` is opened in non blocking mode, the function will
+/// return error.WouldBlock when EAGAIN is received.
+/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
+/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
+///
+/// This operation is non-atomic on the following systems:
+/// * Windows
+/// On these systems, the read races with concurrent writes to the same file descriptor.
+///
+/// This function assumes that all vectors, including zero-length vectors, have
+/// a pointer within the address space of the application.
+pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {
+ if (native_os == .windows) {
+ // TODO improve this to use ReadFileScatter
+ if (iov.len == 0) return 0;
+ const first = iov[0];
+ return read(fd, first.iov_base[0..first.iov_len]);
+ }
+ if (native_os == .wasi and !builtin.link_libc) {
+ var nread: usize = undefined;
+ switch (wasi.fd_read(fd, iov.ptr, iov.len, &nread)) {
+ .SUCCESS => return nread,
+ .INTR => unreachable,
+ .INVAL => unreachable,
+ .FAULT => unreachable,
+ .AGAIN => unreachable, // currently not support in WASI
+ .BADF => return error.NotOpenForReading, // can be a race condition
+ .IO => return error.InputOutput,
+ .ISDIR => return error.IsDir,
+ .NOBUFS => return error.SystemResources,
+ .NOMEM => return error.SystemResources,
+ .NOTCONN => return error.SocketNotConnected,
+ .CONNRESET => return error.ConnectionResetByPeer,
+ .TIMEDOUT => return error.ConnectionTimedOut,
+ .NOTCAPABLE => return error.AccessDenied,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+
+ while (true) {
+ const rc = system.readv(fd, iov.ptr, @min(iov.len, IOV_MAX));
+ switch (errno(rc)) {
+ .SUCCESS => return @intCast(rc),
+ .INTR => continue,
+ .INVAL => unreachable,
+ .FAULT => unreachable,
+ .AGAIN => return error.WouldBlock,
+ .BADF => return error.NotOpenForReading, // can be a race condition
+ .IO => return error.InputOutput,
+ .ISDIR => return error.IsDir,
+ .NOBUFS => return error.SystemResources,
+ .NOMEM => return error.SystemResources,
+ .NOTCONN => return error.SocketNotConnected,
+ .CONNRESET => return error.ConnectionResetByPeer,
+ .TIMEDOUT => return error.ConnectionTimedOut,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+}
+
+pub const PReadError = ReadError || error{Unseekable};
+
+/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.
+///
+/// Retries when interrupted by a signal.
+///
+/// For POSIX systems, if `fd` is opened in non blocking mode, the function will
+/// return error.WouldBlock when EAGAIN is received.
+/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
+/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
+///
+/// Linux has a limit on how many bytes may be transferred in one `pread` call, which is `0x7ffff000`
+/// on both 64-bit and 32-bit systems. This is due to using a signed C int as the return value, as
+/// well as stuffing the errno codes into the last `4096` values. This is noted on the `read` man page.
+/// The limit on Darwin is `0x7fffffff`, trying to read more than that returns EINVAL.
+/// The corresponding POSIX limit is `maxInt(isize)`.
+pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize {
+ if (buf.len == 0) return 0;
+ if (native_os == .windows) {
+ return windows.ReadFile(fd, buf, offset);
+ }
+ if (native_os == .wasi and !builtin.link_libc) {
+ const iovs = [1]iovec{iovec{
+ .iov_base = buf.ptr,
+ .iov_len = buf.len,
+ }};
+
+ var nread: usize = undefined;
+ switch (wasi.fd_pread(fd, &iovs, iovs.len, offset, &nread)) {
+ .SUCCESS => return nread,
+ .INTR => unreachable,
+ .INVAL => unreachable,
+ .FAULT => unreachable,
+ .AGAIN => unreachable,
+ .BADF => return error.NotOpenForReading, // Can be a race condition.
+ .IO => return error.InputOutput,
+ .ISDIR => return error.IsDir,
+ .NOBUFS => return error.SystemResources,
+ .NOMEM => return error.SystemResources,
+ .NOTCONN => return error.SocketNotConnected,
+ .CONNRESET => return error.ConnectionResetByPeer,
+ .TIMEDOUT => return error.ConnectionTimedOut,
+ .NXIO => return error.Unseekable,
+ .SPIPE => return error.Unseekable,
+ .OVERFLOW => return error.Unseekable,
+ .NOTCAPABLE => return error.AccessDenied,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+
+ // Prevent EINVAL.
+ const max_count = switch (native_os) {
+ .linux => 0x7ffff000,
+ .macos, .ios, .watchos, .tvos => maxInt(i32),
+ else => maxInt(isize),
+ };
+
+ const pread_sym = if (lfs64_abi) system.pread64 else system.pread;
+ while (true) {
+ const rc = pread_sym(fd, buf.ptr, @min(buf.len, max_count), @bitCast(offset));
+ switch (errno(rc)) {
+ .SUCCESS => return @intCast(rc),
+ .INTR => continue,
+ .INVAL => unreachable,
+ .FAULT => unreachable,
+ .AGAIN => return error.WouldBlock,
+ .BADF => return error.NotOpenForReading, // Can be a race condition.
+ .IO => return error.InputOutput,
+ .ISDIR => return error.IsDir,
+ .NOBUFS => return error.SystemResources,
+ .NOMEM => return error.SystemResources,
+ .NOTCONN => return error.SocketNotConnected,
+ .CONNRESET => return error.ConnectionResetByPeer,
+ .TIMEDOUT => return error.ConnectionTimedOut,
+ .NXIO => return error.Unseekable,
+ .SPIPE => return error.Unseekable,
+ .OVERFLOW => return error.Unseekable,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+}
+
+pub const TruncateError = error{
+ FileTooBig,
+ InputOutput,
+ FileBusy,
+
+ /// In WASI, this error occurs when the file descriptor does
+ /// not hold the required rights to call `ftruncate` on it.
+ AccessDenied,
+} || UnexpectedError;
+
+pub fn ftruncate(fd: fd_t, length: u64) TruncateError!void {
+ if (native_os == .windows) {
+ var io_status_block: windows.IO_STATUS_BLOCK = undefined;
+ var eof_info = windows.FILE_END_OF_FILE_INFORMATION{
+ .EndOfFile = @bitCast(length),
+ };
+
+ const rc = windows.ntdll.NtSetInformationFile(
+ fd,
+ &io_status_block,
+ &eof_info,
+ @sizeOf(windows.FILE_END_OF_FILE_INFORMATION),
+ .FileEndOfFileInformation,
+ );
+
+ switch (rc) {
+ .SUCCESS => return,
+ .INVALID_HANDLE => unreachable, // Handle not open for writing
+ .ACCESS_DENIED => return error.AccessDenied,
+ else => return windows.unexpectedStatus(rc),
+ }
+ }
+ if (native_os == .wasi and !builtin.link_libc) {
+ switch (wasi.fd_filestat_set_size(fd, length)) {
+ .SUCCESS => return,
+ .INTR => unreachable,
+ .FBIG => return error.FileTooBig,
+ .IO => return error.InputOutput,
+ .PERM => return error.AccessDenied,
+ .TXTBSY => return error.FileBusy,
+ .BADF => unreachable, // Handle not open for writing
+ .INVAL => unreachable, // Handle not open for writing
+ .NOTCAPABLE => return error.AccessDenied,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+
+ const ftruncate_sym = if (lfs64_abi) system.ftruncate64 else system.ftruncate;
+ while (true) {
+ switch (errno(ftruncate_sym(fd, @bitCast(length)))) {
+ .SUCCESS => return,
+ .INTR => continue,
+ .FBIG => return error.FileTooBig,
+ .IO => return error.InputOutput,
+ .PERM => return error.AccessDenied,
+ .TXTBSY => return error.FileBusy,
+ .BADF => unreachable, // Handle not open for writing
+ .INVAL => unreachable, // Handle not open for writing
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+}
+
+/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.
+///
+/// Retries when interrupted by a signal.
+///
+/// For POSIX systems, if `fd` is opened in non blocking mode, the function will
+/// return error.WouldBlock when EAGAIN is received.
+/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
+/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
+///
+/// This operation is non-atomic on the following systems:
+/// * Darwin
+/// * Windows
+/// On these systems, the read races with concurrent writes to the same file descriptor.
+pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) PReadError!usize {
+ const have_pread_but_not_preadv = switch (native_os) {
+ .windows, .macos, .ios, .watchos, .tvos, .haiku => true,
+ else => false,
+ };
+ if (have_pread_but_not_preadv) {
+ // We could loop here; but proper usage of `preadv` must handle partial reads anyway.
+ // So we simply read into the first vector only.
+ if (iov.len == 0) return 0;
+ const first = iov[0];
+ return pread(fd, first.iov_base[0..first.iov_len], offset);
+ }
+ if (native_os == .wasi and !builtin.link_libc) {
+ var nread: usize = undefined;
+ switch (wasi.fd_pread(fd, iov.ptr, iov.len, offset, &nread)) {
+ .SUCCESS => return nread,
+ .INTR => unreachable,
+ .INVAL => unreachable,
+ .FAULT => unreachable,
+ .AGAIN => unreachable,
+ .BADF => return error.NotOpenForReading, // can be a race condition
+ .IO => return error.InputOutput,
+ .ISDIR => return error.IsDir,
+ .NOBUFS => return error.SystemResources,
+ .NOMEM => return error.SystemResources,
+ .NOTCONN => return error.SocketNotConnected,
+ .CONNRESET => return error.ConnectionResetByPeer,
+ .TIMEDOUT => return error.ConnectionTimedOut,
+ .NXIO => return error.Unseekable,
+ .SPIPE => return error.Unseekable,
+ .OVERFLOW => return error.Unseekable,
+ .NOTCAPABLE => return error.AccessDenied,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+
+ const preadv_sym = if (lfs64_abi) system.preadv64 else system.preadv;
+ while (true) {
+ const rc = preadv_sym(fd, iov.ptr, @min(iov.len, IOV_MAX), @bitCast(offset));
+ switch (errno(rc)) {
+ .SUCCESS => return @bitCast(rc),
+ .INTR => continue,
+ .INVAL => unreachable,
+ .FAULT => unreachable,
+ .AGAIN => return error.WouldBlock,
+ .BADF => return error.NotOpenForReading, // can be a race condition
+ .IO => return error.InputOutput,
+ .ISDIR => return error.IsDir,
+ .NOBUFS => return error.SystemResources,
+ .NOMEM => return error.SystemResources,
+ .NOTCONN => return error.SocketNotConnected,
+ .CONNRESET => return error.ConnectionResetByPeer,
+ .TIMEDOUT => return error.ConnectionTimedOut,
+ .NXIO => return error.Unseekable,
+ .SPIPE => return error.Unseekable,
+ .OVERFLOW => return error.Unseekable,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+}
+
+pub const WriteError = error{
+ DiskQuota,
+ FileTooBig,
+ InputOutput,
+ NoSpaceLeft,
+ DeviceBusy,
+ InvalidArgument,
+
+ /// In WASI, this error may occur when the file descriptor does
+ /// not hold the required rights to write to it.
+ AccessDenied,
+ BrokenPipe,
+ SystemResources,
+ OperationAborted,
+ NotOpenForWriting,
+
+ /// The process cannot access the file because another process has locked
+ /// a portion of the file. Windows-only.
+ LockViolation,
+
+ /// This error occurs when no global event loop is configured,
+ /// and reading from the file descriptor would block.
+ WouldBlock,
+
+ /// Connection reset by peer.
+ ConnectionResetByPeer,
+} || UnexpectedError;
+
+/// Write to a file descriptor.
+/// Retries when interrupted by a signal.
+/// Returns the number of bytes written. If nonzero bytes were supplied, this will be nonzero.
+///
+/// Note that a successful write() may transfer fewer than count bytes. Such partial writes can
+/// occur for various reasons; for example, because there was insufficient space on the disk
+/// device to write all of the requested bytes, or because a blocked write() to a socket, pipe, or
+/// similar was interrupted by a signal handler after it had transferred some, but before it had
+/// transferred all of the requested bytes. In the event of a partial write, the caller can make
+/// another write() call to transfer the remaining bytes. The subsequent call will either
+/// transfer further bytes or may result in an error (e.g., if the disk is now full).
+///
+/// For POSIX systems, if `fd` is opened in non blocking mode, the function will
+/// return error.WouldBlock when EAGAIN is received.
+/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
+/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
+///
+/// Linux has a limit on how many bytes may be transferred in one `write` call, which is `0x7ffff000`
+/// on both 64-bit and 32-bit systems. This is due to using a signed C int as the return value, as
+/// well as stuffing the errno codes into the last `4096` values. This is noted on the `write` man page.
+/// The limit on Darwin is `0x7fffffff`, trying to read more than that returns EINVAL.
+/// The corresponding POSIX limit is `maxInt(isize)`.
+pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize {
+ if (bytes.len == 0) return 0;
+ if (native_os == .windows) {
+ return windows.WriteFile(fd, bytes, null);
+ }
+
+ if (native_os == .wasi and !builtin.link_libc) {
+ const ciovs = [_]iovec_const{iovec_const{
+ .iov_base = bytes.ptr,
+ .iov_len = bytes.len,
+ }};
+ var nwritten: usize = undefined;
+ switch (wasi.fd_write(fd, &ciovs, ciovs.len, &nwritten)) {
+ .SUCCESS => return nwritten,
+ .INTR => unreachable,
+ .INVAL => unreachable,
+ .FAULT => unreachable,
+ .AGAIN => unreachable,
+ .BADF => return error.NotOpenForWriting, // can be a race condition.
+ .DESTADDRREQ => unreachable, // `connect` was never called.
+ .DQUOT => return error.DiskQuota,
+ .FBIG => return error.FileTooBig,
+ .IO => return error.InputOutput,
+ .NOSPC => return error.NoSpaceLeft,
+ .PERM => return error.AccessDenied,
+ .PIPE => return error.BrokenPipe,
+ .NOTCAPABLE => return error.AccessDenied,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+
+ const max_count = switch (native_os) {
+ .linux => 0x7ffff000,
+ .macos, .ios, .watchos, .tvos => maxInt(i32),
+ else => maxInt(isize),
+ };
+ while (true) {
+ const rc = system.write(fd, bytes.ptr, @min(bytes.len, max_count));
+ switch (errno(rc)) {
+ .SUCCESS => return @intCast(rc),
+ .INTR => continue,
+ .INVAL => return error.InvalidArgument,
+ .FAULT => unreachable,
+ .AGAIN => return error.WouldBlock,
+ .BADF => return error.NotOpenForWriting, // can be a race condition.
+ .DESTADDRREQ => unreachable, // `connect` was never called.
+ .DQUOT => return error.DiskQuota,
+ .FBIG => return error.FileTooBig,
+ .IO => return error.InputOutput,
+ .NOSPC => return error.NoSpaceLeft,
+ .PERM => return error.AccessDenied,
+ .PIPE => return error.BrokenPipe,
+ .CONNRESET => return error.ConnectionResetByPeer,
+ .BUSY => return error.DeviceBusy,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+}
+
+/// Write multiple buffers to a file descriptor.
+/// Retries when interrupted by a signal.
+/// Returns the number of bytes written. If nonzero bytes were supplied, this will be nonzero.
+///
+/// Note that a successful write() may transfer fewer bytes than supplied. Such partial writes can
+/// occur for various reasons; for example, because there was insufficient space on the disk
+/// device to write all of the requested bytes, or because a blocked write() to a socket, pipe, or
+/// similar was interrupted by a signal handler after it had transferred some, but before it had
+/// transferred all of the requested bytes. In the event of a partial write, the caller can make
+/// another write() call to transfer the remaining bytes. The subsequent call will either
+/// transfer further bytes or may result in an error (e.g., if the disk is now full).
+///
+/// For POSIX systems, if `fd` is opened in non blocking mode, the function will
+/// return error.WouldBlock when EAGAIN is received.
+/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
+/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
+///
+/// If `iov.len` is larger than `IOV_MAX`, a partial write will occur.
+///
+/// This function assumes that all vectors, including zero-length vectors, have
+/// a pointer within the address space of the application.
+pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!usize {
+ if (native_os == .windows) {
+ // TODO improve this to use WriteFileScatter
+ if (iov.len == 0) return 0;
+ const first = iov[0];
+ return write(fd, first.iov_base[0..first.iov_len]);
+ }
+ if (native_os == .wasi and !builtin.link_libc) {
+ var nwritten: usize = undefined;
+ switch (wasi.fd_write(fd, iov.ptr, iov.len, &nwritten)) {
+ .SUCCESS => return nwritten,
+ .INTR => unreachable,
+ .INVAL => unreachable,
+ .FAULT => unreachable,
+ .AGAIN => unreachable,
+ .BADF => return error.NotOpenForWriting, // can be a race condition.
+ .DESTADDRREQ => unreachable, // `connect` was never called.
+ .DQUOT => return error.DiskQuota,
+ .FBIG => return error.FileTooBig,
+ .IO => return error.InputOutput,
+ .NOSPC => return error.NoSpaceLeft,
+ .PERM => return error.AccessDenied,
+ .PIPE => return error.BrokenPipe,
+ .NOTCAPABLE => return error.AccessDenied,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+
+ while (true) {
+ const rc = system.writev(fd, iov.ptr, @min(iov.len, IOV_MAX));
+ switch (errno(rc)) {
+ .SUCCESS => return @intCast(rc),
+ .INTR => continue,
+ .INVAL => return error.InvalidArgument,
+ .FAULT => unreachable,
+ .AGAIN => return error.WouldBlock,
+ .BADF => return error.NotOpenForWriting, // Can be a race condition.
+ .DESTADDRREQ => unreachable, // `connect` was never called.
+ .DQUOT => return error.DiskQuota,
+ .FBIG => return error.FileTooBig,
+ .IO => return error.InputOutput,
+ .NOSPC => return error.NoSpaceLeft,
+ .PERM => return error.AccessDenied,
+ .PIPE => return error.BrokenPipe,
+ .CONNRESET => return error.ConnectionResetByPeer,
+ .BUSY => return error.DeviceBusy,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+}
+
+pub const PWriteError = WriteError || error{Unseekable};
+
+/// Write to a file descriptor, with a position offset.
+/// Retries when interrupted by a signal.
+/// Returns the number of bytes written. If nonzero bytes were supplied, this will be nonzero.
+///
+/// Note that a successful write() may transfer fewer bytes than supplied. Such partial writes can
+/// occur for various reasons; for example, because there was insufficient space on the disk
+/// device to write all of the requested bytes, or because a blocked write() to a socket, pipe, or
+/// similar was interrupted by a signal handler after it had transferred some, but before it had
+/// transferred all of the requested bytes. In the event of a partial write, the caller can make
+/// another write() call to transfer the remaining bytes. The subsequent call will either
+/// transfer further bytes or may result in an error (e.g., if the disk is now full).
+///
+/// For POSIX systems, if `fd` is opened in non blocking mode, the function will
+/// return error.WouldBlock when EAGAIN is received.
+/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
+/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
+///
+/// Linux has a limit on how many bytes may be transferred in one `pwrite` call, which is `0x7ffff000`
+/// on both 64-bit and 32-bit systems. This is due to using a signed C int as the return value, as
+/// well as stuffing the errno codes into the last `4096` values. This is noted on the `write` man page.
+/// The limit on Darwin is `0x7fffffff`, trying to write more than that returns EINVAL.
+/// The corresponding POSIX limit is `maxInt(isize)`.
+pub fn pwrite(fd: fd_t, bytes: []const u8, offset: u64) PWriteError!usize {
+ if (bytes.len == 0) return 0;
+ if (native_os == .windows) {
+ return windows.WriteFile(fd, bytes, offset);
+ }
+ if (native_os == .wasi and !builtin.link_libc) {
+ const ciovs = [1]iovec_const{iovec_const{
+ .iov_base = bytes.ptr,
+ .iov_len = bytes.len,
+ }};
+
+ var nwritten: usize = undefined;
+ switch (wasi.fd_pwrite(fd, &ciovs, ciovs.len, offset, &nwritten)) {
+ .SUCCESS => return nwritten,
+ .INTR => unreachable,
+ .INVAL => unreachable,
+ .FAULT => unreachable,
+ .AGAIN => unreachable,
+ .BADF => return error.NotOpenForWriting, // can be a race condition.
+ .DESTADDRREQ => unreachable, // `connect` was never called.
+ .DQUOT => return error.DiskQuota,
+ .FBIG => return error.FileTooBig,
+ .IO => return error.InputOutput,
+ .NOSPC => return error.NoSpaceLeft,
+ .PERM => return error.AccessDenied,
+ .PIPE => return error.BrokenPipe,
+ .NXIO => return error.Unseekable,
+ .SPIPE => return error.Unseekable,
+ .OVERFLOW => return error.Unseekable,
+ .NOTCAPABLE => return error.AccessDenied,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+
+ // Prevent EINVAL.
+ const max_count = switch (native_os) {
+ .linux => 0x7ffff000,
+ .macos, .ios, .watchos, .tvos => maxInt(i32),
+ else => maxInt(isize),
+ };
+
+ const pwrite_sym = if (lfs64_abi) system.pwrite64 else system.pwrite;
+ while (true) {
+ const rc = pwrite_sym(fd, bytes.ptr, @min(bytes.len, max_count), @bitCast(offset));
+ switch (errno(rc)) {
+ .SUCCESS => return @intCast(rc),
+ .INTR => continue,
+ .INVAL => return error.InvalidArgument,
+ .FAULT => unreachable,
+ .AGAIN => return error.WouldBlock,
+ .BADF => return error.NotOpenForWriting, // Can be a race condition.
+ .DESTADDRREQ => unreachable, // `connect` was never called.
+ .DQUOT => return error.DiskQuota,
+ .FBIG => return error.FileTooBig,
+ .IO => return error.InputOutput,
+ .NOSPC => return error.NoSpaceLeft,
+ .PERM => return error.AccessDenied,
+ .PIPE => return error.BrokenPipe,
+ .NXIO => return error.Unseekable,
+ .SPIPE => return error.Unseekable,
+ .OVERFLOW => return error.Unseekable,
+ .BUSY => return error.DeviceBusy,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+}
+
+/// Write multiple buffers to a file descriptor, with a position offset.
+/// Retries when interrupted by a signal.
+/// Returns the number of bytes written. If nonzero bytes were supplied, this will be nonzero.
+///
+/// Note that a successful write() may transfer fewer than count bytes. Such partial writes can
+/// occur for various reasons; for example, because there was insufficient space on the disk
+/// device to write all of the requested bytes, or because a blocked write() to a socket, pipe, or
+/// similar was interrupted by a signal handler after it had transferred some, but before it had
+/// transferred all of the requested bytes. In the event of a partial write, the caller can make
+/// another write() call to transfer the remaining bytes. The subsequent call will either
+/// transfer further bytes or may result in an error (e.g., if the disk is now full).
+///
+/// If `fd` is opened in non blocking mode, the function will
+/// return error.WouldBlock when EAGAIN is received.
+///
+/// The following systems do not have this syscall, and will return partial writes if more than one
+/// vector is provided:
+/// * Darwin
+/// * Windows
+///
+/// If `iov.len` is larger than `IOV_MAX`, a partial write will occur.
+pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) PWriteError!usize {
+ const have_pwrite_but_not_pwritev = switch (native_os) {
+ .windows, .macos, .ios, .watchos, .tvos, .haiku => true,
+ else => false,
+ };
+
+ if (have_pwrite_but_not_pwritev) {
+ // We could loop here; but proper usage of `pwritev` must handle partial writes anyway.
+ // So we simply write the first vector only.
+ if (iov.len == 0) return 0;
+ const first = iov[0];
+ return pwrite(fd, first.iov_base[0..first.iov_len], offset);
+ }
+ if (native_os == .wasi and !builtin.link_libc) {
+ var nwritten: usize = undefined;
+ switch (wasi.fd_pwrite(fd, iov.ptr, iov.len, offset, &nwritten)) {
+ .SUCCESS => return nwritten,
+ .INTR => unreachable,
+ .INVAL => unreachable,
+ .FAULT => unreachable,
+ .AGAIN => unreachable,
+ .BADF => return error.NotOpenForWriting, // Can be a race condition.
+ .DESTADDRREQ => unreachable, // `connect` was never called.
+ .DQUOT => return error.DiskQuota,
+ .FBIG => return error.FileTooBig,
+ .IO => return error.InputOutput,
+ .NOSPC => return error.NoSpaceLeft,
+ .PERM => return error.AccessDenied,
+ .PIPE => return error.BrokenPipe,
+ .NXIO => return error.Unseekable,
+ .SPIPE => return error.Unseekable,
+ .OVERFLOW => return error.Unseekable,
+ .NOTCAPABLE => return error.AccessDenied,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+
+ const pwritev_sym = if (lfs64_abi) system.pwritev64 else system.pwritev;
+ while (true) {
+ const rc = pwritev_sym(fd, iov.ptr, @min(iov.len, IOV_MAX), @bitCast(offset));
+ switch (errno(rc)) {
+ .SUCCESS => return @intCast(rc),
+ .INTR => continue,
+ .INVAL => return error.InvalidArgument,
+ .FAULT => unreachable,
+ .AGAIN => return error.WouldBlock,
+ .BADF => return error.NotOpenForWriting, // Can be a race condition.
+ .DESTADDRREQ => unreachable, // `connect` was never called.
+ .DQUOT => return error.DiskQuota,
+ .FBIG => return error.FileTooBig,
+ .IO => return error.InputOutput,
+ .NOSPC => return error.NoSpaceLeft,
+ .PERM => return error.AccessDenied,
+ .PIPE => return error.BrokenPipe,
+ .NXIO => return error.Unseekable,
+ .SPIPE => return error.Unseekable,
+ .OVERFLOW => return error.Unseekable,
+ .BUSY => return error.DeviceBusy,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+}
+
+pub const OpenError = error{
+ /// In WASI, this error may occur when the file descriptor does
+ /// not hold the required rights to open a new resource relative to it.
+ AccessDenied,
+ SymLinkLoop,
+ ProcessFdQuotaExceeded,
+ SystemFdQuotaExceeded,
+ NoDevice,
+ FileNotFound,
+
+ /// The path exceeded `max_path_bytes` bytes.
+ NameTooLong,
+
+ /// Insufficient kernel memory was available, or
+ /// the named file is a FIFO and per-user hard limit on
+ /// memory allocation for pipes has been reached.
+ SystemResources,
+
+ /// The file is too large to be opened. This error is unreachable
+ /// for 64-bit targets, as well as when opening directories.
+ FileTooBig,
+
+ /// The path refers to directory but the `DIRECTORY` flag was not provided.
+ IsDir,
+
+ /// A new path cannot be created because the device has no room for the new file.
+ /// This error is only reachable when the `CREAT` flag is provided.
+ NoSpaceLeft,
+
+ /// A component used as a directory in the path was not, in fact, a directory, or
+ /// `DIRECTORY` was specified and the path was not a directory.
+ NotDir,
+
+ /// The path already exists and the `CREAT` and `EXCL` flags were provided.
+ PathAlreadyExists,
+ DeviceBusy,
+
+ /// The underlying filesystem does not support file locks
+ FileLocksNotSupported,
+
+ /// Path contains characters that are disallowed by the underlying filesystem.
+ BadPathName,
+
+ /// WASI-only; file paths must be valid UTF-8.
+ InvalidUtf8,
+
+ /// Windows-only; file paths provided by the user must be valid WTF-8.
+ /// https://simonsapin.github.io/wtf-8/
+ InvalidWtf8,
+
+ /// On Windows, `\\server` or `\\server\share` was not found.
+ NetworkNotFound,
+
+ /// One of these three things:
+ /// * pathname refers to an executable image which is currently being
+ /// executed and write access was requested.
+ /// * pathname refers to a file that is currently in use as a swap
+ /// file, and the O_TRUNC flag was specified.
+ /// * pathname refers to a file that is currently being read by the
+ /// kernel (e.g., for module/firmware loading), and write access was
+ /// requested.
+ FileBusy,
+
+ WouldBlock,
+} || UnexpectedError;
+
+/// Open and possibly create a file. Keeps trying if it gets interrupted.
+/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `file_path` should be encoded as valid UTF-8.
+/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
+/// See also `openZ`.
+pub fn open(file_path: []const u8, flags: O, perm: mode_t) OpenError!fd_t {
+ if (native_os == .windows) {
+ @compileError("Windows does not support POSIX; use Windows-specific API or cross-platform std.fs API");
+ } else if (native_os == .wasi and !builtin.link_libc) {
+ return openat(AT.FDCWD, file_path, flags, perm);
+ }
+ const file_path_c = try toPosixPath(file_path);
+ return openZ(&file_path_c, flags, perm);
+}
+
+/// Open and possibly create a file. Keeps trying if it gets interrupted.
+/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `file_path` should be encoded as valid UTF-8.
+/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
+/// See also `open`.
+pub fn openZ(file_path: [*:0]const u8, flags: O, perm: mode_t) OpenError!fd_t {
+ if (native_os == .windows) {
+ @compileError("Windows does not support POSIX; use Windows-specific API or cross-platform std.fs API");
+ } else if (native_os == .wasi and !builtin.link_libc) {
+ return open(mem.sliceTo(file_path, 0), flags, perm);
+ }
+
+ const open_sym = if (lfs64_abi) system.open64 else system.open;
+ while (true) {
+ const rc = open_sym(file_path, flags, perm);
+ switch (errno(rc)) {
+ .SUCCESS => return @intCast(rc),
+ .INTR => continue,
+
+ .FAULT => unreachable,
+ .INVAL => unreachable,
+ .ACCES => return error.AccessDenied,
+ .FBIG => return error.FileTooBig,
+ .OVERFLOW => return error.FileTooBig,
+ .ISDIR => return error.IsDir,
+ .LOOP => return error.SymLinkLoop,
+ .MFILE => return error.ProcessFdQuotaExceeded,
+ .NAMETOOLONG => return error.NameTooLong,
+ .NFILE => return error.SystemFdQuotaExceeded,
+ .NODEV => return error.NoDevice,
+ .NOENT => return error.FileNotFound,
+ .NOMEM => return error.SystemResources,
+ .NOSPC => return error.NoSpaceLeft,
+ .NOTDIR => return error.NotDir,
+ .PERM => return error.AccessDenied,
+ .EXIST => return error.PathAlreadyExists,
+ .BUSY => return error.DeviceBusy,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+}
+
+/// Open and possibly create a file. Keeps trying if it gets interrupted.
+/// `file_path` is relative to the open directory handle `dir_fd`.
+/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `file_path` should be encoded as valid UTF-8.
+/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
+/// See also `openatZ`.
+pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: O, mode: mode_t) OpenError!fd_t {
+ if (native_os == .windows) {
+ @compileError("Windows does not support POSIX; use Windows-specific API or cross-platform std.fs API");
+ } else if (native_os == .wasi and !builtin.link_libc) {
+ // `mode` is ignored on WASI, which does not support unix-style file permissions
+ const opts = try openOptionsFromFlagsWasi(flags);
+ const fd = try openatWasi(
+ dir_fd,
+ file_path,
+ opts.lookup_flags,
+ opts.oflags,
+ opts.fs_flags,
+ opts.fs_rights_base,
+ opts.fs_rights_inheriting,
+ );
+ errdefer close(fd);
+
+ if (flags.write) {
+ const info = try fstat_wasi(fd);
+ if (info.filetype == .DIRECTORY)
+ return error.IsDir;
+ }
+
+ return fd;
+ }
+ const file_path_c = try toPosixPath(file_path);
+ return openatZ(dir_fd, &file_path_c, flags, mode);
+}
+
+/// Open and possibly create a file in WASI.
+pub fn openatWasi(
+ dir_fd: fd_t,
+ file_path: []const u8,
+ lookup_flags: wasi.lookupflags_t,
+ oflags: wasi.oflags_t,
+ fdflags: wasi.fdflags_t,
+ base: wasi.rights_t,
+ inheriting: wasi.rights_t,
+) OpenError!fd_t {
+ while (true) {
+ var fd: fd_t = undefined;
+ switch (wasi.path_open(dir_fd, lookup_flags, file_path.ptr, file_path.len, oflags, base, inheriting, fdflags, &fd)) {
+ .SUCCESS => return fd,
+ .INTR => continue,
+
+ .FAULT => unreachable,
+ .INVAL => unreachable,
+ .BADF => unreachable,
+ .ACCES => return error.AccessDenied,
+ .FBIG => return error.FileTooBig,
+ .OVERFLOW => return error.FileTooBig,
+ .ISDIR => return error.IsDir,
+ .LOOP => return error.SymLinkLoop,
+ .MFILE => return error.ProcessFdQuotaExceeded,
+ .NAMETOOLONG => return error.NameTooLong,
+ .NFILE => return error.SystemFdQuotaExceeded,
+ .NODEV => return error.NoDevice,
+ .NOENT => return error.FileNotFound,
+ .NOMEM => return error.SystemResources,
+ .NOSPC => return error.NoSpaceLeft,
+ .NOTDIR => return error.NotDir,
+ .PERM => return error.AccessDenied,
+ .EXIST => return error.PathAlreadyExists,
+ .BUSY => return error.DeviceBusy,
+ .NOTCAPABLE => return error.AccessDenied,
+ .ILSEQ => return error.InvalidUtf8,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+}
+
+/// A struct to contain all lookup/rights flags accepted by `wasi.path_open`
+const WasiOpenOptions = struct {
+ oflags: wasi.oflags_t,
+ lookup_flags: wasi.lookupflags_t,
+ fs_rights_base: wasi.rights_t,
+ fs_rights_inheriting: wasi.rights_t,
+ fs_flags: wasi.fdflags_t,
+};
+
+/// Compute rights + flags corresponding to the provided POSIX access mode.
+fn openOptionsFromFlagsWasi(oflag: O) OpenError!WasiOpenOptions {
+ const w = std.os.wasi;
+
+ // Next, calculate the read/write rights to request, depending on the
+ // provided POSIX access mode
+ var rights: w.rights_t = .{};
+ if (oflag.read) {
+ rights.FD_READ = true;
+ rights.FD_READDIR = true;
+ }
+ if (oflag.write) {
+ rights.FD_DATASYNC = true;
+ rights.FD_WRITE = true;
+ rights.FD_ALLOCATE = true;
+ rights.FD_FILESTAT_SET_SIZE = true;
+ }
+
+ // https://github.com/ziglang/zig/issues/18882
+ const flag_bits: u32 = @bitCast(oflag);
+ const oflags_int: u16 = @as(u12, @truncate(flag_bits >> 12));
+ const fs_flags_int: u16 = @as(u12, @truncate(flag_bits));
+
+ return .{
+ // https://github.com/ziglang/zig/issues/18882
+ .oflags = @bitCast(oflags_int),
+ .lookup_flags = .{
+ .SYMLINK_FOLLOW = !oflag.NOFOLLOW,
+ },
+ .fs_rights_base = rights,
+ .fs_rights_inheriting = rights,
+ // https://github.com/ziglang/zig/issues/18882
+ .fs_flags = @bitCast(fs_flags_int),
+ };
+}
+
+/// Open and possibly create a file. Keeps trying if it gets interrupted.
+/// `file_path` is relative to the open directory handle `dir_fd`.
+/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `file_path` should be encoded as valid UTF-8.
+/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
+/// See also `openat`.
+pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: O, mode: mode_t) OpenError!fd_t {
+ if (native_os == .windows) {
+ @compileError("Windows does not support POSIX; use Windows-specific API or cross-platform std.fs API");
+ } else if (native_os == .wasi and !builtin.link_libc) {
+ return openat(dir_fd, mem.sliceTo(file_path, 0), flags, mode);
+ }
+
+ const openat_sym = if (lfs64_abi) system.openat64 else system.openat;
+ while (true) {
+ const rc = openat_sym(dir_fd, file_path, flags, mode);
+ switch (errno(rc)) {
+ .SUCCESS => return @intCast(rc),
+ .INTR => continue,
+
+ .FAULT => unreachable,
+ .INVAL => unreachable,
+ .BADF => unreachable,
+ .ACCES => return error.AccessDenied,
+ .FBIG => return error.FileTooBig,
+ .OVERFLOW => return error.FileTooBig,
+ .ISDIR => return error.IsDir,
+ .LOOP => return error.SymLinkLoop,
+ .MFILE => return error.ProcessFdQuotaExceeded,
+ .NAMETOOLONG => return error.NameTooLong,
+ .NFILE => return error.SystemFdQuotaExceeded,
+ .NODEV => return error.NoDevice,
+ .NOENT => return error.FileNotFound,
+ .NOMEM => return error.SystemResources,
+ .NOSPC => return error.NoSpaceLeft,
+ .NOTDIR => return error.NotDir,
+ .PERM => return error.AccessDenied,
+ .EXIST => return error.PathAlreadyExists,
+ .BUSY => return error.DeviceBusy,
+ .OPNOTSUPP => return error.FileLocksNotSupported,
+ .AGAIN => return error.WouldBlock,
+ .TXTBSY => return error.FileBusy,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+}
+
+pub fn dup(old_fd: fd_t) !fd_t {
+ const rc = system.dup(old_fd);
+ return switch (errno(rc)) {
+ .SUCCESS => return @intCast(rc),
+ .MFILE => error.ProcessFdQuotaExceeded,
+ .BADF => unreachable, // invalid file descriptor
+ else => |err| return unexpectedErrno(err),
+ };
+}
+
+pub fn dup2(old_fd: fd_t, new_fd: fd_t) !void {
+ while (true) {
+ switch (errno(system.dup2(old_fd, new_fd))) {
+ .SUCCESS => return,
+ .BUSY, .INTR => continue,
+ .MFILE => return error.ProcessFdQuotaExceeded,
+ .INVAL => unreachable, // invalid parameters passed to dup2
+ .BADF => unreachable, // invalid file descriptor
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+}
+
+pub const ExecveError = error{
+ SystemResources,
+ AccessDenied,
+ InvalidExe,
+ FileSystem,
+ IsDir,
+ FileNotFound,
+ NotDir,
+ FileBusy,
+ ProcessFdQuotaExceeded,
+ SystemFdQuotaExceeded,
+ NameTooLong,
+} || UnexpectedError;
+
+/// This function ignores PATH environment variable. See `execvpeZ` for that.
+pub fn execveZ(
+ path: [*:0]const u8,
+ child_argv: [*:null]const ?[*:0]const u8,
+ envp: [*:null]const ?[*:0]const u8,
+) ExecveError {
+ switch (errno(system.execve(path, child_argv, envp))) {
+ .SUCCESS => unreachable,
+ .FAULT => unreachable,
+ .@"2BIG" => return error.SystemResources,
+ .MFILE => return error.ProcessFdQuotaExceeded,
+ .NAMETOOLONG => return error.NameTooLong,
+ .NFILE => return error.SystemFdQuotaExceeded,
+ .NOMEM => return error.SystemResources,
+ .ACCES => return error.AccessDenied,
+ .PERM => return error.AccessDenied,
+ .INVAL => return error.InvalidExe,
+ .NOEXEC => return error.InvalidExe,
+ .IO => return error.FileSystem,
+ .LOOP => return error.FileSystem,
+ .ISDIR => return error.IsDir,
+ .NOENT => return error.FileNotFound,
+ .NOTDIR => return error.NotDir,
+ .TXTBSY => return error.FileBusy,
+ else => |err| switch (native_os) {
+ .macos, .ios, .tvos, .watchos => switch (err) {
+ .BADEXEC => return error.InvalidExe,
+ .BADARCH => return error.InvalidExe,
+ else => return unexpectedErrno(err),
+ },
+ .linux => switch (err) {
+ .LIBBAD => return error.InvalidExe,
+ else => return unexpectedErrno(err),
+ },
+ else => return unexpectedErrno(err),
+ },
+ }
+}
+
+pub const Arg0Expand = enum {
+ expand,
+ no_expand,
+};
+
+/// Like `execvpeZ` except if `arg0_expand` is `.expand`, then `argv` is mutable,
+/// and `argv[0]` is expanded to be the same absolute path that is passed to the execve syscall.
+/// If this function returns with an error, `argv[0]` will be restored to the value it was when it was passed in.
+pub fn execvpeZ_expandArg0(
+ comptime arg0_expand: Arg0Expand,
+ file: [*:0]const u8,
+ child_argv: switch (arg0_expand) {
+ .expand => [*:null]?[*:0]const u8,
+ .no_expand => [*:null]const ?[*:0]const u8,
+ },
+ envp: [*:null]const ?[*:0]const u8,
+) ExecveError {
+ const file_slice = mem.sliceTo(file, 0);
+ if (mem.indexOfScalar(u8, file_slice, '/') != null) return execveZ(file, child_argv, envp);
+
+ const PATH = getenvZ("PATH") orelse "/usr/local/bin:/bin/:/usr/bin";
+ // Use of PATH_MAX here is valid as the path_buf will be passed
+ // directly to the operating system in execveZ.
+ var path_buf: [PATH_MAX]u8 = undefined;
+ var it = mem.tokenizeScalar(u8, PATH, ':');
+ var seen_eacces = false;
+ var err: ExecveError = error.FileNotFound;
+
+ // In case of expanding arg0 we must put it back if we return with an error.
+ const prev_arg0 = child_argv[0];
+ defer switch (arg0_expand) {
+ .expand => child_argv[0] = prev_arg0,
+ .no_expand => {},
+ };
+
+ while (it.next()) |search_path| {
+ const path_len = search_path.len + file_slice.len + 1;
+ if (path_buf.len < path_len + 1) return error.NameTooLong;
+ @memcpy(path_buf[0..search_path.len], search_path);
+ path_buf[search_path.len] = '/';
+ @memcpy(path_buf[search_path.len + 1 ..][0..file_slice.len], file_slice);
+ path_buf[path_len] = 0;
+ const full_path = path_buf[0..path_len :0].ptr;
+ switch (arg0_expand) {
+ .expand => child_argv[0] = full_path,
+ .no_expand => {},
+ }
+ err = execveZ(full_path, child_argv, envp);
+ switch (err) {
+ error.AccessDenied => seen_eacces = true,
+ error.FileNotFound, error.NotDir => {},
+ else => |e| return e,
+ }
+ }
+ if (seen_eacces) return error.AccessDenied;
+ return err;
+}
+
+/// This function also uses the PATH environment variable to get the full path to the executable.
+/// If `file` is an absolute path, this is the same as `execveZ`.
+pub fn execvpeZ(
+ file: [*:0]const u8,
+ argv_ptr: [*:null]const ?[*:0]const u8,
+ envp: [*:null]const ?[*:0]const u8,
+) ExecveError {
+ return execvpeZ_expandArg0(.no_expand, file, argv_ptr, envp);
+}
+
+/// Get an environment variable.
+/// See also `getenvZ`.
+pub fn getenv(key: []const u8) ?[:0]const u8 {
+ if (native_os == .windows) {
+ @compileError("std.os.getenv is unavailable for Windows because environment strings are in WTF-16 format. See std.process.getEnvVarOwned for a cross-platform API or std.process.getenvW for a Windows-specific API.");
+ }
+ if (builtin.link_libc) {
+ var ptr = std.c.environ;
+ while (ptr[0]) |line| : (ptr += 1) {
+ var line_i: usize = 0;
+ while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {}
+ const this_key = line[0..line_i];
+
+ if (!mem.eql(u8, this_key, key)) continue;
+
+ return mem.sliceTo(line + line_i + 1, 0);
+ }
+ return null;
+ }
+ if (native_os == .wasi) {
+ @compileError("std.os.getenv is unavailable for WASI. See std.process.getEnvMap or std.process.getEnvVarOwned for a cross-platform API.");
+ }
+ // The simplified start logic doesn't populate environ.
+ if (std.start.simplified_logic) return null;
+ // TODO see https://github.com/ziglang/zig/issues/4524
+ for (std.os.environ) |ptr| {
+ var line_i: usize = 0;
+ while (ptr[line_i] != 0 and ptr[line_i] != '=') : (line_i += 1) {}
+ const this_key = ptr[0..line_i];
+ if (!mem.eql(u8, key, this_key)) continue;
+
+ return mem.sliceTo(ptr + line_i + 1, 0);
+ }
+ return null;
+}
+
+/// Get an environment variable with a null-terminated name.
+/// See also `getenv`.
+pub fn getenvZ(key: [*:0]const u8) ?[:0]const u8 {
+ if (builtin.link_libc) {
+ const value = system.getenv(key) orelse return null;
+ return mem.sliceTo(value, 0);
+ }
+ if (native_os == .windows) {
+ @compileError("std.os.getenvZ is unavailable for Windows because environment string is in WTF-16 format. See std.process.getEnvVarOwned for cross-platform API or std.process.getenvW for Windows-specific API.");
+ }
+ return getenv(mem.sliceTo(key, 0));
+}
+
+pub const GetCwdError = error{
+ NameTooLong,
+ CurrentWorkingDirectoryUnlinked,
+} || UnexpectedError;
+
+/// The result is a slice of out_buffer, indexed from 0.
+pub fn getcwd(out_buffer: []u8) GetCwdError![]u8 {
+ if (native_os == .windows) {
+ return windows.GetCurrentDirectory(out_buffer);
+ } else if (native_os == .wasi and !builtin.link_libc) {
+ const path = ".";
+ if (out_buffer.len < path.len) return error.NameTooLong;
+ const result = out_buffer[0..path.len];
+ @memcpy(result, path);
+ return result;
+ }
+
+ const err: E = if (builtin.link_libc) err: {
+ const c_err = if (std.c.getcwd(out_buffer.ptr, out_buffer.len)) |_| 0 else std.c._errno().*;
+ break :err @enumFromInt(c_err);
+ } else err: {
+ break :err errno(system.getcwd(out_buffer.ptr, out_buffer.len));
+ };
+ switch (err) {
+ .SUCCESS => return mem.sliceTo(out_buffer, 0),
+ .FAULT => unreachable,
+ .INVAL => unreachable,
+ .NOENT => return error.CurrentWorkingDirectoryUnlinked,
+ .RANGE => return error.NameTooLong,
+ else => return unexpectedErrno(err),
+ }
+}
+
+pub const SymLinkError = error{
+ /// In WASI, this error may occur when the file descriptor does
+ /// not hold the required rights to create a new symbolic link relative to it.
+ AccessDenied,
+ DiskQuota,
+ PathAlreadyExists,
+ FileSystem,
+ SymLinkLoop,
+ FileNotFound,
+ SystemResources,
+ NoSpaceLeft,
+ ReadOnlyFileSystem,
+ NotDir,
+ NameTooLong,
+
+ /// WASI-only; file paths must be valid UTF-8.
+ InvalidUtf8,
+
+ /// Windows-only; file paths provided by the user must be valid WTF-8.
+ /// https://simonsapin.github.io/wtf-8/
+ InvalidWtf8,
+
+ BadPathName,
+} || UnexpectedError;
+
+/// Creates a symbolic link named `sym_link_path` which contains the string `target_path`.
+/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
+/// one; the latter case is known as a dangling link.
+/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, both paths should be encoded as valid UTF-8.
+/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
+/// If `sym_link_path` exists, it will not be overwritten.
+/// See also `symlinkZ.
+pub fn symlink(target_path: []const u8, sym_link_path: []const u8) SymLinkError!void {
+ if (native_os == .windows) {
+ @compileError("symlink is not supported on Windows; use std.os.windows.CreateSymbolicLink instead");
+ } else if (native_os == .wasi and !builtin.link_libc) {
+ return symlinkat(target_path, wasi.AT.FDCWD, sym_link_path);
+ }
+ const target_path_c = try toPosixPath(target_path);
+ const sym_link_path_c = try toPosixPath(sym_link_path);
+ return symlinkZ(&target_path_c, &sym_link_path_c);
+}
+
+/// This is the same as `symlink` except the parameters are null-terminated pointers.
+/// See also `symlink`.
+pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLinkError!void {
+ if (native_os == .windows) {
+ @compileError("symlink is not supported on Windows; use std.os.windows.CreateSymbolicLink instead");
+ } else if (native_os == .wasi and !builtin.link_libc) {
+ return symlinkatZ(target_path, fs.cwd().fd, sym_link_path);
+ }
+ switch (errno(system.symlink(target_path, sym_link_path))) {
+ .SUCCESS => return,
+ .FAULT => unreachable,
+ .INVAL => unreachable,
+ .ACCES => return error.AccessDenied,
+ .PERM => return error.AccessDenied,
+ .DQUOT => return error.DiskQuota,
+ .EXIST => return error.PathAlreadyExists,
+ .IO => return error.FileSystem,
+ .LOOP => return error.SymLinkLoop,
+ .NAMETOOLONG => return error.NameTooLong,
+ .NOENT => return error.FileNotFound,
+ .NOTDIR => return error.NotDir,
+ .NOMEM => return error.SystemResources,
+ .NOSPC => return error.NoSpaceLeft,
+ .ROFS => return error.ReadOnlyFileSystem,
+ .ILSEQ => |err| if (native_os == .wasi)
+ return error.InvalidUtf8
+ else
+ return unexpectedErrno(err),
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+/// Similar to `symlink`, however, creates a symbolic link named `sym_link_path` which contains the string
+/// `target_path` **relative** to `newdirfd` directory handle.
+/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
+/// one; the latter case is known as a dangling link.
+/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, both paths should be encoded as valid UTF-8.
+/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
+/// If `sym_link_path` exists, it will not be overwritten.
+/// See also `symlinkatWasi`, `symlinkatZ` and `symlinkatW`.
+pub fn symlinkat(target_path: []const u8, newdirfd: fd_t, sym_link_path: []const u8) SymLinkError!void {
+ if (native_os == .windows) {
+ @compileError("symlinkat is not supported on Windows; use std.os.windows.CreateSymbolicLink instead");
+ } else if (native_os == .wasi and !builtin.link_libc) {
+ return symlinkatWasi(target_path, newdirfd, sym_link_path);
+ }
+ const target_path_c = try toPosixPath(target_path);
+ const sym_link_path_c = try toPosixPath(sym_link_path);
+ return symlinkatZ(&target_path_c, newdirfd, &sym_link_path_c);
+}
+
+/// WASI-only. The same as `symlinkat` but targeting WASI.
+/// See also `symlinkat`.
+pub fn symlinkatWasi(target_path: []const u8, newdirfd: fd_t, sym_link_path: []const u8) SymLinkError!void {
+ switch (wasi.path_symlink(target_path.ptr, target_path.len, newdirfd, sym_link_path.ptr, sym_link_path.len)) {
+ .SUCCESS => {},
+ .FAULT => unreachable,
+ .INVAL => unreachable,
+ .BADF => unreachable,
+ .ACCES => return error.AccessDenied,
+ .PERM => return error.AccessDenied,
+ .DQUOT => return error.DiskQuota,
+ .EXIST => return error.PathAlreadyExists,
+ .IO => return error.FileSystem,
+ .LOOP => return error.SymLinkLoop,
+ .NAMETOOLONG => return error.NameTooLong,
+ .NOENT => return error.FileNotFound,
+ .NOTDIR => return error.NotDir,
+ .NOMEM => return error.SystemResources,
+ .NOSPC => return error.NoSpaceLeft,
+ .ROFS => return error.ReadOnlyFileSystem,
+ .NOTCAPABLE => return error.AccessDenied,
+ .ILSEQ => return error.InvalidUtf8,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+/// The same as `symlinkat` except the parameters are null-terminated pointers.
+/// See also `symlinkat`.
+pub fn symlinkatZ(target_path: [*:0]const u8, newdirfd: fd_t, sym_link_path: [*:0]const u8) SymLinkError!void {
+ if (native_os == .windows) {
+ @compileError("symlinkat is not supported on Windows; use std.os.windows.CreateSymbolicLink instead");
+ } else if (native_os == .wasi and !builtin.link_libc) {
+ return symlinkat(mem.sliceTo(target_path, 0), newdirfd, mem.sliceTo(sym_link_path, 0));
+ }
+ switch (errno(system.symlinkat(target_path, newdirfd, sym_link_path))) {
+ .SUCCESS => return,
+ .FAULT => unreachable,
+ .INVAL => unreachable,
+ .ACCES => return error.AccessDenied,
+ .PERM => return error.AccessDenied,
+ .DQUOT => return error.DiskQuota,
+ .EXIST => return error.PathAlreadyExists,
+ .IO => return error.FileSystem,
+ .LOOP => return error.SymLinkLoop,
+ .NAMETOOLONG => return error.NameTooLong,
+ .NOENT => return error.FileNotFound,
+ .NOTDIR => return error.NotDir,
+ .NOMEM => return error.SystemResources,
+ .NOSPC => return error.NoSpaceLeft,
+ .ROFS => return error.ReadOnlyFileSystem,
+ .ILSEQ => |err| if (native_os == .wasi)
+ return error.InvalidUtf8
+ else
+ return unexpectedErrno(err),
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+pub const LinkError = UnexpectedError || error{
+ AccessDenied,
+ DiskQuota,
+ PathAlreadyExists,
+ FileSystem,
+ SymLinkLoop,
+ LinkQuotaExceeded,
+ NameTooLong,
+ FileNotFound,
+ SystemResources,
+ NoSpaceLeft,
+ ReadOnlyFileSystem,
+ NotSameFileSystem,
+
+ /// WASI-only; file paths must be valid UTF-8.
+ InvalidUtf8,
+};
+
+/// On WASI, both paths should be encoded as valid UTF-8.
+/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
+pub fn linkZ(oldpath: [*:0]const u8, newpath: [*:0]const u8, flags: i32) LinkError!void {
+ if (native_os == .wasi and !builtin.link_libc) {
+ return link(mem.sliceTo(oldpath, 0), mem.sliceTo(newpath, 0), flags);
+ }
+ switch (errno(system.link(oldpath, newpath, flags))) {
+ .SUCCESS => return,
+ .ACCES => return error.AccessDenied,
+ .DQUOT => return error.DiskQuota,
+ .EXIST => return error.PathAlreadyExists,
+ .FAULT => unreachable,
+ .IO => return error.FileSystem,
+ .LOOP => return error.SymLinkLoop,
+ .MLINK => return error.LinkQuotaExceeded,
+ .NAMETOOLONG => return error.NameTooLong,
+ .NOENT => return error.FileNotFound,
+ .NOMEM => return error.SystemResources,
+ .NOSPC => return error.NoSpaceLeft,
+ .PERM => return error.AccessDenied,
+ .ROFS => return error.ReadOnlyFileSystem,
+ .XDEV => return error.NotSameFileSystem,
+ .INVAL => unreachable,
+ .ILSEQ => |err| if (native_os == .wasi)
+ return error.InvalidUtf8
+ else
+ return unexpectedErrno(err),
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+/// On WASI, both paths should be encoded as valid UTF-8.
+/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
+pub fn link(oldpath: []const u8, newpath: []const u8, flags: i32) LinkError!void {
+ if (native_os == .wasi and !builtin.link_libc) {
+ return linkat(wasi.AT.FDCWD, oldpath, wasi.AT.FDCWD, newpath, flags) catch |err| switch (err) {
+ error.NotDir => unreachable, // link() does not support directories
+ else => |e| return e,
+ };
+ }
+ const old = try toPosixPath(oldpath);
+ const new = try toPosixPath(newpath);
+ return try linkZ(&old, &new, flags);
+}
+
+pub const LinkatError = LinkError || error{NotDir};
+
+/// On WASI, both paths should be encoded as valid UTF-8.
+/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
+pub fn linkatZ(
+ olddir: fd_t,
+ oldpath: [*:0]const u8,
+ newdir: fd_t,
+ newpath: [*:0]const u8,
+ flags: i32,
+) LinkatError!void {
+ if (native_os == .wasi and !builtin.link_libc) {
+ return linkat(olddir, mem.sliceTo(oldpath, 0), newdir, mem.sliceTo(newpath, 0), flags);
+ }
+ switch (errno(system.linkat(olddir, oldpath, newdir, newpath, flags))) {
+ .SUCCESS => return,
+ .ACCES => return error.AccessDenied,
+ .DQUOT => return error.DiskQuota,
+ .EXIST => return error.PathAlreadyExists,
+ .FAULT => unreachable,
+ .IO => return error.FileSystem,
+ .LOOP => return error.SymLinkLoop,
+ .MLINK => return error.LinkQuotaExceeded,
+ .NAMETOOLONG => return error.NameTooLong,
+ .NOENT => return error.FileNotFound,
+ .NOMEM => return error.SystemResources,
+ .NOSPC => return error.NoSpaceLeft,
+ .NOTDIR => return error.NotDir,
+ .PERM => return error.AccessDenied,
+ .ROFS => return error.ReadOnlyFileSystem,
+ .XDEV => return error.NotSameFileSystem,
+ .INVAL => unreachable,
+ .ILSEQ => |err| if (native_os == .wasi)
+ return error.InvalidUtf8
+ else
+ return unexpectedErrno(err),
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+/// On WASI, both paths should be encoded as valid UTF-8.
+/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
+pub fn linkat(
+ olddir: fd_t,
+ oldpath: []const u8,
+ newdir: fd_t,
+ newpath: []const u8,
+ flags: i32,
+) LinkatError!void {
+ if (native_os == .wasi and !builtin.link_libc) {
+ const old: RelativePathWasi = .{ .dir_fd = olddir, .relative_path = oldpath };
+ const new: RelativePathWasi = .{ .dir_fd = newdir, .relative_path = newpath };
+ const old_flags: wasi.lookupflags_t = .{
+ .SYMLINK_FOLLOW = (flags & AT.SYMLINK_FOLLOW) != 0,
+ };
+ switch (wasi.path_link(
+ old.dir_fd,
+ old_flags,
+ old.relative_path.ptr,
+ old.relative_path.len,
+ new.dir_fd,
+ new.relative_path.ptr,
+ new.relative_path.len,
+ )) {
+ .SUCCESS => return,
+ .ACCES => return error.AccessDenied,
+ .DQUOT => return error.DiskQuota,
+ .EXIST => return error.PathAlreadyExists,
+ .FAULT => unreachable,
+ .IO => return error.FileSystem,
+ .LOOP => return error.SymLinkLoop,
+ .MLINK => return error.LinkQuotaExceeded,
+ .NAMETOOLONG => return error.NameTooLong,
+ .NOENT => return error.FileNotFound,
+ .NOMEM => return error.SystemResources,
+ .NOSPC => return error.NoSpaceLeft,
+ .NOTDIR => return error.NotDir,
+ .PERM => return error.AccessDenied,
+ .ROFS => return error.ReadOnlyFileSystem,
+ .XDEV => return error.NotSameFileSystem,
+ .INVAL => unreachable,
+ .ILSEQ => return error.InvalidUtf8,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+ const old = try toPosixPath(oldpath);
+ const new = try toPosixPath(newpath);
+ return try linkatZ(olddir, &old, newdir, &new, flags);
+}
+
+pub const UnlinkError = error{
+ FileNotFound,
+
+ /// In WASI, this error may occur when the file descriptor does
+ /// not hold the required rights to unlink a resource by path relative to it.
+ AccessDenied,
+ FileBusy,
+ FileSystem,
+ IsDir,
+ SymLinkLoop,
+ NameTooLong,
+ NotDir,
+ SystemResources,
+ ReadOnlyFileSystem,
+
+ /// WASI-only; file paths must be valid UTF-8.
+ InvalidUtf8,
+
+ /// Windows-only; file paths provided by the user must be valid WTF-8.
+ /// https://simonsapin.github.io/wtf-8/
+ InvalidWtf8,
+
+ /// On Windows, file paths cannot contain these characters:
+ /// '/', '*', '?', '"', '<', '>', '|'
+ BadPathName,
+
+ /// On Windows, `\\server` or `\\server\share` was not found.
+ NetworkNotFound,
+} || UnexpectedError;
+
+/// Delete a name and possibly the file it refers to.
+/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `file_path` should be encoded as valid UTF-8.
+/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
+/// See also `unlinkZ`.
+pub fn unlink(file_path: []const u8) UnlinkError!void {
+ if (native_os == .wasi and !builtin.link_libc) {
+ return unlinkat(wasi.AT.FDCWD, file_path, 0) catch |err| switch (err) {
+ error.DirNotEmpty => unreachable, // only occurs when targeting directories
+ else => |e| return e,
+ };
+ } else if (native_os == .windows) {
+ const file_path_w = try windows.sliceToPrefixedFileW(null, file_path);
+ return unlinkW(file_path_w.span());
+ } else {
+ const file_path_c = try toPosixPath(file_path);
+ return unlinkZ(&file_path_c);
+ }
+}
+
+/// Same as `unlink` except the parameter is null terminated.
+pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void {
+ if (native_os == .windows) {
+ const file_path_w = try windows.cStrToPrefixedFileW(null, file_path);
+ return unlinkW(file_path_w.span());
+ } else if (native_os == .wasi and !builtin.link_libc) {
+ return unlink(mem.sliceTo(file_path, 0));
+ }
+ switch (errno(system.unlink(file_path))) {
+ .SUCCESS => return,
+ .ACCES => return error.AccessDenied,
+ .PERM => return error.AccessDenied,
+ .BUSY => return error.FileBusy,
+ .FAULT => unreachable,
+ .INVAL => unreachable,
+ .IO => return error.FileSystem,
+ .ISDIR => return error.IsDir,
+ .LOOP => return error.SymLinkLoop,
+ .NAMETOOLONG => return error.NameTooLong,
+ .NOENT => return error.FileNotFound,
+ .NOTDIR => return error.NotDir,
+ .NOMEM => return error.SystemResources,
+ .ROFS => return error.ReadOnlyFileSystem,
+ .ILSEQ => |err| if (native_os == .wasi)
+ return error.InvalidUtf8
+ else
+ return unexpectedErrno(err),
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+/// Windows-only. Same as `unlink` except the parameter is null-terminated, WTF16 LE encoded.
+pub fn unlinkW(file_path_w: []const u16) UnlinkError!void {
+ windows.DeleteFile(file_path_w, .{ .dir = fs.cwd().fd }) catch |err| switch (err) {
+ error.DirNotEmpty => unreachable, // we're not passing .remove_dir = true
+ else => |e| return e,
+ };
+}
+
+pub const UnlinkatError = UnlinkError || error{
+ /// When passing `AT.REMOVEDIR`, this error occurs when the named directory is not empty.
+ DirNotEmpty,
+};
+
+/// Delete a file name and possibly the file it refers to, based on an open directory handle.
+/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `file_path` should be encoded as valid UTF-8.
+/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
+/// Asserts that the path parameter has no null bytes.
+pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void {
+ if (native_os == .windows) {
+ const file_path_w = try windows.sliceToPrefixedFileW(dirfd, file_path);
+ return unlinkatW(dirfd, file_path_w.span(), flags);
+ } else if (native_os == .wasi and !builtin.link_libc) {
+ return unlinkatWasi(dirfd, file_path, flags);
+ } else {
+ const file_path_c = try toPosixPath(file_path);
+ return unlinkatZ(dirfd, &file_path_c, flags);
+ }
+}
+
+/// WASI-only. Same as `unlinkat` but targeting WASI.
+/// See also `unlinkat`.
+pub fn unlinkatWasi(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void {
+ const remove_dir = (flags & AT.REMOVEDIR) != 0;
+ const res = if (remove_dir)
+ wasi.path_remove_directory(dirfd, file_path.ptr, file_path.len)
+ else
+ wasi.path_unlink_file(dirfd, file_path.ptr, file_path.len);
+ switch (res) {
+ .SUCCESS => return,
+ .ACCES => return error.AccessDenied,
+ .PERM => return error.AccessDenied,
+ .BUSY => return error.FileBusy,
+ .FAULT => unreachable,
+ .IO => return error.FileSystem,
+ .ISDIR => return error.IsDir,
+ .LOOP => return error.SymLinkLoop,
+ .NAMETOOLONG => return error.NameTooLong,
+ .NOENT => return error.FileNotFound,
+ .NOTDIR => return error.NotDir,
+ .NOMEM => return error.SystemResources,
+ .ROFS => return error.ReadOnlyFileSystem,
+ .NOTEMPTY => return error.DirNotEmpty,
+ .NOTCAPABLE => return error.AccessDenied,
+ .ILSEQ => return error.InvalidUtf8,
+
+ .INVAL => unreachable, // invalid flags, or pathname has . as last component
+ .BADF => unreachable, // always a race condition
+
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+/// Same as `unlinkat` but `file_path` is a null-terminated string.
+pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatError!void {
+ if (native_os == .windows) {
+ const file_path_w = try windows.cStrToPrefixedFileW(dirfd, file_path_c);
+ return unlinkatW(dirfd, file_path_w.span(), flags);
+ } else if (native_os == .wasi and !builtin.link_libc) {
+ return unlinkat(dirfd, mem.sliceTo(file_path_c, 0), flags);
+ }
+ switch (errno(system.unlinkat(dirfd, file_path_c, flags))) {
+ .SUCCESS => return,
+ .ACCES => return error.AccessDenied,
+ .PERM => return error.AccessDenied,
+ .BUSY => return error.FileBusy,
+ .FAULT => unreachable,
+ .IO => return error.FileSystem,
+ .ISDIR => return error.IsDir,
+ .LOOP => return error.SymLinkLoop,
+ .NAMETOOLONG => return error.NameTooLong,
+ .NOENT => return error.FileNotFound,
+ .NOTDIR => return error.NotDir,
+ .NOMEM => return error.SystemResources,
+ .ROFS => return error.ReadOnlyFileSystem,
+ .EXIST => return error.DirNotEmpty,
+ .NOTEMPTY => return error.DirNotEmpty,
+ .ILSEQ => |err| if (native_os == .wasi)
+ return error.InvalidUtf8
+ else
+ return unexpectedErrno(err),
+
+ .INVAL => unreachable, // invalid flags, or pathname has . as last component
+ .BADF => unreachable, // always a race condition
+
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+/// Same as `unlinkat` but `sub_path_w` is WTF16LE, NT prefixed. Windows only.
+pub fn unlinkatW(dirfd: fd_t, sub_path_w: []const u16, flags: u32) UnlinkatError!void {
+ const remove_dir = (flags & AT.REMOVEDIR) != 0;
+ return windows.DeleteFile(sub_path_w, .{ .dir = dirfd, .remove_dir = remove_dir });
+}
+
+pub const RenameError = error{
+ /// In WASI, this error may occur when the file descriptor does
+ /// not hold the required rights to rename a resource by path relative to it.
+ ///
+ /// On Windows, this error may be returned instead of PathAlreadyExists when
+ /// renaming a directory over an existing directory.
+ AccessDenied,
+ FileBusy,
+ DiskQuota,
+ IsDir,
+ SymLinkLoop,
+ LinkQuotaExceeded,
+ NameTooLong,
+ FileNotFound,
+ NotDir,
+ SystemResources,
+ NoSpaceLeft,
+ PathAlreadyExists,
+ ReadOnlyFileSystem,
+ RenameAcrossMountPoints,
+ /// WASI-only; file paths must be valid UTF-8.
+ InvalidUtf8,
+ /// Windows-only; file paths provided by the user must be valid WTF-8.
+ /// https://simonsapin.github.io/wtf-8/
+ InvalidWtf8,
+ BadPathName,
+ NoDevice,
+ SharingViolation,
+ PipeBusy,
+ /// On Windows, `\\server` or `\\server\share` was not found.
+ NetworkNotFound,
+ /// On Windows, antivirus software is enabled by default. It can be
+ /// disabled, but Windows Update sometimes ignores the user's preference
+ /// and re-enables it. When enabled, antivirus software on Windows
+ /// intercepts file system operations and makes them significantly slower
+ /// in addition to possibly failing with this error code.
+ AntivirusInterference,
+} || UnexpectedError;
+
+/// Change the name or location of a file.
+/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, both paths should be encoded as valid UTF-8.
+/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
+pub fn rename(old_path: []const u8, new_path: []const u8) RenameError!void {
+ if (native_os == .wasi and !builtin.link_libc) {
+ return renameat(wasi.AT.FDCWD, old_path, wasi.AT.FDCWD, new_path);
+ } else if (native_os == .windows) {
+ const old_path_w = try windows.sliceToPrefixedFileW(null, old_path);
+ const new_path_w = try windows.sliceToPrefixedFileW(null, new_path);
+ return renameW(old_path_w.span().ptr, new_path_w.span().ptr);
+ } else {
+ const old_path_c = try toPosixPath(old_path);
+ const new_path_c = try toPosixPath(new_path);
+ return renameZ(&old_path_c, &new_path_c);
+ }
+}
+
+/// Same as `rename` except the parameters are null-terminated.
+pub fn renameZ(old_path: [*:0]const u8, new_path: [*:0]const u8) RenameError!void {
+ if (native_os == .windows) {
+ const old_path_w = try windows.cStrToPrefixedFileW(null, old_path);
+ const new_path_w = try windows.cStrToPrefixedFileW(null, new_path);
+ return renameW(old_path_w.span().ptr, new_path_w.span().ptr);
+ } else if (native_os == .wasi and !builtin.link_libc) {
+ return rename(mem.sliceTo(old_path, 0), mem.sliceTo(new_path, 0));
+ }
+ switch (errno(system.rename(old_path, new_path))) {
+ .SUCCESS => return,
+ .ACCES => return error.AccessDenied,
+ .PERM => return error.AccessDenied,
+ .BUSY => return error.FileBusy,
+ .DQUOT => return error.DiskQuota,
+ .FAULT => unreachable,
+ .INVAL => unreachable,
+ .ISDIR => return error.IsDir,
+ .LOOP => return error.SymLinkLoop,
+ .MLINK => return error.LinkQuotaExceeded,
+ .NAMETOOLONG => return error.NameTooLong,
+ .NOENT => return error.FileNotFound,
+ .NOTDIR => return error.NotDir,
+ .NOMEM => return error.SystemResources,
+ .NOSPC => return error.NoSpaceLeft,
+ .EXIST => return error.PathAlreadyExists,
+ .NOTEMPTY => return error.PathAlreadyExists,
+ .ROFS => return error.ReadOnlyFileSystem,
+ .XDEV => return error.RenameAcrossMountPoints,
+ .ILSEQ => |err| if (native_os == .wasi)
+ return error.InvalidUtf8
+ else
+ return unexpectedErrno(err),
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+/// Same as `rename` except the parameters are null-terminated and WTF16LE encoded.
+/// Assumes target is Windows.
+pub fn renameW(old_path: [*:0]const u16, new_path: [*:0]const u16) RenameError!void {
+ const flags = windows.MOVEFILE_REPLACE_EXISTING | windows.MOVEFILE_WRITE_THROUGH;
+ return windows.MoveFileExW(old_path, new_path, flags);
+}
+
+/// Change the name or location of a file based on an open directory handle.
+/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, both paths should be encoded as valid UTF-8.
+/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
+pub fn renameat(
+ old_dir_fd: fd_t,
+ old_path: []const u8,
+ new_dir_fd: fd_t,
+ new_path: []const u8,
+) RenameError!void {
+ if (native_os == .windows) {
+ const old_path_w = try windows.sliceToPrefixedFileW(old_dir_fd, old_path);
+ const new_path_w = try windows.sliceToPrefixedFileW(new_dir_fd, new_path);
+ return renameatW(old_dir_fd, old_path_w.span(), new_dir_fd, new_path_w.span(), windows.TRUE);
+ } else if (native_os == .wasi and !builtin.link_libc) {
+ const old: RelativePathWasi = .{ .dir_fd = old_dir_fd, .relative_path = old_path };
+ const new: RelativePathWasi = .{ .dir_fd = new_dir_fd, .relative_path = new_path };
+ return renameatWasi(old, new);
+ } else {
+ const old_path_c = try toPosixPath(old_path);
+ const new_path_c = try toPosixPath(new_path);
+ return renameatZ(old_dir_fd, &old_path_c, new_dir_fd, &new_path_c);
+ }
+}
+
+/// WASI-only. Same as `renameat` expect targeting WASI.
+/// See also `renameat`.
+fn renameatWasi(old: RelativePathWasi, new: RelativePathWasi) RenameError!void {
+ switch (wasi.path_rename(old.dir_fd, old.relative_path.ptr, old.relative_path.len, new.dir_fd, new.relative_path.ptr, new.relative_path.len)) {
+ .SUCCESS => return,
+ .ACCES => return error.AccessDenied,
+ .PERM => return error.AccessDenied,
+ .BUSY => return error.FileBusy,
+ .DQUOT => return error.DiskQuota,
+ .FAULT => unreachable,
+ .INVAL => unreachable,
+ .ISDIR => return error.IsDir,
+ .LOOP => return error.SymLinkLoop,
+ .MLINK => return error.LinkQuotaExceeded,
+ .NAMETOOLONG => return error.NameTooLong,
+ .NOENT => return error.FileNotFound,
+ .NOTDIR => return error.NotDir,
+ .NOMEM => return error.SystemResources,
+ .NOSPC => return error.NoSpaceLeft,
+ .EXIST => return error.PathAlreadyExists,
+ .NOTEMPTY => return error.PathAlreadyExists,
+ .ROFS => return error.ReadOnlyFileSystem,
+ .XDEV => return error.RenameAcrossMountPoints,
+ .NOTCAPABLE => return error.AccessDenied,
+ .ILSEQ => return error.InvalidUtf8,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+/// An fd-relative file path
+///
+/// This is currently only used for WASI-specific functionality, but the concept
+/// is the same as the dirfd/pathname pairs in the `*at(...)` POSIX functions.
+const RelativePathWasi = struct {
+ /// Handle to directory
+ dir_fd: fd_t,
+ /// Path to resource within `dir_fd`.
+ relative_path: []const u8,
+};
+
+/// Same as `renameat` except the parameters are null-terminated.
+pub fn renameatZ(
+ old_dir_fd: fd_t,
+ old_path: [*:0]const u8,
+ new_dir_fd: fd_t,
+ new_path: [*:0]const u8,
+) RenameError!void {
+ if (native_os == .windows) {
+ const old_path_w = try windows.cStrToPrefixedFileW(old_dir_fd, old_path);
+ const new_path_w = try windows.cStrToPrefixedFileW(new_dir_fd, new_path);
+ return renameatW(old_dir_fd, old_path_w.span(), new_dir_fd, new_path_w.span(), windows.TRUE);
+ } else if (native_os == .wasi and !builtin.link_libc) {
+ return renameat(old_dir_fd, mem.sliceTo(old_path, 0), new_dir_fd, mem.sliceTo(new_path, 0));
+ }
+
+ switch (errno(system.renameat(old_dir_fd, old_path, new_dir_fd, new_path))) {
+ .SUCCESS => return,
+ .ACCES => return error.AccessDenied,
+ .PERM => return error.AccessDenied,
+ .BUSY => return error.FileBusy,
+ .DQUOT => return error.DiskQuota,
+ .FAULT => unreachable,
+ .INVAL => unreachable,
+ .ISDIR => return error.IsDir,
+ .LOOP => return error.SymLinkLoop,
+ .MLINK => return error.LinkQuotaExceeded,
+ .NAMETOOLONG => return error.NameTooLong,
+ .NOENT => return error.FileNotFound,
+ .NOTDIR => return error.NotDir,
+ .NOMEM => return error.SystemResources,
+ .NOSPC => return error.NoSpaceLeft,
+ .EXIST => return error.PathAlreadyExists,
+ .NOTEMPTY => return error.PathAlreadyExists,
+ .ROFS => return error.ReadOnlyFileSystem,
+ .XDEV => return error.RenameAcrossMountPoints,
+ .ILSEQ => |err| if (native_os == .wasi)
+ return error.InvalidUtf8
+ else
+ return unexpectedErrno(err),
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+/// Same as `renameat` but Windows-only and the path parameters are
+/// [WTF-16](https://simonsapin.github.io/wtf-8/#potentially-ill-formed-utf-16) encoded.
+pub fn renameatW(
+ old_dir_fd: fd_t,
+ old_path_w: []const u16,
+ new_dir_fd: fd_t,
+ new_path_w: []const u16,
+ ReplaceIfExists: windows.BOOLEAN,
+) RenameError!void {
+ const src_fd = windows.OpenFile(old_path_w, .{
+ .dir = old_dir_fd,
+ .access_mask = windows.SYNCHRONIZE | windows.GENERIC_WRITE | windows.DELETE,
+ .creation = windows.FILE_OPEN,
+ .filter = .any, // This function is supposed to rename both files and directories.
+ .follow_symlinks = false,
+ }) catch |err| switch (err) {
+ error.WouldBlock => unreachable, // Not possible without `.share_access_nonblocking = true`.
+ else => |e| return e,
+ };
+ defer windows.CloseHandle(src_fd);
+
+ var need_fallback = true;
+ var rc: windows.NTSTATUS = undefined;
+ // FILE_RENAME_INFORMATION_EX and FILE_RENAME_POSIX_SEMANTICS require >= win10_rs1,
+ // but FILE_RENAME_IGNORE_READONLY_ATTRIBUTE requires >= win10_rs5. We check >= rs5 here
+ // so that we only use POSIX_SEMANTICS when we know IGNORE_READONLY_ATTRIBUTE will also be
+ // supported in order to avoid either (1) using a redundant call that we can know in advance will return
+ // STATUS_NOT_SUPPORTED or (2) only setting IGNORE_READONLY_ATTRIBUTE when >= rs5
+ // and therefore having different behavior when the Windows version is >= rs1 but < rs5.
+ if (builtin.target.os.isAtLeast(.windows, .win10_rs5) orelse false) {
+ const struct_buf_len = @sizeOf(windows.FILE_RENAME_INFORMATION_EX) + (max_path_bytes - 1);
+ var rename_info_buf: [struct_buf_len]u8 align(@alignOf(windows.FILE_RENAME_INFORMATION_EX)) = undefined;
+ const struct_len = @sizeOf(windows.FILE_RENAME_INFORMATION_EX) - 1 + new_path_w.len * 2;
+ if (struct_len > struct_buf_len) return error.NameTooLong;
+
+ const rename_info: *windows.FILE_RENAME_INFORMATION_EX = @ptrCast(&rename_info_buf);
+ var io_status_block: windows.IO_STATUS_BLOCK = undefined;
+
+ var flags: windows.ULONG = windows.FILE_RENAME_POSIX_SEMANTICS | windows.FILE_RENAME_IGNORE_READONLY_ATTRIBUTE;
+ if (ReplaceIfExists == windows.TRUE) flags |= windows.FILE_RENAME_REPLACE_IF_EXISTS;
+ rename_info.* = .{
+ .Flags = flags,
+ .RootDirectory = if (fs.path.isAbsoluteWindowsWTF16(new_path_w)) null else new_dir_fd,
+ .FileNameLength = @intCast(new_path_w.len * 2), // already checked error.NameTooLong
+ .FileName = undefined,
+ };
+ @memcpy((&rename_info.FileName).ptr, new_path_w);
+ rc = windows.ntdll.NtSetInformationFile(
+ src_fd,
+ &io_status_block,
+ rename_info,
+ @intCast(struct_len), // already checked for error.NameTooLong
+ .FileRenameInformationEx,
+ );
+ switch (rc) {
+ .SUCCESS => return,
+ // INVALID_PARAMETER here means that the filesystem does not support FileRenameInformationEx
+ .INVALID_PARAMETER => {},
+ .DIRECTORY_NOT_EMPTY => return error.PathAlreadyExists,
+ .FILE_IS_A_DIRECTORY => return error.IsDir,
+ .NOT_A_DIRECTORY => return error.NotDir,
+ // For all other statuses, fall down to the switch below to handle them.
+ else => need_fallback = false,
+ }
+ }
+
+ if (need_fallback) {
+ const struct_buf_len = @sizeOf(windows.FILE_RENAME_INFORMATION) + (max_path_bytes - 1);
+ var rename_info_buf: [struct_buf_len]u8 align(@alignOf(windows.FILE_RENAME_INFORMATION)) = undefined;
+ const struct_len = @sizeOf(windows.FILE_RENAME_INFORMATION) - 1 + new_path_w.len * 2;
+ if (struct_len > struct_buf_len) return error.NameTooLong;
+
+ const rename_info: *windows.FILE_RENAME_INFORMATION = @ptrCast(&rename_info_buf);
+ var io_status_block: windows.IO_STATUS_BLOCK = undefined;
+
+ rename_info.* = .{
+ .Flags = ReplaceIfExists,
+ .RootDirectory = if (fs.path.isAbsoluteWindowsWTF16(new_path_w)) null else new_dir_fd,
+ .FileNameLength = @intCast(new_path_w.len * 2), // already checked error.NameTooLong
+ .FileName = undefined,
+ };
+ @memcpy((&rename_info.FileName).ptr, new_path_w);
+
+ rc =
+ windows.ntdll.NtSetInformationFile(
+ src_fd,
+ &io_status_block,
+ rename_info,
+ @intCast(struct_len), // already checked for error.NameTooLong
+ .FileRenameInformation,
+ );
+ }
+
+ switch (rc) {
+ .SUCCESS => {},
+ .INVALID_HANDLE => unreachable,
+ .INVALID_PARAMETER => unreachable,
+ .OBJECT_PATH_SYNTAX_BAD => unreachable,
+ .ACCESS_DENIED => return error.AccessDenied,
+ .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
+ .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
+ .NOT_SAME_DEVICE => return error.RenameAcrossMountPoints,
+ .OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
+ else => return windows.unexpectedStatus(rc),
+ }
+}
+
+/// On Windows, `sub_dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `sub_dir_path` should be encoded as valid UTF-8.
+/// On other platforms, `sub_dir_path` is an opaque sequence of bytes with no particular encoding.
+pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!void {
+ if (native_os == .windows) {
+ const sub_dir_path_w = try windows.sliceToPrefixedFileW(dir_fd, sub_dir_path);
+ return mkdiratW(dir_fd, sub_dir_path_w.span(), mode);
+ } else if (native_os == .wasi and !builtin.link_libc) {
+ return mkdiratWasi(dir_fd, sub_dir_path, mode);
+ } else {
+ const sub_dir_path_c = try toPosixPath(sub_dir_path);
+ return mkdiratZ(dir_fd, &sub_dir_path_c, mode);
+ }
+}
+
+pub fn mkdiratWasi(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!void {
+ _ = mode;
+ switch (wasi.path_create_directory(dir_fd, sub_dir_path.ptr, sub_dir_path.len)) {
+ .SUCCESS => return,
+ .ACCES => return error.AccessDenied,
+ .BADF => unreachable,
+ .PERM => return error.AccessDenied,
+ .DQUOT => return error.DiskQuota,
+ .EXIST => return error.PathAlreadyExists,
+ .FAULT => unreachable,
+ .LOOP => return error.SymLinkLoop,
+ .MLINK => return error.LinkQuotaExceeded,
+ .NAMETOOLONG => return error.NameTooLong,
+ .NOENT => return error.FileNotFound,
+ .NOMEM => return error.SystemResources,
+ .NOSPC => return error.NoSpaceLeft,
+ .NOTDIR => return error.NotDir,
+ .ROFS => return error.ReadOnlyFileSystem,
+ .NOTCAPABLE => return error.AccessDenied,
+ .ILSEQ => return error.InvalidUtf8,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+/// Same as `mkdirat` except the parameters are null-terminated.
+pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirError!void {
+ if (native_os == .windows) {
+ const sub_dir_path_w = try windows.cStrToPrefixedFileW(dir_fd, sub_dir_path);
+ return mkdiratW(dir_fd, sub_dir_path_w.span(), mode);
+ } else if (native_os == .wasi and !builtin.link_libc) {
+ return mkdirat(dir_fd, mem.sliceTo(sub_dir_path, 0), mode);
+ }
+ switch (errno(system.mkdirat(dir_fd, sub_dir_path, mode))) {
+ .SUCCESS => return,
+ .ACCES => return error.AccessDenied,
+ .BADF => unreachable,
+ .PERM => return error.AccessDenied,
+ .DQUOT => return error.DiskQuota,
+ .EXIST => return error.PathAlreadyExists,
+ .FAULT => unreachable,
+ .LOOP => return error.SymLinkLoop,
+ .MLINK => return error.LinkQuotaExceeded,
+ .NAMETOOLONG => return error.NameTooLong,
+ .NOENT => return error.FileNotFound,
+ .NOMEM => return error.SystemResources,
+ .NOSPC => return error.NoSpaceLeft,
+ .NOTDIR => return error.NotDir,
+ .ROFS => return error.ReadOnlyFileSystem,
+ // dragonfly: when dir_fd is unlinked from filesystem
+ .NOTCONN => return error.FileNotFound,
+ .ILSEQ => |err| if (native_os == .wasi)
+ return error.InvalidUtf8
+ else
+ return unexpectedErrno(err),
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+/// Windows-only. Same as `mkdirat` except the parameter WTF16 LE encoded.
+pub fn mkdiratW(dir_fd: fd_t, sub_path_w: []const u16, mode: u32) MakeDirError!void {
+ _ = mode;
+ const sub_dir_handle = windows.OpenFile(sub_path_w, .{
+ .dir = dir_fd,
+ .access_mask = windows.GENERIC_READ | windows.SYNCHRONIZE,
+ .creation = windows.FILE_CREATE,
+ .filter = .dir_only,
+ }) catch |err| switch (err) {
+ error.IsDir => return error.Unexpected,
+ error.PipeBusy => return error.Unexpected,
+ error.WouldBlock => return error.Unexpected,
+ error.AntivirusInterference => return error.Unexpected,
+ else => |e| return e,
+ };
+ windows.CloseHandle(sub_dir_handle);
+}
+
+pub const MakeDirError = error{
+ /// In WASI, this error may occur when the file descriptor does
+ /// not hold the required rights to create a new directory relative to it.
+ AccessDenied,
+ DiskQuota,
+ PathAlreadyExists,
+ SymLinkLoop,
+ LinkQuotaExceeded,
+ NameTooLong,
+ FileNotFound,
+ SystemResources,
+ NoSpaceLeft,
+ NotDir,
+ ReadOnlyFileSystem,
+ /// WASI-only; file paths must be valid UTF-8.
+ InvalidUtf8,
+ /// Windows-only; file paths provided by the user must be valid WTF-8.
+ /// https://simonsapin.github.io/wtf-8/
+ InvalidWtf8,
+ BadPathName,
+ NoDevice,
+ /// On Windows, `\\server` or `\\server\share` was not found.
+ NetworkNotFound,
+} || UnexpectedError;
+
+/// Create a directory.
+/// `mode` is ignored on Windows and WASI.
+/// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `dir_path` should be encoded as valid UTF-8.
+/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
+pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void {
+ if (native_os == .wasi and !builtin.link_libc) {
+ return mkdirat(wasi.AT.FDCWD, dir_path, mode);
+ } else if (native_os == .windows) {
+ const dir_path_w = try windows.sliceToPrefixedFileW(null, dir_path);
+ return mkdirW(dir_path_w.span(), mode);
+ } else {
+ const dir_path_c = try toPosixPath(dir_path);
+ return mkdirZ(&dir_path_c, mode);
+ }
+}
+
+/// Same as `mkdir` but the parameter is null-terminated.
+/// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `dir_path` should be encoded as valid UTF-8.
+/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
+pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void {
+ if (native_os == .windows) {
+ const dir_path_w = try windows.cStrToPrefixedFileW(null, dir_path);
+ return mkdirW(dir_path_w.span(), mode);
+ } else if (native_os == .wasi and !builtin.link_libc) {
+ return mkdir(mem.sliceTo(dir_path, 0), mode);
+ }
+ switch (errno(system.mkdir(dir_path, mode))) {
+ .SUCCESS => return,
+ .ACCES => return error.AccessDenied,
+ .PERM => return error.AccessDenied,
+ .DQUOT => return error.DiskQuota,
+ .EXIST => return error.PathAlreadyExists,
+ .FAULT => unreachable,
+ .LOOP => return error.SymLinkLoop,
+ .MLINK => return error.LinkQuotaExceeded,
+ .NAMETOOLONG => return error.NameTooLong,
+ .NOENT => return error.FileNotFound,
+ .NOMEM => return error.SystemResources,
+ .NOSPC => return error.NoSpaceLeft,
+ .NOTDIR => return error.NotDir,
+ .ROFS => return error.ReadOnlyFileSystem,
+ .ILSEQ => |err| if (native_os == .wasi)
+ return error.InvalidUtf8
+ else
+ return unexpectedErrno(err),
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+/// Windows-only. Same as `mkdir` but the parameters is WTF16LE encoded.
+pub fn mkdirW(dir_path_w: []const u16, mode: u32) MakeDirError!void {
+ _ = mode;
+ const sub_dir_handle = windows.OpenFile(dir_path_w, .{
+ .dir = fs.cwd().fd,
+ .access_mask = windows.GENERIC_READ | windows.SYNCHRONIZE,
+ .creation = windows.FILE_CREATE,
+ .filter = .dir_only,
+ }) catch |err| switch (err) {
+ error.IsDir => return error.Unexpected,
+ error.PipeBusy => return error.Unexpected,
+ error.WouldBlock => return error.Unexpected,
+ error.AntivirusInterference => return error.Unexpected,
+ else => |e| return e,
+ };
+ windows.CloseHandle(sub_dir_handle);
+}
+
+pub const DeleteDirError = error{
+ AccessDenied,
+ FileBusy,
+ SymLinkLoop,
+ NameTooLong,
+ FileNotFound,
+ SystemResources,
+ NotDir,
+ DirNotEmpty,
+ ReadOnlyFileSystem,
+ /// WASI-only; file paths must be valid UTF-8.
+ InvalidUtf8,
+ /// Windows-only; file paths provided by the user must be valid WTF-8.
+ /// https://simonsapin.github.io/wtf-8/
+ InvalidWtf8,
+ BadPathName,
+ /// On Windows, `\\server` or `\\server\share` was not found.
+ NetworkNotFound,
+} || UnexpectedError;
+
+/// Deletes an empty directory.
+/// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `dir_path` should be encoded as valid UTF-8.
+/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
+pub fn rmdir(dir_path: []const u8) DeleteDirError!void {
+ if (native_os == .wasi and !builtin.link_libc) {
+ return unlinkat(wasi.AT.FDCWD, dir_path, AT.REMOVEDIR) catch |err| switch (err) {
+ error.FileSystem => unreachable, // only occurs when targeting files
+ error.IsDir => unreachable, // only occurs when targeting files
+ else => |e| return e,
+ };
+ } else if (native_os == .windows) {
+ const dir_path_w = try windows.sliceToPrefixedFileW(null, dir_path);
+ return rmdirW(dir_path_w.span());
+ } else {
+ const dir_path_c = try toPosixPath(dir_path);
+ return rmdirZ(&dir_path_c);
+ }
+}
+
+/// Same as `rmdir` except the parameter is null-terminated.
+/// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `dir_path` should be encoded as valid UTF-8.
+/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
+pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void {
+ if (native_os == .windows) {
+ const dir_path_w = try windows.cStrToPrefixedFileW(null, dir_path);
+ return rmdirW(dir_path_w.span());
+ } else if (native_os == .wasi and !builtin.link_libc) {
+ return rmdir(mem.sliceTo(dir_path, 0));
+ }
+ switch (errno(system.rmdir(dir_path))) {
+ .SUCCESS => return,
+ .ACCES => return error.AccessDenied,
+ .PERM => return error.AccessDenied,
+ .BUSY => return error.FileBusy,
+ .FAULT => unreachable,
+ .INVAL => return error.BadPathName,
+ .LOOP => return error.SymLinkLoop,
+ .NAMETOOLONG => return error.NameTooLong,
+ .NOENT => return error.FileNotFound,
+ .NOMEM => return error.SystemResources,
+ .NOTDIR => return error.NotDir,
+ .EXIST => return error.DirNotEmpty,
+ .NOTEMPTY => return error.DirNotEmpty,
+ .ROFS => return error.ReadOnlyFileSystem,
+ .ILSEQ => |err| if (native_os == .wasi)
+ return error.InvalidUtf8
+ else
+ return unexpectedErrno(err),
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+/// Windows-only. Same as `rmdir` except the parameter is WTF-16 LE encoded.
+pub fn rmdirW(dir_path_w: []const u16) DeleteDirError!void {
+ return windows.DeleteFile(dir_path_w, .{ .dir = fs.cwd().fd, .remove_dir = true }) catch |err| switch (err) {
+ error.IsDir => unreachable,
+ else => |e| return e,
+ };
+}
+
+pub const ChangeCurDirError = error{
+ AccessDenied,
+ FileSystem,
+ SymLinkLoop,
+ NameTooLong,
+ FileNotFound,
+ SystemResources,
+ NotDir,
+ BadPathName,
+ /// WASI-only; file paths must be valid UTF-8.
+ InvalidUtf8,
+ /// Windows-only; file paths provided by the user must be valid WTF-8.
+ /// https://simonsapin.github.io/wtf-8/
+ InvalidWtf8,
+} || UnexpectedError;
+
+/// Changes the current working directory of the calling process.
+/// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `dir_path` should be encoded as valid UTF-8.
+/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
+pub fn chdir(dir_path: []const u8) ChangeCurDirError!void {
+ if (native_os == .wasi and !builtin.link_libc) {
+ @compileError("WASI does not support os.chdir");
+ } else if (native_os == .windows) {
+ var wtf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined;
+ const len = try std.unicode.wtf8ToWtf16Le(wtf16_dir_path[0..], dir_path);
+ if (len > wtf16_dir_path.len) return error.NameTooLong;
+ return chdirW(wtf16_dir_path[0..len]);
+ } else {
+ const dir_path_c = try toPosixPath(dir_path);
+ return chdirZ(&dir_path_c);
+ }
+}
+
+/// Same as `chdir` except the parameter is null-terminated.
+/// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `dir_path` should be encoded as valid UTF-8.
+/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
+pub fn chdirZ(dir_path: [*:0]const u8) ChangeCurDirError!void {
+ if (native_os == .windows) {
+ var wtf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined;
+ const len = try std.unicode.wtf8ToWtf16Le(wtf16_dir_path[0..], mem.span(dir_path));
+ if (len > wtf16_dir_path.len) return error.NameTooLong;
+ return chdirW(wtf16_dir_path[0..len]);
+ } else if (native_os == .wasi and !builtin.link_libc) {
+ return chdir(mem.span(dir_path));
+ }
+ switch (errno(system.chdir(dir_path))) {
+ .SUCCESS => return,
+ .ACCES => return error.AccessDenied,
+ .FAULT => unreachable,
+ .IO => return error.FileSystem,
+ .LOOP => return error.SymLinkLoop,
+ .NAMETOOLONG => return error.NameTooLong,
+ .NOENT => return error.FileNotFound,
+ .NOMEM => return error.SystemResources,
+ .NOTDIR => return error.NotDir,
+ .ILSEQ => |err| if (native_os == .wasi)
+ return error.InvalidUtf8
+ else
+ return unexpectedErrno(err),
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+/// Windows-only. Same as `chdir` except the parameter is WTF16 LE encoded.
+pub fn chdirW(dir_path: []const u16) ChangeCurDirError!void {
+ windows.SetCurrentDirectory(dir_path) catch |err| switch (err) {
+ error.NoDevice => return error.FileSystem,
+ else => |e| return e,
+ };
+}
+
+pub const FchdirError = error{
+ AccessDenied,
+ NotDir,
+ FileSystem,
+} || UnexpectedError;
+
+pub fn fchdir(dirfd: fd_t) FchdirError!void {
+ if (dirfd == AT.FDCWD) return;
+ while (true) {
+ switch (errno(system.fchdir(dirfd))) {
+ .SUCCESS => return,
+ .ACCES => return error.AccessDenied,
+ .BADF => unreachable,
+ .NOTDIR => return error.NotDir,
+ .INTR => continue,
+ .IO => return error.FileSystem,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+}
+
+pub const ReadLinkError = error{
+ /// In WASI, this error may occur when the file descriptor does
+ /// not hold the required rights to read value of a symbolic link relative to it.
+ AccessDenied,
+ FileSystem,
+ SymLinkLoop,
+ NameTooLong,
+ FileNotFound,
+ SystemResources,
+ NotLink,
+ NotDir,
+ /// WASI-only; file paths must be valid UTF-8.
+ InvalidUtf8,
+ /// Windows-only; file paths provided by the user must be valid WTF-8.
+ /// https://simonsapin.github.io/wtf-8/
+ InvalidWtf8,
+ BadPathName,
+ /// Windows-only. This error may occur if the opened reparse point is
+ /// of unsupported type.
+ UnsupportedReparsePointType,
+ /// On Windows, `\\server` or `\\server\share` was not found.
+ NetworkNotFound,
+} || UnexpectedError;
+
+/// Read value of a symbolic link.
+/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `file_path` should be encoded as valid UTF-8.
+/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
+/// The return value is a slice of `out_buffer` from index 0.
+/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, the result is encoded as UTF-8.
+/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
+pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
+ if (native_os == .wasi and !builtin.link_libc) {
+ return readlinkat(wasi.AT.FDCWD, file_path, out_buffer);
+ } else if (native_os == .windows) {
+ const file_path_w = try windows.sliceToPrefixedFileW(null, file_path);
+ return readlinkW(file_path_w.span(), out_buffer);
+ } else {
+ const file_path_c = try toPosixPath(file_path);
+ return readlinkZ(&file_path_c, out_buffer);
+ }
+}
+
+/// Windows-only. Same as `readlink` except `file_path` is WTF16 LE encoded.
+/// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// See also `readlinkZ`.
+pub fn readlinkW(file_path: []const u16, out_buffer: []u8) ReadLinkError![]u8 {
+ return windows.ReadLink(fs.cwd().fd, file_path, out_buffer);
+}
+
+/// Same as `readlink` except `file_path` is null-terminated.
+pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 {
+ if (native_os == .windows) {
+ const file_path_w = try windows.cStrToPrefixedFileW(null, file_path);
+ return readlinkW(file_path_w.span(), out_buffer);
+ } else if (native_os == .wasi and !builtin.link_libc) {
+ return readlink(mem.sliceTo(file_path, 0), out_buffer);
+ }
+ const rc = system.readlink(file_path, out_buffer.ptr, out_buffer.len);
+ switch (errno(rc)) {
+ .SUCCESS => return out_buffer[0..@bitCast(rc)],
+ .ACCES => return error.AccessDenied,
+ .FAULT => unreachable,
+ .INVAL => return error.NotLink,
+ .IO => return error.FileSystem,
+ .LOOP => return error.SymLinkLoop,
+ .NAMETOOLONG => return error.NameTooLong,
+ .NOENT => return error.FileNotFound,
+ .NOMEM => return error.SystemResources,
+ .NOTDIR => return error.NotDir,
+ .ILSEQ => |err| if (native_os == .wasi)
+ return error.InvalidUtf8
+ else
+ return unexpectedErrno(err),
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+/// Similar to `readlink` except reads value of a symbolink link **relative** to `dirfd` directory handle.
+/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `file_path` should be encoded as valid UTF-8.
+/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
+/// The return value is a slice of `out_buffer` from index 0.
+/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, the result is encoded as UTF-8.
+/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
+/// See also `readlinkatWasi`, `realinkatZ` and `realinkatW`.
+pub fn readlinkat(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
+ if (native_os == .wasi and !builtin.link_libc) {
+ return readlinkatWasi(dirfd, file_path, out_buffer);
+ }
+ if (native_os == .windows) {
+ const file_path_w = try windows.sliceToPrefixedFileW(dirfd, file_path);
+ return readlinkatW(dirfd, file_path_w.span(), out_buffer);
+ }
+ const file_path_c = try toPosixPath(file_path);
+ return readlinkatZ(dirfd, &file_path_c, out_buffer);
+}
+
+/// WASI-only. Same as `readlinkat` but targets WASI.
+/// See also `readlinkat`.
+pub fn readlinkatWasi(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
+ var bufused: usize = undefined;
+ switch (wasi.path_readlink(dirfd, file_path.ptr, file_path.len, out_buffer.ptr, out_buffer.len, &bufused)) {
+ .SUCCESS => return out_buffer[0..bufused],
+ .ACCES => return error.AccessDenied,
+ .FAULT => unreachable,
+ .INVAL => return error.NotLink,
+ .IO => return error.FileSystem,
+ .LOOP => return error.SymLinkLoop,
+ .NAMETOOLONG => return error.NameTooLong,
+ .NOENT => return error.FileNotFound,
+ .NOMEM => return error.SystemResources,
+ .NOTDIR => return error.NotDir,
+ .NOTCAPABLE => return error.AccessDenied,
+ .ILSEQ => return error.InvalidUtf8,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+/// Windows-only. Same as `readlinkat` except `file_path` is null-terminated, WTF16 LE encoded.
+/// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// See also `readlinkat`.
+pub fn readlinkatW(dirfd: fd_t, file_path: []const u16, out_buffer: []u8) ReadLinkError![]u8 {
+ return windows.ReadLink(dirfd, file_path, out_buffer);
+}
+
+/// Same as `readlinkat` except `file_path` is null-terminated.
+/// See also `readlinkat`.
+pub fn readlinkatZ(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 {
+ if (native_os == .windows) {
+ const file_path_w = try windows.cStrToPrefixedFileW(dirfd, file_path);
+ return readlinkatW(dirfd, file_path_w.span(), out_buffer);
+ } else if (native_os == .wasi and !builtin.link_libc) {
+ return readlinkat(dirfd, mem.sliceTo(file_path, 0), out_buffer);
+ }
+ const rc = system.readlinkat(dirfd, file_path, out_buffer.ptr, out_buffer.len);
+ switch (errno(rc)) {
+ .SUCCESS => return out_buffer[0..@bitCast(rc)],
+ .ACCES => return error.AccessDenied,
+ .FAULT => unreachable,
+ .INVAL => return error.NotLink,
+ .IO => return error.FileSystem,
+ .LOOP => return error.SymLinkLoop,
+ .NAMETOOLONG => return error.NameTooLong,
+ .NOENT => return error.FileNotFound,
+ .NOMEM => return error.SystemResources,
+ .NOTDIR => return error.NotDir,
+ .ILSEQ => |err| if (native_os == .wasi)
+ return error.InvalidUtf8
+ else
+ return unexpectedErrno(err),
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+pub const SetEidError = error{
+ InvalidUserId,
+ PermissionDenied,
+} || UnexpectedError;
+
+pub const SetIdError = error{ResourceLimitReached} || SetEidError;
+
+pub fn setuid(uid: uid_t) SetIdError!void {
+ switch (errno(system.setuid(uid))) {
+ .SUCCESS => return,
+ .AGAIN => return error.ResourceLimitReached,
+ .INVAL => return error.InvalidUserId,
+ .PERM => return error.PermissionDenied,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+pub fn seteuid(uid: uid_t) SetEidError!void {
+ switch (errno(system.seteuid(uid))) {
+ .SUCCESS => return,
+ .INVAL => return error.InvalidUserId,
+ .PERM => return error.PermissionDenied,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+pub fn setreuid(ruid: uid_t, euid: uid_t) SetIdError!void {
+ switch (errno(system.setreuid(ruid, euid))) {
+ .SUCCESS => return,
+ .AGAIN => return error.ResourceLimitReached,
+ .INVAL => return error.InvalidUserId,
+ .PERM => return error.PermissionDenied,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+pub fn setgid(gid: gid_t) SetIdError!void {
+ switch (errno(system.setgid(gid))) {
+ .SUCCESS => return,
+ .AGAIN => return error.ResourceLimitReached,
+ .INVAL => return error.InvalidUserId,
+ .PERM => return error.PermissionDenied,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+pub fn setegid(uid: uid_t) SetEidError!void {
+ switch (errno(system.setegid(uid))) {
+ .SUCCESS => return,
+ .INVAL => return error.InvalidUserId,
+ .PERM => return error.PermissionDenied,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+pub fn setregid(rgid: gid_t, egid: gid_t) SetIdError!void {
+ switch (errno(system.setregid(rgid, egid))) {
+ .SUCCESS => return,
+ .AGAIN => return error.ResourceLimitReached,
+ .INVAL => return error.InvalidUserId,
+ .PERM => return error.PermissionDenied,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+/// Test whether a file descriptor refers to a terminal.
+pub fn isatty(handle: fd_t) bool {
+ if (native_os == .windows) {
+ if (fs.File.isCygwinPty(.{ .handle = handle }))
+ return true;
+
+ var out: windows.DWORD = undefined;
+ return windows.kernel32.GetConsoleMode(handle, &out) != 0;
+ }
+ if (builtin.link_libc) {
+ return system.isatty(handle) != 0;
+ }
+ if (native_os == .wasi) {
+ var statbuf: wasi.fdstat_t = undefined;
+ const err = wasi.fd_fdstat_get(handle, &statbuf);
+ if (err != .SUCCESS)
+ return false;
+
+ // A tty is a character device that we can't seek or tell on.
+ if (statbuf.fs_filetype != .CHARACTER_DEVICE)
+ return false;
+ if (statbuf.fs_rights_base.FD_SEEK or statbuf.fs_rights_base.FD_TELL)
+ return false;
+
+ return true;
+ }
+ if (native_os == .linux) {
+ while (true) {
+ var wsz: linux.winsize = undefined;
+ const fd: usize = @bitCast(@as(isize, handle));
+ const rc = linux.syscall3(.ioctl, fd, linux.T.IOCGWINSZ, @intFromPtr(&wsz));
+ switch (linux.E.init(rc)) {
+ .SUCCESS => return true,
+ .INTR => continue,
+ else => return false,
+ }
+ }
+ }
+ return system.isatty(handle) != 0;
+}
+
+pub const SocketError = error{
+ /// Permission to create a socket of the specified type and/or
+ /// pro‐tocol is denied.
+ PermissionDenied,
+
+ /// The implementation does not support the specified address family.
+ AddressFamilyNotSupported,
+
+ /// Unknown protocol, or protocol family not available.
+ ProtocolFamilyNotAvailable,
+
+ /// The per-process limit on the number of open file descriptors has been reached.
+ ProcessFdQuotaExceeded,
+
+ /// The system-wide limit on the total number of open files has been reached.
+ SystemFdQuotaExceeded,
+
+ /// Insufficient memory is available. The socket cannot be created until sufficient
+ /// resources are freed.
+ SystemResources,
+
+ /// The protocol type or the specified protocol is not supported within this domain.
+ ProtocolNotSupported,
+
+ /// The socket type is not supported by the protocol.
+ SocketTypeNotSupported,
+} || UnexpectedError;
+
+pub fn socket(domain: u32, socket_type: u32, protocol: u32) SocketError!socket_t {
+ if (native_os == .windows) {
+ // NOTE: windows translates the SOCK.NONBLOCK/SOCK.CLOEXEC flags into
+ // windows-analagous operations
+ const filtered_sock_type = socket_type & ~@as(u32, SOCK.NONBLOCK | SOCK.CLOEXEC);
+ const flags: u32 = if ((socket_type & SOCK.CLOEXEC) != 0)
+ windows.ws2_32.WSA_FLAG_NO_HANDLE_INHERIT
+ else
+ 0;
+ const rc = try windows.WSASocketW(
+ @bitCast(domain),
+ @bitCast(filtered_sock_type),
+ @bitCast(protocol),
+ null,
+ 0,
+ flags,
+ );
+ errdefer windows.closesocket(rc) catch unreachable;
+ if ((socket_type & SOCK.NONBLOCK) != 0) {
+ var mode: c_ulong = 1; // nonblocking
+ if (windows.ws2_32.SOCKET_ERROR == windows.ws2_32.ioctlsocket(rc, windows.ws2_32.FIONBIO, &mode)) {
+ switch (windows.ws2_32.WSAGetLastError()) {
+ // have not identified any error codes that should be handled yet
+ else => unreachable,
+ }
+ }
+ }
+ return rc;
+ }
+
+ const have_sock_flags = !builtin.target.isDarwin();
+ const filtered_sock_type = if (!have_sock_flags)
+ socket_type & ~@as(u32, SOCK.NONBLOCK | SOCK.CLOEXEC)
+ else
+ socket_type;
+ const rc = system.socket(domain, filtered_sock_type, protocol);
+ switch (errno(rc)) {
+ .SUCCESS => {
+ const fd: fd_t = @intCast(rc);
+ errdefer close(fd);
+ if (!have_sock_flags) {
+ try setSockFlags(fd, socket_type);
+ }
+ return fd;
+ },
+ .ACCES => return error.PermissionDenied,
+ .AFNOSUPPORT => return error.AddressFamilyNotSupported,
+ .INVAL => return error.ProtocolFamilyNotAvailable,
+ .MFILE => return error.ProcessFdQuotaExceeded,
+ .NFILE => return error.SystemFdQuotaExceeded,
+ .NOBUFS => return error.SystemResources,
+ .NOMEM => return error.SystemResources,
+ .PROTONOSUPPORT => return error.ProtocolNotSupported,
+ .PROTOTYPE => return error.SocketTypeNotSupported,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+pub const ShutdownError = error{
+ ConnectionAborted,
+
+ /// Connection was reset by peer, application should close socket as it is no longer usable.
+ ConnectionResetByPeer,
+ BlockingOperationInProgress,
+
+ /// The network subsystem has failed.
+ NetworkSubsystemFailed,
+
+ /// The socket is not connected (connection-oriented sockets only).
+ SocketNotConnected,
+ SystemResources,
+} || UnexpectedError;
+
+pub const ShutdownHow = enum { recv, send, both };
+
+/// Shutdown socket send/receive operations
+pub fn shutdown(sock: socket_t, how: ShutdownHow) ShutdownError!void {
+ if (native_os == .windows) {
+ const result = windows.ws2_32.shutdown(sock, switch (how) {
+ .recv => windows.ws2_32.SD_RECEIVE,
+ .send => windows.ws2_32.SD_SEND,
+ .both => windows.ws2_32.SD_BOTH,
+ });
+ if (0 != result) switch (windows.ws2_32.WSAGetLastError()) {
+ .WSAECONNABORTED => return error.ConnectionAborted,
+ .WSAECONNRESET => return error.ConnectionResetByPeer,
+ .WSAEINPROGRESS => return error.BlockingOperationInProgress,
+ .WSAEINVAL => unreachable,
+ .WSAENETDOWN => return error.NetworkSubsystemFailed,
+ .WSAENOTCONN => return error.SocketNotConnected,
+ .WSAENOTSOCK => unreachable,
+ .WSANOTINITIALISED => unreachable,
+ else => |err| return windows.unexpectedWSAError(err),
+ };
+ } else {
+ const rc = system.shutdown(sock, switch (how) {
+ .recv => SHUT.RD,
+ .send => SHUT.WR,
+ .both => SHUT.RDWR,
+ });
+ switch (errno(rc)) {
+ .SUCCESS => return,
+ .BADF => unreachable,
+ .INVAL => unreachable,
+ .NOTCONN => return error.SocketNotConnected,
+ .NOTSOCK => unreachable,
+ .NOBUFS => return error.SystemResources,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+}
+
+pub const BindError = error{
+ /// The address is protected, and the user is not the superuser.
+ /// For UNIX domain sockets: Search permission is denied on a component
+ /// of the path prefix.
+ AccessDenied,
+
+ /// The given address is already in use, or in the case of Internet domain sockets,
+ /// The port number was specified as zero in the socket
+ /// address structure, but, upon attempting to bind to an ephemeral port, it was
+ /// determined that all port numbers in the ephemeral port range are currently in
+ /// use. See the discussion of /proc/sys/net/ipv4/ip_local_port_range ip(7).
+ AddressInUse,
+
+ /// A nonexistent interface was requested or the requested address was not local.
+ AddressNotAvailable,
+
+ /// The address is not valid for the address family of socket.
+ AddressFamilyNotSupported,
+
+ /// Too many symbolic links were encountered in resolving addr.
+ SymLinkLoop,
+
+ /// addr is too long.
+ NameTooLong,
+
+ /// A component in the directory prefix of the socket pathname does not exist.
+ FileNotFound,
+
+ /// Insufficient kernel memory was available.
+ SystemResources,
+
+ /// A component of the path prefix is not a directory.
+ NotDir,
+
+ /// The socket inode would reside on a read-only filesystem.
+ ReadOnlyFileSystem,
+
+ /// The network subsystem has failed.
+ NetworkSubsystemFailed,
+
+ FileDescriptorNotASocket,
+
+ AlreadyBound,
+} || UnexpectedError;
+
+/// addr is `*const T` where T is one of the sockaddr
+pub fn bind(sock: socket_t, addr: *const sockaddr, len: socklen_t) BindError!void {
+ if (native_os == .windows) {
+ const rc = windows.bind(sock, addr, len);
+ if (rc == windows.ws2_32.SOCKET_ERROR) {
+ switch (windows.ws2_32.WSAGetLastError()) {
+ .WSANOTINITIALISED => unreachable, // not initialized WSA
+ .WSAEACCES => return error.AccessDenied,
+ .WSAEADDRINUSE => return error.AddressInUse,
+ .WSAEADDRNOTAVAIL => return error.AddressNotAvailable,
+ .WSAENOTSOCK => return error.FileDescriptorNotASocket,
+ .WSAEFAULT => unreachable, // invalid pointers
+ .WSAEINVAL => return error.AlreadyBound,
+ .WSAENOBUFS => return error.SystemResources,
+ .WSAENETDOWN => return error.NetworkSubsystemFailed,
+ else => |err| return windows.unexpectedWSAError(err),
+ }
+ unreachable;
+ }
+ return;
+ } else {
+ const rc = system.bind(sock, addr, len);
+ switch (errno(rc)) {
+ .SUCCESS => return,
+ .ACCES, .PERM => return error.AccessDenied,
+ .ADDRINUSE => return error.AddressInUse,
+ .BADF => unreachable, // always a race condition if this error is returned
+ .INVAL => unreachable, // invalid parameters
+ .NOTSOCK => unreachable, // invalid `sockfd`
+ .AFNOSUPPORT => return error.AddressFamilyNotSupported,
+ .ADDRNOTAVAIL => return error.AddressNotAvailable,
+ .FAULT => unreachable, // invalid `addr` pointer
+ .LOOP => return error.SymLinkLoop,
+ .NAMETOOLONG => return error.NameTooLong,
+ .NOENT => return error.FileNotFound,
+ .NOMEM => return error.SystemResources,
+ .NOTDIR => return error.NotDir,
+ .ROFS => return error.ReadOnlyFileSystem,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+ unreachable;
+}
+
+pub const ListenError = error{
+ /// Another socket is already listening on the same port.
+ /// For Internet domain sockets, the socket referred to by sockfd had not previously
+ /// been bound to an address and, upon attempting to bind it to an ephemeral port, it
+ /// was determined that all port numbers in the ephemeral port range are currently in
+ /// use. See the discussion of /proc/sys/net/ipv4/ip_local_port_range in ip(7).
+ AddressInUse,
+
+ /// The file descriptor sockfd does not refer to a socket.
+ FileDescriptorNotASocket,
+
+ /// The socket is not of a type that supports the listen() operation.
+ OperationNotSupported,
+
+ /// The network subsystem has failed.
+ NetworkSubsystemFailed,
+
+ /// Ran out of system resources
+ /// On Windows it can either run out of socket descriptors or buffer space
+ SystemResources,
+
+ /// Already connected
+ AlreadyConnected,
+
+ /// Socket has not been bound yet
+ SocketNotBound,
+} || UnexpectedError;
+
+pub fn listen(sock: socket_t, backlog: u31) ListenError!void {
+ if (native_os == .windows) {
+ const rc = windows.listen(sock, backlog);
+ if (rc == windows.ws2_32.SOCKET_ERROR) {
+ switch (windows.ws2_32.WSAGetLastError()) {
+ .WSANOTINITIALISED => unreachable, // not initialized WSA
+ .WSAENETDOWN => return error.NetworkSubsystemFailed,
+ .WSAEADDRINUSE => return error.AddressInUse,
+ .WSAEISCONN => return error.AlreadyConnected,
+ .WSAEINVAL => return error.SocketNotBound,
+ .WSAEMFILE, .WSAENOBUFS => return error.SystemResources,
+ .WSAENOTSOCK => return error.FileDescriptorNotASocket,
+ .WSAEOPNOTSUPP => return error.OperationNotSupported,
+ .WSAEINPROGRESS => unreachable,
+ else => |err| return windows.unexpectedWSAError(err),
+ }
+ }
+ return;
+ } else {
+ const rc = system.listen(sock, backlog);
+ switch (errno(rc)) {
+ .SUCCESS => return,
+ .ADDRINUSE => return error.AddressInUse,
+ .BADF => unreachable,
+ .NOTSOCK => return error.FileDescriptorNotASocket,
+ .OPNOTSUPP => return error.OperationNotSupported,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+}
+
+pub const AcceptError = error{
+ ConnectionAborted,
+
+ /// The file descriptor sockfd does not refer to a socket.
+ FileDescriptorNotASocket,
+
+ /// The per-process limit on the number of open file descriptors has been reached.
+ ProcessFdQuotaExceeded,
+
+ /// The system-wide limit on the total number of open files has been reached.
+ SystemFdQuotaExceeded,
+
+ /// Not enough free memory. This often means that the memory allocation is limited
+ /// by the socket buffer limits, not by the system memory.
+ SystemResources,
+
+ /// Socket is not listening for new connections.
+ SocketNotListening,
+
+ ProtocolFailure,
+
+ /// Firewall rules forbid connection.
+ BlockedByFirewall,
+
+ /// This error occurs when no global event loop is configured,
+ /// and accepting from the socket would block.
+ WouldBlock,
+
+ /// An incoming connection was indicated, but was subsequently terminated by the
+ /// remote peer prior to accepting the call.
+ ConnectionResetByPeer,
+
+ /// The network subsystem has failed.
+ NetworkSubsystemFailed,
+
+ /// The referenced socket is not a type that supports connection-oriented service.
+ OperationNotSupported,
+} || UnexpectedError;
+
+/// Accept a connection on a socket.
+/// If `sockfd` is opened in non blocking mode, the function will
+/// return error.WouldBlock when EAGAIN is received.
+pub fn accept(
+ /// This argument is a socket that has been created with `socket`, bound to a local address
+ /// with `bind`, and is listening for connections after a `listen`.
+ sock: socket_t,
+ /// This argument is a pointer to a sockaddr structure. This structure is filled in with the
+ /// address of the peer socket, as known to the communications layer. The exact format of the
+ /// address returned addr is determined by the socket's address family (see `socket` and the
+ /// respective protocol man pages).
+ addr: ?*sockaddr,
+ /// This argument is a value-result argument: the caller must initialize it to contain the
+ /// size (in bytes) of the structure pointed to by addr; on return it will contain the actual size
+ /// of the peer address.
+ ///
+ /// The returned address is truncated if the buffer provided is too small; in this case, `addr_size`
+ /// will return a value greater than was supplied to the call.
+ addr_size: ?*socklen_t,
+ /// The following values can be bitwise ORed in flags to obtain different behavior:
+ /// * `SOCK.NONBLOCK` - Set the `NONBLOCK` file status flag on the open file description (see `open`)
+ /// referred to by the new file descriptor. Using this flag saves extra calls to `fcntl` to achieve
+ /// the same result.
+ /// * `SOCK.CLOEXEC` - Set the close-on-exec (`FD_CLOEXEC`) flag on the new file descriptor. See the
+ /// description of the `CLOEXEC` flag in `open` for reasons why this may be useful.
+ flags: u32,
+) AcceptError!socket_t {
+ const have_accept4 = !(builtin.target.isDarwin() or native_os == .windows);
+ assert(0 == (flags & ~@as(u32, SOCK.NONBLOCK | SOCK.CLOEXEC))); // Unsupported flag(s)
+
+ const accepted_sock: socket_t = while (true) {
+ const rc = if (have_accept4)
+ system.accept4(sock, addr, addr_size, flags)
+ else if (native_os == .windows)
+ windows.accept(sock, addr, addr_size)
+ else
+ system.accept(sock, addr, addr_size);
+
+ if (native_os == .windows) {
+ if (rc == windows.ws2_32.INVALID_SOCKET) {
+ switch (windows.ws2_32.WSAGetLastError()) {
+ .WSANOTINITIALISED => unreachable, // not initialized WSA
+ .WSAECONNRESET => return error.ConnectionResetByPeer,
+ .WSAEFAULT => unreachable,
+ .WSAEINVAL => return error.SocketNotListening,
+ .WSAEMFILE => return error.ProcessFdQuotaExceeded,
+ .WSAENETDOWN => return error.NetworkSubsystemFailed,
+ .WSAENOBUFS => return error.FileDescriptorNotASocket,
+ .WSAEOPNOTSUPP => return error.OperationNotSupported,
+ .WSAEWOULDBLOCK => return error.WouldBlock,
+ else => |err| return windows.unexpectedWSAError(err),
+ }
+ } else {
+ break rc;
+ }
+ } else {
+ switch (errno(rc)) {
+ .SUCCESS => break @intCast(rc),
+ .INTR => continue,
+ .AGAIN => return error.WouldBlock,
+ .BADF => unreachable, // always a race condition
+ .CONNABORTED => return error.ConnectionAborted,
+ .FAULT => unreachable,
+ .INVAL => return error.SocketNotListening,
+ .NOTSOCK => unreachable,
+ .MFILE => return error.ProcessFdQuotaExceeded,
+ .NFILE => return error.SystemFdQuotaExceeded,
+ .NOBUFS => return error.SystemResources,
+ .NOMEM => return error.SystemResources,
+ .OPNOTSUPP => unreachable,
+ .PROTO => return error.ProtocolFailure,
+ .PERM => return error.BlockedByFirewall,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+ };
+
+ errdefer switch (native_os) {
+ .windows => windows.closesocket(accepted_sock) catch unreachable,
+ else => close(accepted_sock),
+ };
+ if (!have_accept4) {
+ try setSockFlags(accepted_sock, flags);
+ }
+ return accepted_sock;
+}
+
+fn setSockFlags(sock: socket_t, flags: u32) !void {
+ if ((flags & SOCK.CLOEXEC) != 0) {
+ if (native_os == .windows) {
+ // TODO: Find out if this is supported for sockets
+ } else {
+ var fd_flags = fcntl(sock, F.GETFD, 0) catch |err| switch (err) {
+ error.FileBusy => unreachable,
+ error.Locked => unreachable,
+ error.PermissionDenied => unreachable,
+ error.DeadLock => unreachable,
+ error.LockedRegionLimitExceeded => unreachable,
+ else => |e| return e,
+ };
+ fd_flags |= FD_CLOEXEC;
+ _ = fcntl(sock, F.SETFD, fd_flags) catch |err| switch (err) {
+ error.FileBusy => unreachable,
+ error.Locked => unreachable,
+ error.PermissionDenied => unreachable,
+ error.DeadLock => unreachable,
+ error.LockedRegionLimitExceeded => unreachable,
+ else => |e| return e,
+ };
+ }
+ }
+ if ((flags & SOCK.NONBLOCK) != 0) {
+ if (native_os == .windows) {
+ var mode: c_ulong = 1;
+ if (windows.ws2_32.ioctlsocket(sock, windows.ws2_32.FIONBIO, &mode) == windows.ws2_32.SOCKET_ERROR) {
+ switch (windows.ws2_32.WSAGetLastError()) {
+ .WSANOTINITIALISED => unreachable,
+ .WSAENETDOWN => return error.NetworkSubsystemFailed,
+ .WSAENOTSOCK => return error.FileDescriptorNotASocket,
+ // TODO: handle more errors
+ else => |err| return windows.unexpectedWSAError(err),
+ }
+ }
+ } else {
+ var fl_flags = fcntl(sock, F.GETFL, 0) catch |err| switch (err) {
+ error.FileBusy => unreachable,
+ error.Locked => unreachable,
+ error.PermissionDenied => unreachable,
+ error.DeadLock => unreachable,
+ error.LockedRegionLimitExceeded => unreachable,
+ else => |e| return e,
+ };
+ fl_flags |= 1 << @bitOffsetOf(O, "NONBLOCK");
+ _ = fcntl(sock, F.SETFL, fl_flags) catch |err| switch (err) {
+ error.FileBusy => unreachable,
+ error.Locked => unreachable,
+ error.PermissionDenied => unreachable,
+ error.DeadLock => unreachable,
+ error.LockedRegionLimitExceeded => unreachable,
+ else => |e| return e,
+ };
+ }
+ }
+}
+
+pub const EpollCreateError = error{
+ /// The per-user limit on the number of epoll instances imposed by
+ /// /proc/sys/fs/epoll/max_user_instances was encountered. See epoll(7) for further
+ /// details.
+ /// Or, The per-process limit on the number of open file descriptors has been reached.
+ ProcessFdQuotaExceeded,
+
+ /// The system-wide limit on the total number of open files has been reached.
+ SystemFdQuotaExceeded,
+
+ /// There was insufficient memory to create the kernel object.
+ SystemResources,
+} || UnexpectedError;
+
+pub fn epoll_create1(flags: u32) EpollCreateError!i32 {
+ const rc = system.epoll_create1(flags);
+ switch (errno(rc)) {
+ .SUCCESS => return @intCast(rc),
+ else => |err| return unexpectedErrno(err),
+
+ .INVAL => unreachable,
+ .MFILE => return error.ProcessFdQuotaExceeded,
+ .NFILE => return error.SystemFdQuotaExceeded,
+ .NOMEM => return error.SystemResources,
+ }
+}
+
+pub const EpollCtlError = error{
+ /// op was EPOLL_CTL_ADD, and the supplied file descriptor fd is already registered
+ /// with this epoll instance.
+ FileDescriptorAlreadyPresentInSet,
+
+ /// fd refers to an epoll instance and this EPOLL_CTL_ADD operation would result in a
+ /// circular loop of epoll instances monitoring one another.
+ OperationCausesCircularLoop,
+
+ /// op was EPOLL_CTL_MOD or EPOLL_CTL_DEL, and fd is not registered with this epoll
+ /// instance.
+ FileDescriptorNotRegistered,
+
+ /// There was insufficient memory to handle the requested op control operation.
+ SystemResources,
+
+ /// The limit imposed by /proc/sys/fs/epoll/max_user_watches was encountered while
+ /// trying to register (EPOLL_CTL_ADD) a new file descriptor on an epoll instance.
+ /// See epoll(7) for further details.
+ UserResourceLimitReached,
+
+ /// The target file fd does not support epoll. This error can occur if fd refers to,
+ /// for example, a regular file or a directory.
+ FileDescriptorIncompatibleWithEpoll,
+} || UnexpectedError;
+
+pub fn epoll_ctl(epfd: i32, op: u32, fd: i32, event: ?*linux.epoll_event) EpollCtlError!void {
+ const rc = system.epoll_ctl(epfd, op, fd, event);
+ switch (errno(rc)) {
+ .SUCCESS => return,
+ else => |err| return unexpectedErrno(err),
+
+ .BADF => unreachable, // always a race condition if this happens
+ .EXIST => return error.FileDescriptorAlreadyPresentInSet,
+ .INVAL => unreachable,
+ .LOOP => return error.OperationCausesCircularLoop,
+ .NOENT => return error.FileDescriptorNotRegistered,
+ .NOMEM => return error.SystemResources,
+ .NOSPC => return error.UserResourceLimitReached,
+ .PERM => return error.FileDescriptorIncompatibleWithEpoll,
+ }
+}
+
+/// Waits for an I/O event on an epoll file descriptor.
+/// Returns the number of file descriptors ready for the requested I/O,
+/// or zero if no file descriptor became ready during the requested timeout milliseconds.
+pub fn epoll_wait(epfd: i32, events: []linux.epoll_event, timeout: i32) usize {
+ while (true) {
+ // TODO get rid of the @intCast
+ const rc = system.epoll_wait(epfd, events.ptr, @intCast(events.len), timeout);
+ switch (errno(rc)) {
+ .SUCCESS => return @intCast(rc),
+ .INTR => continue,
+ .BADF => unreachable,
+ .FAULT => unreachable,
+ .INVAL => unreachable,
+ else => unreachable,
+ }
+ }
+}
+
+pub const EventFdError = error{
+ SystemResources,
+ ProcessFdQuotaExceeded,
+ SystemFdQuotaExceeded,
+} || UnexpectedError;
+
+pub fn eventfd(initval: u32, flags: u32) EventFdError!i32 {
+ const rc = system.eventfd(initval, flags);
+ switch (errno(rc)) {
+ .SUCCESS => return @intCast(rc),
+ else => |err| return unexpectedErrno(err),
+
+ .INVAL => unreachable, // invalid parameters
+ .MFILE => return error.ProcessFdQuotaExceeded,
+ .NFILE => return error.SystemFdQuotaExceeded,
+ .NODEV => return error.SystemResources,
+ .NOMEM => return error.SystemResources,
+ }
+}
+
+pub const GetSockNameError = error{
+ /// Insufficient resources were available in the system to perform the operation.
+ SystemResources,
+
+ /// The network subsystem has failed.
+ NetworkSubsystemFailed,
+
+ /// Socket hasn't been bound yet
+ SocketNotBound,
+
+ FileDescriptorNotASocket,
+} || UnexpectedError;
+
+pub fn getsockname(sock: socket_t, addr: *sockaddr, addrlen: *socklen_t) GetSockNameError!void {
+ if (native_os == .windows) {
+ const rc = windows.getsockname(sock, addr, addrlen);
+ if (rc == windows.ws2_32.SOCKET_ERROR) {
+ switch (windows.ws2_32.WSAGetLastError()) {
+ .WSANOTINITIALISED => unreachable,
+ .WSAENETDOWN => return error.NetworkSubsystemFailed,
+ .WSAEFAULT => unreachable, // addr or addrlen have invalid pointers or addrlen points to an incorrect value
+ .WSAENOTSOCK => return error.FileDescriptorNotASocket,
+ .WSAEINVAL => return error.SocketNotBound,
+ else => |err| return windows.unexpectedWSAError(err),
+ }
+ }
+ return;
+ } else {
+ const rc = system.getsockname(sock, addr, addrlen);
+ switch (errno(rc)) {
+ .SUCCESS => return,
+ else => |err| return unexpectedErrno(err),
+
+ .BADF => unreachable, // always a race condition
+ .FAULT => unreachable,
+ .INVAL => unreachable, // invalid parameters
+ .NOTSOCK => return error.FileDescriptorNotASocket,
+ .NOBUFS => return error.SystemResources,
+ }
+ }
+}
+
+pub fn getpeername(sock: socket_t, addr: *sockaddr, addrlen: *socklen_t) GetSockNameError!void {
+ if (native_os == .windows) {
+ const rc = windows.getpeername(sock, addr, addrlen);
+ if (rc == windows.ws2_32.SOCKET_ERROR) {
+ switch (windows.ws2_32.WSAGetLastError()) {
+ .WSANOTINITIALISED => unreachable,
+ .WSAENETDOWN => return error.NetworkSubsystemFailed,
+ .WSAEFAULT => unreachable, // addr or addrlen have invalid pointers or addrlen points to an incorrect value
+ .WSAENOTSOCK => return error.FileDescriptorNotASocket,
+ .WSAEINVAL => return error.SocketNotBound,
+ else => |err| return windows.unexpectedWSAError(err),
+ }
+ }
+ return;
+ } else {
+ const rc = system.getpeername(sock, addr, addrlen);
+ switch (errno(rc)) {
+ .SUCCESS => return,
+ else => |err| return unexpectedErrno(err),
+
+ .BADF => unreachable, // always a race condition
+ .FAULT => unreachable,
+ .INVAL => unreachable, // invalid parameters
+ .NOTSOCK => return error.FileDescriptorNotASocket,
+ .NOBUFS => return error.SystemResources,
+ }
+ }
+}
+
+pub const ConnectError = error{
+ /// For UNIX domain sockets, which are identified by pathname: Write permission is denied on the socket
+ /// file, or search permission is denied for one of the directories in the path prefix.
+ /// or
+ /// The user tried to connect to a broadcast address without having the socket broadcast flag enabled or
+ /// the connection request failed because of a local firewall rule.
+ PermissionDenied,
+
+ /// Local address is already in use.
+ AddressInUse,
+
+ /// (Internet domain sockets) The socket referred to by sockfd had not previously been bound to an
+ /// address and, upon attempting to bind it to an ephemeral port, it was determined that all port numbers
+ /// in the ephemeral port range are currently in use. See the discussion of
+ /// /proc/sys/net/ipv4/ip_local_port_range in ip(7).
+ AddressNotAvailable,
+
+ /// The passed address didn't have the correct address family in its sa_family field.
+ AddressFamilyNotSupported,
+
+ /// Insufficient entries in the routing cache.
+ SystemResources,
+
+ /// A connect() on a stream socket found no one listening on the remote address.
+ ConnectionRefused,
+
+ /// Network is unreachable.
+ NetworkUnreachable,
+
+ /// Timeout while attempting connection. The server may be too busy to accept new connections. Note
+ /// that for IP sockets the timeout may be very long when syncookies are enabled on the server.
+ ConnectionTimedOut,
+
+ /// This error occurs when no global event loop is configured,
+ /// and connecting to the socket would block.
+ WouldBlock,
+
+ /// The given path for the unix socket does not exist.
+ FileNotFound,
+
+ /// Connection was reset by peer before connect could complete.
+ ConnectionResetByPeer,
+
+ /// Socket is non-blocking and already has a pending connection in progress.
+ ConnectionPending,
+} || UnexpectedError;
+
+/// Initiate a connection on a socket.
+/// If `sockfd` is opened in non blocking mode, the function will
+/// return error.WouldBlock when EAGAIN or EINPROGRESS is received.
+pub fn connect(sock: socket_t, sock_addr: *const sockaddr, len: socklen_t) ConnectError!void {
+ if (native_os == .windows) {
+ const rc = windows.ws2_32.connect(sock, sock_addr, @intCast(len));
+ if (rc == 0) return;
+ switch (windows.ws2_32.WSAGetLastError()) {
+ .WSAEADDRINUSE => return error.AddressInUse,
+ .WSAEADDRNOTAVAIL => return error.AddressNotAvailable,
+ .WSAECONNREFUSED => return error.ConnectionRefused,
+ .WSAECONNRESET => return error.ConnectionResetByPeer,
+ .WSAETIMEDOUT => return error.ConnectionTimedOut,
+ .WSAEHOSTUNREACH, // TODO: should we return NetworkUnreachable in this case as well?
+ .WSAENETUNREACH,
+ => return error.NetworkUnreachable,
+ .WSAEFAULT => unreachable,
+ .WSAEINVAL => unreachable,
+ .WSAEISCONN => unreachable,
+ .WSAENOTSOCK => unreachable,
+ .WSAEWOULDBLOCK => return error.WouldBlock,
+ .WSAEACCES => unreachable,
+ .WSAENOBUFS => return error.SystemResources,
+ .WSAEAFNOSUPPORT => return error.AddressFamilyNotSupported,
+ else => |err| return windows.unexpectedWSAError(err),
+ }
+ return;
+ }
+
+ while (true) {
+ switch (errno(system.connect(sock, sock_addr, len))) {
+ .SUCCESS => return,
+ .ACCES => return error.PermissionDenied,
+ .PERM => return error.PermissionDenied,
+ .ADDRINUSE => return error.AddressInUse,
+ .ADDRNOTAVAIL => return error.AddressNotAvailable,
+ .AFNOSUPPORT => return error.AddressFamilyNotSupported,
+ .AGAIN, .INPROGRESS => return error.WouldBlock,
+ .ALREADY => return error.ConnectionPending,
+ .BADF => unreachable, // sockfd is not a valid open file descriptor.
+ .CONNREFUSED => return error.ConnectionRefused,
+ .CONNRESET => return error.ConnectionResetByPeer,
+ .FAULT => unreachable, // The socket structure address is outside the user's address space.
+ .INTR => continue,
+ .ISCONN => unreachable, // The socket is already connected.
+ .HOSTUNREACH => return error.NetworkUnreachable,
+ .NETUNREACH => return error.NetworkUnreachable,
+ .NOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.
+ .PROTOTYPE => unreachable, // The socket type does not support the requested communications protocol.
+ .TIMEDOUT => return error.ConnectionTimedOut,
+ .NOENT => return error.FileNotFound, // Returned when socket is AF.UNIX and the given path does not exist.
+ .CONNABORTED => unreachable, // Tried to reuse socket that previously received error.ConnectionRefused.
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+}
+
+pub fn getsockoptError(sockfd: fd_t) ConnectError!void {
+ var err_code: i32 = undefined;
+ var size: u32 = @sizeOf(u32);
+ const rc = system.getsockopt(sockfd, SOL.SOCKET, SO.ERROR, @ptrCast(&err_code), &size);
+ assert(size == 4);
+ switch (errno(rc)) {
+ .SUCCESS => switch (@as(E, @enumFromInt(err_code))) {
+ .SUCCESS => return,
+ .ACCES => return error.PermissionDenied,
+ .PERM => return error.PermissionDenied,
+ .ADDRINUSE => return error.AddressInUse,
+ .ADDRNOTAVAIL => return error.AddressNotAvailable,
+ .AFNOSUPPORT => return error.AddressFamilyNotSupported,
+ .AGAIN => return error.SystemResources,
+ .ALREADY => return error.ConnectionPending,
+ .BADF => unreachable, // sockfd is not a valid open file descriptor.
+ .CONNREFUSED => return error.ConnectionRefused,
+ .FAULT => unreachable, // The socket structure address is outside the user's address space.
+ .ISCONN => unreachable, // The socket is already connected.
+ .HOSTUNREACH => return error.NetworkUnreachable,
+ .NETUNREACH => return error.NetworkUnreachable,
+ .NOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.
+ .PROTOTYPE => unreachable, // The socket type does not support the requested communications protocol.
+ .TIMEDOUT => return error.ConnectionTimedOut,
+ .CONNRESET => return error.ConnectionResetByPeer,
+ else => |err| return unexpectedErrno(err),
+ },
+ .BADF => unreachable, // The argument sockfd is not a valid file descriptor.
+ .FAULT => unreachable, // The address pointed to by optval or optlen is not in a valid part of the process address space.
+ .INVAL => unreachable,
+ .NOPROTOOPT => unreachable, // The option is unknown at the level indicated.
+ .NOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+pub const WaitPidResult = struct {
+ pid: pid_t,
+ status: u32,
+};
+
+/// Use this version of the `waitpid` wrapper if you spawned your child process using explicit
+/// `fork` and `execve` method.
+pub fn waitpid(pid: pid_t, flags: u32) WaitPidResult {
+ var status: if (builtin.link_libc) c_int else u32 = undefined;
+ while (true) {
+ const rc = system.waitpid(pid, &status, @intCast(flags));
+ switch (errno(rc)) {
+ .SUCCESS => return .{
+ .pid = @intCast(rc),
+ .status = @bitCast(status),
+ },
+ .INTR => continue,
+ .CHILD => unreachable, // The process specified does not exist. It would be a race condition to handle this error.
+ .INVAL => unreachable, // Invalid flags.
+ else => unreachable,
+ }
+ }
+}
+
+pub fn wait4(pid: pid_t, flags: u32, ru: ?*rusage) WaitPidResult {
+ var status: if (builtin.link_libc) c_int else u32 = undefined;
+ while (true) {
+ const rc = system.wait4(pid, &status, @intCast(flags), ru);
+ switch (errno(rc)) {
+ .SUCCESS => return .{
+ .pid = @intCast(rc),
+ .status = @bitCast(status),
+ },
+ .INTR => continue,
+ .CHILD => unreachable, // The process specified does not exist. It would be a race condition to handle this error.
+ .INVAL => unreachable, // Invalid flags.
+ else => unreachable,
+ }
+ }
+}
+
+pub const FStatError = error{
+ SystemResources,
+
+ /// In WASI, this error may occur when the file descriptor does
+ /// not hold the required rights to get its filestat information.
+ AccessDenied,
+} || UnexpectedError;
+
+/// Return information about a file descriptor.
+pub fn fstat(fd: fd_t) FStatError!Stat {
+ if (native_os == .wasi and !builtin.link_libc) {
+ return Stat.fromFilestat(try fstat_wasi(fd));
+ }
+ if (native_os == .windows) {
+ @compileError("fstat is not yet implemented on Windows");
+ }
+
+ const fstat_sym = if (lfs64_abi) system.fstat64 else system.fstat;
+ var stat = mem.zeroes(Stat);
+ switch (errno(fstat_sym(fd, &stat))) {
+ .SUCCESS => return stat,
+ .INVAL => unreachable,
+ .BADF => unreachable, // Always a race condition.
+ .NOMEM => return error.SystemResources,
+ .ACCES => return error.AccessDenied,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+fn fstat_wasi(fd: fd_t) FStatError!wasi.filestat_t {
+ var stat: wasi.filestat_t = undefined;
+ switch (wasi.fd_filestat_get(fd, &stat)) {
+ .SUCCESS => return stat,
+ .INVAL => unreachable,
+ .BADF => unreachable, // Always a race condition.
+ .NOMEM => return error.SystemResources,
+ .ACCES => return error.AccessDenied,
+ .NOTCAPABLE => return error.AccessDenied,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+pub const FStatAtError = FStatError || error{
+ NameTooLong,
+ FileNotFound,
+ SymLinkLoop,
+ /// WASI-only; file paths must be valid UTF-8.
+ InvalidUtf8,
+};
+
+/// Similar to `fstat`, but returns stat of a resource pointed to by `pathname`
+/// which is relative to `dirfd` handle.
+/// On WASI, `pathname` should be encoded as valid UTF-8.
+/// On other platforms, `pathname` is an opaque sequence of bytes with no particular encoding.
+/// See also `fstatatZ` and `fstatat_wasi`.
+pub fn fstatat(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!Stat {
+ if (native_os == .wasi and !builtin.link_libc) {
+ const filestat = try fstatat_wasi(dirfd, pathname, .{
+ .SYMLINK_FOLLOW = (flags & AT.SYMLINK_NOFOLLOW) == 0,
+ });
+ return Stat.fromFilestat(filestat);
+ } else if (native_os == .windows) {
+ @compileError("fstatat is not yet implemented on Windows");
+ } else {
+ const pathname_c = try toPosixPath(pathname);
+ return fstatatZ(dirfd, &pathname_c, flags);
+ }
+}
+
+/// Same as `fstatat` but `pathname` is null-terminated.
+/// See also `fstatat`.
+pub fn fstatatZ(dirfd: fd_t, pathname: [*:0]const u8, flags: u32) FStatAtError!Stat {
+ if (native_os == .wasi and !builtin.link_libc) {
+ const filestat = try fstatat_wasi(dirfd, mem.sliceTo(pathname, 0), .{
+ .SYMLINK_FOLLOW = (flags & AT.SYMLINK_NOFOLLOW) == 0,
+ });
+ return Stat.fromFilestat(filestat);
+ }
+
+ const fstatat_sym = if (lfs64_abi) system.fstatat64 else system.fstatat;
+ var stat = mem.zeroes(Stat);
+ switch (errno(fstatat_sym(dirfd, pathname, &stat, flags))) {
+ .SUCCESS => return stat,
+ .INVAL => unreachable,
+ .BADF => unreachable, // Always a race condition.
+ .NOMEM => return error.SystemResources,
+ .ACCES => return error.AccessDenied,
+ .PERM => return error.AccessDenied,
+ .FAULT => unreachable,
+ .NAMETOOLONG => return error.NameTooLong,
+ .LOOP => return error.SymLinkLoop,
+ .NOENT => return error.FileNotFound,
+ .NOTDIR => return error.FileNotFound,
+ .ILSEQ => |err| if (native_os == .wasi)
+ return error.InvalidUtf8
+ else
+ return unexpectedErrno(err),
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+/// WASI-only. Same as `fstatat` but targeting WASI.
+/// `pathname` should be encoded as valid UTF-8.
+/// See also `fstatat`.
+fn fstatat_wasi(dirfd: fd_t, pathname: []const u8, flags: wasi.lookupflags_t) FStatAtError!wasi.filestat_t {
+ var stat: wasi.filestat_t = undefined;
+ switch (wasi.path_filestat_get(dirfd, flags, pathname.ptr, pathname.len, &stat)) {
+ .SUCCESS => return stat,
+ .INVAL => unreachable,
+ .BADF => unreachable, // Always a race condition.
+ .NOMEM => return error.SystemResources,
+ .ACCES => return error.AccessDenied,
+ .FAULT => unreachable,
+ .NAMETOOLONG => return error.NameTooLong,
+ .NOENT => return error.FileNotFound,
+ .NOTDIR => return error.FileNotFound,
+ .NOTCAPABLE => return error.AccessDenied,
+ .ILSEQ => return error.InvalidUtf8,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+pub const KQueueError = error{
+ /// The per-process limit on the number of open file descriptors has been reached.
+ ProcessFdQuotaExceeded,
+
+ /// The system-wide limit on the total number of open files has been reached.
+ SystemFdQuotaExceeded,
+} || UnexpectedError;
+
+pub fn kqueue() KQueueError!i32 {
+ const rc = system.kqueue();
+ switch (errno(rc)) {
+ .SUCCESS => return @intCast(rc),
+ .MFILE => return error.ProcessFdQuotaExceeded,
+ .NFILE => return error.SystemFdQuotaExceeded,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+pub const KEventError = error{
+ /// The process does not have permission to register a filter.
+ AccessDenied,
+
+ /// The event could not be found to be modified or deleted.
+ EventNotFound,
+
+ /// No memory was available to register the event.
+ SystemResources,
+
+ /// The specified process to attach to does not exist.
+ ProcessNotFound,
+
+ /// changelist or eventlist had too many items on it.
+ /// TODO remove this possibility
+ Overflow,
+};
+
+pub fn kevent(
+ kq: i32,
+ changelist: []const Kevent,
+ eventlist: []Kevent,
+ timeout: ?*const timespec,
+) KEventError!usize {
+ while (true) {
+ const rc = system.kevent(
+ kq,
+ changelist.ptr,
+ cast(c_int, changelist.len) orelse return error.Overflow,
+ eventlist.ptr,
+ cast(c_int, eventlist.len) orelse return error.Overflow,
+ timeout,
+ );
+ switch (errno(rc)) {
+ .SUCCESS => return @intCast(rc),
+ .ACCES => return error.AccessDenied,
+ .FAULT => unreachable,
+ .BADF => unreachable, // Always a race condition.
+ .INTR => continue,
+ .INVAL => unreachable,
+ .NOENT => return error.EventNotFound,
+ .NOMEM => return error.SystemResources,
+ .SRCH => return error.ProcessNotFound,
+ else => unreachable,
+ }
+ }
+}
+
+pub const INotifyInitError = error{
+ ProcessFdQuotaExceeded,
+ SystemFdQuotaExceeded,
+ SystemResources,
+} || UnexpectedError;
+
+/// initialize an inotify instance
+pub fn inotify_init1(flags: u32) INotifyInitError!i32 {
+ const rc = system.inotify_init1(flags);
+ switch (errno(rc)) {
+ .SUCCESS => return @intCast(rc),
+ .INVAL => unreachable,
+ .MFILE => return error.ProcessFdQuotaExceeded,
+ .NFILE => return error.SystemFdQuotaExceeded,
+ .NOMEM => return error.SystemResources,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+pub const INotifyAddWatchError = error{
+ AccessDenied,
+ NameTooLong,
+ FileNotFound,
+ SystemResources,
+ UserResourceLimitReached,
+ NotDir,
+ WatchAlreadyExists,
+} || UnexpectedError;
+
+/// add a watch to an initialized inotify instance
+pub fn inotify_add_watch(inotify_fd: i32, pathname: []const u8, mask: u32) INotifyAddWatchError!i32 {
+ const pathname_c = try toPosixPath(pathname);
+ return inotify_add_watchZ(inotify_fd, &pathname_c, mask);
+}
+
+/// Same as `inotify_add_watch` except pathname is null-terminated.
+pub fn inotify_add_watchZ(inotify_fd: i32, pathname: [*:0]const u8, mask: u32) INotifyAddWatchError!i32 {
+ const rc = system.inotify_add_watch(inotify_fd, pathname, mask);
+ switch (errno(rc)) {
+ .SUCCESS => return @intCast(rc),
+ .ACCES => return error.AccessDenied,
+ .BADF => unreachable,
+ .FAULT => unreachable,
+ .INVAL => unreachable,
+ .NAMETOOLONG => return error.NameTooLong,
+ .NOENT => return error.FileNotFound,
+ .NOMEM => return error.SystemResources,
+ .NOSPC => return error.UserResourceLimitReached,
+ .NOTDIR => return error.NotDir,
+ .EXIST => return error.WatchAlreadyExists,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+/// remove an existing watch from an inotify instance
+pub fn inotify_rm_watch(inotify_fd: i32, wd: i32) void {
+ switch (errno(system.inotify_rm_watch(inotify_fd, wd))) {
+ .SUCCESS => return,
+ .BADF => unreachable,
+ .INVAL => unreachable,
+ else => unreachable,
+ }
+}
+
+pub const FanotifyInitError = error{
+ ProcessFdQuotaExceeded,
+ SystemFdQuotaExceeded,
+ SystemResources,
+ OperationNotSupported,
+ PermissionDenied,
+} || UnexpectedError;
+
+pub fn fanotify_init(flags: u32, event_f_flags: u32) FanotifyInitError!i32 {
+ const rc = system.fanotify_init(flags, event_f_flags);
+ switch (errno(rc)) {
+ .SUCCESS => return @intCast(rc),
+ .INVAL => unreachable,
+ .MFILE => return error.ProcessFdQuotaExceeded,
+ .NFILE => return error.SystemFdQuotaExceeded,
+ .NOMEM => return error.SystemResources,
+ .NOSYS => return error.OperationNotSupported,
+ .PERM => return error.PermissionDenied,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+pub const FanotifyMarkError = error{
+ MarkAlreadyExists,
+ IsDir,
+ NotAssociatedWithFileSystem,
+ FileNotFound,
+ SystemResources,
+ UserMarkQuotaExceeded,
+ NotImplemented,
+ NotDir,
+ OperationNotSupported,
+ PermissionDenied,
+ NotSameFileSystem,
+ NameTooLong,
+} || UnexpectedError;
+
+pub fn fanotify_mark(fanotify_fd: i32, flags: u32, mask: u64, dirfd: i32, pathname: ?[]const u8) FanotifyMarkError!void {
+ if (pathname) |path| {
+ const path_c = try toPosixPath(path);
+ return fanotify_markZ(fanotify_fd, flags, mask, dirfd, &path_c);
+ }
+
+ return fanotify_markZ(fanotify_fd, flags, mask, dirfd, null);
+}
+
+pub fn fanotify_markZ(fanotify_fd: i32, flags: u32, mask: u64, dirfd: i32, pathname: ?[*:0]const u8) FanotifyMarkError!void {
+ const rc = system.fanotify_mark(fanotify_fd, flags, mask, dirfd, pathname);
+ switch (errno(rc)) {
+ .SUCCESS => return,
+ .BADF => unreachable,
+ .EXIST => return error.MarkAlreadyExists,
+ .INVAL => unreachable,
+ .ISDIR => return error.IsDir,
+ .NODEV => return error.NotAssociatedWithFileSystem,
+ .NOENT => return error.FileNotFound,
+ .NOMEM => return error.SystemResources,
+ .NOSPC => return error.UserMarkQuotaExceeded,
+ .NOSYS => return error.NotImplemented,
+ .NOTDIR => return error.NotDir,
+ .OPNOTSUPP => return error.OperationNotSupported,
+ .PERM => return error.PermissionDenied,
+ .XDEV => return error.NotSameFileSystem,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+pub const MProtectError = error{
+ /// The memory cannot be given the specified access. This can happen, for example, if you
+ /// mmap(2) a file to which you have read-only access, then ask mprotect() to mark it
+ /// PROT_WRITE.
+ AccessDenied,
+
+ /// Changing the protection of a memory region would result in the total number of map‐
+ /// pings with distinct attributes (e.g., read versus read/write protection) exceeding the
+ /// allowed maximum. (For example, making the protection of a range PROT_READ in the mid‐
+ /// dle of a region currently protected as PROT_READ|PROT_WRITE would result in three map‐
+ /// pings: two read/write mappings at each end and a read-only mapping in the middle.)
+ OutOfMemory,
+} || UnexpectedError;
+
+/// `memory.len` must be page-aligned.
+pub fn mprotect(memory: []align(mem.page_size) u8, protection: u32) MProtectError!void {
+ assert(mem.isAligned(memory.len, mem.page_size));
+ if (native_os == .windows) {
+ const win_prot: windows.DWORD = switch (@as(u3, @truncate(protection))) {
+ 0b000 => windows.PAGE_NOACCESS,
+ 0b001 => windows.PAGE_READONLY,
+ 0b010 => unreachable, // +w -r not allowed
+ 0b011 => windows.PAGE_READWRITE,
+ 0b100 => windows.PAGE_EXECUTE,
+ 0b101 => windows.PAGE_EXECUTE_READ,
+ 0b110 => unreachable, // +w -r not allowed
+ 0b111 => windows.PAGE_EXECUTE_READWRITE,
+ };
+ var old: windows.DWORD = undefined;
+ windows.VirtualProtect(memory.ptr, memory.len, win_prot, &old) catch |err| switch (err) {
+ error.InvalidAddress => return error.AccessDenied,
+ error.Unexpected => return error.Unexpected,
+ };
+ } else {
+ switch (errno(system.mprotect(memory.ptr, memory.len, protection))) {
+ .SUCCESS => return,
+ .INVAL => unreachable,
+ .ACCES => return error.AccessDenied,
+ .NOMEM => return error.OutOfMemory,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+}
+
+pub const ForkError = error{SystemResources} || UnexpectedError;
+
+pub fn fork() ForkError!pid_t {
+ const rc = system.fork();
+ switch (errno(rc)) {
+ .SUCCESS => return @intCast(rc),
+ .AGAIN => return error.SystemResources,
+ .NOMEM => return error.SystemResources,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+pub const MMapError = error{
+ /// The underlying filesystem of the specified file does not support memory mapping.
+ MemoryMappingNotSupported,
+
+ /// A file descriptor refers to a non-regular file. Or a file mapping was requested,
+ /// but the file descriptor is not open for reading. Or `MAP.SHARED` was requested
+ /// and `PROT_WRITE` is set, but the file descriptor is not open in `RDWR` mode.
+ /// Or `PROT_WRITE` is set, but the file is append-only.
+ AccessDenied,
+
+ /// The `prot` argument asks for `PROT_EXEC` but the mapped area belongs to a file on
+ /// a filesystem that was mounted no-exec.
+ PermissionDenied,
+ LockedMemoryLimitExceeded,
+ ProcessFdQuotaExceeded,
+ SystemFdQuotaExceeded,
+ OutOfMemory,
+} || UnexpectedError;
+
+/// Map files or devices into memory.
+/// `length` does not need to be aligned.
+/// Use of a mapped region can result in these signals:
+/// * SIGSEGV - Attempted write into a region mapped as read-only.
+/// * SIGBUS - Attempted access to a portion of the buffer that does not correspond to the file
+pub fn mmap(
+ ptr: ?[*]align(mem.page_size) u8,
+ length: usize,
+ prot: u32,
+ flags: system.MAP,
+ fd: fd_t,
+ offset: u64,
+) MMapError![]align(mem.page_size) u8 {
+ const mmap_sym = if (lfs64_abi) system.mmap64 else system.mmap;
+ const rc = mmap_sym(ptr, length, prot, @bitCast(flags), fd, @bitCast(offset));
+ const err: E = if (builtin.link_libc) blk: {
+ if (rc != std.c.MAP_FAILED) return @as([*]align(mem.page_size) u8, @ptrCast(@alignCast(rc)))[0..length];
+ break :blk @enumFromInt(system._errno().*);
+ } else blk: {
+ const err = errno(rc);
+ if (err == .SUCCESS) return @as([*]align(mem.page_size) u8, @ptrFromInt(rc))[0..length];
+ break :blk err;
+ };
+ switch (err) {
+ .SUCCESS => unreachable,
+ .TXTBSY => return error.AccessDenied,
+ .ACCES => return error.AccessDenied,
+ .PERM => return error.PermissionDenied,
+ .AGAIN => return error.LockedMemoryLimitExceeded,
+ .BADF => unreachable, // Always a race condition.
+ .OVERFLOW => unreachable, // The number of pages used for length + offset would overflow.
+ .NODEV => return error.MemoryMappingNotSupported,
+ .INVAL => unreachable, // Invalid parameters to mmap()
+ .MFILE => return error.ProcessFdQuotaExceeded,
+ .NFILE => return error.SystemFdQuotaExceeded,
+ .NOMEM => return error.OutOfMemory,
+ else => return unexpectedErrno(err),
+ }
+}
+
+/// Deletes the mappings for the specified address range, causing
+/// further references to addresses within the range to generate invalid memory references.
+/// Note that while POSIX allows unmapping a region in the middle of an existing mapping,
+/// Zig's munmap function does not, for two reasons:
+/// * It violates the Zig principle that resource deallocation must succeed.
+/// * The Windows function, VirtualFree, has this restriction.
+pub fn munmap(memory: []align(mem.page_size) const u8) void {
+ switch (errno(system.munmap(memory.ptr, memory.len))) {
+ .SUCCESS => return,
+ .INVAL => unreachable, // Invalid parameters.
+ .NOMEM => unreachable, // Attempted to unmap a region in the middle of an existing mapping.
+ else => unreachable,
+ }
+}
+
+pub const MSyncError = error{
+ UnmappedMemory,
+} || UnexpectedError;
+
+pub fn msync(memory: []align(mem.page_size) u8, flags: i32) MSyncError!void {
+ switch (errno(system.msync(memory.ptr, memory.len, flags))) {
+ .SUCCESS => return,
+ .NOMEM => return error.UnmappedMemory, // Unsuccessful, provided pointer does not point mapped memory
+ .INVAL => unreachable, // Invalid parameters.
+ else => unreachable,
+ }
+}
+
+pub const AccessError = error{
+ PermissionDenied,
+ FileNotFound,
+ NameTooLong,
+ InputOutput,
+ SystemResources,
+ BadPathName,
+ FileBusy,
+ SymLinkLoop,
+ ReadOnlyFileSystem,
+ /// WASI-only; file paths must be valid UTF-8.
+ InvalidUtf8,
+ /// Windows-only; file paths provided by the user must be valid WTF-8.
+ /// https://simonsapin.github.io/wtf-8/
+ InvalidWtf8,
+} || UnexpectedError;
+
+/// check user's permissions for a file
+///
+/// * On Windows, asserts `path` is valid [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// * On WASI, invalid UTF-8 passed to `path` causes `error.InvalidUtf8`.
+/// * On other platforms, `path` is an opaque sequence of bytes with no particular encoding.
+///
+/// On Windows, `mode` is ignored. This is a POSIX API that is only partially supported by
+/// Windows. See `fs` for the cross-platform file system API.
+pub fn access(path: []const u8, mode: u32) AccessError!void {
+ if (native_os == .windows) {
+ const path_w = windows.sliceToPrefixedFileW(null, path) catch |err| switch (err) {
+ error.AccessDenied => return error.PermissionDenied,
+ else => |e| return e,
+ };
+ _ = try windows.GetFileAttributesW(path_w.span().ptr);
+ return;
+ } else if (native_os == .wasi and !builtin.link_libc) {
+ return faccessat(wasi.AT.FDCWD, path, mode, 0);
+ }
+ const path_c = try toPosixPath(path);
+ return accessZ(&path_c, mode);
+}
+
+/// Same as `access` except `path` is null-terminated.
+pub fn accessZ(path: [*:0]const u8, mode: u32) AccessError!void {
+ if (native_os == .windows) {
+ const path_w = windows.cStrToPrefixedFileW(null, path) catch |err| switch (err) {
+ error.AccessDenied => return error.PermissionDenied,
+ else => |e| return e,
+ };
+ _ = try windows.GetFileAttributesW(path_w.span().ptr);
+ return;
+ } else if (native_os == .wasi and !builtin.link_libc) {
+ return access(mem.sliceTo(path, 0), mode);
+ }
+ switch (errno(system.access(path, mode))) {
+ .SUCCESS => return,
+ .ACCES => return error.PermissionDenied,
+ .ROFS => return error.ReadOnlyFileSystem,
+ .LOOP => return error.SymLinkLoop,
+ .TXTBSY => return error.FileBusy,
+ .NOTDIR => return error.FileNotFound,
+ .NOENT => return error.FileNotFound,
+ .NAMETOOLONG => return error.NameTooLong,
+ .INVAL => unreachable,
+ .FAULT => unreachable,
+ .IO => return error.InputOutput,
+ .NOMEM => return error.SystemResources,
+ .ILSEQ => |err| if (native_os == .wasi)
+ return error.InvalidUtf8
+ else
+ return unexpectedErrno(err),
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+/// Check user's permissions for a file, based on an open directory handle.
+///
+/// * On Windows, asserts `path` is valid [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// * On WASI, invalid UTF-8 passed to `path` causes `error.InvalidUtf8`.
+/// * On other platforms, `path` is an opaque sequence of bytes with no particular encoding.
+///
+/// On Windows, `mode` is ignored. This is a POSIX API that is only partially supported by
+/// Windows. See `fs` for the cross-platform file system API.
+pub fn faccessat(dirfd: fd_t, path: []const u8, mode: u32, flags: u32) AccessError!void {
+ if (native_os == .windows) {
+ const path_w = try windows.sliceToPrefixedFileW(dirfd, path);
+ return faccessatW(dirfd, path_w.span().ptr);
+ } else if (native_os == .wasi and !builtin.link_libc) {
+ const resolved: RelativePathWasi = .{ .dir_fd = dirfd, .relative_path = path };
+
+ const st = blk: {
+ break :blk fstatat_wasi(dirfd, path, .{
+ .SYMLINK_FOLLOW = (flags & AT.SYMLINK_NOFOLLOW) == 0,
+ });
+ } catch |err| switch (err) {
+ error.AccessDenied => return error.PermissionDenied,
+ else => |e| return e,
+ };
+
+ if (mode != F_OK) {
+ var directory: wasi.fdstat_t = undefined;
+ if (wasi.fd_fdstat_get(resolved.dir_fd, &directory) != .SUCCESS) {
+ return error.PermissionDenied;
+ }
+
+ var rights: wasi.rights_t = .{};
+ if (mode & R_OK != 0) {
+ if (st.filetype == .DIRECTORY) {
+ rights.FD_READDIR = true;
+ } else {
+ rights.FD_READ = true;
+ }
+ }
+ if (mode & W_OK != 0) {
+ rights.FD_WRITE = true;
+ }
+ // No validation for X_OK
+
+ // https://github.com/ziglang/zig/issues/18882
+ const rights_int: u64 = @bitCast(rights);
+ const inheriting_int: u64 = @bitCast(directory.fs_rights_inheriting);
+ if ((rights_int & inheriting_int) != rights_int) {
+ return error.PermissionDenied;
+ }
+ }
+ return;
+ }
+ const path_c = try toPosixPath(path);
+ return faccessatZ(dirfd, &path_c, mode, flags);
+}
+
+/// Same as `faccessat` except the path parameter is null-terminated.
+pub fn faccessatZ(dirfd: fd_t, path: [*:0]const u8, mode: u32, flags: u32) AccessError!void {
+ if (native_os == .windows) {
+ const path_w = try windows.cStrToPrefixedFileW(dirfd, path);
+ return faccessatW(dirfd, path_w.span().ptr);
+ } else if (native_os == .wasi and !builtin.link_libc) {
+ return faccessat(dirfd, mem.sliceTo(path, 0), mode, flags);
+ }
+ switch (errno(system.faccessat(dirfd, path, mode, flags))) {
+ .SUCCESS => return,
+ .ACCES => return error.PermissionDenied,
+ .ROFS => return error.ReadOnlyFileSystem,
+ .LOOP => return error.SymLinkLoop,
+ .TXTBSY => return error.FileBusy,
+ .NOTDIR => return error.FileNotFound,
+ .NOENT => return error.FileNotFound,
+ .NAMETOOLONG => return error.NameTooLong,
+ .INVAL => unreachable,
+ .FAULT => unreachable,
+ .IO => return error.InputOutput,
+ .NOMEM => return error.SystemResources,
+ .ILSEQ => |err| if (native_os == .wasi)
+ return error.InvalidUtf8
+ else
+ return unexpectedErrno(err),
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+/// Same as `faccessat` except asserts the target is Windows and the path parameter
+/// is NtDll-prefixed, null-terminated, WTF-16 encoded.
+pub fn faccessatW(dirfd: fd_t, sub_path_w: [*:0]const u16) AccessError!void {
+ if (sub_path_w[0] == '.' and sub_path_w[1] == 0) {
+ return;
+ }
+ if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) {
+ return;
+ }
+
+ const path_len_bytes = cast(u16, mem.sliceTo(sub_path_w, 0).len * 2) orelse return error.NameTooLong;
+ var nt_name = windows.UNICODE_STRING{
+ .Length = path_len_bytes,
+ .MaximumLength = path_len_bytes,
+ .Buffer = @constCast(sub_path_w),
+ };
+ var attr = windows.OBJECT_ATTRIBUTES{
+ .Length = @sizeOf(windows.OBJECT_ATTRIBUTES),
+ .RootDirectory = if (fs.path.isAbsoluteWindowsW(sub_path_w)) null else dirfd,
+ .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
+ .ObjectName = &nt_name,
+ .SecurityDescriptor = null,
+ .SecurityQualityOfService = null,
+ };
+ var basic_info: windows.FILE_BASIC_INFORMATION = undefined;
+ switch (windows.ntdll.NtQueryAttributesFile(&attr, &basic_info)) {
+ .SUCCESS => return,
+ .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
+ .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
+ .OBJECT_NAME_INVALID => unreachable,
+ .INVALID_PARAMETER => unreachable,
+ .ACCESS_DENIED => return error.PermissionDenied,
+ .OBJECT_PATH_SYNTAX_BAD => unreachable,
+ else => |rc| return windows.unexpectedStatus(rc),
+ }
+}
+
+pub const PipeError = error{
+ SystemFdQuotaExceeded,
+ ProcessFdQuotaExceeded,
+} || UnexpectedError;
+
+/// Creates a unidirectional data channel that can be used for interprocess communication.
+pub fn pipe() PipeError![2]fd_t {
+ var fds: [2]fd_t = undefined;
+ switch (errno(system.pipe(&fds))) {
+ .SUCCESS => return fds,
+ .INVAL => unreachable, // Invalid parameters to pipe()
+ .FAULT => unreachable, // Invalid fds pointer
+ .NFILE => return error.SystemFdQuotaExceeded,
+ .MFILE => return error.ProcessFdQuotaExceeded,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+pub fn pipe2(flags: O) PipeError![2]fd_t {
+ // https://github.com/ziglang/zig/issues/19352
+ if (@hasDecl(system, "pipe2")) {
+ var fds: [2]fd_t = undefined;
+ switch (errno(system.pipe2(&fds, flags))) {
+ .SUCCESS => return fds,
+ .INVAL => unreachable, // Invalid flags
+ .FAULT => unreachable, // Invalid fds pointer
+ .NFILE => return error.SystemFdQuotaExceeded,
+ .MFILE => return error.ProcessFdQuotaExceeded,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+
+ const fds: [2]fd_t = try pipe();
+ errdefer {
+ close(fds[0]);
+ close(fds[1]);
+ }
+
+ // https://github.com/ziglang/zig/issues/18882
+ if (@as(u32, @bitCast(flags)) == 0)
+ return fds;
+
+ // CLOEXEC is special, it's a file descriptor flag and must be set using
+ // F.SETFD.
+ if (flags.CLOEXEC) {
+ for (fds) |fd| {
+ switch (errno(system.fcntl(fd, F.SETFD, @as(u32, FD_CLOEXEC)))) {
+ .SUCCESS => {},
+ .INVAL => unreachable, // Invalid flags
+ .BADF => unreachable, // Always a race condition
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+ }
+
+ const new_flags: u32 = f: {
+ var new_flags = flags;
+ new_flags.CLOEXEC = false;
+ break :f @bitCast(new_flags);
+ };
+ // Set every other flag affecting the file status using F.SETFL.
+ if (new_flags != 0) {
+ for (fds) |fd| {
+ switch (errno(system.fcntl(fd, F.SETFL, new_flags))) {
+ .SUCCESS => {},
+ .INVAL => unreachable, // Invalid flags
+ .BADF => unreachable, // Always a race condition
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+ }
+
+ return fds;
+}
+
+pub const SysCtlError = error{
+ PermissionDenied,
+ SystemResources,
+ NameTooLong,
+ UnknownName,
+} || UnexpectedError;
+
+pub fn sysctl(
+ name: []const c_int,
+ oldp: ?*anyopaque,
+ oldlenp: ?*usize,
+ newp: ?*anyopaque,
+ newlen: usize,
+) SysCtlError!void {
+ if (native_os == .wasi) {
+ @panic("unsupported"); // TODO should be compile error, not panic
+ }
+ if (native_os == .haiku) {
+ @panic("unsupported"); // TODO should be compile error, not panic
+ }
+
+ const name_len = cast(c_uint, name.len) orelse return error.NameTooLong;
+ switch (errno(system.sysctl(name.ptr, name_len, oldp, oldlenp, newp, newlen))) {
+ .SUCCESS => return,
+ .FAULT => unreachable,
+ .PERM => return error.PermissionDenied,
+ .NOMEM => return error.SystemResources,
+ .NOENT => return error.UnknownName,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+pub fn sysctlbynameZ(
+ name: [*:0]const u8,
+ oldp: ?*anyopaque,
+ oldlenp: ?*usize,
+ newp: ?*anyopaque,
+ newlen: usize,
+) SysCtlError!void {
+ if (native_os == .wasi) {
+ @panic("unsupported"); // TODO should be compile error, not panic
+ }
+ if (native_os == .haiku) {
+ @panic("unsupported"); // TODO should be compile error, not panic
+ }
+
+ switch (errno(system.sysctlbyname(name, oldp, oldlenp, newp, newlen))) {
+ .SUCCESS => return,
+ .FAULT => unreachable,
+ .PERM => return error.PermissionDenied,
+ .NOMEM => return error.SystemResources,
+ .NOENT => return error.UnknownName,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+pub fn gettimeofday(tv: ?*timeval, tz: ?*timezone) void {
+ switch (errno(system.gettimeofday(tv, tz))) {
+ .SUCCESS => return,
+ .INVAL => unreachable,
+ else => unreachable,
+ }
+}
+
+pub const SeekError = error{
+ Unseekable,
+
+ /// In WASI, this error may occur when the file descriptor does
+ /// not hold the required rights to seek on it.
+ AccessDenied,
+} || UnexpectedError;
+
+/// Repositions read/write file offset relative to the beginning.
+pub fn lseek_SET(fd: fd_t, offset: u64) SeekError!void {
+ if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) {
+ var result: u64 = undefined;
+ switch (errno(system.llseek(fd, offset, &result, SEEK.SET))) {
+ .SUCCESS => return,
+ .BADF => unreachable, // always a race condition
+ .INVAL => return error.Unseekable,
+ .OVERFLOW => return error.Unseekable,
+ .SPIPE => return error.Unseekable,
+ .NXIO => return error.Unseekable,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+ if (native_os == .windows) {
+ return windows.SetFilePointerEx_BEGIN(fd, offset);
+ }
+ if (native_os == .wasi and !builtin.link_libc) {
+ var new_offset: wasi.filesize_t = undefined;
+ switch (wasi.fd_seek(fd, @bitCast(offset), .SET, &new_offset)) {
+ .SUCCESS => return,
+ .BADF => unreachable, // always a race condition
+ .INVAL => return error.Unseekable,
+ .OVERFLOW => return error.Unseekable,
+ .SPIPE => return error.Unseekable,
+ .NXIO => return error.Unseekable,
+ .NOTCAPABLE => return error.AccessDenied,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+
+ const lseek_sym = if (lfs64_abi) system.lseek64 else system.lseek;
+ switch (errno(lseek_sym(fd, @bitCast(offset), SEEK.SET))) {
+ .SUCCESS => return,
+ .BADF => unreachable, // always a race condition
+ .INVAL => return error.Unseekable,
+ .OVERFLOW => return error.Unseekable,
+ .SPIPE => return error.Unseekable,
+ .NXIO => return error.Unseekable,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+/// Repositions read/write file offset relative to the current offset.
+pub fn lseek_CUR(fd: fd_t, offset: i64) SeekError!void {
+ if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) {
+ var result: u64 = undefined;
+ switch (errno(system.llseek(fd, @bitCast(offset), &result, SEEK.CUR))) {
+ .SUCCESS => return,
+ .BADF => unreachable, // always a race condition
+ .INVAL => return error.Unseekable,
+ .OVERFLOW => return error.Unseekable,
+ .SPIPE => return error.Unseekable,
+ .NXIO => return error.Unseekable,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+ if (native_os == .windows) {
+ return windows.SetFilePointerEx_CURRENT(fd, offset);
+ }
+ if (native_os == .wasi and !builtin.link_libc) {
+ var new_offset: wasi.filesize_t = undefined;
+ switch (wasi.fd_seek(fd, offset, .CUR, &new_offset)) {
+ .SUCCESS => return,
+ .BADF => unreachable, // always a race condition
+ .INVAL => return error.Unseekable,
+ .OVERFLOW => return error.Unseekable,
+ .SPIPE => return error.Unseekable,
+ .NXIO => return error.Unseekable,
+ .NOTCAPABLE => return error.AccessDenied,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+ const lseek_sym = if (lfs64_abi) system.lseek64 else system.lseek;
+ switch (errno(lseek_sym(fd, @bitCast(offset), SEEK.CUR))) {
+ .SUCCESS => return,
+ .BADF => unreachable, // always a race condition
+ .INVAL => return error.Unseekable,
+ .OVERFLOW => return error.Unseekable,
+ .SPIPE => return error.Unseekable,
+ .NXIO => return error.Unseekable,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+/// Repositions read/write file offset relative to the end.
+pub fn lseek_END(fd: fd_t, offset: i64) SeekError!void {
+ if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) {
+ var result: u64 = undefined;
+ switch (errno(system.llseek(fd, @bitCast(offset), &result, SEEK.END))) {
+ .SUCCESS => return,
+ .BADF => unreachable, // always a race condition
+ .INVAL => return error.Unseekable,
+ .OVERFLOW => return error.Unseekable,
+ .SPIPE => return error.Unseekable,
+ .NXIO => return error.Unseekable,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+ if (native_os == .windows) {
+ return windows.SetFilePointerEx_END(fd, offset);
+ }
+ if (native_os == .wasi and !builtin.link_libc) {
+ var new_offset: wasi.filesize_t = undefined;
+ switch (wasi.fd_seek(fd, offset, .END, &new_offset)) {
+ .SUCCESS => return,
+ .BADF => unreachable, // always a race condition
+ .INVAL => return error.Unseekable,
+ .OVERFLOW => return error.Unseekable,
+ .SPIPE => return error.Unseekable,
+ .NXIO => return error.Unseekable,
+ .NOTCAPABLE => return error.AccessDenied,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+ const lseek_sym = if (lfs64_abi) system.lseek64 else system.lseek;
+ switch (errno(lseek_sym(fd, @bitCast(offset), SEEK.END))) {
+ .SUCCESS => return,
+ .BADF => unreachable, // always a race condition
+ .INVAL => return error.Unseekable,
+ .OVERFLOW => return error.Unseekable,
+ .SPIPE => return error.Unseekable,
+ .NXIO => return error.Unseekable,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+/// Returns the read/write file offset relative to the beginning.
+pub fn lseek_CUR_get(fd: fd_t) SeekError!u64 {
+ if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) {
+ var result: u64 = undefined;
+ switch (errno(system.llseek(fd, 0, &result, SEEK.CUR))) {
+ .SUCCESS => return result,
+ .BADF => unreachable, // always a race condition
+ .INVAL => return error.Unseekable,
+ .OVERFLOW => return error.Unseekable,
+ .SPIPE => return error.Unseekable,
+ .NXIO => return error.Unseekable,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+ if (native_os == .windows) {
+ return windows.SetFilePointerEx_CURRENT_get(fd);
+ }
+ if (native_os == .wasi and !builtin.link_libc) {
+ var new_offset: wasi.filesize_t = undefined;
+ switch (wasi.fd_seek(fd, 0, .CUR, &new_offset)) {
+ .SUCCESS => return new_offset,
+ .BADF => unreachable, // always a race condition
+ .INVAL => return error.Unseekable,
+ .OVERFLOW => return error.Unseekable,
+ .SPIPE => return error.Unseekable,
+ .NXIO => return error.Unseekable,
+ .NOTCAPABLE => return error.AccessDenied,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+ const lseek_sym = if (lfs64_abi) system.lseek64 else system.lseek;
+ const rc = lseek_sym(fd, 0, SEEK.CUR);
+ switch (errno(rc)) {
+ .SUCCESS => return @bitCast(rc),
+ .BADF => unreachable, // always a race condition
+ .INVAL => return error.Unseekable,
+ .OVERFLOW => return error.Unseekable,
+ .SPIPE => return error.Unseekable,
+ .NXIO => return error.Unseekable,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+pub const FcntlError = error{
+ PermissionDenied,
+ FileBusy,
+ ProcessFdQuotaExceeded,
+ Locked,
+ DeadLock,
+ LockedRegionLimitExceeded,
+} || UnexpectedError;
+
+pub fn fcntl(fd: fd_t, cmd: i32, arg: usize) FcntlError!usize {
+ while (true) {
+ const rc = system.fcntl(fd, cmd, arg);
+ switch (errno(rc)) {
+ .SUCCESS => return @intCast(rc),
+ .INTR => continue,
+ .AGAIN, .ACCES => return error.Locked,
+ .BADF => unreachable,
+ .BUSY => return error.FileBusy,
+ .INVAL => unreachable, // invalid parameters
+ .PERM => return error.PermissionDenied,
+ .MFILE => return error.ProcessFdQuotaExceeded,
+ .NOTDIR => unreachable, // invalid parameter
+ .DEADLK => return error.DeadLock,
+ .NOLCK => return error.LockedRegionLimitExceeded,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+}
+
+pub const FlockError = error{
+ WouldBlock,
+
+ /// The kernel ran out of memory for allocating file locks
+ SystemResources,
+
+ /// The underlying filesystem does not support file locks
+ FileLocksNotSupported,
+} || UnexpectedError;
+
+/// Depending on the operating system `flock` may or may not interact with
+/// `fcntl` locks made by other processes.
+pub fn flock(fd: fd_t, operation: i32) FlockError!void {
+ while (true) {
+ const rc = system.flock(fd, operation);
+ switch (errno(rc)) {
+ .SUCCESS => return,
+ .BADF => unreachable,
+ .INTR => continue,
+ .INVAL => unreachable, // invalid parameters
+ .NOLCK => return error.SystemResources,
+ .AGAIN => return error.WouldBlock, // TODO: integrate with async instead of just returning an error
+ .OPNOTSUPP => return error.FileLocksNotSupported,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+}
+
+pub const RealPathError = error{
+ FileNotFound,
+ AccessDenied,
+ NameTooLong,
+ NotSupported,
+ NotDir,
+ SymLinkLoop,
+ InputOutput,
+ FileTooBig,
+ IsDir,
+ ProcessFdQuotaExceeded,
+ SystemFdQuotaExceeded,
+ NoDevice,
+ SystemResources,
+ NoSpaceLeft,
+ FileSystem,
+ BadPathName,
+ DeviceBusy,
+
+ SharingViolation,
+ PipeBusy,
+
+ /// Windows-only; file paths provided by the user must be valid WTF-8.
+ /// https://simonsapin.github.io/wtf-8/
+ InvalidWtf8,
+
+ /// On Windows, `\\server` or `\\server\share` was not found.
+ NetworkNotFound,
+
+ PathAlreadyExists,
+
+ /// On Windows, antivirus software is enabled by default. It can be
+ /// disabled, but Windows Update sometimes ignores the user's preference
+ /// and re-enables it. When enabled, antivirus software on Windows
+ /// intercepts file system operations and makes them significantly slower
+ /// in addition to possibly failing with this error code.
+ AntivirusInterference,
+
+ /// On Windows, the volume does not contain a recognized file system. File
+ /// system drivers might not be loaded, or the volume may be corrupt.
+ UnrecognizedVolume,
+} || UnexpectedError;
+
+/// Return the canonicalized absolute pathname.
+///
+/// Expands all symbolic links and resolves references to `.`, `..`, and
+/// extra `/` characters in `pathname`.
+///
+/// On Windows, `pathname` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+///
+/// On other platforms, `pathname` is an opaque sequence of bytes with no particular encoding.
+///
+/// The return value is a slice of `out_buffer`, but not necessarily from the beginning.
+///
+/// See also `realpathZ` and `realpathW`.
+///
+/// * On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// * On other platforms, the result is an opaque sequence of bytes with no particular encoding.
+///
+/// Calling this function is usually a bug.
+pub fn realpath(pathname: []const u8, out_buffer: *[max_path_bytes]u8) RealPathError![]u8 {
+ if (native_os == .windows) {
+ const pathname_w = try windows.sliceToPrefixedFileW(null, pathname);
+ return realpathW(pathname_w.span(), out_buffer);
+ } else if (native_os == .wasi and !builtin.link_libc) {
+ @compileError("WASI does not support os.realpath");
+ }
+ const pathname_c = try toPosixPath(pathname);
+ return realpathZ(&pathname_c, out_buffer);
+}
+
+/// Same as `realpath` except `pathname` is null-terminated.
+///
+/// Calling this function is usually a bug.
+pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[max_path_bytes]u8) RealPathError![]u8 {
+ if (native_os == .windows) {
+ const pathname_w = try windows.cStrToPrefixedFileW(null, pathname);
+ return realpathW(pathname_w.span(), out_buffer);
+ } else if (native_os == .wasi and !builtin.link_libc) {
+ return realpath(mem.sliceTo(pathname, 0), out_buffer);
+ }
+ if (!builtin.link_libc) {
+ const flags: O = switch (native_os) {
+ .linux => .{
+ .NONBLOCK = true,
+ .CLOEXEC = true,
+ .PATH = true,
+ },
+ else => .{
+ .NONBLOCK = true,
+ .CLOEXEC = true,
+ },
+ };
+ const fd = openZ(pathname, flags, 0) catch |err| switch (err) {
+ error.FileLocksNotSupported => unreachable,
+ error.WouldBlock => unreachable,
+ error.FileBusy => unreachable, // not asking for write permissions
+ error.InvalidUtf8 => unreachable, // WASI-only
+ else => |e| return e,
+ };
+ defer close(fd);
+
+ return std.os.getFdPath(fd, out_buffer);
+ }
+ const result_path = std.c.realpath(pathname, out_buffer) orelse switch (@as(E, @enumFromInt(std.c._errno().*))) {
+ .SUCCESS => unreachable,
+ .INVAL => unreachable,
+ .BADF => unreachable,
+ .FAULT => unreachable,
+ .ACCES => return error.AccessDenied,
+ .NOENT => return error.FileNotFound,
+ .OPNOTSUPP => return error.NotSupported,
+ .NOTDIR => return error.NotDir,
+ .NAMETOOLONG => return error.NameTooLong,
+ .LOOP => return error.SymLinkLoop,
+ .IO => return error.InputOutput,
+ else => |err| return unexpectedErrno(err),
+ };
+ return mem.sliceTo(result_path, 0);
+}
+
+/// Same as `realpath` except `pathname` is WTF16LE-encoded.
+///
+/// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+///
+/// Calling this function is usually a bug.
+pub fn realpathW(pathname: []const u16, out_buffer: *[max_path_bytes]u8) RealPathError![]u8 {
+ const w = windows;
+
+ const dir = fs.cwd().fd;
+ const access_mask = w.GENERIC_READ | w.SYNCHRONIZE;
+ const share_access = w.FILE_SHARE_READ;
+ const creation = w.FILE_OPEN;
+ const h_file = blk: {
+ const res = w.OpenFile(pathname, .{
+ .dir = dir,
+ .access_mask = access_mask,
+ .share_access = share_access,
+ .creation = creation,
+ .filter = .any,
+ }) catch |err| switch (err) {
+ error.WouldBlock => unreachable,
+ else => |e| return e,
+ };
+ break :blk res;
+ };
+ defer w.CloseHandle(h_file);
+
+ return std.os.getFdPath(h_file, out_buffer);
+}
+
+/// Spurious wakeups are possible and no precision of timing is guaranteed.
+pub fn nanosleep(seconds: u64, nanoseconds: u64) void {
+ var req = timespec{
+ .tv_sec = cast(isize, seconds) orelse maxInt(isize),
+ .tv_nsec = cast(isize, nanoseconds) orelse maxInt(isize),
+ };
+ var rem: timespec = undefined;
+ while (true) {
+ switch (errno(system.nanosleep(&req, &rem))) {
+ .FAULT => unreachable,
+ .INVAL => {
+ // Sometimes Darwin returns EINVAL for no reason.
+ // We treat it as a spurious wakeup.
+ return;
+ },
+ .INTR => {
+ req = rem;
+ continue;
+ },
+ // This prong handles success as well as unexpected errors.
+ else => return,
+ }
+ }
+}
+
+pub fn dl_iterate_phdr(
+ context: anytype,
+ comptime Error: type,
+ comptime callback: fn (info: *dl_phdr_info, size: usize, context: @TypeOf(context)) Error!void,
+) Error!void {
+ const Context = @TypeOf(context);
+ const elf = std.elf;
+ const dl = @import("dynamic_library.zig");
+
+ switch (builtin.object_format) {
+ .elf, .c => {},
+ else => @compileError("dl_iterate_phdr is not available for this target"),
+ }
+
+ if (builtin.link_libc) {
+ switch (system.dl_iterate_phdr(struct {
+ fn callbackC(info: *dl_phdr_info, size: usize, data: ?*anyopaque) callconv(.C) c_int {
+ const context_ptr: *const Context = @ptrCast(@alignCast(data));
+ callback(info, size, context_ptr.*) catch |err| return @intFromError(err);
+ return 0;
+ }
+ }.callbackC, @ptrCast(@constCast(&context)))) {
+ 0 => return,
+ else => |err| return @as(Error, @errorCast(@errorFromInt(@as(std.meta.Int(.unsigned, @bitSizeOf(anyerror)), @intCast(err))))),
+ }
+ }
+
+ const elf_base = std.process.getBaseAddress();
+ const ehdr: *elf.Ehdr = @ptrFromInt(elf_base);
+ // Make sure the base address points to an ELF image.
+ assert(mem.eql(u8, ehdr.e_ident[0..4], elf.MAGIC));
+ const n_phdr = ehdr.e_phnum;
+ const phdrs = (@as([*]elf.Phdr, @ptrFromInt(elf_base + ehdr.e_phoff)))[0..n_phdr];
+
+ var it = dl.linkmap_iterator(phdrs) catch unreachable;
+
+ // The executable has no dynamic link segment, create a single entry for
+ // the whole ELF image.
+ if (it.end()) {
+ // Find the base address for the ELF image, if this is a PIE the value
+ // is non-zero.
+ const base_address = for (phdrs) |*phdr| {
+ if (phdr.p_type == elf.PT_PHDR) {
+ break @intFromPtr(phdrs.ptr) - phdr.p_vaddr;
+ // We could try computing the difference between _DYNAMIC and
+ // the p_vaddr of the PT_DYNAMIC section, but using the phdr is
+ // good enough (Is it?).
+ }
+ } else unreachable;
+
+ var info = dl_phdr_info{
+ .dlpi_addr = base_address,
+ .dlpi_name = "/proc/self/exe",
+ .dlpi_phdr = phdrs.ptr,
+ .dlpi_phnum = ehdr.e_phnum,
+ };
+
+ return callback(&info, @sizeOf(dl_phdr_info), context);
+ }
+
+ // Last return value from the callback function.
+ while (it.next()) |entry| {
+ var dlpi_phdr: [*]elf.Phdr = undefined;
+ var dlpi_phnum: u16 = undefined;
+
+ if (entry.l_addr != 0) {
+ const elf_header: *elf.Ehdr = @ptrFromInt(entry.l_addr);
+ dlpi_phdr = @ptrFromInt(entry.l_addr + elf_header.e_phoff);
+ dlpi_phnum = elf_header.e_phnum;
+ } else {
+ // This is the running ELF image
+ dlpi_phdr = @ptrFromInt(elf_base + ehdr.e_phoff);
+ dlpi_phnum = ehdr.e_phnum;
+ }
+
+ var info = dl_phdr_info{
+ .dlpi_addr = entry.l_addr,
+ .dlpi_name = entry.l_name,
+ .dlpi_phdr = dlpi_phdr,
+ .dlpi_phnum = dlpi_phnum,
+ };
+
+ try callback(&info, @sizeOf(dl_phdr_info), context);
+ }
+}
+
+pub const ClockGetTimeError = error{UnsupportedClock} || UnexpectedError;
+
+/// TODO: change this to return the timespec as a return value
+/// TODO: look into making clk_id an enum
+pub fn clock_gettime(clk_id: i32, tp: *timespec) ClockGetTimeError!void {
+ if (native_os == .wasi and !builtin.link_libc) {
+ var ts: timestamp_t = undefined;
+ switch (system.clock_time_get(@bitCast(clk_id), 1, &ts)) {
+ .SUCCESS => {
+ tp.* = .{
+ .tv_sec = @intCast(ts / std.time.ns_per_s),
+ .tv_nsec = @intCast(ts % std.time.ns_per_s),
+ };
+ },
+ .INVAL => return error.UnsupportedClock,
+ else => |err| return unexpectedErrno(err),
+ }
+ return;
+ }
+ if (native_os == .windows) {
+ if (clk_id == CLOCK.REALTIME) {
+ var ft: windows.FILETIME = undefined;
+ windows.kernel32.GetSystemTimeAsFileTime(&ft);
+ // FileTime has a granularity of 100 nanoseconds and uses the NTFS/Windows epoch.
+ const ft64 = (@as(u64, ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
+ const ft_per_s = std.time.ns_per_s / 100;
+ tp.* = .{
+ .tv_sec = @as(i64, @intCast(ft64 / ft_per_s)) + std.time.epoch.windows,
+ .tv_nsec = @as(c_long, @intCast(ft64 % ft_per_s)) * 100,
+ };
+ return;
+ } else {
+ // TODO POSIX implementation of CLOCK.MONOTONIC on Windows.
+ return error.UnsupportedClock;
+ }
+ }
+
+ switch (errno(system.clock_gettime(clk_id, tp))) {
+ .SUCCESS => return,
+ .FAULT => unreachable,
+ .INVAL => return error.UnsupportedClock,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+pub fn clock_getres(clk_id: i32, res: *timespec) ClockGetTimeError!void {
+ if (native_os == .wasi and !builtin.link_libc) {
+ var ts: timestamp_t = undefined;
+ switch (system.clock_res_get(@bitCast(clk_id), &ts)) {
+ .SUCCESS => res.* = .{
+ .tv_sec = @intCast(ts / std.time.ns_per_s),
+ .tv_nsec = @intCast(ts % std.time.ns_per_s),
+ },
+ .INVAL => return error.UnsupportedClock,
+ else => |err| return unexpectedErrno(err),
+ }
+ return;
+ }
+
+ switch (errno(system.clock_getres(clk_id, res))) {
+ .SUCCESS => return,
+ .FAULT => unreachable,
+ .INVAL => return error.UnsupportedClock,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+pub const SchedGetAffinityError = error{PermissionDenied} || UnexpectedError;
+
+pub fn sched_getaffinity(pid: pid_t) SchedGetAffinityError!cpu_set_t {
+ var set: cpu_set_t = undefined;
+ switch (errno(system.sched_getaffinity(pid, @sizeOf(cpu_set_t), &set))) {
+ .SUCCESS => return set,
+ .FAULT => unreachable,
+ .INVAL => unreachable,
+ .SRCH => unreachable,
+ .PERM => return error.PermissionDenied,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+pub const SigaltstackError = error{
+ /// The supplied stack size was less than MINSIGSTKSZ.
+ SizeTooSmall,
+
+ /// Attempted to change the signal stack while it was active.
+ PermissionDenied,
+} || UnexpectedError;
+
+pub fn sigaltstack(ss: ?*stack_t, old_ss: ?*stack_t) SigaltstackError!void {
+ switch (errno(system.sigaltstack(ss, old_ss))) {
+ .SUCCESS => return,
+ .FAULT => unreachable,
+ .INVAL => unreachable,
+ .NOMEM => return error.SizeTooSmall,
+ .PERM => return error.PermissionDenied,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+/// Examine and change a signal action.
+pub fn sigaction(sig: u6, noalias act: ?*const Sigaction, noalias oact: ?*Sigaction) error{OperationNotSupported}!void {
+ switch (errno(system.sigaction(sig, act, oact))) {
+ .SUCCESS => return,
+ .INVAL, .NOSYS => return error.OperationNotSupported,
+ else => unreachable,
+ }
+}
+
+/// Sets the thread signal mask.
+pub fn sigprocmask(flags: u32, noalias set: ?*const sigset_t, noalias oldset: ?*sigset_t) void {
+ switch (errno(system.sigprocmask(@bitCast(flags), set, oldset))) {
+ .SUCCESS => return,
+ .FAULT => unreachable,
+ .INVAL => unreachable,
+ else => unreachable,
+ }
+}
+
+pub const FutimensError = error{
+ /// times is NULL, or both tv_nsec values are UTIME_NOW, and either:
+ /// * the effective user ID of the caller does not match the owner
+ /// of the file, the caller does not have write access to the
+ /// file, and the caller is not privileged (Linux: does not have
+ /// either the CAP_FOWNER or the CAP_DAC_OVERRIDE capability);
+ /// or,
+ /// * the file is marked immutable (see chattr(1)).
+ AccessDenied,
+
+ /// The caller attempted to change one or both timestamps to a value
+ /// other than the current time, or to change one of the timestamps
+ /// to the current time while leaving the other timestamp unchanged,
+ /// (i.e., times is not NULL, neither tv_nsec field is UTIME_NOW,
+ /// and neither tv_nsec field is UTIME_OMIT) and either:
+ /// * the caller's effective user ID does not match the owner of
+ /// file, and the caller is not privileged (Linux: does not have
+ /// the CAP_FOWNER capability); or,
+ /// * the file is marked append-only or immutable (see chattr(1)).
+ PermissionDenied,
+
+ ReadOnlyFileSystem,
+} || UnexpectedError;
+
+pub fn futimens(fd: fd_t, times: *const [2]timespec) FutimensError!void {
+ if (native_os == .wasi and !builtin.link_libc) {
+ // TODO WASI encodes `wasi.fstflags` to signify magic values
+ // similar to UTIME_NOW and UTIME_OMIT. Currently, we ignore
+ // this here, but we should really handle it somehow.
+ const atim = times[0].toTimestamp();
+ const mtim = times[1].toTimestamp();
+ switch (wasi.fd_filestat_set_times(fd, atim, mtim, .{
+ .ATIM = true,
+ .MTIM = true,
+ })) {
+ .SUCCESS => return,
+ .ACCES => return error.AccessDenied,
+ .PERM => return error.PermissionDenied,
+ .BADF => unreachable, // always a race condition
+ .FAULT => unreachable,
+ .INVAL => unreachable,
+ .ROFS => return error.ReadOnlyFileSystem,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+
+ switch (errno(system.futimens(fd, times))) {
+ .SUCCESS => return,
+ .ACCES => return error.AccessDenied,
+ .PERM => return error.PermissionDenied,
+ .BADF => unreachable, // always a race condition
+ .FAULT => unreachable,
+ .INVAL => unreachable,
+ .ROFS => return error.ReadOnlyFileSystem,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+pub const GetHostNameError = error{PermissionDenied} || UnexpectedError;
+
+pub fn gethostname(name_buffer: *[HOST_NAME_MAX]u8) GetHostNameError![]u8 {
+ if (builtin.link_libc) {
+ switch (errno(system.gethostname(name_buffer, name_buffer.len))) {
+ .SUCCESS => return mem.sliceTo(name_buffer, 0),
+ .FAULT => unreachable,
+ .NAMETOOLONG => unreachable, // HOST_NAME_MAX prevents this
+ .PERM => return error.PermissionDenied,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+ if (native_os == .linux) {
+ const uts = uname();
+ const hostname = mem.sliceTo(&uts.nodename, 0);
+ const result = name_buffer[0..hostname.len];
+ @memcpy(result, hostname);
+ return result;
+ }
+
+ @compileError("TODO implement gethostname for this OS");
+}
+
+pub fn uname() utsname {
+ var uts: utsname = undefined;
+ switch (errno(system.uname(&uts))) {
+ .SUCCESS => return uts,
+ .FAULT => unreachable,
+ else => unreachable,
+ }
+}
+
+pub fn res_mkquery(
+ op: u4,
+ dname: []const u8,
+ class: u8,
+ ty: u8,
+ data: []const u8,
+ newrr: ?[*]const u8,
+ buf: []u8,
+) usize {
+ _ = data;
+ _ = newrr;
+ // This implementation is ported from musl libc.
+ // A more idiomatic "ziggy" implementation would be welcome.
+ var name = dname;
+ if (mem.endsWith(u8, name, ".")) name.len -= 1;
+ assert(name.len <= 253);
+ const n = 17 + name.len + @intFromBool(name.len != 0);
+
+ // Construct query template - ID will be filled later
+ var q: [280]u8 = undefined;
+ @memset(q[0..n], 0);
+ q[2] = @as(u8, op) * 8 + 1;
+ q[5] = 1;
+ @memcpy(q[13..][0..name.len], name);
+ var i: usize = 13;
+ var j: usize = undefined;
+ while (q[i] != 0) : (i = j + 1) {
+ j = i;
+ while (q[j] != 0 and q[j] != '.') : (j += 1) {}
+ // TODO determine the circumstances for this and whether or
+ // not this should be an error.
+ if (j - i - 1 > 62) unreachable;
+ q[i - 1] = @intCast(j - i);
+ }
+ q[i + 1] = ty;
+ q[i + 3] = class;
+
+ // Make a reasonably unpredictable id
+ var ts: timespec = undefined;
+ clock_gettime(CLOCK.REALTIME, &ts) catch {};
+ const UInt = std.meta.Int(.unsigned, @bitSizeOf(@TypeOf(ts.tv_nsec)));
+ const unsec: UInt = @bitCast(ts.tv_nsec);
+ const id: u32 = @truncate(unsec + unsec / 65536);
+ q[0] = @truncate(id / 256);
+ q[1] = @truncate(id);
+
+ @memcpy(buf[0..n], q[0..n]);
+ return n;
+}
+
+pub const SendError = error{
+ /// (For UNIX domain sockets, which are identified by pathname) Write permission is denied
+ /// on the destination socket file, or search permission is denied for one of the
+ /// directories the path prefix. (See path_resolution(7).)
+ /// (For UDP sockets) An attempt was made to send to a network/broadcast address as though
+ /// it was a unicast address.
+ AccessDenied,
+
+ /// The socket is marked nonblocking and the requested operation would block, and
+ /// there is no global event loop configured.
+ /// It's also possible to get this error under the following condition:
+ /// (Internet domain datagram sockets) The socket referred to by sockfd had not previously
+ /// been bound to an address and, upon attempting to bind it to an ephemeral port, it was
+ /// determined that all port numbers in the ephemeral port range are currently in use. See
+ /// the discussion of /proc/sys/net/ipv4/ip_local_port_range in ip(7).
+ WouldBlock,
+
+ /// Another Fast Open is already in progress.
+ FastOpenAlreadyInProgress,
+
+ /// Connection reset by peer.
+ ConnectionResetByPeer,
+
+ /// The socket type requires that message be sent atomically, and the size of the message
+ /// to be sent made this impossible. The message is not transmitted.
+ MessageTooBig,
+
+ /// The output queue for a network interface was full. This generally indicates that the
+ /// interface has stopped sending, but may be caused by transient congestion. (Normally,
+ /// this does not occur in Linux. Packets are just silently dropped when a device queue
+ /// overflows.)
+ /// This is also caused when there is not enough kernel memory available.
+ SystemResources,
+
+ /// The local end has been shut down on a connection oriented socket. In this case, the
+ /// process will also receive a SIGPIPE unless MSG.NOSIGNAL is set.
+ BrokenPipe,
+
+ FileDescriptorNotASocket,
+
+ /// Network is unreachable.
+ NetworkUnreachable,
+
+ /// The local network interface used to reach the destination is down.
+ NetworkSubsystemFailed,
+} || UnexpectedError;
+
+pub const SendMsgError = SendError || error{
+ /// The passed address didn't have the correct address family in its sa_family field.
+ AddressFamilyNotSupported,
+
+ /// Returned when socket is AF.UNIX and the given path has a symlink loop.
+ SymLinkLoop,
+
+ /// Returned when socket is AF.UNIX and the given path length exceeds `max_path_bytes` bytes.
+ NameTooLong,
+
+ /// Returned when socket is AF.UNIX and the given path does not point to an existing file.
+ FileNotFound,
+ NotDir,
+
+ /// The socket is not connected (connection-oriented sockets only).
+ SocketNotConnected,
+ AddressNotAvailable,
+};
+
+pub fn sendmsg(
+ /// The file descriptor of the sending socket.
+ sockfd: socket_t,
+ /// Message header and iovecs
+ msg: *const msghdr_const,
+ flags: u32,
+) SendMsgError!usize {
+ while (true) {
+ const rc = system.sendmsg(sockfd, msg, flags);
+ if (native_os == .windows) {
+ if (rc == windows.ws2_32.SOCKET_ERROR) {
+ switch (windows.ws2_32.WSAGetLastError()) {
+ .WSAEACCES => return error.AccessDenied,
+ .WSAEADDRNOTAVAIL => return error.AddressNotAvailable,
+ .WSAECONNRESET => return error.ConnectionResetByPeer,
+ .WSAEMSGSIZE => return error.MessageTooBig,
+ .WSAENOBUFS => return error.SystemResources,
+ .WSAENOTSOCK => return error.FileDescriptorNotASocket,
+ .WSAEAFNOSUPPORT => return error.AddressFamilyNotSupported,
+ .WSAEDESTADDRREQ => unreachable, // A destination address is required.
+ .WSAEFAULT => unreachable, // The lpBuffers, lpTo, lpOverlapped, lpNumberOfBytesSent, or lpCompletionRoutine parameters are not part of the user address space, or the lpTo parameter is too small.
+ .WSAEHOSTUNREACH => return error.NetworkUnreachable,
+ // TODO: WSAEINPROGRESS, WSAEINTR
+ .WSAEINVAL => unreachable,
+ .WSAENETDOWN => return error.NetworkSubsystemFailed,
+ .WSAENETRESET => return error.ConnectionResetByPeer,
+ .WSAENETUNREACH => return error.NetworkUnreachable,
+ .WSAENOTCONN => return error.SocketNotConnected,
+ .WSAESHUTDOWN => unreachable, // The socket has been shut down; it is not possible to WSASendTo on a socket after shutdown has been invoked with how set to SD_SEND or SD_BOTH.
+ .WSAEWOULDBLOCK => return error.WouldBlock,
+ .WSANOTINITIALISED => unreachable, // A successful WSAStartup call must occur before using this function.
+ else => |err| return windows.unexpectedWSAError(err),
+ }
+ } else {
+ return @intCast(rc);
+ }
+ } else {
+ switch (errno(rc)) {
+ .SUCCESS => return @intCast(rc),
+
+ .ACCES => return error.AccessDenied,
+ .AGAIN => return error.WouldBlock,
+ .ALREADY => return error.FastOpenAlreadyInProgress,
+ .BADF => unreachable, // always a race condition
+ .CONNRESET => return error.ConnectionResetByPeer,
+ .DESTADDRREQ => unreachable, // The socket is not connection-mode, and no peer address is set.
+ .FAULT => unreachable, // An invalid user space address was specified for an argument.
+ .INTR => continue,
+ .INVAL => unreachable, // Invalid argument passed.
+ .ISCONN => unreachable, // connection-mode socket was connected already but a recipient was specified
+ .MSGSIZE => return error.MessageTooBig,
+ .NOBUFS => return error.SystemResources,
+ .NOMEM => return error.SystemResources,
+ .NOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.
+ .OPNOTSUPP => unreachable, // Some bit in the flags argument is inappropriate for the socket type.
+ .PIPE => return error.BrokenPipe,
+ .AFNOSUPPORT => return error.AddressFamilyNotSupported,
+ .LOOP => return error.SymLinkLoop,
+ .NAMETOOLONG => return error.NameTooLong,
+ .NOENT => return error.FileNotFound,
+ .NOTDIR => return error.NotDir,
+ .HOSTUNREACH => return error.NetworkUnreachable,
+ .NETUNREACH => return error.NetworkUnreachable,
+ .NOTCONN => return error.SocketNotConnected,
+ .NETDOWN => return error.NetworkSubsystemFailed,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+ }
+}
+
+pub const SendToError = SendMsgError || error{
+ /// The destination address is not reachable by the bound address.
+ UnreachableAddress,
+};
+
+/// Transmit a message to another socket.
+///
+/// The `sendto` call may be used only when the socket is in a connected state (so that the intended
+/// recipient is known). The following call
+///
+/// send(sockfd, buf, len, flags);
+///
+/// is equivalent to
+///
+/// sendto(sockfd, buf, len, flags, NULL, 0);
+///
+/// If sendto() is used on a connection-mode (`SOCK.STREAM`, `SOCK.SEQPACKET`) socket, the arguments
+/// `dest_addr` and `addrlen` are asserted to be `null` and `0` respectively, and asserted
+/// that the socket was actually connected.
+/// Otherwise, the address of the target is given by `dest_addr` with `addrlen` specifying its size.
+///
+/// If the message is too long to pass atomically through the underlying protocol,
+/// `SendError.MessageTooBig` is returned, and the message is not transmitted.
+///
+/// There is no indication of failure to deliver.
+///
+/// When the message does not fit into the send buffer of the socket, `sendto` normally blocks,
+/// unless the socket has been placed in nonblocking I/O mode. In nonblocking mode it would fail
+/// with `SendError.WouldBlock`. The `select` call may be used to determine when it is
+/// possible to send more data.
+pub fn sendto(
+ /// The file descriptor of the sending socket.
+ sockfd: socket_t,
+ /// Message to send.
+ buf: []const u8,
+ flags: u32,
+ dest_addr: ?*const sockaddr,
+ addrlen: socklen_t,
+) SendToError!usize {
+ if (native_os == .windows) {
+ switch (windows.sendto(sockfd, buf.ptr, buf.len, flags, dest_addr, addrlen)) {
+ windows.ws2_32.SOCKET_ERROR => switch (windows.ws2_32.WSAGetLastError()) {
+ .WSAEACCES => return error.AccessDenied,
+ .WSAEADDRNOTAVAIL => return error.AddressNotAvailable,
+ .WSAECONNRESET => return error.ConnectionResetByPeer,
+ .WSAEMSGSIZE => return error.MessageTooBig,
+ .WSAENOBUFS => return error.SystemResources,
+ .WSAENOTSOCK => return error.FileDescriptorNotASocket,
+ .WSAEAFNOSUPPORT => return error.AddressFamilyNotSupported,
+ .WSAEDESTADDRREQ => unreachable, // A destination address is required.
+ .WSAEFAULT => unreachable, // The lpBuffers, lpTo, lpOverlapped, lpNumberOfBytesSent, or lpCompletionRoutine parameters are not part of the user address space, or the lpTo parameter is too small.
+ .WSAEHOSTUNREACH => return error.NetworkUnreachable,
+ // TODO: WSAEINPROGRESS, WSAEINTR
+ .WSAEINVAL => unreachable,
+ .WSAENETDOWN => return error.NetworkSubsystemFailed,
+ .WSAENETRESET => return error.ConnectionResetByPeer,
+ .WSAENETUNREACH => return error.NetworkUnreachable,
+ .WSAENOTCONN => return error.SocketNotConnected,
+ .WSAESHUTDOWN => unreachable, // The socket has been shut down; it is not possible to WSASendTo on a socket after shutdown has been invoked with how set to SD_SEND or SD_BOTH.
+ .WSAEWOULDBLOCK => return error.WouldBlock,
+ .WSANOTINITIALISED => unreachable, // A successful WSAStartup call must occur before using this function.
+ else => |err| return windows.unexpectedWSAError(err),
+ },
+ else => |rc| return @intCast(rc),
+ }
+ }
+ while (true) {
+ const rc = system.sendto(sockfd, buf.ptr, buf.len, flags, dest_addr, addrlen);
+ switch (errno(rc)) {
+ .SUCCESS => return @intCast(rc),
+
+ .ACCES => return error.AccessDenied,
+ .AGAIN => return error.WouldBlock,
+ .ALREADY => return error.FastOpenAlreadyInProgress,
+ .BADF => unreachable, // always a race condition
+ .CONNRESET => return error.ConnectionResetByPeer,
+ .DESTADDRREQ => unreachable, // The socket is not connection-mode, and no peer address is set.
+ .FAULT => unreachable, // An invalid user space address was specified for an argument.
+ .INTR => continue,
+ .INVAL => return error.UnreachableAddress,
+ .ISCONN => unreachable, // connection-mode socket was connected already but a recipient was specified
+ .MSGSIZE => return error.MessageTooBig,
+ .NOBUFS => return error.SystemResources,
+ .NOMEM => return error.SystemResources,
+ .NOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.
+ .OPNOTSUPP => unreachable, // Some bit in the flags argument is inappropriate for the socket type.
+ .PIPE => return error.BrokenPipe,
+ .AFNOSUPPORT => return error.AddressFamilyNotSupported,
+ .LOOP => return error.SymLinkLoop,
+ .NAMETOOLONG => return error.NameTooLong,
+ .NOENT => return error.FileNotFound,
+ .NOTDIR => return error.NotDir,
+ .HOSTUNREACH => return error.NetworkUnreachable,
+ .NETUNREACH => return error.NetworkUnreachable,
+ .NOTCONN => return error.SocketNotConnected,
+ .NETDOWN => return error.NetworkSubsystemFailed,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+}
+
+/// Transmit a message to another socket.
+///
+/// The `send` call may be used only when the socket is in a connected state (so that the intended
+/// recipient is known). The only difference between `send` and `write` is the presence of
+/// flags. With a zero flags argument, `send` is equivalent to `write`. Also, the following
+/// call
+///
+/// send(sockfd, buf, len, flags);
+///
+/// is equivalent to
+///
+/// sendto(sockfd, buf, len, flags, NULL, 0);
+///
+/// There is no indication of failure to deliver.
+///
+/// When the message does not fit into the send buffer of the socket, `send` normally blocks,
+/// unless the socket has been placed in nonblocking I/O mode. In nonblocking mode it would fail
+/// with `SendError.WouldBlock`. The `select` call may be used to determine when it is
+/// possible to send more data.
+pub fn send(
+ /// The file descriptor of the sending socket.
+ sockfd: socket_t,
+ buf: []const u8,
+ flags: u32,
+) SendError!usize {
+ return sendto(sockfd, buf, flags, null, 0) catch |err| switch (err) {
+ error.AddressFamilyNotSupported => unreachable,
+ error.SymLinkLoop => unreachable,
+ error.NameTooLong => unreachable,
+ error.FileNotFound => unreachable,
+ error.NotDir => unreachable,
+ error.NetworkUnreachable => unreachable,
+ error.AddressNotAvailable => unreachable,
+ error.SocketNotConnected => unreachable,
+ error.UnreachableAddress => unreachable,
+ else => |e| return e,
+ };
+}
+
+pub const SendFileError = PReadError || WriteError || SendError;
+
+/// Transfer data between file descriptors, with optional headers and trailers.
+///
+/// Returns the number of bytes written, which can be zero.
+///
+/// The `sendfile` call copies `in_len` bytes from one file descriptor to another. When possible,
+/// this is done within the operating system kernel, which can provide better performance
+/// characteristics than transferring data from kernel to user space and back, such as with
+/// `read` and `write` calls. When `in_len` is `0`, it means to copy until the end of the input file has been
+/// reached. Note, however, that partial writes are still possible in this case.
+///
+/// `in_fd` must be a file descriptor opened for reading, and `out_fd` must be a file descriptor
+/// opened for writing. They may be any kind of file descriptor; however, if `in_fd` is not a regular
+/// file system file, it may cause this function to fall back to calling `read` and `write`, in which case
+/// atomicity guarantees no longer apply.
+///
+/// Copying begins reading at `in_offset`. The input file descriptor seek position is ignored and not updated.
+/// If the output file descriptor has a seek position, it is updated as bytes are written. When
+/// `in_offset` is past the end of the input file, it successfully reads 0 bytes.
+///
+/// `flags` has different meanings per operating system; refer to the respective man pages.
+///
+/// These systems support atomically sending everything, including headers and trailers:
+/// * macOS
+/// * FreeBSD
+///
+/// These systems support in-kernel data copying, but headers and trailers are not sent atomically:
+/// * Linux
+///
+/// Other systems fall back to calling `read` / `write`.
+///
+/// Linux has a limit on how many bytes may be transferred in one `sendfile` call, which is `0x7ffff000`
+/// on both 64-bit and 32-bit systems. This is due to using a signed C int as the return value, as
+/// well as stuffing the errno codes into the last `4096` values. This is noted on the `sendfile` man page.
+/// The limit on Darwin is `0x7fffffff`, trying to write more than that returns EINVAL.
+/// The corresponding POSIX limit on this is `maxInt(isize)`.
+pub fn sendfile(
+ out_fd: fd_t,
+ in_fd: fd_t,
+ in_offset: u64,
+ in_len: u64,
+ headers: []const iovec_const,
+ trailers: []const iovec_const,
+ flags: u32,
+) SendFileError!usize {
+ var header_done = false;
+ var total_written: usize = 0;
+
+ // Prevents EOVERFLOW.
+ const size_t = std.meta.Int(.unsigned, @typeInfo(usize).Int.bits - 1);
+ const max_count = switch (native_os) {
+ .linux => 0x7ffff000,
+ .macos, .ios, .watchos, .tvos => maxInt(i32),
+ else => maxInt(size_t),
+ };
+
+ switch (native_os) {
+ .linux => sf: {
+ // sendfile() first appeared in Linux 2.2, glibc 2.1.
+ const call_sf = comptime if (builtin.link_libc)
+ std.c.versionCheck(.{ .major = 2, .minor = 1, .patch = 0 })
+ else
+ builtin.os.version_range.linux.range.max.order(.{ .major = 2, .minor = 2, .patch = 0 }) != .lt;
+ if (!call_sf) break :sf;
+
+ if (headers.len != 0) {
+ const amt = try writev(out_fd, headers);
+ total_written += amt;
+ if (amt < count_iovec_bytes(headers)) return total_written;
+ header_done = true;
+ }
+
+ // Here we match BSD behavior, making a zero count value send as many bytes as possible.
+ const adjusted_count = if (in_len == 0) max_count else @min(in_len, max_count);
+
+ const sendfile_sym = if (lfs64_abi) system.sendfile64 else system.sendfile;
+ while (true) {
+ var offset: off_t = @bitCast(in_offset);
+ const rc = sendfile_sym(out_fd, in_fd, &offset, adjusted_count);
+ switch (errno(rc)) {
+ .SUCCESS => {
+ const amt: usize = @bitCast(rc);
+ total_written += amt;
+ if (in_len == 0 and amt == 0) {
+ // We have detected EOF from `in_fd`.
+ break;
+ } else if (amt < in_len) {
+ return total_written;
+ } else {
+ break;
+ }
+ },
+
+ .BADF => unreachable, // Always a race condition.
+ .FAULT => unreachable, // Segmentation fault.
+ .OVERFLOW => unreachable, // We avoid passing too large of a `count`.
+ .NOTCONN => return error.BrokenPipe, // `out_fd` is an unconnected socket
+
+ .INVAL, .NOSYS => {
+ // EINVAL could be any of the following situations:
+ // * Descriptor is not valid or locked
+ // * an mmap(2)-like operation is not available for in_fd
+ // * count is negative
+ // * out_fd has the APPEND flag set
+ // Because of the "mmap(2)-like operation" possibility, we fall back to doing read/write
+ // manually, the same as ENOSYS.
+ break :sf;
+ },
+ .AGAIN => return error.WouldBlock,
+ .IO => return error.InputOutput,
+ .PIPE => return error.BrokenPipe,
+ .NOMEM => return error.SystemResources,
+ .NXIO => return error.Unseekable,
+ .SPIPE => return error.Unseekable,
+ else => |err| {
+ unexpectedErrno(err) catch {};
+ break :sf;
+ },
+ }
+ }
+
+ if (trailers.len != 0) {
+ total_written += try writev(out_fd, trailers);
+ }
+
+ return total_written;
+ },
+ .freebsd => sf: {
+ var hdtr_data: std.c.sf_hdtr = undefined;
+ var hdtr: ?*std.c.sf_hdtr = null;
+ if (headers.len != 0 or trailers.len != 0) {
+ // Here we carefully avoid `@intCast` by returning partial writes when
+ // too many io vectors are provided.
+ const hdr_cnt = cast(u31, headers.len) orelse maxInt(u31);
+ if (headers.len > hdr_cnt) return writev(out_fd, headers);
+
+ const trl_cnt = cast(u31, trailers.len) orelse maxInt(u31);
+
+ hdtr_data = std.c.sf_hdtr{
+ .headers = headers.ptr,
+ .hdr_cnt = hdr_cnt,
+ .trailers = trailers.ptr,
+ .trl_cnt = trl_cnt,
+ };
+ hdtr = &hdtr_data;
+ }
+
+ while (true) {
+ var sbytes: off_t = undefined;
+ const err = errno(system.sendfile(in_fd, out_fd, @bitCast(in_offset), @min(in_len, max_count), hdtr, &sbytes, flags));
+ const amt: usize = @bitCast(sbytes);
+ switch (err) {
+ .SUCCESS => return amt,
+
+ .BADF => unreachable, // Always a race condition.
+ .FAULT => unreachable, // Segmentation fault.
+ .NOTCONN => return error.BrokenPipe, // `out_fd` is an unconnected socket
+
+ .INVAL, .OPNOTSUPP, .NOTSOCK, .NOSYS => {
+ // EINVAL could be any of the following situations:
+ // * The fd argument is not a regular file.
+ // * The s argument is not a SOCK.STREAM type socket.
+ // * The offset argument is negative.
+ // Because of some of these possibilities, we fall back to doing read/write
+ // manually, the same as ENOSYS.
+ break :sf;
+ },
+
+ .INTR => if (amt != 0) return amt else continue,
+
+ .AGAIN => if (amt != 0) {
+ return amt;
+ } else {
+ return error.WouldBlock;
+ },
+
+ .BUSY => if (amt != 0) {
+ return amt;
+ } else {
+ return error.WouldBlock;
+ },
+
+ .IO => return error.InputOutput,
+ .NOBUFS => return error.SystemResources,
+ .PIPE => return error.BrokenPipe,
+
+ else => {
+ unexpectedErrno(err) catch {};
+ if (amt != 0) {
+ return amt;
+ } else {
+ break :sf;
+ }
+ },
+ }
+ }
+ },
+ .macos, .ios, .tvos, .watchos => sf: {
+ var hdtr_data: std.c.sf_hdtr = undefined;
+ var hdtr: ?*std.c.sf_hdtr = null;
+ if (headers.len != 0 or trailers.len != 0) {
+ // Here we carefully avoid `@intCast` by returning partial writes when
+ // too many io vectors are provided.
+ const hdr_cnt = cast(u31, headers.len) orelse maxInt(u31);
+ if (headers.len > hdr_cnt) return writev(out_fd, headers);
+
+ const trl_cnt = cast(u31, trailers.len) orelse maxInt(u31);
+
+ hdtr_data = std.c.sf_hdtr{
+ .headers = headers.ptr,
+ .hdr_cnt = hdr_cnt,
+ .trailers = trailers.ptr,
+ .trl_cnt = trl_cnt,
+ };
+ hdtr = &hdtr_data;
+ }
+
+ while (true) {
+ var sbytes: off_t = @min(in_len, max_count);
+ const err = errno(system.sendfile(in_fd, out_fd, @bitCast(in_offset), &sbytes, hdtr, flags));
+ const amt: usize = @bitCast(sbytes);
+ switch (err) {
+ .SUCCESS => return amt,
+
+ .BADF => unreachable, // Always a race condition.
+ .FAULT => unreachable, // Segmentation fault.
+ .INVAL => unreachable,
+ .NOTCONN => return error.BrokenPipe, // `out_fd` is an unconnected socket
+
+ .OPNOTSUPP, .NOTSOCK, .NOSYS => break :sf,
+
+ .INTR => if (amt != 0) return amt else continue,
+
+ .AGAIN => if (amt != 0) {
+ return amt;
+ } else {
+ return error.WouldBlock;
+ },
+
+ .IO => return error.InputOutput,
+ .PIPE => return error.BrokenPipe,
+
+ else => {
+ unexpectedErrno(err) catch {};
+ if (amt != 0) {
+ return amt;
+ } else {
+ break :sf;
+ }
+ },
+ }
+ }
+ },
+ else => {}, // fall back to read/write
+ }
+
+ if (headers.len != 0 and !header_done) {
+ const amt = try writev(out_fd, headers);
+ total_written += amt;
+ if (amt < count_iovec_bytes(headers)) return total_written;
+ }
+
+ rw: {
+ var buf: [8 * 4096]u8 = undefined;
+ // Here we match BSD behavior, making a zero count value send as many bytes as possible.
+ const adjusted_count = if (in_len == 0) buf.len else @min(buf.len, in_len);
+ const amt_read = try pread(in_fd, buf[0..adjusted_count], in_offset);
+ if (amt_read == 0) {
+ if (in_len == 0) {
+ // We have detected EOF from `in_fd`.
+ break :rw;
+ } else {
+ return total_written;
+ }
+ }
+ const amt_written = try write(out_fd, buf[0..amt_read]);
+ total_written += amt_written;
+ if (amt_written < in_len or in_len == 0) return total_written;
+ }
+
+ if (trailers.len != 0) {
+ total_written += try writev(out_fd, trailers);
+ }
+
+ return total_written;
+}
+
+fn count_iovec_bytes(iovs: []const iovec_const) usize {
+ var count: usize = 0;
+ for (iovs) |iov| {
+ count += iov.iov_len;
+ }
+ return count;
+}
+
+pub const CopyFileRangeError = error{
+ FileTooBig,
+ InputOutput,
+ /// `fd_in` is not open for reading; or `fd_out` is not open for writing;
+ /// or the `APPEND` flag is set for `fd_out`.
+ FilesOpenedWithWrongFlags,
+ IsDir,
+ OutOfMemory,
+ NoSpaceLeft,
+ Unseekable,
+ PermissionDenied,
+ SwapFile,
+ CorruptedData,
+} || PReadError || PWriteError || UnexpectedError;
+
+/// Transfer data between file descriptors at specified offsets.
+///
+/// Returns the number of bytes written, which can less than requested.
+///
+/// The `copy_file_range` call copies `len` bytes from one file descriptor to another. When possible,
+/// this is done within the operating system kernel, which can provide better performance
+/// characteristics than transferring data from kernel to user space and back, such as with
+/// `pread` and `pwrite` calls.
+///
+/// `fd_in` must be a file descriptor opened for reading, and `fd_out` must be a file descriptor
+/// opened for writing. They may be any kind of file descriptor; however, if `fd_in` is not a regular
+/// file system file, it may cause this function to fall back to calling `pread` and `pwrite`, in which case
+/// atomicity guarantees no longer apply.
+///
+/// If `fd_in` and `fd_out` are the same, source and target ranges must not overlap.
+/// The file descriptor seek positions are ignored and not updated.
+/// When `off_in` is past the end of the input file, it successfully reads 0 bytes.
+///
+/// `flags` has different meanings per operating system; refer to the respective man pages.
+///
+/// These systems support in-kernel data copying:
+/// * Linux 4.5 (cross-filesystem 5.3)
+/// * FreeBSD 13.0
+///
+/// Other systems fall back to calling `pread` / `pwrite`.
+///
+/// Maximum offsets on Linux and FreeBSD are `maxInt(i64)`.
+pub fn copy_file_range(fd_in: fd_t, off_in: u64, fd_out: fd_t, off_out: u64, len: usize, flags: u32) CopyFileRangeError!usize {
+ const global = struct {
+ var has_copy_file_range = true;
+ };
+
+ if ((comptime builtin.os.isAtLeast(.freebsd, .{ .major = 13, .minor = 0, .patch = 0 }) orelse false) or
+ ((comptime builtin.os.isAtLeast(.linux, .{ .major = 4, .minor = 5, .patch = 0 }) orelse false and
+ std.c.versionCheck(.{ .major = 2, .minor = 27, .patch = 0 })) and
+ @atomicLoad(bool, &global.has_copy_file_range, .monotonic)))
+ {
+ var off_in_copy: i64 = @bitCast(off_in);
+ var off_out_copy: i64 = @bitCast(off_out);
+
+ while (true) {
+ const rc = system.copy_file_range(fd_in, &off_in_copy, fd_out, &off_out_copy, len, flags);
+ if (native_os == .freebsd) {
+ switch (errno(rc)) {
+ .SUCCESS => return @intCast(rc),
+ .BADF => return error.FilesOpenedWithWrongFlags,
+ .FBIG => return error.FileTooBig,
+ .IO => return error.InputOutput,
+ .ISDIR => return error.IsDir,
+ .NOSPC => return error.NoSpaceLeft,
+ .INVAL => break, // these may not be regular files, try fallback
+ .INTEGRITY => return error.CorruptedData,
+ .INTR => continue,
+ else => |err| return unexpectedErrno(err),
+ }
+ } else { // assume linux
+ switch (errno(rc)) {
+ .SUCCESS => return @intCast(rc),
+ .BADF => return error.FilesOpenedWithWrongFlags,
+ .FBIG => return error.FileTooBig,
+ .IO => return error.InputOutput,
+ .ISDIR => return error.IsDir,
+ .NOSPC => return error.NoSpaceLeft,
+ .INVAL => break, // these may not be regular files, try fallback
+ .NOMEM => return error.OutOfMemory,
+ .OVERFLOW => return error.Unseekable,
+ .PERM => return error.PermissionDenied,
+ .TXTBSY => return error.SwapFile,
+ .XDEV => break, // support for cross-filesystem copy added in Linux 5.3, use fallback
+ .NOSYS => {
+ @atomicStore(bool, &global.has_copy_file_range, false, .monotonic);
+ break;
+ },
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+ }
+ }
+
+ var buf: [8 * 4096]u8 = undefined;
+ const amt_read = try pread(fd_in, buf[0..@min(buf.len, len)], off_in);
+ if (amt_read == 0) return 0;
+ return pwrite(fd_out, buf[0..amt_read], off_out);
+}
+
+pub const PollError = error{
+ /// The network subsystem has failed.
+ NetworkSubsystemFailed,
+
+ /// The kernel had no space to allocate file descriptor tables.
+ SystemResources,
+} || UnexpectedError;
+
+pub fn poll(fds: []pollfd, timeout: i32) PollError!usize {
+ while (true) {
+ const fds_count = cast(nfds_t, fds.len) orelse return error.SystemResources;
+ const rc = system.poll(fds.ptr, fds_count, timeout);
+ if (native_os == .windows) {
+ if (rc == windows.ws2_32.SOCKET_ERROR) {
+ switch (windows.ws2_32.WSAGetLastError()) {
+ .WSANOTINITIALISED => unreachable,
+ .WSAENETDOWN => return error.NetworkSubsystemFailed,
+ .WSAENOBUFS => return error.SystemResources,
+ // TODO: handle more errors
+ else => |err| return windows.unexpectedWSAError(err),
+ }
+ } else {
+ return @intCast(rc);
+ }
+ } else {
+ switch (errno(rc)) {
+ .SUCCESS => return @intCast(rc),
+ .FAULT => unreachable,
+ .INTR => continue,
+ .INVAL => unreachable,
+ .NOMEM => return error.SystemResources,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+ unreachable;
+ }
+}
+
+pub const PPollError = error{
+ /// The operation was interrupted by a delivery of a signal before it could complete.
+ SignalInterrupt,
+
+ /// The kernel had no space to allocate file descriptor tables.
+ SystemResources,
+} || UnexpectedError;
+
+pub fn ppoll(fds: []pollfd, timeout: ?*const timespec, mask: ?*const sigset_t) PPollError!usize {
+ var ts: timespec = undefined;
+ var ts_ptr: ?*timespec = null;
+ if (timeout) |timeout_ns| {
+ ts_ptr = &ts;
+ ts = timeout_ns.*;
+ }
+ const fds_count = cast(nfds_t, fds.len) orelse return error.SystemResources;
+ const rc = system.ppoll(fds.ptr, fds_count, ts_ptr, mask);
+ switch (errno(rc)) {
+ .SUCCESS => return @intCast(rc),
+ .FAULT => unreachable,
+ .INTR => return error.SignalInterrupt,
+ .INVAL => unreachable,
+ .NOMEM => return error.SystemResources,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+pub const RecvFromError = error{
+ /// The socket is marked nonblocking and the requested operation would block, and
+ /// there is no global event loop configured.
+ WouldBlock,
+
+ /// A remote host refused to allow the network connection, typically because it is not
+ /// running the requested service.
+ ConnectionRefused,
+
+ /// Could not allocate kernel memory.
+ SystemResources,
+
+ ConnectionResetByPeer,
+ ConnectionTimedOut,
+
+ /// The socket has not been bound.
+ SocketNotBound,
+
+ /// The UDP message was too big for the buffer and part of it has been discarded
+ MessageTooBig,
+
+ /// The network subsystem has failed.
+ NetworkSubsystemFailed,
+
+ /// The socket is not connected (connection-oriented sockets only).
+ SocketNotConnected,
+} || UnexpectedError;
+
+pub fn recv(sock: socket_t, buf: []u8, flags: u32) RecvFromError!usize {
+ return recvfrom(sock, buf, flags, null, null);
+}
+
+/// If `sockfd` is opened in non blocking mode, the function will
+/// return error.WouldBlock when EAGAIN is received.
+pub fn recvfrom(
+ sockfd: socket_t,
+ buf: []u8,
+ flags: u32,
+ src_addr: ?*sockaddr,
+ addrlen: ?*socklen_t,
+) RecvFromError!usize {
+ while (true) {
+ const rc = system.recvfrom(sockfd, buf.ptr, buf.len, flags, src_addr, addrlen);
+ if (native_os == .windows) {
+ if (rc == windows.ws2_32.SOCKET_ERROR) {
+ switch (windows.ws2_32.WSAGetLastError()) {
+ .WSANOTINITIALISED => unreachable,
+ .WSAECONNRESET => return error.ConnectionResetByPeer,
+ .WSAEINVAL => return error.SocketNotBound,
+ .WSAEMSGSIZE => return error.MessageTooBig,
+ .WSAENETDOWN => return error.NetworkSubsystemFailed,
+ .WSAENOTCONN => return error.SocketNotConnected,
+ .WSAEWOULDBLOCK => return error.WouldBlock,
+ .WSAETIMEDOUT => return error.ConnectionTimedOut,
+ // TODO: handle more errors
+ else => |err| return windows.unexpectedWSAError(err),
+ }
+ } else {
+ return @intCast(rc);
+ }
+ } else {
+ switch (errno(rc)) {
+ .SUCCESS => return @intCast(rc),
+ .BADF => unreachable, // always a race condition
+ .FAULT => unreachable,
+ .INVAL => unreachable,
+ .NOTCONN => return error.SocketNotConnected,
+ .NOTSOCK => unreachable,
+ .INTR => continue,
+ .AGAIN => return error.WouldBlock,
+ .NOMEM => return error.SystemResources,
+ .CONNREFUSED => return error.ConnectionRefused,
+ .CONNRESET => return error.ConnectionResetByPeer,
+ .TIMEDOUT => return error.ConnectionTimedOut,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+ }
+}
+
+pub const DnExpandError = error{InvalidDnsPacket};
+
+pub fn dn_expand(
+ msg: []const u8,
+ comp_dn: []const u8,
+ exp_dn: []u8,
+) DnExpandError!usize {
+ // This implementation is ported from musl libc.
+ // A more idiomatic "ziggy" implementation would be welcome.
+ var p = comp_dn.ptr;
+ var len: usize = maxInt(usize);
+ const end = msg.ptr + msg.len;
+ if (p == end or exp_dn.len == 0) return error.InvalidDnsPacket;
+ var dest = exp_dn.ptr;
+ const dend = dest + @min(exp_dn.len, 254);
+ // detect reference loop using an iteration counter
+ var i: usize = 0;
+ while (i < msg.len) : (i += 2) {
+ // loop invariants: p<end, dest<dend
+ if ((p[0] & 0xc0) != 0) {
+ if (p + 1 == end) return error.InvalidDnsPacket;
+ const j = @as(usize, p[0] & 0x3f) << 8 | p[1];
+ if (len == maxInt(usize)) len = @intFromPtr(p) + 2 - @intFromPtr(comp_dn.ptr);
+ if (j >= msg.len) return error.InvalidDnsPacket;
+ p = msg.ptr + j;
+ } else if (p[0] != 0) {
+ if (dest != exp_dn.ptr) {
+ dest[0] = '.';
+ dest += 1;
+ }
+ var j = p[0];
+ p += 1;
+ if (j >= @intFromPtr(end) - @intFromPtr(p) or j >= @intFromPtr(dend) - @intFromPtr(dest)) {
+ return error.InvalidDnsPacket;
+ }
+ while (j != 0) {
+ j -= 1;
+ dest[0] = p[0];
+ dest += 1;
+ p += 1;
+ }
+ } else {
+ dest[0] = 0;
+ if (len == maxInt(usize)) len = @intFromPtr(p) + 1 - @intFromPtr(comp_dn.ptr);
+ return len;
+ }
+ }
+ return error.InvalidDnsPacket;
+}
+
+pub const SetSockOptError = error{
+ /// The socket is already connected, and a specified option cannot be set while the socket is connected.
+ AlreadyConnected,
+
+ /// The option is not supported by the protocol.
+ InvalidProtocolOption,
+
+ /// The send and receive timeout values are too big to fit into the timeout fields in the socket structure.
+ TimeoutTooBig,
+
+ /// Insufficient resources are available in the system to complete the call.
+ SystemResources,
+
+ // Setting the socket option requires more elevated permissions.
+ PermissionDenied,
+
+ NetworkSubsystemFailed,
+ FileDescriptorNotASocket,
+ SocketNotBound,
+ NoDevice,
+} || UnexpectedError;
+
+/// Set a socket's options.
+pub fn setsockopt(fd: socket_t, level: u32, optname: u32, opt: []const u8) SetSockOptError!void {
+ if (native_os == .windows) {
+ const rc = windows.ws2_32.setsockopt(fd, @intCast(level), @intCast(optname), opt.ptr, @intCast(opt.len));
+ if (rc == windows.ws2_32.SOCKET_ERROR) {
+ switch (windows.ws2_32.WSAGetLastError()) {
+ .WSANOTINITIALISED => unreachable,
+ .WSAENETDOWN => return error.NetworkSubsystemFailed,
+ .WSAEFAULT => unreachable,
+ .WSAENOTSOCK => return error.FileDescriptorNotASocket,
+ .WSAEINVAL => return error.SocketNotBound,
+ else => |err| return windows.unexpectedWSAError(err),
+ }
+ }
+ return;
+ } else {
+ switch (errno(system.setsockopt(fd, level, optname, opt.ptr, @intCast(opt.len)))) {
+ .SUCCESS => {},
+ .BADF => unreachable, // always a race condition
+ .NOTSOCK => unreachable, // always a race condition
+ .INVAL => unreachable,
+ .FAULT => unreachable,
+ .DOM => return error.TimeoutTooBig,
+ .ISCONN => return error.AlreadyConnected,
+ .NOPROTOOPT => return error.InvalidProtocolOption,
+ .NOMEM => return error.SystemResources,
+ .NOBUFS => return error.SystemResources,
+ .PERM => return error.PermissionDenied,
+ .NODEV => return error.NoDevice,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+}
+
+pub const MemFdCreateError = error{
+ SystemFdQuotaExceeded,
+ ProcessFdQuotaExceeded,
+ OutOfMemory,
+ /// Either the name provided exceeded `NAME_MAX`, or invalid flags were passed.
+ NameTooLong,
+
+ /// memfd_create is available in Linux 3.17 and later. This error is returned
+ /// for older kernel versions.
+ SystemOutdated,
+} || UnexpectedError;
+
+pub fn memfd_createZ(name: [*:0]const u8, flags: u32) MemFdCreateError!fd_t {
+ switch (native_os) {
+ .linux => {
+ // memfd_create is available only in glibc versions starting with 2.27.
+ const use_c = std.c.versionCheck(.{ .major = 2, .minor = 27, .patch = 0 });
+ const sys = if (use_c) std.c else linux;
+ const rc = sys.memfd_create(name, flags);
+ switch (errno(rc)) {
+ .SUCCESS => return @intCast(rc),
+ .FAULT => unreachable, // name has invalid memory
+ .INVAL => return error.NameTooLong, // or, program has a bug and flags are faulty
+ .NFILE => return error.SystemFdQuotaExceeded,
+ .MFILE => return error.ProcessFdQuotaExceeded,
+ .NOMEM => return error.OutOfMemory,
+ .NOSYS => return error.SystemOutdated,
+ else => |err| return unexpectedErrno(err),
+ }
+ },
+ .freebsd => {
+ if (comptime builtin.os.version_range.semver.max.order(.{ .major = 13, .minor = 0, .patch = 0 }) == .lt)
+ @compileError("memfd_create is unavailable on FreeBSD < 13.0");
+ const rc = system.memfd_create(name, flags);
+ switch (errno(rc)) {
+ .SUCCESS => return rc,
+ .BADF => unreachable, // name argument NULL
+ .INVAL => unreachable, // name too long or invalid/unsupported flags.
+ .MFILE => return error.ProcessFdQuotaExceeded,
+ .NFILE => return error.SystemFdQuotaExceeded,
+ .NOSYS => return error.SystemOutdated,
+ else => |err| return unexpectedErrno(err),
+ }
+ },
+ else => @compileError("target OS does not support memfd_create()"),
+ }
+}
+
+pub fn memfd_create(name: []const u8, flags: u32) MemFdCreateError!fd_t {
+ var buffer: [NAME_MAX - "memfd:".len - 1:0]u8 = undefined;
+ if (name.len > buffer.len) return error.NameTooLong;
+ @memcpy(buffer[0..name.len], name);
+ buffer[name.len] = 0;
+ return memfd_createZ(&buffer, flags);
+}
+
+pub fn getrusage(who: i32) rusage {
+ var result: rusage = undefined;
+ const rc = system.getrusage(who, &result);
+ switch (errno(rc)) {
+ .SUCCESS => return result,
+ .INVAL => unreachable,
+ .FAULT => unreachable,
+ else => unreachable,
+ }
+}
+
+pub const TIOCError = error{NotATerminal};
+
+pub const TermiosGetError = TIOCError || UnexpectedError;
+
+pub fn tcgetattr(handle: fd_t) TermiosGetError!termios {
+ while (true) {
+ var term: termios = undefined;
+ switch (errno(system.tcgetattr(handle, &term))) {
+ .SUCCESS => return term,
+ .INTR => continue,
+ .BADF => unreachable,
+ .NOTTY => return error.NotATerminal,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+}
+
+pub const TermiosSetError = TermiosGetError || error{ProcessOrphaned};
+
+pub fn tcsetattr(handle: fd_t, optional_action: TCSA, termios_p: termios) TermiosSetError!void {
+ while (true) {
+ switch (errno(system.tcsetattr(handle, optional_action, &termios_p))) {
+ .SUCCESS => return,
+ .BADF => unreachable,
+ .INTR => continue,
+ .INVAL => unreachable,
+ .NOTTY => return error.NotATerminal,
+ .IO => return error.ProcessOrphaned,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+}
+
+pub const TermioGetPgrpError = TIOCError || UnexpectedError;
+
+/// Returns the process group ID for the TTY associated with the given handle.
+pub fn tcgetpgrp(handle: fd_t) TermioGetPgrpError!pid_t {
+ while (true) {
+ var pgrp: pid_t = undefined;
+ switch (errno(system.tcgetpgrp(handle, &pgrp))) {
+ .SUCCESS => return pgrp,
+ .BADF => unreachable,
+ .INVAL => unreachable,
+ .INTR => continue,
+ .NOTTY => return error.NotATerminal,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+}
+
+pub const TermioSetPgrpError = TermioGetPgrpError || error{NotAPgrpMember};
+
+/// Sets the controlling process group ID for given TTY.
+/// handle must be valid fd_t to a TTY associated with calling process.
+/// pgrp must be a valid process group, and the calling process must be a member
+/// of that group.
+pub fn tcsetpgrp(handle: fd_t, pgrp: pid_t) TermioSetPgrpError!void {
+ while (true) {
+ switch (errno(system.tcsetpgrp(handle, &pgrp))) {
+ .SUCCESS => return,
+ .BADF => unreachable,
+ .INVAL => unreachable,
+ .INTR => continue,
+ .NOTTY => return error.NotATerminal,
+ .PERM => return TermioSetPgrpError.NotAPgrpMember,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+}
+
+pub fn signalfd(fd: fd_t, mask: *const sigset_t, flags: u32) !fd_t {
+ const rc = system.signalfd(fd, mask, flags);
+ switch (errno(rc)) {
+ .SUCCESS => return @intCast(rc),
+ .BADF, .INVAL => unreachable,
+ .NFILE => return error.SystemFdQuotaExceeded,
+ .NOMEM => return error.SystemResources,
+ .MFILE => return error.ProcessResources,
+ .NODEV => return error.InodeMountFail,
+ .NOSYS => return error.SystemOutdated,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+pub const SyncError = error{
+ InputOutput,
+ NoSpaceLeft,
+ DiskQuota,
+ AccessDenied,
+} || UnexpectedError;
+
+/// Write all pending file contents and metadata modifications to all filesystems.
+pub fn sync() void {
+ system.sync();
+}
+
+/// Write all pending file contents and metadata modifications to the filesystem which contains the specified file.
+pub fn syncfs(fd: fd_t) SyncError!void {
+ const rc = system.syncfs(fd);
+ switch (errno(rc)) {
+ .SUCCESS => return,
+ .BADF, .INVAL, .ROFS => unreachable,
+ .IO => return error.InputOutput,
+ .NOSPC => return error.NoSpaceLeft,
+ .DQUOT => return error.DiskQuota,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+/// Write all pending file contents and metadata modifications for the specified file descriptor to the underlying filesystem.
+pub fn fsync(fd: fd_t) SyncError!void {
+ if (native_os == .windows) {
+ if (windows.kernel32.FlushFileBuffers(fd) != 0)
+ return;
+ switch (windows.kernel32.GetLastError()) {
+ .SUCCESS => return,
+ .INVALID_HANDLE => unreachable,
+ .ACCESS_DENIED => return error.AccessDenied, // a sync was performed but the system couldn't update the access time
+ .UNEXP_NET_ERR => return error.InputOutput,
+ else => return error.InputOutput,
+ }
+ }
+ const rc = system.fsync(fd);
+ switch (errno(rc)) {
+ .SUCCESS => return,
+ .BADF, .INVAL, .ROFS => unreachable,
+ .IO => return error.InputOutput,
+ .NOSPC => return error.NoSpaceLeft,
+ .DQUOT => return error.DiskQuota,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+/// Write all pending file contents for the specified file descriptor to the underlying filesystem, but not necessarily the metadata.
+pub fn fdatasync(fd: fd_t) SyncError!void {
+ if (native_os == .windows) {
+ return fsync(fd) catch |err| switch (err) {
+ SyncError.AccessDenied => return, // fdatasync doesn't promise that the access time was synced
+ else => return err,
+ };
+ }
+ const rc = system.fdatasync(fd);
+ switch (errno(rc)) {
+ .SUCCESS => return,
+ .BADF, .INVAL, .ROFS => unreachable,
+ .IO => return error.InputOutput,
+ .NOSPC => return error.NoSpaceLeft,
+ .DQUOT => return error.DiskQuota,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+pub const PrctlError = error{
+ /// Can only occur with PR_SET_SECCOMP/SECCOMP_MODE_FILTER or
+ /// PR_SET_MM/PR_SET_MM_EXE_FILE
+ AccessDenied,
+ /// Can only occur with PR_SET_MM/PR_SET_MM_EXE_FILE
+ InvalidFileDescriptor,
+ InvalidAddress,
+ /// Can only occur with PR_SET_SPECULATION_CTRL, PR_MPX_ENABLE_MANAGEMENT,
+ /// or PR_MPX_DISABLE_MANAGEMENT
+ UnsupportedFeature,
+ /// Can only occur with PR_SET_FP_MODE
+ OperationNotSupported,
+ PermissionDenied,
+} || UnexpectedError;
+
+pub fn prctl(option: PR, args: anytype) PrctlError!u31 {
+ if (@typeInfo(@TypeOf(args)) != .Struct)
+ @compileError("Expected tuple or struct argument, found " ++ @typeName(@TypeOf(args)));
+ if (args.len > 4)
+ @compileError("prctl takes a maximum of 4 optional arguments");
+
+ var buf: [4]usize = undefined;
+ {
+ comptime var i = 0;
+ inline while (i < args.len) : (i += 1) buf[i] = args[i];
+ }
+
+ const rc = system.prctl(@intFromEnum(option), buf[0], buf[1], buf[2], buf[3]);
+ switch (errno(rc)) {
+ .SUCCESS => return @intCast(rc),
+ .ACCES => return error.AccessDenied,
+ .BADF => return error.InvalidFileDescriptor,
+ .FAULT => return error.InvalidAddress,
+ .INVAL => unreachable,
+ .NODEV, .NXIO => return error.UnsupportedFeature,
+ .OPNOTSUPP => return error.OperationNotSupported,
+ .PERM, .BUSY => return error.PermissionDenied,
+ .RANGE => unreachable,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+pub const GetrlimitError = UnexpectedError;
+
+pub fn getrlimit(resource: rlimit_resource) GetrlimitError!rlimit {
+ const getrlimit_sym = if (lfs64_abi) system.getrlimit64 else system.getrlimit;
+
+ var limits: rlimit = undefined;
+ switch (errno(getrlimit_sym(resource, &limits))) {
+ .SUCCESS => return limits,
+ .FAULT => unreachable, // bogus pointer
+ .INVAL => unreachable,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+pub const SetrlimitError = error{ PermissionDenied, LimitTooBig } || UnexpectedError;
+
+pub fn setrlimit(resource: rlimit_resource, limits: rlimit) SetrlimitError!void {
+ const setrlimit_sym = if (lfs64_abi) system.setrlimit64 else system.setrlimit;
+
+ switch (errno(setrlimit_sym(resource, &limits))) {
+ .SUCCESS => return,
+ .FAULT => unreachable, // bogus pointer
+ .INVAL => return error.LimitTooBig, // this could also mean "invalid resource", but that would be unreachable
+ .PERM => return error.PermissionDenied,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+pub const MincoreError = error{
+ /// A kernel resource was temporarily unavailable.
+ SystemResources,
+ /// vec points to an invalid address.
+ InvalidAddress,
+ /// addr is not page-aligned.
+ InvalidSyscall,
+ /// One of the following:
+ /// * length is greater than user space TASK_SIZE - addr
+ /// * addr + length contains unmapped memory
+ OutOfMemory,
+ /// The mincore syscall is not available on this version and configuration
+ /// of this UNIX-like kernel.
+ MincoreUnavailable,
+} || UnexpectedError;
+
+/// Determine whether pages are resident in memory.
+pub fn mincore(ptr: [*]align(mem.page_size) u8, length: usize, vec: [*]u8) MincoreError!void {
+ return switch (errno(system.mincore(ptr, length, vec))) {
+ .SUCCESS => {},
+ .AGAIN => error.SystemResources,
+ .FAULT => error.InvalidAddress,
+ .INVAL => error.InvalidSyscall,
+ .NOMEM => error.OutOfMemory,
+ .NOSYS => error.MincoreUnavailable,
+ else => |err| unexpectedErrno(err),
+ };
+}
+
+pub const MadviseError = error{
+ /// advice is MADV.REMOVE, but the specified address range is not a shared writable mapping.
+ AccessDenied,
+ /// advice is MADV.HWPOISON, but the caller does not have the CAP_SYS_ADMIN capability.
+ PermissionDenied,
+ /// A kernel resource was temporarily unavailable.
+ SystemResources,
+ /// One of the following:
+ /// * addr is not page-aligned or length is negative
+ /// * advice is not valid
+ /// * advice is MADV.DONTNEED or MADV.REMOVE and the specified address range
+ /// includes locked, Huge TLB pages, or VM_PFNMAP pages.
+ /// * advice is MADV.MERGEABLE or MADV.UNMERGEABLE, but the kernel was not
+ /// configured with CONFIG_KSM.
+ /// * advice is MADV.FREE or MADV.WIPEONFORK but the specified address range
+ /// includes file, Huge TLB, MAP.SHARED, or VM_PFNMAP ranges.
+ InvalidSyscall,
+ /// (for MADV.WILLNEED) Paging in this area would exceed the process's
+ /// maximum resident set size.
+ WouldExceedMaximumResidentSetSize,
+ /// One of the following:
+ /// * (for MADV.WILLNEED) Not enough memory: paging in failed.
+ /// * Addresses in the specified range are not currently mapped, or
+ /// are outside the address space of the process.
+ OutOfMemory,
+ /// The madvise syscall is not available on this version and configuration
+ /// of the Linux kernel.
+ MadviseUnavailable,
+ /// The operating system returned an undocumented error code.
+ Unexpected,
+};
+
+/// Give advice about use of memory.
+/// This syscall is optional and is sometimes configured to be disabled.
+pub fn madvise(ptr: [*]align(mem.page_size) u8, length: usize, advice: u32) MadviseError!void {
+ switch (errno(system.madvise(ptr, length, advice))) {
+ .SUCCESS => return,
+ .ACCES => return error.AccessDenied,
+ .AGAIN => return error.SystemResources,
+ .BADF => unreachable, // The map exists, but the area maps something that isn't a file.
+ .INVAL => return error.InvalidSyscall,
+ .IO => return error.WouldExceedMaximumResidentSetSize,
+ .NOMEM => return error.OutOfMemory,
+ .NOSYS => return error.MadviseUnavailable,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+pub const PerfEventOpenError = error{
+ /// Returned if the perf_event_attr size value is too small (smaller
+ /// than PERF_ATTR_SIZE_VER0), too big (larger than the page size),
+ /// or larger than the kernel supports and the extra bytes are not
+ /// zero. When E2BIG is returned, the perf_event_attr size field is
+ /// overwritten by the kernel to be the size of the structure it was
+ /// expecting.
+ TooBig,
+ /// Returned when the requested event requires CAP_SYS_ADMIN permis‐
+ /// sions (or a more permissive perf_event paranoid setting). Some
+ /// common cases where an unprivileged process may encounter this
+ /// error: attaching to a process owned by a different user; moni‐
+ /// toring all processes on a given CPU (i.e., specifying the pid
+ /// argument as -1); and not setting exclude_kernel when the para‐
+ /// noid setting requires it.
+ /// Also:
+ /// Returned on many (but not all) architectures when an unsupported
+ /// exclude_hv, exclude_idle, exclude_user, or exclude_kernel set‐
+ /// ting is specified.
+ /// It can also happen, as with EACCES, when the requested event re‐
+ /// quires CAP_SYS_ADMIN permissions (or a more permissive
+ /// perf_event paranoid setting). This includes setting a break‐
+ /// point on a kernel address, and (since Linux 3.13) setting a ker‐
+ /// nel function-trace tracepoint.
+ PermissionDenied,
+ /// Returned if another event already has exclusive access to the
+ /// PMU.
+ DeviceBusy,
+ /// Each opened event uses one file descriptor. If a large number
+ /// of events are opened, the per-process limit on the number of
+ /// open file descriptors will be reached, and no more events can be
+ /// created.
+ ProcessResources,
+ EventRequiresUnsupportedCpuFeature,
+ /// Returned if you try to add more breakpoint
+ /// events than supported by the hardware.
+ TooManyBreakpoints,
+ /// Returned if PERF_SAMPLE_STACK_USER is set in sample_type and it
+ /// is not supported by hardware.
+ SampleStackNotSupported,
+ /// Returned if an event requiring a specific hardware feature is
+ /// requested but there is no hardware support. This includes re‐
+ /// questing low-skid events if not supported, branch tracing if it
+ /// is not available, sampling if no PMU interrupt is available, and
+ /// branch stacks for software events.
+ EventNotSupported,
+ /// Returned if PERF_SAMPLE_CALLCHAIN is requested and sam‐
+ /// ple_max_stack is larger than the maximum specified in
+ /// /proc/sys/kernel/perf_event_max_stack.
+ SampleMaxStackOverflow,
+ /// Returned if attempting to attach to a process that does not exist.
+ ProcessNotFound,
+} || UnexpectedError;
+
+pub fn perf_event_open(
+ attr: *linux.perf_event_attr,
+ pid: pid_t,
+ cpu: i32,
+ group_fd: fd_t,
+ flags: usize,
+) PerfEventOpenError!fd_t {
+ const rc = linux.perf_event_open(attr, pid, cpu, group_fd, flags);
+ switch (errno(rc)) {
+ .SUCCESS => return @intCast(rc),
+ .@"2BIG" => return error.TooBig,
+ .ACCES => return error.PermissionDenied,
+ .BADF => unreachable, // group_fd file descriptor is not valid.
+ .BUSY => return error.DeviceBusy,
+ .FAULT => unreachable, // Segmentation fault.
+ .INVAL => unreachable, // Bad attr settings.
+ .INTR => unreachable, // Mixed perf and ftrace handling for a uprobe.
+ .MFILE => return error.ProcessResources,
+ .NODEV => return error.EventRequiresUnsupportedCpuFeature,
+ .NOENT => unreachable, // Invalid type setting.
+ .NOSPC => return error.TooManyBreakpoints,
+ .NOSYS => return error.SampleStackNotSupported,
+ .OPNOTSUPP => return error.EventNotSupported,
+ .OVERFLOW => return error.SampleMaxStackOverflow,
+ .PERM => return error.PermissionDenied,
+ .SRCH => return error.ProcessNotFound,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+pub const TimerFdCreateError = error{
+ AccessDenied,
+ ProcessFdQuotaExceeded,
+ SystemFdQuotaExceeded,
+ NoDevice,
+ SystemResources,
+} || UnexpectedError;
+
+pub const TimerFdGetError = error{InvalidHandle} || UnexpectedError;
+pub const TimerFdSetError = TimerFdGetError || error{Canceled};
+
+pub fn timerfd_create(clokid: i32, flags: linux.TFD) TimerFdCreateError!fd_t {
+ const rc = linux.timerfd_create(clokid, flags);
+ return switch (errno(rc)) {
+ .SUCCESS => @intCast(rc),
+ .INVAL => unreachable,
+ .MFILE => return error.ProcessFdQuotaExceeded,
+ .NFILE => return error.SystemFdQuotaExceeded,
+ .NODEV => return error.NoDevice,
+ .NOMEM => return error.SystemResources,
+ .PERM => return error.AccessDenied,
+ else => |err| return unexpectedErrno(err),
+ };
+}
+
+pub fn timerfd_settime(
+ fd: i32,
+ flags: linux.TFD.TIMER,
+ new_value: *const linux.itimerspec,
+ old_value: ?*linux.itimerspec,
+) TimerFdSetError!void {
+ const rc = linux.timerfd_settime(fd, flags, new_value, old_value);
+ return switch (errno(rc)) {
+ .SUCCESS => {},
+ .BADF => error.InvalidHandle,
+ .FAULT => unreachable,
+ .INVAL => unreachable,
+ .CANCELED => error.Canceled,
+ else => |err| return unexpectedErrno(err),
+ };
+}
+
+pub fn timerfd_gettime(fd: i32) TimerFdGetError!linux.itimerspec {
+ var curr_value: linux.itimerspec = undefined;
+ const rc = linux.timerfd_gettime(fd, &curr_value);
+ return switch (errno(rc)) {
+ .SUCCESS => return curr_value,
+ .BADF => error.InvalidHandle,
+ .FAULT => unreachable,
+ .INVAL => unreachable,
+ else => |err| return unexpectedErrno(err),
+ };
+}
+
+pub const PtraceError = error{
+ DeviceBusy,
+ InputOutput,
+ ProcessNotFound,
+ PermissionDenied,
+} || UnexpectedError;
+
+pub fn ptrace(request: u32, pid: pid_t, addr: usize, signal: usize) PtraceError!void {
+ if (native_os == .windows or native_os == .wasi)
+ @compileError("Unsupported OS");
+
+ return switch (native_os) {
+ .linux => switch (errno(linux.ptrace(request, pid, addr, signal, 0))) {
+ .SUCCESS => {},
+ .SRCH => error.ProcessNotFound,
+ .FAULT => unreachable,
+ .INVAL => unreachable,
+ .IO => return error.InputOutput,
+ .PERM => error.PermissionDenied,
+ .BUSY => error.DeviceBusy,
+ else => |err| return unexpectedErrno(err),
+ },
+
+ .macos, .ios, .tvos, .watchos => switch (errno(std.c.ptrace(
+ @intCast(request),
+ pid,
+ @ptrFromInt(addr),
+ @intCast(signal),
+ ))) {
+ .SUCCESS => {},
+ .SRCH => error.ProcessNotFound,
+ .INVAL => unreachable,
+ .PERM => error.PermissionDenied,
+ .BUSY => error.DeviceBusy,
+ else => |err| return unexpectedErrno(err),
+ },
+
+ else => switch (errno(system.ptrace(request, pid, addr, signal))) {
+ .SUCCESS => {},
+ .SRCH => error.ProcessNotFound,
+ .INVAL => unreachable,
+ .PERM => error.PermissionDenied,
+ .BUSY => error.DeviceBusy,
+ else => |err| return unexpectedErrno(err),
+ },
+ };
+}
+
+pub const IoCtl_SIOCGIFINDEX_Error = error{
+ FileSystem,
+ InterfaceNotFound,
+} || UnexpectedError;
+
+pub fn ioctl_SIOCGIFINDEX(fd: fd_t, ifr: *ifreq) IoCtl_SIOCGIFINDEX_Error!void {
+ while (true) {
+ switch (errno(system.ioctl(fd, SIOCGIFINDEX, @intFromPtr(ifr)))) {
+ .SUCCESS => return,
+ .INVAL => unreachable, // Bad parameters.
+ .NOTTY => unreachable,
+ .NXIO => unreachable,
+ .BADF => unreachable, // Always a race condition.
+ .FAULT => unreachable, // Bad pointer parameter.
+ .INTR => continue,
+ .IO => return error.FileSystem,
+ .NODEV => return error.InterfaceNotFound,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+}
+
+const lfs64_abi = native_os == .linux and builtin.link_libc and builtin.abi.isGnu();
+
+/// Whether or not `error.Unexpected` will print its value and a stack trace.
+///
+/// If this happens the fix is to add the error code to the corresponding
+/// switch expression, possibly introduce a new error in the error set, and
+/// send a patch to Zig.
+pub const unexpected_error_tracing = builtin.zig_backend == .stage2_llvm and builtin.mode == .Debug;
+
+pub const UnexpectedError = error{
+ /// The Operating System returned an undocumented error code.
+ ///
+ /// This error is in theory not possible, but it would be better
+ /// to handle this error than to invoke undefined behavior.
+ ///
+ /// When this error code is observed, it usually means the Zig Standard
+ /// Library needs a small patch to add the error code to the error set for
+ /// the respective function.
+ Unexpected,
+};
+
+/// Call this when you made a syscall or something that sets errno
+/// and you get an unexpected error.
+pub fn unexpectedErrno(err: E) UnexpectedError {
+ if (unexpected_error_tracing) {
+ std.debug.print("unexpected errno: {d}\n", .{@intFromEnum(err)});
+ std.debug.dumpCurrentStackTrace(null);
+ }
+ return error.Unexpected;
+}
+
+/// Used to convert a slice to a null terminated slice on the stack.
+pub fn toPosixPath(file_path: []const u8) error{NameTooLong}![PATH_MAX - 1:0]u8 {
+ if (std.debug.runtime_safety) assert(mem.indexOfScalar(u8, file_path, 0) == null);
+ var path_with_null: [PATH_MAX - 1:0]u8 = undefined;
+ // >= rather than > to make room for the null byte
+ if (file_path.len >= PATH_MAX) return error.NameTooLong;
+ @memcpy(path_with_null[0..file_path.len], file_path);
+ path_with_null[file_path.len] = 0;
+ return path_with_null;
+}