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