tal/path: new module
authorRusty Russell <rusty@rustcorp.com.au>
Thu, 22 Nov 2012 01:12:25 +0000 (11:42 +1030)
committerRusty Russell <rusty@rustcorp.com.au>
Thu, 22 Nov 2012 01:12:25 +0000 (11:42 +1030)
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>
19 files changed:
Makefile-ccan
ccan/tal/path/LICENSE [new symlink]
ccan/tal/path/_info [new file with mode: 0644]
ccan/tal/path/path.c [new file with mode: 0644]
ccan/tal/path/path.h [new file with mode: 0644]
ccan/tal/path/test/run-basename.c [new file with mode: 0644]
ccan/tal/path/test/run-canon.c [new file with mode: 0644]
ccan/tal/path/test/run-cwd.c [new file with mode: 0644]
ccan/tal/path/test/run-dirname.c [new file with mode: 0644]
ccan/tal/path/test/run-ext_off.c [new file with mode: 0644]
ccan/tal/path/test/run-is_abs.c [new file with mode: 0644]
ccan/tal/path/test/run-is_dir.c [new file with mode: 0644]
ccan/tal/path/test/run-is_file.c [new file with mode: 0644]
ccan/tal/path/test/run-join.c [new file with mode: 0644]
ccan/tal/path/test/run-pushd.c [new file with mode: 0644]
ccan/tal/path/test/run-readlink.c [new file with mode: 0644]
ccan/tal/path/test/run-rel.c [new file with mode: 0644]
ccan/tal/path/test/run-simplify.c [new file with mode: 0644]
ccan/tal/path/test/run-split.c [new file with mode: 0644]

index e16c87a8696a0fbaa0e2fe841e7636c1ba8256d8..394673836e9dde22b671cb1afd996ab7746f8fed 100644 (file)
@@ -72,6 +72,7 @@ MODS_NORMAL_WITH_SRC := antithread \
        str_talloc \
        take \
        tal \
        str_talloc \
        take \
        tal \
+       tal/path \
        tal/str \
        talloc \
        talloc_link \
        tal/str \
        talloc \
        talloc_link \
diff --git a/ccan/tal/path/LICENSE b/ccan/tal/path/LICENSE
new file mode 120000 (symlink)
index 0000000..2b1feca
--- /dev/null
@@ -0,0 +1 @@
+../../../licenses/BSD-MIT
\ No newline at end of file
diff --git a/ccan/tal/path/_info b/ccan/tal/path/_info
new file mode 100644 (file)
index 0000000..24b9e46
--- /dev/null
@@ -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;
+}
diff --git a/ccan/tal/path/path.c b/ccan/tal/path/path.c
new file mode 100644 (file)
index 0000000..0ad168c
--- /dev/null
@@ -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);
+}
diff --git a/ccan/tal/path/path.h b/ccan/tal/path/path.h
new file mode 100644 (file)
index 0000000..65d539c
--- /dev/null
@@ -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 */
diff --git a/ccan/tal/path/test/run-basename.c b/ccan/tal/path/test/run-basename.c
new file mode 100644 (file)
index 0000000..c99353a
--- /dev/null
@@ -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();
+}
diff --git a/ccan/tal/path/test/run-canon.c b/ccan/tal/path/test/run-canon.c
new file mode 100644 (file)
index 0000000..e01567b
--- /dev/null
@@ -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();
+}
diff --git a/ccan/tal/path/test/run-cwd.c b/ccan/tal/path/test/run-cwd.c
new file mode 100644 (file)
index 0000000..d751774
--- /dev/null
@@ -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();
+}
diff --git a/ccan/tal/path/test/run-dirname.c b/ccan/tal/path/test/run-dirname.c
new file mode 100644 (file)
index 0000000..46589ae
--- /dev/null
@@ -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();
+}
diff --git a/ccan/tal/path/test/run-ext_off.c b/ccan/tal/path/test/run-ext_off.c
new file mode 100644 (file)
index 0000000..ab0d0a5
--- /dev/null
@@ -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();
+}
diff --git a/ccan/tal/path/test/run-is_abs.c b/ccan/tal/path/test/run-is_abs.c
new file mode 100644 (file)
index 0000000..8cb0a1d
--- /dev/null
@@ -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();
+}
diff --git a/ccan/tal/path/test/run-is_dir.c b/ccan/tal/path/test/run-is_dir.c
new file mode 100644 (file)
index 0000000..15b0c4d
--- /dev/null
@@ -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();
+}
diff --git a/ccan/tal/path/test/run-is_file.c b/ccan/tal/path/test/run-is_file.c
new file mode 100644 (file)
index 0000000..fc3c7d3
--- /dev/null
@@ -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();
+}
diff --git a/ccan/tal/path/test/run-join.c b/ccan/tal/path/test/run-join.c
new file mode 100644 (file)
index 0000000..a4f63b3
--- /dev/null
@@ -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();
+}
diff --git a/ccan/tal/path/test/run-pushd.c b/ccan/tal/path/test/run-pushd.c
new file mode 100644 (file)
index 0000000..dc3f2ea
--- /dev/null
@@ -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();
+}
diff --git a/ccan/tal/path/test/run-readlink.c b/ccan/tal/path/test/run-readlink.c
new file mode 100644 (file)
index 0000000..28dcf87
--- /dev/null
@@ -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();
+}
diff --git a/ccan/tal/path/test/run-rel.c b/ccan/tal/path/test/run-rel.c
new file mode 100644 (file)
index 0000000..7083c4b
--- /dev/null
@@ -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();
+}
diff --git a/ccan/tal/path/test/run-simplify.c b/ccan/tal/path/test/run-simplify.c
new file mode 100644 (file)
index 0000000..3d1b351
--- /dev/null
@@ -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();
+}
diff --git a/ccan/tal/path/test/run-split.c b/ccan/tal/path/test/run-split.c
new file mode 100644 (file)
index 0000000..732333c
--- /dev/null
@@ -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();
+}