1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
3 #include "alloc-util.h"
4 #include "dirent-util.h"
7 #include "missing_syscall.h"
8 #include "mountpoint-util.h"
9 #include "recurse-dir.h"
10 #include "sort-util.h"
12 #define DEFAULT_RECURSION_MAX 100
14 static int sort_func(struct dirent
* const *a
, struct dirent
* const *b
) {
15 return strcmp((*a
)->d_name
, (*b
)->d_name
);
18 static bool ignore_dirent(const struct dirent
*de
, RecurseDirFlags flags
) {
21 /* Depending on flag either ignore everything starting with ".", or just "." itself and ".." */
23 return FLAGS_SET(flags
, RECURSE_DIR_IGNORE_DOT
) ?
24 de
->d_name
[0] == '.' :
25 dot_or_dot_dot(de
->d_name
);
28 int readdir_all(int dir_fd
,
29 RecurseDirFlags flags
,
30 DirectoryEntries
**ret
) {
32 _cleanup_free_ DirectoryEntries
*de
= NULL
;
34 DirectoryEntries
*nde
;
40 /* Returns an array with pointers to "struct dirent" directory entries, optionally sorted. Free the
41 * array with readdir_all_freep().
43 * Start with space for up to 8 directory entries. We expect at least 2 ("." + ".."), hence hopefully
44 * 8 will cover most cases comprehensively. (Note that most likely a lot more entries will actually
45 * fit in the buffer, given we calculate maximum file name length here.) */
46 de
= malloc(offsetof(DirectoryEntries
, buffer
) + DIRENT_SIZE_MAX
* 8);
55 bs
= MIN(MALLOC_SIZEOF_SAFE(de
) - offsetof(DirectoryEntries
, buffer
), (size_t) SSIZE_MAX
);
56 assert(bs
> de
->buffer_size
);
58 n
= getdents64(dir_fd
, (uint8_t*) de
->buffer
+ de
->buffer_size
, bs
- de
->buffer_size
);
64 msan_unpoison((uint8_t*) de
->buffer
+ de
->buffer_size
, n
);
68 if (de
->buffer_size
< bs
- DIRENT_SIZE_MAX
) /* Still room for one more entry, then try to
69 * fill it up without growing the structure. */
72 if (bs
>= SSIZE_MAX
- offsetof(DirectoryEntries
, buffer
))
74 bs
= bs
>= (SSIZE_MAX
- offsetof(DirectoryEntries
, buffer
))/2 ? SSIZE_MAX
- offsetof(DirectoryEntries
, buffer
) : bs
* 2;
76 nde
= realloc(de
, bs
);
84 FOREACH_DIRENT_IN_BUFFER(entry
, de
->buffer
, de
->buffer_size
) {
85 if (ignore_dirent(entry
, flags
))
88 if (FLAGS_SET(flags
, RECURSE_DIR_ENSURE_TYPE
)) {
89 r
= dirent_ensure_type(dir_fd
, entry
);
91 /* dentry gone by now? no problem, let's just suppress it */
100 sz
= ALIGN(offsetof(DirectoryEntries
, buffer
) + de
->buffer_size
);
101 add
= sizeof(struct dirent
*) * de
->n_entries
;
102 if (add
> SIZE_MAX
- add
)
105 nde
= realloc(de
, sz
+ add
);
110 de
->entries
= (struct dirent
**) ((uint8_t*) de
+ ALIGN(offsetof(DirectoryEntries
, buffer
) + de
->buffer_size
));
113 FOREACH_DIRENT_IN_BUFFER(entry
, de
->buffer
, de
->buffer_size
) {
114 if (ignore_dirent(entry
, flags
))
117 /* If d_type == DT_UNKNOWN that means we failed to ensure the type in the earlier loop and
118 * didn't include the dentry in de->n_entries and as such should skip it here as well. */
119 if (FLAGS_SET(flags
, RECURSE_DIR_ENSURE_TYPE
) && entry
->d_type
== DT_UNKNOWN
)
122 de
->entries
[j
++] = entry
;
124 assert(j
== de
->n_entries
);
126 if (FLAGS_SET(flags
, RECURSE_DIR_SORT
))
127 typesafe_qsort(de
->entries
, de
->n_entries
, sort_func
);
139 unsigned n_depth_max
,
140 RecurseDirFlags flags
,
141 recurse_dir_func_t func
,
144 _cleanup_free_ DirectoryEntries
*de
= NULL
;
145 STRUCT_STATX_DEFINE(root_sx
);
151 /* This is a lot like ftw()/nftw(), but a lot more modern, i.e. built around openat()/statx()/O_PATH,
152 * and under the assumption that fds are not as 'expensive' as they used to be. */
154 if (n_depth_max
== 0)
156 if (n_depth_max
== UINT_MAX
) /* special marker for "default" */
157 n_depth_max
= DEFAULT_RECURSION_MAX
;
159 if (FLAGS_SET(flags
, RECURSE_DIR_TOPLEVEL
)) {
160 if (statx_mask
!= 0) {
161 r
= statx_fallback(dir_fd
, "", AT_EMPTY_PATH
, statx_mask
, &root_sx
);
166 r
= func(RECURSE_DIR_ENTER
,
168 -1, /* we have no parent fd */
170 NULL
, /* we have no dirent */
171 statx_mask
!= 0 ? &root_sx
: NULL
,
173 if (IN_SET(r
, RECURSE_DIR_LEAVE_DIRECTORY
, RECURSE_DIR_SKIP_ENTRY
))
175 if (r
!= RECURSE_DIR_CONTINUE
)
179 /* Mask out RECURSE_DIR_ENSURE_TYPE so we can do it ourselves and avoid an extra statx() call. */
180 r
= readdir_all(dir_fd
, flags
& ~RECURSE_DIR_ENSURE_TYPE
, &de
);
184 for (size_t i
= 0; i
< de
->n_entries
; i
++) {
185 _cleanup_close_
int inode_fd
= -EBADF
, subdir_fd
= -EBADF
;
186 _cleanup_free_
char *joined
= NULL
;
187 STRUCT_STATX_DEFINE(sx
);
188 bool sx_valid
= false;
191 /* For each directory entry we'll do one of the following:
193 * 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)
194 * 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)
195 * 3) Otherwise, we'll statx() the directory entry via the directory fd we are currently looking at (if requested)
199 joined
= path_join(path
, de
->entries
[i
]->d_name
);
205 p
= de
->entries
[i
]->d_name
;
207 if (IN_SET(de
->entries
[i
]->d_type
, DT_UNKNOWN
, DT_DIR
)) {
208 subdir_fd
= openat(dir_fd
, de
->entries
[i
]->d_name
, O_DIRECTORY
|O_NOFOLLOW
|O_CLOEXEC
);
210 if (errno
== ENOENT
) /* Vanished by now, go for next file immediately */
213 /* If it is a subdir but we failed to open it, then fail */
214 if (!IN_SET(errno
, ENOTDIR
, ELOOP
)) {
215 log_debug_errno(errno
, "Failed to open directory '%s': %m", p
);
217 assert(errno
<= RECURSE_DIR_SKIP_OPEN_DIR_ERROR_MAX
- RECURSE_DIR_SKIP_OPEN_DIR_ERROR_BASE
);
219 r
= func(RECURSE_DIR_SKIP_OPEN_DIR_ERROR_BASE
+ errno
,
226 if (r
== RECURSE_DIR_LEAVE_DIRECTORY
)
228 if (!IN_SET(r
, RECURSE_DIR_CONTINUE
, RECURSE_DIR_SKIP_ENTRY
))
234 /* If it's not a subdir, then let's handle it like a regular inode below */
237 /* If we managed to get a DIR* off the inode, it's definitely a directory. */
238 de
->entries
[i
]->d_type
= DT_DIR
;
240 if (statx_mask
!= 0 || (flags
& RECURSE_DIR_SAME_MOUNT
)) {
241 r
= statx_fallback(subdir_fd
, "", AT_EMPTY_PATH
, statx_mask
, &sx
);
251 /* It's not a subdirectory. */
253 if (flags
& RECURSE_DIR_INODE_FD
) {
255 inode_fd
= openat(dir_fd
, de
->entries
[i
]->d_name
, O_PATH
|O_NOFOLLOW
|O_CLOEXEC
);
257 if (errno
== ENOENT
) /* Vanished by now, go for next file immediately */
260 log_debug_errno(errno
, "Failed to open directory entry '%s': %m", p
);
262 assert(errno
<= RECURSE_DIR_SKIP_OPEN_INODE_ERROR_MAX
- RECURSE_DIR_SKIP_OPEN_INODE_ERROR_BASE
);
264 r
= func(RECURSE_DIR_SKIP_OPEN_INODE_ERROR_BASE
+ errno
,
271 if (r
== RECURSE_DIR_LEAVE_DIRECTORY
)
273 if (!IN_SET(r
, RECURSE_DIR_CONTINUE
, RECURSE_DIR_SKIP_ENTRY
))
279 /* If we open the inode, then verify it's actually a non-directory, like we
280 * assume. Let's guarantee that we never pass statx data of a directory where
281 * caller expects a non-directory */
283 r
= statx_fallback(inode_fd
, "", AT_EMPTY_PATH
, statx_mask
| STATX_TYPE
, &sx
);
287 assert(sx
.stx_mask
& STATX_TYPE
);
290 if (S_ISDIR(sx
.stx_mode
)) {
291 /* What? It's a directory now? Then someone must have quickly
292 * replaced it. Let's handle that gracefully: convert it to a
293 * directory fd — which should be riskless now that we pinned the
296 subdir_fd
= fd_reopen(inode_fd
, O_DIRECTORY
|O_CLOEXEC
);
300 inode_fd
= safe_close(inode_fd
);
303 } else if (statx_mask
!= 0 || (de
->entries
[i
]->d_type
== DT_UNKNOWN
&& (flags
& RECURSE_DIR_ENSURE_TYPE
))) {
305 r
= statx_fallback(dir_fd
, de
->entries
[i
]->d_name
, AT_SYMLINK_NOFOLLOW
, statx_mask
| STATX_TYPE
, &sx
);
306 if (r
== -ENOENT
) /* Vanished by now? Go for next file immediately */
309 log_debug_errno(r
, "Failed to stat directory entry '%s': %m", p
);
311 assert(errno
<= RECURSE_DIR_SKIP_STAT_INODE_ERROR_MAX
- RECURSE_DIR_SKIP_STAT_INODE_ERROR_BASE
);
313 r
= func(RECURSE_DIR_SKIP_STAT_INODE_ERROR_BASE
+ -r
,
320 if (r
== RECURSE_DIR_LEAVE_DIRECTORY
)
322 if (!IN_SET(r
, RECURSE_DIR_CONTINUE
, RECURSE_DIR_SKIP_ENTRY
))
328 assert(sx
.stx_mask
& STATX_TYPE
);
331 if (S_ISDIR(sx
.stx_mode
)) {
332 /* So it suddenly is a directory, but we couldn't open it as such
333 * earlier? That is weird, and probably means somebody is racing
334 * against us. We could of course retry and open it as a directory
335 * again, but the chance to win here is limited. Hence, let's
336 * propagate this as EISDIR error instead. That way we make this
337 * something that can be reasonably handled, even though we give the
338 * guarantee that RECURSE_DIR_ENTRY is strictly issued for
339 * non-directory dirents. */
341 log_debug_errno(r
, "Non-directory entry '%s' suddenly became a directory: %m", p
);
343 r
= func(RECURSE_DIR_SKIP_STAT_INODE_ERROR_BASE
+ EISDIR
,
350 if (r
== RECURSE_DIR_LEAVE_DIRECTORY
)
352 if (!IN_SET(r
, RECURSE_DIR_CONTINUE
, RECURSE_DIR_SKIP_ENTRY
))
361 /* Copy over the data we acquired through statx() if we acquired any */
362 if (sx
.stx_mask
& STATX_TYPE
) {
363 assert((subdir_fd
< 0) == !S_ISDIR(sx
.stx_mode
));
364 de
->entries
[i
]->d_type
= IFTODT(sx
.stx_mode
);
367 if (sx
.stx_mask
& STATX_INO
)
368 de
->entries
[i
]->d_ino
= sx
.stx_ino
;
371 if (subdir_fd
>= 0) {
372 if (FLAGS_SET(flags
, RECURSE_DIR_SAME_MOUNT
)) {
375 if (sx_valid
&& FLAGS_SET(sx
.stx_attributes_mask
, STATX_ATTR_MOUNT_ROOT
))
376 is_mount
= FLAGS_SET(sx
.stx_attributes
, STATX_ATTR_MOUNT_ROOT
);
378 r
= fd_is_mount_point(dir_fd
, de
->entries
[i
]->d_name
, 0);
380 log_debug_errno(r
, "Failed to determine whether %s is a submount, assuming not: %m", p
);
386 r
= func(RECURSE_DIR_SKIP_MOUNT
,
391 statx_mask
!= 0 ? &sx
: NULL
, /* only pass sx if user asked for it */
393 if (r
== RECURSE_DIR_LEAVE_DIRECTORY
)
395 if (!IN_SET(r
, RECURSE_DIR_CONTINUE
, RECURSE_DIR_SKIP_ENTRY
))
402 if (n_depth_max
<= 1) {
403 /* When we reached max depth, generate a special event */
405 r
= func(RECURSE_DIR_SKIP_DEPTH
,
410 statx_mask
!= 0 ? &sx
: NULL
, /* only pass sx if user asked for it */
412 if (r
== RECURSE_DIR_LEAVE_DIRECTORY
)
414 if (!IN_SET(r
, RECURSE_DIR_CONTINUE
, RECURSE_DIR_SKIP_ENTRY
))
420 r
= func(RECURSE_DIR_ENTER
,
425 statx_mask
!= 0 ? &sx
: NULL
, /* only pass sx if user asked for it */
427 if (r
== RECURSE_DIR_LEAVE_DIRECTORY
)
429 if (r
== RECURSE_DIR_SKIP_ENTRY
)
431 if (r
!= RECURSE_DIR_CONTINUE
)
434 r
= recurse_dir(subdir_fd
,
438 flags
&~ RECURSE_DIR_TOPLEVEL
, /* we already called the callback for this entry */
444 r
= func(RECURSE_DIR_LEAVE
,
449 statx_mask
!= 0 ? &sx
: NULL
, /* only pass sx if user asked for it */
452 /* Non-directory inode */
453 r
= func(RECURSE_DIR_ENTRY
,
458 statx_mask
!= 0 ? &sx
: NULL
, /* only pass sx if user asked for it */
462 if (r
== RECURSE_DIR_LEAVE_DIRECTORY
)
464 if (!IN_SET(r
, RECURSE_DIR_SKIP_ENTRY
, RECURSE_DIR_CONTINUE
))
468 if (FLAGS_SET(flags
, RECURSE_DIR_TOPLEVEL
)) {
470 r
= func(RECURSE_DIR_LEAVE
,
475 statx_mask
!= 0 ? &root_sx
: NULL
,
477 if (!IN_SET(r
, RECURSE_DIR_LEAVE_DIRECTORY
, RECURSE_DIR_SKIP_ENTRY
, RECURSE_DIR_CONTINUE
))
488 unsigned n_depth_max
,
489 RecurseDirFlags flags
,
490 recurse_dir_func_t func
,
493 _cleanup_close_
int fd
= -EBADF
;
495 assert(atfd
>= 0 || atfd
== AT_FDCWD
);
498 fd
= openat(atfd
, path
?: ".", O_DIRECTORY
|O_CLOEXEC
);
502 return recurse_dir(fd
, path
, statx_mask
, n_depth_max
, flags
, func
, userdata
);