1 //! Preopen functionality for WASI libc, for emulating support for absolute
2 //! path names on top of WASI's OCap-style API.
4 //! This file is derived from code in libpreopen, the upstream for which is:
6 //! https://github.com/musec/libpreopen
8 //! and which bears the following copyrights and license:
11 * Copyright (c) 2016-2017 Stanley Uche Godfrey
12 * Copyright (c) 2016-2018 Jonathan Anderson
13 * All rights reserved.
15 * This software was developed at Memorial University under the
16 * NSERC Discovery program (RGPIN-2015-06048).
18 * Redistribution and use in source and binary forms, with or without
19 * modification, are permitted provided that the following conditions
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.
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
42 #error "__wasilibc_register_preopened_fd doesn't yet support multiple threads"
57 #include <wasi/libc.h>
58 #include <wasi/libc-find-relpath.h>
60 ////////////////////////////////////////////////////////////////////////////////
62 // POSIX API compatibility wrappers
64 ////////////////////////////////////////////////////////////////////////////////
67 open(const char *path
, int flags
, ...)
69 const char *relative_path
;
70 int dirfd
= __wasilibc_find_relpath(path
, __WASI_RIGHT_PATH_OPEN
, 0,
73 // If we can't find a preopened directory handle to open this file with,
74 // indicate that the program lacks the capabilities.
80 // WASI libc's openat ignores the mode argument, so don't bother passing
81 // the actual mode value through.
82 return openat(dirfd
, relative_path
, flags
);
86 access(const char *path
, int mode
)
88 const char *relative_path
;
89 int dirfd
= __wasilibc_find_relpath(path
, __WASI_RIGHT_PATH_FILESTAT_GET
, 0,
92 // If we can't find a preopened directory handle to open this file with,
93 // indicate that the program lacks the capabilities.
99 return faccessat(dirfd
, relative_path
, mode
, 0);
103 lstat(const char *path
, struct stat
*st
)
105 const char *relative_path
;
106 int dirfd
= __wasilibc_find_relpath(path
, __WASI_RIGHT_PATH_FILESTAT_GET
, 0,
109 // If we can't find a preopened directory handle to open this file with,
110 // indicate that the program lacks the capabilities.
116 return fstatat(dirfd
, relative_path
, st
, AT_SYMLINK_NOFOLLOW
);
120 rename(const char *from
, const char *to
)
122 const char *from_relative_path
;
123 int from_dirfd
= __wasilibc_find_relpath(from
, __WASI_RIGHT_PATH_RENAME_SOURCE
, 0,
124 &from_relative_path
);
126 const char *to_relative_path
;
127 int to_dirfd
= __wasilibc_find_relpath(to
, __WASI_RIGHT_PATH_RENAME_TARGET
, 0,
130 // If we can't find a preopened directory handle to open this file with,
131 // indicate that the program lacks the capabilities.
132 if (from_dirfd
== -1 || to_dirfd
== -1) {
137 return renameat(from_dirfd
, from_relative_path
, to_dirfd
, to_relative_path
);
141 stat(const char *path
, struct stat
*st
)
143 const char *relative_path
;
144 int dirfd
= __wasilibc_find_relpath(path
, __WASI_RIGHT_PATH_FILESTAT_GET
, 0,
147 // If we can't find a preopened directory handle to open this file with,
148 // indicate that the program lacks the capabilities.
154 return fstatat(dirfd
, relative_path
, st
, AT_SYMLINK_NOFOLLOW
);
158 unlink(const char *path
)
160 const char *relative_path
;
161 int dirfd
= __wasilibc_find_relpath(path
, __WASI_RIGHT_PATH_UNLINK_FILE
, 0,
164 // If we can't find a preopened directory handle to open this file with,
165 // indicate that the program lacks the capabilities.
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
);
178 rmdir(const char *pathname
)
180 const char *relative_path
;
181 int dirfd
= __wasilibc_find_relpath(pathname
, __WASI_RIGHT_PATH_REMOVE_DIRECTORY
, 0,
184 // If we can't find a preopened directory handle to open this file with,
185 // indicate that the program lacks the capabilities.
191 return __wasilibc_rmdirat(dirfd
, relative_path
);
195 remove(const char *pathname
)
197 const char *relative_path
;
198 int dirfd
= __wasilibc_find_relpath(pathname
,
199 __WASI_RIGHT_PATH_UNLINK_FILE
|
200 __WASI_RIGHT_PATH_REMOVE_DIRECTORY
,
204 // If searching for both file and directory rights failed, try searching
205 // for either individually.
207 dirfd
= __wasilibc_find_relpath(pathname
, __WASI_RIGHT_PATH_UNLINK_FILE
, 0,
210 dirfd
= __wasilibc_find_relpath(pathname
, __WASI_RIGHT_PATH_REMOVE_DIRECTORY
, 0,
215 // If we can't find a preopened directory handle to open this file with,
216 // indicate that the program lacks the capabilities.
222 int r
= __wasilibc_unlinkat(dirfd
, relative_path
);
223 if (r
!= 0 && (errno
== EISDIR
|| errno
== ENOTCAPABLE
))
224 r
= __wasilibc_rmdirat(dirfd
, relative_path
);
229 link(const char *oldpath
, const char *newpath
)
231 const char *old_relative_path
;
232 int old_dirfd
= __wasilibc_find_relpath(oldpath
, __WASI_RIGHT_PATH_LINK_SOURCE
, 0,
235 const char *new_relative_path
;
236 int new_dirfd
= __wasilibc_find_relpath(newpath
, __WASI_RIGHT_PATH_LINK_TARGET
, 0,
239 // If we can't find a preopened directory handle to open this file with,
240 // indicate that the program lacks the capabilities.
241 if (old_dirfd
== -1 || new_dirfd
== -1) {
246 return linkat(old_dirfd
, old_relative_path
, new_dirfd
, new_relative_path
, 0);
250 mkdir(const char *pathname
, mode_t mode
)
252 const char *relative_path
;
253 int dirfd
= __wasilibc_find_relpath(pathname
, __WASI_RIGHT_PATH_CREATE_DIRECTORY
, 0,
256 // If we can't find a preopened directory handle to open this file with,
257 // indicate that the program lacks the capabilities.
263 return mkdirat(dirfd
, relative_path
, mode
);
267 opendir(const char *name
)
269 const char *relative_path
;
270 int dirfd
= __wasilibc_find_relpath(name
, __WASI_RIGHT_PATH_OPEN
, 0,
273 // If we can't find a preopened directory handle to open this file with,
274 // indicate that the program lacks the capabilities.
280 return opendirat(dirfd
, relative_path
);
284 readlink(const char *pathname
, char *buf
, size_t bufsiz
)
286 const char *relative_path
;
287 int dirfd
= __wasilibc_find_relpath(pathname
, __WASI_RIGHT_PATH_READLINK
, 0,
290 // If we can't find a preopened directory handle to open this file with,
291 // indicate that the program lacks the capabilities.
297 return readlinkat(dirfd
, relative_path
, buf
, bufsiz
);
303 struct dirent
***namelist
,
304 int (*filter
)(const struct dirent
*),
305 int (*compar
)(const struct dirent
**, const struct dirent
**))
307 const char *relative_path
;
308 int dirfd
= __wasilibc_find_relpath(dirp
,
309 __WASI_RIGHT_PATH_OPEN
,
310 __WASI_RIGHT_FD_READDIR
,
313 // If we can't find a preopened directory handle to open this file with,
314 // indicate that the program lacks the capabilities.
320 return scandirat(dirfd
, relative_path
, namelist
, filter
, compar
);
324 symlink(const char *target
, const char *linkpath
)
326 const char *relative_path
;
327 int dirfd
= __wasilibc_find_relpath(linkpath
, __WASI_RIGHT_PATH_SYMLINK
, 0,
330 // If we can't find a preopened directory handle to open this file with,
331 // indicate that the program lacks the capabilities.
337 return symlinkat(target
, dirfd
, relative_path
);
340 ////////////////////////////////////////////////////////////////////////////////
344 ////////////////////////////////////////////////////////////////////////////////
346 /// An entry in a po_map.
347 struct po_map_entry
{
348 /// The name this file or directory is mapped to.
350 /// This name should look like a path, but it does not necessarily need
351 /// match to match the path it was originally obtained from.
354 /// File descriptor (which may be a directory)
357 /// Capability rights associated with the file descriptor
358 __wasi_rights_t rights_base
;
359 __wasi_rights_t rights_inheriting
;
362 /// A vector of po_map_entry.
364 struct po_map_entry
*entries
;
369 static struct po_map global_map
;
371 /// Is a directory a prefix of a given path?
373 /// @param dir a directory path, e.g., `/foo/bar`
374 /// @param dirlen the length of @b dir
375 /// @param path a path that may have @b dir as a prefix,
376 /// e.g., `/foo/bar/baz`
378 po_isprefix(const char *dir
, size_t dirlen
, const char *path
)
381 assert(path
!= NULL
);
383 // Allow an empty string as a prefix of any relative path.
384 if (path
[0] != '/' && dirlen
== 0)
388 for (i
= 0; i
< dirlen
; i
++)
390 if (path
[i
] != dir
[i
])
394 // Ignore trailing slashes in directory names.
395 while (i
> 0 && dir
[i
- 1] == '/') {
399 return path
[i
] == '/' || path
[i
] == '\0';
402 /// Enlarge a @ref po_map's capacity.
404 /// This results in new memory being allocated and existing entries being copied.
405 /// If the allocation fails, the function will return -1.
409 size_t start_capacity
= 4;
410 size_t new_capacity
= global_map
.capacity
== 0 ?
412 global_map
.capacity
* 2;
414 struct po_map_entry
*enlarged
=
415 calloc(sizeof(struct po_map_entry
), new_capacity
);
416 if (enlarged
== NULL
) {
419 memcpy(enlarged
, global_map
.entries
, global_map
.length
* sizeof(*enlarged
));
420 free(global_map
.entries
);
421 global_map
.entries
= enlarged
;
422 global_map
.capacity
= new_capacity
;
426 /// Assert that the global_map is valid.
428 #define po_map_assertvalid()
431 po_map_assertvalid(void)
433 const struct po_map_entry
*entry
;
436 assert(global_map
.length
<= global_map
.capacity
);
437 assert(global_map
.entries
!= NULL
|| global_map
.capacity
== 0);
439 for (i
= 0; i
< global_map
.length
; i
++) {
440 entry
= &global_map
.entries
[i
];
442 assert(entry
->name
!= NULL
);
443 assert(entry
->fd
>= 0);
448 /// Register the given pre-opened file descriptor under the given path.
450 __wasilibc_register_preopened_fd(int fd
, const char *path
)
452 po_map_assertvalid();
455 assert(path
!= NULL
);
457 if (global_map
.length
== global_map
.capacity
) {
458 int n
= po_map_enlarge();
460 po_map_assertvalid();
467 __wasi_fdstat_t statbuf
;
468 int r
= __wasi_fd_fdstat_get((__wasi_fd_t
)fd
, &statbuf
);
471 return -1; // TODO: Add an infallible way to get the rights?
474 const char *name
= strdup(path
);
479 struct po_map_entry
*entry
= &global_map
.entries
[global_map
.length
++];
483 entry
->rights_base
= statbuf
.fs_rights_base
;
484 entry
->rights_inheriting
= statbuf
.fs_rights_inheriting
;
486 po_map_assertvalid();
492 __wasilibc_find_relpath(
494 __wasi_rights_t rights_base
,
495 __wasi_rights_t rights_inheriting
,
496 const char **relative_path
)
501 po_map_assertvalid();
503 assert(path
!= NULL
);
504 assert(relative_path
!= NULL
);
506 bool any_matches
= false;
507 for (size_t i
= 0; i
< global_map
.length
; i
++) {
508 const struct po_map_entry
*entry
= &global_map
.entries
[i
];
509 const char *name
= entry
->name
;
510 size_t len
= strlen(name
);
512 if (path
[0] != '/' && (path
[0] != '.' || (path
[1] != '/' && path
[1] != '\0'))) {
513 // We're matching a relative path that doesn't start with "./" and isn't ".".
514 if (len
>= 2 && name
[0] == '.' && name
[1] == '/') {
515 // The entry starts with "./", so skip that prefix.
518 } else if (len
== 1 && name
[0] == '.') {
519 // The entry is ".", so match it as an empty string.
525 if ((any_matches
&& len
<= bestlen
) || !po_isprefix(name
, len
, path
)) {
529 if ((rights_base
& ~entry
->rights_base
) != 0 ||
530 (rights_inheriting
& ~entry
->rights_inheriting
) != 0) {
539 const char *relpath
= path
+ bestlen
;
541 while (*relpath
== '/') {
545 if (*relpath
== '\0') {
549 *relative_path
= relpath
;