Merge pull request #122 from Thealexbarney/multi-commit

Add IMultiCommitManager to FS server.
Recovering an interrupted commit is currently not implemented.
This commit is contained in:
Alex Barney 2020-03-23 09:19:10 -07:00 committed by GitHub
commit 1f4c904b04
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 457 additions and 13 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,The version of the multi-commit context file is to high for the current MultiCommitManager implementation.
2,4792,,InvalidMultiCommitContextState,The multi-commit has not been provisionally committed.
# The range name is a guess. 4812 is currently the only result in it
2,4811,4819,ZeroBitmapFileCorrupted,
2,4812,,IncompleteBlockInZeroBitmapHashStorageFile,
@ -155,10 +159,14 @@ Module,DescriptionStart,DescriptionEnd,Name,Summary
2,6061,,InvalidOffset,
2,6062,,InvalidSize,
2,6063,,NullptrArgument,
2,6064,,InvalidAlignment,
2,6065,,InvalidMountName,
2,6066,,ExtensionSizeTooLarge,
2,6067,,ExtensionSizeInvalid,
2,6068,,ReadOldSaveDataInfoReader,
2,6068,,InvalidSaveDataInfoReader,
2,6069,,InvalidCacheStorageSize,
2,6070,,InvalidCacheStorageIndex,
2,6071,,InvalidCommitNameCount,Up to 10 file systems can be committed at the same time.
2,6080,6099,InvalidEnumValue,
2,6081,,InvalidSaveDataState,
@ -182,14 +190,17 @@ Module,DescriptionStart,DescriptionEnd,Name,Summary
2,6351,,UnsupportedOperationInRoGameCardStorageSetSize,
2,6359,,UnsupportedOperationInConcatFsQueryEntry,
2,6364,,UnsupportedOperationModifyRomFsFileSystem,
2,6365,,UnsupportedOperationInRomFsFileSystem,Called RomFsFileSystem::CommitProvisionally.
2,6366,,UnsupportedOperationRomFsFileSystemGetSpace,
2,6367,,UnsupportedOperationModifyRomFsFile,
2,6369,,UnsupportedOperationModifyReadOnlyFileSystem,
2,6371,,UnsupportedOperationReadOnlyFileSystemGetSpace,
2,6372,,UnsupportedOperationModifyReadOnlyFile,
2,6374,,UnsupportedOperationModifyPartitionFileSystem,
2,6375,,UnsupportedOperationInPartitionFileSystem,Called PartitionFileSystemCore::CommitProvisionally.
2,6376,,UnsupportedOperationInPartitionFileSetSize,
2,6377,,UnsupportedOperationIdInPartitionFileSystem,
2,6384,,UnsupportedOperationInDirectorySaveDataFileSystem,Called DirectorySaveDataFileSystem::CommitProvisionally on a non-user savedata.
2,6400,6449,PermissionDenied,
@ -197,14 +208,17 @@ Module,DescriptionStart,DescriptionEnd,Name,Summary
2,6454,,WriteStateUnflushed,
2,6457,,WriteModeFileNotClosed,
2,6461,,AllocatorAlignmentViolation,
2,6463,,MultiCommitFileSystemAlreadyAdded,The provided file system has already been added to the multi-commit manager.
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,The maximum number of file systems have been added to the multi-commit manager.
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,6064,,InvalidAlignment,
136 2,6065,,InvalidMountName,
159 2,6364,,UnsupportedOperationModifyRomFsFileSystem, 2,6350,,UnsupportedOperationInRoGameCardStorageWrite,
160 2,6366,,UnsupportedOperationRomFsFileSystemGetSpace, 2,6351,,UnsupportedOperationInRoGameCardStorageSetSize,
161 2,6367,,UnsupportedOperationModifyRomFsFile, 2,6359,,UnsupportedOperationInConcatFsQueryEntry,
162 2,6364,,UnsupportedOperationModifyRomFsFileSystem,
163 2,6369,,UnsupportedOperationModifyReadOnlyFileSystem, 2,6365,,UnsupportedOperationInRomFsFileSystem,Called RomFsFileSystem::CommitProvisionally.
164 2,6371,,UnsupportedOperationReadOnlyFileSystemGetSpace, 2,6366,,UnsupportedOperationRomFsFileSystemGetSpace,
165 2,6372,,UnsupportedOperationModifyReadOnlyFile, 2,6367,,UnsupportedOperationModifyRomFsFile,
166 2,6374,,UnsupportedOperationModifyPartitionFileSystem, 2,6369,,UnsupportedOperationModifyReadOnlyFileSystem,
167 2,6371,,UnsupportedOperationReadOnlyFileSystemGetSpace,
168 2,6372,,UnsupportedOperationModifyReadOnlyFile,
169 2,6374,,UnsupportedOperationModifyPartitionFileSystem,
170 2,6376,,UnsupportedOperationInPartitionFileSetSize, 2,6375,,UnsupportedOperationInPartitionFileSystem,Called PartitionFileSystemCore::CommitProvisionally.
171 2,6377,,UnsupportedOperationIdInPartitionFileSystem, 2,6376,,UnsupportedOperationInPartitionFileSetSize,
172 2,6400,6449,PermissionDenied, 2,6377,,UnsupportedOperationIdInPartitionFileSystem,
190 20,1,,TooLargeKeyOrDbFull, 2,6900,6999,BadState,
191 20,2,,KeyNotFound, 2,6902,,SubStorageNotInitialized,
192 20,4,,AllocationFailed, 2,6905,,NotMounted,
193 2,6906,,SaveDataIsExtending,
194 20,5,,InvalidKeyValue, 20,1,,TooLargeKeyOrDbFull,
195 20,6,,BufferInsufficient, 20,2,,KeyNotFound,
196 24,1,,DeviceNotFound, 20,4,,AllocationFailed,
197 24,4,,DeviceAsleep, 20,5,,InvalidKeyValue,
198 123,0,4999,SslService, 20,6,,BufferInsufficient,
199 124,0,,Cancelled, 24,1,,DeviceNotFound,
200 24,4,,DeviceAsleep,
201 124,1,,CancelledByUser, 123,0,4999,SslService,
202 124,100,,UserNotExist, 124,0,,Cancelled,
203 124,1,,CancelledByUser,
204 124,200,269,NetworkServiceAccountUnavailable, 124,100,,UserNotExist,
205 124,430,499,TokenCacheUnavailable, 124,200,269,NetworkServiceAccountUnavailable,
206 124,3000,8191,NetworkCommunicationError, 124,430,499,TokenCacheUnavailable,
208 202,601,,DualConnected, 202,140,149,Invalid,
209 202,602,,SameJoyTypeConnected, 202,601,,DualConnected,
210 202,603,,ColorNotAvailable, 202,602,,SameJoyTypeConnected,
211 202,603,,ColorNotAvailable,
212 202,604,,ControllerNotConnected,
213 202,3101,,Canceled,
214 202,3102,,NotSupportedNpadStyle,
215 202,3200,3209,ControllerFirmwareUpdateError,
216 202,3201,,ControllerFirmwareUpdateFailed,
217 202,3201,,ControllerFirmwareUpdateFailed, 205,110,119,IrsensorUnavailable,
218 205,110,119,IrsensorUnavailable, 205,110,,IrsensorUnconnected,
219 205,110,,IrsensorUnconnected, 205,111,,IrsensorUnsupported,
220 205,111,,IrsensorUnsupported, 205,120,,IrsensorNotReady,
221 205,122,139,IrsensorDeviceError,
222
223
224

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>The version of the multi-commit context file is to high for the current MultiCommitManager implementation.<br/>Error code: 2002-4791; Inner value: 0x256e02</summary>
public static Result.Base InvalidMultiCommitContextVersion => new Result.Base(ModuleFs, 4791);
/// <summary>The multi-commit has not been provisionally committed.<br/>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>
@ -306,6 +313,8 @@ namespace LibHac.Fs
public static Result.Base InvalidSize => new Result.Base(ModuleFs, 6062);
/// <summary>Error code: 2002-6063; Inner value: 0x2f5e02</summary>
public static Result.Base NullptrArgument => new Result.Base(ModuleFs, 6063);
/// <summary>Error code: 2002-6064; Inner value: 0x2f6002</summary>
public static Result.Base InvalidAlignment => new Result.Base(ModuleFs, 6064);
/// <summary>Error code: 2002-6065; Inner value: 0x2f6202</summary>
public static Result.Base InvalidMountName => new Result.Base(ModuleFs, 6065);
/// <summary>Error code: 2002-6066; Inner value: 0x2f6402</summary>
@ -313,7 +322,13 @@ namespace LibHac.Fs
/// <summary>Error code: 2002-6067; Inner value: 0x2f6602</summary>
public static Result.Base ExtensionSizeInvalid => new Result.Base(ModuleFs, 6067);
/// <summary>Error code: 2002-6068; Inner value: 0x2f6802</summary>
public static Result.Base ReadOldSaveDataInfoReader => new Result.Base(ModuleFs, 6068);
public static Result.Base InvalidSaveDataInfoReader => new Result.Base(ModuleFs, 6068);
/// <summary>Error code: 2002-6069; Inner value: 0x2f6a02</summary>
public static Result.Base InvalidCacheStorageSize => new Result.Base(ModuleFs, 6069);
/// <summary>Error code: 2002-6070; Inner value: 0x2f6c02</summary>
public static Result.Base InvalidCacheStorageIndex => new Result.Base(ModuleFs, 6070);
/// <summary>Up to 10 file systems can be committed at the same time.<br/>Error code: 2002-6071; Inner value: 0x2f6e02</summary>
public static Result.Base InvalidCommitNameCount => new Result.Base(ModuleFs, 6071);
/// <summary>Error code: 2002-6080; Range: 6080-6099; Inner value: 0x2f8002</summary>
public static Result.Base InvalidEnumValue { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 6080, 6099); }
@ -357,6 +372,8 @@ namespace LibHac.Fs
public static Result.Base UnsupportedOperationInConcatFsQueryEntry => new Result.Base(ModuleFs, 6359);
/// <summary>Error code: 2002-6364; Inner value: 0x31b802</summary>
public static Result.Base UnsupportedOperationModifyRomFsFileSystem => new Result.Base(ModuleFs, 6364);
/// <summary>Called RomFsFileSystem::CommitProvisionally.<br/>Error code: 2002-6365; Inner value: 0x31ba02</summary>
public static Result.Base UnsupportedOperationInRomFsFileSystem => new Result.Base(ModuleFs, 6365);
/// <summary>Error code: 2002-6366; Inner value: 0x31bc02</summary>
public static Result.Base UnsupportedOperationRomFsFileSystemGetSpace => new Result.Base(ModuleFs, 6366);
/// <summary>Error code: 2002-6367; Inner value: 0x31be02</summary>
@ -369,10 +386,14 @@ namespace LibHac.Fs
public static Result.Base UnsupportedOperationModifyReadOnlyFile => new Result.Base(ModuleFs, 6372);
/// <summary>Error code: 2002-6374; Inner value: 0x31cc02</summary>
public static Result.Base UnsupportedOperationModifyPartitionFileSystem => new Result.Base(ModuleFs, 6374);
/// <summary>Called PartitionFileSystemCore::CommitProvisionally.<br/>Error code: 2002-6375; Inner value: 0x31ce02</summary>
public static Result.Base UnsupportedOperationInPartitionFileSystem => new Result.Base(ModuleFs, 6375);
/// <summary>Error code: 2002-6376; Inner value: 0x31d002</summary>
public static Result.Base UnsupportedOperationInPartitionFileSetSize => new Result.Base(ModuleFs, 6376);
/// <summary>Error code: 2002-6377; Inner value: 0x31d202</summary>
public static Result.Base UnsupportedOperationIdInPartitionFileSystem => new Result.Base(ModuleFs, 6377);
/// <summary>Called DirectorySaveDataFileSystem::CommitProvisionally on a non-user savedata.<br/>Error code: 2002-6384; Inner value: 0x31e002</summary>
public static Result.Base UnsupportedOperationInDirectorySaveDataFileSystem => new Result.Base(ModuleFs, 6384);
/// <summary>Error code: 2002-6400; Range: 6400-6449; Inner value: 0x320002</summary>
public static Result.Base PermissionDenied { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 6400, 6449); }
@ -385,6 +406,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>The provided file system has already been added to the multi-commit manager.<br/>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 +424,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>The maximum number of file systems have been added to the multi-commit manager.<br/>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; }
@ -160,10 +161,10 @@ namespace LibHac.FsService
// Check if permissions allow deleting save data
}
reader.Indexer.SetState(saveDataId, SaveDataState.Creating);
rc = reader.Indexer.SetState(saveDataId, SaveDataState.Creating);
if (rc.IsFailure()) return rc;
reader.Indexer.Commit();
rc = reader.Indexer.Commit();
if (rc.IsFailure()) return rc;
}
@ -406,7 +407,7 @@ namespace LibHac.FsService
// Revert changes if an error happened in the middle of creation
if (isDeleteNeeded)
{
DeleteSaveDataFileSystemImpl2(creationInfo.SpaceId, saveDataId);
DeleteSaveDataFileSystemImpl2(creationInfo.SpaceId, saveDataId).IgnoreResult();
if (reader.IsInitialized && saveDataId != FileSystemServer.SaveIndexerId)
{
@ -414,8 +415,8 @@ namespace LibHac.FsService
if (rc.IsSuccess() && value.SpaceId == creationInfo.SpaceId)
{
reader.Indexer.Delete(saveDataId);
reader.Indexer.Commit();
reader.Indexer.Delete(saveDataId).IgnoreResult();
reader.Indexer.Commit().IgnoreResult();
}
}
}
@ -1151,7 +1152,7 @@ namespace LibHac.FsService
rc = FsServer.SaveDataIndexerManager.GetSaveDataIndexer(out reader, SaveDataSpaceId.Temporary);
if (rc.IsFailure()) return rc;
reader.Indexer.Reset();
reader.Indexer.Reset().IgnoreResult();
return Result.Success;
}
@ -1175,6 +1176,28 @@ namespace LibHac.FsService
return Result.Success;
}
public Result OpenMultiCommitManager(out IMultiCommitManager commitManager)
{
commitManager = new MultiCommitManager(this);
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

@ -677,7 +677,8 @@ namespace LibHac.FsService
if (rc.IsFailure()) return rc;
baseFileSystem.EnsureDirectoryExists(contentDirPath.ToString());
rc = baseFileSystem.EnsureDirectoryExists(contentDirPath.ToString());
if (rc.IsFailure()) return rc;
rc = FsCreators.SubDirectoryFileSystemCreator.Create(out IFileSystem subDirFileSystem,
baseFileSystem, contentDirPath);
@ -879,7 +880,7 @@ namespace LibHac.FsService
if (cacheExtraData)
{
// Missing extra data caching
// todo: Missing extra data caching
}
fileSystem = openReadOnly ? new ReadOnlyFileSystem(saveFs) : saveFs;

View file

@ -42,7 +42,7 @@ namespace LibHac.FsService
SaveDataIndexerManager = new SaveDataIndexerManager(FsClient, SaveIndexerId);
fsProxy.CleanUpTemporaryStorage();
fsProxy.CleanUpTemporaryStorage().IgnoreResult();
}
/// <summary>

View file

@ -100,5 +100,6 @@ namespace LibHac.FsService
Result GetProgramIndexForAccessLog(out int programIndex, out int programCount);
Result OverrideSaveDataTransferTokenSignVerificationKey(ReadOnlySpan<byte> key);
Result CorruptSaveDataFileSystemByOffset(SaveDataSpaceId spaceId, ulong saveDataId, long offset);
Result OpenMultiCommitManager(out IMultiCommitManager commitManager);
}
}

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,272 @@
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.Explicit, Size = 0x18)]
private struct CommitContext
{
[FieldOffset(0x00)] public int Version;
[FieldOffset(0x04)] public CommitState State;
[FieldOffset(0x08)] public int FileSystemCount;
[FieldOffset(0x10)] 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;
}
}
finally
{
contextFile?.Dispose();
}
try
{
Result rc = _fileSystem.OpenFile(out contextFile, ContextFileName, OpenMode.ReadWrite);
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;
}
}
}
}

View file

@ -648,7 +648,7 @@ namespace LibHac.FsService
// Indexer has been reloaded since this info reader was created
if (Version != Indexer.Version)
{
return ResultFs.ReadOldSaveDataInfoReader.Log();
return ResultFs.InvalidSaveDataInfoReader.Log();
}
// No more to iterate

View file

@ -221,6 +221,16 @@ namespace LibHac.FsSystem
return BaseFileSystem.Commit();
}
protected override Result CommitProvisionallyImpl(long commitCount)
{
return BaseFileSystem.CommitProvisionally(commitCount);
}
protected override Result RollbackImpl()
{
return BaseFileSystem.Rollback();
}
protected override Result QueryEntryImpl(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, QueryId queryId,
U8Span path)
{

View file

@ -306,6 +306,16 @@ namespace LibHac.FsSystem
return BaseFileSystem.Commit();
}
protected override Result CommitProvisionallyImpl(long commitCount)
{
return BaseFileSystem.CommitProvisionally(commitCount);
}
protected override Result FlushImpl()
{
return BaseFileSystem.Flush();
}
protected override Result QueryEntryImpl(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, QueryId queryId,
U8Span path)
{

View file

@ -299,6 +299,23 @@ namespace LibHac.FsSystem
}
}
protected override Result CommitProvisionallyImpl(long commitCount)
{
if (!IsUserSaveData)
return ResultFs.UnsupportedOperationIdInPartitionFileSystem.Log();
return Result.Success;
}
protected override Result RollbackImpl()
{
// No old data is kept for temporary save data, so there's nothing to rollback to
if (!IsPersistentSaveData)
return Result.Success;
return Initialize(IsPersistentSaveData, IsUserSaveData);
}
private Result ResolveFullPath(Span<byte> outPath, U8Span relativePath)
{
if (StringUtils.GetLength(relativePath, PathTools.MaxPathLength + 1) > PathTools.MaxPathLength)

View file

@ -108,6 +108,7 @@ namespace LibHac.FsSystem
protected override Result DeleteFileImpl(U8Span path) => ResultFs.UnsupportedOperationModifyPartitionFileSystem.Log();
protected override Result RenameDirectoryImpl(U8Span oldPath, U8Span newPath) => ResultFs.UnsupportedOperationModifyPartitionFileSystem.Log();
protected override Result RenameFileImpl(U8Span oldPath, U8Span newPath) => ResultFs.UnsupportedOperationModifyPartitionFileSystem.Log();
protected override Result CommitProvisionallyImpl(long commitCount) => ResultFs.UnsupportedOperationInPartitionFileSystem.Log();
private class PartitionFile : FileBase
{

View file

@ -92,6 +92,7 @@ namespace LibHac.FsSystem.RomFs
protected override Result DeleteFileImpl(U8Span path) => ResultFs.UnsupportedOperationModifyRomFsFileSystem.Log();
protected override Result RenameDirectoryImpl(U8Span oldPath, U8Span newPath) => ResultFs.UnsupportedOperationModifyRomFsFileSystem.Log();
protected override Result RenameFileImpl(U8Span oldPath, U8Span newPath) => ResultFs.UnsupportedOperationModifyRomFsFileSystem.Log();
protected override Result CommitProvisionallyImpl(long commitCount) => ResultFs.UnsupportedOperationInRomFsFileSystem.Log();
protected override Result GetFreeSpaceSizeImpl(out long freeSpace, U8Span path)
{

View file

@ -194,6 +194,16 @@ namespace LibHac.FsSystem
return BaseFileSystem.Commit();
}
protected override Result CommitProvisionallyImpl(long commitCount)
{
return BaseFileSystem.CommitProvisionally(commitCount);
}
protected override Result RollbackImpl()
{
return BaseFileSystem.Rollback();
}
protected override Result GetFreeSpaceSizeImpl(out long freeSpace, U8Span path)
{
freeSpace = default;

View file

@ -70,6 +70,11 @@ namespace LibHac
public bool IsSuccess() => _value == SuccessValue;
public bool IsFailure() => !IsSuccess();
/// <summary>
/// Specifies that the <see cref="Result"/> from a returned function is explicitly ignored.
/// </summary>
public void IgnoreResult() { }
public void ThrowIfFailure()
{
if (IsFailure())
@ -212,6 +217,7 @@ namespace LibHac
/// <c>new Result.Base(ModuleFs, 2000, 2499)</c>. The property will need to have the aggressive inlining flag set like so:
/// <c>public static Result.Base SdCardAccessFailed { [MethodImpl(MethodImplOptions.AggressiveInlining)] get =&gt; new Result.Base(ModuleFs, 2000, 2499); }</c>
/// </remarks>
[DebuggerDisplay("{" + nameof(ToStringWithName) + "(),nq}")]
public struct Base
{
private const int DescriptionEndBitsOffset = ReservedBitsOffset;