[PATCH v3 1/2] [GSoC] dir_iterator: iterate over dir after its contents
Create an option for the dir_iterator API to iterate over a directory path only after having iterated through its contents. This feature was predicted, although not implemented by 0fe5043 ("dir_iterator: new API for iterating over a directory tree", 2016-06-18).
This is useful for recursively removing a directory and calling rmdir() on a directory only after all of its contents have been wiped.
An "options" member has been added to the dir_iterator struct. It contains the "iterate_dirs_after_files" flag, that enables the feature when set to 1. Default behavior continues to be iterating over directory paths before its contents.
Two inline functions have been added to dir_iterator's code to avoid code repetition inside dir_iterator_advance() and make code more clear.
No particular functions or wrappers for setting the options struct's fields have been added to avoid either breaking the current dir_iterator API or over-engineering an extremely simple option architecture.
Signed-off-by: Daniel Ferreira <bnmvco@gmail.com>
---
dir-iterator.c | 100 ++++++++++++++++++++++++++++++++++++++++++++-------------
dir-iterator.h | 7 ++++
2 files changed, 84 insertions(+), 23 deletions(-)
Show changes to 2 files +84 −24
dir-iterator.c, dir-iterator.h
diff --git a/dir-iterator.c b/dir-iterator.c
index 34182a9..833d56a 100644
--- a/dir-iterator.c
+++ b/dir-iterator.c
@@ -50,6 +50,43 @@ struct dir_iterator_int {
struct dir_iterator_level *levels;
};
+static inline void push_dir_level(struct dir_iterator_int *iter, struct dir_iterator_level *level)
+{
+ level->dir_state = DIR_STATE_RECURSE;
+ ALLOC_GROW(iter->levels, iter->levels_nr + 1,
+ iter->levels_alloc);
+ level = &iter->levels[iter->levels_nr++];
+ level->initialized = 0;
+}
+
+static inline int pop_dir_level(struct dir_iterator_int *iter, struct dir_iterator_level *level)
+{
+ return --iter->levels_nr;
+}
+
+static inline int set_iterator_data(struct dir_iterator_int *iter, struct dir_iterator_level *level)
+{
+ if (lstat(iter->base.path.buf, &iter->base.st) < 0) {
+ if (errno != ENOENT)
+ warning("error reading path '%s': %s",
+ iter->base.path.buf,
+ strerror(errno));
+ return -1;
+ }
+
+ /*
+ * We have to set these each time because
+ * the path strbuf might have been realloc()ed.
+ */
+ iter->base.relative_path =
+ iter->base.path.buf + iter->levels[0].prefix_len;
+ iter->base.basename =
+ iter->base.path.buf + level->prefix_len;
+ level->dir_state = DIR_STATE_ITER;
+
+ return 0;
+}
+
int dir_iterator_advance(struct dir_iterator *dir_iterator)
{
struct dir_iterator_int *iter =
@@ -77,18 +114,16 @@ int dir_iterator_advance(struct dir_iterator *dir_iterator)
}
level->initialized = 1;
- } else if (S_ISDIR(iter->base.st.st_mode)) {
+ } else if (S_ISDIR(iter->base.st.st_mode) &&
+ !iter->base.options.iterate_dirs_after_files) {
if (level->dir_state == DIR_STATE_ITER) {
/*
* The directory was just iterated
* over; now prepare to iterate into
- * it.
+ * it (unless an option is set for us
+ * to do otherwise).
*/
- level->dir_state = DIR_STATE_RECURSE;
- ALLOC_GROW(iter->levels, iter->levels_nr + 1,
- iter->levels_alloc);
- level = &iter->levels[iter->levels_nr++];
- level->initialized = 0;
+ push_dir_level(iter, level);
continue;
} else {
/*
@@ -104,7 +139,7 @@ int dir_iterator_advance(struct dir_iterator *dir_iterator)
* This level is exhausted (or wasn't opened
* successfully); pop up a level.
*/
- if (--iter->levels_nr == 0)
+ if (pop_dir_level(iter, level) == 0)
return dir_iterator_abort(dir_iterator);
continue;
@@ -120,16 +155,33 @@ int dir_iterator_advance(struct dir_iterator *dir_iterator)
de = readdir(level->dir);
if (!de) {
- /* This level is exhausted; pop up a level. */
+ /* This level is exhausted */
if (errno) {
warning("error reading directory %s: %s",
iter->base.path.buf, strerror(errno));
+ } else if (iter->base.options.iterate_dirs_after_files) {
+ /* If we are handling dirpaths after their contents,
+ * we have to iterate over the directory now that we'll
+ * have finished iterating into it. */
+ level->dir = NULL;
+
+ if (pop_dir_level(iter, level) == 0)
+ return dir_iterator_abort(dir_iterator);
+
+ level = &iter->levels[iter->levels_nr - 1];
+ /* Remove a trailing slash */
+ strbuf_strip_suffix(&iter->base.path, "/");
+
+ if (set_iterator_data(iter, level))
+ continue;
+
+ return ITER_OK;
} else if (closedir(level->dir))
warning("error closing directory %s: %s",
iter->base.path.buf, strerror(errno));
level->dir = NULL;
- if (--iter->levels_nr == 0)
+ if (pop_dir_level(iter, level) == 0)
return dir_iterator_abort(dir_iterator);
break;
}
@@ -138,26 +190,26 @@ int dir_iterator_advance(struct dir_iterator *dir_iterator)
continue;
strbuf_addstr(&iter->base.path, de->d_name);
- if (lstat(iter->base.path.buf, &iter->base.st) < 0) {
- if (errno != ENOENT)
- warning("error reading path '%s': %s",
- iter->base.path.buf,
- strerror(errno));
+
+ if (set_iterator_data(iter, level))
continue;
- }
/*
- * We have to set these each time because
- * the path strbuf might have been realloc()ed.
+ * If we want to iterate dirs after files, we shall
+ * begin looking into them *before* we return the dir
+ * itself.
*/
- iter->base.relative_path =
- iter->base.path.buf + iter->levels[0].prefix_len;
- iter->base.basename =
- iter->base.path.buf + level->prefix_len;
- level->dir_state = DIR_STATE_ITER;
+ if (S_ISDIR(iter->base.st.st_mode) &&
+ iter->base.options.iterate_dirs_after_files) {
+ push_dir_level(iter, level);
+ goto continue_outer_loop;
+ }
return ITER_OK;
}
+
+continue_outer_loop:
+ ;
}
}
@@ -190,6 +242,8 @@ struct dir_iterator *dir_iterator_begin(const char *path)
if (!path || !*path)
die("BUG: empty path passed to dir_iterator_begin()");
+ iter->base.options.iterate_dirs_after_files = 0;
+
strbuf_init(&iter->base.path, PATH_MAX);
strbuf_addstr(&iter->base.path, path);
diff --git a/dir-iterator.h b/dir-iterator.h
index 27739e6..4304913 100644
--- a/dir-iterator.h
+++ b/dir-iterator.h
@@ -38,7 +38,14 @@
* dir_iterator_advance() again.
*/
+struct dir_iterator_options {
+ unsigned iterate_dirs_after_files : 1;
+};
+
struct dir_iterator {
+ /* Options for dir_iterator */
+ struct dir_iterator_options options;
+
/* The current path: */
struct strbuf path;
--
2.7.4 (Apple Git-66)