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)
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)
......
......@@ -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;
......
......@@ -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;
/* 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) {
strcpy(dir, ".");
return 1;
Exit:
result = len;
if (buffer == NULL) {
return result;
}
if (len > (int)bufflen-1) {
len = (int)bufflen-1;
result = GIT_ENOMEM;
}
if (s == path) { /* "/[aaa]" */
strcpy(dir, "/");
return 1;
if (len >= 0) {
memcpy(buffer, startp, len);
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);
dir[len] = '\0';
if (git__dirname_r(dname, len, path) < GIT_SUCCESS) {
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;
size_t len;
char *bname = NULL;
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) {
strcpy(base, ".");
return 1;
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];
}
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);
base[len] = '\0';
*output = 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)
......
......@@ -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
......
......@@ -61,61 +61,76 @@ BEGIN_TEST("strutil", suffix_comparison)
END_TEST
BEGIN_TEST("strutil", dirname)
char dir[64];
must_be_true(!(git__dirname(dir, sizeof(dir), NULL) < 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), "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, "/"));
char dir[64], *dir2;
#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); \
}
/* 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"));
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), "/usr/lib") < 0));
must_be_true(!strcmp(dir, "/usr"));
#undef DIRNAME_TEST
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;
#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));
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), "") < 0));
must_be_true(!strcmp(base, "."));
#undef BASENAME_TEST
must_be_true(!(git__basename(base, sizeof(base), "a") < 0));
must_be_true(!strcmp(base, "a"));
END_TEST
must_be_true(!(git__basename(base, sizeof(base), "/") < 0));
must_be_true(!strcmp(base, "/"));
BEGIN_TEST("strutil", topdir)
const char *dir;
must_be_true(!(git__basename(base, sizeof(base), "/usr") < 0));
must_be_true(!strcmp(base, "usr"));
#define TOPDIR_TEST(A, B) { \
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) */
must_be_true(!(git__basename(base, sizeof(base), "/usr/") < 0));
must_be_true(!strcmp(base, ""));
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);
must_be_true(!(git__basename(base, sizeof(base), "usr/lib") < 0));
must_be_true(!strcmp(base, "lib"));
#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);
......
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