]> git.proxmox.com Git - wasi-libc.git/blob - libc-bottom-half/sources/preopens.c
Add braces to `if` statements whose bodies have multiple statements.
[wasi-libc.git] / libc-bottom-half / sources / preopens.c
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
5 #include <assert.h>
6 #include <errno.h>
7 #include <fcntl.h>
8 #include <limits.h>
9 #include <lock.h>
10 #include <stdbool.h>
11 #include <stdlib.h>
12 #include <string.h>
13 #include <sysexits.h>
14 #include <wasi/api.h>
15 #include <wasi/libc-find-relpath.h>
16 #include <wasi/libc.h>
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
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
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) {
61 LOCK(lock);
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);
68 if (new_preopens == NULL) {
69 UNLOCK(lock);
70 return -1;
71 }
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();
79 UNLOCK(lock);
80 return 0;
81 }
82
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
103 /// Register the given preopened file descriptor under the given path.
104 ///
105 /// This function takes ownership of `prefix`.
106 static int internal_register_preopened_fd(__wasi_fd_t fd, const char *relprefix) {
107 LOCK(lock);
108
109 // Check preconditions.
110 assert_invariants();
111 assert(fd != AT_FDCWD);
112 assert(fd != -1);
113 assert(relprefix != NULL);
114
115 if (num_preopens == preopen_capacity && resize() != 0) {
116 UNLOCK(lock);
117 return -1;
118 }
119
120 char *prefix = strdup(strip_prefixes(relprefix));
121 if (prefix == NULL) {
122 UNLOCK(lock);
123 return -1;
124 }
125 preopens[num_preopens++] = (preopen) { prefix, fd, };
126
127 assert_invariants();
128 UNLOCK(lock);
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) {
155 return internal_register_preopened_fd((__wasi_fd_t)fd, prefix);
156 }
157
158 // See the documentation in libc-find-relpath.h.
159 int __wasilibc_find_relpath(const char *path,
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 }
170
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++;
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;
183 LOCK(lock);
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
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;
197 *abs_prefix = prefix;
198 }
199 }
200 UNLOCK(lock);
201
202 if (fd == -1) {
203 errno = ENOENT;
204 return -1;
205 }
206
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;
254 free(prefix);
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 }