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

View file

@ -125,9 +125,15 @@ file static class Anonymous
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
{
@ -136,7 +142,9 @@ public class JournalIntegritySaveDataFileSystem : IFileSystem
public long Counter;
}
public struct CommitData2 { }
public struct CommitData2
{
}
}
public class ControlAreaHolder : IDisposable
@ -435,10 +443,12 @@ public class JournalIntegritySaveDataFileSystem : IFileSystem
throw new NotImplementedException();
}
public static Result QuerySize(out long outSizeTotal, long sizeBlock, int countAvailableBlock,
int countJournalBlock, int countExpandMax, uint minimumVersion)
public static Result QuerySize(out long outSizeTotal, long sizeBlock, uint countAvailableBlock,
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,

View file

@ -1,12 +1,17 @@
// ReSharper disable UnusedMember.Local UnusedType.Local
#pragma warning disable CS0169 // Field is never used
using System;
using LibHac.Common;
using LibHac.Diag;
using LibHac.Fs;
using LibHac.FsSystem.Buffers;
using LibHac.Os;
using LibHac.Util;
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
{
private ValueSubStorage _baseStorage;
@ -17,26 +22,70 @@ public class JournalIntegritySaveDataFileSystemDriver : ProxyFileSystemWithRetry
public JournalIntegritySaveDataFileSystemDriver()
{
throw new NotImplementedException();
_baseStorage = new ValueSubStorage();
_mutex = new SdkRecursiveMutex();
_fileSystem = new JournalIntegritySaveDataFileSystem();
}
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)
{
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,
uint countJournalBlock, int countExpandMax, uint version)
{
// Todo: Implement
outSizeTotal = 0;
return Result.Success;
return JournalIntegritySaveDataFileSystem.QuerySize(out outSizeTotal, blockSize, countAvailableBlock,
countJournalBlock, countExpandMax, version).Ret();
}
public static Result Format(
@ -52,12 +101,33 @@ public class JournalIntegritySaveDataFileSystemDriver : ProxyFileSystemWithRetry
RandomDataGenerator encryptionKeyGenerator,
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)
{
throw new NotImplementedException();
long dataSize = ((long)journalBlockCount + availableBlockCount) * blockSize;
return BitUtil.DivideUp(dataSize, 0x2000000) * 0x10000 + 0x100000;
}
public static Result OperateExpand(
@ -71,7 +141,49 @@ public class JournalIntegritySaveDataFileSystemDriver : ProxyFileSystemWithRetry
IHash256GeneratorFactorySelector hashGeneratorFactorySelector,
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(
@ -80,61 +192,150 @@ public class JournalIntegritySaveDataFileSystemDriver : ProxyFileSystemWithRetry
long blockSize,
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,
in ValueSubStorage saveImageStorage, IBufferManager bufferManager, IMacGenerator macGenerator,
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,
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;
}
public void FinalizeObject()
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 new void FinalizeObject()
{
throw new NotImplementedException();
using var scopedRegistration = new ScopedBufferManagerContextRegistration();
BufferManagerUtility.EnableBlockingBufferManagerAllocation();
_fileSystem.FinalizeObject();
base.FinalizeObject();
}
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()
{
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,
IHash256GeneratorFactorySelector hashGeneratorFactorySelector, uint minimumVersion)
{
throw new NotImplementedException();
return JournalIntegritySaveDataFileSystem
.UpdateMac(in saveDataStorage, macGenerator, hashGeneratorFactorySelector, minimumVersion).Ret();
}
public long GetCounterForBundledCommit()
{
throw new NotImplementedException();
return _fileSystem.GetCounterForBundledCommit();
}
public Result CommitFileSystem()
{
throw new NotImplementedException();
return Commit().Ret();
}
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)
@ -151,11 +352,23 @@ public class JournalIntegritySaveDataFileSystemDriver : ProxyFileSystemWithRetry
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)
{
throw new NotImplementedException();
Assert.SdkRequiresNotNull(macGenerator);
return _fileSystem.UpdateMacAndCommit(macGenerator).Ret();
}
}

View file

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

View file

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

View file

@ -77,7 +77,7 @@ public class UnionStorage : IStorage
header[0] = blockSize;
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,
@ -157,7 +157,7 @@ public class UnionStorage : IStorage
if (res.IsFailure()) return res.Miss();
}
return _baseStorage.Flush();
return _baseStorage.Flush().Ret();
}
public override Result Read(long offset, Span<byte> destination)
@ -292,7 +292,7 @@ public class UnionStorage : IStorage
{
Assert.SdkRequiresNotNull(_buffer);
return _baseStorage.GetSize(out size);
return _baseStorage.GetSize(out size).Ret();
}
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)

View file

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