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.Shim;
using LibHac.FsSystem;
namespace LibHac.Fs
{
@ -26,6 +27,7 @@ namespace LibHac.Fs
public FsContextHandlerGlobals FsContextHandler;
public ResultHandlingUtilityGlobals ResultHandlingUtility;
public PathUtilityGlobals PathUtility;
public DirectorySaveDataFileSystemGlobals DirectorySaveDataFileSystem;
public void Initialize(FileSystemClient fsClient, HorizonClient horizonClient)
{
@ -35,6 +37,7 @@ namespace LibHac.Fs
UserMountTable.Initialize(fsClient);
FsContextHandler.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;
rc = DirectorySaveDataFileSystem.CreateNew(out DirectorySaveDataFileSystem saveFs, subDirFs,
isPersistentSaveData, isUserSaveData);
isPersistentSaveData, isUserSaveData, true);
if (rc.IsFailure()) return rc;
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.Fs;
using LibHac.Fs.Fsa;
using LibHac.Os;
using LibHac.Util;
namespace LibHac.FsSystem
{
internal struct DirectorySaveDataFileSystemGlobals
{
public SdkMutexType SynchronizeDirectoryMutex;
public void Initialize(FileSystemClient fsClient)
{
SynchronizeDirectoryMutex.Initialize();
}
}
/// <summary>
/// An <see cref="IFileSystem"/> that provides transactional commits for savedata on top of another base IFileSystem.
/// </summary>
/// <remarks>
/// Transactional commits should be atomic as long as the <see cref="IFileSystem.RenameDirectory"/> function of the
/// 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>
public class DirectorySaveDataFileSystem : IFileSystem
{
@ -27,17 +38,82 @@ namespace LibHac.FsSystem
private U8Span WorkingDirectoryPath => new U8Span(WorkingDirectoryBytes);
private U8Span SynchronizingDirectoryPath => new U8Span(SynchronizingDirectoryBytes);
private IFileSystem BaseFs { get; }
private object Locker { get; } = new object();
private int OpenWritableFileCount { get; set; }
private bool IsPersistentSaveData { get; set; }
private bool CanCommitProvisionally { get; set; }
private FileSystemClient _fsClient;
private IFileSystem _baseFs;
private SdkMutexType _mutex;
// Todo: Unique file system for disposal
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,
bool isPersistentSaveData, bool canCommitProvisionally)
bool isPersistentSaveData, bool canCommitProvisionally, bool useTransactions,
FileSystemClient fsClient = null)
{
var obj = new DirectorySaveDataFileSystem(baseFileSystem);
Result rc = obj.Initialize(isPersistentSaveData, canCommitProvisionally);
var obj = new DirectorySaveDataFileSystem(baseFileSystem, fsClient);
Result rc = obj.Initialize(isPersistentSaveData, canCommitProvisionally, useTransactions);
if (rc.IsSuccess())
{
@ -50,29 +126,58 @@ namespace LibHac.FsSystem
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;
CanCommitProvisionally = canCommitProvisionally;
_baseFs = baseFileSystem;
_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
Result rc = BaseFs.GetEntryType(out _, WorkingDirectoryPath);
Result rc = _baseFs.GetEntryType(out _, WorkingDirectoryPath);
if (rc.IsFailure())
{
if (!ResultFs.PathNotFound.Includes(rc)) return rc;
rc = BaseFs.CreateDirectory(WorkingDirectoryPath);
rc = _baseFs.CreateDirectory(WorkingDirectoryPath);
if (rc.IsFailure()) return rc;
if (!IsPersistentSaveData) return Result.Success;
rc = BaseFs.CreateDirectory(CommittedDirectoryPath);
if (_isPersistentSaveData)
{
rc = _baseFs.CreateDirectory(CommittedDirectoryPath);
if (rc.IsFailure()) return rc;
}
// Nintendo returns on all failures, but we'll keep going if committed already exists
// 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
if (!IsPersistentSaveData) return Result.Success;
if (!_isPersistentSaveData)
return Result.Success;
rc = BaseFs.GetEntryType(out _, CommittedDirectoryPath);
rc = _baseFs.GetEntryType(out _, CommittedDirectoryPath);
if (rc.IsSuccess())
{
if (!_useTransactions)
return Result.Success;
return SynchronizeDirectory(WorkingDirectoryPath, CommittedDirectoryPath);
}
@ -97,20 +206,22 @@ namespace LibHac.FsSystem
rc = SynchronizeDirectory(SynchronizingDirectoryPath, WorkingDirectoryPath);
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);
if (rc.IsFailure()) return rc;
U8Span workingPath = !_useTransactions && _isPersistentSaveData
? CommittedDirectoryPath
: WorkingDirectoryPath;
lock (Locker)
{
return BaseFs.CreateDirectory(fullPath);
}
StringUtils.Copy(outPath, workingPath);
outPath[outPath.Length - 1] = StringTraits.NullTerminator;
return PathNormalizer.Normalize(outPath.Slice(2), out _, relativePath, false, false);
}
protected override Result DoCreateFile(U8Span path, long size, CreateFileOptions options)
@ -120,49 +231,9 @@ namespace LibHac.FsSystem
Result rc = ResolveFullPath(fullPath.Str, path);
if (rc.IsFailure()) return rc;
lock (Locker)
{
return BaseFs.CreateFile(fullPath, size, options);
}
}
using ScopedLock<SdkMutexType> lk = ScopedLock.Lock(ref _mutex);
protected override Result DoDeleteDirectory(U8Span path)
{
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);
}
return _baseFs.CreateFile(fullPath, size, options);
}
protected override Result DoDeleteFile(U8Span path)
@ -172,69 +243,57 @@ namespace LibHac.FsSystem
Result rc = ResolveFullPath(fullPath.Str, path);
if (rc.IsFailure()) return rc;
lock (Locker)
{
return BaseFs.DeleteFile(fullPath);
}
using ScopedLock<SdkMutexType> lk = ScopedLock.Lock(ref _mutex);
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);
Result rc = ResolveFullPath(fullPath.Str, path);
if (rc.IsFailure())
{
UnsafeHelpers.SkipParamInit(out directory);
return rc;
if (rc.IsFailure()) return rc;
using ScopedLock<SdkMutexType> lk = ScopedLock.Lock(ref _mutex);
return _baseFs.CreateDirectory(fullPath);
}
lock (Locker)
protected override Result DoDeleteDirectory(U8Span path)
{
return BaseFs.OpenDirectory(out directory, fullPath, mode);
}
}
protected override Result DoOpenFile(out IFile file, U8Span path, OpenMode mode)
{
UnsafeHelpers.SkipParamInit(out file);
Unsafe.SkipInit(out FsPath fullPath);
Result rc = ResolveFullPath(fullPath.Str, path);
if (rc.IsFailure()) return rc;
lock (Locker)
using ScopedLock<SdkMutexType> lk = ScopedLock.Lock(ref _mutex);
return _baseFs.DeleteDirectory(fullPath);
}
protected override Result DoDeleteDirectoryRecursively(U8Span path)
{
rc = BaseFs.OpenFile(out IFile baseFile, fullPath, mode);
Unsafe.SkipInit(out FsPath fullPath);
Result rc = ResolveFullPath(fullPath.Str, path);
if (rc.IsFailure()) return rc;
file = new DirectorySaveDataFile(this, baseFile, mode);
using ScopedLock<SdkMutexType> lk = ScopedLock.Lock(ref _mutex);
if (mode.HasFlag(OpenMode.Write))
return _baseFs.DeleteDirectoryRecursively(fullPath);
}
protected override Result DoCleanDirectoryRecursively(U8Span path)
{
OpenWritableFileCount++;
}
Unsafe.SkipInit(out FsPath fullPath);
return Result.Success;
}
}
protected override Result DoRenameDirectory(U8Span oldPath, U8Span newPath)
{
Unsafe.SkipInit(out FsPath fullCurrentPath);
Unsafe.SkipInit(out FsPath fullNewPath);
Result rc = ResolveFullPath(fullCurrentPath.Str, oldPath);
Result rc = ResolveFullPath(fullPath.Str, path);
if (rc.IsFailure()) return rc;
rc = ResolveFullPath(fullNewPath.Str, newPath);
if (rc.IsFailure()) return rc;
using ScopedLock<SdkMutexType> lk = ScopedLock.Lock(ref _mutex);
lock (Locker)
{
return BaseFs.RenameDirectory(fullCurrentPath, fullNewPath);
}
return _baseFs.CleanDirectoryRecursively(fullPath);
}
protected override Result DoRenameFile(U8Span oldPath, U8Span newPath)
@ -248,10 +307,25 @@ namespace LibHac.FsSystem
rc = ResolveFullPath(fullNewPath.Str, newPath);
if (rc.IsFailure()) return rc;
lock (Locker)
{
return BaseFs.RenameFile(fullCurrentPath, fullNewPath);
using ScopedLock<SdkMutexType> lk = ScopedLock.Lock(ref _mutex);
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)
@ -265,28 +339,105 @@ namespace LibHac.FsSystem
return rc;
}
lock (Locker)
using ScopedLock<SdkMutexType> lk = ScopedLock.Lock(ref _mutex);
return _baseFs.GetEntryType(out entryType, fullPath);
}
protected override Result DoOpenFile(out IFile file, U8Span path, OpenMode mode)
{
return BaseFs.GetEntryType(out entryType, fullPath);
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))
{
_openWritableFileCount++;
}
return Result.Success;
}
protected override Result DoOpenDirectory(out IDirectory directory, U8Span path, OpenDirectoryMode mode)
{
UnsafeHelpers.SkipParamInit(out directory);
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.OpenDirectory(out directory, fullPath, mode);
}
/// <summary>
/// Creates the destination directory if needed and copies the source directory to it.
/// </summary>
/// <param name="destPath">The path of the destination directory.</param>
/// <param name="sourcePath">The path of the source directory.</param>
/// <returns>The <see cref="Result"/> of the operation.</returns>
private Result SynchronizeDirectory(U8Span destPath, U8Span sourcePath)
{
// Delete destination dir and recreate it.
Result rc = _baseFs.DeleteDirectoryRecursively(destPath);
// 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.
// We'll ignore PathNotFound errors to be more user-friendly to users who might accidentally
// put the save directory in an invalid state.
if (rc.IsFailure() && !ResultFs.PathNotFound.Includes(rc)) return rc;
rc = _baseFs.CreateDirectory(destPath);
if (rc.IsFailure()) return rc;
// Lock only if initialized with a client
if(_fsClient is not null)
{
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);
}
}
}
protected override Result DoCommit()
{
lock (Locker)
{
if (!IsPersistentSaveData)
using ScopedLock<SdkMutexType> lk = ScopedLock.Lock(ref _mutex);
if (!_useTransactions || !_isPersistentSaveData)
return Result.Success;
if (OpenWritableFileCount > 0)
if (_openWritableFileCount > 0)
{
// All files must be closed before commiting save data.
return ResultFs.WriteModeFileNotClosed.Log();
}
Result RenameCommittedDir() => BaseFs.RenameDirectory(CommittedDirectoryPath, SynchronizingDirectoryPath);
Result RenameCommittedDir() => _baseFs.RenameDirectory(CommittedDirectoryPath, SynchronizingDirectoryPath);
Result SynchronizeWorkingDir() => SynchronizeDirectory(SynchronizingDirectoryPath, WorkingDirectoryPath);
Result RenameSynchronizingDir() => BaseFs.RenameDirectory(SynchronizingDirectoryPath, CommittedDirectoryPath);
Result RenameSynchronizingDir() => _baseFs.RenameDirectory(SynchronizingDirectoryPath, CommittedDirectoryPath);
// Get rid of the previous commit by renaming the folder
Result rc = Utility.RetryFinitelyForTargetLocked(RenameCommittedDir);
@ -303,11 +454,10 @@ namespace LibHac.FsSystem
return Result.Success;
}
}
protected override Result DoCommitProvisionally(long counter)
{
if (!CanCommitProvisionally)
if (!_canCommitProvisionally)
return ResultFs.UnsupportedCommitProvisionallyForDirectorySaveDataFileSystem.Log();
return Result.Success;
@ -316,10 +466,10 @@ namespace LibHac.FsSystem
protected override Result DoRollback()
{
// No old data is kept for temporary save data, so there's nothing to rollback to
if (!IsPersistentSaveData)
if (!_isPersistentSaveData)
return Result.Success;
return Initialize(IsPersistentSaveData, CanCommitProvisionally);
return Initialize(_isPersistentSaveData, _canCommitProvisionally, _useTransactions);
}
protected override Result DoGetFreeSpaceSize(out long freeSpace, U8Span path)
@ -331,10 +481,9 @@ namespace LibHac.FsSystem
Result rc = ResolveFullPath(fullPath.Str, path);
if (rc.IsFailure()) return rc;
lock (Locker)
{
return BaseFs.GetFreeSpaceSize(out freeSpace, fullPath);
}
using ScopedLock<SdkMutexType> lk = ScopedLock.Lock(ref _mutex);
return _baseFs.GetFreeSpaceSize(out freeSpace, fullPath);
}
protected override Result DoGetTotalSpaceSize(out long totalSpace, U8Span path)
@ -346,56 +495,16 @@ namespace LibHac.FsSystem
Result rc = ResolveFullPath(fullPath.Str, path);
if (rc.IsFailure()) return rc;
lock (Locker)
{
return BaseFs.GetTotalSpaceSize(out totalSpace, fullPath);
}
using ScopedLock<SdkMutexType> lk = ScopedLock.Lock(ref _mutex);
return _baseFs.GetTotalSpaceSize(out totalSpace, fullPath);
}
private Result ResolveFullPath(Span<byte> outPath, U8Span relativePath)
internal void DecrementWriteOpenFileCount()
{
if (StringUtils.GetLength(relativePath, PathTools.MaxPathLength + 1) > PathTools.MaxPathLength)
return ResultFs.TooLongPath.Log();
using ScopedLock<SdkMutexType> lk = ScopedLock.Lock(ref _mutex);
StringUtils.Copy(outPath, WorkingDirectoryBytes);
outPath[outPath.Length - 1] = StringTraits.NullTerminator;
return PathNormalizer.Normalize(outPath.Slice(2), out _, relativePath, false, false);
}
/// <summary>
/// Creates the destination directory if needed and copies the source directory to it.
/// </summary>
/// <param name="destPath">The path of the destination directory.</param>
/// <param name="sourcePath">The path of the source directory.</param>
/// <returns>The <see cref="Result"/> of the operation.</returns>
private Result SynchronizeDirectory(U8Span destPath, U8Span sourcePath)
{
// Delete destination dir and recreate it.
Result rc = BaseFs.DeleteDirectoryRecursively(destPath);
// 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.
// We'll ignore PathNotFound errors to be more user-friendly to users who might accidentally
// put the save directory in an invalid state.
if (rc.IsFailure() && !ResultFs.PathNotFound.Includes(rc)) return rc;
rc = BaseFs.CreateDirectory(destPath);
if (rc.IsFailure()) return rc;
// Get a work buffer to work with.
using (var buffer = new RentedArray<byte>(IdealWorkBufferSize))
{
return Utility.CopyDirectoryRecursively(BaseFs, destPath, sourcePath, buffer.Span);
}
}
internal void NotifyCloseWritableFile()
{
lock (Locker)
{
OpenWritableFileCount--;
}
_openWritableFileCount--;
}
}
}

View file

@ -194,9 +194,9 @@ namespace LibHac.FsSystem
var sb = new U8StringBuilder(destPath.Str);
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;
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()
{
DirectorySaveDataFileSystem.CreateNew(out DirectorySaveDataFileSystem saveFs, BaseFileSystem, true, true)
DirectorySaveDataFileSystem
.CreateNew(out DirectorySaveDataFileSystem saveFs, BaseFileSystem, true, true, true)
.ThrowIfFailure();
return saveFs;
@ -41,7 +42,7 @@ namespace LibHac.Tests.Fs
{
var baseFs = new InMemoryFileSystem();
DirectorySaveDataFileSystem.CreateNew(out DirectorySaveDataFileSystem saveFs, baseFs, true, true)
DirectorySaveDataFileSystem.CreateNew(out DirectorySaveDataFileSystem saveFs, baseFs, true, true, true)
.ThrowIfFailure();
return (baseFs, saveFs);
@ -127,7 +128,7 @@ namespace LibHac.Tests.Fs
baseFs.CreateFile("/0/file1".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();
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("/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();
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
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();
Assert.Result(ResultFs.PathNotFound, saveFs.GetEntryType(out _, "/file1".ToU8Span()));