mirror of
https://github.com/borgbackup/borg.git
synced 2024-12-25 09:19:31 +00:00
Merge pull request #335 from ThomasWaldmann/save-atime-ctime
backup atime and ctime additionally to mtime, fixes #317
This commit is contained in:
commit
fe87cb9c9f
4 changed files with 51 additions and 13 deletions
|
@ -18,7 +18,8 @@
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from . import xattr
|
from . import xattr
|
||||||
from .helpers import parse_timestamp, Error, uid2user, user2uid, gid2group, group2gid, format_timedelta, \
|
from .helpers import parse_timestamp, Error, uid2user, user2uid, gid2group, group2gid, format_timedelta, \
|
||||||
Manifest, Statistics, decode_dict, st_mtime_ns, make_path_safe, StableDict, int_to_bigint, bigint_to_int, have_cython
|
Manifest, Statistics, decode_dict, make_path_safe, StableDict, int_to_bigint, bigint_to_int, have_cython, \
|
||||||
|
st_atime_ns, st_ctime_ns, st_mtime_ns
|
||||||
if have_cython():
|
if have_cython():
|
||||||
from .platform import acl_get, acl_set
|
from .platform import acl_get, acl_set
|
||||||
from .chunker import Chunker
|
from .chunker import Chunker
|
||||||
|
@ -384,12 +385,17 @@ def restore_attrs(self, path, item, symlink=False, fd=None):
|
||||||
elif has_lchmod: # Not available on Linux
|
elif has_lchmod: # Not available on Linux
|
||||||
os.lchmod(path, item[b'mode'])
|
os.lchmod(path, item[b'mode'])
|
||||||
mtime = bigint_to_int(item[b'mtime'])
|
mtime = bigint_to_int(item[b'mtime'])
|
||||||
|
if b'atime' in item:
|
||||||
|
atime = bigint_to_int(item[b'atime'])
|
||||||
|
else:
|
||||||
|
# old archives only had mtime in item metadata
|
||||||
|
atime = mtime
|
||||||
if fd and utime_supports_fd: # Python >= 3.3
|
if fd and utime_supports_fd: # Python >= 3.3
|
||||||
os.utime(fd, None, ns=(mtime, mtime))
|
os.utime(fd, None, ns=(atime, mtime))
|
||||||
elif utime_supports_follow_symlinks: # Python >= 3.3
|
elif utime_supports_follow_symlinks: # Python >= 3.3
|
||||||
os.utime(path, None, ns=(mtime, mtime), follow_symlinks=False)
|
os.utime(path, None, ns=(atime, mtime), follow_symlinks=False)
|
||||||
elif not symlink:
|
elif not symlink:
|
||||||
os.utime(path, (mtime / 1e9, mtime / 1e9))
|
os.utime(path, (atime / 1e9, mtime / 1e9))
|
||||||
acl_set(path, item, self.numeric_owner)
|
acl_set(path, item, self.numeric_owner)
|
||||||
# Only available on OS X and FreeBSD
|
# Only available on OS X and FreeBSD
|
||||||
if has_lchflags and b'bsdflags' in item:
|
if has_lchflags and b'bsdflags' in item:
|
||||||
|
@ -428,7 +434,9 @@ def stat_attrs(self, st, path):
|
||||||
b'mode': st.st_mode,
|
b'mode': st.st_mode,
|
||||||
b'uid': st.st_uid, b'user': uid2user(st.st_uid),
|
b'uid': st.st_uid, b'user': uid2user(st.st_uid),
|
||||||
b'gid': st.st_gid, b'group': gid2group(st.st_gid),
|
b'gid': st.st_gid, b'group': gid2group(st.st_gid),
|
||||||
b'mtime': int_to_bigint(st_mtime_ns(st))
|
b'atime': int_to_bigint(st_atime_ns(st)),
|
||||||
|
b'ctime': int_to_bigint(st_ctime_ns(st)),
|
||||||
|
b'mtime': int_to_bigint(st_mtime_ns(st)),
|
||||||
}
|
}
|
||||||
if self.numeric_owner:
|
if self.numeric_owner:
|
||||||
item[b'user'] = item[b'group'] = None
|
item[b'user'] = item[b'group'] = None
|
||||||
|
|
13
borg/fuse.py
13
borg/fuse.py
|
@ -14,7 +14,7 @@
|
||||||
import msgpack
|
import msgpack
|
||||||
|
|
||||||
# Does this version of llfuse support ns precision?
|
# Does this version of llfuse support ns precision?
|
||||||
have_fuse_mtime_ns = hasattr(llfuse.EntryAttributes, 'st_mtime_ns')
|
have_fuse_xtime_ns = hasattr(llfuse.EntryAttributes, 'st_mtime_ns')
|
||||||
|
|
||||||
|
|
||||||
class ItemCache:
|
class ItemCache:
|
||||||
|
@ -155,14 +155,15 @@ def getattr(self, inode):
|
||||||
entry.st_size = size
|
entry.st_size = size
|
||||||
entry.st_blksize = 512
|
entry.st_blksize = 512
|
||||||
entry.st_blocks = dsize / 512
|
entry.st_blocks = dsize / 512
|
||||||
if have_fuse_mtime_ns:
|
# note: older archives only have mtime (not atime nor ctime)
|
||||||
entry.st_atime_ns = item[b'mtime']
|
if have_fuse_xtime_ns:
|
||||||
|
entry.st_atime_ns = item.get(b'atime') or item[b'mtime']
|
||||||
entry.st_mtime_ns = item[b'mtime']
|
entry.st_mtime_ns = item[b'mtime']
|
||||||
entry.st_ctime_ns = item[b'mtime']
|
entry.st_ctime_ns = item.get(b'ctime') or item[b'mtime']
|
||||||
else:
|
else:
|
||||||
entry.st_atime = item[b'mtime'] / 1e9
|
entry.st_atime = (item.get(b'atime') or item[b'mtime']) / 1e9
|
||||||
entry.st_mtime = item[b'mtime'] / 1e9
|
entry.st_mtime = item[b'mtime'] / 1e9
|
||||||
entry.st_ctime = item[b'mtime'] / 1e9
|
entry.st_ctime = (item.get(b'ctime') or item[b'mtime']) / 1e9
|
||||||
return entry
|
return entry
|
||||||
|
|
||||||
def listxattr(self, inode):
|
def listxattr(self, inode):
|
||||||
|
|
|
@ -747,7 +747,13 @@ def items(self):
|
||||||
|
|
||||||
|
|
||||||
if sys.version < '3.3':
|
if sys.version < '3.3':
|
||||||
# st_mtime_ns attribute only available in 3.3+
|
# st_xtime_ns attributes only available in 3.3+
|
||||||
|
def st_atime_ns(st):
|
||||||
|
return int(st.st_atime * 1e9)
|
||||||
|
|
||||||
|
def st_ctime_ns(st):
|
||||||
|
return int(st.st_ctime * 1e9)
|
||||||
|
|
||||||
def st_mtime_ns(st):
|
def st_mtime_ns(st):
|
||||||
return int(st.st_mtime * 1e9)
|
return int(st.st_mtime * 1e9)
|
||||||
|
|
||||||
|
@ -757,6 +763,12 @@ def unhexlify(data):
|
||||||
data = data.encode('ascii')
|
data = data.encode('ascii')
|
||||||
return binascii.unhexlify(data)
|
return binascii.unhexlify(data)
|
||||||
else:
|
else:
|
||||||
|
def st_atime_ns(st):
|
||||||
|
return st.st_atime_ns
|
||||||
|
|
||||||
|
def st_ctime_ns(st):
|
||||||
|
return st.st_ctime_ns
|
||||||
|
|
||||||
def st_mtime_ns(st):
|
def st_mtime_ns(st):
|
||||||
return st.st_mtime_ns
|
return st.st_mtime_ns
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
from ..archiver import Archiver
|
from ..archiver import Archiver
|
||||||
from ..cache import Cache
|
from ..cache import Cache
|
||||||
from ..crypto import bytes_to_long, num_aes_blocks
|
from ..crypto import bytes_to_long, num_aes_blocks
|
||||||
from ..helpers import Manifest, EXIT_SUCCESS, EXIT_WARNING, EXIT_ERROR
|
from ..helpers import Manifest, EXIT_SUCCESS, EXIT_WARNING, EXIT_ERROR, st_atime_ns, st_mtime_ns
|
||||||
from ..remote import RemoteRepository, PathNotAllowed
|
from ..remote import RemoteRepository, PathNotAllowed
|
||||||
from ..repository import Repository
|
from ..repository import Repository
|
||||||
from . import BaseTestCase
|
from . import BaseTestCase
|
||||||
|
@ -286,6 +286,23 @@ def test_basic_functionality(self):
|
||||||
# end the same way as info_output
|
# end the same way as info_output
|
||||||
assert info_output2.endswith(info_output)
|
assert info_output2.endswith(info_output)
|
||||||
|
|
||||||
|
def test_atime(self):
|
||||||
|
have_root = self.create_test_files()
|
||||||
|
atime, mtime = 123456780, 234567890
|
||||||
|
os.utime('input/file1', (atime, mtime))
|
||||||
|
self.cmd('init', self.repository_location)
|
||||||
|
self.cmd('create', self.repository_location + '::test', 'input')
|
||||||
|
with changedir('output'):
|
||||||
|
self.cmd('extract', self.repository_location + '::test')
|
||||||
|
sti = os.stat('input/file1')
|
||||||
|
sto = os.stat('output/input/file1')
|
||||||
|
assert st_mtime_ns(sti) == st_mtime_ns(sto) == mtime * 1e9
|
||||||
|
if hasattr(os, 'O_NOATIME'):
|
||||||
|
assert st_atime_ns(sti) == st_atime_ns(sto) == atime * 1e9
|
||||||
|
else:
|
||||||
|
# it touched the input file's atime while backing it up
|
||||||
|
assert st_atime_ns(sto) == atime * 1e9
|
||||||
|
|
||||||
def _extract_repository_id(self, path):
|
def _extract_repository_id(self, path):
|
||||||
return Repository(self.repository_path).id
|
return Repository(self.repository_path).id
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue