@@ -478,24 +478,52 @@ def fwalk(top=".", topdown=True, onerror=None, *, follow_symlinks=False, dir_fd=
478478 """
479479sys .audit ("os.fwalk" ,top ,topdown ,onerror ,follow_symlinks ,dir_fd )
480480top = 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 ):
495492# Note: This uses O(depth of the directory tree) file descriptors: if
496493# necessary, it can be adapted to only require O(1) FDs, see issue
497494# #13734.
498495
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+
499527scandir_it = scandir (topfd )
500528dirs = []
501529nondirs = []
@@ -521,31 +549,18 @@ def _fwalk(topfd, toppath, isbytes, topdown, onerror, follow_symlinks):
521549
522550if topdown :
523551yield toppath ,dirs ,nondirs ,topfd
552+ else :
553+ stack .append ((_fwalk_yield , (toppath ,dirs ,nondirs ,topfd )))
524554
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 ]))
549564
550565__all__ .append ("fwalk" )
551566