Skip to content

Commit 5b148b6

Browse files
authored
Add basic emulation of getcwd/chdir (#214)
* 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 Actions
1 parent 9083fe8 commit 5b148b6

File tree

9 files changed

+378
-66
lines changed

9 files changed

+378
-66
lines changed

.github/workflows/main.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ jobs:
2828
rustup update stable
2929
rustup default stable
3030
rustup component add llvm-tools-preview
31-
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"
31+
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
3232
if: matrix.os == 'windows-latest'
3333

3434
- name: Install clang (MacOS)

Makefile

+8
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,14 @@ LIBC_BOTTOM_HALF_SOURCES = $(LIBC_BOTTOM_HALF_DIR)/sources
4444
LIBC_BOTTOM_HALF_ALL_SOURCES = \
4545
$(shell find $(LIBC_BOTTOM_HALF_CLOUDLIBC_SRC) -name \*.c) \
4646
$(shell find $(LIBC_BOTTOM_HALF_SOURCES) -name \*.c)
47+
48+
# FIXME(https://reviews.llvm.org/D85567) - due to a bug in LLD the weak
49+
# references to a function defined in `chdir.c` only work if `chdir.c` is at the
50+
# end of the archive, but once that LLD review lands and propagates into LLVM
51+
# then we don't have to do this.
52+
LIBC_BOTTOM_HALF_ALL_SOURCES := $(filter-out $(LIBC_BOTTOM_HALF_SOURCES)/chdir.c,$(LIBC_BOTTOM_HALF_ALL_SOURCES))
53+
LIBC_BOTTOM_HALF_ALL_SOURCES := $(LIBC_BOTTOM_HALF_ALL_SOURCES) $(LIBC_BOTTOM_HALF_SOURCES)/chdir.c
54+
4755
LIBWASI_EMULATED_MMAN_SOURCES = \
4856
$(shell find $(LIBC_BOTTOM_HALF_DIR)/mman -name \*.c)
4957
LIBWASI_EMULATED_SIGNAL_SOURCES = \

expected/wasm32-wasi/defined-symbols.txt

+5
Original file line numberDiff line numberDiff line change
@@ -250,11 +250,14 @@ __uflow
250250
__unlist_locked_file
251251
__uselocale
252252
__utc
253+
__wasilibc_cwd
253254
__wasilibc_ensure_environ
254255
__wasilibc_environ
255256
__wasilibc_environ
256257
__wasilibc_fd_renumber
258+
__wasilibc_find_abspath
257259
__wasilibc_find_relpath
260+
__wasilibc_find_relpath_alloc
258261
__wasilibc_initialize_environ
259262
__wasilibc_open_nomode
260263
__wasilibc_openat_nomode
@@ -368,6 +371,7 @@ ceill
368371
cexp
369372
cexpf
370373
cexpl
374+
chdir
371375
cimag
372376
cimagf
373377
cimagl
@@ -579,6 +583,7 @@ getc
579583
getc_unlocked
580584
getchar
581585
getchar_unlocked
586+
getcwd
582587
getdate
583588
getdate_err
584589
getdelim

libc-bottom-half/headers/public/wasi/libc-find-relpath.h

+61-4
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,72 @@ extern "C" {
66
#endif
77

88
/**
9-
* Look up the given path in the preopened directory map. If a suitable
10-
* entry is found, return its directory file descriptor, and store the
11-
* computed relative path in *relative_path.
9+
* Look up the given `path`, relative to the cwd, in the preopened directory
10+
* map. If a suitable entry is found, then the file descriptor for that entry
11+
* is returned. Additionally the absolute path of the directory's file
12+
* descriptor is returned in `abs_prefix` and the relative portion that needs
13+
* to be opened is stored in `*relative_path`.
1214
*
13-
* Returns -1 if no directories were suitable.
15+
* The `relative_path` argument must be a pointer to a buffer valid for
16+
* `relative_path_len` bytes, and this may be used as storage for the relative
17+
* portion of the path being returned through `*relative_path`.
18+
*
19+
* See documentation on `__wasilibc_find_abspath` for more info about what the
20+
* paths look like.
21+
*
22+
* Returns -1 on failure. Errno is set to either:
23+
*
24+
* * ENOMEM - failed to allocate memory for internal routines.
25+
* * ENOENT - the `path` could not be found relative to any preopened dir.
26+
* * ERANGE - the `relative_path` buffer is too small to hold the relative path.
1427
*/
1528
int __wasilibc_find_relpath(const char *path,
29+
const char **__restrict__ abs_prefix,
30+
char **relative_path,
31+
size_t relative_path_len);
32+
33+
/**
34+
* Look up the given `path`, which is interpreted as absolute, in the preopened
35+
* directory map. If a suitable entry is found, then the file descriptor for
36+
* that entry is returned. Additionally the relative portion of the path to
37+
* where the fd is opened is returned through `relative_path`, the absolute
38+
* prefix which was matched is stored to `abs_prefix`, and `relative_path` may
39+
* be an interior pointer to the `abspath` string.
40+
*
41+
* The `abs_prefix` returned string will not contain a leading `/`. Note that
42+
* this may be the empty string. Additionally the returned `relative_path` will
43+
* not contain a leading `/`. The `relative_path` return will not return an
44+
* empty string, it will return `"."` instead if it would otherwise do so.
45+
*
46+
* Returns -1 on failure. Errno is set to either:
47+
*
48+
* * ENOMEM - failed to allocate memory for internal routines.
49+
* * ENOENT - the `path` could not be found relative to any preopened dir.
50+
*/
51+
int __wasilibc_find_abspath(const char *abspath,
52+
const char **__restrict__ abs_prefix,
1653
const char **__restrict__ relative_path);
1754

55+
/**
56+
* Same as `__wasilibc_find_relpath`, except that this function will interpret
57+
* `relative` as a malloc'd buffer that will be `realloc`'d to the appropriate
58+
* size to contain the relative path.
59+
*
60+
* Note that this is a weak symbol and if it's not defined you can use
61+
* `__wasilibc_find_relpath`. The weak-nature of this symbols means that if it's
62+
* not otherwise included in the compilation then `chdir` wasn't used an there's
63+
* no need for this symbol.
64+
*
65+
* See documentation on `__wasilibc_find_relpath` for more information.
66+
*/
67+
int __wasilibc_find_relpath_alloc(
68+
const char *path,
69+
const char **abs,
70+
char **relative,
71+
size_t *relative_len,
72+
int can_realloc
73+
) __attribute__((weak));
74+
1875
#ifdef __cplusplus
1976
}
2077
#endif

libc-bottom-half/sources/chdir.c

+151
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
#include <errno.h>
2+
#include <fcntl.h>
3+
#include <limits.h>
4+
#include <stdlib.h>
5+
#include <string.h>
6+
#include <sys/stat.h>
7+
#include <unistd.h>
8+
#include <wasi/libc-find-relpath.h>
9+
#include <wasi/libc.h>
10+
11+
#ifdef _REENTRANT
12+
#error "chdir doesn't yet support multiple threads"
13+
#endif
14+
15+
extern char *__wasilibc_cwd;
16+
static int __wasilibc_cwd_mallocd = 0;
17+
18+
int chdir(const char *path)
19+
{
20+
static char *relative_buf = NULL;
21+
static size_t relative_buf_len = 0;
22+
23+
// Find a preopen'd directory as well as a relative path we're anchored
24+
// from which we're changing directories to.
25+
const char *abs;
26+
int parent_fd = __wasilibc_find_relpath_alloc(path, &abs, &relative_buf, &relative_buf_len, 1);
27+
if (parent_fd == -1)
28+
return -1;
29+
30+
// Make sure that this directory we're accessing is indeed a directory.
31+
struct stat dirinfo;
32+
int ret = fstatat(parent_fd, relative_buf, &dirinfo, 0);
33+
if (ret == -1)
34+
return -1;
35+
if (!S_ISDIR(dirinfo.st_mode)) {
36+
errno = ENOTDIR;
37+
return -1;
38+
}
39+
40+
// Create a string that looks like:
41+
//
42+
// __wasilibc_cwd = "/" + abs + "/" + relative_buf
43+
//
44+
// If `relative_buf` is equal to "." or `abs` is equal to the empty string,
45+
// however, we skip that part and the middle slash.
46+
size_t len = strlen(abs) + 1;
47+
int copy_relative = strcmp(relative_buf, ".") != 0;
48+
int mid = copy_relative && abs[0] != 0;
49+
char *new_cwd = malloc(len + (copy_relative ? strlen(relative_buf) + mid: 0));
50+
if (new_cwd == NULL) {
51+
errno = ENOMEM;
52+
return -1;
53+
}
54+
new_cwd[0] = '/';
55+
strcpy(new_cwd + 1, abs);
56+
if (mid)
57+
new_cwd[strlen(abs) + 1] = '/';
58+
if (copy_relative)
59+
strcpy(new_cwd + 1 + mid + strlen(abs), relative_buf);
60+
61+
// And set our new malloc'd buffer into the global cwd, freeing the
62+
// previous one if necessary.
63+
char *prev_cwd = __wasilibc_cwd;
64+
__wasilibc_cwd = new_cwd;
65+
if (__wasilibc_cwd_mallocd)
66+
free(prev_cwd);
67+
__wasilibc_cwd_mallocd = 1;
68+
return 0;
69+
}
70+
71+
static const char *make_absolute(const char *path) {
72+
static char *make_absolute_buf = NULL;
73+
static size_t make_absolute_len = 0;
74+
75+
// If this path is absolute, then we return it as-is.
76+
if (path[0] == '/') {
77+
return path;
78+
}
79+
80+
// If the path is empty, or points to the current directory, then return
81+
// the current directory.
82+
if (path[0] == 0 || !strcmp(path, ".") || !strcmp(path, "./")) {
83+
return __wasilibc_cwd;
84+
}
85+
86+
// If the path starts with `./` then we won't be appending that to the cwd.
87+
if (path[0] == '.' && path[1] == '/')
88+
path += 2;
89+
90+
// Otherwise we'll take the current directory, add a `/`, and then add the
91+
// input `path`. Note that this doesn't do any normalization (like removing
92+
// `/./`).
93+
size_t cwd_len = strlen(__wasilibc_cwd);
94+
size_t path_len = strlen(path);
95+
int need_slash = __wasilibc_cwd[cwd_len - 1] == '/' ? 0 : 1;
96+
size_t alloc_len = cwd_len + path_len + 1 + need_slash;
97+
if (alloc_len > make_absolute_len) {
98+
make_absolute_buf = realloc(make_absolute_buf, alloc_len);
99+
if (make_absolute_buf == NULL)
100+
return NULL;
101+
make_absolute_len = alloc_len;
102+
}
103+
strcpy(make_absolute_buf, __wasilibc_cwd);
104+
if (need_slash)
105+
strcpy(make_absolute_buf + cwd_len, "/");
106+
strcpy(make_absolute_buf + cwd_len + need_slash, path);
107+
return make_absolute_buf;
108+
}
109+
110+
// Helper function defined only in this object file and weakly referenced from
111+
// `preopens.c` and `posix.c` This function isn't necessary unless `chdir` is
112+
// pulled in because all paths are otherwise absolute or relative to the root.
113+
int __wasilibc_find_relpath_alloc(
114+
const char *path,
115+
const char **abs_prefix,
116+
char **relative_buf,
117+
size_t *relative_buf_len,
118+
int can_realloc
119+
) {
120+
// First, make our path absolute taking the cwd into account.
121+
const char *abspath = make_absolute(path);
122+
if (abspath == NULL) {
123+
errno = ENOMEM;
124+
return -1;
125+
}
126+
127+
// Next use our absolute path and split it. Find the preopened `fd` parent
128+
// directory and set `abs_prefix`. Next up we'll be trying to fit `rel`
129+
// into `relative_buf`.
130+
const char *rel;
131+
int fd = __wasilibc_find_abspath(abspath, abs_prefix, &rel);
132+
if (fd == -1)
133+
return -1;
134+
135+
size_t rel_len = strlen(rel);
136+
if (*relative_buf_len < rel_len + 1) {
137+
if (!can_realloc) {
138+
errno = ERANGE;
139+
return -1;
140+
}
141+
char *tmp = realloc(*relative_buf, rel_len + 1);
142+
if (tmp == NULL) {
143+
errno = ENOMEM;
144+
return -1;
145+
}
146+
*relative_buf = tmp;
147+
*relative_buf_len = rel_len + 1;
148+
}
149+
strcpy(*relative_buf, rel);
150+
return fd;
151+
}

libc-bottom-half/sources/getcwd.c

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#include <unistd.h>
2+
#include <errno.h>
3+
#include <string.h>
4+
5+
// For threads this needs to synchronize with chdir
6+
#ifdef _REENTRANT
7+
#error "getcwd doesn't yet support multiple threads"
8+
#endif
9+
10+
char *__wasilibc_cwd = "/";
11+
12+
char *getcwd(char *buf, size_t size)
13+
{
14+
if (!buf) {
15+
buf = strdup(__wasilibc_cwd);
16+
if (!buf) {
17+
errno = ENOMEM;
18+
return NULL;
19+
}
20+
} else {
21+
size_t len = strlen(__wasilibc_cwd);
22+
if (size < strlen(__wasilibc_cwd) + 1) {
23+
errno = ERANGE;
24+
return NULL;
25+
}
26+
strcpy(buf, __wasilibc_cwd);
27+
}
28+
return buf;
29+
}
30+

0 commit comments

Comments
 (0)