Fix bug in BucketTree, diff with v16, use new C# features

This commit is contained in:
Alex Barney 2023-09-27 17:28:44 -07:00
parent 04a5d03b5f
commit 809cecd1aa
2 changed files with 178 additions and 170 deletions

View file

@ -12,175 +12,10 @@ using Buffer = LibHac.Mem.Buffer;
namespace LibHac.FsSystem; namespace LibHac.FsSystem;
/// <summary> file struct StorageNode
/// Allows searching and iterating the entries in a bucket tree data structure.
/// </summary>
/// <remarks>Based on nnSdk 14.3.0 (FS 14.1.0)</remarks>
public partial class BucketTree : IDisposable
{ {
private const uint ExpectedMagic = 0x52544B42; // BKTR private static int NodeHeaderSize => Unsafe.SizeOf<BucketTree.NodeHeader>();
private const int MaxVersion = 1;
private const int NodeSizeMin = 1024;
private const int NodeSizeMax = 1024 * 512;
private static readonly int BufferAlignment = sizeof(long);
private static int NodeHeaderSize => Unsafe.SizeOf<NodeHeader>();
private ValueSubStorage _nodeStorage;
private ValueSubStorage _entryStorage;
private NodeBuffer _nodeL1;
private long _nodeSize;
private long _entrySize;
private int _entryCount;
private int _offsetCount;
private int _entrySetCount;
private OffsetCache _offsetCache;
public struct ContinuousReadingInfo
{
private long _readSize;
private int _skipCount;
private bool _isDone;
public readonly bool CanDo() => _readSize != 0;
public bool CheckNeedScan() => --_skipCount <= 0;
public readonly bool IsDone() => _isDone;
public void Done()
{
_readSize = 0;
_isDone = true;
}
public readonly long GetReadSize() => _readSize;
public void SetReadSize(long readSize) => _readSize = readSize;
public void Reset()
{
_readSize = 0;
_skipCount = 0;
_isDone = false;
}
public void SetSkipCount(int count)
{
Assert.SdkRequiresGreaterEqual(count, 0);
_skipCount = count;
}
}
public interface IContinuousReadingEntry
{
int FragmentSizeMax { get; }
long GetVirtualOffset();
long GetPhysicalOffset();
bool IsFragment();
}
private struct ContinuousReadingParam<TEntry> where TEntry : unmanaged, IContinuousReadingEntry
{
public long Offset;
public long Size;
public NodeHeader EntrySet;
public int EntryIndex;
public Offsets TreeOffsets;
public TEntry Entry;
}
public struct NodeHeader
{
public int Index;
public int EntryCount;
public long OffsetEnd;
public Result Verify(int nodeIndex, long nodeSize, long entrySize)
{
if (Index != nodeIndex)
return ResultFs.InvalidBucketTreeNodeIndex.Log();
if (entrySize == 0 || nodeSize < entrySize + NodeHeaderSize)
return ResultFs.InvalidSize.Log();
long maxEntryCount = (nodeSize - NodeHeaderSize) / entrySize;
if (EntryCount <= 0 || maxEntryCount < EntryCount)
return ResultFs.InvalidBucketTreeNodeEntryCount.Log();
if (OffsetEnd < 0)
return ResultFs.InvalidBucketTreeNodeOffset.Log();
return Result.Success;
}
}
[NonCopyable]
private struct NodeBuffer : IDisposable
{
private MemoryResource _allocator;
private Buffer _header;
public void Dispose()
{
Assert.SdkAssert(_header.IsNull);
}
public readonly MemoryResource GetAllocator() => _allocator;
public bool Allocate(MemoryResource allocator, int nodeSize)
{
Assert.SdkRequires(_header.IsNull);
_allocator = allocator;
_header = allocator.Allocate(nodeSize, BufferAlignment);
return !_header.IsNull;
}
public void Free()
{
if (!_header.IsNull)
{
_allocator.Deallocate(ref _header, BufferAlignment);
_header = Buffer.Empty;
}
_allocator = null;
}
public void FillZero()
{
if (!_header.IsNull)
{
_header.Span.Clear();
}
}
public readonly ref NodeHeader GetHeader()
{
Assert.SdkRequiresGreaterEqual(_header.Length * sizeof(long), Unsafe.SizeOf<NodeHeader>());
return ref Unsafe.As<byte, NodeHeader>(ref _header.Span[0]);
}
public readonly Span<byte> GetBuffer()
{
return _header.Span;
}
public readonly BucketTreeNode<TEntry> GetNode<TEntry>() where TEntry : unmanaged
{
return new BucketTreeNode<TEntry>(GetBuffer());
}
}
private struct StorageNode
{
private Offset _start; private Offset _start;
private int _count; private int _count;
private int _index; private int _index;
@ -287,6 +122,173 @@ public partial class BucketTree : IDisposable
} }
} }
/// <summary>
/// Allows searching and iterating the entries in a bucket tree data structure.
/// </summary>
/// <remarks>Based on nnSdk 16.2.0 (FS 16.0.0)</remarks>
public partial class BucketTree : IDisposable
{
private const uint Signature = 0x52544B42; // BKTR
private const int MaxVersion = 1;
private const int NodeSizeMin = 1024;
private const int NodeSizeMax = 1024 * 512;
private static readonly int BufferAlignment = sizeof(long);
private static int NodeHeaderSize => Unsafe.SizeOf<NodeHeader>();
private ValueSubStorage _nodeStorage;
private ValueSubStorage _entryStorage;
private NodeBuffer _nodeL1;
private long _nodeSize;
private long _entrySize;
private int _entryCount;
private int _offsetCount;
private int _entrySetCount;
private OffsetCache _offsetCache;
public struct ContinuousReadingInfo
{
private long _readSize;
private int _skipCount;
private bool _isDone;
public readonly bool CanDo() => _readSize != 0;
public bool CheckNeedScan() => --_skipCount <= 0;
public readonly bool IsDone() => _isDone;
public void Done()
{
_readSize = 0;
_isDone = true;
}
public readonly long GetReadSize() => _readSize;
public void SetReadSize(long readSize) => _readSize = readSize;
public void Reset()
{
_readSize = 0;
_skipCount = 0;
_isDone = false;
}
public void SetSkipCount(int count)
{
Assert.SdkRequiresGreaterEqual(count, 0);
_skipCount = count;
}
}
public interface IContinuousReadingEntry
{
public static abstract int FragmentSizeMax { get; }
long GetVirtualOffset();
long GetPhysicalOffset();
bool IsFragment();
}
private struct ContinuousReadingParam<TEntry> where TEntry : unmanaged, IContinuousReadingEntry
{
public long Offset;
public long Size;
public NodeHeader EntrySet;
public int EntryIndex;
public Offsets TreeOffsets;
public TEntry Entry;
}
public struct NodeHeader
{
public int Index;
public int EntryCount;
public long OffsetEnd;
public Result Verify(int nodeIndex, long nodeSize, long entrySize)
{
if (Index != nodeIndex)
return ResultFs.InvalidBucketTreeNodeIndex.Log();
if (entrySize == 0 || nodeSize < entrySize + NodeHeaderSize)
return ResultFs.InvalidSize.Log();
long maxEntryCount = (nodeSize - NodeHeaderSize) / entrySize;
if (EntryCount <= 0 || maxEntryCount < EntryCount)
return ResultFs.InvalidBucketTreeNodeEntryCount.Log();
if (OffsetEnd < 0)
return ResultFs.InvalidBucketTreeNodeOffset.Log();
return Result.Success;
}
}
[NonCopyable]
private struct NodeBuffer : IDisposable
{
private MemoryResource _allocator;
private Buffer _header;
public void Dispose()
{
Assert.SdkAssert(_header.IsNull);
}
public readonly MemoryResource GetAllocator() => _allocator;
public bool Allocate(MemoryResource allocator, int nodeSize)
{
Assert.SdkRequires(_header.IsNull);
_allocator = allocator;
_header = allocator.Allocate(nodeSize, BufferAlignment);
return !_header.IsNull;
}
public void Free()
{
if (!_header.IsNull)
{
_allocator.Deallocate(ref _header, BufferAlignment);
_header = Buffer.Empty;
}
_allocator = null;
}
public void FillZero()
{
if (!_header.IsNull)
{
_header.Span.Clear();
}
}
public readonly ref NodeHeader GetHeader()
{
Assert.SdkRequiresGreaterEqual(_header.Length * sizeof(long), Unsafe.SizeOf<NodeHeader>());
return ref Unsafe.As<byte, NodeHeader>(ref _header.Span[0]);
}
public readonly Span<byte> GetBuffer()
{
return _header.Span;
}
public readonly BucketTreeNode<TEntry> GetNode<TEntry>() where TEntry : unmanaged
{
return new BucketTreeNode<TEntry>(GetBuffer());
}
}
private struct OffsetCache private struct OffsetCache
{ {
public OffsetCache() public OffsetCache()
@ -320,26 +322,24 @@ public partial class BucketTree : IDisposable
public struct Header public struct Header
{ {
public uint Magic; public uint HeaderSignature;
public uint Version; public uint Version;
public int EntryCount; public int EntryCount;
#pragma warning disable 414 public int Reserved;
private int _reserved;
#pragma warning restore 414
public void Format(int entryCount) public void Format(int entryCount)
{ {
Assert.SdkRequiresLessEqual(0, entryCount); Assert.SdkRequiresLessEqual(0, entryCount);
Magic = ExpectedMagic; HeaderSignature = Signature;
Version = MaxVersion; Version = MaxVersion;
EntryCount = entryCount; EntryCount = entryCount;
_reserved = 0; Reserved = 0;
} }
public readonly Result Verify() public readonly Result Verify()
{ {
if (Magic != ExpectedMagic) if (HeaderSignature != Signature)
return ResultFs.InvalidBucketTreeSignature.Log(); return ResultFs.InvalidBucketTreeSignature.Log();
if (EntryCount < 0) if (EntryCount < 0)
@ -503,21 +503,19 @@ public partial class BucketTree : IDisposable
if (entryCount <= 0) if (entryCount <= 0)
return ResultFs.InvalidArgument.Log(); return ResultFs.InvalidArgument.Log();
Result result = ResultFs.BufferAllocationFailed.Value;
// Allocate node. // Allocate node.
if (!_nodeL1.Allocate(allocator, nodeSize)) if (_nodeL1.Allocate(allocator, nodeSize))
return ResultFs.BufferAllocationFailed.Log();
bool needFree = true;
try
{ {
// Read node. result = nodeStorage.Read(0, _nodeL1.GetBuffer());
Result res = nodeStorage.Read(0, _nodeL1.GetBuffer());
if (res.IsFailure()) return res.Miss();
// Verify node. if (result.IsSuccess())
res = _nodeL1.GetHeader().Verify(0, nodeSize, sizeof(long)); {
if (res.IsFailure()) return res.Miss(); result = _nodeL1.GetHeader().Verify(0, nodeSize, sizeof(long));
if (result.IsSuccess())
{
// Validate offsets. // Validate offsets.
int offsetCount = GetOffsetCount(nodeSize); int offsetCount = GetOffsetCount(nodeSize);
int entrySetCount = GetEntrySetCount(nodeSize, entrySize, entryCount); int entrySetCount = GetEntrySetCount(nodeSize, entrySize, entryCount);
@ -533,11 +531,12 @@ public partial class BucketTree : IDisposable
startOffset = node.GetBeginOffset(); startOffset = node.GetBeginOffset();
} }
if (startOffset == 0 && startOffset <= node.GetBeginOffset())
{
long endOffset = node.GetEndOffset(); long endOffset = node.GetEndOffset();
if (startOffset < 0 || startOffset > node.GetBeginOffset() || startOffset >= endOffset) if (startOffset < endOffset)
return ResultFs.InvalidBucketTreeEntryOffset.Log(); {
_nodeStorage.Set(in nodeStorage); _nodeStorage.Set(in nodeStorage);
_entryStorage.Set(in entryStorage); _entryStorage.Set(in entryStorage);
_nodeSize = nodeSize; _nodeSize = nodeSize;
@ -549,15 +548,21 @@ public partial class BucketTree : IDisposable
_offsetCache.Offsets.StartOffset = startOffset; _offsetCache.Offsets.StartOffset = startOffset;
_offsetCache.Offsets.EndOffset = endOffset; _offsetCache.Offsets.EndOffset = endOffset;
needFree = false;
return Result.Success; return Result.Success;
} }
finally }
{
if (needFree) result = ResultFs.InvalidBucketTreeEntryOffset.Value;
}
}
_nodeL1.Free(); _nodeL1.Free();
} }
if (result.IsFailure())
return result.Log();
return Result.Success;
} }
public void Initialize(long nodeSize, long endOffset) public void Initialize(long nodeSize, long endOffset)
@ -795,7 +800,7 @@ public partial class BucketTree : IDisposable
if (entry.IsFragment()) if (entry.IsFragment())
{ {
// If we can't merge, stop looping. // If we can't merge, stop looping.
if (readSize >= entry.FragmentSizeMax || remainingSize <= dataSize) if (readSize >= TEntry.FragmentSizeMax || remainingSize <= dataSize)
break; break;
// Otherwise, add the current size to the merge size. // Otherwise, add the current size to the merge size.
@ -911,7 +916,8 @@ public partial class BucketTree : IDisposable
public readonly bool IsValid() => _entryIndex >= 0; public readonly bool IsValid() => _entryIndex >= 0;
public readonly Offsets GetTreeOffsets() => _treeOffsets; [UnscopedRef]
public readonly ref readonly Offsets GetTreeOffsets() => ref _treeOffsets;
public readonly bool CanMoveNext() public readonly bool CanMoveNext()
{ {
@ -1205,7 +1211,7 @@ public partial class BucketTree : IDisposable
} }
else else
{ {
_entryIndex = 1; _entryIndex = -1;
} }
// Read the new entry // Read the new entry
@ -1275,6 +1281,8 @@ public partial class BucketTree : IDisposable
public readonly Result ScanContinuousReading<TEntry>(out ContinuousReadingInfo info, long offset, long size) public readonly Result ScanContinuousReading<TEntry>(out ContinuousReadingInfo info, long offset, long size)
where TEntry : unmanaged, IContinuousReadingEntry where TEntry : unmanaged, IContinuousReadingEntry
{ {
Assert.SdkRequires(IsValid());
var param = new ContinuousReadingParam<TEntry> var param = new ContinuousReadingParam<TEntry>
{ {
Offset = offset, Offset = offset,

View file

@ -52,7 +52,7 @@ public class IndirectStorage : IStorage
private struct ContinuousReadingEntry : BucketTree.IContinuousReadingEntry private struct ContinuousReadingEntry : BucketTree.IContinuousReadingEntry
{ {
public int FragmentSizeMax => 1024 * 4; public static int FragmentSizeMax => 1024 * 4;
#pragma warning disable CS0649 #pragma warning disable CS0649
// This field will be read in by BucketTree.Visitor.ScanContinuousReading // This field will be read in by BucketTree.Visitor.ScanContinuousReading