mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2025-02-09 13:14:46 +01:00
Update FsSystem.Utility
This commit is contained in:
parent
b86b57a4d3
commit
8bb6b0e824
118
src/LibHac/Fs/Common/DirectoryPathParser.cs
Normal file
118
src/LibHac/Fs/Common/DirectoryPathParser.cs
Normal file
@ -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<byte> _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<byte> pathBuffer = path.GetWriteBufferLength() != 0 ? path.GetWriteBuffer() : Span<byte>.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<byte> 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<byte> nextEntry = ReadNextImpl();
|
||||
|
||||
if (nextEntry.IsEmpty)
|
||||
{
|
||||
isFinished = true;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
return CurrentPath.AppendChild(nextEntry);
|
||||
}
|
||||
|
||||
private Span<byte> ReadNextImpl()
|
||||
{
|
||||
// Check if we've already hit the end of the path.
|
||||
if (_position < 0 || _buffer.At(0) == 0)
|
||||
return Span<byte>.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<byte> entry = _buffer.Slice(_position);
|
||||
|
||||
int i;
|
||||
for (i = _position; _buffer.At(i) != DirectorySeparator; i++)
|
||||
{
|
||||
if (_buffer.At(i) == 0)
|
||||
{
|
||||
if (i == _position)
|
||||
entry = Span<byte>.Empty;
|
||||
|
||||
_position = -1;
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
Assert.SdkAssert(_buffer.At(i + 1) != NullTerminator);
|
||||
|
||||
_replacedChar = DirectorySeparator;
|
||||
_buffer[i] = 0;
|
||||
_position = i;
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
}
|
@ -122,20 +122,26 @@ namespace LibHac.FsSrv.Impl
|
||||
}
|
||||
|
||||
public static Result CreateSubDirectoryFileSystem(out ReferenceCountedDisposable<IFileSystem> subDirFileSystem,
|
||||
ref ReferenceCountedDisposable<IFileSystem> baseFileSystem, U8Span subPath, bool preserveUnc = false)
|
||||
ref ReferenceCountedDisposable<IFileSystem> 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<SubdirectoryFileSystem>(fs))
|
||||
{
|
||||
rc = subDirFs.Target.Initialize(subPath);
|
||||
rc = subDirFs.Target.Initialize(in subPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
subDirFileSystem = subDirFs.AddReference<IFileSystem>();
|
||||
|
@ -27,15 +27,15 @@ namespace LibHac.FsSrv
|
||||
private ReferenceCountedDisposable<NcaFileSystemService>.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<NcaFileSystemService> CreateShared(
|
||||
|
@ -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<SaveDataFileSystemService> CreateShared(
|
||||
|
@ -139,9 +139,9 @@ namespace LibHac.FsSrv
|
||||
return _isNormalStorageOpened || _isInternalStorageOpened;
|
||||
}
|
||||
|
||||
public UniqueLock<SdkMutexType> GetLock()
|
||||
public UniqueLockRef<SdkMutexType> GetLock()
|
||||
{
|
||||
return new UniqueLock<SdkMutexType>(ref _mutex);
|
||||
return new UniqueLockRef<SdkMutexType>(ref _mutex);
|
||||
}
|
||||
}
|
||||
|
||||
@ -199,7 +199,7 @@ namespace LibHac.FsSrv
|
||||
|
||||
protected override Result DoRead(long offset, Span<byte> destination)
|
||||
{
|
||||
using UniqueLock<SdkMutexType> scopedLock = _baseStorage.Target.GetLock();
|
||||
using UniqueLockRef<SdkMutexType> 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<byte> source)
|
||||
{
|
||||
using UniqueLock<SdkMutexType> scopedLock = _baseStorage.Target.GetLock();
|
||||
using UniqueLockRef<SdkMutexType> 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<SdkMutexType> scopedLock = _baseStorage.Target.GetLock();
|
||||
using UniqueLockRef<SdkMutexType> 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<SdkMutexType> scopedLock = _baseStorage.Target.GetLock();
|
||||
using UniqueLockRef<SdkMutexType> 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<SdkMutexType> scopedLock = _baseStorage.Target.GetLock();
|
||||
using UniqueLockRef<SdkMutexType> 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<byte> outBuffer, OperationId operationId, long offset, long size,
|
||||
ReadOnlySpan<byte> inBuffer)
|
||||
{
|
||||
using UniqueLock<SdkMutexType> scopedLock = _baseStorage.Target.GetLock();
|
||||
using UniqueLockRef<SdkMutexType> scopedLock = _baseStorage.Target.GetLock();
|
||||
|
||||
Result rc = AccessCheck(isWriteAccess: true);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
@ -196,6 +196,39 @@ namespace LibHac.FsSystem
|
||||
base.Dispose();
|
||||
}
|
||||
|
||||
private Result RetryFinitelyForTargetLocked(Func<Result> 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;
|
||||
|
@ -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();
|
@ -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.</remarks>
|
||||
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<T> : IUniqueLock where T : class, IDisposable
|
||||
{
|
||||
private UniqueLockSemaphore _semaphore;
|
||||
private UniqueLock<SemaphoreAdapter> _semaphore;
|
||||
private ReferenceCountedDisposable<T> _pinnedObject;
|
||||
|
||||
public UniqueLockWithPin(ref UniqueLockSemaphore semaphore, ref ReferenceCountedDisposable<T> pinnedObject)
|
||||
public UniqueLockWithPin(ref UniqueLock<SemaphoreAdapter> semaphore, ref ReferenceCountedDisposable<T> pinnedObject)
|
||||
{
|
||||
Shared.Move(out _semaphore, ref semaphore);
|
||||
Shared.Move(out _pinnedObject, ref pinnedObject);
|
||||
|
||||
Assert.SdkAssert(_semaphore.IsLocked);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
@ -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<T>(out IUniqueLock uniqueLock, SemaphoreAdaptor semaphore,
|
||||
public static Result MakeUniqueLockWithPin<T>(out IUniqueLock uniqueLock, SemaphoreAdapter semaphore,
|
||||
ref ReferenceCountedDisposable<T> objectToPin) where T : class, IDisposable
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out uniqueLock);
|
||||
@ -266,30 +266,5 @@ namespace LibHac.FsSystem
|
||||
tempUniqueLock.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public static Result RetryFinitelyForTargetLocked(Func<Result> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
473
src/LibHac/FsSystem/Utility12.cs
Normal file
473
src/LibHac/FsSystem/Utility12.cs
Normal file
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Various utility functions used by the <see cref="LibHac.FsSystem"/> namespace.
|
||||
/// </summary>
|
||||
/// <remarks>Based on FS 12.0.3 (nnSdk 12.3.1)</remarks>
|
||||
internal static class Utility12
|
||||
{
|
||||
public delegate Result FsIterationTask(in Path path, in DirectoryEntry entry, ref FsIterationTaskClosure closure);
|
||||
|
||||
/// <summary>
|
||||
/// Used to pass various ref structs to an <see cref="FsIterationTask"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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 <see cref="FsIterationTask"/> 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 <see cref="Utility12.IterateDirectoryRecursively"/> 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.
|
||||
/// </remarks>
|
||||
public ref struct FsIterationTaskClosure
|
||||
{
|
||||
public Span<byte> Buffer;
|
||||
public Path DestinationPathBuffer;
|
||||
public IFileSystem SourceFileSystem;
|
||||
public IFileSystem DestFileSystem;
|
||||
}
|
||||
|
||||
private static ReadOnlySpan<byte> 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<byte> 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<byte> 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<byte> 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<byte> 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<byte> 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<SemaphoreAdapter> uniqueLock, SemaphoreAdapter semaphore)
|
||||
{
|
||||
UniqueLock<SemaphoreAdapter> tempUniqueLock = default;
|
||||
try
|
||||
{
|
||||
tempUniqueLock = new UniqueLock<SemaphoreAdapter>(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<T>(out IUniqueLock uniqueLock, SemaphoreAdapter semaphore,
|
||||
ref ReferenceCountedDisposable<T> objectToPin) where T : class, IDisposable
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out uniqueLock);
|
||||
|
||||
UniqueLock<SemaphoreAdapter> tempUniqueLock = default;
|
||||
try
|
||||
{
|
||||
Result rc = TryAcquireCountSemaphore(out tempUniqueLock, semaphore);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
uniqueLock = new UniqueLockWithPin<T>(ref tempUniqueLock, ref objectToPin);
|
||||
return Result.Success;
|
||||
}
|
||||
finally
|
||||
{
|
||||
tempUniqueLock.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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<TMutex> Lock<TMutex>(ref TMutex lockable) where TMutex : ILockable
|
||||
public static UniqueLockRef<TMutex> Lock<TMutex>(ref TMutex lockable) where TMutex : struct, ILockable
|
||||
{
|
||||
return new UniqueLock<TMutex>(ref lockable);
|
||||
return new UniqueLockRef<TMutex>(ref lockable);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static UniqueLock<TMutex> Lock<TMutex>(TMutex lockable) where TMutex : class, ILockable
|
||||
{
|
||||
return new UniqueLock<TMutex>(lockable);
|
||||
}
|
||||
}
|
||||
|
||||
public ref struct UniqueLock<TMutex> where TMutex : ILockable
|
||||
public ref struct UniqueLockRef<TMutex> where TMutex : struct, ILockable
|
||||
{
|
||||
private Ref<TMutex> _mutex;
|
||||
private bool _ownsLock;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public UniqueLock(ref TMutex mutex)
|
||||
public UniqueLockRef(ref TMutex mutex)
|
||||
{
|
||||
_mutex = new Ref<TMutex>(ref mutex);
|
||||
mutex.Lock();
|
||||
_ownsLock = true;
|
||||
}
|
||||
|
||||
public UniqueLock(ref UniqueLock<TMutex> other)
|
||||
public UniqueLockRef(ref UniqueLockRef<TMutex> other)
|
||||
{
|
||||
this = other;
|
||||
other = default;
|
||||
}
|
||||
|
||||
public void Set(ref UniqueLock<TMutex> other)
|
||||
public void Set(ref UniqueLockRef<TMutex> other)
|
||||
{
|
||||
if (_ownsLock)
|
||||
_mutex.Value.Unlock();
|
||||
@ -83,4 +90,75 @@ namespace LibHac.Os
|
||||
this = default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct UniqueLock<TMutex> : IDisposable where TMutex : class, ILockable
|
||||
{
|
||||
private TMutex _mutex;
|
||||
private bool _ownsLock;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public UniqueLock(TMutex mutex)
|
||||
{
|
||||
_mutex = new Ref<TMutex>(ref mutex);
|
||||
mutex.Lock();
|
||||
_ownsLock = true;
|
||||
}
|
||||
|
||||
public UniqueLock(ref UniqueLock<TMutex> other)
|
||||
{
|
||||
this = other;
|
||||
other = default;
|
||||
}
|
||||
|
||||
public void Set(ref UniqueLock<TMutex> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user