summaryrefslogtreecommitdiff
path: root/src/path.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/path.c')
-rw-r--r--src/path.c413
1 files changed, 275 insertions, 138 deletions
diff --git a/src/path.c b/src/path.c
index 6a636bbd2..df6762c3a 100644
--- a/src/path.c
+++ b/src/path.c
@@ -10,6 +10,7 @@
#include "repository.h"
#ifdef GIT_WIN32
#include "win32/posix.h"
+#include "win32/buffer.h"
#include "win32/w32_util.h"
#else
#include <dirent.h>
@@ -260,6 +261,20 @@ int git_path_root(const char *path)
return -1; /* Not a real error - signals that path is not rooted */
}
+void git_path_trim_slashes(git_buf *path)
+{
+ int ceiling = git_path_root(path->ptr) + 1;
+ assert(ceiling >= 0);
+
+ while (path->size > (size_t)ceiling) {
+ if (path->ptr[path->size-1] != '/')
+ break;
+
+ path->ptr[path->size-1] = '\0';
+ path->size--;
+ }
+}
+
int git_path_join_unrooted(
git_buf *path_out, const char *path, const char *base, ssize_t *root_at)
{
@@ -1064,205 +1079,327 @@ int git_path_direach(
return error;
}
-static int entry_path_alloc(
- char **out,
+#if defined(GIT_WIN32) && !defined(__MINGW32__)
+
+/* Using _FIND_FIRST_EX_LARGE_FETCH may increase performance in Windows 7
+ * and better. Prior versions will ignore this.
+ */
+#ifndef FIND_FIRST_EX_LARGE_FETCH
+# define FIND_FIRST_EX_LARGE_FETCH 2
+#endif
+
+int git_path_diriter_init(
+ git_path_diriter *diriter,
const char *path,
- size_t path_len,
- const char *de_path,
- size_t de_len,
- size_t alloc_extra)
+ unsigned int flags)
{
- int need_slash = (path_len > 0 && path[path_len-1] != '/') ? 1 : 0;
- size_t alloc_size;
- char *entry_path;
+ git_win32_path path_filter;
+ git_buf hack = {0};
- GITERR_CHECK_ALLOC_ADD(&alloc_size, path_len, de_len);
- GITERR_CHECK_ALLOC_ADD(&alloc_size, alloc_size, need_slash);
- GITERR_CHECK_ALLOC_ADD(&alloc_size, alloc_size, 1);
- GITERR_CHECK_ALLOC_ADD(&alloc_size, alloc_size, alloc_extra);
- entry_path = git__calloc(1, alloc_size);
- GITERR_CHECK_ALLOC(entry_path);
+ assert(diriter && path);
- if (path_len)
- memcpy(entry_path, path, path_len);
+ memset(diriter, 0, sizeof(git_path_diriter));
+ diriter->handle = INVALID_HANDLE_VALUE;
- if (need_slash)
- entry_path[path_len] = '/';
+ if (git_buf_puts(&diriter->path_utf8, path) < 0)
+ return -1;
- memcpy(&entry_path[path_len + need_slash], de_path, de_len);
+ git_path_trim_slashes(&diriter->path_utf8);
- *out = entry_path;
+ if (diriter->path_utf8.size == 0) {
+ giterr_set(GITERR_FILESYSTEM, "Could not open directory '%s'", path);
+ return -1;
+ }
+
+ if ((diriter->parent_len = git_win32_path_from_utf8(diriter->path, diriter->path_utf8.ptr)) < 0 ||
+ !git_win32__findfirstfile_filter(path_filter, diriter->path_utf8.ptr)) {
+ giterr_set(GITERR_OS, "Could not parse the directory path '%s'", path);
+ return -1;
+ }
+
+ diriter->handle = FindFirstFileExW(
+ path_filter,
+ FindExInfoBasic,
+ &diriter->current,
+ FindExSearchNameMatch,
+ NULL,
+ FIND_FIRST_EX_LARGE_FETCH);
+
+ if (diriter->handle == INVALID_HANDLE_VALUE) {
+ giterr_set(GITERR_OS, "Could not open directory '%s'", path);
+ return -1;
+ }
+
+ diriter->parent_utf8_len = diriter->path_utf8.size;
+ diriter->flags = flags;
return 0;
}
-int git_path_dirload(
- const char *path,
- size_t prefix_len,
- size_t alloc_extra,
- unsigned int flags,
- git_vector *contents)
+static int diriter_update_paths(git_path_diriter *diriter)
{
- int error;
- DIR *dir;
- size_t path_len;
- path_dirent_data de_data;
- struct dirent *de, *de_buf = (struct dirent *)&de_data;
+ size_t filename_len, path_len;
-#ifdef GIT_USE_ICONV
- git_path_iconv_t ic = GIT_PATH_ICONV_INIT;
-#endif
+ filename_len = wcslen(diriter->current.cFileName);
- GIT_UNUSED(flags);
+ if (GIT_ADD_SIZET_OVERFLOW(&path_len, diriter->parent_len, filename_len) ||
+ GIT_ADD_SIZET_OVERFLOW(&path_len, path_len, 2))
+ return -1;
+
+ if (path_len > GIT_WIN_PATH_UTF16) {
+ giterr_set(GITERR_FILESYSTEM,
+ "invalid path '%.*ls\\%ls' (path too long)",
+ diriter->parent_len, diriter->path, diriter->current.cFileName);
+ return -1;
+ }
+
+ diriter->path[diriter->parent_len] = L'\\';
+ memcpy(&diriter->path[diriter->parent_len+1],
+ diriter->current.cFileName, filename_len * sizeof(wchar_t));
+ diriter->path[path_len-1] = L'\0';
+
+ git_buf_truncate(&diriter->path_utf8, diriter->parent_utf8_len);
+ git_buf_putc(&diriter->path_utf8, '/');
+ git_buf_put_w(&diriter->path_utf8, diriter->current.cFileName, filename_len);
+
+ if (git_buf_oom(&diriter->path_utf8))
+ return -1;
+
+ return 0;
+}
+
+int git_path_diriter_next(git_path_diriter *diriter)
+{
+ bool skip_dot = !(diriter->flags & GIT_PATH_DIR_INCLUDE_DOT_AND_DOTDOT);
+
+ do {
+ /* Our first time through, we already have the data from
+ * FindFirstFileW. Use it, otherwise get the next file.
+ */
+ if (!diriter->needs_next)
+ diriter->needs_next = 1;
+ else if (!FindNextFileW(diriter->handle, &diriter->current))
+ return GIT_ITEROVER;
+ } while (skip_dot && git_path_is_dot_or_dotdotW(diriter->current.cFileName));
+
+ if (diriter_update_paths(diriter) < 0)
+ return -1;
+
+ return 0;
+}
+
+int git_path_diriter_filename(
+ const char **out,
+ size_t *out_len,
+ git_path_diriter *diriter)
+{
+ assert(out && out_len && diriter);
+
+ assert(diriter->path_utf8.size > diriter->parent_utf8_len);
+
+ *out = &diriter->path_utf8.ptr[diriter->parent_utf8_len+1];
+ *out_len = diriter->path_utf8.size - diriter->parent_utf8_len - 1;
+ return 0;
+}
+
+int git_path_diriter_fullpath(
+ const char **out,
+ size_t *out_len,
+ git_path_diriter *diriter)
+{
+ assert(out && out_len && diriter);
+
+ *out = diriter->path_utf8.ptr;
+ *out_len = diriter->path_utf8.size;
+ return 0;
+}
+
+int git_path_diriter_stat(struct stat *out, git_path_diriter *diriter)
+{
+ assert(out && diriter);
+
+ return git_win32__file_attribute_to_stat(out,
+ (WIN32_FILE_ATTRIBUTE_DATA *)&diriter->current,
+ diriter->path);
+}
+
+void git_path_diriter_free(git_path_diriter *diriter)
+{
+ if (diriter == NULL)
+ return;
+
+ if (diriter->handle != INVALID_HANDLE_VALUE) {
+ FindClose(diriter->handle);
+ diriter->handle = INVALID_HANDLE_VALUE;
+ }
+}
+
+#else
+
+int git_path_diriter_init(
+ git_path_diriter *diriter,
+ const char *path,
+ unsigned int flags)
+{
+ assert(diriter && path);
- assert(path && contents);
+ memset(diriter, 0, sizeof(git_path_diriter));
+
+ if (git_buf_puts(&diriter->path, path) < 0)
+ return -1;
- path_len = strlen(path);
+ git_path_trim_slashes(&diriter->path);
- if (!path_len || path_len < prefix_len) {
- giterr_set(GITERR_INVALID, "Invalid directory path '%s'", path);
+ if (diriter->path.size == 0) {
+ giterr_set(GITERR_FILESYSTEM, "Could not open directory '%s'", path);
return -1;
}
- if ((dir = opendir(path)) == NULL) {
+
+ if ((diriter->dir = opendir(diriter->path.ptr)) == NULL) {
+ git_buf_free(&diriter->path);
+
giterr_set(GITERR_OS, "Failed to open directory '%s'", path);
return -1;
}
#ifdef GIT_USE_ICONV
if ((flags & GIT_PATH_DIR_PRECOMPOSE_UNICODE) != 0)
- (void)git_path_iconv_init_precompose(&ic);
+ (void)git_path_iconv_init_precompose(&diriter->ic);
#endif
- path += prefix_len;
- path_len -= prefix_len;
+ diriter->parent_len = diriter->path.size;
+ diriter->flags = flags;
- while ((error = p_readdir_r(dir, de_buf, &de)) == 0 && de != NULL) {
- char *entry_path, *de_path = de->d_name;
- size_t de_len = strlen(de_path);
+ return 0;
+}
- if (git_path_is_dot_or_dotdot(de_path))
- continue;
+int git_path_diriter_next(git_path_diriter *diriter)
+{
+ struct dirent *de;
+ const char *filename;
+ size_t filename_len;
+ bool skip_dot = !(diriter->flags & GIT_PATH_DIR_INCLUDE_DOT_AND_DOTDOT);
+ int error = 0;
-#ifdef GIT_USE_ICONV
- if ((error = git_path_iconv(&ic, &de_path, &de_len)) < 0)
- break;
-#endif
+ assert(diriter);
- if ((error = entry_path_alloc(&entry_path,
- path, path_len, de_path, de_len, alloc_extra)) < 0)
- break;
+ errno = 0;
- if ((error = git_vector_insert(contents, entry_path)) < 0) {
- git__free(entry_path);
- break;
+ do {
+ if ((de = readdir(diriter->dir)) == NULL) {
+ if (!errno)
+ return GIT_ITEROVER;
+
+ giterr_set(GITERR_OS,
+ "Could not read directory '%s'", diriter->path);
+ return -1;
}
- }
+ } while (skip_dot && git_path_is_dot_or_dotdot(de->d_name));
- closedir(dir);
+ filename = de->d_name;
+ filename_len = strlen(filename);
#ifdef GIT_USE_ICONV
- git_path_iconv_clear(&ic);
+ if ((diriter->flags & GIT_PATH_DIR_PRECOMPOSE_UNICODE) != 0 &&
+ (error = git_path_iconv(&diriter->ic, (char **)&filename, &filename_len)) < 0)
+ return error;
#endif
- if (error != 0)
- giterr_set(GITERR_OS, "Failed to process directory entry in '%s'", path);
+ git_buf_truncate(&diriter->path, diriter->parent_len);
+ git_buf_putc(&diriter->path, '/');
+ git_buf_put(&diriter->path, filename, filename_len);
+
+ if (git_buf_oom(&diriter->path))
+ return -1;
return error;
}
-int git_path_with_stat_cmp(const void *a, const void *b)
+int git_path_diriter_filename(
+ const char **out,
+ size_t *out_len,
+ git_path_diriter *diriter)
+{
+ assert(out && out_len && diriter);
+
+ assert(diriter->path.size > diriter->parent_len);
+
+ *out = &diriter->path.ptr[diriter->parent_len+1];
+ *out_len = diriter->path.size - diriter->parent_len - 1;
+ return 0;
+}
+
+int git_path_diriter_fullpath(
+ const char **out,
+ size_t *out_len,
+ git_path_diriter *diriter)
{
- const git_path_with_stat *psa = a, *psb = b;
- return strcmp(psa->path, psb->path);
+ assert(out && out_len && diriter);
+
+ *out = diriter->path.ptr;
+ *out_len = diriter->path.size;
+ return 0;
}
-int git_path_with_stat_cmp_icase(const void *a, const void *b)
+int git_path_diriter_stat(struct stat *out, git_path_diriter *diriter)
{
- const git_path_with_stat *psa = a, *psb = b;
- return strcasecmp(psa->path, psb->path);
+ assert(out && diriter);
+
+ return git_path_lstat(diriter->path.ptr, out);
}
-int git_path_dirload_with_stat(
- const char *path,
- size_t prefix_len,
- unsigned int flags,
- const char *start_stat,
- const char *end_stat,
- git_vector *contents)
+void git_path_diriter_free(git_path_diriter *diriter)
{
- int error;
- unsigned int i;
- git_path_with_stat *ps;
- git_buf full = GIT_BUF_INIT;
- int (*strncomp)(const char *a, const char *b, size_t sz);
- size_t start_len = start_stat ? strlen(start_stat) : 0;
- size_t end_len = end_stat ? strlen(end_stat) : 0, cmp_len;
-
- if (git_buf_set(&full, path, prefix_len) < 0)
- return -1;
+ if (diriter == NULL)
+ return;
- error = git_path_dirload(
- path, prefix_len, sizeof(git_path_with_stat) + 1, flags, contents);
- if (error < 0) {
- git_buf_free(&full);
- return error;
+ if (diriter->dir) {
+ closedir(diriter->dir);
+ diriter->dir = NULL;
}
- strncomp = (flags & GIT_PATH_DIR_IGNORE_CASE) != 0 ?
- git__strncasecmp : git__strncmp;
+#ifdef GIT_USE_ICONV
+ git_path_iconv_clear(&diriter->ic);
+#endif
- /* stat struct at start of git_path_with_stat, so shift path text */
- 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;
- }
+ git_buf_free(&diriter->path);
+}
- git_vector_foreach(contents, i, ps) {
- /* skip if before start_stat or after end_stat */
- cmp_len = min(start_len, ps->path_len);
- if (cmp_len && strncomp(ps->path, start_stat, cmp_len) < 0)
- continue;
- cmp_len = min(end_len, ps->path_len);
- if (cmp_len && strncomp(ps->path, end_stat, cmp_len) > 0)
- continue;
+#endif
- git_buf_truncate(&full, prefix_len);
+int git_path_dirload(
+ git_vector *contents,
+ const char *path,
+ size_t prefix_len,
+ unsigned int flags)
+{
+ git_path_diriter iter = GIT_PATH_DIRITER_INIT;
+ const char *name;
+ size_t name_len;
+ char *dup;
+ int error;
- if ((error = git_buf_joinpath(&full, full.ptr, ps->path)) < 0 ||
- (error = git_path_lstat(full.ptr, &ps->st)) < 0) {
+ assert(contents && path);
- if (error == GIT_ENOTFOUND) {
- /* file was removed between readdir and lstat */
- char *entry_path = git_vector_get(contents, i);
- git_vector_remove(contents, i--);
- git__free(entry_path);
- } else {
- /* Treat the file as unreadable if we get any other error */
- memset(&ps->st, 0, sizeof(ps->st));
- ps->st.st_mode = GIT_FILEMODE_UNREADABLE;
- }
+ if ((error = git_path_diriter_init(&iter, path, flags)) < 0)
+ return error;
- giterr_clear();
- error = 0;
- continue;
- }
+ while ((error = git_path_diriter_next(&iter)) == 0) {
+ if ((error = git_path_diriter_fullpath(&name, &name_len, &iter)) < 0)
+ break;
- if (S_ISDIR(ps->st.st_mode)) {
- ps->path[ps->path_len++] = '/';
- ps->path[ps->path_len] = '\0';
- }
- else if (!S_ISREG(ps->st.st_mode) && !S_ISLNK(ps->st.st_mode)) {
- char *entry_path = git_vector_get(contents, i);
- git_vector_remove(contents, i--);
- git__free(entry_path);
- }
- }
+ assert(name_len > prefix_len);
+
+ dup = git__strndup(name + prefix_len, name_len - prefix_len);
+ GITERR_CHECK_ALLOC(dup);
- /* sort now that directory suffix is added */
- git_vector_sort(contents);
+ if ((error = git_vector_insert(contents, dup)) < 0)
+ break;
+ }
- git_buf_free(&full);
+ if (error == GIT_ITEROVER)
+ error = 0;
+ git_path_diriter_free(&iter);
return error;
}