authorgravatar for topolarity@tapscott.meCody Tapscott <topolarity@tapscott.me> 2022-03-01 10:17:05-07:00
committergravatar for topolarity@tapscott.meCody Tapscott <topolarity@tapscott.me> 2022-03-03 14:31:49-07:00
log58f961f4cb9875bbce3070969438ecf08f392c9f
tree0e4c4f98c023a9d3dd093dc63e519e26d9154930
parent3605dd307ffff74726cc0ce5099599f1a6f9ddb2

stdlib: Add emulated CWD to std.os for WASI targets

This adds a special CWD file descriptor, AT.FDCWD (-2), to refer to the current working directory. The `*at(...)` functions look for this and resolve relative paths against the stored CWD. Absolute paths are dynamically matched against the stored Preopens. "os.initPreopensWasi()" must be called before std.os functions will resolve relative or absolute paths correctly. This is asserted at runtime. Support has been added for: `open`, `rename`, `mkdir`, `rmdir`, `chdir`, `fchdir`, `link`, `symlink`, `unlink`, `readlink`, `fstatat`, `access`, and `faccessat`. This also includes limited support for `getcwd()` and `realpath()`. These return an error if the CWD does not correspond to a Preopen with an absolute path. They also do not currently expand symlinks.

13 files changed, 757 insertions(+), 100 deletions(-)

lib/std/build.zig+2
......@@ -2707,6 +2707,8 @@ pub const LibExeObjStep = struct {
27072707 try zig_args.append("--test-cmd");
27082708 try zig_args.append("--dir=.");
27092709 try zig_args.append("--test-cmd");
2710 try zig_args.append("--mapdir=/cwd::.");
2711 try zig_args.append("--test-cmd");
27102712 try zig_args.append("--allow-unknown-exports"); // TODO: Remove when stage2 is default compiler
27112713 try zig_args.append("--test-cmd-bin");
27122714 } else {
lib/std/c/wasi.zig+5-5
......@@ -62,21 +62,21 @@ pub const Stat = extern struct {
6262/// https://github.com/WebAssembly/wasi-libc/blob/main/expected/wasm32-wasi/predefined-macros.txt
6363pub const O = struct {
6464 pub const ACCMODE = (EXEC | RDWR | SEARCH);
65 pub const APPEND = FDFLAG.APPEND;
65 pub const APPEND = @as(u32, FDFLAG.APPEND);
6666 pub const CLOEXEC = (0);
6767 pub const CREAT = ((1 << 0) << 12); // = __WASI_OFLAGS_CREAT << 12
6868 pub const DIRECTORY = ((1 << 1) << 12); // = __WASI_OFLAGS_DIRECTORY << 12
69 pub const DSYNC = FDFLAG.DSYNC;
69 pub const DSYNC = @as(u32, FDFLAG.DSYNC);
7070 pub const EXCL = ((1 << 2) << 12); // = __WASI_OFLAGS_EXCL << 12
7171 pub const EXEC = (0x02000000);
7272 pub const NOCTTY = (0);
7373 pub const NOFOLLOW = (0x01000000);
74 pub const NONBLOCK = (1 << FDFLAG.NONBLOCK);
74 pub const NONBLOCK = @as(u32, FDFLAG.NONBLOCK);
7575 pub const RDONLY = (0x04000000);
7676 pub const RDWR = (RDONLY | WRONLY);
77 pub const RSYNC = (1 << FDFLAG.RSYNC);
77 pub const RSYNC = @as(u32, FDFLAG.RSYNC);
7878 pub const SEARCH = (0x08000000);
79 pub const SYNC = (1 << FDFLAG.SYNC);
79 pub const SYNC = @as(u32, FDFLAG.SYNC);
8080 pub const TRUNC = ((1 << 3) << 12); // = __WASI_OFLAGS_TRUNC << 12
8181 pub const TTY_INIT = (0);
8282 pub const WRONLY = (0x10000000);
lib/std/child_process.zig+1
......@@ -563,6 +563,7 @@ pub const ChildProcess = struct {
563563 error.DeviceBusy => unreachable,
564564 error.FileLocksNotSupported => unreachable,
565565 error.BadPathName => unreachable, // Windows-only
566 error.InvalidHandle => unreachable, // WASI-only
566567 error.WouldBlock => unreachable,
567568 else => |e| return e,
568569 }
lib/std/fs.zig+44-28
......@@ -923,6 +923,7 @@ pub const Dir = struct {
923923 pub const OpenError = error{
924924 FileNotFound,
925925 NotDir,
926 InvalidHandle,
926927 AccessDenied,
927928 SymLinkLoop,
928929 ProcessFdQuotaExceeded,
......@@ -981,6 +982,13 @@ pub const Dir = struct {
981982 w.RIGHT.FD_FILESTAT_SET_TIMES |
982983 w.RIGHT.FD_FILESTAT_SET_SIZE;
983984 }
985 if (self.fd == os.wasi.AT.FDCWD or path.isAbsolute(sub_path)) {
986 // Resolve absolute or CWD-relative paths to a path within a Preopen
987 var resolved_path_buf: [MAX_PATH_BYTES]u8 = undefined;
988 const resolved_path = try os.resolvePathWasi(sub_path, &resolved_path_buf);
989 const fd = try os.openatWasi(resolved_path.dir_fd, resolved_path.relative_path, 0x0, 0x0, fdflags, base, 0x0);
990 return File{ .handle = fd };
991 }
984992 const fd = try os.openatWasi(self.fd, sub_path, 0x0, 0x0, fdflags, base, 0x0);
985993 return File{ .handle = fd };
986994 }
......@@ -1145,6 +1153,13 @@ pub const Dir = struct {
11451153 if (flags.exclusive) {
11461154 oflags |= w.O.EXCL;
11471155 }
1156 if (self.fd == os.wasi.AT.FDCWD or path.isAbsolute(sub_path)) {
1157 // Resolve absolute or CWD-relative paths to a path within a Preopen
1158 var resolved_path_buf: [MAX_PATH_BYTES]u8 = undefined;
1159 const resolved_path = try os.resolvePathWasi(sub_path, &resolved_path_buf);
1160 const fd = try os.openatWasi(resolved_path.dir_fd, resolved_path.relative_path, 0x0, oflags, 0x0, base, 0x0);
1161 return File{ .handle = fd };
1162 }
11481163 const fd = try os.openatWasi(self.fd, sub_path, 0x0, oflags, 0x0, base, 0x0);
11491164 return File{ .handle = fd };
11501165 }
......@@ -1330,7 +1345,19 @@ pub const Dir = struct {
13301345 /// See also `Dir.realpathZ`, `Dir.realpathW`, and `Dir.realpathAlloc`.
13311346 pub fn realpath(self: Dir, pathname: []const u8, out_buffer: []u8) ![]u8 {
13321347 if (builtin.os.tag == .wasi) {
1333 @compileError("realpath is unsupported in WASI");
1348 if (self.fd == os.wasi.AT.FDCWD or path.isAbsolute(pathname)) {
1349 var buffer: [MAX_PATH_BYTES]u8 = undefined;
1350 const out_path = try os.realpath(pathname, &buffer);
1351 if (out_path.len > out_buffer.len) {
1352 return error.NameTooLong;
1353 }
1354 mem.copy(u8, out_buffer, out_path);
1355 return out_buffer[0..out_path.len];
1356 } else {
1357 // Unfortunately, we have no ability to look up the path for an fd_t
1358 // on WASI, so we have to give up here.
1359 return error.InvalidHandle;
1360 }
13341361 }
13351362 if (builtin.os.tag == .windows) {
13361363 const pathname_w = try os.windows.sliceToPrefixedFileW(pathname);
......@@ -1507,7 +1534,16 @@ pub const Dir = struct {
15071534 // TODO do we really need all the rights here?
15081535 const inheriting: w.rights_t = w.RIGHT.ALL ^ w.RIGHT.SOCK_SHUTDOWN;
15091536
1510 const result = os.openatWasi(self.fd, sub_path, symlink_flags, w.O.DIRECTORY, 0x0, base, inheriting);
1537 const result = blk: {
1538 if (self.fd == os.wasi.AT.FDCWD or path.isAbsolute(sub_path)) {
1539 // Resolve absolute or CWD-relative paths to a path within a Preopen
1540 var resolved_path_buf: [MAX_PATH_BYTES]u8 = undefined;
1541 const resolved_path = try os.resolvePathWasi(sub_path, &resolved_path_buf);
1542 break :blk os.openatWasi(resolved_path.dir_fd, resolved_path.relative_path, symlink_flags, w.O.DIRECTORY, 0x0, base, inheriting);
1543 } else {
1544 break :blk os.openatWasi(self.fd, sub_path, symlink_flags, w.O.DIRECTORY, 0x0, base, inheriting);
1545 }
1546 };
15111547 const fd = result catch |err| switch (err) {
15121548 error.FileTooBig => unreachable, // can't happen for directories
15131549 error.IsDir => unreachable, // we're providing O.DIRECTORY
......@@ -1622,7 +1658,7 @@ pub const Dir = struct {
16221658 const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
16231659 return self.deleteFileW(sub_path_w.span());
16241660 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
1625 os.unlinkatWasi(self.fd, sub_path, 0) catch |err| switch (err) {
1661 os.unlinkat(self.fd, sub_path, 0) catch |err| switch (err) {
16261662 error.DirNotEmpty => unreachable, // not passing AT.REMOVEDIR
16271663 else => |e| return e,
16281664 };
......@@ -1761,7 +1797,7 @@ pub const Dir = struct {
17611797 sym_link_path: []const u8,
17621798 _: SymLinkFlags,
17631799 ) !void {
1764 return os.symlinkatWasi(target_path, self.fd, sym_link_path);
1800 return os.symlinkat(target_path, self.fd, sym_link_path);
17651801 }
17661802
17671803 /// Same as `symLink`, except the pathname parameters are null-terminated.
......@@ -1807,7 +1843,7 @@ pub const Dir = struct {
18071843
18081844 /// WASI-only. Same as `readLink` except targeting WASI.
18091845 pub fn readLinkWasi(self: Dir, sub_path: []const u8, buffer: []u8) ![]u8 {
1810 return os.readlinkatWasi(self.fd, sub_path, buffer);
1846 return os.readlinkat(self.fd, sub_path, buffer);
18111847 }
18121848
18131849 /// Same as `readLink`, except the `pathname` parameter is null-terminated.
......@@ -1870,6 +1906,7 @@ pub const Dir = struct {
18701906 }
18711907
18721908 pub const DeleteTreeError = error{
1909 InvalidHandle,
18731910 AccessDenied,
18741911 FileTooBig,
18751912 SymLinkLoop,
......@@ -1935,6 +1972,7 @@ pub const Dir = struct {
19351972 continue :start_over;
19361973 },
19371974
1975 error.InvalidHandle,
19381976 error.AccessDenied,
19391977 error.SymLinkLoop,
19401978 error.ProcessFdQuotaExceeded,
......@@ -2002,6 +2040,7 @@ pub const Dir = struct {
20022040 continue :scan_dir;
20032041 },
20042042
2043 error.InvalidHandle,
20052044 error.AccessDenied,
20062045 error.SymLinkLoop,
20072046 error.ProcessFdQuotaExceeded,
......@@ -2272,8 +2311,6 @@ pub const Dir = struct {
22722311pub fn cwd() Dir {
22732312 if (builtin.os.tag == .windows) {
22742313 return Dir{ .fd = os.windows.peb().ProcessParameters.CurrentDirectory.Handle };
2275 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
2276 @compileError("WASI doesn't have a concept of cwd(); use std.fs.wasi.PreopenList to get available Dir handles instead");
22772314 } else {
22782315 return Dir{ .fd = os.AT.FDCWD };
22792316 }
......@@ -2285,26 +2322,17 @@ pub fn cwd() Dir {
22852322///
22862323/// Asserts that the path parameter has no null bytes.
22872324pub fn openDirAbsolute(absolute_path: []const u8, flags: Dir.OpenDirOptions) File.OpenError!Dir {
2288 if (builtin.os.tag == .wasi) {
2289 @compileError("WASI doesn't have the concept of an absolute directory; use openDir instead for WASI.");
2290 }
22912325 assert(path.isAbsolute(absolute_path));
22922326 return cwd().openDir(absolute_path, flags);
22932327}
22942328
22952329/// Same as `openDirAbsolute` but the path parameter is null-terminated.
22962330pub fn openDirAbsoluteZ(absolute_path_c: [*:0]const u8, flags: Dir.OpenDirOptions) File.OpenError!Dir {
2297 if (builtin.os.tag == .wasi) {
2298 @compileError("WASI doesn't have the concept of an absolute directory; use openDir instead for WASI.");
2299 }
23002331 assert(path.isAbsoluteZ(absolute_path_c));
23012332 return cwd().openDirZ(absolute_path_c, flags);
23022333}
23032334/// Same as `openDirAbsolute` but the path parameter is null-terminated.
23042335pub fn openDirAbsoluteW(absolute_path_c: [*:0]const u16, flags: Dir.OpenDirOptions) File.OpenError!Dir {
2305 if (builtin.os.tag == .wasi) {
2306 @compileError("WASI doesn't have the concept of an absolute directory; use openDir instead for WASI.");
2307 }
23082336 assert(path.isAbsoluteWindowsW(absolute_path_c));
23092337 return cwd().openDirW(absolute_path_c, flags);
23102338}
......@@ -2339,25 +2367,16 @@ pub fn openFileAbsoluteW(absolute_path_w: []const u16, flags: File.OpenFlags) Fi
23392367/// open it and handle the error for file not found.
23402368/// See `accessAbsoluteZ` for a function that accepts a null-terminated path.
23412369pub fn accessAbsolute(absolute_path: []const u8, flags: File.OpenFlags) Dir.AccessError!void {
2342 if (builtin.os.tag == .wasi) {
2343 @compileError("WASI doesn't have the concept of an absolute path; use access instead for WASI.");
2344 }
23452370 assert(path.isAbsolute(absolute_path));
23462371 try cwd().access(absolute_path, flags);
23472372}
23482373/// Same as `accessAbsolute` but the path parameter is null-terminated.
23492374pub fn accessAbsoluteZ(absolute_path: [*:0]const u8, flags: File.OpenFlags) Dir.AccessError!void {
2350 if (builtin.os.tag == .wasi) {
2351 @compileError("WASI doesn't have the concept of an absolute path; use access instead for WASI.");
2352 }
23532375 assert(path.isAbsoluteZ(absolute_path));
23542376 try cwd().accessZ(absolute_path, flags);
23552377}
23562378/// Same as `accessAbsolute` but the path parameter is WTF-16 encoded.
23572379pub fn accessAbsoluteW(absolute_path: [*:0]const 16, flags: File.OpenFlags) Dir.AccessError!void {
2358 if (builtin.os.tag == .wasi) {
2359 @compileError("WASI doesn't have the concept of an absolute path; use access instead for WASI.");
2360 }
23612380 assert(path.isAbsoluteWindowsW(absolute_path));
23622381 try cwd().accessW(absolute_path, flags);
23632382}
......@@ -2458,9 +2477,6 @@ pub const SymLinkFlags = struct {
24582477/// If `sym_link_path` exists, it will not be overwritten.
24592478/// See also `symLinkAbsoluteZ` and `symLinkAbsoluteW`.
24602479pub fn symLinkAbsolute(target_path: []const u8, sym_link_path: []const u8, flags: SymLinkFlags) !void {
2461 if (builtin.os.tag == .wasi) {
2462 @compileError("symLinkAbsolute is not supported in WASI; use Dir.symLinkWasi instead");
2463 }
24642480 assert(path.isAbsolute(target_path));
24652481 assert(path.isAbsolute(sym_link_path));
24662482 if (builtin.os.tag == .windows) {
lib/std/fs/path.zig+9-4
......@@ -8,6 +8,7 @@ const fmt = std.fmt;
88const Allocator = mem.Allocator;
99const math = std.math;
1010const windows = std.os.windows;
11const os = std.os;
1112const fs = std.fs;
1213const process = std.process;
1314const native_os = builtin.target.os.tag;
......@@ -733,7 +734,8 @@ pub fn resolvePosix(allocator: Allocator, paths: []const []const u8) ![]u8 {
733734}
734735
735736test "resolve" {
736 if (native_os == .wasi) return error.SkipZigTest;
737 if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
738 if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/cwd");
737739
738740 const cwd = try process.getCwdAlloc(testing.allocator);
739741 defer testing.allocator.free(cwd);
......@@ -753,7 +755,8 @@ test "resolveWindows" {
753755 // TODO https://github.com/ziglang/zig/issues/3288
754756 return error.SkipZigTest;
755757 }
756 if (native_os == .wasi) return error.SkipZigTest;
758 if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
759 if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/cwd");
757760 if (native_os == .windows) {
758761 const cwd = try process.getCwdAlloc(testing.allocator);
759762 defer testing.allocator.free(cwd);
......@@ -798,7 +801,8 @@ test "resolveWindows" {
798801}
799802
800803test "resolvePosix" {
801 if (native_os == .wasi) return error.SkipZigTest;
804 if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
805 if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/cwd");
802806
803807 try testResolvePosix(&[_][]const u8{ "/a/b", "c" }, "/a/b/c");
804808 try testResolvePosix(&[_][]const u8{ "/a/b", "c", "//d", "e///" }, "/d/e");
......@@ -1211,7 +1215,8 @@ test "relative" {
12111215 // TODO https://github.com/ziglang/zig/issues/3288
12121216 return error.SkipZigTest;
12131217 }
1214 if (native_os == .wasi) return error.SkipZigTest;
1218 if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
1219 if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/cwd");
12151220
12161221 try testRelativeWindows("c:/blah\\blah", "d:/games", "D:\\games");
12171222 try testRelativeWindows("c:/aaaa/bbbb", "c:/aaaa", "..");
lib/std/fs/test.zig+15-7
......@@ -1,6 +1,7 @@
11const std = @import("../std.zig");
22const builtin = @import("builtin");
33const testing = std.testing;
4const os = std.os;
45const fs = std.fs;
56const mem = std.mem;
67const wasi = std.os.wasi;
......@@ -45,7 +46,8 @@ fn testReadLink(dir: Dir, target_path: []const u8, symlink_path: []const u8) !vo
4546}
4647
4748test "accessAbsolute" {
48 if (builtin.os.tag == .wasi) return error.SkipZigTest;
49 if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest;
50 if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/cwd");
4951
5052 var tmp = tmpDir(.{});
5153 defer tmp.cleanup();
......@@ -63,7 +65,8 @@ test "accessAbsolute" {
6365}
6466
6567test "openDirAbsolute" {
66 if (builtin.os.tag == .wasi) return error.SkipZigTest;
68 if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest;
69 if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/cwd");
6770
6871 var tmp = tmpDir(.{});
6972 defer tmp.cleanup();
......@@ -99,7 +102,8 @@ test "openDir cwd parent .." {
99102}
100103
101104test "readLinkAbsolute" {
102 if (builtin.os.tag == .wasi) return error.SkipZigTest;
105 if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest;
106 if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/cwd");
103107
104108 var tmp = tmpDir(.{});
105109 defer tmp.cleanup();
......@@ -507,7 +511,8 @@ test "rename" {
507511}
508512
509513test "renameAbsolute" {
510 if (builtin.os.tag == .wasi) return error.SkipZigTest;
514 if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest;
515 if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/cwd");
511516
512517 var tmp_dir = tmpDir(.{});
513518 defer tmp_dir.cleanup();
......@@ -941,7 +946,8 @@ test "open file with exclusive nonblocking lock twice (absolute paths)" {
941946}
942947
943948test "walker" {
944 if (builtin.os.tag == .wasi) return error.SkipZigTest;
949 if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest;
950 if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/cwd");
945951
946952 var tmp = tmpDir(.{ .iterate = true });
947953 defer tmp.cleanup();
......@@ -991,7 +997,8 @@ test "walker" {
991997}
992998
993999test ". and .. in fs.Dir functions" {
994 if (builtin.os.tag == .wasi) return error.SkipZigTest;
1000 if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest;
1001 if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/cwd");
9951002
9961003 var tmp = tmpDir(.{});
9971004 defer tmp.cleanup();
......@@ -1019,7 +1026,8 @@ test ". and .. in fs.Dir functions" {
10191026}
10201027
10211028test ". and .. in absolute functions" {
1022 if (builtin.os.tag == .wasi) return error.SkipZigTest;
1029 if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest;
1030 if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/cwd");
10231031
10241032 var tmp = tmpDir(.{});
10251033 defer tmp.cleanup();
lib/std/fs/wasi.zig+54-5
......@@ -25,13 +25,29 @@ pub const PreopenType = union(PreopenTypeTag) {
2525 const Self = @This();
2626
2727 pub fn eql(self: Self, other: PreopenType) bool {
28 if (!mem.eql(u8, @tagName(self), @tagName(other))) return false;
28 if (std.meta.activeTag(self) != std.meta.activeTag(other)) return false;
2929
3030 switch (self) {
3131 PreopenTypeTag.Dir => |this_path| return mem.eql(u8, this_path, other.Dir),
3232 }
3333 }
3434
35 // Checks whether `other` refers to a subdirectory of `self` and, if so,
36 // returns the relative path to `other` from `self`
37 pub fn getRelativePath(self: Self, other: PreopenType) ?[]const u8 {
38 if (std.meta.activeTag(self) != std.meta.activeTag(other)) return null;
39
40 switch (self) {
41 PreopenTypeTag.Dir => |this_path| {
42 const other_path = other.Dir;
43 if (mem.indexOfDiff(u8, this_path, other_path)) |index| {
44 if (index < this_path.len) return null;
45 }
46 return other_path[this_path.len..];
47 },
48 }
49 }
50
3551 pub fn format(self: Self, comptime fmt: []const u8, options: std.fmt.FormatOptions, out_stream: anytype) !void {
3652 _ = fmt;
3753 _ = options;
......@@ -62,6 +78,15 @@ pub const Preopen = struct {
6278 }
6379};
6480
81/// WASI resource identifier struct. This is effectively a path within
82/// a WASI Preopen.
83pub const PreopenUri = struct {
84 /// WASI Preopen containing the resource.
85 base: Preopen,
86 /// Path to resource within `base`.
87 relative_path: []const u8,
88};
89
6590/// Dynamically-sized array list of WASI preopens. This struct is a
6691/// convenience wrapper for issuing `std.os.wasi.fd_prestat_get` and
6792/// `std.os.wasi.fd_prestat_dir_name` syscalls to the WASI runtime, and
......@@ -137,12 +162,38 @@ pub const PreopenList = struct {
137162 .SUCCESS => {},
138163 else => |err| return os.unexpectedErrno(err),
139164 }
165
140166 const preopen = Preopen.new(fd, PreopenType{ .Dir = path_buf });
141167 try self.buffer.append(preopen);
142168 fd = try math.add(fd_t, fd, 1);
143169 }
144170 }
145171
172 /// Find a preopen which includes access to `preopen_type`.
173 ///
174 /// If the preopen exists, `relative_path` is updated to point to the relative
175 /// portion of `preopen_type` and the matching Preopen is returned. If multiple
176 /// preopens match the provided resource, the most recent one is used.
177 pub fn findContaining(self: Self, preopen_type: PreopenType) ?PreopenUri {
178 // Search in reverse, so that most recently added preopens take precedence
179 var k: usize = self.buffer.items.len;
180 while (k > 0) {
181 k -= 1;
182
183 const preopen = self.buffer.items[k];
184 if (preopen.@"type".getRelativePath(preopen_type)) |rel_path_orig| {
185 var rel_path = rel_path_orig;
186 while (rel_path.len > 0 and rel_path[0] == '/') rel_path = rel_path[1..];
187
188 return PreopenUri{
189 .base = preopen,
190 .relative_path = if (rel_path.len == 0) "." else rel_path,
191 };
192 }
193 }
194 return null;
195 }
196
146197 /// Find preopen by type. If the preopen exists, return it.
147198 /// Otherwise, return `null`.
148199 pub fn find(self: Self, preopen_type: PreopenType) ?*const Preopen {
......@@ -173,8 +224,6 @@ test "extracting WASI preopens" {
173224
174225 try preopens.populate();
175226
176 try std.testing.expectEqual(@as(usize, 1), preopens.asSlice().len);
177 const preopen = preopens.find(PreopenType{ .Dir = "." }) orelse unreachable;
178 try std.testing.expect(preopen.@"type".eql(PreopenType{ .Dir = "." }));
179 try std.testing.expectEqual(@as(i32, 3), preopen.fd);
227 const preopen = preopens.find(PreopenType{ .Dir = "/cwd" }) orelse unreachable;
228 try std.testing.expect(preopen.@"type".eql(PreopenType{ .Dir = "/cwd" }));
180229}
lib/std/os.zig+494-39
......@@ -21,9 +21,13 @@ const assert = std.debug.assert;
2121const math = std.math;
2222const mem = std.mem;
2323const elf = std.elf;
24const fs = std.fs;
2425const dl = @import("dynamic_library.zig");
2526const MAX_PATH_BYTES = std.fs.MAX_PATH_BYTES;
2627const is_windows = builtin.os.tag == .windows;
28const Allocator = std.mem.Allocator;
29const Preopen = std.fs.wasi.Preopen;
30const PreopenList = std.fs.wasi.PreopenList;
2731
2832pub const darwin = std.c;
2933pub const dragonfly = std.c;
......@@ -93,7 +97,12 @@ pub const MAX_ADDR_LEN = system.MAX_ADDR_LEN;
9397pub const MMAP2_UNIT = system.MMAP2_UNIT;
9498pub const MSG = system.MSG;
9599pub const NAME_MAX = system.NAME_MAX;
96pub const O = system.O;
100pub const O = switch (builtin.os.tag) {
101 // We want to expose the POSIX-like OFLAGS, so we use std.c.wasi.O instead
102 // of std.os.wasi.O, which is for non-POSIX-like `wasi.path_open`, etc.
103 .wasi => std.c.O,
104 else => system.O,
105};
97106pub const PATH_MAX = system.PATH_MAX;
98107pub const POLL = system.POLL;
99108pub const POSIX_FADV = system.POSIX_FADV;
......@@ -210,6 +219,13 @@ pub const LOG = struct {
210219 pub const DEBUG = 7;
211220};
212221
222pub const RelativePath = struct {
223 /// Handle to directory
224 dir_fd: fd_t,
225 /// Path to resource within `dir_fd`.
226 relative_path: []const u8,
227};
228
213229pub const socket_t = if (builtin.os.tag == .windows) windows.ws2_32.SOCKET else fd_t;
214230
215231/// See also `getenv`. Populated by startup code before main().
......@@ -1239,6 +1255,9 @@ pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) PWriteError!usiz
12391255}
12401256
12411257pub const OpenError = error{
1258 /// In WASI, this error may occur when the provided file handle is invalid.
1259 InvalidHandle,
1260
12421261 /// In WASI, this error may occur when the file descriptor does
12431262 /// not hold the required rights to open a new resource relative to it.
12441263 AccessDenied,
......@@ -1300,6 +1319,8 @@ pub fn open(file_path: []const u8, flags: u32, perm: mode_t) OpenError!fd_t {
13001319 if (builtin.os.tag == .windows) {
13011320 const file_path_w = try windows.sliceToPrefixedFileW(file_path);
13021321 return openW(file_path_w.span(), flags, perm);
1322 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
1323 return openat(wasi.AT.FDCWD, file_path, flags, perm);
13031324 }
13041325 const file_path_c = try toPosixPath(file_path);
13051326 return openZ(&file_path_c, flags, perm);
......@@ -1311,6 +1332,8 @@ pub fn openZ(file_path: [*:0]const u8, flags: u32, perm: mode_t) OpenError!fd_t
13111332 if (builtin.os.tag == .windows) {
13121333 const file_path_w = try windows.cStrToPrefixedFileW(file_path);
13131334 return openW(file_path_w.span(), flags, perm);
1335 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
1336 return open(mem.sliceTo(file_path, 0), flags, perm);
13141337 }
13151338
13161339 const open_sym = if (builtin.os.tag == .linux and builtin.link_libc)
......@@ -1347,7 +1370,7 @@ pub fn openZ(file_path: [*:0]const u8, flags: u32, perm: mode_t) OpenError!fd_t
13471370 }
13481371}
13491372
1350fn openOptionsFromFlags(flags: u32) windows.OpenFileOptions {
1373fn openOptionsFromFlagsWindows(flags: u32) windows.OpenFileOptions {
13511374 const w = windows;
13521375
13531376 var access_mask: w.ULONG = w.READ_CONTROL | w.FILE_WRITE_ATTRIBUTES | w.SYNCHRONIZE;
......@@ -1387,7 +1410,7 @@ fn openOptionsFromFlags(flags: u32) windows.OpenFileOptions {
13871410/// or makes use of perm argument.
13881411pub fn openW(file_path_w: []const u16, flags: u32, perm: mode_t) OpenError!fd_t {
13891412 _ = perm;
1390 var options = openOptionsFromFlags(flags);
1413 var options = openOptionsFromFlagsWindows(flags);
13911414 options.dir = std.fs.cwd().fd;
13921415 return windows.OpenFile(file_path_w, options) catch |err| switch (err) {
13931416 error.WouldBlock => unreachable,
......@@ -1396,21 +1419,187 @@ pub fn openW(file_path_w: []const u16, flags: u32, perm: mode_t) OpenError!fd_t
13961419 };
13971420}
13981421
1422var wasi_cwd = if (builtin.os.tag == .wasi and !builtin.link_libc) struct {
1423 // List of available Preopens
1424 preopens: ?PreopenList = null,
1425 // Memory buffer for storing the relative portion of the CWD
1426 path_buffer: [MAX_PATH_BYTES]u8 = undefined,
1427 // Current Working Directory, stored as an fd_t and a relative path
1428 cwd: ?RelativePath = null,
1429 // Preopen associated with `cwd`, if any
1430 cwd_preopen: ?Preopen = null,
1431}{} else undefined;
1432
1433/// Initialize the available Preopen list on WASI and set the CWD to `cwd_init`.
1434///
1435/// This must be called before using any relative or absolute paths with `std.os`
1436/// functions, if you are on WASI without linking libc.
1437///
1438/// `alloc` must not be a temporary or leak-detecting allocator, since `std.os`
1439/// retains ownership of allocations internally and may never call free().
1440pub fn initPreopensWasi(alloc: Allocator, cwd_init: ?[]const u8) !void {
1441 if (builtin.os.tag == .wasi) {
1442 if (!builtin.link_libc) {
1443 if (wasi_cwd.preopens == null) {
1444 var preopen_list = PreopenList.init(alloc);
1445 try preopen_list.populate();
1446 wasi_cwd.preopens = preopen_list;
1447 }
1448 if (cwd_init) |cwd| {
1449 const preopen = wasi_cwd.preopens.?.findContaining(.{ .Dir = cwd });
1450 if (preopen) |po| {
1451 wasi_cwd.cwd_preopen = po.base;
1452 wasi_cwd.cwd = RelativePath{
1453 .dir_fd = po.base.fd,
1454 .relative_path = po.relative_path,
1455 };
1456 } else {
1457 // No matching preopen found
1458 return error.FileNotFound;
1459 }
1460 }
1461 } else {
1462 if (cwd_init) |cwd| try chdir(cwd);
1463 }
1464 }
1465}
1466
1467/// Resolve a relative or absolute path to an handle (`fd_t`) and a relative subpath.
1468///
1469/// For absolute paths, this automatically searches among available Preopens to find
1470/// a match. For relative paths, it uses the "emulated" CWD.
1471pub fn resolvePathWasi(path: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) !RelativePath {
1472 // Note: Due to WASI's "sandboxed" file handles, operations with this RelativePath
1473 // will fail if the relative path navigates outside of `dir_fd` using ".."
1474 return resolvePathAndGetWasiPreopen(path, null, out_buffer);
1475}
1476
1477fn resolvePathAndGetWasiPreopen(path: []const u8, preopen: ?*?Preopen, out_buffer: *[MAX_PATH_BYTES]u8) !RelativePath {
1478 var allocator = std.heap.FixedBufferAllocator.init(out_buffer);
1479 var alloc = allocator.allocator();
1480
1481 if (fs.path.isAbsolute(path) or wasi_cwd.cwd == null) {
1482 if (wasi_cwd.preopens == null) @panic("On WASI, `initPreopensWasi` must be called to initialize preopens " ++
1483 "before using any CWD-relative or absolute paths.\n");
1484
1485 // If the path is absolute, we need to lookup a containing Preopen
1486 const abs_path = std.fs.path.resolve(alloc, &.{ "/", path }) catch return error.NameTooLong;
1487 const preopen_uri = wasi_cwd.preopens.?.findContaining(.{ .Dir = abs_path });
1488
1489 if (preopen_uri) |po| {
1490 if (preopen) |p| p.* = po.base;
1491 return RelativePath{
1492 .dir_fd = po.base.fd,
1493 .relative_path = po.relative_path,
1494 };
1495 } else {
1496 // No matching preopen found
1497 return error.AccessDenied;
1498 }
1499 } else {
1500 const cwd = wasi_cwd.cwd.?;
1501
1502 // If the path is empty or "." or "./", return CWD
1503 if (std.mem.eql(u8, path, ".") or std.mem.eql(u8, path, "./")) {
1504 return cwd;
1505 }
1506
1507 // First resolve a combined path, where the "/" corresponds to `cwd.dir_fd`
1508 // not the true filesystem root
1509 const paths = &.{ "/", cwd.relative_path, path };
1510 const resolved_path = std.fs.path.resolve(alloc, paths) catch return error.NameTooLong;
1511
1512 // Strip off the fake root to get the relative path w.r.t. `cwd.dir_fd`
1513 const resolved_relative_path = resolved_path[1..];
1514
1515 if (preopen) |p| p.* = wasi_cwd.cwd_preopen;
1516 return RelativePath{
1517 .dir_fd = cwd.dir_fd,
1518 .relative_path = resolved_relative_path,
1519 };
1520 }
1521}
1522
13991523/// Open and possibly create a file. Keeps trying if it gets interrupted.
14001524/// `file_path` is relative to the open directory handle `dir_fd`.
14011525/// See also `openatZ`.
14021526pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: mode_t) OpenError!fd_t {
1403 if (builtin.os.tag == .wasi and !builtin.link_libc) {
1404 @compileError("use openatWasi instead");
1405 }
14061527 if (builtin.os.tag == .windows) {
14071528 const file_path_w = try windows.sliceToPrefixedFileW(file_path);
14081529 return openatW(dir_fd, file_path_w.span(), flags, mode);
1530 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
1531 // `mode` is ignored on WASI, which does not support unix-style file permissions
1532 const fd = if (dir_fd == wasi.AT.FDCWD or fs.path.isAbsolute(file_path)) blk: {
1533 // Resolve absolute or CWD-relative paths to a path within a Preopen
1534 var path_buf: [MAX_PATH_BYTES]u8 = undefined;
1535 const path = try resolvePathWasi(file_path, &path_buf);
1536
1537 const opts = try openOptionsFromFlagsWasi(path.dir_fd, flags);
1538 break :blk try openatWasi(path.dir_fd, path.relative_path, opts.lookup_flags, opts.oflags, opts.fs_flags, opts.fs_rights_base, opts.fs_rights_inheriting);
1539 } else blk: {
1540 const opts = try openOptionsFromFlagsWasi(dir_fd, flags);
1541 break :blk try openatWasi(dir_fd, file_path, opts.lookup_flags, opts.oflags, opts.fs_flags, opts.fs_rights_base, opts.fs_rights_inheriting);
1542 };
1543 errdefer close(fd);
1544
1545 const info = try fstat(fd);
1546 if (flags & O.WRONLY != 0 and info.filetype == .DIRECTORY)
1547 return error.IsDir;
1548
1549 return fd;
14091550 }
14101551 const file_path_c = try toPosixPath(file_path);
14111552 return openatZ(dir_fd, &file_path_c, flags, mode);
14121553}
14131554
1555const WasiOpenOptions = struct {
1556 oflags: wasi.oflags_t,
1557 lookup_flags: wasi.lookupflags_t,
1558 fs_rights_base: wasi.rights_t,
1559 fs_rights_inheriting: wasi.rights_t,
1560 fs_flags: wasi.fdflags_t,
1561};
1562
1563/// Compute rights + flags corresponding to the provided POSIX access mode.
1564fn openOptionsFromFlagsWasi(fd: fd_t, oflag: u32) OpenError!WasiOpenOptions {
1565 const w = std.os.wasi;
1566
1567 // First, discover the rights that we can derive from `fd`
1568 var fsb_cur: wasi.fdstat_t = undefined;
1569 _ = switch (w.fd_fdstat_get(fd, &fsb_cur)) {
1570 .SUCCESS => .{},
1571 .BADF => return error.InvalidHandle,
1572 else => |err| return unexpectedErrno(err),
1573 };
1574
1575 // Next, calculate the read/write rights to request, depending on the
1576 // provided POSIX access mode
1577 var rights: w.rights_t = 0;
1578 if (oflag & O.RDONLY != 0) {
1579 rights |= w.RIGHT.FD_READ | w.RIGHT.FD_READDIR;
1580 }
1581 if (oflag & O.WRONLY != 0) {
1582 rights |= w.RIGHT.FD_DATASYNC | w.RIGHT.FD_WRITE |
1583 w.RIGHT.FD_ALLOCATE | w.RIGHT.FD_FILESTAT_SET_SIZE;
1584 }
1585
1586 // Request all other rights unconditionally
1587 rights |= ~(w.RIGHT.FD_DATASYNC | w.RIGHT.FD_READ |
1588 w.RIGHT.FD_WRITE | w.RIGHT.FD_ALLOCATE |
1589 w.RIGHT.FD_READDIR | w.RIGHT.FD_FILESTAT_SET_SIZE);
1590
1591 // But only take rights that we can actually inherit
1592 rights &= fsb_cur.fs_rights_inheriting;
1593
1594 return WasiOpenOptions{
1595 .oflags = @truncate(w.oflags_t, (oflag >> 12)) & 0xfff,
1596 .lookup_flags = if (oflag & O.NOFOLLOW == 0) w.LOOKUP_SYMLINK_FOLLOW else 0,
1597 .fs_rights_base = rights,
1598 .fs_rights_inheriting = fsb_cur.fs_rights_inheriting,
1599 .fs_flags = @truncate(w.fdflags_t, oflag & 0xfff),
1600 };
1601}
1602
14141603/// Open and possibly create a file in WASI.
14151604pub fn openatWasi(dir_fd: fd_t, file_path: []const u8, lookup_flags: lookupflags_t, oflags: oflags_t, fdflags: fdflags_t, base: rights_t, inheriting: rights_t) OpenError!fd_t {
14161605 while (true) {
......@@ -1450,6 +1639,8 @@ pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: mode_t)
14501639 if (builtin.os.tag == .windows) {
14511640 const file_path_w = try windows.cStrToPrefixedFileW(file_path);
14521641 return openatW(dir_fd, file_path_w.span(), flags, mode);
1642 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
1643 return openat(dir_fd, mem.sliceTo(file_path, 0), flags, mode);
14531644 }
14541645
14551646 const openat_sym = if (builtin.os.tag == .linux and builtin.link_libc)
......@@ -1496,7 +1687,7 @@ pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: mode_t)
14961687/// or makes use of perm argument.
14971688pub fn openatW(dir_fd: fd_t, file_path_w: []const u16, flags: u32, mode: mode_t) OpenError!fd_t {
14981689 _ = mode;
1499 var options = openOptionsFromFlags(flags);
1690 var options = openOptionsFromFlagsWindows(flags);
15001691 options.dir = dir_fd;
15011692 return windows.OpenFile(file_path_w, options) catch |err| switch (err) {
15021693 error.WouldBlock => unreachable,
......@@ -1764,9 +1955,29 @@ pub const GetCwdError = error{
17641955pub fn getcwd(out_buffer: []u8) GetCwdError![]u8 {
17651956 if (builtin.os.tag == .windows) {
17661957 return windows.GetCurrentDirectory(out_buffer);
1767 }
1768 if (builtin.os.tag == .wasi and !builtin.link_libc) {
1769 @compileError("WASI doesn't have a concept of cwd(); use std.fs.wasi.PreopenList to get available Dir handles instead");
1958 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
1959 var allocator = std.heap.FixedBufferAllocator.init(out_buffer);
1960 var alloc = allocator.allocator();
1961 if (wasi_cwd.cwd) |cwd| {
1962 if (wasi_cwd.cwd_preopen) |po| {
1963 var base_cwd_dir = switch (po.@"type") {
1964 .Dir => |dir| dir,
1965 };
1966 if (!fs.path.isAbsolute(base_cwd_dir)) {
1967 // This preopen is not based on an absolute path, so we have
1968 // no way to know the absolute path of the CWD
1969 return error.CurrentWorkingDirectoryUnlinked;
1970 }
1971 const paths = &.{ base_cwd_dir, cwd.relative_path };
1972 return std.fs.path.resolve(alloc, paths) catch return error.NameTooLong;
1973 } else {
1974 // The CWD is not rooted to an existing Preopen,
1975 // so we have no way to know its absolute path
1976 return error.CurrentWorkingDirectoryUnlinked;
1977 }
1978 } else {
1979 return alloc.dupe(u8, "/") catch return error.NameTooLong;
1980 }
17701981 }
17711982
17721983 const err = if (builtin.link_libc) blk: {
......@@ -1809,11 +2020,10 @@ pub const SymLinkError = error{
18092020/// If `sym_link_path` exists, it will not be overwritten.
18102021/// See also `symlinkZ.
18112022pub fn symlink(target_path: []const u8, sym_link_path: []const u8) SymLinkError!void {
1812 if (builtin.os.tag == .wasi and !builtin.link_libc) {
1813 @compileError("symlink is not supported in WASI; use symlinkat instead");
1814 }
18152023 if (builtin.os.tag == .windows) {
18162024 @compileError("symlink is not supported on Windows; use std.os.windows.CreateSymbolicLink instead");
2025 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
2026 return symlinkat(target_path, wasi.AT.FDCWD, sym_link_path);
18172027 }
18182028 const target_path_c = try toPosixPath(target_path);
18192029 const sym_link_path_c = try toPosixPath(sym_link_path);
......@@ -1825,6 +2035,8 @@ pub fn symlink(target_path: []const u8, sym_link_path: []const u8) SymLinkError!
18252035pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLinkError!void {
18262036 if (builtin.os.tag == .windows) {
18272037 @compileError("symlink is not supported on Windows; use std.os.windows.CreateSymbolicLink instead");
2038 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
2039 return symlink(mem.sliceTo(target_path, 0), mem.sliceTo(sym_link_path, 0));
18282040 }
18292041 switch (errno(system.symlink(target_path, sym_link_path))) {
18302042 .SUCCESS => return,
......@@ -1853,11 +2065,16 @@ pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLin
18532065/// If `sym_link_path` exists, it will not be overwritten.
18542066/// See also `symlinkatWasi`, `symlinkatZ` and `symlinkatW`.
18552067pub fn symlinkat(target_path: []const u8, newdirfd: fd_t, sym_link_path: []const u8) SymLinkError!void {
1856 if (builtin.os.tag == .wasi and !builtin.link_libc) {
1857 return symlinkatWasi(target_path, newdirfd, sym_link_path);
1858 }
18592068 if (builtin.os.tag == .windows) {
18602069 @compileError("symlinkat is not supported on Windows; use std.os.windows.CreateSymbolicLink instead");
2070 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
2071 if (newdirfd == wasi.AT.FDCWD or fs.path.isAbsolute(target_path)) {
2072 // Resolve absolute or CWD-relative paths to a path within a Preopen
2073 var path_buf: [MAX_PATH_BYTES]u8 = undefined;
2074 const path = try resolvePathWasi(sym_link_path, &path_buf);
2075 return symlinkatWasi(target_path, path.dir_fd, path.relative_path);
2076 }
2077 return symlinkatWasi(target_path, newdirfd, sym_link_path);
18612078 }
18622079 const target_path_c = try toPosixPath(target_path);
18632080 const sym_link_path_c = try toPosixPath(sym_link_path);
......@@ -1893,6 +2110,8 @@ pub fn symlinkatWasi(target_path: []const u8, newdirfd: fd_t, sym_link_path: []c
18932110pub fn symlinkatZ(target_path: [*:0]const u8, newdirfd: fd_t, sym_link_path: [*:0]const u8) SymLinkError!void {
18942111 if (builtin.os.tag == .windows) {
18952112 @compileError("symlinkat is not supported on Windows; use std.os.windows.CreateSymbolicLink instead");
2113 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
2114 return symlinkat(mem.sliceTo(target_path, 0), newdirfd, mem.sliceTo(sym_link_path, 0));
18962115 }
18972116 switch (errno(system.symlinkat(target_path, newdirfd, sym_link_path))) {
18982117 .SUCCESS => return,
......@@ -1930,6 +2149,9 @@ pub const LinkError = UnexpectedError || error{
19302149};
19312150
19322151pub fn linkZ(oldpath: [*:0]const u8, newpath: [*:0]const u8, flags: i32) LinkError!void {
2152 if (builtin.os.tag == .wasi and !builtin.link_libc) {
2153 return link(mem.sliceTo(oldpath, 0), mem.sliceTo(newpath, 0), flags);
2154 }
19332155 switch (errno(system.link(oldpath, newpath, flags))) {
19342156 .SUCCESS => return,
19352157 .ACCES => return error.AccessDenied,
......@@ -1952,6 +2174,12 @@ pub fn linkZ(oldpath: [*:0]const u8, newpath: [*:0]const u8, flags: i32) LinkErr
19522174}
19532175
19542176pub fn link(oldpath: []const u8, newpath: []const u8, flags: i32) LinkError!void {
2177 if (builtin.os.tag == .wasi and !builtin.link_libc) {
2178 return linkat(wasi.AT.FDCWD, oldpath, wasi.AT.FDCWD, newpath, flags) catch |err| switch (err) {
2179 error.NotDir => unreachable, // link() does not support directories
2180 else => |e| return e,
2181 };
2182 }
19552183 const old = try toPosixPath(oldpath);
19562184 const new = try toPosixPath(newpath);
19572185 return try linkZ(&old, &new, flags);
......@@ -1966,6 +2194,9 @@ pub fn linkatZ(
19662194 newpath: [*:0]const u8,
19672195 flags: i32,
19682196) LinkatError!void {
2197 if (builtin.os.tag == .wasi and !builtin.link_libc) {
2198 return linkat(olddir, mem.sliceTo(oldpath, 0), newdir, mem.sliceTo(newpath, 0), flags);
2199 }
19692200 switch (errno(system.linkat(olddir, oldpath, newdir, newpath, flags))) {
19702201 .SUCCESS => return,
19712202 .ACCES => return error.AccessDenied,
......@@ -1995,11 +2226,62 @@ pub fn linkat(
19952226 newpath: []const u8,
19962227 flags: i32,
19972228) LinkatError!void {
2229 if (builtin.os.tag == .wasi and !builtin.link_libc) {
2230 var resolve_olddir: bool = (olddir == wasi.AT.FDCWD or fs.path.isAbsolute(oldpath));
2231 var resolve_newdir: bool = (newdir == wasi.AT.FDCWD or fs.path.isAbsolute(newpath));
2232
2233 var old: RelativePath = .{ .dir_fd = olddir, .relative_path = oldpath };
2234 var new: RelativePath = .{ .dir_fd = newdir, .relative_path = newpath };
2235
2236 // Resolve absolute or CWD-relative paths to a path within a Preopen
2237 if (resolve_olddir or resolve_newdir) {
2238 var buf_old: [MAX_PATH_BYTES]u8 = undefined;
2239 var buf_new: [MAX_PATH_BYTES]u8 = undefined;
2240
2241 if (resolve_olddir)
2242 old = try resolvePathWasi(oldpath, &buf_old);
2243
2244 if (resolve_newdir)
2245 new = try resolvePathWasi(newpath, &buf_new);
2246
2247 return linkatWasi(old, new, flags);
2248 }
2249 return linkatWasi(old, new, flags);
2250 }
19982251 const old = try toPosixPath(oldpath);
19992252 const new = try toPosixPath(newpath);
20002253 return try linkatZ(olddir, &old, newdir, &new, flags);
20012254}
20022255
2256/// WASI-only. The same as `linkat` but targeting WASI.
2257/// See also `linkat`.
2258pub fn linkatWasi(old: RelativePath, new: RelativePath, flags: i32) LinkatError!void {
2259 var old_flags: wasi.lookupflags_t = 0;
2260 // TODO: Why is this not defined in wasi-libc?
2261 if (flags & linux.AT.SYMLINK_FOLLOW != 0) old_flags |= wasi.LOOKUP_SYMLINK_FOLLOW;
2262
2263 switch (wasi.path_link(old.dir_fd, old_flags, old.relative_path.ptr, old.relative_path.len, new.dir_fd, new.relative_path.ptr, new.relative_path.len)) {
2264 .SUCCESS => return,
2265 .ACCES => return error.AccessDenied,
2266 .DQUOT => return error.DiskQuota,
2267 .EXIST => return error.PathAlreadyExists,
2268 .FAULT => unreachable,
2269 .IO => return error.FileSystem,
2270 .LOOP => return error.SymLinkLoop,
2271 .MLINK => return error.LinkQuotaExceeded,
2272 .NAMETOOLONG => return error.NameTooLong,
2273 .NOENT => return error.FileNotFound,
2274 .NOMEM => return error.SystemResources,
2275 .NOSPC => return error.NoSpaceLeft,
2276 .NOTDIR => return error.NotDir,
2277 .PERM => return error.AccessDenied,
2278 .ROFS => return error.ReadOnlyFileSystem,
2279 .XDEV => return error.NotSameFileSystem,
2280 .INVAL => unreachable,
2281 else => |err| return unexpectedErrno(err),
2282 }
2283}
2284
20032285pub const UnlinkError = error{
20042286 FileNotFound,
20052287
......@@ -2027,7 +2309,10 @@ pub const UnlinkError = error{
20272309/// See also `unlinkZ`.
20282310pub fn unlink(file_path: []const u8) UnlinkError!void {
20292311 if (builtin.os.tag == .wasi and !builtin.link_libc) {
2030 @compileError("unlink is not supported in WASI; use unlinkat instead");
2312 return unlinkat(wasi.AT.FDCWD, file_path, 0) catch |err| switch (err) {
2313 error.DirNotEmpty => unreachable, // only occurs when targeting directories
2314 else => |e| return e,
2315 };
20312316 } else if (builtin.os.tag == .windows) {
20322317 const file_path_w = try windows.sliceToPrefixedFileW(file_path);
20332318 return unlinkW(file_path_w.span());
......@@ -2042,6 +2327,8 @@ pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void {
20422327 if (builtin.os.tag == .windows) {
20432328 const file_path_w = try windows.cStrToPrefixedFileW(file_path);
20442329 return unlinkW(file_path_w.span());
2330 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
2331 return unlink(mem.sliceTo(file_path, 0));
20452332 }
20462333 switch (errno(system.unlink(file_path))) {
20472334 .SUCCESS => return,
......@@ -2079,6 +2366,12 @@ pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!vo
20792366 const file_path_w = try windows.sliceToPrefixedFileW(file_path);
20802367 return unlinkatW(dirfd, file_path_w.span(), flags);
20812368 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
2369 if (dirfd == wasi.AT.FDCWD or fs.path.isAbsolute(file_path)) {
2370 // Resolve absolute or CWD-relative paths to a path within a Preopen
2371 var path_buf: [MAX_PATH_BYTES]u8 = undefined;
2372 const path = try resolvePathWasi(file_path, &path_buf);
2373 return unlinkatWasi(path.dir_fd, path.relative_path, flags);
2374 }
20822375 return unlinkatWasi(dirfd, file_path, flags);
20832376 } else {
20842377 const file_path_c = try toPosixPath(file_path);
......@@ -2123,6 +2416,8 @@ pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatEr
21232416 if (builtin.os.tag == .windows) {
21242417 const file_path_w = try windows.cStrToPrefixedFileW(file_path_c);
21252418 return unlinkatW(dirfd, file_path_w.span(), flags);
2419 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
2420 return unlinkat(dirfd, mem.sliceTo(file_path_c, 0), flags);
21262421 }
21272422 switch (errno(system.unlinkat(dirfd, file_path_c, flags))) {
21282423 .SUCCESS => return,
......@@ -2181,7 +2476,7 @@ pub const RenameError = error{
21812476/// Change the name or location of a file.
21822477pub fn rename(old_path: []const u8, new_path: []const u8) RenameError!void {
21832478 if (builtin.os.tag == .wasi and !builtin.link_libc) {
2184 @compileError("rename is not supported in WASI; use renameat instead");
2479 return renameat(wasi.AT.FDCWD, old_path, wasi.AT.FDCWD, new_path);
21852480 } else if (builtin.os.tag == .windows) {
21862481 const old_path_w = try windows.sliceToPrefixedFileW(old_path);
21872482 const new_path_w = try windows.sliceToPrefixedFileW(new_path);
......@@ -2199,6 +2494,8 @@ pub fn renameZ(old_path: [*:0]const u8, new_path: [*:0]const u8) RenameError!voi
21992494 const old_path_w = try windows.cStrToPrefixedFileW(old_path);
22002495 const new_path_w = try windows.cStrToPrefixedFileW(new_path);
22012496 return renameW(old_path_w.span().ptr, new_path_w.span().ptr);
2497 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
2498 return rename(mem.sliceTo(old_path, 0), mem.sliceTo(new_path, 0));
22022499 }
22032500 switch (errno(system.rename(old_path, new_path))) {
22042501 .SUCCESS => return,
......@@ -2243,7 +2540,25 @@ pub fn renameat(
22432540 const new_path_w = try windows.sliceToPrefixedFileW(new_path);
22442541 return renameatW(old_dir_fd, old_path_w.span(), new_dir_fd, new_path_w.span(), windows.TRUE);
22452542 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
2246 return renameatWasi(old_dir_fd, old_path, new_dir_fd, new_path);
2543 var resolve_old: bool = (old_dir_fd == wasi.AT.FDCWD or fs.path.isAbsolute(old_path));
2544 var resolve_new: bool = (new_dir_fd == wasi.AT.FDCWD or fs.path.isAbsolute(new_path));
2545
2546 var old: RelativePath = .{ .dir_fd = old_dir_fd, .relative_path = old_path };
2547 var new: RelativePath = .{ .dir_fd = new_dir_fd, .relative_path = new_path };
2548
2549 // Resolve absolute or CWD-relative paths to a path within a Preopen
2550 if (resolve_old or resolve_new) {
2551 var buf_old: [MAX_PATH_BYTES]u8 = undefined;
2552 var buf_new: [MAX_PATH_BYTES]u8 = undefined;
2553
2554 if (resolve_old)
2555 old = try resolvePathWasi(old_path, &buf_old);
2556 if (resolve_new)
2557 new = try resolvePathWasi(new_path, &buf_new);
2558
2559 return renameatWasi(old, new);
2560 }
2561 return renameatWasi(old, new);
22472562 } else {
22482563 const old_path_c = try toPosixPath(old_path);
22492564 const new_path_c = try toPosixPath(new_path);
......@@ -2253,8 +2568,8 @@ pub fn renameat(
22532568
22542569/// WASI-only. Same as `renameat` expect targeting WASI.
22552570/// See also `renameat`.
2256pub fn renameatWasi(old_dir_fd: fd_t, old_path: []const u8, new_dir_fd: fd_t, new_path: []const u8) RenameError!void {
2257 switch (wasi.path_rename(old_dir_fd, old_path.ptr, old_path.len, new_dir_fd, new_path.ptr, new_path.len)) {
2571pub fn renameatWasi(old: RelativePath, new: RelativePath) RenameError!void {
2572 switch (wasi.path_rename(old.dir_fd, old.relative_path.ptr, old.relative_path.len, new.dir_fd, new.relative_path.ptr, new.relative_path.len)) {
22582573 .SUCCESS => return,
22592574 .ACCES => return error.AccessDenied,
22602575 .PERM => return error.AccessDenied,
......@@ -2290,6 +2605,8 @@ pub fn renameatZ(
22902605 const old_path_w = try windows.cStrToPrefixedFileW(old_path);
22912606 const new_path_w = try windows.cStrToPrefixedFileW(new_path);
22922607 return renameatW(old_dir_fd, old_path_w.span(), new_dir_fd, new_path_w.span(), windows.TRUE);
2608 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
2609 return renameat(old_dir_fd, mem.sliceTo(old_path, 0), new_dir_fd, mem.sliceTo(new_path, 0));
22932610 }
22942611
22952612 switch (errno(system.renameat(old_dir_fd, old_path, new_dir_fd, new_path))) {
......@@ -2380,6 +2697,12 @@ pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!v
23802697 const sub_dir_path_w = try windows.sliceToPrefixedFileW(sub_dir_path);
23812698 return mkdiratW(dir_fd, sub_dir_path_w.span(), mode);
23822699 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
2700 if (dir_fd == wasi.AT.FDCWD or fs.path.isAbsolute(sub_dir_path)) {
2701 // Resolve absolute or CWD-relative paths to a path within a Preopen
2702 var path_buf: [MAX_PATH_BYTES]u8 = undefined;
2703 const path = try resolvePathWasi(sub_dir_path, &path_buf);
2704 return mkdiratWasi(path.dir_fd, path.relative_path, mode);
2705 }
23832706 return mkdiratWasi(dir_fd, sub_dir_path, mode);
23842707 } else {
23852708 const sub_dir_path_c = try toPosixPath(sub_dir_path);
......@@ -2414,6 +2737,8 @@ pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirErr
24142737 if (builtin.os.tag == .windows) {
24152738 const sub_dir_path_w = try windows.cStrToPrefixedFileW(sub_dir_path);
24162739 return mkdiratW(dir_fd, sub_dir_path_w.span().ptr, mode);
2740 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
2741 return mkdirat(dir_fd, mem.sliceTo(sub_dir_path, 0), mode);
24172742 }
24182743 switch (errno(system.mkdirat(dir_fd, sub_dir_path, mode))) {
24192744 .SUCCESS => return,
......@@ -2472,10 +2797,10 @@ pub const MakeDirError = error{
24722797} || UnexpectedError;
24732798
24742799/// Create a directory.
2475/// `mode` is ignored on Windows.
2800/// `mode` is ignored on Windows and WASI.
24762801pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void {
24772802 if (builtin.os.tag == .wasi and !builtin.link_libc) {
2478 @compileError("mkdir is not supported in WASI; use mkdirat instead");
2803 return mkdirat(wasi.AT.FDCWD, dir_path, mode);
24792804 } else if (builtin.os.tag == .windows) {
24802805 const dir_path_w = try windows.sliceToPrefixedFileW(dir_path);
24812806 return mkdirW(dir_path_w.span(), mode);
......@@ -2490,6 +2815,8 @@ pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void {
24902815 if (builtin.os.tag == .windows) {
24912816 const dir_path_w = try windows.cStrToPrefixedFileW(dir_path);
24922817 return mkdirW(dir_path_w.span(), mode);
2818 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
2819 return mkdir(mem.sliceTo(dir_path, 0), mode);
24932820 }
24942821 switch (errno(system.mkdir(dir_path, mode))) {
24952822 .SUCCESS => return,
......@@ -2545,7 +2872,11 @@ pub const DeleteDirError = error{
25452872/// Deletes an empty directory.
25462873pub fn rmdir(dir_path: []const u8) DeleteDirError!void {
25472874 if (builtin.os.tag == .wasi and !builtin.link_libc) {
2548 @compileError("rmdir is not supported in WASI; use unlinkat instead");
2875 return unlinkat(wasi.AT.FDCWD, dir_path, AT.REMOVEDIR) catch |err| switch (err) {
2876 error.FileSystem => unreachable, // only occurs when targeting files
2877 error.IsDir => unreachable, // only occurs when targeting files
2878 else => |e| return e,
2879 };
25492880 } else if (builtin.os.tag == .windows) {
25502881 const dir_path_w = try windows.sliceToPrefixedFileW(dir_path);
25512882 return rmdirW(dir_path_w.span());
......@@ -2560,6 +2891,8 @@ pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void {
25602891 if (builtin.os.tag == .windows) {
25612892 const dir_path_w = try windows.cStrToPrefixedFileW(dir_path);
25622893 return rmdirW(dir_path_w.span());
2894 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
2895 return rmdir(mem.sliceTo(dir_path, 0));
25632896 }
25642897 switch (errno(system.rmdir(dir_path))) {
25652898 .SUCCESS => return,
......@@ -2606,7 +2939,17 @@ pub const ChangeCurDirError = error{
26062939/// `dir_path` is recommended to be a UTF-8 encoded string.
26072940pub fn chdir(dir_path: []const u8) ChangeCurDirError!void {
26082941 if (builtin.os.tag == .wasi and !builtin.link_libc) {
2609 @compileError("chdir is not supported in WASI");
2942 var preopen: ?Preopen = null;
2943 const path = try resolvePathAndGetWasiPreopen(dir_path, &preopen, &wasi_cwd.path_buffer);
2944
2945 const dirinfo = try fstatat(path.dir_fd, path.relative_path, 0);
2946 if (dirinfo.filetype != .DIRECTORY) {
2947 return error.NotDir;
2948 }
2949
2950 wasi_cwd.cwd_preopen = preopen;
2951 wasi_cwd.cwd = path;
2952 return;
26102953 } else if (builtin.os.tag == .windows) {
26112954 var utf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined;
26122955 const len = try std.unicode.utf8ToUtf16Le(utf16_dir_path[0..], dir_path);
......@@ -2625,6 +2968,8 @@ pub fn chdirZ(dir_path: [*:0]const u8) ChangeCurDirError!void {
26252968 const len = try std.unicode.utf8ToUtf16Le(utf16_dir_path[0..], dir_path);
26262969 if (len > utf16_dir_path.len) return error.NameTooLong;
26272970 return chdirW(utf16_dir_path[0..len]);
2971 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
2972 return chdir(mem.sliceTo(dir_path, 0));
26282973 }
26292974 switch (errno(system.chdir(dir_path))) {
26302975 .SUCCESS => return,
......@@ -2655,15 +3000,29 @@ pub const FchdirError = error{
26553000} || UnexpectedError;
26563001
26573002pub fn fchdir(dirfd: fd_t) FchdirError!void {
2658 while (true) {
2659 switch (errno(system.fchdir(dirfd))) {
2660 .SUCCESS => return,
2661 .ACCES => return error.AccessDenied,
2662 .BADF => unreachable,
2663 .NOTDIR => return error.NotDir,
2664 .INTR => continue,
2665 .IO => return error.FileSystem,
2666 else => |err| return unexpectedErrno(err),
3003 if (builtin.os.tag == .wasi) {
3004 // Check that this is a directory
3005 const dirinfo = fstatat(dirfd, ".", 0) catch unreachable;
3006 if (dirinfo.filetype != .DIRECTORY) {
3007 return error.NotDir;
3008 }
3009
3010 wasi_cwd.cwd = .{
3011 .dir_fd = dirfd,
3012 .relative_path = ".",
3013 };
3014 wasi_cwd.cwd_preopen = null;
3015 } else {
3016 while (true) {
3017 switch (errno(system.fchdir(dirfd))) {
3018 .SUCCESS => return,
3019 .ACCES => return error.AccessDenied,
3020 .BADF => unreachable,
3021 .NOTDIR => return error.NotDir,
3022 .INTR => continue,
3023 .IO => return error.FileSystem,
3024 else => |err| return unexpectedErrno(err),
3025 }
26673026 }
26683027 }
26693028}
......@@ -2690,7 +3049,7 @@ pub const ReadLinkError = error{
26903049/// The return value is a slice of `out_buffer` from index 0.
26913050pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
26923051 if (builtin.os.tag == .wasi and !builtin.link_libc) {
2693 @compileError("readlink is not supported in WASI; use readlinkat instead");
3052 return readlinkat(wasi.AT.FDCWD, file_path, out_buffer);
26943053 } else if (builtin.os.tag == .windows) {
26953054 const file_path_w = try windows.sliceToPrefixedFileW(file_path);
26963055 return readlinkW(file_path_w.span(), out_buffer);
......@@ -2711,6 +3070,8 @@ pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8
27113070 if (builtin.os.tag == .windows) {
27123071 const file_path_w = try windows.cStrToWin32PrefixedFileW(file_path);
27133072 return readlinkW(file_path_w.span(), out_buffer);
3073 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
3074 return readlink(mem.sliceTo(file_path, 0), out_buffer);
27143075 }
27153076 const rc = system.readlink(file_path, out_buffer.ptr, out_buffer.len);
27163077 switch (errno(rc)) {
......@@ -2733,6 +3094,12 @@ pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8
27333094/// See also `readlinkatWasi`, `realinkatZ` and `realinkatW`.
27343095pub fn readlinkat(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
27353096 if (builtin.os.tag == .wasi and !builtin.link_libc) {
3097 if (dirfd == wasi.AT.FDCWD or fs.path.isAbsolute(file_path)) {
3098 // Resolve absolute or CWD-relative paths to a path within a Preopen
3099 var path_buf: [MAX_PATH_BYTES]u8 = undefined;
3100 var path = try resolvePathWasi(file_path, &path_buf);
3101 return readlinkatWasi(path.dir_fd, path.relative_path, out_buffer);
3102 }
27363103 return readlinkatWasi(dirfd, file_path, out_buffer);
27373104 }
27383105 if (builtin.os.tag == .windows) {
......@@ -2775,6 +3142,8 @@ pub fn readlinkatZ(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) Read
27753142 if (builtin.os.tag == .windows) {
27763143 const file_path_w = try windows.cStrToPrefixedFileW(file_path);
27773144 return readlinkatW(dirfd, file_path_w.span(), out_buffer);
3145 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
3146 return readlinkat(dirfd, mem.sliceTo(file_path, 0), out_buffer);
27783147 }
27793148 const rc = system.readlinkat(dirfd, file_path, out_buffer.ptr, out_buffer.len);
27803149 switch (errno(rc)) {
......@@ -3727,7 +4096,14 @@ pub const FStatAtError = FStatError || error{ NameTooLong, FileNotFound, SymLink
37274096/// See also `fstatatZ` and `fstatatWasi`.
37284097pub fn fstatat(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!Stat {
37294098 if (builtin.os.tag == .wasi and !builtin.link_libc) {
3730 return fstatatWasi(dirfd, pathname, flags);
4099 const wasi_flags = if (flags & linux.AT.SYMLINK_NOFOLLOW == 0) wasi.LOOKUP_SYMLINK_FOLLOW else 0;
4100 if (dirfd == wasi.AT.FDCWD or fs.path.isAbsolute(pathname)) {
4101 // Resolve absolute or CWD-relative paths to a path within a Preopen
4102 var path_buf: [MAX_PATH_BYTES]u8 = undefined;
4103 const path = try resolvePathWasi(pathname, &path_buf);
4104 return fstatatWasi(path.dir_fd, path.relative_path, wasi_flags);
4105 }
4106 return fstatatWasi(dirfd, pathname, wasi_flags);
37314107 } else if (builtin.os.tag == .windows) {
37324108 @compileError("fstatat is not yet implemented on Windows");
37334109 } else {
......@@ -3758,6 +4134,10 @@ pub fn fstatatWasi(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!S
37584134/// Same as `fstatat` but `pathname` is null-terminated.
37594135/// See also `fstatat`.
37604136pub fn fstatatZ(dirfd: fd_t, pathname: [*:0]const u8, flags: u32) FStatAtError!Stat {
4137 if (builtin.os.tag == .wasi and !builtin.link_libc) {
4138 return fstatatWasi(dirfd, mem.sliceTo(pathname), flags);
4139 }
4140
37614141 const fstatat_sym = if (builtin.os.tag == .linux and builtin.link_libc)
37624142 system.fstatat64
37634143 else
......@@ -4056,6 +4436,8 @@ pub fn access(path: []const u8, mode: u32) AccessError!void {
40564436 const path_w = try windows.sliceToPrefixedFileW(path);
40574437 _ = try windows.GetFileAttributesW(path_w.span().ptr);
40584438 return;
4439 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
4440 return faccessat(wasi.AT.FDCWD, path, mode, 0);
40594441 }
40604442 const path_c = try toPosixPath(path);
40614443 return accessZ(&path_c, mode);
......@@ -4067,6 +4449,8 @@ pub fn accessZ(path: [*:0]const u8, mode: u32) AccessError!void {
40674449 const path_w = try windows.cStrToPrefixedFileW(path);
40684450 _ = try windows.GetFileAttributesW(path_w.span().ptr);
40694451 return;
4452 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
4453 return access(mem.sliceTo(path, 0), mode);
40704454 }
40714455 switch (errno(system.access(path, mode))) {
40724456 .SUCCESS => return,
......@@ -4108,6 +4492,45 @@ pub fn faccessat(dirfd: fd_t, path: []const u8, mode: u32, flags: u32) AccessErr
41084492 if (builtin.os.tag == .windows) {
41094493 const path_w = try windows.sliceToPrefixedFileW(path);
41104494 return faccessatW(dirfd, path_w.span().ptr, mode, flags);
4495 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
4496 var resolved = RelativePath{ .dir_fd = dirfd, .relative_path = path };
4497
4498 const file = blk: {
4499 if (dirfd == wasi.AT.FDCWD or fs.path.isAbsolute(path)) {
4500 // Resolve absolute or CWD-relative paths to a path within a Preopen
4501 var path_buf: [MAX_PATH_BYTES]u8 = undefined;
4502 resolved = resolvePathWasi(path, &path_buf) catch |err| break :blk @as(FStatAtError!Stat, err);
4503 break :blk fstatat(resolved.dir_fd, resolved.relative_path, flags);
4504 }
4505 break :blk fstatat(dirfd, path, flags);
4506 } catch |err| switch (err) {
4507 error.AccessDenied => return error.PermissionDenied,
4508 else => |e| return e,
4509 };
4510
4511 if (mode != F_OK) {
4512 var directory: wasi.fdstat_t = undefined;
4513 if (wasi.fd_fdstat_get(resolved.dir_fd, &directory) != .SUCCESS) {
4514 return error.PermissionDenied;
4515 }
4516
4517 var rights: wasi.rights_t = 0;
4518 if (mode & R_OK != 0) {
4519 rights |= if (file.filetype == .DIRECTORY)
4520 wasi.RIGHT.FD_READDIR
4521 else
4522 wasi.RIGHT.FD_READ;
4523 }
4524 if (mode & W_OK != 0) {
4525 rights |= wasi.RIGHT.FD_WRITE;
4526 }
4527 // No validation for X_OK
4528
4529 if ((rights & directory.fs_rights_inheriting) != rights) {
4530 return error.PermissionDenied;
4531 }
4532 }
4533 return;
41114534 }
41124535 const path_c = try toPosixPath(path);
41134536 return faccessatZ(dirfd, &path_c, mode, flags);
......@@ -4118,6 +4541,8 @@ pub fn faccessatZ(dirfd: fd_t, path: [*:0]const u8, mode: u32, flags: u32) Acces
41184541 if (builtin.os.tag == .windows) {
41194542 const path_w = try windows.cStrToPrefixedFileW(path);
41204543 return faccessatW(dirfd, path_w.span().ptr, mode, flags);
4544 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
4545 return faccessat(dirfd, mem.sliceTo(path, 0), mode, flags);
41214546 }
41224547 switch (errno(system.faccessat(dirfd, path, mode, flags))) {
41234548 .SUCCESS => return,
......@@ -4645,6 +5070,9 @@ pub const RealPathError = error{
46455070 SharingViolation,
46465071 PipeBusy,
46475072
5073 /// On WASI, the current CWD may not be associated with an absolute path.
5074 InvalidHandle,
5075
46485076 /// On Windows, file paths must be valid Unicode.
46495077 InvalidUtf8,
46505078
......@@ -4660,9 +5088,33 @@ pub fn realpath(pathname: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathE
46605088 if (builtin.os.tag == .windows) {
46615089 const pathname_w = try windows.sliceToPrefixedFileW(pathname);
46625090 return realpathW(pathname_w.span(), out_buffer);
4663 }
4664 if (builtin.os.tag == .wasi and !builtin.link_libc) {
4665 @compileError("Use std.fs.wasi.PreopenList to obtain valid Dir handles instead of using absolute paths");
5091 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
5092 // NOTE: This emulation is incomplete. Symbolic links are not
5093 // currently expanded during path canonicalization.
5094 var alloc = std.heap.FixedBufferAllocator.init(out_buffer);
5095 if (fs.path.isAbsolute(pathname))
5096 return try fs.path.resolve(alloc.allocator(), &.{pathname}) catch error.NameTooLong;
5097 if (wasi_cwd.cwd) |cwd| {
5098 if (wasi_cwd.cwd_preopen) |po| {
5099 var base_cwd_dir = switch (po.@"type") {
5100 .Dir => |dir| dir,
5101 };
5102 if (!fs.path.isAbsolute(base_cwd_dir)) {
5103 // This preopen is not based on an absolute path, so we have
5104 // no way to know the absolute path of the CWD
5105 return error.InvalidHandle;
5106 }
5107
5108 const paths = &.{ base_cwd_dir, cwd.relative_path, pathname };
5109 return fs.path.resolve(alloc.allocator(), paths) catch error.NameTooLong;
5110 } else {
5111 // The CWD is not rooted to an existing Preopen,
5112 // so we have no way to know its absolute path
5113 return error.InvalidHandle;
5114 }
5115 } else {
5116 return try fs.path.resolve(alloc.allocator(), &.{ "/", pathname }) catch error.NameTooLong;
5117 }
46665118 }
46675119 const pathname_c = try toPosixPath(pathname);
46685120 return realpathZ(&pathname_c, out_buffer);
......@@ -4673,6 +5125,8 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP
46735125 if (builtin.os.tag == .windows) {
46745126 const pathname_w = try windows.cStrToPrefixedFileW(pathname);
46755127 return realpathW(pathname_w.span(), out_buffer);
5128 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
5129 return realpath(mem.sliceTo(pathname, 0), out_buffer);
46765130 }
46775131 if (!builtin.link_libc) {
46785132 const flags = if (builtin.os.tag == .linux) O.PATH | O.NONBLOCK | O.CLOEXEC else O.NONBLOCK | O.CLOEXEC;
......@@ -4680,6 +5134,7 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP
46805134 error.FileLocksNotSupported => unreachable,
46815135 error.WouldBlock => unreachable,
46825136 error.FileBusy => unreachable, // not asking for write permissions
5137 error.InvalidHandle => unreachable, // WASI-only
46835138 else => |e| return e,
46845139 };
46855140 defer close(fd);
lib/std/os/test.zig+123-10
......@@ -22,7 +22,7 @@ const Dir = std.fs.Dir;
2222const ArenaAllocator = std.heap.ArenaAllocator;
2323
2424test "chdir smoke test" {
25 if (native_os == .wasi) return error.SkipZigTest;
25 if (native_os == .wasi) return error.SkipZigTest; // WASI doesn't allow navigating outside of a preopen
2626
2727 // Get current working directory path
2828 var old_cwd_buf: [fs.MAX_PATH_BYTES]u8 = undefined;
......@@ -48,7 +48,8 @@ test "chdir smoke test" {
4848}
4949
5050test "open smoke test" {
51 if (native_os == .wasi) return error.SkipZigTest;
51 if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
52 if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/cwd");
5253
5354 // TODO verify file attributes using `fstat`
5455
......@@ -102,7 +103,8 @@ test "open smoke test" {
102103}
103104
104105test "openat smoke test" {
105 if (native_os == .wasi) return error.SkipZigTest;
106 if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
107 if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/cwd");
106108
107109 // TODO verify file attributes using `fstatat`
108110
......@@ -138,7 +140,8 @@ test "openat smoke test" {
138140}
139141
140142test "symlink with relative paths" {
141 if (native_os == .wasi) return error.SkipZigTest;
143 if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
144 if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/cwd");
142145
143146 const cwd = fs.cwd();
144147 cwd.deleteFile("file.txt") catch {};
......@@ -190,6 +193,13 @@ fn testReadlink(target_path: []const u8, symlink_path: []const u8) !void {
190193
191194test "link with relative paths" {
192195 switch (native_os) {
196 .wasi => {
197 if (builtin.link_libc) {
198 return error.SkipZigTest;
199 } else {
200 try os.initPreopensWasi(std.heap.page_allocator, "/cwd");
201 }
202 },
193203 .linux, .solaris => {},
194204 else => return error.SkipZigTest,
195205 }
......@@ -212,14 +222,14 @@ test "link with relative paths" {
212222 const nstat = try os.fstat(nfd.handle);
213223
214224 try testing.expectEqual(estat.ino, nstat.ino);
215 try testing.expectEqual(@as(usize, 2), nstat.nlink);
225 try testing.expectEqual(@as(@TypeOf(nstat.nlink), 2), nstat.nlink);
216226 }
217227
218228 try os.unlink("new.txt");
219229
220230 {
221231 const estat = try os.fstat(efd.handle);
222 try testing.expectEqual(@as(usize, 1), estat.nlink);
232 try testing.expectEqual(@as(@TypeOf(estat.nlink), 1), estat.nlink);
223233 }
224234
225235 try cwd.deleteFile("example.txt");
......@@ -227,6 +237,7 @@ test "link with relative paths" {
227237
228238test "linkat with different directories" {
229239 switch (native_os) {
240 .wasi => if (!builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/cwd"),
230241 .linux, .solaris => {},
231242 else => return error.SkipZigTest,
232243 }
......@@ -250,14 +261,14 @@ test "linkat with different directories" {
250261 const nstat = try os.fstat(nfd.handle);
251262
252263 try testing.expectEqual(estat.ino, nstat.ino);
253 try testing.expectEqual(@as(usize, 2), nstat.nlink);
264 try testing.expectEqual(@as(@TypeOf(nstat.nlink), 2), nstat.nlink);
254265 }
255266
256267 try os.unlinkat(tmp.dir.fd, "new.txt", 0);
257268
258269 {
259270 const estat = try os.fstat(efd.handle);
260 try testing.expectEqual(@as(usize, 1), estat.nlink);
271 try testing.expectEqual(@as(@TypeOf(estat.nlink), 1), estat.nlink);
261272 }
262273
263274 try cwd.deleteFile("example.txt");
......@@ -388,8 +399,6 @@ test "getrandom" {
388399}
389400
390401test "getcwd" {
391 if (native_os == .wasi) return error.SkipZigTest;
392
393402 // at least call it so it gets compiled
394403 var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
395404 _ = os.getcwd(&buf) catch undefined;
......@@ -878,3 +887,107 @@ test "POSIX file locking with fcntl" {
878887 try expect(result.status == 0 * 256);
879888 }
880889}
890
891test "rename smoke test" {
892 if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
893 if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/cwd");
894
895 var tmp = tmpDir(.{});
896 defer tmp.cleanup();
897
898 // Get base abs path
899 var arena = ArenaAllocator.init(testing.allocator);
900 defer arena.deinit();
901 const allocator = arena.allocator();
902
903 const base_path = blk: {
904 const relative_path = try fs.path.join(allocator, &[_][]const u8{ "zig-cache", "tmp", tmp.sub_path[0..] });
905 break :blk try fs.realpathAlloc(allocator, relative_path);
906 };
907
908 var file_path: []u8 = undefined;
909 var fd: os.fd_t = undefined;
910 const mode: os.mode_t = if (native_os == .windows) 0 else 0o666;
911
912 // Create some file using `open`.
913 file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" });
914 fd = try os.open(file_path, os.O.RDWR | os.O.CREAT | os.O.EXCL, mode);
915 os.close(fd);
916
917 // Rename the file
918 var new_file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_other_file" });
919 try os.rename(file_path, new_file_path);
920
921 // Try opening renamed file
922 file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_other_file" });
923 fd = try os.open(file_path, os.O.RDWR, mode);
924 os.close(fd);
925
926 // Try opening original file - should fail with error.FileNotFound
927 file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" });
928 try expectError(error.FileNotFound, os.open(file_path, os.O.RDWR, mode));
929
930 // Create some directory
931 file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_dir" });
932 try os.mkdir(file_path, mode);
933
934 // Rename the directory
935 new_file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_other_dir" });
936 try os.rename(file_path, new_file_path);
937
938 // Try opening renamed directory
939 file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_other_dir" });
940 fd = try os.open(file_path, os.O.RDONLY | os.O.DIRECTORY, mode);
941 os.close(fd);
942
943 // Try opening original directory - should fail with error.FileNotFound
944 file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_dir" });
945 try expectError(error.FileNotFound, os.open(file_path, os.O.RDONLY | os.O.DIRECTORY, mode));
946}
947
948test "access smoke test" {
949 if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
950 if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/cwd");
951
952 var tmp = tmpDir(.{});
953 defer tmp.cleanup();
954
955 // Get base abs path
956 var arena = ArenaAllocator.init(testing.allocator);
957 defer arena.deinit();
958 const allocator = arena.allocator();
959
960 const base_path = blk: {
961 const relative_path = try fs.path.join(allocator, &[_][]const u8{ "zig-cache", "tmp", tmp.sub_path[0..] });
962 break :blk try fs.realpathAlloc(allocator, relative_path);
963 };
964
965 var file_path: []u8 = undefined;
966 var fd: os.fd_t = undefined;
967 const mode: os.mode_t = if (native_os == .windows) 0 else 0o666;
968
969 // Create some file using `open`.
970 file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" });
971 fd = try os.open(file_path, os.O.RDWR | os.O.CREAT | os.O.EXCL, mode);
972 os.close(fd);
973
974 // Try to access() the file
975 file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" });
976 if (builtin.os.tag == .windows) {
977 try os.access(file_path, os.F_OK);
978 } else {
979 try os.access(file_path, os.F_OK | os.W_OK | os.R_OK);
980 }
981
982 // Try to access() a non-existent file - should fail with error.FileNotFound
983 file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_other_file" });
984 try expectError(error.FileNotFound, os.access(file_path, os.F_OK));
985
986 // Create some directory
987 file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_dir" });
988 try os.mkdir(file_path, mode);
989
990 // Try to access() the directory
991 file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_dir" });
992 try os.access(file_path, os.F_OK);
993}
lib/std/os/wasi.zig+5
......@@ -15,6 +15,11 @@ comptime {
1515 // assert(@alignOf(u64) == 8);
1616}
1717
18pub const F_OK = 0;
19pub const X_OK = 1;
20pub const W_OK = 2;
21pub const R_OK = 4;
22
1823pub const iovec_t = std.os.iovec;
1924pub const ciovec_t = std.os.iovec_const;
2025
lib/std/testing.zig+2-2
......@@ -327,8 +327,8 @@ fn getCwdOrWasiPreopen() std.fs.Dir {
327327 defer preopens.deinit();
328328 preopens.populate() catch
329329 @panic("unable to make tmp dir for testing: unable to populate preopens");
330 const preopen = preopens.find(std.fs.wasi.PreopenType{ .Dir = "." }) orelse
331 @panic("unable to make tmp dir for testing: didn't find '.' in the preopens");
330 const preopen = preopens.find(std.fs.wasi.PreopenType{ .Dir = "/cwd" }) orelse
331 @panic("unable to make tmp dir for testing: didn't find '/cwd' in the preopens");
332332
333333 return std.fs.Dir{ .fd = preopen.fd };
334334 } else {
lib/std/zig/system/NativeTargetInfo.zig+2
......@@ -369,6 +369,7 @@ fn detectAbiAndDynamicLinker(
369369
370370 error.IsDir,
371371 error.NotDir,
372 error.InvalidHandle,
372373 error.AccessDenied,
373374 error.NoDevice,
374375 error.FileNotFound,
......@@ -670,6 +671,7 @@ pub fn abiAndDynamicLinkerFromFile(
670671
671672 error.FileNotFound,
672673 error.NotDir,
674 error.InvalidHandle,
673675 error.AccessDenied,
674676 error.NoDevice,
675677 => continue,
src/test.zig+1
......@@ -1188,6 +1188,7 @@ pub const TestContext = struct {
11881188 .wasmtime => |wasmtime_bin_name| if (enable_wasmtime) {
11891189 try argv.append(wasmtime_bin_name);
11901190 try argv.append("--dir=.");
1191 try argv.append("--mapdir=/cwd::.");
11911192 try argv.append(exe_path);
11921193 } else {
11931194 return; // wasmtime not available; pass test.