Kaynağa Gözat

Add platform.SaveFile

Marian Beermann 9 yıl önce
ebeveyn
işleme
36ebc82748

+ 1 - 1
src/borg/platform/__init__.py

@@ -8,7 +8,7 @@ Public APIs are documented in platform.base.
 
 from .base import acl_get, acl_set
 from .base import set_flags, get_flags
-from .base import SyncFile, sync_dir, fdatasync
+from .base import SaveFile, SyncFile, sync_dir, fdatasync
 from .base import swidth, API_VERSION
 
 if sys.platform.startswith('linux'):  # pragma: linux only

+ 42 - 2
src/borg/platform/base.py

@@ -80,8 +80,11 @@ class SyncFile:
     TODO: A Windows implementation should use CreateFile with FILE_FLAG_WRITE_THROUGH.
     """
 
-    def __init__(self, path):
-        self.fd = open(path, 'xb')
+    def __init__(self, path, binary=True):
+        mode = 'x'
+        if binary:
+            mode += 'b'
+        self.fd = open(path, mode)
         self.fileno = self.fd.fileno()
 
     def __enter__(self):
@@ -112,6 +115,43 @@ class SyncFile:
         platform.sync_dir(os.path.dirname(self.fd.name))
 
 
+class SaveFile:
+    """
+    Update file contents atomically.
+
+    Must be used as a context manager (defining the scope of the transaction).
+
+    On a journaling file system the file contents are always updated
+    atomically and won't become corrupted, even on pure failures or
+    crashes (for caveats see SyncFile).
+    """
+
+    SUFFIX = '.tmp'
+
+    def __init__(self, path, binary=True):
+        self.binary = binary
+        self.path = path
+        self.tmppath = self.path + self.SUFFIX
+
+    def __enter__(self):
+        from .. import platform
+        try:
+            os.unlink(self.tmppath)
+        except OSError:
+            pass
+        self.fd = platform.SyncFile(self.tmppath, self.binary)
+        return self.fd
+
+    def __exit__(self, exc_type, exc_val, exc_tb):
+        from .. import platform
+        self.fd.close()
+        if exc_type is not None:
+            os.unlink(self.tmppath)
+            return
+        os.rename(self.tmppath, self.path)
+        platform.sync_dir(os.path.dirname(self.path))
+
+
 def swidth(s):
     """terminal output width of string <s>
 

+ 2 - 2
src/borg/platform/linux.pyx

@@ -228,8 +228,8 @@ class SyncFile(BaseSyncFile):
     disk in the immediate future.
     """
 
-    def __init__(self, path):
-        super().__init__(path)
+    def __init__(self, path, binary=True):
+        super().__init__(path, binary)
         self.offset = 0
         self.write_window = (16 * 1024 ** 2) & ~PAGE_MASK
         self.last_sync = 0