]>
Commit | Line | Data |
---|---|---|
5b148b61 AC |
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 | ||
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. | |
809e2d36 | 46 | size_t abs_len = strlen(abs); |
5b148b61 AC |
47 | int copy_relative = strcmp(relative_buf, ".") != 0; |
48 | int mid = copy_relative && abs[0] != 0; | |
809e2d36 | 49 | char *new_cwd = malloc(1 + abs_len + mid + (copy_relative ? strlen(relative_buf) : 0) + 1); |
5b148b61 AC |
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) | |
809e2d36 | 57 | new_cwd[1 + abs_len] = '/'; |
5b148b61 | 58 | if (copy_relative) |
809e2d36 | 59 | strcpy(new_cwd + 1 + abs_len + mid, relative_buf); |
5b148b61 AC |
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 | ||
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) { | |
0c65fc5e GS |
98 | char *tmp = realloc(make_absolute_buf, alloc_len); |
99 | if (tmp == NULL) | |
5b148b61 | 100 | return NULL; |
0c65fc5e | 101 | make_absolute_buf = tmp; |
5b148b61 AC |
102 | make_absolute_len = alloc_len; |
103 | } | |
104 | strcpy(make_absolute_buf, __wasilibc_cwd); | |
105 | if (need_slash) | |
106 | strcpy(make_absolute_buf + cwd_len, "/"); | |
107 | strcpy(make_absolute_buf + cwd_len + need_slash, path); | |
108 | return make_absolute_buf; | |
109 | } | |
110 | ||
111 | // Helper function defined only in this object file and weakly referenced from | |
112 | // `preopens.c` and `posix.c` This function isn't necessary unless `chdir` is | |
113 | // pulled in because all paths are otherwise absolute or relative to the root. | |
114 | int __wasilibc_find_relpath_alloc( | |
115 | const char *path, | |
116 | const char **abs_prefix, | |
117 | char **relative_buf, | |
118 | size_t *relative_buf_len, | |
119 | int can_realloc | |
120 | ) { | |
121 | // First, make our path absolute taking the cwd into account. | |
122 | const char *abspath = make_absolute(path); | |
123 | if (abspath == NULL) { | |
124 | errno = ENOMEM; | |
125 | return -1; | |
126 | } | |
127 | ||
128 | // Next use our absolute path and split it. Find the preopened `fd` parent | |
129 | // directory and set `abs_prefix`. Next up we'll be trying to fit `rel` | |
130 | // into `relative_buf`. | |
131 | const char *rel; | |
132 | int fd = __wasilibc_find_abspath(abspath, abs_prefix, &rel); | |
133 | if (fd == -1) | |
134 | return -1; | |
135 | ||
136 | size_t rel_len = strlen(rel); | |
137 | if (*relative_buf_len < rel_len + 1) { | |
138 | if (!can_realloc) { | |
139 | errno = ERANGE; | |
140 | return -1; | |
141 | } | |
142 | char *tmp = realloc(*relative_buf, rel_len + 1); | |
143 | if (tmp == NULL) { | |
144 | errno = ENOMEM; | |
145 | return -1; | |
146 | } | |
147 | *relative_buf = tmp; | |
148 | *relative_buf_len = rel_len + 1; | |
149 | } | |
150 | strcpy(*relative_buf, rel); | |
151 | return fd; | |
152 | } |