@@ -478,24 +478,52 @@ def fwalk(top=".", topdown=True, onerror=None, *, follow_symlinks=False, dir_fd=
478
478
"""
479
479
sys .audit ("os.fwalk" , top , topdown , onerror , follow_symlinks , dir_fd )
480
480
top = fspath (top )
481
- # Note: To guard against symlink races, we use the standard
482
- # lstat()/open()/fstat() trick.
483
- if not follow_symlinks :
484
- orig_st = stat (top , follow_symlinks = False , dir_fd = dir_fd )
485
- topfd = open (top , O_RDONLY | O_NONBLOCK , dir_fd = dir_fd )
486
- try :
487
- if (follow_symlinks or (st .S_ISDIR (orig_st .st_mode ) and
488
- path .samestat (orig_st , stat (topfd )))):
489
- yield from _fwalk (topfd , top , isinstance (top , bytes ),
490
- topdown , onerror , follow_symlinks )
491
- finally :
492
- close (topfd )
493
-
494
- def _fwalk (topfd , toppath , isbytes , topdown , onerror , follow_symlinks ):
481
+ stack = [(_fwalk_walk , (True , dir_fd , top , top , None ))]
482
+ isbytes = isinstance (top , bytes )
483
+ while stack :
484
+ yield from _fwalk (stack , isbytes , topdown , onerror , follow_symlinks )
485
+
486
+ # Each item in the _fwalk() stack is a pair (action, args).
487
+ _fwalk_walk = 0 # args: (isroot, dirfd, toppath, topname, entry)
488
+ _fwalk_yield = 1 # args: (toppath, dirnames, filenames, topfd)
489
+ _fwalk_close = 2 # args: dirfd
490
+
491
+ def _fwalk (stack , isbytes , topdown , onerror , follow_symlinks ):
495
492
# Note: This uses O(depth of the directory tree) file descriptors: if
496
493
# necessary, it can be adapted to only require O(1) FDs, see issue
497
494
# #13734.
498
495
496
+ action , value = stack .pop ()
497
+ if action == _fwalk_close :
498
+ close (value )
499
+ return
500
+ elif action == _fwalk_yield :
501
+ yield value
502
+ return
503
+ assert action == _fwalk_walk
504
+ isroot , dirfd , toppath , topname , entry = value
505
+ try :
506
+ if not follow_symlinks :
507
+ # Note: To guard against symlink races, we use the standard
508
+ # lstat()/open()/fstat() trick.
509
+ if entry is None :
510
+ orig_st = stat (topname , follow_symlinks = False , dir_fd = dirfd )
511
+ else :
512
+ orig_st = entry .stat (follow_symlinks = False )
513
+ topfd = open (topname , O_RDONLY | O_NONBLOCK , dir_fd = dirfd )
514
+ except OSError as err :
515
+ if isroot :
516
+ raise
517
+ if onerror is not None :
518
+ onerror (err )
519
+ return
520
+ stack .append ((_fwalk_close , topfd ))
521
+ if not follow_symlinks :
522
+ if isroot and not st .S_ISDIR (orig_st .st_mode ):
523
+ return
524
+ if not path .samestat (orig_st , stat (topfd )):
525
+ return
526
+
499
527
scandir_it = scandir (topfd )
500
528
dirs = []
501
529
nondirs = []
@@ -521,31 +549,18 @@ def _fwalk(topfd, toppath, isbytes, topdown, onerror, follow_symlinks):
521
549
522
550
if topdown :
523
551
yield toppath , dirs , nondirs , topfd
552
+ else :
553
+ stack .append ((_fwalk_yield , (toppath , dirs , nondirs , topfd )))
524
554
525
- for name in dirs if entries is None else zip (dirs , entries ):
526
- try :
527
- if not follow_symlinks :
528
- if topdown :
529
- orig_st = stat (name , dir_fd = topfd , follow_symlinks = False )
530
- else :
531
- assert entries is not None
532
- name , entry = name
533
- orig_st = entry .stat (follow_symlinks = False )
534
- dirfd = open (name , O_RDONLY | O_NONBLOCK , dir_fd = topfd )
535
- except OSError as err :
536
- if onerror is not None :
537
- onerror (err )
538
- continue
539
- try :
540
- if follow_symlinks or path .samestat (orig_st , stat (dirfd )):
541
- dirpath = path .join (toppath , name )
542
- yield from _fwalk (dirfd , dirpath , isbytes ,
543
- topdown , onerror , follow_symlinks )
544
- finally :
545
- close (dirfd )
546
-
547
- if not topdown :
548
- yield toppath , dirs , nondirs , topfd
555
+ toppath = path .join (toppath , toppath [:0 ]) # Add trailing slash.
556
+ if entries is None :
557
+ stack .extend (
558
+ (_fwalk_walk , (False , topfd , toppath + name , name , None ))
559
+ for name in dirs [::- 1 ])
560
+ else :
561
+ stack .extend (
562
+ (_fwalk_walk , (False , topfd , toppath + name , name , entry ))
563
+ for name , entry in zip (dirs [::- 1 ], entries [::- 1 ]))
549
564
550
565
__all__ .append ("fwalk" )
551
566
0 commit comments