From 874df5be4dc05a8099d078f551f3957ec2e3c9b8 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 4 Aug 2020 14:14:36 -0700 Subject: [PATCH] Add basic emulation of getcwd/chdir This commit adds basic emulation of a current working directory to wasi-libc. The `getcwd` and `chdir` symbols are now implemented and available for use. The `getcwd` implementation is pretty simple in that it just copies out of a new global, `__wasilibc_cwd`, which defaults to `"/"`. The `chdir` implementation is much more involved and has more ramification, however. A new function, `make_absolute`, was added to the preopens object. Paths stored in the preopen table are now always stored as absolute paths instead of relative paths, and initial relative paths are interpreted as being relative to `/`. Looking up a path to preopen now always turns it into an absolute path, relative to the current working directory, and an appropriate path is then returned. The signature of `__wasilibc_find_relpath` has changed as well. It now returns two path components, one for the absolute part and one for the relative part. Additionally the relative part is always dynamically allocated since it may no longer be a substring of the original input path. This has been tested lightly against the Rust standard library so far, but I'm not a regular C developer so there's likely a few things to improve! --- expected/wasm32-wasi/defined-symbols.txt | 3 + .../headers/public/wasi/libc-find-relpath.h | 18 +- libc-bottom-half/sources/chdir.c | 61 ++++++ libc-bottom-half/sources/getcwd.c | 25 +++ libc-bottom-half/sources/posix.c | 175 +++++++++++------- libc-bottom-half/sources/preopens.c | 74 +++++--- libc-top-half/musl/include/unistd.h | 6 +- 7 files changed, 270 insertions(+), 92 deletions(-) create mode 100644 libc-bottom-half/sources/chdir.c create mode 100644 libc-bottom-half/sources/getcwd.c diff --git a/expected/wasm32-wasi/defined-symbols.txt b/expected/wasm32-wasi/defined-symbols.txt index 7cab06cb8..79e34ff06 100644 --- a/expected/wasm32-wasi/defined-symbols.txt +++ b/expected/wasm32-wasi/defined-symbols.txt @@ -250,6 +250,7 @@ __uflow __unlist_locked_file __uselocale __utc +__wasilibc_cwd __wasilibc_ensure_environ __wasilibc_environ __wasilibc_environ @@ -367,6 +368,7 @@ ceill cexp cexpf cexpl +chdir cimag cimagf cimagl @@ -578,6 +580,7 @@ getc getc_unlocked getchar getchar_unlocked +getcwd getdate getdate_err getdelim diff --git a/libc-bottom-half/headers/public/wasi/libc-find-relpath.h b/libc-bottom-half/headers/public/wasi/libc-find-relpath.h index 7395ad6da..58d5c8ba0 100644 --- a/libc-bottom-half/headers/public/wasi/libc-find-relpath.h +++ b/libc-bottom-half/headers/public/wasi/libc-find-relpath.h @@ -6,14 +6,22 @@ extern "C" { #endif /** - * Look up the given path in the preopened directory map. If a suitable - * entry is found, return its directory file descriptor, and store the - * computed relative path in *relative_path. + * Look up the given `path`, relative to the cwd, in the preopened directory + * map. If a suitable entry is found, then the file descriptor for that entry + * is returned. Additionally the absolute path of the directory's file + * descriptor is returned in `abs_prefix` and the relative portion that needs + * to be opened is stored in `relative_path`. * - * Returns -1 if no directories were suitable. + * Returns -1 if no directories were suitable or if an allocation error + * happens. + * + * On success the `relative_path` points to a malloc'd string, so you'll + * need to call `free` on it. The `abs_prefix` return does not need to be + * free'd. */ int __wasilibc_find_relpath(const char *path, - const char **__restrict__ relative_path); + const char **__restrict__ abs_prefix, + char **__restrict__ relative_path); #ifdef __cplusplus } diff --git a/libc-bottom-half/sources/chdir.c b/libc-bottom-half/sources/chdir.c new file mode 100644 index 000000000..a398e634f --- /dev/null +++ b/libc-bottom-half/sources/chdir.c @@ -0,0 +1,61 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +char *__wasilibc_cwd = "/"; +static int __wasilibc_cwd_mallocd = 0; + +int chdir(const char *path) +{ + // Find a preopen'd directory as well as a relative path we're anchored + // from which we're changing directories to. + char *relative; + const char *abs; + int parent_fd = __wasilibc_find_relpath(path, &abs, &relative); + if (parent_fd == -1) { + errno = ENOENT; + return -1; + } + + // Make sure that this directory we're accessing is indeed a directory. + struct stat dirinfo; + int ret = fstatat(parent_fd, relative, &dirinfo, 0); + if (ret == -1) { + free(relative); + return -1; + } + if (!S_ISDIR(dirinfo.st_mode)) { + free(relative); + errno = ENOTDIR; + return -1; + } + + // Copy over our new absolute path into `__wasilibc_cwd`. Only copy over + // the relative portion of the path if it's not equal to `.` + size_t len = strlen(abs); + int copy_relative = strcmp(relative, ".") != 0; + char *new_cwd = malloc(len + (copy_relative ? strlen(relative) : 0)); + if (new_cwd == NULL) { + errno = ENOMEM; + return -1; + } + strcpy(new_cwd, abs); + if (copy_relative) + strcpy(new_cwd + strlen(abs), relative); + free(relative); + + // And set our new malloc'd buffer into the global cwd, freeing the + // previous one if necessary. + char *prev_cwd = __wasilibc_cwd; + __wasilibc_cwd = new_cwd; + if (__wasilibc_cwd_mallocd) + free(prev_cwd); + __wasilibc_cwd_mallocd = 1; + return 0; +} diff --git a/libc-bottom-half/sources/getcwd.c b/libc-bottom-half/sources/getcwd.c new file mode 100644 index 000000000..78208721a --- /dev/null +++ b/libc-bottom-half/sources/getcwd.c @@ -0,0 +1,25 @@ +#include +#include +#include + +extern char *__wasilibc_cwd; + +char *getcwd(char *buf, size_t size) +{ + if (!buf) { + buf = strdup(__wasilibc_cwd); + if (!buf) { + errno = ENOMEM; + return -1; + } + } else { + size_t len = strlen(__wasilibc_cwd); + if (size < strlen(__wasilibc_cwd) + 1) { + errno = ERANGE; + return 0; + } + strcpy(buf, __wasilibc_cwd); + } + return buf; +} + diff --git a/libc-bottom-half/sources/posix.c b/libc-bottom-half/sources/posix.c index 64e6bfaa9..b9c1d20a5 100644 --- a/libc-bottom-half/sources/posix.c +++ b/libc-bottom-half/sources/posix.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -18,8 +19,9 @@ int open(const char *path, int oflag, ...) { // See the documentation in libc.h int __wasilibc_open_nomode(const char *path, int oflag) { - const char *relative_path; - int dirfd = __wasilibc_find_relpath(path, &relative_path); + const char *abs; + char *relative_path; + int dirfd = __wasilibc_find_relpath(path, &abs, &relative_path); // If we can't find a preopen for it, indicate that we lack capabilities. if (dirfd == -1) { @@ -27,12 +29,15 @@ int __wasilibc_open_nomode(const char *path, int oflag) { return -1; } - return __wasilibc_openat_nomode(dirfd, relative_path, oflag); + int ret = __wasilibc_openat_nomode(dirfd, relative_path, oflag); + free(relative_path); + return ret; } int access(const char *path, int amode) { - const char *relative_path; - int dirfd = __wasilibc_find_relpath(path, &relative_path); + const char *abs; + char *relative_path; + int dirfd = __wasilibc_find_relpath(path, &abs, &relative_path); // If we can't find a preopen for it, indicate that we lack capabilities. if (dirfd == -1) { @@ -40,7 +45,9 @@ int access(const char *path, int amode) { return -1; } - return faccessat(dirfd, relative_path, amode, 0); + int ret = faccessat(dirfd, relative_path, amode, 0); + free(relative_path); + return ret; } ssize_t readlink( @@ -48,8 +55,9 @@ ssize_t readlink( char *restrict buf, size_t bufsize) { - const char *relative_path; - int dirfd = __wasilibc_find_relpath(path, &relative_path); + const char *abs; + char *relative_path; + int dirfd = __wasilibc_find_relpath(path, &abs, &relative_path); // If we can't find a preopen for it, indicate that we lack capabilities. if (dirfd == -1) { @@ -57,12 +65,15 @@ ssize_t readlink( return -1; } - return readlinkat(dirfd, relative_path, buf, bufsize); + ssize_t ret = readlinkat(dirfd, relative_path, buf, bufsize); + free(relative_path); + return ret; } int stat(const char *restrict path, struct stat *restrict buf) { - const char *relative_path; - int dirfd = __wasilibc_find_relpath(path, &relative_path); + const char *abs; + char *relative_path; + int dirfd = __wasilibc_find_relpath(path, &abs, &relative_path); // If we can't find a preopen for it, indicate that we lack capabilities. if (dirfd == -1) { @@ -70,12 +81,15 @@ int stat(const char *restrict path, struct stat *restrict buf) { return -1; } - return fstatat(dirfd, relative_path, buf, 0); + int ret = fstatat(dirfd, relative_path, buf, 0); + free(relative_path); + return ret; } int lstat(const char *restrict path, struct stat *restrict buf) { - const char *relative_path; - int dirfd = __wasilibc_find_relpath(path, &relative_path); + const char *abs; + char *relative_path; + int dirfd = __wasilibc_find_relpath(path, &abs, &relative_path); // If we can't find a preopen for it, indicate that we lack capabilities. if (dirfd == -1) { @@ -83,12 +97,15 @@ int lstat(const char *restrict path, struct stat *restrict buf) { return -1; } - return fstatat(dirfd, relative_path, buf, AT_SYMLINK_NOFOLLOW); + int ret = fstatat(dirfd, relative_path, buf, AT_SYMLINK_NOFOLLOW); + free(relative_path); + return ret; } int utime(const char *path, const struct utimbuf *times) { - const char *relative_path; - int dirfd = __wasilibc_find_relpath(path, &relative_path); + const char *abs; + char *relative_path; + int dirfd = __wasilibc_find_relpath(path, &abs, &relative_path); // If we can't find a preopen for it, indicate that we lack capabilities. if (dirfd == -1) { @@ -96,18 +113,21 @@ int utime(const char *path, const struct utimbuf *times) { return -1; } - return utimensat(dirfd, relative_path, + int ret = utimensat(dirfd, relative_path, times ? ((struct timespec [2]) { { .tv_sec = times->actime }, { .tv_sec = times->modtime } }) : NULL, 0); + free(relative_path); + return ret; } int unlink(const char *path) { - const char *relative_path; - int dirfd = __wasilibc_find_relpath(path, &relative_path); + const char *abs; + char *relative_path; + int dirfd = __wasilibc_find_relpath(path, &abs, &relative_path); // If we can't find a preopen for it, indicate that we lack capabilities. if (dirfd == -1) { @@ -118,12 +138,15 @@ int unlink(const char *path) { // `unlinkat` imports `__wasi_path_remove_directory` even when // `AT_REMOVEDIR` isn't passed. Instead, use a specialized function which // just imports `__wasi_path_unlink_file`. - return __wasilibc_unlinkat(dirfd, relative_path); + int ret = __wasilibc_unlinkat(dirfd, relative_path); + free(relative_path); + return ret; } int rmdir(const char *path) { - const char *relative_path; - int dirfd = __wasilibc_find_relpath(path, &relative_path); + const char *abs; + char *relative_path; + int dirfd = __wasilibc_find_relpath(path, &abs, &relative_path); // If we can't find a preopen for it, indicate that we lack capabilities. if (dirfd == -1) { @@ -131,12 +154,15 @@ int rmdir(const char *path) { return -1; } - return __wasilibc_rmdirat(dirfd, relative_path); + int ret = __wasilibc_rmdirat(dirfd, relative_path); + free(relative_path); + return ret; } int remove(const char *path) { - const char *relative_path; - int dirfd = __wasilibc_find_relpath(path, &relative_path); + const char *abs; + char *relative_path; + int dirfd = __wasilibc_find_relpath(path, &abs, &relative_path); // If we can't find a preopen for it, indicate that we lack capabilities. if (dirfd == -1) { @@ -154,12 +180,14 @@ int remove(const char *path) { if (errno == ENOTDIR) errno = ENOTCAPABLE; } + free(relative_path); return r; } int mkdir(const char *path, mode_t mode) { - const char *relative_path; - int dirfd = __wasilibc_find_relpath(path, &relative_path); + const char *abs; + char *relative_path; + int dirfd = __wasilibc_find_relpath(path, &abs, &relative_path); // If we can't find a preopen for it, indicate that we lack capabilities. if (dirfd == -1) { @@ -167,12 +195,15 @@ int mkdir(const char *path, mode_t mode) { return -1; } - return mkdirat(dirfd, relative_path, mode); + int ret = mkdirat(dirfd, relative_path, mode); + free(relative_path); + return ret; } DIR *opendir(const char *dirname) { - const char *relative_path; - int dirfd = __wasilibc_find_relpath(dirname, &relative_path); + const char *abs; + char *relative_path; + int dirfd = __wasilibc_find_relpath(dirname, &abs, &relative_path); // If we can't find a preopen for it, indicate that we lack capabilities. if (dirfd == -1) { @@ -180,7 +211,9 @@ DIR *opendir(const char *dirname) { return NULL; } - return opendirat(dirfd, relative_path); + DIR *ret = opendirat(dirfd, relative_path); + free(relative_path); + return ret; } int scandir( @@ -189,8 +222,9 @@ int scandir( int (*filter)(const struct dirent *), int (*compar)(const struct dirent **, const struct dirent **) ) { - const char *relative_path; - int dirfd = __wasilibc_find_relpath(dir, &relative_path); + const char *abs; + char *relative_path; + int dirfd = __wasilibc_find_relpath(dir, &abs, &relative_path); // If we can't find a preopen for it, indicate that we lack capabilities. if (dirfd == -1) { @@ -198,12 +232,15 @@ int scandir( return -1; } - return scandirat(dirfd, relative_path, namelist, filter, compar); + int ret = scandirat(dirfd, relative_path, namelist, filter, compar); + free(relative_path); + return ret; } int symlink(const char *target, const char *linkpath) { - const char *relative_path; - int dirfd = __wasilibc_find_relpath(linkpath, &relative_path); + const char *abs; + char *relative_path; + int dirfd = __wasilibc_find_relpath(linkpath, &abs, &relative_path); // If we can't find a preopen for it, indicate that we lack capabilities. if (dirfd == -1) { @@ -211,41 +248,55 @@ int symlink(const char *target, const char *linkpath) { return -1; } - return symlinkat(target, dirfd, relative_path); + int ret = symlinkat(target, dirfd, relative_path); + free(relative_path); + return ret; } int link(const char *old, const char *new) { - const char *old_relative_path; - int old_dirfd = __wasilibc_find_relpath(old, &old_relative_path); + const char *abs; + char *old_relative_path; + char *new_relative_path; - if (old_dirfd != -1) { - const char *new_relative_path; - int new_dirfd = __wasilibc_find_relpath(new, &new_relative_path); - - if (new_dirfd != -1) - return linkat(old_dirfd, old_relative_path, - new_dirfd, new_relative_path, 0); + int old_dirfd = __wasilibc_find_relpath(old, &abs, &old_relative_path); + // We couldn't find a preopen for it; indicate that we lack capabilities. + if (old_dirfd == -1) { + errno = ENOTCAPABLE; + return -1; + } + int new_dirfd = __wasilibc_find_relpath(new, &abs, &new_relative_path); + if (new_dirfd == -1) { + free(old_relative_path); + errno = ENOTCAPABLE; + return -1; } - // We couldn't find a preopen for it; indicate that we lack capabilities. - errno = ENOTCAPABLE; - return -1; + int ret = linkat(old_dirfd, old_relative_path, new_dirfd, new_relative_path, 0); + free(old_relative_path); + free(new_relative_path); + return ret; } int rename(const char *old, const char *new) { - const char *old_relative_path; - int old_dirfd = __wasilibc_find_relpath(old, &old_relative_path); + const char *abs; + char *old_relative_path; + char *new_relative_path; - if (old_dirfd != -1) { - const char *new_relative_path; - int new_dirfd = __wasilibc_find_relpath(new, &new_relative_path); - - if (new_dirfd != -1) - return renameat(old_dirfd, old_relative_path, - new_dirfd, new_relative_path); + int old_dirfd = __wasilibc_find_relpath(old, &abs, &old_relative_path); + // We couldn't find a preopen for it; indicate that we lack capabilities. + if (old_dirfd == -1) { + errno = ENOTCAPABLE; + return -1; + } + int new_dirfd = __wasilibc_find_relpath(new, &abs, &new_relative_path); + if (new_dirfd == -1) { + free(old_relative_path); + errno = ENOTCAPABLE; + return -1; } - // We couldn't find a preopen for it; indicate that we lack capabilities. - errno = ENOTCAPABLE; - return -1; + int ret = renameat(old_dirfd, old_relative_path, new_dirfd, new_relative_path); + free(old_relative_path); + free(new_relative_path); + return ret; } diff --git a/libc-bottom-half/sources/preopens.c b/libc-bottom-half/sources/preopens.c index fa0e12f99..5e375b808 100644 --- a/libc-bottom-half/sources/preopens.c +++ b/libc-bottom-half/sources/preopens.c @@ -71,15 +71,52 @@ static int resize(void) { return 0; } +extern char *__wasilibc_cwd; + +static char *make_absolute(const char *path) { + // If this path is absolute, then we return it as-is. + if (path[0] == '/') { + return strdup(path); + } + + // If the path is empty, or points to the current directory, then return + // the current directory. + if (path[0] == 0 || !strcmp(path, ".") || !strcmp(path, "./")) { + return strdup(__wasilibc_cwd); + } + + // If the path starts with `./` then we won't be appending that to the cwd. + if (path[0] == '.' && path[1] == '/') + path += 2; + + // Otherwise we'll take the current directory, add a `/`, and then add the + // input `path`. Note that this doesn't do any normalization (like removing + // `/./`). + size_t cwd_len = strlen(__wasilibc_cwd); + size_t path_len = strlen(path); + int need_slash = __wasilibc_cwd[cwd_len - 1] == '/' ? 0 : 1; + char *abspath = malloc(cwd_len + path_len + 1 + need_slash); + if (!abspath) + return abspath; + strcpy(abspath, __wasilibc_cwd); + if (need_slash) + strcpy(abspath + cwd_len, "/"); + strcpy(abspath + cwd_len + need_slash, path); + return abspath; +} + /// Register the given preopened file descriptor under the given path. /// /// This function takes ownership of `prefix`. -static int internal_register_preopened_fd(__wasi_fd_t fd, const char *prefix) { +static int internal_register_preopened_fd(__wasi_fd_t fd, const char *relprefix) { assert_invariants(); if (num_preopens == preopen_capacity && resize() != 0) return -1; + char *prefix = make_absolute(relprefix); + if (prefix == NULL) + return -1; preopens[num_preopens++] = (preopen) { prefix, fd, }; assert_invariants(); @@ -109,41 +146,29 @@ static bool prefix_matches(const char *prefix, size_t prefix_len, const char *pa // See the documentation in libc.h int __wasilibc_register_preopened_fd(int fd, const char *prefix) { - prefix = strdup(prefix); return prefix == NULL ? -1 : internal_register_preopened_fd((__wasi_fd_t)fd, prefix); } // See the documentation in libc-find-relpath.h. -int __wasilibc_find_relpath(const char *path, - const char **restrict relative_path) { +int __wasilibc_find_relpath(const char *relpath, + const char **restrict abs_prefix, + char **restrict relative_path) { assert_invariants(); + char *path = make_absolute(relpath); + if (!path) + return -1; // Search through the preopens table. Iterate in reverse so that more // recently added preopens take precedence over less recently addded ones. size_t match_len = 0; int fd = -1; + *abs_prefix = ""; for (size_t i = num_preopens; i > 0; --i) { const preopen *pre = &preopens[i - 1]; const char *prefix = pre->prefix; size_t len = strlen(prefix); - if (path[0] != '/' && - (path[0] != '.' || (path[1] != '/' && path[1] != '\0'))) - { - // We're matching a relative path that doesn't start with "./" and - // isn't ".". - if (len >= 2 && prefix[0] == '.' && prefix[1] == '/') { - // The preopen starts with "./", so skip that prefix. - prefix += 2; - len -= 2; - } else if (len == 1 && prefix[0] == '.') { - // The preopen is ".", so match it as an empty string. - prefix += 1; - len -= 1; - } - } - // If we haven't had a match yet, or the candidate path is longer than // our current best match's path, and the candidate path is a prefix of // the requested path, take that as the new best path. @@ -152,11 +177,12 @@ int __wasilibc_find_relpath(const char *path, { fd = pre->fd; match_len = len; + *abs_prefix = prefix; } } // The relative path is the substring after the portion that was matched. - const char *computed = path + match_len; + char *computed = path + match_len; // Omit leading slashes in the relative path. while (*computed == '/') @@ -166,7 +192,10 @@ int __wasilibc_find_relpath(const char *path, if (*computed == '\0') computed = "."; - *relative_path = computed; + *relative_path = strdup(computed); + free(path); + if (*relative_path == NULL) + return -1; return fd; } @@ -202,6 +231,7 @@ static void __wasilibc_populate_preopens(void) { if (internal_register_preopened_fd(fd, prefix) != 0) goto software; + free(prefix); break; } diff --git a/libc-top-half/musl/include/unistd.h b/libc-top-half/musl/include/unistd.h index e55f638e9..0eac1cfe9 100644 --- a/libc-top-half/musl/include/unistd.h +++ b/libc-top-half/musl/include/unistd.h @@ -118,11 +118,11 @@ int ftruncate(int, off_t); int access(const char *, int); int faccessat(int, const char *, int, int); -#ifdef __wasilibc_unmodified_upstream /* WASI has no cwd */ -int chdir(const char *); +#ifdef __wasilibc_unmodified_upstream /* WASI has no fchdir */ int fchdir(int); -char *getcwd(char *, size_t); #endif +int chdir(const char *); +char *getcwd(char *, size_t); #ifdef __wasilibc_unmodified_upstream /* WASI has no signals */ unsigned alarm(unsigned);