@@ -478,24 +478,52 @@ def fwalk(top=".", topdown=True, onerror=None, *, follow_symlinks=False, dir_fd=
478478 """
479479 sys .audit ("os.fwalk" , top , topdown , onerror , follow_symlinks , dir_fd )
480480 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 ):
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+
499527 scandir_it = scandir (topfd )
500528 dirs = []
501529 nondirs = []
@@ -521,31 +549,18 @@ def _fwalk(topfd, toppath, isbytes, topdown, onerror, follow_symlinks):
521549
522550 if topdown :
523551 yield 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
0 commit comments