4 Copyright (C) 2007 Andreas Gruenbacher <a.gruenbacher@computer.org>
6 This program is free software; you can redistribute it and/or modify it under
7 the terms of the GNU Lesser General Public License as published by the
8 Free Software Foundation; either version 2.1 of the License, or (at
9 your option) any later version.
11 This program is distributed in the hope that it will be useful, but WITHOUT
12 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
14 License for more details.
16 You should have received a copy of the GNU Lesser General Public
17 License along with this program. If not, see <http://www.gnu.org/licenses/>.
20 #include <sys/types.h>
24 #include <sys/resource.h>
30 #include "walk_tree.h"
33 struct entry_handle *prev, *next;
40 struct entry_handle head = {
43 /* The other fields are unused. */
45 struct entry_handle *closed = &head;
46 unsigned int num_dir_handles;
48 static int walk_tree_visited(dev_t dev, ino_t ino)
50 struct entry_handle *i;
52 for (i = head.next; i != &head; i = i->next)
53 if (i->dev == dev && i->ino == ino)
58 static int walk_tree_rec(const char *path, int walk_flags,
59 int (*func)(const char *, const struct stat *, int,
60 void *), void *arg, int depth)
62 int follow_symlinks = (walk_flags & WALK_TREE_LOGICAL) ||
63 (!(walk_flags & WALK_TREE_PHYSICAL) &&
65 int have_dir_stat = 0, flags = walk_flags, err;
66 struct entry_handle dir;
70 * If (walk_flags & WALK_TREE_PHYSICAL), do not traverse symlinks.
71 * If (walk_flags & WALK_TREE_LOGICAL), traverse all symlinks.
72 * Otherwise, traverse only top-level symlinks.
75 flags |= WALK_TREE_TOPLEVEL;
77 if (lstat(path, &st) != 0)
78 return func(path, NULL, flags | WALK_TREE_FAILED, arg);
79 if (S_ISLNK(st.st_mode)) {
80 flags |= WALK_TREE_SYMLINK;
81 if ((flags & WALK_TREE_DEREFERENCE) ||
82 ((flags & WALK_TREE_TOPLEVEL) &&
83 (flags & WALK_TREE_DEREFERENCE_TOPLEVEL))) {
84 if (stat(path, &st) != 0)
85 return func(path, NULL,
86 flags | WALK_TREE_FAILED, arg);
91 } else if (S_ISDIR(st.st_mode)) {
96 err = func(path, &st, flags, arg);
99 * Recurse if WALK_TREE_RECURSIVE and the path is:
100 * a dir not from a symlink
101 * a link and follow_symlinks
103 if ((flags & WALK_TREE_RECURSIVE) &&
104 (!(flags & WALK_TREE_SYMLINK) && S_ISDIR(st.st_mode)) ||
105 ((flags & WALK_TREE_SYMLINK) && follow_symlinks)) {
106 struct dirent *entry;
109 * Check if we have already visited this directory to break
112 * If we haven't stat()ed the file yet, do an opendir() for
113 * figuring out whether we have a directory, and check whether
114 * the directory has been visited afterwards. This saves a
115 * system call for each non-directory found.
117 if (have_dir_stat && walk_tree_visited(dir.dev, dir.ino))
120 if (num_dir_handles == 0 && closed->prev != &head) {
122 /* Close the topmost directory handle still open. */
123 closed = closed->prev;
124 closed->pos = telldir(closed->stream);
125 closedir(closed->stream);
126 closed->stream = NULL;
130 dir.stream = opendir(path);
132 if (errno == ENFILE && closed->prev != &head) {
133 /* Ran out of file descriptors. */
135 goto close_another_dir;
139 * PATH may be a symlink to a regular file, or a dead
140 * symlink which we didn't follow above.
142 if (errno != ENOTDIR && errno != ENOENT)
143 err += func(path, NULL, flags |
144 WALK_TREE_FAILED, arg);
148 /* See walk_tree_visited() comment above... */
149 if (!have_dir_stat) {
150 if (stat(path, &st) != 0)
154 if (walk_tree_visited(dir.dev, dir.ino))
158 /* Insert into the list of handles. */
159 dir.next = head.next;
161 dir.prev->next = &dir;
162 dir.next->prev = &dir;
165 while ((entry = readdir(dir.stream)) != NULL) {
168 if (!strcmp(entry->d_name, ".") ||
169 !strcmp(entry->d_name, ".."))
171 path_end = strchr(path, 0);
172 if ((path_end - path) + strlen(entry->d_name) + 1 >=
174 errno = ENAMETOOLONG;
175 err += func(path, NULL,
176 flags | WALK_TREE_FAILED, arg);
180 strcpy(path_end, entry->d_name);
181 err += walk_tree_rec(path, walk_flags, func, arg,
185 /* Reopen the directory handle. */
186 dir.stream = opendir(path);
188 return err + func(path, NULL, flags |
189 WALK_TREE_FAILED, arg);
190 seekdir(dir.stream, dir.pos);
192 closed = closed->next;
197 /* Remove from the list of handles. */
198 dir.prev->next = dir.next;
199 dir.next->prev = dir.prev;
203 if (closedir(dir.stream) != 0)
204 err += func(path, NULL, flags | WALK_TREE_FAILED, arg);
209 int walk_tree(const char *path, int walk_flags, unsigned int num,
210 int (*func)(const char *, const struct stat *, int, void *),
213 char path_copy[FILENAME_MAX];
215 num_dir_handles = num;
216 if (num_dir_handles < 1) {
217 struct rlimit rlimit;
220 if (getrlimit(RLIMIT_NOFILE, &rlimit) == 0 &&
221 rlimit.rlim_cur >= 2)
222 num_dir_handles = rlimit.rlim_cur / 2;
224 if (strlen(path) >= FILENAME_MAX) {
225 errno = ENAMETOOLONG;
226 return func(path, NULL, WALK_TREE_FAILED, arg);
228 strcpy(path_copy, path);
229 return walk_tree_rec(path_copy, walk_flags, func, arg, 0);