From e67968cf5406d4b8bc38734576f8563c1ad8c2b0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Jul 2026 13:14:54 +0000 Subject: [PATCH 1/7] Fix F_FULLFSYNC on network file systems on macOS 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 https://github.com/dotnet/runtime/issues/124722 Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com> --- src/native/libs/System.Native/pal_io.c | 36 +++++++++++++++++++++----- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/src/native/libs/System.Native/pal_io.c b/src/native/libs/System.Native/pal_io.c index 8bd4b6affea1c1..d2e1211dfb6d11 100644 --- a/src/native/libs/System.Native/pal_io.c +++ b/src/native/libs/System.Native/pal_io.c @@ -783,21 +783,30 @@ int32_t SystemNative_FChMod(intptr_t fd, int32_t mode) #endif /* HAVE_FCHMOD */ } +#ifdef TARGET_OSX +static int32_t IsNetworkFileSystem(int fileDescriptor); +#endif + int32_t SystemNative_FSync(intptr_t fd) { int fileDescriptor = ToFileDescriptor(fd); int32_t result; #ifdef TARGET_OSX - while ((result = fcntl(fileDescriptor, F_FULLFSYNC)) < 0 && errno == EINTR); - if (result >= 0) + // F_FULLFSYNC is not supported on network file systems (e.g., NFS, SMB, CIFS) and may cause + // pending writes to be discarded. See https://github.com/dotnet/runtime/issues/124722. + if (!IsNetworkFileSystem(fileDescriptor)) { - return result; - } + while ((result = fcntl(fileDescriptor, F_FULLFSYNC)) < 0 && errno == EINTR); + if (result >= 0) + { + return result; + } - // F_FULLFSYNC is not supported on all file systems and handle types (e.g., - // network file systems, read-only handles). Fall back to fsync. - // For genuine I/O errors (e.g., EIO), fsync will also fail and propagate the error. + // F_FULLFSYNC is not supported on all file systems and handle types (e.g., + // network file systems, read-only handles). Fall back to fsync. + // For genuine I/O errors (e.g., EIO), fsync will also fail and propagate the error. + } #endif while ((result = fsync(fileDescriptor)) < 0 && errno == EINTR); return result; @@ -1708,6 +1717,19 @@ static uint32_t FileSystemNameSupportsLocking(const char* fileSystemName) } return 1; } + +#ifdef TARGET_OSX +// Returns 1 if the file descriptor is on a network file system (e.g., NFS, SMB, CIFS). +// These file systems don't reliably support F_FULLFSYNC and may discard pending writes. +// See https://github.com/dotnet/runtime/issues/124722. +static int32_t IsNetworkFileSystem(int fileDescriptor) +{ + struct statfs statfsArgs; + int statfsRes; + while ((statfsRes = fstatfs(fileDescriptor, &statfsArgs)) == -1 && errno == EINTR); + return (statfsRes != -1) && !FileSystemNameSupportsLocking(statfsArgs.f_fstypename); +} +#endif #endif #endif /* TARGET_WASI */ From d633e5a1eef6ed7373d771847e8e4180b03d4aa6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Jul 2026 13:16:18 +0000 Subject: [PATCH 2/7] Address code review: improve IsNetworkFileSystem error handling 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> --- src/native/libs/System.Native/pal_io.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/native/libs/System.Native/pal_io.c b/src/native/libs/System.Native/pal_io.c index d2e1211dfb6d11..441c2ea3858b84 100644 --- a/src/native/libs/System.Native/pal_io.c +++ b/src/native/libs/System.Native/pal_io.c @@ -1727,7 +1727,13 @@ static int32_t IsNetworkFileSystem(int fileDescriptor) struct statfs statfsArgs; int statfsRes; while ((statfsRes = fstatfs(fileDescriptor, &statfsArgs)) == -1 && errno == EINTR); - return (statfsRes != -1) && !FileSystemNameSupportsLocking(statfsArgs.f_fstypename); + if (statfsRes == -1) + { + // Cannot determine the file system type; skip F_FULLFSYNC to avoid potential data loss. + return 1; + } + // FileSystemNameSupportsLocking returns 0 for network file systems (NFS, CIFS, SMB, SMB2). + return !FileSystemNameSupportsLocking(statfsArgs.f_fstypename); } #endif #endif From cc4b6339d968b6b81c489634863a59a381429225 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Jul 2026 13:46:09 +0000 Subject: [PATCH 3/7] Move IsNetworkFileSystem check to managed SafeFileHandle, cache in Init Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com> --- .../Unix/System.Native/Interop.FSync.cs | 2 +- .../Interop.IsNetworkFileSystem.cs | 15 ++++++++++++ .../Win32/SafeHandles/SafeFileHandle.Unix.cs | 20 ++++++++++++++++ .../System.Private.CoreLib.Shared.projitems | 3 +++ .../IO/Strategies/FileStreamHelpers.Unix.cs | 2 +- src/native/libs/System.Native/entrypoints.c | 1 + src/native/libs/System.Native/pal_io.c | 24 ++++++++++++------- src/native/libs/System.Native/pal_io.h | 9 ++++++- 8 files changed, 64 insertions(+), 12 deletions(-) create mode 100644 src/libraries/Common/src/Interop/Unix/System.Native/Interop.IsNetworkFileSystem.cs diff --git a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.FSync.cs b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.FSync.cs index 4c902e08285ca5..e3cb769d01c46e 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.FSync.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.FSync.cs @@ -9,6 +9,6 @@ internal static partial class Interop internal static partial class Sys { [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_FSync", SetLastError = true)] - internal static partial int FSync(SafeFileHandle fd); + internal static partial int FSync(SafeFileHandle fd, [MarshalAs(UnmanagedType.Bool)] bool useFullFSync); } } diff --git a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.IsNetworkFileSystem.cs b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.IsNetworkFileSystem.cs new file mode 100644 index 00000000000000..860d3b5ae9f309 --- /dev/null +++ b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.IsNetworkFileSystem.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; +using Microsoft.Win32.SafeHandles; + +internal static partial class Interop +{ + internal static partial class Sys + { + [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_IsNetworkFileSystem")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static partial bool IsNetworkFileSystem(SafeFileHandle fd); + } +} diff --git a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs index f65aaf042bf891..2d0c57b36318d3 100644 --- a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs @@ -41,9 +41,19 @@ public sealed partial class SafeFileHandle : SafeHandleZeroOrMinusOneIsInvalid private NullableBool _canSeek /* = NullableBool.Undefined */; private NullableBool _supportsRandomAccess /* = NullableBool.Undefined */; private NullableBool _isAsync /* = NullableBool.Undefined */; + // Cached on macOS when locking is enabled: True = is network FS (use fsync only), + // False = is local FS (use F_FULLFSYNC), Undefined = unknown (use F_FULLFSYNC). + // See https://github.com/dotnet/runtime/issues/124722. + private NullableBool _isNetworkFileSystem /* = NullableBool.Undefined */; private bool _deleteOnClose; private bool _isLocked; + // Returns true when F_FULLFSYNC should be attempted (the file is on a local file system or + // the file system type is unknown). Returns false only when the file is known to be on a + // network file system where F_FULLFSYNC can silently discard writes. The value is only used + // on macOS; native code ignores it on other platforms. + internal bool UseFullFSync => _isNetworkFileSystem != NullableBool.True; + public SafeFileHandle() : this(ownsHandle: true) { } @@ -461,6 +471,16 @@ private bool Init(string path, FileMode mode, FileAccess access, FileShare share return false; } } + + // On macOS, F_FULLFSYNC on network file systems (NFS, SMB, CIFS) can silently discard + // pending writes. Cache whether this file is on a network FS so FSync can avoid F_FULLFSYNC + // for such handles. The check is only needed when file locking is enabled because that + // already queries the file system type. See https://github.com/dotnet/runtime/issues/124722. + if (OperatingSystem.IsMacOS() && !DisableFileLocking) + { + _isNetworkFileSystem = Interop.Sys.IsNetworkFileSystem(this) ? NullableBool.True : NullableBool.False; + } + // Enable DeleteOnClose when we've successfully locked the file. // On Windows, the locking happens atomically as part of opening the file. _deleteOnClose = (options & FileOptions.DeleteOnClose) != 0; diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 9670007e075ff4..3248d3c509712d 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -2473,6 +2473,9 @@ Common\Interop\Unix\System.Native\Interop.FSync.cs + + Common\Interop\Unix\System.Native\Interop.IsNetworkFileSystem.cs + Common\Interop\Unix\System.Native\Interop.FTruncate.cs diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Unix.cs index 15da21326a9451..54749df42879ad 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Unix.cs @@ -39,7 +39,7 @@ internal static void ThrowInvalidArgument(SafeFileHandle handle) => /// Flushes the file's OS buffer. internal static void FlushToDisk(SafeFileHandle handle) { - if (Interop.Sys.FSync(handle) < 0) + if (Interop.Sys.FSync(handle, handle.UseFullFSync) < 0) { Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo(); switch (errorInfo.Error) diff --git a/src/native/libs/System.Native/entrypoints.c b/src/native/libs/System.Native/entrypoints.c index 78e08565015511..8a55018ed11013 100644 --- a/src/native/libs/System.Native/entrypoints.c +++ b/src/native/libs/System.Native/entrypoints.c @@ -120,6 +120,7 @@ static const Entry s_sysNative[] = DllImportEntry(SystemNative_RealPath) DllImportEntry(SystemNative_GetPeerID) DllImportEntry(SystemNative_FileSystemSupportsLocking) + DllImportEntry(SystemNative_IsNetworkFileSystem) DllImportEntry(SystemNative_LockFileRegion) DllImportEntry(SystemNative_LChflags) DllImportEntry(SystemNative_LChflagsCanSetHiddenFlag) diff --git a/src/native/libs/System.Native/pal_io.c b/src/native/libs/System.Native/pal_io.c index 441c2ea3858b84..7f204fd2b3b4d1 100644 --- a/src/native/libs/System.Native/pal_io.c +++ b/src/native/libs/System.Native/pal_io.c @@ -783,19 +783,13 @@ int32_t SystemNative_FChMod(intptr_t fd, int32_t mode) #endif /* HAVE_FCHMOD */ } -#ifdef TARGET_OSX -static int32_t IsNetworkFileSystem(int fileDescriptor); -#endif - -int32_t SystemNative_FSync(intptr_t fd) +int32_t SystemNative_FSync(intptr_t fd, int32_t useFullFSync) { int fileDescriptor = ToFileDescriptor(fd); int32_t result; #ifdef TARGET_OSX - // F_FULLFSYNC is not supported on network file systems (e.g., NFS, SMB, CIFS) and may cause - // pending writes to be discarded. See https://github.com/dotnet/runtime/issues/124722. - if (!IsNetworkFileSystem(fileDescriptor)) + if (useFullFSync) { while ((result = fcntl(fileDescriptor, F_FULLFSYNC)) < 0 && errno == EINTR); if (result >= 0) @@ -804,9 +798,11 @@ int32_t SystemNative_FSync(intptr_t fd) } // F_FULLFSYNC is not supported on all file systems and handle types (e.g., - // network file systems, read-only handles). Fall back to fsync. + // read-only handles). Fall back to fsync. // For genuine I/O errors (e.g., EIO), fsync will also fail and propagate the error. } +#else + (void)useFullFSync; #endif while ((result = fsync(fileDescriptor)) < 0 && errno == EINTR); return result; @@ -1739,6 +1735,16 @@ static int32_t IsNetworkFileSystem(int fileDescriptor) #endif #endif /* TARGET_WASI */ +int32_t SystemNative_IsNetworkFileSystem(intptr_t fd) +{ +#ifdef TARGET_OSX + return IsNetworkFileSystem(ToFileDescriptor(fd)); +#else + (void)fd; + return 0; +#endif +} + // LOCK_SH does not work well for write access on nfs/cifs/samba. For example, writes are dropped silently. // See https://github.com/dotnet/runtime/issues/44546 and https://github.com/dotnet/runtime/issues/53182. uint32_t SystemNative_FileSystemSupportsLocking(intptr_t fd, int32_t lockOperation, int32_t accessWrite) diff --git a/src/native/libs/System.Native/pal_io.h b/src/native/libs/System.Native/pal_io.h index 10332df8043230..ad31dea81dc222 100644 --- a/src/native/libs/System.Native/pal_io.h +++ b/src/native/libs/System.Native/pal_io.h @@ -536,10 +536,17 @@ PALEXPORT int32_t SystemNative_FChMod(intptr_t fd, int32_t mode); /** * Flushes all modified data and attribtues of the specified File Descriptor to the storage medium. + * On macOS, attempts F_FULLFSYNC when useFullFSync is non-zero, falling back to fsync on failure. * * Returns 0 for success; on fail, -1 is returned and errno is set. */ -PALEXPORT int32_t SystemNative_FSync(intptr_t fd); +PALEXPORT int32_t SystemNative_FSync(intptr_t fd, int32_t useFullFSync); + +/** + * Returns 1 if the file descriptor refers to a file on a network file system (NFS, SMB, CIFS, SMB2), + * 0 otherwise. On non-macOS platforms, always returns 0. + */ +PALEXPORT int32_t SystemNative_IsNetworkFileSystem(intptr_t fd); /** * Changes the advisory lock status on a given File Descriptor From c914251a885fcf37b544e42a1b6af499a5928566 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Jul 2026 13:48:36 +0000 Subject: [PATCH 4/7] Address code review feedback: improve comments in SafeFileHandle and pal_io.c Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com> --- .../Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs | 10 +++++++--- src/native/libs/System.Native/pal_io.c | 2 +- src/native/libs/System.Native/pal_io.h | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs index 2d0c57b36318d3..152f40e6105609 100644 --- a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs @@ -41,15 +41,19 @@ public sealed partial class SafeFileHandle : SafeHandleZeroOrMinusOneIsInvalid private NullableBool _canSeek /* = NullableBool.Undefined */; private NullableBool _supportsRandomAccess /* = NullableBool.Undefined */; private NullableBool _isAsync /* = NullableBool.Undefined */; - // Cached on macOS when locking is enabled: True = is network FS (use fsync only), - // False = is local FS (use F_FULLFSYNC), Undefined = unknown (use F_FULLFSYNC). + // Set during Init() on macOS when file locking is enabled: True = is network FS (skip + // F_FULLFSYNC), False = is local FS (use F_FULLFSYNC). Stays Undefined on non-macOS or + // when file locking is disabled; UseFullFSync treats Undefined the same as False (use + // F_FULLFSYNC). This is a deliberate tradeoff: network FS detection is coupled with the + // locking check to avoid an extra fstatfs call per open; when locking is disabled, + // F_FULLFSYNC may still be attempted on network FSs. // See https://github.com/dotnet/runtime/issues/124722. private NullableBool _isNetworkFileSystem /* = NullableBool.Undefined */; private bool _deleteOnClose; private bool _isLocked; // Returns true when F_FULLFSYNC should be attempted (the file is on a local file system or - // the file system type is unknown). Returns false only when the file is known to be on a + // the file system type is unknown). Returns false only when the file is confirmed to be on a // network file system where F_FULLFSYNC can silently discard writes. The value is only used // on macOS; native code ignores it on other platforms. internal bool UseFullFSync => _isNetworkFileSystem != NullableBool.True; diff --git a/src/native/libs/System.Native/pal_io.c b/src/native/libs/System.Native/pal_io.c index 7f204fd2b3b4d1..5140bca31ef955 100644 --- a/src/native/libs/System.Native/pal_io.c +++ b/src/native/libs/System.Native/pal_io.c @@ -798,7 +798,7 @@ int32_t SystemNative_FSync(intptr_t fd, int32_t useFullFSync) } // F_FULLFSYNC is not supported on all file systems and handle types (e.g., - // read-only handles). Fall back to fsync. + // network file systems, read-only handles). Fall back to fsync. // For genuine I/O errors (e.g., EIO), fsync will also fail and propagate the error. } #else diff --git a/src/native/libs/System.Native/pal_io.h b/src/native/libs/System.Native/pal_io.h index ad31dea81dc222..651ccb12e2eda2 100644 --- a/src/native/libs/System.Native/pal_io.h +++ b/src/native/libs/System.Native/pal_io.h @@ -535,7 +535,7 @@ PALEXPORT int32_t SystemNative_ChMod(const char* path, int32_t mode); PALEXPORT int32_t SystemNative_FChMod(intptr_t fd, int32_t mode); /** - * Flushes all modified data and attribtues of the specified File Descriptor to the storage medium. + * Flushes all modified data and attributes of the specified File Descriptor to the storage medium. * On macOS, attempts F_FULLFSYNC when useFullFSync is non-zero, falling back to fsync on failure. * * Returns 0 for success; on fail, -1 is returned and errno is set. From b4094fc57c837704534248b8f506ad23e602d56a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Jul 2026 15:10:07 +0000 Subject: [PATCH 5/7] Address review feedback: remove native IsNetworkFileSystem, use CanLockTheFile in managed code Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com> --- .../Interop.IsNetworkFileSystem.cs | 15 --------- .../Win32/SafeHandles/SafeFileHandle.Unix.cs | 32 +++++++++++-------- .../System.Private.CoreLib.Shared.projitems | 3 -- src/native/libs/System.Native/entrypoints.c | 1 - src/native/libs/System.Native/pal_io.c | 29 ----------------- src/native/libs/System.Native/pal_io.h | 6 ---- 6 files changed, 18 insertions(+), 68 deletions(-) delete mode 100644 src/libraries/Common/src/Interop/Unix/System.Native/Interop.IsNetworkFileSystem.cs diff --git a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.IsNetworkFileSystem.cs b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.IsNetworkFileSystem.cs deleted file mode 100644 index 860d3b5ae9f309..00000000000000 --- a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.IsNetworkFileSystem.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Runtime.InteropServices; -using Microsoft.Win32.SafeHandles; - -internal static partial class Interop -{ - internal static partial class Sys - { - [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_IsNetworkFileSystem")] - [return: MarshalAs(UnmanagedType.Bool)] - internal static partial bool IsNetworkFileSystem(SafeFileHandle fd); - } -} diff --git a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs index 152f40e6105609..5314dafbef00f0 100644 --- a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs @@ -41,14 +41,13 @@ public sealed partial class SafeFileHandle : SafeHandleZeroOrMinusOneIsInvalid private NullableBool _canSeek /* = NullableBool.Undefined */; private NullableBool _supportsRandomAccess /* = NullableBool.Undefined */; private NullableBool _isAsync /* = NullableBool.Undefined */; - // Set during Init() on macOS when file locking is enabled: True = is network FS (skip - // F_FULLFSYNC), False = is local FS (use F_FULLFSYNC). Stays Undefined on non-macOS or - // when file locking is disabled; UseFullFSync treats Undefined the same as False (use - // F_FULLFSYNC). This is a deliberate tradeoff: network FS detection is coupled with the - // locking check to avoid an extra fstatfs call per open; when locking is disabled, - // F_FULLFSYNC may still be attempted on network FSs. + // Set during Init() on macOS: True = use F_FULLFSYNC (no exclusive lock was acquired, + // or share mode allows sharing, or LOCK_SH check confirms a local FS). + // False = skip F_FULLFSYNC (exclusive lock was acquired AND share is FileShare.None AND + // LOCK_SH check returns false, indicating a network FS such as NFS/SMB/CIFS). + // Stays Undefined on non-macOS; UseFullFSync treats Undefined the same as True (use F_FULLFSYNC). // See https://github.com/dotnet/runtime/issues/124722. - private NullableBool _isNetworkFileSystem /* = NullableBool.Undefined */; + private NullableBool _useFullFsync /* = NullableBool.Undefined */; private bool _deleteOnClose; private bool _isLocked; @@ -56,7 +55,7 @@ public sealed partial class SafeFileHandle : SafeHandleZeroOrMinusOneIsInvalid // the file system type is unknown). Returns false only when the file is confirmed to be on a // network file system where F_FULLFSYNC can silently discard writes. The value is only used // on macOS; native code ignores it on other platforms. - internal bool UseFullFSync => _isNetworkFileSystem != NullableBool.True; + internal bool UseFullFSync => _useFullFsync != NullableBool.False; public SafeFileHandle() : this(ownsHandle: true) { @@ -476,13 +475,18 @@ private bool Init(string path, FileMode mode, FileAccess access, FileShare share } } - // On macOS, F_FULLFSYNC on network file systems (NFS, SMB, CIFS) can silently discard - // pending writes. Cache whether this file is on a network FS so FSync can avoid F_FULLFSYNC - // for such handles. The check is only needed when file locking is enabled because that - // already queries the file system type. See https://github.com/dotnet/runtime/issues/124722. - if (OperatingSystem.IsMacOS() && !DisableFileLocking) + // On macOS, F_FULLFSYNC on network file systems (NFS, SMB, CIFS, SMB2) can silently discard + // pending writes. Detect network FS by reusing CanLockTheFile: SystemNative_FileSystemSupportsLocking + // always returns 1 (true) for LOCK_EX operations, so an exclusive advisory lock (_isLocked) is + // acquired even on network FS. However, it returns 0 (false) for LOCK_SH+write on network FS. + // When we hold an exclusive lock (_isLocked), share is FileShare.None, and the LOCK_SH check + // returns false, we know the file is on a network FS and must skip F_FULLFSYNC. + // In all other cases (no exclusive lock acquired, sharing allowed, or LOCK_SH check succeeds), + // we default to using F_FULLFSYNC. See https://github.com/dotnet/runtime/issues/124722. + if (OperatingSystem.IsMacOS()) { - _isNetworkFileSystem = Interop.Sys.IsNetworkFileSystem(this) ? NullableBool.True : NullableBool.False; + _useFullFsync = !_isLocked || share != FileShare.None || CanLockTheFile(Interop.Sys.LockOperations.LOCK_SH, access) + ? NullableBool.True : NullableBool.False; } // Enable DeleteOnClose when we've successfully locked the file. diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 3248d3c509712d..9670007e075ff4 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -2473,9 +2473,6 @@ Common\Interop\Unix\System.Native\Interop.FSync.cs - - Common\Interop\Unix\System.Native\Interop.IsNetworkFileSystem.cs - Common\Interop\Unix\System.Native\Interop.FTruncate.cs diff --git a/src/native/libs/System.Native/entrypoints.c b/src/native/libs/System.Native/entrypoints.c index 8a55018ed11013..78e08565015511 100644 --- a/src/native/libs/System.Native/entrypoints.c +++ b/src/native/libs/System.Native/entrypoints.c @@ -120,7 +120,6 @@ static const Entry s_sysNative[] = DllImportEntry(SystemNative_RealPath) DllImportEntry(SystemNative_GetPeerID) DllImportEntry(SystemNative_FileSystemSupportsLocking) - DllImportEntry(SystemNative_IsNetworkFileSystem) DllImportEntry(SystemNative_LockFileRegion) DllImportEntry(SystemNative_LChflags) DllImportEntry(SystemNative_LChflagsCanSetHiddenFlag) diff --git a/src/native/libs/System.Native/pal_io.c b/src/native/libs/System.Native/pal_io.c index 5140bca31ef955..621839231895b6 100644 --- a/src/native/libs/System.Native/pal_io.c +++ b/src/native/libs/System.Native/pal_io.c @@ -1713,38 +1713,9 @@ static uint32_t FileSystemNameSupportsLocking(const char* fileSystemName) } return 1; } - -#ifdef TARGET_OSX -// Returns 1 if the file descriptor is on a network file system (e.g., NFS, SMB, CIFS). -// These file systems don't reliably support F_FULLFSYNC and may discard pending writes. -// See https://github.com/dotnet/runtime/issues/124722. -static int32_t IsNetworkFileSystem(int fileDescriptor) -{ - struct statfs statfsArgs; - int statfsRes; - while ((statfsRes = fstatfs(fileDescriptor, &statfsArgs)) == -1 && errno == EINTR); - if (statfsRes == -1) - { - // Cannot determine the file system type; skip F_FULLFSYNC to avoid potential data loss. - return 1; - } - // FileSystemNameSupportsLocking returns 0 for network file systems (NFS, CIFS, SMB, SMB2). - return !FileSystemNameSupportsLocking(statfsArgs.f_fstypename); -} -#endif #endif #endif /* TARGET_WASI */ -int32_t SystemNative_IsNetworkFileSystem(intptr_t fd) -{ -#ifdef TARGET_OSX - return IsNetworkFileSystem(ToFileDescriptor(fd)); -#else - (void)fd; - return 0; -#endif -} - // LOCK_SH does not work well for write access on nfs/cifs/samba. For example, writes are dropped silently. // See https://github.com/dotnet/runtime/issues/44546 and https://github.com/dotnet/runtime/issues/53182. uint32_t SystemNative_FileSystemSupportsLocking(intptr_t fd, int32_t lockOperation, int32_t accessWrite) diff --git a/src/native/libs/System.Native/pal_io.h b/src/native/libs/System.Native/pal_io.h index 651ccb12e2eda2..02b58edd993a5c 100644 --- a/src/native/libs/System.Native/pal_io.h +++ b/src/native/libs/System.Native/pal_io.h @@ -542,12 +542,6 @@ PALEXPORT int32_t SystemNative_FChMod(intptr_t fd, int32_t mode); */ PALEXPORT int32_t SystemNative_FSync(intptr_t fd, int32_t useFullFSync); -/** - * Returns 1 if the file descriptor refers to a file on a network file system (NFS, SMB, CIFS, SMB2), - * 0 otherwise. On non-macOS platforms, always returns 0. - */ -PALEXPORT int32_t SystemNative_IsNetworkFileSystem(intptr_t fd); - /** * Changes the advisory lock status on a given File Descriptor * From 011f209466c9052ecd445020b3ec7f7843cc6dc8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Jul 2026 16:32:14 +0000 Subject: [PATCH 6/7] Address review feedback: lazy UseFullFSync for all Apple platforms, fix WASM FSync signature Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com> --- .../vm/wasm/browser/callhelpers-pinvoke.cpp | 2 +- .../vm/wasm/wasi/callhelpers-pinvoke.cpp | 2 +- .../Win32/SafeHandles/SafeFileHandle.Unix.cs | 40 +++++++++---------- 3 files changed, 21 insertions(+), 23 deletions(-) diff --git a/src/coreclr/vm/wasm/browser/callhelpers-pinvoke.cpp b/src/coreclr/vm/wasm/browser/callhelpers-pinvoke.cpp index f18883c38a6431..009343c71df98d 100644 --- a/src/coreclr/vm/wasm/browser/callhelpers-pinvoke.cpp +++ b/src/coreclr/vm/wasm/browser/callhelpers-pinvoke.cpp @@ -79,7 +79,7 @@ extern "C" { int32_t SystemNative_FChflags (void *, uint32_t); int32_t SystemNative_FLock (void *, int32_t); int32_t SystemNative_FStat (void *, void *); - int32_t SystemNative_FSync (void *); + int32_t SystemNative_FSync (void *, int32_t); int32_t SystemNative_FTruncate (void *, int64_t); int32_t SystemNative_FUTimens (void *, void *); int32_t SystemNative_FcntlGetIsNonBlocking (void *, void *); diff --git a/src/coreclr/vm/wasm/wasi/callhelpers-pinvoke.cpp b/src/coreclr/vm/wasm/wasi/callhelpers-pinvoke.cpp index 5e23ab70e7bd98..79ad61f5c3903e 100644 --- a/src/coreclr/vm/wasm/wasi/callhelpers-pinvoke.cpp +++ b/src/coreclr/vm/wasm/wasi/callhelpers-pinvoke.cpp @@ -76,7 +76,7 @@ extern "C" { int32_t SystemNative_FChflags (void *, uint32_t); int32_t SystemNative_FLock (void *, int32_t); int32_t SystemNative_FStat (void *, void *); - int32_t SystemNative_FSync (void *); + int32_t SystemNative_FSync (void *, int32_t); int32_t SystemNative_FTruncate (void *, int64_t); int32_t SystemNative_FUTimens (void *, void *); int32_t SystemNative_FcntlGetFD (void *); diff --git a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs index 5314dafbef00f0..7810a27dc48b4f 100644 --- a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs @@ -41,11 +41,10 @@ public sealed partial class SafeFileHandle : SafeHandleZeroOrMinusOneIsInvalid private NullableBool _canSeek /* = NullableBool.Undefined */; private NullableBool _supportsRandomAccess /* = NullableBool.Undefined */; private NullableBool _isAsync /* = NullableBool.Undefined */; - // Set during Init() on macOS: True = use F_FULLFSYNC (no exclusive lock was acquired, - // or share mode allows sharing, or LOCK_SH check confirms a local FS). - // False = skip F_FULLFSYNC (exclusive lock was acquired AND share is FileShare.None AND - // LOCK_SH check returns false, indicating a network FS such as NFS/SMB/CIFS). - // Stays Undefined on non-macOS; UseFullFSync treats Undefined the same as True (use F_FULLFSYNC). + // Lazily initialized on Apple platforms (macOS, iOS, tvOS, Mac Catalyst). + // True = use F_FULLFSYNC (file system supports locking, i.e., is local). + // False = skip F_FULLFSYNC (file system does not support locking, i.e., is a network FS such as NFS/SMB/CIFS). + // Stays Undefined until first queried; UseFullFSync treats Undefined as True (safe default). // See https://github.com/dotnet/runtime/issues/124722. private NullableBool _useFullFsync /* = NullableBool.Undefined */; private bool _deleteOnClose; @@ -54,8 +53,21 @@ public sealed partial class SafeFileHandle : SafeHandleZeroOrMinusOneIsInvalid // Returns true when F_FULLFSYNC should be attempted (the file is on a local file system or // the file system type is unknown). Returns false only when the file is confirmed to be on a // network file system where F_FULLFSYNC can silently discard writes. The value is only used - // on macOS; native code ignores it on other platforms. - internal bool UseFullFSync => _useFullFsync != NullableBool.False; + // on Apple platforms; native code ignores it on other platforms. + // Lazily computed and cached on first access to avoid a syscall for callers that never flush to disk. + internal bool UseFullFSync + { + get + { + NullableBool useFullFsync = _useFullFsync; + if (useFullFsync == NullableBool.Undefined && !IsClosed && OperatingSystem.IsApplePlatform()) + { + _useFullFsync = useFullFsync = Interop.Sys.FileSystemSupportsLocking(this, Interop.Sys.LockOperations.LOCK_SH, accessWrite: true) + ? NullableBool.True : NullableBool.False; + } + return useFullFsync != NullableBool.False; + } + } public SafeFileHandle() : this(ownsHandle: true) { @@ -475,20 +487,6 @@ private bool Init(string path, FileMode mode, FileAccess access, FileShare share } } - // On macOS, F_FULLFSYNC on network file systems (NFS, SMB, CIFS, SMB2) can silently discard - // pending writes. Detect network FS by reusing CanLockTheFile: SystemNative_FileSystemSupportsLocking - // always returns 1 (true) for LOCK_EX operations, so an exclusive advisory lock (_isLocked) is - // acquired even on network FS. However, it returns 0 (false) for LOCK_SH+write on network FS. - // When we hold an exclusive lock (_isLocked), share is FileShare.None, and the LOCK_SH check - // returns false, we know the file is on a network FS and must skip F_FULLFSYNC. - // In all other cases (no exclusive lock acquired, sharing allowed, or LOCK_SH check succeeds), - // we default to using F_FULLFSYNC. See https://github.com/dotnet/runtime/issues/124722. - if (OperatingSystem.IsMacOS()) - { - _useFullFsync = !_isLocked || share != FileShare.None || CanLockTheFile(Interop.Sys.LockOperations.LOCK_SH, access) - ? NullableBool.True : NullableBool.False; - } - // Enable DeleteOnClose when we've successfully locked the file. // On Windows, the locking happens atomically as part of opening the file. _deleteOnClose = (options & FileOptions.DeleteOnClose) != 0; From f16793c0f1928285802c14f72b4d5e31e792e5c4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Jul 2026 17:21:47 +0000 Subject: [PATCH 7/7] Update SafeFileHandle F_FULLFSYNC comments for conservative probe behavior Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com> --- .../Win32/SafeHandles/SafeFileHandle.Unix.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs index 7810a27dc48b4f..d13e3eecadbb9c 100644 --- a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs @@ -42,18 +42,19 @@ public sealed partial class SafeFileHandle : SafeHandleZeroOrMinusOneIsInvalid private NullableBool _supportsRandomAccess /* = NullableBool.Undefined */; private NullableBool _isAsync /* = NullableBool.Undefined */; // Lazily initialized on Apple platforms (macOS, iOS, tvOS, Mac Catalyst). - // True = use F_FULLFSYNC (file system supports locking, i.e., is local). - // False = skip F_FULLFSYNC (file system does not support locking, i.e., is a network FS such as NFS/SMB/CIFS). - // Stays Undefined until first queried; UseFullFSync treats Undefined as True (safe default). + // True = use F_FULLFSYNC. + // False = skip F_FULLFSYNC because the probe indicates the file system should avoid it + // (e.g., NFS/SMB/CIFS/SMB2, or when the probe fails). + // Stays Undefined until first queried; UseFullFSync treats Undefined as True. // See https://github.com/dotnet/runtime/issues/124722. private NullableBool _useFullFsync /* = NullableBool.Undefined */; private bool _deleteOnClose; private bool _isLocked; - // Returns true when F_FULLFSYNC should be attempted (the file is on a local file system or - // the file system type is unknown). Returns false only when the file is confirmed to be on a - // network file system where F_FULLFSYNC can silently discard writes. The value is only used - // on Apple platforms; native code ignores it on other platforms. + // Returns true when F_FULLFSYNC should be attempted. Returns false when + // FileSystemSupportsLocking(LOCK_SH, accessWrite: true) reports the file system as unsupported + // for this probe, which includes known network file systems and probe failures. + // The value is only used on Apple platforms; native code ignores it on other platforms. // Lazily computed and cached on first access to avoid a syscall for callers that never flush to disk. internal bool UseFullFSync {