Commit f725931b by Vicent Marti

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 <tanoku@gmail.com>
parent c836c332
...@@ -59,7 +59,7 @@ static int make_temp_file(git_file *fd, char *tmp, size_t n, char *file) ...@@ -59,7 +59,7 @@ static int make_temp_file(git_file *fd, char *tmp, size_t n, char *file)
size_t tmplen = strlen(template); size_t tmplen = strlen(template);
int dirlen; int dirlen;
if ((dirlen = git__dirname(tmp, n, file)) < 0) if ((dirlen = git__dirname_r(tmp, n, file)) < 0)
return GIT_ERROR; return GIT_ERROR;
if ((dirlen + tmplen) >= n) if ((dirlen + tmplen) >= n)
......
...@@ -157,7 +157,9 @@ static int assign_repository_DIRs(git_repository *repo, ...@@ -157,7 +157,9 @@ static int assign_repository_DIRs(git_repository *repo,
static int guess_repository_DIRs(git_repository *repo, const char *repository_path) 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 path_len;
int error = GIT_SUCCESS; int error = GIT_SUCCESS;
...@@ -185,12 +187,10 @@ static int guess_repository_DIRs(git_repository *repo, const char *repository_pa ...@@ -185,12 +187,10 @@ static int guess_repository_DIRs(git_repository *repo, const char *repository_pa
path_aux[path_len] = 0; 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 != '/') if (strcmp(topdir, GIT_DIR) == 0) {
last_DIR--;
if (strcmp(last_DIR, GIT_DIR) == 0) {
repo->is_bare = 0; repo->is_bare = 0;
/* index file */ /* index file */
...@@ -198,8 +198,9 @@ static int guess_repository_DIRs(git_repository *repo, const char *repository_pa ...@@ -198,8 +198,9 @@ static int guess_repository_DIRs(git_repository *repo, const char *repository_pa
repo->path_index = git__strdup(path_aux); repo->path_index = git__strdup(path_aux);
/* working dir */ /* working dir */
*(last_DIR + 1) = 0; repo->path_workdir = git__dirname(path_aux);
repo->path_workdir = git__strdup(path_aux); if (repo->path_workdir == NULL)
return GIT_EINVALIDPATH;
} else { } else {
repo->is_bare = 1; repo->is_bare = 1;
......
...@@ -36,65 +36,193 @@ int git__suffixcmp(const char *str, const char *suffix) ...@@ -36,65 +36,193 @@ int git__suffixcmp(const char *str, const char *suffix)
return strcmp(str + (a - b), 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; const char *endp, *startp;
size_t len; int len, result;
/* Empty or NULL string gets treated as "." */
if (path == NULL || *path == '\0') {
startp = ".";
len = 1;
goto Exit;
}
/* 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;
}
/* Find the start of the base */
startp = endp;
while (startp > path && *(startp - 1) != '/')
startp--;
assert(dir && n > 1); len = endp - startp +1;
if (!path || !*path || (s = strrchr(path, '/')) == NULL) { Exit:
strcpy(dir, "."); result = len;
return 1; if (buffer == NULL) {
return result;
}
if (len > (int)bufflen-1) {
len = (int)bufflen-1;
result = GIT_ENOMEM;
} }
if (s == path) { /* "/[aaa]" */ if (len >= 0) {
strcpy(dir, "/"); memcpy(buffer, startp, len);
return 1; buffer[len] = 0;
} }
return result;
}
if ((len = s - path) >= n) /*
return GIT_ERROR; * 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)
{
const char *endp;
int result, len;
/* Empty or NULL string gets treated as "." */
if (path == NULL || *path == '\0') {
path = ".";
len = 1;
goto Exit;
}
/* 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;
memcpy(dir, path, len); if (git__dirname_r(dname, len, path) < GIT_SUCCESS) {
dir[len] = '\0'; free(dname);
return NULL;
}
return len; return dname;
} }
int git__basename(char *base, size_t n, char *path) char *git__basename(const char *path)
{ {
char *s; char *bname = NULL;
size_t len; int len;
assert(base && n > 1); len = (path ? strlen(path) : 0) + 2;
bname = (char *)git__malloc(len);
if (bname == NULL)
return NULL;
if (!path || !*path) { if (git__basename_r(bname, len, path) < GIT_SUCCESS) {
strcpy(base, "."); free(bname);
return 1; return NULL;
} }
return bname;
}
const char *git__topdir(const char *path)
{
size_t len;
int i;
assert(path);
len = strlen(path); len = strlen(path);
if ((s = strrchr(path, '/')) == NULL) { if (!len || path[len - 1] != '/')
if (len >= n) return NULL;
return GIT_ERROR;
strcpy(base, path);
return len;
}
if (s == path && len == 1) { /* "/" */ for (i = len - 2; i >= 0; --i)
strcpy(base, "/"); if (path[i] == '/')
return 1; break;
}
len -= s - path; return &path[i + 1];
if (len >= n) }
return GIT_ERROR;
static char *strtok_raw(char *output, char *src, char *delimit, int keep)
{
while (*src && strchr(delimit, *src) == NULL)
*output++ = *src++;
memcpy(base, s+1, len); *output = 0;
base[len] = '\0';
if (keep)
return src;
else
return *src ? src+1 : src;
}
return len; 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) void git__hexdump(const char *buffer, size_t len)
......
...@@ -19,8 +19,44 @@ extern int git__fmt(char *, size_t, const char *, ...) ...@@ -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__prefixcmp(const char *str, const char *prefix);
extern int git__suffixcmp(const char *str, const char *suffix); 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 void git__hexdump(const char *buffer, size_t n);
extern uint32_t git__hash(const void *key, int len, uint32_t seed); 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) ...@@ -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)))) # define git__rotl(v, s) (uint32_t)(((uint32_t)(v) << (s)) | ((uint32_t)(v) >> (32 - (s))))
#endif #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 * Realloc the buffer pointed at by variable 'x' so that it can hold
* at least 'nr' entries; the number of entries currently allocated * at least 'nr' entries; the number of entries currently allocated
......
...@@ -61,61 +61,76 @@ BEGIN_TEST("strutil", suffix_comparison) ...@@ -61,61 +61,76 @@ BEGIN_TEST("strutil", suffix_comparison)
END_TEST END_TEST
BEGIN_TEST("strutil", dirname) BEGIN_TEST("strutil", dirname)
char dir[64]; char dir[64], *dir2;
must_be_true(!(git__dirname(dir, sizeof(dir), NULL) < 0)); #define DIRNAME_TEST(A, B) { \
must_be_true(!strcmp(dir, ".")); must_be_true(git__dirname_r(dir, sizeof(dir), A) >= 0); \
must_be_true(strcmp(dir, B) == 0); \
must_be_true(!(git__dirname(dir, sizeof(dir), "") < 0)); must_be_true((dir2 = git__dirname(A)) != NULL); \
must_be_true(!strcmp(dir, ".")); must_be_true(strcmp(dir2, B) == 0); \
free(dir2); \
must_be_true(!(git__dirname(dir, sizeof(dir), "a") < 0)); }
must_be_true(!strcmp(dir, "."));
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) */ DIRNAME_TEST(NULL, ".");
must_be_true(!(git__dirname(dir, sizeof(dir), "/usr/") < 0)); DIRNAME_TEST("", ".");
must_be_true(!strcmp(dir, "/usr")); 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), "/usr/lib") < 0)); #undef DIRNAME_TEST
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 END_TEST
BEGIN_TEST("strutil", basename) BEGIN_TEST("strutil", basename)
char base[64]; char base[64], *base2;
#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), NULL) < 0)); BASENAME_TEST(NULL, ".");
must_be_true(!strcmp(base, ".")); 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), "") < 0)); #undef BASENAME_TEST
must_be_true(!strcmp(base, "."));
must_be_true(!(git__basename(base, sizeof(base), "a") < 0)); END_TEST
must_be_true(!strcmp(base, "a"));
must_be_true(!(git__basename(base, sizeof(base), "/") < 0)); BEGIN_TEST("strutil", topdir)
must_be_true(!strcmp(base, "/")); const char *dir;
must_be_true(!(git__basename(base, sizeof(base), "/usr") < 0)); #define TOPDIR_TEST(A, B) { \
must_be_true(!strcmp(base, "usr")); must_be_true((dir = git__topdir(A)) != NULL); \
must_be_true(strcmp(dir, B) == 0); \
}
/* TODO: should this be "usr" instead (ie strip trailing / first) */ TOPDIR_TEST(".git/", ".git/");
must_be_true(!(git__basename(base, sizeof(base), "/usr/") < 0)); TOPDIR_TEST("/.git/", ".git/");
must_be_true(!strcmp(base, "")); 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(git__topdir("/usr/.git") == NULL);
must_be_true(!strcmp(base, "lib")); must_be_true(git__topdir(".") == NULL);
must_be_true(git__topdir("") == NULL);
must_be_true(git__topdir("a") == NULL);
must_be_true(!(git__basename(base, sizeof(base), "usr/lib") < 0)); #undef TOPDIR_TEST
must_be_true(!strcmp(base, "lib"));
END_TEST END_TEST
/* Initial size of 1 will cause writing past array bounds prior to fix */ /* Initial size of 1 will cause writing past array bounds prior to fix */
...@@ -579,6 +594,7 @@ git_testsuite *libgit2_suite_core(void) ...@@ -579,6 +594,7 @@ git_testsuite *libgit2_suite_core(void)
ADD_TEST(suite, "strutil", suffix_comparison); ADD_TEST(suite, "strutil", suffix_comparison);
ADD_TEST(suite, "strutil", dirname); ADD_TEST(suite, "strutil", dirname);
ADD_TEST(suite, "strutil", basename); ADD_TEST(suite, "strutil", basename);
ADD_TEST(suite, "strutil", topdir);
ADD_TEST(suite, "vector", initial_size_one); ADD_TEST(suite, "vector", initial_size_one);
ADD_TEST(suite, "vector", remove); ADD_TEST(suite, "vector", remove);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment