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/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/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..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 @@ -41,9 +41,35 @@ public sealed partial class SafeFileHandle : SafeHandleZeroOrMinusOneIsInvalid private NullableBool _canSeek /* = NullableBool.Undefined */; private NullableBool _supportsRandomAccess /* = NullableBool.Undefined */; private NullableBool _isAsync /* = NullableBool.Undefined */; + // Lazily initialized on Apple platforms (macOS, iOS, tvOS, Mac Catalyst). + // 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. 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 + { + 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) { } @@ -461,6 +487,7 @@ private bool Init(string path, FileMode mode, FileAccess access, FileShare share return 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/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/pal_io.c b/src/native/libs/System.Native/pal_io.c index 8bd4b6affea1c1..621839231895b6 100644 --- a/src/native/libs/System.Native/pal_io.c +++ b/src/native/libs/System.Native/pal_io.c @@ -783,21 +783,26 @@ int32_t SystemNative_FChMod(intptr_t fd, int32_t mode) #endif /* HAVE_FCHMOD */ } -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 - while ((result = fcntl(fileDescriptor, F_FULLFSYNC)) < 0 && errno == EINTR); - if (result >= 0) + if (useFullFSync) { - 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. + } +#else + (void)useFullFSync; #endif while ((result = fsync(fileDescriptor)) < 0 && errno == EINTR); return result; diff --git a/src/native/libs/System.Native/pal_io.h b/src/native/libs/System.Native/pal_io.h index 10332df8043230..02b58edd993a5c 100644 --- a/src/native/libs/System.Native/pal_io.h +++ b/src/native/libs/System.Native/pal_io.h @@ -535,11 +535,12 @@ 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. */ -PALEXPORT int32_t SystemNative_FSync(intptr_t fd); +PALEXPORT int32_t SystemNative_FSync(intptr_t fd, int32_t useFullFSync); /** * Changes the advisory lock status on a given File Descriptor