Skip to content

stdlib std.os: Update CWD emulation to match wasi-libc behavior #11053

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion doc/langref.html.in
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
Expand Down
8 changes: 4 additions & 4 deletions lib/std/fs/path.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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", "..");
Expand Down
14 changes: 7 additions & 7 deletions lib/std/fs/test.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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();
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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();
Expand Down
112 changes: 84 additions & 28 deletions lib/std/fs/wasi.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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..];
},
}
}
Expand Down Expand Up @@ -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") {
Expand All @@ -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)) {
Expand All @@ -156,42 +185,57 @@ 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);
}
}

/// 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.
Expand Down Expand Up @@ -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"));
}
Loading