1
0
Fork 0
mirror of https://github.com/borgbackup/borg.git synced 2025-02-22 06:01:54 +00:00

Merge pull request #6056 from ThomasWaldmann/fix-cache-tag-race-master

atomically create the CACHE_TAG file, see #6028
This commit is contained in:
TW 2021-12-12 22:54:11 +01:00 committed by GitHub
commit bf392367f1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 24 additions and 10 deletions

View file

@ -92,15 +92,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

View file

@ -141,12 +141,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()
@ -193,6 +195,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'