dir-iterator.con commit t7610: run 'git reset --hard' after each test to clean up (c3ad312)
   1#include "cache.h"
   2#include "dir.h"
   3#include "iterator.h"
   4#include "dir-iterator.h"
   5
   6struct dir_iterator_level {
   7        int initialized;
   8
   9        DIR *dir;
  10
  11        /*
  12         * The length of the directory part of path at this level
  13         * (including a trailing '/'):
  14         */
  15        size_t prefix_len;
  16
  17        /*
  18         * The last action that has been taken with the current entry
  19         * (needed for directories, which have to be included in the
  20         * iteration and also iterated into):
  21         */
  22        enum {
  23                DIR_STATE_ITER,
  24                DIR_STATE_RECURSE
  25        } dir_state;
  26};
  27
  28/*
  29 * The full data structure used to manage the internal directory
  30 * iteration state. It includes members that are not part of the
  31 * public interface.
  32 */
  33struct dir_iterator_int {
  34        struct dir_iterator base;
  35
  36        /*
  37         * The number of levels currently on the stack. This is always
  38         * at least 1, because when it becomes zero the iteration is
  39         * ended and this struct is freed.
  40         */
  41        size_t levels_nr;
  42
  43        /* The number of levels that have been allocated on the stack */
  44        size_t levels_alloc;
  45
  46        /*
  47         * A stack of levels. levels[0] is the uppermost directory
  48         * that will be included in this iteration.
  49         */
  50        struct dir_iterator_level *levels;
  51};
  52
  53int dir_iterator_advance(struct dir_iterator *dir_iterator)
  54{
  55        struct dir_iterator_int *iter =
  56                (struct dir_iterator_int *)dir_iterator;
  57
  58        while (1) {
  59                struct dir_iterator_level *level =
  60                        &iter->levels[iter->levels_nr - 1];
  61                struct dirent *de;
  62
  63                if (!level->initialized) {
  64                        /*
  65                         * Note: dir_iterator_begin() ensures that
  66                         * path is not the empty string.
  67                         */
  68                        if (!is_dir_sep(iter->base.path.buf[iter->base.path.len - 1]))
  69                                strbuf_addch(&iter->base.path, '/');
  70                        level->prefix_len = iter->base.path.len;
  71
  72                        level->dir = opendir(iter->base.path.buf);
  73                        if (!level->dir && errno != ENOENT) {
  74                                warning("error opening directory %s: %s",
  75                                        iter->base.path.buf, strerror(errno));
  76                                /* Popping the level is handled below */
  77                        }
  78
  79                        level->initialized = 1;
  80                } else if (S_ISDIR(iter->base.st.st_mode)) {
  81                        if (level->dir_state == DIR_STATE_ITER) {
  82                                /*
  83                                 * The directory was just iterated
  84                                 * over; now prepare to iterate into
  85                                 * it.
  86                                 */
  87                                level->dir_state = DIR_STATE_RECURSE;
  88                                ALLOC_GROW(iter->levels, iter->levels_nr + 1,
  89                                           iter->levels_alloc);
  90                                level = &iter->levels[iter->levels_nr++];
  91                                level->initialized = 0;
  92                                continue;
  93                        } else {
  94                                /*
  95                                 * The directory has already been
  96                                 * iterated over and iterated into;
  97                                 * we're done with it.
  98                                 */
  99                        }
 100                }
 101
 102                if (!level->dir) {
 103                        /*
 104                         * This level is exhausted (or wasn't opened
 105                         * successfully); pop up a level.
 106                         */
 107                        if (--iter->levels_nr == 0)
 108                                return dir_iterator_abort(dir_iterator);
 109
 110                        continue;
 111                }
 112
 113                /*
 114                 * Loop until we find an entry that we can give back
 115                 * to the caller:
 116                 */
 117                while (1) {
 118                        strbuf_setlen(&iter->base.path, level->prefix_len);
 119                        errno = 0;
 120                        de = readdir(level->dir);
 121
 122                        if (!de) {
 123                                /* This level is exhausted; pop up a level. */
 124                                if (errno) {
 125                                        warning("error reading directory %s: %s",
 126                                                iter->base.path.buf, strerror(errno));
 127                                } else if (closedir(level->dir))
 128                                        warning("error closing directory %s: %s",
 129                                                iter->base.path.buf, strerror(errno));
 130
 131                                level->dir = NULL;
 132                                if (--iter->levels_nr == 0)
 133                                        return dir_iterator_abort(dir_iterator);
 134                                break;
 135                        }
 136
 137                        if (is_dot_or_dotdot(de->d_name))
 138                                continue;
 139
 140                        strbuf_addstr(&iter->base.path, de->d_name);
 141                        if (lstat(iter->base.path.buf, &iter->base.st) < 0) {
 142                                if (errno != ENOENT)
 143                                        warning("error reading path '%s': %s",
 144                                                iter->base.path.buf,
 145                                                strerror(errno));
 146                                continue;
 147                        }
 148
 149                        /*
 150                         * We have to set these each time because
 151                         * the path strbuf might have been realloc()ed.
 152                         */
 153                        iter->base.relative_path =
 154                                iter->base.path.buf + iter->levels[0].prefix_len;
 155                        iter->base.basename =
 156                                iter->base.path.buf + level->prefix_len;
 157                        level->dir_state = DIR_STATE_ITER;
 158
 159                        return ITER_OK;
 160                }
 161        }
 162}
 163
 164int dir_iterator_abort(struct dir_iterator *dir_iterator)
 165{
 166        struct dir_iterator_int *iter = (struct dir_iterator_int *)dir_iterator;
 167
 168        for (; iter->levels_nr; iter->levels_nr--) {
 169                struct dir_iterator_level *level =
 170                        &iter->levels[iter->levels_nr - 1];
 171
 172                if (level->dir && closedir(level->dir)) {
 173                        strbuf_setlen(&iter->base.path, level->prefix_len);
 174                        warning("error closing directory %s: %s",
 175                                iter->base.path.buf, strerror(errno));
 176                }
 177        }
 178
 179        free(iter->levels);
 180        strbuf_release(&iter->base.path);
 181        free(iter);
 182        return ITER_DONE;
 183}
 184
 185struct dir_iterator *dir_iterator_begin(const char *path)
 186{
 187        struct dir_iterator_int *iter = xcalloc(1, sizeof(*iter));
 188        struct dir_iterator *dir_iterator = &iter->base;
 189
 190        if (!path || !*path)
 191                die("BUG: empty path passed to dir_iterator_begin()");
 192
 193        strbuf_init(&iter->base.path, PATH_MAX);
 194        strbuf_addstr(&iter->base.path, path);
 195
 196        ALLOC_GROW(iter->levels, 10, iter->levels_alloc);
 197
 198        iter->levels_nr = 1;
 199        iter->levels[0].initialized = 0;
 200
 201        return dir_iterator;
 202}