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