authorgravatar for topolarity@tapscott.meCody Tapscott <topolarity@tapscott.me> 2022-03-03 12:25:21-07:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2022-04-16 18:08:05+02:00
log7b090df6689909ba82ff46be4d21733f84f32c33
tree105f0782e6ec8cd105402c689db814f8720c5858
parent8f75823728d247bc11d916b7257d7eebc7be06de

stdlib std.os: Improve wasi-libc parity for WASI CWD emulation

Two major changes here: 1. We store the CWD as a simple `[]const u8` and lookup Preopens for every absolute or CWD-referenced file operation, based on the Preopen with the longest match (i.e. most specific path) 2. Preorders are normalized to POSIX absolute paths at init time. Behavior depends on the "cwd_root" parameter of `initPreopensWasi`: `cwd_root` is used for any Preopens that start with "." For example: "./foo/bar" - inits to -> "{cwd_root}/foo/bar" "foo/bar" - inits to -> "/foo/bar" "/foo/bar" - inits to -> "/foo/bar" `cwd_root` must be an absolute path. Using "/" as `cwd_root` gives behavior similar to wasi-libc.

7 files changed, 159 insertions(+), 197 deletions(-)

doc/langref.html.in+1-1
......@@ -11279,7 +11279,7 @@ pub fn main() !void {
1127911279 var preopens = PreopenList.init(gpa);
1128011280 defer preopens.deinit();
1128111281
11282 try preopens.populate();
11282 try preopens.populate(null);
1128311283
1128411284 for (preopens.asSlice()) |preopen, i| {
1128511285 std.debug.print("{}: {}\n", .{ i, preopen });
lib/std/fs/path.zig+4-4
......@@ -735,7 +735,7 @@ pub fn resolvePosix(allocator: Allocator, paths: []const []const u8) ![]u8 {
735735
736736test "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, ".");
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, ".");
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
803803test "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, ".");
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, ".");
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+7-7
......@@ -47,7 +47,7 @@ fn testReadLink(dir: Dir, target_path: []const u8, symlink_path: []const u8) !vo
4747
4848test "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, ".");
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
6767test "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, ".");
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
104104test "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, ".");
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();
......@@ -535,7 +535,7 @@ test "rename" {
535535
536536test "renameAbsolute" {
537537 if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest;
538 if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, ".");
538 if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/");
539539
540540 var tmp_dir = tmpDir(.{});
541541 defer tmp_dir.cleanup();
......@@ -980,7 +980,7 @@ test "open file with exclusive nonblocking lock twice (absolute paths)" {
980980
981981test "walker" {
982982 if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest;
983 if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, ".");
983 if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/");
984984
985985 var tmp = tmpDir(.{ .iterate = true });
986986 defer tmp.cleanup();
......@@ -1031,7 +1031,7 @@ test "walker" {
10311031
10321032test ". and .. in fs.Dir functions" {
10331033 if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest;
1034 if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, ".");
1034 if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/");
10351035
10361036 var tmp = tmpDir(.{});
10371037 defer tmp.cleanup();
......@@ -1060,7 +1060,7 @@ test ". and .. in fs.Dir functions" {
10601060
10611061test ". and .. in absolute functions" {
10621062 if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest;
1063 if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, ".");
1063 if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/");
10641064
10651065 var tmp = tmpDir(.{});
10661066 defer tmp.cleanup();
lib/std/fs/wasi.zig+84-28
......@@ -3,6 +3,8 @@ const builtin = @import("builtin");
33const os = std.os;
44const mem = std.mem;
55const math = std.math;
6const fs = std.fs;
7const assert = std.debug.assert;
68const Allocator = mem.Allocator;
79const wasi = std.os.wasi;
810const fd_t = wasi.fd_t;
......@@ -34,16 +36,27 @@ pub const PreopenType = union(PreopenTypeTag) {
3436
3537 // Checks whether `other` refers to a subdirectory of `self` and, if so,
3638 // returns the relative path to `other` from `self`
39 //
40 // Expects `other` to be a canonical path, not containing "." or ".."
3741 pub fn getRelativePath(self: Self, other: PreopenType) ?[]const u8 {
3842 if (std.meta.activeTag(self) != std.meta.activeTag(other)) return null;
3943
4044 switch (self) {
41 PreopenTypeTag.Dir => |this_path| {
45 PreopenTypeTag.Dir => |self_path| {
4246 const other_path = other.Dir;
43 if (mem.indexOfDiff(u8, this_path, other_path)) |index| {
44 if (index < this_path.len) return null;
47 if (mem.indexOfDiff(u8, self_path, other_path)) |index| {
48 if (index < self_path.len) return null;
49 }
50
51 const rel_path = other_path[self_path.len..];
52 if (rel_path.len == 0) {
53 return rel_path;
54 } else if (rel_path[0] == '/') {
55 return rel_path[1..];
56 } else {
57 if (self_path[self_path.len - 1] != '/') return null;
58 return rel_path;
4559 }
46 return other_path[this_path.len..];
4760 },
4861 }
4962 }
......@@ -130,7 +143,22 @@ pub const PreopenList = struct {
130143 /// the preopen list still contains all valid preopened file descriptors that are valid
131144 /// for use. Therefore, it is fine to call `find`, `asSlice`, or `toOwnedSlice`. Finally,
132145 /// `deinit` still must be called!
133 pub fn populate(self: *Self) Error!void {
146 ///
147 /// Usage of `cwd_root`:
148 /// If provided, `cwd_root` is inserted as prefix for any Preopens that
149 /// begin with "." and all paths are normalized as POSIX-style absolute
150 /// paths. `cwd_root` must be an absolute path.
151 ///
152 /// For example:
153 /// "./foo/bar" -> "{cwd_root}/foo/bar"
154 /// "foo/bar" -> "/foo/bar"
155 /// "/foo/bar" -> "/foo/bar"
156 ///
157 /// If `cwd_root` is not provided, all preopen directories are unmodified.
158 ///
159 pub fn populate(self: *Self, cwd_root: ?[]const u8) Error!void {
160 if (cwd_root) |root| assert(fs.path.isAbsolute(root));
161
134162 // Clear contents if we're being called again
135163 for (self.toOwnedSlice()) |preopen| {
136164 switch (preopen.@"type") {
......@@ -140,6 +168,7 @@ pub const PreopenList = struct {
140168 errdefer self.deinit();
141169 var fd: fd_t = 3; // start fd has to be beyond stdio fds
142170
171 var path_buf: [fs.MAX_PATH_BYTES]u8 = undefined;
143172 while (true) {
144173 var buf: prestat_t = undefined;
145174 switch (wasi.fd_prestat_get(fd, &buf)) {
......@@ -156,14 +185,34 @@ pub const PreopenList = struct {
156185 else => |err| return os.unexpectedErrno(err),
157186 }
158187 const preopen_len = buf.u.dir.pr_name_len;
159 const path_buf = try self.buffer.allocator.alloc(u8, preopen_len);
160 mem.set(u8, path_buf, 0);
161 switch (wasi.fd_prestat_dir_name(fd, path_buf.ptr, preopen_len)) {
188
189 mem.set(u8, path_buf[0..preopen_len], 0);
190 switch (wasi.fd_prestat_dir_name(fd, &path_buf, preopen_len)) {
162191 .SUCCESS => {},
163192 else => |err| return os.unexpectedErrno(err),
164193 }
165194
166 const preopen = Preopen.new(fd, PreopenType{ .Dir = path_buf });
195 // Unfortunately, WASI runtimes (e.g. wasmer) are not consistent about whether the
196 // NULL sentinel is included in the reported Preopen name_len
197 const raw_path = if (path_buf[preopen_len - 1] == 0) blk: {
198 break :blk path_buf[0 .. preopen_len - 1];
199 } else path_buf[0..preopen_len];
200
201 // If we were provided a CWD root to resolve against, we try to treat Preopen dirs as
202 // POSIX paths, relative to "/" or `cwd_root` depending on whether they start with "."
203 const path = if (cwd_root) |cwd| blk: {
204 const resolve_paths: [][]const u8 = if (raw_path[0] == '.') &.{ cwd, raw_path } else &.{ "/", raw_path };
205 break :blk fs.path.resolve(self.buffer.allocator, resolve_paths) catch |err| switch (err) {
206 error.CurrentWorkingDirectoryUnlinked => unreachable, // root is absolute, so CWD not queried
207 else => |e| return e,
208 };
209 } else blk: {
210 // If we were provided no CWD root, we preserve the preopen dir without resolving
211 break :blk try self.buffer.allocator.dupe(u8, raw_path);
212 };
213 errdefer self.buffer.allocator.free(path);
214 const preopen = Preopen.new(fd, .{ .Dir = path });
215
167216 try self.buffer.append(preopen);
168217 fd = try math.add(fd_t, fd, 1);
169218 }
......@@ -171,27 +220,22 @@ pub const PreopenList = struct {
171220
172221 /// Find a preopen which includes access to `preopen_type`.
173222 ///
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.
223 /// If multiple preopens match the provided resource, the most specific
224 /// match is returned. More recent preopens take priority, as well.
177225 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 };
226 var best_match: ?PreopenUri = null;
227
228 for (self.buffer.items) |preopen| {
229 if (preopen.@"type".getRelativePath(preopen_type)) |rel_path| {
230 if (best_match == null or rel_path.len <= best_match.?.relative_path.len) {
231 best_match = PreopenUri{
232 .base = preopen,
233 .relative_path = if (rel_path.len == 0) "." else rel_path,
234 };
235 }
192236 }
193237 }
194 return null;
238 return best_match;
195239 }
196240
197241 /// Find preopen by fd. If the preopen exists, return it.
......@@ -233,8 +277,20 @@ test "extracting WASI preopens" {
233277 var preopens = PreopenList.init(std.testing.allocator);
234278 defer preopens.deinit();
235279
236 try preopens.populate();
280 try preopens.populate(null);
237281
238282 const preopen = preopens.find(PreopenType{ .Dir = "." }) orelse unreachable;
239283 try std.testing.expect(preopen.@"type".eql(PreopenType{ .Dir = "." }));
284
285 const po_type1 = PreopenType{ .Dir = "/" };
286 try std.testing.expect(std.mem.eql(u8, po_type1.getRelativePath(.{ .Dir = "/" }).?, ""));
287 try std.testing.expect(std.mem.eql(u8, po_type1.getRelativePath(.{ .Dir = "/test/foobar" }).?, "test/foobar"));
288
289 const po_type2 = PreopenType{ .Dir = "/test/foo" };
290 try std.testing.expect(po_type2.getRelativePath(.{ .Dir = "/test/foobar" }) == null);
291
292 const po_type3 = PreopenType{ .Dir = "/test" };
293 try std.testing.expect(std.mem.eql(u8, po_type3.getRelativePath(.{ .Dir = "/test" }).?, ""));
294 try std.testing.expect(std.mem.eql(u8, po_type3.getRelativePath(.{ .Dir = "/test/" }).?, ""));
295 try std.testing.expect(std.mem.eql(u8, po_type3.getRelativePath(.{ .Dir = "/test/foo/bar" }).?, "foo/bar"));
240296}
lib/std/os.zig+55-149
......@@ -1431,10 +1431,8 @@ var wasi_cwd = if (builtin.os.tag == .wasi and !builtin.link_libc) struct {
14311431 preopens: ?PreopenList = null,
14321432 // Memory buffer for storing the relative portion of the CWD
14331433 path_buffer: [MAX_PATH_BYTES]u8 = undefined,
1434 // Current Working Directory, stored as an fd_t and a relative path
1435 cwd: ?RelativePathWasi = null,
1436 // Preopen associated with `cwd`, if any
1437 cwd_preopen: ?Preopen = null,
1434 // The absolute path associated with the current working directory
1435 cwd: []const u8 = "/",
14381436}{} else undefined;
14391437
14401438/// Initialize the available Preopen list on WASI and set the CWD to `cwd_init`.
......@@ -1444,31 +1442,33 @@ var wasi_cwd = if (builtin.os.tag == .wasi and !builtin.link_libc) struct {
14441442/// This must be called before using any relative or absolute paths with `std.os`
14451443/// functions, if you are on WASI without linking libc.
14461444///
1445/// The current working directory is initialized to `cwd_root`, and `cwd_root`
1446/// is inserted as a prefix for any Preopens whose dir begins with "."
1447/// For example:
1448/// "./foo/bar" - canonicalizes to -> "{cwd_root}/foo/bar"
1449/// "foo/bar" - canonicalizes to -> "/foo/bar"
1450/// "/foo/bar" - canonicalizes to -> "/foo/bar"
1451///
1452/// `cwd_root` must be an absolute path. For initialization behavior similar to
1453/// wasi-libc, use "/" as the `cwd_root`
1454///
14471455/// `alloc` must not be a temporary or leak-detecting allocator, since `std.os`
14481456/// retains ownership of allocations internally and may never call free().
1449pub fn initPreopensWasi(alloc: Allocator, cwd_init: ?[]const u8) !void {
1457pub fn initPreopensWasi(alloc: Allocator, cwd_root: []const u8) !void {
14501458 if (builtin.os.tag == .wasi) {
14511459 if (!builtin.link_libc) {
1452 if (wasi_cwd.preopens == null) {
1453 var preopen_list = PreopenList.init(alloc);
1454 try preopen_list.populate();
1455 wasi_cwd.preopens = preopen_list;
1456 }
1457 if (cwd_init) |cwd| {
1458 const preopen = wasi_cwd.preopens.?.findContaining(.{ .Dir = cwd });
1459 if (preopen) |po| {
1460 wasi_cwd.cwd_preopen = po.base;
1461 wasi_cwd.cwd = RelativePathWasi{
1462 .dir_fd = po.base.fd,
1463 .relative_path = po.relative_path,
1464 };
1465 } else {
1466 // No matching preopen found
1467 return error.FileNotFound;
1468 }
1469 }
1460 var preopen_list = PreopenList.init(alloc);
1461 errdefer preopen_list.deinit();
1462 try preopen_list.populate(cwd_root);
1463
1464 var path_alloc = std.heap.FixedBufferAllocator.init(&wasi_cwd.path_buffer);
1465 wasi_cwd.cwd = try path_alloc.allocator().dupe(u8, cwd_root);
1466
1467 if (wasi_cwd.preopens) |preopens| preopens.deinit();
1468 wasi_cwd.preopens = preopen_list;
14701469 } else {
1471 if (cwd_init) |cwd| try chdir(cwd);
1470 // wasi-libc defaults to an effective CWD root of "/"
1471 if (!mem.eql(u8, cwd_root, "/")) return error.UnsupportedDirectory;
14721472 }
14731473 }
14741474}
......@@ -1477,69 +1477,22 @@ pub fn initPreopensWasi(alloc: Allocator, cwd_init: ?[]const u8) !void {
14771477///
14781478/// For absolute paths, this automatically searches among available Preopens to find
14791479/// a match. For relative paths, it uses the "emulated" CWD.
1480/// Automatically looks up the correct Preopen corresponding to the provided path.
14801481pub fn resolvePathWasi(path: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) !RelativePathWasi {
1481 // Note: Due to WASI's "sandboxed" file handles, operations with this RelativePathWasi
1482 // will fail if the relative path navigates outside of `dir_fd` using ".."
1483 return resolvePathAndGetWasiPreopen(path, null, out_buffer);
1484}
1485
1486fn resolvePathAndGetWasiPreopen(path: []const u8, preopen: ?*?Preopen, out_buffer: *[MAX_PATH_BYTES]u8) !RelativePathWasi {
14871482 var allocator = std.heap.FixedBufferAllocator.init(out_buffer);
14881483 var alloc = allocator.allocator();
14891484
1490 if (fs.path.isAbsolute(path) or wasi_cwd.cwd == null) {
1491 if (wasi_cwd.preopens == null) @panic("On WASI, `initPreopensWasi` must be called to initialize preopens " ++
1492 "before using any CWD-relative or absolute paths.\n");
1493
1494 if (mem.startsWith(u8, path, "/preopens/fd/")) {
1495 // "/preopens/fd/<N>" is a special prefix, which refers to a Preopen directly by fd
1496 const fd_start = "/preopens/fd/".len;
1497 const fd_end = mem.indexOfScalarPos(u8, path, fd_start, '/') orelse path.len;
1498 const fd = std.fmt.parseUnsigned(fd_t, path[fd_start..fd_end], 10) catch unreachable;
1499 const rel_path = if (path.len > fd_end + 1) path[fd_end + 1 ..] else ".";
1500
1501 if (preopen) |p| p.* = wasi_cwd.preopens.?.findByFd(fd);
1502 return RelativePathWasi{
1503 .dir_fd = fd,
1504 .relative_path = alloc.dupe(u8, rel_path) catch return error.NameTooLong,
1505 };
1506 }
1507
1508 // For any other absolute path, we need to lookup a containing Preopen
1509 const abs_path = fs.path.resolve(alloc, &.{ "/", path }) catch return error.NameTooLong;
1510 const preopen_uri = wasi_cwd.preopens.?.findContaining(.{ .Dir = abs_path });
1485 const abs_path = fs.path.resolve(alloc, &.{ wasi_cwd.cwd, path }) catch return error.NameTooLong;
1486 const preopen_uri = wasi_cwd.preopens.?.findContaining(.{ .Dir = abs_path });
15111487
1512 if (preopen_uri) |po| {
1513 if (preopen) |p| p.* = po.base;
1514 return RelativePathWasi{
1515 .dir_fd = po.base.fd,
1516 .relative_path = po.relative_path,
1517 };
1518 } else {
1519 // No matching preopen found
1520 return error.AccessDenied;
1521 }
1522 } else {
1523 const cwd = wasi_cwd.cwd.?;
1524
1525 // If the path is empty or "." or "./", return CWD
1526 if (std.mem.eql(u8, path, ".") or std.mem.eql(u8, path, "./")) {
1527 return cwd;
1528 }
1529
1530 // First resolve a combined path, where the "/" corresponds to `cwd.dir_fd`
1531 // not the true filesystem root
1532 const paths = &.{ "/", cwd.relative_path, path };
1533 const resolved_path = fs.path.resolve(alloc, paths) catch return error.NameTooLong;
1534
1535 // Strip off the fake root to get the relative path w.r.t. `cwd.dir_fd`
1536 const resolved_relative_path = resolved_path[1..];
1537
1538 if (preopen) |p| p.* = wasi_cwd.cwd_preopen;
1488 if (preopen_uri) |po| {
15391489 return RelativePathWasi{
1540 .dir_fd = cwd.dir_fd,
1541 .relative_path = resolved_relative_path,
1490 .dir_fd = po.base.fd,
1491 .relative_path = po.relative_path,
15421492 };
1493 } else {
1494 // No matching preopen found
1495 return error.AccessDenied;
15431496 }
15441497}
15451498
......@@ -1980,11 +1933,7 @@ pub fn getcwd(out_buffer: []u8) GetCwdError![]u8 {
19801933 if (builtin.os.tag == .windows) {
19811934 return windows.GetCurrentDirectory(out_buffer);
19821935 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
1983 var buf: [MAX_PATH_BYTES]u8 = undefined;
1984 const path = realpathWasi(".", &buf) catch |err| switch (err) {
1985 error.NameTooLong => return error.NameTooLong,
1986 error.InvalidHandle => return error.CurrentWorkingDirectoryUnlinked,
1987 };
1936 const path = wasi_cwd.cwd;
19881937 if (out_buffer.len < path.len) return error.NameTooLong;
19891938 std.mem.copy(u8, out_buffer, path);
19901939 return out_buffer[0..path.len];
......@@ -2949,16 +2898,17 @@ pub const ChangeCurDirError = error{
29492898/// `dir_path` is recommended to be a UTF-8 encoded string.
29502899pub fn chdir(dir_path: []const u8) ChangeCurDirError!void {
29512900 if (builtin.os.tag == .wasi and !builtin.link_libc) {
2952 var preopen: ?Preopen = null;
2953 const path = try resolvePathAndGetWasiPreopen(dir_path, &preopen, &wasi_cwd.path_buffer);
2901 var buf: [MAX_PATH_BYTES]u8 = undefined;
2902 var alloc = std.heap.FixedBufferAllocator.init(&buf);
2903 const path = try fs.resolve(alloc.allocator(), &.{ wasi_cwd.cwd, dir_path });
29542904
2955 const dirinfo = try fstatat(path.dir_fd, path.relative_path, 0);
2905 const dirinfo = try fstatat(AT.FDCWD, path, 0);
29562906 if (dirinfo.filetype != .DIRECTORY) {
29572907 return error.NotDir;
29582908 }
29592909
2960 wasi_cwd.cwd_preopen = preopen;
2961 wasi_cwd.cwd = path;
2910 var cwd_alloc = std.heap.FixedBufferAllocator.init(&wasi_cwd.path_buffer);
2911 wasi_cwd.cwd = try cwd_alloc.allocator().dupe(u8, path);
29622912 return;
29632913 } else if (builtin.os.tag == .windows) {
29642914 var utf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined;
......@@ -3010,29 +2960,15 @@ pub const FchdirError = error{
30102960} || UnexpectedError;
30112961
30122962pub fn fchdir(dirfd: fd_t) FchdirError!void {
3013 if (builtin.os.tag == .wasi) {
3014 // Check that this is a directory
3015 const dirinfo = fstatat(dirfd, ".", 0) catch unreachable;
3016 if (dirinfo.filetype != .DIRECTORY) {
3017 return error.NotDir;
3018 }
3019
3020 wasi_cwd.cwd = .{
3021 .dir_fd = dirfd,
3022 .relative_path = ".",
3023 };
3024 wasi_cwd.cwd_preopen = null;
3025 } else {
3026 while (true) {
3027 switch (errno(system.fchdir(dirfd))) {
3028 .SUCCESS => return,
3029 .ACCES => return error.AccessDenied,
3030 .BADF => unreachable,
3031 .NOTDIR => return error.NotDir,
3032 .INTR => continue,
3033 .IO => return error.FileSystem,
3034 else => |err| return unexpectedErrno(err),
3035 }
2963 while (true) {
2964 switch (errno(system.fchdir(dirfd))) {
2965 .SUCCESS => return,
2966 .ACCES => return error.AccessDenied,
2967 .BADF => unreachable,
2968 .NOTDIR => return error.NotDir,
2969 .INTR => continue,
2970 .IO => return error.FileSystem,
2971 else => |err| return unexpectedErrno(err),
30362972 }
30372973 }
30382974}
......@@ -5102,47 +5038,17 @@ pub fn realpath(pathname: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathE
51025038 const pathname_w = try windows.sliceToPrefixedFileW(pathname);
51035039 return realpathW(pathname_w.span(), out_buffer);
51045040 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
5105 return realpathWasi(pathname, out_buffer);
5041 var alloc = std.heap.FixedBufferAllocator.init(out_buffer);
5042
5043 // NOTE: This emulation is incomplete. Symbolic links are not
5044 // currently expanded during path canonicalization.
5045 const paths = &.{ wasi_cwd.cwd, pathname };
5046 return fs.path.resolve(alloc.allocator(), paths) catch error.NameTooLong;
51065047 }
51075048 const pathname_c = try toPosixPath(pathname);
51085049 return realpathZ(&pathname_c, out_buffer);
51095050}
51105051
5111/// Return an emulated canonicalized absolute pathname on WASI.
5112///
5113/// NOTE: This emulation is incomplete. Symbolic links are not
5114/// currently expanded during path canonicalization.
5115fn realpathWasi(pathname: []const u8, out_buffer: []u8) ![]u8 {
5116 var alloc = std.heap.FixedBufferAllocator.init(out_buffer);
5117 if (fs.path.isAbsolute(pathname))
5118 return try fs.path.resolve(alloc.allocator(), &.{pathname}) catch error.NameTooLong;
5119 if (wasi_cwd.cwd) |cwd| {
5120 if (wasi_cwd.cwd_preopen) |po| {
5121 var base_cwd_dir = switch (po.@"type") {
5122 .Dir => |dir| dir,
5123 };
5124 const paths: [][]const u8 = if (fs.path.isAbsolute(base_cwd_dir)) blk: {
5125 break :blk &.{ base_cwd_dir, cwd.relative_path, pathname };
5126 } else blk: {
5127 // No absolute path is associated with this preopen, so
5128 // instead we use a special "/preopens/fd/<N>/" prefix
5129 var buf: [16]u8 = undefined;
5130 var fbs = std.io.fixedBufferStream(&buf);
5131 std.fmt.formatInt(po.fd, 10, .lower, .{}, fbs.writer()) catch return error.NameTooLong;
5132 break :blk &.{ "/preopens/fd/", fbs.getWritten(), cwd.relative_path, pathname };
5133 };
5134
5135 return fs.path.resolve(alloc.allocator(), paths) catch error.NameTooLong;
5136 } else {
5137 // The CWD is not rooted to an existing Preopen,
5138 // so we have no way to know its absolute path
5139 return error.InvalidHandle;
5140 }
5141 } else {
5142 return try fs.path.resolve(alloc.allocator(), &.{ "/", pathname }) catch error.NameTooLong;
5143 }
5144}
5145
51465052/// Same as `realpath` except `pathname` is null-terminated.
51475053pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
51485054 if (builtin.os.tag == .windows) {
lib/std/os/test.zig+7-7
......@@ -49,7 +49,7 @@ test "chdir smoke test" {
4949
5050test "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, ".");
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
105105test "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, ".");
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
142142test "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, ".");
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, ".");
200 try os.initPreopensWasi(std.heap.page_allocator, "/");
201201 }
202202 },
203203 .linux, .solaris => {},
......@@ -237,7 +237,7 @@ test "link with relative paths" {
237237
238238test "linkat with different directories" {
239239 switch (native_os) {
240 .wasi => if (!builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "."),
240 .wasi => if (!builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/"),
241241 .linux, .solaris => {},
242242 else => return error.SkipZigTest,
243243 }
......@@ -898,7 +898,7 @@ test "POSIX file locking with fcntl" {
898898
899899test "rename smoke test" {
900900 if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
901 if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, ".");
901 if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/");
902902
903903 var tmp = tmpDir(.{});
904904 defer tmp.cleanup();
......@@ -955,7 +955,7 @@ test "rename smoke test" {
955955
956956test "access smoke test" {
957957 if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
958 if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, ".");
958 if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/");
959959
960960 var tmp = tmpDir(.{});
961961 defer tmp.cleanup();
lib/std/testing.zig+1-1
......@@ -380,7 +380,7 @@ fn getCwdOrWasiPreopen() std.fs.Dir {
380380 if (builtin.os.tag == .wasi and !builtin.link_libc) {
381381 var preopens = std.fs.wasi.PreopenList.init(allocator);
382382 defer preopens.deinit();
383 preopens.populate() catch
383 preopens.populate(null) catch
384384 @panic("unable to make tmp dir for testing: unable to populate preopens");
385385 const preopen = preopens.find(std.fs.wasi.PreopenType{ .Dir = "." }) orelse
386386 @panic("unable to make tmp dir for testing: didn't find '.' in the preopens");