1"""scandir, a better directory iterator and faster os.walk(), now in the Python 3.5 stdlib
2
3scandir() is a generator version of os.listdir() that returns an
4iterator over files in a directory, and also exposes the extra
5information most OSes provide while iterating files in a directory
6(such as type and stat information).
7
8This module also includes a version of os.walk() that uses scandir()
9to speed it up significantly.
10
11See README.md or https://github.com/benhoyt/scandir for rationale and
12docs, or read PEP 471 (https://www.python.org/dev/peps/pep-0471/) for
13more details on its inclusion into Python 3.5
14
15scandir is released under the new BSD 3-clause license.
16
17Copyright (c) 2012, Ben Hoyt
18All rights reserved.
19
20Redistribution and use in source and binary forms, with or without
21modification, are permitted provided that the following conditions are met:
22
23* Redistributions of source code must retain the above copyright notice, this
24list of conditions and the following disclaimer.
25
26* Redistributions in binary form must reproduce the above copyright notice,
27this list of conditions and the following disclaimer in the documentation
28and/or other materials provided with the distribution.
29
30* Neither the name of Ben Hoyt nor the names of its contributors may be used
31to endorse or promote products derived from this software without specific
32prior written permission.
33
34THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
35AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
36IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
37DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
38FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
39DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
40SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
41CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
42OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
43OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
44"""
45
46from __future__ import division
47
48from errno import ENOENT
49from os import listdir, lstat, stat, strerror
50from os.path import join, islink
51from stat import S_IFDIR, S_IFLNK, S_IFREG
52import collections
53import sys
54
55try:
56    import _scandir
57except ImportError:
58    _scandir = None
59
60try:
61    import ctypes
62except ImportError:
63    ctypes = None
64
65if _scandir is None and ctypes is None:
66    import warnings
67    warnings.warn("scandir can't find the compiled _scandir C module "
68                  "or ctypes, using slow generic fallback")
69
70__version__ = '1.10.0'
71__all__ = ['scandir', 'walk']
72
73# Windows FILE_ATTRIBUTE constants for interpreting the
74# FIND_DATA.dwFileAttributes member
75FILE_ATTRIBUTE_ARCHIVE = 32
76FILE_ATTRIBUTE_COMPRESSED = 2048
77FILE_ATTRIBUTE_DEVICE = 64
78FILE_ATTRIBUTE_DIRECTORY = 16
79FILE_ATTRIBUTE_ENCRYPTED = 16384
80FILE_ATTRIBUTE_HIDDEN = 2
81FILE_ATTRIBUTE_INTEGRITY_STREAM = 32768
82FILE_ATTRIBUTE_NORMAL = 128
83FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 8192
84FILE_ATTRIBUTE_NO_SCRUB_DATA = 131072
85FILE_ATTRIBUTE_OFFLINE = 4096
86FILE_ATTRIBUTE_READONLY = 1
87FILE_ATTRIBUTE_REPARSE_POINT = 1024
88FILE_ATTRIBUTE_SPARSE_FILE = 512
89FILE_ATTRIBUTE_SYSTEM = 4
90FILE_ATTRIBUTE_TEMPORARY = 256
91FILE_ATTRIBUTE_VIRTUAL = 65536
92
93IS_PY3 = sys.version_info >= (3, 0)
94
95if IS_PY3:
96    unicode = str  # Because Python <= 3.2 doesn't have u'unicode' syntax
97
98
99class GenericDirEntry(object):
100    __slots__ = ('name', '_stat', '_lstat', '_scandir_path', '_path')
101
102    def __init__(self, scandir_path, name):
103        self._scandir_path = scandir_path
104        self.name = name
105        self._stat = None
106        self._lstat = None
107        self._path = None
108
109    @property
110    def path(self):
111        if self._path is None:
112            self._path = join(self._scandir_path, self.name)
113        return self._path
114
115    def stat(self, follow_symlinks=True):
116        if follow_symlinks:
117            if self._stat is None:
118                self._stat = stat(self.path)
119            return self._stat
120        else:
121            if self._lstat is None:
122                self._lstat = lstat(self.path)
123            return self._lstat
124
125    # The code duplication below is intentional: this is for slightly
126    # better performance on systems that fall back to GenericDirEntry.
127    # It avoids an additional attribute lookup and method call, which
128    # are relatively slow on CPython.
129    def is_dir(self, follow_symlinks=True):
130        try:
131            st = self.stat(follow_symlinks=follow_symlinks)
132        except OSError as e:
133            if e.errno != ENOENT:
134                raise
135            return False  # Path doesn't exist or is a broken symlink
136        return st.st_mode & 0o170000 == S_IFDIR
137
138    def is_file(self, follow_symlinks=True):
139        try:
140            st = self.stat(follow_symlinks=follow_symlinks)
141        except OSError as e:
142            if e.errno != ENOENT:
143                raise
144            return False  # Path doesn't exist or is a broken symlink
145        return st.st_mode & 0o170000 == S_IFREG
146
147    def is_symlink(self):
148        try:
149            st = self.stat(follow_symlinks=False)
150        except OSError as e:
151            if e.errno != ENOENT:
152                raise
153            return False  # Path doesn't exist or is a broken symlink
154        return st.st_mode & 0o170000 == S_IFLNK
155
156    def inode(self):
157        st = self.stat(follow_symlinks=False)
158        return st.st_ino
159
160    def __str__(self):
161        return '<{0}: {1!r}>'.format(self.__class__.__name__, self.name)
162
163    __repr__ = __str__
164
165
166def _scandir_generic(path=unicode('.')):
167    """Like os.listdir(), but yield DirEntry objects instead of returning
168    a list of names.
169    """
170    for name in listdir(path):
171        yield GenericDirEntry(path, name)
172
173
174if IS_PY3 and sys.platform == 'win32':
175    def scandir_generic(path=unicode('.')):
176        if isinstance(path, bytes):
177            raise TypeError("os.scandir() doesn't support bytes path on Windows, use Unicode instead")
178        return _scandir_generic(path)
179    scandir_generic.__doc__ = _scandir_generic.__doc__
180else:
181    scandir_generic = _scandir_generic
182
183
184scandir_c = None
185scandir_python = None
186
187
188if sys.platform == 'win32':
189    if ctypes is not None:
190        from ctypes import wintypes
191
192        # Various constants from windows.h
193        INVALID_HANDLE_VALUE = ctypes.c_void_p(-1).value
194        ERROR_FILE_NOT_FOUND = 2
195        ERROR_NO_MORE_FILES = 18
196        IO_REPARSE_TAG_SYMLINK = 0xA000000C
197
198        # Numer of seconds between 1601-01-01 and 1970-01-01
199        SECONDS_BETWEEN_EPOCHS = 11644473600
200
201        kernel32 = ctypes.windll.kernel32
202
203        # ctypes wrappers for (wide string versions of) FindFirstFile,
204        # FindNextFile, and FindClose
205        FindFirstFile = kernel32.FindFirstFileW
206        FindFirstFile.argtypes = [
207            wintypes.LPCWSTR,
208            ctypes.POINTER(wintypes.WIN32_FIND_DATAW),
209        ]
210        FindFirstFile.restype = wintypes.HANDLE
211
212        FindNextFile = kernel32.FindNextFileW
213        FindNextFile.argtypes = [
214            wintypes.HANDLE,
215            ctypes.POINTER(wintypes.WIN32_FIND_DATAW),
216        ]
217        FindNextFile.restype = wintypes.BOOL
218
219        FindClose = kernel32.FindClose
220        FindClose.argtypes = [wintypes.HANDLE]
221        FindClose.restype = wintypes.BOOL
222
223        Win32StatResult = collections.namedtuple('Win32StatResult', [
224            'st_mode',
225            'st_ino',
226            'st_dev',
227            'st_nlink',
228            'st_uid',
229            'st_gid',
230            'st_size',
231            'st_atime',
232            'st_mtime',
233            'st_ctime',
234            'st_atime_ns',
235            'st_mtime_ns',
236            'st_ctime_ns',
237            'st_file_attributes',
238        ])
239
240        def filetime_to_time(filetime):
241            """Convert Win32 FILETIME to time since Unix epoch in seconds."""
242            total = filetime.dwHighDateTime << 32 | filetime.dwLowDateTime
243            return total / 10000000 - SECONDS_BETWEEN_EPOCHS
244
245        def find_data_to_stat(data):
246            """Convert Win32 FIND_DATA struct to stat_result."""
247            # First convert Win32 dwFileAttributes to st_mode
248            attributes = data.dwFileAttributes
249            st_mode = 0
250            if attributes & FILE_ATTRIBUTE_DIRECTORY:
251                st_mode |= S_IFDIR | 0o111
252            else:
253                st_mode |= S_IFREG
254            if attributes & FILE_ATTRIBUTE_READONLY:
255                st_mode |= 0o444
256            else:
257                st_mode |= 0o666
258            if (attributes & FILE_ATTRIBUTE_REPARSE_POINT and
259                    data.dwReserved0 == IO_REPARSE_TAG_SYMLINK):
260                st_mode ^= st_mode & 0o170000
261                st_mode |= S_IFLNK
262
263            st_size = data.nFileSizeHigh << 32 | data.nFileSizeLow
264            st_atime = filetime_to_time(data.ftLastAccessTime)
265            st_mtime = filetime_to_time(data.ftLastWriteTime)
266            st_ctime = filetime_to_time(data.ftCreationTime)
267
268            # Some fields set to zero per CPython's posixmodule.c: st_ino, st_dev,
269            # st_nlink, st_uid, st_gid
270            return Win32StatResult(st_mode, 0, 0, 0, 0, 0, st_size,
271                                   st_atime, st_mtime, st_ctime,
272                                   int(st_atime * 1000000000),
273                                   int(st_mtime * 1000000000),
274                                   int(st_ctime * 1000000000),
275                                   attributes)
276
277        class Win32DirEntryPython(object):
278            __slots__ = ('name', '_stat', '_lstat', '_find_data', '_scandir_path', '_path', '_inode')
279
280            def __init__(self, scandir_path, name, find_data):
281                self._scandir_path = scandir_path
282                self.name = name
283                self._stat = None
284                self._lstat = None
285                self._find_data = find_data
286                self._path = None
287                self._inode = None
288
289            @property
290            def path(self):
291                if self._path is None:
292                    self._path = join(self._scandir_path, self.name)
293                return self._path
294
295            def stat(self, follow_symlinks=True):
296                if follow_symlinks:
297                    if self._stat is None:
298                        if self.is_symlink():
299                            # It's a symlink, call link-following stat()
300                            self._stat = stat(self.path)
301                        else:
302                            # Not a symlink, stat is same as lstat value
303                            if self._lstat is None:
304                                self._lstat = find_data_to_stat(self._find_data)
305                            self._stat = self._lstat
306                    return self._stat
307                else:
308                    if self._lstat is None:
309                        # Lazily convert to stat object, because it's slow
310                        # in Python, and often we only need is_dir() etc
311                        self._lstat = find_data_to_stat(self._find_data)
312                    return self._lstat
313
314            def is_dir(self, follow_symlinks=True):
315                is_symlink = self.is_symlink()
316                if follow_symlinks and is_symlink:
317                    try:
318                        return self.stat().st_mode & 0o170000 == S_IFDIR
319                    except OSError as e:
320                        if e.errno != ENOENT:
321                            raise
322                        return False
323                elif is_symlink:
324                    return False
325                else:
326                    return (self._find_data.dwFileAttributes &
327                            FILE_ATTRIBUTE_DIRECTORY != 0)
328
329            def is_file(self, follow_symlinks=True):
330                is_symlink = self.is_symlink()
331                if follow_symlinks and is_symlink:
332                    try:
333                        return self.stat().st_mode & 0o170000 == S_IFREG
334                    except OSError as e:
335                        if e.errno != ENOENT:
336                            raise
337                        return False
338                elif is_symlink:
339                    return False
340                else:
341                    return (self._find_data.dwFileAttributes &
342                            FILE_ATTRIBUTE_DIRECTORY == 0)
343
344            def is_symlink(self):
345                return (self._find_data.dwFileAttributes &
346                            FILE_ATTRIBUTE_REPARSE_POINT != 0 and
347                        self._find_data.dwReserved0 == IO_REPARSE_TAG_SYMLINK)
348
349            def inode(self):
350                if self._inode is None:
351                    self._inode = lstat(self.path).st_ino
352                return self._inode
353
354            def __str__(self):
355                return '<{0}: {1!r}>'.format(self.__class__.__name__, self.name)
356
357            __repr__ = __str__
358
359        def win_error(error, filename):
360            exc = WindowsError(error, ctypes.FormatError(error))
361            exc.filename = filename
362            return exc
363
364        def _scandir_python(path=unicode('.')):
365            """Like os.listdir(), but yield DirEntry objects instead of returning
366            a list of names.
367            """
368            # Call FindFirstFile and handle errors
369            if isinstance(path, bytes):
370                is_bytes = True
371                filename = join(path.decode('mbcs', 'strict'), '*.*')
372            else:
373                is_bytes = False
374                filename = join(path, '*.*')
375            data = wintypes.WIN32_FIND_DATAW()
376            data_p = ctypes.byref(data)
377            handle = FindFirstFile(filename, data_p)
378            if handle == INVALID_HANDLE_VALUE:
379                error = ctypes.GetLastError()
380                if error == ERROR_FILE_NOT_FOUND:
381                    # No files, don't yield anything
382                    return
383                raise win_error(error, path)
384
385            # Call FindNextFile in a loop, stopping when no more files
386            try:
387                while True:
388                    # Skip '.' and '..' (current and parent directory), but
389                    # otherwise yield (filename, stat_result) tuple
390                    name = data.cFileName
391                    if name not in ('.', '..'):
392                        if is_bytes:
393                            name = name.encode('mbcs', 'replace')
394                        yield Win32DirEntryPython(path, name, data)
395
396                    data = wintypes.WIN32_FIND_DATAW()
397                    data_p = ctypes.byref(data)
398                    success = FindNextFile(handle, data_p)
399                    if not success:
400                        error = ctypes.GetLastError()
401                        if error == ERROR_NO_MORE_FILES:
402                            break
403                        raise win_error(error, path)
404            finally:
405                if not FindClose(handle):
406                    raise win_error(ctypes.GetLastError(), path)
407
408        if IS_PY3:
409            def scandir_python(path=unicode('.')):
410                if isinstance(path, bytes):
411                    raise TypeError("os.scandir() doesn't support bytes path on Windows, use Unicode instead")
412                return _scandir_python(path)
413            scandir_python.__doc__ = _scandir_python.__doc__
414        else:
415            scandir_python = _scandir_python
416
417    if _scandir is not None:
418        scandir_c = _scandir.scandir
419        DirEntry_c = _scandir.DirEntry
420
421    if _scandir is not None:
422        scandir = scandir_c
423        DirEntry = DirEntry_c
424    elif ctypes is not None:
425        scandir = scandir_python
426        DirEntry = Win32DirEntryPython
427    else:
428        scandir = scandir_generic
429        DirEntry = GenericDirEntry
430
431
432# Linux, OS X, and BSD implementation
433elif sys.platform.startswith(('linux', 'darwin', 'sunos5')) or 'bsd' in sys.platform:
434    have_dirent_d_type = (sys.platform != 'sunos5')
435
436    if ctypes is not None and have_dirent_d_type:
437        import ctypes.util
438
439        DIR_p = ctypes.c_void_p
440
441        # Rather annoying how the dirent struct is slightly different on each
442        # platform. The only fields we care about are d_name and d_type.
443        class Dirent(ctypes.Structure):
444            if sys.platform.startswith('linux'):
445                _fields_ = (
446                    ('d_ino', ctypes.c_ulong),
447                    ('d_off', ctypes.c_long),
448                    ('d_reclen', ctypes.c_ushort),
449                    ('d_type', ctypes.c_byte),
450                    ('d_name', ctypes.c_char * 256),
451                )
452            elif 'openbsd' in sys.platform:
453                _fields_ = (
454                    ('d_ino', ctypes.c_uint64),
455                    ('d_off', ctypes.c_uint64),
456                    ('d_reclen', ctypes.c_uint16),
457                    ('d_type', ctypes.c_uint8),
458                    ('d_namlen', ctypes.c_uint8),
459                    ('__d_padding', ctypes.c_uint8 * 4),
460                    ('d_name', ctypes.c_char * 256),
461                )
462            else:
463                _fields_ = (
464                    ('d_ino', ctypes.c_uint32),  # must be uint32, not ulong
465                    ('d_reclen', ctypes.c_ushort),
466                    ('d_type', ctypes.c_byte),
467                    ('d_namlen', ctypes.c_byte),
468                    ('d_name', ctypes.c_char * 256),
469                )
470
471        DT_UNKNOWN = 0
472        DT_DIR = 4
473        DT_REG = 8
474        DT_LNK = 10
475
476        Dirent_p = ctypes.POINTER(Dirent)
477        Dirent_pp = ctypes.POINTER(Dirent_p)
478
479        libc = ctypes.CDLL(ctypes.util.find_library('c'), use_errno=True)
480        opendir = libc.opendir
481        opendir.argtypes = [ctypes.c_char_p]
482        opendir.restype = DIR_p
483
484        readdir_r = libc.readdir_r
485        readdir_r.argtypes = [DIR_p, Dirent_p, Dirent_pp]
486        readdir_r.restype = ctypes.c_int
487
488        closedir = libc.closedir
489        closedir.argtypes = [DIR_p]
490        closedir.restype = ctypes.c_int
491
492        file_system_encoding = sys.getfilesystemencoding()
493
494        class PosixDirEntry(object):
495            __slots__ = ('name', '_d_type', '_stat', '_lstat', '_scandir_path', '_path', '_inode')
496
497            def __init__(self, scandir_path, name, d_type, inode):
498                self._scandir_path = scandir_path
499                self.name = name
500                self._d_type = d_type
501                self._inode = inode
502                self._stat = None
503                self._lstat = None
504                self._path = None
505
506            @property
507            def path(self):
508                if self._path is None:
509                    self._path = join(self._scandir_path, self.name)
510                return self._path
511
512            def stat(self, follow_symlinks=True):
513                if follow_symlinks:
514                    if self._stat is None:
515                        if self.is_symlink():
516                            self._stat = stat(self.path)
517                        else:
518                            if self._lstat is None:
519                                self._lstat = lstat(self.path)
520                            self._stat = self._lstat
521                    return self._stat
522                else:
523                    if self._lstat is None:
524                        self._lstat = lstat(self.path)
525                    return self._lstat
526
527            def is_dir(self, follow_symlinks=True):
528                if (self._d_type == DT_UNKNOWN or
529                        (follow_symlinks and self.is_symlink())):
530                    try:
531                        st = self.stat(follow_symlinks=follow_symlinks)
532                    except OSError as e:
533                        if e.errno != ENOENT:
534                            raise
535                        return False
536                    return st.st_mode & 0o170000 == S_IFDIR
537                else:
538                    return self._d_type == DT_DIR
539
540            def is_file(self, follow_symlinks=True):
541                if (self._d_type == DT_UNKNOWN or
542                        (follow_symlinks and self.is_symlink())):
543                    try:
544                        st = self.stat(follow_symlinks=follow_symlinks)
545                    except OSError as e:
546                        if e.errno != ENOENT:
547                            raise
548                        return False
549                    return st.st_mode & 0o170000 == S_IFREG
550                else:
551                    return self._d_type == DT_REG
552
553            def is_symlink(self):
554                if self._d_type == DT_UNKNOWN:
555                    try:
556                        st = self.stat(follow_symlinks=False)
557                    except OSError as e:
558                        if e.errno != ENOENT:
559                            raise
560                        return False
561                    return st.st_mode & 0o170000 == S_IFLNK
562                else:
563                    return self._d_type == DT_LNK
564
565            def inode(self):
566                return self._inode
567
568            def __str__(self):
569                return '<{0}: {1!r}>'.format(self.__class__.__name__, self.name)
570
571            __repr__ = __str__
572
573        def posix_error(filename):
574            errno = ctypes.get_errno()
575            exc = OSError(errno, strerror(errno))
576            exc.filename = filename
577            return exc
578
579        def scandir_python(path=unicode('.')):
580            """Like os.listdir(), but yield DirEntry objects instead of returning
581            a list of names.
582            """
583            if isinstance(path, bytes):
584                opendir_path = path
585                is_bytes = True
586            else:
587                opendir_path = path.encode(file_system_encoding)
588                is_bytes = False
589            dir_p = opendir(opendir_path)
590            if not dir_p:
591                raise posix_error(path)
592            try:
593                result = Dirent_p()
594                while True:
595                    entry = Dirent()
596                    if readdir_r(dir_p, entry, result):
597                        raise posix_error(path)
598                    if not result:
599                        break
600                    name = entry.d_name
601                    if name not in (b'.', b'..'):
602                        if not is_bytes:
603                            name = name.decode(file_system_encoding)
604                        yield PosixDirEntry(path, name, entry.d_type, entry.d_ino)
605            finally:
606                if closedir(dir_p):
607                    raise posix_error(path)
608
609    if _scandir is not None:
610        scandir_c = _scandir.scandir
611        DirEntry_c = _scandir.DirEntry
612
613    if _scandir is not None:
614        scandir = scandir_c
615        DirEntry = DirEntry_c
616    elif ctypes is not None and have_dirent_d_type:
617        scandir = scandir_python
618        DirEntry = PosixDirEntry
619    else:
620        scandir = scandir_generic
621        DirEntry = GenericDirEntry
622
623
624# Some other system -- no d_type or stat information
625else:
626    scandir = scandir_generic
627    DirEntry = GenericDirEntry
628
629
630def _walk(top, topdown=True, onerror=None, followlinks=False):
631    """Like Python 3.5's implementation of os.walk() -- faster than
632    the pre-Python 3.5 version as it uses scandir() internally.
633    """
634    dirs = []
635    nondirs = []
636
637    # We may not have read permission for top, in which case we can't
638    # get a list of the files the directory contains.  os.walk
639    # always suppressed the exception then, rather than blow up for a
640    # minor reason when (say) a thousand readable directories are still
641    # left to visit.  That logic is copied here.
642    try:
643        scandir_it = scandir(top)
644    except OSError as error:
645        if onerror is not None:
646            onerror(error)
647        return
648
649    while True:
650        try:
651            try:
652                entry = next(scandir_it)
653            except StopIteration:
654                break
655        except OSError as error:
656            if onerror is not None:
657                onerror(error)
658            return
659
660        try:
661            is_dir = entry.is_dir()
662        except OSError:
663            # If is_dir() raises an OSError, consider that the entry is not
664            # a directory, same behaviour than os.path.isdir().
665            is_dir = False
666
667        if is_dir:
668            dirs.append(entry.name)
669        else:
670            nondirs.append(entry.name)
671
672        if not topdown and is_dir:
673            # Bottom-up: recurse into sub-directory, but exclude symlinks to
674            # directories if followlinks is False
675            if followlinks:
676                walk_into = True
677            else:
678                try:
679                    is_symlink = entry.is_symlink()
680                except OSError:
681                    # If is_symlink() raises an OSError, consider that the
682                    # entry is not a symbolic link, same behaviour than
683                    # os.path.islink().
684                    is_symlink = False
685                walk_into = not is_symlink
686
687            if walk_into:
688                for entry in walk(entry.path, topdown, onerror, followlinks):
689                    yield entry
690
691    # Yield before recursion if going top down
692    if topdown:
693        yield top, dirs, nondirs
694
695        # Recurse into sub-directories
696        for name in dirs:
697            new_path = join(top, name)
698            # Issue #23605: os.path.islink() is used instead of caching
699            # entry.is_symlink() result during the loop on os.scandir() because
700            # the caller can replace the directory entry during the "yield"
701            # above.
702            if followlinks or not islink(new_path):
703                for entry in walk(new_path, topdown, onerror, followlinks):
704                    yield entry
705    else:
706        # Yield after recursion if going bottom up
707        yield top, dirs, nondirs
708
709
710if IS_PY3 or sys.platform != 'win32':
711    walk = _walk
712else:
713    # Fix for broken unicode handling on Windows on Python 2.x, see:
714    # https://github.com/benhoyt/scandir/issues/54
715    file_system_encoding = sys.getfilesystemencoding()
716
717    def walk(top, topdown=True, onerror=None, followlinks=False):
718        if isinstance(top, bytes):
719            top = top.decode(file_system_encoding)
720        return _walk(top, topdown, onerror, followlinks)
721