| """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 |
| from os.path import abspath |
| |
| __all__ = ["copyfileobj","copyfile","copymode","copystat","copy","copy2", |
| "copytree","move","rmtree","Error"] |
| |
| class Error(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, mtime, flags) 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) |
| if hasattr(os, 'chflags') and hasattr(st, 'st_flags'): |
| os.chflags(dst, st.st_flags) |
| |
| |
| 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.makedirs(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, str(why))) |
| # catch the Error from the recursive copytree so that we can |
| # continue with other files |
| except Error, err: |
| errors.extend(err.args[0]) |
| try: |
| copystat(src, dst) |
| except WindowsError: |
| # can't copy file access times on Windows |
| pass |
| except OSError, why: |
| errors.extend((src, dst, str(why))) |
| if errors: |
| raise Error, errors |
| |
| 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 with arguments (func, |
| path, exc_info) where func is os.listdir, os.remove, or os.rmdir; |
| path is the argument to that function that caused it to fail; and |
| exc_info is a tuple returned by sys.exc_info(). If ignore_errors |
| is false and onerror is None, an exception is raised. |
| |
| """ |
| if ignore_errors: |
| def onerror(*args): |
| pass |
| elif onerror is None: |
| def onerror(*args): |
| raise |
| try: |
| if os.path.islink(path): |
| # symlinks to directories are forbidden, see bug #1669 |
| raise OSError("Cannot call rmtree on a symbolic link") |
| except OSError: |
| onerror(os.path.islink, path, sys.exc_info()) |
| # can't continue even if onerror hook returns |
| return |
| names = [] |
| try: |
| names = os.listdir(path) |
| except os.error, err: |
| onerror(os.listdir, path, sys.exc_info()) |
| for name in names: |
| fullname = os.path.join(path, name) |
| try: |
| mode = os.lstat(fullname).st_mode |
| except os.error: |
| mode = 0 |
| if stat.S_ISDIR(mode): |
| rmtree(fullname, ignore_errors, onerror) |
| else: |
| try: |
| os.remove(fullname) |
| except os.error, err: |
| onerror(os.remove, fullname, sys.exc_info()) |
| try: |
| os.rmdir(path) |
| except os.error: |
| onerror(os.rmdir, path, sys.exc_info()) |
| |
| 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)) |