4325 lines
161 KiB
Zig
4325 lines
161 KiB
Zig
// This file contains thin wrappers around OS-specific APIs, with these
|
||
// specific goals in mind:
|
||
// * Convert "errno"-style error codes into Zig errors.
|
||
// * When null-terminated byte buffers are required, provide APIs which accept
|
||
// slices as well as APIs which accept null-terminated byte buffers. Same goes
|
||
// for UTF-16LE encoding.
|
||
// * Where operating systems share APIs, e.g. POSIX, these thin wrappers provide
|
||
// cross platform abstracting.
|
||
// * When there exists a corresponding libc function and linking libc, the libc
|
||
// implementation is used. Exceptions are made for known buggy areas of libc.
|
||
// On Linux libc can be side-stepped by using `std.os.linux` directly.
|
||
// * For Windows, this file represents the API that libc would provide for
|
||
// Windows. For thin wrappers around Windows-specific APIs, see `std.os.windows`.
|
||
// Note: The Zig standard library does not support POSIX thread cancellation, and
|
||
// in general EINTR is handled by trying again.
|
||
|
||
const root = @import("root");
|
||
const std = @import("std.zig");
|
||
const builtin = @import("builtin");
|
||
const assert = std.debug.assert;
|
||
const math = std.math;
|
||
const mem = std.mem;
|
||
const elf = std.elf;
|
||
const dl = @import("dynamic_library.zig");
|
||
const MAX_PATH_BYTES = std.fs.MAX_PATH_BYTES;
|
||
|
||
pub const darwin = @import("os/darwin.zig");
|
||
pub const dragonfly = @import("os/dragonfly.zig");
|
||
pub const freebsd = @import("os/freebsd.zig");
|
||
pub const netbsd = @import("os/netbsd.zig");
|
||
pub const linux = @import("os/linux.zig");
|
||
pub const uefi = @import("os/uefi.zig");
|
||
pub const wasi = @import("os/wasi.zig");
|
||
pub const windows = @import("os/windows.zig");
|
||
|
||
comptime {
|
||
assert(@import("std") == std); // std lib tests require --override-lib-dir
|
||
}
|
||
|
||
test "" {
|
||
_ = darwin;
|
||
_ = freebsd;
|
||
_ = linux;
|
||
_ = netbsd;
|
||
_ = uefi;
|
||
_ = wasi;
|
||
_ = windows;
|
||
|
||
_ = @import("os/test.zig");
|
||
}
|
||
|
||
/// Applications can override the `system` API layer in their root source file.
|
||
/// Otherwise, when linking libc, this is the C API.
|
||
/// When not linking libc, it is the OS-specific system interface.
|
||
pub const system = if (@hasDecl(root, "os") and root.os != @This())
|
||
root.os.system
|
||
else if (builtin.link_libc)
|
||
std.c
|
||
else switch (builtin.os.tag) {
|
||
.macosx, .ios, .watchos, .tvos => darwin,
|
||
.freebsd => freebsd,
|
||
.linux => linux,
|
||
.netbsd => netbsd,
|
||
.dragonfly => dragonfly,
|
||
.wasi => wasi,
|
||
.windows => windows,
|
||
else => struct {},
|
||
};
|
||
|
||
pub usingnamespace @import("os/bits.zig");
|
||
|
||
/// See also `getenv`. Populated by startup code before main().
|
||
/// TODO this is a footgun because the value will be undefined when using `zig build-lib`.
|
||
/// https://github.com/ziglang/zig/issues/4524
|
||
pub var environ: [][*:0]u8 = undefined;
|
||
|
||
/// Populated by startup code before main().
|
||
/// Not available on Windows. See `std.process.args`
|
||
/// for obtaining the process arguments.
|
||
pub var argv: [][*:0]u8 = undefined;
|
||
|
||
/// To obtain errno, call this function with the return value of the
|
||
/// system function call. For some systems this will obtain the value directly
|
||
/// from the return code; for others it will use a thread-local errno variable.
|
||
/// Therefore, this function only returns a well-defined value when it is called
|
||
/// directly after the system function call which one wants to learn the errno
|
||
/// value of.
|
||
pub const errno = system.getErrno;
|
||
|
||
/// Closes the file descriptor.
|
||
/// This function is not capable of returning any indication of failure. An
|
||
/// application which wants to ensure writes have succeeded before closing
|
||
/// must call `fsync` before `close`.
|
||
/// Note: The Zig standard library does not support POSIX thread cancellation.
|
||
pub fn close(fd: fd_t) void {
|
||
if (builtin.os.tag == .windows) {
|
||
return windows.CloseHandle(fd);
|
||
}
|
||
if (builtin.os.tag == .wasi) {
|
||
_ = wasi.fd_close(fd);
|
||
}
|
||
if (comptime std.Target.current.isDarwin()) {
|
||
// This avoids the EINTR problem.
|
||
switch (darwin.getErrno(darwin.@"close$NOCANCEL"(fd))) {
|
||
EBADF => unreachable, // Always a race condition.
|
||
else => return,
|
||
}
|
||
}
|
||
switch (errno(system.close(fd))) {
|
||
EBADF => unreachable, // Always a race condition.
|
||
EINTR => return, // This is still a success. See https://github.com/ziglang/zig/issues/2425
|
||
else => return,
|
||
}
|
||
}
|
||
|
||
pub const GetRandomError = OpenError;
|
||
|
||
/// Obtain a series of random bytes. These bytes can be used to seed user-space
|
||
/// random number generators or for cryptographic purposes.
|
||
/// When linking against libc, this calls the
|
||
/// appropriate OS-specific library call. Otherwise it uses the zig standard
|
||
/// library implementation.
|
||
pub fn getrandom(buffer: []u8) GetRandomError!void {
|
||
if (builtin.os.tag == .windows) {
|
||
return windows.RtlGenRandom(buffer);
|
||
}
|
||
if (builtin.os.tag == .linux or builtin.os.tag == .freebsd) {
|
||
var buf = buffer;
|
||
const use_c = builtin.os.tag != .linux or
|
||
std.c.versionCheck(builtin.Version{ .major = 2, .minor = 25, .patch = 0 }).ok;
|
||
|
||
while (buf.len != 0) {
|
||
var err: u16 = undefined;
|
||
|
||
const num_read = if (use_c) blk: {
|
||
const rc = std.c.getrandom(buf.ptr, buf.len, 0);
|
||
err = std.c.getErrno(rc);
|
||
break :blk @bitCast(usize, rc);
|
||
} else blk: {
|
||
const rc = linux.getrandom(buf.ptr, buf.len, 0);
|
||
err = linux.getErrno(rc);
|
||
break :blk rc;
|
||
};
|
||
|
||
switch (err) {
|
||
0 => buf = buf[num_read..],
|
||
EINVAL => unreachable,
|
||
EFAULT => unreachable,
|
||
EINTR => continue,
|
||
ENOSYS => return getRandomBytesDevURandom(buf),
|
||
else => return unexpectedErrno(err),
|
||
}
|
||
}
|
||
return;
|
||
}
|
||
if (builtin.os.tag == .wasi) {
|
||
switch (wasi.random_get(buffer.ptr, buffer.len)) {
|
||
0 => return,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
return getRandomBytesDevURandom(buffer);
|
||
}
|
||
|
||
fn getRandomBytesDevURandom(buf: []u8) !void {
|
||
const fd = try openC("/dev/urandom", O_RDONLY | O_CLOEXEC, 0);
|
||
defer close(fd);
|
||
|
||
const st = try fstat(fd);
|
||
if (!S_ISCHR(st.mode)) {
|
||
return error.NoDevice;
|
||
}
|
||
|
||
const file = std.fs.File{
|
||
.handle = fd,
|
||
.io_mode = .blocking,
|
||
.async_block_allowed = std.fs.File.async_block_allowed_yes,
|
||
};
|
||
const stream = file.inStream();
|
||
stream.readNoEof(buf) catch return error.Unexpected;
|
||
}
|
||
|
||
/// Causes abnormal process termination.
|
||
/// If linking against libc, this calls the abort() libc function. Otherwise
|
||
/// it raises SIGABRT followed by SIGKILL and finally lo
|
||
pub fn abort() noreturn {
|
||
@setCold(true);
|
||
// MSVCRT abort() sometimes opens a popup window which is undesirable, so
|
||
// even when linking libc on Windows we use our own abort implementation.
|
||
// See https://github.com/ziglang/zig/issues/2071 for more details.
|
||
if (builtin.os.tag == .windows) {
|
||
if (builtin.mode == .Debug) {
|
||
@breakpoint();
|
||
}
|
||
windows.kernel32.ExitProcess(3);
|
||
}
|
||
if (!builtin.link_libc and builtin.os.tag == .linux) {
|
||
raise(SIGABRT) catch {};
|
||
|
||
// TODO the rest of the implementation of abort() from musl libc here
|
||
|
||
raise(SIGKILL) catch {};
|
||
exit(127);
|
||
}
|
||
if (builtin.os.tag == .uefi) {
|
||
exit(0); // TODO choose appropriate exit code
|
||
}
|
||
if (builtin.os.tag == .wasi) {
|
||
@breakpoint();
|
||
exit(1);
|
||
}
|
||
|
||
system.abort();
|
||
}
|
||
|
||
pub const RaiseError = UnexpectedError;
|
||
|
||
pub fn raise(sig: u8) RaiseError!void {
|
||
if (builtin.link_libc) {
|
||
switch (errno(system.raise(sig))) {
|
||
0 => return,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
|
||
if (builtin.os.tag == .linux) {
|
||
var set: linux.sigset_t = undefined;
|
||
// block application signals
|
||
_ = linux.sigprocmask(SIG_BLOCK, &linux.app_mask, &set);
|
||
|
||
const tid = linux.gettid();
|
||
const rc = linux.tkill(tid, sig);
|
||
|
||
// restore signal mask
|
||
_ = linux.sigprocmask(SIG_SETMASK, &set, null);
|
||
|
||
switch (errno(rc)) {
|
||
0 => return,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
|
||
@compileError("std.os.raise unimplemented for this target");
|
||
}
|
||
|
||
pub const KillError = error{PermissionDenied} || UnexpectedError;
|
||
|
||
pub fn kill(pid: pid_t, sig: u8) KillError!void {
|
||
switch (errno(system.kill(pid, sig))) {
|
||
0 => return,
|
||
EINVAL => unreachable, // invalid signal
|
||
EPERM => return error.PermissionDenied,
|
||
ESRCH => unreachable, // always a race condition
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
|
||
/// Exits the program cleanly with the specified status code.
|
||
pub fn exit(status: u8) noreturn {
|
||
if (builtin.link_libc) {
|
||
system.exit(status);
|
||
}
|
||
if (builtin.os.tag == .windows) {
|
||
windows.kernel32.ExitProcess(status);
|
||
}
|
||
if (builtin.os.tag == .wasi) {
|
||
wasi.proc_exit(status);
|
||
}
|
||
if (builtin.os.tag == .linux and !builtin.single_threaded) {
|
||
linux.exit_group(status);
|
||
}
|
||
if (builtin.os.tag == .uefi) {
|
||
// exit() is only avaliable if exitBootServices() has not been called yet.
|
||
// This call to exit should not fail, so we don't care about its return value.
|
||
if (uefi.system_table.boot_services) |bs| {
|
||
_ = bs.exit(uefi.handle, @intToEnum(uefi.Status, status), 0, null);
|
||
}
|
||
// If we can't exit, reboot the system instead.
|
||
uefi.system_table.runtime_services.resetSystem(uefi.tables.ResetType.ResetCold, @intToEnum(uefi.Status, status), 0, null);
|
||
}
|
||
system.exit(status);
|
||
}
|
||
|
||
pub const ReadError = error{
|
||
InputOutput,
|
||
SystemResources,
|
||
IsDir,
|
||
OperationAborted,
|
||
BrokenPipe,
|
||
ConnectionResetByPeer,
|
||
|
||
/// This error occurs when no global event loop is configured,
|
||
/// and reading from the file descriptor would block.
|
||
WouldBlock,
|
||
} || UnexpectedError;
|
||
|
||
/// Returns the number of bytes that were read, which can be less than
|
||
/// buf.len. If 0 bytes were read, that means EOF.
|
||
/// If the application has a global event loop enabled, EAGAIN is handled
|
||
/// via the event loop. Otherwise EAGAIN results in error.WouldBlock.
|
||
///
|
||
/// Linux has a limit on how many bytes may be transferred in one `read` call, which is `0x7ffff000`
|
||
/// on both 64-bit and 32-bit systems. This is due to using a signed C int as the return value, as
|
||
/// well as stuffing the errno codes into the last `4096` values. This is noted on the `read` man page.
|
||
/// For POSIX the limit is `math.maxInt(isize)`.
|
||
pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
|
||
if (builtin.os.tag == .windows) {
|
||
return windows.ReadFile(fd, buf, null);
|
||
}
|
||
|
||
if (builtin.os.tag == .wasi and !builtin.link_libc) {
|
||
const iovs = [1]iovec{iovec{
|
||
.iov_base = buf.ptr,
|
||
.iov_len = buf.len,
|
||
}};
|
||
|
||
var nread: usize = undefined;
|
||
switch (wasi.fd_read(fd, &iovs, iovs.len, &nread)) {
|
||
0 => return nread,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
|
||
// Prevents EINVAL.
|
||
const max_count = switch (std.Target.current.os.tag) {
|
||
.linux => 0x7ffff000,
|
||
else => math.maxInt(isize),
|
||
};
|
||
const adjusted_len = math.min(max_count, buf.len);
|
||
|
||
while (true) {
|
||
const rc = system.read(fd, buf.ptr, adjusted_len);
|
||
switch (errno(rc)) {
|
||
0 => return @intCast(usize, rc),
|
||
EINTR => continue,
|
||
EINVAL => unreachable,
|
||
EFAULT => unreachable,
|
||
EAGAIN => if (std.event.Loop.instance) |loop| {
|
||
loop.waitUntilFdReadable(fd);
|
||
continue;
|
||
} else {
|
||
return error.WouldBlock;
|
||
},
|
||
EBADF => unreachable, // Always a race condition.
|
||
EIO => return error.InputOutput,
|
||
EISDIR => return error.IsDir,
|
||
ENOBUFS => return error.SystemResources,
|
||
ENOMEM => return error.SystemResources,
|
||
ECONNRESET => return error.ConnectionResetByPeer,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
return index;
|
||
}
|
||
|
||
/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.
|
||
///
|
||
/// For POSIX systems, if the application has a global event loop enabled, EAGAIN is handled
|
||
/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`.
|
||
/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
|
||
/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
|
||
///
|
||
/// This operation is non-atomic on the following systems:
|
||
/// * Windows
|
||
/// On these systems, the read races with concurrent writes to the same file descriptor.
|
||
pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {
|
||
if (std.Target.current.os.tag == .windows) {
|
||
// TODO does Windows have a way to read an io vector?
|
||
if (iov.len == 0) return @as(usize, 0);
|
||
const first = iov[0];
|
||
return read(fd, first.iov_base[0..first.iov_len]);
|
||
}
|
||
|
||
while (true) {
|
||
// TODO handle the case when iov_len is too large and get rid of this @intCast
|
||
const rc = system.readv(fd, iov.ptr, iov_count);
|
||
switch (errno(rc)) {
|
||
0 => return @intCast(usize, rc),
|
||
EINTR => continue,
|
||
EINVAL => unreachable,
|
||
EFAULT => unreachable,
|
||
EAGAIN => if (std.event.Loop.instance) |loop| {
|
||
loop.waitUntilFdReadable(fd);
|
||
continue;
|
||
} else {
|
||
return error.WouldBlock;
|
||
},
|
||
EBADF => unreachable, // always a race condition
|
||
EIO => return error.InputOutput,
|
||
EISDIR => return error.IsDir,
|
||
ENOBUFS => return error.SystemResources,
|
||
ENOMEM => return error.SystemResources,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
}
|
||
|
||
pub const PReadError = ReadError || error{Unseekable};
|
||
|
||
/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.
|
||
///
|
||
/// Retries when interrupted by a signal.
|
||
///
|
||
/// For POSIX systems, if the application has a global event loop enabled, EAGAIN is handled
|
||
/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`.
|
||
/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
|
||
/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
|
||
pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize {
|
||
if (builtin.os.tag == .windows) {
|
||
return windows.ReadFile(fd, buf, offset);
|
||
}
|
||
|
||
while (true) {
|
||
const rc = system.pread(fd, buf.ptr, buf.len, offset);
|
||
switch (errno(rc)) {
|
||
0 => return @intCast(usize, rc),
|
||
EINTR => continue,
|
||
EINVAL => unreachable,
|
||
EFAULT => unreachable,
|
||
EAGAIN => if (std.event.Loop.instance) |loop| {
|
||
loop.waitUntilFdReadable(fd);
|
||
continue;
|
||
} else {
|
||
return error.WouldBlock;
|
||
},
|
||
EBADF => unreachable, // Always a race condition.
|
||
EIO => return error.InputOutput,
|
||
EISDIR => return error.IsDir,
|
||
ENOBUFS => return error.SystemResources,
|
||
ENOMEM => return error.SystemResources,
|
||
ECONNRESET => return error.ConnectionResetByPeer,
|
||
ENXIO => return error.Unseekable,
|
||
ESPIPE => return error.Unseekable,
|
||
EOVERFLOW => return error.Unseekable,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
return index;
|
||
}
|
||
|
||
pub const TruncateError = error{
|
||
FileTooBig,
|
||
InputOutput,
|
||
CannotTruncate,
|
||
FileBusy,
|
||
} || UnexpectedError;
|
||
|
||
pub fn ftruncate(fd: fd_t, length: u64) TruncateError!void {
|
||
if (std.Target.current.os.tag == .windows) {
|
||
var io_status_block: windows.IO_STATUS_BLOCK = undefined;
|
||
var eof_info = windows.FILE_END_OF_FILE_INFORMATION{
|
||
.EndOfFile = @bitCast(windows.LARGE_INTEGER, length),
|
||
};
|
||
|
||
const rc = windows.ntdll.NtSetInformationFile(
|
||
fd,
|
||
&io_status_block,
|
||
&eof_info,
|
||
@sizeOf(windows.FILE_END_OF_FILE_INFORMATION),
|
||
.FileEndOfFileInformation,
|
||
);
|
||
|
||
switch (rc) {
|
||
.SUCCESS => return,
|
||
.INVALID_HANDLE => unreachable, // Handle not open for writing
|
||
.ACCESS_DENIED => return error.CannotTruncate,
|
||
else => return windows.unexpectedStatus(rc),
|
||
}
|
||
}
|
||
|
||
while (true) {
|
||
const rc = if (builtin.link_libc)
|
||
if (std.Target.current.os.tag == .linux)
|
||
system.ftruncate64(fd, @bitCast(off_t, length))
|
||
else
|
||
system.ftruncate(fd, @bitCast(off_t, length))
|
||
else
|
||
system.ftruncate(fd, length);
|
||
|
||
switch (errno(rc)) {
|
||
0 => return,
|
||
EINTR => continue,
|
||
EFBIG => return error.FileTooBig,
|
||
EIO => return error.InputOutput,
|
||
EPERM => return error.CannotTruncate,
|
||
ETXTBSY => return error.FileBusy,
|
||
EBADF => unreachable, // Handle not open for writing
|
||
EINVAL => unreachable, // Handle not open for writing
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.
|
||
///
|
||
/// Retries when interrupted by a signal.
|
||
///
|
||
/// For POSIX systems, if the application has a global event loop enabled, EAGAIN is handled
|
||
/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`.
|
||
/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
|
||
/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
|
||
///
|
||
/// This operation is non-atomic on the following systems:
|
||
/// * Darwin
|
||
/// * Windows
|
||
/// On these systems, the read races with concurrent writes to the same file descriptor.
|
||
pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) PReadError!usize {
|
||
const have_pread_but_not_preadv = switch (std.Target.current.os.tag) {
|
||
.windows, .macosx, .ios, .watchos, .tvos => true,
|
||
else => false,
|
||
};
|
||
if (have_pread_but_not_preadv) {
|
||
// We could loop here; but proper usage of `preadv` must handle partial reads anyway.
|
||
// So we simply read into the first vector only.
|
||
if (iov.len == 0) return @as(usize, 0);
|
||
const first = iov[0];
|
||
return pread(fd, first.iov_base[0..first.iov_len], offset);
|
||
}
|
||
|
||
const iov_count = math.cast(u31, iov.len) catch math.maxInt(u31);
|
||
|
||
while (true) {
|
||
const rc = system.preadv(fd, iov.ptr, iov_count, offset);
|
||
switch (errno(rc)) {
|
||
0 => return @bitCast(usize, rc),
|
||
EINTR => continue,
|
||
EINVAL => unreachable,
|
||
EFAULT => unreachable,
|
||
EAGAIN => if (std.event.Loop.instance) |loop| {
|
||
loop.waitUntilFdReadable(fd);
|
||
continue;
|
||
} else {
|
||
return error.WouldBlock;
|
||
},
|
||
EBADF => unreachable, // always a race condition
|
||
EIO => return error.InputOutput,
|
||
EISDIR => return error.IsDir,
|
||
ENOBUFS => return error.SystemResources,
|
||
ENOMEM => return error.SystemResources,
|
||
ENXIO => return error.Unseekable,
|
||
ESPIPE => return error.Unseekable,
|
||
EOVERFLOW => return error.Unseekable,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
}
|
||
|
||
pub const WriteError = error{
|
||
DiskQuota,
|
||
FileTooBig,
|
||
InputOutput,
|
||
NoSpaceLeft,
|
||
AccessDenied,
|
||
BrokenPipe,
|
||
SystemResources,
|
||
OperationAborted,
|
||
|
||
/// This error occurs when no global event loop is configured,
|
||
/// and reading from the file descriptor would block.
|
||
WouldBlock,
|
||
} || UnexpectedError;
|
||
|
||
/// Write to a file descriptor.
|
||
/// Retries when interrupted by a signal.
|
||
/// Returns the number of bytes written. If nonzero bytes were supplied, this will be nonzero.
|
||
///
|
||
/// Note that a successful write() may transfer fewer than count bytes. Such partial writes can
|
||
/// occur for various reasons; for example, because there was insufficient space on the disk
|
||
/// device to write all of the requested bytes, or because a blocked write() to a socket, pipe, or
|
||
/// similar was interrupted by a signal handler after it had transferred some, but before it had
|
||
/// transferred all of the requested bytes. In the event of a partial write, the caller can make
|
||
/// another write() call to transfer the remaining bytes. The subsequent call will either
|
||
/// transfer further bytes or may result in an error (e.g., if the disk is now full).
|
||
///
|
||
/// For POSIX systems, if the application has a global event loop enabled, EAGAIN is handled
|
||
/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`.
|
||
/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
|
||
/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
|
||
///
|
||
/// Linux has a limit on how many bytes may be transferred in one `write` call, which is `0x7ffff000`
|
||
/// on both 64-bit and 32-bit systems. This is due to using a signed C int as the return value, as
|
||
/// well as stuffing the errno codes into the last `4096` values. This is noted on the `write` man page.
|
||
/// The corresponding POSIX limit is `math.maxInt(isize)`.
|
||
pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize {
|
||
if (builtin.os.tag == .windows) {
|
||
return windows.WriteFile(fd, bytes, null);
|
||
}
|
||
|
||
if (builtin.os.tag == .wasi and !builtin.link_libc) {
|
||
const ciovs = [1]iovec_const{iovec_const{
|
||
.iov_base = bytes.ptr,
|
||
.iov_len = bytes.len,
|
||
}};
|
||
var nwritten: usize = undefined;
|
||
switch (wasi.fd_write(fd, &ciovs, ciovs.len, &nwritten)) {
|
||
0 => return nwritten,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
|
||
const max_count = switch (std.Target.current.os.tag) {
|
||
.linux => 0x7ffff000,
|
||
else => math.maxInt(isize),
|
||
};
|
||
const adjusted_len = math.min(max_count, bytes.len);
|
||
|
||
while (true) {
|
||
const rc = system.write(fd, bytes.ptr, adjusted_len);
|
||
switch (errno(rc)) {
|
||
0 => return @intCast(usize, rc),
|
||
EINTR => continue,
|
||
EINVAL => unreachable,
|
||
EFAULT => unreachable,
|
||
EAGAIN => if (std.event.Loop.instance) |loop| {
|
||
loop.waitUntilFdWritable(fd);
|
||
continue;
|
||
} else {
|
||
return error.WouldBlock;
|
||
},
|
||
EBADF => unreachable, // Always a race condition.
|
||
EDESTADDRREQ => unreachable, // `connect` was never called.
|
||
EDQUOT => return error.DiskQuota,
|
||
EFBIG => return error.FileTooBig,
|
||
EIO => return error.InputOutput,
|
||
ENOSPC => return error.NoSpaceLeft,
|
||
EPERM => return error.AccessDenied,
|
||
EPIPE => return error.BrokenPipe,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Write multiple buffers to a file descriptor.
|
||
/// Retries when interrupted by a signal.
|
||
/// Returns the number of bytes written. If nonzero bytes were supplied, this will be nonzero.
|
||
///
|
||
/// Note that a successful write() may transfer fewer bytes than supplied. Such partial writes can
|
||
/// occur for various reasons; for example, because there was insufficient space on the disk
|
||
/// device to write all of the requested bytes, or because a blocked write() to a socket, pipe, or
|
||
/// similar was interrupted by a signal handler after it had transferred some, but before it had
|
||
/// transferred all of the requested bytes. In the event of a partial write, the caller can make
|
||
/// another write() call to transfer the remaining bytes. The subsequent call will either
|
||
/// transfer further bytes or may result in an error (e.g., if the disk is now full).
|
||
///
|
||
/// For POSIX systems, if the application has a global event loop enabled, EAGAIN is handled
|
||
/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`.
|
||
/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
|
||
/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
|
||
///
|
||
/// If `iov.len` is larger than will fit in a `u31`, a partial write will occur.
|
||
pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!usize {
|
||
if (std.Target.current.os.tag == .windows) {
|
||
// TODO does Windows have a way to write an io vector?
|
||
if (iov.len == 0) return @as(usize, 0);
|
||
const first = iov[0];
|
||
return write(fd, first.iov_base[0..first.iov_len]);
|
||
}
|
||
|
||
const iov_count = math.cast(u31, iov.len) catch math.maxInt(u31);
|
||
while (true) {
|
||
const rc = system.writev(fd, iov.ptr, iov_count);
|
||
switch (errno(rc)) {
|
||
0 => return @intCast(usize, rc),
|
||
EINTR => continue,
|
||
EINVAL => unreachable,
|
||
EFAULT => unreachable,
|
||
EAGAIN => if (std.event.Loop.instance) |loop| {
|
||
loop.waitUntilFdWritable(fd);
|
||
continue;
|
||
} else {
|
||
return error.WouldBlock;
|
||
},
|
||
EBADF => unreachable, // Always a race condition.
|
||
EDESTADDRREQ => unreachable, // `connect` was never called.
|
||
EDQUOT => return error.DiskQuota,
|
||
EFBIG => return error.FileTooBig,
|
||
EIO => return error.InputOutput,
|
||
ENOSPC => return error.NoSpaceLeft,
|
||
EPERM => return error.AccessDenied,
|
||
EPIPE => return error.BrokenPipe,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
}
|
||
|
||
pub const PWriteError = WriteError || error{Unseekable};
|
||
|
||
/// Write to a file descriptor, with a position offset.
|
||
/// Retries when interrupted by a signal.
|
||
/// Returns the number of bytes written. If nonzero bytes were supplied, this will be nonzero.
|
||
///
|
||
/// Note that a successful write() may transfer fewer bytes than supplied. Such partial writes can
|
||
/// occur for various reasons; for example, because there was insufficient space on the disk
|
||
/// device to write all of the requested bytes, or because a blocked write() to a socket, pipe, or
|
||
/// similar was interrupted by a signal handler after it had transferred some, but before it had
|
||
/// transferred all of the requested bytes. In the event of a partial write, the caller can make
|
||
/// another write() call to transfer the remaining bytes. The subsequent call will either
|
||
/// transfer further bytes or may result in an error (e.g., if the disk is now full).
|
||
///
|
||
/// For POSIX systems, if the application has a global event loop enabled, EAGAIN is handled
|
||
/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`.
|
||
/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
|
||
/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
|
||
///
|
||
/// Linux has a limit on how many bytes may be transferred in one `pwrite` call, which is `0x7ffff000`
|
||
/// on both 64-bit and 32-bit systems. This is due to using a signed C int as the return value, as
|
||
/// well as stuffing the errno codes into the last `4096` values. This is noted on the `write` man page.
|
||
/// The corresponding POSIX limit is `math.maxInt(isize)`.
|
||
pub fn pwrite(fd: fd_t, bytes: []const u8, offset: u64) PWriteError!usize {
|
||
if (std.Target.current.os.tag == .windows) {
|
||
return windows.WriteFile(fd, bytes, offset);
|
||
}
|
||
|
||
// Prevent EINVAL.
|
||
const max_count = switch (std.Target.current.os.tag) {
|
||
.linux => 0x7ffff000,
|
||
else => math.maxInt(isize),
|
||
};
|
||
const adjusted_len = math.min(max_count, bytes.len);
|
||
|
||
while (true) {
|
||
const rc = system.pwrite(fd, bytes.ptr, adjusted_len, offset);
|
||
switch (errno(rc)) {
|
||
0 => return @intCast(usize, rc),
|
||
EINTR => continue,
|
||
EINVAL => unreachable,
|
||
EFAULT => unreachable,
|
||
EAGAIN => if (std.event.Loop.instance) |loop| {
|
||
loop.waitUntilFdWritable(fd);
|
||
continue;
|
||
} else {
|
||
return error.WouldBlock;
|
||
},
|
||
EBADF => unreachable, // Always a race condition.
|
||
EDESTADDRREQ => unreachable, // `connect` was never called.
|
||
EDQUOT => return error.DiskQuota,
|
||
EFBIG => return error.FileTooBig,
|
||
EIO => return error.InputOutput,
|
||
ENOSPC => return error.NoSpaceLeft,
|
||
EPERM => return error.AccessDenied,
|
||
EPIPE => return error.BrokenPipe,
|
||
ENXIO => return error.Unseekable,
|
||
ESPIPE => return error.Unseekable,
|
||
EOVERFLOW => return error.Unseekable,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Write multiple buffers to a file descriptor, with a position offset.
|
||
/// Retries when interrupted by a signal.
|
||
/// Returns the number of bytes written. If nonzero bytes were supplied, this will be nonzero.
|
||
///
|
||
/// Note that a successful write() may transfer fewer than count bytes. Such partial writes can
|
||
/// occur for various reasons; for example, because there was insufficient space on the disk
|
||
/// device to write all of the requested bytes, or because a blocked write() to a socket, pipe, or
|
||
/// similar was interrupted by a signal handler after it had transferred some, but before it had
|
||
/// transferred all of the requested bytes. In the event of a partial write, the caller can make
|
||
/// another write() call to transfer the remaining bytes. The subsequent call will either
|
||
/// transfer further bytes or may result in an error (e.g., if the disk is now full).
|
||
///
|
||
/// If the application has a global event loop enabled, EAGAIN is handled
|
||
/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`.
|
||
///
|
||
/// The following systems do not have this syscall, and will return partial writes if more than one
|
||
/// vector is provided:
|
||
/// * Darwin
|
||
/// * Windows
|
||
///
|
||
/// If `iov.len` is larger than will fit in a `u31`, a partial write will occur.
|
||
pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) PWriteError!usize {
|
||
const have_pwrite_but_not_pwritev = switch (std.Target.current.os.tag) {
|
||
.windows, .macosx, .ios, .watchos, .tvos => true,
|
||
else => false,
|
||
};
|
||
|
||
if (have_pwrite_but_not_pwritev) {
|
||
// We could loop here; but proper usage of `pwritev` must handle partial writes anyway.
|
||
// So we simply write the first vector only.
|
||
if (iov.len == 0) return @as(usize, 0);
|
||
const first = iov[0];
|
||
return pwrite(fd, first.iov_base[0..first.iov_len], offset);
|
||
}
|
||
|
||
const iov_count = math.cast(u31, iov.len) catch math.maxInt(u31);
|
||
while (true) {
|
||
const rc = system.pwritev(fd, iov.ptr, iov_count, offset);
|
||
switch (errno(rc)) {
|
||
0 => return @intCast(usize, rc),
|
||
EINTR => continue,
|
||
EINVAL => unreachable,
|
||
EFAULT => unreachable,
|
||
EAGAIN => if (std.event.Loop.instance) |loop| {
|
||
loop.waitUntilFdWritable(fd);
|
||
continue;
|
||
} else {
|
||
return error.WouldBlock;
|
||
},
|
||
EBADF => unreachable, // Always a race condition.
|
||
EDESTADDRREQ => unreachable, // `connect` was never called.
|
||
EDQUOT => return error.DiskQuota,
|
||
EFBIG => return error.FileTooBig,
|
||
EIO => return error.InputOutput,
|
||
ENOSPC => return error.NoSpaceLeft,
|
||
EPERM => return error.AccessDenied,
|
||
EPIPE => return error.BrokenPipe,
|
||
ENXIO => return error.Unseekable,
|
||
ESPIPE => return error.Unseekable,
|
||
EOVERFLOW => return error.Unseekable,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
}
|
||
|
||
pub const OpenError = error{
|
||
AccessDenied,
|
||
SymLinkLoop,
|
||
ProcessFdQuotaExceeded,
|
||
SystemFdQuotaExceeded,
|
||
NoDevice,
|
||
FileNotFound,
|
||
|
||
/// The path exceeded `MAX_PATH_BYTES` bytes.
|
||
NameTooLong,
|
||
|
||
/// Insufficient kernel memory was available, or
|
||
/// the named file is a FIFO and per-user hard limit on
|
||
/// memory allocation for pipes has been reached.
|
||
SystemResources,
|
||
|
||
/// The file is too large to be opened. This error is unreachable
|
||
/// for 64-bit targets, as well as when opening directories.
|
||
FileTooBig,
|
||
|
||
/// The path refers to directory but the `O_DIRECTORY` flag was not provided.
|
||
IsDir,
|
||
|
||
/// A new path cannot be created because the device has no room for the new file.
|
||
/// This error is only reachable when the `O_CREAT` flag is provided.
|
||
NoSpaceLeft,
|
||
|
||
/// A component used as a directory in the path was not, in fact, a directory, or
|
||
/// `O_DIRECTORY` was specified and the path was not a directory.
|
||
NotDir,
|
||
|
||
/// The path already exists and the `O_CREAT` and `O_EXCL` flags were provided.
|
||
PathAlreadyExists,
|
||
DeviceBusy,
|
||
} || UnexpectedError;
|
||
|
||
/// Open and possibly create a file. Keeps trying if it gets interrupted.
|
||
/// See also `openC`.
|
||
/// TODO support windows
|
||
pub fn open(file_path: []const u8, flags: u32, perm: usize) OpenError!fd_t {
|
||
const file_path_c = try toPosixPath(file_path);
|
||
return openC(&file_path_c, flags, perm);
|
||
}
|
||
|
||
/// Open and possibly create a file. Keeps trying if it gets interrupted.
|
||
/// See also `open`.
|
||
/// TODO support windows
|
||
pub fn openC(file_path: [*:0]const u8, flags: u32, perm: usize) OpenError!fd_t {
|
||
while (true) {
|
||
const rc = system.open(file_path, flags, perm);
|
||
switch (errno(rc)) {
|
||
0 => return @intCast(fd_t, rc),
|
||
EINTR => continue,
|
||
|
||
EFAULT => unreachable,
|
||
EINVAL => unreachable,
|
||
EACCES => return error.AccessDenied,
|
||
EFBIG => return error.FileTooBig,
|
||
EOVERFLOW => return error.FileTooBig,
|
||
EISDIR => return error.IsDir,
|
||
ELOOP => return error.SymLinkLoop,
|
||
EMFILE => return error.ProcessFdQuotaExceeded,
|
||
ENAMETOOLONG => return error.NameTooLong,
|
||
ENFILE => return error.SystemFdQuotaExceeded,
|
||
ENODEV => return error.NoDevice,
|
||
ENOENT => return error.FileNotFound,
|
||
ENOMEM => return error.SystemResources,
|
||
ENOSPC => return error.NoSpaceLeft,
|
||
ENOTDIR => return error.NotDir,
|
||
EPERM => return error.AccessDenied,
|
||
EEXIST => return error.PathAlreadyExists,
|
||
EBUSY => return error.DeviceBusy,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Open and possibly create a file. Keeps trying if it gets interrupted.
|
||
/// `file_path` is relative to the open directory handle `dir_fd`.
|
||
/// See also `openatC`.
|
||
/// TODO support windows
|
||
pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: mode_t) OpenError!fd_t {
|
||
const file_path_c = try toPosixPath(file_path);
|
||
return openatC(dir_fd, &file_path_c, flags, mode);
|
||
}
|
||
|
||
/// Open and possibly create a file. Keeps trying if it gets interrupted.
|
||
/// `file_path` is relative to the open directory handle `dir_fd`.
|
||
/// See also `openat`.
|
||
/// TODO support windows
|
||
pub fn openatC(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: mode_t) OpenError!fd_t {
|
||
while (true) {
|
||
const rc = system.openat(dir_fd, file_path, flags, mode);
|
||
switch (errno(rc)) {
|
||
0 => return @intCast(fd_t, rc),
|
||
EINTR => continue,
|
||
|
||
EFAULT => unreachable,
|
||
EINVAL => unreachable,
|
||
EACCES => return error.AccessDenied,
|
||
EFBIG => return error.FileTooBig,
|
||
EOVERFLOW => return error.FileTooBig,
|
||
EISDIR => return error.IsDir,
|
||
ELOOP => return error.SymLinkLoop,
|
||
EMFILE => return error.ProcessFdQuotaExceeded,
|
||
ENAMETOOLONG => return error.NameTooLong,
|
||
ENFILE => return error.SystemFdQuotaExceeded,
|
||
ENODEV => return error.NoDevice,
|
||
ENOENT => return error.FileNotFound,
|
||
ENOMEM => return error.SystemResources,
|
||
ENOSPC => return error.NoSpaceLeft,
|
||
ENOTDIR => return error.NotDir,
|
||
EPERM => return error.AccessDenied,
|
||
EEXIST => return error.PathAlreadyExists,
|
||
EBUSY => return error.DeviceBusy,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
}
|
||
|
||
pub fn dup2(old_fd: fd_t, new_fd: fd_t) !void {
|
||
while (true) {
|
||
switch (errno(system.dup2(old_fd, new_fd))) {
|
||
0 => return,
|
||
EBUSY, EINTR => continue,
|
||
EMFILE => return error.ProcessFdQuotaExceeded,
|
||
EINVAL => unreachable, // invalid parameters passed to dup2
|
||
EBADF => unreachable, // always a race condition
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
}
|
||
|
||
pub const ExecveError = error{
|
||
SystemResources,
|
||
AccessDenied,
|
||
InvalidExe,
|
||
FileSystem,
|
||
IsDir,
|
||
FileNotFound,
|
||
NotDir,
|
||
FileBusy,
|
||
ProcessFdQuotaExceeded,
|
||
SystemFdQuotaExceeded,
|
||
NameTooLong,
|
||
} || UnexpectedError;
|
||
|
||
/// Deprecated in favor of `execveZ`.
|
||
pub const execveC = execveZ;
|
||
|
||
/// Like `execve` except the parameters are null-terminated,
|
||
/// matching the syscall API on all targets. This removes the need for an allocator.
|
||
/// This function ignores PATH environment variable. See `execvpeZ` for that.
|
||
pub fn execveZ(
|
||
path: [*:0]const u8,
|
||
child_argv: [*:null]const ?[*:0]const u8,
|
||
envp: [*:null]const ?[*:0]const u8,
|
||
) ExecveError {
|
||
switch (errno(system.execve(path, child_argv, envp))) {
|
||
0 => unreachable,
|
||
EFAULT => unreachable,
|
||
E2BIG => return error.SystemResources,
|
||
EMFILE => return error.ProcessFdQuotaExceeded,
|
||
ENAMETOOLONG => return error.NameTooLong,
|
||
ENFILE => return error.SystemFdQuotaExceeded,
|
||
ENOMEM => return error.SystemResources,
|
||
EACCES => return error.AccessDenied,
|
||
EPERM => return error.AccessDenied,
|
||
EINVAL => return error.InvalidExe,
|
||
ENOEXEC => return error.InvalidExe,
|
||
EIO => return error.FileSystem,
|
||
ELOOP => return error.FileSystem,
|
||
EISDIR => return error.IsDir,
|
||
ENOENT => return error.FileNotFound,
|
||
ENOTDIR => return error.NotDir,
|
||
ETXTBSY => return error.FileBusy,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
|
||
/// Deprecated in favor of `execvpeZ`.
|
||
pub const execvpeC = execvpeZ;
|
||
|
||
pub const Arg0Expand = enum {
|
||
expand,
|
||
no_expand,
|
||
};
|
||
|
||
/// Like `execvpeZ` except if `arg0_expand` is `.expand`, then `argv` is mutable,
|
||
/// and `argv[0]` is expanded to be the same absolute path that is passed to the execve syscall.
|
||
/// If this function returns with an error, `argv[0]` will be restored to the value it was when it was passed in.
|
||
pub fn execvpeZ_expandArg0(
|
||
comptime arg0_expand: Arg0Expand,
|
||
file: [*:0]const u8,
|
||
child_argv: switch (arg0_expand) {
|
||
.expand => [*:null]?[*:0]const u8,
|
||
.no_expand => [*:null]const ?[*:0]const u8,
|
||
},
|
||
envp: [*:null]const ?[*:0]const u8,
|
||
) ExecveError {
|
||
const file_slice = mem.toSliceConst(u8, file);
|
||
if (mem.indexOfScalar(u8, file_slice, '/') != null) return execveZ(file, child_argv, envp);
|
||
|
||
const PATH = getenvZ("PATH") orelse "/usr/local/bin:/bin/:/usr/bin";
|
||
var path_buf: [MAX_PATH_BYTES]u8 = undefined;
|
||
var it = mem.tokenize(PATH, ":");
|
||
var seen_eacces = false;
|
||
var err: ExecveError = undefined;
|
||
|
||
// In case of expanding arg0 we must put it back if we return with an error.
|
||
const prev_arg0 = child_argv[0];
|
||
defer switch (arg0_expand) {
|
||
.expand => child_argv[0] = prev_arg0,
|
||
.no_expand => {},
|
||
};
|
||
|
||
while (it.next()) |search_path| {
|
||
if (path_buf.len < search_path.len + file_slice.len + 1) return error.NameTooLong;
|
||
mem.copy(u8, &path_buf, search_path);
|
||
path_buf[search_path.len] = '/';
|
||
mem.copy(u8, path_buf[search_path.len + 1 ..], file_slice);
|
||
const path_len = search_path.len + file_slice.len + 1;
|
||
path_buf[path_len] = 0;
|
||
const full_path = path_buf[0..path_len :0].ptr;
|
||
switch (arg0_expand) {
|
||
.expand => child_argv[0] = full_path,
|
||
.no_expand => {},
|
||
}
|
||
err = execveZ(full_path, child_argv, envp);
|
||
switch (err) {
|
||
error.AccessDenied => seen_eacces = true,
|
||
error.FileNotFound, error.NotDir => {},
|
||
else => |e| return e,
|
||
}
|
||
}
|
||
if (seen_eacces) return error.AccessDenied;
|
||
return err;
|
||
}
|
||
|
||
/// Like `execvpe` except the parameters are null-terminated,
|
||
/// matching the syscall API on all targets. This removes the need for an allocator.
|
||
/// This function also uses the PATH environment variable to get the full path to the executable.
|
||
/// If `file` is an absolute path, this is the same as `execveZ`.
|
||
pub fn execvpeZ(
|
||
file: [*:0]const u8,
|
||
argv: [*:null]const ?[*:0]const u8,
|
||
envp: [*:null]const ?[*:0]const u8,
|
||
) ExecveError {
|
||
return execvpeZ_expandArg0(.no_expand, file, argv, envp);
|
||
}
|
||
|
||
/// This is the same as `execvpe` except if the `arg0_expand` parameter is set to `.expand`,
|
||
/// then argv[0] will be replaced with the expanded version of it, after resolving in accordance
|
||
/// with the PATH environment variable.
|
||
pub fn execvpe_expandArg0(
|
||
allocator: *mem.Allocator,
|
||
arg0_expand: Arg0Expand,
|
||
argv_slice: []const []const u8,
|
||
env_map: *const std.BufMap,
|
||
) (ExecveError || error{OutOfMemory}) {
|
||
const argv_buf = try allocator.alloc(?[*:0]u8, argv_slice.len + 1);
|
||
mem.set(?[*:0]u8, argv_buf, null);
|
||
defer {
|
||
for (argv_buf) |arg| {
|
||
const arg_buf = if (arg) |ptr| mem.toSlice(u8, ptr) else break;
|
||
allocator.free(arg_buf);
|
||
}
|
||
allocator.free(argv_buf);
|
||
}
|
||
for (argv_slice) |arg, i| {
|
||
const arg_buf = try allocator.alloc(u8, arg.len + 1);
|
||
@memcpy(arg_buf.ptr, arg.ptr, arg.len);
|
||
arg_buf[arg.len] = 0;
|
||
argv_buf[i] = arg_buf[0..arg.len :0].ptr;
|
||
}
|
||
argv_buf[argv_slice.len] = null;
|
||
const argv_ptr = argv_buf[0..argv_slice.len :null].ptr;
|
||
|
||
const envp_buf = try createNullDelimitedEnvMap(allocator, env_map);
|
||
defer freeNullDelimitedEnvMap(allocator, envp_buf);
|
||
|
||
switch (arg0_expand) {
|
||
.expand => return execvpeZ_expandArg0(.expand, argv_buf.ptr[0].?, argv_ptr, envp_buf.ptr),
|
||
.no_expand => return execvpeZ_expandArg0(.no_expand, argv_buf.ptr[0].?, argv_ptr, envp_buf.ptr),
|
||
}
|
||
}
|
||
|
||
/// This function must allocate memory to add a null terminating bytes on path and each arg.
|
||
/// It must also convert to KEY=VALUE\0 format for environment variables, and include null
|
||
/// pointers after the args and after the environment variables.
|
||
/// `argv_slice[0]` is the executable path.
|
||
/// This function also uses the PATH environment variable to get the full path to the executable.
|
||
pub fn execvpe(
|
||
allocator: *mem.Allocator,
|
||
argv_slice: []const []const u8,
|
||
env_map: *const std.BufMap,
|
||
) (ExecveError || error{OutOfMemory}) {
|
||
return execvpe_expandArg0(allocator, .no_expand, argv_slice, env_map);
|
||
}
|
||
|
||
pub fn createNullDelimitedEnvMap(allocator: *mem.Allocator, env_map: *const std.BufMap) ![:null]?[*:0]u8 {
|
||
const envp_count = env_map.count();
|
||
const envp_buf = try allocator.alloc(?[*:0]u8, envp_count + 1);
|
||
mem.set(?[*:0]u8, envp_buf, null);
|
||
errdefer freeNullDelimitedEnvMap(allocator, envp_buf);
|
||
{
|
||
var it = env_map.iterator();
|
||
var i: usize = 0;
|
||
while (it.next()) |pair| : (i += 1) {
|
||
const env_buf = try allocator.alloc(u8, pair.key.len + pair.value.len + 2);
|
||
@memcpy(env_buf.ptr, pair.key.ptr, pair.key.len);
|
||
env_buf[pair.key.len] = '=';
|
||
@memcpy(env_buf.ptr + pair.key.len + 1, pair.value.ptr, pair.value.len);
|
||
const len = env_buf.len - 1;
|
||
env_buf[len] = 0;
|
||
envp_buf[i] = env_buf[0..len :0].ptr;
|
||
}
|
||
assert(i == envp_count);
|
||
}
|
||
return envp_buf[0..envp_count :null];
|
||
}
|
||
|
||
pub fn freeNullDelimitedEnvMap(allocator: *mem.Allocator, envp_buf: []?[*:0]u8) void {
|
||
for (envp_buf) |env| {
|
||
const env_buf = if (env) |ptr| ptr[0 .. mem.len(ptr) + 1] else break;
|
||
allocator.free(env_buf);
|
||
}
|
||
allocator.free(envp_buf);
|
||
}
|
||
|
||
/// Get an environment variable.
|
||
/// See also `getenvZ`.
|
||
pub fn getenv(key: []const u8) ?[]const u8 {
|
||
if (builtin.link_libc) {
|
||
var small_key_buf: [64]u8 = undefined;
|
||
if (key.len < small_key_buf.len) {
|
||
mem.copy(u8, &small_key_buf, key);
|
||
small_key_buf[key.len] = 0;
|
||
const key0 = small_key_buf[0..key.len :0];
|
||
return getenvZ(key0);
|
||
}
|
||
// Search the entire `environ` because we don't have a null terminated pointer.
|
||
var ptr = std.c.environ;
|
||
while (ptr.*) |line| : (ptr += 1) {
|
||
var line_i: usize = 0;
|
||
while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {}
|
||
const this_key = line[0..line_i];
|
||
|
||
if (!mem.eql(u8, this_key, key)) continue;
|
||
|
||
var end_i: usize = line_i;
|
||
while (line[end_i] != 0) : (end_i += 1) {}
|
||
const value = line[line_i + 1 .. end_i];
|
||
|
||
return value;
|
||
}
|
||
return null;
|
||
}
|
||
if (builtin.os.tag == .windows) {
|
||
@compileError("std.os.getenv is unavailable for Windows because environment string is in WTF-16 format. See std.process.getEnvVarOwned for cross-platform API or std.os.getenvW for Windows-specific API.");
|
||
}
|
||
// TODO see https://github.com/ziglang/zig/issues/4524
|
||
for (environ) |ptr| {
|
||
var line_i: usize = 0;
|
||
while (ptr[line_i] != 0 and ptr[line_i] != '=') : (line_i += 1) {}
|
||
const this_key = ptr[0..line_i];
|
||
if (!mem.eql(u8, key, this_key)) continue;
|
||
|
||
var end_i: usize = line_i;
|
||
while (ptr[end_i] != 0) : (end_i += 1) {}
|
||
const this_value = ptr[line_i + 1 .. end_i];
|
||
|
||
return this_value;
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/// Deprecated in favor of `getenvZ`.
|
||
pub const getenvC = getenvZ;
|
||
|
||
/// Get an environment variable with a null-terminated name.
|
||
/// See also `getenv`.
|
||
pub fn getenvZ(key: [*:0]const u8) ?[]const u8 {
|
||
if (builtin.link_libc) {
|
||
const value = system.getenv(key) orelse return null;
|
||
return mem.toSliceConst(u8, value);
|
||
}
|
||
if (builtin.os.tag == .windows) {
|
||
@compileError("std.os.getenvZ is unavailable for Windows because environment string is in WTF-16 format. See std.process.getEnvVarOwned for cross-platform API or std.os.getenvW for Windows-specific API.");
|
||
}
|
||
return getenv(mem.toSliceConst(u8, key));
|
||
}
|
||
|
||
/// Windows-only. Get an environment variable with a null-terminated, WTF-16 encoded name.
|
||
/// See also `getenv`.
|
||
pub fn getenvW(key: [*:0]const u16) ?[:0]const u16 {
|
||
if (builtin.os.tag != .windows) {
|
||
@compileError("std.os.getenvW is a Windows-only API");
|
||
}
|
||
const key_slice = mem.toSliceConst(u16, key);
|
||
const ptr = windows.peb().ProcessParameters.Environment;
|
||
var i: usize = 0;
|
||
while (ptr[i] != 0) {
|
||
const key_start = i;
|
||
|
||
while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {}
|
||
const this_key = ptr[key_start..i];
|
||
|
||
if (ptr[i] == '=') i += 1;
|
||
|
||
const value_start = i;
|
||
while (ptr[i] != 0) : (i += 1) {}
|
||
const this_value = ptr[value_start..i :0];
|
||
|
||
if (mem.eql(u16, key_slice, this_key)) return this_value;
|
||
|
||
i += 1; // skip over null byte
|
||
}
|
||
return null;
|
||
}
|
||
|
||
pub const GetCwdError = error{
|
||
NameTooLong,
|
||
CurrentWorkingDirectoryUnlinked,
|
||
} || UnexpectedError;
|
||
|
||
/// The result is a slice of out_buffer, indexed from 0.
|
||
pub fn getcwd(out_buffer: []u8) GetCwdError![]u8 {
|
||
if (builtin.os.tag == .windows) {
|
||
return windows.GetCurrentDirectory(out_buffer);
|
||
}
|
||
|
||
const err = if (builtin.link_libc) blk: {
|
||
break :blk if (std.c.getcwd(out_buffer.ptr, out_buffer.len)) |_| 0 else std.c._errno().*;
|
||
} else blk: {
|
||
break :blk errno(system.getcwd(out_buffer.ptr, out_buffer.len));
|
||
};
|
||
switch (err) {
|
||
0 => return mem.toSlice(u8, @ptrCast([*:0]u8, out_buffer.ptr)),
|
||
EFAULT => unreachable,
|
||
EINVAL => unreachable,
|
||
ENOENT => return error.CurrentWorkingDirectoryUnlinked,
|
||
ERANGE => return error.NameTooLong,
|
||
else => return unexpectedErrno(@intCast(usize, err)),
|
||
}
|
||
}
|
||
|
||
pub const SymLinkError = error{
|
||
AccessDenied,
|
||
DiskQuota,
|
||
PathAlreadyExists,
|
||
FileSystem,
|
||
SymLinkLoop,
|
||
FileNotFound,
|
||
SystemResources,
|
||
NoSpaceLeft,
|
||
ReadOnlyFileSystem,
|
||
NotDir,
|
||
NameTooLong,
|
||
InvalidUtf8,
|
||
BadPathName,
|
||
} || UnexpectedError;
|
||
|
||
/// Creates a symbolic link named `sym_link_path` which contains the string `target_path`.
|
||
/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
|
||
/// one; the latter case is known as a dangling link.
|
||
/// If `sym_link_path` exists, it will not be overwritten.
|
||
/// See also `symlinkC` and `symlinkW`.
|
||
pub fn symlink(target_path: []const u8, sym_link_path: []const u8) SymLinkError!void {
|
||
if (builtin.os.tag == .windows) {
|
||
const target_path_w = try windows.sliceToPrefixedFileW(target_path);
|
||
const sym_link_path_w = try windows.sliceToPrefixedFileW(sym_link_path);
|
||
return windows.CreateSymbolicLinkW(&sym_link_path_w, &target_path_w, 0);
|
||
} else {
|
||
const target_path_c = try toPosixPath(target_path);
|
||
const sym_link_path_c = try toPosixPath(sym_link_path);
|
||
return symlinkC(&target_path_c, &sym_link_path_c);
|
||
}
|
||
}
|
||
|
||
/// This is the same as `symlink` except the parameters are null-terminated pointers.
|
||
/// See also `symlink`.
|
||
pub fn symlinkC(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLinkError!void {
|
||
if (builtin.os.tag == .windows) {
|
||
const target_path_w = try windows.cStrToPrefixedFileW(target_path);
|
||
const sym_link_path_w = try windows.cStrToPrefixedFileW(sym_link_path);
|
||
return windows.CreateSymbolicLinkW(&sym_link_path_w, &target_path_w, 0);
|
||
}
|
||
switch (errno(system.symlink(target_path, sym_link_path))) {
|
||
0 => return,
|
||
EFAULT => unreachable,
|
||
EINVAL => unreachable,
|
||
EACCES => return error.AccessDenied,
|
||
EPERM => return error.AccessDenied,
|
||
EDQUOT => return error.DiskQuota,
|
||
EEXIST => return error.PathAlreadyExists,
|
||
EIO => return error.FileSystem,
|
||
ELOOP => return error.SymLinkLoop,
|
||
ENAMETOOLONG => return error.NameTooLong,
|
||
ENOENT => return error.FileNotFound,
|
||
ENOTDIR => return error.NotDir,
|
||
ENOMEM => return error.SystemResources,
|
||
ENOSPC => return error.NoSpaceLeft,
|
||
EROFS => return error.ReadOnlyFileSystem,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
|
||
pub fn symlinkat(target_path: []const u8, newdirfd: fd_t, sym_link_path: []const u8) SymLinkError!void {
|
||
const target_path_c = try toPosixPath(target_path);
|
||
const sym_link_path_c = try toPosixPath(sym_link_path);
|
||
return symlinkatC(target_path_c, newdirfd, sym_link_path_c);
|
||
}
|
||
|
||
pub fn symlinkatC(target_path: [*:0]const u8, newdirfd: fd_t, sym_link_path: [*:0]const u8) SymLinkError!void {
|
||
switch (errno(system.symlinkat(target_path, newdirfd, sym_link_path))) {
|
||
0 => return,
|
||
EFAULT => unreachable,
|
||
EINVAL => unreachable,
|
||
EACCES => return error.AccessDenied,
|
||
EPERM => return error.AccessDenied,
|
||
EDQUOT => return error.DiskQuota,
|
||
EEXIST => return error.PathAlreadyExists,
|
||
EIO => return error.FileSystem,
|
||
ELOOP => return error.SymLinkLoop,
|
||
ENAMETOOLONG => return error.NameTooLong,
|
||
ENOENT => return error.FileNotFound,
|
||
ENOTDIR => return error.NotDir,
|
||
ENOMEM => return error.SystemResources,
|
||
ENOSPC => return error.NoSpaceLeft,
|
||
EROFS => return error.ReadOnlyFileSystem,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
|
||
pub const UnlinkError = error{
|
||
FileNotFound,
|
||
AccessDenied,
|
||
FileBusy,
|
||
FileSystem,
|
||
IsDir,
|
||
SymLinkLoop,
|
||
NameTooLong,
|
||
NotDir,
|
||
SystemResources,
|
||
ReadOnlyFileSystem,
|
||
|
||
/// On Windows, file paths must be valid Unicode.
|
||
InvalidUtf8,
|
||
|
||
/// On Windows, file paths cannot contain these characters:
|
||
/// '/', '*', '?', '"', '<', '>', '|'
|
||
BadPathName,
|
||
} || UnexpectedError;
|
||
|
||
/// Delete a name and possibly the file it refers to.
|
||
/// See also `unlinkC`.
|
||
pub fn unlink(file_path: []const u8) UnlinkError!void {
|
||
if (builtin.os.tag == .windows) {
|
||
const file_path_w = try windows.sliceToPrefixedFileW(file_path);
|
||
return windows.DeleteFileW(&file_path_w);
|
||
} else {
|
||
const file_path_c = try toPosixPath(file_path);
|
||
return unlinkC(&file_path_c);
|
||
}
|
||
}
|
||
|
||
/// Same as `unlink` except the parameter is a null terminated UTF8-encoded string.
|
||
pub fn unlinkC(file_path: [*:0]const u8) UnlinkError!void {
|
||
if (builtin.os.tag == .windows) {
|
||
const file_path_w = try windows.cStrToPrefixedFileW(file_path);
|
||
return windows.DeleteFileW(&file_path_w);
|
||
}
|
||
switch (errno(system.unlink(file_path))) {
|
||
0 => return,
|
||
EACCES => return error.AccessDenied,
|
||
EPERM => return error.AccessDenied,
|
||
EBUSY => return error.FileBusy,
|
||
EFAULT => unreachable,
|
||
EINVAL => unreachable,
|
||
EIO => return error.FileSystem,
|
||
EISDIR => return error.IsDir,
|
||
ELOOP => return error.SymLinkLoop,
|
||
ENAMETOOLONG => return error.NameTooLong,
|
||
ENOENT => return error.FileNotFound,
|
||
ENOTDIR => return error.NotDir,
|
||
ENOMEM => return error.SystemResources,
|
||
EROFS => return error.ReadOnlyFileSystem,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
|
||
pub const UnlinkatError = UnlinkError || error{
|
||
/// When passing `AT_REMOVEDIR`, this error occurs when the named directory is not empty.
|
||
DirNotEmpty,
|
||
};
|
||
|
||
/// Delete a file name and possibly the file it refers to, based on an open directory handle.
|
||
/// Asserts that the path parameter has no null bytes.
|
||
pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void {
|
||
if (builtin.os.tag == .windows) {
|
||
const file_path_w = try windows.sliceToPrefixedFileW(file_path);
|
||
return unlinkatW(dirfd, &file_path_w, flags);
|
||
}
|
||
const file_path_c = try toPosixPath(file_path);
|
||
return unlinkatC(dirfd, &file_path_c, flags);
|
||
}
|
||
|
||
/// Same as `unlinkat` but `file_path` is a null-terminated string.
|
||
pub fn unlinkatC(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatError!void {
|
||
if (builtin.os.tag == .windows) {
|
||
const file_path_w = try windows.cStrToPrefixedFileW(file_path_c);
|
||
return unlinkatW(dirfd, &file_path_w, flags);
|
||
}
|
||
switch (errno(system.unlinkat(dirfd, file_path_c, flags))) {
|
||
0 => return,
|
||
EACCES => return error.AccessDenied,
|
||
EPERM => return error.AccessDenied,
|
||
EBUSY => return error.FileBusy,
|
||
EFAULT => unreachable,
|
||
EIO => return error.FileSystem,
|
||
EISDIR => return error.IsDir,
|
||
ELOOP => return error.SymLinkLoop,
|
||
ENAMETOOLONG => return error.NameTooLong,
|
||
ENOENT => return error.FileNotFound,
|
||
ENOTDIR => return error.NotDir,
|
||
ENOMEM => return error.SystemResources,
|
||
EROFS => return error.ReadOnlyFileSystem,
|
||
ENOTEMPTY => return error.DirNotEmpty,
|
||
|
||
EINVAL => unreachable, // invalid flags, or pathname has . as last component
|
||
EBADF => unreachable, // always a race condition
|
||
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
|
||
/// Same as `unlinkat` but `sub_path_w` is UTF16LE, NT prefixed. Windows only.
|
||
pub fn unlinkatW(dirfd: fd_t, sub_path_w: [*:0]const u16, flags: u32) UnlinkatError!void {
|
||
const w = windows;
|
||
|
||
const want_rmdir_behavior = (flags & AT_REMOVEDIR) != 0;
|
||
const create_options_flags = if (want_rmdir_behavior)
|
||
@as(w.ULONG, w.FILE_DELETE_ON_CLOSE)
|
||
else
|
||
@as(w.ULONG, w.FILE_DELETE_ON_CLOSE | w.FILE_NON_DIRECTORY_FILE);
|
||
|
||
const path_len_bytes = @intCast(u16, mem.toSliceConst(u16, sub_path_w).len * 2);
|
||
var nt_name = w.UNICODE_STRING{
|
||
.Length = path_len_bytes,
|
||
.MaximumLength = path_len_bytes,
|
||
// The Windows API makes this mutable, but it will not mutate here.
|
||
.Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w)),
|
||
};
|
||
|
||
if (sub_path_w[0] == '.' and sub_path_w[1] == 0) {
|
||
// Windows does not recognize this, but it does work with empty string.
|
||
nt_name.Length = 0;
|
||
}
|
||
if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) {
|
||
// Can't remove the parent directory with an open handle.
|
||
return error.FileBusy;
|
||
}
|
||
|
||
var attr = w.OBJECT_ATTRIBUTES{
|
||
.Length = @sizeOf(w.OBJECT_ATTRIBUTES),
|
||
.RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w)) null else dirfd,
|
||
.Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
|
||
.ObjectName = &nt_name,
|
||
.SecurityDescriptor = null,
|
||
.SecurityQualityOfService = null,
|
||
};
|
||
var io: w.IO_STATUS_BLOCK = undefined;
|
||
var tmp_handle: w.HANDLE = undefined;
|
||
var rc = w.ntdll.NtCreateFile(
|
||
&tmp_handle,
|
||
w.SYNCHRONIZE | w.DELETE,
|
||
&attr,
|
||
&io,
|
||
null,
|
||
0,
|
||
w.FILE_SHARE_READ | w.FILE_SHARE_WRITE | w.FILE_SHARE_DELETE,
|
||
w.FILE_OPEN,
|
||
create_options_flags,
|
||
null,
|
||
0,
|
||
);
|
||
if (rc == .SUCCESS) {
|
||
rc = w.ntdll.NtClose(tmp_handle);
|
||
}
|
||
switch (rc) {
|
||
.SUCCESS => return,
|
||
.OBJECT_NAME_INVALID => unreachable,
|
||
.OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
|
||
.INVALID_PARAMETER => unreachable,
|
||
.FILE_IS_A_DIRECTORY => return error.IsDir,
|
||
else => return w.unexpectedStatus(rc),
|
||
}
|
||
}
|
||
|
||
const RenameError = error{
|
||
AccessDenied,
|
||
FileBusy,
|
||
DiskQuota,
|
||
IsDir,
|
||
SymLinkLoop,
|
||
LinkQuotaExceeded,
|
||
NameTooLong,
|
||
FileNotFound,
|
||
NotDir,
|
||
SystemResources,
|
||
NoSpaceLeft,
|
||
PathAlreadyExists,
|
||
ReadOnlyFileSystem,
|
||
RenameAcrossMountPoints,
|
||
InvalidUtf8,
|
||
BadPathName,
|
||
NoDevice,
|
||
SharingViolation,
|
||
PipeBusy,
|
||
} || UnexpectedError;
|
||
|
||
/// Change the name or location of a file.
|
||
pub fn rename(old_path: []const u8, new_path: []const u8) RenameError!void {
|
||
if (builtin.os.tag == .windows) {
|
||
const old_path_w = try windows.sliceToPrefixedFileW(old_path);
|
||
const new_path_w = try windows.sliceToPrefixedFileW(new_path);
|
||
return renameW(&old_path_w, &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: [*:0]const u8, new_path: [*:0]const u8) RenameError!void {
|
||
if (builtin.os.tag == .windows) {
|
||
const old_path_w = try windows.cStrToPrefixedFileW(old_path);
|
||
const new_path_w = try windows.cStrToPrefixedFileW(new_path);
|
||
return renameW(&old_path_w, &new_path_w);
|
||
}
|
||
switch (errno(system.rename(old_path, new_path))) {
|
||
0 => return,
|
||
EACCES => return error.AccessDenied,
|
||
EPERM => return error.AccessDenied,
|
||
EBUSY => return error.FileBusy,
|
||
EDQUOT => return error.DiskQuota,
|
||
EFAULT => unreachable,
|
||
EINVAL => unreachable,
|
||
EISDIR => return error.IsDir,
|
||
ELOOP => return error.SymLinkLoop,
|
||
EMLINK => return error.LinkQuotaExceeded,
|
||
ENAMETOOLONG => return error.NameTooLong,
|
||
ENOENT => return error.FileNotFound,
|
||
ENOTDIR => return error.NotDir,
|
||
ENOMEM => return error.SystemResources,
|
||
ENOSPC => return error.NoSpaceLeft,
|
||
EEXIST => return error.PathAlreadyExists,
|
||
ENOTEMPTY => return error.PathAlreadyExists,
|
||
EROFS => return error.ReadOnlyFileSystem,
|
||
EXDEV => return error.RenameAcrossMountPoints,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
|
||
/// Same as `rename` except the parameters are null-terminated UTF16LE encoded byte arrays.
|
||
/// Assumes target is Windows.
|
||
pub fn renameW(old_path: [*:0]const u16, new_path: [*:0]const u16) RenameError!void {
|
||
const flags = windows.MOVEFILE_REPLACE_EXISTING | windows.MOVEFILE_WRITE_THROUGH;
|
||
return windows.MoveFileExW(old_path, new_path, flags);
|
||
}
|
||
|
||
/// Change the name or location of a file based on an open directory handle.
|
||
pub fn renameat(
|
||
old_dir_fd: fd_t,
|
||
old_path: []const u8,
|
||
new_dir_fd: fd_t,
|
||
new_path: []const u8,
|
||
) RenameError!void {
|
||
if (builtin.os.tag == .windows) {
|
||
const old_path_w = try windows.sliceToPrefixedFileW(old_path);
|
||
const new_path_w = try windows.sliceToPrefixedFileW(new_path);
|
||
return renameatW(old_dir_fd, &old_path_w, new_dir_fd, &new_path_w, windows.TRUE);
|
||
} else {
|
||
const old_path_c = try toPosixPath(old_path);
|
||
const new_path_c = try toPosixPath(new_path);
|
||
return renameatZ(old_dir_fd, &old_path_c, new_dir_fd, &new_path_c);
|
||
}
|
||
}
|
||
|
||
/// Same as `renameat` except the parameters are null-terminated byte arrays.
|
||
pub fn renameatZ(
|
||
old_dir_fd: fd_t,
|
||
old_path: [*:0]const u8,
|
||
new_dir_fd: fd_t,
|
||
new_path: [*:0]const u8,
|
||
) RenameError!void {
|
||
if (builtin.os.tag == .windows) {
|
||
const old_path_w = try windows.cStrToPrefixedFileW(old_path);
|
||
const new_path_w = try windows.cStrToPrefixedFileW(new_path);
|
||
return renameatW(old_dir_fd, &old_path_w, new_dir_fd, &new_path_w, windows.TRUE);
|
||
}
|
||
|
||
switch (errno(system.renameat(old_dir_fd, old_path, new_dir_fd, new_path))) {
|
||
0 => return,
|
||
EACCES => return error.AccessDenied,
|
||
EPERM => return error.AccessDenied,
|
||
EBUSY => return error.FileBusy,
|
||
EDQUOT => return error.DiskQuota,
|
||
EFAULT => unreachable,
|
||
EINVAL => unreachable,
|
||
EISDIR => return error.IsDir,
|
||
ELOOP => return error.SymLinkLoop,
|
||
EMLINK => return error.LinkQuotaExceeded,
|
||
ENAMETOOLONG => return error.NameTooLong,
|
||
ENOENT => return error.FileNotFound,
|
||
ENOTDIR => return error.NotDir,
|
||
ENOMEM => return error.SystemResources,
|
||
ENOSPC => return error.NoSpaceLeft,
|
||
EEXIST => return error.PathAlreadyExists,
|
||
ENOTEMPTY => return error.PathAlreadyExists,
|
||
EROFS => return error.ReadOnlyFileSystem,
|
||
EXDEV => return error.RenameAcrossMountPoints,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
|
||
/// Same as `renameat` except the parameters are null-terminated UTF16LE encoded byte arrays.
|
||
/// Assumes target is Windows.
|
||
/// TODO these args can actually be slices when using ntdll. audit the rest of the W functions too.
|
||
pub fn renameatW(
|
||
old_dir_fd: fd_t,
|
||
old_path: [*:0]const u16,
|
||
new_dir_fd: fd_t,
|
||
new_path_w: [*:0]const u16,
|
||
ReplaceIfExists: windows.BOOLEAN,
|
||
) RenameError!void {
|
||
const access_mask = windows.SYNCHRONIZE | windows.GENERIC_WRITE | windows.DELETE;
|
||
const src_fd = try windows.OpenFileW(old_dir_fd, old_path, null, access_mask, windows.FILE_OPEN);
|
||
defer windows.CloseHandle(src_fd);
|
||
|
||
const struct_buf_len = @sizeOf(windows.FILE_RENAME_INFORMATION) + (MAX_PATH_BYTES - 1);
|
||
var rename_info_buf: [struct_buf_len]u8 align(@alignOf(windows.FILE_RENAME_INFORMATION)) = undefined;
|
||
const new_path = mem.span(new_path_w);
|
||
const struct_len = @sizeOf(windows.FILE_RENAME_INFORMATION) - 1 + new_path.len * 2;
|
||
if (struct_len > struct_buf_len) return error.NameTooLong;
|
||
|
||
const rename_info = @ptrCast(*windows.FILE_RENAME_INFORMATION, &rename_info_buf);
|
||
|
||
rename_info.* = .{
|
||
.ReplaceIfExists = ReplaceIfExists,
|
||
.RootDirectory = if (std.fs.path.isAbsoluteWindowsW(new_path_w)) null else new_dir_fd,
|
||
.FileNameLength = @intCast(u32, new_path.len * 2), // already checked error.NameTooLong
|
||
.FileName = undefined,
|
||
};
|
||
std.mem.copy(u16, @as([*]u16, &rename_info.FileName)[0..new_path.len], new_path);
|
||
|
||
var io_status_block: windows.IO_STATUS_BLOCK = undefined;
|
||
|
||
const rc = windows.ntdll.NtSetInformationFile(
|
||
src_fd,
|
||
&io_status_block,
|
||
rename_info,
|
||
@intCast(u32, struct_len), // already checked for error.NameTooLong
|
||
.FileRenameInformation,
|
||
);
|
||
|
||
switch (rc) {
|
||
.SUCCESS => return,
|
||
.INVALID_HANDLE => unreachable,
|
||
.INVALID_PARAMETER => unreachable,
|
||
.OBJECT_PATH_SYNTAX_BAD => unreachable,
|
||
.ACCESS_DENIED => return error.AccessDenied,
|
||
.OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
|
||
.OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
|
||
else => return windows.unexpectedStatus(rc),
|
||
}
|
||
}
|
||
|
||
pub const MakeDirError = error{
|
||
AccessDenied,
|
||
DiskQuota,
|
||
PathAlreadyExists,
|
||
SymLinkLoop,
|
||
LinkQuotaExceeded,
|
||
NameTooLong,
|
||
FileNotFound,
|
||
SystemResources,
|
||
NoSpaceLeft,
|
||
NotDir,
|
||
ReadOnlyFileSystem,
|
||
InvalidUtf8,
|
||
BadPathName,
|
||
NoDevice,
|
||
} || UnexpectedError;
|
||
|
||
pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!void {
|
||
if (builtin.os.tag == .windows) {
|
||
const sub_dir_path_w = try windows.sliceToPrefixedFileW(sub_dir_path);
|
||
return mkdiratW(dir_fd, &sub_dir_path_w, mode);
|
||
} else {
|
||
const sub_dir_path_c = try toPosixPath(sub_dir_path);
|
||
return mkdiratC(dir_fd, &sub_dir_path_c, mode);
|
||
}
|
||
}
|
||
|
||
pub fn mkdiratC(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirError!void {
|
||
if (builtin.os.tag == .windows) {
|
||
const sub_dir_path_w = try windows.cStrToPrefixedFileW(sub_dir_path);
|
||
return mkdiratW(dir_fd, &sub_dir_path_w, mode);
|
||
}
|
||
switch (errno(system.mkdirat(dir_fd, sub_dir_path, mode))) {
|
||
0 => return,
|
||
EACCES => return error.AccessDenied,
|
||
EBADF => unreachable,
|
||
EPERM => return error.AccessDenied,
|
||
EDQUOT => return error.DiskQuota,
|
||
EEXIST => return error.PathAlreadyExists,
|
||
EFAULT => unreachable,
|
||
ELOOP => return error.SymLinkLoop,
|
||
EMLINK => return error.LinkQuotaExceeded,
|
||
ENAMETOOLONG => return error.NameTooLong,
|
||
ENOENT => return error.FileNotFound,
|
||
ENOMEM => return error.SystemResources,
|
||
ENOSPC => return error.NoSpaceLeft,
|
||
ENOTDIR => return error.NotDir,
|
||
EROFS => return error.ReadOnlyFileSystem,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
|
||
pub fn mkdiratW(dir_fd: fd_t, sub_path_w: [*:0]const u16, mode: u32) MakeDirError!void {
|
||
const sub_dir_handle = try windows.CreateDirectoryW(dir_fd, sub_path_w, null);
|
||
windows.CloseHandle(sub_dir_handle);
|
||
}
|
||
|
||
/// Create a directory.
|
||
/// `mode` is ignored on Windows.
|
||
pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void {
|
||
if (builtin.os.tag == .windows) {
|
||
const sub_dir_handle = try windows.CreateDirectory(null, dir_path, null);
|
||
windows.CloseHandle(sub_dir_handle);
|
||
return;
|
||
} else {
|
||
const dir_path_c = try toPosixPath(dir_path);
|
||
return mkdirZ(&dir_path_c, mode);
|
||
}
|
||
}
|
||
|
||
/// Same as `mkdir` but the parameter is a null-terminated UTF8-encoded string.
|
||
pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void {
|
||
if (builtin.os.tag == .windows) {
|
||
const dir_path_w = try windows.cStrToPrefixedFileW(dir_path);
|
||
const sub_dir_handle = try windows.CreateDirectoryW(null, &dir_path_w, null);
|
||
windows.CloseHandle(sub_dir_handle);
|
||
return;
|
||
}
|
||
switch (errno(system.mkdir(dir_path, mode))) {
|
||
0 => return,
|
||
EACCES => return error.AccessDenied,
|
||
EPERM => return error.AccessDenied,
|
||
EDQUOT => return error.DiskQuota,
|
||
EEXIST => return error.PathAlreadyExists,
|
||
EFAULT => unreachable,
|
||
ELOOP => return error.SymLinkLoop,
|
||
EMLINK => return error.LinkQuotaExceeded,
|
||
ENAMETOOLONG => return error.NameTooLong,
|
||
ENOENT => return error.FileNotFound,
|
||
ENOMEM => return error.SystemResources,
|
||
ENOSPC => return error.NoSpaceLeft,
|
||
ENOTDIR => return error.NotDir,
|
||
EROFS => return error.ReadOnlyFileSystem,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
|
||
pub const DeleteDirError = error{
|
||
AccessDenied,
|
||
FileBusy,
|
||
SymLinkLoop,
|
||
NameTooLong,
|
||
FileNotFound,
|
||
SystemResources,
|
||
NotDir,
|
||
DirNotEmpty,
|
||
ReadOnlyFileSystem,
|
||
InvalidUtf8,
|
||
BadPathName,
|
||
} || UnexpectedError;
|
||
|
||
/// Deletes an empty directory.
|
||
pub fn rmdir(dir_path: []const u8) DeleteDirError!void {
|
||
if (builtin.os.tag == .windows) {
|
||
const dir_path_w = try windows.sliceToPrefixedFileW(dir_path);
|
||
return windows.RemoveDirectoryW(&dir_path_w);
|
||
} else {
|
||
const dir_path_c = try toPosixPath(dir_path);
|
||
return rmdirC(&dir_path_c);
|
||
}
|
||
}
|
||
|
||
/// Same as `rmdir` except the parameter is null-terminated.
|
||
pub fn rmdirC(dir_path: [*:0]const u8) DeleteDirError!void {
|
||
if (builtin.os.tag == .windows) {
|
||
const dir_path_w = try windows.cStrToPrefixedFileW(dir_path);
|
||
return windows.RemoveDirectoryW(&dir_path_w);
|
||
}
|
||
switch (errno(system.rmdir(dir_path))) {
|
||
0 => return,
|
||
EACCES => return error.AccessDenied,
|
||
EPERM => return error.AccessDenied,
|
||
EBUSY => return error.FileBusy,
|
||
EFAULT => unreachable,
|
||
EINVAL => unreachable,
|
||
ELOOP => return error.SymLinkLoop,
|
||
ENAMETOOLONG => return error.NameTooLong,
|
||
ENOENT => return error.FileNotFound,
|
||
ENOMEM => return error.SystemResources,
|
||
ENOTDIR => return error.NotDir,
|
||
EEXIST => return error.DirNotEmpty,
|
||
ENOTEMPTY => return error.DirNotEmpty,
|
||
EROFS => return error.ReadOnlyFileSystem,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
|
||
pub const ChangeCurDirError = error{
|
||
AccessDenied,
|
||
FileSystem,
|
||
SymLinkLoop,
|
||
NameTooLong,
|
||
FileNotFound,
|
||
SystemResources,
|
||
NotDir,
|
||
} || UnexpectedError;
|
||
|
||
/// Changes the current working directory of the calling process.
|
||
/// `dir_path` is recommended to be a UTF-8 encoded string.
|
||
pub fn chdir(dir_path: []const u8) ChangeCurDirError!void {
|
||
if (builtin.os.tag == .windows) {
|
||
const dir_path_w = try windows.sliceToPrefixedFileW(dir_path);
|
||
@compileError("TODO implement chdir for Windows");
|
||
} else {
|
||
const dir_path_c = try toPosixPath(dir_path);
|
||
return chdirC(&dir_path_c);
|
||
}
|
||
}
|
||
|
||
/// Same as `chdir` except the parameter is null-terminated.
|
||
pub fn chdirC(dir_path: [*:0]const u8) ChangeCurDirError!void {
|
||
if (builtin.os.tag == .windows) {
|
||
const dir_path_w = try windows.cStrToPrefixedFileW(dir_path);
|
||
@compileError("TODO implement chdir for Windows");
|
||
}
|
||
switch (errno(system.chdir(dir_path))) {
|
||
0 => return,
|
||
EACCES => return error.AccessDenied,
|
||
EFAULT => unreachable,
|
||
EIO => return error.FileSystem,
|
||
ELOOP => return error.SymLinkLoop,
|
||
ENAMETOOLONG => return error.NameTooLong,
|
||
ENOENT => return error.FileNotFound,
|
||
ENOMEM => return error.SystemResources,
|
||
ENOTDIR => return error.NotDir,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
|
||
pub const FchdirError = error{
|
||
AccessDenied,
|
||
NotDir,
|
||
FileSystem,
|
||
} || UnexpectedError;
|
||
|
||
pub fn fchdir(dirfd: fd_t) FchdirError!void {
|
||
while (true) {
|
||
switch (errno(system.fchdir(dirfd))) {
|
||
0 => return,
|
||
EACCES => return error.AccessDenied,
|
||
EBADF => unreachable,
|
||
ENOTDIR => return error.NotDir,
|
||
EINTR => continue,
|
||
EIO => return error.FileSystem,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
}
|
||
|
||
pub const ReadLinkError = error{
|
||
AccessDenied,
|
||
FileSystem,
|
||
SymLinkLoop,
|
||
NameTooLong,
|
||
FileNotFound,
|
||
SystemResources,
|
||
NotDir,
|
||
} || UnexpectedError;
|
||
|
||
/// Read value of a symbolic link.
|
||
/// The return value is a slice of `out_buffer` from index 0.
|
||
pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
|
||
if (builtin.os.tag == .windows) {
|
||
const file_path_w = try windows.sliceToPrefixedFileW(file_path);
|
||
@compileError("TODO implement readlink for Windows");
|
||
} else {
|
||
const file_path_c = try toPosixPath(file_path);
|
||
return readlinkC(&file_path_c, out_buffer);
|
||
}
|
||
}
|
||
|
||
/// Same as `readlink` except `file_path` is null-terminated.
|
||
pub fn readlinkC(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 {
|
||
if (builtin.os.tag == .windows) {
|
||
const file_path_w = try windows.cStrToPrefixedFileW(file_path);
|
||
@compileError("TODO implement readlink for Windows");
|
||
}
|
||
const rc = system.readlink(file_path, out_buffer.ptr, out_buffer.len);
|
||
switch (errno(rc)) {
|
||
0 => return out_buffer[0..@bitCast(usize, rc)],
|
||
EACCES => return error.AccessDenied,
|
||
EFAULT => unreachable,
|
||
EINVAL => unreachable,
|
||
EIO => return error.FileSystem,
|
||
ELOOP => return error.SymLinkLoop,
|
||
ENAMETOOLONG => return error.NameTooLong,
|
||
ENOENT => return error.FileNotFound,
|
||
ENOMEM => return error.SystemResources,
|
||
ENOTDIR => return error.NotDir,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
|
||
pub fn readlinkatC(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 {
|
||
if (builtin.os.tag == .windows) {
|
||
const file_path_w = try windows.cStrToPrefixedFileW(file_path);
|
||
@compileError("TODO implement readlink for Windows");
|
||
}
|
||
const rc = system.readlinkat(dirfd, file_path, out_buffer.ptr, out_buffer.len);
|
||
switch (errno(rc)) {
|
||
0 => return out_buffer[0..@bitCast(usize, rc)],
|
||
EACCES => return error.AccessDenied,
|
||
EFAULT => unreachable,
|
||
EINVAL => unreachable,
|
||
EIO => return error.FileSystem,
|
||
ELOOP => return error.SymLinkLoop,
|
||
ENAMETOOLONG => return error.NameTooLong,
|
||
ENOENT => return error.FileNotFound,
|
||
ENOMEM => return error.SystemResources,
|
||
ENOTDIR => return error.NotDir,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
|
||
pub const SetIdError = error{
|
||
ResourceLimitReached,
|
||
InvalidUserId,
|
||
PermissionDenied,
|
||
} || UnexpectedError;
|
||
|
||
pub fn setuid(uid: u32) SetIdError!void {
|
||
switch (errno(system.setuid(uid))) {
|
||
0 => return,
|
||
EAGAIN => return error.ResourceLimitReached,
|
||
EINVAL => return error.InvalidUserId,
|
||
EPERM => return error.PermissionDenied,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
|
||
pub fn setreuid(ruid: u32, euid: u32) SetIdError!void {
|
||
switch (errno(system.setreuid(ruid, euid))) {
|
||
0 => return,
|
||
EAGAIN => return error.ResourceLimitReached,
|
||
EINVAL => return error.InvalidUserId,
|
||
EPERM => return error.PermissionDenied,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
|
||
pub fn setgid(gid: u32) SetIdError!void {
|
||
switch (errno(system.setgid(gid))) {
|
||
0 => return,
|
||
EAGAIN => return error.ResourceLimitReached,
|
||
EINVAL => return error.InvalidUserId,
|
||
EPERM => return error.PermissionDenied,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
|
||
pub fn setregid(rgid: u32, egid: u32) SetIdError!void {
|
||
switch (errno(system.setregid(rgid, egid))) {
|
||
0 => return,
|
||
EAGAIN => return error.ResourceLimitReached,
|
||
EINVAL => return error.InvalidUserId,
|
||
EPERM => return error.PermissionDenied,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
|
||
/// Test whether a file descriptor refers to a terminal.
|
||
pub fn isatty(handle: fd_t) bool {
|
||
if (builtin.os.tag == .windows) {
|
||
if (isCygwinPty(handle))
|
||
return true;
|
||
|
||
var out: windows.DWORD = undefined;
|
||
return windows.kernel32.GetConsoleMode(handle, &out) != 0;
|
||
}
|
||
if (builtin.link_libc) {
|
||
return system.isatty(handle) != 0;
|
||
}
|
||
if (builtin.os.tag == .wasi) {
|
||
var statbuf: fdstat_t = undefined;
|
||
const err = system.fd_fdstat_get(handle, &statbuf);
|
||
if (err != 0) {
|
||
// errno = err;
|
||
return false;
|
||
}
|
||
|
||
// A tty is a character device that we can't seek or tell on.
|
||
if (statbuf.fs_filetype != FILETYPE_CHARACTER_DEVICE or
|
||
(statbuf.fs_rights_base & (RIGHT_FD_SEEK | RIGHT_FD_TELL)) != 0)
|
||
{
|
||
// errno = ENOTTY;
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
if (builtin.os.tag == .linux) {
|
||
var wsz: linux.winsize = undefined;
|
||
return linux.syscall3(linux.SYS_ioctl, @bitCast(usize, @as(isize, handle)), linux.TIOCGWINSZ, @ptrToInt(&wsz)) == 0;
|
||
}
|
||
unreachable;
|
||
}
|
||
|
||
pub fn isCygwinPty(handle: fd_t) bool {
|
||
if (builtin.os.tag != .windows) return false;
|
||
|
||
const size = @sizeOf(windows.FILE_NAME_INFO);
|
||
var name_info_bytes align(@alignOf(windows.FILE_NAME_INFO)) = [_]u8{0} ** (size + windows.MAX_PATH);
|
||
|
||
if (windows.kernel32.GetFileInformationByHandleEx(
|
||
handle,
|
||
windows.FileNameInfo,
|
||
@ptrCast(*c_void, &name_info_bytes),
|
||
name_info_bytes.len,
|
||
) == 0) {
|
||
return false;
|
||
}
|
||
|
||
const name_info = @ptrCast(*const windows.FILE_NAME_INFO, &name_info_bytes[0]);
|
||
const name_bytes = name_info_bytes[size .. size + @as(usize, name_info.FileNameLength)];
|
||
const name_wide = mem.bytesAsSlice(u16, name_bytes);
|
||
return mem.indexOf(u16, name_wide, &[_]u16{ 'm', 's', 'y', 's', '-' }) != null or
|
||
mem.indexOf(u16, name_wide, &[_]u16{ '-', 'p', 't', 'y' }) != null;
|
||
}
|
||
|
||
pub const SocketError = error{
|
||
/// Permission to create a socket of the specified type and/or
|
||
/// pro‐tocol is denied.
|
||
PermissionDenied,
|
||
|
||
/// The implementation does not support the specified address family.
|
||
AddressFamilyNotSupported,
|
||
|
||
/// Unknown protocol, or protocol family not available.
|
||
ProtocolFamilyNotAvailable,
|
||
|
||
/// The per-process limit on the number of open file descriptors has been reached.
|
||
ProcessFdQuotaExceeded,
|
||
|
||
/// The system-wide limit on the total number of open files has been reached.
|
||
SystemFdQuotaExceeded,
|
||
|
||
/// Insufficient memory is available. The socket cannot be created until sufficient
|
||
/// resources are freed.
|
||
SystemResources,
|
||
|
||
/// The protocol type or the specified protocol is not supported within this domain.
|
||
ProtocolNotSupported,
|
||
} || UnexpectedError;
|
||
|
||
pub fn socket(domain: u32, socket_type: u32, protocol: u32) SocketError!fd_t {
|
||
const rc = system.socket(domain, socket_type, protocol);
|
||
switch (errno(rc)) {
|
||
0 => return @intCast(fd_t, rc),
|
||
EACCES => return error.PermissionDenied,
|
||
EAFNOSUPPORT => return error.AddressFamilyNotSupported,
|
||
EINVAL => return error.ProtocolFamilyNotAvailable,
|
||
EMFILE => return error.ProcessFdQuotaExceeded,
|
||
ENFILE => return error.SystemFdQuotaExceeded,
|
||
ENOBUFS => return error.SystemResources,
|
||
ENOMEM => return error.SystemResources,
|
||
EPROTONOSUPPORT => return error.ProtocolNotSupported,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
|
||
pub const BindError = error{
|
||
/// The address is protected, and the user is not the superuser.
|
||
/// For UNIX domain sockets: Search permission is denied on a component
|
||
/// of the path prefix.
|
||
AccessDenied,
|
||
|
||
/// The given address is already in use, or in the case of Internet domain sockets,
|
||
/// The port number was specified as zero in the socket
|
||
/// address structure, but, upon attempting to bind to an ephemeral port, it was
|
||
/// determined that all port numbers in the ephemeral port range are currently in
|
||
/// use. See the discussion of /proc/sys/net/ipv4/ip_local_port_range ip(7).
|
||
AddressInUse,
|
||
|
||
/// A nonexistent interface was requested or the requested address was not local.
|
||
AddressNotAvailable,
|
||
|
||
/// Too many symbolic links were encountered in resolving addr.
|
||
SymLinkLoop,
|
||
|
||
/// addr is too long.
|
||
NameTooLong,
|
||
|
||
/// A component in the directory prefix of the socket pathname does not exist.
|
||
FileNotFound,
|
||
|
||
/// Insufficient kernel memory was available.
|
||
SystemResources,
|
||
|
||
/// A component of the path prefix is not a directory.
|
||
NotDir,
|
||
|
||
/// The socket inode would reside on a read-only filesystem.
|
||
ReadOnlyFileSystem,
|
||
} || UnexpectedError;
|
||
|
||
/// addr is `*const T` where T is one of the sockaddr
|
||
pub fn bind(sockfd: fd_t, addr: *const sockaddr, len: socklen_t) BindError!void {
|
||
const rc = system.bind(sockfd, addr, len);
|
||
switch (errno(rc)) {
|
||
0 => return,
|
||
EACCES => return error.AccessDenied,
|
||
EADDRINUSE => return error.AddressInUse,
|
||
EBADF => unreachable, // always a race condition if this error is returned
|
||
EINVAL => unreachable, // invalid parameters
|
||
ENOTSOCK => unreachable, // invalid `sockfd`
|
||
EADDRNOTAVAIL => return error.AddressNotAvailable,
|
||
EFAULT => unreachable, // invalid `addr` pointer
|
||
ELOOP => return error.SymLinkLoop,
|
||
ENAMETOOLONG => return error.NameTooLong,
|
||
ENOENT => return error.FileNotFound,
|
||
ENOMEM => return error.SystemResources,
|
||
ENOTDIR => return error.NotDir,
|
||
EROFS => return error.ReadOnlyFileSystem,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
|
||
const ListenError = error{
|
||
/// Another socket is already listening on the same port.
|
||
/// For Internet domain sockets, the socket referred to by sockfd had not previously
|
||
/// been bound to an address and, upon attempting to bind it to an ephemeral port, it
|
||
/// was determined that all port numbers in the ephemeral port range are currently in
|
||
/// use. See the discussion of /proc/sys/net/ipv4/ip_local_port_range in ip(7).
|
||
AddressInUse,
|
||
|
||
/// The file descriptor sockfd does not refer to a socket.
|
||
FileDescriptorNotASocket,
|
||
|
||
/// The socket is not of a type that supports the listen() operation.
|
||
OperationNotSupported,
|
||
} || UnexpectedError;
|
||
|
||
pub fn listen(sockfd: fd_t, backlog: u32) ListenError!void {
|
||
const rc = system.listen(sockfd, backlog);
|
||
switch (errno(rc)) {
|
||
0 => return,
|
||
EADDRINUSE => return error.AddressInUse,
|
||
EBADF => unreachable,
|
||
ENOTSOCK => return error.FileDescriptorNotASocket,
|
||
EOPNOTSUPP => return error.OperationNotSupported,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
|
||
pub const AcceptError = error{
|
||
ConnectionAborted,
|
||
|
||
/// The per-process limit on the number of open file descriptors has been reached.
|
||
ProcessFdQuotaExceeded,
|
||
|
||
/// The system-wide limit on the total number of open files has been reached.
|
||
SystemFdQuotaExceeded,
|
||
|
||
/// Not enough free memory. This often means that the memory allocation is limited
|
||
/// by the socket buffer limits, not by the system memory.
|
||
SystemResources,
|
||
|
||
ProtocolFailure,
|
||
|
||
/// Firewall rules forbid connection.
|
||
BlockedByFirewall,
|
||
|
||
/// This error occurs when no global event loop is configured,
|
||
/// and accepting from the socket would block.
|
||
WouldBlock,
|
||
} || UnexpectedError;
|
||
|
||
/// Accept a connection on a socket.
|
||
/// If the application has a global event loop enabled, EAGAIN is handled
|
||
/// via the event loop. Otherwise EAGAIN results in error.WouldBlock.
|
||
pub fn accept4(
|
||
/// This argument is a socket that has been created with `socket`, bound to a local address
|
||
/// with `bind`, and is listening for connections after a `listen`.
|
||
sockfd: fd_t,
|
||
/// This argument is a pointer to a sockaddr structure. This structure is filled in with the
|
||
/// address of the peer socket, as known to the communications layer. The exact format of the
|
||
/// address returned addr is determined by the socket's address family (see `socket` and the
|
||
/// respective protocol man pages).
|
||
addr: *sockaddr,
|
||
/// This argument is a value-result argument: the caller must initialize it to contain the
|
||
/// size (in bytes) of the structure pointed to by addr; on return it will contain the actual size
|
||
/// of the peer address.
|
||
///
|
||
/// The returned address is truncated if the buffer provided is too small; in this case, `addr_size`
|
||
/// will return a value greater than was supplied to the call.
|
||
addr_size: *socklen_t,
|
||
/// If flags is 0, then `accept4` is the same as `accept`. The following values can be bitwise
|
||
/// ORed in flags to obtain different behavior:
|
||
/// * `SOCK_NONBLOCK` - Set the `O_NONBLOCK` file status flag on the open file description (see `open`)
|
||
/// referred to by the new file descriptor. Using this flag saves extra calls to `fcntl` to achieve
|
||
/// the same result.
|
||
/// * `SOCK_CLOEXEC` - Set the close-on-exec (`FD_CLOEXEC`) flag on the new file descriptor. See the
|
||
/// description of the `O_CLOEXEC` flag in `open` for reasons why this may be useful.
|
||
flags: u32,
|
||
) AcceptError!fd_t {
|
||
while (true) {
|
||
const rc = system.accept4(sockfd, addr, addr_size, flags);
|
||
switch (errno(rc)) {
|
||
0 => return @intCast(fd_t, rc),
|
||
EINTR => continue,
|
||
|
||
EAGAIN => if (std.event.Loop.instance) |loop| {
|
||
loop.waitUntilFdReadable(sockfd);
|
||
continue;
|
||
} else {
|
||
return error.WouldBlock;
|
||
},
|
||
EBADF => unreachable, // always a race condition
|
||
ECONNABORTED => return error.ConnectionAborted,
|
||
EFAULT => unreachable,
|
||
EINVAL => unreachable,
|
||
ENOTSOCK => unreachable,
|
||
EMFILE => return error.ProcessFdQuotaExceeded,
|
||
ENFILE => return error.SystemFdQuotaExceeded,
|
||
ENOBUFS => return error.SystemResources,
|
||
ENOMEM => return error.SystemResources,
|
||
EOPNOTSUPP => unreachable,
|
||
EPROTO => return error.ProtocolFailure,
|
||
EPERM => return error.BlockedByFirewall,
|
||
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
}
|
||
|
||
pub const EpollCreateError = error{
|
||
/// The per-user limit on the number of epoll instances imposed by
|
||
/// /proc/sys/fs/epoll/max_user_instances was encountered. See epoll(7) for further
|
||
/// details.
|
||
/// Or, The per-process limit on the number of open file descriptors has been reached.
|
||
ProcessFdQuotaExceeded,
|
||
|
||
/// The system-wide limit on the total number of open files has been reached.
|
||
SystemFdQuotaExceeded,
|
||
|
||
/// There was insufficient memory to create the kernel object.
|
||
SystemResources,
|
||
} || UnexpectedError;
|
||
|
||
pub fn epoll_create1(flags: u32) EpollCreateError!i32 {
|
||
const rc = system.epoll_create1(flags);
|
||
switch (errno(rc)) {
|
||
0 => return @intCast(i32, rc),
|
||
else => |err| return unexpectedErrno(err),
|
||
|
||
EINVAL => unreachable,
|
||
EMFILE => return error.ProcessFdQuotaExceeded,
|
||
ENFILE => return error.SystemFdQuotaExceeded,
|
||
ENOMEM => return error.SystemResources,
|
||
}
|
||
}
|
||
|
||
pub const EpollCtlError = error{
|
||
/// op was EPOLL_CTL_ADD, and the supplied file descriptor fd is already registered
|
||
/// with this epoll instance.
|
||
FileDescriptorAlreadyPresentInSet,
|
||
|
||
/// fd refers to an epoll instance and this EPOLL_CTL_ADD operation would result in a
|
||
/// circular loop of epoll instances monitoring one another.
|
||
OperationCausesCircularLoop,
|
||
|
||
/// op was EPOLL_CTL_MOD or EPOLL_CTL_DEL, and fd is not registered with this epoll
|
||
/// instance.
|
||
FileDescriptorNotRegistered,
|
||
|
||
/// There was insufficient memory to handle the requested op control operation.
|
||
SystemResources,
|
||
|
||
/// The limit imposed by /proc/sys/fs/epoll/max_user_watches was encountered while
|
||
/// trying to register (EPOLL_CTL_ADD) a new file descriptor on an epoll instance.
|
||
/// See epoll(7) for further details.
|
||
UserResourceLimitReached,
|
||
|
||
/// The target file fd does not support epoll. This error can occur if fd refers to,
|
||
/// for example, a regular file or a directory.
|
||
FileDescriptorIncompatibleWithEpoll,
|
||
} || UnexpectedError;
|
||
|
||
pub fn epoll_ctl(epfd: i32, op: u32, fd: i32, event: ?*epoll_event) EpollCtlError!void {
|
||
const rc = system.epoll_ctl(epfd, op, fd, event);
|
||
switch (errno(rc)) {
|
||
0 => return,
|
||
else => |err| return unexpectedErrno(err),
|
||
|
||
EBADF => unreachable, // always a race condition if this happens
|
||
EEXIST => return error.FileDescriptorAlreadyPresentInSet,
|
||
EINVAL => unreachable,
|
||
ELOOP => return error.OperationCausesCircularLoop,
|
||
ENOENT => return error.FileDescriptorNotRegistered,
|
||
ENOMEM => return error.SystemResources,
|
||
ENOSPC => return error.UserResourceLimitReached,
|
||
EPERM => return error.FileDescriptorIncompatibleWithEpoll,
|
||
}
|
||
}
|
||
|
||
/// Waits for an I/O event on an epoll file descriptor.
|
||
/// Returns the number of file descriptors ready for the requested I/O,
|
||
/// or zero if no file descriptor became ready during the requested timeout milliseconds.
|
||
pub fn epoll_wait(epfd: i32, events: []epoll_event, timeout: i32) usize {
|
||
while (true) {
|
||
// TODO get rid of the @intCast
|
||
const rc = system.epoll_wait(epfd, events.ptr, @intCast(u32, events.len), timeout);
|
||
switch (errno(rc)) {
|
||
0 => return @intCast(usize, rc),
|
||
EINTR => continue,
|
||
EBADF => unreachable,
|
||
EFAULT => unreachable,
|
||
EINVAL => unreachable,
|
||
else => unreachable,
|
||
}
|
||
}
|
||
}
|
||
|
||
pub const EventFdError = error{
|
||
SystemResources,
|
||
ProcessFdQuotaExceeded,
|
||
SystemFdQuotaExceeded,
|
||
} || UnexpectedError;
|
||
|
||
pub fn eventfd(initval: u32, flags: u32) EventFdError!i32 {
|
||
const rc = system.eventfd(initval, flags);
|
||
switch (errno(rc)) {
|
||
0 => return @intCast(i32, rc),
|
||
else => |err| return unexpectedErrno(err),
|
||
|
||
EINVAL => unreachable, // invalid parameters
|
||
EMFILE => return error.ProcessFdQuotaExceeded,
|
||
ENFILE => return error.SystemFdQuotaExceeded,
|
||
ENODEV => return error.SystemResources,
|
||
ENOMEM => return error.SystemResources,
|
||
}
|
||
}
|
||
|
||
pub const GetSockNameError = error{
|
||
/// Insufficient resources were available in the system to perform the operation.
|
||
SystemResources,
|
||
} || UnexpectedError;
|
||
|
||
pub fn getsockname(sockfd: fd_t, addr: *sockaddr, addrlen: *socklen_t) GetSockNameError!void {
|
||
switch (errno(system.getsockname(sockfd, addr, addrlen))) {
|
||
0 => return,
|
||
else => |err| return unexpectedErrno(err),
|
||
|
||
EBADF => unreachable, // always a race condition
|
||
EFAULT => unreachable,
|
||
EINVAL => unreachable, // invalid parameters
|
||
ENOTSOCK => unreachable,
|
||
ENOBUFS => return error.SystemResources,
|
||
}
|
||
}
|
||
|
||
pub const ConnectError = error{
|
||
/// For UNIX domain sockets, which are identified by pathname: Write permission is denied on the socket
|
||
/// file, or search permission is denied for one of the directories in the path prefix.
|
||
/// or
|
||
/// The user tried to connect to a broadcast address without having the socket broadcast flag enabled or
|
||
/// the connection request failed because of a local firewall rule.
|
||
PermissionDenied,
|
||
|
||
/// Local address is already in use.
|
||
AddressInUse,
|
||
|
||
/// (Internet domain sockets) The socket referred to by sockfd had not previously been bound to an
|
||
/// address and, upon attempting to bind it to an ephemeral port, it was determined that all port numbers
|
||
/// in the ephemeral port range are currently in use. See the discussion of
|
||
/// /proc/sys/net/ipv4/ip_local_port_range in ip(7).
|
||
AddressNotAvailable,
|
||
|
||
/// The passed address didn't have the correct address family in its sa_family field.
|
||
AddressFamilyNotSupported,
|
||
|
||
/// Insufficient entries in the routing cache.
|
||
SystemResources,
|
||
|
||
/// A connect() on a stream socket found no one listening on the remote address.
|
||
ConnectionRefused,
|
||
|
||
/// Network is unreachable.
|
||
NetworkUnreachable,
|
||
|
||
/// Timeout while attempting connection. The server may be too busy to accept new connections. Note
|
||
/// that for IP sockets the timeout may be very long when syncookies are enabled on the server.
|
||
ConnectionTimedOut,
|
||
|
||
/// This error occurs when no global event loop is configured,
|
||
/// and connecting to the socket would block.
|
||
WouldBlock,
|
||
|
||
/// The given path for the unix socket does not exist.
|
||
FileNotFound,
|
||
} || UnexpectedError;
|
||
|
||
/// Initiate a connection on a socket.
|
||
pub fn connect(sockfd: fd_t, sock_addr: *const sockaddr, len: socklen_t) ConnectError!void {
|
||
while (true) {
|
||
switch (errno(system.connect(sockfd, sock_addr, len))) {
|
||
0 => return,
|
||
EACCES => return error.PermissionDenied,
|
||
EPERM => return error.PermissionDenied,
|
||
EADDRINUSE => return error.AddressInUse,
|
||
EADDRNOTAVAIL => return error.AddressNotAvailable,
|
||
EAFNOSUPPORT => return error.AddressFamilyNotSupported,
|
||
EAGAIN, EINPROGRESS => {
|
||
const loop = std.event.Loop.instance orelse return error.WouldBlock;
|
||
loop.waitUntilFdWritableOrReadable(sockfd);
|
||
return getsockoptError(sockfd);
|
||
},
|
||
EALREADY => unreachable, // The socket is nonblocking and a previous connection attempt has not yet been completed.
|
||
EBADF => unreachable, // sockfd is not a valid open file descriptor.
|
||
ECONNREFUSED => return error.ConnectionRefused,
|
||
EFAULT => unreachable, // The socket structure address is outside the user's address space.
|
||
EINTR => continue,
|
||
EISCONN => unreachable, // The socket is already connected.
|
||
ENETUNREACH => return error.NetworkUnreachable,
|
||
ENOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.
|
||
EPROTOTYPE => unreachable, // The socket type does not support the requested communications protocol.
|
||
ETIMEDOUT => return error.ConnectionTimedOut,
|
||
ENOENT => return error.FileNotFound, // Returned when socket is AF_UNIX and the given path does not exist.
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
}
|
||
|
||
pub fn getsockoptError(sockfd: fd_t) ConnectError!void {
|
||
var err_code: u32 = undefined;
|
||
var size: u32 = @sizeOf(u32);
|
||
const rc = system.getsockopt(sockfd, SOL_SOCKET, SO_ERROR, @ptrCast([*]u8, &err_code), &size);
|
||
assert(size == 4);
|
||
switch (errno(rc)) {
|
||
0 => switch (err_code) {
|
||
0 => return,
|
||
EACCES => return error.PermissionDenied,
|
||
EPERM => return error.PermissionDenied,
|
||
EADDRINUSE => return error.AddressInUse,
|
||
EADDRNOTAVAIL => return error.AddressNotAvailable,
|
||
EAFNOSUPPORT => return error.AddressFamilyNotSupported,
|
||
EAGAIN => return error.SystemResources,
|
||
EALREADY => unreachable, // The socket is nonblocking and a previous connection attempt has not yet been completed.
|
||
EBADF => unreachable, // sockfd is not a valid open file descriptor.
|
||
ECONNREFUSED => return error.ConnectionRefused,
|
||
EFAULT => unreachable, // The socket structure address is outside the user's address space.
|
||
EISCONN => unreachable, // The socket is already connected.
|
||
ENETUNREACH => return error.NetworkUnreachable,
|
||
ENOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.
|
||
EPROTOTYPE => unreachable, // The socket type does not support the requested communications protocol.
|
||
ETIMEDOUT => return error.ConnectionTimedOut,
|
||
else => |err| return unexpectedErrno(err),
|
||
},
|
||
EBADF => unreachable, // The argument sockfd is not a valid file descriptor.
|
||
EFAULT => unreachable, // The address pointed to by optval or optlen is not in a valid part of the process address space.
|
||
EINVAL => unreachable,
|
||
ENOPROTOOPT => unreachable, // The option is unknown at the level indicated.
|
||
ENOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
|
||
pub fn waitpid(pid: i32, flags: u32) u32 {
|
||
// TODO allow implicit pointer cast from *u32 to *c_uint ?
|
||
const Status = if (builtin.link_libc) c_uint else u32;
|
||
var status: Status = undefined;
|
||
while (true) {
|
||
switch (errno(system.waitpid(pid, &status, flags))) {
|
||
0 => return @bitCast(u32, status),
|
||
EINTR => continue,
|
||
ECHILD => unreachable, // The process specified does not exist. It would be a race condition to handle this error.
|
||
EINVAL => unreachable, // The options argument was invalid
|
||
else => unreachable,
|
||
}
|
||
}
|
||
}
|
||
|
||
pub const FStatError = error{
|
||
SystemResources,
|
||
AccessDenied,
|
||
} || UnexpectedError;
|
||
|
||
pub fn fstat(fd: fd_t) FStatError!Stat {
|
||
var stat: Stat = undefined;
|
||
if (comptime std.Target.current.isDarwin()) {
|
||
switch (darwin.getErrno(darwin.@"fstat$INODE64"(fd, &stat))) {
|
||
0 => return stat,
|
||
EINVAL => unreachable,
|
||
EBADF => unreachable, // Always a race condition.
|
||
ENOMEM => return error.SystemResources,
|
||
EACCES => return error.AccessDenied,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
|
||
switch (errno(system.fstat(fd, &stat))) {
|
||
0 => return stat,
|
||
EINVAL => unreachable,
|
||
EBADF => unreachable, // Always a race condition.
|
||
ENOMEM => return error.SystemResources,
|
||
EACCES => return error.AccessDenied,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
|
||
const FStatAtError = FStatError || error{NameTooLong};
|
||
|
||
pub fn fstatat(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError![]Stat {
|
||
const pathname_c = try toPosixPath(pathname);
|
||
return fstatatC(dirfd, &pathname_c, flags);
|
||
}
|
||
|
||
pub fn fstatatC(dirfd: fd_t, pathname: [*:0]const u8, flags: u32) FStatAtError!Stat {
|
||
var stat: Stat = undefined;
|
||
switch (errno(system.fstatat(dirfd, pathname, &stat, flags))) {
|
||
0 => return stat,
|
||
EINVAL => unreachable,
|
||
EBADF => unreachable, // Always a race condition.
|
||
ENOMEM => return error.SystemResources,
|
||
EACCES => return error.AccessDenied,
|
||
EFAULT => unreachable,
|
||
ENAMETOOLONG => return error.NameTooLong,
|
||
ENOENT => return error.FileNotFound,
|
||
ENOTDIR => return error.FileNotFound,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
|
||
pub const KQueueError = error{
|
||
/// The per-process limit on the number of open file descriptors has been reached.
|
||
ProcessFdQuotaExceeded,
|
||
|
||
/// The system-wide limit on the total number of open files has been reached.
|
||
SystemFdQuotaExceeded,
|
||
} || UnexpectedError;
|
||
|
||
pub fn kqueue() KQueueError!i32 {
|
||
const rc = system.kqueue();
|
||
switch (errno(rc)) {
|
||
0 => return @intCast(i32, rc),
|
||
EMFILE => return error.ProcessFdQuotaExceeded,
|
||
ENFILE => return error.SystemFdQuotaExceeded,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
|
||
pub const KEventError = error{
|
||
/// The process does not have permission to register a filter.
|
||
AccessDenied,
|
||
|
||
/// The event could not be found to be modified or deleted.
|
||
EventNotFound,
|
||
|
||
/// No memory was available to register the event.
|
||
SystemResources,
|
||
|
||
/// The specified process to attach to does not exist.
|
||
ProcessNotFound,
|
||
|
||
/// changelist or eventlist had too many items on it.
|
||
/// TODO remove this possibility
|
||
Overflow,
|
||
};
|
||
|
||
pub fn kevent(
|
||
kq: i32,
|
||
changelist: []const Kevent,
|
||
eventlist: []Kevent,
|
||
timeout: ?*const timespec,
|
||
) KEventError!usize {
|
||
while (true) {
|
||
const rc = system.kevent(
|
||
kq,
|
||
changelist.ptr,
|
||
try math.cast(c_int, changelist.len),
|
||
eventlist.ptr,
|
||
try math.cast(c_int, eventlist.len),
|
||
timeout,
|
||
);
|
||
switch (errno(rc)) {
|
||
0 => return @intCast(usize, rc),
|
||
EACCES => return error.AccessDenied,
|
||
EFAULT => unreachable,
|
||
EBADF => unreachable, // Always a race condition.
|
||
EINTR => continue,
|
||
EINVAL => unreachable,
|
||
ENOENT => return error.EventNotFound,
|
||
ENOMEM => return error.SystemResources,
|
||
ESRCH => return error.ProcessNotFound,
|
||
else => unreachable,
|
||
}
|
||
}
|
||
}
|
||
|
||
pub const INotifyInitError = error{
|
||
ProcessFdQuotaExceeded,
|
||
SystemFdQuotaExceeded,
|
||
SystemResources,
|
||
} || UnexpectedError;
|
||
|
||
/// initialize an inotify instance
|
||
pub fn inotify_init1(flags: u32) INotifyInitError!i32 {
|
||
const rc = system.inotify_init1(flags);
|
||
switch (errno(rc)) {
|
||
0 => return @intCast(i32, rc),
|
||
EINVAL => unreachable,
|
||
EMFILE => return error.ProcessFdQuotaExceeded,
|
||
ENFILE => return error.SystemFdQuotaExceeded,
|
||
ENOMEM => return error.SystemResources,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
|
||
pub const INotifyAddWatchError = error{
|
||
AccessDenied,
|
||
NameTooLong,
|
||
FileNotFound,
|
||
SystemResources,
|
||
UserResourceLimitReached,
|
||
} || UnexpectedError;
|
||
|
||
/// add a watch to an initialized inotify instance
|
||
pub fn inotify_add_watch(inotify_fd: i32, pathname: []const u8, mask: u32) INotifyAddWatchError!i32 {
|
||
const pathname_c = try toPosixPath(pathname);
|
||
return inotify_add_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: [*:0]const u8, mask: u32) INotifyAddWatchError!i32 {
|
||
const rc = system.inotify_add_watch(inotify_fd, pathname, mask);
|
||
switch (errno(rc)) {
|
||
0 => return @intCast(i32, rc),
|
||
EACCES => return error.AccessDenied,
|
||
EBADF => unreachable,
|
||
EFAULT => unreachable,
|
||
EINVAL => unreachable,
|
||
ENAMETOOLONG => return error.NameTooLong,
|
||
ENOENT => return error.FileNotFound,
|
||
ENOMEM => return error.SystemResources,
|
||
ENOSPC => return error.UserResourceLimitReached,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
|
||
/// remove an existing watch from an inotify instance
|
||
pub fn inotify_rm_watch(inotify_fd: i32, wd: i32) void {
|
||
switch (errno(system.inotify_rm_watch(inotify_fd, wd))) {
|
||
0 => return,
|
||
EBADF => unreachable,
|
||
EINVAL => unreachable,
|
||
else => unreachable,
|
||
}
|
||
}
|
||
|
||
pub const MProtectError = error{
|
||
/// The memory cannot be given the specified access. This can happen, for example, if you
|
||
/// mmap(2) a file to which you have read-only access, then ask mprotect() to mark it
|
||
/// PROT_WRITE.
|
||
AccessDenied,
|
||
|
||
/// Changing the protection of a memory region would result in the total number of map‐
|
||
/// pings with distinct attributes (e.g., read versus read/write protection) exceeding the
|
||
/// allowed maximum. (For example, making the protection of a range PROT_READ in the mid‐
|
||
/// dle of a region currently protected as PROT_READ|PROT_WRITE would result in three map‐
|
||
/// pings: two read/write mappings at each end and a read-only mapping in the middle.)
|
||
OutOfMemory,
|
||
} || UnexpectedError;
|
||
|
||
/// `memory.len` must be page-aligned.
|
||
pub fn mprotect(memory: []align(mem.page_size) u8, protection: u32) MProtectError!void {
|
||
assert(mem.isAligned(memory.len, mem.page_size));
|
||
switch (errno(system.mprotect(memory.ptr, memory.len, protection))) {
|
||
0 => return,
|
||
EINVAL => unreachable,
|
||
EACCES => return error.AccessDenied,
|
||
ENOMEM => return error.OutOfMemory,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
|
||
pub const ForkError = error{SystemResources} || UnexpectedError;
|
||
|
||
pub fn fork() ForkError!pid_t {
|
||
const rc = system.fork();
|
||
switch (errno(rc)) {
|
||
0 => return @intCast(pid_t, rc),
|
||
EAGAIN => return error.SystemResources,
|
||
ENOMEM => return error.SystemResources,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
|
||
pub const MMapError = error{
|
||
/// The underlying filesystem of the specified file does not support memory mapping.
|
||
MemoryMappingNotSupported,
|
||
|
||
/// A file descriptor refers to a non-regular file. Or a file mapping was requested,
|
||
/// but the file descriptor is not open for reading. Or `MAP_SHARED` was requested
|
||
/// and `PROT_WRITE` is set, but the file descriptor is not open in `O_RDWR` mode.
|
||
/// Or `PROT_WRITE` is set, but the file is append-only.
|
||
AccessDenied,
|
||
|
||
/// The `prot` argument asks for `PROT_EXEC` but the mapped area belongs to a file on
|
||
/// a filesystem that was mounted no-exec.
|
||
PermissionDenied,
|
||
LockedMemoryLimitExceeded,
|
||
OutOfMemory,
|
||
} || UnexpectedError;
|
||
|
||
/// Map files or devices into memory.
|
||
/// `length` does not need to be aligned.
|
||
/// Use of a mapped region can result in these signals:
|
||
/// * SIGSEGV - Attempted write into a region mapped as read-only.
|
||
/// * SIGBUS - Attempted access to a portion of the buffer that does not correspond to the file
|
||
pub fn mmap(
|
||
ptr: ?[*]align(mem.page_size) u8,
|
||
length: usize,
|
||
prot: u32,
|
||
flags: u32,
|
||
fd: fd_t,
|
||
offset: u64,
|
||
) MMapError![]align(mem.page_size) u8 {
|
||
const err = if (builtin.link_libc) blk: {
|
||
const rc = std.c.mmap(ptr, length, prot, flags, fd, offset);
|
||
if (rc != std.c.MAP_FAILED) return @ptrCast([*]align(mem.page_size) u8, @alignCast(mem.page_size, rc))[0..length];
|
||
break :blk @intCast(usize, system._errno().*);
|
||
} else blk: {
|
||
const rc = system.mmap(ptr, length, prot, flags, fd, offset);
|
||
const err = errno(rc);
|
||
if (err == 0) return @intToPtr([*]align(mem.page_size) u8, rc)[0..length];
|
||
break :blk err;
|
||
};
|
||
switch (err) {
|
||
ETXTBSY => return error.AccessDenied,
|
||
EACCES => return error.AccessDenied,
|
||
EPERM => return error.PermissionDenied,
|
||
EAGAIN => return error.LockedMemoryLimitExceeded,
|
||
EBADF => unreachable, // Always a race condition.
|
||
EOVERFLOW => unreachable, // The number of pages used for length + offset would overflow.
|
||
ENODEV => return error.MemoryMappingNotSupported,
|
||
EINVAL => unreachable, // Invalid parameters to mmap()
|
||
ENOMEM => return error.OutOfMemory,
|
||
else => return unexpectedErrno(err),
|
||
}
|
||
}
|
||
|
||
/// Deletes the mappings for the specified address range, causing
|
||
/// further references to addresses within the range to generate invalid memory references.
|
||
/// Note that while POSIX allows unmapping a region in the middle of an existing mapping,
|
||
/// Zig's munmap function does not, for two reasons:
|
||
/// * It violates the Zig principle that resource deallocation must succeed.
|
||
/// * The Windows function, VirtualFree, has this restriction.
|
||
pub fn munmap(memory: []align(mem.page_size) u8) void {
|
||
switch (errno(system.munmap(memory.ptr, memory.len))) {
|
||
0 => return,
|
||
EINVAL => unreachable, // Invalid parameters.
|
||
ENOMEM => unreachable, // Attempted to unmap a region in the middle of an existing mapping.
|
||
else => unreachable,
|
||
}
|
||
}
|
||
|
||
pub const AccessError = error{
|
||
PermissionDenied,
|
||
FileNotFound,
|
||
NameTooLong,
|
||
InputOutput,
|
||
SystemResources,
|
||
BadPathName,
|
||
FileBusy,
|
||
SymLinkLoop,
|
||
ReadOnlyFileSystem,
|
||
|
||
/// On Windows, file paths must be valid Unicode.
|
||
InvalidUtf8,
|
||
} || UnexpectedError;
|
||
|
||
/// check user's permissions for a file
|
||
/// TODO currently this assumes `mode` is `F_OK` on Windows.
|
||
pub fn access(path: []const u8, mode: u32) AccessError!void {
|
||
if (builtin.os.tag == .windows) {
|
||
const path_w = try windows.sliceToPrefixedFileW(path);
|
||
_ = try windows.GetFileAttributesW(&path_w);
|
||
return;
|
||
}
|
||
const path_c = try toPosixPath(path);
|
||
return accessC(&path_c, mode);
|
||
}
|
||
|
||
/// Deprecated in favor of `accessZ`.
|
||
pub const accessC = accessZ;
|
||
|
||
/// Same as `access` except `path` is null-terminated.
|
||
pub fn accessZ(path: [*:0]const u8, mode: u32) AccessError!void {
|
||
if (builtin.os.tag == .windows) {
|
||
const path_w = try windows.cStrToPrefixedFileW(path);
|
||
_ = try windows.GetFileAttributesW(&path_w);
|
||
return;
|
||
}
|
||
switch (errno(system.access(path, mode))) {
|
||
0 => return,
|
||
EACCES => return error.PermissionDenied,
|
||
EROFS => return error.ReadOnlyFileSystem,
|
||
ELOOP => return error.SymLinkLoop,
|
||
ETXTBSY => return error.FileBusy,
|
||
ENOTDIR => return error.FileNotFound,
|
||
ENOENT => return error.FileNotFound,
|
||
ENAMETOOLONG => return error.NameTooLong,
|
||
EINVAL => unreachable,
|
||
EFAULT => unreachable,
|
||
EIO => return error.InputOutput,
|
||
ENOMEM => return error.SystemResources,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
|
||
/// Call from Windows-specific code if you already have a UTF-16LE encoded, null terminated string.
|
||
/// Otherwise use `access` or `accessC`.
|
||
/// TODO currently this ignores `mode`.
|
||
pub fn accessW(path: [*:0]const u16, mode: u32) windows.GetFileAttributesError!void {
|
||
const ret = try windows.GetFileAttributesW(path);
|
||
if (ret != windows.INVALID_FILE_ATTRIBUTES) {
|
||
return;
|
||
}
|
||
switch (windows.kernel32.GetLastError()) {
|
||
.FILE_NOT_FOUND => return error.FileNotFound,
|
||
.PATH_NOT_FOUND => return error.FileNotFound,
|
||
.ACCESS_DENIED => return error.PermissionDenied,
|
||
else => |err| return windows.unexpectedError(err),
|
||
}
|
||
}
|
||
|
||
/// Check user's permissions for a file, based on an open directory handle.
|
||
/// TODO currently this ignores `mode` and `flags` on Windows.
|
||
pub fn faccessat(dirfd: fd_t, path: []const u8, mode: u32, flags: u32) AccessError!void {
|
||
if (builtin.os.tag == .windows) {
|
||
const path_w = try windows.sliceToPrefixedFileW(path);
|
||
return faccessatW(dirfd, &path_w, mode, flags);
|
||
}
|
||
const path_c = try toPosixPath(path);
|
||
return faccessatZ(dirfd, &path_c, mode, flags);
|
||
}
|
||
|
||
/// Same as `faccessat` except the path parameter is null-terminated.
|
||
pub fn faccessatZ(dirfd: fd_t, path: [*:0]const u8, mode: u32, flags: u32) AccessError!void {
|
||
if (builtin.os.tag == .windows) {
|
||
const path_w = try windows.cStrToPrefixedFileW(path);
|
||
return faccessatW(dirfd, &path_w, mode, flags);
|
||
}
|
||
switch (errno(system.faccessat(dirfd, path, mode, flags))) {
|
||
0 => return,
|
||
EACCES => return error.PermissionDenied,
|
||
EROFS => return error.ReadOnlyFileSystem,
|
||
ELOOP => return error.SymLinkLoop,
|
||
ETXTBSY => return error.FileBusy,
|
||
ENOTDIR => return error.FileNotFound,
|
||
ENOENT => return error.FileNotFound,
|
||
ENAMETOOLONG => return error.NameTooLong,
|
||
EINVAL => unreachable,
|
||
EFAULT => unreachable,
|
||
EIO => return error.InputOutput,
|
||
ENOMEM => return error.SystemResources,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
|
||
/// Same as `faccessat` except asserts the target is Windows and the path parameter
|
||
/// is NtDll-prefixed, null-terminated, WTF-16 encoded.
|
||
/// TODO currently this ignores `mode` and `flags`
|
||
pub fn faccessatW(dirfd: fd_t, sub_path_w: [*:0]const u16, mode: u32, flags: u32) AccessError!void {
|
||
if (sub_path_w[0] == '.' and sub_path_w[1] == 0) {
|
||
return;
|
||
}
|
||
if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) {
|
||
return;
|
||
}
|
||
|
||
const path_len_bytes = math.cast(u16, mem.toSliceConst(u16, sub_path_w).len * 2) catch |err| switch (err) {
|
||
error.Overflow => return error.NameTooLong,
|
||
};
|
||
var nt_name = windows.UNICODE_STRING{
|
||
.Length = path_len_bytes,
|
||
.MaximumLength = path_len_bytes,
|
||
.Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w)),
|
||
};
|
||
var attr = windows.OBJECT_ATTRIBUTES{
|
||
.Length = @sizeOf(windows.OBJECT_ATTRIBUTES),
|
||
.RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w)) null else dirfd,
|
||
.Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
|
||
.ObjectName = &nt_name,
|
||
.SecurityDescriptor = null,
|
||
.SecurityQualityOfService = null,
|
||
};
|
||
var basic_info: windows.FILE_BASIC_INFORMATION = undefined;
|
||
switch (windows.ntdll.NtQueryAttributesFile(&attr, &basic_info)) {
|
||
.SUCCESS => return,
|
||
.OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
|
||
.OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
|
||
.INVALID_PARAMETER => unreachable,
|
||
.ACCESS_DENIED => return error.PermissionDenied,
|
||
.OBJECT_PATH_SYNTAX_BAD => unreachable,
|
||
else => |rc| return windows.unexpectedStatus(rc),
|
||
}
|
||
}
|
||
|
||
pub const PipeError = error{
|
||
SystemFdQuotaExceeded,
|
||
ProcessFdQuotaExceeded,
|
||
} || UnexpectedError;
|
||
|
||
/// Creates a unidirectional data channel that can be used for interprocess communication.
|
||
pub fn pipe() PipeError![2]fd_t {
|
||
var fds: [2]fd_t = undefined;
|
||
switch (errno(system.pipe(&fds))) {
|
||
0 => return fds,
|
||
EINVAL => unreachable, // Invalid parameters to pipe()
|
||
EFAULT => unreachable, // Invalid fds pointer
|
||
ENFILE => return error.SystemFdQuotaExceeded,
|
||
EMFILE => return error.ProcessFdQuotaExceeded,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
|
||
pub fn pipe2(flags: u32) PipeError![2]fd_t {
|
||
if (comptime std.Target.current.isDarwin()) {
|
||
var fds: [2]fd_t = try pipe();
|
||
if (flags == 0) return fds;
|
||
errdefer {
|
||
close(fds[0]);
|
||
close(fds[1]);
|
||
}
|
||
for (fds) |fd| switch (errno(system.fcntl(fd, F_SETFL, flags))) {
|
||
0 => {},
|
||
EINVAL => unreachable, // Invalid flags
|
||
EBADF => unreachable, // Always a race condition
|
||
else => |err| return unexpectedErrno(err),
|
||
};
|
||
return fds;
|
||
}
|
||
|
||
var fds: [2]fd_t = undefined;
|
||
switch (errno(system.pipe2(&fds, flags))) {
|
||
0 => return fds,
|
||
EINVAL => unreachable, // Invalid flags
|
||
EFAULT => unreachable, // Invalid fds pointer
|
||
ENFILE => return error.SystemFdQuotaExceeded,
|
||
EMFILE => return error.ProcessFdQuotaExceeded,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
|
||
pub const SysCtlError = error{
|
||
PermissionDenied,
|
||
SystemResources,
|
||
NameTooLong,
|
||
UnknownName,
|
||
} || UnexpectedError;
|
||
|
||
pub fn sysctl(
|
||
name: []const c_int,
|
||
oldp: ?*c_void,
|
||
oldlenp: ?*usize,
|
||
newp: ?*c_void,
|
||
newlen: usize,
|
||
) SysCtlError!void {
|
||
const name_len = math.cast(c_uint, name.len) catch return error.NameTooLong;
|
||
switch (errno(system.sysctl(name.ptr, name_len, oldp, oldlenp, newp, newlen))) {
|
||
0 => return,
|
||
EFAULT => unreachable,
|
||
EPERM => return error.PermissionDenied,
|
||
ENOMEM => return error.SystemResources,
|
||
ENOENT => return error.UnknownName,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
|
||
pub fn sysctlbynameC(
|
||
name: [*:0]const u8,
|
||
oldp: ?*c_void,
|
||
oldlenp: ?*usize,
|
||
newp: ?*c_void,
|
||
newlen: usize,
|
||
) SysCtlError!void {
|
||
switch (errno(system.sysctlbyname(name, oldp, oldlenp, newp, newlen))) {
|
||
0 => return,
|
||
EFAULT => unreachable,
|
||
EPERM => return error.PermissionDenied,
|
||
ENOMEM => return error.SystemResources,
|
||
ENOENT => return error.UnknownName,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
|
||
pub fn gettimeofday(tv: ?*timeval, tz: ?*timezone) void {
|
||
switch (errno(system.gettimeofday(tv, tz))) {
|
||
0 => return,
|
||
EINVAL => unreachable,
|
||
else => unreachable,
|
||
}
|
||
}
|
||
|
||
pub const SeekError = error{Unseekable} || UnexpectedError;
|
||
|
||
/// Repositions read/write file offset relative to the beginning.
|
||
pub fn lseek_SET(fd: fd_t, offset: u64) SeekError!void {
|
||
if (builtin.os.tag == .linux and !builtin.link_libc and @sizeOf(usize) == 4) {
|
||
var result: u64 = undefined;
|
||
switch (errno(system.llseek(fd, offset, &result, SEEK_SET))) {
|
||
0 => return,
|
||
EBADF => unreachable, // always a race condition
|
||
EINVAL => return error.Unseekable,
|
||
EOVERFLOW => return error.Unseekable,
|
||
ESPIPE => return error.Unseekable,
|
||
ENXIO => return error.Unseekable,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
if (builtin.os.tag == .windows) {
|
||
return windows.SetFilePointerEx_BEGIN(fd, offset);
|
||
}
|
||
const ipos = @bitCast(i64, offset); // the OS treats this as unsigned
|
||
switch (errno(system.lseek(fd, ipos, SEEK_SET))) {
|
||
0 => return,
|
||
EBADF => unreachable, // always a race condition
|
||
EINVAL => return error.Unseekable,
|
||
EOVERFLOW => return error.Unseekable,
|
||
ESPIPE => return error.Unseekable,
|
||
ENXIO => return error.Unseekable,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
|
||
/// Repositions read/write file offset relative to the current offset.
|
||
pub fn lseek_CUR(fd: fd_t, offset: i64) SeekError!void {
|
||
if (builtin.os.tag == .linux and !builtin.link_libc and @sizeOf(usize) == 4) {
|
||
var result: u64 = undefined;
|
||
switch (errno(system.llseek(fd, @bitCast(u64, offset), &result, SEEK_CUR))) {
|
||
0 => return,
|
||
EBADF => unreachable, // always a race condition
|
||
EINVAL => return error.Unseekable,
|
||
EOVERFLOW => return error.Unseekable,
|
||
ESPIPE => return error.Unseekable,
|
||
ENXIO => return error.Unseekable,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
if (builtin.os.tag == .windows) {
|
||
return windows.SetFilePointerEx_CURRENT(fd, offset);
|
||
}
|
||
switch (errno(system.lseek(fd, offset, SEEK_CUR))) {
|
||
0 => return,
|
||
EBADF => unreachable, // always a race condition
|
||
EINVAL => return error.Unseekable,
|
||
EOVERFLOW => return error.Unseekable,
|
||
ESPIPE => return error.Unseekable,
|
||
ENXIO => return error.Unseekable,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
|
||
/// Repositions read/write file offset relative to the end.
|
||
pub fn lseek_END(fd: fd_t, offset: i64) SeekError!void {
|
||
if (builtin.os.tag == .linux and !builtin.link_libc and @sizeOf(usize) == 4) {
|
||
var result: u64 = undefined;
|
||
switch (errno(system.llseek(fd, @bitCast(u64, offset), &result, SEEK_END))) {
|
||
0 => return,
|
||
EBADF => unreachable, // always a race condition
|
||
EINVAL => return error.Unseekable,
|
||
EOVERFLOW => return error.Unseekable,
|
||
ESPIPE => return error.Unseekable,
|
||
ENXIO => return error.Unseekable,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
if (builtin.os.tag == .windows) {
|
||
return windows.SetFilePointerEx_END(fd, offset);
|
||
}
|
||
switch (errno(system.lseek(fd, offset, SEEK_END))) {
|
||
0 => return,
|
||
EBADF => unreachable, // always a race condition
|
||
EINVAL => return error.Unseekable,
|
||
EOVERFLOW => return error.Unseekable,
|
||
ESPIPE => return error.Unseekable,
|
||
ENXIO => return error.Unseekable,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
|
||
/// Returns the read/write file offset relative to the beginning.
|
||
pub fn lseek_CUR_get(fd: fd_t) SeekError!u64 {
|
||
if (builtin.os.tag == .linux and !builtin.link_libc and @sizeOf(usize) == 4) {
|
||
var result: u64 = undefined;
|
||
switch (errno(system.llseek(fd, 0, &result, SEEK_CUR))) {
|
||
0 => return result,
|
||
EBADF => unreachable, // always a race condition
|
||
EINVAL => return error.Unseekable,
|
||
EOVERFLOW => return error.Unseekable,
|
||
ESPIPE => return error.Unseekable,
|
||
ENXIO => return error.Unseekable,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
if (builtin.os.tag == .windows) {
|
||
return windows.SetFilePointerEx_CURRENT_get(fd);
|
||
}
|
||
const rc = system.lseek(fd, 0, SEEK_CUR);
|
||
switch (errno(rc)) {
|
||
0 => return @bitCast(u64, rc),
|
||
EBADF => unreachable, // always a race condition
|
||
EINVAL => return error.Unseekable,
|
||
EOVERFLOW => return error.Unseekable,
|
||
ESPIPE => return error.Unseekable,
|
||
ENXIO => return error.Unseekable,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
|
||
pub const FcntlError = error{
|
||
PermissionDenied,
|
||
FileBusy,
|
||
ProcessFdQuotaExceeded,
|
||
Locked,
|
||
} || UnexpectedError;
|
||
|
||
pub fn fcntl(fd: fd_t, cmd: i32, arg: usize) FcntlError!usize {
|
||
while (true) {
|
||
const rc = system.fcntl(fd, cmd, arg);
|
||
switch (errno(rc)) {
|
||
0 => return @intCast(usize, rc),
|
||
EINTR => continue,
|
||
EACCES => return error.Locked,
|
||
EBADF => unreachable,
|
||
EBUSY => return error.FileBusy,
|
||
EINVAL => unreachable, // invalid parameters
|
||
EPERM => return error.PermissionDenied,
|
||
EMFILE => return error.ProcessFdQuotaExceeded,
|
||
ENOTDIR => unreachable, // invalid parameter
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
}
|
||
|
||
pub const RealPathError = error{
|
||
FileNotFound,
|
||
AccessDenied,
|
||
NameTooLong,
|
||
NotSupported,
|
||
NotDir,
|
||
SymLinkLoop,
|
||
InputOutput,
|
||
FileTooBig,
|
||
IsDir,
|
||
ProcessFdQuotaExceeded,
|
||
SystemFdQuotaExceeded,
|
||
NoDevice,
|
||
SystemResources,
|
||
NoSpaceLeft,
|
||
FileSystem,
|
||
BadPathName,
|
||
DeviceBusy,
|
||
|
||
SharingViolation,
|
||
PipeBusy,
|
||
|
||
/// On Windows, file paths must be valid Unicode.
|
||
InvalidUtf8,
|
||
|
||
PathAlreadyExists,
|
||
} || UnexpectedError;
|
||
|
||
/// Return the canonicalized absolute pathname.
|
||
/// Expands all symbolic links and resolves references to `.`, `..`, and
|
||
/// extra `/` characters in `pathname`.
|
||
/// The return value is a slice of `out_buffer`, but not necessarily from the beginning.
|
||
/// See also `realpathC` and `realpathW`.
|
||
pub fn realpath(pathname: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
|
||
if (builtin.os.tag == .windows) {
|
||
const pathname_w = try windows.sliceToPrefixedFileW(pathname);
|
||
return realpathW(&pathname_w, out_buffer);
|
||
}
|
||
const pathname_c = try toPosixPath(pathname);
|
||
return realpathC(&pathname_c, out_buffer);
|
||
}
|
||
|
||
/// Same as `realpath` except `pathname` is null-terminated.
|
||
pub fn realpathC(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
|
||
if (builtin.os.tag == .windows) {
|
||
const pathname_w = try windows.cStrToPrefixedFileW(pathname);
|
||
return realpathW(&pathname_w, out_buffer);
|
||
}
|
||
if (builtin.os.tag == .linux and !builtin.link_libc) {
|
||
const fd = try openC(pathname, linux.O_PATH | linux.O_NONBLOCK | linux.O_CLOEXEC, 0);
|
||
defer close(fd);
|
||
|
||
var procfs_buf: ["/proc/self/fd/-2147483648".len:0]u8 = undefined;
|
||
const proc_path = std.fmt.bufPrint(procfs_buf[0..], "/proc/self/fd/{}\x00", .{fd}) catch unreachable;
|
||
|
||
return readlinkC(@ptrCast([*:0]const u8, proc_path.ptr), out_buffer);
|
||
}
|
||
const result_path = std.c.realpath(pathname, out_buffer) orelse switch (std.c._errno().*) {
|
||
EINVAL => unreachable,
|
||
EBADF => unreachable,
|
||
EFAULT => unreachable,
|
||
EACCES => return error.AccessDenied,
|
||
ENOENT => return error.FileNotFound,
|
||
ENOTSUP => return error.NotSupported,
|
||
ENOTDIR => return error.NotDir,
|
||
ENAMETOOLONG => return error.NameTooLong,
|
||
ELOOP => return error.SymLinkLoop,
|
||
EIO => return error.InputOutput,
|
||
else => |err| return unexpectedErrno(@intCast(usize, err)),
|
||
};
|
||
return mem.toSlice(u8, result_path);
|
||
}
|
||
|
||
/// Same as `realpath` except `pathname` is null-terminated and UTF16LE-encoded.
|
||
/// TODO use ntdll for better semantics
|
||
pub fn realpathW(pathname: [*:0]const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
|
||
const h_file = try windows.CreateFileW(
|
||
pathname,
|
||
windows.GENERIC_READ,
|
||
windows.FILE_SHARE_READ,
|
||
null,
|
||
windows.OPEN_EXISTING,
|
||
windows.FILE_FLAG_BACKUP_SEMANTICS,
|
||
null,
|
||
);
|
||
defer windows.CloseHandle(h_file);
|
||
|
||
var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined;
|
||
const wide_slice = try windows.GetFinalPathNameByHandleW(h_file, &wide_buf, wide_buf.len, windows.VOLUME_NAME_DOS);
|
||
|
||
// Windows returns \\?\ prepended to the path.
|
||
// We strip it to make this function consistent across platforms.
|
||
const prefix = [_]u16{ '\\', '\\', '?', '\\' };
|
||
const start_index = if (mem.startsWith(u16, wide_slice, &prefix)) prefix.len else 0;
|
||
|
||
// Trust that Windows gives us valid UTF-16LE.
|
||
const end_index = std.unicode.utf16leToUtf8(out_buffer, wide_slice[start_index..]) catch unreachable;
|
||
return out_buffer[0..end_index];
|
||
}
|
||
|
||
/// Spurious wakeups are possible and no precision of timing is guaranteed.
|
||
pub fn nanosleep(seconds: u64, nanoseconds: u64) void {
|
||
var req = timespec{
|
||
.tv_sec = math.cast(isize, seconds) catch math.maxInt(isize),
|
||
.tv_nsec = math.cast(isize, nanoseconds) catch math.maxInt(isize),
|
||
};
|
||
var rem: timespec = undefined;
|
||
while (true) {
|
||
switch (errno(system.nanosleep(&req, &rem))) {
|
||
EFAULT => unreachable,
|
||
EINVAL => {
|
||
// Sometimes Darwin returns EINVAL for no reason.
|
||
// We treat it as a spurious wakeup.
|
||
return;
|
||
},
|
||
EINTR => {
|
||
req = rem;
|
||
continue;
|
||
},
|
||
// This prong handles success as well as unexpected errors.
|
||
else => return,
|
||
}
|
||
}
|
||
}
|
||
|
||
pub fn dl_iterate_phdr(
|
||
context: var,
|
||
comptime Error: type,
|
||
comptime callback: fn (info: *dl_phdr_info, size: usize, context: @TypeOf(context)) Error!void,
|
||
) Error!void {
|
||
const Context = @TypeOf(context);
|
||
|
||
if (builtin.object_format != .elf)
|
||
@compileError("dl_iterate_phdr is not available for this target");
|
||
|
||
if (builtin.link_libc) {
|
||
switch (system.dl_iterate_phdr(struct {
|
||
fn callbackC(info: *dl_phdr_info, size: usize, data: ?*c_void) callconv(.C) c_int {
|
||
const context_ptr = @ptrCast(*const Context, @alignCast(@alignOf(*const Context), data));
|
||
callback(info, size, context_ptr.*) catch |err| return @errorToInt(err);
|
||
return 0;
|
||
}
|
||
}.callbackC, @intToPtr(?*c_void, @ptrToInt(&context)))) {
|
||
0 => return,
|
||
else => |err| return @errSetCast(Error, @intToError(@intCast(u16, err))), // TODO don't hardcode u16
|
||
}
|
||
}
|
||
|
||
const elf_base = std.process.getBaseAddress();
|
||
const ehdr = @intToPtr(*elf.Ehdr, elf_base);
|
||
// Make sure the base address points to an ELF image
|
||
assert(mem.eql(u8, ehdr.e_ident[0..4], "\x7fELF"));
|
||
const n_phdr = ehdr.e_phnum;
|
||
const phdrs = (@intToPtr([*]elf.Phdr, elf_base + ehdr.e_phoff))[0..n_phdr];
|
||
|
||
var it = dl.linkmap_iterator(phdrs) catch unreachable;
|
||
|
||
// The executable has no dynamic link segment, create a single entry for
|
||
// the whole ELF image
|
||
if (it.end()) {
|
||
var info = dl_phdr_info{
|
||
.dlpi_addr = 0,
|
||
.dlpi_name = "/proc/self/exe",
|
||
.dlpi_phdr = phdrs.ptr,
|
||
.dlpi_phnum = ehdr.e_phnum,
|
||
};
|
||
|
||
return callback(&info, @sizeOf(dl_phdr_info), context);
|
||
}
|
||
|
||
// Last return value from the callback function
|
||
while (it.next()) |entry| {
|
||
var dlpi_phdr: [*]elf.Phdr = undefined;
|
||
var dlpi_phnum: u16 = undefined;
|
||
|
||
if (entry.l_addr != 0) {
|
||
const elf_header = @intToPtr(*elf.Ehdr, entry.l_addr);
|
||
dlpi_phdr = @intToPtr([*]elf.Phdr, entry.l_addr + elf_header.e_phoff);
|
||
dlpi_phnum = elf_header.e_phnum;
|
||
} else {
|
||
// This is the running ELF image
|
||
dlpi_phdr = @intToPtr([*]elf.Phdr, elf_base + ehdr.e_phoff);
|
||
dlpi_phnum = ehdr.e_phnum;
|
||
}
|
||
|
||
var info = dl_phdr_info{
|
||
.dlpi_addr = entry.l_addr,
|
||
.dlpi_name = entry.l_name,
|
||
.dlpi_phdr = dlpi_phdr,
|
||
.dlpi_phnum = dlpi_phnum,
|
||
};
|
||
|
||
try callback(&info, @sizeOf(dl_phdr_info), context);
|
||
}
|
||
}
|
||
|
||
pub const ClockGetTimeError = error{UnsupportedClock} || UnexpectedError;
|
||
|
||
pub fn clock_gettime(clk_id: i32, tp: *timespec) ClockGetTimeError!void {
|
||
if (std.Target.current.os.tag == .wasi) {
|
||
var ts: timestamp_t = undefined;
|
||
switch (system.clock_time_get(@bitCast(u32, clk_id), 1, &ts)) {
|
||
0 => {
|
||
tp.* = .{
|
||
.tv_sec = @intCast(i64, ts / std.time.ns_per_s),
|
||
.tv_nsec = @intCast(isize, ts % std.time.ns_per_s),
|
||
};
|
||
},
|
||
EINVAL => return error.UnsupportedClock,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
return;
|
||
}
|
||
switch (errno(system.clock_gettime(clk_id, tp))) {
|
||
0 => return,
|
||
EFAULT => unreachable,
|
||
EINVAL => return error.UnsupportedClock,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
|
||
pub fn clock_getres(clk_id: i32, res: *timespec) ClockGetTimeError!void {
|
||
if (std.Target.current.os.tag == .wasi) {
|
||
var ts: timestamp_t = undefined;
|
||
switch (system.clock_res_get(@bitCast(u32, clk_id), &ts)) {
|
||
0 => res.* = .{
|
||
.tv_sec = @intCast(i64, ts / std.time.ns_per_s),
|
||
.tv_nsec = @intCast(isize, ts % std.time.ns_per_s),
|
||
},
|
||
EINVAL => return error.UnsupportedClock,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
return;
|
||
}
|
||
|
||
switch (errno(system.clock_getres(clk_id, res))) {
|
||
0 => return,
|
||
EFAULT => unreachable,
|
||
EINVAL => return error.UnsupportedClock,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
|
||
pub const SchedGetAffinityError = error{PermissionDenied} || UnexpectedError;
|
||
|
||
pub fn sched_getaffinity(pid: pid_t) SchedGetAffinityError!cpu_set_t {
|
||
var set: cpu_set_t = undefined;
|
||
switch (errno(system.sched_getaffinity(pid, @sizeOf(cpu_set_t), &set))) {
|
||
0 => return set,
|
||
EFAULT => unreachable,
|
||
EINVAL => unreachable,
|
||
ESRCH => unreachable,
|
||
EPERM => return error.PermissionDenied,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
|
||
/// Used to convert a slice to a null terminated slice on the stack.
|
||
/// TODO https://github.com/ziglang/zig/issues/287
|
||
pub fn toPosixPath(file_path: []const u8) ![PATH_MAX - 1:0]u8 {
|
||
if (std.debug.runtime_safety) assert(std.mem.indexOfScalar(u8, file_path, 0) == null);
|
||
var path_with_null: [PATH_MAX - 1:0]u8 = undefined;
|
||
// >= rather than > to make room for the null byte
|
||
if (file_path.len >= PATH_MAX) return error.NameTooLong;
|
||
mem.copy(u8, &path_with_null, file_path);
|
||
path_with_null[file_path.len] = 0;
|
||
return path_with_null;
|
||
}
|
||
|
||
/// Whether or not error.Unexpected will print its value and a stack trace.
|
||
/// if this happens the fix is to add the error code to the corresponding
|
||
/// switch expression, possibly introduce a new error in the error set, and
|
||
/// send a patch to Zig.
|
||
pub const unexpected_error_tracing = builtin.mode == .Debug;
|
||
|
||
pub const UnexpectedError = error{
|
||
/// The Operating System returned an undocumented error code.
|
||
/// This error is in theory not possible, but it would be better
|
||
/// to handle this error than to invoke undefined behavior.
|
||
Unexpected,
|
||
};
|
||
|
||
/// Call this when you made a syscall or something that sets errno
|
||
/// and you get an unexpected error.
|
||
pub fn unexpectedErrno(err: usize) UnexpectedError {
|
||
if (unexpected_error_tracing) {
|
||
std.debug.warn("unexpected errno: {}\n", .{err});
|
||
std.debug.dumpCurrentStackTrace(null);
|
||
}
|
||
return error.Unexpected;
|
||
}
|
||
|
||
pub const SigaltstackError = error{
|
||
/// The supplied stack size was less than MINSIGSTKSZ.
|
||
SizeTooSmall,
|
||
|
||
/// Attempted to change the signal stack while it was active.
|
||
PermissionDenied,
|
||
} || UnexpectedError;
|
||
|
||
pub fn sigaltstack(ss: ?*stack_t, old_ss: ?*stack_t) SigaltstackError!void {
|
||
if (builtin.os.tag == .windows or builtin.os.tag == .uefi or builtin.os.tag == .wasi)
|
||
@compileError("std.os.sigaltstack not available for this target");
|
||
|
||
switch (errno(system.sigaltstack(ss, old_ss))) {
|
||
0 => return,
|
||
EFAULT => unreachable,
|
||
EINVAL => unreachable,
|
||
ENOMEM => return error.SizeTooSmall,
|
||
EPERM => return error.PermissionDenied,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
|
||
/// Examine and change a signal action.
|
||
pub fn sigaction(sig: u6, act: *const Sigaction, oact: ?*Sigaction) void {
|
||
switch (errno(system.sigaction(sig, act, oact))) {
|
||
0 => return,
|
||
EFAULT => unreachable,
|
||
EINVAL => unreachable,
|
||
else => unreachable,
|
||
}
|
||
}
|
||
|
||
pub const FutimensError = error{
|
||
/// times is NULL, or both tv_nsec values are UTIME_NOW, and either:
|
||
/// * the effective user ID of the caller does not match the owner
|
||
/// of the file, the caller does not have write access to the
|
||
/// file, and the caller is not privileged (Linux: does not have
|
||
/// either the CAP_FOWNER or the CAP_DAC_OVERRIDE capability);
|
||
/// or,
|
||
/// * the file is marked immutable (see chattr(1)).
|
||
AccessDenied,
|
||
|
||
/// The caller attempted to change one or both timestamps to a value
|
||
/// other than the current time, or to change one of the timestamps
|
||
/// to the current time while leaving the other timestamp unchanged,
|
||
/// (i.e., times is not NULL, neither tv_nsec field is UTIME_NOW,
|
||
/// and neither tv_nsec field is UTIME_OMIT) and either:
|
||
/// * the caller's effective user ID does not match the owner of
|
||
/// file, and the caller is not privileged (Linux: does not have
|
||
/// the CAP_FOWNER capability); or,
|
||
/// * the file is marked append-only or immutable (see chattr(1)).
|
||
PermissionDenied,
|
||
|
||
ReadOnlyFileSystem,
|
||
} || UnexpectedError;
|
||
|
||
pub fn futimens(fd: fd_t, times: *const [2]timespec) FutimensError!void {
|
||
switch (errno(system.futimens(fd, times))) {
|
||
0 => return,
|
||
EACCES => return error.AccessDenied,
|
||
EPERM => return error.PermissionDenied,
|
||
EBADF => unreachable, // always a race condition
|
||
EFAULT => unreachable,
|
||
EINVAL => unreachable,
|
||
EROFS => return error.ReadOnlyFileSystem,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
|
||
pub const GetHostNameError = error{PermissionDenied} || UnexpectedError;
|
||
|
||
pub fn gethostname(name_buffer: *[HOST_NAME_MAX]u8) GetHostNameError![]u8 {
|
||
if (builtin.link_libc) {
|
||
switch (errno(system.gethostname(name_buffer, name_buffer.len))) {
|
||
0 => return mem.toSlice(u8, @ptrCast([*:0]u8, name_buffer)),
|
||
EFAULT => unreachable,
|
||
ENAMETOOLONG => unreachable, // HOST_NAME_MAX prevents this
|
||
EPERM => return error.PermissionDenied,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
if (builtin.os.tag == .linux) {
|
||
const uts = uname();
|
||
const hostname = mem.toSliceConst(u8, @ptrCast([*:0]const u8, &uts.nodename));
|
||
mem.copy(u8, name_buffer, hostname);
|
||
return name_buffer[0..hostname.len];
|
||
}
|
||
|
||
@compileError("TODO implement gethostname for this OS");
|
||
}
|
||
|
||
pub fn uname() utsname {
|
||
var uts: utsname = undefined;
|
||
switch (errno(system.uname(&uts))) {
|
||
0 => return uts,
|
||
EFAULT => unreachable,
|
||
else => unreachable,
|
||
}
|
||
}
|
||
|
||
pub fn res_mkquery(
|
||
op: u4,
|
||
dname: []const u8,
|
||
class: u8,
|
||
ty: u8,
|
||
data: []const u8,
|
||
newrr: ?[*]const u8,
|
||
buf: []u8,
|
||
) usize {
|
||
// This implementation is ported from musl libc.
|
||
// A more idiomatic "ziggy" implementation would be welcome.
|
||
var name = dname;
|
||
if (mem.endsWith(u8, name, ".")) name.len -= 1;
|
||
assert(name.len <= 253);
|
||
const n = 17 + name.len + @boolToInt(name.len != 0);
|
||
|
||
// Construct query template - ID will be filled later
|
||
var q: [280]u8 = undefined;
|
||
@memset(&q, 0, n);
|
||
q[2] = @as(u8, op) * 8 + 1;
|
||
q[5] = 1;
|
||
mem.copy(u8, q[13..], name);
|
||
var i: usize = 13;
|
||
var j: usize = undefined;
|
||
while (q[i] != 0) : (i = j + 1) {
|
||
j = i;
|
||
while (q[j] != 0 and q[j] != '.') : (j += 1) {}
|
||
// TODO determine the circumstances for this and whether or
|
||
// not this should be an error.
|
||
if (j - i - 1 > 62) unreachable;
|
||
q[i - 1] = @intCast(u8, j - i);
|
||
}
|
||
q[i + 1] = ty;
|
||
q[i + 3] = class;
|
||
|
||
// Make a reasonably unpredictable id
|
||
var ts: timespec = undefined;
|
||
clock_gettime(CLOCK_REALTIME, &ts) catch {};
|
||
const UInt = std.meta.IntType(false, @TypeOf(ts.tv_nsec).bit_count);
|
||
const unsec = @bitCast(UInt, ts.tv_nsec);
|
||
const id = @truncate(u32, unsec + unsec / 65536);
|
||
q[0] = @truncate(u8, id / 256);
|
||
q[1] = @truncate(u8, id);
|
||
|
||
mem.copy(u8, buf, q[0..n]);
|
||
return n;
|
||
}
|
||
|
||
pub const SendError = error{
|
||
/// (For UNIX domain sockets, which are identified by pathname) Write permission is denied
|
||
/// on the destination socket file, or search permission is denied for one of the
|
||
/// directories the path prefix. (See path_resolution(7).)
|
||
/// (For UDP sockets) An attempt was made to send to a network/broadcast address as though
|
||
/// it was a unicast address.
|
||
AccessDenied,
|
||
|
||
/// The socket is marked nonblocking and the requested operation would block, and
|
||
/// there is no global event loop configured.
|
||
/// It's also possible to get this error under the following condition:
|
||
/// (Internet domain datagram sockets) The socket referred to by sockfd had not previously
|
||
/// been bound to an address and, upon attempting to bind it to an ephemeral port, it was
|
||
/// determined that all port numbers in the ephemeral port range are currently in use. See
|
||
/// the discussion of /proc/sys/net/ipv4/ip_local_port_range in ip(7).
|
||
WouldBlock,
|
||
|
||
/// Another Fast Open is already in progress.
|
||
FastOpenAlreadyInProgress,
|
||
|
||
/// Connection reset by peer.
|
||
ConnectionResetByPeer,
|
||
|
||
/// The socket type requires that message be sent atomically, and the size of the message
|
||
/// to be sent made this impossible. The message is not transmitted.
|
||
MessageTooBig,
|
||
|
||
/// The output queue for a network interface was full. This generally indicates that the
|
||
/// interface has stopped sending, but may be caused by transient congestion. (Normally,
|
||
/// this does not occur in Linux. Packets are just silently dropped when a device queue
|
||
/// overflows.)
|
||
/// This is also caused when there is not enough kernel memory available.
|
||
SystemResources,
|
||
|
||
/// The local end has been shut down on a connection oriented socket. In this case, the
|
||
/// process will also receive a SIGPIPE unless MSG_NOSIGNAL is set.
|
||
BrokenPipe,
|
||
} || UnexpectedError;
|
||
|
||
/// Transmit a message to another socket.
|
||
///
|
||
/// The `sendto` call may be used only when the socket is in a connected state (so that the intended
|
||
/// recipient is known). The following call
|
||
///
|
||
/// send(sockfd, buf, len, flags);
|
||
///
|
||
/// is equivalent to
|
||
///
|
||
/// sendto(sockfd, buf, len, flags, NULL, 0);
|
||
///
|
||
/// If sendto() is used on a connection-mode (`SOCK_STREAM`, `SOCK_SEQPACKET`) socket, the arguments
|
||
/// `dest_addr` and `addrlen` are asserted to be `null` and `0` respectively, and asserted
|
||
/// that the socket was actually connected.
|
||
/// Otherwise, the address of the target is given by `dest_addr` with `addrlen` specifying its size.
|
||
///
|
||
/// If the message is too long to pass atomically through the underlying protocol,
|
||
/// `SendError.MessageTooBig` is returned, and the message is not transmitted.
|
||
///
|
||
/// There is no indication of failure to deliver.
|
||
///
|
||
/// When the message does not fit into the send buffer of the socket, `sendto` normally blocks,
|
||
/// unless the socket has been placed in nonblocking I/O mode. In nonblocking mode it would fail
|
||
/// with `SendError.WouldBlock`. The `select` call may be used to determine when it is
|
||
/// possible to send more data.
|
||
pub fn sendto(
|
||
/// The file descriptor of the sending socket.
|
||
sockfd: fd_t,
|
||
/// Message to send.
|
||
buf: []const u8,
|
||
flags: u32,
|
||
dest_addr: ?*const sockaddr,
|
||
addrlen: socklen_t,
|
||
) SendError!usize {
|
||
while (true) {
|
||
const rc = system.sendto(sockfd, buf.ptr, buf.len, flags, dest_addr, addrlen);
|
||
switch (errno(rc)) {
|
||
0 => return @intCast(usize, rc),
|
||
|
||
EACCES => return error.AccessDenied,
|
||
EAGAIN => if (std.event.Loop.instance) |loop| {
|
||
loop.waitUntilFdWritable(sockfd);
|
||
continue;
|
||
} else {
|
||
return error.WouldBlock;
|
||
},
|
||
EALREADY => return error.FastOpenAlreadyInProgress,
|
||
EBADF => unreachable, // always a race condition
|
||
ECONNRESET => return error.ConnectionResetByPeer,
|
||
EDESTADDRREQ => unreachable, // The socket is not connection-mode, and no peer address is set.
|
||
EFAULT => unreachable, // An invalid user space address was specified for an argument.
|
||
EINTR => continue,
|
||
EINVAL => unreachable, // Invalid argument passed.
|
||
EISCONN => unreachable, // connection-mode socket was connected already but a recipient was specified
|
||
EMSGSIZE => return error.MessageTooBig,
|
||
ENOBUFS => return error.SystemResources,
|
||
ENOMEM => return error.SystemResources,
|
||
ENOTCONN => unreachable, // The socket is not connected, and no target has been given.
|
||
ENOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.
|
||
EOPNOTSUPP => unreachable, // Some bit in the flags argument is inappropriate for the socket type.
|
||
EPIPE => return error.BrokenPipe,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Transmit a message to another socket.
|
||
///
|
||
/// The `send` call may be used only when the socket is in a connected state (so that the intended
|
||
/// recipient is known). The only difference between `send` and `write` is the presence of
|
||
/// flags. With a zero flags argument, `send` is equivalent to `write`. Also, the following
|
||
/// call
|
||
///
|
||
/// send(sockfd, buf, len, flags);
|
||
///
|
||
/// is equivalent to
|
||
///
|
||
/// sendto(sockfd, buf, len, flags, NULL, 0);
|
||
///
|
||
/// There is no indication of failure to deliver.
|
||
///
|
||
/// When the message does not fit into the send buffer of the socket, `send` normally blocks,
|
||
/// unless the socket has been placed in nonblocking I/O mode. In nonblocking mode it would fail
|
||
/// with `SendError.WouldBlock`. The `select` call may be used to determine when it is
|
||
/// possible to send more data.
|
||
pub fn send(
|
||
/// The file descriptor of the sending socket.
|
||
sockfd: fd_t,
|
||
buf: []const u8,
|
||
flags: u32,
|
||
) SendError!usize {
|
||
return sendto(sockfd, buf, flags, null, 0);
|
||
}
|
||
|
||
pub const SendFileError = PReadError || WriteError || SendError;
|
||
|
||
fn count_iovec_bytes(iovs: []const iovec_const) usize {
|
||
var count: usize = 0;
|
||
for (iovs) |iov| {
|
||
count += iov.iov_len;
|
||
}
|
||
return count;
|
||
}
|
||
|
||
/// Transfer data between file descriptors, with optional headers and trailers.
|
||
/// Returns the number of bytes written, which can be zero.
|
||
///
|
||
/// The `sendfile` call copies `in_len` bytes from one file descriptor to another. When possible,
|
||
/// this is done within the operating system kernel, which can provide better performance
|
||
/// characteristics than transferring data from kernel to user space and back, such as with
|
||
/// `read` and `write` calls. When `in_len` is `0`, it means to copy until the end of the input file has been
|
||
/// reached. Note, however, that partial writes are still possible in this case.
|
||
///
|
||
/// `in_fd` must be a file descriptor opened for reading, and `out_fd` must be a file descriptor
|
||
/// opened for writing. They may be any kind of file descriptor; however, if `in_fd` is not a regular
|
||
/// file system file, it may cause this function to fall back to calling `read` and `write`, in which case
|
||
/// atomicity guarantees no longer apply.
|
||
///
|
||
/// Copying begins reading at `in_offset`. The input file descriptor seek position is ignored and not updated.
|
||
/// If the output file descriptor has a seek position, it is updated as bytes are written. When
|
||
/// `in_offset` is past the end of the input file, it successfully reads 0 bytes.
|
||
///
|
||
/// `flags` has different meanings per operating system; refer to the respective man pages.
|
||
///
|
||
/// These systems support atomically sending everything, including headers and trailers:
|
||
/// * macOS
|
||
/// * FreeBSD
|
||
///
|
||
/// These systems support in-kernel data copying, but headers and trailers are not sent atomically:
|
||
/// * Linux
|
||
///
|
||
/// Other systems fall back to calling `read` / `write`.
|
||
///
|
||
/// Linux has a limit on how many bytes may be transferred in one `sendfile` call, which is `0x7ffff000`
|
||
/// on both 64-bit and 32-bit systems. This is due to using a signed C int as the return value, as
|
||
/// well as stuffing the errno codes into the last `4096` values. This is cited on the `sendfile` man page.
|
||
/// The corresponding POSIX limit on this is `math.maxInt(isize)`.
|
||
pub fn sendfile(
|
||
out_fd: fd_t,
|
||
in_fd: fd_t,
|
||
in_offset: u64,
|
||
in_len: u64,
|
||
headers: []const iovec_const,
|
||
trailers: []const iovec_const,
|
||
flags: u32,
|
||
) SendFileError!usize {
|
||
var header_done = false;
|
||
var total_written: usize = 0;
|
||
|
||
// Prevents EOVERFLOW.
|
||
const size_t = @Type(std.builtin.TypeInfo{
|
||
.Int = .{
|
||
.is_signed = false,
|
||
.bits = @typeInfo(usize).Int.bits - 1,
|
||
},
|
||
});
|
||
const max_count = switch (std.Target.current.os.tag) {
|
||
.linux => 0x7ffff000,
|
||
else => math.maxInt(size_t),
|
||
};
|
||
|
||
switch (std.Target.current.os.tag) {
|
||
.linux => sf: {
|
||
// sendfile() first appeared in Linux 2.2, glibc 2.1.
|
||
const call_sf = comptime if (builtin.link_libc)
|
||
std.c.versionCheck(.{ .major = 2, .minor = 1 }).ok
|
||
else
|
||
std.Target.current.os.version_range.linux.range.max.order(.{ .major = 2, .minor = 2 }) != .lt;
|
||
if (!call_sf) break :sf;
|
||
|
||
if (headers.len != 0) {
|
||
const amt = try writev(out_fd, headers);
|
||
total_written += amt;
|
||
if (amt < count_iovec_bytes(headers)) return total_written;
|
||
header_done = true;
|
||
}
|
||
|
||
// Here we match BSD behavior, making a zero count value send as many bytes as possible.
|
||
const adjusted_count = if (in_len == 0) max_count else math.min(in_len, @as(size_t, max_count));
|
||
|
||
while (true) {
|
||
var offset: off_t = @bitCast(off_t, in_offset);
|
||
const rc = system.sendfile(out_fd, in_fd, &offset, adjusted_count);
|
||
switch (errno(rc)) {
|
||
0 => {
|
||
const amt = @bitCast(usize, rc);
|
||
total_written += amt;
|
||
if (in_len == 0 and amt == 0) {
|
||
// We have detected EOF from `in_fd`.
|
||
break;
|
||
} else if (amt < in_len) {
|
||
return total_written;
|
||
} else {
|
||
break;
|
||
}
|
||
},
|
||
|
||
EBADF => unreachable, // Always a race condition.
|
||
EFAULT => unreachable, // Segmentation fault.
|
||
EOVERFLOW => unreachable, // We avoid passing too large of a `count`.
|
||
ENOTCONN => unreachable, // `out_fd` is an unconnected socket.
|
||
|
||
EINVAL, ENOSYS => {
|
||
// EINVAL could be any of the following situations:
|
||
// * Descriptor is not valid or locked
|
||
// * an mmap(2)-like operation is not available for in_fd
|
||
// * count is negative
|
||
// * out_fd has the O_APPEND flag set
|
||
// Because of the "mmap(2)-like operation" possibility, we fall back to doing read/write
|
||
// manually, the same as ENOSYS.
|
||
break :sf;
|
||
},
|
||
EAGAIN => if (std.event.Loop.instance) |loop| {
|
||
loop.waitUntilFdWritable(out_fd);
|
||
continue;
|
||
} else {
|
||
return error.WouldBlock;
|
||
},
|
||
EIO => return error.InputOutput,
|
||
EPIPE => return error.BrokenPipe,
|
||
ENOMEM => return error.SystemResources,
|
||
ENXIO => return error.Unseekable,
|
||
ESPIPE => return error.Unseekable,
|
||
else => |err| {
|
||
const discard = unexpectedErrno(err);
|
||
break :sf;
|
||
},
|
||
}
|
||
}
|
||
|
||
if (trailers.len != 0) {
|
||
total_written += try writev(out_fd, trailers);
|
||
}
|
||
|
||
return total_written;
|
||
},
|
||
.freebsd => sf: {
|
||
var hdtr_data: std.c.sf_hdtr = undefined;
|
||
var hdtr: ?*std.c.sf_hdtr = null;
|
||
if (headers.len != 0 or trailers.len != 0) {
|
||
// Here we carefully avoid `@intCast` by returning partial writes when
|
||
// too many io vectors are provided.
|
||
const hdr_cnt = math.cast(u31, headers.len) catch math.maxInt(u31);
|
||
if (headers.len > hdr_cnt) return writev(out_fd, headers);
|
||
|
||
const trl_cnt = math.cast(u31, trailers.len) catch math.maxInt(u31);
|
||
|
||
hdtr_data = std.c.sf_hdtr{
|
||
.headers = headers.ptr,
|
||
.hdr_cnt = hdr_cnt,
|
||
.trailers = trailers.ptr,
|
||
.trl_cnt = trl_cnt,
|
||
};
|
||
hdtr = &hdtr_data;
|
||
}
|
||
|
||
const adjusted_count = math.min(in_len, max_count);
|
||
|
||
while (true) {
|
||
var sbytes: off_t = undefined;
|
||
const offset = @bitCast(off_t, in_offset);
|
||
const err = errno(system.sendfile(in_fd, out_fd, offset, adjusted_count, hdtr, &sbytes, flags));
|
||
const amt = @bitCast(usize, sbytes);
|
||
switch (err) {
|
||
0 => return amt,
|
||
|
||
EBADF => unreachable, // Always a race condition.
|
||
EFAULT => unreachable, // Segmentation fault.
|
||
ENOTCONN => unreachable, // `out_fd` is an unconnected socket.
|
||
|
||
EINVAL, EOPNOTSUPP, ENOTSOCK, ENOSYS => {
|
||
// EINVAL could be any of the following situations:
|
||
// * The fd argument is not a regular file.
|
||
// * The s argument is not a SOCK_STREAM type socket.
|
||
// * The offset argument is negative.
|
||
// Because of some of these possibilities, we fall back to doing read/write
|
||
// manually, the same as ENOSYS.
|
||
break :sf;
|
||
},
|
||
|
||
EINTR => if (amt != 0) return amt else continue,
|
||
|
||
EAGAIN => if (amt != 0) {
|
||
return amt;
|
||
} else if (std.event.Loop.instance) |loop| {
|
||
loop.waitUntilFdWritable(out_fd);
|
||
continue;
|
||
} else {
|
||
return error.WouldBlock;
|
||
},
|
||
|
||
EBUSY => if (amt != 0) {
|
||
return amt;
|
||
} else if (std.event.Loop.instance) |loop| {
|
||
loop.waitUntilFdReadable(in_fd);
|
||
continue;
|
||
} else {
|
||
return error.WouldBlock;
|
||
},
|
||
|
||
EIO => return error.InputOutput,
|
||
ENOBUFS => return error.SystemResources,
|
||
EPIPE => return error.BrokenPipe,
|
||
|
||
else => {
|
||
const discard = unexpectedErrno(err);
|
||
if (amt != 0) {
|
||
return amt;
|
||
} else {
|
||
break :sf;
|
||
}
|
||
},
|
||
}
|
||
}
|
||
},
|
||
.macosx, .ios, .tvos, .watchos => sf: {
|
||
var hdtr_data: std.c.sf_hdtr = undefined;
|
||
var hdtr: ?*std.c.sf_hdtr = null;
|
||
if (headers.len != 0 or trailers.len != 0) {
|
||
// Here we carefully avoid `@intCast` by returning partial writes when
|
||
// too many io vectors are provided.
|
||
const hdr_cnt = math.cast(u31, headers.len) catch math.maxInt(u31);
|
||
if (headers.len > hdr_cnt) return writev(out_fd, headers);
|
||
|
||
const trl_cnt = math.cast(u31, trailers.len) catch math.maxInt(u31);
|
||
|
||
hdtr_data = std.c.sf_hdtr{
|
||
.headers = headers.ptr,
|
||
.hdr_cnt = hdr_cnt,
|
||
.trailers = trailers.ptr,
|
||
.trl_cnt = trl_cnt,
|
||
};
|
||
hdtr = &hdtr_data;
|
||
}
|
||
|
||
const adjusted_count = math.min(in_len, @as(u63, max_count));
|
||
|
||
while (true) {
|
||
var sbytes: off_t = adjusted_count;
|
||
const signed_offset = @bitCast(i64, in_offset);
|
||
const err = errno(system.sendfile(in_fd, out_fd, signed_offset, &sbytes, hdtr, flags));
|
||
const amt = @bitCast(usize, sbytes);
|
||
switch (err) {
|
||
0 => return amt,
|
||
|
||
EBADF => unreachable, // Always a race condition.
|
||
EFAULT => unreachable, // Segmentation fault.
|
||
EINVAL => unreachable,
|
||
ENOTCONN => unreachable, // `out_fd` is an unconnected socket.
|
||
|
||
ENOTSUP, ENOTSOCK, ENOSYS => break :sf,
|
||
|
||
EINTR => if (amt != 0) return amt else continue,
|
||
|
||
EAGAIN => if (amt != 0) {
|
||
return amt;
|
||
} else if (std.event.Loop.instance) |loop| {
|
||
loop.waitUntilFdWritable(out_fd);
|
||
continue;
|
||
} else {
|
||
return error.WouldBlock;
|
||
},
|
||
|
||
EIO => return error.InputOutput,
|
||
EPIPE => return error.BrokenPipe,
|
||
|
||
else => {
|
||
const discard = unexpectedErrno(err);
|
||
if (amt != 0) {
|
||
return amt;
|
||
} else {
|
||
break :sf;
|
||
}
|
||
},
|
||
}
|
||
}
|
||
},
|
||
else => {}, // fall back to read/write
|
||
}
|
||
|
||
if (headers.len != 0 and !header_done) {
|
||
const amt = try writev(out_fd, headers);
|
||
total_written += amt;
|
||
if (amt < count_iovec_bytes(headers)) return total_written;
|
||
}
|
||
|
||
rw: {
|
||
var buf: [8 * 4096]u8 = undefined;
|
||
// Here we match BSD behavior, making a zero count value send as many bytes as possible.
|
||
const adjusted_count = if (in_len == 0) buf.len else math.min(buf.len, in_len);
|
||
const amt_read = try pread(in_fd, buf[0..adjusted_count], in_offset);
|
||
if (amt_read == 0) {
|
||
if (in_len == 0) {
|
||
// We have detected EOF from `in_fd`.
|
||
break :rw;
|
||
} else {
|
||
return total_written;
|
||
}
|
||
}
|
||
const amt_written = try write(out_fd, buf[0..amt_read]);
|
||
total_written += amt_written;
|
||
if (amt_written < in_len or in_len == 0) return total_written;
|
||
}
|
||
|
||
if (trailers.len != 0) {
|
||
total_written += try writev(out_fd, trailers);
|
||
}
|
||
|
||
return total_written;
|
||
}
|
||
|
||
pub const PollError = error{
|
||
/// The kernel had no space to allocate file descriptor tables.
|
||
SystemResources,
|
||
} || UnexpectedError;
|
||
|
||
pub fn poll(fds: []pollfd, timeout: i32) PollError!usize {
|
||
while (true) {
|
||
const rc = system.poll(fds.ptr, fds.len, timeout);
|
||
switch (errno(rc)) {
|
||
0 => return @intCast(usize, rc),
|
||
EFAULT => unreachable,
|
||
EINTR => continue,
|
||
EINVAL => unreachable,
|
||
ENOMEM => return error.SystemResources,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
}
|
||
|
||
pub const RecvFromError = error{
|
||
/// The socket is marked nonblocking and the requested operation would block, and
|
||
/// there is no global event loop configured.
|
||
WouldBlock,
|
||
|
||
/// A remote host refused to allow the network connection, typically because it is not
|
||
/// running the requested service.
|
||
ConnectionRefused,
|
||
|
||
/// Could not allocate kernel memory.
|
||
SystemResources,
|
||
} || UnexpectedError;
|
||
|
||
pub fn recvfrom(
|
||
sockfd: fd_t,
|
||
buf: []u8,
|
||
flags: u32,
|
||
src_addr: ?*sockaddr,
|
||
addrlen: ?*socklen_t,
|
||
) RecvFromError!usize {
|
||
while (true) {
|
||
const rc = system.recvfrom(sockfd, buf.ptr, buf.len, flags, src_addr, addrlen);
|
||
switch (errno(rc)) {
|
||
0 => return @intCast(usize, rc),
|
||
EBADF => unreachable, // always a race condition
|
||
EFAULT => unreachable,
|
||
EINVAL => unreachable,
|
||
ENOTCONN => unreachable,
|
||
ENOTSOCK => unreachable,
|
||
EINTR => continue,
|
||
EAGAIN => if (std.event.Loop.instance) |loop| {
|
||
loop.waitUntilFdReadable(sockfd);
|
||
continue;
|
||
} else {
|
||
return error.WouldBlock;
|
||
},
|
||
ENOMEM => return error.SystemResources,
|
||
ECONNREFUSED => return error.ConnectionRefused,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
}
|
||
|
||
pub const DnExpandError = error{InvalidDnsPacket};
|
||
|
||
pub fn dn_expand(
|
||
msg: []const u8,
|
||
comp_dn: []const u8,
|
||
exp_dn: []u8,
|
||
) DnExpandError!usize {
|
||
// This implementation is ported from musl libc.
|
||
// A more idiomatic "ziggy" implementation would be welcome.
|
||
var p = comp_dn.ptr;
|
||
var len: usize = std.math.maxInt(usize);
|
||
const end = msg.ptr + msg.len;
|
||
if (p == end or exp_dn.len == 0) return error.InvalidDnsPacket;
|
||
var dest = exp_dn.ptr;
|
||
const dend = dest + std.math.min(exp_dn.len, 254);
|
||
// detect reference loop using an iteration counter
|
||
var i: usize = 0;
|
||
while (i < msg.len) : (i += 2) {
|
||
// loop invariants: p<end, dest<dend
|
||
if ((p[0] & 0xc0) != 0) {
|
||
if (p + 1 == end) return error.InvalidDnsPacket;
|
||
var j = ((p[0] & @as(usize, 0x3f)) << 8) | p[1];
|
||
if (len == std.math.maxInt(usize)) len = @ptrToInt(p) + 2 - @ptrToInt(comp_dn.ptr);
|
||
if (j >= msg.len) return error.InvalidDnsPacket;
|
||
p = msg.ptr + j;
|
||
} else if (p[0] != 0) {
|
||
if (dest != exp_dn.ptr) {
|
||
dest.* = '.';
|
||
dest += 1;
|
||
}
|
||
var j = p[0];
|
||
p += 1;
|
||
if (j >= @ptrToInt(end) - @ptrToInt(p) or j >= @ptrToInt(dend) - @ptrToInt(dest)) {
|
||
return error.InvalidDnsPacket;
|
||
}
|
||
while (j != 0) {
|
||
j -= 1;
|
||
dest.* = p[0];
|
||
dest += 1;
|
||
p += 1;
|
||
}
|
||
} else {
|
||
dest.* = 0;
|
||
if (len == std.math.maxInt(usize)) len = @ptrToInt(p) + 1 - @ptrToInt(comp_dn.ptr);
|
||
return len;
|
||
}
|
||
}
|
||
return error.InvalidDnsPacket;
|
||
}
|
||
|
||
pub const SchedYieldError = error{
|
||
/// The system is not configured to allow yielding
|
||
SystemCannotYield,
|
||
};
|
||
|
||
pub fn sched_yield() SchedYieldError!void {
|
||
if (builtin.os.tag == .windows) {
|
||
// The return value has to do with how many other threads there are; it is not
|
||
// an error condition on Windows.
|
||
_ = windows.kernel32.SwitchToThread();
|
||
return;
|
||
}
|
||
switch (errno(system.sched_yield())) {
|
||
0 => return,
|
||
ENOSYS => return error.SystemCannotYield,
|
||
else => return error.SystemCannotYield,
|
||
}
|
||
}
|
||
|
||
pub const SetSockOptError = error{
|
||
/// The socket is already connected, and a specified option cannot be set while the socket is connected.
|
||
AlreadyConnected,
|
||
|
||
/// The option is not supported by the protocol.
|
||
InvalidProtocolOption,
|
||
|
||
/// The send and receive timeout values are too big to fit into the timeout fields in the socket structure.
|
||
TimeoutTooBig,
|
||
|
||
/// Insufficient resources are available in the system to complete the call.
|
||
SystemResources,
|
||
} || UnexpectedError;
|
||
|
||
/// Set a socket's options.
|
||
pub fn setsockopt(fd: fd_t, level: u32, optname: u32, opt: []const u8) SetSockOptError!void {
|
||
switch (errno(system.setsockopt(fd, level, optname, opt.ptr, @intCast(socklen_t, opt.len)))) {
|
||
0 => {},
|
||
EBADF => unreachable, // always a race condition
|
||
ENOTSOCK => unreachable, // always a race condition
|
||
EINVAL => unreachable,
|
||
EFAULT => unreachable,
|
||
EDOM => return error.TimeoutTooBig,
|
||
EISCONN => return error.AlreadyConnected,
|
||
ENOPROTOOPT => return error.InvalidProtocolOption,
|
||
ENOMEM => return error.SystemResources,
|
||
ENOBUFS => return error.SystemResources,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
|
||
pub const MemFdCreateError = error{
|
||
SystemFdQuotaExceeded,
|
||
ProcessFdQuotaExceeded,
|
||
OutOfMemory,
|
||
|
||
/// memfd_create is available in Linux 3.17 and later. This error is returned
|
||
/// for older kernel versions.
|
||
SystemOutdated,
|
||
} || UnexpectedError;
|
||
|
||
pub fn memfd_createC(name: [*:0]const u8, flags: u32) MemFdCreateError!fd_t {
|
||
// memfd_create is available only in glibc versions starting with 2.27.
|
||
const use_c = std.c.versionCheck(.{ .major = 2, .minor = 27, .patch = 0 }).ok;
|
||
const sys = if (use_c) std.c else linux;
|
||
const getErrno = if (use_c) std.c.getErrno else linux.getErrno;
|
||
const rc = sys.memfd_create(name, flags);
|
||
switch (getErrno(rc)) {
|
||
0 => return @intCast(fd_t, rc),
|
||
EFAULT => unreachable, // name has invalid memory
|
||
EINVAL => unreachable, // name/flags are faulty
|
||
ENFILE => return error.SystemFdQuotaExceeded,
|
||
EMFILE => return error.ProcessFdQuotaExceeded,
|
||
ENOMEM => return error.OutOfMemory,
|
||
ENOSYS => return error.SystemOutdated,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
|
||
pub const MFD_NAME_PREFIX = "memfd:";
|
||
pub const MFD_MAX_NAME_LEN = NAME_MAX - MFD_NAME_PREFIX.len;
|
||
fn toMemFdPath(name: []const u8) ![MFD_MAX_NAME_LEN:0]u8 {
|
||
var path_with_null: [MFD_MAX_NAME_LEN:0]u8 = undefined;
|
||
// >= rather than > to make room for the null byte
|
||
if (name.len >= MFD_MAX_NAME_LEN) return error.NameTooLong;
|
||
mem.copy(u8, &path_with_null, name);
|
||
path_with_null[name.len] = 0;
|
||
return path_with_null;
|
||
}
|
||
|
||
pub fn memfd_create(name: []const u8, flags: u32) !fd_t {
|
||
const name_t = try toMemFdPath(name);
|
||
return memfd_createC(&name_t, flags);
|
||
}
|
||
|
||
pub fn getrusage(who: i32) rusage {
|
||
var result: rusage = undefined;
|
||
const rc = system.getrusage(who, &result);
|
||
switch (errno(rc)) {
|
||
0 => return result,
|
||
EINVAL => unreachable,
|
||
EFAULT => unreachable,
|
||
else => unreachable,
|
||
}
|
||
}
|
||
|
||
pub const TermiosGetError = error{NotATerminal} || UnexpectedError;
|
||
|
||
pub fn tcgetattr(handle: fd_t) TermiosGetError!termios {
|
||
var term: termios = undefined;
|
||
switch (errno(system.tcgetattr(handle, &term))) {
|
||
0 => return term,
|
||
EBADF => unreachable,
|
||
ENOTTY => return error.NotATerminal,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
|
||
pub const TermiosSetError = TermiosGetError || error{ProcessOrphaned};
|
||
|
||
pub fn tcsetattr(handle: fd_t, optional_action: TCSA, termios_p: termios) TermiosSetError!void {
|
||
while (true) {
|
||
switch (errno(system.tcsetattr(handle, optional_action, &termios_p))) {
|
||
0 => return,
|
||
EBADF => unreachable,
|
||
EINTR => continue,
|
||
EINVAL => unreachable,
|
||
ENOTTY => return error.NotATerminal,
|
||
EIO => return error.ProcessOrphaned,
|
||
else => |err| return unexpectedErrno(err),
|
||
}
|
||
}
|
||
}
|