]> git.proxmox.com Git - wasi-libc.git/blame - libc-bottom-half/libpreopen/libpreopen.c
Avoid varargs conventions when calling open (#126)
[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>
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
66int
67open(const char *path, int flags, ...)
a94d2d04
DG
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
74int
75__wasilibc_open_nomode(const char *path, int flags)
7fcc4f29
DG
76{
77 const char *relative_path;
78 int dirfd = __wasilibc_find_relpath(path, __WASI_RIGHT_PATH_OPEN, 0,
79 &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
a94d2d04 88 return __wasilibc_openat_nomode(dirfd, relative_path, flags);
7fcc4f29
DG
89}
90
91int
92access(const char *path, int mode)
93{
94 const char *relative_path;
95 int dirfd = __wasilibc_find_relpath(path, __WASI_RIGHT_PATH_FILESTAT_GET, 0,
96 &relative_path);
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;
112 int dirfd = __wasilibc_find_relpath(path, __WASI_RIGHT_PATH_FILESTAT_GET, 0,
113 &relative_path);
114
115 // If we can't find a preopened directory handle to open this file with,
116 // indicate that the program lacks the capabilities.
117 if (dirfd == -1) {
118 errno = ENOTCAPABLE;
119 return -1;
120 }
121
122 return fstatat(dirfd, relative_path, st, AT_SYMLINK_NOFOLLOW);
123}
124
125int
126rename(const char *from, const char *to)
127{
128 const char *from_relative_path;
129 int from_dirfd = __wasilibc_find_relpath(from, __WASI_RIGHT_PATH_RENAME_SOURCE, 0,
130 &from_relative_path);
131
132 const char *to_relative_path;
133 int to_dirfd = __wasilibc_find_relpath(to, __WASI_RIGHT_PATH_RENAME_TARGET, 0,
134 &to_relative_path);
135
136 // If we can't find a preopened directory handle to open this file with,
137 // indicate that the program lacks the capabilities.
138 if (from_dirfd == -1 || to_dirfd == -1) {
139 errno = ENOTCAPABLE;
140 return -1;
141 }
142
143 return renameat(from_dirfd, from_relative_path, to_dirfd, to_relative_path);
144}
145
146int
147stat(const char *path, struct stat *st)
148{
149 const char *relative_path;
150 int dirfd = __wasilibc_find_relpath(path, __WASI_RIGHT_PATH_FILESTAT_GET, 0,
151 &relative_path);
152
153 // If we can't find a preopened directory handle to open this file with,
154 // indicate that the program lacks the capabilities.
155 if (dirfd == -1) {
156 errno = ENOTCAPABLE;
157 return -1;
158 }
159
160 return fstatat(dirfd, relative_path, st, AT_SYMLINK_NOFOLLOW);
161}
162
163int
164unlink(const char *path)
165{
166 const char *relative_path;
167 int dirfd = __wasilibc_find_relpath(path, __WASI_RIGHT_PATH_UNLINK_FILE, 0,
168 &relative_path);
169
170 // If we can't find a preopened directory handle to open this file with,
171 // indicate that the program lacks the capabilities.
172 if (dirfd == -1) {
173 errno = ENOTCAPABLE;
174 return -1;
175 }
176
177 // `unlinkat` ends up importing `__wasi_path_remove_directory` even
178 // though we're not passing `AT_REMOVEDIR` here. So instead, use a
179 // specialized function which just imports `__wasi_path_unlink_file`.
180 return __wasilibc_unlinkat(dirfd, relative_path);
181}
182
183int
184rmdir(const char *pathname)
185{
186 const char *relative_path;
187 int dirfd = __wasilibc_find_relpath(pathname, __WASI_RIGHT_PATH_REMOVE_DIRECTORY, 0,
188 &relative_path);
189
190 // If we can't find a preopened directory handle to open this file with,
191 // indicate that the program lacks the capabilities.
192 if (dirfd == -1) {
193 errno = ENOTCAPABLE;
194 return -1;
195 }
196
197 return __wasilibc_rmdirat(dirfd, relative_path);
198}
199
200int
201remove(const char *pathname)
202{
203 const char *relative_path;
204 int dirfd = __wasilibc_find_relpath(pathname,
205 __WASI_RIGHT_PATH_UNLINK_FILE |
206 __WASI_RIGHT_PATH_REMOVE_DIRECTORY,
207 0,
208 &relative_path);
209
210 // If searching for both file and directory rights failed, try searching
211 // for either individually.
212 if (dirfd == -1) {
213 dirfd = __wasilibc_find_relpath(pathname, __WASI_RIGHT_PATH_UNLINK_FILE, 0,
214 &relative_path);
215 if (dirfd == -1) {
216 dirfd = __wasilibc_find_relpath(pathname, __WASI_RIGHT_PATH_REMOVE_DIRECTORY, 0,
217 &relative_path);
218 }
219 }
220
221 // If we can't find a preopened directory handle to open this file with,
222 // indicate that the program lacks the capabilities.
223 if (dirfd == -1) {
224 errno = ENOTCAPABLE;
225 return -1;
226 }
227
228 int r = __wasilibc_unlinkat(dirfd, relative_path);
229 if (r != 0 && (errno == EISDIR || errno == ENOTCAPABLE))
230 r = __wasilibc_rmdirat(dirfd, relative_path);
231 return r;
232}
233
234int
235link(const char *oldpath, const char *newpath)
236{
237 const char *old_relative_path;
238 int old_dirfd = __wasilibc_find_relpath(oldpath, __WASI_RIGHT_PATH_LINK_SOURCE, 0,
239 &old_relative_path);
240
241 const char *new_relative_path;
242 int new_dirfd = __wasilibc_find_relpath(newpath, __WASI_RIGHT_PATH_LINK_TARGET, 0,
243 &new_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 (old_dirfd == -1 || new_dirfd == -1) {
248 errno = ENOTCAPABLE;
249 return -1;
250 }
251
252 return linkat(old_dirfd, old_relative_path, new_dirfd, new_relative_path, 0);
253}
254
255int
256mkdir(const char *pathname, mode_t mode)
257{
258 const char *relative_path;
259 int dirfd = __wasilibc_find_relpath(pathname, __WASI_RIGHT_PATH_CREATE_DIRECTORY, 0,
260 &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 -1;
267 }
268
269 return mkdirat(dirfd, relative_path, mode);
270}
271
272DIR *
273opendir(const char *name)
274{
275 const char *relative_path;
276 int dirfd = __wasilibc_find_relpath(name, __WASI_RIGHT_PATH_OPEN, 0,
277 &relative_path);
278
279 // If we can't find a preopened directory handle to open this file with,
280 // indicate that the program lacks the capabilities.
281 if (dirfd == -1) {
282 errno = ENOTCAPABLE;
283 return NULL;
284 }
285
286 return opendirat(dirfd, relative_path);
287}
288
289ssize_t
290readlink(const char *pathname, char *buf, size_t bufsiz)
291{
292 const char *relative_path;
293 int dirfd = __wasilibc_find_relpath(pathname, __WASI_RIGHT_PATH_READLINK, 0,
294 &relative_path);
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;
314 int dirfd = __wasilibc_find_relpath(dirp,
315 __WASI_RIGHT_PATH_OPEN,
316 __WASI_RIGHT_FD_READDIR,
317 &relative_path);
318
319 // If we can't find a preopened directory handle to open this file with,
320 // indicate that the program lacks the capabilities.
321 if (dirfd == -1) {
322 errno = ENOTCAPABLE;
323 return -1;
324 }
325
326 return scandirat(dirfd, relative_path, namelist, filter, compar);
327}
328
329int
330symlink(const char *target, const char *linkpath)
331{
332 const char *relative_path;
333 int dirfd = __wasilibc_find_relpath(linkpath, __WASI_RIGHT_PATH_SYMLINK, 0,
334 &relative_path);
335
336 // If we can't find a preopened directory handle to open this file with,
337 // indicate that the program lacks the capabilities.
338 if (dirfd == -1) {
339 errno = ENOTCAPABLE;
340 return -1;
341 }
342
343 return symlinkat(target, dirfd, relative_path);
344}
345
346////////////////////////////////////////////////////////////////////////////////
347//
348// Support library
349//
350////////////////////////////////////////////////////////////////////////////////
351
352/// An entry in a po_map.
353struct po_map_entry {
354 /// The name this file or directory is mapped to.
355 ///
356 /// This name should look like a path, but it does not necessarily need
357 /// match to match the path it was originally obtained from.
358 const char *name;
359
360 /// File descriptor (which may be a directory)
361 int fd;
362
363 /// Capability rights associated with the file descriptor
364 __wasi_rights_t rights_base;
365 __wasi_rights_t rights_inheriting;
366};
367
368/// A vector of po_map_entry.
369struct po_map {
370 struct po_map_entry *entries;
371 size_t capacity;
372 size_t length;
373};
374
375static struct po_map global_map;
376
377/// Is a directory a prefix of a given path?
378///
379/// @param dir a directory path, e.g., `/foo/bar`
380/// @param dirlen the length of @b dir
381/// @param path a path that may have @b dir as a prefix,
382/// e.g., `/foo/bar/baz`
383static bool
384po_isprefix(const char *dir, size_t dirlen, const char *path)
385{
386 assert(dir != NULL);
387 assert(path != NULL);
388
389 // Allow an empty string as a prefix of any relative path.
390 if (path[0] != '/' && dirlen == 0)
391 return true;
392
393 size_t i;
394 for (i = 0; i < dirlen; i++)
395 {
396 if (path[i] != dir[i])
397 return false;
398 }
399
400 // Ignore trailing slashes in directory names.
401 while (i > 0 && dir[i - 1] == '/') {
402 --i;
403 }
404
405 return path[i] == '/' || path[i] == '\0';
406}
407
408/// Enlarge a @ref po_map's capacity.
409///
410/// This results in new memory being allocated and existing entries being copied.
411/// If the allocation fails, the function will return -1.
412static int
413po_map_enlarge(void)
414{
415 size_t start_capacity = 4;
416 size_t new_capacity = global_map.capacity == 0 ?
417 start_capacity :
418 global_map.capacity * 2;
419
420 struct po_map_entry *enlarged =
421 calloc(sizeof(struct po_map_entry), new_capacity);
422 if (enlarged == NULL) {
423 return -1;
424 }
425 memcpy(enlarged, global_map.entries, global_map.length * sizeof(*enlarged));
426 free(global_map.entries);
427 global_map.entries = enlarged;
428 global_map.capacity = new_capacity;
429 return 0;
430}
431
432/// Assert that the global_map is valid.
433#ifdef NDEBUG
434#define po_map_assertvalid()
435#else
436static void
437po_map_assertvalid(void)
438{
439 const struct po_map_entry *entry;
440 size_t i;
441
442 assert(global_map.length <= global_map.capacity);
443 assert(global_map.entries != NULL || global_map.capacity == 0);
444
445 for (i = 0; i < global_map.length; i++) {
446 entry = &global_map.entries[i];
447
448 assert(entry->name != NULL);
449 assert(entry->fd >= 0);
450 }
451}
452#endif
453
454/// Register the given pre-opened file descriptor under the given path.
455int
456__wasilibc_register_preopened_fd(int fd, const char *path)
457{
458 po_map_assertvalid();
459
460 assert(fd >= 0);
461 assert(path != NULL);
462
463 if (global_map.length == global_map.capacity) {
464 int n = po_map_enlarge();
465
466 po_map_assertvalid();
467
468 if (n != 0) {
469 return n;
470 }
471 }
472
473 __wasi_fdstat_t statbuf;
474 int r = __wasi_fd_fdstat_get((__wasi_fd_t)fd, &statbuf);
475 if (r != 0) {
476 errno = r;
477 return -1; // TODO: Add an infallible way to get the rights?
478 }
479
480 const char *name = strdup(path);
481 if (name == NULL) {
482 return -1;
483 }
484
485 struct po_map_entry *entry = &global_map.entries[global_map.length++];
486
487 entry->name = name;
488 entry->fd = fd;
489 entry->rights_base = statbuf.fs_rights_base;
490 entry->rights_inheriting = statbuf.fs_rights_inheriting;
491
492 po_map_assertvalid();
493
494 return 0;
495}
496
497int
498__wasilibc_find_relpath(
499 const char *path,
500 __wasi_rights_t rights_base,
501 __wasi_rights_t rights_inheriting,
502 const char **relative_path)
503{
504 size_t bestlen = 0;
505 int best = -1;
506
507 po_map_assertvalid();
508
509 assert(path != NULL);
510 assert(relative_path != NULL);
511
512 bool any_matches = false;
513 for (size_t i = 0; i < global_map.length; i++) {
514 const struct po_map_entry *entry = &global_map.entries[i];
515 const char *name = entry->name;
516 size_t len = strlen(name);
517
518 if (path[0] != '/' && (path[0] != '.' || (path[1] != '/' && path[1] != '\0'))) {
519 // We're matching a relative path that doesn't start with "./" and isn't ".".
520 if (len >= 2 && name[0] == '.' && name[1] == '/') {
521 // The entry starts with "./", so skip that prefix.
522 name += 2;
523 len -= 2;
524 } else if (len == 1 && name[0] == '.') {
525 // The entry is ".", so match it as an empty string.
526 name += 1;
527 len -= 1;
528 }
529 }
530
531 if ((any_matches && len <= bestlen) || !po_isprefix(name, len, path)) {
532 continue;
533 }
534
535 if ((rights_base & ~entry->rights_base) != 0 ||
536 (rights_inheriting & ~entry->rights_inheriting) != 0) {
537 continue;
538 }
539
540 best = entry->fd;
541 bestlen = len;
542 any_matches = true;
543 }
544
545 const char *relpath = path + bestlen;
546
547 while (*relpath == '/') {
548 relpath++;
549 }
550
551 if (*relpath == '\0') {
552 relpath = ".";
553 }
554
555 *relative_path = relpath;
556 return best;
557}