#include #include #include #include #include #include #include #include #include #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; }