Browse Source

Merge pull request #335 from ThomasWaldmann/save-atime-ctime

backup atime and ctime additionally to mtime, fixes #317
TW 9 years ago
parent
commit
fe87cb9c9f
4 changed files with 51 additions and 13 deletions
  1. 13 5
      borg/archive.py
  2. 7 6
      borg/fuse.py
  3. 13 1
      borg/helpers.py
  4. 18 1
      borg/testsuite/archiver.py

+ 13 - 5
borg/archive.py

@@ -18,7 +18,8 @@ import time
 from io import BytesIO
 from . import xattr
 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():
     from .platform import acl_get, acl_set
     from .chunker import Chunker
@@ -384,12 +385,17 @@ Number of files: {0.stats.nfiles}'''.format(self)
         elif has_lchmod:  # Not available on Linux
             os.lchmod(path, item[b'mode'])
         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
-            os.utime(fd, None, ns=(mtime, mtime))
+            os.utime(fd, None, ns=(atime, mtime))
         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:
-            os.utime(path, (mtime / 1e9, mtime / 1e9))
+            os.utime(path, (atime / 1e9, mtime / 1e9))
         acl_set(path, item, self.numeric_owner)
         # Only available on OS X and FreeBSD
         if has_lchflags and b'bsdflags' in item:
@@ -428,7 +434,9 @@ Number of files: {0.stats.nfiles}'''.format(self)
             b'mode': st.st_mode,
             b'uid': st.st_uid, b'user': uid2user(st.st_uid),
             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:
             item[b'user'] = item[b'group'] = None

+ 7 - 6
borg/fuse.py

@@ -14,7 +14,7 @@ if have_cython():
     import msgpack
 
 # 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:
@@ -155,14 +155,15 @@ class FuseOperations(llfuse.Operations):
         entry.st_size = size
         entry.st_blksize = 512
         entry.st_blocks = dsize / 512
-        if have_fuse_mtime_ns:
-            entry.st_atime_ns = item[b'mtime']
+        # note: older archives only have mtime (not atime nor ctime)
+        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_ctime_ns = item[b'mtime']
+            entry.st_ctime_ns = item.get(b'ctime') or item[b'mtime']
         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_ctime = item[b'mtime'] / 1e9
+            entry.st_ctime = (item.get(b'ctime') or item[b'mtime']) / 1e9
         return entry
 
     def listxattr(self, inode):

+ 13 - 1
borg/helpers.py

@@ -747,7 +747,13 @@ class StableDict(dict):
 
 
 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):
         return int(st.st_mtime * 1e9)
 
@@ -757,6 +763,12 @@ if sys.version < '3.3':
             data = data.encode('ascii')
         return binascii.unhexlify(data)
 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):
         return st.st_mtime_ns
 

+ 18 - 1
borg/testsuite/archiver.py

@@ -20,7 +20,7 @@ from ..archive import Archive, ChunkBuffer, CHUNK_MAX_EXP
 from ..archiver import Archiver
 from ..cache import Cache
 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 ..repository import Repository
 from . import BaseTestCase
@@ -286,6 +286,23 @@ class ArchiverTestCase(ArchiverTestCaseBase):
         # end the same way as 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):
         return Repository(self.repository_path).id