1//! POSIX-like functions supporting absolute path arguments, implemented in
  2//! terms of `__wasilibc_find_relpath` and `*at`-style functions.
  3
  4#include <errno.h>
  5#include <dirent.h>
  6#include <fcntl.h>
  7#include <stdlib.h>
  8#include <sys/stat.h>
  9#include <sys/statvfs.h>
 10#include <unistd.h>
 11#include <utime.h>
 12#include <wasi/libc.h>
 13#include <wasi/libc-find-relpath.h>
 14#include <wasi/libc-nocwd.h>
 15
 16static int find_relpath2(
 17    const char *path,
 18    char **relative,
 19    size_t *relative_len
 20) {
 21    // See comments in `preopens.c` for what this trick is doing.
 22    const char *abs;
 23    if (__wasilibc_find_relpath_alloc)
 24        return __wasilibc_find_relpath_alloc(path, &abs, relative, relative_len, 1);
 25    return __wasilibc_find_relpath(path, &abs, relative, *relative_len);
 26}
 27
 28// Helper to call `__wasilibc_find_relpath` and return an already-managed
 29// pointer for the `relative` path. This function is not reentrant since the
 30// `relative` pointer will point to static data that cannot be reused until
 31// `relative` is no longer used.
 32static int find_relpath(const char *path, char **relative) {
 33    static __thread char *relative_buf = NULL;
 34    static __thread size_t relative_buf_len = 0;
 35    int fd = find_relpath2(path, &relative_buf, &relative_buf_len);
 36    // find_relpath2 can update relative_buf, so assign it after the call
 37    *relative = relative_buf;
 38    return fd;
 39}
 40
 41// same as `find_relpath`, but uses another set of static variables to cache
 42static int find_relpath_alt(const char *path, char **relative) {
 43    static __thread char *relative_buf = NULL;
 44    static __thread size_t relative_buf_len = 0;
 45    int fd = find_relpath2(path, &relative_buf, &relative_buf_len);
 46    // find_relpath2 can update relative_buf, so assign it after the call
 47    *relative = relative_buf;
 48    return fd;
 49}
 50
 51int open(const char *path, int oflag, ...) {
 52    // WASI libc's `openat` ignores the mode argument, so call a special
 53    // entrypoint which avoids the varargs calling convention.
 54    return __wasilibc_open_nomode(path, oflag);
 55}
 56
 57// See the documentation in libc.h
 58int __wasilibc_open_nomode(const char *path, int oflag) {
 59    char *relative_path;
 60    int dirfd = find_relpath(path, &relative_path);
 61
 62    // If we can't find a preopen for it, fail as if we can't find the path.
 63    if (dirfd == -1) {
 64        errno = ENOENT;
 65        return -1;
 66    }
 67
 68    return __wasilibc_nocwd_openat_nomode(dirfd, relative_path, oflag);
 69}
 70
 71int access(const char *path, int amode) {
 72    char *relative_path;
 73    int dirfd = find_relpath(path, &relative_path);
 74
 75    // If we can't find a preopen for it, fail as if we can't find the path.
 76    if (dirfd == -1) {
 77        errno = ENOENT;
 78        return -1;
 79    }
 80
 81    return __wasilibc_nocwd_faccessat(dirfd, relative_path, amode, 0);
 82}
 83
 84ssize_t readlink(
 85    const char *restrict path,
 86    char *restrict buf,
 87    size_t bufsize)
 88{
 89    char *relative_path;
 90    int dirfd = find_relpath(path, &relative_path);
 91
 92    // If we can't find a preopen for it, fail as if we can't find the path.
 93    if (dirfd == -1) {
 94        errno = ENOENT;
 95        return -1;
 96    }
 97
 98    return __wasilibc_nocwd_readlinkat(dirfd, relative_path, buf, bufsize);
 99}
100
101int stat(const char *restrict path, struct stat *restrict buf) {
102    char *relative_path;
103    int dirfd = find_relpath(path, &relative_path);
104
105    // If we can't find a preopen for it, fail as if we can't find the path.
106    if (dirfd == -1) {
107        errno = ENOENT;
108        return -1;
109    }
110
111    return __wasilibc_nocwd_fstatat(dirfd, relative_path, buf, 0);
112}
113
114int lstat(const char *restrict path, struct stat *restrict buf) {
115    char *relative_path;
116    int dirfd = find_relpath(path, &relative_path);
117
118    // If we can't find a preopen for it, fail as if we can't find the path.
119    if (dirfd == -1) {
120        errno = ENOENT;
121        return -1;
122    }
123
124    return __wasilibc_nocwd_fstatat(dirfd, relative_path, buf, AT_SYMLINK_NOFOLLOW);
125}
126
127int utime(const char *path, const struct utimbuf *times) {
128    char *relative_path;
129    int dirfd = find_relpath(path, &relative_path);
130
131    // If we can't find a preopen for it, fail as if we can't find the path.
132    if (dirfd == -1) {
133        errno = ENOENT;
134        return -1;
135    }
136
137    return __wasilibc_nocwd_utimensat(
138             dirfd, relative_path,
139                     times ? ((struct timespec [2]) {
140                                 { .tv_sec = times->actime },
141                                 { .tv_sec = times->modtime }
142                             })
143                           : NULL,
144                     0);
145}
146
147int utimes(const char *path, const struct timeval times[2]) {
148    char *relative_path;
149    int dirfd = find_relpath(path, &relative_path);
150
151    // If we can't find a preopen for it, fail as if we can't find the path.
152    if (dirfd == -1) {
153        errno = ENOENT;
154        return -1;
155    }
156
157    return __wasilibc_nocwd_utimensat(
158             dirfd, relative_path,
159                     times ? ((struct timespec [2]) {
160                                 { .tv_sec = times[0].tv_sec,
161				   .tv_nsec = times[0].tv_usec * 1000 },
162                                 { .tv_sec = times[1].tv_sec,
163				   .tv_nsec = times[1].tv_usec * 1000 },
164                             })
165                           : NULL,
166                     0);
167}
168
169int unlink(const char *path) {
170    char *relative_path;
171    int dirfd = find_relpath(path, &relative_path);
172
173    // If we can't find a preopen for it, fail as if we can't find the path.
174    if (dirfd == -1) {
175        errno = ENOENT;
176        return -1;
177    }
178
179    // `unlinkat` imports `__wasi_path_remove_directory` even when
180    // `AT_REMOVEDIR` isn't passed. Instead, use a specialized function which
181    // just imports `__wasi_path_unlink_file`.
182    return __wasilibc_nocwd___wasilibc_unlinkat(dirfd, relative_path);
183}
184
185int rmdir(const char *path) {
186    char *relative_path;
187    int dirfd = find_relpath(path, &relative_path);
188
189    // If we can't find a preopen for it, fail as if we can't find the path.
190    if (dirfd == -1) {
191        errno = ENOENT;
192        return -1;
193    }
194
195    return __wasilibc_nocwd___wasilibc_rmdirat(dirfd, relative_path);
196}
197
198int remove(const char *path) {
199    char *relative_path;
200    int dirfd = find_relpath(path, &relative_path);
201
202    // If we can't find a preopen for it, fail as if we can't find the path.
203    if (dirfd == -1) {
204        errno = ENOENT;
205        return -1;
206    }
207
208    // First try to remove it as a file.
209    int r = __wasilibc_nocwd___wasilibc_unlinkat(dirfd, relative_path);
210    if (r != 0 && (errno == EISDIR || errno == ENOENT)) {
211        // That failed, but it might be a directory.
212        r = __wasilibc_nocwd___wasilibc_rmdirat(dirfd, relative_path);
213
214        // If it isn't a directory, we lack capabilities to remove it as a file.
215        if (errno == ENOTDIR)
216            errno = ENOENT;
217    }
218    return r;
219}
220
221int mkdir(const char *path, mode_t mode) {
222    char *relative_path;
223    int dirfd = find_relpath(path, &relative_path);
224
225    // If we can't find a preopen for it, fail as if we can't find the path.
226    if (dirfd == -1) {
227        errno = ENOENT;
228        return -1;
229    }
230
231    return __wasilibc_nocwd_mkdirat_nomode(dirfd, relative_path);
232}
233
234DIR *opendir(const char *dirname) {
235    char *relative_path;
236    int dirfd = find_relpath(dirname, &relative_path);
237
238    // If we can't find a preopen for it, fail as if we can't find the path.
239    if (dirfd == -1) {
240        errno = ENOENT;
241        return NULL;
242    }
243
244    return __wasilibc_nocwd_opendirat(dirfd, relative_path);
245}
246
247int scandir(
248    const char *restrict dir,
249    struct dirent ***restrict namelist,
250    int (*filter)(const struct dirent *),
251    int (*compar)(const struct dirent **, const struct dirent **)
252) {
253    char *relative_path;
254    int dirfd = find_relpath(dir, &relative_path);
255
256    // If we can't find a preopen for it, fail as if we can't find the path.
257    if (dirfd == -1) {
258        errno = ENOENT;
259        return -1;
260    }
261
262    return __wasilibc_nocwd_scandirat(dirfd, relative_path, namelist, filter, compar);
263}
264
265int symlink(const char *target, const char *linkpath) {
266    char *relative_path;
267    int dirfd = find_relpath(linkpath, &relative_path);
268
269    // If we can't find a preopen for it, fail as if we can't find the path.
270    if (dirfd == -1) {
271        errno = ENOENT;
272        return -1;
273    }
274
275    return __wasilibc_nocwd_symlinkat(target, dirfd, relative_path);
276}
277
278int link(const char *old, const char *new) {
279    char *old_relative_path;
280    int old_dirfd = find_relpath_alt(old, &old_relative_path);
281
282    if (old_dirfd != -1) {
283        char *new_relative_path;
284        int new_dirfd = find_relpath(new, &new_relative_path);
285
286        if (new_dirfd != -1)
287            return __wasilibc_nocwd_linkat(old_dirfd, old_relative_path,
288                                           new_dirfd, new_relative_path, 0);
289    }
290
291    // We couldn't find a preopen for it; fail as if we can't find the path.
292    errno = ENOENT;
293    return -1;
294}
295
296int rename(const char *old, const char *new) {
297    char *old_relative_path;
298    int old_dirfd = find_relpath_alt(old, &old_relative_path);
299
300    if (old_dirfd != -1) {
301        char *new_relative_path;
302        int new_dirfd = find_relpath(new, &new_relative_path);
303
304        if (new_dirfd != -1)
305            return __wasilibc_nocwd_renameat(old_dirfd, old_relative_path,
306                                             new_dirfd, new_relative_path);
307    }
308
309    // We couldn't find a preopen for it; fail as if we can't find the path.
310    errno = ENOENT;
311    return -1;
312}
313
314int chmod(const char *path, mode_t mode) {
315    // TODO: We plan to support this eventually in WASI, but not yet.
316    // Meanwhile, we provide a stub so that libc++'s `<filesystem>`
317    // implementation will build unmodified.
318    errno = ENOSYS;
319    return -1;
320}
321
322int fchmod(int fd, mode_t mode) {
323    // TODO: We plan to support this eventually in WASI, but not yet.
324    // Meanwhile, we provide a stub so that libc++'s `<filesystem>`
325    // implementation will build unmodified.
326    errno = ENOSYS;
327    return -1;
328}
329
330int fchmodat(int fd, const char *path, mode_t mode, int flag) {
331    // TODO: We plan to support this eventually in WASI, but not yet.
332    // Meanwhile, we provide a stub so that libc++'s `<filesystem>`
333    // implementation will build unmodified.
334    errno = ENOSYS;
335    return -1;
336}
337
338int statvfs(const char *__restrict path, struct statvfs *__restrict buf) {
339    // TODO: We plan to support this eventually in WASI, but not yet.
340    // Meanwhile, we provide a stub so that libc++'s `<filesystem>`
341    // implementation will build unmodified.
342    errno = ENOSYS;
343    return -1;
344}
345
346int fstatvfs(int fd, struct statvfs *buf) {
347    // TODO: We plan to support this eventually in WASI, but not yet.
348    // Meanwhile, we provide a stub so that libc++'s `<filesystem>`
349    // implementation will build unmodified.
350    errno = ENOSYS;
351    return -1;
352}
353
354// Like `access`, but with `faccessat`'s flags argument.
355int
356__wasilibc_access(const char *path, int mode, int flags)
357{
358    char *relative_path;
359    int dirfd = find_relpath(path, &relative_path);
360
361    // If we can't find a preopen for it, fail as if we can't find the path.
362    if (dirfd == -1) {
363        errno = ENOENT;
364        return -1;
365    }
366
367    return __wasilibc_nocwd_faccessat(dirfd, relative_path,
368                                      mode, flags);
369}
370
371// Like `utimensat`, but without the `at` part.
372int
373__wasilibc_utimens(const char *path, const struct timespec times[2], int flags)
374{
375    char *relative_path;
376    int dirfd = find_relpath(path, &relative_path);
377
378    // If we can't find a preopen for it, fail as if we can't find the path.
379    if (dirfd == -1) {
380        errno = ENOENT;
381        return -1;
382    }
383
384    return __wasilibc_nocwd_utimensat(dirfd, relative_path,
385                                      times, flags);
386}
387
388// Like `stat`, but with `fstatat`'s flags argument.
389int
390__wasilibc_stat(const char *__restrict path, struct stat *__restrict st, int flags)
391{
392    char *relative_path;
393    int dirfd = find_relpath(path, &relative_path);
394
395    // If we can't find a preopen for it, fail as if we can't find the path.
396    if (dirfd == -1) {
397        errno = ENOENT;
398        return -1;
399    }
400
401    return __wasilibc_nocwd_fstatat(dirfd, relative_path, st, flags);
402}
403
404// Like `link`, but with `linkat`'s flags argument.
405int
406__wasilibc_link(const char *oldpath, const char *newpath, int flags)
407{
408    char *old_relative_path;
409    char *new_relative_path;
410    int old_dirfd = find_relpath(oldpath, &old_relative_path);
411    int new_dirfd = find_relpath(newpath, &new_relative_path);
412
413    // If we can't find a preopen for it, fail as if we can't find the path.
414    if (old_dirfd == -1 || new_dirfd == -1) {
415        errno = ENOENT;
416        return -1;
417    }
418
419    return __wasilibc_nocwd_linkat(old_dirfd, old_relative_path,
420                                   new_dirfd, new_relative_path,
421                                   flags);
422}
423
424// Like `__wasilibc_link`, but oldpath is relative to olddirfd.
425int
426__wasilibc_link_oldat(int olddirfd, const char *oldpath, const char *newpath, int flags)
427{
428    char *new_relative_path;
429    int new_dirfd = find_relpath(newpath, &new_relative_path);
430
431    // If we can't find a preopen for it, fail as if we can't find the path.
432    if (new_dirfd == -1) {
433        errno = ENOENT;
434        return -1;
435    }
436
437    return __wasilibc_nocwd_linkat(olddirfd, oldpath,
438                                   new_dirfd, new_relative_path,
439                                   flags);
440}
441
442// Like `__wasilibc_link`, but newpath is relative to newdirfd.
443int
444__wasilibc_link_newat(const char *oldpath, int newdirfd, const char *newpath, int flags)
445{
446    char *old_relative_path;
447    int old_dirfd = find_relpath(oldpath, &old_relative_path);
448
449    // If we can't find a preopen for it, fail as if we can't find the path.
450    if (old_dirfd == -1) {
451        errno = ENOENT;
452        return -1;
453    }
454
455    return __wasilibc_nocwd_linkat(old_dirfd, old_relative_path,
456                                   newdirfd, newpath,
457                                   flags);
458}
459
460// Like `rename`, but from is relative to fromdirfd.
461int
462__wasilibc_rename_oldat(int fromdirfd, const char *from, const char *to)
463{
464    char *to_relative_path;
465    int to_dirfd = find_relpath(to, &to_relative_path);
466
467    // If we can't find a preopen for it, fail as if we can't find the path.
468    if (to_dirfd == -1) {
469        errno = ENOENT;
470        return -1;
471    }
472
473    return __wasilibc_nocwd_renameat(fromdirfd, from, to_dirfd, to_relative_path);
474}
475
476// Like `rename`, but to is relative to todirfd.
477int
478__wasilibc_rename_newat(const char *from, int todirfd, const char *to)
479{
480    char *from_relative_path;
481    int from_dirfd = find_relpath(from, &from_relative_path);
482
483    // If we can't find a preopen for it, fail as if we can't find the path.
484    if (from_dirfd == -1) {
485        errno = ENOENT;
486        return -1;
487    }
488
489    return __wasilibc_nocwd_renameat(from_dirfd, from_relative_path, todirfd, to);
490}