Update DirectorySaveDataFileSystem to implement ISaveDataFileSystem

This commit is contained in:
Alex Barney 2022-04-23 12:38:10 -07:00
parent bc7fea5714
commit 7dfcebfc28
14 changed files with 188 additions and 196 deletions

View file

@ -2,6 +2,7 @@
using LibHac.Common.Keys; using LibHac.Common.Keys;
using LibHac.FsSrv.FsCreator; using LibHac.FsSrv.FsCreator;
using LibHac.FsSrv.Sf; using LibHac.FsSrv.Sf;
using LibHac.FsSystem;
using IFileSystem = LibHac.Fs.Fsa.IFileSystem; using IFileSystem = LibHac.Fs.Fsa.IFileSystem;
namespace LibHac.FsSrv; namespace LibHac.FsSrv;
@ -14,7 +15,7 @@ public class DefaultFsServerObjects
public EmulatedSdCard SdCard { get; set; } public EmulatedSdCard SdCard { get; set; }
public static DefaultFsServerObjects GetDefaultEmulatedCreators(IFileSystem rootFileSystem, KeySet keySet, public static DefaultFsServerObjects GetDefaultEmulatedCreators(IFileSystem rootFileSystem, KeySet keySet,
FileSystemServer fsServer) FileSystemServer fsServer, RandomDataGenerator randomGenerator)
{ {
var creators = new FileSystemCreatorInterfaces(); var creators = new FileSystemCreatorInterfaces();
var gameCard = new EmulatedGameCard(keySet); var gameCard = new EmulatedGameCard(keySet);
@ -31,7 +32,7 @@ public class DefaultFsServerObjects
creators.StorageOnNcaCreator = new StorageOnNcaCreator(keySet); creators.StorageOnNcaCreator = new StorageOnNcaCreator(keySet);
creators.TargetManagerFileSystemCreator = new TargetManagerFileSystemCreator(); creators.TargetManagerFileSystemCreator = new TargetManagerFileSystemCreator();
creators.SubDirectoryFileSystemCreator = new SubDirectoryFileSystemCreator(); creators.SubDirectoryFileSystemCreator = new SubDirectoryFileSystemCreator();
creators.SaveDataFileSystemCreator = new SaveDataFileSystemCreator(fsServer, keySet, null, null); creators.SaveDataFileSystemCreator = new SaveDataFileSystemCreator(fsServer, keySet, null, randomGenerator);
creators.GameCardStorageCreator = gcStorageCreator; creators.GameCardStorageCreator = gcStorageCreator;
creators.GameCardFileSystemCreator = new EmulatedGameCardFsCreator(gcStorageCreator, gameCard); creators.GameCardFileSystemCreator = new EmulatedGameCardFsCreator(gcStorageCreator, gameCard);
creators.EncryptedFileSystemCreator = new EncryptedFileSystemCreator(keySet); creators.EncryptedFileSystemCreator = new EncryptedFileSystemCreator(keySet);

View file

@ -63,13 +63,6 @@ public static class FileSystemServerInitializer
private static FileSystemProxyConfiguration InitializeFileSystemProxy(FileSystemServer server, private static FileSystemProxyConfiguration InitializeFileSystemProxy(FileSystemServer server,
FileSystemServerConfig config) FileSystemServerConfig config)
{ {
var random = new Random();
RandomDataGenerator randomGenerator = buffer =>
{
random.NextBytes(buffer);
return Result.Success;
};
var bufferManager = new FileSystemBufferManager(); var bufferManager = new FileSystemBufferManager();
Memory<byte> heapBuffer = GC.AllocateArray<byte>(BufferManagerHeapSize, true); Memory<byte> heapBuffer = GC.AllocateArray<byte>(BufferManagerHeapSize, true);
bufferManager.Initialize(BufferManagerCacheSize, heapBuffer, BufferManagerBlockSize); bufferManager.Initialize(BufferManagerCacheSize, heapBuffer, BufferManagerBlockSize);
@ -141,7 +134,7 @@ public static class FileSystemServerInitializer
saveFsServiceConfig.EncryptedFsCreator = config.FsCreators.EncryptedFileSystemCreator; saveFsServiceConfig.EncryptedFsCreator = config.FsCreators.EncryptedFileSystemCreator;
saveFsServiceConfig.ProgramRegistryService = programRegistryService; saveFsServiceConfig.ProgramRegistryService = programRegistryService;
saveFsServiceConfig.BufferManager = bufferManager; saveFsServiceConfig.BufferManager = bufferManager;
saveFsServiceConfig.GenerateRandomData = randomGenerator; saveFsServiceConfig.GenerateRandomData = config.RandomGenerator;
saveFsServiceConfig.IsPseudoSaveData = () => true; saveFsServiceConfig.IsPseudoSaveData = () => true;
saveFsServiceConfig.MaxSaveFsCacheCount = 1; saveFsServiceConfig.MaxSaveFsCacheCount = 1;
saveFsServiceConfig.SaveIndexerManager = saveDataIndexerManager; saveFsServiceConfig.SaveIndexerManager = saveDataIndexerManager;
@ -278,4 +271,9 @@ public class FileSystemServerConfig
/// If null, an empty set will be created. /// If null, an empty set will be created.
/// </summary> /// </summary>
public ExternalKeySet ExternalKeySet { get; set; } public ExternalKeySet ExternalKeySet { get; set; }
/// <summary>
/// Used for generating random data for save data.
/// </summary>
public RandomDataGenerator RandomGenerator { get; set; }
} }

View file

@ -78,8 +78,8 @@ public class SaveDataFileSystemCreator : ISaveDataFileSystemCreator
using var saveDirFs = new SharedRef<DirectorySaveDataFileSystem>( using var saveDirFs = new SharedRef<DirectorySaveDataFileSystem>(
new DirectorySaveDataFileSystem(ref tempFs.Ref(), _fsServer.Hos.Fs)); new DirectorySaveDataFileSystem(ref tempFs.Ref(), _fsServer.Hos.Fs));
rc = saveDirFs.Get.Initialize(timeStampGetter, _randomGenerator, isJournalingSupported, rc = saveDirFs.Get.Initialize(isJournalingSupported, isMultiCommitSupported, !openReadOnly,
isMultiCommitSupported, !openReadOnly); timeStampGetter, _randomGenerator);
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
outFileSystem.SetByCopy(in saveDirFs); outFileSystem.SetByCopy(in saveDirFs);

View file

@ -77,7 +77,7 @@ public class SaveDataExtraDataAccessorCacheManager : ISaveDataExtraDataAccessorO
public Result Register(in SharedRef<ISaveDataExtraDataAccessor> accessor, SaveDataSpaceId spaceId, public Result Register(in SharedRef<ISaveDataExtraDataAccessor> accessor, SaveDataSpaceId spaceId,
ulong saveDataId) ulong saveDataId)
{ {
accessor.Get.RegisterCacheObserver(this, spaceId, saveDataId); accessor.Get.RegisterExtraDataAccessorObserver(this, spaceId, saveDataId);
var node = new LinkedListNode<Cache>(new Cache(in accessor, spaceId, saveDataId)); var node = new LinkedListNode<Cache>(new Cache(in accessor, spaceId, saveDataId));

View file

@ -170,7 +170,7 @@ public class SaveDataFileSystemServiceImpl
// Cache the extra data accessor if needed // Cache the extra data accessor if needed
if (cacheExtraData && extraDataAccessor.HasValue) if (cacheExtraData && extraDataAccessor.HasValue)
{ {
extraDataAccessor.Get.RegisterCacheObserver(_extraDataCacheManager, spaceId, saveDataId); extraDataAccessor.Get.RegisterExtraDataAccessorObserver(_extraDataCacheManager, spaceId, saveDataId);
rc = _extraDataCacheManager.Register(in extraDataAccessor, spaceId, saveDataId); rc = _extraDataCacheManager.Register(in extraDataAccessor, spaceId, saveDataId);
if (rc.IsFailure()) return rc.Miss(); if (rc.IsFailure()) return rc.Miss();
@ -359,7 +359,7 @@ public class SaveDataFileSystemServiceImpl
extraData.TimeStamp = 0; extraData.TimeStamp = 0;
extraData.CommitId = 0; extraData.CommitId = 0;
_config.GenerateRandomData(SpanHelpers.AsByteSpan(ref extraData.CommitId)).IgnoreResult(); _config.GenerateRandomData(SpanHelpers.AsByteSpan(ref extraData.CommitId));
extraData.Flags = creationInfo.Flags; extraData.Flags = creationInfo.Flags;
extraData.DataSize = creationInfo.Size; extraData.DataSize = creationInfo.Size;

View file

@ -83,7 +83,8 @@ public class ApplicationTemporaryFileSystem : IFileSystem, ISaveDataExtraDataAcc
throw new NotImplementedException(); throw new NotImplementedException();
} }
public void RegisterCacheObserver(ISaveDataExtraDataAccessorObserver observer, SaveDataSpaceId spaceId, ulong saveDataId) public void RegisterExtraDataAccessorObserver(ISaveDataExtraDataAccessorObserver observer, SaveDataSpaceId spaceId,
ulong saveDataId)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }

View file

@ -2,4 +2,4 @@
namespace LibHac.FsSystem; namespace LibHac.FsSystem;
public delegate Result RandomDataGenerator(Span<byte> buffer); public delegate void RandomDataGenerator(Span<byte> buffer);

View file

@ -14,7 +14,7 @@ internal struct DirectorySaveDataFileSystemGlobals
public void Initialize(FileSystemClient fsClient) public void Initialize(FileSystemClient fsClient)
{ {
SynchronizeDirectoryMutex.Initialize(); SynchronizeDirectoryMutex = new SdkMutexType();
} }
} }
@ -24,9 +24,9 @@ internal struct DirectorySaveDataFileSystemGlobals
/// <remarks> /// <remarks>
/// Transactional commits should be atomic as long as the <see cref="IFileSystem.RenameDirectory"/> function of the /// Transactional commits should be atomic as long as the <see cref="IFileSystem.RenameDirectory"/> function of the
/// underlying <see cref="IFileSystem"/> is atomic. /// underlying <see cref="IFileSystem"/> is atomic.
/// <para>Based on FS 13.1.0 (nnSdk 13.4.0)</para> /// <para>Based on FS 14.1.0 (nnSdk 14.3.0)</para>
/// </remarks> /// </remarks>
public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccessor public class DirectorySaveDataFileSystem : ISaveDataFileSystem
{ {
private const int IdealWorkBufferSize = 0x100000; // 1 MiB private const int IdealWorkBufferSize = 0x100000; // 1 MiB
@ -35,7 +35,6 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess
private static ReadOnlySpan<byte> SynchronizingDirectoryName => new[] { (byte)'/', (byte)'_' }; private static ReadOnlySpan<byte> SynchronizingDirectoryName => new[] { (byte)'/', (byte)'_' };
private static ReadOnlySpan<byte> LockFileName => new[] { (byte)'/', (byte)'.', (byte)'l', (byte)'o', (byte)'c', (byte)'k' }; private static ReadOnlySpan<byte> LockFileName => new[] { (byte)'/', (byte)'.', (byte)'l', (byte)'o', (byte)'c', (byte)'k' };
private FileSystemClient _fsClient;
private IFileSystem _baseFs; private IFileSystem _baseFs;
private SdkMutexType _mutex; private SdkMutexType _mutex;
private UniqueRef<IFileSystem> _uniqueBaseFs; private UniqueRef<IFileSystem> _uniqueBaseFs;
@ -45,16 +44,17 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess
private bool _isMultiCommitSupported; private bool _isMultiCommitSupported;
private bool _isJournalingEnabled; private bool _isJournalingEnabled;
// Additions to support extra data private ISaveDataExtraDataAccessorObserver _cacheObserver;
private ulong _saveDataId;
private SaveDataSpaceId _spaceId;
private ISaveDataCommitTimeStampGetter _timeStampGetter; private ISaveDataCommitTimeStampGetter _timeStampGetter;
private RandomDataGenerator _randomGenerator; private RandomDataGenerator _randomGenerator;
// Additions to support caching // LibHac additions
private ISaveDataExtraDataAccessorObserver _cacheObserver; private FileSystemClient _fsClient;
private SaveDataSpaceId _spaceId;
private ulong _saveDataId;
// Additions to ensure only one directory save data fs is opened at a time // Addition to ensure only one directory save data fs is opened at a time
private UniqueRef<IFile> _lockFile; private UniqueRef<IFile> _lockFile;
private class DirectorySaveDataFile : IFile private class DirectorySaveDataFile : IFile
@ -116,27 +116,6 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess
} }
} }
/// <summary>
/// Create an uninitialized <see cref="DirectorySaveDataFileSystem"/>.
/// </summary>
/// <param name="baseFileSystem">The base <see cref="IFileSystem"/> to use.</param>
public DirectorySaveDataFileSystem(IFileSystem baseFileSystem)
{
_baseFs = baseFileSystem;
_mutex.Initialize();
}
/// <summary>
/// Create an uninitialized <see cref="DirectorySaveDataFileSystem"/>.
/// </summary>
/// <param name="baseFileSystem">The base <see cref="IFileSystem"/> to use.</param>
public DirectorySaveDataFileSystem(ref UniqueRef<IFileSystem> baseFileSystem)
{
_baseFs = baseFileSystem.Get;
_mutex.Initialize();
_uniqueBaseFs = new UniqueRef<IFileSystem>(ref baseFileSystem);
}
/// <summary> /// <summary>
/// Create an uninitialized <see cref="DirectorySaveDataFileSystem"/>. /// Create an uninitialized <see cref="DirectorySaveDataFileSystem"/>.
/// If a <see cref="FileSystemClient"/> is provided a global mutex will be used when synchronizing directories. /// If a <see cref="FileSystemClient"/> is provided a global mutex will be used when synchronizing directories.
@ -145,10 +124,10 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess
/// </summary> /// </summary>
/// <param name="baseFileSystem">The base <see cref="IFileSystem"/> to use.</param> /// <param name="baseFileSystem">The base <see cref="IFileSystem"/> to use.</param>
/// <param name="fsClient">The <see cref="FileSystemClient"/> to use. May be null.</param> /// <param name="fsClient">The <see cref="FileSystemClient"/> to use. May be null.</param>
public DirectorySaveDataFileSystem(IFileSystem baseFileSystem, FileSystemClient fsClient) public DirectorySaveDataFileSystem(IFileSystem baseFileSystem, FileSystemClient fsClient = null)
{ {
_baseFs = baseFileSystem; _baseFs = baseFileSystem;
_mutex.Initialize(); _mutex = new SdkMutexType();
_fsClient = fsClient; _fsClient = fsClient;
} }
@ -160,10 +139,10 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess
/// </summary> /// </summary>
/// <param name="baseFileSystem">The base <see cref="IFileSystem"/> to use.</param> /// <param name="baseFileSystem">The base <see cref="IFileSystem"/> to use.</param>
/// <param name="fsClient">The <see cref="FileSystemClient"/> to use. May be null.</param> /// <param name="fsClient">The <see cref="FileSystemClient"/> to use. May be null.</param>
public DirectorySaveDataFileSystem(ref UniqueRef<IFileSystem> baseFileSystem, FileSystemClient fsClient) public DirectorySaveDataFileSystem(ref UniqueRef<IFileSystem> baseFileSystem, FileSystemClient fsClient = null)
{ {
_baseFs = baseFileSystem.Get; _baseFs = baseFileSystem.Get;
_mutex.Initialize(); _mutex = new SdkMutexType();
_uniqueBaseFs = new UniqueRef<IFileSystem>(ref baseFileSystem); _uniqueBaseFs = new UniqueRef<IFileSystem>(ref baseFileSystem);
_fsClient = fsClient; _fsClient = fsClient;
} }
@ -185,6 +164,14 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess
public Path ModifiedPath; public Path ModifiedPath;
public Path SynchronizingPath; public Path SynchronizingPath;
public RetryClosure(DirectorySaveDataFileSystem fs)
{
This = fs;
CommittedPath = new Path();
ModifiedPath = new Path();
SynchronizingPath = new Path();
}
public void Dispose() public void Dispose()
{ {
CommittedPath.Dispose(); CommittedPath.Dispose();
@ -195,7 +182,7 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess
private delegate Result RetryDelegate(in RetryClosure closure); private delegate Result RetryDelegate(in RetryClosure closure);
private Result RetryFinitelyForTargetLocked(RetryDelegate function, in RetryClosure closure) private Result RetryFinitelyForTargetLocked(in RetryClosure closure, RetryDelegate function)
{ {
const int maxRetryCount = 10; const int maxRetryCount = 10;
const int retryWaitTimeMs = 100; const int retryWaitTimeMs = 100;
@ -228,22 +215,17 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess
} }
} }
public Result Initialize(bool isJournalingSupported, bool isMultiCommitSupported, bool isJournalingEnabled) public Result Initialize(bool isJournalingSupported, bool isMultiCommitSupported,
{ bool isJournalingEnabled, ISaveDataCommitTimeStampGetter timeStampGetter, RandomDataGenerator randomGenerator)
return Initialize(null, null, isJournalingSupported, isMultiCommitSupported, isJournalingEnabled);
}
public Result Initialize(ISaveDataCommitTimeStampGetter timeStampGetter, RandomDataGenerator randomGenerator,
bool isJournalingSupported, bool isMultiCommitSupported, bool isJournalingEnabled)
{ {
_isJournalingSupported = isJournalingSupported; _isJournalingSupported = isJournalingSupported;
_isMultiCommitSupported = isMultiCommitSupported; _isMultiCommitSupported = isMultiCommitSupported;
_isJournalingEnabled = isJournalingEnabled; _isJournalingEnabled = isJournalingEnabled;
_timeStampGetter = timeStampGetter ?? _timeStampGetter; _timeStampGetter = timeStampGetter;
_randomGenerator = randomGenerator ?? _randomGenerator; _randomGenerator = randomGenerator;
// Open the lock file // Open the lock file
Result rc = GetFileSystemLock(); Result rc = AcquireLockFile();
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
using var pathModifiedDirectory = new Path(); using var pathModifiedDirectory = new Path();
@ -273,8 +255,8 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess
{ {
rc = _baseFs.CreateDirectory(in pathCommittedDirectory); rc = _baseFs.CreateDirectory(in pathCommittedDirectory);
// Nintendo returns on all failures, but we'll keep going if committed already exists // Changed: Nintendo returns on all failures, but we'll keep going if committed already
// to avoid confusing people manually creating savedata in emulators // exists to avoid confusing people manually creating savedata in emulators
if (rc.IsFailure() && !ResultFs.PathAlreadyExists.Includes(rc)) if (rc.IsFailure() && !ResultFs.PathAlreadyExists.Includes(rc))
return rc; return rc;
} }
@ -316,7 +298,7 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess
return Result.Success; return Result.Success;
} }
private Result GetFileSystemLock() private Result AcquireLockFile()
{ {
// Having an open lock file means we already have the lock for the file system. // Having an open lock file means we already have the lock for the file system.
if (_lockFile.HasValue) if (_lockFile.HasValue)
@ -326,7 +308,8 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess
Result rc = PathFunctions.SetUpFixedPath(ref pathLockFile.Ref(), LockFileName); Result rc = PathFunctions.SetUpFixedPath(ref pathLockFile.Ref(), LockFileName);
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
rc = _baseFs.OpenFile(ref _lockFile, in pathLockFile, OpenMode.ReadWrite); using var lockFile = new UniqueRef<IFile>();
rc = _baseFs.OpenFile(ref lockFile.Ref(), in pathLockFile, OpenMode.ReadWrite);
if (rc.IsFailure()) if (rc.IsFailure())
{ {
@ -335,15 +318,16 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess
rc = _baseFs.CreateFile(in pathLockFile, 0); rc = _baseFs.CreateFile(in pathLockFile, 0);
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
rc = _baseFs.OpenFile(ref _lockFile, in pathLockFile, OpenMode.ReadWrite); rc = _baseFs.OpenFile(ref lockFile.Ref(), in pathLockFile, OpenMode.ReadWrite);
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
} }
else else
{ {
return rc; return rc.Miss();
} }
} }
_lockFile.Set(ref lockFile.Ref());
return Result.Success; return Result.Success;
} }
@ -552,8 +536,8 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess
// Delete destination dir and recreate it. // Delete destination dir and recreate it.
Result rc = _baseFs.DeleteDirectoryRecursively(destPath); Result rc = _baseFs.DeleteDirectoryRecursively(destPath);
// Nintendo returns all errors unconditionally because SynchronizeDirectory is always called in situations // Changed: Nintendo returns all errors unconditionally because SynchronizeDirectory is always called
// where a PathNotFound error would mean the save directory was in an invalid state. // in situations where a PathNotFound error would mean the save directory was in an invalid state.
// We'll ignore PathNotFound errors to be more user-friendly to users who might accidentally // We'll ignore PathNotFound errors to be more user-friendly to users who might accidentally
// put the save directory in an invalid state. // put the save directory in an invalid state.
if (rc.IsFailure() && !ResultFs.PathNotFound.Includes(rc)) return rc; if (rc.IsFailure() && !ResultFs.PathNotFound.Includes(rc)) return rc;
@ -563,7 +547,7 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess
var directoryEntry = new DirectoryEntry(); var directoryEntry = new DirectoryEntry();
// Lock only if initialized with a client // Changed: Lock only if initialized with a client
if (_fsClient is not null) if (_fsClient is not null)
{ {
using ScopedLock<SdkMutexType> scopedLock = using ScopedLock<SdkMutexType> scopedLock =
@ -585,63 +569,63 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess
} }
} }
protected override Result DoCommit() private Result DoCommit(bool updateTimeStamp)
{ {
using ScopedLock<SdkMutexType> lk = ScopedLock.Lock(ref _mutex); using ScopedLock<SdkMutexType> scopedLock = ScopedLock.Lock(ref _mutex);
if (!_isJournalingEnabled || !_isJournalingSupported) if (!_isJournalingEnabled || !_isJournalingSupported)
return Result.Success; return Result.Success;
var closure = new RetryClosure(); using var closure = new RetryClosure(this);
closure.This = this;
Result rc = PathFunctions.SetUpFixedPath(ref closure.ModifiedPath, ModifiedDirectoryName); Result rc = PathFunctions.SetUpFixedPath(ref closure.ModifiedPath.Ref(), ModifiedDirectoryName);
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
rc = PathFunctions.SetUpFixedPath(ref closure.CommittedPath, CommittedDirectoryName); rc = PathFunctions.SetUpFixedPath(ref closure.CommittedPath.Ref(), CommittedDirectoryName);
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
rc = PathFunctions.SetUpFixedPath(ref closure.SynchronizingPath, SynchronizingDirectoryName); rc = PathFunctions.SetUpFixedPath(ref closure.SynchronizingPath.Ref(), SynchronizingDirectoryName);
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
if (_openWritableFileCount > 0)
{
// All files must be closed before commiting save data. // All files must be closed before commiting save data.
if (_openWritableFileCount > 0)
return ResultFs.WriteModeFileNotClosed.Log(); return ResultFs.WriteModeFileNotClosed.Log();
}
static Result RenameCommittedDir(in RetryClosure closure)
{
return closure.This._baseFs.RenameDirectory(in closure.CommittedPath,
in closure.SynchronizingPath);
}
static Result SynchronizeWorkingDir(in RetryClosure closure)
{
return closure.This.SynchronizeDirectory(in closure.SynchronizingPath,
in closure.ModifiedPath);
}
static Result RenameSynchronizingDir(in RetryClosure closure)
{
return closure.This._baseFs.RenameDirectory(in closure.SynchronizingPath,
in closure.CommittedPath);
}
// Get rid of the previous commit by renaming the folder. // Get rid of the previous commit by renaming the folder.
rc = RetryFinitelyForTargetLocked(RenameCommittedDir, in closure); rc = RetryFinitelyForTargetLocked(in closure,
(in RetryClosure c) => c.This._baseFs.RenameDirectory(in c.CommittedPath, in c.SynchronizingPath));
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
// If something goes wrong beyond this point, the commit will be // If something goes wrong beyond this point, the commit of the main data
// completed the next time the savedata is opened. // will be completed the next time the savedata is opened.
rc = RetryFinitelyForTargetLocked(SynchronizeWorkingDir, in closure); if (updateTimeStamp && _timeStampGetter is not null)
{
Assert.SdkNotNull(_randomGenerator);
rc = UpdateExtraDataTimeStamp();
if (rc.IsFailure()) return rc.Miss();
}
rc = CommitExtraDataImpl();
if (rc.IsFailure()) return rc.Miss();
rc = RetryFinitelyForTargetLocked(in closure,
(in RetryClosure c) => c.This.SynchronizeDirectory(in c.SynchronizingPath, in c.ModifiedPath));
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
rc = RetryFinitelyForTargetLocked(RenameSynchronizingDir, in closure); rc = RetryFinitelyForTargetLocked(in closure,
(in RetryClosure c) => c.This._baseFs.RenameDirectory(in c.SynchronizingPath, in c.CommittedPath));
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
closure.Dispose(); return Result.Success;
}
protected override Result DoCommit()
{
Result rc = DoCommit(updateTimeStamp: true);
if (rc.IsFailure()) return rc.Miss();
return Result.Success; return Result.Success;
} }
@ -655,11 +639,15 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess
protected override Result DoRollback() protected override Result DoRollback()
{ {
// No old data is kept for non-journaling save data, so there's nothing to rollback to // No old data is kept for non-journaling save data, so there's nothing to rollback to in that case
if (!_isJournalingSupported) if (_isJournalingSupported)
return Result.Success; {
Result rc = Initialize(_isJournalingSupported, _isMultiCommitSupported, _isJournalingEnabled,
_timeStampGetter, _randomGenerator);
if (rc.IsFailure()) return rc.Miss();
}
return Initialize(_isJournalingSupported, _isMultiCommitSupported, _isJournalingEnabled); return Result.Success;
} }
protected override Result DoGetFreeSpaceSize(out long freeSpace, in Path path) protected override Result DoGetFreeSpaceSize(out long freeSpace, in Path path)
@ -694,6 +682,46 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess
return Result.Success; return Result.Success;
} }
public override bool IsSaveDataFileSystemCacheEnabled()
{
return false;
}
public override Result RollbackOnlyModified()
{
return ResultFs.UnsupportedRollbackOnlyModifiedForDirectorySaveDataFileSystem.Log();
}
public override Result WriteExtraData(in SaveDataExtraData extraData)
{
using ScopedLock<SdkMutexType> scopedLock = ScopedLock.Lock(ref _mutex);
return WriteExtraDataImpl(in extraData);
}
public override Result CommitExtraData(bool updateTimeStamp)
{
Result rc = DoCommit(updateTimeStamp);
if (rc.IsFailure()) return rc.Miss();
return Result.Success;
}
public override Result ReadExtraData(out SaveDataExtraData extraData)
{
using ScopedLock<SdkMutexType> scopedLock = ScopedLock.Lock(ref _mutex);
return ReadExtraDataImpl(out extraData);
}
public override void RegisterExtraDataAccessorObserver(ISaveDataExtraDataAccessorObserver observer,
SaveDataSpaceId spaceId, ulong saveDataId)
{
_cacheObserver = observer;
_spaceId = spaceId;
_saveDataId = saveDataId;
}
private void DecrementWriteOpenFileCount() private void DecrementWriteOpenFileCount()
{ {
// Todo?: Calling OpenFile when outFile already contains a DirectorySaveDataFile // Todo?: Calling OpenFile when outFile already contains a DirectorySaveDataFile
@ -703,7 +731,8 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess
_openWritableFileCount--; _openWritableFileCount--;
} }
// The original class doesn't support extra data. // The original class doesn't support transactional extra data,
// always writing the extra data directly to the /extradata file.
// Everything below this point is a LibHac extension. // Everything below this point is a LibHac extension.
private static ReadOnlySpan<byte> CommittedExtraDataName => // "/ExtraData0" private static ReadOnlySpan<byte> CommittedExtraDataName => // "/ExtraData0"
@ -860,6 +889,15 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess
return Result.Success; return Result.Success;
} }
private Result GetExtraDataPath(ref Path path)
{
ReadOnlySpan<byte> extraDataName = _isJournalingSupported && !_isJournalingEnabled
? CommittedExtraDataName
: ModifiedExtraDataName;
return PathFunctions.SetUpFixedPath(ref path, extraDataName);
}
private Result EnsureExtraDataSize(in Path path) private Result EnsureExtraDataSize(in Path path)
{ {
using var file = new UniqueRef<IFile>(); using var file = new UniqueRef<IFile>();
@ -902,42 +940,6 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess
return Result.Success; return Result.Success;
} }
private Result GetExtraDataPath(ref Path path)
{
ReadOnlySpan<byte> extraDataName = _isJournalingSupported && !_isJournalingEnabled
? CommittedExtraDataName
: ModifiedExtraDataName;
return PathFunctions.SetUpFixedPath(ref path, extraDataName);
}
public Result WriteExtraData(in SaveDataExtraData extraData)
{
using ScopedLock<SdkMutexType> scopedLock = ScopedLock.Lock(ref _mutex);
return WriteExtraDataImpl(in extraData);
}
public Result CommitExtraData(bool updateTimeStamp)
{
using ScopedLock<SdkMutexType> scopedLock = ScopedLock.Lock(ref _mutex);
if (updateTimeStamp && _timeStampGetter is not null && _randomGenerator is not null)
{
Result rc = UpdateExtraDataTimeStamp();
if (rc.IsFailure()) return rc;
}
return CommitExtraDataImpl();
}
public Result ReadExtraData(out SaveDataExtraData extraData)
{
using ScopedLock<SdkMutexType> scopedLock = ScopedLock.Lock(ref _mutex);
return ReadExtraDataImpl(out extraData);
}
private Result UpdateExtraDataTimeStamp() private Result UpdateExtraDataTimeStamp()
{ {
Assert.SdkRequires(_mutex.IsLockedByCurrentThread()); Assert.SdkRequires(_mutex.IsLockedByCurrentThread());
@ -987,50 +989,33 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess
if (!_isJournalingSupported || !_isJournalingEnabled) if (!_isJournalingSupported || !_isJournalingEnabled)
return Result.Success; return Result.Success;
var closure = new RetryClosure(); using var closure = new RetryClosure(this);
closure.This = this;
Result rc = PathFunctions.SetUpFixedPath(ref closure.ModifiedPath, ModifiedExtraDataName); Result rc = PathFunctions.SetUpFixedPath(ref closure.ModifiedPath.Ref(), ModifiedExtraDataName);
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
rc = PathFunctions.SetUpFixedPath(ref closure.CommittedPath, CommittedExtraDataName); rc = PathFunctions.SetUpFixedPath(ref closure.CommittedPath.Ref(), CommittedExtraDataName);
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
rc = PathFunctions.SetUpFixedPath(ref closure.SynchronizingPath, SynchronizingExtraDataName); rc = PathFunctions.SetUpFixedPath(ref closure.SynchronizingPath.Ref(), SynchronizingExtraDataName);
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
static Result RenameCommittedFile(in RetryClosure closure)
{
return closure.This._baseFs.RenameFile(in closure.CommittedPath,
in closure.SynchronizingPath);
}
static Result SynchronizeWorkingFile(in RetryClosure closure)
{
return closure.This.SynchronizeExtraData(in closure.SynchronizingPath,
in closure.ModifiedPath);
}
static Result RenameSynchronizingFile(in RetryClosure closure)
{
return closure.This._baseFs.RenameFile(in closure.SynchronizingPath,
in closure.CommittedPath);
}
// Get rid of the previous commit by renaming the file. // Get rid of the previous commit by renaming the file.
rc = RetryFinitelyForTargetLocked(RenameCommittedFile, in closure); rc = RetryFinitelyForTargetLocked(in closure,
(in RetryClosure c) => c.This._baseFs.RenameFile(in c.CommittedPath, in c.SynchronizingPath));
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
// If something goes wrong beyond this point, the commit will be // If something goes wrong beyond this point, the commit will be
// completed the next time the savedata is opened. // completed the next time the savedata is opened.
rc = RetryFinitelyForTargetLocked(SynchronizeWorkingFile, in closure); rc = RetryFinitelyForTargetLocked(in closure,
(in RetryClosure c) => c.This.SynchronizeExtraData(in c.SynchronizingPath, in c.ModifiedPath));
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
rc = RetryFinitelyForTargetLocked(RenameSynchronizingFile, in closure); rc = RetryFinitelyForTargetLocked(in closure,
(in RetryClosure c) => c.This._baseFs.RenameFile(in c.SynchronizingPath, in c.CommittedPath));
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
closure.Dispose();
return Result.Success; return Result.Success;
} }
@ -1056,14 +1041,6 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess
return Result.Success; return Result.Success;
} }
public void RegisterCacheObserver(ISaveDataExtraDataAccessorObserver observer, SaveDataSpaceId spaceId,
ulong saveDataId)
{
_cacheObserver = observer;
_spaceId = spaceId;
_saveDataId = saveDataId;
}
public SaveDataSpaceId GetSaveDataSpaceId() => _spaceId; public SaveDataSpaceId GetSaveDataSpaceId() => _spaceId;
public ulong GetSaveDataId() => _saveDataId; public ulong GetSaveDataId() => _saveDataId;
} }

View file

@ -6,11 +6,11 @@ namespace LibHac.FsSystem;
/// <summary> /// <summary>
/// Provides read/write access to a save data file system's extra data. /// Provides read/write access to a save data file system's extra data.
/// </summary> /// </summary>
/// <remarks>Based on FS 13.1.0 (nnSdk 13.4.0)</remarks> /// <remarks>Based on FS 14.1.0 (nnSdk 14.3.0)</remarks>
public interface ISaveDataExtraDataAccessor : IDisposable public interface ISaveDataExtraDataAccessor : IDisposable
{ {
Result WriteExtraData(in SaveDataExtraData extraData); Result WriteExtraData(in SaveDataExtraData extraData);
Result CommitExtraData(bool updateTimeStamp); Result CommitExtraData(bool updateTimeStamp);
Result ReadExtraData(out SaveDataExtraData extraData); Result ReadExtraData(out SaveDataExtraData extraData);
void RegisterCacheObserver(ISaveDataExtraDataAccessorObserver observer, SaveDataSpaceId spaceId, ulong saveDataId); void RegisterExtraDataAccessorObserver(ISaveDataExtraDataAccessorObserver observer, SaveDataSpaceId spaceId, ulong saveDataId);
} }

View file

@ -12,7 +12,7 @@ public abstract class ISaveDataFileSystem : IFileSystem, ICacheableSaveDataFileS
public abstract Result WriteExtraData(in SaveDataExtraData extraData); public abstract Result WriteExtraData(in SaveDataExtraData extraData);
public abstract Result CommitExtraData(bool updateTimeStamp); public abstract Result CommitExtraData(bool updateTimeStamp);
public abstract Result ReadExtraData(out SaveDataExtraData extraData); public abstract Result ReadExtraData(out SaveDataExtraData extraData);
public abstract void RegisterCacheObserver(ISaveDataExtraDataAccessorObserver observer, SaveDataSpaceId spaceId, ulong saveDataId); public abstract void RegisterExtraDataAccessorObserver(ISaveDataExtraDataAccessorObserver observer, SaveDataSpaceId spaceId, ulong saveDataId);
} }
public interface ICacheableSaveDataFileSystem public interface ICacheableSaveDataFileSystem

View file

@ -1,7 +1,9 @@
using LibHac.Bcat; using System;
using LibHac.Bcat;
using LibHac.Common.Keys; using LibHac.Common.Keys;
using LibHac.Fs.Fsa; using LibHac.Fs.Fsa;
using LibHac.FsSrv; using LibHac.FsSrv;
using LibHac.FsSystem;
namespace LibHac; namespace LibHac;
@ -15,13 +17,17 @@ public static class HorizonFactory
HorizonClient fsServerClient = horizon.CreatePrivilegedHorizonClient(); HorizonClient fsServerClient = horizon.CreatePrivilegedHorizonClient();
var fsServer = new FileSystemServer(fsServerClient); var fsServer = new FileSystemServer(fsServerClient);
var defaultObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(rootFileSystem, keySet, fsServer); var random = new Random();
RandomDataGenerator randomGenerator = buffer => random.NextBytes(buffer);
var defaultObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(rootFileSystem, keySet, fsServer, randomGenerator);
var fsServerConfig = new FileSystemServerConfig var fsServerConfig = new FileSystemServerConfig
{ {
DeviceOperator = defaultObjects.DeviceOperator, DeviceOperator = defaultObjects.DeviceOperator,
ExternalKeySet = keySet.ExternalKeySet, ExternalKeySet = keySet.ExternalKeySet,
FsCreators = defaultObjects.FsCreators, FsCreators = defaultObjects.FsCreators,
RandomGenerator = randomGenerator
}; };
FileSystemServerInitializer.InitializeWithConfig(fsServerClient, fsServer, fsServerConfig); FileSystemServerInitializer.InitializeWithConfig(fsServerClient, fsServer, fsServerConfig);

View file

@ -2,6 +2,7 @@
using LibHac.Fs; using LibHac.Fs;
using LibHac.Fs.Fsa; using LibHac.Fs.Fsa;
using LibHac.FsSrv; using LibHac.FsSrv;
using LibHac.FsSystem;
using LibHac.Tools.Fs; using LibHac.Tools.Fs;
namespace LibHac.Tests.Fs.FileSystemClientTests; namespace LibHac.Tests.Fs.FileSystemClientTests;
@ -18,7 +19,10 @@ public static class FileSystemServerFactory
HorizonClient fsServerClient = horizon.CreatePrivilegedHorizonClient(); HorizonClient fsServerClient = horizon.CreatePrivilegedHorizonClient();
var fsServer = new FileSystemServer(fsServerClient); var fsServer = new FileSystemServer(fsServerClient);
var defaultObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(rootFs, keySet, fsServer); var random = new Random(12345);
RandomDataGenerator randomGenerator = buffer => random.NextBytes(buffer);
var defaultObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(rootFs, keySet, fsServer, randomGenerator);
defaultObjects.SdCard.SetSdCardInsertionStatus(sdCardInserted); defaultObjects.SdCard.SetSdCardInsertionStatus(sdCardInserted);
@ -26,6 +30,7 @@ public static class FileSystemServerFactory
config.FsCreators = defaultObjects.FsCreators; config.FsCreators = defaultObjects.FsCreators;
config.DeviceOperator = defaultObjects.DeviceOperator; config.DeviceOperator = defaultObjects.DeviceOperator;
config.ExternalKeySet = new ExternalKeySet(); config.ExternalKeySet = new ExternalKeySet();
config.RandomGenerator = randomGenerator;
FileSystemServerInitializer.InitializeWithConfig(fsServerClient, fsServer, config); FileSystemServerInitializer.InitializeWithConfig(fsServerClient, fsServer, config);
return horizon; return horizon;

View file

@ -48,8 +48,8 @@ public class DirectorySaveDataFileSystemTests : CommittableIFileSystemTests
FileSystemClient fsClient) FileSystemClient fsClient)
{ {
var obj = new DirectorySaveDataFileSystem(baseFileSystem, fsClient); var obj = new DirectorySaveDataFileSystem(baseFileSystem, fsClient);
Result rc = obj.Initialize(timeStampGetter, randomGenerator, isJournalingSupported, isMultiCommitSupported, Result rc = obj.Initialize(isJournalingSupported, isMultiCommitSupported, isJournalingEnabled, timeStampGetter,
isJournalingEnabled); randomGenerator);
if (rc.IsSuccess()) if (rc.IsSuccess())
{ {
@ -521,7 +521,7 @@ public class DirectorySaveDataFileSystemTests : CommittableIFileSystemTests
private int _index; private int _index;
public Result GenerateRandom(Span<byte> output) public void GenerateRandom(Span<byte> output)
{ {
if (output.Length != 8) if (output.Length != 8)
throw new ArgumentException(); throw new ArgumentException();
@ -529,7 +529,6 @@ public class DirectorySaveDataFileSystemTests : CommittableIFileSystemTests
Unsafe.As<byte, long>(ref MemoryMarshal.GetReference(output)) = Values[_index]; Unsafe.As<byte, long>(ref MemoryMarshal.GetReference(output)) = Values[_index];
_index = (_index + 1) % Values.Length; _index = (_index + 1) % Values.Length;
return Result.Success;
} }
} }

View file

@ -2,6 +2,7 @@
using LibHac.Common.Keys; using LibHac.Common.Keys;
using LibHac.Fs.Fsa; using LibHac.Fs.Fsa;
using LibHac.FsSrv; using LibHac.FsSrv;
using LibHac.FsSystem;
using LibHac.Tools.Fs; using LibHac.Tools.Fs;
namespace LibHac.Tests; namespace LibHac.Tests;
@ -18,12 +19,16 @@ public static class HorizonFactory
HorizonClient fsServerClient = horizon.CreatePrivilegedHorizonClient(); HorizonClient fsServerClient = horizon.CreatePrivilegedHorizonClient();
var fsServer = new FileSystemServer(fsServerClient); var fsServer = new FileSystemServer(fsServerClient);
var defaultObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(rootFs, keySet, fsServer); var random = new Random(12345);
RandomDataGenerator randomGenerator = buffer => random.NextBytes(buffer);
var defaultObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(rootFs, keySet, fsServer, randomGenerator);
var config = new FileSystemServerConfig(); var config = new FileSystemServerConfig();
config.FsCreators = defaultObjects.FsCreators; config.FsCreators = defaultObjects.FsCreators;
config.DeviceOperator = defaultObjects.DeviceOperator; config.DeviceOperator = defaultObjects.DeviceOperator;
config.ExternalKeySet = new ExternalKeySet(); config.ExternalKeySet = new ExternalKeySet();
config.RandomGenerator = randomGenerator;
FileSystemServerInitializer.InitializeWithConfig(fsServerClient, fsServer, config); FileSystemServerInitializer.InitializeWithConfig(fsServerClient, fsServer, config);