diff --git a/lib/std/Build/CompileStep.zig b/lib/std/Build/CompileStep.zig index 1f145f8171..eeb7da1596 100644 --- a/lib/std/Build/CompileStep.zig +++ b/lib/std/Build/CompileStep.zig @@ -1555,7 +1555,9 @@ fn make(step: *Step) !void { try zig_args.append("--test-cmd"); try zig_args.append(bin_name); try zig_args.append("--test-cmd"); - try zig_args.append("--dir=."); + try zig_args.append("--mapdir=/::."); + try zig_args.append("--test-cmd"); + try zig_args.append("--mapdir=/tmp::/tmp"); try zig_args.append("--test-cmd-bin"); } else { try zig_args.append("--test-no-exec"); diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 16458d7dc4..957fe4902f 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -13,6 +13,11 @@ const File = std.fs.File; const tmpDir = testing.tmpDir; const tmpIterableDir = testing.tmpIterableDir; +// ensure tests for fs/wasi.zig are run +comptime { + _ = std.fs.wasi; +} + test "Dir.readLink" { var tmp = tmpDir(.{}); defer tmp.cleanup(); diff --git a/lib/std/fs/wasi.zig b/lib/std/fs/wasi.zig index fa9de0dff1..a8b9fae2e9 100644 --- a/lib/std/fs/wasi.zig +++ b/lib/std/fs/wasi.zig @@ -9,6 +9,7 @@ const Allocator = mem.Allocator; const wasi = std.os.wasi; const fd_t = wasi.fd_t; const prestat_t = wasi.prestat_t; +const testing = std.testing; pub const Preopens = struct { // Indexed by file descriptor number. @@ -22,6 +23,30 @@ pub const Preopens = struct { } return null; } + + pub fn findDir(p: Preopens, full_path: []const u8, flags: std.fs.Dir.OpenDirOptions) std.fs.Dir.OpenError!std.fs.Dir { + if (p.names.len <= 2) + return std.fs.Dir.OpenError.BadPathName; // there are no preopens + + var prefix: []const u8 = ""; + var fd: usize = 0; + for (p.names) |preopen, i| { + if (i > 2 and wasiPathPrefixMatches(preopen, full_path)) { + if (preopen.len > prefix.len) { + prefix = preopen; + fd = i; + } + } + } + + // still no match + if (fd == 0) { + return std.fs.Dir.OpenError.FileNotFound; + } + const d = std.fs.Dir{ .fd = @intCast(os.fd_t, fd) }; + const rel = full_path[prefix.len + 1 .. full_path.len]; + return d.openDirWasi(rel, flags); + } }; pub fn preopensAlloc(gpa: Allocator) Allocator.Error!Preopens { @@ -54,3 +79,86 @@ pub fn preopensAlloc(gpa: Allocator) Allocator.Error!Preopens { names.appendAssumeCapacity(name); } } + +fn wasiPathPrefixMatches(prefix: []const u8, path: []const u8) bool { + if (path[0] != '/' and prefix.len == 0) + return true; + + if (path.len < prefix.len) + return false; + + if (prefix.len == 1) { + return prefix[0] == path[0]; + } + + if (!std.mem.eql(u8, path[0..prefix.len], prefix)) { + return false; + } + + return path.len == prefix.len or + path[prefix.len] == '/'; +} + +test "preopens" { + if (builtin.os.tag != .wasi) return error.SkipZigTest; + + // lifted from `testing` + const random_bytes_count = 12; + const buf_size = 256; + const path = "/tmp"; + const tmp_file_name = "file.txt"; + const nonsense = "nonsense"; + + var random_bytes: [random_bytes_count]u8 = undefined; + var buf: [buf_size]u8 = undefined; + + std.crypto.random.bytes(&random_bytes); + const sub_path = std.fs.base64_encoder.encode(&buf, &random_bytes); + + // find all preopens + const allocator = std.heap.page_allocator; + var wasi_preopens = try std.fs.wasi.preopensAlloc(allocator); + + // look for the exact "/tmp" preopen match + const fd = std.fs.wasi.Preopens.find(wasi_preopens, path) orelse unreachable; + const base_dir = std.fs.Dir{ .fd = fd }; + + var tmp_path = base_dir.makeOpenPath(sub_path, .{}) catch + @panic("unable to make tmp dir for testing: /tmp/"); + + defer tmp_path.close(); + defer tmp_path.deleteTree(sub_path) catch {}; + + // create a file under /tmp//file.txt with contents "nonsense" + try tmp_path.writeFile(tmp_file_name, nonsense); + + // now look for the file as a single path + var tmp_dir_path_buf: [buf_size]u8 = undefined; + const tmp_dir_path = try std.fmt.bufPrint(&tmp_dir_path_buf, "{s}/{s}", .{ path, sub_path }); + + // find "/tmp/" using `findDir()` + const tmp_file_dir = try wasi_preopens.findDir(tmp_dir_path, .{}); + + const text = try tmp_file_dir.readFile(tmp_file_name, &buf); + + // ensure the file contents match "nonsense" + try testing.expect(std.mem.eql(u8, nonsense, text)); +} + +test "wasiPathPrefixMatches" { + try testing.expect(wasiPathPrefixMatches("/", "/foo")); + try testing.expect(wasiPathPrefixMatches("/testcases", "/testcases/test.txt")); + try testing.expect(wasiPathPrefixMatches("", "foo")); + try testing.expect(wasiPathPrefixMatches("foo", "foo")); + try testing.expect(wasiPathPrefixMatches("foo", "foo/bar")); + try testing.expect(!wasiPathPrefixMatches("bar", "foo/bar")); + try testing.expect(!wasiPathPrefixMatches("bar", "foo")); + try testing.expect(wasiPathPrefixMatches("foo", "foo/bar")); + try testing.expect(!wasiPathPrefixMatches("fooo", "foo")); + try testing.expect(!wasiPathPrefixMatches("foo", "fooo")); + try testing.expect(!wasiPathPrefixMatches("foo/bar", "foo")); + try testing.expect(!wasiPathPrefixMatches("bar/foo", "foo")); + try testing.expect(wasiPathPrefixMatches("/foo", "/foo")); + try testing.expect(wasiPathPrefixMatches("/foo", "/foo")); + try testing.expect(wasiPathPrefixMatches("/foo", "/foo/")); +}