From edbfc52cdd8657371c53070c5e09b58e004bb67a Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Wed, 29 Apr 2015 11:05:27 -0400 Subject: [PATCH] git_path: introduce 'git_path_diriter' Introduce a new `git_path_diriter` that can iterate directories efficiently for each platform. --- src/path.c | 130 +++++++++++++++++++++++++++++++++++++++++++++++ src/path.h | 32 ++++++++++++ src/posix.h | 1 - src/unix/posix.h | 1 + 4 files changed, 163 insertions(+), 1 deletion(-) diff --git a/src/path.c b/src/path.c index 6a636bbd2..2390b4fd0 100644 --- a/src/path.c +++ b/src/path.c @@ -260,6 +260,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) { @@ -1181,6 +1195,122 @@ int git_path_with_stat_cmp_icase(const void *a, const void *b) return strcasecmp(psa->path, psb->path); } +int git_path_diriter_init( + git_path_diriter *diriter, + const char *path, + unsigned int flags) +{ + assert(diriter && path); + + memset(diriter, 0, sizeof(git_path_diriter)); + + if (git_buf_puts(&diriter->path, path) < 0) + return -1; + + git_path_mkposix(diriter->path.ptr); + git_path_trim_slashes(&diriter->path); + + 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); +#endif + + diriter->parent_len = diriter->path.size; + diriter->flags = flags; + + return 0; +} + +int git_path_diriter_next( + const char **out, + size_t *out_len, + 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; + + assert(out && out_len && diriter); + + *out = NULL; + *out_len = 0; + + errno = 0; + + 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)); + + filename = de->d_name; + filename_len = strlen(filename); + +#ifdef GIT_USE_ICONV + if ((error = git_path_iconv(&diriter->ic, &filename, &filename_len)) < 0) + return error; +#endif + + 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; + + *out = &diriter->path.ptr[diriter->parent_len+1]; + *out_len = filename_len; + + return error; +} + +int git_path_diriter_fullpath( + const char **out, + size_t *out_len, + git_path_diriter *diriter) +{ + assert(out && out_len && diriter); + + *out = diriter->path.ptr; + *out_len = diriter->path.size; + return 0; +} + +int git_path_diriter_stat(struct stat *out, git_path_diriter *diriter) +{ + assert(out && diriter); + + return git_path_lstat(diriter->path.ptr, out); +} + +void git_path_diriter_free(git_path_diriter *diriter) +{ + if (diriter == NULL) + return; + + closedir(diriter->dir); + +#ifdef GIT_USE_ICONV + git_path_iconv_clear(&diriter->ic); +#endif + + git_buf_free(&diriter->path); +} + int git_path_dirload_with_stat( const char *path, size_t prefix_len, diff --git a/src/path.h b/src/path.h index 440b5420c..3a25d4aed 100644 --- a/src/path.h +++ b/src/path.h @@ -273,6 +273,7 @@ extern int git_path_apply_relative(git_buf *target, const char *relpath); enum { GIT_PATH_DIR_IGNORE_CASE = (1u << 0), GIT_PATH_DIR_PRECOMPOSE_UNICODE = (1u << 1), + GIT_PATH_DIR_INCLUDE_DOT_AND_DOTDOT = (1u << 2), }; /** @@ -326,6 +327,37 @@ extern int git_path_walk_up( int (*callback)(void *payload, const char *path), void *payload); +typedef struct git_path_diriter git_path_diriter; + +struct git_path_diriter +{ + git_buf path; + size_t parent_len; + + unsigned int flags; + + DIR *dir; +}; + +extern int git_path_diriter_init( + git_path_diriter *diriter, + const char *path, + unsigned int flags); + +extern int git_path_diriter_next( + const char **out, + size_t *out_len, + git_path_diriter *diriter); + +extern int git_path_diriter_fullpath( + const char **out, + size_t *out_len, + git_path_diriter *diriter); + +extern int git_path_diriter_stat(struct stat *out, git_path_diriter *diriter); + +extern void git_path_diriter_free(git_path_diriter *diriter); + /** * Load all directory entries (except '.' and '..') into a vector. * diff --git a/src/posix.h b/src/posix.h index 22f472c90..8785a4c99 100644 --- a/src/posix.h +++ b/src/posix.h @@ -122,7 +122,6 @@ extern int git__page_size(size_t *page_size); #include "strnlen.h" #ifdef NO_READDIR_R -# include GIT_INLINE(int) p_readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result) { GIT_UNUSED(entry); diff --git a/src/unix/posix.h b/src/unix/posix.h index e4f3ac67a..8b4f427f7 100644 --- a/src/unix/posix.h +++ b/src/unix/posix.h @@ -8,6 +8,7 @@ #define INCLUDE_posix__unix_h__ #include +#include #include typedef int GIT_SOCKET;