changeset 13280:6052bbc7aabd

reintroduces util.unlink, for POSIX and Windows. windows: factor it out of rename posix: add alias 'unlink' for os.unlink Note that this new unlink function now has different semantics than the unlink() we had before changeset 6bf39d88c857 ("rename util.unlink to unlinkpath").
author Adrian Buehlmann <adrian@cadifra.com>
date Tue, 11 Jan 2011 14:10:16 +0100
parents eed22340b7aa
children 95de08ffa324
files mercurial/posix.py mercurial/windows.py
diffstat 2 files changed, 42 insertions(+), 29 deletions(-) [+]
line wrap: on
line diff
--- a/mercurial/posix.py	Tue Jan 11 14:10:16 2011 +0100
+++ b/mercurial/posix.py	Tue Jan 11 14:10:16 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/windows.py	Tue Jan 11 14:10:16 2011 +0100
+++ b/mercurial/windows.py	Tue Jan 11 14:10:16 2011 +0100
@@ -285,6 +285,46 @@
     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:
@@ -292,35 +332,7 @@
     except OSError, e:
         if e.errno != errno.EEXIST:
             raise
-
-        # 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
+        unlink(dst)
         os.rename(src, dst)
 
 def spawndetached(args):