0c8880868a0b08a3a3548886e8a2ed28605d09fe
   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_errno("error opening directory '%s'",
  75                                              iter->base.path.buf);
  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_errno("error reading directory '%s'",
 126                                                      iter->base.path.buf);
 127                                } else if (closedir(level->dir))
 128                                        warning_errno("error closing directory '%s'",
 129                                                      iter->base.path.buf);
 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_errno("failed to stat '%s'",
 144                                                      iter->base.path.buf);
 145                                continue;
 146                        }
 147
 148                        /*
 149                         * We have to set these each time because
 150                         * the path strbuf might have been realloc()ed.
 151                         */
 152                        iter->base.relative_path =
 153                                iter->base.path.buf + iter->levels[0].prefix_len;
 154                        iter->base.basename =
 155                                iter->base.path.buf + level->prefix_len;
 156                        level->dir_state = DIR_STATE_ITER;
 157
 158                        return ITER_OK;
 159                }
 160        }
 161}
 162
 163int dir_iterator_abort(struct dir_iterator *dir_iterator)
 164{
 165        struct dir_iterator_int *iter = (struct dir_iterator_int *)dir_iterator;
 166
 167        for (; iter->levels_nr; iter->levels_nr--) {
 168                struct dir_iterator_level *level =
 169                        &iter->levels[iter->levels_nr - 1];
 170
 171                if (level->dir && closedir(level->dir)) {
 172                        int saved_errno = errno;
 173                        strbuf_setlen(&iter->base.path, level->prefix_len);
 174                        errno = saved_errno;
 175                        warning_errno("error closing directory '%s'",
 176                                      iter->base.path.buf);
 177                }
 178        }
 179
 180        free(iter->levels);
 181        strbuf_release(&iter->base.path);
 182        free(iter);
 183        return ITER_DONE;
 184}
 185
 186struct dir_iterator *dir_iterator_begin(const char *path)
 187{
 188        struct dir_iterator_int *iter = xcalloc(1, sizeof(*iter));
 189        struct dir_iterator *dir_iterator = &iter->base;
 190
 191        if (!path || !*path)
 192                BUG("empty path passed to dir_iterator_begin()");
 193
 194        strbuf_init(&iter->base.path, PATH_MAX);
 195        strbuf_addstr(&iter->base.path, path);
 196
 197        ALLOC_GROW(iter->levels, 10, iter->levels_alloc);
 198
 199        iter->levels_nr = 1;
 200        iter->levels[0].initialized = 0;
 201
 202        return dir_iterator;
 203}