File Coverage

src/b_find.c
Criterion Covered Total %
statement 113 193 58.5
branch 64 136 47.0
condition n/a
subroutine n/a
pod n/a
total 177 329 53.8


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