zig/std/os/posix.zig
2019-05-26 18:32:40 -04:00

2160 lines
79 KiB
Zig
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// This is the "Zig-flavored POSIX" API layer.
// The purpose is not to match POSIX as closely as possible. Instead,
// the goal is to provide a very specific layer of abstraction:
// * Implement the POSIX functions, types, and definitions where possible,
// using lower-level target-specific API. For example, on Linux `rename` might call
// SYS_renameat or SYS_rename depending on the architecture.
// * When null-terminated byte buffers are required, provide APIs which accept
// slices as well as APIs which accept null-terminated byte buffers. Same goes
// for UTF-16LE encoding.
// * Convert "errno"-style error codes into Zig errors.
// * Work around kernel bugs and limitations. For example, if a function accepts
// a `usize` number of bytes to write, but the kernel can only handle maxInt(u32)
// number of bytes, this API layer should introduce a loop to make multiple
// syscalls so that the full `usize` number of bytes are written.
// * Implement the OS-specific functions, types, and definitions that the Zig
// standard library needs, at the same API abstraction layer as outlined above.
// this includes, for example Windows functions.
// * When there exists a corresponding libc function and linking libc, call the
// libc function.
// Note: The Zig standard library does not support POSIX thread cancellation, and
// in general EINTR is handled by trying again.
const std = @import("../std.zig");
const builtin = @import("builtin");
const assert = std.debug.assert;
const os = @import("../os.zig");
const system = os.system;
const mem = std.mem;
const BufMap = std.BufMap;
const Allocator = mem.Allocator;
const windows = os.windows;
const wasi = os.wasi;
const linux = os.linux;
const testing = std.testing;
pub const FileHandle = if (windows.is_the_target) windows.HANDLE else if (wasi.is_the_target) wasi.fd_t else i32;
pub use system.errno_codes;
pub const PATH_MAX = system.PATH_MAX;
/// > The maximum path of 32,767 characters is approximate, because the "\\?\"
/// > prefix may be expanded to a longer string by the system at run time, and
/// > this expansion applies to the total length.
/// from https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file#maximum-path-length-limitation
pub const PATH_MAX_WIDE = 32767;
pub const iovec = system.iovec;
pub const iovec_const = system.iovec_const;
/// See also `getenv`.
pub var environ: [][*]u8 = undefined;
/// To obtain errno, call this function with the return value of the
/// system function call. For some systems this will obtain the value directly
/// from the return code; for others it will use a thread-local errno variable.
/// Therefore, this function only returns a well-defined value when it is called
/// directly after the system function call which one wants to learn the errno
/// value of.
pub const errno = system.getErrno;
/// Closes the file handle.
/// This function is not capable of returning any indication of failure. An
/// application which wants to ensure writes have succeeded before closing
/// must call `fsync` before `close`.
/// Note: The Zig standard library does not support POSIX thread cancellation.
pub fn close(handle: FileHandle) void {
if (windows.is_the_target and !builtin.link_libc) {
assert(windows.CloseHandle(handle) != 0);
return;
}
if (wasi.is_the_target) {
switch (wasi.fd_close(handle)) {
0 => return,
else => |err| return unexpectedErrno(err),
}
}
switch (system.getErrno(system.close(handle))) {
EBADF => unreachable, // Always a race condition.
EINTR => return, // This is still a success. See https://github.com/ziglang/zig/issues/2425
else => return,
}
}
pub const GetRandomError = error{};
/// Obtain a series of random bytes. These bytes can be used to seed user-space
/// random number generators or for cryptographic purposes.
/// When linking against libc, this calls the
/// appropriate OS-specific library call. Otherwise it uses the zig standard
/// library implementation.
pub fn getrandom(buf: []u8) GetRandomError!void {
if (windows.is_the_target) {
// 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),
};
}
return;
}
if (linux.is_the_target) {
while (true) {
switch (system.getErrno(system.getrandom(buf.ptr, buf.len, 0))) {
0 => return,
EINVAL => unreachable,
EFAULT => unreachable,
EINTR => continue,
ENOSYS => return getRandomBytesDevURandom(buf),
else => |err| return unexpectedErrno(err),
}
}
}
if (wasi.is_the_target) {
switch (os.wasi.random_get(buf.ptr, buf.len)) {
0 => return,
else => |err| return unexpectedErrno(err),
}
}
return getRandomBytesDevURandom(buf);
}
fn getRandomBytesDevURandom(buf: []u8) !void {
const fd = try openC(c"/dev/urandom", O_RDONLY | O_CLOEXEC, 0);
defer close(fd);
const stream = &os.File.openHandle(fd).inStream().stream;
stream.readNoEof(buf) catch return error.Unexpected;
}
test "os.getRandomBytes" {
var buf_a: [50]u8 = undefined;
var buf_b: [50]u8 = undefined;
try getRandomBytes(&buf_a);
try getRandomBytes(&buf_b);
// If this test fails the chance is significantly higher that there is a bug than
// that two sets of 50 bytes were equal.
testing.expect(!mem.eql(u8, buf_a, buf_b));
}
/// Causes abnormal process termination.
/// If linking against libc, this calls the abort() libc function. Otherwise
/// it raises SIGABRT followed by SIGKILL and finally lo
pub fn abort() noreturn {
@setCold(true);
if (builtin.link_libc) {
c.abort();
}
if (windows.is_the_target) {
if (builtin.mode == .Debug) {
@breakpoint();
}
windows.ExitProcess(3);
}
if (builtin.os == .uefi) {
// TODO there must be a better thing to do here than loop forever
while (true) {}
}
raise(SIGABRT);
// TODO the rest of the implementation of abort() from musl libc here
raise(SIGKILL);
exit(127);
}
pub const RaiseError = error{};
pub fn raise(sig: u8) RaiseError!void {
if (builtin.link_libc) {
switch (system.getErrno(system.raise(sig))) {
0 => return,
else => |err| return unexpectedErrno(err),
}
}
if (wasi.is_the_target) {
switch (wasi.proc_raise(SIGABRT)) {
0 => return,
else => |err| return unexpectedErrno(err),
}
}
if (windows.is_the_target) {
@compileError("TODO implement std.posix.raise for Windows");
}
var set: system.sigset_t = undefined;
system.blockAppSignals(&set);
const tid = system.syscall0(system.SYS_gettid);
const rc = system.syscall2(system.SYS_tkill, tid, sig);
system.restoreSignals(&set);
switch (system.getErrno(rc)) {
0 => return,
else => |err| return unexpectedErrno(err),
}
}
/// Exits the program cleanly with the specified status code.
pub fn exit(status: u8) noreturn {
if (builtin.link_libc) {
std.c.exit(status);
}
if (windows.is_the_target) {
windows.ExitProcess(status);
}
if (wasi.is_the_target) {
wasi.proc_exit(status);
}
if (linux.is_the_target and !builtin.single_threaded) {
linux.exit_group(status);
}
system.exit(status);
}
pub const ReadError = error{
InputOutput,
SystemResources,
IsDir,
OperationAborted,
BrokenPipe,
Unexpected,
};
/// Returns the number of bytes that were read, which can be less than
/// buf.len. If 0 bytes were read, that means EOF.
/// This function is for blocking file descriptors only. For non-blocking, see
/// `readAsync`.
pub fn read(fd: FileHandle, buf: []u8) ReadError!usize {
if (windows.is_the_target and !builtin.link_libc) {
var index: usize = 0;
while (index < buffer.len) {
const want_read_count = @intCast(windows.DWORD, math.min(windows.DWORD(math.maxInt(windows.DWORD)), buffer.len - index));
var amt_read: windows.DWORD = undefined;
if (windows.ReadFile(fd, buffer.ptr + index, want_read_count, &amt_read, null) == 0) {
const err = windows.GetLastError();
return switch (err) {
windows.ERROR.OPERATION_ABORTED => continue,
windows.ERROR.BROKEN_PIPE => return index,
else => unexpectedErrorWindows(err),
};
}
if (amt_read == 0) return index;
index += amt_read;
}
return index;
}
if (wasi.is_the_target and !builtin.link_libc) {
const iovs = [1]was.iovec_t{wasi.iovec_t{
.buf = buf.ptr,
.buf_len = buf.len,
}};
var nread: usize = undefined;
switch (fd_read(fd, &iovs, iovs.len, &nread)) {
0 => return nread,
else => |err| return unexpectedErrno(err),
}
}
// 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 = system.read(fd, buf.ptr + index, want_to_read);
switch (system.getErrno(rc)) {
0 => {
index += rc;
if (rc == want_to_read) continue;
// Read returned less than buf.len.
return index;
},
EINTR => continue,
EINVAL => unreachable,
EFAULT => unreachable,
EAGAIN => unreachable, // This function is for blocking reads.
EBADF => unreachable, // Always a race condition.
EIO => return error.InputOutput,
EISDIR => return error.IsDir,
ENOBUFS => return error.SystemResources,
ENOMEM => return error.SystemResources,
else => |err| return unexpectedErrno(err),
}
}
return index;
}
/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.
/// This function is for blocking file descriptors only. For non-blocking, see
/// `preadvAsync`.
pub fn preadv(fd: FileHandle, iov: [*]const iovec, count: usize, offset: u64) ReadError!usize {
if (os.darwin.is_the_target) {
// Darwin does not have preadv but it does have pread.
var off: usize = 0;
var iov_i: usize = 0;
var inner_off: usize = 0;
while (true) {
const v = iov[iov_i];
const rc = darwin.pread(fd, v.iov_base + inner_off, v.iov_len - inner_off, offset + off);
const err = darwin.getErrno(rc);
switch (err) {
0 => {
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;
},
EINTR => continue,
EINVAL => unreachable,
EFAULT => unreachable,
ESPIPE => unreachable, // fd is not seekable
EAGAIN => unreachable, // This function is for blocking reads.
EBADF => unreachable, // always a race condition
EIO => return error.InputOutput,
EISDIR => return error.IsDir,
ENOBUFS => return error.SystemResources,
ENOMEM => return error.SystemResources,
else => return unexpectedErrno(err),
}
}
}
while (true) {
const rc = system.preadv(fd, iov, count, offset);
const err = system.getErrno(rc);
switch (err) {
0 => return rc,
EINTR => continue,
EINVAL => unreachable,
EFAULT => unreachable,
EAGAIN => unreachable, // This function is for blocking reads.
EBADF => unreachable, // always a race condition
EIO => return error.InputOutput,
EISDIR => return error.IsDir,
ENOBUFS => return error.SystemResources,
ENOMEM => return error.SystemResources,
else => return unexpectedErrno(err),
}
}
}
pub const WriteError = error{
DiskQuota,
FileTooBig,
InputOutput,
NoSpaceLeft,
AccessDenied,
BrokenPipe,
SystemResources,
OperationAborted,
Unexpected,
};
/// Write to a file descriptor. Keeps trying if it gets interrupted.
/// This function is for blocking file descriptors only. For non-blocking, see
/// `writeAsync`.
pub fn write(fd: FileHandle, bytes: []const u8) WriteError!void {
if (windows.is_the_target and !builtin.link_libc) {
var bytes_written: windows.DWORD = undefined;
// TODO replace this @intCast with a loop that writes all the bytes
if (windows.WriteFile(handle, bytes.ptr, @intCast(u32, bytes.len), &bytes_written, null) == 0) {
switch (windows.GetLastError()) {
windows.ERROR.INVALID_USER_BUFFER => return error.SystemResources,
windows.ERROR.NOT_ENOUGH_MEMORY => return error.SystemResources,
windows.ERROR.OPERATION_ABORTED => return error.OperationAborted,
windows.ERROR.NOT_ENOUGH_QUOTA => return error.SystemResources,
windows.ERROR.IO_PENDING => unreachable,
windows.ERROR.BROKEN_PIPE => return error.BrokenPipe,
else => |err| return unexpectedErrorWindows(err),
}
}
}
if (wasi.is_the_target and !builtin.link_libc) {
const ciovs = [1]wasi.ciovec_t{wasi.ciovec_t{
.buf = bytes.ptr,
.buf_len = bytes.len,
}};
var nwritten: usize = undefined;
switch (fd_write(fd, &ciovs, ciovs.len, &nwritten)) {
0 => return,
else => |err| return unexpectedErrno(err),
}
}
// Linux can return EINVAL when write amount is > 0x7ffff000
// See https://github.com/ziglang/zig/pull/743#issuecomment-363165856
const max_bytes_len = 0x7ffff000;
var index: usize = 0;
while (index < bytes.len) {
const amt_to_write = math.min(bytes.len - index, usize(max_bytes_len));
const rc = system.write(fd, bytes.ptr + index, amt_to_write);
const write_err = system.getErrno(rc);
switch (write_err) {
0 => {
index += rc;
continue;
},
EINTR => continue,
EINVAL => unreachable,
EFAULT => unreachable,
EAGAIN => unreachable, // This function is for blocking writes.
EBADF => unreachable, // Always a race condition.
EDESTADDRREQ => unreachable, // `connect` was never called.
EDQUOT => return error.DiskQuota,
EFBIG => return error.FileTooBig,
EIO => return error.InputOutput,
ENOSPC => return error.NoSpaceLeft,
EPERM => return error.AccessDenied,
EPIPE => return error.BrokenPipe,
else => return unexpectedErrno(write_err),
}
}
}
/// Write multiple buffers to a file descriptor. Keeps trying if it gets interrupted.
/// This function is for blocking file descriptors only. For non-blocking, see
/// `pwritevAsync`.
pub fn pwritev(fd: FileHandle, iov: [*]const iovec_const, count: usize, offset: u64) WriteError!void {
if (darwin.is_the_target) {
// Darwin does not have pwritev but it does have pwrite.
var off: usize = 0;
var iov_i: usize = 0;
var inner_off: usize = 0;
while (true) {
const v = iov[iov_i];
const rc = darwin.pwrite(fd, v.iov_base + inner_off, v.iov_len - inner_off, offset + off);
const err = darwin.getErrno(rc);
switch (err) {
0 => {
off += rc;
inner_off += rc;
if (inner_off == v.iov_len) {
iov_i += 1;
inner_off = 0;
if (iov_i == count) {
return;
}
}
continue;
},
EINTR => continue,
ESPIPE => unreachable, // `fd` is not seekable.
EINVAL => unreachable,
EFAULT => unreachable,
EAGAIN => unreachable, // This function is for blocking writes.
EBADF => unreachable, // Always a race condition.
EDESTADDRREQ => unreachable, // `connect` was never called.
EDQUOT => return error.DiskQuota,
EFBIG => return error.FileTooBig,
EIO => return error.InputOutput,
ENOSPC => return error.NoSpaceLeft,
EPERM => return error.AccessDenied,
EPIPE => return error.BrokenPipe,
else => return unexpectedErrno(err),
}
}
}
while (true) {
const rc = system.pwritev(fd, iov, count, offset);
const err = system.getErrno(rc);
switch (err) {
0 => return,
EINTR => continue,
EINVAL => unreachable,
EFAULT => unreachable,
EAGAIN => unreachable, // This function is for blocking writes.
EBADF => unreachable, // Always a race condition.
EDESTADDRREQ => unreachable, // `connect` was never called.
EDQUOT => return error.DiskQuota,
EFBIG => return error.FileTooBig,
EIO => return error.InputOutput,
ENOSPC => return error.NoSpaceLeft,
EPERM => return error.AccessDenied,
EPIPE => return error.BrokenPipe,
else => return unexpectedErrno(err),
}
}
}
pub const OpenError = error{
AccessDenied,
FileTooBig,
IsDir,
SymLinkLoop,
ProcessFdQuotaExceeded,
NameTooLong,
SystemFdQuotaExceeded,
NoDevice,
FileNotFound,
SystemResources,
NoSpaceLeft,
NotDir,
PathAlreadyExists,
DeviceBusy,
Unexpected,
};
/// Open and possibly create a file. Keeps trying if it gets interrupted.
/// `file_path` needs to be copied in memory to add a null terminating byte.
/// See also `openC`.
pub fn open(file_path: []const u8, flags: u32, perm: usize) OpenError!FileHandle {
const file_path_c = try toPosixPath(file_path);
return openC(&file_path_c, flags, perm);
}
/// Open and possibly create a file. Keeps trying if it gets interrupted.
/// See also `open`.
/// TODO https://github.com/ziglang/zig/issues/265
pub fn openC(file_path: [*]const u8, flags: u32, perm: usize) OpenError!FileHandle {
while (true) {
const rc = system.open(file_path, flags, perm);
switch (system.getErrno(rc)) {
0 => return @intCast(FileHandle, rc),
EINTR => continue,
EFAULT => unreachable,
EINVAL => unreachable,
EACCES => return error.AccessDenied,
EFBIG => return error.FileTooBig,
EOVERFLOW => return error.FileTooBig,
EISDIR => return error.IsDir,
ELOOP => return error.SymLinkLoop,
EMFILE => return error.ProcessFdQuotaExceeded,
ENAMETOOLONG => return error.NameTooLong,
ENFILE => return error.SystemFdQuotaExceeded,
ENODEV => return error.NoDevice,
ENOENT => return error.FileNotFound,
ENOMEM => return error.SystemResources,
ENOSPC => return error.NoSpaceLeft,
ENOTDIR => return error.NotDir,
EPERM => return error.AccessDenied,
EEXIST => return error.PathAlreadyExists,
EBUSY => return error.DeviceBusy,
else => |err| return unexpectedErrno(err),
}
}
}
pub const WindowsOpenError = error{
SharingViolation,
PathAlreadyExists,
/// When any of the path components can not be found or the file component can not
/// be found. Some operating systems distinguish between path components not found and
/// file components not found, but they are collapsed into FileNotFound to gain
/// consistency across operating systems.
FileNotFound,
AccessDenied,
PipeBusy,
NameTooLong,
/// On Windows, file paths must be valid Unicode.
InvalidUtf8,
/// On Windows, file paths cannot contain these characters:
/// '/', '*', '?', '"', '<', '>', '|'
BadPathName,
Unexpected,
};
pub fn openWindows(
file_path: []const u8,
desired_access: windows.DWORD,
share_mode: windows.DWORD,
creation_disposition: windows.DWORD,
flags_and_attrs: windows.DWORD,
) WindowsOpenError!FileHandle {
const file_path_w = try sliceToPrefixedFileW(file_path);
return openW(&file_path_w, desired_access, share_mode, creation_disposition, flags_and_attrs);
}
pub fn openW(
file_path_w: [*]const u16,
desired_access: windows.DWORD,
share_mode: windows.DWORD,
creation_disposition: windows.DWORD,
flags_and_attrs: windows.DWORD,
) WindowsOpenError!windows.HANDLE {
const result = windows.CreateFileW(file_path_w, desired_access, share_mode, null, creation_disposition, flags_and_attrs, null);
if (result == windows.INVALID_HANDLE_VALUE) {
const err = windows.GetLastError();
switch (err) {
windows.ERROR.SHARING_VIOLATION => return error.SharingViolation,
windows.ERROR.ALREADY_EXISTS => return error.PathAlreadyExists,
windows.ERROR.FILE_EXISTS => return error.PathAlreadyExists,
windows.ERROR.FILE_NOT_FOUND => return error.FileNotFound,
windows.ERROR.PATH_NOT_FOUND => return error.FileNotFound,
windows.ERROR.ACCESS_DENIED => return error.AccessDenied,
windows.ERROR.PIPE_BUSY => return error.PipeBusy,
else => return unexpectedErrorWindows(err),
}
}
return result;
}
pub fn dup2(old_fd: FileHandle, new_fd: FileHandle) !void {
while (true) {
switch (system.getErrno(system.dup2(old_fd, new_fd))) {
0 => return,
EBUSY, EINTR => continue,
EMFILE => return error.ProcessFdQuotaExceeded,
EINVAL => unreachable,
else => |err| return unexpectedErrno(err),
}
}
}
/// This function must allocate memory to add a null terminating bytes on path and each arg.
/// It must also convert to KEY=VALUE\0 format for environment variables, and include null
/// pointers after the args and after the environment variables.
/// `argv[0]` is the executable path.
/// This function also uses the PATH environment variable to get the full path to the executable.
pub fn execve(allocator: *Allocator, argv: []const []const u8, env_map: *const BufMap) !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 execveErrnoToErr(system.getErrno(system.execve(argv_buf[0].?, argv_buf.ptr, envp_buf.ptr)));
}
const PATH = getenv("PATH") orelse "/usr/local/bin:/bin/:/usr/bin";
// PATH.len because it is >= the largest search_path
// +1 for the / to join the search path and exe_path
// +1 for the null terminating byte
const path_buf = try allocator.alloc(u8, PATH.len + exe_path.len + 2);
defer allocator.free(path_buf);
var it = mem.tokenize(PATH, ":");
var seen_eacces = false;
var err: usize = undefined;
while (it.next()) |search_path| {
mem.copy(u8, path_buf, search_path);
path_buf[search_path.len] = '/';
mem.copy(u8, path_buf[search_path.len + 1 ..], exe_path);
path_buf[search_path.len + exe_path.len + 1] = 0;
err = system.getErrno(system.execve(path_buf.ptr, argv_buf.ptr, envp_buf.ptr));
assert(err > 0);
if (err == EACCES) {
seen_eacces = true;
} else if (err != ENOENT) {
return execveErrnoToErr(err);
}
}
if (seen_eacces) {
err = EACCES;
}
return execveErrnoToErr(err);
}
pub fn createNullDelimitedEnvMap(allocator: *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);
}
pub const ExecveError = error{
SystemResources,
AccessDenied,
InvalidExe,
FileSystem,
IsDir,
FileNotFound,
NotDir,
FileBusy,
Unexpected,
};
fn execveErrnoToErr(err: usize) ExecveError {
assert(err > 0);
switch (err) {
EFAULT => unreachable,
E2BIG => return error.SystemResources,
EMFILE => return error.ProcessFdQuotaExceeded,
ENAMETOOLONG => return error.NameTooLong,
ENFILE => return error.SystemFdQuotaExceeded,
ENOMEM => return error.SystemResources,
EACCES => return error.AccessDenied,
EPERM => return error.AccessDenied,
EINVAL => return error.InvalidExe,
ENOEXEC => return error.InvalidExe,
EIO => return error.FileSystem,
ELOOP => return error.FileSystem,
EISDIR => return error.IsDir,
ENOENT => return error.FileNotFound,
ENOTDIR => return error.NotDir,
ETXTBSY => return error.FileBusy,
else => return unexpectedErrno(err),
}
}
/// Get an environment variable.
/// See also `getenvC`.
/// TODO make this go through libc when we have it
pub fn getenv(key: []const u8) ?[]const u8 {
for (environ) |ptr| {
var line_i: usize = 0;
while (ptr[line_i] != 0 and ptr[line_i] != '=') : (line_i += 1) {}
const this_key = ptr[0..line_i];
if (!mem.eql(u8, key, this_key)) continue;
var end_i: usize = line_i;
while (ptr[end_i] != 0) : (end_i += 1) {}
const this_value = ptr[line_i + 1 .. end_i];
return this_value;
}
return null;
}
/// Get an environment variable with a null-terminated name.
/// See also `getenv`.
/// TODO https://github.com/ziglang/zig/issues/265
pub fn getenvC(key: [*]const u8) ?[]const u8 {
if (builtin.link_libc) {
const value = std.c.getenv(key) orelse return null;
return mem.toSliceConst(u8, value);
}
return getenv(mem.toSliceConst(u8, key));
}
/// See std.elf for the constants.
pub fn getauxval(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 const GetCwdError = error{
NameTooLong,
CurrentWorkingDirectoryUnlinked,
Unexpected,
};
/// The result is a slice of out_buffer, indexed from 0.
pub fn getcwd(out_buffer: []u8) GetCwdError![]u8 {
if (windows.is_the_target and !builtin.link_libc) {
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.
var end_index: usize = 0;
var it = std.unicode.Utf16LeIterator.init(utf16le);
while (it.nextCodepoint() catch unreachable) |codepoint| {
if (end_index + std.unicode.utf8CodepointSequenceLength(codepoint) >= out_buffer.len)
return error.NameTooLong;
end_index += utf8Encode(codepoint, out_buffer[end_index..]) catch unreachable;
}
return out_buffer[0..end_index];
}
const err = if (builtin.link_libc) blk: {
break :blk if (std.c.getcwd(out_buffer.ptr, out_buffer.len)) |_| 0 else std.c._errno().*;
} else blk: {
break :blk system.getErrno(system.getcwd(out_buffer, out_buffer.len));
};
switch (err) {
0 => return mem.toSlice(u8, out_buffer),
EFAULT => unreachable,
EINVAL => unreachable,
ENOENT => return error.CurrentWorkingDirectoryUnlinked,
ERANGE => return error.NameTooLong,
else => |err| return unexpectedErrno(err),
}
}
test "getcwd" {
// at least call it so it gets compiled
var buf: [os.MAX_PATH_BYTES]u8 = undefined;
_ = getcwd(&buf) catch {};
}
pub const SymLinkError = error{
AccessDenied,
DiskQuota,
PathAlreadyExists,
FileSystem,
SymLinkLoop,
FileNotFound,
SystemResources,
NoSpaceLeft,
ReadOnlyFileSystem,
NotDir,
NameTooLong,
InvalidUtf8,
BadPathName,
Unexpected,
};
/// Creates a symbolic link named `new_path` which contains the string `target_path`.
/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
/// one; the latter case is known as a dangling link.
/// If `new_path` exists, it will not be overwritten.
/// See also `symlinkC` and `symlinkW`.
pub fn symlink(target_path: []const u8, new_path: []const u8) SymLinkError!void {
if (windows.is_the_target and !builtin.link_libc) {
const target_path_w = try cStrToPrefixedFileW(target_path);
const new_path_w = try cStrToPrefixedFileW(new_path);
return symlinkW(&target_path_w, &new_path_w);
} else {
const target_path_c = try toPosixPath(target_path);
const new_path_c = try toPosixPath(new_path);
return symlinkC(&target_path_c, &new_path_c);
}
}
pub fn symlinkat(target_path: []const u8, newdirfd: FileHandle, new_path: []const u8) SymLinkError!void {
const target_path_c = try toPosixPath(target_path);
const new_path_c = try toPosixPath(new_path);
return symlinkatC(target_path_c, newdirfd, new_path_c);
}
pub fn symlinkatC(target_path: [*]const u8, newdirfd: FileHandle, new_path: [*]const u8) SymLinkError!void {
const err = blk: {
if (builtin.link_libc) {
break :blk if (std.c.symlinkat(target_path, newdirfd, new_path) == -1) errno().* else 0;
} else {
break :blk system.getErrno(system.symlinkat(target_path, newdirfd, new_path));
}
};
switch (err) {
0 => return,
EFAULT => unreachable,
EINVAL => unreachable,
EACCES => return error.AccessDenied,
EPERM => return error.AccessDenied,
EDQUOT => return error.DiskQuota,
EEXIST => return error.PathAlreadyExists,
EIO => return error.FileSystem,
ELOOP => return error.SymLinkLoop,
ENAMETOOLONG => return error.NameTooLong,
ENOENT => return error.FileNotFound,
ENOTDIR => return error.NotDir,
ENOMEM => return error.SystemResources,
ENOSPC => return error.NoSpaceLeft,
EROFS => return error.ReadOnlyFileSystem,
else => return unexpectedErrno(err),
}
}
/// This is the same as `symlink` except the parameters are null-terminated pointers.
/// See also `symlink` and `symlinkW`.
pub fn symlinkC(target_path: [*]const u8, new_path: [*]const u8) SymLinkError!void {
if (windows.is_the_target and !builtin.link_libc) {
const target_path_w = try cStrToPrefixedFileW(target_path);
const new_path_w = try cStrToPrefixedFileW(new_path);
return symlinkW(&target_path_w, &new_path_w);
}
const err = if (builtin.link_libc) blk: {
break :blk if (std.c.symlink(target_path, new_path) == -1) errno().* else 0;
} else if (@hasDecl(system, "symlink")) blk: {
break :blk system.getErrno(system.symlink(target_path, new_path));
} else blk: {
break :blk system.getErrno(system.symlinkat(target_path, AT_FDCWD, new_path));
};
switch (err) {
0 => return,
EFAULT => unreachable,
EINVAL => unreachable,
EACCES => return error.AccessDenied,
EPERM => return error.AccessDenied,
EDQUOT => return error.DiskQuota,
EEXIST => return error.PathAlreadyExists,
EIO => return error.FileSystem,
ELOOP => return error.SymLinkLoop,
ENAMETOOLONG => return error.NameTooLong,
ENOENT => return error.FileNotFound,
ENOTDIR => return error.NotDir,
ENOMEM => return error.SystemResources,
ENOSPC => return error.NoSpaceLeft,
EROFS => return error.ReadOnlyFileSystem,
else => return unexpectedErrno(err),
}
}
/// This is the same as `symlink` except the parameters are null-terminated pointers to
/// UTF-16LE encoded strings.
/// See also `symlink` and `symlinkC`.
/// TODO handle when linking libc
pub fn symlinkW(target_path_w: [*]const u16, new_path_w: [*]const u16) SymLinkError!void {
if (windows.CreateSymbolicLinkW(target_path_w, new_path_w, 0) == 0) {
const err = windows.GetLastError();
switch (err) {
else => return unexpectedErrorWindows(err),
}
}
}
pub const UnlinkError = error{
FileNotFound,
AccessDenied,
FileBusy,
FileSystem,
IsDir,
SymLinkLoop,
NameTooLong,
NotDir,
SystemResources,
ReadOnlyFileSystem,
Unexpected,
/// On Windows, file paths must be valid Unicode.
InvalidUtf8,
/// On Windows, file paths cannot contain these characters:
/// '/', '*', '?', '"', '<', '>', '|'
BadPathName,
};
/// Delete a name and possibly the file it refers to.
pub fn unlink(file_path: []const u8) UnlinkError!void {
if (windows.is_the_target and !builtin.link_libc) {
const file_path_w = try sliceToPrefixedFileW(file_path);
return unlinkW(&file_path_w);
} else {
const file_path_c = try toPosixPath(file_path);
return unlinkC(&file_path_c);
}
}
/// Same as `unlink` except the parameter is a UTF16LE-encoded string.
/// TODO handle when linking libc
pub fn unlinkW(file_path: [*]const u16) UnlinkError!void {
if (windows.unlinkW(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),
}
}
}
/// Same as `unlink` except the parameter is a null terminated UTF8-encoded string.
pub fn unlinkC(file_path: [*]const u8) UnlinkError!void {
if (windows.is_the_target and !builtin.link_libc) {
const file_path_w = try cStrToPrefixedFileW(file_path);
return unlinkW(&file_path_w);
}
const err = if (builtin.link_libc) blk: {
break :blk if (std.c.unlink(file_path) == -1) errno().* else 0;
} else if (@hasDecl(system, "unlink")) blk: {
break :blk system.getErrno(system.unlink(file_path));
} else blk: {
break :blk system.getErrno(system.unlinkat(AT_FDCWD, file_path, 0));
};
switch (err) {
0 => return,
EACCES => return error.AccessDenied,
EPERM => return error.AccessDenied,
EBUSY => return error.FileBusy,
EFAULT => unreachable,
EINVAL => unreachable,
EIO => return error.FileSystem,
EISDIR => return error.IsDir,
ELOOP => return error.SymLinkLoop,
ENAMETOOLONG => return error.NameTooLong,
ENOENT => return error.FileNotFound,
ENOTDIR => return error.NotDir,
ENOMEM => return error.SystemResources,
EROFS => return error.ReadOnlyFileSystem,
else => return unexpectedErrno(err),
}
}
const RenameError = error{}; // TODO
/// Change the name or location of a file.
pub fn rename(old_path: []const u8, new_path: []const u8) RenameError!void {
if (windows.is_the_target and !builtin.link_libc) {
const old_path_w = try sliceToPrefixedFileW(old_path);
const new_path_w = try sliceToPrefixedFileW(new_path);
return renameW(&old_path_w, &new_path_w);
} else {
const old_path_c = try toPosixPath(old_path);
const new_path_c = try toPosixPath(new_path);
return renameC(&old_path_c, &new_path_c);
}
}
/// Same as `rename` except the parameters are null-terminated byte arrays.
pub fn renameC(old_path: [*]const u8, new_path: [*]const u8) RenameError!void {
if (windows.is_the_target and !builtin.link_libc) {
const old_path_w = try cStrToPrefixedFileW(old_path);
const new_path_w = try cStrToPrefixedFileW(new_path);
return renameW(&old_path_w, &new_path_w);
}
const err = if (builtin.link_libc) blk: {
break :blk if (std.c.rename(old_path, new_path) == -1) errno().* else 0;
} else if (@hasDecl(system, "rename")) blk: {
break :blk system.getErrno(system.rename(old_path, new_path));
} else if (@hasDecl(system, "renameat")) blk: {
break :blk system.getErrno(system.renameat(AT_FDCWD, old_path, AT_FDCWD, new_path));
} else blk: {
break :blk system.getErrno(system.renameat2(AT_FDCWD, old_path, AT_FDCWD, new_path, 0));
};
switch (err) {
0 => return,
EACCES => return error.AccessDenied,
EPERM => return error.AccessDenied,
EBUSY => return error.FileBusy,
EDQUOT => return error.DiskQuota,
EFAULT => unreachable,
EINVAL => unreachable,
EISDIR => return error.IsDir,
ELOOP => return error.SymLinkLoop,
EMLINK => return error.LinkQuotaExceeded,
ENAMETOOLONG => return error.NameTooLong,
ENOENT => return error.FileNotFound,
ENOTDIR => return error.NotDir,
ENOMEM => return error.SystemResources,
ENOSPC => return error.NoSpaceLeft,
EEXIST => return error.PathAlreadyExists,
ENOTEMPTY => return error.PathAlreadyExists,
EROFS => return error.ReadOnlyFileSystem,
EXDEV => return error.RenameAcrossMountPoints,
else => return unexpectedErrno(err),
}
}
/// Same as `rename` except the parameters are null-terminated UTF16LE-encoded strings.
/// TODO handle when linking libc
pub fn renameW(old_path: [*]const u16, new_path: [*]const u16) RenameError!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 const MakeDirError = error{};
/// Create a directory.
/// `mode` is ignored on Windows.
pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void {
if (windows.is_the_target and !builtin.link_libc) {
const dir_path_w = try sliceToPrefixedFileW(dir_path);
return mkdirW(&dir_path_w, mode);
} else {
const dir_path_c = try toPosixPath(dir_path);
return mkdirC(&dir_path_c, mode);
}
}
/// Same as `mkdir` but the parameter is a null-terminated UTF8-encoded string.
pub fn mkdirC(dir_path: [*]const u8, mode: u32) MakeDirError!void {
if (windows.is_the_target and !builtin.link_libc) {
const dir_path_w = try cStrToPrefixedFileW(dir_path);
return mkdirW(&dir_path_w, mode);
}
const err = if (builtin.link_libc) blk: {
break :blk if (std.c.mkdir(dir_path, mode) == -1) errno().* else 0;
} else if (@hasDecl(system, "mkdir")) blk: {
break :blk system.getErrno(system.mkdir(dir_path, mode));
} else blk: {
break :blk system.getErrno(system.mkdirat(AT_FDCWD, dir_path, mode));
};
switch (err) {
0 => return,
EACCES => return error.AccessDenied,
EPERM => return error.AccessDenied,
EDQUOT => return error.DiskQuota,
EEXIST => return error.PathAlreadyExists,
EFAULT => unreachable,
ELOOP => return error.SymLinkLoop,
EMLINK => return error.LinkQuotaExceeded,
ENAMETOOLONG => return error.NameTooLong,
ENOENT => return error.FileNotFound,
ENOMEM => return error.SystemResources,
ENOSPC => return error.NoSpaceLeft,
ENOTDIR => return error.NotDir,
EROFS => return error.ReadOnlyFileSystem,
else => return unexpectedErrno(err),
}
}
/// Same as `mkdir` but the parameter is a null-terminated UTF16LE-encoded string.
pub fn mkdirW(dir_path: []const u8, mode: u32) MakeDirError!void {
const dir_path_w = try sliceToPrefixedFileW(dir_path);
if (windows.CreateDirectoryW(&dir_path_w, null) == 0) {
const err = windows.GetLastError();
switch (err) {
windows.ERROR.ALREADY_EXISTS => return error.PathAlreadyExists,
windows.ERROR.PATH_NOT_FOUND => return error.FileNotFound,
else => return unexpectedErrorWindows(err),
}
}
}
pub const DeleteDirError = error{
AccessDenied,
FileBusy,
SymLinkLoop,
NameTooLong,
FileNotFound,
SystemResources,
NotDir,
DirNotEmpty,
ReadOnlyFileSystem,
InvalidUtf8,
BadPathName,
Unexpected,
};
/// Deletes an empty directory.
pub fn rmdir(dir_path: []const u8) DeleteDirError!void {
if (windows.is_the_target and !builtin.link_libc) {
const dir_path_w = try sliceToPrefixedFileW(dir_path);
return rmdirW(&dir_path_w);
} else {
const dir_path_c = try toPosixPath(dir_path);
return rmdirC(&dir_path_c);
}
}
/// Same as `rmdir` except the parameter is a null-terminated UTF8-encoded string.
pub fn rmdirC(dir_path: [*]const u8) DeleteDirError!void {
if (windows.is_the_target and !builtin.link_libc) {
const dir_path_w = try cStrToPrefixedFileW(dir_path);
return rmdirW(&dir_path_w);
}
const err = if (builtin.link_libc) blk: {
break :blk if (std.c.rmdir(dir_path) == -1) errno().* else 0;
} else if (@hasDecl(system, "rmdir")) blk: {
break :blk system.getErrno(system.rmdir(dir_path));
} else blk: {
break :blk system.getErrno(system.unlinkat(AT_FDCWD, dir_path, AT_REMOVEDIR));
};
switch (err) {
0 => return,
EACCES => return error.AccessDenied,
EPERM => return error.AccessDenied,
EBUSY => return error.FileBusy,
EFAULT => unreachable,
EINVAL => unreachable,
ELOOP => return error.SymLinkLoop,
ENAMETOOLONG => return error.NameTooLong,
ENOENT => return error.FileNotFound,
ENOMEM => return error.SystemResources,
ENOTDIR => return error.NotDir,
EEXIST => return error.DirNotEmpty,
ENOTEMPTY => return error.DirNotEmpty,
EROFS => return error.ReadOnlyFileSystem,
else => return unexpectedErrno(err),
}
}
/// Same as `rmdir` except the parameter is a null-terminated UTF16LE-encoded string.
/// TODO handle linking libc
pub fn rmdirW(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),
}
}
}
pub const ChangeCurDirError = error{};
/// Changes the current working directory of the calling process.
/// `dir_path` is recommended to be a UTF-8 encoded string.
pub fn chdir(dir_path: []const u8) ChangeCurDirError!void {
if (windows.is_the_target and !builtin.link_libc) {
const dir_path_w = try sliceToPrefixedFileW(dir_path);
return chdirW(&dir_path_w);
} else {
const dir_path_c = try toPosixPath(dir_path);
return chdirC(&dir_path_c);
}
}
/// Same as `chdir` except the parameter is null-terminated.
pub fn chdirC(dir_path: [*]const u8) ChangeCurDirError!void {
if (windows.is_the_target and !builtin.link_libc) {
const dir_path_w = try cStrToPrefixedFileW(dir_path);
return chdirW(&dir_path_w);
}
const err = if (builtin.link_libc) blk: {
break :blk if (std.c.chdir(dir_path) == -1) errno().* else 0;
} else blk: {
break :blk system.getErrno(system.chdir(dir_path));
};
switch (err) {
0 => return,
EACCES => return error.AccessDenied,
EFAULT => unreachable,
EIO => return error.FileSystem,
ELOOP => return error.SymLinkLoop,
ENAMETOOLONG => return error.NameTooLong,
ENOENT => return error.FileNotFound,
ENOMEM => return error.SystemResources,
ENOTDIR => return error.NotDir,
else => return unexpectedErrno(err),
}
}
/// Same as `chdir` except the parameter is a null-terminated, UTF16LE-encoded string.
/// TODO handle linking libc
pub fn chdirW(dir_path: [*]const u16) ChangeCurDirError!void {
@compileError("TODO implement chdir for Windows");
}
pub const ReadLinkError = error{};
/// Read value of a symbolic link.
/// The return value is a slice of `out_buffer` from index 0.
pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
if (windows.is_the_target and !builtin.link_libc) {
const file_path_w = try sliceToPrefixedFileW(file_path);
return readlinkW(&file_path_w, out_buffer);
} else {
const file_path_c = try toPosixPath(file_path);
return readlinkC(&file_path_c, out_buffer);
}
}
/// Same as `readlink` except `file_path` is null-terminated.
pub fn readlinkC(file_path: [*]const u8, out_buffer: []u8) ReadLinkError![]u8 {
if (windows.is_the_target and !builtin.link_libc) {
const file_path_w = try cStrToPrefixedFileW(file_path);
return readlinkW(&file_path_w, out_buffer);
}
const err = if (builtin.link_libc) blk: {
break :blk if (std.c.readlink(file_path, out_buffer.ptr, out_buffer.len) == -1) errno().* else 0;
} else if (@hasDecl(system, "readlink")) blk: {
break :blk system.getErrno(system.readlink(file_path, out_buffer.ptr, out_buffer.len));
} else blk: {
break :blk system.getErrno(system.readlinkat(AT_FDCWD, file_path, out_buffer.ptr, out_buffer.len));
};
const rc = system.readlink(file_path, out_buffer, out_buffer.len);
switch (system.getErrno(rc)) {
0 => return out_buffer[0..rc],
EACCES => return error.AccessDenied,
EFAULT => unreachable,
EINVAL => unreachable,
EIO => return error.FileSystem,
ELOOP => return error.SymLinkLoop,
ENAMETOOLONG => return error.NameTooLong,
ENOENT => return error.FileNotFound,
ENOMEM => return error.SystemResources,
ENOTDIR => return error.NotDir,
else => |err| return unexpectedErrno(err),
}
}
pub const SetIdError = error{
ResourceLimitReached,
InvalidUserId,
PermissionDenied,
Unexpected,
};
pub fn setuid(uid: u32) SetIdError!void {
switch (system.getErrno(system.setuid(uid))) {
0 => return,
EAGAIN => return error.ResourceLimitReached,
EINVAL => return error.InvalidUserId,
EPERM => return error.PermissionDenied,
else => |err| return unexpectedErrno(err),
}
}
pub fn setreuid(ruid: u32, euid: u32) SetIdError!void {
switch (system.getErrno(system.setreuid(ruid, euid))) {
0 => return,
EAGAIN => return error.ResourceLimitReached,
EINVAL => return error.InvalidUserId,
EPERM => return error.PermissionDenied,
else => |err| return unexpectedErrno(err),
}
}
pub fn setgid(gid: u32) SetIdError!void {
switch (system.getErrno(system.setgid(gid))) {
0 => return,
EAGAIN => return error.ResourceLimitReached,
EINVAL => return error.InvalidUserId,
EPERM => return error.PermissionDenied,
else => |err| return unexpectedErrno(err),
}
}
pub fn setregid(rgid: u32, egid: u32) SetIdError!void {
switch (system.getErrno(system.setregid(rgid, egid))) {
0 => return,
EAGAIN => return error.ResourceLimitReached,
EINVAL => return error.InvalidUserId,
EPERM => return error.PermissionDenied,
else => |err| return unexpectedErrno(err),
}
}
pub const GetStdHandleError = error{
NoStandardHandleAttached,
Unexpected,
};
pub fn GetStdHandle(handle_id: windows.DWORD) GetStdHandleError!FileHandle {
if (windows.is_the_target) {
const handle = windows.GetStdHandle(handle_id) orelse return error.NoStandardHandleAttached;
if (handle == windows.INVALID_HANDLE_VALUE) {
switch (windows.GetLastError()) {
else => |err| unexpectedErrorWindows(err),
}
}
return handle;
}
switch (handle_id) {
windows.STD_ERROR_HANDLE => return STDERR_FILENO,
windows.STD_OUTPUT_HANDLE => return STDOUT_FILENO,
windows.STD_INPUT_HANDLE => return STDIN_FILENO,
else => unreachable,
}
}
/// Test whether a file descriptor refers to a terminal.
pub fn isatty(handle: FileHandle) bool {
if (builtin.link_libc) {
return c.isatty(handle) != 0;
}
if (windows.is_the_target) {
if (isCygwinPty(handle))
return true;
var out: windows.DWORD = undefined;
return windows.GetConsoleMode(handle, &out) != 0;
}
if (wasi.is_the_target) {
@compileError("TODO implement std.os.posix.isatty for WASI");
}
var wsz: system.winsize = undefined;
return system.syscall3(system.SYS_ioctl, @bitCast(usize, isize(handle)), TIOCGWINSZ, @ptrToInt(&wsz)) == 0;
}
pub fn isCygwinPty(handle: FileHandle) bool {
if (!windows.is_the_target) return false;
const size = @sizeOf(windows.FILE_NAME_INFO);
var name_info_bytes align(@alignOf(windows.FILE_NAME_INFO)) = []u8{0} ** (size + windows.MAX_PATH);
if (windows.GetFileInformationByHandleEx(
handle,
windows.FileNameInfo,
@ptrCast(*c_void, &name_info_bytes[0]),
@intCast(u32, name_info_bytes.len),
) == 0) {
return false;
}
const name_info = @ptrCast(*const windows.FILE_NAME_INFO, &name_info_bytes[0]);
const name_bytes = name_info_bytes[size .. size + usize(name_info.FileNameLength)];
const name_wide = @bytesToSlice(u16, name_bytes);
return mem.indexOf(u16, name_wide, []u16{ 'm', 's', 'y', 's', '-' }) != null or
mem.indexOf(u16, name_wide, []u16{ '-', 'p', 't', 'y' }) != null;
}
pub const SocketError = error{
/// Permission to create a socket of the specified type and/or
/// protocol is denied.
PermissionDenied,
/// The implementation does not support the specified address family.
AddressFamilyNotSupported,
/// Unknown protocol, or protocol family not available.
ProtocolFamilyNotAvailable,
/// The per-process limit on the number of open file descriptors has been reached.
ProcessFdQuotaExceeded,
/// The system-wide limit on the total number of open files has been reached.
SystemFdQuotaExceeded,
/// Insufficient memory is available. The socket cannot be created until sufficient
/// resources are freed.
SystemResources,
/// The protocol type or the specified protocol is not supported within this domain.
ProtocolNotSupported,
Unexpected,
};
pub fn socket(domain: u32, socket_type: u32, protocol: u32) SocketError!i32 {
const rc = system.socket(domain, socket_type, protocol);
switch (system.getErrno(rc)) {
0 => return @intCast(i32, rc),
EACCES => return error.PermissionDenied,
EAFNOSUPPORT => return error.AddressFamilyNotSupported,
EINVAL => return error.ProtocolFamilyNotAvailable,
EMFILE => return error.ProcessFdQuotaExceeded,
ENFILE => return error.SystemFdQuotaExceeded,
ENOBUFS, ENOMEM => return error.SystemResources,
EPROTONOSUPPORT => return error.ProtocolNotSupported,
else => |err| return unexpectedErrno(err),
}
}
pub const BindError = error{
/// The address is protected, and the user is not the superuser.
/// For UNIX domain sockets: Search permission is denied on a component
/// of the path prefix.
AccessDenied,
/// The given address is already in use, or in the case of Internet domain sockets,
/// The port number was specified as zero in the socket
/// address structure, but, upon attempting to bind to an ephemeral port, it was
/// determined that all port numbers in the ephemeral port range are currently in
/// use. See the discussion of /proc/sys/net/ipv4/ip_local_port_range ip(7).
AddressInUse,
/// A nonexistent interface was requested or the requested address was not local.
AddressNotAvailable,
/// Too many symbolic links were encountered in resolving addr.
SymLinkLoop,
/// addr is too long.
NameTooLong,
/// A component in the directory prefix of the socket pathname does not exist.
FileNotFound,
/// Insufficient kernel memory was available.
SystemResources,
/// A component of the path prefix is not a directory.
NotDir,
/// The socket inode would reside on a read-only filesystem.
ReadOnlyFileSystem,
Unexpected,
};
/// addr is `*const T` where T is one of the sockaddr
pub fn bind(fd: i32, addr: *const sockaddr) BindError!void {
const rc = system.bind(fd, system, @sizeOf(sockaddr));
switch (system.getErrno(rc)) {
0 => return,
EACCES => return error.AccessDenied,
EADDRINUSE => return error.AddressInUse,
EBADF => unreachable, // always a race condition if this error is returned
EINVAL => unreachable,
ENOTSOCK => unreachable,
EADDRNOTAVAIL => return error.AddressNotAvailable,
EFAULT => unreachable,
ELOOP => return error.SymLinkLoop,
ENAMETOOLONG => return error.NameTooLong,
ENOENT => return error.FileNotFound,
ENOMEM => return error.SystemResources,
ENOTDIR => return error.NotDir,
EROFS => return error.ReadOnlyFileSystem,
else => |err| return unexpectedErrno(err),
}
}
const ListenError = error{
/// Another socket is already listening on the same port.
/// For Internet domain sockets, the socket referred to by sockfd had not previously
/// been bound to an address and, upon attempting to bind it to an ephemeral port, it
/// was determined that all port numbers in the ephemeral port range are currently in
/// use. See the discussion of /proc/sys/net/ipv4/ip_local_port_range in ip(7).
AddressInUse,
/// The file descriptor sockfd does not refer to a socket.
FileDescriptorNotASocket,
/// The socket is not of a type that supports the listen() operation.
OperationNotSupported,
Unexpected,
};
pub fn listen(sockfd: i32, backlog: u32) ListenError!void {
const rc = system.listen(sockfd, backlog);
switch (system.getErrno(rc)) {
0 => return,
EADDRINUSE => return error.AddressInUse,
EBADF => unreachable,
ENOTSOCK => return error.FileDescriptorNotASocket,
EOPNOTSUPP => return error.OperationNotSupported,
else => |err| return unexpectedErrno(err),
}
}
pub const AcceptError = error{
ConnectionAborted,
/// The per-process limit on the number of open file descriptors has been reached.
ProcessFdQuotaExceeded,
/// The system-wide limit on the total number of open files has been reached.
SystemFdQuotaExceeded,
/// Not enough free memory. This often means that the memory allocation is limited
/// by the socket buffer limits, not by the system memory.
SystemResources,
/// The file descriptor sockfd does not refer to a socket.
FileDescriptorNotASocket,
/// The referenced socket is not of type SOCK_STREAM.
OperationNotSupported,
ProtocolFailure,
/// Firewall rules forbid connection.
BlockedByFirewall,
Unexpected,
};
/// Accept a connection on a socket. `fd` must be opened in blocking mode.
/// See also `accept4_async`.
pub fn accept4(fd: i32, addr: *sockaddr, flags: u32) AcceptError!i32 {
while (true) {
var sockaddr_size = u32(@sizeOf(sockaddr));
const rc = system.accept4(fd, addr, &sockaddr_size, flags);
switch (system.getErrno(rc)) {
0 => return @intCast(i32, rc),
EINTR => continue,
else => |err| return unexpectedErrno(err),
EAGAIN => unreachable, // This function is for blocking only.
EBADF => unreachable, // always a race condition
ECONNABORTED => return error.ConnectionAborted,
EFAULT => unreachable,
EINVAL => unreachable,
EMFILE => return error.ProcessFdQuotaExceeded,
ENFILE => return error.SystemFdQuotaExceeded,
ENOBUFS => return error.SystemResources,
ENOMEM => return error.SystemResources,
ENOTSOCK => return error.FileDescriptorNotASocket,
EOPNOTSUPP => return error.OperationNotSupported,
EPROTO => return error.ProtocolFailure,
EPERM => return error.BlockedByFirewall,
}
}
}
/// This is the same as `accept4` except `fd` is expected to be non-blocking.
/// Returns -1 if would block.
pub fn accept4_async(fd: i32, addr: *sockaddr, flags: u32) AcceptError!i32 {
while (true) {
var sockaddr_size = u32(@sizeOf(sockaddr));
const rc = system.accept4(fd, addr, &sockaddr_size, flags);
switch (system.getErrno(rc)) {
0 => return @intCast(i32, rc),
EINTR => continue,
else => |err| return unexpectedErrno(err),
EAGAIN => return -1,
EBADF => unreachable, // always a race condition
ECONNABORTED => return error.ConnectionAborted,
EFAULT => unreachable,
EINVAL => unreachable,
EMFILE => return error.ProcessFdQuotaExceeded,
ENFILE => return error.SystemFdQuotaExceeded,
ENOBUFS => return error.SystemResources,
ENOMEM => return error.SystemResources,
ENOTSOCK => return error.FileDescriptorNotASocket,
EOPNOTSUPP => return error.OperationNotSupported,
EPROTO => return error.ProtocolFailure,
EPERM => return error.BlockedByFirewall,
}
}
}
pub const EpollCreateError = error{
/// The per-user limit on the number of epoll instances imposed by
/// /proc/sys/fs/epoll/max_user_instances was encountered. See epoll(7) for further
/// details.
/// Or, The per-process limit on the number of open file descriptors has been reached.
ProcessFdQuotaExceeded,
/// The system-wide limit on the total number of open files has been reached.
SystemFdQuotaExceeded,
/// There was insufficient memory to create the kernel object.
SystemResources,
Unexpected,
};
pub fn epoll_create1(flags: u32) EpollCreateError!i32 {
const rc = system.epoll_create1(flags);
switch (system.getErrno(rc)) {
0 => return @intCast(i32, rc),
else => |err| return unexpectedErrno(err),
EINVAL => unreachable,
EMFILE => return error.ProcessFdQuotaExceeded,
ENFILE => return error.SystemFdQuotaExceeded,
ENOMEM => return error.SystemResources,
}
}
pub const EpollCtlError = error{
/// op was EPOLL_CTL_ADD, and the supplied file descriptor fd is already registered
/// with this epoll instance.
FileDescriptorAlreadyPresentInSet,
/// fd refers to an epoll instance and this EPOLL_CTL_ADD operation would result in a
/// circular loop of epoll instances monitoring one another.
OperationCausesCircularLoop,
/// op was EPOLL_CTL_MOD or EPOLL_CTL_DEL, and fd is not registered with this epoll
/// instance.
FileDescriptorNotRegistered,
/// There was insufficient memory to handle the requested op control operation.
SystemResources,
/// The limit imposed by /proc/sys/fs/epoll/max_user_watches was encountered while
/// trying to register (EPOLL_CTL_ADD) a new file descriptor on an epoll instance.
/// See epoll(7) for further details.
UserResourceLimitReached,
/// The target file fd does not support epoll. This error can occur if fd refers to,
/// for example, a regular file or a directory.
FileDescriptorIncompatibleWithEpoll,
Unexpected,
};
pub fn epoll_ctl(epfd: i32, op: u32, fd: i32, event: *epoll_event) EpollCtlError!void {
const rc = system.epoll_ctl(epfd, op, fd, event);
switch (system.getErrno(rc)) {
0 => return,
else => |err| return unexpectedErrno(err),
EBADF => unreachable, // always a race condition if this happens
EEXIST => return error.FileDescriptorAlreadyPresentInSet,
EINVAL => unreachable,
ELOOP => return error.OperationCausesCircularLoop,
ENOENT => return error.FileDescriptorNotRegistered,
ENOMEM => return error.SystemResources,
ENOSPC => return error.UserResourceLimitReached,
EPERM => return error.FileDescriptorIncompatibleWithEpoll,
}
}
/// Waits for an I/O event on an epoll file descriptor.
/// Returns the number of file descriptors ready for the requested I/O,
/// or zero if no file descriptor became ready during the requested timeout milliseconds.
pub fn epoll_wait(epfd: i32, events: []epoll_event, timeout: i32) usize {
while (true) {
// TODO get rid of the @intCast
const rc = system.epoll_wait(epfd, events.ptr, @intCast(u32, events.len), timeout);
switch (system.getErrno(rc)) {
0 => return rc,
EINTR => continue,
EBADF => unreachable,
EFAULT => unreachable,
EINVAL => unreachable,
else => unreachable,
}
}
}
pub const EventFdError = error{
SystemResources,
ProcessFdQuotaExceeded,
SystemFdQuotaExceeded,
Unexpected,
};
pub fn eventfd(initval: u32, flags: u32) EventFdError!i32 {
const rc = system.eventfd(initval, flags);
switch (system.getErrno(rc)) {
0 => return @intCast(i32, rc),
else => |err| return unexpectedErrno(err),
EINVAL => unreachable, // invalid parameters
EMFILE => return error.ProcessFdQuotaExceeded,
ENFILE => return error.SystemFdQuotaExceeded,
ENODEV => return error.SystemResources,
ENOMEM => return error.SystemResources,
}
}
pub const GetSockNameError = error{
/// Insufficient resources were available in the system to perform the operation.
SystemResources,
Unexpected,
};
pub fn getsockname(sockfd: i32) GetSockNameError!sockaddr {
var addr: sockaddr = undefined;
var addrlen: socklen_t = @sizeOf(sockaddr);
switch (system.getErrno(system.getsockname(sockfd, &addr, &addrlen))) {
0 => return addr,
else => |err| return unexpectedErrno(err),
EBADF => unreachable, // always a race condition
EFAULT => unreachable,
EINVAL => unreachable, // invalid parameters
ENOTSOCK => unreachable,
ENOBUFS => return error.SystemResources,
}
}
pub const ConnectError = error{
/// For UNIX domain sockets, which are identified by pathname: Write permission is denied on the socket
/// file, or search permission is denied for one of the directories in the path prefix.
/// or
/// The user tried to connect to a broadcast address without having the socket broadcast flag enabled or
/// the connection request failed because of a local firewall rule.
PermissionDenied,
/// Local address is already in use.
AddressInUse,
/// (Internet domain sockets) The socket referred to by sockfd had not previously been bound to an
/// address and, upon attempting to bind it to an ephemeral port, it was determined that all port numbers
/// in the ephemeral port range are currently in use. See the discussion of
/// /proc/sys/net/ipv4/ip_local_port_range in ip(7).
AddressNotAvailable,
/// The passed address didn't have the correct address family in its sa_family field.
AddressFamilyNotSupported,
/// Insufficient entries in the routing cache.
SystemResources,
/// A connect() on a stream socket found no one listening on the remote address.
ConnectionRefused,
/// Network is unreachable.
NetworkUnreachable,
/// Timeout while attempting connection. The server may be too busy to accept new connections. Note
/// that for IP sockets the timeout may be very long when syncookies are enabled on the server.
ConnectionTimedOut,
Unexpected,
};
/// Initiate a connection on a socket.
/// This is for blocking file descriptors only.
/// For non-blocking, see `connect_async`.
pub fn connect(sockfd: i32, sockaddr: *const sockaddr) ConnectError!void {
while (true) {
switch (system.getErrno(system.connect(sockfd, sockaddr, @sizeOf(sockaddr)))) {
0 => return,
else => |err| return unexpectedErrno(err),
EACCES => return error.PermissionDenied,
EPERM => return error.PermissionDenied,
EADDRINUSE => return error.AddressInUse,
EADDRNOTAVAIL => return error.AddressNotAvailable,
EAFNOSUPPORT => return error.AddressFamilyNotSupported,
EAGAIN => return error.SystemResources,
EALREADY => unreachable, // The socket is nonblocking and a previous connection attempt has not yet been completed.
EBADF => unreachable, // sockfd is not a valid open file descriptor.
ECONNREFUSED => return error.ConnectionRefused,
EFAULT => unreachable, // The socket structure address is outside the user's address space.
EINPROGRESS => unreachable, // The socket is nonblocking and the connection cannot be completed immediately.
EINTR => continue,
EISCONN => unreachable, // The socket is already connected.
ENETUNREACH => return error.NetworkUnreachable,
ENOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.
EPROTOTYPE => unreachable, // The socket type does not support the requested communications protocol.
ETIMEDOUT => return error.ConnectionTimedOut,
}
}
}
/// Same as `connect` except it is for blocking socket file descriptors.
/// It expects to receive EINPROGRESS`.
pub fn connect_async(sockfd: i32, sockaddr: *const c_void, len: u32) ConnectError!void {
while (true) {
switch (system.getErrno(system.connect(sockfd, sockaddr, len))) {
0, EINPROGRESS => return,
EINTR => continue,
else => return unexpectedErrno(err),
EACCES => return error.PermissionDenied,
EPERM => return error.PermissionDenied,
EADDRINUSE => return error.AddressInUse,
EADDRNOTAVAIL => return error.AddressNotAvailable,
EAFNOSUPPORT => return error.AddressFamilyNotSupported,
EAGAIN => return error.SystemResources,
EALREADY => unreachable, // The socket is nonblocking and a previous connection attempt has not yet been completed.
EBADF => unreachable, // sockfd is not a valid open file descriptor.
ECONNREFUSED => return error.ConnectionRefused,
EFAULT => unreachable, // The socket structure address is outside the user's address space.
EISCONN => unreachable, // The socket is already connected.
ENETUNREACH => return error.NetworkUnreachable,
ENOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.
EPROTOTYPE => unreachable, // The socket type does not support the requested communications protocol.
ETIMEDOUT => return error.ConnectionTimedOut,
}
}
}
pub fn getsockoptError(sockfd: i32) ConnectError!void {
var err_code: i32 = undefined;
var size: u32 = @sizeOf(i32);
const rc = system.getsockopt(sockfd, SOL_SOCKET, SO_ERROR, @ptrCast([*]u8, &err_code), &size);
assert(size == 4);
switch (system.getErrno(rc)) {
0 => switch (err_code) {
0 => return,
EACCES => return error.PermissionDenied,
EPERM => return error.PermissionDenied,
EADDRINUSE => return error.AddressInUse,
EADDRNOTAVAIL => return error.AddressNotAvailable,
EAFNOSUPPORT => return error.AddressFamilyNotSupported,
EAGAIN => return error.SystemResources,
EALREADY => unreachable, // The socket is nonblocking and a previous connection attempt has not yet been completed.
EBADF => unreachable, // sockfd is not a valid open file descriptor.
ECONNREFUSED => return error.ConnectionRefused,
EFAULT => unreachable, // The socket structure address is outside the user's address space.
EISCONN => unreachable, // The socket is already connected.
ENETUNREACH => return error.NetworkUnreachable,
ENOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.
EPROTOTYPE => unreachable, // The socket type does not support the requested communications protocol.
ETIMEDOUT => return error.ConnectionTimedOut,
else => |err| return unexpectedErrno(err),
},
EBADF => unreachable, // The argument sockfd is not a valid file descriptor.
EFAULT => unreachable, // The address pointed to by optval or optlen is not in a valid part of the process address space.
EINVAL => unreachable,
ENOPROTOOPT => unreachable, // The option is unknown at the level indicated.
ENOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.
else => |err| return unexpectedErrno(err),
}
}
pub fn wait(pid: i32) i32 {
var status: i32 = undefined;
while (true) {
switch (system.getErrno(system.waitpid(pid, &status, 0))) {
0 => return status,
EINTR => continue,
ECHILD => unreachable, // The process specified does not exist. It would be a race condition to handle this error.
EINVAL => unreachable, // The options argument was invalid
else => unreachable,
}
}
}
pub fn fstat(fd: FileHandle) !Stat {
var stat: Stat = undefined;
switch (system.getErrno(system.fstat(fd, &stat))) {
0 => return stat,
EBADF => unreachable, // Always a race condition.
ENOMEM => return error.SystemResources,
else => |err| return unexpectedErrno(err),
}
return stat;
}
pub const KQueueError = error{
/// The per-process limit on the number of open file descriptors has been reached.
ProcessFdQuotaExceeded,
/// The system-wide limit on the total number of open files has been reached.
SystemFdQuotaExceeded,
Unexpected,
};
pub fn kqueue() KQueueError!i32 {
const rc = system.kqueue();
switch (system.getErrno(rc)) {
0 => return @intCast(i32, rc),
EMFILE => return error.ProcessFdQuotaExceeded,
ENFILE => return error.SystemFdQuotaExceeded,
else => |err| return unexpectedErrno(err),
}
}
pub const KEventError = error{
/// The process does not have permission to register a filter.
AccessDenied,
/// The event could not be found to be modified or deleted.
EventNotFound,
/// No memory was available to register the event.
SystemResources,
/// The specified process to attach to does not exist.
ProcessNotFound,
};
pub fn kevent(
kq: i32,
changelist: []const Kevent,
eventlist: []Kevent,
timeout: ?*const timespec,
) KEventError!usize {
while (true) {
const rc = system.kevent(kq, changelist, eventlist, timeout);
switch (system.getErrno(rc)) {
0 => return rc,
EACCES => return error.AccessDenied,
EFAULT => unreachable,
EBADF => unreachable, // Always a race condition.
EINTR => continue,
EINVAL => unreachable,
ENOENT => return error.EventNotFound,
ENOMEM => return error.SystemResources,
ESRCH => return error.ProcessNotFound,
else => unreachable,
}
}
}
pub const INotifyInitError = error{
ProcessFdQuotaExceeded,
SystemFdQuotaExceeded,
SystemResources,
Unexpected,
};
/// initialize an inotify instance
pub fn inotify_init1(flags: u32) INotifyInitError!i32 {
const rc = system.inotify_init1(flags);
switch (system.getErrno(rc)) {
0 => return @intCast(i32, rc),
EINVAL => unreachable,
EMFILE => return error.ProcessFdQuotaExceeded,
ENFILE => return error.SystemFdQuotaExceeded,
ENOMEM => return error.SystemResources,
else => |err| return unexpectedErrno(err),
}
}
pub const INotifyAddWatchError = error{
AccessDenied,
NameTooLong,
FileNotFound,
SystemResources,
UserResourceLimitReached,
Unexpected,
};
/// add a watch to an initialized inotify instance
pub fn inotify_add_watch(inotify_fd: i32, pathname: []const u8, mask: u32) INotifyAddWatchError!i32 {
const pathname_c = try toPosixPath(pathname);
return inotify_add_watchC(inotify_fd, &pathname_c, mask);
}
/// Same as `inotify_add_watch` except pathname is null-terminated.
pub fn inotify_add_watchC(inotify_fd: i32, pathname: [*]const u8, mask: u32) INotifyAddWatchError!i32 {
const rc = system.inotify_add_watch(inotify_fd, pathname, mask);
switch (system.getErrno(rc)) {
0 => return @intCast(i32, rc),
EACCES => return error.AccessDenied,
EBADF => unreachable,
EFAULT => unreachable,
EINVAL => unreachable,
ENAMETOOLONG => return error.NameTooLong,
ENOENT => return error.FileNotFound,
ENOMEM => return error.SystemResources,
ENOSPC => return error.UserResourceLimitReached,
else => |err| return unexpectedErrno(err),
}
}
/// remove an existing watch from an inotify instance
pub fn inotify_rm_watch(inotify_fd: i32, wd: i32) void {
switch (system.getErrno(system.inotify_rm_watch(inotify_fd, wd))) {
0 => return,
EBADF => unreachable,
EINVAL => unreachable,
else => unreachable,
}
}
pub const MProtectError = error{
AccessDenied,
OutOfMemory,
Unexpected,
};
/// address and length must be page-aligned
pub fn mprotect(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);
switch (system.getErrno(system.mprotect(address, length, protection))) {
0 => return,
EINVAL => unreachable,
EACCES => return error.AccessDenied,
ENOMEM => return error.OutOfMemory,
else => return unexpectedErrno(err),
}
}
/// Used to convert a slice to a null terminated slice on the stack.
/// TODO https://github.com/ziglang/zig/issues/287
pub fn toPosixPath(file_path: []const u8) ![PATH_MAX]u8 {
var path_with_null: [PATH_MAX]u8 = undefined;
// >= rather than > to make room for the null byte
if (file_path.len >= PATH_MAX) return error.NameTooLong;
mem.copy(u8, &path_with_null, file_path);
path_with_null[file_path.len] = 0;
return path_with_null;
}
const unexpected_error_tracing = builtin.mode == .Debug;
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 unexpectedErrno(errno: usize) UnexpectedError {
if (unexpected_error_tracing) {
std.debug.warn("unexpected errno: {}\n", errno);
std.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) {
std.debug.warn("unexpected GetLastError(): {}\n", err);
std.debug.dumpCurrentStackTrace(null);
}
return error.Unexpected;
}
pub fn cStrToPrefixedFileW(s: [*]const u8) ![PATH_MAX_WIDE + 1]u16 {
return sliceToPrefixedFileW(mem.toSliceConst(u8, s));
}
pub fn sliceToPrefixedFileW(s: []const u8) ![PATH_MAX_WIDE + 1]u16 {
return sliceToPrefixedSuffixedFileW(s, []u16{0});
}
pub fn sliceToPrefixedSuffixedFileW(s: []const u8, comptime suffix: []const u16) ![PATH_MAX_WIDE + suffix.len]u16 {
// TODO well defined copy elision
var result: [PATH_MAX_WIDE + suffix.len]u16 = undefined;
// > File I/O functions in the Windows API convert "/" to "\" as part of
// > converting the name to an NT-style name, except when using the "\\?\"
// > prefix as detailed in the following sections.
// from https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file#maximum-path-length-limitation
// Because we want the larger maximum path length for absolute paths, we
// disallow forward slashes in zig std lib file functions on Windows.
for (s) |byte| {
switch (byte) {
'/', '*', '?', '"', '<', '>', '|' => return error.BadPathName,
else => {},
}
}
const start_index = if (mem.startsWith(u8, s, "\\\\") or !os.path.isAbsolute(s)) 0 else blk: {
const prefix = []u16{ '\\', '\\', '?', '\\' };
mem.copy(u16, result[0..], prefix);
break :blk prefix.len;
};
const end_index = start_index + try std.unicode.utf8ToUtf16Le(result[start_index..], s);
assert(end_index <= result.len);
if (end_index + suffix.len > result.len) return error.NameTooLong;
mem.copy(u16, result[end_index..], suffix);
return result;
}