Skip to content

platform: use F_FULLFSYNC on macOS for SyncFile data durability, fixes #9383#9592

Open
mr-raj12 wants to merge 1 commit intoborgbackup:1.4-maintfrom
mr-raj12:backport-9385-to-1.4-maint
Open

platform: use F_FULLFSYNC on macOS for SyncFile data durability, fixes #9383#9592
mr-raj12 wants to merge 1 commit intoborgbackup:1.4-maintfrom
mr-raj12:backport-9385-to-1.4-maint

Conversation

@mr-raj12
Copy link
Copy Markdown
Contributor

@mr-raj12 mr-raj12 commented May 4, 2026

Backport of #9385 to 1.4-maint.

SyncFile.sync() calls platform.fdatasync(self.fd), which on macOS resolves to os.fsync(). fsync() only flushes to the drive's write cache as it does not guarantee data reaches persistent storage. A power loss could silently lose data
that SyncFile believed was safely persisted.

The fix was acknowledged with a TODO in src/borg/platform/base.py: TODO: Use F_FULLSYNC on macOS.

This backport implements the platform-specific override suggested in #9383: add fdatasync() and sync_dir() to platform/darwin.pyx using fcntl(fd, F_FULLFSYNC) with an os.fsync() fallback for network filesystems that do not support F_FULLFSYNC.

Changes

File Change
src/borg/platform/darwin.pyx Add fdatasync() using fcntl F_FULLFSYNC with os.fsync() fallback; add sync_dir() using the same
src/borg/platform/__init__.py Import fdatasync, sync_dir from .darwin in the darwin block
src/borg/platform/base.py Remove resolved TODO comment
src/borg/testsuite/platform.py Add 4 tests: F_FULLFSYNC usage, fsync fallback, fdatasync integration, sync_dir integration
docs/changes.rst Add changelog entry for 1.4.x

Checklist

@codecov
Copy link
Copy Markdown

codecov Bot commented May 4, 2026

Codecov Report

❌ Patch coverage is 50.00000% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 81.94%. Comparing base (0eaa76f) to head (58e1c0f).
⚠️ Report is 1 commits behind head on 1.4-maint.

Files with missing lines Patch % Lines
src/borg/platform/__init__.py 50.00% 1 Missing ⚠️
Additional details and impacted files
@@              Coverage Diff              @@
##           1.4-maint    #9592      +/-   ##
=============================================
+ Coverage      81.92%   81.94%   +0.01%     
=============================================
  Files             38       38              
  Lines          11306    11307       +1     
  Branches        1781     1781              
=============================================
+ Hits            9263     9265       +2     
  Misses          1456     1456              
+ Partials         587      586       -1     

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

Comment thread docs/changes.rst Outdated
…borgbackup#9383

On macOS, os.fsync() only flushes to the drive's write cache, not to
persistent storage. True durability requires fcntl(fd, F_FULLFSYNC).
Without it a power loss can silently discard data that SyncFile
considered safely persisted.

Add fdatasync() and sync_dir() to darwin.pyx using F_FULLFSYNC with
an os.fsync() fallback for network filesystems that do not support it.
Import them in platform/__init__.py so they override the base
implementations on macOS.

Backport of borgbackup#9385 to 1.4-maint.

Signed-off-by: Mrityunjay Raj <mr.raj.earth@gmail.com>
@mr-raj12 mr-raj12 force-pushed the backport-9385-to-1.4-maint branch from b186f07 to 58e1c0f Compare May 5, 2026 00:00
Comment on lines +7 to +14
from ..platformflags import (
is_win32,
is_linux,
is_freebsd,
is_darwin,
is_cygwin,
is_haiku,
)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

there is still a black change.

@ThomasWaldmann
Copy link
Copy Markdown
Member

I ran the tests on macOS (ARM), they passed.

Comment on lines +268 to +269
On macOS, os.fsync() only flushes to the drive's write cache.
fcntl F_FULLFSYNC flushes to persistent storage.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The first sentence is a bit weird. It "only" flushes the drive's write cache. What else is there to flush that FULLSYNC does?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

fsync() on macOS

fsync() (and Python's os.fsync()) asks the OS kernel to flush dirty data from its page cache to the drive. On macOS, however, fsync() does not guarantee that data has been committed to stable storage on the physical device. It only ensures the data has been handed off to the drive's own write buffer/cache.

If the drive has a volatile write cache (most HDDs and many SSDs do), a power loss after fsync() returns can still result in data loss or corruption, because the drive firmware hasn't necessarily flushed its internal cache to the platters/NAND.

F_FULLFSYNC (macOS-specific)

F_FULLFSYNC is a macOS-specific fcntl command that goes one step further: it issues a hardware flush command (like FLUSH CACHE in ATA or SYNCHRONIZE CACHE in SCSI/NVMe) to the drive, forcing it to commit all buffered writes from the drive's own cache to persistent storage.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

so, os.fsync is only a OS-level flush, while fcntl F_FULLSYNC does a hw-level write buffer flush additionally.

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