ext/rugged/vendor/libgit2-dist/src/path.c in rugged-0.16.0 vs ext/rugged/vendor/libgit2-dist/src/path.c in rugged-0.17.0b1

- old
+ new

@@ -1,24 +1,33 @@ /* - * Copyright (C) 2009-2011 the libgit2 contributors + * Copyright (C) 2009-2012 the libgit2 contributors * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file. */ #include "common.h" #include "path.h" #include "posix.h" - +#ifdef GIT_WIN32 +#include "win32/dir.h" +#include "win32/posix.h" +#else +#include <dirent.h> +#endif #include <stdarg.h> #include <stdio.h> #include <ctype.h> +#ifdef GIT_WIN32 +#define LOOKS_LIKE_DRIVE_PREFIX(S) (git__isalpha((S)[0]) && (S)[1] == ':') +#endif + /* * Based on the Android implementation, BSD licensed. * Check http://android.git.kernel.org/ */ -int git_path_basename_r(char *buffer, size_t bufflen, const char *path) +int git_path_basename_r(git_buf *buffer, const char *path) { const char *endp, *startp; int len, result; /* Empty or NULL string gets treated as "." */ @@ -43,34 +52,27 @@ /* Find the start of the base */ startp = endp; while (startp > path && *(startp - 1) != '/') startp--; - len = endp - startp +1; + /* Cast is safe because max path < max int */ + len = (int)(endp - startp + 1); Exit: result = len; - if (buffer == NULL) { - return result; - } - if (len > (int)bufflen-1) { - len = (int)bufflen-1; - result = GIT_ENOMEM; - } - if (len >= 0) { - memmove(buffer, startp, len); - buffer[len] = 0; - } + if (buffer != NULL && git_buf_set(buffer, startp, len) < 0) + return -1; + return result; } /* * Based on the Android implementation, BSD licensed. * Check http://android.git.kernel.org/ */ -int git_path_dirname_r(char *buffer, size_t bufflen, const char *path) +int git_path_dirname_r(git_buf *buffer, const char *path) { const char *endp; int result, len; /* Empty or NULL string gets treated as "." */ @@ -98,173 +100,561 @@ do { endp--; } while (endp > path && *endp == '/'); - len = endp - path +1; + /* Cast is safe because max path < max int */ + len = (int)(endp - path + 1); #ifdef GIT_WIN32 /* Mimic unix behavior where '/.git' returns '/': 'C:/.git' will return 'C:/' here */ - if (len == 2 && isalpha(path[0]) && path[1] == ':') { + if (len == 2 && LOOKS_LIKE_DRIVE_PREFIX(path)) { len = 3; goto Exit; } #endif 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 (buffer != NULL && git_buf_set(buffer, path, len) < 0) + return -1; - if (len >= 0) { - memmove(buffer, path, len); - buffer[len] = 0; - } return result; } char *git_path_dirname(const char *path) { - char *dname = NULL; - int len; + git_buf buf = GIT_BUF_INIT; + char *dirname; - len = (path ? strlen(path) : 0) + 2; - dname = (char *)git__malloc(len); - if (dname == NULL) - return NULL; + git_path_dirname_r(&buf, path); + dirname = git_buf_detach(&buf); + git_buf_free(&buf); /* avoid memleak if error occurs */ - if (git_path_dirname_r(dname, len, path) < GIT_SUCCESS) { - git__free(dname); - return NULL; - } - - return dname; + return dirname; } char *git_path_basename(const char *path) { - char *bname = NULL; - int len; + git_buf buf = GIT_BUF_INIT; + char *basename; - len = (path ? strlen(path) : 0) + 2; - bname = (char *)git__malloc(len); - if (bname == NULL) - return NULL; + git_path_basename_r(&buf, path); + basename = git_buf_detach(&buf); + git_buf_free(&buf); /* avoid memleak if error occurs */ - if (git_path_basename_r(bname, len, path) < GIT_SUCCESS) { - git__free(bname); - return NULL; - } - - return bname; + return basename; } const char *git_path_topdir(const char *path) { size_t len; - int i; + ssize_t i; assert(path); len = strlen(path); if (!len || path[len - 1] != '/') return NULL; - for (i = len - 2; i >= 0; --i) + for (i = (ssize_t)len - 2; i >= 0; --i) if (path[i] == '/') break; return &path[i + 1]; } -void git_path_join_n(char *buffer_out, int count, ...) +int git_path_root(const char *path) { - va_list ap; - int i; - char *buffer_start = buffer_out; + int offset = 0; - va_start(ap, count); - for (i = 0; i < count; ++i) { - const char *path; - int len; +#ifdef GIT_WIN32 + /* Does the root of the path look like a windows drive ? */ + if (LOOKS_LIKE_DRIVE_PREFIX(path)) + offset += 2; - path = va_arg(ap, const char *); + /* Are we dealing with a windows network path? */ + else if ((path[0] == '/' && path[1] == '/') || + (path[0] == '\\' && path[1] == '\\')) + { + offset += 2; - assert((i == 0) || path != buffer_start); + /* Skip the computer name segment */ + while (path[offset] && path[offset] != '/' && path[offset] != '\\') + offset++; + } +#endif - if (i > 0 && *path == '/' && buffer_out > buffer_start && buffer_out[-1] == '/') - path++; + if (path[offset] == '/' || path[offset] == '\\') + return offset; - if (!*path) - continue; + return -1; /* Not a real error - signals that path is not rooted */ +} - len = strlen(path); - memmove(buffer_out, path, len); - buffer_out = buffer_out + len; +int git_path_prettify(git_buf *path_out, const char *path, const char *base) +{ + char buf[GIT_PATH_MAX]; - if (i < count - 1 && buffer_out[-1] != '/') - *buffer_out++ = '/'; + assert(path && path_out); + + /* construct path if needed */ + if (base != NULL && git_path_root(path) < 0) { + if (git_buf_joinpath(path_out, base, path) < 0) + return -1; + path = path_out->ptr; } - va_end(ap); - *buffer_out = '\0'; + if (p_realpath(path, buf) == NULL) { + /* giterr_set resets the errno when dealing with a GITERR_OS kind of error */ + int error = (errno == ENOENT || errno == ENOTDIR) ? GIT_ENOTFOUND : -1; + giterr_set(GITERR_OS, "Failed to resolve path '%s'", path); + + git_buf_clear(path_out); + + return error; + } + + return git_buf_sets(path_out, buf); } -int git_path_root(const char *path) +int git_path_prettify_dir(git_buf *path_out, const char *path, const char *base) { - int offset = 0; + int error = git_path_prettify(path_out, path, base); + return (error < 0) ? error : git_path_to_dir(path_out); +} -#ifdef GIT_WIN32 - /* Does the root of the path look like a windows drive ? */ - if (isalpha(path[0]) && (path[1] == ':')) - offset += 2; +int git_path_to_dir(git_buf *path) +{ + if (path->asize > 0 && + git_buf_len(path) > 0 && + path->ptr[git_buf_len(path) - 1] != '/') + git_buf_putc(path, '/'); + + return git_buf_oom(path) ? -1 : 0; +} + +void git_path_string_to_dir(char* path, size_t size) +{ + size_t end = strlen(path); + + if (end && path[end - 1] != '/' && end < size) { + path[end] = '/'; + path[end + 1] = '\0'; + } +} + +int git__percent_decode(git_buf *decoded_out, const char *input) +{ + int len, hi, lo, i; + assert(decoded_out && input); + + len = (int)strlen(input); + git_buf_clear(decoded_out); + + for(i = 0; i < len; i++) + { + char c = input[i]; + + if (c != '%') + goto append; + + if (i >= len - 2) + goto append; + + hi = git__fromhex(input[i + 1]); + lo = git__fromhex(input[i + 2]); + + if (hi < 0 || lo < 0) + goto append; + + c = (char)(hi << 4 | lo); + i += 2; + +append: + if (git_buf_putc(decoded_out, c) < 0) + return -1; + } + + return 0; +} + +static int error_invalid_local_file_uri(const char *uri) +{ + giterr_set(GITERR_CONFIG, "'%s' is not a valid local file URI", uri); + return -1; +} + +int git_path_fromurl(git_buf *local_path_out, const char *file_url) +{ + int offset = 0, len; + + assert(local_path_out && file_url); + + if (git__prefixcmp(file_url, "file://") != 0) + return error_invalid_local_file_uri(file_url); + + offset += 7; + len = (int)strlen(file_url); + + if (offset < len && file_url[offset] == '/') + offset++; + else if (offset < len && git__prefixcmp(file_url + offset, "localhost/") == 0) + offset += 10; + else + return error_invalid_local_file_uri(file_url); + + if (offset >= len || file_url[offset] == '/') + return error_invalid_local_file_uri(file_url); + +#ifndef _MSC_VER + offset--; /* A *nix absolute path starts with a forward slash */ #endif - if (*(path + offset) == '/') - return offset; + git_buf_clear(local_path_out); - return -1; /* Not a real error. Rather a signal than the path is not rooted */ + return git__percent_decode(local_path_out, file_url + offset); } -int git_path_prettify(char *path_out, const char *path, const char *base) +int git_path_walk_up( + git_buf *path, + const char *ceiling, + int (*cb)(void *data, git_buf *), + void *data) { - char *result; + int error = 0; + git_buf iter; + ssize_t stop = 0, scan; + char oldc = '\0'; - if (base == NULL || git_path_root(path) >= 0) { - result = p_realpath(path, path_out); - } else { - char aux_path[GIT_PATH_MAX]; - git_path_join(aux_path, base, path); - result = p_realpath(aux_path, path_out); + assert(path && cb); + + if (ceiling != NULL) { + if (git__prefixcmp(path->ptr, ceiling) == 0) + stop = (ssize_t)strlen(ceiling); + else + stop = git_buf_len(path); } + scan = git_buf_len(path); - return result ? GIT_SUCCESS : GIT_EOSERR; + iter.ptr = path->ptr; + iter.size = git_buf_len(path); + iter.asize = path->asize; + + while (scan >= stop) { + if ((error = cb(data, &iter)) < 0) + break; + iter.ptr[scan] = oldc; + scan = git_buf_rfind_next(&iter, '/'); + if (scan >= 0) { + scan++; + oldc = iter.ptr[scan]; + iter.size = scan; + iter.ptr[scan] = '\0'; + } + } + + if (scan >= 0) + iter.ptr[scan] = oldc; + + return error; } -int git_path_prettify_dir(char *path_out, const char *path, const char *base) +bool git_path_exists(const char *path) { - size_t end; + assert(path); + return p_access(path, F_OK) == 0; +} - if (git_path_prettify(path_out, path, base) < GIT_SUCCESS) - return GIT_EOSERR; +bool git_path_isdir(const char *path) +{ + struct stat st; + if (p_stat(path, &st) < 0) + return false; - end = strlen(path_out); + return S_ISDIR(st.st_mode) != 0; +} - if (end && path_out[end - 1] != '/') { - path_out[end] = '/'; - path_out[end + 1] = '\0'; +bool git_path_isfile(const char *path) +{ + struct stat st; + + assert(path); + if (p_stat(path, &st) < 0) + return false; + + return S_ISREG(st.st_mode) != 0; +} + +int git_path_lstat(const char *path, struct stat *st) +{ + int err = 0; + + if (p_lstat(path, st) < 0) { + err = (errno == ENOENT) ? GIT_ENOTFOUND : -1; + giterr_set(GITERR_OS, "Failed to stat file '%s'", path); } - return GIT_SUCCESS; + return err; +} + +static bool _check_dir_contents( + git_buf *dir, + const char *sub, + bool (*predicate)(const char *)) +{ + bool result; + size_t dir_size = git_buf_len(dir); + size_t sub_size = strlen(sub); + + /* leave base valid even if we could not make space for subdir */ + if (git_buf_try_grow(dir, dir_size + sub_size + 2) < 0) + return false; + + /* save excursion */ + git_buf_joinpath(dir, dir->ptr, sub); + + result = predicate(dir->ptr); + + /* restore path */ + git_buf_truncate(dir, dir_size); + return result; +} + +bool git_path_contains(git_buf *dir, const char *item) +{ + return _check_dir_contents(dir, item, &git_path_exists); +} + +bool git_path_contains_dir(git_buf *base, const char *subdir) +{ + return _check_dir_contents(base, subdir, &git_path_isdir); +} + +bool git_path_contains_file(git_buf *base, const char *file) +{ + return _check_dir_contents(base, file, &git_path_isfile); +} + +int git_path_find_dir(git_buf *dir, const char *path, const char *base) +{ + int error; + + if (base != NULL && git_path_root(path) < 0) + error = git_buf_joinpath(dir, base, path); + else + error = git_buf_sets(dir, path); + + if (!error) { + char buf[GIT_PATH_MAX]; + if (p_realpath(dir->ptr, buf) != NULL) + error = git_buf_sets(dir, buf); + } + + /* call dirname if this is not a directory */ + if (!error && git_path_isdir(dir->ptr) == false) + error = git_path_dirname_r(dir, dir->ptr); + + if (!error) + error = git_path_to_dir(dir); + + return error; +} + +int git_path_cmp( + const char *name1, size_t len1, int isdir1, + const char *name2, size_t len2, int isdir2) +{ + size_t len = len1 < len2 ? len1 : len2; + int cmp; + + cmp = memcmp(name1, name2, len); + if (cmp) + return cmp; + if (len1 < len2) + return (!isdir1 && !isdir2) ? -1 : + (isdir1 ? '/' - name2[len1] : name2[len1] - '/'); + if (len1 > len2) + return (!isdir1 && !isdir2) ? 1 : + (isdir2 ? name1[len2] - '/' : '/' - name1[len2]); + return 0; +} + +/* Taken from git.git */ +GIT_INLINE(int) is_dot_or_dotdot(const char *name) +{ + return (name[0] == '.' && + (name[1] == '\0' || + (name[1] == '.' && name[2] == '\0'))); +} + +int git_path_direach( + git_buf *path, + int (*fn)(void *, git_buf *), + void *arg) +{ + ssize_t wd_len; + DIR *dir; + struct dirent *de, *de_buf; + + if (git_path_to_dir(path) < 0) + return -1; + + wd_len = git_buf_len(path); + + if ((dir = opendir(path->ptr)) == NULL) { + giterr_set(GITERR_OS, "Failed to open directory '%s'", path->ptr); + return -1; + } + +#ifdef __sun + de_buf = git__malloc(sizeof(struct dirent) + FILENAME_MAX + 1); +#else + de_buf = git__malloc(sizeof(struct dirent)); +#endif + + while (p_readdir_r(dir, de_buf, &de) == 0 && de != NULL) { + int result; + + if (is_dot_or_dotdot(de->d_name)) + continue; + + if (git_buf_puts(path, de->d_name) < 0) { + closedir(dir); + git__free(de_buf); + return -1; + } + + result = fn(arg, path); + + git_buf_truncate(path, wd_len); /* restore path */ + + if (result < 0) { + closedir(dir); + git__free(de_buf); + return -1; + } + } + + closedir(dir); + git__free(de_buf); + return 0; +} + +int git_path_dirload( + const char *path, + size_t prefix_len, + size_t alloc_extra, + git_vector *contents) +{ + int error, need_slash; + DIR *dir; + struct dirent *de, *de_buf; + size_t path_len; + + assert(path != NULL && contents != NULL); + path_len = strlen(path); + assert(path_len > 0 && path_len >= prefix_len); + + if ((dir = opendir(path)) == NULL) { + giterr_set(GITERR_OS, "Failed to open directory '%s'", path); + return -1; + } + +#ifdef __sun + de_buf = git__malloc(sizeof(struct dirent) + FILENAME_MAX + 1); +#else + de_buf = git__malloc(sizeof(struct dirent)); +#endif + + path += prefix_len; + path_len -= prefix_len; + need_slash = (path_len > 0 && path[path_len-1] != '/') ? 1 : 0; + + while ((error = p_readdir_r(dir, de_buf, &de)) == 0 && de != NULL) { + char *entry_path; + size_t entry_len; + + if (is_dot_or_dotdot(de->d_name)) + continue; + + entry_len = strlen(de->d_name); + + entry_path = git__malloc( + path_len + need_slash + entry_len + 1 + alloc_extra); + GITERR_CHECK_ALLOC(entry_path); + + if (path_len) + memcpy(entry_path, path, path_len); + if (need_slash) + entry_path[path_len] = '/'; + memcpy(&entry_path[path_len + need_slash], de->d_name, entry_len); + entry_path[path_len + need_slash + entry_len] = '\0'; + + if (git_vector_insert(contents, entry_path) < 0) { + closedir(dir); + git__free(de_buf); + return -1; + } + } + + closedir(dir); + git__free(de_buf); + + if (error != 0) + giterr_set(GITERR_OS, "Failed to process directory entry in '%s'", path); + + return error; +} + +int git_path_with_stat_cmp(const void *a, const void *b) +{ + const git_path_with_stat *psa = a, *psb = b; + return git__strcmp_cb(psa->path, psb->path); +} + +int git_path_dirload_with_stat( + const char *path, + size_t prefix_len, + git_vector *contents) +{ + int error; + unsigned int i; + git_path_with_stat *ps; + git_buf full = GIT_BUF_INIT; + + if (git_buf_set(&full, path, prefix_len) < 0) + return -1; + + error = git_path_dirload( + path, prefix_len, sizeof(git_path_with_stat) + 1, contents); + if (error < 0) { + git_buf_free(&full); + return error; + } + + git_vector_foreach(contents, i, ps) { + size_t path_len = strlen((char *)ps); + + memmove(ps->path, ps, path_len + 1); + ps->path_len = path_len; + + if ((error = git_buf_joinpath(&full, full.ptr, ps->path)) < 0 || + (error = git_path_lstat(full.ptr, &ps->st)) < 0) + break; + + git_buf_truncate(&full, prefix_len); + + if (S_ISDIR(ps->st.st_mode)) { + ps->path[path_len] = '/'; + ps->path[path_len + 1] = '\0'; + } + } + + git_buf_free(&full); + + return error; }