diff options
| author | Andrew Kelley <andrew@ziglang.org> | 2019-03-02 16:46:04 -0500 |
|---|---|---|
| committer | Andrew Kelley <andrew@ziglang.org> | 2019-03-02 16:46:04 -0500 |
| commit | e40245570422c137f7239f411128973cc217389e (patch) | |
| tree | 93ab6cef5d0744ca93aaf9fc768608f7ce8a26f3 /std/os.zig | |
| parent | f7835000b62fb5b501f1d84c90f56e2aa11bc55a (diff) | |
| download | zig-e40245570422c137f7239f411128973cc217389e.tar.gz zig-e40245570422c137f7239f411128973cc217389e.zip | |
rename std lib files to new convention
Diffstat (limited to 'std/os.zig')
| -rw-r--r-- | std/os.zig | 3432 |
1 files changed, 3432 insertions, 0 deletions
diff --git a/std/os.zig b/std/os.zig new file mode 100644 index 0000000000..c686c2a054 --- /dev/null +++ b/std/os.zig @@ -0,0 +1,3432 @@ +const std = @import("std.zig"); +const builtin = @import("builtin"); +const Os = builtin.Os; +const is_windows = builtin.os == Os.windows; +const is_posix = switch (builtin.os) { + builtin.Os.linux, builtin.Os.macosx, builtin.Os.freebsd, builtin.Os.netbsd => true, + else => false, +}; +const os = @This(); + +comptime { + assert(@import("std") == std); // You have to run the std lib tests with --override-std-dir +} + +test "std.os" { + _ = @import("os/child_process.zig"); + _ = @import("os/darwin.zig"); + _ = @import("os/darwin/errno.zig"); + _ = @import("os/get_user_id.zig"); + _ = @import("os/linux.zig"); + _ = @import("os/path.zig"); + _ = @import("os/test.zig"); + _ = @import("os/time.zig"); + _ = @import("os/windows.zig"); + _ = @import("os/uefi.zig"); + _ = @import("os/get_app_data_dir.zig"); +} + +pub const windows = @import("os/windows.zig"); +pub const darwin = @import("os/darwin.zig"); +pub const linux = @import("os/linux.zig"); +pub const freebsd = @import("os/freebsd.zig"); +pub const netbsd = @import("os/netbsd.zig"); +pub const zen = @import("os/zen.zig"); +pub const uefi = @import("os/uefi.zig"); + +pub const posix = switch (builtin.os) { + Os.linux => linux, + Os.macosx, Os.ios => darwin, + Os.freebsd => freebsd, + Os.netbsd => netbsd, + Os.zen => zen, + else => @compileError("Unsupported OS"), +}; + +pub const net = @import("net.zig"); + +pub const ChildProcess = @import("os/child_process.zig").ChildProcess; +pub const path = @import("os/path.zig"); +pub const File = @import("os/file.zig").File; +pub const time = @import("os/time.zig"); + +pub const page_size = 4 * 1024; +pub const MAX_PATH_BYTES = switch (builtin.os) { + Os.linux, Os.macosx, Os.ios, Os.freebsd, Os.netbsd => posix.PATH_MAX, + // Each UTF-16LE character may be expanded to 3 UTF-8 bytes. + // If it would require 4 UTF-8 bytes, then there would be a surrogate + // pair in the UTF-16LE, and we (over)account 3 bytes for it that way. + // +1 for the null byte at the end, which can be encoded in 1 byte. + Os.windows => windows_util.PATH_MAX_WIDE * 3 + 1, + else => @compileError("Unsupported OS"), +}; + +pub const UserInfo = @import("os/get_user_id.zig").UserInfo; +pub const getUserInfo = @import("os/get_user_id.zig").getUserInfo; + +const windows_util = @import("os/windows/util.zig"); +pub const windowsWaitSingle = windows_util.windowsWaitSingle; +pub const windowsWrite = windows_util.windowsWrite; +pub const windowsIsCygwinPty = windows_util.windowsIsCygwinPty; +pub const windowsOpen = windows_util.windowsOpen; +pub const windowsOpenW = windows_util.windowsOpenW; +pub const createWindowsEnvBlock = windows_util.createWindowsEnvBlock; + +pub const WindowsCreateIoCompletionPortError = windows_util.WindowsCreateIoCompletionPortError; +pub const windowsCreateIoCompletionPort = windows_util.windowsCreateIoCompletionPort; + +pub const WindowsPostQueuedCompletionStatusError = windows_util.WindowsPostQueuedCompletionStatusError; +pub const windowsPostQueuedCompletionStatus = windows_util.windowsPostQueuedCompletionStatus; + +pub const WindowsWaitResult = windows_util.WindowsWaitResult; +pub const windowsGetQueuedCompletionStatus = windows_util.windowsGetQueuedCompletionStatus; + +pub const WindowsWaitError = windows_util.WaitError; +pub const WindowsOpenError = windows_util.OpenError; +pub const WindowsWriteError = windows_util.WriteError; +pub const WindowsReadError = windows_util.ReadError; + +pub const FileHandle = if (is_windows) windows.HANDLE else i32; + +pub const getAppDataDir = @import("os/get_app_data_dir.zig").getAppDataDir; +pub const GetAppDataDirError = @import("os/get_app_data_dir.zig").GetAppDataDirError; + +const debug = std.debug; +const assert = debug.assert; +const testing = std.testing; + +const c = std.c; + +const mem = std.mem; +const Allocator = mem.Allocator; + +const BufMap = std.BufMap; +const cstr = std.cstr; + +const io = std.io; +const base64 = std.base64; +const ArrayList = std.ArrayList; +const Buffer = std.Buffer; +const math = std.math; + +/// Fills `buf` with random bytes. If linking against libc, this calls the +/// appropriate OS-specific library call. Otherwise it uses the zig standard +/// library implementation. +pub fn getRandomBytes(buf: []u8) !void { + switch (builtin.os) { + Os.linux => while (true) { + // TODO check libc version and potentially call c.getrandom. + // See #397 + const errno = posix.getErrno(posix.getrandom(buf.ptr, buf.len, 0)); + switch (errno) { + 0 => return, + posix.EINVAL => unreachable, + posix.EFAULT => unreachable, + posix.EINTR => continue, + posix.ENOSYS => return getRandomBytesDevURandom(buf), + else => return unexpectedErrorPosix(errno), + } + }, + Os.macosx, Os.ios, Os.freebsd, Os.netbsd => return getRandomBytesDevURandom(buf), + Os.windows => { + // Call RtlGenRandom() instead of CryptGetRandom() on Windows + // https://github.com/rust-lang-nursery/rand/issues/111 + // https://bugzilla.mozilla.org/show_bug.cgi?id=504270 + if (windows.RtlGenRandom(buf.ptr, buf.len) == 0) { + const err = windows.GetLastError(); + return switch (err) { + else => unexpectedErrorWindows(err), + }; + } + }, + Os.zen => { + const randomness = []u8{ 42, 1, 7, 12, 22, 17, 99, 16, 26, 87, 41, 45 }; + var i: usize = 0; + while (i < buf.len) : (i += 1) { + if (i > randomness.len) return error.Unknown; + buf[i] = randomness[i]; + } + }, + else => @compileError("Unsupported OS"), + } +} + +fn getRandomBytesDevURandom(buf: []u8) !void { + const fd = try posixOpenC(c"/dev/urandom", posix.O_RDONLY | posix.O_CLOEXEC, 0); + defer close(fd); + + const stream = &File.openHandle(fd).inStream().stream; + stream.readNoEof(buf) catch |err| switch (err) { + error.EndOfStream => unreachable, + error.OperationAborted => unreachable, + error.BrokenPipe => unreachable, + error.Unexpected => return error.Unexpected, + error.InputOutput => return error.Unexpected, + error.SystemResources => return error.Unexpected, + error.IsDir => unreachable, + }; +} + +test "os.getRandomBytes" { + var buf_a: [50]u8 = undefined; + var buf_b: [50]u8 = undefined; + // Call Twice + try getRandomBytes(buf_a[0..]); + try getRandomBytes(buf_b[0..]); + + // Check if random (not 100% conclusive) + testing.expect(!mem.eql(u8, buf_a, buf_b)); +} + +/// Raises a signal in the current kernel thread, ending its execution. +/// If linking against libc, this calls the abort() libc function. Otherwise +/// it uses the zig standard library implementation. +pub fn abort() noreturn { + @setCold(true); + if (builtin.link_libc) { + c.abort(); + } + switch (builtin.os) { + Os.linux, Os.macosx, Os.ios, Os.freebsd, Os.netbsd => { + _ = posix.raise(posix.SIGABRT); + _ = posix.raise(posix.SIGKILL); + while (true) {} + }, + Os.windows => { + if (builtin.mode == builtin.Mode.Debug) { + @breakpoint(); + } + windows.ExitProcess(3); + }, + Os.uefi => { + // TODO there's gotta be a better thing to do here than loop forever + while (true) {} + }, + else => @compileError("Unsupported OS"), + } +} + +/// Exits the program cleanly with the specified status code. +pub fn exit(status: u8) noreturn { + @setCold(true); + if (builtin.link_libc) { + c.exit(status); + } + switch (builtin.os) { + Os.linux, Os.macosx, Os.ios, Os.freebsd, Os.netbsd => { + posix.exit(status); + }, + Os.windows => { + windows.ExitProcess(status); + }, + else => @compileError("Unsupported OS"), + } +} + +/// When a file descriptor is closed on linux, it pops the first +/// node from this queue and resumes it. +/// Async functions which get the EMFILE error code can suspend, +/// putting their coroutine handle into this list. +/// TODO make this an atomic linked list +pub var emfile_promise_queue = std.LinkedList(promise).init(); + +/// Closes the file handle. Keeps trying if it gets interrupted by a signal. +pub fn close(handle: FileHandle) void { + if (is_windows) { + windows_util.windowsClose(handle); + } else { + while (true) { + const err = posix.getErrno(posix.close(handle)); + switch (err) { + posix.EINTR => continue, + else => { + if (emfile_promise_queue.popFirst()) |p| resume p.data; + return; + }, + } + } + } +} + +pub const PosixReadError = error{ + InputOutput, + SystemResources, + IsDir, + Unexpected, +}; + +/// Returns the number of bytes that were read, which can be less than +/// buf.len. If 0 bytes were read, that means EOF. +pub fn posixRead(fd: i32, buf: []u8) PosixReadError!usize { + // Linux can return EINVAL when read amount is > 0x7ffff000 + // See https://github.com/ziglang/zig/pull/743#issuecomment-363158274 + const max_buf_len = 0x7ffff000; + + var index: usize = 0; + while (index < buf.len) { + const want_to_read = math.min(buf.len - index, usize(max_buf_len)); + const rc = posix.read(fd, buf.ptr + index, want_to_read); + const err = posix.getErrno(rc); + switch (err) { + 0 => { + index += rc; + if (rc == want_to_read) continue; + // Read returned less than buf.len. + return index; + }, + posix.EINTR => continue, + posix.EINVAL => unreachable, + posix.EFAULT => unreachable, + posix.EAGAIN => unreachable, + posix.EBADF => unreachable, // always a race condition + posix.EIO => return error.InputOutput, + posix.EISDIR => return error.IsDir, + posix.ENOBUFS => return error.SystemResources, + posix.ENOMEM => return error.SystemResources, + else => return unexpectedErrorPosix(err), + } + } + return index; +} + +/// Number of bytes read is returned. Upon reading end-of-file, zero is returned. +pub fn posix_preadv(fd: i32, iov: [*]const posix.iovec, count: usize, offset: u64) PosixReadError!usize { + switch (builtin.os) { + builtin.Os.macosx => { + // Darwin does not have preadv but it does have pread. + var off: usize = 0; + var iov_i: usize = 0; + var inner_off: usize = 0; + while (true) { + const v = iov[iov_i]; + const rc = darwin.pread(fd, v.iov_base + inner_off, v.iov_len - inner_off, offset + off); + const err = darwin.getErrno(rc); + switch (err) { + 0 => { + off += rc; + inner_off += rc; + if (inner_off == v.iov_len) { + iov_i += 1; + inner_off = 0; + if (iov_i == count) { + return off; + } + } + if (rc == 0) return off; // EOF + continue; + }, + posix.EINTR => continue, + posix.EINVAL => unreachable, + posix.EFAULT => unreachable, + posix.ESPIPE => unreachable, // fd is not seekable + posix.EAGAIN => unreachable, // this function is not for non blocking + posix.EBADF => unreachable, // always a race condition + posix.EIO => return error.InputOutput, + posix.EISDIR => return error.IsDir, + posix.ENOBUFS => return error.SystemResources, + posix.ENOMEM => return error.SystemResources, + else => return unexpectedErrorPosix(err), + } + } + }, + builtin.Os.linux, builtin.Os.freebsd, Os.netbsd => while (true) { + const rc = posix.preadv(fd, iov, count, offset); + const err = posix.getErrno(rc); + switch (err) { + 0 => return rc, + posix.EINTR => continue, + posix.EINVAL => unreachable, + posix.EFAULT => unreachable, + posix.EAGAIN => unreachable, // don't call this function for non blocking + posix.EBADF => unreachable, // always a race condition + posix.EIO => return error.InputOutput, + posix.EISDIR => return error.IsDir, + posix.ENOBUFS => return error.SystemResources, + posix.ENOMEM => return error.SystemResources, + else => return unexpectedErrorPosix(err), + } + }, + else => @compileError("Unsupported OS"), + } +} + +pub const PosixWriteError = error{ + DiskQuota, + FileTooBig, + InputOutput, + NoSpaceLeft, + AccessDenied, + BrokenPipe, + + /// See https://github.com/ziglang/zig/issues/1396 + Unexpected, +}; + +/// Calls POSIX write, and keeps trying if it gets interrupted. +pub fn posixWrite(fd: i32, bytes: []const u8) PosixWriteError!void { + // Linux can return EINVAL when write amount is > 0x7ffff000 + // See https://github.com/ziglang/zig/pull/743#issuecomment-363165856 + const max_bytes_len = 0x7ffff000; + + var index: usize = 0; + while (index < bytes.len) { + const amt_to_write = math.min(bytes.len - index, usize(max_bytes_len)); + const rc = posix.write(fd, bytes.ptr + index, amt_to_write); + const write_err = posix.getErrno(rc); + switch (write_err) { + 0 => { + index += rc; + continue; + }, + posix.EINTR => continue, + posix.EINVAL => unreachable, + posix.EFAULT => unreachable, + posix.EAGAIN => unreachable, // use posixAsyncWrite for non-blocking + posix.EBADF => unreachable, // always a race condition + posix.EDESTADDRREQ => unreachable, // connect was never called + posix.EDQUOT => return PosixWriteError.DiskQuota, + posix.EFBIG => return PosixWriteError.FileTooBig, + posix.EIO => return PosixWriteError.InputOutput, + posix.ENOSPC => return PosixWriteError.NoSpaceLeft, + posix.EPERM => return PosixWriteError.AccessDenied, + posix.EPIPE => return PosixWriteError.BrokenPipe, + else => return unexpectedErrorPosix(write_err), + } + } +} + +pub fn posix_pwritev(fd: i32, iov: [*]const posix.iovec_const, count: usize, offset: u64) PosixWriteError!void { + switch (builtin.os) { + builtin.Os.macosx => { + // Darwin does not have pwritev but it does have pwrite. + var off: usize = 0; + var iov_i: usize = 0; + var inner_off: usize = 0; + while (true) { + const v = iov[iov_i]; + const rc = darwin.pwrite(fd, v.iov_base + inner_off, v.iov_len - inner_off, offset + off); + const err = darwin.getErrno(rc); + switch (err) { + 0 => { + off += rc; + inner_off += rc; + if (inner_off == v.iov_len) { + iov_i += 1; + inner_off = 0; + if (iov_i == count) { + return; + } + } + continue; + }, + posix.EINTR => continue, + posix.ESPIPE => unreachable, // fd is not seekable + posix.EINVAL => unreachable, + posix.EFAULT => unreachable, + posix.EAGAIN => unreachable, // use posixAsyncPWriteV for non-blocking + posix.EBADF => unreachable, // always a race condition + posix.EDESTADDRREQ => unreachable, // connect was never called + posix.EDQUOT => return PosixWriteError.DiskQuota, + posix.EFBIG => return PosixWriteError.FileTooBig, + posix.EIO => return PosixWriteError.InputOutput, + posix.ENOSPC => return PosixWriteError.NoSpaceLeft, + posix.EPERM => return PosixWriteError.AccessDenied, + posix.EPIPE => return PosixWriteError.BrokenPipe, + else => return unexpectedErrorPosix(err), + } + } + }, + builtin.Os.linux, builtin.Os.freebsd, builtin.Os.netbsd => while (true) { + const rc = posix.pwritev(fd, iov, count, offset); + const err = posix.getErrno(rc); + switch (err) { + 0 => return, + posix.EINTR => continue, + posix.EINVAL => unreachable, + posix.EFAULT => unreachable, + posix.EAGAIN => unreachable, // use posixAsyncPWriteV for non-blocking + posix.EBADF => unreachable, // always a race condition + posix.EDESTADDRREQ => unreachable, // connect was never called + posix.EDQUOT => return PosixWriteError.DiskQuota, + posix.EFBIG => return PosixWriteError.FileTooBig, + posix.EIO => return PosixWriteError.InputOutput, + posix.ENOSPC => return PosixWriteError.NoSpaceLeft, + posix.EPERM => return PosixWriteError.AccessDenied, + posix.EPIPE => return PosixWriteError.BrokenPipe, + else => return unexpectedErrorPosix(err), + } + }, + else => @compileError("Unsupported OS"), + } +} + +pub const PosixOpenError = error{ + AccessDenied, + FileTooBig, + IsDir, + SymLinkLoop, + ProcessFdQuotaExceeded, + NameTooLong, + SystemFdQuotaExceeded, + NoDevice, + FileNotFound, + SystemResources, + NoSpaceLeft, + NotDir, + PathAlreadyExists, + DeviceBusy, + + /// See https://github.com/ziglang/zig/issues/1396 + Unexpected, +}; + +/// ::file_path needs to be copied in memory to add a null terminating byte. +/// Calls POSIX open, keeps trying if it gets interrupted, and translates +/// the return value into zig errors. +pub fn posixOpen(file_path: []const u8, flags: u32, perm: usize) PosixOpenError!i32 { + const file_path_c = try toPosixPath(file_path); + return posixOpenC(&file_path_c, flags, perm); +} + +// TODO https://github.com/ziglang/zig/issues/265 +pub fn posixOpenC(file_path: [*]const u8, flags: u32, perm: usize) !i32 { + while (true) { + const result = posix.open(file_path, flags, perm); + const err = posix.getErrno(result); + if (err > 0) { + switch (err) { + posix.EINTR => continue, + + posix.EFAULT => unreachable, + posix.EINVAL => unreachable, + posix.EACCES => return PosixOpenError.AccessDenied, + posix.EFBIG, posix.EOVERFLOW => return PosixOpenError.FileTooBig, + posix.EISDIR => return PosixOpenError.IsDir, + posix.ELOOP => return PosixOpenError.SymLinkLoop, + posix.EMFILE => return PosixOpenError.ProcessFdQuotaExceeded, + posix.ENAMETOOLONG => return PosixOpenError.NameTooLong, + posix.ENFILE => return PosixOpenError.SystemFdQuotaExceeded, + posix.ENODEV => return PosixOpenError.NoDevice, + posix.ENOENT => return PosixOpenError.FileNotFound, + posix.ENOMEM => return PosixOpenError.SystemResources, + posix.ENOSPC => return PosixOpenError.NoSpaceLeft, + posix.ENOTDIR => return PosixOpenError.NotDir, + posix.EPERM => return PosixOpenError.AccessDenied, + posix.EEXIST => return PosixOpenError.PathAlreadyExists, + posix.EBUSY => return PosixOpenError.DeviceBusy, + else => return unexpectedErrorPosix(err), + } + } + return @intCast(i32, result); + } +} + +/// Used to convert a slice to a null terminated slice on the stack. +/// TODO well defined copy elision +pub fn toPosixPath(file_path: []const u8) ![posix.PATH_MAX]u8 { + var path_with_null: [posix.PATH_MAX]u8 = undefined; + if (file_path.len >= posix.PATH_MAX) return error.NameTooLong; + mem.copy(u8, path_with_null[0..], file_path); + path_with_null[file_path.len] = 0; + return path_with_null; +} + +pub fn posixDup2(old_fd: i32, new_fd: i32) !void { + while (true) { + const err = posix.getErrno(posix.dup2(old_fd, new_fd)); + if (err > 0) { + return switch (err) { + posix.EBUSY, posix.EINTR => continue, + posix.EMFILE => error.ProcessFdQuotaExceeded, + posix.EINVAL => unreachable, + else => unexpectedErrorPosix(err), + }; + } + return; + } +} + +pub fn createNullDelimitedEnvMap(allocator: *Allocator, env_map: *const BufMap) ![]?[*]u8 { + const envp_count = env_map.count(); + const envp_buf = try allocator.alloc(?[*]u8, envp_count + 1); + mem.set(?[*]u8, envp_buf, null); + errdefer freeNullDelimitedEnvMap(allocator, envp_buf); + { + var it = env_map.iterator(); + var i: usize = 0; + while (it.next()) |pair| : (i += 1) { + const env_buf = try allocator.alloc(u8, pair.key.len + pair.value.len + 2); + @memcpy(env_buf.ptr, pair.key.ptr, pair.key.len); + env_buf[pair.key.len] = '='; + @memcpy(env_buf.ptr + pair.key.len + 1, pair.value.ptr, pair.value.len); + env_buf[env_buf.len - 1] = 0; + + envp_buf[i] = env_buf.ptr; + } + assert(i == envp_count); + } + assert(envp_buf[envp_count] == null); + return envp_buf; +} + +pub fn freeNullDelimitedEnvMap(allocator: *Allocator, envp_buf: []?[*]u8) void { + for (envp_buf) |env| { + const env_buf = if (env) |ptr| ptr[0 .. cstr.len(ptr) + 1] else break; + allocator.free(env_buf); + } + allocator.free(envp_buf); +} + +/// This function must allocate memory to add a null terminating bytes on path and each arg. +/// It must also convert to KEY=VALUE\0 format for environment variables, and include null +/// pointers after the args and after the environment variables. +/// `argv[0]` is the executable path. +/// This function also uses the PATH environment variable to get the full path to the executable. +pub fn posixExecve(argv: []const []const u8, env_map: *const BufMap, allocator: *Allocator) !void { + const argv_buf = try allocator.alloc(?[*]u8, argv.len + 1); + mem.set(?[*]u8, argv_buf, null); + defer { + for (argv_buf) |arg| { + const arg_buf = if (arg) |ptr| cstr.toSlice(ptr) else break; + allocator.free(arg_buf); + } + allocator.free(argv_buf); + } + for (argv) |arg, i| { + const arg_buf = try allocator.alloc(u8, arg.len + 1); + @memcpy(arg_buf.ptr, arg.ptr, arg.len); + arg_buf[arg.len] = 0; + + argv_buf[i] = arg_buf.ptr; + } + argv_buf[argv.len] = null; + + const envp_buf = try createNullDelimitedEnvMap(allocator, env_map); + defer freeNullDelimitedEnvMap(allocator, envp_buf); + + const exe_path = argv[0]; + if (mem.indexOfScalar(u8, exe_path, '/') != null) { + return posixExecveErrnoToErr(posix.getErrno(posix.execve(argv_buf[0].?, argv_buf.ptr, envp_buf.ptr))); + } + + const PATH = getEnvPosix("PATH") orelse "/usr/local/bin:/bin/:/usr/bin"; + // PATH.len because it is >= the largest search_path + // +1 for the / to join the search path and exe_path + // +1 for the null terminating byte + const path_buf = try allocator.alloc(u8, PATH.len + exe_path.len + 2); + defer allocator.free(path_buf); + var it = mem.tokenize(PATH, ":"); + var seen_eacces = false; + var err: usize = undefined; + while (it.next()) |search_path| { + mem.copy(u8, path_buf, search_path); + path_buf[search_path.len] = '/'; + mem.copy(u8, path_buf[search_path.len + 1 ..], exe_path); + path_buf[search_path.len + exe_path.len + 1] = 0; + err = posix.getErrno(posix.execve(path_buf.ptr, argv_buf.ptr, envp_buf.ptr)); + assert(err > 0); + if (err == posix.EACCES) { + seen_eacces = true; + } else if (err != posix.ENOENT) { + return posixExecveErrnoToErr(err); + } + } + if (seen_eacces) { + err = posix.EACCES; + } + return posixExecveErrnoToErr(err); +} + +pub const PosixExecveError = error{ + SystemResources, + AccessDenied, + InvalidExe, + FileSystem, + IsDir, + FileNotFound, + NotDir, + FileBusy, + + /// See https://github.com/ziglang/zig/issues/1396 + Unexpected, +}; + +fn posixExecveErrnoToErr(err: usize) PosixExecveError { + assert(err > 0); + switch (err) { + posix.EFAULT => unreachable, + posix.E2BIG => return error.SystemResources, + posix.EMFILE => return error.SystemResources, + posix.ENAMETOOLONG => return error.SystemResources, + posix.ENFILE => return error.SystemResources, + posix.ENOMEM => return error.SystemResources, + posix.EACCES => return error.AccessDenied, + posix.EPERM => return error.AccessDenied, + posix.EINVAL => return error.InvalidExe, + posix.ENOEXEC => return error.InvalidExe, + posix.EIO => return error.FileSystem, + posix.ELOOP => return error.FileSystem, + posix.EISDIR => return error.IsDir, + posix.ENOENT => return error.FileNotFound, + posix.ENOTDIR => return error.NotDir, + posix.ETXTBSY => return error.FileBusy, + else => return unexpectedErrorPosix(err), + } +} + +pub var linux_elf_aux_maybe: ?[*]std.elf.Auxv = null; +pub var posix_environ_raw: [][*]u8 = undefined; + +/// See std.elf for the constants. +pub fn linuxGetAuxVal(index: usize) usize { + if (builtin.link_libc) { + return usize(std.c.getauxval(index)); + } else if (linux_elf_aux_maybe) |auxv| { + var i: usize = 0; + while (auxv[i].a_type != std.elf.AT_NULL) : (i += 1) { + if (auxv[i].a_type == index) + return auxv[i].a_un.a_val; + } + } + return 0; +} + +pub fn getBaseAddress() usize { + switch (builtin.os) { + builtin.Os.linux => { + const base = linuxGetAuxVal(std.elf.AT_BASE); + if (base != 0) { + return base; + } + const phdr = linuxGetAuxVal(std.elf.AT_PHDR); + return phdr - @sizeOf(std.elf.Ehdr); + }, + builtin.Os.macosx, builtin.Os.freebsd, builtin.Os.netbsd => { + return @ptrToInt(&std.c._mh_execute_header); + }, + builtin.Os.windows => return @ptrToInt(windows.GetModuleHandleW(null)), + else => @compileError("Unsupported OS"), + } +} + +/// Caller must free result when done. +/// TODO make this go through libc when we have it +pub fn getEnvMap(allocator: *Allocator) !BufMap { + var result = BufMap.init(allocator); + errdefer result.deinit(); + + if (is_windows) { + const ptr = windows.GetEnvironmentStringsW() orelse return error.OutOfMemory; + defer assert(windows.FreeEnvironmentStringsW(ptr) != 0); + + var i: usize = 0; + while (true) { + if (ptr[i] == 0) return result; + + const key_start = i; + + while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {} + const key_w = ptr[key_start..i]; + const key = try std.unicode.utf16leToUtf8Alloc(allocator, key_w); + errdefer allocator.free(key); + + if (ptr[i] == '=') i += 1; + + const value_start = i; + while (ptr[i] != 0) : (i += 1) {} + const value_w = ptr[value_start..i]; + const value = try std.unicode.utf16leToUtf8Alloc(allocator, value_w); + errdefer allocator.free(value); + + i += 1; // skip over null byte + + try result.setMove(key, value); + } + } else { + for (posix_environ_raw) |ptr| { + var line_i: usize = 0; + while (ptr[line_i] != 0 and ptr[line_i] != '=') : (line_i += 1) {} + const key = ptr[0..line_i]; + + var end_i: usize = line_i; + while (ptr[end_i] != 0) : (end_i += 1) {} + const value = ptr[line_i + 1 .. end_i]; + + try result.set(key, value); + } + return result; + } +} + +test "os.getEnvMap" { + var env = try getEnvMap(std.debug.global_allocator); + defer env.deinit(); +} + +/// TODO make this go through libc when we have it +pub fn getEnvPosix(key: []const u8) ?[]const u8 { + for (posix_environ_raw) |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 GetEnvVarOwnedError = error{ + OutOfMemory, + EnvironmentVariableNotFound, + + /// See https://github.com/ziglang/zig/issues/1774 + InvalidUtf8, +}; + +/// Caller must free returned memory. +/// TODO make this go through libc when we have it +pub fn getEnvVarOwned(allocator: *mem.Allocator, key: []const u8) GetEnvVarOwnedError![]u8 { + if (is_windows) { + const key_with_null = try std.unicode.utf8ToUtf16LeWithNull(allocator, key); + defer allocator.free(key_with_null); + + var buf = try allocator.alloc(u16, 256); + defer allocator.free(buf); + + while (true) { + const windows_buf_len = math.cast(windows.DWORD, buf.len) catch return error.OutOfMemory; + const result = windows.GetEnvironmentVariableW(key_with_null.ptr, buf.ptr, windows_buf_len); + + if (result == 0) { + const err = windows.GetLastError(); + return switch (err) { + windows.ERROR.ENVVAR_NOT_FOUND => error.EnvironmentVariableNotFound, + else => { + _ = unexpectedErrorWindows(err); + return error.EnvironmentVariableNotFound; + }, + }; + } + + if (result > buf.len) { + buf = try allocator.realloc(u16, buf, result); + continue; + } + + return std.unicode.utf16leToUtf8Alloc(allocator, buf) catch |err| switch (err) { + error.DanglingSurrogateHalf => return error.InvalidUtf8, + error.ExpectedSecondSurrogateHalf => return error.InvalidUtf8, + error.UnexpectedSecondSurrogateHalf => return error.InvalidUtf8, + error.OutOfMemory => return error.OutOfMemory, + }; + } + } else { + const result = getEnvPosix(key) orelse return error.EnvironmentVariableNotFound; + return mem.dupe(allocator, u8, result); + } +} + +test "os.getEnvVarOwned" { + var ga = debug.global_allocator; + testing.expectError(error.EnvironmentVariableNotFound, getEnvVarOwned(ga, "BADENV")); +} + +/// Caller must free the returned memory. +pub fn getCwdAlloc(allocator: *Allocator) ![]u8 { + var buf: [MAX_PATH_BYTES]u8 = undefined; + return mem.dupe(allocator, u8, try getCwd(&buf)); +} + +pub const GetCwdError = error{Unexpected}; + +/// The result is a slice of out_buffer. +pub fn getCwd(out_buffer: *[MAX_PATH_BYTES]u8) GetCwdError![]u8 { + switch (builtin.os) { + Os.windows => { + var utf16le_buf: [windows_util.PATH_MAX_WIDE]u16 = undefined; + const casted_len = @intCast(windows.DWORD, utf16le_buf.len); // TODO shouldn't need this cast + const casted_ptr = ([*]u16)(&utf16le_buf); // TODO shouldn't need this cast + const result = windows.GetCurrentDirectoryW(casted_len, casted_ptr); + if (result == 0) { + const err = windows.GetLastError(); + switch (err) { + else => return unexpectedErrorWindows(err), + } + } + assert(result <= utf16le_buf.len); + const utf16le_slice = utf16le_buf[0..result]; + // Trust that Windows gives us valid UTF-16LE. + const end_index = std.unicode.utf16leToUtf8(out_buffer, utf16le_slice) catch unreachable; + return out_buffer[0..end_index]; + }, + else => { + const err = posix.getErrno(posix.getcwd(out_buffer, out_buffer.len)); + switch (err) { + 0 => return cstr.toSlice(out_buffer), + posix.ERANGE => unreachable, + else => return unexpectedErrorPosix(err), + } + }, + } +} + +test "os.getCwd" { + // at least call it so it gets compiled + _ = getCwdAlloc(debug.global_allocator); + var buf: [MAX_PATH_BYTES]u8 = undefined; + _ = getCwd(&buf); +} + +pub const SymLinkError = PosixSymLinkError || WindowsSymLinkError; + +/// TODO add a symLinkC variant +pub fn symLink(existing_path: []const u8, new_path: []const u8) SymLinkError!void { + if (is_windows) { + return symLinkWindows(existing_path, new_path); + } else { + return symLinkPosix(existing_path, new_path); + } +} + +pub const WindowsSymLinkError = error{ + NameTooLong, + InvalidUtf8, + BadPathName, + + /// See https://github.com/ziglang/zig/issues/1396 + Unexpected, +}; + +pub fn symLinkW(existing_path_w: [*]const u16, new_path_w: [*]const u16) WindowsSymLinkError!void { + if (windows.CreateSymbolicLinkW(existing_path_w, new_path_w, 0) == 0) { + const err = windows.GetLastError(); + switch (err) { + else => return unexpectedErrorWindows(err), + } + } +} + +pub fn symLinkWindows(existing_path: []const u8, new_path: []const u8) WindowsSymLinkError!void { + const existing_path_w = try windows_util.sliceToPrefixedFileW(existing_path); + const new_path_w = try windows_util.sliceToPrefixedFileW(new_path); + return symLinkW(&existing_path_w, &new_path_w); +} + +pub const PosixSymLinkError = error{ + AccessDenied, + DiskQuota, + PathAlreadyExists, + FileSystem, + SymLinkLoop, + NameTooLong, + FileNotFound, + SystemResources, + NoSpaceLeft, + ReadOnlyFileSystem, + NotDir, + + /// See https://github.com/ziglang/zig/issues/1396 + Unexpected, +}; + +pub fn symLinkPosixC(existing_path: [*]const u8, new_path: [*]const u8) PosixSymLinkError!void { + const err = posix.getErrno(posix.symlink(existing_path, new_path)); + switch (err) { + 0 => return, + posix.EFAULT => unreachable, + posix.EINVAL => unreachable, + posix.EACCES => return error.AccessDenied, + posix.EPERM => return error.AccessDenied, + posix.EDQUOT => return error.DiskQuota, + posix.EEXIST => return error.PathAlreadyExists, + posix.EIO => return error.FileSystem, + posix.ELOOP => return error.SymLinkLoop, + posix.ENAMETOOLONG => return error.NameTooLong, + posix.ENOENT => return error.FileNotFound, + posix.ENOTDIR => return error.NotDir, + posix.ENOMEM => return error.SystemResources, + posix.ENOSPC => return error.NoSpaceLeft, + posix.EROFS => return error.ReadOnlyFileSystem, + else => return unexpectedErrorPosix(err), + } +} + +pub fn symLinkPosix(existing_path: []const u8, new_path: []const u8) PosixSymLinkError!void { + const existing_path_c = try toPosixPath(existing_path); + const new_path_c = try toPosixPath(new_path); + return symLinkPosixC(&existing_path_c, &new_path_c); +} + +// here we replace the standard +/ with -_ so that it can be used in a file name +const b64_fs_encoder = base64.Base64Encoder.init("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_", base64.standard_pad_char); + +/// TODO remove the allocator requirement from this API +pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path: []const u8) !void { + if (symLink(existing_path, new_path)) { + return; + } else |err| switch (err) { + error.PathAlreadyExists => {}, + else => return err, // TODO zig should know this set does not include PathAlreadyExists + } + + const dirname = os.path.dirname(new_path) orelse "."; + + var rand_buf: [12]u8 = undefined; + const tmp_path = try allocator.alloc(u8, dirname.len + 1 + base64.Base64Encoder.calcSize(rand_buf.len)); + defer allocator.free(tmp_path); + mem.copy(u8, tmp_path[0..], dirname); + tmp_path[dirname.len] = os.path.sep; + while (true) { + try getRandomBytes(rand_buf[0..]); + b64_fs_encoder.encode(tmp_path[dirname.len + 1 ..], rand_buf); + + if (symLink(existing_path, tmp_path)) { + return rename(tmp_path, new_path); + } else |err| switch (err) { + error.PathAlreadyExists => continue, + else => return err, // TODO zig should know this set does not include PathAlreadyExists + } + } +} + +pub const DeleteFileError = 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, + + /// See https://github.com/ziglang/zig/issues/1396 + Unexpected, +}; + +pub fn deleteFile(file_path: []const u8) DeleteFileError!void { + if (builtin.os == Os.windows) { + const file_path_w = try windows_util.sliceToPrefixedFileW(file_path); + return deleteFileW(&file_path_w); + } else { + const file_path_c = try toPosixPath(file_path); + return deleteFileC(&file_path_c); + } +} + +pub fn deleteFileW(file_path: [*]const u16) DeleteFileError!void { + if (windows.DeleteFileW(file_path) == 0) { + const err = windows.GetLastError(); + switch (err) { + windows.ERROR.FILE_NOT_FOUND => return error.FileNotFound, + windows.ERROR.ACCESS_DENIED => return error.AccessDenied, + windows.ERROR.FILENAME_EXCED_RANGE => return error.NameTooLong, + windows.ERROR.INVALID_PARAMETER => return error.NameTooLong, + else => return unexpectedErrorWindows(err), + } + } +} + +pub fn deleteFileC(file_path: [*]const u8) DeleteFileError!void { + if (is_windows) { + const file_path_w = try windows_util.cStrToPrefixedFileW(file_path); + return deleteFileW(&file_path_w); + } else { + const err = posix.getErrno(posix.unlink(file_path)); + switch (err) { + 0 => return, + posix.EACCES => return error.AccessDenied, + posix.EPERM => return error.AccessDenied, + posix.EBUSY => return error.FileBusy, + posix.EFAULT => unreachable, + posix.EINVAL => unreachable, + posix.EIO => return error.FileSystem, + posix.EISDIR => return error.IsDir, + posix.ELOOP => return error.SymLinkLoop, + posix.ENAMETOOLONG => return error.NameTooLong, + posix.ENOENT => return error.FileNotFound, + posix.ENOTDIR => return error.NotDir, + posix.ENOMEM => return error.SystemResources, + posix.EROFS => return error.ReadOnlyFileSystem, + else => return unexpectedErrorPosix(err), + } + } +} + +/// Guaranteed to be atomic. However until https://patchwork.kernel.org/patch/9636735/ is +/// merged and readily available, +/// there is a possibility of power loss or application termination leaving temporary files present +/// in the same directory as dest_path. +/// Destination file will have the same mode as the source file. +pub fn copyFile(source_path: []const u8, dest_path: []const u8) !void { + var in_file = try os.File.openRead(source_path); + defer in_file.close(); + + const mode = try in_file.mode(); + + var atomic_file = try AtomicFile.init(dest_path, mode); + defer atomic_file.deinit(); + + var buf: [page_size]u8 = undefined; + while (true) { + const amt = try in_file.readFull(buf[0..]); + try atomic_file.file.write(buf[0..amt]); + if (amt != buf.len) { + return atomic_file.finish(); + } + } +} + +/// Guaranteed to be atomic. However until https://patchwork.kernel.org/patch/9636735/ is +/// merged and readily available, +/// there is a possibility of power loss or application termination leaving temporary files present +pub fn copyFileMode(source_path: []const u8, dest_path: []const u8, mode: File.Mode) !void { + var in_file = try os.File.openRead(source_path); + defer in_file.close(); + + var atomic_file = try AtomicFile.init(dest_path, mode); + defer atomic_file.deinit(); + + var buf: [page_size]u8 = undefined; + while (true) { + const amt = try in_file.read(buf[0..]); + try atomic_file.file.write(buf[0..amt]); + if (amt != buf.len) { + return atomic_file.finish(); + } + } +} + +pub const AtomicFile = struct { + file: os.File, + tmp_path_buf: [MAX_PATH_BYTES]u8, + dest_path: []const u8, + finished: bool, + + const InitError = os.File.OpenError; + + /// dest_path must remain valid for the lifetime of AtomicFile + /// call finish to atomically replace dest_path with contents + /// TODO once we have null terminated pointers, use the + /// openWriteNoClobberN function + pub fn init(dest_path: []const u8, mode: File.Mode) InitError!AtomicFile { + const dirname = os.path.dirname(dest_path); + var rand_buf: [12]u8 = undefined; + const dirname_component_len = if (dirname) |d| d.len + 1 else 0; + const encoded_rand_len = comptime base64.Base64Encoder.calcSize(rand_buf.len); + const tmp_path_len = dirname_component_len + encoded_rand_len; + var tmp_path_buf: [MAX_PATH_BYTES]u8 = undefined; + if (tmp_path_len >= tmp_path_buf.len) return error.NameTooLong; + + if (dirname) |dir| { + mem.copy(u8, tmp_path_buf[0..], dir); + tmp_path_buf[dir.len] = os.path.sep; + } + + tmp_path_buf[tmp_path_len] = 0; + + while (true) { + try getRandomBytes(rand_buf[0..]); + b64_fs_encoder.encode(tmp_path_buf[dirname_component_len..tmp_path_len], rand_buf); + + const file = os.File.openWriteNoClobberC(&tmp_path_buf, mode) catch |err| switch (err) { + error.PathAlreadyExists => continue, + // TODO zig should figure out that this error set does not include PathAlreadyExists since + // it is handled in the above switch + else => return err, + }; + + return AtomicFile{ + .file = file, + .tmp_path_buf = tmp_path_buf, + .dest_path = dest_path, + .finished = false, + }; + } + } + + /// always call deinit, even after successful finish() + pub fn deinit(self: *AtomicFile) void { + if (!self.finished) { + self.file.close(); + deleteFileC(&self.tmp_path_buf) catch {}; + self.finished = true; + } + } + + pub fn finish(self: *AtomicFile) !void { + assert(!self.finished); + self.file.close(); + self.finished = true; + if (is_posix) { + const dest_path_c = try toPosixPath(self.dest_path); + return renameC(&self.tmp_path_buf, &dest_path_c); + } else if (is_windows) { + const dest_path_w = try windows_util.sliceToPrefixedFileW(self.dest_path); + const tmp_path_w = try windows_util.cStrToPrefixedFileW(&self.tmp_path_buf); + return renameW(&tmp_path_w, &dest_path_w); + } else { + @compileError("Unsupported OS"); + } + } +}; + +pub fn renameC(old_path: [*]const u8, new_path: [*]const u8) !void { + if (is_windows) { + const old_path_w = try windows_util.cStrToPrefixedFileW(old_path); + const new_path_w = try windows_util.cStrToPrefixedFileW(new_path); + return renameW(&old_path_w, &new_path_w); + } else { + const err = posix.getErrno(posix.rename(old_path, new_path)); + switch (err) { + 0 => return, + posix.EACCES => return error.AccessDenied, + posix.EPERM => return error.AccessDenied, + posix.EBUSY => return error.FileBusy, + posix.EDQUOT => return error.DiskQuota, + posix.EFAULT => unreachable, + posix.EINVAL => unreachable, + posix.EISDIR => return error.IsDir, + posix.ELOOP => return error.SymLinkLoop, + posix.EMLINK => return error.LinkQuotaExceeded, + posix.ENAMETOOLONG => return error.NameTooLong, + posix.ENOENT => return error.FileNotFound, + posix.ENOTDIR => return error.NotDir, + posix.ENOMEM => return error.SystemResources, + posix.ENOSPC => return error.NoSpaceLeft, + posix.EEXIST => return error.PathAlreadyExists, + posix.ENOTEMPTY => return error.PathAlreadyExists, + posix.EROFS => return error.ReadOnlyFileSystem, + posix.EXDEV => return error.RenameAcrossMountPoints, + else => return unexpectedErrorPosix(err), + } + } +} + +pub fn renameW(old_path: [*]const u16, new_path: [*]const u16) !void { + const flags = windows.MOVEFILE_REPLACE_EXISTING | windows.MOVEFILE_WRITE_THROUGH; + if (windows.MoveFileExW(old_path, new_path, flags) == 0) { + const err = windows.GetLastError(); + switch (err) { + else => return unexpectedErrorWindows(err), + } + } +} + +pub fn rename(old_path: []const u8, new_path: []const u8) !void { + if (is_windows) { + const old_path_w = try windows_util.sliceToPrefixedFileW(old_path); + const new_path_w = try windows_util.sliceToPrefixedFileW(new_path); + return renameW(&old_path_w, &new_path_w); + } else { + const old_path_c = try toPosixPath(old_path); + const new_path_c = try toPosixPath(new_path); + return renameC(&old_path_c, &new_path_c); + } +} + +pub fn makeDir(dir_path: []const u8) !void { + if (is_windows) { + return makeDirWindows(dir_path); + } else { + return makeDirPosix(dir_path); + } +} + +pub fn makeDirWindows(dir_path: []const u8) !void { + const dir_path_w = try windows_util.sliceToPrefixedFileW(dir_path); + + if (windows.CreateDirectoryW(&dir_path_w, null) == 0) { + const err = windows.GetLastError(); + return switch (err) { + windows.ERROR.ALREADY_EXISTS => error.PathAlreadyExists, + windows.ERROR.PATH_NOT_FOUND => error.FileNotFound, + else => unexpectedErrorWindows(err), + }; + } +} + +pub fn makeDirPosixC(dir_path: [*]const u8) !void { + const err = posix.getErrno(posix.mkdir(dir_path, 0o755)); + switch (err) { + 0 => return, + posix.EACCES => return error.AccessDenied, + posix.EPERM => return error.AccessDenied, + posix.EDQUOT => return error.DiskQuota, + posix.EEXIST => return error.PathAlreadyExists, + posix.EFAULT => unreachable, + posix.ELOOP => return error.SymLinkLoop, + posix.EMLINK => return error.LinkQuotaExceeded, + posix.ENAMETOOLONG => return error.NameTooLong, + posix.ENOENT => return error.FileNotFound, + posix.ENOMEM => return error.SystemResources, + posix.ENOSPC => return error.NoSpaceLeft, + posix.ENOTDIR => return error.NotDir, + posix.EROFS => return error.ReadOnlyFileSystem, + else => return unexpectedErrorPosix(err), + } +} + +pub fn makeDirPosix(dir_path: []const u8) !void { + const dir_path_c = try toPosixPath(dir_path); + return makeDirPosixC(&dir_path_c); +} + +/// Calls makeDir recursively to make an entire path. Returns success if the path +/// already exists and is a directory. +/// TODO determine if we can remove the allocator requirement from this function +pub fn makePath(allocator: *Allocator, full_path: []const u8) !void { + const resolved_path = try path.resolve(allocator, [][]const u8{full_path}); + defer allocator.free(resolved_path); + + var end_index: usize = resolved_path.len; + while (true) { + makeDir(resolved_path[0..end_index]) catch |err| switch (err) { + error.PathAlreadyExists => { + // TODO stat the file and return an error if it's not a directory + // this is important because otherwise a dangling symlink + // could cause an infinite loop + if (end_index == resolved_path.len) return; + }, + error.FileNotFound => { + // march end_index backward until next path component + while (true) { + end_index -= 1; + if (os.path.isSep(resolved_path[end_index])) break; + } + continue; + }, + else => return err, + }; + if (end_index == resolved_path.len) return; + // march end_index forward until next path component + while (true) { + end_index += 1; + if (end_index == resolved_path.len or os.path.isSep(resolved_path[end_index])) break; + } + } +} + +pub const DeleteDirError = error{ + AccessDenied, + FileBusy, + SymLinkLoop, + NameTooLong, + FileNotFound, + SystemResources, + NotDir, + DirNotEmpty, + ReadOnlyFileSystem, + InvalidUtf8, + BadPathName, + + /// See https://github.com/ziglang/zig/issues/1396 + Unexpected, +}; + +pub fn deleteDirC(dir_path: [*]const u8) DeleteDirError!void { + switch (builtin.os) { + Os.windows => { + const dir_path_w = try windows_util.cStrToPrefixedFileW(dir_path); + return deleteDirW(&dir_path_w); + }, + Os.linux, Os.macosx, Os.ios, Os.freebsd, Os.netbsd => { + const err = posix.getErrno(posix.rmdir(dir_path)); + switch (err) { + 0 => return, + posix.EACCES => return error.AccessDenied, + posix.EPERM => return error.AccessDenied, + posix.EBUSY => return error.FileBusy, + posix.EFAULT => unreachable, + posix.EINVAL => unreachable, + posix.ELOOP => return error.SymLinkLoop, + posix.ENAMETOOLONG => return error.NameTooLong, + posix.ENOENT => return error.FileNotFound, + posix.ENOMEM => return error.SystemResources, + posix.ENOTDIR => return error.NotDir, + posix.EEXIST => return error.DirNotEmpty, + posix.ENOTEMPTY => return error.DirNotEmpty, + posix.EROFS => return error.ReadOnlyFileSystem, + else => return unexpectedErrorPosix(err), + } + }, + else => @compileError("unimplemented"), + } +} + +pub fn deleteDirW(dir_path_w: [*]const u16) DeleteDirError!void { + if (windows.RemoveDirectoryW(dir_path_w) == 0) { + const err = windows.GetLastError(); + switch (err) { + windows.ERROR.PATH_NOT_FOUND => return error.FileNotFound, + windows.ERROR.DIR_NOT_EMPTY => return error.DirNotEmpty, + else => return unexpectedErrorWindows(err), + } + } +} + +/// Returns ::error.DirNotEmpty if the directory is not empty. +/// To delete a directory recursively, see ::deleteTree +pub fn deleteDir(dir_path: []const u8) DeleteDirError!void { + switch (builtin.os) { + Os.windows => { + const dir_path_w = try windows_util.sliceToPrefixedFileW(dir_path); + return deleteDirW(&dir_path_w); + }, + Os.linux, Os.macosx, Os.ios, Os.freebsd, Os.netbsd => { + const dir_path_c = try toPosixPath(dir_path); + return deleteDirC(&dir_path_c); + }, + else => @compileError("unimplemented"), + } +} + +/// Whether ::full_path describes a symlink, file, or directory, this function +/// removes it. If it cannot be removed because it is a non-empty directory, +/// this function recursively removes its entries and then tries again. +const DeleteTreeError = error{ + OutOfMemory, + AccessDenied, + FileTooBig, + IsDir, + SymLinkLoop, + ProcessFdQuotaExceeded, + NameTooLong, + SystemFdQuotaExceeded, + NoDevice, + SystemResources, + NoSpaceLeft, + PathAlreadyExists, + ReadOnlyFileSystem, + NotDir, + FileNotFound, + FileSystem, + FileBusy, + DirNotEmpty, + DeviceBusy, + + /// On Windows, file paths must be valid Unicode. + InvalidUtf8, + + /// On Windows, file paths cannot contain these characters: + /// '/', '*', '?', '"', '<', '>', '|' + BadPathName, + + /// See https://github.com/ziglang/zig/issues/1396 + Unexpected, +}; + +/// TODO determine if we can remove the allocator requirement +pub fn deleteTree(allocator: *Allocator, full_path: []const u8) DeleteTreeError!void { + start_over: while (true) { + var got_access_denied = false; + // First, try deleting the item as a file. This way we don't follow sym links. + if (deleteFile(full_path)) { + return; + } else |err| switch (err) { + error.FileNotFound => return, + error.IsDir => {}, + error.AccessDenied => got_access_denied = true, + + error.InvalidUtf8, + error.SymLinkLoop, + error.NameTooLong, + error.SystemResources, + error.ReadOnlyFileSystem, + error.NotDir, + error.FileSystem, + error.FileBusy, + error.BadPathName, + error.Unexpected, + => return err, + } + { + var dir = Dir.open(allocator, full_path) catch |err| switch (err) { + error.NotDir => { + if (got_access_denied) { + return error.AccessDenied; + } + continue :start_over; + }, + + error.OutOfMemory, + error.AccessDenied, + error.FileTooBig, + error.IsDir, + error.SymLinkLoop, + error.ProcessFdQuotaExceeded, + error.NameTooLong, + error.SystemFdQuotaExceeded, + error.NoDevice, + error.FileNotFound, + error.SystemResources, + error.NoSpaceLeft, + error.PathAlreadyExists, + error.Unexpected, + error.InvalidUtf8, + error.BadPathName, + error.DeviceBusy, + => return err, + }; + defer dir.close(); + + var full_entry_buf = ArrayList(u8).init(allocator); + defer full_entry_buf.deinit(); + + while (try dir.next()) |entry| { + try full_entry_buf.resize(full_path.len + entry.name.len + 1); + const full_entry_path = full_entry_buf.toSlice(); + mem.copy(u8, full_entry_path, full_path); + full_entry_path[full_path.len] = path.sep; + mem.copy(u8, full_entry_path[full_path.len + 1 ..], entry.name); + + try deleteTree(allocator, full_entry_path); + } + } + return deleteDir(full_path); + } +} + +pub const Dir = struct { + handle: Handle, + allocator: *Allocator, + + pub const Handle = switch (builtin.os) { + Os.macosx, Os.ios, Os.freebsd, Os.netbsd => struct { + fd: i32, + seek: i64, + buf: []u8, + index: usize, + end_index: usize, + }, + Os.linux => struct { + fd: i32, + buf: []u8, + index: usize, + end_index: usize, + }, + Os.windows => struct { + handle: windows.HANDLE, + find_file_data: windows.WIN32_FIND_DATAW, + first: bool, + name_data: [256]u8, + }, + else => @compileError("unimplemented"), + }; + + pub const Entry = struct { + name: []const u8, + kind: Kind, + + pub const Kind = enum { + BlockDevice, + CharacterDevice, + Directory, + NamedPipe, + SymLink, + File, + UnixDomainSocket, + Whiteout, + Unknown, + }; + }; + + pub const OpenError = error{ + FileNotFound, + NotDir, + AccessDenied, + FileTooBig, + IsDir, + SymLinkLoop, + ProcessFdQuotaExceeded, + NameTooLong, + SystemFdQuotaExceeded, + NoDevice, + SystemResources, + NoSpaceLeft, + PathAlreadyExists, + OutOfMemory, + InvalidUtf8, + BadPathName, + DeviceBusy, + + /// See https://github.com/ziglang/zig/issues/1396 + Unexpected, + }; + + /// TODO remove the allocator requirement from this API + pub fn open(allocator: *Allocator, dir_path: []const u8) OpenError!Dir { + return Dir{ + .allocator = allocator, + .handle = switch (builtin.os) { + Os.windows => blk: { + var find_file_data: windows.WIN32_FIND_DATAW = undefined; + const handle = try windows_util.windowsFindFirstFile(dir_path, &find_file_data); + break :blk Handle{ + .handle = handle, + .find_file_data = find_file_data, // TODO guaranteed copy elision + .first = true, + .name_data = undefined, + }; + }, + Os.macosx, Os.ios, Os.freebsd, Os.netbsd => Handle{ + .fd = try posixOpen( + dir_path, + posix.O_RDONLY | posix.O_NONBLOCK | posix.O_DIRECTORY | posix.O_CLOEXEC, + 0, + ), + .seek = 0, + .index = 0, + .end_index = 0, + .buf = []u8{}, + }, + Os.linux => Handle{ + .fd = try posixOpen( + dir_path, + posix.O_RDONLY | posix.O_DIRECTORY | posix.O_CLOEXEC, + 0, + ), + .index = 0, + .end_index = 0, + .buf = []u8{}, + }, + else => @compileError("unimplemented"), + }, + }; + } + + pub fn close(self: *Dir) void { + switch (builtin.os) { + Os.windows => { + _ = windows.FindClose(self.handle.handle); + }, + Os.macosx, Os.ios, Os.linux, Os.freebsd, Os.netbsd => { + self.allocator.free(self.handle.buf); + os.close(self.handle.fd); + }, + else => @compileError("unimplemented"), + } + } + + /// Memory such as file names referenced in this returned entry becomes invalid + /// with subsequent calls to next, as well as when this `Dir` is deinitialized. + pub fn next(self: *Dir) !?Entry { + switch (builtin.os) { + Os.linux => return self.nextLinux(), + Os.macosx, Os.ios => return self.nextDarwin(), + Os.windows => return self.nextWindows(), + Os.freebsd => return self.nextFreebsd(), + Os.netbsd => return self.nextFreebsd(), + else => @compileError("unimplemented"), + } + } + + fn nextDarwin(self: *Dir) !?Entry { + start_over: while (true) { + if (self.handle.index >= self.handle.end_index) { + if (self.handle.buf.len == 0) { + self.handle.buf = try self.allocator.alloc(u8, page_size); + } + + while (true) { + const result = posix.getdirentries64(self.handle.fd, self.handle.buf.ptr, self.handle.buf.len, &self.handle.seek); + const err = posix.getErrno(result); + if (err > 0) { + switch (err) { + posix.EBADF, posix.EFAULT, posix.ENOTDIR => unreachable, + posix.EINVAL => { + self.handle.buf = try self.allocator.realloc(u8, self.handle.buf, self.handle.buf.len * 2); + continue; + }, + else => return unexpectedErrorPosix(err), + } + } + if (result == 0) return null; + self.handle.index = 0; + self.handle.end_index = result; + break; + } + } + const darwin_entry = @ptrCast(*align(1) posix.dirent, &self.handle.buf[self.handle.index]); + const next_index = self.handle.index + darwin_entry.d_reclen; + self.handle.index = next_index; + + const name = @ptrCast([*]u8, &darwin_entry.d_name)[0..darwin_entry.d_namlen]; + + if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) { + continue :start_over; + } + + const entry_kind = switch (darwin_entry.d_type) { + posix.DT_BLK => Entry.Kind.BlockDevice, + posix.DT_CHR => Entry.Kind.CharacterDevice, + posix.DT_DIR => Entry.Kind.Directory, + posix.DT_FIFO => Entry.Kind.NamedPipe, + posix.DT_LNK => Entry.Kind.SymLink, + posix.DT_REG => Entry.Kind.File, + posix.DT_SOCK => Entry.Kind.UnixDomainSocket, + posix.DT_WHT => Entry.Kind.Whiteout, + else => Entry.Kind.Unknown, + }; + return Entry{ + .name = name, + .kind = entry_kind, + }; + } + } + + fn nextWindows(self: *Dir) !?Entry { + while (true) { + if (self.handle.first) { + self.handle.first = false; + } else { + if (!try windows_util.windowsFindNextFile(self.handle.handle, &self.handle.find_file_data)) + return null; + } + const name_utf16le = mem.toSlice(u16, self.handle.find_file_data.cFileName[0..].ptr); + if (mem.eql(u16, name_utf16le, []u16{'.'}) or mem.eql(u16, name_utf16le, []u16{ '.', '.' })) + continue; + // Trust that Windows gives us valid UTF-16LE + const name_utf8_len = std.unicode.utf16leToUtf8(self.handle.name_data[0..], name_utf16le) catch unreachable; + const name_utf8 = self.handle.name_data[0..name_utf8_len]; + const kind = blk: { + const attrs = self.handle.find_file_data.dwFileAttributes; + if (attrs & windows.FILE_ATTRIBUTE_DIRECTORY != 0) break :blk Entry.Kind.Directory; + if (attrs & windows.FILE_ATTRIBUTE_REPARSE_POINT != 0) break :blk Entry.Kind.SymLink; + if (attrs & windows.FILE_ATTRIBUTE_NORMAL != 0) break :blk Entry.Kind.File; + break :blk Entry.Kind.Unknown; + }; + return Entry{ + .name = name_utf8, + .kind = kind, + }; + } + } + + fn nextLinux(self: *Dir) !?Entry { + start_over: while (true) { + if (self.handle.index >= self.handle.end_index) { + if (self.handle.buf.len == 0) { + self.handle.buf = try self.allocator.alloc(u8, page_size); + } + + while (true) { + const result = posix.getdents64(self.handle.fd, self.handle.buf.ptr, self.handle.buf.len); + const err = posix.getErrno(result); + if (err > 0) { + switch (err) { + posix.EBADF, posix.EFAULT, posix.ENOTDIR => unreachable, + posix.EINVAL => { + self.handle.buf = try self.allocator.realloc(u8, self.handle.buf, self.handle.buf.len * 2); + continue; + }, + else => return unexpectedErrorPosix(err), + } + } + if (result == 0) return null; + self.handle.index = 0; + self.handle.end_index = result; + break; + } + } + const linux_entry = @ptrCast(*align(1) posix.dirent64, &self.handle.buf[self.handle.index]); + const next_index = self.handle.index + linux_entry.d_reclen; + self.handle.index = next_index; + + const name = cstr.toSlice(@ptrCast([*]u8, &linux_entry.d_name)); + + // skip . and .. entries + if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) { + continue :start_over; + } + + const entry_kind = switch (linux_entry.d_type) { + posix.DT_BLK => Entry.Kind.BlockDevice, + posix.DT_CHR => Entry.Kind.CharacterDevice, + posix.DT_DIR => Entry.Kind.Directory, + posix.DT_FIFO => Entry.Kind.NamedPipe, + posix.DT_LNK => Entry.Kind.SymLink, + posix.DT_REG => Entry.Kind.File, + posix.DT_SOCK => Entry.Kind.UnixDomainSocket, + else => Entry.Kind.Unknown, + }; + return Entry{ + .name = name, + .kind = entry_kind, + }; + } + } + + fn nextFreebsd(self: *Dir) !?Entry { + start_over: while (true) { + if (self.handle.index >= self.handle.end_index) { + if (self.handle.buf.len == 0) { + self.handle.buf = try self.allocator.alloc(u8, page_size); + } + + while (true) { + const result = posix.getdirentries(self.handle.fd, self.handle.buf.ptr, self.handle.buf.len, &self.handle.seek); + const err = posix.getErrno(result); + if (err > 0) { + switch (err) { + posix.EBADF, posix.EFAULT, posix.ENOTDIR => unreachable, + posix.EINVAL => { + self.handle.buf = try self.allocator.realloc(u8, self.handle.buf, self.handle.buf.len * 2); + continue; + }, + else => return unexpectedErrorPosix(err), + } + } + if (result == 0) return null; + self.handle.index = 0; + self.handle.end_index = result; + break; + } + } + const freebsd_entry = @ptrCast(*align(1) posix.dirent, &self.handle.buf[self.handle.index]); + const next_index = self.handle.index + freebsd_entry.d_reclen; + self.handle.index = next_index; + + const name = @ptrCast([*]u8, &freebsd_entry.d_name)[0..freebsd_entry.d_namlen]; + + if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) { + continue :start_over; + } + + const entry_kind = switch (freebsd_entry.d_type) { + posix.DT_BLK => Entry.Kind.BlockDevice, + posix.DT_CHR => Entry.Kind.CharacterDevice, + posix.DT_DIR => Entry.Kind.Directory, + posix.DT_FIFO => Entry.Kind.NamedPipe, + posix.DT_LNK => Entry.Kind.SymLink, + posix.DT_REG => Entry.Kind.File, + posix.DT_SOCK => Entry.Kind.UnixDomainSocket, + posix.DT_WHT => Entry.Kind.Whiteout, + else => Entry.Kind.Unknown, + }; + return Entry{ + .name = name, + .kind = entry_kind, + }; + } + } +}; + +pub fn changeCurDir(allocator: *Allocator, dir_path: []const u8) !void { + const path_buf = try allocator.alloc(u8, dir_path.len + 1); + defer allocator.free(path_buf); + + mem.copy(u8, path_buf, dir_path); + path_buf[dir_path.len] = 0; + + const err = posix.getErrno(posix.chdir(path_buf.ptr)); + if (err > 0) { + return switch (err) { + posix.EACCES => error.AccessDenied, + posix.EFAULT => unreachable, + posix.EIO => error.FileSystem, + posix.ELOOP => error.SymLinkLoop, + posix.ENAMETOOLONG => error.NameTooLong, + posix.ENOENT => error.FileNotFound, + posix.ENOMEM => error.SystemResources, + posix.ENOTDIR => error.NotDir, + else => unexpectedErrorPosix(err), + }; + } +} + +/// Read value of a symbolic link. +/// The return value is a slice of out_buffer. +pub fn readLinkC(out_buffer: *[posix.PATH_MAX]u8, pathname: [*]const u8) ![]u8 { + const rc = posix.readlink(pathname, out_buffer, out_buffer.len); + const err = posix.getErrno(rc); + switch (err) { + 0 => return out_buffer[0..rc], + posix.EACCES => return error.AccessDenied, + posix.EFAULT => unreachable, + posix.EINVAL => unreachable, + posix.EIO => return error.FileSystem, + posix.ELOOP => return error.SymLinkLoop, + posix.ENAMETOOLONG => unreachable, // out_buffer is at least PATH_MAX + posix.ENOENT => return error.FileNotFound, + posix.ENOMEM => return error.SystemResources, + posix.ENOTDIR => return error.NotDir, + else => return unexpectedErrorPosix(err), + } +} + +/// Read value of a symbolic link. +/// The return value is a slice of out_buffer. +pub fn readLink(out_buffer: *[posix.PATH_MAX]u8, file_path: []const u8) ![]u8 { + const file_path_c = try toPosixPath(file_path); + return readLinkC(out_buffer, &file_path_c); +} + +pub fn posix_setuid(uid: u32) !void { + const err = posix.getErrno(posix.setuid(uid)); + if (err == 0) return; + return switch (err) { + posix.EAGAIN => error.ResourceLimitReached, + posix.EINVAL => error.InvalidUserId, + posix.EPERM => error.PermissionDenied, + else => unexpectedErrorPosix(err), + }; +} + +pub fn posix_setreuid(ruid: u32, euid: u32) !void { + const err = posix.getErrno(posix.setreuid(ruid, euid)); + if (err == 0) return; + return switch (err) { + posix.EAGAIN => error.ResourceLimitReached, + posix.EINVAL => error.InvalidUserId, + posix.EPERM => error.PermissionDenied, + else => unexpectedErrorPosix(err), + }; +} + +pub fn posix_setgid(gid: u32) !void { + const err = posix.getErrno(posix.setgid(gid)); + if (err == 0) return; + return switch (err) { + posix.EAGAIN => error.ResourceLimitReached, + posix.EINVAL => error.InvalidUserId, + posix.EPERM => error.PermissionDenied, + else => unexpectedErrorPosix(err), + }; +} + +pub fn posix_setregid(rgid: u32, egid: u32) !void { + const err = posix.getErrno(posix.setregid(rgid, egid)); + if (err == 0) return; + return switch (err) { + posix.EAGAIN => error.ResourceLimitReached, + posix.EINVAL => error.InvalidUserId, + posix.EPERM => error.PermissionDenied, + else => unexpectedErrorPosix(err), + }; +} + +pub const WindowsGetStdHandleErrs = error{ + NoStdHandles, + + /// See https://github.com/ziglang/zig/issues/1396 + Unexpected, +}; + +pub fn windowsGetStdHandle(handle_id: windows.DWORD) WindowsGetStdHandleErrs!windows.HANDLE { + if (windows.GetStdHandle(handle_id)) |handle| { + if (handle == windows.INVALID_HANDLE_VALUE) { + const err = windows.GetLastError(); + return switch (err) { + else => os.unexpectedErrorWindows(err), + }; + } + return handle; + } else { + return error.NoStdHandles; + } +} + +pub const ArgIteratorPosix = struct { + index: usize, + count: usize, + + pub fn init() ArgIteratorPosix { + return ArgIteratorPosix{ + .index = 0, + .count = raw.len, + }; + } + + pub fn next(self: *ArgIteratorPosix) ?[]const u8 { + if (self.index == self.count) return null; + + const s = raw[self.index]; + self.index += 1; + return cstr.toSlice(s); + } + + pub fn skip(self: *ArgIteratorPosix) bool { + if (self.index == self.count) return false; + + self.index += 1; + return true; + } + + /// This is marked as public but actually it's only meant to be used + /// internally by zig's startup code. + pub var raw: [][*]u8 = undefined; +}; + +pub const ArgIteratorWindows = struct { + index: usize, + cmd_line: [*]const u8, + in_quote: bool, + quote_count: usize, + seen_quote_count: usize, + + pub const NextError = error{OutOfMemory}; + + pub fn init() ArgIteratorWindows { + return initWithCmdLine(windows.GetCommandLineA()); + } + + pub fn initWithCmdLine(cmd_line: [*]const u8) ArgIteratorWindows { + return ArgIteratorWindows{ + .index = 0, + .cmd_line = cmd_line, + .in_quote = false, + .quote_count = countQuotes(cmd_line), + .seen_quote_count = 0, + }; + } + + /// You must free the returned memory when done. + pub fn next(self: *ArgIteratorWindows, allocator: *Allocator) ?(NextError![]u8) { + // march forward over whitespace + while (true) : (self.index += 1) { + const byte = self.cmd_line[self.index]; + switch (byte) { + 0 => return null, + ' ', '\t' => continue, + else => break, + } + } + + return self.internalNext(allocator); + } + + pub fn skip(self: *ArgIteratorWindows) bool { + // march forward over whitespace + while (true) : (self.index += 1) { + const byte = self.cmd_line[self.index]; + switch (byte) { + 0 => return false, + ' ', '\t' => continue, + else => break, + } + } + + var backslash_count: usize = 0; + while (true) : (self.index += 1) { + const byte = self.cmd_line[self.index]; + switch (byte) { + 0 => return true, + '"' => { + const quote_is_real = backslash_count % 2 == 0; + if (quote_is_real) { + self.seen_quote_count += 1; + } + }, + '\\' => { + backslash_count += 1; + }, + ' ', '\t' => { + if (self.seen_quote_count % 2 == 0 or self.seen_quote_count == self.quote_count) { + return true; + } + backslash_count = 0; + }, + else => { + backslash_count = 0; + continue; + }, + } + } + } + + fn internalNext(self: *ArgIteratorWindows, allocator: *Allocator) NextError![]u8 { + var buf = try Buffer.initSize(allocator, 0); + defer buf.deinit(); + + var backslash_count: usize = 0; + while (true) : (self.index += 1) { + const byte = self.cmd_line[self.index]; + switch (byte) { + 0 => return buf.toOwnedSlice(), + '"' => { + const quote_is_real = backslash_count % 2 == 0; + try self.emitBackslashes(&buf, backslash_count / 2); + backslash_count = 0; + + if (quote_is_real) { + self.seen_quote_count += 1; + if (self.seen_quote_count == self.quote_count and self.seen_quote_count % 2 == 1) { + try buf.appendByte('"'); + } + } else { + try buf.appendByte('"'); + } + }, + '\\' => { + backslash_count += 1; + }, + ' ', '\t' => { + try self.emitBackslashes(&buf, backslash_count); + backslash_count = 0; + if (self.seen_quote_count % 2 == 1 and self.seen_quote_count != self.quote_count) { + try buf.appendByte(byte); + } else { + return buf.toOwnedSlice(); + } + }, + else => { + try self.emitBackslashes(&buf, backslash_count); + backslash_count = 0; + try buf.appendByte(byte); + }, + } + } + } + + fn emitBackslashes(self: *ArgIteratorWindows, buf: *Buffer, emit_count: usize) !void { + var i: usize = 0; + while (i < emit_count) : (i += 1) { + try buf.appendByte('\\'); + } + } + + fn countQuotes(cmd_line: [*]const u8) usize { + var result: usize = 0; + var backslash_count: usize = 0; + var index: usize = 0; + while (true) : (index += 1) { + const byte = cmd_line[index]; + switch (byte) { + 0 => return result, + '\\' => backslash_count += 1, + '"' => { + result += 1 - (backslash_count % 2); + backslash_count = 0; + }, + else => { + backslash_count = 0; + }, + } + } + } +}; + +pub const ArgIterator = struct { + const InnerType = if (builtin.os == Os.windows) ArgIteratorWindows else ArgIteratorPosix; + + inner: InnerType, + + pub fn init() ArgIterator { + return ArgIterator{ .inner = InnerType.init() }; + } + + pub const NextError = ArgIteratorWindows.NextError; + + /// You must free the returned memory when done. + pub fn next(self: *ArgIterator, allocator: *Allocator) ?(NextError![]u8) { + if (builtin.os == Os.windows) { + return self.inner.next(allocator); + } else { + return mem.dupe(allocator, u8, self.inner.next() orelse return null); + } + } + + /// If you only are targeting posix you can call this and not need an allocator. + pub fn nextPosix(self: *ArgIterator) ?[]const u8 { + return self.inner.next(); + } + + /// Parse past 1 argument without capturing it. + /// Returns `true` if skipped an arg, `false` if we are at the end. + pub fn skip(self: *ArgIterator) bool { + return self.inner.skip(); + } +}; + +pub fn args() ArgIterator { + return ArgIterator.init(); +} + +/// Caller must call argsFree on result. +pub fn argsAlloc(allocator: *mem.Allocator) ![]const []u8 { + // TODO refactor to only make 1 allocation. + var it = args(); + var contents = try Buffer.initSize(allocator, 0); + defer contents.deinit(); + + var slice_list = ArrayList(usize).init(allocator); + defer slice_list.deinit(); + + while (it.next(allocator)) |arg_or_err| { + const arg = try arg_or_err; + defer allocator.free(arg); + try contents.append(arg); + try slice_list.append(arg.len); + } + + const contents_slice = contents.toSliceConst(); + const slice_sizes = slice_list.toSliceConst(); + const slice_list_bytes = try math.mul(usize, @sizeOf([]u8), slice_sizes.len); + const total_bytes = try math.add(usize, slice_list_bytes, contents_slice.len); + const buf = try allocator.alignedAlloc(u8, @alignOf([]u8), total_bytes); + errdefer allocator.free(buf); + + const result_slice_list = @bytesToSlice([]u8, buf[0..slice_list_bytes]); + const result_contents = buf[slice_list_bytes..]; + mem.copy(u8, result_contents, contents_slice); + + var contents_index: usize = 0; + for (slice_sizes) |len, i| { + const new_index = contents_index + len; + result_slice_list[i] = result_contents[contents_index..new_index]; + contents_index = new_index; + } + + return result_slice_list; +} + +pub fn argsFree(allocator: *mem.Allocator, args_alloc: []const []u8) void { + var total_bytes: usize = 0; + for (args_alloc) |arg| { + total_bytes += @sizeOf([]u8) + arg.len; + } + const unaligned_allocated_buf = @ptrCast([*]const u8, args_alloc.ptr)[0..total_bytes]; + const aligned_allocated_buf = @alignCast(@alignOf([]u8), unaligned_allocated_buf); + return allocator.free(aligned_allocated_buf); +} + +test "windows arg parsing" { + testWindowsCmdLine(c"a b\tc d", [][]const u8{ "a", "b", "c", "d" }); + testWindowsCmdLine(c"\"abc\" d e", [][]const u8{ "abc", "d", "e" }); + testWindowsCmdLine(c"a\\\\\\b d\"e f\"g h", [][]const u8{ "a\\\\\\b", "de fg", "h" }); + testWindowsCmdLine(c"a\\\\\\\"b c d", [][]const u8{ "a\\\"b", "c", "d" }); + testWindowsCmdLine(c"a\\\\\\\\\"b c\" d e", [][]const u8{ "a\\\\b c", "d", "e" }); + testWindowsCmdLine(c"a b\tc \"d f", [][]const u8{ "a", "b", "c", "\"d", "f" }); + + testWindowsCmdLine(c"\".\\..\\zig-cache\\build\" \"bin\\zig.exe\" \".\\..\" \".\\..\\zig-cache\" \"--help\"", [][]const u8{ + ".\\..\\zig-cache\\build", + "bin\\zig.exe", + ".\\..", + ".\\..\\zig-cache", + "--help", + }); +} + +fn testWindowsCmdLine(input_cmd_line: [*]const u8, expected_args: []const []const u8) void { + var it = ArgIteratorWindows.initWithCmdLine(input_cmd_line); + for (expected_args) |expected_arg| { + const arg = it.next(debug.global_allocator).? catch unreachable; + testing.expectEqualSlices(u8, expected_arg, arg); + } + testing.expect(it.next(debug.global_allocator) == null); +} + +// TODO make this a build variable that you can set +const unexpected_error_tracing = false; +const UnexpectedError = error{ + /// The Operating System returned an undocumented error code. + Unexpected, +}; + +/// Call this when you made a syscall or something that sets errno +/// and you get an unexpected error. +pub fn unexpectedErrorPosix(errno: usize) UnexpectedError { + if (unexpected_error_tracing) { + debug.warn("unexpected errno: {}\n", errno); + debug.dumpCurrentStackTrace(null); + } + return error.Unexpected; +} + +/// Call this when you made a windows DLL call or something that does SetLastError +/// and you get an unexpected error. +pub fn unexpectedErrorWindows(err: windows.DWORD) UnexpectedError { + if (unexpected_error_tracing) { + debug.warn("unexpected GetLastError(): {}\n", err); + @breakpoint(); + debug.dumpCurrentStackTrace(null); + } + return error.Unexpected; +} + +pub fn openSelfExe() !os.File { + switch (builtin.os) { + Os.linux => return os.File.openReadC(c"/proc/self/exe"), + Os.macosx, Os.ios, Os.freebsd, Os.netbsd => { + var buf: [MAX_PATH_BYTES]u8 = undefined; + const self_exe_path = try selfExePath(&buf); + buf[self_exe_path.len] = 0; + return os.File.openReadC(self_exe_path.ptr); + }, + Os.windows => { + var buf: [windows_util.PATH_MAX_WIDE]u16 = undefined; + const wide_slice = try selfExePathW(&buf); + return os.File.openReadW(wide_slice.ptr); + }, + else => @compileError("Unsupported OS"), + } +} + +test "openSelfExe" { + switch (builtin.os) { + Os.linux, Os.macosx, Os.ios, Os.windows, Os.freebsd => (try openSelfExe()).close(), + else => return error.SkipZigTest, // Unsupported OS. + } +} + +pub fn selfExePathW(out_buffer: *[windows_util.PATH_MAX_WIDE]u16) ![]u16 { + const casted_len = @intCast(windows.DWORD, out_buffer.len); // TODO shouldn't need this cast + const rc = windows.GetModuleFileNameW(null, out_buffer, casted_len); + assert(rc <= out_buffer.len); + if (rc == 0) { + const err = windows.GetLastError(); + switch (err) { + else => return unexpectedErrorWindows(err), + } + } + return out_buffer[0..rc]; +} + +/// Get the path to the current executable. +/// If you only need the directory, use selfExeDirPath. +/// If you only want an open file handle, use openSelfExe. +/// This function may return an error if the current executable +/// was deleted after spawning. +/// Returned value is a slice of out_buffer. +/// +/// On Linux, depends on procfs being mounted. If the currently executing binary has +/// been deleted, the file path looks something like `/a/b/c/exe (deleted)`. +/// TODO make the return type of this a null terminated pointer +pub fn selfExePath(out_buffer: *[MAX_PATH_BYTES]u8) ![]u8 { + switch (builtin.os) { + Os.linux => return readLink(out_buffer, "/proc/self/exe"), + Os.freebsd => { + var mib = [4]c_int{ posix.CTL_KERN, posix.KERN_PROC, posix.KERN_PROC_PATHNAME, -1 }; + var out_len: usize = out_buffer.len; + const err = posix.getErrno(posix.sysctl(&mib, 4, out_buffer, &out_len, null, 0)); + + if (err == 0) return mem.toSlice(u8, out_buffer); + + return switch (err) { + posix.EFAULT => error.BadAdress, + posix.EPERM => error.PermissionDenied, + else => unexpectedErrorPosix(err), + }; + }, + Os.netbsd => { + var mib = [4]c_int{ posix.CTL_KERN, posix.KERN_PROC_ARGS, -1, posix.KERN_PROC_PATHNAME }; + var out_len: usize = out_buffer.len; + const err = posix.getErrno(posix.sysctl(&mib, 4, out_buffer, &out_len, null, 0)); + + if (err == 0) return mem.toSlice(u8, out_buffer); + + return switch (err) { + posix.EFAULT => error.BadAdress, + posix.EPERM => error.PermissionDenied, + else => unexpectedErrorPosix(err), + }; + }, + Os.windows => { + var utf16le_buf: [windows_util.PATH_MAX_WIDE]u16 = undefined; + const utf16le_slice = try selfExePathW(&utf16le_buf); + // Trust that Windows gives us valid UTF-16LE. + const end_index = std.unicode.utf16leToUtf8(out_buffer, utf16le_slice) catch unreachable; + return out_buffer[0..end_index]; + }, + Os.macosx, Os.ios => { + var u32_len: u32 = @intCast(u32, out_buffer.len); // TODO shouldn't need this cast + const rc = c._NSGetExecutablePath(out_buffer, &u32_len); + if (rc != 0) return error.NameTooLong; + return mem.toSlice(u8, out_buffer); + }, + else => @compileError("Unsupported OS"), + } +} + +/// `selfExeDirPath` except allocates the result on the heap. +/// Caller owns returned memory. +pub fn selfExeDirPathAlloc(allocator: *Allocator) ![]u8 { + var buf: [MAX_PATH_BYTES]u8 = undefined; + return mem.dupe(allocator, u8, try selfExeDirPath(&buf)); +} + +/// Get the directory path that contains the current executable. +/// Returned value is a slice of out_buffer. +pub fn selfExeDirPath(out_buffer: *[MAX_PATH_BYTES]u8) ![]const u8 { + switch (builtin.os) { + Os.linux => { + // If the currently executing binary has been deleted, + // the file path looks something like `/a/b/c/exe (deleted)` + // This path cannot be opened, but it's valid for determining the directory + // the executable was in when it was run. + const full_exe_path = try readLinkC(out_buffer, c"/proc/self/exe"); + // Assume that /proc/self/exe has an absolute path, and therefore dirname + // will not return null. + return path.dirname(full_exe_path).?; + }, + Os.windows, Os.macosx, Os.ios, Os.freebsd, Os.netbsd => { + const self_exe_path = try selfExePath(out_buffer); + // Assume that the OS APIs return absolute paths, and therefore dirname + // will not return null. + return path.dirname(self_exe_path).?; + }, + else => @compileError("Unsupported OS"), + } +} + +pub fn isTty(handle: FileHandle) bool { + if (is_windows) { + return windows_util.windowsIsTty(handle); + } else { + if (builtin.link_libc) { + return c.isatty(handle) != 0; + } else { + return posix.isatty(handle); + } + } +} + +pub fn supportsAnsiEscapeCodes(handle: FileHandle) bool { + if (is_windows) { + return windows_util.windowsIsCygwinPty(handle); + } else { + if (builtin.link_libc) { + return c.isatty(handle) != 0; + } else { + return posix.isatty(handle); + } + } +} + +pub const PosixSocketError = 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, +}; + +pub fn posixSocket(domain: u32, socket_type: u32, protocol: u32) !i32 { + const rc = posix.socket(domain, socket_type, protocol); + const err = posix.getErrno(rc); + switch (err) { + 0 => return @intCast(i32, rc), + posix.EACCES => return PosixSocketError.PermissionDenied, + posix.EAFNOSUPPORT => return PosixSocketError.AddressFamilyNotSupported, + posix.EINVAL => return PosixSocketError.ProtocolFamilyNotAvailable, + posix.EMFILE => return PosixSocketError.ProcessFdQuotaExceeded, + posix.ENFILE => return PosixSocketError.SystemFdQuotaExceeded, + posix.ENOBUFS, posix.ENOMEM => return PosixSocketError.SystemResources, + posix.EPROTONOSUPPORT => return PosixSocketError.ProtocolNotSupported, + else => return unexpectedErrorPosix(err), + } +} + +pub const PosixBindError = 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, + + /// See https://github.com/ziglang/zig/issues/1396 + Unexpected, +}; + +/// addr is `&const T` where T is one of the sockaddr +pub fn posixBind(fd: i32, addr: *const posix.sockaddr) PosixBindError!void { + const rc = posix.bind(fd, addr, @sizeOf(posix.sockaddr)); + const err = posix.getErrno(rc); + switch (err) { + 0 => return, + posix.EACCES => return PosixBindError.AccessDenied, + posix.EADDRINUSE => return PosixBindError.AddressInUse, + posix.EBADF => unreachable, // always a race condition if this error is returned + posix.EINVAL => unreachable, + posix.ENOTSOCK => unreachable, + posix.EADDRNOTAVAIL => return PosixBindError.AddressNotAvailable, + posix.EFAULT => unreachable, + posix.ELOOP => return PosixBindError.SymLinkLoop, + posix.ENAMETOOLONG => return PosixBindError.NameTooLong, + posix.ENOENT => return PosixBindError.FileNotFound, + posix.ENOMEM => return PosixBindError.SystemResources, + posix.ENOTDIR => return PosixBindError.NotDir, + posix.EROFS => return PosixBindError.ReadOnlyFileSystem, + else => return unexpectedErrorPosix(err), + } +} + +const PosixListenError = 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, + + /// See https://github.com/ziglang/zig/issues/1396 + Unexpected, +}; + +pub fn posixListen(sockfd: i32, backlog: u32) PosixListenError!void { + const rc = posix.listen(sockfd, backlog); + const err = posix.getErrno(rc); + switch (err) { + 0 => return, + posix.EADDRINUSE => return PosixListenError.AddressInUse, + posix.EBADF => unreachable, + posix.ENOTSOCK => return PosixListenError.FileDescriptorNotASocket, + posix.EOPNOTSUPP => return PosixListenError.OperationNotSupported, + else => return unexpectedErrorPosix(err), + } +} + +pub const PosixAcceptError = error{ + ConnectionAborted, + + /// The per-process limit on the number of open file descriptors has been reached. + ProcessFdQuotaExceeded, + + /// The system-wide limit on the total number of open files has been reached. + SystemFdQuotaExceeded, + + /// Not enough free memory. This often means that the memory allocation is limited + /// by the socket buffer limits, not by the system memory. + SystemResources, + + /// The file descriptor sockfd does not refer to a socket. + FileDescriptorNotASocket, + + /// The referenced socket is not of type SOCK_STREAM. + OperationNotSupported, + + ProtocolFailure, + + /// Firewall rules forbid connection. + BlockedByFirewall, + + /// See https://github.com/ziglang/zig/issues/1396 + Unexpected, +}; + +pub fn posixAccept(fd: i32, addr: *posix.sockaddr, flags: u32) PosixAcceptError!i32 { + while (true) { + var sockaddr_size = u32(@sizeOf(posix.sockaddr)); + const rc = posix.accept4(fd, addr, &sockaddr_size, flags); + const err = posix.getErrno(rc); + switch (err) { + 0 => return @intCast(i32, rc), + posix.EINTR => continue, + else => return unexpectedErrorPosix(err), + + posix.EAGAIN => unreachable, // use posixAsyncAccept for non-blocking + posix.EBADF => unreachable, // always a race condition + posix.ECONNABORTED => return PosixAcceptError.ConnectionAborted, + posix.EFAULT => unreachable, + posix.EINVAL => unreachable, + posix.EMFILE => return PosixAcceptError.ProcessFdQuotaExceeded, + posix.ENFILE => return PosixAcceptError.SystemFdQuotaExceeded, + posix.ENOBUFS => return PosixAcceptError.SystemResources, + posix.ENOMEM => return PosixAcceptError.SystemResources, + posix.ENOTSOCK => return PosixAcceptError.FileDescriptorNotASocket, + posix.EOPNOTSUPP => return PosixAcceptError.OperationNotSupported, + posix.EPROTO => return PosixAcceptError.ProtocolFailure, + posix.EPERM => return PosixAcceptError.BlockedByFirewall, + } + } +} + +/// Returns -1 if would block. +pub fn posixAsyncAccept(fd: i32, addr: *posix.sockaddr, flags: u32) PosixAcceptError!i32 { + while (true) { + var sockaddr_size = u32(@sizeOf(posix.sockaddr)); + const rc = posix.accept4(fd, addr, &sockaddr_size, flags); + const err = posix.getErrno(rc); + switch (err) { + 0 => return @intCast(i32, rc), + posix.EINTR => continue, + else => return unexpectedErrorPosix(err), + + posix.EAGAIN => return -1, + posix.EBADF => unreachable, // always a race condition + posix.ECONNABORTED => return PosixAcceptError.ConnectionAborted, + posix.EFAULT => unreachable, + posix.EINVAL => unreachable, + posix.EMFILE => return PosixAcceptError.ProcessFdQuotaExceeded, + posix.ENFILE => return PosixAcceptError.SystemFdQuotaExceeded, + posix.ENOBUFS => return PosixAcceptError.SystemResources, + posix.ENOMEM => return PosixAcceptError.SystemResources, + posix.ENOTSOCK => return PosixAcceptError.FileDescriptorNotASocket, + posix.EOPNOTSUPP => return PosixAcceptError.OperationNotSupported, + posix.EPROTO => return PosixAcceptError.ProtocolFailure, + posix.EPERM => return PosixAcceptError.BlockedByFirewall, + } + } +} + +pub const LinuxEpollCreateError = 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, + + /// See https://github.com/ziglang/zig/issues/1396 + Unexpected, +}; + +pub fn linuxEpollCreate(flags: u32) LinuxEpollCreateError!i32 { + const rc = posix.epoll_create1(flags); + const err = posix.getErrno(rc); + switch (err) { + 0 => return @intCast(i32, rc), + else => return unexpectedErrorPosix(err), + + posix.EINVAL => unreachable, + posix.EMFILE => return LinuxEpollCreateError.ProcessFdQuotaExceeded, + posix.ENFILE => return LinuxEpollCreateError.SystemFdQuotaExceeded, + posix.ENOMEM => return LinuxEpollCreateError.SystemResources, + } +} + +pub const LinuxEpollCtlError = 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, + + /// See https://github.com/ziglang/zig/issues/1396 + Unexpected, +}; + +pub fn linuxEpollCtl(epfd: i32, op: u32, fd: i32, event: *linux.epoll_event) LinuxEpollCtlError!void { + const rc = posix.epoll_ctl(epfd, op, fd, event); + const err = posix.getErrno(rc); + switch (err) { + 0 => return, + else => return unexpectedErrorPosix(err), + + posix.EBADF => unreachable, // always a race condition if this happens + posix.EEXIST => return LinuxEpollCtlError.FileDescriptorAlreadyPresentInSet, + posix.EINVAL => unreachable, + posix.ELOOP => return LinuxEpollCtlError.OperationCausesCircularLoop, + posix.ENOENT => return LinuxEpollCtlError.FileDescriptorNotRegistered, + posix.ENOMEM => return LinuxEpollCtlError.SystemResources, + posix.ENOSPC => return LinuxEpollCtlError.UserResourceLimitReached, + posix.EPERM => return LinuxEpollCtlError.FileDescriptorIncompatibleWithEpoll, + } +} + +pub fn linuxEpollWait(epfd: i32, events: []linux.epoll_event, timeout: i32) usize { + while (true) { + const rc = posix.epoll_wait(epfd, events.ptr, @intCast(u32, events.len), timeout); + const err = posix.getErrno(rc); + switch (err) { + 0 => return rc, + posix.EINTR => continue, + posix.EBADF => unreachable, + posix.EFAULT => unreachable, + posix.EINVAL => unreachable, + else => unreachable, + } + } +} + +pub const LinuxEventFdError = error{ + InvalidFlagValue, + SystemResources, + ProcessFdQuotaExceeded, + SystemFdQuotaExceeded, + + /// See https://github.com/ziglang/zig/issues/1396 + Unexpected, +}; + +pub fn linuxEventFd(initval: u32, flags: u32) LinuxEventFdError!i32 { + const rc = posix.eventfd(initval, flags); + const err = posix.getErrno(rc); + switch (err) { + 0 => return @intCast(i32, rc), + else => return unexpectedErrorPosix(err), + + posix.EINVAL => return LinuxEventFdError.InvalidFlagValue, + posix.EMFILE => return LinuxEventFdError.ProcessFdQuotaExceeded, + posix.ENFILE => return LinuxEventFdError.SystemFdQuotaExceeded, + posix.ENODEV => return LinuxEventFdError.SystemResources, + posix.ENOMEM => return LinuxEventFdError.SystemResources, + } +} + +pub const PosixGetSockNameError = error{ + /// Insufficient resources were available in the system to perform the operation. + SystemResources, + + /// See https://github.com/ziglang/zig/issues/1396 + Unexpected, +}; + +pub fn posixGetSockName(sockfd: i32) PosixGetSockNameError!posix.sockaddr { + var addr: posix.sockaddr = undefined; + var addrlen: posix.socklen_t = @sizeOf(posix.sockaddr); + const rc = posix.getsockname(sockfd, &addr, &addrlen); + const err = posix.getErrno(rc); + switch (err) { + 0 => return addr, + else => return unexpectedErrorPosix(err), + + posix.EBADF => unreachable, + posix.EFAULT => unreachable, + posix.EINVAL => unreachable, + posix.ENOTSOCK => unreachable, + posix.ENOBUFS => return PosixGetSockNameError.SystemResources, + } +} + +pub const PosixConnectError = 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, + + /// See https://github.com/ziglang/zig/issues/1396 + Unexpected, +}; + +pub fn posixConnect(sockfd: i32, sockaddr: *const posix.sockaddr) PosixConnectError!void { + while (true) { + const rc = posix.connect(sockfd, sockaddr, @sizeOf(posix.sockaddr)); + const err = posix.getErrno(rc); + switch (err) { + 0 => return, + else => return unexpectedErrorPosix(err), + + posix.EACCES => return PosixConnectError.PermissionDenied, + posix.EPERM => return PosixConnectError.PermissionDenied, + posix.EADDRINUSE => return PosixConnectError.AddressInUse, + posix.EADDRNOTAVAIL => return PosixConnectError.AddressNotAvailable, + posix.EAFNOSUPPORT => return PosixConnectError.AddressFamilyNotSupported, + posix.EAGAIN => return PosixConnectError.SystemResources, + posix.EALREADY => unreachable, // The socket is nonblocking and a previous connection attempt has not yet been completed. + posix.EBADF => unreachable, // sockfd is not a valid open file descriptor. + posix.ECONNREFUSED => return PosixConnectError.ConnectionRefused, + posix.EFAULT => unreachable, // The socket structure address is outside the user's address space. + posix.EINPROGRESS => unreachable, // The socket is nonblocking and the connection cannot be completed immediately. + posix.EINTR => continue, + posix.EISCONN => unreachable, // The socket is already connected. + posix.ENETUNREACH => return PosixConnectError.NetworkUnreachable, + posix.ENOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket. + posix.EPROTOTYPE => unreachable, // The socket type does not support the requested communications protocol. + posix.ETIMEDOUT => return PosixConnectError.ConnectionTimedOut, + } + } +} + +/// Same as posixConnect except it is for blocking socket file descriptors. +/// It expects to receive EINPROGRESS. +pub fn posixConnectAsync(sockfd: i32, sockaddr: *const c_void, len: u32) PosixConnectError!void { + while (true) { + const rc = posix.connect(sockfd, sockaddr, len); + const err = posix.getErrno(rc); + switch (err) { + 0, posix.EINPROGRESS => return, + else => return unexpectedErrorPosix(err), + + posix.EACCES => return PosixConnectError.PermissionDenied, + posix.EPERM => return PosixConnectError.PermissionDenied, + posix.EADDRINUSE => return PosixConnectError.AddressInUse, + posix.EADDRNOTAVAIL => return PosixConnectError.AddressNotAvailable, + posix.EAFNOSUPPORT => return PosixConnectError.AddressFamilyNotSupported, + posix.EAGAIN => return PosixConnectError.SystemResources, + posix.EALREADY => unreachable, // The socket is nonblocking and a previous connection attempt has not yet been completed. + posix.EBADF => unreachable, // sockfd is not a valid open file descriptor. + posix.ECONNREFUSED => return PosixConnectError.ConnectionRefused, + posix.EFAULT => unreachable, // The socket structure address is outside the user's address space. + posix.EINTR => continue, + posix.EISCONN => unreachable, // The socket is already connected. + posix.ENETUNREACH => return PosixConnectError.NetworkUnreachable, + posix.ENOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket. + posix.EPROTOTYPE => unreachable, // The socket type does not support the requested communications protocol. + posix.ETIMEDOUT => return PosixConnectError.ConnectionTimedOut, + } + } +} + +pub fn posixGetSockOptConnectError(sockfd: i32) PosixConnectError!void { + var err_code: i32 = undefined; + var size: u32 = @sizeOf(i32); + const rc = posix.getsockopt(sockfd, posix.SOL_SOCKET, posix.SO_ERROR, @ptrCast([*]u8, &err_code), &size); + assert(size == 4); + const err = posix.getErrno(rc); + switch (err) { + 0 => switch (err_code) { + 0 => return, + else => return unexpectedErrorPosix(err), + + posix.EACCES => return PosixConnectError.PermissionDenied, + posix.EPERM => return PosixConnectError.PermissionDenied, + posix.EADDRINUSE => return PosixConnectError.AddressInUse, + posix.EADDRNOTAVAIL => return PosixConnectError.AddressNotAvailable, + posix.EAFNOSUPPORT => return PosixConnectError.AddressFamilyNotSupported, + posix.EAGAIN => return PosixConnectError.SystemResources, + posix.EALREADY => unreachable, // The socket is nonblocking and a previous connection attempt has not yet been completed. + posix.EBADF => unreachable, // sockfd is not a valid open file descriptor. + posix.ECONNREFUSED => return PosixConnectError.ConnectionRefused, + posix.EFAULT => unreachable, // The socket structure address is outside the user's address space. + posix.EISCONN => unreachable, // The socket is already connected. + posix.ENETUNREACH => return PosixConnectError.NetworkUnreachable, + posix.ENOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket. + posix.EPROTOTYPE => unreachable, // The socket type does not support the requested communications protocol. + posix.ETIMEDOUT => return PosixConnectError.ConnectionTimedOut, + }, + else => return unexpectedErrorPosix(err), + posix.EBADF => unreachable, // The argument sockfd is not a valid file descriptor. + posix.EFAULT => unreachable, // The address pointed to by optval or optlen is not in a valid part of the process address space. + posix.EINVAL => unreachable, + posix.ENOPROTOOPT => unreachable, // The option is unknown at the level indicated. + posix.ENOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket. + } +} + +pub const Thread = struct { + data: Data, + + pub const use_pthreads = is_posix and builtin.link_libc; + + /// Represents a kernel thread handle. + /// May be an integer or a pointer depending on the platform. + /// On Linux and POSIX, this is the same as Id. + pub const Handle = if (use_pthreads) + c.pthread_t + else switch (builtin.os) { + builtin.Os.linux => i32, + builtin.Os.windows => windows.HANDLE, + else => @compileError("Unsupported OS"), + }; + + /// Represents a unique ID per thread. + /// May be an integer or pointer depending on the platform. + /// On Linux and POSIX, this is the same as Handle. + pub const Id = switch (builtin.os) { + builtin.Os.windows => windows.DWORD, + else => Handle, + }; + + pub const Data = if (use_pthreads) + struct { + handle: Thread.Handle, + mmap_addr: usize, + mmap_len: usize, + } + else switch (builtin.os) { + builtin.Os.linux => struct { + handle: Thread.Handle, + mmap_addr: usize, + mmap_len: usize, + tls_end_addr: usize, + }, + builtin.Os.windows => struct { + handle: Thread.Handle, + alloc_start: *c_void, + heap_handle: windows.HANDLE, + }, + else => @compileError("Unsupported OS"), + }; + + /// Returns the ID of the calling thread. + /// Makes a syscall every time the function is called. + /// On Linux and POSIX, this Id is the same as a Handle. + pub fn getCurrentId() Id { + if (use_pthreads) { + return c.pthread_self(); + } else + return switch (builtin.os) { + builtin.Os.linux => linux.gettid(), + builtin.Os.windows => windows.GetCurrentThreadId(), + else => @compileError("Unsupported OS"), + }; + } + + /// Returns the handle of this thread. + /// On Linux and POSIX, this is the same as Id. + pub fn handle(self: Thread) Handle { + return self.data.handle; + } + + pub fn wait(self: *const Thread) void { + if (use_pthreads) { + const err = c.pthread_join(self.data.handle, null); + switch (err) { + 0 => {}, + posix.EINVAL => unreachable, + posix.ESRCH => unreachable, + posix.EDEADLK => unreachable, + else => unreachable, + } + assert(posix.munmap(self.data.mmap_addr, self.data.mmap_len) == 0); + } else switch (builtin.os) { + builtin.Os.linux => { + while (true) { + const pid_value = @atomicLoad(i32, &self.data.handle, builtin.AtomicOrder.SeqCst); + if (pid_value == 0) break; + const rc = linux.futex_wait(&self.data.handle, linux.FUTEX_WAIT, pid_value, null); + switch (linux.getErrno(rc)) { + 0 => continue, + posix.EINTR => continue, + posix.EAGAIN => continue, + else => unreachable, + } + } + assert(posix.munmap(self.data.mmap_addr, self.data.mmap_len) == 0); + }, + builtin.Os.windows => { + assert(windows.WaitForSingleObject(self.data.handle, windows.INFINITE) == windows.WAIT_OBJECT_0); + assert(windows.CloseHandle(self.data.handle) != 0); + assert(windows.HeapFree(self.data.heap_handle, 0, self.data.alloc_start) != 0); + }, + else => @compileError("Unsupported OS"), + } + } +}; + +pub const SpawnThreadError = error{ + /// A system-imposed limit on the number of threads was encountered. + /// There are a number of limits that may trigger this error: + /// * the RLIMIT_NPROC soft resource limit (set via setrlimit(2)), + /// which limits the number of processes and threads for a real + /// user ID, was reached; + /// * the kernel's system-wide limit on the number of processes and + /// threads, /proc/sys/kernel/threads-max, was reached (see + /// proc(5)); + /// * the maximum number of PIDs, /proc/sys/kernel/pid_max, was + /// reached (see proc(5)); or + /// * the PID limit (pids.max) imposed by the cgroup "process num‐ + /// ber" (PIDs) controller was reached. + ThreadQuotaExceeded, + + /// The kernel cannot allocate sufficient memory to allocate a task structure + /// for the child, or to copy those parts of the caller's context that need to + /// be copied. + SystemResources, + + /// Not enough userland memory to spawn the thread. + OutOfMemory, + + /// See https://github.com/ziglang/zig/issues/1396 + Unexpected, +}; + +pub var linux_tls_phdr: ?*std.elf.Phdr = null; +pub var linux_tls_img_src: [*]const u8 = undefined; // defined if linux_tls_phdr is + +/// caller must call wait on the returned thread +/// fn startFn(@typeOf(context)) T +/// where T is u8, noreturn, void, or !void +/// caller must call wait on the returned thread +pub fn spawnThread(context: var, comptime startFn: var) SpawnThreadError!*Thread { + if (builtin.single_threaded) @compileError("cannot spawn thread when building in single-threaded mode"); + // TODO compile-time call graph analysis to determine stack upper bound + // https://github.com/ziglang/zig/issues/157 + const default_stack_size = 8 * 1024 * 1024; + + const Context = @typeOf(context); + comptime assert(@ArgType(@typeOf(startFn), 0) == Context); + + if (builtin.os == builtin.Os.windows) { + const WinThread = struct { + const OuterContext = struct { + thread: Thread, + inner: Context, + }; + extern fn threadMain(raw_arg: windows.LPVOID) windows.DWORD { + const arg = if (@sizeOf(Context) == 0) {} else @ptrCast(*Context, @alignCast(@alignOf(Context), raw_arg)).*; + switch (@typeId(@typeOf(startFn).ReturnType)) { + builtin.TypeId.Int => { + return startFn(arg); + }, + builtin.TypeId.Void => { + startFn(arg); + return 0; + }, + else => @compileError("expected return type of startFn to be 'u8', 'noreturn', 'void', or '!void'"), + } + } + }; + + const heap_handle = windows.GetProcessHeap() orelse return SpawnThreadError.OutOfMemory; + const byte_count = @alignOf(WinThread.OuterContext) + @sizeOf(WinThread.OuterContext); + const bytes_ptr = windows.HeapAlloc(heap_handle, 0, byte_count) orelse return SpawnThreadError.OutOfMemory; + errdefer assert(windows.HeapFree(heap_handle, 0, bytes_ptr) != 0); + const bytes = @ptrCast([*]u8, bytes_ptr)[0..byte_count]; + const outer_context = std.heap.FixedBufferAllocator.init(bytes).allocator.create(WinThread.OuterContext) catch unreachable; + outer_context.* = WinThread.OuterContext{ + .thread = Thread{ + .data = Thread.Data{ + .heap_handle = heap_handle, + .alloc_start = bytes_ptr, + .handle = undefined, + }, + }, + .inner = context, + }; + + const parameter = if (@sizeOf(Context) == 0) null else @ptrCast(*c_void, &outer_context.inner); + outer_context.thread.data.handle = windows.CreateThread(null, default_stack_size, WinThread.threadMain, parameter, 0, null) orelse { + const err = windows.GetLastError(); + return switch (err) { + else => os.unexpectedErrorWindows(err), + }; + }; + return &outer_context.thread; + } + + const MainFuncs = struct { + extern fn linuxThreadMain(ctx_addr: usize) u8 { + const arg = if (@sizeOf(Context) == 0) {} else @intToPtr(*const Context, ctx_addr).*; + + switch (@typeId(@typeOf(startFn).ReturnType)) { + builtin.TypeId.Int => { + return startFn(arg); + }, + builtin.TypeId.Void => { + startFn(arg); + return 0; + }, + else => @compileError("expected return type of startFn to be 'u8', 'noreturn', 'void', or '!void'"), + } + } + extern fn posixThreadMain(ctx: ?*c_void) ?*c_void { + if (@sizeOf(Context) == 0) { + _ = startFn({}); + return null; + } else { + _ = startFn(@ptrCast(*const Context, @alignCast(@alignOf(Context), ctx)).*); + return null; + } + } + }; + + const MAP_GROWSDOWN = if (builtin.os == builtin.Os.linux) linux.MAP_GROWSDOWN else 0; + + var stack_end_offset: usize = undefined; + var thread_start_offset: usize = undefined; + var context_start_offset: usize = undefined; + var tls_start_offset: usize = undefined; + const mmap_len = blk: { + // First in memory will be the stack, which grows downwards. + var l: usize = mem.alignForward(default_stack_size, os.page_size); + stack_end_offset = l; + // Above the stack, so that it can be in the same mmap call, put the Thread object. + l = mem.alignForward(l, @alignOf(Thread)); + thread_start_offset = l; + l += @sizeOf(Thread); + // Next, the Context object. + if (@sizeOf(Context) != 0) { + l = mem.alignForward(l, @alignOf(Context)); + context_start_offset = l; + l += @sizeOf(Context); + } + // Finally, the Thread Local Storage, if any. + if (!Thread.use_pthreads) { + if (linux_tls_phdr) |tls_phdr| { + l = mem.alignForward(l, tls_phdr.p_align); + tls_start_offset = l; + l += tls_phdr.p_memsz; + } + } + break :blk l; + }; + const mmap_addr = posix.mmap(null, mmap_len, posix.PROT_READ | posix.PROT_WRITE, posix.MAP_PRIVATE | posix.MAP_ANONYMOUS | MAP_GROWSDOWN, -1, 0); + if (mmap_addr == posix.MAP_FAILED) return error.OutOfMemory; + errdefer assert(posix.munmap(mmap_addr, mmap_len) == 0); + + const thread_ptr = @alignCast(@alignOf(Thread), @intToPtr(*Thread, mmap_addr + thread_start_offset)); + thread_ptr.data.mmap_addr = mmap_addr; + thread_ptr.data.mmap_len = mmap_len; + + var arg: usize = undefined; + if (@sizeOf(Context) != 0) { + arg = mmap_addr + context_start_offset; + const context_ptr = @alignCast(@alignOf(Context), @intToPtr(*Context, arg)); + context_ptr.* = context; + } + + if (Thread.use_pthreads) { + // use pthreads + var attr: c.pthread_attr_t = undefined; + if (c.pthread_attr_init(&attr) != 0) return SpawnThreadError.SystemResources; + defer assert(c.pthread_attr_destroy(&attr) == 0); + + assert(c.pthread_attr_setstack(&attr, @intToPtr(*c_void, mmap_addr), stack_end_offset) == 0); + + const err = c.pthread_create(&thread_ptr.data.handle, &attr, MainFuncs.posixThreadMain, @intToPtr(*c_void, arg)); + switch (err) { + 0 => return thread_ptr, + posix.EAGAIN => return SpawnThreadError.SystemResources, + posix.EPERM => unreachable, + posix.EINVAL => unreachable, + else => return unexpectedErrorPosix(@intCast(usize, err)), + } + } else if (builtin.os == builtin.Os.linux) { + var flags: u32 = posix.CLONE_VM | posix.CLONE_FS | posix.CLONE_FILES | posix.CLONE_SIGHAND | + posix.CLONE_THREAD | posix.CLONE_SYSVSEM | posix.CLONE_PARENT_SETTID | posix.CLONE_CHILD_CLEARTID | + posix.CLONE_DETACHED; + var newtls: usize = undefined; + if (linux_tls_phdr) |tls_phdr| { + @memcpy(@intToPtr([*]u8, mmap_addr + tls_start_offset), linux_tls_img_src, tls_phdr.p_filesz); + thread_ptr.data.tls_end_addr = mmap_addr + mmap_len; + newtls = @ptrToInt(&thread_ptr.data.tls_end_addr); + flags |= posix.CLONE_SETTLS; + } + const rc = posix.clone(MainFuncs.linuxThreadMain, mmap_addr + stack_end_offset, flags, arg, &thread_ptr.data.handle, newtls, &thread_ptr.data.handle); + const err = posix.getErrno(rc); + switch (err) { + 0 => return thread_ptr, + posix.EAGAIN => return SpawnThreadError.ThreadQuotaExceeded, + posix.EINVAL => unreachable, + posix.ENOMEM => return SpawnThreadError.SystemResources, + posix.ENOSPC => unreachable, + posix.EPERM => unreachable, + posix.EUSERS => unreachable, + else => return unexpectedErrorPosix(err), + } + } else { + @compileError("Unsupported OS"); + } +} + +pub fn posixWait(pid: i32) i32 { + var status: i32 = undefined; + while (true) { + const err = posix.getErrno(posix.waitpid(pid, &status, 0)); + switch (err) { + 0 => return status, + posix.EINTR => continue, + posix.ECHILD => unreachable, // The process specified does not exist. It would be a race condition to handle this error. + posix.EINVAL => unreachable, // The options argument was invalid + else => unreachable, + } + } +} + +pub fn posixFStat(fd: i32) !posix.Stat { + var stat: posix.Stat = undefined; + const err = posix.getErrno(posix.fstat(fd, &stat)); + if (err > 0) { + return switch (err) { + // We do not make this an error code because if you get EBADF it's always a bug, + // since the fd could have been reused. + posix.EBADF => unreachable, + posix.ENOMEM => error.SystemResources, + else => os.unexpectedErrorPosix(err), + }; + } + + return stat; +} + +pub const CpuCountError = error{ + OutOfMemory, + PermissionDenied, + + /// See https://github.com/ziglang/zig/issues/1396 + Unexpected, +}; + +pub fn cpuCount(fallback_allocator: *mem.Allocator) CpuCountError!usize { + switch (builtin.os) { + builtin.Os.macosx, builtin.Os.freebsd, builtin.Os.netbsd => { + var count: c_int = undefined; + var count_len: usize = @sizeOf(c_int); + const rc = posix.sysctlbyname(switch (builtin.os) { + builtin.Os.macosx => c"hw.logicalcpu", + else => c"hw.ncpu", + }, @ptrCast(*c_void, &count), &count_len, null, 0); + const err = posix.getErrno(rc); + switch (err) { + 0 => return @intCast(usize, count), + posix.EFAULT => unreachable, + posix.EINVAL => unreachable, + posix.ENOMEM => return CpuCountError.OutOfMemory, + posix.ENOTDIR => unreachable, + posix.EISDIR => unreachable, + posix.ENOENT => unreachable, + posix.EPERM => unreachable, + else => return os.unexpectedErrorPosix(err), + } + }, + builtin.Os.linux => { + const usize_count = 16; + const allocator = std.heap.stackFallback(usize_count * @sizeOf(usize), fallback_allocator).get(); + + var set = try allocator.alloc(usize, usize_count); + defer allocator.free(set); + + while (true) { + const rc = posix.sched_getaffinity(0, set); + const err = posix.getErrno(rc); + switch (err) { + 0 => { + if (rc < set.len * @sizeOf(usize)) { + const result = set[0 .. rc / @sizeOf(usize)]; + var sum: usize = 0; + for (result) |x| { + sum += @popCount(x); + } + return sum; + } else { + set = try allocator.realloc(usize, set, set.len * 2); + continue; + } + }, + posix.EFAULT => unreachable, + posix.EINVAL => unreachable, + posix.EPERM => return CpuCountError.PermissionDenied, + posix.ESRCH => unreachable, + else => return os.unexpectedErrorPosix(err), + } + } + }, + builtin.Os.windows => { + var system_info: windows.SYSTEM_INFO = undefined; + windows.GetSystemInfo(&system_info); + return @intCast(usize, system_info.dwNumberOfProcessors); + }, + else => @compileError("unsupported OS"), + } +} + +pub const BsdKQueueError = 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, + + /// See https://github.com/ziglang/zig/issues/1396 + Unexpected, +}; + +pub fn bsdKQueue() BsdKQueueError!i32 { + const rc = posix.kqueue(); + const err = posix.getErrno(rc); + switch (err) { + 0 => return @intCast(i32, rc), + posix.EMFILE => return BsdKQueueError.ProcessFdQuotaExceeded, + posix.ENFILE => return BsdKQueueError.SystemFdQuotaExceeded, + else => return unexpectedErrorPosix(err), + } +} + +pub const BsdKEventError = 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, +}; + +pub fn bsdKEvent( + kq: i32, + changelist: []const posix.Kevent, + eventlist: []posix.Kevent, + timeout: ?*const posix.timespec, +) BsdKEventError!usize { + while (true) { + const rc = posix.kevent(kq, changelist, eventlist, timeout); + const err = posix.getErrno(rc); + switch (err) { + 0 => return rc, + posix.EACCES => return BsdKEventError.AccessDenied, + posix.EFAULT => unreachable, + posix.EBADF => unreachable, + posix.EINTR => continue, + posix.EINVAL => unreachable, + posix.ENOENT => return BsdKEventError.EventNotFound, + posix.ENOMEM => return BsdKEventError.SystemResources, + posix.ESRCH => return BsdKEventError.ProcessNotFound, + else => unreachable, + } + } +} + +pub fn linuxINotifyInit1(flags: u32) !i32 { + const rc = linux.inotify_init1(flags); + const err = posix.getErrno(rc); + switch (err) { + 0 => return @intCast(i32, rc), + posix.EINVAL => unreachable, + posix.EMFILE => return error.ProcessFdQuotaExceeded, + posix.ENFILE => return error.SystemFdQuotaExceeded, + posix.ENOMEM => return error.SystemResources, + else => return unexpectedErrorPosix(err), + } +} + +pub fn linuxINotifyAddWatchC(inotify_fd: i32, pathname: [*]const u8, mask: u32) !i32 { + const rc = linux.inotify_add_watch(inotify_fd, pathname, mask); + const err = posix.getErrno(rc); + switch (err) { + 0 => return @intCast(i32, rc), + posix.EACCES => return error.AccessDenied, + posix.EBADF => unreachable, + posix.EFAULT => unreachable, + posix.EINVAL => unreachable, + posix.ENAMETOOLONG => return error.NameTooLong, + posix.ENOENT => return error.FileNotFound, + posix.ENOMEM => return error.SystemResources, + posix.ENOSPC => return error.UserResourceLimitReached, + else => return unexpectedErrorPosix(err), + } +} + +pub fn linuxINotifyRmWatch(inotify_fd: i32, wd: i32) !void { + const rc = linux.inotify_rm_watch(inotify_fd, wd); + const err = posix.getErrno(rc); + switch (err) { + 0 => return rc, + posix.EBADF => unreachable, + posix.EINVAL => unreachable, + else => unreachable, + } +} + +pub const MProtectError = error{ + AccessDenied, + OutOfMemory, + Unexpected, +}; + +/// address and length must be page-aligned +pub fn posixMProtect(address: usize, length: usize, protection: u32) MProtectError!void { + const negative_page_size = @bitCast(usize, -isize(page_size)); + const aligned_address = address & negative_page_size; + const aligned_end = (address + length + page_size - 1) & negative_page_size; + assert(address == aligned_address); + assert(length == aligned_end - aligned_address); + const rc = posix.mprotect(address, length, protection); + const err = posix.getErrno(rc); + switch (err) { + 0 => return, + posix.EINVAL => unreachable, + posix.EACCES => return error.AccessDenied, + posix.ENOMEM => return error.OutOfMemory, + else => return unexpectedErrorPosix(err), + } +} |
