master
  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.
 19typedef 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`.
 28static _Atomic _Bool preopens_populated = false;
 29static preopen *preopens;
 30static size_t num_preopens;
 31static size_t preopen_capacity;
 32
 33/// Access to the the above preopen must be protected in the presence of
 34/// threads.
 35#ifdef _REENTRANT
 36static volatile int lock[1];
 37#endif
 38
 39#ifdef NDEBUG
 40#define assert_invariants() // assertions disabled
 41#else
 42static 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.
 61static 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.
 85static 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.
102static 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`.
126static 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`?
137static 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
158int __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.
165int __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.
178int __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)))
232void __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;
286oserr:
287    _Exit(EX_OSERR);
288software:
289    _Exit(EX_SOFTWARE);
290}
291
292void __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}