Implement JournalIntegritySaveDataFileSystemDriver

This commit is contained in:
Alex Barney 2024-03-10 14:37:39 -07:00
parent 45975ddadd
commit 8027310320
7 changed files with 274 additions and 46 deletions

View file

@ -1,6 +1,7 @@
using System; using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading; using System.Threading;
using JetBrains.Annotations;
using LibHac.Diag; using LibHac.Diag;
using LibHac.Fs; using LibHac.Fs;
using Buffer = LibHac.Mem.Buffer; using Buffer = LibHac.Mem.Buffer;
@ -19,8 +20,7 @@ public struct ScopedBufferManagerContextRegistration : IDisposable
{ {
private BufferManagerContext _oldContext; private BufferManagerContext _oldContext;
// ReSharper disable once UnusedParameter.Local public ScopedBufferManagerContextRegistration()
public ScopedBufferManagerContextRegistration(int unused = default)
{ {
_oldContext = BufferManagerUtility.GetBufferManagerContext(); _oldContext = BufferManagerUtility.GetBufferManagerContext();
} }
@ -41,15 +41,15 @@ internal static class BufferManagerUtility
public delegate bool IsValidBufferFunction(in Buffer buffer); public delegate bool IsValidBufferFunction(in Buffer buffer);
public static Result DoContinuouslyUntilBufferIsAllocated(Func<Result> function, Func<Result> onFailure, public static Result DoContinuouslyUntilBufferIsAllocated([InstantHandle] Func<Result> function,
[CallerMemberName] string callerName = "") [InstantHandle] Func<Result> onFailure, [CallerMemberName] string callerName = "")
{ {
const int bufferAllocationRetryLogCountMax = 10; const int bufferAllocationRetryLogCountMax = 10;
const int bufferAllocationRetryLogInterval = 100; const int bufferAllocationRetryLogInterval = 100;
Result result; Result result;
for (int count = 1; ; count++) for (int count = 1;; count++)
{ {
result = function(); result = function();
if (!ResultFs.BufferAllocationFailed.Includes(result)) if (!ResultFs.BufferAllocationFailed.Includes(result))
@ -71,7 +71,7 @@ internal static class BufferManagerUtility
return result; return result;
} }
public static Result DoContinuouslyUntilBufferIsAllocated(Func<Result> function, public static Result DoContinuouslyUntilBufferIsAllocated([InstantHandle] Func<Result> function,
[CallerMemberName] string callerName = "") [CallerMemberName] string callerName = "")
{ {
return DoContinuouslyUntilBufferIsAllocated(function, static () => Result.Success, callerName); return DoContinuouslyUntilBufferIsAllocated(function, static () => Result.Success, callerName);

View file

@ -125,9 +125,15 @@ file static class Anonymous
public class JournalIntegritySaveDataFileSystem : IFileSystem public class JournalIntegritySaveDataFileSystem : IFileSystem
{ {
public struct ExtraData { } public struct ExtraData
{
public int Dummy;
}
public struct FileSystemLayoutHeader { } public struct FileSystemLayoutHeader
{
public int Dummy;
}
public struct MasterHeader public struct MasterHeader
{ {
@ -136,7 +142,9 @@ public class JournalIntegritySaveDataFileSystem : IFileSystem
public long Counter; public long Counter;
} }
public struct CommitData2 { } public struct CommitData2
{
}
} }
public class ControlAreaHolder : IDisposable public class ControlAreaHolder : IDisposable
@ -435,10 +443,12 @@ public class JournalIntegritySaveDataFileSystem : IFileSystem
throw new NotImplementedException(); throw new NotImplementedException();
} }
public static Result QuerySize(out long outSizeTotal, long sizeBlock, int countAvailableBlock, public static Result QuerySize(out long outSizeTotal, long sizeBlock, uint countAvailableBlock,
int countJournalBlock, int countExpandMax, uint minimumVersion) uint countJournalBlock, int countExpandMax, uint minimumVersion)
{ {
throw new NotImplementedException(); // Todo: Implement
outSizeTotal = 0;
return Result.Success;
} }
private static Result Sign(Span<byte> outBuffer, IMacGenerator macGenerator, private static Result Sign(Span<byte> outBuffer, IMacGenerator macGenerator,

View file

@ -1,12 +1,17 @@
// ReSharper disable UnusedMember.Local UnusedType.Local using LibHac.Common;
#pragma warning disable CS0169 // Field is never used using LibHac.Diag;
using System;
using LibHac.Fs; using LibHac.Fs;
using LibHac.FsSystem.Buffers;
using LibHac.Os; using LibHac.Os;
using LibHac.Util; using LibHac.Util;
namespace LibHac.FsSystem.Save; namespace LibHac.FsSystem.Save;
/// <summary>
/// Provides access to a <see cref="JournalIntegritySaveDataFileSystem"/>. Abstracts some operations such as expansion,
/// and retries operations if buffer allocation fails.
/// </summary>
/// <remarks>Based on nnSdk 17.5.0 (FS 17.0.0)</remarks>
public class JournalIntegritySaveDataFileSystemDriver : ProxyFileSystemWithRetryingBufferAllocation, IInternalStorageFileSystem public class JournalIntegritySaveDataFileSystemDriver : ProxyFileSystemWithRetryingBufferAllocation, IInternalStorageFileSystem
{ {
private ValueSubStorage _baseStorage; private ValueSubStorage _baseStorage;
@ -17,26 +22,70 @@ public class JournalIntegritySaveDataFileSystemDriver : ProxyFileSystemWithRetry
public JournalIntegritySaveDataFileSystemDriver() public JournalIntegritySaveDataFileSystemDriver()
{ {
throw new NotImplementedException(); _baseStorage = new ValueSubStorage();
_mutex = new SdkRecursiveMutex();
_fileSystem = new JournalIntegritySaveDataFileSystem();
} }
public override void Dispose() public override void Dispose()
{ {
throw new NotImplementedException(); FinalizeObject();
_fileSystem.Dispose();
_baseStorage.Dispose();
base.Dispose();
} }
public static Result QueryDataBlockCount(out uint outCountDataBlock, long blockSize, long availableSize, public static Result QueryDataBlockCount(out uint outCountDataBlock, long blockSize, long totalSize,
long journalSize, int countExpandMax, uint version) long journalSize, int countExpandMax, uint version)
{ {
throw new NotImplementedException(); outCountDataBlock = 0;
uint lowerBound = 0;
uint upperBound = (uint)(totalSize / blockSize);
uint journalBlockCount = (uint)(journalSize / blockSize);
int iterationsRemaining = 32;
// Do a binary search to find the maximum number of data blocks you can have without going over the total
// save image size limit.
uint midpoint;
do
{
if (upperBound == lowerBound)
break;
if (iterationsRemaining-- == 0)
break;
midpoint = (upperBound + lowerBound) / 2;
Result res = QueryTotalSize(out long sizeQuery, blockSize, midpoint, journalBlockCount, countExpandMax, version);
if (res.IsFailure()) return res.Miss();
if (sizeQuery > totalSize)
upperBound = midpoint;
else
lowerBound = midpoint;
} while (upperBound - lowerBound > 1 || midpoint != lowerBound);
{
Result res = QueryTotalSize(out long sizeQuery, blockSize, lowerBound, journalBlockCount, countExpandMax, version);
if (res.IsFailure()) return res.Miss();
if (sizeQuery > totalSize)
{
return ResultFs.UsableSpaceNotEnough.Log();
}
outCountDataBlock = lowerBound;
}
return Result.Success;
} }
public static Result QueryTotalSize(out long outSizeTotal, long blockSize, uint countAvailableBlock, public static Result QueryTotalSize(out long outSizeTotal, long blockSize, uint countAvailableBlock,
uint countJournalBlock, int countExpandMax, uint version) uint countJournalBlock, int countExpandMax, uint version)
{ {
// Todo: Implement return JournalIntegritySaveDataFileSystem.QuerySize(out outSizeTotal, blockSize, countAvailableBlock,
outSizeTotal = 0; countJournalBlock, countExpandMax, version).Ret();
return Result.Success;
} }
public static Result Format( public static Result Format(
@ -52,12 +101,33 @@ public class JournalIntegritySaveDataFileSystemDriver : ProxyFileSystemWithRetry
RandomDataGenerator encryptionKeyGenerator, RandomDataGenerator encryptionKeyGenerator,
uint version) uint version)
{ {
throw new NotImplementedException(); // Create a local copy of the SubStorage so it can be captured for use in a local function or lambda
using var tempSaveFileStorage = new ValueSubStorage(in saveFileStorage);
var locker = new SdkRecursiveMutex();
var bufferManagerSet = new FileSystemBufferManagerSet();
for (int i = 0; i < bufferManagerSet.Buffers.Length; i++)
{
bufferManagerSet.Buffers[i] = bufferManager;
}
return BufferManagerUtility.DoContinuouslyUntilBufferIsAllocated(FormatImpl).Ret();
Result FormatImpl()
{
using var scopedRegistration = new ScopedBufferManagerContextRegistration();
return JournalIntegritySaveDataFileSystem.Format(in tempSaveFileStorage, blockSize, blockCount,
journalBlockCount, countExpandMax, bufferManagerSet, bufferManager, locker, macGenerator,
hashGeneratorFactorySelector, hashSalt, encryptionKeyGenerator, version).Ret();
}
} }
public static long QueryExpandLogSize(long blockSize, uint availableBlockCount, uint journalBlockCount) public static long QueryExpandLogSize(long blockSize, uint availableBlockCount, uint journalBlockCount)
{ {
throw new NotImplementedException(); long dataSize = ((long)journalBlockCount + availableBlockCount) * blockSize;
return BitUtil.DivideUp(dataSize, 0x2000000) * 0x10000 + 0x100000;
} }
public static Result OperateExpand( public static Result OperateExpand(
@ -71,7 +141,49 @@ public class JournalIntegritySaveDataFileSystemDriver : ProxyFileSystemWithRetry
IHash256GeneratorFactorySelector hashGeneratorFactorySelector, IHash256GeneratorFactorySelector hashGeneratorFactorySelector,
uint minimumVersion) uint minimumVersion)
{ {
throw new NotImplementedException(); using var tempBaseStorage = new ValueSubStorage(in baseStorage);
using var tempLogStorage = new ValueSubStorage(in logStorage);
var locker = new SdkRecursiveMutex();
return BufferManagerUtility.DoContinuouslyUntilBufferIsAllocated(OperateImpl).Ret();
Result OperateImpl()
{
using var scopedRegistration = new ScopedBufferManagerContextRegistration();
using var unionStorage = new UnionStorage();
Result res = tempLogStorage.GetSize(out long logStorageSize);
if (res.IsFailure()) return res.Miss();
using var storageBufferingLog = new BufferedStorage();
res = storageBufferingLog.Initialize(in tempLogStorage, bufferManager, (int)blockSize, bufferCount: 2);
if (res.IsFailure()) return res.Miss();
using var bufferedLogStorage = new ValueSubStorage(storageBufferingLog, 0, logStorageSize);
res = UnionStorage.Format(in bufferedLogStorage, blockSize);
if (res.IsFailure()) return res.Miss();
res = unionStorage.Initialize(in tempBaseStorage, in bufferedLogStorage, blockSize);
if (res.IsFailure()) return res.Miss();
res = unionStorage.GetSize(out long sizeUnionStorage);
if (res.IsFailure()) return res.Miss();
using var tempUnionStorage = new ValueSubStorage(unionStorage, 0, sizeUnionStorage);
res = JournalIntegritySaveDataFileSystem.Expand(in tempUnionStorage, availableBlockCount, journalBlockCount,
bufferManager, locker, macGenerator, hashGeneratorFactorySelector, minimumVersion);
if (res.IsFailure()) return res.Miss();
res = unionStorage.Freeze();
if (res.IsFailure()) return res.Miss();
res = unionStorage.Flush();
if (res.IsFailure()) return res.Miss();
return Result.Success;
}
} }
public static Result CommitExpand( public static Result CommitExpand(
@ -80,61 +192,150 @@ public class JournalIntegritySaveDataFileSystemDriver : ProxyFileSystemWithRetry
long blockSize, long blockSize,
IBufferManager bufferManager) IBufferManager bufferManager)
{ {
throw new NotImplementedException(); using var tempBaseStorage = new ValueSubStorage(in baseStorage);
using var tempLogStorage = new ValueSubStorage(in logStorage);
return BufferManagerUtility.DoContinuouslyUntilBufferIsAllocated(CommitImpl).Ret();
Result CommitImpl()
{
using var scopedRegistration = new ScopedBufferManagerContextRegistration();
using var unionStorage = new UnionStorage();
Result res = tempBaseStorage.GetSize(out long baseStorageSize);
if (res.IsFailure()) return res.Miss();
using var storageBufferingBase = new BufferedStorage();
res = storageBufferingBase.Initialize(in tempBaseStorage, bufferManager, (int)blockSize, bufferCount: 16);
if (res.IsFailure()) return res.Miss();
res = tempLogStorage.GetSize(out long logStorageSize);
if (res.IsFailure()) return res.Miss();
using var storageBufferingLog = new BufferedStorage();
res = storageBufferingBase.Initialize(in tempLogStorage, bufferManager, (int)blockSize, bufferCount: 2);
if (res.IsFailure()) return res.Miss();
using var bufferedBaseStorage = new ValueSubStorage(storageBufferingBase, 0, baseStorageSize);
using var bufferedLogStorage = new ValueSubStorage(storageBufferingLog, 0, logStorageSize);
res = unionStorage.Initialize(in bufferedBaseStorage, in bufferedLogStorage, blockSize);
if (res.IsFailure()) return res.Miss();
res = unionStorage.Commit();
if (res.IsFailure()) return res.Miss();
res = unionStorage.Flush();
if (res.IsFailure()) return res.Miss();
return Result.Success;
}
} }
public static Result ReadExtraData(out JournalIntegritySaveDataFileSystem.ExtraData outData, public static Result ReadExtraData(out JournalIntegritySaveDataFileSystem.ExtraData outData,
in ValueSubStorage saveImageStorage, IBufferManager bufferManager, IMacGenerator macGenerator, in ValueSubStorage saveImageStorage, IBufferManager bufferManager, IMacGenerator macGenerator,
IHash256GeneratorFactorySelector hashGeneratorFactorySelector, uint minimumVersion) IHash256GeneratorFactorySelector hashGeneratorFactorySelector, uint minimumVersion)
{ {
throw new NotImplementedException(); UnsafeHelpers.SkipParamInit(out outData);
using var tempSaveImageStorage = new ValueSubStorage(in saveImageStorage);
JournalIntegritySaveDataFileSystem.ExtraData extraData = default;
Result res = BufferManagerUtility.DoContinuouslyUntilBufferIsAllocated(ReadExtraDataImpl).Ret();
if (res.IsFailure()) return res.Miss();
outData = extraData;
return Result.Success;
Result ReadExtraDataImpl()
{
using var scopedRegistration = new ScopedBufferManagerContextRegistration();
return JournalIntegritySaveDataFileSystem.ReadExtraData(out extraData, in tempSaveImageStorage,
bufferManager, macGenerator, hashGeneratorFactorySelector, minimumVersion).Ret();
}
} }
public Result Initialize(in ValueSubStorage baseStorage, IBufferManager bufferManager, IMacGenerator macGenerator, public Result Initialize(in ValueSubStorage baseStorage, IBufferManager bufferManager, IMacGenerator macGenerator,
IHash256GeneratorFactorySelector hashGeneratorFactorySelector, uint minimumVersion) IHash256GeneratorFactorySelector hashGeneratorFactorySelector, uint minimumVersion)
{ {
throw new NotImplementedException(); Assert.SdkRequiresNotNull(bufferManager);
_baseStorage.Set(in baseStorage);
_bufferManager = bufferManager;
_bufferManagerSet = new FileSystemBufferManagerSet();
for (int i = 0; i < _bufferManagerSet.Buffers.Length; i++)
{
_bufferManagerSet.Buffers[i] = bufferManager;
}
using var scopedRegistration = new ScopedBufferManagerContextRegistration();
BufferManagerUtility.EnableBlockingBufferManagerAllocation();
Result res = _fileSystem.Initialize(in _baseStorage, _bufferManagerSet, _bufferManager, _mutex, macGenerator,
hashGeneratorFactorySelector, minimumVersion);
if (res.IsFailure()) return res.Miss();
base.Initialize(_fileSystem);
return Result.Success;
} }
public void FinalizeObject() public new void FinalizeObject()
{ {
throw new NotImplementedException(); using var scopedRegistration = new ScopedBufferManagerContextRegistration();
BufferManagerUtility.EnableBlockingBufferManagerAllocation();
_fileSystem.FinalizeObject();
base.FinalizeObject();
} }
public Result WriteExtraData(in JournalIntegritySaveDataFileSystem.ExtraData extraData) public Result WriteExtraData(in JournalIntegritySaveDataFileSystem.ExtraData extraData)
{ {
throw new NotImplementedException(); return _fileSystem.WriteExtraData(in extraData).Ret();
} }
public Result ReadExtraData(out JournalIntegritySaveDataFileSystem.ExtraData outExtraData) public void ReadExtraData(out JournalIntegritySaveDataFileSystem.ExtraData outExtraData)
{ {
throw new NotImplementedException(); _fileSystem.ReadExtraData(out outExtraData);
} }
public Result RollbackOnlyModified() public Result RollbackOnlyModified()
{ {
throw new NotImplementedException(); return BufferManagerUtility.DoContinuouslyUntilBufferIsAllocated(RollbackOnlyModifiedImpl).Ret();
Result RollbackOnlyModifiedImpl()
{
using var scopedRegistration = new ScopedBufferManagerContextRegistration();
return _fileSystem.RollbackOnlyModified().Ret();
}
} }
public static Result UpdateMac(in ValueSubStorage saveDataStorage, IMacGenerator macGenerator, public static Result UpdateMac(in ValueSubStorage saveDataStorage, IMacGenerator macGenerator,
IHash256GeneratorFactorySelector hashGeneratorFactorySelector, uint minimumVersion) IHash256GeneratorFactorySelector hashGeneratorFactorySelector, uint minimumVersion)
{ {
throw new NotImplementedException(); return JournalIntegritySaveDataFileSystem
.UpdateMac(in saveDataStorage, macGenerator, hashGeneratorFactorySelector, minimumVersion).Ret();
} }
public long GetCounterForBundledCommit() public long GetCounterForBundledCommit()
{ {
throw new NotImplementedException(); return _fileSystem.GetCounterForBundledCommit();
} }
public Result CommitFileSystem() public Result CommitFileSystem()
{ {
throw new NotImplementedException(); return Commit().Ret();
} }
public void ExtractParameters(out JournalIntegritySaveDataParameters outParams) public void ExtractParameters(out JournalIntegritySaveDataParameters outParams)
{ {
throw new NotImplementedException(); outParams = default;
_fileSystem.ExtractParameters(out outParams.BlockSize, out outParams.CountDataBlock,
out outParams.CountJournalBlock, out outParams.CountExpandMax, out outParams.Version,
out outParams.IsMetaSetVerificationEnabled);
} }
public static JournalIntegritySaveDataParameters SetUpSaveDataParameters(long blockSize, long dataSize, long journalSize) public static JournalIntegritySaveDataParameters SetUpSaveDataParameters(long blockSize, long dataSize, long journalSize)
@ -151,11 +352,23 @@ public class JournalIntegritySaveDataFileSystemDriver : ProxyFileSystemWithRetry
public Result AcceptVisitor(IInternalStorageFileSystemVisitor visitor) public Result AcceptVisitor(IInternalStorageFileSystemVisitor visitor)
{ {
throw new NotImplementedException(); Assert.SdkRequiresNotNull(visitor);
return BufferManagerUtility.DoContinuouslyUntilBufferIsAllocated(AcceptVisitorImpl).Ret();
Result AcceptVisitorImpl()
{
using var scopedRegistration = new ScopedBufferManagerContextRegistration();
BufferManagerUtility.EnableBlockingBufferManagerAllocation();
return _fileSystem.AcceptVisitor(visitor).Ret();
}
} }
public Result UpdateMac(IMacGenerator macGenerator) public Result UpdateMac(IMacGenerator macGenerator)
{ {
throw new NotImplementedException(); Assert.SdkRequiresNotNull(macGenerator);
return _fileSystem.UpdateMacAndCommit(macGenerator).Ret();
} }
} }

View file

@ -107,7 +107,7 @@ public class ProxyFileSystemWithRetryingBufferAllocation : IFileSystem
throw new NotImplementedException(); throw new NotImplementedException();
} }
public void Finalize(IFileSystem fileSystem) public void FinalizeObject()
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }

View file

@ -24,7 +24,7 @@ public interface IInternalStorageFileSystem
{ {
Result AcceptVisitor(IInternalStorageFileSystemVisitor visitor); Result AcceptVisitor(IInternalStorageFileSystemVisitor visitor);
Result WriteExtraData(in JournalIntegritySaveDataFileSystem.ExtraData extraData); Result WriteExtraData(in JournalIntegritySaveDataFileSystem.ExtraData extraData);
Result ReadExtraData(out JournalIntegritySaveDataFileSystem.ExtraData outExtraData); void ReadExtraData(out JournalIntegritySaveDataFileSystem.ExtraData outExtraData);
Result CommitFileSystem(); Result CommitFileSystem();
Result UpdateMac(IMacGenerator macGenerator); Result UpdateMac(IMacGenerator macGenerator);
} }

View file

@ -77,7 +77,7 @@ public class UnionStorage : IStorage
header[0] = blockSize; header[0] = blockSize;
header[1] = Sentinel; header[1] = Sentinel;
return storage.Write(0, SpanHelpers.AsReadOnlyByteSpan(in header)); return storage.Write(0, SpanHelpers.AsReadOnlyByteSpan(in header)).Ret();
} }
public Result Initialize(ref readonly ValueSubStorage baseStorage, ref readonly ValueSubStorage logStorage, public Result Initialize(ref readonly ValueSubStorage baseStorage, ref readonly ValueSubStorage logStorage,
@ -157,7 +157,7 @@ public class UnionStorage : IStorage
if (res.IsFailure()) return res.Miss(); if (res.IsFailure()) return res.Miss();
} }
return _baseStorage.Flush(); return _baseStorage.Flush().Ret();
} }
public override Result Read(long offset, Span<byte> destination) public override Result Read(long offset, Span<byte> destination)
@ -292,7 +292,7 @@ public class UnionStorage : IStorage
{ {
Assert.SdkRequiresNotNull(_buffer); Assert.SdkRequiresNotNull(_buffer);
return _baseStorage.GetSize(out size); return _baseStorage.GetSize(out size).Ret();
} }
public override Result SetSize(long size) public override Result SetSize(long size)
@ -317,7 +317,7 @@ public class UnionStorage : IStorage
} }
} }
return _baseStorage.OperateRange(outBuffer, operationId, offset, size, inBuffer); return _baseStorage.OperateRange(outBuffer, operationId, offset, size, inBuffer).Ret();
} }
private Result FindLog(out bool logFound, out long outLogOffset, long offsetOriginal) private Result FindLog(out bool logFound, out long outLogOffset, long offsetOriginal)

View file

@ -43,4 +43,9 @@
</AssemblyAttribute> </AssemblyAttribute>
</ItemGroup> </ItemGroup>
<!-- Packages that are only used when building -->
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2023.3.0" ExcludeAssets="runtime" PrivateAssets="all" />
</ItemGroup>
</Project> </Project>