Skip to content

Commit 908f41a

Browse files
authored
Merge pull request #11021 from topolarity/wasi-cwd
stdlib: Add emulated CWD to std.os for WASI targets
2 parents ac936c0 + aafcd8e commit 908f41a

File tree

10 files changed

+778
-96
lines changed

10 files changed

+778
-96
lines changed

lib/std/c/wasi.zig

Lines changed: 5 additions & 5 deletions
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 44 additions & 28 deletions
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

Lines changed: 9 additions & 4 deletions
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, ".");
737739

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

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, ".");
802806

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

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

lib/std/fs/test.zig

Lines changed: 15 additions & 7 deletions
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, ".");
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, ".");
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, ".");
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, ".");
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, ".");
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, ".");
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, ".");
10231031

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

0 commit comments

Comments
 (0)