Skip to content
Merged

Y2038+ #9430

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions src/borg/helpers/time.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,21 @@ def parse_local_timestamp(timestamp, tzinfo=None):
return dt


_EPOCH = datetime(1970, 1, 1, tzinfo=timezone.utc)


def utcfromtimestampns(ts_ns: int) -> datetime:
# similar to datetime.fromtimestamp, but works with ns and avoids floating point.
# also, it would avoid an overflow on 32bit platforms with old glibc.
return _EPOCH + timedelta(microseconds=ts_ns // 1000)


def timestamp(s):
"""Convert a --timestamp=s argument to a datetime object."""
try:
# is it pointing to a file / directory?
ts = safe_s(os.stat(s).st_mtime)
return datetime.fromtimestamp(ts, tz=timezone.utc)
ts_ns = safe_ns(os.stat(s).st_mtime_ns)
return utcfromtimestampns(ts_ns)
except OSError:
# didn't work, try parsing as an ISO timestamp. if no TZ is given, we assume local timezone.
return parse_local_timestamp(s)
Expand All @@ -44,7 +53,7 @@ def timestamp(s):
# As long as people are using borg on 32bit platforms to access borg archives, we must
# keep this value True. But we can expect that we can stop supporting 32bit platforms
# well before coming close to the year 2038, so this will never be a practical problem.
SUPPORT_32BIT_PLATFORMS = True # set this to False before y2038.
SUPPORT_32BIT_PLATFORMS = False # set this to False before y2038.

if SUPPORT_32BIT_PLATFORMS:
# second timestamps will fit into a signed int32 (platform time_t limit).
Expand Down Expand Up @@ -84,7 +93,7 @@ def safe_ns(ts):

def safe_timestamp(item_timestamp_ns):
t_ns = safe_ns(item_timestamp_ns)
return datetime.fromtimestamp(t_ns / 1e9, timezone.utc) # return tz-aware utc datetime obj
return utcfromtimestampns(t_ns) # return tz-aware utc datetime obj


def format_time(ts: datetime, format_spec=""):
Expand Down
2 changes: 1 addition & 1 deletion src/borg/testsuite/archiver/create_cmd_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ def test_nobirthtime(archivers, request):
assert same_ts_ns(sti.st_birthtime * 1e9, birthtime * 1e9)
assert same_ts_ns(sto.st_birthtime * 1e9, mtime * 1e9)
assert same_ts_ns(sti.st_mtime_ns, sto.st_mtime_ns)
assert same_ts_ns(sto.st_mtime_ns, mtime * 1e9)
assert same_ts_ns(sto.st_mtime_ns, mtime * 10**9)


def test_create_stdin(archivers, request):
Expand Down
24 changes: 20 additions & 4 deletions src/borg/testsuite/archiver/extract_cmd_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,13 +153,13 @@ def has_noatime(some_file):
sti = os.stat("input/file1")
sto = os.stat("output/input/file1")
assert same_ts_ns(sti.st_mtime_ns, sto.st_mtime_ns)
assert same_ts_ns(sto.st_mtime_ns, mtime * 1e9)
assert same_ts_ns(sto.st_mtime_ns, mtime * 10**9)
if have_noatime:
assert same_ts_ns(sti.st_atime_ns, sto.st_atime_ns)
assert same_ts_ns(sto.st_atime_ns, atime * 1e9)
assert same_ts_ns(sto.st_atime_ns, atime * 10**9)
else:
# it touched the input file's atime while backing it up
assert same_ts_ns(sto.st_atime_ns, atime * 1e9)
assert same_ts_ns(sto.st_atime_ns, atime * 10**9)


@pytest.mark.skipif(not is_utime_fully_supported(), reason="cannot setup and execute test without utime")
Expand All @@ -179,7 +179,7 @@ def test_birthtime(archivers, request):
assert same_ts_ns(sti.st_birthtime * 1e9, sto.st_birthtime * 1e9)
assert same_ts_ns(sto.st_birthtime * 1e9, birthtime * 1e9)
assert same_ts_ns(sti.st_mtime_ns, sto.st_mtime_ns)
assert same_ts_ns(sto.st_mtime_ns, mtime * 1e9)
assert same_ts_ns(sto.st_mtime_ns, mtime * 10**9)


@pytest.mark.skipif(is_win32, reason="frequent test failures on github CI on win32")
Expand Down Expand Up @@ -825,3 +825,19 @@ def test_extract_existing_directory(archivers, request):
cmd(archiver, "extract", "test")
st2 = os.stat("input/dir")
assert st1.st_ino == st2.st_ino


@pytest.mark.skipif(not is_utime_fully_supported(), reason="cannot properly setup and execute test without utime")
def test_extract_y2261(archivers, request):
# test if roundtripping of timestamps well beyond y2038 works
archiver = request.getfixturevalue(archivers)
create_regular_file(archiver.input_path, "file_y2261", contents=b"post y2038 test")
# 2261-01-01 00:00:00 UTC as a Unix timestamp (seconds).
time_y2261 = 9183110400
os.utime("input/file_y2261", (time_y2261, time_y2261))
cmd(archiver, "repo-create", RK_ENCRYPTION)
cmd(archiver, "create", "test", "input")
with changedir("output"):
cmd(archiver, "extract", "test")
sto = os.stat("output/input/file_y2261")
assert same_ts_ns(sto.st_mtime_ns, time_y2261 * 10**9)
Binary file modified src/borg/testsuite/archiver/repo12.tar.gz
Binary file not shown.