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

Two major changes here:
  1. We store the CWD as a simple `[]const u8` and lookup Preopens for
     every absolute or CWD-referenced file operation, based on the
     Preopen with the longest match (i.e. most specific path)
  2. Preorders are normalized to POSIX absolute paths at init time.
     Behavior depends on the "cwd_root" parameter of `initPreopensWasi`:

	`cwd_root` is used for any Preopens that start with "."

	  For example:
            "./foo/bar" - inits to -> "{cwd_root}/foo/bar"
            "foo/bar"   - inits to -> "/foo/bar"
	    "/foo/bar"  - inits to -> "/foo/bar"

        `cwd_root` must be an absolute path.

	Using "/" as `cwd_root` gives behavior similar to wasi-libc.
This commit is contained in:
Cody Tapscott 2022-03-03 12:25:21 -07:00 committed by Jakub Konka
parent 8f75823728
commit 7b090df668
7 changed files with 158 additions and 196 deletions

View File

@ -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 });

View File

@ -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", "..");

View File

@ -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();

View File

@ -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;
var best_match: ?PreopenUri = null;
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,
};
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"));
}

View File

@ -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");
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 (mem.startsWith(u8, path, "/preopens/fd/")) {
// "/preopens/fd/<N>" 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 });
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/<N>/" 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) {

View File

@ -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();

View File

@ -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");