]> git.proxmox.com Git - wasi-libc.git/blob - libc-bottom-half/sources/preopens.c
Fix `make THREAD_MODEL=posix` (#311)
[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 memcpy(new_preopens, old_preopens, num_preopens * sizeof(preopen));
73 preopens = new_preopens;
74 preopen_capacity = new_capacity;
75 free(old_preopens);
76
77 assert_invariants();
78 UNLOCK(lock);
79 return 0;
80 }
81
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) {
87 while (1) {
88 if (path[0] == '/') {
89 path++;
90 } else if (path[0] == '.' && path[1] == '/') {
91 path += 2;
92 } else if (path[0] == '.' && path[1] == 0) {
93 path++;
94 } else {
95 break;
96 }
97 }
98
99 return path;
100 }
101
102 /// Register the given preopened file descriptor under the given path.
103 ///
104 /// This function takes ownership of `prefix`.
105 static int internal_register_preopened_fd(__wasi_fd_t fd, const char *relprefix) {
106 LOCK(lock);
107
108 // Check preconditions.
109 assert_invariants();
110 assert(fd != AT_FDCWD);
111 assert(fd != -1);
112 assert(relprefix != NULL);
113
114 if (num_preopens == preopen_capacity && resize() != 0)
115 UNLOCK(lock);
116 return -1;
117
118 char *prefix = strdup(strip_prefixes(relprefix));
119 if (prefix == NULL)
120 UNLOCK(lock);
121 return -1;
122 preopens[num_preopens++] = (preopen) { prefix, fd, };
123
124 assert_invariants();
125 UNLOCK(lock);
126 return 0;
127 }
128
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)
133 return true;
134
135 // Check whether any bytes of the prefix differ.
136 if (memcmp(path, prefix, prefix_len) != 0)
137 return false;
138
139 // Ignore trailing slashes in directory names.
140 size_t i = prefix_len;
141 while (i > 0 && prefix[i - 1] == '/') {
142 --i;
143 }
144
145 // Match only complete path components.
146 char last = path[i];
147 return last == '/' || last == '\0';
148 }
149
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);
153 }
154
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);
166 }
167
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
173 // them.
174 while (*path == '/')
175 path++;
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;
179 int fd = -1;
180 LOCK(lock);
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);
185
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))
191 {
192 fd = pre->fd;
193 match_len = len;
194 *abs_prefix = prefix;
195 }
196 }
197 UNLOCK(lock);
198
199 if (fd == -1) {
200 errno = ENOENT;
201 return -1;
202 }
203
204 // The relative path is the substring after the portion that was matched.
205 const char *computed = path + match_len;
206
207 // Omit leading slashes in the relative path.
208 while (*computed == '/')
209 ++computed;
210
211 // *at syscalls don't accept empty relative paths, so use "." instead.
212 if (*computed == '\0')
213 computed = ".";
214
215 *relative_path = computed;
216 return fd;
217 }
218
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
221 /// needed.
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
227 // file descriptor.
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)
232 break;
233 if (ret != __WASI_ERRNO_SUCCESS)
234 goto oserr;
235 switch (prestat.tag) {
236 case __WASI_PREOPENTYPE_DIR: {
237 char *prefix = malloc(prestat.u.dir.pr_name_len + 1);
238 if (prefix == NULL)
239 goto software;
240
241 // TODO: Remove the cast on `path` once the witx is updated with
242 // char8 support.
243 ret = __wasi_fd_prestat_dir_name(fd, (uint8_t *)prefix,
244 prestat.u.dir.pr_name_len);
245 if (ret != __WASI_ERRNO_SUCCESS)
246 goto oserr;
247 prefix[prestat.u.dir.pr_name_len] = '\0';
248
249 if (internal_register_preopened_fd(fd, prefix) != 0)
250 goto software;
251 free(prefix);
252
253 break;
254 }
255 default:
256 break;
257 }
258 }
259
260 return;
261 oserr:
262 _Exit(EX_OSERR);
263 software:
264 _Exit(EX_SOFTWARE);
265 }