]> git.proxmox.com Git - wasi-libc.git/blob - libc-bottom-half/libpreopen/libpreopen.c
Revamp and simplify the libpreopen code. (#110)
[wasi-libc.git] / 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.
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
66 int
67 open(const char *path, int flags, ...)
68 {
69 const char *relative_path;
70 int dirfd = __wasilibc_find_relpath(path, __WASI_RIGHT_PATH_OPEN, 0,
71 &relative_path);
72
73 // If we can't find a preopened directory handle to open this file with,
74 // indicate that the program lacks the capabilities.
75 if (dirfd == -1) {
76 errno = ENOTCAPABLE;
77 return -1;
78 }
79
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);
83 }
84
85 int
86 access(const char *path, int mode)
87 {
88 const char *relative_path;
89 int dirfd = __wasilibc_find_relpath(path, __WASI_RIGHT_PATH_FILESTAT_GET, 0,
90 &relative_path);
91
92 // If we can't find a preopened directory handle to open this file with,
93 // indicate that the program lacks the capabilities.
94 if (dirfd == -1) {
95 errno = ENOTCAPABLE;
96 return -1;
97 }
98
99 return faccessat(dirfd, relative_path, mode, 0);
100 }
101
102 int
103 lstat(const char *path, struct stat *st)
104 {
105 const char *relative_path;
106 int dirfd = __wasilibc_find_relpath(path, __WASI_RIGHT_PATH_FILESTAT_GET, 0,
107 &relative_path);
108
109 // If we can't find a preopened directory handle to open this file with,
110 // indicate that the program lacks the capabilities.
111 if (dirfd == -1) {
112 errno = ENOTCAPABLE;
113 return -1;
114 }
115
116 return fstatat(dirfd, relative_path, st, AT_SYMLINK_NOFOLLOW);
117 }
118
119 int
120 rename(const char *from, const char *to)
121 {
122 const char *from_relative_path;
123 int from_dirfd = __wasilibc_find_relpath(from, __WASI_RIGHT_PATH_RENAME_SOURCE, 0,
124 &from_relative_path);
125
126 const char *to_relative_path;
127 int to_dirfd = __wasilibc_find_relpath(to, __WASI_RIGHT_PATH_RENAME_TARGET, 0,
128 &to_relative_path);
129
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) {
133 errno = ENOTCAPABLE;
134 return -1;
135 }
136
137 return renameat(from_dirfd, from_relative_path, to_dirfd, to_relative_path);
138 }
139
140 int
141 stat(const char *path, struct stat *st)
142 {
143 const char *relative_path;
144 int dirfd = __wasilibc_find_relpath(path, __WASI_RIGHT_PATH_FILESTAT_GET, 0,
145 &relative_path);
146
147 // If we can't find a preopened directory handle to open this file with,
148 // indicate that the program lacks the capabilities.
149 if (dirfd == -1) {
150 errno = ENOTCAPABLE;
151 return -1;
152 }
153
154 return fstatat(dirfd, relative_path, st, AT_SYMLINK_NOFOLLOW);
155 }
156
157 int
158 unlink(const char *path)
159 {
160 const char *relative_path;
161 int dirfd = __wasilibc_find_relpath(path, __WASI_RIGHT_PATH_UNLINK_FILE, 0,
162 &relative_path);
163
164 // If we can't find a preopened directory handle to open this file with,
165 // indicate that the program lacks the capabilities.
166 if (dirfd == -1) {
167 errno = ENOTCAPABLE;
168 return -1;
169 }
170
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);
175 }
176
177 int
178 rmdir(const char *pathname)
179 {
180 const char *relative_path;
181 int dirfd = __wasilibc_find_relpath(pathname, __WASI_RIGHT_PATH_REMOVE_DIRECTORY, 0,
182 &relative_path);
183
184 // If we can't find a preopened directory handle to open this file with,
185 // indicate that the program lacks the capabilities.
186 if (dirfd == -1) {
187 errno = ENOTCAPABLE;
188 return -1;
189 }
190
191 return __wasilibc_rmdirat(dirfd, relative_path);
192 }
193
194 int
195 remove(const char *pathname)
196 {
197 const char *relative_path;
198 int dirfd = __wasilibc_find_relpath(pathname,
199 __WASI_RIGHT_PATH_UNLINK_FILE |
200 __WASI_RIGHT_PATH_REMOVE_DIRECTORY,
201 0,
202 &relative_path);
203
204 // If searching for both file and directory rights failed, try searching
205 // for either individually.
206 if (dirfd == -1) {
207 dirfd = __wasilibc_find_relpath(pathname, __WASI_RIGHT_PATH_UNLINK_FILE, 0,
208 &relative_path);
209 if (dirfd == -1) {
210 dirfd = __wasilibc_find_relpath(pathname, __WASI_RIGHT_PATH_REMOVE_DIRECTORY, 0,
211 &relative_path);
212 }
213 }
214
215 // If we can't find a preopened directory handle to open this file with,
216 // indicate that the program lacks the capabilities.
217 if (dirfd == -1) {
218 errno = ENOTCAPABLE;
219 return -1;
220 }
221
222 int r = __wasilibc_unlinkat(dirfd, relative_path);
223 if (r != 0 && (errno == EISDIR || errno == ENOTCAPABLE))
224 r = __wasilibc_rmdirat(dirfd, relative_path);
225 return r;
226 }
227
228 int
229 link(const char *oldpath, const char *newpath)
230 {
231 const char *old_relative_path;
232 int old_dirfd = __wasilibc_find_relpath(oldpath, __WASI_RIGHT_PATH_LINK_SOURCE, 0,
233 &old_relative_path);
234
235 const char *new_relative_path;
236 int new_dirfd = __wasilibc_find_relpath(newpath, __WASI_RIGHT_PATH_LINK_TARGET, 0,
237 &new_relative_path);
238
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) {
242 errno = ENOTCAPABLE;
243 return -1;
244 }
245
246 return linkat(old_dirfd, old_relative_path, new_dirfd, new_relative_path, 0);
247 }
248
249 int
250 mkdir(const char *pathname, mode_t mode)
251 {
252 const char *relative_path;
253 int dirfd = __wasilibc_find_relpath(pathname, __WASI_RIGHT_PATH_CREATE_DIRECTORY, 0,
254 &relative_path);
255
256 // If we can't find a preopened directory handle to open this file with,
257 // indicate that the program lacks the capabilities.
258 if (dirfd == -1) {
259 errno = ENOTCAPABLE;
260 return -1;
261 }
262
263 return mkdirat(dirfd, relative_path, mode);
264 }
265
266 DIR *
267 opendir(const char *name)
268 {
269 const char *relative_path;
270 int dirfd = __wasilibc_find_relpath(name, __WASI_RIGHT_PATH_OPEN, 0,
271 &relative_path);
272
273 // If we can't find a preopened directory handle to open this file with,
274 // indicate that the program lacks the capabilities.
275 if (dirfd == -1) {
276 errno = ENOTCAPABLE;
277 return NULL;
278 }
279
280 return opendirat(dirfd, relative_path);
281 }
282
283 ssize_t
284 readlink(const char *pathname, char *buf, size_t bufsiz)
285 {
286 const char *relative_path;
287 int dirfd = __wasilibc_find_relpath(pathname, __WASI_RIGHT_PATH_READLINK, 0,
288 &relative_path);
289
290 // If we can't find a preopened directory handle to open this file with,
291 // indicate that the program lacks the capabilities.
292 if (dirfd == -1) {
293 errno = ENOTCAPABLE;
294 return -1;
295 }
296
297 return readlinkat(dirfd, relative_path, buf, bufsiz);
298 }
299
300 int
301 scandir(
302 const char *dirp,
303 struct dirent ***namelist,
304 int (*filter)(const struct dirent *),
305 int (*compar)(const struct dirent **, const struct dirent **))
306 {
307 const char *relative_path;
308 int dirfd = __wasilibc_find_relpath(dirp,
309 __WASI_RIGHT_PATH_OPEN,
310 __WASI_RIGHT_FD_READDIR,
311 &relative_path);
312
313 // If we can't find a preopened directory handle to open this file with,
314 // indicate that the program lacks the capabilities.
315 if (dirfd == -1) {
316 errno = ENOTCAPABLE;
317 return -1;
318 }
319
320 return scandirat(dirfd, relative_path, namelist, filter, compar);
321 }
322
323 int
324 symlink(const char *target, const char *linkpath)
325 {
326 const char *relative_path;
327 int dirfd = __wasilibc_find_relpath(linkpath, __WASI_RIGHT_PATH_SYMLINK, 0,
328 &relative_path);
329
330 // If we can't find a preopened directory handle to open this file with,
331 // indicate that the program lacks the capabilities.
332 if (dirfd == -1) {
333 errno = ENOTCAPABLE;
334 return -1;
335 }
336
337 return symlinkat(target, dirfd, relative_path);
338 }
339
340 ////////////////////////////////////////////////////////////////////////////////
341 //
342 // Support library
343 //
344 ////////////////////////////////////////////////////////////////////////////////
345
346 /// An entry in a po_map.
347 struct po_map_entry {
348 /// The name this file or directory is mapped to.
349 ///
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.
352 const char *name;
353
354 /// File descriptor (which may be a directory)
355 int fd;
356
357 /// Capability rights associated with the file descriptor
358 __wasi_rights_t rights_base;
359 __wasi_rights_t rights_inheriting;
360 };
361
362 /// A vector of po_map_entry.
363 struct po_map {
364 struct po_map_entry *entries;
365 size_t capacity;
366 size_t length;
367 };
368
369 static struct po_map global_map;
370
371 /// Is a directory a prefix of a given path?
372 ///
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`
377 static bool
378 po_isprefix(const char *dir, size_t dirlen, const char *path)
379 {
380 assert(dir != NULL);
381 assert(path != NULL);
382
383 // Allow an empty string as a prefix of any relative path.
384 if (path[0] != '/' && dirlen == 0)
385 return true;
386
387 size_t i;
388 for (i = 0; i < dirlen; i++)
389 {
390 if (path[i] != dir[i])
391 return false;
392 }
393
394 // Ignore trailing slashes in directory names.
395 while (i > 0 && dir[i - 1] == '/') {
396 --i;
397 }
398
399 return path[i] == '/' || path[i] == '\0';
400 }
401
402 /// Enlarge a @ref po_map's capacity.
403 ///
404 /// This results in new memory being allocated and existing entries being copied.
405 /// If the allocation fails, the function will return -1.
406 static int
407 po_map_enlarge(void)
408 {
409 size_t start_capacity = 4;
410 size_t new_capacity = global_map.capacity == 0 ?
411 start_capacity :
412 global_map.capacity * 2;
413
414 struct po_map_entry *enlarged =
415 calloc(sizeof(struct po_map_entry), new_capacity);
416 if (enlarged == NULL) {
417 return -1;
418 }
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;
423 return 0;
424 }
425
426 /// Assert that the global_map is valid.
427 #ifdef NDEBUG
428 #define po_map_assertvalid()
429 #else
430 static void
431 po_map_assertvalid(void)
432 {
433 const struct po_map_entry *entry;
434 size_t i;
435
436 assert(global_map.length <= global_map.capacity);
437 assert(global_map.entries != NULL || global_map.capacity == 0);
438
439 for (i = 0; i < global_map.length; i++) {
440 entry = &global_map.entries[i];
441
442 assert(entry->name != NULL);
443 assert(entry->fd >= 0);
444 }
445 }
446 #endif
447
448 /// Register the given pre-opened file descriptor under the given path.
449 int
450 __wasilibc_register_preopened_fd(int fd, const char *path)
451 {
452 po_map_assertvalid();
453
454 assert(fd >= 0);
455 assert(path != NULL);
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
467 __wasi_fdstat_t statbuf;
468 int r = __wasi_fd_fdstat_get((__wasi_fd_t)fd, &statbuf);
469 if (r != 0) {
470 errno = r;
471 return -1; // TODO: Add an infallible way to get the rights?
472 }
473
474 const char *name = strdup(path);
475 if (name == NULL) {
476 return -1;
477 }
478
479 struct po_map_entry *entry = &global_map.entries[global_map.length++];
480
481 entry->name = name;
482 entry->fd = fd;
483 entry->rights_base = statbuf.fs_rights_base;
484 entry->rights_inheriting = statbuf.fs_rights_inheriting;
485
486 po_map_assertvalid();
487
488 return 0;
489 }
490
491 int
492 __wasilibc_find_relpath(
493 const char *path,
494 __wasi_rights_t rights_base,
495 __wasi_rights_t rights_inheriting,
496 const char **relative_path)
497 {
498 size_t bestlen = 0;
499 int best = -1;
500
501 po_map_assertvalid();
502
503 assert(path != NULL);
504 assert(relative_path != NULL);
505
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);
511
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.
516 name += 2;
517 len -= 2;
518 } else if (len == 1 && name[0] == '.') {
519 // The entry is ".", so match it as an empty string.
520 name += 1;
521 len -= 1;
522 }
523 }
524
525 if ((any_matches && len <= bestlen) || !po_isprefix(name, len, path)) {
526 continue;
527 }
528
529 if ((rights_base & ~entry->rights_base) != 0 ||
530 (rights_inheriting & ~entry->rights_inheriting) != 0) {
531 continue;
532 }
533
534 best = entry->fd;
535 bestlen = len;
536 any_matches = true;
537 }
538
539 const char *relpath = path + bestlen;
540
541 while (*relpath == '/') {
542 relpath++;
543 }
544
545 if (*relpath == '\0') {
546 relpath = ".";
547 }
548
549 *relative_path = relpath;
550 return best;
551 }