|
|
| version 1.1, 2007/11/21 05:13:52 | version 1.2, 2007/12/04 05:10:47 |
|---|---|
| Line 21 | Line 21 |
| #include <sys/types.h> | #include <sys/types.h> |
| #include <sys/stat.h> | #include <sys/stat.h> |
| #include <unistd.h> | #include <unistd.h> |
| #include <sys/time.h> | |
| #include <sys/resource.h> | |
| #include <dirent.h> | #include <dirent.h> |
| #include <stdio.h> | #include <stdio.h> |
| #include <string.h> | #include <string.h> |
| Line 28 | Line 30 |
| #include "walk_tree.h" | #include "walk_tree.h" |
| struct entry_handle { | |
| struct entry_handle *prev, *next; | |
| dev_t dev; | |
| ino_t ino; | |
| DIR *stream; | |
| off_t pos; | |
| }; | |
| struct entry_handle head = { | |
| .next = &head, | |
| .prev = &head, | |
| /* The other fields are unused. */ | |
| }; | |
| struct entry_handle *closed = &head; | |
| unsigned int num_dir_handles; | |
| static int walk_tree_visited(dev_t dev, ino_t ino) | |
| { | |
| struct entry_handle *i; | |
| for (i = head.next; i != &head; i = i->next) | |
| if (i->dev == dev && i->ino == ino) | |
| return 1; | |
| return 0; | |
| } | |
| static int walk_tree_rec(const char *path, int walk_flags, | static int walk_tree_rec(const char *path, int walk_flags, |
| int (*func)(const char *, const struct stat *, int, void *), | int (*func)(const char *, const struct stat *, int, |
| void *arg, int depth) | void *), void *arg, int depth) |
| { | { |
| int (*xstat)(const char *, struct stat *) = lstat; | int follow_symlinks = (walk_flags & WALK_TREE_LOGICAL) || |
| (!(walk_flags & WALK_TREE_PHYSICAL) && | |
| depth == 0); | |
| int have_dir_stat = 0, flags = walk_flags, err; | |
| struct entry_handle dir; | |
| struct stat st; | struct stat st; |
| int local_walk_flags = walk_flags, err; | |
| /* Default to traversing symlinks on the command line, traverse all symlinks | /* |
| * with -L, and do not traverse symlinks with -P. (This is similar to chown.) | * If (walk_flags & WALK_TREE_PHYSICAL), do not traverse symlinks. |
| * If (walk_flags & WALK_TREE_LOGICAL), traverse all symlinks. | |
| * Otherwise, traverse only top-level symlinks. | |
| */ | */ |
| if (depth == 0) | |
| flags |= WALK_TREE_TOPLEVEL; | |
| follow_symlink: | if (lstat(path, &st) != 0) |
| if (xstat(path, &st) != 0) | return func(path, NULL, flags | WALK_TREE_FAILED, arg); |
| return func(path, NULL, local_walk_flags | WALK_TREE_FAILED, arg); | |
| if (S_ISLNK(st.st_mode)) { | if (S_ISLNK(st.st_mode)) { |
| if ((local_walk_flags & WALK_TREE_PHYSICAL) || | flags |= WALK_TREE_SYMLINK; |
| (!(local_walk_flags & WALK_TREE_LOGICAL) && depth > 1)) | if (flags & WALK_TREE_DEREFERENCE) { |
| return 0; | if (stat(path, &st) != 0) |
| local_walk_flags |= WALK_TREE_SYMLINK; | return func(path, NULL, |
| xstat = stat; | flags | WALK_TREE_FAILED, arg); |
| if (local_walk_flags & WALK_TREE_DEREFERENCE) | dir.dev = st.st_dev; |
| goto follow_symlink; | dir.ino = st.st_ino; |
| have_dir_stat = 1; | |
| } | |
| } else if (S_ISDIR(st.st_mode)) { | |
| dir.dev = st.st_dev; | |
| dir.ino = st.st_ino; | |
| have_dir_stat = 1; | |
| } | } |
| err = func(path, &st, local_walk_flags, arg); | err = func(path, &st, flags, arg); |
| if ((local_walk_flags & WALK_TREE_RECURSIVE) && | if ((flags & WALK_TREE_RECURSIVE) && |
| (S_ISDIR(st.st_mode) || S_ISLNK(st.st_mode))) { | (S_ISDIR(st.st_mode) || (S_ISLNK(st.st_mode) && follow_symlinks))) { |
| char path2[FILENAME_MAX]; | |
| DIR *dir; | |
| struct dirent *entry; | struct dirent *entry; |
| int err2; | |
| dir = opendir(path); | /* |
| if (!dir) { | * Check if we have already visited this directory to break |
| /* PATH may be a symlink to a regular file or a dead symlink | * endless loops. |
| * which we didn't follow above. | * |
| * If we haven't stat()ed the file yet, do an opendir() for | |
| * figuring out whether we have a directory, and check whether | |
| * the directory has been visited afterwards. This saves a | |
| * system call for each non-directory found. | |
| */ | |
| if (have_dir_stat && walk_tree_visited(dir.dev, dir.ino)) | |
| return err; | |
| if (num_dir_handles == 0 && closed->prev != &head) { | |
| close_another_dir: | |
| /* Close the topmost directory handle still open. */ | |
| closed = closed->prev; | |
| closed->pos = telldir(closed->stream); | |
| closedir(closed->stream); | |
| closed->stream = NULL; | |
| num_dir_handles++; | |
| } | |
| dir.stream = opendir(path); | |
| if (!dir.stream) { | |
| if (errno == ENFILE && closed->prev != &head) { | |
| /* Ran out of file descriptors. */ | |
| num_dir_handles = 0; | |
| goto close_another_dir; | |
| } | |
| /* | |
| * PATH may be a symlink to a regular file, or a dead | |
| * symlink which we didn't follow above. | |
| */ | */ |
| if (errno != ENOTDIR && errno != ENOENT) | if (errno != ENOTDIR && errno != ENOENT) |
| err += func(path, &st, | err += func(path, NULL, flags | |
| local_walk_flags | WALK_TREE_FAILED, arg); | WALK_TREE_FAILED, arg); |
| return err; | return err; |
| } | } |
| while ((entry = readdir(dir)) != NULL) { | |
| if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) | /* See walk_tree_visited() comment above... */ |
| if (!have_dir_stat) { | |
| if (stat(path, &st) != 0) | |
| goto skip_dir; | |
| dir.dev = st.st_dev; | |
| dir.ino = st.st_ino; | |
| if (walk_tree_visited(dir.dev, dir.ino)) | |
| goto skip_dir; | |
| } | |
| /* Insert into the list of handles. */ | |
| dir.next = head.next; | |
| dir.prev = &head; | |
| dir.prev->next = &dir; | |
| dir.next->prev = &dir; | |
| num_dir_handles--; | |
| while ((entry = readdir(dir.stream)) != NULL) { | |
| char *path_end; | |
| if (!strcmp(entry->d_name, ".") || | |
| !strcmp(entry->d_name, "..")) | |
| continue; | continue; |
| err2 = snprintf(path2, sizeof(path2), "%s/%s", path, | path_end = strchr(path, 0); |
| entry->d_name); | if ((path_end - path) + strlen(entry->d_name) + 1 >= |
| if (err2 < 0 || err2 > FILENAME_MAX) { | FILENAME_MAX) { |
| errno = ENAMETOOLONG; | errno = ENAMETOOLONG; |
| err += func(path, NULL, | err += func(path, NULL, |
| local_walk_flags | WALK_TREE_FAILED, arg); | flags | WALK_TREE_FAILED, arg); |
| continue; | continue; |
| } | } |
| err += walk_tree_rec(path2, walk_flags, func, arg, depth + 1); | *path_end++ = '/'; |
| strcpy(path_end, entry->d_name); | |
| err += walk_tree_rec(path, walk_flags, func, arg, | |
| depth + 1); | |
| *--path_end = 0; | |
| if (!dir.stream) { | |
| /* Reopen the directory handle. */ | |
| dir.stream = opendir(path); | |
| if (!dir.stream) | |
| return err + func(path, NULL, flags | | |
| WALK_TREE_FAILED, arg); | |
| seekdir(dir.stream, dir.pos); | |
| closed = closed->next; | |
| num_dir_handles--; | |
| } | |
| } | } |
| if (closedir(dir) != 0) | |
| err += func(path, &st, local_walk_flags | WALK_TREE_FAILED, arg); | /* Remove from the list of handles. */ |
| dir.prev->next = dir.next; | |
| dir.next->prev = dir.prev; | |
| num_dir_handles++; | |
| skip_dir: | |
| if (closedir(dir.stream) != 0) | |
| err += func(path, NULL, flags | WALK_TREE_FAILED, arg); | |
| } | } |
| return err; | return err; |
| } | } |
| int walk_tree(const char *path, int walk_flags, | int walk_tree(const char *path, int walk_flags, unsigned int num, |
| int (*func)(const char *, const struct stat *, int, void *), void *arg) | int (*func)(const char *, const struct stat *, int, void *), |
| void *arg) | |
| { | { |
| char path_copy[FILENAME_MAX]; | |
| num_dir_handles = num; | |
| if (num_dir_handles < 1) { | |
| struct rlimit rlimit; | |
| num_dir_handles = 1; | |
| if (getrlimit(RLIMIT_NOFILE, &rlimit) == 0 && | |
| rlimit.rlim_cur >= 2) | |
| num_dir_handles = rlimit.rlim_cur / 2; | |
| } | |
| if (strlen(path) >= FILENAME_MAX) { | if (strlen(path) >= FILENAME_MAX) { |
| errno = ENAMETOOLONG; | errno = ENAMETOOLONG; |
| return func(path, NULL, WALK_TREE_FAILED, arg); | return func(path, NULL, WALK_TREE_FAILED, arg); |
| } | } |
| return walk_tree_rec(path, walk_flags, func, arg, 1); | strcpy(path_copy, path); |
| return walk_tree_rec(path_copy, walk_flags, func, arg, 0); | |
| } | } |