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 
chdir(const char * path)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 
make_absolute(const char * path)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.
__wasilibc_find_relpath_alloc(const char * path,const char ** abs_prefix,char ** relative_buf,size_t * relative_buf_len,int can_realloc)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 }
152