Преглед на файлове

Fix crash on extreme mtime timestamps (year 2400+)

Closes #81
Jonas Borgström преди 11 години
родител
ревизия
3ab53b776d
променени са 6 файла, в които са добавени 43 реда и са изтрити 9 реда
  1. 1 0
      CHANGES
  2. 6 5
      attic/archive.py
  3. 2 2
      attic/archiver.py
  4. 19 1
      attic/helpers.py
  5. 2 0
      attic/testsuite/archiver.py
  6. 13 1
      attic/testsuite/helpers.py

+ 1 - 0
CHANGES

@@ -15,6 +15,7 @@ Version 0.13
 - Fix bug where xattrs on symlinks were not correctly restored
 - Added cachedir support. CACHEDIR.TAG compatible cache directories
   can now be excluded using ``--exclude-caches`` (#74)
+- Fix crash on extreme mtime timestamps (year 2400+) (#81)
 
 Version 0.12
 ------------

+ 6 - 5
attic/archive.py

@@ -18,7 +18,7 @@ from attic.platform import acl_get, acl_set
 from attic.chunker import chunkify
 from attic.hashindex import ChunkIndex
 from attic.helpers import Error, uid2user, user2uid, gid2group, group2gid, \
-    Manifest, Statistics, decode_dict, st_mtime_ns, make_path_safe, StableDict
+    Manifest, Statistics, decode_dict, st_mtime_ns, make_path_safe, StableDict, int_to_bigint, bigint_to_int
 
 ITEMS_BUFFER = 1024 * 1024
 CHUNK_MIN = 1024
@@ -311,12 +311,13 @@ class Archive:
             os.chmod(path, item[b'mode'])
         elif has_lchmod:  # Not available on Linux
             os.lchmod(path, item[b'mode'])
+        mtime = bigint_to_int(item[b'mtime'])
         if fd and utime_supports_fd:  # Python >= 3.3
-            os.utime(fd, None, ns=(item[b'mtime'], item[b'mtime']))
+            os.utime(fd, None, ns=(mtime, mtime))
         elif utime_supports_fd:  # Python >= 3.3
-            os.utime(path, None, ns=(item[b'mtime'], item[b'mtime']), follow_symlinks=False)
+            os.utime(path, None, ns=(mtime, mtime), follow_symlinks=False)
         elif not symlink:
-            os.utime(path, (item[b'mtime'] / 10**9, item[b'mtime'] / 10**9))
+            os.utime(path, (mtime / 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:
@@ -343,7 +344,7 @@ class Archive:
             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': st_mtime_ns(st),
+            b'mtime': int_to_bigint(st_mtime_ns(st))
         }
         if self.numeric_owner:
             item[b'user'] = item[b'group'] = None

+ 2 - 2
attic/archiver.py

@@ -18,7 +18,7 @@ from attic.helpers import Error, location_validator, format_time, \
     format_file_mode, ExcludePattern, exclude_path, adjust_patterns, to_localtime, \
     get_cache_dir, get_keys_dir, format_timedelta, prune_within, prune_split, \
     Manifest, remove_surrogates, update_excludes, format_archive, check_extension_modules, Statistics, \
-    is_cachedir
+    is_cachedir, bigint_to_int
 from attic.remote import RepositoryServer, RemoteRepository
 
 
@@ -272,7 +272,7 @@ Type "Yes I am sure" if you understand this and want to continue.\n""")
                         size = sum(size for _, size, _ in item[b'chunks'])
                     except KeyError:
                         pass
-                mtime = format_time(datetime.fromtimestamp(item[b'mtime'] / 10**9))
+                mtime = format_time(datetime.fromtimestamp(bigint_to_int(item[b'mtime']) / 1e9))
                 if b'source' in item:
                     if type == 'l':
                         extra = ' -> %s' % item[b'source']

+ 19 - 1
attic/helpers.py

@@ -280,7 +280,7 @@ def walk_path(path, skip_inodes=None):
 def format_time(t):
     """Format datetime suitable for fixed length list output
     """
-    if (datetime.now() - t).days < 365:
+    if abs((datetime.now() - t).days) < 365:
         return t.strftime('%b %d %H:%M')
     else:
         return t.strftime('%b %d  %Y')
@@ -548,3 +548,21 @@ else:
         return st.st_mtime_ns
 
     unhexlify = binascii.unhexlify
+
+
+def bigint_to_int(mtime):
+    """Convert bytearray to int
+    """
+    if isinstance(mtime, bytes):
+        return int.from_bytes(mtime, 'little', signed=True)
+    return mtime
+
+
+def int_to_bigint(value):
+    """Convert integers larger than 64 bits to bytearray
+
+    Smaller integers are left alone
+    """
+    if value.bit_length() > 63:
+        return value.to_bytes((value.bit_length() + 9) // 8, 'little', signed=True)
+    return value

+ 2 - 0
attic/testsuite/archiver.py

@@ -120,6 +120,8 @@ class ArchiverTestCase(ArchiverTestCaseBase):
         """
         # File
         self.create_regular_file('empty', size=0)
+        # 2600-01-01 > 2**64 ns
+        os.utime('input/empty', (19880895600, 19880895600))
         self.create_regular_file('file1', size=1024 * 80)
         self.create_regular_file('flagfile', size=1024)
         # Directory

+ 13 - 1
attic/testsuite/helpers.py

@@ -5,11 +5,23 @@ import os
 import tempfile
 import unittest
 from attic.helpers import adjust_patterns, exclude_path, Location, format_timedelta, IncludePattern, ExcludePattern, make_path_safe, UpgradableLock, prune_within, prune_split, to_localtime, \
-    StableDict
+    StableDict, int_to_bigint, bigint_to_int
 from attic.testsuite import AtticTestCase
 import msgpack
 
 
+class BigIntTestCase(AtticTestCase):
+
+    def test_bigint(self):
+        self.assert_equal(int_to_bigint(0), 0)
+        self.assert_equal(int_to_bigint(2**63-1), 2**63-1)
+        self.assert_equal(int_to_bigint(-2**63+1), -2**63+1)
+        self.assert_equal(int_to_bigint(2**63), b'\x00\x00\x00\x00\x00\x00\x00\x80\x00')
+        self.assert_equal(int_to_bigint(-2**63), b'\x00\x00\x00\x00\x00\x00\x00\x80\xff')
+        self.assert_equal(bigint_to_int(int_to_bigint(-2**70)), -2**70)
+        self.assert_equal(bigint_to_int(int_to_bigint(2**70)), 2**70)
+
+
 class LocationTestCase(AtticTestCase):
 
     def test(self):