1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
5 #include "alloc-util.h"
6 #include "dirent-util.h"
10 #include "mountpoint-util.h"
11 #include "path-util.h"
12 #include "recurse-dir.h"
13 #include "sort-util.h"
15 #define DEFAULT_RECURSION_MAX 100
17 static int sort_func(struct dirent
* const *a
, struct dirent
* const *b
) {
18 return strcmp((*a
)->d_name
, (*b
)->d_name
);
21 static bool ignore_dirent(const struct dirent
*de
, RecurseDirFlags flags
) {
24 /* Depending on flag either ignore everything starting with ".", or just "." itself and ".." */
26 return FLAGS_SET(flags
, RECURSE_DIR_IGNORE_DOT
) ?
27 de
->d_name
[0] == '.' :
28 dot_or_dot_dot(de
->d_name
);
31 int readdir_all(int dir_fd
, RecurseDirFlags flags
, DirectoryEntries
**ret
) {
32 _cleanup_free_ DirectoryEntries
*de
= NULL
;
33 DirectoryEntries
*nde
;
38 /* Returns an array with pointers to "struct dirent" directory entries, optionally sorted. Free the
39 * array with readdir_all_freep().
41 * Start with space for up to 8 directory entries. We expect at least 2 ("." + ".."), hence hopefully
42 * 8 will cover most cases comprehensively. (Note that most likely a lot more entries will actually
43 * fit in the buffer, given we calculate maximum file name length here.) */
44 de
= malloc(offsetof(DirectoryEntries
, buffer
) + DIRENT_SIZE_MAX
* 8);
53 bs
= MIN(MALLOC_SIZEOF_SAFE(de
) - offsetof(DirectoryEntries
, buffer
), (size_t) SSIZE_MAX
);
54 assert(bs
> de
->buffer_size
);
56 n
= getdents64(dir_fd
, (struct dirent
*) ((uint8_t*) de
->buffer
+ de
->buffer_size
), bs
- de
->buffer_size
);
62 msan_unpoison((uint8_t*) de
->buffer
+ de
->buffer_size
, n
);
66 if (de
->buffer_size
< bs
- DIRENT_SIZE_MAX
) /* Still room for one more entry, then try to
67 * fill it up without growing the structure. */
70 if (bs
>= SSIZE_MAX
- offsetof(DirectoryEntries
, buffer
))
72 bs
= bs
>= (SSIZE_MAX
- offsetof(DirectoryEntries
, buffer
))/2 ? SSIZE_MAX
- offsetof(DirectoryEntries
, buffer
) : bs
* 2;
74 nde
= realloc(de
, bs
);
82 FOREACH_DIRENT_IN_BUFFER(entry
, de
->buffer
, de
->buffer_size
) {
83 if (ignore_dirent(entry
, flags
))
86 if (FLAGS_SET(flags
, RECURSE_DIR_ENSURE_TYPE
)) {
87 r
= dirent_ensure_type(dir_fd
, entry
);
89 /* dentry gone by now? no problem, let's just suppress it */
100 sz
= ALIGN(offsetof(DirectoryEntries
, buffer
) + de
->buffer_size
);
101 if (!INC_SAFE(&sz
, sizeof(struct dirent
*) * de
->n_entries
))
104 nde
= realloc(de
, sz
);
109 de
->entries
= (struct dirent
**) ((uint8_t*) de
+ ALIGN(offsetof(DirectoryEntries
, buffer
) + de
->buffer_size
));
112 FOREACH_DIRENT_IN_BUFFER(entry
, de
->buffer
, de
->buffer_size
) {
113 if (ignore_dirent(entry
, flags
))
116 /* If d_type == DT_UNKNOWN that means we failed to ensure the type in the earlier loop and
117 * didn't include the dentry in de->n_entries and as such should skip it here as well. */
118 if (FLAGS_SET(flags
, RECURSE_DIR_ENSURE_TYPE
) && entry
->d_type
== DT_UNKNOWN
)
121 de
->entries
[j
++] = entry
;
123 assert(j
== de
->n_entries
);
125 if (FLAGS_SET(flags
, RECURSE_DIR_SORT
))
126 typesafe_qsort(de
->entries
, de
->n_entries
, sort_func
);
134 int readdir_all_at(int fd
, const char *path
, RecurseDirFlags flags
, DirectoryEntries
**ret
) {
135 _cleanup_close_
int dir_fd
= -EBADF
;
137 assert(fd
>= 0 || fd
== AT_FDCWD
);
139 dir_fd
= xopenat(fd
, path
, O_DIRECTORY
|O_CLOEXEC
);
143 return readdir_all(dir_fd
, flags
, ret
);
150 unsigned n_depth_max
,
151 RecurseDirFlags flags
,
152 recurse_dir_func_t func
,
155 _cleanup_free_ DirectoryEntries
*de
= NULL
;
156 struct statx root_sx
;
162 /* This is a lot like ftw()/nftw(), but a lot more modern, i.e. built around openat()/statx()/O_PATH,
163 * and under the assumption that fds are not as 'expensive' as they used to be. */
165 if (n_depth_max
== 0)
167 if (n_depth_max
== UINT_MAX
) /* special marker for "default" */
168 n_depth_max
= DEFAULT_RECURSION_MAX
;
170 if (FLAGS_SET(flags
, RECURSE_DIR_TOPLEVEL
)) {
171 if (statx_mask
!= 0) {
172 if (statx(dir_fd
, "", AT_EMPTY_PATH
, statx_mask
, &root_sx
) < 0)
176 r
= func(RECURSE_DIR_ENTER
,
178 -1, /* we have no parent fd */
180 NULL
, /* we have no dirent */
181 statx_mask
!= 0 ? &root_sx
: NULL
,
183 if (IN_SET(r
, RECURSE_DIR_LEAVE_DIRECTORY
, RECURSE_DIR_SKIP_ENTRY
))
185 if (r
!= RECURSE_DIR_CONTINUE
)
189 /* Mask out RECURSE_DIR_ENSURE_TYPE so we can do it ourselves and avoid an extra statx() call. */
190 r
= readdir_all(dir_fd
, flags
& ~RECURSE_DIR_ENSURE_TYPE
, &de
);
194 for (size_t i
= 0; i
< de
->n_entries
; i
++) {
195 _cleanup_close_
int inode_fd
= -EBADF
, subdir_fd
= -EBADF
;
196 _cleanup_free_
char *joined
= NULL
;
198 bool sx_valid
= false;
201 /* For each directory entry we'll do one of the following:
203 * 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)
204 * 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)
205 * 3) Otherwise, we'll statx() the directory entry via the directory fd we are currently looking at (if requested)
209 joined
= path_join(path
, de
->entries
[i
]->d_name
);
215 p
= de
->entries
[i
]->d_name
;
217 if (IN_SET(de
->entries
[i
]->d_type
, DT_UNKNOWN
, DT_DIR
)) {
218 subdir_fd
= openat(dir_fd
, de
->entries
[i
]->d_name
, O_DIRECTORY
|O_NOFOLLOW
|O_CLOEXEC
);
220 if (errno
== ENOENT
) /* Vanished by now, go for next file immediately */
223 /* If it is a subdir but we failed to open it, then fail */
224 if (!IN_SET(errno
, ENOTDIR
, ELOOP
)) {
225 log_debug_errno(errno
, "Failed to open directory '%s': %m", p
);
227 assert(errno
<= RECURSE_DIR_SKIP_OPEN_DIR_ERROR_MAX
- RECURSE_DIR_SKIP_OPEN_DIR_ERROR_BASE
);
229 r
= func(RECURSE_DIR_SKIP_OPEN_DIR_ERROR_BASE
+ errno
,
236 if (r
== RECURSE_DIR_LEAVE_DIRECTORY
)
238 if (!IN_SET(r
, RECURSE_DIR_CONTINUE
, RECURSE_DIR_SKIP_ENTRY
))
244 /* If it's not a subdir, then let's handle it like a regular inode below */
247 /* If we managed to get a DIR* off the inode, it's definitely a directory. */
248 de
->entries
[i
]->d_type
= DT_DIR
;
250 if (statx_mask
!= 0 || (flags
& RECURSE_DIR_SAME_MOUNT
)) {
251 if (statx(subdir_fd
, "", AT_EMPTY_PATH
, statx_mask
, &sx
) < 0)
260 /* It's not a subdirectory. */
262 if (flags
& RECURSE_DIR_INODE_FD
) {
264 inode_fd
= openat(dir_fd
, de
->entries
[i
]->d_name
, O_PATH
|O_NOFOLLOW
|O_CLOEXEC
);
266 if (errno
== ENOENT
) /* Vanished by now, go for next file immediately */
269 log_debug_errno(errno
, "Failed to open directory entry '%s': %m", p
);
271 assert(errno
<= RECURSE_DIR_SKIP_OPEN_INODE_ERROR_MAX
- RECURSE_DIR_SKIP_OPEN_INODE_ERROR_BASE
);
273 r
= func(RECURSE_DIR_SKIP_OPEN_INODE_ERROR_BASE
+ errno
,
280 if (r
== RECURSE_DIR_LEAVE_DIRECTORY
)
282 if (!IN_SET(r
, RECURSE_DIR_CONTINUE
, RECURSE_DIR_SKIP_ENTRY
))
288 /* If we open the inode, then verify it's actually a non-directory, like we
289 * assume. Let's guarantee that we never pass statx data of a directory where
290 * caller expects a non-directory */
292 if (statx(inode_fd
, "", AT_EMPTY_PATH
, statx_mask
| STATX_TYPE
, &sx
) < 0)
295 assert(sx
.stx_mask
& STATX_TYPE
);
298 if (S_ISDIR(sx
.stx_mode
)) {
299 /* What? It's a directory now? Then someone must have quickly
300 * replaced it. Let's handle that gracefully: convert it to a
301 * directory fd — which should be riskless now that we pinned the
304 subdir_fd
= fd_reopen(inode_fd
, O_DIRECTORY
|O_CLOEXEC
);
308 inode_fd
= safe_close(inode_fd
);
311 } else if (statx_mask
!= 0 || (de
->entries
[i
]->d_type
== DT_UNKNOWN
&& (flags
& RECURSE_DIR_ENSURE_TYPE
))) {
313 if (statx(dir_fd
, de
->entries
[i
]->d_name
, AT_SYMLINK_NOFOLLOW
, statx_mask
| STATX_TYPE
, &sx
) < 0) {
314 if (errno
== ENOENT
) /* Vanished by now? Go for next file immediately */
317 log_debug_errno(errno
, "Failed to stat directory entry '%s': %m", p
);
319 assert(errno
<= RECURSE_DIR_SKIP_STAT_INODE_ERROR_MAX
- RECURSE_DIR_SKIP_STAT_INODE_ERROR_BASE
);
321 r
= func(RECURSE_DIR_SKIP_STAT_INODE_ERROR_BASE
+ errno
,
328 if (r
== RECURSE_DIR_LEAVE_DIRECTORY
)
330 if (!IN_SET(r
, RECURSE_DIR_CONTINUE
, RECURSE_DIR_SKIP_ENTRY
))
336 assert(sx
.stx_mask
& STATX_TYPE
);
339 if (S_ISDIR(sx
.stx_mode
)) {
340 /* So it suddenly is a directory, but we couldn't open it as such
341 * earlier? That is weird, and probably means somebody is racing
342 * against us. We could of course retry and open it as a directory
343 * again, but the chance to win here is limited. Hence, let's
344 * propagate this as EISDIR error instead. That way we make this
345 * something that can be reasonably handled, even though we give the
346 * guarantee that RECURSE_DIR_ENTRY is strictly issued for
347 * non-directory dirents. */
349 log_debug("Non-directory entry '%s' suddenly became a directory.", p
);
351 r
= func(RECURSE_DIR_SKIP_STAT_INODE_ERROR_BASE
+ EISDIR
,
358 if (r
== RECURSE_DIR_LEAVE_DIRECTORY
)
360 if (!IN_SET(r
, RECURSE_DIR_CONTINUE
, RECURSE_DIR_SKIP_ENTRY
))
369 /* Copy over the data we acquired through statx() if we acquired any */
370 if (sx
.stx_mask
& STATX_TYPE
) {
371 assert((subdir_fd
< 0) == !S_ISDIR(sx
.stx_mode
));
372 de
->entries
[i
]->d_type
= IFTODT(sx
.stx_mode
);
375 if (sx
.stx_mask
& STATX_INO
)
376 de
->entries
[i
]->d_ino
= sx
.stx_ino
;
379 if (subdir_fd
>= 0) {
380 if (FLAGS_SET(flags
, RECURSE_DIR_SAME_MOUNT
)) {
383 if (sx_valid
&& FLAGS_SET(sx
.stx_attributes_mask
, STATX_ATTR_MOUNT_ROOT
))
384 is_mount
= FLAGS_SET(sx
.stx_attributes
, STATX_ATTR_MOUNT_ROOT
);
386 r
= is_mount_point_at(dir_fd
, de
->entries
[i
]->d_name
, 0);
388 log_debug_errno(r
, "Failed to determine whether %s is a submount, assuming not: %m", p
);
394 r
= func(RECURSE_DIR_SKIP_MOUNT
,
399 statx_mask
!= 0 ? &sx
: NULL
, /* only pass sx if user asked for it */
401 if (r
== RECURSE_DIR_LEAVE_DIRECTORY
)
403 if (!IN_SET(r
, RECURSE_DIR_CONTINUE
, RECURSE_DIR_SKIP_ENTRY
))
410 if (n_depth_max
<= 1) {
411 /* When we reached max depth, generate a special event */
413 r
= func(RECURSE_DIR_SKIP_DEPTH
,
418 statx_mask
!= 0 ? &sx
: NULL
, /* only pass sx if user asked for it */
420 if (r
== RECURSE_DIR_LEAVE_DIRECTORY
)
422 if (!IN_SET(r
, RECURSE_DIR_CONTINUE
, RECURSE_DIR_SKIP_ENTRY
))
428 r
= func(RECURSE_DIR_ENTER
,
433 statx_mask
!= 0 ? &sx
: NULL
, /* only pass sx if user asked for it */
435 if (r
== RECURSE_DIR_LEAVE_DIRECTORY
)
437 if (r
== RECURSE_DIR_SKIP_ENTRY
)
439 if (r
!= RECURSE_DIR_CONTINUE
)
442 r
= recurse_dir(subdir_fd
,
446 flags
&~ RECURSE_DIR_TOPLEVEL
, /* we already called the callback for this entry */
452 r
= func(RECURSE_DIR_LEAVE
,
457 statx_mask
!= 0 ? &sx
: NULL
, /* only pass sx if user asked for it */
460 /* Non-directory inode */
461 r
= func(RECURSE_DIR_ENTRY
,
466 statx_mask
!= 0 ? &sx
: NULL
, /* only pass sx if user asked for it */
469 if (r
== RECURSE_DIR_LEAVE_DIRECTORY
)
471 if (!IN_SET(r
, RECURSE_DIR_SKIP_ENTRY
, RECURSE_DIR_CONTINUE
))
475 if (FLAGS_SET(flags
, RECURSE_DIR_TOPLEVEL
)) {
477 r
= func(RECURSE_DIR_LEAVE
,
482 statx_mask
!= 0 ? &root_sx
: NULL
,
484 if (!IN_SET(r
, RECURSE_DIR_LEAVE_DIRECTORY
, RECURSE_DIR_SKIP_ENTRY
, RECURSE_DIR_CONTINUE
))
495 unsigned n_depth_max
,
496 RecurseDirFlags flags
,
497 recurse_dir_func_t func
,
500 _cleanup_close_
int fd
= -EBADF
;
502 assert(atfd
>= 0 || atfd
== AT_FDCWD
);
505 fd
= openat(atfd
, path
?: ".", O_DIRECTORY
|O_CLOEXEC
);
509 return recurse_dir(fd
, path
, statx_mask
, n_depth_max
, flags
, func
, userdata
);