stat-util: fix dir_is_empty() with hidden/backup files

This is a follow-up for f470cb6d13 which in
turn is a follow-up for a068aceafb.

The latter started to honour hidden files when deciding whether a
directory is empty. The former reverted to the old behaviour to fix
issue #23220.

It introduced a bug though: when a directory contains a larger number of
hidden entries the getdents64() buffer will not suffice to read them,
since we just allocate three entries for it (which is definitely enough
if we just ignore the . + .. entries, but not ig we ignore more).

I think it's a bit confusing that dir_is_empty() can return true even if
rmdir() on the dir would return ENOTEMPTY. Hence, let's rework the
function to make it optional whether hidden files are ignored or not.
After all, I looking at the users of this function I am pretty sure in
more cases we want to honour hidden files.
This commit is contained in:
Lennart Poettering
2022-05-04 13:29:14 +02:00
parent 080b8c2ace
commit db55bbf29b
18 changed files with 58 additions and 42 deletions
+25 -13
View File
@@ -71,13 +71,10 @@ int is_device_node(const char *path) {
return !!(S_ISBLK(info.st_mode) || S_ISCHR(info.st_mode));
}
int dir_is_empty_at(int dir_fd, const char *path) {
int dir_is_empty_at(int dir_fd, const char *path, bool ignore_hidden_or_backup) {
_cleanup_close_ int fd = -1;
/* Allocate space for at least 3 full dirents, since every dir has at least two entries ("." +
* ".."), and only once we have seen if there's a third we know whether the dir is empty or not. */
DEFINE_DIRENT_BUFFER(buffer, 3);
struct dirent *de;
ssize_t n;
struct dirent *buf;
size_t m;
if (path) {
assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
@@ -99,15 +96,30 @@ int dir_is_empty_at(int dir_fd, const char *path) {
return fd;
}
n = getdents64(fd, &buffer, sizeof(buffer));
if (n < 0)
return -errno;
/* Allocate space for at least 3 full dirents, since every dir has at least two entries ("." +
* ".."), and only once we have seen if there's a third we know whether the dir is empty or not. If
* 'ignore_hidden_or_backup' is true we'll allocate a bit more, since we might skip over a bunch of
* entries that we end up ignoring. */
m = (ignore_hidden_or_backup ? 16 : 3) * DIRENT_SIZE_MAX;
buf = alloca(m);
msan_unpoison(&buffer, n);
for (;;) {
struct dirent *de;
ssize_t n;
FOREACH_DIRENT_IN_BUFFER(de, &buffer.de, n)
if (!hidden_or_backup_file(de->d_name))
return 0;
n = getdents64(fd, buf, m);
if (n < 0)
return -errno;
if (n == 0)
break;
assert((size_t) n <= m);
msan_unpoison(buf, n);
FOREACH_DIRENT_IN_BUFFER(de, buf, n)
if (!(ignore_hidden_or_backup ? hidden_or_backup_file(de->d_name) : dot_or_dot_dot(de->d_name)))
return 0;
}
return 1;
}
+5 -5
View File
@@ -17,14 +17,14 @@ int is_dir(const char *path, bool follow);
int is_dir_fd(int fd);
int is_device_node(const char *path);
int dir_is_empty_at(int dir_fd, const char *path);
static inline int dir_is_empty(const char *path) {
return dir_is_empty_at(AT_FDCWD, path);
int dir_is_empty_at(int dir_fd, const char *path, bool ignore_hidden_or_backup);
static inline int dir_is_empty(const char *path, bool ignore_hidden_or_backup) {
return dir_is_empty_at(AT_FDCWD, path, ignore_hidden_or_backup);
}
static inline int dir_is_populated(const char *path) {
static inline int dir_is_populated(const char *path, bool ignore_hidden_or_backup) {
int r;
r = dir_is_empty(path);
r = dir_is_empty(path, ignore_hidden_or_backup);
if (r < 0)
return r;
return !r;
+1 -1
View File
@@ -1639,7 +1639,7 @@ static int are_we_installed(const char *esp_path) {
return log_oom();
log_debug("Checking whether %s contains any files…", p);
r = dir_is_empty(p);
r = dir_is_empty(p, /* ignore_hidden_or_backup= */ false);
if (r < 0 && r != -ENOENT)
return log_error_errno(r, "Failed to check whether %s contains any files: %m", p);
+1 -1
View File
@@ -1290,7 +1290,7 @@ static void test_usr(void) {
/* Check that /usr is either on the same file system as / or mounted already. */
if (dir_is_empty("/usr") <= 0)
if (dir_is_empty("/usr", /* ignore_hidden_or_backup= */ false) <= 0)
return;
log_warning("/usr appears to be on its own filesystem and is not already mounted. This is not a supported setup. "
+1 -1
View File
@@ -976,7 +976,7 @@ int manager_new(LookupScope scope, ManagerTestRunFlags test_run_flags, Manager *
m->taint_usr =
!in_initrd() &&
dir_is_empty("/usr") > 0;
dir_is_empty("/usr", /* ignore_hidden_or_backup= */ false) > 0;
/* Note that we do not set up the notify fd here. We do that after deserialization,
* since they might have gotten serialized across the reexec. */
+1 -1
View File
@@ -213,7 +213,7 @@ static bool path_spec_check_good(PathSpec *s, bool initial, bool from_trigger_no
case PATH_DIRECTORY_NOT_EMPTY: {
int k;
k = dir_is_empty(s->path);
k = dir_is_empty(s->path, /* ignore_hidden_or_backup= */ true);
good = !(IN_SET(k, -ENOENT, -ENOTDIR) || k > 0);
break;
}
+1 -1
View File
@@ -4776,7 +4776,7 @@ void unit_warn_if_dir_nonempty(Unit *u, const char* where) {
if (!unit_log_level_test(u, LOG_NOTICE))
return;
r = dir_is_empty(where);
r = dir_is_empty(where, /* ignore_hidden_or_backup= */ false);
if (r > 0 || r == -ENOTDIR)
return;
if (r < 0) {
+1 -1
View File
@@ -305,7 +305,7 @@ static int path_is_busy(const char *where) {
return log_warning_errno(r, "Cannot check if \"%s\" is a mount point: %m", where);
/* not a mountpoint but it contains files */
r = dir_is_empty(where);
r = dir_is_empty(where, /* ignore_hidden_or_backup= */ false);
if (r < 0)
return log_warning_errno(r, "Cannot check if \"%s\" is empty: %m", where);
if (r > 0)
+1 -1
View File
@@ -207,7 +207,7 @@ int home_create_cifs(UserRecord *h, HomeSetup *setup, UserRecord **ret_home) {
if (r < 0)
return r;
r = dir_is_empty_at(setup->root_fd, NULL);
r = dir_is_empty_at(setup->root_fd, NULL, /* ignore_hidden_or_backup= */ false);
if (r < 0)
return log_error_errno(r, "Failed to detect if CIFS directory is empty: %m");
if (r == 0)
+1 -1
View File
@@ -445,7 +445,7 @@ int user_record_test_home_directory(UserRecord *h) {
}
/* Otherwise it's not OK */
r = dir_is_empty(hd);
r = dir_is_empty(hd, /* ignore_hidden_or_backup= */ false);
if (r < 0)
return r;
if (r == 0)
+1 -1
View File
@@ -449,7 +449,7 @@ int remove_bridge(const char *bridge_name) {
path = strjoina("/sys/class/net/", bridge_name, "/brif");
r = dir_is_empty(path);
r = dir_is_empty(path, /* ignore_hidden_or_backup= */ false);
if (r == -ENOENT) /* Already gone? */
return 0;
if (r < 0)
+1 -1
View File
@@ -2680,7 +2680,7 @@ static int setup_journal(const char *directory) {
} else if (access(p, F_OK) < 0)
return 0;
if (dir_is_empty(q) == 0)
if (dir_is_empty(q, /* ignore_hidden_or_backup= */ false) == 0)
log_warning("%s is not empty, proceeding anyway.", q);
r = userns_mkdir(directory, p, 0755, 0, 0);
+1 -1
View File
@@ -922,7 +922,7 @@ static int condition_test_directory_not_empty(Condition *c, char **env) {
assert(c->parameter);
assert(c->type == CONDITION_DIRECTORY_NOT_EMPTY);
r = dir_is_empty(c->parameter);
r = dir_is_empty(c->parameter, /* ignore_hidden_or_backup= */ true);
return r <= 0 && !IN_SET(r, -ENOENT, -ENOTDIR);
}
+1 -1
View File
@@ -905,7 +905,7 @@ static int fd_copy_directory(
exists = false;
if (copy_flags & COPY_MERGE_EMPTY) {
r = dir_is_empty_at(dt, to);
r = dir_is_empty_at(dt, to, /* ignore_hidden_or_backup= */ false);
if (r < 0 && r != -ENOENT)
return r;
else if (r == 1)
+1 -1
View File
@@ -1552,7 +1552,7 @@ int dissected_image_mount(
if (r < 0) {
if (r != -ENOENT)
return r;
} else if (dir_is_empty(p) > 0) {
} else if (dir_is_empty(p, /* ignore_hidden_or_backup= */ false) > 0) {
/* It exists and is an empty directory. Let's mount the ESP there. */
r = mount_partition(m->partitions + PARTITION_ESP, where, "/boot", uid_shift, uid_range, flags);
if (r < 0)
+1 -1
View File
@@ -1466,7 +1466,7 @@ Tpm2Support tpm2_support(void) {
* got the host sysfs mounted. Since devices are generally not virtualized for containers,
* let's assume containers never have a TPM, at least for now. */
r = dir_is_empty("/sys/class/tpmrm");
r = dir_is_empty("/sys/class/tpmrm", /* ignore_hidden_or_backup= */ false);
if (r < 0) {
if (r != -ENOENT)
log_debug_errno(r, "Unable to test whether /sys/class/tpmrm/ exists and is populated, assuming it is not: %m");
+2 -2
View File
@@ -296,7 +296,7 @@ static int merge_hierarchy(
else if (r < 0)
return log_error_errno(r, "Failed to resolve host hierarchy '%s': %m", hierarchy);
else {
r = dir_is_empty(resolved_hierarchy);
r = dir_is_empty(resolved_hierarchy, /* ignore_hidden_or_backup= */ false);
if (r < 0)
return log_error_errno(r, "Failed to check if host hierarchy '%s' is empty: %m", resolved_hierarchy);
if (r > 0) {
@@ -337,7 +337,7 @@ static int merge_hierarchy(
if (r < 0)
return log_error_errno(r, "Failed to resolve hierarchy '%s' in extension '%s': %m", hierarchy, *p);
r = dir_is_empty(resolved);
r = dir_is_empty(resolved, /* ignore_hidden_or_backup= */ false);
if (r < 0)
return log_error_errno(r, "Failed to check if hierarchy '%s' in extension '%s' is empty: %m", resolved, *p);
if (r > 0) {
+12 -8
View File
@@ -153,17 +153,17 @@ TEST(dir_is_empty) {
_cleanup_(rm_rf_physical_and_freep) char *empty_dir = NULL;
_cleanup_free_ char *j = NULL, *jj = NULL, *jjj = NULL;
assert_se(dir_is_empty_at(AT_FDCWD, "/proc") == 0);
assert_se(dir_is_empty_at(AT_FDCWD, "/icertainlydontexistdoi") == -ENOENT);
assert_se(dir_is_empty_at(AT_FDCWD, "/proc", /* ignore_hidden_or_backup= */ true) == 0);
assert_se(dir_is_empty_at(AT_FDCWD, "/icertainlydontexistdoi", /* ignore_hidden_or_backup= */ true) == -ENOENT);
assert_se(mkdtemp_malloc("/tmp/emptyXXXXXX", &empty_dir) >= 0);
assert_se(dir_is_empty_at(AT_FDCWD, empty_dir) > 0);
assert_se(dir_is_empty_at(AT_FDCWD, empty_dir, /* ignore_hidden_or_backup= */ true) > 0);
j = path_join(empty_dir, "zzz");
assert_se(j);
assert_se(touch(j) >= 0);
assert_se(dir_is_empty_at(AT_FDCWD, empty_dir) == 0);
assert_se(dir_is_empty_at(AT_FDCWD, empty_dir, /* ignore_hidden_or_backup= */ true) == 0);
jj = path_join(empty_dir, "ppp");
assert_se(jj);
@@ -173,13 +173,17 @@ TEST(dir_is_empty) {
assert_se(jjj);
assert_se(touch(jjj) >= 0);
assert_se(dir_is_empty_at(AT_FDCWD, empty_dir) == 0);
assert_se(dir_is_empty_at(AT_FDCWD, empty_dir, /* ignore_hidden_or_backup= */ true) == 0);
assert_se(dir_is_empty_at(AT_FDCWD, empty_dir, /* ignore_hidden_or_backup= */ false) == 0);
assert_se(unlink(j) >= 0);
assert_se(dir_is_empty_at(AT_FDCWD, empty_dir) == 0);
assert_se(dir_is_empty_at(AT_FDCWD, empty_dir, /* ignore_hidden_or_backup= */ true) == 0);
assert_se(dir_is_empty_at(AT_FDCWD, empty_dir, /* ignore_hidden_or_backup= */ false) == 0);
assert_se(unlink(jj) >= 0);
assert_se(dir_is_empty_at(AT_FDCWD, empty_dir) > 0);
assert_se(dir_is_empty_at(AT_FDCWD, empty_dir, /* ignore_hidden_or_backup= */ true) > 0);
assert_se(dir_is_empty_at(AT_FDCWD, empty_dir, /* ignore_hidden_or_backup= */ false) == 0);
assert_se(unlink(jjj) >= 0);
assert_se(dir_is_empty_at(AT_FDCWD, empty_dir) > 0);
assert_se(dir_is_empty_at(AT_FDCWD, empty_dir, /* ignore_hidden_or_backup= */ true) > 0);
assert_se(dir_is_empty_at(AT_FDCWD, empty_dir, /* ignore_hidden_or_backup= */ false) > 0);
}
static int intro(void) {