Implement AlignmentMatchingStorage classes

This commit is contained in:
Alex Barney 2022-03-12 15:02:54 -07:00
parent eaff8059ba
commit 2c154ec3ba
2 changed files with 709 additions and 0 deletions

View file

@ -0,0 +1,518 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using LibHac.Common;
using LibHac.Diag;
using LibHac.Fs;
using LibHac.Util;
namespace LibHac.FsSystem;
public interface IAlignmentMatchingStorageSize { }
[StructLayout(LayoutKind.Sequential, Size = 1)] public struct AlignmentMatchingStorageSize1 : IAlignmentMatchingStorageSize { }
[StructLayout(LayoutKind.Sequential, Size = 16)] public struct AlignmentMatchingStorageSize16 : IAlignmentMatchingStorageSize { }
[StructLayout(LayoutKind.Sequential, Size = 512)] public struct AlignmentMatchingStorageSize512 : IAlignmentMatchingStorageSize { }
/// <summary>
/// Handles accessing a base <see cref="IStorage"/> that must always be accessed via an aligned offset and size.
/// </summary>
/// <typeparam name="TDataAlignment">The alignment of all accesses made to the base storage.
/// Must be a power of 2 that is less than or equal to 0x200.</typeparam>
/// <typeparam name="TBufferAlignment">The alignment of the destination buffer for the core read. Must be a power of 2.</typeparam>
/// <remarks><para>This class uses a work buffer on the stack to avoid allocations. Because of this the data alignment
/// must be kept small; no larger than 0x200. The <see cref="AlignmentMatchingStoragePooledBuffer{TBufferAlignment}"/> class
/// should be used for data alignment sizes larger than this.</para>
/// <para>Based on FS 13.1.0 (nnSdk 13.4.0)</para></remarks>
[SkipLocalsInit]
public class AlignmentMatchingStorage<TDataAlignment, TBufferAlignment> : IStorage
where TDataAlignment : struct, IAlignmentMatchingStorageSize
where TBufferAlignment : struct, IAlignmentMatchingStorageSize
{
public static uint DataAlign => (uint)Unsafe.SizeOf<TDataAlignment>();
public static uint BufferAlign => (uint)Unsafe.SizeOf<TBufferAlignment>();
public static uint DataAlignMax => 0x200;
private static void VerifyTypeParameters()
{
Abort.DoAbortUnless(DataAlign <= DataAlignMax);
Abort.DoAbortUnless(BitUtil.IsPowerOfTwo(DataAlign));
Abort.DoAbortUnless(BitUtil.IsPowerOfTwo(BufferAlign));
}
private IStorage _baseStorage;
private long _baseStorageSize;
private bool _isBaseStorageSizeDirty;
private SharedRef<IStorage> _sharedBaseStorage;
public AlignmentMatchingStorage(ref SharedRef<IStorage> baseStorage)
{
VerifyTypeParameters();
_baseStorage = baseStorage.Get;
_isBaseStorageSizeDirty = true;
_sharedBaseStorage = SharedRef<IStorage>.CreateMove(ref baseStorage);
}
public AlignmentMatchingStorage(IStorage baseStorage)
{
VerifyTypeParameters();
_baseStorage = baseStorage;
_isBaseStorageSizeDirty = true;
}
public override void Dispose()
{
_sharedBaseStorage.Destroy();
base.Dispose();
}
public override Result Read(long offset, Span<byte> destination)
{
Span<byte> workBuffer = stackalloc byte[(int)DataAlign];
if (destination.Length == 0)
return Result.Success;
Result rc = GetSize(out long totalSize);
if (rc.IsFailure()) return rc.Miss();
if (!CheckAccessRange(offset, destination.Length, totalSize))
return ResultFs.OutOfRange.Log();
return AlignmentMatchingStorageImpl.Read(_baseStorage, workBuffer, DataAlign, BufferAlign, offset, destination);
}
public override Result Write(long offset, ReadOnlySpan<byte> source)
{
Span<byte> workBuffer = stackalloc byte[(int)DataAlign];
if (source.Length == 0)
return Result.Success;
Result rc = GetSize(out long totalSize);
if (rc.IsFailure()) return rc.Miss();
if (!CheckAccessRange(offset, source.Length, totalSize))
return ResultFs.OutOfRange.Log();
return AlignmentMatchingStorageImpl.Write(_baseStorage, workBuffer, DataAlign, BufferAlign, offset, source);
}
public override Result Flush()
{
return _baseStorage.Flush();
}
public override Result SetSize(long size)
{
Result rc = _baseStorage.SetSize(Alignment.AlignUpPow2(size, DataAlign));
_isBaseStorageSizeDirty = true;
return rc;
}
public override Result GetSize(out long size)
{
UnsafeHelpers.SkipParamInit(out size);
if (_isBaseStorageSizeDirty)
{
Result rc = _baseStorage.GetSize(out long baseStorageSize);
if (rc.IsFailure()) return rc.Miss();
_baseStorageSize = baseStorageSize;
_isBaseStorageSizeDirty = false;
}
size = _baseStorageSize;
return Result.Success;
}
public override Result OperateRange(Span<byte> outBuffer, OperationId operationId, long offset, long size,
ReadOnlySpan<byte> inBuffer)
{
if (operationId == OperationId.InvalidateCache)
{
return _baseStorage.OperateRange(OperationId.InvalidateCache, offset, size);
}
if (size == 0)
return Result.Success;
Result rc = GetSize(out long baseStorageSize);
if (rc.IsFailure()) return rc.Miss();
if (!CheckOffsetAndSize(offset, size))
return ResultFs.OutOfRange.Log();
long validSize = Math.Min(size, baseStorageSize - offset);
long alignedOffset = Alignment.AlignDownPow2(offset, DataAlign);
long alignedOffsetEnd = Alignment.AlignUpPow2(offset + validSize, DataAlign);
long alignedSize = alignedOffsetEnd - alignedOffset;
return _baseStorage.OperateRange(outBuffer, operationId, alignedOffset, alignedSize, inBuffer);
}
}
/// <summary>
/// Handles accessing a base <see cref="IStorage"/> that must always be accessed via an aligned offset and size.
/// </summary>
/// <typeparam name="TBufferAlignment">The alignment of the destination buffer for the core read. Must be a power of 2.</typeparam>
/// <remarks><para>On every access this class allocates a work buffer that is used for handling any partial blocks at
/// the beginning or end of the requested range. For data alignment sizes of 0x200 or smaller
/// <see cref="AlignmentMatchingStorage{TDataAlignment,TBufferAlignment}"/> should be used instead
/// to avoid these allocations.</para>
/// <para>Based on FS 13.1.0 (nnSdk 13.4.0)</para></remarks>
public class AlignmentMatchingStoragePooledBuffer<TBufferAlignment> : IStorage
where TBufferAlignment : struct, IAlignmentMatchingStorageSize
{
public static uint BufferAlign => (uint)Unsafe.SizeOf<TBufferAlignment>();
private IStorage _baseStorage;
private long _baseStorageSize;
private uint _dataAlignment;
private bool _isBaseStorageSizeDirty;
// LibHac addition: This field goes unused if initialized with a plain IStorage.
// The original class uses a template for both the shared and non-shared IStorage which avoids needing this field.
private SharedRef<IStorage> _sharedBaseStorage;
public AlignmentMatchingStoragePooledBuffer(IStorage baseStorage, int dataAlign)
{
Abort.DoAbortUnless(BitUtil.IsPowerOfTwo(BufferAlign));
_baseStorage = baseStorage;
_dataAlignment = (uint)dataAlign;
_isBaseStorageSizeDirty = true;
Assert.SdkRequires(BitUtil.IsPowerOfTwo(dataAlign), "DataAlign must be a power of 2.");
}
public AlignmentMatchingStoragePooledBuffer(in SharedRef<IStorage> baseStorage, int dataAlign)
{
Abort.DoAbortUnless(BitUtil.IsPowerOfTwo(BufferAlign));
_baseStorage = baseStorage.Get;
_dataAlignment = (uint)dataAlign;
_isBaseStorageSizeDirty = true;
Assert.SdkRequires(BitUtil.IsPowerOfTwo(dataAlign), "DataAlign must be a power of 2.");
_sharedBaseStorage = SharedRef<IStorage>.CreateCopy(in baseStorage);
}
public override void Dispose()
{
_sharedBaseStorage.Destroy();
base.Dispose();
}
public override Result Read(long offset, Span<byte> destination)
{
if (destination.Length == 0)
return Result.Success;
Result rc = GetSize(out long baseStorageSize);
if (rc.IsFailure()) return rc.Miss();
if (!CheckAccessRange(offset, destination.Length, baseStorageSize))
return ResultFs.OutOfRange.Log();
using var pooledBuffer = new PooledBuffer();
pooledBuffer.AllocateParticularlyLarge((int)_dataAlignment, (int)_dataAlignment);
return AlignmentMatchingStorageImpl.Read(_baseStorage, pooledBuffer.GetBuffer(), _dataAlignment, BufferAlign,
offset, destination);
}
public override Result Write(long offset, ReadOnlySpan<byte> source)
{
if (source.Length == 0)
return Result.Success;
Result rc = GetSize(out long baseStorageSize);
if (rc.IsFailure()) return rc.Miss();
if (!CheckAccessRange(offset, source.Length, baseStorageSize))
return ResultFs.OutOfRange.Log();
using var pooledBuffer = new PooledBuffer();
pooledBuffer.AllocateParticularlyLarge((int)_dataAlignment, (int)_dataAlignment);
return AlignmentMatchingStorageImpl.Write(_baseStorage, pooledBuffer.GetBuffer(), _dataAlignment, BufferAlign,
offset, source);
}
public override Result Flush()
{
return _baseStorage.Flush();
}
public override Result SetSize(long size)
{
Result rc = _baseStorage.SetSize(Alignment.AlignUpPow2(size, _dataAlignment));
_isBaseStorageSizeDirty = true;
return rc;
}
public override Result GetSize(out long size)
{
UnsafeHelpers.SkipParamInit(out size);
if (_isBaseStorageSizeDirty)
{
Result rc = _baseStorage.GetSize(out long baseStorageSize);
if (rc.IsFailure()) return rc.Miss();
_isBaseStorageSizeDirty = false;
_baseStorageSize = baseStorageSize;
}
size = _baseStorageSize;
return Result.Success;
}
public override Result OperateRange(Span<byte> outBuffer, OperationId operationId, long offset, long size,
ReadOnlySpan<byte> inBuffer)
{
if (operationId == OperationId.InvalidateCache)
{
return _baseStorage.OperateRange(OperationId.InvalidateCache, offset, size);
}
if (size == 0)
return Result.Success;
Result rc = GetSize(out long baseStorageSize);
if (rc.IsFailure()) return rc.Miss();
if (!CheckOffsetAndSize(offset, size))
return ResultFs.OutOfRange.Log();
long validSize = Math.Min(size, baseStorageSize - offset);
long alignedOffset = Alignment.AlignDownPow2(offset, _dataAlignment);
long alignedOffsetEnd = Alignment.AlignUpPow2(offset + validSize, _dataAlignment);
long alignedSize = alignedOffsetEnd - alignedOffset;
return _baseStorage.OperateRange(outBuffer, operationId, alignedOffset, alignedSize, inBuffer);
}
}
/// <summary>
/// Handles accessing a base <see cref="IStorage"/> that must always be accessed via an aligned offset and size.
/// </summary>
/// <typeparam name="TBufferAlignment">The alignment of the destination buffer for the core read. Must be a power of 2.</typeparam>
/// <remarks><para>This class is basically the same as <see cref="AlignmentMatchingStoragePooledBuffer{TBufferAlignment}"/> except
/// it doesn't allocate a work buffer for reads that are already aligned, and it ignores the buffer alignment for reads.</para>
/// <para>Based on FS 13.1.0 (nnSdk 13.4.0)</para></remarks>
public class AlignmentMatchingStorageInBulkRead<TBufferAlignment> : IStorage
where TBufferAlignment : struct, IAlignmentMatchingStorageSize
{
public static uint BufferAlign => (uint)Unsafe.SizeOf<TBufferAlignment>();
private IStorage _baseStorage;
private SharedRef<IStorage> _sharedBaseStorage;
private long _baseStorageSize;
private uint _dataAlignment;
public AlignmentMatchingStorageInBulkRead(IStorage baseStorage, int dataAlignment)
{
Abort.DoAbortUnless(BitUtil.IsPowerOfTwo(BufferAlign));
_baseStorage = baseStorage;
_baseStorageSize = -1;
_dataAlignment = (uint)dataAlignment;
Assert.SdkRequires(BitUtil.IsPowerOfTwo(dataAlignment));
}
public AlignmentMatchingStorageInBulkRead(in SharedRef<IStorage> baseStorage, int dataAlignment)
{
Abort.DoAbortUnless(BitUtil.IsPowerOfTwo(BufferAlign));
_baseStorage = baseStorage.Get;
_baseStorageSize = -1;
_dataAlignment = (uint)dataAlignment;
Assert.SdkRequires(BitUtil.IsPowerOfTwo(dataAlignment));
_sharedBaseStorage = SharedRef<IStorage>.CreateCopy(in baseStorage);
}
public override void Dispose()
{
_sharedBaseStorage.Destroy();
base.Dispose();
}
// The original template doesn't define this function, requiring a specialized function for each TBufferAlignment used.
// The only buffer alignment used by that template is 1, so we use that specialization for our Read method.
public override Result Read(long offset, Span<byte> destination)
{
if (destination.Length == 0)
return Result.Success;
Result rc = GetSize(out long baseStorageSize);
if (rc.IsFailure()) return rc.Miss();
if (!CheckAccessRange(offset, destination.Length, baseStorageSize))
return ResultFs.OutOfRange.Log();
// Calculate the aligned offsets of the requested region.
long offsetEnd = offset + destination.Length;
long alignedOffset = Alignment.AlignDownPow2(offset, _dataAlignment);
long alignedOffsetEnd = Alignment.AlignUpPow2(offsetEnd, _dataAlignment);
long alignedSize = alignedOffsetEnd - alignedOffset;
using var pooledBuffer = new PooledBuffer();
// If we aren't aligned we need to allocate a buffer.
if (alignedOffset != offset || alignedSize != destination.Length)
{
if (alignedSize <= PooledBuffer.GetAllocatableSizeMax())
{
// Try to allocate a buffer that will fit the entire aligned read.
pooledBuffer.Allocate((int)alignedSize, (int)_dataAlignment);
// If we were able to get a buffer that fits the entire aligned read then read it
// into the buffer and copy the unaligned portion to the destination buffer.
if (alignedSize <= pooledBuffer.GetSize())
{
rc = _baseStorage.Read(alignedOffset, pooledBuffer.GetBuffer().Slice(0, (int)alignedSize));
if (rc.IsFailure()) return rc.Miss();
pooledBuffer.GetBuffer().Slice((int)(offset - alignedOffset), destination.Length)
.CopyTo(destination);
return Result.Success;
}
// We couldn't get as large a buffer as we wanted.
// Shrink the buffer since we only need a single block.
pooledBuffer.Shrink((int)_dataAlignment);
}
else
{
// The requested read is larger than we can allocate, so only allocate a single block.
pooledBuffer.Allocate((int)_dataAlignment, (int)_dataAlignment);
}
}
// Determine read extents for the aligned portion.
long coreOffset = Alignment.AlignUpPow2(offset, _dataAlignment);
long coreOffsetEnd = Alignment.AlignDownPow2(offsetEnd, _dataAlignment);
// Handle any data before the aligned portion.
if (offset < coreOffset)
{
int headSize = (int)(coreOffset - offset);
Assert.SdkLess(headSize, destination.Length);
rc = _baseStorage.Read(alignedOffset, pooledBuffer.GetBuffer().Slice(0, (int)_dataAlignment));
if (rc.IsFailure()) return rc.Miss();
pooledBuffer.GetBuffer().Slice((int)(offset - alignedOffset), headSize).CopyTo(destination);
}
// Handle the aligned portion.
if (coreOffset < coreOffsetEnd)
{
int coreSize = (int)(coreOffsetEnd - coreOffset);
Span<byte> coreBuffer = destination.Slice((int)(coreOffset - offset), coreSize);
rc = _baseStorage.Read(coreOffset, coreBuffer);
if (rc.IsFailure()) return rc.Miss();
}
// Handle any data after the aligned portion.
if (coreOffsetEnd < offsetEnd)
{
int tailSize = (int)(offsetEnd - coreOffsetEnd);
rc = _baseStorage.Read(coreOffsetEnd, pooledBuffer.GetBuffer().Slice(0, (int)_dataAlignment));
if (rc.IsFailure()) return rc.Miss();
pooledBuffer.GetBuffer().Slice(0, tailSize).CopyTo(destination.Slice((int)(coreOffsetEnd - offset)));
}
return Result.Success;
}
public override Result Write(long offset, ReadOnlySpan<byte> source)
{
if (source.Length == 0)
return Result.Success;
Result rc = GetSize(out long baseStorageSize);
if (rc.IsFailure()) return rc.Miss();
if (!CheckAccessRange(offset, source.Length, baseStorageSize))
return ResultFs.OutOfRange.Log();
using var pooledBuffer = new PooledBuffer((int)_dataAlignment, (int)_dataAlignment);
return AlignmentMatchingStorageImpl.Write(_baseStorage, pooledBuffer.GetBuffer(), _dataAlignment, BufferAlign,
offset, source);
}
public override Result Flush()
{
return _baseStorage.Flush();
}
public override Result SetSize(long size)
{
Result rc = _baseStorage.SetSize(Alignment.AlignUpPow2(size, _dataAlignment));
_baseStorageSize = -1;
return rc;
}
public override Result GetSize(out long size)
{
UnsafeHelpers.SkipParamInit(out size);
if (_baseStorageSize < 0)
{
Result rc = _baseStorage.GetSize(out long baseStorageSize);
if (rc.IsFailure()) return rc.Miss();
_baseStorageSize = baseStorageSize;
}
size = _baseStorageSize;
return Result.Success;
}
public override Result OperateRange(Span<byte> outBuffer, OperationId operationId, long offset, long size,
ReadOnlySpan<byte> inBuffer)
{
if (operationId == OperationId.InvalidateCache)
{
return _baseStorage.OperateRange(OperationId.InvalidateCache, offset, size);
}
if (size == 0)
return Result.Success;
Result rc = GetSize(out long baseStorageSize);
if (rc.IsFailure()) return rc.Miss();
if (!CheckOffsetAndSize(offset, size))
return ResultFs.OutOfRange.Log();
long validSize = Math.Min(size, baseStorageSize - offset);
long alignedOffset = Alignment.AlignDownPow2(offset, _dataAlignment);
long alignedOffsetEnd = Alignment.AlignUpPow2(offset + validSize, _dataAlignment);
long alignedSize = alignedOffsetEnd - alignedOffset;
return _baseStorage.OperateRange(outBuffer, operationId, alignedOffset, alignedSize, inBuffer);
}
}

View file

@ -0,0 +1,191 @@
using System;
using LibHac.Common;
using LibHac.Diag;
using LibHac.Fs;
using LibHac.Util;
namespace LibHac.FsSystem;
/// <summary>
/// Contains the functions used by classes like <see cref="AlignmentMatchingStorage{TDataAlignment,TBufferAlignment}"/> for
/// accessing an aligned <see cref="IStorage"/>.
/// </summary>
/// <remarks>Based on FS 13.1.0 (nnSdk 13.4.0)</remarks>
public static class AlignmentMatchingStorageImpl
{
public static uint GetRoundDownDifference(int value, uint alignment)
{
return (uint)(value - Alignment.AlignDownPow2(value, alignment));
}
public static uint GetRoundDownDifference(long value, uint alignment)
{
return (uint)(value - Alignment.AlignDownPow2(value, alignment));
}
public static uint GetRoundUpDifference(int value, uint alignment)
{
return (uint)(Alignment.AlignUpPow2(value, alignment) - value);
}
private static uint GetRoundUpDifference(long value, uint alignment)
{
return (uint)(Alignment.AlignUpPow2(value, alignment) - value);
}
public static Result Read(in SharedRef<IStorage> storage, Span<byte> workBuffer, uint dataAlignment,
uint bufferAlignment, long offset, Span<byte> destination)
{
return Read(storage.Get, workBuffer, dataAlignment, bufferAlignment, offset, destination);
}
public static Result Write(in SharedRef<IStorage> storage, Span<byte> subBuffer, uint dataAlignment,
uint bufferAlignment, long offset, ReadOnlySpan<byte> source)
{
return Write(storage.Get, subBuffer, dataAlignment, bufferAlignment, offset, source);
}
public static Result Read(IStorage storage, Span<byte> workBuffer, uint dataAlignment, uint bufferAlignment,
long offset, Span<byte> destination)
{
// We don't support buffer alignment because Nintendo never uses any alignment other than 1, and because
// we'd have to mess with pinning the buffer.
Abort.DoAbortUnless(bufferAlignment == 1);
Assert.SdkRequiresGreaterEqual((uint)workBuffer.Length, dataAlignment);
if (destination.Length == 0)
return Result.Success;
// Calculate the range that contains only full data blocks.
uint offsetRoundUpDifference = GetRoundUpDifference(offset, dataAlignment);
long coreOffset = Alignment.AlignUpPow2(offset, dataAlignment);
long coreSize = destination.Length < offsetRoundUpDifference
? 0
: Alignment.AlignDownPow2(destination.Length - offsetRoundUpDifference, dataAlignment);
long coveredOffset = coreSize > 0 ? coreOffset : offset;
// Read the core portion that doesn't contain any partial blocks.
if (coreSize > 0)
{
Result rc = storage.Read(coreOffset, destination.Slice((int)offsetRoundUpDifference, (int)coreSize));
if (rc.IsFailure()) return rc.Miss();
}
// Read any partial block at the head of the requested range
if (offset < coveredOffset)
{
long headOffset = Alignment.AlignDownPow2(offset, dataAlignment);
int headSize = (int)(coveredOffset - offset);
Assert.SdkAssert(GetRoundDownDifference(offset, dataAlignment) + headSize <= workBuffer.Length);
Result rc = storage.Read(headOffset, workBuffer.Slice(0, (int)dataAlignment));
if (rc.IsFailure()) return rc.Miss();
workBuffer.Slice((int)GetRoundDownDifference(offset, dataAlignment), headSize).CopyTo(destination);
}
long tailOffset = coveredOffset + coreSize;
long remainingTailSize = offset + destination.Length - tailOffset;
// Read any partial block at the tail of the requested range
while (remainingTailSize > 0)
{
long alignedTailOffset = Alignment.AlignDownPow2(tailOffset, dataAlignment);
long copySize = Math.Min(alignedTailOffset + dataAlignment - tailOffset, remainingTailSize);
Result rc = storage.Read(alignedTailOffset, workBuffer.Slice(0, (int)dataAlignment));
if (rc.IsFailure()) return rc.Miss();
Assert.SdkAssert(tailOffset - offset + copySize <= destination.Length);
Assert.SdkAssert(tailOffset - alignedTailOffset + copySize <= dataAlignment);
workBuffer.Slice((int)(tailOffset - alignedTailOffset), (int)copySize)
.CopyTo(destination.Slice((int)(tailOffset - offset)));
remainingTailSize -= copySize;
tailOffset += copySize;
}
return Result.Success;
}
public static Result Write(IStorage storage, Span<byte> workBuffer, uint dataAlignment, uint bufferAlignment,
long offset, ReadOnlySpan<byte> source)
{
// We don't support buffer alignment because Nintendo never uses any alignment other than 1, and because
// we'd have to mess with pinning the buffer.
Abort.DoAbortUnless(bufferAlignment == 1);
Assert.SdkRequiresGreaterEqual((uint)workBuffer.Length, dataAlignment);
if (source.Length == 0)
return Result.Success;
// Calculate the range that contains only full data blocks.
uint offsetRoundUpDifference = GetRoundUpDifference(offset, dataAlignment);
long coreOffset = Alignment.AlignUpPow2(offset, dataAlignment);
long coreSize = source.Length < offsetRoundUpDifference
? 0
: Alignment.AlignDownPow2(source.Length - offsetRoundUpDifference, dataAlignment);
long coveredOffset = coreSize > 0 ? coreOffset : offset;
// Write the core portion that doesn't contain any partial blocks.
if (coreSize > 0)
{
Result rc = storage.Write(coreOffset, source.Slice((int)offsetRoundUpDifference, (int)coreSize));
if (rc.IsFailure()) return rc.Miss();
}
// Write any partial block at the head of the specified range
if (offset < coveredOffset)
{
long headOffset = Alignment.AlignDownPow2(offset, dataAlignment);
int headSize = (int)(coveredOffset - offset);
Assert.SdkAssert((offset - headOffset) + headSize <= workBuffer.Length);
// Read the existing block, copy the partial block to the appropriate portion,
// and write the modified block back to the base storage.
Result rc = storage.Read(headOffset, workBuffer.Slice(0, (int)dataAlignment));
if (rc.IsFailure()) return rc.Miss();
source.Slice(0, headSize).CopyTo(workBuffer.Slice((int)(offset - headOffset)));
rc = storage.Write(headOffset, workBuffer.Slice(0, (int)dataAlignment));
if (rc.IsFailure()) return rc.Miss();
}
long tailOffset = coveredOffset + coreSize;
long remainingTailSize = offset + source.Length - tailOffset;
// Write any partial block at the tail of the specified range
while (remainingTailSize > 0)
{
Assert.SdkAssert(tailOffset - offset < source.Length);
long alignedTailOffset = Alignment.AlignDownPow2(tailOffset, dataAlignment);
long copySize = Math.Min(alignedTailOffset + dataAlignment - tailOffset, remainingTailSize);
// Read the existing block, copy the partial block to the appropriate portion,
// and write the modified block back to the base storage.
Result rc = storage.Read(alignedTailOffset, workBuffer.Slice(0, (int)dataAlignment));
if (rc.IsFailure()) return rc.Miss();
source.Slice((int)(tailOffset - offset), (int)copySize)
.CopyTo(workBuffer.Slice((int)GetRoundDownDifference(tailOffset, dataAlignment)));
rc = storage.Write(alignedTailOffset, workBuffer.Slice(0, (int)dataAlignment));
if (rc.IsFailure()) return rc.Miss();
remainingTailSize -= copySize;
tailOffset += copySize;
}
return Result.Success;
}
}