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