Update FsSystem.Utility

This commit is contained in:
Alex Barney 2021-07-23 16:08:12 -07:00
parent b86b57a4d3
commit 8bb6b0e824
11 changed files with 757 additions and 69 deletions

View 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;
}
}
}

View File

@ -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>();

View File

@ -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(

View File

@ -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(

View File

@ -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;

View File

@ -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;

View File

@ -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();

View File

@ -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()

View File

@ -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);
}
}
}
}

View 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();
}
}
}
}

View File

@ -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;
}
}
}