| 477 | close(topfd) |
| 478 | |
| 479 | def _fwalk(topfd, toppath, isbytes, topdown, onerror, follow_symlinks): |
| 480 | # Note: This uses O(depth of the directory tree) file descriptors: if |
| 481 | # necessary, it can be adapted to only require O(1) FDs, see issue |
| 482 | # #13734. |
| 483 | |
| 484 | scandir_it = scandir(topfd) |
| 485 | dirs = [] |
| 486 | nondirs = [] |
| 487 | entries = None if topdown or follow_symlinks else [] |
| 488 | for entry in scandir_it: |
| 489 | name = entry.name |
| 490 | if isbytes: |
| 491 | name = fsencode(name) |
| 492 | try: |
| 493 | if entry.is_dir(): |
| 494 | dirs.append(name) |
| 495 | if entries is not None: |
| 496 | entries.append(entry) |
| 497 | else: |
| 498 | nondirs.append(name) |
| 499 | except OSError: |
| 500 | try: |
| 501 | # Add dangling symlinks, ignore disappeared files |
| 502 | if entry.is_symlink(): |
| 503 | nondirs.append(name) |
| 504 | except OSError: |
| 505 | pass |
| 506 | |
| 507 | if topdown: |
| 508 | yield toppath, dirs, nondirs, topfd |
| 509 | |
| 510 | for name in dirs if entries is None else zip(dirs, entries): |
| 511 | try: |
| 512 | if not follow_symlinks: |
| 513 | if topdown: |
| 514 | orig_st = stat(name, dir_fd=topfd, follow_symlinks=False) |
| 515 | else: |
| 516 | assert entries is not None |
| 517 | name, entry = name |
| 518 | orig_st = entry.stat(follow_symlinks=False) |
| 519 | dirfd = open(name, O_RDONLY | O_NONBLOCK, dir_fd=topfd) |
| 520 | except OSError as err: |
| 521 | if onerror is not None: |
| 522 | onerror(err) |
| 523 | continue |
| 524 | try: |
| 525 | if follow_symlinks or path.samestat(orig_st, stat(dirfd)): |
| 526 | dirpath = path.join(toppath, name) |
| 527 | yield from _fwalk(dirfd, dirpath, isbytes, |
| 528 | topdown, onerror, follow_symlinks) |
| 529 | finally: |
| 530 | close(dirfd) |
| 531 | |
| 532 | if not topdown: |
| 533 | yield toppath, dirs, nondirs, topfd |
| 534 | |
| 535 | __all__.append("fwalk") |
| 536 | |