changeset 5573:285d4897339e

filesys: take posixemulation.rename code from werkzeug 0.6.1, it has atomic rename support for >=Vista/Server2008
author Thomas Waldmann <tw AT waldmann-edv DOT de>
date Mon, 22 Feb 2010 19:35:55 +0100
parents 1302238342cd
children 8b9acb287705
files MoinMoin/util/filesys.py
diffstat 1 files changed, 84 insertions(+), 44 deletions(-) [+]
line wrap: on
line diff
--- a/MoinMoin/util/filesys.py	Mon Feb 22 19:26:13 2010 +0100
+++ b/MoinMoin/util/filesys.py	Mon Feb 22 19:35:55 2010 +0100
@@ -7,7 +7,7 @@
     @license: GNU GPL, see COPYING for details.
 """
 
-import sys, os, shutil, time, errno, random
+import sys, os, shutil, time, errno
 from stat import S_ISDIR, ST_MODE, S_IMODE
 
 from MoinMoin import log
@@ -29,53 +29,93 @@
             raise
 
 
-def rename(oldname, newname):
-    """ Multiplatform rename
-
-    Needed because win32 rename is not POSIX compliant, and does not
-    remove target file if it exists.
+# begin copy of werkzeug.posixemulation from werkzeug 0.6.1(pre) repo
+r"""
+    werkzeug.posixemulation
+    ~~~~~~~~~~~~~~~~~~~~~~~
 
-    Problem: this "rename" is not atomic any more on win32.
+    Provides a POSIX emulation for some features that are relevant to
+    web applications.  The main purpose is to simplify support for
+    systems such as Windows NT that are not 100% POSIX compatible.
 
-    FIXME: What about rename locking? we can have a lock file in the
-    page directory, named: PageName.lock, and lock this file before we
-    rename, then unlock when finished.
-    """
-    if os.name == 'nt':
-        # Windows "rename" taken from Mercurial's util.py. Thanks!
+    Currently this only implements a :func:`rename` function that
+    follows POSIX semantics.  Eg: if the target file already exists it
+    will be replaced without asking.
+
+    This module was introduced in 0.6.1 and is not a public interface.
+    It might become one in later versions of Werkzeug.
+
+    :copyright: (c) 2010 by the Werkzeug Team, see AUTHORS for more details.
+    :license: BSD, see LICENSE for more details.
+"""
+import os
+import errno
+import random
+
+
+can_rename_open_file = False
+if os.name == 'nt':
+    _rename = lambda src, dst: False
+    _rename_atomic = lambda src, dst: False
+
+    try:
+        import ctypes
+        _MOVEFILE_REPLACE_EXISTING = 0x1
+        _MOVEFILE_WRITE_THROUGH = 0x8
+        _MoveFileEx = ctypes.windll.kernel32.MoveFileExW
+
+        def _rename(src, dst):
+            if not isinstance(src, unicode):
+                src = unicode(src, sys.getfilesystemencoding())
+            if not isinstance(dst, unicode):
+                dst = unicode(dst, sys.getfilesystemencoding())
+            if _rename_atomic(src, dst):
+                return True
+            return _MoveFileEx(src, dst, _MOVEFILE_REPLACE_EXISTING |
+                                         _MOVEFILE_WRITE_THROUGH)
+
+        _CreateTransaction = ctypes.windll.ktmw32.CreateTransaction
+        _CommitTransaction = ctypes.windll.ktmw32.CommitTransaction
+        _MoveFileTransacted = ctypes.windll.kernel32.MoveFileTransactedW
+        _CloseHandle = ctypes.windll.kernel32.CloseHandle
+        can_rename_open_file = True
+
+        def _rename_atomic(src, dst):
+            ta = _CreateTransaction(None, 0, 0, 0, 0, 1000, 'Werkzeug rename')
+            if ta == -1:
+                return False
+            try:
+                return (_MoveFileTransacted(src, dst, None, None,
+                                            _MOVEFILE_REPLACE_EXISTING |
+                                            _MOVEFILE_WRITE_THROUGH, ta)
+                        and _CommitTransaction(ta))
+            finally:
+                _CloseHandle(ta)
+    except Exception:
+        pass
+
+    def rename(src, dst):
+        # Try atomic or pseudo-atomic rename
+        if _rename(src, dst):
+            return
+        # Fall back to "move away and replace"
         try:
-            os.rename(oldname, newname)
-        except OSError, err:
-            # 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.
-            # The usual race condition this introduces can't be avoided as
-            # we need the name to rename into, and not the file itself. Due
-            # to the nature of the operation however, any races will at worst
-            # lead to the rename failing and the current operation aborting.
+            os.rename(src, dst)
+        except OSError, e:
+            if e.errno != errno.EEXIST:
+                raise
+            old = "%s-%08x" % (dst, random.randint(0, sys.maxint))
+            os.rename(dst, old)
+            os.rename(src, dst)
+            try:
+                os.unlink(old)
+            except Exception:
+                pass
+else:
+    rename = os.rename
+    can_rename_open_file = True
 
-            if err.errno != errno.EEXIST:
-                raise
-
-            def tempname(prefix):
-                for tries in xrange(10):
-                    temp = '%s-%08x' % (prefix, random.randint(0, 0xffffffff))
-                    if not os.path.exists(temp):
-                        return temp
-                raise IOError, (errno.EEXIST, "No usable temporary filename found")
-
-            temp = tempname(newname)
-            os.rename(newname, temp)
-            os.unlink(temp)
-            os.rename(oldname, newname)
-    else:
-        # POSIX: just do it :)
-        os.rename(oldname, newname)
+# end copy of werkzeug.posixemulation
 
 rename_overwrite = rename