]> git.ozlabs.org Git - ccan/blob - ccan/tal/path/path.c
base64: fix for unsigned chars (e.g. ARM).
[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 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
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
67 static void pushd_destroy(struct path_pushd *pushd)
68 {
69         close(pushd->fd);
70 }
71
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
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
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
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
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! */
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
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
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
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. */
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
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
507 bool path_is_abs(const char *path)
508 {
509         return path[0] == PATH_SEP;
510 }
511
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
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
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
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 }