util: make sure to fstatat() at most once in rm_rf_children()

This commit is contained in:
Lennart Poettering 2012-05-22 16:14:34 +02:00
parent 49e7f02774
commit 7925c22a78

View file

@ -3146,7 +3146,7 @@ int rm_rf_children(int fd, bool only_dirs, bool honour_sticky, struct stat *root
assert(fd >= 0); assert(fd >= 0);
/* This returns the first error we run into, but nevertheless /* This returns the first error we run into, but nevertheless
* tries to go on */ * tries to go on. This closes the passed fd. */
d = fdopendir(fd); d = fdopendir(fd);
if (!d) { if (!d) {
@ -3157,7 +3157,8 @@ int rm_rf_children(int fd, bool only_dirs, bool honour_sticky, struct stat *root
for (;;) { for (;;) {
struct dirent buf, *de; struct dirent buf, *de;
bool is_dir, keep_around = false; bool is_dir, keep_around;
struct stat st;
int r; int r;
r = readdir_r(d, &buf, &de); r = readdir_r(d, &buf, &de);
@ -3172,73 +3173,50 @@ int rm_rf_children(int fd, bool only_dirs, bool honour_sticky, struct stat *root
if (streq(de->d_name, ".") || streq(de->d_name, "..")) if (streq(de->d_name, ".") || streq(de->d_name, ".."))
continue; continue;
if (de->d_type == DT_UNKNOWN) { if (de->d_type == DT_UNKNOWN ||
struct stat st; honour_sticky ||
(de->d_type == DT_DIR && root_dev)) {
if (fstatat(fd, de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) { if (fstatat(fd, de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
if (ret == 0 && errno != ENOENT) if (ret == 0 && errno != ENOENT)
ret = -errno; ret = -errno;
continue; continue;
} }
if (honour_sticky)
keep_around =
(st.st_uid == 0 || st.st_uid == getuid()) &&
(st.st_mode & S_ISVTX);
is_dir = S_ISDIR(st.st_mode); is_dir = S_ISDIR(st.st_mode);
keep_around =
honour_sticky &&
(st.st_uid == 0 || st.st_uid == getuid()) &&
(st.st_mode & S_ISVTX);
} else { } else {
if (honour_sticky) {
struct stat st;
if (fstatat(fd, de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
if (ret == 0 && errno != ENOENT)
ret = -errno;
continue;
}
keep_around =
(st.st_uid == 0 || st.st_uid == getuid()) &&
(st.st_mode & S_ISVTX);
}
is_dir = de->d_type == DT_DIR; is_dir = de->d_type == DT_DIR;
keep_around = false;
} }
if (is_dir) { if (is_dir) {
int subdir_fd; int subdir_fd;
struct stat sb;
if (root_dev) {
if (fstatat(fd, de->d_name, &sb, AT_SYMLINK_NOFOLLOW)) {
if (ret == 0 && errno != ENOENT)
ret = -errno;
continue;
}
}
/* if root_dev is set, remove subdirectories only, if device is same as dir */ /* if root_dev is set, remove subdirectories only, if device is same as dir */
if ((root_dev == NULL) || (sb.st_dev == root_dev->st_dev)) { if (root_dev && st.st_dev != root_dev->st_dev)
continue;
subdir_fd = openat(fd, de->d_name, subdir_fd = openat(fd, de->d_name,
O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME); O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME);
if (subdir_fd < 0) { if (subdir_fd < 0) {
if (ret == 0 && errno != ENOENT)
ret = -errno;
continue;
}
r = rm_rf_children(subdir_fd, only_dirs, honour_sticky, root_dev);
if (r < 0 && ret == 0)
ret = r;
if (!keep_around)
if (unlinkat(fd, de->d_name, AT_REMOVEDIR) < 0) {
if (ret == 0 && errno != ENOENT) if (ret == 0 && errno != ENOENT)
ret = -errno; ret = -errno;
continue;
} }
r = rm_rf_children(subdir_fd, only_dirs, honour_sticky, root_dev);
if (r < 0 && ret == 0)
ret = r;
if (!keep_around)
if (unlinkat(fd, de->d_name, AT_REMOVEDIR) < 0) {
if (ret == 0 && errno != ENOENT)
ret = -errno;
}
}
} else if (!only_dirs && !keep_around) { } else if (!only_dirs && !keep_around) {
if (unlinkat(fd, de->d_name, 0) < 0) { if (unlinkat(fd, de->d_name, 0) < 0) {