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_DEREFERENCE) &&
64 !(walk_flags & WALK_TREE_PHYSICAL) &&
66 int have_dir_stat = 0, flags = walk_flags, err;
67 struct entry_handle dir;
71 * If (walk_flags & WALK_TREE_PHYSICAL), do not traverse symlinks.
72 * If (walk_flags & WALK_TREE_LOGICAL), traverse all symlinks.
73 * Otherwise, traverse only top-level symlinks.
76 flags |= WALK_TREE_TOPLEVEL;
78 if (lstat(path, &st) != 0)
79 return func(path, NULL, flags | WALK_TREE_FAILED, arg);
80 if (S_ISLNK(st.st_mode)) {
81 flags |= WALK_TREE_SYMLINK;
82 if ((flags & WALK_TREE_DEREFERENCE) ||
83 ((flags & WALK_TREE_TOPLEVEL) &&
84 (flags & WALK_TREE_DEREFERENCE_TOPLEVEL))) {
85 if (stat(path, &st) != 0)
86 return func(path, NULL,
87 flags | WALK_TREE_FAILED, arg);
92 } else if (S_ISDIR(st.st_mode)) {
97 err = func(path, &st, flags, arg);
100 * Recurse if WALK_TREE_RECURSIVE and the path is:
101 * a dir not from a symlink
102 * a link and follow_symlinks
104 if ((flags & WALK_TREE_RECURSIVE) &&
105 (!(flags & WALK_TREE_SYMLINK) && S_ISDIR(st.st_mode)) ||
106 ((flags & WALK_TREE_SYMLINK) && follow_symlinks)) {
107 struct dirent *entry;
110 * Check if we have already visited this directory to break
113 * If we haven't stat()ed the file yet, do an opendir() for
114 * figuring out whether we have a directory, and check whether
115 * the directory has been visited afterwards. This saves a
116 * system call for each non-directory found.
118 if (have_dir_stat && walk_tree_visited(dir.dev, dir.ino))
121 if (num_dir_handles == 0 && closed->prev != &head) {
123 /* Close the topmost directory handle still open. */
124 closed = closed->prev;
125 closed->pos = telldir(closed->stream);
126 closedir(closed->stream);
127 closed->stream = NULL;
131 dir.stream = opendir(path);
133 if (errno == ENFILE && closed->prev != &head) {
134 /* Ran out of file descriptors. */
136 goto close_another_dir;
140 * PATH may be a symlink to a regular file, or a dead
141 * symlink which we didn't follow above.
143 if (errno != ENOTDIR && errno != ENOENT)
144 err += func(path, NULL, flags |
145 WALK_TREE_FAILED, arg);
149 /* See walk_tree_visited() comment above... */
150 if (!have_dir_stat) {
151 if (stat(path, &st) != 0)
155 if (walk_tree_visited(dir.dev, dir.ino))
159 /* Insert into the list of handles. */
160 dir.next = head.next;
162 dir.prev->next = &dir;
163 dir.next->prev = &dir;
166 while ((entry = readdir(dir.stream)) != NULL) {
169 if (!strcmp(entry->d_name, ".") ||
170 !strcmp(entry->d_name, ".."))
172 path_end = strchr(path, 0);
173 if ((path_end - path) + strlen(entry->d_name) + 1 >=
175 errno = ENAMETOOLONG;
176 err += func(path, NULL,
177 flags | WALK_TREE_FAILED, arg);
181 strcpy(path_end, entry->d_name);
182 err += walk_tree_rec(path, walk_flags, func, arg,
186 /* Reopen the directory handle. */
187 dir.stream = opendir(path);
189 return err + func(path, NULL, flags |
190 WALK_TREE_FAILED, arg);
191 seekdir(dir.stream, dir.pos);
193 closed = closed->next;
198 /* Remove from the list of handles. */
199 dir.prev->next = dir.next;
200 dir.next->prev = dir.prev;
204 if (closedir(dir.stream) != 0)
205 err += func(path, NULL, flags | WALK_TREE_FAILED, arg);
210 int walk_tree(const char *path, int walk_flags, unsigned int num,
211 int (*func)(const char *, const struct stat *, int, void *),
214 char path_copy[FILENAME_MAX];
216 num_dir_handles = num;
217 if (num_dir_handles < 1) {
218 struct rlimit rlimit;
221 if (getrlimit(RLIMIT_NOFILE, &rlimit) == 0 &&
222 rlimit.rlim_cur >= 2)
223 num_dir_handles = rlimit.rlim_cur / 2;
225 if (strlen(path) >= FILENAME_MAX) {
226 errno = ENAMETOOLONG;
227 return func(path, NULL, WALK_TREE_FAILED, arg);
229 strcpy(path_copy, path);
230 return walk_tree_rec(path_copy, walk_flags, func, arg, 0);