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