mirror of
https://github.com/ziglang/zig.git
synced 2025-12-06 06:13:07 +00:00
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
This commit is contained in:
parent
feade9ef00
commit
d43c08a3e5
@ -60,6 +60,7 @@ pub fn build(b: *Builder) !void {
|
||||
const skip_release_safe = b.option(bool, "skip-release-safe", "Main test suite skips release-safe builds") orelse skip_release;
|
||||
const skip_non_native = b.option(bool, "skip-non-native", "Main test suite skips non-native builds") orelse false;
|
||||
const skip_libc = b.option(bool, "skip-libc", "Main test suite skips tests that link libc") orelse false;
|
||||
const skip_wasi = b.option(bool, "skip-wasi", "Main test suite skips WASI build") orelse false;
|
||||
|
||||
const only_install_lib_files = b.option(bool, "lib-files-only", "Only install library files") orelse false;
|
||||
const enable_llvm = b.option(bool, "enable-llvm", "Build self-hosted compiler with LLVM backend enabled") orelse false;
|
||||
@ -115,11 +116,12 @@ pub fn build(b: *Builder) !void {
|
||||
const fmt_step = b.step("test-fmt", "Run zig fmt against build.zig to make sure it works");
|
||||
fmt_step.dependOn(&fmt_build_zig.step);
|
||||
|
||||
test_step.dependOn(tests.addPkgTests(b, test_filter, "test/stage1/behavior.zig", "behavior", "Run the behavior tests", modes, false, skip_non_native, skip_libc, is_wine_enabled, is_qemu_enabled, glibc_multi_dir));
|
||||
// TODO for the moment, skip wasm32-wasi until bugs are sorted out.
|
||||
test_step.dependOn(tests.addPkgTests(b, test_filter, "test/stage1/behavior.zig", "behavior", "Run the behavior tests", modes, false, skip_non_native, skip_libc, true, is_wine_enabled, is_qemu_enabled, glibc_multi_dir));
|
||||
|
||||
test_step.dependOn(tests.addPkgTests(b, test_filter, "lib/std/std.zig", "std", "Run the standard library tests", modes, false, skip_non_native, skip_libc, is_wine_enabled, is_qemu_enabled, glibc_multi_dir));
|
||||
test_step.dependOn(tests.addPkgTests(b, test_filter, "lib/std/std.zig", "std", "Run the standard library tests", modes, false, skip_non_native, skip_libc, skip_wasi, is_wine_enabled, is_qemu_enabled, glibc_multi_dir));
|
||||
|
||||
test_step.dependOn(tests.addPkgTests(b, test_filter, "lib/std/special/compiler_rt.zig", "compiler-rt", "Run the compiler_rt tests", modes, true, skip_non_native, true, is_wine_enabled, is_qemu_enabled, glibc_multi_dir));
|
||||
test_step.dependOn(tests.addPkgTests(b, test_filter, "lib/std/special/compiler_rt.zig", "compiler-rt", "Run the compiler_rt tests", modes, true, skip_non_native, true, skip_wasi, is_wine_enabled, is_qemu_enabled, glibc_multi_dir));
|
||||
|
||||
test_step.dependOn(tests.addCompareOutputTests(b, test_filter, modes));
|
||||
test_step.dependOn(tests.addStandaloneTests(b, test_filter, modes));
|
||||
|
||||
@ -2089,6 +2089,8 @@ pub const LibExeObjStep = struct {
|
||||
.wasmtime => |bin_name| if (self.enable_wasmtime) {
|
||||
try zig_args.append("--test-cmd");
|
||||
try zig_args.append(bin_name);
|
||||
try zig_args.append("--test-cmd");
|
||||
try zig_args.append("--dir=.");
|
||||
try zig_args.append("--test-cmd-bin");
|
||||
},
|
||||
}
|
||||
|
||||
@ -1684,12 +1684,15 @@ test "vector" {
|
||||
// https://github.com/ziglang/zig/issues/4486
|
||||
return error.SkipZigTest;
|
||||
}
|
||||
|
||||
if (builtin.arch != .wasm32) {
|
||||
// TODO investigate why this fails on wasm32
|
||||
const vbool: std.meta.Vector(4, bool) = [_]bool{ true, false, true, false };
|
||||
try testFmt("{ true, false, true, false }", "{}", .{vbool});
|
||||
}
|
||||
|
||||
const vi64: std.meta.Vector(4, i64) = [_]i64{ -2, -1, 0, 1 };
|
||||
const vu64: std.meta.Vector(4, u64) = [_]u64{ 1000, 2000, 3000, 4000 };
|
||||
|
||||
try testFmt("{ true, false, true, false }", "{}", .{vbool});
|
||||
try testFmt("{ -2, -1, 0, 1 }", "{}", .{vi64});
|
||||
try testFmt("{ - 2, - 1, + 0, + 1 }", "{d:5}", .{vi64});
|
||||
try testFmt("{ 1000, 2000, 3000, 4000 }", "{}", .{vu64});
|
||||
|
||||
175
lib/std/fs.zig
175
lib/std/fs.zig
@ -44,6 +44,8 @@ pub const MAX_PATH_BYTES = switch (builtin.os.tag) {
|
||||
// pair in the UTF-16LE, and we (over)account 3 bytes for it that way.
|
||||
// +1 for the null byte at the end, which can be encoded in 1 byte.
|
||||
.windows => os.windows.PATH_MAX_WIDE * 3 + 1,
|
||||
// TODO work out what a reasonable value we should use here
|
||||
.wasi => 4096,
|
||||
else => @compileError("Unsupported OS"),
|
||||
};
|
||||
|
||||
@ -155,7 +157,7 @@ pub const AtomicFile = struct {
|
||||
try crypto.randomBytes(rand_buf[0..]);
|
||||
base64_encoder.encode(&tmp_path_buf, &rand_buf);
|
||||
|
||||
const file = dir.createFileZ(
|
||||
const file = dir.createFile(
|
||||
&tmp_path_buf,
|
||||
.{ .mode = mode, .exclusive = true },
|
||||
) catch |err| switch (err) {
|
||||
@ -182,7 +184,7 @@ pub const AtomicFile = struct {
|
||||
self.file_open = false;
|
||||
}
|
||||
if (self.file_exists) {
|
||||
self.dir.deleteFileZ(&self.tmp_path_buf) catch {};
|
||||
self.dir.deleteFile(&self.tmp_path_buf) catch {};
|
||||
self.file_exists = false;
|
||||
}
|
||||
if (self.close_dir_on_deinit) {
|
||||
@ -197,16 +199,8 @@ pub const AtomicFile = struct {
|
||||
self.file.close();
|
||||
self.file_open = false;
|
||||
}
|
||||
if (std.Target.current.os.tag == .windows) {
|
||||
const dest_path_w = try os.windows.sliceToPrefixedFileW(self.dest_basename);
|
||||
const tmp_path_w = try os.windows.cStrToPrefixedFileW(&self.tmp_path_buf);
|
||||
try os.renameatW(self.dir.fd, tmp_path_w.span(), self.dir.fd, dest_path_w.span(), os.windows.TRUE);
|
||||
try os.renameat(self.dir.fd, self.tmp_path_buf[0..], self.dir.fd, self.dest_basename);
|
||||
self.file_exists = false;
|
||||
} else {
|
||||
const dest_path_c = try os.toPosixPath(self.dest_basename);
|
||||
try os.renameatZ(self.dir.fd, &self.tmp_path_buf, self.dir.fd, &dest_path_c);
|
||||
self.file_exists = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -522,6 +516,66 @@ pub const Dir = struct {
|
||||
}
|
||||
}
|
||||
},
|
||||
.wasi => struct {
|
||||
dir: Dir,
|
||||
buf: [8192]u8, // TODO align(@alignOf(os.wasi.dirent_t)),
|
||||
cookie: u64,
|
||||
index: usize,
|
||||
end_index: usize,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub const Error = IteratorError;
|
||||
|
||||
/// Memory such as file names referenced in this returned entry becomes invalid
|
||||
/// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
|
||||
pub fn next(self: *Self) Error!?Entry {
|
||||
const w = os.wasi;
|
||||
start_over: while (true) {
|
||||
if (self.index >= self.end_index) {
|
||||
var bufused: usize = undefined;
|
||||
switch (w.fd_readdir(self.dir.fd, &self.buf, self.buf.len, self.cookie, &bufused)) {
|
||||
w.ESUCCESS => {},
|
||||
w.EBADF => unreachable, // Dir is invalid or was opened without iteration ability
|
||||
w.EFAULT => unreachable,
|
||||
w.ENOTDIR => unreachable,
|
||||
w.EINVAL => unreachable,
|
||||
else => |err| return os.unexpectedErrno(err),
|
||||
}
|
||||
if (bufused == 0) return null;
|
||||
self.index = 0;
|
||||
self.end_index = bufused;
|
||||
}
|
||||
const entry = @ptrCast(*align(1) os.wasi.dirent_t, &self.buf[self.index]);
|
||||
const entry_size = @sizeOf(os.wasi.dirent_t);
|
||||
const name_index = self.index + entry_size;
|
||||
const name = mem.span(self.buf[name_index .. name_index + entry.d_namlen]);
|
||||
|
||||
const next_index = name_index + entry.d_namlen;
|
||||
self.index = next_index;
|
||||
self.cookie = entry.d_next;
|
||||
|
||||
// skip . and .. entries
|
||||
if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) {
|
||||
continue :start_over;
|
||||
}
|
||||
|
||||
const entry_kind = switch (entry.d_type) {
|
||||
wasi.FILETYPE_BLOCK_DEVICE => Entry.Kind.BlockDevice,
|
||||
wasi.FILETYPE_CHARACTER_DEVICE => Entry.Kind.CharacterDevice,
|
||||
wasi.FILETYPE_DIRECTORY => Entry.Kind.Directory,
|
||||
wasi.FILETYPE_SYMBOLIC_LINK => Entry.Kind.SymLink,
|
||||
wasi.FILETYPE_REGULAR_FILE => Entry.Kind.File,
|
||||
wasi.FILETYPE_SOCKET_STREAM, wasi.FILETYPE_SOCKET_DGRAM => Entry.Kind.UnixDomainSocket,
|
||||
else => Entry.Kind.Unknown,
|
||||
};
|
||||
return Entry{
|
||||
.name = name,
|
||||
.kind = entry_kind,
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
else => @compileError("unimplemented"),
|
||||
};
|
||||
|
||||
@ -548,6 +602,13 @@ pub const Dir = struct {
|
||||
.buf = undefined,
|
||||
.name_data = undefined,
|
||||
},
|
||||
.wasi => return Iterator{
|
||||
.dir = self,
|
||||
.cookie = os.wasi.DIRCOOKIE_START,
|
||||
.index = 0,
|
||||
.end_index = 0,
|
||||
.buf = undefined,
|
||||
},
|
||||
else => @compileError("unimplemented"),
|
||||
}
|
||||
}
|
||||
@ -595,24 +656,25 @@ pub const Dir = struct {
|
||||
/// Save as `openFile` but WASI only.
|
||||
pub fn openFileWasi(self: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
|
||||
const w = os.wasi;
|
||||
var fdflags: w.fdflag_t = 0x0;
|
||||
var rights: w.rights_t = 0x0;
|
||||
var fdflags: w.fdflags_t = 0x0;
|
||||
var base: w.rights_t = 0x0;
|
||||
if (flags.read) {
|
||||
rights |= w.FD_READ | w.FD_TELL | w.FD_FILESTAT_GET;
|
||||
base |= w.RIGHT_FD_READ | w.RIGHT_FD_TELL | w.RIGHT_FD_SEEK | w.RIGHT_FD_FILESTAT_GET;
|
||||
}
|
||||
if (flags.write) {
|
||||
fdflags |= w.FDFLAG_APPEND;
|
||||
rights |= w.FD_WRITE |
|
||||
w.FD_DATASYNC |
|
||||
w.FD_SEEK |
|
||||
w.FD_FDSTAT_SET_FLAGS |
|
||||
w.FD_SYNC |
|
||||
w.FD_ALLOCATE |
|
||||
w.FD_ADVISE |
|
||||
w.FD_FILESTAT_SET_TIMES |
|
||||
w.FD_FILESTAT_SET_SIZE;
|
||||
base |= w.RIGHT_FD_WRITE |
|
||||
w.RIGHT_FD_TELL |
|
||||
w.RIGHT_FD_SEEK |
|
||||
w.RIGHT_FD_DATASYNC |
|
||||
w.RIGHT_FD_FDSTAT_SET_FLAGS |
|
||||
w.RIGHT_FD_SYNC |
|
||||
w.RIGHT_FD_ALLOCATE |
|
||||
w.RIGHT_FD_ADVISE |
|
||||
w.RIGHT_FD_FILESTAT_SET_TIMES |
|
||||
w.RIGHT_FD_FILESTAT_SET_SIZE;
|
||||
}
|
||||
const fd = try wasi.openat(self.fd, sub_path, 0x0, fdflags, rights);
|
||||
const fd = try os.openatWasi(self.fd, sub_path, 0x0, fdflags, base, 0x0);
|
||||
return File{ .handle = fd };
|
||||
}
|
||||
|
||||
@ -712,17 +774,19 @@ pub const Dir = struct {
|
||||
pub fn createFileWasi(self: Dir, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File {
|
||||
const w = os.wasi;
|
||||
var oflags = w.O_CREAT;
|
||||
var rights = w.RIGHT_FD_WRITE |
|
||||
var base: w.rights_t = w.RIGHT_FD_WRITE |
|
||||
w.RIGHT_FD_DATASYNC |
|
||||
w.RIGHT_FD_SEEK |
|
||||
w.RIGHT_FD_TELL |
|
||||
w.RIGHT_FD_FDSTAT_SET_FLAGS |
|
||||
w.RIGHT_FD_SYNC |
|
||||
w.RIGHT_FD_ALLOCATE |
|
||||
w.RIGHT_FD_ADVISE |
|
||||
w.RIGHT_FD_FILESTAT_SET_TIMES |
|
||||
w.RIGHT_FD_FILESTAT_SET_SIZE;
|
||||
w.RIGHT_FD_FILESTAT_SET_SIZE |
|
||||
w.RIGHT_FD_FILESTAT_GET;
|
||||
if (flags.read) {
|
||||
rights |= w.RIGHT_FD_READ | w.RIGHT_FD_TELL | w.RIGHT_FD_FILESTAT_GET;
|
||||
base |= w.RIGHT_FD_READ;
|
||||
}
|
||||
if (flags.truncate) {
|
||||
oflags |= w.O_TRUNC;
|
||||
@ -730,7 +794,7 @@ pub const Dir = struct {
|
||||
if (flags.exclusive) {
|
||||
oflags |= w.O_EXCL;
|
||||
}
|
||||
const fd = try wasi.openat(self.fd, sub_path, oflags, 0x0, rights);
|
||||
const fd = try os.openatWasi(self.fd, sub_path, oflags, 0x0, base, 0x0);
|
||||
return File{ .handle = fd };
|
||||
}
|
||||
|
||||
@ -877,6 +941,9 @@ pub const Dir = struct {
|
||||
/// Not all targets support this. For example, WASI does not have the concept
|
||||
/// of a current working directory.
|
||||
pub fn setAsCwd(self: Dir) !void {
|
||||
if (builtin.os.tag == .wasi) {
|
||||
@compileError("changing cwd is not currently possible in WASI");
|
||||
}
|
||||
try os.fchdir(self.fd);
|
||||
}
|
||||
|
||||
@ -899,6 +966,8 @@ pub const Dir = struct {
|
||||
if (builtin.os.tag == .windows) {
|
||||
const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
|
||||
return self.openDirW(sub_path_w.span().ptr, args);
|
||||
} else if (builtin.os.tag == .wasi) {
|
||||
return self.openDirWasi(sub_path, args);
|
||||
} else {
|
||||
const sub_path_c = try os.toPosixPath(sub_path);
|
||||
return self.openDirZ(&sub_path_c, args);
|
||||
@ -907,6 +976,44 @@ pub const Dir = struct {
|
||||
|
||||
pub const openDirC = @compileError("deprecated: renamed to openDirZ");
|
||||
|
||||
/// Same as `openDir` except only WASI.
|
||||
pub fn openDirWasi(self: Dir, sub_path: []const u8, args: OpenDirOptions) OpenError!Dir {
|
||||
const w = os.wasi;
|
||||
var base: w.rights_t = w.RIGHT_FD_FILESTAT_GET | w.RIGHT_FD_FDSTAT_SET_FLAGS | w.RIGHT_FD_FILESTAT_SET_TIMES;
|
||||
if (args.iterate) {
|
||||
base |= w.RIGHT_FD_READDIR;
|
||||
}
|
||||
if (args.access_sub_paths) {
|
||||
base |= w.RIGHT_PATH_CREATE_DIRECTORY |
|
||||
w.RIGHT_PATH_CREATE_FILE |
|
||||
w.RIGHT_PATH_LINK_SOURCE |
|
||||
w.RIGHT_PATH_LINK_TARGET |
|
||||
w.RIGHT_PATH_OPEN |
|
||||
w.RIGHT_PATH_READLINK |
|
||||
w.RIGHT_PATH_RENAME_SOURCE |
|
||||
w.RIGHT_PATH_RENAME_TARGET |
|
||||
w.RIGHT_PATH_FILESTAT_GET |
|
||||
w.RIGHT_PATH_FILESTAT_SET_SIZE |
|
||||
w.RIGHT_PATH_FILESTAT_SET_TIMES |
|
||||
w.RIGHT_PATH_SYMLINK |
|
||||
w.RIGHT_PATH_REMOVE_DIRECTORY |
|
||||
w.RIGHT_PATH_UNLINK_FILE;
|
||||
}
|
||||
// TODO do we really need all the rights here?
|
||||
const inheriting: w.rights_t = w.RIGHT_ALL ^ w.RIGHT_SOCK_SHUTDOWN;
|
||||
|
||||
const result = os.openatWasi(self.fd, sub_path, w.O_DIRECTORY, 0x0, base, inheriting);
|
||||
const fd = result catch |err| switch (err) {
|
||||
error.FileTooBig => unreachable, // can't happen for directories
|
||||
error.IsDir => unreachable, // we're providing O_DIRECTORY
|
||||
error.NoSpaceLeft => unreachable, // not providing O_CREAT
|
||||
error.PathAlreadyExists => unreachable, // not providing O_CREAT
|
||||
error.FileLocksNotSupported => unreachable, // locking folders is not supported
|
||||
else => |e| return e,
|
||||
};
|
||||
return Dir{ .fd = fd };
|
||||
}
|
||||
|
||||
/// Same as `openDir` except the parameter is null-terminated.
|
||||
pub fn openDirZ(self: Dir, sub_path_c: [*:0]const u8, args: OpenDirOptions) OpenError!Dir {
|
||||
if (builtin.os.tag == .windows) {
|
||||
@ -1054,10 +1161,16 @@ pub const Dir = struct {
|
||||
if (builtin.os.tag == .windows) {
|
||||
const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
|
||||
return self.deleteDirW(sub_path_w.span().ptr);
|
||||
}
|
||||
} else if (builtin.os.tag == .wasi) {
|
||||
os.unlinkat(self.fd, sub_path, os.AT_REMOVEDIR) catch |err| switch (err) {
|
||||
error.IsDir => unreachable, // not possible since we pass AT_REMOVEDIR
|
||||
else => |e| return e,
|
||||
};
|
||||
} else {
|
||||
const sub_path_c = try os.toPosixPath(sub_path);
|
||||
return self.deleteDirZ(&sub_path_c);
|
||||
}
|
||||
}
|
||||
|
||||
/// Same as `deleteDir` except the parameter is null-terminated.
|
||||
pub fn deleteDirZ(self: Dir, sub_path_c: [*:0]const u8) DeleteDirError!void {
|
||||
@ -1448,7 +1561,7 @@ pub fn cwd() Dir {
|
||||
if (builtin.os.tag == .windows) {
|
||||
return Dir{ .fd = os.windows.peb().ProcessParameters.CurrentDirectory.Handle };
|
||||
} else if (builtin.os.tag == .wasi) {
|
||||
@compileError("WASI doesn't have a concept of cwd; use TODO instead");
|
||||
@compileError("WASI doesn't have a concept of cwd(); use std.fs.wasi.PreopenList to get available Dir handles instead");
|
||||
} else {
|
||||
return Dir{ .fd = os.AT_FDCWD };
|
||||
}
|
||||
@ -1754,10 +1867,12 @@ pub fn realpathAlloc(allocator: *Allocator, pathname: []const u8) ![]u8 {
|
||||
}
|
||||
|
||||
test "" {
|
||||
if (builtin.os.tag != .wasi) {
|
||||
_ = makeDirAbsolute;
|
||||
_ = makeDirAbsoluteZ;
|
||||
_ = copyFileAbsolute;
|
||||
_ = updateFileAbsolute;
|
||||
}
|
||||
_ = Dir.copyFile;
|
||||
_ = @import("fs/test.zig");
|
||||
_ = @import("fs/path.zig");
|
||||
|
||||
@ -140,6 +140,14 @@ pub const File = struct {
|
||||
if (builtin.os.tag == .windows) {
|
||||
return os.isCygwinPty(self.handle);
|
||||
}
|
||||
if (builtin.os.tag == .wasi) {
|
||||
// WASI sanitizes stdout when fd is a tty so ANSI escape codes
|
||||
// will not be interpreted as actual cursor commands.
|
||||
if (self.handle == os.STDOUT_FILENO and self.isTty()) return false;
|
||||
// stderr is always sanitized.
|
||||
if (self.handle == os.STDERR_FILENO) return false;
|
||||
return true;
|
||||
}
|
||||
if (self.isTty()) {
|
||||
if (self.handle == os.STDOUT_FILENO or self.handle == os.STDERR_FILENO) {
|
||||
// Use getenvC to workaround https://github.com/ziglang/zig/issues/3511
|
||||
@ -259,10 +267,11 @@ pub const File = struct {
|
||||
const atime = st.atime();
|
||||
const mtime = st.mtime();
|
||||
const ctime = st.ctime();
|
||||
const m = if (builtin.os.tag == .wasi) 0 else st.mode;
|
||||
return Stat{
|
||||
.inode = st.ino,
|
||||
.size = @bitCast(u64, st.size),
|
||||
.mode = st.mode,
|
||||
.mode = m,
|
||||
.atime = @as(i64, atime.tv_sec) * std.time.ns_per_s + atime.tv_nsec,
|
||||
.mtime = @as(i64, mtime.tv_sec) * std.time.ns_per_s + mtime.tv_nsec,
|
||||
.ctime = @as(i64, ctime.tv_sec) * std.time.ns_per_s + ctime.tv_nsec,
|
||||
|
||||
@ -56,6 +56,8 @@ pub fn getAppDataDir(allocator: *mem.Allocator, appname: []const u8) GetAppDataD
|
||||
}
|
||||
|
||||
test "getAppDataDir" {
|
||||
if (builtin.os.tag == .wasi) return error.SkipZigTest;
|
||||
|
||||
// We can't actually validate the result
|
||||
const dir = getAppDataDir(std.testing.allocator, "zig") catch return;
|
||||
defer std.testing.allocator.free(dir);
|
||||
|
||||
@ -653,6 +653,8 @@ pub fn resolvePosix(allocator: *Allocator, paths: []const []const u8) ![]u8 {
|
||||
}
|
||||
|
||||
test "resolve" {
|
||||
if (builtin.os.tag == .wasi) return error.SkipZigTest;
|
||||
|
||||
const cwd = try process.getCwdAlloc(testing.allocator);
|
||||
defer testing.allocator.free(cwd);
|
||||
if (builtin.os.tag == .windows) {
|
||||
@ -667,10 +669,11 @@ test "resolve" {
|
||||
}
|
||||
|
||||
test "resolveWindows" {
|
||||
if (@import("builtin").arch == .aarch64) {
|
||||
if (builtin.arch == .aarch64) {
|
||||
// TODO https://github.com/ziglang/zig/issues/3288
|
||||
return error.SkipZigTest;
|
||||
}
|
||||
if (builtin.os.tag == .wasi) return error.SkipZigTest;
|
||||
if (builtin.os.tag == .windows) {
|
||||
const cwd = try process.getCwdAlloc(testing.allocator);
|
||||
defer testing.allocator.free(cwd);
|
||||
@ -715,6 +718,8 @@ test "resolveWindows" {
|
||||
}
|
||||
|
||||
test "resolvePosix" {
|
||||
if (builtin.os.tag == .wasi) return error.SkipZigTest;
|
||||
|
||||
try testResolvePosix(&[_][]const u8{ "/a/b", "c" }, "/a/b/c");
|
||||
try testResolvePosix(&[_][]const u8{ "/a/b", "c", "//d", "e///" }, "/d/e");
|
||||
try testResolvePosix(&[_][]const u8{ "/a/b/c", "..", "../" }, "/a");
|
||||
@ -1116,10 +1121,12 @@ pub fn relativePosix(allocator: *Allocator, from: []const u8, to: []const u8) ![
|
||||
}
|
||||
|
||||
test "relative" {
|
||||
if (@import("builtin").arch == .aarch64) {
|
||||
if (builtin.arch == .aarch64) {
|
||||
// TODO https://github.com/ziglang/zig/issues/3288
|
||||
return error.SkipZigTest;
|
||||
}
|
||||
if (builtin.os.tag == .wasi) return error.SkipZigTest;
|
||||
|
||||
try testRelativeWindows("c:/blah\\blah", "d:/games", "D:\\games");
|
||||
try testRelativeWindows("c:/aaaa/bbbb", "c:/aaaa", "..");
|
||||
try testRelativeWindows("c:/aaaa/bbbb", "c:/cccc", "..\\..\\cccc");
|
||||
|
||||
@ -4,6 +4,8 @@ const fs = std.fs;
|
||||
const File = std.fs.File;
|
||||
|
||||
test "openSelfExe" {
|
||||
if (builtin.os.tag == .wasi) return error.SkipZigTest;
|
||||
|
||||
const self_exe_file = try std.fs.openSelfExe();
|
||||
self_exe_file.close();
|
||||
}
|
||||
@ -11,6 +13,8 @@ test "openSelfExe" {
|
||||
const FILE_LOCK_TEST_SLEEP_TIME = 5 * std.time.millisecond;
|
||||
|
||||
test "open file with exclusive nonblocking lock twice" {
|
||||
if (builtin.os.tag == .wasi) return error.SkipZigTest;
|
||||
|
||||
const dir = fs.cwd();
|
||||
const filename = "file_nonblocking_lock_test.txt";
|
||||
|
||||
@ -111,6 +115,8 @@ test "create file, lock and read from multiple process at once" {
|
||||
}
|
||||
|
||||
test "open file with exclusive nonblocking lock twice (absolute paths)" {
|
||||
if (builtin.os.tag == .wasi) return error.SkipZigTest;
|
||||
|
||||
const allocator = std.testing.allocator;
|
||||
|
||||
const file_paths: [1][]const u8 = .{"zig-test-absolute-paths.txt"};
|
||||
|
||||
@ -138,14 +138,3 @@ pub const PreopenList = struct {
|
||||
return self.buffer.toOwnedSlice();
|
||||
}
|
||||
};
|
||||
|
||||
/// Convenience wrapper for `std.os.wasi.path_open` syscall.
|
||||
pub fn openat(dir_fd: fd_t, file_path: []const u8, oflags: oflags_t, fdflags: fdflags_t, rights: rights_t) os.OpenError!fd_t {
|
||||
var fd: fd_t = undefined;
|
||||
switch (path_open(dir_fd, 0x0, file_path.ptr, file_path.len, oflags, rights, 0x0, fdflags, &fd)) {
|
||||
0 => {},
|
||||
// TODO map errors
|
||||
else => |err| return std.os.unexpectedErrno(err),
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
@ -11,15 +11,17 @@ const mem = std.mem;
|
||||
const fs = std.fs;
|
||||
const File = std.fs.File;
|
||||
|
||||
const getTestDir = std.testing.getTestDir;
|
||||
|
||||
test "write a file, read it, then delete it" {
|
||||
const cwd = fs.cwd();
|
||||
const test_dir = getTestDir();
|
||||
|
||||
var data: [1024]u8 = undefined;
|
||||
var prng = DefaultPrng.init(1234);
|
||||
prng.random.bytes(data[0..]);
|
||||
const tmp_file_name = "temp_test_file.txt";
|
||||
{
|
||||
var file = try cwd.createFile(tmp_file_name, .{});
|
||||
var file = try test_dir.createFile(tmp_file_name, .{});
|
||||
defer file.close();
|
||||
|
||||
var buf_stream = io.bufferedOutStream(file.outStream());
|
||||
@ -32,7 +34,7 @@ test "write a file, read it, then delete it" {
|
||||
|
||||
{
|
||||
// Make sure the exclusive flag is honored.
|
||||
if (cwd.createFile(tmp_file_name, .{ .exclusive = true })) |file| {
|
||||
if (test_dir.createFile(tmp_file_name, .{ .exclusive = true })) |file| {
|
||||
unreachable;
|
||||
} else |err| {
|
||||
std.debug.assert(err == File.OpenError.PathAlreadyExists);
|
||||
@ -40,7 +42,7 @@ test "write a file, read it, then delete it" {
|
||||
}
|
||||
|
||||
{
|
||||
var file = try cwd.openFile(tmp_file_name, .{});
|
||||
var file = try test_dir.openFile(tmp_file_name, .{});
|
||||
defer file.close();
|
||||
|
||||
const file_size = try file.getEndPos();
|
||||
@ -56,13 +58,14 @@ test "write a file, read it, then delete it" {
|
||||
expect(mem.eql(u8, contents["begin".len .. contents.len - "end".len], &data));
|
||||
expect(mem.eql(u8, contents[contents.len - "end".len ..], "end"));
|
||||
}
|
||||
try cwd.deleteFile(tmp_file_name);
|
||||
try test_dir.deleteFile(tmp_file_name);
|
||||
}
|
||||
|
||||
test "BitStreams with File Stream" {
|
||||
var test_dir = getTestDir();
|
||||
const tmp_file_name = "temp_test_file.txt";
|
||||
{
|
||||
var file = try fs.cwd().createFile(tmp_file_name, .{});
|
||||
var file = try test_dir.createFile(tmp_file_name, .{});
|
||||
defer file.close();
|
||||
|
||||
var bit_stream = io.bitOutStream(builtin.endian, file.outStream());
|
||||
@ -76,7 +79,7 @@ test "BitStreams with File Stream" {
|
||||
try bit_stream.flushBits();
|
||||
}
|
||||
{
|
||||
var file = try fs.cwd().openFile(tmp_file_name, .{});
|
||||
var file = try test_dir.openFile(tmp_file_name, .{});
|
||||
defer file.close();
|
||||
|
||||
var bit_stream = io.bitInStream(builtin.endian, file.inStream());
|
||||
@ -98,15 +101,16 @@ test "BitStreams with File Stream" {
|
||||
|
||||
expectError(error.EndOfStream, bit_stream.readBitsNoEof(u1, 1));
|
||||
}
|
||||
try fs.cwd().deleteFile(tmp_file_name);
|
||||
try test_dir.deleteFile(tmp_file_name);
|
||||
}
|
||||
|
||||
test "File seek ops" {
|
||||
var test_dir = getTestDir();
|
||||
const tmp_file_name = "temp_test_file.txt";
|
||||
var file = try fs.cwd().createFile(tmp_file_name, .{});
|
||||
var file = try test_dir.createFile(tmp_file_name, .{});
|
||||
defer {
|
||||
file.close();
|
||||
fs.cwd().deleteFile(tmp_file_name) catch {};
|
||||
test_dir.deleteFile(tmp_file_name) catch {};
|
||||
}
|
||||
|
||||
try file.writeAll(&([_]u8{0x55} ** 8192));
|
||||
@ -129,11 +133,12 @@ test "setEndPos" {
|
||||
// https://github.com/ziglang/zig/issues/5127
|
||||
if (std.Target.current.cpu.arch == .mips) return error.SkipZigTest;
|
||||
|
||||
var test_dir = getTestDir();
|
||||
const tmp_file_name = "temp_test_file.txt";
|
||||
var file = try fs.cwd().createFile(tmp_file_name, .{});
|
||||
var file = try test_dir.createFile(tmp_file_name, .{});
|
||||
defer {
|
||||
file.close();
|
||||
fs.cwd().deleteFile(tmp_file_name) catch {};
|
||||
test_dir.deleteFile(tmp_file_name) catch {};
|
||||
}
|
||||
|
||||
// Verify that the file size changes and the file offset is not moved
|
||||
@ -152,11 +157,12 @@ test "setEndPos" {
|
||||
}
|
||||
|
||||
test "updateTimes" {
|
||||
var test_dir = getTestDir();
|
||||
const tmp_file_name = "just_a_temporary_file.txt";
|
||||
var file = try fs.cwd().createFile(tmp_file_name, .{ .read = true });
|
||||
var file = try test_dir.createFile(tmp_file_name, .{ .read = true });
|
||||
defer {
|
||||
file.close();
|
||||
std.fs.cwd().deleteFile(tmp_file_name) catch {};
|
||||
test_dir.deleteFile(tmp_file_name) catch {};
|
||||
}
|
||||
var stat_old = try file.stat();
|
||||
// Set atime and mtime to 5s before
|
||||
|
||||
@ -1,9 +1,12 @@
|
||||
const std = @import("../std.zig");
|
||||
const builtin = std.builtin;
|
||||
const net = std.net;
|
||||
const mem = std.mem;
|
||||
const testing = std.testing;
|
||||
|
||||
test "parse and render IPv6 addresses" {
|
||||
if (builtin.os.tag == .wasi) return error.SkipZigTest;
|
||||
|
||||
var buffer: [100]u8 = undefined;
|
||||
const ips = [_][]const u8{
|
||||
"FF01:0:0:0:0:0:0:FB",
|
||||
@ -42,6 +45,8 @@ test "parse and render IPv6 addresses" {
|
||||
}
|
||||
|
||||
test "parse and render IPv4 addresses" {
|
||||
if (builtin.os.tag == .wasi) return error.SkipZigTest;
|
||||
|
||||
var buffer: [18]u8 = undefined;
|
||||
for ([_][]const u8{
|
||||
"0.0.0.0",
|
||||
@ -63,7 +68,7 @@ test "parse and render IPv4 addresses" {
|
||||
}
|
||||
|
||||
test "resolve DNS" {
|
||||
if (std.builtin.os.tag == .windows) {
|
||||
if (builtin.os.tag == .windows or builtin.os.tag == .wasi) {
|
||||
// DNS resolution not implemented on Windows yet.
|
||||
return error.SkipZigTest;
|
||||
}
|
||||
@ -101,6 +106,8 @@ test "listen on a port, send bytes, receive bytes" {
|
||||
}
|
||||
|
||||
fn testClient(addr: net.Address) anyerror!void {
|
||||
if (builtin.os.tag == .wasi) return error.SkipZigTest;
|
||||
|
||||
const socket_file = try net.tcpConnectToAddress(addr);
|
||||
defer socket_file.close();
|
||||
|
||||
@ -111,6 +118,8 @@ fn testClient(addr: net.Address) anyerror!void {
|
||||
}
|
||||
|
||||
fn testServer(server: *net.StreamServer) anyerror!void {
|
||||
if (builtin.os.tag == .wasi) return error.SkipZigTest;
|
||||
|
||||
var client = try server.accept();
|
||||
|
||||
const stream = client.file.outStream();
|
||||
|
||||
380
lib/std/os.zig
380
lib/std/os.zig
@ -98,6 +98,7 @@ pub fn close(fd: fd_t) void {
|
||||
}
|
||||
if (builtin.os.tag == .wasi) {
|
||||
_ = wasi.fd_close(fd);
|
||||
return;
|
||||
}
|
||||
if (comptime std.Target.current.isDarwin()) {
|
||||
// This avoids the EINTR problem.
|
||||
@ -312,7 +313,6 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
|
||||
if (builtin.os.tag == .windows) {
|
||||
return windows.ReadFile(fd, buf, null, std.io.default_mode);
|
||||
}
|
||||
|
||||
if (builtin.os.tag == .wasi and !builtin.link_libc) {
|
||||
const iovs = [1]iovec{iovec{
|
||||
.iov_base = buf.ptr,
|
||||
@ -321,7 +321,18 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
|
||||
|
||||
var nread: usize = undefined;
|
||||
switch (wasi.fd_read(fd, &iovs, iovs.len, &nread)) {
|
||||
0 => return nread,
|
||||
wasi.ESUCCESS => return nread,
|
||||
wasi.EINTR => unreachable,
|
||||
wasi.EINVAL => unreachable,
|
||||
wasi.EFAULT => unreachable,
|
||||
wasi.EAGAIN => unreachable,
|
||||
wasi.EBADF => unreachable, // Always a race condition.
|
||||
wasi.EIO => return error.InputOutput,
|
||||
wasi.EISDIR => return error.IsDir,
|
||||
wasi.ENOBUFS => return error.SystemResources,
|
||||
wasi.ENOMEM => return error.SystemResources,
|
||||
wasi.ECONNRESET => return error.ConnectionResetByPeer,
|
||||
wasi.ETIMEDOUT => return error.ConnectionTimedOut,
|
||||
else => |err| return unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
@ -376,6 +387,22 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {
|
||||
const first = iov[0];
|
||||
return read(fd, first.iov_base[0..first.iov_len]);
|
||||
}
|
||||
if (builtin.os.tag == .wasi) {
|
||||
var nread: usize = undefined;
|
||||
switch (wasi.fd_read(fd, iov.ptr, iov.len, &nread)) {
|
||||
wasi.ESUCCESS => return nread,
|
||||
wasi.EINTR => unreachable,
|
||||
wasi.EINVAL => unreachable,
|
||||
wasi.EFAULT => unreachable,
|
||||
wasi.EAGAIN => unreachable, // currently not support in WASI
|
||||
wasi.EBADF => unreachable, // always a race condition
|
||||
wasi.EIO => return error.InputOutput,
|
||||
wasi.EISDIR => return error.IsDir,
|
||||
wasi.ENOBUFS => return error.SystemResources,
|
||||
wasi.ENOMEM => return error.SystemResources,
|
||||
else => |err| return unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
|
||||
const iov_count = math.cast(u31, iov.len) catch math.maxInt(u31);
|
||||
while (true) {
|
||||
@ -416,6 +443,31 @@ pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize {
|
||||
if (builtin.os.tag == .windows) {
|
||||
return windows.ReadFile(fd, buf, offset, std.io.default_mode);
|
||||
}
|
||||
if (builtin.os.tag == .wasi) {
|
||||
const iovs = [1]iovec{iovec{
|
||||
.iov_base = buf.ptr,
|
||||
.iov_len = buf.len,
|
||||
}};
|
||||
|
||||
var nread: usize = undefined;
|
||||
switch (wasi.fd_pread(fd, &iovs, iovs.len, offset, &nread)) {
|
||||
wasi.ESUCCESS => return nread,
|
||||
wasi.EINTR => unreachable,
|
||||
wasi.EINVAL => unreachable,
|
||||
wasi.EFAULT => unreachable,
|
||||
wasi.EAGAIN => unreachable,
|
||||
wasi.EBADF => unreachable, // Always a race condition.
|
||||
wasi.EIO => return error.InputOutput,
|
||||
wasi.EISDIR => return error.IsDir,
|
||||
wasi.ENOBUFS => return error.SystemResources,
|
||||
wasi.ENOMEM => return error.SystemResources,
|
||||
wasi.ECONNRESET => return error.ConnectionResetByPeer,
|
||||
wasi.ENXIO => return error.Unseekable,
|
||||
wasi.ESPIPE => return error.Unseekable,
|
||||
wasi.EOVERFLOW => return error.Unseekable,
|
||||
else => |err| return unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
|
||||
while (true) {
|
||||
const rc = system.pread(fd, buf.ptr, buf.len, offset);
|
||||
@ -442,7 +494,6 @@ pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize {
|
||||
else => |err| return unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
pub const TruncateError = error{
|
||||
@ -474,6 +525,19 @@ pub fn ftruncate(fd: fd_t, length: u64) TruncateError!void {
|
||||
else => return windows.unexpectedStatus(rc),
|
||||
}
|
||||
}
|
||||
if (std.Target.current.os.tag == .wasi) {
|
||||
switch (wasi.fd_filestat_set_size(fd, length)) {
|
||||
wasi.ESUCCESS => return,
|
||||
wasi.EINTR => unreachable,
|
||||
wasi.EFBIG => return error.FileTooBig,
|
||||
wasi.EIO => return error.InputOutput,
|
||||
wasi.EPERM => return error.CannotTruncate,
|
||||
wasi.ETXTBSY => return error.FileBusy,
|
||||
wasi.EBADF => unreachable, // Handle not open for writing
|
||||
wasi.EINVAL => unreachable, // Handle not open for writing
|
||||
else => |err| return unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
|
||||
while (true) {
|
||||
const rc = if (builtin.link_libc)
|
||||
@ -523,6 +587,25 @@ pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) PReadError!usize {
|
||||
const first = iov[0];
|
||||
return pread(fd, first.iov_base[0..first.iov_len], offset);
|
||||
}
|
||||
if (builtin.os.tag == .wasi) {
|
||||
var nread: usize = undefined;
|
||||
switch (wasi.fd_pread(fd, iov.ptr, iov.len, offset, &nread)) {
|
||||
wasi.ESUCCESS => return nread,
|
||||
wasi.EINTR => unreachable,
|
||||
wasi.EINVAL => unreachable,
|
||||
wasi.EFAULT => unreachable,
|
||||
wasi.EAGAIN => unreachable,
|
||||
wasi.EBADF => unreachable, // always a race condition
|
||||
wasi.EIO => return error.InputOutput,
|
||||
wasi.EISDIR => return error.IsDir,
|
||||
wasi.ENOBUFS => return error.SystemResources,
|
||||
wasi.ENOMEM => return error.SystemResources,
|
||||
wasi.ENXIO => return error.Unseekable,
|
||||
wasi.ESPIPE => return error.Unseekable,
|
||||
wasi.EOVERFLOW => return error.Unseekable,
|
||||
else => |err| return unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
|
||||
const iov_count = math.cast(u31, iov.len) catch math.maxInt(u31);
|
||||
|
||||
@ -594,13 +677,25 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize {
|
||||
}
|
||||
|
||||
if (builtin.os.tag == .wasi and !builtin.link_libc) {
|
||||
const ciovs = [1]iovec_const{iovec_const{
|
||||
const ciovs = [_]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,
|
||||
wasi.ESUCCESS => return nwritten,
|
||||
wasi.EINTR => unreachable,
|
||||
wasi.EINVAL => unreachable,
|
||||
wasi.EFAULT => unreachable,
|
||||
wasi.EAGAIN => unreachable,
|
||||
wasi.EBADF => unreachable, // Always a race condition.
|
||||
wasi.EDESTADDRREQ => unreachable, // `connect` was never called.
|
||||
wasi.EDQUOT => return error.DiskQuota,
|
||||
wasi.EFBIG => return error.FileTooBig,
|
||||
wasi.EIO => return error.InputOutput,
|
||||
wasi.ENOSPC => return error.NoSpaceLeft,
|
||||
wasi.EPERM => return error.AccessDenied,
|
||||
wasi.EPIPE => return error.BrokenPipe,
|
||||
else => |err| return unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
@ -662,6 +757,25 @@ pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!usize {
|
||||
const first = iov[0];
|
||||
return write(fd, first.iov_base[0..first.iov_len]);
|
||||
}
|
||||
if (builtin.os.tag == .wasi) {
|
||||
var nwritten: usize = undefined;
|
||||
switch (wasi.fd_write(fd, iov.ptr, iov.len, &nwritten)) {
|
||||
wasi.ESUCCESS => return nwritten,
|
||||
wasi.EINTR => unreachable,
|
||||
wasi.EINVAL => unreachable,
|
||||
wasi.EFAULT => unreachable,
|
||||
wasi.EAGAIN => unreachable,
|
||||
wasi.EBADF => unreachable, // Always a race condition.
|
||||
wasi.EDESTADDRREQ => unreachable, // `connect` was never called.
|
||||
wasi.EDQUOT => return error.DiskQuota,
|
||||
wasi.EFBIG => return error.FileTooBig,
|
||||
wasi.EIO => return error.InputOutput,
|
||||
wasi.ENOSPC => return error.NoSpaceLeft,
|
||||
wasi.EPERM => return error.AccessDenied,
|
||||
wasi.EPIPE => return error.BrokenPipe,
|
||||
else => |err| return unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
|
||||
const iov_count = math.cast(u31, iov.len) catch math.maxInt(u31);
|
||||
while (true) {
|
||||
@ -717,6 +831,33 @@ 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, std.io.default_mode);
|
||||
}
|
||||
if (builtin.os.tag == .wasi) {
|
||||
const ciovs = [1]iovec_const{iovec_const{
|
||||
.iov_base = bytes.ptr,
|
||||
.iov_len = bytes.len,
|
||||
}};
|
||||
|
||||
var nwritten: usize = undefined;
|
||||
switch (wasi.fd_pwrite(fd, &ciovs, ciovs.len, offset, &nwritten)) {
|
||||
wasi.ESUCCESS => return nwritten,
|
||||
wasi.EINTR => unreachable,
|
||||
wasi.EINVAL => unreachable,
|
||||
wasi.EFAULT => unreachable,
|
||||
wasi.EAGAIN => unreachable,
|
||||
wasi.EBADF => unreachable, // Always a race condition.
|
||||
wasi.EDESTADDRREQ => unreachable, // `connect` was never called.
|
||||
wasi.EDQUOT => return error.DiskQuota,
|
||||
wasi.EFBIG => return error.FileTooBig,
|
||||
wasi.EIO => return error.InputOutput,
|
||||
wasi.ENOSPC => return error.NoSpaceLeft,
|
||||
wasi.EPERM => return error.AccessDenied,
|
||||
wasi.EPIPE => return error.BrokenPipe,
|
||||
wasi.ENXIO => return error.Unseekable,
|
||||
wasi.ESPIPE => return error.Unseekable,
|
||||
wasi.EOVERFLOW => return error.Unseekable,
|
||||
else => |err| return unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent EINVAL.
|
||||
const max_count = switch (std.Target.current.os.tag) {
|
||||
@ -788,6 +929,28 @@ pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) PWriteError!usiz
|
||||
const first = iov[0];
|
||||
return pwrite(fd, first.iov_base[0..first.iov_len], offset);
|
||||
}
|
||||
if (builtin.os.tag == .wasi) {
|
||||
var nwritten: usize = undefined;
|
||||
switch (wasi.fd_pwrite(fd, iov.ptr, iov.len, offset, &nwritten)) {
|
||||
wasi.ESUCCESS => return nwritten,
|
||||
wasi.EINTR => unreachable,
|
||||
wasi.EINVAL => unreachable,
|
||||
wasi.EFAULT => unreachable,
|
||||
wasi.EAGAIN => unreachable,
|
||||
wasi.EBADF => unreachable, // Always a race condition.
|
||||
wasi.EDESTADDRREQ => unreachable, // `connect` was never called.
|
||||
wasi.EDQUOT => return error.DiskQuota,
|
||||
wasi.EFBIG => return error.FileTooBig,
|
||||
wasi.EIO => return error.InputOutput,
|
||||
wasi.ENOSPC => return error.NoSpaceLeft,
|
||||
wasi.EPERM => return error.AccessDenied,
|
||||
wasi.EPIPE => return error.BrokenPipe,
|
||||
wasi.ENXIO => return error.Unseekable,
|
||||
wasi.ESPIPE => return error.Unseekable,
|
||||
wasi.EOVERFLOW => return error.Unseekable,
|
||||
else => |err| return unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
|
||||
const iov_count = math.cast(u31, iov.len) catch math.maxInt(u31);
|
||||
while (true) {
|
||||
@ -923,6 +1086,37 @@ pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: mode_t) Ope
|
||||
return openatZ(dir_fd, &file_path_c, flags, mode);
|
||||
}
|
||||
|
||||
/// Open and possibly create a file in WASI.
|
||||
pub fn openatWasi(dir_fd: fd_t, file_path: []const u8, oflags: oflags_t, fdflags: fdflags_t, base: rights_t, inheriting: rights_t) OpenError!fd_t {
|
||||
while (true) {
|
||||
var fd: fd_t = undefined;
|
||||
switch (wasi.path_open(dir_fd, 0x0, file_path.ptr, file_path.len, oflags, base, inheriting, fdflags, &fd)) {
|
||||
wasi.ESUCCESS => return fd,
|
||||
wasi.EINTR => continue,
|
||||
|
||||
wasi.EFAULT => unreachable,
|
||||
wasi.EINVAL => unreachable,
|
||||
wasi.EACCES => return error.AccessDenied,
|
||||
wasi.EFBIG => return error.FileTooBig,
|
||||
wasi.EOVERFLOW => return error.FileTooBig,
|
||||
wasi.EISDIR => return error.IsDir,
|
||||
wasi.ELOOP => return error.SymLinkLoop,
|
||||
wasi.EMFILE => return error.ProcessFdQuotaExceeded,
|
||||
wasi.ENAMETOOLONG => return error.NameTooLong,
|
||||
wasi.ENFILE => return error.SystemFdQuotaExceeded,
|
||||
wasi.ENODEV => return error.NoDevice,
|
||||
wasi.ENOENT => return error.FileNotFound,
|
||||
wasi.ENOMEM => return error.SystemResources,
|
||||
wasi.ENOSPC => return error.NoSpaceLeft,
|
||||
wasi.ENOTDIR => return error.NotDir,
|
||||
wasi.EPERM => return error.AccessDenied,
|
||||
wasi.EEXIST => return error.PathAlreadyExists,
|
||||
wasi.EBUSY => return error.DeviceBusy,
|
||||
else => |err| return unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub const openatC = @compileError("deprecated: renamed to openatZ");
|
||||
|
||||
/// Open and possibly create a file. Keeps trying if it gets interrupted.
|
||||
@ -1282,6 +1476,9 @@ pub fn getcwd(out_buffer: []u8) GetCwdError![]u8 {
|
||||
if (builtin.os.tag == .windows) {
|
||||
return windows.GetCurrentDirectory(out_buffer);
|
||||
}
|
||||
if (builtin.os.tag == .wasi) {
|
||||
@compileError("WASI doesn't have a concept of cwd(); use std.fs.wasi.PreopenList to get available Dir handles instead");
|
||||
}
|
||||
|
||||
const err = if (builtin.link_libc) blk: {
|
||||
break :blk if (std.c.getcwd(out_buffer.ptr, out_buffer.len)) |_| 0 else std.c._errno().*;
|
||||
@ -1460,13 +1657,45 @@ pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!vo
|
||||
if (builtin.os.tag == .windows) {
|
||||
const file_path_w = try windows.sliceToPrefixedFileW(file_path);
|
||||
return unlinkatW(dirfd, file_path_w.span().ptr, flags);
|
||||
}
|
||||
} else if (builtin.os.tag == .wasi) {
|
||||
return unlinkatWasi(dirfd, file_path, flags);
|
||||
} else {
|
||||
const file_path_c = try toPosixPath(file_path);
|
||||
return unlinkatZ(dirfd, &file_path_c, flags);
|
||||
}
|
||||
}
|
||||
|
||||
pub const unlinkatC = @compileError("deprecated: renamed to unlinkatZ");
|
||||
|
||||
pub fn unlinkatWasi(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void {
|
||||
const remove_dir = (flags & AT_REMOVEDIR) != 0;
|
||||
const res = if (remove_dir)
|
||||
wasi.path_remove_directory(dirfd, file_path.ptr, file_path.len)
|
||||
else
|
||||
wasi.path_unlink_file(dirfd, file_path.ptr, file_path.len);
|
||||
switch (res) {
|
||||
wasi.ESUCCESS => return,
|
||||
wasi.EACCES => return error.AccessDenied,
|
||||
wasi.EPERM => return error.AccessDenied,
|
||||
wasi.EBUSY => return error.FileBusy,
|
||||
wasi.EFAULT => unreachable,
|
||||
wasi.EIO => return error.FileSystem,
|
||||
wasi.EISDIR => return error.IsDir,
|
||||
wasi.ELOOP => return error.SymLinkLoop,
|
||||
wasi.ENAMETOOLONG => return error.NameTooLong,
|
||||
wasi.ENOENT => return error.FileNotFound,
|
||||
wasi.ENOTDIR => return error.NotDir,
|
||||
wasi.ENOMEM => return error.SystemResources,
|
||||
wasi.EROFS => return error.ReadOnlyFileSystem,
|
||||
wasi.ENOTEMPTY => return error.DirNotEmpty,
|
||||
|
||||
wasi.EINVAL => unreachable, // invalid flags, or pathname has . as last component
|
||||
wasi.EBADF => unreachable, // always a race condition
|
||||
|
||||
else => |err| return unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
|
||||
/// Same as `unlinkat` but `file_path` is a null-terminated string.
|
||||
pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatError!void {
|
||||
if (builtin.os.tag == .windows) {
|
||||
@ -1645,6 +1874,8 @@ pub fn renameat(
|
||||
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.span(), new_dir_fd, new_path_w.span(), windows.TRUE);
|
||||
} else if (builtin.os.tag == .wasi) {
|
||||
return renameatWasi(old_dir_fd, old_path, new_dir_fd, new_path);
|
||||
} else {
|
||||
const old_path_c = try toPosixPath(old_path);
|
||||
const new_path_c = try toPosixPath(new_path);
|
||||
@ -1652,6 +1883,32 @@ pub fn renameat(
|
||||
}
|
||||
}
|
||||
|
||||
/// Same as `renameat` expect only WASI.
|
||||
pub fn renameatWasi(old_dir_fd: fd_t, old_path: []const u8, new_dir_fd: fd_t, new_path: []const u8) RenameError!void {
|
||||
switch (wasi.path_rename(old_dir_fd, old_path.ptr, old_path.len, new_dir_fd, new_path.ptr, new_path.len)) {
|
||||
wasi.ESUCCESS => return,
|
||||
wasi.EACCES => return error.AccessDenied,
|
||||
wasi.EPERM => return error.AccessDenied,
|
||||
wasi.EBUSY => return error.FileBusy,
|
||||
wasi.EDQUOT => return error.DiskQuota,
|
||||
wasi.EFAULT => unreachable,
|
||||
wasi.EINVAL => unreachable,
|
||||
wasi.EISDIR => return error.IsDir,
|
||||
wasi.ELOOP => return error.SymLinkLoop,
|
||||
wasi.EMLINK => return error.LinkQuotaExceeded,
|
||||
wasi.ENAMETOOLONG => return error.NameTooLong,
|
||||
wasi.ENOENT => return error.FileNotFound,
|
||||
wasi.ENOTDIR => return error.NotDir,
|
||||
wasi.ENOMEM => return error.SystemResources,
|
||||
wasi.ENOSPC => return error.NoSpaceLeft,
|
||||
wasi.EEXIST => return error.PathAlreadyExists,
|
||||
wasi.ENOTEMPTY => return error.PathAlreadyExists,
|
||||
wasi.EROFS => return error.ReadOnlyFileSystem,
|
||||
wasi.EXDEV => return error.RenameAcrossMountPoints,
|
||||
else => |err| return unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
|
||||
/// Same as `renameat` except the parameters are null-terminated byte arrays.
|
||||
pub fn renameatZ(
|
||||
old_dir_fd: fd_t,
|
||||
@ -1767,6 +2024,8 @@ pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!v
|
||||
if (builtin.os.tag == .windows) {
|
||||
const sub_dir_path_w = try windows.sliceToPrefixedFileW(sub_dir_path);
|
||||
return mkdiratW(dir_fd, sub_dir_path_w.span().ptr, mode);
|
||||
} else if (builtin.os.tag == .wasi) {
|
||||
return mkdiratWasi(dir_fd, sub_dir_path, mode);
|
||||
} else {
|
||||
const sub_dir_path_c = try toPosixPath(sub_dir_path);
|
||||
return mkdiratZ(dir_fd, &sub_dir_path_c, mode);
|
||||
@ -1775,6 +2034,27 @@ pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!v
|
||||
|
||||
pub const mkdiratC = @compileError("deprecated: renamed to mkdiratZ");
|
||||
|
||||
pub fn mkdiratWasi(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!void {
|
||||
switch (wasi.path_create_directory(dir_fd, sub_dir_path.ptr, sub_dir_path.len)) {
|
||||
wasi.ESUCCESS => return,
|
||||
wasi.EACCES => return error.AccessDenied,
|
||||
wasi.EBADF => unreachable,
|
||||
wasi.EPERM => return error.AccessDenied,
|
||||
wasi.EDQUOT => return error.DiskQuota,
|
||||
wasi.EEXIST => return error.PathAlreadyExists,
|
||||
wasi.EFAULT => unreachable,
|
||||
wasi.ELOOP => return error.SymLinkLoop,
|
||||
wasi.EMLINK => return error.LinkQuotaExceeded,
|
||||
wasi.ENAMETOOLONG => return error.NameTooLong,
|
||||
wasi.ENOENT => return error.FileNotFound,
|
||||
wasi.ENOMEM => return error.SystemResources,
|
||||
wasi.ENOSPC => return error.NoSpaceLeft,
|
||||
wasi.ENOTDIR => return error.NotDir,
|
||||
wasi.EROFS => return error.ReadOnlyFileSystem,
|
||||
else => |err| return unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mkdiratZ(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);
|
||||
@ -2623,8 +2903,19 @@ pub const FStatError = error{
|
||||
} || UnexpectedError;
|
||||
|
||||
pub fn fstat(fd: fd_t) FStatError!Stat {
|
||||
if (builtin.os.tag == .wasi) {
|
||||
var stat: Stat = undefined;
|
||||
switch (wasi.fd_filestat_get(fd, &stat)) {
|
||||
wasi.ESUCCESS => return stat,
|
||||
wasi.EINVAL => unreachable,
|
||||
wasi.EBADF => unreachable, // Always a race condition.
|
||||
wasi.ENOMEM => return error.SystemResources,
|
||||
wasi.EACCES => return error.AccessDenied,
|
||||
else => |err| return unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
|
||||
var stat: Stat = undefined;
|
||||
switch (errno(system.fstat(fd, &stat))) {
|
||||
0 => return stat,
|
||||
EINVAL => unreachable,
|
||||
@ -3097,6 +3388,10 @@ pub fn sysctl(
|
||||
newp: ?*c_void,
|
||||
newlen: usize,
|
||||
) SysCtlError!void {
|
||||
if (builtin.os.tag == .wasi) {
|
||||
@panic("unsupported");
|
||||
}
|
||||
|
||||
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,
|
||||
@ -3117,6 +3412,10 @@ pub fn sysctlbynameZ(
|
||||
newp: ?*c_void,
|
||||
newlen: usize,
|
||||
) SysCtlError!void {
|
||||
if (builtin.os.tag == .wasi) {
|
||||
@panic("unsupported");
|
||||
}
|
||||
|
||||
switch (errno(system.sysctlbyname(name, oldp, oldlenp, newp, newlen))) {
|
||||
0 => return,
|
||||
EFAULT => unreachable,
|
||||
@ -3154,6 +3453,18 @@ pub fn lseek_SET(fd: fd_t, offset: u64) SeekError!void {
|
||||
if (builtin.os.tag == .windows) {
|
||||
return windows.SetFilePointerEx_BEGIN(fd, offset);
|
||||
}
|
||||
if (builtin.os.tag == .wasi) {
|
||||
var new_offset: wasi.filesize_t = undefined;
|
||||
switch (wasi.fd_seek(fd, @bitCast(wasi.filedelta_t, offset), wasi.WHENCE_SET, &new_offset)) {
|
||||
wasi.ESUCCESS => return,
|
||||
wasi.EBADF => unreachable, // always a race condition
|
||||
wasi.EINVAL => return error.Unseekable,
|
||||
wasi.EOVERFLOW => return error.Unseekable,
|
||||
wasi.ESPIPE => return error.Unseekable,
|
||||
wasi.ENXIO => return error.Unseekable,
|
||||
else => |err| return unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
const ipos = @bitCast(i64, offset); // the OS treats this as unsigned
|
||||
switch (errno(system.lseek(fd, ipos, SEEK_SET))) {
|
||||
0 => return,
|
||||
@ -3183,6 +3494,18 @@ pub fn lseek_CUR(fd: fd_t, offset: i64) SeekError!void {
|
||||
if (builtin.os.tag == .windows) {
|
||||
return windows.SetFilePointerEx_CURRENT(fd, offset);
|
||||
}
|
||||
if (builtin.os.tag == .wasi) {
|
||||
var new_offset: wasi.filesize_t = undefined;
|
||||
switch (wasi.fd_seek(fd, offset, wasi.WHENCE_CUR, &new_offset)) {
|
||||
wasi.ESUCCESS => return,
|
||||
wasi.EBADF => unreachable, // always a race condition
|
||||
wasi.EINVAL => return error.Unseekable,
|
||||
wasi.EOVERFLOW => return error.Unseekable,
|
||||
wasi.ESPIPE => return error.Unseekable,
|
||||
wasi.ENXIO => return error.Unseekable,
|
||||
else => |err| return unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
switch (errno(system.lseek(fd, offset, SEEK_CUR))) {
|
||||
0 => return,
|
||||
EBADF => unreachable, // always a race condition
|
||||
@ -3211,6 +3534,18 @@ pub fn lseek_END(fd: fd_t, offset: i64) SeekError!void {
|
||||
if (builtin.os.tag == .windows) {
|
||||
return windows.SetFilePointerEx_END(fd, offset);
|
||||
}
|
||||
if (builtin.os.tag == .wasi) {
|
||||
var new_offset: wasi.filesize_t = undefined;
|
||||
switch (wasi.fd_seek(fd, offset, wasi.WHENCE_END, &new_offset)) {
|
||||
wasi.ESUCCESS => return,
|
||||
wasi.EBADF => unreachable, // always a race condition
|
||||
wasi.EINVAL => return error.Unseekable,
|
||||
wasi.EOVERFLOW => return error.Unseekable,
|
||||
wasi.ESPIPE => return error.Unseekable,
|
||||
wasi.ENXIO => return error.Unseekable,
|
||||
else => |err| return unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
switch (errno(system.lseek(fd, offset, SEEK_END))) {
|
||||
0 => return,
|
||||
EBADF => unreachable, // always a race condition
|
||||
@ -3239,6 +3574,18 @@ pub fn lseek_CUR_get(fd: fd_t) SeekError!u64 {
|
||||
if (builtin.os.tag == .windows) {
|
||||
return windows.SetFilePointerEx_CURRENT_get(fd);
|
||||
}
|
||||
if (builtin.os.tag == .wasi) {
|
||||
var new_offset: wasi.filesize_t = undefined;
|
||||
switch (wasi.fd_seek(fd, 0, wasi.WHENCE_CUR, &new_offset)) {
|
||||
wasi.ESUCCESS => return new_offset,
|
||||
wasi.EBADF => unreachable, // always a race condition
|
||||
wasi.EINVAL => return error.Unseekable,
|
||||
wasi.EOVERFLOW => return error.Unseekable,
|
||||
wasi.ESPIPE => return error.Unseekable,
|
||||
wasi.ENXIO => return error.Unseekable,
|
||||
else => |err| return unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
const rc = system.lseek(fd, 0, SEEK_CUR);
|
||||
switch (errno(rc)) {
|
||||
0 => return @bitCast(u64, rc),
|
||||
@ -3365,6 +3712,9 @@ pub fn realpath(pathname: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathE
|
||||
const pathname_w = try windows.sliceToPrefixedFileW(pathname);
|
||||
return realpathW(pathname_w.span().ptr, out_buffer);
|
||||
}
|
||||
if (builtin.os.tag == .wasi) {
|
||||
@compileError("Use std.fs.wasi.PreopenList to obtain valid Dir handles instead of using absolute paths");
|
||||
}
|
||||
const pathname_c = try toPosixPath(pathname);
|
||||
return realpathZ(&pathname_c, out_buffer);
|
||||
}
|
||||
@ -3679,6 +4029,24 @@ pub const FutimensError = error{
|
||||
} || UnexpectedError;
|
||||
|
||||
pub fn futimens(fd: fd_t, times: *const [2]timespec) FutimensError!void {
|
||||
if (builtin.os.tag == .wasi) {
|
||||
// TODO WASI encodes `wasi.fstflags` to signify magic values
|
||||
// similar to UTIME_NOW and UTIME_OMIT. Currently, we ignore
|
||||
// this here, but we should really handle it somehow.
|
||||
const atim = times[0].toTimestamp();
|
||||
const mtim = times[1].toTimestamp();
|
||||
switch (wasi.fd_filestat_set_times(fd, atim, mtim, wasi.FILESTAT_SET_ATIM | wasi.FILESTAT_SET_MTIM)) {
|
||||
wasi.ESUCCESS => return,
|
||||
wasi.EACCES => return error.AccessDenied,
|
||||
wasi.EPERM => return error.PermissionDenied,
|
||||
wasi.EBADF => unreachable, // always a race condition
|
||||
wasi.EFAULT => unreachable,
|
||||
wasi.EINVAL => unreachable,
|
||||
wasi.EROFS => return error.ReadOnlyFileSystem,
|
||||
else => |err| return unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
|
||||
switch (errno(system.futimens(fd, times))) {
|
||||
0 => return,
|
||||
EACCES => return error.AccessDenied,
|
||||
|
||||
@ -10,7 +10,25 @@ pub const time_t = i64; // match https://github.com/CraneStation/wasi-libc
|
||||
pub const timespec = extern struct {
|
||||
tv_sec: time_t,
|
||||
tv_nsec: isize,
|
||||
|
||||
pub fn fromTimestamp(tm: timestamp_t) timespec {
|
||||
const tv_sec: timestamp_t = tm / 1_000_000_000;
|
||||
const tv_nsec = tm - tv_sec * 1_000_000_000;
|
||||
return timespec{
|
||||
.tv_sec = @intCast(time_t, tv_sec),
|
||||
.tv_nsec = @intCast(isize, tv_nsec),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn toTimestamp(ts: timespec) timestamp_t {
|
||||
const tm = @intCast(timestamp_t, ts.tv_sec * 1_000_000_000) + @intCast(timestamp_t, ts.tv_nsec);
|
||||
return tm;
|
||||
}
|
||||
};
|
||||
|
||||
pub const Stat = filestat_t;
|
||||
|
||||
pub const AT_REMOVEDIR: u32 = 1; // there's no AT_REMOVEDIR in WASI, but we simulate here to match other OSes
|
||||
|
||||
// As defined in the wasi_snapshot_preview1 spec file:
|
||||
// https://github.com/WebAssembly/WASI/blob/master/phases/snapshot/witx/typenames.witx
|
||||
@ -164,14 +182,26 @@ pub const filedelta_t = i64;
|
||||
pub const filesize_t = u64;
|
||||
|
||||
pub const filestat_t = extern struct {
|
||||
st_dev: device_t,
|
||||
st_ino: inode_t,
|
||||
st_filetype: filetype_t,
|
||||
st_nlink: linkcount_t,
|
||||
st_size: filesize_t,
|
||||
st_atim: timestamp_t,
|
||||
st_mtim: timestamp_t,
|
||||
st_ctim: timestamp_t,
|
||||
dev: device_t,
|
||||
ino: inode_t,
|
||||
filetype: filetype_t,
|
||||
nlink: linkcount_t,
|
||||
size: filesize_t,
|
||||
atim: timestamp_t,
|
||||
mtim: timestamp_t,
|
||||
ctim: timestamp_t,
|
||||
|
||||
pub fn atime(self: filestat_t) timespec {
|
||||
return timespec.fromTimestamp(self.atim);
|
||||
}
|
||||
|
||||
pub fn mtime(self: filestat_t) timespec {
|
||||
return timespec.fromTimestamp(self.mtim);
|
||||
}
|
||||
|
||||
pub fn ctime(self: filestat_t) timespec {
|
||||
return timespec.fromTimestamp(self.ctim);
|
||||
}
|
||||
};
|
||||
|
||||
pub const filetype_t = u8;
|
||||
@ -254,6 +284,35 @@ pub const RIGHT_PATH_REMOVE_DIRECTORY: rights_t = 0x0000000002000000;
|
||||
pub const RIGHT_PATH_UNLINK_FILE: rights_t = 0x0000000004000000;
|
||||
pub const RIGHT_POLL_FD_READWRITE: rights_t = 0x0000000008000000;
|
||||
pub const RIGHT_SOCK_SHUTDOWN: rights_t = 0x0000000010000000;
|
||||
pub const RIGHT_ALL: rights_t = RIGHT_FD_DATASYNC |
|
||||
RIGHT_FD_READ |
|
||||
RIGHT_FD_SEEK |
|
||||
RIGHT_FD_FDSTAT_SET_FLAGS |
|
||||
RIGHT_FD_SYNC |
|
||||
RIGHT_FD_TELL |
|
||||
RIGHT_FD_WRITE |
|
||||
RIGHT_FD_ADVISE |
|
||||
RIGHT_FD_ALLOCATE |
|
||||
RIGHT_PATH_CREATE_DIRECTORY |
|
||||
RIGHT_PATH_CREATE_FILE |
|
||||
RIGHT_PATH_LINK_SOURCE |
|
||||
RIGHT_PATH_LINK_TARGET |
|
||||
RIGHT_PATH_OPEN |
|
||||
RIGHT_FD_READDIR |
|
||||
RIGHT_PATH_READLINK |
|
||||
RIGHT_PATH_RENAME_SOURCE |
|
||||
RIGHT_PATH_RENAME_TARGET |
|
||||
RIGHT_PATH_FILESTAT_GET |
|
||||
RIGHT_PATH_FILESTAT_SET_SIZE |
|
||||
RIGHT_PATH_FILESTAT_SET_TIMES |
|
||||
RIGHT_FD_FILESTAT_GET |
|
||||
RIGHT_FD_FILESTAT_SET_SIZE |
|
||||
RIGHT_FD_FILESTAT_SET_TIMES |
|
||||
RIGHT_PATH_SYMLINK |
|
||||
RIGHT_PATH_REMOVE_DIRECTORY |
|
||||
RIGHT_PATH_UNLINK_FILE |
|
||||
RIGHT_POLL_FD_READWRITE |
|
||||
RIGHT_SOCK_SHUTDOWN;
|
||||
|
||||
pub const roflags_t = u16;
|
||||
pub const SOCK_RECV_DATA_TRUNCATED: roflags_t = 0x0001;
|
||||
@ -306,7 +365,7 @@ pub const subscription_t = extern struct {
|
||||
};
|
||||
|
||||
pub const subscription_clock_t = extern struct {
|
||||
id: clock_id_t,
|
||||
id: clockid_t,
|
||||
timeout: timestamp_t,
|
||||
precision: timestamp_t,
|
||||
flags: subclockflags_t,
|
||||
|
||||
@ -15,13 +15,15 @@ const a = std.testing.allocator;
|
||||
const builtin = @import("builtin");
|
||||
const AtomicRmwOp = builtin.AtomicRmwOp;
|
||||
const AtomicOrder = builtin.AtomicOrder;
|
||||
const getTestDir = std.testing.getTestDir;
|
||||
|
||||
test "makePath, put some files in it, deleteTree" {
|
||||
try fs.cwd().makePath("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c");
|
||||
try fs.cwd().writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c" ++ fs.path.sep_str ++ "file.txt", "nonsense");
|
||||
try fs.cwd().writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "file2.txt", "blah");
|
||||
try fs.cwd().deleteTree("os_test_tmp");
|
||||
if (fs.cwd().openDir("os_test_tmp", .{})) |dir| {
|
||||
var test_dir = getTestDir();
|
||||
try test_dir.makePath("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c");
|
||||
try test_dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c" ++ fs.path.sep_str ++ "file.txt", "nonsense");
|
||||
try test_dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "file2.txt", "blah");
|
||||
try test_dir.deleteTree("os_test_tmp");
|
||||
if (test_dir.openDir("os_test_tmp", .{})) |dir| {
|
||||
@panic("expected error");
|
||||
} else |err| {
|
||||
expect(err == error.FileNotFound);
|
||||
@ -29,16 +31,19 @@ test "makePath, put some files in it, deleteTree" {
|
||||
}
|
||||
|
||||
test "access file" {
|
||||
try fs.cwd().makePath("os_test_tmp");
|
||||
if (fs.cwd().access("os_test_tmp" ++ fs.path.sep_str ++ "file.txt", .{})) |ok| {
|
||||
if (builtin.os.tag == .wasi) return error.SkipZigTest;
|
||||
|
||||
var test_dir = getTestDir();
|
||||
try test_dir.makePath("os_test_tmp");
|
||||
if (test_dir.access("os_test_tmp" ++ fs.path.sep_str ++ "file.txt", .{})) |ok| {
|
||||
@panic("expected error");
|
||||
} else |err| {
|
||||
expect(err == error.FileNotFound);
|
||||
}
|
||||
|
||||
try fs.cwd().writeFile("os_test_tmp" ++ fs.path.sep_str ++ "file.txt", "");
|
||||
try fs.cwd().access("os_test_tmp" ++ fs.path.sep_str ++ "file.txt", .{});
|
||||
try fs.cwd().deleteTree("os_test_tmp");
|
||||
try test_dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "file.txt", "");
|
||||
try test_dir.access("os_test_tmp" ++ fs.path.sep_str ++ "file.txt", .{});
|
||||
try test_dir.deleteTree("os_test_tmp");
|
||||
}
|
||||
|
||||
fn testThreadIdFn(thread_id: *Thread.Id) void {
|
||||
@ -46,10 +51,11 @@ fn testThreadIdFn(thread_id: *Thread.Id) void {
|
||||
}
|
||||
|
||||
test "sendfile" {
|
||||
try fs.cwd().makePath("os_test_tmp");
|
||||
defer fs.cwd().deleteTree("os_test_tmp") catch {};
|
||||
var test_dir = getTestDir();
|
||||
try test_dir.makePath("os_test_tmp");
|
||||
defer test_dir.deleteTree("os_test_tmp") catch {};
|
||||
|
||||
var dir = try fs.cwd().openDir("os_test_tmp", .{});
|
||||
var dir = try test_dir.openDir("os_test_tmp", .{});
|
||||
defer dir.close();
|
||||
|
||||
const line1 = "line1\n";
|
||||
@ -65,12 +71,12 @@ test "sendfile" {
|
||||
},
|
||||
};
|
||||
|
||||
var src_file = try dir.createFileZ("sendfile1.txt", .{ .read = true });
|
||||
var src_file = try dir.createFile("sendfile1.txt", .{ .read = true });
|
||||
defer src_file.close();
|
||||
|
||||
try src_file.writevAll(&vecs);
|
||||
|
||||
var dest_file = try dir.createFileZ("sendfile2.txt", .{ .read = true });
|
||||
var dest_file = try dir.createFile("sendfile2.txt", .{ .read = true });
|
||||
defer dest_file.close();
|
||||
|
||||
const header1 = "header1\n";
|
||||
@ -113,23 +119,23 @@ test "fs.copyFile" {
|
||||
const dest_file = "tmp_test_copy_file2.txt";
|
||||
const dest_file2 = "tmp_test_copy_file3.txt";
|
||||
|
||||
const cwd = fs.cwd();
|
||||
const test_dir = getTestDir();
|
||||
|
||||
try cwd.writeFile(src_file, data);
|
||||
defer cwd.deleteFile(src_file) catch {};
|
||||
try test_dir.writeFile(src_file, data);
|
||||
defer test_dir.deleteFile(src_file) catch {};
|
||||
|
||||
try cwd.copyFile(src_file, cwd, dest_file, .{});
|
||||
defer cwd.deleteFile(dest_file) catch {};
|
||||
try test_dir.copyFile(src_file, test_dir, dest_file, .{});
|
||||
defer test_dir.deleteFile(dest_file) catch {};
|
||||
|
||||
try cwd.copyFile(src_file, cwd, dest_file2, .{ .override_mode = File.default_mode });
|
||||
defer cwd.deleteFile(dest_file2) catch {};
|
||||
try test_dir.copyFile(src_file, test_dir, dest_file2, .{ .override_mode = File.default_mode });
|
||||
defer test_dir.deleteFile(dest_file2) catch {};
|
||||
|
||||
try expectFileContents(dest_file, data);
|
||||
try expectFileContents(dest_file2, data);
|
||||
}
|
||||
|
||||
fn expectFileContents(file_path: []const u8, data: []const u8) !void {
|
||||
const contents = try fs.cwd().readFileAlloc(testing.allocator, file_path, 1000);
|
||||
const contents = try getTestDir().readFileAlloc(testing.allocator, file_path, 1000);
|
||||
defer testing.allocator.free(contents);
|
||||
|
||||
testing.expectEqualSlices(u8, data, contents);
|
||||
@ -181,6 +187,8 @@ fn start2(ctx: *i32) u8 {
|
||||
}
|
||||
|
||||
test "cpu count" {
|
||||
if (builtin.os.tag == .wasi) return error.SkipZigTest;
|
||||
|
||||
const cpu_count = try Thread.cpuCount();
|
||||
expect(cpu_count >= 1);
|
||||
}
|
||||
@ -191,17 +199,18 @@ test "AtomicFile" {
|
||||
\\ hello!
|
||||
\\ this is a test file
|
||||
;
|
||||
var test_dir = getTestDir();
|
||||
{
|
||||
var af = try fs.cwd().atomicFile(test_out_file, .{});
|
||||
var af = try test_dir.atomicFile(test_out_file, .{});
|
||||
defer af.deinit();
|
||||
try af.file.writeAll(test_content);
|
||||
try af.finish();
|
||||
}
|
||||
const content = try fs.cwd().readFileAlloc(testing.allocator, test_out_file, 9999);
|
||||
const content = try test_dir.readFileAlloc(testing.allocator, test_out_file, 9999);
|
||||
defer testing.allocator.free(content);
|
||||
expect(mem.eql(u8, content, test_content));
|
||||
|
||||
try fs.cwd().deleteFile(test_out_file);
|
||||
try test_dir.deleteFile(test_out_file);
|
||||
}
|
||||
|
||||
test "thread local storage" {
|
||||
@ -231,12 +240,16 @@ test "getrandom" {
|
||||
}
|
||||
|
||||
test "getcwd" {
|
||||
if (builtin.os.tag == .wasi) return error.SkipZigTest;
|
||||
|
||||
// at least call it so it gets compiled
|
||||
var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
|
||||
_ = os.getcwd(&buf) catch undefined;
|
||||
}
|
||||
|
||||
test "realpath" {
|
||||
if (builtin.os.tag == .wasi) return error.SkipZigTest;
|
||||
|
||||
var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
|
||||
testing.expectError(error.FileNotFound, fs.realpath("definitely_bogus_does_not_exist1234", &buf));
|
||||
}
|
||||
@ -304,7 +317,7 @@ test "dl_iterate_phdr" {
|
||||
}
|
||||
|
||||
test "gethostname" {
|
||||
if (builtin.os.tag == .windows)
|
||||
if (builtin.os.tag == .windows or builtin.os.tag == .wasi)
|
||||
return error.SkipZigTest;
|
||||
|
||||
var buf: [os.HOST_NAME_MAX]u8 = undefined;
|
||||
@ -313,7 +326,7 @@ test "gethostname" {
|
||||
}
|
||||
|
||||
test "pipe" {
|
||||
if (builtin.os.tag == .windows)
|
||||
if (builtin.os.tag == .windows or builtin.os.tag == .wasi)
|
||||
return error.SkipZigTest;
|
||||
|
||||
var fds = try os.pipe();
|
||||
@ -349,7 +362,7 @@ test "memfd_create" {
|
||||
}
|
||||
|
||||
test "mmap" {
|
||||
if (builtin.os.tag == .windows)
|
||||
if (builtin.os.tag == .windows or builtin.os.tag == .wasi)
|
||||
return error.SkipZigTest;
|
||||
|
||||
// Simple mmap() call with non page-aligned size
|
||||
@ -451,7 +464,7 @@ test "getenv" {
|
||||
}
|
||||
|
||||
test "fcntl" {
|
||||
if (builtin.os.tag == .windows)
|
||||
if (builtin.os.tag == .windows or builtin.os.tag == .wasi)
|
||||
return error.SkipZigTest;
|
||||
|
||||
const test_out_file = "os_tmp_test";
|
||||
|
||||
@ -314,6 +314,9 @@ pub fn PackedIntSliceEndian(comptime Int: type, comptime endian: builtin.Endian)
|
||||
}
|
||||
|
||||
test "PackedIntArray" {
|
||||
// TODO @setEvalBranchQuota generates panics in wasm32. Investigate.
|
||||
if (builtin.arch == .wasm32) return error.SkipZigTest;
|
||||
|
||||
@setEvalBranchQuota(10000);
|
||||
const max_bits = 256;
|
||||
const int_count = 19;
|
||||
@ -357,6 +360,9 @@ test "PackedIntArray init" {
|
||||
}
|
||||
|
||||
test "PackedIntSlice" {
|
||||
// TODO @setEvalBranchQuota generates panics in wasm32. Investigate.
|
||||
if (builtin.arch == .wasm32) return error.SkipZigTest;
|
||||
|
||||
@setEvalBranchQuota(10000);
|
||||
const max_bits = 256;
|
||||
const int_count = 19;
|
||||
|
||||
@ -26,6 +26,8 @@ pub fn getCwdAlloc(allocator: *Allocator) ![]u8 {
|
||||
}
|
||||
|
||||
test "getCwdAlloc" {
|
||||
if (builtin.os.tag == .wasi) return error.SkipZigTest;
|
||||
|
||||
const cwd = try getCwdAlloc(testing.allocator);
|
||||
testing.allocator.free(cwd);
|
||||
}
|
||||
@ -69,9 +71,7 @@ pub fn getEnvMap(allocator: *Allocator) !BufMap {
|
||||
return os.unexpectedErrno(environ_sizes_get_ret);
|
||||
}
|
||||
|
||||
// TODO: Verify that the documentation is incorrect
|
||||
// https://github.com/WebAssembly/WASI/issues/27
|
||||
var environ = try allocator.alloc(?[*:0]u8, environ_count + 1);
|
||||
var environ = try allocator.alloc([*:0]u8, environ_count);
|
||||
defer allocator.free(environ);
|
||||
var environ_buf = try allocator.alloc(u8, environ_buf_size);
|
||||
defer allocator.free(environ_buf);
|
||||
@ -82,14 +82,12 @@ pub fn getEnvMap(allocator: *Allocator) !BufMap {
|
||||
}
|
||||
|
||||
for (environ) |env| {
|
||||
if (env) |ptr| {
|
||||
const pair = mem.spanZ(ptr);
|
||||
const pair = mem.spanZ(env);
|
||||
var parts = mem.split(pair, "=");
|
||||
const key = parts.next().?;
|
||||
const value = parts.next().?;
|
||||
try result.set(key, value);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
} else if (builtin.link_libc) {
|
||||
var ptr = std.c.environ;
|
||||
|
||||
@ -75,5 +75,10 @@ comptime {
|
||||
}
|
||||
|
||||
test "" {
|
||||
// TODO is there a way around this? When enabled for WASI, we pick up functions
|
||||
// which generate compile error. Perhaps semantic analyser should skip those
|
||||
// if running in test mode?
|
||||
if (builtin.os.tag != .wasi) {
|
||||
meta.refAllDecls(@This());
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,6 +14,21 @@ pub var failing_allocator_instance = FailingAllocator.init(&base_allocator_insta
|
||||
pub var base_allocator_instance = std.heap.ThreadSafeFixedBufferAllocator.init(allocator_mem[0..]);
|
||||
var allocator_mem: [2 * 1024 * 1024]u8 = undefined;
|
||||
|
||||
/// This function is intended to be used only in tests. It should be used in any testcase
|
||||
/// where we intend to test WASI and should be used a replacement for `std.fs.cwd()` in WASI.
|
||||
pub fn getTestDir() std.fs.Dir {
|
||||
if (@import("builtin").os.tag == .wasi) {
|
||||
var preopens = std.fs.wasi.PreopenList.init(allocator);
|
||||
defer preopens.deinit();
|
||||
preopens.populate() catch unreachable;
|
||||
|
||||
const preopen = preopens.find(".") orelse unreachable;
|
||||
return std.fs.Dir{ .fd = preopen.fd };
|
||||
} else {
|
||||
return std.fs.cwd();
|
||||
}
|
||||
}
|
||||
|
||||
/// This function is intended to be used only in tests. It prints diagnostics to stderr
|
||||
/// and then aborts when actual_error_union is not expected_error.
|
||||
pub fn expectError(expected_error: anyerror, actual_error_union: var) void {
|
||||
|
||||
@ -19,6 +19,39 @@ pub fn sleep(nanoseconds: u64) void {
|
||||
os.windows.kernel32.Sleep(ms);
|
||||
return;
|
||||
}
|
||||
if (builtin.os.tag == .wasi) {
|
||||
const w = std.os.wasi;
|
||||
const userdata: w.userdata_t = 0x0123_45678;
|
||||
const clock = w.subscription_clock_t{
|
||||
.id = w.CLOCK_MONOTONIC,
|
||||
.timeout = nanoseconds,
|
||||
.precision = 0,
|
||||
.flags = 0,
|
||||
};
|
||||
const in = w.subscription_t{
|
||||
.userdata = userdata,
|
||||
.u = w.subscription_u_t{
|
||||
.tag = w.EVENTTYPE_CLOCK,
|
||||
.u = w.subscription_u_u_t{
|
||||
.clock = clock,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
var event: w.event_t = undefined;
|
||||
var nevents: usize = undefined;
|
||||
switch (w.poll_oneoff(&in, &event, 1, &nevents)) {
|
||||
w.ESUCCESS => {},
|
||||
else => |err| @panic("unexpected error of poll_oneoff"),
|
||||
}
|
||||
|
||||
if (nevents == 1 and event.userdata == userdata and event.@"error" == w.ESUCCESS and event.@"type" == w.EVENTTYPE_CLOCK) {
|
||||
return;
|
||||
}
|
||||
|
||||
@panic("unexpected result of poll_oneoff");
|
||||
}
|
||||
|
||||
const s = nanoseconds / ns_per_s;
|
||||
const ns = nanoseconds % ns_per_s;
|
||||
std.os.nanosleep(s, ns);
|
||||
@ -202,7 +235,7 @@ test "sleep" {
|
||||
|
||||
test "timestamp" {
|
||||
const ns_per_ms = (ns_per_s / ms_per_s);
|
||||
const margin = 50;
|
||||
const margin = ns_per_ms * 50;
|
||||
|
||||
const time_0 = milliTimestamp();
|
||||
sleep(ns_per_ms);
|
||||
|
||||
@ -50,6 +50,15 @@ const test_targets = blk: {
|
||||
.single_threaded = true,
|
||||
},
|
||||
|
||||
TestTarget{
|
||||
.target = .{
|
||||
.cpu_arch = .wasm32,
|
||||
.os_tag = .wasi,
|
||||
},
|
||||
.link_libc = false,
|
||||
.single_threaded = true,
|
||||
},
|
||||
|
||||
TestTarget{
|
||||
.target = .{
|
||||
.cpu_arch = .x86_64,
|
||||
@ -463,6 +472,7 @@ pub fn addPkgTests(
|
||||
skip_single_threaded: bool,
|
||||
skip_non_native: bool,
|
||||
skip_libc: bool,
|
||||
skip_wasi: bool,
|
||||
is_wine_enabled: bool,
|
||||
is_qemu_enabled: bool,
|
||||
glibc_dir: ?[]const u8,
|
||||
@ -473,6 +483,15 @@ pub fn addPkgTests(
|
||||
if (skip_non_native and !test_target.target.isNative())
|
||||
continue;
|
||||
|
||||
if (skip_wasi) {
|
||||
if (test_target.target.os_tag) |tag| {
|
||||
if (tag == .wasi) {
|
||||
warn("Skipping {} on wasm32-wasi.\n", .{root_src});
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (skip_libc and test_target.link_libc)
|
||||
continue;
|
||||
|
||||
@ -525,6 +544,7 @@ pub fn addPkgTests(
|
||||
these_tests.overrideZigLibDir("lib");
|
||||
these_tests.enable_wine = is_wine_enabled;
|
||||
these_tests.enable_qemu = is_qemu_enabled;
|
||||
these_tests.enable_wasmtime = !skip_wasi;
|
||||
these_tests.glibc_multi_install_dir = glibc_dir;
|
||||
|
||||
step.dependOn(&these_tests.step);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user