Fix crash on extreme mtime timestamps (year 2400+)

Closes #81
This commit is contained in:
Jonas Borgström 2014-05-18 18:28:26 +02:00
parent 11687fbec1
commit 3ab53b776d
6 changed files with 43 additions and 9 deletions

View File

@ -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
------------

View File

@ -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

View File

@ -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']

View File

@ -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

View File

@ -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

View File

@ -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):