aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Kelley <andrew@ziglang.org>2020-05-18 19:00:25 -0400
committerGitHub <noreply@github.com>2020-05-18 19:00:25 -0400
commitfe28d732710b916695c6926a06aad0cc93156516 (patch)
treec3e674556afe22d31536748d6211639333e0b7d5
parenta80ad0782d5e7ce065dd47f21bcb81f69c1d1670 (diff)
parentcd8daa533ad7e53a3d071dcb782bc14c6fc72711 (diff)
downloadzig-fe28d732710b916695c6926a06aad0cc93156516.tar.gz
zig-fe28d732710b916695c6926a06aad0cc93156516.zip
Merge pull request #5303 from kubkon/dir-fns
Add/fix missing WASI functionality to pass libstd tests
-rw-r--r--build.zig10
-rwxr-xr-xci/azure/linux_script7
-rw-r--r--lib/std/build.zig6
-rw-r--r--lib/std/fmt.zig7
-rw-r--r--lib/std/fs.zig187
-rw-r--r--lib/std/fs/file.zig7
-rw-r--r--lib/std/fs/get_app_data_dir.zig2
-rw-r--r--lib/std/fs/path.zig11
-rw-r--r--lib/std/fs/test.zig7
-rw-r--r--lib/std/fs/wasi.zig15
-rw-r--r--lib/std/io/test.zig43
-rw-r--r--lib/std/net/test.zig11
-rw-r--r--lib/std/os.zig384
-rw-r--r--lib/std/os/bits/wasi.zig119
-rw-r--r--lib/std/os/test.zig108
-rw-r--r--lib/std/packed_int_array.zig6
-rw-r--r--lib/std/process.zig18
-rw-r--r--lib/std/testing.zig18
-rw-r--r--lib/std/time.zig27
-rw-r--r--lib/std/zig/parser_test.zig7
-rw-r--r--test/stage1/behavior.zig8
-rw-r--r--test/stage1/behavior/align.zig6
-rw-r--r--test/stage1/behavior/shuffle.zig4
-rw-r--r--test/stage1/behavior/vector.zig4
-rw-r--r--test/tests.zig11
25 files changed, 886 insertions, 147 deletions
diff --git a/build.zig b/build.zig
index 3636da4f28..4d71b0fb36 100644
--- a/build.zig
+++ b/build.zig
@@ -86,6 +86,7 @@ pub fn build(b: *Builder) !void {
const is_wine_enabled = b.option(bool, "enable-wine", "Use Wine to run cross compiled Windows tests") orelse false;
const is_qemu_enabled = b.option(bool, "enable-qemu", "Use QEMU to run cross compiled foreign architecture tests") orelse false;
+ const is_wasmtime_enabled = b.option(bool, "enable-wasmtime", "Use Wasmtime to enable and run WASI libstd tests") orelse false;
const glibc_multi_dir = b.option([]const u8, "enable-foreign-glibc", "Provide directory with glibc installations to run cross compiled tests that link glibc");
const test_stage2_step = b.step("test-stage2", "Run the stage2 compiler tests");
@@ -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, is_wine_enabled, is_qemu_enabled, is_wasmtime_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, is_wine_enabled, is_qemu_enabled, is_wasmtime_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, is_wine_enabled, is_qemu_enabled, is_wasmtime_enabled, glibc_multi_dir));
test_step.dependOn(tests.addCompareOutputTests(b, test_filter, modes));
test_step.dependOn(tests.addStandaloneTests(b, test_filter, modes));
@@ -130,7 +132,7 @@ pub fn build(b: *Builder) !void {
test_step.dependOn(tests.addTranslateCTests(b, test_filter));
test_step.dependOn(tests.addRunTranslatedCTests(b, test_filter));
// tests for this feature are disabled until we have the self-hosted compiler available
- //test_step.dependOn(tests.addGenHTests(b, test_filter));
+ // test_step.dependOn(tests.addGenHTests(b, test_filter));
test_step.dependOn(tests.addCompileErrorTests(b, test_filter, modes));
test_step.dependOn(docs_step);
}
diff --git a/ci/azure/linux_script b/ci/azure/linux_script
index 2a5d2ef1a8..2bc9402063 100755
--- a/ci/azure/linux_script
+++ b/ci/azure/linux_script
@@ -19,6 +19,11 @@ wget https://ziglang.org/deps/$QEMUBASE.tar.xz
tar xf $QEMUBASE.tar.xz
PATH=$PWD/$QEMUBASE/bin:$PATH
+WASMTIME="wasmtime-v0.15.0-x86_64-linux"
+wget https://github.com/bytecodealliance/wasmtime/releases/download/v0.15.0/$WASMTIME.tar.xz
+tar xf $WASMTIME.tar.xz
+PATH=$PWD/$WASMTIME:$PATH
+
# Make the `zig version` number consistent.
# This will affect the cmake command below.
git config core.abbrev 9
@@ -45,7 +50,7 @@ mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
make -j$(nproc) install
-./zig build test -Denable-qemu
+./zig build test -Denable-qemu -Denable-wasmtime
VERSION="$(./zig version)"
if [ "${BUILD_REASON}" != "PullRequest" ]; then
diff --git a/lib/std/build.zig b/lib/std/build.zig
index 08b014dbdd..4fdb64903f 100644
--- a/lib/std/build.zig
+++ b/lib/std/build.zig
@@ -1061,6 +1061,8 @@ pub const Builder = struct {
};
test "builder.findProgram compiles" {
+ if (builtin.os.tag == .wasi) return error.SkipZigTest;
+
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
@@ -1706,7 +1708,7 @@ pub const LibExeObjStep = struct {
.Enum => |enum_info| {
out.print("const {} = enum {{\n", .{@typeName(T)}) catch unreachable;
inline for (enum_info.fields) |field| {
- out.print(" {},\n", .{ field.name }) catch unreachable;
+ out.print(" {},\n", .{field.name}) catch unreachable;
}
out.print("}};\n", .{}) catch unreachable;
},
@@ -2089,6 +2091,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");
},
}
diff --git a/lib/std/fmt.zig b/lib/std/fmt.zig
index 16b3439f0c..f609ca69ac 100644
--- a/lib/std/fmt.zig
+++ b/lib/std/fmt.zig
@@ -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 vbool: std.meta.Vector(4, bool) = [_]bool{ true, false, true, false };
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});
diff --git a/lib/std/fs.zig b/lib/std/fs.zig
index 862fc84ef1..a006d4ea05 100644
--- a/lib/std/fs.zig
+++ b/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);
- 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;
- }
+ try os.renameat(self.dir.fd, self.tmp_path_buf[0..], self.dir.fd, self.dest_basename);
+ 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,9 +1161,15 @@ 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);
}
- const sub_path_c = try os.toPosixPath(sub_path);
- return self.deleteDirZ(&sub_path_c);
}
/// Same as `deleteDir` except the parameter is null-terminated.
@@ -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 "" {
- _ = makeDirAbsolute;
- _ = makeDirAbsoluteZ;
- _ = copyFileAbsolute;
- _ = updateFileAbsolute;
+ if (builtin.os.tag != .wasi) {
+ _ = makeDirAbsolute;
+ _ = makeDirAbsoluteZ;
+ _ = copyFileAbsolute;
+ _ = updateFileAbsolute;
+ }
_ = Dir.copyFile;
_ = @import("fs/test.zig");
_ = @import("fs/path.zig");
diff --git a/lib/std/fs/file.zig b/lib/std/fs/file.zig
index 3ea147679d..f8cf69cbb8 100644
--- a/lib/std/fs/file.zig
+++ b/lib/std/fs/file.zig
@@ -30,6 +30,7 @@ pub const File = struct {
pub const default_mode = switch (builtin.os.tag) {
.windows => 0,
+ .wasi => 0,
else => 0o666,
};
@@ -140,6 +141,12 @@ 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, and
+ // stderr is always sanitized.
+ return false;
+ }
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
diff --git a/lib/std/fs/get_app_data_dir.zig b/lib/std/fs/get_app_data_dir.zig
index e104aa99e9..c68232d0b5 100644
--- a/lib/std/fs/get_app_data_dir.zig
+++ b/lib/std/fs/get_app_data_dir.zig
@@ -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);
diff --git a/lib/std/fs/path.zig b/lib/std/fs/path.zig
index e040330dd0..7504570d2d 100644
--- a/lib/std/fs/path.zig
+++ b/lib/std/fs/path.zig
@@ -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");
diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig
index 7d238c7e92..a857c3a7f8 100644
--- a/lib/std/fs/test.zig
+++ b/lib/std/fs/test.zig
@@ -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";
@@ -35,7 +39,6 @@ test "open file with lock twice, make sure it wasn't open at the same time" {
}
const filename = "file_lock_test.txt";
-
var contexts = [_]FileLockTestContext{
.{ .filename = filename, .create = true, .lock = .Exclusive },
.{ .filename = filename, .create = true, .lock = .Exclusive },
@@ -111,6 +114,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"};
diff --git a/lib/std/fs/wasi.zig b/lib/std/fs/wasi.zig
index 1cf56e6c7b..194e1d41a6 100644
--- a/lib/std/fs/wasi.zig
+++ b/lib/std/fs/wasi.zig
@@ -117,7 +117,7 @@ pub const PreopenList = struct {
/// TODO make the function more generic by searching by `PreopenType` union. This will
/// be needed in the future when WASI extends its capabilities to resources
/// other than preopened directories.
- pub fn find(self: *const Self, path: []const u8) ?*const Preopen {
+ pub fn find(self: Self, path: []const u8) ?*const Preopen {
for (self.buffer.items) |preopen| {
switch (preopen.@"type") {
PreopenType.Dir => |preopen_path| {
@@ -129,7 +129,7 @@ pub const PreopenList = struct {
}
/// Return the inner buffer as read-only slice.
- pub fn asSlice(self: *const Self) []const Preopen {
+ pub fn asSlice(self: Self) []const Preopen {
return self.buffer.items;
}
@@ -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;
-}
diff --git a/lib/std/io/test.zig b/lib/std/io/test.zig
index 32a317d029..9ed02262b4 100644
--- a/lib/std/io/test.zig
+++ b/lib/std/io/test.zig
@@ -11,15 +11,18 @@ const mem = std.mem;
const fs = std.fs;
const File = std.fs.File;
+const tmpDir = std.testing.tmpDir;
+
test "write a file, read it, then delete it" {
- const cwd = fs.cwd();
+ var tmp = tmpDir(.{});
+ defer tmp.cleanup();
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 tmp.dir.createFile(tmp_file_name, .{});
defer file.close();
var buf_stream = io.bufferedOutStream(file.outStream());
@@ -32,7 +35,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 (tmp.dir.createFile(tmp_file_name, .{ .exclusive = true })) |file| {
unreachable;
} else |err| {
std.debug.assert(err == File.OpenError.PathAlreadyExists);
@@ -40,7 +43,7 @@ test "write a file, read it, then delete it" {
}
{
- var file = try cwd.openFile(tmp_file_name, .{});
+ var file = try tmp.dir.openFile(tmp_file_name, .{});
defer file.close();
const file_size = try file.getEndPos();
@@ -56,13 +59,16 @@ 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 tmp.dir.deleteFile(tmp_file_name);
}
test "BitStreams with File Stream" {
+ var tmp = tmpDir(.{});
+ defer tmp.cleanup();
+
const tmp_file_name = "temp_test_file.txt";
{
- var file = try fs.cwd().createFile(tmp_file_name, .{});
+ var file = try tmp.dir.createFile(tmp_file_name, .{});
defer file.close();
var bit_stream = io.bitOutStream(builtin.endian, file.outStream());
@@ -76,7 +82,7 @@ test "BitStreams with File Stream" {
try bit_stream.flushBits();
}
{
- var file = try fs.cwd().openFile(tmp_file_name, .{});
+ var file = try tmp.dir.openFile(tmp_file_name, .{});
defer file.close();
var bit_stream = io.bitInStream(builtin.endian, file.inStream());
@@ -98,15 +104,18 @@ test "BitStreams with File Stream" {
expectError(error.EndOfStream, bit_stream.readBitsNoEof(u1, 1));
}
- try fs.cwd().deleteFile(tmp_file_name);
+ try tmp.dir.deleteFile(tmp_file_name);
}
test "File seek ops" {
+ var tmp = tmpDir(.{});
+ defer tmp.cleanup();
+
const tmp_file_name = "temp_test_file.txt";
- var file = try fs.cwd().createFile(tmp_file_name, .{});
+ var file = try tmp.dir.createFile(tmp_file_name, .{});
defer {
file.close();
- fs.cwd().deleteFile(tmp_file_name) catch {};
+ tmp.dir.deleteFile(tmp_file_name) catch {};
}
try file.writeAll(&([_]u8{0x55} ** 8192));
@@ -129,11 +138,14 @@ test "setEndPos" {
// https://github.com/ziglang/zig/issues/5127
if (std.Target.current.cpu.arch == .mips) return error.SkipZigTest;
+ var tmp = tmpDir(.{});
+ defer tmp.cleanup();
+
const tmp_file_name = "temp_test_file.txt";
- var file = try fs.cwd().createFile(tmp_file_name, .{});
+ var file = try tmp.dir.createFile(tmp_file_name, .{});
defer {
file.close();
- fs.cwd().deleteFile(tmp_file_name) catch {};
+ tmp.dir.deleteFile(tmp_file_name) catch {};
}
// Verify that the file size changes and the file offset is not moved
@@ -152,11 +164,14 @@ test "setEndPos" {
}
test "updateTimes" {
+ var tmp = tmpDir(.{});
+ defer tmp.cleanup();
+
const tmp_file_name = "just_a_temporary_file.txt";
- var file = try fs.cwd().createFile(tmp_file_name, .{ .read = true });
+ var file = try tmp.dir.createFile(tmp_file_name, .{ .read = true });
defer {
file.close();
- std.fs.cwd().deleteFile(tmp_file_name) catch {};
+ tmp.dir.deleteFile(tmp_file_name) catch {};
}
var stat_old = try file.stat();
// Set atime and mtime to 5s before
diff --git a/lib/std/net/test.zig b/lib/std/net/test.zig
index f4f97d3944..1be8f83038 100644
--- a/lib/std/net/test.zig
+++ b/lib/std/net/test.zig
@@ -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();
diff --git a/lib/std/os.zig b/lib/std/os.zig
index 1eab9affe7..bc8803e3b0 100644
--- a/lib/std/os.zig
+++ b/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);
}
- 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 {
- var stat: Stat = undefined;
+ if (builtin.os.tag == .wasi) {
+ var stat: wasi.filestat_t = undefined;
+ switch (wasi.fd_filestat_get(fd, &stat)) {
+ wasi.ESUCCESS => return Stat.fromFilestat(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,
diff --git a/lib/std/os/bits/wasi.zig b/lib/std/os/bits/wasi.zig
index 0da984021d..270345c53b 100644
--- a/lib/std/os/bits/wasi.zig
+++ b/lib/std/os/bits/wasi.zig
@@ -3,15 +3,71 @@ pub const STDIN_FILENO = 0;
pub const STDOUT_FILENO = 1;
pub const STDERR_FILENO = 2;
-pub const mode_t = u32;
+pub const mode_t = u0;
pub const time_t = i64; // match https://github.com/CraneStation/wasi-libc
-pub const timespec = extern struct {
+pub const timespec = 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 = struct {
+ dev: device_t,
+ ino: inode_t,
+ mode: mode_t,
+ filetype: filetype_t,
+ nlink: linkcount_t,
+ size: filesize_t,
+ atim: timespec,
+ mtim: timespec,
+ ctim: timespec,
+
+ const Self = @This();
+
+ pub fn fromFilestat(stat: filestat_t) Self {
+ return Self{
+ .dev = stat.dev,
+ .ino = stat.ino,
+ .mode = 0,
+ .filetype = stat.filetype,
+ .nlink = stat.nlink,
+ .size = stat.size,
+ .atim = stat.atime(),
+ .mtim = stat.mtime(),
+ .ctim = stat.ctime(),
+ };
+ }
+
+ pub fn atime(self: Self) timespec {
+ return self.atim;
+ }
+
+ pub fn mtime(self: Self) timespec {
+ return self.mtim;
+ }
+
+ pub fn ctime(self: Self) timespec {
+ return self.ctim;
+ }
};
+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
pub const advice_t = u8;
@@ -164,14 +220,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 +322,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 +403,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,
diff --git a/lib/std/os/test.zig b/lib/std/os/test.zig
index 9c5ee44e88..cc3b4f5741 100644
--- a/lib/std/os/test.zig
+++ b/lib/std/os/test.zig
@@ -15,13 +15,18 @@ const a = std.testing.allocator;
const builtin = @import("builtin");
const AtomicRmwOp = builtin.AtomicRmwOp;
const AtomicOrder = builtin.AtomicOrder;
+const tmpDir = std.testing.tmpDir;
+const Dir = std.fs.Dir;
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 tmp = tmpDir(.{});
+ defer tmp.cleanup();
+
+ try tmp.dir.makePath("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c");
+ try tmp.dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c" ++ fs.path.sep_str ++ "file.txt", "nonsense");
+ try tmp.dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "file2.txt", "blah");
+ try tmp.dir.deleteTree("os_test_tmp");
+ if (tmp.dir.openDir("os_test_tmp", .{})) |dir| {
@panic("expected error");
} else |err| {
expect(err == error.FileNotFound);
@@ -29,16 +34,21 @@ 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 tmp = tmpDir(.{});
+ defer tmp.cleanup();
+
+ try tmp.dir.makePath("os_test_tmp");
+ if (tmp.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 tmp.dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "file.txt", "");
+ try tmp.dir.access("os_test_tmp" ++ fs.path.sep_str ++ "file.txt", .{});
+ try tmp.dir.deleteTree("os_test_tmp");
}
fn testThreadIdFn(thread_id: *Thread.Id) void {
@@ -46,10 +56,13 @@ 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 tmp = tmpDir(.{});
+ defer tmp.cleanup();
+
+ try tmp.dir.makePath("os_test_tmp");
+ defer tmp.dir.deleteTree("os_test_tmp") catch {};
- var dir = try fs.cwd().openDir("os_test_tmp", .{});
+ var dir = try tmp.dir.openDir("os_test_tmp", .{});
defer dir.close();
const line1 = "line1\n";
@@ -65,12 +78,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 +126,24 @@ test "fs.copyFile" {
const dest_file = "tmp_test_copy_file2.txt";
const dest_file2 = "tmp_test_copy_file3.txt";
- const cwd = fs.cwd();
+ var tmp = tmpDir(.{});
+ defer tmp.cleanup();
- try cwd.writeFile(src_file, data);
- defer cwd.deleteFile(src_file) catch {};
+ try tmp.dir.writeFile(src_file, data);
+ defer tmp.dir.deleteFile(src_file) catch {};
- try cwd.copyFile(src_file, cwd, dest_file, .{});
- defer cwd.deleteFile(dest_file) catch {};
+ try tmp.dir.copyFile(src_file, tmp.dir, dest_file, .{});
+ defer tmp.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 tmp.dir.copyFile(src_file, tmp.dir, dest_file2, .{ .override_mode = File.default_mode });
+ defer tmp.dir.deleteFile(dest_file2) catch {};
- try expectFileContents(dest_file, data);
- try expectFileContents(dest_file2, data);
+ try expectFileContents(tmp.dir, dest_file, data);
+ try expectFileContents(tmp.dir, dest_file2, data);
}
-fn expectFileContents(file_path: []const u8, data: []const u8) !void {
- const contents = try fs.cwd().readFileAlloc(testing.allocator, file_path, 1000);
+fn expectFileContents(dir: Dir, file_path: []const u8, data: []const u8) !void {
+ const contents = try dir.readFileAlloc(testing.allocator, file_path, 1000);
defer testing.allocator.free(contents);
testing.expectEqualSlices(u8, data, contents);
@@ -181,6 +195,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 +207,21 @@ test "AtomicFile" {
\\ hello!
\\ this is a test file
;
+
+ var tmp = tmpDir(.{});
+ defer tmp.cleanup();
+
{
- var af = try fs.cwd().atomicFile(test_out_file, .{});
+ var af = try tmp.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 tmp.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 tmp.dir.deleteFile(test_out_file);
}
test "thread local storage" {
@@ -231,12 +251,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 +328,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 +337,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,9 +373,12 @@ test "memfd_create" {
}
test "mmap" {
- if (builtin.os.tag == .windows)
+ if (builtin.os.tag == .windows or builtin.os.tag == .wasi)
return error.SkipZigTest;
+ var tmp = tmpDir(.{});
+ defer tmp.cleanup();
+
// Simple mmap() call with non page-aligned size
{
const data = try os.mmap(
@@ -380,7 +407,7 @@ test "mmap" {
// Create a file used for testing mmap() calls with a file descriptor
{
- const file = try fs.cwd().createFile(test_out_file, .{});
+ const file = try tmp.dir.createFile(test_out_file, .{});
defer file.close();
const stream = file.outStream();
@@ -393,7 +420,7 @@ test "mmap" {
// Map the whole file
{
- const file = try fs.cwd().openFile(test_out_file, .{});
+ const file = try tmp.dir.openFile(test_out_file, .{});
defer file.close();
const data = try os.mmap(
@@ -417,7 +444,7 @@ test "mmap" {
// Map the upper half of the file
{
- const file = try fs.cwd().openFile(test_out_file, .{});
+ const file = try tmp.dir.openFile(test_out_file, .{});
defer file.close();
const data = try os.mmap(
@@ -439,7 +466,7 @@ test "mmap" {
}
}
- try fs.cwd().deleteFile(test_out_file);
+ try tmp.dir.deleteFile(test_out_file);
}
test "getenv" {
@@ -451,15 +478,18 @@ test "getenv" {
}
test "fcntl" {
- if (builtin.os.tag == .windows)
+ if (builtin.os.tag == .windows or builtin.os.tag == .wasi)
return error.SkipZigTest;
+ var tmp = tmpDir(.{});
+ defer tmp.cleanup();
+
const test_out_file = "os_tmp_test";
- const file = try fs.cwd().createFile(test_out_file, .{});
+ const file = try tmp.dir.createFile(test_out_file, .{});
defer {
file.close();
- fs.cwd().deleteFile(test_out_file) catch {};
+ tmp.dir.deleteFile(test_out_file) catch {};
}
// Note: The test assumes createFile opens the file with O_CLOEXEC
diff --git a/lib/std/packed_int_array.zig b/lib/std/packed_int_array.zig
index 8a0dbfbf8d..d4bb1276d8 100644
--- a/lib/std/packed_int_array.zig
+++ b/lib/std/packed_int_array.zig
@@ -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;
diff --git a/lib/std/process.zig b/lib/std/process.zig
index b4baf49cf2..8c1feeffb2 100644
--- a/lib/std/process.zig
+++ b/lib/std/process.zig
@@ -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,13 +82,11 @@ pub fn getEnvMap(allocator: *Allocator) !BufMap {
}
for (environ) |env| {
- if (env) |ptr| {
- const pair = mem.spanZ(ptr);
- var parts = mem.split(pair, "=");
- const key = parts.next().?;
- const value = parts.next().?;
- try result.set(key, value);
- }
+ 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) {
diff --git a/lib/std/testing.zig b/lib/std/testing.zig
index 0f6cefb787..34bebad043 100644
--- a/lib/std/testing.zig
+++ b/lib/std/testing.zig
@@ -209,6 +209,21 @@ pub const TmpDir = struct {
}
};
+fn getCwdOrWasiPreopen() std.fs.Dir {
+ if (@import("builtin").os.tag == .wasi) {
+ var preopens = std.fs.wasi.PreopenList.init(allocator);
+ defer preopens.deinit();
+ preopens.populate() catch
+ @panic("unable to make tmp dir for testing: unable to populate preopens");
+ const preopen = preopens.find(".") orelse
+ @panic("unable to make tmp dir for testing: didn't find '.' in the preopens");
+
+ return std.fs.Dir{ .fd = preopen.fd };
+ } else {
+ return std.fs.cwd();
+ }
+}
+
pub fn tmpDir(opts: std.fs.Dir.OpenDirOptions) TmpDir {
var random_bytes: [TmpDir.random_bytes_count]u8 = undefined;
std.crypto.randomBytes(&random_bytes) catch
@@ -216,7 +231,8 @@ pub fn tmpDir(opts: std.fs.Dir.OpenDirOptions) TmpDir {
var sub_path: [TmpDir.sub_path_len]u8 = undefined;
std.fs.base64_encoder.encode(&sub_path, &random_bytes);
- var cache_dir = std.fs.cwd().makeOpenPath("zig-cache", .{}) catch
+ var cwd = getCwdOrWasiPreopen();
+ var cache_dir = cwd.makeOpenPath("zig-cache", .{}) catch
@panic("unable to make tmp dir for testing: unable to make and open zig-cache dir");
defer cache_dir.close();
var parent_dir = cache_dir.makeOpenPath("tmp", .{}) catch
diff --git a/lib/std/time.zig b/lib/std/time.zig
index 4112fb7bda..dd9a521543 100644
--- a/lib/std/time.zig
+++ b/lib/std/time.zig
@@ -19,6 +19,31 @@ 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;
+ _ = w.poll_oneoff(&in, &event, 1, &nevents);
+ return;
+ }
+
const s = nanoseconds / ns_per_s;
const ns = nanoseconds % ns_per_s;
std.os.nanosleep(s, ns);
@@ -202,7 +227,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);
diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig
index 186a59e74d..0a3a47cf94 100644
--- a/lib/std/zig/parser_test.zig
+++ b/lib/std/zig/parser_test.zig
@@ -1,3 +1,5 @@
+const builtin = @import("builtin");
+
test "recovery: top level" {
try testError(
\\test "" {inline}
@@ -941,6 +943,9 @@ test "zig fmt: same-line doc comment on variable declaration" {
}
test "zig fmt: if-else with comment before else" {
+ // TODO investigate why this fails in wasm.
+ if (builtin.cpu.arch == .wasm32) return error.SkipZigTest;
+
try testCanonical(
\\comptime {
\\ // cexp(finite|nan +- i inf|nan) = nan + i nan
@@ -1555,6 +1560,8 @@ test "zig fmt: comment after if before another if" {
}
test "zig fmt: line comment between if block and else keyword" {
+ // TODO investigate why this fails in wasm.
+ if (builtin.cpu.arch == .wasm32) return error.SkipZigTest;
try testCanonical(
\\test "aoeu" {
\\ // cexp(finite|nan +- i inf|nan) = nan + i nan
diff --git a/test/stage1/behavior.zig b/test/stage1/behavior.zig
index 61b0c1aa56..cc498df959 100644
--- a/test/stage1/behavior.zig
+++ b/test/stage1/behavior.zig
@@ -1,9 +1,13 @@
+const builtin = @import("builtin");
+
comptime {
_ = @import("behavior/align.zig");
_ = @import("behavior/alignof.zig");
_ = @import("behavior/array.zig");
- _ = @import("behavior/asm.zig");
- _ = @import("behavior/async_fn.zig");
+ if (builtin.os.tag != .wasi) {
+ _ = @import("behavior/asm.zig");
+ _ = @import("behavior/async_fn.zig");
+ }
_ = @import("behavior/atomics.zig");
_ = @import("behavior/await_struct.zig");
_ = @import("behavior/bit_shifting.zig");
diff --git a/test/stage1/behavior/align.zig b/test/stage1/behavior/align.zig
index e9b7e0f1d6..2d0e8f0982 100644
--- a/test/stage1/behavior/align.zig
+++ b/test/stage1/behavior/align.zig
@@ -133,6 +133,9 @@ fn alignedBig() align(16) i32 {
}
test "@alignCast functions" {
+ // TODO investigate why this fails when cross-compiled to wasm.
+ if (builtin.os.tag == .wasi) return error.SkipZigTest;
+
expect(fnExpectsOnly1(simple4) == 0x19);
}
fn fnExpectsOnly1(ptr: fn () align(1) i32) i32 {
@@ -324,6 +327,9 @@ test "align(@alignOf(T)) T does not force resolution of T" {
}
test "align(N) on functions" {
+ // TODO investigate why this fails when cross-compiled to wasm.
+ if (builtin.os.tag == .wasi) return error.SkipZigTest;
+
expect((@ptrToInt(overaligned_fn) & (0x1000 - 1)) == 0);
}
fn overaligned_fn() align(0x1000) i32 {
diff --git a/test/stage1/behavior/shuffle.zig b/test/stage1/behavior/shuffle.zig
index 4281d645e5..3b6412b386 100644
--- a/test/stage1/behavior/shuffle.zig
+++ b/test/stage1/behavior/shuffle.zig
@@ -1,9 +1,13 @@
const std = @import("std");
+const builtin = @import("builtin");
const mem = std.mem;
const expect = std.testing.expect;
const Vector = std.meta.Vector;
test "@shuffle" {
+ // TODO investigate why this fails when cross-compiling to wasm.
+ if (builtin.os.tag == .wasi) return error.SkipZigTest;
+
const S = struct {
fn doTheTest() void {
var v: Vector(4, i32) = [4]i32{ 2147483647, -2, 30, 40 };
diff --git a/test/stage1/behavior/vector.zig b/test/stage1/behavior/vector.zig
index 2638bb42ff..851074e0d1 100644
--- a/test/stage1/behavior/vector.zig
+++ b/test/stage1/behavior/vector.zig
@@ -1,4 +1,5 @@
const std = @import("std");
+const builtin = @import("builtin");
const mem = std.mem;
const math = std.math;
const expect = std.testing.expect;
@@ -387,6 +388,9 @@ test "vector bitwise not operator" {
}
test "vector shift operators" {
+ // TODO investigate why this fails when cross-compiled to wasm.
+ if (builtin.os.tag == .wasi) return error.SkipZigTest;
+
const S = struct {
fn doTheTestShift(x: var, y: var) void {
const N = @typeInfo(@TypeOf(x)).Array.len;
diff --git a/test/tests.zig b/test/tests.zig
index d5fdbd74cf..74aa9b105a 100644
--- a/test/tests.zig
+++ b/test/tests.zig
@@ -52,6 +52,15 @@ const test_targets = blk: {
TestTarget{
.target = .{
+ .cpu_arch = .wasm32,
+ .os_tag = .wasi,
+ },
+ .link_libc = false,
+ .single_threaded = true,
+ },
+
+ TestTarget{
+ .target = .{
.cpu_arch = .x86_64,
.os_tag = .linux,
.abi = .none,
@@ -465,6 +474,7 @@ pub fn addPkgTests(
skip_libc: bool,
is_wine_enabled: bool,
is_qemu_enabled: bool,
+ is_wasmtime_enabled: bool,
glibc_dir: ?[]const u8,
) *build.Step {
const step = b.step(b.fmt("test-{}", .{name}), desc);
@@ -525,6 +535,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 = is_wasmtime_enabled;
these_tests.glibc_multi_install_dir = glibc_dir;
step.dependOn(&these_tests.step);