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