Made Windows handle unaligned read and write

This commit is contained in:
Carl Johnsen
2026-03-11 15:10:38 +01:00
parent dac373a03f
commit 5b52eae0bc
+50 -44
View File
@@ -274,51 +274,51 @@ namespace Duplicati.Proprietary.DiskImage.Disk
int length = destination.Length;
if (offset % SectorSize != 0)
throw new InvalidOperationException($"Address {offset:X016} is not a multiple of the sector size {SectorSize}");
if (length % SectorSize != 0)
throw new InvalidOperationException($"The requested length of {length} is not a multiple of sector size {SectorSize}");
if (offset + length > Size)
throw new InvalidOperationException($"The requested read would read beyond disk size: {offset} + {length} > {Size}");
// Calculate aligned offset and length for unbuffered I/O (FILE_FLAG_NO_BUFFERING)
long alignedOffset = (offset / SectorSize) * SectorSize;
long offsetDelta = offset - alignedOffset;
long alignedLength = ((offsetDelta + length + SectorSize - 1) / SectorSize) * SectorSize;
await m_ioLock.WaitAsync(cancellationToken);
try
{
// Ensure reusable buffer is large enough
EnsureAlignedBuffer(length);
EnsureAlignedBuffer((int)alignedLength);
// Move file pointer to the desired offset
var seeked = SetFilePointerEx(m_deviceHandle, offset, out _, SeekOrigin.Begin);
// Move file pointer to the aligned offset
var seeked = SetFilePointerEx(m_deviceHandle, alignedOffset, out _, SeekOrigin.Begin);
if (!seeked)
{
var error = Marshal.GetLastWin32Error();
var msg = new System.ComponentModel.Win32Exception(error).Message;
throw new IOException($"Failed to seek to offset. Win32 Error Code: {error}. Message: {msg}");
throw new IOException($"Failed to seek to offset {alignedOffset}. Win32 Error Code: {error}. Message: {msg}");
}
bool result = ReadFile(m_deviceHandle, m_alignedBuffer!, (uint)length, out uint bytesRead, IntPtr.Zero);
bool result = ReadFile(m_deviceHandle, m_alignedBuffer!, (uint)alignedLength, out uint bytesRead, IntPtr.Zero);
if (!result || bytesRead != length)
if (!result || bytesRead < offsetDelta + length)
{
var error = Marshal.GetLastWin32Error();
var msg = new System.ComponentModel.Win32Exception(error).Message;
throw new IOException($"Failed to read from disk. Win32 Error Code: {error}. Message: {msg}");
throw new IOException($"Failed to read from disk at aligned offset {alignedOffset}. Win32 Error Code: {error}. Message: {msg}");
}
// Copy from native buffer to destination
// Copy only the requested portion from the aligned buffer
unsafe
{
var srcPtr = (byte*)m_alignedBuffer!.DangerousGetHandle().ToPointer();
var destSpan = destination.Span;
for (int i = 0; i < bytesRead; i++)
int bytesToCopy = Math.Min(length, (int)(bytesRead - offsetDelta));
for (int i = 0; i < bytesToCopy; i++)
{
destSpan[i] = srcPtr[i];
destSpan[i] = srcPtr[offsetDelta + i];
}
}
return (int)bytesRead;
return Math.Min(length, (int)(bytesRead - offsetDelta));
}
finally
{
@@ -357,58 +357,64 @@ namespace Duplicati.Proprietary.DiskImage.Disk
int dataLength = data.Length;
// Pad to sector size if necessary
int remainder = dataLength % (int)m_sectorSize;
int alignedLength = remainder == 0
? dataLength
: dataLength + ((int)m_sectorSize - remainder);
if (offset + dataLength > Size)
throw new InvalidOperationException($"The requested write would write beyond disk size: {offset} + {dataLength} > {Size}");
// Calculate aligned offset and length for unbuffered I/O (FILE_FLAG_NO_BUFFERING)
long alignedOffset = (offset / SectorSize) * SectorSize;
long offsetDelta = offset - alignedOffset;
long alignedLength = ((offsetDelta + dataLength + SectorSize - 1) / SectorSize) * SectorSize;
await m_ioLock.WaitAsync(cancellationToken);
try
{
// Ensure reusable buffer is large enough
EnsureAlignedBuffer(alignedLength);
EnsureAlignedBuffer((int)alignedLength);
// Move file pointer to the desired offset
var seeked = SetFilePointerEx(m_deviceHandle, offset, out _, SeekOrigin.Begin);
if (!seeked)
{
var error = Marshal.GetLastWin32Error();
var msg = new System.ComponentModel.Win32Exception(error).Message;
throw new IOException($"Failed to seek to offset. Win32 Error Code: {error}. Message: {msg}");
}
// Check if this is an unaligned write (needs read-modify-write)
bool isUnaligned = offsetDelta != 0 || dataLength != alignedLength;
if (remainder != 0)
if (isUnaligned)
{
// Read existing data to fill the remainder
// Move file pointer to the aligned offset
var seeked = SetFilePointerEx(m_deviceHandle, alignedOffset, out _, SeekOrigin.Begin);
if (!seeked)
{
var error = Marshal.GetLastWin32Error();
var msg = new System.ComponentModel.Win32Exception(error).Message;
throw new IOException($"Failed to seek to aligned offset {alignedOffset}. Win32 Error Code: {error}. Message: {msg}");
}
// Read existing data first (read-modify-write)
bool readResult = ReadFile(m_deviceHandle, m_alignedBuffer!, (uint)alignedLength, out uint bytesRead, IntPtr.Zero);
if (!readResult)
{
var error = Marshal.GetLastWin32Error();
var msg = new System.ComponentModel.Win32Exception(error).Message;
throw new IOException($"Failed to read existing data for padding. Win32 Error Code: {error}. Message: {msg}");
}
// Seek back to the original offset after reading
var seekedBack = SetFilePointerEx(m_deviceHandle, offset, out _, SeekOrigin.Begin);
if (!seekedBack)
{
var error = Marshal.GetLastWin32Error();
var msg = new System.ComponentModel.Win32Exception(error).Message;
throw new IOException($"Failed to seek back to offset after reading. Win32 Error Code: {error}. Message: {msg}");
throw new IOException($"Failed to read existing data for unaligned write at offset {alignedOffset}. Win32 Error Code: {error}. Message: {msg}");
}
}
// Copy data from managed buffer to native buffer
// Copy data from managed buffer to native buffer at the correct offset
unsafe
{
var destPtr = (byte*)m_alignedBuffer!.DangerousGetHandle().ToPointer();
var srcSpan = data.Span;
for (int i = 0; i < dataLength; i++)
{
destPtr[i] = srcSpan[i];
destPtr[offsetDelta + i] = srcSpan[i];
}
}
// Move file pointer to the aligned offset for writing
var seekedForWrite = SetFilePointerEx(m_deviceHandle, alignedOffset, out _, SeekOrigin.Begin);
if (!seekedForWrite)
{
var error = Marshal.GetLastWin32Error();
var msg = new System.ComponentModel.Win32Exception(error).Message;
throw new IOException($"Failed to seek to aligned offset {alignedOffset} for writing. Win32 Error Code: {error}. Message: {msg}");
}
bool result = WriteFile(m_deviceHandle, m_alignedBuffer!, (uint)alignedLength, out uint bytesWritten, IntPtr.Zero);
if (!result)