Update DirectorySaveDataFileSystem to 11.0.0

This commit is contained in:
Alex Barney 2021-04-15 12:50:54 -07:00
parent 4ea2896b72
commit bb2c870f27
6 changed files with 323 additions and 271 deletions

View file

@ -1,5 +1,6 @@
using LibHac.Fs.Fsa; using LibHac.Fs.Fsa;
using LibHac.Fs.Shim; using LibHac.Fs.Shim;
using LibHac.FsSystem;
namespace LibHac.Fs namespace LibHac.Fs
{ {
@ -26,6 +27,7 @@ namespace LibHac.Fs
public FsContextHandlerGlobals FsContextHandler; public FsContextHandlerGlobals FsContextHandler;
public ResultHandlingUtilityGlobals ResultHandlingUtility; public ResultHandlingUtilityGlobals ResultHandlingUtility;
public PathUtilityGlobals PathUtility; public PathUtilityGlobals PathUtility;
public DirectorySaveDataFileSystemGlobals DirectorySaveDataFileSystem;
public void Initialize(FileSystemClient fsClient, HorizonClient horizonClient) public void Initialize(FileSystemClient fsClient, HorizonClient horizonClient)
{ {
@ -35,6 +37,7 @@ namespace LibHac.Fs
UserMountTable.Initialize(fsClient); UserMountTable.Initialize(fsClient);
FsContextHandler.Initialize(fsClient); FsContextHandler.Initialize(fsClient);
PathUtility.Initialize(fsClient); PathUtility.Initialize(fsClient);
DirectorySaveDataFileSystem.Initialize(fsClient);
} }
} }

View file

@ -50,7 +50,7 @@ namespace LibHac.FsSrv.FsCreator
bool isUserSaveData = type == SaveDataType.Account || type == SaveDataType.Device; bool isUserSaveData = type == SaveDataType.Account || type == SaveDataType.Device;
rc = DirectorySaveDataFileSystem.CreateNew(out DirectorySaveDataFileSystem saveFs, subDirFs, rc = DirectorySaveDataFileSystem.CreateNew(out DirectorySaveDataFileSystem saveFs, subDirFs,
isPersistentSaveData, isUserSaveData); isPersistentSaveData, isUserSaveData, true);
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
fileSystem = saveFs; fileSystem = saveFs;

View file

@ -1,61 +0,0 @@
using System;
using LibHac.Fs;
using LibHac.Fs.Fsa;
namespace LibHac.FsSystem
{
public class DirectorySaveDataFile : IFile
{
private IFile BaseFile { get; }
private DirectorySaveDataFileSystem ParentFs { get; }
private OpenMode Mode { get; }
public DirectorySaveDataFile(DirectorySaveDataFileSystem parentFs, IFile baseFile, OpenMode mode)
{
ParentFs = parentFs;
BaseFile = baseFile;
Mode = mode;
}
protected override Result DoRead(out long bytesRead, long offset, Span<byte> destination,
in ReadOption option)
{
return BaseFile.Read(out bytesRead, offset, destination, in option);
}
protected override Result DoWrite(long offset, ReadOnlySpan<byte> source, in WriteOption option)
{
return BaseFile.Write(offset, source, in option);
}
protected override Result DoFlush()
{
return BaseFile.Flush();
}
protected override Result DoGetSize(out long size)
{
return BaseFile.GetSize(out size);
}
protected override Result DoSetSize(long size)
{
return BaseFile.SetSize(size);
}
protected override Result DoOperateRange(Span<byte> outBuffer, OperationId operationId, long offset, long size, ReadOnlySpan<byte> inBuffer)
{
return BaseFile.OperateRange(outBuffer, operationId, offset, size, inBuffer);
}
protected override void Dispose(bool disposing)
{
if (Mode.HasFlag(OpenMode.Write))
{
ParentFs.NotifyCloseWritableFile();
}
BaseFile?.Dispose();
}
}
}

View file

@ -3,17 +3,28 @@ using System.Runtime.CompilerServices;
using LibHac.Common; using LibHac.Common;
using LibHac.Fs; using LibHac.Fs;
using LibHac.Fs.Fsa; using LibHac.Fs.Fsa;
using LibHac.Os;
using LibHac.Util; using LibHac.Util;
namespace LibHac.FsSystem namespace LibHac.FsSystem
{ {
internal struct DirectorySaveDataFileSystemGlobals
{
public SdkMutexType SynchronizeDirectoryMutex;
public void Initialize(FileSystemClient fsClient)
{
SynchronizeDirectoryMutex.Initialize();
}
}
/// <summary> /// <summary>
/// An <see cref="IFileSystem"/> that provides transactional commits for savedata on top of another base IFileSystem. /// An <see cref="IFileSystem"/> that provides transactional commits for savedata on top of another base IFileSystem.
/// </summary> /// </summary>
/// <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.
/// <br/>Based on FS 10.0.0 (nnSdk 10.4.0) /// <br/>Based on FS 11.0.0 (nnSdk 11.4.0)
/// </remarks> /// </remarks>
public class DirectorySaveDataFileSystem : IFileSystem public class DirectorySaveDataFileSystem : IFileSystem
{ {
@ -27,17 +38,82 @@ namespace LibHac.FsSystem
private U8Span WorkingDirectoryPath => new U8Span(WorkingDirectoryBytes); private U8Span WorkingDirectoryPath => new U8Span(WorkingDirectoryBytes);
private U8Span SynchronizingDirectoryPath => new U8Span(SynchronizingDirectoryBytes); private U8Span SynchronizingDirectoryPath => new U8Span(SynchronizingDirectoryBytes);
private IFileSystem BaseFs { get; } private FileSystemClient _fsClient;
private object Locker { get; } = new object(); private IFileSystem _baseFs;
private int OpenWritableFileCount { get; set; } private SdkMutexType _mutex;
private bool IsPersistentSaveData { get; set; } // Todo: Unique file system for disposal
private bool CanCommitProvisionally { get; set; } private int _openWritableFileCount;
private bool _isPersistentSaveData;
private bool _canCommitProvisionally;
private bool _useTransactions;
private class DirectorySaveDataFile : IFile
{
private IFile _baseFile;
private DirectorySaveDataFileSystem _parentFs;
private OpenMode _mode;
public DirectorySaveDataFile(DirectorySaveDataFileSystem parentFs, IFile baseFile, OpenMode mode)
{
_parentFs = parentFs;
_baseFile = baseFile;
_mode = mode;
}
protected override Result DoRead(out long bytesRead, long offset, Span<byte> destination,
in ReadOption option)
{
return _baseFile.Read(out bytesRead, offset, destination, in option);
}
protected override Result DoWrite(long offset, ReadOnlySpan<byte> source, in WriteOption option)
{
return _baseFile.Write(offset, source, in option);
}
protected override Result DoFlush()
{
return _baseFile.Flush();
}
protected override Result DoGetSize(out long size)
{
return _baseFile.GetSize(out size);
}
protected override Result DoSetSize(long size)
{
return _baseFile.SetSize(size);
}
protected override Result DoOperateRange(Span<byte> outBuffer, OperationId operationId, long offset, long size, ReadOnlySpan<byte> inBuffer)
{
return _baseFile.OperateRange(outBuffer, operationId, offset, size, inBuffer);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
_baseFile?.Dispose();
if (_mode.HasFlag(OpenMode.Write))
{
_parentFs.DecrementWriteOpenFileCount();
_mode = default;
}
}
base.Dispose(disposing);
}
}
public static Result CreateNew(out DirectorySaveDataFileSystem created, IFileSystem baseFileSystem, public static Result CreateNew(out DirectorySaveDataFileSystem created, IFileSystem baseFileSystem,
bool isPersistentSaveData, bool canCommitProvisionally) bool isPersistentSaveData, bool canCommitProvisionally, bool useTransactions,
FileSystemClient fsClient = null)
{ {
var obj = new DirectorySaveDataFileSystem(baseFileSystem); var obj = new DirectorySaveDataFileSystem(baseFileSystem, fsClient);
Result rc = obj.Initialize(isPersistentSaveData, canCommitProvisionally); Result rc = obj.Initialize(isPersistentSaveData, canCommitProvisionally, useTransactions);
if (rc.IsSuccess()) if (rc.IsSuccess())
{ {
@ -50,29 +126,58 @@ namespace LibHac.FsSystem
return rc; return rc;
} }
private DirectorySaveDataFileSystem(IFileSystem baseFileSystem) /// <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; _baseFs = baseFileSystem;
_mutex.Initialize();
} }
private Result Initialize(bool isPersistentSaveData, bool canCommitProvisionally) /// <summary>
/// Create an uninitialized <see cref="DirectorySaveDataFileSystem"/>.
/// If a <see cref="FileSystemClient"/> is provided a global mutex will be used when synchronizing directories.
/// Running outside of a Horizon context doesn't require this mutex,
/// and null can be passed to <paramref name="fsClient"/>.
/// </summary>
/// <param name="baseFileSystem">The base <see cref="IFileSystem"/> to use.</param>
/// <param name="fsClient">The <see cref="FileSystemClient"/> to use. May be null.</param>
public DirectorySaveDataFileSystem(IFileSystem baseFileSystem, FileSystemClient fsClient)
{ {
IsPersistentSaveData = isPersistentSaveData; _baseFs = baseFileSystem;
CanCommitProvisionally = canCommitProvisionally; _mutex.Initialize();
_fsClient = fsClient;
}
protected override void Dispose(bool disposing)
{
_baseFs?.Dispose();
base.Dispose(disposing);
}
private Result Initialize(bool isPersistentSaveData, bool canCommitProvisionally, bool useTransactions)
{
_isPersistentSaveData = isPersistentSaveData;
_canCommitProvisionally = canCommitProvisionally;
_useTransactions = useTransactions;
// Ensure the working directory exists // Ensure the working directory exists
Result rc = BaseFs.GetEntryType(out _, WorkingDirectoryPath); Result rc = _baseFs.GetEntryType(out _, WorkingDirectoryPath);
if (rc.IsFailure()) if (rc.IsFailure())
{ {
if (!ResultFs.PathNotFound.Includes(rc)) return rc; if (!ResultFs.PathNotFound.Includes(rc)) return rc;
rc = BaseFs.CreateDirectory(WorkingDirectoryPath); rc = _baseFs.CreateDirectory(WorkingDirectoryPath);
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
if (!IsPersistentSaveData) return Result.Success; if (_isPersistentSaveData)
{
rc = BaseFs.CreateDirectory(CommittedDirectoryPath); rc = _baseFs.CreateDirectory(CommittedDirectoryPath);
if (rc.IsFailure()) return rc;
}
// Nintendo returns on all failures, but we'll keep going if committed already exists // Nintendo returns on all failures, but we'll keep going if committed already exists
// to avoid confusing people manually creating savedata in emulators // to avoid confusing people manually creating savedata in emulators
@ -80,12 +185,16 @@ namespace LibHac.FsSystem
} }
// Only the working directory is needed for temporary savedata // Only the working directory is needed for temporary savedata
if (!IsPersistentSaveData) return Result.Success; if (!_isPersistentSaveData)
return Result.Success;
rc = BaseFs.GetEntryType(out _, CommittedDirectoryPath); rc = _baseFs.GetEntryType(out _, CommittedDirectoryPath);
if (rc.IsSuccess()) if (rc.IsSuccess())
{ {
if (!_useTransactions)
return Result.Success;
return SynchronizeDirectory(WorkingDirectoryPath, CommittedDirectoryPath); return SynchronizeDirectory(WorkingDirectoryPath, CommittedDirectoryPath);
} }
@ -97,20 +206,22 @@ namespace LibHac.FsSystem
rc = SynchronizeDirectory(SynchronizingDirectoryPath, WorkingDirectoryPath); rc = SynchronizeDirectory(SynchronizingDirectoryPath, WorkingDirectoryPath);
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
return BaseFs.RenameDirectory(SynchronizingDirectoryPath, CommittedDirectoryPath); return _baseFs.RenameDirectory(SynchronizingDirectoryPath, CommittedDirectoryPath);
} }
protected override Result DoCreateDirectory(U8Span path) private Result ResolveFullPath(Span<byte> outPath, U8Span relativePath)
{ {
Unsafe.SkipInit(out FsPath fullPath); if (StringUtils.GetLength(relativePath, PathTools.MaxPathLength + 1) > PathTools.MaxPathLength)
return ResultFs.TooLongPath.Log();
Result rc = ResolveFullPath(fullPath.Str, path); U8Span workingPath = !_useTransactions && _isPersistentSaveData
if (rc.IsFailure()) return rc; ? CommittedDirectoryPath
: WorkingDirectoryPath;
lock (Locker) StringUtils.Copy(outPath, workingPath);
{ outPath[outPath.Length - 1] = StringTraits.NullTerminator;
return BaseFs.CreateDirectory(fullPath);
} return PathNormalizer.Normalize(outPath.Slice(2), out _, relativePath, false, false);
} }
protected override Result DoCreateFile(U8Span path, long size, CreateFileOptions options) protected override Result DoCreateFile(U8Span path, long size, CreateFileOptions options)
@ -120,49 +231,9 @@ namespace LibHac.FsSystem
Result rc = ResolveFullPath(fullPath.Str, path); Result rc = ResolveFullPath(fullPath.Str, path);
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
lock (Locker) using ScopedLock<SdkMutexType> lk = ScopedLock.Lock(ref _mutex);
{
return BaseFs.CreateFile(fullPath, size, options);
}
}
protected override Result DoDeleteDirectory(U8Span path) return _baseFs.CreateFile(fullPath, size, options);
{
Unsafe.SkipInit(out FsPath fullPath);
Result rc = ResolveFullPath(fullPath.Str, path);
if (rc.IsFailure()) return rc;
lock (Locker)
{
return BaseFs.DeleteDirectory(fullPath);
}
}
protected override Result DoDeleteDirectoryRecursively(U8Span path)
{
Unsafe.SkipInit(out FsPath fullPath);
Result rc = ResolveFullPath(fullPath.Str, path);
if (rc.IsFailure()) return rc;
lock (Locker)
{
return BaseFs.DeleteDirectoryRecursively(fullPath);
}
}
protected override Result DoCleanDirectoryRecursively(U8Span path)
{
Unsafe.SkipInit(out FsPath fullPath);
Result rc = ResolveFullPath(fullPath.Str, path);
if (rc.IsFailure()) return rc;
lock (Locker)
{
return BaseFs.CleanDirectoryRecursively(fullPath);
}
} }
protected override Result DoDeleteFile(U8Span path) protected override Result DoDeleteFile(U8Span path)
@ -172,69 +243,57 @@ namespace LibHac.FsSystem
Result rc = ResolveFullPath(fullPath.Str, path); Result rc = ResolveFullPath(fullPath.Str, path);
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
lock (Locker) using ScopedLock<SdkMutexType> lk = ScopedLock.Lock(ref _mutex);
{
return BaseFs.DeleteFile(fullPath); return _baseFs.DeleteFile(fullPath);
}
} }
protected override Result DoOpenDirectory(out IDirectory directory, U8Span path, OpenDirectoryMode mode) protected override Result DoCreateDirectory(U8Span path)
{ {
Unsafe.SkipInit(out FsPath fullPath); Unsafe.SkipInit(out FsPath fullPath);
Result rc = ResolveFullPath(fullPath.Str, path); Result rc = ResolveFullPath(fullPath.Str, path);
if (rc.IsFailure()) if (rc.IsFailure()) return rc;
{
UnsafeHelpers.SkipParamInit(out directory);
return rc;
}
lock (Locker) using ScopedLock<SdkMutexType> lk = ScopedLock.Lock(ref _mutex);
{
return BaseFs.OpenDirectory(out directory, fullPath, mode); return _baseFs.CreateDirectory(fullPath);
}
} }
protected override Result DoOpenFile(out IFile file, U8Span path, OpenMode mode) protected override Result DoDeleteDirectory(U8Span path)
{ {
UnsafeHelpers.SkipParamInit(out file);
Unsafe.SkipInit(out FsPath fullPath); Unsafe.SkipInit(out FsPath fullPath);
Result rc = ResolveFullPath(fullPath.Str, path); Result rc = ResolveFullPath(fullPath.Str, path);
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
lock (Locker) using ScopedLock<SdkMutexType> lk = ScopedLock.Lock(ref _mutex);
{
rc = BaseFs.OpenFile(out IFile baseFile, fullPath, mode);
if (rc.IsFailure()) return rc;
file = new DirectorySaveDataFile(this, baseFile, mode); return _baseFs.DeleteDirectory(fullPath);
if (mode.HasFlag(OpenMode.Write))
{
OpenWritableFileCount++;
}
return Result.Success;
}
} }
protected override Result DoRenameDirectory(U8Span oldPath, U8Span newPath) protected override Result DoDeleteDirectoryRecursively(U8Span path)
{ {
Unsafe.SkipInit(out FsPath fullCurrentPath); Unsafe.SkipInit(out FsPath fullPath);
Unsafe.SkipInit(out FsPath fullNewPath);
Result rc = ResolveFullPath(fullCurrentPath.Str, oldPath); Result rc = ResolveFullPath(fullPath.Str, path);
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
rc = ResolveFullPath(fullNewPath.Str, newPath); using ScopedLock<SdkMutexType> lk = ScopedLock.Lock(ref _mutex);
return _baseFs.DeleteDirectoryRecursively(fullPath);
}
protected override Result DoCleanDirectoryRecursively(U8Span path)
{
Unsafe.SkipInit(out FsPath fullPath);
Result rc = ResolveFullPath(fullPath.Str, path);
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
lock (Locker) using ScopedLock<SdkMutexType> lk = ScopedLock.Lock(ref _mutex);
{
return BaseFs.RenameDirectory(fullCurrentPath, fullNewPath); return _baseFs.CleanDirectoryRecursively(fullPath);
}
} }
protected override Result DoRenameFile(U8Span oldPath, U8Span newPath) protected override Result DoRenameFile(U8Span oldPath, U8Span newPath)
@ -248,10 +307,25 @@ namespace LibHac.FsSystem
rc = ResolveFullPath(fullNewPath.Str, newPath); rc = ResolveFullPath(fullNewPath.Str, newPath);
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
lock (Locker) using ScopedLock<SdkMutexType> lk = ScopedLock.Lock(ref _mutex);
{
return BaseFs.RenameFile(fullCurrentPath, fullNewPath); return _baseFs.RenameFile(fullCurrentPath, fullNewPath);
} }
protected override Result DoRenameDirectory(U8Span oldPath, U8Span newPath)
{
Unsafe.SkipInit(out FsPath fullCurrentPath);
Unsafe.SkipInit(out FsPath fullNewPath);
Result rc = ResolveFullPath(fullCurrentPath.Str, oldPath);
if (rc.IsFailure()) return rc;
rc = ResolveFullPath(fullNewPath.Str, newPath);
if (rc.IsFailure()) return rc;
using ScopedLock<SdkMutexType> lk = ScopedLock.Lock(ref _mutex);
return _baseFs.RenameDirectory(fullCurrentPath, fullNewPath);
} }
protected override Result DoGetEntryType(out DirectoryEntryType entryType, U8Span path) protected override Result DoGetEntryType(out DirectoryEntryType entryType, U8Span path)
@ -265,102 +339,47 @@ namespace LibHac.FsSystem
return rc; return rc;
} }
lock (Locker) using ScopedLock<SdkMutexType> lk = ScopedLock.Lock(ref _mutex);
{
return BaseFs.GetEntryType(out entryType, fullPath); return _baseFs.GetEntryType(out entryType, fullPath);
}
} }
protected override Result DoCommit() protected override Result DoOpenFile(out IFile file, U8Span path, OpenMode mode)
{ {
lock (Locker) UnsafeHelpers.SkipParamInit(out file);
Unsafe.SkipInit(out FsPath fullPath);
Result rc = ResolveFullPath(fullPath.Str, path);
if (rc.IsFailure()) return rc;
using ScopedLock<SdkMutexType> lk = ScopedLock.Lock(ref _mutex);
rc = _baseFs.OpenFile(out IFile baseFile, fullPath, mode);
if (rc.IsFailure()) return rc;
file = new DirectorySaveDataFile(this, baseFile, mode);
if (mode.HasFlag(OpenMode.Write))
{ {
if (!IsPersistentSaveData) _openWritableFileCount++;
return Result.Success;
if (OpenWritableFileCount > 0)
{
// All files must be closed before commiting save data.
return ResultFs.WriteModeFileNotClosed.Log();
}
Result RenameCommittedDir() => BaseFs.RenameDirectory(CommittedDirectoryPath, SynchronizingDirectoryPath);
Result SynchronizeWorkingDir() => SynchronizeDirectory(SynchronizingDirectoryPath, WorkingDirectoryPath);
Result RenameSynchronizingDir() => BaseFs.RenameDirectory(SynchronizingDirectoryPath, CommittedDirectoryPath);
// Get rid of the previous commit by renaming the folder
Result rc = Utility.RetryFinitelyForTargetLocked(RenameCommittedDir);
if (rc.IsFailure()) return rc;
// If something goes wrong beyond this point, the commit will be
// completed the next time the savedata is opened
rc = Utility.RetryFinitelyForTargetLocked(SynchronizeWorkingDir);
if (rc.IsFailure()) return rc;
rc = Utility.RetryFinitelyForTargetLocked(RenameSynchronizingDir);
if (rc.IsFailure()) return rc;
return Result.Success;
} }
}
protected override Result DoCommitProvisionally(long counter)
{
if (!CanCommitProvisionally)
return ResultFs.UnsupportedCommitProvisionallyForDirectorySaveDataFileSystem.Log();
return Result.Success; return Result.Success;
} }
protected override Result DoRollback() protected override Result DoOpenDirectory(out IDirectory directory, U8Span path, OpenDirectoryMode mode)
{ {
// No old data is kept for temporary save data, so there's nothing to rollback to UnsafeHelpers.SkipParamInit(out directory);
if (!IsPersistentSaveData)
return Result.Success;
return Initialize(IsPersistentSaveData, CanCommitProvisionally);
}
protected override Result DoGetFreeSpaceSize(out long freeSpace, U8Span path)
{
UnsafeHelpers.SkipParamInit(out freeSpace);
Unsafe.SkipInit(out FsPath fullPath); Unsafe.SkipInit(out FsPath fullPath);
Result rc = ResolveFullPath(fullPath.Str, path); Result rc = ResolveFullPath(fullPath.Str, path);
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
lock (Locker) using ScopedLock<SdkMutexType> lk = ScopedLock.Lock(ref _mutex);
{
return BaseFs.GetFreeSpaceSize(out freeSpace, fullPath);
}
}
protected override Result DoGetTotalSpaceSize(out long totalSpace, U8Span path) return _baseFs.OpenDirectory(out directory, fullPath, mode);
{
UnsafeHelpers.SkipParamInit(out totalSpace);
Unsafe.SkipInit(out FsPath fullPath);
Result rc = ResolveFullPath(fullPath.Str, path);
if (rc.IsFailure()) return rc;
lock (Locker)
{
return BaseFs.GetTotalSpaceSize(out totalSpace, fullPath);
}
}
private Result ResolveFullPath(Span<byte> outPath, U8Span relativePath)
{
if (StringUtils.GetLength(relativePath, PathTools.MaxPathLength + 1) > PathTools.MaxPathLength)
return ResultFs.TooLongPath.Log();
StringUtils.Copy(outPath, WorkingDirectoryBytes);
outPath[outPath.Length - 1] = StringTraits.NullTerminator;
return PathNormalizer.Normalize(outPath.Slice(2), out _, relativePath, false, false);
} }
/// <summary> /// <summary>
@ -372,7 +391,7 @@ namespace LibHac.FsSystem
private Result SynchronizeDirectory(U8Span destPath, U8Span sourcePath) private Result SynchronizeDirectory(U8Span destPath, U8Span sourcePath)
{ {
// Delete destination dir and recreate it. // Delete destination dir and recreate it.
Result rc = BaseFs.DeleteDirectoryRecursively(destPath); Result rc = _baseFs.DeleteDirectoryRecursively(destPath);
// Nintendo returns error unconditionally because SynchronizeDirectory is always called in situations // Nintendo returns error unconditionally because SynchronizeDirectory is always called in situations
// where a PathNotFound error would mean the save directory was in an invalid state. // where a PathNotFound error would mean the save directory was in an invalid state.
@ -380,22 +399,112 @@ namespace LibHac.FsSystem
// 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;
rc = BaseFs.CreateDirectory(destPath); rc = _baseFs.CreateDirectory(destPath);
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
// Get a work buffer to work with. // Lock only if initialized with a client
using (var buffer = new RentedArray<byte>(IdealWorkBufferSize)) if(_fsClient is not null)
{ {
return Utility.CopyDirectoryRecursively(BaseFs, destPath, sourcePath, buffer.Span); using ScopedLock<SdkMutexType> lk =
ScopedLock.Lock(ref _fsClient.Globals.DirectorySaveDataFileSystem.SynchronizeDirectoryMutex);
using (var buffer = new RentedArray<byte>(IdealWorkBufferSize))
{
return Utility.CopyDirectoryRecursively(_baseFs, destPath, sourcePath, buffer.Span);
}
}
else
{
using (var buffer = new RentedArray<byte>(IdealWorkBufferSize))
{
return Utility.CopyDirectoryRecursively(_baseFs, destPath, sourcePath, buffer.Span);
}
} }
} }
internal void NotifyCloseWritableFile() protected override Result DoCommit()
{ {
lock (Locker) using ScopedLock<SdkMutexType> lk = ScopedLock.Lock(ref _mutex);
if (!_useTransactions || !_isPersistentSaveData)
return Result.Success;
if (_openWritableFileCount > 0)
{ {
OpenWritableFileCount--; // All files must be closed before commiting save data.
return ResultFs.WriteModeFileNotClosed.Log();
} }
Result RenameCommittedDir() => _baseFs.RenameDirectory(CommittedDirectoryPath, SynchronizingDirectoryPath);
Result SynchronizeWorkingDir() => SynchronizeDirectory(SynchronizingDirectoryPath, WorkingDirectoryPath);
Result RenameSynchronizingDir() => _baseFs.RenameDirectory(SynchronizingDirectoryPath, CommittedDirectoryPath);
// Get rid of the previous commit by renaming the folder
Result rc = Utility.RetryFinitelyForTargetLocked(RenameCommittedDir);
if (rc.IsFailure()) return rc;
// If something goes wrong beyond this point, the commit will be
// completed the next time the savedata is opened
rc = Utility.RetryFinitelyForTargetLocked(SynchronizeWorkingDir);
if (rc.IsFailure()) return rc;
rc = Utility.RetryFinitelyForTargetLocked(RenameSynchronizingDir);
if (rc.IsFailure()) return rc;
return Result.Success;
}
protected override Result DoCommitProvisionally(long counter)
{
if (!_canCommitProvisionally)
return ResultFs.UnsupportedCommitProvisionallyForDirectorySaveDataFileSystem.Log();
return Result.Success;
}
protected override Result DoRollback()
{
// No old data is kept for temporary save data, so there's nothing to rollback to
if (!_isPersistentSaveData)
return Result.Success;
return Initialize(_isPersistentSaveData, _canCommitProvisionally, _useTransactions);
}
protected override Result DoGetFreeSpaceSize(out long freeSpace, U8Span path)
{
UnsafeHelpers.SkipParamInit(out freeSpace);
Unsafe.SkipInit(out FsPath fullPath);
Result rc = ResolveFullPath(fullPath.Str, path);
if (rc.IsFailure()) return rc;
using ScopedLock<SdkMutexType> lk = ScopedLock.Lock(ref _mutex);
return _baseFs.GetFreeSpaceSize(out freeSpace, fullPath);
}
protected override Result DoGetTotalSpaceSize(out long totalSpace, U8Span path)
{
UnsafeHelpers.SkipParamInit(out totalSpace);
Unsafe.SkipInit(out FsPath fullPath);
Result rc = ResolveFullPath(fullPath.Str, path);
if (rc.IsFailure()) return rc;
using ScopedLock<SdkMutexType> lk = ScopedLock.Lock(ref _mutex);
return _baseFs.GetTotalSpaceSize(out totalSpace, fullPath);
}
internal void DecrementWriteOpenFileCount()
{
using ScopedLock<SdkMutexType> lk = ScopedLock.Lock(ref _mutex);
_openWritableFileCount--;
} }
} }
} }

View file

@ -194,9 +194,9 @@ namespace LibHac.FsSystem
var sb = new U8StringBuilder(destPath.Str); var sb = new U8StringBuilder(destPath.Str);
sb.Append(destParentPath).Append(entry.Name); sb.Append(destParentPath).Append(entry.Name);
Abort.DoAbortUnless(sb.Length < Unsafe.SizeOf<FsPath>()); Assert.SdkLess(sb.Length, Unsafe.SizeOf<FsPath>());
rc = destFileSystem.CreateFile(new U8Span(destPath.Str), entry.Size, CreateFileOptions.None); rc = destFileSystem.CreateFile(new U8Span(destPath.Str), entry.Size);
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
rc = destFileSystem.OpenFile(out IFile destFile, new U8Span(destPath.Str), OpenMode.Write); rc = destFileSystem.OpenFile(out IFile destFile, new U8Span(destPath.Str), OpenMode.Write);

View file

@ -30,7 +30,8 @@ namespace LibHac.Tests.Fs
public IFileSystem Create() public IFileSystem Create()
{ {
DirectorySaveDataFileSystem.CreateNew(out DirectorySaveDataFileSystem saveFs, BaseFileSystem, true, true) DirectorySaveDataFileSystem
.CreateNew(out DirectorySaveDataFileSystem saveFs, BaseFileSystem, true, true, true)
.ThrowIfFailure(); .ThrowIfFailure();
return saveFs; return saveFs;
@ -41,7 +42,7 @@ namespace LibHac.Tests.Fs
{ {
var baseFs = new InMemoryFileSystem(); var baseFs = new InMemoryFileSystem();
DirectorySaveDataFileSystem.CreateNew(out DirectorySaveDataFileSystem saveFs, baseFs, true, true) DirectorySaveDataFileSystem.CreateNew(out DirectorySaveDataFileSystem saveFs, baseFs, true, true, true)
.ThrowIfFailure(); .ThrowIfFailure();
return (baseFs, saveFs); return (baseFs, saveFs);
@ -127,7 +128,7 @@ namespace LibHac.Tests.Fs
baseFs.CreateFile("/0/file1".ToU8Span(), 0, CreateFileOptions.None).ThrowIfFailure(); baseFs.CreateFile("/0/file1".ToU8Span(), 0, CreateFileOptions.None).ThrowIfFailure();
baseFs.CreateFile("/1/file2".ToU8Span(), 0, CreateFileOptions.None).ThrowIfFailure(); baseFs.CreateFile("/1/file2".ToU8Span(), 0, CreateFileOptions.None).ThrowIfFailure();
DirectorySaveDataFileSystem.CreateNew(out DirectorySaveDataFileSystem saveFs, baseFs, true, true) DirectorySaveDataFileSystem.CreateNew(out DirectorySaveDataFileSystem saveFs, baseFs, true, true, true)
.ThrowIfFailure(); .ThrowIfFailure();
Assert.Success(saveFs.GetEntryType(out _, "/file1".ToU8Span())); Assert.Success(saveFs.GetEntryType(out _, "/file1".ToU8Span()));
@ -146,7 +147,7 @@ namespace LibHac.Tests.Fs
baseFs.CreateFile("/_/file1".ToU8Span(), 0, CreateFileOptions.None).ThrowIfFailure(); baseFs.CreateFile("/_/file1".ToU8Span(), 0, CreateFileOptions.None).ThrowIfFailure();
baseFs.CreateFile("/1/file2".ToU8Span(), 0, CreateFileOptions.None).ThrowIfFailure(); baseFs.CreateFile("/1/file2".ToU8Span(), 0, CreateFileOptions.None).ThrowIfFailure();
DirectorySaveDataFileSystem.CreateNew(out DirectorySaveDataFileSystem saveFs, baseFs, true, true) DirectorySaveDataFileSystem.CreateNew(out DirectorySaveDataFileSystem saveFs, baseFs, true, true, true)
.ThrowIfFailure(); .ThrowIfFailure();
Assert.Result(ResultFs.PathNotFound, saveFs.GetEntryType(out _, "/file1".ToU8Span())); Assert.Result(ResultFs.PathNotFound, saveFs.GetEntryType(out _, "/file1".ToU8Span()));
@ -163,7 +164,7 @@ namespace LibHac.Tests.Fs
// Set the existing files before initializing the save FS // Set the existing files before initializing the save FS
baseFs.CreateFile("/1/file2".ToU8Span(), 0, CreateFileOptions.None).ThrowIfFailure(); baseFs.CreateFile("/1/file2".ToU8Span(), 0, CreateFileOptions.None).ThrowIfFailure();
DirectorySaveDataFileSystem.CreateNew(out DirectorySaveDataFileSystem saveFs, baseFs, true, true) DirectorySaveDataFileSystem.CreateNew(out DirectorySaveDataFileSystem saveFs, baseFs, true, true, true)
.ThrowIfFailure(); .ThrowIfFailure();
Assert.Result(ResultFs.PathNotFound, saveFs.GetEntryType(out _, "/file1".ToU8Span())); Assert.Result(ResultFs.PathNotFound, saveFs.GetEntryType(out _, "/file1".ToU8Span()));