/* Depending on flag either ignore everything starting with ".", or just "." itself and ".." */
+ if ((flags & _RECURSE_DIR_MUST_BE_MASK) != 0) {
+ RecurseDirFlags f;
+
+ switch (de->d_type) {
+
+ case DT_DIR:
+ f = RECURSE_DIR_MUST_BE_DIRECTORY;
+ break;
+
+ case DT_REG:
+ f = RECURSE_DIR_MUST_BE_REGULAR;
+ break;
+
+ case DT_LNK:
+ f = RECURSE_DIR_MUST_BE_SYMLINK;
+ break;
+
+ case DT_SOCK:
+ f = RECURSE_DIR_MUST_BE_SOCKET;
+ break;
+
+ default:
+ return true;
+ }
+
+ if (!FLAGS_SET(flags, f))
+ return true;
+ }
+
return FLAGS_SET(flags, RECURSE_DIR_IGNORE_DOT) ?
de->d_name[0] == '.' :
dot_or_dot_dot(de->d_name);
assert(dir_fd >= 0);
+ if ((flags & _RECURSE_DIR_MUST_BE_MASK) != 0) /* We need the type to validate it */
+ flags |= RECURSE_DIR_ENSURE_TYPE;
+
/* Returns an array with pointers to "struct dirent" directory entries, optionally sorted.
*
* Start with space for up to 8 directory entries. We expect at least 2 ("." + ".."), hence hopefully
de->n_entries = 0;
struct dirent *entry;
FOREACH_DIRENT_IN_BUFFER(entry, de->buffer, de->buffer_size) {
- if (ignore_dirent(entry, flags))
- continue;
-
if (FLAGS_SET(flags, RECURSE_DIR_ENSURE_TYPE)) {
r = dirent_ensure_type(dir_fd, entry);
if (r == -ENOENT)
return r;
}
+ if (ignore_dirent(entry, flags))
+ continue;
+
de->n_entries++;
}
j = 0;
FOREACH_DIRENT_IN_BUFFER(entry, de->buffer, de->buffer_size) {
- if (ignore_dirent(entry, flags))
- continue;
-
/* If d_type == DT_UNKNOWN that means we failed to ensure the type in the earlier loop and
* didn't include the dentry in de->n_entries and as such should skip it here as well. */
if (FLAGS_SET(flags, RECURSE_DIR_ENSURE_TYPE) && entry->d_type == DT_UNKNOWN)
continue;
+ if (ignore_dirent(entry, flags))
+ continue;
+
de->entries[j++] = entry;
}
assert(j == de->n_entries);
assert(dir_fd >= 0);
assert(func);
+ /* We cannot descend into dirs if we are supposed to ignore them */
+ assert((flags & _RECURSE_DIR_MUST_BE_MASK) == 0 || FLAGS_SET(flags, RECURSE_DIR_MUST_BE_DIRECTORY));
+
/* This is a lot like ftw()/nftw(), but a lot more modern, i.e. built around openat()/statx()/O_PATH,
* and under the assumption that fds are not as 'expensive' as they used to be. */
typedef enum RecurseDirFlags {
/* Interpreted by readdir_all() */
- RECURSE_DIR_SORT = 1 << 0, /* sort file directory entries before processing them */
- RECURSE_DIR_IGNORE_DOT = 1 << 1, /* ignore all dot files ("." and ".." are always ignored) */
- RECURSE_DIR_ENSURE_TYPE = 1 << 2, /* guarantees that 'd_type' field of 'de' is not DT_UNKNOWN */
+ RECURSE_DIR_SORT = 1 << 0, /* sort file directory entries before processing them */
+ RECURSE_DIR_IGNORE_DOT = 1 << 1, /* ignore all dot files ("." and ".." are always ignored) */
+ RECURSE_DIR_ENSURE_TYPE = 1 << 2, /* guarantees that 'd_type' field of 'de' is not DT_UNKNOWN */
+ RECURSE_DIR_MUST_BE_DIRECTORY = 1 << 3, /* ignore all entries that aren't directories */
+ RECURSE_DIR_MUST_BE_REGULAR = 1 << 4, /* ignore all entries that aren't regular files */
+ RECURSE_DIR_MUST_BE_SYMLINK = 1 << 5, /* ignore all entries that aren't symlinks */
+ RECURSE_DIR_MUST_BE_SOCKET = 1 << 6, /* ignore all entries that aren't socket */
+ _RECURSE_DIR_MUST_BE_MASK = RECURSE_DIR_MUST_BE_DIRECTORY|RECURSE_DIR_MUST_BE_REGULAR|RECURSE_DIR_MUST_BE_SYMLINK|RECURSE_DIR_MUST_BE_SOCKET,
/* Interpreted by recurse_dir() */
- RECURSE_DIR_SAME_MOUNT = 1 << 3, /* skips over subdirectories that are submounts */
- RECURSE_DIR_INODE_FD = 1 << 4, /* passes an opened inode fd (O_DIRECTORY fd in case of dirs, O_PATH otherwise) */
- RECURSE_DIR_TOPLEVEL = 1 << 5, /* call RECURSE_DIR_ENTER/RECURSE_DIR_LEAVE once for top-level dir, too, with dir_fd=-1 and NULL dirent */
+ RECURSE_DIR_SAME_MOUNT = 1 << 7, /* skips over subdirectories that are submounts */
+ RECURSE_DIR_INODE_FD = 1 << 8, /* passes an opened inode fd (O_DIRECTORY fd in case of dirs, O_PATH otherwise) */
+ RECURSE_DIR_TOPLEVEL = 1 << 9, /* call RECURSE_DIR_ENTER/RECURSE_DIR_LEAVE once for top-level dir, too, with dir_fd=-1 and NULL dirent */
} RecurseDirFlags;
typedef struct DirectoryEntries {
assert(credentials_dir);
- r = readdir_all_at(AT_FDCWD, credentials_dir, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_ENSURE_TYPE, &des);
+ r = readdir_all_at(AT_FDCWD, credentials_dir, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_MUST_BE_REGULAR, &des);
if (r < 0)
return log_error_errno(r, "Failed to enumerate credentials from credentials directory '%s': %m", credentials_dir);
struct dirent *de = *i;
const char *unit, *dropin;
- if (de->d_type != DT_REG)
- continue;
-
unit = startswith(de->d_name, "systemd.extra-unit.");
dropin = startswith(de->d_name, "systemd.unit-dropin.");
#include "build.h"
#include "chase.h"
#include "conf-files.h"
-#include "dirent-util.h"
#include "dissect-image.h"
#include "env-file.h"
#include "env-util.h"
return log_error_errno(fd, "Failed to open %s/usr/lib/modules/: %m", strempty(arg_root));
_cleanup_free_ DirectoryEntries *de = NULL;
- r = readdir_all(fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT, &de);
+ r = readdir_all(fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_MUST_BE_DIRECTORY, &de);
if (r < 0)
return log_error_errno(r, "Failed to numerate /usr/lib/modules/ contents: %m");
FOREACH_ARRAY(d, de->entries, de->n_entries) {
- r = dirent_ensure_type(fd, *d);
- if (r < 0) {
- if (r != -ENOENT) /* don't log if just gone by now */
- log_debug_errno(r, "Failed to check if '%s/usr/lib/modules/%s' is a directory, ignoring: %m", strempty(arg_root), (*d)->d_name);
- continue;
- }
-
- if ((*d)->d_type != DT_DIR)
- continue;
-
_cleanup_free_ char *fn = path_join((*d)->d_name, "vmlinuz");
if (!fn)
return log_oom();
return log_error_errno(fd, "Failed to open %s/usr/lib/modules/: %m", strempty(arg_root));
_cleanup_free_ DirectoryEntries *de = NULL;
- r = readdir_all(fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT, &de);
+ r = readdir_all(fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_MUST_BE_DIRECTORY, &de);
if (r < 0)
return log_error_errno(r, "Failed to numerate /usr/lib/modules/ contents: %m");
if (!j)
return log_oom();
- r = dirent_ensure_type(fd, *d);
- if (r < 0) {
- if (r != -ENOENT) /* don't log if just gone by now */
- log_debug_errno(r, "Failed to check if '%s/%s' is a directory, ignoring: %m", strempty(arg_root), j);
- continue;
- }
-
- if ((*d)->d_type != DT_DIR)
- continue;
-
_cleanup_free_ char *fn = path_join((*d)->d_name, "vmlinuz");
if (!fn)
return log_oom();
return log_debug_errno(errno, "Failed to open '%s': %m", path);
_cleanup_free_ DirectoryEntries *dentries = NULL;
- r = readdir_all(fd, RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_ENSURE_TYPE, &dentries);
+ r = readdir_all(fd, RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_MUST_BE_SOCKET|RECURSE_DIR_MUST_BE_SYMLINK, &dentries);
if (r < 0)
return log_debug_errno(r, "Failed to enumerate '%s': %m", path);
FOREACH_ARRAY(dp, dentries->entries, dentries->n_entries) {
struct dirent *de = *dp;
- if (!IN_SET(de->d_type, DT_SOCK, DT_LNK))
- continue;
-
t++;
_cleanup_free_ char *j = path_join(path, de->d_name);
_cleanup_free_ DirectoryEntries *de = NULL;
- r = readdir_all_at(dir_fd, uid_fn, RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_ENSURE_TYPE, &de);
+ r = readdir_all_at(dir_fd, uid_fn, RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_MUST_BE_REGULAR, &de);
if (r == -ENOENT)
return 0;
if (r < 0)
FOREACH_ARRAY(i, de->entries, de->n_entries) {
struct dirent *e = *i;
- if (e->d_type != DT_REG)
- continue;
-
if (!startswith(e->d_name, "i") || !endswith(e->d_name, ".userns"))
continue;
return log_error_errno(credential_dir_fd, "Failed to open credentials directory: %m");
_cleanup_free_ DirectoryEntries *des = NULL;
- r = readdir_all(credential_dir_fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_ENSURE_TYPE, &des);
+ r = readdir_all(credential_dir_fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_MUST_BE_REGULAR, &des);
if (r < 0)
return log_error_errno(r, "Failed to enumerate credentials: %m");
FOREACH_ARRAY(i, des->entries, des->n_entries) {
struct dirent *de = *i;
- if (de->d_type != DT_REG)
- continue;
-
FOREACH_ARRAY(t, table, n_table_entry) {
r = pick_up_credential_one(credential_dir_fd, de->d_name, t);
if (r != 0) {
return log_error_errno(errno, "Failed to open '%s': %m", socket_path);
} else {
_cleanup_free_ DirectoryEntries *dentries = NULL;
- r = readdir_all(fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_ENSURE_TYPE, &dentries);
+ r = readdir_all(fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_MUST_BE_SOCKET, &dentries);
if (r < 0)
return log_error_errno(r, "Failed to enumerate '%s': %m", socket_path);
FOREACH_ARRAY(dp, dentries->entries, dentries->n_entries) {
struct dirent *de = *dp;
- if (de->d_type != DT_SOCK)
- continue;
-
if (!storage_provider_name_is_valid(de->d_name))
continue;
#include <ftw.h>
#include <linux/magic.h>
+#include <sys/stat.h>
#include "fd-util.h"
#include "log.h"
#include "recurse-dir.h"
+#include "rm-rf.h"
#include "stat-util.h"
#include "strv.h"
#include "tests.h"
#include "time-util.h"
+#include "tmpfile-util.h"
static char **list_nftw = NULL;
return RECURSE_DIR_CONTINUE;
}
+static void assert_entries(DirectoryEntries *de, char **expected) {
+ ASSERT_NOT_NULL(de);
+
+ /* Verifies that the directory entries enumerated in 'de' are exactly the ones listed in
+ * 'expected' (order is irrelevant). */
+
+ ASSERT_EQ(de->n_entries, strv_length(expected));
+
+ FOREACH_ARRAY(i, de->entries, de->n_entries)
+ ASSERT_TRUE(strv_contains(expected, (*i)->d_name));
+
+ STRV_FOREACH(e, expected) {
+ bool found = false;
+
+ FOREACH_ARRAY(i, de->entries, de->n_entries)
+ if (streq((*i)->d_name, *e)) {
+ found = true;
+ break;
+ }
+
+ ASSERT_TRUE(found);
+ }
+}
+
+static void check_readdir_all(int tfd, RecurseDirFlags flags, char **expected) {
+ _cleanup_free_ DirectoryEntries *de = NULL;
+ _cleanup_close_ int fd = -EBADF;
+
+ /* readdir_all() consumes the directory fd offset, hence reopen a fresh fd for each enumeration. */
+ ASSERT_OK(fd = fd_reopen(tfd, O_DIRECTORY|O_CLOEXEC));
+ ASSERT_OK(readdir_all(fd, flags, &de));
+ assert_entries(de, expected);
+}
+
+static void test_must_be_flags(void) {
+ _cleanup_(rm_rf_physical_and_freep) char *t = NULL;
+ _cleanup_close_ int tfd = -EBADF, reg_fd = -EBADF;
+
+ log_info("/* %s */", __func__);
+
+ /* Populate a temporary directory with one entry of each interesting type and verify that the
+ * RECURSE_DIR_MUST_BE_* flags select exactly the right subset. */
+
+ ASSERT_OK(tfd = mkdtemp_open(NULL, O_DIRECTORY|O_CLOEXEC, &t));
+
+ ASSERT_OK_ERRNO(mkdirat(tfd, "dir", 0777));
+ ASSERT_OK_ERRNO(reg_fd = openat(tfd, "reg", O_CREAT|O_EXCL|O_WRONLY|O_CLOEXEC, 0666));
+ reg_fd = safe_close(reg_fd);
+ ASSERT_OK_ERRNO(symlinkat("reg", tfd, "lnk"));
+ ASSERT_OK_ERRNO(mknodat(tfd, "sock", S_IFSOCK|0666, 0));
+
+ /* Without any MUST_BE flag we get all four entries. */
+ check_readdir_all(tfd, 0, STRV_MAKE("dir", "reg", "lnk", "sock"));
+
+ /* A single MUST_BE flag selects exactly the entries of the matching type. */
+ check_readdir_all(tfd, RECURSE_DIR_MUST_BE_DIRECTORY, STRV_MAKE("dir"));
+ check_readdir_all(tfd, RECURSE_DIR_MUST_BE_REGULAR, STRV_MAKE("reg"));
+ check_readdir_all(tfd, RECURSE_DIR_MUST_BE_SYMLINK, STRV_MAKE("lnk"));
+ check_readdir_all(tfd, RECURSE_DIR_MUST_BE_SOCKET, STRV_MAKE("sock"));
+
+ /* The flags may be combined, in which case we get the union of the matching entries. */
+ check_readdir_all(tfd, RECURSE_DIR_MUST_BE_REGULAR|RECURSE_DIR_MUST_BE_SYMLINK, STRV_MAKE("reg", "lnk"));
+ check_readdir_all(tfd, RECURSE_DIR_MUST_BE_DIRECTORY|RECURSE_DIR_MUST_BE_SOCKET, STRV_MAKE("dir", "sock"));
+}
+
int main(int argc, char *argv[]) {
_cleanup_strv_free_ char **list_recurse_dir = NULL;
const char *p;
log_show_color(true);
test_setup_logging(LOG_INFO);
+ test_must_be_flags();
+
if (argc > 1)
p = argv[1];
else