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