1 # Copyright 2010-2011 Gentoo Foundation
2 # Distributed under the terms of the GNU General Public License v2
8 import shutil as _shutil
12 from portage import bsd_chflags, _encodings, _os_overrides, _selinux, \
13 _unicode_decode, _unicode_func_wrapper, _unicode_module_wrapper
14 from portage.const import MOVE_BINARY
15 from portage.localization import _
16 from portage.process import spawn
17 from portage.util import writemsg
19 def movefile(src, dest, newmtime=None, sstat=None, mysettings=None,
20 hardlink_candidates=None, encoding=_encodings['fs']):
21 """moves a file from src to dest, preserving all permissions and attributes; mtime will
22 be preserved even when moving across filesystems. Returns true on success and false on
23 failure. Move is atomic."""
24 #print "movefile("+str(src)+","+str(dest)+","+str(newmtime)+","+str(sstat)+")"
26 if mysettings is None:
27 mysettings = portage.settings
29 selinux_enabled = mysettings.selinux_enabled()
31 selinux = _unicode_module_wrapper(_selinux, encoding=encoding)
33 lchown = _unicode_func_wrapper(portage.data.lchown, encoding=encoding)
34 os = _unicode_module_wrapper(_os,
35 encoding=encoding, overrides=_os_overrides)
36 shutil = _unicode_module_wrapper(_shutil, encoding=encoding)
42 except SystemExit as e:
44 except Exception as e:
45 print(_("!!! Stating source file failed... movefile()"))
52 except (OSError, IOError):
53 dstat=os.lstat(os.path.dirname(dest))
57 if destexists and dstat.st_flags != 0:
58 bsd_chflags.lchflags(dest, 0)
59 # Use normal stat/chflags for the parent since we want to
60 # follow any symlinks to the real parent directory.
61 pflags = os.stat(os.path.dirname(dest)).st_flags
63 bsd_chflags.chflags(os.path.dirname(dest), 0)
66 if stat.S_ISLNK(dstat[stat.ST_MODE]):
70 except SystemExit as e:
72 except Exception as e:
75 if stat.S_ISLNK(sstat[stat.ST_MODE]):
77 target=os.readlink(src)
78 if mysettings and mysettings["D"]:
79 if target.find(mysettings["D"])==0:
80 target=target[len(mysettings["D"]):]
81 if destexists and not stat.S_ISDIR(dstat[stat.ST_MODE]):
85 selinux.symlink(target, dest, src)
87 os.symlink(target, dest)
89 # Some programs will create symlinks automatically, so we have
90 # to tolerate these links being recreated during the merge
91 # process. In any case, if the link is pointing at the right
92 # place, we're in good shape.
93 if e.errno not in (errno.ENOENT, errno.EEXIST) or \
94 target != os.readlink(dest):
96 lchown(dest,sstat[stat.ST_UID],sstat[stat.ST_GID])
97 # utime() only works on the target of a symlink, so it's not
98 # possible to perserve mtime on symlinks.
99 return os.lstat(dest)[stat.ST_MTIME]
100 except SystemExit as e:
102 except Exception as e:
103 print(_("!!! failed to properly create symlink:"))
104 print("!!!",dest,"->",target)
109 # Since identical files might be merged to multiple filesystems,
110 # so os.link() calls might fail for some paths, so try them all.
111 # For atomic replacement, first create the link as a temp file
112 # and them use os.rename() to replace the destination.
113 if hardlink_candidates:
114 head, tail = os.path.split(dest)
115 hardlink_tmp = os.path.join(head, ".%s._portage_merge_.%s" % \
118 os.unlink(hardlink_tmp)
120 if e.errno != errno.ENOENT:
121 writemsg(_("!!! Failed to remove hardlink temp file: %s\n") % \
122 (hardlink_tmp,), noiselevel=-1)
123 writemsg("!!! %s\n" % (e,), noiselevel=-1)
126 for hardlink_src in hardlink_candidates:
128 os.link(hardlink_src, hardlink_tmp)
133 os.rename(hardlink_tmp, dest)
135 writemsg(_("!!! Failed to rename %s to %s\n") % \
136 (hardlink_tmp, dest), noiselevel=-1)
137 writemsg("!!! %s\n" % (e,), noiselevel=-1)
145 if not hardlinked and (selinux_enabled or sstat.st_dev == dstat.st_dev):
148 selinux.rename(src, dest)
153 if e.errno != errno.EXDEV:
155 print(_("!!! Failed to move %(src)s to %(dest)s") % {"src": src, "dest": dest})
158 # Invalid cross-device-link 'bind' mounted or actually Cross-Device
161 if stat.S_ISREG(sstat[stat.ST_MODE]):
162 try: # For safety copy then move it over.
164 selinux.copyfile(src, dest + "#new")
165 selinux.rename(dest + "#new", dest)
167 shutil.copyfile(src,dest+"#new")
168 os.rename(dest+"#new",dest)
170 except SystemExit as e:
172 except Exception as e:
173 print(_('!!! copy %(src)s -> %(dest)s failed.') % {"src": src, "dest": dest})
177 #we don't yet handle special, so we need to fall back to /bin/mv
178 a = spawn([MOVE_BINARY, '-f', src, dest], env=os.environ)
180 writemsg(_("!!! Failed to move special file:\n"), noiselevel=-1)
181 writemsg(_("!!! '%(src)s' to '%(dest)s'\n") % \
182 {"src": _unicode_decode(src, encoding=encoding),
183 "dest": _unicode_decode(dest, encoding=encoding)}, noiselevel=-1)
184 writemsg("!!! %s\n" % a, noiselevel=-1)
185 return None # failure
188 if stat.S_ISLNK(sstat[stat.ST_MODE]):
189 lchown(dest,sstat[stat.ST_UID],sstat[stat.ST_GID])
191 os.chown(dest,sstat[stat.ST_UID],sstat[stat.ST_GID])
192 os.chmod(dest, stat.S_IMODE(sstat[stat.ST_MODE])) # Sticky is reset on chown
194 except SystemExit as e:
196 except Exception as e:
197 print(_("!!! Failed to chown/chmod/unlink in movefile()"))
202 # Always use stat_obj[stat.ST_MTIME] for the integral timestamp which
203 # is returned, since the stat_obj.st_mtime float attribute rounds *up*
204 # if the nanosecond part of the timestamp is 999999881 ns or greater.
207 newmtime = os.stat(dest)[stat.ST_MTIME]
209 # Note: It is not possible to preserve nanosecond precision
210 # (supported in POSIX.1-2008 via utimensat) with the IEEE 754
211 # double precision float which only has a 53 bit significand.
212 if newmtime is not None:
213 os.utime(dest, (newmtime, newmtime))
215 newmtime = sstat[stat.ST_MTIME]
217 # If rename succeeded then timestamps are automatically
218 # preserved with complete precision because the source
219 # and destination inode are the same. Otherwise, round
220 # down to the nearest whole second since python's float
221 # st_mtime cannot be used to preserve the st_mtim.tv_nsec
222 # field with complete precision. Note that we have to use
223 # stat_obj[stat.ST_MTIME] here because the float
224 # stat_obj.st_mtime rounds *up* sometimes.
225 os.utime(dest, (newmtime, newmtime))
227 # The utime can fail here with EPERM even though the move succeeded.
228 # Instead of failing, use stat to return the mtime if possible.
230 newmtime = os.stat(dest)[stat.ST_MTIME]
232 writemsg(_("!!! Failed to stat in movefile()\n"), noiselevel=-1)
233 writemsg("!!! %s\n" % dest, noiselevel=-1)
234 writemsg("!!! %s\n" % str(e), noiselevel=-1)
238 # Restore the flags we saved before moving
240 bsd_chflags.chflags(os.path.dirname(dest), pflags)