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 {...@@ -11279,7 +11279,7 @@ pub fn main() !void {
11279 var preopens = PreopenList.init(gpa);11279 var preopens = PreopenList.init(gpa);
11280 defer preopens.deinit();11280 defer preopens.deinit();
1128111281
11282 try preopens.populate();11282 try preopens.populate(null);
1128311283
11284 for (preopens.asSlice()) |preopen, i| {11284 for (preopens.asSlice()) |preopen, i| {
11285 std.debug.print("{}: {}\n", .{ i, preopen });11285 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 {...@@ -735,7 +735,7 @@ pub fn resolvePosix(allocator: Allocator, paths: []const []const u8) ![]u8 {
735735
736test "resolve" {736test "resolve" {
737 if (native_os == .wasi and builtin.link_libc) 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, ".");738 if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/");
739739
740 const cwd = try process.getCwdAlloc(testing.allocator);740 const cwd = try process.getCwdAlloc(testing.allocator);
741 defer testing.allocator.free(cwd);741 defer testing.allocator.free(cwd);
...@@ -756,7 +756,7 @@ test "resolveWindows" {...@@ -756,7 +756,7 @@ test "resolveWindows" {
756 return error.SkipZigTest;756 return error.SkipZigTest;
757 }757 }
758 if (native_os == .wasi and builtin.link_libc) 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, ".");759 if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/");
760 if (native_os == .windows) {760 if (native_os == .windows) {
761 const cwd = try process.getCwdAlloc(testing.allocator);761 const cwd = try process.getCwdAlloc(testing.allocator);
762 defer testing.allocator.free(cwd);762 defer testing.allocator.free(cwd);
...@@ -802,7 +802,7 @@ test "resolveWindows" {...@@ -802,7 +802,7 @@ test "resolveWindows" {
802802
803test "resolvePosix" {803test "resolvePosix" {
804 if (native_os == .wasi and builtin.link_libc) 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, ".");805 if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/");
806806
807 try testResolvePosix(&[_][]const u8{ "/a/b", "c" }, "/a/b/c");807 try testResolvePosix(&[_][]const u8{ "/a/b", "c" }, "/a/b/c");
808 try testResolvePosix(&[_][]const u8{ "/a/b", "c", "//d", "e///" }, "/d/e");808 try testResolvePosix(&[_][]const u8{ "/a/b", "c", "//d", "e///" }, "/d/e");
...@@ -1216,7 +1216,7 @@ test "relative" {...@@ -1216,7 +1216,7 @@ test "relative" {
1216 return error.SkipZigTest;1216 return error.SkipZigTest;
1217 }1217 }
1218 if (native_os == .wasi and builtin.link_libc) 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, ".");1219 if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/");
12201220
1221 try testRelativeWindows("c:/blah\\blah", "d:/games", "D:\\games");1221 try testRelativeWindows("c:/blah\\blah", "d:/games", "D:\\games");
1222 try testRelativeWindows("c:/aaaa/bbbb", "c:/aaaa", "..");1222 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...@@ -47,7 +47,7 @@ fn testReadLink(dir: Dir, target_path: []const u8, symlink_path: []const u8) !vo
4747
48test "accessAbsolute" {48test "accessAbsolute" {
49 if (builtin.os.tag == .wasi and builtin.link_libc) 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, ".");50 if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/");
5151
52 var tmp = tmpDir(.{});52 var tmp = tmpDir(.{});
53 defer tmp.cleanup();53 defer tmp.cleanup();
...@@ -66,7 +66,7 @@ test "accessAbsolute" {...@@ -66,7 +66,7 @@ test "accessAbsolute" {
6666
67test "openDirAbsolute" {67test "openDirAbsolute" {
68 if (builtin.os.tag == .wasi and builtin.link_libc) 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, ".");69 if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/");
7070
71 var tmp = tmpDir(.{});71 var tmp = tmpDir(.{});
72 defer tmp.cleanup();72 defer tmp.cleanup();
...@@ -103,7 +103,7 @@ test "openDir cwd parent .." {...@@ -103,7 +103,7 @@ test "openDir cwd parent .." {
103103
104test "readLinkAbsolute" {104test "readLinkAbsolute" {
105 if (builtin.os.tag == .wasi and builtin.link_libc) 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, ".");106 if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/");
107107
108 var tmp = tmpDir(.{});108 var tmp = tmpDir(.{});
109 defer tmp.cleanup();109 defer tmp.cleanup();
...@@ -535,7 +535,7 @@ test "rename" {...@@ -535,7 +535,7 @@ test "rename" {
535535
536test "renameAbsolute" {536test "renameAbsolute" {
537 if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest;537 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
540 var tmp_dir = tmpDir(.{});540 var tmp_dir = tmpDir(.{});
541 defer tmp_dir.cleanup();541 defer tmp_dir.cleanup();
...@@ -980,7 +980,7 @@ test "open file with exclusive nonblocking lock twice (absolute paths)" {...@@ -980,7 +980,7 @@ test "open file with exclusive nonblocking lock twice (absolute paths)" {
980980
981test "walker" {981test "walker" {
982 if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest;982 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
985 var tmp = tmpDir(.{ .iterate = true });985 var tmp = tmpDir(.{ .iterate = true });
986 defer tmp.cleanup();986 defer tmp.cleanup();
...@@ -1031,7 +1031,7 @@ test "walker" {...@@ -1031,7 +1031,7 @@ test "walker" {
10311031
1032test ". and .. in fs.Dir functions" {1032test ". and .. in fs.Dir functions" {
1033 if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest;1033 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
1036 var tmp = tmpDir(.{});1036 var tmp = tmpDir(.{});
1037 defer tmp.cleanup();1037 defer tmp.cleanup();
...@@ -1060,7 +1060,7 @@ test ". and .. in fs.Dir functions" {...@@ -1060,7 +1060,7 @@ test ". and .. in fs.Dir functions" {
10601060
1061test ". and .. in absolute functions" {1061test ". and .. in absolute functions" {
1062 if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest;1062 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
1065 var tmp = tmpDir(.{});1065 var tmp = tmpDir(.{});
1066 defer tmp.cleanup();1066 defer tmp.cleanup();
lib/std/fs/wasi.zig+84-28
...@@ -3,6 +3,8 @@ const builtin = @import("builtin");...@@ -3,6 +3,8 @@ const builtin = @import("builtin");
3const os = std.os;3const os = std.os;
4const mem = std.mem;4const mem = std.mem;
5const math = std.math;5const math = std.math;
6const fs = std.fs;
7const assert = std.debug.assert;
6const Allocator = mem.Allocator;8const Allocator = mem.Allocator;
7const wasi = std.os.wasi;9const wasi = std.os.wasi;
8const fd_t = wasi.fd_t;10const fd_t = wasi.fd_t;
...@@ -34,16 +36,27 @@ pub const PreopenType = union(PreopenTypeTag) {...@@ -34,16 +36,27 @@ pub const PreopenType = union(PreopenTypeTag) {
3436
35 // Checks whether `other` refers to a subdirectory of `self` and, if so,37 // Checks whether `other` refers to a subdirectory of `self` and, if so,
36 // returns the relative path to `other` from `self`38 // returns the relative path to `other` from `self`
39 //
40 // Expects `other` to be a canonical path, not containing "." or ".."
37 pub fn getRelativePath(self: Self, other: PreopenType) ?[]const u8 {41 pub fn getRelativePath(self: Self, other: PreopenType) ?[]const u8 {
38 if (std.meta.activeTag(self) != std.meta.activeTag(other)) return null;42 if (std.meta.activeTag(self) != std.meta.activeTag(other)) return null;
3943
40 switch (self) {44 switch (self) {
41 PreopenTypeTag.Dir => |this_path| {45 PreopenTypeTag.Dir => |self_path| {
42 const other_path = other.Dir;46 const other_path = other.Dir;
43 if (mem.indexOfDiff(u8, this_path, other_path)) |index| {47 if (mem.indexOfDiff(u8, self_path, other_path)) |index| {
44 if (index < this_path.len) return null;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;
45 }59 }
46 return other_path[this_path.len..];
47 },60 },
48 }61 }
49 }62 }
...@@ -130,7 +143,22 @@ pub const PreopenList = struct {...@@ -130,7 +143,22 @@ pub const PreopenList = struct {
130 /// the preopen list still contains all valid preopened file descriptors that are valid143 /// the preopen list still contains all valid preopened file descriptors that are valid
131 /// for use. Therefore, it is fine to call `find`, `asSlice`, or `toOwnedSlice`. Finally,144 /// for use. Therefore, it is fine to call `find`, `asSlice`, or `toOwnedSlice`. Finally,
132 /// `deinit` still must be called!145 /// `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
134 // Clear contents if we're being called again162 // Clear contents if we're being called again
135 for (self.toOwnedSlice()) |preopen| {163 for (self.toOwnedSlice()) |preopen| {
136 switch (preopen.@"type") {164 switch (preopen.@"type") {
...@@ -140,6 +168,7 @@ pub const PreopenList = struct {...@@ -140,6 +168,7 @@ pub const PreopenList = struct {
140 errdefer self.deinit();168 errdefer self.deinit();
141 var fd: fd_t = 3; // start fd has to be beyond stdio fds169 var fd: fd_t = 3; // start fd has to be beyond stdio fds
142170
171 var path_buf: [fs.MAX_PATH_BYTES]u8 = undefined;
143 while (true) {172 while (true) {
144 var buf: prestat_t = undefined;173 var buf: prestat_t = undefined;
145 switch (wasi.fd_prestat_get(fd, &buf)) {174 switch (wasi.fd_prestat_get(fd, &buf)) {
...@@ -156,14 +185,34 @@ pub const PreopenList = struct {...@@ -156,14 +185,34 @@ pub const PreopenList = struct {
156 else => |err| return os.unexpectedErrno(err),185 else => |err| return os.unexpectedErrno(err),
157 }186 }
158 const preopen_len = buf.u.dir.pr_name_len;187 const preopen_len = buf.u.dir.pr_name_len;
159 const path_buf = try self.buffer.allocator.alloc(u8, preopen_len);188
160 mem.set(u8, path_buf, 0);189 mem.set(u8, path_buf[0..preopen_len], 0);
161 switch (wasi.fd_prestat_dir_name(fd, path_buf.ptr, preopen_len)) {190 switch (wasi.fd_prestat_dir_name(fd, &path_buf, preopen_len)) {
162 .SUCCESS => {},191 .SUCCESS => {},
163 else => |err| return os.unexpectedErrno(err),192 else => |err| return os.unexpectedErrno(err),
164 }193 }
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
167 try self.buffer.append(preopen);216 try self.buffer.append(preopen);
168 fd = try math.add(fd_t, fd, 1);217 fd = try math.add(fd_t, fd, 1);
169 }218 }
...@@ -171,27 +220,22 @@ pub const PreopenList = struct {...@@ -171,27 +220,22 @@ pub const PreopenList = struct {
171220
172 /// Find a preopen which includes access to `preopen_type`.221 /// Find a preopen which includes access to `preopen_type`.
173 ///222 ///
174 /// If the preopen exists, `relative_path` is updated to point to the relative223 /// If multiple preopens match the provided resource, the most specific
175 /// portion of `preopen_type` and the matching Preopen is returned. If multiple224 /// match is returned. More recent preopens take priority, as well.
176 /// preopens match the provided resource, the most recent one is used.
177 pub fn findContaining(self: Self, preopen_type: PreopenType) ?PreopenUri {225 pub fn findContaining(self: Self, preopen_type: PreopenType) ?PreopenUri {
178 // Search in reverse, so that most recently added preopens take precedence226 var best_match: ?PreopenUri = null;
179 var k: usize = self.buffer.items.len;227
180 while (k > 0) {228 for (self.buffer.items) |preopen| {
181 k -= 1;229 if (preopen.@"type".getRelativePath(preopen_type)) |rel_path| {
182230 if (best_match == null or rel_path.len <= best_match.?.relative_path.len) {
183 const preopen = self.buffer.items[k];231 best_match = PreopenUri{
184 if (preopen.@"type".getRelativePath(preopen_type)) |rel_path_orig| {232 .base = preopen,
185 var rel_path = rel_path_orig;233 .relative_path = if (rel_path.len == 0) "." else rel_path,
186 while (rel_path.len > 0 and rel_path[0] == '/') rel_path = rel_path[1..];234 };
187235 }
188 return PreopenUri{
189 .base = preopen,
190 .relative_path = if (rel_path.len == 0) "." else rel_path,
191 };
192 }236 }
193 }237 }
194 return null;238 return best_match;
195 }239 }
196240
197 /// Find preopen by fd. If the preopen exists, return it.241 /// Find preopen by fd. If the preopen exists, return it.
...@@ -233,8 +277,20 @@ test "extracting WASI preopens" {...@@ -233,8 +277,20 @@ test "extracting WASI preopens" {
233 var preopens = PreopenList.init(std.testing.allocator);277 var preopens = PreopenList.init(std.testing.allocator);
234 defer preopens.deinit();278 defer preopens.deinit();
235279
236 try preopens.populate();280 try preopens.populate(null);
237281
238 const preopen = preopens.find(PreopenType{ .Dir = "." }) orelse unreachable;282 const preopen = preopens.find(PreopenType{ .Dir = "." }) orelse unreachable;
239 try std.testing.expect(preopen.@"type".eql(PreopenType{ .Dir = "." }));283 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"));
240}296}
lib/std/os.zig+55-149
...@@ -1431,10 +1431,8 @@ var wasi_cwd = if (builtin.os.tag == .wasi and !builtin.link_libc) struct {...@@ -1431,10 +1431,8 @@ var wasi_cwd = if (builtin.os.tag == .wasi and !builtin.link_libc) struct {
1431 preopens: ?PreopenList = null,1431 preopens: ?PreopenList = null,
1432 // Memory buffer for storing the relative portion of the CWD1432 // Memory buffer for storing the relative portion of the CWD
1433 path_buffer: [MAX_PATH_BYTES]u8 = undefined,1433 path_buffer: [MAX_PATH_BYTES]u8 = undefined,
1434 // Current Working Directory, stored as an fd_t and a relative path1434 // The absolute path associated with the current working directory
1435 cwd: ?RelativePathWasi = null,1435 cwd: []const u8 = "/",
1436 // Preopen associated with `cwd`, if any
1437 cwd_preopen: ?Preopen = null,
1438}{} else undefined;1436}{} else undefined;
14391437
1440/// Initialize the available Preopen list on WASI and set the CWD to `cwd_init`.1438/// 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 {...@@ -1444,31 +1442,33 @@ var wasi_cwd = if (builtin.os.tag == .wasi and !builtin.link_libc) struct {
1444/// This must be called before using any relative or absolute paths with `std.os`1442/// This must be called before using any relative or absolute paths with `std.os`
1445/// functions, if you are on WASI without linking libc.1443/// functions, if you are on WASI without linking libc.
1446///1444///
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///
1447/// `alloc` must not be a temporary or leak-detecting allocator, since `std.os`1455/// `alloc` must not be a temporary or leak-detecting allocator, since `std.os`
1448/// retains ownership of allocations internally and may never call free().1456/// 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 {
1450 if (builtin.os.tag == .wasi) {1458 if (builtin.os.tag == .wasi) {
1451 if (!builtin.link_libc) {1459 if (!builtin.link_libc) {
1452 if (wasi_cwd.preopens == null) {1460 var preopen_list = PreopenList.init(alloc);
1453 var preopen_list = PreopenList.init(alloc);1461 errdefer preopen_list.deinit();
1454 try preopen_list.populate();1462 try preopen_list.populate(cwd_root);
1455 wasi_cwd.preopens = preopen_list;1463
1456 }1464 var path_alloc = std.heap.FixedBufferAllocator.init(&wasi_cwd.path_buffer);
1457 if (cwd_init) |cwd| {1465 wasi_cwd.cwd = try path_alloc.allocator().dupe(u8, cwd_root);
1458 const preopen = wasi_cwd.preopens.?.findContaining(.{ .Dir = cwd });1466
1459 if (preopen) |po| {1467 if (wasi_cwd.preopens) |preopens| preopens.deinit();
1460 wasi_cwd.cwd_preopen = po.base;1468 wasi_cwd.preopens = preopen_list;
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 }
1470 } else {1469 } 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;
1472 }1472 }
1473 }1473 }
1474}1474}
...@@ -1477,69 +1477,22 @@ pub fn initPreopensWasi(alloc: Allocator, cwd_init: ?[]const u8) !void {...@@ -1477,69 +1477,22 @@ pub fn initPreopensWasi(alloc: Allocator, cwd_init: ?[]const u8) !void {
1477///1477///
1478/// For absolute paths, this automatically searches among available Preopens to find1478/// For absolute paths, this automatically searches among available Preopens to find
1479/// a match. For relative paths, it uses the "emulated" CWD.1479/// a match. For relative paths, it uses the "emulated" CWD.
1480/// Automatically looks up the correct Preopen corresponding to the provided path.
1480pub fn resolvePathWasi(path: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) !RelativePathWasi {1481pub 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 {
1487 var allocator = std.heap.FixedBufferAllocator.init(out_buffer);1482 var allocator = std.heap.FixedBufferAllocator.init(out_buffer);
1488 var alloc = allocator.allocator();1483 var alloc = allocator.allocator();
14891484
1490 if (fs.path.isAbsolute(path) or wasi_cwd.cwd == null) {1485 const abs_path = fs.path.resolve(alloc, &.{ wasi_cwd.cwd, path }) catch return error.NameTooLong;
1491 if (wasi_cwd.preopens == null) @panic("On WASI, `initPreopensWasi` must be called to initialize preopens " ++1486 const preopen_uri = wasi_cwd.preopens.?.findContaining(.{ .Dir = abs_path });
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 });
15111487
1512 if (preopen_uri) |po| {1488 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;
1539 return RelativePathWasi{1489 return RelativePathWasi{
1540 .dir_fd = cwd.dir_fd,1490 .dir_fd = po.base.fd,
1541 .relative_path = resolved_relative_path,1491 .relative_path = po.relative_path,
1542 };1492 };
1493 } else {
1494 // No matching preopen found
1495 return error.AccessDenied;
1543 }1496 }
1544}1497}
15451498
...@@ -1980,11 +1933,7 @@ pub fn getcwd(out_buffer: []u8) GetCwdError![]u8 {...@@ -1980,11 +1933,7 @@ pub fn getcwd(out_buffer: []u8) GetCwdError![]u8 {
1980 if (builtin.os.tag == .windows) {1933 if (builtin.os.tag == .windows) {
1981 return windows.GetCurrentDirectory(out_buffer);1934 return windows.GetCurrentDirectory(out_buffer);
1982 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {1935 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
1983 var buf: [MAX_PATH_BYTES]u8 = undefined;1936 const path = wasi_cwd.cwd;
1984 const path = realpathWasi(".", &buf) catch |err| switch (err) {
1985 error.NameTooLong => return error.NameTooLong,
1986 error.InvalidHandle => return error.CurrentWorkingDirectoryUnlinked,
1987 };
1988 if (out_buffer.len < path.len) return error.NameTooLong;1937 if (out_buffer.len < path.len) return error.NameTooLong;
1989 std.mem.copy(u8, out_buffer, path);1938 std.mem.copy(u8, out_buffer, path);
1990 return out_buffer[0..path.len];1939 return out_buffer[0..path.len];
...@@ -2949,16 +2898,17 @@ pub const ChangeCurDirError = error{...@@ -2949,16 +2898,17 @@ pub const ChangeCurDirError = error{
2949/// `dir_path` is recommended to be a UTF-8 encoded string.2898/// `dir_path` is recommended to be a UTF-8 encoded string.
2950pub fn chdir(dir_path: []const u8) ChangeCurDirError!void {2899pub fn chdir(dir_path: []const u8) ChangeCurDirError!void {
2951 if (builtin.os.tag == .wasi and !builtin.link_libc) {2900 if (builtin.os.tag == .wasi and !builtin.link_libc) {
2952 var preopen: ?Preopen = null;2901 var buf: [MAX_PATH_BYTES]u8 = undefined;
2953 const path = try resolvePathAndGetWasiPreopen(dir_path, &preopen, &wasi_cwd.path_buffer);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);
2956 if (dirinfo.filetype != .DIRECTORY) {2906 if (dirinfo.filetype != .DIRECTORY) {
2957 return error.NotDir;2907 return error.NotDir;
2958 }2908 }
29592909
2960 wasi_cwd.cwd_preopen = preopen;2910 var cwd_alloc = std.heap.FixedBufferAllocator.init(&wasi_cwd.path_buffer);
2961 wasi_cwd.cwd = path;2911 wasi_cwd.cwd = try cwd_alloc.allocator().dupe(u8, path);
2962 return;2912 return;
2963 } else if (builtin.os.tag == .windows) {2913 } else if (builtin.os.tag == .windows) {
2964 var utf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined;2914 var utf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined;
...@@ -3010,29 +2960,15 @@ pub const FchdirError = error{...@@ -3010,29 +2960,15 @@ pub const FchdirError = error{
3010} || UnexpectedError;2960} || UnexpectedError;
30112961
3012pub fn fchdir(dirfd: fd_t) FchdirError!void {2962pub fn fchdir(dirfd: fd_t) FchdirError!void {
3013 if (builtin.os.tag == .wasi) {2963 while (true) {
3014 // Check that this is a directory2964 switch (errno(system.fchdir(dirfd))) {
3015 const dirinfo = fstatat(dirfd, ".", 0) catch unreachable;2965 .SUCCESS => return,
3016 if (dirinfo.filetype != .DIRECTORY) {2966 .ACCES => return error.AccessDenied,
3017 return error.NotDir;2967 .BADF => unreachable,
3018 }2968 .NOTDIR => return error.NotDir,
30192969 .INTR => continue,
3020 wasi_cwd.cwd = .{2970 .IO => return error.FileSystem,
3021 .dir_fd = dirfd,2971 else => |err| return unexpectedErrno(err),
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 }
3036 }2972 }
3037 }2973 }
3038}2974}
...@@ -5102,47 +5038,17 @@ pub fn realpath(pathname: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathE...@@ -5102,47 +5038,17 @@ pub fn realpath(pathname: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathE
5102 const pathname_w = try windows.sliceToPrefixedFileW(pathname);5038 const pathname_w = try windows.sliceToPrefixedFileW(pathname);
5103 return realpathW(pathname_w.span(), out_buffer);5039 return realpathW(pathname_w.span(), out_buffer);
5104 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {5040 } 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;
5106 }5047 }
5107 const pathname_c = try toPosixPath(pathname);5048 const pathname_c = try toPosixPath(pathname);
5108 return realpathZ(&pathname_c, out_buffer);5049 return realpathZ(&pathname_c, out_buffer);
5109}5050}
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
5146/// Same as `realpath` except `pathname` is null-terminated.5052/// Same as `realpath` except `pathname` is null-terminated.
5147pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {5053pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
5148 if (builtin.os.tag == .windows) {5054 if (builtin.os.tag == .windows) {
lib/std/os/test.zig+7-7
...@@ -49,7 +49,7 @@ test "chdir smoke test" {...@@ -49,7 +49,7 @@ test "chdir smoke test" {
4949
50test "open smoke test" {50test "open smoke test" {
51 if (native_os == .wasi and builtin.link_libc) 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, ".");52 if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/");
5353
54 // TODO verify file attributes using `fstat`54 // TODO verify file attributes using `fstat`
5555
...@@ -104,7 +104,7 @@ test "open smoke test" {...@@ -104,7 +104,7 @@ test "open smoke test" {
104104
105test "openat smoke test" {105test "openat smoke test" {
106 if (native_os == .wasi and builtin.link_libc) 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, ".");107 if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/");
108108
109 // TODO verify file attributes using `fstatat`109 // TODO verify file attributes using `fstatat`
110110
...@@ -141,7 +141,7 @@ test "openat smoke test" {...@@ -141,7 +141,7 @@ test "openat smoke test" {
141141
142test "symlink with relative paths" {142test "symlink with relative paths" {
143 if (native_os == .wasi and builtin.link_libc) 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, ".");144 if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/");
145145
146 const cwd = fs.cwd();146 const cwd = fs.cwd();
147 cwd.deleteFile("file.txt") catch {};147 cwd.deleteFile("file.txt") catch {};
...@@ -197,7 +197,7 @@ test "link with relative paths" {...@@ -197,7 +197,7 @@ test "link with relative paths" {
197 if (builtin.link_libc) {197 if (builtin.link_libc) {
198 return error.SkipZigTest;198 return error.SkipZigTest;
199 } else {199 } else {
200 try os.initPreopensWasi(std.heap.page_allocator, ".");200 try os.initPreopensWasi(std.heap.page_allocator, "/");
201 }201 }
202 },202 },
203 .linux, .solaris => {},203 .linux, .solaris => {},
...@@ -237,7 +237,7 @@ test "link with relative paths" {...@@ -237,7 +237,7 @@ test "link with relative paths" {
237237
238test "linkat with different directories" {238test "linkat with different directories" {
239 switch (native_os) {239 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, "/"),
241 .linux, .solaris => {},241 .linux, .solaris => {},
242 else => return error.SkipZigTest,242 else => return error.SkipZigTest,
243 }243 }
...@@ -898,7 +898,7 @@ test "POSIX file locking with fcntl" {...@@ -898,7 +898,7 @@ test "POSIX file locking with fcntl" {
898898
899test "rename smoke test" {899test "rename smoke test" {
900 if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;900 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
903 var tmp = tmpDir(.{});903 var tmp = tmpDir(.{});
904 defer tmp.cleanup();904 defer tmp.cleanup();
...@@ -955,7 +955,7 @@ test "rename smoke test" {...@@ -955,7 +955,7 @@ test "rename smoke test" {
955955
956test "access smoke test" {956test "access smoke test" {
957 if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;957 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
960 var tmp = tmpDir(.{});960 var tmp = tmpDir(.{});
961 defer tmp.cleanup();961 defer tmp.cleanup();
lib/std/testing.zig+1-1
...@@ -380,7 +380,7 @@ fn getCwdOrWasiPreopen() std.fs.Dir {...@@ -380,7 +380,7 @@ fn getCwdOrWasiPreopen() std.fs.Dir {
380 if (builtin.os.tag == .wasi and !builtin.link_libc) {380 if (builtin.os.tag == .wasi and !builtin.link_libc) {
381 var preopens = std.fs.wasi.PreopenList.init(allocator);381 var preopens = std.fs.wasi.PreopenList.init(allocator);
382 defer preopens.deinit();382 defer preopens.deinit();
383 preopens.populate() catch383 preopens.populate(null) catch
384 @panic("unable to make tmp dir for testing: unable to populate preopens");384 @panic("unable to make tmp dir for testing: unable to populate preopens");
385 const preopen = preopens.find(std.fs.wasi.PreopenType{ .Dir = "." }) orelse385 const preopen = preopens.find(std.fs.wasi.PreopenType{ .Dir = "." }) orelse
386 @panic("unable to make tmp dir for testing: didn't find '.' in the preopens");386 @panic("unable to make tmp dir for testing: didn't find '.' in the preopens");