2021-10-15 04:17:30 +00:00
|
|
|
//! 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
|
2024-02-14 00:56:50 +00:00
|
|
|
//! for WTF-16LE encoding.
|
2021-10-15 04:17:30 +00:00
|
|
|
//! * 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`.
|
2019-05-24 22:27:18 +00:00
|
|
|
|
2019-12-02 20:02:17 +00:00
|
|
|
const root = @import("root");
|
2019-03-02 21:46:04 +00:00
|
|
|
const std = @import("std.zig");
|
2021-08-24 20:43:41 +00:00
|
|
|
const builtin = @import("builtin");
|
2019-05-26 17:17:34 +00:00
|
|
|
const assert = std.debug.assert;
|
2019-05-25 02:52:07 +00:00
|
|
|
const math = std.math;
|
2019-05-26 17:17:34 +00:00
|
|
|
const mem = std.mem;
|
2019-05-29 08:30:30 +00:00
|
|
|
const elf = std.elf;
|
stdlib: Add emulated CWD to std.os for WASI targets
This adds a special CWD file descriptor, AT.FDCWD (-2), to refer to the
current working directory. The `*at(...)` functions look for this and
resolve relative paths against the stored CWD. Absolute paths are
dynamically matched against the stored Preopens.
"os.initPreopensWasi()" must be called before std.os functions will
resolve relative or absolute paths correctly. This is asserted at
runtime.
Support has been added for: `open`, `rename`, `mkdir`, `rmdir`, `chdir`,
`fchdir`, `link`, `symlink`, `unlink`, `readlink`, `fstatat`, `access`,
and `faccessat`.
This also includes limited support for `getcwd()` and `realpath()`.
These return an error if the CWD does not correspond to a Preopen with
an absolute path. They also do not currently expand symlinks.
2022-03-01 17:17:05 +00:00
|
|
|
const fs = std.fs;
|
2019-05-29 08:30:30 +00:00
|
|
|
const dl = @import("dynamic_library.zig");
|
2024-05-03 03:20:41 +00:00
|
|
|
const max_path_bytes = std.fs.max_path_bytes;
|
2024-03-19 06:59:35 +00:00
|
|
|
const posix = std.posix;
|
2024-07-19 06:35:19 +00:00
|
|
|
const native_os = builtin.os.tag;
|
2017-10-11 14:16:13 +00:00
|
|
|
|
2019-10-22 22:06:35 +00:00
|
|
|
pub const linux = @import("os/linux.zig");
|
2021-10-11 22:10:12 +00:00
|
|
|
pub const plan9 = @import("os/plan9.zig");
|
2019-03-02 21:46:04 +00:00
|
|
|
pub const uefi = @import("os/uefi.zig");
|
2019-04-13 20:15:39 +00:00
|
|
|
pub const wasi = @import("os/wasi.zig");
|
2023-09-20 09:12:31 +00:00
|
|
|
pub const emscripten = @import("os/emscripten.zig");
|
2019-05-24 22:27:18 +00:00
|
|
|
pub const windows = @import("os/windows.zig");
|
2018-12-24 03:44:02 +00:00
|
|
|
|
2021-01-22 14:45:28 +00:00
|
|
|
test {
|
2019-10-24 04:30:17 +00:00
|
|
|
_ = linux;
|
2024-07-19 06:35:19 +00:00
|
|
|
if (native_os == .uefi) {
|
2022-10-30 19:08:32 +00:00
|
|
|
_ = uefi;
|
|
|
|
}
|
2019-10-24 04:30:17 +00:00
|
|
|
_ = wasi;
|
|
|
|
_ = windows;
|
move types from builtin to std
* All the data types from `@import("builtin")` are moved to
`@import("std").builtin`. The target-related types are moved
to `std.Target`. This allows the data types to have methods, such as
`std.Target.current.isDarwin()`.
* `std.os.windows.subsystem` is moved to
`std.Target.current.subsystem`.
* Remove the concept of the panic package from the compiler
implementation. Instead, `std.builtin.panic` is always the panic
function. It checks for `@hasDecl(@import("root"), "panic")`,
or else provides a default implementation.
This is an important step for multibuilds (#3028). Without this change,
the types inside the builtin namespace look like different types, when
trying to merge builds with different target settings. With this change,
Zig can figure out that, e.g., `std.builtin.Os` (the enum type) from one
compilation and `std.builtin.Os` from another compilation are the same
type, even if the target OS value differs.
2019-10-23 22:43:24 +00:00
|
|
|
}
|
|
|
|
|
2019-05-26 17:17:34 +00:00
|
|
|
/// See also `getenv`. Populated by startup code before main().
|
2020-02-22 20:59:13 +00:00
|
|
|
/// TODO this is a footgun because the value will be undefined when using `zig build-lib`.
|
|
|
|
/// https://github.com/ziglang/zig/issues/4524
|
2019-11-25 02:12:01 +00:00
|
|
|
pub var environ: [][*:0]u8 = undefined;
|
2019-05-24 22:27:18 +00:00
|
|
|
|
2019-05-26 17:17:34 +00:00
|
|
|
/// Populated by startup code before main().
|
2022-01-30 13:22:49 +00:00
|
|
|
/// Not available on WASI or Windows without libc. See `std.process.argsAlloc`
|
|
|
|
/// or `std.process.argsWithAllocator` for a cross-platform alternative.
|
2024-07-19 06:35:19 +00:00
|
|
|
pub var argv: [][*:0]u8 = if (builtin.link_libc) undefined else switch (native_os) {
|
2022-01-30 13:22:49 +00:00
|
|
|
.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,
|
|
|
|
};
|
2019-05-26 17:17:34 +00:00
|
|
|
|
2024-03-19 05:39:59 +00:00
|
|
|
/// 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) {
|
Add/fix missing WASI functionality to pass libstd tests
This rather large commit adds/fixes missing WASI functionality
in `libstd` needed to pass the `libstd` tests. As such, now by
default tests targeting `wasm32-wasi` target are enabled in
`test/tests.zig` module. However, they can be disabled by passing
the `-Dskip-wasi=true` flag when invoking the `zig build test`
command. When the flag is set to `false`, i.e., when WASI tests are
included, `wasmtime` with `--dir=.` is used as the default testing
command.
Since the majority of `libstd` tests were relying on `fs.cwd()`
call to get current working directory handle wrapped in `Dir`
struct, in order to make the tests WASI-friendly, `fs.cwd()`
call was replaced with `testing.getTestDir()` function which
resolved to either `fs.cwd()` for non-WASI targets, or tries to
fetch the preopen list from the WASI runtime and extract a
preopen for '.' path.
The summary of changes introduced by this commit:
* implement `Dir.makeDir` and `Dir.openDir` targeting WASI
* implement `Dir.deleteFile` and `Dir.deleteDir` targeting WASI
* fix `os.close` and map errors in `unlinkat`
* move WASI-specific `mkdirat` and `unlinkat` from `std.fs.wasi`
to `std.os` module
* implement `lseek_{SET, CUR, END}` targeting WASI
* implement `futimens` targeting WASI
* implement `ftruncate` targeting WASI
* implement `readv`, `writev`, `pread{v}`, `pwrite{v}` targeting WASI
* make sure ANSI escape codes are _not_ used in stderr or stdout
in WASI, as WASI always sanitizes stderr, and sanitizes stdout if
fd is a TTY
* fix specifying WASI rights when opening/creating files/dirs
* tweak `AtomicFile` to be WASI-compatible
* implement `os.renameatWasi` for WASI-compliant `os.renameat` function
* implement sleep() targeting WASI
* fix `process.getEnvMap` targeting WASI
2020-05-05 15:23:49 +00:00
|
|
|
return;
|
2019-05-27 18:41:13 +00:00
|
|
|
}
|
2024-07-15 17:49:51 +00:00
|
|
|
switch (windows.GetLastError()) {
|
2024-03-19 05:39:59 +00:00
|
|
|
.FILE_NOT_FOUND => return error.FileNotFound,
|
|
|
|
.PATH_NOT_FOUND => return error.FileNotFound,
|
|
|
|
.ACCESS_DENIED => return error.PermissionDenied,
|
|
|
|
else => |err| return windows.unexpectedError(err),
|
2018-08-25 07:07:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-19 05:39:59 +00:00
|
|
|
pub fn isGetFdPathSupportedOnTarget(os: std.Target.Os) bool {
|
|
|
|
return switch (os.tag) {
|
|
|
|
.windows,
|
|
|
|
.macos,
|
|
|
|
.ios,
|
|
|
|
.watchos,
|
|
|
|
.tvos,
|
2024-05-09 13:04:13 +00:00
|
|
|
.visionos,
|
2024-03-19 05:39:59 +00:00
|
|
|
.linux,
|
|
|
|
.solaris,
|
|
|
|
.illumos,
|
|
|
|
.freebsd,
|
|
|
|
=> true,
|
2021-11-05 15:19:49 +00:00
|
|
|
|
2024-03-19 05:39:59 +00:00
|
|
|
.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,
|
|
|
|
};
|
2023-03-03 01:32:15 +00:00
|
|
|
}
|
2021-11-05 15:19:49 +00:00
|
|
|
|
2024-03-19 05:39:59 +00:00
|
|
|
/// Return canonical path of handle `fd`.
|
2023-11-04 06:06:16 +00:00
|
|
|
///
|
2024-03-19 05:39:59 +00:00
|
|
|
/// 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.
|
2023-11-04 06:06:16 +00:00
|
|
|
///
|
2024-03-19 05:39:59 +00:00
|
|
|
/// * 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.
|
2023-11-04 06:06:16 +00:00
|
|
|
///
|
2024-03-19 05:39:59 +00:00
|
|
|
/// Calling this function is usually a bug.
|
2024-05-03 03:20:41 +00:00
|
|
|
pub fn getFdPath(fd: std.posix.fd_t, out_buffer: *[max_path_bytes]u8) std.posix.RealPathError![]u8 {
|
2024-03-19 05:39:59 +00:00
|
|
|
if (!comptime isGetFdPathSupportedOnTarget(builtin.os)) {
|
|
|
|
@compileError("querying for canonical path of a handle is unsupported on this host");
|
2021-11-05 15:19:49 +00:00
|
|
|
}
|
2024-07-19 06:35:19 +00:00
|
|
|
switch (native_os) {
|
2024-03-19 05:39:59 +00:00
|
|
|
.windows => {
|
|
|
|
var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined;
|
|
|
|
const wide_slice = try windows.GetFinalPathNameByHandle(fd, .{}, wide_buf[0..]);
|
2022-10-27 11:25:48 +00:00
|
|
|
|
2024-03-19 05:39:59 +00:00
|
|
|
const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice);
|
|
|
|
return out_buffer[0..end_index];
|
|
|
|
},
|
2024-05-09 13:04:13 +00:00
|
|
|
.macos, .ios, .watchos, .tvos, .visionos => {
|
2024-03-19 05:39:59 +00:00
|
|
|
// On macOS, we can use F.GETPATH fcntl command to query the OS for
|
|
|
|
// the path to the file descriptor.
|
2024-05-03 03:20:41 +00:00
|
|
|
@memset(out_buffer[0..max_path_bytes], 0);
|
2024-03-19 05:39:59 +00:00
|
|
|
switch (posix.errno(posix.system.fcntl(fd, posix.F.GETPATH, out_buffer))) {
|
2022-10-27 11:25:48 +00:00
|
|
|
.SUCCESS => {},
|
2024-03-19 05:39:59 +00:00
|
|
|
.BADF => return error.FileNotFound,
|
|
|
|
.NOSPC => return error.NameTooLong,
|
|
|
|
// 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),
|
2022-10-27 11:25:48 +00:00
|
|
|
}
|
2024-05-03 03:20:41 +00:00
|
|
|
const len = mem.indexOfScalar(u8, out_buffer[0..], 0) orelse max_path_bytes;
|
2024-03-19 05:39:59 +00:00
|
|
|
return out_buffer[0..len];
|
2022-10-27 11:25:48 +00:00
|
|
|
},
|
2024-03-19 05:39:59 +00:00
|
|
|
.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;
|
2017-04-03 22:11:57 +00:00
|
|
|
|
2024-03-19 05:39:59 +00:00
|
|
|
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;
|
2019-08-20 19:25:30 +00:00
|
|
|
|
2024-03-19 05:39:59 +00:00
|
|
|
const target = posix.readlinkZ(proc_path, out_buffer) catch |err| switch (err) {
|
|
|
|
error.UnsupportedReparsePointType => unreachable,
|
|
|
|
error.NotLink => unreachable,
|
2024-07-19 06:35:19 +00:00
|
|
|
error.InvalidUtf8 => unreachable, // WASI-only
|
2024-03-19 05:39:59 +00:00
|
|
|
else => |e| return e,
|
|
|
|
};
|
|
|
|
return target;
|
|
|
|
},
|
|
|
|
.freebsd => {
|
|
|
|
if (comptime 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),
|
|
|
|
}
|
2024-05-03 03:20:41 +00:00
|
|
|
const len = mem.indexOfScalar(u8, &kfile.path, 0) orelse max_path_bytes;
|
2024-03-19 05:39:59 +00:00
|
|
|
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,
|
2021-03-02 08:09:51 +00:00
|
|
|
};
|
2024-03-19 05:39:59 +00:00
|
|
|
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,
|
2021-03-02 08:09:51 +00:00
|
|
|
};
|
2024-03-19 05:39:59 +00:00
|
|
|
var i: usize = 0;
|
|
|
|
while (i < len) {
|
|
|
|
const kf: *align(1) std.c.kinfo_file = @ptrCast(&buf[i]);
|
|
|
|
if (kf.fd == fd) {
|
2024-05-03 03:20:41 +00:00
|
|
|
len = mem.indexOfScalar(u8, &kf.path, 0) orelse max_path_bytes;
|
2024-03-19 05:39:59 +00:00
|
|
|
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;
|
2019-08-05 16:21:12 +00:00
|
|
|
}
|
2020-10-18 14:28:11 +00:00
|
|
|
},
|
2024-03-19 05:39:59 +00:00
|
|
|
.dragonfly => {
|
2024-05-03 03:20:41 +00:00
|
|
|
@memset(out_buffer[0..max_path_bytes], 0);
|
2024-03-19 05:39:59 +00:00
|
|
|
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),
|
|
|
|
}
|
2024-05-03 03:20:41 +00:00
|
|
|
const len = mem.indexOfScalar(u8, out_buffer[0..], 0) orelse max_path_bytes;
|
2024-03-19 05:39:59 +00:00
|
|
|
return out_buffer[0..len];
|
|
|
|
},
|
|
|
|
.netbsd => {
|
2024-05-03 03:20:41 +00:00
|
|
|
@memset(out_buffer[0..max_path_bytes], 0);
|
2024-03-19 05:39:59 +00:00
|
|
|
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),
|
|
|
|
}
|
2024-05-03 03:20:41 +00:00
|
|
|
const len = mem.indexOfScalar(u8, out_buffer[0..], 0) orelse max_path_bytes;
|
2024-03-19 05:39:59 +00:00
|
|
|
return out_buffer[0..len];
|
2020-10-18 14:28:11 +00:00
|
|
|
},
|
2024-03-19 05:39:59 +00:00
|
|
|
else => unreachable, // made unreachable by isGetFdPathSupportedOnTarget above
|
2019-07-28 21:51:51 +00:00
|
|
|
}
|
2017-04-17 10:45:44 +00:00
|
|
|
}
|
2024-03-19 06:59:35 +00:00
|
|
|
|
|
|
|
/// 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),
|
|
|
|
}
|
|
|
|
}
|