diff options
| author | Andrew Kelley <andrew@ziglang.org> | 2020-05-18 19:00:25 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-05-18 19:00:25 -0400 |
| commit | fe28d732710b916695c6926a06aad0cc93156516 (patch) | |
| tree | c3e674556afe22d31536748d6211639333e0b7d5 | |
| parent | a80ad0782d5e7ce065dd47f21bcb81f69c1d1670 (diff) | |
| parent | cd8daa533ad7e53a3d071dcb782bc14c6fc72711 (diff) | |
| download | zig-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.zig | 10 | ||||
| -rwxr-xr-x | ci/azure/linux_script | 7 | ||||
| -rw-r--r-- | lib/std/build.zig | 6 | ||||
| -rw-r--r-- | lib/std/fmt.zig | 7 | ||||
| -rw-r--r-- | lib/std/fs.zig | 187 | ||||
| -rw-r--r-- | lib/std/fs/file.zig | 7 | ||||
| -rw-r--r-- | lib/std/fs/get_app_data_dir.zig | 2 | ||||
| -rw-r--r-- | lib/std/fs/path.zig | 11 | ||||
| -rw-r--r-- | lib/std/fs/test.zig | 7 | ||||
| -rw-r--r-- | lib/std/fs/wasi.zig | 15 | ||||
| -rw-r--r-- | lib/std/io/test.zig | 43 | ||||
| -rw-r--r-- | lib/std/net/test.zig | 11 | ||||
| -rw-r--r-- | lib/std/os.zig | 384 | ||||
| -rw-r--r-- | lib/std/os/bits/wasi.zig | 119 | ||||
| -rw-r--r-- | lib/std/os/test.zig | 108 | ||||
| -rw-r--r-- | lib/std/packed_int_array.zig | 6 | ||||
| -rw-r--r-- | lib/std/process.zig | 18 | ||||
| -rw-r--r-- | lib/std/testing.zig | 18 | ||||
| -rw-r--r-- | lib/std/time.zig | 27 | ||||
| -rw-r--r-- | lib/std/zig/parser_test.zig | 7 | ||||
| -rw-r--r-- | test/stage1/behavior.zig | 8 | ||||
| -rw-r--r-- | test/stage1/behavior/align.zig | 6 | ||||
| -rw-r--r-- | test/stage1/behavior/shuffle.zig | 4 | ||||
| -rw-r--r-- | test/stage1/behavior/vector.zig | 4 | ||||
| -rw-r--r-- | test/tests.zig | 11 |
25 files changed, 886 insertions, 147 deletions
@@ -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); |
