Commit a250af5a51

Edoardo Vacchi <evacchi@users.noreply.github.com>
2023-02-16 18:16:31
wasi: add Preopens.findDir, update tests to preopen `/tmp'
Signed-off-by: Edoardo Vacchi <evacchi@users.noreply.github.com>
1 parent 07630eb
Changed files (3)
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");
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();
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/<rand-path>");
+
+    defer tmp_path.close();
+    defer tmp_path.deleteTree(sub_path) catch {};
+
+    // create a file under /tmp/<rand>/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/<rand>" 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/"));
+}