diff --git a/proprietary/DiskImage/Disk/Mac.cs b/proprietary/DiskImage/Disk/Mac.cs index 8e9101e6b..8a691b005 100644 --- a/proprietary/DiskImage/Disk/Mac.cs +++ b/proprietary/DiskImage/Disk/Mac.cs @@ -34,9 +34,6 @@ namespace Duplicati.Proprietary.DiskImage.Disk private const int O_RDONLY = 0x0000; private const int O_RDWR = 0x0002; private const int O_SYNC = 0x0080; - // F_NOCACHE: bypass kernel cache for unbuffered I/O (equivalent to O_DIRECT on Linux) - // Value is 0x100000 on macOS - private const int O_DIRECT = 0x100000; private readonly string m_devicePath; private int m_fileDescriptor = -1; @@ -153,9 +150,8 @@ namespace Duplicati.Proprietary.DiskImage.Disk if (m_initialized) return Task.FromResult(true); - // Open the device with O_DIRECT (F_NOCACHE) for unbuffered I/O - // Note: O_DIRECT requires sector-aligned buffers and lengths - int flags = enableWrite ? O_RDWR | O_DIRECT : O_RDONLY | O_DIRECT; + // Open the device without O_DIRECT initially + int flags = enableWrite ? O_RDWR : O_RDONLY; m_fileDescriptor = open(m_devicePath, flags); if (m_fileDescriptor < 0) @@ -166,6 +162,17 @@ namespace Duplicati.Proprietary.DiskImage.Disk return Task.FromResult(false); } + // Use F_NOCACHE to bypass kernel cache (equivalent to O_DIRECT on Linux) + if (fcntl_nocache(m_fileDescriptor) < 0) + { + int errorCode = Marshal.GetLastWin32Error(); + string errorMessage = Marshal.GetPInvokeErrorMessage(errorCode); + Duplicati.Library.Logging.Log.WriteErrorMessage(LOGTAG, "initialize", null, $"Failed to set F_NOCACHE on device {m_devicePath}: {errorMessage} (errno: {errorCode})"); + close(m_fileDescriptor); + m_fileDescriptor = -1; + return Task.FromResult(false); + } + // Get disk geometry using ioctls try { @@ -436,6 +443,9 @@ namespace Duplicati.Proprietary.DiskImage.Disk [LibraryImport("runtimes/osx/native/libSystem_wrapper.dylib", SetLastError = true)] internal static partial int ioctl_no_arg(int fd, ulong request); + [LibraryImport("runtimes/osx/native/libSystem_wrapper.dylib", SetLastError = true)] + internal static partial int fcntl_nocache(int fd); + [LibraryImport("libSystem", SetLastError = true)] private static partial int open([MarshalAs(UnmanagedType.LPStr)] string pathname, int flags); diff --git a/proprietary/DiskImage/NativeLibraryWrappers/libSystem_wrapper.dylib b/proprietary/DiskImage/NativeLibraryWrappers/libSystem_wrapper.dylib index 6d3a01838..a8af6e796 100755 Binary files a/proprietary/DiskImage/NativeLibraryWrappers/libSystem_wrapper.dylib and b/proprietary/DiskImage/NativeLibraryWrappers/libSystem_wrapper.dylib differ diff --git a/proprietary/DiskImage/NativeLibraryWrappers/macos_libSystem_wrapper.c b/proprietary/DiskImage/NativeLibraryWrappers/macos_libSystem_wrapper.c index 8e4a58498..e21be8aa8 100644 --- a/proprietary/DiskImage/NativeLibraryWrappers/macos_libSystem_wrapper.c +++ b/proprietary/DiskImage/NativeLibraryWrappers/macos_libSystem_wrapper.c @@ -1,5 +1,6 @@ #include #include +#include // TODO This C wrapper is required since P/Invoke cannot correctly pass variadic // arguments to ioctl on non-Windows platforms. @@ -25,3 +26,9 @@ int ioctl_no_arg(int fd, unsigned long request) { return ioctl(fd, request); } + +// Wrapper for fcntl to set F_NOCACHE (equivalent to O_DIRECT on Linux) +int fcntl_nocache(int fd) +{ + return fcntl(fd, F_NOCACHE, 1); +}