This file contains thin wrappers around OS-specific APIs, with these specific goals in mind:
std.os.linux directly.std.os.windows.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 = undefinedPopulated by startup code before main().
Not available on WASI or Windows without libc. See std.process.argsAlloc
or std.process.argsWithAllocator for a cross-platform alternative.
pub var argv: [][*:0]u8 = if (builtin.link_libc) undefined else switch (native_os) {
.windows => @compileError("argv isn't supported on Windows: use std.process.argsAlloc instead"),
.wasi => @compileError("argv isn't supported on WASI: use std.process.argsAlloc instead"),
else => undefined,
}pub fn accessW(path: [*:0]const u16) windows.GetFileAttributesError!voidCall from Windows-specific code if you already have a WTF-16LE encoded, null terminated string.
Otherwise use access or accessZ.
path: [*:0]const u16pub fn accessW(path: [*:0]const u16) windows.GetFileAttributesError!void {
const ret = try windows.GetFileAttributesW(path);
if (ret != windows.INVALID_FILE_ATTRIBUTES) {
return;
}
switch (windows.GetLastError()) {
.FILE_NOT_FOUND => return error.FileNotFound,
.PATH_NOT_FOUND => return error.FileNotFound,
.ACCESS_DENIED => return error.PermissionDenied,
else => |err| return windows.unexpectedError(err),
}
}pub fn isGetFdPathSupportedOnTarget(os: std.Target.Os) bool {
return switch (os.tag) {
.windows,
.macos,
.ios,
.watchos,
.tvos,
.visionos,
.linux,
.solaris,
.illumos,
.freebsd,
=> true,
.dragonfly => os.version_range.semver.max.order(.{ .major = 6, .minor = 0, .patch = 0 }) != .lt,
.netbsd => os.version_range.semver.max.order(.{ .major = 10, .minor = 0, .patch = 0 }) != .lt,
else => false,
};
}pub fn getFdPath(fd: std.posix.fd_t, out_buffer: *[max_path_bytes]u8) std.posix.RealPathError![]u8Return canonical path of handle fd.
This function is very host-specific and is not universally supported by all hosts. For example, while it generally works on Linux, macOS, FreeBSD or Windows, it is unsupported on WASI.
Calling this function is usually a bug.
out_buffer: *[max_path_bytes]u8pub fn getFdPath(fd: std.posix.fd_t, out_buffer: *[max_path_bytes]u8) std.posix.RealPathError![]u8 {
if (!comptime isGetFdPathSupportedOnTarget(builtin.os)) {
@compileError("querying for canonical path of a handle is unsupported on this host");
}
switch (native_os) {
.windows => {
var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined;
const wide_slice = try windows.GetFinalPathNameByHandle(fd, .{}, wide_buf[0..]);
const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice);
return out_buffer[0..end_index];
},
.macos, .ios, .watchos, .tvos, .visionos => {
// On macOS, we can use F.GETPATH fcntl command to query the OS for
// the path to the file descriptor.
@memset(out_buffer[0..max_path_bytes], 0);
switch (posix.errno(posix.system.fcntl(fd, posix.F.GETPATH, out_buffer))) {
.SUCCESS => {},
.BADF => return error.FileNotFound,
.NOSPC => return error.NameTooLong,
.NOENT => return error.FileNotFound,
// TODO man pages for fcntl on macOS don't really tell you what
// errno values to expect when command is F.GETPATH...
else => |err| return posix.unexpectedErrno(err),
}
const len = mem.indexOfScalar(u8, out_buffer[0..], 0) orelse max_path_bytes;
return out_buffer[0..len];
},
.linux => {
var procfs_buf: ["/proc/self/fd/-2147483648\x00".len]u8 = undefined;
const proc_path = std.fmt.bufPrintZ(procfs_buf[0..], "/proc/self/fd/{d}", .{fd}) catch unreachable;
const target = posix.readlinkZ(proc_path, out_buffer) catch |err| {
switch (err) {
error.NotLink => unreachable,
error.BadPathName => unreachable,
error.InvalidUtf8 => unreachable, // WASI-only
error.InvalidWtf8 => unreachable, // Windows-only
error.UnsupportedReparsePointType => unreachable, // Windows-only
error.NetworkNotFound => unreachable, // Windows-only
else => |e| return e,
}
};
return target;
},
.solaris, .illumos => {
var procfs_buf: ["/proc/self/path/-2147483648\x00".len]u8 = undefined;
const proc_path = std.fmt.bufPrintZ(procfs_buf[0..], "/proc/self/path/{d}", .{fd}) catch unreachable;
const target = posix.readlinkZ(proc_path, out_buffer) catch |err| switch (err) {
error.UnsupportedReparsePointType => unreachable,
error.NotLink => unreachable,
error.InvalidUtf8 => unreachable, // WASI-only
else => |e| return e,
};
return target;
},
.freebsd => {
if (builtin.os.isAtLeast(.freebsd, .{ .major = 13, .minor = 0, .patch = 0 }) orelse false) {
var kfile: std.c.kinfo_file = undefined;
kfile.structsize = std.c.KINFO_FILE_SIZE;
switch (posix.errno(std.c.fcntl(fd, std.c.F.KINFO, @intFromPtr(&kfile)))) {
.SUCCESS => {},
.BADF => return error.FileNotFound,
else => |err| return posix.unexpectedErrno(err),
}
const len = mem.indexOfScalar(u8, &kfile.path, 0) orelse max_path_bytes;
if (len == 0) return error.NameTooLong;
const result = out_buffer[0..len];
@memcpy(result, kfile.path[0..len]);
return result;
} else {
// This fallback implementation reimplements libutil's `kinfo_getfile()`.
// The motivation is to avoid linking -lutil when building zig or general
// user executables.
var mib = [4]c_int{ posix.CTL.KERN, posix.KERN.PROC, posix.KERN.PROC_FILEDESC, std.c.getpid() };
var len: usize = undefined;
posix.sysctl(&mib, null, &len, null, 0) catch |err| switch (err) {
error.PermissionDenied => unreachable,
error.SystemResources => return error.SystemResources,
error.NameTooLong => unreachable,
error.UnknownName => unreachable,
else => return error.Unexpected,
};
len = len * 4 / 3;
const buf = std.heap.c_allocator.alloc(u8, len) catch return error.SystemResources;
defer std.heap.c_allocator.free(buf);
len = buf.len;
posix.sysctl(&mib, &buf[0], &len, null, 0) catch |err| switch (err) {
error.PermissionDenied => unreachable,
error.SystemResources => return error.SystemResources,
error.NameTooLong => unreachable,
error.UnknownName => unreachable,
else => return error.Unexpected,
};
var i: usize = 0;
while (i < len) {
const kf: *align(1) std.c.kinfo_file = @ptrCast(&buf[i]);
if (kf.fd == fd) {
len = mem.indexOfScalar(u8, &kf.path, 0) orelse max_path_bytes;
if (len == 0) return error.NameTooLong;
const result = out_buffer[0..len];
@memcpy(result, kf.path[0..len]);
return result;
}
i += @intCast(kf.structsize);
}
return error.FileNotFound;
}
},
.dragonfly => {
@memset(out_buffer[0..max_path_bytes], 0);
switch (posix.errno(std.c.fcntl(fd, posix.F.GETPATH, out_buffer))) {
.SUCCESS => {},
.BADF => return error.FileNotFound,
.RANGE => return error.NameTooLong,
else => |err| return posix.unexpectedErrno(err),
}
const len = mem.indexOfScalar(u8, out_buffer[0..], 0) orelse max_path_bytes;
return out_buffer[0..len];
},
.netbsd => {
@memset(out_buffer[0..max_path_bytes], 0);
switch (posix.errno(std.c.fcntl(fd, posix.F.GETPATH, out_buffer))) {
.SUCCESS => {},
.ACCES => return error.AccessDenied,
.BADF => return error.FileNotFound,
.NOENT => return error.FileNotFound,
.NOMEM => return error.SystemResources,
.RANGE => return error.NameTooLong,
else => |err| return posix.unexpectedErrno(err),
}
const len = mem.indexOfScalar(u8, out_buffer[0..], 0) orelse max_path_bytes;
return out_buffer[0..len];
},
else => unreachable, // made unreachable by isGetFdPathSupportedOnTarget above
}
}pub fn fstatat_wasi(dirfd: posix.fd_t, pathname: []const u8, flags: wasi.lookupflags_t) posix.FStatAtError!wasi.filestat_tWASI-only. Same as fstatat but targeting WASI.
pathname should be encoded as valid UTF-8.
See also fstatat.
pub fn fstatat_wasi(dirfd: posix.fd_t, pathname: []const u8, flags: wasi.lookupflags_t) posix.FStatAtError!wasi.filestat_t {
var stat: wasi.filestat_t = undefined;
switch (wasi.path_filestat_get(dirfd, flags, pathname.ptr, pathname.len, &stat)) {
.SUCCESS => return stat,
.INVAL => unreachable,
.BADF => unreachable, // Always a race condition.
.NOMEM => return error.SystemResources,
.ACCES => return error.AccessDenied,
.FAULT => unreachable,
.NAMETOOLONG => return error.NameTooLong,
.NOENT => return error.FileNotFound,
.NOTDIR => return error.FileNotFound,
.NOTCAPABLE => return error.AccessDenied,
.ILSEQ => return error.InvalidUtf8,
else => |err| return posix.unexpectedErrno(err),
}
}pub fn fstat_wasi(fd: posix.fd_t) posix.FStatError!wasi.filestat_tpub fn fstat_wasi(fd: posix.fd_t) posix.FStatError!wasi.filestat_t {
var stat: wasi.filestat_t = undefined;
switch (wasi.fd_filestat_get(fd, &stat)) {
.SUCCESS => return stat,
.INVAL => unreachable,
.BADF => unreachable, // Always a race condition.
.NOMEM => return error.SystemResources,
.ACCES => return error.AccessDenied,
.NOTCAPABLE => return error.AccessDenied,
else => |err| return posix.unexpectedErrno(err),
}
}//! 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 WTF-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`.
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 fs = std.fs;
const dl = @import("dynamic_library.zig");
const max_path_bytes = std.fs.max_path_bytes;
const posix = std.posix;
const native_os = builtin.os.tag;
pub const linux = @import("os/linux.zig");
pub const plan9 = @import("os/plan9.zig");
pub const uefi = @import("os/uefi.zig");
pub const wasi = @import("os/wasi.zig");
pub const emscripten = @import("os/emscripten.zig");
pub const windows = @import("os/windows.zig");
test {
_ = linux;
if (native_os == .uefi) {
_ = uefi;
}
_ = wasi;
_ = windows;
}
/// 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 WASI or Windows without libc. See `std.process.argsAlloc`
/// or `std.process.argsWithAllocator` for a cross-platform alternative.
pub var argv: [][*:0]u8 = if (builtin.link_libc) undefined else switch (native_os) {
.windows => @compileError("argv isn't supported on Windows: use std.process.argsAlloc instead"),
.wasi => @compileError("argv isn't supported on WASI: use std.process.argsAlloc instead"),
else => undefined,
};
/// Call from Windows-specific code if you already have a WTF-16LE encoded, null terminated string.
/// Otherwise use `access` or `accessZ`.
pub fn accessW(path: [*:0]const u16) windows.GetFileAttributesError!void {
const ret = try windows.GetFileAttributesW(path);
if (ret != windows.INVALID_FILE_ATTRIBUTES) {
return;
}
switch (windows.GetLastError()) {
.FILE_NOT_FOUND => return error.FileNotFound,
.PATH_NOT_FOUND => return error.FileNotFound,
.ACCESS_DENIED => return error.PermissionDenied,
else => |err| return windows.unexpectedError(err),
}
}
pub fn isGetFdPathSupportedOnTarget(os: std.Target.Os) bool {
return switch (os.tag) {
.windows,
.macos,
.ios,
.watchos,
.tvos,
.visionos,
.linux,
.solaris,
.illumos,
.freebsd,
=> true,
.dragonfly => os.version_range.semver.max.order(.{ .major = 6, .minor = 0, .patch = 0 }) != .lt,
.netbsd => os.version_range.semver.max.order(.{ .major = 10, .minor = 0, .patch = 0 }) != .lt,
else => false,
};
}
/// Return canonical path of handle `fd`.
///
/// This function is very host-specific and is not universally supported by all hosts.
/// For example, while it generally works on Linux, macOS, FreeBSD or Windows, it is
/// unsupported on WASI.
///
/// * On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// * On other platforms, the result is an opaque sequence of bytes with no particular encoding.
///
/// Calling this function is usually a bug.
pub fn getFdPath(fd: std.posix.fd_t, out_buffer: *[max_path_bytes]u8) std.posix.RealPathError![]u8 {
if (!comptime isGetFdPathSupportedOnTarget(builtin.os)) {
@compileError("querying for canonical path of a handle is unsupported on this host");
}
switch (native_os) {
.windows => {
var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined;
const wide_slice = try windows.GetFinalPathNameByHandle(fd, .{}, wide_buf[0..]);
const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice);
return out_buffer[0..end_index];
},
.macos, .ios, .watchos, .tvos, .visionos => {
// On macOS, we can use F.GETPATH fcntl command to query the OS for
// the path to the file descriptor.
@memset(out_buffer[0..max_path_bytes], 0);
switch (posix.errno(posix.system.fcntl(fd, posix.F.GETPATH, out_buffer))) {
.SUCCESS => {},
.BADF => return error.FileNotFound,
.NOSPC => return error.NameTooLong,
.NOENT => return error.FileNotFound,
// TODO man pages for fcntl on macOS don't really tell you what
// errno values to expect when command is F.GETPATH...
else => |err| return posix.unexpectedErrno(err),
}
const len = mem.indexOfScalar(u8, out_buffer[0..], 0) orelse max_path_bytes;
return out_buffer[0..len];
},
.linux => {
var procfs_buf: ["/proc/self/fd/-2147483648\x00".len]u8 = undefined;
const proc_path = std.fmt.bufPrintZ(procfs_buf[0..], "/proc/self/fd/{d}", .{fd}) catch unreachable;
const target = posix.readlinkZ(proc_path, out_buffer) catch |err| {
switch (err) {
error.NotLink => unreachable,
error.BadPathName => unreachable,
error.InvalidUtf8 => unreachable, // WASI-only
error.InvalidWtf8 => unreachable, // Windows-only
error.UnsupportedReparsePointType => unreachable, // Windows-only
error.NetworkNotFound => unreachable, // Windows-only
else => |e| return e,
}
};
return target;
},
.solaris, .illumos => {
var procfs_buf: ["/proc/self/path/-2147483648\x00".len]u8 = undefined;
const proc_path = std.fmt.bufPrintZ(procfs_buf[0..], "/proc/self/path/{d}", .{fd}) catch unreachable;
const target = posix.readlinkZ(proc_path, out_buffer) catch |err| switch (err) {
error.UnsupportedReparsePointType => unreachable,
error.NotLink => unreachable,
error.InvalidUtf8 => unreachable, // WASI-only
else => |e| return e,
};
return target;
},
.freebsd => {
if (builtin.os.isAtLeast(.freebsd, .{ .major = 13, .minor = 0, .patch = 0 }) orelse false) {
var kfile: std.c.kinfo_file = undefined;
kfile.structsize = std.c.KINFO_FILE_SIZE;
switch (posix.errno(std.c.fcntl(fd, std.c.F.KINFO, @intFromPtr(&kfile)))) {
.SUCCESS => {},
.BADF => return error.FileNotFound,
else => |err| return posix.unexpectedErrno(err),
}
const len = mem.indexOfScalar(u8, &kfile.path, 0) orelse max_path_bytes;
if (len == 0) return error.NameTooLong;
const result = out_buffer[0..len];
@memcpy(result, kfile.path[0..len]);
return result;
} else {
// This fallback implementation reimplements libutil's `kinfo_getfile()`.
// The motivation is to avoid linking -lutil when building zig or general
// user executables.
var mib = [4]c_int{ posix.CTL.KERN, posix.KERN.PROC, posix.KERN.PROC_FILEDESC, std.c.getpid() };
var len: usize = undefined;
posix.sysctl(&mib, null, &len, null, 0) catch |err| switch (err) {
error.PermissionDenied => unreachable,
error.SystemResources => return error.SystemResources,
error.NameTooLong => unreachable,
error.UnknownName => unreachable,
else => return error.Unexpected,
};
len = len * 4 / 3;
const buf = std.heap.c_allocator.alloc(u8, len) catch return error.SystemResources;
defer std.heap.c_allocator.free(buf);
len = buf.len;
posix.sysctl(&mib, &buf[0], &len, null, 0) catch |err| switch (err) {
error.PermissionDenied => unreachable,
error.SystemResources => return error.SystemResources,
error.NameTooLong => unreachable,
error.UnknownName => unreachable,
else => return error.Unexpected,
};
var i: usize = 0;
while (i < len) {
const kf: *align(1) std.c.kinfo_file = @ptrCast(&buf[i]);
if (kf.fd == fd) {
len = mem.indexOfScalar(u8, &kf.path, 0) orelse max_path_bytes;
if (len == 0) return error.NameTooLong;
const result = out_buffer[0..len];
@memcpy(result, kf.path[0..len]);
return result;
}
i += @intCast(kf.structsize);
}
return error.FileNotFound;
}
},
.dragonfly => {
@memset(out_buffer[0..max_path_bytes], 0);
switch (posix.errno(std.c.fcntl(fd, posix.F.GETPATH, out_buffer))) {
.SUCCESS => {},
.BADF => return error.FileNotFound,
.RANGE => return error.NameTooLong,
else => |err| return posix.unexpectedErrno(err),
}
const len = mem.indexOfScalar(u8, out_buffer[0..], 0) orelse max_path_bytes;
return out_buffer[0..len];
},
.netbsd => {
@memset(out_buffer[0..max_path_bytes], 0);
switch (posix.errno(std.c.fcntl(fd, posix.F.GETPATH, out_buffer))) {
.SUCCESS => {},
.ACCES => return error.AccessDenied,
.BADF => return error.FileNotFound,
.NOENT => return error.FileNotFound,
.NOMEM => return error.SystemResources,
.RANGE => return error.NameTooLong,
else => |err| return posix.unexpectedErrno(err),
}
const len = mem.indexOfScalar(u8, out_buffer[0..], 0) orelse max_path_bytes;
return out_buffer[0..len];
},
else => unreachable, // made unreachable by isGetFdPathSupportedOnTarget above
}
}
/// WASI-only. Same as `fstatat` but targeting WASI.
/// `pathname` should be encoded as valid UTF-8.
/// See also `fstatat`.
pub fn fstatat_wasi(dirfd: posix.fd_t, pathname: []const u8, flags: wasi.lookupflags_t) posix.FStatAtError!wasi.filestat_t {
var stat: wasi.filestat_t = undefined;
switch (wasi.path_filestat_get(dirfd, flags, pathname.ptr, pathname.len, &stat)) {
.SUCCESS => return stat,
.INVAL => unreachable,
.BADF => unreachable, // Always a race condition.
.NOMEM => return error.SystemResources,
.ACCES => return error.AccessDenied,
.FAULT => unreachable,
.NAMETOOLONG => return error.NameTooLong,
.NOENT => return error.FileNotFound,
.NOTDIR => return error.FileNotFound,
.NOTCAPABLE => return error.AccessDenied,
.ILSEQ => return error.InvalidUtf8,
else => |err| return posix.unexpectedErrno(err),
}
}
pub fn fstat_wasi(fd: posix.fd_t) posix.FStatError!wasi.filestat_t {
var stat: wasi.filestat_t = undefined;
switch (wasi.fd_filestat_get(fd, &stat)) {
.SUCCESS => return stat,
.INVAL => unreachable,
.BADF => unreachable, // Always a race condition.
.NOMEM => return error.SystemResources,
.ACCES => return error.AccessDenied,
.NOTCAPABLE => return error.AccessDenied,
else => |err| return posix.unexpectedErrno(err),
}
}