mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Add MultiCommitManager
- Recovering from an interrupted commit isn't implemented yet
This commit is contained in:
parent
d1110392b4
commit
ce54ae111c
8 changed files with 351 additions and 1 deletions
|
@ -127,6 +127,10 @@ Module,DescriptionStart,DescriptionEnd,Name,Summary
|
|||
2,4771,4779,SignedSystemPartitionDataCorrupted,
|
||||
2,4781,,GameCardLogoDataCorrupted,
|
||||
|
||||
2,4791,4799,MultiCommitContextCorrupted,
|
||||
2,4791,,InvalidMultiCommitContextVersion,
|
||||
2,4792,,InvalidMultiCommitContextState,
|
||||
|
||||
# The range name is a guess. 4812 is currently the only result in it
|
||||
2,4811,4819,ZeroBitmapFileCorrupted,
|
||||
2,4812,,IncompleteBlockInZeroBitmapHashStorageFile,
|
||||
|
@ -197,14 +201,17 @@ Module,DescriptionStart,DescriptionEnd,Name,Summary
|
|||
2,6454,,WriteStateUnflushed,
|
||||
2,6457,,WriteModeFileNotClosed,
|
||||
2,6461,,AllocatorAlignmentViolation,
|
||||
2,6463,,MultiCommitFileSystemAlreadyAdded,
|
||||
2,6465,,UserNotExist,
|
||||
|
||||
2,6600,6699,EntryNotFound,
|
||||
2,6606,,TargetProgramIndexNotFound,Specified program index is not found
|
||||
|
||||
2,6700,6799,OutOfResource,
|
||||
2,6706,,MappingTableFull,
|
||||
2,6707,,AllocationTableInsufficientFreeBlocks,
|
||||
2,6709,,OpenCountLimit,
|
||||
2,6710,,MultiCommitFileSystemLimit,
|
||||
|
||||
2,6800,6899,MappingFailed,
|
||||
2,6811,,RemapStorageMapFull,
|
||||
|
|
|
|
@ -35,6 +35,21 @@ namespace LibHac.Fs
|
|||
return ResultFs.NotImplemented.Log();
|
||||
}
|
||||
|
||||
protected virtual Result CommitProvisionallyImpl(long commitCount)
|
||||
{
|
||||
return ResultFs.NotImplemented.Log();
|
||||
}
|
||||
|
||||
protected virtual Result RollbackImpl()
|
||||
{
|
||||
return ResultFs.NotImplemented.Log();
|
||||
}
|
||||
|
||||
protected virtual Result FlushImpl()
|
||||
{
|
||||
return ResultFs.NotImplemented.Log();
|
||||
}
|
||||
|
||||
protected virtual Result GetFileTimeStampRawImpl(out FileTimeStampRaw timeStamp, U8Span path)
|
||||
{
|
||||
timeStamp = default;
|
||||
|
@ -200,6 +215,27 @@ namespace LibHac.Fs
|
|||
return CommitImpl();
|
||||
}
|
||||
|
||||
public Result CommitProvisionally(long commitCount)
|
||||
{
|
||||
if (IsDisposed) return ResultFs.PreconditionViolation.Log();
|
||||
|
||||
return CommitProvisionallyImpl(commitCount);
|
||||
}
|
||||
|
||||
public Result Rollback()
|
||||
{
|
||||
if (IsDisposed) return ResultFs.PreconditionViolation.Log();
|
||||
|
||||
return RollbackImpl();
|
||||
}
|
||||
|
||||
public Result Flush()
|
||||
{
|
||||
if (IsDisposed) return ResultFs.PreconditionViolation.Log();
|
||||
|
||||
return FlushImpl();
|
||||
}
|
||||
|
||||
public Result QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, QueryId queryId, U8Span path)
|
||||
{
|
||||
if (IsDisposed) return ResultFs.PreconditionViolation.Log();
|
||||
|
|
|
@ -183,6 +183,12 @@ namespace LibHac.Fs
|
|||
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
|
||||
Result Commit();
|
||||
|
||||
Result CommitProvisionally(long commitCount);
|
||||
|
||||
Result Rollback();
|
||||
|
||||
Result Flush();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the creation, last accessed, and last modified timestamps of a file or directory.
|
||||
/// </summary>
|
||||
|
|
|
@ -256,6 +256,13 @@ namespace LibHac.Fs
|
|||
/// <summary>Error code: 2002-4781; Inner value: 0x255a02</summary>
|
||||
public static Result.Base GameCardLogoDataCorrupted => new Result.Base(ModuleFs, 4781);
|
||||
|
||||
/// <summary>Error code: 2002-4791; Range: 4791-4799; Inner value: 0x256e02</summary>
|
||||
public static Result.Base MultiCommitContextCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4791, 4799); }
|
||||
/// <summary>Error code: 2002-4791; Inner value: 0x256e02</summary>
|
||||
public static Result.Base InvalidMultiCommitContextVersion => new Result.Base(ModuleFs, 4791);
|
||||
/// <summary>Error code: 2002-4792; Inner value: 0x257002</summary>
|
||||
public static Result.Base InvalidMultiCommitContextState => new Result.Base(ModuleFs, 4792);
|
||||
|
||||
/// <summary>Error code: 2002-4811; Range: 4811-4819; Inner value: 0x259602</summary>
|
||||
public static Result.Base ZeroBitmapFileCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4811, 4819); }
|
||||
/// <summary>Error code: 2002-4812; Inner value: 0x259802</summary>
|
||||
|
@ -385,6 +392,8 @@ namespace LibHac.Fs
|
|||
public static Result.Base WriteModeFileNotClosed => new Result.Base(ModuleFs, 6457);
|
||||
/// <summary>Error code: 2002-6461; Inner value: 0x327a02</summary>
|
||||
public static Result.Base AllocatorAlignmentViolation => new Result.Base(ModuleFs, 6461);
|
||||
/// <summary>Error code: 2002-6463; Inner value: 0x327e02</summary>
|
||||
public static Result.Base MultiCommitFileSystemAlreadyAdded => new Result.Base(ModuleFs, 6463);
|
||||
/// <summary>Error code: 2002-6465; Inner value: 0x328202</summary>
|
||||
public static Result.Base UserNotExist => new Result.Base(ModuleFs, 6465);
|
||||
|
||||
|
@ -401,6 +410,8 @@ namespace LibHac.Fs
|
|||
public static Result.Base AllocationTableInsufficientFreeBlocks => new Result.Base(ModuleFs, 6707);
|
||||
/// <summary>Error code: 2002-6709; Inner value: 0x346a02</summary>
|
||||
public static Result.Base OpenCountLimit => new Result.Base(ModuleFs, 6709);
|
||||
/// <summary>Error code: 2002-6710; Inner value: 0x346c02</summary>
|
||||
public static Result.Base MultiCommitFileSystemLimit => new Result.Base(ModuleFs, 6710);
|
||||
|
||||
/// <summary>Error code: 2002-6800; Range: 6800-6899; Inner value: 0x352002</summary>
|
||||
public static Result.Base MappingFailed { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 6800, 6899); }
|
||||
|
|
|
@ -8,6 +8,7 @@ namespace LibHac.Fs
|
|||
[StructLayout(LayoutKind.Explicit, Size = 0x40)]
|
||||
public struct SaveDataAttribute : IEquatable<SaveDataAttribute>, IComparable<SaveDataAttribute>
|
||||
{
|
||||
// todo: rename to ProgramId
|
||||
[FieldOffset(0x00)] public TitleId TitleId;
|
||||
[FieldOffset(0x08)] public UserId UserId;
|
||||
[FieldOffset(0x18)] public ulong SaveDataId;
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.FsService.Impl;
|
||||
using LibHac.FsSystem;
|
||||
using LibHac.Kvdb;
|
||||
using LibHac.Ncm;
|
||||
|
@ -12,7 +13,7 @@ namespace LibHac.FsService
|
|||
public class FileSystemProxy : IFileSystemProxy
|
||||
{
|
||||
private FileSystemProxyCore FsProxyCore { get; }
|
||||
private FileSystemServer FsServer { get; }
|
||||
internal FileSystemServer FsServer { get; }
|
||||
|
||||
public long CurrentProcess { get; private set; }
|
||||
|
||||
|
@ -1175,6 +1176,22 @@ namespace LibHac.FsService
|
|||
return Result.Success;
|
||||
}
|
||||
|
||||
internal Result OpenMultiCommitContextSaveData(out IFileSystem fileSystem)
|
||||
{
|
||||
fileSystem = default;
|
||||
|
||||
SaveDataAttribute attribute = default;
|
||||
attribute.TitleId = new TitleId(MultiCommitManager.ProgramId);
|
||||
attribute.SaveDataId = MultiCommitManager.SaveDataId;
|
||||
|
||||
Result rc = OpenSaveDataFileSystemImpl(out IFileSystem saveFs, out _, SaveDataSpaceId.System, ref attribute,
|
||||
false, true);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
fileSystem = saveFs;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
private static bool IsSystemSaveDataId(ulong id)
|
||||
{
|
||||
return (long)id < 0;
|
||||
|
|
10
src/LibHac/FsService/IMultiCommitManager.cs
Normal file
10
src/LibHac/FsService/IMultiCommitManager.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
using LibHac.Fs;
|
||||
|
||||
namespace LibHac.FsService
|
||||
{
|
||||
public interface IMultiCommitManager
|
||||
{
|
||||
Result Add(IFileSystem fileSystem);
|
||||
Result Commit();
|
||||
}
|
||||
}
|
262
src/LibHac/FsService/Impl/MultiCommitManager.cs
Normal file
262
src/LibHac/FsService/Impl/MultiCommitManager.cs
Normal file
|
@ -0,0 +1,262 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Shim;
|
||||
|
||||
namespace LibHac.FsService.Impl
|
||||
{
|
||||
internal class MultiCommitManager : IMultiCommitManager
|
||||
{
|
||||
private const int MaxFileSystemCount = 10;
|
||||
private const int CurrentContextVersion = 0x10000;
|
||||
|
||||
public const ulong ProgramId = 0x100000000000000;
|
||||
public const ulong SaveDataId = 0x8000000000000001;
|
||||
private const long SaveDataSize = 0xC000;
|
||||
private const long SaveJournalSize = 0xC000;
|
||||
|
||||
private const long ContextFileSize = 0x200;
|
||||
|
||||
// /commitinfo
|
||||
private static U8Span ContextFileName =>
|
||||
new U8Span(new[] { (byte)'/', (byte)'c', (byte)'o', (byte)'m', (byte)'m', (byte)'i', (byte)'t', (byte)'i', (byte)'n', (byte)'f', (byte)'o' });
|
||||
|
||||
private static readonly object Locker = new object();
|
||||
|
||||
private FileSystemProxy FsProxy { get; }
|
||||
private List<IFileSystem> FileSystems { get; } = new List<IFileSystem>(MaxFileSystemCount);
|
||||
private long CommitCount { get; set; }
|
||||
|
||||
public MultiCommitManager(FileSystemProxy fsProxy)
|
||||
{
|
||||
FsProxy = fsProxy;
|
||||
}
|
||||
|
||||
public Result Add(IFileSystem fileSystem)
|
||||
{
|
||||
if (FileSystems.Count >= MaxFileSystemCount)
|
||||
return ResultFs.MultiCommitFileSystemLimit.Log();
|
||||
|
||||
// Check that the file system hasn't already been added
|
||||
for (int i = 0; i < FileSystems.Count; i++)
|
||||
{
|
||||
if (ReferenceEquals(FileSystems[i], fileSystem))
|
||||
return ResultFs.MultiCommitFileSystemAlreadyAdded.Log();
|
||||
}
|
||||
|
||||
FileSystems.Add(fileSystem);
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result Commit()
|
||||
{
|
||||
lock (Locker)
|
||||
{
|
||||
Result rc = CreateSave();
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = FsProxy.OpenMultiCommitContextSaveData(out IFileSystem contextFs);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return CommitImpl(contextFs);
|
||||
}
|
||||
}
|
||||
|
||||
private Result CommitImpl(IFileSystem contextFileSystem)
|
||||
{
|
||||
var context = new CommitContextManager(contextFileSystem);
|
||||
|
||||
try
|
||||
{
|
||||
CommitCount = 1;
|
||||
|
||||
Result rc = context.Initialize(CommitCount, FileSystems.Count);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = CommitProvisionally();
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = context.SetCommittedProvisionally();
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
foreach (IFileSystem fs in FileSystems)
|
||||
{
|
||||
rc = fs.Commit();
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
|
||||
rc = context.Close();
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
finally
|
||||
{
|
||||
context.Dispose();
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
private Result CreateSave()
|
||||
{
|
||||
Result rc = FsProxy.OpenMultiCommitContextSaveData(out IFileSystem contextFs);
|
||||
|
||||
if (rc.IsFailure())
|
||||
{
|
||||
if (!ResultFs.TargetNotFound.Includes(rc))
|
||||
{
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = FsProxy.FsServer.FsClient.CreateSystemSaveData(SaveDataId, SaveDataSize, SaveJournalSize,
|
||||
SaveDataFlags.None);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
|
||||
contextFs?.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
private Result CommitProvisionally()
|
||||
{
|
||||
Result rc = Result.Success;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < FileSystems.Count; i++)
|
||||
{
|
||||
rc = FileSystems[i].CommitProvisionally(CommitCount);
|
||||
|
||||
if (rc.IsFailure())
|
||||
break;
|
||||
}
|
||||
|
||||
if (rc.IsFailure())
|
||||
{
|
||||
// Rollback all provisional commits including the failed commit
|
||||
for (int j = 0; j <= i; j++)
|
||||
{
|
||||
FileSystems[j].Rollback().IgnoreResult();
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x18)]
|
||||
private struct CommitContext
|
||||
{
|
||||
public int Version;
|
||||
public CommitState State;
|
||||
public int FileSystemCount;
|
||||
public long CommitCount; // I think?
|
||||
}
|
||||
|
||||
private enum CommitState
|
||||
{
|
||||
// ReSharper disable once UnusedMember.Local
|
||||
None = 0,
|
||||
NotCommitted = 1,
|
||||
ProvisionallyCommitted = 2
|
||||
}
|
||||
|
||||
private struct CommitContextManager
|
||||
{
|
||||
private IFileSystem _fileSystem;
|
||||
private CommitContext _context;
|
||||
|
||||
public CommitContextManager(IFileSystem contextFileSystem)
|
||||
{
|
||||
_fileSystem = contextFileSystem;
|
||||
_context = default;
|
||||
}
|
||||
|
||||
public Result Initialize(long commitCount, int fileSystemCount)
|
||||
{
|
||||
IFile contextFile = null;
|
||||
|
||||
try
|
||||
{
|
||||
// Open context file and create if it doesn't exist
|
||||
Result rc = _fileSystem.OpenFile(out contextFile, ContextFileName, OpenMode.Read);
|
||||
|
||||
if (rc.IsFailure())
|
||||
{
|
||||
if (!ResultFs.PathNotFound.Includes(rc))
|
||||
return rc;
|
||||
|
||||
rc = _fileSystem.CreateFile(ContextFileName, ContextFileSize, CreateFileOptions.None);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = _fileSystem.OpenFile(out contextFile, ContextFileName, OpenMode.Read);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
|
||||
_context.Version = CurrentContextVersion;
|
||||
_context.State = CommitState.NotCommitted;
|
||||
_context.FileSystemCount = fileSystemCount;
|
||||
_context.CommitCount = commitCount;
|
||||
|
||||
// Write the initial context to the file
|
||||
rc = contextFile.Write(0, SpanHelpers.AsByteSpan(ref _context), WriteOption.None);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = contextFile.Flush();
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
finally
|
||||
{
|
||||
contextFile?.Dispose();
|
||||
}
|
||||
|
||||
return _fileSystem.Commit();
|
||||
}
|
||||
|
||||
public Result SetCommittedProvisionally()
|
||||
{
|
||||
IFile contextFile = null;
|
||||
|
||||
try
|
||||
{
|
||||
Result rc = _fileSystem.OpenFile(out contextFile, ContextFileName, OpenMode.ReadWrite);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
_context.State = CommitState.ProvisionallyCommitted;
|
||||
|
||||
rc = contextFile.Write(0, SpanHelpers.AsByteSpan(ref _context), WriteOption.None);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = contextFile.Flush();
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
finally
|
||||
{
|
||||
contextFile?.Dispose();
|
||||
}
|
||||
|
||||
return _fileSystem.Commit();
|
||||
}
|
||||
|
||||
public Result Close()
|
||||
{
|
||||
Result rc = _fileSystem.DeleteFile(ContextFileName);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = _fileSystem.Commit();
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
_fileSystem = null;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_fileSystem is null) return;
|
||||
|
||||
_fileSystem.DeleteFile(ContextFileName).IgnoreResult();
|
||||
_fileSystem.Commit().IgnoreResult();
|
||||
|
||||
_fileSystem = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue