diff --git a/src/basic/stat-util.c b/src/basic/stat-util.c index a7812714aa..64c2f80f3c 100644 --- a/src/basic/stat-util.c +++ b/src/basic/stat-util.c @@ -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; } diff --git a/src/basic/stat-util.h b/src/basic/stat-util.h index 4483ceb7de..ce3c8cccfe 100644 --- a/src/basic/stat-util.h +++ b/src/basic/stat-util.h @@ -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; diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c index 69703a74c1..058dac5381 100644 --- a/src/boot/bootctl.c +++ b/src/boot/bootctl.c @@ -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); diff --git a/src/core/main.c b/src/core/main.c index 5fa404fc66..409b84a006 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -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. " diff --git a/src/core/manager.c b/src/core/manager.c index 18daff66c7..d09d14911c 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -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. */ diff --git a/src/core/path.c b/src/core/path.c index 0814391222..69bbddf158 100644 --- a/src/core/path.c +++ b/src/core/path.c @@ -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; } diff --git a/src/core/unit.c b/src/core/unit.c index ff1288dcac..42ee8892a4 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -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) { diff --git a/src/gpt-auto-generator/gpt-auto-generator.c b/src/gpt-auto-generator/gpt-auto-generator.c index cd4b05c52d..589a2cc582 100644 --- a/src/gpt-auto-generator/gpt-auto-generator.c +++ b/src/gpt-auto-generator/gpt-auto-generator.c @@ -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) diff --git a/src/home/homework-cifs.c b/src/home/homework-cifs.c index 6d499f76b2..728a92260c 100644 --- a/src/home/homework-cifs.c +++ b/src/home/homework-cifs.c @@ -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) diff --git a/src/home/user-record-util.c b/src/home/user-record-util.c index 2b727df533..9d85f2abfa 100644 --- a/src/home/user-record-util.c +++ b/src/home/user-record-util.c @@ -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) diff --git a/src/nspawn/nspawn-network.c b/src/nspawn/nspawn-network.c index fab4eb9609..f3d7f403f9 100644 --- a/src/nspawn/nspawn-network.c +++ b/src/nspawn/nspawn-network.c @@ -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) diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 59768bcc0b..c5fd978395 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -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); diff --git a/src/shared/condition.c b/src/shared/condition.c index 1d87ed53ce..67de6fc62f 100644 --- a/src/shared/condition.c +++ b/src/shared/condition.c @@ -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); } diff --git a/src/shared/copy.c b/src/shared/copy.c index 0043c4ff72..4d52b4c26b 100644 --- a/src/shared/copy.c +++ b/src/shared/copy.c @@ -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) diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index 25fed4cf50..057fe14125 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -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) diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index 2d1bc7cf46..84120000aa 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -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"); diff --git a/src/sysext/sysext.c b/src/sysext/sysext.c index 20fbb916e9..76c2fe6978 100644 --- a/src/sysext/sysext.c +++ b/src/sysext/sysext.c @@ -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) { diff --git a/src/test/test-stat-util.c b/src/test/test-stat-util.c index a8fdbb7c18..04d52d364f 100644 --- a/src/test/test-stat-util.c +++ b/src/test/test-stat-util.c @@ -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) {