1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
|
#include "cache.h"
#include "dir.h"
#include "iterator.h"
#include "dir-iterator.h"
struct dir_iterator_level {
DIR *dir;
/*
* The length of the directory part of path at this level
* (including a trailing '/'):
*/
size_t prefix_len;
/*
* The last action that has been taken with the current entry
* (needed for directories, which have to be included in the
* iteration and also iterated into):
*/
enum {
DIR_STATE_PUSHED,
DIR_STATE_PRE_ITERATION,
DIR_STATE_ITERATING,
DIR_STATE_POST_ITERATION,
DIR_STATE_EXHAUSTED
} dir_state;
/* The stat structure for the directory this level represents. */
struct stat st;
};
/*
* The full data structure used to manage the internal directory
* iteration state. It includes members that are not part of the
* public interface.
*/
struct dir_iterator_int {
struct dir_iterator base;
/*
* The number of levels currently on the stack. This is always
* at least 1, because when it becomes zero the iteration is
* ended and this struct is freed.
*/
size_t levels_nr;
/* The number of levels that have been allocated on the stack */
size_t levels_alloc;
/*
* A stack of levels. levels[0] is the uppermost directory
* that will be included in this iteration.
*/
struct dir_iterator_level *levels;
/* Holds the flags passed to dir_iterator_begin(). */
unsigned flags;
};
static void push_dir_level(struct dir_iterator_int *iter, struct stat *st)
{
struct dir_iterator_level *level;
ALLOC_GROW(iter->levels, iter->levels_nr + 1,
iter->levels_alloc);
/* Push a new level */
level = &iter->levels[iter->levels_nr++];
level->dir = NULL;
level->dir_state = DIR_STATE_PUSHED;
level->st = *st;
}
static int pop_dir_level(struct dir_iterator_int *iter)
{
return --iter->levels_nr;
}
static int adjust_iterator_data(struct dir_iterator_int *iter,
struct dir_iterator_level *level)
{
if (level->dir_state != DIR_STATE_ITERATING) {
iter->base.st = level->st;
} else 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;
}
/*
* Check if we are dealing with the root directory as an
* item that's being iterated through.
*/
if (level->dir_state != DIR_STATE_ITERATING &&
iter->levels_nr == 1) {
const char *realpath, *last_path_component;
iter->base.relative_path = ".";
/*
* To get the root directory basename, we get the absolute path
* to our directory. Then, we get whatever is after the last '/'
* as the basename (or the whole directory if that does not exist,
* which should never happen.
*/
realpath = real_path(iter->base.path.buf);
last_path_component = strrchr(realpath, '/');
iter->base.basename = last_path_component ?
last_path_component + 1 : realpath;
} else {
iter->base.relative_path =
iter->base.path.buf + iter->levels[0].prefix_len;
if (S_ISDIR(iter->base.st.st_mode))
iter->base.basename =
iter->base.path.buf + iter->levels[iter->levels_nr - 2].prefix_len;
else
iter->base.basename =
iter->base.path.buf + level->prefix_len;
}
return 0;
}
/*
* This function uses a state machine with the following states:
* - DIR_STATE_PUSHED: the directory has been pushed to the
* iterator traversal tree.
* - DIR_STATE_PRE_ITERATION: the directory is not opened with opendir(). The
* dirpath has already been returned if pre-order traversal is set.
* - DIR_STATE_ITERATING: the directory is initialized. We are traversing
* through it.
* - DIR_STATE_POST_ITERATION: the directory has been iterated through, and has
* been closed.
* - DIR_STATE_EXHAUSTED: the directory is closed and ready to be popped.
*/
int dir_iterator_advance(struct dir_iterator *dir_iterator)
{
struct dir_iterator_int *iter =
(struct dir_iterator_int *)dir_iterator;
while (1) {
struct dir_iterator_level *level =
&iter->levels[iter->levels_nr - 1];
if (level->dir_state == DIR_STATE_PUSHED) {
level->dir_state = DIR_STATE_PRE_ITERATION;
/* We may not want the root directory to be iterated over */
if ((iter->flags & DIR_ITERATOR_PRE_ORDER_TRAVERSAL) && (
iter->levels_nr != 1 ||
(iter->flags & DIR_ITERATOR_LIST_ROOT_DIR))) {
/*
* This will only error if we fail to lstat() the
* root directory. In this case, we bail.
*/
if (adjust_iterator_data(iter, level)) {
level->dir_state = DIR_STATE_EXHAUSTED;
continue;
}
return ITER_OK;
}
} else if (level->dir_state == DIR_STATE_PRE_ITERATION) {
/*
* Note: dir_iterator_begin() ensures that
* path is not the empty string.
*/
if (!is_dir_sep(iter->base.path.buf[iter->base.path.len - 1]))
strbuf_addch(&iter->base.path, '/');
level->prefix_len = iter->base.path.len;
level->dir = opendir(iter->base.path.buf);
if (!level->dir) {
/*
* This level wasn't opened sucessfully; pretend we
* iterated through it already.
*/
if (errno != ENOENT)
warning("error opening directory %s: %s",
iter->base.path.buf, strerror(errno));
level->dir_state = DIR_STATE_POST_ITERATION;
continue;
}
level->dir_state = DIR_STATE_ITERATING;
} else if (level->dir_state == DIR_STATE_ITERATING) {
struct dirent *de;
strbuf_setlen(&iter->base.path, level->prefix_len);
errno = 0;
de = readdir(level->dir);
if (!de) {
/* In case of readdir() error */
if (errno) {
warning("error reading directory %s: %s",
iter->base.path.buf, strerror(errno));
}
if (closedir(level->dir))
warning("error closing directory %s: %s",
iter->base.path.buf, strerror(errno));
level->dir_state = DIR_STATE_POST_ITERATION;
continue;
}
if (is_dot_or_dotdot(de->d_name))
continue;
strbuf_addstr(&iter->base.path, de->d_name);
if (adjust_iterator_data(iter, level))
continue;
if (S_ISDIR(iter->base.st.st_mode)) {
push_dir_level(iter, &iter->base.st);
continue;
}
return ITER_OK;
} else if (level->dir_state == DIR_STATE_POST_ITERATION) {
level->dir_state = DIR_STATE_EXHAUSTED;
strbuf_setlen(&iter->base.path, level->prefix_len);
/*
* Since we are iterating through the dirpath
* after we have gone through it, we still need
* to get rid of the trailing slash we appended.
*/
strbuf_strip_suffix(&iter->base.path, "/");
/* We may not want the root directory to be iterated over */
if ((iter->flags & DIR_ITERATOR_POST_ORDER_TRAVERSAL) && (
iter->levels_nr != 1 ||
(iter->flags & DIR_ITERATOR_LIST_ROOT_DIR))) {
/*
* In this state, adjust_iterator_data() should never return
* an error.
*/
adjust_iterator_data(iter, level);
return ITER_OK;
}
} else if (level->dir_state == DIR_STATE_EXHAUSTED) {
if (pop_dir_level(iter) == 0)
return dir_iterator_abort(dir_iterator);
}
}
}
int dir_iterator_abort(struct dir_iterator *dir_iterator)
{
struct dir_iterator_int *iter = (struct dir_iterator_int *)dir_iterator;
for (; iter->levels_nr; iter->levels_nr--) {
struct dir_iterator_level *level =
&iter->levels[iter->levels_nr - 1];
if (level->dir && closedir(level->dir)) {
strbuf_setlen(&iter->base.path, level->prefix_len);
warning("error closing directory %s: %s",
iter->base.path.buf, strerror(errno));
}
}
free(iter->levels);
strbuf_release(&iter->base.path);
free(iter);
return ITER_DONE;
}
struct dir_iterator *dir_iterator_begin(const char *path, unsigned flags)
{
struct dir_iterator_int *iter;
struct dir_iterator *dir_iterator;
struct stat st;
if (!path || !*path)
die("BUG: empty path passed to dir_iterator_begin()");
if (lstat(path, &st) < 0)
return NULL;
if (!S_ISDIR(st.st_mode)) {
errno = ENOTDIR;
return NULL;
}
iter = xcalloc(1, sizeof(*iter));
dir_iterator = &iter->base;
iter->flags = flags;
dir_iterator->st = st;
strbuf_init(&iter->base.path, PATH_MAX);
strbuf_addstr(&iter->base.path, path);
ALLOC_GROW(iter->levels, 10, iter->levels_alloc);
iter->levels_nr = 0;
push_dir_level(iter, &dir_iterator->st);
return dir_iterator;
}
|