recurse-dir: optionally, only enumerate dentries of a specific type

At various places we filter directory enumerations by inode type. Let's
add explicit support for that, so that the "struct dirent" array we
return already suppresses them.

This shortens code and makes things more robust.
This commit is contained in:
Lennart Poettering
2026-06-22 14:40:16 +02:00
parent fe83c5be7f
commit 90d285ade7
9 changed files with 129 additions and 55 deletions
+41 -6
View File
@@ -27,6 +27,35 @@ static bool ignore_dirent(const struct dirent *de, RecurseDirFlags flags) {
/* 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);
@@ -39,6 +68,9 @@ int readdir_all(int dir_fd, RecurseDirFlags flags, DirectoryEntries **ret) {
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
@@ -87,9 +119,6 @@ int readdir_all(int dir_fd, RecurseDirFlags flags, DirectoryEntries **ret) {
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)
@@ -99,6 +128,9 @@ int readdir_all(int dir_fd, RecurseDirFlags flags, DirectoryEntries **ret) {
return r;
}
if (ignore_dirent(entry, flags))
continue;
de->n_entries++;
}
@@ -117,14 +149,14 @@ int readdir_all(int dir_fd, RecurseDirFlags flags, DirectoryEntries **ret) {
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);
@@ -166,6 +198,9 @@ int recurse_dir(
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. */
+11 -6
View File
@@ -7,14 +7,19 @@
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 {