File Coverage

src/b_find.c
Criterion Covered Total %
statement 103 168 61.3
branch 64 136 47.0
condition n/a
subroutine n/a
pod n/a
total 167 304 54.9


line stmt bran cond sub pod time code
1             #define _GNU_SOURCE 1
2             #include
3             #include
4             #include
5             #include
6             #include
7             #include
8             #include
9             #include
10             #include "b_builder.h"
11             #include "b_string.h"
12             #include "b_stack.h"
13             #include "b_path.h"
14             #include "b_find.h"
15             #include "b_error.h"
16              
17 6588           static inline int b_stat(b_string *path, struct stat *st, int flags) {
18 6588 50         int (*statfn)(const char *, struct stat *) = (flags & B_FIND_FOLLOW_SYMLINKS)? stat: lstat;
19              
20 6588           return statfn(path->str, st);
21             }
22              
23             typedef struct {
24             DIR * dp;
25             b_string * path;
26             } b_dir;
27              
28 88           b_dir *b_dir_open(b_string *path) {
29             b_dir *dir;
30              
31 88 50         if ((dir = malloc(sizeof(*dir))) == NULL) {
32 0           goto error_malloc;
33             }
34              
35 88 50         if ((dir->dp = opendir(path->str)) == NULL) {
36 0           goto error_opendir;
37             }
38              
39 88 50         if ((dir->path = b_string_dup(path)) == NULL) {
40 0           goto error_string_dup;
41             }
42              
43 88           return dir;
44              
45             error_string_dup:
46 0           closedir(dir->dp);
47              
48             error_opendir:
49 0           free(dir);
50              
51             error_malloc:
52 0           return NULL;
53             }
54              
55 88           static void b_dir_close(b_dir *item) {
56 88 50         if (item->dp) {
57 88           closedir(item->dp);
58             }
59 88           }
60              
61 88           static void b_dir_destroy(b_dir *item) {
62 88 50         if (item == NULL) return;
63              
64 88           b_dir_close(item);
65              
66 88           b_string_free(item->path);
67 88           item->path = NULL;
68              
69 88           free(item);
70             }
71              
72             typedef struct {
73             b_string * path;
74             b_string * name;
75             } b_dir_item;
76              
77 787           static b_dir_item *b_dir_read(b_dir *dir, int flags) {
78             b_dir_item *item;
79             struct dirent *entry;
80              
81             /*
82             * If readdir() returns null, then don't bother with setting up any other
83             * state.
84             */
85 787 100         if ((entry = readdir(dir->dp)) == NULL) {
86 88           goto error_readdir;
87             }
88              
89 699 50         if ((item = malloc(sizeof(*item))) == NULL) {
90 0           goto error_malloc;
91             }
92              
93 699 50         if ((item->path = b_string_dup(dir->path)) == NULL) {
94 0           goto error_string_dup;
95             }
96              
97 699 50         if ((item->name = b_string_new(entry->d_name)) == NULL) {
98 0           goto error_string_new;
99             }
100              
101             /*
102             * If the current path is /, then do not bother adding another slash.
103             */
104 699 50         if (strcmp(item->path->str, "/") != 0) {
105 699 50         if (b_string_append_str(item->path, "/") == NULL) {
106 0           goto error_string_append;
107             }
108             }
109              
110 699 50         if (b_string_append_str(item->path, entry->d_name) == NULL) {
111 0           goto error_string_append;
112             }
113              
114 699           return item;
115              
116             error_string_append:
117 0           b_string_free(item->name);
118              
119             error_string_new:
120 0           b_string_free(item->path);
121              
122             error_string_dup:
123 0           free(item);
124              
125             error_malloc:
126             error_readdir:
127 88           return NULL;
128             }
129              
130 699           static void b_dir_item_free(b_dir_item *item) {
131 699 50         if (item == NULL) return;
132              
133 699           b_string_free(item->name);
134 699           item->name = NULL;
135              
136 699           b_string_free(item->path);
137 699           item->path = NULL;
138              
139 699           free(item);
140             }
141              
142 481           static b_string *subst_member_name(b_string *path, b_string *member_name, b_string *current) {
143 481           b_string *new_member_name = NULL;
144              
145             /*
146             * If the path prefix differs from the member name, then replace the start
147             * of the path with the member name as the caller wishes it to be.
148             */
149 481 100         if (strcmp(path->str, member_name->str) != 0) {
150 42 50         if ((new_member_name = b_string_dup(member_name)) == NULL) {
151 0           goto error_string_dup;
152             }
153              
154 42 50         if (b_string_append_str(new_member_name, current->str + b_string_len(path)) == NULL) {
155 0           goto error_string_append;
156             }
157             }
158              
159 481           return new_member_name;
160              
161             error_string_append:
162 0           b_string_free(new_member_name);
163              
164             error_string_dup:
165 0           return NULL;
166             }
167              
168             /*
169             * callback() should return a 0 or 1; 0 to indicate that traversal at the current
170             * level should halt, or 1 that it should continue.
171             */
172 6588           int b_find(b_builder *builder, b_string *path, b_string *member_name, b_find_callback callback, int flags) {
173             b_stack *dirs;
174             b_dir *dir;
175             struct stat st, item_st;
176 6588           int fd = 0, res, oflags = O_RDONLY | O_NOFOLLOW | O_NONBLOCK;
177              
178 6588           b_error *err = b_builder_get_error(builder);
179              
180             b_string *clean_path;
181             b_string *clean_member_name;
182              
183 6588 50         if (flags & B_FIND_FOLLOW_SYMLINKS) {
184 0           oflags &= ~O_NOFOLLOW;
185             }
186              
187 6588 50         if ((clean_path = b_path_clean(path)) == NULL) {
188 0           goto error_path_clean;
189             }
190              
191 6588 50         if ((clean_member_name = b_path_clean(member_name)) == NULL) {
192 0           goto error_path_clean_member_name;
193             }
194              
195 6588 50         if ((dirs = b_stack_new(0)) == NULL) {
196 0           goto error_stack_new;
197             }
198              
199 6588           b_stack_set_destructor(dirs, B_STACK_DESTRUCTOR(b_dir_destroy));
200              
201 6588 50         if (b_stat(clean_path, &st, flags) < 0) {
202 0           goto error_stat;
203             }
204              
205             /*
206             * If the item we're dealing with is not a directory, or is not wanted by
207             * the callback, then do not bother with traversal code. Otherwise, all
208             * code after these guard clauses pertains to the case of 'path' being a
209             * directory.
210             */
211 6588 100         if ((st.st_mode & S_IFMT) == S_IFREG) {
212 6556 50         if ((fd = open(clean_path->str, oflags)) < 0) {
213 0           goto error_open;
214             }
215 6556 50         if (fcntl(fd, F_SETFL, oflags & ~O_NONBLOCK)) // previously clear_nonblock, however we know oflags so we can do it outselves
216 0           goto error_open;
217             }
218              
219 6588           res = callback(builder, clean_path, clean_member_name, &st, fd);
220              
221 6588 100         if (fd > 0) {
222 6556           close(fd);
223 6556           fd = 0;
224             }
225              
226 6588 50         if (res == 0) {
227 0           goto cleanup;
228 6588 100         } else if (res < 0) {
229 8           goto error_callback;
230             }
231              
232 6580 100         if ((st.st_mode & S_IFMT) != S_IFDIR) {
233 6548           return 0;
234             }
235              
236 32 50         if ((dir = b_dir_open(clean_path)) == NULL) {
237 0 0         if (err) {
238 0           b_error_set(err, B_ERROR_WARN, errno, "Unable to open directory", clean_path);
239             }
240              
241 0           goto error_dir_open;
242             }
243              
244 32 50         if (b_stack_push(dirs, dir) == NULL) {
245 0           b_dir_destroy(dir);
246              
247 0           goto error_stack_push;
248             }
249              
250             while (1) {
251             b_dir_item *item;
252             b_string *new_member_name;
253 819           b_dir *cwd = b_stack_top(dirs);
254 819           int item_fd = 0;
255              
256 819 100         if (cwd == NULL) {
257 32           break;
258             }
259              
260 787 100         if ((item = b_dir_read(cwd, flags)) == NULL) {
261 88           b_dir *oldcwd = b_stack_pop(dirs);
262              
263 88 50         if (oldcwd) {
264 88           b_dir_destroy(oldcwd);
265             }
266              
267 88           continue;
268             }
269              
270 699 100         if (strcmp(item->name->str, ".") == 0 || strcmp(item->name->str, "..") == 0) {
    100          
271             goto cleanup_item;
272             }
273              
274             /*
275             * Only test to see if the current member is excluded if any exclusions or
276             * inclusions were actually specified, to save time calling the exclusion
277             * engine.
278             */
279 523 100         if (builder->match != NULL && lafe_excluded(builder->match, (const char *)item->path->str)) {
    100          
280 42           goto cleanup_item;
281             }
282              
283 481 50         if ((item_fd = open(item->path->str, oflags)) < 0) {
284             /*
285             * If O_NOFOLLOW is used (which is default) to open() the current
286             * item, then check for ELOOP; this condition will occur when
287             * attempting to open a symlink. This means that we will need to
288             * simply use lstat() to retrieve information on the symlink inode
289             * itself.
290             *
291             * POSIX specifies ELOOP in this case, but FreeBSD uses EMLINK and
292             * NetBSD uses EFTYPE. Work around this bugginess.
293             */
294             #ifndef EFTYPE
295             #define EFTYPE ELOOP
296             #endif
297 0 0         if ((oflags & O_NOFOLLOW) && (errno == ELOOP || errno == EMLINK || errno == EFTYPE)) {
    0          
    0          
    0          
298 0 0         if (lstat(item->path->str, &item_st) < 0) {
299 0 0         if (err) {
300 0           b_error_set(err, B_ERROR_WARN, errno, "Cannot lstat() file", item->path);
301             }
302              
303 0           goto cleanup_item;
304             }
305             } else {
306 0 0         if( flags & B_FIND_IGNORE_SOCKETS ) {
307 0 0         if (stat(item->path->str, &item_st) < 0) {
308 0 0         if (err) {
309 0           b_error_set(err, B_ERROR_WARN, errno, "Cannot stat() file", item->path);
310             }
311             }
312             else {
313 0 0         if( err && (item_st.st_mode & S_IFMT) != S_IFSOCK ) {
    0          
314 0           b_error_set(err, B_ERROR_WARN, errno, "Cannot open file", item->path);
315             }
316             }
317             }
318             else {
319 0 0         if (err) {
320 0           b_error_set(err, B_ERROR_WARN, errno, "Cannot open file", item->path);
321             }
322             }
323              
324 0           goto cleanup_item;
325             }
326             } else {
327 481 50         if (fcntl(fd, F_SETFL, oflags & ~O_NONBLOCK)) // previously clear_nonblock, however we know oflags so we can do it outselves
328 0           goto cleanup_item;
329 481 50         if (fstat(item_fd, &item_st) < 0) {
330 0 0         if (err) {
331 0           b_error_set(err, B_ERROR_WARN, errno, "Cannot fstat() file descriptor", item->path);
332             }
333              
334 0           goto cleanup_item;
335             }
336             }
337              
338             /*
339             * Attempt to obtain and use a substituted member name based on the
340             * real path, and use it, if possible.
341             */
342 481           new_member_name = subst_member_name(clean_path, clean_member_name, item->path);
343              
344 481 100         res = callback(builder, item->path, new_member_name? new_member_name: item->path, &item_st, item_fd);
345              
346 481           b_string_free(new_member_name);
347              
348 481 50         if (res == 0) {
349 0           goto cleanup_item;
350 481 50         } else if (res < 0) {
351 0 0         if (err && !b_error_fatal(err)) {
    0          
352             goto cleanup_item;
353             } else {
354             goto error_item;
355             }
356             }
357              
358 481 100         if ((item_st.st_mode & S_IFMT) == S_IFDIR) {
359             b_dir *newdir;
360              
361 56 50         if ((newdir = b_dir_open(item->path)) == NULL) {
362 0 0         if (err) {
363 0           b_error_set(err, B_ERROR_WARN, errno, "Unable to open directory", item->path);
364             }
365              
366 0 0         if (errno == EACCES) {
367 0           goto cleanup_item;
368             } else {
369 0           goto error_item;
370             }
371             }
372              
373 56 50         if (b_stack_push(dirs, newdir) == NULL) {
374 0           b_dir_destroy(newdir);
375              
376 0           goto error_stack_push;
377             }
378             }
379              
380             cleanup_item:
381 699 100         if (item_fd > 0) {
382 481           close(item_fd);
383 481           item_fd = 0;
384             }
385              
386 699           b_dir_item_free(item);
387              
388 699           continue;
389              
390             error_item:
391 0 0         if (item_fd > 0) {
392 0           close(item_fd);
393 0           item_fd = 0;
394             }
395              
396 0           b_dir_item_free(item);
397              
398 0           goto error_cleanup;
399 787           }
400              
401             cleanup:
402 32           b_stack_destroy(dirs);
403 32           b_string_free(clean_path);
404 32           b_string_free(clean_member_name);
405              
406 32           return 0;
407              
408             error_cleanup:
409             error_stack_push:
410             error_dir_open:
411             error_callback:
412 8 50         if (fd > 0) {
413 0           close(fd);
414 0           fd = 0;
415             }
416              
417             error_open:
418             error_stat:
419 8           b_stack_destroy(dirs);
420              
421             error_stack_new:
422 8           b_string_free(clean_member_name);
423              
424             error_path_clean_member_name:
425 8           b_string_free(clean_path);
426              
427             error_path_clean:
428 6588           return -1;
429             }