diff options
author | Tim Shimmin <tes@sgi.com> | 2007-12-04 05:11:38 +0000 |
---|---|---|
committer | Tim Shimmin <tes@sgi.com> | 2007-12-04 05:11:38 +0000 |
commit | 73819e442294ef3bf4a8fc6cfd47cead00a1a2c2 (patch) | |
tree | 65ebf846f3a6967b847101e50d7ce3a7de725bd6 | |
parent | 264bff299a6da24bcdbec4e8ba2131cd865718cc (diff) | |
download | acl-73819e442294ef3bf4a8fc6cfd47cead00a1a2c2.tar.gz |
Add some code to the tree walking to better handle file descriptors.
Merge of master-melb:xfs-cmds:30195a by kenmcd.
-rw-r--r-- | VERSION | 2 | ||||
-rw-r--r-- | doc/CHANGES | 7 | ||||
-rw-r--r-- | getfacl/getfacl.c | 16 | ||||
-rw-r--r-- | include/walk_tree.h | 11 | ||||
-rw-r--r-- | libmisc/walk_tree.c | 202 | ||||
-rw-r--r-- | setfacl/do_set.c | 10 | ||||
-rw-r--r-- | setfacl/setfacl.c | 4 |
7 files changed, 199 insertions, 53 deletions
@@ -3,5 +3,5 @@ # PKG_MAJOR=2 PKG_MINOR=2 -PKG_REVISION=46 +PKG_REVISION=47 PKG_BUILD=1 diff --git a/doc/CHANGES b/doc/CHANGES index 5ae69f2..9244e7d 100644 --- a/doc/CHANGES +++ b/doc/CHANGES @@ -1,6 +1,7 @@ -2.2.47 -* Update setfacl/getfacl man pages for symlink related options - thanks to Andreas G. +2.2.47 (4 December 2007) +* From Andreas Gruenbacher: + * fix issues for tree walking with file descriptors + - fd duplicates and running out 2.2.46 (21 November 2007) * From Andreas Gruenbacher: diff --git a/getfacl/getfacl.c b/getfacl/getfacl.c index e0c564e..77315f3 100644 --- a/getfacl/getfacl.c +++ b/getfacl/getfacl.c @@ -435,6 +435,16 @@ int do_print(const char *path_p, const struct stat *st, int walk_flags, void *un return 1; } + /* + * Symlinks can never have ACLs, so when doing a physical walk, we + * skip symlinks altogether, and when doing a half-logical walk, we + * skip all non-toplevel symlinks. + */ + if ((walk_flags & WALK_TREE_SYMLINK) && + ((walk_flags & WALK_TREE_PHYSICAL) || + !(walk_flags & (WALK_TREE_TOPLEVEL | WALK_TREE_LOGICAL)))) + return 0; + if (opt_print_acl) { acl = acl_get_file(path_p, ACL_TYPE_ACCESS); if (acl == NULL && (errno == ENOSYS || errno == ENOTSUP)) @@ -697,7 +707,8 @@ int main(int argc, char *argv[]) if (*line == '\0') continue; - had_errors += walk_tree(line, walk_flags, do_print, NULL); + had_errors += walk_tree(line, walk_flags, 0, + do_print, NULL); } if (!feof(stdin)) { fprintf(stderr, _("%s: Standard input: %s\n"), @@ -705,7 +716,8 @@ int main(int argc, char *argv[]) had_errors++; } } else - had_errors += walk_tree(argv[optind], walk_flags, do_print, NULL); + had_errors += walk_tree(argv[optind], walk_flags, 0, + do_print, NULL); optind++; } while (optind < argc); diff --git a/include/walk_tree.h b/include/walk_tree.h index 9045bad..73cbc7c 100644 --- a/include/walk_tree.h +++ b/include/walk_tree.h @@ -26,13 +26,14 @@ #define WALK_TREE_LOGICAL 0x4 #define WALK_TREE_DEREFERENCE 0x8 -#define WALK_TREE_SYMLINK 0x10 -#define WALK_TREE_FAILED 0x20 +#define WALK_TREE_TOPLEVEL 0x100 +#define WALK_TREE_SYMLINK 0x200 +#define WALK_TREE_FAILED 0x400 struct stat; -extern int walk_tree(const char *path, int walk_flags, - int (*func)(const char *, const struct stat *, int, void *), - void *arg); +extern int walk_tree(const char *path, int walk_flags, unsigned int num, + int (*func)(const char *, const struct stat *, int, + void *), void *arg); #endif diff --git a/libmisc/walk_tree.c b/libmisc/walk_tree.c index 62bc74d..bd02d9e 100644 --- a/libmisc/walk_tree.c +++ b/libmisc/walk_tree.c @@ -21,6 +21,8 @@ #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> +#include <sys/time.h> +#include <sys/resource.h> #include <dirent.h> #include <stdio.h> #include <string.h> @@ -28,73 +30,193 @@ #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, - int (*func)(const char *, const struct stat *, int, void *), - void *arg, int depth) + int (*func)(const char *, const struct stat *, int, + 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; - 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 (xstat(path, &st) != 0) - return func(path, NULL, local_walk_flags | WALK_TREE_FAILED, arg); + if (lstat(path, &st) != 0) + return func(path, NULL, flags | WALK_TREE_FAILED, arg); if (S_ISLNK(st.st_mode)) { - if ((local_walk_flags & WALK_TREE_PHYSICAL) || - (!(local_walk_flags & WALK_TREE_LOGICAL) && depth > 1)) - return 0; - local_walk_flags |= WALK_TREE_SYMLINK; - xstat = stat; - if (local_walk_flags & WALK_TREE_DEREFERENCE) - goto follow_symlink; + flags |= WALK_TREE_SYMLINK; + if (flags & WALK_TREE_DEREFERENCE) { + if (stat(path, &st) != 0) + return func(path, NULL, + flags | WALK_TREE_FAILED, arg); + dir.dev = st.st_dev; + 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); - if ((local_walk_flags & WALK_TREE_RECURSIVE) && - (S_ISDIR(st.st_mode) || S_ISLNK(st.st_mode))) { - char path2[FILENAME_MAX]; - DIR *dir; + err = func(path, &st, flags, arg); + if ((flags & WALK_TREE_RECURSIVE) && + (S_ISDIR(st.st_mode) || (S_ISLNK(st.st_mode) && follow_symlinks))) { struct dirent *entry; - int err2; - dir = opendir(path); - if (!dir) { - /* PATH may be a symlink to a regular file or a dead symlink - * which we didn't follow above. + /* + * Check if we have already visited this directory to break + * endless loops. + * + * 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) - err += func(path, &st, - local_walk_flags | WALK_TREE_FAILED, arg); + err += func(path, NULL, flags | + WALK_TREE_FAILED, arg); 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; - err2 = snprintf(path2, sizeof(path2), "%s/%s", path, - entry->d_name); - if (err2 < 0 || err2 > FILENAME_MAX) { + path_end = strchr(path, 0); + if ((path_end - path) + strlen(entry->d_name) + 1 >= + FILENAME_MAX) { errno = ENAMETOOLONG; err += func(path, NULL, - local_walk_flags | WALK_TREE_FAILED, arg); + flags | WALK_TREE_FAILED, arg); 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; } -int walk_tree(const char *path, int walk_flags, - int (*func)(const char *, const struct stat *, int, void *), void *arg) +int walk_tree(const char *path, int walk_flags, unsigned int num, + 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) { errno = ENAMETOOLONG; 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); } diff --git a/setfacl/do_set.c b/setfacl/do_set.c index a4fc760..b0fd88c 100644 --- a/setfacl/do_set.c +++ b/setfacl/do_set.c @@ -279,6 +279,16 @@ do_set( return 1; } + /* + * Symlinks can never have ACLs, so when doing a physical walk, we + * skip symlinks altogether, and when doing a half-logical walk, we + * skip all non-toplevel symlinks. + */ + if ((walk_flags & WALK_TREE_SYMLINK) && + ((walk_flags & WALK_TREE_PHYSICAL) || + !(walk_flags & (WALK_TREE_TOPLEVEL | WALK_TREE_LOGICAL)))) + return 0; + /* Execute the commands in seq (read ACLs on demand) */ error = seq_get_cmd(seq, SEQ_FIRST_CMD, &cmd); if (error == 0) diff --git a/setfacl/setfacl.c b/setfacl/setfacl.c index c9cdec2..db7eed5 100644 --- a/setfacl/setfacl.c +++ b/setfacl/setfacl.c @@ -275,14 +275,14 @@ int next_file(const char *arg, seq_t seq) if (strcmp(arg, "-") == 0) { while ((line = next_line(stdin))) - errors = walk_tree(line, walk_flags, do_set, seq); + errors = walk_tree(line, walk_flags, 0, do_set, seq); if (!feof(stdin)) { fprintf(stderr, _("%s: Standard input: %s\n"), progname, strerror(errno)); errors = 1; } } else { - errors = walk_tree(arg, walk_flags, do_set, seq); + errors = walk_tree(arg, walk_flags, 0, do_set, seq); } return errors ? 1 : 0; } |