]> git.proxmox.com Git - wasi-libc.git/blob - libc-bottom-half/sources/preopens.c
b6fb9f7a7cf97696cfbf426e3ec503d83c8d11d1
[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 #ifdef _REENTRANT
6 #error "__wasilibc_register_preopened_fd doesn't yet support multiple threads"
7 #endif
8
9 #include <assert.h>
10 #include <errno.h>
11 #include <fcntl.h>
12 #include <limits.h>
13 #include <stdbool.h>
14 #include <stdlib.h>
15 #include <string.h>
16 #include <sysexits.h>
17 #include <wasi/api.h>
18 #include <wasi/libc-find-relpath.h>
19 #include <wasi/libc.h>
20
21 /// A name and file descriptor pair.
22 typedef struct preopen {
23 /// The path prefix associated with the file descriptor.
24 const char *prefix;
25
26 /// The file descriptor.
27 __wasi_fd_t fd;
28 } preopen;
29
30 /// A simple growable array of `preopen`.
31 static preopen *preopens;
32 static size_t num_preopens;
33 static size_t preopen_capacity;
34
35 #ifdef NDEBUG
36 #define assert_invariants() // assertions disabled
37 #else
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);
43
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);
48 #ifdef __wasm__
49 assert((uintptr_t)pre->prefix <
50 (__uint128_t)__builtin_wasm_memory_size(0) * PAGESIZE);
51 #endif
52 }
53 }
54 #endif
55
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;
61
62 preopen *old_preopens = preopens;
63 preopen *new_preopens = calloc(sizeof(preopen), new_capacity);
64 if (new_preopens == NULL)
65 return -1;
66
67 memcpy(new_preopens, old_preopens, num_preopens * sizeof(preopen));
68 preopens = new_preopens;
69 preopen_capacity = new_capacity;
70 free(old_preopens);
71
72 assert_invariants();
73 return 0;
74 }
75
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) {
81 while (1) {
82 if (path[0] == '/') {
83 path++;
84 } else if (path[0] == '.' && path[1] == '/') {
85 path += 2;
86 } else if (path[0] == '.' && path[1] == 0) {
87 path++;
88 } else {
89 break;
90 }
91 }
92
93 return path;
94 }
95
96 /// Register the given preopened file descriptor under the given path.
97 ///
98 /// This function takes ownership of `prefix`.
99 static int internal_register_preopened_fd(__wasi_fd_t fd, const char *relprefix) {
100 // Check preconditions.
101 assert_invariants();
102 assert(fd != AT_FDCWD);
103 assert(fd != -1);
104 assert(relprefix != NULL);
105
106 if (num_preopens == preopen_capacity && resize() != 0)
107 return -1;
108
109 char *prefix = strdup(strip_prefixes(relprefix));
110 if (prefix == NULL)
111 return -1;
112 preopens[num_preopens++] = (preopen) { prefix, fd, };
113
114 assert_invariants();
115 return 0;
116 }
117
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)
122 return true;
123
124 // Check whether any bytes of the prefix differ.
125 if (memcmp(path, prefix, prefix_len) != 0)
126 return false;
127
128 // Ignore trailing slashes in directory names.
129 size_t i = prefix_len;
130 while (i > 0 && prefix[i - 1] == '/') {
131 --i;
132 }
133
134 // Match only complete path components.
135 char last = path[i];
136 return last == '/' || last == '\0';
137 }
138
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);
142 }
143
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);
155 }
156
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
162 // them.
163 while (*path == '/')
164 path++;
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;
168 int fd = -1;
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);
173
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))
179 {
180 fd = pre->fd;
181 match_len = len;
182 *abs_prefix = prefix;
183 }
184 }
185
186 if (fd == -1) {
187 errno = ENOENT;
188 return -1;
189 }
190
191 // The relative path is the substring after the portion that was matched.
192 const char *computed = path + match_len;
193
194 // Omit leading slashes in the relative path.
195 while (*computed == '/')
196 ++computed;
197
198 // *at syscalls don't accept empty relative paths, so use "." instead.
199 if (*computed == '\0')
200 computed = ".";
201
202 *relative_path = computed;
203 return fd;
204 }
205
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
208 /// needed.
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
214 // file descriptor.
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)
219 break;
220 if (ret != __WASI_ERRNO_SUCCESS)
221 goto oserr;
222 switch (prestat.tag) {
223 case __WASI_PREOPENTYPE_DIR: {
224 char *prefix = malloc(prestat.u.dir.pr_name_len + 1);
225 if (prefix == NULL)
226 goto software;
227
228 // TODO: Remove the cast on `path` once the witx is updated with
229 // char8 support.
230 ret = __wasi_fd_prestat_dir_name(fd, (uint8_t *)prefix,
231 prestat.u.dir.pr_name_len);
232 if (ret != __WASI_ERRNO_SUCCESS)
233 goto oserr;
234 prefix[prestat.u.dir.pr_name_len] = '\0';
235
236 if (internal_register_preopened_fd(fd, prefix) != 0)
237 goto software;
238 free(prefix);
239
240 break;
241 }
242 default:
243 break;
244 }
245 }
246
247 return;
248 oserr:
249 _Exit(EX_OSERR);
250 software:
251 _Exit(EX_SOFTWARE);
252 }