Add MultiCommitManager

- Recovering from an interrupted commit isn't implemented yet
This commit is contained in:
Alex Barney 2020-03-21 22:44:38 -07:00
parent d1110392b4
commit ce54ae111c
8 changed files with 351 additions and 1 deletions

View file

@ -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,

1 Module,DescriptionStart,DescriptionEnd,Name,Summary
127 2,6033,,PathNotFoundInSaveDataFileTable, 2,6030,6059,InvalidPathForOperation,
128 2,6034,,DifferentDestFileSystem, 2,6031,,DirectoryNotDeletable,
129 2,6061,,InvalidOffset, 2,6032,,DestinationIsSubPathOfSource,
130 2,6033,,PathNotFoundInSaveDataFileTable,
131 2,6034,,DifferentDestFileSystem,
132 2,6061,,InvalidOffset,
133 2,6062,,InvalidSize,
134 2,6062,,InvalidSize, 2,6063,,NullptrArgument,
135 2,6063,,NullptrArgument, 2,6065,,InvalidMountName,
136 2,6065,,InvalidMountName, 2,6066,,ExtensionSizeTooLarge,
201 202,601,,DualConnected, 202,140,149,Invalid,
202 202,602,,SameJoyTypeConnected, 202,601,,DualConnected,
203 202,603,,ColorNotAvailable, 202,602,,SameJoyTypeConnected,
204 202,603,,ColorNotAvailable,
205 202,604,,ControllerNotConnected,
206 202,3101,,Canceled,
207 202,3102,,NotSupportedNpadStyle,
208 202,3200,3209,ControllerFirmwareUpdateError,
209 202,3201,,ControllerFirmwareUpdateFailed,
210 202,3201,,ControllerFirmwareUpdateFailed, 205,110,119,IrsensorUnavailable,
211 205,110,119,IrsensorUnavailable, 205,110,,IrsensorUnconnected,
212 205,110,,IrsensorUnconnected, 205,111,,IrsensorUnsupported,
213 205,111,,IrsensorUnsupported, 205,120,,IrsensorNotReady,
214 205,122,139,IrsensorDeviceError,
215
216
217

View file

@ -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();

View file

@ -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>

View file

@ -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); }

View file

@ -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;

View file

@ -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;

View file

@ -0,0 +1,10 @@
using LibHac.Fs;
namespace LibHac.FsService
{
public interface IMultiCommitManager
{
Result Add(IFileSystem fileSystem);
Result Commit();
}
}

View 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;
}
}
}
}