2160 lines
79 KiB
Zig
2160 lines
79 KiB
Zig
|
// 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
|
|||
|
/// pro‐tocol is denied.
|
|||
|
PermissionDenied,
|
|||
|
|
|||
|
/// The implementation does not support the specified address family.
|
|||
|
AddressFamilyNotSupported,
|
|||
|
|
|||
|
/// Unknown protocol, or protocol family not available.
|
|||
|
ProtocolFamilyNotAvailable,
|
|||
|
|
|||
|
/// The per-process limit on the number of open file descriptors has been reached.
|
|||
|
ProcessFdQuotaExceeded,
|
|||
|
|
|||
|
/// The system-wide limit on the total number of open files has been reached.
|
|||
|
SystemFdQuotaExceeded,
|
|||
|
|
|||
|
/// Insufficient memory is available. The socket cannot be created until sufficient
|
|||
|
/// resources are freed.
|
|||
|
SystemResources,
|
|||
|
|
|||
|
/// The protocol type or the specified protocol is not supported within this domain.
|
|||
|
ProtocolNotSupported,
|
|||
|
|
|||
|
Unexpected,
|
|||
|
};
|
|||
|
|
|||
|
pub fn socket(domain: u32, socket_type: u32, protocol: u32) SocketError!i32 {
|
|||
|
const rc = system.socket(domain, socket_type, protocol);
|
|||
|
switch (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;
|
|||
|
}
|