authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-11-10 13:50:44-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-12-06 12:15:04-07:00
logd5312d53a066092ba9efd687e25b29a87eb6290c
treeb94a89b89f333cf381046982afe6a86c72c8122e
parent0a2fdfbdb934faae7fcf63e9b3ab760c00f47918

WASI: remove absolute path emulation from std lib

Instead of checking for absolute paths and current working directories in various file system operations, there is one simple solution: allow overriding `std.fs.cwd` on WASI. os.realpath is back to causing a compile error when used on WASI. This caused a compile error in the Sema handling of `@src()`. The compiler should never call realpath, so the commit that made this change is reverted (95ab942184427e7c9b840d71f4d093931e3e48fb). If this breaks debug info, a different strategy is needed to solve it other than using realpath. I also removed the preopens code and replaced it with something much simpler. There is no longer any global state in the standard library. Additionally- * os.openat no longer does an unnecessary fstat on WASI when O.WRONLY is not provided. * os.chdir is back to causing a compile error on WASI.

6 files changed, 148 insertions(+), 550 deletions(-)

lib/std/fs.zig+11-24
...@@ -1130,13 +1130,6 @@ pub const Dir = struct {...@@ -1130,13 +1130,6 @@ pub const Dir = struct {
1130 w.RIGHT.FD_FILESTAT_SET_TIMES |1130 w.RIGHT.FD_FILESTAT_SET_TIMES |
1131 w.RIGHT.FD_FILESTAT_SET_SIZE;1131 w.RIGHT.FD_FILESTAT_SET_SIZE;
1132 }1132 }
1133 if (self.fd == os.wasi.AT.FDCWD or path.isAbsolute(sub_path)) {
1134 // Resolve absolute or CWD-relative paths to a path within a Preopen
1135 var resolved_path_buf: [MAX_PATH_BYTES]u8 = undefined;
1136 const resolved_path = try os.resolvePathWasi(sub_path, &resolved_path_buf);
1137 const fd = try os.openatWasi(resolved_path.dir_fd, resolved_path.relative_path, 0x0, 0x0, fdflags, base, 0x0);
1138 return File{ .handle = fd };
1139 }
1140 const fd = try os.openatWasi(self.fd, sub_path, 0x0, 0x0, fdflags, base, 0x0);1133 const fd = try os.openatWasi(self.fd, sub_path, 0x0, 0x0, fdflags, base, 0x0);
1141 return File{ .handle = fd };1134 return File{ .handle = fd };
1142 }1135 }
...@@ -1301,13 +1294,6 @@ pub const Dir = struct {...@@ -1301,13 +1294,6 @@ pub const Dir = struct {
1301 if (flags.exclusive) {1294 if (flags.exclusive) {
1302 oflags |= w.O.EXCL;1295 oflags |= w.O.EXCL;
1303 }1296 }
1304 if (self.fd == os.wasi.AT.FDCWD or path.isAbsolute(sub_path)) {
1305 // Resolve absolute or CWD-relative paths to a path within a Preopen
1306 var resolved_path_buf: [MAX_PATH_BYTES]u8 = undefined;
1307 const resolved_path = try os.resolvePathWasi(sub_path, &resolved_path_buf);
1308 const fd = try os.openatWasi(resolved_path.dir_fd, resolved_path.relative_path, 0x0, oflags, 0x0, base, 0x0);
1309 return File{ .handle = fd };
1310 }
1311 const fd = try os.openatWasi(self.fd, sub_path, 0x0, oflags, 0x0, base, 0x0);1297 const fd = try os.openatWasi(self.fd, sub_path, 0x0, oflags, 0x0, base, 0x0);
1312 return File{ .handle = fd };1298 return File{ .handle = fd };
1313 }1299 }
...@@ -1711,16 +1697,15 @@ pub const Dir = struct {...@@ -1711,16 +1697,15 @@ pub const Dir = struct {
1711 // TODO do we really need all the rights here?1697 // TODO do we really need all the rights here?
1712 const inheriting: w.rights_t = w.RIGHT.ALL ^ w.RIGHT.SOCK_SHUTDOWN;1698 const inheriting: w.rights_t = w.RIGHT.ALL ^ w.RIGHT.SOCK_SHUTDOWN;
17131699
1714 const result = blk: {1700 const result = os.openatWasi(
1715 if (self.fd == os.wasi.AT.FDCWD or path.isAbsolute(sub_path)) {1701 self.fd,
1716 // Resolve absolute or CWD-relative paths to a path within a Preopen1702 sub_path,
1717 var resolved_path_buf: [MAX_PATH_BYTES]u8 = undefined;1703 symlink_flags,
1718 const resolved_path = try os.resolvePathWasi(sub_path, &resolved_path_buf);1704 w.O.DIRECTORY,
1719 break :blk os.openatWasi(resolved_path.dir_fd, resolved_path.relative_path, symlink_flags, w.O.DIRECTORY, 0x0, base, inheriting);1705 0x0,
1720 } else {1706 base,
1721 break :blk os.openatWasi(self.fd, sub_path, symlink_flags, w.O.DIRECTORY, 0x0, base, inheriting);1707 inheriting,
1722 }1708 );
1723 };
1724 const fd = result catch |err| switch (err) {1709 const fd = result catch |err| switch (err) {
1725 error.FileTooBig => unreachable, // can't happen for directories1710 error.FileTooBig => unreachable, // can't happen for directories
1726 error.IsDir => unreachable, // we're providing O.DIRECTORY1711 error.IsDir => unreachable, // we're providing O.DIRECTORY
...@@ -2667,6 +2652,8 @@ pub const Dir = struct {...@@ -2667,6 +2652,8 @@ pub const Dir = struct {
2667pub fn cwd() Dir {2652pub fn cwd() Dir {
2668 if (builtin.os.tag == .windows) {2653 if (builtin.os.tag == .windows) {
2669 return Dir{ .fd = os.windows.peb().ProcessParameters.CurrentDirectory.Handle };2654 return Dir{ .fd = os.windows.peb().ProcessParameters.CurrentDirectory.Handle };
2655 } else if (builtin.os.tag == .wasi and @hasDecl(root, "wasi_cwd")) {
2656 return root.wasi_cwd();
2670 } else {2657 } else {
2671 return Dir{ .fd = os.AT.FDCWD };2658 return Dir{ .fd = os.AT.FDCWD };
2672 }2659 }
lib/std/fs/wasi.zig+37-274
...@@ -10,284 +10,47 @@ const wasi = std.os.wasi;...@@ -10,284 +10,47 @@ const wasi = std.os.wasi;
10const fd_t = wasi.fd_t;10const fd_t = wasi.fd_t;
11const prestat_t = wasi.prestat_t;11const prestat_t = wasi.prestat_t;
1212
13/// Type-tag of WASI preopen.13pub const Preopens = struct {
14///14 // Indexed by file descriptor number.
15/// WASI currently offers only `Dir` as a valid preopen resource.15 names: []const []const u8,
16pub const PreopenTypeTag = enum {16
17 Dir,17 pub fn find(p: Preopens, name: []const u8) ?os.fd_t {
18};18 for (p.names) |elem_name, i| {
1919 if (mem.eql(u8, elem_name, name)) {
20/// Type of WASI preopen.20 return @intCast(os.fd_t, i);
21///
22/// WASI currently offers only `Dir` as a valid preopen resource.
23pub const PreopenType = union(PreopenTypeTag) {
24 /// Preopened directory type.
25 Dir: []const u8,
26
27 const Self = @This();
28
29 pub fn eql(self: Self, other: PreopenType) bool {
30 if (std.meta.activeTag(self) != std.meta.activeTag(other)) return false;
31
32 switch (self) {
33 PreopenTypeTag.Dir => |this_path| return mem.eql(u8, this_path, other.Dir),
34 }
35 }
36
37 // Checks whether `other` refers to a subdirectory of `self` and, if so,
38 // returns the relative path to `other` from `self`
39 //
40 // Expects `other` to be a canonical path, not containing "." or ".."
41 pub fn getRelativePath(self: Self, other: PreopenType) ?[]const u8 {
42 if (std.meta.activeTag(self) != std.meta.activeTag(other)) return null;
43
44 switch (self) {
45 PreopenTypeTag.Dir => |self_path| {
46 const other_path = other.Dir;
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;
59 }
60 },
61 }
62 }
63
64 pub fn format(self: Self, comptime fmt: []const u8, options: std.fmt.FormatOptions, out_stream: anytype) !void {
65 if (fmt.len != 0) std.fmt.invalidFmtError(fmt, self);
66 _ = options;
67 try out_stream.print("PreopenType{{ ", .{});
68 switch (self) {
69 PreopenType.Dir => |path| try out_stream.print(".Dir = '{}'", .{std.zig.fmtId(path)}),
70 }
71 return out_stream.print(" }}", .{});
72 }
73};
74
75/// WASI preopen struct. This struct consists of a WASI file descriptor
76/// and type of WASI preopen. It can be obtained directly from the WASI
77/// runtime using `PreopenList.populate()` method.
78pub const Preopen = struct {
79 /// WASI file descriptor.
80 fd: fd_t,
81
82 /// Type of the preopen.
83 type: PreopenType,
84
85 /// Construct new `Preopen` instance.
86 pub fn new(fd: fd_t, preopen_type: PreopenType) Preopen {
87 return Preopen{
88 .fd = fd,
89 .type = preopen_type,
90 };
91 }
92};
93
94/// WASI resource identifier struct. This is effectively a path within
95/// a WASI Preopen.
96pub const PreopenUri = struct {
97 /// WASI Preopen containing the resource.
98 base: Preopen,
99 /// Path to resource within `base`.
100 relative_path: []const u8,
101};
102
103/// Dynamically-sized array list of WASI preopens. This struct is a
104/// convenience wrapper for issuing `std.os.wasi.fd_prestat_get` and
105/// `std.os.wasi.fd_prestat_dir_name` syscalls to the WASI runtime, and
106/// collecting the returned preopens.
107///
108/// This struct is intended to be used in any WASI program which intends
109/// to use the capabilities as passed on by the user of the runtime.
110pub const PreopenList = struct {
111 const InnerList = std.ArrayList(Preopen);
112
113 /// Internal dynamically-sized buffer for storing the gathered preopens.
114 buffer: InnerList,
115
116 const Self = @This();
117
118 pub const Error = error{ OutOfMemory, Overflow } || os.UnexpectedError;
119
120 /// Deinitialize with `deinit`.
121 pub fn init(allocator: Allocator) Self {
122 return Self{ .buffer = InnerList.init(allocator) };
123 }
124
125 /// Release all allocated memory.
126 pub fn deinit(pm: Self) void {
127 for (pm.buffer.items) |preopen| {
128 switch (preopen.type) {
129 PreopenType.Dir => |path| pm.buffer.allocator.free(path),
130 }
131 }
132 pm.buffer.deinit();
133 }
134
135 /// Populate the list with the preopens by issuing `std.os.wasi.fd_prestat_get`
136 /// and `std.os.wasi.fd_prestat_dir_name` syscalls to the runtime.
137 ///
138 /// If called more than once, it will clear its contents every time before
139 /// issuing the syscalls.
140 ///
141 /// In the unlinkely event of overflowing the number of available file descriptors,
142 /// returns `error.Overflow`. In this case, even though an error condition was reached
143 /// the preopen list still contains all valid preopened file descriptors that are valid
144 /// for use. Therefore, it is fine to call `find`, `asSlice`, or `toOwnedSlice`. Finally,
145 /// `deinit` still must be called!
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
162 // Clear contents if we're being called again
163 for (try self.toOwnedSlice()) |preopen| {
164 switch (preopen.type) {
165 PreopenType.Dir => |path| self.buffer.allocator.free(path),
166 }
167 }
168 errdefer self.deinit();
169 var fd: fd_t = 3; // start fd has to be beyond stdio fds
170
171 var path_buf: [fs.MAX_PATH_BYTES]u8 = undefined;
172 while (true) {
173 var buf: prestat_t = undefined;
174 switch (wasi.fd_prestat_get(fd, &buf)) {
175 .SUCCESS => {},
176 .OPNOTSUPP => {
177 // not a preopen, so keep going
178 fd = try math.add(fd_t, fd, 1);
179 continue;
180 },
181 .BADF => {
182 // OK, no more fds available
183 break;
184 },
185 else => |err| return os.unexpectedErrno(err),
186 }
187 const preopen_len = buf.u.dir.pr_name_len;
188
189 mem.set(u8, path_buf[0..preopen_len], 0);
190 switch (wasi.fd_prestat_dir_name(fd, &path_buf, preopen_len)) {
191 .SUCCESS => {},
192 else => |err| return os.unexpectedErrno(err),
193 }
194
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 []const u8 = if (raw_path[0] == '.') &.{ cwd, raw_path } else &.{ "/", raw_path };
205 break :blk try fs.path.resolve(self.buffer.allocator, resolve_paths);
206 } else blk: {
207 // If we were provided no CWD root, we preserve the preopen dir without resolving
208 break :blk try self.buffer.allocator.dupe(u8, raw_path);
209 };
210 errdefer self.buffer.allocator.free(path);
211 const preopen = Preopen.new(fd, .{ .Dir = path });
212
213 try self.buffer.append(preopen);
214 fd = try math.add(fd_t, fd, 1);
215 }
216 }
217
218 /// Find a preopen which includes access to `preopen_type`.
219 ///
220 /// If multiple preopens match the provided resource, the most specific
221 /// match is returned. More recent preopens take priority, as well.
222 pub fn findContaining(self: Self, preopen_type: PreopenType) ?PreopenUri {
223 var best_match: ?PreopenUri = null;
224
225 for (self.buffer.items) |preopen| {
226 if (preopen.type.getRelativePath(preopen_type)) |rel_path| {
227 if (best_match == null or rel_path.len <= best_match.?.relative_path.len) {
228 best_match = PreopenUri{
229 .base = preopen,
230 .relative_path = if (rel_path.len == 0) "." else rel_path,
231 };
232 }
233 }
234 }
235 return best_match;
236 }
237
238 /// Find preopen by fd. If the preopen exists, return it.
239 /// Otherwise, return `null`.
240 pub fn findByFd(self: Self, fd: fd_t) ?Preopen {
241 for (self.buffer.items) |preopen| {
242 if (preopen.fd == fd) {
243 return preopen;
244 }21 }
245 }22 }
246 return null;23 return null;
247 }24 }
248
249 /// Find preopen by type. If the preopen exists, return it.
250 /// Otherwise, return `null`.
251 pub fn find(self: Self, preopen_type: PreopenType) ?*const Preopen {
252 for (self.buffer.items) |*preopen| {
253 if (preopen.type.eql(preopen_type)) {
254 return preopen;
255 }
256 }
257 return null;
258 }
259
260 /// Return the inner buffer as read-only slice.
261 pub fn asSlice(self: Self) []const Preopen {
262 return self.buffer.items;
263 }
264
265 /// The caller owns the returned memory. ArrayList becomes empty.
266 pub fn toOwnedSlice(self: *Self) ![]Preopen {
267 return try self.buffer.toOwnedSlice();
268 }
269};25};
27026
271test "extracting WASI preopens" {27pub fn preopensAlloc(gpa: Allocator) Allocator.Error!Preopens {
272 if (builtin.os.tag != .wasi or builtin.link_libc) return error.SkipZigTest;28 var names: std.ArrayListUnmanaged([]const u8) = .{};
27329 defer names.deinit(gpa);
274 var preopens = PreopenList.init(std.testing.allocator);30
275 defer preopens.deinit();31 try names.ensureUnusedCapacity(gpa, 3);
27632
277 try preopens.populate(null);33 names.appendAssumeCapacity("stdin"); // 0
27834 names.appendAssumeCapacity("stdout"); // 1
279 const preopen = preopens.find(PreopenType{ .Dir = "." }) orelse unreachable;35 names.appendAssumeCapacity("stderr"); // 2
280 try std.testing.expect(preopen.type.eql(PreopenType{ .Dir = "." }));36 while (true) {
28137 const fd = @intCast(wasi.fd_t, names.items.len);
282 const po_type1 = PreopenType{ .Dir = "/" };38 var prestat: prestat_t = undefined;
283 try std.testing.expect(std.mem.eql(u8, po_type1.getRelativePath(.{ .Dir = "/" }).?, ""));39 switch (wasi.fd_prestat_get(fd, &prestat)) {
284 try std.testing.expect(std.mem.eql(u8, po_type1.getRelativePath(.{ .Dir = "/test/foobar" }).?, "test/foobar"));40 .SUCCESS => {},
28541 .OPNOTSUPP, .BADF => return .{ .names = names.toOwnedSlice(gpa) },
286 const po_type2 = PreopenType{ .Dir = "/test/foo" };42 else => @panic("fd_prestat_get: unexpected error"),
287 try std.testing.expect(po_type2.getRelativePath(.{ .Dir = "/test/foobar" }) == null);43 }
28844 try names.ensureUnusedCapacity(gpa, 1);
289 const po_type3 = PreopenType{ .Dir = "/test" };45 // This length does not include a null byte. Let's keep it this way to
290 try std.testing.expect(std.mem.eql(u8, po_type3.getRelativePath(.{ .Dir = "/test" }).?, ""));46 // gently encourage WASI implementations to behave properly.
291 try std.testing.expect(std.mem.eql(u8, po_type3.getRelativePath(.{ .Dir = "/test/" }).?, ""));47 const name_len = prestat.u.dir.pr_name_len;
292 try std.testing.expect(std.mem.eql(u8, po_type3.getRelativePath(.{ .Dir = "/test/foo/bar" }).?, "foo/bar"));48 const name = try gpa.alloc(u8, name_len);
49 errdefer gpa.free(name);
50 switch (wasi.fd_prestat_dir_name(fd, name.ptr, name.len)) {
51 .SUCCESS => {},
52 else => @panic("fd_prestat_dir_name: unexpected error"),
53 }
54 names.appendAssumeCapacity(name);
55 }
293}56}
lib/std/os.zig+31-181
...@@ -1521,76 +1521,6 @@ pub fn openW(file_path_w: []const u16, flags: u32, perm: mode_t) OpenError!fd_t...@@ -1521,76 +1521,6 @@ pub fn openW(file_path_w: []const u16, flags: u32, perm: mode_t) OpenError!fd_t
1521 };1521 };
1522}1522}
15231523
1524var wasi_cwd = if (builtin.os.tag == .wasi and !builtin.link_libc) struct {
1525 // List of available Preopens
1526 preopens: ?PreopenList = null,
1527 // Memory buffer for storing the relative portion of the CWD
1528 path_buffer: [MAX_PATH_BYTES]u8 = undefined,
1529 // The absolute path associated with the current working directory
1530 cwd: []const u8 = "/",
1531}{} else undefined;
1532
1533/// Initialize the available Preopen list on WASI and set the CWD to `cwd_init`.
1534/// Note that `cwd_init` corresponds to a Preopen directory, not necessarily
1535/// a POSIX path. For example, "." matches a Preopen provided with `--dir=.`
1536///
1537/// This must be called before using any relative or absolute paths with `std.os`
1538/// functions, if you are on WASI without linking libc.
1539///
1540/// The current working directory is initialized to `cwd_root`, and `cwd_root`
1541/// is inserted as a prefix for any Preopens whose dir begins with "."
1542/// For example:
1543/// "./foo/bar" - canonicalizes to -> "{cwd_root}/foo/bar"
1544/// "foo/bar" - canonicalizes to -> "/foo/bar"
1545/// "/foo/bar" - canonicalizes to -> "/foo/bar"
1546///
1547/// `cwd_root` must be an absolute path. For initialization behavior similar to
1548/// wasi-libc, use "/" as the `cwd_root`
1549///
1550/// `alloc` must not be a temporary or leak-detecting allocator, since `std.os`
1551/// retains ownership of allocations internally and may never call free().
1552pub fn initPreopensWasi(alloc: Allocator, cwd_root: []const u8) !void {
1553 if (builtin.os.tag == .wasi) {
1554 if (!builtin.link_libc) {
1555 var preopen_list = PreopenList.init(alloc);
1556 errdefer preopen_list.deinit();
1557 try preopen_list.populate(cwd_root);
1558
1559 var path_alloc = std.heap.FixedBufferAllocator.init(&wasi_cwd.path_buffer);
1560 wasi_cwd.cwd = try path_alloc.allocator().dupe(u8, cwd_root);
1561
1562 if (wasi_cwd.preopens) |preopens| preopens.deinit();
1563 wasi_cwd.preopens = preopen_list;
1564 } else {
1565 // wasi-libc defaults to an effective CWD root of "/"
1566 if (!mem.eql(u8, cwd_root, "/")) return error.UnsupportedDirectory;
1567 }
1568 }
1569}
1570
1571/// Resolve a relative or absolute path to an handle (`fd_t`) and a relative subpath.
1572///
1573/// For absolute paths, this automatically searches among available Preopens to find
1574/// a match. For relative paths, it uses the "emulated" CWD.
1575/// Automatically looks up the correct Preopen corresponding to the provided path.
1576pub fn resolvePathWasi(path: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) !RelativePathWasi {
1577 var allocator = std.heap.FixedBufferAllocator.init(out_buffer);
1578 var alloc = allocator.allocator();
1579
1580 const abs_path = fs.path.resolve(alloc, &.{ wasi_cwd.cwd, path }) catch return error.NameTooLong;
1581 const preopen_uri = wasi_cwd.preopens.?.findContaining(.{ .Dir = abs_path });
1582
1583 if (preopen_uri) |po| {
1584 return RelativePathWasi{
1585 .dir_fd = po.base.fd,
1586 .relative_path = po.relative_path,
1587 };
1588 } else {
1589 // No matching preopen found
1590 return error.AccessDenied;
1591 }
1592}
1593
1594/// Open and possibly create a file. Keeps trying if it gets interrupted.1524/// Open and possibly create a file. Keeps trying if it gets interrupted.
1595/// `file_path` is relative to the open directory handle `dir_fd`.1525/// `file_path` is relative to the open directory handle `dir_fd`.
1596/// See also `openatZ`.1526/// See also `openatZ`.
...@@ -1600,22 +1530,23 @@ pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: mode_t) Ope...@@ -1600,22 +1530,23 @@ pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: mode_t) Ope
1600 return openatW(dir_fd, file_path_w.span(), flags, mode);1530 return openatW(dir_fd, file_path_w.span(), flags, mode);
1601 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {1531 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
1602 // `mode` is ignored on WASI, which does not support unix-style file permissions1532 // `mode` is ignored on WASI, which does not support unix-style file permissions
1603 const fd = if (dir_fd == wasi.AT.FDCWD or fs.path.isAbsolute(file_path)) blk: {1533 const opts = try openOptionsFromFlagsWasi(dir_fd, flags);
1604 // Resolve absolute or CWD-relative paths to a path within a Preopen1534 const fd = try openatWasi(
1605 var path_buf: [MAX_PATH_BYTES]u8 = undefined;1535 dir_fd,
1606 const path = try resolvePathWasi(file_path, &path_buf);1536 file_path,
16071537 opts.lookup_flags,
1608 const opts = try openOptionsFromFlagsWasi(path.dir_fd, flags);1538 opts.oflags,
1609 break :blk try openatWasi(path.dir_fd, path.relative_path, opts.lookup_flags, opts.oflags, opts.fs_flags, opts.fs_rights_base, opts.fs_rights_inheriting);1539 opts.fs_flags,
1610 } else blk: {1540 opts.fs_rights_base,
1611 const opts = try openOptionsFromFlagsWasi(dir_fd, flags);1541 opts.fs_rights_inheriting,
1612 break :blk try openatWasi(dir_fd, file_path, opts.lookup_flags, opts.oflags, opts.fs_flags, opts.fs_rights_base, opts.fs_rights_inheriting);1542 );
1613 };
1614 errdefer close(fd);1543 errdefer close(fd);
16151544
1616 const info = try fstat(fd);1545 if (flags & O.WRONLY != 0) {
1617 if (flags & O.WRONLY != 0 and info.filetype == .DIRECTORY)1546 const info = try fstat(fd);
1618 return error.IsDir;1547 if (info.filetype == .DIRECTORY)
1548 return error.IsDir;
1549 }
16191550
1620 return fd;1551 return fd;
1621 }1552 }
...@@ -1673,7 +1604,15 @@ fn openOptionsFromFlagsWasi(fd: fd_t, oflag: u32) OpenError!WasiOpenOptions {...@@ -1673,7 +1604,15 @@ fn openOptionsFromFlagsWasi(fd: fd_t, oflag: u32) OpenError!WasiOpenOptions {
1673}1604}
16741605
1675/// Open and possibly create a file in WASI.1606/// Open and possibly create a file in WASI.
1676pub fn openatWasi(dir_fd: fd_t, file_path: []const u8, lookup_flags: lookupflags_t, oflags: oflags_t, fdflags: fdflags_t, base: rights_t, inheriting: rights_t) OpenError!fd_t {1607pub fn openatWasi(
1608 dir_fd: fd_t,
1609 file_path: []const u8,
1610 lookup_flags: lookupflags_t,
1611 oflags: oflags_t,
1612 fdflags: fdflags_t,
1613 base: rights_t,
1614 inheriting: rights_t,
1615) OpenError!fd_t {
1677 while (true) {1616 while (true) {
1678 var fd: fd_t = undefined;1617 var fd: fd_t = undefined;
1679 switch (wasi.path_open(dir_fd, lookup_flags, file_path.ptr, file_path.len, oflags, base, inheriting, fdflags, &fd)) {1618 switch (wasi.path_open(dir_fd, lookup_flags, file_path.ptr, file_path.len, oflags, base, inheriting, fdflags, &fd)) {
...@@ -2031,7 +1970,7 @@ pub fn getcwd(out_buffer: []u8) GetCwdError![]u8 {...@@ -2031,7 +1970,7 @@ pub fn getcwd(out_buffer: []u8) GetCwdError![]u8 {
2031 if (builtin.os.tag == .windows) {1970 if (builtin.os.tag == .windows) {
2032 return windows.GetCurrentDirectory(out_buffer);1971 return windows.GetCurrentDirectory(out_buffer);
2033 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {1972 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
2034 const path = wasi_cwd.cwd;1973 const path = ".";
2035 if (out_buffer.len < path.len) return error.NameTooLong;1974 if (out_buffer.len < path.len) return error.NameTooLong;
2036 std.mem.copy(u8, out_buffer, path);1975 std.mem.copy(u8, out_buffer, path);
2037 return out_buffer[0..path.len];1976 return out_buffer[0..path.len];
...@@ -2125,12 +2064,6 @@ pub fn symlinkat(target_path: []const u8, newdirfd: fd_t, sym_link_path: []const...@@ -2125,12 +2064,6 @@ pub fn symlinkat(target_path: []const u8, newdirfd: fd_t, sym_link_path: []const
2125 if (builtin.os.tag == .windows) {2064 if (builtin.os.tag == .windows) {
2126 @compileError("symlinkat is not supported on Windows; use std.os.windows.CreateSymbolicLink instead");2065 @compileError("symlinkat is not supported on Windows; use std.os.windows.CreateSymbolicLink instead");
2127 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {2066 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
2128 if (newdirfd == wasi.AT.FDCWD or fs.path.isAbsolute(target_path)) {
2129 // Resolve absolute or CWD-relative paths to a path within a Preopen
2130 var path_buf: [MAX_PATH_BYTES]u8 = undefined;
2131 const path = try resolvePathWasi(sym_link_path, &path_buf);
2132 return symlinkatWasi(target_path, path.dir_fd, path.relative_path);
2133 }
2134 return symlinkatWasi(target_path, newdirfd, sym_link_path);2067 return symlinkatWasi(target_path, newdirfd, sym_link_path);
2135 }2068 }
2136 const target_path_c = try toPosixPath(target_path);2069 const target_path_c = try toPosixPath(target_path);
...@@ -2284,25 +2217,8 @@ pub fn linkat(...@@ -2284,25 +2217,8 @@ pub fn linkat(
2284 flags: i32,2217 flags: i32,
2285) LinkatError!void {2218) LinkatError!void {
2286 if (builtin.os.tag == .wasi and !builtin.link_libc) {2219 if (builtin.os.tag == .wasi and !builtin.link_libc) {
2287 var resolve_olddir: bool = (olddir == wasi.AT.FDCWD or fs.path.isAbsolute(oldpath));2220 const old: RelativePathWasi = .{ .dir_fd = olddir, .relative_path = oldpath };
2288 var resolve_newdir: bool = (newdir == wasi.AT.FDCWD or fs.path.isAbsolute(newpath));2221 const new: RelativePathWasi = .{ .dir_fd = newdir, .relative_path = newpath };
2289
2290 var old: RelativePathWasi = .{ .dir_fd = olddir, .relative_path = oldpath };
2291 var new: RelativePathWasi = .{ .dir_fd = newdir, .relative_path = newpath };
2292
2293 // Resolve absolute or CWD-relative paths to a path within a Preopen
2294 if (resolve_olddir or resolve_newdir) {
2295 var buf_old: [MAX_PATH_BYTES]u8 = undefined;
2296 var buf_new: [MAX_PATH_BYTES]u8 = undefined;
2297
2298 if (resolve_olddir)
2299 old = try resolvePathWasi(oldpath, &buf_old);
2300
2301 if (resolve_newdir)
2302 new = try resolvePathWasi(newpath, &buf_new);
2303
2304 return linkatWasi(old, new, flags);
2305 }
2306 return linkatWasi(old, new, flags);2222 return linkatWasi(old, new, flags);
2307 }2223 }
2308 const old = try toPosixPath(oldpath);2224 const old = try toPosixPath(oldpath);
...@@ -2423,12 +2339,6 @@ pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!vo...@@ -2423,12 +2339,6 @@ pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!vo
2423 const file_path_w = try windows.sliceToPrefixedFileW(file_path);2339 const file_path_w = try windows.sliceToPrefixedFileW(file_path);
2424 return unlinkatW(dirfd, file_path_w.span(), flags);2340 return unlinkatW(dirfd, file_path_w.span(), flags);
2425 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {2341 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
2426 if (dirfd == wasi.AT.FDCWD or fs.path.isAbsolute(file_path)) {
2427 // Resolve absolute or CWD-relative paths to a path within a Preopen
2428 var path_buf: [MAX_PATH_BYTES]u8 = undefined;
2429 const path = try resolvePathWasi(file_path, &path_buf);
2430 return unlinkatWasi(path.dir_fd, path.relative_path, flags);
2431 }
2432 return unlinkatWasi(dirfd, file_path, flags);2342 return unlinkatWasi(dirfd, file_path, flags);
2433 } else {2343 } else {
2434 const file_path_c = try toPosixPath(file_path);2344 const file_path_c = try toPosixPath(file_path);
...@@ -2597,24 +2507,8 @@ pub fn renameat(...@@ -2597,24 +2507,8 @@ pub fn renameat(
2597 const new_path_w = try windows.sliceToPrefixedFileW(new_path);2507 const new_path_w = try windows.sliceToPrefixedFileW(new_path);
2598 return renameatW(old_dir_fd, old_path_w.span(), new_dir_fd, new_path_w.span(), windows.TRUE);2508 return renameatW(old_dir_fd, old_path_w.span(), new_dir_fd, new_path_w.span(), windows.TRUE);
2599 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {2509 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
2600 var resolve_old: bool = (old_dir_fd == wasi.AT.FDCWD or fs.path.isAbsolute(old_path));2510 const old: RelativePathWasi = .{ .dir_fd = old_dir_fd, .relative_path = old_path };
2601 var resolve_new: bool = (new_dir_fd == wasi.AT.FDCWD or fs.path.isAbsolute(new_path));2511 const new: RelativePathWasi = .{ .dir_fd = new_dir_fd, .relative_path = new_path };
2602
2603 var old: RelativePathWasi = .{ .dir_fd = old_dir_fd, .relative_path = old_path };
2604 var new: RelativePathWasi = .{ .dir_fd = new_dir_fd, .relative_path = new_path };
2605
2606 // Resolve absolute or CWD-relative paths to a path within a Preopen
2607 if (resolve_old or resolve_new) {
2608 var buf_old: [MAX_PATH_BYTES]u8 = undefined;
2609 var buf_new: [MAX_PATH_BYTES]u8 = undefined;
2610
2611 if (resolve_old)
2612 old = try resolvePathWasi(old_path, &buf_old);
2613 if (resolve_new)
2614 new = try resolvePathWasi(new_path, &buf_new);
2615
2616 return renameatWasi(old, new);
2617 }
2618 return renameatWasi(old, new);2512 return renameatWasi(old, new);
2619 } else {2513 } else {
2620 const old_path_c = try toPosixPath(old_path);2514 const old_path_c = try toPosixPath(old_path);
...@@ -2755,12 +2649,6 @@ pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!v...@@ -2755,12 +2649,6 @@ pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!v
2755 const sub_dir_path_w = try windows.sliceToPrefixedFileW(sub_dir_path);2649 const sub_dir_path_w = try windows.sliceToPrefixedFileW(sub_dir_path);
2756 return mkdiratW(dir_fd, sub_dir_path_w.span(), mode);2650 return mkdiratW(dir_fd, sub_dir_path_w.span(), mode);
2757 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {2651 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
2758 if (dir_fd == wasi.AT.FDCWD or fs.path.isAbsolute(sub_dir_path)) {
2759 // Resolve absolute or CWD-relative paths to a path within a Preopen
2760 var path_buf: [MAX_PATH_BYTES]u8 = undefined;
2761 const path = try resolvePathWasi(sub_dir_path, &path_buf);
2762 return mkdiratWasi(path.dir_fd, path.relative_path, mode);
2763 }
2764 return mkdiratWasi(dir_fd, sub_dir_path, mode);2652 return mkdiratWasi(dir_fd, sub_dir_path, mode);
2765 } else {2653 } else {
2766 const sub_dir_path_c = try toPosixPath(sub_dir_path);2654 const sub_dir_path_c = try toPosixPath(sub_dir_path);
...@@ -2997,22 +2885,7 @@ pub const ChangeCurDirError = error{...@@ -2997,22 +2885,7 @@ pub const ChangeCurDirError = error{
2997/// `dir_path` is recommended to be a UTF-8 encoded string.2885/// `dir_path` is recommended to be a UTF-8 encoded string.
2998pub fn chdir(dir_path: []const u8) ChangeCurDirError!void {2886pub fn chdir(dir_path: []const u8) ChangeCurDirError!void {
2999 if (builtin.os.tag == .wasi and !builtin.link_libc) {2887 if (builtin.os.tag == .wasi and !builtin.link_libc) {
3000 var buf: [MAX_PATH_BYTES]u8 = undefined;2888 @compileError("WASI does not support os.chdir");
3001 var alloc = std.heap.FixedBufferAllocator.init(&buf);
3002 const path = fs.path.resolve(alloc.allocator(), &.{ wasi_cwd.cwd, dir_path }) catch |err| switch (err) {
3003 error.OutOfMemory => return error.NameTooLong,
3004 else => |e| return e,
3005 };
3006
3007 const dirinfo = try fstatat(AT.FDCWD, path, 0);
3008 if (dirinfo.filetype != .DIRECTORY) {
3009 return error.NotDir;
3010 }
3011
3012 // This copy is guaranteed to succeed, since buf and path_buffer are the same size.
3013 var cwd_alloc = std.heap.FixedBufferAllocator.init(&wasi_cwd.path_buffer);
3014 wasi_cwd.cwd = cwd_alloc.allocator().dupe(u8, path) catch unreachable;
3015 return;
3016 } else if (builtin.os.tag == .windows) {2889 } else if (builtin.os.tag == .windows) {
3017 var utf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined;2890 var utf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined;
3018 const len = try std.unicode.utf8ToUtf16Le(utf16_dir_path[0..], dir_path);2891 const len = try std.unicode.utf8ToUtf16Le(utf16_dir_path[0..], dir_path);
...@@ -3143,12 +3016,6 @@ pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8...@@ -3143,12 +3016,6 @@ pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8
3143/// See also `readlinkatWasi`, `realinkatZ` and `realinkatW`.3016/// See also `readlinkatWasi`, `realinkatZ` and `realinkatW`.
3144pub fn readlinkat(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {3017pub fn readlinkat(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
3145 if (builtin.os.tag == .wasi and !builtin.link_libc) {3018 if (builtin.os.tag == .wasi and !builtin.link_libc) {
3146 if (dirfd == wasi.AT.FDCWD or fs.path.isAbsolute(file_path)) {
3147 // Resolve absolute or CWD-relative paths to a path within a Preopen
3148 var path_buf: [MAX_PATH_BYTES]u8 = undefined;
3149 var path = try resolvePathWasi(file_path, &path_buf);
3150 return readlinkatWasi(path.dir_fd, path.relative_path, out_buffer);
3151 }
3152 return readlinkatWasi(dirfd, file_path, out_buffer);3019 return readlinkatWasi(dirfd, file_path, out_buffer);
3153 }3020 }
3154 if (builtin.os.tag == .windows) {3021 if (builtin.os.tag == .windows) {
...@@ -4155,12 +4022,6 @@ pub const FStatAtError = FStatError || error{ NameTooLong, FileNotFound, SymLink...@@ -4155,12 +4022,6 @@ pub const FStatAtError = FStatError || error{ NameTooLong, FileNotFound, SymLink
4155pub fn fstatat(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!Stat {4022pub fn fstatat(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!Stat {
4156 if (builtin.os.tag == .wasi and !builtin.link_libc) {4023 if (builtin.os.tag == .wasi and !builtin.link_libc) {
4157 const wasi_flags = if (flags & linux.AT.SYMLINK_NOFOLLOW == 0) wasi.LOOKUP_SYMLINK_FOLLOW else 0;4024 const wasi_flags = if (flags & linux.AT.SYMLINK_NOFOLLOW == 0) wasi.LOOKUP_SYMLINK_FOLLOW else 0;
4158 if (dirfd == wasi.AT.FDCWD or fs.path.isAbsolute(pathname)) {
4159 // Resolve absolute or CWD-relative paths to a path within a Preopen
4160 var path_buf: [MAX_PATH_BYTES]u8 = undefined;
4161 const path = try resolvePathWasi(pathname, &path_buf);
4162 return fstatatWasi(path.dir_fd, path.relative_path, wasi_flags);
4163 }
4164 return fstatatWasi(dirfd, pathname, wasi_flags);4025 return fstatatWasi(dirfd, pathname, wasi_flags);
4165 } else if (builtin.os.tag == .windows) {4026 } else if (builtin.os.tag == .windows) {
4166 @compileError("fstatat is not yet implemented on Windows");4027 @compileError("fstatat is not yet implemented on Windows");
...@@ -4556,12 +4417,6 @@ pub fn faccessat(dirfd: fd_t, path: []const u8, mode: u32, flags: u32) AccessErr...@@ -4556,12 +4417,6 @@ pub fn faccessat(dirfd: fd_t, path: []const u8, mode: u32, flags: u32) AccessErr
4556 var resolved = RelativePathWasi{ .dir_fd = dirfd, .relative_path = path };4417 var resolved = RelativePathWasi{ .dir_fd = dirfd, .relative_path = path };
45574418
4558 const file = blk: {4419 const file = blk: {
4559 if (dirfd == wasi.AT.FDCWD or fs.path.isAbsolute(path)) {
4560 // Resolve absolute or CWD-relative paths to a path within a Preopen
4561 var path_buf: [MAX_PATH_BYTES]u8 = undefined;
4562 resolved = resolvePathWasi(path, &path_buf) catch |err| break :blk @as(FStatAtError!Stat, err);
4563 break :blk fstatat(resolved.dir_fd, resolved.relative_path, flags);
4564 }
4565 break :blk fstatat(dirfd, path, flags);4420 break :blk fstatat(dirfd, path, flags);
4566 } catch |err| switch (err) {4421 } catch |err| switch (err) {
4567 error.AccessDenied => return error.PermissionDenied,4422 error.AccessDenied => return error.PermissionDenied,
...@@ -5147,12 +5002,7 @@ pub fn realpath(pathname: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathE...@@ -5147,12 +5002,7 @@ pub fn realpath(pathname: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathE
5147 const pathname_w = try windows.sliceToPrefixedFileW(pathname);5002 const pathname_w = try windows.sliceToPrefixedFileW(pathname);
5148 return realpathW(pathname_w.span(), out_buffer);5003 return realpathW(pathname_w.span(), out_buffer);
5149 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {5004 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
5150 var alloc = std.heap.FixedBufferAllocator.init(out_buffer);5005 @compileError("WASI does not support os.realpath");
5151
5152 // NOTE: This emulation is incomplete. Symbolic links are not
5153 // currently expanded during path canonicalization.
5154 const paths = &.{ wasi_cwd.cwd, pathname };
5155 return fs.path.resolve(alloc.allocator(), paths) catch error.NameTooLong;
5156 }5006 }
5157 const pathname_c = try toPosixPath(pathname);5007 const pathname_c = try toPosixPath(pathname);
5158 return realpathZ(&pathname_c, out_buffer);5008 return realpathZ(&pathname_c, out_buffer);
src/Sema.zig+4-7
...@@ -15082,14 +15082,11 @@ fn zirBuiltinSrc(...@@ -15082,14 +15082,11 @@ fn zirBuiltinSrc(
15082 const file_name_val = blk: {15082 const file_name_val = blk: {
15083 var anon_decl = try block.startAnonDecl();15083 var anon_decl = try block.startAnonDecl();
15084 defer anon_decl.deinit();15084 defer anon_decl.deinit();
15085 const relative_path = try fn_owner_decl.getFileScope().fullPath(sema.arena);15085 // The compiler must not call realpath anywhere.
15086 const absolute_path = std.fs.realpathAlloc(sema.arena, relative_path) catch |err| {15086 const name = try fn_owner_decl.getFileScope().fullPathZ(anon_decl.arena());
15087 return sema.fail(block, src, "failed to get absolute path of file '{s}': {s}", .{ relative_path, @errorName(err) });
15088 };
15089 const aboslute_duped = try anon_decl.arena().dupeZ(u8, absolute_path);
15090 const new_decl = try anon_decl.finish(15087 const new_decl = try anon_decl.finish(
15091 try Type.Tag.array_u8_sentinel_0.create(anon_decl.arena(), aboslute_duped.len),15088 try Type.Tag.array_u8_sentinel_0.create(anon_decl.arena(), name.len),
15092 try Value.Tag.bytes.create(anon_decl.arena(), aboslute_duped[0 .. aboslute_duped.len + 1]),15089 try Value.Tag.bytes.create(anon_decl.arena(), name[0 .. name.len + 1]),
15093 0, // default alignment15090 0, // default alignment
15094 );15091 );
15095 break :blk try Value.Tag.decl_ref.create(sema.arena, new_decl);15092 break :blk try Value.Tag.decl_ref.create(sema.arena, new_decl);
src/introspect.zig+5-39
...@@ -37,34 +37,7 @@ fn testZigInstallPrefix(base_dir: fs.Dir) ?Compilation.Directory {...@@ -37,34 +37,7 @@ fn testZigInstallPrefix(base_dir: fs.Dir) ?Compilation.Directory {
37/// based on a hard-coded Preopen directory ("/zig")37/// based on a hard-coded Preopen directory ("/zig")
38pub fn findZigExePath(allocator: mem.Allocator) ![]u8 {38pub fn findZigExePath(allocator: mem.Allocator) ![]u8 {
39 if (builtin.os.tag == .wasi) {39 if (builtin.os.tag == .wasi) {
40 var args = try std.process.argsWithAllocator(allocator);40 @compileError("this function is unsupported on WASI");
41 defer args.deinit();
42 // On WASI, argv[0] is always just the basename of the current executable
43 const argv0 = args.next() orelse return error.FileNotFound;
44
45 // Check these paths:
46 // 1. "/zig/{exe_name}"
47 // 2. "/zig/bin/{exe_name}"
48 const base_paths_to_check = &[_][]const u8{ "/zig", "/zig/bin" };
49 const exe_names_to_check = &[_][]const u8{ fs.path.basename(argv0), "zig.wasm" };
50
51 for (base_paths_to_check) |base_path| {
52 for (exe_names_to_check) |exe_name| {
53 const test_path = fs.path.join(allocator, &.{ base_path, exe_name }) catch continue;
54 defer allocator.free(test_path);
55
56 // Make sure it's a file we're pointing to
57 const file = os.fstatat(os.wasi.AT.FDCWD, test_path, 0) catch continue;
58 if (file.filetype != .REGULAR_FILE) continue;
59
60 // Path seems to be valid, let's try to turn it into an absolute path
61 var real_path_buf: [fs.MAX_PATH_BYTES]u8 = undefined;
62 if (os.realpath(test_path, &real_path_buf)) |real_path| {
63 return allocator.dupe(u8, real_path); // Success: return absolute path
64 } else |_| continue;
65 }
66 }
67 return error.FileNotFound;
68 }41 }
6942
70 return fs.selfExePathAlloc(allocator);43 return fs.selfExePathAlloc(allocator);
...@@ -107,6 +80,9 @@ pub fn findZigLibDirFromSelfExe(...@@ -107,6 +80,9 @@ pub fn findZigLibDirFromSelfExe(
10780
108/// Caller owns returned memory.81/// Caller owns returned memory.
109pub fn resolveGlobalCacheDir(allocator: mem.Allocator) ![]u8 {82pub fn resolveGlobalCacheDir(allocator: mem.Allocator) ![]u8 {
83 if (builtin.os.tag == .wasi) {
84 @compileError("on WASI the global cache dir must be resolved with preopens");
85 }
110 if (std.process.getEnvVarOwned(allocator, "ZIG_GLOBAL_CACHE_DIR")) |value| {86 if (std.process.getEnvVarOwned(allocator, "ZIG_GLOBAL_CACHE_DIR")) |value| {
111 if (value.len > 0) {87 if (value.len > 0) {
112 return value;88 return value;
...@@ -125,17 +101,7 @@ pub fn resolveGlobalCacheDir(allocator: mem.Allocator) ![]u8 {...@@ -125,17 +101,7 @@ pub fn resolveGlobalCacheDir(allocator: mem.Allocator) ![]u8 {
125 }101 }
126 }102 }
127103
128 if (builtin.os.tag == .wasi) {104 return fs.getAppDataDir(allocator, appname);
129 // On WASI, we have no way to get an App data dir, so we try to use a fixed
130 // Preopen path "/cache" as a last resort
131 const path = "/cache";
132
133 const file = os.fstatat(os.wasi.AT.FDCWD, path, 0) catch return error.CacheDirUnavailable;
134 if (file.filetype != .DIRECTORY) return error.CacheDirUnavailable;
135 return allocator.dupe(u8, path);
136 } else {
137 return fs.getAppDataDir(allocator, appname);
138 }
139}105}
140106
141/// Similar to std.fs.path.resolve, with a few important differences:107/// Similar to std.fs.path.resolve, with a few important differences:
src/main.zig+60-25
...@@ -27,6 +27,23 @@ const crash_report = @import("crash_report.zig");...@@ -27,6 +27,23 @@ const crash_report = @import("crash_report.zig");
27// Crash report needs to override the panic handler and other root decls27// Crash report needs to override the panic handler and other root decls
28pub usingnamespace crash_report.root_decls;28pub usingnamespace crash_report.root_decls;
2929
30var wasi_preopens: fs.wasi.Preopens = undefined;
31pub inline fn wasi_cwd() fs.Dir {
32 // Expect the first preopen to be current working directory.
33 const cwd_fd: std.os.fd_t = 3;
34 assert(mem.eql(u8, wasi_preopens.names[cwd_fd], "."));
35 return .{ .fd = cwd_fd };
36}
37
38pub fn getWasiPreopen(name: []const u8) Compilation.Directory {
39 return .{
40 .path = name,
41 .handle = .{
42 .fd = wasi_preopens.find(name) orelse fatal("WASI preopen not found: '{s}'", .{name}),
43 },
44 };
45}
46
30pub fn fatal(comptime format: []const u8, args: anytype) noreturn {47pub fn fatal(comptime format: []const u8, args: anytype) noreturn {
31 std.log.err(format, args);48 std.log.err(format, args);
32 process.exit(1);49 process.exit(1);
...@@ -161,20 +178,14 @@ pub fn main() anyerror!void {...@@ -161,20 +178,14 @@ pub fn main() anyerror!void {
161 return mainArgs(gpa_tracy.allocator(), arena, args);178 return mainArgs(gpa_tracy.allocator(), arena, args);
162 }179 }
163180
164 // WASI: `--dir` instructs the WASM runtime to "preopen" a directory, making
165 // it available to the us, the guest program. This is the only way for us to
166 // access files/dirs on the host filesystem
167 if (builtin.os.tag == .wasi) {181 if (builtin.os.tag == .wasi) {
168 // This sets our CWD to "/preopens/cwd"182 wasi_preopens = try fs.wasi.preopensAlloc(arena);
169 // Dot-prefixed preopens like `--dir=.` are "mounted" at "/preopens/cwd"
170 // Other preopens like `--dir=lib` are "mounted" at "/"
171 try std.os.initPreopensWasi(arena, "/preopens/cwd");
172 }183 }
173184
174 // Short circuit some of the other logic for bootstrapping.185 // Short circuit some of the other logic for bootstrapping.
175 if (build_options.only_c) {186 if (build_options.only_c) {
176 assert(mem.eql(u8, args[1], "build-obj"));187 assert(mem.eql(u8, args[1], "build-exe"));
177 return buildOutputType(gpa, arena, args, .{ .build = .Obj });188 return buildOutputType(gpa, arena, args, .{ .build = .Exe });
178 }189 }
179190
180 return mainArgs(gpa, arena, args);191 return mainArgs(gpa, arena, args);
...@@ -2300,7 +2311,7 @@ fn buildOutputType(...@@ -2300,7 +2311,7 @@ fn buildOutputType(
2300 },2311 },
2301 }2312 }
23022313
2303 if (std.fs.path.isAbsolute(lib_name)) {2314 if (fs.path.isAbsolute(lib_name)) {
2304 fatal("cannot use absolute path as a system library: {s}", .{lib_name});2315 fatal("cannot use absolute path as a system library: {s}", .{lib_name});
2305 }2316 }
23062317
...@@ -2763,18 +2774,33 @@ fn buildOutputType(...@@ -2763,18 +2774,33 @@ fn buildOutputType(
2763 }2774 }
2764 }2775 }
27652776
2766 const self_exe_path = try introspect.findZigExePath(arena);2777 const self_exe_path: ?[]const u8 = if (!process.can_spawn)
2767 var zig_lib_directory: Compilation.Directory = if (override_lib_dir) |unresolved_lib_dir| l: {2778 null
2768 const lib_dir = try introspect.resolvePath(arena, unresolved_lib_dir);2779 else
2769 break :l .{2780 introspect.findZigExePath(arena) catch |err| {
2770 .path = lib_dir,2781 fatal("unable to find zig self exe path: {s}", .{@errorName(err)});
2771 .handle = fs.cwd().openDir(lib_dir, .{}) catch |err| {
2772 fatal("unable to open zig lib directory '{s}': {s}", .{ lib_dir, @errorName(err) });
2773 },
2774 };2782 };
2775 } else introspect.findZigLibDirFromSelfExe(arena, self_exe_path) catch |err| {2783
2776 fatal("unable to find zig installation directory: {s}\n", .{@errorName(err)});2784 var zig_lib_directory: Compilation.Directory = d: {
2785 if (override_lib_dir) |unresolved_lib_dir| {
2786 const lib_dir = try introspect.resolvePath(arena, unresolved_lib_dir);
2787 break :d .{
2788 .path = lib_dir,
2789 .handle = fs.cwd().openDir(lib_dir, .{}) catch |err| {
2790 fatal("unable to open zig lib directory '{s}': {s}", .{ lib_dir, @errorName(err) });
2791 },
2792 };
2793 } else if (builtin.os.tag == .wasi) {
2794 break :d getWasiPreopen("/lib");
2795 } else if (self_exe_path) |p| {
2796 break :d introspect.findZigLibDirFromSelfExe(arena, p) catch |err| {
2797 fatal("unable to find zig installation directory: {s}", .{@errorName(err)});
2798 };
2799 } else {
2800 unreachable;
2801 }
2777 };2802 };
2803
2778 defer zig_lib_directory.handle.close();2804 defer zig_lib_directory.handle.close();
27792805
2780 var thread_pool: ThreadPool = undefined;2806 var thread_pool: ThreadPool = undefined;
...@@ -2791,7 +2817,16 @@ fn buildOutputType(...@@ -2791,7 +2817,16 @@ fn buildOutputType(
2791 }2817 }
27922818
2793 var global_cache_directory: Compilation.Directory = l: {2819 var global_cache_directory: Compilation.Directory = l: {
2794 const p = override_global_cache_dir orelse try introspect.resolveGlobalCacheDir(arena);2820 if (override_global_cache_dir) |p| {
2821 break :l .{
2822 .handle = try fs.cwd().makeOpenPath(p, .{}),
2823 .path = p,
2824 };
2825 }
2826 if (builtin.os.tag == .wasi) {
2827 break :l getWasiPreopen("/cache");
2828 }
2829 const p = try introspect.resolveGlobalCacheDir(arena);
2795 break :l .{2830 break :l .{
2796 .handle = try fs.cwd().makeOpenPath(p, .{}),2831 .handle = try fs.cwd().makeOpenPath(p, .{}),
2797 .path = p,2832 .path = p,
...@@ -3082,7 +3117,7 @@ fn buildOutputType(...@@ -3082,7 +3117,7 @@ fn buildOutputType(
3082 gpa,3117 gpa,
3083 arena,3118 arena,
3084 test_exec_args.items,3119 test_exec_args.items,
3085 self_exe_path,3120 self_exe_path.?,
3086 arg_mode,3121 arg_mode,
3087 target_info,3122 target_info,
3088 watch,3123 watch,
...@@ -3154,7 +3189,7 @@ fn buildOutputType(...@@ -3154,7 +3189,7 @@ fn buildOutputType(
3154 gpa,3189 gpa,
3155 arena,3190 arena,
3156 test_exec_args.items,3191 test_exec_args.items,
3157 self_exe_path,3192 self_exe_path.?,
3158 arg_mode,3193 arg_mode,
3159 target_info,3194 target_info,
3160 watch,3195 watch,
...@@ -3179,7 +3214,7 @@ fn buildOutputType(...@@ -3179,7 +3214,7 @@ fn buildOutputType(
3179 gpa,3214 gpa,
3180 arena,3215 arena,
3181 test_exec_args.items,3216 test_exec_args.items,
3182 self_exe_path,3217 self_exe_path.?,
3183 arg_mode,3218 arg_mode,
3184 target_info,3219 target_info,
3185 watch,3220 watch,
...@@ -3523,7 +3558,7 @@ fn cmdTranslateC(comp: *Compilation, arena: Allocator, enable_cache: bool) !void...@@ -3523,7 +3558,7 @@ fn cmdTranslateC(comp: *Compilation, arena: Allocator, enable_cache: bool) !void
3523 defer tree.deinit(comp.gpa);3558 defer tree.deinit(comp.gpa);
35243559
3525 if (out_dep_path) |dep_file_path| {3560 if (out_dep_path) |dep_file_path| {
3526 const dep_basename = std.fs.path.basename(dep_file_path);3561 const dep_basename = fs.path.basename(dep_file_path);
3527 // Add the files depended on to the cache system.3562 // Add the files depended on to the cache system.
3528 try man.addDepFilePost(zig_cache_tmp_dir, dep_basename);3563 try man.addDepFilePost(zig_cache_tmp_dir, dep_basename);
3529 // Just to save disk space, we delete the file because it is never needed again.3564 // Just to save disk space, we delete the file because it is never needed again.