stat-util: fix dir_is_empty() with hidden/backup files
This is a follow-up forf470cb6d13which in turn is a follow-up fora068aceafb. 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:
+25
-13
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user