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.
6 #error "__wasilibc_register_preopened_fd doesn't yet support multiple threads"
18 #include <wasi/libc-find-relpath.h>
19 #include <wasi/libc.h>
21 /// A name and file descriptor pair.
22 typedef struct preopen
{
23 /// The path prefix associated with the file descriptor.
26 /// The file descriptor.
30 /// A simple growable array of `preopen`.
31 static preopen
*preopens
;
32 static size_t num_preopens
;
33 static size_t preopen_capacity
;
36 #define assert_invariants() // assertions disabled
38 static void assert_invariants(void) {
39 assert(num_preopens
<= preopen_capacity
);
40 assert(preopen_capacity
== 0 || preopens
!= NULL
);
41 assert(preopen_capacity
== 0 ||
42 preopen_capacity
* sizeof(preopen
) > preopen_capacity
);
44 for (size_t i
= 0; i
< num_preopens
; ++i
) {
45 const preopen
*pre
= &preopens
[i
];
46 assert(pre
->prefix
!= NULL
);
47 assert(pre
->fd
!= (__wasi_fd_t
)-1);
49 assert((uintptr_t)pre
->prefix
<
50 (__uint128_t
)__builtin_wasm_memory_size(0) * PAGESIZE
);
56 /// Allocate space for more preopens. Returns 0 on success and -1 on failure.
57 static int resize(void) {
58 size_t start_capacity
= 4;
59 size_t old_capacity
= preopen_capacity
;
60 size_t new_capacity
= old_capacity
== 0 ? start_capacity
: old_capacity
* 2;
62 preopen
*old_preopens
= preopens
;
63 preopen
*new_preopens
= calloc(sizeof(preopen
), new_capacity
);
64 if (new_preopens
== NULL
)
67 memcpy(new_preopens
, old_preopens
, num_preopens
* sizeof(preopen
));
68 preopens
= new_preopens
;
69 preopen_capacity
= new_capacity
;
76 // Normalize an absolute path. Removes leading `/` and leading `./`, so the
77 // first character is the start of a directory name. This works because our
78 // process always starts with a working directory of `/`. Additionally translate
79 // `.` to the empty string.
80 static const char *strip_prefixes(const char *path
) {
84 } else if (path
[0] == '.' && path
[1] == '/') {
86 } else if (path
[0] == '.' && path
[1] == 0) {
96 /// Register the given preopened file descriptor under the given path.
98 /// This function takes ownership of `prefix`.
99 static int internal_register_preopened_fd(__wasi_fd_t fd
, const char *relprefix
) {
100 // Check preconditions.
102 assert(fd
!= AT_FDCWD
);
104 assert(relprefix
!= NULL
);
106 if (num_preopens
== preopen_capacity
&& resize() != 0)
109 char *prefix
= strdup(strip_prefixes(relprefix
));
112 preopens
[num_preopens
++] = (preopen
) { prefix
, fd
, };
118 /// Are the `prefix_len` bytes pointed to by `prefix` a prefix of `path`?
119 static bool prefix_matches(const char *prefix
, size_t prefix_len
, const char *path
) {
120 // Allow an empty string as a prefix of any relative path.
121 if (path
[0] != '/' && prefix_len
== 0)
124 // Check whether any bytes of the prefix differ.
125 if (memcmp(path
, prefix
, prefix_len
) != 0)
128 // Ignore trailing slashes in directory names.
129 size_t i
= prefix_len
;
130 while (i
> 0 && prefix
[i
- 1] == '/') {
134 // Match only complete path components.
136 return last
== '/' || last
== '\0';
139 // See the documentation in libc.h
140 int __wasilibc_register_preopened_fd(int fd
, const char *prefix
) {
141 return internal_register_preopened_fd((__wasi_fd_t
)fd
, prefix
);
144 // See the documentation in libc-find-relpath.h.
145 int __wasilibc_find_relpath(const char *path
,
146 const char **abs_prefix
,
147 char **relative_path
,
148 size_t relative_path_len
) {
149 // If `chdir` is linked, whose object file defines this symbol, then we
150 // call that. Otherwise if the program can't `chdir` then `path` is
151 // absolute (or relative to the root dir), so we delegate to `find_abspath`
152 if (__wasilibc_find_relpath_alloc
)
153 return __wasilibc_find_relpath_alloc(path
, abs_prefix
, relative_path
, &relative_path_len
, 0);
154 return __wasilibc_find_abspath(path
, abs_prefix
, (const char**) relative_path
);
157 // See the documentation in libc-find-relpath.h.
158 int __wasilibc_find_abspath(const char *path
,
159 const char **abs_prefix
,
160 const char **relative_path
) {
161 // Strip leading `/` characters, the prefixes we're mataching won't have
165 // Search through the preopens table. Iterate in reverse so that more
166 // recently added preopens take precedence over less recently addded ones.
167 size_t match_len
= 0;
169 for (size_t i
= num_preopens
; i
> 0; --i
) {
170 const preopen
*pre
= &preopens
[i
- 1];
171 const char *prefix
= pre
->prefix
;
172 size_t len
= strlen(prefix
);
174 // If we haven't had a match yet, or the candidate path is longer than
175 // our current best match's path, and the candidate path is a prefix of
176 // the requested path, take that as the new best path.
177 if ((fd
== -1 || len
> match_len
) &&
178 prefix_matches(prefix
, len
, path
))
182 *abs_prefix
= prefix
;
191 // The relative path is the substring after the portion that was matched.
192 const char *computed
= path
+ match_len
;
194 // Omit leading slashes in the relative path.
195 while (*computed
== '/')
198 // *at syscalls don't accept empty relative paths, so use "." instead.
199 if (*computed
== '\0')
202 *relative_path
= computed
;
206 /// This is referenced by weak reference from crt1.c and lives in the same
207 /// source file as `__wasilibc_find_relpath` so that it's linked in when it's
209 // Concerning the 51 -- see the comment by the constructor priority in
210 // libc-bottom-half/sources/environ.c.
211 __attribute__((constructor(51)))
212 static void __wasilibc_populate_preopens(void) {
213 // Skip stdin, stdout, and stderr, and count up until we reach an invalid
215 for (__wasi_fd_t fd
= 3; fd
!= 0; ++fd
) {
216 __wasi_prestat_t prestat
;
217 __wasi_errno_t ret
= __wasi_fd_prestat_get(fd
, &prestat
);
218 if (ret
== __WASI_ERRNO_BADF
)
220 if (ret
!= __WASI_ERRNO_SUCCESS
)
222 switch (prestat
.tag
) {
223 case __WASI_PREOPENTYPE_DIR
: {
224 char *prefix
= malloc(prestat
.u
.dir
.pr_name_len
+ 1);
228 // TODO: Remove the cast on `path` once the witx is updated with
230 ret
= __wasi_fd_prestat_dir_name(fd
, (uint8_t *)prefix
,
231 prestat
.u
.dir
.pr_name_len
);
232 if (ret
!= __WASI_ERRNO_SUCCESS
)
234 prefix
[prestat
.u
.dir
.pr_name_len
] = '\0';
236 if (internal_register_preopened_fd(fd
, prefix
) != 0)