summaryrefslogtreecommitdiff
path: root/Lib/os.py
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/os.py')
-rw-r--r--Lib/os.py126
1 files changed, 108 insertions, 18 deletions
diff --git a/Lib/os.py b/Lib/os.py
index 27b241ae97..b4c651d24d 100644
--- a/Lib/os.py
+++ b/Lib/os.py
@@ -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)