• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"""Utility functions for copying and archiving files and directory trees.
2
3XXX The functions here don't copy the resource fork or other metadata on Mac.
4
5"""
6
7import os
8import sys
9import stat
10import fnmatch
11import collections
12import errno
13
14try:
15    import zlib
16    del zlib
17    _ZLIB_SUPPORTED = True
18except ImportError:
19    _ZLIB_SUPPORTED = False
20
21try:
22    import bz2
23    del bz2
24    _BZ2_SUPPORTED = True
25except ImportError:
26    _BZ2_SUPPORTED = False
27
28try:
29    import lzma
30    del lzma
31    _LZMA_SUPPORTED = True
32except ImportError:
33    _LZMA_SUPPORTED = False
34
35try:
36    from pwd import getpwnam
37except ImportError:
38    getpwnam = None
39
40try:
41    from grp import getgrnam
42except ImportError:
43    getgrnam = None
44
45_WINDOWS = os.name == 'nt'
46posix = nt = None
47if os.name == 'posix':
48    import posix
49elif _WINDOWS:
50    import nt
51
52COPY_BUFSIZE = 1024 * 1024 if _WINDOWS else 64 * 1024
53_USE_CP_SENDFILE = hasattr(os, "sendfile") and sys.platform.startswith("linux")
54_HAS_FCOPYFILE = posix and hasattr(posix, "_fcopyfile")  # macOS
55
56__all__ = ["copyfileobj", "copyfile", "copymode", "copystat", "copy", "copy2",
57           "copytree", "move", "rmtree", "Error", "SpecialFileError",
58           "ExecError", "make_archive", "get_archive_formats",
59           "register_archive_format", "unregister_archive_format",
60           "get_unpack_formats", "register_unpack_format",
61           "unregister_unpack_format", "unpack_archive",
62           "ignore_patterns", "chown", "which", "get_terminal_size",
63           "SameFileError"]
64           # disk_usage is added later, if available on the platform
65
66class Error(OSError):
67    pass
68
69class SameFileError(Error):
70    """Raised when source and destination are the same file."""
71
72class SpecialFileError(OSError):
73    """Raised when trying to do a kind of operation (e.g. copying) which is
74    not supported on a special file (e.g. a named pipe)"""
75
76class ExecError(OSError):
77    """Raised when a command could not be executed"""
78
79class ReadError(OSError):
80    """Raised when an archive cannot be read"""
81
82class RegistryError(Exception):
83    """Raised when a registry operation with the archiving
84    and unpacking registries fails"""
85
86class _GiveupOnFastCopy(Exception):
87    """Raised as a signal to fallback on using raw read()/write()
88    file copy when fast-copy functions fail to do so.
89    """
90
91def _fastcopy_fcopyfile(fsrc, fdst, flags):
92    """Copy a regular file content or metadata by using high-performance
93    fcopyfile(3) syscall (macOS).
94    """
95    try:
96        infd = fsrc.fileno()
97        outfd = fdst.fileno()
98    except Exception as err:
99        raise _GiveupOnFastCopy(err)  # not a regular file
100
101    try:
102        posix._fcopyfile(infd, outfd, flags)
103    except OSError as err:
104        err.filename = fsrc.name
105        err.filename2 = fdst.name
106        if err.errno in {errno.EINVAL, errno.ENOTSUP}:
107            raise _GiveupOnFastCopy(err)
108        else:
109            raise err from None
110
111def _fastcopy_sendfile(fsrc, fdst):
112    """Copy data from one regular mmap-like fd to another by using
113    high-performance sendfile(2) syscall.
114    This should work on Linux >= 2.6.33 only.
115    """
116    # Note: copyfileobj() is left alone in order to not introduce any
117    # unexpected breakage. Possible risks by using zero-copy calls
118    # in copyfileobj() are:
119    # - fdst cannot be open in "a"(ppend) mode
120    # - fsrc and fdst may be open in "t"(ext) mode
121    # - fsrc may be a BufferedReader (which hides unread data in a buffer),
122    #   GzipFile (which decompresses data), HTTPResponse (which decodes
123    #   chunks).
124    # - possibly others (e.g. encrypted fs/partition?)
125    global _USE_CP_SENDFILE
126    try:
127        infd = fsrc.fileno()
128        outfd = fdst.fileno()
129    except Exception as err:
130        raise _GiveupOnFastCopy(err)  # not a regular file
131
132    # Hopefully the whole file will be copied in a single call.
133    # sendfile() is called in a loop 'till EOF is reached (0 return)
134    # so a bufsize smaller or bigger than the actual file size
135    # should not make any difference, also in case the file content
136    # changes while being copied.
137    try:
138        blocksize = max(os.fstat(infd).st_size, 2 ** 23)  # min 8MiB
139    except OSError:
140        blocksize = 2 ** 27  # 128MiB
141    # On 32-bit architectures truncate to 1GiB to avoid OverflowError,
142    # see bpo-38319.
143    if sys.maxsize < 2 ** 32:
144        blocksize = min(blocksize, 2 ** 30)
145
146    offset = 0
147    while True:
148        try:
149            sent = os.sendfile(outfd, infd, offset, blocksize)
150        except OSError as err:
151            # ...in oder to have a more informative exception.
152            err.filename = fsrc.name
153            err.filename2 = fdst.name
154
155            if err.errno == errno.ENOTSOCK:
156                # sendfile() on this platform (probably Linux < 2.6.33)
157                # does not support copies between regular files (only
158                # sockets).
159                _USE_CP_SENDFILE = False
160                raise _GiveupOnFastCopy(err)
161
162            if err.errno == errno.ENOSPC:  # filesystem is full
163                raise err from None
164
165            # Give up on first call and if no data was copied.
166            if offset == 0 and os.lseek(outfd, 0, os.SEEK_CUR) == 0:
167                raise _GiveupOnFastCopy(err)
168
169            raise err
170        else:
171            if sent == 0:
172                break  # EOF
173            offset += sent
174
175def _copyfileobj_readinto(fsrc, fdst, length=COPY_BUFSIZE):
176    """readinto()/memoryview() based variant of copyfileobj().
177    *fsrc* must support readinto() method and both files must be
178    open in binary mode.
179    """
180    # Localize variable access to minimize overhead.
181    fsrc_readinto = fsrc.readinto
182    fdst_write = fdst.write
183    with memoryview(bytearray(length)) as mv:
184        while True:
185            n = fsrc_readinto(mv)
186            if not n:
187                break
188            elif n < length:
189                with mv[:n] as smv:
190                    fdst.write(smv)
191            else:
192                fdst_write(mv)
193
194def copyfileobj(fsrc, fdst, length=0):
195    """copy data from file-like object fsrc to file-like object fdst"""
196    # Localize variable access to minimize overhead.
197    if not length:
198        length = COPY_BUFSIZE
199    fsrc_read = fsrc.read
200    fdst_write = fdst.write
201    while True:
202        buf = fsrc_read(length)
203        if not buf:
204            break
205        fdst_write(buf)
206
207def _samefile(src, dst):
208    # Macintosh, Unix.
209    if isinstance(src, os.DirEntry) and hasattr(os.path, 'samestat'):
210        try:
211            return os.path.samestat(src.stat(), os.stat(dst))
212        except OSError:
213            return False
214
215    if hasattr(os.path, 'samefile'):
216        try:
217            return os.path.samefile(src, dst)
218        except OSError:
219            return False
220
221    # All other platforms: check for same pathname.
222    return (os.path.normcase(os.path.abspath(src)) ==
223            os.path.normcase(os.path.abspath(dst)))
224
225def _stat(fn):
226    return fn.stat() if isinstance(fn, os.DirEntry) else os.stat(fn)
227
228def _islink(fn):
229    return fn.is_symlink() if isinstance(fn, os.DirEntry) else os.path.islink(fn)
230
231def copyfile(src, dst, *, follow_symlinks=True):
232    """Copy data from src to dst in the most efficient way possible.
233
234    If follow_symlinks is not set and src is a symbolic link, a new
235    symlink will be created instead of copying the file it points to.
236
237    """
238    if _samefile(src, dst):
239        raise SameFileError("{!r} and {!r} are the same file".format(src, dst))
240
241    file_size = 0
242    for i, fn in enumerate([src, dst]):
243        try:
244            st = _stat(fn)
245        except OSError:
246            # File most likely does not exist
247            pass
248        else:
249            # XXX What about other special files? (sockets, devices...)
250            if stat.S_ISFIFO(st.st_mode):
251                fn = fn.path if isinstance(fn, os.DirEntry) else fn
252                raise SpecialFileError("`%s` is a named pipe" % fn)
253            if _WINDOWS and i == 0:
254                file_size = st.st_size
255
256    if not follow_symlinks and _islink(src):
257        os.symlink(os.readlink(src), dst)
258    else:
259        with open(src, 'rb') as fsrc, open(dst, 'wb') as fdst:
260            # macOS
261            if _HAS_FCOPYFILE:
262                try:
263                    _fastcopy_fcopyfile(fsrc, fdst, posix._COPYFILE_DATA)
264                    return dst
265                except _GiveupOnFastCopy:
266                    pass
267            # Linux
268            elif _USE_CP_SENDFILE:
269                try:
270                    _fastcopy_sendfile(fsrc, fdst)
271                    return dst
272                except _GiveupOnFastCopy:
273                    pass
274            # Windows, see:
275            # https://github.com/python/cpython/pull/7160#discussion_r195405230
276            elif _WINDOWS and file_size > 0:
277                _copyfileobj_readinto(fsrc, fdst, min(file_size, COPY_BUFSIZE))
278                return dst
279
280            copyfileobj(fsrc, fdst)
281
282    return dst
283
284def copymode(src, dst, *, follow_symlinks=True):
285    """Copy mode bits from src to dst.
286
287    If follow_symlinks is not set, symlinks aren't followed if and only
288    if both `src` and `dst` are symlinks.  If `lchmod` isn't available
289    (e.g. Linux) this method does nothing.
290
291    """
292    if not follow_symlinks and _islink(src) and os.path.islink(dst):
293        if hasattr(os, 'lchmod'):
294            stat_func, chmod_func = os.lstat, os.lchmod
295        else:
296            return
297    else:
298        stat_func, chmod_func = _stat, os.chmod
299
300    st = stat_func(src)
301    chmod_func(dst, stat.S_IMODE(st.st_mode))
302
303if hasattr(os, 'listxattr'):
304    def _copyxattr(src, dst, *, follow_symlinks=True):
305        """Copy extended filesystem attributes from `src` to `dst`.
306
307        Overwrite existing attributes.
308
309        If `follow_symlinks` is false, symlinks won't be followed.
310
311        """
312
313        try:
314            names = os.listxattr(src, follow_symlinks=follow_symlinks)
315        except OSError as e:
316            if e.errno not in (errno.ENOTSUP, errno.ENODATA, errno.EINVAL):
317                raise
318            return
319        for name in names:
320            try:
321                value = os.getxattr(src, name, follow_symlinks=follow_symlinks)
322                os.setxattr(dst, name, value, follow_symlinks=follow_symlinks)
323            except OSError as e:
324                if e.errno not in (errno.EPERM, errno.ENOTSUP, errno.ENODATA,
325                                   errno.EINVAL):
326                    raise
327else:
328    def _copyxattr(*args, **kwargs):
329        pass
330
331def copystat(src, dst, *, follow_symlinks=True):
332    """Copy file metadata
333
334    Copy the permission bits, last access time, last modification time, and
335    flags from `src` to `dst`. On Linux, copystat() also copies the "extended
336    attributes" where possible. The file contents, owner, and group are
337    unaffected. `src` and `dst` are path-like objects or path names given as
338    strings.
339
340    If the optional flag `follow_symlinks` is not set, symlinks aren't
341    followed if and only if both `src` and `dst` are symlinks.
342    """
343    def _nop(*args, ns=None, follow_symlinks=None):
344        pass
345
346    # follow symlinks (aka don't not follow symlinks)
347    follow = follow_symlinks or not (_islink(src) and os.path.islink(dst))
348    if follow:
349        # use the real function if it exists
350        def lookup(name):
351            return getattr(os, name, _nop)
352    else:
353        # use the real function only if it exists
354        # *and* it supports follow_symlinks
355        def lookup(name):
356            fn = getattr(os, name, _nop)
357            if fn in os.supports_follow_symlinks:
358                return fn
359            return _nop
360
361    if isinstance(src, os.DirEntry):
362        st = src.stat(follow_symlinks=follow)
363    else:
364        st = lookup("stat")(src, follow_symlinks=follow)
365    mode = stat.S_IMODE(st.st_mode)
366    lookup("utime")(dst, ns=(st.st_atime_ns, st.st_mtime_ns),
367        follow_symlinks=follow)
368    # We must copy extended attributes before the file is (potentially)
369    # chmod()'ed read-only, otherwise setxattr() will error with -EACCES.
370    _copyxattr(src, dst, follow_symlinks=follow)
371    try:
372        lookup("chmod")(dst, mode, follow_symlinks=follow)
373    except NotImplementedError:
374        # if we got a NotImplementedError, it's because
375        #   * follow_symlinks=False,
376        #   * lchown() is unavailable, and
377        #   * either
378        #       * fchownat() is unavailable or
379        #       * fchownat() doesn't implement AT_SYMLINK_NOFOLLOW.
380        #         (it returned ENOSUP.)
381        # therefore we're out of options--we simply cannot chown the
382        # symlink.  give up, suppress the error.
383        # (which is what shutil always did in this circumstance.)
384        pass
385    if hasattr(st, 'st_flags'):
386        try:
387            lookup("chflags")(dst, st.st_flags, follow_symlinks=follow)
388        except OSError as why:
389            for err in 'EOPNOTSUPP', 'ENOTSUP':
390                if hasattr(errno, err) and why.errno == getattr(errno, err):
391                    break
392            else:
393                raise
394
395def copy(src, dst, *, follow_symlinks=True):
396    """Copy data and mode bits ("cp src dst"). Return the file's destination.
397
398    The destination may be a directory.
399
400    If follow_symlinks is false, symlinks won't be followed. This
401    resembles GNU's "cp -P src dst".
402
403    If source and destination are the same file, a SameFileError will be
404    raised.
405
406    """
407    if os.path.isdir(dst):
408        dst = os.path.join(dst, os.path.basename(src))
409    copyfile(src, dst, follow_symlinks=follow_symlinks)
410    copymode(src, dst, follow_symlinks=follow_symlinks)
411    return dst
412
413def copy2(src, dst, *, follow_symlinks=True):
414    """Copy data and metadata. Return the file's destination.
415
416    Metadata is copied with copystat(). Please see the copystat function
417    for more information.
418
419    The destination may be a directory.
420
421    If follow_symlinks is false, symlinks won't be followed. This
422    resembles GNU's "cp -P src dst".
423    """
424    if os.path.isdir(dst):
425        dst = os.path.join(dst, os.path.basename(src))
426    copyfile(src, dst, follow_symlinks=follow_symlinks)
427    copystat(src, dst, follow_symlinks=follow_symlinks)
428    return dst
429
430def ignore_patterns(*patterns):
431    """Function that can be used as copytree() ignore parameter.
432
433    Patterns is a sequence of glob-style patterns
434    that are used to exclude files"""
435    def _ignore_patterns(path, names):
436        ignored_names = []
437        for pattern in patterns:
438            ignored_names.extend(fnmatch.filter(names, pattern))
439        return set(ignored_names)
440    return _ignore_patterns
441
442def _copytree(entries, src, dst, symlinks, ignore, copy_function,
443              ignore_dangling_symlinks, dirs_exist_ok=False):
444    if ignore is not None:
445        ignored_names = ignore(src, {x.name for x in entries})
446    else:
447        ignored_names = set()
448
449    os.makedirs(dst, exist_ok=dirs_exist_ok)
450    errors = []
451    use_srcentry = copy_function is copy2 or copy_function is copy
452
453    for srcentry in entries:
454        if srcentry.name in ignored_names:
455            continue
456        srcname = os.path.join(src, srcentry.name)
457        dstname = os.path.join(dst, srcentry.name)
458        srcobj = srcentry if use_srcentry else srcname
459        try:
460            is_symlink = srcentry.is_symlink()
461            if is_symlink and os.name == 'nt':
462                # Special check for directory junctions, which appear as
463                # symlinks but we want to recurse.
464                lstat = srcentry.stat(follow_symlinks=False)
465                if lstat.st_reparse_tag == stat.IO_REPARSE_TAG_MOUNT_POINT:
466                    is_symlink = False
467            if is_symlink:
468                linkto = os.readlink(srcname)
469                if symlinks:
470                    # We can't just leave it to `copy_function` because legacy
471                    # code with a custom `copy_function` may rely on copytree
472                    # doing the right thing.
473                    os.symlink(linkto, dstname)
474                    copystat(srcobj, dstname, follow_symlinks=not symlinks)
475                else:
476                    # ignore dangling symlink if the flag is on
477                    if not os.path.exists(linkto) and ignore_dangling_symlinks:
478                        continue
479                    # otherwise let the copy occur. copy2 will raise an error
480                    if srcentry.is_dir():
481                        copytree(srcobj, dstname, symlinks, ignore,
482                                 copy_function, dirs_exist_ok=dirs_exist_ok)
483                    else:
484                        copy_function(srcobj, dstname)
485            elif srcentry.is_dir():
486                copytree(srcobj, dstname, symlinks, ignore, copy_function,
487                         dirs_exist_ok=dirs_exist_ok)
488            else:
489                # Will raise a SpecialFileError for unsupported file types
490                copy_function(srcobj, dstname)
491        # catch the Error from the recursive copytree so that we can
492        # continue with other files
493        except Error as err:
494            errors.extend(err.args[0])
495        except OSError as why:
496            errors.append((srcname, dstname, str(why)))
497    try:
498        copystat(src, dst)
499    except OSError as why:
500        # Copying file access times may fail on Windows
501        if getattr(why, 'winerror', None) is None:
502            errors.append((src, dst, str(why)))
503    if errors:
504        raise Error(errors)
505    return dst
506
507def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2,
508             ignore_dangling_symlinks=False, dirs_exist_ok=False):
509    """Recursively copy a directory tree and return the destination directory.
510
511    dirs_exist_ok dictates whether to raise an exception in case dst or any
512    missing parent directory already exists.
513
514    If exception(s) occur, an Error is raised with a list of reasons.
515
516    If the optional symlinks flag is true, symbolic links in the
517    source tree result in symbolic links in the destination tree; if
518    it is false, the contents of the files pointed to by symbolic
519    links are copied. If the file pointed by the symlink doesn't
520    exist, an exception will be added in the list of errors raised in
521    an Error exception at the end of the copy process.
522
523    You can set the optional ignore_dangling_symlinks flag to true if you
524    want to silence this exception. Notice that this has no effect on
525    platforms that don't support os.symlink.
526
527    The optional ignore argument is a callable. If given, it
528    is called with the `src` parameter, which is the directory
529    being visited by copytree(), and `names` which is the list of
530    `src` contents, as returned by os.listdir():
531
532        callable(src, names) -> ignored_names
533
534    Since copytree() is called recursively, the callable will be
535    called once for each directory that is copied. It returns a
536    list of names relative to the `src` directory that should
537    not be copied.
538
539    The optional copy_function argument is a callable that will be used
540    to copy each file. It will be called with the source path and the
541    destination path as arguments. By default, copy2() is used, but any
542    function that supports the same signature (like copy()) can be used.
543
544    """
545    sys.audit("shutil.copytree", src, dst)
546    with os.scandir(src) as itr:
547        entries = list(itr)
548    return _copytree(entries=entries, src=src, dst=dst, symlinks=symlinks,
549                     ignore=ignore, copy_function=copy_function,
550                     ignore_dangling_symlinks=ignore_dangling_symlinks,
551                     dirs_exist_ok=dirs_exist_ok)
552
553if hasattr(os.stat_result, 'st_file_attributes'):
554    # Special handling for directory junctions to make them behave like
555    # symlinks for shutil.rmtree, since in general they do not appear as
556    # regular links.
557    def _rmtree_isdir(entry):
558        try:
559            st = entry.stat(follow_symlinks=False)
560            return (stat.S_ISDIR(st.st_mode) and not
561                (st.st_file_attributes & stat.FILE_ATTRIBUTE_REPARSE_POINT
562                 and st.st_reparse_tag == stat.IO_REPARSE_TAG_MOUNT_POINT))
563        except OSError:
564            return False
565
566    def _rmtree_islink(path):
567        try:
568            st = os.lstat(path)
569            return (stat.S_ISLNK(st.st_mode) or
570                (st.st_file_attributes & stat.FILE_ATTRIBUTE_REPARSE_POINT
571                 and st.st_reparse_tag == stat.IO_REPARSE_TAG_MOUNT_POINT))
572        except OSError:
573            return False
574else:
575    def _rmtree_isdir(entry):
576        try:
577            return entry.is_dir(follow_symlinks=False)
578        except OSError:
579            return False
580
581    def _rmtree_islink(path):
582        return os.path.islink(path)
583
584# version vulnerable to race conditions
585def _rmtree_unsafe(path, onerror):
586    try:
587        with os.scandir(path) as scandir_it:
588            entries = list(scandir_it)
589    except OSError:
590        onerror(os.scandir, path, sys.exc_info())
591        entries = []
592    for entry in entries:
593        fullname = entry.path
594        if _rmtree_isdir(entry):
595            try:
596                if entry.is_symlink():
597                    # This can only happen if someone replaces
598                    # a directory with a symlink after the call to
599                    # os.scandir or entry.is_dir above.
600                    raise OSError("Cannot call rmtree on a symbolic link")
601            except OSError:
602                onerror(os.path.islink, fullname, sys.exc_info())
603                continue
604            _rmtree_unsafe(fullname, onerror)
605        else:
606            try:
607                os.unlink(fullname)
608            except OSError:
609                onerror(os.unlink, fullname, sys.exc_info())
610    try:
611        os.rmdir(path)
612    except OSError:
613        onerror(os.rmdir, path, sys.exc_info())
614
615# Version using fd-based APIs to protect against races
616def _rmtree_safe_fd(topfd, path, onerror):
617    try:
618        with os.scandir(topfd) as scandir_it:
619            entries = list(scandir_it)
620    except OSError as err:
621        err.filename = path
622        onerror(os.scandir, path, sys.exc_info())
623        return
624    for entry in entries:
625        fullname = os.path.join(path, entry.name)
626        try:
627            is_dir = entry.is_dir(follow_symlinks=False)
628        except OSError:
629            is_dir = False
630        else:
631            if is_dir:
632                try:
633                    orig_st = entry.stat(follow_symlinks=False)
634                    is_dir = stat.S_ISDIR(orig_st.st_mode)
635                except OSError:
636                    onerror(os.lstat, fullname, sys.exc_info())
637                    continue
638        if is_dir:
639            try:
640                dirfd = os.open(entry.name, os.O_RDONLY, dir_fd=topfd)
641            except OSError:
642                onerror(os.open, fullname, sys.exc_info())
643            else:
644                try:
645                    if os.path.samestat(orig_st, os.fstat(dirfd)):
646                        _rmtree_safe_fd(dirfd, fullname, onerror)
647                        try:
648                            os.rmdir(entry.name, dir_fd=topfd)
649                        except OSError:
650                            onerror(os.rmdir, fullname, sys.exc_info())
651                    else:
652                        try:
653                            # This can only happen if someone replaces
654                            # a directory with a symlink after the call to
655                            # os.scandir or stat.S_ISDIR above.
656                            raise OSError("Cannot call rmtree on a symbolic "
657                                          "link")
658                        except OSError:
659                            onerror(os.path.islink, fullname, sys.exc_info())
660                finally:
661                    os.close(dirfd)
662        else:
663            try:
664                os.unlink(entry.name, dir_fd=topfd)
665            except OSError:
666                onerror(os.unlink, fullname, sys.exc_info())
667
668_use_fd_functions = ({os.open, os.stat, os.unlink, os.rmdir} <=
669                     os.supports_dir_fd and
670                     os.scandir in os.supports_fd and
671                     os.stat in os.supports_follow_symlinks)
672
673def rmtree(path, ignore_errors=False, onerror=None):
674    """Recursively delete a directory tree.
675
676    If ignore_errors is set, errors are ignored; otherwise, if onerror
677    is set, it is called to handle the error with arguments (func,
678    path, exc_info) where func is platform and implementation dependent;
679    path is the argument to that function that caused it to fail; and
680    exc_info is a tuple returned by sys.exc_info().  If ignore_errors
681    is false and onerror is None, an exception is raised.
682
683    """
684    sys.audit("shutil.rmtree", path)
685    if ignore_errors:
686        def onerror(*args):
687            pass
688    elif onerror is None:
689        def onerror(*args):
690            raise
691    if _use_fd_functions:
692        # While the unsafe rmtree works fine on bytes, the fd based does not.
693        if isinstance(path, bytes):
694            path = os.fsdecode(path)
695        # Note: To guard against symlink races, we use the standard
696        # lstat()/open()/fstat() trick.
697        try:
698            orig_st = os.lstat(path)
699        except Exception:
700            onerror(os.lstat, path, sys.exc_info())
701            return
702        try:
703            fd = os.open(path, os.O_RDONLY)
704        except Exception:
705            onerror(os.lstat, path, sys.exc_info())
706            return
707        try:
708            if os.path.samestat(orig_st, os.fstat(fd)):
709                _rmtree_safe_fd(fd, path, onerror)
710                try:
711                    os.rmdir(path)
712                except OSError:
713                    onerror(os.rmdir, path, sys.exc_info())
714            else:
715                try:
716                    # symlinks to directories are forbidden, see bug #1669
717                    raise OSError("Cannot call rmtree on a symbolic link")
718                except OSError:
719                    onerror(os.path.islink, path, sys.exc_info())
720        finally:
721            os.close(fd)
722    else:
723        try:
724            if _rmtree_islink(path):
725                # symlinks to directories are forbidden, see bug #1669
726                raise OSError("Cannot call rmtree on a symbolic link")
727        except OSError:
728            onerror(os.path.islink, path, sys.exc_info())
729            # can't continue even if onerror hook returns
730            return
731        return _rmtree_unsafe(path, onerror)
732
733# Allow introspection of whether or not the hardening against symlink
734# attacks is supported on the current platform
735rmtree.avoids_symlink_attacks = _use_fd_functions
736
737def _basename(path):
738    # A basename() variant which first strips the trailing slash, if present.
739    # Thus we always get the last component of the path, even for directories.
740    sep = os.path.sep + (os.path.altsep or '')
741    return os.path.basename(path.rstrip(sep))
742
743def move(src, dst, copy_function=copy2):
744    """Recursively move a file or directory to another location. This is
745    similar to the Unix "mv" command. Return the file or directory's
746    destination.
747
748    If the destination is a directory or a symlink to a directory, the source
749    is moved inside the directory. The destination path must not already
750    exist.
751
752    If the destination already exists but is not a directory, it may be
753    overwritten depending on os.rename() semantics.
754
755    If the destination is on our current filesystem, then rename() is used.
756    Otherwise, src is copied to the destination and then removed. Symlinks are
757    recreated under the new name if os.rename() fails because of cross
758    filesystem renames.
759
760    The optional `copy_function` argument is a callable that will be used
761    to copy the source or it will be delegated to `copytree`.
762    By default, copy2() is used, but any function that supports the same
763    signature (like copy()) can be used.
764
765    A lot more could be done here...  A look at a mv.c shows a lot of
766    the issues this implementation glosses over.
767
768    """
769    real_dst = dst
770    if os.path.isdir(dst):
771        if _samefile(src, dst):
772            # We might be on a case insensitive filesystem,
773            # perform the rename anyway.
774            os.rename(src, dst)
775            return
776
777        real_dst = os.path.join(dst, _basename(src))
778        if os.path.exists(real_dst):
779            raise Error("Destination path '%s' already exists" % real_dst)
780    try:
781        os.rename(src, real_dst)
782    except OSError:
783        if os.path.islink(src):
784            linkto = os.readlink(src)
785            os.symlink(linkto, real_dst)
786            os.unlink(src)
787        elif os.path.isdir(src):
788            if _destinsrc(src, dst):
789                raise Error("Cannot move a directory '%s' into itself"
790                            " '%s'." % (src, dst))
791            copytree(src, real_dst, copy_function=copy_function,
792                     symlinks=True)
793            rmtree(src)
794        else:
795            copy_function(src, real_dst)
796            os.unlink(src)
797    return real_dst
798
799def _destinsrc(src, dst):
800    src = os.path.abspath(src)
801    dst = os.path.abspath(dst)
802    if not src.endswith(os.path.sep):
803        src += os.path.sep
804    if not dst.endswith(os.path.sep):
805        dst += os.path.sep
806    return dst.startswith(src)
807
808def _get_gid(name):
809    """Returns a gid, given a group name."""
810    if getgrnam is None or name is None:
811        return None
812    try:
813        result = getgrnam(name)
814    except KeyError:
815        result = None
816    if result is not None:
817        return result[2]
818    return None
819
820def _get_uid(name):
821    """Returns an uid, given a user name."""
822    if getpwnam is None or name is None:
823        return None
824    try:
825        result = getpwnam(name)
826    except KeyError:
827        result = None
828    if result is not None:
829        return result[2]
830    return None
831
832def _make_tarball(base_name, base_dir, compress="gzip", verbose=0, dry_run=0,
833                  owner=None, group=None, logger=None):
834    """Create a (possibly compressed) tar file from all the files under
835    'base_dir'.
836
837    'compress' must be "gzip" (the default), "bzip2", "xz", or None.
838
839    'owner' and 'group' can be used to define an owner and a group for the
840    archive that is being built. If not provided, the current owner and group
841    will be used.
842
843    The output tar file will be named 'base_name' +  ".tar", possibly plus
844    the appropriate compression extension (".gz", ".bz2", or ".xz").
845
846    Returns the output filename.
847    """
848    if compress is None:
849        tar_compression = ''
850    elif _ZLIB_SUPPORTED and compress == 'gzip':
851        tar_compression = 'gz'
852    elif _BZ2_SUPPORTED and compress == 'bzip2':
853        tar_compression = 'bz2'
854    elif _LZMA_SUPPORTED and compress == 'xz':
855        tar_compression = 'xz'
856    else:
857        raise ValueError("bad value for 'compress', or compression format not "
858                         "supported : {0}".format(compress))
859
860    import tarfile  # late import for breaking circular dependency
861
862    compress_ext = '.' + tar_compression if compress else ''
863    archive_name = base_name + '.tar' + compress_ext
864    archive_dir = os.path.dirname(archive_name)
865
866    if archive_dir and not os.path.exists(archive_dir):
867        if logger is not None:
868            logger.info("creating %s", archive_dir)
869        if not dry_run:
870            os.makedirs(archive_dir)
871
872    # creating the tarball
873    if logger is not None:
874        logger.info('Creating tar archive')
875
876    uid = _get_uid(owner)
877    gid = _get_gid(group)
878
879    def _set_uid_gid(tarinfo):
880        if gid is not None:
881            tarinfo.gid = gid
882            tarinfo.gname = group
883        if uid is not None:
884            tarinfo.uid = uid
885            tarinfo.uname = owner
886        return tarinfo
887
888    if not dry_run:
889        tar = tarfile.open(archive_name, 'w|%s' % tar_compression)
890        try:
891            tar.add(base_dir, filter=_set_uid_gid)
892        finally:
893            tar.close()
894
895    return archive_name
896
897def _make_zipfile(base_name, base_dir, verbose=0, dry_run=0, logger=None):
898    """Create a zip file from all the files under 'base_dir'.
899
900    The output zip file will be named 'base_name' + ".zip".  Returns the
901    name of the output zip file.
902    """
903    import zipfile  # late import for breaking circular dependency
904
905    zip_filename = base_name + ".zip"
906    archive_dir = os.path.dirname(base_name)
907
908    if archive_dir and not os.path.exists(archive_dir):
909        if logger is not None:
910            logger.info("creating %s", archive_dir)
911        if not dry_run:
912            os.makedirs(archive_dir)
913
914    if logger is not None:
915        logger.info("creating '%s' and adding '%s' to it",
916                    zip_filename, base_dir)
917
918    if not dry_run:
919        with zipfile.ZipFile(zip_filename, "w",
920                             compression=zipfile.ZIP_DEFLATED) as zf:
921            path = os.path.normpath(base_dir)
922            if path != os.curdir:
923                zf.write(path, path)
924                if logger is not None:
925                    logger.info("adding '%s'", path)
926            for dirpath, dirnames, filenames in os.walk(base_dir):
927                for name in sorted(dirnames):
928                    path = os.path.normpath(os.path.join(dirpath, name))
929                    zf.write(path, path)
930                    if logger is not None:
931                        logger.info("adding '%s'", path)
932                for name in filenames:
933                    path = os.path.normpath(os.path.join(dirpath, name))
934                    if os.path.isfile(path):
935                        zf.write(path, path)
936                        if logger is not None:
937                            logger.info("adding '%s'", path)
938
939    return zip_filename
940
941_ARCHIVE_FORMATS = {
942    'tar':   (_make_tarball, [('compress', None)], "uncompressed tar file"),
943}
944
945if _ZLIB_SUPPORTED:
946    _ARCHIVE_FORMATS['gztar'] = (_make_tarball, [('compress', 'gzip')],
947                                "gzip'ed tar-file")
948    _ARCHIVE_FORMATS['zip'] = (_make_zipfile, [], "ZIP file")
949
950if _BZ2_SUPPORTED:
951    _ARCHIVE_FORMATS['bztar'] = (_make_tarball, [('compress', 'bzip2')],
952                                "bzip2'ed tar-file")
953
954if _LZMA_SUPPORTED:
955    _ARCHIVE_FORMATS['xztar'] = (_make_tarball, [('compress', 'xz')],
956                                "xz'ed tar-file")
957
958def get_archive_formats():
959    """Returns a list of supported formats for archiving and unarchiving.
960
961    Each element of the returned sequence is a tuple (name, description)
962    """
963    formats = [(name, registry[2]) for name, registry in
964               _ARCHIVE_FORMATS.items()]
965    formats.sort()
966    return formats
967
968def register_archive_format(name, function, extra_args=None, description=''):
969    """Registers an archive format.
970
971    name is the name of the format. function is the callable that will be
972    used to create archives. If provided, extra_args is a sequence of
973    (name, value) tuples that will be passed as arguments to the callable.
974    description can be provided to describe the format, and will be returned
975    by the get_archive_formats() function.
976    """
977    if extra_args is None:
978        extra_args = []
979    if not callable(function):
980        raise TypeError('The %s object is not callable' % function)
981    if not isinstance(extra_args, (tuple, list)):
982        raise TypeError('extra_args needs to be a sequence')
983    for element in extra_args:
984        if not isinstance(element, (tuple, list)) or len(element) !=2:
985            raise TypeError('extra_args elements are : (arg_name, value)')
986
987    _ARCHIVE_FORMATS[name] = (function, extra_args, description)
988
989def unregister_archive_format(name):
990    del _ARCHIVE_FORMATS[name]
991
992def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0,
993                 dry_run=0, owner=None, group=None, logger=None):
994    """Create an archive file (eg. zip or tar).
995
996    'base_name' is the name of the file to create, minus any format-specific
997    extension; 'format' is the archive format: one of "zip", "tar", "gztar",
998    "bztar", or "xztar".  Or any other registered format.
999
1000    'root_dir' is a directory that will be the root directory of the
1001    archive; ie. we typically chdir into 'root_dir' before creating the
1002    archive.  'base_dir' is the directory where we start archiving from;
1003    ie. 'base_dir' will be the common prefix of all files and
1004    directories in the archive.  'root_dir' and 'base_dir' both default
1005    to the current directory.  Returns the name of the archive file.
1006
1007    'owner' and 'group' are used when creating a tar archive. By default,
1008    uses the current owner and group.
1009    """
1010    sys.audit("shutil.make_archive", base_name, format, root_dir, base_dir)
1011    save_cwd = os.getcwd()
1012    if root_dir is not None:
1013        if logger is not None:
1014            logger.debug("changing into '%s'", root_dir)
1015        base_name = os.path.abspath(base_name)
1016        if not dry_run:
1017            os.chdir(root_dir)
1018
1019    if base_dir is None:
1020        base_dir = os.curdir
1021
1022    kwargs = {'dry_run': dry_run, 'logger': logger}
1023
1024    try:
1025        format_info = _ARCHIVE_FORMATS[format]
1026    except KeyError:
1027        raise ValueError("unknown archive format '%s'" % format) from None
1028
1029    func = format_info[0]
1030    for arg, val in format_info[1]:
1031        kwargs[arg] = val
1032
1033    if format != 'zip':
1034        kwargs['owner'] = owner
1035        kwargs['group'] = group
1036
1037    try:
1038        filename = func(base_name, base_dir, **kwargs)
1039    finally:
1040        if root_dir is not None:
1041            if logger is not None:
1042                logger.debug("changing back to '%s'", save_cwd)
1043            os.chdir(save_cwd)
1044
1045    return filename
1046
1047
1048def get_unpack_formats():
1049    """Returns a list of supported formats for unpacking.
1050
1051    Each element of the returned sequence is a tuple
1052    (name, extensions, description)
1053    """
1054    formats = [(name, info[0], info[3]) for name, info in
1055               _UNPACK_FORMATS.items()]
1056    formats.sort()
1057    return formats
1058
1059def _check_unpack_options(extensions, function, extra_args):
1060    """Checks what gets registered as an unpacker."""
1061    # first make sure no other unpacker is registered for this extension
1062    existing_extensions = {}
1063    for name, info in _UNPACK_FORMATS.items():
1064        for ext in info[0]:
1065            existing_extensions[ext] = name
1066
1067    for extension in extensions:
1068        if extension in existing_extensions:
1069            msg = '%s is already registered for "%s"'
1070            raise RegistryError(msg % (extension,
1071                                       existing_extensions[extension]))
1072
1073    if not callable(function):
1074        raise TypeError('The registered function must be a callable')
1075
1076
1077def register_unpack_format(name, extensions, function, extra_args=None,
1078                           description=''):
1079    """Registers an unpack format.
1080
1081    `name` is the name of the format. `extensions` is a list of extensions
1082    corresponding to the format.
1083
1084    `function` is the callable that will be
1085    used to unpack archives. The callable will receive archives to unpack.
1086    If it's unable to handle an archive, it needs to raise a ReadError
1087    exception.
1088
1089    If provided, `extra_args` is a sequence of
1090    (name, value) tuples that will be passed as arguments to the callable.
1091    description can be provided to describe the format, and will be returned
1092    by the get_unpack_formats() function.
1093    """
1094    if extra_args is None:
1095        extra_args = []
1096    _check_unpack_options(extensions, function, extra_args)
1097    _UNPACK_FORMATS[name] = extensions, function, extra_args, description
1098
1099def unregister_unpack_format(name):
1100    """Removes the pack format from the registry."""
1101    del _UNPACK_FORMATS[name]
1102
1103def _ensure_directory(path):
1104    """Ensure that the parent directory of `path` exists"""
1105    dirname = os.path.dirname(path)
1106    if not os.path.isdir(dirname):
1107        os.makedirs(dirname)
1108
1109def _unpack_zipfile(filename, extract_dir):
1110    """Unpack zip `filename` to `extract_dir`
1111    """
1112    import zipfile  # late import for breaking circular dependency
1113
1114    if not zipfile.is_zipfile(filename):
1115        raise ReadError("%s is not a zip file" % filename)
1116
1117    zip = zipfile.ZipFile(filename)
1118    try:
1119        for info in zip.infolist():
1120            name = info.filename
1121
1122            # don't extract absolute paths or ones with .. in them
1123            if name.startswith('/') or '..' in name:
1124                continue
1125
1126            target = os.path.join(extract_dir, *name.split('/'))
1127            if not target:
1128                continue
1129
1130            _ensure_directory(target)
1131            if not name.endswith('/'):
1132                # file
1133                data = zip.read(info.filename)
1134                f = open(target, 'wb')
1135                try:
1136                    f.write(data)
1137                finally:
1138                    f.close()
1139                    del data
1140    finally:
1141        zip.close()
1142
1143def _unpack_tarfile(filename, extract_dir):
1144    """Unpack tar/tar.gz/tar.bz2/tar.xz `filename` to `extract_dir`
1145    """
1146    import tarfile  # late import for breaking circular dependency
1147    try:
1148        tarobj = tarfile.open(filename)
1149    except tarfile.TarError:
1150        raise ReadError(
1151            "%s is not a compressed or uncompressed tar file" % filename)
1152    try:
1153        tarobj.extractall(extract_dir)
1154    finally:
1155        tarobj.close()
1156
1157_UNPACK_FORMATS = {
1158    'tar':   (['.tar'], _unpack_tarfile, [], "uncompressed tar file"),
1159    'zip':   (['.zip'], _unpack_zipfile, [], "ZIP file"),
1160}
1161
1162if _ZLIB_SUPPORTED:
1163    _UNPACK_FORMATS['gztar'] = (['.tar.gz', '.tgz'], _unpack_tarfile, [],
1164                                "gzip'ed tar-file")
1165
1166if _BZ2_SUPPORTED:
1167    _UNPACK_FORMATS['bztar'] = (['.tar.bz2', '.tbz2'], _unpack_tarfile, [],
1168                                "bzip2'ed tar-file")
1169
1170if _LZMA_SUPPORTED:
1171    _UNPACK_FORMATS['xztar'] = (['.tar.xz', '.txz'], _unpack_tarfile, [],
1172                                "xz'ed tar-file")
1173
1174def _find_unpack_format(filename):
1175    for name, info in _UNPACK_FORMATS.items():
1176        for extension in info[0]:
1177            if filename.endswith(extension):
1178                return name
1179    return None
1180
1181def unpack_archive(filename, extract_dir=None, format=None):
1182    """Unpack an archive.
1183
1184    `filename` is the name of the archive.
1185
1186    `extract_dir` is the name of the target directory, where the archive
1187    is unpacked. If not provided, the current working directory is used.
1188
1189    `format` is the archive format: one of "zip", "tar", "gztar", "bztar",
1190    or "xztar".  Or any other registered format.  If not provided,
1191    unpack_archive will use the filename extension and see if an unpacker
1192    was registered for that extension.
1193
1194    In case none is found, a ValueError is raised.
1195    """
1196    if extract_dir is None:
1197        extract_dir = os.getcwd()
1198
1199    extract_dir = os.fspath(extract_dir)
1200    filename = os.fspath(filename)
1201
1202    if format is not None:
1203        try:
1204            format_info = _UNPACK_FORMATS[format]
1205        except KeyError:
1206            raise ValueError("Unknown unpack format '{0}'".format(format)) from None
1207
1208        func = format_info[1]
1209        func(filename, extract_dir, **dict(format_info[2]))
1210    else:
1211        # we need to look at the registered unpackers supported extensions
1212        format = _find_unpack_format(filename)
1213        if format is None:
1214            raise ReadError("Unknown archive format '{0}'".format(filename))
1215
1216        func = _UNPACK_FORMATS[format][1]
1217        kwargs = dict(_UNPACK_FORMATS[format][2])
1218        func(filename, extract_dir, **kwargs)
1219
1220
1221if hasattr(os, 'statvfs'):
1222
1223    __all__.append('disk_usage')
1224    _ntuple_diskusage = collections.namedtuple('usage', 'total used free')
1225    _ntuple_diskusage.total.__doc__ = 'Total space in bytes'
1226    _ntuple_diskusage.used.__doc__ = 'Used space in bytes'
1227    _ntuple_diskusage.free.__doc__ = 'Free space in bytes'
1228
1229    def disk_usage(path):
1230        """Return disk usage statistics about the given path.
1231
1232        Returned value is a named tuple with attributes 'total', 'used' and
1233        'free', which are the amount of total, used and free space, in bytes.
1234        """
1235        st = os.statvfs(path)
1236        free = st.f_bavail * st.f_frsize
1237        total = st.f_blocks * st.f_frsize
1238        used = (st.f_blocks - st.f_bfree) * st.f_frsize
1239        return _ntuple_diskusage(total, used, free)
1240
1241elif _WINDOWS:
1242
1243    __all__.append('disk_usage')
1244    _ntuple_diskusage = collections.namedtuple('usage', 'total used free')
1245
1246    def disk_usage(path):
1247        """Return disk usage statistics about the given path.
1248
1249        Returned values is a named tuple with attributes 'total', 'used' and
1250        'free', which are the amount of total, used and free space, in bytes.
1251        """
1252        total, free = nt._getdiskusage(path)
1253        used = total - free
1254        return _ntuple_diskusage(total, used, free)
1255
1256
1257def chown(path, user=None, group=None):
1258    """Change owner user and group of the given path.
1259
1260    user and group can be the uid/gid or the user/group names, and in that case,
1261    they are converted to their respective uid/gid.
1262    """
1263
1264    if user is None and group is None:
1265        raise ValueError("user and/or group must be set")
1266
1267    _user = user
1268    _group = group
1269
1270    # -1 means don't change it
1271    if user is None:
1272        _user = -1
1273    # user can either be an int (the uid) or a string (the system username)
1274    elif isinstance(user, str):
1275        _user = _get_uid(user)
1276        if _user is None:
1277            raise LookupError("no such user: {!r}".format(user))
1278
1279    if group is None:
1280        _group = -1
1281    elif not isinstance(group, int):
1282        _group = _get_gid(group)
1283        if _group is None:
1284            raise LookupError("no such group: {!r}".format(group))
1285
1286    os.chown(path, _user, _group)
1287
1288def get_terminal_size(fallback=(80, 24)):
1289    """Get the size of the terminal window.
1290
1291    For each of the two dimensions, the environment variable, COLUMNS
1292    and LINES respectively, is checked. If the variable is defined and
1293    the value is a positive integer, it is used.
1294
1295    When COLUMNS or LINES is not defined, which is the common case,
1296    the terminal connected to sys.__stdout__ is queried
1297    by invoking os.get_terminal_size.
1298
1299    If the terminal size cannot be successfully queried, either because
1300    the system doesn't support querying, or because we are not
1301    connected to a terminal, the value given in fallback parameter
1302    is used. Fallback defaults to (80, 24) which is the default
1303    size used by many terminal emulators.
1304
1305    The value returned is a named tuple of type os.terminal_size.
1306    """
1307    # columns, lines are the working values
1308    try:
1309        columns = int(os.environ['COLUMNS'])
1310    except (KeyError, ValueError):
1311        columns = 0
1312
1313    try:
1314        lines = int(os.environ['LINES'])
1315    except (KeyError, ValueError):
1316        lines = 0
1317
1318    # only query if necessary
1319    if columns <= 0 or lines <= 0:
1320        try:
1321            size = os.get_terminal_size(sys.__stdout__.fileno())
1322        except (AttributeError, ValueError, OSError):
1323            # stdout is None, closed, detached, or not a terminal, or
1324            # os.get_terminal_size() is unsupported
1325            size = os.terminal_size(fallback)
1326        if columns <= 0:
1327            columns = size.columns
1328        if lines <= 0:
1329            lines = size.lines
1330
1331    return os.terminal_size((columns, lines))
1332
1333
1334# Check that a given file can be accessed with the correct mode.
1335# Additionally check that `file` is not a directory, as on Windows
1336# directories pass the os.access check.
1337def _access_check(fn, mode):
1338    return (os.path.exists(fn) and os.access(fn, mode)
1339            and not os.path.isdir(fn))
1340
1341
1342def which(cmd, mode=os.F_OK | os.X_OK, path=None):
1343    """Given a command, mode, and a PATH string, return the path which
1344    conforms to the given mode on the PATH, or None if there is no such
1345    file.
1346
1347    `mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result
1348    of os.environ.get("PATH"), or can be overridden with a custom search
1349    path.
1350
1351    """
1352    # If we're given a path with a directory part, look it up directly rather
1353    # than referring to PATH directories. This includes checking relative to the
1354    # current directory, e.g. ./script
1355    if os.path.dirname(cmd):
1356        if _access_check(cmd, mode):
1357            return cmd
1358        return None
1359
1360    use_bytes = isinstance(cmd, bytes)
1361
1362    if path is None:
1363        path = os.environ.get("PATH", None)
1364        if path is None:
1365            try:
1366                path = os.confstr("CS_PATH")
1367            except (AttributeError, ValueError):
1368                # os.confstr() or CS_PATH is not available
1369                path = os.defpath
1370        # bpo-35755: Don't use os.defpath if the PATH environment variable is
1371        # set to an empty string
1372
1373    # PATH='' doesn't match, whereas PATH=':' looks in the current directory
1374    if not path:
1375        return None
1376
1377    if use_bytes:
1378        path = os.fsencode(path)
1379        path = path.split(os.fsencode(os.pathsep))
1380    else:
1381        path = os.fsdecode(path)
1382        path = path.split(os.pathsep)
1383
1384    if sys.platform == "win32":
1385        # The current directory takes precedence on Windows.
1386        curdir = os.curdir
1387        if use_bytes:
1388            curdir = os.fsencode(curdir)
1389        if curdir not in path:
1390            path.insert(0, curdir)
1391
1392        # PATHEXT is necessary to check on Windows.
1393        pathext = os.environ.get("PATHEXT", "").split(os.pathsep)
1394        if use_bytes:
1395            pathext = [os.fsencode(ext) for ext in pathext]
1396        # See if the given file matches any of the expected path extensions.
1397        # This will allow us to short circuit when given "python.exe".
1398        # If it does match, only test that one, otherwise we have to try
1399        # others.
1400        if any(cmd.lower().endswith(ext.lower()) for ext in pathext):
1401            files = [cmd]
1402        else:
1403            files = [cmd + ext for ext in pathext]
1404    else:
1405        # On other platforms you don't have things like PATHEXT to tell you
1406        # what file suffixes are executable, so just pass on cmd as-is.
1407        files = [cmd]
1408
1409    seen = set()
1410    for dir in path:
1411        normdir = os.path.normcase(dir)
1412        if not normdir in seen:
1413            seen.add(normdir)
1414            for thefile in files:
1415                name = os.path.join(dir, thefile)
1416                if _access_check(name, mode):
1417                    return name
1418    return None
1419