Skip to content

[native] Skip F_FULLFSYNC on macOS for network file systems#130779

Closed
adamsitnik with Copilot wants to merge 7 commits into
mainfrom
copilot/fix-network-file-system-locking
Closed

[native] Skip F_FULLFSYNC on macOS for network file systems#130779
adamsitnik with Copilot wants to merge 7 commits into
mainfrom
copilot/fix-network-file-system-locking

Conversation

Copilot AI commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

On macOS, F_FULLFSYNC on network file systems (NFS, SMB, CIFS, SMB2) can return success while silently discarding pending writes, causing data corruption. The existing fsync fallback only triggers on failure, so the data loss goes undetected.

Changes in src/native/libs/System.Native/pal_io.c:

  • Added IsNetworkFileSystem(int fileDescriptor) — macOS-only static helper that calls fstatfs and reuses the existing FileSystemNameSupportsLocking logic to identify NFS/CIFS/SMB/SMB2 mounts. Conservatively returns 1 (is network FS) when fstatfs fails, to skip F_FULLFSYNC and avoid data loss.
  • Modified SystemNative_FSync to skip F_FULLFSYNC entirely when IsNetworkFileSystem returns true, going directly to fsync. Behavior on local file systems is unchanged.

This mirrors the approach already used by CanLockTheFile / SystemNative_FileSystemSupportsLocking, which skip advisory locking on the same set of network file systems for the same category of reasons.

Copilot AI and others added 2 commits July 15, 2026 13:14
Don't use F_FULLFSYNC on macOS for network file systems (NFS, SMB, CIFS, SMB2)
as it may cause pending writes to be discarded. Use the same detection logic
already used by the locking code (fstatfs + FileSystemNameSupportsLocking).

Fixes #124722

Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
On fstatfs failure, return 1 (treat as network FS) to safely skip F_FULLFSYNC
and avoid potential data loss. Also improve comment clarity on return value logic.

Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Copilot AI self-assigned this Jul 15, 2026
Copilot AI review requested due to automatic review settings July 15, 2026 13:16
Copilot AI removed the request for review from Copilot July 15, 2026 13:16
Copilot AI requested a review from adamsitnik July 15, 2026 13:17
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
12 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@adamsitnik adamsitnik left a comment

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.

@copilot please address my feedback

Comment thread src/native/libs/System.Native/pal_io.c Outdated
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-io
See info in area-owners.md if you want to be subscribed.

Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Copilot AI requested review from Copilot and removed request for Copilot July 15, 2026 13:46
…pal_io.c

Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Copilot AI requested review from adamsitnik and Copilot and removed request for Copilot July 15, 2026 13:48

@adamsitnik adamsitnik left a comment

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.

@copilot address my feedback

Comment thread src/native/libs/System.Native/pal_io.c Outdated
Comment thread src/native/libs/System.Native/pal_io.h Outdated
Comment thread src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems Outdated
Comment thread src/native/libs/System.Native/entrypoints.c Outdated
…ckTheFile in managed code

Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Copilot AI requested review from Copilot and removed request for Copilot July 15, 2026 15:10
@adamsitnik
adamsitnik marked this pull request as ready for review July 15, 2026 16:36
Copilot AI review requested due to automatic review settings July 15, 2026 16:36
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
12 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Comment thread src/native/libs/System.Native/pal_io.c

@adamsitnik adamsitnik left a comment

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.

@copilot address feedback

Comment thread src/native/libs/System.Native/pal_io.c
…avior

Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>

@adamsitnik adamsitnik left a comment

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.

LGTM

@adamsitnik
adamsitnik requested a review from jozkee July 16, 2026 12:48
@adamsitnik

Copy link
Copy Markdown
Member

@jozkee the PR is ready for review, PTAL. #124722 is the context

get
{
NullableBool useFullFsync = _useFullFsync;
if (useFullFsync == NullableBool.Undefined && !IsClosed && OperatingSystem.IsApplePlatform())

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.

SystemNative_FSync uses TARGET_OSX only.

Suggested change
if (useFullFsync == NullableBool.Undefined && !IsClosed && OperatingSystem.IsApplePlatform())
if (useFullFsync == NullableBool.Undefined && !IsClosed && OperatingSystem.IsMacOS())

NullableBool useFullFsync = _useFullFsync;
if (useFullFsync == NullableBool.Undefined && !IsClosed && OperatingSystem.IsApplePlatform())
{
_useFullFsync = useFullFsync = Interop.Sys.FileSystemSupportsLocking(this, Interop.Sys.LockOperations.LOCK_SH, accessWrite: true)

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 PR reuses SystemNative_FileSystemSupportsLocking as a proxy for "is this a network FS," but that function's contract is about locking and its result depends on lockOperation/accessWrite, so it can't simply be renamed to IsNetworkFileSystem. Consider extracting the shared network-FS detection (the FileSystemNameSupportsLocking nfs/cifs/smb/smb2 check) into a dedicated helper.

@jozkee

jozkee commented Jul 18, 2026

Copy link
Copy Markdown
Member

I tried to reproduce #124722 on a macOS client (macOS 26.5, arm64) connecting to a Samba server running in WSL2, but was not able to trigger the truncation.

What I tried:

  • The exact repro from the issue (16 × 64 MB,  FileShare.None  +  Flush(flushToDisk: true) ), plus larger sizes (256 MB × 16 and 1 GB × 4) and multiple runs.
  • Sequential, async, and fully-parallel (concurrent) writes.
  • Verifying final file sizes three ways to rule out the macOS SMB client's metadata cache: post-close  stat , a full byte-for-byte read-back from a separate process, and a force-unmount/remount followed by a fresh  stat .

In every case all files came out at their exact expected size — 0 truncated.

@adamsitnik

Copy link
Copy Markdown
Member

I tried to reproduce #124722 on a macOS client (macOS 26.5, arm64) connecting to a Samba server running in WSL2, but was not able to trigger the truncation.

What I tried:

  • The exact repro from the issue (16 × 64 MB,  FileShare.None  +  Flush(flushToDisk: true) ), plus larger sizes (256 MB × 16 and 1 GB × 4) and multiple runs.
  • Sequential, async, and fully-parallel (concurrent) writes.
  • Verifying final file sizes three ways to rule out the macOS SMB client's metadata cache: post-close  stat , a full byte-for-byte read-back from a separate process, and a force-unmount/remount followed by a fresh  stat .

In every case all files came out at their exact expected size — 0 truncated.

@sschultze is there something more specific required to reproduce the problem (#124722)?

@github-actions

github-actions Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Workflow state for the Holistic Review Orchestrator.

{
  "version": 5,
  "last_dispatched_commit": "f16793c0f1928285802c14f72b4d5e31e792e5c4",
  "last_dispatched_base_ref": "main",
  "last_dispatched_base_sha": "051c1fd3c13fea8c1e60b68d9c7f63c3173e5593",
  "last_reviewed_commit": "f16793c0f1928285802c14f72b4d5e31e792e5c4",
  "last_reviewed_base_ref": "main",
  "last_reviewed_base_sha": "051c1fd3c13fea8c1e60b68d9c7f63c3173e5593",
  "last_recorded_worker_run_id": "29688125118",
  "review_attempt_commit": "",
  "review_attempt_base_ref": "",
  "review_attempt_count": 0,
  "max_review_attempts": 5,
  "review_history_format": "holistic-review-disclosure-v1",
  "review_history": [
    {
      "commit": "f16793c0f1928285802c14f72b4d5e31e792e5c4",
      "review_id": 4730810937
    }
  ]
}

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Holistic Review

Motivation: On macOS, F_FULLFSYNC issued against network file systems (NFS/SMB/CIFS/SMB2) can return success while silently discarding pending writes, causing undetected data loss. Because the existing fallback to fsync only triggers when F_FULLFSYNC fails, the corruption goes unnoticed (#124722). The PR proactively skips F_FULLFSYNC on file systems known to misbehave.

Approach: Rather than adding a new native file-system probe, the PR threads a useFullFSync flag from managed code into SystemNative_FSync. SafeFileHandle.UseFullFSync lazily computes and caches the decision by reusing the existing FileSystemSupportsLocking(LOCK_SH, accessWrite: true) probe — the same syscall/name-matching machinery already used to skip advisory locking on the identical set of network file systems (#44546, #53182). The probe is gated on OperatingSystem.IsApplePlatform() and only queried on first flush, so callers that never flush to disk pay no syscall. Native SystemNative_FSync now only attempts F_FULLFSYNC when the flag is set (still falling back to fsync on failure), and ignores the flag on non-Apple platforms via (void)useFullFSync. The p/invoke declaration, both WASM callhelper prototypes, and a doc-comment typo are updated to match. Reusing the proven locking probe keeps the network-FS list authoritative in one place.

Summary: A well-scoped, low-risk fix that reuses an established mechanism instead of duplicating file-system detection. Behavior on local file systems is unchanged (F_FULLFSYNC still attempted, fsync fallback preserved), and non-macOS platforms are unaffected since the flag is only consulted inside the TARGET_OSX block. The caching design correctly treats NullableBool.Undefined as "use full fsync," so a closed handle or a not-yet-probed handle stays on the durable path. The change is consistent with the concurrent CanLockTheFile/SystemNative_FileSystemSupportsLocking design and has already received a maintainer approval (LGTM from adamsitnik). I concur: LGTM.

Detailed Findings

No blocking issues found.

Minor (non-blocking): UseFullFSync gates the probe on OperatingSystem.IsApplePlatform(), which is true for macOS, iOS, tvOS, and Mac Catalyst, but the native F_FULLFSYNC path is compiled only under #ifdef TARGET_OSX. On non-macOS Apple platforms the useFullFSync argument is discarded via (void)useFullFSync, so the managed FileSystemSupportsLocking probe performs an fstatfs syscall (once per handle, on first flush) whose result is never used. This is harmless and only costs one cached syscall on iOS/tvOS/MacCatalyst; if you want to avoid it you could narrow the gate to OperatingSystem.IsMacOS() to match the native compilation, but this is not required for correctness.

Note

This review was generated by this repository's Holistic Review agentic workflow to complement the built-in Copilot review.

Generated by Holistic Review · 80.9 AIC · ⌖ 14.8 AIC · ⊞ 10K

@adamsitnik

Copy link
Copy Markdown
Member

Closing as we are unable to test it properly. The recommended fix is to report a bug directly in Samba (#124722 (comment))

@adamsitnik adamsitnik closed this Jul 24, 2026
@adamsitnik
adamsitnik deleted the copilot/fix-network-file-system-locking branch July 24, 2026 19:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FileStream.Flush(flushToDisk: true) causes corrupt (truncated) files to be written on macOS + SMB share

4 participants