diff options
Diffstat (limited to '')
-rw-r--r-- | libc-bottom-half/sources/chdir.c | 171 |
1 files changed, 171 insertions, 0 deletions
diff --git a/libc-bottom-half/sources/chdir.c b/libc-bottom-half/sources/chdir.c new file mode 100644 index 0000000..37c95a4 --- /dev/null +++ b/libc-bottom-half/sources/chdir.c @@ -0,0 +1,171 @@ +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> +#include <wasi/libc-find-relpath.h> +#include <wasi/libc.h> + +#ifdef _REENTRANT +void __wasilibc_cwd_lock(void); +void __wasilibc_cwd_unlock(void); +#else +#define __wasilibc_cwd_lock() (void)0 +#define __wasilibc_cwd_unlock() (void)0 +#endif +extern char *__wasilibc_cwd; +static int __wasilibc_cwd_mallocd = 0; + +int chdir(const char *path) +{ + static char *relative_buf = NULL; + static size_t relative_buf_len = 0; + + // Find a preopen'd directory as well as a relative path we're anchored + // from which we're changing directories to. + const char *abs; + int parent_fd = __wasilibc_find_relpath_alloc(path, &abs, &relative_buf, &relative_buf_len, 1); + if (parent_fd == -1) + return -1; + + // Make sure that this directory we're accessing is indeed a directory. + struct stat dirinfo; + int ret = fstatat(parent_fd, relative_buf, &dirinfo, 0); + if (ret == -1) + return -1; + if (!S_ISDIR(dirinfo.st_mode)) { + errno = ENOTDIR; + return -1; + } + + // Create a string that looks like: + // + // __wasilibc_cwd = "/" + abs + "/" + relative_buf + // + // If `relative_buf` is equal to "." or `abs` is equal to the empty string, + // however, we skip that part and the middle slash. + size_t abs_len = strlen(abs); + int copy_relative = strcmp(relative_buf, ".") != 0; + int mid = copy_relative && abs[0] != 0; + char *new_cwd = malloc(1 + abs_len + mid + (copy_relative ? strlen(relative_buf) : 0) + 1); + if (new_cwd == NULL) { + errno = ENOMEM; + return -1; + } + new_cwd[0] = '/'; + strcpy(new_cwd + 1, abs); + if (mid) + new_cwd[1 + abs_len] = '/'; + if (copy_relative) + strcpy(new_cwd + 1 + abs_len + mid, relative_buf); + + // And set our new malloc'd buffer into the global cwd, freeing the + // previous one if necessary. + __wasilibc_cwd_lock(); + char *prev_cwd = __wasilibc_cwd; + __wasilibc_cwd = new_cwd; + __wasilibc_cwd_unlock(); + if (__wasilibc_cwd_mallocd) + free(prev_cwd); + __wasilibc_cwd_mallocd = 1; + return 0; +} + +static const char *make_absolute(const char *path) { + static char *make_absolute_buf = NULL; + static size_t make_absolute_len = 0; + + // If this path is absolute, then we return it as-is. + if (path[0] == '/') { + return path; + } + +#ifndef _REENTRANT + // 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 __wasilibc_cwd; + } +#endif + + // 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 + // `/./`). + __wasilibc_cwd_lock(); + size_t cwd_len = strlen(__wasilibc_cwd); + size_t path_len = path ? strlen(path) : 0; + __wasilibc_cwd_unlock(); + int need_slash = __wasilibc_cwd[cwd_len - 1] == '/' ? 0 : 1; + size_t alloc_len = cwd_len + path_len + 1 + need_slash; + if (alloc_len > make_absolute_len) { + char *tmp = realloc(make_absolute_buf, alloc_len); + if (tmp == NULL) { + __wasilibc_cwd_unlock(); + return NULL; + } + make_absolute_buf = tmp; + make_absolute_len = alloc_len; + } + strcpy(make_absolute_buf, __wasilibc_cwd); + __wasilibc_cwd_unlock(); + +#ifdef _REENTRANT + if (path[0] == 0 || !strcmp(path, ".") || !strcmp(path, "./")) { + return make_absolute_buf; + } +#endif + + if (need_slash) + strcpy(make_absolute_buf + cwd_len, "/"); + strcpy(make_absolute_buf + cwd_len + need_slash, path); + return make_absolute_buf; +} + +// Helper function defined only in this object file and weakly referenced from +// `preopens.c` and `posix.c` This function isn't necessary unless `chdir` is +// pulled in because all paths are otherwise absolute or relative to the root. +int __wasilibc_find_relpath_alloc( + const char *path, + const char **abs_prefix, + char **relative_buf, + size_t *relative_buf_len, + int can_realloc +) { + // First, make our path absolute taking the cwd into account. + const char *abspath = make_absolute(path); + if (abspath == NULL) { + errno = ENOMEM; + return -1; + } + + // Next use our absolute path and split it. Find the preopened `fd` parent + // directory and set `abs_prefix`. Next up we'll be trying to fit `rel` + // into `relative_buf`. + const char *rel; + int fd = __wasilibc_find_abspath(abspath, abs_prefix, &rel); + if (fd == -1) + return -1; + + size_t rel_len = strlen(rel); + if (*relative_buf_len < rel_len + 1) { + if (!can_realloc) { + errno = ERANGE; + return -1; + } + char *tmp = realloc(*relative_buf, rel_len + 1); + if (tmp == NULL) { + errno = ENOMEM; + return -1; + } + *relative_buf = tmp; + *relative_buf_len = rel_len + 1; + } + strcpy(*relative_buf, rel); + return fd; +} |