Catch situations where currentframe() returns None. See SF patch #1447410, this is...
[python.git] / Lib / shutil.py
blob2fca61c164d3bd97abdbb443a8f453a9fa7144b7
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 and mtime) 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)
72 def copy(src, dst):
73 """Copy data and mode bits ("cp src dst").
75 The destination may be a directory.
77 """
78 if os.path.isdir(dst):
79 dst = os.path.join(dst, os.path.basename(src))
80 copyfile(src, dst)
81 copymode(src, dst)
83 def copy2(src, dst):
84 """Copy data and all stat info ("cp -p src dst").
86 The destination may be a directory.
88 """
89 if os.path.isdir(dst):
90 dst = os.path.join(dst, os.path.basename(src))
91 copyfile(src, dst)
92 copystat(src, dst)
95 def copytree(src, dst, symlinks=False):
96 """Recursively copy a directory tree using copy2().
98 The destination directory must not already exist.
99 If exception(s) occur, an Error is raised with a list of reasons.
101 If the optional symlinks flag is true, symbolic links in the
102 source tree result in symbolic links in the destination tree; if
103 it is false, the contents of the files pointed to by symbolic
104 links are copied.
106 XXX Consider this example code rather than the ultimate tool.
109 names = os.listdir(src)
110 os.makedirs(dst)
111 errors = []
112 for name in names:
113 srcname = os.path.join(src, name)
114 dstname = os.path.join(dst, name)
115 try:
116 if symlinks and os.path.islink(srcname):
117 linkto = os.readlink(srcname)
118 os.symlink(linkto, dstname)
119 elif os.path.isdir(srcname):
120 copytree(srcname, dstname, symlinks)
121 else:
122 copy2(srcname, dstname)
123 # XXX What about devices, sockets etc.?
124 except (IOError, os.error), why:
125 errors.append((srcname, dstname, why))
126 # catch the Error from the recursive copytree so that we can
127 # continue with other files
128 except Error, err:
129 errors.extend(err.args[0])
130 copystat(src, dst)
131 if errors:
132 raise Error, errors
134 def rmtree(path, ignore_errors=False, onerror=None):
135 """Recursively delete a directory tree.
137 If ignore_errors is set, errors are ignored; otherwise, if onerror
138 is set, it is called to handle the error with arguments (func,
139 path, exc_info) where func is os.listdir, os.remove, or os.rmdir;
140 path is the argument to that function that caused it to fail; and
141 exc_info is a tuple returned by sys.exc_info(). If ignore_errors
142 is false and onerror is None, an exception is raised.
145 if ignore_errors:
146 def onerror(*args):
147 pass
148 elif onerror is None:
149 def onerror(*args):
150 raise
151 names = []
152 try:
153 names = os.listdir(path)
154 except os.error, err:
155 onerror(os.listdir, path, sys.exc_info())
156 for name in names:
157 fullname = os.path.join(path, name)
158 try:
159 mode = os.lstat(fullname).st_mode
160 except os.error:
161 mode = 0
162 if stat.S_ISDIR(mode):
163 rmtree(fullname, ignore_errors, onerror)
164 else:
165 try:
166 os.remove(fullname)
167 except os.error, err:
168 onerror(os.remove, fullname, sys.exc_info())
169 try:
170 os.rmdir(path)
171 except os.error:
172 onerror(os.rmdir, path, sys.exc_info())
174 def move(src, dst):
175 """Recursively move a file or directory to another location.
177 If the destination is on our current filesystem, then simply use
178 rename. Otherwise, copy src to the dst and then remove src.
179 A lot more could be done here... A look at a mv.c shows a lot of
180 the issues this implementation glosses over.
184 try:
185 os.rename(src, dst)
186 except OSError:
187 if os.path.isdir(src):
188 if destinsrc(src, dst):
189 raise Error, "Cannot move a directory '%s' into itself '%s'." % (src, dst)
190 copytree(src, dst, symlinks=True)
191 rmtree(src)
192 else:
193 copy2(src,dst)
194 os.unlink(src)
196 def destinsrc(src, dst):
197 return abspath(dst).startswith(abspath(src))