1 #include <errno.h>
2 #include <sys/stat.h>
3 #include <unistd.h>
4 #include "path.h"
5
make_absolute(char * dst,size_t size,const char * src)6 static bool make_absolute(char *dst, size_t size, const char *src)
7 {
8 size_t pos = 0;
9 if (!path_is_absolute(src)) {
10 if (!getcwd(dst, size)) {
11 return false;
12 }
13 pos = strlen(dst);
14 dst[pos++] = '/';
15 }
16
17 const size_t len = strlen(src);
18 if (pos + len + 1 > size) {
19 errno = ENAMETOOLONG;
20 return false;
21 }
22
23 memcpy(dst + pos, src, len + 1);
24 return true;
25 }
26
remove_double_slashes(char * str)27 static size_t remove_double_slashes(char *str)
28 {
29 size_t d = 0;
30 for (size_t s = 0; str[s]; s++) {
31 if (str[s] != '/' || str[s + 1] != '/') {
32 str[d++] = str[s];
33 }
34 }
35 str[d] = '\0';
36 return d;
37 }
38
39 /*
40 * Canonicalizes path name
41 *
42 * - Replaces double-slashes with one slash
43 * - Removes any "." or ".." path components
44 * - Makes path absolute
45 * - Expands symbolic links
46 * - Checks that all but the last expanded path component are directories
47 * - Last path component is allowed to not exist
48 */
path_absolute(const char * filename)49 char *path_absolute(const char *filename)
50 {
51 unsigned int depth = 0;
52 char buf[8192];
53
54 if (!make_absolute(buf, sizeof(buf), filename)) {
55 return NULL;
56 }
57
58 remove_double_slashes(buf);
59
60 // For each component:
61 // * Remove "."
62 // * Remove ".." and previous component
63 // * If symlink then replace with link destination and start over
64
65 char *sp = buf + 1;
66 while (*sp) {
67 char *ep = strchr(sp, '/');
68 bool last = !ep;
69
70 if (ep) {
71 *ep = 0;
72 }
73 if (sp[0] == '.' && sp[1] == '\0') {
74 if (last) {
75 *sp = 0;
76 break;
77 }
78 memmove(sp, ep + 1, strlen(ep + 1) + 1);
79 continue;
80 }
81 if (sp[0] == '.' && sp[1] == '.' && sp[2] == '\0') {
82 if (sp != buf + 1) {
83 // Not first component, remove previous component
84 sp--;
85 while (sp[-1] != '/') {
86 sp--;
87 }
88 }
89
90 if (last) {
91 *sp = 0;
92 break;
93 }
94 memmove(sp, ep + 1, strlen(ep + 1) + 1);
95 continue;
96 }
97
98 struct stat st;
99 int rc = lstat(buf, &st);
100 if (rc) {
101 if (last && errno == ENOENT) {
102 break;
103 }
104 return NULL;
105 }
106
107 if (S_ISLNK(st.st_mode)) {
108 char target[8192];
109 char tmp[8192];
110 size_t total_len = 0;
111 size_t buf_len = sp - 1 - buf;
112 size_t rest_len = 0;
113 size_t pos = 0;
114 const char *rest = NULL;
115
116 if (!last) {
117 rest = ep + 1;
118 rest_len = strlen(rest);
119 }
120 if (++depth > 8) {
121 errno = ELOOP;
122 return NULL;
123 }
124 ssize_t target_len = readlink(buf, target, sizeof(target));
125 if (target_len < 0) {
126 return NULL;
127 }
128 if (target_len == sizeof(target)) {
129 errno = ENAMETOOLONG;
130 return NULL;
131 }
132 target[target_len] = '\0';
133
134 // Calculate length
135 if (target[0] != '/') {
136 total_len = buf_len + 1;
137 }
138 total_len += target_len;
139 if (rest) {
140 total_len += 1 + rest_len;
141 }
142 if (total_len + 1 > sizeof(tmp)) {
143 errno = ENAMETOOLONG;
144 return NULL;
145 }
146
147 // Build new path
148 if (target[0] != '/') {
149 memcpy(tmp, buf, buf_len);
150 pos += buf_len;
151 tmp[pos++] = '/';
152 }
153 memcpy(tmp + pos, target, target_len);
154 pos += target_len;
155 if (rest) {
156 tmp[pos++] = '/';
157 memcpy(tmp + pos, rest, rest_len);
158 pos += rest_len;
159 }
160 tmp[pos] = '\0';
161 pos = remove_double_slashes(tmp);
162
163 // Restart
164 memcpy(buf, tmp, pos + 1);
165 sp = buf + 1;
166 continue;
167 }
168
169 if (last) {
170 break;
171 }
172
173 if (!S_ISDIR(st.st_mode)) {
174 errno = ENOTDIR;
175 return NULL;
176 }
177
178 *ep = '/';
179 sp = ep + 1;
180 }
181 return xstrdup(buf);
182 }
183
path_component(const char * path,size_t pos)184 static bool path_component(const char *path, size_t pos)
185 {
186 return path[pos] == '\0' || pos == 0 || path[pos - 1] == '/';
187 }
188
relative_filename(const char * f,const char * cwd)189 char *relative_filename(const char *f, const char *cwd)
190 {
191 // Annoying special case
192 if (cwd[1] == '\0') {
193 if (f[1] == '\0') {
194 return xstrdup(f);
195 }
196 return xstrdup(f + 1);
197 }
198
199 // Length of common path
200 size_t clen = 0;
201 while (cwd[clen] && cwd[clen] == f[clen]) {
202 clen++;
203 }
204
205 if (!cwd[clen] && f[clen] == '/') {
206 // cwd = /home/user
207 // abs = /home/user/project-a/file.c
208 // common = /home/user
209 return xstrdup(f + clen + 1);
210 }
211
212 // Common path components
213 if (!path_component(cwd, clen) || !path_component(f, clen)) {
214 while (clen > 0 && f[clen - 1] != '/') {
215 clen--;
216 }
217 }
218
219 // Number of "../" needed
220 size_t dotdot = 1;
221 for (size_t i = clen + 1; cwd[i]; i++) {
222 if (cwd[i] == '/') {
223 dotdot++;
224 }
225 }
226 if (dotdot > 2) {
227 return xstrdup(f);
228 }
229
230 size_t tlen = strlen(f + clen);
231 size_t len = dotdot * 3 + tlen;
232
233 char *filename = xmalloc(len + 1);
234 for (size_t i = 0; i < dotdot; i++) {
235 memcpy(filename + i * 3, "../", 3);
236 }
237 memcpy(filename + dotdot * 3, f + clen, tlen + 1);
238 return filename;
239 }
240