]> git.proxmox.com Git - wasi-libc.git/blob - libc-bottom-half/libpreopen/libpreopen.c
Lazy-initialize the environment variables. (#184)
[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 <sysexits.h>
58 #include <wasi/libc.h>
59 #include <wasi/libc-find-relpath.h>
60
61 ////////////////////////////////////////////////////////////////////////////////
62 //
63 // POSIX API compatibility wrappers
64 //
65 ////////////////////////////////////////////////////////////////////////////////
66
67 int
68 open(const char *path, int flags, ...)
69 {
70 // WASI libc's openat ignores the mode argument, so call a special
71 // entrypoint which avoids the varargs calling convention.
72 return __wasilibc_open_nomode(path, flags);
73 }
74
75 int
76 __wasilibc_open_nomode(const char *path, int flags)
77 {
78 const char *relative_path;
79 int dirfd = __wasilibc_find_relpath(path, &relative_path);
80
81 // If we can't find a preopened directory handle to open this file with,
82 // indicate that the program lacks the capabilities.
83 if (dirfd == -1) {
84 errno = ENOTCAPABLE;
85 return -1;
86 }
87
88 return __wasilibc_openat_nomode(dirfd, relative_path, flags);
89 }
90
91 int
92 access(const char *path, int mode)
93 {
94 const char *relative_path;
95 int dirfd = __wasilibc_find_relpath(path, &relative_path);
96
97 // If we can't find a preopened directory handle to open this file with,
98 // indicate that the program lacks the capabilities.
99 if (dirfd == -1) {
100 errno = ENOTCAPABLE;
101 return -1;
102 }
103
104 return faccessat(dirfd, relative_path, mode, 0);
105 }
106
107 int
108 lstat(const char *path, struct stat *st)
109 {
110 const char *relative_path;
111 int dirfd = __wasilibc_find_relpath(path, &relative_path);
112
113 // If we can't find a preopened directory handle to open this file with,
114 // indicate that the program lacks the capabilities.
115 if (dirfd == -1) {
116 errno = ENOTCAPABLE;
117 return -1;
118 }
119
120 return fstatat(dirfd, relative_path, st, AT_SYMLINK_NOFOLLOW);
121 }
122
123 int
124 rename(const char *from, const char *to)
125 {
126 const char *from_relative_path;
127 int from_dirfd = __wasilibc_find_relpath(from, &from_relative_path);
128
129 const char *to_relative_path;
130 int to_dirfd = __wasilibc_find_relpath(to, &to_relative_path);
131
132 // If we can't find a preopened directory handle to open this file with,
133 // indicate that the program lacks the capabilities.
134 if (from_dirfd == -1 || to_dirfd == -1) {
135 errno = ENOTCAPABLE;
136 return -1;
137 }
138
139 return renameat(from_dirfd, from_relative_path, to_dirfd, to_relative_path);
140 }
141
142 int
143 stat(const char *path, struct stat *st)
144 {
145 const char *relative_path;
146 int dirfd = __wasilibc_find_relpath(path, &relative_path);
147
148 // If we can't find a preopened directory handle to open this file with,
149 // indicate that the program lacks the capabilities.
150 if (dirfd == -1) {
151 errno = ENOTCAPABLE;
152 return -1;
153 }
154
155 return fstatat(dirfd, relative_path, st, AT_SYMLINK_NOFOLLOW);
156 }
157
158 int
159 unlink(const char *path)
160 {
161 const char *relative_path;
162 int dirfd = __wasilibc_find_relpath(path, &relative_path);
163
164 // If we can't find a preopened directory handle to open this file with,
165 // indicate that the program lacks the capabilities.
166 if (dirfd == -1) {
167 errno = ENOTCAPABLE;
168 return -1;
169 }
170
171 // `unlinkat` ends up importing `__wasi_path_remove_directory` even
172 // though we're not passing `AT_REMOVEDIR` here. So instead, use a
173 // specialized function which just imports `__wasi_path_unlink_file`.
174 return __wasilibc_unlinkat(dirfd, relative_path);
175 }
176
177 int
178 rmdir(const char *pathname)
179 {
180 const char *relative_path;
181 int dirfd = __wasilibc_find_relpath(pathname, &relative_path);
182
183 // If we can't find a preopened directory handle to open this file with,
184 // indicate that the program lacks the capabilities.
185 if (dirfd == -1) {
186 errno = ENOTCAPABLE;
187 return -1;
188 }
189
190 return __wasilibc_rmdirat(dirfd, relative_path);
191 }
192
193 int
194 remove(const char *pathname)
195 {
196 const char *relative_path;
197 int dirfd = __wasilibc_find_relpath(pathname, &relative_path);
198
199 // If searching for both file and directory rights failed, try searching
200 // for either individually.
201 if (dirfd == -1) {
202 dirfd = __wasilibc_find_relpath(pathname, &relative_path);
203 if (dirfd == -1) {
204 dirfd = __wasilibc_find_relpath(pathname, &relative_path);
205 }
206 }
207
208 // If we can't find a preopened directory handle to open this file with,
209 // indicate that the program lacks the capabilities.
210 if (dirfd == -1) {
211 errno = ENOTCAPABLE;
212 return -1;
213 }
214
215 int r = __wasilibc_unlinkat(dirfd, relative_path);
216 if (r != 0 && (errno == EISDIR || errno == ENOTCAPABLE))
217 r = __wasilibc_rmdirat(dirfd, relative_path);
218 return r;
219 }
220
221 int
222 link(const char *oldpath, const char *newpath)
223 {
224 const char *old_relative_path;
225 int old_dirfd = __wasilibc_find_relpath(oldpath, &old_relative_path);
226
227 const char *new_relative_path;
228 int new_dirfd = __wasilibc_find_relpath(newpath, &new_relative_path);
229
230 // If we can't find a preopened directory handle to open this file with,
231 // indicate that the program lacks the capabilities.
232 if (old_dirfd == -1 || new_dirfd == -1) {
233 errno = ENOTCAPABLE;
234 return -1;
235 }
236
237 return linkat(old_dirfd, old_relative_path, new_dirfd, new_relative_path, 0);
238 }
239
240 int
241 mkdir(const char *pathname, mode_t mode)
242 {
243 const char *relative_path;
244 int dirfd = __wasilibc_find_relpath(pathname, &relative_path);
245
246 // If we can't find a preopened directory handle to open this file with,
247 // indicate that the program lacks the capabilities.
248 if (dirfd == -1) {
249 errno = ENOTCAPABLE;
250 return -1;
251 }
252
253 return mkdirat(dirfd, relative_path, mode);
254 }
255
256 DIR *
257 opendir(const char *name)
258 {
259 const char *relative_path;
260 int dirfd = __wasilibc_find_relpath(name, &relative_path);
261
262 // If we can't find a preopened directory handle to open this file with,
263 // indicate that the program lacks the capabilities.
264 if (dirfd == -1) {
265 errno = ENOTCAPABLE;
266 return NULL;
267 }
268
269 return opendirat(dirfd, relative_path);
270 }
271
272 ssize_t
273 readlink(const char *pathname, char *buf, size_t bufsiz)
274 {
275 const char *relative_path;
276 int dirfd = __wasilibc_find_relpath(pathname, &relative_path);
277
278 // If we can't find a preopened directory handle to open this file with,
279 // indicate that the program lacks the capabilities.
280 if (dirfd == -1) {
281 errno = ENOTCAPABLE;
282 return -1;
283 }
284
285 return readlinkat(dirfd, relative_path, buf, bufsiz);
286 }
287
288 int
289 scandir(
290 const char *dirp,
291 struct dirent ***namelist,
292 int (*filter)(const struct dirent *),
293 int (*compar)(const struct dirent **, const struct dirent **))
294 {
295 const char *relative_path;
296 int dirfd = __wasilibc_find_relpath(dirp, &relative_path);
297
298 // If we can't find a preopened directory handle to open this file with,
299 // indicate that the program lacks the capabilities.
300 if (dirfd == -1) {
301 errno = ENOTCAPABLE;
302 return -1;
303 }
304
305 return scandirat(dirfd, relative_path, namelist, filter, compar);
306 }
307
308 int
309 symlink(const char *target, const char *linkpath)
310 {
311 const char *relative_path;
312 int dirfd = __wasilibc_find_relpath(linkpath, &relative_path);
313
314 // If we can't find a preopened directory handle to open this file with,
315 // indicate that the program lacks the capabilities.
316 if (dirfd == -1) {
317 errno = ENOTCAPABLE;
318 return -1;
319 }
320
321 return symlinkat(target, dirfd, relative_path);
322 }
323
324 ////////////////////////////////////////////////////////////////////////////////
325 //
326 // Support library
327 //
328 ////////////////////////////////////////////////////////////////////////////////
329
330 /// An entry in a po_map.
331 struct po_map_entry {
332 /// The name this file or directory is mapped to.
333 ///
334 /// This name should look like a path, but it does not necessarily need
335 /// match to match the path it was originally obtained from.
336 const char *name;
337
338 /// File descriptor (which may be a directory)
339 int fd;
340 };
341
342 /// A vector of po_map_entry.
343 struct po_map {
344 struct po_map_entry *entries;
345 size_t capacity;
346 size_t length;
347 };
348
349 static struct po_map global_map;
350
351 /// Is a directory a prefix of a given path?
352 ///
353 /// @param dir a directory path, e.g., `/foo/bar`
354 /// @param dirlen the length of @b dir
355 /// @param path a path that may have @b dir as a prefix,
356 /// e.g., `/foo/bar/baz`
357 static bool
358 po_isprefix(const char *dir, size_t dirlen, const char *path)
359 {
360 assert(dir != NULL);
361 assert(path != NULL);
362
363 // Allow an empty string as a prefix of any relative path.
364 if (path[0] != '/' && dirlen == 0)
365 return true;
366
367 size_t i;
368 for (i = 0; i < dirlen; i++)
369 {
370 if (path[i] != dir[i])
371 return false;
372 }
373
374 // Ignore trailing slashes in directory names.
375 while (i > 0 && dir[i - 1] == '/') {
376 --i;
377 }
378
379 return path[i] == '/' || path[i] == '\0';
380 }
381
382 /// Enlarge a @ref po_map's capacity.
383 ///
384 /// This results in new memory being allocated and existing entries being copied.
385 /// If the allocation fails, the function will return -1.
386 static int
387 po_map_enlarge(void)
388 {
389 size_t start_capacity = 4;
390 size_t new_capacity = global_map.capacity == 0 ?
391 start_capacity :
392 global_map.capacity * 2;
393
394 struct po_map_entry *enlarged =
395 calloc(sizeof(struct po_map_entry), new_capacity);
396 if (enlarged == NULL) {
397 return -1;
398 }
399 memcpy(enlarged, global_map.entries, global_map.length * sizeof(*enlarged));
400 free(global_map.entries);
401 global_map.entries = enlarged;
402 global_map.capacity = new_capacity;
403 return 0;
404 }
405
406 /// Assert that the global_map is valid.
407 #ifdef NDEBUG
408 #define po_map_assertvalid()
409 #else
410 static void
411 po_map_assertvalid(void)
412 {
413 const struct po_map_entry *entry;
414 size_t i;
415
416 assert(global_map.length <= global_map.capacity);
417 assert(global_map.entries != NULL || global_map.capacity == 0);
418
419 for (i = 0; i < global_map.length; i++) {
420 entry = &global_map.entries[i];
421
422 assert(entry->name != NULL);
423 assert(entry->fd >= 0);
424 }
425 }
426 #endif
427
428 /// Register the given pre-opened file descriptor under the given path.
429 ///
430 /// This function takes ownership of `name`.
431 static int
432 internal_register_preopened_fd(int fd, const char *name)
433 {
434 po_map_assertvalid();
435
436 assert(fd >= 0);
437 assert(name != NULL);
438
439 if (global_map.length == global_map.capacity) {
440 int n = po_map_enlarge();
441
442 po_map_assertvalid();
443
444 if (n != 0) {
445 return n;
446 }
447 }
448
449 struct po_map_entry *entry = &global_map.entries[global_map.length++];
450
451 entry->name = name;
452 entry->fd = fd;
453
454 po_map_assertvalid();
455
456 return 0;
457 }
458
459 /// Register the given pre-opened file descriptor under the given path.
460 ///
461 /// This function does not take ownership of `path`.
462 int
463 __wasilibc_register_preopened_fd(int fd, const char *path)
464 {
465 const char *name = strdup(path);
466 return name == NULL ? -1 : internal_register_preopened_fd(fd, name);
467 }
468
469 int
470 __wasilibc_find_relpath(
471 const char *path,
472 const char **relative_path)
473 {
474 size_t bestlen = 0;
475 int best = -1;
476
477 po_map_assertvalid();
478
479 assert(path != NULL);
480 assert(relative_path != NULL);
481
482 bool any_matches = false;
483 for (size_t i = 0; i < global_map.length; i++) {
484 const struct po_map_entry *entry = &global_map.entries[i];
485 const char *name = entry->name;
486 size_t len = strlen(name);
487
488 if (path[0] != '/' && (path[0] != '.' || (path[1] != '/' && path[1] != '\0'))) {
489 // We're matching a relative path that doesn't start with "./" and isn't ".".
490 if (len >= 2 && name[0] == '.' && name[1] == '/') {
491 // The entry starts with "./", so skip that prefix.
492 name += 2;
493 len -= 2;
494 } else if (len == 1 && name[0] == '.') {
495 // The entry is ".", so match it as an empty string.
496 name += 1;
497 len -= 1;
498 }
499 }
500
501 if ((any_matches && len <= bestlen) || !po_isprefix(name, len, path)) {
502 continue;
503 }
504
505 best = entry->fd;
506 bestlen = len;
507 any_matches = true;
508 }
509
510 const char *relpath = path + bestlen;
511
512 while (*relpath == '/') {
513 relpath++;
514 }
515
516 if (*relpath == '\0') {
517 relpath = ".";
518 }
519
520 *relative_path = relpath;
521 return best;
522 }
523
524 /// This is referenced by weak reference from crt1.c and lives in the same source
525 /// file as `__wasilibc_find_relpath` so that it's linked in when it's needed.
526 // Concerning the 51 -- see the comment by the constructor priority in
527 // libc-bottom-half/sources/__wasilibc_environ.c.
528 __attribute__((constructor(51)))
529 static void
530 __wasilibc_populate_libpreopen(void)
531 {
532 // Skip stdin, stdout, and stderr, and count up until we reach an invalid
533 // file descriptor.
534 for (__wasi_fd_t fd = 3; fd != 0; ++fd) {
535 __wasi_prestat_t prestat;
536 __wasi_errno_t ret = __wasi_fd_prestat_get(fd, &prestat);
537 if (ret == __WASI_ERRNO_BADF)
538 break;
539 if (ret != __WASI_ERRNO_SUCCESS)
540 goto oserr;
541 switch (prestat.tag) {
542 case __WASI_PREOPENTYPE_DIR: {
543 char *path = malloc(prestat.u.dir.pr_name_len + 1);
544 if (path == NULL)
545 goto software;
546
547 // TODO: Remove the cast on `path` once the witx is updated with char8 support.
548 ret = __wasi_fd_prestat_dir_name(fd, (uint8_t *)path, prestat.u.dir.pr_name_len);
549 if (ret != __WASI_ERRNO_SUCCESS) {
550 goto oserr;
551 }
552 path[prestat.u.dir.pr_name_len] = '\0';
553
554 if (internal_register_preopened_fd(fd, path) != 0) {
555 goto software;
556 }
557
558 break;
559 }
560 default:
561 break;
562 }
563 }
564
565 return;
566 oserr:
567 _Exit(EX_OSERR);
568 software:
569 _Exit(EX_SOFTWARE);
570 }