]> git.ipfire.org Git - thirdparty/systemd.git/blame_incremental - src/basic/recurse-dir.c
man: Fix typo in name of sd_id128_to_uuid_string
[thirdparty/systemd.git] / src / basic / recurse-dir.c
... / ...
CommitLineData
1/* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3#include <sys/stat.h>
4
5#include "alloc-util.h"
6#include "dirent-util.h"
7#include "fd-util.h"
8#include "fs-util.h"
9#include "log.h"
10#include "mountpoint-util.h"
11#include "path-util.h"
12#include "recurse-dir.h"
13#include "sort-util.h"
14
15#define DEFAULT_RECURSION_MAX 100
16
17static int sort_func(struct dirent * const *a, struct dirent * const *b) {
18 return strcmp((*a)->d_name, (*b)->d_name);
19}
20
21static bool ignore_dirent(const struct dirent *de, RecurseDirFlags flags) {
22 assert(de);
23
24 /* Depending on flag either ignore everything starting with ".", or just "." itself and ".." */
25
26 return FLAGS_SET(flags, RECURSE_DIR_IGNORE_DOT) ?
27 de->d_name[0] == '.' :
28 dot_or_dot_dot(de->d_name);
29}
30
31int readdir_all(int dir_fd, RecurseDirFlags flags, DirectoryEntries **ret) {
32 _cleanup_free_ DirectoryEntries *de = NULL;
33 DirectoryEntries *nde;
34 int r;
35
36 assert(dir_fd >= 0);
37
38 /* Returns an array with pointers to "struct dirent" directory entries, optionally sorted. Free the
39 * array with readdir_all_freep().
40 *
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);
45 if (!de)
46 return -ENOMEM;
47
48 de->buffer_size = 0;
49 for (;;) {
50 size_t bs;
51 ssize_t n;
52
53 bs = MIN(MALLOC_SIZEOF_SAFE(de) - offsetof(DirectoryEntries, buffer), (size_t) SSIZE_MAX);
54 assert(bs > de->buffer_size);
55
56 n = getdents64(dir_fd, (struct dirent*) ((uint8_t*) de->buffer + de->buffer_size), bs - de->buffer_size);
57 if (n < 0)
58 return -errno;
59 if (n == 0)
60 break;
61
62 msan_unpoison((uint8_t*) de->buffer + de->buffer_size, n);
63
64 de->buffer_size += n;
65
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. */
68 continue;
69
70 if (bs >= SSIZE_MAX - offsetof(DirectoryEntries, buffer))
71 return -EFBIG;
72 bs = bs >= (SSIZE_MAX - offsetof(DirectoryEntries, buffer))/2 ? SSIZE_MAX - offsetof(DirectoryEntries, buffer) : bs * 2;
73
74 nde = realloc(de, bs);
75 if (!nde)
76 return -ENOMEM;
77 de = nde;
78 }
79
80 de->n_entries = 0;
81 struct dirent *entry;
82 FOREACH_DIRENT_IN_BUFFER(entry, de->buffer, de->buffer_size) {
83 if (ignore_dirent(entry, flags))
84 continue;
85
86 if (FLAGS_SET(flags, RECURSE_DIR_ENSURE_TYPE)) {
87 r = dirent_ensure_type(dir_fd, entry);
88 if (r == -ENOENT)
89 /* dentry gone by now? no problem, let's just suppress it */
90 continue;
91 if (r < 0)
92 return r;
93 }
94
95 de->n_entries++;
96 }
97
98 size_t sz, j;
99
100 sz = ALIGN(offsetof(DirectoryEntries, buffer) + de->buffer_size);
101 if (!INC_SAFE(&sz, sizeof(struct dirent*) * de->n_entries))
102 return -ENOMEM;
103
104 nde = realloc(de, sz);
105 if (!nde)
106 return -ENOMEM;
107 de = nde;
108
109 de->entries = (struct dirent**) ((uint8_t*) de + ALIGN(offsetof(DirectoryEntries, buffer) + de->buffer_size));
110
111 j = 0;
112 FOREACH_DIRENT_IN_BUFFER(entry, de->buffer, de->buffer_size) {
113 if (ignore_dirent(entry, flags))
114 continue;
115
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)
119 continue;
120
121 de->entries[j++] = entry;
122 }
123 assert(j == de->n_entries);
124
125 if (FLAGS_SET(flags, RECURSE_DIR_SORT))
126 typesafe_qsort(de->entries, de->n_entries, sort_func);
127
128 if (ret)
129 *ret = TAKE_PTR(de);
130
131 return 0;
132}
133
134int readdir_all_at(int fd, const char *path, RecurseDirFlags flags, DirectoryEntries **ret) {
135 _cleanup_close_ int dir_fd = -EBADF;
136
137 assert(fd >= 0 || fd == AT_FDCWD);
138
139 dir_fd = xopenat(fd, path, O_DIRECTORY|O_CLOEXEC);
140 if (dir_fd < 0)
141 return dir_fd;
142
143 return readdir_all(dir_fd, flags, ret);
144}
145
146int recurse_dir(
147 int dir_fd,
148 const char *path,
149 unsigned statx_mask,
150 unsigned n_depth_max,
151 RecurseDirFlags flags,
152 recurse_dir_func_t func,
153 void *userdata) {
154
155 _cleanup_free_ DirectoryEntries *de = NULL;
156 struct statx root_sx;
157 int r;
158
159 assert(dir_fd >= 0);
160 assert(func);
161
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. */
164
165 if (n_depth_max == 0)
166 return -EOVERFLOW;
167 if (n_depth_max == UINT_MAX) /* special marker for "default" */
168 n_depth_max = DEFAULT_RECURSION_MAX;
169
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)
173 return -errno;
174 }
175
176 r = func(RECURSE_DIR_ENTER,
177 path,
178 -1, /* we have no parent fd */
179 dir_fd,
180 NULL, /* we have no dirent */
181 statx_mask != 0 ? &root_sx : NULL,
182 userdata);
183 if (IN_SET(r, RECURSE_DIR_LEAVE_DIRECTORY, RECURSE_DIR_SKIP_ENTRY))
184 return 0;
185 if (r != RECURSE_DIR_CONTINUE)
186 return r;
187 }
188
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);
191 if (r < 0)
192 return r;
193
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;
197 struct statx sx;
198 bool sx_valid = false;
199 const char *p;
200
201 /* For each directory entry we'll do one of the following:
202 *
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)
206 */
207
208 if (path) {
209 joined = path_join(path, de->entries[i]->d_name);
210 if (!joined)
211 return -ENOMEM;
212
213 p = joined;
214 } else
215 p = de->entries[i]->d_name;
216
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);
219 if (subdir_fd < 0) {
220 if (errno == ENOENT) /* Vanished by now, go for next file immediately */
221 continue;
222
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);
226
227 assert(errno <= RECURSE_DIR_SKIP_OPEN_DIR_ERROR_MAX - RECURSE_DIR_SKIP_OPEN_DIR_ERROR_BASE);
228
229 r = func(RECURSE_DIR_SKIP_OPEN_DIR_ERROR_BASE + errno,
230 p,
231 dir_fd,
232 -1,
233 de->entries[i],
234 NULL,
235 userdata);
236 if (r == RECURSE_DIR_LEAVE_DIRECTORY)
237 break;
238 if (!IN_SET(r, RECURSE_DIR_CONTINUE, RECURSE_DIR_SKIP_ENTRY))
239 return r;
240
241 continue;
242 }
243
244 /* If it's not a subdir, then let's handle it like a regular inode below */
245
246 } else {
247 /* If we managed to get a DIR* off the inode, it's definitely a directory. */
248 de->entries[i]->d_type = DT_DIR;
249
250 if (statx_mask != 0 || (flags & RECURSE_DIR_SAME_MOUNT)) {
251 if (statx(subdir_fd, "", AT_EMPTY_PATH, statx_mask, &sx) < 0)
252 return -errno;
253
254 sx_valid = true;
255 }
256 }
257 }
258
259 if (subdir_fd < 0) {
260 /* It's not a subdirectory. */
261
262 if (flags & RECURSE_DIR_INODE_FD) {
263
264 inode_fd = openat(dir_fd, de->entries[i]->d_name, O_PATH|O_NOFOLLOW|O_CLOEXEC);
265 if (inode_fd < 0) {
266 if (errno == ENOENT) /* Vanished by now, go for next file immediately */
267 continue;
268
269 log_debug_errno(errno, "Failed to open directory entry '%s': %m", p);
270
271 assert(errno <= RECURSE_DIR_SKIP_OPEN_INODE_ERROR_MAX - RECURSE_DIR_SKIP_OPEN_INODE_ERROR_BASE);
272
273 r = func(RECURSE_DIR_SKIP_OPEN_INODE_ERROR_BASE + errno,
274 p,
275 dir_fd,
276 -1,
277 de->entries[i],
278 NULL,
279 userdata);
280 if (r == RECURSE_DIR_LEAVE_DIRECTORY)
281 break;
282 if (!IN_SET(r, RECURSE_DIR_CONTINUE, RECURSE_DIR_SKIP_ENTRY))
283 return r;
284
285 continue;
286 }
287
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 */
291
292 if (statx(inode_fd, "", AT_EMPTY_PATH, statx_mask | STATX_TYPE, &sx) < 0)
293 return -errno;
294
295 assert(sx.stx_mask & STATX_TYPE);
296 sx_valid = true;
297
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
302 * inode. */
303
304 subdir_fd = fd_reopen(inode_fd, O_DIRECTORY|O_CLOEXEC);
305 if (subdir_fd < 0)
306 return subdir_fd;
307
308 inode_fd = safe_close(inode_fd);
309 }
310
311 } else if (statx_mask != 0 || (de->entries[i]->d_type == DT_UNKNOWN && (flags & RECURSE_DIR_ENSURE_TYPE))) {
312
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 */
315 continue;
316
317 log_debug_errno(errno, "Failed to stat directory entry '%s': %m", p);
318
319 assert(errno <= RECURSE_DIR_SKIP_STAT_INODE_ERROR_MAX - RECURSE_DIR_SKIP_STAT_INODE_ERROR_BASE);
320
321 r = func(RECURSE_DIR_SKIP_STAT_INODE_ERROR_BASE + errno,
322 p,
323 dir_fd,
324 -1,
325 de->entries[i],
326 NULL,
327 userdata);
328 if (r == RECURSE_DIR_LEAVE_DIRECTORY)
329 break;
330 if (!IN_SET(r, RECURSE_DIR_CONTINUE, RECURSE_DIR_SKIP_ENTRY))
331 return r;
332
333 continue;
334 }
335
336 assert(sx.stx_mask & STATX_TYPE);
337 sx_valid = true;
338
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. */
348
349 log_debug("Non-directory entry '%s' suddenly became a directory.", p);
350
351 r = func(RECURSE_DIR_SKIP_STAT_INODE_ERROR_BASE + EISDIR,
352 p,
353 dir_fd,
354 -1,
355 de->entries[i],
356 NULL,
357 userdata);
358 if (r == RECURSE_DIR_LEAVE_DIRECTORY)
359 break;
360 if (!IN_SET(r, RECURSE_DIR_CONTINUE, RECURSE_DIR_SKIP_ENTRY))
361 return r;
362
363 continue;
364 }
365 }
366 }
367
368 if (sx_valid) {
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);
373 }
374
375 if (sx.stx_mask & STATX_INO)
376 de->entries[i]->d_ino = sx.stx_ino;
377 }
378
379 if (subdir_fd >= 0) {
380 if (FLAGS_SET(flags, RECURSE_DIR_SAME_MOUNT)) {
381 bool is_mount;
382
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);
385 else {
386 r = is_mount_point_at(dir_fd, de->entries[i]->d_name, 0);
387 if (r < 0)
388 log_debug_errno(r, "Failed to determine whether %s is a submount, assuming not: %m", p);
389
390 is_mount = r > 0;
391 }
392
393 if (is_mount) {
394 r = func(RECURSE_DIR_SKIP_MOUNT,
395 p,
396 dir_fd,
397 subdir_fd,
398 de->entries[i],
399 statx_mask != 0 ? &sx : NULL, /* only pass sx if user asked for it */
400 userdata);
401 if (r == RECURSE_DIR_LEAVE_DIRECTORY)
402 break;
403 if (!IN_SET(r, RECURSE_DIR_CONTINUE, RECURSE_DIR_SKIP_ENTRY))
404 return r;
405
406 continue;
407 }
408 }
409
410 if (n_depth_max <= 1) {
411 /* When we reached max depth, generate a special event */
412
413 r = func(RECURSE_DIR_SKIP_DEPTH,
414 p,
415 dir_fd,
416 subdir_fd,
417 de->entries[i],
418 statx_mask != 0 ? &sx : NULL, /* only pass sx if user asked for it */
419 userdata);
420 if (r == RECURSE_DIR_LEAVE_DIRECTORY)
421 break;
422 if (!IN_SET(r, RECURSE_DIR_CONTINUE, RECURSE_DIR_SKIP_ENTRY))
423 return r;
424
425 continue;
426 }
427
428 r = func(RECURSE_DIR_ENTER,
429 p,
430 dir_fd,
431 subdir_fd,
432 de->entries[i],
433 statx_mask != 0 ? &sx : NULL, /* only pass sx if user asked for it */
434 userdata);
435 if (r == RECURSE_DIR_LEAVE_DIRECTORY)
436 break;
437 if (r == RECURSE_DIR_SKIP_ENTRY)
438 continue;
439 if (r != RECURSE_DIR_CONTINUE)
440 return r;
441
442 r = recurse_dir(subdir_fd,
443 p,
444 statx_mask,
445 n_depth_max - 1,
446 flags &~ RECURSE_DIR_TOPLEVEL, /* we already called the callback for this entry */
447 func,
448 userdata);
449 if (r != 0)
450 return r;
451
452 r = func(RECURSE_DIR_LEAVE,
453 p,
454 dir_fd,
455 subdir_fd,
456 de->entries[i],
457 statx_mask != 0 ? &sx : NULL, /* only pass sx if user asked for it */
458 userdata);
459 } else
460 /* Non-directory inode */
461 r = func(RECURSE_DIR_ENTRY,
462 p,
463 dir_fd,
464 inode_fd,
465 de->entries[i],
466 statx_mask != 0 ? &sx : NULL, /* only pass sx if user asked for it */
467 userdata);
468
469 if (r == RECURSE_DIR_LEAVE_DIRECTORY)
470 break;
471 if (!IN_SET(r, RECURSE_DIR_SKIP_ENTRY, RECURSE_DIR_CONTINUE))
472 return r;
473 }
474
475 if (FLAGS_SET(flags, RECURSE_DIR_TOPLEVEL)) {
476
477 r = func(RECURSE_DIR_LEAVE,
478 path,
479 -1,
480 dir_fd,
481 NULL,
482 statx_mask != 0 ? &root_sx : NULL,
483 userdata);
484 if (!IN_SET(r, RECURSE_DIR_LEAVE_DIRECTORY, RECURSE_DIR_SKIP_ENTRY, RECURSE_DIR_CONTINUE))
485 return r;
486 }
487
488 return 0;
489}
490
491int recurse_dir_at(
492 int atfd,
493 const char *path,
494 unsigned statx_mask,
495 unsigned n_depth_max,
496 RecurseDirFlags flags,
497 recurse_dir_func_t func,
498 void *userdata) {
499
500 _cleanup_close_ int fd = -EBADF;
501
502 assert(atfd >= 0 || atfd == AT_FDCWD);
503 assert(func);
504
505 fd = openat(atfd, path ?: ".", O_DIRECTORY|O_CLOEXEC);
506 if (fd < 0)
507 return -errno;
508
509 return recurse_dir(fd, path, statx_mask, n_depth_max, flags, func, userdata);
510}