1 /* Licensed under BSD-MIT - see LICENSE file for details */
2 #include <ccan/tal/path/path.h>
3 #include <ccan/str/str.h>
4 #include <ccan/tal/str/str.h>
5 #include <sys/types.h>
6 #include <sys/stat.h>
7 #include <fcntl.h>
8 #include <unistd.h>
9 #include <limits.h>
10 #include <stdlib.h>
11 #include <errno.h>
12 #include <assert.h>
13
path_cwd(const tal_t * ctx)14 char *path_cwd(const tal_t *ctx)
15 {
16 size_t len = 64;
17 char *cwd;
18
19 /* *This* is why people hate C. */
20 cwd = tal_arr(ctx, char, len);
21 while (cwd && !getcwd(cwd, len)) {
22 if (errno != ERANGE || !tal_resize(&cwd, len *= 2))
23 cwd = tal_free(cwd);
24 }
25 return cwd;
26 }
27
path_join(const tal_t * ctx,const char * base,const char * a)28 char *path_join(const tal_t *ctx, const char *base, const char *a)
29 {
30 char *ret = NULL;
31 size_t len;
32
33 if (unlikely(!a) && taken(a)) {
34 if (taken(base))
35 tal_free(base);
36 return NULL;
37 }
38
39 if (a[0] == PATH_SEP) {
40 if (taken(base))
41 tal_free(base);
42 return tal_strdup(ctx, a);
43 }
44
45 if (unlikely(!base) && taken(base))
46 goto out;
47
48 len = strlen(base);
49 ret = tal_dup_arr(ctx, char, base, len, 1 + strlen(a) + 1);
50 if (!ret)
51 goto out;
52 if (len != 0 && ret[len-1] != PATH_SEP)
53 ret[len++] = PATH_SEP;
54 strcpy(ret + len, a);
55
56 out:
57 if (taken(a))
58 tal_free(a);
59 return ret;
60 }
61
62 #if HAVE_FCHDIR
63 struct path_pushd {
64 int fd;
65 };
66
pushd_destroy(struct path_pushd * pushd)67 static void pushd_destroy(struct path_pushd *pushd)
68 {
69 close(pushd->fd);
70 }
71
path_pushd(const tal_t * ctx,const char * dir)72 struct path_pushd *path_pushd(const tal_t *ctx, const char *dir)
73 {
74 struct path_pushd *old = tal(ctx, struct path_pushd);
75
76 if (!old)
77 return NULL;
78
79 if (unlikely(!dir) && taken(dir))
80 return tal_free(old);
81
82 if (!tal_add_destructor(old, pushd_destroy))
83 old = tal_free(old);
84 else {
85 old->fd = open(".", O_RDONLY);
86 if (old->fd < 0)
87 old = tal_free(old);
88 else if (chdir(dir) != 0)
89 old = tal_free(old);
90 }
91
92 if (taken(dir))
93 tal_free(dir);
94 return old;
95 }
96
path_popd(struct path_pushd * olddir)97 bool path_popd(struct path_pushd *olddir)
98 {
99 bool ok = (fchdir(olddir->fd) == 0);
100
101 tal_free(olddir);
102 return ok;
103 }
104 #else
105 struct path_pushd {
106 const char *olddir;
107 };
108
path_pushd(const tal_t * ctx,const char * dir)109 struct path_pushd *path_pushd(const tal_t *ctx, const char *dir)
110 {
111 struct path_pushd *old = tal(ctx, struct path_pushd);
112
113 if (!old)
114 return NULL;
115
116 old->olddir = path_cwd(old);
117 if (unlikely(!old->olddir))
118 old = tal_free(old);
119 else if (unlikely(!dir) && is_taken(dir))
120 old = tal_free(old);
121 else if (chdir(dir) != 0)
122 old = tal_free(old);
123
124 if (taken(dir))
125 tal_free(dir);
126
127 return old;
128 }
129
path_popd(struct path_pushd * olddir)130 bool path_popd(struct path_pushd *olddir)
131 {
132 bool ok = (chdir(olddir->olddir) == 0);
133
134 tal_free(olddir);
135 return ok;
136 }
137 #endif /* !HAVE_FCHDIR */
138
path_canon(const tal_t * ctx,const char * a)139 char *path_canon(const tal_t *ctx, const char *a)
140 {
141 #if 0
142 char *oldcwd, *path, *p;
143 void *tmpctx;
144 size_t len;
145 struct path_pushd *olddir;
146
147 /* A good guess as to size. */
148 len = strlen(a) + 1;
149 if (a[0] != PATH_SEP) {
150 tmpctx = oldcwd = path_cwd(ctx);
151 if (!oldcwd)
152 return NULL;
153 len += strlen(oldcwd) + strlen(PATH_SEP_STR);
154
155 path = tal_array(tmpctx, char, len);
156 if (!path)
157 goto out;
158
159 len = strlen(oldcwd);
160 memcpy(path, oldcwd, len);
161 path[len++] = PATH_SEP;
162 } else {
163 tmpctx = path = tal_array(ctx, char, len);
164 if (!path)
165 return NULL;
166 len = 0;
167 }
168 strcpy(path + len, a);
169
170 p = strrchr(path, PATH_SEP);
171 *p = '\0';
172
173 olddir = path_pushd(tmpctx, path);
174 if (!olddir)
175 goto out;
176
177 /* Make OS canonicalize path for us. */
178 path = path_cwd(tmpctx);
179 if (!path)
180 goto out;
181
182 /* Append rest of old path. */
183 len = strlen(p+1);
184 if (len) {
185 size_t oldlen = tal_array_length(path);
186 if (path[oldlen-1] != PATH_SEP) {
187 /* Include / to append. */
188 *p = PATH_SEP;
189 p--;
190 len++;
191 }
192 path = tal_realloc(NULL, path, char, oldlen+len+1);
193 if (!path)
194 goto out;
195 memcpy(path + oldlen, p, len+1);
196 }
197
198 path = tal_steal(ctx, path);
199 out:
200 /* This can happen if old cwd is deleted. */
201 if (!path_popd(olddir))
202 path = tal_free(path);
203
204 tal_free(tmpctx);
205 return path;
206 #else
207 char *path;
208 if (unlikely(!a) && is_taken(a))
209 path = NULL;
210 else {
211 path = tal_arr(ctx, char, PATH_MAX);
212 if (path && !realpath(a, path))
213 path = tal_free(path);
214 }
215 if (taken(a))
216 tal_free(a);
217 return path;
218 #endif
219 }
220
221 /* Symlinks make this hard! */
path_rel(const tal_t * ctx,const char * from,const char * to)222 char *path_rel(const tal_t *ctx, const char *from, const char *to)
223 {
224 char *cfrom, *cto, *ret, *p;
225 tal_t *tmpctx;
226 size_t common, num_back, i, postlen;
227
228 /* This frees from if we're supposed to take it. */
229 tmpctx = cfrom = path_canon(ctx, from);
230 if (!cfrom)
231 goto fail_take_to;
232
233 /* From is a directory, so we append / to it. */
234 if (!streq(cfrom, PATH_SEP_STR)) {
235 if (!tal_resize(&cfrom, strlen(cfrom)+2))
236 goto fail_take_to;
237 tmpctx = cfrom;
238 strcat(cfrom, PATH_SEP_STR);
239 }
240
241 /* This frees to if we're supposed to take it. */
242 cto = path_canon(tmpctx, to);
243 if (!cto) {
244 ret = NULL;
245 goto out;
246 }
247
248 /* How much is in common? */
249 for (common = i = 0; cfrom[i] && cto[i]; i++) {
250 if (cfrom[i] != cto[i])
251 break;
252 if (cfrom[i] == PATH_SEP)
253 common = i + 1;
254 }
255
256 /* Skip over / if matches end of other path. */
257 if (!cfrom[i] && cto[i] == PATH_SEP) {
258 cto++;
259 common = i;
260 } else if (!cto[i] && cfrom[i] == PATH_SEP) {
261 cfrom++;
262 common = i;
263 }
264
265 /* Normalize so strings point past common area. */
266 cfrom += common;
267 cto += common;
268
269 /* One .. for every path element remaining in 'from', to get
270 * back to common prefix. Then the rest of 'to'. */
271 num_back = strcount(cfrom, PATH_SEP_STR);
272 postlen = strlen(cto) + 1;
273
274 /* Nothing left? That's ".". */
275 if (num_back == 0 && postlen == 1) {
276 ret = tal_strdup(ctx, ".");
277 goto out;
278 }
279
280 ret = tal_arr(ctx, char,
281 strlen(".." PATH_SEP_STR) * num_back + postlen);
282 if (!ret)
283 goto out;
284
285 for (i = 0, p = ret; i < num_back; i++, p += strlen(".." PATH_SEP_STR))
286 memcpy(p, ".." PATH_SEP_STR, strlen(".." PATH_SEP_STR));
287 /* Nothing to append? Trim the final / */
288 if (postlen == 1)
289 p--;
290 memcpy(p, cto, postlen);
291
292 out:
293 tal_free(tmpctx);
294 return ret;
295
296 fail_take_to:
297 if (taken(to))
298 tal_free(to);
299 ret = NULL;
300 goto out;
301 }
302
path_readlink(const tal_t * ctx,const char * linkname)303 char *path_readlink(const tal_t *ctx, const char *linkname)
304 {
305 ssize_t maxlen = 64; /* good first guess. */
306 char *ret = NULL;
307
308 if (unlikely(!linkname) && is_taken(linkname))
309 goto fail;
310
311 ret = tal_arr(ctx, char, maxlen + 1);
312
313 while (ret) {
314 ssize_t len = readlink(linkname, ret, maxlen);
315
316 if (len < 0)
317 goto fail;
318
319 if (len < maxlen) {
320 ret[len] = '\0';
321 break;
322 }
323
324 if (!tal_resize(&ret, maxlen *= 2 + 1))
325 goto fail;
326 }
327
328 out:
329 if (taken(linkname))
330 tal_free(linkname);
331
332 return ret;
333
334 fail:
335 ret = tal_free(ret);
336 goto out;
337 }
338
path_simplify(const tal_t * ctx,const char * path)339 char *path_simplify(const tal_t *ctx, const char *path)
340 {
341 size_t i, j, start, len;
342 char *ret;
343 bool ended = false;
344
345 ret = tal_strdup(ctx, path);
346 if (!ret)
347 return NULL;
348
349 /* Always need first / if there is one. */
350 if (ret[0] == PATH_SEP)
351 start = 1;
352 else
353 start = 0;
354
355 for (i = j = start; !ended; i += len) {
356 /* Get length of this segment, including terminator. */
357 for (len = 0; ret[i+len] != PATH_SEP; len++) {
358 if (!ret[i+len]) {
359 ended = true;
360 break;
361 }
362 }
363 len++;
364
365 /* Empty segment is //; ignore first one. */
366 if (len == 1)
367 continue;
368
369 /* Always ignore slashdot. */
370 if (len == 2 && ret[i] == '.')
371 continue;
372
373 /* .. => remove previous if there is one, unless symlink. */
374 if (len == 3 && ret[i] == '.' && ret[i+1] == '.') {
375 struct stat st;
376
377 if (j > start) {
378 /* eg. /foo/, foo/ or foo/bar/ */
379 assert(ret[j-1] == PATH_SEP);
380 ret[j-1] = '\0';
381
382 /* Avoid stepping back over ..! */
383 if (streq(ret, "..")
384 || strends(ret, PATH_SEP_STR"..")) {
385 ret[j-1] = PATH_SEP;
386 goto copy;
387 }
388
389 if (lstat(ret, &st) == 0
390 && !S_ISLNK(st.st_mode)) {
391 char *sep = strrchr(ret, PATH_SEP);
392 if (sep)
393 j = sep - ret + 1;
394 else
395 j = 0;
396 }
397 continue;
398 } else if (start) {
399 /* /.. => / */
400 j = 1;
401 /* nul term in case we're at end */
402 ret[1] = '\0';
403 continue;
404 }
405 }
406
407 copy:
408 memmove(ret + j, ret + i, len);
409 /* Don't count nul terminator. */
410 j += len - ended;
411 }
412
413 /* Empty string created by ../ elimination. */
414 if (j == 0) {
415 ret[0] = '.';
416 ret[1] = '\0';
417 } else if (j > 1 && ret[j-1] == PATH_SEP) {
418 ret[j-1] = '\0';
419 } else
420 ret[j] = '\0';
421
422 return ret;
423 }
424
path_basename(const tal_t * ctx,const char * path)425 char *path_basename(const tal_t *ctx, const char *path)
426 {
427 const char *sep;
428 char *ret;
429
430 if (unlikely(!path) && taken(path))
431 return NULL;
432
433 sep = strrchr(path, PATH_SEP);
434 if (!sep)
435 return tal_strdup(ctx, path);
436
437 /* Trailing slashes need to be trimmed. */
438 if (!sep[1]) {
439 const char *end;
440
441 for (end = sep; end != path; end--)
442 if (*end != PATH_SEP)
443 break;
444
445 /* Find *previous* / */
446 for (sep = end; sep >= path && *sep != PATH_SEP; sep--);
447
448 /* All /? Just return / */
449 if (end == sep)
450 ret = tal_strdup(ctx, PATH_SEP_STR);
451 else
452 ret = tal_strndup(ctx, sep+1, end - sep);
453 } else
454 ret = tal_strdup(ctx, sep + 1);
455
456 if (taken(path))
457 tal_free(path);
458 return ret;
459 }
460
461 /* This reuses str if we're to take it. */
fixed_string(const tal_t * ctx,const char * str,const char * path)462 static char *fixed_string(const tal_t *ctx,
463 const char *str, const char *path)
464 {
465 char *ret = tal_dup_arr(ctx, char, path, 0, strlen(str)+1);
466 if (ret)
467 strcpy(ret, str);
468 return ret;
469 }
470
path_dirname(const tal_t * ctx,const char * path)471 char *path_dirname(const tal_t *ctx, const char *path)
472 {
473 const char *sep;
474
475 if (unlikely(!path) && taken(path))
476 return NULL;
477
478 sep = strrchr(path, PATH_SEP);
479 if (!sep)
480 return fixed_string(ctx, ".", path);
481
482 /* Trailing slashes need to be trimmed. */
483 if (!sep[1]) {
484 const char *end;
485
486 for (end = sep; end != path; end--)
487 if (*end != PATH_SEP)
488 break;
489
490 /* Find *previous* / */
491 for (sep = end; sep > path && *sep != PATH_SEP; sep--);
492 }
493
494 /* In case there are multiple / in a row. */
495 while (sep > path && sep[-1] == PATH_SEP)
496 sep--;
497
498 if (sep == path) {
499 if (path_is_abs(path))
500 return tal_strndup(ctx, path, 1);
501 else
502 return fixed_string(ctx, ".", path);
503 }
504 return tal_strndup(ctx, path, sep - path);
505 }
506
path_is_abs(const char * path)507 bool path_is_abs(const char *path)
508 {
509 return path[0] == PATH_SEP;
510 }
511
path_is_file(const char * path)512 bool path_is_file(const char *path)
513 {
514 struct stat st;
515
516 return stat(path, &st) == 0 && S_ISREG(st.st_mode);
517 }
518
path_is_dir(const char * path)519 bool path_is_dir(const char *path)
520 {
521 struct stat st;
522
523 return stat(path, &st) == 0 && S_ISDIR(st.st_mode);
524 }
525
path_split(const tal_t * ctx,const char * path)526 char **path_split(const tal_t *ctx, const char *path)
527 {
528 bool empty = path && !path[0];
529 char **ret = tal_strsplit(ctx, path, PATH_SEP_STR, STR_NO_EMPTY);
530
531 /* Handle the "/" case */
532 if (ret && !empty && !ret[0]) {
533 if (!tal_resize(&ret, 2))
534 ret = tal_free(ret);
535 else {
536 ret[1] = NULL;
537 ret[0] = tal_strdup(ret, PATH_SEP_STR);
538 if (!ret[0])
539 ret = tal_free(ret);
540 }
541 }
542
543 return ret;
544 }
545
path_ext_off(const char * path)546 size_t path_ext_off(const char *path)
547 {
548 const char *dot, *base;
549
550 dot = strrchr(path, '.');
551 if (dot) {
552 base = strrchr(path, PATH_SEP);
553 if (!base)
554 base = path;
555 else
556 base++;
557 if (dot > base)
558 return dot - path;
559 }
560 return strlen(path);
561 }
562