Merge branch 'tg/add-chmod+x-fix' into maint
[git] / dir-iterator.c
1 #include "cache.h"
2 #include "dir.h"
3 #include "iterator.h"
4 #include "dir-iterator.h"
5
6 struct 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  */
33 struct 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
53 int 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
164 int 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
185 struct 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 }