Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(3)

Delta Between Two Patch Sets: Lib/shutil.py

Issue 21697: shutil.copytree() handles symbolic directory incorrectly
Left Patch Set: Created 5 years, 8 months ago
Right Patch Set: Created 4 years, 7 months ago
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
Left: Side by side diff | Download
Right: Side by side diff | Download
« no previous file with change/comment | « no previous file | Lib/test/test_shutil.py » ('j') | no next file with change/comment »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
1 """Utility functions for copying and archiving files and directory trees. 1 """Utility functions for copying and archiving files and directory trees.
2 2
3 XXX The functions here don't copy the resource fork or other metadata on Mac. 3 XXX The functions here don't copy the resource fork or other metadata on Mac.
4 4
5 """ 5 """
6 6
7 import os 7 import os
8 import sys 8 import sys
9 import stat 9 import stat
10 from os.path import abspath
11 import fnmatch 10 import fnmatch
12 import collections 11 import collections
13 import errno 12 import errno
14 import tarfile 13 import tarfile
15 14
16 try: 15 try:
17 import bz2 16 import bz2
18 del bz2 17 del bz2
19 _BZ2_SUPPORTED = True 18 _BZ2_SUPPORTED = True
20 except ImportError: 19 except ImportError:
21 _BZ2_SUPPORTED = False 20 _BZ2_SUPPORTED = False
22 21
23 try: 22 try:
23 import lzma
24 del lzma
25 _LZMA_SUPPORTED = True
26 except ImportError:
27 _LZMA_SUPPORTED = False
28
29 try:
24 from pwd import getpwnam 30 from pwd import getpwnam
25 except ImportError: 31 except ImportError:
26 getpwnam = None 32 getpwnam = None
27 33
28 try: 34 try:
29 from grp import getgrnam 35 from grp import getgrnam
30 except ImportError: 36 except ImportError:
31 getgrnam = None 37 getgrnam = None
32 38
33 __all__ = ["copyfileobj", "copyfile", "copymode", "copystat", "copy", "copy2", 39 __all__ = ["copyfileobj", "copyfile", "copymode", "copystat", "copy", "copy2",
34 "copytree", "move", "rmtree", "Error", "SpecialFileError", 40 "copytree", "move", "rmtree", "Error", "SpecialFileError",
35 "ExecError", "make_archive", "get_archive_formats", 41 "ExecError", "make_archive", "get_archive_formats",
36 "register_archive_format", "unregister_archive_format", 42 "register_archive_format", "unregister_archive_format",
37 "get_unpack_formats", "register_unpack_format", 43 "get_unpack_formats", "register_unpack_format",
38 "unregister_unpack_format", "unpack_archive", 44 "unregister_unpack_format", "unpack_archive",
39 "ignore_patterns", "chown", "which"] 45 "ignore_patterns", "chown", "which", "get_terminal_size",
46 "SameFileError"]
40 # disk_usage is added later, if available on the platform 47 # disk_usage is added later, if available on the platform
41 48
42 class Error(OSError): 49 class Error(OSError):
43 pass 50 pass
44 51
45 class SameFileError(Error): 52 class SameFileError(Error):
46 """Raised when source and destination are the same file.""" 53 """Raised when source and destination are the same file."""
47 54
48 class SpecialFileError(OSError): 55 class SpecialFileError(OSError):
49 """Raised when trying to do a kind of operation (e.g. copying) which is 56 """Raised when trying to do a kind of operation (e.g. copying) which is
(...skipping 264 matching lines...) Expand 10 before | Expand all | Expand 10 after
314 # code with a custom `copy_function` may rely on copytree 321 # code with a custom `copy_function` may rely on copytree
315 # doing the right thing. 322 # doing the right thing.
316 os.symlink(linkto, dstname) 323 os.symlink(linkto, dstname)
317 copystat(srcname, dstname, follow_symlinks=not symlinks) 324 copystat(srcname, dstname, follow_symlinks=not symlinks)
318 else: 325 else:
319 # ignore dangling symlink if the flag is on 326 # ignore dangling symlink if the flag is on
320 if not os.path.exists(linkto) and ignore_dangling_symlinks: 327 if not os.path.exists(linkto) and ignore_dangling_symlinks:
321 continue 328 continue
322 # otherwise let the copy occurs. copy2 will raise an error 329 # otherwise let the copy occurs. copy2 will raise an error
323 if os.path.isdir(srcname): 330 if os.path.isdir(srcname):
324 copytree(srcname, dstname, symlinks, ignore, copy_functi on) 331 copytree(srcname, dstname, symlinks, ignore,
332 copy_function)
325 else: 333 else:
326 copy_function(srcname, dstname) 334 copy_function(srcname, dstname)
327 elif os.path.isdir(srcname): 335 elif os.path.isdir(srcname):
328 copytree(srcname, dstname, symlinks, ignore, copy_function) 336 copytree(srcname, dstname, symlinks, ignore, copy_function)
329 else: 337 else:
330 # Will raise a SpecialFileError for unsupported file types 338 # Will raise a SpecialFileError for unsupported file types
331 copy_function(srcname, dstname) 339 copy_function(srcname, dstname)
332 # catch the Error from the recursive copytree so that we can 340 # catch the Error from the recursive copytree so that we can
333 # continue with other files 341 # continue with other files
334 except Error as err: 342 except Error as err:
335 errors.extend(err.args[0]) 343 errors.extend(err.args[0])
336 except OSError as why: 344 except OSError as why:
337 errors.append((srcname, dstname, str(why))) 345 errors.append((srcname, dstname, str(why)))
338 try: 346 try:
339 copystat(src, dst) 347 copystat(src, dst)
340 except OSError as why: 348 except OSError as why:
341 # Copying file access times may fail on Windows 349 # Copying file access times may fail on Windows
342 if why.winerror is None: 350 if getattr(why, 'winerror', None) is None:
343 errors.append((src, dst, str(why))) 351 errors.append((src, dst, str(why)))
344 if errors: 352 if errors:
345 raise Error(errors) 353 raise Error(errors)
346 return dst 354 return dst
347 355
348 # version vulnerable to race conditions 356 # version vulnerable to race conditions
349 def _rmtree_unsafe(path, onerror): 357 def _rmtree_unsafe(path, onerror):
350 try: 358 try:
351 if os.path.islink(path): 359 if os.path.islink(path):
352 # symlinks to directories are forbidden, see bug #1669 360 # symlinks to directories are forbidden, see bug #1669
(...skipping 186 matching lines...) Expand 10 before | Expand all | Expand 10 after
539 " '%s'." % (src, dst)) 547 " '%s'." % (src, dst))
540 copytree(src, real_dst, copy_function=copy_function, 548 copytree(src, real_dst, copy_function=copy_function,
541 symlinks=True) 549 symlinks=True)
542 rmtree(src) 550 rmtree(src)
543 else: 551 else:
544 copy_function(src, real_dst) 552 copy_function(src, real_dst)
545 os.unlink(src) 553 os.unlink(src)
546 return real_dst 554 return real_dst
547 555
548 def _destinsrc(src, dst): 556 def _destinsrc(src, dst):
549 src = abspath(src) 557 src = os.path.abspath(src)
550 dst = abspath(dst) 558 dst = os.path.abspath(dst)
551 if not src.endswith(os.path.sep): 559 if not src.endswith(os.path.sep):
552 src += os.path.sep 560 src += os.path.sep
553 if not dst.endswith(os.path.sep): 561 if not dst.endswith(os.path.sep):
554 dst += os.path.sep 562 dst += os.path.sep
555 return dst.startswith(src) 563 return dst.startswith(src)
556 564
557 def _get_gid(name): 565 def _get_gid(name):
558 """Returns a gid, given a group name.""" 566 """Returns a gid, given a group name."""
559 if getgrnam is None or name is None: 567 if getgrnam is None or name is None:
560 return None 568 return None
(...skipping 15 matching lines...) Expand all
576 result = None 584 result = None
577 if result is not None: 585 if result is not None:
578 return result[2] 586 return result[2]
579 return None 587 return None
580 588
581 def _make_tarball(base_name, base_dir, compress="gzip", verbose=0, dry_run=0, 589 def _make_tarball(base_name, base_dir, compress="gzip", verbose=0, dry_run=0,
582 owner=None, group=None, logger=None): 590 owner=None, group=None, logger=None):
583 """Create a (possibly compressed) tar file from all the files under 591 """Create a (possibly compressed) tar file from all the files under
584 'base_dir'. 592 'base_dir'.
585 593
586 'compress' must be "gzip" (the default), "bzip2", or None. 594 'compress' must be "gzip" (the default), "bzip2", "xz", or None.
587 595
588 'owner' and 'group' can be used to define an owner and a group for the 596 'owner' and 'group' can be used to define an owner and a group for the
589 archive that is being built. If not provided, the current owner and group 597 archive that is being built. If not provided, the current owner and group
590 will be used. 598 will be used.
591 599
592 The output tar file will be named 'base_name' + ".tar", possibly plus 600 The output tar file will be named 'base_name' + ".tar", possibly plus
593 the appropriate compression extension (".gz", or ".bz2"). 601 the appropriate compression extension (".gz", ".bz2", or ".xz").
594 602
595 Returns the output filename. 603 Returns the output filename.
596 """ 604 """
597 tar_compression = {'gzip': 'gz', None: ''} 605 tar_compression = {'gzip': 'gz', None: ''}
598 compress_ext = {'gzip': '.gz'} 606 compress_ext = {'gzip': '.gz'}
599 607
600 if _BZ2_SUPPORTED: 608 if _BZ2_SUPPORTED:
601 tar_compression['bzip2'] = 'bz2' 609 tar_compression['bzip2'] = 'bz2'
602 compress_ext['bzip2'] = '.bz2' 610 compress_ext['bzip2'] = '.bz2'
611
612 if _LZMA_SUPPORTED:
613 tar_compression['xz'] = 'xz'
614 compress_ext['xz'] = '.xz'
603 615
604 # flags for compression program, each element of list will be an argument 616 # flags for compression program, each element of list will be an argument
605 if compress is not None and compress not in compress_ext: 617 if compress is not None and compress not in compress_ext:
606 raise ValueError("bad value for 'compress', or compression format not " 618 raise ValueError("bad value for 'compress', or compression format not "
607 "supported : {0}".format(compress)) 619 "supported : {0}".format(compress))
608 620
609 archive_name = base_name + '.tar' + compress_ext.get(compress, '') 621 archive_name = base_name + '.tar' + compress_ext.get(compress, '')
610 archive_dir = os.path.dirname(archive_name) 622 archive_dir = os.path.dirname(archive_name)
611 623
612 if not os.path.exists(archive_dir): 624 if archive_dir and not os.path.exists(archive_dir):
613 if logger is not None: 625 if logger is not None:
614 logger.info("creating %s", archive_dir) 626 logger.info("creating %s", archive_dir)
615 if not dry_run: 627 if not dry_run:
616 os.makedirs(archive_dir) 628 os.makedirs(archive_dir)
617 629
618 # creating the tarball 630 # creating the tarball
619 if logger is not None: 631 if logger is not None:
620 logger.info('Creating tar archive') 632 logger.info('Creating tar archive')
621 633
622 uid = _get_uid(owner) 634 uid = _get_uid(owner)
(...skipping 24 matching lines...) Expand all
647 "zipfile" Python module (if available) or the InfoZIP "zip" utility 659 "zipfile" Python module (if available) or the InfoZIP "zip" utility
648 (if installed and found on the default search path). If neither tool is 660 (if installed and found on the default search path). If neither tool is
649 available, raises ExecError. Returns the name of the output zip 661 available, raises ExecError. Returns the name of the output zip
650 file. 662 file.
651 """ 663 """
652 import zipfile 664 import zipfile
653 665
654 zip_filename = base_name + ".zip" 666 zip_filename = base_name + ".zip"
655 archive_dir = os.path.dirname(base_name) 667 archive_dir = os.path.dirname(base_name)
656 668
657 if not os.path.exists(archive_dir): 669 if archive_dir and not os.path.exists(archive_dir):
658 if logger is not None: 670 if logger is not None:
659 logger.info("creating %s", archive_dir) 671 logger.info("creating %s", archive_dir)
660 if not dry_run: 672 if not dry_run:
661 os.makedirs(archive_dir) 673 os.makedirs(archive_dir)
662 674
663 if logger is not None: 675 if logger is not None:
664 logger.info("creating '%s' and adding '%s' to it", 676 logger.info("creating '%s' and adding '%s' to it",
665 zip_filename, base_dir) 677 zip_filename, base_dir)
666 678
667 if not dry_run: 679 if not dry_run:
(...skipping 11 matching lines...) Expand all
679 691
680 _ARCHIVE_FORMATS = { 692 _ARCHIVE_FORMATS = {
681 'gztar': (_make_tarball, [('compress', 'gzip')], "gzip'ed tar-file"), 693 'gztar': (_make_tarball, [('compress', 'gzip')], "gzip'ed tar-file"),
682 'tar': (_make_tarball, [('compress', None)], "uncompressed tar file"), 694 'tar': (_make_tarball, [('compress', None)], "uncompressed tar file"),
683 'zip': (_make_zipfile, [], "ZIP file") 695 'zip': (_make_zipfile, [], "ZIP file")
684 } 696 }
685 697
686 if _BZ2_SUPPORTED: 698 if _BZ2_SUPPORTED:
687 _ARCHIVE_FORMATS['bztar'] = (_make_tarball, [('compress', 'bzip2')], 699 _ARCHIVE_FORMATS['bztar'] = (_make_tarball, [('compress', 'bzip2')],
688 "bzip2'ed tar-file") 700 "bzip2'ed tar-file")
701
702 if _LZMA_SUPPORTED:
703 _ARCHIVE_FORMATS['xztar'] = (_make_tarball, [('compress', 'xz')],
704 "xz'ed tar-file")
689 705
690 def get_archive_formats(): 706 def get_archive_formats():
691 """Returns a list of supported formats for archiving and unarchiving. 707 """Returns a list of supported formats for archiving and unarchiving.
692 708
693 Each element of the returned sequence is a tuple (name, description) 709 Each element of the returned sequence is a tuple (name, description)
694 """ 710 """
695 formats = [(name, registry[2]) for name, registry in 711 formats = [(name, registry[2]) for name, registry in
696 _ARCHIVE_FORMATS.items()] 712 _ARCHIVE_FORMATS.items()]
697 formats.sort() 713 formats.sort()
698 return formats 714 return formats
(...skipping 169 matching lines...) Expand 10 before | Expand all | Expand 10 after
868 f = open(target, 'wb') 884 f = open(target, 'wb')
869 try: 885 try:
870 f.write(data) 886 f.write(data)
871 finally: 887 finally:
872 f.close() 888 f.close()
873 del data 889 del data
874 finally: 890 finally:
875 zip.close() 891 zip.close()
876 892
877 def _unpack_tarfile(filename, extract_dir): 893 def _unpack_tarfile(filename, extract_dir):
878 """Unpack tar/tar.gz/tar.bz2 `filename` to `extract_dir` 894 """Unpack tar/tar.gz/tar.bz2/tar.xz `filename` to `extract_dir`
879 """ 895 """
880 try: 896 try:
881 tarobj = tarfile.open(filename) 897 tarobj = tarfile.open(filename)
882 except tarfile.TarError: 898 except tarfile.TarError:
883 raise ReadError( 899 raise ReadError(
884 "%s is not a compressed or uncompressed tar file" % filename) 900 "%s is not a compressed or uncompressed tar file" % filename)
885 try: 901 try:
886 tarobj.extractall(extract_dir) 902 tarobj.extractall(extract_dir)
887 finally: 903 finally:
888 tarobj.close() 904 tarobj.close()
889 905
890 _UNPACK_FORMATS = { 906 _UNPACK_FORMATS = {
891 'gztar': (['.tar.gz', '.tgz'], _unpack_tarfile, [], "gzip'ed tar-file"), 907 'gztar': (['.tar.gz', '.tgz'], _unpack_tarfile, [], "gzip'ed tar-file"),
892 'tar': (['.tar'], _unpack_tarfile, [], "uncompressed tar file"), 908 'tar': (['.tar'], _unpack_tarfile, [], "uncompressed tar file"),
893 'zip': (['.zip'], _unpack_zipfile, [], "ZIP file") 909 'zip': (['.zip'], _unpack_zipfile, [], "ZIP file")
894 } 910 }
895 911
896 if _BZ2_SUPPORTED: 912 if _BZ2_SUPPORTED:
897 _UNPACK_FORMATS['bztar'] = (['.bz2'], _unpack_tarfile, [], 913 _UNPACK_FORMATS['bztar'] = (['.tar.bz2', '.tbz2'], _unpack_tarfile, [],
898 "bzip2'ed tar-file") 914 "bzip2'ed tar-file")
915
916 if _LZMA_SUPPORTED:
917 _UNPACK_FORMATS['xztar'] = (['.tar.xz', '.txz'], _unpack_tarfile, [],
918 "xz'ed tar-file")
899 919
900 def _find_unpack_format(filename): 920 def _find_unpack_format(filename):
901 for name, info in _UNPACK_FORMATS.items(): 921 for name, info in _UNPACK_FORMATS.items():
902 for extension in info[0]: 922 for extension in info[0]:
903 if filename.endswith(extension): 923 if filename.endswith(extension):
904 return name 924 return name
905 return None 925 return None
906 926
907 def unpack_archive(filename, extract_dir=None, format=None): 927 def unpack_archive(filename, extract_dir=None, format=None):
908 """Unpack an archive. 928 """Unpack an archive.
(...skipping 194 matching lines...) Expand 10 before | Expand all | Expand 10 after
1103 seen = set() 1123 seen = set()
1104 for dir in path: 1124 for dir in path:
1105 normdir = os.path.normcase(dir) 1125 normdir = os.path.normcase(dir)
1106 if not normdir in seen: 1126 if not normdir in seen:
1107 seen.add(normdir) 1127 seen.add(normdir)
1108 for thefile in files: 1128 for thefile in files:
1109 name = os.path.join(dir, thefile) 1129 name = os.path.join(dir, thefile)
1110 if _access_check(name, mode): 1130 if _access_check(name, mode):
1111 return name 1131 return name
1112 return None 1132 return None
LEFTRIGHT

RSS Feeds Recent Issues | This issue
This is Rietveld 894c83f36cb7+