From 0be0aa227b015dbe86d293f9dd829de97de93764 Mon Sep 17 00:00:00 2001 From: Carl Johnsen Date: Thu, 12 Mar 2026 06:23:26 +0100 Subject: [PATCH] Mac doesn't have O_DIRECT, but it has F_NOCACHE --- proprietary/DiskImage/Disk/Mac.cs | 22 +++++++++++++----- .../libSystem_wrapper.dylib | Bin 49872 -> 49968 bytes .../macos_libSystem_wrapper.c | 7 ++++++ 3 files changed, 23 insertions(+), 6 deletions(-) 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 6d3a01838c9bed2fa5791b167954ad12aabfb165..a8af6e7960cd04c71bf2fc0b5f4ce87953d93bec 100755 GIT binary patch delta 1397 zcmcc6%DkbCd4jlP1p@;E44-;#)`>`LeZWH3=9m$ z3=9nd3=9kj3JeSZ3=9lR3=9ks3=9lA6c`xT7#J8d6d~f=3=kS>GMEC{2ErgKSS1u0 z7+5A63QI^sO|UL!GME-)mfQGyj)wiQ7fh1{gbM^WC@?T^FfcG^GB7ZJtlcqrqHqE4 zbOr_nkLDu^v4{EPT_)cXR%T3}Y$T%YW)Be;h&`N^mX@Z+FW&;TH`Jr^sYml0fzH}L z-L-%Afx>4OBLf4&28I{xa2*W)Ra3Yb82+oKO->ilWt=*Bp^Mh${UZF_%mNLJn;8{; z^Ybcz6hr;RG+EHVx}Fi_ruekvypkM-_{{v|k{kvG2F7>>bCA8v3?Dccm>CX$hzINp z%nTDa7#Nt0jTnT@Oc*2>nDX-D6N}Or_)0VLN*H7rn83!w=jA6SCTFBFaDt7CXHb8@ zZeRd%GAN|^85tM~KtagBz#z!LkjnrPaDWQPGcbVp512u=Gcf2v`2tXRYmnO*7(hM; zD{w;NgOsr{FfhPq1qO)EU{0F+&_IV3?8wdBhST{bIZR-@F?phcyWj$l4rnZbOkObg zrGvfb4yY(Bae?@tn4cWs2u@s%;Kb!<&1AqlS;;91HFY&WQV+<_;MCOsO>(Kfi(LE{3pwy+fp#d#*HGrJYz#t1vZ4HwbItMfAP5$a^%_u+F z&_&xsgn?no-)hH||C<>m9$=2Y_P?3o;@@h41}6DylXFC5cuz1hFo3cv2rt;Y&_&f6lF%A{+oLD6 z0|XP=8&DbpC$tlwgw{eTMO!jb6QwZOGCi|@-o=gBUpd+*!OaCyA{tm z8~q9NH zGzc&-FhnRYFjz1!FfcJNFn}B}LxF(-WZDC$I5z_WLjcrR7{$!MzyQLG6Ah(VZzwP@ zFikWRmXL;;5UfAdNlD)%uxk~Mugss>KPL+a7YI&JU|`^2U|`T>U|;}QJ7e-h;R4=x z1_lO?<|7KRhxz4QCf^fQX4Ic-B%;p84iOiKJF8&ppw> zlCfd4pz9xgUImak1_lNn1_lPk$$|#f^$hWu`N<_Y5SKDSIPnY`AU-q00S*Rch6n5n z%nTDa7#Nt0jTl7COc*2?nDX-D6N}Or_)9bMN*LrGup1bFG=PGHpOJyV0TdokcZ2v3 zKz?UnV31>AU;yz2pz=Bl3}Ot73?Ky34hbWWT7}6LhB~ZZhix=`$2ZAg0^^d&6CK_sb}=7FLKBn#q0V$%^En~vbvbhKu=z%*IODGD_@8_=S&0UDi9 zal+BrfEJx=R?j`vp=s%{WUKIs(r+?rK+&lJ! zgQt_2w!8%?QuF?Coy&S%w0YIhtwyuvznX8f*4f&2lK;AetGv<_&dvA^@)ZLE01A%T A=>Px# 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); +}