Skip to content

windows: implement st_birthtime restoration, fixes #8730#9341

Open
trxvorr wants to merge 1 commit intoborgbackup:masterfrom
trxvorr:fix-8730-win-birthtime
Open

windows: implement st_birthtime restoration, fixes #8730#9341
trxvorr wants to merge 1 commit intoborgbackup:masterfrom
trxvorr:fix-8730-win-birthtime

Conversation

@trxvorr
Copy link
Contributor

@trxvorr trxvorr commented Feb 15, 2026

This PR implements the restoration of file creation time (st_birthtime) on Windows, addressing Issue #8730.

Changes

  • Platform Support: Added set_birthtime in src/borg/platform/init.py using ctypes and SetFileTime to precisely set creation time on NTFS.
  • Archiver: Updated Archive.restore_attrs to call set_birthtime on Windows when birthtime metadata is present.
  • Tests:
    • Added unit (win32_birthtime_test.py) and E2E (win32_birthtime_e2e_test.py) tests verifying birthtime preservation.
    • Updated is_birthtime_fully_supported and fixed test_nobirthtime to validly test birthtime behavior on Windows (handling st_birthtime != st_mtime semantics).

Verification

  • Verified on Windows with Python 3.12+.
  • New unit and E2E tests pass.
  • Existing test_nobirthtime now passes locally on Windows.

@trxvorr trxvorr force-pushed the fix-8730-win-birthtime branch from d6dbc75 to 2f50ad0 Compare February 15, 2026 15:50
@codecov
Copy link

codecov bot commented Feb 15, 2026

Codecov Report

❌ Patch coverage is 33.33333% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 76.01%. Comparing base (3349cdc) to head (91be6ae).
⚠️ Report is 1 commits behind head on master.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/borg/platform/__init__.py 20.00% 4 Missing ⚠️
src/borg/archive.py 33.33% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #9341      +/-   ##
==========================================
- Coverage   76.03%   76.01%   -0.03%     
==========================================
  Files          85       85              
  Lines       14744    14752       +8     
  Branches     2194     2195       +1     
==========================================
+ Hits        11211    11213       +2     
- Misses       2856     2862       +6     
  Partials      677      677              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@trxvorr trxvorr force-pushed the fix-8730-win-birthtime branch 6 times, most recently from 9b57619 to a1face0 Compare February 15, 2026 19:59
@trxvorr trxvorr force-pushed the fix-8730-win-birthtime branch 3 times, most recently from 2037eea to 55000f7 Compare February 15, 2026 20:56
@trxvorr trxvorr force-pushed the fix-8730-win-birthtime branch from 55000f7 to 91be6ae Compare February 15, 2026 20:58
raise NotImplementedError


def set_birthtime(path, birthtime_ns):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there also a way to do this with an open file descriptor?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a lot of borg file functions take a file descriptor and a path. if the (open) file descriptor is provided, the function uses that and if not, it falls back to using the path.

Comment on lines +216 to +217
if sys.platform == "win32":
platform.set_birthtime("input/file1", birthtime_ns)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is_win32

also: don't kill it for other platforms.

create_test_files(archiver.input_path)
birthtime, mtime, atime = 946598400, 946684800, 946771200
os.utime("input/file1", (atime, birthtime))
set_birthtime("input/file1", birthtime * 1_000_000_000) # noqa: F821
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't kill it for other platforms.

Comment on lines +181 to +182
# allow for small differences (e.g. 10ms)
assert abs(sto.st_birthtime * 1e9 - birthtime * 1e9) < 10_000_000
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we generally have to expect up to 10ms variance on windows, this could be moved into same_ts_ns.

Comment on lines +42 to +43
if not os.path.exists("output"):
os.makedirs("output")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't we always have the output dir?

also: check makedirs parameters.

@@ -0,0 +1,52 @@
import os
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in the end, this should be in the tests of the extract command.


from ..fuse_impl import llfuse, has_any_fuse, has_llfuse, has_pyfuse3, has_mfusepy, ENOATTR # NOQA
from .. import platform
from borg import platform as borg_platform
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't do absolute imports.

new_stats = os.stat(filepath, follow_symlinks=False)
if new_stats.st_birthtime == birthtime and new_stats.st_mtime == mtime and new_stats.st_atime == atime:
birthtime_ns, mtime_ns, atime_ns = 946598400 * 10**9, 946684800 * 10**9, 946771200 * 10**9
borg_platform.set_birthtime(filepath, birthtime_ns)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't kill it for other platforms.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants