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:
commit
bf392367f1
2 changed files with 24 additions and 10 deletions
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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'
|
||||
|
|
Loading…
Reference in a new issue