Tweak the comments and formatting.
[python.git] / Lib / shutil.py
blob6ce402326bbe7638ccc1def763250c90cf41cd75
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
12 __all__ = ["copyfileobj","copyfile","copymode","copystat","copy","copy2",
13 "copytree","move","rmtree","Error"]
15 class Error(EnvironmentError):
16 pass
18 def copyfileobj(fsrc, fdst, length=16*1024):
19 """copy data from file-like object fsrc to file-like object fdst"""
20 while 1:
21 buf = fsrc.read(length)
22 if not buf:
23 break
24 fdst.write(buf)
26 def _samefile(src, dst):
27 # Macintosh, Unix.
28 if hasattr(os.path,'samefile'):
29 try:
30 return os.path.samefile(src, dst)
31 except OSError:
32 return False
34 # All other platforms: check for same pathname.
35 return (os.path.normcase(os.path.abspath(src)) ==
36 os.path.normcase(os.path.abspath(dst)))
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)
43 fsrc = None
44 fdst = None
45 try:
46 fsrc = open(src, 'rb')
47 fdst = open(dst, 'wb')
48 copyfileobj(fsrc, fdst)
49 finally:
50 if fdst:
51 fdst.close()
52 if fsrc:
53 fsrc.close()
55 def copymode(src, dst):
56 """Copy mode bits from src to dst"""
57 if hasattr(os, 'chmod'):
58 st = os.stat(src)
59 mode = stat.S_IMODE(st.st_mode)
60 os.chmod(dst, mode)
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 if hasattr(os, 'chmod'):
69 os.chmod(dst, mode)
70 if hasattr(os, 'chflags') and hasattr(st, 'st_flags'):
71 os.chflags(dst, st.st_flags)
74 def copy(src, dst):
75 """Copy data and mode bits ("cp src dst").
77 The destination may be a directory.
79 """
80 if os.path.isdir(dst):
81 dst = os.path.join(dst, os.path.basename(src))
82 copyfile(src, dst)
83 copymode(src, dst)
85 def copy2(src, dst):
86 """Copy data and all stat info ("cp -p src dst").
88 The destination may be a directory.
90 """
91 if os.path.isdir(dst):
92 dst = os.path.join(dst, os.path.basename(src))
93 copyfile(src, dst)
94 copystat(src, dst)
97 def copytree(src, dst, symlinks=False):
98 """Recursively copy a directory tree using copy2().
100 The destination directory must not already exist.
101 If exception(s) occur, an Error is raised with a list of reasons.
103 If the optional symlinks flag is true, symbolic links in the
104 source tree result in symbolic links in the destination tree; if
105 it is false, the contents of the files pointed to by symbolic
106 links are copied.
108 XXX Consider this example code rather than the ultimate tool.
111 names = os.listdir(src)
112 os.makedirs(dst)
113 errors = []
114 for name in names:
115 srcname = os.path.join(src, name)
116 dstname = os.path.join(dst, name)
117 try:
118 if symlinks and os.path.islink(srcname):
119 linkto = os.readlink(srcname)
120 os.symlink(linkto, dstname)
121 elif os.path.isdir(srcname):
122 copytree(srcname, dstname, symlinks)
123 else:
124 copy2(srcname, dstname)
125 # XXX What about devices, sockets etc.?
126 except (IOError, os.error), why:
127 errors.append((srcname, dstname, str(why)))
128 # catch the Error from the recursive copytree so that we can
129 # continue with other files
130 except Error, err:
131 errors.extend(err.args[0])
132 try:
133 copystat(src, dst)
134 except WindowsError:
135 # can't copy file access times on Windows
136 pass
137 except OSError, why:
138 errors.extend((src, dst, str(why)))
139 if errors:
140 raise Error, errors
142 def rmtree(path, ignore_errors=False, onerror=None):
143 """Recursively delete a directory tree.
145 If ignore_errors is set, errors are ignored; otherwise, if onerror
146 is set, it is called to handle the error with arguments (func,
147 path, exc_info) where func is os.listdir, os.remove, or os.rmdir;
148 path is the argument to that function that caused it to fail; and
149 exc_info is a tuple returned by sys.exc_info(). If ignore_errors
150 is false and onerror is None, an exception is raised.
153 if ignore_errors:
154 def onerror(*args):
155 pass
156 elif onerror is None:
157 def onerror(*args):
158 raise
159 try:
160 if os.path.islink(path):
161 # symlinks to directories are forbidden, see bug #1669
162 raise OSError("Cannot call rmtree on a symbolic link")
163 except OSError:
164 onerror(os.path.islink, path, sys.exc_info())
165 # can't continue even if onerror hook returns
166 return
167 names = []
168 try:
169 names = os.listdir(path)
170 except os.error, err:
171 onerror(os.listdir, path, sys.exc_info())
172 for name in names:
173 fullname = os.path.join(path, name)
174 try:
175 mode = os.lstat(fullname).st_mode
176 except os.error:
177 mode = 0
178 if stat.S_ISDIR(mode):
179 rmtree(fullname, ignore_errors, onerror)
180 else:
181 try:
182 os.remove(fullname)
183 except os.error, err:
184 onerror(os.remove, fullname, sys.exc_info())
185 try:
186 os.rmdir(path)
187 except os.error:
188 onerror(os.rmdir, path, sys.exc_info())
191 def _basename(path):
192 # A basename() variant which first strips the trailing slash, if present.
193 # Thus we always get the last component of the path, even for directories.
194 return os.path.basename(path.rstrip(os.path.sep))
196 def move(src, dst):
197 """Recursively move a file or directory to another location. This is
198 similar to the Unix "mv" command.
200 If the destination is a directory or a symlink to a directory, the source
201 is moved inside the directory. The destination path must not already
202 exist.
204 If the destination already exists but is not a directory, it may be
205 overwritten depending on os.rename() semantics.
207 If the destination is on our current filesystem, then rename() is used.
208 Otherwise, src is copied to the destination and then removed.
209 A lot more could be done here... A look at a mv.c shows a lot of
210 the issues this implementation glosses over.
213 real_dst = dst
214 if os.path.isdir(dst):
215 real_dst = os.path.join(dst, _basename(src))
216 if os.path.exists(real_dst):
217 raise Error, "Destination path '%s' already exists" % real_dst
218 try:
219 os.rename(src, real_dst)
220 except OSError:
221 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 rmtree(src)
226 else:
227 copy2(src, real_dst)
228 os.unlink(src)
230 def destinsrc(src, dst):
231 return abspath(dst).startswith(abspath(src))