diff --git a/doc/langref.html.in b/doc/langref.html.in index 0f90a88822a1..6ae2e456e471 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -11279,7 +11279,7 @@ pub fn main() !void { var preopens = PreopenList.init(gpa); defer preopens.deinit(); - try preopens.populate(); + try preopens.populate(null); for (preopens.asSlice()) |preopen, i| { std.debug.print("{}: {}\n", .{ i, preopen }); diff --git a/lib/std/fs/path.zig b/lib/std/fs/path.zig index bdd1c53ed5df..d5583dcc80d8 100644 --- a/lib/std/fs/path.zig +++ b/lib/std/fs/path.zig @@ -735,7 +735,7 @@ pub fn resolvePosix(allocator: Allocator, paths: []const []const u8) ![]u8 { test "resolve" { if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest; - if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "."); + if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/"); const cwd = try process.getCwdAlloc(testing.allocator); defer testing.allocator.free(cwd); @@ -756,7 +756,7 @@ test "resolveWindows" { return error.SkipZigTest; } if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest; - if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "."); + if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/"); if (native_os == .windows) { const cwd = try process.getCwdAlloc(testing.allocator); defer testing.allocator.free(cwd); @@ -802,7 +802,7 @@ test "resolveWindows" { test "resolvePosix" { if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest; - if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "."); + if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/"); try testResolvePosix(&[_][]const u8{ "/a/b", "c" }, "/a/b/c"); try testResolvePosix(&[_][]const u8{ "/a/b", "c", "//d", "e///" }, "/d/e"); @@ -1216,7 +1216,7 @@ test "relative" { return error.SkipZigTest; } if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest; - if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "."); + if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/"); try testRelativeWindows("c:/blah\\blah", "d:/games", "D:\\games"); try testRelativeWindows("c:/aaaa/bbbb", "c:/aaaa", ".."); diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 82005152e517..b8e6c4d89a8f 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -47,7 +47,7 @@ fn testReadLink(dir: Dir, target_path: []const u8, symlink_path: []const u8) !vo test "accessAbsolute" { if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest; - if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "."); + if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/"); var tmp = tmpDir(.{}); defer tmp.cleanup(); @@ -66,7 +66,7 @@ test "accessAbsolute" { test "openDirAbsolute" { if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest; - if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "."); + if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/"); var tmp = tmpDir(.{}); defer tmp.cleanup(); @@ -103,7 +103,7 @@ test "openDir cwd parent .." { test "readLinkAbsolute" { if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest; - if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "."); + if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/"); var tmp = tmpDir(.{}); defer tmp.cleanup(); @@ -535,7 +535,7 @@ test "rename" { test "renameAbsolute" { if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest; - if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "."); + if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/"); var tmp_dir = tmpDir(.{}); defer tmp_dir.cleanup(); @@ -980,7 +980,7 @@ test "open file with exclusive nonblocking lock twice (absolute paths)" { test "walker" { if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest; - if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "."); + if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/"); var tmp = tmpDir(.{ .iterate = true }); defer tmp.cleanup(); @@ -1031,7 +1031,7 @@ test "walker" { test ". and .. in fs.Dir functions" { if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest; - if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "."); + if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/"); var tmp = tmpDir(.{}); defer tmp.cleanup(); @@ -1060,7 +1060,7 @@ test ". and .. in fs.Dir functions" { test ". and .. in absolute functions" { if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest; - if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "."); + if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/"); var tmp = tmpDir(.{}); defer tmp.cleanup(); diff --git a/lib/std/fs/wasi.zig b/lib/std/fs/wasi.zig index 4754ded630d1..81a43062dcf4 100644 --- a/lib/std/fs/wasi.zig +++ b/lib/std/fs/wasi.zig @@ -3,6 +3,8 @@ const builtin = @import("builtin"); const os = std.os; const mem = std.mem; const math = std.math; +const fs = std.fs; +const assert = std.debug.assert; const Allocator = mem.Allocator; const wasi = std.os.wasi; const fd_t = wasi.fd_t; @@ -34,16 +36,27 @@ pub const PreopenType = union(PreopenTypeTag) { // Checks whether `other` refers to a subdirectory of `self` and, if so, // returns the relative path to `other` from `self` + // + // Expects `other` to be a canonical path, not containing "." or ".." pub fn getRelativePath(self: Self, other: PreopenType) ?[]const u8 { if (std.meta.activeTag(self) != std.meta.activeTag(other)) return null; switch (self) { - PreopenTypeTag.Dir => |this_path| { + PreopenTypeTag.Dir => |self_path| { const other_path = other.Dir; - if (mem.indexOfDiff(u8, this_path, other_path)) |index| { - if (index < this_path.len) return null; + if (mem.indexOfDiff(u8, self_path, other_path)) |index| { + if (index < self_path.len) return null; + } + + const rel_path = other_path[self_path.len..]; + if (rel_path.len == 0) { + return rel_path; + } else if (rel_path[0] == '/') { + return rel_path[1..]; + } else { + if (self_path[self_path.len - 1] != '/') return null; + return rel_path; } - return other_path[this_path.len..]; }, } } @@ -130,7 +143,22 @@ pub const PreopenList = struct { /// the preopen list still contains all valid preopened file descriptors that are valid /// for use. Therefore, it is fine to call `find`, `asSlice`, or `toOwnedSlice`. Finally, /// `deinit` still must be called! - pub fn populate(self: *Self) Error!void { + /// + /// Usage of `cwd_root`: + /// If provided, `cwd_root` is inserted as prefix for any Preopens that + /// begin with "." and all paths are normalized as POSIX-style absolute + /// paths. `cwd_root` must be an absolute path. + /// + /// For example: + /// "./foo/bar" -> "{cwd_root}/foo/bar" + /// "foo/bar" -> "/foo/bar" + /// "/foo/bar" -> "/foo/bar" + /// + /// If `cwd_root` is not provided, all preopen directories are unmodified. + /// + pub fn populate(self: *Self, cwd_root: ?[]const u8) Error!void { + if (cwd_root) |root| assert(fs.path.isAbsolute(root)); + // Clear contents if we're being called again for (self.toOwnedSlice()) |preopen| { switch (preopen.@"type") { @@ -140,6 +168,7 @@ pub const PreopenList = struct { errdefer self.deinit(); var fd: fd_t = 3; // start fd has to be beyond stdio fds + var path_buf: [fs.MAX_PATH_BYTES]u8 = undefined; while (true) { var buf: prestat_t = undefined; switch (wasi.fd_prestat_get(fd, &buf)) { @@ -156,14 +185,34 @@ pub const PreopenList = struct { else => |err| return os.unexpectedErrno(err), } const preopen_len = buf.u.dir.pr_name_len; - const path_buf = try self.buffer.allocator.alloc(u8, preopen_len); - mem.set(u8, path_buf, 0); - switch (wasi.fd_prestat_dir_name(fd, path_buf.ptr, preopen_len)) { + + mem.set(u8, path_buf[0..preopen_len], 0); + switch (wasi.fd_prestat_dir_name(fd, &path_buf, preopen_len)) { .SUCCESS => {}, else => |err| return os.unexpectedErrno(err), } - const preopen = Preopen.new(fd, PreopenType{ .Dir = path_buf }); + // Unfortunately, WASI runtimes (e.g. wasmer) are not consistent about whether the + // NULL sentinel is included in the reported Preopen name_len + const raw_path = if (path_buf[preopen_len - 1] == 0) blk: { + break :blk path_buf[0 .. preopen_len - 1]; + } else path_buf[0..preopen_len]; + + // If we were provided a CWD root to resolve against, we try to treat Preopen dirs as + // POSIX paths, relative to "/" or `cwd_root` depending on whether they start with "." + const path = if (cwd_root) |cwd| blk: { + const resolve_paths: [][]const u8 = if (raw_path[0] == '.') &.{ cwd, raw_path } else &.{ "/", raw_path }; + break :blk fs.path.resolve(self.buffer.allocator, resolve_paths) catch |err| switch (err) { + error.CurrentWorkingDirectoryUnlinked => unreachable, // root is absolute, so CWD not queried + else => |e| return e, + }; + } else blk: { + // If we were provided no CWD root, we preserve the preopen dir without resolving + break :blk try self.buffer.allocator.dupe(u8, raw_path); + }; + errdefer self.buffer.allocator.free(path); + const preopen = Preopen.new(fd, .{ .Dir = path }); + try self.buffer.append(preopen); fd = try math.add(fd_t, fd, 1); } @@ -171,27 +220,22 @@ pub const PreopenList = struct { /// Find a preopen which includes access to `preopen_type`. /// - /// If the preopen exists, `relative_path` is updated to point to the relative - /// portion of `preopen_type` and the matching Preopen is returned. If multiple - /// preopens match the provided resource, the most recent one is used. + /// If multiple preopens match the provided resource, the most specific + /// match is returned. More recent preopens take priority, as well. pub fn findContaining(self: Self, preopen_type: PreopenType) ?PreopenUri { - // Search in reverse, so that most recently added preopens take precedence - var k: usize = self.buffer.items.len; - while (k > 0) { - k -= 1; - - const preopen = self.buffer.items[k]; - if (preopen.@"type".getRelativePath(preopen_type)) |rel_path_orig| { - var rel_path = rel_path_orig; - while (rel_path.len > 0 and rel_path[0] == '/') rel_path = rel_path[1..]; - - return PreopenUri{ - .base = preopen, - .relative_path = if (rel_path.len == 0) "." else rel_path, - }; + var best_match: ?PreopenUri = null; + + for (self.buffer.items) |preopen| { + if (preopen.@"type".getRelativePath(preopen_type)) |rel_path| { + if (best_match == null or rel_path.len <= best_match.?.relative_path.len) { + best_match = PreopenUri{ + .base = preopen, + .relative_path = if (rel_path.len == 0) "." else rel_path, + }; + } } } - return null; + return best_match; } /// Find preopen by fd. If the preopen exists, return it. @@ -233,8 +277,20 @@ test "extracting WASI preopens" { var preopens = PreopenList.init(std.testing.allocator); defer preopens.deinit(); - try preopens.populate(); + try preopens.populate(null); const preopen = preopens.find(PreopenType{ .Dir = "." }) orelse unreachable; try std.testing.expect(preopen.@"type".eql(PreopenType{ .Dir = "." })); + + const po_type1 = PreopenType{ .Dir = "/" }; + try std.testing.expect(std.mem.eql(u8, po_type1.getRelativePath(.{ .Dir = "/" }).?, "")); + try std.testing.expect(std.mem.eql(u8, po_type1.getRelativePath(.{ .Dir = "/test/foobar" }).?, "test/foobar")); + + const po_type2 = PreopenType{ .Dir = "/test/foo" }; + try std.testing.expect(po_type2.getRelativePath(.{ .Dir = "/test/foobar" }) == null); + + const po_type3 = PreopenType{ .Dir = "/test" }; + try std.testing.expect(std.mem.eql(u8, po_type3.getRelativePath(.{ .Dir = "/test" }).?, "")); + try std.testing.expect(std.mem.eql(u8, po_type3.getRelativePath(.{ .Dir = "/test/" }).?, "")); + try std.testing.expect(std.mem.eql(u8, po_type3.getRelativePath(.{ .Dir = "/test/foo/bar" }).?, "foo/bar")); } diff --git a/lib/std/os.zig b/lib/std/os.zig index df42d14c3d4b..f9ee65a31744 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -1431,10 +1431,8 @@ var wasi_cwd = if (builtin.os.tag == .wasi and !builtin.link_libc) struct { preopens: ?PreopenList = null, // Memory buffer for storing the relative portion of the CWD path_buffer: [MAX_PATH_BYTES]u8 = undefined, - // Current Working Directory, stored as an fd_t and a relative path - cwd: ?RelativePathWasi = null, - // Preopen associated with `cwd`, if any - cwd_preopen: ?Preopen = null, + // The absolute path associated with the current working directory + cwd: []const u8 = "/", }{} else undefined; /// Initialize the available Preopen list on WASI and set the CWD to `cwd_init`. @@ -1444,31 +1442,33 @@ var wasi_cwd = if (builtin.os.tag == .wasi and !builtin.link_libc) struct { /// This must be called before using any relative or absolute paths with `std.os` /// functions, if you are on WASI without linking libc. /// +/// The current working directory is initialized to `cwd_root`, and `cwd_root` +/// is inserted as a prefix for any Preopens whose dir begins with "." +/// For example: +/// "./foo/bar" - canonicalizes to -> "{cwd_root}/foo/bar" +/// "foo/bar" - canonicalizes to -> "/foo/bar" +/// "/foo/bar" - canonicalizes to -> "/foo/bar" +/// +/// `cwd_root` must be an absolute path. For initialization behavior similar to +/// wasi-libc, use "/" as the `cwd_root` +/// /// `alloc` must not be a temporary or leak-detecting allocator, since `std.os` /// retains ownership of allocations internally and may never call free(). -pub fn initPreopensWasi(alloc: Allocator, cwd_init: ?[]const u8) !void { +pub fn initPreopensWasi(alloc: Allocator, cwd_root: []const u8) !void { if (builtin.os.tag == .wasi) { if (!builtin.link_libc) { - if (wasi_cwd.preopens == null) { - var preopen_list = PreopenList.init(alloc); - try preopen_list.populate(); - wasi_cwd.preopens = preopen_list; - } - if (cwd_init) |cwd| { - const preopen = wasi_cwd.preopens.?.findContaining(.{ .Dir = cwd }); - if (preopen) |po| { - wasi_cwd.cwd_preopen = po.base; - wasi_cwd.cwd = RelativePathWasi{ - .dir_fd = po.base.fd, - .relative_path = po.relative_path, - }; - } else { - // No matching preopen found - return error.FileNotFound; - } - } + var preopen_list = PreopenList.init(alloc); + errdefer preopen_list.deinit(); + try preopen_list.populate(cwd_root); + + var path_alloc = std.heap.FixedBufferAllocator.init(&wasi_cwd.path_buffer); + wasi_cwd.cwd = try path_alloc.allocator().dupe(u8, cwd_root); + + if (wasi_cwd.preopens) |preopens| preopens.deinit(); + wasi_cwd.preopens = preopen_list; } else { - if (cwd_init) |cwd| try chdir(cwd); + // wasi-libc defaults to an effective CWD root of "/" + if (!mem.eql(u8, cwd_root, "/")) return error.UnsupportedDirectory; } } } @@ -1477,69 +1477,22 @@ pub fn initPreopensWasi(alloc: Allocator, cwd_init: ?[]const u8) !void { /// /// For absolute paths, this automatically searches among available Preopens to find /// a match. For relative paths, it uses the "emulated" CWD. +/// Automatically looks up the correct Preopen corresponding to the provided path. pub fn resolvePathWasi(path: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) !RelativePathWasi { - // Note: Due to WASI's "sandboxed" file handles, operations with this RelativePathWasi - // will fail if the relative path navigates outside of `dir_fd` using ".." - return resolvePathAndGetWasiPreopen(path, null, out_buffer); -} - -fn resolvePathAndGetWasiPreopen(path: []const u8, preopen: ?*?Preopen, out_buffer: *[MAX_PATH_BYTES]u8) !RelativePathWasi { var allocator = std.heap.FixedBufferAllocator.init(out_buffer); var alloc = allocator.allocator(); - if (fs.path.isAbsolute(path) or wasi_cwd.cwd == null) { - if (wasi_cwd.preopens == null) @panic("On WASI, `initPreopensWasi` must be called to initialize preopens " ++ - "before using any CWD-relative or absolute paths.\n"); - - if (mem.startsWith(u8, path, "/preopens/fd/")) { - // "/preopens/fd/" is a special prefix, which refers to a Preopen directly by fd - const fd_start = "/preopens/fd/".len; - const fd_end = mem.indexOfScalarPos(u8, path, fd_start, '/') orelse path.len; - const fd = std.fmt.parseUnsigned(fd_t, path[fd_start..fd_end], 10) catch unreachable; - const rel_path = if (path.len > fd_end + 1) path[fd_end + 1 ..] else "."; - - if (preopen) |p| p.* = wasi_cwd.preopens.?.findByFd(fd); - return RelativePathWasi{ - .dir_fd = fd, - .relative_path = alloc.dupe(u8, rel_path) catch return error.NameTooLong, - }; - } - - // For any other absolute path, we need to lookup a containing Preopen - const abs_path = fs.path.resolve(alloc, &.{ "/", path }) catch return error.NameTooLong; - const preopen_uri = wasi_cwd.preopens.?.findContaining(.{ .Dir = abs_path }); + const abs_path = fs.path.resolve(alloc, &.{ wasi_cwd.cwd, path }) catch return error.NameTooLong; + const preopen_uri = wasi_cwd.preopens.?.findContaining(.{ .Dir = abs_path }); - if (preopen_uri) |po| { - if (preopen) |p| p.* = po.base; - return RelativePathWasi{ - .dir_fd = po.base.fd, - .relative_path = po.relative_path, - }; - } else { - // No matching preopen found - return error.AccessDenied; - } - } else { - const cwd = wasi_cwd.cwd.?; - - // If the path is empty or "." or "./", return CWD - if (std.mem.eql(u8, path, ".") or std.mem.eql(u8, path, "./")) { - return cwd; - } - - // First resolve a combined path, where the "/" corresponds to `cwd.dir_fd` - // not the true filesystem root - const paths = &.{ "/", cwd.relative_path, path }; - const resolved_path = fs.path.resolve(alloc, paths) catch return error.NameTooLong; - - // Strip off the fake root to get the relative path w.r.t. `cwd.dir_fd` - const resolved_relative_path = resolved_path[1..]; - - if (preopen) |p| p.* = wasi_cwd.cwd_preopen; + if (preopen_uri) |po| { return RelativePathWasi{ - .dir_fd = cwd.dir_fd, - .relative_path = resolved_relative_path, + .dir_fd = po.base.fd, + .relative_path = po.relative_path, }; + } else { + // No matching preopen found + return error.AccessDenied; } } @@ -1980,11 +1933,7 @@ pub fn getcwd(out_buffer: []u8) GetCwdError![]u8 { if (builtin.os.tag == .windows) { return windows.GetCurrentDirectory(out_buffer); } else if (builtin.os.tag == .wasi and !builtin.link_libc) { - var buf: [MAX_PATH_BYTES]u8 = undefined; - const path = realpathWasi(".", &buf) catch |err| switch (err) { - error.NameTooLong => return error.NameTooLong, - error.InvalidHandle => return error.CurrentWorkingDirectoryUnlinked, - }; + const path = wasi_cwd.cwd; if (out_buffer.len < path.len) return error.NameTooLong; std.mem.copy(u8, out_buffer, path); return out_buffer[0..path.len]; @@ -2949,16 +2898,17 @@ pub const ChangeCurDirError = error{ /// `dir_path` is recommended to be a UTF-8 encoded string. pub fn chdir(dir_path: []const u8) ChangeCurDirError!void { if (builtin.os.tag == .wasi and !builtin.link_libc) { - var preopen: ?Preopen = null; - const path = try resolvePathAndGetWasiPreopen(dir_path, &preopen, &wasi_cwd.path_buffer); + var buf: [MAX_PATH_BYTES]u8 = undefined; + var alloc = std.heap.FixedBufferAllocator.init(&buf); + const path = try fs.resolve(alloc.allocator(), &.{ wasi_cwd.cwd, dir_path }); - const dirinfo = try fstatat(path.dir_fd, path.relative_path, 0); + const dirinfo = try fstatat(AT.FDCWD, path, 0); if (dirinfo.filetype != .DIRECTORY) { return error.NotDir; } - wasi_cwd.cwd_preopen = preopen; - wasi_cwd.cwd = path; + var cwd_alloc = std.heap.FixedBufferAllocator.init(&wasi_cwd.path_buffer); + wasi_cwd.cwd = try cwd_alloc.allocator().dupe(u8, path); return; } else if (builtin.os.tag == .windows) { var utf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined; @@ -3010,29 +2960,15 @@ pub const FchdirError = error{ } || UnexpectedError; pub fn fchdir(dirfd: fd_t) FchdirError!void { - if (builtin.os.tag == .wasi) { - // Check that this is a directory - const dirinfo = fstatat(dirfd, ".", 0) catch unreachable; - if (dirinfo.filetype != .DIRECTORY) { - return error.NotDir; - } - - wasi_cwd.cwd = .{ - .dir_fd = dirfd, - .relative_path = ".", - }; - wasi_cwd.cwd_preopen = null; - } else { - while (true) { - switch (errno(system.fchdir(dirfd))) { - .SUCCESS => return, - .ACCES => return error.AccessDenied, - .BADF => unreachable, - .NOTDIR => return error.NotDir, - .INTR => continue, - .IO => return error.FileSystem, - else => |err| return unexpectedErrno(err), - } + while (true) { + switch (errno(system.fchdir(dirfd))) { + .SUCCESS => return, + .ACCES => return error.AccessDenied, + .BADF => unreachable, + .NOTDIR => return error.NotDir, + .INTR => continue, + .IO => return error.FileSystem, + else => |err| return unexpectedErrno(err), } } } @@ -5102,47 +5038,17 @@ pub fn realpath(pathname: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathE const pathname_w = try windows.sliceToPrefixedFileW(pathname); return realpathW(pathname_w.span(), out_buffer); } else if (builtin.os.tag == .wasi and !builtin.link_libc) { - return realpathWasi(pathname, out_buffer); + var alloc = std.heap.FixedBufferAllocator.init(out_buffer); + + // NOTE: This emulation is incomplete. Symbolic links are not + // currently expanded during path canonicalization. + const paths = &.{ wasi_cwd.cwd, pathname }; + return fs.path.resolve(alloc.allocator(), paths) catch error.NameTooLong; } const pathname_c = try toPosixPath(pathname); return realpathZ(&pathname_c, out_buffer); } -/// Return an emulated canonicalized absolute pathname on WASI. -/// -/// NOTE: This emulation is incomplete. Symbolic links are not -/// currently expanded during path canonicalization. -fn realpathWasi(pathname: []const u8, out_buffer: []u8) ![]u8 { - var alloc = std.heap.FixedBufferAllocator.init(out_buffer); - if (fs.path.isAbsolute(pathname)) - return try fs.path.resolve(alloc.allocator(), &.{pathname}) catch error.NameTooLong; - if (wasi_cwd.cwd) |cwd| { - if (wasi_cwd.cwd_preopen) |po| { - var base_cwd_dir = switch (po.@"type") { - .Dir => |dir| dir, - }; - const paths: [][]const u8 = if (fs.path.isAbsolute(base_cwd_dir)) blk: { - break :blk &.{ base_cwd_dir, cwd.relative_path, pathname }; - } else blk: { - // No absolute path is associated with this preopen, so - // instead we use a special "/preopens/fd//" prefix - var buf: [16]u8 = undefined; - var fbs = std.io.fixedBufferStream(&buf); - std.fmt.formatInt(po.fd, 10, .lower, .{}, fbs.writer()) catch return error.NameTooLong; - break :blk &.{ "/preopens/fd/", fbs.getWritten(), cwd.relative_path, pathname }; - }; - - return fs.path.resolve(alloc.allocator(), paths) catch error.NameTooLong; - } else { - // The CWD is not rooted to an existing Preopen, - // so we have no way to know its absolute path - return error.InvalidHandle; - } - } else { - return try fs.path.resolve(alloc.allocator(), &.{ "/", pathname }) catch error.NameTooLong; - } -} - /// Same as `realpath` except `pathname` is null-terminated. pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 { if (builtin.os.tag == .windows) { diff --git a/lib/std/os/test.zig b/lib/std/os/test.zig index 8598c7b3be3e..ae0d14ef7e5c 100644 --- a/lib/std/os/test.zig +++ b/lib/std/os/test.zig @@ -49,7 +49,7 @@ test "chdir smoke test" { test "open smoke test" { if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest; - if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "."); + if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/"); // TODO verify file attributes using `fstat` @@ -104,7 +104,7 @@ test "open smoke test" { test "openat smoke test" { if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest; - if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "."); + if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/"); // TODO verify file attributes using `fstatat` @@ -141,7 +141,7 @@ test "openat smoke test" { test "symlink with relative paths" { if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest; - if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "."); + if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/"); const cwd = fs.cwd(); cwd.deleteFile("file.txt") catch {}; @@ -197,7 +197,7 @@ test "link with relative paths" { if (builtin.link_libc) { return error.SkipZigTest; } else { - try os.initPreopensWasi(std.heap.page_allocator, "."); + try os.initPreopensWasi(std.heap.page_allocator, "/"); } }, .linux, .solaris => {}, @@ -237,7 +237,7 @@ test "link with relative paths" { test "linkat with different directories" { switch (native_os) { - .wasi => if (!builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "."), + .wasi => if (!builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/"), .linux, .solaris => {}, else => return error.SkipZigTest, } @@ -898,7 +898,7 @@ test "POSIX file locking with fcntl" { test "rename smoke test" { if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest; - if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "."); + if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/"); var tmp = tmpDir(.{}); defer tmp.cleanup(); @@ -955,7 +955,7 @@ test "rename smoke test" { test "access smoke test" { if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest; - if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "."); + if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/"); var tmp = tmpDir(.{}); defer tmp.cleanup(); diff --git a/lib/std/testing.zig b/lib/std/testing.zig index 56cc86d769d1..004e2d0fa7af 100644 --- a/lib/std/testing.zig +++ b/lib/std/testing.zig @@ -380,7 +380,7 @@ fn getCwdOrWasiPreopen() std.fs.Dir { if (builtin.os.tag == .wasi and !builtin.link_libc) { var preopens = std.fs.wasi.PreopenList.init(allocator); defer preopens.deinit(); - preopens.populate() catch + preopens.populate(null) catch @panic("unable to make tmp dir for testing: unable to populate preopens"); const preopen = preopens.find(std.fs.wasi.PreopenType{ .Dir = "." }) orelse @panic("unable to make tmp dir for testing: didn't find '.' in the preopens");