]>
Commit | Line | Data |
---|---|---|
84c0778b DG |
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. | |
4 | ||
84c0778b | 5 | #include <assert.h> |
5b148b61 | 6 | #include <errno.h> |
f2e779e5 | 7 | #include <fcntl.h> |
84c0778b | 8 | #include <limits.h> |
dcd28cf8 | 9 | #include <lock.h> |
84c0778b DG |
10 | #include <stdbool.h> |
11 | #include <stdlib.h> | |
12 | #include <string.h> | |
13 | #include <sysexits.h> | |
14 | #include <wasi/api.h> | |
84c0778b | 15 | #include <wasi/libc-find-relpath.h> |
5b148b61 | 16 | #include <wasi/libc.h> |
84c0778b DG |
17 | |
18 | /// A name and file descriptor pair. | |
19 | typedef struct preopen { | |
20 | /// The path prefix associated with the file descriptor. | |
21 | const char *prefix; | |
22 | ||
23 | /// The file descriptor. | |
24 | __wasi_fd_t fd; | |
25 | } preopen; | |
26 | ||
27 | /// A simple growable array of `preopen`. | |
28 | static preopen *preopens; | |
29 | static size_t num_preopens; | |
30 | static size_t preopen_capacity; | |
31 | ||
dcd28cf8 AB |
32 | /// Access to the the above preopen must be protected in the presence of |
33 | /// threads. | |
34 | #ifdef _REENTRANT | |
35 | static volatile int lock[1]; | |
36 | #endif | |
37 | ||
84c0778b DG |
38 | #ifdef NDEBUG |
39 | #define assert_invariants() // assertions disabled | |
40 | #else | |
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); | |
46 | ||
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); | |
51 | #ifdef __wasm__ | |
52 | assert((uintptr_t)pre->prefix < | |
53 | (__uint128_t)__builtin_wasm_memory_size(0) * PAGESIZE); | |
54 | #endif | |
55 | } | |
56 | } | |
57 | #endif | |
58 | ||
59 | /// Allocate space for more preopens. Returns 0 on success and -1 on failure. | |
60 | static int resize(void) { | |
dcd28cf8 | 61 | LOCK(lock); |
84c0778b DG |
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; | |
65 | ||
66 | preopen *old_preopens = preopens; | |
67 | preopen *new_preopens = calloc(sizeof(preopen), new_capacity); | |
a0229804 | 68 | if (new_preopens == NULL) { |
dcd28cf8 | 69 | UNLOCK(lock); |
84c0778b | 70 | return -1; |
a0229804 | 71 | } |
84c0778b DG |
72 | |
73 | memcpy(new_preopens, old_preopens, num_preopens * sizeof(preopen)); | |
74 | preopens = new_preopens; | |
75 | preopen_capacity = new_capacity; | |
76 | free(old_preopens); | |
77 | ||
78 | assert_invariants(); | |
dcd28cf8 | 79 | UNLOCK(lock); |
84c0778b DG |
80 | return 0; |
81 | } | |
82 | ||
5b148b61 AC |
83 | // Normalize an absolute path. Removes leading `/` and leading `./`, so the |
84 | // first character is the start of a directory name. This works because our | |
85 | // process always starts with a working directory of `/`. Additionally translate | |
86 | // `.` to the empty string. | |
87 | static const char *strip_prefixes(const char *path) { | |
88 | while (1) { | |
89 | if (path[0] == '/') { | |
90 | path++; | |
91 | } else if (path[0] == '.' && path[1] == '/') { | |
92 | path += 2; | |
93 | } else if (path[0] == '.' && path[1] == 0) { | |
94 | path++; | |
95 | } else { | |
96 | break; | |
97 | } | |
98 | } | |
99 | ||
100 | return path; | |
101 | } | |
102 | ||
84c0778b DG |
103 | /// Register the given preopened file descriptor under the given path. |
104 | /// | |
105 | /// This function takes ownership of `prefix`. | |
5b148b61 | 106 | static int internal_register_preopened_fd(__wasi_fd_t fd, const char *relprefix) { |
dcd28cf8 AB |
107 | LOCK(lock); |
108 | ||
f2e779e5 | 109 | // Check preconditions. |
84c0778b | 110 | assert_invariants(); |
f2e779e5 DG |
111 | assert(fd != AT_FDCWD); |
112 | assert(fd != -1); | |
113 | assert(relprefix != NULL); | |
84c0778b | 114 | |
a0229804 | 115 | if (num_preopens == preopen_capacity && resize() != 0) { |
dcd28cf8 | 116 | UNLOCK(lock); |
84c0778b | 117 | return -1; |
a0229804 | 118 | } |
84c0778b | 119 | |
5b148b61 | 120 | char *prefix = strdup(strip_prefixes(relprefix)); |
a0229804 | 121 | if (prefix == NULL) { |
dcd28cf8 | 122 | UNLOCK(lock); |
5b148b61 | 123 | return -1; |
a0229804 | 124 | } |
84c0778b DG |
125 | preopens[num_preopens++] = (preopen) { prefix, fd, }; |
126 | ||
127 | assert_invariants(); | |
dcd28cf8 | 128 | UNLOCK(lock); |
84c0778b DG |
129 | return 0; |
130 | } | |
131 | ||
132 | /// Are the `prefix_len` bytes pointed to by `prefix` a prefix of `path`? | |
133 | static bool prefix_matches(const char *prefix, size_t prefix_len, const char *path) { | |
134 | // Allow an empty string as a prefix of any relative path. | |
135 | if (path[0] != '/' && prefix_len == 0) | |
136 | return true; | |
137 | ||
138 | // Check whether any bytes of the prefix differ. | |
139 | if (memcmp(path, prefix, prefix_len) != 0) | |
140 | return false; | |
141 | ||
142 | // Ignore trailing slashes in directory names. | |
143 | size_t i = prefix_len; | |
144 | while (i > 0 && prefix[i - 1] == '/') { | |
145 | --i; | |
146 | } | |
147 | ||
148 | // Match only complete path components. | |
149 | char last = path[i]; | |
150 | return last == '/' || last == '\0'; | |
151 | } | |
152 | ||
153 | // See the documentation in libc.h | |
154 | int __wasilibc_register_preopened_fd(int fd, const char *prefix) { | |
5b148b61 | 155 | return internal_register_preopened_fd((__wasi_fd_t)fd, prefix); |
84c0778b DG |
156 | } |
157 | ||
158 | // See the documentation in libc-find-relpath.h. | |
159 | int __wasilibc_find_relpath(const char *path, | |
5b148b61 AC |
160 | const char **abs_prefix, |
161 | char **relative_path, | |
162 | size_t relative_path_len) { | |
163 | // If `chdir` is linked, whose object file defines this symbol, then we | |
164 | // call that. Otherwise if the program can't `chdir` then `path` is | |
165 | // absolute (or relative to the root dir), so we delegate to `find_abspath` | |
166 | if (__wasilibc_find_relpath_alloc) | |
167 | return __wasilibc_find_relpath_alloc(path, abs_prefix, relative_path, &relative_path_len, 0); | |
168 | return __wasilibc_find_abspath(path, abs_prefix, (const char**) relative_path); | |
169 | } | |
84c0778b | 170 | |
5b148b61 AC |
171 | // See the documentation in libc-find-relpath.h. |
172 | int __wasilibc_find_abspath(const char *path, | |
173 | const char **abs_prefix, | |
174 | const char **relative_path) { | |
175 | // Strip leading `/` characters, the prefixes we're mataching won't have | |
176 | // them. | |
177 | while (*path == '/') | |
178 | path++; | |
84c0778b DG |
179 | // Search through the preopens table. Iterate in reverse so that more |
180 | // recently added preopens take precedence over less recently addded ones. | |
181 | size_t match_len = 0; | |
182 | int fd = -1; | |
dcd28cf8 | 183 | LOCK(lock); |
84c0778b DG |
184 | for (size_t i = num_preopens; i > 0; --i) { |
185 | const preopen *pre = &preopens[i - 1]; | |
186 | const char *prefix = pre->prefix; | |
187 | size_t len = strlen(prefix); | |
188 | ||
84c0778b DG |
189 | // If we haven't had a match yet, or the candidate path is longer than |
190 | // our current best match's path, and the candidate path is a prefix of | |
191 | // the requested path, take that as the new best path. | |
192 | if ((fd == -1 || len > match_len) && | |
193 | prefix_matches(prefix, len, path)) | |
194 | { | |
195 | fd = pre->fd; | |
196 | match_len = len; | |
5b148b61 | 197 | *abs_prefix = prefix; |
84c0778b DG |
198 | } |
199 | } | |
dcd28cf8 | 200 | UNLOCK(lock); |
84c0778b | 201 | |
5b148b61 AC |
202 | if (fd == -1) { |
203 | errno = ENOENT; | |
204 | return -1; | |
205 | } | |
206 | ||
84c0778b DG |
207 | // The relative path is the substring after the portion that was matched. |
208 | const char *computed = path + match_len; | |
209 | ||
210 | // Omit leading slashes in the relative path. | |
211 | while (*computed == '/') | |
212 | ++computed; | |
213 | ||
214 | // *at syscalls don't accept empty relative paths, so use "." instead. | |
215 | if (*computed == '\0') | |
216 | computed = "."; | |
217 | ||
218 | *relative_path = computed; | |
219 | return fd; | |
220 | } | |
221 | ||
222 | /// This is referenced by weak reference from crt1.c and lives in the same | |
223 | /// source file as `__wasilibc_find_relpath` so that it's linked in when it's | |
224 | /// needed. | |
225 | // Concerning the 51 -- see the comment by the constructor priority in | |
226 | // libc-bottom-half/sources/environ.c. | |
227 | __attribute__((constructor(51))) | |
228 | static void __wasilibc_populate_preopens(void) { | |
229 | // Skip stdin, stdout, and stderr, and count up until we reach an invalid | |
230 | // file descriptor. | |
231 | for (__wasi_fd_t fd = 3; fd != 0; ++fd) { | |
232 | __wasi_prestat_t prestat; | |
233 | __wasi_errno_t ret = __wasi_fd_prestat_get(fd, &prestat); | |
234 | if (ret == __WASI_ERRNO_BADF) | |
235 | break; | |
236 | if (ret != __WASI_ERRNO_SUCCESS) | |
237 | goto oserr; | |
238 | switch (prestat.tag) { | |
239 | case __WASI_PREOPENTYPE_DIR: { | |
240 | char *prefix = malloc(prestat.u.dir.pr_name_len + 1); | |
241 | if (prefix == NULL) | |
242 | goto software; | |
243 | ||
244 | // TODO: Remove the cast on `path` once the witx is updated with | |
245 | // char8 support. | |
246 | ret = __wasi_fd_prestat_dir_name(fd, (uint8_t *)prefix, | |
247 | prestat.u.dir.pr_name_len); | |
248 | if (ret != __WASI_ERRNO_SUCCESS) | |
249 | goto oserr; | |
250 | prefix[prestat.u.dir.pr_name_len] = '\0'; | |
251 | ||
252 | if (internal_register_preopened_fd(fd, prefix) != 0) | |
253 | goto software; | |
5b148b61 | 254 | free(prefix); |
84c0778b DG |
255 | |
256 | break; | |
257 | } | |
258 | default: | |
259 | break; | |
260 | } | |
261 | } | |
262 | ||
263 | return; | |
264 | oserr: | |
265 | _Exit(EX_OSERR); | |
266 | software: | |
267 | _Exit(EX_SOFTWARE); | |
268 | } |