authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2023-02-19 17:09:58+01:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-02-19 17:09:58+01:00
log772a0eb68ac95b7e24508580499b49872fdb541f
treebcb3828f033149338184fda3486c45415511ab9d
parent0bb178bbb2451238a326c6e916ecf38fbc34cab1
parent4940afc434170966a9445b789374ba2a0f0318ab
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #14661 from evacchi/zig-wasi-preopens


3 files changed, 116 insertions(+), 1 deletions(-)

lib/std/Build/CompileStep.zig+3-1
...@@ -1555,7 +1555,9 @@ fn make(step: *Step) !void {...@@ -1555,7 +1555,9 @@ fn make(step: *Step) !void {
1555 try zig_args.append("--test-cmd");1555 try zig_args.append("--test-cmd");
1556 try zig_args.append(bin_name);1556 try zig_args.append(bin_name);
1557 try zig_args.append("--test-cmd");1557 try zig_args.append("--test-cmd");
1558 try zig_args.append("--dir=.");1558 try zig_args.append("--mapdir=/::.");
1559 try zig_args.append("--test-cmd");
1560 try zig_args.append("--mapdir=/tmp::/tmp");
1559 try zig_args.append("--test-cmd-bin");1561 try zig_args.append("--test-cmd-bin");
1560 } else {1562 } else {
1561 try zig_args.append("--test-no-exec");1563 try zig_args.append("--test-no-exec");
lib/std/fs/test.zig+5
...@@ -13,6 +13,11 @@ const File = std.fs.File;...@@ -13,6 +13,11 @@ const File = std.fs.File;
13const tmpDir = testing.tmpDir;13const tmpDir = testing.tmpDir;
14const tmpIterableDir = testing.tmpIterableDir;14const tmpIterableDir = testing.tmpIterableDir;
1515
16// ensure tests for fs/wasi.zig are run
17comptime {
18 _ = std.fs.wasi;
19}
20
16test "Dir.readLink" {21test "Dir.readLink" {
17 var tmp = tmpDir(.{});22 var tmp = tmpDir(.{});
18 defer tmp.cleanup();23 defer tmp.cleanup();
lib/std/fs/wasi.zig+108
...@@ -9,6 +9,7 @@ const Allocator = mem.Allocator;...@@ -9,6 +9,7 @@ const Allocator = mem.Allocator;
9const wasi = std.os.wasi;9const wasi = std.os.wasi;
10const fd_t = wasi.fd_t;10const fd_t = wasi.fd_t;
11const prestat_t = wasi.prestat_t;11const prestat_t = wasi.prestat_t;
12const testing = std.testing;
1213
13pub const Preopens = struct {14pub 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};
2651
27pub fn preopensAlloc(gpa: Allocator) Allocator.Error!Preopens {52pub 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
83fn 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
102test "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
148test "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}