diff --git a/dir-iterator.c b/dir-iterator.c index f58a97e089..de619846f2 100644 --- a/dir-iterator.c +++ b/dir-iterator.c @@ -2,10 +2,19 @@ #include "dir.h" #include "iterator.h" #include "dir-iterator.h" +#include "string-list.h" struct dir_iterator_level { DIR *dir; + /* + * The directory entries of the current level. This list will only be + * populated when the iterator is ordered. In that case, `dir` will be + * set to `NULL`. + */ + struct string_list entries; + size_t entries_idx; + /* * The length of the directory part of path at this level * (including a trailing '/'): @@ -43,6 +52,31 @@ struct dir_iterator_int { unsigned int flags; }; +static int next_directory_entry(DIR *dir, const char *path, + struct dirent **out) +{ + struct dirent *de; + +repeat: + errno = 0; + de = readdir(dir); + if (!de) { + if (errno) { + warning_errno("error reading directory '%s'", + path); + return -1; + } + + return 1; + } + + if (is_dot_or_dotdot(de->d_name)) + goto repeat; + + *out = de; + return 0; +} + /* * Push a level in the iter stack and initialize it with information from * the directory pointed by iter->base->path. It is assumed that this @@ -72,6 +106,35 @@ static int push_level(struct dir_iterator_int *iter) return -1; } + string_list_init_dup(&level->entries); + level->entries_idx = 0; + + /* + * When the iterator is sorted we read and sort all directory entries + * directly. + */ + if (iter->flags & DIR_ITERATOR_SORTED) { + struct dirent *de; + + while (1) { + int ret = next_directory_entry(level->dir, iter->base.path.buf, &de); + if (ret < 0) { + if (errno != ENOENT && + iter->flags & DIR_ITERATOR_PEDANTIC) + return -1; + continue; + } else if (ret > 0) { + break; + } + + string_list_append(&level->entries, de->d_name); + } + string_list_sort(&level->entries); + + closedir(level->dir); + level->dir = NULL; + } + return 0; } @@ -88,6 +151,7 @@ static int pop_level(struct dir_iterator_int *iter) warning_errno("error closing directory '%s'", iter->base.path.buf); level->dir = NULL; + string_list_clear(&level->entries, 0); return --iter->levels_nr; } @@ -139,27 +203,34 @@ int dir_iterator_advance(struct dir_iterator *dir_iterator) struct dirent *de; struct dir_iterator_level *level = &iter->levels[iter->levels_nr - 1]; + const char *name; strbuf_setlen(&iter->base.path, level->prefix_len); - errno = 0; - de = readdir(level->dir); - if (!de) { - if (errno) { - warning_errno("error reading directory '%s'", - iter->base.path.buf); + if (level->dir) { + int ret = next_directory_entry(level->dir, iter->base.path.buf, &de); + if (ret < 0) { if (iter->flags & DIR_ITERATOR_PEDANTIC) goto error_out; - } else if (pop_level(iter) == 0) { - return dir_iterator_abort(dir_iterator); + continue; + } else if (ret > 0) { + if (pop_level(iter) == 0) + return dir_iterator_abort(dir_iterator); + continue; } - continue; + + name = de->d_name; + } else { + if (level->entries_idx >= level->entries.nr) { + if (pop_level(iter) == 0) + return dir_iterator_abort(dir_iterator); + continue; + } + + name = level->entries.items[level->entries_idx++].string; } - if (is_dot_or_dotdot(de->d_name)) - continue; - - if (prepare_next_entry_data(iter, de->d_name)) { + if (prepare_next_entry_data(iter, name)) { if (errno != ENOENT && iter->flags & DIR_ITERATOR_PEDANTIC) goto error_out; continue; @@ -188,6 +259,8 @@ int dir_iterator_abort(struct dir_iterator *dir_iterator) warning_errno("error closing directory '%s'", iter->base.path.buf); } + + string_list_clear(&level->entries, 0); } free(iter->levels); diff --git a/dir-iterator.h b/dir-iterator.h index 479e1ec784..6d438809b6 100644 --- a/dir-iterator.h +++ b/dir-iterator.h @@ -54,8 +54,11 @@ * and ITER_ERROR is returned immediately. In both cases, a meaningful * warning is emitted. Note: ENOENT errors are always ignored so that * the API users may remove files during iteration. + * + * - DIR_ITERATOR_SORTED: sort directory entries alphabetically. */ #define DIR_ITERATOR_PEDANTIC (1 << 0) +#define DIR_ITERATOR_SORTED (1 << 1) struct dir_iterator { /* The current path: */