shutil.py 7.02 KB
Newer Older
1
"""Utility functions for copying files and directory trees.
2

3
XXX The functions here don't copy the resource fork or other metadata on Mac.
4 5

"""
Guido van Rossum's avatar
Guido van Rossum committed
6

Guido van Rossum's avatar
Guido van Rossum committed
7
import os
8
import sys
9
import stat
10
from os.path import abspath
Guido van Rossum's avatar
Guido van Rossum committed
11

12
__all__ = ["copyfileobj","copyfile","copymode","copystat","copy","copy2",
13
           "copytree","move","rmtree","Error"]
14

15
class Error(EnvironmentError):
16
    pass
Guido van Rossum's avatar
Guido van Rossum committed
17

18 19 20 21 22 23 24 25
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)

26 27 28
def _samefile(src, dst):
    # Macintosh, Unix.
    if hasattr(os.path,'samefile'):
29 30 31 32
        try:
            return os.path.samefile(src, dst)
        except OSError:
            return False
33 34 35 36

    # All other platforms: check for same pathname.
    return (os.path.normcase(os.path.abspath(src)) ==
            os.path.normcase(os.path.abspath(dst)))
Tim Peters's avatar
Tim Peters committed
37

Guido van Rossum's avatar
Guido van Rossum committed
38
def copyfile(src, dst):
39
    """Copy data from src to dst"""
40
    if _samefile(src, dst):
41
        raise Error("`%s` and `%s` are the same file" % (src, dst))
42

43 44 45
    fsrc = None
    fdst = None
    try:
46 47
        fsrc = open(src, 'rb')
        fdst = open(dst, 'wb')
48
        copyfileobj(fsrc, fdst)
49
    finally:
50 51 52 53
        if fdst:
            fdst.close()
        if fsrc:
            fsrc.close()
Guido van Rossum's avatar
Guido van Rossum committed
54 55

def copymode(src, dst):
56
    """Copy mode bits from src to dst"""
57 58
    if hasattr(os, 'chmod'):
        st = os.stat(src)
59
        mode = stat.S_IMODE(st.st_mode)
60
        os.chmod(dst, mode)
Guido van Rossum's avatar
Guido van Rossum committed
61 62

def copystat(src, dst):
63
    """Copy all stat info (mode bits, atime, mtime, flags) from src to dst"""
64
    st = os.stat(src)
65
    mode = stat.S_IMODE(st.st_mode)
66
    if hasattr(os, 'utime'):
67
        os.utime(dst, (st.st_atime, st.st_mtime))
68 69
    if hasattr(os, 'chmod'):
        os.chmod(dst, mode)
70 71
    if hasattr(os, 'chflags') and hasattr(st, 'st_flags'):
        os.chflags(dst, st.st_flags)
72

Guido van Rossum's avatar
Guido van Rossum committed
73 74

def copy(src, dst):
75
    """Copy data and mode bits ("cp src dst").
Tim Peters's avatar
Tim Peters committed
76

77 78 79
    The destination may be a directory.

    """
80
    if os.path.isdir(dst):
81
        dst = os.path.join(dst, os.path.basename(src))
82 83
    copyfile(src, dst)
    copymode(src, dst)
Guido van Rossum's avatar
Guido van Rossum committed
84 85

def copy2(src, dst):
86 87 88 89 90
    """Copy data and all stat info ("cp -p src dst").

    The destination may be a directory.

    """
91
    if os.path.isdir(dst):
92
        dst = os.path.join(dst, os.path.basename(src))
93 94
    copyfile(src, dst)
    copystat(src, dst)
Guido van Rossum's avatar
Guido van Rossum committed
95

96

97
def copytree(src, dst, symlinks=False):
98 99 100
    """Recursively copy a directory tree using copy2().

    The destination directory must not already exist.
101
    If exception(s) occur, an Error is raised with a list of reasons.
102 103 104 105 106 107 108 109 110

    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.

    """
111
    names = os.listdir(src)
Johannes Gijsbers's avatar
Johannes Gijsbers committed
112
    os.makedirs(dst)
113
    errors = []
114
    for name in names:
115 116 117 118 119 120 121
        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):
122
                copytree(srcname, dstname, symlinks)
123 124 125
            else:
                copy2(srcname, dstname)
            # XXX What about devices, sockets etc.?
126
        except (IOError, os.error) as why:
127
            errors.append((srcname, dstname, str(why)))
128 129
        # catch the Error from the recursive copytree so that we can
        # continue with other files
130
        except Error as err:
131
            errors.extend(err.args[0])
132 133 134 135 136
    try:
        copystat(src, dst)
    except WindowsError:
        # can't copy file access times on Windows
        pass
137
    except OSError as why:
138
        errors.extend((src, dst, str(why)))
139
    if errors:
140
        raise Error(errors)
141

142
def rmtree(path, ignore_errors=False, onerror=None):
143 144
    """Recursively delete a directory tree.

145 146 147 148 149 150 151
    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.

152
    """
153 154
    if ignore_errors:
        def onerror(*args):
155
            pass
156 157 158
    elif onerror is None:
        def onerror(*args):
            raise
159 160 161 162 163 164 165 166
    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
167 168 169
    names = []
    try:
        names = os.listdir(path)
170
    except os.error as err:
171 172 173 174 175 176 177 178 179
        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)
180
        else:
181 182
            try:
                os.remove(fullname)
183
            except os.error as err:
184 185 186 187 188
                onerror(os.remove, fullname, sys.exc_info())
    try:
        os.rmdir(path)
    except os.error:
        onerror(os.rmdir, path, sys.exc_info())
189

190 191 192 193 194 195

def _basename(path):
    # A basename() variant which first strips the trailing slash, if present.
    # Thus we always get the last component of the path, even for directories.
    return os.path.basename(path.rstrip(os.path.sep))

196
def move(src, dst):
197 198 199 200 201 202
    """Recursively move a file or directory to another location. This is
    similar to the Unix "mv" command.

    If the destination is a directory or a symlink to a directory, the source
    is moved inside the directory. The destination path must not already
    exist.
203

204 205 206 207 208
    If the destination already exists but is not a directory, it may be
    overwritten depending on os.rename() semantics.

    If the destination is on our current filesystem, then rename() is used.
    Otherwise, src is copied to the destination and then removed.
209 210 211 212
    A lot more could be done here...  A look at a mv.c shows a lot of
    the issues this implementation glosses over.

    """
213 214 215 216 217
    real_dst = dst
    if os.path.isdir(dst):
        real_dst = os.path.join(dst, _basename(src))
        if os.path.exists(real_dst):
            raise Error("Destination path '%s' already exists" % real_dst)
218
    try:
219
        os.rename(src, real_dst)
220 221
    except OSError:
        if os.path.isdir(src):
222
            if destinsrc(src, dst):
223
                raise Error("Cannot move a directory '%s' into itself '%s'." % (src, dst))
224
            copytree(src, real_dst, symlinks=True)
225 226
            rmtree(src)
        else:
227
            copy2(src, real_dst)
228
            os.unlink(src)
229 230 231

def destinsrc(src, dst):
    return abspath(dst).startswith(abspath(src))