diff --git a/Directory.Build.props b/Directory.Build.props index 1fd2481..4f27924 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -26,7 +26,7 @@ - 0.5.0 + 0.5.1 0.9.9999.0 Microsoft diff --git a/README.md b/README.md index 4e6d27f..326eec4 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,7 @@ File clones on Windows do not actually allocate space on-drive for the clone. Th [![NuGet version (CopyOnWrite)](https://img.shields.io/nuget/v/CopyOnWrite?style=plastic)](https://www.nuget.org/packages/CopyOnWrite) +* 0.5.1 August 2026: Fix file cloning on .NET 11 by using the native `HANDLE` layout for `DUPLICATE_EXTENTS_DATA`. * 0.5.0 December 2024: Move package framework support from .NET 6 to .NET 8 after 6 reached end-of-life. * 0.4.2 November 2024: Loosened error handling getting volume information, any volume resulting in an error is ignored. This aids continued failures finding new unique error types based on al lthe various disk subsystems and drivers out there. * 0.4.1 October 2024: Add ERROR_DEV_NOT_EXIST handling on volume enumeration diff --git a/lib/Windows/NativeMethods.cs b/lib/Windows/NativeMethods.cs index b533235..10758a9 100644 --- a/lib/Windows/NativeMethods.cs +++ b/lib/Windows/NativeMethods.cs @@ -168,9 +168,9 @@ public enum FileSystemFeature : uint public static readonly int SizeOfDuplicateExtentsData = Marshal.SizeOf(typeof(DUPLICATE_EXTENTS_DATA)); [StructLayout(LayoutKind.Sequential)] - public ref struct DUPLICATE_EXTENTS_DATA + public struct DUPLICATE_EXTENTS_DATA { - public SafeHandle? FileHandle; + public IntPtr FileHandle; public long SourceFileOffset; public long TargetFileOffset; public long ByteCount; diff --git a/lib/Windows/WindowsCopyOnWriteFilesystem.cs b/lib/Windows/WindowsCopyOnWriteFilesystem.cs index 0de225f..e6a21d7 100644 --- a/lib/Windows/WindowsCopyOnWriteFilesystem.cs +++ b/lib/Windows/WindowsCopyOnWriteFilesystem.cs @@ -246,7 +246,6 @@ public void CloneFile(string source, string destination, CloneFlags cloneFlags) } } - // Separate method to avoid error creating DUPLICATE_EXTENTS_DATA on stack in async method. private void DuplicateExtents( SafeFileHandle sourceFileHandle, SafeFileHandle destFileHandle, @@ -254,6 +253,35 @@ private void DuplicateExtents( VolumeInfo sourceVolume, string source, string destination) + { + bool sourceFileHandleRefAdded = false; + try + { + sourceFileHandle.DangerousAddRef(ref sourceFileHandleRefAdded); + DuplicateExtentsCore( + sourceFileHandle.DangerousGetHandle(), + destFileHandle, + sourceFileLength, + sourceVolume, + source, + destination); + } + finally + { + if (sourceFileHandleRefAdded) + { + sourceFileHandle.DangerousRelease(); + } + } + } + + private void DuplicateExtentsCore( + IntPtr sourceFileHandle, + SafeFileHandle destFileHandle, + long sourceFileLength, + VolumeInfo sourceVolume, + string source, + string destination) { var duplicateExtentsData = new NativeMethods.DUPLICATE_EXTENTS_DATA { diff --git a/tests/unit/Windows/CopyOnWriteTests_Windows.cs b/tests/unit/Windows/CopyOnWriteTests_Windows.cs index ed552bb..65f9cd5 100644 --- a/tests/unit/Windows/CopyOnWriteTests_Windows.cs +++ b/tests/unit/Windows/CopyOnWriteTests_Windows.cs @@ -21,6 +21,20 @@ namespace Microsoft.CopyOnWrite.Tests.Windows; [DoNotParallelize] // Ensure the 32-bit and 64-bit suites do not collide. public sealed class CopyOnWriteTests_Windows { + [TestMethod] + public void DuplicateExtentsDataMatchesWin32Layout() + { + Type duplicateExtentsDataType = typeof(NativeMethods.DUPLICATE_EXTENTS_DATA); + + Assert.IsFalse(duplicateExtentsDataType.IsByRefLike); + Assert.AreEqual(typeof(IntPtr), duplicateExtentsDataType.GetField(nameof(NativeMethods.DUPLICATE_EXTENTS_DATA.FileHandle))?.FieldType); + Assert.AreEqual(0, Marshal.OffsetOf(nameof(NativeMethods.DUPLICATE_EXTENTS_DATA.FileHandle)).ToInt32()); + Assert.AreEqual(8, Marshal.OffsetOf(nameof(NativeMethods.DUPLICATE_EXTENTS_DATA.SourceFileOffset)).ToInt32()); + Assert.AreEqual(16, Marshal.OffsetOf(nameof(NativeMethods.DUPLICATE_EXTENTS_DATA.TargetFileOffset)).ToInt32()); + Assert.AreEqual(24, Marshal.OffsetOf(nameof(NativeMethods.DUPLICATE_EXTENTS_DATA.ByteCount)).ToInt32()); + Assert.AreEqual(32, Marshal.SizeOf()); + } + [TestMethod] [DataRow(false)] [DataRow(true)]