From f725931b4865317b58c1f1600724cb36e586c332 Mon Sep 17 00:00:00 2001 From: Vicent Marti Date: Sat, 5 Feb 2011 12:42:41 +0200 Subject: [PATCH] Fix directory/path manipulation methods The `dirname` and `dirbase` methods have been replaced with the Android implementation, which is actually compilant to some kind of standard. A new method `topdir` has been added, which returns the topmost directory in a path. These changes fix issue #49: `gitfo_prettify_dir_path` converts "./.git/" to ".git/", so the code at src/repository.c:190 goes out of bounds when trying to find the topmost directory. The new `git__topdir` method handles this gracefully, and the fixed `git__dirname` now returns the proper value for the repository's working dir. E.g. /repo/.git/ ==> working dir '/repo/' .git/ ==> working dir '.' Signed-off-by: Vicent Marti --- src/odb_loose.c | 2 +- src/repository.c | 17 ++-- src/util.c | 206 ++++++++++++++++++++++++++++++++++++++--------- src/util.h | 55 ++++++++++++- tests/t00-core.c | 96 +++++++++++++--------- 5 files changed, 286 insertions(+), 90 deletions(-) diff --git a/src/odb_loose.c b/src/odb_loose.c index b031bde3f..f89eb71ff 100644 --- a/src/odb_loose.c +++ b/src/odb_loose.c @@ -59,7 +59,7 @@ static int make_temp_file(git_file *fd, char *tmp, size_t n, char *file) size_t tmplen = strlen(template); int dirlen; - if ((dirlen = git__dirname(tmp, n, file)) < 0) + if ((dirlen = git__dirname_r(tmp, n, file)) < 0) return GIT_ERROR; if ((dirlen + tmplen) >= n) diff --git a/src/repository.c b/src/repository.c index 37d5a49f0..3093cf5bd 100644 --- a/src/repository.c +++ b/src/repository.c @@ -157,7 +157,9 @@ static int assign_repository_DIRs(git_repository *repo, static int guess_repository_DIRs(git_repository *repo, const char *repository_path) { - char path_aux[GIT_PATH_MAX], *last_DIR; + char path_aux[GIT_PATH_MAX]; + const char *topdir; + int path_len; int error = GIT_SUCCESS; @@ -185,12 +187,10 @@ static int guess_repository_DIRs(git_repository *repo, const char *repository_pa path_aux[path_len] = 0; - last_DIR = (path_aux + path_len - 2); + if ((topdir = git__topdir(path_aux)) == NULL) + return GIT_EINVALIDPATH; - while (*last_DIR != '/') - last_DIR--; - - if (strcmp(last_DIR, GIT_DIR) == 0) { + if (strcmp(topdir, GIT_DIR) == 0) { repo->is_bare = 0; /* index file */ @@ -198,8 +198,9 @@ static int guess_repository_DIRs(git_repository *repo, const char *repository_pa repo->path_index = git__strdup(path_aux); /* working dir */ - *(last_DIR + 1) = 0; - repo->path_workdir = git__strdup(path_aux); + repo->path_workdir = git__dirname(path_aux); + if (repo->path_workdir == NULL) + return GIT_EINVALIDPATH; } else { repo->is_bare = 1; diff --git a/src/util.c b/src/util.c index ef97f68a4..d9d77eccf 100644 --- a/src/util.c +++ b/src/util.c @@ -36,65 +36,193 @@ int git__suffixcmp(const char *str, const char *suffix) return strcmp(str + (a - b), suffix); } -int git__dirname(char *dir, size_t n, char *path) +/* + * Based on the Android implementation, BSD licensed. + * Check http://android.git.kernel.org/ + */ +int git__basename_r(char *buffer, size_t bufflen, const char *path) { - char *s; - size_t len; + const char *endp, *startp; + int len, result; - assert(dir && n > 1); - - if (!path || !*path || (s = strrchr(path, '/')) == NULL) { - strcpy(dir, "."); - return 1; + /* Empty or NULL string gets treated as "." */ + if (path == NULL || *path == '\0') { + startp = "."; + len = 1; + goto Exit; } - if (s == path) { /* "/[aaa]" */ - strcpy(dir, "/"); - return 1; + /* Strip trailing slashes */ + endp = path + strlen(path) - 1; + while (endp > path && *endp == '/') + endp--; + + /* All slashes becomes "/" */ + if (endp == path && *endp == '/') { + startp = "/"; + len = 1; + goto Exit; } - if ((len = s - path) >= n) - return GIT_ERROR; + /* Find the start of the base */ + startp = endp; + while (startp > path && *(startp - 1) != '/') + startp--; - memcpy(dir, path, len); - dir[len] = '\0'; + len = endp - startp +1; - return len; +Exit: + result = len; + if (buffer == NULL) { + return result; + } + if (len > (int)bufflen-1) { + len = (int)bufflen-1; + result = GIT_ENOMEM; + } + + if (len >= 0) { + memcpy(buffer, startp, len); + buffer[len] = 0; + } + return result; } -int git__basename(char *base, size_t n, char *path) +/* + * Based on the Android implementation, BSD licensed. + * Check http://android.git.kernel.org/ + */ +int git__dirname_r(char *buffer, size_t bufflen, const char *path) { - char *s; - size_t len; + const char *endp; + int result, len; - assert(base && n > 1); + /* Empty or NULL string gets treated as "." */ + if (path == NULL || *path == '\0') { + path = "."; + len = 1; + goto Exit; + } - if (!path || !*path) { - strcpy(base, "."); - return 1; + /* Strip trailing slashes */ + endp = path + strlen(path) - 1; + while (endp > path && *endp == '/') + endp--; + + /* Find the start of the dir */ + while (endp > path && *endp != '/') + endp--; + + /* Either the dir is "/" or there are no slashes */ + if (endp == path) { + path = (*endp == '/') ? "/" : "."; + len = 1; + goto Exit; + } + + do { + endp--; + } while (endp > path && *endp == '/'); + + len = endp - path +1; + +Exit: + result = len; + if (len+1 > GIT_PATH_MAX) { + return GIT_ENOMEM; + } + if (buffer == NULL) + return result; + + if (len > (int)bufflen-1) { + len = (int)bufflen-1; + result = GIT_ENOMEM; + } + + if (len >= 0) { + memcpy(buffer, path, len); + buffer[len] = 0; + } + return result; +} + + +char *git__dirname(const char *path) +{ + char *dname = NULL; + int len; + + len = (path ? strlen(path) : 0) + 2; + dname = (char *)git__malloc(len); + if (dname == NULL) + return NULL; + + if (git__dirname_r(dname, len, path) < GIT_SUCCESS) { + free(dname); + return NULL; } + + return dname; +} + +char *git__basename(const char *path) +{ + char *bname = NULL; + int len; + + len = (path ? strlen(path) : 0) + 2; + bname = (char *)git__malloc(len); + if (bname == NULL) + return NULL; + + if (git__basename_r(bname, len, path) < GIT_SUCCESS) { + free(bname); + return NULL; + } + + return bname; +} + + +const char *git__topdir(const char *path) +{ + size_t len; + int i; + + assert(path); len = strlen(path); - if ((s = strrchr(path, '/')) == NULL) { - if (len >= n) - return GIT_ERROR; - strcpy(base, path); - return len; - } + if (!len || path[len - 1] != '/') + return NULL; - if (s == path && len == 1) { /* "/" */ - strcpy(base, "/"); - return 1; - } + for (i = len - 2; i >= 0; --i) + if (path[i] == '/') + break; - len -= s - path; - if (len >= n) - return GIT_ERROR; + return &path[i + 1]; +} - memcpy(base, s+1, len); - base[len] = '\0'; +static char *strtok_raw(char *output, char *src, char *delimit, int keep) +{ + while (*src && strchr(delimit, *src) == NULL) + *output++ = *src++; - return len; + *output = 0; + + if (keep) + return src; + else + return *src ? src+1 : src; +} + +char *git__strtok(char *output, char *src, char *delimit) +{ + return strtok_raw(output, src, delimit, 0); +} + +char *git__strtok_keep(char *output, char *src, char *delimit) +{ + return strtok_raw(output, src, delimit, 1); } void git__hexdump(const char *buffer, size_t len) diff --git a/src/util.h b/src/util.h index 99c4f5a84..67ff4aec1 100644 --- a/src/util.h +++ b/src/util.h @@ -19,8 +19,44 @@ extern int git__fmt(char *, size_t, const char *, ...) extern int git__prefixcmp(const char *str, const char *prefix); extern int git__suffixcmp(const char *str, const char *suffix); -extern int git__dirname(char *dir, size_t n, char *path); -extern int git__basename(char *base, size_t n, char *path); +/* + * The dirname() function shall take a pointer to a character string + * that contains a pathname, and return a pointer to a string that is a + * pathname of the parent directory of that file. Trailing '/' characters + * in the path are not counted as part of the path. + * + * If path does not contain a '/', then dirname() shall return a pointer to + * the string ".". If path is a null pointer or points to an empty string, + * dirname() shall return a pointer to the string "." . + * + * The `git__dirname` implementation is thread safe. The returned + * string must be manually free'd. + * + * The `git__dirname_r` implementation expects a string allocated + * by the user with big enough size. + */ +extern char *git__dirname(const char *path); +extern int git__dirname_r(char *buffer, size_t bufflen, const char *path); + +/* + * This function returns the basename of the file, which is the last + * part of its full name given by fname, with the drive letter and + * leading directories stripped off. For example, the basename of + * c:/foo/bar/file.ext is file.ext, and the basename of a:foo is foo. + * + * Trailing slashes and backslashes are significant: the basename of + * c:/foo/bar/ is an empty string after the rightmost slash. + * + * The `git__basename` implementation is thread safe. The returned + * string must be manually free'd. + * + * The `git__basename_r` implementation expects a string allocated + * by the user with big enough size. + */ +extern char *git__basename(const char *path); +extern int git__basename_r(char *buffer, size_t bufflen, const char *path); + +extern const char *git__topdir(const char *path); extern void git__hexdump(const char *buffer, size_t n); extern uint32_t git__hash(const void *key, int len, uint32_t seed); @@ -40,6 +76,21 @@ GIT_INLINE(int) git__is_sizet(git_off_t p) # define git__rotl(v, s) (uint32_t)(((uint32_t)(v) << (s)) | ((uint32_t)(v) >> (32 - (s)))) #endif +enum git_splitpath_flags +{ + GIT_SPL_PATH = 1, + GIT_SPL_FILE = 2, + GIT_SPL_EXT = 4, + GIT_SPL_PATH_FILE = GIT_SPL_PATH + GIT_SPL_FILE, + GIT_SPL_FILE_EXT = GIT_SPL_FILE + GIT_SPL_EXT, + GIT_SPL_EXT_NO_PERIOD = 8, +}; + + +extern char *git__splitpath(char *path, int flag); +extern char *git__strtok(char *output, char *src, char *delimit); +extern char *git__strtok_keep(char *output, char *src, char *delimit); + /* * Realloc the buffer pointed at by variable 'x' so that it can hold * at least 'nr' entries; the number of entries currently allocated diff --git a/tests/t00-core.c b/tests/t00-core.c index 08bd5cb13..7dd09955f 100644 --- a/tests/t00-core.c +++ b/tests/t00-core.c @@ -61,61 +61,76 @@ BEGIN_TEST("strutil", suffix_comparison) END_TEST BEGIN_TEST("strutil", dirname) - char dir[64]; + char dir[64], *dir2; - must_be_true(!(git__dirname(dir, sizeof(dir), NULL) < 0)); - must_be_true(!strcmp(dir, ".")); +#define DIRNAME_TEST(A, B) { \ + must_be_true(git__dirname_r(dir, sizeof(dir), A) >= 0); \ + must_be_true(strcmp(dir, B) == 0); \ + must_be_true((dir2 = git__dirname(A)) != NULL); \ + must_be_true(strcmp(dir2, B) == 0); \ + free(dir2); \ +} - must_be_true(!(git__dirname(dir, sizeof(dir), "") < 0)); - must_be_true(!strcmp(dir, ".")); + DIRNAME_TEST(NULL, "."); + DIRNAME_TEST("", "."); + DIRNAME_TEST("a", "."); + DIRNAME_TEST("/", "/"); + DIRNAME_TEST("/usr", "/"); + DIRNAME_TEST("/usr/", "/"); + DIRNAME_TEST("/usr/lib", "/usr"); + DIRNAME_TEST("usr/lib", "usr"); + DIRNAME_TEST(".git/", "."); - must_be_true(!(git__dirname(dir, sizeof(dir), "a") < 0)); - must_be_true(!strcmp(dir, ".")); +#undef DIRNAME_TEST - must_be_true(!(git__dirname(dir, sizeof(dir), "/") < 0)); - must_be_true(!strcmp(dir, "/")); - - must_be_true(!(git__dirname(dir, sizeof(dir), "/usr") < 0)); - must_be_true(!strcmp(dir, "/")); - - /* TODO: should this be "/" instead (ie strip trailing / first) */ - must_be_true(!(git__dirname(dir, sizeof(dir), "/usr/") < 0)); - must_be_true(!strcmp(dir, "/usr")); - - must_be_true(!(git__dirname(dir, sizeof(dir), "/usr/lib") < 0)); - must_be_true(!strcmp(dir, "/usr")); - - must_be_true(!(git__dirname(dir, sizeof(dir), "usr/lib") < 0)); - must_be_true(!strcmp(dir, "usr")); END_TEST BEGIN_TEST("strutil", basename) - char base[64]; + char base[64], *base2; - must_be_true(!(git__basename(base, sizeof(base), NULL) < 0)); - must_be_true(!strcmp(base, ".")); +#define BASENAME_TEST(A, B) { \ + must_be_true(git__basename_r(base, sizeof(base), A) >= 0); \ + must_be_true(strcmp(base, B) == 0); \ + must_be_true((base2 = git__basename(A)) != NULL); \ + must_be_true(strcmp(base2, B) == 0); \ + free(base2); \ +} - must_be_true(!(git__basename(base, sizeof(base), "") < 0)); - must_be_true(!strcmp(base, ".")); + BASENAME_TEST(NULL, "."); + BASENAME_TEST("", "."); + BASENAME_TEST("a", "a"); + BASENAME_TEST("/", "/"); + BASENAME_TEST("/usr", "usr"); + BASENAME_TEST("/usr/", "usr"); + BASENAME_TEST("/usr/lib", "lib"); + BASENAME_TEST("usr/lib", "lib"); - must_be_true(!(git__basename(base, sizeof(base), "a") < 0)); - must_be_true(!strcmp(base, "a")); +#undef BASENAME_TEST - must_be_true(!(git__basename(base, sizeof(base), "/") < 0)); - must_be_true(!strcmp(base, "/")); +END_TEST - must_be_true(!(git__basename(base, sizeof(base), "/usr") < 0)); - must_be_true(!strcmp(base, "usr")); +BEGIN_TEST("strutil", topdir) + const char *dir; - /* TODO: should this be "usr" instead (ie strip trailing / first) */ - must_be_true(!(git__basename(base, sizeof(base), "/usr/") < 0)); - must_be_true(!strcmp(base, "")); +#define TOPDIR_TEST(A, B) { \ + must_be_true((dir = git__topdir(A)) != NULL); \ + must_be_true(strcmp(dir, B) == 0); \ +} - must_be_true(!(git__basename(base, sizeof(base), "/usr/lib") < 0)); - must_be_true(!strcmp(base, "lib")); + TOPDIR_TEST(".git/", ".git/"); + TOPDIR_TEST("/.git/", ".git/"); + TOPDIR_TEST("usr/local/.git/", ".git/"); + TOPDIR_TEST("./.git/", ".git/"); + TOPDIR_TEST("/usr/.git/", ".git/"); + TOPDIR_TEST("/", "/"); + TOPDIR_TEST("a/", "a/"); - must_be_true(!(git__basename(base, sizeof(base), "usr/lib") < 0)); - must_be_true(!strcmp(base, "lib")); + must_be_true(git__topdir("/usr/.git") == NULL); + must_be_true(git__topdir(".") == NULL); + must_be_true(git__topdir("") == NULL); + must_be_true(git__topdir("a") == NULL); + +#undef TOPDIR_TEST END_TEST /* Initial size of 1 will cause writing past array bounds prior to fix */ @@ -579,6 +594,7 @@ git_testsuite *libgit2_suite_core(void) ADD_TEST(suite, "strutil", suffix_comparison); ADD_TEST(suite, "strutil", dirname); ADD_TEST(suite, "strutil", basename); + ADD_TEST(suite, "strutil", topdir); ADD_TEST(suite, "vector", initial_size_one); ADD_TEST(suite, "vector", remove);