From 596a8bef7cc62859e594dac19d612392f4218a87 Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Thu, 11 Feb 2021 00:11:14 -0700 Subject: [PATCH] Rewrite file system accessors --- src/LibHac/Common/FixedArrays/Array8.cs | 29 ++ src/LibHac/Diag/Abort.cs | 14 +- src/LibHac/Fs/FileSystemClient.cs | 24 +- src/LibHac/Fs/Fsa/DirectoryAccessor.cs | 39 ++ src/LibHac/Fs/Fsa/FileAccessor.cs | 181 +++++++++ src/LibHac/Fs/Fsa/FileSystemAccessor.cs | 403 ++++++++++++++++++++ src/LibHac/Fs/Fsa/Registrar.cs | 15 + src/LibHac/Fs/ICommonMountNameGenerator.cs | 9 - src/LibHac/Fs/Impl/FileDataCacheAccessor.cs | 26 ++ src/LibHac/Fs/Impl/FilePathHash.cs | 7 + src/LibHac/Fs/Impl/FileRegion.cs | 93 +++++ src/LibHac/Fs/Impl/IFileDataCache.cs | 92 +++++ src/LibHac/Fs/ScopedSetter.cs | 29 ++ src/LibHac/Fs/Shim/Bis.cs | 3 + src/LibHac/Fs/Shim/ContentStorage.cs | 3 + src/LibHac/Fs/Shim/GameCard.cs | 3 + src/LibHac/Fs/Shim/Host.cs | 5 + src/LibHac/Fs/Shim/SaveDataManagement.cs | 2 +- 18 files changed, 957 insertions(+), 20 deletions(-) create mode 100644 src/LibHac/Common/FixedArrays/Array8.cs create mode 100644 src/LibHac/Fs/Fsa/DirectoryAccessor.cs create mode 100644 src/LibHac/Fs/Fsa/FileAccessor.cs create mode 100644 src/LibHac/Fs/Fsa/FileSystemAccessor.cs create mode 100644 src/LibHac/Fs/Fsa/Registrar.cs delete mode 100644 src/LibHac/Fs/ICommonMountNameGenerator.cs create mode 100644 src/LibHac/Fs/Impl/FileDataCacheAccessor.cs create mode 100644 src/LibHac/Fs/Impl/FilePathHash.cs create mode 100644 src/LibHac/Fs/Impl/FileRegion.cs create mode 100644 src/LibHac/Fs/Impl/IFileDataCache.cs create mode 100644 src/LibHac/Fs/ScopedSetter.cs diff --git a/src/LibHac/Common/FixedArrays/Array8.cs b/src/LibHac/Common/FixedArrays/Array8.cs new file mode 100644 index 00000000..b951f5bf --- /dev/null +++ b/src/LibHac/Common/FixedArrays/Array8.cs @@ -0,0 +1,29 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace LibHac.Common.FixedArrays +{ + [StructLayout(LayoutKind.Sequential)] + public struct Array8 + { + public const int Length = 8; + + private T _item01; + private T _item02; + private T _item03; + private T _item04; + private T _item05; + private T _item06; + private T _item07; + private T _item08; + + public ref T this[int i] => ref Items[i]; + + public Span Items => SpanHelpers.CreateSpan(ref _item01, Length); + public readonly ReadOnlySpan ItemsRo => SpanHelpers.CreateReadOnlySpan(in _item01, Length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ReadOnlySpan(in Array8 value) => value.ItemsRo; + } +} \ No newline at end of file diff --git a/src/LibHac/Diag/Abort.cs b/src/LibHac/Diag/Abort.cs index df473d21..7d396168 100644 --- a/src/LibHac/Diag/Abort.cs +++ b/src/LibHac/Diag/Abort.cs @@ -6,11 +6,11 @@ namespace LibHac.Diag public static class Abort { [DoesNotReturn] - public static void DoAbort(string message = null) + public static void DoAbort(Result result, string message = null) { if (string.IsNullOrWhiteSpace(message)) { - throw new LibHacException("Abort."); + throw new HorizonResultException(result, "Abort."); } throw new LibHacException($"Abort: {message}"); @@ -21,7 +21,15 @@ namespace LibHac.Diag if (condition) return; - DoAbort(message); + DoAbort(default, message); + } + + public static void DoAbortUnless([DoesNotReturnIf(false)] bool condition, Result result, string message = null) + { + if (condition) + return; + + DoAbort(result, message); } [DoesNotReturn] diff --git a/src/LibHac/Fs/FileSystemClient.cs b/src/LibHac/Fs/FileSystemClient.cs index 25e89262..c1ee578b 100644 --- a/src/LibHac/Fs/FileSystemClient.cs +++ b/src/LibHac/Fs/FileSystemClient.cs @@ -12,6 +12,23 @@ using IFileSystem = LibHac.Fs.Fsa.IFileSystem; namespace LibHac.Fs { + // Functions in the nn::fssrv::detail namespace use this struct. + public readonly struct FileSystemClientImpl + { + internal readonly FileSystemClient Fs; + internal HorizonClient Hos => Fs.Hos; + internal ref FileSystemClientGlobals Globals => ref Fs.Globals; + + internal FileSystemClientImpl(FileSystemClient parentClient) => Fs = parentClient; + } + + internal struct FileSystemClientGlobals + { + public HorizonClient Hos; + public object InitMutex; + public FileSystemProxyServiceObjectGlobals FileSystemProxyServiceObject; + } + public partial class FileSystemClient { internal FileSystemClientGlobals Globals; @@ -38,13 +55,6 @@ namespace LibHac.Fs Assert.NotNull(Time); } - internal struct FileSystemClientGlobals - { - public HorizonClient Hos; - public object InitMutex; - public FileSystemProxyServiceObjectGlobals FileSystemProxyServiceObject; - } - public bool HasFileSystemServer() { return Hos != null; diff --git a/src/LibHac/Fs/Fsa/DirectoryAccessor.cs b/src/LibHac/Fs/Fsa/DirectoryAccessor.cs new file mode 100644 index 00000000..e5f282cf --- /dev/null +++ b/src/LibHac/Fs/Fsa/DirectoryAccessor.cs @@ -0,0 +1,39 @@ +using System; +using LibHac.Common; +using LibHac.Fs.Fsa; + +// ReSharper disable once CheckNamespace +namespace LibHac.Fs.Impl +{ + internal class DirectoryAccessor : IDisposable + { + private IDirectory _directory; + private FileSystemAccessor _parentFileSystem; + + public DirectoryAccessor(ref IDirectory directory, FileSystemAccessor parentFileSystem) + { + _directory = Shared.Move(ref directory); + _parentFileSystem = parentFileSystem; + } + + public void Dispose() + { + _directory?.Dispose(); + _directory = null; + + _parentFileSystem.NotifyCloseDirectory(this); + } + + public FileSystemAccessor GetParent() => _parentFileSystem; + + public Result Read(out long entriesRead, Span entryBuffer) + { + return _directory.Read(out entriesRead, entryBuffer); + } + + public Result GetEntryCount(out long entryCount) + { + return _directory.GetEntryCount(out entryCount); + } + } +} diff --git a/src/LibHac/Fs/Fsa/FileAccessor.cs b/src/LibHac/Fs/Fsa/FileAccessor.cs new file mode 100644 index 00000000..236faf48 --- /dev/null +++ b/src/LibHac/Fs/Fsa/FileAccessor.cs @@ -0,0 +1,181 @@ +using System; +using System.Runtime.CompilerServices; +using LibHac.Common; +using LibHac.Diag; +using LibHac.Fs.Fsa; + +// ReSharper disable once CheckNamespace +namespace LibHac.Fs.Impl +{ + internal enum WriteState + { + None, + NeedsFlush, + Failed, + } + + internal class FileAccessor : IDisposable + { + private IFile _file; + private FileSystemAccessor _parentFileSystem; + private WriteState _writeState; + private Result _lastResult; + private OpenMode _openMode; + private FilePathHash _filePathHash; + // ReSharper disable once NotAccessedField.Local + private int _pathHashIndex; + + public FileAccessor(ref IFile file, FileSystemAccessor parentFileSystem, OpenMode mode) + { + _file = Shared.Move(ref file); + _parentFileSystem = parentFileSystem; + _openMode = mode; + } + + public void Dispose() + { + if (_lastResult.IsSuccess() && _writeState == WriteState.NeedsFlush) + { + Abort.DoAbort(ResultFs.NeedFlush.Log(), "File needs flush before closing."); + } + + _parentFileSystem?.NotifyCloseFile(this); + _file?.Dispose(); + + _file = null; + } + + public OpenMode GetOpenMode() => _openMode; + public WriteState GetWriteState() => _writeState; + public FileSystemAccessor GetParent() => _parentFileSystem; + + public void SetFilePathHash(FilePathHash filePathHash, int index) + { + _filePathHash = filePathHash; + _pathHashIndex = index; + } + + private Result UpdateLastResult(Result result) + { + if (!ResultFs.UsableSpaceNotEnough.Includes(result)) + _lastResult = result; + + return result; + } + + public Result ReadWithoutCacheAccessLog(out long bytesRead, long offset, Span destination, + in ReadOption option) + { + return _file.Read(out bytesRead, offset, destination, in option); + } + + private Result ReadWithCacheAccessLog(out long bytesRead, long offset, Span destination, + in ReadOption option, bool usePathCache, bool useDataCache) + { + throw new NotImplementedException(); + } + + public Result Read(out long bytesRead, long offset, Span destination, in ReadOption option) + { + Unsafe.SkipInit(out bytesRead); + + if (_lastResult.IsFailure()) + { + // Todo: Access log + return _lastResult; + } + + // ReSharper disable ConditionIsAlwaysTrueOrFalse + bool usePathCache = _parentFileSystem is not null && _filePathHash.Data != 0; + + // Todo: Call IsGlobalFileDataCacheEnabled +#pragma warning disable 162 + bool useDataCache = false && _parentFileSystem is not null && _parentFileSystem.IsFileDataCacheAttachable(); +#pragma warning restore 162 + if (usePathCache || useDataCache) + { + return ReadWithCacheAccessLog(out bytesRead, offset, destination, in option, usePathCache, + useDataCache); + } + else + { + return ReadWithoutCacheAccessLog(out bytesRead, offset, destination, in option); + } + // ReSharper restore ConditionIsAlwaysTrueOrFalse + } + + public Result Write(long offset, ReadOnlySpan source, in WriteOption option) + { + if (_lastResult.IsFailure()) + return _lastResult; + + using ScopedSetter setter = + ScopedSetter.MakeScopedSetter(ref _writeState, WriteState.Failed); + + if (_filePathHash.Data != 0) + { + throw new NotImplementedException(); + } + else + { + Result rc = UpdateLastResult(_file.Write(offset, source, in option)); + if (rc.IsFailure()) return rc; + } + + setter.Set(option.HasFlushFlag() ? WriteState.None : WriteState.NeedsFlush); + return Result.Success; + } + + public Result Flush() + { + if (_lastResult.IsFailure()) + return _lastResult; + + using ScopedSetter setter = + ScopedSetter.MakeScopedSetter(ref _writeState, WriteState.Failed); + + Result rc = UpdateLastResult(_file.Flush()); + if (rc.IsFailure()) return rc; + + setter.Set(WriteState.None); + return Result.Success; + } + + public Result SetSize(long size) + { + if (_lastResult.IsFailure()) + return _lastResult; + + WriteState oldWriteState = _writeState; + using ScopedSetter setter = + ScopedSetter.MakeScopedSetter(ref _writeState, WriteState.Failed); + + Result rc = UpdateLastResult(_file.SetSize(size)); + if (rc.IsFailure()) return rc; + + if (_filePathHash.Data != 0) + { + throw new NotImplementedException(); + } + + setter.Set(oldWriteState); + return Result.Success; + } + + public Result GetSize(out long size) + { + Unsafe.SkipInit(out size); + + if (_lastResult.IsFailure()) + return _lastResult; + + return _file.GetSize(out size); + } + + public Result OperateRange(Span outBuffer, OperationId operationId, long offset, long size, + ReadOnlySpan inBuffer) + { + return _file.OperateRange(outBuffer, operationId, offset, size, inBuffer); + } + } +} diff --git a/src/LibHac/Fs/Fsa/FileSystemAccessor.cs b/src/LibHac/Fs/Fsa/FileSystemAccessor.cs new file mode 100644 index 00000000..ef21d925 --- /dev/null +++ b/src/LibHac/Fs/Fsa/FileSystemAccessor.cs @@ -0,0 +1,403 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using LibHac.Common; +using LibHac.Diag; +using LibHac.Fs.Fsa; +using LibHac.Os; +using LibHac.Util; +using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem; + +// ReSharper disable once CheckNamespace +namespace LibHac.Fs.Impl +{ + internal class FileSystemAccessor : IDisposable + { + private MountName _mountName; + private IFileSystem _fileSystem; + private LinkedList _openFiles; + private LinkedList _openDirectories; + private SdkMutexType _openListLock; + private ICommonMountNameGenerator _mountNameGenerator; + private ISaveDataAttributeGetter _saveDataAttributeGetter; + private bool _isAccessLogEnabled; + private bool _isDataCacheAttachable; + private bool _isPathCacheAttachable; + private bool _isPathCacheAttached; + private IMultiCommitTarget _multiCommitTarget; + + public FileSystemAccessor(U8Span name, IMultiCommitTarget multiCommitTarget, IFileSystem fileSystem, + ICommonMountNameGenerator mountNameGenerator, ISaveDataAttributeGetter saveAttributeGetter) + { + _fileSystem = fileSystem; + _openFiles = new LinkedList(); + _openDirectories = new LinkedList(); + _openListLock.Initialize(); + _mountNameGenerator = mountNameGenerator; + _saveDataAttributeGetter = saveAttributeGetter; + _multiCommitTarget = multiCommitTarget; + + if (name.IsEmpty()) + Abort.DoAbort(ResultFs.InvalidMountName.Log()); + + if (StringUtils.GetLength(name, PathTool.MountNameLengthMax + 1) > PathTool.MountNameLengthMax) + Abort.DoAbort(ResultFs.InvalidMountName.Log()); + + StringUtils.Copy(_mountName.Name.Slice(0, PathTool.MountNameLengthMax), name); + _mountName.Name[PathTool.MountNameLengthMax] = 0; + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (!disposing) return; + + using (ScopedLock.Lock(ref _openListLock)) + { + Abort.DoAbortUnless(_openFiles.Count == 0, ResultFs.FileNotClosed.Log(), + "All files must be closed before unmounting."); + + Abort.DoAbortUnless(_openDirectories.Count == 0, ResultFs.DirectoryNotClosed.Log(), + "All directories must be closed before unmounting."); + + if (_isPathCacheAttached) + { + throw new NotImplementedException(); + } + } + + _saveDataAttributeGetter?.Dispose(); + _saveDataAttributeGetter = null; + + _mountNameGenerator?.Dispose(); + _mountNameGenerator = null; + + _fileSystem?.Dispose(); + _fileSystem = null; + } + + private static void Remove(LinkedList list, T item) + { + LinkedListNode node = list.Find(item); + Abort.DoAbortUnless(node is not null, "Invalid file or directory object."); + + list.Remove(node); + } + + private static Result CheckPath(U8Span mountName, U8Span path) + { + int mountNameLength = StringUtils.GetLength(mountName, PathTool.MountNameLengthMax); + int pathLength = StringUtils.GetLength(path, PathTool.EntryNameLengthMax); + + if (mountNameLength + 1 + pathLength > PathTool.EntryNameLengthMax) + return ResultFs.TooLongPath.Log(); + + return Result.Success; + } + + private static bool HasOpenWriteModeFiles(LinkedList list) + { + for (LinkedListNode file = list.First; file is not null; file = file.Next) + { + if (file.Value.GetOpenMode().HasFlag(OpenMode.Write)) + { + return true; + } + } + + return false; + } + + public void SetAccessLog(bool isEnabled) => _isAccessLogEnabled = isEnabled; + public void SetFileDataCacheAttachable(bool isAttachable) => _isDataCacheAttachable = isAttachable; + public void SetPathBasedFileDataCacheAttachable(bool isAttachable) => _isPathCacheAttachable = isAttachable; + + public bool IsEnabledAccessLog() => _isAccessLogEnabled; + public bool IsFileDataCacheAttachable() => _isDataCacheAttachable; + public bool IsPathBasedFileDataCacheAttachable() => _isPathCacheAttachable; + + public void AttachPathBasedFileDataCache() + { + if (_isPathCacheAttachable) + _isPathCacheAttached = true; + } + + public Result CreateFile(U8Span path, long size, CreateFileOptions option) + { + Result rc = CheckPath(new U8Span(_mountName.Name), path); + if (rc.IsFailure()) return rc; + + if (_isPathCacheAttached) + { + throw new NotImplementedException(); + } + else + { + rc = _fileSystem.CreateFile(path, size, option); + if (rc.IsFailure()) return rc; + } + + return Result.Success; + } + + public Result DeleteFile(U8Span path) + { + Result rc = CheckPath(new U8Span(_mountName.Name), path); + if (rc.IsFailure()) return rc; + + return _fileSystem.DeleteDirectory(path); + } + + public Result CreateDirectory(U8Span path) + { + Result rc = CheckPath(new U8Span(_mountName.Name), path); + if (rc.IsFailure()) return rc; + + return _fileSystem.CreateDirectory(path); + } + + public Result DeleteDirectory(U8Span path) + { + Result rc = CheckPath(new U8Span(_mountName.Name), path); + if (rc.IsFailure()) return rc; + + return _fileSystem.DeleteDirectory(path); + } + + public Result DeleteDirectoryRecursively(U8Span path) + { + Result rc = CheckPath(new U8Span(_mountName.Name), path); + if (rc.IsFailure()) return rc; + + return _fileSystem.DeleteDirectoryRecursively(path); + } + + public Result CleanDirectoryRecursively(U8Span path) + { + Result rc = CheckPath(new U8Span(_mountName.Name), path); + if (rc.IsFailure()) return rc; + + return _fileSystem.CleanDirectoryRecursively(path); + } + + public Result RenameFile(U8Span oldPath, U8Span newPath) + { + Result rc = CheckPath(new U8Span(_mountName.Name), oldPath); + if (rc.IsFailure()) return rc; + + rc = CheckPath(new U8Span(_mountName.Name), newPath); + if (rc.IsFailure()) return rc; + + if (_isPathCacheAttached) + { + throw new NotImplementedException(); + } + else + { + rc = _fileSystem.RenameFile(oldPath, newPath); + if (rc.IsFailure()) return rc; + } + + return Result.Success; + } + + public Result RenameDirectory(U8Span oldPath, U8Span newPath) + { + Result rc = CheckPath(new U8Span(_mountName.Name), oldPath); + if (rc.IsFailure()) return rc; + + rc = CheckPath(new U8Span(_mountName.Name), newPath); + if (rc.IsFailure()) return rc; + + if (_isPathCacheAttached) + { + throw new NotImplementedException(); + } + else + { + rc = _fileSystem.RenameDirectory(oldPath, newPath); + if (rc.IsFailure()) return rc; + } + + return Result.Success; + } + + public Result GetEntryType(out DirectoryEntryType entryType, U8Span path) + { + Unsafe.SkipInit(out entryType); + + Result rc = CheckPath(new U8Span(_mountName.Name), path); + if (rc.IsFailure()) return rc; + + return _fileSystem.GetEntryType(out entryType, path); + } + + public Result GetFreeSpaceSize(out long freeSpace, U8Span path) + { + Unsafe.SkipInit(out freeSpace); + + Result rc = CheckPath(new U8Span(_mountName.Name), path); + if (rc.IsFailure()) return rc; + + return _fileSystem.GetFreeSpaceSize(out freeSpace, path); + } + + public Result GetTotalSpaceSize(out long totalSpace, U8Span path) + { + Unsafe.SkipInit(out totalSpace); + + Result rc = CheckPath(new U8Span(_mountName.Name), path); + if (rc.IsFailure()) return rc; + + return _fileSystem.GetTotalSpaceSize(out totalSpace, path); + } + + public Result OpenFile(out FileAccessor file, U8Span path, OpenMode mode) + { + file = default; + + Result rc = CheckPath(new U8Span(_mountName.Name), path); + if (rc.IsFailure()) return rc; + + IFile iFile = null; + try + { + rc = _fileSystem.OpenFile(out iFile, path, mode); + if (rc.IsFailure()) return rc; + + var fileAccessor = new FileAccessor(ref iFile, this, mode); + + using (ScopedLock.Lock(ref _openListLock)) + { + _openFiles.AddLast(fileAccessor); + } + + if (_isPathCacheAttached) + { + if (mode.HasFlag(OpenMode.AllowAppend)) + { + throw new NotImplementedException(); + } + else + { + throw new NotImplementedException(); + } + } + + file = Shared.Move(ref fileAccessor); + return Result.Success; + } + finally + { + iFile?.Dispose(); + } + } + + public Result OpenDirectory(out DirectoryAccessor directory, U8Span path, OpenDirectoryMode mode) + { + directory = default; + + Result rc = CheckPath(new U8Span(_mountName.Name), path); + if (rc.IsFailure()) return rc; + + IDirectory iDirectory = null; + try + { + rc = _fileSystem.OpenDirectory(out iDirectory, path, mode); + if (rc.IsFailure()) return rc; + + var directoryAccessor = new DirectoryAccessor(ref iDirectory, this); + + using (ScopedLock.Lock(ref _openListLock)) + { + _openDirectories.AddLast(directoryAccessor); + } + + directory = Shared.Move(ref directoryAccessor); + return Result.Success; + } + finally + { + iDirectory?.Dispose(); + } + } + + public Result Commit() + { + using (ScopedLock.Lock(ref _openListLock)) + { + DumpUnclosedAccessorList(OpenMode.Write, 0); + + if (HasOpenWriteModeFiles(_openFiles)) + return ResultFs.WriteModeFileNotClosed.Log(); + } + + return _fileSystem.Commit(); + } + + public Result GetFileTimeStampRaw(out FileTimeStampRaw timeStamp, U8Span path) + { + return _fileSystem.GetFileTimeStampRaw(out timeStamp, path); + } + + public Result QueryEntry(Span outBuffer, ReadOnlySpan inBuffer, QueryId queryId, U8Span path) + { + return _fileSystem.QueryEntry(outBuffer, inBuffer, queryId, path); + } + + public U8Span GetName() + { + return new U8Span(_mountName.Name); + } + + public Result GetCommonMountName(Span nameBuffer) + { + if (_mountNameGenerator is null) + return ResultFs.PreconditionViolation.Log(); + + return _mountNameGenerator.GenerateCommonMountName(nameBuffer); + } + + public Result GetSaveDataAttribute(out SaveDataAttribute attribute) + { + Unsafe.SkipInit(out attribute); + + if (_saveDataAttributeGetter is null) + return ResultFs.PreconditionViolation.Log(); + + return _saveDataAttributeGetter.GetSaveDataAttribute(out attribute); + } + + public ReferenceCountedDisposable GetMultiCommitTarget() + { + return _multiCommitTarget?.GetMultiCommitTarget(); + } + + public void PurgeFileDataCache(FileDataCacheAccessor cacheAccessor) + { + cacheAccessor.Purge(_fileSystem); + } + + public void NotifyCloseFile(FileAccessor file) + { + using ScopedLock lk = ScopedLock.Lock(ref _openListLock); + Remove(_openFiles, file); + } + + public void NotifyCloseDirectory(DirectoryAccessor directory) + { + using ScopedLock lk = ScopedLock.Lock(ref _openListLock); + Remove(_openDirectories, directory); + } + + private void DumpUnclosedAccessorList(OpenMode fileOpenModeMask, OpenDirectoryMode directoryOpenModeMask) + { + // Todo: Implement + } + } +} diff --git a/src/LibHac/Fs/Fsa/Registrar.cs b/src/LibHac/Fs/Fsa/Registrar.cs new file mode 100644 index 00000000..127d64b9 --- /dev/null +++ b/src/LibHac/Fs/Fsa/Registrar.cs @@ -0,0 +1,15 @@ +using System; + +namespace LibHac.Fs.Fsa +{ + public interface ICommonMountNameGenerator : IDisposable + { + Result GenerateCommonMountName(Span nameBuffer); + } + + public interface ISaveDataAttributeGetter : IDisposable + { + Result GetSaveDataAttribute(out SaveDataAttribute attribute); + } +} + diff --git a/src/LibHac/Fs/ICommonMountNameGenerator.cs b/src/LibHac/Fs/ICommonMountNameGenerator.cs deleted file mode 100644 index 32211de5..00000000 --- a/src/LibHac/Fs/ICommonMountNameGenerator.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace LibHac.Fs -{ - public interface ICommonMountNameGenerator - { - Result GenerateCommonMountName(Span nameBuffer); - } -} \ No newline at end of file diff --git a/src/LibHac/Fs/Impl/FileDataCacheAccessor.cs b/src/LibHac/Fs/Impl/FileDataCacheAccessor.cs new file mode 100644 index 00000000..0d1e2076 --- /dev/null +++ b/src/LibHac/Fs/Impl/FileDataCacheAccessor.cs @@ -0,0 +1,26 @@ +using System; +using LibHac.Fs.Fsa; + +namespace LibHac.Fs.Impl +{ + internal class FileDataCacheAccessor + { + private IFileDataCache _cache; + + public FileDataCacheAccessor(IFileDataCache cache) + { + _cache = cache; + } + + public Result Read(IFile file, out long bytesRead, long offset, Span destination, in ReadOption option, + ref FileDataCacheAccessResult cacheAccessResult) + { + return _cache.Read(file, out bytesRead, offset, destination, in option, ref cacheAccessResult); + } + + public void Purge(IFileSystem fileSystem) + { + _cache.Purge(fileSystem); + } + } +} diff --git a/src/LibHac/Fs/Impl/FilePathHash.cs b/src/LibHac/Fs/Impl/FilePathHash.cs new file mode 100644 index 00000000..65b1db8d --- /dev/null +++ b/src/LibHac/Fs/Impl/FilePathHash.cs @@ -0,0 +1,7 @@ +namespace LibHac.Fs.Impl +{ + public struct FilePathHash + { + public int Data; + } +} diff --git a/src/LibHac/Fs/Impl/FileRegion.cs b/src/LibHac/Fs/Impl/FileRegion.cs new file mode 100644 index 00000000..0d062c47 --- /dev/null +++ b/src/LibHac/Fs/Impl/FileRegion.cs @@ -0,0 +1,93 @@ +using System; +using LibHac.Diag; +using LibHac.Util; + +namespace LibHac.Fs.Impl +{ + public readonly struct FileRegion + { + public readonly long Offset; + public readonly long Size; + + public FileRegion(long offset, long size) + { + Offset = offset; + Size = size; + + Abort.DoAbortUnless(size >= 0); + } + + public long GetEndOffset() + { + return Offset + Size; + } + + public bool Includes(FileRegion other) + { + return Offset <= other.Offset && other.GetEndOffset() <= GetEndOffset(); + } + + public bool Intersects(FileRegion other) + { + return HasIntersection(this, other); + } + + public FileRegion GetIntersection(FileRegion other) + { + return GetIntersection(this, other); + } + + public FileRegion ExpandAndAlign(uint alignment) + { + long alignedStartOffset = Alignment.AlignDownPow2(Offset, alignment); + long alignedEndOffset = Alignment.AlignUpPow2(GetEndOffset(), alignment); + long alignedSize = alignedEndOffset - alignedStartOffset; + + return new FileRegion(alignedStartOffset, alignedSize); + } + + public FileRegion ShrinkAndAlign(uint alignment) + { + long alignedStartOffset = Alignment.AlignUpPow2(Offset, alignment); + long alignedEndOffset = Alignment.AlignDownPow2(GetEndOffset(), alignment); + long alignedSize = alignedEndOffset - alignedStartOffset; + + return new FileRegion(alignedStartOffset, alignedSize); + } + + public FileRegion GetEndRegionWithSizeLimit(long size) + { + if (size >= Size) + return this; + + return new FileRegion(GetEndOffset() - size, size); + } + + public static bool HasIntersection(FileRegion region1, FileRegion region2) + { + return region1.GetEndOffset() >= region2.Offset && + region2.GetEndOffset() >= region1.Offset; + } + + public static FileRegion GetIntersection(FileRegion region1, FileRegion region2) + { + if (!region1.Intersects(region2)) + return new FileRegion(); + + long intersectionStartOffset = Math.Max(region1.Offset, region2.Offset); + long intersectionEndOffset = Math.Min(region1.GetEndOffset(), region2.GetEndOffset()); + long intersectionSize = intersectionEndOffset - intersectionStartOffset; + + return new FileRegion(intersectionStartOffset, intersectionSize); + } + + public static FileRegion GetInclusion(FileRegion region1, FileRegion region2) + { + long inclusionStartOffset = Math.Min(region1.Offset, region2.Offset); + long inclusionEndOffset = Math.Max(region1.GetEndOffset(), region2.GetEndOffset()); + long inclusionSize = inclusionEndOffset - inclusionStartOffset; + + return new FileRegion(inclusionStartOffset, inclusionSize); + } + } +} diff --git a/src/LibHac/Fs/Impl/IFileDataCache.cs b/src/LibHac/Fs/Impl/IFileDataCache.cs new file mode 100644 index 00000000..136c908d --- /dev/null +++ b/src/LibHac/Fs/Impl/IFileDataCache.cs @@ -0,0 +1,92 @@ +using System; +using System.Runtime.CompilerServices; +using LibHac.Common.FixedArrays; +using LibHac.Diag; +using LibHac.Fs.Fsa; + +namespace LibHac.Fs.Impl +{ + // ReSharper disable once InconsistentNaming + internal abstract class IFileDataCache : IDisposable + { + public abstract void Dispose(); + + public abstract void Purge(IFileSystem fileSystem); + + protected abstract Result DoRead(IFile file, out long bytesRead, long offset, Span destination, + in ReadOption option, ref FileDataCacheAccessResult cacheAccessResult); + + public Result Read(IFile file, out long bytesRead, long offset, Span destination, in ReadOption option, + ref FileDataCacheAccessResult cacheAccessResult) + { + Unsafe.SkipInit(out bytesRead); + + if (destination.Length == 0) + { + bytesRead = 0; + cacheAccessResult.SetFileDataCacheUsed(true); + return Result.Success; + } + + if (offset < 0) + return ResultFs.OutOfRange.Log(); + + if (destination.Length < 0) + return ResultFs.OutOfRange.Log(); + + if (long.MaxValue - offset < destination.Length) + return ResultFs.OutOfRange.Log(); + + return DoRead(file, out bytesRead, offset, destination, in option, ref cacheAccessResult); + } + } + + internal struct FileDataCacheAccessResult + { + private const int MaxRegionCount = 8; + + private int _regionCount; + private Array8 _regions; + private bool _isFileDataCacheUsed; + private bool _exceededMaxRegionCount; + + public bool IsFileDataCacheUsed() => _isFileDataCacheUsed; + public bool SetFileDataCacheUsed(bool useFileDataCache) => _isFileDataCacheUsed = useFileDataCache; + + public int GetCacheFetchedRegionCount() + { + Assert.True(_isFileDataCacheUsed); + return _regionCount; + } + + public bool ExceededMaxCacheFetchedRegionCount() => _exceededMaxRegionCount; + + public FileRegion GetCacheFetchedRegion(int index) + { + Assert.True(IsFileDataCacheUsed()); + Assert.True(index >= 0); + Assert.True(index < _regionCount); + + return _regions[index]; + } + + public void AddCacheFetchedRegion(FileRegion region) + { + _isFileDataCacheUsed = true; + + if (region.Size == 0) + return; + + if (_regionCount >= MaxRegionCount) + { + _regions[MaxRegionCount - 1] = region; + _exceededMaxRegionCount = true; + } + else + { + _regions[_regionCount] = region; + _regionCount++; + } + } + } +} diff --git a/src/LibHac/Fs/ScopedSetter.cs b/src/LibHac/Fs/ScopedSetter.cs new file mode 100644 index 00000000..68e04354 --- /dev/null +++ b/src/LibHac/Fs/ScopedSetter.cs @@ -0,0 +1,29 @@ +using LibHac.Common; + +namespace LibHac.Fs +{ + public ref struct ScopedSetter + { + private Ref _ref; + private T _value; + + public ScopedSetter(ref T reference, T value) + { + _ref = new Ref(ref reference); + _value = value; + } + + public void Dispose() + { + if (!_ref.IsNull) + _ref.Value = _value; + } + + public void Set(T value) => _value = value; + + public static ScopedSetter MakeScopedSetter(ref T reference, T value) + { + return new ScopedSetter(ref reference, value); + } + } +} diff --git a/src/LibHac/Fs/Shim/Bis.cs b/src/LibHac/Fs/Shim/Bis.cs index 99fb53fd..11bccacd 100644 --- a/src/LibHac/Fs/Shim/Bis.cs +++ b/src/LibHac/Fs/Shim/Bis.cs @@ -2,6 +2,7 @@ using System.Diagnostics; using System.Runtime.CompilerServices; using LibHac.Common; +using LibHac.Fs.Fsa; using LibHac.Fs.Impl; using LibHac.FsSrv.Sf; using LibHac.FsSystem; @@ -23,6 +24,8 @@ namespace LibHac.Fs.Shim PartitionId = partitionId; } + public void Dispose() { } + public Result GenerateCommonMountName(Span nameBuffer) { U8Span mountName = GetBisMountName(PartitionId); diff --git a/src/LibHac/Fs/Shim/ContentStorage.cs b/src/LibHac/Fs/Shim/ContentStorage.cs index bc3dc90d..09dfcc16 100644 --- a/src/LibHac/Fs/Shim/ContentStorage.cs +++ b/src/LibHac/Fs/Shim/ContentStorage.cs @@ -1,5 +1,6 @@ using System; using LibHac.Common; +using LibHac.Fs.Fsa; using LibHac.Fs.Impl; using LibHac.FsSrv.Sf; using LibHac.Util; @@ -59,6 +60,8 @@ namespace LibHac.Fs.Shim StorageId = storageId; } + public void Dispose() { } + public Result GenerateCommonMountName(Span nameBuffer) { U8String mountName = GetContentStorageMountName(StorageId); diff --git a/src/LibHac/Fs/Shim/GameCard.cs b/src/LibHac/Fs/Shim/GameCard.cs index 6ef4e071..1b9e1842 100644 --- a/src/LibHac/Fs/Shim/GameCard.cs +++ b/src/LibHac/Fs/Shim/GameCard.cs @@ -1,5 +1,6 @@ using System; using LibHac.Common; +using LibHac.Fs.Fsa; using LibHac.Fs.Impl; using LibHac.FsSrv; using LibHac.FsSrv.Sf; @@ -105,6 +106,8 @@ namespace LibHac.Fs.Shim PartitionId = partitionId; } + public void Dispose() { } + public Result GenerateCommonMountName(Span nameBuffer) { char letter = GetGameCardMountNameSuffix(PartitionId); diff --git a/src/LibHac/Fs/Shim/Host.cs b/src/LibHac/Fs/Shim/Host.cs index c8e6301b..8d361704 100644 --- a/src/LibHac/Fs/Shim/Host.cs +++ b/src/LibHac/Fs/Shim/Host.cs @@ -2,6 +2,7 @@ using System.Diagnostics; using System.Runtime.CompilerServices; using LibHac.Common; +using LibHac.Fs.Fsa; using LibHac.Fs.Impl; using LibHac.FsSrv.Sf; using LibHac.FsSystem; @@ -38,6 +39,8 @@ namespace LibHac.Fs.Shim } } + public void Dispose() { } + public Result GenerateCommonMountName(Span nameBuffer) { int requiredNameBufferSize = StringUtils.GetLength(_path.Str, FsPath.MaxLength) + HostRootFileSystemPathLength; @@ -56,6 +59,8 @@ namespace LibHac.Fs.Shim private class HostRootCommonMountNameGenerator : ICommonMountNameGenerator { + public void Dispose() { } + public Result GenerateCommonMountName(Span nameBuffer) { const int requiredNameBufferSize = HostRootFileSystemPathLength; diff --git a/src/LibHac/Fs/Shim/SaveDataManagement.cs b/src/LibHac/Fs/Shim/SaveDataManagement.cs index fe521980..06302a6e 100644 --- a/src/LibHac/Fs/Shim/SaveDataManagement.cs +++ b/src/LibHac/Fs/Shim/SaveDataManagement.cs @@ -397,7 +397,7 @@ namespace LibHac.Fs.Shim if (rc.IsFailure()) { - Abort.DoAbort(); + Abort.DoAbort(rc); } } }