diff --git a/src/LibHac/Fs/Common/DirectoryPathParser.cs b/src/LibHac/Fs/Common/DirectoryPathParser.cs new file mode 100644 index 00000000..50639999 --- /dev/null +++ b/src/LibHac/Fs/Common/DirectoryPathParser.cs @@ -0,0 +1,118 @@ +using System; +using LibHac.Common; +using LibHac.Diag; +using static LibHac.Fs.StringTraits; + +namespace LibHac.Fs.Common +{ + public ref struct DirectoryPathParser + { + private Span _buffer; + private byte _replacedChar; + private int _position; + + // Todo: Make private so we can use the GetCurrentPath method once lifetime tracking is better + public Path CurrentPath; + + public void Dispose() + { + CurrentPath.Dispose(); + } + + public Result Initialize(ref Path path) + { + Span pathBuffer = path.GetWriteBufferLength() != 0 ? path.GetWriteBuffer() : Span.Empty; + + int windowsSkipLength = WindowsPath12.GetWindowsSkipLength(pathBuffer); + _buffer = pathBuffer.Slice(windowsSkipLength); + + if (windowsSkipLength != 0) + { + Result rc = CurrentPath.InitializeWithNormalization(pathBuffer, windowsSkipLength + 1); + if (rc.IsFailure()) return rc; + + _buffer = _buffer.Slice(1); + } + else + { + Span initialPath = ReadNextImpl(); + + if (!initialPath.IsEmpty) + { + Result rc = CurrentPath.InitializeWithNormalization(initialPath); + if (rc.IsFailure()) return rc; + } + } + + return Result.Success; + } + + // Todo: Return reference when escape semantics are better + public readonly Path GetCurrentPath() + { + return CurrentPath; + } + + public Result ReadNext(out bool isFinished) + { + isFinished = false; + + Span nextEntry = ReadNextImpl(); + + if (nextEntry.IsEmpty) + { + isFinished = true; + return Result.Success; + } + + return CurrentPath.AppendChild(nextEntry); + } + + private Span ReadNextImpl() + { + // Check if we've already hit the end of the path. + if (_position < 0 || _buffer.At(0) == 0) + return Span.Empty; + + // Restore the character we previously replaced with a null terminator. + if (_replacedChar != 0) + { + _buffer[_position] = _replacedChar; + + if (_replacedChar == DirectorySeparator) + _position++; + } + + // If the path is rooted, the first entry should be the root directory. + if (_position == 0 && _buffer.At(0) == DirectorySeparator) + { + _replacedChar = _buffer[1]; + _buffer[1] = 0; + return _buffer; + } + + // Find the end of the next entry, replacing the directory separator with a null terminator. + Span entry = _buffer.Slice(_position); + + int i; + for (i = _position; _buffer.At(i) != DirectorySeparator; i++) + { + if (_buffer.At(i) == 0) + { + if (i == _position) + entry = Span.Empty; + + _position = -1; + return entry; + } + } + + Assert.SdkAssert(_buffer.At(i + 1) != NullTerminator); + + _replacedChar = DirectorySeparator; + _buffer[i] = 0; + _position = i; + return entry; + } + } +} diff --git a/src/LibHac/FsSrv/Impl/Utility.cs b/src/LibHac/FsSrv/Impl/Utility.cs index 2526629d..bc1ac103 100644 --- a/src/LibHac/FsSrv/Impl/Utility.cs +++ b/src/LibHac/FsSrv/Impl/Utility.cs @@ -122,20 +122,26 @@ namespace LibHac.FsSrv.Impl } public static Result CreateSubDirectoryFileSystem(out ReferenceCountedDisposable subDirFileSystem, - ref ReferenceCountedDisposable baseFileSystem, U8Span subPath, bool preserveUnc = false) + ref ReferenceCountedDisposable baseFileSystem, in Path subPath) { UnsafeHelpers.SkipParamInit(out subDirFileSystem); + if (subPath.IsEmpty()) + { + subDirFileSystem = Shared.Move(ref baseFileSystem); + return Result.Success; + } + // Check if the directory exists Result rc = baseFileSystem.Target.OpenDirectory(out IDirectory dir, subPath, OpenDirectoryMode.Directory); if (rc.IsFailure()) return rc; dir.Dispose(); - var fs = new SubdirectoryFileSystem(ref baseFileSystem, preserveUnc); + var fs = new SubdirectoryFileSystem(ref baseFileSystem); using (var subDirFs = new ReferenceCountedDisposable(fs)) { - rc = subDirFs.Target.Initialize(subPath); + rc = subDirFs.Target.Initialize(in subPath); if (rc.IsFailure()) return rc; subDirFileSystem = subDirFs.AddReference(); diff --git a/src/LibHac/FsSrv/NcaFileSystemService.cs b/src/LibHac/FsSrv/NcaFileSystemService.cs index 121661b8..7e76ff98 100644 --- a/src/LibHac/FsSrv/NcaFileSystemService.cs +++ b/src/LibHac/FsSrv/NcaFileSystemService.cs @@ -27,15 +27,15 @@ namespace LibHac.FsSrv private ReferenceCountedDisposable.WeakReference SelfReference { get; set; } private NcaFileSystemServiceImpl ServiceImpl { get; } private ulong ProcessId { get; } - private SemaphoreAdaptor AocMountCountSemaphore { get; } - private SemaphoreAdaptor RomMountCountSemaphore { get; } + private SemaphoreAdapter AocMountCountSemaphore { get; } + private SemaphoreAdapter RomMountCountSemaphore { get; } private NcaFileSystemService(NcaFileSystemServiceImpl serviceImpl, ulong processId) { ServiceImpl = serviceImpl; ProcessId = processId; - AocMountCountSemaphore = new SemaphoreAdaptor(AocSemaphoreCount, AocSemaphoreCount); - RomMountCountSemaphore = new SemaphoreAdaptor(RomSemaphoreCount, RomSemaphoreCount); + AocMountCountSemaphore = new SemaphoreAdapter(AocSemaphoreCount, AocSemaphoreCount); + RomMountCountSemaphore = new SemaphoreAdapter(RomSemaphoreCount, RomSemaphoreCount); } public static ReferenceCountedDisposable CreateShared( diff --git a/src/LibHac/FsSrv/SaveDataFileSystemService.cs b/src/LibHac/FsSrv/SaveDataFileSystemService.cs index c791495b..d1c789c2 100644 --- a/src/LibHac/FsSrv/SaveDataFileSystemService.cs +++ b/src/LibHac/FsSrv/SaveDataFileSystemService.cs @@ -38,8 +38,8 @@ namespace LibHac.FsSrv private SaveDataFileSystemServiceImpl ServiceImpl { get; } private ulong ProcessId { get; } private FsPath SaveDataRootPath { get; set; } - private SemaphoreAdaptor OpenEntryCountSemaphore { get; } - private SemaphoreAdaptor SaveDataMountCountSemaphore { get; } + private SemaphoreAdapter OpenEntryCountSemaphore { get; } + private SemaphoreAdapter SaveDataMountCountSemaphore { get; } private HorizonClient Hos => ServiceImpl.Hos; @@ -47,8 +47,8 @@ namespace LibHac.FsSrv { ServiceImpl = serviceImpl; ProcessId = processId; - OpenEntryCountSemaphore = new SemaphoreAdaptor(OpenEntrySemaphoreCount, OpenEntrySemaphoreCount); - SaveDataMountCountSemaphore = new SemaphoreAdaptor(SaveMountSemaphoreCount, SaveMountSemaphoreCount); + OpenEntryCountSemaphore = new SemaphoreAdapter(OpenEntrySemaphoreCount, OpenEntrySemaphoreCount); + SaveDataMountCountSemaphore = new SemaphoreAdapter(SaveMountSemaphoreCount, SaveMountSemaphoreCount); } public static ReferenceCountedDisposable CreateShared( diff --git a/src/LibHac/FsSrv/SaveDataSharedFileStorage.cs b/src/LibHac/FsSrv/SaveDataSharedFileStorage.cs index 76358950..c7d76b90 100644 --- a/src/LibHac/FsSrv/SaveDataSharedFileStorage.cs +++ b/src/LibHac/FsSrv/SaveDataSharedFileStorage.cs @@ -139,9 +139,9 @@ namespace LibHac.FsSrv return _isNormalStorageOpened || _isInternalStorageOpened; } - public UniqueLock GetLock() + public UniqueLockRef GetLock() { - return new UniqueLock(ref _mutex); + return new UniqueLockRef(ref _mutex); } } @@ -199,7 +199,7 @@ namespace LibHac.FsSrv protected override Result DoRead(long offset, Span destination) { - using UniqueLock scopedLock = _baseStorage.Target.GetLock(); + using UniqueLockRef scopedLock = _baseStorage.Target.GetLock(); Result rc = AccessCheck(isWriteAccess: false); if (rc.IsFailure()) return rc; @@ -209,7 +209,7 @@ namespace LibHac.FsSrv protected override Result DoWrite(long offset, ReadOnlySpan source) { - using UniqueLock scopedLock = _baseStorage.Target.GetLock(); + using UniqueLockRef scopedLock = _baseStorage.Target.GetLock(); Result rc = AccessCheck(isWriteAccess: true); if (rc.IsFailure()) return rc; @@ -219,7 +219,7 @@ namespace LibHac.FsSrv protected override Result DoFlush() { - using UniqueLock scopedLock = _baseStorage.Target.GetLock(); + using UniqueLockRef scopedLock = _baseStorage.Target.GetLock(); Result rc = AccessCheck(isWriteAccess: true); if (rc.IsFailure()) return rc; @@ -229,7 +229,7 @@ namespace LibHac.FsSrv protected override Result DoSetSize(long size) { - using UniqueLock scopedLock = _baseStorage.Target.GetLock(); + using UniqueLockRef scopedLock = _baseStorage.Target.GetLock(); Result rc = AccessCheck(isWriteAccess: true); if (rc.IsFailure()) return rc; @@ -241,7 +241,7 @@ namespace LibHac.FsSrv { Unsafe.SkipInit(out size); - using UniqueLock scopedLock = _baseStorage.Target.GetLock(); + using UniqueLockRef scopedLock = _baseStorage.Target.GetLock(); Result rc = AccessCheck(isWriteAccess: false); if (rc.IsFailure()) return rc; @@ -252,7 +252,7 @@ namespace LibHac.FsSrv protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, ReadOnlySpan inBuffer) { - using UniqueLock scopedLock = _baseStorage.Target.GetLock(); + using UniqueLockRef scopedLock = _baseStorage.Target.GetLock(); Result rc = AccessCheck(isWriteAccess: true); if (rc.IsFailure()) return rc; diff --git a/src/LibHac/FsSystem/DirectorySaveDataFileSystem.cs b/src/LibHac/FsSystem/DirectorySaveDataFileSystem.cs index 708b4a60..eff324c9 100644 --- a/src/LibHac/FsSystem/DirectorySaveDataFileSystem.cs +++ b/src/LibHac/FsSystem/DirectorySaveDataFileSystem.cs @@ -196,6 +196,39 @@ namespace LibHac.FsSystem base.Dispose(); } + private Result RetryFinitelyForTargetLocked(Func function) + { + const int maxRetryCount = 10; + const int retryWaitTimeMs = 100; + + int remainingRetries = maxRetryCount; + + while (true) + { + Result rc = function(); + + if (rc.IsSuccess()) + return rc; + + if (!ResultFs.TargetLocked.Includes(rc)) + return rc; + + if (remainingRetries <= 0) + return rc; + + remainingRetries--; + + if (_fsClient is not null) + { + _fsClient.Hos.Os.SleepThread(TimeSpan.FromMilliSeconds(retryWaitTimeMs)); + } + else + { + System.Threading.Thread.Sleep(retryWaitTimeMs); + } + } + } + public Result Initialize(bool isJournalingSupported, bool isMultiCommitSupported, bool isJournalingEnabled) { return Initialize(null, null, isJournalingSupported, isMultiCommitSupported, isJournalingEnabled); @@ -517,16 +550,16 @@ namespace LibHac.FsSystem _baseFs.RenameDirectory(SynchronizingDirectoryPath, CommittedDirectoryPath); // Get rid of the previous commit by renaming the folder - Result rc = Utility.RetryFinitelyForTargetLocked(RenameCommittedDir); + Result rc = RetryFinitelyForTargetLocked(RenameCommittedDir); if (rc.IsFailure()) return rc; // If something goes wrong beyond this point, the commit will be // completed the next time the savedata is opened - rc = Utility.RetryFinitelyForTargetLocked(SynchronizeWorkingDir); + rc = RetryFinitelyForTargetLocked(SynchronizeWorkingDir); if (rc.IsFailure()) return rc; - rc = Utility.RetryFinitelyForTargetLocked(RenameSynchronizingDir); + rc = RetryFinitelyForTargetLocked(RenameSynchronizingDir); if (rc.IsFailure()) return rc; return Result.Success; @@ -800,16 +833,16 @@ namespace LibHac.FsSystem Result RenameSynchronizingFile() => _baseFs.RenameFile(SynchronizingExtraDataPath, CommittedExtraDataPath); // Get rid of the previous commit by renaming the folder - Result rc = Utility.RetryFinitelyForTargetLocked(RenameCommittedFile); + Result rc = RetryFinitelyForTargetLocked(RenameCommittedFile); if (rc.IsFailure()) return rc; // If something goes wrong beyond this point, the commit will be // completed the next time the savedata is opened - rc = Utility.RetryFinitelyForTargetLocked(SynchronizeWorkingFile); + rc = RetryFinitelyForTargetLocked(SynchronizeWorkingFile); if (rc.IsFailure()) return rc; - rc = Utility.RetryFinitelyForTargetLocked(RenameSynchronizingFile); + rc = RetryFinitelyForTargetLocked(RenameSynchronizingFile); if (rc.IsFailure()) return rc; return Result.Success; diff --git a/src/LibHac/FsSystem/SemaphoreAdaptor.cs b/src/LibHac/FsSystem/SemaphoreAdapter.cs similarity index 69% rename from src/LibHac/FsSystem/SemaphoreAdaptor.cs rename to src/LibHac/FsSystem/SemaphoreAdapter.cs index d787d923..46f94dcf 100644 --- a/src/LibHac/FsSystem/SemaphoreAdaptor.cs +++ b/src/LibHac/FsSystem/SemaphoreAdapter.cs @@ -1,13 +1,14 @@ using System; using System.Threading; +using LibHac.Os; namespace LibHac.FsSystem { - public class SemaphoreAdaptor : IDisposable + public class SemaphoreAdapter : IDisposable, ILockable { private SemaphoreSlim _semaphore; - public SemaphoreAdaptor(int initialCount, int maxCount) + public SemaphoreAdapter(int initialCount, int maxCount) { _semaphore = new SemaphoreSlim(initialCount, maxCount); } @@ -17,6 +18,11 @@ namespace LibHac.FsSystem return _semaphore.Wait(System.TimeSpan.Zero); } + public void Lock() + { + _semaphore.Wait(); + } + public void Unlock() { _semaphore.Release(); diff --git a/src/LibHac/FsSystem/UniqueLockSemaphore.cs b/src/LibHac/FsSystem/UniqueLockSemaphore.cs index 766dab15..dfa01846 100644 --- a/src/LibHac/FsSystem/UniqueLockSemaphore.cs +++ b/src/LibHac/FsSystem/UniqueLockSemaphore.cs @@ -2,6 +2,7 @@ using System.Threading; using LibHac.Common; using LibHac.Diag; +using LibHac.Os; namespace LibHac.FsSystem { @@ -16,10 +17,10 @@ namespace LibHac.FsSystem /// reference or moved via the move constructor. public struct UniqueLockSemaphore : IDisposable { - private SemaphoreAdaptor _semaphore; + private SemaphoreAdapter _semaphore; private bool _isLocked; - public UniqueLockSemaphore(SemaphoreAdaptor semaphore) + public UniqueLockSemaphore(SemaphoreAdapter semaphore) { _semaphore = semaphore; _isLocked = false; @@ -72,15 +73,13 @@ namespace LibHac.FsSystem public class UniqueLockWithPin : IUniqueLock where T : class, IDisposable { - private UniqueLockSemaphore _semaphore; + private UniqueLock _semaphore; private ReferenceCountedDisposable _pinnedObject; - public UniqueLockWithPin(ref UniqueLockSemaphore semaphore, ref ReferenceCountedDisposable pinnedObject) + public UniqueLockWithPin(ref UniqueLock semaphore, ref ReferenceCountedDisposable pinnedObject) { Shared.Move(out _semaphore, ref semaphore); Shared.Move(out _pinnedObject, ref pinnedObject); - - Assert.SdkAssert(_semaphore.IsLocked); } public void Dispose() diff --git a/src/LibHac/FsSystem/Utility.cs b/src/LibHac/FsSystem/Utility.cs index e7a1c060..66ae8aa2 100644 --- a/src/LibHac/FsSystem/Utility.cs +++ b/src/LibHac/FsSystem/Utility.cs @@ -1,9 +1,9 @@ using System; using System.Runtime.CompilerServices; -using System.Threading; using LibHac.Common; using LibHac.Diag; using LibHac.Fs; +using LibHac.Fs.Common; using LibHac.Fs.Fsa; using LibHac.Util; @@ -225,7 +225,7 @@ namespace LibHac.FsSystem return Result.Success; } - public static Result TryAcquireCountSemaphore(out UniqueLockSemaphore uniqueLock, SemaphoreAdaptor semaphore) + public static Result TryAcquireCountSemaphore(out UniqueLockSemaphore uniqueLock, SemaphoreAdapter semaphore) { UniqueLockSemaphore tempUniqueLock = default; try @@ -247,7 +247,7 @@ namespace LibHac.FsSystem } } - public static Result MakeUniqueLockWithPin(out IUniqueLock uniqueLock, SemaphoreAdaptor semaphore, + public static Result MakeUniqueLockWithPin(out IUniqueLock uniqueLock, SemaphoreAdapter semaphore, ref ReferenceCountedDisposable objectToPin) where T : class, IDisposable { UnsafeHelpers.SkipParamInit(out uniqueLock); @@ -266,30 +266,5 @@ namespace LibHac.FsSystem tempUniqueLock.Dispose(); } } - - public static Result RetryFinitelyForTargetLocked(Func function) - { - const int maxRetryCount = 10; - const int retryWaitTimeMs = 100; - - int remainingRetries = maxRetryCount; - - while (true) - { - Result rc = function(); - - if (rc.IsSuccess()) - return rc; - - if (!ResultFs.TargetLocked.Includes(rc)) - return rc; - - if (remainingRetries <= 0) - return rc; - - remainingRetries--; - Thread.Sleep(retryWaitTimeMs); - } - } } } diff --git a/src/LibHac/FsSystem/Utility12.cs b/src/LibHac/FsSystem/Utility12.cs new file mode 100644 index 00000000..b3427b11 --- /dev/null +++ b/src/LibHac/FsSystem/Utility12.cs @@ -0,0 +1,473 @@ +using System; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Common; +using LibHac.Fs.Fsa; +using LibHac.Os; + +namespace LibHac.FsSystem +{ + /// + /// Various utility functions used by the namespace. + /// + /// Based on FS 12.0.3 (nnSdk 12.3.1) + internal static class Utility12 + { + public delegate Result FsIterationTask(in Path path, in DirectoryEntry entry, ref FsIterationTaskClosure closure); + + /// + /// Used to pass various ref structs to an . + /// + /// + /// C# does not allow closures over byref-like types. This struct is used as a sort of manual imitation of a closure struct. + /// It contains various fields that can used if needed to pass references to methods. + /// The main shortcomings are that every type that might possibly be passed must have a field in the struct. + /// The struct must also be manually passed through the method. + /// And because ref fields aren't as thing as of C# 10, some ref structs may have to be copied into the closure struct. + /// + public ref struct FsIterationTaskClosure + { + public Span Buffer; + public Path DestinationPathBuffer; + public IFileSystem SourceFileSystem; + public IFileSystem DestFileSystem; + } + + private static ReadOnlySpan RootPath => new[] { (byte)'/' }; + + private static Result IterateDirectoryRecursivelyInternal(IFileSystem fs, ref Path workPath, + ref DirectoryEntry dirEntry, FsIterationTask onEnterDir, FsIterationTask onExitDir, FsIterationTask onFile, + ref FsIterationTaskClosure closure) + { + IDirectory directory = null; + try + { + Result rc = fs.OpenDirectory(out directory, in workPath, OpenDirectoryMode.All); + if (rc.IsFailure()) return rc; + + while (true) + { + rc = directory.Read(out long entriesRead, SpanHelpers.AsSpan(ref dirEntry)); + if (rc.IsFailure()) return rc; + + if (entriesRead == 0) + break; + + workPath.AppendChild(dirEntry.Name); + if (rc.IsFailure()) return rc; + + if (dirEntry.Type == DirectoryEntryType.Directory) + { + rc = onEnterDir(in workPath, in dirEntry, ref closure); + if (rc.IsFailure()) return rc; + + rc = IterateDirectoryRecursivelyInternal(fs, ref workPath, ref dirEntry, onEnterDir, onExitDir, + onFile, ref closure); + if (rc.IsFailure()) return rc; + + rc = onExitDir(in workPath, in dirEntry, ref closure); + if (rc.IsFailure()) return rc; + } + else + { + rc = onFile(in workPath, in dirEntry, ref closure); + if (rc.IsFailure()) return rc; + } + + rc = workPath.RemoveChild(); + if (rc.IsFailure()) return rc; + } + + return Result.Success; + } + finally + { + directory?.Dispose(); + } + } + + private static Result CleanupDirectoryRecursivelyInternal(IFileSystem fs, ref Path workPath, + ref DirectoryEntry dirEntry, FsIterationTask onEnterDir, FsIterationTask onExitDir, FsIterationTask onFile, + ref FsIterationTaskClosure closure) + { + IDirectory directory = null; + try + { + while (true) + { + Result rc = fs.OpenDirectory(out directory, in workPath, OpenDirectoryMode.All); + if (rc.IsFailure()) return rc; + + rc = directory.Read(out long entriesRead, SpanHelpers.AsSpan(ref dirEntry)); + if (rc.IsFailure()) return rc; + + directory.Dispose(); + directory = null; + + if (entriesRead == 0) + break; + + rc = workPath.AppendChild(dirEntry.Name); + if (rc.IsFailure()) return rc; + + if (dirEntry.Type == DirectoryEntryType.Directory) + { + rc = onEnterDir(in workPath, in dirEntry, ref closure); + if (rc.IsFailure()) return rc; + + rc = CleanupDirectoryRecursivelyInternal(fs, ref workPath, ref dirEntry, onEnterDir, onExitDir, + onFile, ref closure); + if (rc.IsFailure()) return rc; + + rc = onExitDir(in workPath, in dirEntry, ref closure); + if (rc.IsFailure()) return rc; + } + else + { + rc = onFile(in workPath, in dirEntry, ref closure); + if (rc.IsFailure()) return rc; + } + + rc = workPath.RemoveChild(); + if (rc.IsFailure()) return rc; + } + + return Result.Success; + } + finally + { + directory?.Dispose(); + } + } + + public static Result IterateDirectoryRecursively(IFileSystem fs, in Path rootPath, ref DirectoryEntry dirEntry, + FsIterationTask onEnterDir, FsIterationTask onExitDir, FsIterationTask onFile, + ref FsIterationTaskClosure closure) + { + var pathBuffer = new Path(); + Result rc = pathBuffer.Initialize(in rootPath); + if (rc.IsFailure()) return rc; + + rc = IterateDirectoryRecursivelyInternal(fs, ref pathBuffer, ref dirEntry, onEnterDir, onExitDir, onFile, + ref closure); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + public static Result CleanupDirectoryRecursively(IFileSystem fs, in Path rootPath, ref DirectoryEntry dirEntry, + FsIterationTask onEnterDir, FsIterationTask onExitDir, FsIterationTask onFile, + ref FsIterationTaskClosure closure) + { + var pathBuffer = new Path(); + Result rc = pathBuffer.Initialize(in rootPath); + if (rc.IsFailure()) return rc; + + return CleanupDirectoryRecursivelyInternal(fs, ref pathBuffer, ref dirEntry, onEnterDir, onExitDir, onFile, + ref closure); + } + + public static Result CopyFile(IFileSystem destFileSystem, IFileSystem sourceFileSystem, in Path destPath, + in Path sourcePath, Span workBuffer) + { + // Open source file. + Result rc = sourceFileSystem.OpenFile(out IFile sourceFile, sourcePath, OpenMode.Read); + if (rc.IsFailure()) return rc; + + using (sourceFile) + { + rc = sourceFile.GetSize(out long fileSize); + if (rc.IsFailure()) return rc; + + rc = destFileSystem.CreateFile(in destPath, fileSize); + if (rc.IsFailure()) return rc; + + rc = destFileSystem.OpenFile(out IFile destFile, in destPath, OpenMode.Write); + if (rc.IsFailure()) return rc; + + using (destFile) + { + // Read/Write file in work buffer sized chunks. + long remaining = fileSize; + long offset = 0; + + while (remaining > 0) + { + rc = sourceFile.Read(out long bytesRead, offset, workBuffer, ReadOption.None); + if (rc.IsFailure()) return rc; + + rc = destFile.Write(offset, workBuffer.Slice(0, (int)bytesRead), WriteOption.None); + if (rc.IsFailure()) return rc; + + remaining -= bytesRead; + offset += bytesRead; + } + } + } + + return Result.Success; + } + + public static Result CopyDirectoryRecursively(IFileSystem destinationFileSystem, IFileSystem sourceFileSystem, + in Path destinationPath, in Path sourcePath, ref DirectoryEntry dirEntry, Span workBuffer) + { + static Result OnEnterDir(in Path path, in DirectoryEntry entry, ref FsIterationTaskClosure closure) + { + Result rc = closure.DestinationPathBuffer.AppendChild(entry.Name); + if (rc.IsFailure()) return rc; + + return closure.SourceFileSystem.CreateDirectory(in closure.DestinationPathBuffer); + } + + static Result OnExitDir(in Path path, in DirectoryEntry entry, ref FsIterationTaskClosure closure) + { + return closure.DestinationPathBuffer.RemoveChild(); + } + + static Result OnFile(in Path path, in DirectoryEntry entry, ref FsIterationTaskClosure closure) + { + Result rc = closure.DestinationPathBuffer.AppendChild(entry.Name); + if (rc.IsFailure()) return rc; + + rc = CopyFile(closure.DestFileSystem, closure.SourceFileSystem, in closure.DestinationPathBuffer, + in path, closure.Buffer); + if (rc.IsFailure()) return rc; + + return closure.DestinationPathBuffer.RemoveChild(); + } + + var closure = new FsIterationTaskClosure(); + closure.Buffer = workBuffer; + closure.SourceFileSystem = sourceFileSystem; + closure.DestFileSystem = destinationFileSystem; + + Result rc = closure.DestinationPathBuffer.Initialize(destinationPath); + if (rc.IsFailure()) return rc; + + rc = IterateDirectoryRecursively(sourceFileSystem, in sourcePath, ref dirEntry, OnEnterDir, OnExitDir, + OnFile, ref closure); + + closure.DestinationPathBuffer.Dispose(); + return rc; + } + + public static Result CopyDirectoryRecursively(IFileSystem fileSystem, in Path destinationPath, + in Path sourcePath, ref DirectoryEntry dirEntry, Span workBuffer) + { + var closure = new FsIterationTaskClosure(); + closure.Buffer = workBuffer; + closure.SourceFileSystem = fileSystem; + + Result rc = closure.DestinationPathBuffer.Initialize(destinationPath); + if (rc.IsFailure()) return rc; + + static Result OnEnterDir(in Path path, in DirectoryEntry entry, ref FsIterationTaskClosure closure) + { + Result rc = closure.DestinationPathBuffer.AppendChild(entry.Name); + if (rc.IsFailure()) return rc; + + return closure.SourceFileSystem.CreateDirectory(in closure.DestinationPathBuffer); + } + + static Result OnExitDir(in Path path, in DirectoryEntry entry, ref FsIterationTaskClosure closure) + { + return closure.DestinationPathBuffer.RemoveChild(); + } + + static Result OnFile(in Path path, in DirectoryEntry entry, ref FsIterationTaskClosure closure) + { + Result rc = closure.DestinationPathBuffer.AppendChild(entry.Name); + if (rc.IsFailure()) return rc; + + rc = CopyFile(closure.SourceFileSystem, closure.SourceFileSystem, in closure.DestinationPathBuffer, + in path, closure.Buffer); + if (rc.IsFailure()) return rc; + + return closure.DestinationPathBuffer.RemoveChild(); + } + + rc = IterateDirectoryRecursively(fileSystem, in sourcePath, ref dirEntry, OnEnterDir, OnExitDir, OnFile, + ref closure); + + closure.DestinationPathBuffer.Dispose(); + return rc; + } + + public static Result VerifyDirectoryRecursively(IFileSystem fileSystem, Span workBuffer) + { + static Result OnEnterAndExitDir(in Path path, in DirectoryEntry entry, ref FsIterationTaskClosure closure) => + Result.Success; + + static Result OnFile(in Path path, in DirectoryEntry entry, ref FsIterationTaskClosure closure) + { + IFile file = null; + try + { + Result rc = closure.SourceFileSystem.OpenFile(out file, in path, OpenMode.Read); + if (rc.IsFailure()) return rc; + + long offset = 0; + + while (true) + { + rc = file.Read(out long bytesRead, offset, closure.Buffer, ReadOption.None); + if (rc.IsFailure()) return rc; + + if (bytesRead < closure.Buffer.Length) + break; + + offset += bytesRead; + } + + return Result.Success; + } + finally + { + file?.Dispose(); + } + } + + var rootPath = new Path(); + Result rc = PathFunctions.SetUpFixedPath(ref rootPath, RootPath); + if (rc.IsFailure()) return rc; + + var closure = new FsIterationTaskClosure(); + closure.Buffer = workBuffer; + closure.SourceFileSystem = fileSystem; + + var dirEntryBuffer = new DirectoryEntry(); + + return IterateDirectoryRecursively(fileSystem, in rootPath, ref dirEntryBuffer, OnEnterAndExitDir, + OnEnterAndExitDir, OnFile, ref closure); + } + + private static Result EnsureDirectoryImpl(IFileSystem fileSystem, in Path path) + { + var pathCopy = new Path(); + + try + { + bool isFinished; + + Result rc = pathCopy.Initialize(in path); + if (rc.IsFailure()) return rc; + + using var parser = new DirectoryPathParser(); + rc = parser.Initialize(ref pathCopy); + if (rc.IsFailure()) return rc; + + do + { + // Check if the path exists + rc = fileSystem.GetEntryType(out DirectoryEntryType type, in parser.CurrentPath); + if (!rc.IsSuccess()) + { + // Something went wrong if we get a result other than PathNotFound + if (!ResultFs.PathNotFound.Includes(rc)) + return rc; + + // Create the directory + rc = fileSystem.CreateDirectory(in parser.CurrentPath); + if (rc.IsFailure() && !ResultFs.PathAlreadyExists.Includes(rc)) + return rc; + + // Check once more if the path exists + rc = fileSystem.GetEntryType(out type, in parser.CurrentPath); + if (rc.IsFailure()) return rc; + } + + if (type == DirectoryEntryType.File) + return ResultFs.PathAlreadyExists.Log(); + + rc = parser.ReadNext(out isFinished); + if (rc.IsFailure()) return rc; + } while (!isFinished); + + return Result.Success; + } + finally + { + pathCopy.Dispose(); + } + } + + public static Result EnsureDirectory(IFileSystem fileSystem, in Path path) + { + Result rc = fileSystem.GetEntryType(out _, in path); + + if (!rc.IsSuccess()) + { + if (!ResultFs.PathNotFound.Includes(rc)) + return rc; + + rc = EnsureDirectoryImpl(fileSystem, in path); + if (rc.IsFailure()) return rc; + } + + return Result.Success; + } + + public static void AddCounter(Span counter, ulong value) + { + const int bitsPerByte = 8; + + ulong remaining = value; + byte carry = 0; + + for (int i = 0; i < counter.Length; i++) + { + int sum = counter[counter.Length - 1 - i] + (byte)remaining + carry; + carry = (byte)(sum >> bitsPerByte); + + counter[counter.Length - 1 - i] = (byte)sum; + + remaining >>= bitsPerByte; + + if (carry == 0 && remaining == 0) + break; + } + } + + public static Result TryAcquireCountSemaphore(out UniqueLock uniqueLock, SemaphoreAdapter semaphore) + { + UniqueLock tempUniqueLock = default; + try + { + tempUniqueLock = new UniqueLock(semaphore); + + if (!tempUniqueLock.TryLock()) + { + uniqueLock = default; + return ResultFs.OpenCountLimit.Log(); + } + + uniqueLock = Shared.Move(ref tempUniqueLock); + return Result.Success; + } + finally + { + tempUniqueLock.Dispose(); + } + } + + public static Result MakeUniqueLockWithPin(out IUniqueLock uniqueLock, SemaphoreAdapter semaphore, + ref ReferenceCountedDisposable objectToPin) where T : class, IDisposable + { + UnsafeHelpers.SkipParamInit(out uniqueLock); + + UniqueLock tempUniqueLock = default; + try + { + Result rc = TryAcquireCountSemaphore(out tempUniqueLock, semaphore); + if (rc.IsFailure()) return rc; + + uniqueLock = new UniqueLockWithPin(ref tempUniqueLock, ref objectToPin); + return Result.Success; + } + finally + { + tempUniqueLock.Dispose(); + } + } + } +} diff --git a/src/LibHac/Os/UniqueLock.cs b/src/LibHac/Os/UniqueLock.cs index 47ce3352..13f34b52 100644 --- a/src/LibHac/Os/UniqueLock.cs +++ b/src/LibHac/Os/UniqueLock.cs @@ -1,4 +1,5 @@ -using System.Runtime.CompilerServices; +using System; +using System.Runtime.CompilerServices; using System.Threading; using LibHac.Common; @@ -7,32 +8,38 @@ namespace LibHac.Os public static class UniqueLock { [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static UniqueLock Lock(ref TMutex lockable) where TMutex : ILockable + public static UniqueLockRef Lock(ref TMutex lockable) where TMutex : struct, ILockable { - return new UniqueLock(ref lockable); + return new UniqueLockRef(ref lockable); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static UniqueLock Lock(TMutex lockable) where TMutex : class, ILockable + { + return new UniqueLock(lockable); } } - public ref struct UniqueLock where TMutex : ILockable + public ref struct UniqueLockRef where TMutex : struct, ILockable { private Ref _mutex; private bool _ownsLock; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public UniqueLock(ref TMutex mutex) + public UniqueLockRef(ref TMutex mutex) { _mutex = new Ref(ref mutex); mutex.Lock(); _ownsLock = true; } - public UniqueLock(ref UniqueLock other) + public UniqueLockRef(ref UniqueLockRef other) { this = other; other = default; } - public void Set(ref UniqueLock other) + public void Set(ref UniqueLockRef other) { if (_ownsLock) _mutex.Value.Unlock(); @@ -83,4 +90,75 @@ namespace LibHac.Os this = default; } } -} \ No newline at end of file + + public struct UniqueLock : IDisposable where TMutex : class, ILockable + { + private TMutex _mutex; + private bool _ownsLock; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public UniqueLock(TMutex mutex) + { + _mutex = new Ref(ref mutex); + mutex.Lock(); + _ownsLock = true; + } + + public UniqueLock(ref UniqueLock other) + { + this = other; + other = default; + } + + public void Set(ref UniqueLock other) + { + if (_ownsLock) + _mutex.Unlock(); + + this = other; + other = default; + } + + public void Lock() + { + if (_mutex is null) + throw new SynchronizationLockException("UniqueLock.Lock: References null mutex"); + + if (_ownsLock) + throw new SynchronizationLockException("UniqueLock.Lock: Already locked"); + + _mutex.Lock(); + _ownsLock = true; + } + + public bool TryLock() + { + if (_mutex is null) + throw new SynchronizationLockException("UniqueLock.TryLock: References null mutex"); + + if (_ownsLock) + throw new SynchronizationLockException("UniqueLock.TryLock: Already locked"); + + _ownsLock = _mutex.TryLock(); + return _ownsLock; + } + + public void Unlock() + { + if (_ownsLock) + throw new SynchronizationLockException("UniqueLock.Unlock: Not locked"); + + _mutex.Unlock(); + _ownsLock = false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Dispose() + { + if (_ownsLock) + _mutex.Unlock(); + + this = default; + } + } +}