Merged revisions 82952,82954 via svnmerge from
[python/dscho.git] / Lib / shutil.py
blob4862ae6a13eebd05f7f2bab8341d3cafeabdb660
1 """Utility functions for copying files and directory trees.
3 XXX The functions here don't copy the resource fork or other metadata on Mac.
5 """
7 import os
8 import sys
9 import stat
10 from os.path import abspath
11 import fnmatch
12 import errno
14 __all__ = ["copyfileobj","copyfile","copymode","copystat","copy","copy2",
15 "copytree","move","rmtree","Error", "SpecialFileError"]
17 class Error(EnvironmentError):
18 pass
20 class SpecialFileError(EnvironmentError):
21 """Raised when trying to do a kind of operation (e.g. copying) which is
22 not supported on a special file (e.g. a named pipe)"""
24 try:
25 WindowsError
26 except NameError:
27 WindowsError = None
29 def copyfileobj(fsrc, fdst, length=16*1024):
30 """copy data from file-like object fsrc to file-like object fdst"""
31 while 1:
32 buf = fsrc.read(length)
33 if not buf:
34 break
35 fdst.write(buf)
37 def _samefile(src, dst):
38 # Macintosh, Unix.
39 if hasattr(os.path,'samefile'):
40 try:
41 return os.path.samefile(src, dst)
42 except OSError:
43 return False
45 # All other platforms: check for same pathname.
46 return (os.path.normcase(os.path.abspath(src)) ==
47 os.path.normcase(os.path.abspath(dst)))
49 def copyfile(src, dst):
50 """Copy data from src to dst"""
51 if _samefile(src, dst):
52 raise Error("`%s` and `%s` are the same file" % (src, dst))
54 for fn in [src, dst]:
55 try:
56 st = os.stat(fn)
57 except OSError:
58 # File most likely does not exist
59 pass
60 else:
61 # XXX What about other special files? (sockets, devices...)
62 if stat.S_ISFIFO(st.st_mode):
63 raise SpecialFileError("`%s` is a named pipe" % fn)
65 with open(src, 'rb') as fsrc:
66 with open(dst, 'wb') as fdst:
67 copyfileobj(fsrc, fdst)
69 def copymode(src, dst):
70 """Copy mode bits from src to dst"""
71 if hasattr(os, 'chmod'):
72 st = os.stat(src)
73 mode = stat.S_IMODE(st.st_mode)
74 os.chmod(dst, mode)
76 def copystat(src, dst):
77 """Copy all stat info (mode bits, atime, mtime, flags) from src to dst"""
78 st = os.stat(src)
79 mode = stat.S_IMODE(st.st_mode)
80 if hasattr(os, 'utime'):
81 os.utime(dst, (st.st_atime, st.st_mtime))
82 if hasattr(os, 'chmod'):
83 os.chmod(dst, mode)
84 if hasattr(os, 'chflags') and hasattr(st, 'st_flags'):
85 try:
86 os.chflags(dst, st.st_flags)
87 except OSError as why:
88 if not hasattr(errno, 'EOPNOTSUPP') or why.errno != errno.EOPNOTSUPP:
89 raise
91 def copy(src, dst):
92 """Copy data and mode bits ("cp src dst").
94 The destination may be a directory.
96 """
97 if os.path.isdir(dst):
98 dst = os.path.join(dst, os.path.basename(src))
99 copyfile(src, dst)
100 copymode(src, dst)
102 def copy2(src, dst):
103 """Copy data and all stat info ("cp -p src dst").
105 The destination may be a directory.
108 if os.path.isdir(dst):
109 dst = os.path.join(dst, os.path.basename(src))
110 copyfile(src, dst)
111 copystat(src, dst)
113 def ignore_patterns(*patterns):
114 """Function that can be used as copytree() ignore parameter.
116 Patterns is a sequence of glob-style patterns
117 that are used to exclude files"""
118 def _ignore_patterns(path, names):
119 ignored_names = []
120 for pattern in patterns:
121 ignored_names.extend(fnmatch.filter(names, pattern))
122 return set(ignored_names)
123 return _ignore_patterns
125 def copytree(src, dst, symlinks=False, ignore=None):
126 """Recursively copy a directory tree using copy2().
128 The destination directory must not already exist.
129 If exception(s) occur, an Error is raised with a list of reasons.
131 If the optional symlinks flag is true, symbolic links in the
132 source tree result in symbolic links in the destination tree; if
133 it is false, the contents of the files pointed to by symbolic
134 links are copied.
136 The optional ignore argument is a callable. If given, it
137 is called with the `src` parameter, which is the directory
138 being visited by copytree(), and `names` which is the list of
139 `src` contents, as returned by os.listdir():
141 callable(src, names) -> ignored_names
143 Since copytree() is called recursively, the callable will be
144 called once for each directory that is copied. It returns a
145 list of names relative to the `src` directory that should
146 not be copied.
148 XXX Consider this example code rather than the ultimate tool.
151 names = os.listdir(src)
152 if ignore is not None:
153 ignored_names = ignore(src, names)
154 else:
155 ignored_names = set()
157 os.makedirs(dst)
158 errors = []
159 for name in names:
160 if name in ignored_names:
161 continue
162 srcname = os.path.join(src, name)
163 dstname = os.path.join(dst, name)
164 try:
165 if symlinks and os.path.islink(srcname):
166 linkto = os.readlink(srcname)
167 os.symlink(linkto, dstname)
168 elif os.path.isdir(srcname):
169 copytree(srcname, dstname, symlinks, ignore)
170 else:
171 # Will raise a SpecialFileError for unsupported file types
172 copy2(srcname, dstname)
173 # catch the Error from the recursive copytree so that we can
174 # continue with other files
175 except Error as err:
176 errors.extend(err.args[0])
177 except EnvironmentError as why:
178 errors.append((srcname, dstname, str(why)))
179 try:
180 copystat(src, dst)
181 except OSError as why:
182 if WindowsError is not None and isinstance(why, WindowsError):
183 # Copying file access times may fail on Windows
184 pass
185 else:
186 errors.extend((src, dst, str(why)))
187 if errors:
188 raise Error(errors)
190 def rmtree(path, ignore_errors=False, onerror=None):
191 """Recursively delete a directory tree.
193 If ignore_errors is set, errors are ignored; otherwise, if onerror
194 is set, it is called to handle the error with arguments (func,
195 path, exc_info) where func is os.listdir, os.remove, or os.rmdir;
196 path is the argument to that function that caused it to fail; and
197 exc_info is a tuple returned by sys.exc_info(). If ignore_errors
198 is false and onerror is None, an exception is raised.
201 if ignore_errors:
202 def onerror(*args):
203 pass
204 elif onerror is None:
205 def onerror(*args):
206 raise
207 try:
208 if os.path.islink(path):
209 # symlinks to directories are forbidden, see bug #1669
210 raise OSError("Cannot call rmtree on a symbolic link")
211 except OSError:
212 onerror(os.path.islink, path, sys.exc_info())
213 # can't continue even if onerror hook returns
214 return
215 names = []
216 try:
217 names = os.listdir(path)
218 except os.error as err:
219 onerror(os.listdir, path, sys.exc_info())
220 for name in names:
221 fullname = os.path.join(path, name)
222 try:
223 mode = os.lstat(fullname).st_mode
224 except os.error:
225 mode = 0
226 if stat.S_ISDIR(mode):
227 rmtree(fullname, ignore_errors, onerror)
228 else:
229 try:
230 os.remove(fullname)
231 except os.error as err:
232 onerror(os.remove, fullname, sys.exc_info())
233 try:
234 os.rmdir(path)
235 except os.error:
236 onerror(os.rmdir, path, sys.exc_info())
239 def _basename(path):
240 # A basename() variant which first strips the trailing slash, if present.
241 # Thus we always get the last component of the path, even for directories.
242 return os.path.basename(path.rstrip(os.path.sep))
244 def move(src, dst):
245 """Recursively move a file or directory to another location. This is
246 similar to the Unix "mv" command.
248 If the destination is a directory or a symlink to a directory, the source
249 is moved inside the directory. The destination path must not already
250 exist.
252 If the destination already exists but is not a directory, it may be
253 overwritten depending on os.rename() semantics.
255 If the destination is on our current filesystem, then rename() is used.
256 Otherwise, src is copied to the destination and then removed.
257 A lot more could be done here... A look at a mv.c shows a lot of
258 the issues this implementation glosses over.
261 real_dst = dst
262 if os.path.isdir(dst):
263 real_dst = os.path.join(dst, _basename(src))
264 if os.path.exists(real_dst):
265 raise Error("Destination path '%s' already exists" % real_dst)
266 try:
267 os.rename(src, real_dst)
268 except OSError:
269 if os.path.isdir(src):
270 if _destinsrc(src, dst):
271 raise Error("Cannot move a directory '%s' into itself '%s'." % (src, dst))
272 copytree(src, real_dst, symlinks=True)
273 rmtree(src)
274 else:
275 copy2(src, real_dst)
276 os.unlink(src)
278 def _destinsrc(src, dst):
279 src = abspath(src)
280 dst = abspath(dst)
281 if not src.endswith(os.path.sep):
282 src += os.path.sep
283 if not dst.endswith(os.path.sep):
284 dst += os.path.sep
285 return dst.startswith(src)