// 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 root = @import("root"); 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; pub const darwin = @import("os/darwin.zig"); pub const dragonfly = @import("os/dragonfly.zig"); pub const freebsd = @import("os/freebsd.zig"); pub const netbsd = @import("os/netbsd.zig"); pub const linux = @import("os/linux.zig"); pub const uefi = @import("os/uefi.zig"); pub const wasi = @import("os/wasi.zig"); pub const windows = @import("os/windows.zig"); comptime { assert(@import("std") == std); // std lib tests require --override-lib-dir } test "" { _ = darwin; _ = freebsd; _ = linux; _ = netbsd; _ = uefi; _ = wasi; _ = windows; _ = @import("os/test.zig"); } /// Applications can override the `system` API layer in their root source file. /// Otherwise, when linking libc, this is the C API. /// When not linking libc, it is the OS-specific system interface. pub const system = if (@hasDecl(root, "os") and root.os != @This()) root.os.system else if (builtin.link_libc) std.c else switch (builtin.os.tag) { .macosx, .ios, .watchos, .tvos => darwin, .freebsd => freebsd, .linux => linux, .netbsd => netbsd, .dragonfly => dragonfly, .wasi => wasi, .windows => windows, else => struct {}, }; pub usingnamespace @import("os/bits.zig"); /// See also `getenv`. Populated by startup code before main(). /// TODO this is a footgun because the value will be undefined when using `zig build-lib`. /// https://github.com/ziglang/zig/issues/4524 pub var environ: [][*:0]u8 = undefined; /// Populated by startup code before main(). /// Not available on Windows. See `std.process.args` /// for obtaining the process arguments. pub var argv: [][*:0]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 (builtin.os.tag == .windows) { return windows.CloseHandle(fd); } if (builtin.os.tag == .wasi) { _ = wasi.fd_close(fd); return; } if (comptime std.Target.current.isDarwin()) { // 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 (builtin.os.tag == .windows) { return windows.RtlGenRandom(buffer); } if (builtin.os.tag == .linux or builtin.os.tag == .freebsd) { var buf = buffer; const use_c = builtin.os.tag != .linux or std.c.versionCheck(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 (builtin.os.tag == .netbsd) { netbsd.arc4random_buf(buffer.ptr, buffer.len); return; } if (builtin.os.tag == .wasi) { 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 openZ("/dev/urandom", O_RDONLY | O_CLOEXEC, 0); defer close(fd); const st = try fstat(fd); if (!S_ISCHR(st.mode)) { return error.NoDevice; } const file = std.fs.File{ .handle = fd, .capable_io_mode = .blocking, .intended_io_mode = .blocking, }; const stream = file.inStream(); 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 (builtin.os.tag == .windows) { if (builtin.mode == .Debug) { @breakpoint(); } windows.kernel32.ExitProcess(3); } if (!builtin.link_libc and builtin.os.tag == .linux) { raise(SIGABRT) catch {}; // TODO the rest of the implementation of abort() from musl libc here raise(SIGKILL) catch {}; exit(127); } if (builtin.os.tag == .uefi) { exit(0); // TODO choose appropriate exit code } if (builtin.os.tag == .wasi) { @breakpoint(); exit(1); } system.abort(); } pub const RaiseError = UnexpectedError; pub fn raise(sig: u8) RaiseError!void { if (builtin.link_libc) { switch (errno(system.raise(sig))) { 0 => return, else => |err| return unexpectedErrno(err), } } if (builtin.os.tag == .linux) { var set: linux.sigset_t = undefined; // block application signals _ = linux.sigprocmask(SIG_BLOCK, &linux.app_mask, &set); const tid = linux.gettid(); const rc = linux.tkill(tid, sig); // restore signal mask _ = linux.sigprocmask(SIG_SETMASK, &set, null); switch (errno(rc)) { 0 => return, else => |err| return unexpectedErrno(err), } } @compileError("std.os.raise unimplemented for this target"); } pub const KillError = error{PermissionDenied} || UnexpectedError; pub fn kill(pid: pid_t, sig: u8) KillError!void { switch (errno(system.kill(pid, sig))) { 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 (builtin.os.tag == .windows) { windows.kernel32.ExitProcess(status); } if (builtin.os.tag == .wasi) { wasi.proc_exit(status); } if (builtin.os.tag == .linux and !builtin.single_threaded) { linux.exit_group(status); } if (builtin.os.tag == .uefi) { // exit() is only 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, @intToEnum(uefi.Status, status), 0, null); } // If we can't exit, reboot the system instead. uefi.system_table.runtime_services.resetSystem(uefi.tables.ResetType.ResetCold, @intToEnum(uefi.Status, status), 0, null); } system.exit(status); } pub const ReadError = error{ InputOutput, SystemResources, IsDir, OperationAborted, BrokenPipe, ConnectionResetByPeer, ConnectionTimedOut, /// This error occurs when no global event loop is configured, /// and reading from the file descriptor would block. WouldBlock, } || UnexpectedError; /// Returns the number of bytes that were read, which can be less than /// buf.len. If 0 bytes were read, that means EOF. /// If the application has a global event loop enabled, EAGAIN is handled /// via the event loop. Otherwise EAGAIN results in error.WouldBlock. /// /// 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. /// For POSIX the limit is `math.maxInt(isize)`. pub fn read(fd: fd_t, buf: []u8) ReadError!usize { if (builtin.os.tag == .windows) { return windows.ReadFile(fd, buf, null, std.io.default_mode); } if (builtin.os.tag == .wasi and !builtin.link_libc) { const iovs = [1]iovec{iovec{ .iov_base = buf.ptr, .iov_len = buf.len, }}; var nread: usize = undefined; switch (wasi.fd_read(fd, &iovs, iovs.len, &nread)) { wasi.ESUCCESS => return nread, wasi.EINTR => unreachable, wasi.EINVAL => unreachable, wasi.EFAULT => unreachable, wasi.EAGAIN => unreachable, wasi.EBADF => unreachable, // Always a race condition. wasi.EIO => return error.InputOutput, wasi.EISDIR => return error.IsDir, wasi.ENOBUFS => return error.SystemResources, wasi.ENOMEM => return error.SystemResources, wasi.ECONNRESET => return error.ConnectionResetByPeer, wasi.ETIMEDOUT => return error.ConnectionTimedOut, else => |err| return unexpectedErrno(err), } } // Prevents EINVAL. const max_count = switch (std.Target.current.os.tag) { .linux => 0x7ffff000, else => math.maxInt(isize), }; const adjusted_len = math.min(max_count, buf.len); while (true) { const rc = system.read(fd, buf.ptr, adjusted_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); 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, ECONNRESET => return error.ConnectionResetByPeer, ETIMEDOUT => return error.ConnectionTimedOut, else => |err| return unexpectedErrno(err), } } return index; } /// Number of bytes read is returned. Upon reading end-of-file, zero is returned. /// /// For POSIX systems, if the application has a global event loop enabled, EAGAIN is handled /// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`. /// 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. pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize { if (std.Target.current.os.tag == .windows) { // TODO improve this to use ReadFileScatter if (iov.len == 0) return @as(usize, 0); const first = iov[0]; return read(fd, first.iov_base[0..first.iov_len]); } if (builtin.os.tag == .wasi) { var nread: usize = undefined; switch (wasi.fd_read(fd, iov.ptr, iov.len, &nread)) { wasi.ESUCCESS => return nread, wasi.EINTR => unreachable, wasi.EINVAL => unreachable, wasi.EFAULT => unreachable, wasi.EAGAIN => unreachable, // currently not support in WASI wasi.EBADF => unreachable, // always a race condition wasi.EIO => return error.InputOutput, wasi.EISDIR => return error.IsDir, wasi.ENOBUFS => return error.SystemResources, wasi.ENOMEM => return error.SystemResources, else => |err| return unexpectedErrno(err), } } const iov_count = math.cast(u31, iov.len) catch math.maxInt(u31); while (true) { // TODO handle the case when iov_len is too large and get rid of this @intCast const rc = system.readv(fd, iov.ptr, iov_count); switch (errno(rc)) { 0 => return @intCast(usize, rc), EINTR => continue, EINVAL => unreachable, EFAULT => unreachable, EAGAIN => if (std.event.Loop.instance) |loop| { loop.waitUntilFdReadable(fd); 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), } } } pub const PReadError = ReadError || error{Unseekable}; /// Number of bytes read is returned. Upon reading end-of-file, zero is returned. /// /// Retries when interrupted by a signal. /// /// For POSIX systems, if the application has a global event loop enabled, EAGAIN is handled /// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`. /// 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. pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize { if (builtin.os.tag == .windows) { return windows.ReadFile(fd, buf, offset, std.io.default_mode); } if (builtin.os.tag == .wasi) { const iovs = [1]iovec{iovec{ .iov_base = buf.ptr, .iov_len = buf.len, }}; var nread: usize = undefined; switch (wasi.fd_pread(fd, &iovs, iovs.len, offset, &nread)) { wasi.ESUCCESS => return nread, wasi.EINTR => unreachable, wasi.EINVAL => unreachable, wasi.EFAULT => unreachable, wasi.EAGAIN => unreachable, wasi.EBADF => unreachable, // Always a race condition. wasi.EIO => return error.InputOutput, wasi.EISDIR => return error.IsDir, wasi.ENOBUFS => return error.SystemResources, wasi.ENOMEM => return error.SystemResources, wasi.ECONNRESET => return error.ConnectionResetByPeer, wasi.ENXIO => return error.Unseekable, wasi.ESPIPE => return error.Unseekable, wasi.EOVERFLOW => return error.Unseekable, else => |err| return unexpectedErrno(err), } } while (true) { const rc = system.pread(fd, buf.ptr, buf.len, offset); switch (errno(rc)) { 0 => return @intCast(usize, rc), EINTR => continue, EINVAL => unreachable, EFAULT => unreachable, EAGAIN => if (std.event.Loop.instance) |loop| { loop.waitUntilFdReadable(fd); 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, ECONNRESET => return error.ConnectionResetByPeer, ENXIO => return error.Unseekable, ESPIPE => return error.Unseekable, EOVERFLOW => return error.Unseekable, else => |err| return unexpectedErrno(err), } } } pub const TruncateError = error{ FileTooBig, InputOutput, CannotTruncate, FileBusy, } || UnexpectedError; pub fn ftruncate(fd: fd_t, length: u64) TruncateError!void { if (std.Target.current.os.tag == .windows) { var io_status_block: windows.IO_STATUS_BLOCK = undefined; var eof_info = windows.FILE_END_OF_FILE_INFORMATION{ .EndOfFile = @bitCast(windows.LARGE_INTEGER, length), }; const rc = windows.ntdll.NtSetInformationFile( fd, &io_status_block, &eof_info, @sizeOf(windows.FILE_END_OF_FILE_INFORMATION), .FileEndOfFileInformation, ); switch (rc) { .SUCCESS => return, .INVALID_HANDLE => unreachable, // Handle not open for writing .ACCESS_DENIED => return error.CannotTruncate, else => return windows.unexpectedStatus(rc), } } if (std.Target.current.os.tag == .wasi) { switch (wasi.fd_filestat_set_size(fd, length)) { wasi.ESUCCESS => return, wasi.EINTR => unreachable, wasi.EFBIG => return error.FileTooBig, wasi.EIO => return error.InputOutput, wasi.EPERM => return error.CannotTruncate, wasi.ETXTBSY => return error.FileBusy, wasi.EBADF => unreachable, // Handle not open for writing wasi.EINVAL => unreachable, // Handle not open for writing else => |err| return unexpectedErrno(err), } } while (true) { const rc = if (builtin.link_libc) if (std.Target.current.os.tag == .linux) system.ftruncate64(fd, @bitCast(off_t, length)) else system.ftruncate(fd, @bitCast(off_t, length)) else system.ftruncate(fd, length); switch (errno(rc)) { 0 => return, EINTR => continue, EFBIG => return error.FileTooBig, EIO => return error.InputOutput, EPERM => return error.CannotTruncate, ETXTBSY => return error.FileBusy, EBADF => unreachable, // Handle not open for writing EINVAL => unreachable, // Handle not open for writing else => |err| return unexpectedErrno(err), } } } /// Number of bytes read is returned. Upon reading end-of-file, zero is returned. /// /// Retries when interrupted by a signal. /// /// For POSIX systems, if the application has a global event loop enabled, EAGAIN is handled /// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`. /// On Windows, if the application has a global event loop enabled, I/O Completion Ports are /// used to perform the I/O. `error.WouldBlock` is not possible on Windows. /// /// This operation is non-atomic on the following systems: /// * Darwin /// * Windows /// On these systems, the read races with concurrent writes to the same file descriptor. pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) PReadError!usize { const have_pread_but_not_preadv = switch (std.Target.current.os.tag) { .windows, .macosx, .ios, .watchos, .tvos => 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 @as(usize, 0); const first = iov[0]; return pread(fd, first.iov_base[0..first.iov_len], offset); } if (builtin.os.tag == .wasi) { var nread: usize = undefined; switch (wasi.fd_pread(fd, iov.ptr, iov.len, offset, &nread)) { wasi.ESUCCESS => return nread, wasi.EINTR => unreachable, wasi.EINVAL => unreachable, wasi.EFAULT => unreachable, wasi.EAGAIN => unreachable, wasi.EBADF => unreachable, // always a race condition wasi.EIO => return error.InputOutput, wasi.EISDIR => return error.IsDir, wasi.ENOBUFS => return error.SystemResources, wasi.ENOMEM => return error.SystemResources, wasi.ENXIO => return error.Unseekable, wasi.ESPIPE => return error.Unseekable, wasi.EOVERFLOW => return error.Unseekable, else => |err| return unexpectedErrno(err), } } const iov_count = math.cast(u31, iov.len) catch math.maxInt(u31); while (true) { const rc = system.preadv(fd, iov.ptr, iov_count, offset); switch (errno(rc)) { 0 => return @bitCast(usize, rc), EINTR => continue, EINVAL => unreachable, EFAULT => unreachable, EAGAIN => if (std.event.Loop.instance) |loop| { loop.waitUntilFdReadable(fd); 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, ENXIO => return error.Unseekable, ESPIPE => return error.Unseekable, EOVERFLOW => return error.Unseekable, else => |err| return unexpectedErrno(err), } } } pub const WriteError = error{ DiskQuota, FileTooBig, InputOutput, NoSpaceLeft, AccessDenied, BrokenPipe, SystemResources, OperationAborted, /// This error occurs when no global event loop is configured, /// and reading from the file descriptor would block. WouldBlock, } || 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 the application has a global event loop enabled, EAGAIN is handled /// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`. /// 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 corresponding POSIX limit is `math.maxInt(isize)`. pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize { if (builtin.os.tag == .windows) { return windows.WriteFile(fd, bytes, null, std.io.default_mode); } if (builtin.os.tag == .wasi and !builtin.link_libc) { const ciovs = [_]iovec_const{iovec_const{ .iov_base = bytes.ptr, .iov_len = bytes.len, }}; var nwritten: usize = undefined; switch (wasi.fd_write(fd, &ciovs, ciovs.len, &nwritten)) { wasi.ESUCCESS => return nwritten, wasi.EINTR => unreachable, wasi.EINVAL => unreachable, wasi.EFAULT => unreachable, wasi.EAGAIN => unreachable, wasi.EBADF => unreachable, // Always a race condition. wasi.EDESTADDRREQ => unreachable, // `connect` was never called. wasi.EDQUOT => return error.DiskQuota, wasi.EFBIG => return error.FileTooBig, wasi.EIO => return error.InputOutput, wasi.ENOSPC => return error.NoSpaceLeft, wasi.EPERM => return error.AccessDenied, wasi.EPIPE => return error.BrokenPipe, else => |err| return unexpectedErrno(err), } } const max_count = switch (std.Target.current.os.tag) { .linux => 0x7ffff000, else => math.maxInt(isize), }; const adjusted_len = math.min(max_count, bytes.len); while (true) { const rc = system.write(fd, bytes.ptr, adjusted_len); switch (errno(rc)) { 0 => return @intCast(usize, rc), EINTR => continue, EINVAL => unreachable, EFAULT => unreachable, EAGAIN => if (std.event.Loop.instance) |loop| { loop.waitUntilFdWritable(fd); continue; } else { return error.WouldBlock; }, 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. /// 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 the application has a global event loop enabled, EAGAIN is handled /// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`. /// 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 will fit in a `u31`, a partial write will occur. pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!usize { if (std.Target.current.os.tag == .windows) { // TODO improve this to use WriteFileScatter if (iov.len == 0) return @as(usize, 0); const first = iov[0]; return write(fd, first.iov_base[0..first.iov_len]); } if (builtin.os.tag == .wasi) { var nwritten: usize = undefined; switch (wasi.fd_write(fd, iov.ptr, iov.len, &nwritten)) { wasi.ESUCCESS => return nwritten, wasi.EINTR => unreachable, wasi.EINVAL => unreachable, wasi.EFAULT => unreachable, wasi.EAGAIN => unreachable, wasi.EBADF => unreachable, // Always a race condition. wasi.EDESTADDRREQ => unreachable, // `connect` was never called. wasi.EDQUOT => return error.DiskQuota, wasi.EFBIG => return error.FileTooBig, wasi.EIO => return error.InputOutput, wasi.ENOSPC => return error.NoSpaceLeft, wasi.EPERM => return error.AccessDenied, wasi.EPIPE => return error.BrokenPipe, else => |err| return unexpectedErrno(err), } } const iov_count = math.cast(u31, iov.len) catch math.maxInt(u31); while (true) { const rc = system.writev(fd, iov.ptr, iov_count); switch (errno(rc)) { 0 => return @intCast(usize, rc), EINTR => continue, EINVAL => unreachable, EFAULT => unreachable, EAGAIN => if (std.event.Loop.instance) |loop| { loop.waitUntilFdWritable(fd); continue; } else { return error.WouldBlock; }, 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 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 the application has a global event loop enabled, EAGAIN is handled /// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`. /// 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 corresponding POSIX limit is `math.maxInt(isize)`. pub fn pwrite(fd: fd_t, bytes: []const u8, offset: u64) PWriteError!usize { if (std.Target.current.os.tag == .windows) { return windows.WriteFile(fd, bytes, offset, std.io.default_mode); } if (builtin.os.tag == .wasi) { const ciovs = [1]iovec_const{iovec_const{ .iov_base = bytes.ptr, .iov_len = bytes.len, }}; var nwritten: usize = undefined; switch (wasi.fd_pwrite(fd, &ciovs, ciovs.len, offset, &nwritten)) { wasi.ESUCCESS => return nwritten, wasi.EINTR => unreachable, wasi.EINVAL => unreachable, wasi.EFAULT => unreachable, wasi.EAGAIN => unreachable, wasi.EBADF => unreachable, // Always a race condition. wasi.EDESTADDRREQ => unreachable, // `connect` was never called. wasi.EDQUOT => return error.DiskQuota, wasi.EFBIG => return error.FileTooBig, wasi.EIO => return error.InputOutput, wasi.ENOSPC => return error.NoSpaceLeft, wasi.EPERM => return error.AccessDenied, wasi.EPIPE => return error.BrokenPipe, wasi.ENXIO => return error.Unseekable, wasi.ESPIPE => return error.Unseekable, wasi.EOVERFLOW => return error.Unseekable, else => |err| return unexpectedErrno(err), } } // Prevent EINVAL. const max_count = switch (std.Target.current.os.tag) { .linux => 0x7ffff000, else => math.maxInt(isize), }; const adjusted_len = math.min(max_count, bytes.len); while (true) { const rc = system.pwrite(fd, bytes.ptr, adjusted_len, offset); switch (errno(rc)) { 0 => return @intCast(usize, rc), EINTR => continue, EINVAL => unreachable, EFAULT => unreachable, EAGAIN => if (std.event.Loop.instance) |loop| { loop.waitUntilFdWritable(fd); continue; } else { return error.WouldBlock; }, 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, ENXIO => return error.Unseekable, ESPIPE => return error.Unseekable, EOVERFLOW => return error.Unseekable, 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 the application has a global event loop enabled, EAGAIN is handled /// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`. /// /// 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 will fit in a `u31`, 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 (std.Target.current.os.tag) { .windows, .macosx, .ios, .watchos, .tvos => 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 @as(usize, 0); const first = iov[0]; return pwrite(fd, first.iov_base[0..first.iov_len], offset); } if (builtin.os.tag == .wasi) { var nwritten: usize = undefined; switch (wasi.fd_pwrite(fd, iov.ptr, iov.len, offset, &nwritten)) { wasi.ESUCCESS => return nwritten, wasi.EINTR => unreachable, wasi.EINVAL => unreachable, wasi.EFAULT => unreachable, wasi.EAGAIN => unreachable, wasi.EBADF => unreachable, // Always a race condition. wasi.EDESTADDRREQ => unreachable, // `connect` was never called. wasi.EDQUOT => return error.DiskQuota, wasi.EFBIG => return error.FileTooBig, wasi.EIO => return error.InputOutput, wasi.ENOSPC => return error.NoSpaceLeft, wasi.EPERM => return error.AccessDenied, wasi.EPIPE => return error.BrokenPipe, wasi.ENXIO => return error.Unseekable, wasi.ESPIPE => return error.Unseekable, wasi.EOVERFLOW => return error.Unseekable, else => |err| return unexpectedErrno(err), } } const iov_count = math.cast(u31, iov.len) catch math.maxInt(u31); while (true) { const rc = system.pwritev(fd, iov.ptr, iov_count, offset); switch (errno(rc)) { 0 => return @intCast(usize, rc), EINTR => continue, EINVAL => unreachable, EFAULT => unreachable, EAGAIN => if (std.event.Loop.instance) |loop| { loop.waitUntilFdWritable(fd); continue; } else { return error.WouldBlock; }, 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, ENXIO => return error.Unseekable, ESPIPE => return error.Unseekable, EOVERFLOW => return error.Unseekable, else => |err| return unexpectedErrno(err), } } } pub const OpenError = error{ AccessDenied, SymLinkLoop, ProcessFdQuotaExceeded, SystemFdQuotaExceeded, NoDevice, FileNotFound, /// The path exceeded `MAX_PATH_BYTES` bytes. NameTooLong, /// Insufficient kernel memory was available, or /// the named file is a FIFO and per-user hard limit on /// memory allocation for pipes has been reached. SystemResources, /// The file is too large to be opened. This error is unreachable /// for 64-bit targets, as well as when opening directories. FileTooBig, /// The path refers to directory but the `O_DIRECTORY` flag was not provided. IsDir, /// A new path cannot be created because the device has no room for the new file. /// This error is only reachable when the `O_CREAT` flag is provided. NoSpaceLeft, /// A component used as a directory in the path was not, in fact, a directory, or /// `O_DIRECTORY` was specified and the path was not a directory. NotDir, /// The path already exists and the `O_CREAT` and `O_EXCL` flags were provided. PathAlreadyExists, DeviceBusy, /// The underlying filesystem does not support file locks FileLocksNotSupported, } || UnexpectedError; /// 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: mode_t) OpenError!fd_t { if (std.Target.current.os.tag == .windows) { const file_path_w = try windows.sliceToPrefixedFileW(file_path); return openW(file_path_w.span(), flags, perm); } const file_path_c = try toPosixPath(file_path); return openZ(&file_path_c, flags, perm); } pub const openC = @compileError("deprecated: renamed to openZ"); /// Open and possibly create a file. Keeps trying if it gets interrupted. /// See also `open`. pub fn openZ(file_path: [*:0]const u8, flags: u32, perm: mode_t) OpenError!fd_t { if (std.Target.current.os.tag == .windows) { const file_path_w = try windows.cStrToPrefixedFileW(file_path); return openW(file_path_w.span(), flags, perm); } 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), } } } /// Windows-only. The path parameter is /// [WTF-16](https://simonsapin.github.io/wtf-8/#potentially-ill-formed-utf-16) encoded. /// Translates the POSIX open API call to a Windows API call. pub fn openW(file_path_w: []const u16, flags: u32, perm: usize) OpenError!fd_t { @compileError("TODO implement openW for windows"); } /// 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`. /// TODO support windows pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: mode_t) OpenError!fd_t { const file_path_c = try toPosixPath(file_path); return openatZ(dir_fd, &file_path_c, flags, mode); } /// Open and possibly create a file in WASI. pub fn openatWasi(dir_fd: fd_t, file_path: []const u8, oflags: oflags_t, fdflags: fdflags_t, base: rights_t, inheriting: rights_t) OpenError!fd_t { while (true) { var fd: fd_t = undefined; switch (wasi.path_open(dir_fd, 0x0, file_path.ptr, file_path.len, oflags, base, inheriting, fdflags, &fd)) { wasi.ESUCCESS => return fd, wasi.EINTR => continue, wasi.EFAULT => unreachable, wasi.EINVAL => unreachable, wasi.EACCES => return error.AccessDenied, wasi.EFBIG => return error.FileTooBig, wasi.EOVERFLOW => return error.FileTooBig, wasi.EISDIR => return error.IsDir, wasi.ELOOP => return error.SymLinkLoop, wasi.EMFILE => return error.ProcessFdQuotaExceeded, wasi.ENAMETOOLONG => return error.NameTooLong, wasi.ENFILE => return error.SystemFdQuotaExceeded, wasi.ENODEV => return error.NoDevice, wasi.ENOENT => return error.FileNotFound, wasi.ENOMEM => return error.SystemResources, wasi.ENOSPC => return error.NoSpaceLeft, wasi.ENOTDIR => return error.NotDir, wasi.EPERM => return error.AccessDenied, wasi.EEXIST => return error.PathAlreadyExists, wasi.EBUSY => return error.DeviceBusy, else => |err| return unexpectedErrno(err), } } } pub const openatC = @compileError("deprecated: renamed to openatZ"); /// 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`. /// TODO support windows pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: mode_t) 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, EOPNOTSUPP => return error.FileLocksNotSupported, 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, // invalid parameters passed to dup2 EBADF => unreachable, // always a race condition else => |err| return unexpectedErrno(err), } } } pub const ExecveError = error{ SystemResources, AccessDenied, InvalidExe, FileSystem, IsDir, FileNotFound, NotDir, FileBusy, ProcessFdQuotaExceeded, SystemFdQuotaExceeded, NameTooLong, } || UnexpectedError; pub const execveC = @compileError("deprecated: use execveZ"); /// Like `execve` except the parameters are null-terminated, /// matching the syscall API on all targets. This removes the need for an allocator. /// 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))) { 0 => unreachable, 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 => |err| return unexpectedErrno(err), } } pub const execvpeC = @compileError("deprecated in favor of execvpeZ"); 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.spanZ(file); if (mem.indexOfScalar(u8, file_slice, '/') != null) return execveZ(file, child_argv, envp); const PATH = getenvZ("PATH") orelse "/usr/local/bin:/bin/:/usr/bin"; var path_buf: [MAX_PATH_BYTES]u8 = undefined; var it = mem.tokenize(PATH, ":"); var seen_eacces = false; var err: ExecveError = undefined; // 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| { if (path_buf.len < search_path.len + file_slice.len + 1) return error.NameTooLong; mem.copy(u8, &path_buf, search_path); path_buf[search_path.len] = '/'; mem.copy(u8, path_buf[search_path.len + 1 ..], file_slice); const path_len = search_path.len + file_slice.len + 1; 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; } /// Like `execvpe` except the parameters are null-terminated, /// matching the syscall API on all targets. This removes the need for an allocator. /// 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: [*:null]const ?[*:0]const u8, envp: [*:null]const ?[*:0]const u8, ) ExecveError { return execvpeZ_expandArg0(.no_expand, file, argv, envp); } /// This is the same as `execvpe` except if the `arg0_expand` parameter is set to `.expand`, /// then argv[0] will be replaced with the expanded version of it, after resolving in accordance /// with the PATH environment variable. pub fn execvpe_expandArg0( allocator: *mem.Allocator, arg0_expand: Arg0Expand, argv_slice: []const []const u8, env_map: *const std.BufMap, ) (ExecveError || error{OutOfMemory}) { const argv_buf = try allocator.alloc(?[*:0]u8, argv_slice.len + 1); mem.set(?[*:0]u8, argv_buf, null); defer { for (argv_buf) |arg| { const arg_buf = mem.spanZ(arg) orelse 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[0..arg.len :0].ptr; } argv_buf[argv_slice.len] = null; const argv_ptr = argv_buf[0..argv_slice.len :null].ptr; const envp_buf = try createNullDelimitedEnvMap(allocator, env_map); defer freeNullDelimitedEnvMap(allocator, envp_buf); switch (arg0_expand) { .expand => return execvpeZ_expandArg0(.expand, argv_buf.ptr[0].?, argv_ptr, envp_buf.ptr), .no_expand => return execvpeZ_expandArg0(.no_expand, argv_buf.ptr[0].?, argv_ptr, envp_buf.ptr), } } /// 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_slice[0]` is the executable path. /// This function also uses the PATH environment variable to get the full path to the executable. pub fn execvpe( allocator: *mem.Allocator, argv_slice: []const []const u8, env_map: *const std.BufMap, ) (ExecveError || error{OutOfMemory}) { return execvpe_expandArg0(allocator, .no_expand, argv_slice, env_map); } pub fn createNullDelimitedEnvMap(allocator: *mem.Allocator, env_map: *const std.BufMap) ![:null]?[*:0]u8 { const envp_count = env_map.count(); const envp_buf = try allocator.alloc(?[*:0]u8, envp_count + 1); mem.set(?[*:0]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); const len = env_buf.len - 1; env_buf[len] = 0; envp_buf[i] = env_buf[0..len :0].ptr; } assert(i == envp_count); } return envp_buf[0..envp_count :null]; } pub fn freeNullDelimitedEnvMap(allocator: *mem.Allocator, envp_buf: []?[*:0]u8) void { for (envp_buf) |env| { const env_buf = if (env) |ptr| ptr[0 .. mem.len(ptr) + 1] else break; allocator.free(env_buf); } allocator.free(envp_buf); } /// Get an environment variable. /// See also `getenvZ`. pub fn getenv(key: []const u8) ?[]const u8 { if (builtin.link_libc) { var small_key_buf: [64]u8 = undefined; if (key.len < small_key_buf.len) { mem.copy(u8, &small_key_buf, key); small_key_buf[key.len] = 0; const key0 = small_key_buf[0..key.len :0]; return getenvZ(key0); } // Search the entire `environ` because we don't have a null terminated pointer. var ptr = std.c.environ; while (ptr.*) |line| : (ptr += 1) { var line_i: usize = 0; while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {} const this_key = line[0..line_i]; if (!mem.eql(u8, this_key, key)) continue; var end_i: usize = line_i; while (line[end_i] != 0) : (end_i += 1) {} const value = line[line_i + 1 .. end_i]; return value; } return null; } if (builtin.os.tag == .windows) { @compileError("std.os.getenv is unavailable for Windows because environment string is in WTF-16 format. See std.process.getEnvVarOwned for cross-platform API or std.os.getenvW for Windows-specific API."); } // TODO see https://github.com/ziglang/zig/issues/4524 for (environ) |ptr| { var line_i: usize = 0; while (ptr[line_i] != 0 and ptr[line_i] != '=') : (line_i += 1) {} const this_key = ptr[0..line_i]; if (!mem.eql(u8, key, this_key)) continue; 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; } pub const getenvC = @compileError("Deprecated in favor of `getenvZ`"); /// Get an environment variable with a null-terminated name. /// See also `getenv`. pub fn getenvZ(key: [*:0]const u8) ?[]const u8 { if (builtin.link_libc) { const value = system.getenv(key) orelse return null; return mem.spanZ(value); } if (builtin.os.tag == .windows) { @compileError("std.os.getenvZ is unavailable for Windows because environment string is in WTF-16 format. See std.process.getEnvVarOwned for cross-platform API or std.os.getenvW for Windows-specific API."); } return getenv(mem.spanZ(key)); } /// Windows-only. Get an environment variable with a null-terminated, WTF-16 encoded name. /// See also `getenv`. /// This function first attempts a case-sensitive lookup. If no match is found, and `key` /// is ASCII, then it attempts a second case-insensitive lookup. pub fn getenvW(key: [*:0]const u16) ?[:0]const u16 { if (builtin.os.tag != .windows) { @compileError("std.os.getenvW is a Windows-only API"); } const key_slice = mem.spanZ(key); const ptr = windows.peb().ProcessParameters.Environment; var ascii_match: ?[:0]const u16 = null; var i: usize = 0; while (ptr[i] != 0) { const key_start = i; while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {} const this_key = ptr[key_start..i]; if (ptr[i] == '=') i += 1; const value_start = i; while (ptr[i] != 0) : (i += 1) {} const this_value = ptr[value_start..i :0]; if (mem.eql(u16, key_slice, this_key)) return this_value; ascii_check: { if (ascii_match != null) break :ascii_check; if (key_slice.len != this_key.len) break :ascii_check; for (key_slice) |a_c, key_index| { const a = math.cast(u8, a_c) catch break :ascii_check; const b = math.cast(u8, this_key[key_index]) catch break :ascii_check; if (std.ascii.toLower(a) != std.ascii.toLower(b)) break :ascii_check; } ascii_match = this_value; } i += 1; // skip over null byte } return ascii_match; } pub const GetCwdError = error{ NameTooLong, CurrentWorkingDirectoryUnlinked, } || UnexpectedError; /// The result is a slice of out_buffer, indexed from 0. pub fn getcwd(out_buffer: []u8) GetCwdError![]u8 { if (builtin.os.tag == .windows) { return windows.GetCurrentDirectory(out_buffer); } if (builtin.os.tag == .wasi) { @compileError("WASI doesn't have a concept of cwd(); use std.fs.wasi.PreopenList to get available Dir handles instead"); } 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.spanZ(@ptrCast([*:0]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, } || 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. /// 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 (builtin.os.tag == .windows) { 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.span().ptr, target_path_w.span().ptr, 0); } else { 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); } } pub const symlinkC = @compileError("deprecated: renamed to symlinkZ"); /// This is the same as `symlink` except the parameters are null-terminated pointers. /// See also `symlink`. pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLinkError!void { if (builtin.os.tag == .windows) { 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.span().ptr, target_path_w.span().ptr, 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 symlinkatZ(target_path_c, newdirfd, sym_link_path_c); } pub const symlinkatC = @compileError("deprecated: renamed to symlinkatZ"); pub fn symlinkatZ(target_path: [*:0]const u8, newdirfd: fd_t, sym_link_path: [*:0]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, /// On Windows, file paths must be valid Unicode. InvalidUtf8, /// On Windows, file paths cannot contain these characters: /// '/', '*', '?', '"', '<', '>', '|' BadPathName, } || UnexpectedError; /// Delete a name and possibly the file it refers to. /// See also `unlinkC`. pub fn unlink(file_path: []const u8) UnlinkError!void { if (builtin.os.tag == .windows) { const file_path_w = try windows.sliceToPrefixedFileW(file_path); return windows.DeleteFileW(file_path_w.span().ptr); } else { const file_path_c = try toPosixPath(file_path); return unlinkZ(&file_path_c); } } pub const unlinkC = @compileError("deprecated: renamed to unlinkZ"); /// Same as `unlink` except the parameter is a null terminated UTF8-encoded string. pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void { if (builtin.os.tag == .windows) { const file_path_w = try windows.cStrToPrefixedFileW(file_path); return windows.DeleteFileW(file_path_w.span().ptr); } 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), } } 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. /// Asserts that the path parameter has no null bytes. pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void { if (builtin.os.tag == .windows) { const file_path_w = try windows.sliceToPrefixedFileW(file_path); return unlinkatW(dirfd, file_path_w.span().ptr, flags); } else if (builtin.os.tag == .wasi) { return unlinkatWasi(dirfd, file_path, flags); } else { const file_path_c = try toPosixPath(file_path); return unlinkatZ(dirfd, &file_path_c, flags); } } pub const unlinkatC = @compileError("deprecated: renamed to unlinkatZ"); 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) { wasi.ESUCCESS => return, wasi.EACCES => return error.AccessDenied, wasi.EPERM => return error.AccessDenied, wasi.EBUSY => return error.FileBusy, wasi.EFAULT => unreachable, wasi.EIO => return error.FileSystem, wasi.EISDIR => return error.IsDir, wasi.ELOOP => return error.SymLinkLoop, wasi.ENAMETOOLONG => return error.NameTooLong, wasi.ENOENT => return error.FileNotFound, wasi.ENOTDIR => return error.NotDir, wasi.ENOMEM => return error.SystemResources, wasi.EROFS => return error.ReadOnlyFileSystem, wasi.ENOTEMPTY => return error.DirNotEmpty, wasi.EINVAL => unreachable, // invalid flags, or pathname has . as last component wasi.EBADF => unreachable, // always a race condition else => |err| return unexpectedErrno(err), } } /// Same as `unlinkat` but `file_path` is a null-terminated string. pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatError!void { if (builtin.os.tag == .windows) { const file_path_w = try windows.cStrToPrefixedFileW(file_path_c); return unlinkatW(dirfd, file_path_w.span().ptr, flags); } switch (errno(system.unlinkat(dirfd, file_path_c, flags))) { 0 => return, EACCES => return error.AccessDenied, EPERM => return error.AccessDenied, EBUSY => return error.FileBusy, EFAULT => 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, ENOTEMPTY => return error.DirNotEmpty, EINVAL => unreachable, // invalid flags, or pathname has . as last component EBADF => unreachable, // always a race condition else => |err| return unexpectedErrno(err), } } /// Same as `unlinkat` but `sub_path_w` is UTF16LE, NT prefixed. Windows only. pub fn unlinkatW(dirfd: fd_t, sub_path_w: [*:0]const u16, flags: u32) UnlinkatError!void { const w = windows; const want_rmdir_behavior = (flags & AT_REMOVEDIR) != 0; const create_options_flags = if (want_rmdir_behavior) @as(w.ULONG, w.FILE_DELETE_ON_CLOSE) else @as(w.ULONG, w.FILE_DELETE_ON_CLOSE | w.FILE_NON_DIRECTORY_FILE); const path_len_bytes = @intCast(u16, mem.lenZ(sub_path_w) * 2); var nt_name = w.UNICODE_STRING{ .Length = path_len_bytes, .MaximumLength = path_len_bytes, // The Windows API makes this mutable, but it will not mutate here. .Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w)), }; if (sub_path_w[0] == '.' and sub_path_w[1] == 0) { // Windows does not recognize this, but it does work with empty string. nt_name.Length = 0; } if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) { // Can't remove the parent directory with an open handle. return error.FileBusy; } var attr = w.OBJECT_ATTRIBUTES{ .Length = @sizeOf(w.OBJECT_ATTRIBUTES), .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w)) null else dirfd, .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here. .ObjectName = &nt_name, .SecurityDescriptor = null, .SecurityQualityOfService = null, }; var io: w.IO_STATUS_BLOCK = undefined; var tmp_handle: w.HANDLE = undefined; var rc = w.ntdll.NtCreateFile( &tmp_handle, w.SYNCHRONIZE | w.DELETE, &attr, &io, null, 0, w.FILE_SHARE_READ | w.FILE_SHARE_WRITE | w.FILE_SHARE_DELETE, w.FILE_OPEN, create_options_flags, null, 0, ); if (rc == .SUCCESS) { rc = w.ntdll.NtClose(tmp_handle); } switch (rc) { .SUCCESS => return, .OBJECT_NAME_INVALID => unreachable, .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, .INVALID_PARAMETER => unreachable, .FILE_IS_A_DIRECTORY => return error.IsDir, else => return w.unexpectedStatus(rc), } } const RenameError = error{ AccessDenied, FileBusy, DiskQuota, IsDir, SymLinkLoop, LinkQuotaExceeded, NameTooLong, FileNotFound, NotDir, SystemResources, NoSpaceLeft, PathAlreadyExists, ReadOnlyFileSystem, RenameAcrossMountPoints, InvalidUtf8, BadPathName, NoDevice, SharingViolation, PipeBusy, } || UnexpectedError; /// Change the name or location of a file. pub fn rename(old_path: []const u8, new_path: []const u8) RenameError!void { if (builtin.os.tag == .windows) { const old_path_w = try windows.sliceToPrefixedFileW(old_path); const new_path_w = try windows.sliceToPrefixedFileW(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); } } pub const renameC = @compileError("deprecated: renamed to renameZ"); /// Same as `rename` except the parameters are null-terminated byte arrays. pub fn renameZ(old_path: [*:0]const u8, new_path: [*:0]const u8) RenameError!void { if (builtin.os.tag == .windows) { const old_path_w = try windows.cStrToPrefixedFileW(old_path); const new_path_w = try windows.cStrToPrefixedFileW(new_path); return renameW(old_path_w.span().ptr, new_path_w.span().ptr); } 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: [*: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. pub fn renameat( old_dir_fd: fd_t, old_path: []const u8, new_dir_fd: fd_t, new_path: []const u8, ) RenameError!void { if (builtin.os.tag == .windows) { const old_path_w = try windows.sliceToPrefixedFileW(old_path); const new_path_w = try windows.sliceToPrefixedFileW(new_path); return renameatW(old_dir_fd, old_path_w.span(), new_dir_fd, new_path_w.span(), windows.TRUE); } else if (builtin.os.tag == .wasi) { return renameatWasi(old_dir_fd, old_path, new_dir_fd, new_path); } 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); } } /// Same as `renameat` expect only WASI. pub fn renameatWasi(old_dir_fd: fd_t, old_path: []const u8, new_dir_fd: fd_t, new_path: []const u8) RenameError!void { switch (wasi.path_rename(old_dir_fd, old_path.ptr, old_path.len, new_dir_fd, new_path.ptr, new_path.len)) { wasi.ESUCCESS => return, wasi.EACCES => return error.AccessDenied, wasi.EPERM => return error.AccessDenied, wasi.EBUSY => return error.FileBusy, wasi.EDQUOT => return error.DiskQuota, wasi.EFAULT => unreachable, wasi.EINVAL => unreachable, wasi.EISDIR => return error.IsDir, wasi.ELOOP => return error.SymLinkLoop, wasi.EMLINK => return error.LinkQuotaExceeded, wasi.ENAMETOOLONG => return error.NameTooLong, wasi.ENOENT => return error.FileNotFound, wasi.ENOTDIR => return error.NotDir, wasi.ENOMEM => return error.SystemResources, wasi.ENOSPC => return error.NoSpaceLeft, wasi.EEXIST => return error.PathAlreadyExists, wasi.ENOTEMPTY => return error.PathAlreadyExists, wasi.EROFS => return error.ReadOnlyFileSystem, wasi.EXDEV => return error.RenameAcrossMountPoints, else => |err| return unexpectedErrno(err), } } /// Same as `renameat` except the parameters are null-terminated byte arrays. pub fn renameatZ( old_dir_fd: fd_t, old_path: [*:0]const u8, new_dir_fd: fd_t, new_path: [*:0]const u8, ) RenameError!void { if (builtin.os.tag == .windows) { const old_path_w = try windows.cStrToPrefixedFileW(old_path); const new_path_w = try windows.cStrToPrefixedFileW(new_path); return renameatW(old_dir_fd, old_path_w.span(), new_dir_fd, new_path_w.span(), windows.TRUE); } switch (errno(system.renameat(old_dir_fd, old_path, new_dir_fd, 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 `renameat` but Windows-only and the path parameters are /// [WTF-16](https://simonsapin.github.io/wtf-8/#potentially-ill-formed-utf-16) encoded. pub fn renameatW( old_dir_fd: fd_t, old_path_w: []const u16, new_dir_fd: fd_t, new_path_w: []const u16, ReplaceIfExists: windows.BOOLEAN, ) RenameError!void { const src_fd = windows.OpenFile(old_path_w, .{ .dir = old_dir_fd, .access_mask = windows.SYNCHRONIZE | windows.GENERIC_WRITE | windows.DELETE, .creation = windows.FILE_OPEN, .io_mode = .blocking, }) catch |err| switch (err) { error.WouldBlock => unreachable, // Not possible without `.share_access_nonblocking = true`. else => |e| return e, }; defer windows.CloseHandle(src_fd); 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 = @ptrCast(*windows.FILE_RENAME_INFORMATION, &rename_info_buf); rename_info.* = .{ .ReplaceIfExists = ReplaceIfExists, .RootDirectory = if (std.fs.path.isAbsoluteWindowsWTF16(new_path_w)) null else new_dir_fd, .FileNameLength = @intCast(u32, new_path_w.len * 2), // already checked error.NameTooLong .FileName = undefined, }; std.mem.copy(u16, @as([*]u16, &rename_info.FileName)[0..new_path_w.len], new_path_w); var io_status_block: windows.IO_STATUS_BLOCK = undefined; const rc = windows.ntdll.NtSetInformationFile( src_fd, &io_status_block, rename_info, @intCast(u32, struct_len), // already checked for error.NameTooLong .FileRenameInformation, ); switch (rc) { .SUCCESS => return, .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, else => return windows.unexpectedStatus(rc), } } pub const MakeDirError = error{ AccessDenied, DiskQuota, PathAlreadyExists, SymLinkLoop, LinkQuotaExceeded, NameTooLong, FileNotFound, SystemResources, NoSpaceLeft, NotDir, ReadOnlyFileSystem, InvalidUtf8, BadPathName, NoDevice, } || UnexpectedError; pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!void { if (builtin.os.tag == .windows) { const sub_dir_path_w = try windows.sliceToPrefixedFileW(sub_dir_path); return mkdiratW(dir_fd, sub_dir_path_w.span().ptr, mode); } else if (builtin.os.tag == .wasi) { return mkdiratWasi(dir_fd, sub_dir_path, mode); } else { const sub_dir_path_c = try toPosixPath(sub_dir_path); return mkdiratZ(dir_fd, &sub_dir_path_c, mode); } } pub const mkdiratC = @compileError("deprecated: renamed to mkdiratZ"); pub fn mkdiratWasi(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!void { switch (wasi.path_create_directory(dir_fd, sub_dir_path.ptr, sub_dir_path.len)) { wasi.ESUCCESS => return, wasi.EACCES => return error.AccessDenied, wasi.EBADF => unreachable, wasi.EPERM => return error.AccessDenied, wasi.EDQUOT => return error.DiskQuota, wasi.EEXIST => return error.PathAlreadyExists, wasi.EFAULT => unreachable, wasi.ELOOP => return error.SymLinkLoop, wasi.EMLINK => return error.LinkQuotaExceeded, wasi.ENAMETOOLONG => return error.NameTooLong, wasi.ENOENT => return error.FileNotFound, wasi.ENOMEM => return error.SystemResources, wasi.ENOSPC => return error.NoSpaceLeft, wasi.ENOTDIR => return error.NotDir, wasi.EROFS => return error.ReadOnlyFileSystem, else => |err| return unexpectedErrno(err), } } pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirError!void { if (builtin.os.tag == .windows) { const sub_dir_path_w = try windows.cStrToPrefixedFileW(sub_dir_path); return mkdiratW(dir_fd, sub_dir_path_w.span().ptr, mode); } switch (errno(system.mkdirat(dir_fd, sub_dir_path, mode))) { 0 => return, EACCES => return error.AccessDenied, EBADF => unreachable, 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 fn mkdiratW(dir_fd: fd_t, sub_path_w: [*:0]const u16, mode: u32) MakeDirError!void { const sub_dir_handle = try windows.CreateDirectoryW(dir_fd, sub_path_w, null); windows.CloseHandle(sub_dir_handle); } /// Create a directory. /// `mode` is ignored on Windows. pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void { if (builtin.os.tag == .windows) { const sub_dir_handle = try windows.CreateDirectory(null, dir_path, null); windows.CloseHandle(sub_dir_handle); return; } else { const dir_path_c = try toPosixPath(dir_path); return mkdirZ(&dir_path_c, mode); } } /// Same as `mkdir` but the parameter is a null-terminated UTF8-encoded string. pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void { if (builtin.os.tag == .windows) { const dir_path_w = try windows.cStrToPrefixedFileW(dir_path); const sub_dir_handle = try windows.CreateDirectoryW(null, dir_path_w.span().ptr, null); windows.CloseHandle(sub_dir_handle); return; } 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, } || UnexpectedError; /// Deletes an empty directory. pub fn rmdir(dir_path: []const u8) DeleteDirError!void { if (builtin.os.tag == .windows) { const dir_path_w = try windows.sliceToPrefixedFileW(dir_path); return windows.RemoveDirectoryW(dir_path_w.span().ptr); } else { const dir_path_c = try toPosixPath(dir_path); return rmdirZ(&dir_path_c); } } pub const rmdirC = @compileError("deprecated: renamed to rmdirZ"); /// Same as `rmdir` except the parameter is null-terminated. pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void { if (builtin.os.tag == .windows) { const dir_path_w = try windows.cStrToPrefixedFileW(dir_path); return windows.RemoveDirectoryW(dir_path_w.span().ptr); } 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, } || UnexpectedError; /// 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 (builtin.os.tag == .windows) { 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 chdirZ(&dir_path_c); } } pub const chdirC = @compileError("deprecated: renamed to chdirZ"); /// Same as `chdir` except the parameter is null-terminated. pub fn chdirZ(dir_path: [*:0]const u8) ChangeCurDirError!void { if (builtin.os.tag == .windows) { 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 FchdirError = error{ AccessDenied, NotDir, FileSystem, } || UnexpectedError; pub fn fchdir(dirfd: fd_t) FchdirError!void { while (true) { switch (errno(system.fchdir(dirfd))) { 0 => return, EACCES => return error.AccessDenied, EBADF => unreachable, ENOTDIR => return error.NotDir, EINTR => continue, EIO => return error.FileSystem, else => |err| return unexpectedErrno(err), } } } pub const ReadLinkError = error{ AccessDenied, FileSystem, SymLinkLoop, NameTooLong, FileNotFound, SystemResources, NotDir, } || UnexpectedError; /// 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 (builtin.os.tag == .windows) { 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 readlinkZ(&file_path_c, out_buffer); } } pub const readlinkC = @compileError("deprecated: renamed to readlinkZ"); /// Same as `readlink` except `file_path` is null-terminated. pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 { if (builtin.os.tag == .windows) { const file_path_w = try windows.cStrToPrefixedFileW(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 readlinkatC = @compileError("deprecated: renamed to readlinkatZ"); pub fn readlinkatZ(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 { if (builtin.os.tag == .windows) { const file_path_w = try windows.cStrToPrefixedFileW(file_path); @compileError("TODO implement readlink for Windows"); } const rc = system.readlinkat(dirfd, 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, } || UnexpectedError; 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 (builtin.os.tag == .windows) { if (isCygwinPty(handle)) return true; var out: windows.DWORD = undefined; return windows.kernel32.GetConsoleMode(handle, &out) != 0; } if (builtin.link_libc) { return system.isatty(handle) != 0; } if (builtin.os.tag == .wasi) { var statbuf: fdstat_t = undefined; const err = system.fd_fdstat_get(handle, &statbuf); if (err != 0) { // errno = err; return false; } // A tty is a character device that we can't seek or tell on. if (statbuf.fs_filetype != FILETYPE_CHARACTER_DEVICE or (statbuf.fs_rights_base & (RIGHT_FD_SEEK | RIGHT_FD_TELL)) != 0) { // errno = ENOTTY; return false; } return true; } if (builtin.os.tag == .linux) { var wsz: linux.winsize = undefined; return linux.syscall3(.ioctl, @bitCast(usize, @as(isize, handle)), linux.TIOCGWINSZ, @ptrToInt(&wsz)) == 0; } unreachable; } pub fn isCygwinPty(handle: fd_t) bool { if (builtin.os.tag != .windows) 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 + @as(usize, name_info.FileNameLength)]; const name_wide = mem.bytesAsSlice(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, /// The socket type is not supported by the protocol. SocketTypeNotSupported, } || UnexpectedError; pub fn socket(domain: u32, socket_type: u32, protocol: u32) SocketError!fd_t { const have_sock_flags = comptime !std.Target.current.isDarwin(); const filtered_sock_type = if (!have_sock_flags) socket_type & ~@as(u32, SOCK_NONBLOCK | SOCK_CLOEXEC) else socket_type; const rc = system.socket(domain, filtered_sock_type, protocol); switch (errno(rc)) { 0 => { const fd = @intCast(fd_t, rc); if (!have_sock_flags) { try setSockFlags(fd, socket_type); } return fd; }, EACCES => return error.PermissionDenied, EAFNOSUPPORT => return error.AddressFamilyNotSupported, EINVAL => return error.ProtocolFamilyNotAvailable, EMFILE => return error.ProcessFdQuotaExceeded, ENFILE => return error.SystemFdQuotaExceeded, ENOBUFS => return error.SystemResources, ENOMEM => return error.SystemResources, EPROTONOSUPPORT => return error.ProtocolNotSupported, EPROTOTYPE => return error.SocketTypeNotSupported, 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, } || UnexpectedError; /// addr is `*const T` where T is one of the sockaddr pub fn bind(sockfd: fd_t, addr: *const sockaddr, len: socklen_t) BindError!void { const rc = system.bind(sockfd, addr, len); 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, // invalid parameters ENOTSOCK => unreachable, // invalid `sockfd` EADDRNOTAVAIL => return error.AddressNotAvailable, EFAULT => unreachable, // invalid `addr` pointer 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, } || UnexpectedError; pub fn listen(sockfd: fd_t, 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, ProtocolFailure, /// Firewall rules forbid connection. BlockedByFirewall, /// This error occurs when no global event loop is configured, /// and accepting from the socket would block. WouldBlock, /// Permission to create a socket of the specified type and/or /// protocol is denied. PermissionDenied, } || UnexpectedError; /// Accept a connection on a socket. /// If the application has a global event loop enabled, EAGAIN is handled /// via the event loop. Otherwise EAGAIN results in error.WouldBlock. pub fn accept( /// This argument is a socket that has been created with `socket`, bound to a local address /// with `bind`, and is listening for connections after a `listen`. sockfd: fd_t, /// This argument is a pointer to a sockaddr structure. This structure is filled in with the /// address of the peer socket, as known to the communications layer. The exact format of the /// address returned addr is determined by the socket's address family (see `socket` and the /// respective protocol man pages). addr: *sockaddr, /// This argument is a value-result argument: the caller must initialize it to contain the /// size (in bytes) of the structure pointed to by addr; on return it will contain the actual size /// of the peer address. /// /// The returned address is truncated if the buffer provided is too small; in this case, `addr_size` /// will return a value greater than was supplied to the call. addr_size: *socklen_t, /// The following values can be bitwise ORed in flags to obtain different behavior: /// * `SOCK_NONBLOCK` - Set the `O_NONBLOCK` file status flag on the open file description (see `open`) /// referred to by the new file descriptor. Using this flag saves extra calls to `fcntl` to achieve /// the same result. /// * `SOCK_CLOEXEC` - Set the close-on-exec (`FD_CLOEXEC`) flag on the new file descriptor. See the /// description of the `O_CLOEXEC` flag in `open` for reasons why this may be useful. flags: u32, ) AcceptError!fd_t { const have_accept4 = comptime !std.Target.current.isDarwin(); assert(0 == (flags & ~@as(u32, SOCK_NONBLOCK | SOCK_CLOEXEC))); // Unsupported flag(s) while (true) { const rc = if (have_accept4) system.accept4(sockfd, addr, addr_size, flags) else system.accept(sockfd, addr, addr_size); switch (errno(rc)) { 0 => { const fd = @intCast(fd_t, rc); if (!have_accept4) { try setSockFlags(fd, flags); } return fd; }, EINTR => continue, EAGAIN => if (std.event.Loop.instance) |loop| { loop.waitUntilFdReadable(sockfd); continue; } else { return error.WouldBlock; }, EBADF => unreachable, // always a race condition ECONNABORTED => return error.ConnectionAborted, EFAULT => unreachable, EINVAL => unreachable, ENOTSOCK => unreachable, EMFILE => return error.ProcessFdQuotaExceeded, ENFILE => return error.SystemFdQuotaExceeded, ENOBUFS => return error.SystemResources, ENOMEM => return error.SystemResources, EOPNOTSUPP => unreachable, EPROTO => return error.ProtocolFailure, EPERM => return error.BlockedByFirewall, else => |err| return unexpectedErrno(err), } } } 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)) { 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, } || UnexpectedError; 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, } || UnexpectedError; 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, } || UnexpectedError; pub fn getsockname(sockfd: fd_t, addr: *sockaddr, addrlen: *socklen_t) GetSockNameError!void { switch (errno(system.getsockname(sockfd, addr, addrlen))) { 0 => return, 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, /// This error occurs when no global event loop is configured, /// and connecting to the socket would block. WouldBlock, /// The given path for the unix socket does not exist. FileNotFound, } || UnexpectedError; /// Initiate a connection on a socket. pub fn connect(sockfd: fd_t, sock_addr: *const 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, EINPROGRESS => { const loop = std.event.Loop.instance orelse return error.WouldBlock; loop.waitUntilFdWritable(sockfd); return getsockoptError(sockfd); }, 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. 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, ENOENT => return error.FileNotFound, // Returned when socket is AF_UNIX and the given path does not exist. else => |err| return unexpectedErrno(err), } } } pub fn getsockoptError(sockfd: fd_t) 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, AccessDenied, } || UnexpectedError; pub fn fstat(fd: fd_t) FStatError!Stat { if (builtin.os.tag == .wasi) { var stat: wasi.filestat_t = undefined; switch (wasi.fd_filestat_get(fd, &stat)) { wasi.ESUCCESS => return Stat.fromFilestat(stat), wasi.EINVAL => unreachable, wasi.EBADF => unreachable, // Always a race condition. wasi.ENOMEM => return error.SystemResources, wasi.EACCES => return error.AccessDenied, else => |err| return unexpectedErrno(err), } } var stat: Stat = undefined; switch (errno(system.fstat(fd, &stat))) { 0 => return stat, EINVAL => unreachable, EBADF => unreachable, // Always a race condition. ENOMEM => return error.SystemResources, EACCES => return error.AccessDenied, else => |err| return unexpectedErrno(err), } } pub const FStatAtError = FStatError || error{ NameTooLong, FileNotFound }; pub fn fstatat(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!Stat { const pathname_c = try toPosixPath(pathname); return fstatatZ(dirfd, &pathname_c, flags); } pub const fstatatC = @compileError("deprecated: renamed to fstatatZ"); pub fn fstatatZ(dirfd: fd_t, pathname: [*:0]const u8, flags: u32) FStatAtError!Stat { var stat: Stat = undefined; switch (errno(system.fstatat(dirfd, pathname, &stat, flags))) { 0 => return stat, EINVAL => unreachable, EBADF => unreachable, // Always a race condition. ENOMEM => return error.SystemResources, EACCES => return error.AccessDenied, EFAULT => unreachable, ENAMETOOLONG => return error.NameTooLong, ENOENT => return error.FileNotFound, ENOTDIR => return error.FileNotFound, 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)) { 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, } || UnexpectedError; /// 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, } || 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); } pub const inotify_add_watchC = @compileError("deprecated: renamed to inotify_add_watchZ"); /// 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)) { 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, } || UnexpectedError; /// `memory.len` must be page-aligned. pub fn mprotect(memory: []align(mem.page_size) u8, protection: u32) MProtectError!void { assert(mem.isAligned(memory.len, mem.page_size)); 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} || UnexpectedError; 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, } || UnexpectedError; /// Map files or devices into memory. /// `length` does not need to be aligned. /// Use of a mapped region can result in these signals: /// * SIGSEGV - Attempted write into a region mapped as read-only. /// * SIGBUS - Attempted access to a portion of the buffer that does not correspond to the file pub fn mmap( ptr: ?[*]align(mem.page_size) u8, length: usize, prot: u32, flags: u32, fd: fd_t, offset: u64, ) MMapError![]align(mem.page_size) u8 { const 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, FileBusy, SymLinkLoop, ReadOnlyFileSystem, /// On Windows, file paths must be valid Unicode. InvalidUtf8, } || UnexpectedError; /// 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 (builtin.os.tag == .windows) { const path_w = try windows.sliceToPrefixedFileW(path); _ = try windows.GetFileAttributesW(path_w.span().ptr); return; } const path_c = try toPosixPath(path); return accessZ(&path_c, mode); } pub const accessC = @compileError("Deprecated in favor of `accessZ`"); /// Same as `access` except `path` is null-terminated. pub fn accessZ(path: [*:0]const u8, mode: u32) AccessError!void { if (builtin.os.tag == .windows) { const path_w = try windows.cStrToPrefixedFileW(path); _ = try windows.GetFileAttributesW(path_w.span().ptr); return; } switch (errno(system.access(path, mode))) { 0 => return, EACCES => return error.PermissionDenied, EROFS => return error.ReadOnlyFileSystem, ELOOP => return error.SymLinkLoop, ETXTBSY => return error.FileBusy, 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: [*:0]const u16, mode: u32) windows.GetFileAttributesError!void { const ret = try windows.GetFileAttributesW(path); if (ret != windows.INVALID_FILE_ATTRIBUTES) { return; } switch (windows.kernel32.GetLastError()) { .FILE_NOT_FOUND => return error.FileNotFound, .PATH_NOT_FOUND => return error.FileNotFound, .ACCESS_DENIED => return error.PermissionDenied, else => |err| return windows.unexpectedError(err), } } /// Check user's permissions for a file, based on an open directory handle. /// TODO currently this ignores `mode` and `flags` on Windows. pub fn faccessat(dirfd: fd_t, path: []const u8, mode: u32, flags: u32) AccessError!void { if (builtin.os.tag == .windows) { const path_w = try windows.sliceToPrefixedFileW(path); return faccessatW(dirfd, path_w.span().ptr, mode, flags); } const path_c = try toPosixPath(path); return faccessatZ(dirfd, &path_c, mode, flags); } /// Same as `faccessat` except the path parameter is null-terminated. pub fn faccessatZ(dirfd: fd_t, path: [*:0]const u8, mode: u32, flags: u32) AccessError!void { if (builtin.os.tag == .windows) { const path_w = try windows.cStrToPrefixedFileW(path); return faccessatW(dirfd, path_w.span().ptr, mode, flags); } switch (errno(system.faccessat(dirfd, path, mode, flags))) { 0 => return, EACCES => return error.PermissionDenied, EROFS => return error.ReadOnlyFileSystem, ELOOP => return error.SymLinkLoop, ETXTBSY => return error.FileBusy, 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), } } /// Same as `faccessat` except asserts the target is Windows and the path parameter /// is NtDll-prefixed, null-terminated, WTF-16 encoded. /// TODO currently this ignores `mode` and `flags` pub fn faccessatW(dirfd: fd_t, sub_path_w: [*:0]const u16, mode: u32, flags: u32) AccessError!void { if (sub_path_w[0] == '.' and sub_path_w[1] == 0) { return; } if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) { return; } const path_len_bytes = math.cast(u16, mem.lenZ(sub_path_w) * 2) catch |err| switch (err) { error.Overflow => return error.NameTooLong, }; var nt_name = windows.UNICODE_STRING{ .Length = path_len_bytes, .MaximumLength = path_len_bytes, .Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w)), }; var attr = windows.OBJECT_ATTRIBUTES{ .Length = @sizeOf(windows.OBJECT_ATTRIBUTES), .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w)) null else dirfd, .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here. .ObjectName = &nt_name, .SecurityDescriptor = null, .SecurityQualityOfService = null, }; var basic_info: windows.FILE_BASIC_INFORMATION = undefined; switch (windows.ntdll.NtQueryAttributesFile(&attr, &basic_info)) { .SUCCESS => return, .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, .INVALID_PARAMETER => unreachable, .ACCESS_DENIED => return error.PermissionDenied, .OBJECT_PATH_SYNTAX_BAD => unreachable, else => |rc| return windows.unexpectedStatus(rc), } } pub const PipeError = error{ SystemFdQuotaExceeded, ProcessFdQuotaExceeded, } || UnexpectedError; /// Creates a unidirectional data channel that can be used for interprocess communication. pub fn pipe() PipeError![2]fd_t { var fds: [2]fd_t = undefined; switch (errno(system.pipe(&fds))) { 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 { if (comptime std.Target.current.isDarwin()) { var fds: [2]fd_t = try pipe(); if (flags == 0) return fds; errdefer { close(fds[0]); close(fds[1]); } for (fds) |fd| switch (errno(system.fcntl(fd, F_SETFL, flags))) { 0 => {}, EINVAL => unreachable, // Invalid flags EBADF => unreachable, // Always a race condition else => |err| return unexpectedErrno(err), }; return fds; } 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, UnknownName, } || UnexpectedError; pub fn sysctl( name: []const c_int, oldp: ?*c_void, oldlenp: ?*usize, newp: ?*c_void, newlen: usize, ) SysCtlError!void { if (builtin.os.tag == .wasi) { @panic("unsupported"); } 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, ENOENT => return error.UnknownName, else => |err| return unexpectedErrno(err), } } pub const sysctlbynameC = @compileError("deprecated: renamed to sysctlbynameZ"); pub fn sysctlbynameZ( name: [*:0]const u8, oldp: ?*c_void, oldlenp: ?*usize, newp: ?*c_void, newlen: usize, ) SysCtlError!void { if (builtin.os.tag == .wasi) { @panic("unsupported"); } switch (errno(system.sysctlbyname(name, oldp, oldlenp, newp, newlen))) { 0 => return, EFAULT => unreachable, EPERM => return error.PermissionDenied, ENOMEM => return error.SystemResources, ENOENT => return error.UnknownName, 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} || UnexpectedError; /// Repositions read/write file offset relative to the beginning. pub fn lseek_SET(fd: fd_t, offset: u64) SeekError!void { if (builtin.os.tag == .linux and !builtin.link_libc and @sizeOf(usize) == 4) { var result: u64 = undefined; switch (errno(system.llseek(fd, offset, &result, SEEK_SET))) { 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 (builtin.os.tag == .windows) { return windows.SetFilePointerEx_BEGIN(fd, offset); } if (builtin.os.tag == .wasi) { var new_offset: wasi.filesize_t = undefined; switch (wasi.fd_seek(fd, @bitCast(wasi.filedelta_t, offset), wasi.WHENCE_SET, &new_offset)) { wasi.ESUCCESS => return, wasi.EBADF => unreachable, // always a race condition wasi.EINVAL => return error.Unseekable, wasi.EOVERFLOW => return error.Unseekable, wasi.ESPIPE => return error.Unseekable, wasi.ENXIO => return error.Unseekable, else => |err| return unexpectedErrno(err), } } 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 (builtin.os.tag == .linux 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 (builtin.os.tag == .windows) { return windows.SetFilePointerEx_CURRENT(fd, offset); } if (builtin.os.tag == .wasi) { var new_offset: wasi.filesize_t = undefined; switch (wasi.fd_seek(fd, offset, wasi.WHENCE_CUR, &new_offset)) { wasi.ESUCCESS => return, wasi.EBADF => unreachable, // always a race condition wasi.EINVAL => return error.Unseekable, wasi.EOVERFLOW => return error.Unseekable, wasi.ESPIPE => return error.Unseekable, wasi.ENXIO => return error.Unseekable, else => |err| return unexpectedErrno(err), } } 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 (builtin.os.tag == .linux 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 (builtin.os.tag == .windows) { return windows.SetFilePointerEx_END(fd, offset); } if (builtin.os.tag == .wasi) { var new_offset: wasi.filesize_t = undefined; switch (wasi.fd_seek(fd, offset, wasi.WHENCE_END, &new_offset)) { wasi.ESUCCESS => return, wasi.EBADF => unreachable, // always a race condition wasi.EINVAL => return error.Unseekable, wasi.EOVERFLOW => return error.Unseekable, wasi.ESPIPE => return error.Unseekable, wasi.ENXIO => return error.Unseekable, else => |err| return unexpectedErrno(err), } } 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 (builtin.os.tag == .linux and !builtin.link_libc and @sizeOf(usize) == 4) { var result: u64 = undefined; switch (errno(system.llseek(fd, 0, &result, SEEK_CUR))) { 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 (builtin.os.tag == .windows) { return windows.SetFilePointerEx_CURRENT_get(fd); } if (builtin.os.tag == .wasi) { var new_offset: wasi.filesize_t = undefined; switch (wasi.fd_seek(fd, 0, wasi.WHENCE_CUR, &new_offset)) { wasi.ESUCCESS => return new_offset, wasi.EBADF => unreachable, // always a race condition wasi.EINVAL => return error.Unseekable, wasi.EOVERFLOW => return error.Unseekable, wasi.ESPIPE => return error.Unseekable, wasi.ENXIO => return error.Unseekable, else => |err| return unexpectedErrno(err), } } 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 FcntlError = error{ PermissionDenied, FileBusy, ProcessFdQuotaExceeded, Locked, } || 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)) { 0 => return @intCast(usize, rc), EINTR => continue, EACCES => return error.Locked, EBADF => unreachable, EBUSY => return error.FileBusy, EINVAL => unreachable, // invalid parameters EPERM => return error.PermissionDenied, EMFILE => return error.ProcessFdQuotaExceeded, ENOTDIR => unreachable, // invalid parameter else => |err| return unexpectedErrno(err), } } } fn setSockFlags(fd: fd_t, flags: u32) !void { if ((flags & SOCK_CLOEXEC) != 0) { var fd_flags = fcntl(fd, F_GETFD, 0) catch |err| switch (err) { error.FileBusy => unreachable, error.Locked => unreachable, else => |e| return e, }; fd_flags |= FD_CLOEXEC; _ = fcntl(fd, F_SETFD, fd_flags) catch |err| switch (err) { error.FileBusy => unreachable, error.Locked => unreachable, else => |e| return e, }; } if ((flags & SOCK_NONBLOCK) != 0) { var fl_flags = fcntl(fd, F_GETFL, 0) catch |err| switch (err) { error.FileBusy => unreachable, error.Locked => unreachable, else => |e| return e, }; fl_flags |= O_NONBLOCK; _ = fcntl(fd, F_SETFL, fl_flags) catch |err| switch (err) { error.FileBusy => unreachable, error.Locked => unreachable, else => |e| return e, }; } } pub const FlockError = error{ WouldBlock, /// The kernel ran out of memory for allocating file locks SystemResources, } || UnexpectedError; pub fn flock(fd: fd_t, operation: i32) FlockError!void { while (true) { const rc = system.flock(fd, operation); switch (errno(rc)) { 0 => return, EBADF => unreachable, EINTR => continue, EINVAL => unreachable, // invalid parameters ENOLCK => return error.SystemResources, EWOULDBLOCK => return error.WouldBlock, // TODO: integrate with async instead of just returning an error 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, } || UnexpectedError; /// 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 (builtin.os.tag == .windows) { const pathname_w = try windows.sliceToPrefixedFileW(pathname); return realpathW(pathname_w.span().ptr, out_buffer); } if (builtin.os.tag == .wasi) { @compileError("Use std.fs.wasi.PreopenList to obtain valid Dir handles instead of using absolute paths"); } const pathname_c = try toPosixPath(pathname); return realpathZ(&pathname_c, out_buffer); } pub const realpathC = @compileError("deprecated: renamed realpathZ"); /// Same as `realpath` except `pathname` is null-terminated. pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 { if (builtin.os.tag == .windows) { const pathname_w = try windows.cStrToPrefixedFileW(pathname); return realpathW(pathname_w.span().ptr, out_buffer); } if (builtin.os.tag == .linux and !builtin.link_libc) { const fd = openZ(pathname, linux.O_PATH | linux.O_NONBLOCK | linux.O_CLOEXEC, 0) catch |err| switch (err) { error.FileLocksNotSupported => unreachable, else => |e| return e, }; defer close(fd); var procfs_buf: ["/proc/self/fd/-2147483648".len:0]u8 = undefined; const proc_path = std.fmt.bufPrint(procfs_buf[0..], "/proc/self/fd/{}\x00", .{fd}) catch unreachable; return readlinkZ(@ptrCast([*:0]const u8, 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.spanZ(result_path); } /// Same as `realpath` except `pathname` is null-terminated and UTF16LE-encoded. /// TODO use ntdll for better semantics pub fn realpathW(pathname: [*:0]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_FLAG_BACKUP_SEMANTICS, null, ); defer windows.CloseHandle(h_file); var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined; const wide_slice = try windows.GetFinalPathNameByHandleW(h_file, &wide_buf, wide_buf.len, windows.VOLUME_NAME_DOS); // 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( context: var, comptime Error: type, comptime callback: fn (info: *dl_phdr_info, size: usize, context: @TypeOf(context)) Error!void, ) Error!void { const Context = @TypeOf(context); if (builtin.object_format != .elf) @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: ?*c_void) callconv(.C) c_int { const context_ptr = @ptrCast(*const Context, @alignCast(@alignOf(*const Context), data)); callback(info, size, context_ptr.*) catch |err| return @errorToInt(err); return 0; } }.callbackC, @intToPtr(?*c_void, @ptrToInt(&context)))) { 0 => return, else => |err| return @errSetCast(Error, @intToError(@intCast(u16, err))), // TODO don't hardcode u16 } } 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 = 0, .dlpi_name = "/proc/self/exe", .dlpi_phdr = phdrs.ptr, .dlpi_phnum = ehdr.e_phnum, }; return callback(&info, @sizeOf(dl_phdr_info), context); } // Last return value from the callback function while (it.next()) |entry| { var dlpi_phdr: [*]elf.Phdr = undefined; var dlpi_phnum: u16 = undefined; if (entry.l_addr != 0) { const elf_header = @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, }; try callback(&info, @sizeOf(dl_phdr_info), context); } } pub const ClockGetTimeError = error{UnsupportedClock} || UnexpectedError; /// TODO: change this to return the timespec as a return value /// TODO: look into making clk_id an enum pub fn clock_gettime(clk_id: i32, tp: *timespec) ClockGetTimeError!void { if (std.Target.current.os.tag == .wasi) { var ts: timestamp_t = undefined; switch (system.clock_time_get(@bitCast(u32, clk_id), 1, &ts)) { 0 => { tp.* = .{ .tv_sec = @intCast(i64, ts / std.time.ns_per_s), .tv_nsec = @intCast(isize, ts % std.time.ns_per_s), }; }, EINVAL => return error.UnsupportedClock, else => |err| return unexpectedErrno(err), } return; } if (std.Target.current.os.tag == .windows) { if (clk_id == CLOCK_REALTIME) { var ft: windows.FILETIME = undefined; windows.kernel32.GetSystemTimeAsFileTime(&ft); // FileTime has a granularity of 100 nanoseconds and uses the NTFS/Windows epoch. const ft64 = (@as(u64, ft.dwHighDateTime) << 32) | ft.dwLowDateTime; const ft_per_s = std.time.ns_per_s / 100; tp.* = .{ .tv_sec = @intCast(i64, ft64 / ft_per_s) + std.time.epoch.windows, .tv_nsec = @intCast(c_long, ft64 % ft_per_s) * 100, }; return; } else { // TODO POSIX implementation of CLOCK_MONOTONIC on Windows. return error.UnsupportedClock; } } switch (errno(system.clock_gettime(clk_id, tp))) { 0 => return, EFAULT => unreachable, EINVAL => return error.UnsupportedClock, else => |err| return unexpectedErrno(err), } } pub fn clock_getres(clk_id: i32, res: *timespec) ClockGetTimeError!void { if (std.Target.current.os.tag == .wasi) { var ts: timestamp_t = undefined; switch (system.clock_res_get(@bitCast(u32, clk_id), &ts)) { 0 => res.* = .{ .tv_sec = @intCast(i64, ts / std.time.ns_per_s), .tv_nsec = @intCast(isize, ts % std.time.ns_per_s), }, EINVAL => return error.UnsupportedClock, else => |err| return unexpectedErrno(err), } return; } 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} || 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))) { 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 - 1:0]u8 { if (std.debug.runtime_safety) assert(std.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; 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, } || UnexpectedError; pub fn sigaltstack(ss: ?*stack_t, old_ss: ?*stack_t) SigaltstackError!void { 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, } || UnexpectedError; pub fn futimens(fd: fd_t, times: *const [2]timespec) FutimensError!void { if (builtin.os.tag == .wasi) { // TODO WASI encodes `wasi.fstflags` to signify magic values // similar to UTIME_NOW and UTIME_OMIT. Currently, we ignore // this here, but we should really handle it somehow. const atim = times[0].toTimestamp(); const mtim = times[1].toTimestamp(); switch (wasi.fd_filestat_set_times(fd, atim, mtim, wasi.FILESTAT_SET_ATIM | wasi.FILESTAT_SET_MTIM)) { wasi.ESUCCESS => return, wasi.EACCES => return error.AccessDenied, wasi.EPERM => return error.PermissionDenied, wasi.EBADF => unreachable, // always a race condition wasi.EFAULT => unreachable, wasi.EINVAL => unreachable, wasi.EROFS => return error.ReadOnlyFileSystem, else => |err| return unexpectedErrno(err), } } 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} || 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))) { 0 => return mem.spanZ(@ptrCast([*:0]u8, name_buffer)), EFAULT => unreachable, ENAMETOOLONG => unreachable, // HOST_NAME_MAX prevents this EPERM => return error.PermissionDenied, else => |err| return unexpectedErrno(err), } } if (builtin.os.tag == .linux) { const uts = uname(); const hostname = mem.spanZ(@ptrCast([*:0]const u8, &uts.nodename)); mem.copy(u8, name_buffer, hostname); return name_buffer[0..hostname.len]; } @compileError("TODO implement gethostname for this OS"); } pub fn uname() utsname { var uts: utsname = undefined; switch (errno(system.uname(&uts))) { 0 => return uts, EFAULT => unreachable, else => unreachable, } } pub fn res_mkquery( op: u4, dname: []const u8, class: u8, ty: u8, data: []const u8, newrr: ?[*]const u8, buf: []u8, ) usize { // This implementation is ported from musl libc. // A more idiomatic "ziggy" implementation would be welcome. var name = dname; if (mem.endsWith(u8, name, ".")) name.len -= 1; assert(name.len <= 253); const n = 17 + name.len + @boolToInt(name.len != 0); // Construct query template - ID will be filled later var q: [280]u8 = undefined; @memset(&q, 0, n); q[2] = @as(u8, op) * 8 + 1; q[5] = 1; mem.copy(u8, q[13..], name); var i: usize = 13; var j: usize = undefined; while (q[i] != 0) : (i = j + 1) { j = i; while (q[j] != 0 and q[j] != '.') : (j += 1) {} // TODO determine the circumstances for this and whether or // not this should be an error. if (j - i - 1 > 62) unreachable; q[i - 1] = @intCast(u8, j - i); } q[i + 1] = ty; q[i + 3] = class; // Make a reasonably unpredictable id var ts: timespec = undefined; clock_gettime(CLOCK_REALTIME, &ts) catch {}; const UInt = std.meta.Int(false, @TypeOf(ts.tv_nsec).bit_count); const unsec = @bitCast(UInt, ts.tv_nsec); const id = @truncate(u32, unsec + unsec / 65536); q[0] = @truncate(u8, id / 256); q[1] = @truncate(u8, id); mem.copy(u8, buf, q[0..n]); return n; } pub const SendError = error{ /// (For UNIX domain sockets, which are identified by pathname) Write permission is denied /// on the destination socket file, or search permission is denied for one of the /// directories the path prefix. (See path_resolution(7).) /// (For UDP sockets) An attempt was made to send to a network/broadcast address as though /// it was a unicast address. AccessDenied, /// The socket is marked nonblocking and the requested operation would block, and /// there is no global event loop configured. /// It's also possible to get this error under the following condition: /// (Internet domain datagram sockets) The socket referred to by sockfd had not previously /// been bound to an address and, upon attempting to bind it to an ephemeral port, it was /// determined that all port numbers in the ephemeral port range are currently in use. See /// the discussion of /proc/sys/net/ipv4/ip_local_port_range in ip(7). WouldBlock, /// Another Fast Open is already in progress. FastOpenAlreadyInProgress, /// Connection reset by peer. ConnectionResetByPeer, /// The socket type requires that message be sent atomically, and the size of the message /// to be sent made this impossible. The message is not transmitted. MessageTooBig, /// The output queue for a network interface was full. This generally indicates that the /// interface has stopped sending, but may be caused by transient congestion. (Normally, /// this does not occur in Linux. Packets are just silently dropped when a device queue /// overflows.) /// This is also caused when there is not enough kernel memory available. SystemResources, /// The local end has been shut down on a connection oriented socket. In this case, the /// process will also receive a SIGPIPE unless MSG_NOSIGNAL is set. BrokenPipe, } || UnexpectedError; /// Transmit a message to another socket. /// /// The `sendto` call may be used only when the socket is in a connected state (so that the intended /// recipient is known). The following call /// /// send(sockfd, buf, len, flags); /// /// is equivalent to /// /// sendto(sockfd, buf, len, flags, NULL, 0); /// /// If sendto() is used on a connection-mode (`SOCK_STREAM`, `SOCK_SEQPACKET`) socket, the arguments /// `dest_addr` and `addrlen` are asserted to be `null` and `0` respectively, and asserted /// that the socket was actually connected. /// Otherwise, the address of the target is given by `dest_addr` with `addrlen` specifying its size. /// /// If the message is too long to pass atomically through the underlying protocol, /// `SendError.MessageTooBig` is returned, and the message is not transmitted. /// /// There is no indication of failure to deliver. /// /// When the message does not fit into the send buffer of the socket, `sendto` normally blocks, /// unless the socket has been placed in nonblocking I/O mode. In nonblocking mode it would fail /// with `SendError.WouldBlock`. The `select` call may be used to determine when it is /// possible to send more data. pub fn sendto( /// The file descriptor of the sending socket. sockfd: fd_t, /// Message to send. buf: []const u8, flags: u32, dest_addr: ?*const sockaddr, addrlen: socklen_t, ) SendError!usize { while (true) { const rc = system.sendto(sockfd, buf.ptr, buf.len, flags, dest_addr, addrlen); switch (errno(rc)) { 0 => return @intCast(usize, rc), EACCES => return error.AccessDenied, EAGAIN => if (std.event.Loop.instance) |loop| { loop.waitUntilFdWritable(sockfd); continue; } else { return error.WouldBlock; }, EALREADY => return error.FastOpenAlreadyInProgress, EBADF => unreachable, // always a race condition ECONNRESET => return error.ConnectionResetByPeer, EDESTADDRREQ => unreachable, // The socket is not connection-mode, and no peer address is set. EFAULT => unreachable, // An invalid user space address was specified for an argument. EINTR => continue, EINVAL => unreachable, // Invalid argument passed. EISCONN => unreachable, // connection-mode socket was connected already but a recipient was specified EMSGSIZE => return error.MessageTooBig, ENOBUFS => return error.SystemResources, ENOMEM => return error.SystemResources, ENOTCONN => unreachable, // The socket is not connected, and no target has been given. ENOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket. EOPNOTSUPP => unreachable, // Some bit in the flags argument is inappropriate for the socket type. EPIPE => return error.BrokenPipe, 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: fd_t, buf: []const u8, flags: u32, ) SendError!usize { return sendto(sockfd, buf, flags, null, 0); } pub const SendFileError = PReadError || WriteError || SendError; fn count_iovec_bytes(iovs: []const iovec_const) usize { var count: usize = 0; for (iovs) |iov| { count += iov.iov_len; } return count; } /// Transfer data between file descriptors, with optional headers and trailers. /// Returns the number of bytes written, which can be zero. /// /// The `sendfile` call copies `in_len` bytes from one file descriptor to another. When possible, /// this is done within the operating system kernel, which can provide better performance /// characteristics than transferring data from kernel to user space and back, such as with /// `read` and `write` calls. When `in_len` is `0`, it means to copy until the end of the input file has been /// reached. Note, however, that partial writes are still possible in this case. /// /// `in_fd` must be a file descriptor opened for reading, and `out_fd` must be a file descriptor /// opened for writing. They may be any kind of file descriptor; however, if `in_fd` is not a regular /// file system file, it may cause this function to fall back to calling `read` and `write`, in which case /// atomicity guarantees no longer apply. /// /// Copying begins reading at `in_offset`. The input file descriptor seek position is ignored and not updated. /// If the output file descriptor has a seek position, it is updated as bytes are written. When /// `in_offset` is past the end of the input file, it successfully reads 0 bytes. /// /// `flags` has different meanings per operating system; refer to the respective man pages. /// /// These systems support atomically sending everything, including headers and trailers: /// * macOS /// * FreeBSD /// /// These systems support in-kernel data copying, but headers and trailers are not sent atomically: /// * Linux /// /// Other systems fall back to calling `read` / `write`. /// /// Linux has a limit on how many bytes may be transferred in one `sendfile` call, which is `0x7ffff000` /// on both 64-bit and 32-bit systems. This is due to using a signed C int as the return value, as /// well as stuffing the errno codes into the last `4096` values. This is cited on the `sendfile` man page. /// The corresponding POSIX limit on this is `math.maxInt(isize)`. pub fn sendfile( out_fd: fd_t, in_fd: fd_t, in_offset: u64, in_len: u64, headers: []const iovec_const, trailers: []const iovec_const, flags: u32, ) SendFileError!usize { var header_done = false; var total_written: usize = 0; // Prevents EOVERFLOW. const size_t = @Type(std.builtin.TypeInfo{ .Int = .{ .is_signed = false, .bits = @typeInfo(usize).Int.bits - 1, }, }); const max_count = switch (std.Target.current.os.tag) { .linux => 0x7ffff000, else => math.maxInt(size_t), }; switch (std.Target.current.os.tag) { .linux => sf: { // sendfile() first appeared in Linux 2.2, glibc 2.1. const call_sf = comptime if (builtin.link_libc) std.c.versionCheck(.{ .major = 2, .minor = 1 }).ok else std.Target.current.os.version_range.linux.range.max.order(.{ .major = 2, .minor = 2 }) != .lt; if (!call_sf) break :sf; if (headers.len != 0) { const amt = try writev(out_fd, headers); total_written += amt; if (amt < count_iovec_bytes(headers)) return total_written; header_done = true; } // Here we match BSD behavior, making a zero count value send as many bytes as possible. const adjusted_count = if (in_len == 0) max_count else math.min(in_len, @as(size_t, max_count)); while (true) { var offset: off_t = @bitCast(off_t, in_offset); const rc = system.sendfile(out_fd, in_fd, &offset, adjusted_count); switch (errno(rc)) { 0 => { const amt = @bitCast(usize, rc); total_written += amt; if (in_len == 0 and amt == 0) { // We have detected EOF from `in_fd`. break; } else if (amt < in_len) { return total_written; } else { break; } }, EBADF => unreachable, // Always a race condition. EFAULT => unreachable, // Segmentation fault. EOVERFLOW => unreachable, // We avoid passing too large of a `count`. ENOTCONN => unreachable, // `out_fd` is an unconnected socket. EINVAL, ENOSYS => { // EINVAL could be any of the following situations: // * Descriptor is not valid or locked // * an mmap(2)-like operation is not available for in_fd // * count is negative // * out_fd has the O_APPEND flag set // Because of the "mmap(2)-like operation" possibility, we fall back to doing read/write // manually, the same as ENOSYS. break :sf; }, EAGAIN => if (std.event.Loop.instance) |loop| { loop.waitUntilFdWritable(out_fd); continue; } else { return error.WouldBlock; }, EIO => return error.InputOutput, EPIPE => return error.BrokenPipe, ENOMEM => return error.SystemResources, ENXIO => return error.Unseekable, ESPIPE => return error.Unseekable, else => |err| { const discard = unexpectedErrno(err); break :sf; }, } } if (trailers.len != 0) { total_written += try writev(out_fd, trailers); } return total_written; }, .freebsd => sf: { var hdtr_data: std.c.sf_hdtr = undefined; var hdtr: ?*std.c.sf_hdtr = null; if (headers.len != 0 or trailers.len != 0) { // Here we carefully avoid `@intCast` by returning partial writes when // too many io vectors are provided. const hdr_cnt = math.cast(u31, headers.len) catch math.maxInt(u31); if (headers.len > hdr_cnt) return writev(out_fd, headers); const trl_cnt = math.cast(u31, trailers.len) catch math.maxInt(u31); hdtr_data = std.c.sf_hdtr{ .headers = headers.ptr, .hdr_cnt = hdr_cnt, .trailers = trailers.ptr, .trl_cnt = trl_cnt, }; hdtr = &hdtr_data; } const adjusted_count = math.min(in_len, max_count); while (true) { var sbytes: off_t = undefined; const offset = @bitCast(off_t, in_offset); const err = errno(system.sendfile(in_fd, out_fd, offset, adjusted_count, hdtr, &sbytes, flags)); const amt = @bitCast(usize, sbytes); switch (err) { 0 => return amt, EBADF => unreachable, // Always a race condition. EFAULT => unreachable, // Segmentation fault. ENOTCONN => unreachable, // `out_fd` is an unconnected socket. EINVAL, EOPNOTSUPP, ENOTSOCK, ENOSYS => { // EINVAL could be any of the following situations: // * The fd argument is not a regular file. // * The s argument is not a SOCK_STREAM type socket. // * The offset argument is negative. // Because of some of these possibilities, we fall back to doing read/write // manually, the same as ENOSYS. break :sf; }, EINTR => if (amt != 0) return amt else continue, EAGAIN => if (amt != 0) { return amt; } else if (std.event.Loop.instance) |loop| { loop.waitUntilFdWritable(out_fd); continue; } else { return error.WouldBlock; }, EBUSY => if (amt != 0) { return amt; } else if (std.event.Loop.instance) |loop| { loop.waitUntilFdReadable(in_fd); continue; } else { return error.WouldBlock; }, EIO => return error.InputOutput, ENOBUFS => return error.SystemResources, EPIPE => return error.BrokenPipe, else => { const discard = unexpectedErrno(err); if (amt != 0) { return amt; } else { break :sf; } }, } } }, .macosx, .ios, .tvos, .watchos => sf: { var hdtr_data: std.c.sf_hdtr = undefined; var hdtr: ?*std.c.sf_hdtr = null; if (headers.len != 0 or trailers.len != 0) { // Here we carefully avoid `@intCast` by returning partial writes when // too many io vectors are provided. const hdr_cnt = math.cast(u31, headers.len) catch math.maxInt(u31); if (headers.len > hdr_cnt) return writev(out_fd, headers); const trl_cnt = math.cast(u31, trailers.len) catch math.maxInt(u31); hdtr_data = std.c.sf_hdtr{ .headers = headers.ptr, .hdr_cnt = hdr_cnt, .trailers = trailers.ptr, .trl_cnt = trl_cnt, }; hdtr = &hdtr_data; } const adjusted_count = math.min(in_len, @as(u63, max_count)); while (true) { var sbytes: off_t = adjusted_count; const signed_offset = @bitCast(i64, in_offset); const err = errno(system.sendfile(in_fd, out_fd, signed_offset, &sbytes, hdtr, flags)); const amt = @bitCast(usize, sbytes); switch (err) { 0 => return amt, EBADF => unreachable, // Always a race condition. EFAULT => unreachable, // Segmentation fault. EINVAL => unreachable, ENOTCONN => unreachable, // `out_fd` is an unconnected socket. ENOTSUP, ENOTSOCK, ENOSYS => break :sf, EINTR => if (amt != 0) return amt else continue, EAGAIN => if (amt != 0) { return amt; } else if (std.event.Loop.instance) |loop| { loop.waitUntilFdWritable(out_fd); continue; } else { return error.WouldBlock; }, EIO => return error.InputOutput, EPIPE => return error.BrokenPipe, else => { const discard = unexpectedErrno(err); if (amt != 0) { return amt; } else { break :sf; } }, } } }, else => {}, // fall back to read/write } if (headers.len != 0 and !header_done) { const amt = try writev(out_fd, headers); total_written += amt; if (amt < count_iovec_bytes(headers)) return total_written; } rw: { var buf: [8 * 4096]u8 = undefined; // Here we match BSD behavior, making a zero count value send as many bytes as possible. const adjusted_count = if (in_len == 0) buf.len else math.min(buf.len, in_len); const amt_read = try pread(in_fd, buf[0..adjusted_count], in_offset); if (amt_read == 0) { if (in_len == 0) { // We have detected EOF from `in_fd`. break :rw; } else { return total_written; } } const amt_written = try write(out_fd, buf[0..amt_read]); total_written += amt_written; if (amt_written < in_len or in_len == 0) return total_written; } if (trailers.len != 0) { total_written += try writev(out_fd, trailers); } return total_written; } pub const PollError = error{ /// The kernel had no space to allocate file descriptor tables. SystemResources, } || UnexpectedError; pub fn poll(fds: []pollfd, timeout: i32) PollError!usize { while (true) { const rc = system.poll(fds.ptr, fds.len, timeout); switch (errno(rc)) { 0 => return @intCast(usize, rc), EFAULT => unreachable, EINTR => continue, EINVAL => unreachable, ENOMEM => 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, } || UnexpectedError; pub fn recvfrom( sockfd: fd_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); switch (errno(rc)) { 0 => return @intCast(usize, rc), EBADF => unreachable, // always a race condition EFAULT => unreachable, EINVAL => unreachable, ENOTCONN => unreachable, ENOTSOCK => unreachable, EINTR => continue, EAGAIN => if (std.event.Loop.instance) |loop| { loop.waitUntilFdReadable(sockfd); continue; } else { return error.WouldBlock; }, ENOMEM => return error.SystemResources, ECONNREFUSED => return error.ConnectionRefused, else => |err| return unexpectedErrno(err), } } } pub const DnExpandError = error{InvalidDnsPacket}; pub fn dn_expand( msg: []const u8, comp_dn: []const u8, exp_dn: []u8, ) DnExpandError!usize { // This implementation is ported from musl libc. // A more idiomatic "ziggy" implementation would be welcome. var p = comp_dn.ptr; var len: usize = std.math.maxInt(usize); const end = msg.ptr + msg.len; if (p == end or exp_dn.len == 0) return error.InvalidDnsPacket; var dest = exp_dn.ptr; const dend = dest + std.math.min(exp_dn.len, 254); // detect reference loop using an iteration counter var i: usize = 0; while (i < msg.len) : (i += 2) { // loop invariants: p= msg.len) return error.InvalidDnsPacket; p = msg.ptr + j; } else if (p[0] != 0) { if (dest != exp_dn.ptr) { dest.* = '.'; dest += 1; } var j = p[0]; p += 1; if (j >= @ptrToInt(end) - @ptrToInt(p) or j >= @ptrToInt(dend) - @ptrToInt(dest)) { return error.InvalidDnsPacket; } while (j != 0) { j -= 1; dest.* = p[0]; dest += 1; p += 1; } } else { dest.* = 0; if (len == std.math.maxInt(usize)) len = @ptrToInt(p) + 1 - @ptrToInt(comp_dn.ptr); return len; } } return error.InvalidDnsPacket; } pub const SchedYieldError = error{ /// The system is not configured to allow yielding SystemCannotYield, }; pub fn sched_yield() SchedYieldError!void { if (builtin.os.tag == .windows) { // The return value has to do with how many other threads there are; it is not // an error condition on Windows. _ = windows.kernel32.SwitchToThread(); return; } switch (errno(system.sched_yield())) { 0 => return, ENOSYS => return error.SystemCannotYield, else => return error.SystemCannotYield, } } 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, } || UnexpectedError; /// Set a socket's options. pub fn setsockopt(fd: fd_t, level: u32, optname: u32, opt: []const u8) SetSockOptError!void { switch (errno(system.setsockopt(fd, level, optname, opt.ptr, @intCast(socklen_t, opt.len)))) { 0 => {}, EBADF => unreachable, // always a race condition ENOTSOCK => unreachable, // always a race condition EINVAL => unreachable, EFAULT => unreachable, EDOM => return error.TimeoutTooBig, EISCONN => return error.AlreadyConnected, ENOPROTOOPT => return error.InvalidProtocolOption, ENOMEM => return error.SystemResources, ENOBUFS => return error.SystemResources, else => |err| return unexpectedErrno(err), } } pub const MemFdCreateError = error{ SystemFdQuotaExceeded, ProcessFdQuotaExceeded, OutOfMemory, /// memfd_create is available in Linux 3.17 and later. This error is returned /// for older kernel versions. SystemOutdated, } || UnexpectedError; pub const memfd_createC = @compileError("deprecated: renamed to memfd_createZ"); pub fn memfd_createZ(name: [*:0]const u8, flags: u32) MemFdCreateError!fd_t { // memfd_create is available only in glibc versions starting with 2.27. const use_c = std.c.versionCheck(.{ .major = 2, .minor = 27, .patch = 0 }).ok; const sys = if (use_c) std.c else linux; const getErrno = if (use_c) std.c.getErrno else linux.getErrno; const rc = sys.memfd_create(name, flags); switch (getErrno(rc)) { 0 => return @intCast(fd_t, rc), EFAULT => unreachable, // name has invalid memory EINVAL => unreachable, // name/flags are faulty ENFILE => return error.SystemFdQuotaExceeded, EMFILE => return error.ProcessFdQuotaExceeded, ENOMEM => return error.OutOfMemory, ENOSYS => return error.SystemOutdated, else => |err| return unexpectedErrno(err), } } pub const MFD_NAME_PREFIX = "memfd:"; pub const MFD_MAX_NAME_LEN = NAME_MAX - MFD_NAME_PREFIX.len; fn toMemFdPath(name: []const u8) ![MFD_MAX_NAME_LEN:0]u8 { var path_with_null: [MFD_MAX_NAME_LEN:0]u8 = undefined; // >= rather than > to make room for the null byte if (name.len >= MFD_MAX_NAME_LEN) return error.NameTooLong; mem.copy(u8, &path_with_null, name); path_with_null[name.len] = 0; return path_with_null; } pub fn memfd_create(name: []const u8, flags: u32) !fd_t { const name_t = try toMemFdPath(name); return memfd_createZ(&name_t, flags); } pub fn getrusage(who: i32) rusage { var result: rusage = undefined; const rc = system.getrusage(who, &result); switch (errno(rc)) { 0 => return result, EINVAL => unreachable, EFAULT => unreachable, else => unreachable, } } pub const TermiosGetError = error{NotATerminal} || UnexpectedError; pub fn tcgetattr(handle: fd_t) TermiosGetError!termios { var term: termios = undefined; switch (errno(system.tcgetattr(handle, &term))) { 0 => return term, EBADF => unreachable, ENOTTY => 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))) { 0 => return, EBADF => unreachable, EINTR => continue, EINVAL => unreachable, ENOTTY => return error.NotATerminal, EIO => return error.ProcessOrphaned, else => |err| return unexpectedErrno(err), } } }