1 //! Support for "preopens", file descriptors passed into the program from the
2 //! environment, with associated path prefixes, which can be used to map
3 //! absolute paths to capabilities with relative paths.
15 #include <wasi/libc-find-relpath.h>
16 #include <wasi/libc.h>
18 /// A name and file descriptor pair.
19 typedef struct preopen
{
20 /// The path prefix associated with the file descriptor.
23 /// The file descriptor.
27 /// A simple growable array of `preopen`.
28 static preopen
*preopens
;
29 static size_t num_preopens
;
30 static size_t preopen_capacity
;
32 /// Access to the the above preopen must be protected in the presence of
35 static volatile int lock
[1];
39 #define assert_invariants() // assertions disabled
41 static void assert_invariants(void) {
42 assert(num_preopens
<= preopen_capacity
);
43 assert(preopen_capacity
== 0 || preopens
!= NULL
);
44 assert(preopen_capacity
== 0 ||
45 preopen_capacity
* sizeof(preopen
) > preopen_capacity
);
47 for (size_t i
= 0; i
< num_preopens
; ++i
) {
48 const preopen
*pre
= &preopens
[i
];
49 assert(pre
->prefix
!= NULL
);
50 assert(pre
->fd
!= (__wasi_fd_t
)-1);
52 assert((uintptr_t)pre
->prefix
<
53 (__uint128_t
)__builtin_wasm_memory_size(0) * PAGESIZE
);
59 /// Allocate space for more preopens. Returns 0 on success and -1 on failure.
60 static int resize(void) {
62 size_t start_capacity
= 4;
63 size_t old_capacity
= preopen_capacity
;
64 size_t new_capacity
= old_capacity
== 0 ? start_capacity
: old_capacity
* 2;
66 preopen
*old_preopens
= preopens
;
67 preopen
*new_preopens
= calloc(sizeof(preopen
), new_capacity
);
68 if (new_preopens
== NULL
)
72 memcpy(new_preopens
, old_preopens
, num_preopens
* sizeof(preopen
));
73 preopens
= new_preopens
;
74 preopen_capacity
= new_capacity
;
82 // Normalize an absolute path. Removes leading `/` and leading `./`, so the
83 // first character is the start of a directory name. This works because our
84 // process always starts with a working directory of `/`. Additionally translate
85 // `.` to the empty string.
86 static const char *strip_prefixes(const char *path
) {
90 } else if (path
[0] == '.' && path
[1] == '/') {
92 } else if (path
[0] == '.' && path
[1] == 0) {
102 /// Register the given preopened file descriptor under the given path.
104 /// This function takes ownership of `prefix`.
105 static int internal_register_preopened_fd(__wasi_fd_t fd
, const char *relprefix
) {
108 // Check preconditions.
110 assert(fd
!= AT_FDCWD
);
112 assert(relprefix
!= NULL
);
114 if (num_preopens
== preopen_capacity
&& resize() != 0)
118 char *prefix
= strdup(strip_prefixes(relprefix
));
122 preopens
[num_preopens
++] = (preopen
) { prefix
, fd
, };
129 /// Are the `prefix_len` bytes pointed to by `prefix` a prefix of `path`?
130 static bool prefix_matches(const char *prefix
, size_t prefix_len
, const char *path
) {
131 // Allow an empty string as a prefix of any relative path.
132 if (path
[0] != '/' && prefix_len
== 0)
135 // Check whether any bytes of the prefix differ.
136 if (memcmp(path
, prefix
, prefix_len
) != 0)
139 // Ignore trailing slashes in directory names.
140 size_t i
= prefix_len
;
141 while (i
> 0 && prefix
[i
- 1] == '/') {
145 // Match only complete path components.
147 return last
== '/' || last
== '\0';
150 // See the documentation in libc.h
151 int __wasilibc_register_preopened_fd(int fd
, const char *prefix
) {
152 return internal_register_preopened_fd((__wasi_fd_t
)fd
, prefix
);
155 // See the documentation in libc-find-relpath.h.
156 int __wasilibc_find_relpath(const char *path
,
157 const char **abs_prefix
,
158 char **relative_path
,
159 size_t relative_path_len
) {
160 // If `chdir` is linked, whose object file defines this symbol, then we
161 // call that. Otherwise if the program can't `chdir` then `path` is
162 // absolute (or relative to the root dir), so we delegate to `find_abspath`
163 if (__wasilibc_find_relpath_alloc
)
164 return __wasilibc_find_relpath_alloc(path
, abs_prefix
, relative_path
, &relative_path_len
, 0);
165 return __wasilibc_find_abspath(path
, abs_prefix
, (const char**) relative_path
);
168 // See the documentation in libc-find-relpath.h.
169 int __wasilibc_find_abspath(const char *path
,
170 const char **abs_prefix
,
171 const char **relative_path
) {
172 // Strip leading `/` characters, the prefixes we're mataching won't have
176 // Search through the preopens table. Iterate in reverse so that more
177 // recently added preopens take precedence over less recently addded ones.
178 size_t match_len
= 0;
181 for (size_t i
= num_preopens
; i
> 0; --i
) {
182 const preopen
*pre
= &preopens
[i
- 1];
183 const char *prefix
= pre
->prefix
;
184 size_t len
= strlen(prefix
);
186 // If we haven't had a match yet, or the candidate path is longer than
187 // our current best match's path, and the candidate path is a prefix of
188 // the requested path, take that as the new best path.
189 if ((fd
== -1 || len
> match_len
) &&
190 prefix_matches(prefix
, len
, path
))
194 *abs_prefix
= prefix
;
204 // The relative path is the substring after the portion that was matched.
205 const char *computed
= path
+ match_len
;
207 // Omit leading slashes in the relative path.
208 while (*computed
== '/')
211 // *at syscalls don't accept empty relative paths, so use "." instead.
212 if (*computed
== '\0')
215 *relative_path
= computed
;
219 /// This is referenced by weak reference from crt1.c and lives in the same
220 /// source file as `__wasilibc_find_relpath` so that it's linked in when it's
222 // Concerning the 51 -- see the comment by the constructor priority in
223 // libc-bottom-half/sources/environ.c.
224 __attribute__((constructor(51)))
225 static void __wasilibc_populate_preopens(void) {
226 // Skip stdin, stdout, and stderr, and count up until we reach an invalid
228 for (__wasi_fd_t fd
= 3; fd
!= 0; ++fd
) {
229 __wasi_prestat_t prestat
;
230 __wasi_errno_t ret
= __wasi_fd_prestat_get(fd
, &prestat
);
231 if (ret
== __WASI_ERRNO_BADF
)
233 if (ret
!= __WASI_ERRNO_SUCCESS
)
235 switch (prestat
.tag
) {
236 case __WASI_PREOPENTYPE_DIR
: {
237 char *prefix
= malloc(prestat
.u
.dir
.pr_name_len
+ 1);
241 // TODO: Remove the cast on `path` once the witx is updated with
243 ret
= __wasi_fd_prestat_dir_name(fd
, (uint8_t *)prefix
,
244 prestat
.u
.dir
.pr_name_len
);
245 if (ret
!= __WASI_ERRNO_SUCCESS
)
247 prefix
[prestat
.u
.dir
.pr_name_len
] = '\0';
249 if (internal_register_preopened_fd(fd
, prefix
) != 0)