mirror of
https://github.com/borgbackup/borg.git
synced 2025-02-23 14:41:43 +00:00
process_file: avoid race condition: stat data vs. content
always open the file and then do all operations with the fd: - fstat - read - get xattrs, acls, bsdflags
This commit is contained in:
parent
dca04ba538
commit
677102f292
1 changed files with 49 additions and 49 deletions
|
@ -1126,60 +1126,60 @@ def process_stdin(self, path, cache):
|
|||
|
||||
def process_file(self, path, st, cache):
|
||||
with self.create_helper(path, st, None) as (item, status, hardlinked, hardlink_master): # no status yet
|
||||
md = None
|
||||
is_special_file = is_special(st.st_mode)
|
||||
if not hardlinked or hardlink_master:
|
||||
if not is_special_file:
|
||||
path_hash = self.key.id_hash(safe_encode(os.path.join(self.cwd, path)))
|
||||
known, ids = cache.file_known_and_unchanged(path_hash, st)
|
||||
else:
|
||||
# in --read-special mode, we may be called for special files.
|
||||
# there should be no information in the cache about special files processed in
|
||||
# read-special mode, but we better play safe as this was wrong in the past:
|
||||
path_hash = None
|
||||
known, ids = False, None
|
||||
chunks = None
|
||||
if ids is not None:
|
||||
# Make sure all ids are available
|
||||
for id_ in ids:
|
||||
if not cache.seen_chunk(id_):
|
||||
status = 'M' # cache said it is unmodified, but we lost a chunk: process file like modified
|
||||
break
|
||||
with backup_io('open'):
|
||||
fd = Archive._open_rb(path)
|
||||
try:
|
||||
with backup_io('fstat'):
|
||||
curr_st = os.fstat(fd)
|
||||
# XXX do some checks here: st vs. curr_st
|
||||
assert stat.S_ISREG(curr_st.st_mode)
|
||||
# make sure stats refer to same object that we are processing below
|
||||
st = curr_st
|
||||
is_special_file = is_special(st.st_mode)
|
||||
if not hardlinked or hardlink_master:
|
||||
if not is_special_file:
|
||||
path_hash = self.key.id_hash(safe_encode(os.path.join(self.cwd, path)))
|
||||
known, ids = cache.file_known_and_unchanged(path_hash, st)
|
||||
else:
|
||||
chunks = [cache.chunk_incref(id_, self.stats) for id_ in ids]
|
||||
status = 'U' # regular file, unchanged
|
||||
else:
|
||||
status = 'M' if known else 'A' # regular file, modified or added
|
||||
item.hardlink_master = hardlinked
|
||||
item.update(self.metadata_collector.stat_simple_attrs(st))
|
||||
# Only chunkify the file if needed
|
||||
if chunks is not None:
|
||||
item.chunks = chunks
|
||||
else:
|
||||
with backup_io('open'):
|
||||
fh = Archive._open_rb(path)
|
||||
try:
|
||||
self.process_file_chunks(item, cache, self.stats, self.show_progress, backup_io_iter(self.chunker.chunkify(None, fh)))
|
||||
md = self.metadata_collector.stat_attrs(st, path, fd=fh)
|
||||
finally:
|
||||
os.close(fh)
|
||||
# in --read-special mode, we may be called for special files.
|
||||
# there should be no information in the cache about special files processed in
|
||||
# read-special mode, but we better play safe as this was wrong in the past:
|
||||
path_hash = None
|
||||
known, ids = False, None
|
||||
chunks = None
|
||||
if ids is not None:
|
||||
# Make sure all ids are available
|
||||
for id_ in ids:
|
||||
if not cache.seen_chunk(id_):
|
||||
status = 'M' # cache said it is unmodified, but we lost a chunk: process file like modified
|
||||
break
|
||||
else:
|
||||
chunks = [cache.chunk_incref(id_, self.stats) for id_ in ids]
|
||||
status = 'U' # regular file, unchanged
|
||||
else:
|
||||
status = 'M' if known else 'A' # regular file, modified or added
|
||||
item.hardlink_master = hardlinked
|
||||
item.update(self.metadata_collector.stat_simple_attrs(st))
|
||||
# Only chunkify the file if needed
|
||||
if chunks is not None:
|
||||
item.chunks = chunks
|
||||
else:
|
||||
with backup_io('read'):
|
||||
self.process_file_chunks(item, cache, self.stats, self.show_progress, backup_io_iter(self.chunker.chunkify(None, fd)))
|
||||
if not is_special_file:
|
||||
# we must not memorize special files, because the contents of e.g. a
|
||||
# block or char device will change without its mtime/size/inode changing.
|
||||
cache.memorize_file(path_hash, st, [c.id for c in item.chunks])
|
||||
self.stats.nfiles += 1
|
||||
if md is None:
|
||||
fh = Archive._open_rb(path)
|
||||
try:
|
||||
md = self.metadata_collector.stat_attrs(st, path, fd=fh)
|
||||
finally:
|
||||
os.close(fh)
|
||||
item.update(md)
|
||||
item.get_size(memorize=True)
|
||||
if is_special_file:
|
||||
# we processed a special file like a regular file. reflect that in mode,
|
||||
# so it can be extracted / accessed in FUSE mount like a regular file:
|
||||
item.mode = stat.S_IFREG | stat.S_IMODE(item.mode)
|
||||
self.stats.nfiles += 1
|
||||
md = self.metadata_collector.stat_attrs(st, path, fd=fd)
|
||||
item.update(md)
|
||||
item.get_size(memorize=True)
|
||||
if is_special_file:
|
||||
# we processed a special file like a regular file. reflect that in mode,
|
||||
# so it can be extracted / accessed in FUSE mount like a regular file:
|
||||
item.mode = stat.S_IFREG | stat.S_IMODE(item.mode)
|
||||
finally:
|
||||
os.close(fd)
|
||||
return status
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue