From 62a276e55287a06d66373f02202ab39c7ba74c00 Mon Sep 17 00:00:00 2001 From: Greg Hudson Date: Sun, 7 Aug 2011 01:12:28 +0000 Subject: [PATCH] Add internal APIs for portable path manipulation 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 | 22 ++ src/util/support/Makefile.in | 21 +- src/util/support/libkrb5support-fixed.exports | 3 + src/util/support/path.c | 161 +++++++++++++++ src/util/support/t_path.c | 190 ++++++++++++++++++ 5 files changed, 395 insertions(+), 2 deletions(-) create mode 100644 src/util/support/path.c create mode 100644 src/util/support/t_path.c diff --git a/src/include/k5-platform.h b/src/include/k5-platform.h index d8324839b..147f87fb7 100644 --- a/src/include/k5-platform.h +++ b/src/include/k5-platform.h @@ -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, diff --git a/src/util/support/Makefile.in b/src/util/support/Makefile.in index 88520438f..8e4f70f3c 100644 --- a/src/util/support/Makefile.in +++ b/src/util/support/Makefile.in @@ -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:: diff --git a/src/util/support/libkrb5support-fixed.exports b/src/util/support/libkrb5support-fixed.exports index 40023e709..496259c99 100644 --- a/src/util/support/libkrb5support-fixed.exports +++ b/src/util/support/libkrb5support-fixed.exports @@ -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 index 000000000..221fb4a6a --- /dev/null +++ b/src/util/support/path.c @@ -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 + +/* 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 index 000000000..550d748b7 --- /dev/null +++ b/src/util/support/t_path.c @@ -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 + +/* 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; +} -- 2.26.2