Skip to content

Commit 59b0254

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

File tree

13 files changed

+757
-100
lines changed

13 files changed

+757
-100
lines changed

lib/std/build.zig

+2
Original file line numberDiff line numberDiff line change
@@ -2707,6 +2707,8 @@ pub const LibExeObjStep = struct {
27072707
try zig_args.append("--test-cmd");
27082708
try zig_args.append("--dir=.");
27092709
try zig_args.append("--test-cmd");
2710+
try zig_args.append("--mapdir=/cwd::.");
2711+
try zig_args.append("--test-cmd");
27102712
try zig_args.append("--allow-unknown-exports"); // TODO: Remove when stage2 is default compiler
27112713
try zig_args.append("--test-cmd-bin");
27122714
} else {

lib/std/c/wasi.zig

+5-5
Original file line numberDiff line numberDiff line change
@@ -62,21 +62,21 @@ pub const Stat = extern struct {
6262
/// https://github.com/WebAssembly/wasi-libc/blob/main/expected/wasm32-wasi/predefined-macros.txt
6363
pub const O = struct {
6464
pub const ACCMODE = (EXEC | RDWR | SEARCH);
65-
pub const APPEND = FDFLAG.APPEND;
65+
pub const APPEND = @as(u32, FDFLAG.APPEND);
6666
pub const CLOEXEC = (0);
6767
pub const CREAT = ((1 << 0) << 12); // = __WASI_OFLAGS_CREAT << 12
6868
pub const DIRECTORY = ((1 << 1) << 12); // = __WASI_OFLAGS_DIRECTORY << 12
69-
pub const DSYNC = FDFLAG.DSYNC;
69+
pub const DSYNC = @as(u32, FDFLAG.DSYNC);
7070
pub const EXCL = ((1 << 2) << 12); // = __WASI_OFLAGS_EXCL << 12
7171
pub const EXEC = (0x02000000);
7272
pub const NOCTTY = (0);
7373
pub const NOFOLLOW = (0x01000000);
74-
pub const NONBLOCK = (1 << FDFLAG.NONBLOCK);
74+
pub const NONBLOCK = @as(u32, FDFLAG.NONBLOCK);
7575
pub const RDONLY = (0x04000000);
7676
pub const RDWR = (RDONLY | WRONLY);
77-
pub const RSYNC = (1 << FDFLAG.RSYNC);
77+
pub const RSYNC = @as(u32, FDFLAG.RSYNC);
7878
pub const SEARCH = (0x08000000);
79-
pub const SYNC = (1 << FDFLAG.SYNC);
79+
pub const SYNC = @as(u32, FDFLAG.SYNC);
8080
pub const TRUNC = ((1 << 3) << 12); // = __WASI_OFLAGS_TRUNC << 12
8181
pub const TTY_INIT = (0);
8282
pub const WRONLY = (0x10000000);

lib/std/child_process.zig

+1
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,7 @@ pub const ChildProcess = struct {
563563
error.DeviceBusy => unreachable,
564564
error.FileLocksNotSupported => unreachable,
565565
error.BadPathName => unreachable, // Windows-only
566+
error.InvalidHandle => unreachable, // WASI-only
566567
error.WouldBlock => unreachable,
567568
else => |e| return e,
568569
}

lib/std/fs.zig

+44-28
Original file line numberDiff line numberDiff line change
@@ -923,6 +923,7 @@ pub const Dir = struct {
923923
pub const OpenError = error{
924924
FileNotFound,
925925
NotDir,
926+
InvalidHandle,
926927
AccessDenied,
927928
SymLinkLoop,
928929
ProcessFdQuotaExceeded,
@@ -981,6 +982,13 @@ pub const Dir = struct {
981982
w.RIGHT.FD_FILESTAT_SET_TIMES |
982983
w.RIGHT.FD_FILESTAT_SET_SIZE;
983984
}
985+
if (self.fd == os.wasi.AT.FDCWD or path.isAbsolute(sub_path)) {
986+
// Resolve absolute or CWD-relative paths to a path within a Preopen
987+
var resolved_path_buf: [MAX_PATH_BYTES]u8 = undefined;
988+
const resolved_path = try os.resolvePathWasi(sub_path, &resolved_path_buf);
989+
const fd = try os.openatWasi(resolved_path.dir_fd, resolved_path.relative_path, 0x0, 0x0, fdflags, base, 0x0);
990+
return File{ .handle = fd };
991+
}
984992
const fd = try os.openatWasi(self.fd, sub_path, 0x0, 0x0, fdflags, base, 0x0);
985993
return File{ .handle = fd };
986994
}
@@ -1145,6 +1153,13 @@ pub const Dir = struct {
11451153
if (flags.exclusive) {
11461154
oflags |= w.O.EXCL;
11471155
}
1156+
if (self.fd == os.wasi.AT.FDCWD or path.isAbsolute(sub_path)) {
1157+
// Resolve absolute or CWD-relative paths to a path within a Preopen
1158+
var resolved_path_buf: [MAX_PATH_BYTES]u8 = undefined;
1159+
const resolved_path = try os.resolvePathWasi(sub_path, &resolved_path_buf);
1160+
const fd = try os.openatWasi(resolved_path.dir_fd, resolved_path.relative_path, 0x0, oflags, 0x0, base, 0x0);
1161+
return File{ .handle = fd };
1162+
}
11481163
const fd = try os.openatWasi(self.fd, sub_path, 0x0, oflags, 0x0, base, 0x0);
11491164
return File{ .handle = fd };
11501165
}
@@ -1330,7 +1345,19 @@ pub const Dir = struct {
13301345
/// See also `Dir.realpathZ`, `Dir.realpathW`, and `Dir.realpathAlloc`.
13311346
pub fn realpath(self: Dir, pathname: []const u8, out_buffer: []u8) ![]u8 {
13321347
if (builtin.os.tag == .wasi) {
1333-
@compileError("realpath is unsupported in WASI");
1348+
if (self.fd == os.wasi.AT.FDCWD or path.isAbsolute(pathname)) {
1349+
var buffer: [MAX_PATH_BYTES]u8 = undefined;
1350+
const out_path = try os.realpath(pathname, &buffer);
1351+
if (out_path.len > out_buffer.len) {
1352+
return error.NameTooLong;
1353+
}
1354+
mem.copy(u8, out_buffer, out_path);
1355+
return out_buffer[0..out_path.len];
1356+
} else {
1357+
// Unfortunately, we have no ability to look up the path for an fd_t
1358+
// on WASI, so we have to give up here.
1359+
return error.InvalidHandle;
1360+
}
13341361
}
13351362
if (builtin.os.tag == .windows) {
13361363
const pathname_w = try os.windows.sliceToPrefixedFileW(pathname);
@@ -1507,7 +1534,16 @@ pub const Dir = struct {
15071534
// TODO do we really need all the rights here?
15081535
const inheriting: w.rights_t = w.RIGHT.ALL ^ w.RIGHT.SOCK_SHUTDOWN;
15091536

1510-
const result = os.openatWasi(self.fd, sub_path, symlink_flags, w.O.DIRECTORY, 0x0, base, inheriting);
1537+
const result = blk: {
1538+
if (self.fd == os.wasi.AT.FDCWD or path.isAbsolute(sub_path)) {
1539+
// Resolve absolute or CWD-relative paths to a path within a Preopen
1540+
var resolved_path_buf: [MAX_PATH_BYTES]u8 = undefined;
1541+
const resolved_path = try os.resolvePathWasi(sub_path, &resolved_path_buf);
1542+
break :blk os.openatWasi(resolved_path.dir_fd, resolved_path.relative_path, symlink_flags, w.O.DIRECTORY, 0x0, base, inheriting);
1543+
} else {
1544+
break :blk os.openatWasi(self.fd, sub_path, symlink_flags, w.O.DIRECTORY, 0x0, base, inheriting);
1545+
}
1546+
};
15111547
const fd = result catch |err| switch (err) {
15121548
error.FileTooBig => unreachable, // can't happen for directories
15131549
error.IsDir => unreachable, // we're providing O.DIRECTORY
@@ -1622,7 +1658,7 @@ pub const Dir = struct {
16221658
const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
16231659
return self.deleteFileW(sub_path_w.span());
16241660
} else if (builtin.os.tag == .wasi and !builtin.link_libc) {
1625-
os.unlinkatWasi(self.fd, sub_path, 0) catch |err| switch (err) {
1661+
os.unlinkat(self.fd, sub_path, 0) catch |err| switch (err) {
16261662
error.DirNotEmpty => unreachable, // not passing AT.REMOVEDIR
16271663
else => |e| return e,
16281664
};
@@ -1761,7 +1797,7 @@ pub const Dir = struct {
17611797
sym_link_path: []const u8,
17621798
_: SymLinkFlags,
17631799
) !void {
1764-
return os.symlinkatWasi(target_path, self.fd, sym_link_path);
1800+
return os.symlinkat(target_path, self.fd, sym_link_path);
17651801
}
17661802

17671803
/// Same as `symLink`, except the pathname parameters are null-terminated.
@@ -1807,7 +1843,7 @@ pub const Dir = struct {
18071843

18081844
/// WASI-only. Same as `readLink` except targeting WASI.
18091845
pub fn readLinkWasi(self: Dir, sub_path: []const u8, buffer: []u8) ![]u8 {
1810-
return os.readlinkatWasi(self.fd, sub_path, buffer);
1846+
return os.readlinkat(self.fd, sub_path, buffer);
18111847
}
18121848

18131849
/// Same as `readLink`, except the `pathname` parameter is null-terminated.
@@ -1870,6 +1906,7 @@ pub const Dir = struct {
18701906
}
18711907

18721908
pub const DeleteTreeError = error{
1909+
InvalidHandle,
18731910
AccessDenied,
18741911
FileTooBig,
18751912
SymLinkLoop,
@@ -1935,6 +1972,7 @@ pub const Dir = struct {
19351972
continue :start_over;
19361973
},
19371974

1975+
error.InvalidHandle,
19381976
error.AccessDenied,
19391977
error.SymLinkLoop,
19401978
error.ProcessFdQuotaExceeded,
@@ -2002,6 +2040,7 @@ pub const Dir = struct {
20022040
continue :scan_dir;
20032041
},
20042042

2043+
error.InvalidHandle,
20052044
error.AccessDenied,
20062045
error.SymLinkLoop,
20072046
error.ProcessFdQuotaExceeded,
@@ -2272,8 +2311,6 @@ pub const Dir = struct {
22722311
pub fn cwd() Dir {
22732312
if (builtin.os.tag == .windows) {
22742313
return Dir{ .fd = os.windows.peb().ProcessParameters.CurrentDirectory.Handle };
2275-
} else if (builtin.os.tag == .wasi and !builtin.link_libc) {
2276-
@compileError("WASI doesn't have a concept of cwd(); use std.fs.wasi.PreopenList to get available Dir handles instead");
22772314
} else {
22782315
return Dir{ .fd = os.AT.FDCWD };
22792316
}
@@ -2285,26 +2322,17 @@ pub fn cwd() Dir {
22852322
///
22862323
/// Asserts that the path parameter has no null bytes.
22872324
pub fn openDirAbsolute(absolute_path: []const u8, flags: Dir.OpenDirOptions) File.OpenError!Dir {
2288-
if (builtin.os.tag == .wasi) {
2289-
@compileError("WASI doesn't have the concept of an absolute directory; use openDir instead for WASI.");
2290-
}
22912325
assert(path.isAbsolute(absolute_path));
22922326
return cwd().openDir(absolute_path, flags);
22932327
}
22942328

22952329
/// Same as `openDirAbsolute` but the path parameter is null-terminated.
22962330
pub fn openDirAbsoluteZ(absolute_path_c: [*:0]const u8, flags: Dir.OpenDirOptions) File.OpenError!Dir {
2297-
if (builtin.os.tag == .wasi) {
2298-
@compileError("WASI doesn't have the concept of an absolute directory; use openDir instead for WASI.");
2299-
}
23002331
assert(path.isAbsoluteZ(absolute_path_c));
23012332
return cwd().openDirZ(absolute_path_c, flags);
23022333
}
23032334
/// Same as `openDirAbsolute` but the path parameter is null-terminated.
23042335
pub fn openDirAbsoluteW(absolute_path_c: [*:0]const u16, flags: Dir.OpenDirOptions) File.OpenError!Dir {
2305-
if (builtin.os.tag == .wasi) {
2306-
@compileError("WASI doesn't have the concept of an absolute directory; use openDir instead for WASI.");
2307-
}
23082336
assert(path.isAbsoluteWindowsW(absolute_path_c));
23092337
return cwd().openDirW(absolute_path_c, flags);
23102338
}
@@ -2339,25 +2367,16 @@ pub fn openFileAbsoluteW(absolute_path_w: []const u16, flags: File.OpenFlags) Fi
23392367
/// open it and handle the error for file not found.
23402368
/// See `accessAbsoluteZ` for a function that accepts a null-terminated path.
23412369
pub fn accessAbsolute(absolute_path: []const u8, flags: File.OpenFlags) Dir.AccessError!void {
2342-
if (builtin.os.tag == .wasi) {
2343-
@compileError("WASI doesn't have the concept of an absolute path; use access instead for WASI.");
2344-
}
23452370
assert(path.isAbsolute(absolute_path));
23462371
try cwd().access(absolute_path, flags);
23472372
}
23482373
/// Same as `accessAbsolute` but the path parameter is null-terminated.
23492374
pub fn accessAbsoluteZ(absolute_path: [*:0]const u8, flags: File.OpenFlags) Dir.AccessError!void {
2350-
if (builtin.os.tag == .wasi) {
2351-
@compileError("WASI doesn't have the concept of an absolute path; use access instead for WASI.");
2352-
}
23532375
assert(path.isAbsoluteZ(absolute_path));
23542376
try cwd().accessZ(absolute_path, flags);
23552377
}
23562378
/// Same as `accessAbsolute` but the path parameter is WTF-16 encoded.
23572379
pub fn accessAbsoluteW(absolute_path: [*:0]const 16, flags: File.OpenFlags) Dir.AccessError!void {
2358-
if (builtin.os.tag == .wasi) {
2359-
@compileError("WASI doesn't have the concept of an absolute path; use access instead for WASI.");
2360-
}
23612380
assert(path.isAbsoluteWindowsW(absolute_path));
23622381
try cwd().accessW(absolute_path, flags);
23632382
}
@@ -2458,9 +2477,6 @@ pub const SymLinkFlags = struct {
24582477
/// If `sym_link_path` exists, it will not be overwritten.
24592478
/// See also `symLinkAbsoluteZ` and `symLinkAbsoluteW`.
24602479
pub fn symLinkAbsolute(target_path: []const u8, sym_link_path: []const u8, flags: SymLinkFlags) !void {
2461-
if (builtin.os.tag == .wasi) {
2462-
@compileError("symLinkAbsolute is not supported in WASI; use Dir.symLinkWasi instead");
2463-
}
24642480
assert(path.isAbsolute(target_path));
24652481
assert(path.isAbsolute(sym_link_path));
24662482
if (builtin.os.tag == .windows) {

lib/std/fs/path.zig

+9-4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const fmt = std.fmt;
88
const Allocator = mem.Allocator;
99
const math = std.math;
1010
const windows = std.os.windows;
11+
const os = std.os;
1112
const fs = std.fs;
1213
const process = std.process;
1314
const native_os = builtin.target.os.tag;
@@ -733,7 +734,8 @@ pub fn resolvePosix(allocator: Allocator, paths: []const []const u8) ![]u8 {
733734
}
734735

735736
test "resolve" {
736-
if (native_os == .wasi) return error.SkipZigTest;
737+
if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
738+
if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/cwd");
737739

738740
const cwd = try process.getCwdAlloc(testing.allocator);
739741
defer testing.allocator.free(cwd);
@@ -753,7 +755,8 @@ test "resolveWindows" {
753755
// TODO https://github.com/ziglang/zig/issues/3288
754756
return error.SkipZigTest;
755757
}
756-
if (native_os == .wasi) return error.SkipZigTest;
758+
if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
759+
if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/cwd");
757760
if (native_os == .windows) {
758761
const cwd = try process.getCwdAlloc(testing.allocator);
759762
defer testing.allocator.free(cwd);
@@ -798,7 +801,8 @@ test "resolveWindows" {
798801
}
799802

800803
test "resolvePosix" {
801-
if (native_os == .wasi) return error.SkipZigTest;
804+
if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
805+
if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/cwd");
802806

803807
try testResolvePosix(&[_][]const u8{ "/a/b", "c" }, "/a/b/c");
804808
try testResolvePosix(&[_][]const u8{ "/a/b", "c", "//d", "e///" }, "/d/e");
@@ -1211,7 +1215,8 @@ test "relative" {
12111215
// TODO https://github.com/ziglang/zig/issues/3288
12121216
return error.SkipZigTest;
12131217
}
1214-
if (native_os == .wasi) return error.SkipZigTest;
1218+
if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
1219+
if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/cwd");
12151220

12161221
try testRelativeWindows("c:/blah\\blah", "d:/games", "D:\\games");
12171222
try testRelativeWindows("c:/aaaa/bbbb", "c:/aaaa", "..");

lib/std/fs/test.zig

+15-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const std = @import("../std.zig");
22
const builtin = @import("builtin");
33
const testing = std.testing;
4+
const os = std.os;
45
const fs = std.fs;
56
const mem = std.mem;
67
const wasi = std.os.wasi;
@@ -45,7 +46,8 @@ fn testReadLink(dir: Dir, target_path: []const u8, symlink_path: []const u8) !vo
4546
}
4647

4748
test "accessAbsolute" {
48-
if (builtin.os.tag == .wasi) return error.SkipZigTest;
49+
if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest;
50+
if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/cwd");
4951

5052
var tmp = tmpDir(.{});
5153
defer tmp.cleanup();
@@ -63,7 +65,8 @@ test "accessAbsolute" {
6365
}
6466

6567
test "openDirAbsolute" {
66-
if (builtin.os.tag == .wasi) return error.SkipZigTest;
68+
if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest;
69+
if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/cwd");
6770

6871
var tmp = tmpDir(.{});
6972
defer tmp.cleanup();
@@ -99,7 +102,8 @@ test "openDir cwd parent .." {
99102
}
100103

101104
test "readLinkAbsolute" {
102-
if (builtin.os.tag == .wasi) return error.SkipZigTest;
105+
if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest;
106+
if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/cwd");
103107

104108
var tmp = tmpDir(.{});
105109
defer tmp.cleanup();
@@ -507,7 +511,8 @@ test "rename" {
507511
}
508512

509513
test "renameAbsolute" {
510-
if (builtin.os.tag == .wasi) return error.SkipZigTest;
514+
if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest;
515+
if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/cwd");
511516

512517
var tmp_dir = tmpDir(.{});
513518
defer tmp_dir.cleanup();
@@ -941,7 +946,8 @@ test "open file with exclusive nonblocking lock twice (absolute paths)" {
941946
}
942947

943948
test "walker" {
944-
if (builtin.os.tag == .wasi) return error.SkipZigTest;
949+
if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest;
950+
if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/cwd");
945951

946952
var tmp = tmpDir(.{ .iterate = true });
947953
defer tmp.cleanup();
@@ -991,7 +997,8 @@ test "walker" {
991997
}
992998

993999
test ". and .. in fs.Dir functions" {
994-
if (builtin.os.tag == .wasi) return error.SkipZigTest;
1000+
if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest;
1001+
if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/cwd");
9951002

9961003
var tmp = tmpDir(.{});
9971004
defer tmp.cleanup();
@@ -1019,7 +1026,8 @@ test ". and .. in fs.Dir functions" {
10191026
}
10201027

10211028
test ". and .. in absolute functions" {
1022-
if (builtin.os.tag == .wasi) return error.SkipZigTest;
1029+
if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest;
1030+
if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/cwd");
10231031

10241032
var tmp = tmpDir(.{});
10251033
defer tmp.cleanup();

0 commit comments

Comments
 (0)