Add internal APIs for portable path manipulation
authorGreg Hudson <ghudson@mit.edu>
Sun, 7 Aug 2011 01:12:28 +0000 (01:12 +0000)
committerGreg Hudson <ghudson@mit.edu>
Sun, 7 Aug 2011 01:12:28 +0000 (01:12 +0000)
k5_path_split separates a path into dirname and basename.
k5_path_join joins two paths.
k5_path_isabs determines if a path is absolute.

All three functions follow the Python path function semantics.
Currently the test module doesn't run in the Windows build, but the
Windows path semantics are tested in the Unix build using specially
built objects.

git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@25074 dc483132-0cff-0310-8789-dd5450dbe970

src/include/k5-platform.h
src/util/support/Makefile.in
src/util/support/libkrb5support-fixed.exports
src/util/support/path.c [new file with mode: 0644]
src/util/support/t_path.c [new file with mode: 0644]

index d8324839b70d45f9cae8ba2daa9182697bf10e05..147f87fb727b59b5170ead43fd465b26ae4e78a9 100644 (file)
@@ -36,6 +36,9 @@
  * + consistent getpwnam/getpwuid interfaces
  * + va_copy fudged if not provided
  * + [v]asprintf
+ * + mkstemp
+ * + zap (support function; macro is in k5-int.h)
+ * + path manipulation
  * + _, N_, dgettext, bindtextdomain, setlocale (for localization)
  */
 
@@ -1011,6 +1014,25 @@ extern int krb5int_mkstemp(char *);
 
 extern void krb5int_zap(void *ptr, size_t len);
 
+/*
+ * Split a path into parent directory and basename.  Either output parameter
+ * may be NULL if the caller doesn't need it.  parent_out will be empty if path
+ * has no basename.  basename_out will be empty if path ends with a path
+ * separator.  Returns 0 on success or ENOMEM on allocation failure.
+ */
+long k5_path_split(const char *path, char **parent_out, char **basename_out);
+
+/*
+ * Compose two path components, inserting the platform-appropriate path
+ * separator if needed.  If path2 is an absolute path, path1 will be discarded
+ * and path_out will be a copy of path2.  Returns 0 on success or ENOMEM on
+ * allocation failure.
+ */
+long k5_path_join(const char *path1, const char *path2, char **path_out);
+
+/* Return 1 if path is absolute, 0 if it is relative. */
+int k5_path_isabs(const char *path);
+
 /*
  * Localization macros.  If we have gettext, define _ appropriately for
  * translating a string.  If we do not have gettext, define _, bindtextdomain,
index 88520438fa34cc61ab541208917d2dbc72fd90bf..8e4f70f3c14cbf1cf434f8647232fd354cc10762 100644 (file)
@@ -63,6 +63,7 @@ STLIBOBJS= \
        utf8.o \
        utf8_conv.o \
        zap.o \
+       path.o \
        $(IPC_ST_OBJ) \
        $(STRLCPY_ST_OBJ) \
        $(PRINTF_ST_OBJ) \
@@ -79,6 +80,7 @@ LIBOBJS= \
        $(OUTPRE)utf8.$(OBJEXT) \
        $(OUTPRE)utf8_conv.$(OBJEXT) \
        $(OUTPRE)zap.$(OBJEXT) \
+       $(OUTPRE)path.$(OBJEXT) \
        $(IPC_OBJ) \
        $(STRLCPY_OBJ) \
        $(PRINTF_OBJ) \
@@ -104,7 +106,8 @@ SRCS=\
        $(srcdir)/mkstemp.c \
        $(srcdir)/t_k5buf.c \
        $(srcdir)/t_unal.c \
-       $(srcdir)/zap.c
+       $(srcdir)/zap.c \
+       $(srcdir)/path.c
 
 SHLIB_EXPDEPS =
 # Add -lm if dumping thread stats, for sqrt.
@@ -155,13 +158,27 @@ T_K5BUF_OBJS= t_k5buf.o k5buf.o $(PRINTF_ST_OBJ)
 t_k5buf: $(T_K5BUF_OBJS)
        $(CC_LINK) -o t_k5buf $(T_K5BUF_OBJS)
 
+t_path: t_path.o path.o $(PRINTF_ST_OBJ)
+       $(CC_LINK) -o $@ t_path.o path.o $(PRINTF_ST_OBJ)
+
+t_path_win: t_path_win.o path_win.o $(PRINTF_ST_OBJ)
+       $(CC_LINK) -o $@ t_path_win.o path_win.o $(PRINTF_ST_OBJ)
+
+t_path_win.o: $(srcdir)/t_path.c
+       $(CC) $(ALL_CFLAGS) -DWINDOWS_PATHS -c $(srcdir)/t_path.c -o $@
+
+path_win.o: $(srcdir)/path.c
+       $(CC) $(ALL_CFLAGS) -DWINDOWS_PATHS -c $(srcdir)/path.c -o $@
+
 t_unal: t_unal.o
        $(CC_LINK) -o t_unal t_unal.o
 
-TEST_PROGS= t_k5buf t_unal
+TEST_PROGS= t_k5buf t_path t_path_win t_unal
 
 check-unix:: $(TEST_PROGS)
        ./t_k5buf
+       ./t_path
+       ./t_path_win
        ./t_unal
 
 clean::
index 40023e709f17ea92b1cf06f165208b6514ff495d..496259c992bc033bb13efaafeb029d5e7129e30c 100644 (file)
@@ -1,3 +1,6 @@
+k5_path_isabs
+k5_path_join
+k5_path_split
 krb5int_key_register
 krb5int_key_delete
 krb5int_getspecific
diff --git a/src/util/support/path.c b/src/util/support/path.c
new file mode 100644 (file)
index 0000000..221fb4a
--- /dev/null
@@ -0,0 +1,161 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* util/support/path.c - Portable path manipulation functions */
+/*
+ * Copyright (C) 2011 by the Massachusetts Institute of Technology.
+ * All rights reserved.
+ *
+ * Export of this software from the United States of America may
+ *   require a specific license from the United States Government.
+ *   It is the responsibility of any person or organization contemplating
+ *   export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission.  Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose.  It is provided "as is" without express
+ * or implied warranty.
+ */
+
+#include <k5-platform.h>
+
+/* For testing purposes, use a different symbol for Windows path semantics. */
+#ifdef _WIN32
+#define WINDOWS_PATHS
+#endif
+
+/*
+ * This file implements a limited set of portable path manipulation functions.
+ * When in doubt about edge cases, we follow the Python os.path semantics.
+ */
+
+#ifdef WINDOWS_PATHS
+#define SEP '\\'
+#define IS_SEPARATOR(c) ((c) == '\\' || (c) == '/')
+#else
+#define SEP '/'
+#define IS_SEPARATOR(c) ((c) == '/')
+#endif
+
+/* Find the rightmost path separator in path, or NULL if there is none. */
+static inline const char *
+find_sep(const char *path)
+{
+#ifdef WINDOWS_PATHS
+    const char *slash, *backslash;
+
+    slash = strrchr(path, '/');
+    backslash = strrchr(path, '\\');
+    if (slash != NULL && backslash != NULL)
+       return (slash > backslash) ? slash : backslash;
+    else
+       return (slash != NULL) ? slash : backslash;
+#else
+    return strrchr(path, '/');
+#endif
+}
+
+/* XXX drive letter prefixes */
+long
+k5_path_split(const char *path, char **parent_out, char **basename_out)
+{
+    const char *pathstart, *sep, *pend, *bstart;
+    char *parent = NULL, *basename = NULL;
+
+    if (parent_out != NULL)
+       *parent_out = NULL;
+    if (basename_out != NULL)
+       *basename_out = NULL;
+
+    pathstart = path;
+#ifdef WINDOWS_PATHS
+    if (*path != '\0' && path[1] == ':')
+       pathstart = path + 2;
+#endif
+
+    sep = find_sep(pathstart);
+    if (sep != NULL) {
+       bstart = sep + 1;
+       /* Strip off excess separators before the one we found. */
+       pend = sep;
+       while (pend > pathstart && IS_SEPARATOR(pend[-1]))
+           pend--;
+       /* But if we hit the start, keep the whole separator sequence. */
+       if (pend == pathstart)
+           pend = sep + 1;
+    } else {
+       bstart = pathstart;
+       pend = pathstart;
+    }
+
+    if (parent_out) {
+       parent = malloc(pend - path + 1);
+       if (parent == NULL)
+           return ENOMEM;
+       memcpy(parent, path, pend - path);
+       parent[pend - path] = '\0';
+    }
+    if (basename_out) {
+       basename = strdup(bstart);
+       if (basename == NULL) {
+           free(parent);
+           return ENOMEM;
+       }
+    }
+
+    if (parent_out)
+       *parent_out = parent;
+    if (basename_out)
+       *basename_out = basename;
+    return 0;
+}
+
+long
+k5_path_join(const char *path1, const char *path2, char **path_out)
+{
+    char *path, c;
+    int ret;
+
+    *path_out = NULL;
+    if (k5_path_isabs(path2) || *path1 == '\0') {
+       /* Discard path1 and return a copy of path2. */
+       path = strdup(path2);
+       if (path == NULL)
+           return ENOMEM;
+    } else {
+       /*
+        * Compose path1 and path2, adding a separator if path1 is non-empty
+        * there's no separator between them already.  (*path2 can be a
+        * separator in the weird case where it starts with /: or \: on
+        * Windows, and Python doesn't insert a separator in this case.)
+        */
+       c = path1[strlen(path1) - 1];
+       if (IS_SEPARATOR(c) || IS_SEPARATOR(*path2))
+           ret = asprintf(&path, "%s%s", path1, path2);
+       else
+           ret = asprintf(&path, "%s%c%s", path1, SEP, path2);
+       if (ret < 0)
+           return ENOMEM;
+    }
+    *path_out = path;
+    return 0;
+}
+
+int
+k5_path_isabs(const char *path)
+{
+#ifdef WINDOWS_PATHS
+    if (*path != '\0' && path[1] == ':')
+       path += 2;
+    return (*path == '/' || *path == '\\');
+#else
+    return (*path == '/');
+#endif
+}
diff --git a/src/util/support/t_path.c b/src/util/support/t_path.c
new file mode 100644 (file)
index 0000000..550d748
--- /dev/null
@@ -0,0 +1,190 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* util/support/t_path.c - Path manipulation tests */
+/*
+ * Copyright (C) 2011 by the Massachusetts Institute of Technology.
+ * All rights reserved.
+ *
+ * Export of this software from the United States of America may
+ *   require a specific license from the United States Government.
+ *   It is the responsibility of any person or organization contemplating
+ *   export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission.  Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose.  It is provided "as is" without express
+ * or implied warranty.
+ */
+
+#include <k5-platform.h>
+
+/* For testing purposes, use a different symbol for Windows path semantics. */
+#ifdef _WIN32
+#define WINDOWS_PATHS
+#endif
+
+/*
+ * The ultimate arbiter of these tests is the dirname, basename, and isabs
+ * methods of the Python posixpath and ntpath modules.
+ */
+
+struct {
+    const char *path;
+    const char *posix_dirname;
+    const char *posix_basename;
+    const char *win_dirname;
+    const char *win_basename;
+} split_tests[] = {
+    { "",          "",      "",          "",       ""  },
+    { "a/b/c",     "a/b",   "c",         "a/b",    "c" },
+    { "a/b/",      "a/b",   "",          "a/b",    ""  },
+    { "a\\b\\c",   "",      "a\\b\\c",   "a\\b",   "c" },
+    { "a\\b\\",    "",      "a\\b\\",    "a\\b",   ""  },
+    { "a/b\\c",    "a",     "b\\c",      "a/b",    "c" },
+    { "a//b",      "a",     "b",         "a",      "b" },
+    { "a/\\/b",    "a/\\",  "b",         "a",      "b" },
+    { "a//b/c",    "a//b",  "c",         "a//b",   "c" },
+
+    { "/",         "/",     "",          "/",      ""  },
+    { "\\",        "",      "\\",        "\\",     ""  },
+    { "/a/b/c",    "/a/b",  "c",         "/a/b",   "c" },
+    { "\\a/b/c",   "\\a/b", "c",         "\\a/b",  "c" },
+    { "/a",        "/",     "a",         "/",      "a" },
+    { "//a",       "//",    "a",         "//",     "a" },
+    { "\\//\\a",   "\\",    "\\a",       "\\//\\", "a" },
+
+    { "/:",        "/",     ":",         "/:",     ""  },
+    { "c:\\",      "",      "c:\\",      "c:\\",   ""  },
+    { "c:/",       "c:",    "",          "c:/",    ""  },
+    { "c:/\\a",    "c:",    "\\a",       "c:/\\",  "a" },
+    { "c:a",       "",      "c:a",       "c:",     "a" },
+};
+
+struct {
+    const char *path1;
+    const char *path2;
+    const char *posix_result;
+    const char *win_result;
+} join_tests[] = {
+    { "",     "",     "",         ""      },
+    { "",     "a",    "a",        "a"     },
+    { "",     "/a",   "/a",       "/a"    },
+    { "",     "c:",   "c:",       "c:"    },
+
+    { "a",    "",     "a/",       "a\\"   },
+    { "a/",   "",     "a/",       "a/"    },
+    { "a\\",  "",     "a\\/",     "a\\"   },
+    { "a/\\", "",     "a/\\/",    "a/\\"  },
+
+    { "a",    "b",    "a/b",      "a\\b"  },
+    { "a",    "/b",   "/b",       "/b"    },
+    { "a",    "c:",   "a/c:",     "a\\c:" },
+    { "a",    "c:/",  "a/c:/",    "c:/"   },
+    { "a",    "c:/a", "a/c:/a",   "c:/a"  },
+    { "a",    "/:",   "/:",       "a/:"   },
+    { "a/",   "b",    "a/b",      "a/b"   },
+    { "a/",   "",     "a/",       "a/"    },
+    { "a\\",  "b",    "a\\/b",    "a\\b"  },
+
+    { "a//",  "b",    "a//b",     "a//b"  },
+    { "a/\\", "b",    "a/\\/b",   "a/\\b" },
+};
+
+struct {
+    const char *path;
+    int posix_result;
+    int win_result;
+} isabs_tests[] = {
+    { "",      0, 0 },
+    { "/",     1, 1 },
+    { "/a",    1, 1 },
+    { "a/b",   0, 0 },
+    { "\\",    0, 1 },
+    { "\\a",   0, 1 },
+    { "c:",    0, 0 },
+    { "/:",    1, 0 },
+    { "\\:",   0, 0 },
+    { "c:/a",  0, 1 },
+    { "c:\\a", 0, 1 },
+    { "c:a",   0, 0 },
+    { "c:a/b", 0, 0 },
+    { "/:a/b", 1, 0 },
+};
+
+int
+main(void)
+{
+    char *dirname, *basename, *joined;
+    const char *edirname, *ebasename, *ejoined, *ipath, *path1, *path2;
+    int result, eresult, status = 0;
+    size_t i;
+
+    for (i = 0; i < sizeof(split_tests) / sizeof(*split_tests); i++) {
+       ipath = split_tests[i].path;
+#ifdef WINDOWS_PATHS
+       edirname = split_tests[i].win_dirname;
+       ebasename = split_tests[i].win_basename;
+#else
+       edirname = split_tests[i].posix_dirname;
+       ebasename = split_tests[i].posix_basename;
+#endif
+       assert(k5_path_split(ipath, NULL, NULL) == 0);
+       assert(k5_path_split(ipath, &dirname, NULL) == 0);
+       free(dirname);
+       assert(k5_path_split(ipath, NULL, &basename) == 0);
+       free(basename);
+       assert(k5_path_split(ipath, &dirname, &basename) == 0);
+       if (strcmp(dirname, edirname) != 0) {
+           fprintf(stderr, "Split test %d: dirname %s != expected %s\n",
+                   (int)i, dirname, edirname);
+           status = 1;
+       }
+       if (strcmp(basename, ebasename) != 0) {
+           fprintf(stderr, "Split test %d: basename %s != expected %s\n",
+                   (int)i, basename, ebasename);
+           status = 1;
+       }
+       free(dirname);
+       free(basename);
+    }
+
+    for (i = 0; i < sizeof(join_tests) / sizeof(*join_tests); i++) {
+       path1 = join_tests[i].path1;
+       path2 = join_tests[i].path2;
+#ifdef WINDOWS_PATHS
+       ejoined = join_tests[i].win_result;
+#else
+       ejoined = join_tests[i].posix_result;
+#endif
+       assert(k5_path_join(path1, path2, &joined) == 0);
+       if (strcmp(joined, ejoined) != 0) {
+           fprintf(stderr, "Join test %d: %s != expected %s\n",
+                   (int)i, joined, ejoined);
+           status = 1;
+       }
+    }
+
+    for (i = 0; i < sizeof(isabs_tests) / sizeof(*isabs_tests); i++) {
+#ifdef WINDOWS_PATHS
+       eresult = isabs_tests[i].win_result;
+#else
+       eresult = isabs_tests[i].posix_result;
+#endif
+       result = k5_path_isabs(isabs_tests[i].path);
+       if (result != eresult) {
+           fprintf(stderr, "isabs test %d: %d != expected %d\n",
+                   (int)i, result, eresult);
+           status = 1;
+       }
+    }
+
+    return status;
+}