summaryrefslogtreecommitdiffstats
path: root/libc-bottom-half/sources/chdir.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--libc-bottom-half/sources/chdir.c171
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;
+}