]> git.proxmox.com Git - wasi-libc.git/blob - libc-bottom-half/libpreopen/libpreopen.c
Wasi snapshot preview1 (#140)
[wasi-libc.git] / libc-bottom-half / libpreopen / libpreopen.c
1 //! Preopen functionality for WASI libc, for emulating support for absolute
2 //! path names on top of WASI's OCap-style API.
3 //!
4 //! This file is derived from code in libpreopen, the upstream for which is:
5 //!
6 //! https://github.com/musec/libpreopen
7 //!
8 //! and which bears the following copyrights and license:
9
10 /*-
11 * Copyright (c) 2016-2017 Stanley Uche Godfrey
12 * Copyright (c) 2016-2018 Jonathan Anderson
13 * All rights reserved.
14 *
15 * This software was developed at Memorial University under the
16 * NSERC Discovery program (RGPIN-2015-06048).
17 *
18 * Redistribution and use in source and binary forms, with or without
19 * modification, are permitted provided that the following conditions
20 * are met:
21 *
22 * 1. Redistributions of source code must retain the above copyright
23 * notice, this list of conditions and the following disclaimer.
24 * 2. Redistributions in binary form must reproduce the above copyright
25 * notice, this list of conditions and the following disclaimer in the
26 * documentation and/or other materials provided with the distribution.
27 *
28 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
29 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
30 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
31 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
32 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
33 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
34 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
35 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
36 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
37 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
38 * SUCH DAMAGE.
39 */
40
41 #ifdef _REENTRANT
42 #error "__wasilibc_register_preopened_fd doesn't yet support multiple threads"
43 #endif
44
45 #define _ALL_SOURCE
46 #include <sys/stat.h>
47 #include <fcntl.h>
48 #include <stdarg.h>
49 #include <stdlib.h>
50 #include <stdio.h>
51 #include <stdbool.h>
52 #include <string.h>
53 #include <unistd.h>
54 #include <errno.h>
55 #include <dirent.h>
56 #include <assert.h>
57 #include <wasi/libc.h>
58 #include <wasi/libc-find-relpath.h>
59
60 ////////////////////////////////////////////////////////////////////////////////
61 //
62 // POSIX API compatibility wrappers
63 //
64 ////////////////////////////////////////////////////////////////////////////////
65
66 int
67 open(const char *path, int flags, ...)
68 {
69 // WASI libc's openat ignores the mode argument, so call a special
70 // entrypoint which avoids the varargs calling convention.
71 return __wasilibc_open_nomode(path, flags);
72 }
73
74 int
75 __wasilibc_open_nomode(const char *path, int flags)
76 {
77 const char *relative_path;
78 int dirfd = __wasilibc_find_relpath(path, &relative_path);
79
80 // If we can't find a preopened directory handle to open this file with,
81 // indicate that the program lacks the capabilities.
82 if (dirfd == -1) {
83 errno = ENOTCAPABLE;
84 return -1;
85 }
86
87 return __wasilibc_openat_nomode(dirfd, relative_path, flags);
88 }
89
90 int
91 access(const char *path, int mode)
92 {
93 const char *relative_path;
94 int dirfd = __wasilibc_find_relpath(path, &relative_path);
95
96 // If we can't find a preopened directory handle to open this file with,
97 // indicate that the program lacks the capabilities.
98 if (dirfd == -1) {
99 errno = ENOTCAPABLE;
100 return -1;
101 }
102
103 return faccessat(dirfd, relative_path, mode, 0);
104 }
105
106 int
107 lstat(const char *path, struct stat *st)
108 {
109 const char *relative_path;
110 int dirfd = __wasilibc_find_relpath(path, &relative_path);
111
112 // If we can't find a preopened directory handle to open this file with,
113 // indicate that the program lacks the capabilities.
114 if (dirfd == -1) {
115 errno = ENOTCAPABLE;
116 return -1;
117 }
118
119 return fstatat(dirfd, relative_path, st, AT_SYMLINK_NOFOLLOW);
120 }
121
122 int
123 rename(const char *from, const char *to)
124 {
125 const char *from_relative_path;
126 int from_dirfd = __wasilibc_find_relpath(from, &from_relative_path);
127
128 const char *to_relative_path;
129 int to_dirfd = __wasilibc_find_relpath(to, &to_relative_path);
130
131 // If we can't find a preopened directory handle to open this file with,
132 // indicate that the program lacks the capabilities.
133 if (from_dirfd == -1 || to_dirfd == -1) {
134 errno = ENOTCAPABLE;
135 return -1;
136 }
137
138 return renameat(from_dirfd, from_relative_path, to_dirfd, to_relative_path);
139 }
140
141 int
142 stat(const char *path, struct stat *st)
143 {
144 const char *relative_path;
145 int dirfd = __wasilibc_find_relpath(path, &relative_path);
146
147 // If we can't find a preopened directory handle to open this file with,
148 // indicate that the program lacks the capabilities.
149 if (dirfd == -1) {
150 errno = ENOTCAPABLE;
151 return -1;
152 }
153
154 return fstatat(dirfd, relative_path, st, AT_SYMLINK_NOFOLLOW);
155 }
156
157 int
158 unlink(const char *path)
159 {
160 const char *relative_path;
161 int dirfd = __wasilibc_find_relpath(path, &relative_path);
162
163 // If we can't find a preopened directory handle to open this file with,
164 // indicate that the program lacks the capabilities.
165 if (dirfd == -1) {
166 errno = ENOTCAPABLE;
167 return -1;
168 }
169
170 // `unlinkat` ends up importing `__wasi_path_remove_directory` even
171 // though we're not passing `AT_REMOVEDIR` here. So instead, use a
172 // specialized function which just imports `__wasi_path_unlink_file`.
173 return __wasilibc_unlinkat(dirfd, relative_path);
174 }
175
176 int
177 rmdir(const char *pathname)
178 {
179 const char *relative_path;
180 int dirfd = __wasilibc_find_relpath(pathname, &relative_path);
181
182 // If we can't find a preopened directory handle to open this file with,
183 // indicate that the program lacks the capabilities.
184 if (dirfd == -1) {
185 errno = ENOTCAPABLE;
186 return -1;
187 }
188
189 return __wasilibc_rmdirat(dirfd, relative_path);
190 }
191
192 int
193 remove(const char *pathname)
194 {
195 const char *relative_path;
196 int dirfd = __wasilibc_find_relpath(pathname, &relative_path);
197
198 // If searching for both file and directory rights failed, try searching
199 // for either individually.
200 if (dirfd == -1) {
201 dirfd = __wasilibc_find_relpath(pathname, &relative_path);
202 if (dirfd == -1) {
203 dirfd = __wasilibc_find_relpath(pathname, &relative_path);
204 }
205 }
206
207 // If we can't find a preopened directory handle to open this file with,
208 // indicate that the program lacks the capabilities.
209 if (dirfd == -1) {
210 errno = ENOTCAPABLE;
211 return -1;
212 }
213
214 int r = __wasilibc_unlinkat(dirfd, relative_path);
215 if (r != 0 && (errno == EISDIR || errno == ENOTCAPABLE))
216 r = __wasilibc_rmdirat(dirfd, relative_path);
217 return r;
218 }
219
220 int
221 link(const char *oldpath, const char *newpath)
222 {
223 const char *old_relative_path;
224 int old_dirfd = __wasilibc_find_relpath(oldpath, &old_relative_path);
225
226 const char *new_relative_path;
227 int new_dirfd = __wasilibc_find_relpath(newpath, &new_relative_path);
228
229 // If we can't find a preopened directory handle to open this file with,
230 // indicate that the program lacks the capabilities.
231 if (old_dirfd == -1 || new_dirfd == -1) {
232 errno = ENOTCAPABLE;
233 return -1;
234 }
235
236 return linkat(old_dirfd, old_relative_path, new_dirfd, new_relative_path, 0);
237 }
238
239 int
240 mkdir(const char *pathname, mode_t mode)
241 {
242 const char *relative_path;
243 int dirfd = __wasilibc_find_relpath(pathname, &relative_path);
244
245 // If we can't find a preopened directory handle to open this file with,
246 // indicate that the program lacks the capabilities.
247 if (dirfd == -1) {
248 errno = ENOTCAPABLE;
249 return -1;
250 }
251
252 return mkdirat(dirfd, relative_path, mode);
253 }
254
255 DIR *
256 opendir(const char *name)
257 {
258 const char *relative_path;
259 int dirfd = __wasilibc_find_relpath(name, &relative_path);
260
261 // If we can't find a preopened directory handle to open this file with,
262 // indicate that the program lacks the capabilities.
263 if (dirfd == -1) {
264 errno = ENOTCAPABLE;
265 return NULL;
266 }
267
268 return opendirat(dirfd, relative_path);
269 }
270
271 ssize_t
272 readlink(const char *pathname, char *buf, size_t bufsiz)
273 {
274 const char *relative_path;
275 int dirfd = __wasilibc_find_relpath(pathname, &relative_path);
276
277 // If we can't find a preopened directory handle to open this file with,
278 // indicate that the program lacks the capabilities.
279 if (dirfd == -1) {
280 errno = ENOTCAPABLE;
281 return -1;
282 }
283
284 return readlinkat(dirfd, relative_path, buf, bufsiz);
285 }
286
287 int
288 scandir(
289 const char *dirp,
290 struct dirent ***namelist,
291 int (*filter)(const struct dirent *),
292 int (*compar)(const struct dirent **, const struct dirent **))
293 {
294 const char *relative_path;
295 int dirfd = __wasilibc_find_relpath(dirp, &relative_path);
296
297 // If we can't find a preopened directory handle to open this file with,
298 // indicate that the program lacks the capabilities.
299 if (dirfd == -1) {
300 errno = ENOTCAPABLE;
301 return -1;
302 }
303
304 return scandirat(dirfd, relative_path, namelist, filter, compar);
305 }
306
307 int
308 symlink(const char *target, const char *linkpath)
309 {
310 const char *relative_path;
311 int dirfd = __wasilibc_find_relpath(linkpath, &relative_path);
312
313 // If we can't find a preopened directory handle to open this file with,
314 // indicate that the program lacks the capabilities.
315 if (dirfd == -1) {
316 errno = ENOTCAPABLE;
317 return -1;
318 }
319
320 return symlinkat(target, dirfd, relative_path);
321 }
322
323 ////////////////////////////////////////////////////////////////////////////////
324 //
325 // Support library
326 //
327 ////////////////////////////////////////////////////////////////////////////////
328
329 /// An entry in a po_map.
330 struct po_map_entry {
331 /// The name this file or directory is mapped to.
332 ///
333 /// This name should look like a path, but it does not necessarily need
334 /// match to match the path it was originally obtained from.
335 const char *name;
336
337 /// File descriptor (which may be a directory)
338 int fd;
339 };
340
341 /// A vector of po_map_entry.
342 struct po_map {
343 struct po_map_entry *entries;
344 size_t capacity;
345 size_t length;
346 };
347
348 static struct po_map global_map;
349
350 /// Is a directory a prefix of a given path?
351 ///
352 /// @param dir a directory path, e.g., `/foo/bar`
353 /// @param dirlen the length of @b dir
354 /// @param path a path that may have @b dir as a prefix,
355 /// e.g., `/foo/bar/baz`
356 static bool
357 po_isprefix(const char *dir, size_t dirlen, const char *path)
358 {
359 assert(dir != NULL);
360 assert(path != NULL);
361
362 // Allow an empty string as a prefix of any relative path.
363 if (path[0] != '/' && dirlen == 0)
364 return true;
365
366 size_t i;
367 for (i = 0; i < dirlen; i++)
368 {
369 if (path[i] != dir[i])
370 return false;
371 }
372
373 // Ignore trailing slashes in directory names.
374 while (i > 0 && dir[i - 1] == '/') {
375 --i;
376 }
377
378 return path[i] == '/' || path[i] == '\0';
379 }
380
381 /// Enlarge a @ref po_map's capacity.
382 ///
383 /// This results in new memory being allocated and existing entries being copied.
384 /// If the allocation fails, the function will return -1.
385 static int
386 po_map_enlarge(void)
387 {
388 size_t start_capacity = 4;
389 size_t new_capacity = global_map.capacity == 0 ?
390 start_capacity :
391 global_map.capacity * 2;
392
393 struct po_map_entry *enlarged =
394 calloc(sizeof(struct po_map_entry), new_capacity);
395 if (enlarged == NULL) {
396 return -1;
397 }
398 memcpy(enlarged, global_map.entries, global_map.length * sizeof(*enlarged));
399 free(global_map.entries);
400 global_map.entries = enlarged;
401 global_map.capacity = new_capacity;
402 return 0;
403 }
404
405 /// Assert that the global_map is valid.
406 #ifdef NDEBUG
407 #define po_map_assertvalid()
408 #else
409 static void
410 po_map_assertvalid(void)
411 {
412 const struct po_map_entry *entry;
413 size_t i;
414
415 assert(global_map.length <= global_map.capacity);
416 assert(global_map.entries != NULL || global_map.capacity == 0);
417
418 for (i = 0; i < global_map.length; i++) {
419 entry = &global_map.entries[i];
420
421 assert(entry->name != NULL);
422 assert(entry->fd >= 0);
423 }
424 }
425 #endif
426
427 /// Register the given pre-opened file descriptor under the given path.
428 ///
429 /// This function takes ownership of `name`.
430 static int
431 internal_register_preopened_fd(int fd, const char *name)
432 {
433 po_map_assertvalid();
434
435 assert(fd >= 0);
436 assert(name != NULL);
437
438 if (global_map.length == global_map.capacity) {
439 int n = po_map_enlarge();
440
441 po_map_assertvalid();
442
443 if (n != 0) {
444 return n;
445 }
446 }
447
448 struct po_map_entry *entry = &global_map.entries[global_map.length++];
449
450 entry->name = name;
451 entry->fd = fd;
452
453 po_map_assertvalid();
454
455 return 0;
456 }
457
458 /// Register the given pre-opened file descriptor under the given path.
459 ///
460 /// This function does not take ownership of `path`.
461 int
462 __wasilibc_register_preopened_fd(int fd, const char *path)
463 {
464 const char *name = strdup(path);
465 return name == NULL ? -1 : internal_register_preopened_fd(fd, name);
466 }
467
468 int
469 __wasilibc_find_relpath(
470 const char *path,
471 const char **relative_path)
472 {
473 size_t bestlen = 0;
474 int best = -1;
475
476 po_map_assertvalid();
477
478 assert(path != NULL);
479 assert(relative_path != NULL);
480
481 bool any_matches = false;
482 for (size_t i = 0; i < global_map.length; i++) {
483 const struct po_map_entry *entry = &global_map.entries[i];
484 const char *name = entry->name;
485 size_t len = strlen(name);
486
487 if (path[0] != '/' && (path[0] != '.' || (path[1] != '/' && path[1] != '\0'))) {
488 // We're matching a relative path that doesn't start with "./" and isn't ".".
489 if (len >= 2 && name[0] == '.' && name[1] == '/') {
490 // The entry starts with "./", so skip that prefix.
491 name += 2;
492 len -= 2;
493 } else if (len == 1 && name[0] == '.') {
494 // The entry is ".", so match it as an empty string.
495 name += 1;
496 len -= 1;
497 }
498 }
499
500 if ((any_matches && len <= bestlen) || !po_isprefix(name, len, path)) {
501 continue;
502 }
503
504 best = entry->fd;
505 bestlen = len;
506 any_matches = true;
507 }
508
509 const char *relpath = path + bestlen;
510
511 while (*relpath == '/') {
512 relpath++;
513 }
514
515 if (*relpath == '\0') {
516 relpath = ".";
517 }
518
519 *relative_path = relpath;
520 return best;
521 }
522
523 /// This is referenced by weak reference from crt1.c and lives in the same source
524 /// file as `__wasilibc_find_relpath` so that it's linked in when it's needed.
525 __wasi_errno_t
526 __wasilibc_populate_libpreopen(void)
527 {
528 // Skip stdin, stdout, and stderr, and count up until we reach an invalid
529 // file descriptor.
530 for (__wasi_fd_t fd = 3; fd != 0; ++fd) {
531 __wasi_prestat_t prestat;
532 __wasi_errno_t ret = __wasi_fd_prestat_get(fd, &prestat);
533 if (ret == __WASI_ERRNO_BADF)
534 break;
535 if (ret != __WASI_ERRNO_SUCCESS)
536 return ret;
537 switch (prestat.pr_type) {
538 case __WASI_PREOPENTYPE_DIR: {
539 char *path = malloc(prestat.u.dir.pr_name_len + 1);
540 if (path == NULL)
541 return __WASI_ERRNO_NOMEM;
542
543 // TODO: Remove the cast on `path` once the witx is updated with char8 support.
544 ret = __wasi_fd_prestat_dir_name(fd, (uint8_t *)path, prestat.u.dir.pr_name_len);
545 if (ret != __WASI_ERRNO_SUCCESS) {
546 free(path);
547 return ret;
548 }
549 path[prestat.u.dir.pr_name_len] = '\0';
550
551 if (internal_register_preopened_fd(fd, path) != 0) {
552 free(path);
553 return __WASI_ERRNO_NOMEM;
554 }
555
556 break;
557 }
558 default:
559 break;
560 }
561 }
562
563 return __WASI_ERRNO_SUCCESS;
564 }