Initial public busybox upstream commit
[busybox4maemo] / libbb / recursive_action.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * Utility routines.
4  *
5  * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
6  *
7  * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
8  */
9
10 #include "libbb.h"
11
12 #undef DEBUG_RECURS_ACTION
13
14 /*
15  * Walk down all the directories under the specified
16  * location, and do something (something specified
17  * by the fileAction and dirAction function pointers).
18  *
19  * Unfortunately, while nftw(3) could replace this and reduce
20  * code size a bit, nftw() wasn't supported before GNU libc 2.1,
21  * and so isn't sufficiently portable to take over since glibc2.1
22  * is so stinking huge.
23  */
24
25 static int true_action(const char *fileName ATTRIBUTE_UNUSED,
26                 struct stat *statbuf ATTRIBUTE_UNUSED,
27                 void* userData ATTRIBUTE_UNUSED,
28                 int depth ATTRIBUTE_UNUSED)
29 {
30         return TRUE;
31 }
32
33 /* fileAction return value of 0 on any file in directory will make
34  * recursive_action() return 0, but it doesn't stop directory traversal
35  * (fileAction/dirAction will be called on each file).
36  *
37  * if !depthFirst, dirAction return value of 0 (FALSE) or 2 (SKIP)
38  * prevents recursion into that directory, instead
39  * recursive_action() returns 0 (if FALSE) or 1 (if SKIP).
40  *
41  * followLinks=0/1 differs mainly in handling of links to dirs.
42  * 0: lstat(statbuf). Calls fileAction on link name even if points to dir.
43  * 1: stat(statbuf). Calls dirAction and optionally recurse on link to dir.
44  */
45
46 int recursive_action(const char *fileName,
47                 unsigned flags,
48                 int (*fileAction)(const char *fileName, struct stat *statbuf, void* userData, int depth),
49                 int (*dirAction)(const char *fileName, struct stat *statbuf, void* userData, int depth),
50                 void* userData,
51                 unsigned depth)
52 {
53         struct stat statbuf;
54         int status;
55         DIR *dir;
56         struct dirent *next;
57
58         if (!fileAction) fileAction = true_action;
59         if (!dirAction) dirAction = true_action;
60
61         status = ACTION_FOLLOWLINKS; /* hijack a variable for bitmask... */
62         if (!depth) status = ACTION_FOLLOWLINKS | ACTION_FOLLOWLINKS_L0;
63         status = ((flags & status) ? stat : lstat)(fileName, &statbuf);
64         if (status < 0) {
65 #ifdef DEBUG_RECURS_ACTION
66                 bb_error_msg("status=%d flags=%x", status, flags);
67 #endif
68                 goto done_nak_warn;
69         }
70
71         /* If S_ISLNK(m), then we know that !S_ISDIR(m).
72          * Then we can skip checking first part: if it is true, then
73          * (!dir) is also true! */
74         if ( /* (!(flags & ACTION_FOLLOWLINKS) && S_ISLNK(statbuf.st_mode)) || */
75          !S_ISDIR(statbuf.st_mode)
76         ) {
77                 return fileAction(fileName, &statbuf, userData, depth);
78         }
79
80         /* It's a directory (or a link to one, and followLinks is set) */
81
82         if (!(flags & ACTION_RECURSE)) {
83                 return dirAction(fileName, &statbuf, userData, depth);
84         }
85
86         if (!(flags & ACTION_DEPTHFIRST)) {
87                 status = dirAction(fileName, &statbuf, userData, depth);
88                 if (!status)
89                         goto done_nak_warn;
90                 if (status == SKIP)
91                         return TRUE;
92         }
93
94         dir = opendir(fileName);
95         if (!dir) {
96                 /* findutils-4.1.20 reports this */
97                 /* (i.e. it doesn't silently return with exit code 1) */
98                 /* To trigger: "find -exec rm -rf {} \;" */
99                 goto done_nak_warn;
100         }
101         status = TRUE;
102         while ((next = readdir(dir)) != NULL) {
103                 char *nextFile;
104
105                 nextFile = concat_subpath_file(fileName, next->d_name);
106                 if (nextFile == NULL)
107                         continue;
108                 /* now descend into it (NB: ACTION_RECURSE is set in flags) */
109                 if (!recursive_action(nextFile, flags, fileAction, dirAction, userData, depth+1))
110                         status = FALSE;
111                 free(nextFile);
112         }
113         closedir(dir);
114
115         if (flags & ACTION_DEPTHFIRST) {
116                 if (!dirAction(fileName, &statbuf, userData, depth))
117                         goto done_nak_warn;
118         }
119
120         if (!status)
121                 return FALSE;
122         return TRUE;
123
124  done_nak_warn:
125         bb_simple_perror_msg(fileName);
126         return FALSE;
127 }