瀏覽代碼

Merge pull request #6176 from KN4CK3R/backport-6056

Backport atomically create the CACHE_TAG file, see #6028 (#6056)
TW 3 年之前
父節點
當前提交
233b9d1d88
共有 2 個文件被更改,包括 24 次插入10 次删除
  1. 16 9
      src/borg/helpers.py
  2. 8 1
      src/borg/platform/base.py

+ 16 - 9
src/borg/helpers.py

@@ -584,15 +584,22 @@ def get_cache_dir():
     cache_dir = os.environ.get('BORG_CACHE_DIR', os.path.join(cache_home, 'borg'))
     # Create path if it doesn't exist yet
     ensure_dir(cache_dir)
-    cache_fn = os.path.join(cache_dir, CACHE_TAG_NAME)
-    if not os.path.exists(cache_fn):
-        with open(cache_fn, 'wb') as fd:
-            fd.write(CACHE_TAG_CONTENTS)
-            fd.write(textwrap.dedent("""
-            # This file is a cache directory tag created by Borg.
-            # For information about cache directory tags, see:
-            #       http://www.bford.info/cachedir/spec.html
-            """).encode('ascii'))
+    cache_tag_fn = os.path.join(cache_dir, CACHE_TAG_NAME)
+    if not os.path.exists(cache_tag_fn):
+        cache_tag_contents = CACHE_TAG_CONTENTS + textwrap.dedent("""
+        # This file is a cache directory tag created by Borg.
+        # For information about cache directory tags, see:
+        #       http://www.bford.info/cachedir/spec.html
+        """).encode('ascii')
+        from .platform import SaveFile
+        try:
+            with SaveFile(cache_tag_fn, binary=True) as fd:
+                fd.write(cache_tag_contents)
+        except FileExistsError:
+            # if we have multiple SaveFile calls running in parallel for same cache_tag_fn,
+            # it is fine if just one (usually first/quicker one) of them run gets through
+            # and all others raise FileExistsError.
+            pass
     return cache_dir
 
 

+ 8 - 1
src/borg/platform/base.py

@@ -97,12 +97,14 @@ class SyncFile:
     Note that POSIX doesn't specify *anything* about power failures (or similar failures). A system that
     routinely loses files or corrupts file on power loss is POSIX compliant.
 
+    Calling SyncFile(path) for an existing path will raise FileExistsError, see comment in __init__.
+
     TODO: Use F_FULLSYNC on OSX.
     TODO: A Windows implementation should use CreateFile with FILE_FLAG_WRITE_THROUGH.
     """
 
     def __init__(self, path, binary=False):
-        mode = 'xb' if binary else 'x'
+        mode = 'xb' if binary else 'x'  # x -> raise FileExists exception in open() if file exists already
         self.fd = open(path, mode)
         self.fileno = self.fd.fileno()
 
@@ -149,6 +151,11 @@ class SaveFile:
     On a journaling file system the file contents are always updated
     atomically and won't become corrupted, even on power failures or
     crashes (for caveats see SyncFile).
+
+    Calling SaveFile(path) in parallel for same path is safe (even when using the same SUFFIX), but the
+    caller needs to catch potential FileExistsError exceptions that may happen in this racy situation.
+    The caller executing SaveFile->SyncFile->open() first will win.
+    All other callers will raise a FileExistsError in open(), at least until the os.replace is executed.
     """
 
     SUFFIX = '.tmp'