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

Merge pull request #11021 from topolarity/wasi-cwd

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

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

lib/std/c/wasi.zig+5-5
...@@ -62,21 +62,21 @@ pub const Stat = extern struct {...@@ -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, ".");
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, ".");
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, ".");
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, ".");
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, ".");
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, ".");
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, ".");
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, ".");
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, ".");
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, ".");
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, ".");
10231031
1024 var tmp = tmpDir(.{});1032 var tmp = tmpDir(.{});
1025 defer tmp.cleanup();1033 defer tmp.cleanup();
lib/std/fs/wasi.zig+63-3
...@@ -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,49 @@ pub const PreopenList = struct {...@@ -137,12 +162,49 @@ 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
197 /// Find preopen by fd. If the preopen exists, return it.
198 /// Otherwise, return `null`.
199 pub fn findByFd(self: Self, fd: fd_t) ?Preopen {
200 for (self.buffer.items) |preopen| {
201 if (preopen.fd == fd) {
202 return preopen;
203 }
204 }
205 return null;
206 }
207
146 /// Find preopen by type. If the preopen exists, return it.208 /// Find preopen by type. If the preopen exists, return it.
147 /// Otherwise, return `null`.209 /// Otherwise, return `null`.
148 pub fn find(self: Self, preopen_type: PreopenType) ?*const Preopen {210 pub fn find(self: Self, preopen_type: PreopenType) ?*const Preopen {
...@@ -173,8 +235,6 @@ test "extracting WASI preopens" {...@@ -173,8 +235,6 @@ test "extracting WASI preopens" {
173235
174 try preopens.populate();236 try preopens.populate();
175237
176 try std.testing.expectEqual(@as(usize, 1), preopens.asSlice().len);
177 const preopen = preopens.find(PreopenType{ .Dir = "." }) orelse unreachable;238 const preopen = preopens.find(PreopenType{ .Dir = "." }) orelse unreachable;
178 try std.testing.expect(preopen.@"type".eql(PreopenType{ .Dir = "." }));239 try std.testing.expect(preopen.@"type".eql(PreopenType{ .Dir = "." }));
179 try std.testing.expectEqual(@as(i32, 3), preopen.fd);
180}240}
lib/std/os.zig+511-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,17 @@ pub const LOG = struct {...@@ -210,6 +219,17 @@ pub const LOG = struct {
210 pub const DEBUG = 7;219 pub const DEBUG = 7;
211};220};
212221
222/// An fd-relative file path
223///
224/// This is currently only used for WASI-specific functionality, but the concept
225/// is the same as the dirfd/pathname pairs in the `*at(...)` POSIX functions.
226pub const RelativePathWasi = struct {
227 /// Handle to directory
228 dir_fd: fd_t,
229 /// Path to resource within `dir_fd`.
230 relative_path: []const u8,
231};
232
213pub const socket_t = if (builtin.os.tag == .windows) windows.ws2_32.SOCKET else fd_t;233pub const socket_t = if (builtin.os.tag == .windows) windows.ws2_32.SOCKET else fd_t;
214234
215/// See also `getenv`. Populated by startup code before main().235/// See also `getenv`. Populated by startup code before main().
...@@ -1239,6 +1259,9 @@ pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) PWriteError!usiz...@@ -1239,6 +1259,9 @@ pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) PWriteError!usiz
1239}1259}
12401260
1241pub const OpenError = error{1261pub const OpenError = error{
1262 /// In WASI, this error may occur when the provided file handle is invalid.
1263 InvalidHandle,
1264
1242 /// In WASI, this error may occur when the file descriptor does1265 /// 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.1266 /// not hold the required rights to open a new resource relative to it.
1244 AccessDenied,1267 AccessDenied,
...@@ -1300,6 +1323,8 @@ pub fn open(file_path: []const u8, flags: u32, perm: mode_t) OpenError!fd_t {...@@ -1300,6 +1323,8 @@ pub fn open(file_path: []const u8, flags: u32, perm: mode_t) OpenError!fd_t {
1300 if (builtin.os.tag == .windows) {1323 if (builtin.os.tag == .windows) {
1301 const file_path_w = try windows.sliceToPrefixedFileW(file_path);1324 const file_path_w = try windows.sliceToPrefixedFileW(file_path);
1302 return openW(file_path_w.span(), flags, perm);1325 return openW(file_path_w.span(), flags, perm);
1326 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
1327 return openat(wasi.AT.FDCWD, file_path, flags, perm);
1303 }1328 }
1304 const file_path_c = try toPosixPath(file_path);1329 const file_path_c = try toPosixPath(file_path);
1305 return openZ(&file_path_c, flags, perm);1330 return openZ(&file_path_c, flags, perm);
...@@ -1311,6 +1336,8 @@ pub fn openZ(file_path: [*:0]const u8, flags: u32, perm: mode_t) OpenError!fd_t...@@ -1311,6 +1336,8 @@ pub fn openZ(file_path: [*:0]const u8, flags: u32, perm: mode_t) OpenError!fd_t
1311 if (builtin.os.tag == .windows) {1336 if (builtin.os.tag == .windows) {
1312 const file_path_w = try windows.cStrToPrefixedFileW(file_path);1337 const file_path_w = try windows.cStrToPrefixedFileW(file_path);
1313 return openW(file_path_w.span(), flags, perm);1338 return openW(file_path_w.span(), flags, perm);
1339 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
1340 return open(mem.sliceTo(file_path, 0), flags, perm);
1314 }1341 }
13151342
1316 const open_sym = if (builtin.os.tag == .linux and builtin.link_libc)1343 const open_sym = if (builtin.os.tag == .linux and builtin.link_libc)
...@@ -1347,7 +1374,7 @@ pub fn openZ(file_path: [*:0]const u8, flags: u32, perm: mode_t) OpenError!fd_t...@@ -1347,7 +1374,7 @@ pub fn openZ(file_path: [*:0]const u8, flags: u32, perm: mode_t) OpenError!fd_t
1347 }1374 }
1348}1375}
13491376
1350fn openOptionsFromFlags(flags: u32) windows.OpenFileOptions {1377fn openOptionsFromFlagsWindows(flags: u32) windows.OpenFileOptions {
1351 const w = windows;1378 const w = windows;
13521379
1353 var access_mask: w.ULONG = w.READ_CONTROL | w.FILE_WRITE_ATTRIBUTES | w.SYNCHRONIZE;1380 var access_mask: w.ULONG = w.READ_CONTROL | w.FILE_WRITE_ATTRIBUTES | w.SYNCHRONIZE;
...@@ -1387,7 +1414,7 @@ fn openOptionsFromFlags(flags: u32) windows.OpenFileOptions {...@@ -1387,7 +1414,7 @@ fn openOptionsFromFlags(flags: u32) windows.OpenFileOptions {
1387/// or makes use of perm argument.1414/// or makes use of perm argument.
1388pub fn openW(file_path_w: []const u16, flags: u32, perm: mode_t) OpenError!fd_t {1415pub fn openW(file_path_w: []const u16, flags: u32, perm: mode_t) OpenError!fd_t {
1389 _ = perm;1416 _ = perm;
1390 var options = openOptionsFromFlags(flags);1417 var options = openOptionsFromFlagsWindows(flags);
1391 options.dir = std.fs.cwd().fd;1418 options.dir = std.fs.cwd().fd;
1392 return windows.OpenFile(file_path_w, options) catch |err| switch (err) {1419 return windows.OpenFile(file_path_w, options) catch |err| switch (err) {
1393 error.WouldBlock => unreachable,1420 error.WouldBlock => unreachable,
...@@ -1396,21 +1423,204 @@ pub fn openW(file_path_w: []const u16, flags: u32, perm: mode_t) OpenError!fd_t...@@ -1396,21 +1423,204 @@ pub fn openW(file_path_w: []const u16, flags: u32, perm: mode_t) OpenError!fd_t
1396 };1423 };
1397}1424}
13981425
1426var wasi_cwd = if (builtin.os.tag == .wasi and !builtin.link_libc) struct {
1427 // List of available Preopens
1428 preopens: ?PreopenList = null,
1429 // Memory buffer for storing the relative portion of the CWD
1430 path_buffer: [MAX_PATH_BYTES]u8 = undefined,
1431 // Current Working Directory, stored as an fd_t and a relative path
1432 cwd: ?RelativePathWasi = null,
1433 // Preopen associated with `cwd`, if any
1434 cwd_preopen: ?Preopen = null,
1435}{} else undefined;
1436
1437/// Initialize the available Preopen list on WASI and set the CWD to `cwd_init`.
1438/// Note that `cwd_init` corresponds to a Preopen directory, not necessarily
1439/// a POSIX path. For example, "." matches a Preopen provided with `--dir=.`
1440///
1441/// This must be called before using any relative or absolute paths with `std.os`
1442/// functions, if you are on WASI without linking libc.
1443///
1444/// `alloc` must not be a temporary or leak-detecting allocator, since `std.os`
1445/// retains ownership of allocations internally and may never call free().
1446pub fn initPreopensWasi(alloc: Allocator, cwd_init: ?[]const u8) !void {
1447 if (builtin.os.tag == .wasi) {
1448 if (!builtin.link_libc) {
1449 if (wasi_cwd.preopens == null) {
1450 var preopen_list = PreopenList.init(alloc);
1451 try preopen_list.populate();
1452 wasi_cwd.preopens = preopen_list;
1453 }
1454 if (cwd_init) |cwd| {
1455 const preopen = wasi_cwd.preopens.?.findContaining(.{ .Dir = cwd });
1456 if (preopen) |po| {
1457 wasi_cwd.cwd_preopen = po.base;
1458 wasi_cwd.cwd = RelativePathWasi{
1459 .dir_fd = po.base.fd,
1460 .relative_path = po.relative_path,
1461 };
1462 } else {
1463 // No matching preopen found
1464 return error.FileNotFound;
1465 }
1466 }
1467 } else {
1468 if (cwd_init) |cwd| try chdir(cwd);
1469 }
1470 }
1471}
1472
1473/// Resolve a relative or absolute path to an handle (`fd_t`) and a relative subpath.
1474///
1475/// For absolute paths, this automatically searches among available Preopens to find
1476/// a match. For relative paths, it uses the "emulated" CWD.
1477pub fn resolvePathWasi(path: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) !RelativePathWasi {
1478 // Note: Due to WASI's "sandboxed" file handles, operations with this RelativePathWasi
1479 // will fail if the relative path navigates outside of `dir_fd` using ".."
1480 return resolvePathAndGetWasiPreopen(path, null, out_buffer);
1481}
1482
1483fn resolvePathAndGetWasiPreopen(path: []const u8, preopen: ?*?Preopen, out_buffer: *[MAX_PATH_BYTES]u8) !RelativePathWasi {
1484 var allocator = std.heap.FixedBufferAllocator.init(out_buffer);
1485 var alloc = allocator.allocator();
1486
1487 if (fs.path.isAbsolute(path) or wasi_cwd.cwd == null) {
1488 if (wasi_cwd.preopens == null) @panic("On WASI, `initPreopensWasi` must be called to initialize preopens " ++
1489 "before using any CWD-relative or absolute paths.\n");
1490
1491 if (mem.startsWith(u8, path, "/preopens/fd/")) {
1492 // "/preopens/fd/<N>" is a special prefix, which refers to a Preopen directly by fd
1493 const fd_start = "/preopens/fd/".len;
1494 const fd_end = mem.indexOfScalarPos(u8, path, fd_start, '/') orelse path.len;
1495 const fd = std.fmt.parseUnsigned(fd_t, path[fd_start..fd_end], 10) catch unreachable;
1496 const rel_path = if (path.len > fd_end + 1) path[fd_end + 1 ..] else ".";
1497
1498 if (preopen) |p| p.* = wasi_cwd.preopens.?.findByFd(fd);
1499 return RelativePathWasi{
1500 .dir_fd = fd,
1501 .relative_path = alloc.dupe(u8, rel_path) catch return error.NameTooLong,
1502 };
1503 }
1504
1505 // For any other absolute path, we need to lookup a containing Preopen
1506 const abs_path = fs.path.resolve(alloc, &.{ "/", path }) catch return error.NameTooLong;
1507 const preopen_uri = wasi_cwd.preopens.?.findContaining(.{ .Dir = abs_path });
1508
1509 if (preopen_uri) |po| {
1510 if (preopen) |p| p.* = po.base;
1511 return RelativePathWasi{
1512 .dir_fd = po.base.fd,
1513 .relative_path = po.relative_path,
1514 };
1515 } else {
1516 // No matching preopen found
1517 return error.AccessDenied;
1518 }
1519 } else {
1520 const cwd = wasi_cwd.cwd.?;
1521
1522 // If the path is empty or "." or "./", return CWD
1523 if (std.mem.eql(u8, path, ".") or std.mem.eql(u8, path, "./")) {
1524 return cwd;
1525 }
1526
1527 // First resolve a combined path, where the "/" corresponds to `cwd.dir_fd`
1528 // not the true filesystem root
1529 const paths = &.{ "/", cwd.relative_path, path };
1530 const resolved_path = fs.path.resolve(alloc, paths) catch return error.NameTooLong;
1531
1532 // Strip off the fake root to get the relative path w.r.t. `cwd.dir_fd`
1533 const resolved_relative_path = resolved_path[1..];
1534
1535 if (preopen) |p| p.* = wasi_cwd.cwd_preopen;
1536 return RelativePathWasi{
1537 .dir_fd = cwd.dir_fd,
1538 .relative_path = resolved_relative_path,
1539 };
1540 }
1541}
1542
1399/// Open and possibly create a file. Keeps trying if it gets interrupted.1543/// Open and possibly create a file. Keeps trying if it gets interrupted.
1400/// `file_path` is relative to the open directory handle `dir_fd`.1544/// `file_path` is relative to the open directory handle `dir_fd`.
1401/// See also `openatZ`.1545/// See also `openatZ`.
1402pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: mode_t) OpenError!fd_t {1546pub 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) {1547 if (builtin.os.tag == .windows) {
1407 const file_path_w = try windows.sliceToPrefixedFileW(file_path);1548 const file_path_w = try windows.sliceToPrefixedFileW(file_path);
1408 return openatW(dir_fd, file_path_w.span(), flags, mode);1549 return openatW(dir_fd, file_path_w.span(), flags, mode);
1550 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
1551 // `mode` is ignored on WASI, which does not support unix-style file permissions
1552 const fd = if (dir_fd == wasi.AT.FDCWD or fs.path.isAbsolute(file_path)) blk: {
1553 // Resolve absolute or CWD-relative paths to a path within a Preopen
1554 var path_buf: [MAX_PATH_BYTES]u8 = undefined;
1555 const path = try resolvePathWasi(file_path, &path_buf);
1556
1557 const opts = try openOptionsFromFlagsWasi(path.dir_fd, flags);
1558 break :blk try openatWasi(path.dir_fd, path.relative_path, opts.lookup_flags, opts.oflags, opts.fs_flags, opts.fs_rights_base, opts.fs_rights_inheriting);
1559 } else blk: {
1560 const opts = try openOptionsFromFlagsWasi(dir_fd, flags);
1561 break :blk try openatWasi(dir_fd, file_path, opts.lookup_flags, opts.oflags, opts.fs_flags, opts.fs_rights_base, opts.fs_rights_inheriting);
1562 };
1563 errdefer close(fd);
1564
1565 const info = try fstat(fd);
1566 if (flags & O.WRONLY != 0 and info.filetype == .DIRECTORY)
1567 return error.IsDir;
1568
1569 return fd;
1409 }1570 }
1410 const file_path_c = try toPosixPath(file_path);1571 const file_path_c = try toPosixPath(file_path);
1411 return openatZ(dir_fd, &file_path_c, flags, mode);1572 return openatZ(dir_fd, &file_path_c, flags, mode);
1412}1573}
14131574
1575/// A struct to contain all lookup/rights flags accepted by `wasi.path_open`
1576const WasiOpenOptions = struct {
1577 oflags: wasi.oflags_t,
1578 lookup_flags: wasi.lookupflags_t,
1579 fs_rights_base: wasi.rights_t,
1580 fs_rights_inheriting: wasi.rights_t,
1581 fs_flags: wasi.fdflags_t,
1582};
1583
1584/// Compute rights + flags corresponding to the provided POSIX access mode.
1585fn openOptionsFromFlagsWasi(fd: fd_t, oflag: u32) OpenError!WasiOpenOptions {
1586 const w = std.os.wasi;
1587
1588 // First, discover the rights that we can derive from `fd`
1589 var fsb_cur: wasi.fdstat_t = undefined;
1590 _ = switch (w.fd_fdstat_get(fd, &fsb_cur)) {
1591 .SUCCESS => .{},
1592 .BADF => return error.InvalidHandle,
1593 else => |err| return unexpectedErrno(err),
1594 };
1595
1596 // Next, calculate the read/write rights to request, depending on the
1597 // provided POSIX access mode
1598 var rights: w.rights_t = 0;
1599 if (oflag & O.RDONLY != 0) {
1600 rights |= w.RIGHT.FD_READ | w.RIGHT.FD_READDIR;
1601 }
1602 if (oflag & O.WRONLY != 0) {
1603 rights |= w.RIGHT.FD_DATASYNC | w.RIGHT.FD_WRITE |
1604 w.RIGHT.FD_ALLOCATE | w.RIGHT.FD_FILESTAT_SET_SIZE;
1605 }
1606
1607 // Request all other rights unconditionally
1608 rights |= ~(w.RIGHT.FD_DATASYNC | w.RIGHT.FD_READ |
1609 w.RIGHT.FD_WRITE | w.RIGHT.FD_ALLOCATE |
1610 w.RIGHT.FD_READDIR | w.RIGHT.FD_FILESTAT_SET_SIZE);
1611
1612 // But only take rights that we can actually inherit
1613 rights &= fsb_cur.fs_rights_inheriting;
1614
1615 return WasiOpenOptions{
1616 .oflags = @truncate(w.oflags_t, (oflag >> 12)) & 0xfff,
1617 .lookup_flags = if (oflag & O.NOFOLLOW == 0) w.LOOKUP_SYMLINK_FOLLOW else 0,
1618 .fs_rights_base = rights,
1619 .fs_rights_inheriting = fsb_cur.fs_rights_inheriting,
1620 .fs_flags = @truncate(w.fdflags_t, oflag & 0xfff),
1621 };
1622}
1623
1414/// Open and possibly create a file in WASI.1624/// 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 {1625pub 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) {1626 while (true) {
...@@ -1450,6 +1660,8 @@ pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: mode_t)...@@ -1450,6 +1660,8 @@ pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: mode_t)
1450 if (builtin.os.tag == .windows) {1660 if (builtin.os.tag == .windows) {
1451 const file_path_w = try windows.cStrToPrefixedFileW(file_path);1661 const file_path_w = try windows.cStrToPrefixedFileW(file_path);
1452 return openatW(dir_fd, file_path_w.span(), flags, mode);1662 return openatW(dir_fd, file_path_w.span(), flags, mode);
1663 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
1664 return openat(dir_fd, mem.sliceTo(file_path, 0), flags, mode);
1453 }1665 }
14541666
1455 const openat_sym = if (builtin.os.tag == .linux and builtin.link_libc)1667 const openat_sym = if (builtin.os.tag == .linux and builtin.link_libc)
...@@ -1496,7 +1708,7 @@ pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: mode_t)...@@ -1496,7 +1708,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.1708/// 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 {1709pub fn openatW(dir_fd: fd_t, file_path_w: []const u16, flags: u32, mode: mode_t) OpenError!fd_t {
1498 _ = mode;1710 _ = mode;
1499 var options = openOptionsFromFlags(flags);1711 var options = openOptionsFromFlagsWindows(flags);
1500 options.dir = dir_fd;1712 options.dir = dir_fd;
1501 return windows.OpenFile(file_path_w, options) catch |err| switch (err) {1713 return windows.OpenFile(file_path_w, options) catch |err| switch (err) {
1502 error.WouldBlock => unreachable,1714 error.WouldBlock => unreachable,
...@@ -1764,9 +1976,15 @@ pub const GetCwdError = error{...@@ -1764,9 +1976,15 @@ pub const GetCwdError = error{
1764pub fn getcwd(out_buffer: []u8) GetCwdError![]u8 {1976pub fn getcwd(out_buffer: []u8) GetCwdError![]u8 {
1765 if (builtin.os.tag == .windows) {1977 if (builtin.os.tag == .windows) {
1766 return windows.GetCurrentDirectory(out_buffer);1978 return windows.GetCurrentDirectory(out_buffer);
1767 }1979 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
1768 if (builtin.os.tag == .wasi and !builtin.link_libc) {1980 var buf: [MAX_PATH_BYTES]u8 = undefined;
1769 @compileError("WASI doesn't have a concept of cwd(); use std.fs.wasi.PreopenList to get available Dir handles instead");1981 const path = realpathWasi(".", &buf) catch |err| switch (err) {
1982 error.NameTooLong => return error.NameTooLong,
1983 error.InvalidHandle => return error.CurrentWorkingDirectoryUnlinked,
1984 };
1985 if (out_buffer.len < path.len) return error.NameTooLong;
1986 std.mem.copy(u8, out_buffer, path);
1987 return out_buffer[0..path.len];
1770 }1988 }
17711989
1772 const err = if (builtin.link_libc) blk: {1990 const err = if (builtin.link_libc) blk: {
...@@ -1809,11 +2027,10 @@ pub const SymLinkError = error{...@@ -1809,11 +2027,10 @@ pub const SymLinkError = error{
1809/// If `sym_link_path` exists, it will not be overwritten.2027/// If `sym_link_path` exists, it will not be overwritten.
1810/// See also `symlinkZ.2028/// See also `symlinkZ.
1811pub fn symlink(target_path: []const u8, sym_link_path: []const u8) SymLinkError!void {2029pub 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) {2030 if (builtin.os.tag == .windows) {
1816 @compileError("symlink is not supported on Windows; use std.os.windows.CreateSymbolicLink instead");2031 @compileError("symlink is not supported on Windows; use std.os.windows.CreateSymbolicLink instead");
2032 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
2033 return symlinkat(target_path, wasi.AT.FDCWD, sym_link_path);
1817 }2034 }
1818 const target_path_c = try toPosixPath(target_path);2035 const target_path_c = try toPosixPath(target_path);
1819 const sym_link_path_c = try toPosixPath(sym_link_path);2036 const sym_link_path_c = try toPosixPath(sym_link_path);
...@@ -1825,6 +2042,8 @@ pub fn symlink(target_path: []const u8, sym_link_path: []const u8) SymLinkError!...@@ -1825,6 +2042,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 {2042pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLinkError!void {
1826 if (builtin.os.tag == .windows) {2043 if (builtin.os.tag == .windows) {
1827 @compileError("symlink is not supported on Windows; use std.os.windows.CreateSymbolicLink instead");2044 @compileError("symlink is not supported on Windows; use std.os.windows.CreateSymbolicLink instead");
2045 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
2046 return symlink(mem.sliceTo(target_path, 0), mem.sliceTo(sym_link_path, 0));
1828 }2047 }
1829 switch (errno(system.symlink(target_path, sym_link_path))) {2048 switch (errno(system.symlink(target_path, sym_link_path))) {
1830 .SUCCESS => return,2049 .SUCCESS => return,
...@@ -1853,11 +2072,16 @@ pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLin...@@ -1853,11 +2072,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.2072/// If `sym_link_path` exists, it will not be overwritten.
1854/// See also `symlinkatWasi`, `symlinkatZ` and `symlinkatW`.2073/// See also `symlinkatWasi`, `symlinkatZ` and `symlinkatW`.
1855pub fn symlinkat(target_path: []const u8, newdirfd: fd_t, sym_link_path: []const u8) SymLinkError!void {2074pub 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) {2075 if (builtin.os.tag == .windows) {
1860 @compileError("symlinkat is not supported on Windows; use std.os.windows.CreateSymbolicLink instead");2076 @compileError("symlinkat is not supported on Windows; use std.os.windows.CreateSymbolicLink instead");
2077 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
2078 if (newdirfd == wasi.AT.FDCWD or fs.path.isAbsolute(target_path)) {
2079 // Resolve absolute or CWD-relative paths to a path within a Preopen
2080 var path_buf: [MAX_PATH_BYTES]u8 = undefined;
2081 const path = try resolvePathWasi(sym_link_path, &path_buf);
2082 return symlinkatWasi(target_path, path.dir_fd, path.relative_path);
2083 }
2084 return symlinkatWasi(target_path, newdirfd, sym_link_path);
1861 }2085 }
1862 const target_path_c = try toPosixPath(target_path);2086 const target_path_c = try toPosixPath(target_path);
1863 const sym_link_path_c = try toPosixPath(sym_link_path);2087 const sym_link_path_c = try toPosixPath(sym_link_path);
...@@ -1893,6 +2117,8 @@ pub fn symlinkatWasi(target_path: []const u8, newdirfd: fd_t, sym_link_path: []c...@@ -1893,6 +2117,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 {2117pub fn symlinkatZ(target_path: [*:0]const u8, newdirfd: fd_t, sym_link_path: [*:0]const u8) SymLinkError!void {
1894 if (builtin.os.tag == .windows) {2118 if (builtin.os.tag == .windows) {
1895 @compileError("symlinkat is not supported on Windows; use std.os.windows.CreateSymbolicLink instead");2119 @compileError("symlinkat is not supported on Windows; use std.os.windows.CreateSymbolicLink instead");
2120 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
2121 return symlinkat(mem.sliceTo(target_path, 0), newdirfd, mem.sliceTo(sym_link_path, 0));
1896 }2122 }
1897 switch (errno(system.symlinkat(target_path, newdirfd, sym_link_path))) {2123 switch (errno(system.symlinkat(target_path, newdirfd, sym_link_path))) {
1898 .SUCCESS => return,2124 .SUCCESS => return,
...@@ -1930,6 +2156,9 @@ pub const LinkError = UnexpectedError || error{...@@ -1930,6 +2156,9 @@ pub const LinkError = UnexpectedError || error{
1930};2156};
19312157
1932pub fn linkZ(oldpath: [*:0]const u8, newpath: [*:0]const u8, flags: i32) LinkError!void {2158pub fn linkZ(oldpath: [*:0]const u8, newpath: [*:0]const u8, flags: i32) LinkError!void {
2159 if (builtin.os.tag == .wasi and !builtin.link_libc) {
2160 return link(mem.sliceTo(oldpath, 0), mem.sliceTo(newpath, 0), flags);
2161 }
1933 switch (errno(system.link(oldpath, newpath, flags))) {2162 switch (errno(system.link(oldpath, newpath, flags))) {
1934 .SUCCESS => return,2163 .SUCCESS => return,
1935 .ACCES => return error.AccessDenied,2164 .ACCES => return error.AccessDenied,
...@@ -1952,6 +2181,12 @@ pub fn linkZ(oldpath: [*:0]const u8, newpath: [*:0]const u8, flags: i32) LinkErr...@@ -1952,6 +2181,12 @@ pub fn linkZ(oldpath: [*:0]const u8, newpath: [*:0]const u8, flags: i32) LinkErr
1952}2181}
19532182
1954pub fn link(oldpath: []const u8, newpath: []const u8, flags: i32) LinkError!void {2183pub fn link(oldpath: []const u8, newpath: []const u8, flags: i32) LinkError!void {
2184 if (builtin.os.tag == .wasi and !builtin.link_libc) {
2185 return linkat(wasi.AT.FDCWD, oldpath, wasi.AT.FDCWD, newpath, flags) catch |err| switch (err) {
2186 error.NotDir => unreachable, // link() does not support directories
2187 else => |e| return e,
2188 };
2189 }
1955 const old = try toPosixPath(oldpath);2190 const old = try toPosixPath(oldpath);
1956 const new = try toPosixPath(newpath);2191 const new = try toPosixPath(newpath);
1957 return try linkZ(&old, &new, flags);2192 return try linkZ(&old, &new, flags);
...@@ -1966,6 +2201,9 @@ pub fn linkatZ(...@@ -1966,6 +2201,9 @@ pub fn linkatZ(
1966 newpath: [*:0]const u8,2201 newpath: [*:0]const u8,
1967 flags: i32,2202 flags: i32,
1968) LinkatError!void {2203) LinkatError!void {
2204 if (builtin.os.tag == .wasi and !builtin.link_libc) {
2205 return linkat(olddir, mem.sliceTo(oldpath, 0), newdir, mem.sliceTo(newpath, 0), flags);
2206 }
1969 switch (errno(system.linkat(olddir, oldpath, newdir, newpath, flags))) {2207 switch (errno(system.linkat(olddir, oldpath, newdir, newpath, flags))) {
1970 .SUCCESS => return,2208 .SUCCESS => return,
1971 .ACCES => return error.AccessDenied,2209 .ACCES => return error.AccessDenied,
...@@ -1995,11 +2233,62 @@ pub fn linkat(...@@ -1995,11 +2233,62 @@ pub fn linkat(
1995 newpath: []const u8,2233 newpath: []const u8,
1996 flags: i32,2234 flags: i32,
1997) LinkatError!void {2235) LinkatError!void {
2236 if (builtin.os.tag == .wasi and !builtin.link_libc) {
2237 var resolve_olddir: bool = (olddir == wasi.AT.FDCWD or fs.path.isAbsolute(oldpath));
2238 var resolve_newdir: bool = (newdir == wasi.AT.FDCWD or fs.path.isAbsolute(newpath));
2239
2240 var old: RelativePathWasi = .{ .dir_fd = olddir, .relative_path = oldpath };
2241 var new: RelativePathWasi = .{ .dir_fd = newdir, .relative_path = newpath };
2242
2243 // Resolve absolute or CWD-relative paths to a path within a Preopen
2244 if (resolve_olddir or resolve_newdir) {
2245 var buf_old: [MAX_PATH_BYTES]u8 = undefined;
2246 var buf_new: [MAX_PATH_BYTES]u8 = undefined;
2247
2248 if (resolve_olddir)
2249 old = try resolvePathWasi(oldpath, &buf_old);
2250
2251 if (resolve_newdir)
2252 new = try resolvePathWasi(newpath, &buf_new);
2253
2254 return linkatWasi(old, new, flags);
2255 }
2256 return linkatWasi(old, new, flags);
2257 }
1998 const old = try toPosixPath(oldpath);2258 const old = try toPosixPath(oldpath);
1999 const new = try toPosixPath(newpath);2259 const new = try toPosixPath(newpath);
2000 return try linkatZ(olddir, &old, newdir, &new, flags);2260 return try linkatZ(olddir, &old, newdir, &new, flags);
2001}2261}
20022262
2263/// WASI-only. The same as `linkat` but targeting WASI.
2264/// See also `linkat`.
2265pub fn linkatWasi(old: RelativePathWasi, new: RelativePathWasi, flags: i32) LinkatError!void {
2266 var old_flags: wasi.lookupflags_t = 0;
2267 // TODO: Why is this not defined in wasi-libc?
2268 if (flags & linux.AT.SYMLINK_FOLLOW != 0) old_flags |= wasi.LOOKUP_SYMLINK_FOLLOW;
2269
2270 switch (wasi.path_link(old.dir_fd, old_flags, old.relative_path.ptr, old.relative_path.len, new.dir_fd, new.relative_path.ptr, new.relative_path.len)) {
2271 .SUCCESS => return,
2272 .ACCES => return error.AccessDenied,
2273 .DQUOT => return error.DiskQuota,
2274 .EXIST => return error.PathAlreadyExists,
2275 .FAULT => unreachable,
2276 .IO => return error.FileSystem,
2277 .LOOP => return error.SymLinkLoop,
2278 .MLINK => return error.LinkQuotaExceeded,
2279 .NAMETOOLONG => return error.NameTooLong,
2280 .NOENT => return error.FileNotFound,
2281 .NOMEM => return error.SystemResources,
2282 .NOSPC => return error.NoSpaceLeft,
2283 .NOTDIR => return error.NotDir,
2284 .PERM => return error.AccessDenied,
2285 .ROFS => return error.ReadOnlyFileSystem,
2286 .XDEV => return error.NotSameFileSystem,
2287 .INVAL => unreachable,
2288 else => |err| return unexpectedErrno(err),
2289 }
2290}
2291
2003pub const UnlinkError = error{2292pub const UnlinkError = error{
2004 FileNotFound,2293 FileNotFound,
20052294
...@@ -2027,7 +2316,10 @@ pub const UnlinkError = error{...@@ -2027,7 +2316,10 @@ pub const UnlinkError = error{
2027/// See also `unlinkZ`.2316/// See also `unlinkZ`.
2028pub fn unlink(file_path: []const u8) UnlinkError!void {2317pub fn unlink(file_path: []const u8) UnlinkError!void {
2029 if (builtin.os.tag == .wasi and !builtin.link_libc) {2318 if (builtin.os.tag == .wasi and !builtin.link_libc) {
2030 @compileError("unlink is not supported in WASI; use unlinkat instead");2319 return unlinkat(wasi.AT.FDCWD, file_path, 0) catch |err| switch (err) {
2320 error.DirNotEmpty => unreachable, // only occurs when targeting directories
2321 else => |e| return e,
2322 };
2031 } else if (builtin.os.tag == .windows) {2323 } else if (builtin.os.tag == .windows) {
2032 const file_path_w = try windows.sliceToPrefixedFileW(file_path);2324 const file_path_w = try windows.sliceToPrefixedFileW(file_path);
2033 return unlinkW(file_path_w.span());2325 return unlinkW(file_path_w.span());
...@@ -2042,6 +2334,8 @@ pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void {...@@ -2042,6 +2334,8 @@ pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void {
2042 if (builtin.os.tag == .windows) {2334 if (builtin.os.tag == .windows) {
2043 const file_path_w = try windows.cStrToPrefixedFileW(file_path);2335 const file_path_w = try windows.cStrToPrefixedFileW(file_path);
2044 return unlinkW(file_path_w.span());2336 return unlinkW(file_path_w.span());
2337 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
2338 return unlink(mem.sliceTo(file_path, 0));
2045 }2339 }
2046 switch (errno(system.unlink(file_path))) {2340 switch (errno(system.unlink(file_path))) {
2047 .SUCCESS => return,2341 .SUCCESS => return,
...@@ -2079,6 +2373,12 @@ pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!vo...@@ -2079,6 +2373,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);2373 const file_path_w = try windows.sliceToPrefixedFileW(file_path);
2080 return unlinkatW(dirfd, file_path_w.span(), flags);2374 return unlinkatW(dirfd, file_path_w.span(), flags);
2081 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {2375 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
2376 if (dirfd == wasi.AT.FDCWD or fs.path.isAbsolute(file_path)) {
2377 // Resolve absolute or CWD-relative paths to a path within a Preopen
2378 var path_buf: [MAX_PATH_BYTES]u8 = undefined;
2379 const path = try resolvePathWasi(file_path, &path_buf);
2380 return unlinkatWasi(path.dir_fd, path.relative_path, flags);
2381 }
2082 return unlinkatWasi(dirfd, file_path, flags);2382 return unlinkatWasi(dirfd, file_path, flags);
2083 } else {2383 } else {
2084 const file_path_c = try toPosixPath(file_path);2384 const file_path_c = try toPosixPath(file_path);
...@@ -2123,6 +2423,8 @@ pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatEr...@@ -2123,6 +2423,8 @@ pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatEr
2123 if (builtin.os.tag == .windows) {2423 if (builtin.os.tag == .windows) {
2124 const file_path_w = try windows.cStrToPrefixedFileW(file_path_c);2424 const file_path_w = try windows.cStrToPrefixedFileW(file_path_c);
2125 return unlinkatW(dirfd, file_path_w.span(), flags);2425 return unlinkatW(dirfd, file_path_w.span(), flags);
2426 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
2427 return unlinkat(dirfd, mem.sliceTo(file_path_c, 0), flags);
2126 }2428 }
2127 switch (errno(system.unlinkat(dirfd, file_path_c, flags))) {2429 switch (errno(system.unlinkat(dirfd, file_path_c, flags))) {
2128 .SUCCESS => return,2430 .SUCCESS => return,
...@@ -2181,7 +2483,7 @@ pub const RenameError = error{...@@ -2181,7 +2483,7 @@ pub const RenameError = error{
2181/// Change the name or location of a file.2483/// Change the name or location of a file.
2182pub fn rename(old_path: []const u8, new_path: []const u8) RenameError!void {2484pub fn rename(old_path: []const u8, new_path: []const u8) RenameError!void {
2183 if (builtin.os.tag == .wasi and !builtin.link_libc) {2485 if (builtin.os.tag == .wasi and !builtin.link_libc) {
2184 @compileError("rename is not supported in WASI; use renameat instead");2486 return renameat(wasi.AT.FDCWD, old_path, wasi.AT.FDCWD, new_path);
2185 } else if (builtin.os.tag == .windows) {2487 } else if (builtin.os.tag == .windows) {
2186 const old_path_w = try windows.sliceToPrefixedFileW(old_path);2488 const old_path_w = try windows.sliceToPrefixedFileW(old_path);
2187 const new_path_w = try windows.sliceToPrefixedFileW(new_path);2489 const new_path_w = try windows.sliceToPrefixedFileW(new_path);
...@@ -2199,6 +2501,8 @@ pub fn renameZ(old_path: [*:0]const u8, new_path: [*:0]const u8) RenameError!voi...@@ -2199,6 +2501,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);2501 const old_path_w = try windows.cStrToPrefixedFileW(old_path);
2200 const new_path_w = try windows.cStrToPrefixedFileW(new_path);2502 const new_path_w = try windows.cStrToPrefixedFileW(new_path);
2201 return renameW(old_path_w.span().ptr, new_path_w.span().ptr);2503 return renameW(old_path_w.span().ptr, new_path_w.span().ptr);
2504 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
2505 return rename(mem.sliceTo(old_path, 0), mem.sliceTo(new_path, 0));
2202 }2506 }
2203 switch (errno(system.rename(old_path, new_path))) {2507 switch (errno(system.rename(old_path, new_path))) {
2204 .SUCCESS => return,2508 .SUCCESS => return,
...@@ -2243,7 +2547,25 @@ pub fn renameat(...@@ -2243,7 +2547,25 @@ pub fn renameat(
2243 const new_path_w = try windows.sliceToPrefixedFileW(new_path);2547 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);2548 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) {2549 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
2246 return renameatWasi(old_dir_fd, old_path, new_dir_fd, new_path);2550 var resolve_old: bool = (old_dir_fd == wasi.AT.FDCWD or fs.path.isAbsolute(old_path));
2551 var resolve_new: bool = (new_dir_fd == wasi.AT.FDCWD or fs.path.isAbsolute(new_path));
2552
2553 var old: RelativePathWasi = .{ .dir_fd = old_dir_fd, .relative_path = old_path };
2554 var new: RelativePathWasi = .{ .dir_fd = new_dir_fd, .relative_path = new_path };
2555
2556 // Resolve absolute or CWD-relative paths to a path within a Preopen
2557 if (resolve_old or resolve_new) {
2558 var buf_old: [MAX_PATH_BYTES]u8 = undefined;
2559 var buf_new: [MAX_PATH_BYTES]u8 = undefined;
2560
2561 if (resolve_old)
2562 old = try resolvePathWasi(old_path, &buf_old);
2563 if (resolve_new)
2564 new = try resolvePathWasi(new_path, &buf_new);
2565
2566 return renameatWasi(old, new);
2567 }
2568 return renameatWasi(old, new);
2247 } else {2569 } else {
2248 const old_path_c = try toPosixPath(old_path);2570 const old_path_c = try toPosixPath(old_path);
2249 const new_path_c = try toPosixPath(new_path);2571 const new_path_c = try toPosixPath(new_path);
...@@ -2253,8 +2575,8 @@ pub fn renameat(...@@ -2253,8 +2575,8 @@ pub fn renameat(
22532575
2254/// WASI-only. Same as `renameat` expect targeting WASI.2576/// WASI-only. Same as `renameat` expect targeting WASI.
2255/// See also `renameat`.2577/// 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 {2578pub fn renameatWasi(old: RelativePathWasi, new: RelativePathWasi) 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)) {2579 switch (wasi.path_rename(old.dir_fd, old.relative_path.ptr, old.relative_path.len, new.dir_fd, new.relative_path.ptr, new.relative_path.len)) {
2258 .SUCCESS => return,2580 .SUCCESS => return,
2259 .ACCES => return error.AccessDenied,2581 .ACCES => return error.AccessDenied,
2260 .PERM => return error.AccessDenied,2582 .PERM => return error.AccessDenied,
...@@ -2290,6 +2612,8 @@ pub fn renameatZ(...@@ -2290,6 +2612,8 @@ pub fn renameatZ(
2290 const old_path_w = try windows.cStrToPrefixedFileW(old_path);2612 const old_path_w = try windows.cStrToPrefixedFileW(old_path);
2291 const new_path_w = try windows.cStrToPrefixedFileW(new_path);2613 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);2614 return renameatW(old_dir_fd, old_path_w.span(), new_dir_fd, new_path_w.span(), windows.TRUE);
2615 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
2616 return renameat(old_dir_fd, mem.sliceTo(old_path, 0), new_dir_fd, mem.sliceTo(new_path, 0));
2293 }2617 }
22942618
2295 switch (errno(system.renameat(old_dir_fd, old_path, new_dir_fd, new_path))) {2619 switch (errno(system.renameat(old_dir_fd, old_path, new_dir_fd, new_path))) {
...@@ -2380,6 +2704,12 @@ pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!v...@@ -2380,6 +2704,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);2704 const sub_dir_path_w = try windows.sliceToPrefixedFileW(sub_dir_path);
2381 return mkdiratW(dir_fd, sub_dir_path_w.span(), mode);2705 return mkdiratW(dir_fd, sub_dir_path_w.span(), mode);
2382 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {2706 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
2707 if (dir_fd == wasi.AT.FDCWD or fs.path.isAbsolute(sub_dir_path)) {
2708 // Resolve absolute or CWD-relative paths to a path within a Preopen
2709 var path_buf: [MAX_PATH_BYTES]u8 = undefined;
2710 const path = try resolvePathWasi(sub_dir_path, &path_buf);
2711 return mkdiratWasi(path.dir_fd, path.relative_path, mode);
2712 }
2383 return mkdiratWasi(dir_fd, sub_dir_path, mode);2713 return mkdiratWasi(dir_fd, sub_dir_path, mode);
2384 } else {2714 } else {
2385 const sub_dir_path_c = try toPosixPath(sub_dir_path);2715 const sub_dir_path_c = try toPosixPath(sub_dir_path);
...@@ -2414,6 +2744,8 @@ pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirErr...@@ -2414,6 +2744,8 @@ pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirErr
2414 if (builtin.os.tag == .windows) {2744 if (builtin.os.tag == .windows) {
2415 const sub_dir_path_w = try windows.cStrToPrefixedFileW(sub_dir_path);2745 const sub_dir_path_w = try windows.cStrToPrefixedFileW(sub_dir_path);
2416 return mkdiratW(dir_fd, sub_dir_path_w.span().ptr, mode);2746 return mkdiratW(dir_fd, sub_dir_path_w.span().ptr, mode);
2747 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
2748 return mkdirat(dir_fd, mem.sliceTo(sub_dir_path, 0), mode);
2417 }2749 }
2418 switch (errno(system.mkdirat(dir_fd, sub_dir_path, mode))) {2750 switch (errno(system.mkdirat(dir_fd, sub_dir_path, mode))) {
2419 .SUCCESS => return,2751 .SUCCESS => return,
...@@ -2472,10 +2804,10 @@ pub const MakeDirError = error{...@@ -2472,10 +2804,10 @@ pub const MakeDirError = error{
2472} || UnexpectedError;2804} || UnexpectedError;
24732805
2474/// Create a directory.2806/// Create a directory.
2475/// `mode` is ignored on Windows.2807/// `mode` is ignored on Windows and WASI.
2476pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void {2808pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void {
2477 if (builtin.os.tag == .wasi and !builtin.link_libc) {2809 if (builtin.os.tag == .wasi and !builtin.link_libc) {
2478 @compileError("mkdir is not supported in WASI; use mkdirat instead");2810 return mkdirat(wasi.AT.FDCWD, dir_path, mode);
2479 } else if (builtin.os.tag == .windows) {2811 } else if (builtin.os.tag == .windows) {
2480 const dir_path_w = try windows.sliceToPrefixedFileW(dir_path);2812 const dir_path_w = try windows.sliceToPrefixedFileW(dir_path);
2481 return mkdirW(dir_path_w.span(), mode);2813 return mkdirW(dir_path_w.span(), mode);
...@@ -2490,6 +2822,8 @@ pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void {...@@ -2490,6 +2822,8 @@ pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void {
2490 if (builtin.os.tag == .windows) {2822 if (builtin.os.tag == .windows) {
2491 const dir_path_w = try windows.cStrToPrefixedFileW(dir_path);2823 const dir_path_w = try windows.cStrToPrefixedFileW(dir_path);
2492 return mkdirW(dir_path_w.span(), mode);2824 return mkdirW(dir_path_w.span(), mode);
2825 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
2826 return mkdir(mem.sliceTo(dir_path, 0), mode);
2493 }2827 }
2494 switch (errno(system.mkdir(dir_path, mode))) {2828 switch (errno(system.mkdir(dir_path, mode))) {
2495 .SUCCESS => return,2829 .SUCCESS => return,
...@@ -2545,7 +2879,11 @@ pub const DeleteDirError = error{...@@ -2545,7 +2879,11 @@ pub const DeleteDirError = error{
2545/// Deletes an empty directory.2879/// Deletes an empty directory.
2546pub fn rmdir(dir_path: []const u8) DeleteDirError!void {2880pub fn rmdir(dir_path: []const u8) DeleteDirError!void {
2547 if (builtin.os.tag == .wasi and !builtin.link_libc) {2881 if (builtin.os.tag == .wasi and !builtin.link_libc) {
2548 @compileError("rmdir is not supported in WASI; use unlinkat instead");2882 return unlinkat(wasi.AT.FDCWD, dir_path, AT.REMOVEDIR) catch |err| switch (err) {
2883 error.FileSystem => unreachable, // only occurs when targeting files
2884 error.IsDir => unreachable, // only occurs when targeting files
2885 else => |e| return e,
2886 };
2549 } else if (builtin.os.tag == .windows) {2887 } else if (builtin.os.tag == .windows) {
2550 const dir_path_w = try windows.sliceToPrefixedFileW(dir_path);2888 const dir_path_w = try windows.sliceToPrefixedFileW(dir_path);
2551 return rmdirW(dir_path_w.span());2889 return rmdirW(dir_path_w.span());
...@@ -2560,6 +2898,8 @@ pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void {...@@ -2560,6 +2898,8 @@ pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void {
2560 if (builtin.os.tag == .windows) {2898 if (builtin.os.tag == .windows) {
2561 const dir_path_w = try windows.cStrToPrefixedFileW(dir_path);2899 const dir_path_w = try windows.cStrToPrefixedFileW(dir_path);
2562 return rmdirW(dir_path_w.span());2900 return rmdirW(dir_path_w.span());
2901 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
2902 return rmdir(mem.sliceTo(dir_path, 0));
2563 }2903 }
2564 switch (errno(system.rmdir(dir_path))) {2904 switch (errno(system.rmdir(dir_path))) {
2565 .SUCCESS => return,2905 .SUCCESS => return,
...@@ -2606,7 +2946,17 @@ pub const ChangeCurDirError = error{...@@ -2606,7 +2946,17 @@ pub const ChangeCurDirError = error{
2606/// `dir_path` is recommended to be a UTF-8 encoded string.2946/// `dir_path` is recommended to be a UTF-8 encoded string.
2607pub fn chdir(dir_path: []const u8) ChangeCurDirError!void {2947pub fn chdir(dir_path: []const u8) ChangeCurDirError!void {
2608 if (builtin.os.tag == .wasi and !builtin.link_libc) {2948 if (builtin.os.tag == .wasi and !builtin.link_libc) {
2609 @compileError("chdir is not supported in WASI");2949 var preopen: ?Preopen = null;
2950 const path = try resolvePathAndGetWasiPreopen(dir_path, &preopen, &wasi_cwd.path_buffer);
2951
2952 const dirinfo = try fstatat(path.dir_fd, path.relative_path, 0);
2953 if (dirinfo.filetype != .DIRECTORY) {
2954 return error.NotDir;
2955 }
2956
2957 wasi_cwd.cwd_preopen = preopen;
2958 wasi_cwd.cwd = path;
2959 return;
2610 } else if (builtin.os.tag == .windows) {2960 } else if (builtin.os.tag == .windows) {
2611 var utf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined;2961 var utf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined;
2612 const len = try std.unicode.utf8ToUtf16Le(utf16_dir_path[0..], dir_path);2962 const len = try std.unicode.utf8ToUtf16Le(utf16_dir_path[0..], dir_path);
...@@ -2625,6 +2975,8 @@ pub fn chdirZ(dir_path: [*:0]const u8) ChangeCurDirError!void {...@@ -2625,6 +2975,8 @@ pub fn chdirZ(dir_path: [*:0]const u8) ChangeCurDirError!void {
2625 const len = try std.unicode.utf8ToUtf16Le(utf16_dir_path[0..], dir_path);2975 const len = try std.unicode.utf8ToUtf16Le(utf16_dir_path[0..], dir_path);
2626 if (len > utf16_dir_path.len) return error.NameTooLong;2976 if (len > utf16_dir_path.len) return error.NameTooLong;
2627 return chdirW(utf16_dir_path[0..len]);2977 return chdirW(utf16_dir_path[0..len]);
2978 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
2979 return chdir(mem.sliceTo(dir_path, 0));
2628 }2980 }
2629 switch (errno(system.chdir(dir_path))) {2981 switch (errno(system.chdir(dir_path))) {
2630 .SUCCESS => return,2982 .SUCCESS => return,
...@@ -2655,15 +3007,29 @@ pub const FchdirError = error{...@@ -2655,15 +3007,29 @@ pub const FchdirError = error{
2655} || UnexpectedError;3007} || UnexpectedError;
26563008
2657pub fn fchdir(dirfd: fd_t) FchdirError!void {3009pub fn fchdir(dirfd: fd_t) FchdirError!void {
2658 while (true) {3010 if (builtin.os.tag == .wasi) {
2659 switch (errno(system.fchdir(dirfd))) {3011 // Check that this is a directory
2660 .SUCCESS => return,3012 const dirinfo = fstatat(dirfd, ".", 0) catch unreachable;
2661 .ACCES => return error.AccessDenied,3013 if (dirinfo.filetype != .DIRECTORY) {
2662 .BADF => unreachable,3014 return error.NotDir;
2663 .NOTDIR => return error.NotDir,3015 }
2664 .INTR => continue,3016
2665 .IO => return error.FileSystem,3017 wasi_cwd.cwd = .{
2666 else => |err| return unexpectedErrno(err),3018 .dir_fd = dirfd,
3019 .relative_path = ".",
3020 };
3021 wasi_cwd.cwd_preopen = null;
3022 } else {
3023 while (true) {
3024 switch (errno(system.fchdir(dirfd))) {
3025 .SUCCESS => return,
3026 .ACCES => return error.AccessDenied,
3027 .BADF => unreachable,
3028 .NOTDIR => return error.NotDir,
3029 .INTR => continue,
3030 .IO => return error.FileSystem,
3031 else => |err| return unexpectedErrno(err),
3032 }
2667 }3033 }
2668 }3034 }
2669}3035}
...@@ -2690,7 +3056,7 @@ pub const ReadLinkError = error{...@@ -2690,7 +3056,7 @@ pub const ReadLinkError = error{
2690/// The return value is a slice of `out_buffer` from index 0.3056/// The return value is a slice of `out_buffer` from index 0.
2691pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {3057pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
2692 if (builtin.os.tag == .wasi and !builtin.link_libc) {3058 if (builtin.os.tag == .wasi and !builtin.link_libc) {
2693 @compileError("readlink is not supported in WASI; use readlinkat instead");3059 return readlinkat(wasi.AT.FDCWD, file_path, out_buffer);
2694 } else if (builtin.os.tag == .windows) {3060 } else if (builtin.os.tag == .windows) {
2695 const file_path_w = try windows.sliceToPrefixedFileW(file_path);3061 const file_path_w = try windows.sliceToPrefixedFileW(file_path);
2696 return readlinkW(file_path_w.span(), out_buffer);3062 return readlinkW(file_path_w.span(), out_buffer);
...@@ -2711,6 +3077,8 @@ pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8...@@ -2711,6 +3077,8 @@ pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8
2711 if (builtin.os.tag == .windows) {3077 if (builtin.os.tag == .windows) {
2712 const file_path_w = try windows.cStrToWin32PrefixedFileW(file_path);3078 const file_path_w = try windows.cStrToWin32PrefixedFileW(file_path);
2713 return readlinkW(file_path_w.span(), out_buffer);3079 return readlinkW(file_path_w.span(), out_buffer);
3080 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
3081 return readlink(mem.sliceTo(file_path, 0), out_buffer);
2714 }3082 }
2715 const rc = system.readlink(file_path, out_buffer.ptr, out_buffer.len);3083 const rc = system.readlink(file_path, out_buffer.ptr, out_buffer.len);
2716 switch (errno(rc)) {3084 switch (errno(rc)) {
...@@ -2733,6 +3101,12 @@ pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8...@@ -2733,6 +3101,12 @@ pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8
2733/// See also `readlinkatWasi`, `realinkatZ` and `realinkatW`.3101/// See also `readlinkatWasi`, `realinkatZ` and `realinkatW`.
2734pub fn readlinkat(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {3102pub fn readlinkat(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
2735 if (builtin.os.tag == .wasi and !builtin.link_libc) {3103 if (builtin.os.tag == .wasi and !builtin.link_libc) {
3104 if (dirfd == wasi.AT.FDCWD or fs.path.isAbsolute(file_path)) {
3105 // Resolve absolute or CWD-relative paths to a path within a Preopen
3106 var path_buf: [MAX_PATH_BYTES]u8 = undefined;
3107 var path = try resolvePathWasi(file_path, &path_buf);
3108 return readlinkatWasi(path.dir_fd, path.relative_path, out_buffer);
3109 }
2736 return readlinkatWasi(dirfd, file_path, out_buffer);3110 return readlinkatWasi(dirfd, file_path, out_buffer);
2737 }3111 }
2738 if (builtin.os.tag == .windows) {3112 if (builtin.os.tag == .windows) {
...@@ -2775,6 +3149,8 @@ pub fn readlinkatZ(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) Read...@@ -2775,6 +3149,8 @@ pub fn readlinkatZ(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) Read
2775 if (builtin.os.tag == .windows) {3149 if (builtin.os.tag == .windows) {
2776 const file_path_w = try windows.cStrToPrefixedFileW(file_path);3150 const file_path_w = try windows.cStrToPrefixedFileW(file_path);
2777 return readlinkatW(dirfd, file_path_w.span(), out_buffer);3151 return readlinkatW(dirfd, file_path_w.span(), out_buffer);
3152 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
3153 return readlinkat(dirfd, mem.sliceTo(file_path, 0), out_buffer);
2778 }3154 }
2779 const rc = system.readlinkat(dirfd, file_path, out_buffer.ptr, out_buffer.len);3155 const rc = system.readlinkat(dirfd, file_path, out_buffer.ptr, out_buffer.len);
2780 switch (errno(rc)) {3156 switch (errno(rc)) {
...@@ -3727,7 +4103,14 @@ pub const FStatAtError = FStatError || error{ NameTooLong, FileNotFound, SymLink...@@ -3727,7 +4103,14 @@ pub const FStatAtError = FStatError || error{ NameTooLong, FileNotFound, SymLink
3727/// See also `fstatatZ` and `fstatatWasi`.4103/// See also `fstatatZ` and `fstatatWasi`.
3728pub fn fstatat(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!Stat {4104pub fn fstatat(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!Stat {
3729 if (builtin.os.tag == .wasi and !builtin.link_libc) {4105 if (builtin.os.tag == .wasi and !builtin.link_libc) {
3730 return fstatatWasi(dirfd, pathname, flags);4106 const wasi_flags = if (flags & linux.AT.SYMLINK_NOFOLLOW == 0) wasi.LOOKUP_SYMLINK_FOLLOW else 0;
4107 if (dirfd == wasi.AT.FDCWD or fs.path.isAbsolute(pathname)) {
4108 // Resolve absolute or CWD-relative paths to a path within a Preopen
4109 var path_buf: [MAX_PATH_BYTES]u8 = undefined;
4110 const path = try resolvePathWasi(pathname, &path_buf);
4111 return fstatatWasi(path.dir_fd, path.relative_path, wasi_flags);
4112 }
4113 return fstatatWasi(dirfd, pathname, wasi_flags);
3731 } else if (builtin.os.tag == .windows) {4114 } else if (builtin.os.tag == .windows) {
3732 @compileError("fstatat is not yet implemented on Windows");4115 @compileError("fstatat is not yet implemented on Windows");
3733 } else {4116 } else {
...@@ -3758,6 +4141,10 @@ pub fn fstatatWasi(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!S...@@ -3758,6 +4141,10 @@ pub fn fstatatWasi(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!S
3758/// Same as `fstatat` but `pathname` is null-terminated.4141/// Same as `fstatat` but `pathname` is null-terminated.
3759/// See also `fstatat`.4142/// See also `fstatat`.
3760pub fn fstatatZ(dirfd: fd_t, pathname: [*:0]const u8, flags: u32) FStatAtError!Stat {4143pub fn fstatatZ(dirfd: fd_t, pathname: [*:0]const u8, flags: u32) FStatAtError!Stat {
4144 if (builtin.os.tag == .wasi and !builtin.link_libc) {
4145 return fstatatWasi(dirfd, mem.sliceTo(pathname), flags);
4146 }
4147
3761 const fstatat_sym = if (builtin.os.tag == .linux and builtin.link_libc)4148 const fstatat_sym = if (builtin.os.tag == .linux and builtin.link_libc)
3762 system.fstatat644149 system.fstatat64
3763 else4150 else
...@@ -4056,6 +4443,8 @@ pub fn access(path: []const u8, mode: u32) AccessError!void {...@@ -4056,6 +4443,8 @@ pub fn access(path: []const u8, mode: u32) AccessError!void {
4056 const path_w = try windows.sliceToPrefixedFileW(path);4443 const path_w = try windows.sliceToPrefixedFileW(path);
4057 _ = try windows.GetFileAttributesW(path_w.span().ptr);4444 _ = try windows.GetFileAttributesW(path_w.span().ptr);
4058 return;4445 return;
4446 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
4447 return faccessat(wasi.AT.FDCWD, path, mode, 0);
4059 }4448 }
4060 const path_c = try toPosixPath(path);4449 const path_c = try toPosixPath(path);
4061 return accessZ(&path_c, mode);4450 return accessZ(&path_c, mode);
...@@ -4067,6 +4456,8 @@ pub fn accessZ(path: [*:0]const u8, mode: u32) AccessError!void {...@@ -4067,6 +4456,8 @@ pub fn accessZ(path: [*:0]const u8, mode: u32) AccessError!void {
4067 const path_w = try windows.cStrToPrefixedFileW(path);4456 const path_w = try windows.cStrToPrefixedFileW(path);
4068 _ = try windows.GetFileAttributesW(path_w.span().ptr);4457 _ = try windows.GetFileAttributesW(path_w.span().ptr);
4069 return;4458 return;
4459 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
4460 return access(mem.sliceTo(path, 0), mode);
4070 }4461 }
4071 switch (errno(system.access(path, mode))) {4462 switch (errno(system.access(path, mode))) {
4072 .SUCCESS => return,4463 .SUCCESS => return,
...@@ -4108,6 +4499,45 @@ pub fn faccessat(dirfd: fd_t, path: []const u8, mode: u32, flags: u32) AccessErr...@@ -4108,6 +4499,45 @@ pub fn faccessat(dirfd: fd_t, path: []const u8, mode: u32, flags: u32) AccessErr
4108 if (builtin.os.tag == .windows) {4499 if (builtin.os.tag == .windows) {
4109 const path_w = try windows.sliceToPrefixedFileW(path);4500 const path_w = try windows.sliceToPrefixedFileW(path);
4110 return faccessatW(dirfd, path_w.span().ptr, mode, flags);4501 return faccessatW(dirfd, path_w.span().ptr, mode, flags);
4502 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
4503 var resolved = RelativePathWasi{ .dir_fd = dirfd, .relative_path = path };
4504
4505 const file = blk: {
4506 if (dirfd == wasi.AT.FDCWD or fs.path.isAbsolute(path)) {
4507 // Resolve absolute or CWD-relative paths to a path within a Preopen
4508 var path_buf: [MAX_PATH_BYTES]u8 = undefined;
4509 resolved = resolvePathWasi(path, &path_buf) catch |err| break :blk @as(FStatAtError!Stat, err);
4510 break :blk fstatat(resolved.dir_fd, resolved.relative_path, flags);
4511 }
4512 break :blk fstatat(dirfd, path, flags);
4513 } catch |err| switch (err) {
4514 error.AccessDenied => return error.PermissionDenied,
4515 else => |e| return e,
4516 };
4517
4518 if (mode != F_OK) {
4519 var directory: wasi.fdstat_t = undefined;
4520 if (wasi.fd_fdstat_get(resolved.dir_fd, &directory) != .SUCCESS) {
4521 return error.PermissionDenied;
4522 }
4523
4524 var rights: wasi.rights_t = 0;
4525 if (mode & R_OK != 0) {
4526 rights |= if (file.filetype == .DIRECTORY)
4527 wasi.RIGHT.FD_READDIR
4528 else
4529 wasi.RIGHT.FD_READ;
4530 }
4531 if (mode & W_OK != 0) {
4532 rights |= wasi.RIGHT.FD_WRITE;
4533 }
4534 // No validation for X_OK
4535
4536 if ((rights & directory.fs_rights_inheriting) != rights) {
4537 return error.PermissionDenied;
4538 }
4539 }
4540 return;
4111 }4541 }
4112 const path_c = try toPosixPath(path);4542 const path_c = try toPosixPath(path);
4113 return faccessatZ(dirfd, &path_c, mode, flags);4543 return faccessatZ(dirfd, &path_c, mode, flags);
...@@ -4118,6 +4548,8 @@ pub fn faccessatZ(dirfd: fd_t, path: [*:0]const u8, mode: u32, flags: u32) Acces...@@ -4118,6 +4548,8 @@ pub fn faccessatZ(dirfd: fd_t, path: [*:0]const u8, mode: u32, flags: u32) Acces
4118 if (builtin.os.tag == .windows) {4548 if (builtin.os.tag == .windows) {
4119 const path_w = try windows.cStrToPrefixedFileW(path);4549 const path_w = try windows.cStrToPrefixedFileW(path);
4120 return faccessatW(dirfd, path_w.span().ptr, mode, flags);4550 return faccessatW(dirfd, path_w.span().ptr, mode, flags);
4551 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
4552 return faccessat(dirfd, mem.sliceTo(path, 0), mode, flags);
4121 }4553 }
4122 switch (errno(system.faccessat(dirfd, path, mode, flags))) {4554 switch (errno(system.faccessat(dirfd, path, mode, flags))) {
4123 .SUCCESS => return,4555 .SUCCESS => return,
...@@ -4645,6 +5077,9 @@ pub const RealPathError = error{...@@ -4645,6 +5077,9 @@ pub const RealPathError = error{
4645 SharingViolation,5077 SharingViolation,
4646 PipeBusy,5078 PipeBusy,
46475079
5080 /// On WASI, the current CWD may not be associated with an absolute path.
5081 InvalidHandle,
5082
4648 /// On Windows, file paths must be valid Unicode.5083 /// On Windows, file paths must be valid Unicode.
4649 InvalidUtf8,5084 InvalidUtf8,
46505085
...@@ -4660,19 +5095,55 @@ pub fn realpath(pathname: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathE...@@ -4660,19 +5095,55 @@ pub fn realpath(pathname: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathE
4660 if (builtin.os.tag == .windows) {5095 if (builtin.os.tag == .windows) {
4661 const pathname_w = try windows.sliceToPrefixedFileW(pathname);5096 const pathname_w = try windows.sliceToPrefixedFileW(pathname);
4662 return realpathW(pathname_w.span(), out_buffer);5097 return realpathW(pathname_w.span(), out_buffer);
4663 }5098 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
4664 if (builtin.os.tag == .wasi and !builtin.link_libc) {5099 return realpathWasi(pathname, out_buffer);
4665 @compileError("Use std.fs.wasi.PreopenList to obtain valid Dir handles instead of using absolute paths");
4666 }5100 }
4667 const pathname_c = try toPosixPath(pathname);5101 const pathname_c = try toPosixPath(pathname);
4668 return realpathZ(&pathname_c, out_buffer);5102 return realpathZ(&pathname_c, out_buffer);
4669}5103}
46705104
5105/// Return an emulated canonicalized absolute pathname on WASI.
5106///
5107/// NOTE: This emulation is incomplete. Symbolic links are not
5108/// currently expanded during path canonicalization.
5109fn realpathWasi(pathname: []const u8, out_buffer: []u8) ![]u8 {
5110 var alloc = std.heap.FixedBufferAllocator.init(out_buffer);
5111 if (fs.path.isAbsolute(pathname))
5112 return try fs.path.resolve(alloc.allocator(), &.{pathname}) catch error.NameTooLong;
5113 if (wasi_cwd.cwd) |cwd| {
5114 if (wasi_cwd.cwd_preopen) |po| {
5115 var base_cwd_dir = switch (po.@"type") {
5116 .Dir => |dir| dir,
5117 };
5118 const paths: [][]const u8 = if (fs.path.isAbsolute(base_cwd_dir)) blk: {
5119 break :blk &.{ base_cwd_dir, cwd.relative_path, pathname };
5120 } else blk: {
5121 // No absolute path is associated with this preopen, so
5122 // instead we use a special "/preopens/fd/<N>/" prefix
5123 var buf: [16]u8 = undefined;
5124 var fbs = std.io.fixedBufferStream(&buf);
5125 std.fmt.formatInt(po.fd, 10, .lower, .{}, fbs.writer()) catch return error.NameTooLong;
5126 break :blk &.{ "/preopens/fd/", fbs.getWritten(), cwd.relative_path, pathname };
5127 };
5128
5129 return fs.path.resolve(alloc.allocator(), paths) catch error.NameTooLong;
5130 } else {
5131 // The CWD is not rooted to an existing Preopen,
5132 // so we have no way to know its absolute path
5133 return error.InvalidHandle;
5134 }
5135 } else {
5136 return try fs.path.resolve(alloc.allocator(), &.{ "/", pathname }) catch error.NameTooLong;
5137 }
5138}
5139
4671/// Same as `realpath` except `pathname` is null-terminated.5140/// Same as `realpath` except `pathname` is null-terminated.
4672pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {5141pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
4673 if (builtin.os.tag == .windows) {5142 if (builtin.os.tag == .windows) {
4674 const pathname_w = try windows.cStrToPrefixedFileW(pathname);5143 const pathname_w = try windows.cStrToPrefixedFileW(pathname);
4675 return realpathW(pathname_w.span(), out_buffer);5144 return realpathW(pathname_w.span(), out_buffer);
5145 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
5146 return realpath(mem.sliceTo(pathname, 0), out_buffer);
4676 }5147 }
4677 if (!builtin.link_libc) {5148 if (!builtin.link_libc) {
4678 const flags = if (builtin.os.tag == .linux) O.PATH | O.NONBLOCK | O.CLOEXEC else O.NONBLOCK | O.CLOEXEC;5149 const flags = if (builtin.os.tag == .linux) O.PATH | O.NONBLOCK | O.CLOEXEC else O.NONBLOCK | O.CLOEXEC;
...@@ -4680,6 +5151,7 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP...@@ -4680,6 +5151,7 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP
4680 error.FileLocksNotSupported => unreachable,5151 error.FileLocksNotSupported => unreachable,
4681 error.WouldBlock => unreachable,5152 error.WouldBlock => unreachable,
4682 error.FileBusy => unreachable, // not asking for write permissions5153 error.FileBusy => unreachable, // not asking for write permissions
5154 error.InvalidHandle => unreachable, // WASI-only
4683 else => |e| return e,5155 else => |e| return e,
4684 };5156 };
4685 defer close(fd);5157 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, ".");
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, ".");
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, ".");
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, ".");
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, "."),
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, ".");
894
895 var tmp = tmpDir(.{});
896 defer tmp.cleanup();
897
898 // Get base abs path
899 var arena = ArenaAllocator.init(testing.allocator);
900 defer arena.deinit();
901 const allocator = arena.allocator();
902
903 const base_path = blk: {
904 const relative_path = try fs.path.join(allocator, &[_][]const u8{ "zig-cache", "tmp", tmp.sub_path[0..] });
905 break :blk try fs.realpathAlloc(allocator, relative_path);
906 };
907
908 var file_path: []u8 = undefined;
909 var fd: os.fd_t = undefined;
910 const mode: os.mode_t = if (native_os == .windows) 0 else 0o666;
911
912 // Create some file using `open`.
913 file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" });
914 fd = try os.open(file_path, os.O.RDWR | os.O.CREAT | os.O.EXCL, mode);
915 os.close(fd);
916
917 // Rename the file
918 var new_file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_other_file" });
919 try os.rename(file_path, new_file_path);
920
921 // Try opening renamed file
922 file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_other_file" });
923 fd = try os.open(file_path, os.O.RDWR, mode);
924 os.close(fd);
925
926 // Try opening original file - should fail with error.FileNotFound
927 file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" });
928 try expectError(error.FileNotFound, os.open(file_path, os.O.RDWR, mode));
929
930 // Create some directory
931 file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_dir" });
932 try os.mkdir(file_path, mode);
933
934 // Rename the directory
935 new_file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_other_dir" });
936 try os.rename(file_path, new_file_path);
937
938 // Try opening renamed directory
939 file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_other_dir" });
940 fd = try os.open(file_path, os.O.RDONLY | os.O.DIRECTORY, mode);
941 os.close(fd);
942
943 // Try opening original directory - should fail with error.FileNotFound
944 file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_dir" });
945 try expectError(error.FileNotFound, os.open(file_path, os.O.RDONLY | os.O.DIRECTORY, mode));
946}
947
948test "access smoke test" {
949 if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
950 if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, ".");
951
952 var tmp = tmpDir(.{});
953 defer tmp.cleanup();
954
955 // Get base abs path
956 var arena = ArenaAllocator.init(testing.allocator);
957 defer arena.deinit();
958 const allocator = arena.allocator();
959
960 const base_path = blk: {
961 const relative_path = try fs.path.join(allocator, &[_][]const u8{ "zig-cache", "tmp", tmp.sub_path[0..] });
962 break :blk try fs.realpathAlloc(allocator, relative_path);
963 };
964
965 var file_path: []u8 = undefined;
966 var fd: os.fd_t = undefined;
967 const mode: os.mode_t = if (native_os == .windows) 0 else 0o666;
968
969 // Create some file using `open`.
970 file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" });
971 fd = try os.open(file_path, os.O.RDWR | os.O.CREAT | os.O.EXCL, mode);
972 os.close(fd);
973
974 // Try to access() the file
975 file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" });
976 if (builtin.os.tag == .windows) {
977 try os.access(file_path, os.F_OK);
978 } else {
979 try os.access(file_path, os.F_OK | os.W_OK | os.R_OK);
980 }
981
982 // Try to access() a non-existent file - should fail with error.FileNotFound
983 file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_other_file" });
984 try expectError(error.FileNotFound, os.access(file_path, os.F_OK));
985
986 // Create some directory
987 file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_dir" });
988 try os.mkdir(file_path, mode);
989
990 // Try to access() the directory
991 file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_dir" });
992 try os.access(file_path, os.F_OK);
993}
lib/std/os/wasi.zig+5
...@@ -15,6 +15,11 @@ comptime {...@@ -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/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,