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