merge with main
authorBenoit Boissinot <benoit.boissinot@ens-lyon.org>
Thu, 20 Jan 2011 20:35:54 +0100
changeset 13283 a3e834a9e9c4
parent 13282 c01eea6c455b (diff)
parent 13277 9f707b297b0f (current diff)
child 13284 77351c88dd10
merge with main
--- a/mercurial/lock.py	Tue Jan 18 15:55:49 2011 -0600
+++ b/mercurial/lock.py	Thu Jan 20 20:35:54 2011 +0100
@@ -113,7 +113,7 @@
         # held, or can race and break valid lock.
         try:
             l = lock(self.f + '.break', timeout=0)
-            os.unlink(self.f)
+            util.unlink(self.f)
             l.release()
         except error.LockError:
             return locker
@@ -126,7 +126,7 @@
             if self.releasefn:
                 self.releasefn()
             try:
-                os.unlink(self.f)
+                util.unlink(self.f)
             except OSError:
                 pass
 
--- a/mercurial/posix.py	Tue Jan 18 15:55:49 2011 -0600
+++ b/mercurial/posix.py	Thu Jan 20 20:35:54 2011 +0100
@@ -13,6 +13,7 @@
 nulldev = '/dev/null'
 normpath = os.path.normpath
 samestat = os.path.samestat
+unlink = os.unlink
 rename = os.rename
 expandglobs = False
 
--- a/mercurial/util.py	Tue Jan 18 15:55:49 2011 -0600
+++ b/mercurial/util.py	Thu Jan 20 20:35:54 2011 +0100
@@ -911,7 +911,7 @@
                 return atomictempfile(f, mode, self.createmode)
             try:
                 if 'w' in mode:
-                    os.unlink(f)
+                    unlink(f)
                     nlink = 0
                 else:
                     # nlinks() may behave differently for files on Windows
@@ -919,7 +919,9 @@
                     fd = open(f)
                     nlink = nlinks(f)
                     fd.close()
-            except (OSError, IOError):
+            except (OSError, IOError), e:
+                if e.errno != errno.ENOENT:
+                    raise
                 nlink = 0
                 if not os.path.isdir(dirname):
                     makedirs(dirname, self.createmode)
--- a/mercurial/windows.py	Tue Jan 18 15:55:49 2011 -0600
+++ b/mercurial/windows.py	Thu Jan 20 20:35:54 2011 +0100
@@ -285,40 +285,54 @@
     except OSError:
         pass
 
+def unlink(f):
+    '''try to implement POSIX' unlink semantics on Windows'''
+
+    # POSIX allows to unlink and rename open files. Windows has serious
+    # problems with doing that:
+    # - Calling os.unlink (or os.rename) on a file f fails if f or any
+    #   hardlinked copy of f has been opened with Python's open(). There is no
+    #   way such a file can be deleted or renamed on Windows (other than
+    #   scheduling the delete or rename for the next reboot).
+    # - Calling os.unlink on a file that has been opened with Mercurial's
+    #   posixfile (or comparable methods) will delay the actual deletion of
+    #   the file for as long as the file is held open. The filename is blocked
+    #   during that time and cannot be used for recreating a new file under
+    #   that same name ("zombie file"). Directories containing such zombie files
+    #   cannot be removed or moved.
+    # A file that has been opened with posixfile can be renamed, so we rename
+    # f to a random temporary name before calling os.unlink on it. This allows
+    # callers to recreate f immediately while having other readers do their
+    # implicit zombie filename blocking on a temporary name.
+
+    for tries in xrange(10):
+        temp = '%s-%08x' % (f, random.randint(0, 0xffffffff))
+        try:
+            os.rename(f, temp)  # raises OSError EEXIST if temp exists
+            break
+        except OSError, e:
+            if e.errno != errno.EEXIST:
+                raise
+    else:
+        raise IOError, (errno.EEXIST, "No usable temporary filename found")
+
+    try:
+        os.unlink(temp)
+    except:
+        # Some very rude AV-scanners on Windows may cause this unlink to fail.
+        # Not aborting here just leaks the temp file, whereas aborting at this
+        # point may leave serious inconsistencies. Ideally, we would notify
+        # the user in this case here.
+        pass
+
 def rename(src, dst):
     '''atomically rename file src to dst, replacing dst if it exists'''
     try:
         os.rename(src, dst)
-    except OSError: # FIXME: check err (EEXIST ?)
-
-        # On windows, rename to existing file is not allowed, so we
-        # must delete destination first. But if a file is open, unlink
-        # schedules it for delete but does not delete it. Rename
-        # happens immediately even for open files, so we rename
-        # destination to a temporary name, then delete that. Then
-        # rename is safe to do.
-        # The temporary name is chosen at random to avoid the situation
-        # where a file is left lying around from a previous aborted run.
-
-        for tries in xrange(10):
-            temp = '%s-%08x' % (dst, random.randint(0, 0xffffffff))
-            try:
-                os.rename(dst, temp)  # raises OSError EEXIST if temp exists
-                break
-            except OSError, e:
-                if e.errno != errno.EEXIST:
-                    raise
-        else:
-            raise IOError, (errno.EEXIST, "No usable temporary filename found")
-
-        try:
-            os.unlink(temp)
-        except:
-            # Some rude AV-scanners on Windows may cause the unlink to
-            # fail. Not aborting here just leaks the temp file, whereas
-            # aborting at this point may leave serious inconsistencies.
-            # Ideally, we would notify the user here.
-            pass
+    except OSError, e:
+        if e.errno != errno.EEXIST:
+            raise
+        unlink(dst)
         os.rename(src, dst)
 
 def spawndetached(args):