| ... | @@ -9,6 +9,7 @@ const Allocator = mem.Allocator; | ... | @@ -9,6 +9,7 @@ const Allocator = mem.Allocator; |
| 9 | const wasi = std.os.wasi; | 9 | const wasi = std.os.wasi; |
| 10 | const fd_t = wasi.fd_t; | 10 | const fd_t = wasi.fd_t; |
| 11 | const prestat_t = wasi.prestat_t; | 11 | const prestat_t = wasi.prestat_t; |
| | 12 | const testing = std.testing; |
| 12 | | 13 | |
| 13 | pub const Preopens = struct { | 14 | pub const Preopens = struct { |
| 14 | // Indexed by file descriptor number. | 15 | // Indexed by file descriptor number. |
| ... | @@ -22,6 +23,30 @@ pub const Preopens = struct { | ... | @@ -22,6 +23,30 @@ pub const Preopens = struct { |
| 22 | } | 23 | } |
| 23 | return null; | 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 | pub fn preopensAlloc(gpa: Allocator) Allocator.Error!Preopens { | 52 | pub fn preopensAlloc(gpa: Allocator) Allocator.Error!Preopens { |
| ... | @@ -54,3 +79,86 @@ pub fn preopensAlloc(gpa: Allocator) Allocator.Error!Preopens { | ... | @@ -54,3 +79,86 @@ pub fn preopensAlloc(gpa: Allocator) Allocator.Error!Preopens { |
| 54 | names.appendAssumeCapacity(name); | 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 or builtin.link_libc) 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 | } |