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