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