//! 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; const page_size_min = std.heap.page_size_min; 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; /// A libc-compatible API layer. pub const system = if (use_libc) std.c else switch (native_os) { .linux => linux, .plan9 => std.os.plan9, else => struct { pub const pid_t = void; pub const pollfd = void; pub const fd_t = void; pub const uid_t = void; pub const gid_t = void; pub const mode_t = u0; pub const ino_t = void; pub const IFNAMESIZE = {}; }, }; pub const AF = system.AF; pub const AF_SUN = system.AF_SUN; pub const AI = system.AI; 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 IP = system.IP; pub const IPV6 = system.IPV6; pub const IPPROTO = system.IPPROTO; pub const IPTOS = system.IPTOS; pub const KERN = system.KERN; pub const Kevent = system.Kevent; pub const MADV = system.MADV; pub const MAP = system.MAP; pub const MAX_ADDR_LEN = system.MAX_ADDR_LEN; pub const MCL = system.MCL; pub const MFD = system.MFD; pub const MLOCK = system.MLOCK; pub const MREMAP = system.MREMAP; pub const MSF = system.MSF; pub const MSG = system.MSG; pub const NAME_MAX = system.NAME_MAX; pub const NSIG = system.NSIG; 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 RLIM = system.RLIM; pub const S = system.S; pub const SA = system.SA; pub const SC = system.SC; pub const SCM = system.SCM; 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 IFF = system.IFF; 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 TCP = system.TCP; pub const VDSO = system.VDSO; pub const W = system.W; pub const _SC = system._SC; pub const addrinfo = system.addrinfo; pub const blkcnt_t = system.blkcnt_t; pub const blksize_t = system.blksize_t; pub const clock_t = system.clock_t; pub const clockid_t = system.clockid_t; pub const timerfd_clockid_t = system.timerfd_clockid_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 fd_t = system.fd_t; pub const file_obj = system.file_obj; pub const gid_t = system.gid_t; pub const ifreq = system.ifreq; pub const in_pktinfo = system.in_pktinfo; pub const in6_pktinfo = system.in6_pktinfo; pub const ino_t = system.ino_t; pub const linger = system.linger; 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_event = system.port_event; pub const port_notify = system.port_notify; pub const port_t = system.port_t; 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 sigrtmin = system.sigrtmin; pub const sigrtmax = system.sigrtmax; 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 uid_t = system.uid_t; pub const user_desc = system.user_desc; pub const utsname = system.utsname; 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 { base: [*]u8, len: usize, }; pub const iovec_const = extern struct { base: [*]const u8, len: usize, }; pub const ACCMODE = switch (native_os) { // POSIX has a note about the access mode values: // // In historical implementations the value of O_RDONLY is zero. Because of // that, it is not possible to detect the presence of O_RDONLY and another // option. Future implementations should encode O_RDONLY and O_WRONLY as // bit flags so that: O_RDONLY | O_WRONLY == O_RDWR // // In practice SerenityOS is the only system supported by Zig that // implements this suggestion. // https://github.com/SerenityOS/serenity/blob/4adc51fdf6af7d50679c48b39362e062f5a3b2cb/Kernel/API/POSIX/fcntl.h#L28-L30 .serenity => enum(u2) { NONE = 0, RDONLY = 1, WRONLY = 2, RDWR = 3, }, else => enum(u2) { RDONLY = 0, WRONLY = 1, RDWR = 2, }, }; pub const TCSA = enum(c_uint) { NOW, DRAIN, FLUSH, _, }; pub const winsize = extern struct { row: u16, col: u16, xpixel: u16, ypixel: u16, }; pub const LOCK = struct { pub const SH = 1; pub const EX = 2; pub const NB = 4; pub const UN = 8; }; 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. /// /// Asserts the file descriptor is open. /// /// 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; } 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, PermissionDenied, 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.PermissionDenied, .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, Canceled, }; /// 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 (!builtin.abi.isAndroid() and 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.PermissionDenied, .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.PermissionDenied, .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.PermissionDenied, .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.Streaming => unreachable, error.BadPathName => return error.Unexpected, error.Canceled => return error.Canceled, 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.bufPrintSentinel(procfs_buf[0..], "/proc/self/fd/{d}", .{pathfd}, 0) 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.PermissionDenied, .ROFS => return error.ReadOnlyFileSystem, else => |err| return unexpectedErrno(err), } } } pub const FChownError = error{ AccessDenied, PermissionDenied, 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 `Dir.OpenOptions{ .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.PermissionDenied, .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.posix.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 (builtin.link_libc and @TypeOf(system.arc4random_buf) != void) { system.arc4random_buf(buffer.ptr, buffer.len); return; } if (native_os == .wasi) switch (wasi.random_get(buffer.ptr, buffer.len)) { .SUCCESS => return, else => |err| return unexpectedErrno(err), }; if (@TypeOf(system.getrandom) != void) { var buf = buffer; const use_c = native_os != .linux or std.c.versionCheck(if (builtin.abi.isAndroid()) .{ .major = 28, .minor = 0, .patch = 0 } else .{ .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, 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), } } return getRandomBytesDevURandom(buffer); } fn getRandomBytesDevURandom(buf: []u8) GetRandomError!void { const fd = try openZ("/dev/urandom", .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0); defer close(fd); const st = fstat(fd) catch |err| switch (err) { error.Streaming => return error.NoDevice, else => |e| return e, }; if (!S.ISCHR(st.mode)) { return error.NoDevice; } var i: usize = 0; while (i < buf.len) { i += read(fd, buf[i..]) 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 { @branchHint(.cold); // 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 and windows.peb().BeingDebugged != 0) { @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. const filledset = linux.sigfillset(); sigprocmask(SIG.BLOCK, &filledset, 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 = sigemptyset(), .flags = 0, }; sigaction(SIG.ABRT, &sigact, null); _ = linux.tkill(linux.gettid(), SIG.ABRT); var sigabrtmask = sigemptyset(); sigaddset(&sigabrtmask, SIG.ABRT); 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) { // Block all signals so a `fork` (from a signal handler) between the gettid() and kill() syscalls // cannot trigger an extra, unexpected, inter-process signal. Signal paranoia inherited from Musl. const filled = linux.sigfillset(); var orig: sigset_t = undefined; sigprocmask(SIG.BLOCK, &filled, &orig); const rc = linux.tkill(linux.gettid(), sig); sigprocmask(SIG.SETMASK, &orig, null); switch (errno(rc)) { .SUCCESS => return, else => |err| return unexpectedErrno(err), } } @compileError("std.posix.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 catch-ignore errors. if (uefi.system_table.boot_services) |bs| { bs.exit(uefi.handle, @enumFromInt(status), null) catch {}; } // If we can't exit, reboot the system instead. uefi.system_table.runtime_services.resetSystem(.cold, @enumFromInt(status), null); } system.exit(status); } pub const ReadError = std.Io.File.ReadStreamingError; /// 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{ .base = buf.ptr, .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.SocketUnconnected, .CONNRESET => return error.ConnectionResetByPeer, .TIMEDOUT => return error.Timeout, .NOTCAPABLE => return error.AccessDenied, else => |err| return unexpectedErrno(err), } } // Prevents EINVAL. const max_count = switch (native_os) { .linux => 0x7ffff000, .macos, .ios, .watchos, .tvos, .visionos => maxInt(i32), else => maxInt(isize), }; while (true) { const rc = system.read(fd, buf.ptr, @min(buf.len, max_count)); switch (errno(rc)) { .SUCCESS => return @intCast(rc), .INTR => continue, .INVAL => unreachable, .FAULT => unreachable, .SRCH => return error.ProcessNotFound, .AGAIN => return error.WouldBlock, .CANCELED => return error.Canceled, .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.SocketUnconnected, .CONNRESET => return error.ConnectionResetByPeer, .TIMEDOUT => return error.Timeout, 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) { if (iov.len == 0) return 0; const first = iov[0]; return read(fd, first.base[0..first.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.SocketUnconnected, .CONNRESET => return error.ConnectionResetByPeer, .TIMEDOUT => return error.Timeout, .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, .SRCH => return error.ProcessNotFound, .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.SocketUnconnected, .CONNRESET => return error.ConnectionResetByPeer, .TIMEDOUT => return error.Timeout, else => |err| return unexpectedErrno(err), } } } pub const PReadError = std.Io.File.ReadPositionalError; /// 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{ .base = buf.ptr, .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.SocketUnconnected, .CONNRESET => return error.ConnectionResetByPeer, .TIMEDOUT => return error.Timeout, .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, .visionos => 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, .SRCH => return error.ProcessNotFound, .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.SocketUnconnected, .CONNRESET => return error.ConnectionResetByPeer, .TIMEDOUT => return error.Timeout, .NXIO => return error.Unseekable, .SPIPE => return error.Unseekable, .OVERFLOW => return error.Unseekable, else => |err| return unexpectedErrno(err), } } } pub const TruncateError = error{ FileTooBig, InputOutput, FileBusy, AccessDenied, PermissionDenied, NonResizable, } || UnexpectedError; /// Length must be positive when treated as an i64. pub fn ftruncate(fd: fd_t, length: u64) TruncateError!void { const signed_len: i64 = @bitCast(length); if (signed_len < 0) return error.FileTooBig; // avoid ambiguous EINVAL errors if (native_os == .windows) { var io_status_block: windows.IO_STATUS_BLOCK = undefined; var eof_info = windows.FILE_END_OF_FILE_INFORMATION{ .EndOfFile = signed_len, }; 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, .USER_MAPPED_FILE => return error.AccessDenied, .INVALID_PARAMETER => return error.FileTooBig, 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.PermissionDenied, .TXTBSY => return error.FileBusy, .BADF => unreachable, // Handle not open for writing .INVAL => return error.NonResizable, .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, signed_len))) { .SUCCESS => return, .INTR => continue, .FBIG => return error.FileTooBig, .IO => return error.InputOutput, .PERM => return error.PermissionDenied, .TXTBSY => return error.FileBusy, .BADF => unreachable, // Handle not open for writing .INVAL => return error.NonResizable, // This is returned for /dev/null for example. 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 { // NOTE: serenity does not have preadv but it *does* have pwritev. const have_pread_but_not_preadv = switch (native_os) { .windows, .macos, .ios, .watchos, .tvos, .visionos, .haiku, .serenity => 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.base[0..first.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.SocketUnconnected, .CONNRESET => return error.ConnectionResetByPeer, .TIMEDOUT => return error.Timeout, .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, .SRCH => return error.ProcessNotFound, .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.SocketUnconnected, .CONNRESET => return error.ConnectionResetByPeer, .TIMEDOUT => return error.Timeout, .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, /// File descriptor does not hold the required rights to write to it. AccessDenied, PermissionDenied, BrokenPipe, SystemResources, Canceled, NotOpenForWriting, /// The process cannot access the file because another process has locked /// a portion of the file. Windows-only. LockViolation, /// This error occurs when no global event loop is configured, /// and reading from the file descriptor would block. WouldBlock, /// Connection reset by peer. ConnectionResetByPeer, /// This error occurs in Linux if the process being written to /// no longer exists. ProcessNotFound, /// This error occurs when a device gets disconnected before or mid-flush /// while it's being written to - errno(6): No such device or address. NoDevice, /// The socket type requires that message be sent atomically, and the size of the message /// to be sent made this impossible. The message is not transmitted. MessageOversize, } || UnexpectedError; /// Write to a file descriptor. /// Retries when interrupted by a signal. /// Returns the number of bytes written. If nonzero bytes were supplied, this will be nonzero. /// /// Note that a successful write() may transfer fewer than count bytes. Such partial writes can /// occur for various reasons; for example, because there was insufficient space on the disk /// device to write all of the requested bytes, or because a blocked write() to a socket, pipe, or /// similar was interrupted by a signal handler after it had transferred some, but before it had /// transferred all of the requested bytes. In the event of a partial write, the caller can make /// another write() call to transfer the remaining bytes. The subsequent call will either /// transfer further bytes or may result in an error (e.g., if the disk is now full). /// /// For POSIX systems, if `fd` is opened in non blocking mode, the function will /// return error.WouldBlock when EAGAIN is received. /// On Windows, if the application has a global event loop enabled, I/O Completion Ports are /// used to perform the I/O. `error.WouldBlock` is not possible on Windows. /// /// Linux has a limit on how many bytes may be transferred in one `write` call, which is `0x7ffff000` /// on both 64-bit and 32-bit systems. This is due to using a signed C int as the return value, as /// well as stuffing the errno codes into the last `4096` values. This is noted on the `write` man page. /// The limit on Darwin is `0x7fffffff`, trying to read more than that returns EINVAL. /// The corresponding POSIX limit is `maxInt(isize)`. pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize { if (bytes.len == 0) return 0; if (native_os == .windows) { return windows.WriteFile(fd, bytes, null); } if (native_os == .wasi and !builtin.link_libc) { const ciovs = [_]iovec_const{iovec_const{ .base = bytes.ptr, .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.PermissionDenied, .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, .visionos => 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, .SRCH => return error.ProcessNotFound, .AGAIN => return error.WouldBlock, .BADF => return error.NotOpenForWriting, // can be a race condition. .DESTADDRREQ => unreachable, // `connect` was never called. .DQUOT => return error.DiskQuota, .FBIG => return error.FileTooBig, .IO => return error.InputOutput, .NOSPC => return error.NoSpaceLeft, .ACCES => return error.AccessDenied, .PERM => return error.PermissionDenied, .PIPE => return error.BrokenPipe, .CONNRESET => return error.ConnectionResetByPeer, .BUSY => return error.DeviceBusy, .NXIO => return error.NoDevice, .MSGSIZE => return error.MessageOversize, else => |err| return unexpectedErrno(err), } } } /// 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.base[0..first.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.PermissionDenied, .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, .SRCH => return error.ProcessNotFound, .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.PermissionDenied, .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{ .base = bytes.ptr, .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.PermissionDenied, .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, .visionos => 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, .SRCH => return error.ProcessNotFound, .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.PermissionDenied, .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, .visionos, .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.base[0..first.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.PermissionDenied, .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, .SRCH => return error.ProcessNotFound, .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.PermissionDenied, .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 = std.Io.File.OpenError || error{WouldBlock}; /// Open and possibly create a file. Keeps trying if it gets interrupted. /// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, `file_path` should be encoded as valid UTF-8. /// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding. /// See also `openZ`. pub fn open(file_path: []const u8, flags: O, perm: mode_t) OpenError!fd_t { if (native_os == .windows) { @compileError("Windows does not support POSIX; use Windows-specific API or cross-platform std.fs API"); } else if (native_os == .wasi and !builtin.link_libc) { return openat(AT.FDCWD, file_path, flags, perm); } const file_path_c = try toPosixPath(file_path); return openZ(&file_path_c, flags, perm); } /// Open and possibly create a file. Keeps trying if it gets interrupted. /// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, `file_path` should be encoded as valid UTF-8. /// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding. /// See also `open`. pub fn openZ(file_path: [*:0]const u8, flags: O, perm: mode_t) OpenError!fd_t { if (native_os == .windows) { @compileError("Windows does not support POSIX; use Windows-specific API or cross-platform std.fs API"); } else if (native_os == .wasi and !builtin.link_libc) { return open(mem.sliceTo(file_path, 0), flags, perm); } const open_sym = if (lfs64_abi) system.open64 else system.open; while (true) { const rc = open_sym(file_path, flags, perm); switch (errno(rc)) { .SUCCESS => return @intCast(rc), .INTR => continue, .FAULT => unreachable, .INVAL => return error.BadPathName, .ACCES => return error.AccessDenied, .FBIG => return error.FileTooBig, .OVERFLOW => return error.FileTooBig, .ISDIR => return error.IsDir, .LOOP => return error.SymLinkLoop, .MFILE => return error.ProcessFdQuotaExceeded, .NAMETOOLONG => return error.NameTooLong, .NFILE => return error.SystemFdQuotaExceeded, .NODEV => return error.NoDevice, .NOENT => return error.FileNotFound, .SRCH => return error.ProcessNotFound, .NOMEM => return error.SystemResources, .NOSPC => return error.NoSpaceLeft, .NOTDIR => return error.NotDir, .PERM => return error.PermissionDenied, .EXIST => return error.PathAlreadyExists, .BUSY => return error.DeviceBusy, .ILSEQ => return error.BadPathName, else => |err| return unexpectedErrno(err), } } } /// Open and possibly create a file. Keeps trying if it gets interrupted. /// `file_path` is relative to the open directory handle `dir_fd`. /// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// 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) { @compileError("use std.Io instead"); } const file_path_c = try toPosixPath(file_path); return openatZ(dir_fd, &file_path_c, flags, mode); } /// Open and possibly create a file. Keeps trying if it gets interrupted. /// `file_path` is relative to the open directory handle `dir_fd`. /// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// 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 => return error.BadPathName, .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, .SRCH => return error.ProcessNotFound, .NOMEM => return error.SystemResources, .NOSPC => return error.NoSpaceLeft, .NOTDIR => return error.NotDir, .PERM => return error.PermissionDenied, .EXIST => return error.PathAlreadyExists, .BUSY => return error.DeviceBusy, .OPNOTSUPP => return error.FileLocksNotSupported, .AGAIN => return error.WouldBlock, .TXTBSY => return error.FileBusy, .NXIO => return error.NoDevice, .ILSEQ => return error.BadPathName, 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, PermissionDenied, 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.PermissionDenied, .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, .visionos => 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.posix.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 (mem.indexOfScalar(u8, key, '=') != null) { return null; } 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) : (line_i += 1) { if (line_i == key.len) break; if (line[line_i] != key[line_i]) break; } if ((line_i != key.len) or (line[line_i] != '=')) continue; return mem.sliceTo(line + line_i + 1, 0); } return null; } if (native_os == .wasi) { @compileError("std.posix.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) : (line_i += 1) { if (line_i == key.len) break; if (ptr[line_i] != key[line_i]) break; } if ((line_i != key.len) or (ptr[line_i] != '=')) 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.posix.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, PermissionDenied, DiskQuota, PathAlreadyExists, FileSystem, SymLinkLoop, FileNotFound, SystemResources, NoSpaceLeft, ReadOnlyFileSystem, NotDir, NameTooLong, /// WASI: file paths must be valid UTF-8. /// Windows: file paths provided by the user must be valid WTF-8. /// https://wtf-8.codeberg.page/ 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://wtf-8.codeberg.page/). /// 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, 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.PermissionDenied, .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 => return error.BadPathName, 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://wtf-8.codeberg.page/). /// 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.PermissionDenied, .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.BadPathName, 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.PermissionDenied, .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 => return error.BadPathName, else => |err| return unexpectedErrno(err), } } pub const LinkError = UnexpectedError || error{ AccessDenied, PermissionDenied, DiskQuota, PathAlreadyExists, FileSystem, SymLinkLoop, LinkQuotaExceeded, NameTooLong, FileNotFound, SystemResources, NoSpaceLeft, ReadOnlyFileSystem, NotSameFileSystem, BadPathName, }; /// 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) LinkError!void { if (native_os == .wasi and !builtin.link_libc) { return link(mem.sliceTo(oldpath, 0), mem.sliceTo(newpath, 0)); } switch (errno(system.link(oldpath, newpath))) { .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.PermissionDenied, .ROFS => return error.ReadOnlyFileSystem, .XDEV => return error.NotSameFileSystem, .INVAL => unreachable, .ILSEQ => return error.BadPathName, 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) LinkError!void { if (native_os == .wasi and !builtin.link_libc) { return linkat(AT.FDCWD, oldpath, AT.FDCWD, newpath, 0) 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); } 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.PermissionDenied, .ROFS => return error.ReadOnlyFileSystem, .XDEV => return error.NotSameFileSystem, .INVAL => unreachable, .ILSEQ => return error.BadPathName, 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.PermissionDenied, .ROFS => return error.ReadOnlyFileSystem, .XDEV => return error.NotSameFileSystem, .INVAL => unreachable, .ILSEQ => return error.BadPathName, 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, PermissionDenied, FileBusy, FileSystem, IsDir, SymLinkLoop, NameTooLong, NotDir, SystemResources, ReadOnlyFileSystem, /// WASI: file paths must be valid UTF-8. /// Windows: file paths provided by the user must be valid WTF-8. /// https://wtf-8.codeberg.page/ /// 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://wtf-8.codeberg.page/). /// On WASI, `file_path` should be encoded as valid UTF-8. /// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding. /// See also `unlinkZ`. pub fn unlink(file_path: []const u8) UnlinkError!void { if (native_os == .wasi and !builtin.link_libc) { return unlinkat(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.PermissionDenied, .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 => return error.BadPathName, 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://wtf-8.codeberg.page/). /// On WASI, `file_path` should be encoded as valid UTF-8. /// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding. /// 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.PermissionDenied, .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.BadPathName, .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.PermissionDenied, .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 => return error.BadPathName, .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, PermissionDenied, FileBusy, DiskQuota, IsDir, SymLinkLoop, LinkQuotaExceeded, NameTooLong, FileNotFound, NotDir, SystemResources, NoSpaceLeft, PathAlreadyExists, ReadOnlyFileSystem, RenameAcrossMountPoints, /// WASI: file paths must be valid UTF-8. /// Windows: file paths provided by the user must be valid WTF-8. /// https://wtf-8.codeberg.page/ 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://wtf-8.codeberg.page/). /// 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(AT.FDCWD, old_path, 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.PermissionDenied, .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 => return error.BadPathName, 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://wtf-8.codeberg.page/). /// 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.PermissionDenied, .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.BadPathName, 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.PermissionDenied, .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 => return error.BadPathName, else => |err| return unexpectedErrno(err), } } /// Same as `renameat` but Windows-only and the path parameters are /// [WTF-16](https://wtf-8.codeberg.page/#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 rc: windows.NTSTATUS = undefined; // FileRenameInformationEx has varying levels of support: // - FILE_RENAME_INFORMATION_EX requires >= win10_rs1 // (INVALID_INFO_CLASS is returned if not supported) // - Requires the NTFS filesystem // (on filesystems like FAT32, INVALID_PARAMETER is returned) // - FILE_RENAME_POSIX_SEMANTICS requires >= win10_rs1 // - FILE_RENAME_IGNORE_READONLY_ATTRIBUTE requires >= win10_rs5 // (NOT_SUPPORTED is returned if a flag is unsupported) // // The strategy here is just to try using FileRenameInformationEx and fall back to // FileRenameInformation if the return value lets us know that some aspect of it is not supported. const need_fallback = need_fallback: { 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, // The filesystem does not support FileDispositionInformationEx .INVALID_PARAMETER, // The operating system does not support FileDispositionInformationEx .INVALID_INFO_CLASS, // The operating system does not support one of the flags .NOT_SUPPORTED, => break :need_fallback true, // For all other statuses, fall down to the switch below to handle them. else => break :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, .DIRECTORY_NOT_EMPTY => return error.PathAlreadyExists, .FILE_IS_A_DIRECTORY => return error.IsDir, .NOT_A_DIRECTORY => return error.NotDir, else => return windows.unexpectedStatus(rc), } } /// On Windows, `sub_dir_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// 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: mode_t) MakeDirError!void { if (native_os == .windows) { @compileError("use std.Io instead"); } else if (native_os == .wasi and !builtin.link_libc) { @compileError("use std.Io instead"); } else { const sub_dir_path_c = try toPosixPath(sub_dir_path); return mkdiratZ(dir_fd, &sub_dir_path_c, mode); } } /// Same as `mkdirat` except the parameters are null-terminated. pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: mode_t) MakeDirError!void { if (native_os == .windows) { @compileError("use std.Io instead"); } else if (native_os == .wasi and !builtin.link_libc) { @compileError("use std.Io instead"); } switch (errno(system.mkdirat(dir_fd, sub_dir_path, mode))) { .SUCCESS => return, .ACCES => return error.AccessDenied, .BADF => unreachable, .PERM => return error.PermissionDenied, .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 => return error.BadPathName, else => |err| return unexpectedErrno(err), } } pub const MakeDirError = std.Io.Dir.MakeError; /// Create a directory. /// `mode` is ignored on Windows and WASI. /// On Windows, `dir_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// 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: mode_t) MakeDirError!void { if (native_os == .wasi and !builtin.link_libc) { return mkdirat(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://wtf-8.codeberg.page/). /// 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: mode_t) 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.PermissionDenied, .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 => return error.BadPathName, 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: mode_t) 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.NoDevice => 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, PermissionDenied, FileBusy, SymLinkLoop, NameTooLong, FileNotFound, SystemResources, NotDir, DirNotEmpty, ReadOnlyFileSystem, /// WASI: file paths must be valid UTF-8. /// Windows: file paths provided by the user must be valid WTF-8. /// https://wtf-8.codeberg.page/ 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://wtf-8.codeberg.page/). /// 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(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://wtf-8.codeberg.page/). /// 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.PermissionDenied, .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 => return error.BadPathName, 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, /// WASI: file paths must be valid UTF-8. /// Windows: file paths provided by the user must be valid WTF-8. /// https://wtf-8.codeberg.page/ BadPathName, } || UnexpectedError; /// Changes the current working directory of the calling process. /// On Windows, `dir_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// 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 windows.wtf8ToWtf16Le(&wtf16_dir_path, dir_path); 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://wtf-8.codeberg.page/). /// 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) { const dir_path_span = mem.span(dir_path); var wtf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined; const len = try windows.wtf8ToWtf16Le(&wtf16_dir_path, dir_path_span); 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 => return error.BadPathName, 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, PermissionDenied, FileSystem, SymLinkLoop, NameTooLong, FileNotFound, SystemResources, NotLink, NotDir, /// WASI: file paths must be valid UTF-8. /// Windows: file paths provided by the user must be valid WTF-8. /// https://wtf-8.codeberg.page/ 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://wtf-8.codeberg.page/). /// On WASI, `file_path` should be encoded as valid UTF-8. /// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding. /// The return value is a slice of `out_buffer` from index 0. /// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). /// 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(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://wtf-8.codeberg.page/). /// 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 => return error.BadPathName, 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://wtf-8.codeberg.page/). /// On WASI, `file_path` should be encoded as valid UTF-8. /// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding. /// The return value is a slice of `out_buffer` from index 0. /// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). /// 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.BadPathName, 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://wtf-8.codeberg.page/). /// 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 => return error.BadPathName, 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), } } pub const SetPgidError = error{ ProcessAlreadyExec, InvalidProcessGroupId, PermissionDenied, ProcessNotFound, } || UnexpectedError; pub fn setpgid(pid: pid_t, pgid: pid_t) SetPgidError!void { switch (errno(system.setpgid(pid, pgid))) { .SUCCESS => return, .ACCES => return error.ProcessAlreadyExec, .INVAL => return error.InvalidProcessGroupId, .PERM => return error.PermissionDenied, .SRCH => return error.ProcessNotFound, else => |err| return unexpectedErrno(err), } } pub fn getuid() uid_t { return system.getuid(); } pub fn geteuid() uid_t { return system.geteuid(); } pub fn getgid() gid_t { return system.getgid(); } pub fn getegid() gid_t { return system.getegid(); } /// 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: 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. AccessDenied, /// The implementation does not support the specified address family. AddressFamilyUnsupported, /// 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 { const have_sock_flags = !builtin.target.os.tag.isDarwin() and native_os != .haiku; const filtered_sock_type = if (!have_sock_flags) socket_type & ~@as(u32, SOCK.NONBLOCK | SOCK.CLOEXEC) else socket_type; const rc = system.socket(domain, filtered_sock_type, protocol); switch (errno(rc)) { .SUCCESS => { const fd: fd_t = @intCast(rc); errdefer close(fd); if (!have_sock_flags) { try setSockFlags(fd, socket_type); } return fd; }, .ACCES => return error.AccessDenied, .AFNOSUPPORT => return error.AddressFamilyUnsupported, .INVAL => return error.ProtocolFamilyNotAvailable, .MFILE => return error.ProcessFdQuotaExceeded, .NFILE => return error.SystemFdQuotaExceeded, .NOBUFS => return error.SystemResources, .NOMEM => return error.SystemResources, .PROTONOSUPPORT => return error.ProtocolNotSupported, .PROTOTYPE => return error.SocketTypeNotSupported, else => |err| return unexpectedErrno(err), } } pub fn socketpair(domain: u32, socket_type: u32, protocol: u32) SocketError![2]socket_t { // Note to the future: we could provide a shim here for e.g. windows which // creates a listening socket, then creates a second socket and connects it // to the listening socket, and then returns the two. if (@TypeOf(system.socketpair) == void) @compileError("socketpair() not supported by this OS"); // I'm not really sure if haiku supports flags here. I'm following the // existing filter here from pipe2(), because it sure seems like it // supports flags there too, but haiku can be hard to understand. const have_sock_flags = !builtin.target.os.tag.isDarwin() and native_os != .haiku; const filtered_sock_type = if (!have_sock_flags) socket_type & ~@as(u32, SOCK.NONBLOCK | SOCK.CLOEXEC) else socket_type; var socks: [2]socket_t = undefined; const rc = system.socketpair(domain, filtered_sock_type, protocol, &socks); switch (errno(rc)) { .SUCCESS => { errdefer close(socks[0]); errdefer close(socks[1]); if (!have_sock_flags) { try setSockFlags(socks[0], socket_type); try setSockFlags(socks[1], socket_type); } return socks; }, .ACCES => return error.AccessDenied, .AFNOSUPPORT => return error.AddressFamilyUnsupported, .INVAL => return error.ProtocolFamilyNotAvailable, .MFILE => return error.ProcessFdQuotaExceeded, .NFILE => return error.SystemFdQuotaExceeded, .NOBUFS => return error.SystemResources, .NOMEM => return error.SystemResources, .PROTONOSUPPORT => return error.ProtocolNotSupported, .PROTOTYPE => return error.SocketTypeNotSupported, else => |err| return unexpectedErrno(err), } } pub const ShutdownError = error{ ConnectionAborted, /// Connection was reset by peer, application should close socket as it is no longer usable. ConnectionResetByPeer, BlockingOperationInProgress, /// The network subsystem has failed. NetworkDown, /// The socket is not connected (connection-oriented sockets only). SocketUnconnected, SystemResources, } || UnexpectedError; pub const ShutdownHow = enum { recv, send, both }; /// Shutdown socket send/receive operations pub fn shutdown(sock: socket_t, how: ShutdownHow) ShutdownError!void { if (native_os == .windows) { const result = windows.ws2_32.shutdown(sock, switch (how) { .recv => windows.ws2_32.SD_RECEIVE, .send => windows.ws2_32.SD_SEND, .both => windows.ws2_32.SD_BOTH, }); if (0 != result) switch (windows.ws2_32.WSAGetLastError()) { .ECONNABORTED => return error.ConnectionAborted, .ECONNRESET => return error.ConnectionResetByPeer, .EINPROGRESS => return error.BlockingOperationInProgress, .EINVAL => unreachable, .ENETDOWN => return error.NetworkDown, .ENOTCONN => return error.SocketUnconnected, .ENOTSOCK => unreachable, .NOTINITIALISED => unreachable, else => |err| return windows.unexpectedWSAError(err), }; } else { const rc = system.shutdown(sock, switch (how) { .recv => SHUT.RD, .send => SHUT.WR, .both => SHUT.RDWR, }); switch (errno(rc)) { .SUCCESS => return, .BADF => unreachable, .INVAL => unreachable, .NOTCONN => return error.SocketUnconnected, .NOTSOCK => unreachable, .NOBUFS => return error.SystemResources, else => |err| return unexpectedErrno(err), } } } pub const BindError = error{ SymLinkLoop, NameTooLong, FileNotFound, NotDir, ReadOnlyFileSystem, AccessDenied, } || std.Io.net.IpAddress.BindError; pub fn bind(sock: socket_t, addr: *const sockaddr, len: socklen_t) BindError!void { if (native_os == .windows) { @compileError("use std.Io instead"); } else { const rc = system.bind(sock, addr, len); switch (errno(rc)) { .SUCCESS => return, .ACCES, .PERM => return error.AccessDenied, .ADDRINUSE => return error.AddressInUse, .BADF => unreachable, // always a race condition if this error is returned .INVAL => unreachable, // invalid parameters .NOTSOCK => unreachable, // invalid `sockfd` .AFNOSUPPORT => return error.AddressFamilyUnsupported, .ADDRNOTAVAIL => return error.AddressUnavailable, .FAULT => unreachable, // invalid `addr` pointer .LOOP => return error.SymLinkLoop, .NAMETOOLONG => return error.NameTooLong, .NOENT => return error.FileNotFound, .NOMEM => return error.SystemResources, .NOTDIR => return error.NotDir, .ROFS => return error.ReadOnlyFileSystem, else => |err| return unexpectedErrno(err), } } unreachable; } pub const ListenError = error{ FileDescriptorNotASocket, OperationNotSupported, } || std.Io.net.IpAddress.ListenError || std.Io.net.UnixAddress.ListenError; pub fn listen(sock: socket_t, backlog: u31) ListenError!void { if (native_os == .windows) { @compileError("use std.Io instead"); } else { const rc = system.listen(sock, backlog); switch (errno(rc)) { .SUCCESS => return, .ADDRINUSE => return error.AddressInUse, .BADF => unreachable, .NOTSOCK => return error.FileDescriptorNotASocket, .OPNOTSUPP => return error.OperationNotSupported, else => |err| return unexpectedErrno(err), } } } pub const AcceptError = std.Io.net.Server.AcceptError; pub fn accept( sock: socket_t, addr: ?*sockaddr, addr_size: ?*socklen_t, flags: u32, ) AcceptError!socket_t { const have_accept4 = !(builtin.target.os.tag.isDarwin() or native_os == .windows or native_os == .haiku); assert(0 == (flags & ~@as(u32, SOCK.NONBLOCK | SOCK.CLOEXEC))); // Unsupported flag(s) const accepted_sock: socket_t = while (true) { const rc = if (have_accept4) system.accept4(sock, addr, addr_size, flags) else system.accept(sock, addr, addr_size); if (native_os == .windows) { @compileError("use std.Io instead"); } else { switch (errno(rc)) { .SUCCESS => break @intCast(rc), .INTR => continue, .AGAIN => return error.WouldBlock, .BADF => unreachable, // always a race condition .CONNABORTED => return error.ConnectionAborted, .FAULT => unreachable, .INVAL => return error.SocketNotListening, .NOTSOCK => unreachable, .MFILE => return error.ProcessFdQuotaExceeded, .NFILE => return error.SystemFdQuotaExceeded, .NOBUFS => return error.SystemResources, .NOMEM => return error.SystemResources, .OPNOTSUPP => unreachable, .PROTO => return error.ProtocolFailure, .PERM => return error.BlockedByFirewall, else => |err| return unexpectedErrno(err), } } }; errdefer switch (native_os) { .windows => windows.closesocket(accepted_sock) catch unreachable, else => close(accepted_sock), }; if (!have_accept4) { try setSockFlags(accepted_sock, flags); } return accepted_sock; } fn setSockFlags(sock: socket_t, flags: u32) !void { if ((flags & SOCK.CLOEXEC) != 0) { if (native_os == .windows) { // 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()) { .NOTINITIALISED => unreachable, .ENETDOWN => return error.NetworkDown, .ENOTSOCK => 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: ?*system.epoll_event) EpollCtlError!void { const rc = system.epoll_ctl(epfd, op, fd, event); switch (errno(rc)) { .SUCCESS => return, else => |err| return unexpectedErrno(err), .BADF => unreachable, // always a race condition if this happens .EXIST => return error.FileDescriptorAlreadyPresentInSet, .INVAL => unreachable, .LOOP => return error.OperationCausesCircularLoop, .NOENT => return error.FileDescriptorNotRegistered, .NOMEM => return error.SystemResources, .NOSPC => return error.UserResourceLimitReached, .PERM => return error.FileDescriptorIncompatibleWithEpoll, } } /// Waits for an I/O event on an epoll file descriptor. /// Returns the number of file descriptors ready for the requested I/O, /// or zero if no file descriptor became ready during the requested timeout milliseconds. pub fn epoll_wait(epfd: i32, events: []system.epoll_event, timeout: i32) usize { while (true) { // TODO get rid of the @intCast const rc = system.epoll_wait(epfd, events.ptr, @intCast(events.len), timeout); switch (errno(rc)) { .SUCCESS => return @intCast(rc), .INTR => continue, .BADF => unreachable, .FAULT => unreachable, .INVAL => unreachable, else => unreachable, } } } pub const EventFdError = error{ SystemResources, ProcessFdQuotaExceeded, 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. NetworkDown, /// 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()) { .NOTINITIALISED => unreachable, .ENETDOWN => return error.NetworkDown, .EFAULT => unreachable, // addr or addrlen have invalid pointers or addrlen points to an incorrect value .ENOTSOCK => return error.FileDescriptorNotASocket, .EINVAL => return error.SocketNotBound, else => |err| return windows.unexpectedWSAError(err), } } return; } else { const rc = system.getsockname(sock, addr, addrlen); switch (errno(rc)) { .SUCCESS => return, else => |err| return unexpectedErrno(err), .BADF => unreachable, // always a race condition .FAULT => unreachable, .INVAL => unreachable, // invalid parameters .NOTSOCK => return error.FileDescriptorNotASocket, .NOBUFS => return error.SystemResources, } } } pub fn getpeername(sock: socket_t, addr: *sockaddr, addrlen: *socklen_t) GetSockNameError!void { if (native_os == .windows) { const rc = windows.getpeername(sock, addr, addrlen); if (rc == windows.ws2_32.SOCKET_ERROR) { switch (windows.ws2_32.WSAGetLastError()) { .NOTINITIALISED => unreachable, .ENETDOWN => return error.NetworkDown, .EFAULT => unreachable, // addr or addrlen have invalid pointers or addrlen points to an incorrect value .ENOTSOCK => return error.FileDescriptorNotASocket, .EINVAL => return error.SocketNotBound, else => |err| return windows.unexpectedWSAError(err), } } return; } else { const rc = system.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 = std.Io.net.IpAddress.ConnectError || std.Io.net.UnixAddress.ConnectError; pub fn connect(sock: socket_t, sock_addr: *const sockaddr, len: socklen_t) ConnectError!void { if (native_os == .windows) { @compileError("use std.Io instead"); } while (true) { switch (errno(system.connect(sock, sock_addr, len))) { .SUCCESS => return, .ACCES => return error.AccessDenied, .PERM => return error.PermissionDenied, .ADDRNOTAVAIL => return error.AddressUnavailable, .AFNOSUPPORT => return error.AddressFamilyUnsupported, .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 => @panic("AlreadyConnected"), // The socket is already connected. .HOSTUNREACH => return error.NetworkUnreachable, .NETUNREACH => return error.NetworkUnreachable, .NOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket. .PROTOTYPE => unreachable, // The socket type does not support the requested communications protocol. .TIMEDOUT => return error.Timeout, .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 const GetSockOptError = error{ /// The calling process does not have the appropriate privileges. AccessDenied, /// The option is not supported by the protocol. InvalidProtocolOption, /// Insufficient resources are available in the system to complete the call. SystemResources, } || UnexpectedError; pub fn getsockopt(fd: socket_t, level: i32, optname: u32, opt: []u8) GetSockOptError!void { var len: socklen_t = @intCast(opt.len); switch (errno(system.getsockopt(fd, level, optname, opt.ptr, &len))) { .SUCCESS => { std.debug.assert(len == opt.len); }, .BADF => unreachable, .NOTSOCK => unreachable, .INVAL => unreachable, .FAULT => unreachable, .NOPROTOOPT => return error.InvalidProtocolOption, .NOMEM => return error.SystemResources, .NOBUFS => return error.SystemResources, .ACCES => return error.AccessDenied, else => |err| return unexpectedErrno(err), } } pub fn getsockoptError(sockfd: fd_t) ConnectError!void { var err_code: i32 = undefined; var size: u32 = @sizeOf(u32); const rc = system.getsockopt(sockfd, SOL.SOCKET, SO.ERROR, @ptrCast(&err_code), &size); assert(size == 4); switch (errno(rc)) { .SUCCESS => switch (@as(E, @enumFromInt(err_code))) { .SUCCESS => return, .ACCES => return error.AccessDenied, .PERM => return error.PermissionDenied, .ADDRINUSE => return error.AddressInUse, .ADDRNOTAVAIL => return error.AddressUnavailable, .AFNOSUPPORT => return error.AddressFamilyUnsupported, .AGAIN => return error.SystemResources, .ALREADY => return error.ConnectionPending, .BADF => unreachable, // sockfd is not a valid open file descriptor. .CONNREFUSED => return error.ConnectionRefused, .FAULT => unreachable, // The socket structure address is outside the user's address space. .ISCONN => return error.AlreadyConnected, // The socket is already connected. .HOSTUNREACH => return error.NetworkUnreachable, .NETUNREACH => return error.NetworkUnreachable, .NOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket. .PROTOTYPE => unreachable, // The socket type does not support the requested communications protocol. .TIMEDOUT => return error.Timeout, .CONNRESET => return error.ConnectionResetByPeer, else => |err| return unexpectedErrno(err), }, .BADF => unreachable, // The argument sockfd is not a valid file descriptor. .FAULT => unreachable, // The address pointed to by optval or optlen is not in a valid part of the process address space. .INVAL => unreachable, .NOPROTOOPT => unreachable, // The option is unknown at the level indicated. .NOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket. else => |err| return unexpectedErrno(err), } } pub const 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 = std.Io.File.StatError; /// 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 std.os.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), } } pub const FStatAtError = FStatError || error{ NameTooLong, FileNotFound, SymLinkLoop, BadPathName, }; /// Similar to `fstat`, but returns stat of a resource pointed to by `pathname` /// which is relative to `dirfd` handle. /// On WASI, `pathname` should be encoded as valid UTF-8. /// On other platforms, `pathname` is an opaque sequence of bytes with no particular encoding. /// See also `fstatatZ`. pub fn fstatat(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!Stat { if (native_os == .wasi and !builtin.link_libc) { @compileError("use std.Io instead"); } else if (native_os == .windows) { @compileError("fstatat is not yet implemented on Windows"); } else { const pathname_c = try toPosixPath(pathname); return fstatatZ(dirfd, &pathname_c, flags); } } /// Same as `fstatat` but `pathname` is null-terminated. /// See also `fstatat`. pub fn fstatatZ(dirfd: fd_t, pathname: [*:0]const u8, flags: u32) FStatAtError!Stat { if (native_os == .wasi and !builtin.link_libc) { @compileError("use std.Io instead"); } 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.PermissionDenied, .FAULT => unreachable, .NAMETOOLONG => return error.NameTooLong, .LOOP => return error.SymLinkLoop, .NOENT => return error.FileNotFound, .NOTDIR => return error.FileNotFound, .ILSEQ => return error.BadPathName, else => |err| return unexpectedErrno(err), } } pub const KQueueError = error{ /// The per-process limit on the number of open file descriptors has been reached. ProcessFdQuotaExceeded, /// 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, PermissionDenied, /// The kernel does not recognize the flags passed, likely because it is an /// older version. UnsupportedFlags, } || UnexpectedError; pub fn fanotify_init(flags: std.os.linux.fanotify.InitFlags, event_f_flags: u32) FanotifyInitError!i32 { const rc = system.fanotify_init(flags, event_f_flags); switch (errno(rc)) { .SUCCESS => return @intCast(rc), .INVAL => return error.UnsupportedFlags, .MFILE => return error.ProcessFdQuotaExceeded, .NFILE => return error.SystemFdQuotaExceeded, .NOMEM => return error.SystemResources, .PERM => return error.PermissionDenied, else => |err| return unexpectedErrno(err), } } pub const FanotifyMarkError = error{ MarkAlreadyExists, IsDir, NotAssociatedWithFileSystem, FileNotFound, SystemResources, UserMarkQuotaExceeded, NotDir, OperationNotSupported, PermissionDenied, NotSameFileSystem, NameTooLong, } || UnexpectedError; pub fn fanotify_mark( fanotify_fd: fd_t, flags: std.os.linux.fanotify.MarkFlags, mask: std.os.linux.fanotify.MarkMask, dirfd: fd_t, pathname: ?[]const u8, ) FanotifyMarkError!void { if (pathname) |path| { const path_c = try toPosixPath(path); return fanotify_markZ(fanotify_fd, flags, mask, dirfd, &path_c); } else { return fanotify_markZ(fanotify_fd, flags, mask, dirfd, null); } } pub fn fanotify_markZ( fanotify_fd: fd_t, flags: std.os.linux.fanotify.MarkFlags, mask: std.os.linux.fanotify.MarkMask, dirfd: fd_t, 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, .NOTDIR => return error.NotDir, .OPNOTSUPP => return error.OperationNotSupported, .PERM => return error.PermissionDenied, .XDEV => return error.NotSameFileSystem, else => |err| return unexpectedErrno(err), } } pub const MlockError = error{ PermissionDenied, LockedMemoryLimitExceeded, SystemResources, } || UnexpectedError; pub fn mlock(memory: []align(page_size_min) const u8) MlockError!void { if (@TypeOf(system.mlock) == void) @compileError("mlock not supported on this OS"); return switch (errno(system.mlock(memory.ptr, memory.len))) { .SUCCESS => {}, .INVAL => unreachable, // unaligned, negative, runs off end of addrspace .PERM => error.PermissionDenied, .NOMEM => error.LockedMemoryLimitExceeded, .AGAIN => error.SystemResources, else => |err| unexpectedErrno(err), }; } pub fn mlock2(memory: []align(page_size_min) const u8, flags: MLOCK) MlockError!void { if (@TypeOf(system.mlock2) == void) @compileError("mlock2 not supported on this OS"); return switch (errno(system.mlock2(memory.ptr, memory.len, flags))) { .SUCCESS => {}, .INVAL => unreachable, // bad memory or bad flags .PERM => error.PermissionDenied, .NOMEM => error.LockedMemoryLimitExceeded, .AGAIN => error.SystemResources, else => |err| unexpectedErrno(err), }; } pub fn munlock(memory: []align(page_size_min) const u8) MlockError!void { if (@TypeOf(system.munlock) == void) @compileError("munlock not supported on this OS"); return switch (errno(system.munlock(memory.ptr, memory.len))) { .SUCCESS => {}, .INVAL => unreachable, // unaligned or runs off end of addr space .PERM => return error.PermissionDenied, .NOMEM => return error.LockedMemoryLimitExceeded, .AGAIN => return error.SystemResources, else => |err| unexpectedErrno(err), }; } pub fn mlockall(flags: MCL) MlockError!void { if (@TypeOf(system.mlockall) == void) @compileError("mlockall not supported on this OS"); return switch (errno(system.mlockall(flags))) { .SUCCESS => {}, .INVAL => unreachable, // bad flags .PERM => error.PermissionDenied, .NOMEM => error.LockedMemoryLimitExceeded, .AGAIN => error.SystemResources, else => |err| unexpectedErrno(err), }; } pub fn munlockall() MlockError!void { if (@TypeOf(system.munlockall) == void) @compileError("munlockall not supported on this OS"); return switch (errno(system.munlockall())) { .SUCCESS => {}, .PERM => error.PermissionDenied, .NOMEM => error.LockedMemoryLimitExceeded, .AGAIN => error.SystemResources, else => |err| 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; pub fn mprotect(memory: []align(page_size_min) u8, protection: u32) MProtectError!void { 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, /// Using FIXED_NOREPLACE flag and the process has already mapped memory at the given address MappingAlreadyExists, } || 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(page_size_min) u8, length: usize, prot: u32, flags: system.MAP, fd: fd_t, offset: u64, ) MMapError![]align(page_size_min) 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(page_size_min) u8, @ptrCast(@alignCast(rc)))[0..length]; break :blk @enumFromInt(system._errno().*); } else blk: { const err = errno(rc); if (err == .SUCCESS) return @as([*]align(page_size_min) 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, .EXIST => return error.MappingAlreadyExists, 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(page_size_min) 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 => |e| if (unexpected_error_tracing) { std.debug.panic("unexpected errno: {d} ({t})", .{ @intFromEnum(e), e }); } else unreachable, } } pub const MRemapError = error{ LockedMemoryLimitExceeded, /// Either a bug in the calling code, or the operating system abused the /// EINVAL error code. InvalidSyscallParameters, OutOfMemory, } || UnexpectedError; pub fn mremap( old_address: ?[*]align(page_size_min) u8, old_len: usize, new_len: usize, flags: system.MREMAP, new_address: ?[*]align(page_size_min) u8, ) MRemapError![]align(page_size_min) u8 { const rc = system.mremap(old_address, old_len, new_len, flags, new_address); const err: E = if (builtin.link_libc) blk: { if (rc != std.c.MAP_FAILED) return @as([*]align(page_size_min) u8, @ptrCast(@alignCast(rc)))[0..new_len]; break :blk @enumFromInt(system._errno().*); } else blk: { const err = errno(rc); if (err == .SUCCESS) return @as([*]align(page_size_min) u8, @ptrFromInt(rc))[0..new_len]; break :blk err; }; switch (err) { .SUCCESS => unreachable, .AGAIN => return error.LockedMemoryLimitExceeded, .INVAL => return error.InvalidSyscallParameters, .NOMEM => return error.OutOfMemory, .FAULT => unreachable, else => return unexpectedErrno(err), } } pub const MSyncError = error{ UnmappedMemory, PermissionDenied, } || UnexpectedError; pub fn msync(memory: []align(page_size_min) u8, flags: i32) MSyncError!void { switch (errno(system.msync(memory.ptr, memory.len, flags))) { .SUCCESS => return, .PERM => return error.PermissionDenied, .NOMEM => return error.UnmappedMemory, // Unsuccessful, provided pointer does not point mapped memory .INVAL => unreachable, // Invalid parameters. else => unreachable, } } pub const AccessError = error{ AccessDenied, PermissionDenied, FileNotFound, NameTooLong, InputOutput, SystemResources, FileBusy, SymLinkLoop, ReadOnlyFileSystem, /// WASI: file paths must be valid UTF-8. /// Windows: file paths provided by the user must be valid WTF-8. /// https://wtf-8.codeberg.page/ BadPathName, Canceled, } || UnexpectedError; /// check user's permissions for a file /// /// * On Windows, asserts `path` is valid [WTF-8](https://wtf-8.codeberg.page/). /// * On WASI, invalid UTF-8 passed to `path` causes `error.BadPathName`. /// * 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) { @compileError("use std.Io instead"); } else if (native_os == .wasi and !builtin.link_libc) { @compileError("wasi doesn't support absolute paths"); } 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) { @compileError("use std.Io instead"); } 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.AccessDenied, .PERM => 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 => return error.BadPathName, else => |err| return unexpectedErrno(err), } } pub const PipeError = error{ SystemFdQuotaExceeded, ProcessFdQuotaExceeded, } || UnexpectedError; /// Creates a unidirectional data channel that can be used for interprocess communication. pub fn pipe() PipeError![2]fd_t { var fds: [2]fd_t = undefined; switch (errno(system.pipe(&fds))) { .SUCCESS => return fds, .INVAL => unreachable, // Invalid parameters to pipe() .FAULT => unreachable, // Invalid fds pointer .NFILE => return error.SystemFdQuotaExceeded, .MFILE => return error.ProcessFdQuotaExceeded, else => |err| return unexpectedErrno(err), } } pub fn pipe2(flags: O) PipeError![2]fd_t { if (@TypeOf(system.pipe2) != void) { 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) { @compileError("sysctl not supported on WASI"); } if (native_os == .haiku) { @compileError("sysctl not supported on Haiku"); } 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 const SysCtlByNameError = error{ PermissionDenied, SystemResources, UnknownName, } || UnexpectedError; pub fn sysctlbynameZ( name: [*:0]const u8, oldp: ?*anyopaque, oldlenp: ?*usize, newp: ?*anyopaque, newlen: usize, ) SysCtlByNameError!void { if (native_os == .wasi) { @compileError("sysctl not supported on WASI"); } if (native_os == .haiku) { @compileError("sysctl not supported on Haiku"); } 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 = std.Io.File.SeekError; 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, PermissionDenied, NameTooLong, NotSupported, NotDir, SymLinkLoop, InputOutput, FileTooBig, IsDir, ProcessFdQuotaExceeded, SystemFdQuotaExceeded, NoDevice, SystemResources, NoSpaceLeft, FileSystem, DeviceBusy, ProcessNotFound, SharingViolation, PipeBusy, /// Windows: file paths provided by the user must be valid WTF-8. /// https://wtf-8.codeberg.page/ BadPathName, /// 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, Canceled, } || 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://wtf-8.codeberg.page/). /// /// 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://wtf-8.codeberg.page/). /// * 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) { var pathname_w = try windows.sliceToPrefixedFileW(null, pathname); const wide_slice = try realpathW2(pathname_w.span(), &pathname_w.data); const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice); return out_buffer[0..end_index]; } 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) { var pathname_w = try windows.cStrToPrefixedFileW(null, pathname); const wide_slice = try realpathW2(pathname_w.span(), &pathname_w.data); const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice); return out_buffer[0..end_index]; } 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 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); } /// Deprecated: use `realpathW2`. /// /// Same as `realpath` except `pathname` is WTF16LE-encoded. /// /// The result is encoded as [WTF-8](https://wtf-8.codeberg.page/). /// /// Calling this function is usually a bug. pub fn realpathW(pathname: []const u16, out_buffer: *[max_path_bytes]u8) RealPathError![]u8 { return fs.cwd().realpathW(pathname, out_buffer); } /// Same as `realpath` except `pathname` is WTF16LE-encoded. /// /// The result is encoded as WTF16LE. /// /// Calling this function is usually a bug. pub fn realpathW2(pathname: []const u16, out_buffer: *[std.os.windows.PATH_MAX_WIDE]u16) RealPathError![]u16 { return fs.cwd().realpathW2(pathname, out_buffer); } /// Spurious wakeups are possible and no precision of timing is guaranteed. pub fn nanosleep(seconds: u64, nanoseconds: u64) void { var req = timespec{ .sec = cast(isize, seconds) orelse maxInt(isize), .nsec = cast(isize, nanoseconds) orelse maxInt(isize), }; var rem: timespec = undefined; while (true) { switch (errno(system.nanosleep(&req, &rem))) { .FAULT => unreachable, .INVAL => { // Sometimes Darwin returns EINVAL for no reason. // We treat it as a spurious wakeup. return; }, .INTR => { req = rem; continue; }, // This prong handles success as well as unexpected errors. else => return, } } } pub fn 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{ .addr = base_address, .name = "/proc/self/exe", .phdr = phdrs.ptr, .phnum = ehdr.e_phnum, }; return callback(&info, @sizeOf(dl_phdr_info), context); } // Last return value from the callback function. while (it.next()) |entry| { var phdr: [*]elf.Phdr = undefined; var phnum: u16 = undefined; if (entry.l_addr != 0) { const elf_header: *elf.Ehdr = @ptrFromInt(entry.l_addr); phdr = @ptrFromInt(entry.l_addr + elf_header.e_phoff); phnum = elf_header.e_phnum; } else { // This is the running ELF image phdr = @ptrFromInt(elf_base + ehdr.e_phoff); phnum = ehdr.e_phnum; } var info = dl_phdr_info{ .addr = entry.l_addr, .name = entry.l_name, .phdr = phdr, .phnum = phnum, }; try callback(&info, @sizeOf(dl_phdr_info), context); } } pub const ClockGetTimeError = error{UnsupportedClock} || UnexpectedError; pub fn clock_gettime(clock_id: clockid_t) ClockGetTimeError!timespec { var tp: timespec = undefined; if (native_os == .windows) { @compileError("Windows does not support POSIX; use Windows-specific API or cross-platform std.time API"); } else if (native_os == .wasi and !builtin.link_libc) { var ts: timestamp_t = undefined; switch (system.clock_time_get(clock_id, 1, &ts)) { .SUCCESS => { tp = .{ .sec = @intCast(ts / std.time.ns_per_s), .nsec = @intCast(ts % std.time.ns_per_s), }; }, .INVAL => return error.UnsupportedClock, else => |err| return unexpectedErrno(err), } return tp; } switch (errno(system.clock_gettime(clock_id, &tp))) { .SUCCESS => return tp, .FAULT => unreachable, .INVAL => return error.UnsupportedClock, else => |err| return unexpectedErrno(err), } } pub fn clock_getres(clock_id: clockid_t, res: *timespec) ClockGetTimeError!void { if (native_os == .wasi and !builtin.link_libc) { var ts: timestamp_t = undefined; switch (system.clock_res_get(@bitCast(clock_id), &ts)) { .SUCCESS => res.* = .{ .sec = @intCast(ts / std.time.ns_per_s), .nsec = @intCast(ts % std.time.ns_per_s), }, .INVAL => return error.UnsupportedClock, else => |err| return unexpectedErrno(err), } return; } switch (errno(system.clock_getres(clock_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), } } /// Return a filled sigset_t. pub fn sigfillset() sigset_t { if (builtin.link_libc) { var set: sigset_t = undefined; switch (errno(system.sigfillset(&set))) { .SUCCESS => return set, else => unreachable, } } return system.sigfillset(); } /// Return an empty sigset_t. pub fn sigemptyset() sigset_t { if (builtin.link_libc) { var set: sigset_t = undefined; switch (errno(system.sigemptyset(&set))) { .SUCCESS => return set, else => unreachable, } } return system.sigemptyset(); } pub fn sigaddset(set: *sigset_t, sig: u8) void { if (builtin.link_libc) { switch (errno(system.sigaddset(set, sig))) { .SUCCESS => return, else => unreachable, } } system.sigaddset(set, sig); } pub fn sigdelset(set: *sigset_t, sig: u8) void { if (builtin.link_libc) { switch (errno(system.sigdelset(set, sig))) { .SUCCESS => return, else => unreachable, } } system.sigdelset(set, sig); } pub fn sigismember(set: *const sigset_t, sig: u8) bool { if (builtin.link_libc) { const rc = system.sigismember(set, sig); switch (errno(rc)) { .SUCCESS => return rc == 1, else => unreachable, } } return system.sigismember(set, sig); } /// Examine and change a signal action. pub fn sigaction(sig: u8, noalias act: ?*const Sigaction, noalias oact: ?*Sigaction) void { switch (errno(system.sigaction(sig, act, oact))) { .SUCCESS => return, // EINVAL means the signal is either invalid or some signal that cannot have its action // changed. For POSIX, this means SIGKILL/SIGSTOP. For e.g. illumos, this also includes the // non-standard SIGWAITING, SIGCANCEL, and SIGLWP. Either way, programmer error. .INVAL => unreachable, 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 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 nsec field is UTIME_NOW, /// and neither 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 error_code = blk: { if (times) |times_arr| { const atim = times_arr[0].toTimestamp(); const mtim = times_arr[1].toTimestamp(); break :blk wasi.fd_filestat_set_times(fd, atim, mtim, .{ .ATIM = true, .MTIM = true, }); } break :blk wasi.fd_filestat_set_times(fd, 0, 0, .{ .ATIM_NOW = true, .MTIM_NOW = true, }); }; switch (error_code) { .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 const SendError = error{ /// (For UNIX domain sockets, which are identified by pathname) Write permission is denied /// on the destination socket file, or search permission is denied for one of the /// directories the path prefix. (See path_resolution(7).) /// (For UDP sockets) An attempt was made to send to a network/broadcast address as though /// it was a unicast address. AccessDenied, /// The socket is marked nonblocking and the requested operation would block, and /// there is no global event loop configured. /// It's also possible to get this error under the following condition: /// (Internet domain datagram sockets) The socket referred to by sockfd had not previously /// been bound to an address and, upon attempting to bind it to an ephemeral port, it was /// determined that all port numbers in the ephemeral port range are currently in use. See /// the discussion of /proc/sys/net/ipv4/ip_local_port_range in ip(7). WouldBlock, /// Another Fast Open is already in progress. FastOpenAlreadyInProgress, /// Connection reset by peer. ConnectionResetByPeer, /// The socket type requires that message be sent atomically, and the size of the message /// to be sent made this impossible. The message is not transmitted. MessageOversize, /// The output queue for a network interface was full. This generally indicates that the /// interface has stopped sending, but may be caused by transient congestion. (Normally, /// this does not occur in Linux. Packets are just silently dropped when a device queue /// overflows.) /// This is also caused when there is not enough kernel memory available. SystemResources, /// The local end has been shut down on a connection oriented socket. In this case, the /// process will also receive a SIGPIPE unless MSG.NOSIGNAL is set. BrokenPipe, FileDescriptorNotASocket, /// Network is unreachable. NetworkUnreachable, /// The local network interface used to reach the destination is down. NetworkDown, /// The destination address is not listening. ConnectionRefused, } || UnexpectedError; pub const SendMsgError = SendError || error{ /// The passed address didn't have the correct address family in its sa_family field. AddressFamilyUnsupported, /// Returned when socket is AF.UNIX and the given path has a symlink loop. SymLinkLoop, /// Returned when socket is AF.UNIX and the given path length exceeds `max_path_bytes` bytes. NameTooLong, /// Returned when socket is AF.UNIX and the given path does not point to an existing file. FileNotFound, NotDir, /// The socket is not connected (connection-oriented sockets only). SocketUnconnected, AddressUnavailable, }; pub fn sendmsg( /// The file descriptor of the sending socket. sockfd: socket_t, /// Message header and iovecs msg: *const msghdr_const, flags: u32, ) SendMsgError!usize { while (true) { const rc = system.sendmsg(sockfd, msg, flags); if (native_os == .windows) { if (rc == windows.ws2_32.SOCKET_ERROR) { switch (windows.ws2_32.WSAGetLastError()) { .EACCES => return error.AccessDenied, .EADDRNOTAVAIL => return error.AddressUnavailable, .ECONNRESET => return error.ConnectionResetByPeer, .EMSGSIZE => return error.MessageOversize, .ENOBUFS => return error.SystemResources, .ENOTSOCK => return error.FileDescriptorNotASocket, .EAFNOSUPPORT => return error.AddressFamilyUnsupported, .EDESTADDRREQ => unreachable, // A destination address is required. .EFAULT => unreachable, // The lpBuffers, lpTo, lpOverlapped, lpNumberOfBytesSent, or lpCompletionRoutine parameters are not part of the user address space, or the lpTo parameter is too small. .EHOSTUNREACH => return error.NetworkUnreachable, // TODO: EINPROGRESS, EINTR .EINVAL => unreachable, .ENETDOWN => return error.NetworkDown, .ENETRESET => return error.ConnectionResetByPeer, .ENETUNREACH => return error.NetworkUnreachable, .ENOTCONN => return error.SocketUnconnected, .ESHUTDOWN => unreachable, // The socket has been shut down; it is not possible to WSASendTo on a socket after shutdown has been invoked with how set to SD_SEND or SD_BOTH. .EWOULDBLOCK => return error.WouldBlock, .NOTINITIALISED => unreachable, // A successful WSAStartup call must occur before using this function. else => |err| return windows.unexpectedWSAError(err), } } else { return @intCast(rc); } } else { switch (errno(rc)) { .SUCCESS => return @intCast(rc), .ACCES => return error.AccessDenied, .AGAIN => return error.WouldBlock, .ALREADY => return error.FastOpenAlreadyInProgress, .BADF => unreachable, // always a race condition .CONNRESET => return error.ConnectionResetByPeer, .DESTADDRREQ => unreachable, // The socket is not connection-mode, and no peer address is set. .FAULT => unreachable, // An invalid user space address was specified for an argument. .INTR => continue, .INVAL => unreachable, // Invalid argument passed. .ISCONN => unreachable, // connection-mode socket was connected already but a recipient was specified .MSGSIZE => return error.MessageOversize, .NOBUFS => return error.SystemResources, .NOMEM => return error.SystemResources, .NOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket. .OPNOTSUPP => unreachable, // Some bit in the flags argument is inappropriate for the socket type. .PIPE => return error.BrokenPipe, .AFNOSUPPORT => return error.AddressFamilyUnsupported, .LOOP => return error.SymLinkLoop, .NAMETOOLONG => return error.NameTooLong, .NOENT => return error.FileNotFound, .NOTDIR => return error.NotDir, .HOSTUNREACH => return error.NetworkUnreachable, .NETUNREACH => return error.NetworkUnreachable, .NOTCONN => return error.SocketUnconnected, .NETDOWN => return error.NetworkDown, else => |err| return unexpectedErrno(err), } } } } pub const SendToError = SendMsgError || error{ /// The destination address is not reachable by the bound address. UnreachableAddress, /// The destination address is not listening. ConnectionRefused, }; /// Transmit a message to another socket. /// /// The `sendto` call may be used only when the socket is in a connected state (so that the intended /// recipient is known). The following call /// /// send(sockfd, buf, len, flags); /// /// is equivalent to /// /// sendto(sockfd, buf, len, flags, NULL, 0); /// /// If sendto() is used on a connection-mode (`SOCK.STREAM`, `SOCK.SEQPACKET`) socket, the arguments /// `dest_addr` and `addrlen` are asserted to be `null` and `0` respectively, and asserted /// that the socket was actually connected. /// Otherwise, the address of the target is given by `dest_addr` with `addrlen` specifying its size. /// /// If the message is too long to pass atomically through the underlying protocol, /// `SendError.MessageOversize` is returned, and the message is not transmitted. /// /// There is no indication of failure to deliver. /// /// When the message does not fit into the send buffer of the socket, `sendto` normally blocks, /// unless the socket has been placed in nonblocking I/O mode. In nonblocking mode it would fail /// with `SendError.WouldBlock`. The `select` call may be used to determine when it is /// possible to send more data. pub fn sendto( /// The file descriptor of the sending socket. sockfd: socket_t, /// Message to send. buf: []const u8, flags: u32, dest_addr: ?*const sockaddr, addrlen: socklen_t, ) SendToError!usize { if (native_os == .windows) { switch (windows.sendto(sockfd, buf.ptr, buf.len, flags, dest_addr, addrlen)) { windows.ws2_32.SOCKET_ERROR => switch (windows.ws2_32.WSAGetLastError()) { .EACCES => return error.AccessDenied, .EADDRNOTAVAIL => return error.AddressUnavailable, .ECONNRESET => return error.ConnectionResetByPeer, .EMSGSIZE => return error.MessageOversize, .ENOBUFS => return error.SystemResources, .ENOTSOCK => return error.FileDescriptorNotASocket, .EAFNOSUPPORT => return error.AddressFamilyUnsupported, .EDESTADDRREQ => unreachable, // A destination address is required. .EFAULT => unreachable, // The lpBuffers, lpTo, lpOverlapped, lpNumberOfBytesSent, or lpCompletionRoutine parameters are not part of the user address space, or the lpTo parameter is too small. .EHOSTUNREACH => return error.NetworkUnreachable, // TODO: EINPROGRESS, EINTR .EINVAL => unreachable, .ENETDOWN => return error.NetworkDown, .ENETRESET => return error.ConnectionResetByPeer, .ENETUNREACH => return error.NetworkUnreachable, .ENOTCONN => return error.SocketUnconnected, .ESHUTDOWN => unreachable, // The socket has been shut down; it is not possible to WSASendTo on a socket after shutdown has been invoked with how set to SD_SEND or SD_BOTH. .EWOULDBLOCK => return error.WouldBlock, .NOTINITIALISED => unreachable, // A successful WSAStartup call must occur before using this function. else => |err| return windows.unexpectedWSAError(err), }, else => |rc| return @intCast(rc), } } while (true) { const rc = system.sendto(sockfd, buf.ptr, buf.len, flags, dest_addr, addrlen); switch (errno(rc)) { .SUCCESS => return @intCast(rc), .ACCES => return error.AccessDenied, .AGAIN => return error.WouldBlock, .ALREADY => return error.FastOpenAlreadyInProgress, .BADF => unreachable, // always a race condition .CONNREFUSED => return error.ConnectionRefused, .CONNRESET => return error.ConnectionResetByPeer, .DESTADDRREQ => unreachable, // The socket is not connection-mode, and no peer address is set. .FAULT => unreachable, // An invalid user space address was specified for an argument. .INTR => continue, .INVAL => return error.UnreachableAddress, .ISCONN => unreachable, // connection-mode socket was connected already but a recipient was specified .MSGSIZE => return error.MessageOversize, .NOBUFS => return error.SystemResources, .NOMEM => return error.SystemResources, .NOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket. .OPNOTSUPP => unreachable, // Some bit in the flags argument is inappropriate for the socket type. .PIPE => return error.BrokenPipe, .AFNOSUPPORT => return error.AddressFamilyUnsupported, .LOOP => return error.SymLinkLoop, .NAMETOOLONG => return error.NameTooLong, .NOENT => return error.FileNotFound, .NOTDIR => return error.NotDir, .HOSTUNREACH => return error.NetworkUnreachable, .NETUNREACH => return error.NetworkUnreachable, .NOTCONN => return error.SocketUnconnected, .NETDOWN => return error.NetworkDown, else => |err| return unexpectedErrno(err), } } } /// Transmit a message to another socket. /// /// The `send` call may be used only when the socket is in a connected state (so that the intended /// recipient is known). The only difference between `send` and `write` is the presence of /// flags. With a zero flags argument, `send` is equivalent to `write`. Also, the following /// call /// /// send(sockfd, buf, len, flags); /// /// is equivalent to /// /// sendto(sockfd, buf, len, flags, NULL, 0); /// /// There is no indication of failure to deliver. /// /// When the message does not fit into the send buffer of the socket, `send` normally blocks, /// unless the socket has been placed in nonblocking I/O mode. In nonblocking mode it would fail /// with `SendError.WouldBlock`. The `select` call may be used to determine when it is /// possible to send more data. pub fn send( /// The file descriptor of the sending socket. sockfd: socket_t, buf: []const u8, flags: u32, ) SendError!usize { return sendto(sockfd, buf, flags, null, 0) catch |err| switch (err) { error.AddressFamilyUnsupported => unreachable, error.SymLinkLoop => unreachable, error.NameTooLong => unreachable, error.FileNotFound => unreachable, error.NotDir => unreachable, error.NetworkUnreachable => unreachable, error.AddressUnavailable => unreachable, error.SocketUnconnected => unreachable, error.UnreachableAddress => unreachable, else => |e| return e, }; } pub const 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 (cross-filesystem from version 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 { if (builtin.os.tag == .freebsd or builtin.os.tag == .linux) { const use_c = native_os != .linux or std.c.versionCheck(if (builtin.abi.isAndroid()) .{ .major = 34, .minor = 0, .patch = 0 } else .{ .major = 2, .minor = 27, .patch = 0 }); const sys = if (use_c) std.c else linux; var off_in_copy: i64 = @bitCast(off_in); var off_out_copy: i64 = @bitCast(off_out); while (true) { const rc = sys.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 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. NetworkDown, /// The kernel had no space to allocate file descriptor tables. SystemResources, } || UnexpectedError; pub fn poll(fds: []pollfd, timeout: i32) PollError!usize { if (native_os == .windows) { switch (windows.poll(fds.ptr, @intCast(fds.len), timeout)) { windows.ws2_32.SOCKET_ERROR => switch (windows.ws2_32.WSAGetLastError()) { .NOTINITIALISED => unreachable, .ENETDOWN => return error.NetworkDown, .ENOBUFS => return error.SystemResources, // TODO: handle more errors else => |err| return windows.unexpectedWSAError(err), }, else => |rc| return @intCast(rc), } } while (true) { const fds_count = cast(nfds_t, fds.len) orelse return error.SystemResources; const rc = system.poll(fds.ptr, fds_count, timeout); 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, Timeout, /// The socket has not been bound. SocketNotBound, /// The UDP message was too big for the buffer and part of it has been discarded MessageOversize, /// The network subsystem has failed. NetworkDown, /// The socket is not connected (connection-oriented sockets only). SocketUnconnected, /// The other end closed the socket unexpectedly or a read is executed on a shut down socket BrokenPipe, } || UnexpectedError; pub fn recv(sock: socket_t, buf: []u8, flags: u32) RecvFromError!usize { return recvfrom(sock, buf, flags, null, null); } /// If `sockfd` is opened in non blocking mode, the function will /// return error.WouldBlock when EAGAIN is received. pub fn recvfrom( sockfd: socket_t, buf: []u8, flags: u32, src_addr: ?*sockaddr, addrlen: ?*socklen_t, ) RecvFromError!usize { while (true) { const rc = system.recvfrom(sockfd, buf.ptr, buf.len, flags, src_addr, addrlen); if (native_os == .windows) { if (rc == windows.ws2_32.SOCKET_ERROR) { switch (windows.ws2_32.WSAGetLastError()) { .NOTINITIALISED => unreachable, .ECONNRESET => return error.ConnectionResetByPeer, .EINVAL => return error.SocketNotBound, .EMSGSIZE => return error.MessageOversize, .ENETDOWN => return error.NetworkDown, .ENOTCONN => return error.SocketUnconnected, .EWOULDBLOCK => return error.WouldBlock, .ETIMEDOUT => return error.Timeout, // TODO: handle more errors else => |err| return windows.unexpectedWSAError(err), } } else { return @intCast(rc); } } else { switch (errno(rc)) { .SUCCESS => return @intCast(rc), .BADF => unreachable, // always a race condition .FAULT => unreachable, .INVAL => unreachable, .NOTCONN => return error.SocketUnconnected, .NOTSOCK => unreachable, .INTR => continue, .AGAIN => return error.WouldBlock, .NOMEM => return error.SystemResources, .CONNREFUSED => return error.ConnectionRefused, .CONNRESET => return error.ConnectionResetByPeer, .TIMEDOUT => return error.Timeout, .PIPE => return error.BrokenPipe, else => |err| return unexpectedErrno(err), } } } } pub const RecvMsgError = RecvFromError || error{ /// Reception of SCM_RIGHTS fds via ancillary data in msg.control would /// exceed some system limit (generally this is retryable by trying to /// receive fewer fds or closing some existing fds) SystemFdQuotaExceeded, /// Reception of SCM_RIGHTS fds via ancillary data in msg.control would /// exceed some process limit (generally this is retryable by trying to /// receive fewer fds, closing some existing fds, or changing the ulimit) ProcessFdQuotaExceeded, }; /// If `sockfd` is opened in non blocking mode, the function will /// return error.WouldBlock when EAGAIN is received. pub fn recvmsg( /// The file descriptor of the sending socket. sockfd: socket_t, /// Message header and iovecs msg: *msghdr, flags: u32, ) RecvMsgError!usize { if (@TypeOf(system.recvmsg) == void) @compileError("recvmsg() not supported on this OS"); while (true) { const rc = system.recvmsg(sockfd, msg, flags); switch (errno(rc)) { .SUCCESS => return @intCast(rc), .AGAIN => return error.WouldBlock, .BADF => unreachable, // always a race condition .NFILE => return error.SystemFdQuotaExceeded, .MFILE => return error.ProcessFdQuotaExceeded, .INTR => continue, .FAULT => unreachable, // An invalid user space address was specified for an argument. .INVAL => unreachable, // Invalid argument passed. .ISCONN => unreachable, // connection-mode socket was connected already but a recipient was specified .NOBUFS => return error.SystemResources, .NOMEM => return error.SystemResources, .NOTCONN => return error.SocketUnconnected, .NOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket. .MSGSIZE => return error.MessageOversize, .PIPE => return error.BrokenPipe, .OPNOTSUPP => unreachable, // Some bit in the flags argument is inappropriate for the socket type. .CONNRESET => return error.ConnectionResetByPeer, .NETDOWN => return error.NetworkDown, else => |err| return unexpectedErrno(err), } } } pub const SetSockOptError = error{ /// The socket is already connected, and a specified option cannot be set while the socket is connected. AlreadyConnected, /// 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, OperationNotSupported, NetworkDown, FileDescriptorNotASocket, SocketNotBound, NoDevice, } || UnexpectedError; /// Set a socket's options. pub fn setsockopt(fd: socket_t, level: i32, optname: u32, opt: []const u8) SetSockOptError!void { if (native_os == .windows) { const rc = windows.ws2_32.setsockopt(fd, level, @intCast(optname), opt.ptr, @intCast(opt.len)); if (rc == windows.ws2_32.SOCKET_ERROR) { switch (windows.ws2_32.WSAGetLastError()) { .NOTINITIALISED => unreachable, .ENETDOWN => return error.NetworkDown, .EFAULT => unreachable, .ENOTSOCK => return error.FileDescriptorNotASocket, .EINVAL => 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, .OPNOTSUPP => return error.OperationNotSupported, 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, 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 and bionic versions starting with 30. const use_c = std.c.versionCheck(if (builtin.abi.isAndroid()) .{ .major = 30, .minor = 0, .patch = 0 } else .{ .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, 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 const SetSidError = error{ /// The calling process is already a process group leader, or the process group ID of a process other than the calling process matches the process ID of the calling process. PermissionDenied, } || UnexpectedError; pub fn setsid() SetSidError!pid_t { const rc = system.setsid(); switch (errno(rc)) { .SUCCESS => return @intCast(rc), .PERM => return error.PermissionDenied, else => |err| return unexpectedErrno(err), } } pub fn signalfd(fd: fd_t, mask: *const sigset_t, flags: u32) !fd_t { const rc = system.signalfd(fd, mask, flags); switch (errno(rc)) { .SUCCESS => return @intCast(rc), .BADF, .INVAL => unreachable, .NFILE => return error.SystemFdQuotaExceeded, .NOMEM => return error.SystemResources, .MFILE => return error.ProcessResources, .NODEV => return error.InodeMountFail, 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.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(page_size_min) 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(page_size_min) u8, length: usize, advice: u32) MadviseError!void { switch (errno(system.madvise(ptr, length, advice))) { .SUCCESS => return, .PERM => return error.PermissionDenied, .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: *system.perf_event_attr, pid: pid_t, cpu: i32, group_fd: fd_t, flags: usize, ) PerfEventOpenError!fd_t { if (native_os == .linux) { // There is no syscall wrapper for this function exposed by libcs 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{ PermissionDenied, ProcessFdQuotaExceeded, SystemFdQuotaExceeded, NoDevice, SystemResources, } || UnexpectedError; pub const TimerFdGetError = error{InvalidHandle} || UnexpectedError; pub const TimerFdSetError = TimerFdGetError || error{Canceled}; pub fn timerfd_create(clock_id: system.timerfd_clockid_t, flags: system.TFD) TimerFdCreateError!fd_t { const rc = system.timerfd_create(clock_id, @bitCast(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.PermissionDenied, else => |err| return unexpectedErrno(err), }; } pub fn timerfd_settime( fd: i32, flags: system.TFD.TIMER, new_value: *const system.itimerspec, old_value: ?*system.itimerspec, ) TimerFdSetError!void { const rc = system.timerfd_settime(fd, @bitCast(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!system.itimerspec { var curr_value: system.itimerspec = undefined; const rc = system.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{ DeadLock, DeviceBusy, InputOutput, NameTooLong, OperationNotSupported, OutOfMemory, ProcessNotFound, PermissionDenied, } || UnexpectedError; pub fn ptrace(request: u32, pid: pid_t, addr: usize, data: usize) PtraceError!void { return switch (native_os) { .windows, .wasi, .emscripten, .haiku, .illumos, .plan9, => @compileError("ptrace unsupported by target OS"), .linux => switch (errno(if (builtin.link_libc) std.c.ptrace( @intCast(request), pid, @ptrFromInt(addr), @ptrFromInt(data), ) else linux.ptrace(request, pid, addr, data, 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, .visionos => switch (errno(std.c.ptrace( @enumFromInt(request), pid, @ptrFromInt(addr), @intCast(data), ))) { .SUCCESS => {}, .SRCH => error.ProcessNotFound, .INVAL => unreachable, .PERM => error.PermissionDenied, .BUSY => error.DeviceBusy, else => |err| return unexpectedErrno(err), }, .dragonfly => switch (errno(std.c.ptrace( @intCast(request), pid, @ptrFromInt(addr), @intCast(data), ))) { .SUCCESS => {}, .SRCH => error.ProcessNotFound, .INVAL => unreachable, .PERM => error.PermissionDenied, .BUSY => error.DeviceBusy, else => |err| return unexpectedErrno(err), }, .freebsd => switch (errno(std.c.ptrace( @intCast(request), pid, @ptrFromInt(addr), @intCast(data), ))) { .SUCCESS => {}, .SRCH => error.ProcessNotFound, .INVAL => unreachable, .PERM => error.PermissionDenied, .BUSY => error.DeviceBusy, .NOENT, .NOMEM => error.OutOfMemory, .NAMETOOLONG => error.NameTooLong, else => |err| return unexpectedErrno(err), }, .netbsd => switch (errno(std.c.ptrace( @intCast(request), pid, @ptrFromInt(addr), @intCast(data), ))) { .SUCCESS => {}, .SRCH => error.ProcessNotFound, .INVAL => unreachable, .PERM => error.PermissionDenied, .BUSY => error.DeviceBusy, .DEADLK => error.DeadLock, else => |err| return unexpectedErrno(err), }, .openbsd => switch (errno(std.c.ptrace( @intCast(request), pid, @ptrFromInt(addr), @intCast(data), ))) { .SUCCESS => {}, .SRCH => error.ProcessNotFound, .INVAL => unreachable, .PERM => error.PermissionDenied, .BUSY => error.DeviceBusy, .NOTSUP => error.OperationNotSupported, else => |err| return unexpectedErrno(err), }, else => @compileError("std.posix.ptrace unimplemented for target OS"), }; } pub const NameToFileHandleAtError = error{ FileNotFound, NotDir, OperationNotSupported, NameTooLong, Unexpected, }; pub fn name_to_handle_at( dirfd: fd_t, pathname: []const u8, handle: *std.os.linux.file_handle, mount_id: *i32, flags: u32, ) NameToFileHandleAtError!void { const pathname_c = try toPosixPath(pathname); return name_to_handle_atZ(dirfd, &pathname_c, handle, mount_id, flags); } pub fn name_to_handle_atZ( dirfd: fd_t, pathname_z: [*:0]const u8, handle: *std.os.linux.file_handle, mount_id: *i32, flags: u32, ) NameToFileHandleAtError!void { switch (errno(system.name_to_handle_at(dirfd, pathname_z, handle, mount_id, flags))) { .SUCCESS => {}, .FAULT => unreachable, // pathname, mount_id, or handle outside accessible address space .INVAL => unreachable, // bad flags, or handle_bytes too big .NOENT => return error.FileNotFound, .NOTDIR => return error.NotDir, .OPNOTSUPP => return error.OperationNotSupported, .OVERFLOW => return error.NameTooLong, else => |err| return unexpectedErrno(err), } } pub const IoCtl_SIOCGIFINDEX_Error = error{ FileSystem, InterfaceNotFound, } || UnexpectedError; pub fn ioctl_SIOCGIFINDEX(fd: fd_t, ifr: *ifreq) IoCtl_SIOCGIFINDEX_Error!void { while (true) { switch (errno(system.ioctl(fd, SIOCGIFINDEX, @intFromPtr(ifr)))) { .SUCCESS => return, .INVAL => unreachable, // Bad parameters. .NOTTY => unreachable, .NXIO => unreachable, .BADF => unreachable, // Always a race condition. .FAULT => unreachable, // Bad pointer parameter. .INTR => continue, .IO => return error.FileSystem, .NODEV => return error.InterfaceNotFound, else => |err| return unexpectedErrno(err), } } } pub const lfs64_abi = native_os == .linux and builtin.link_libc and (builtin.abi.isGnu() or builtin.abi.isAndroid()); /// 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.mode == .Debug and switch (builtin.zig_backend) { .stage2_llvm, .stage2_x86_64 => true, else => false, }; pub const UnexpectedError = std.Io.UnexpectedError; /// 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(.{}); } 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; }