]>
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"
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 // 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
);
75 __wasilibc_open_nomode(const char *path
, int flags
)
77 const char *relative_path
;
78 int dirfd
= __wasilibc_find_relpath(path
, &relative_path
);
80 // If we can't find a preopened directory handle to open this file with,
81 // indicate that the program lacks the capabilities.
87 return __wasilibc_openat_nomode(dirfd
, relative_path
, flags
);
91 access(const char *path
, int mode
)
93 const char *relative_path
;
94 int dirfd
= __wasilibc_find_relpath(path
, &relative_path
);
96 // If we can't find a preopened directory handle to open this file with,
97 // indicate that the program lacks the capabilities.
103 return faccessat(dirfd
, relative_path
, mode
, 0);
107 lstat(const char *path
, struct stat
*st
)
109 const char *relative_path
;
110 int dirfd
= __wasilibc_find_relpath(path
, &relative_path
);
112 // If we can't find a preopened directory handle to open this file with,
113 // indicate that the program lacks the capabilities.
119 return fstatat(dirfd
, relative_path
, st
, AT_SYMLINK_NOFOLLOW
);
123 rename(const char *from
, const char *to
)
125 const char *from_relative_path
;
126 int from_dirfd
= __wasilibc_find_relpath(from
, &from_relative_path
);
128 const char *to_relative_path
;
129 int to_dirfd
= __wasilibc_find_relpath(to
, &to_relative_path
);
131 // If we can't find a preopened directory handle to open this file with,
132 // indicate that the program lacks the capabilities.
133 if (from_dirfd
== -1 || to_dirfd
== -1) {
138 return renameat(from_dirfd
, from_relative_path
, to_dirfd
, to_relative_path
);
142 stat(const char *path
, struct stat
*st
)
144 const char *relative_path
;
145 int dirfd
= __wasilibc_find_relpath(path
, &relative_path
);
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
, &relative_path
);
163 // If we can't find a preopened directory handle to open this file with,
164 // indicate that the program lacks the capabilities.
170 // `unlinkat` ends up importing `__wasi_path_remove_directory` even
171 // though we're not passing `AT_REMOVEDIR` here. So instead, use a
172 // specialized function which just imports `__wasi_path_unlink_file`.
173 return __wasilibc_unlinkat(dirfd
, relative_path
);
177 rmdir(const char *pathname
)
179 const char *relative_path
;
180 int dirfd
= __wasilibc_find_relpath(pathname
, &relative_path
);
182 // If we can't find a preopened directory handle to open this file with,
183 // indicate that the program lacks the capabilities.
189 return __wasilibc_rmdirat(dirfd
, relative_path
);
193 remove(const char *pathname
)
195 const char *relative_path
;
196 int dirfd
= __wasilibc_find_relpath(pathname
, &relative_path
);
198 // If searching for both file and directory rights failed, try searching
199 // for either individually.
201 dirfd
= __wasilibc_find_relpath(pathname
, &relative_path
);
203 dirfd
= __wasilibc_find_relpath(pathname
, &relative_path
);
207 // If we can't find a preopened directory handle to open this file with,
208 // indicate that the program lacks the capabilities.
214 int r
= __wasilibc_unlinkat(dirfd
, relative_path
);
215 if (r
!= 0 && (errno
== EISDIR
|| errno
== ENOTCAPABLE
))
216 r
= __wasilibc_rmdirat(dirfd
, relative_path
);
221 link(const char *oldpath
, const char *newpath
)
223 const char *old_relative_path
;
224 int old_dirfd
= __wasilibc_find_relpath(oldpath
, &old_relative_path
);
226 const char *new_relative_path
;
227 int new_dirfd
= __wasilibc_find_relpath(newpath
, &new_relative_path
);
229 // If we can't find a preopened directory handle to open this file with,
230 // indicate that the program lacks the capabilities.
231 if (old_dirfd
== -1 || new_dirfd
== -1) {
236 return linkat(old_dirfd
, old_relative_path
, new_dirfd
, new_relative_path
, 0);
240 mkdir(const char *pathname
, mode_t mode
)
242 const char *relative_path
;
243 int dirfd
= __wasilibc_find_relpath(pathname
, &relative_path
);
245 // If we can't find a preopened directory handle to open this file with,
246 // indicate that the program lacks the capabilities.
252 return mkdirat(dirfd
, relative_path
, mode
);
256 opendir(const char *name
)
258 const char *relative_path
;
259 int dirfd
= __wasilibc_find_relpath(name
, &relative_path
);
261 // If we can't find a preopened directory handle to open this file with,
262 // indicate that the program lacks the capabilities.
268 return opendirat(dirfd
, relative_path
);
272 readlink(const char *pathname
, char *buf
, size_t bufsiz
)
274 const char *relative_path
;
275 int dirfd
= __wasilibc_find_relpath(pathname
, &relative_path
);
277 // If we can't find a preopened directory handle to open this file with,
278 // indicate that the program lacks the capabilities.
284 return readlinkat(dirfd
, relative_path
, buf
, bufsiz
);
290 struct dirent
***namelist
,
291 int (*filter
)(const struct dirent
*),
292 int (*compar
)(const struct dirent
**, const struct dirent
**))
294 const char *relative_path
;
295 int dirfd
= __wasilibc_find_relpath(dirp
, &relative_path
);
297 // If we can't find a preopened directory handle to open this file with,
298 // indicate that the program lacks the capabilities.
304 return scandirat(dirfd
, relative_path
, namelist
, filter
, compar
);
308 symlink(const char *target
, const char *linkpath
)
310 const char *relative_path
;
311 int dirfd
= __wasilibc_find_relpath(linkpath
, &relative_path
);
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 symlinkat(target
, dirfd
, relative_path
);
323 ////////////////////////////////////////////////////////////////////////////////
327 ////////////////////////////////////////////////////////////////////////////////
329 /// An entry in a po_map.
330 struct po_map_entry
{
331 /// The name this file or directory is mapped to.
333 /// This name should look like a path, but it does not necessarily need
334 /// match to match the path it was originally obtained from.
337 /// File descriptor (which may be a directory)
341 /// A vector of po_map_entry.
343 struct po_map_entry
*entries
;
348 static struct po_map global_map
;
350 /// Is a directory a prefix of a given path?
352 /// @param dir a directory path, e.g., `/foo/bar`
353 /// @param dirlen the length of @b dir
354 /// @param path a path that may have @b dir as a prefix,
355 /// e.g., `/foo/bar/baz`
357 po_isprefix(const char *dir
, size_t dirlen
, const char *path
)
360 assert(path
!= NULL
);
362 // Allow an empty string as a prefix of any relative path.
363 if (path
[0] != '/' && dirlen
== 0)
367 for (i
= 0; i
< dirlen
; i
++)
369 if (path
[i
] != dir
[i
])
373 // Ignore trailing slashes in directory names.
374 while (i
> 0 && dir
[i
- 1] == '/') {
378 return path
[i
] == '/' || path
[i
] == '\0';
381 /// Enlarge a @ref po_map's capacity.
383 /// This results in new memory being allocated and existing entries being copied.
384 /// If the allocation fails, the function will return -1.
388 size_t start_capacity
= 4;
389 size_t new_capacity
= global_map
.capacity
== 0 ?
391 global_map
.capacity
* 2;
393 struct po_map_entry
*enlarged
=
394 calloc(sizeof(struct po_map_entry
), new_capacity
);
395 if (enlarged
== NULL
) {
398 memcpy(enlarged
, global_map
.entries
, global_map
.length
* sizeof(*enlarged
));
399 free(global_map
.entries
);
400 global_map
.entries
= enlarged
;
401 global_map
.capacity
= new_capacity
;
405 /// Assert that the global_map is valid.
407 #define po_map_assertvalid()
410 po_map_assertvalid(void)
412 const struct po_map_entry
*entry
;
415 assert(global_map
.length
<= global_map
.capacity
);
416 assert(global_map
.entries
!= NULL
|| global_map
.capacity
== 0);
418 for (i
= 0; i
< global_map
.length
; i
++) {
419 entry
= &global_map
.entries
[i
];
421 assert(entry
->name
!= NULL
);
422 assert(entry
->fd
>= 0);
427 /// Register the given pre-opened file descriptor under the given path.
429 /// This function takes ownership of `name`.
431 internal_register_preopened_fd(int fd
, const char *name
)
433 po_map_assertvalid();
436 assert(name
!= NULL
);
438 if (global_map
.length
== global_map
.capacity
) {
439 int n
= po_map_enlarge();
441 po_map_assertvalid();
448 struct po_map_entry
*entry
= &global_map
.entries
[global_map
.length
++];
453 po_map_assertvalid();
458 /// Register the given pre-opened file descriptor under the given path.
460 /// This function does not take ownership of `path`.
462 __wasilibc_register_preopened_fd(int fd
, const char *path
)
464 const char *name
= strdup(path
);
465 return name
== NULL
? -1 : internal_register_preopened_fd(fd
, name
);
469 __wasilibc_find_relpath(
471 const char **relative_path
)
476 po_map_assertvalid();
478 assert(path
!= NULL
);
479 assert(relative_path
!= NULL
);
481 bool any_matches
= false;
482 for (size_t i
= 0; i
< global_map
.length
; i
++) {
483 const struct po_map_entry
*entry
= &global_map
.entries
[i
];
484 const char *name
= entry
->name
;
485 size_t len
= strlen(name
);
487 if (path
[0] != '/' && (path
[0] != '.' || (path
[1] != '/' && path
[1] != '\0'))) {
488 // We're matching a relative path that doesn't start with "./" and isn't ".".
489 if (len
>= 2 && name
[0] == '.' && name
[1] == '/') {
490 // The entry starts with "./", so skip that prefix.
493 } else if (len
== 1 && name
[0] == '.') {
494 // The entry is ".", so match it as an empty string.
500 if ((any_matches
&& len
<= bestlen
) || !po_isprefix(name
, len
, path
)) {
509 const char *relpath
= path
+ bestlen
;
511 while (*relpath
== '/') {
515 if (*relpath
== '\0') {
519 *relative_path
= relpath
;
523 /// This is referenced by weak reference from crt1.c and lives in the same source
524 /// file as `__wasilibc_find_relpath` so that it's linked in when it's needed.
526 __wasilibc_populate_libpreopen(void)
528 // Skip stdin, stdout, and stderr, and count up until we reach an invalid
530 for (__wasi_fd_t fd
= 3; fd
!= 0; ++fd
) {
531 __wasi_prestat_t prestat
;
532 __wasi_errno_t ret
= __wasi_fd_prestat_get(fd
, &prestat
);
533 if (ret
== __WASI_EBADF
)
535 if (ret
!= __WASI_ESUCCESS
)
537 switch (prestat
.pr_type
) {
538 case __WASI_PREOPENTYPE_DIR
: {
539 char *path
= malloc(prestat
.u
.dir
.pr_name_len
+ 1);
541 return __WASI_ENOMEM
;
543 ret
= __wasi_fd_prestat_dir_name(fd
, path
, prestat
.u
.dir
.pr_name_len
);
544 if (ret
!= __WASI_ESUCCESS
) {
548 path
[prestat
.u
.dir
.pr_name_len
] = '\0';
550 if (internal_register_preopened_fd(fd
, path
) != 0) {
552 return __WASI_ENOMEM
;
562 return __WASI_ESUCCESS
;