Skip to content

Commit f357598

Browse files
committed
stdlib WASI: Add realpath() support for non-absolute Preopens
1 parent 59b0254 commit f357598

File tree

8 files changed

+95
-75
lines changed

8 files changed

+95
-75
lines changed

lib/std/build.zig

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2707,8 +2707,6 @@ 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");
27122710
try zig_args.append("--allow-unknown-exports"); // TODO: Remove when stage2 is default compiler
27132711
try zig_args.append("--test-cmd-bin");
27142712
} else {

lib/std/fs/path.zig

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -735,7 +735,7 @@ pub fn resolvePosix(allocator: Allocator, paths: []const []const u8) ![]u8 {
735735

736736
test "resolve" {
737737
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");
738+
if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, ".");
739739

740740
const cwd = try process.getCwdAlloc(testing.allocator);
741741
defer testing.allocator.free(cwd);
@@ -756,7 +756,7 @@ test "resolveWindows" {
756756
return error.SkipZigTest;
757757
}
758758
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");
759+
if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, ".");
760760
if (native_os == .windows) {
761761
const cwd = try process.getCwdAlloc(testing.allocator);
762762
defer testing.allocator.free(cwd);
@@ -802,7 +802,7 @@ test "resolveWindows" {
802802

803803
test "resolvePosix" {
804804
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");
805+
if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, ".");
806806

807807
try testResolvePosix(&[_][]const u8{ "/a/b", "c" }, "/a/b/c");
808808
try testResolvePosix(&[_][]const u8{ "/a/b", "c", "//d", "e///" }, "/d/e");
@@ -1216,7 +1216,7 @@ test "relative" {
12161216
return error.SkipZigTest;
12171217
}
12181218
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");
1219+
if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, ".");
12201220

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

lib/std/fs/test.zig

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ fn testReadLink(dir: Dir, target_path: []const u8, symlink_path: []const u8) !vo
4747

4848
test "accessAbsolute" {
4949
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");
50+
if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, ".");
5151

5252
var tmp = tmpDir(.{});
5353
defer tmp.cleanup();
@@ -66,7 +66,7 @@ test "accessAbsolute" {
6666

6767
test "openDirAbsolute" {
6868
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");
69+
if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, ".");
7070

7171
var tmp = tmpDir(.{});
7272
defer tmp.cleanup();
@@ -103,7 +103,7 @@ test "openDir cwd parent .." {
103103

104104
test "readLinkAbsolute" {
105105
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");
106+
if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, ".");
107107

108108
var tmp = tmpDir(.{});
109109
defer tmp.cleanup();
@@ -512,7 +512,7 @@ test "rename" {
512512

513513
test "renameAbsolute" {
514514
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");
515+
if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, ".");
516516

517517
var tmp_dir = tmpDir(.{});
518518
defer tmp_dir.cleanup();
@@ -947,7 +947,7 @@ test "open file with exclusive nonblocking lock twice (absolute paths)" {
947947

948948
test "walker" {
949949
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");
950+
if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, ".");
951951

952952
var tmp = tmpDir(.{ .iterate = true });
953953
defer tmp.cleanup();
@@ -998,7 +998,7 @@ test "walker" {
998998

999999
test ". and .. in fs.Dir functions" {
10001000
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");
1001+
if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, ".");
10021002

10031003
var tmp = tmpDir(.{});
10041004
defer tmp.cleanup();
@@ -1027,7 +1027,7 @@ test ". and .. in fs.Dir functions" {
10271027

10281028
test ". and .. in absolute functions" {
10291029
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");
1030+
if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, ".");
10311031

10321032
var tmp = tmpDir(.{});
10331033
defer tmp.cleanup();

lib/std/fs/wasi.zig

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,17 @@ pub const PreopenList = struct {
194194
return null;
195195
}
196196

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+
197208
/// Find preopen by type. If the preopen exists, return it.
198209
/// Otherwise, return `null`.
199210
pub fn find(self: Self, preopen_type: PreopenType) ?*const Preopen {
@@ -224,6 +235,6 @@ test "extracting WASI preopens" {
224235

225236
try preopens.populate();
226237

227-
const preopen = preopens.find(PreopenType{ .Dir = "/cwd" }) orelse unreachable;
228-
try std.testing.expect(preopen.@"type".eql(PreopenType{ .Dir = "/cwd" }));
238+
const preopen = preopens.find(PreopenType{ .Dir = "." }) orelse unreachable;
239+
try std.testing.expect(preopen.@"type".eql(PreopenType{ .Dir = "." }));
229240
}

lib/std/os.zig

Lines changed: 62 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1431,6 +1431,8 @@ var wasi_cwd = if (builtin.os.tag == .wasi and !builtin.link_libc) struct {
14311431
}{} else undefined;
14321432

14331433
/// Initialize the available Preopen list on WASI and set the CWD to `cwd_init`.
1434+
/// Note that `cwd_init` corresponds to a Preopen directory, not necessarily
1435+
/// a POSIX path. For example, "." matches a Preopen provided with `--dir=.`
14341436
///
14351437
/// This must be called before using any relative or absolute paths with `std.os`
14361438
/// functions, if you are on WASI without linking libc.
@@ -1482,8 +1484,22 @@ fn resolvePathAndGetWasiPreopen(path: []const u8, preopen: ?*?Preopen, out_buffe
14821484
if (wasi_cwd.preopens == null) @panic("On WASI, `initPreopensWasi` must be called to initialize preopens " ++
14831485
"before using any CWD-relative or absolute paths.\n");
14841486

1485-
// If the path is absolute, we need to lookup a containing Preopen
1486-
const abs_path = std.fs.path.resolve(alloc, &.{ "/", path }) catch return error.NameTooLong;
1487+
if (mem.startsWith(u8, path, "/preopens/fd/")) {
1488+
// "/preopens/fd/<N>" is a special prefix, which refers to a Preopen directly by fd
1489+
const fd_start = "/preopens/fd/".len;
1490+
const fd_end = mem.indexOfScalarPos(u8, path, fd_start, '/') orelse path.len;
1491+
const fd = std.fmt.parseUnsigned(fd_t, path[fd_start..fd_end], 10) catch unreachable;
1492+
const rel_path = if (path.len > fd_end + 1) path[fd_end + 1 ..] else ".";
1493+
1494+
if (preopen) |p| p.* = wasi_cwd.preopens.?.findByFd(fd);
1495+
return RelativePath{
1496+
.dir_fd = fd,
1497+
.relative_path = alloc.dupe(u8, rel_path) catch return error.NameTooLong,
1498+
};
1499+
}
1500+
1501+
// For any other absolute path, we need to lookup a containing Preopen
1502+
const abs_path = fs.path.resolve(alloc, &.{ "/", path }) catch return error.NameTooLong;
14871503
const preopen_uri = wasi_cwd.preopens.?.findContaining(.{ .Dir = abs_path });
14881504

14891505
if (preopen_uri) |po| {
@@ -1507,7 +1523,7 @@ fn resolvePathAndGetWasiPreopen(path: []const u8, preopen: ?*?Preopen, out_buffe
15071523
// First resolve a combined path, where the "/" corresponds to `cwd.dir_fd`
15081524
// not the true filesystem root
15091525
const paths = &.{ "/", cwd.relative_path, path };
1510-
const resolved_path = std.fs.path.resolve(alloc, paths) catch return error.NameTooLong;
1526+
const resolved_path = fs.path.resolve(alloc, paths) catch return error.NameTooLong;
15111527

15121528
// Strip off the fake root to get the relative path w.r.t. `cwd.dir_fd`
15131529
const resolved_relative_path = resolved_path[1..];
@@ -1956,28 +1972,14 @@ pub fn getcwd(out_buffer: []u8) GetCwdError![]u8 {
19561972
if (builtin.os.tag == .windows) {
19571973
return windows.GetCurrentDirectory(out_buffer);
19581974
} else if (builtin.os.tag == .wasi and !builtin.link_libc) {
1959-
var allocator = std.heap.FixedBufferAllocator.init(out_buffer);
1960-
var alloc = allocator.allocator();
1961-
if (wasi_cwd.cwd) |cwd| {
1962-
if (wasi_cwd.cwd_preopen) |po| {
1963-
var base_cwd_dir = switch (po.@"type") {
1964-
.Dir => |dir| dir,
1965-
};
1966-
if (!fs.path.isAbsolute(base_cwd_dir)) {
1967-
// This preopen is not based on an absolute path, so we have
1968-
// no way to know the absolute path of the CWD
1969-
return error.CurrentWorkingDirectoryUnlinked;
1970-
}
1971-
const paths = &.{ base_cwd_dir, cwd.relative_path };
1972-
return std.fs.path.resolve(alloc, paths) catch return error.NameTooLong;
1973-
} else {
1974-
// The CWD is not rooted to an existing Preopen,
1975-
// so we have no way to know its absolute path
1976-
return error.CurrentWorkingDirectoryUnlinked;
1977-
}
1978-
} else {
1979-
return alloc.dupe(u8, "/") catch return error.NameTooLong;
1980-
}
1975+
var buf: [MAX_PATH_BYTES]u8 = undefined;
1976+
const path = realpathWasi(".", &buf) catch |err| switch (err) {
1977+
error.NameTooLong => return error.NameTooLong,
1978+
error.InvalidHandle => return error.CurrentWorkingDirectoryUnlinked,
1979+
};
1980+
if (out_buffer.len < path.len) return error.NameTooLong;
1981+
std.mem.copy(u8, out_buffer, path);
1982+
return out_buffer[0..path.len];
19811983
}
19821984

19831985
const err = if (builtin.link_libc) blk: {
@@ -5089,35 +5091,45 @@ pub fn realpath(pathname: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathE
50895091
const pathname_w = try windows.sliceToPrefixedFileW(pathname);
50905092
return realpathW(pathname_w.span(), out_buffer);
50915093
} else if (builtin.os.tag == .wasi and !builtin.link_libc) {
5092-
// NOTE: This emulation is incomplete. Symbolic links are not
5093-
// currently expanded during path canonicalization.
5094-
var alloc = std.heap.FixedBufferAllocator.init(out_buffer);
5095-
if (fs.path.isAbsolute(pathname))
5096-
return try fs.path.resolve(alloc.allocator(), &.{pathname}) catch error.NameTooLong;
5097-
if (wasi_cwd.cwd) |cwd| {
5098-
if (wasi_cwd.cwd_preopen) |po| {
5099-
var base_cwd_dir = switch (po.@"type") {
5100-
.Dir => |dir| dir,
5101-
};
5102-
if (!fs.path.isAbsolute(base_cwd_dir)) {
5103-
// This preopen is not based on an absolute path, so we have
5104-
// no way to know the absolute path of the CWD
5105-
return error.InvalidHandle;
5106-
}
5094+
return realpathWasi(pathname, out_buffer);
5095+
}
5096+
const pathname_c = try toPosixPath(pathname);
5097+
return realpathZ(&pathname_c, out_buffer);
5098+
}
51075099

5108-
const paths = &.{ base_cwd_dir, cwd.relative_path, pathname };
5109-
return fs.path.resolve(alloc.allocator(), paths) catch error.NameTooLong;
5110-
} else {
5111-
// The CWD is not rooted to an existing Preopen,
5112-
// so we have no way to know its absolute path
5113-
return error.InvalidHandle;
5114-
}
5100+
/// Return an emulated canonicalized absolute pathname on WASI.
5101+
///
5102+
/// NOTE: This emulation is incomplete. Symbolic links are not
5103+
/// currently expanded during path canonicalization.
5104+
fn realpathWasi(pathname: []const u8, out_buffer: []u8) ![]u8 {
5105+
var alloc = std.heap.FixedBufferAllocator.init(out_buffer);
5106+
if (fs.path.isAbsolute(pathname))
5107+
return try fs.path.resolve(alloc.allocator(), &.{pathname}) catch error.NameTooLong;
5108+
if (wasi_cwd.cwd) |cwd| {
5109+
if (wasi_cwd.cwd_preopen) |po| {
5110+
var base_cwd_dir = switch (po.@"type") {
5111+
.Dir => |dir| dir,
5112+
};
5113+
const paths: [][]const u8 = if (fs.path.isAbsolute(base_cwd_dir)) blk: {
5114+
break :blk &.{ base_cwd_dir, cwd.relative_path, pathname };
5115+
} else blk: {
5116+
// No absolute path is associated with this preopen, so
5117+
// instead we use a special "/preopens/fd/<N>/" prefix
5118+
var buf: [16]u8 = undefined;
5119+
var fbs = std.io.fixedBufferStream(&buf);
5120+
std.fmt.formatInt(po.fd, 10, .lower, .{}, fbs.writer()) catch return error.NameTooLong;
5121+
break :blk &.{ "/preopens/fd/", fbs.getWritten(), cwd.relative_path, pathname };
5122+
};
5123+
5124+
return fs.path.resolve(alloc.allocator(), paths) catch error.NameTooLong;
51155125
} else {
5116-
return try fs.path.resolve(alloc.allocator(), &.{ "/", pathname }) catch error.NameTooLong;
5126+
// The CWD is not rooted to an existing Preopen,
5127+
// so we have no way to know its absolute path
5128+
return error.InvalidHandle;
51175129
}
5130+
} else {
5131+
return try fs.path.resolve(alloc.allocator(), &.{ "/", pathname }) catch error.NameTooLong;
51185132
}
5119-
const pathname_c = try toPosixPath(pathname);
5120-
return realpathZ(&pathname_c, out_buffer);
51215133
}
51225134

51235135
/// Same as `realpath` except `pathname` is null-terminated.

lib/std/os/test.zig

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ test "chdir smoke test" {
4949

5050
test "open smoke test" {
5151
if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
52-
if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/cwd");
52+
if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, ".");
5353

5454
// TODO verify file attributes using `fstat`
5555

@@ -104,7 +104,7 @@ test "open smoke test" {
104104

105105
test "openat smoke test" {
106106
if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
107-
if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/cwd");
107+
if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, ".");
108108

109109
// TODO verify file attributes using `fstatat`
110110

@@ -141,7 +141,7 @@ test "openat smoke test" {
141141

142142
test "symlink with relative paths" {
143143
if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
144-
if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/cwd");
144+
if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, ".");
145145

146146
const cwd = fs.cwd();
147147
cwd.deleteFile("file.txt") catch {};
@@ -197,7 +197,7 @@ test "link with relative paths" {
197197
if (builtin.link_libc) {
198198
return error.SkipZigTest;
199199
} else {
200-
try os.initPreopensWasi(std.heap.page_allocator, "/cwd");
200+
try os.initPreopensWasi(std.heap.page_allocator, ".");
201201
}
202202
},
203203
.linux, .solaris => {},
@@ -237,7 +237,7 @@ test "link with relative paths" {
237237

238238
test "linkat with different directories" {
239239
switch (native_os) {
240-
.wasi => if (!builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/cwd"),
240+
.wasi => if (!builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "."),
241241
.linux, .solaris => {},
242242
else => return error.SkipZigTest,
243243
}
@@ -890,7 +890,7 @@ test "POSIX file locking with fcntl" {
890890

891891
test "rename smoke test" {
892892
if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
893-
if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/cwd");
893+
if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, ".");
894894

895895
var tmp = tmpDir(.{});
896896
defer tmp.cleanup();
@@ -947,7 +947,7 @@ test "rename smoke test" {
947947

948948
test "access smoke test" {
949949
if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
950-
if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/cwd");
950+
if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, ".");
951951

952952
var tmp = tmpDir(.{});
953953
defer tmp.cleanup();

lib/std/testing.zig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -327,8 +327,8 @@ fn getCwdOrWasiPreopen() std.fs.Dir {
327327
defer preopens.deinit();
328328
preopens.populate() catch
329329
@panic("unable to make tmp dir for testing: unable to populate preopens");
330-
const preopen = preopens.find(std.fs.wasi.PreopenType{ .Dir = "/cwd" }) orelse
331-
@panic("unable to make tmp dir for testing: didn't find '/cwd' in the preopens");
330+
const preopen = preopens.find(std.fs.wasi.PreopenType{ .Dir = "." }) orelse
331+
@panic("unable to make tmp dir for testing: didn't find '.' in the preopens");
332332

333333
return std.fs.Dir{ .fd = preopen.fd };
334334
} else {

src/test.zig

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1188,7 +1188,6 @@ pub const TestContext = struct {
11881188
.wasmtime => |wasmtime_bin_name| if (enable_wasmtime) {
11891189
try argv.append(wasmtime_bin_name);
11901190
try argv.append("--dir=.");
1191-
try argv.append("--mapdir=/cwd::.");
11921191
try argv.append(exe_path);
11931192
} else {
11941193
return; // wasmtime not available; pass test.

0 commit comments

Comments
 (0)