]> git.lizzy.rs Git - plan9front.git/blob - sys/lib/python/distutils/file_util.py
getpass
[plan9front.git] / sys / lib / python / distutils / file_util.py
1 """distutils.file_util
2
3 Utility functions for operating on single files.
4 """
5
6 # This module should be kept compatible with Python 2.1.
7
8 __revision__ = "$Id: file_util.py 37828 2004-11-10 22:23:15Z loewis $"
9
10 import os
11 from distutils.errors import DistutilsFileError
12 from distutils import log
13
14 # for generating verbose output in 'copy_file()'
15 _copy_action = { None:   'copying',
16                  'hard': 'hard linking',
17                  'sym':  'symbolically linking' }
18
19
20 def _copy_file_contents (src, dst, buffer_size=16*1024):
21     """Copy the file 'src' to 'dst'; both must be filenames.  Any error
22     opening either file, reading from 'src', or writing to 'dst', raises
23     DistutilsFileError.  Data is read/written in chunks of 'buffer_size'
24     bytes (default 16k).  No attempt is made to handle anything apart from
25     regular files.
26     """
27     # Stolen from shutil module in the standard library, but with
28     # custom error-handling added.
29
30     fsrc = None
31     fdst = None
32     try:
33         try:
34             fsrc = open(src, 'rb')
35         except os.error, (errno, errstr):
36             raise DistutilsFileError, \
37                   "could not open '%s': %s" % (src, errstr)
38
39         if os.path.exists(dst):
40             try:
41                 os.unlink(dst)
42             except os.error, (errno, errstr):
43                 raise DistutilsFileError, \
44                       "could not delete '%s': %s" % (dst, errstr)
45
46         try:
47             fdst = open(dst, 'wb')
48         except os.error, (errno, errstr):
49             raise DistutilsFileError, \
50                   "could not create '%s': %s" % (dst, errstr)
51
52         while 1:
53             try:
54                 buf = fsrc.read(buffer_size)
55             except os.error, (errno, errstr):
56                 raise DistutilsFileError, \
57                       "could not read from '%s': %s" % (src, errstr)
58
59             if not buf:
60                 break
61
62             try:
63                 fdst.write(buf)
64             except os.error, (errno, errstr):
65                 raise DistutilsFileError, \
66                       "could not write to '%s': %s" % (dst, errstr)
67
68     finally:
69         if fdst:
70             fdst.close()
71         if fsrc:
72             fsrc.close()
73
74 # _copy_file_contents()
75
76 def copy_file (src, dst,
77                preserve_mode=1,
78                preserve_times=1,
79                update=0,
80                link=None,
81                verbose=0,
82                dry_run=0):
83
84     """Copy a file 'src' to 'dst'.  If 'dst' is a directory, then 'src' is
85     copied there with the same name; otherwise, it must be a filename.  (If
86     the file exists, it will be ruthlessly clobbered.)  If 'preserve_mode'
87     is true (the default), the file's mode (type and permission bits, or
88     whatever is analogous on the current platform) is copied.  If
89     'preserve_times' is true (the default), the last-modified and
90     last-access times are copied as well.  If 'update' is true, 'src' will
91     only be copied if 'dst' does not exist, or if 'dst' does exist but is
92     older than 'src'.
93
94     'link' allows you to make hard links (os.link) or symbolic links
95     (os.symlink) instead of copying: set it to "hard" or "sym"; if it is
96     None (the default), files are copied.  Don't set 'link' on systems that
97     don't support it: 'copy_file()' doesn't check if hard or symbolic
98     linking is available.
99
100     Under Mac OS, uses the native file copy function in macostools; on
101     other systems, uses '_copy_file_contents()' to copy file contents.
102
103     Return a tuple (dest_name, copied): 'dest_name' is the actual name of
104     the output file, and 'copied' is true if the file was copied (or would
105     have been copied, if 'dry_run' true).
106     """
107     # XXX if the destination file already exists, we clobber it if
108     # copying, but blow up if linking.  Hmmm.  And I don't know what
109     # macostools.copyfile() does.  Should definitely be consistent, and
110     # should probably blow up if destination exists and we would be
111     # changing it (ie. it's not already a hard/soft link to src OR
112     # (not update) and (src newer than dst).
113
114     from distutils.dep_util import newer
115     from stat import ST_ATIME, ST_MTIME, ST_MODE, S_IMODE
116
117     if not os.path.isfile(src):
118         raise DistutilsFileError, \
119               "can't copy '%s': doesn't exist or not a regular file" % src
120
121     if os.path.isdir(dst):
122         dir = dst
123         dst = os.path.join(dst, os.path.basename(src))
124     else:
125         dir = os.path.dirname(dst)
126
127     if update and not newer(src, dst):
128         log.debug("not copying %s (output up-to-date)", src)
129         return dst, 0
130
131     try:
132         action = _copy_action[link]
133     except KeyError:
134         raise ValueError, \
135               "invalid value '%s' for 'link' argument" % link
136     if os.path.basename(dst) == os.path.basename(src):
137         log.info("%s %s -> %s", action, src, dir)
138     else:
139         log.info("%s %s -> %s", action, src, dst)
140
141     if dry_run:
142         return (dst, 1)
143
144     # On Mac OS, use the native file copy routine
145     if os.name == 'mac':
146         import macostools
147         try:
148             macostools.copy(src, dst, 0, preserve_times)
149         except os.error, exc:
150             raise DistutilsFileError, \
151                   "could not copy '%s' to '%s': %s" % (src, dst, exc[-1])
152
153     # If linking (hard or symbolic), use the appropriate system call
154     # (Unix only, of course, but that's the caller's responsibility)
155     elif link == 'hard':
156         if not (os.path.exists(dst) and os.path.samefile(src, dst)):
157             os.link(src, dst)
158     elif link == 'sym':
159         if not (os.path.exists(dst) and os.path.samefile(src, dst)):
160             os.symlink(src, dst)
161
162     # Otherwise (non-Mac, not linking), copy the file contents and
163     # (optionally) copy the times and mode.
164     else:
165         _copy_file_contents(src, dst)
166         if preserve_mode or preserve_times:
167             st = os.stat(src)
168
169             # According to David Ascher <da@ski.org>, utime() should be done
170             # before chmod() (at least under NT).
171             if preserve_times:
172                 os.utime(dst, (st[ST_ATIME], st[ST_MTIME]))
173             if preserve_mode:
174                 os.chmod(dst, S_IMODE(st[ST_MODE]))
175
176     return (dst, 1)
177
178 # copy_file ()
179
180
181 # XXX I suspect this is Unix-specific -- need porting help!
182 def move_file (src, dst,
183                verbose=0,
184                dry_run=0):
185
186     """Move a file 'src' to 'dst'.  If 'dst' is a directory, the file will
187     be moved into it with the same name; otherwise, 'src' is just renamed
188     to 'dst'.  Return the new full name of the file.
189
190     Handles cross-device moves on Unix using 'copy_file()'.  What about
191     other systems???
192     """
193     from os.path import exists, isfile, isdir, basename, dirname
194     import errno
195
196     log.info("moving %s -> %s", src, dst)
197
198     if dry_run:
199         return dst
200
201     if not isfile(src):
202         raise DistutilsFileError, \
203               "can't move '%s': not a regular file" % src
204
205     if isdir(dst):
206         dst = os.path.join(dst, basename(src))
207     elif exists(dst):
208         raise DistutilsFileError, \
209               "can't move '%s': destination '%s' already exists" % \
210               (src, dst)
211
212     if not isdir(dirname(dst)):
213         raise DistutilsFileError, \
214               "can't move '%s': destination '%s' not a valid path" % \
215               (src, dst)
216
217     copy_it = 0
218     try:
219         os.rename(src, dst)
220     except os.error, (num, msg):
221         if num == errno.EXDEV:
222             copy_it = 1
223         else:
224             raise DistutilsFileError, \
225                   "couldn't move '%s' to '%s': %s" % (src, dst, msg)
226
227     if copy_it:
228         copy_file(src, dst)
229         try:
230             os.unlink(src)
231         except os.error, (num, msg):
232             try:
233                 os.unlink(dst)
234             except os.error:
235                 pass
236             raise DistutilsFileError, \
237                   ("couldn't move '%s' to '%s' by copy/delete: " +
238                    "delete '%s' failed: %s") % \
239                   (src, dst, src, msg)
240
241     return dst
242
243 # move_file ()
244
245
246 def write_file (filename, contents):
247     """Create a file with the specified name and write 'contents' (a
248     sequence of strings without line terminators) to it.
249     """
250     f = open(filename, "w")
251     for line in contents:
252         f.write(line + "\n")
253     f.close()