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>
15 #define PATH_SEP_STR "/"
16 #define PATH_SEP (PATH_SEP_STR[0])
18 char *path_cwd(const tal_t *ctx)
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))
32 char *path_join(const tal_t *ctx, const char *base, const char *a)
37 if (unlikely(!a) && taken(a)) {
43 if (a[0] == PATH_SEP) {
46 return tal_strdup(ctx, a);
49 if (unlikely(!base) && taken(base))
53 ret = tal_dup_arr(ctx, char, base, len, 1 + strlen(a) + 1);
56 if (ret[len-1] != PATH_SEP)
57 ret[len++] = PATH_SEP;
71 static void pushd_destroy(struct path_pushd *pushd)
76 struct path_pushd *path_pushd(const tal_t *ctx, const char *dir)
78 struct path_pushd *old = tal(ctx, struct path_pushd);
83 if (unlikely(!dir) && taken(dir))
86 if (!tal_add_destructor(old, pushd_destroy))
89 old->fd = open(".", O_RDONLY);
92 else if (chdir(dir) != 0)
101 bool path_popd(struct path_pushd *olddir)
103 bool ok = (fchdir(olddir->fd) == 0);
113 struct path_pushd *path_pushd(const tal_t *ctx, const char *dir)
115 struct path_pushd *old = tal(ctx, struct path_pushd);
120 old->olddir = path_cwd(old);
121 if (unlikely(!old->olddir))
123 else if (unlikely(!dir) && is_taken(dir))
125 else if (chdir(dir) != 0)
134 bool path_popd(struct path_pushd *olddir)
136 bool ok = (chdir(olddir->olddir) == 0);
141 #endif /* !HAVE_FCHDIR */
143 char *path_canon(const tal_t *ctx, const char *a)
146 char *oldcwd, *path, *p;
149 struct path_pushd *olddir;
151 /* A good guess as to size. */
153 if (a[0] != PATH_SEP) {
154 tmpctx = oldcwd = path_cwd(ctx);
157 len += strlen(oldcwd) + strlen(PATH_SEP_STR);
159 path = tal_array(tmpctx, char, len);
163 len = strlen(oldcwd);
164 memcpy(path, oldcwd, len);
165 path[len++] = PATH_SEP;
167 tmpctx = path = tal_array(ctx, char, len);
172 strcpy(path + len, a);
174 p = strrchr(path, PATH_SEP);
177 olddir = path_pushd(tmpctx, path);
181 /* Make OS canonicalize path for us. */
182 path = path_cwd(tmpctx);
186 /* Append rest of old path. */
189 size_t oldlen = tal_array_length(path);
190 if (path[oldlen-1] != PATH_SEP) {
191 /* Include / to append. */
196 path = tal_realloc(NULL, path, char, oldlen+len+1);
199 memcpy(path + oldlen, p, len+1);
202 path = tal_steal(ctx, path);
204 /* This can happen if old cwd is deleted. */
205 if (!path_popd(olddir))
206 path = tal_free(path);
212 if (unlikely(!a) && is_taken(a))
215 path = tal_arr(ctx, char, PATH_MAX);
216 if (path && !realpath(a, path))
217 path = tal_free(path);
225 /* Symlinks make this hard! */
226 char *path_rel(const tal_t *ctx, const char *from, const char *to)
228 char *cfrom, *cto, *ret, *p;
230 size_t common, num_back, i, postlen;
232 /* This frees from if we're supposed to take it. */
233 tmpctx = cfrom = path_canon(ctx, from);
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))
242 strcat(cfrom, PATH_SEP_STR);
245 /* This frees to if we're supposed to take it. */
246 cto = path_canon(tmpctx, to);
252 /* How much is in common? */
253 for (common = i = 0; cfrom[i] && cto[i]; i++) {
254 if (cfrom[i] != cto[i])
256 if (cfrom[i] == PATH_SEP)
260 /* Skip over / if matches end of other path. */
261 if (!cfrom[i] && cto[i] == PATH_SEP) {
264 } else if (!cto[i] && cfrom[i] == PATH_SEP) {
269 /* Normalize so strings point past common area. */
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;
278 /* Nothing left? That's ".". */
279 if (num_back == 0 && postlen == 1) {
280 ret = tal_strdup(ctx, ".");
284 ret = tal_arr(ctx, char,
285 strlen(".." PATH_SEP_STR) * num_back + postlen);
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 / */
294 memcpy(p, cto, postlen);
307 char *path_readlink(const tal_t *ctx, const char *linkname)
309 ssize_t len, maxlen = 64; /* good first guess. */
312 if (unlikely(!linkname) && is_taken(linkname))
315 ret = tal_arr(ctx, char, maxlen + 1);
318 len = readlink(linkname, ret, maxlen);
324 if (!tal_resize(&ret, maxlen *= 2 + 1))
342 char *path_simplify(const tal_t *ctx, const char *path)
344 size_t i, j, start, len;
348 ret = tal_strdup(ctx, path);
352 /* Always need first / if there is one. */
353 if (ret[0] == PATH_SEP)
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++) {
368 /* Empty segment is //; ignore first one. */
372 /* Always ignore slashdot. */
373 if (len == 2 && ret[i] == '.')
376 /* .. => remove previous if there is one, unless symlink. */
377 if (len == 3 && ret[i] == '.' && ret[i+1] == '.') {
381 /* eg. /foo/, foo/ or foo/bar/ */
382 assert(ret[j-1] == PATH_SEP);
385 /* Avoid stepping back over ..! */
387 || strends(ret, PATH_SEP_STR"..")) {
392 if (lstat(ret, &st) == 0
393 && !S_ISLNK(st.st_mode)) {
394 char *sep = strrchr(ret, PATH_SEP);
404 /* nul term in case we're at end */
411 memmove(ret + j, ret + i, len);
412 /* Don't count nul terminator. */
416 /* Empty string created by ../ elimination. */
420 } else if (j > 1 && ret[j-1] == PATH_SEP) {
428 char *path_basename(const tal_t *ctx, const char *path)
433 if (unlikely(!path) && taken(path))
436 sep = strrchr(path, PATH_SEP);
438 return tal_strdup(ctx, path);
440 /* Trailing slashes need to be trimmed. */
444 for (end = sep; end != path; end--)
445 if (*end != PATH_SEP)
448 /* Find *previous* / */
449 for (sep = end; sep >= path && *sep != PATH_SEP; sep--);
451 /* All /? Just return / */
453 ret = tal_strdup(ctx, PATH_SEP_STR);
455 ret = tal_strndup(ctx, sep+1, end - sep);
457 ret = tal_strdup(ctx, sep + 1);
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)
468 char *ret = tal_dup_arr(ctx, char, path, 0, strlen(str)+1);
474 char *path_dirname(const tal_t *ctx, const char *path)
478 if (unlikely(!path) && taken(path))
481 sep = strrchr(path, PATH_SEP);
483 return fixed_string(ctx, ".", path);
485 /* Trailing slashes need to be trimmed. */
489 for (end = sep; end != path; end--)
490 if (*end != PATH_SEP)
493 /* Find *previous* / */
494 for (sep = end; sep > path && *sep != PATH_SEP; sep--);
497 /* In case there are multiple / in a row. */
498 while (sep > path && sep[-1] == PATH_SEP)
502 if (path_is_abs(path))
503 return tal_strndup(ctx, path, 1);
505 return fixed_string(ctx, ".", path);
507 return tal_strndup(ctx, path, sep - path);
510 bool path_is_abs(const char *path)
512 return path[0] == PATH_SEP;
515 bool path_is_file(const char *path)
519 return stat(path, &st) == 0 && S_ISREG(st.st_mode);
522 bool path_is_dir(const char *path)
526 return stat(path, &st) == 0 && S_ISDIR(st.st_mode);
529 char **path_split(const tal_t *ctx, const char *path)
531 bool empty = path && !path[0];
532 char **ret = tal_strsplit(ctx, path, PATH_SEP_STR, STR_NO_EMPTY);
534 /* Handle the "/" case */
535 if (ret && !empty && !ret[0]) {
536 if (!tal_resize(&ret, 2))
540 ret[0] = tal_strdup(ret, PATH_SEP_STR);
549 size_t path_ext_off(const char *path)
551 const char *dot, *base;
553 dot = strrchr(path, '.');
555 base = strrchr(path, PATH_SEP);