]> git.proxmox.com Git - wasi-libc.git/blame - libc-bottom-half/sources/preopens.c
New upstream version 0.0~git20221206.8b7148f
[wasi-libc.git] / libc-bottom-half / sources / preopens.c
CommitLineData
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.
19typedef 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`.
28static preopen *preopens;
29static size_t num_preopens;
30static 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
35static volatile int lock[1];
36#endif
37
84c0778b
DG
38#ifdef NDEBUG
39#define assert_invariants() // assertions disabled
40#else
41static 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.
60static 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.
87static 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 106static 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`?
133static 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
154int __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.
159int __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.
172int __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)))
228static 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;
264oserr:
265 _Exit(EX_OSERR);
266software:
267 _Exit(EX_SOFTWARE);
268}