motiejus/zig

fork of https://codeberg.org/ziglang/zig
git clone https://git.jakstys.lt/motiejus/zig.git
Log | Tree | Refs | README | LICENSE

lib/libc/wasi/libc-bottom-half/sources/preopens.c (9220B) - Raw


      1 //! Support for "preopens", file descriptors passed into the program from the
      2 //! environment, with associated path prefixes, which can be used to map
      3 //! absolute paths to capabilities with relative paths.
      4 
      5 #include <assert.h>
      6 #include <errno.h>
      7 #include <fcntl.h>
      8 #include <limits.h>
      9 #include <lock.h>
     10 #include <stdbool.h>
     11 #include <stdlib.h>
     12 #include <string.h>
     13 #include <sysexits.h>
     14 #include <wasi/api.h>
     15 #include <wasi/libc-find-relpath.h>
     16 #include <wasi/libc.h>
     17 
     18 /// A name and file descriptor pair.
     19 typedef struct preopen {
     20     /// The path prefix associated with the file descriptor.
     21     const char *prefix;
     22 
     23     /// The file descriptor.
     24     __wasi_fd_t fd;
     25 } preopen;
     26 
     27 /// A simple growable array of `preopen`.
     28 static _Atomic _Bool preopens_populated = false;
     29 static preopen *preopens;
     30 static size_t num_preopens;
     31 static size_t preopen_capacity;
     32 
     33 /// Access to the the above preopen must be protected in the presence of
     34 /// threads.
     35 #ifdef _REENTRANT
     36 static volatile int lock[1];
     37 #endif
     38 
     39 #ifdef NDEBUG
     40 #define assert_invariants() // assertions disabled
     41 #else
     42 static void assert_invariants(void) {
     43     assert(num_preopens <= preopen_capacity);
     44     assert(preopen_capacity == 0 || preopens != NULL);
     45     assert(preopen_capacity == 0 ||
     46            preopen_capacity * sizeof(preopen) > preopen_capacity);
     47 
     48     for (size_t i = 0; i < num_preopens; ++i) {
     49         const preopen *pre = &preopens[i];
     50         assert(pre->prefix != NULL);
     51         assert(pre->fd != (__wasi_fd_t)-1);
     52 #ifdef __wasm__
     53         assert((uintptr_t)pre->prefix <
     54                (__uint128_t)__builtin_wasm_memory_size(0) * PAGESIZE);
     55 #endif
     56     }
     57 }
     58 #endif
     59 
     60 /// Allocate space for more preopens. Returns 0 on success and -1 on failure.
     61 static int resize(void) {
     62     size_t start_capacity = 4;
     63     size_t old_capacity = preopen_capacity;
     64     size_t new_capacity = old_capacity == 0 ? start_capacity : old_capacity * 2;
     65 
     66     preopen *old_preopens = preopens;
     67     preopen *new_preopens = calloc(sizeof(preopen), new_capacity);
     68     if (new_preopens == NULL) {
     69         return -1;
     70     }
     71 
     72     memcpy(new_preopens, old_preopens, num_preopens * sizeof(preopen));
     73     preopens = new_preopens;
     74     preopen_capacity = new_capacity;
     75     free(old_preopens);
     76 
     77     assert_invariants();
     78     return 0;
     79 }
     80 
     81 // Normalize an absolute path. Removes leading `/` and leading `./`, so the
     82 // first character is the start of a directory name. This works because our
     83 // process always starts with a working directory of `/`. Additionally translate
     84 // `.` to the empty string.
     85 static const char *strip_prefixes(const char *path) {
     86     while (1) {
     87         if (path[0] == '/') {
     88             path++;
     89         } else if (path[0] == '.' && path[1] == '/') {
     90             path += 2;
     91         } else if (path[0] == '.' && path[1] == 0) {
     92             path++;
     93         } else {
     94             break;
     95         }
     96     }
     97 
     98     return path;
     99 }
    100 
    101 /// Similar to `internal_register_preopened_fd` but does not take a lock.
    102 static int internal_register_preopened_fd_unlocked(__wasi_fd_t fd, const char *relprefix) {
    103     // Check preconditions.
    104     assert_invariants();
    105     assert(fd != AT_FDCWD);
    106     assert(fd != -1);
    107     assert(relprefix != NULL);
    108 
    109     if (num_preopens == preopen_capacity && resize() != 0) {
    110         return -1;
    111     }
    112 
    113     char *prefix = strdup(strip_prefixes(relprefix));
    114     if (prefix == NULL) {
    115         return -1;
    116     }
    117     preopens[num_preopens++] = (preopen) { prefix, fd, };
    118 
    119     assert_invariants();
    120     return 0;
    121 }
    122 
    123 /// Register the given preopened file descriptor under the given path.
    124 ///
    125 /// This function takes ownership of `prefix`.
    126 static int internal_register_preopened_fd(__wasi_fd_t fd, const char *relprefix) {
    127     LOCK(lock);
    128 
    129     int r = internal_register_preopened_fd_unlocked(fd, relprefix);
    130 
    131     UNLOCK(lock);
    132 
    133     return r;
    134 }
    135 
    136 /// Are the `prefix_len` bytes pointed to by `prefix` a prefix of `path`?
    137 static bool prefix_matches(const char *prefix, size_t prefix_len, const char *path) {
    138     // Allow an empty string as a prefix of any relative path.
    139     if (path[0] != '/' && prefix_len == 0)
    140         return true;
    141 
    142     // Check whether any bytes of the prefix differ.
    143     if (memcmp(path, prefix, prefix_len) != 0)
    144         return false;
    145 
    146     // Ignore trailing slashes in directory names.
    147     size_t i = prefix_len;
    148     while (i > 0 && prefix[i - 1] == '/') {
    149         --i;
    150     }
    151 
    152     // Match only complete path components.
    153     char last = path[i];
    154     return last == '/' || last == '\0';
    155 }
    156 
    157 // See the documentation in libc.h
    158 int __wasilibc_register_preopened_fd(int fd, const char *prefix) {
    159     __wasilibc_populate_preopens();
    160 
    161     return internal_register_preopened_fd((__wasi_fd_t)fd, prefix);
    162 }
    163 
    164 // See the documentation in libc-find-relpath.h.
    165 int __wasilibc_find_relpath(const char *path,
    166                             const char **abs_prefix,
    167                             char **relative_path,
    168                             size_t relative_path_len) {
    169     // If `chdir` is linked, whose object file defines this symbol, then we
    170     // call that. Otherwise if the program can't `chdir` then `path` is
    171     // absolute (or relative to the root dir), so we delegate to `find_abspath`
    172     if (__wasilibc_find_relpath_alloc)
    173         return __wasilibc_find_relpath_alloc(path, abs_prefix, relative_path, &relative_path_len, 0);
    174     return __wasilibc_find_abspath(path, abs_prefix, (const char**) relative_path);
    175 }
    176 
    177 // See the documentation in libc-find-relpath.h.
    178 int __wasilibc_find_abspath(const char *path,
    179                             const char **abs_prefix,
    180                             const char **relative_path) {
    181     __wasilibc_populate_preopens();
    182 
    183     // Strip leading `/` characters, the prefixes we're matching won't have
    184     // them.
    185     while (*path == '/')
    186         path++;
    187     // Search through the preopens table. Iterate in reverse so that more
    188     // recently added preopens take precedence over less recently addded ones.
    189     size_t match_len = 0;
    190     int fd = -1;
    191     LOCK(lock);
    192     for (size_t i = num_preopens; i > 0; --i) {
    193         const preopen *pre = &preopens[i - 1];
    194         const char *prefix = pre->prefix;
    195         size_t len = strlen(prefix);
    196 
    197         // If we haven't had a match yet, or the candidate path is longer than
    198         // our current best match's path, and the candidate path is a prefix of
    199         // the requested path, take that as the new best path.
    200         if ((fd == -1 || len > match_len) &&
    201             prefix_matches(prefix, len, path))
    202         {
    203             fd = pre->fd;
    204             match_len = len;
    205             *abs_prefix = prefix;
    206         }
    207     }
    208     UNLOCK(lock);
    209 
    210     if (fd == -1) {
    211         errno = ENOENT;
    212         return -1;
    213     }
    214 
    215     // The relative path is the substring after the portion that was matched.
    216     const char *computed = path + match_len;
    217 
    218     // Omit leading slashes in the relative path.
    219     while (*computed == '/')
    220         ++computed;
    221 
    222     // *at syscalls don't accept empty relative paths, so use "." instead.
    223     if (*computed == '\0')
    224         computed = ".";
    225 
    226     *relative_path = computed;
    227     return fd;
    228 }
    229 
    230 /* zig patch: initialize preopens early so zig code doesn't have to call __wasilibc_populate_preopens */
    231 __attribute__((constructor(51)))
    232 void __wasilibc_populate_preopens(void) {
    233     // Fast path: If the preopens are already initialized, do nothing.
    234     if (preopens_populated) {
    235         return;
    236     }
    237 
    238     LOCK(lock);
    239 
    240     // Check whether another thread initialized the preopens already.
    241     if (preopens_populated) {
    242         UNLOCK(lock);
    243         return;
    244     }
    245 
    246     // Skip stdin, stdout, and stderr, and count up until we reach an invalid
    247     // file descriptor.
    248     for (__wasi_fd_t fd = 3; fd != 0; ++fd) {
    249         __wasi_prestat_t prestat;
    250         __wasi_errno_t ret = __wasi_fd_prestat_get(fd, &prestat);
    251         if (ret == __WASI_ERRNO_BADF)
    252             break;
    253         if (ret != __WASI_ERRNO_SUCCESS)
    254             goto oserr;
    255         switch (prestat.tag) {
    256         case __WASI_PREOPENTYPE_DIR: {
    257             char *prefix = malloc(prestat.u.dir.pr_name_len + 1);
    258             if (prefix == NULL)
    259                 goto software;
    260 
    261             // TODO: Remove the cast on `prefix` once the witx is updated with
    262             // char8 support.
    263             ret = __wasi_fd_prestat_dir_name(fd, (uint8_t *)prefix,
    264                                              prestat.u.dir.pr_name_len);
    265             if (ret != __WASI_ERRNO_SUCCESS)
    266                 goto oserr;
    267             prefix[prestat.u.dir.pr_name_len] = '\0';
    268 
    269             if (internal_register_preopened_fd_unlocked(fd, prefix) != 0)
    270                 goto software;
    271             free(prefix);
    272 
    273             break;
    274         }
    275         default:
    276             break;
    277         }
    278     }
    279 
    280     // Preopens are now initialized.
    281     preopens_populated = true;
    282 
    283     UNLOCK(lock);
    284 
    285     return;
    286 oserr:
    287     _Exit(EX_OSERR);
    288 software:
    289     _Exit(EX_SOFTWARE);
    290 }
    291 
    292 void __wasilibc_reset_preopens(void) {
    293     LOCK(lock);
    294 
    295     if (num_preopens) {
    296         for (int i = 0; i < num_preopens; ++i) {
    297             free((void*) preopens[i].prefix);
    298         }
    299         free(preopens);
    300     }
    301     
    302     preopens_populated = false;
    303     preopens = NULL;
    304     num_preopens = 0;
    305     preopen_capacity = 0;
    306     
    307     assert_invariants();
    308     
    309     UNLOCK(lock);
    310 }