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