master
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}