]>
Commit | Line | Data |
---|---|---|
0fe5043d MH |
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) | |
033abf97 | 191 | BUG("empty path passed to dir_iterator_begin()"); |
0fe5043d MH |
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 | } |