@@ -3,6 +3,8 @@ const builtin = @import("builtin");
3
3
const os = std .os ;
4
4
const mem = std .mem ;
5
5
const math = std .math ;
6
+ const fs = std .fs ;
7
+ const assert = std .debug .assert ;
6
8
const Allocator = mem .Allocator ;
7
9
const wasi = std .os .wasi ;
8
10
const fd_t = wasi .fd_t ;
@@ -34,16 +36,27 @@ pub const PreopenType = union(PreopenTypeTag) {
34
36
35
37
// Checks whether `other` refers to a subdirectory of `self` and, if so,
36
38
// returns the relative path to `other` from `self`
39
+ //
40
+ // Expects `other` to be a canonical path, not containing "." or ".."
37
41
pub fn getRelativePath (self : Self , other : PreopenType ) ? []const u8 {
38
42
if (std .meta .activeTag (self ) != std .meta .activeTag (other )) return null ;
39
43
40
44
switch (self ) {
41
- PreopenTypeTag .Dir = > | this_path | {
45
+ PreopenTypeTag .Dir = > | self_path | {
42
46
const other_path = other .Dir ;
43
- if (mem .indexOfDiff (u8 , this_path , other_path )) | index | {
44
- if (index < this_path .len ) return null ;
47
+ if (mem .indexOfDiff (u8 , self_path , other_path )) | index | {
48
+ if (index < self_path .len ) return null ;
49
+ }
50
+
51
+ const rel_path = other_path [self_path .len .. ];
52
+ if (rel_path .len == 0 ) {
53
+ return rel_path ;
54
+ } else if (rel_path [0 ] == '/' ) {
55
+ return rel_path [1.. ];
56
+ } else {
57
+ if (self_path [self_path .len - 1 ] != '/' ) return null ;
58
+ return rel_path ;
45
59
}
46
- return other_path [this_path .len .. ];
47
60
},
48
61
}
49
62
}
@@ -130,7 +143,22 @@ pub const PreopenList = struct {
130
143
/// the preopen list still contains all valid preopened file descriptors that are valid
131
144
/// for use. Therefore, it is fine to call `find`, `asSlice`, or `toOwnedSlice`. Finally,
132
145
/// `deinit` still must be called!
133
- pub fn populate (self : * Self ) Error ! void {
146
+ ///
147
+ /// Usage of `cwd_root`:
148
+ /// If provided, `cwd_root` is inserted as prefix for any Preopens that
149
+ /// begin with "." and all paths are normalized as POSIX-style absolute
150
+ /// paths. `cwd_root` must be an absolute path.
151
+ ///
152
+ /// For example:
153
+ /// "./foo/bar" -> "{cwd_root}/foo/bar"
154
+ /// "foo/bar" -> "/foo/bar"
155
+ /// "/foo/bar" -> "/foo/bar"
156
+ ///
157
+ /// If `cwd_root` is not provided, all preopen directories are unmodified.
158
+ ///
159
+ pub fn populate (self : * Self , cwd_root : ? []const u8 ) Error ! void {
160
+ if (cwd_root ) | root | assert (fs .path .isAbsolute (root ));
161
+
134
162
// Clear contents if we're being called again
135
163
for (self .toOwnedSlice ()) | preopen | {
136
164
switch (preopen .@"type" ) {
@@ -140,6 +168,7 @@ pub const PreopenList = struct {
140
168
errdefer self .deinit ();
141
169
var fd : fd_t = 3 ; // start fd has to be beyond stdio fds
142
170
171
+ var path_buf : [fs .MAX_PATH_BYTES ]u8 = undefined ;
143
172
while (true ) {
144
173
var buf : prestat_t = undefined ;
145
174
switch (wasi .fd_prestat_get (fd , & buf )) {
@@ -156,42 +185,57 @@ pub const PreopenList = struct {
156
185
else = > | err | return os .unexpectedErrno (err ),
157
186
}
158
187
const preopen_len = buf .u .dir .pr_name_len ;
159
- const path_buf = try self . buffer . allocator . alloc ( u8 , preopen_len );
160
- mem .set (u8 , path_buf , 0 );
161
- switch (wasi .fd_prestat_dir_name (fd , path_buf . ptr , preopen_len )) {
188
+
189
+ mem .set (u8 , path_buf [0 .. preopen_len ] , 0 );
190
+ switch (wasi .fd_prestat_dir_name (fd , & path_buf , preopen_len )) {
162
191
.SUCCESS = > {},
163
192
else = > | err | return os .unexpectedErrno (err ),
164
193
}
165
194
166
- const preopen = Preopen .new (fd , PreopenType { .Dir = path_buf });
195
+ // Unfortunately, WASI runtimes (e.g. wasmer) are not consistent about whether the
196
+ // NULL sentinel is included in the reported Preopen name_len
197
+ const raw_path = if (path_buf [preopen_len - 1 ] == 0 ) blk : {
198
+ break :blk path_buf [0 .. preopen_len - 1 ];
199
+ } else path_buf [0.. preopen_len ];
200
+
201
+ // If we were provided a CWD root to resolve against, we try to treat Preopen dirs as
202
+ // POSIX paths, relative to "/" or `cwd_root` depending on whether they start with "."
203
+ const path = if (cwd_root ) | cwd | blk : {
204
+ const resolve_paths : [][]const u8 = if (raw_path [0 ] == '.' ) &.{ cwd , raw_path } else &.{ "/" , raw_path };
205
+ break :blk fs .path .resolve (self .buffer .allocator , resolve_paths ) catch | err | switch (err ) {
206
+ error .CurrentWorkingDirectoryUnlinked = > unreachable , // root is absolute, so CWD not queried
207
+ else = > | e | return e ,
208
+ };
209
+ } else blk : {
210
+ // If we were provided no CWD root, we preserve the preopen dir without resolving
211
+ break :blk try self .buffer .allocator .dupe (u8 , raw_path );
212
+ };
213
+ errdefer self .buffer .allocator .free (path );
214
+ const preopen = Preopen .new (fd , .{ .Dir = path });
215
+
167
216
try self .buffer .append (preopen );
168
217
fd = try math .add (fd_t , fd , 1 );
169
218
}
170
219
}
171
220
172
221
/// Find a preopen which includes access to `preopen_type`.
173
222
///
174
- /// If the preopen exists, `relative_path` is updated to point to the relative
175
- /// portion of `preopen_type` and the matching Preopen is returned. If multiple
176
- /// preopens match the provided resource, the most recent one is used.
223
+ /// If multiple preopens match the provided resource, the most specific
224
+ /// match is returned. More recent preopens take priority, as well.
177
225
pub fn findContaining (self : Self , preopen_type : PreopenType ) ? PreopenUri {
178
- // Search in reverse, so that most recently added preopens take precedence
179
- var k : usize = self .buffer .items .len ;
180
- while (k > 0 ) {
181
- k -= 1 ;
182
-
183
- const preopen = self .buffer .items [k ];
184
- if (preopen .@"type" .getRelativePath (preopen_type )) | rel_path_orig | {
185
- var rel_path = rel_path_orig ;
186
- while (rel_path .len > 0 and rel_path [0 ] == '/' ) rel_path = rel_path [1.. ];
187
-
188
- return PreopenUri {
189
- .base = preopen ,
190
- .relative_path = if (rel_path .len == 0 ) "." else rel_path ,
191
- };
226
+ var best_match : ? PreopenUri = null ;
227
+
228
+ for (self .buffer .items ) | preopen | {
229
+ if (preopen .@"type" .getRelativePath (preopen_type )) | rel_path | {
230
+ if (best_match == null or rel_path .len <= best_match .? .relative_path .len ) {
231
+ best_match = PreopenUri {
232
+ .base = preopen ,
233
+ .relative_path = if (rel_path .len == 0 ) "." else rel_path ,
234
+ };
235
+ }
192
236
}
193
237
}
194
- return null ;
238
+ return best_match ;
195
239
}
196
240
197
241
/// Find preopen by fd. If the preopen exists, return it.
@@ -233,8 +277,20 @@ test "extracting WASI preopens" {
233
277
var preopens = PreopenList .init (std .testing .allocator );
234
278
defer preopens .deinit ();
235
279
236
- try preopens .populate ();
280
+ try preopens .populate (null );
237
281
238
282
const preopen = preopens .find (PreopenType { .Dir = "." }) orelse unreachable ;
239
283
try std .testing .expect (preopen .@"type" .eql (PreopenType { .Dir = "." }));
284
+
285
+ const po_type1 = PreopenType { .Dir = "/" };
286
+ try std .testing .expect (std .mem .eql (u8 , po_type1 .getRelativePath (.{ .Dir = "/" }).? , "" ));
287
+ try std .testing .expect (std .mem .eql (u8 , po_type1 .getRelativePath (.{ .Dir = "/test/foobar" }).? , "test/foobar" ));
288
+
289
+ const po_type2 = PreopenType { .Dir = "/test/foo" };
290
+ try std .testing .expect (po_type2 .getRelativePath (.{ .Dir = "/test/foobar" }) == null );
291
+
292
+ const po_type3 = PreopenType { .Dir = "/test" };
293
+ try std .testing .expect (std .mem .eql (u8 , po_type3 .getRelativePath (.{ .Dir = "/test" }).? , "" ));
294
+ try std .testing .expect (std .mem .eql (u8 , po_type3 .getRelativePath (.{ .Dir = "/test/" }).? , "" ));
295
+ try std .testing .expect (std .mem .eql (u8 , po_type3 .getRelativePath (.{ .Dir = "/test/foo/bar" }).? , "foo/bar" ));
240
296
}
0 commit comments