mirror of https://github.com/WebAssembly/wasi-libc
Browse Source
* 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! * Amortize mallocs made in syscalls * Avoid size bloat on programs that don't use `chdir` * Add threading compat * Collect `link`/`renameat` second path lookup * Update comments about chdir.c in makefile * Move definition of `__wasilibc_find_relpath_alloc` to header * Expand comments * Document the format of strings a bit more * Fixup a few issues in path logic * Fix GitHub Actionspull/225/head
Alex Crichton
4 years ago
committed by
GitHub
9 changed files with 378 additions and 66 deletions
@ -0,0 +1,151 @@ |
|||
#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 |
|||
#error "chdir doesn't yet support multiple threads" |
|||
#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 len = strlen(abs) + 1; |
|||
int copy_relative = strcmp(relative_buf, ".") != 0; |
|||
int mid = copy_relative && abs[0] != 0; |
|||
char *new_cwd = malloc(len + (copy_relative ? strlen(relative_buf) + mid: 0)); |
|||
if (new_cwd == NULL) { |
|||
errno = ENOMEM; |
|||
return -1; |
|||
} |
|||
new_cwd[0] = '/'; |
|||
strcpy(new_cwd + 1, abs); |
|||
if (mid) |
|||
new_cwd[strlen(abs) + 1] = '/'; |
|||
if (copy_relative) |
|||
strcpy(new_cwd + 1 + mid + strlen(abs), relative_buf); |
|||
|
|||
// 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; |
|||
} |
|||
|
|||
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; |
|||
} |
|||
|
|||
// 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; |
|||
} |
|||
|
|||
// 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; |
|||
size_t alloc_len = cwd_len + path_len + 1 + need_slash; |
|||
if (alloc_len > make_absolute_len) { |
|||
make_absolute_buf = realloc(make_absolute_buf, alloc_len); |
|||
if (make_absolute_buf == NULL) |
|||
return NULL; |
|||
make_absolute_len = alloc_len; |
|||
} |
|||
strcpy(make_absolute_buf, __wasilibc_cwd); |
|||
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; |
|||
} |
@ -0,0 +1,30 @@ |
|||
#include <unistd.h> |
|||
#include <errno.h> |
|||
#include <string.h> |
|||
|
|||
// For threads this needs to synchronize with chdir
|
|||
#ifdef _REENTRANT |
|||
#error "getcwd doesn't yet support multiple threads" |
|||
#endif |
|||
|
|||
char *__wasilibc_cwd = "/"; |
|||
|
|||
char *getcwd(char *buf, size_t size) |
|||
{ |
|||
if (!buf) { |
|||
buf = strdup(__wasilibc_cwd); |
|||
if (!buf) { |
|||
errno = ENOMEM; |
|||
return NULL; |
|||
} |
|||
} else { |
|||
size_t len = strlen(__wasilibc_cwd); |
|||
if (size < strlen(__wasilibc_cwd) + 1) { |
|||
errno = ERANGE; |
|||
return NULL; |
|||
} |
|||
strcpy(buf, __wasilibc_cwd); |
|||
} |
|||
return buf; |
|||
} |
|||
|
Loading…
Reference in new issue