1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
3 #include "alloc-util.h"
4 #include "dirent-util.h"
8 #include "missing_syscall.h"
9 #include "mountpoint-util.h"
10 #include "recurse-dir.h"
11 #include "sort-util.h"
13 #define DEFAULT_RECURSION_MAX 100
15 static int sort_func(struct dirent
* const *a
, struct dirent
* const *b
) {
16 return strcmp((*a
)->d_name
, (*b
)->d_name
);
19 static bool ignore_dirent(const struct dirent
*de
, RecurseDirFlags flags
) {
22 /* Depending on flag either ignore everything starting with ".", or just "." itself and ".." */
24 return FLAGS_SET(flags
, RECURSE_DIR_IGNORE_DOT
) ?
25 de
->d_name
[0] == '.' :
26 dot_or_dot_dot(de
->d_name
);
29 int readdir_all(int dir_fd
,
30 RecurseDirFlags flags
,
31 DirectoryEntries
**ret
) {
33 _cleanup_free_ DirectoryEntries
*de
= NULL
;
35 DirectoryEntries
*nde
;
41 /* Returns an array with pointers to "struct dirent" directory entries, optionally sorted. Free the
42 * array with readdir_all_freep().
44 * Start with space for up to 8 directory entries. We expect at least 2 ("." + ".."), hence hopefully
45 * 8 will cover most cases comprehensively. (Note that most likely a lot more entries will actually
46 * fit in the buffer, given we calculate maximum file name length here.) */
47 de
= malloc(offsetof(DirectoryEntries
, buffer
) + DIRENT_SIZE_MAX
* 8);
56 bs
= MIN(MALLOC_SIZEOF_SAFE(de
) - offsetof(DirectoryEntries
, buffer
), (size_t) SSIZE_MAX
);
57 assert(bs
> de
->buffer_size
);
59 n
= getdents64(dir_fd
, (uint8_t*) de
->buffer
+ de
->buffer_size
, bs
- de
->buffer_size
);
65 msan_unpoison((uint8_t*) de
->buffer
+ de
->buffer_size
, n
);
69 if (de
->buffer_size
< bs
- DIRENT_SIZE_MAX
) /* Still room for one more entry, then try to
70 * fill it up without growing the structure. */
73 if (bs
>= SSIZE_MAX
- offsetof(DirectoryEntries
, buffer
))
75 bs
= bs
>= (SSIZE_MAX
- offsetof(DirectoryEntries
, buffer
))/2 ? SSIZE_MAX
- offsetof(DirectoryEntries
, buffer
) : bs
* 2;
77 nde
= realloc(de
, bs
);
85 FOREACH_DIRENT_IN_BUFFER(entry
, de
->buffer
, de
->buffer_size
) {
86 if (ignore_dirent(entry
, flags
))
89 if (FLAGS_SET(flags
, RECURSE_DIR_ENSURE_TYPE
)) {
90 r
= dirent_ensure_type(dir_fd
, entry
);
92 /* dentry gone by now? no problem, let's just suppress it */
101 sz
= ALIGN(offsetof(DirectoryEntries
, buffer
) + de
->buffer_size
);
102 add
= sizeof(struct dirent
*) * de
->n_entries
;
103 if (add
> SIZE_MAX
- add
)
106 nde
= realloc(de
, sz
+ add
);
111 de
->entries
= (struct dirent
**) ((uint8_t*) de
+ ALIGN(offsetof(DirectoryEntries
, buffer
) + de
->buffer_size
));
114 FOREACH_DIRENT_IN_BUFFER(entry
, de
->buffer
, de
->buffer_size
) {
115 if (ignore_dirent(entry
, flags
))
118 /* If d_type == DT_UNKNOWN that means we failed to ensure the type in the earlier loop and
119 * didn't include the dentry in de->n_entries and as such should skip it here as well. */
120 if (FLAGS_SET(flags
, RECURSE_DIR_ENSURE_TYPE
) && entry
->d_type
== DT_UNKNOWN
)
123 de
->entries
[j
++] = entry
;
125 assert(j
== de
->n_entries
);
127 if (FLAGS_SET(flags
, RECURSE_DIR_SORT
))
128 typesafe_qsort(de
->entries
, de
->n_entries
, sort_func
);
136 int readdir_all_at(int fd
, const char *path
, RecurseDirFlags flags
, DirectoryEntries
**ret
) {
137 _cleanup_close_
int dir_fd
= -EBADF
;
139 assert(fd
>= 0 || fd
== AT_FDCWD
);
141 dir_fd
= xopenat(fd
, path
, O_DIRECTORY
|O_CLOEXEC
, /* xopen_flags= */ 0, /* mode= */ 0);
145 return readdir_all(dir_fd
, flags
, ret
);
152 unsigned n_depth_max
,
153 RecurseDirFlags flags
,
154 recurse_dir_func_t func
,
157 _cleanup_free_ DirectoryEntries
*de
= NULL
;
158 STRUCT_STATX_DEFINE(root_sx
);
164 /* This is a lot like ftw()/nftw(), but a lot more modern, i.e. built around openat()/statx()/O_PATH,
165 * and under the assumption that fds are not as 'expensive' as they used to be. */
167 if (n_depth_max
== 0)
169 if (n_depth_max
== UINT_MAX
) /* special marker for "default" */
170 n_depth_max
= DEFAULT_RECURSION_MAX
;
172 if (FLAGS_SET(flags
, RECURSE_DIR_TOPLEVEL
)) {
173 if (statx_mask
!= 0) {
174 r
= statx_fallback(dir_fd
, "", AT_EMPTY_PATH
, statx_mask
, &root_sx
);
179 r
= func(RECURSE_DIR_ENTER
,
181 -1, /* we have no parent fd */
183 NULL
, /* we have no dirent */
184 statx_mask
!= 0 ? &root_sx
: NULL
,
186 if (IN_SET(r
, RECURSE_DIR_LEAVE_DIRECTORY
, RECURSE_DIR_SKIP_ENTRY
))
188 if (r
!= RECURSE_DIR_CONTINUE
)
192 /* Mask out RECURSE_DIR_ENSURE_TYPE so we can do it ourselves and avoid an extra statx() call. */
193 r
= readdir_all(dir_fd
, flags
& ~RECURSE_DIR_ENSURE_TYPE
, &de
);
197 for (size_t i
= 0; i
< de
->n_entries
; i
++) {
198 _cleanup_close_
int inode_fd
= -EBADF
, subdir_fd
= -EBADF
;
199 _cleanup_free_
char *joined
= NULL
;
200 STRUCT_STATX_DEFINE(sx
);
201 bool sx_valid
= false;
204 /* For each directory entry we'll do one of the following:
206 * 1) If the entry refers to a directory, we'll open it as O_DIRECTORY 'subdir_fd' and then statx() the opened directory via that new fd (if requested)
207 * 2) Otherwise, if RECURSE_DIR_INODE_FD is set we'll open it as O_PATH 'inode_fd' and then statx() the opened inode via that new fd (if requested)
208 * 3) Otherwise, we'll statx() the directory entry via the directory fd we are currently looking at (if requested)
212 joined
= path_join(path
, de
->entries
[i
]->d_name
);
218 p
= de
->entries
[i
]->d_name
;
220 if (IN_SET(de
->entries
[i
]->d_type
, DT_UNKNOWN
, DT_DIR
)) {
221 subdir_fd
= openat(dir_fd
, de
->entries
[i
]->d_name
, O_DIRECTORY
|O_NOFOLLOW
|O_CLOEXEC
);
223 if (errno
== ENOENT
) /* Vanished by now, go for next file immediately */
226 /* If it is a subdir but we failed to open it, then fail */
227 if (!IN_SET(errno
, ENOTDIR
, ELOOP
)) {
228 log_debug_errno(errno
, "Failed to open directory '%s': %m", p
);
230 assert(errno
<= RECURSE_DIR_SKIP_OPEN_DIR_ERROR_MAX
- RECURSE_DIR_SKIP_OPEN_DIR_ERROR_BASE
);
232 r
= func(RECURSE_DIR_SKIP_OPEN_DIR_ERROR_BASE
+ errno
,
239 if (r
== RECURSE_DIR_LEAVE_DIRECTORY
)
241 if (!IN_SET(r
, RECURSE_DIR_CONTINUE
, RECURSE_DIR_SKIP_ENTRY
))
247 /* If it's not a subdir, then let's handle it like a regular inode below */
250 /* If we managed to get a DIR* off the inode, it's definitely a directory. */
251 de
->entries
[i
]->d_type
= DT_DIR
;
253 if (statx_mask
!= 0 || (flags
& RECURSE_DIR_SAME_MOUNT
)) {
254 r
= statx_fallback(subdir_fd
, "", AT_EMPTY_PATH
, statx_mask
, &sx
);
264 /* It's not a subdirectory. */
266 if (flags
& RECURSE_DIR_INODE_FD
) {
268 inode_fd
= openat(dir_fd
, de
->entries
[i
]->d_name
, O_PATH
|O_NOFOLLOW
|O_CLOEXEC
);
270 if (errno
== ENOENT
) /* Vanished by now, go for next file immediately */
273 log_debug_errno(errno
, "Failed to open directory entry '%s': %m", p
);
275 assert(errno
<= RECURSE_DIR_SKIP_OPEN_INODE_ERROR_MAX
- RECURSE_DIR_SKIP_OPEN_INODE_ERROR_BASE
);
277 r
= func(RECURSE_DIR_SKIP_OPEN_INODE_ERROR_BASE
+ errno
,
284 if (r
== RECURSE_DIR_LEAVE_DIRECTORY
)
286 if (!IN_SET(r
, RECURSE_DIR_CONTINUE
, RECURSE_DIR_SKIP_ENTRY
))
292 /* If we open the inode, then verify it's actually a non-directory, like we
293 * assume. Let's guarantee that we never pass statx data of a directory where
294 * caller expects a non-directory */
296 r
= statx_fallback(inode_fd
, "", AT_EMPTY_PATH
, statx_mask
| STATX_TYPE
, &sx
);
300 assert(sx
.stx_mask
& STATX_TYPE
);
303 if (S_ISDIR(sx
.stx_mode
)) {
304 /* What? It's a directory now? Then someone must have quickly
305 * replaced it. Let's handle that gracefully: convert it to a
306 * directory fd — which should be riskless now that we pinned the
309 subdir_fd
= fd_reopen(inode_fd
, O_DIRECTORY
|O_CLOEXEC
);
313 inode_fd
= safe_close(inode_fd
);
316 } else if (statx_mask
!= 0 || (de
->entries
[i
]->d_type
== DT_UNKNOWN
&& (flags
& RECURSE_DIR_ENSURE_TYPE
))) {
318 r
= statx_fallback(dir_fd
, de
->entries
[i
]->d_name
, AT_SYMLINK_NOFOLLOW
, statx_mask
| STATX_TYPE
, &sx
);
319 if (r
== -ENOENT
) /* Vanished by now? Go for next file immediately */
322 log_debug_errno(r
, "Failed to stat directory entry '%s': %m", p
);
324 assert(errno
<= RECURSE_DIR_SKIP_STAT_INODE_ERROR_MAX
- RECURSE_DIR_SKIP_STAT_INODE_ERROR_BASE
);
326 r
= func(RECURSE_DIR_SKIP_STAT_INODE_ERROR_BASE
+ -r
,
333 if (r
== RECURSE_DIR_LEAVE_DIRECTORY
)
335 if (!IN_SET(r
, RECURSE_DIR_CONTINUE
, RECURSE_DIR_SKIP_ENTRY
))
341 assert(sx
.stx_mask
& STATX_TYPE
);
344 if (S_ISDIR(sx
.stx_mode
)) {
345 /* So it suddenly is a directory, but we couldn't open it as such
346 * earlier? That is weird, and probably means somebody is racing
347 * against us. We could of course retry and open it as a directory
348 * again, but the chance to win here is limited. Hence, let's
349 * propagate this as EISDIR error instead. That way we make this
350 * something that can be reasonably handled, even though we give the
351 * guarantee that RECURSE_DIR_ENTRY is strictly issued for
352 * non-directory dirents. */
354 log_debug_errno(r
, "Non-directory entry '%s' suddenly became a directory: %m", p
);
356 r
= func(RECURSE_DIR_SKIP_STAT_INODE_ERROR_BASE
+ EISDIR
,
363 if (r
== RECURSE_DIR_LEAVE_DIRECTORY
)
365 if (!IN_SET(r
, RECURSE_DIR_CONTINUE
, RECURSE_DIR_SKIP_ENTRY
))
374 /* Copy over the data we acquired through statx() if we acquired any */
375 if (sx
.stx_mask
& STATX_TYPE
) {
376 assert((subdir_fd
< 0) == !S_ISDIR(sx
.stx_mode
));
377 de
->entries
[i
]->d_type
= IFTODT(sx
.stx_mode
);
380 if (sx
.stx_mask
& STATX_INO
)
381 de
->entries
[i
]->d_ino
= sx
.stx_ino
;
384 if (subdir_fd
>= 0) {
385 if (FLAGS_SET(flags
, RECURSE_DIR_SAME_MOUNT
)) {
388 if (sx_valid
&& FLAGS_SET(sx
.stx_attributes_mask
, STATX_ATTR_MOUNT_ROOT
))
389 is_mount
= FLAGS_SET(sx
.stx_attributes
, STATX_ATTR_MOUNT_ROOT
);
391 r
= fd_is_mount_point(dir_fd
, de
->entries
[i
]->d_name
, 0);
393 log_debug_errno(r
, "Failed to determine whether %s is a submount, assuming not: %m", p
);
399 r
= func(RECURSE_DIR_SKIP_MOUNT
,
404 statx_mask
!= 0 ? &sx
: NULL
, /* only pass sx if user asked for it */
406 if (r
== RECURSE_DIR_LEAVE_DIRECTORY
)
408 if (!IN_SET(r
, RECURSE_DIR_CONTINUE
, RECURSE_DIR_SKIP_ENTRY
))
415 if (n_depth_max
<= 1) {
416 /* When we reached max depth, generate a special event */
418 r
= func(RECURSE_DIR_SKIP_DEPTH
,
423 statx_mask
!= 0 ? &sx
: NULL
, /* only pass sx if user asked for it */
425 if (r
== RECURSE_DIR_LEAVE_DIRECTORY
)
427 if (!IN_SET(r
, RECURSE_DIR_CONTINUE
, RECURSE_DIR_SKIP_ENTRY
))
433 r
= func(RECURSE_DIR_ENTER
,
438 statx_mask
!= 0 ? &sx
: NULL
, /* only pass sx if user asked for it */
440 if (r
== RECURSE_DIR_LEAVE_DIRECTORY
)
442 if (r
== RECURSE_DIR_SKIP_ENTRY
)
444 if (r
!= RECURSE_DIR_CONTINUE
)
447 r
= recurse_dir(subdir_fd
,
451 flags
&~ RECURSE_DIR_TOPLEVEL
, /* we already called the callback for this entry */
457 r
= func(RECURSE_DIR_LEAVE
,
462 statx_mask
!= 0 ? &sx
: NULL
, /* only pass sx if user asked for it */
465 /* Non-directory inode */
466 r
= func(RECURSE_DIR_ENTRY
,
471 statx_mask
!= 0 ? &sx
: NULL
, /* only pass sx if user asked for it */
475 if (r
== RECURSE_DIR_LEAVE_DIRECTORY
)
477 if (!IN_SET(r
, RECURSE_DIR_SKIP_ENTRY
, RECURSE_DIR_CONTINUE
))
481 if (FLAGS_SET(flags
, RECURSE_DIR_TOPLEVEL
)) {
483 r
= func(RECURSE_DIR_LEAVE
,
488 statx_mask
!= 0 ? &root_sx
: NULL
,
490 if (!IN_SET(r
, RECURSE_DIR_LEAVE_DIRECTORY
, RECURSE_DIR_SKIP_ENTRY
, RECURSE_DIR_CONTINUE
))
501 unsigned n_depth_max
,
502 RecurseDirFlags flags
,
503 recurse_dir_func_t func
,
506 _cleanup_close_
int fd
= -EBADF
;
508 assert(atfd
>= 0 || atfd
== AT_FDCWD
);
511 fd
= openat(atfd
, path
?: ".", O_DIRECTORY
|O_CLOEXEC
);
515 return recurse_dir(fd
, path
, statx_mask
, n_depth_max
, flags
, func
, userdata
);