]>
| Commit | Line | Data |
|---|---|---|
| 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 | ||
| 17 | static int sort_func(struct dirent * const *a, struct dirent * const *b) { | |
| 18 | return strcmp((*a)->d_name, (*b)->d_name); | |
| 19 | } | |
| 20 | ||
| 21 | static 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 | ||
| 31 | int 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 | ||
| 134 | int 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 | ||
| 146 | int 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 | -EBADF, /* 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 | FOREACH_ARRAY(entry, de->entries, de->n_entries) { | |
| 195 | struct dirent *i = *entry; | |
| 196 | _cleanup_close_ int inode_fd = -EBADF, subdir_fd = -EBADF; | |
| 197 | _cleanup_free_ char *joined = NULL; | |
| 198 | struct statx sx; | |
| 199 | bool sx_valid = false; | |
| 200 | const char *p; | |
| 201 | ||
| 202 | /* For each directory entry we'll do one of the following: | |
| 203 | * | |
| 204 | * 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) | |
| 205 | * 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) | |
| 206 | * 3) Otherwise, we'll statx() the directory entry via the directory fd we are currently looking at (if requested) | |
| 207 | */ | |
| 208 | ||
| 209 | if (path) { | |
| 210 | joined = path_join(path, i->d_name); | |
| 211 | if (!joined) | |
| 212 | return -ENOMEM; | |
| 213 | ||
| 214 | p = joined; | |
| 215 | } else | |
| 216 | p = i->d_name; | |
| 217 | ||
| 218 | if (IN_SET(i->d_type, DT_UNKNOWN, DT_DIR)) { | |
| 219 | subdir_fd = openat(dir_fd, i->d_name, O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); | |
| 220 | if (subdir_fd < 0) { | |
| 221 | if (errno == ENOENT) /* Vanished by now, go for next file immediately */ | |
| 222 | continue; | |
| 223 | ||
| 224 | /* If it is a subdir but we failed to open it, then fail */ | |
| 225 | if (!IN_SET(errno, ENOTDIR, ELOOP)) { | |
| 226 | log_debug_errno(errno, "Failed to open directory '%s': %m", p); | |
| 227 | ||
| 228 | assert(errno <= RECURSE_DIR_SKIP_OPEN_DIR_ERROR_MAX - RECURSE_DIR_SKIP_OPEN_DIR_ERROR_BASE); | |
| 229 | ||
| 230 | r = func(RECURSE_DIR_SKIP_OPEN_DIR_ERROR_BASE + errno, | |
| 231 | p, | |
| 232 | dir_fd, | |
| 233 | /* inode_fd = */ -EBADF, | |
| 234 | i, | |
| 235 | /* sx = */ NULL, | |
| 236 | userdata); | |
| 237 | if (r == RECURSE_DIR_LEAVE_DIRECTORY) | |
| 238 | break; | |
| 239 | if (!IN_SET(r, RECURSE_DIR_CONTINUE, RECURSE_DIR_SKIP_ENTRY)) | |
| 240 | return r; | |
| 241 | ||
| 242 | continue; | |
| 243 | } | |
| 244 | ||
| 245 | /* If it's not a subdir, then let's handle it like a regular inode below */ | |
| 246 | ||
| 247 | } else { | |
| 248 | /* If we managed to get a DIR* off the inode, it's definitely a directory. */ | |
| 249 | i->d_type = DT_DIR; | |
| 250 | ||
| 251 | if (statx_mask != 0 || (flags & RECURSE_DIR_SAME_MOUNT)) { | |
| 252 | if (statx(subdir_fd, "", AT_EMPTY_PATH, statx_mask, &sx) < 0) | |
| 253 | return -errno; | |
| 254 | ||
| 255 | sx_valid = true; | |
| 256 | } | |
| 257 | } | |
| 258 | } | |
| 259 | ||
| 260 | if (subdir_fd < 0) { | |
| 261 | /* It's not a subdirectory. */ | |
| 262 | ||
| 263 | if (flags & RECURSE_DIR_INODE_FD) { | |
| 264 | ||
| 265 | inode_fd = openat(dir_fd, i->d_name, O_PATH|O_NOFOLLOW|O_CLOEXEC); | |
| 266 | if (inode_fd < 0) { | |
| 267 | if (errno == ENOENT) /* Vanished by now, go for next file immediately */ | |
| 268 | continue; | |
| 269 | ||
| 270 | log_debug_errno(errno, "Failed to open directory entry '%s': %m", p); | |
| 271 | ||
| 272 | assert(errno <= RECURSE_DIR_SKIP_OPEN_INODE_ERROR_MAX - RECURSE_DIR_SKIP_OPEN_INODE_ERROR_BASE); | |
| 273 | ||
| 274 | r = func(RECURSE_DIR_SKIP_OPEN_INODE_ERROR_BASE + errno, | |
| 275 | p, | |
| 276 | dir_fd, | |
| 277 | /* inode_fd = */ -EBADF, | |
| 278 | i, | |
| 279 | /* sx = */ NULL, | |
| 280 | userdata); | |
| 281 | if (r == RECURSE_DIR_LEAVE_DIRECTORY) | |
| 282 | break; | |
| 283 | if (!IN_SET(r, RECURSE_DIR_CONTINUE, RECURSE_DIR_SKIP_ENTRY)) | |
| 284 | return r; | |
| 285 | ||
| 286 | continue; | |
| 287 | } | |
| 288 | ||
| 289 | /* If we open the inode, then verify it's actually a non-directory, like we | |
| 290 | * assume. Let's guarantee that we never pass statx data of a directory where | |
| 291 | * caller expects a non-directory */ | |
| 292 | ||
| 293 | if (statx(inode_fd, "", AT_EMPTY_PATH, statx_mask | STATX_TYPE, &sx) < 0) | |
| 294 | return -errno; | |
| 295 | ||
| 296 | assert(sx.stx_mask & STATX_TYPE); | |
| 297 | sx_valid = true; | |
| 298 | ||
| 299 | if (S_ISDIR(sx.stx_mode)) { | |
| 300 | /* What? It's a directory now? Then someone must have quickly | |
| 301 | * replaced it. Let's handle that gracefully: convert it to a | |
| 302 | * directory fd — which should be riskless now that we pinned the | |
| 303 | * inode. */ | |
| 304 | ||
| 305 | subdir_fd = fd_reopen(inode_fd, O_DIRECTORY|O_CLOEXEC); | |
| 306 | if (subdir_fd < 0) | |
| 307 | return subdir_fd; | |
| 308 | ||
| 309 | inode_fd = safe_close(inode_fd); | |
| 310 | } | |
| 311 | ||
| 312 | } else if (statx_mask != 0 || (i->d_type == DT_UNKNOWN && (flags & RECURSE_DIR_ENSURE_TYPE))) { | |
| 313 | ||
| 314 | if (statx(dir_fd, i->d_name, AT_SYMLINK_NOFOLLOW, statx_mask | STATX_TYPE, &sx) < 0) { | |
| 315 | if (errno == ENOENT) /* Vanished by now? Go for next file immediately */ | |
| 316 | continue; | |
| 317 | ||
| 318 | log_debug_errno(errno, "Failed to stat directory entry '%s': %m", p); | |
| 319 | ||
| 320 | assert(errno <= RECURSE_DIR_SKIP_STAT_INODE_ERROR_MAX - RECURSE_DIR_SKIP_STAT_INODE_ERROR_BASE); | |
| 321 | ||
| 322 | r = func(RECURSE_DIR_SKIP_STAT_INODE_ERROR_BASE + errno, | |
| 323 | p, | |
| 324 | dir_fd, | |
| 325 | /* inode_fd = */ -EBADF, | |
| 326 | i, | |
| 327 | /* sx = */ NULL, | |
| 328 | userdata); | |
| 329 | if (r == RECURSE_DIR_LEAVE_DIRECTORY) | |
| 330 | break; | |
| 331 | if (!IN_SET(r, RECURSE_DIR_CONTINUE, RECURSE_DIR_SKIP_ENTRY)) | |
| 332 | return r; | |
| 333 | ||
| 334 | continue; | |
| 335 | } | |
| 336 | ||
| 337 | assert(sx.stx_mask & STATX_TYPE); | |
| 338 | sx_valid = true; | |
| 339 | ||
| 340 | if (S_ISDIR(sx.stx_mode)) { | |
| 341 | /* So it suddenly is a directory, but we couldn't open it as such | |
| 342 | * earlier? That is weird, and probably means somebody is racing | |
| 343 | * against us. We could of course retry and open it as a directory | |
| 344 | * again, but the chance to win here is limited. Hence, let's | |
| 345 | * propagate this as EISDIR error instead. That way we make this | |
| 346 | * something that can be reasonably handled, even though we give the | |
| 347 | * guarantee that RECURSE_DIR_ENTRY is strictly issued for | |
| 348 | * non-directory dirents. */ | |
| 349 | ||
| 350 | log_debug("Non-directory entry '%s' suddenly became a directory.", p); | |
| 351 | ||
| 352 | r = func(RECURSE_DIR_SKIP_STAT_INODE_ERROR_BASE + EISDIR, | |
| 353 | p, | |
| 354 | dir_fd, | |
| 355 | /* inode_fd = */ -EBADF, | |
| 356 | i, | |
| 357 | /* sx = */ NULL, | |
| 358 | userdata); | |
| 359 | if (r == RECURSE_DIR_LEAVE_DIRECTORY) | |
| 360 | break; | |
| 361 | if (!IN_SET(r, RECURSE_DIR_CONTINUE, RECURSE_DIR_SKIP_ENTRY)) | |
| 362 | return r; | |
| 363 | ||
| 364 | continue; | |
| 365 | } | |
| 366 | } | |
| 367 | } | |
| 368 | ||
| 369 | if (sx_valid) { | |
| 370 | /* Copy over the data we acquired through statx() if we acquired any */ | |
| 371 | if (sx.stx_mask & STATX_TYPE) { | |
| 372 | assert((subdir_fd < 0) == !S_ISDIR(sx.stx_mode)); | |
| 373 | i->d_type = IFTODT(sx.stx_mode); | |
| 374 | } | |
| 375 | ||
| 376 | if (sx.stx_mask & STATX_INO) | |
| 377 | i->d_ino = sx.stx_ino; | |
| 378 | } | |
| 379 | ||
| 380 | if (subdir_fd >= 0) { | |
| 381 | if (FLAGS_SET(flags, RECURSE_DIR_SAME_MOUNT)) { | |
| 382 | bool is_mount; | |
| 383 | ||
| 384 | if (sx_valid && FLAGS_SET(sx.stx_attributes_mask, STATX_ATTR_MOUNT_ROOT)) | |
| 385 | is_mount = FLAGS_SET(sx.stx_attributes, STATX_ATTR_MOUNT_ROOT); | |
| 386 | else { | |
| 387 | r = is_mount_point_at(dir_fd, i->d_name, /* flags = */ 0); | |
| 388 | if (r < 0) | |
| 389 | log_debug_errno(r, "Failed to determine whether %s is a submount, assuming not: %m", p); | |
| 390 | ||
| 391 | is_mount = r > 0; | |
| 392 | } | |
| 393 | ||
| 394 | if (is_mount) { | |
| 395 | r = func(RECURSE_DIR_SKIP_MOUNT, | |
| 396 | p, | |
| 397 | dir_fd, | |
| 398 | subdir_fd, | |
| 399 | i, | |
| 400 | statx_mask != 0 ? &sx : NULL, /* only pass sx if user asked for it */ | |
| 401 | userdata); | |
| 402 | if (r == RECURSE_DIR_LEAVE_DIRECTORY) | |
| 403 | break; | |
| 404 | if (!IN_SET(r, RECURSE_DIR_CONTINUE, RECURSE_DIR_SKIP_ENTRY)) | |
| 405 | return r; | |
| 406 | ||
| 407 | continue; | |
| 408 | } | |
| 409 | } | |
| 410 | ||
| 411 | if (n_depth_max <= 1) { | |
| 412 | /* When we reached max depth, generate a special event */ | |
| 413 | ||
| 414 | r = func(RECURSE_DIR_SKIP_DEPTH, | |
| 415 | p, | |
| 416 | dir_fd, | |
| 417 | subdir_fd, | |
| 418 | i, | |
| 419 | statx_mask != 0 ? &sx : NULL, /* only pass sx if user asked for it */ | |
| 420 | userdata); | |
| 421 | if (r == RECURSE_DIR_LEAVE_DIRECTORY) | |
| 422 | break; | |
| 423 | if (!IN_SET(r, RECURSE_DIR_CONTINUE, RECURSE_DIR_SKIP_ENTRY)) | |
| 424 | return r; | |
| 425 | ||
| 426 | continue; | |
| 427 | } | |
| 428 | ||
| 429 | r = func(RECURSE_DIR_ENTER, | |
| 430 | p, | |
| 431 | dir_fd, | |
| 432 | subdir_fd, | |
| 433 | i, | |
| 434 | statx_mask != 0 ? &sx : NULL, /* only pass sx if user asked for it */ | |
| 435 | userdata); | |
| 436 | if (r == RECURSE_DIR_LEAVE_DIRECTORY) | |
| 437 | break; | |
| 438 | if (r == RECURSE_DIR_SKIP_ENTRY) | |
| 439 | continue; | |
| 440 | if (r != RECURSE_DIR_CONTINUE) | |
| 441 | return r; | |
| 442 | ||
| 443 | r = recurse_dir(subdir_fd, | |
| 444 | p, | |
| 445 | statx_mask, | |
| 446 | n_depth_max - 1, | |
| 447 | flags & ~RECURSE_DIR_TOPLEVEL, /* we already called the callback for this entry */ | |
| 448 | func, | |
| 449 | userdata); | |
| 450 | if (r != 0) | |
| 451 | return r; | |
| 452 | ||
| 453 | r = func(RECURSE_DIR_LEAVE, | |
| 454 | p, | |
| 455 | dir_fd, | |
| 456 | subdir_fd, | |
| 457 | i, | |
| 458 | statx_mask != 0 ? &sx : NULL, /* only pass sx if user asked for it */ | |
| 459 | userdata); | |
| 460 | } else | |
| 461 | /* Non-directory inode */ | |
| 462 | r = func(RECURSE_DIR_ENTRY, | |
| 463 | p, | |
| 464 | dir_fd, | |
| 465 | inode_fd, | |
| 466 | i, | |
| 467 | statx_mask != 0 ? &sx : NULL, /* only pass sx if user asked for it */ | |
| 468 | userdata); | |
| 469 | ||
| 470 | if (r == RECURSE_DIR_LEAVE_DIRECTORY) | |
| 471 | break; | |
| 472 | if (!IN_SET(r, RECURSE_DIR_SKIP_ENTRY, RECURSE_DIR_CONTINUE)) | |
| 473 | return r; | |
| 474 | } | |
| 475 | ||
| 476 | if (FLAGS_SET(flags, RECURSE_DIR_TOPLEVEL)) { | |
| 477 | ||
| 478 | r = func(RECURSE_DIR_LEAVE, | |
| 479 | path, | |
| 480 | -EBADF, /* we have no parent fd */ | |
| 481 | dir_fd, | |
| 482 | NULL, /* we have no dirent */ | |
| 483 | statx_mask != 0 ? &root_sx : NULL, | |
| 484 | userdata); | |
| 485 | if (!IN_SET(r, RECURSE_DIR_LEAVE_DIRECTORY, RECURSE_DIR_SKIP_ENTRY, RECURSE_DIR_CONTINUE)) | |
| 486 | return r; | |
| 487 | } | |
| 488 | ||
| 489 | return 0; | |
| 490 | } | |
| 491 | ||
| 492 | int recurse_dir_at( | |
| 493 | int atfd, | |
| 494 | const char *path, | |
| 495 | unsigned statx_mask, | |
| 496 | unsigned n_depth_max, | |
| 497 | RecurseDirFlags flags, | |
| 498 | recurse_dir_func_t func, | |
| 499 | void *userdata) { | |
| 500 | ||
| 501 | _cleanup_close_ int fd = -EBADF; | |
| 502 | ||
| 503 | assert(atfd >= 0 || atfd == AT_FDCWD); | |
| 504 | assert(func); | |
| 505 | ||
| 506 | fd = openat(atfd, path ?: ".", O_DIRECTORY|O_CLOEXEC); | |
| 507 | if (fd < 0) | |
| 508 | return -errno; | |
| 509 | ||
| 510 | return recurse_dir(fd, path, statx_mask, n_depth_max, flags, func, userdata); | |
| 511 | } |