mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 05:31:20 +00:00 
			
		
		
		
	 7db385eef5
			
		
	
	
		7db385eef5
		
	
	
	
	
		
			
			The shutils.rmtree() implementation uses an excessive amount of memory when deleting large directory hierarchies. Before actually deleting any files, it builds up a list of (function, filename) tuples for all the files that it is going to remove.
		
			
				
	
	
		
			187 lines
		
	
	
	
		
			5.3 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			187 lines
		
	
	
	
		
			5.3 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| """Utility functions for copying files and directory trees.
 | |
| 
 | |
| XXX The functions here don't copy the resource fork or other metadata on Mac.
 | |
| 
 | |
| """
 | |
| 
 | |
| import os
 | |
| import sys
 | |
| import stat
 | |
| import exceptions
 | |
| from os.path import abspath
 | |
| 
 | |
| __all__ = ["copyfileobj","copyfile","copymode","copystat","copy","copy2",
 | |
|            "copytree","move","rmtree","Error"]
 | |
| 
 | |
| class Error(exceptions.EnvironmentError):
 | |
|     pass
 | |
| 
 | |
| def copyfileobj(fsrc, fdst, length=16*1024):
 | |
|     """copy data from file-like object fsrc to file-like object fdst"""
 | |
|     while 1:
 | |
|         buf = fsrc.read(length)
 | |
|         if not buf:
 | |
|             break
 | |
|         fdst.write(buf)
 | |
| 
 | |
| def _samefile(src, dst):
 | |
|     # Macintosh, Unix.
 | |
|     if hasattr(os.path,'samefile'):
 | |
|         try:
 | |
|             return os.path.samefile(src, dst)
 | |
|         except OSError:
 | |
|             return False
 | |
| 
 | |
|     # All other platforms: check for same pathname.
 | |
|     return (os.path.normcase(os.path.abspath(src)) ==
 | |
|             os.path.normcase(os.path.abspath(dst)))
 | |
| 
 | |
| def copyfile(src, dst):
 | |
|     """Copy data from src to dst"""
 | |
|     if _samefile(src, dst):
 | |
|         raise Error, "`%s` and `%s` are the same file" % (src, dst)
 | |
| 
 | |
|     fsrc = None
 | |
|     fdst = None
 | |
|     try:
 | |
|         fsrc = open(src, 'rb')
 | |
|         fdst = open(dst, 'wb')
 | |
|         copyfileobj(fsrc, fdst)
 | |
|     finally:
 | |
|         if fdst:
 | |
|             fdst.close()
 | |
|         if fsrc:
 | |
|             fsrc.close()
 | |
| 
 | |
| def copymode(src, dst):
 | |
|     """Copy mode bits from src to dst"""
 | |
|     if hasattr(os, 'chmod'):
 | |
|         st = os.stat(src)
 | |
|         mode = stat.S_IMODE(st.st_mode)
 | |
|         os.chmod(dst, mode)
 | |
| 
 | |
| def copystat(src, dst):
 | |
|     """Copy all stat info (mode bits, atime and mtime) from src to dst"""
 | |
|     st = os.stat(src)
 | |
|     mode = stat.S_IMODE(st.st_mode)
 | |
|     if hasattr(os, 'utime'):
 | |
|         os.utime(dst, (st.st_atime, st.st_mtime))
 | |
|     if hasattr(os, 'chmod'):
 | |
|         os.chmod(dst, mode)
 | |
| 
 | |
| 
 | |
| def copy(src, dst):
 | |
|     """Copy data and mode bits ("cp src dst").
 | |
| 
 | |
|     The destination may be a directory.
 | |
| 
 | |
|     """
 | |
|     if os.path.isdir(dst):
 | |
|         dst = os.path.join(dst, os.path.basename(src))
 | |
|     copyfile(src, dst)
 | |
|     copymode(src, dst)
 | |
| 
 | |
| def copy2(src, dst):
 | |
|     """Copy data and all stat info ("cp -p src dst").
 | |
| 
 | |
|     The destination may be a directory.
 | |
| 
 | |
|     """
 | |
|     if os.path.isdir(dst):
 | |
|         dst = os.path.join(dst, os.path.basename(src))
 | |
|     copyfile(src, dst)
 | |
|     copystat(src, dst)
 | |
| 
 | |
| 
 | |
| def copytree(src, dst, symlinks=False):
 | |
|     """Recursively copy a directory tree using copy2().
 | |
| 
 | |
|     The destination directory must not already exist.
 | |
|     If exception(s) occur, an Error is raised with a list of reasons.
 | |
| 
 | |
|     If the optional symlinks flag is true, symbolic links in the
 | |
|     source tree result in symbolic links in the destination tree; if
 | |
|     it is false, the contents of the files pointed to by symbolic
 | |
|     links are copied.
 | |
| 
 | |
|     XXX Consider this example code rather than the ultimate tool.
 | |
| 
 | |
|     """
 | |
|     names = os.listdir(src)
 | |
|     os.mkdir(dst)
 | |
|     errors = []
 | |
|     for name in names:
 | |
|         srcname = os.path.join(src, name)
 | |
|         dstname = os.path.join(dst, name)
 | |
|         try:
 | |
|             if symlinks and os.path.islink(srcname):
 | |
|                 linkto = os.readlink(srcname)
 | |
|                 os.symlink(linkto, dstname)
 | |
|             elif os.path.isdir(srcname):
 | |
|                 copytree(srcname, dstname, symlinks)
 | |
|             else:
 | |
|                 copy2(srcname, dstname)
 | |
|             # XXX What about devices, sockets etc.?
 | |
|         except (IOError, os.error), why:
 | |
|             errors.append((srcname, dstname, why))
 | |
|     if errors:
 | |
|         raise Error, errors
 | |
| 
 | |
| def _raise_err(err):
 | |
|     raise err
 | |
| 
 | |
| def rmtree(path, ignore_errors=False, onerror=None):
 | |
|     """Recursively delete a directory tree.
 | |
| 
 | |
|     If ignore_errors is set, errors are ignored; otherwise, if
 | |
|     onerror is set, it is called to handle the error; otherwise, an
 | |
|     exception is raised.
 | |
|     """
 | |
|     # This strange way of calling functions is necessary to keep the onerror
 | |
|     # argument working. Maybe sys._getframe hackery would work as well, but
 | |
|     # this is simple.
 | |
|     func = os.listdir
 | |
|     arg = path
 | |
|     try:
 | |
|         for (dirpath, dirnames, filenames) in os.walk(path, topdown=False,
 | |
|                                                       onerror=_raise_err):
 | |
|             for filename in filenames:
 | |
|                 func = os.remove
 | |
|                 arg = os.path.join(dirpath, filename)
 | |
|                 func(arg)
 | |
|             func = os.rmdir
 | |
|             arg = dirpath
 | |
|             func(arg)
 | |
|     except OSError:
 | |
|         exc = sys.exc_info()
 | |
|         if ignore_errors:
 | |
|             pass
 | |
|         elif onerror is not None:
 | |
|             onerror(func, arg, exc)
 | |
|         else:
 | |
|             raise exc[0], (exc[1][0], exc[1][1] + ' removing '+arg)
 | |
| 
 | |
| def move(src, dst):
 | |
|     """Recursively move a file or directory to another location.
 | |
| 
 | |
|     If the destination is on our current filesystem, then simply use
 | |
|     rename.  Otherwise, copy src to the dst and then remove src.
 | |
|     A lot more could be done here...  A look at a mv.c shows a lot of
 | |
|     the issues this implementation glosses over.
 | |
| 
 | |
|     """
 | |
| 
 | |
|     try:
 | |
|         os.rename(src, dst)
 | |
|     except OSError:
 | |
|         if os.path.isdir(src):
 | |
|             if destinsrc(src, dst):
 | |
|                 raise Error, "Cannot move a directory '%s' into itself '%s'." % (src, dst)
 | |
|             copytree(src, dst, symlinks=True)
 | |
|             rmtree(src)
 | |
|         else:
 | |
|             copy2(src,dst)
 | |
|             os.unlink(src)
 | |
| 
 | |
| def destinsrc(src, dst):
 | |
|     return abspath(dst).startswith(abspath(src))
 |