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 {...@@ -2707,6 +2707,8 @@ pub const LibExeObjStep = struct {
2707 try zig_args.append("--test-cmd");2707 try zig_args.append("--test-cmd");
2708 try zig_args.append("--dir=.");2708 try zig_args.append("--dir=.");
2709 try zig_args.append("--test-cmd");2709 try zig_args.append("--test-cmd");
2710 try zig_args.append("--mapdir=/cwd::.");
2711 try zig_args.append("--test-cmd");
2710 try zig_args.append("--allow-unknown-exports"); // TODO: Remove when stage2 is default compiler2712 try zig_args.append("--allow-unknown-exports"); // TODO: Remove when stage2 is default compiler
2711 try zig_args.append("--test-cmd-bin");2713 try zig_args.append("--test-cmd-bin");
2712 } else {2714 } else {
lib/std/c/wasi.zig+5-5
...@@ -62,21 +62,21 @@ pub const Stat = extern struct {...@@ -62,21 +62,21 @@ pub const Stat = extern struct {
62/// https://github.com/WebAssembly/wasi-libc/blob/main/expected/wasm32-wasi/predefined-macros.txt62/// https://github.com/WebAssembly/wasi-libc/blob/main/expected/wasm32-wasi/predefined-macros.txt
63pub const O = struct {63pub const O = struct {
64 pub const ACCMODE = (EXEC | RDWR | SEARCH);64 pub const ACCMODE = (EXEC | RDWR | SEARCH);
65 pub const APPEND = FDFLAG.APPEND;65 pub const APPEND = @as(u32, FDFLAG.APPEND);
66 pub const CLOEXEC = (0);66 pub const CLOEXEC = (0);
67 pub const CREAT = ((1 << 0) << 12); // = __WASI_OFLAGS_CREAT << 1267 pub const CREAT = ((1 << 0) << 12); // = __WASI_OFLAGS_CREAT << 12
68 pub const DIRECTORY = ((1 << 1) << 12); // = __WASI_OFLAGS_DIRECTORY << 1268 pub const DIRECTORY = ((1 << 1) << 12); // = __WASI_OFLAGS_DIRECTORY << 12
69 pub const DSYNC = FDFLAG.DSYNC;69 pub const DSYNC = @as(u32, FDFLAG.DSYNC);
70 pub const EXCL = ((1 << 2) << 12); // = __WASI_OFLAGS_EXCL << 1270 pub const EXCL = ((1 << 2) << 12); // = __WASI_OFLAGS_EXCL << 12
71 pub const EXEC = (0x02000000);71 pub const EXEC = (0x02000000);
72 pub const NOCTTY = (0);72 pub const NOCTTY = (0);
73 pub const NOFOLLOW = (0x01000000);73 pub const NOFOLLOW = (0x01000000);
74 pub const NONBLOCK = (1 << FDFLAG.NONBLOCK);74 pub const NONBLOCK = @as(u32, FDFLAG.NONBLOCK);
75 pub const RDONLY = (0x04000000);75 pub const RDONLY = (0x04000000);
76 pub const RDWR = (RDONLY | WRONLY);76 pub const RDWR = (RDONLY | WRONLY);
77 pub const RSYNC = (1 << FDFLAG.RSYNC);77 pub const RSYNC = @as(u32, FDFLAG.RSYNC);
78 pub const SEARCH = (0x08000000);78 pub const SEARCH = (0x08000000);
79 pub const SYNC = (1 << FDFLAG.SYNC);79 pub const SYNC = @as(u32, FDFLAG.SYNC);
80 pub const TRUNC = ((1 << 3) << 12); // = __WASI_OFLAGS_TRUNC << 1280 pub const TRUNC = ((1 << 3) << 12); // = __WASI_OFLAGS_TRUNC << 12
81 pub const TTY_INIT = (0);81 pub const TTY_INIT = (0);
82 pub const WRONLY = (0x10000000);82 pub const WRONLY = (0x10000000);
lib/std/child_process.zig+1
...@@ -563,6 +563,7 @@ pub const ChildProcess = struct {...@@ -563,6 +563,7 @@ pub const ChildProcess = struct {
563 error.DeviceBusy => unreachable,563 error.DeviceBusy => unreachable,
564 error.FileLocksNotSupported => unreachable,564 error.FileLocksNotSupported => unreachable,
565 error.BadPathName => unreachable, // Windows-only565 error.BadPathName => unreachable, // Windows-only
566 error.InvalidHandle => unreachable, // WASI-only
566 error.WouldBlock => unreachable,567 error.WouldBlock => unreachable,
567 else => |e| return e,568 else => |e| return e,
568 }569 }
lib/std/fs.zig+44-28
...@@ -923,6 +923,7 @@ pub const Dir = struct {...@@ -923,6 +923,7 @@ pub const Dir = struct {
923 pub const OpenError = error{923 pub const OpenError = error{
924 FileNotFound,924 FileNotFound,
925 NotDir,925 NotDir,
926 InvalidHandle,
926 AccessDenied,927 AccessDenied,
927 SymLinkLoop,928 SymLinkLoop,
928 ProcessFdQuotaExceeded,929 ProcessFdQuotaExceeded,
...@@ -981,6 +982,13 @@ pub const Dir = struct {...@@ -981,6 +982,13 @@ pub const Dir = struct {
981 w.RIGHT.FD_FILESTAT_SET_TIMES |982 w.RIGHT.FD_FILESTAT_SET_TIMES |
982 w.RIGHT.FD_FILESTAT_SET_SIZE;983 w.RIGHT.FD_FILESTAT_SET_SIZE;
983 }984 }
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 }
984 const fd = try os.openatWasi(self.fd, sub_path, 0x0, 0x0, fdflags, base, 0x0);992 const fd = try os.openatWasi(self.fd, sub_path, 0x0, 0x0, fdflags, base, 0x0);
985 return File{ .handle = fd };993 return File{ .handle = fd };
986 }994 }
...@@ -1145,6 +1153,13 @@ pub const Dir = struct {...@@ -1145,6 +1153,13 @@ pub const Dir = struct {
1145 if (flags.exclusive) {1153 if (flags.exclusive) {
1146 oflags |= w.O.EXCL;1154 oflags |= w.O.EXCL;
1147 }1155 }
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 }
1148 const fd = try os.openatWasi(self.fd, sub_path, 0x0, oflags, 0x0, base, 0x0);1163 const fd = try os.openatWasi(self.fd, sub_path, 0x0, oflags, 0x0, base, 0x0);
1149 return File{ .handle = fd };1164 return File{ .handle = fd };
1150 }1165 }
...@@ -1330,7 +1345,19 @@ pub const Dir = struct {...@@ -1330,7 +1345,19 @@ pub const Dir = struct {
1330 /// See also `Dir.realpathZ`, `Dir.realpathW`, and `Dir.realpathAlloc`.1345 /// See also `Dir.realpathZ`, `Dir.realpathW`, and `Dir.realpathAlloc`.
1331 pub fn realpath(self: Dir, pathname: []const u8, out_buffer: []u8) ![]u8 {1346 pub fn realpath(self: Dir, pathname: []const u8, out_buffer: []u8) ![]u8 {
1332 if (builtin.os.tag == .wasi) {1347 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 }
1334 }1361 }
1335 if (builtin.os.tag == .windows) {1362 if (builtin.os.tag == .windows) {
1336 const pathname_w = try os.windows.sliceToPrefixedFileW(pathname);1363 const pathname_w = try os.windows.sliceToPrefixedFileW(pathname);
...@@ -1507,7 +1534,16 @@ pub const Dir = struct {...@@ -1507,7 +1534,16 @@ pub const Dir = struct {
1507 // TODO do we really need all the rights here?1534 // TODO do we really need all the rights here?
1508 const inheriting: w.rights_t = w.RIGHT.ALL ^ w.RIGHT.SOCK_SHUTDOWN;1535 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 };
1511 const fd = result catch |err| switch (err) {1547 const fd = result catch |err| switch (err) {
1512 error.FileTooBig => unreachable, // can't happen for directories1548 error.FileTooBig => unreachable, // can't happen for directories
1513 error.IsDir => unreachable, // we're providing O.DIRECTORY1549 error.IsDir => unreachable, // we're providing O.DIRECTORY
...@@ -1622,7 +1658,7 @@ pub const Dir = struct {...@@ -1622,7 +1658,7 @@ pub const Dir = struct {
1622 const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);1658 const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
1623 return self.deleteFileW(sub_path_w.span());1659 return self.deleteFileW(sub_path_w.span());
1624 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {1660 } 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) {
1626 error.DirNotEmpty => unreachable, // not passing AT.REMOVEDIR1662 error.DirNotEmpty => unreachable, // not passing AT.REMOVEDIR
1627 else => |e| return e,1663 else => |e| return e,
1628 };1664 };
...@@ -1761,7 +1797,7 @@ pub const Dir = struct {...@@ -1761,7 +1797,7 @@ pub const Dir = struct {
1761 sym_link_path: []const u8,1797 sym_link_path: []const u8,
1762 _: SymLinkFlags,1798 _: SymLinkFlags,
1763 ) !void {1799 ) !void {
1764 return os.symlinkatWasi(target_path, self.fd, sym_link_path);1800 return os.symlinkat(target_path, self.fd, sym_link_path);
1765 }1801 }
17661802
1767 /// Same as `symLink`, except the pathname parameters are null-terminated.1803 /// Same as `symLink`, except the pathname parameters are null-terminated.
...@@ -1807,7 +1843,7 @@ pub const Dir = struct {...@@ -1807,7 +1843,7 @@ pub const Dir = struct {
18071843
1808 /// WASI-only. Same as `readLink` except targeting WASI.1844 /// WASI-only. Same as `readLink` except targeting WASI.
1809 pub fn readLinkWasi(self: Dir, sub_path: []const u8, buffer: []u8) ![]u8 {1845 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);
1811 }1847 }
18121848
1813 /// Same as `readLink`, except the `pathname` parameter is null-terminated.1849 /// Same as `readLink`, except the `pathname` parameter is null-terminated.
...@@ -1870,6 +1906,7 @@ pub const Dir = struct {...@@ -1870,6 +1906,7 @@ pub const Dir = struct {
1870 }1906 }
18711907
1872 pub const DeleteTreeError = error{1908 pub const DeleteTreeError = error{
1909 InvalidHandle,
1873 AccessDenied,1910 AccessDenied,
1874 FileTooBig,1911 FileTooBig,
1875 SymLinkLoop,1912 SymLinkLoop,
...@@ -1935,6 +1972,7 @@ pub const Dir = struct {...@@ -1935,6 +1972,7 @@ pub const Dir = struct {
1935 continue :start_over;1972 continue :start_over;
1936 },1973 },
19371974
1975 error.InvalidHandle,
1938 error.AccessDenied,1976 error.AccessDenied,
1939 error.SymLinkLoop,1977 error.SymLinkLoop,
1940 error.ProcessFdQuotaExceeded,1978 error.ProcessFdQuotaExceeded,
...@@ -2002,6 +2040,7 @@ pub const Dir = struct {...@@ -2002,6 +2040,7 @@ pub const Dir = struct {
2002 continue :scan_dir;2040 continue :scan_dir;
2003 },2041 },
20042042
2043 error.InvalidHandle,
2005 error.AccessDenied,2044 error.AccessDenied,
2006 error.SymLinkLoop,2045 error.SymLinkLoop,
2007 error.ProcessFdQuotaExceeded,2046 error.ProcessFdQuotaExceeded,
...@@ -2272,8 +2311,6 @@ pub const Dir = struct {...@@ -2272,8 +2311,6 @@ pub const Dir = struct {
2272pub fn cwd() Dir {2311pub fn cwd() Dir {
2273 if (builtin.os.tag == .windows) {2312 if (builtin.os.tag == .windows) {
2274 return Dir{ .fd = os.windows.peb().ProcessParameters.CurrentDirectory.Handle };2313 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");
2277 } else {2314 } else {
2278 return Dir{ .fd = os.AT.FDCWD };2315 return Dir{ .fd = os.AT.FDCWD };
2279 }2316 }
...@@ -2285,26 +2322,17 @@ pub fn cwd() Dir {...@@ -2285,26 +2322,17 @@ pub fn cwd() Dir {
2285///2322///
2286/// Asserts that the path parameter has no null bytes.2323/// Asserts that the path parameter has no null bytes.
2287pub fn openDirAbsolute(absolute_path: []const u8, flags: Dir.OpenDirOptions) File.OpenError!Dir {2324pub 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 }
2291 assert(path.isAbsolute(absolute_path));2325 assert(path.isAbsolute(absolute_path));
2292 return cwd().openDir(absolute_path, flags);2326 return cwd().openDir(absolute_path, flags);
2293}2327}
22942328
2295/// Same as `openDirAbsolute` but the path parameter is null-terminated.2329/// Same as `openDirAbsolute` but the path parameter is null-terminated.
2296pub fn openDirAbsoluteZ(absolute_path_c: [*:0]const u8, flags: Dir.OpenDirOptions) File.OpenError!Dir {2330pub 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 }
2300 assert(path.isAbsoluteZ(absolute_path_c));2331 assert(path.isAbsoluteZ(absolute_path_c));
2301 return cwd().openDirZ(absolute_path_c, flags);2332 return cwd().openDirZ(absolute_path_c, flags);
2302}2333}
2303/// Same as `openDirAbsolute` but the path parameter is null-terminated.2334/// Same as `openDirAbsolute` but the path parameter is null-terminated.
2304pub fn openDirAbsoluteW(absolute_path_c: [*:0]const u16, flags: Dir.OpenDirOptions) File.OpenError!Dir {2335pub 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 }
2308 assert(path.isAbsoluteWindowsW(absolute_path_c));2336 assert(path.isAbsoluteWindowsW(absolute_path_c));
2309 return cwd().openDirW(absolute_path_c, flags);2337 return cwd().openDirW(absolute_path_c, flags);
2310}2338}
...@@ -2339,25 +2367,16 @@ pub fn openFileAbsoluteW(absolute_path_w: []const u16, flags: File.OpenFlags) Fi...@@ -2339,25 +2367,16 @@ pub fn openFileAbsoluteW(absolute_path_w: []const u16, flags: File.OpenFlags) Fi
2339/// open it and handle the error for file not found.2367/// open it and handle the error for file not found.
2340/// See `accessAbsoluteZ` for a function that accepts a null-terminated path.2368/// See `accessAbsoluteZ` for a function that accepts a null-terminated path.
2341pub fn accessAbsolute(absolute_path: []const u8, flags: File.OpenFlags) Dir.AccessError!void {2369pub 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 }
2345 assert(path.isAbsolute(absolute_path));2370 assert(path.isAbsolute(absolute_path));
2346 try cwd().access(absolute_path, flags);2371 try cwd().access(absolute_path, flags);
2347}2372}
2348/// Same as `accessAbsolute` but the path parameter is null-terminated.2373/// Same as `accessAbsolute` but the path parameter is null-terminated.
2349pub fn accessAbsoluteZ(absolute_path: [*:0]const u8, flags: File.OpenFlags) Dir.AccessError!void {2374pub 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 }
2353 assert(path.isAbsoluteZ(absolute_path));2375 assert(path.isAbsoluteZ(absolute_path));
2354 try cwd().accessZ(absolute_path, flags);2376 try cwd().accessZ(absolute_path, flags);
2355}2377}
2356/// Same as `accessAbsolute` but the path parameter is WTF-16 encoded.2378/// Same as `accessAbsolute` but the path parameter is WTF-16 encoded.
2357pub fn accessAbsoluteW(absolute_path: [*:0]const 16, flags: File.OpenFlags) Dir.AccessError!void {2379pub 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 }
2361 assert(path.isAbsoluteWindowsW(absolute_path));2380 assert(path.isAbsoluteWindowsW(absolute_path));
2362 try cwd().accessW(absolute_path, flags);2381 try cwd().accessW(absolute_path, flags);
2363}2382}
...@@ -2458,9 +2477,6 @@ pub const SymLinkFlags = struct {...@@ -2458,9 +2477,6 @@ pub const SymLinkFlags = struct {
2458/// If `sym_link_path` exists, it will not be overwritten.2477/// If `sym_link_path` exists, it will not be overwritten.
2459/// See also `symLinkAbsoluteZ` and `symLinkAbsoluteW`.2478/// See also `symLinkAbsoluteZ` and `symLinkAbsoluteW`.
2460pub fn symLinkAbsolute(target_path: []const u8, sym_link_path: []const u8, flags: SymLinkFlags) !void {2479pub 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 }
2464 assert(path.isAbsolute(target_path));2480 assert(path.isAbsolute(target_path));
2465 assert(path.isAbsolute(sym_link_path));2481 assert(path.isAbsolute(sym_link_path));
2466 if (builtin.os.tag == .windows) {2482 if (builtin.os.tag == .windows) {
lib/std/fs/path.zig+9-4
...@@ -8,6 +8,7 @@ const fmt = std.fmt;...@@ -8,6 +8,7 @@ const fmt = std.fmt;
8const Allocator = mem.Allocator;8const Allocator = mem.Allocator;
9const math = std.math;9const math = std.math;
10const windows = std.os.windows;10const windows = std.os.windows;
11const os = std.os;
11const fs = std.fs;12const fs = std.fs;
12const process = std.process;13const process = std.process;
13const native_os = builtin.target.os.tag;14const native_os = builtin.target.os.tag;
...@@ -733,7 +734,8 @@ pub fn resolvePosix(allocator: Allocator, paths: []const []const u8) ![]u8 {...@@ -733,7 +734,8 @@ pub fn resolvePosix(allocator: Allocator, paths: []const []const u8) ![]u8 {
733}734}
734735
735test "resolve" {736test "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
738 const cwd = try process.getCwdAlloc(testing.allocator);740 const cwd = try process.getCwdAlloc(testing.allocator);
739 defer testing.allocator.free(cwd);741 defer testing.allocator.free(cwd);
...@@ -753,7 +755,8 @@ test "resolveWindows" {...@@ -753,7 +755,8 @@ test "resolveWindows" {
753 // TODO https://github.com/ziglang/zig/issues/3288755 // TODO https://github.com/ziglang/zig/issues/3288
754 return error.SkipZigTest;756 return error.SkipZigTest;
755 }757 }
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");
757 if (native_os == .windows) {760 if (native_os == .windows) {
758 const cwd = try process.getCwdAlloc(testing.allocator);761 const cwd = try process.getCwdAlloc(testing.allocator);
759 defer testing.allocator.free(cwd);762 defer testing.allocator.free(cwd);
...@@ -798,7 +801,8 @@ test "resolveWindows" {...@@ -798,7 +801,8 @@ test "resolveWindows" {
798}801}
799802
800test "resolvePosix" {803test "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
803 try testResolvePosix(&[_][]const u8{ "/a/b", "c" }, "/a/b/c");807 try testResolvePosix(&[_][]const u8{ "/a/b", "c" }, "/a/b/c");
804 try testResolvePosix(&[_][]const u8{ "/a/b", "c", "//d", "e///" }, "/d/e");808 try testResolvePosix(&[_][]const u8{ "/a/b", "c", "//d", "e///" }, "/d/e");
...@@ -1211,7 +1215,8 @@ test "relative" {...@@ -1211,7 +1215,8 @@ test "relative" {
1211 // TODO https://github.com/ziglang/zig/issues/32881215 // TODO https://github.com/ziglang/zig/issues/3288
1212 return error.SkipZigTest;1216 return error.SkipZigTest;
1213 }1217 }
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
1216 try testRelativeWindows("c:/blah\\blah", "d:/games", "D:\\games");1221 try testRelativeWindows("c:/blah\\blah", "d:/games", "D:\\games");
1217 try testRelativeWindows("c:/aaaa/bbbb", "c:/aaaa", "..");1222 try testRelativeWindows("c:/aaaa/bbbb", "c:/aaaa", "..");
lib/std/fs/test.zig+15-7
...@@ -1,6 +1,7 @@...@@ -1,6 +1,7 @@
1const std = @import("../std.zig");1const std = @import("../std.zig");
2const builtin = @import("builtin");2const builtin = @import("builtin");
3const testing = std.testing;3const testing = std.testing;
4const os = std.os;
4const fs = std.fs;5const fs = std.fs;
5const mem = std.mem;6const mem = std.mem;
6const wasi = std.os.wasi;7const wasi = std.os.wasi;
...@@ -45,7 +46,8 @@ fn testReadLink(dir: Dir, target_path: []const u8, symlink_path: []const u8) !vo...@@ -45,7 +46,8 @@ fn testReadLink(dir: Dir, target_path: []const u8, symlink_path: []const u8) !vo
45}46}
4647
47test "accessAbsolute" {48test "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
50 var tmp = tmpDir(.{});52 var tmp = tmpDir(.{});
51 defer tmp.cleanup();53 defer tmp.cleanup();
...@@ -63,7 +65,8 @@ test "accessAbsolute" {...@@ -63,7 +65,8 @@ test "accessAbsolute" {
63}65}
6466
65test "openDirAbsolute" {67test "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
68 var tmp = tmpDir(.{});71 var tmp = tmpDir(.{});
69 defer tmp.cleanup();72 defer tmp.cleanup();
...@@ -99,7 +102,8 @@ test "openDir cwd parent .." {...@@ -99,7 +102,8 @@ test "openDir cwd parent .." {
99}102}
100103
101test "readLinkAbsolute" {104test "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
104 var tmp = tmpDir(.{});108 var tmp = tmpDir(.{});
105 defer tmp.cleanup();109 defer tmp.cleanup();
...@@ -507,7 +511,8 @@ test "rename" {...@@ -507,7 +511,8 @@ test "rename" {
507}511}
508512
509test "renameAbsolute" {513test "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
512 var tmp_dir = tmpDir(.{});517 var tmp_dir = tmpDir(.{});
513 defer tmp_dir.cleanup();518 defer tmp_dir.cleanup();
...@@ -941,7 +946,8 @@ test "open file with exclusive nonblocking lock twice (absolute paths)" {...@@ -941,7 +946,8 @@ test "open file with exclusive nonblocking lock twice (absolute paths)" {
941}946}
942947
943test "walker" {948test "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
946 var tmp = tmpDir(.{ .iterate = true });952 var tmp = tmpDir(.{ .iterate = true });
947 defer tmp.cleanup();953 defer tmp.cleanup();
...@@ -991,7 +997,8 @@ test "walker" {...@@ -991,7 +997,8 @@ test "walker" {
991}997}
992998
993test ". and .. in fs.Dir functions" {999test ". 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
996 var tmp = tmpDir(.{});1003 var tmp = tmpDir(.{});
997 defer tmp.cleanup();1004 defer tmp.cleanup();
...@@ -1019,7 +1026,8 @@ test ". and .. in fs.Dir functions" {...@@ -1019,7 +1026,8 @@ test ". and .. in fs.Dir functions" {
1019}1026}
10201027
1021test ". and .. in absolute functions" {1028test ". 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
1024 var tmp = tmpDir(.{});1032 var tmp = tmpDir(.{});
1025 defer tmp.cleanup();1033 defer tmp.cleanup();
lib/std/fs/wasi.zig+54-5
...@@ -25,13 +25,29 @@ pub const PreopenType = union(PreopenTypeTag) {...@@ -25,13 +25,29 @@ pub const PreopenType = union(PreopenTypeTag) {
25 const Self = @This();25 const Self = @This();
2626
27 pub fn eql(self: Self, other: PreopenType) bool {27 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
30 switch (self) {30 switch (self) {
31 PreopenTypeTag.Dir => |this_path| return mem.eql(u8, this_path, other.Dir),31 PreopenTypeTag.Dir => |this_path| return mem.eql(u8, this_path, other.Dir),
32 }32 }
33 }33 }
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
35 pub fn format(self: Self, comptime fmt: []const u8, options: std.fmt.FormatOptions, out_stream: anytype) !void {51 pub fn format(self: Self, comptime fmt: []const u8, options: std.fmt.FormatOptions, out_stream: anytype) !void {
36 _ = fmt;52 _ = fmt;
37 _ = options;53 _ = options;
...@@ -62,6 +78,15 @@ pub const Preopen = struct {...@@ -62,6 +78,15 @@ pub const Preopen = struct {
62 }78 }
63};79};
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
65/// Dynamically-sized array list of WASI preopens. This struct is a90/// Dynamically-sized array list of WASI preopens. This struct is a
66/// convenience wrapper for issuing `std.os.wasi.fd_prestat_get` and91/// convenience wrapper for issuing `std.os.wasi.fd_prestat_get` and
67/// `std.os.wasi.fd_prestat_dir_name` syscalls to the WASI runtime, and92/// `std.os.wasi.fd_prestat_dir_name` syscalls to the WASI runtime, and
...@@ -137,12 +162,38 @@ pub const PreopenList = struct {...@@ -137,12 +162,38 @@ pub const PreopenList = struct {
137 .SUCCESS => {},162 .SUCCESS => {},
138 else => |err| return os.unexpectedErrno(err),163 else => |err| return os.unexpectedErrno(err),
139 }164 }
165
140 const preopen = Preopen.new(fd, PreopenType{ .Dir = path_buf });166 const preopen = Preopen.new(fd, PreopenType{ .Dir = path_buf });
141 try self.buffer.append(preopen);167 try self.buffer.append(preopen);
142 fd = try math.add(fd_t, fd, 1);168 fd = try math.add(fd_t, fd, 1);
143 }169 }
144 }170 }
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
146 /// Find preopen by type. If the preopen exists, return it.197 /// Find preopen by type. If the preopen exists, return it.
147 /// Otherwise, return `null`.198 /// Otherwise, return `null`.
148 pub fn find(self: Self, preopen_type: PreopenType) ?*const Preopen {199 pub fn find(self: Self, preopen_type: PreopenType) ?*const Preopen {
...@@ -173,8 +224,6 @@ test "extracting WASI preopens" {...@@ -173,8 +224,6 @@ test "extracting WASI preopens" {
173224
174 try preopens.populate();225 try preopens.populate();
175226
176 try std.testing.expectEqual(@as(usize, 1), preopens.asSlice().len);227 const preopen = preopens.find(PreopenType{ .Dir = "/cwd" }) orelse unreachable;
177 const preopen = preopens.find(PreopenType{ .Dir = "." }) orelse unreachable;228 try std.testing.expect(preopen.@"type".eql(PreopenType{ .Dir = "/cwd" }));
178 try std.testing.expect(preopen.@"type".eql(PreopenType{ .Dir = "." }));
179 try std.testing.expectEqual(@as(i32, 3), preopen.fd);
180}229}
lib/std/os.zig+494-39
...@@ -21,9 +21,13 @@ const assert = std.debug.assert;...@@ -21,9 +21,13 @@ const assert = std.debug.assert;
21const math = std.math;21const math = std.math;
22const mem = std.mem;22const mem = std.mem;
23const elf = std.elf;23const elf = std.elf;
24const fs = std.fs;
24const dl = @import("dynamic_library.zig");25const dl = @import("dynamic_library.zig");
25const MAX_PATH_BYTES = std.fs.MAX_PATH_BYTES;26const MAX_PATH_BYTES = std.fs.MAX_PATH_BYTES;
26const is_windows = builtin.os.tag == .windows;27const is_windows = builtin.os.tag == .windows;
28const Allocator = std.mem.Allocator;
29const Preopen = std.fs.wasi.Preopen;
30const PreopenList = std.fs.wasi.PreopenList;
2731
28pub const darwin = std.c;32pub const darwin = std.c;
29pub const dragonfly = std.c;33pub const dragonfly = std.c;
...@@ -93,7 +97,12 @@ pub const MAX_ADDR_LEN = system.MAX_ADDR_LEN;...@@ -93,7 +97,12 @@ pub const MAX_ADDR_LEN = system.MAX_ADDR_LEN;
93pub const MMAP2_UNIT = system.MMAP2_UNIT;97pub const MMAP2_UNIT = system.MMAP2_UNIT;
94pub const MSG = system.MSG;98pub const MSG = system.MSG;
95pub const NAME_MAX = system.NAME_MAX;99pub 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};
97pub const PATH_MAX = system.PATH_MAX;106pub const PATH_MAX = system.PATH_MAX;
98pub const POLL = system.POLL;107pub const POLL = system.POLL;
99pub const POSIX_FADV = system.POSIX_FADV;108pub const POSIX_FADV = system.POSIX_FADV;
...@@ -210,6 +219,13 @@ pub const LOG = struct {...@@ -210,6 +219,13 @@ pub const LOG = struct {
210 pub const DEBUG = 7;219 pub const DEBUG = 7;
211};220};
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
213pub const socket_t = if (builtin.os.tag == .windows) windows.ws2_32.SOCKET else fd_t;229pub const socket_t = if (builtin.os.tag == .windows) windows.ws2_32.SOCKET else fd_t;
214230
215/// See also `getenv`. Populated by startup code before main().231/// 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...@@ -1239,6 +1255,9 @@ pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) PWriteError!usiz
1239}1255}
12401256
1241pub const OpenError = error{1257pub const OpenError = error{
1258 /// In WASI, this error may occur when the provided file handle is invalid.
1259 InvalidHandle,
1260
1242 /// In WASI, this error may occur when the file descriptor does1261 /// In WASI, this error may occur when the file descriptor does
1243 /// not hold the required rights to open a new resource relative to it.1262 /// not hold the required rights to open a new resource relative to it.
1244 AccessDenied,1263 AccessDenied,
...@@ -1300,6 +1319,8 @@ pub fn open(file_path: []const u8, flags: u32, perm: mode_t) OpenError!fd_t {...@@ -1300,6 +1319,8 @@ pub fn open(file_path: []const u8, flags: u32, perm: mode_t) OpenError!fd_t {
1300 if (builtin.os.tag == .windows) {1319 if (builtin.os.tag == .windows) {
1301 const file_path_w = try windows.sliceToPrefixedFileW(file_path);1320 const file_path_w = try windows.sliceToPrefixedFileW(file_path);
1302 return openW(file_path_w.span(), flags, perm);1321 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);
1303 }1324 }
1304 const file_path_c = try toPosixPath(file_path);1325 const file_path_c = try toPosixPath(file_path);
1305 return openZ(&file_path_c, flags, perm);1326 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...@@ -1311,6 +1332,8 @@ pub fn openZ(file_path: [*:0]const u8, flags: u32, perm: mode_t) OpenError!fd_t
1311 if (builtin.os.tag == .windows) {1332 if (builtin.os.tag == .windows) {
1312 const file_path_w = try windows.cStrToPrefixedFileW(file_path);1333 const file_path_w = try windows.cStrToPrefixedFileW(file_path);
1313 return openW(file_path_w.span(), flags, perm);1334 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);
1314 }1337 }
13151338
1316 const open_sym = if (builtin.os.tag == .linux and builtin.link_libc)1339 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...@@ -1347,7 +1370,7 @@ pub fn openZ(file_path: [*:0]const u8, flags: u32, perm: mode_t) OpenError!fd_t
1347 }1370 }
1348}1371}
13491372
1350fn openOptionsFromFlags(flags: u32) windows.OpenFileOptions {1373fn openOptionsFromFlagsWindows(flags: u32) windows.OpenFileOptions {
1351 const w = windows;1374 const w = windows;
13521375
1353 var access_mask: w.ULONG = w.READ_CONTROL | w.FILE_WRITE_ATTRIBUTES | w.SYNCHRONIZE;1376 var access_mask: w.ULONG = w.READ_CONTROL | w.FILE_WRITE_ATTRIBUTES | w.SYNCHRONIZE;
...@@ -1387,7 +1410,7 @@ fn openOptionsFromFlags(flags: u32) windows.OpenFileOptions {...@@ -1387,7 +1410,7 @@ fn openOptionsFromFlags(flags: u32) windows.OpenFileOptions {
1387/// or makes use of perm argument.1410/// or makes use of perm argument.
1388pub fn openW(file_path_w: []const u16, flags: u32, perm: mode_t) OpenError!fd_t {1411pub fn openW(file_path_w: []const u16, flags: u32, perm: mode_t) OpenError!fd_t {
1389 _ = perm;1412 _ = perm;
1390 var options = openOptionsFromFlags(flags);1413 var options = openOptionsFromFlagsWindows(flags);
1391 options.dir = std.fs.cwd().fd;1414 options.dir = std.fs.cwd().fd;
1392 return windows.OpenFile(file_path_w, options) catch |err| switch (err) {1415 return windows.OpenFile(file_path_w, options) catch |err| switch (err) {
1393 error.WouldBlock => unreachable,1416 error.WouldBlock => unreachable,
...@@ -1396,21 +1419,187 @@ pub fn openW(file_path_w: []const u16, flags: u32, perm: mode_t) OpenError!fd_t...@@ -1396,21 +1419,187 @@ pub fn openW(file_path_w: []const u16, flags: u32, perm: mode_t) OpenError!fd_t
1396 };1419 };
1397}1420}
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
1399/// Open and possibly create a file. Keeps trying if it gets interrupted.1523/// Open and possibly create a file. Keeps trying if it gets interrupted.
1400/// `file_path` is relative to the open directory handle `dir_fd`.1524/// `file_path` is relative to the open directory handle `dir_fd`.
1401/// See also `openatZ`.1525/// See also `openatZ`.
1402pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: mode_t) OpenError!fd_t {1526pub 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 }
1406 if (builtin.os.tag == .windows) {1527 if (builtin.os.tag == .windows) {
1407 const file_path_w = try windows.sliceToPrefixedFileW(file_path);1528 const file_path_w = try windows.sliceToPrefixedFileW(file_path);
1408 return openatW(dir_fd, file_path_w.span(), flags, mode);1529 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;
1409 }1550 }
1410 const file_path_c = try toPosixPath(file_path);1551 const file_path_c = try toPosixPath(file_path);
1411 return openatZ(dir_fd, &file_path_c, flags, mode);1552 return openatZ(dir_fd, &file_path_c, flags, mode);
1412}1553}
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
1414/// Open and possibly create a file in WASI.1603/// Open and possibly create a file in WASI.
1415pub 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 {1604pub 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 {
1416 while (true) {1605 while (true) {
...@@ -1450,6 +1639,8 @@ pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: mode_t)...@@ -1450,6 +1639,8 @@ pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: mode_t)
1450 if (builtin.os.tag == .windows) {1639 if (builtin.os.tag == .windows) {
1451 const file_path_w = try windows.cStrToPrefixedFileW(file_path);1640 const file_path_w = try windows.cStrToPrefixedFileW(file_path);
1452 return openatW(dir_fd, file_path_w.span(), flags, mode);1641 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);
1453 }1644 }
14541645
1455 const openat_sym = if (builtin.os.tag == .linux and builtin.link_libc)1646 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)...@@ -1496,7 +1687,7 @@ pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: mode_t)
1496/// or makes use of perm argument.1687/// or makes use of perm argument.
1497pub fn openatW(dir_fd: fd_t, file_path_w: []const u16, flags: u32, mode: mode_t) OpenError!fd_t {1688pub fn openatW(dir_fd: fd_t, file_path_w: []const u16, flags: u32, mode: mode_t) OpenError!fd_t {
1498 _ = mode;1689 _ = mode;
1499 var options = openOptionsFromFlags(flags);1690 var options = openOptionsFromFlagsWindows(flags);
1500 options.dir = dir_fd;1691 options.dir = dir_fd;
1501 return windows.OpenFile(file_path_w, options) catch |err| switch (err) {1692 return windows.OpenFile(file_path_w, options) catch |err| switch (err) {
1502 error.WouldBlock => unreachable,1693 error.WouldBlock => unreachable,
...@@ -1764,9 +1955,29 @@ pub const GetCwdError = error{...@@ -1764,9 +1955,29 @@ pub const GetCwdError = error{
1764pub fn getcwd(out_buffer: []u8) GetCwdError![]u8 {1955pub fn getcwd(out_buffer: []u8) GetCwdError![]u8 {
1765 if (builtin.os.tag == .windows) {1956 if (builtin.os.tag == .windows) {
1766 return windows.GetCurrentDirectory(out_buffer);1957 return windows.GetCurrentDirectory(out_buffer);
1767 }1958 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
1768 if (builtin.os.tag == .wasi and !builtin.link_libc) {1959 var allocator = std.heap.FixedBufferAllocator.init(out_buffer);
1769 @compileError("WASI doesn't have a concept of cwd(); use std.fs.wasi.PreopenList to get available Dir handles instead");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 }
1770 }1981 }
17711982
1772 const err = if (builtin.link_libc) blk: {1983 const err = if (builtin.link_libc) blk: {
...@@ -1809,11 +2020,10 @@ pub const SymLinkError = error{...@@ -1809,11 +2020,10 @@ pub const SymLinkError = error{
1809/// If `sym_link_path` exists, it will not be overwritten.2020/// If `sym_link_path` exists, it will not be overwritten.
1810/// See also `symlinkZ.2021/// See also `symlinkZ.
1811pub fn symlink(target_path: []const u8, sym_link_path: []const u8) SymLinkError!void {2022pub 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 }
1815 if (builtin.os.tag == .windows) {2023 if (builtin.os.tag == .windows) {
1816 @compileError("symlink is not supported on Windows; use std.os.windows.CreateSymbolicLink instead");2024 @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);
1817 }2027 }
1818 const target_path_c = try toPosixPath(target_path);2028 const target_path_c = try toPosixPath(target_path);
1819 const sym_link_path_c = try toPosixPath(sym_link_path);2029 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!...@@ -1825,6 +2035,8 @@ pub fn symlink(target_path: []const u8, sym_link_path: []const u8) SymLinkError!
1825pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLinkError!void {2035pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLinkError!void {
1826 if (builtin.os.tag == .windows) {2036 if (builtin.os.tag == .windows) {
1827 @compileError("symlink is not supported on Windows; use std.os.windows.CreateSymbolicLink instead");2037 @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));
1828 }2040 }
1829 switch (errno(system.symlink(target_path, sym_link_path))) {2041 switch (errno(system.symlink(target_path, sym_link_path))) {
1830 .SUCCESS => return,2042 .SUCCESS => return,
...@@ -1853,11 +2065,16 @@ pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLin...@@ -1853,11 +2065,16 @@ pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLin
1853/// If `sym_link_path` exists, it will not be overwritten.2065/// If `sym_link_path` exists, it will not be overwritten.
1854/// See also `symlinkatWasi`, `symlinkatZ` and `symlinkatW`.2066/// See also `symlinkatWasi`, `symlinkatZ` and `symlinkatW`.
1855pub fn symlinkat(target_path: []const u8, newdirfd: fd_t, sym_link_path: []const u8) SymLinkError!void {2067pub 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 }
1859 if (builtin.os.tag == .windows) {2068 if (builtin.os.tag == .windows) {
1860 @compileError("symlinkat is not supported on Windows; use std.os.windows.CreateSymbolicLink instead");2069 @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);
1861 }2078 }
1862 const target_path_c = try toPosixPath(target_path);2079 const target_path_c = try toPosixPath(target_path);
1863 const sym_link_path_c = try toPosixPath(sym_link_path);2080 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...@@ -1893,6 +2110,8 @@ pub fn symlinkatWasi(target_path: []const u8, newdirfd: fd_t, sym_link_path: []c
1893pub fn symlinkatZ(target_path: [*:0]const u8, newdirfd: fd_t, sym_link_path: [*:0]const u8) SymLinkError!void {2110pub fn symlinkatZ(target_path: [*:0]const u8, newdirfd: fd_t, sym_link_path: [*:0]const u8) SymLinkError!void {
1894 if (builtin.os.tag == .windows) {2111 if (builtin.os.tag == .windows) {
1895 @compileError("symlinkat is not supported on Windows; use std.os.windows.CreateSymbolicLink instead");2112 @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));
1896 }2115 }
1897 switch (errno(system.symlinkat(target_path, newdirfd, sym_link_path))) {2116 switch (errno(system.symlinkat(target_path, newdirfd, sym_link_path))) {
1898 .SUCCESS => return,2117 .SUCCESS => return,
...@@ -1930,6 +2149,9 @@ pub const LinkError = UnexpectedError || error{...@@ -1930,6 +2149,9 @@ pub const LinkError = UnexpectedError || error{
1930};2149};
19312150
1932pub fn linkZ(oldpath: [*:0]const u8, newpath: [*:0]const u8, flags: i32) LinkError!void {2151pub 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 }
1933 switch (errno(system.link(oldpath, newpath, flags))) {2155 switch (errno(system.link(oldpath, newpath, flags))) {
1934 .SUCCESS => return,2156 .SUCCESS => return,
1935 .ACCES => return error.AccessDenied,2157 .ACCES => return error.AccessDenied,
...@@ -1952,6 +2174,12 @@ pub fn linkZ(oldpath: [*:0]const u8, newpath: [*:0]const u8, flags: i32) LinkErr...@@ -1952,6 +2174,12 @@ pub fn linkZ(oldpath: [*:0]const u8, newpath: [*:0]const u8, flags: i32) LinkErr
1952}2174}
19532175
1954pub fn link(oldpath: []const u8, newpath: []const u8, flags: i32) LinkError!void {2176pub 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 }
1955 const old = try toPosixPath(oldpath);2183 const old = try toPosixPath(oldpath);
1956 const new = try toPosixPath(newpath);2184 const new = try toPosixPath(newpath);
1957 return try linkZ(&old, &new, flags);2185 return try linkZ(&old, &new, flags);
...@@ -1966,6 +2194,9 @@ pub fn linkatZ(...@@ -1966,6 +2194,9 @@ pub fn linkatZ(
1966 newpath: [*:0]const u8,2194 newpath: [*:0]const u8,
1967 flags: i32,2195 flags: i32,
1968) LinkatError!void {2196) 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 }
1969 switch (errno(system.linkat(olddir, oldpath, newdir, newpath, flags))) {2200 switch (errno(system.linkat(olddir, oldpath, newdir, newpath, flags))) {
1970 .SUCCESS => return,2201 .SUCCESS => return,
1971 .ACCES => return error.AccessDenied,2202 .ACCES => return error.AccessDenied,
...@@ -1995,11 +2226,62 @@ pub fn linkat(...@@ -1995,11 +2226,62 @@ pub fn linkat(
1995 newpath: []const u8,2226 newpath: []const u8,
1996 flags: i32,2227 flags: i32,
1997) LinkatError!void {2228) 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 }
1998 const old = try toPosixPath(oldpath);2251 const old = try toPosixPath(oldpath);
1999 const new = try toPosixPath(newpath);2252 const new = try toPosixPath(newpath);
2000 return try linkatZ(olddir, &old, newdir, &new, flags);2253 return try linkatZ(olddir, &old, newdir, &new, flags);
2001}2254}
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
2003pub const UnlinkError = error{2285pub const UnlinkError = error{
2004 FileNotFound,2286 FileNotFound,
20052287
...@@ -2027,7 +2309,10 @@ pub const UnlinkError = error{...@@ -2027,7 +2309,10 @@ pub const UnlinkError = error{
2027/// See also `unlinkZ`.2309/// See also `unlinkZ`.
2028pub fn unlink(file_path: []const u8) UnlinkError!void {2310pub fn unlink(file_path: []const u8) UnlinkError!void {
2029 if (builtin.os.tag == .wasi and !builtin.link_libc) {2311 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 };
2031 } else if (builtin.os.tag == .windows) {2316 } else if (builtin.os.tag == .windows) {
2032 const file_path_w = try windows.sliceToPrefixedFileW(file_path);2317 const file_path_w = try windows.sliceToPrefixedFileW(file_path);
2033 return unlinkW(file_path_w.span());2318 return unlinkW(file_path_w.span());
...@@ -2042,6 +2327,8 @@ pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void {...@@ -2042,6 +2327,8 @@ pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void {
2042 if (builtin.os.tag == .windows) {2327 if (builtin.os.tag == .windows) {
2043 const file_path_w = try windows.cStrToPrefixedFileW(file_path);2328 const file_path_w = try windows.cStrToPrefixedFileW(file_path);
2044 return unlinkW(file_path_w.span());2329 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));
2045 }2332 }
2046 switch (errno(system.unlink(file_path))) {2333 switch (errno(system.unlink(file_path))) {
2047 .SUCCESS => return,2334 .SUCCESS => return,
...@@ -2079,6 +2366,12 @@ pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!vo...@@ -2079,6 +2366,12 @@ pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!vo
2079 const file_path_w = try windows.sliceToPrefixedFileW(file_path);2366 const file_path_w = try windows.sliceToPrefixedFileW(file_path);
2080 return unlinkatW(dirfd, file_path_w.span(), flags);2367 return unlinkatW(dirfd, file_path_w.span(), flags);
2081 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {2368 } 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 }
2082 return unlinkatWasi(dirfd, file_path, flags);2375 return unlinkatWasi(dirfd, file_path, flags);
2083 } else {2376 } else {
2084 const file_path_c = try toPosixPath(file_path);2377 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...@@ -2123,6 +2416,8 @@ pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatEr
2123 if (builtin.os.tag == .windows) {2416 if (builtin.os.tag == .windows) {
2124 const file_path_w = try windows.cStrToPrefixedFileW(file_path_c);2417 const file_path_w = try windows.cStrToPrefixedFileW(file_path_c);
2125 return unlinkatW(dirfd, file_path_w.span(), flags);2418 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);
2126 }2421 }
2127 switch (errno(system.unlinkat(dirfd, file_path_c, flags))) {2422 switch (errno(system.unlinkat(dirfd, file_path_c, flags))) {
2128 .SUCCESS => return,2423 .SUCCESS => return,
...@@ -2181,7 +2476,7 @@ pub const RenameError = error{...@@ -2181,7 +2476,7 @@ pub const RenameError = error{
2181/// Change the name or location of a file.2476/// Change the name or location of a file.
2182pub fn rename(old_path: []const u8, new_path: []const u8) RenameError!void {2477pub fn rename(old_path: []const u8, new_path: []const u8) RenameError!void {
2183 if (builtin.os.tag == .wasi and !builtin.link_libc) {2478 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);
2185 } else if (builtin.os.tag == .windows) {2480 } else if (builtin.os.tag == .windows) {
2186 const old_path_w = try windows.sliceToPrefixedFileW(old_path);2481 const old_path_w = try windows.sliceToPrefixedFileW(old_path);
2187 const new_path_w = try windows.sliceToPrefixedFileW(new_path);2482 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...@@ -2199,6 +2494,8 @@ pub fn renameZ(old_path: [*:0]const u8, new_path: [*:0]const u8) RenameError!voi
2199 const old_path_w = try windows.cStrToPrefixedFileW(old_path);2494 const old_path_w = try windows.cStrToPrefixedFileW(old_path);
2200 const new_path_w = try windows.cStrToPrefixedFileW(new_path);2495 const new_path_w = try windows.cStrToPrefixedFileW(new_path);
2201 return renameW(old_path_w.span().ptr, new_path_w.span().ptr);2496 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));
2202 }2499 }
2203 switch (errno(system.rename(old_path, new_path))) {2500 switch (errno(system.rename(old_path, new_path))) {
2204 .SUCCESS => return,2501 .SUCCESS => return,
...@@ -2243,7 +2540,25 @@ pub fn renameat(...@@ -2243,7 +2540,25 @@ pub fn renameat(
2243 const new_path_w = try windows.sliceToPrefixedFileW(new_path);2540 const new_path_w = try windows.sliceToPrefixedFileW(new_path);
2244 return renameatW(old_dir_fd, old_path_w.span(), new_dir_fd, new_path_w.span(), windows.TRUE);2541 return renameatW(old_dir_fd, old_path_w.span(), new_dir_fd, new_path_w.span(), windows.TRUE);
2245 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {2542 } 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);
2247 } else {2562 } else {
2248 const old_path_c = try toPosixPath(old_path);2563 const old_path_c = try toPosixPath(old_path);
2249 const new_path_c = try toPosixPath(new_path);2564 const new_path_c = try toPosixPath(new_path);
...@@ -2253,8 +2568,8 @@ pub fn renameat(...@@ -2253,8 +2568,8 @@ pub fn renameat(
22532568
2254/// WASI-only. Same as `renameat` expect targeting WASI.2569/// WASI-only. Same as `renameat` expect targeting WASI.
2255/// See also `renameat`.2570/// 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 {2571pub fn renameatWasi(old: RelativePath, new: RelativePath) 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)) {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)) {
2258 .SUCCESS => return,2573 .SUCCESS => return,
2259 .ACCES => return error.AccessDenied,2574 .ACCES => return error.AccessDenied,
2260 .PERM => return error.AccessDenied,2575 .PERM => return error.AccessDenied,
...@@ -2290,6 +2605,8 @@ pub fn renameatZ(...@@ -2290,6 +2605,8 @@ pub fn renameatZ(
2290 const old_path_w = try windows.cStrToPrefixedFileW(old_path);2605 const old_path_w = try windows.cStrToPrefixedFileW(old_path);
2291 const new_path_w = try windows.cStrToPrefixedFileW(new_path);2606 const new_path_w = try windows.cStrToPrefixedFileW(new_path);
2292 return renameatW(old_dir_fd, old_path_w.span(), new_dir_fd, new_path_w.span(), windows.TRUE);2607 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));
2293 }2610 }
22942611
2295 switch (errno(system.renameat(old_dir_fd, old_path, new_dir_fd, new_path))) {2612 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...@@ -2380,6 +2697,12 @@ pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!v
2380 const sub_dir_path_w = try windows.sliceToPrefixedFileW(sub_dir_path);2697 const sub_dir_path_w = try windows.sliceToPrefixedFileW(sub_dir_path);
2381 return mkdiratW(dir_fd, sub_dir_path_w.span(), mode);2698 return mkdiratW(dir_fd, sub_dir_path_w.span(), mode);
2382 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {2699 } 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 }
2383 return mkdiratWasi(dir_fd, sub_dir_path, mode);2706 return mkdiratWasi(dir_fd, sub_dir_path, mode);
2384 } else {2707 } else {
2385 const sub_dir_path_c = try toPosixPath(sub_dir_path);2708 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...@@ -2414,6 +2737,8 @@ pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirErr
2414 if (builtin.os.tag == .windows) {2737 if (builtin.os.tag == .windows) {
2415 const sub_dir_path_w = try windows.cStrToPrefixedFileW(sub_dir_path);2738 const sub_dir_path_w = try windows.cStrToPrefixedFileW(sub_dir_path);
2416 return mkdiratW(dir_fd, sub_dir_path_w.span().ptr, mode);2739 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);
2417 }2742 }
2418 switch (errno(system.mkdirat(dir_fd, sub_dir_path, mode))) {2743 switch (errno(system.mkdirat(dir_fd, sub_dir_path, mode))) {
2419 .SUCCESS => return,2744 .SUCCESS => return,
...@@ -2472,10 +2797,10 @@ pub const MakeDirError = error{...@@ -2472,10 +2797,10 @@ pub const MakeDirError = error{
2472} || UnexpectedError;2797} || UnexpectedError;
24732798
2474/// Create a directory.2799/// Create a directory.
2475/// `mode` is ignored on Windows.2800/// `mode` is ignored on Windows and WASI.
2476pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void {2801pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void {
2477 if (builtin.os.tag == .wasi and !builtin.link_libc) {2802 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);
2479 } else if (builtin.os.tag == .windows) {2804 } else if (builtin.os.tag == .windows) {
2480 const dir_path_w = try windows.sliceToPrefixedFileW(dir_path);2805 const dir_path_w = try windows.sliceToPrefixedFileW(dir_path);
2481 return mkdirW(dir_path_w.span(), mode);2806 return mkdirW(dir_path_w.span(), mode);
...@@ -2490,6 +2815,8 @@ pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void {...@@ -2490,6 +2815,8 @@ pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void {
2490 if (builtin.os.tag == .windows) {2815 if (builtin.os.tag == .windows) {
2491 const dir_path_w = try windows.cStrToPrefixedFileW(dir_path);2816 const dir_path_w = try windows.cStrToPrefixedFileW(dir_path);
2492 return mkdirW(dir_path_w.span(), mode);2817 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);
2493 }2820 }
2494 switch (errno(system.mkdir(dir_path, mode))) {2821 switch (errno(system.mkdir(dir_path, mode))) {
2495 .SUCCESS => return,2822 .SUCCESS => return,
...@@ -2545,7 +2872,11 @@ pub const DeleteDirError = error{...@@ -2545,7 +2872,11 @@ pub const DeleteDirError = error{
2545/// Deletes an empty directory.2872/// Deletes an empty directory.
2546pub fn rmdir(dir_path: []const u8) DeleteDirError!void {2873pub fn rmdir(dir_path: []const u8) DeleteDirError!void {
2547 if (builtin.os.tag == .wasi and !builtin.link_libc) {2874 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 };
2549 } else if (builtin.os.tag == .windows) {2880 } else if (builtin.os.tag == .windows) {
2550 const dir_path_w = try windows.sliceToPrefixedFileW(dir_path);2881 const dir_path_w = try windows.sliceToPrefixedFileW(dir_path);
2551 return rmdirW(dir_path_w.span());2882 return rmdirW(dir_path_w.span());
...@@ -2560,6 +2891,8 @@ pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void {...@@ -2560,6 +2891,8 @@ pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void {
2560 if (builtin.os.tag == .windows) {2891 if (builtin.os.tag == .windows) {
2561 const dir_path_w = try windows.cStrToPrefixedFileW(dir_path);2892 const dir_path_w = try windows.cStrToPrefixedFileW(dir_path);
2562 return rmdirW(dir_path_w.span());2893 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));
2563 }2896 }
2564 switch (errno(system.rmdir(dir_path))) {2897 switch (errno(system.rmdir(dir_path))) {
2565 .SUCCESS => return,2898 .SUCCESS => return,
...@@ -2606,7 +2939,17 @@ pub const ChangeCurDirError = error{...@@ -2606,7 +2939,17 @@ pub const ChangeCurDirError = error{
2606/// `dir_path` is recommended to be a UTF-8 encoded string.2939/// `dir_path` is recommended to be a UTF-8 encoded string.
2607pub fn chdir(dir_path: []const u8) ChangeCurDirError!void {2940pub fn chdir(dir_path: []const u8) ChangeCurDirError!void {
2608 if (builtin.os.tag == .wasi and !builtin.link_libc) {2941 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;
2610 } else if (builtin.os.tag == .windows) {2953 } else if (builtin.os.tag == .windows) {
2611 var utf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined;2954 var utf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined;
2612 const len = try std.unicode.utf8ToUtf16Le(utf16_dir_path[0..], dir_path);2955 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 {...@@ -2625,6 +2968,8 @@ pub fn chdirZ(dir_path: [*:0]const u8) ChangeCurDirError!void {
2625 const len = try std.unicode.utf8ToUtf16Le(utf16_dir_path[0..], dir_path);2968 const len = try std.unicode.utf8ToUtf16Le(utf16_dir_path[0..], dir_path);
2626 if (len > utf16_dir_path.len) return error.NameTooLong;2969 if (len > utf16_dir_path.len) return error.NameTooLong;
2627 return chdirW(utf16_dir_path[0..len]);2970 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));
2628 }2973 }
2629 switch (errno(system.chdir(dir_path))) {2974 switch (errno(system.chdir(dir_path))) {
2630 .SUCCESS => return,2975 .SUCCESS => return,
...@@ -2655,15 +3000,29 @@ pub const FchdirError = error{...@@ -2655,15 +3000,29 @@ pub const FchdirError = error{
2655} || UnexpectedError;3000} || UnexpectedError;
26563001
2657pub fn fchdir(dirfd: fd_t) FchdirError!void {3002pub fn fchdir(dirfd: fd_t) FchdirError!void {
2658 while (true) {3003 if (builtin.os.tag == .wasi) {
2659 switch (errno(system.fchdir(dirfd))) {3004 // Check that this is a directory
2660 .SUCCESS => return,3005 const dirinfo = fstatat(dirfd, ".", 0) catch unreachable;
2661 .ACCES => return error.AccessDenied,3006 if (dirinfo.filetype != .DIRECTORY) {
2662 .BADF => unreachable,3007 return error.NotDir;
2663 .NOTDIR => return error.NotDir,3008 }
2664 .INTR => continue,3009
2665 .IO => return error.FileSystem,3010 wasi_cwd.cwd = .{
2666 else => |err| return unexpectedErrno(err),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 }
2667 }3026 }
2668 }3027 }
2669}3028}
...@@ -2690,7 +3049,7 @@ pub const ReadLinkError = error{...@@ -2690,7 +3049,7 @@ pub const ReadLinkError = error{
2690/// The return value is a slice of `out_buffer` from index 0.3049/// The return value is a slice of `out_buffer` from index 0.
2691pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {3050pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
2692 if (builtin.os.tag == .wasi and !builtin.link_libc) {3051 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);
2694 } else if (builtin.os.tag == .windows) {3053 } else if (builtin.os.tag == .windows) {
2695 const file_path_w = try windows.sliceToPrefixedFileW(file_path);3054 const file_path_w = try windows.sliceToPrefixedFileW(file_path);
2696 return readlinkW(file_path_w.span(), out_buffer);3055 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...@@ -2711,6 +3070,8 @@ pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8
2711 if (builtin.os.tag == .windows) {3070 if (builtin.os.tag == .windows) {
2712 const file_path_w = try windows.cStrToWin32PrefixedFileW(file_path);3071 const file_path_w = try windows.cStrToWin32PrefixedFileW(file_path);
2713 return readlinkW(file_path_w.span(), out_buffer);3072 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);
2714 }3075 }
2715 const rc = system.readlink(file_path, out_buffer.ptr, out_buffer.len);3076 const rc = system.readlink(file_path, out_buffer.ptr, out_buffer.len);
2716 switch (errno(rc)) {3077 switch (errno(rc)) {
...@@ -2733,6 +3094,12 @@ pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8...@@ -2733,6 +3094,12 @@ pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8
2733/// See also `readlinkatWasi`, `realinkatZ` and `realinkatW`.3094/// See also `readlinkatWasi`, `realinkatZ` and `realinkatW`.
2734pub fn readlinkat(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {3095pub fn readlinkat(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
2735 if (builtin.os.tag == .wasi and !builtin.link_libc) {3096 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 }
2736 return readlinkatWasi(dirfd, file_path, out_buffer);3103 return readlinkatWasi(dirfd, file_path, out_buffer);
2737 }3104 }
2738 if (builtin.os.tag == .windows) {3105 if (builtin.os.tag == .windows) {
...@@ -2775,6 +3142,8 @@ pub fn readlinkatZ(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) Read...@@ -2775,6 +3142,8 @@ pub fn readlinkatZ(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) Read
2775 if (builtin.os.tag == .windows) {3142 if (builtin.os.tag == .windows) {
2776 const file_path_w = try windows.cStrToPrefixedFileW(file_path);3143 const file_path_w = try windows.cStrToPrefixedFileW(file_path);
2777 return readlinkatW(dirfd, file_path_w.span(), out_buffer);3144 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);
2778 }3147 }
2779 const rc = system.readlinkat(dirfd, file_path, out_buffer.ptr, out_buffer.len);3148 const rc = system.readlinkat(dirfd, file_path, out_buffer.ptr, out_buffer.len);
2780 switch (errno(rc)) {3149 switch (errno(rc)) {
...@@ -3727,7 +4096,14 @@ pub const FStatAtError = FStatError || error{ NameTooLong, FileNotFound, SymLink...@@ -3727,7 +4096,14 @@ pub const FStatAtError = FStatError || error{ NameTooLong, FileNotFound, SymLink
3727/// See also `fstatatZ` and `fstatatWasi`.4096/// See also `fstatatZ` and `fstatatWasi`.
3728pub fn fstatat(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!Stat {4097pub fn fstatat(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!Stat {
3729 if (builtin.os.tag == .wasi and !builtin.link_libc) {4098 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);
3731 } else if (builtin.os.tag == .windows) {4107 } else if (builtin.os.tag == .windows) {
3732 @compileError("fstatat is not yet implemented on Windows");4108 @compileError("fstatat is not yet implemented on Windows");
3733 } else {4109 } else {
...@@ -3758,6 +4134,10 @@ pub fn fstatatWasi(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!S...@@ -3758,6 +4134,10 @@ pub fn fstatatWasi(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!S
3758/// Same as `fstatat` but `pathname` is null-terminated.4134/// Same as `fstatat` but `pathname` is null-terminated.
3759/// See also `fstatat`.4135/// See also `fstatat`.
3760pub fn fstatatZ(dirfd: fd_t, pathname: [*:0]const u8, flags: u32) FStatAtError!Stat {4136pub 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
3761 const fstatat_sym = if (builtin.os.tag == .linux and builtin.link_libc)4141 const fstatat_sym = if (builtin.os.tag == .linux and builtin.link_libc)
3762 system.fstatat644142 system.fstatat64
3763 else4143 else
...@@ -4056,6 +4436,8 @@ pub fn access(path: []const u8, mode: u32) AccessError!void {...@@ -4056,6 +4436,8 @@ pub fn access(path: []const u8, mode: u32) AccessError!void {
4056 const path_w = try windows.sliceToPrefixedFileW(path);4436 const path_w = try windows.sliceToPrefixedFileW(path);
4057 _ = try windows.GetFileAttributesW(path_w.span().ptr);4437 _ = try windows.GetFileAttributesW(path_w.span().ptr);
4058 return;4438 return;
4439 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
4440 return faccessat(wasi.AT.FDCWD, path, mode, 0);
4059 }4441 }
4060 const path_c = try toPosixPath(path);4442 const path_c = try toPosixPath(path);
4061 return accessZ(&path_c, mode);4443 return accessZ(&path_c, mode);
...@@ -4067,6 +4449,8 @@ pub fn accessZ(path: [*:0]const u8, mode: u32) AccessError!void {...@@ -4067,6 +4449,8 @@ pub fn accessZ(path: [*:0]const u8, mode: u32) AccessError!void {
4067 const path_w = try windows.cStrToPrefixedFileW(path);4449 const path_w = try windows.cStrToPrefixedFileW(path);
4068 _ = try windows.GetFileAttributesW(path_w.span().ptr);4450 _ = try windows.GetFileAttributesW(path_w.span().ptr);
4069 return;4451 return;
4452 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
4453 return access(mem.sliceTo(path, 0), mode);
4070 }4454 }
4071 switch (errno(system.access(path, mode))) {4455 switch (errno(system.access(path, mode))) {
4072 .SUCCESS => return,4456 .SUCCESS => return,
...@@ -4108,6 +4492,45 @@ pub fn faccessat(dirfd: fd_t, path: []const u8, mode: u32, flags: u32) AccessErr...@@ -4108,6 +4492,45 @@ pub fn faccessat(dirfd: fd_t, path: []const u8, mode: u32, flags: u32) AccessErr
4108 if (builtin.os.tag == .windows) {4492 if (builtin.os.tag == .windows) {
4109 const path_w = try windows.sliceToPrefixedFileW(path);4493 const path_w = try windows.sliceToPrefixedFileW(path);
4110 return faccessatW(dirfd, path_w.span().ptr, mode, flags);4494 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;
4111 }4534 }
4112 const path_c = try toPosixPath(path);4535 const path_c = try toPosixPath(path);
4113 return faccessatZ(dirfd, &path_c, mode, flags);4536 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...@@ -4118,6 +4541,8 @@ pub fn faccessatZ(dirfd: fd_t, path: [*:0]const u8, mode: u32, flags: u32) Acces
4118 if (builtin.os.tag == .windows) {4541 if (builtin.os.tag == .windows) {
4119 const path_w = try windows.cStrToPrefixedFileW(path);4542 const path_w = try windows.cStrToPrefixedFileW(path);
4120 return faccessatW(dirfd, path_w.span().ptr, mode, flags);4543 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);
4121 }4546 }
4122 switch (errno(system.faccessat(dirfd, path, mode, flags))) {4547 switch (errno(system.faccessat(dirfd, path, mode, flags))) {
4123 .SUCCESS => return,4548 .SUCCESS => return,
...@@ -4645,6 +5070,9 @@ pub const RealPathError = error{...@@ -4645,6 +5070,9 @@ pub const RealPathError = error{
4645 SharingViolation,5070 SharingViolation,
4646 PipeBusy,5071 PipeBusy,
46475072
5073 /// On WASI, the current CWD may not be associated with an absolute path.
5074 InvalidHandle,
5075
4648 /// On Windows, file paths must be valid Unicode.5076 /// On Windows, file paths must be valid Unicode.
4649 InvalidUtf8,5077 InvalidUtf8,
46505078
...@@ -4660,9 +5088,33 @@ pub fn realpath(pathname: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathE...@@ -4660,9 +5088,33 @@ pub fn realpath(pathname: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathE
4660 if (builtin.os.tag == .windows) {5088 if (builtin.os.tag == .windows) {
4661 const pathname_w = try windows.sliceToPrefixedFileW(pathname);5089 const pathname_w = try windows.sliceToPrefixedFileW(pathname);
4662 return realpathW(pathname_w.span(), out_buffer);5090 return realpathW(pathname_w.span(), out_buffer);
4663 }5091 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
4664 if (builtin.os.tag == .wasi and !builtin.link_libc) {5092 // NOTE: This emulation is incomplete. Symbolic links are not
4665 @compileError("Use std.fs.wasi.PreopenList to obtain valid Dir handles instead of using absolute paths");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 }
4666 }5118 }
4667 const pathname_c = try toPosixPath(pathname);5119 const pathname_c = try toPosixPath(pathname);
4668 return realpathZ(&pathname_c, out_buffer);5120 return realpathZ(&pathname_c, out_buffer);
...@@ -4673,6 +5125,8 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP...@@ -4673,6 +5125,8 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP
4673 if (builtin.os.tag == .windows) {5125 if (builtin.os.tag == .windows) {
4674 const pathname_w = try windows.cStrToPrefixedFileW(pathname);5126 const pathname_w = try windows.cStrToPrefixedFileW(pathname);
4675 return realpathW(pathname_w.span(), out_buffer);5127 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);
4676 }5130 }
4677 if (!builtin.link_libc) {5131 if (!builtin.link_libc) {
4678 const flags = if (builtin.os.tag == .linux) O.PATH | O.NONBLOCK | O.CLOEXEC else O.NONBLOCK | O.CLOEXEC;5132 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...@@ -4680,6 +5134,7 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP
4680 error.FileLocksNotSupported => unreachable,5134 error.FileLocksNotSupported => unreachable,
4681 error.WouldBlock => unreachable,5135 error.WouldBlock => unreachable,
4682 error.FileBusy => unreachable, // not asking for write permissions5136 error.FileBusy => unreachable, // not asking for write permissions
5137 error.InvalidHandle => unreachable, // WASI-only
4683 else => |e| return e,5138 else => |e| return e,
4684 };5139 };
4685 defer close(fd);5140 defer close(fd);
lib/std/os/test.zig+123-10
...@@ -22,7 +22,7 @@ const Dir = std.fs.Dir;...@@ -22,7 +22,7 @@ const Dir = std.fs.Dir;
22const ArenaAllocator = std.heap.ArenaAllocator;22const ArenaAllocator = std.heap.ArenaAllocator;
2323
24test "chdir smoke test" {24test "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
27 // Get current working directory path27 // Get current working directory path
28 var old_cwd_buf: [fs.MAX_PATH_BYTES]u8 = undefined;28 var old_cwd_buf: [fs.MAX_PATH_BYTES]u8 = undefined;
...@@ -48,7 +48,8 @@ test "chdir smoke test" {...@@ -48,7 +48,8 @@ test "chdir smoke test" {
48}48}
4949
50test "open smoke test" {50test "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
53 // TODO verify file attributes using `fstat`54 // TODO verify file attributes using `fstat`
5455
...@@ -102,7 +103,8 @@ test "open smoke test" {...@@ -102,7 +103,8 @@ test "open smoke test" {
102}103}
103104
104test "openat smoke test" {105test "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
107 // TODO verify file attributes using `fstatat`109 // TODO verify file attributes using `fstatat`
108110
...@@ -138,7 +140,8 @@ test "openat smoke test" {...@@ -138,7 +140,8 @@ test "openat smoke test" {
138}140}
139141
140test "symlink with relative paths" {142test "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
143 const cwd = fs.cwd();146 const cwd = fs.cwd();
144 cwd.deleteFile("file.txt") catch {};147 cwd.deleteFile("file.txt") catch {};
...@@ -190,6 +193,13 @@ fn testReadlink(target_path: []const u8, symlink_path: []const u8) !void {...@@ -190,6 +193,13 @@ fn testReadlink(target_path: []const u8, symlink_path: []const u8) !void {
190193
191test "link with relative paths" {194test "link with relative paths" {
192 switch (native_os) {195 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 },
193 .linux, .solaris => {},203 .linux, .solaris => {},
194 else => return error.SkipZigTest,204 else => return error.SkipZigTest,
195 }205 }
...@@ -212,14 +222,14 @@ test "link with relative paths" {...@@ -212,14 +222,14 @@ test "link with relative paths" {
212 const nstat = try os.fstat(nfd.handle);222 const nstat = try os.fstat(nfd.handle);
213223
214 try testing.expectEqual(estat.ino, nstat.ino);224 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);
216 }226 }
217227
218 try os.unlink("new.txt");228 try os.unlink("new.txt");
219229
220 {230 {
221 const estat = try os.fstat(efd.handle);231 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);
223 }233 }
224234
225 try cwd.deleteFile("example.txt");235 try cwd.deleteFile("example.txt");
...@@ -227,6 +237,7 @@ test "link with relative paths" {...@@ -227,6 +237,7 @@ test "link with relative paths" {
227237
228test "linkat with different directories" {238test "linkat with different directories" {
229 switch (native_os) {239 switch (native_os) {
240 .wasi => if (!builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/cwd"),
230 .linux, .solaris => {},241 .linux, .solaris => {},
231 else => return error.SkipZigTest,242 else => return error.SkipZigTest,
232 }243 }
...@@ -250,14 +261,14 @@ test "linkat with different directories" {...@@ -250,14 +261,14 @@ test "linkat with different directories" {
250 const nstat = try os.fstat(nfd.handle);261 const nstat = try os.fstat(nfd.handle);
251262
252 try testing.expectEqual(estat.ino, nstat.ino);263 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);
254 }265 }
255266
256 try os.unlinkat(tmp.dir.fd, "new.txt", 0);267 try os.unlinkat(tmp.dir.fd, "new.txt", 0);
257268
258 {269 {
259 const estat = try os.fstat(efd.handle);270 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);
261 }272 }
262273
263 try cwd.deleteFile("example.txt");274 try cwd.deleteFile("example.txt");
...@@ -388,8 +399,6 @@ test "getrandom" {...@@ -388,8 +399,6 @@ test "getrandom" {
388}399}
389400
390test "getcwd" {401test "getcwd" {
391 if (native_os == .wasi) return error.SkipZigTest;
392
393 // at least call it so it gets compiled402 // at least call it so it gets compiled
394 var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;403 var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
395 _ = os.getcwd(&buf) catch undefined;404 _ = os.getcwd(&buf) catch undefined;
...@@ -878,3 +887,107 @@ test "POSIX file locking with fcntl" {...@@ -878,3 +887,107 @@ test "POSIX file locking with fcntl" {
878 try expect(result.status == 0 * 256);887 try expect(result.status == 0 * 256);
879 }888 }
880}889}
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 {...@@ -15,6 +15,11 @@ comptime {
15 // assert(@alignOf(u64) == 8);15 // assert(@alignOf(u64) == 8);
16}16}
1717
18pub const F_OK = 0;
19pub const X_OK = 1;
20pub const W_OK = 2;
21pub const R_OK = 4;
22
18pub const iovec_t = std.os.iovec;23pub const iovec_t = std.os.iovec;
19pub const ciovec_t = std.os.iovec_const;24pub const ciovec_t = std.os.iovec_const;
2025
lib/std/testing.zig+2-2
...@@ -327,8 +327,8 @@ fn getCwdOrWasiPreopen() std.fs.Dir {...@@ -327,8 +327,8 @@ fn getCwdOrWasiPreopen() std.fs.Dir {
327 defer preopens.deinit();327 defer preopens.deinit();
328 preopens.populate() catch328 preopens.populate() catch
329 @panic("unable to make tmp dir for testing: unable to populate preopens");329 @panic("unable to make tmp dir for testing: unable to populate preopens");
330 const preopen = preopens.find(std.fs.wasi.PreopenType{ .Dir = "." }) orelse330 const preopen = preopens.find(std.fs.wasi.PreopenType{ .Dir = "/cwd" }) orelse
331 @panic("unable to make tmp dir for testing: didn't find '.' in the preopens");331 @panic("unable to make tmp dir for testing: didn't find '/cwd' in the preopens");
332332
333 return std.fs.Dir{ .fd = preopen.fd };333 return std.fs.Dir{ .fd = preopen.fd };
334 } else {334 } else {
lib/std/zig/system/NativeTargetInfo.zig+2
...@@ -369,6 +369,7 @@ fn detectAbiAndDynamicLinker(...@@ -369,6 +369,7 @@ fn detectAbiAndDynamicLinker(
369369
370 error.IsDir,370 error.IsDir,
371 error.NotDir,371 error.NotDir,
372 error.InvalidHandle,
372 error.AccessDenied,373 error.AccessDenied,
373 error.NoDevice,374 error.NoDevice,
374 error.FileNotFound,375 error.FileNotFound,
...@@ -670,6 +671,7 @@ pub fn abiAndDynamicLinkerFromFile(...@@ -670,6 +671,7 @@ pub fn abiAndDynamicLinkerFromFile(
670671
671 error.FileNotFound,672 error.FileNotFound,
672 error.NotDir,673 error.NotDir,
674 error.InvalidHandle,
673 error.AccessDenied,675 error.AccessDenied,
674 error.NoDevice,676 error.NoDevice,
675 => continue,677 => continue,
src/test.zig+1
...@@ -1188,6 +1188,7 @@ pub const TestContext = struct {...@@ -1188,6 +1188,7 @@ pub const TestContext = struct {
1188 .wasmtime => |wasmtime_bin_name| if (enable_wasmtime) {1188 .wasmtime => |wasmtime_bin_name| if (enable_wasmtime) {
1189 try argv.append(wasmtime_bin_name);1189 try argv.append(wasmtime_bin_name);
1190 try argv.append("--dir=.");1190 try argv.append("--dir=.");
1191 try argv.append("--mapdir=/cwd::.");
1191 try argv.append(exe_path);1192 try argv.append(exe_path);
1192 } else {1193 } else {
1193 return; // wasmtime not available; pass test.1194 return; // wasmtime not available; pass test.