| author | |
| committer | |
| log | d5312d53a066092ba9efd687e25b29a87eb6290c |
| tree | b94a89b89f333cf381046982afe6a86c72c8122e |
| parent | 0a2fdfbdb934faae7fcf63e9b3ab760c00f47918 |
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 | 1130 | w.RIGHT.FD_FILESTAT_SET_TIMES | |
| 1131 | 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 | 1133 | const fd = try os.openatWasi(self.fd, sub_path, 0x0, 0x0, fdflags, base, 0x0); |
| 1141 | 1134 | return File{ .handle = fd }; |
| 1142 | 1135 | } |
| ... | ... | @@ -1301,13 +1294,6 @@ pub const Dir = struct { |
| 1301 | 1294 | if (flags.exclusive) { |
| 1302 | 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 | 1297 | const fd = try os.openatWasi(self.fd, sub_path, 0x0, oflags, 0x0, base, 0x0); |
| 1312 | 1298 | return File{ .handle = fd }; |
| 1313 | 1299 | } |
| ... | ... | @@ -1711,16 +1697,15 @@ pub const Dir = struct { |
| 1711 | 1697 | // TODO do we really need all the rights here? |
| 1712 | 1698 | const inheriting: w.rights_t = w.RIGHT.ALL ^ w.RIGHT.SOCK_SHUTDOWN; |
| 1713 | 1699 | |
| 1714 | const result = blk: { | |
| 1715 | if (self.fd == os.wasi.AT.FDCWD or path.isAbsolute(sub_path)) { | |
| 1716 | // Resolve absolute or CWD-relative paths to a path within a Preopen | |
| 1717 | var resolved_path_buf: [MAX_PATH_BYTES]u8 = undefined; | |
| 1718 | const resolved_path = try os.resolvePathWasi(sub_path, &resolved_path_buf); | |
| 1719 | break :blk os.openatWasi(resolved_path.dir_fd, resolved_path.relative_path, symlink_flags, w.O.DIRECTORY, 0x0, base, inheriting); | |
| 1720 | } else { | |
| 1721 | break :blk os.openatWasi(self.fd, sub_path, symlink_flags, w.O.DIRECTORY, 0x0, base, inheriting); | |
| 1722 | } | |
| 1723 | }; | |
| 1700 | const result = os.openatWasi( | |
| 1701 | self.fd, | |
| 1702 | sub_path, | |
| 1703 | symlink_flags, | |
| 1704 | w.O.DIRECTORY, | |
| 1705 | 0x0, | |
| 1706 | base, | |
| 1707 | inheriting, | |
| 1708 | ); | |
| 1724 | 1709 | const fd = result catch |err| switch (err) { |
| 1725 | 1710 | error.FileTooBig => unreachable, // can't happen for directories |
| 1726 | 1711 | error.IsDir => unreachable, // we're providing O.DIRECTORY |
| ... | ... | @@ -2667,6 +2652,8 @@ pub const Dir = struct { |
| 2667 | 2652 | pub fn cwd() Dir { |
| 2668 | 2653 | if (builtin.os.tag == .windows) { |
| 2669 | 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 | 2657 | } else { |
| 2671 | 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 | 10 | const fd_t = wasi.fd_t; |
| 11 | 11 | const prestat_t = wasi.prestat_t; |
| 12 | 12 | |
| 13 | /// Type-tag of WASI preopen. | |
| 14 | /// | |
| 15 | /// WASI currently offers only `Dir` as a valid preopen resource. | |
| 16 | pub const PreopenTypeTag = enum { | |
| 17 | Dir, | |
| 18 | }; | |
| 19 | ||
| 20 | /// Type of WASI preopen. | |
| 21 | /// | |
| 22 | /// WASI currently offers only `Dir` as a valid preopen resource. | |
| 23 | pub 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. | |
| 78 | pub 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. | |
| 96 | pub 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. | |
| 110 | pub 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; | |
| 13 | pub const Preopens = struct { | |
| 14 | // Indexed by file descriptor number. | |
| 15 | names: []const []const u8, | |
| 16 | ||
| 17 | pub fn find(p: Preopens, name: []const u8) ?os.fd_t { | |
| 18 | for (p.names) |elem_name, i| { | |
| 19 | if (mem.eql(u8, elem_name, name)) { | |
| 20 | return @intCast(os.fd_t, i); | |
| 244 | 21 | } |
| 245 | 22 | } |
| 246 | 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 | }; |
| 270 | 26 | |
| 271 | test "extracting WASI preopens" { | |
| 272 | if (builtin.os.tag != .wasi or builtin.link_libc) return error.SkipZigTest; | |
| 273 | ||
| 274 | var preopens = PreopenList.init(std.testing.allocator); | |
| 275 | defer preopens.deinit(); | |
| 276 | ||
| 277 | try preopens.populate(null); | |
| 278 | ||
| 279 | const preopen = preopens.find(PreopenType{ .Dir = "." }) orelse unreachable; | |
| 280 | try std.testing.expect(preopen.type.eql(PreopenType{ .Dir = "." })); | |
| 281 | ||
| 282 | const po_type1 = PreopenType{ .Dir = "/" }; | |
| 283 | try std.testing.expect(std.mem.eql(u8, po_type1.getRelativePath(.{ .Dir = "/" }).?, "")); | |
| 284 | try std.testing.expect(std.mem.eql(u8, po_type1.getRelativePath(.{ .Dir = "/test/foobar" }).?, "test/foobar")); | |
| 285 | ||
| 286 | const po_type2 = PreopenType{ .Dir = "/test/foo" }; | |
| 287 | try std.testing.expect(po_type2.getRelativePath(.{ .Dir = "/test/foobar" }) == null); | |
| 288 | ||
| 289 | const po_type3 = PreopenType{ .Dir = "/test" }; | |
| 290 | try std.testing.expect(std.mem.eql(u8, po_type3.getRelativePath(.{ .Dir = "/test" }).?, "")); | |
| 291 | try std.testing.expect(std.mem.eql(u8, po_type3.getRelativePath(.{ .Dir = "/test/" }).?, "")); | |
| 292 | try std.testing.expect(std.mem.eql(u8, po_type3.getRelativePath(.{ .Dir = "/test/foo/bar" }).?, "foo/bar")); | |
| 27 | pub fn preopensAlloc(gpa: Allocator) Allocator.Error!Preopens { | |
| 28 | var names: std.ArrayListUnmanaged([]const u8) = .{}; | |
| 29 | defer names.deinit(gpa); | |
| 30 | ||
| 31 | try names.ensureUnusedCapacity(gpa, 3); | |
| 32 | ||
| 33 | names.appendAssumeCapacity("stdin"); // 0 | |
| 34 | names.appendAssumeCapacity("stdout"); // 1 | |
| 35 | names.appendAssumeCapacity("stderr"); // 2 | |
| 36 | while (true) { | |
| 37 | const fd = @intCast(wasi.fd_t, names.items.len); | |
| 38 | var prestat: prestat_t = undefined; | |
| 39 | switch (wasi.fd_prestat_get(fd, &prestat)) { | |
| 40 | .SUCCESS => {}, | |
| 41 | .OPNOTSUPP, .BADF => return .{ .names = names.toOwnedSlice(gpa) }, | |
| 42 | else => @panic("fd_prestat_get: unexpected error"), | |
| 43 | } | |
| 44 | try names.ensureUnusedCapacity(gpa, 1); | |
| 45 | // This length does not include a null byte. Let's keep it this way to | |
| 46 | // gently encourage WASI implementations to behave properly. | |
| 47 | const name_len = prestat.u.dir.pr_name_len; | |
| 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 | 1521 | }; |
| 1522 | 1522 | } |
| 1523 | 1523 | |
| 1524 | var 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(). | |
| 1552 | pub 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. | |
| 1576 | pub 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 | 1524 | /// Open and possibly create a file. Keeps trying if it gets interrupted. |
| 1595 | 1525 | /// `file_path` is relative to the open directory handle `dir_fd`. |
| 1596 | 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 | 1530 | return openatW(dir_fd, file_path_w.span(), flags, mode); |
| 1601 | 1531 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 1602 | 1532 | // `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: { | |
| 1604 | // Resolve absolute or CWD-relative paths to a path within a Preopen | |
| 1605 | var path_buf: [MAX_PATH_BYTES]u8 = undefined; | |
| 1606 | const path = try resolvePathWasi(file_path, &path_buf); | |
| 1607 | ||
| 1608 | const opts = try openOptionsFromFlagsWasi(path.dir_fd, flags); | |
| 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); | |
| 1610 | } else blk: { | |
| 1611 | const opts = try openOptionsFromFlagsWasi(dir_fd, flags); | |
| 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); | |
| 1613 | }; | |
| 1533 | const opts = try openOptionsFromFlagsWasi(dir_fd, flags); | |
| 1534 | const fd = try openatWasi( | |
| 1535 | dir_fd, | |
| 1536 | file_path, | |
| 1537 | opts.lookup_flags, | |
| 1538 | opts.oflags, | |
| 1539 | opts.fs_flags, | |
| 1540 | opts.fs_rights_base, | |
| 1541 | opts.fs_rights_inheriting, | |
| 1542 | ); | |
| 1614 | 1543 | errdefer close(fd); |
| 1615 | 1544 | |
| 1616 | const info = try fstat(fd); | |
| 1617 | if (flags & O.WRONLY != 0 and info.filetype == .DIRECTORY) | |
| 1618 | return error.IsDir; | |
| 1545 | if (flags & O.WRONLY != 0) { | |
| 1546 | const info = try fstat(fd); | |
| 1547 | if (info.filetype == .DIRECTORY) | |
| 1548 | return error.IsDir; | |
| 1549 | } | |
| 1619 | 1550 | |
| 1620 | 1551 | return fd; |
| 1621 | 1552 | } |
| ... | ... | @@ -1673,7 +1604,15 @@ fn openOptionsFromFlagsWasi(fd: fd_t, oflag: u32) OpenError!WasiOpenOptions { |
| 1673 | 1604 | } |
| 1674 | 1605 | |
| 1675 | 1606 | /// Open and possibly create a file in WASI. |
| 1676 | pub 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 { | |
| 1607 | pub 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 | 1616 | while (true) { |
| 1678 | 1617 | var fd: fd_t = undefined; |
| 1679 | 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 | 1970 | if (builtin.os.tag == .windows) { |
| 2032 | 1971 | return windows.GetCurrentDirectory(out_buffer); |
| 2033 | 1972 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2034 | const path = wasi_cwd.cwd; | |
| 1973 | const path = "."; | |
| 2035 | 1974 | if (out_buffer.len < path.len) return error.NameTooLong; |
| 2036 | 1975 | std.mem.copy(u8, out_buffer, path); |
| 2037 | 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 | 2064 | if (builtin.os.tag == .windows) { |
| 2126 | 2065 | @compileError("symlinkat is not supported on Windows; use std.os.windows.CreateSymbolicLink instead"); |
| 2127 | 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 | 2067 | return symlinkatWasi(target_path, newdirfd, sym_link_path); |
| 2135 | 2068 | } |
| 2136 | 2069 | const target_path_c = try toPosixPath(target_path); |
| ... | ... | @@ -2284,25 +2217,8 @@ pub fn linkat( |
| 2284 | 2217 | flags: i32, |
| 2285 | 2218 | ) LinkatError!void { |
| 2286 | 2219 | if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2287 | var resolve_olddir: bool = (olddir == wasi.AT.FDCWD or fs.path.isAbsolute(oldpath)); | |
| 2288 | var resolve_newdir: bool = (newdir == wasi.AT.FDCWD or fs.path.isAbsolute(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 | } | |
| 2220 | const old: RelativePathWasi = .{ .dir_fd = olddir, .relative_path = oldpath }; | |
| 2221 | const new: RelativePathWasi = .{ .dir_fd = newdir, .relative_path = newpath }; | |
| 2306 | 2222 | return linkatWasi(old, new, flags); |
| 2307 | 2223 | } |
| 2308 | 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 | 2339 | const file_path_w = try windows.sliceToPrefixedFileW(file_path); |
| 2424 | 2340 | return unlinkatW(dirfd, file_path_w.span(), flags); |
| 2425 | 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 | 2342 | return unlinkatWasi(dirfd, file_path, flags); |
| 2433 | 2343 | } else { |
| 2434 | 2344 | const file_path_c = try toPosixPath(file_path); |
| ... | ... | @@ -2597,24 +2507,8 @@ pub fn renameat( |
| 2597 | 2507 | const new_path_w = try windows.sliceToPrefixedFileW(new_path); |
| 2598 | 2508 | return renameatW(old_dir_fd, old_path_w.span(), new_dir_fd, new_path_w.span(), windows.TRUE); |
| 2599 | 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)); | |
| 2601 | var resolve_new: bool = (new_dir_fd == wasi.AT.FDCWD or fs.path.isAbsolute(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 | } | |
| 2510 | const old: RelativePathWasi = .{ .dir_fd = old_dir_fd, .relative_path = old_path }; | |
| 2511 | const new: RelativePathWasi = .{ .dir_fd = new_dir_fd, .relative_path = new_path }; | |
| 2618 | 2512 | return renameatWasi(old, new); |
| 2619 | 2513 | } else { |
| 2620 | 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 | 2649 | const sub_dir_path_w = try windows.sliceToPrefixedFileW(sub_dir_path); |
| 2756 | 2650 | return mkdiratW(dir_fd, sub_dir_path_w.span(), mode); |
| 2757 | 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 | 2652 | return mkdiratWasi(dir_fd, sub_dir_path, mode); |
| 2765 | 2653 | } else { |
| 2766 | 2654 | const sub_dir_path_c = try toPosixPath(sub_dir_path); |
| ... | ... | @@ -2997,22 +2885,7 @@ pub const ChangeCurDirError = error{ |
| 2997 | 2885 | /// `dir_path` is recommended to be a UTF-8 encoded string. |
| 2998 | 2886 | pub fn chdir(dir_path: []const u8) ChangeCurDirError!void { |
| 2999 | 2887 | if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 3000 | var buf: [MAX_PATH_BYTES]u8 = undefined; | |
| 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; | |
| 2888 | @compileError("WASI does not support os.chdir"); | |
| 3016 | 2889 | } else if (builtin.os.tag == .windows) { |
| 3017 | 2890 | var utf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined; |
| 3018 | 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 | 3016 | /// See also `readlinkatWasi`, `realinkatZ` and `realinkatW`. |
| 3144 | 3017 | pub fn readlinkat(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 { |
| 3145 | 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 | 3019 | return readlinkatWasi(dirfd, file_path, out_buffer); |
| 3153 | 3020 | } |
| 3154 | 3021 | if (builtin.os.tag == .windows) { |
| ... | ... | @@ -4155,12 +4022,6 @@ pub const FStatAtError = FStatError || error{ NameTooLong, FileNotFound, SymLink |
| 4155 | 4022 | pub fn fstatat(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!Stat { |
| 4156 | 4023 | if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 4157 | 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 | 4025 | return fstatatWasi(dirfd, pathname, wasi_flags); |
| 4165 | 4026 | } else if (builtin.os.tag == .windows) { |
| 4166 | 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 | 4417 | var resolved = RelativePathWasi{ .dir_fd = dirfd, .relative_path = path }; |
| 4557 | 4418 | |
| 4558 | 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 | 4420 | break :blk fstatat(dirfd, path, flags); |
| 4566 | 4421 | } catch |err| switch (err) { |
| 4567 | 4422 | error.AccessDenied => return error.PermissionDenied, |
| ... | ... | @@ -5147,12 +5002,7 @@ pub fn realpath(pathname: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathE |
| 5147 | 5002 | const pathname_w = try windows.sliceToPrefixedFileW(pathname); |
| 5148 | 5003 | return realpathW(pathname_w.span(), out_buffer); |
| 5149 | 5004 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 5150 | var alloc = std.heap.FixedBufferAllocator.init(out_buffer); | |
| 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; | |
| 5005 | @compileError("WASI does not support os.realpath"); | |
| 5156 | 5006 | } |
| 5157 | 5007 | const pathname_c = try toPosixPath(pathname); |
| 5158 | 5008 | return realpathZ(&pathname_c, out_buffer); |
src/Sema.zig+4-7| ... | ... | @@ -15082,14 +15082,11 @@ fn zirBuiltinSrc( |
| 15082 | 15082 | const file_name_val = blk: { |
| 15083 | 15083 | var anon_decl = try block.startAnonDecl(); |
| 15084 | 15084 | defer anon_decl.deinit(); |
| 15085 | const relative_path = try fn_owner_decl.getFileScope().fullPath(sema.arena); | |
| 15086 | const absolute_path = std.fs.realpathAlloc(sema.arena, relative_path) catch |err| { | |
| 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); | |
| 15085 | // The compiler must not call realpath anywhere. | |
| 15086 | const name = try fn_owner_decl.getFileScope().fullPathZ(anon_decl.arena()); | |
| 15090 | 15087 | const new_decl = try anon_decl.finish( |
| 15091 | try Type.Tag.array_u8_sentinel_0.create(anon_decl.arena(), aboslute_duped.len), | |
| 15092 | try Value.Tag.bytes.create(anon_decl.arena(), aboslute_duped[0 .. aboslute_duped.len + 1]), | |
| 15088 | try Type.Tag.array_u8_sentinel_0.create(anon_decl.arena(), name.len), | |
| 15089 | try Value.Tag.bytes.create(anon_decl.arena(), name[0 .. name.len + 1]), | |
| 15093 | 15090 | 0, // default alignment |
| 15094 | 15091 | ); |
| 15095 | 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 | 37 | /// based on a hard-coded Preopen directory ("/zig") |
| 38 | 38 | pub fn findZigExePath(allocator: mem.Allocator) ![]u8 { |
| 39 | 39 | if (builtin.os.tag == .wasi) { |
| 40 | var args = try std.process.argsWithAllocator(allocator); | |
| 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; | |
| 40 | @compileError("this function is unsupported on WASI"); | |
| 68 | 41 | } |
| 69 | 42 | |
| 70 | 43 | return fs.selfExePathAlloc(allocator); |
| ... | ... | @@ -107,6 +80,9 @@ pub fn findZigLibDirFromSelfExe( |
| 107 | 80 | |
| 108 | 81 | /// Caller owns returned memory. |
| 109 | 82 | pub 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 | 86 | if (std.process.getEnvVarOwned(allocator, "ZIG_GLOBAL_CACHE_DIR")) |value| { |
| 111 | 87 | if (value.len > 0) { |
| 112 | 88 | return value; |
| ... | ... | @@ -125,17 +101,7 @@ pub fn resolveGlobalCacheDir(allocator: mem.Allocator) ![]u8 { |
| 125 | 101 | } |
| 126 | 102 | } |
| 127 | 103 | |
| 128 | if (builtin.os.tag == .wasi) { | |
| 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 | } | |
| 104 | return fs.getAppDataDir(allocator, appname); | |
| 139 | 105 | } |
| 140 | 106 | |
| 141 | 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 | 27 | // Crash report needs to override the panic handler and other root decls |
| 28 | 28 | pub usingnamespace crash_report.root_decls; |
| 29 | 29 | |
| 30 | var wasi_preopens: fs.wasi.Preopens = undefined; | |
| 31 | pub 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 | ||
| 38 | pub 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 | ||
| 30 | 47 | pub fn fatal(comptime format: []const u8, args: anytype) noreturn { |
| 31 | 48 | std.log.err(format, args); |
| 32 | 49 | process.exit(1); |
| ... | ... | @@ -161,20 +178,14 @@ pub fn main() anyerror!void { |
| 161 | 178 | return mainArgs(gpa_tracy.allocator(), arena, args); |
| 162 | 179 | } |
| 163 | 180 | |
| 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 | 181 | if (builtin.os.tag == .wasi) { |
| 168 | // This sets our CWD to "/preopens/cwd" | |
| 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"); | |
| 182 | wasi_preopens = try fs.wasi.preopensAlloc(arena); | |
| 172 | 183 | } |
| 173 | 184 | |
| 174 | 185 | // Short circuit some of the other logic for bootstrapping. |
| 175 | 186 | if (build_options.only_c) { |
| 176 | assert(mem.eql(u8, args[1], "build-obj")); | |
| 177 | return buildOutputType(gpa, arena, args, .{ .build = .Obj }); | |
| 187 | assert(mem.eql(u8, args[1], "build-exe")); | |
| 188 | return buildOutputType(gpa, arena, args, .{ .build = .Exe }); | |
| 178 | 189 | } |
| 179 | 190 | |
| 180 | 191 | return mainArgs(gpa, arena, args); |
| ... | ... | @@ -2300,7 +2311,7 @@ fn buildOutputType( |
| 2300 | 2311 | }, |
| 2301 | 2312 | } |
| 2302 | 2313 | |
| 2303 | if (std.fs.path.isAbsolute(lib_name)) { | |
| 2314 | if (fs.path.isAbsolute(lib_name)) { | |
| 2304 | 2315 | fatal("cannot use absolute path as a system library: {s}", .{lib_name}); |
| 2305 | 2316 | } |
| 2306 | 2317 | |
| ... | ... | @@ -2763,18 +2774,33 @@ fn buildOutputType( |
| 2763 | 2774 | } |
| 2764 | 2775 | } |
| 2765 | 2776 | |
| 2766 | const self_exe_path = try introspect.findZigExePath(arena); | |
| 2767 | var zig_lib_directory: Compilation.Directory = if (override_lib_dir) |unresolved_lib_dir| l: { | |
| 2768 | const lib_dir = try introspect.resolvePath(arena, unresolved_lib_dir); | |
| 2769 | break :l .{ | |
| 2770 | .path = lib_dir, | |
| 2771 | .handle = fs.cwd().openDir(lib_dir, .{}) catch |err| { | |
| 2772 | fatal("unable to open zig lib directory '{s}': {s}", .{ lib_dir, @errorName(err) }); | |
| 2773 | }, | |
| 2777 | const self_exe_path: ?[]const u8 = if (!process.can_spawn) | |
| 2778 | null | |
| 2779 | else | |
| 2780 | introspect.findZigExePath(arena) catch |err| { | |
| 2781 | fatal("unable to find zig self exe path: {s}", .{@errorName(err)}); | |
| 2774 | 2782 | }; |
| 2775 | } else introspect.findZigLibDirFromSelfExe(arena, self_exe_path) catch |err| { | |
| 2776 | fatal("unable to find zig installation directory: {s}\n", .{@errorName(err)}); | |
| 2783 | ||
| 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 | 2804 | defer zig_lib_directory.handle.close(); |
| 2779 | 2805 | |
| 2780 | 2806 | var thread_pool: ThreadPool = undefined; |
| ... | ... | @@ -2791,7 +2817,16 @@ fn buildOutputType( |
| 2791 | 2817 | } |
| 2792 | 2818 | |
| 2793 | 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 | 2830 | break :l .{ |
| 2796 | 2831 | .handle = try fs.cwd().makeOpenPath(p, .{}), |
| 2797 | 2832 | .path = p, |
| ... | ... | @@ -3082,7 +3117,7 @@ fn buildOutputType( |
| 3082 | 3117 | gpa, |
| 3083 | 3118 | arena, |
| 3084 | 3119 | test_exec_args.items, |
| 3085 | self_exe_path, | |
| 3120 | self_exe_path.?, | |
| 3086 | 3121 | arg_mode, |
| 3087 | 3122 | target_info, |
| 3088 | 3123 | watch, |
| ... | ... | @@ -3154,7 +3189,7 @@ fn buildOutputType( |
| 3154 | 3189 | gpa, |
| 3155 | 3190 | arena, |
| 3156 | 3191 | test_exec_args.items, |
| 3157 | self_exe_path, | |
| 3192 | self_exe_path.?, | |
| 3158 | 3193 | arg_mode, |
| 3159 | 3194 | target_info, |
| 3160 | 3195 | watch, |
| ... | ... | @@ -3179,7 +3214,7 @@ fn buildOutputType( |
| 3179 | 3214 | gpa, |
| 3180 | 3215 | arena, |
| 3181 | 3216 | test_exec_args.items, |
| 3182 | self_exe_path, | |
| 3217 | self_exe_path.?, | |
| 3183 | 3218 | arg_mode, |
| 3184 | 3219 | target_info, |
| 3185 | 3220 | watch, |
| ... | ... | @@ -3523,7 +3558,7 @@ fn cmdTranslateC(comp: *Compilation, arena: Allocator, enable_cache: bool) !void |
| 3523 | 3558 | defer tree.deinit(comp.gpa); |
| 3524 | 3559 | |
| 3525 | 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 | 3562 | // Add the files depended on to the cache system. |
| 3528 | 3563 | try man.addDepFilePost(zig_cache_tmp_dir, dep_basename); |
| 3529 | 3564 | // Just to save disk space, we delete the file because it is never needed again. |