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