blob: f30ba86483cd063e2122d51a014a12e4df3f45c8 [file] [log] [blame]
Tor Norbye3a2425a2013-11-04 10:16:08 -08001"""Utility functions for copying 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
10from os.path import abspath
11
12__all__ = ["copyfileobj","copyfile","copymode","copystat","copy","copy2",
13 "copytree","move","rmtree","Error"]
14
15class Error(EnvironmentError):
16 pass
17
18try:
19 WindowsError
20except NameError:
21 WindowsError = None
22
23def copyfileobj(fsrc, fdst, length=16*1024):
24 """copy data from file-like object fsrc to file-like object fdst"""
25 while 1:
26 buf = fsrc.read(length)
27 if not buf:
28 break
29 fdst.write(buf)
30
31def _samefile(src, dst):
32 # Macintosh, Unix.
33 if hasattr(os.path,'samefile'):
34 try:
35 return os.path.samefile(src, dst)
36 except OSError:
37 return False
38
39 # All other platforms: check for same pathname.
40 return (os.path.normcase(os.path.abspath(src)) ==
41 os.path.normcase(os.path.abspath(dst)))
42
43def copyfile(src, dst):
44 """Copy data from src to dst"""
45 if _samefile(src, dst):
46 raise Error, "`%s` and `%s` are the same file" % (src, dst)
47
48 fsrc = None
49 fdst = None
50 try:
51 fsrc = open(src, 'rb')
52 fdst = open(dst, 'wb')
53 copyfileobj(fsrc, fdst)
54 finally:
55 if fdst:
56 fdst.close()
57 if fsrc:
58 fsrc.close()
59
60def copymode(src, dst):
61 """Copy mode bits from src to dst"""
62 if hasattr(os, 'chmod'):
63 st = os.stat(src)
64 mode = stat.S_IMODE(st.st_mode)
65 os.chmod(dst, mode)
66
67def copystat(src, dst):
68 """Copy all stat info (mode bits, atime and mtime) from src to dst"""
69 st = os.stat(src)
70 mode = stat.S_IMODE(st.st_mode)
71 if hasattr(os, 'utime'):
72 os.utime(dst, (st.st_atime, st.st_mtime))
73 if hasattr(os, 'chmod'):
74 os.chmod(dst, mode)
75
76
77def copy(src, dst):
78 """Copy data and mode bits ("cp src dst").
79
80 The destination may be a directory.
81
82 """
83 if os.path.isdir(dst):
84 dst = os.path.join(dst, os.path.basename(src))
85 copyfile(src, dst)
86 copymode(src, dst)
87
88def copy2(src, dst):
89 """Copy data and all stat info ("cp -p src dst").
90
91 The destination may be a directory.
92
93 """
94 if os.path.isdir(dst):
95 dst = os.path.join(dst, os.path.basename(src))
96 copyfile(src, dst)
97 copystat(src, dst)
98
99
100def copytree(src, dst, symlinks=False):
101 """Recursively copy a directory tree using copy2().
102
103 The destination directory must not already exist.
104 If exception(s) occur, an Error is raised with a list of reasons.
105
106 If the optional symlinks flag is true, symbolic links in the
107 source tree result in symbolic links in the destination tree; if
108 it is false, the contents of the files pointed to by symbolic
109 links are copied.
110
111 XXX Consider this example code rather than the ultimate tool.
112
113 """
114 names = os.listdir(src)
115 os.makedirs(dst)
116 errors = []
117 for name in names:
118 srcname = os.path.join(src, name)
119 dstname = os.path.join(dst, name)
120 try:
121 if symlinks and os.path.islink(srcname):
122 linkto = os.readlink(srcname)
123 os.symlink(linkto, dstname)
124 elif os.path.isdir(srcname):
125 copytree(srcname, dstname, symlinks)
126 else:
127 copy2(srcname, dstname)
128 # XXX What about devices, sockets etc.?
129 except (IOError, os.error), why:
130 errors.append((srcname, dstname, str(why)))
131 # catch the Error from the recursive copytree so that we can
132 # continue with other files
133 except Error, err:
134 errors.extend(err.args[0])
135 try:
136 copystat(src, dst)
137 except OSError, why:
138 if WindowsError is not None and isinstance(why, WindowsError):
139 # Copying file access times may fail on Windows
140 pass
141 else:
142 errors.extend((src, dst, str(why)))
143 if errors:
144 raise Error, errors
145
146def rmtree(path, ignore_errors=False, onerror=None):
147 """Recursively delete a directory tree.
148
149 If ignore_errors is set, errors are ignored; otherwise, if onerror
150 is set, it is called to handle the error with arguments (func,
151 path, exc_info) where func is os.listdir, os.remove, or os.rmdir;
152 path is the argument to that function that caused it to fail; and
153 exc_info is a tuple returned by sys.exc_info(). If ignore_errors
154 is false and onerror is None, an exception is raised.
155
156 """
157 if ignore_errors:
158 def onerror(*args):
159 pass
160 elif onerror is None:
161 def onerror(*args):
162 raise
163 names = []
164 try:
165 names = os.listdir(path)
166 except os.error, err:
167 onerror(os.listdir, path, sys.exc_info())
168 for name in names:
169 fullname = os.path.join(path, name)
170 try:
171 mode = os.lstat(fullname).st_mode
172 except os.error:
173 mode = 0
174 if stat.S_ISDIR(mode):
175 rmtree(fullname, ignore_errors, onerror)
176 else:
177 try:
178 os.remove(fullname)
179 except os.error, err:
180 onerror(os.remove, fullname, sys.exc_info())
181 try:
182 os.rmdir(path)
183 except os.error:
184 onerror(os.rmdir, path, sys.exc_info())
185
186def move(src, dst):
187 """Recursively move a file or directory to another location.
188
189 If the destination is on our current filesystem, then simply use
190 rename. Otherwise, copy src to the dst and then remove src.
191 A lot more could be done here... A look at a mv.c shows a lot of
192 the issues this implementation glosses over.
193
194 """
195
196 try:
197 os.rename(src, dst)
198 except OSError:
199 if os.path.isdir(src):
200 if destinsrc(src, dst):
201 raise Error, "Cannot move a directory '%s' into itself '%s'." % (src, dst)
202 copytree(src, dst, symlinks=True)
203 rmtree(src)
204 else:
205 copy2(src,dst)
206 os.unlink(src)
207
208def destinsrc(src, dst):
209 return abspath(dst).startswith(abspath(src))