authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2022-03-05 20:31:44+01:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2022-03-05 20:31:44+01:00
log908f41a67cbd377eb15d07a27778dbbca0a4309f
tree7037fdf5c2352d72115f5b42b131ebfd1387abc7
parentac936c0aba94c2cf5ff8537c1808445eacf38a15
parentaafcd8eab3c6046a915f26aed766090ee219d920
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #11021 from topolarity/wasi-cwd

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

10 files changed, 778 insertions(+), 96 deletions(-)

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, ".");
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, ".");
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, ".");
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, ".");
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, ".");
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, ".");
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, ".");
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, ".");
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, ".");
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, ".");
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, ".");
10231031
10241032 var tmp = tmpDir(.{});
10251033 defer tmp.cleanup();
lib/std/fs/wasi.zig+63-3
......@@ -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,49 @@ 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
197 /// Find preopen by fd. If the preopen exists, return it.
198 /// Otherwise, return `null`.
199 pub fn findByFd(self: Self, fd: fd_t) ?Preopen {
200 for (self.buffer.items) |preopen| {
201 if (preopen.fd == fd) {
202 return preopen;
203 }
204 }
205 return null;
206 }
207
146208 /// Find preopen by type. If the preopen exists, return it.
147209 /// Otherwise, return `null`.
148210 pub fn find(self: Self, preopen_type: PreopenType) ?*const Preopen {
......@@ -173,8 +235,6 @@ test "extracting WASI preopens" {
173235
174236 try preopens.populate();
175237
176 try std.testing.expectEqual(@as(usize, 1), preopens.asSlice().len);
177238 const preopen = preopens.find(PreopenType{ .Dir = "." }) orelse unreachable;
178239 try std.testing.expect(preopen.@"type".eql(PreopenType{ .Dir = "." }));
179 try std.testing.expectEqual(@as(i32, 3), preopen.fd);
180240}
lib/std/os.zig+511-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,17 @@ pub const LOG = struct {
210219 pub const DEBUG = 7;
211220};
212221
222/// An fd-relative file path
223///
224/// This is currently only used for WASI-specific functionality, but the concept
225/// is the same as the dirfd/pathname pairs in the `*at(...)` POSIX functions.
226pub const RelativePathWasi = struct {
227 /// Handle to directory
228 dir_fd: fd_t,
229 /// Path to resource within `dir_fd`.
230 relative_path: []const u8,
231};
232
213233pub const socket_t = if (builtin.os.tag == .windows) windows.ws2_32.SOCKET else fd_t;
214234
215235/// See also `getenv`. Populated by startup code before main().
......@@ -1239,6 +1259,9 @@ pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) PWriteError!usiz
12391259}
12401260
12411261pub const OpenError = error{
1262 /// In WASI, this error may occur when the provided file handle is invalid.
1263 InvalidHandle,
1264
12421265 /// In WASI, this error may occur when the file descriptor does
12431266 /// not hold the required rights to open a new resource relative to it.
12441267 AccessDenied,
......@@ -1300,6 +1323,8 @@ pub fn open(file_path: []const u8, flags: u32, perm: mode_t) OpenError!fd_t {
13001323 if (builtin.os.tag == .windows) {
13011324 const file_path_w = try windows.sliceToPrefixedFileW(file_path);
13021325 return openW(file_path_w.span(), flags, perm);
1326 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
1327 return openat(wasi.AT.FDCWD, file_path, flags, perm);
13031328 }
13041329 const file_path_c = try toPosixPath(file_path);
13051330 return openZ(&file_path_c, flags, perm);
......@@ -1311,6 +1336,8 @@ pub fn openZ(file_path: [*:0]const u8, flags: u32, perm: mode_t) OpenError!fd_t
13111336 if (builtin.os.tag == .windows) {
13121337 const file_path_w = try windows.cStrToPrefixedFileW(file_path);
13131338 return openW(file_path_w.span(), flags, perm);
1339 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
1340 return open(mem.sliceTo(file_path, 0), flags, perm);
13141341 }
13151342
13161343 const open_sym = if (builtin.os.tag == .linux and builtin.link_libc)
......@@ -1347,7 +1374,7 @@ pub fn openZ(file_path: [*:0]const u8, flags: u32, perm: mode_t) OpenError!fd_t
13471374 }
13481375}
13491376
1350fn openOptionsFromFlags(flags: u32) windows.OpenFileOptions {
1377fn openOptionsFromFlagsWindows(flags: u32) windows.OpenFileOptions {
13511378 const w = windows;
13521379
13531380 var access_mask: w.ULONG = w.READ_CONTROL | w.FILE_WRITE_ATTRIBUTES | w.SYNCHRONIZE;
......@@ -1387,7 +1414,7 @@ fn openOptionsFromFlags(flags: u32) windows.OpenFileOptions {
13871414/// or makes use of perm argument.
13881415pub fn openW(file_path_w: []const u16, flags: u32, perm: mode_t) OpenError!fd_t {
13891416 _ = perm;
1390 var options = openOptionsFromFlags(flags);
1417 var options = openOptionsFromFlagsWindows(flags);
13911418 options.dir = std.fs.cwd().fd;
13921419 return windows.OpenFile(file_path_w, options) catch |err| switch (err) {
13931420 error.WouldBlock => unreachable,
......@@ -1396,21 +1423,204 @@ pub fn openW(file_path_w: []const u16, flags: u32, perm: mode_t) OpenError!fd_t
13961423 };
13971424}
13981425
1426var wasi_cwd = if (builtin.os.tag == .wasi and !builtin.link_libc) struct {
1427 // List of available Preopens
1428 preopens: ?PreopenList = null,
1429 // Memory buffer for storing the relative portion of the CWD
1430 path_buffer: [MAX_PATH_BYTES]u8 = undefined,
1431 // Current Working Directory, stored as an fd_t and a relative path
1432 cwd: ?RelativePathWasi = null,
1433 // Preopen associated with `cwd`, if any
1434 cwd_preopen: ?Preopen = null,
1435}{} else undefined;
1436
1437/// Initialize the available Preopen list on WASI and set the CWD to `cwd_init`.
1438/// Note that `cwd_init` corresponds to a Preopen directory, not necessarily
1439/// a POSIX path. For example, "." matches a Preopen provided with `--dir=.`
1440///
1441/// This must be called before using any relative or absolute paths with `std.os`
1442/// functions, if you are on WASI without linking libc.
1443///
1444/// `alloc` must not be a temporary or leak-detecting allocator, since `std.os`
1445/// retains ownership of allocations internally and may never call free().
1446pub fn initPreopensWasi(alloc: Allocator, cwd_init: ?[]const u8) !void {
1447 if (builtin.os.tag == .wasi) {
1448 if (!builtin.link_libc) {
1449 if (wasi_cwd.preopens == null) {
1450 var preopen_list = PreopenList.init(alloc);
1451 try preopen_list.populate();
1452 wasi_cwd.preopens = preopen_list;
1453 }
1454 if (cwd_init) |cwd| {
1455 const preopen = wasi_cwd.preopens.?.findContaining(.{ .Dir = cwd });
1456 if (preopen) |po| {
1457 wasi_cwd.cwd_preopen = po.base;
1458 wasi_cwd.cwd = RelativePathWasi{
1459 .dir_fd = po.base.fd,
1460 .relative_path = po.relative_path,
1461 };
1462 } else {
1463 // No matching preopen found
1464 return error.FileNotFound;
1465 }
1466 }
1467 } else {
1468 if (cwd_init) |cwd| try chdir(cwd);
1469 }
1470 }
1471}
1472
1473/// Resolve a relative or absolute path to an handle (`fd_t`) and a relative subpath.
1474///
1475/// For absolute paths, this automatically searches among available Preopens to find
1476/// a match. For relative paths, it uses the "emulated" CWD.
1477pub fn resolvePathWasi(path: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) !RelativePathWasi {
1478 // Note: Due to WASI's "sandboxed" file handles, operations with this RelativePathWasi
1479 // will fail if the relative path navigates outside of `dir_fd` using ".."
1480 return resolvePathAndGetWasiPreopen(path, null, out_buffer);
1481}
1482
1483fn resolvePathAndGetWasiPreopen(path: []const u8, preopen: ?*?Preopen, out_buffer: *[MAX_PATH_BYTES]u8) !RelativePathWasi {
1484 var allocator = std.heap.FixedBufferAllocator.init(out_buffer);
1485 var alloc = allocator.allocator();
1486
1487 if (fs.path.isAbsolute(path) or wasi_cwd.cwd == null) {
1488 if (wasi_cwd.preopens == null) @panic("On WASI, `initPreopensWasi` must be called to initialize preopens " ++
1489 "before using any CWD-relative or absolute paths.\n");
1490
1491 if (mem.startsWith(u8, path, "/preopens/fd/")) {
1492 // "/preopens/fd/<N>" is a special prefix, which refers to a Preopen directly by fd
1493 const fd_start = "/preopens/fd/".len;
1494 const fd_end = mem.indexOfScalarPos(u8, path, fd_start, '/') orelse path.len;
1495 const fd = std.fmt.parseUnsigned(fd_t, path[fd_start..fd_end], 10) catch unreachable;
1496 const rel_path = if (path.len > fd_end + 1) path[fd_end + 1 ..] else ".";
1497
1498 if (preopen) |p| p.* = wasi_cwd.preopens.?.findByFd(fd);
1499 return RelativePathWasi{
1500 .dir_fd = fd,
1501 .relative_path = alloc.dupe(u8, rel_path) catch return error.NameTooLong,
1502 };
1503 }
1504
1505 // For any other absolute path, we need to lookup a containing Preopen
1506 const abs_path = fs.path.resolve(alloc, &.{ "/", path }) catch return error.NameTooLong;
1507 const preopen_uri = wasi_cwd.preopens.?.findContaining(.{ .Dir = abs_path });
1508
1509 if (preopen_uri) |po| {
1510 if (preopen) |p| p.* = po.base;
1511 return RelativePathWasi{
1512 .dir_fd = po.base.fd,
1513 .relative_path = po.relative_path,
1514 };
1515 } else {
1516 // No matching preopen found
1517 return error.AccessDenied;
1518 }
1519 } else {
1520 const cwd = wasi_cwd.cwd.?;
1521
1522 // If the path is empty or "." or "./", return CWD
1523 if (std.mem.eql(u8, path, ".") or std.mem.eql(u8, path, "./")) {
1524 return cwd;
1525 }
1526
1527 // First resolve a combined path, where the "/" corresponds to `cwd.dir_fd`
1528 // not the true filesystem root
1529 const paths = &.{ "/", cwd.relative_path, path };
1530 const resolved_path = fs.path.resolve(alloc, paths) catch return error.NameTooLong;
1531
1532 // Strip off the fake root to get the relative path w.r.t. `cwd.dir_fd`
1533 const resolved_relative_path = resolved_path[1..];
1534
1535 if (preopen) |p| p.* = wasi_cwd.cwd_preopen;
1536 return RelativePathWasi{
1537 .dir_fd = cwd.dir_fd,
1538 .relative_path = resolved_relative_path,
1539 };
1540 }
1541}
1542
13991543/// Open and possibly create a file. Keeps trying if it gets interrupted.
14001544/// `file_path` is relative to the open directory handle `dir_fd`.
14011545/// See also `openatZ`.
14021546pub 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 }
14061547 if (builtin.os.tag == .windows) {
14071548 const file_path_w = try windows.sliceToPrefixedFileW(file_path);
14081549 return openatW(dir_fd, file_path_w.span(), flags, mode);
1550 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
1551 // `mode` is ignored on WASI, which does not support unix-style file permissions
1552 const fd = if (dir_fd == wasi.AT.FDCWD or fs.path.isAbsolute(file_path)) blk: {
1553 // Resolve absolute or CWD-relative paths to a path within a Preopen
1554 var path_buf: [MAX_PATH_BYTES]u8 = undefined;
1555 const path = try resolvePathWasi(file_path, &path_buf);
1556
1557 const opts = try openOptionsFromFlagsWasi(path.dir_fd, flags);
1558 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);
1559 } else blk: {
1560 const opts = try openOptionsFromFlagsWasi(dir_fd, flags);
1561 break :blk try openatWasi(dir_fd, file_path, opts.lookup_flags, opts.oflags, opts.fs_flags, opts.fs_rights_base, opts.fs_rights_inheriting);
1562 };
1563 errdefer close(fd);
1564
1565 const info = try fstat(fd);
1566 if (flags & O.WRONLY != 0 and info.filetype == .DIRECTORY)
1567 return error.IsDir;
1568
1569 return fd;
14091570 }
14101571 const file_path_c = try toPosixPath(file_path);
14111572 return openatZ(dir_fd, &file_path_c, flags, mode);
14121573}
14131574
1575/// A struct to contain all lookup/rights flags accepted by `wasi.path_open`
1576const WasiOpenOptions = struct {
1577 oflags: wasi.oflags_t,
1578 lookup_flags: wasi.lookupflags_t,
1579 fs_rights_base: wasi.rights_t,
1580 fs_rights_inheriting: wasi.rights_t,
1581 fs_flags: wasi.fdflags_t,
1582};
1583
1584/// Compute rights + flags corresponding to the provided POSIX access mode.
1585fn openOptionsFromFlagsWasi(fd: fd_t, oflag: u32) OpenError!WasiOpenOptions {
1586 const w = std.os.wasi;
1587
1588 // First, discover the rights that we can derive from `fd`
1589 var fsb_cur: wasi.fdstat_t = undefined;
1590 _ = switch (w.fd_fdstat_get(fd, &fsb_cur)) {
1591 .SUCCESS => .{},
1592 .BADF => return error.InvalidHandle,
1593 else => |err| return unexpectedErrno(err),
1594 };
1595
1596 // Next, calculate the read/write rights to request, depending on the
1597 // provided POSIX access mode
1598 var rights: w.rights_t = 0;
1599 if (oflag & O.RDONLY != 0) {
1600 rights |= w.RIGHT.FD_READ | w.RIGHT.FD_READDIR;
1601 }
1602 if (oflag & O.WRONLY != 0) {
1603 rights |= w.RIGHT.FD_DATASYNC | w.RIGHT.FD_WRITE |
1604 w.RIGHT.FD_ALLOCATE | w.RIGHT.FD_FILESTAT_SET_SIZE;
1605 }
1606
1607 // Request all other rights unconditionally
1608 rights |= ~(w.RIGHT.FD_DATASYNC | w.RIGHT.FD_READ |
1609 w.RIGHT.FD_WRITE | w.RIGHT.FD_ALLOCATE |
1610 w.RIGHT.FD_READDIR | w.RIGHT.FD_FILESTAT_SET_SIZE);
1611
1612 // But only take rights that we can actually inherit
1613 rights &= fsb_cur.fs_rights_inheriting;
1614
1615 return WasiOpenOptions{
1616 .oflags = @truncate(w.oflags_t, (oflag >> 12)) & 0xfff,
1617 .lookup_flags = if (oflag & O.NOFOLLOW == 0) w.LOOKUP_SYMLINK_FOLLOW else 0,
1618 .fs_rights_base = rights,
1619 .fs_rights_inheriting = fsb_cur.fs_rights_inheriting,
1620 .fs_flags = @truncate(w.fdflags_t, oflag & 0xfff),
1621 };
1622}
1623
14141624/// Open and possibly create a file in WASI.
14151625pub 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 {
14161626 while (true) {
......@@ -1450,6 +1660,8 @@ pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: mode_t)
14501660 if (builtin.os.tag == .windows) {
14511661 const file_path_w = try windows.cStrToPrefixedFileW(file_path);
14521662 return openatW(dir_fd, file_path_w.span(), flags, mode);
1663 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
1664 return openat(dir_fd, mem.sliceTo(file_path, 0), flags, mode);
14531665 }
14541666
14551667 const openat_sym = if (builtin.os.tag == .linux and builtin.link_libc)
......@@ -1496,7 +1708,7 @@ pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: mode_t)
14961708/// or makes use of perm argument.
14971709pub fn openatW(dir_fd: fd_t, file_path_w: []const u16, flags: u32, mode: mode_t) OpenError!fd_t {
14981710 _ = mode;
1499 var options = openOptionsFromFlags(flags);
1711 var options = openOptionsFromFlagsWindows(flags);
15001712 options.dir = dir_fd;
15011713 return windows.OpenFile(file_path_w, options) catch |err| switch (err) {
15021714 error.WouldBlock => unreachable,
......@@ -1764,9 +1976,15 @@ pub const GetCwdError = error{
17641976pub fn getcwd(out_buffer: []u8) GetCwdError![]u8 {
17651977 if (builtin.os.tag == .windows) {
17661978 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");
1979 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
1980 var buf: [MAX_PATH_BYTES]u8 = undefined;
1981 const path = realpathWasi(".", &buf) catch |err| switch (err) {
1982 error.NameTooLong => return error.NameTooLong,
1983 error.InvalidHandle => return error.CurrentWorkingDirectoryUnlinked,
1984 };
1985 if (out_buffer.len < path.len) return error.NameTooLong;
1986 std.mem.copy(u8, out_buffer, path);
1987 return out_buffer[0..path.len];
17701988 }
17711989
17721990 const err = if (builtin.link_libc) blk: {
......@@ -1809,11 +2027,10 @@ pub const SymLinkError = error{
18092027/// If `sym_link_path` exists, it will not be overwritten.
18102028/// See also `symlinkZ.
18112029pub 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 }
18152030 if (builtin.os.tag == .windows) {
18162031 @compileError("symlink is not supported on Windows; use std.os.windows.CreateSymbolicLink instead");
2032 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
2033 return symlinkat(target_path, wasi.AT.FDCWD, sym_link_path);
18172034 }
18182035 const target_path_c = try toPosixPath(target_path);
18192036 const sym_link_path_c = try toPosixPath(sym_link_path);
......@@ -1825,6 +2042,8 @@ pub fn symlink(target_path: []const u8, sym_link_path: []const u8) SymLinkError!
18252042pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLinkError!void {
18262043 if (builtin.os.tag == .windows) {
18272044 @compileError("symlink is not supported on Windows; use std.os.windows.CreateSymbolicLink instead");
2045 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
2046 return symlink(mem.sliceTo(target_path, 0), mem.sliceTo(sym_link_path, 0));
18282047 }
18292048 switch (errno(system.symlink(target_path, sym_link_path))) {
18302049 .SUCCESS => return,
......@@ -1853,11 +2072,16 @@ pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLin
18532072/// If `sym_link_path` exists, it will not be overwritten.
18542073/// See also `symlinkatWasi`, `symlinkatZ` and `symlinkatW`.
18552074pub 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 }
18592075 if (builtin.os.tag == .windows) {
18602076 @compileError("symlinkat is not supported on Windows; use std.os.windows.CreateSymbolicLink instead");
2077 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
2078 if (newdirfd == wasi.AT.FDCWD or fs.path.isAbsolute(target_path)) {
2079 // Resolve absolute or CWD-relative paths to a path within a Preopen
2080 var path_buf: [MAX_PATH_BYTES]u8 = undefined;
2081 const path = try resolvePathWasi(sym_link_path, &path_buf);
2082 return symlinkatWasi(target_path, path.dir_fd, path.relative_path);
2083 }
2084 return symlinkatWasi(target_path, newdirfd, sym_link_path);
18612085 }
18622086 const target_path_c = try toPosixPath(target_path);
18632087 const sym_link_path_c = try toPosixPath(sym_link_path);
......@@ -1893,6 +2117,8 @@ pub fn symlinkatWasi(target_path: []const u8, newdirfd: fd_t, sym_link_path: []c
18932117pub fn symlinkatZ(target_path: [*:0]const u8, newdirfd: fd_t, sym_link_path: [*:0]const u8) SymLinkError!void {
18942118 if (builtin.os.tag == .windows) {
18952119 @compileError("symlinkat is not supported on Windows; use std.os.windows.CreateSymbolicLink instead");
2120 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
2121 return symlinkat(mem.sliceTo(target_path, 0), newdirfd, mem.sliceTo(sym_link_path, 0));
18962122 }
18972123 switch (errno(system.symlinkat(target_path, newdirfd, sym_link_path))) {
18982124 .SUCCESS => return,
......@@ -1930,6 +2156,9 @@ pub const LinkError = UnexpectedError || error{
19302156};
19312157
19322158pub fn linkZ(oldpath: [*:0]const u8, newpath: [*:0]const u8, flags: i32) LinkError!void {
2159 if (builtin.os.tag == .wasi and !builtin.link_libc) {
2160 return link(mem.sliceTo(oldpath, 0), mem.sliceTo(newpath, 0), flags);
2161 }
19332162 switch (errno(system.link(oldpath, newpath, flags))) {
19342163 .SUCCESS => return,
19352164 .ACCES => return error.AccessDenied,
......@@ -1952,6 +2181,12 @@ pub fn linkZ(oldpath: [*:0]const u8, newpath: [*:0]const u8, flags: i32) LinkErr
19522181}
19532182
19542183pub fn link(oldpath: []const u8, newpath: []const u8, flags: i32) LinkError!void {
2184 if (builtin.os.tag == .wasi and !builtin.link_libc) {
2185 return linkat(wasi.AT.FDCWD, oldpath, wasi.AT.FDCWD, newpath, flags) catch |err| switch (err) {
2186 error.NotDir => unreachable, // link() does not support directories
2187 else => |e| return e,
2188 };
2189 }
19552190 const old = try toPosixPath(oldpath);
19562191 const new = try toPosixPath(newpath);
19572192 return try linkZ(&old, &new, flags);
......@@ -1966,6 +2201,9 @@ pub fn linkatZ(
19662201 newpath: [*:0]const u8,
19672202 flags: i32,
19682203) LinkatError!void {
2204 if (builtin.os.tag == .wasi and !builtin.link_libc) {
2205 return linkat(olddir, mem.sliceTo(oldpath, 0), newdir, mem.sliceTo(newpath, 0), flags);
2206 }
19692207 switch (errno(system.linkat(olddir, oldpath, newdir, newpath, flags))) {
19702208 .SUCCESS => return,
19712209 .ACCES => return error.AccessDenied,
......@@ -1995,11 +2233,62 @@ pub fn linkat(
19952233 newpath: []const u8,
19962234 flags: i32,
19972235) LinkatError!void {
2236 if (builtin.os.tag == .wasi and !builtin.link_libc) {
2237 var resolve_olddir: bool = (olddir == wasi.AT.FDCWD or fs.path.isAbsolute(oldpath));
2238 var resolve_newdir: bool = (newdir == wasi.AT.FDCWD or fs.path.isAbsolute(newpath));
2239
2240 var old: RelativePathWasi = .{ .dir_fd = olddir, .relative_path = oldpath };
2241 var new: RelativePathWasi = .{ .dir_fd = newdir, .relative_path = newpath };
2242
2243 // Resolve absolute or CWD-relative paths to a path within a Preopen
2244 if (resolve_olddir or resolve_newdir) {
2245 var buf_old: [MAX_PATH_BYTES]u8 = undefined;
2246 var buf_new: [MAX_PATH_BYTES]u8 = undefined;
2247
2248 if (resolve_olddir)
2249 old = try resolvePathWasi(oldpath, &buf_old);
2250
2251 if (resolve_newdir)
2252 new = try resolvePathWasi(newpath, &buf_new);
2253
2254 return linkatWasi(old, new, flags);
2255 }
2256 return linkatWasi(old, new, flags);
2257 }
19982258 const old = try toPosixPath(oldpath);
19992259 const new = try toPosixPath(newpath);
20002260 return try linkatZ(olddir, &old, newdir, &new, flags);
20012261}
20022262
2263/// WASI-only. The same as `linkat` but targeting WASI.
2264/// See also `linkat`.
2265pub fn linkatWasi(old: RelativePathWasi, new: RelativePathWasi, flags: i32) LinkatError!void {
2266 var old_flags: wasi.lookupflags_t = 0;
2267 // TODO: Why is this not defined in wasi-libc?
2268 if (flags & linux.AT.SYMLINK_FOLLOW != 0) old_flags |= wasi.LOOKUP_SYMLINK_FOLLOW;
2269
2270 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)) {
2271 .SUCCESS => return,
2272 .ACCES => return error.AccessDenied,
2273 .DQUOT => return error.DiskQuota,
2274 .EXIST => return error.PathAlreadyExists,
2275 .FAULT => unreachable,
2276 .IO => return error.FileSystem,
2277 .LOOP => return error.SymLinkLoop,
2278 .MLINK => return error.LinkQuotaExceeded,
2279 .NAMETOOLONG => return error.NameTooLong,
2280 .NOENT => return error.FileNotFound,
2281 .NOMEM => return error.SystemResources,
2282 .NOSPC => return error.NoSpaceLeft,
2283 .NOTDIR => return error.NotDir,
2284 .PERM => return error.AccessDenied,
2285 .ROFS => return error.ReadOnlyFileSystem,
2286 .XDEV => return error.NotSameFileSystem,
2287 .INVAL => unreachable,
2288 else => |err| return unexpectedErrno(err),
2289 }
2290}
2291
20032292pub const UnlinkError = error{
20042293 FileNotFound,
20052294
......@@ -2027,7 +2316,10 @@ pub const UnlinkError = error{
20272316/// See also `unlinkZ`.
20282317pub fn unlink(file_path: []const u8) UnlinkError!void {
20292318 if (builtin.os.tag == .wasi and !builtin.link_libc) {
2030 @compileError("unlink is not supported in WASI; use unlinkat instead");
2319 return unlinkat(wasi.AT.FDCWD, file_path, 0) catch |err| switch (err) {
2320 error.DirNotEmpty => unreachable, // only occurs when targeting directories
2321 else => |e| return e,
2322 };
20312323 } else if (builtin.os.tag == .windows) {
20322324 const file_path_w = try windows.sliceToPrefixedFileW(file_path);
20332325 return unlinkW(file_path_w.span());
......@@ -2042,6 +2334,8 @@ pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void {
20422334 if (builtin.os.tag == .windows) {
20432335 const file_path_w = try windows.cStrToPrefixedFileW(file_path);
20442336 return unlinkW(file_path_w.span());
2337 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
2338 return unlink(mem.sliceTo(file_path, 0));
20452339 }
20462340 switch (errno(system.unlink(file_path))) {
20472341 .SUCCESS => return,
......@@ -2079,6 +2373,12 @@ pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!vo
20792373 const file_path_w = try windows.sliceToPrefixedFileW(file_path);
20802374 return unlinkatW(dirfd, file_path_w.span(), flags);
20812375 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
2376 if (dirfd == wasi.AT.FDCWD or fs.path.isAbsolute(file_path)) {
2377 // Resolve absolute or CWD-relative paths to a path within a Preopen
2378 var path_buf: [MAX_PATH_BYTES]u8 = undefined;
2379 const path = try resolvePathWasi(file_path, &path_buf);
2380 return unlinkatWasi(path.dir_fd, path.relative_path, flags);
2381 }
20822382 return unlinkatWasi(dirfd, file_path, flags);
20832383 } else {
20842384 const file_path_c = try toPosixPath(file_path);
......@@ -2123,6 +2423,8 @@ pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatEr
21232423 if (builtin.os.tag == .windows) {
21242424 const file_path_w = try windows.cStrToPrefixedFileW(file_path_c);
21252425 return unlinkatW(dirfd, file_path_w.span(), flags);
2426 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
2427 return unlinkat(dirfd, mem.sliceTo(file_path_c, 0), flags);
21262428 }
21272429 switch (errno(system.unlinkat(dirfd, file_path_c, flags))) {
21282430 .SUCCESS => return,
......@@ -2181,7 +2483,7 @@ pub const RenameError = error{
21812483/// Change the name or location of a file.
21822484pub fn rename(old_path: []const u8, new_path: []const u8) RenameError!void {
21832485 if (builtin.os.tag == .wasi and !builtin.link_libc) {
2184 @compileError("rename is not supported in WASI; use renameat instead");
2486 return renameat(wasi.AT.FDCWD, old_path, wasi.AT.FDCWD, new_path);
21852487 } else if (builtin.os.tag == .windows) {
21862488 const old_path_w = try windows.sliceToPrefixedFileW(old_path);
21872489 const new_path_w = try windows.sliceToPrefixedFileW(new_path);
......@@ -2199,6 +2501,8 @@ pub fn renameZ(old_path: [*:0]const u8, new_path: [*:0]const u8) RenameError!voi
21992501 const old_path_w = try windows.cStrToPrefixedFileW(old_path);
22002502 const new_path_w = try windows.cStrToPrefixedFileW(new_path);
22012503 return renameW(old_path_w.span().ptr, new_path_w.span().ptr);
2504 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
2505 return rename(mem.sliceTo(old_path, 0), mem.sliceTo(new_path, 0));
22022506 }
22032507 switch (errno(system.rename(old_path, new_path))) {
22042508 .SUCCESS => return,
......@@ -2243,7 +2547,25 @@ pub fn renameat(
22432547 const new_path_w = try windows.sliceToPrefixedFileW(new_path);
22442548 return renameatW(old_dir_fd, old_path_w.span(), new_dir_fd, new_path_w.span(), windows.TRUE);
22452549 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
2246 return renameatWasi(old_dir_fd, old_path, new_dir_fd, new_path);
2550 var resolve_old: bool = (old_dir_fd == wasi.AT.FDCWD or fs.path.isAbsolute(old_path));
2551 var resolve_new: bool = (new_dir_fd == wasi.AT.FDCWD or fs.path.isAbsolute(new_path));
2552
2553 var old: RelativePathWasi = .{ .dir_fd = old_dir_fd, .relative_path = old_path };
2554 var new: RelativePathWasi = .{ .dir_fd = new_dir_fd, .relative_path = new_path };
2555
2556 // Resolve absolute or CWD-relative paths to a path within a Preopen
2557 if (resolve_old or resolve_new) {
2558 var buf_old: [MAX_PATH_BYTES]u8 = undefined;
2559 var buf_new: [MAX_PATH_BYTES]u8 = undefined;
2560
2561 if (resolve_old)
2562 old = try resolvePathWasi(old_path, &buf_old);
2563 if (resolve_new)
2564 new = try resolvePathWasi(new_path, &buf_new);
2565
2566 return renameatWasi(old, new);
2567 }
2568 return renameatWasi(old, new);
22472569 } else {
22482570 const old_path_c = try toPosixPath(old_path);
22492571 const new_path_c = try toPosixPath(new_path);
......@@ -2253,8 +2575,8 @@ pub fn renameat(
22532575
22542576/// WASI-only. Same as `renameat` expect targeting WASI.
22552577/// 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)) {
2578pub fn renameatWasi(old: RelativePathWasi, new: RelativePathWasi) RenameError!void {
2579 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)) {
22582580 .SUCCESS => return,
22592581 .ACCES => return error.AccessDenied,
22602582 .PERM => return error.AccessDenied,
......@@ -2290,6 +2612,8 @@ pub fn renameatZ(
22902612 const old_path_w = try windows.cStrToPrefixedFileW(old_path);
22912613 const new_path_w = try windows.cStrToPrefixedFileW(new_path);
22922614 return renameatW(old_dir_fd, old_path_w.span(), new_dir_fd, new_path_w.span(), windows.TRUE);
2615 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
2616 return renameat(old_dir_fd, mem.sliceTo(old_path, 0), new_dir_fd, mem.sliceTo(new_path, 0));
22932617 }
22942618
22952619 switch (errno(system.renameat(old_dir_fd, old_path, new_dir_fd, new_path))) {
......@@ -2380,6 +2704,12 @@ pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!v
23802704 const sub_dir_path_w = try windows.sliceToPrefixedFileW(sub_dir_path);
23812705 return mkdiratW(dir_fd, sub_dir_path_w.span(), mode);
23822706 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
2707 if (dir_fd == wasi.AT.FDCWD or fs.path.isAbsolute(sub_dir_path)) {
2708 // Resolve absolute or CWD-relative paths to a path within a Preopen
2709 var path_buf: [MAX_PATH_BYTES]u8 = undefined;
2710 const path = try resolvePathWasi(sub_dir_path, &path_buf);
2711 return mkdiratWasi(path.dir_fd, path.relative_path, mode);
2712 }
23832713 return mkdiratWasi(dir_fd, sub_dir_path, mode);
23842714 } else {
23852715 const sub_dir_path_c = try toPosixPath(sub_dir_path);
......@@ -2414,6 +2744,8 @@ pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirErr
24142744 if (builtin.os.tag == .windows) {
24152745 const sub_dir_path_w = try windows.cStrToPrefixedFileW(sub_dir_path);
24162746 return mkdiratW(dir_fd, sub_dir_path_w.span().ptr, mode);
2747 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
2748 return mkdirat(dir_fd, mem.sliceTo(sub_dir_path, 0), mode);
24172749 }
24182750 switch (errno(system.mkdirat(dir_fd, sub_dir_path, mode))) {
24192751 .SUCCESS => return,
......@@ -2472,10 +2804,10 @@ pub const MakeDirError = error{
24722804} || UnexpectedError;
24732805
24742806/// Create a directory.
2475/// `mode` is ignored on Windows.
2807/// `mode` is ignored on Windows and WASI.
24762808pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void {
24772809 if (builtin.os.tag == .wasi and !builtin.link_libc) {
2478 @compileError("mkdir is not supported in WASI; use mkdirat instead");
2810 return mkdirat(wasi.AT.FDCWD, dir_path, mode);
24792811 } else if (builtin.os.tag == .windows) {
24802812 const dir_path_w = try windows.sliceToPrefixedFileW(dir_path);
24812813 return mkdirW(dir_path_w.span(), mode);
......@@ -2490,6 +2822,8 @@ pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void {
24902822 if (builtin.os.tag == .windows) {
24912823 const dir_path_w = try windows.cStrToPrefixedFileW(dir_path);
24922824 return mkdirW(dir_path_w.span(), mode);
2825 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
2826 return mkdir(mem.sliceTo(dir_path, 0), mode);
24932827 }
24942828 switch (errno(system.mkdir(dir_path, mode))) {
24952829 .SUCCESS => return,
......@@ -2545,7 +2879,11 @@ pub const DeleteDirError = error{
25452879/// Deletes an empty directory.
25462880pub fn rmdir(dir_path: []const u8) DeleteDirError!void {
25472881 if (builtin.os.tag == .wasi and !builtin.link_libc) {
2548 @compileError("rmdir is not supported in WASI; use unlinkat instead");
2882 return unlinkat(wasi.AT.FDCWD, dir_path, AT.REMOVEDIR) catch |err| switch (err) {
2883 error.FileSystem => unreachable, // only occurs when targeting files
2884 error.IsDir => unreachable, // only occurs when targeting files
2885 else => |e| return e,
2886 };
25492887 } else if (builtin.os.tag == .windows) {
25502888 const dir_path_w = try windows.sliceToPrefixedFileW(dir_path);
25512889 return rmdirW(dir_path_w.span());
......@@ -2560,6 +2898,8 @@ pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void {
25602898 if (builtin.os.tag == .windows) {
25612899 const dir_path_w = try windows.cStrToPrefixedFileW(dir_path);
25622900 return rmdirW(dir_path_w.span());
2901 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
2902 return rmdir(mem.sliceTo(dir_path, 0));
25632903 }
25642904 switch (errno(system.rmdir(dir_path))) {
25652905 .SUCCESS => return,
......@@ -2606,7 +2946,17 @@ pub const ChangeCurDirError = error{
26062946/// `dir_path` is recommended to be a UTF-8 encoded string.
26072947pub fn chdir(dir_path: []const u8) ChangeCurDirError!void {
26082948 if (builtin.os.tag == .wasi and !builtin.link_libc) {
2609 @compileError("chdir is not supported in WASI");
2949 var preopen: ?Preopen = null;
2950 const path = try resolvePathAndGetWasiPreopen(dir_path, &preopen, &wasi_cwd.path_buffer);
2951
2952 const dirinfo = try fstatat(path.dir_fd, path.relative_path, 0);
2953 if (dirinfo.filetype != .DIRECTORY) {
2954 return error.NotDir;
2955 }
2956
2957 wasi_cwd.cwd_preopen = preopen;
2958 wasi_cwd.cwd = path;
2959 return;
26102960 } else if (builtin.os.tag == .windows) {
26112961 var utf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined;
26122962 const len = try std.unicode.utf8ToUtf16Le(utf16_dir_path[0..], dir_path);
......@@ -2625,6 +2975,8 @@ pub fn chdirZ(dir_path: [*:0]const u8) ChangeCurDirError!void {
26252975 const len = try std.unicode.utf8ToUtf16Le(utf16_dir_path[0..], dir_path);
26262976 if (len > utf16_dir_path.len) return error.NameTooLong;
26272977 return chdirW(utf16_dir_path[0..len]);
2978 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
2979 return chdir(mem.sliceTo(dir_path, 0));
26282980 }
26292981 switch (errno(system.chdir(dir_path))) {
26302982 .SUCCESS => return,
......@@ -2655,15 +3007,29 @@ pub const FchdirError = error{
26553007} || UnexpectedError;
26563008
26573009pub 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),
3010 if (builtin.os.tag == .wasi) {
3011 // Check that this is a directory
3012 const dirinfo = fstatat(dirfd, ".", 0) catch unreachable;
3013 if (dirinfo.filetype != .DIRECTORY) {
3014 return error.NotDir;
3015 }
3016
3017 wasi_cwd.cwd = .{
3018 .dir_fd = dirfd,
3019 .relative_path = ".",
3020 };
3021 wasi_cwd.cwd_preopen = null;
3022 } else {
3023 while (true) {
3024 switch (errno(system.fchdir(dirfd))) {
3025 .SUCCESS => return,
3026 .ACCES => return error.AccessDenied,
3027 .BADF => unreachable,
3028 .NOTDIR => return error.NotDir,
3029 .INTR => continue,
3030 .IO => return error.FileSystem,
3031 else => |err| return unexpectedErrno(err),
3032 }
26673033 }
26683034 }
26693035}
......@@ -2690,7 +3056,7 @@ pub const ReadLinkError = error{
26903056/// The return value is a slice of `out_buffer` from index 0.
26913057pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
26923058 if (builtin.os.tag == .wasi and !builtin.link_libc) {
2693 @compileError("readlink is not supported in WASI; use readlinkat instead");
3059 return readlinkat(wasi.AT.FDCWD, file_path, out_buffer);
26943060 } else if (builtin.os.tag == .windows) {
26953061 const file_path_w = try windows.sliceToPrefixedFileW(file_path);
26963062 return readlinkW(file_path_w.span(), out_buffer);
......@@ -2711,6 +3077,8 @@ pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8
27113077 if (builtin.os.tag == .windows) {
27123078 const file_path_w = try windows.cStrToWin32PrefixedFileW(file_path);
27133079 return readlinkW(file_path_w.span(), out_buffer);
3080 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
3081 return readlink(mem.sliceTo(file_path, 0), out_buffer);
27143082 }
27153083 const rc = system.readlink(file_path, out_buffer.ptr, out_buffer.len);
27163084 switch (errno(rc)) {
......@@ -2733,6 +3101,12 @@ pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8
27333101/// See also `readlinkatWasi`, `realinkatZ` and `realinkatW`.
27343102pub fn readlinkat(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
27353103 if (builtin.os.tag == .wasi and !builtin.link_libc) {
3104 if (dirfd == wasi.AT.FDCWD or fs.path.isAbsolute(file_path)) {
3105 // Resolve absolute or CWD-relative paths to a path within a Preopen
3106 var path_buf: [MAX_PATH_BYTES]u8 = undefined;
3107 var path = try resolvePathWasi(file_path, &path_buf);
3108 return readlinkatWasi(path.dir_fd, path.relative_path, out_buffer);
3109 }
27363110 return readlinkatWasi(dirfd, file_path, out_buffer);
27373111 }
27383112 if (builtin.os.tag == .windows) {
......@@ -2775,6 +3149,8 @@ pub fn readlinkatZ(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) Read
27753149 if (builtin.os.tag == .windows) {
27763150 const file_path_w = try windows.cStrToPrefixedFileW(file_path);
27773151 return readlinkatW(dirfd, file_path_w.span(), out_buffer);
3152 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
3153 return readlinkat(dirfd, mem.sliceTo(file_path, 0), out_buffer);
27783154 }
27793155 const rc = system.readlinkat(dirfd, file_path, out_buffer.ptr, out_buffer.len);
27803156 switch (errno(rc)) {
......@@ -3727,7 +4103,14 @@ pub const FStatAtError = FStatError || error{ NameTooLong, FileNotFound, SymLink
37274103/// See also `fstatatZ` and `fstatatWasi`.
37284104pub fn fstatat(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!Stat {
37294105 if (builtin.os.tag == .wasi and !builtin.link_libc) {
3730 return fstatatWasi(dirfd, pathname, flags);
4106 const wasi_flags = if (flags & linux.AT.SYMLINK_NOFOLLOW == 0) wasi.LOOKUP_SYMLINK_FOLLOW else 0;
4107 if (dirfd == wasi.AT.FDCWD or fs.path.isAbsolute(pathname)) {
4108 // Resolve absolute or CWD-relative paths to a path within a Preopen
4109 var path_buf: [MAX_PATH_BYTES]u8 = undefined;
4110 const path = try resolvePathWasi(pathname, &path_buf);
4111 return fstatatWasi(path.dir_fd, path.relative_path, wasi_flags);
4112 }
4113 return fstatatWasi(dirfd, pathname, wasi_flags);
37314114 } else if (builtin.os.tag == .windows) {
37324115 @compileError("fstatat is not yet implemented on Windows");
37334116 } else {
......@@ -3758,6 +4141,10 @@ pub fn fstatatWasi(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!S
37584141/// Same as `fstatat` but `pathname` is null-terminated.
37594142/// See also `fstatat`.
37604143pub fn fstatatZ(dirfd: fd_t, pathname: [*:0]const u8, flags: u32) FStatAtError!Stat {
4144 if (builtin.os.tag == .wasi and !builtin.link_libc) {
4145 return fstatatWasi(dirfd, mem.sliceTo(pathname), flags);
4146 }
4147
37614148 const fstatat_sym = if (builtin.os.tag == .linux and builtin.link_libc)
37624149 system.fstatat64
37634150 else
......@@ -4056,6 +4443,8 @@ pub fn access(path: []const u8, mode: u32) AccessError!void {
40564443 const path_w = try windows.sliceToPrefixedFileW(path);
40574444 _ = try windows.GetFileAttributesW(path_w.span().ptr);
40584445 return;
4446 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
4447 return faccessat(wasi.AT.FDCWD, path, mode, 0);
40594448 }
40604449 const path_c = try toPosixPath(path);
40614450 return accessZ(&path_c, mode);
......@@ -4067,6 +4456,8 @@ pub fn accessZ(path: [*:0]const u8, mode: u32) AccessError!void {
40674456 const path_w = try windows.cStrToPrefixedFileW(path);
40684457 _ = try windows.GetFileAttributesW(path_w.span().ptr);
40694458 return;
4459 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
4460 return access(mem.sliceTo(path, 0), mode);
40704461 }
40714462 switch (errno(system.access(path, mode))) {
40724463 .SUCCESS => return,
......@@ -4108,6 +4499,45 @@ pub fn faccessat(dirfd: fd_t, path: []const u8, mode: u32, flags: u32) AccessErr
41084499 if (builtin.os.tag == .windows) {
41094500 const path_w = try windows.sliceToPrefixedFileW(path);
41104501 return faccessatW(dirfd, path_w.span().ptr, mode, flags);
4502 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
4503 var resolved = RelativePathWasi{ .dir_fd = dirfd, .relative_path = path };
4504
4505 const file = blk: {
4506 if (dirfd == wasi.AT.FDCWD or fs.path.isAbsolute(path)) {
4507 // Resolve absolute or CWD-relative paths to a path within a Preopen
4508 var path_buf: [MAX_PATH_BYTES]u8 = undefined;
4509 resolved = resolvePathWasi(path, &path_buf) catch |err| break :blk @as(FStatAtError!Stat, err);
4510 break :blk fstatat(resolved.dir_fd, resolved.relative_path, flags);
4511 }
4512 break :blk fstatat(dirfd, path, flags);
4513 } catch |err| switch (err) {
4514 error.AccessDenied => return error.PermissionDenied,
4515 else => |e| return e,
4516 };
4517
4518 if (mode != F_OK) {
4519 var directory: wasi.fdstat_t = undefined;
4520 if (wasi.fd_fdstat_get(resolved.dir_fd, &directory) != .SUCCESS) {
4521 return error.PermissionDenied;
4522 }
4523
4524 var rights: wasi.rights_t = 0;
4525 if (mode & R_OK != 0) {
4526 rights |= if (file.filetype == .DIRECTORY)
4527 wasi.RIGHT.FD_READDIR
4528 else
4529 wasi.RIGHT.FD_READ;
4530 }
4531 if (mode & W_OK != 0) {
4532 rights |= wasi.RIGHT.FD_WRITE;
4533 }
4534 // No validation for X_OK
4535
4536 if ((rights & directory.fs_rights_inheriting) != rights) {
4537 return error.PermissionDenied;
4538 }
4539 }
4540 return;
41114541 }
41124542 const path_c = try toPosixPath(path);
41134543 return faccessatZ(dirfd, &path_c, mode, flags);
......@@ -4118,6 +4548,8 @@ pub fn faccessatZ(dirfd: fd_t, path: [*:0]const u8, mode: u32, flags: u32) Acces
41184548 if (builtin.os.tag == .windows) {
41194549 const path_w = try windows.cStrToPrefixedFileW(path);
41204550 return faccessatW(dirfd, path_w.span().ptr, mode, flags);
4551 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
4552 return faccessat(dirfd, mem.sliceTo(path, 0), mode, flags);
41214553 }
41224554 switch (errno(system.faccessat(dirfd, path, mode, flags))) {
41234555 .SUCCESS => return,
......@@ -4645,6 +5077,9 @@ pub const RealPathError = error{
46455077 SharingViolation,
46465078 PipeBusy,
46475079
5080 /// On WASI, the current CWD may not be associated with an absolute path.
5081 InvalidHandle,
5082
46485083 /// On Windows, file paths must be valid Unicode.
46495084 InvalidUtf8,
46505085
......@@ -4660,19 +5095,55 @@ pub fn realpath(pathname: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathE
46605095 if (builtin.os.tag == .windows) {
46615096 const pathname_w = try windows.sliceToPrefixedFileW(pathname);
46625097 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");
5098 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
5099 return realpathWasi(pathname, out_buffer);
46665100 }
46675101 const pathname_c = try toPosixPath(pathname);
46685102 return realpathZ(&pathname_c, out_buffer);
46695103}
46705104
5105/// Return an emulated canonicalized absolute pathname on WASI.
5106///
5107/// NOTE: This emulation is incomplete. Symbolic links are not
5108/// currently expanded during path canonicalization.
5109fn realpathWasi(pathname: []const u8, out_buffer: []u8) ![]u8 {
5110 var alloc = std.heap.FixedBufferAllocator.init(out_buffer);
5111 if (fs.path.isAbsolute(pathname))
5112 return try fs.path.resolve(alloc.allocator(), &.{pathname}) catch error.NameTooLong;
5113 if (wasi_cwd.cwd) |cwd| {
5114 if (wasi_cwd.cwd_preopen) |po| {
5115 var base_cwd_dir = switch (po.@"type") {
5116 .Dir => |dir| dir,
5117 };
5118 const paths: [][]const u8 = if (fs.path.isAbsolute(base_cwd_dir)) blk: {
5119 break :blk &.{ base_cwd_dir, cwd.relative_path, pathname };
5120 } else blk: {
5121 // No absolute path is associated with this preopen, so
5122 // instead we use a special "/preopens/fd/<N>/" prefix
5123 var buf: [16]u8 = undefined;
5124 var fbs = std.io.fixedBufferStream(&buf);
5125 std.fmt.formatInt(po.fd, 10, .lower, .{}, fbs.writer()) catch return error.NameTooLong;
5126 break :blk &.{ "/preopens/fd/", fbs.getWritten(), cwd.relative_path, pathname };
5127 };
5128
5129 return fs.path.resolve(alloc.allocator(), paths) catch error.NameTooLong;
5130 } else {
5131 // The CWD is not rooted to an existing Preopen,
5132 // so we have no way to know its absolute path
5133 return error.InvalidHandle;
5134 }
5135 } else {
5136 return try fs.path.resolve(alloc.allocator(), &.{ "/", pathname }) catch error.NameTooLong;
5137 }
5138}
5139
46715140/// Same as `realpath` except `pathname` is null-terminated.
46725141pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
46735142 if (builtin.os.tag == .windows) {
46745143 const pathname_w = try windows.cStrToPrefixedFileW(pathname);
46755144 return realpathW(pathname_w.span(), out_buffer);
5145 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
5146 return realpath(mem.sliceTo(pathname, 0), out_buffer);
46765147 }
46775148 if (!builtin.link_libc) {
46785149 const flags = if (builtin.os.tag == .linux) O.PATH | O.NONBLOCK | O.CLOEXEC else O.NONBLOCK | O.CLOEXEC;
......@@ -4680,6 +5151,7 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP
46805151 error.FileLocksNotSupported => unreachable,
46815152 error.WouldBlock => unreachable,
46825153 error.FileBusy => unreachable, // not asking for write permissions
5154 error.InvalidHandle => unreachable, // WASI-only
46835155 else => |e| return e,
46845156 };
46855157 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, ".");
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, ".");
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, ".");
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, ".");
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, "."),
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, ".");
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, ".");
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/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,