aboutsummaryrefslogtreecommitdiff
path: root/lib/std/os.zig
diff options
context:
space:
mode:
Diffstat (limited to 'lib/std/os.zig')
-rw-r--r--lib/std/os.zig2738
1 files changed, 2738 insertions, 0 deletions
diff --git a/lib/std/os.zig b/lib/std/os.zig
new file mode 100644
index 0000000000..ed626e0dcc
--- /dev/null
+++ b/lib/std/os.zig
@@ -0,0 +1,2738 @@
+// This file contains thin wrappers around OS-specific APIs, with these
+// specific goals in mind:
+// * Convert "errno"-style error codes into Zig errors.
+// * When null-terminated byte buffers are required, provide APIs which accept
+// slices as well as APIs which accept null-terminated byte buffers. Same goes
+// for UTF-16LE encoding.
+// * Where operating systems share APIs, e.g. POSIX, these thin wrappers provide
+// cross platform abstracting.
+// * When there exists a corresponding libc function and linking libc, the libc
+// implementation is used. Exceptions are made for known buggy areas of libc.
+// On Linux libc can be side-stepped by using `std.os.linux` directly.
+// * For Windows, this file represents the API that libc would provide for
+// Windows. For thin wrappers around Windows-specific APIs, see `std.os.windows`.
+// Note: The Zig standard library does not support POSIX thread cancellation, and
+// in general EINTR is handled by trying again.
+
+const std = @import("std.zig");
+const builtin = @import("builtin");
+const assert = std.debug.assert;
+const math = std.math;
+const mem = std.mem;
+const elf = std.elf;
+const dl = @import("dynamic_library.zig");
+const MAX_PATH_BYTES = std.fs.MAX_PATH_BYTES;
+
+comptime {
+ assert(@import("std") == std); // std lib tests require --override-std-dir
+}
+
+pub const darwin = @import("os/darwin.zig");
+pub const freebsd = @import("os/freebsd.zig");
+pub const linux = @import("os/linux.zig");
+pub const netbsd = @import("os/netbsd.zig");
+pub const uefi = @import("os/uefi.zig");
+pub const wasi = @import("os/wasi.zig");
+pub const windows = @import("os/windows.zig");
+pub const zen = @import("os/zen.zig");
+
+/// When linking libc, this is the C API. Otherwise, it is the OS-specific system interface.
+pub const system = if (builtin.link_libc) std.c else switch (builtin.os) {
+ .macosx, .ios, .watchos, .tvos => darwin,
+ .freebsd => freebsd,
+ .linux => linux,
+ .netbsd => netbsd,
+ .wasi => wasi,
+ .windows => windows,
+ .zen => zen,
+ else => struct {},
+};
+
+pub usingnamespace @import("os/bits.zig");
+
+/// See also `getenv`. Populated by startup code before main().
+pub var environ: [][*]u8 = undefined;
+
+/// Populated by startup code before main().
+/// Not available on Windows. See `std.process.args`
+/// for obtaining the process arguments.
+pub var argv: [][*]u8 = undefined;
+
+/// To obtain errno, call this function with the return value of the
+/// system function call. For some systems this will obtain the value directly
+/// from the return code; for others it will use a thread-local errno variable.
+/// Therefore, this function only returns a well-defined value when it is called
+/// directly after the system function call which one wants to learn the errno
+/// value of.
+pub const errno = system.getErrno;
+
+/// Closes the file descriptor.
+/// This function is not capable of returning any indication of failure. An
+/// application which wants to ensure writes have succeeded before closing
+/// must call `fsync` before `close`.
+/// Note: The Zig standard library does not support POSIX thread cancellation.
+pub fn close(fd: fd_t) void {
+ if (windows.is_the_target) {
+ return windows.CloseHandle(fd);
+ }
+ if (wasi.is_the_target) {
+ _ = wasi.fd_close(fd);
+ }
+ if (darwin.is_the_target) {
+ // This avoids the EINTR problem.
+ switch (darwin.getErrno(darwin.@"close$NOCANCEL"(fd))) {
+ EBADF => unreachable, // Always a race condition.
+ else => return,
+ }
+ }
+ switch (errno(system.close(fd))) {
+ EBADF => unreachable, // Always a race condition.
+ EINTR => return, // This is still a success. See https://github.com/ziglang/zig/issues/2425
+ else => return,
+ }
+}
+
+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 (windows.is_the_target) {
+ return windows.RtlGenRandom(buffer);
+ }
+ if (linux.is_the_target or freebsd.is_the_target) {
+ var buf = buffer;
+ const use_c = !linux.is_the_target or
+ std.c.versionCheck(builtin.Version{ .major = 2, .minor = 25, .patch = 0 }).ok;
+
+ while (buf.len != 0) {
+ var err: u16 = undefined;
+
+ const num_read = if (use_c) blk: {
+ const rc = std.c.getrandom(buf.ptr, buf.len, 0);
+ err = std.c.getErrno(rc);
+ break :blk @bitCast(usize, rc);
+ } else blk: {
+ const rc = linux.getrandom(buf.ptr, buf.len, 0);
+ err = linux.getErrno(rc);
+ break :blk rc;
+ };
+
+ switch (err) {
+ 0 => buf = buf[num_read..],
+ EINVAL => unreachable,
+ EFAULT => unreachable,
+ EINTR => continue,
+ ENOSYS => return getRandomBytesDevURandom(buf),
+ else => return unexpectedErrno(err),
+ }
+ }
+ return;
+ }
+ if (wasi.is_the_target) {
+ switch (wasi.random_get(buffer.ptr, buffer.len)) {
+ 0 => return,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+ return getRandomBytesDevURandom(buffer);
+}
+
+fn getRandomBytesDevURandom(buf: []u8) !void {
+ const fd = try openC(c"/dev/urandom", O_RDONLY | O_CLOEXEC, 0);
+ defer close(fd);
+
+ const st = try fstat(fd);
+ if (!S_ISCHR(st.mode)) {
+ return error.NoDevice;
+ }
+
+ const stream = &std.fs.File.openHandle(fd).inStream().stream;
+ stream.readNoEof(buf) catch return error.Unexpected;
+}
+
+/// Causes abnormal process termination.
+/// If linking against libc, this calls the abort() libc function. Otherwise
+/// it raises SIGABRT followed by SIGKILL and finally lo
+pub fn abort() noreturn {
+ @setCold(true);
+ // MSVCRT abort() sometimes opens a popup window which is undesirable, so
+ // even when linking libc on Windows we use our own abort implementation.
+ // See https://github.com/ziglang/zig/issues/2071 for more details.
+ if (windows.is_the_target) {
+ if (builtin.mode == .Debug) {
+ @breakpoint();
+ }
+ windows.kernel32.ExitProcess(3);
+ }
+ if (builtin.link_libc) {
+ system.abort();
+ }
+ if (builtin.os == .uefi) {
+ exit(0); // TODO choose appropriate exit code
+ }
+
+ raise(SIGABRT) catch {};
+
+ // TODO the rest of the implementation of abort() from musl libc here
+
+ raise(SIGKILL) catch {};
+ exit(127);
+}
+
+pub const RaiseError = error{Unexpected};
+
+pub fn raise(sig: u8) RaiseError!void {
+ if (builtin.link_libc) {
+ switch (errno(system.raise(sig))) {
+ 0 => return,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+
+ if (wasi.is_the_target) {
+ switch (wasi.proc_raise(SIGABRT)) {
+ 0 => return,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+
+ if (linux.is_the_target) {
+ var set: linux.sigset_t = undefined;
+ linux.blockAppSignals(&set);
+ const tid = linux.syscall0(linux.SYS_gettid);
+ const rc = linux.syscall2(linux.SYS_tkill, tid, sig);
+ linux.restoreSignals(&set);
+ switch (errno(rc)) {
+ 0 => return,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+
+ @compileError("std.os.raise unimplemented for this target");
+}
+
+pub const KillError = error{
+ PermissionDenied,
+ Unexpected,
+};
+
+pub fn kill(pid: pid_t, sig: u8) KillError!void {
+ switch (errno(system.kill(pid, sig))) {
+ 0 => return,
+ EINVAL => unreachable, // invalid signal
+ EPERM => return error.PermissionDenied,
+ ESRCH => unreachable, // always a race condition
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+/// Exits the program cleanly with the specified status code.
+pub fn exit(status: u8) noreturn {
+ if (builtin.link_libc) {
+ system.exit(status);
+ }
+ if (windows.is_the_target) {
+ windows.kernel32.ExitProcess(status);
+ }
+ if (wasi.is_the_target) {
+ wasi.proc_exit(status);
+ }
+ if (linux.is_the_target and !builtin.single_threaded) {
+ linux.exit_group(status);
+ }
+ if (uefi.is_the_target) {
+ // exit() is only avaliable if exitBootServices() has not been called yet.
+ // This call to exit should not fail, so we don't care about its return value.
+ if (uefi.system_table.boot_services) |bs| {
+ _ = bs.exit(uefi.handle, status, 0, null);
+ }
+ // If we can't exit, reboot the system instead.
+ uefi.system_table.runtime_services.resetSystem(uefi.tables.ResetType.ResetCold, status, 0, null);
+ }
+ system.exit(status);
+}
+
+pub const ReadError = error{
+ InputOutput,
+ SystemResources,
+ IsDir,
+ OperationAborted,
+ BrokenPipe,
+
+ /// This error occurs when no global event loop is configured,
+ /// and reading from the file descriptor would block.
+ WouldBlock,
+
+ Unexpected,
+};
+
+/// Returns the number of bytes that were read, which can be less than
+/// buf.len. If 0 bytes were read, that means EOF.
+/// If the application has a global event loop enabled, EAGAIN is handled
+/// via the event loop. Otherwise EAGAIN results in error.WouldBlock.
+pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
+ if (windows.is_the_target) {
+ return windows.ReadFile(fd, buf);
+ }
+
+ if (wasi.is_the_target and !builtin.link_libc) {
+ const iovs = [1]iovec{iovec{
+ .iov_base = buf.ptr,
+ .iov_len = buf.len,
+ }};
+
+ var nread: usize = undefined;
+ switch (wasi.fd_read(fd, &iovs, iovs.len, &nread)) {
+ 0 => return nread,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+
+ while (true) {
+ const rc = system.read(fd, buf.ptr, buf.len);
+ switch (errno(rc)) {
+ 0 => return @intCast(usize, rc),
+ EINTR => continue,
+ EINVAL => unreachable,
+ EFAULT => unreachable,
+ EAGAIN => if (std.event.Loop.instance) |loop| {
+ loop.waitUntilFdReadable(fd) catch return error.WouldBlock;
+ continue;
+ } else {
+ return error.WouldBlock;
+ },
+ EBADF => unreachable, // Always a race condition.
+ EIO => return error.InputOutput,
+ EISDIR => return error.IsDir,
+ ENOBUFS => return error.SystemResources,
+ ENOMEM => return error.SystemResources,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+ return index;
+}
+
+/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.
+/// This function is for blocking file descriptors only.
+pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) ReadError!usize {
+ if (darwin.is_the_target) {
+ // Darwin does not have preadv but it does have pread.
+ var off: usize = 0;
+ var iov_i: usize = 0;
+ var inner_off: usize = 0;
+ while (true) {
+ const v = iov[iov_i];
+ const rc = darwin.pread(fd, v.iov_base + inner_off, v.iov_len - inner_off, offset + off);
+ const err = darwin.getErrno(rc);
+ switch (err) {
+ 0 => {
+ const amt_read = @bitCast(usize, rc);
+ off += amt_read;
+ inner_off += amt_read;
+ if (inner_off == v.iov_len) {
+ iov_i += 1;
+ inner_off = 0;
+ if (iov_i == iov.len) {
+ return off;
+ }
+ }
+ if (rc == 0) return off; // EOF
+ continue;
+ },
+ EINTR => continue,
+ EINVAL => unreachable,
+ EFAULT => unreachable,
+ ESPIPE => unreachable, // fd is not seekable
+ EAGAIN => unreachable, // This function is for blocking reads.
+ EBADF => unreachable, // always a race condition
+ EIO => return error.InputOutput,
+ EISDIR => return error.IsDir,
+ ENOBUFS => return error.SystemResources,
+ ENOMEM => return error.SystemResources,
+ else => return unexpectedErrno(err),
+ }
+ }
+ }
+ while (true) {
+ // TODO handle the case when iov_len is too large and get rid of this @intCast
+ const rc = system.preadv(fd, iov.ptr, @intCast(u32, iov.len), offset);
+ switch (errno(rc)) {
+ 0 => return @bitCast(usize, rc),
+ EINTR => continue,
+ EINVAL => unreachable,
+ EFAULT => unreachable,
+ EAGAIN => unreachable, // This function is for blocking reads.
+ EBADF => unreachable, // always a race condition
+ EIO => return error.InputOutput,
+ EISDIR => return error.IsDir,
+ ENOBUFS => return error.SystemResources,
+ ENOMEM => return error.SystemResources,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+}
+
+pub const WriteError = error{
+ DiskQuota,
+ FileTooBig,
+ InputOutput,
+ NoSpaceLeft,
+ AccessDenied,
+ BrokenPipe,
+ SystemResources,
+ OperationAborted,
+ Unexpected,
+};
+
+/// Write to a file descriptor. Keeps trying if it gets interrupted.
+/// This function is for blocking file descriptors only.
+pub fn write(fd: fd_t, bytes: []const u8) WriteError!void {
+ if (windows.is_the_target) {
+ return windows.WriteFile(fd, bytes);
+ }
+
+ if (wasi.is_the_target and !builtin.link_libc) {
+ const ciovs = [1]iovec_const{iovec_const{
+ .iov_base = bytes.ptr,
+ .iov_len = bytes.len,
+ }};
+ var nwritten: usize = undefined;
+ switch (wasi.fd_write(fd, &ciovs, ciovs.len, &nwritten)) {
+ 0 => return,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+
+ // Linux can return EINVAL when write amount is > 0x7ffff000
+ // See https://github.com/ziglang/zig/pull/743#issuecomment-363165856
+ // TODO audit this. Shawn Landden says that this is not actually true.
+ // if this logic should stay, move it to std.os.linux
+ const max_bytes_len = 0x7ffff000;
+
+ var index: usize = 0;
+ while (index < bytes.len) {
+ const amt_to_write = math.min(bytes.len - index, usize(max_bytes_len));
+ const rc = system.write(fd, bytes.ptr + index, amt_to_write);
+ switch (errno(rc)) {
+ 0 => {
+ index += @intCast(usize, rc);
+ continue;
+ },
+ EINTR => continue,
+ EINVAL => unreachable,
+ EFAULT => unreachable,
+ EAGAIN => unreachable, // This function is for blocking writes.
+ EBADF => unreachable, // Always a race condition.
+ EDESTADDRREQ => unreachable, // `connect` was never called.
+ EDQUOT => return error.DiskQuota,
+ EFBIG => return error.FileTooBig,
+ EIO => return error.InputOutput,
+ ENOSPC => return error.NoSpaceLeft,
+ EPERM => return error.AccessDenied,
+ EPIPE => return error.BrokenPipe,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+}
+
+/// Write multiple buffers to a file descriptor. Keeps trying if it gets interrupted.
+/// This function is for blocking file descriptors only. For non-blocking, see
+/// `writevAsync`.
+pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!void {
+ while (true) {
+ // TODO handle the case when iov_len is too large and get rid of this @intCast
+ const rc = system.writev(fd, iov.ptr, @intCast(u32, iov.len));
+ switch (errno(rc)) {
+ 0 => return,
+ EINTR => continue,
+ EINVAL => unreachable,
+ EFAULT => unreachable,
+ EAGAIN => unreachable, // This function is for blocking writes.
+ EBADF => unreachable, // Always a race condition.
+ EDESTADDRREQ => unreachable, // `connect` was never called.
+ EDQUOT => return error.DiskQuota,
+ EFBIG => return error.FileTooBig,
+ EIO => return error.InputOutput,
+ ENOSPC => return error.NoSpaceLeft,
+ EPERM => return error.AccessDenied,
+ EPIPE => return error.BrokenPipe,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+}
+
+/// Write multiple buffers to a file descriptor, with a position offset.
+/// Keeps trying if it gets interrupted.
+/// This function is for blocking file descriptors only. For non-blocking, see
+/// `pwritevAsync`.
+pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) WriteError!void {
+ if (darwin.is_the_target) {
+ // Darwin does not have pwritev but it does have pwrite.
+ var off: usize = 0;
+ var iov_i: usize = 0;
+ var inner_off: usize = 0;
+ while (true) {
+ const v = iov[iov_i];
+ const rc = darwin.pwrite(fd, v.iov_base + inner_off, v.iov_len - inner_off, offset + off);
+ const err = darwin.getErrno(rc);
+ switch (err) {
+ 0 => {
+ const amt_written = @bitCast(usize, rc);
+ off += amt_written;
+ inner_off += amt_written;
+ if (inner_off == v.iov_len) {
+ iov_i += 1;
+ inner_off = 0;
+ if (iov_i == iov.len) {
+ return;
+ }
+ }
+ continue;
+ },
+ EINTR => continue,
+ ESPIPE => unreachable, // `fd` is not seekable.
+ EINVAL => unreachable,
+ EFAULT => unreachable,
+ EAGAIN => unreachable, // This function is for blocking writes.
+ EBADF => unreachable, // Always a race condition.
+ EDESTADDRREQ => unreachable, // `connect` was never called.
+ EDQUOT => return error.DiskQuota,
+ EFBIG => return error.FileTooBig,
+ EIO => return error.InputOutput,
+ ENOSPC => return error.NoSpaceLeft,
+ EPERM => return error.AccessDenied,
+ EPIPE => return error.BrokenPipe,
+ else => return unexpectedErrno(err),
+ }
+ }
+ }
+
+ while (true) {
+ // TODO handle the case when iov_len is too large and get rid of this @intCast
+ const rc = system.pwritev(fd, iov.ptr, @intCast(u32, iov.len), offset);
+ switch (errno(rc)) {
+ 0 => return,
+ EINTR => continue,
+ EINVAL => unreachable,
+ EFAULT => unreachable,
+ EAGAIN => unreachable, // This function is for blocking writes.
+ EBADF => unreachable, // Always a race condition.
+ EDESTADDRREQ => unreachable, // `connect` was never called.
+ EDQUOT => return error.DiskQuota,
+ EFBIG => return error.FileTooBig,
+ EIO => return error.InputOutput,
+ ENOSPC => return error.NoSpaceLeft,
+ EPERM => return error.AccessDenied,
+ EPIPE => return error.BrokenPipe,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+}
+
+pub const OpenError = error{
+ AccessDenied,
+ FileTooBig,
+ IsDir,
+ SymLinkLoop,
+ ProcessFdQuotaExceeded,
+ NameTooLong,
+ SystemFdQuotaExceeded,
+ NoDevice,
+ FileNotFound,
+ SystemResources,
+ NoSpaceLeft,
+ NotDir,
+ PathAlreadyExists,
+ DeviceBusy,
+ Unexpected,
+};
+
+/// Open and possibly create a file. Keeps trying if it gets interrupted.
+/// See also `openC`.
+pub fn open(file_path: []const u8, flags: u32, perm: usize) OpenError!fd_t {
+ const file_path_c = try toPosixPath(file_path);
+ return openC(&file_path_c, flags, perm);
+}
+
+/// Open and possibly create a file. Keeps trying if it gets interrupted.
+/// See also `open`.
+/// TODO https://github.com/ziglang/zig/issues/265
+pub fn openC(file_path: [*]const u8, flags: u32, perm: usize) OpenError!fd_t {
+ while (true) {
+ const rc = system.open(file_path, flags, perm);
+ switch (errno(rc)) {
+ 0 => return @intCast(fd_t, rc),
+ EINTR => continue,
+
+ EFAULT => unreachable,
+ EINVAL => unreachable,
+ EACCES => return error.AccessDenied,
+ EFBIG => return error.FileTooBig,
+ EOVERFLOW => return error.FileTooBig,
+ EISDIR => return error.IsDir,
+ ELOOP => return error.SymLinkLoop,
+ EMFILE => return error.ProcessFdQuotaExceeded,
+ ENAMETOOLONG => return error.NameTooLong,
+ ENFILE => return error.SystemFdQuotaExceeded,
+ ENODEV => return error.NoDevice,
+ ENOENT => return error.FileNotFound,
+ ENOMEM => return error.SystemResources,
+ ENOSPC => return error.NoSpaceLeft,
+ ENOTDIR => return error.NotDir,
+ EPERM => return error.AccessDenied,
+ EEXIST => return error.PathAlreadyExists,
+ EBUSY => return error.DeviceBusy,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+}
+
+/// Open and possibly create a file. Keeps trying if it gets interrupted.
+/// `file_path` is relative to the open directory handle `dir_fd`.
+/// See also `openatC`.
+pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: usize) OpenError!fd_t {
+ const file_path_c = try toPosixPath(file_path);
+ return openatC(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`.
+/// See also `openat`.
+pub fn openatC(dir_fd: fd_t, file_path: [*]const u8, flags: u32, mode: usize) OpenError!fd_t {
+ while (true) {
+ const rc = system.openat(dir_fd, file_path, flags, mode);
+ switch (errno(rc)) {
+ 0 => return @intCast(fd_t, rc),
+ EINTR => continue,
+
+ EFAULT => unreachable,
+ EINVAL => unreachable,
+ EACCES => return error.AccessDenied,
+ EFBIG => return error.FileTooBig,
+ EOVERFLOW => return error.FileTooBig,
+ EISDIR => return error.IsDir,
+ ELOOP => return error.SymLinkLoop,
+ EMFILE => return error.ProcessFdQuotaExceeded,
+ ENAMETOOLONG => return error.NameTooLong,
+ ENFILE => return error.SystemFdQuotaExceeded,
+ ENODEV => return error.NoDevice,
+ ENOENT => return error.FileNotFound,
+ ENOMEM => return error.SystemResources,
+ ENOSPC => return error.NoSpaceLeft,
+ ENOTDIR => return error.NotDir,
+ EPERM => return error.AccessDenied,
+ EEXIST => return error.PathAlreadyExists,
+ EBUSY => return error.DeviceBusy,
+ 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))) {
+ 0 => return,
+ EBUSY, EINTR => continue,
+ EMFILE => return error.ProcessFdQuotaExceeded,
+ EINVAL => unreachable,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+}
+
+/// This function must allocate memory to add a null terminating bytes on path and each arg.
+/// It must also convert to KEY=VALUE\0 format for environment variables, and include null
+/// pointers after the args and after the environment variables.
+/// `argv[0]` is the executable path.
+/// This function also uses the PATH environment variable to get the full path to the executable.
+/// TODO provide execveC which does not take an allocator
+pub fn execve(allocator: *mem.Allocator, argv_slice: []const []const u8, env_map: *const std.BufMap) !void {
+ const argv_buf = try allocator.alloc(?[*]u8, argv_slice.len + 1);
+ mem.set(?[*]u8, argv_buf, null);
+ defer {
+ for (argv_buf) |arg| {
+ const arg_buf = if (arg) |ptr| mem.toSlice(u8, ptr) else break;
+ allocator.free(arg_buf);
+ }
+ allocator.free(argv_buf);
+ }
+ for (argv_slice) |arg, i| {
+ const arg_buf = try allocator.alloc(u8, arg.len + 1);
+ @memcpy(arg_buf.ptr, arg.ptr, arg.len);
+ arg_buf[arg.len] = 0;
+
+ argv_buf[i] = arg_buf.ptr;
+ }
+ argv_buf[argv_slice.len] = null;
+
+ const envp_buf = try createNullDelimitedEnvMap(allocator, env_map);
+ defer freeNullDelimitedEnvMap(allocator, envp_buf);
+
+ const exe_path = argv_slice[0];
+ if (mem.indexOfScalar(u8, exe_path, '/') != null) {
+ return execveErrnoToErr(errno(system.execve(argv_buf[0].?, argv_buf.ptr, envp_buf.ptr)));
+ }
+
+ const PATH = getenv("PATH") orelse "/usr/local/bin:/bin/:/usr/bin";
+ // PATH.len because it is >= the largest search_path
+ // +1 for the / to join the search path and exe_path
+ // +1 for the null terminating byte
+ const path_buf = try allocator.alloc(u8, PATH.len + exe_path.len + 2);
+ defer allocator.free(path_buf);
+ var it = mem.tokenize(PATH, ":");
+ var seen_eacces = false;
+ var err: usize = undefined;
+ while (it.next()) |search_path| {
+ mem.copy(u8, path_buf, search_path);
+ path_buf[search_path.len] = '/';
+ mem.copy(u8, path_buf[search_path.len + 1 ..], exe_path);
+ path_buf[search_path.len + exe_path.len + 1] = 0;
+ err = errno(system.execve(path_buf.ptr, argv_buf.ptr, envp_buf.ptr));
+ assert(err > 0);
+ if (err == EACCES) {
+ seen_eacces = true;
+ } else if (err != ENOENT) {
+ return execveErrnoToErr(err);
+ }
+ }
+ if (seen_eacces) {
+ err = EACCES;
+ }
+ return execveErrnoToErr(err);
+}
+
+pub fn createNullDelimitedEnvMap(allocator: *mem.Allocator, env_map: *const std.BufMap) ![]?[*]u8 {
+ const envp_count = env_map.count();
+ const envp_buf = try allocator.alloc(?[*]u8, envp_count + 1);
+ mem.set(?[*]u8, envp_buf, null);
+ errdefer freeNullDelimitedEnvMap(allocator, envp_buf);
+ {
+ var it = env_map.iterator();
+ var i: usize = 0;
+ while (it.next()) |pair| : (i += 1) {
+ const env_buf = try allocator.alloc(u8, pair.key.len + pair.value.len + 2);
+ @memcpy(env_buf.ptr, pair.key.ptr, pair.key.len);
+ env_buf[pair.key.len] = '=';
+ @memcpy(env_buf.ptr + pair.key.len + 1, pair.value.ptr, pair.value.len);
+ env_buf[env_buf.len - 1] = 0;
+
+ envp_buf[i] = env_buf.ptr;
+ }
+ assert(i == envp_count);
+ }
+ assert(envp_buf[envp_count] == null);
+ return envp_buf;
+}
+
+pub fn freeNullDelimitedEnvMap(allocator: *mem.Allocator, envp_buf: []?[*]u8) void {
+ for (envp_buf) |env| {
+ const env_buf = if (env) |ptr| ptr[0 .. mem.len(u8, ptr) + 1] else break;
+ allocator.free(env_buf);
+ }
+ allocator.free(envp_buf);
+}
+
+pub const ExecveError = error{
+ SystemResources,
+ AccessDenied,
+ InvalidExe,
+ FileSystem,
+ IsDir,
+ FileNotFound,
+ NotDir,
+ FileBusy,
+ ProcessFdQuotaExceeded,
+ SystemFdQuotaExceeded,
+ NameTooLong,
+
+ Unexpected,
+};
+
+fn execveErrnoToErr(err: usize) ExecveError {
+ assert(err > 0);
+ switch (err) {
+ EFAULT => unreachable,
+ E2BIG => return error.SystemResources,
+ EMFILE => return error.ProcessFdQuotaExceeded,
+ ENAMETOOLONG => return error.NameTooLong,
+ ENFILE => return error.SystemFdQuotaExceeded,
+ ENOMEM => return error.SystemResources,
+ EACCES => return error.AccessDenied,
+ EPERM => return error.AccessDenied,
+ EINVAL => return error.InvalidExe,
+ ENOEXEC => return error.InvalidExe,
+ EIO => return error.FileSystem,
+ ELOOP => return error.FileSystem,
+ EISDIR => return error.IsDir,
+ ENOENT => return error.FileNotFound,
+ ENOTDIR => return error.NotDir,
+ ETXTBSY => return error.FileBusy,
+ else => return unexpectedErrno(err),
+ }
+}
+
+/// Get an environment variable.
+/// See also `getenvC`.
+/// TODO make this go through libc when we have it
+pub fn getenv(key: []const u8) ?[]const u8 {
+ for (environ) |ptr| {
+ var line_i: usize = 0;
+ while (ptr[line_i] != 0 and ptr[line_i] != '=') : (line_i += 1) {}
+ const this_key = ptr[0..line_i];
+ if (!mem.eql(u8, key, this_key)) continue;
+
+ var end_i: usize = line_i;
+ while (ptr[end_i] != 0) : (end_i += 1) {}
+ const this_value = ptr[line_i + 1 .. end_i];
+
+ return this_value;
+ }
+ return null;
+}
+
+/// Get an environment variable with a null-terminated name.
+/// See also `getenv`.
+/// TODO https://github.com/ziglang/zig/issues/265
+pub fn getenvC(key: [*]const u8) ?[]const u8 {
+ if (builtin.link_libc) {
+ const value = system.getenv(key) orelse return null;
+ return mem.toSliceConst(u8, value);
+ }
+ return getenv(mem.toSliceConst(u8, key));
+}
+
+pub const GetCwdError = error{
+ NameTooLong,
+ CurrentWorkingDirectoryUnlinked,
+ Unexpected,
+};
+
+/// The result is a slice of out_buffer, indexed from 0.
+pub fn getcwd(out_buffer: []u8) GetCwdError![]u8 {
+ if (windows.is_the_target) {
+ return windows.GetCurrentDirectory(out_buffer);
+ }
+
+ const err = if (builtin.link_libc) blk: {
+ break :blk if (std.c.getcwd(out_buffer.ptr, out_buffer.len)) |_| 0 else std.c._errno().*;
+ } else blk: {
+ break :blk errno(system.getcwd(out_buffer.ptr, out_buffer.len));
+ };
+ switch (err) {
+ 0 => return mem.toSlice(u8, out_buffer.ptr),
+ EFAULT => unreachable,
+ EINVAL => unreachable,
+ ENOENT => return error.CurrentWorkingDirectoryUnlinked,
+ ERANGE => return error.NameTooLong,
+ else => return unexpectedErrno(@intCast(usize, err)),
+ }
+}
+
+pub const SymLinkError = error{
+ AccessDenied,
+ DiskQuota,
+ PathAlreadyExists,
+ FileSystem,
+ SymLinkLoop,
+ FileNotFound,
+ SystemResources,
+ NoSpaceLeft,
+ ReadOnlyFileSystem,
+ NotDir,
+ NameTooLong,
+ InvalidUtf8,
+ BadPathName,
+ Unexpected,
+};
+
+/// 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.
+/// If `sym_link_path` exists, it will not be overwritten.
+/// See also `symlinkC` and `symlinkW`.
+pub fn symlink(target_path: []const u8, sym_link_path: []const u8) SymLinkError!void {
+ if (windows.is_the_target) {
+ const target_path_w = try windows.sliceToPrefixedFileW(target_path);
+ const sym_link_path_w = try windows.sliceToPrefixedFileW(sym_link_path);
+ return windows.CreateSymbolicLinkW(&sym_link_path_w, &target_path_w, 0);
+ } else {
+ const target_path_c = try toPosixPath(target_path);
+ const sym_link_path_c = try toPosixPath(sym_link_path);
+ return symlinkC(&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 symlinkC(target_path: [*]const u8, sym_link_path: [*]const u8) SymLinkError!void {
+ if (windows.is_the_target) {
+ const target_path_w = try windows.cStrToPrefixedFileW(target_path);
+ const sym_link_path_w = try windows.cStrToPrefixedFileW(sym_link_path);
+ return windows.CreateSymbolicLinkW(&sym_link_path_w, &target_path_w, 0);
+ }
+ switch (errno(system.symlink(target_path, sym_link_path))) {
+ 0 => return,
+ EFAULT => unreachable,
+ EINVAL => unreachable,
+ EACCES => return error.AccessDenied,
+ EPERM => return error.AccessDenied,
+ EDQUOT => return error.DiskQuota,
+ EEXIST => return error.PathAlreadyExists,
+ EIO => return error.FileSystem,
+ ELOOP => return error.SymLinkLoop,
+ ENAMETOOLONG => return error.NameTooLong,
+ ENOENT => return error.FileNotFound,
+ ENOTDIR => return error.NotDir,
+ ENOMEM => return error.SystemResources,
+ ENOSPC => return error.NoSpaceLeft,
+ EROFS => return error.ReadOnlyFileSystem,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+pub fn symlinkat(target_path: []const u8, newdirfd: fd_t, sym_link_path: []const u8) SymLinkError!void {
+ const target_path_c = try toPosixPath(target_path);
+ const sym_link_path_c = try toPosixPath(sym_link_path);
+ return symlinkatC(target_path_c, newdirfd, sym_link_path_c);
+}
+
+pub fn symlinkatC(target_path: [*]const u8, newdirfd: fd_t, sym_link_path: [*]const u8) SymLinkError!void {
+ switch (errno(system.symlinkat(target_path, newdirfd, sym_link_path))) {
+ 0 => return,
+ EFAULT => unreachable,
+ EINVAL => unreachable,
+ EACCES => return error.AccessDenied,
+ EPERM => return error.AccessDenied,
+ EDQUOT => return error.DiskQuota,
+ EEXIST => return error.PathAlreadyExists,
+ EIO => return error.FileSystem,
+ ELOOP => return error.SymLinkLoop,
+ ENAMETOOLONG => return error.NameTooLong,
+ ENOENT => return error.FileNotFound,
+ ENOTDIR => return error.NotDir,
+ ENOMEM => return error.SystemResources,
+ ENOSPC => return error.NoSpaceLeft,
+ EROFS => return error.ReadOnlyFileSystem,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+pub const UnlinkError = error{
+ FileNotFound,
+ AccessDenied,
+ FileBusy,
+ FileSystem,
+ IsDir,
+ SymLinkLoop,
+ NameTooLong,
+ NotDir,
+ SystemResources,
+ ReadOnlyFileSystem,
+ Unexpected,
+
+ /// On Windows, file paths must be valid Unicode.
+ InvalidUtf8,
+
+ /// On Windows, file paths cannot contain these characters:
+ /// '/', '*', '?', '"', '<', '>', '|'
+ BadPathName,
+};
+
+/// Delete a name and possibly the file it refers to.
+/// See also `unlinkC`.
+pub fn unlink(file_path: []const u8) UnlinkError!void {
+ if (windows.is_the_target) {
+ const file_path_w = try windows.sliceToPrefixedFileW(file_path);
+ return windows.DeleteFileW(&file_path_w);
+ } else {
+ const file_path_c = try toPosixPath(file_path);
+ return unlinkC(&file_path_c);
+ }
+}
+
+/// Same as `unlink` except the parameter is a null terminated UTF8-encoded string.
+pub fn unlinkC(file_path: [*]const u8) UnlinkError!void {
+ if (windows.is_the_target) {
+ const file_path_w = try windows.cStrToPrefixedFileW(file_path);
+ return windows.DeleteFileW(&file_path_w);
+ }
+ switch (errno(system.unlink(file_path))) {
+ 0 => return,
+ EACCES => return error.AccessDenied,
+ EPERM => return error.AccessDenied,
+ EBUSY => return error.FileBusy,
+ EFAULT => unreachable,
+ EINVAL => unreachable,
+ EIO => return error.FileSystem,
+ EISDIR => return error.IsDir,
+ ELOOP => return error.SymLinkLoop,
+ ENAMETOOLONG => return error.NameTooLong,
+ ENOENT => return error.FileNotFound,
+ ENOTDIR => return error.NotDir,
+ ENOMEM => return error.SystemResources,
+ EROFS => return error.ReadOnlyFileSystem,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+const RenameError = error{
+ AccessDenied,
+ FileBusy,
+ DiskQuota,
+ IsDir,
+ SymLinkLoop,
+ LinkQuotaExceeded,
+ NameTooLong,
+ FileNotFound,
+ NotDir,
+ SystemResources,
+ NoSpaceLeft,
+ PathAlreadyExists,
+ ReadOnlyFileSystem,
+ RenameAcrossMountPoints,
+ InvalidUtf8,
+ BadPathName,
+ Unexpected,
+};
+
+/// Change the name or location of a file.
+pub fn rename(old_path: []const u8, new_path: []const u8) RenameError!void {
+ if (windows.is_the_target) {
+ const old_path_w = try windows.sliceToPrefixedFileW(old_path);
+ const new_path_w = try windows.sliceToPrefixedFileW(new_path);
+ return renameW(&old_path_w, &new_path_w);
+ } else {
+ const old_path_c = try toPosixPath(old_path);
+ const new_path_c = try toPosixPath(new_path);
+ return renameC(&old_path_c, &new_path_c);
+ }
+}
+
+/// Same as `rename` except the parameters are null-terminated byte arrays.
+pub fn renameC(old_path: [*]const u8, new_path: [*]const u8) RenameError!void {
+ if (windows.is_the_target) {
+ const old_path_w = try windows.cStrToPrefixedFileW(old_path);
+ const new_path_w = try windows.cStrToPrefixedFileW(new_path);
+ return renameW(&old_path_w, &new_path_w);
+ }
+ switch (errno(system.rename(old_path, new_path))) {
+ 0 => return,
+ EACCES => return error.AccessDenied,
+ EPERM => return error.AccessDenied,
+ EBUSY => return error.FileBusy,
+ EDQUOT => return error.DiskQuota,
+ EFAULT => unreachable,
+ EINVAL => unreachable,
+ EISDIR => return error.IsDir,
+ ELOOP => return error.SymLinkLoop,
+ EMLINK => return error.LinkQuotaExceeded,
+ ENAMETOOLONG => return error.NameTooLong,
+ ENOENT => return error.FileNotFound,
+ ENOTDIR => return error.NotDir,
+ ENOMEM => return error.SystemResources,
+ ENOSPC => return error.NoSpaceLeft,
+ EEXIST => return error.PathAlreadyExists,
+ ENOTEMPTY => return error.PathAlreadyExists,
+ EROFS => return error.ReadOnlyFileSystem,
+ EXDEV => return error.RenameAcrossMountPoints,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+/// Same as `rename` except the parameters are null-terminated UTF16LE encoded byte arrays.
+/// Assumes target is Windows.
+pub fn renameW(old_path: [*]const u16, new_path: [*]const u16) RenameError!void {
+ const flags = windows.MOVEFILE_REPLACE_EXISTING | windows.MOVEFILE_WRITE_THROUGH;
+ return windows.MoveFileExW(old_path, new_path, flags);
+}
+
+pub const MakeDirError = error{
+ AccessDenied,
+ DiskQuota,
+ PathAlreadyExists,
+ SymLinkLoop,
+ LinkQuotaExceeded,
+ NameTooLong,
+ FileNotFound,
+ SystemResources,
+ NoSpaceLeft,
+ NotDir,
+ ReadOnlyFileSystem,
+ InvalidUtf8,
+ BadPathName,
+ Unexpected,
+};
+
+/// Create a directory.
+/// `mode` is ignored on Windows.
+pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void {
+ if (windows.is_the_target) {
+ const dir_path_w = try windows.sliceToPrefixedFileW(dir_path);
+ return windows.CreateDirectoryW(&dir_path_w, null);
+ } else {
+ const dir_path_c = try toPosixPath(dir_path);
+ return mkdirC(&dir_path_c, mode);
+ }
+}
+
+/// Same as `mkdir` but the parameter is a null-terminated UTF8-encoded string.
+pub fn mkdirC(dir_path: [*]const u8, mode: u32) MakeDirError!void {
+ if (windows.is_the_target) {
+ const dir_path_w = try windows.cStrToPrefixedFileW(dir_path);
+ return windows.CreateDirectoryW(&dir_path_w, null);
+ }
+ switch (errno(system.mkdir(dir_path, mode))) {
+ 0 => return,
+ EACCES => return error.AccessDenied,
+ EPERM => return error.AccessDenied,
+ EDQUOT => return error.DiskQuota,
+ EEXIST => return error.PathAlreadyExists,
+ EFAULT => unreachable,
+ ELOOP => return error.SymLinkLoop,
+ EMLINK => return error.LinkQuotaExceeded,
+ ENAMETOOLONG => return error.NameTooLong,
+ ENOENT => return error.FileNotFound,
+ ENOMEM => return error.SystemResources,
+ ENOSPC => return error.NoSpaceLeft,
+ ENOTDIR => return error.NotDir,
+ EROFS => return error.ReadOnlyFileSystem,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+pub const DeleteDirError = error{
+ AccessDenied,
+ FileBusy,
+ SymLinkLoop,
+ NameTooLong,
+ FileNotFound,
+ SystemResources,
+ NotDir,
+ DirNotEmpty,
+ ReadOnlyFileSystem,
+ InvalidUtf8,
+ BadPathName,
+ Unexpected,
+};
+
+/// Deletes an empty directory.
+pub fn rmdir(dir_path: []const u8) DeleteDirError!void {
+ if (windows.is_the_target) {
+ const dir_path_w = try windows.sliceToPrefixedFileW(dir_path);
+ return windows.RemoveDirectoryW(&dir_path_w);
+ } else {
+ const dir_path_c = try toPosixPath(dir_path);
+ return rmdirC(&dir_path_c);
+ }
+}
+
+/// Same as `rmdir` except the parameter is null-terminated.
+pub fn rmdirC(dir_path: [*]const u8) DeleteDirError!void {
+ if (windows.is_the_target) {
+ const dir_path_w = try windows.cStrToPrefixedFileW(dir_path);
+ return windows.RemoveDirectoryW(&dir_path_w);
+ }
+ switch (errno(system.rmdir(dir_path))) {
+ 0 => return,
+ EACCES => return error.AccessDenied,
+ EPERM => return error.AccessDenied,
+ EBUSY => return error.FileBusy,
+ EFAULT => unreachable,
+ EINVAL => unreachable,
+ ELOOP => return error.SymLinkLoop,
+ ENAMETOOLONG => return error.NameTooLong,
+ ENOENT => return error.FileNotFound,
+ ENOMEM => return error.SystemResources,
+ ENOTDIR => return error.NotDir,
+ EEXIST => return error.DirNotEmpty,
+ ENOTEMPTY => return error.DirNotEmpty,
+ EROFS => return error.ReadOnlyFileSystem,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+pub const ChangeCurDirError = error{
+ AccessDenied,
+ FileSystem,
+ SymLinkLoop,
+ NameTooLong,
+ FileNotFound,
+ SystemResources,
+ NotDir,
+ Unexpected,
+};
+
+/// Changes the current working directory of the calling process.
+/// `dir_path` is recommended to be a UTF-8 encoded string.
+pub fn chdir(dir_path: []const u8) ChangeCurDirError!void {
+ if (windows.is_the_target) {
+ const dir_path_w = try windows.sliceToPrefixedFileW(dir_path);
+ @compileError("TODO implement chdir for Windows");
+ } else {
+ const dir_path_c = try toPosixPath(dir_path);
+ return chdirC(&dir_path_c);
+ }
+}
+
+/// Same as `chdir` except the parameter is null-terminated.
+pub fn chdirC(dir_path: [*]const u8) ChangeCurDirError!void {
+ if (windows.is_the_target) {
+ const dir_path_w = try windows.cStrToPrefixedFileW(dir_path);
+ @compileError("TODO implement chdir for Windows");
+ }
+ switch (errno(system.chdir(dir_path))) {
+ 0 => return,
+ EACCES => return error.AccessDenied,
+ EFAULT => unreachable,
+ EIO => return error.FileSystem,
+ ELOOP => return error.SymLinkLoop,
+ ENAMETOOLONG => return error.NameTooLong,
+ ENOENT => return error.FileNotFound,
+ ENOMEM => return error.SystemResources,
+ ENOTDIR => return error.NotDir,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+pub const ReadLinkError = error{
+ AccessDenied,
+ FileSystem,
+ SymLinkLoop,
+ NameTooLong,
+ FileNotFound,
+ SystemResources,
+ NotDir,
+ Unexpected,
+};
+
+/// Read value of a symbolic link.
+/// The return value is a slice of `out_buffer` from index 0.
+pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
+ if (windows.is_the_target) {
+ const file_path_w = try windows.sliceToPrefixedFileW(file_path);
+ @compileError("TODO implement readlink for Windows");
+ } else {
+ const file_path_c = try toPosixPath(file_path);
+ return readlinkC(&file_path_c, out_buffer);
+ }
+}
+
+/// Same as `readlink` except `file_path` is null-terminated.
+pub fn readlinkC(file_path: [*]const u8, out_buffer: []u8) ReadLinkError![]u8 {
+ if (windows.is_the_target) {
+ const file_path_w = try windows.cStrToPrefixedFileW(file_path);
+ @compileError("TODO implement readlink for Windows");
+ }
+ const rc = system.readlink(file_path, out_buffer.ptr, out_buffer.len);
+ switch (errno(rc)) {
+ 0 => return out_buffer[0..@bitCast(usize, rc)],
+ EACCES => return error.AccessDenied,
+ EFAULT => unreachable,
+ EINVAL => unreachable,
+ EIO => return error.FileSystem,
+ ELOOP => return error.SymLinkLoop,
+ ENAMETOOLONG => return error.NameTooLong,
+ ENOENT => return error.FileNotFound,
+ ENOMEM => return error.SystemResources,
+ ENOTDIR => return error.NotDir,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+pub const SetIdError = error{
+ ResourceLimitReached,
+ InvalidUserId,
+ PermissionDenied,
+ Unexpected,
+};
+
+pub fn setuid(uid: u32) SetIdError!void {
+ switch (errno(system.setuid(uid))) {
+ 0 => return,
+ EAGAIN => return error.ResourceLimitReached,
+ EINVAL => return error.InvalidUserId,
+ EPERM => return error.PermissionDenied,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+pub fn setreuid(ruid: u32, euid: u32) SetIdError!void {
+ switch (errno(system.setreuid(ruid, euid))) {
+ 0 => return,
+ EAGAIN => return error.ResourceLimitReached,
+ EINVAL => return error.InvalidUserId,
+ EPERM => return error.PermissionDenied,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+pub fn setgid(gid: u32) SetIdError!void {
+ switch (errno(system.setgid(gid))) {
+ 0 => return,
+ EAGAIN => return error.ResourceLimitReached,
+ EINVAL => return error.InvalidUserId,
+ EPERM => return error.PermissionDenied,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+pub fn setregid(rgid: u32, egid: u32) SetIdError!void {
+ switch (errno(system.setregid(rgid, egid))) {
+ 0 => return,
+ EAGAIN => return error.ResourceLimitReached,
+ EINVAL => return error.InvalidUserId,
+ EPERM => return error.PermissionDenied,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+/// Test whether a file descriptor refers to a terminal.
+pub fn isatty(handle: fd_t) bool {
+ if (windows.is_the_target) {
+ if (isCygwinPty(handle))
+ return true;
+
+ var out: windows.DWORD = undefined;
+ return windows.kernel32.GetConsoleMode(handle, &out) != 0;
+ }
+ if (builtin.link_libc) {
+ return system.isatty(handle) != 0;
+ }
+ if (wasi.is_the_target) {
+ @compileError("TODO implement std.os.isatty for WASI");
+ }
+ if (linux.is_the_target) {
+ var wsz: linux.winsize = undefined;
+ return linux.syscall3(linux.SYS_ioctl, @bitCast(usize, isize(handle)), linux.TIOCGWINSZ, @ptrToInt(&wsz)) == 0;
+ }
+ unreachable;
+}
+
+pub fn isCygwinPty(handle: fd_t) bool {
+ if (!windows.is_the_target) return false;
+
+ const size = @sizeOf(windows.FILE_NAME_INFO);
+ var name_info_bytes align(@alignOf(windows.FILE_NAME_INFO)) = [_]u8{0} ** (size + windows.MAX_PATH);
+
+ if (windows.kernel32.GetFileInformationByHandleEx(
+ handle,
+ windows.FileNameInfo,
+ @ptrCast(*c_void, &name_info_bytes),
+ name_info_bytes.len,
+ ) == 0) {
+ return false;
+ }
+
+ const name_info = @ptrCast(*const windows.FILE_NAME_INFO, &name_info_bytes[0]);
+ const name_bytes = name_info_bytes[size .. size + usize(name_info.FileNameLength)];
+ const name_wide = @bytesToSlice(u16, name_bytes);
+ return mem.indexOf(u16, name_wide, [_]u16{ 'm', 's', 'y', 's', '-' }) != null or
+ mem.indexOf(u16, name_wide, [_]u16{ '-', 'p', 't', 'y' }) != null;
+}
+
+pub const SocketError = error{
+ /// Permission to create a socket of the specified type and/or
+ /// pro‐tocol is denied.
+ PermissionDenied,
+
+ /// The implementation does not support the specified address family.
+ AddressFamilyNotSupported,
+
+ /// Unknown protocol, or protocol family not available.
+ ProtocolFamilyNotAvailable,
+
+ /// The per-process limit on the number of open file descriptors has been reached.
+ ProcessFdQuotaExceeded,
+
+ /// The system-wide limit on the total number of open files has been reached.
+ SystemFdQuotaExceeded,
+
+ /// Insufficient memory is available. The socket cannot be created until sufficient
+ /// resources are freed.
+ SystemResources,
+
+ /// The protocol type or the specified protocol is not supported within this domain.
+ ProtocolNotSupported,
+
+ Unexpected,
+};
+
+pub fn socket(domain: u32, socket_type: u32, protocol: u32) SocketError!i32 {
+ const rc = system.socket(domain, socket_type, protocol);
+ switch (errno(rc)) {
+ 0 => return @intCast(i32, rc),
+ EACCES => return error.PermissionDenied,
+ EAFNOSUPPORT => return error.AddressFamilyNotSupported,
+ EINVAL => return error.ProtocolFamilyNotAvailable,
+ EMFILE => return error.ProcessFdQuotaExceeded,
+ ENFILE => return error.SystemFdQuotaExceeded,
+ ENOBUFS, ENOMEM => return error.SystemResources,
+ EPROTONOSUPPORT => return error.ProtocolNotSupported,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+pub const BindError = error{
+ /// The address is protected, and the user is not the superuser.
+ /// For UNIX domain sockets: Search permission is denied on a component
+ /// of the path prefix.
+ AccessDenied,
+
+ /// The given address is already in use, or in the case of Internet domain sockets,
+ /// The port number was specified as zero in the socket
+ /// address structure, but, upon attempting to bind to an ephemeral port, it was
+ /// determined that all port numbers in the ephemeral port range are currently in
+ /// use. See the discussion of /proc/sys/net/ipv4/ip_local_port_range ip(7).
+ AddressInUse,
+
+ /// A nonexistent interface was requested or the requested address was not local.
+ AddressNotAvailable,
+
+ /// Too many symbolic links were encountered in resolving addr.
+ SymLinkLoop,
+
+ /// addr is too long.
+ NameTooLong,
+
+ /// A component in the directory prefix of the socket pathname does not exist.
+ FileNotFound,
+
+ /// Insufficient kernel memory was available.
+ SystemResources,
+
+ /// A component of the path prefix is not a directory.
+ NotDir,
+
+ /// The socket inode would reside on a read-only filesystem.
+ ReadOnlyFileSystem,
+
+ Unexpected,
+};
+
+/// addr is `*const T` where T is one of the sockaddr
+pub fn bind(fd: i32, addr: *const sockaddr) BindError!void {
+ const rc = system.bind(fd, addr, @sizeOf(sockaddr));
+ switch (errno(rc)) {
+ 0 => return,
+ EACCES => return error.AccessDenied,
+ EADDRINUSE => return error.AddressInUse,
+ EBADF => unreachable, // always a race condition if this error is returned
+ EINVAL => unreachable,
+ ENOTSOCK => unreachable,
+ EADDRNOTAVAIL => return error.AddressNotAvailable,
+ EFAULT => unreachable,
+ ELOOP => return error.SymLinkLoop,
+ ENAMETOOLONG => return error.NameTooLong,
+ ENOENT => return error.FileNotFound,
+ ENOMEM => return error.SystemResources,
+ ENOTDIR => return error.NotDir,
+ EROFS => return error.ReadOnlyFileSystem,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+const ListenError = error{
+ /// Another socket is already listening on the same port.
+ /// For Internet domain sockets, the socket referred to by sockfd had not previously
+ /// been bound to an address and, upon attempting to bind it to an ephemeral port, it
+ /// was determined that all port numbers in the ephemeral port range are currently in
+ /// use. See the discussion of /proc/sys/net/ipv4/ip_local_port_range in ip(7).
+ AddressInUse,
+
+ /// The file descriptor sockfd does not refer to a socket.
+ FileDescriptorNotASocket,
+
+ /// The socket is not of a type that supports the listen() operation.
+ OperationNotSupported,
+
+ Unexpected,
+};
+
+pub fn listen(sockfd: i32, backlog: u32) ListenError!void {
+ const rc = system.listen(sockfd, backlog);
+ switch (errno(rc)) {
+ 0 => return,
+ EADDRINUSE => return error.AddressInUse,
+ EBADF => unreachable,
+ ENOTSOCK => return error.FileDescriptorNotASocket,
+ EOPNOTSUPP => return error.OperationNotSupported,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+pub const AcceptError = error{
+ ConnectionAborted,
+
+ /// The per-process limit on the number of open file descriptors has been reached.
+ ProcessFdQuotaExceeded,
+
+ /// The system-wide limit on the total number of open files has been reached.
+ SystemFdQuotaExceeded,
+
+ /// Not enough free memory. This often means that the memory allocation is limited
+ /// by the socket buffer limits, not by the system memory.
+ SystemResources,
+
+ /// The file descriptor sockfd does not refer to a socket.
+ FileDescriptorNotASocket,
+
+ /// The referenced socket is not of type SOCK_STREAM.
+ OperationNotSupported,
+
+ ProtocolFailure,
+
+ /// Firewall rules forbid connection.
+ BlockedByFirewall,
+
+ Unexpected,
+};
+
+/// Accept a connection on a socket. `fd` must be opened in blocking mode.
+/// See also `accept4_async`.
+pub fn accept4(fd: i32, addr: *sockaddr, flags: u32) AcceptError!i32 {
+ while (true) {
+ var sockaddr_size = u32(@sizeOf(sockaddr));
+ const rc = system.accept4(fd, addr, &sockaddr_size, flags);
+ switch (errno(rc)) {
+ 0 => return @intCast(i32, rc),
+ EINTR => continue,
+ else => |err| return unexpectedErrno(err),
+
+ EAGAIN => unreachable, // This function is for blocking only.
+ EBADF => unreachable, // always a race condition
+ ECONNABORTED => return error.ConnectionAborted,
+ EFAULT => unreachable,
+ EINVAL => unreachable,
+ EMFILE => return error.ProcessFdQuotaExceeded,
+ ENFILE => return error.SystemFdQuotaExceeded,
+ ENOBUFS => return error.SystemResources,
+ ENOMEM => return error.SystemResources,
+ ENOTSOCK => return error.FileDescriptorNotASocket,
+ EOPNOTSUPP => return error.OperationNotSupported,
+ EPROTO => return error.ProtocolFailure,
+ EPERM => return error.BlockedByFirewall,
+ }
+ }
+}
+
+/// This is the same as `accept4` except `fd` is expected to be non-blocking.
+/// Returns -1 if would block.
+pub fn accept4_async(fd: i32, addr: *sockaddr, flags: u32) AcceptError!i32 {
+ while (true) {
+ var sockaddr_size = u32(@sizeOf(sockaddr));
+ const rc = system.accept4(fd, addr, &sockaddr_size, flags);
+ switch (errno(rc)) {
+ 0 => return @intCast(i32, rc),
+ EINTR => continue,
+ else => |err| return unexpectedErrno(err),
+
+ EAGAIN => return -1,
+ EBADF => unreachable, // always a race condition
+ ECONNABORTED => return error.ConnectionAborted,
+ EFAULT => unreachable,
+ EINVAL => unreachable,
+ EMFILE => return error.ProcessFdQuotaExceeded,
+ ENFILE => return error.SystemFdQuotaExceeded,
+ ENOBUFS => return error.SystemResources,
+ ENOMEM => return error.SystemResources,
+ ENOTSOCK => return error.FileDescriptorNotASocket,
+ EOPNOTSUPP => return error.OperationNotSupported,
+ EPROTO => return error.ProtocolFailure,
+ EPERM => return error.BlockedByFirewall,
+ }
+ }
+}
+
+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,
+
+ Unexpected,
+};
+
+pub fn epoll_create1(flags: u32) EpollCreateError!i32 {
+ const rc = system.epoll_create1(flags);
+ switch (errno(rc)) {
+ 0 => return @intCast(i32, rc),
+ else => |err| return unexpectedErrno(err),
+
+ EINVAL => unreachable,
+ EMFILE => return error.ProcessFdQuotaExceeded,
+ ENFILE => return error.SystemFdQuotaExceeded,
+ ENOMEM => 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,
+
+ Unexpected,
+};
+
+pub fn epoll_ctl(epfd: i32, op: u32, fd: i32, event: ?*epoll_event) EpollCtlError!void {
+ const rc = system.epoll_ctl(epfd, op, fd, event);
+ switch (errno(rc)) {
+ 0 => return,
+ else => |err| return unexpectedErrno(err),
+
+ EBADF => unreachable, // always a race condition if this happens
+ EEXIST => return error.FileDescriptorAlreadyPresentInSet,
+ EINVAL => unreachable,
+ ELOOP => return error.OperationCausesCircularLoop,
+ ENOENT => return error.FileDescriptorNotRegistered,
+ ENOMEM => return error.SystemResources,
+ ENOSPC => return error.UserResourceLimitReached,
+ EPERM => 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: []epoll_event, timeout: i32) usize {
+ while (true) {
+ // TODO get rid of the @intCast
+ const rc = system.epoll_wait(epfd, events.ptr, @intCast(u32, events.len), timeout);
+ switch (errno(rc)) {
+ 0 => return @intCast(usize, rc),
+ EINTR => continue,
+ EBADF => unreachable,
+ EFAULT => unreachable,
+ EINVAL => unreachable,
+ else => unreachable,
+ }
+ }
+}
+
+pub const EventFdError = error{
+ SystemResources,
+ ProcessFdQuotaExceeded,
+ SystemFdQuotaExceeded,
+ Unexpected,
+};
+
+pub fn eventfd(initval: u32, flags: u32) EventFdError!i32 {
+ const rc = system.eventfd(initval, flags);
+ switch (errno(rc)) {
+ 0 => return @intCast(i32, rc),
+ else => |err| return unexpectedErrno(err),
+
+ EINVAL => unreachable, // invalid parameters
+ EMFILE => return error.ProcessFdQuotaExceeded,
+ ENFILE => return error.SystemFdQuotaExceeded,
+ ENODEV => return error.SystemResources,
+ ENOMEM => return error.SystemResources,
+ }
+}
+
+pub const GetSockNameError = error{
+ /// Insufficient resources were available in the system to perform the operation.
+ SystemResources,
+
+ Unexpected,
+};
+
+pub fn getsockname(sockfd: i32) GetSockNameError!sockaddr {
+ var addr: sockaddr = undefined;
+ var addrlen: socklen_t = @sizeOf(sockaddr);
+ switch (errno(system.getsockname(sockfd, &addr, &addrlen))) {
+ 0 => return addr,
+ else => |err| return unexpectedErrno(err),
+
+ EBADF => unreachable, // always a race condition
+ EFAULT => unreachable,
+ EINVAL => unreachable, // invalid parameters
+ ENOTSOCK => unreachable,
+ ENOBUFS => return error.SystemResources,
+ }
+}
+
+pub const ConnectError = error{
+ /// For UNIX domain sockets, which are identified by pathname: Write permission is denied on the socket
+ /// file, or search permission is denied for one of the directories in the path prefix.
+ /// or
+ /// The user tried to connect to a broadcast address without having the socket broadcast flag enabled or
+ /// the connection request failed because of a local firewall rule.
+ PermissionDenied,
+
+ /// Local address is already in use.
+ AddressInUse,
+
+ /// (Internet domain sockets) The socket referred to by sockfd had not previously been bound to an
+ /// address and, upon attempting to bind it to an ephemeral port, it was determined that all port numbers
+ /// in the ephemeral port range are currently in use. See the discussion of
+ /// /proc/sys/net/ipv4/ip_local_port_range in ip(7).
+ AddressNotAvailable,
+
+ /// The passed address didn't have the correct address family in its sa_family field.
+ AddressFamilyNotSupported,
+
+ /// Insufficient entries in the routing cache.
+ SystemResources,
+
+ /// A connect() on a stream socket found no one listening on the remote address.
+ ConnectionRefused,
+
+ /// Network is unreachable.
+ NetworkUnreachable,
+
+ /// Timeout while attempting connection. The server may be too busy to accept new connections. Note
+ /// that for IP sockets the timeout may be very long when syncookies are enabled on the server.
+ ConnectionTimedOut,
+
+ Unexpected,
+};
+
+/// Initiate a connection on a socket.
+/// This is for blocking file descriptors only.
+/// For non-blocking, see `connect_async`.
+pub fn connect(sockfd: i32, sock_addr: *sockaddr, len: socklen_t) ConnectError!void {
+ while (true) {
+ switch (errno(system.connect(sockfd, sock_addr, len))) {
+ 0 => return,
+ EACCES => return error.PermissionDenied,
+ EPERM => return error.PermissionDenied,
+ EADDRINUSE => return error.AddressInUse,
+ EADDRNOTAVAIL => return error.AddressNotAvailable,
+ EAFNOSUPPORT => return error.AddressFamilyNotSupported,
+ EAGAIN => return error.SystemResources,
+ EALREADY => unreachable, // The socket is nonblocking and a previous connection attempt has not yet been completed.
+ EBADF => unreachable, // sockfd is not a valid open file descriptor.
+ ECONNREFUSED => return error.ConnectionRefused,
+ EFAULT => unreachable, // The socket structure address is outside the user's address space.
+ EINPROGRESS => unreachable, // The socket is nonblocking and the connection cannot be completed immediately.
+ EINTR => continue,
+ EISCONN => unreachable, // The socket is already connected.
+ ENETUNREACH => return error.NetworkUnreachable,
+ ENOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.
+ EPROTOTYPE => unreachable, // The socket type does not support the requested communications protocol.
+ ETIMEDOUT => return error.ConnectionTimedOut,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+}
+
+/// Same as `connect` except it is for blocking socket file descriptors.
+/// It expects to receive EINPROGRESS`.
+pub fn connect_async(sockfd: i32, sock_addr: *sockaddr, len: socklen_t) ConnectError!void {
+ while (true) {
+ switch (errno(system.connect(sockfd, sock_addr, len))) {
+ EINVAL => unreachable,
+ EINTR => continue,
+ 0, EINPROGRESS => return,
+ EACCES => return error.PermissionDenied,
+ EPERM => return error.PermissionDenied,
+ EADDRINUSE => return error.AddressInUse,
+ EADDRNOTAVAIL => return error.AddressNotAvailable,
+ EAFNOSUPPORT => return error.AddressFamilyNotSupported,
+ EAGAIN => return error.SystemResources,
+ EALREADY => unreachable, // The socket is nonblocking and a previous connection attempt has not yet been completed.
+ EBADF => unreachable, // sockfd is not a valid open file descriptor.
+ ECONNREFUSED => return error.ConnectionRefused,
+ EFAULT => unreachable, // The socket structure address is outside the user's address space.
+ EISCONN => unreachable, // The socket is already connected.
+ ENETUNREACH => return error.NetworkUnreachable,
+ ENOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.
+ EPROTOTYPE => unreachable, // The socket type does not support the requested communications protocol.
+ ETIMEDOUT => return error.ConnectionTimedOut,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+}
+
+pub fn getsockoptError(sockfd: i32) ConnectError!void {
+ var err_code: u32 = undefined;
+ var size: u32 = @sizeOf(u32);
+ const rc = system.getsockopt(sockfd, SOL_SOCKET, SO_ERROR, @ptrCast([*]u8, &err_code), &size);
+ assert(size == 4);
+ switch (errno(rc)) {
+ 0 => switch (err_code) {
+ 0 => return,
+ EACCES => return error.PermissionDenied,
+ EPERM => return error.PermissionDenied,
+ EADDRINUSE => return error.AddressInUse,
+ EADDRNOTAVAIL => return error.AddressNotAvailable,
+ EAFNOSUPPORT => return error.AddressFamilyNotSupported,
+ EAGAIN => return error.SystemResources,
+ EALREADY => unreachable, // The socket is nonblocking and a previous connection attempt has not yet been completed.
+ EBADF => unreachable, // sockfd is not a valid open file descriptor.
+ ECONNREFUSED => return error.ConnectionRefused,
+ EFAULT => unreachable, // The socket structure address is outside the user's address space.
+ EISCONN => unreachable, // The socket is already connected.
+ ENETUNREACH => return error.NetworkUnreachable,
+ ENOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.
+ EPROTOTYPE => unreachable, // The socket type does not support the requested communications protocol.
+ ETIMEDOUT => return error.ConnectionTimedOut,
+ else => |err| return unexpectedErrno(err),
+ },
+ EBADF => unreachable, // The argument sockfd is not a valid file descriptor.
+ EFAULT => unreachable, // The address pointed to by optval or optlen is not in a valid part of the process address space.
+ EINVAL => unreachable,
+ ENOPROTOOPT => unreachable, // The option is unknown at the level indicated.
+ ENOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+pub fn waitpid(pid: i32, flags: u32) u32 {
+ // TODO allow implicit pointer cast from *u32 to *c_uint ?
+ const Status = if (builtin.link_libc) c_uint else u32;
+ var status: Status = undefined;
+ while (true) {
+ switch (errno(system.waitpid(pid, &status, flags))) {
+ 0 => return @bitCast(u32, status),
+ EINTR => continue,
+ ECHILD => unreachable, // The process specified does not exist. It would be a race condition to handle this error.
+ EINVAL => unreachable, // The options argument was invalid
+ else => unreachable,
+ }
+ }
+}
+
+pub const FStatError = error{
+ SystemResources,
+ Unexpected,
+};
+
+pub fn fstat(fd: fd_t) FStatError!Stat {
+ var stat: Stat = undefined;
+ if (darwin.is_the_target) {
+ switch (darwin.getErrno(darwin.@"fstat$INODE64"(fd, &stat))) {
+ 0 => return stat,
+ EBADF => unreachable, // Always a race condition.
+ ENOMEM => return error.SystemResources,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+
+ switch (errno(system.fstat(fd, &stat))) {
+ 0 => return stat,
+ EBADF => unreachable, // Always a race condition.
+ ENOMEM => return error.SystemResources,
+ 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,
+
+ Unexpected,
+};
+
+pub fn kqueue() KQueueError!i32 {
+ const rc = system.kqueue();
+ switch (errno(rc)) {
+ 0 => return @intCast(i32, rc),
+ EMFILE => return error.ProcessFdQuotaExceeded,
+ ENFILE => 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,
+ try math.cast(c_int, changelist.len),
+ eventlist.ptr,
+ try math.cast(c_int, eventlist.len),
+ timeout,
+ );
+ switch (errno(rc)) {
+ 0 => return @intCast(usize, rc),
+ EACCES => return error.AccessDenied,
+ EFAULT => unreachable,
+ EBADF => unreachable, // Always a race condition.
+ EINTR => continue,
+ EINVAL => unreachable,
+ ENOENT => return error.EventNotFound,
+ ENOMEM => return error.SystemResources,
+ ESRCH => return error.ProcessNotFound,
+ else => unreachable,
+ }
+ }
+}
+
+pub const INotifyInitError = error{
+ ProcessFdQuotaExceeded,
+ SystemFdQuotaExceeded,
+ SystemResources,
+ Unexpected,
+};
+
+/// initialize an inotify instance
+pub fn inotify_init1(flags: u32) INotifyInitError!i32 {
+ const rc = system.inotify_init1(flags);
+ switch (errno(rc)) {
+ 0 => return @intCast(i32, rc),
+ EINVAL => unreachable,
+ EMFILE => return error.ProcessFdQuotaExceeded,
+ ENFILE => return error.SystemFdQuotaExceeded,
+ ENOMEM => return error.SystemResources,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+pub const INotifyAddWatchError = error{
+ AccessDenied,
+ NameTooLong,
+ FileNotFound,
+ SystemResources,
+ UserResourceLimitReached,
+ Unexpected,
+};
+
+/// 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_watchC(inotify_fd, &pathname_c, mask);
+}
+
+/// Same as `inotify_add_watch` except pathname is null-terminated.
+pub fn inotify_add_watchC(inotify_fd: i32, pathname: [*]const u8, mask: u32) INotifyAddWatchError!i32 {
+ const rc = system.inotify_add_watch(inotify_fd, pathname, mask);
+ switch (errno(rc)) {
+ 0 => return @intCast(i32, rc),
+ EACCES => return error.AccessDenied,
+ EBADF => unreachable,
+ EFAULT => unreachable,
+ EINVAL => unreachable,
+ ENAMETOOLONG => return error.NameTooLong,
+ ENOENT => return error.FileNotFound,
+ ENOMEM => return error.SystemResources,
+ ENOSPC => return error.UserResourceLimitReached,
+ 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))) {
+ 0 => return,
+ EBADF => unreachable,
+ EINVAL => unreachable,
+ else => unreachable,
+ }
+}
+
+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,
+ Unexpected,
+};
+
+/// `memory.len` must be page-aligned.
+pub fn mprotect(memory: []align(mem.page_size) u8, protection: u32) MProtectError!void {
+ assert(mem.isAligned(memory.len, mem.page_size));
+ switch (errno(system.mprotect(memory.ptr, memory.len, protection))) {
+ 0 => return,
+ EINVAL => unreachable,
+ EACCES => return error.AccessDenied,
+ ENOMEM => return error.OutOfMemory,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+pub const ForkError = error{
+ SystemResources,
+ Unexpected,
+};
+
+pub fn fork() ForkError!pid_t {
+ const rc = system.fork();
+ switch (errno(rc)) {
+ 0 => return @intCast(pid_t, rc),
+ EAGAIN => return error.SystemResources,
+ ENOMEM => return error.SystemResources,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+pub const MMapError = error{
+ /// The underlying filesystem of the specified file does not support memory mapping.
+ MemoryMappingNotSupported,
+
+ /// A file descriptor refers to a non-regular file. Or a file mapping was requested,
+ /// but the file descriptor is not open for reading. Or `MAP_SHARED` was requested
+ /// and `PROT_WRITE` is set, but the file descriptor is not open in `O_RDWR` mode.
+ /// Or `PROT_WRITE` is set, but the file is append-only.
+ AccessDenied,
+
+ /// The `prot` argument asks for `PROT_EXEC` but the mapped area belongs to a file on
+ /// a filesystem that was mounted no-exec.
+ PermissionDenied,
+ LockedMemoryLimitExceeded,
+ OutOfMemory,
+ Unexpected,
+};
+
+/// Map files or devices into memory.
+/// Use of a mapped region can result in these signals:
+/// * SIGSEGV - Attempted write into a region mapped as read-only.
+/// * SIGBUS - Attempted access to a portion of the buffer that does not correspond to the file
+pub fn mmap(
+ ptr: ?[*]align(mem.page_size) u8,
+ length: usize,
+ prot: u32,
+ flags: u32,
+ fd: fd_t,
+ offset: u64,
+) MMapError![]align(mem.page_size) u8 {
+ const err = if (builtin.link_libc) blk: {
+ const rc = std.c.mmap(ptr, length, prot, flags, fd, offset);
+ if (rc != std.c.MAP_FAILED) return @ptrCast([*]align(mem.page_size) u8, @alignCast(mem.page_size, rc))[0..length];
+ break :blk @intCast(usize, system._errno().*);
+ } else blk: {
+ const rc = system.mmap(ptr, length, prot, flags, fd, offset);
+ const err = errno(rc);
+ if (err == 0) return @intToPtr([*]align(mem.page_size) u8, rc)[0..length];
+ break :blk err;
+ };
+ switch (err) {
+ ETXTBSY => return error.AccessDenied,
+ EACCES => return error.AccessDenied,
+ EPERM => return error.PermissionDenied,
+ EAGAIN => return error.LockedMemoryLimitExceeded,
+ EBADF => unreachable, // Always a race condition.
+ EOVERFLOW => unreachable, // The number of pages used for length + offset would overflow.
+ ENODEV => return error.MemoryMappingNotSupported,
+ EINVAL => unreachable, // Invalid parameters to mmap()
+ ENOMEM => return error.OutOfMemory,
+ else => return unexpectedErrno(err),
+ }
+}
+
+/// Deletes the mappings for the specified address range, causing
+/// further references to addresses within the range to generate invalid memory references.
+/// Note that while POSIX allows unmapping a region in the middle of an existing mapping,
+/// Zig's munmap function does not, for two reasons:
+/// * It violates the Zig principle that resource deallocation must succeed.
+/// * The Windows function, VirtualFree, has this restriction.
+pub fn munmap(memory: []align(mem.page_size) u8) void {
+ switch (errno(system.munmap(memory.ptr, memory.len))) {
+ 0 => return,
+ EINVAL => unreachable, // Invalid parameters.
+ ENOMEM => unreachable, // Attempted to unmap a region in the middle of an existing mapping.
+ else => unreachable,
+ }
+}
+
+pub const AccessError = error{
+ PermissionDenied,
+ FileNotFound,
+ NameTooLong,
+ InputOutput,
+ SystemResources,
+ BadPathName,
+
+ /// On Windows, file paths must be valid Unicode.
+ InvalidUtf8,
+
+ Unexpected,
+};
+
+/// check user's permissions for a file
+/// TODO currently this assumes `mode` is `F_OK` on Windows.
+pub fn access(path: []const u8, mode: u32) AccessError!void {
+ if (windows.is_the_target) {
+ const path_w = try windows.sliceToPrefixedFileW(path);
+ _ = try windows.GetFileAttributesW(&path_w);
+ return;
+ }
+ const path_c = try toPosixPath(path);
+ return accessC(&path_c, mode);
+}
+
+/// Same as `access` except `path` is null-terminated.
+pub fn accessC(path: [*]const u8, mode: u32) AccessError!void {
+ if (windows.is_the_target) {
+ const path_w = try windows.cStrToPrefixedFileW(path);
+ _ = try windows.GetFileAttributesW(&path_w);
+ return;
+ }
+ switch (errno(system.access(path, mode))) {
+ 0 => return,
+ EACCES => return error.PermissionDenied,
+ EROFS => return error.PermissionDenied,
+ ELOOP => return error.PermissionDenied,
+ ETXTBSY => return error.PermissionDenied,
+ ENOTDIR => return error.FileNotFound,
+ ENOENT => return error.FileNotFound,
+
+ ENAMETOOLONG => return error.NameTooLong,
+ EINVAL => unreachable,
+ EFAULT => unreachable,
+ EIO => return error.InputOutput,
+ ENOMEM => return error.SystemResources,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+/// Call from Windows-specific code if you already have a UTF-16LE encoded, null terminated string.
+/// Otherwise use `access` or `accessC`.
+/// TODO currently this ignores `mode`.
+pub fn accessW(path: [*]const u16, mode: u32) windows.GetFileAttributesError!void {
+ const ret = try windows.GetFileAttributesW(path);
+ if (ret != windows.INVALID_FILE_ATTRIBUTES) {
+ return;
+ }
+ switch (windows.kernel32.GetLastError()) {
+ windows.ERROR.FILE_NOT_FOUND => return error.FileNotFound,
+ windows.ERROR.PATH_NOT_FOUND => return error.FileNotFound,
+ windows.ERROR.ACCESS_DENIED => return error.PermissionDenied,
+ else => |err| return windows.unexpectedError(err),
+ }
+}
+
+pub const PipeError = error{
+ SystemFdQuotaExceeded,
+ ProcessFdQuotaExceeded,
+ Unexpected,
+};
+
+/// 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))) {
+ 0 => return fds,
+ EINVAL => unreachable, // Invalid parameters to pipe()
+ EFAULT => unreachable, // Invalid fds pointer
+ ENFILE => return error.SystemFdQuotaExceeded,
+ EMFILE => return error.ProcessFdQuotaExceeded,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+pub fn pipe2(flags: u32) PipeError![2]fd_t {
+ var fds: [2]fd_t = undefined;
+ switch (errno(system.pipe2(&fds, flags))) {
+ 0 => return fds,
+ EINVAL => unreachable, // Invalid flags
+ EFAULT => unreachable, // Invalid fds pointer
+ ENFILE => return error.SystemFdQuotaExceeded,
+ EMFILE => return error.ProcessFdQuotaExceeded,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+pub const SysCtlError = error{
+ PermissionDenied,
+ SystemResources,
+ NameTooLong,
+ Unexpected,
+};
+
+pub fn sysctl(
+ name: []const c_int,
+ oldp: ?*c_void,
+ oldlenp: ?*usize,
+ newp: ?*c_void,
+ newlen: usize,
+) SysCtlError!void {
+ const name_len = math.cast(c_uint, name.len) catch return error.NameTooLong;
+ switch (errno(system.sysctl(name.ptr, name_len, oldp, oldlenp, newp, newlen))) {
+ 0 => return,
+ EFAULT => unreachable,
+ EPERM => return error.PermissionDenied,
+ ENOMEM => return error.SystemResources,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+pub fn sysctlbynameC(
+ name: [*]const u8,
+ oldp: ?*c_void,
+ oldlenp: ?*usize,
+ newp: ?*c_void,
+ newlen: usize,
+) SysCtlError!void {
+ switch (errno(system.sysctlbyname(name, oldp, oldlenp, newp, newlen))) {
+ 0 => return,
+ EFAULT => unreachable,
+ EPERM => return error.PermissionDenied,
+ ENOMEM => return error.SystemResources,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+pub fn gettimeofday(tv: ?*timeval, tz: ?*timezone) void {
+ switch (errno(system.gettimeofday(tv, tz))) {
+ 0 => return,
+ EINVAL => unreachable,
+ else => unreachable,
+ }
+}
+
+pub const SeekError = error{
+ Unseekable,
+ Unexpected,
+};
+
+/// Repositions read/write file offset relative to the beginning.
+pub fn lseek_SET(fd: fd_t, offset: u64) SeekError!void {
+ if (linux.is_the_target and !builtin.link_libc and @sizeOf(usize) == 4) {
+ var result: u64 = undefined;
+ switch (errno(system.llseek(fd, offset, &result, SEEK_SET))) {
+ 0 => return,
+ EBADF => unreachable, // always a race condition
+ EINVAL => return error.Unseekable,
+ EOVERFLOW => return error.Unseekable,
+ ESPIPE => return error.Unseekable,
+ ENXIO => return error.Unseekable,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+ if (windows.is_the_target) {
+ return windows.SetFilePointerEx_BEGIN(fd, offset);
+ }
+ const ipos = @bitCast(i64, offset); // the OS treats this as unsigned
+ switch (errno(system.lseek(fd, ipos, SEEK_SET))) {
+ 0 => return,
+ EBADF => unreachable, // always a race condition
+ EINVAL => return error.Unseekable,
+ EOVERFLOW => return error.Unseekable,
+ ESPIPE => return error.Unseekable,
+ ENXIO => 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 (linux.is_the_target and !builtin.link_libc and @sizeOf(usize) == 4) {
+ var result: u64 = undefined;
+ switch (errno(system.llseek(fd, @bitCast(u64, offset), &result, SEEK_CUR))) {
+ 0 => return,
+ EBADF => unreachable, // always a race condition
+ EINVAL => return error.Unseekable,
+ EOVERFLOW => return error.Unseekable,
+ ESPIPE => return error.Unseekable,
+ ENXIO => return error.Unseekable,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+ if (windows.is_the_target) {
+ return windows.SetFilePointerEx_CURRENT(fd, offset);
+ }
+ switch (errno(system.lseek(fd, offset, SEEK_CUR))) {
+ 0 => return,
+ EBADF => unreachable, // always a race condition
+ EINVAL => return error.Unseekable,
+ EOVERFLOW => return error.Unseekable,
+ ESPIPE => return error.Unseekable,
+ ENXIO => 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 (linux.is_the_target and !builtin.link_libc and @sizeOf(usize) == 4) {
+ var result: u64 = undefined;
+ switch (errno(system.llseek(fd, @bitCast(u64, offset), &result, SEEK_END))) {
+ 0 => return,
+ EBADF => unreachable, // always a race condition
+ EINVAL => return error.Unseekable,
+ EOVERFLOW => return error.Unseekable,
+ ESPIPE => return error.Unseekable,
+ ENXIO => return error.Unseekable,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+ if (windows.is_the_target) {
+ return windows.SetFilePointerEx_END(fd, offset);
+ }
+ switch (errno(system.lseek(fd, offset, SEEK_END))) {
+ 0 => return,
+ EBADF => unreachable, // always a race condition
+ EINVAL => return error.Unseekable,
+ EOVERFLOW => return error.Unseekable,
+ ESPIPE => return error.Unseekable,
+ ENXIO => 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 (linux.is_the_target and !builtin.link_libc and @sizeOf(usize) == 4) {
+ var result: u64 = undefined;
+ switch (errno(system.llseek(fd, 0, &result, SEEK_CUR))) {
+ 0 => return result,
+ EBADF => unreachable, // always a race condition
+ EINVAL => return error.Unseekable,
+ EOVERFLOW => return error.Unseekable,
+ ESPIPE => return error.Unseekable,
+ ENXIO => return error.Unseekable,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+ if (windows.is_the_target) {
+ return windows.SetFilePointerEx_CURRENT_get(fd);
+ }
+ const rc = system.lseek(fd, 0, SEEK_CUR);
+ switch (errno(rc)) {
+ 0 => return @bitCast(u64, rc),
+ EBADF => unreachable, // always a race condition
+ EINVAL => return error.Unseekable,
+ EOVERFLOW => return error.Unseekable,
+ ESPIPE => return error.Unseekable,
+ ENXIO => return error.Unseekable,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+pub const RealPathError = error{
+ FileNotFound,
+ AccessDenied,
+ NameTooLong,
+ NotSupported,
+ NotDir,
+ SymLinkLoop,
+ InputOutput,
+ FileTooBig,
+ IsDir,
+ ProcessFdQuotaExceeded,
+ SystemFdQuotaExceeded,
+ NoDevice,
+ SystemResources,
+ NoSpaceLeft,
+ FileSystem,
+ BadPathName,
+ DeviceBusy,
+
+ SharingViolation,
+ PipeBusy,
+
+ /// On Windows, file paths must be valid Unicode.
+ InvalidUtf8,
+
+ PathAlreadyExists,
+
+ Unexpected,
+};
+
+/// Return the canonicalized absolute pathname.
+/// Expands all symbolic links and resolves references to `.`, `..`, and
+/// extra `/` characters in `pathname`.
+/// The return value is a slice of `out_buffer`, but not necessarily from the beginning.
+/// See also `realpathC` and `realpathW`.
+pub fn realpath(pathname: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
+ if (windows.is_the_target) {
+ const pathname_w = try windows.sliceToPrefixedFileW(pathname);
+ return realpathW(&pathname_w, out_buffer);
+ }
+ const pathname_c = try toPosixPath(pathname);
+ return realpathC(&pathname_c, out_buffer);
+}
+
+/// Same as `realpath` except `pathname` is null-terminated.
+pub fn realpathC(pathname: [*]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
+ if (windows.is_the_target) {
+ const pathname_w = try windows.cStrToPrefixedFileW(pathname);
+ return realpathW(&pathname_w, out_buffer);
+ }
+ if (linux.is_the_target and !builtin.link_libc) {
+ const fd = try openC(pathname, linux.O_PATH | linux.O_NONBLOCK | linux.O_CLOEXEC, 0);
+ defer close(fd);
+
+ var procfs_buf: ["/proc/self/fd/-2147483648\x00".len]u8 = undefined;
+ const proc_path = std.fmt.bufPrint(procfs_buf[0..], "/proc/self/fd/{}\x00", fd) catch unreachable;
+
+ return readlinkC(proc_path.ptr, out_buffer);
+ }
+ const result_path = std.c.realpath(pathname, out_buffer) orelse switch (std.c._errno().*) {
+ EINVAL => unreachable,
+ EBADF => unreachable,
+ EFAULT => unreachable,
+ EACCES => return error.AccessDenied,
+ ENOENT => return error.FileNotFound,
+ ENOTSUP => return error.NotSupported,
+ ENOTDIR => return error.NotDir,
+ ENAMETOOLONG => return error.NameTooLong,
+ ELOOP => return error.SymLinkLoop,
+ EIO => return error.InputOutput,
+ else => |err| return unexpectedErrno(@intCast(usize, err)),
+ };
+ return mem.toSlice(u8, result_path);
+}
+
+/// Same as `realpath` except `pathname` is null-terminated and UTF16LE-encoded.
+pub fn realpathW(pathname: [*]const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
+ const h_file = try windows.CreateFileW(
+ pathname,
+ windows.GENERIC_READ,
+ windows.FILE_SHARE_READ,
+ null,
+ windows.OPEN_EXISTING,
+ windows.FILE_ATTRIBUTE_NORMAL,
+ null,
+ );
+ defer windows.CloseHandle(h_file);
+
+ var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined;
+ const wide_len = try windows.GetFinalPathNameByHandleW(h_file, &wide_buf, wide_buf.len, windows.VOLUME_NAME_DOS);
+ assert(wide_len <= wide_buf.len);
+ const wide_slice = wide_buf[0..wide_len];
+
+ // Windows returns \\?\ prepended to the path.
+ // We strip it to make this function consistent across platforms.
+ const prefix = [_]u16{ '\\', '\\', '?', '\\' };
+ const start_index = if (mem.startsWith(u16, wide_slice, prefix)) prefix.len else 0;
+
+ // Trust that Windows gives us valid UTF-16LE.
+ const end_index = std.unicode.utf16leToUtf8(out_buffer, wide_slice[start_index..]) catch unreachable;
+ return out_buffer[0..end_index];
+}
+
+/// Spurious wakeups are possible and no precision of timing is guaranteed.
+pub fn nanosleep(seconds: u64, nanoseconds: u64) void {
+ var req = timespec{
+ .tv_sec = math.cast(isize, seconds) catch math.maxInt(isize),
+ .tv_nsec = math.cast(isize, nanoseconds) catch math.maxInt(isize),
+ };
+ var rem: timespec = undefined;
+ while (true) {
+ switch (errno(system.nanosleep(&req, &rem))) {
+ EFAULT => unreachable,
+ EINVAL => {
+ // Sometimes Darwin returns EINVAL for no reason.
+ // We treat it as a spurious wakeup.
+ return;
+ },
+ EINTR => {
+ req = rem;
+ continue;
+ },
+ // This prong handles success as well as unexpected errors.
+ else => return,
+ }
+ }
+}
+
+pub fn dl_iterate_phdr(comptime T: type, callback: extern fn (info: *dl_phdr_info, size: usize, data: ?*T) i32, data: ?*T) isize {
+ // This is implemented only for systems using ELF executables
+ if (windows.is_the_target or builtin.os == .uefi or wasi.is_the_target or darwin.is_the_target)
+ @compileError("dl_iterate_phdr is not available for this target");
+
+ if (builtin.link_libc) {
+ return system.dl_iterate_phdr(
+ @ptrCast(std.c.dl_iterate_phdr_callback, callback),
+ @ptrCast(?*c_void, data),
+ );
+ }
+
+ const elf_base = std.process.getBaseAddress();
+ const ehdr = @intToPtr(*elf.Ehdr, elf_base);
+ // Make sure the base address points to an ELF image
+ assert(mem.eql(u8, ehdr.e_ident[0..4], "\x7fELF"));
+ const n_phdr = ehdr.e_phnum;
+ const phdrs = (@intToPtr([*]elf.Phdr, 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()) {
+ var info = dl_phdr_info{
+ .dlpi_addr = elf_base,
+ .dlpi_name = c"/proc/self/exe",
+ .dlpi_phdr = phdrs.ptr,
+ .dlpi_phnum = ehdr.e_phnum,
+ };
+
+ return callback(&info, @sizeOf(dl_phdr_info), data);
+ }
+
+ // Last return value from the callback function
+ var last_r: isize = 0;
+ while (it.next()) |entry| {
+ var dlpi_phdr: [*]elf.Phdr = undefined;
+ var dlpi_phnum: u16 = undefined;
+
+ if (entry.l_addr != 0) {
+ const elf_header = @intToPtr(*elf.Ehdr, entry.l_addr);
+ dlpi_phdr = @intToPtr([*]elf.Phdr, entry.l_addr + elf_header.e_phoff);
+ dlpi_phnum = elf_header.e_phnum;
+ } else {
+ // This is the running ELF image
+ dlpi_phdr = @intToPtr([*]elf.Phdr, elf_base + ehdr.e_phoff);
+ dlpi_phnum = ehdr.e_phnum;
+ }
+
+ var info = dl_phdr_info{
+ .dlpi_addr = entry.l_addr,
+ .dlpi_name = entry.l_name,
+ .dlpi_phdr = dlpi_phdr,
+ .dlpi_phnum = dlpi_phnum,
+ };
+
+ last_r = callback(&info, @sizeOf(dl_phdr_info), data);
+ if (last_r != 0) break;
+ }
+
+ return last_r;
+}
+
+pub const ClockGetTimeError = error{
+ UnsupportedClock,
+ Unexpected,
+};
+
+pub fn clock_gettime(clk_id: i32, tp: *timespec) ClockGetTimeError!void {
+ switch (errno(system.clock_gettime(clk_id, tp))) {
+ 0 => return,
+ EFAULT => unreachable,
+ EINVAL => return error.UnsupportedClock,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+pub fn clock_getres(clk_id: i32, res: *timespec) ClockGetTimeError!void {
+ switch (errno(system.clock_getres(clk_id, res))) {
+ 0 => return,
+ EFAULT => unreachable,
+ EINVAL => return error.UnsupportedClock,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+pub const SchedGetAffinityError = error{
+ PermissionDenied,
+ Unexpected,
+};
+
+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))) {
+ 0 => return set,
+ EFAULT => unreachable,
+ EINVAL => unreachable,
+ ESRCH => unreachable,
+ EPERM => return error.PermissionDenied,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+/// Used to convert a slice to a null terminated slice on the stack.
+/// TODO https://github.com/ziglang/zig/issues/287
+pub fn toPosixPath(file_path: []const u8) ![PATH_MAX]u8 {
+ var path_with_null: [PATH_MAX]u8 = undefined;
+ // >= rather than > to make room for the null byte
+ if (file_path.len >= PATH_MAX) return error.NameTooLong;
+ mem.copy(u8, &path_with_null, file_path);
+ path_with_null[file_path.len] = 0;
+ return path_with_null;
+}
+
+/// Whether or not error.Unexpected will print its value and a stack trace.
+/// if this happens the fix is to add the error code to the corresponding
+/// switch expression, possibly introduce a new error in the error set, and
+/// send a patch to Zig.
+pub const unexpected_error_tracing = builtin.mode == .Debug;
+
+pub const UnexpectedError = error{
+ /// The Operating System returned an undocumented error code.
+ /// This error is in theory not possible, but it would be better
+ /// to handle this error than to invoke undefined behavior.
+ Unexpected,
+};
+
+/// Call this when you made a syscall or something that sets errno
+/// and you get an unexpected error.
+pub fn unexpectedErrno(err: usize) UnexpectedError {
+ if (unexpected_error_tracing) {
+ std.debug.warn("unexpected errno: {}\n", err);
+ std.debug.dumpCurrentStackTrace(null);
+ }
+ return error.Unexpected;
+}
+
+pub const SigaltstackError = error{
+ /// The supplied stack size was less than MINSIGSTKSZ.
+ SizeTooSmall,
+
+ /// Attempted to change the signal stack while it was active.
+ PermissionDenied,
+ Unexpected,
+};
+
+pub fn sigaltstack(ss: ?*stack_t, old_ss: ?*stack_t) SigaltstackError!void {
+ if (windows.is_the_target or uefi.is_the_target or wasi.is_the_target)
+ @compileError("std.os.sigaltstack not available for this target");
+
+ switch (errno(system.sigaltstack(ss, old_ss))) {
+ 0 => return,
+ EFAULT => unreachable,
+ EINVAL => unreachable,
+ ENOMEM => return error.SizeTooSmall,
+ EPERM => return error.PermissionDenied,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+/// Examine and change a signal action.
+pub fn sigaction(sig: u6, act: *const Sigaction, oact: ?*Sigaction) void {
+ switch (errno(system.sigaction(sig, act, oact))) {
+ 0 => return,
+ EFAULT => unreachable,
+ EINVAL => unreachable,
+ else => unreachable,
+ }
+}
+
+pub const FutimensError = error{
+ /// times is NULL, or both tv_nsec values are UTIME_NOW, and either:
+ /// * the effective user ID of the caller does not match the owner
+ /// of the file, the caller does not have write access to the
+ /// file, and the caller is not privileged (Linux: does not have
+ /// either the CAP_FOWNER or the CAP_DAC_OVERRIDE capability);
+ /// or,
+ /// * the file is marked immutable (see chattr(1)).
+ AccessDenied,
+
+ /// The caller attempted to change one or both timestamps to a value
+ /// other than the current time, or to change one of the timestamps
+ /// to the current time while leaving the other timestamp unchanged,
+ /// (i.e., times is not NULL, neither tv_nsec field is UTIME_NOW,
+ /// and neither tv_nsec field is UTIME_OMIT) and either:
+ /// * the caller's effective user ID does not match the owner of
+ /// file, and the caller is not privileged (Linux: does not have
+ /// the CAP_FOWNER capability); or,
+ /// * the file is marked append-only or immutable (see chattr(1)).
+ PermissionDenied,
+
+ ReadOnlyFileSystem,
+
+ Unexpected,
+};
+
+pub fn futimens(fd: fd_t, times: *const [2]timespec) FutimensError!void {
+ switch (errno(system.futimens(fd, times))) {
+ 0 => return,
+ EACCES => return error.AccessDenied,
+ EPERM => return error.PermissionDenied,
+ EBADF => unreachable, // always a race condition
+ EFAULT => unreachable,
+ EINVAL => unreachable,
+ EROFS => return error.ReadOnlyFileSystem,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+pub const GetHostNameError = error{
+ PermissionDenied,
+ Unexpected,
+};
+
+pub fn gethostname(name_buffer: *[HOST_NAME_MAX]u8) GetHostNameError![]u8 {
+ if (builtin.link_libc) {
+ switch (errno(system.gethostname(name_buffer, name_buffer.len))) {
+ 0 => return mem.toSlice(u8, name_buffer),
+ EFAULT => unreachable,
+ ENAMETOOLONG => unreachable, // HOST_NAME_MAX prevents this
+ EPERM => return error.PermissionDenied,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+ if (linux.is_the_target) {
+ var uts: utsname = undefined;
+ switch (errno(system.uname(&uts))) {
+ 0 => {
+ const hostname = mem.toSlice(u8, &uts.nodename);
+ mem.copy(u8, name_buffer, hostname);
+ return name_buffer[0..hostname.len];
+ },
+ EFAULT => unreachable,
+ EPERM => return error.PermissionDenied,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+
+ @compileError("TODO implement gethostname for this OS");
+}
+
+test "" {
+ _ = @import("os/darwin.zig");
+ _ = @import("os/freebsd.zig");
+ _ = @import("os/linux.zig");
+ _ = @import("os/netbsd.zig");
+ _ = @import("os/uefi.zig");
+ _ = @import("os/wasi.zig");
+ _ = @import("os/windows.zig");
+ _ = @import("os/zen.zig");
+
+ _ = @import("os/test.zig");
+}