| ... | ... | @@ -9,6 +9,7 @@ const Allocator = mem.Allocator; |
| 9 | 9 | const wasi = std.os.wasi; |
| 10 | 10 | const fd_t = wasi.fd_t; |
| 11 | 11 | const prestat_t = wasi.prestat_t; |
| 12 | const testing = std.testing; |
| 12 | 13 | |
| 13 | 14 | pub const Preopens = struct { |
| 14 | 15 | // Indexed by file descriptor number. |
| ... | ... | @@ -22,6 +23,30 @@ pub const Preopens = struct { |
| 22 | 23 | } |
| 23 | 24 | return null; |
| 24 | 25 | } |
| 26 | |
| 27 | pub fn findDir(p: Preopens, full_path: []const u8, flags: std.fs.Dir.OpenDirOptions) std.fs.Dir.OpenError!std.fs.Dir { |
| 28 | if (p.names.len <= 2) |
| 29 | return std.fs.Dir.OpenError.BadPathName; // there are no preopens |
| 30 | |
| 31 | var prefix: []const u8 = ""; |
| 32 | var fd: usize = 0; |
| 33 | for (p.names) |preopen, i| { |
| 34 | if (i > 2 and wasiPathPrefixMatches(preopen, full_path)) { |
| 35 | if (preopen.len > prefix.len) { |
| 36 | prefix = preopen; |
| 37 | fd = i; |
| 38 | } |
| 39 | } |
| 40 | } |
| 41 | |
| 42 | // still no match |
| 43 | if (fd == 0) { |
| 44 | return std.fs.Dir.OpenError.FileNotFound; |
| 45 | } |
| 46 | const d = std.fs.Dir{ .fd = @intCast(os.fd_t, fd) }; |
| 47 | const rel = full_path[prefix.len + 1 .. full_path.len]; |
| 48 | return d.openDirWasi(rel, flags); |
| 49 | } |
| 25 | 50 | }; |
| 26 | 51 | |
| 27 | 52 | pub fn preopensAlloc(gpa: Allocator) Allocator.Error!Preopens { |
| ... | ... | @@ -54,3 +79,86 @@ pub fn preopensAlloc(gpa: Allocator) Allocator.Error!Preopens { |
| 54 | 79 | names.appendAssumeCapacity(name); |
| 55 | 80 | } |
| 56 | 81 | } |
| 82 | |
| 83 | fn wasiPathPrefixMatches(prefix: []const u8, path: []const u8) bool { |
| 84 | if (path[0] != '/' and prefix.len == 0) |
| 85 | return true; |
| 86 | |
| 87 | if (path.len < prefix.len) |
| 88 | return false; |
| 89 | |
| 90 | if (prefix.len == 1) { |
| 91 | return prefix[0] == path[0]; |
| 92 | } |
| 93 | |
| 94 | if (!std.mem.eql(u8, path[0..prefix.len], prefix)) { |
| 95 | return false; |
| 96 | } |
| 97 | |
| 98 | return path.len == prefix.len or |
| 99 | path[prefix.len] == '/'; |
| 100 | } |
| 101 | |
| 102 | test "preopens" { |
| 103 | if (builtin.os.tag != .wasi) return error.SkipZigTest; |
| 104 | |
| 105 | // lifted from `testing` |
| 106 | const random_bytes_count = 12; |
| 107 | const buf_size = 256; |
| 108 | const path = "/tmp"; |
| 109 | const tmp_file_name = "file.txt"; |
| 110 | const nonsense = "nonsense"; |
| 111 | |
| 112 | var random_bytes: [random_bytes_count]u8 = undefined; |
| 113 | var buf: [buf_size]u8 = undefined; |
| 114 | |
| 115 | std.crypto.random.bytes(&random_bytes); |
| 116 | const sub_path = std.fs.base64_encoder.encode(&buf, &random_bytes); |
| 117 | |
| 118 | // find all preopens |
| 119 | const allocator = std.heap.page_allocator; |
| 120 | var wasi_preopens = try std.fs.wasi.preopensAlloc(allocator); |
| 121 | |
| 122 | // look for the exact "/tmp" preopen match |
| 123 | const fd = std.fs.wasi.Preopens.find(wasi_preopens, path) orelse unreachable; |
| 124 | const base_dir = std.fs.Dir{ .fd = fd }; |
| 125 | |
| 126 | var tmp_path = base_dir.makeOpenPath(sub_path, .{}) catch |
| 127 | @panic("unable to make tmp dir for testing: /tmp/<rand-path>"); |
| 128 | |
| 129 | defer tmp_path.close(); |
| 130 | defer tmp_path.deleteTree(sub_path) catch {}; |
| 131 | |
| 132 | // create a file under /tmp/<rand>/file.txt with contents "nonsense" |
| 133 | try tmp_path.writeFile(tmp_file_name, nonsense); |
| 134 | |
| 135 | // now look for the file as a single path |
| 136 | var tmp_dir_path_buf: [buf_size]u8 = undefined; |
| 137 | const tmp_dir_path = try std.fmt.bufPrint(&tmp_dir_path_buf, "{s}/{s}", .{ path, sub_path }); |
| 138 | |
| 139 | // find "/tmp/<rand>" using `findDir()` |
| 140 | const tmp_file_dir = try wasi_preopens.findDir(tmp_dir_path, .{}); |
| 141 | |
| 142 | const text = try tmp_file_dir.readFile(tmp_file_name, &buf); |
| 143 | |
| 144 | // ensure the file contents match "nonsense" |
| 145 | try testing.expect(std.mem.eql(u8, nonsense, text)); |
| 146 | } |
| 147 | |
| 148 | test "wasiPathPrefixMatches" { |
| 149 | try testing.expect(wasiPathPrefixMatches("/", "/foo")); |
| 150 | try testing.expect(wasiPathPrefixMatches("/testcases", "/testcases/test.txt")); |
| 151 | try testing.expect(wasiPathPrefixMatches("", "foo")); |
| 152 | try testing.expect(wasiPathPrefixMatches("foo", "foo")); |
| 153 | try testing.expect(wasiPathPrefixMatches("foo", "foo/bar")); |
| 154 | try testing.expect(!wasiPathPrefixMatches("bar", "foo/bar")); |
| 155 | try testing.expect(!wasiPathPrefixMatches("bar", "foo")); |
| 156 | try testing.expect(wasiPathPrefixMatches("foo", "foo/bar")); |
| 157 | try testing.expect(!wasiPathPrefixMatches("fooo", "foo")); |
| 158 | try testing.expect(!wasiPathPrefixMatches("foo", "fooo")); |
| 159 | try testing.expect(!wasiPathPrefixMatches("foo/bar", "foo")); |
| 160 | try testing.expect(!wasiPathPrefixMatches("bar/foo", "foo")); |
| 161 | try testing.expect(wasiPathPrefixMatches("/foo", "/foo")); |
| 162 | try testing.expect(wasiPathPrefixMatches("/foo", "/foo")); |
| 163 | try testing.expect(wasiPathPrefixMatches("/foo", "/foo/")); |
| 164 | } |