From 4934b1cbef29844f8ebc87fa3f58d26bef1c06ca Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Sun, 18 Jul 2021 19:02:34 -0700 Subject: [PATCH] Update FileSystemInterfaceAdapter --- src/LibHac/Fs/Common/Path.cs | 1 + .../FsSrv/Impl/DirectoryInterfaceAdapter.cs | 66 -- src/LibHac/FsSrv/Impl/FileInterfaceAdapter.cs | 133 --- .../FsSrv/Impl/FileSystemInterfaceAdapter.cs | 844 +++++++++++++----- src/LibHac/FsSrv/Sf/IFileSystem.cs | 7 +- src/LibHac/FsSystem/ThreadPriorityChanger.cs | 51 ++ 6 files changed, 654 insertions(+), 448 deletions(-) delete mode 100644 src/LibHac/FsSrv/Impl/DirectoryInterfaceAdapter.cs delete mode 100644 src/LibHac/FsSrv/Impl/FileInterfaceAdapter.cs create mode 100644 src/LibHac/FsSystem/ThreadPriorityChanger.cs diff --git a/src/LibHac/Fs/Common/Path.cs b/src/LibHac/Fs/Common/Path.cs index d5772660..0452f5f3 100644 --- a/src/LibHac/Fs/Common/Path.cs +++ b/src/LibHac/Fs/Common/Path.cs @@ -39,6 +39,7 @@ namespace LibHac.Fs private int _writeBufferLength; private bool _isNormalized; + // Todo: Hack around "using" variables being read only public void Dispose() { byte[] writeBuffer = Shared.Move(ref _writeBuffer); diff --git a/src/LibHac/FsSrv/Impl/DirectoryInterfaceAdapter.cs b/src/LibHac/FsSrv/Impl/DirectoryInterfaceAdapter.cs deleted file mode 100644 index c3872548..00000000 --- a/src/LibHac/FsSrv/Impl/DirectoryInterfaceAdapter.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; -using System.Runtime.InteropServices; -using LibHac.Common; -using LibHac.Fs; -using LibHac.Sf; -using IDirectory = LibHac.Fs.Fsa.IDirectory; -using IDirectorySf = LibHac.FsSrv.Sf.IDirectory; - -namespace LibHac.FsSrv.Impl -{ - public class DirectoryInterfaceAdapter : IDirectorySf - { - private ReferenceCountedDisposable ParentFs { get; } - private IDirectory BaseDirectory { get; } - - public DirectoryInterfaceAdapter(IDirectory baseDirectory, - ref ReferenceCountedDisposable parentFileSystem) - { - BaseDirectory = baseDirectory; - ParentFs = parentFileSystem; - parentFileSystem = null; - } - - public Result Read(out long entriesRead, OutBuffer entryBuffer) - { - const int maxTryCount = 2; - UnsafeHelpers.SkipParamInit(out entriesRead); - - Span entries = MemoryMarshal.Cast(entryBuffer.Buffer); - - Result rc = Result.Success; - long tmpEntriesRead = 0; - - for (int tryNum = 0; tryNum < maxTryCount; tryNum++) - { - rc = BaseDirectory.Read(out tmpEntriesRead, entries); - - // Retry on ResultDataCorrupted - if (!ResultFs.DataCorrupted.Includes(rc)) - break; - } - - if (rc.IsFailure()) return rc; - - entriesRead = tmpEntriesRead; - return Result.Success; - } - - public Result GetEntryCount(out long entryCount) - { - UnsafeHelpers.SkipParamInit(out entryCount); - - Result rc = BaseDirectory.GetEntryCount(out long tmpEntryCount); - if (rc.IsFailure()) return rc; - - entryCount = tmpEntryCount; - return Result.Success; - } - - public void Dispose() - { - BaseDirectory?.Dispose(); - ParentFs?.Dispose(); - } - } -} diff --git a/src/LibHac/FsSrv/Impl/FileInterfaceAdapter.cs b/src/LibHac/FsSrv/Impl/FileInterfaceAdapter.cs deleted file mode 100644 index 5196b0ef..00000000 --- a/src/LibHac/FsSrv/Impl/FileInterfaceAdapter.cs +++ /dev/null @@ -1,133 +0,0 @@ -using System; -using System.Runtime.CompilerServices; -using LibHac.Common; -using LibHac.Fs; -using LibHac.Sf; -using IFile = LibHac.Fs.Fsa.IFile; -using IFileSf = LibHac.FsSrv.Sf.IFile; - -namespace LibHac.FsSrv.Impl -{ - public class FileInterfaceAdapter : IFileSf - { - private ReferenceCountedDisposable ParentFs { get; } - private IFile BaseFile { get; } - - public FileInterfaceAdapter(IFile baseFile, - ref ReferenceCountedDisposable parentFileSystem) - { - BaseFile = baseFile; - ParentFs = parentFileSystem; - parentFileSystem = null; - } - - public Result Read(out long bytesRead, long offset, OutBuffer destination, long size, ReadOption option) - { - const int maxTryCount = 2; - UnsafeHelpers.SkipParamInit(out bytesRead); - - if (offset < 0) - return ResultFs.InvalidOffset.Log(); - - if (destination.Size < 0) - return ResultFs.InvalidSize.Log(); - - Result rc = Result.Success; - long tmpBytesRead = 0; - - for (int tryNum = 0; tryNum < maxTryCount; tryNum++) - { - rc = BaseFile.Read(out tmpBytesRead, offset, destination.Buffer.Slice(0, (int)size), option); - - // Retry on ResultDataCorrupted - if (!ResultFs.DataCorrupted.Includes(rc)) - break; - } - - if (rc.IsFailure()) return rc; - - bytesRead = tmpBytesRead; - return Result.Success; - } - - public Result Write(long offset, InBuffer source, long size, WriteOption option) - { - if (offset < 0) - return ResultFs.InvalidOffset.Log(); - - if (source.Size < 0) - return ResultFs.InvalidSize.Log(); - - // Note: Thread priority is temporarily increased when writing in FS - - return BaseFile.Write(offset, source.Buffer.Slice(0, (int)size), option); - } - - public Result Flush() - { - return BaseFile.Flush(); - } - - public Result SetSize(long size) - { - if (size < 0) - return ResultFs.InvalidSize.Log(); - - return BaseFile.SetSize(size); - } - - public Result GetSize(out long size) - { - const int maxTryCount = 2; - UnsafeHelpers.SkipParamInit(out size); - - Result rc = Result.Success; - long tmpSize = 0; - - for (int tryNum = 0; tryNum < maxTryCount; tryNum++) - { - rc = BaseFile.GetSize(out tmpSize); - - // Retry on ResultDataCorrupted - if (!ResultFs.DataCorrupted.Includes(rc)) - break; - } - - if (rc.IsFailure()) return rc; - - size = tmpSize; - return Result.Success; - } - - public Result OperateRange(out QueryRangeInfo rangeInfo, int operationId, long offset, long size) - { - UnsafeHelpers.SkipParamInit(out rangeInfo); - rangeInfo.Clear(); - - if (operationId == (int)OperationId.InvalidateCache) - { - Result rc = BaseFile.OperateRange(Span.Empty, OperationId.InvalidateCache, offset, size, - ReadOnlySpan.Empty); - if (rc.IsFailure()) return rc; - } - else if (operationId == (int)OperationId.QueryRange) - { - Unsafe.SkipInit(out QueryRangeInfo info); - - Result rc = BaseFile.OperateRange(SpanHelpers.AsByteSpan(ref info), OperationId.QueryRange, offset, - size, ReadOnlySpan.Empty); - if (rc.IsFailure()) return rc; - - rangeInfo.Merge(in info); - } - - return Result.Success; - } - - public void Dispose() - { - BaseFile?.Dispose(); - ParentFs?.Dispose(); - } - } -} \ No newline at end of file diff --git a/src/LibHac/FsSrv/Impl/FileSystemInterfaceAdapter.cs b/src/LibHac/FsSrv/Impl/FileSystemInterfaceAdapter.cs index 5cbc915d..74eaccf8 100644 --- a/src/LibHac/FsSrv/Impl/FileSystemInterfaceAdapter.cs +++ b/src/LibHac/FsSrv/Impl/FileSystemInterfaceAdapter.cs @@ -1,292 +1,644 @@ using System; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; -using LibHac.Util; +using LibHac.FsSystem; +using LibHac.Sf; + +using IFile = LibHac.Fs.Fsa.IFile; +using IFileSf = LibHac.FsSrv.Sf.IFile; +using IDirectory = LibHac.Fs.Fsa.IDirectory; +using IDirectorySf = LibHac.FsSrv.Sf.IDirectory; using IFileSystem = LibHac.Fs.Fsa.IFileSystem; using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem; -using IFileSf = LibHac.FsSrv.Sf.IFile; -using IDirectorySf = LibHac.FsSrv.Sf.IDirectory; -using Path = LibHac.FsSrv.Sf.Path; +using PathSf = LibHac.FsSrv.Sf.Path; namespace LibHac.FsSrv.Impl { + /// + /// Wraps an to allow interfacing with it via the interface over IPC. + /// + /// Based on FS 12.0.3 (nnSdk 12.3.1) + public class FileInterfaceAdapter : IFileSf + { + private ReferenceCountedDisposable _parentFs; + private IFile _baseFile; + private bool _allowAllOperations; + + public FileInterfaceAdapter(IFile baseFile, + ref ReferenceCountedDisposable parentFileSystem, bool allowAllOperations) + { + _baseFile = baseFile; + _parentFs = Shared.Move(ref parentFileSystem); + _allowAllOperations = allowAllOperations; + } + + public void Dispose() + { + _baseFile?.Dispose(); + _parentFs?.Dispose(); + } + + public Result Read(out long bytesRead, long offset, OutBuffer destination, long size, ReadOption option) + { + const int maxTryCount = 2; + UnsafeHelpers.SkipParamInit(out bytesRead); + + if (offset < 0) + return ResultFs.InvalidOffset.Log(); + + if (destination.Size < 0 || destination.Size < (int)size) + return ResultFs.InvalidSize.Log(); + + Result rc = Result.Success; + long tmpBytesRead = 0; + + for (int tryNum = 0; tryNum < maxTryCount; tryNum++) + { + rc = _baseFile.Read(out tmpBytesRead, offset, destination.Buffer.Slice(0, (int)size), option); + + // Retry on ResultDataCorrupted + if (!ResultFs.DataCorrupted.Includes(rc)) + break; + } + + if (rc.IsFailure()) return rc; + + bytesRead = tmpBytesRead; + return Result.Success; + } + + public Result Write(long offset, InBuffer source, long size, WriteOption option) + { + if (offset < 0) + return ResultFs.InvalidOffset.Log(); + + if (source.Size < 0 || source.Size < (int)size) + return ResultFs.InvalidSize.Log(); + + using var scopedPriorityChanger = + new ScopedThreadPriorityChangerByAccessPriority(ScopedThreadPriorityChangerByAccessPriority.AccessMode.Write); + + return _baseFile.Write(offset, source.Buffer.Slice(0, (int)size), option); + } + + public Result Flush() + { + return _baseFile.Flush(); + } + + public Result SetSize(long size) + { + if (size < 0) + return ResultFs.InvalidSize.Log(); + + return _baseFile.SetSize(size); + } + + public Result GetSize(out long size) + { + const int maxTryCount = 2; + UnsafeHelpers.SkipParamInit(out size); + + Result rc = Result.Success; + long tmpSize = 0; + + for (int tryNum = 0; tryNum < maxTryCount; tryNum++) + { + rc = _baseFile.GetSize(out tmpSize); + + // Retry on ResultDataCorrupted + if (!ResultFs.DataCorrupted.Includes(rc)) + break; + } + + if (rc.IsFailure()) return rc; + + size = tmpSize; + return Result.Success; + } + + public Result OperateRange(out QueryRangeInfo rangeInfo, int operationId, long offset, long size) + { + UnsafeHelpers.SkipParamInit(out rangeInfo); + rangeInfo.Clear(); + + if (operationId == (int)OperationId.QueryRange) + { + Unsafe.SkipInit(out QueryRangeInfo info); + + Result rc = _baseFile.OperateRange(SpanHelpers.AsByteSpan(ref info), OperationId.QueryRange, offset, + size, ReadOnlySpan.Empty); + if (rc.IsFailure()) return rc; + + rangeInfo.Merge(in info); + } + else if (operationId == (int)OperationId.InvalidateCache) + { + Result rc = _baseFile.OperateRange(Span.Empty, OperationId.InvalidateCache, offset, size, + ReadOnlySpan.Empty); + if (rc.IsFailure()) return rc; + } + + return Result.Success; + } + + public Result OperateRangeWithBuffer(OutBuffer outBuffer, InBuffer inBuffer, int operationId, long offset, long size) + { + static Result PermissionCheck(OperationId operationId, FileInterfaceAdapter fileAdapter) + { + if (operationId == OperationId.QueryUnpreparedRange || + operationId == OperationId.QueryLazyLoadCompletionRate || + operationId == OperationId.SetLazyLoadPriority) + { + return Result.Success; + } + + if (!fileAdapter._allowAllOperations) + return ResultFs.PermissionDenied.Log(); + + return Result.Success; + } + + Result rc = PermissionCheck((OperationId)operationId, this); + if (rc.IsFailure()) return rc; + + rc = _baseFile.OperateRange(outBuffer.Buffer, (OperationId)operationId, offset, size, inBuffer.Buffer); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + } + + /// + /// Wraps an to allow interfacing with it via the interface over IPC. + /// + /// Based on FS 12.0.3 (nnSdk 12.3.1) + public class DirectoryInterfaceAdapter : IDirectorySf + { + private ReferenceCountedDisposable _parentFs; + private IDirectory _baseDirectory; + + public DirectoryInterfaceAdapter(IDirectory baseDirectory, + ref ReferenceCountedDisposable parentFileSystem) + { + _baseDirectory = baseDirectory; + _parentFs = Shared.Move(ref parentFileSystem); + } + + public void Dispose() + { + _baseDirectory?.Dispose(); + _parentFs?.Dispose(); + } + + public Result Read(out long entriesRead, OutBuffer entryBuffer) + { + const int maxTryCount = 2; + UnsafeHelpers.SkipParamInit(out entriesRead); + + Span entries = MemoryMarshal.Cast(entryBuffer.Buffer); + + Result rc = Result.Success; + long numRead = 0; + + for (int tryNum = 0; tryNum < maxTryCount; tryNum++) + { + rc = _baseDirectory.Read(out numRead, entries); + + // Retry on ResultDataCorrupted + if (!ResultFs.DataCorrupted.Includes(rc)) + break; + } + + if (rc.IsFailure()) return rc; + + entriesRead = numRead; + return Result.Success; + } + + public Result GetEntryCount(out long entryCount) + { + UnsafeHelpers.SkipParamInit(out entryCount); + + Result rc = _baseDirectory.GetEntryCount(out long count); + if (rc.IsFailure()) return rc; + + entryCount = count; + return Result.Success; + } + } + + /// + /// Wraps an to allow interfacing with it via the interface over IPC. + /// All incoming paths are normalized before they are passed to the base . + /// + /// Based on FS 12.0.3 (nnSdk 12.3.1) public class FileSystemInterfaceAdapter : IFileSystemSf { - private ReferenceCountedDisposable BaseFileSystem { get; } - private bool IsHostFsRoot { get; } + private ReferenceCountedDisposable _baseFileSystem; + private PathFlags _pathFlags; + + // This field is always false in FS 12.0.0. Not sure what it's actually used for. + private bool _allowAllOperations; // In FS, FileSystemInterfaceAdapter is derived from ISharedObject, so that's used for ref-counting when // creating files and directories. We don't have an ISharedObject, so a self-reference is used instead. private ReferenceCountedDisposable.WeakReference _selfReference; - /// - /// Initializes a new by creating - /// a new reference to . - /// - /// The base file system. - /// Does the base file system come from the root directory of a host file system? - private FileSystemInterfaceAdapter(ReferenceCountedDisposable fileSystem, - bool isHostFsRoot = false) - { - BaseFileSystem = fileSystem.AddReference(); - IsHostFsRoot = isHostFsRoot; - } - - /// - /// Initializes a new by moving the file system object. - /// Avoids allocations from incrementing and then decrementing the ref-count. - /// - /// The base file system. Will be null upon returning. - /// Does the base file system come from the root directory of a host file system? private FileSystemInterfaceAdapter(ref ReferenceCountedDisposable fileSystem, - bool isHostFsRoot = false) + bool allowAllOperations) { - BaseFileSystem = fileSystem; - fileSystem = null; - IsHostFsRoot = isHostFsRoot; + _baseFileSystem = Shared.Move(ref fileSystem); + _allowAllOperations = allowAllOperations; } - /// - /// Initializes a new , creating a copy of the input file system object. - /// - /// The base file system. - /// Does the base file system come from the root directory of a host file system? - public static ReferenceCountedDisposable CreateShared( - ReferenceCountedDisposable baseFileSystem, bool isHostFsRoot = false) + private FileSystemInterfaceAdapter(ref ReferenceCountedDisposable fileSystem, PathFlags flags, + bool allowAllOperations) { - var adapter = new FileSystemInterfaceAdapter(baseFileSystem, isHostFsRoot); - - return ReferenceCountedDisposable.Create(adapter, out adapter._selfReference); + _baseFileSystem = Shared.Move(ref fileSystem); + _pathFlags = flags; + _allowAllOperations = allowAllOperations; } - /// - /// Initializes a new cast to an - /// by moving the input file system object. Avoids allocations from incrementing and then decrementing the ref-count. - /// - /// The base file system. Will be null upon returning. - /// Does the base file system come from the root directory of a host file system? public static ReferenceCountedDisposable CreateShared( - ref ReferenceCountedDisposable baseFileSystem, bool isHostFsRoot = false) + ref ReferenceCountedDisposable baseFileSystem, bool allowAllOperations) { - var adapter = new FileSystemInterfaceAdapter(ref baseFileSystem, isHostFsRoot); - - return ReferenceCountedDisposable.Create(adapter, out adapter._selfReference); - } - - private static ReadOnlySpan RootDir => new[] { (byte)'/' }; - - public Result GetImpl(out ReferenceCountedDisposable fileSystem) - { - fileSystem = BaseFileSystem.AddReference(); - return Result.Success; - } - - public Result CreateFile(in Path path, long size, int option) - { - if (size < 0) - return ResultFs.InvalidSize.Log(); - - using var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption()); - if (normalizer.Result.IsFailure()) return normalizer.Result; - - return BaseFileSystem.Target.CreateFile(normalizer.Path, size, (CreateFileOptions)option); - } - - public Result DeleteFile(in Path path) - { - using var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption()); - if (normalizer.Result.IsFailure()) return normalizer.Result; - - return BaseFileSystem.Target.DeleteFile(normalizer.Path); - } - - public Result CreateDirectory(in Path path) - { - using var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption()); - if (normalizer.Result.IsFailure()) return normalizer.Result; - - if (StringUtils.Compare(RootDir, normalizer.Path) == 0) - return ResultFs.PathAlreadyExists.Log(); - - return BaseFileSystem.Target.CreateDirectory(normalizer.Path); - } - - public Result DeleteDirectory(in Path path) - { - using var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption()); - if (normalizer.Result.IsFailure()) return normalizer.Result; - - if (StringUtils.Compare(RootDir, normalizer.Path) == 0) - return ResultFs.DirectoryNotDeletable.Log(); - - return BaseFileSystem.Target.DeleteDirectory(normalizer.Path); - } - - public Result DeleteDirectoryRecursively(in Path path) - { - using var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption()); - if (normalizer.Result.IsFailure()) return normalizer.Result; - - if (StringUtils.Compare(RootDir, normalizer.Path) == 0) - return ResultFs.DirectoryNotDeletable.Log(); - - return BaseFileSystem.Target.DeleteDirectoryRecursively(normalizer.Path); - } - - public Result RenameFile(in Path oldPath, in Path newPath) - { - using var normalizerOldPath = new PathNormalizer(new U8Span(oldPath.Str), GetPathNormalizerOption()); - if (normalizerOldPath.Result.IsFailure()) return normalizerOldPath.Result; - - using var normalizerNewPath = new PathNormalizer(new U8Span(newPath.Str), GetPathNormalizerOption()); - if (normalizerNewPath.Result.IsFailure()) return normalizerNewPath.Result; - - return BaseFileSystem.Target.RenameFile(new U8Span(normalizerOldPath.Path), - new U8Span(normalizerNewPath.Path)); - } - - public Result RenameDirectory(in Path oldPath, in Path newPath) - { - using var normalizerOldPath = new PathNormalizer(new U8Span(oldPath.Str), GetPathNormalizerOption()); - if (normalizerOldPath.Result.IsFailure()) return normalizerOldPath.Result; - - using var normalizerNewPath = new PathNormalizer(new U8Span(newPath.Str), GetPathNormalizerOption()); - if (normalizerNewPath.Result.IsFailure()) return normalizerNewPath.Result; - - if (PathUtility.IsSubPath(normalizerOldPath.Path, normalizerNewPath.Path)) - return ResultFs.DirectoryNotRenamable.Log(); - - return BaseFileSystem.Target.RenameDirectory(normalizerOldPath.Path, normalizerNewPath.Path); - } - - public Result GetEntryType(out uint entryType, in Path path) - { - UnsafeHelpers.SkipParamInit(out entryType); - - using var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption()); - if (normalizer.Result.IsFailure()) return normalizer.Result; - - ref DirectoryEntryType type = ref Unsafe.As(ref entryType); - - return BaseFileSystem.Target.GetEntryType(out type, new U8Span(normalizer.Path)); - } - - public Result OpenFile(out ReferenceCountedDisposable file, in Path path, uint mode) - { - const int maxTryCount = 2; - UnsafeHelpers.SkipParamInit(out file); - - using var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption()); - if (normalizer.Result.IsFailure()) return normalizer.Result; - - Result rc = Result.Success; - IFile fileInterface = null; - - for (int tryNum = 0; tryNum < maxTryCount; tryNum++) + ReferenceCountedDisposable sharedAdapter = null; + try { - rc = BaseFileSystem.Target.OpenFile(out fileInterface, new U8Span(normalizer.Path), (OpenMode)mode); + var adapter = new FileSystemInterfaceAdapter(ref baseFileSystem, allowAllOperations); + sharedAdapter = new ReferenceCountedDisposable(adapter); - // Retry on ResultDataCorrupted - if (!ResultFs.DataCorrupted.Includes(rc)) - break; + adapter._selfReference = + new ReferenceCountedDisposable.WeakReference(sharedAdapter); + + return sharedAdapter.AddReference(); } - - if (rc.IsFailure()) return rc; - - ReferenceCountedDisposable selfReference = _selfReference.TryAddReference(); - var adapter = new FileInterfaceAdapter(fileInterface, ref selfReference); - file = new ReferenceCountedDisposable(adapter); - - return Result.Success; - } - - public Result OpenDirectory(out ReferenceCountedDisposable directory, in Path path, uint mode) - { - const int maxTryCount = 2; - UnsafeHelpers.SkipParamInit(out directory); - - using var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption()); - if (normalizer.Result.IsFailure()) return normalizer.Result; - - Result rc = Result.Success; - IDirectory dirInterface = null; - - for (int tryNum = 0; tryNum < maxTryCount; tryNum++) + finally { - rc = BaseFileSystem.Target.OpenDirectory(out dirInterface, new U8Span(normalizer.Path), (OpenDirectoryMode)mode); - - // Retry on ResultDataCorrupted - if (!ResultFs.DataCorrupted.Includes(rc)) - break; + sharedAdapter?.Dispose(); } - - if (rc.IsFailure()) return rc; - - ReferenceCountedDisposable selfReference = _selfReference.TryAddReference(); - var adapter = new DirectoryInterfaceAdapter(dirInterface, ref selfReference); - directory = new ReferenceCountedDisposable(adapter); - - return Result.Success; } - public Result Commit() + public static ReferenceCountedDisposable CreateShared(PathFlags flags, + ref ReferenceCountedDisposable baseFileSystem, bool allowAllOperations) { - return BaseFileSystem.Target.Commit(); - } + ReferenceCountedDisposable sharedAdapter = null; + try + { + var adapter = new FileSystemInterfaceAdapter(ref baseFileSystem, flags, allowAllOperations); + sharedAdapter = new ReferenceCountedDisposable(adapter); - public Result GetFreeSpaceSize(out long freeSpace, in Path path) - { - UnsafeHelpers.SkipParamInit(out freeSpace); + adapter._selfReference = + new ReferenceCountedDisposable.WeakReference(sharedAdapter); - using var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption()); - if (normalizer.Result.IsFailure()) return normalizer.Result; - - return BaseFileSystem.Target.GetFreeSpaceSize(out freeSpace, normalizer.Path); - } - - public Result GetTotalSpaceSize(out long totalSpace, in Path path) - { - UnsafeHelpers.SkipParamInit(out totalSpace); - - using var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption()); - if (normalizer.Result.IsFailure()) return normalizer.Result; - - return BaseFileSystem.Target.GetTotalSpaceSize(out totalSpace, normalizer.Path); - } - - public Result CleanDirectoryRecursively(in Path path) - { - using var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption()); - if (normalizer.Result.IsFailure()) return normalizer.Result; - - return BaseFileSystem.Target.CleanDirectoryRecursively(normalizer.Path); - } - - public Result GetFileTimeStampRaw(out FileTimeStampRaw timeStamp, in Path path) - { - UnsafeHelpers.SkipParamInit(out timeStamp); - - using var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption()); - if (normalizer.Result.IsFailure()) return normalizer.Result; - - return BaseFileSystem.Target.GetFileTimeStampRaw(out timeStamp, normalizer.Path); - } - - public Result QueryEntry(Span outBuffer, ReadOnlySpan inBuffer, int queryId, in Path path) - { - return BaseFileSystem.Target.QueryEntry(outBuffer, inBuffer, (QueryId)queryId, new U8Span(path.Str)); + return sharedAdapter.AddReference(); + } + finally + { + sharedAdapter?.Dispose(); + } } public void Dispose() { - BaseFileSystem?.Dispose(); + ReferenceCountedDisposable tempFs = Shared.Move(ref _baseFileSystem); + tempFs?.Dispose(); } - private PathNormalizer.Option GetPathNormalizerOption() + private static ReadOnlySpan RootDir => new[] { (byte)'/' }; + + private Result SetUpPath(ref Path fsPath, in PathSf sfPath) { - return IsHostFsRoot ? PathNormalizer.Option.PreserveUnc : PathNormalizer.Option.None; + Result rc; + + if (_pathFlags.IsWindowsPathAllowed()) + { + rc = fsPath.InitializeWithReplaceUnc(sfPath.Str); + if (rc.IsFailure()) return rc; + } + else + { + rc = fsPath.Initialize(sfPath.Str); + if (rc.IsFailure()) return rc; + } + + rc = fsPath.Normalize(_pathFlags); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + public Result CreateFile(in PathSf path, long size, int option) + { + if (size < 0) + return ResultFs.InvalidSize.Log(); + + var pathNormalized = new Path(); + Result rc = SetUpPath(ref pathNormalized, in path); + if (rc.IsFailure()) return rc; + + rc = _baseFileSystem.Target.CreateFile(in pathNormalized, size, (CreateFileOptions)option); + if (rc.IsFailure()) return rc; + + pathNormalized.Dispose(); + return Result.Success; + } + + public Result DeleteFile(in PathSf path) + { + var pathNormalized = new Path(); + Result rc = SetUpPath(ref pathNormalized, in path); + if (rc.IsFailure()) return rc; + + rc = _baseFileSystem.Target.DeleteFile(in pathNormalized); + if (rc.IsFailure()) return rc; + + pathNormalized.Dispose(); + return Result.Success; + } + + public Result CreateDirectory(in PathSf path) + { + var pathNormalized = new Path(); + Result rc = SetUpPath(ref pathNormalized, in path); + if (rc.IsFailure()) return rc; + + if (pathNormalized == RootDir) + return ResultFs.PathAlreadyExists.Log(); + + rc = _baseFileSystem.Target.CreateDirectory(in pathNormalized); + if (rc.IsFailure()) return rc; + + pathNormalized.Dispose(); + return Result.Success; + } + + public Result DeleteDirectory(in PathSf path) + { + var pathNormalized = new Path(); + Result rc = SetUpPath(ref pathNormalized, in path); + if (rc.IsFailure()) return rc; + + if (pathNormalized == RootDir) + return ResultFs.DirectoryNotDeletable.Log(); + + rc = _baseFileSystem.Target.DeleteDirectory(in pathNormalized); + if (rc.IsFailure()) return rc; + + pathNormalized.Dispose(); + return Result.Success; + } + + public Result DeleteDirectoryRecursively(in PathSf path) + { + var pathNormalized = new Path(); + Result rc = SetUpPath(ref pathNormalized, in path); + if (rc.IsFailure()) return rc; + + if (pathNormalized == RootDir) + return ResultFs.DirectoryNotDeletable.Log(); + + rc = _baseFileSystem.Target.DeleteDirectoryRecursively(in pathNormalized); + if (rc.IsFailure()) return rc; + + pathNormalized.Dispose(); + return Result.Success; + } + + public Result CleanDirectoryRecursively(in PathSf path) + { + var pathNormalized = new Path(); + Result rc = SetUpPath(ref pathNormalized, in path); + if (rc.IsFailure()) return rc; + + rc = _baseFileSystem.Target.CleanDirectoryRecursively(in pathNormalized); + if (rc.IsFailure()) return rc; + + pathNormalized.Dispose(); + return Result.Success; + } + + public Result RenameFile(in PathSf currentPath, in PathSf newPath) + { + var currentPathNormalized = new Path(); + Result rc = SetUpPath(ref currentPathNormalized, in currentPath); + if (rc.IsFailure()) return rc; + + var newPathNormalized = new Path(); + rc = SetUpPath(ref newPathNormalized, in newPath); + if (rc.IsFailure()) return rc; + + rc = _baseFileSystem.Target.RenameFile(in currentPathNormalized, in newPathNormalized); + if (rc.IsFailure()) return rc; + + currentPathNormalized.Dispose(); + newPathNormalized.Dispose(); + return Result.Success; + } + + public Result RenameDirectory(in PathSf currentPath, in PathSf newPath) + { + var currentPathNormalized = new Path(); + Result rc = SetUpPath(ref currentPathNormalized, in currentPath); + if (rc.IsFailure()) return rc; + + var newPathNormalized = new Path(); + rc = SetUpPath(ref newPathNormalized, in newPath); + if (rc.IsFailure()) return rc; + + if (PathUtility12.IsSubPath(currentPathNormalized.GetString(), newPathNormalized.GetString())) + return ResultFs.DirectoryNotRenamable.Log(); + + rc = _baseFileSystem.Target.RenameDirectory(in currentPathNormalized, in newPathNormalized); + if (rc.IsFailure()) return rc; + + currentPathNormalized.Dispose(); + newPathNormalized.Dispose(); + return Result.Success; + } + + public Result GetEntryType(out uint entryType, in PathSf path) + { + UnsafeHelpers.SkipParamInit(out entryType); + + var pathNormalized = new Path(); + Result rc = SetUpPath(ref pathNormalized, in path); + if (rc.IsFailure()) return rc; + + rc = _baseFileSystem.Target.GetEntryType(out DirectoryEntryType type, in pathNormalized); + if (rc.IsFailure()) return rc; + + entryType = (uint)type; + pathNormalized.Dispose(); + return Result.Success; + } + + public Result GetFreeSpaceSize(out long freeSpace, in PathSf path) + { + UnsafeHelpers.SkipParamInit(out freeSpace); + + var pathNormalized = new Path(); + Result rc = SetUpPath(ref pathNormalized, in path); + if (rc.IsFailure()) return rc; + + rc = _baseFileSystem.Target.GetFreeSpaceSize(out long space, in pathNormalized); + if (rc.IsFailure()) return rc; + + freeSpace = space; + pathNormalized.Dispose(); + return Result.Success; + } + + public Result GetTotalSpaceSize(out long totalSpace, in PathSf path) + { + UnsafeHelpers.SkipParamInit(out totalSpace); + + var pathNormalized = new Path(); + Result rc = SetUpPath(ref pathNormalized, in path); + if (rc.IsFailure()) return rc; + + rc = _baseFileSystem.Target.GetTotalSpaceSize(out long space, in pathNormalized); + if (rc.IsFailure()) return rc; + + totalSpace = space; + pathNormalized.Dispose(); + return Result.Success; + } + + public Result OpenFile(out ReferenceCountedDisposable file, in PathSf path, uint mode) + { + const int maxTryCount = 2; + UnsafeHelpers.SkipParamInit(out file); + + var pathNormalized = new Path(); + Result rc = SetUpPath(ref pathNormalized, in path); + if (rc.IsFailure()) return rc; + + IFile fileInterface = null; + try + { + for (int tryNum = 0; tryNum < maxTryCount; tryNum++) + { + rc = _baseFileSystem.Target.OpenFile(out fileInterface, in pathNormalized, (OpenMode)mode); + + // Retry on ResultDataCorrupted + if (!ResultFs.DataCorrupted.Includes(rc)) + break; + } + + if (rc.IsFailure()) return rc; + + ReferenceCountedDisposable selfReference = _selfReference.AddReference(); + var adapter = new FileInterfaceAdapter(Shared.Move(ref fileInterface), ref selfReference, _allowAllOperations); + file = new ReferenceCountedDisposable(adapter); + + pathNormalized.Dispose(); + return Result.Success; + } + finally + { + fileInterface?.Dispose(); + } + } + + public Result OpenDirectory(out ReferenceCountedDisposable directory, in PathSf path, uint mode) + { + const int maxTryCount = 2; + UnsafeHelpers.SkipParamInit(out directory); + + var pathNormalized = new Path(); + Result rc = SetUpPath(ref pathNormalized, in path); + if (rc.IsFailure()) return rc; + + IDirectory dirInterface = null; + try + { + for (int tryNum = 0; tryNum < maxTryCount; tryNum++) + { + rc = _baseFileSystem.Target.OpenDirectory(out dirInterface, in pathNormalized, + (OpenDirectoryMode)mode); + + // Retry on ResultDataCorrupted + if (!ResultFs.DataCorrupted.Includes(rc)) + break; + } + + if (rc.IsFailure()) return rc; + + ReferenceCountedDisposable selfReference = _selfReference.AddReference(); + var adapter = new DirectoryInterfaceAdapter(dirInterface, ref selfReference); + directory = new ReferenceCountedDisposable(adapter); + + pathNormalized.Dispose(); + return Result.Success; + } + finally + { + dirInterface?.Dispose(); + } + } + + public Result Commit() + { + return _baseFileSystem.Target.Commit(); + } + + public Result GetFileTimeStampRaw(out FileTimeStampRaw timeStamp, in PathSf path) + { + UnsafeHelpers.SkipParamInit(out timeStamp); + + var pathNormalized = new Path(); + Result rc = SetUpPath(ref pathNormalized, in path); + if (rc.IsFailure()) return rc; + + rc = _baseFileSystem.Target.GetFileTimeStampRaw(out FileTimeStampRaw tempTimeStamp, in pathNormalized); + if (rc.IsFailure()) return rc; + + timeStamp = tempTimeStamp; + pathNormalized.Dispose(); + return Result.Success; + } + + public Result QueryEntry(OutBuffer outBuffer, InBuffer inBuffer, int queryId, in PathSf path) + { + static Result PermissionCheck(QueryId queryId, FileSystemInterfaceAdapter fsAdapter) + { + if (queryId == QueryId.SetConcatenationFileAttribute || + queryId == QueryId.IsSignedSystemPartitionOnSdCardValid || + queryId == QueryId.QueryUnpreparedFileInformation) + { + return Result.Success; + } + + if (!fsAdapter._allowAllOperations) + return ResultFs.PermissionDenied.Log(); + + return Result.Success; + } + + Result rc = PermissionCheck((QueryId)queryId, this); + if (rc.IsFailure()) return rc; + + var pathNormalized = new Path(); + rc = SetUpPath(ref pathNormalized, in path); + if (rc.IsFailure()) return rc; + + rc = _baseFileSystem.Target.QueryEntry(outBuffer.Buffer, inBuffer.Buffer, (QueryId)queryId, + in pathNormalized); + if (rc.IsFailure()) return rc; + + pathNormalized.Dispose(); + return Result.Success; + } + + public Result GetImpl(out ReferenceCountedDisposable fileSystem) + { + fileSystem = _baseFileSystem.AddReference(); + return Result.Success; } } } diff --git a/src/LibHac/FsSrv/Sf/IFileSystem.cs b/src/LibHac/FsSrv/Sf/IFileSystem.cs index 268cd40a..cce57d62 100644 --- a/src/LibHac/FsSrv/Sf/IFileSystem.cs +++ b/src/LibHac/FsSrv/Sf/IFileSystem.cs @@ -1,5 +1,6 @@ using System; using LibHac.Fs; +using LibHac.Sf; using IFileSf = LibHac.FsSrv.Sf.IFile; using IDirectorySf = LibHac.FsSrv.Sf.IDirectory; @@ -13,8 +14,8 @@ namespace LibHac.FsSrv.Sf Result CreateDirectory(in Path path); Result DeleteDirectory(in Path path); Result DeleteDirectoryRecursively(in Path path); - Result RenameFile(in Path oldPath, in Path newPath); - Result RenameDirectory(in Path oldPath, in Path newPath); + Result RenameFile(in Path currentPath, in Path newPath); + Result RenameDirectory(in Path currentPath, in Path newPath); Result GetEntryType(out uint entryType, in Path path); Result OpenFile(out ReferenceCountedDisposable file, in Path path, uint mode); Result OpenDirectory(out ReferenceCountedDisposable directory, in Path path, uint mode); @@ -23,6 +24,6 @@ namespace LibHac.FsSrv.Sf Result GetTotalSpaceSize(out long totalSpace, in Path path); Result CleanDirectoryRecursively(in Path path); Result GetFileTimeStampRaw(out FileTimeStampRaw timeStamp, in Path path); - Result QueryEntry(Span outBuffer, ReadOnlySpan inBuffer, int queryId, in Path path); + Result QueryEntry(OutBuffer outBuffer, InBuffer inBuffer, int queryId, in Path path); } } diff --git a/src/LibHac/FsSystem/ThreadPriorityChanger.cs b/src/LibHac/FsSystem/ThreadPriorityChanger.cs new file mode 100644 index 00000000..14c96d9c --- /dev/null +++ b/src/LibHac/FsSystem/ThreadPriorityChanger.cs @@ -0,0 +1,51 @@ +using System; + +namespace LibHac.FsSystem +{ + // Todo: Actually implement both of these structs + public struct ScopedThreadPriorityChanger : IDisposable + { + public enum Mode + { + Absolute, + Relative + } + + public ScopedThreadPriorityChanger(int priority, Mode mode) + { + // Change the current thread priority + } + + public void Dispose() + { + // Change thread priority back + } + } + + public struct ScopedThreadPriorityChangerByAccessPriority : IDisposable + { + public enum AccessMode + { + Read, + Write + } + + private ScopedThreadPriorityChanger _scopedChanger; + + public ScopedThreadPriorityChangerByAccessPriority(AccessMode mode) + { + _scopedChanger = new ScopedThreadPriorityChanger(GetThreadPriorityByAccessPriority(mode), + ScopedThreadPriorityChanger.Mode.Absolute); + } + + public void Dispose() + { + _scopedChanger.Dispose(); + } + + private static int GetThreadPriorityByAccessPriority(AccessMode mode) + { + return 0; + } + } +}