diff options
Diffstat (limited to 'Lib/os.py')
-rw-r--r-- | Lib/os.py | 126 |
1 files changed, 108 insertions, 18 deletions
@@ -61,6 +61,10 @@ if 'posix' in _names: except ImportError: pass + import posix + __all__.extend(_get_exports_list(posix)) + del posix + elif 'nt' in _names: name = 'nt' linesep = '\r\n' @@ -321,7 +325,7 @@ def walk(top, topdown=True, onerror=None, followlinks=False): the value of topdown, the list of subdirectories is retrieved before the tuples for the directory and its subdirectories are generated. - By default errors from the os.listdir() call are ignored. If + By default errors from the os.scandir() call are ignored. If optional arg 'onerror' is specified, it should be a function; it will be called with one argument, an OSError instance. It can report the error to continue with the walk, or raise the exception @@ -350,7 +354,8 @@ def walk(top, topdown=True, onerror=None, followlinks=False): """ - islink, join, isdir = path.islink, path.join, path.isdir + dirs = [] + nondirs = [] # We may not have read permission for top, in which case we can't # get a list of the files the directory contains. os.walk @@ -358,30 +363,115 @@ def walk(top, topdown=True, onerror=None, followlinks=False): # minor reason when (say) a thousand readable directories are still # left to visit. That logic is copied here. try: - # Note that listdir is global in this module due - # to earlier import-*. - names = listdir(top) - except OSError as err: + if name == 'nt' and isinstance(top, bytes): + scandir_it = _dummy_scandir(top) + else: + # Note that scandir is global in this module due + # to earlier import-*. + scandir_it = scandir(top) + entries = list(scandir_it) + except OSError as error: if onerror is not None: - onerror(err) + onerror(error) return - dirs, nondirs = [], [] - for name in names: - if isdir(join(top, name)): - dirs.append(name) + for entry in entries: + try: + is_dir = entry.is_dir() + except OSError: + # If is_dir() raises an OSError, consider that the entry is not + # a directory, same behaviour than os.path.isdir(). + is_dir = False + + if is_dir: + dirs.append(entry.name) else: - nondirs.append(name) + nondirs.append(entry.name) + if not topdown and is_dir: + # Bottom-up: recurse into sub-directory, but exclude symlinks to + # directories if followlinks is False + if followlinks: + walk_into = True + else: + try: + is_symlink = entry.is_symlink() + except OSError: + # If is_symlink() raises an OSError, consider that the + # entry is not a symbolic link, same behaviour than + # os.path.islink(). + is_symlink = False + walk_into = not is_symlink + + if walk_into: + yield from walk(entry.path, topdown, onerror, followlinks) + + # Yield before recursion if going top down if topdown: yield top, dirs, nondirs - for name in dirs: - new_path = join(top, name) - if followlinks or not islink(new_path): - yield from walk(new_path, topdown, onerror, followlinks) - if not topdown: + + # Recurse into sub-directories + islink, join = path.islink, path.join + for dirname in dirs: + new_path = join(top, dirname) + # Issue #23605: os.path.islink() is used instead of caching + # entry.is_symlink() result during the loop on os.scandir() because + # the caller can replace the directory entry during the "yield" + # above. + if followlinks or not islink(new_path): + yield from walk(new_path, topdown, onerror, followlinks) + else: + # Yield after recursion if going bottom up yield top, dirs, nondirs +class _DummyDirEntry: + """Dummy implementation of DirEntry + + Only used internally by os.walk(bytes). Since os.walk() doesn't need the + follow_symlinks parameter: don't implement it, always follow symbolic + links. + """ + + def __init__(self, dir, name): + self.name = name + self.path = path.join(dir, name) + # Mimick FindFirstFile/FindNextFile: we should get file attributes + # while iterating on a directory + self._stat = None + self._lstat = None + try: + self.stat(follow_symlinks=False) + except OSError: + pass + + def stat(self, *, follow_symlinks=True): + if follow_symlinks: + if self._stat is None: + self._stat = stat(self.path) + return self._stat + else: + if self._lstat is None: + self._lstat = stat(self.path, follow_symlinks=False) + return self._lstat + + def is_dir(self): + if self._lstat is not None and not self.is_symlink(): + # use the cache lstat + stat = self.stat(follow_symlinks=False) + return st.S_ISDIR(stat.st_mode) + + stat = self.stat() + return st.S_ISDIR(stat.st_mode) + + def is_symlink(self): + stat = self.stat(follow_symlinks=False) + return st.S_ISLNK(stat.st_mode) + +def _dummy_scandir(dir): + # listdir-based implementation for bytes patches on Windows + for name in listdir(dir): + yield _DummyDirEntry(dir, name) + __all__.append("walk") if {open, stat} <= supports_dir_fd and {listdir, stat} <= supports_fd: @@ -466,7 +556,7 @@ if {open, stat} <= supports_dir_fd and {listdir, stat} <= supports_fd: except OSError as err: if onerror is not None: onerror(err) - return + continue try: if follow_symlinks or path.samestat(orig_st, stat(dirfd)): dirpath = path.join(toppath, name) |