Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add basic emulation of getcwd/chdir #214

Merged
merged 11 commits into from
Nov 30, 2020
16 changes: 8 additions & 8 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ jobs:
run: |
curl -fsSLO https://github.com/llvm/llvm-project/releases/download/llvmorg-10.0.0/LLVM-10.0.0-win64.exe
7z x LLVM-10.0.0-win64.exe -y -o"llvm"
echo "::add-path::$(pwd)/llvm/bin"
echo "::set-env name=WASM_AR::$(pwd)/llvm/bin/llvm-ar.exe"
echo "$(pwd)/llvm/bin" >> $GITHUB_PATH
echo "WASM_AR=$(pwd)/llvm/bin/llvm-ar.exe" >> $GITHUB_ENV
if: matrix.os == 'windows-latest'

- name: Install llvm-nm (Windows)
Expand All @@ -28,25 +28,25 @@ jobs:
rustup update stable
rustup default stable
rustup component add llvm-tools-preview
echo "::set-env name=WASM_NM::$(rustc --print sysroot|sed 's|C:|/c|'|sed 's|\\|/|g')/lib/rustlib/x86_64-pc-windows-msvc/bin/llvm-nm.exe"
echo "WASM_NM=$(rustc --print sysroot|sed 's|C:|/c|'|sed 's|\\|/|g')/lib/rustlib/x86_64-pc-windows-msvc/bin/llvm-nm.exe" >> $GITHUB_ENV
if: matrix.os == 'windows-latest'

- name: Install clang (MacOS)
shell: bash
run: |
curl -sSfL https://github.com/llvm/llvm-project/releases/download/llvmorg-10.0.0/clang+llvm-10.0.0-x86_64-apple-darwin.tar.xz | tar xJf -
export CLANG_DIR=`pwd`/clang+llvm-10.0.0-x86_64-apple-darwin/bin
echo "::add-path::$CLANG_DIR"
echo "::set-env name=WASM_CC::$CLANG_DIR/clang"
echo "$CLANG_DIR" >> $GITHUB_PATH
echo "WASM_CC=$CLANG_DIR/clang" >> $GITHUB_ENV
if: matrix.os == 'macos-latest'

- name: Install clang (Linux)
shell: bash
run: |
curl -sSfL https://github.com/llvm/llvm-project/releases/download/llvmorg-10.0.0/clang+llvm-10.0.0-x86_64-linux-gnu-ubuntu-18.04.tar.xz | tar xJf -
export CLANG_DIR=`pwd`/clang+llvm-10.0.0-x86_64-linux-gnu-ubuntu-18.04/bin
echo "::add-path::$CLANG_DIR"
echo "::set-env name=WASM_CC::$CLANG_DIR/clang"
echo "$CLANG_DIR" >> $GITHUB_PATH
echo "WASM_CC=$CLANG_DIR/clang" >> $GITHUB_ENV
if: matrix.os == 'ubuntu-latest'

- name: Build libc
Expand Down Expand Up @@ -76,7 +76,7 @@ jobs:
- name: Install Rust (macos)
run: |
curl https://sh.rustup.rs | sh -s -- -y
echo "##[add-path]$HOME/.cargo/bin"
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
if: matrix.os == 'macos-latest'
- run: cargo fetch
working-directory: tools/wasi-headers
Expand Down
8 changes: 8 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,14 @@ LIBC_BOTTOM_HALF_SOURCES = $(LIBC_BOTTOM_HALF_DIR)/sources
LIBC_BOTTOM_HALF_ALL_SOURCES = \
$(shell find $(LIBC_BOTTOM_HALF_CLOUDLIBC_SRC) -name \*.c) \
$(shell find $(LIBC_BOTTOM_HALF_SOURCES) -name \*.c)

# FIXME(https://reviews.llvm.org/D85567) - due to a bug in LLD the weak
# references to a function defined in `chdir.c` only work if `chdir.c` is at the
# end of the archive, but once that LLD review lands and propagates into LLVM
# then we don't have to do this.
LIBC_BOTTOM_HALF_ALL_SOURCES := $(filter-out $(LIBC_BOTTOM_HALF_SOURCES)/chdir.c,$(LIBC_BOTTOM_HALF_ALL_SOURCES))
LIBC_BOTTOM_HALF_ALL_SOURCES := $(LIBC_BOTTOM_HALF_ALL_SOURCES) $(LIBC_BOTTOM_HALF_SOURCES)/chdir.c

LIBWASI_EMULATED_MMAN_SOURCES = \
$(shell find $(LIBC_BOTTOM_HALF_DIR)/mman -name \*.c)
LIBWASI_EMULATED_SIGNAL_SOURCES = \
Expand Down
5 changes: 5 additions & 0 deletions expected/wasm32-wasi/defined-symbols.txt
Original file line number Diff line number Diff line change
Expand Up @@ -250,11 +250,14 @@ __uflow
__unlist_locked_file
__uselocale
__utc
__wasilibc_cwd
__wasilibc_ensure_environ
__wasilibc_environ
__wasilibc_environ
__wasilibc_fd_renumber
__wasilibc_find_abspath
__wasilibc_find_relpath
__wasilibc_find_relpath_alloc
__wasilibc_initialize_environ
__wasilibc_open_nomode
__wasilibc_openat_nomode
Expand Down Expand Up @@ -367,6 +370,7 @@ ceill
cexp
cexpf
cexpl
chdir
cimag
cimagf
cimagl
Expand Down Expand Up @@ -578,6 +582,7 @@ getc
getc_unlocked
getchar
getchar_unlocked
getcwd
getdate
getdate_err
getdelim
Expand Down
65 changes: 61 additions & 4 deletions libc-bottom-half/headers/public/wasi/libc-find-relpath.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,72 @@ 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.
* The `relative_path` argument must be a pointer to a buffer valid for
* `relative_path_len` bytes, and this may be used as storage for the relative
* portion of the path being returned through `*relative_path`.
*
* See documentation on `__wasilibc_find_abspath` for more info about what the
* paths look like.
*
* Returns -1 on failure. Errno is set to either:
*
* * ENOMEM - failed to allocate memory for internal routines.
* * ENOENT - the `path` could not be found relative to any preopened dir.
* * ERANGE - the `relative_path` buffer is too small to hold the relative path.
*/
int __wasilibc_find_relpath(const char *path,
const char **__restrict__ abs_prefix,
char **relative_path,
size_t relative_path_len);

/**
* Look up the given `path`, which is interpreted as absolute, in the preopened
* directory map. If a suitable entry is found, then the file descriptor for
* that entry is returned. Additionally the relative portion of the path to
* where the fd is opened is returned through `relative_path`, the absolute
* prefix which was matched is stored to `abs_prefix`, and `relative_path` may
* be an interior pointer to the `abspath` string.
*
* The `abs_prefix` returned string will not contain a leading `/`. Note that
* this may be the empty string. Additionally the returned `relative_path` will
* not contain a leading `/`. The `relative_path` return will not return an
* empty string, it will return `"."` instead if it would otherwise do so.
*
* Returns -1 on failure. Errno is set to either:
*
* * ENOMEM - failed to allocate memory for internal routines.
* * ENOENT - the `path` could not be found relative to any preopened dir.
*/
int __wasilibc_find_abspath(const char *abspath,
const char **__restrict__ abs_prefix,
const char **__restrict__ relative_path);

/**
* Same as `__wasilibc_find_relpath`, except that this function will interpret
* `relative` as a malloc'd buffer that will be `realloc`'d to the appropriate
* size to contain the relative path.
*
* Note that this is a weak symbol and if it's not defined you can use
* `__wasilibc_find_relpath`. The weak-nature of this symbols means that if it's
* not otherwise included in the compilation then `chdir` wasn't used an there's
* no need for this symbol.
*
* See documentation on `__wasilibc_find_relpath` for more information.
*/
int __wasilibc_find_relpath_alloc(
const char *path,
const char **abs,
char **relative,
size_t *relative_len,
int can_realloc
) __attribute__((weak));

#ifdef __cplusplus
}
#endif
Expand Down
151 changes: 151 additions & 0 deletions libc-bottom-half/sources/chdir.c
Original file line number Diff line number Diff line change
@@ -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;
alexcrichton marked this conversation as resolved.
Show resolved Hide resolved

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);
alexcrichton marked this conversation as resolved.
Show resolved Hide resolved
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;
}
30 changes: 30 additions & 0 deletions libc-bottom-half/sources/getcwd.c
Original file line number Diff line number Diff line change
@@ -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