]>
git.proxmox.com Git - wasi-libc.git/blob - 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.
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"
58 #include <wasi/libc.h>
59 #include <wasi/libc-find-relpath.h>
61 ////////////////////////////////////////////////////////////////////////////////
63 // POSIX API compatibility wrappers
65 ////////////////////////////////////////////////////////////////////////////////
68 open(const char *path
, int flags
, ...)
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
);
76 __wasilibc_open_nomode(const char *path
, int flags
)
78 const char *relative_path
;
79 int dirfd
= __wasilibc_find_relpath(path
, &relative_path
);
81 // If we can't find a preopened directory handle to open this file with,
82 // indicate that the program lacks the capabilities.
88 return __wasilibc_openat_nomode(dirfd
, relative_path
, flags
);
92 access(const char *path
, int mode
)
94 const char *relative_path
;
95 int dirfd
= __wasilibc_find_relpath(path
, &relative_path
);
97 // If we can't find a preopened directory handle to open this file with,
98 // indicate that the program lacks the capabilities.
104 return faccessat(dirfd
, relative_path
, mode
, 0);
108 lstat(const char *path
, struct stat
*st
)
110 const char *relative_path
;
111 int dirfd
= __wasilibc_find_relpath(path
, &relative_path
);
113 // If we can't find a preopened directory handle to open this file with,
114 // indicate that the program lacks the capabilities.
120 return fstatat(dirfd
, relative_path
, st
, AT_SYMLINK_NOFOLLOW
);
124 rename(const char *from
, const char *to
)
126 const char *from_relative_path
;
127 int from_dirfd
= __wasilibc_find_relpath(from
, &from_relative_path
);
129 const char *to_relative_path
;
130 int to_dirfd
= __wasilibc_find_relpath(to
, &to_relative_path
);
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) {
139 return renameat(from_dirfd
, from_relative_path
, to_dirfd
, to_relative_path
);
143 stat(const char *path
, struct stat
*st
)
145 const char *relative_path
;
146 int dirfd
= __wasilibc_find_relpath(path
, &relative_path
);
148 // If we can't find a preopened directory handle to open this file with,
149 // indicate that the program lacks the capabilities.
155 return fstatat(dirfd
, relative_path
, st
, AT_SYMLINK_NOFOLLOW
);
159 unlink(const char *path
)
161 const char *relative_path
;
162 int dirfd
= __wasilibc_find_relpath(path
, &relative_path
);
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
, &relative_path
);
183 // If we can't find a preopened directory handle to open this file with,
184 // indicate that the program lacks the capabilities.
190 return __wasilibc_rmdirat(dirfd
, relative_path
);
194 remove(const char *pathname
)
196 const char *relative_path
;
197 int dirfd
= __wasilibc_find_relpath(pathname
, &relative_path
);
199 // If searching for both file and directory rights failed, try searching
200 // for either individually.
202 dirfd
= __wasilibc_find_relpath(pathname
, &relative_path
);
204 dirfd
= __wasilibc_find_relpath(pathname
, &relative_path
);
208 // If we can't find a preopened directory handle to open this file with,
209 // indicate that the program lacks the capabilities.
215 int r
= __wasilibc_unlinkat(dirfd
, relative_path
);
216 if (r
!= 0 && (errno
== EISDIR
|| errno
== ENOTCAPABLE
))
217 r
= __wasilibc_rmdirat(dirfd
, relative_path
);
222 link(const char *oldpath
, const char *newpath
)
224 const char *old_relative_path
;
225 int old_dirfd
= __wasilibc_find_relpath(oldpath
, &old_relative_path
);
227 const char *new_relative_path
;
228 int new_dirfd
= __wasilibc_find_relpath(newpath
, &new_relative_path
);
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) {
237 return linkat(old_dirfd
, old_relative_path
, new_dirfd
, new_relative_path
, 0);
241 mkdir(const char *pathname
, mode_t mode
)
243 const char *relative_path
;
244 int dirfd
= __wasilibc_find_relpath(pathname
, &relative_path
);
246 // If we can't find a preopened directory handle to open this file with,
247 // indicate that the program lacks the capabilities.
253 return mkdirat(dirfd
, relative_path
, mode
);
257 opendir(const char *name
)
259 const char *relative_path
;
260 int dirfd
= __wasilibc_find_relpath(name
, &relative_path
);
262 // If we can't find a preopened directory handle to open this file with,
263 // indicate that the program lacks the capabilities.
269 return opendirat(dirfd
, relative_path
);
273 readlink(const char *pathname
, char *buf
, size_t bufsiz
)
275 const char *relative_path
;
276 int dirfd
= __wasilibc_find_relpath(pathname
, &relative_path
);
278 // If we can't find a preopened directory handle to open this file with,
279 // indicate that the program lacks the capabilities.
285 return readlinkat(dirfd
, relative_path
, buf
, bufsiz
);
291 struct dirent
***namelist
,
292 int (*filter
)(const struct dirent
*),
293 int (*compar
)(const struct dirent
**, const struct dirent
**))
295 const char *relative_path
;
296 int dirfd
= __wasilibc_find_relpath(dirp
, &relative_path
);
298 // If we can't find a preopened directory handle to open this file with,
299 // indicate that the program lacks the capabilities.
305 return scandirat(dirfd
, relative_path
, namelist
, filter
, compar
);
309 symlink(const char *target
, const char *linkpath
)
311 const char *relative_path
;
312 int dirfd
= __wasilibc_find_relpath(linkpath
, &relative_path
);
314 // If we can't find a preopened directory handle to open this file with,
315 // indicate that the program lacks the capabilities.
321 return symlinkat(target
, dirfd
, relative_path
);
324 ////////////////////////////////////////////////////////////////////////////////
328 ////////////////////////////////////////////////////////////////////////////////
330 /// An entry in a po_map.
331 struct po_map_entry
{
332 /// The name this file or directory is mapped to.
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.
338 /// File descriptor (which may be a directory)
342 /// A vector of po_map_entry.
344 struct po_map_entry
*entries
;
349 static struct po_map global_map
;
351 /// Is a directory a prefix of a given path?
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`
358 po_isprefix(const char *dir
, size_t dirlen
, const char *path
)
361 assert(path
!= NULL
);
363 // Allow an empty string as a prefix of any relative path.
364 if (path
[0] != '/' && dirlen
== 0)
368 for (i
= 0; i
< dirlen
; i
++)
370 if (path
[i
] != dir
[i
])
374 // Ignore trailing slashes in directory names.
375 while (i
> 0 && dir
[i
- 1] == '/') {
379 return path
[i
] == '/' || path
[i
] == '\0';
382 /// Enlarge a @ref po_map's capacity.
384 /// This results in new memory being allocated and existing entries being copied.
385 /// If the allocation fails, the function will return -1.
389 size_t start_capacity
= 4;
390 size_t new_capacity
= global_map
.capacity
== 0 ?
392 global_map
.capacity
* 2;
394 struct po_map_entry
*enlarged
=
395 calloc(sizeof(struct po_map_entry
), new_capacity
);
396 if (enlarged
== NULL
) {
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
;
406 /// Assert that the global_map is valid.
408 #define po_map_assertvalid()
411 po_map_assertvalid(void)
413 const struct po_map_entry
*entry
;
416 assert(global_map
.length
<= global_map
.capacity
);
417 assert(global_map
.entries
!= NULL
|| global_map
.capacity
== 0);
419 for (i
= 0; i
< global_map
.length
; i
++) {
420 entry
= &global_map
.entries
[i
];
422 assert(entry
->name
!= NULL
);
423 assert(entry
->fd
>= 0);
428 /// Register the given pre-opened file descriptor under the given path.
430 /// This function takes ownership of `name`.
432 internal_register_preopened_fd(int fd
, const char *name
)
434 po_map_assertvalid();
437 assert(name
!= NULL
);
439 if (global_map
.length
== global_map
.capacity
) {
440 int n
= po_map_enlarge();
442 po_map_assertvalid();
449 struct po_map_entry
*entry
= &global_map
.entries
[global_map
.length
++];
454 po_map_assertvalid();
459 /// Register the given pre-opened file descriptor under the given path.
461 /// This function does not take ownership of `path`.
463 __wasilibc_register_preopened_fd(int fd
, const char *path
)
465 const char *name
= strdup(path
);
466 return name
== NULL
? -1 : internal_register_preopened_fd(fd
, name
);
470 __wasilibc_find_relpath(
472 const char **relative_path
)
477 po_map_assertvalid();
479 assert(path
!= NULL
);
480 assert(relative_path
!= NULL
);
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
);
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.
494 } else if (len
== 1 && name
[0] == '.') {
495 // The entry is ".", so match it as an empty string.
501 if ((any_matches
&& len
<= bestlen
) || !po_isprefix(name
, len
, path
)) {
510 const char *relpath
= path
+ bestlen
;
512 while (*relpath
== '/') {
516 if (*relpath
== '\0') {
520 *relative_path
= relpath
;
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)))
530 __wasilibc_populate_libpreopen(void)
532 // Skip stdin, stdout, and stderr, and count up until we reach an invalid
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
)
539 if (ret
!= __WASI_ERRNO_SUCCESS
)
541 switch (prestat
.tag
) {
542 case __WASI_PREOPENTYPE_DIR
: {
543 char *path
= malloc(prestat
.u
.dir
.pr_name_len
+ 1);
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
) {
552 path
[prestat
.u
.dir
.pr_name_len
] = '\0';
554 if (internal_register_preopened_fd(fd
, path
) != 0) {