Browse Source

tal/path: new module

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>


Header from folded patch 'path-talloc-take.patch':

tal/path: accept take() for arguments.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
Rusty Russell 13 years ago
parent
commit
892f59bdc1

+ 1 - 0
Makefile-ccan

@@ -72,6 +72,7 @@ MODS_NORMAL_WITH_SRC := antithread \
 	str_talloc \
 	str_talloc \
 	take \
 	take \
 	tal \
 	tal \
+	tal/path \
 	tal/str \
 	tal/str \
 	talloc \
 	talloc \
 	talloc_link \
 	talloc_link \

+ 1 - 0
ccan/tal/path/LICENSE

@@ -0,0 +1 @@
+../../../licenses/BSD-MIT

+ 62 - 0
ccan/tal/path/_info

@@ -0,0 +1,62 @@
+#include <string.h>
+#include "config.h"
+
+/**
+ * tal/path - routines to manipulate paths
+ *
+ * This code helps manage paths.
+ *
+ * License: BSD-MIT
+ * Author: Rusty Russell <rusty@rustcorp.com.au>
+ *
+ * Example:
+ *	// Program to print out full path names, recursively.
+ *	#include <ccan/tal/path/path.h>
+ *	#include <sys/types.h>
+ *	#include <dirent.h>
+ *	#include <stdio.h>
+ *	#include <ccan/err/err.h>
+ *
+ *	static void dump(const char *dir)
+ *	{
+ *		struct dirent *di;
+ *		DIR *d = opendir(dir);
+ *		if (!d) {
+ *			warn("Failed to open %s", dir);
+ *			return;
+ *		}
+ *		printf("%s\n", dir);
+ *		while ((di = readdir(d)) != NULL) {
+ *			char *path;
+ *			if (streq(di->d_name, ".") || streq(di->d_name, ".."))
+ *				continue;
+ *			path = path_join(NULL, dir, di->d_name);
+ *			if (path_is_dir(path))
+ *				dump(path);
+ *			tal_free(path);
+ *		}
+ *		closedir(d);
+ *	}
+ *
+ *	int main(void)
+ *	{
+ *		dump(path_cwd(NULL));
+ *		return 0;
+ *	}
+ */
+int main(int argc, char *argv[])
+{
+	/* Expect exactly one argument */
+	if (argc != 2)
+		return 1;
+
+	if (strcmp(argv[1], "depends") == 0) {
+		printf("ccan/str\n");
+		printf("ccan/take\n");
+		printf("ccan/tal\n");
+		printf("ccan/tal/str\n");
+		return 0;
+	}
+
+	return 1;
+}

+ 560 - 0
ccan/tal/path/path.c

@@ -0,0 +1,560 @@
+/* Licensed under BSD-MIT - see LICENSE file for details */
+#include <ccan/tal/path/path.h>
+#include <ccan/str/str.h>
+#include <ccan/take/take.h>
+#include <ccan/tal/str/str.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <assert.h>
+
+#define PATH_SEP_STR "/"
+#define PATH_SEP (PATH_SEP_STR[0])
+
+char *path_cwd(const tal_t *ctx)
+{
+	size_t len = 64;
+	char *cwd;
+
+	/* *This* is why people hate C. */
+	cwd = tal_arr(ctx, char, len);
+	while (cwd && !getcwd(cwd, len)) {
+		if (errno != ERANGE || !tal_resize(&cwd, len *= 2))
+			cwd = tal_free(cwd);
+	}
+	return cwd;
+}
+
+char *path_join(const tal_t *ctx, const char *base, const char *a)
+{
+	char *ret = NULL;
+	size_t len;
+
+	if (unlikely(!a) && taken(a)) {
+		if (taken(base))
+			tal_free(base);
+		return NULL;
+	}
+
+	if (a[0] == PATH_SEP) {
+		if (taken(base))
+			tal_free(base);
+		return tal_strdup(ctx, a);
+	}
+
+	if (unlikely(!base) && taken(base))
+		goto out;
+
+	len = strlen(base);
+	ret = tal_dup(ctx, char, base, len, 1 + strlen(a) + 1);
+	if (!ret)
+		goto out;
+	if (ret[len-1] != PATH_SEP)
+		ret[len++] = PATH_SEP;
+	strcpy(ret + len, a);
+
+out:
+	if (taken(a))
+		tal_free(a);
+	return ret;
+}
+
+#if HAVE_FCHDIR
+struct path_pushd {
+	int fd;
+};
+
+static void pushd_destroy(struct path_pushd *pushd)
+{
+	close(pushd->fd);
+}
+
+struct path_pushd *path_pushd(const tal_t *ctx, const char *dir)
+{
+	struct path_pushd *old = tal(ctx, struct path_pushd);
+
+	if (!old)
+		return NULL;
+
+	if (unlikely(!dir) && taken(dir))
+		return tal_free(old);
+
+	if (!tal_add_destructor(old, pushd_destroy))
+		old = tal_free(old);
+	else {
+		old->fd = open(".", O_RDONLY);
+		if (old->fd < 0)
+			old = tal_free(old);
+		else if (chdir(dir) != 0)
+			old = tal_free(old);
+	}
+
+	if (taken(dir))
+		tal_free(dir);
+	return old;
+}
+
+bool path_popd(struct path_pushd *olddir)
+{
+	bool ok = (fchdir(olddir->fd) == 0);
+
+	tal_free(olddir);
+	return ok;
+}
+#else
+struct path_pushd {
+	const char *olddir;
+};
+
+struct path_pushd *path_pushd(const tal_t *ctx, const char *dir)
+{
+	struct path_pushd *old = tal(ctx, struct path_pushd);
+
+	if (!old)
+		return NULL;
+
+	old->olddir = path_cwd(old);
+	if (unlikely(!old->olddir))
+		old = tal_free(old);
+	else if (unlikely(!dir) && is_taken(dir))
+		old = tal_free(old);
+	else if (chdir(dir) != 0)
+		old = tal_free(old);
+
+	if (taken(dir))
+		tal_free(dir);
+
+	return old;
+}
+
+bool path_popd(struct path_pushd *olddir)
+{
+	bool ok = (chdir(olddir->olddir) == 0);
+
+	tal_free(olddir);
+	return ok;
+}
+#endif /* !HAVE_FCHDIR */
+
+char *path_canon(const tal_t *ctx, const char *a)
+{
+#if 0
+	char *oldcwd, *path, *p;
+	void *tmpctx;
+	size_t len;
+	struct path_pushd *olddir;
+
+	/* A good guess as to size. */
+	len = strlen(a) + 1;
+	if (a[0] != PATH_SEP) {
+		tmpctx = oldcwd = path_cwd(ctx);
+		if (!oldcwd)
+			return NULL;
+		len += strlen(oldcwd) + strlen(PATH_SEP_STR);
+
+		path = tal_array(tmpctx, char, len);
+		if (!path)
+			goto out;
+
+		len = strlen(oldcwd);
+		memcpy(path, oldcwd, len);
+		path[len++] = PATH_SEP;
+	} else {
+		tmpctx = path = tal_array(ctx, char, len);
+		if (!path)
+			return NULL;
+		len = 0;
+	}
+	strcpy(path + len, a);
+
+	p = strrchr(path, PATH_SEP);
+	*p = '\0';
+
+	olddir = path_pushd(tmpctx, path);
+	if (!olddir)
+		goto out;
+
+	/* Make OS canonicalize path for us. */
+	path = path_cwd(tmpctx);
+	if (!path)
+		goto out;
+
+	/* Append rest of old path. */
+	len = strlen(p+1);
+	if (len) {
+		size_t oldlen = tal_array_length(path);
+		if (path[oldlen-1] != PATH_SEP) {
+			/* Include / to append. */
+			*p = PATH_SEP;
+			p--;
+			len++;
+		}
+		path = tal_realloc(NULL, path, char, oldlen+len+1);
+		if (!path)
+			goto out;
+		memcpy(path + oldlen, p, len+1);
+	}
+
+	path = tal_steal(ctx, path);
+out:
+	/* This can happen if old cwd is deleted. */
+	if (!path_popd(olddir))
+		path = tal_free(path);
+
+	tal_free(tmpctx);
+	return path;
+#else
+	char *path;
+	if (unlikely(!a) && is_taken(a))
+		path = NULL;
+	else {
+		path = tal_arr(ctx, char, PATH_MAX);
+		if (path && !realpath(a, path))
+			path = tal_free(path);
+	}
+	if (taken(a))
+		tal_free(a);
+	return path;
+#endif
+}
+
+/* Symlinks make this hard! */
+char *path_rel(const tal_t *ctx, const char *from, const char *to)
+{
+	char *cfrom, *cto, *ret, *p;
+	tal_t *tmpctx;
+	size_t common, num_back, i, postlen;
+
+	/* This frees from if we're supposed to take it. */
+	tmpctx = cfrom = path_canon(ctx, from);
+	if (!cfrom)
+		goto fail_take_to;
+
+	/* From is a directory, so we append / to it. */
+	if (!streq(cfrom, PATH_SEP_STR)) {
+		if (!tal_resize(&cfrom, strlen(cfrom)+2))
+			goto fail_take_to;
+		tmpctx = cfrom;
+		strcat(cfrom, PATH_SEP_STR);
+	}
+
+	/* This frees to if we're supposed to take it. */
+	cto = path_canon(tmpctx, to);
+	if (!cto)
+		goto out;
+
+	/* How much is in common? */
+	for (common = i = 0; cfrom[i] && cto[i]; i++) {
+		if (cfrom[i] != cto[i])
+			break;
+		if (cfrom[i] == PATH_SEP)
+			common = i + 1;
+	}
+
+	/* Skip over / if matches end of other path.  */
+	if (!cfrom[i] && cto[i] == PATH_SEP) {
+		cto++;
+		common = i;
+	} else if (!cto[i] && cfrom[i] == PATH_SEP) {
+		cfrom++;
+		common = i;
+	}
+
+	/* Normalize so strings point past common area. */
+	cfrom += common;
+	cto += common;
+
+	/* One .. for every path element remaining in 'from', to get
+	 * back to common prefix.  Then the rest of 'to'. */
+	num_back = strcount(cfrom, PATH_SEP_STR);
+	postlen = strlen(cto) + 1;
+
+	/* Nothing left?  That's ".". */
+	if (num_back == 0 && postlen == 1) {
+		ret = tal_strdup(ctx, ".");
+		goto out;
+	}
+
+	ret = tal_arr(ctx, char,
+		      strlen(".." PATH_SEP_STR) * num_back + postlen);
+	if (!ret)
+		goto out;
+
+	for (i = 0, p = ret; i < num_back; i++, p += strlen(".." PATH_SEP_STR))
+		memcpy(p, ".." PATH_SEP_STR, strlen(".." PATH_SEP_STR));
+	/* Nothing to append?  Trim the final / */
+	if (postlen == 1)
+		p--;
+	memcpy(p, cto, postlen);
+
+out:
+	tal_free(tmpctx);
+	return ret;
+
+fail_take_to:
+	if (taken(to))
+		tal_free(to);
+	ret = NULL;
+	goto out;
+}
+
+ char *path_readlink(const tal_t *ctx, const char *linkname)
+ {
+	ssize_t len, maxlen = 64; /* good first guess. */
+	char *ret = NULL;
+
+	if (unlikely(!linkname) && is_taken(linkname))
+		goto fail;
+
+	ret = tal_arr(ctx, char, maxlen + 1);
+
+	while (ret) {
+		len = readlink(linkname, ret, maxlen);
+		if (len < 0)
+			goto fail;
+		if (len < maxlen)
+			break;
+
+		if (!tal_resize(&ret, maxlen *= 2 + 1))
+			goto fail;
+	}
+
+	ret[len] = '\0';
+out:
+	if (taken(linkname))
+		tal_free(linkname);
+
+	return ret;
+
+fail:
+	ret = tal_free(ret);
+	goto out;
+}
+
+char *path_simplify(const tal_t *ctx, const char *path)
+{
+	size_t i, j, start, len;
+	char *ret;
+	bool ended = false;
+
+	ret = tal_strdup(ctx, path);
+	if (!ret)
+		return NULL;
+
+	/* Always need first / if there is one. */
+	if (ret[0] == PATH_SEP)
+		start = 1;
+	else
+		start = 0;
+
+	for (i = j = start; !ended; i += len) {
+		/* Get length of this segment, including terminator. */
+		for (len = 0; ret[i+len] != PATH_SEP; len++) {
+			if (!ret[i+len]) {
+				ended = true;
+				break;
+			}
+		}
+		len++;
+
+		/* Empty segment is //; ignore first one. */
+		if (len == 1)
+			continue;
+
+		/* Always ignore slashdot. */
+		if (len == 2 && ret[i] == '.')
+			continue;
+
+		/* .. => remove previous if there is one, unless symlink. */
+		if (len == 3 && ret[i] == '.' && ret[i+1] == '.') {
+			struct stat st;
+
+			if (j > start) {
+				/* eg. /foo/, foo/ or foo/bar/ */
+				assert(ret[j-1] == PATH_SEP);
+				ret[j-1] = '\0';
+
+				/* Avoid stepping back over ..! */
+				if (streq(ret, "..")
+				    || strends(ret, PATH_SEP_STR"..")) {
+					ret[j-1] = PATH_SEP;
+					goto copy;
+				}
+
+				if (lstat(ret, &st) == 0
+				    && !S_ISLNK(st.st_mode)) {
+					char *sep = strrchr(ret, PATH_SEP);
+					if (sep)
+						j = sep - ret + 1;
+					else
+						j = 0;
+				}
+				continue;
+			} else if (start) {
+				/* /.. => / */
+				j = 1;
+				/* nul term in case we're at end */
+				ret[1] = '\0';
+				continue;
+			}
+		}
+
+	copy:
+		memmove(ret + j, ret + i, len);
+		/* Don't count nul terminator. */
+		j += len - ended;
+	}
+
+	/* Empty string created by ../ elimination. */
+	if (j == 0) {
+		ret[0] = '.';
+		ret[1] = '\0';
+	} else if (j > 1 && ret[j-1] == PATH_SEP) {
+		ret[j-1] = '\0';
+	} else
+		ret[j] = '\0';
+
+	return ret;
+}
+
+char *path_basename(const tal_t *ctx, const char *path)
+{
+	const char *sep;
+	char *ret;
+
+	if (unlikely(!path) && taken(path))
+		return NULL;
+
+	sep = strrchr(path, PATH_SEP);
+	if (!sep)
+		return tal_strdup(ctx, path);
+
+	/* Trailing slashes need to be trimmed. */
+	if (!sep[1]) {
+		const char *end;
+
+		for (end = sep; end != path; end--)
+			if (*end != PATH_SEP)
+				break;
+
+		/* Find *previous* / */
+		for (sep = end; sep >= path && *sep != PATH_SEP; sep--);
+
+		/* All /?  Just return / */
+		if (end == sep)
+			ret = tal_strdup(ctx, PATH_SEP_STR);
+		else
+			ret = tal_strndup(ctx, sep+1, end - sep);
+	} else
+		ret = tal_strdup(ctx, sep + 1);
+
+	if (taken(path))
+		tal_free(path);
+	return ret;
+}
+
+/* This reuses str if we're to take it. */
+static char *fixed_string(const tal_t *ctx,
+			  const char *str, const char *path)
+{
+	char *ret = tal_dup(ctx, char, path, 0, strlen(str)+1);
+	if (ret)
+		strcpy(ret, str);
+	return ret;
+}
+
+char *path_dirname(const tal_t *ctx, const char *path)
+{
+	const char *sep;
+
+	if (unlikely(!path) && taken(path))
+		return NULL;
+
+	sep = strrchr(path, PATH_SEP);
+	if (!sep)
+		return fixed_string(ctx, ".", path);
+
+	/* Trailing slashes need to be trimmed. */
+	if (!sep[1]) {
+		const char *end;
+
+		for (end = sep; end != path; end--)
+			if (*end != PATH_SEP)
+				break;
+
+		/* Find *previous* / */
+		for (sep = end; sep > path && *sep != PATH_SEP; sep--);
+	}
+
+	/* In case there are multiple / in a row. */
+	while (sep > path && sep[-1] == PATH_SEP)
+		sep--;
+
+	if (sep == path) {
+		if (path_is_abs(path))
+			return tal_strndup(ctx, path, 1);
+		else
+			return fixed_string(ctx, ".", path);
+	}
+	return tal_strndup(ctx, path, sep - path);
+}
+
+bool path_is_abs(const char *path)
+{
+	return path[0] == PATH_SEP;
+}
+
+bool path_is_file(const char *path)
+{
+	struct stat st;
+
+	return stat(path, &st) == 0 && S_ISREG(st.st_mode);
+}
+
+bool path_is_dir(const char *path)
+{
+	struct stat st;
+
+	return stat(path, &st) == 0 && S_ISDIR(st.st_mode);
+}
+
+char **path_split(const tal_t *ctx, const char *path)
+{
+	bool empty = path && !path[0];
+	char **ret = strsplit(ctx, path, PATH_SEP_STR, STR_NO_EMPTY);
+
+	/* Handle the "/" case */
+	if (ret && !empty && !ret[0]) {
+		if (!tal_resize(&ret, 2))
+			ret = tal_free(ret);
+		else {
+			ret[1] = NULL;
+			ret[0] = tal_strdup(ret, PATH_SEP_STR);
+			if (!ret[0])
+				ret = tal_free(ret);
+		}
+	}
+
+	return ret;
+}
+
+size_t path_ext_off(const char *path)
+{
+	const char *dot, *base;
+
+	dot = strrchr(path, '.');
+	if (dot) {
+		base = strrchr(path, PATH_SEP);
+		if (!base)
+			base = path;
+		else
+			base++;
+		if (dot > base)
+			return dot - path;
+	}
+	return strlen(path);
+}

+ 167 - 0
ccan/tal/path/path.h

@@ -0,0 +1,167 @@
+/* Licensed under BSD-MIT - see LICENSE file for details */
+#ifndef CCAN_PATH_H
+#define CCAN_PATH_H
+#include <ccan/tal/tal.h>
+#include <stdbool.h>
+
+/**
+ * path_cwd - get current directory.
+ * @ctx: the context to tal from
+ *
+ * Returns NULL and sets errno on error.
+ */
+char *path_cwd(const tal_t *ctx);
+
+/**
+ * path_readlink - get a symbolic link contents
+ * @ctx: the context to tal the result from
+ * @link: the link to read (can be take())
+ *
+ * Returns NULL and sets errno on error, otherwise returns nul-terminated
+ * link contents.
+ */
+char *path_readlink(const tal_t *ctx, const char *link);
+
+/**
+ * path_canon - return the canonical absolute pathname.
+ * @ctx: the context to tal the result from.
+ * @a: path to canonicalize (can be take())
+ *
+ * Returns NULL and sets errno on error, otherwise returns an absolute
+ * path with no symbolic links and no extra separators (ie. as per
+ * realpath).
+ */
+char *path_canon(const tal_t *ctx, const char *a);
+
+/**
+ * path_simplify - remove double-/, ./ and some ../, plus trailing /.
+ * @ctx: the context to tal the result from
+ * @a: path to simplify (can be take())
+ *
+ * Unlike path_canon(), this routine does not convert a path to absolute
+ * terms or remove symlinks, but it does neaten it by removing extraneous
+ * parts.
+ */
+char *path_simplify(const tal_t *ctx, const char *a);
+
+/**
+ * path_join - attach one path to another.
+ * @ctx: the context to tal the result from
+ * @base: the path to start at (can be take())
+ * @a: the path to head from there (can be take())
+ *
+ * If @a is an absolute path, return a copy of it.  Otherwise, attach
+ * @a to @base.
+ */
+char *path_join(const tal_t *ctx, const char *base, const char *a);
+
+/**
+ * path_pushd - save old dir and change to a new one.
+ * @ctx: the context to tal the result from
+ * @dir: the directory to return to (can be take())
+ */
+struct path_pushd *path_pushd(const tal_t *ctx, const char *dir);
+
+/**
+ * path_popd - return to old, path_pushd dir.
+ * @olddir: the return from a previous path_pushd.
+ *
+ * Returns false and sets errno if it fails.
+ */
+bool path_popd(struct path_pushd *olddir);
+
+/**
+ * path_rel - get relative path from a to b.
+ * @ctx: the context to tal the result from.
+ * @fromdir: the starting location (can be take())
+ * @to: the destination location (can be take())
+ *
+ * This returns a relative path which leads from @fromdir (assumed to be a
+ * directory) to @to.  If @ctx it TAL_TAKE, frees both @fromdir and @to.
+ *
+ * Example:
+ *	char *path = path_rel(NULL, "/tmp", "/");
+ *	assert(strcmp(path, "..") == 0);
+ */
+char *path_rel(const tal_t *ctx, const char *fromdir, const char *to);
+
+/**
+ * path_basename - get trailing filename part of path
+ * @ctx: the context to tal the result from
+ * @path: the path (can be take())
+ *
+ * This follows SUSv2:
+ *    path         dirname    basename
+ *    "/usr/lib"    "/usr"    "lib"
+ *     "/usr/"       "/"       "usr"
+ *     "usr"         "."       "usr"
+ *     "/"           "/"       "/"
+ *     "."           "."       "."
+ *     ".."          "."       ".."
+ *
+ * See Also:
+ *	path_dirname()
+ */
+char *path_basename(const tal_t *ctx, const char *path);
+
+/**
+ * path_dirname - get the directory part of path
+ * @ctx: the context to tal the result from.
+ * @path: the path (can be take())
+ *
+ * This follows SUSv2.
+ *
+ * See Also:
+ *	path_basename()
+ */
+char *path_dirname(const tal_t *ctx, const char *path);
+
+/**
+ * path_is_abs - is a path absolute?
+ * @path: the path to examine.
+ */
+bool path_is_abs(const char *path);
+
+/**
+ * path_is_file - is a path an existing file (or long to one)?
+ * @path: the path to examine.
+ */
+bool path_is_file(const char *path);
+
+/**
+ * path_is_file - is a path an existing directory (or long to one)?
+ * @path: the path to examine.
+ */
+bool path_is_dir(const char *path);
+
+/**
+ * path_split - split a path into its pathname components
+ * @ctx: the context to tal the result from
+ * @path: the path (can be take())
+ *
+ * This returns the sections of a path, such that joining them with /
+ * will restore the original path.  This means that the resulting
+ * strings will never contain / unless the input path was entirely one
+ * or more "/" characters.
+ *
+ * The final char * in the array will be NULL.
+ *
+ * See Also:
+ *	strjoin()
+ */
+char **path_split(const tal_t *ctx, const char *path);
+
+/**
+ * path_ext_off - get offset of the extension within a pathname.
+ * @path: the path
+ *
+ * This returns the offset of the final . in the pathname (ie.
+ * path[path_ext_off(path)] == '.') or the length of the string
+ * if there is no extension.
+ *
+ * Note that if the only . in the basename is at the start
+ * (eg. /home/person/.bashrc), that is not considered an extension!
+ */
+size_t path_ext_off(const char *path);
+
+#endif /* CCAN_PATH_H */

+ 58 - 0
ccan/tal/path/test/run-basename.c

@@ -0,0 +1,58 @@
+#include <ccan/tal/path/path.h>
+#include <ccan/tal/path/path.c>
+#include <ccan/tap/tap.h>
+
+int main(void)
+{
+	char *path, *ctx = tal_strdup(NULL, "ctx");
+
+	plan_tests(26);
+
+	path = path_basename(ctx, "/usr/lib");
+	ok1(streq(path, "lib"));
+	ok1(tal_parent(path) == ctx);
+	path = path_basename(ctx, "/usr/");
+	ok1(streq(path, "usr"));
+	ok1(tal_parent(path) == ctx);
+	path = path_basename(ctx, "/usr//");
+	ok1(streq(path, "usr"));
+	ok1(tal_parent(path) == ctx);
+	path = path_basename(ctx, "usr");
+	ok1(streq(path, "usr"));
+	ok1(tal_parent(path) == ctx);
+	path = path_basename(ctx, "/");
+	ok1(streq(path, "/"));
+	ok1(tal_parent(path) == ctx);
+	path = path_basename(ctx, "//");
+	ok1(streq(path, "/"));
+	ok1(tal_parent(path) == ctx);
+	path = path_basename(ctx, ".");
+	ok1(streq(path, "."));
+	ok1(tal_parent(path) == ctx);
+	path = path_basename(ctx, "./");
+	ok1(streq(path, "."));
+	ok1(tal_parent(path) == ctx);
+	path = path_basename(ctx, "..");
+	ok1(streq(path, ".."));
+	ok1(tal_parent(path) == ctx);
+	path = path_basename(ctx, "../");
+	ok1(streq(path, ".."));
+	ok1(tal_parent(path) == ctx);
+	tal_free(ctx);
+
+	ctx = tal_strdup(NULL, "ctx");
+	ok1(!tal_first(ctx));
+
+	/* Test take */
+	path = path_basename(ctx, take(tal_strdup(ctx, "..")));
+	ok1(streq(path, ".."));
+	ok1(tal_parent(path) == ctx);
+	ok1(tal_first(ctx) == path && !tal_next(ctx, path));
+	tal_free(path);
+	ok1(path_basename(ctx, take(NULL)) == NULL);
+	ok1(!tal_first(ctx));
+
+	tal_free(ctx);
+
+	return exit_status();
+}

+ 53 - 0
ccan/tal/path/test/run-canon.c

@@ -0,0 +1,53 @@
+#include <ccan/tal/path/path.h>
+#include <ccan/tal/path/path.c>
+#include <ccan/tap/tap.h>
+
+int main(void)
+{
+	char cwd[1024], *path, *path2, *ctx = tal_strdup(NULL, "ctx");
+
+	plan_tests(15);
+
+	if (!getcwd(cwd, sizeof(cwd)))
+		abort();
+
+	unlink("run-canon-link");
+	rmdir("run-canon-foo");
+	if (mkdir("run-canon-foo", 0700) != 0)
+		abort();
+	if (symlink("run-canon-foo", "run-canon-link") != 0)
+		abort();
+
+	path = path_canon(ctx, "run-canon-foo");
+	ok1(tal_parent(path) == ctx);
+	ok1(strends(path, "run-canon-foo"));
+	ok1(strstarts(path, cwd));
+	ok1(path[strlen(cwd)] == PATH_SEP);
+	ok1(strlen(path) == strlen(cwd) + 1 + strlen("run-canon-foo"));
+	tal_free(path);
+
+	ok1(!path_canon(ctx, take(NULL)));
+	ok1(tal_first(ctx) == NULL);
+
+	/* Test take doesn't leak. */
+	ok1(tal_first(ctx) == NULL);
+	path = path_canon(ctx, take(tal_strdup(ctx, "run-canon-foo")));
+	ok1(strends(path, "run-canon-foo"));
+	ok1(strstarts(path, cwd));
+	ok1(path[strlen(cwd)] == PATH_SEP);
+	ok1(strlen(path) == strlen(cwd) + 1 + strlen("run-canon-foo"));
+	ok1(tal_first(ctx) == path && tal_next(ctx, path) == NULL);
+	path2 = path_canon(ctx, "run-canon-link");
+	ok1(streq(path2, path));
+
+	unlink("run-canon-link");
+	if (symlink(".", "run-canon-link") != 0)
+		abort();
+
+	path = path_canon(ctx, "run-canon-link");
+	ok1(streq(path, cwd));
+
+	tal_free(ctx);
+
+	return exit_status();
+}

+ 37 - 0
ccan/tal/path/test/run-cwd.c

@@ -0,0 +1,37 @@
+#include <ccan/tal/path/path.h>
+#include <ccan/tal/path/path.c>
+#include <ccan/tap/tap.h>
+
+int main(void)
+{
+	char path1[1024], *cwd, *ctx = tal_strdup(NULL, "ctx");
+
+	/* This is how many tests you plan to run */
+	plan_tests(5);
+
+	if (!getcwd(path1, sizeof(path1)))
+		abort();
+
+	cwd = path_cwd(ctx);
+	ok1(cwd);
+	ok1(tal_parent(cwd) == ctx);
+	tal_free(cwd);
+
+	rmdir("run-cwd-long-long-long-name/bar-long-long-long-long-name");
+	rmdir("run-cwd-long-long-long-name");
+	if (mkdir("run-cwd-long-long-long-name", 0700) != 0)
+		abort();
+	if (mkdir("run-cwd-long-long-long-name/bar-long-long-long-long-name", 0700) != 0)
+		abort();
+	if (chdir("run-cwd-long-long-long-name/bar-long-long-long-long-name") != 0)
+		abort();
+
+	cwd = path_cwd(ctx);
+	ok1(cwd);
+	ok1(tal_parent(cwd) == ctx);
+	ok1(strends(cwd,
+		    "run-cwd-long-long-long-name/bar-long-long-long-long-name"));
+	tal_free(ctx);
+
+	return exit_status();
+}

+ 58 - 0
ccan/tal/path/test/run-dirname.c

@@ -0,0 +1,58 @@
+#include <ccan/tal/path/path.h>
+#include <ccan/tal/path/path.c>
+#include <ccan/tap/tap.h>
+
+int main(void)
+{
+	char *path, *ctx = tal_strdup(NULL, "ctx");
+
+	plan_tests(26);
+
+	path = path_dirname(ctx, "/usr/lib");
+	ok1(streq(path, "/usr"));
+	ok1(tal_parent(path) == ctx);
+	path = path_dirname(ctx, "/usr/");
+	ok1(streq(path, "/"));
+	ok1(tal_parent(path) == ctx);
+	path = path_dirname(ctx, "/usr//");
+	ok1(streq(path, "/"));
+	ok1(tal_parent(path) == ctx);
+	path = path_dirname(ctx, "usr");
+	ok1(streq(path, "."));
+	ok1(tal_parent(path) == ctx);
+	path = path_dirname(ctx, "/");
+	ok1(streq(path, "/"));
+	ok1(tal_parent(path) == ctx);
+	path = path_dirname(ctx, "//");
+	ok1(streq(path, "/"));
+	ok1(tal_parent(path) == ctx);
+	path = path_dirname(ctx, ".");
+	ok1(streq(path, "."));
+	ok1(tal_parent(path) == ctx);
+	path = path_dirname(ctx, "./");
+	ok1(streq(path, "."));
+	ok1(tal_parent(path) == ctx);
+	path = path_dirname(ctx, "..");
+	ok1(streq(path, "."));
+	ok1(tal_parent(path) == ctx);
+	path = path_dirname(ctx, "../");
+	ok1(streq(path, "."));
+	ok1(tal_parent(path) == ctx);
+	tal_free(ctx);
+
+	ctx = tal_strdup(NULL, "ctx");
+	ok1(!tal_first(ctx));
+
+	/* Test take */
+	path = path_dirname(ctx, take(tal_strdup(ctx, "..")));
+	ok1(streq(path, "."));
+	ok1(tal_parent(path) == ctx);
+	ok1(tal_first(ctx) == path && !tal_next(ctx, path));
+	tal_free(path);
+	ok1(path_dirname(ctx, take(NULL)) == NULL);
+	ok1(!tal_first(ctx));
+
+	tal_free(ctx);
+
+	return exit_status();
+}

+ 19 - 0
ccan/tal/path/test/run-ext_off.c

@@ -0,0 +1,19 @@
+#include <ccan/tal/path/path.h>
+#include <ccan/tal/path/path.c>
+#include <ccan/tap/tap.h>
+
+int main(void)
+{
+	plan_tests(9);
+
+	ok1(path_ext_off("foo") == 3);
+	ok1(path_ext_off(".foo") == 4);
+	ok1(path_ext_off("bar.foo") == 3);
+	ok1(path_ext_off("bar/foo") == 7);
+	ok1(path_ext_off("bar/.foo") == 8);
+	ok1(path_ext_off(".bar/foo") == 8);
+	ok1(path_ext_off("foo.bar/foo") == 11);
+	ok1(path_ext_off("foo.bar/foo.") == 11);
+	ok1(path_ext_off("foo.bar/foo..") == 12);
+	return exit_status();
+}

+ 16 - 0
ccan/tal/path/test/run-is_abs.c

@@ -0,0 +1,16 @@
+#include <ccan/tal/path/path.h>
+#include <ccan/tal/path/path.c>
+#include <ccan/tap/tap.h>
+
+int main(void)
+{
+	plan_tests(5);
+
+	ok1(path_is_abs(PATH_SEP_STR "foo"));
+	ok1(!path_is_abs("foo"));
+	ok1(!path_is_abs("foo" PATH_SEP_STR));
+
+	ok1(path_is_abs(PATH_SEP_STR "foo" PATH_SEP_STR));
+	ok1(path_is_abs(PATH_SEP_STR "."));
+	return exit_status();
+}

+ 41 - 0
ccan/tal/path/test/run-is_dir.c

@@ -0,0 +1,41 @@
+#include <ccan/tal/path/path.h>
+#include <ccan/tal/path/path.c>
+#include <ccan/tap/tap.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+int main(void)
+{
+	char cwd[1024], *path, *ctx = tal_strdup(NULL, "ctx");
+
+	plan_tests(6);
+
+	if (!getcwd(cwd, sizeof(cwd)))
+		abort();
+
+	unlink("run-is_dir-dir-link");
+	unlink("run-is_dir-file-link");
+	unlink("run-is_dir-dir/file");
+	rmdir("run-is_dir-dir");
+	if (mkdir("run-is_dir-dir", 0700) != 0)
+		abort();
+	if (symlink("run-is_dir-dir", "run-is_dir-dir-link") != 0)
+		abort();
+	if (symlink("run-is_dir-dir/file", "run-is_dir-file-link") != 0)
+		abort();
+	close(open("run-is_dir-dir/file", O_WRONLY|O_CREAT, 0600));
+
+	ok1(path_is_dir("run-is_dir-dir-link"));
+	ok1(!path_is_dir("run-is_dir-file-link"));
+	ok1(!path_is_dir("run-is_dir-dir/file"));
+	ok1(path_is_dir("run-is_dir-dir"));
+
+	path = path_join(ctx, cwd, "run-is_dir-dir/file");
+	ok1(!path_is_dir(path));
+	ok1(path_is_dir(cwd));
+
+	tal_free(ctx);
+
+	return exit_status();
+}

+ 42 - 0
ccan/tal/path/test/run-is_file.c

@@ -0,0 +1,42 @@
+#include <ccan/tal/path/path.h>
+#include <ccan/tal/path/path.c>
+#include <ccan/tap/tap.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+int main(void)
+{
+	char cwd[1024], *path, *ctx = tal_strdup(NULL, "ctx");
+
+	plan_tests(7);
+
+	if (!getcwd(cwd, sizeof(cwd)))
+		abort();
+
+	unlink("run-is_file-dir-link");
+	unlink("run-is_file-file-link");
+	unlink("run-is_file-dir/file");
+	rmdir("run-is_file-dir");
+	if (mkdir("run-is_file-dir", 0700) != 0)
+		abort();
+	if (symlink("run-is_file-dir", "run-is_file-dir-link") != 0)
+		abort();
+	if (symlink("run-is_file-dir/file", "run-is_file-file-link") != 0)
+		abort();
+	close(open("run-is_file-dir/file", O_WRONLY|O_CREAT, 0600));
+
+	ok1(!path_is_file("run-is_file-dir-link"));
+	ok1(path_is_file("run-is_file-file-link"));
+	ok1(path_is_file("run-is_file-dir/file"));
+	ok1(!path_is_file("run-is_file-dir"));
+	ok1(!path_is_file("run-is_file-nonexist"));
+
+	path = path_join(ctx, cwd, "run-is_file-dir/file");
+	ok1(path_is_file(path));
+	ok1(!path_is_file(cwd));
+
+	tal_free(ctx);
+
+	return exit_status();
+}

+ 91 - 0
ccan/tal/path/test/run-join.c

@@ -0,0 +1,91 @@
+#include <ccan/tal/path/path.h>
+#include <ccan/tal/path/path.c>
+#include <ccan/tap/tap.h>
+
+int main(void)
+{
+	char *path, *ctx = tal_strdup(NULL, "ctx");
+
+	plan_tests(34);
+
+	path = path_join(ctx, "foo", "bar");
+	ok1(streq(path, "foo/bar"));
+	ok1(tal_parent(path) == ctx);
+	tal_free(path);
+
+	path = path_join(ctx, "foo/", "bar");
+	ok1(streq(path, "foo/bar"));
+	ok1(tal_parent(path) == ctx);
+	tal_free(path);
+
+	path = path_join(ctx, "foo/", "/bar");
+	ok1(streq(path, "/bar"));
+	ok1(tal_parent(path) == ctx);
+	tal_free(path);
+
+	path = path_join(ctx, "foo", "/bar");
+	ok1(streq(path, "/bar"));
+	ok1(tal_parent(path) == ctx);
+	tal_free(path);
+
+	/* Test take */
+	path = path_join(ctx, "foo", take(tal_strdup(ctx, "bar")));
+	ok1(streq(path, "foo/bar"));
+	ok1(tal_parent(path) == ctx);
+	ok1(tal_first(ctx) == path && tal_next(ctx, path) == NULL);
+	tal_free(path);
+
+	path = path_join(ctx, "foo", take(tal_strdup(ctx, "/bar")));
+	ok1(streq(path, "/bar"));
+	ok1(tal_parent(path) == ctx);
+	ok1(tal_first(ctx) == path && tal_next(ctx, path) == NULL);
+	tal_free(path);
+
+	path = path_join(ctx, take(tal_strdup(ctx, "foo")), "bar");
+	ok1(streq(path, "foo/bar"));
+	ok1(tal_parent(path) == ctx);
+	ok1(tal_first(ctx) == path && tal_next(ctx, path) == NULL);
+	tal_free(path);
+
+	path = path_join(ctx, take(tal_strdup(ctx, "foo")), "/bar");
+	ok1(streq(path, "/bar"));
+	ok1(tal_parent(path) == ctx);
+	ok1(tal_first(ctx) == path && tal_next(ctx, path) == NULL);
+	tal_free(path);
+
+	path = path_join(ctx, take(tal_strdup(ctx, "foo")),
+			 take(tal_strdup(ctx, "bar")));
+	ok1(streq(path, "foo/bar"));
+	ok1(tal_parent(path) == ctx);
+	ok1(tal_first(ctx) == path && tal_next(ctx, path) == NULL);
+	tal_free(path);
+
+	path = path_join(ctx, take(tal_strdup(ctx, "foo")),
+			 take(tal_strdup(ctx, "/bar")));
+	ok1(streq(path, "/bar"));
+	ok1(tal_parent(path) == ctx);
+	ok1(tal_first(ctx) == path && tal_next(ctx, path) == NULL);
+	tal_free(path);
+
+	path = path_join(ctx, take(NULL), "bar");
+	ok1(!path);
+	ok1(!tal_first(ctx));
+
+	/* This is allowed to succeed, as first arg unneeded. */
+	path = path_join(ctx, take(NULL), "/bar");
+	ok1(!path || streq(path, "/bar"));
+	tal_free(path);
+	ok1(!tal_first(ctx));
+
+	path = path_join(ctx, "foo", take(NULL));
+	ok1(!path);
+	ok1(!tal_first(ctx));
+
+	path = path_join(ctx, take(NULL), take(NULL));
+	ok1(!path);
+	ok1(!tal_first(ctx));
+
+	tal_free(ctx);
+
+	return exit_status();
+}

+ 75 - 0
ccan/tal/path/test/run-pushd.c

@@ -0,0 +1,75 @@
+#include <ccan/tal/path/path.h>
+#include <ccan/tal/path/path.c>
+#include <ccan/tap/tap.h>
+
+int main(void)
+{
+	struct path_pushd *pd;
+	char path1[1024], path2[1024], *ctx = tal_strdup(NULL, "ctx");
+
+	/* This is how many tests you plan to run */
+	plan_tests(19);
+
+	/* Test pushd/popd */
+	if (!getcwd(path1, sizeof(path1)))
+		abort();
+
+	pd = path_pushd(NULL, "non-existent-dir");
+	ok1(errno == ENOENT);
+	ok1(!pd);
+
+	errno = -100;
+	pd = path_pushd(ctx, take(tal_strdup(ctx, "non-existent-dir")));
+	ok1(errno == ENOENT);
+	ok1(!pd);
+	ok1(!tal_first(ctx));
+
+	errno = -100;
+	pd = path_pushd(ctx, take(NULL));
+	ok1(!pd);
+	ok1(!tal_first(ctx));
+	ok1(errno == -100);
+
+	pd = path_pushd(ctx, "/tmp");
+	ok1(pd);
+	ok1(tal_parent(pd) == ctx);
+
+	if (!getcwd(path2, sizeof(path2)))
+		abort();
+
+	ok1(streq(path2, "/tmp"));
+	path_popd(pd);
+
+	if (!getcwd(path2, sizeof(path2)))
+		abort();
+	ok1(streq(path2, path1));
+
+	pd = path_pushd(ctx, take(tal_strdup(ctx, "/tmp")));
+	ok1(pd);
+	ok1(tal_parent(pd) == ctx);
+	path_popd(pd);
+	if (!getcwd(path2, sizeof(path2)))
+		abort();
+	ok1(streq(path2, path1));
+	ok1(!tal_first(ctx));
+
+	/* Without fchdir, we can't push a path which no longer exists. */
+	if (mkdir("run-pushd-dir", 0700) != 0)
+		abort();
+	if (chdir("run-pushd-dir") != 0)
+		abort();
+	if (rmdir("../run-pushd-dir") != 0)
+		abort();
+
+	pd = path_pushd(ctx, path1);
+#if HAVE_FCHDIR
+	ok1(pd);
+	ok1(path_popd(pd));
+#else
+	ok1(errno == ENOENT);
+	ok1(!pd);
+#endif
+	ok1(!tal_first(ctx));
+	tal_free(ctx);
+	return exit_status();
+}

+ 46 - 0
ccan/tal/path/test/run-readlink.c

@@ -0,0 +1,46 @@
+#include <ccan/tal/path/path.h>
+#include <ccan/tal/path/path.c>
+#include <ccan/tap/tap.h>
+
+int main(void)
+{
+	char *link, *ctx = tal_strdup(NULL, "ctx");
+
+	plan_tests(12);
+
+	unlink("run-readlink-link");
+
+	link = path_readlink(ctx, "run-readlink-link");
+	ok1(errno == ENOENT);
+	ok1(!link);
+
+	link = path_readlink(ctx, take(tal_strdup(ctx, "run-readlink-link")));
+	ok1(errno == ENOENT);
+	ok1(!link);
+	ok1(tal_first(ctx) == NULL);
+
+	if (symlink("/tmp", "run-readlink-link") != 0)
+		abort();
+
+	link = path_readlink(ctx, "run-readlink-link");
+	ok1(tal_parent(link) == ctx);
+	ok1(streq(link, "/tmp"));
+	tal_free(link);
+
+	link = path_readlink(ctx, take(tal_strdup(ctx, "run-readlink-link")));
+	ok1(tal_parent(link) == ctx);
+	ok1(streq(link, "/tmp"));
+	ok1(tal_first(ctx) == link && tal_next(ctx, link) == NULL);
+
+	unlink("run-readlink-link");
+
+	if (symlink("some-really-really-really-really-really-really-really-really-really-really-really-really-really-really-really-really-really-really-really-really-really-really-really-really-really-really-really-really-really-really-long-name", "run-readlink-link") != 0)
+		abort();
+
+	link = path_readlink(ctx, "run-readlink-link");
+	ok1(tal_parent(link) == ctx);
+	ok1(streq(link, "some-really-really-really-really-really-really-really-really-really-really-really-really-really-really-really-really-really-really-really-really-really-really-really-really-really-really-really-really-really-really-long-name"));
+	tal_free(ctx);
+
+	return exit_status();
+}

+ 69 - 0
ccan/tal/path/test/run-rel.c

@@ -0,0 +1,69 @@
+#include <ccan/tal/path/path.h>
+#include <ccan/tal/path/path.c>
+#include <ccan/tap/tap.h>
+
+int main(void)
+{
+	char cwd[1024], *path, *ctx = tal_strdup(NULL, "ctx");
+
+	plan_tests(19);
+
+	if (!getcwd(cwd, sizeof(cwd)))
+		abort();
+
+	unlink("run-rel-link");
+	rmdir("run-rel-foo");
+	if (mkdir("run-rel-foo", 0700) != 0)
+		abort();
+	if (symlink("run-rel-foo", "run-rel-link") != 0)
+		abort();
+
+	path = path_rel(ctx, ".", "run-rel-foo");
+	ok1(streq(path, "run-rel-foo"));
+	ok1(tal_parent(path) == ctx);
+	tal_free(path);
+
+	path = path_rel(ctx, "run-rel-foo", ".");
+	ok1(streq(path, ".."));
+	ok1(tal_parent(path) == ctx);
+	tal_free(path);
+
+	path = path_rel(ctx, ".", "run-rel-link");
+	/* This doesn't specify whether it preserves links. */
+	ok1(streq(path, "run-rel-link") || streq(path, "run-rel-foo"));
+	ok1(tal_parent(path) == ctx);
+	tal_free(path);
+
+	path = path_rel(ctx, "/", ".");
+	ok1(streq(path, cwd + 1));
+	ok1(tal_parent(path) == ctx);
+	tal_free(path);
+
+	path = path_rel(ctx, "run-rel-foo", "run-rel-foo");
+	ok1(streq(path, "."));
+	ok1(tal_parent(path) == ctx);
+	tal_free(path);
+
+	path = path_rel(ctx, take(tal_strdup(ctx, ".")), "run-rel-foo");
+	ok1(streq(path, "run-rel-foo"));
+	ok1(tal_parent(path) == ctx);
+	tal_free(path);
+	ok1(tal_first(ctx) == NULL);
+
+	path = path_rel(ctx, ".", take(tal_strdup(ctx, "run-rel-foo")));
+	ok1(streq(path, "run-rel-foo"));
+	ok1(tal_parent(path) == ctx);
+	tal_free(path);
+	ok1(tal_first(ctx) == NULL);
+
+	path = path_rel(ctx, take(tal_strdup(ctx, ".")),
+			take(tal_strdup(ctx, "run-rel-foo")));
+	ok1(streq(path, "run-rel-foo"));
+	ok1(tal_parent(path) == ctx);
+	tal_free(path);
+	ok1(tal_first(ctx) == NULL);
+
+	tal_free(ctx);
+
+	return exit_status();
+}

+ 244 - 0
ccan/tal/path/test/run-simplify.c

@@ -0,0 +1,244 @@
+#include <ccan/tal/path/path.h>
+#include <ccan/tal/path/path.c>
+#include <ccan/tap/tap.h>
+
+int main(void)
+{
+	char cwd[1024], *path, *ctx = tal_strdup(NULL, "ctx");
+
+	plan_tests(87);
+
+	if (!getcwd(cwd, sizeof(cwd)))
+		abort();
+
+	rmdir("run-simplify-foo");
+	unlink("run-simplify-link");
+	if (mkdir("run-simplify-foo", 0700) != 0)
+		abort();
+	if (symlink("run-simplify-foo", "run-simplify-link") != 0)
+		abort();
+
+	/* Handling of . and .. */
+	path = path_simplify(ctx, ".");
+	ok1(streq(path, "."));
+	ok1(tal_parent(path) == ctx);
+	tal_free(path);
+
+	path = path_simplify(ctx, "./");
+	ok1(streq(path, "."));
+	ok1(tal_parent(path) == ctx);
+	tal_free(path);
+
+	path = path_simplify(ctx, "..");
+	ok1(streq(path, ".."));
+	ok1(tal_parent(path) == ctx);
+	tal_free(path);
+
+	path = path_simplify(ctx, "../");
+	ok1(streq(path, ".."));
+	ok1(tal_parent(path) == ctx);
+	tal_free(path);
+
+	path = path_simplify(ctx, "./..");
+	ok1(streq(path, ".."));
+	ok1(tal_parent(path) == ctx);
+	tal_free(path);
+
+	path = path_simplify(ctx, "./../");
+	ok1(streq(path, ".."));
+	ok1(tal_parent(path) == ctx);
+	tal_free(path);
+
+	path = path_simplify(ctx, "./../.");
+	ok1(streq(path, ".."));
+	ok1(tal_parent(path) == ctx);
+	tal_free(path);
+
+	path = path_simplify(ctx, "./.././");
+	ok1(streq(path, ".."));
+	ok1(tal_parent(path) == ctx);
+	tal_free(path);
+
+	path = path_simplify(ctx, "./../..");
+	ok1(streq(path, "../.."));
+	ok1(tal_parent(path) == ctx);
+	tal_free(path);
+
+	path = path_simplify(ctx, "./../../");
+	ok1(streq(path, "../.."));
+	ok1(tal_parent(path) == ctx);
+	tal_free(path);
+
+	/* Handling of /. and /.. */
+	path = path_simplify(ctx, "/");
+	ok1(streq(path, "/"));
+	ok1(tal_parent(path) == ctx);
+	tal_free(path);
+
+	path = path_simplify(ctx, "//");
+	ok1(streq(path, "/"));
+	ok1(tal_parent(path) == ctx);
+	tal_free(path);
+
+	path = path_simplify(ctx, "/.");
+	ok1(streq(path, "/"));
+	ok1(tal_parent(path) == ctx);
+	tal_free(path);
+
+	path = path_simplify(ctx, "/./");
+	ok1(streq(path, "/"));
+	ok1(tal_parent(path) == ctx);
+	tal_free(path);
+
+	path = path_simplify(ctx, "/..");
+	ok1(streq(path, "/"));
+	ok1(tal_parent(path) == ctx);
+	tal_free(path);
+
+	path = path_simplify(ctx, "/../");
+	ok1(streq(path, "/"));
+	ok1(tal_parent(path) == ctx);
+	tal_free(path);
+
+	path = path_simplify(ctx, "/./..");
+	ok1(streq(path, "/"));
+	ok1(tal_parent(path) == ctx);
+	tal_free(path);
+
+	path = path_simplify(ctx, "/./../");
+	ok1(streq(path, "/"));
+	ok1(tal_parent(path) == ctx);
+	tal_free(path);
+
+	path = path_simplify(ctx, "/./../.");
+	ok1(streq(path, "/"));
+	ok1(tal_parent(path) == ctx);
+	tal_free(path);
+
+	path = path_simplify(ctx, "/./.././");
+	ok1(streq(path, "/"));
+	ok1(tal_parent(path) == ctx);
+	tal_free(path);
+
+	path = path_simplify(ctx, "/./../..");
+	ok1(streq(path, "/"));
+	ok1(tal_parent(path) == ctx);
+	tal_free(path);
+
+	path = path_simplify(ctx, "/./../../");
+	ok1(streq(path, "/"));
+	ok1(tal_parent(path) == ctx);
+	tal_free(path);
+
+	/* Don't trace back over a symlink link */
+	path = path_simplify(ctx, "run-simplify-foo");
+	ok1(streq(path, "run-simplify-foo"));
+	ok1(tal_parent(path) == ctx);
+	tal_free(path);
+
+	path = path_simplify(ctx, "./run-simplify-foo");
+	ok1(streq(path, "run-simplify-foo"));
+	ok1(tal_parent(path) == ctx);
+	tal_free(path);
+
+	path = path_simplify(ctx, "./run-simplify-foo/.");
+	ok1(streq(path, "run-simplify-foo"));
+	ok1(tal_parent(path) == ctx);
+	tal_free(path);
+
+	path = path_simplify(ctx, "run-simplify-link");
+	ok1(streq(path, "run-simplify-link"));
+	ok1(tal_parent(path) == ctx);
+	tal_free(path);
+
+	path = path_simplify(ctx, "./run-simplify-link");
+	ok1(streq(path, "run-simplify-link"));
+	ok1(tal_parent(path) == ctx);
+	tal_free(path);
+
+	path = path_simplify(ctx, "./run-simplify-link/.");
+	ok1(streq(path, "run-simplify-link"));
+	ok1(tal_parent(path) == ctx);
+	tal_free(path);
+
+	path = path_simplify(ctx, "run-simplify-foo/..");
+	ok1(streq(path, "."));
+	ok1(tal_parent(path) == ctx);
+	tal_free(path);
+
+	path = path_simplify(ctx, "run-simplify-foo//..");
+	ok1(streq(path, "."));
+	ok1(tal_parent(path) == ctx);
+	tal_free(path);
+
+	path = path_simplify(ctx, "run-simplify-foo//../");
+	ok1(streq(path, "."));
+	ok1(tal_parent(path) == ctx);
+	tal_free(path);
+
+	/* This is expected to be a real directory. */
+	path = path_simplify(ctx, "/tmp");
+	ok1(streq(path, "/tmp"));
+	ok1(tal_parent(path) == ctx);
+	tal_free(path);
+
+	path = path_simplify(ctx, "/tmp/");
+	ok1(streq(path, "/tmp"));
+	ok1(tal_parent(path) == ctx);
+	tal_free(path);
+
+	path = path_simplify(ctx, "/tmp/.");
+	ok1(streq(path, "/tmp"));
+	ok1(tal_parent(path) == ctx);
+	tal_free(path);
+
+	path = path_simplify(ctx, "/./tmp/.");
+	ok1(streq(path, "/tmp"));
+	ok1(tal_parent(path) == ctx);
+	tal_free(path);
+
+	path = path_simplify(ctx, "/../tmp/.");
+	ok1(streq(path, "/tmp"));
+	ok1(tal_parent(path) == ctx);
+	tal_free(path);
+
+	path = path_simplify(ctx, "/tmp/..");
+	ok1(streq(path, "/"));
+	ok1(tal_parent(path) == ctx);
+	tal_free(path);
+
+	path = path_simplify(ctx, "/tmp/../");
+	ok1(streq(path, "/"));
+	ok1(tal_parent(path) == ctx);
+	tal_free(path);
+
+	path = path_simplify(ctx, "/tmp/../tmp");
+	ok1(streq(path, "/tmp"));
+	ok1(tal_parent(path) == ctx);
+	tal_free(path);
+
+	path = path_simplify(ctx, "/tmp/../tmp/");
+	ok1(streq(path, "/tmp"));
+	ok1(tal_parent(path) == ctx);
+	tal_free(path);
+
+	path = path_simplify(ctx, "/tmp/../tmp/.");
+	ok1(streq(path, "/tmp"));
+	ok1(tal_parent(path) == ctx);
+	tal_free(path);
+
+	/* take tests */
+	path = path_simplify(ctx, take(tal_strdup(ctx, "/tmp/../tmp/.")));
+	ok1(streq(path, "/tmp"));
+	ok1(tal_parent(path) == ctx);
+	tal_free(path);
+	ok1(tal_first(ctx) == NULL);
+
+	path = path_simplify(ctx, take(NULL));
+	ok1(!path);
+	ok1(tal_first(ctx) == NULL);
+
+	tal_free(ctx);
+
+	return exit_status();
+}

+ 105 - 0
ccan/tal/path/test/run-split.c

@@ -0,0 +1,105 @@
+#include <ccan/tal/path/path.h>
+#include <ccan/tal/path/path.c>
+#include <ccan/tap/tap.h>
+
+int main(void)
+{
+	char *ctx = tal_strdup(NULL, "ctx"), **split;
+
+	plan_tests(46);
+
+	split = path_split(ctx, "foo" PATH_SEP_STR "bar");
+	ok1(tal_parent(split) == ctx);
+	ok1(streq(split[0], "foo"));
+	ok1(streq(split[1], "bar"));
+	ok1(split[2] == NULL);
+	tal_free(split);
+
+	split = path_split(ctx, "foo" PATH_SEP_STR "bar" PATH_SEP_STR);
+	ok1(tal_parent(split) == ctx);
+	ok1(streq(split[0], "foo"));
+	ok1(streq(split[1], "bar"));
+	ok1(split[2] == NULL);
+	tal_free(split);
+
+	split = path_split(ctx, PATH_SEP_STR "foo"
+			   PATH_SEP_STR "bar" PATH_SEP_STR);
+	ok1(tal_parent(split) == ctx);
+	ok1(streq(split[0], "foo"));
+	ok1(streq(split[1], "bar"));
+	ok1(split[2] == NULL);
+	tal_free(split);
+
+	split = path_split(ctx, PATH_SEP_STR PATH_SEP_STR "foo"
+			   PATH_SEP_STR PATH_SEP_STR "bar"
+			   PATH_SEP_STR PATH_SEP_STR);
+	ok1(tal_parent(split) == ctx);
+	ok1(streq(split[0], "foo"));
+	ok1(streq(split[1], "bar"));
+	ok1(split[2] == NULL);
+	tal_free(split);
+
+	split = path_split(ctx, "foo");
+	ok1(tal_parent(split) == ctx);
+	ok1(streq(split[0], "foo"));
+	ok1(split[1] == NULL);
+	tal_free(split);
+
+	split = path_split(ctx, PATH_SEP_STR "foo");
+	ok1(tal_parent(split) == ctx);
+	ok1(streq(split[0], "foo"));
+	ok1(split[1] == NULL);
+	tal_free(split);
+
+	split = path_split(ctx, PATH_SEP_STR PATH_SEP_STR "foo");
+	ok1(tal_parent(split) == ctx);
+	ok1(streq(split[0], "foo"));
+	ok1(split[1] == NULL);
+	tal_free(split);
+
+	split = path_split(ctx, "foo" PATH_SEP_STR);
+	ok1(tal_parent(split) == ctx);
+	ok1(streq(split[0], "foo"));
+	ok1(split[1] == NULL);
+	tal_free(split);
+
+	split = path_split(ctx, "foo" PATH_SEP_STR PATH_SEP_STR);
+	ok1(tal_parent(split) == ctx);
+	ok1(streq(split[0], "foo"));
+	ok1(split[1] == NULL);
+	tal_free(split);
+
+	split = path_split(ctx, PATH_SEP_STR "foo" PATH_SEP_STR);
+	ok1(tal_parent(split) == ctx);
+	ok1(streq(split[0], "foo"));
+	ok1(split[1] == NULL);
+	tal_free(split);
+
+	split = path_split(ctx, "");
+	ok1(tal_parent(split) == ctx);
+	ok1(split[0] == NULL);
+	tal_free(split);
+
+	split = path_split(ctx, PATH_SEP_STR);
+	ok1(tal_parent(split) == ctx);
+	ok1(streq(split[0], PATH_SEP_STR));
+	ok1(split[1] == NULL);
+	tal_free(split);
+
+	/* Test take */
+	split = path_split(ctx, take(tal_strdup(ctx, PATH_SEP_STR)));
+	ok1(tal_parent(split) == ctx);
+	ok1(streq(split[0], PATH_SEP_STR));
+	ok1(split[1] == NULL);
+	tal_free(split);
+	ok1(tal_first(ctx) == NULL);
+
+	split = path_split(ctx, take(NULL));
+	ok1(!split);
+	ok1(tal_first(ctx) == NULL);
+
+	ok1(tal_first(NULL) == ctx && tal_next(NULL, ctx) == NULL);
+	tal_free(ctx);
+
+	return exit_status();
+}