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 }