mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2025-02-09 13:14:46 +01:00
Merge pull request #204 from Thealexbarney/ifilesystem-path
Use nn::fs::Path in IFileSystem and other places
This commit is contained in:
commit
373ba8ea20
@ -14,6 +14,10 @@ namespace LibHac.Common
|
||||
private const int NullTerminatorLength = 1;
|
||||
|
||||
public Span<byte> Buffer { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The current length of the string not including the null terminator.
|
||||
/// </summary>
|
||||
public int Length { get; private set; }
|
||||
public bool Overflowed { get; private set; }
|
||||
public bool AutoExpand { get; }
|
||||
|
@ -635,7 +635,7 @@ namespace LibHac.Fs.Impl
|
||||
else
|
||||
{
|
||||
Result rc = fs.Fs.GetGlobalAccessLogMode(out g.GlobalAccessLogMode);
|
||||
fs.LogErrorMessage(rc);
|
||||
fs.LogResultErrorMessage(rc);
|
||||
if (rc.IsFailure()) Abort.DoAbort(rc);
|
||||
|
||||
if (g.GlobalAccessLogMode != GlobalAccessLogMode.None)
|
||||
@ -703,7 +703,7 @@ namespace LibHac.Fs.Impl
|
||||
public static void EnableFileSystemAccessorAccessLog(this FileSystemClientImpl fs, U8Span mountName)
|
||||
{
|
||||
Result rc = fs.Find(out FileSystemAccessor fileSystem, mountName);
|
||||
fs.LogErrorMessage(rc);
|
||||
fs.LogResultErrorMessage(rc);
|
||||
Abort.DoAbortUnless(rc.IsSuccess());
|
||||
|
||||
fileSystem.SetAccessLog(true);
|
||||
|
119
src/LibHac/Fs/Common/DirectoryPathParser.cs
Normal file
119
src/LibHac/Fs/Common/DirectoryPathParser.cs
Normal file
@ -0,0 +1,119 @@
|
||||
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 = WindowsPath.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;
|
||||
_position = 1;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -26,9 +26,103 @@ namespace LibHac.Fs
|
||||
public bool IsBackslashAllowed() => (_value & (1 << 4)) != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a file path stored as a UTF-8 string.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>A <see cref="Path"/> has three parts to it:<br/>
|
||||
/// 1. A <see cref="byte"/> <see cref="ReadOnlySpan{T}"/> that points to the current path string.<br/>
|
||||
/// 2. A write buffer that can be allocated if operations need to be done on the path.<br/>
|
||||
/// 3. An <c>IsNormalized</c> flag that tracks the path normalization status of the current path.</para>
|
||||
/// <para>There are two different ways to initialize a <see cref="Path"/>. The "Initialize*" methods will
|
||||
/// ensure a write buffer is allocated and copy the input path to it. <see cref="SetShallowBuffer"/> will
|
||||
/// directly use the input buffer without copying. If this method is used, the caller must ensure the path
|
||||
/// is normalized before passing it to <see cref="SetShallowBuffer"/>.</para>
|
||||
/// <br/>Based on FS 12.0.3 (nnSdk 12.3.1)</remarks>
|
||||
[DebuggerDisplay("{" + nameof(ToString) + "(),nq}")]
|
||||
public ref struct Path
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to store a path in a non-ref struct.
|
||||
/// </summary>
|
||||
[DebuggerDisplay("{" + nameof(ToString) + "(),nq}")]
|
||||
public struct Stored : IDisposable
|
||||
{
|
||||
private byte[] _buffer;
|
||||
private int _length;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
byte[] buffer = Shared.Move(ref _buffer);
|
||||
if (buffer is not null)
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes this <see cref="Stored"/> path with the data from a standard <see cref="Path"/>.
|
||||
/// <paramref name="path"/> must be normalized.
|
||||
/// </summary>
|
||||
/// <param name="path">The <see cref="Path"/> used to initialize this one.</param>
|
||||
/// <returns><see cref="Result.Success"/>: The operation was successful.<br/>
|
||||
/// <see cref="ResultFs.NotNormalized"/>: The <c>IsNormalized</c> flag of
|
||||
/// <paramref name="path"/> is not <see langword="true"/>.</returns>
|
||||
public Result Initialize(in Path path)
|
||||
{
|
||||
if (!path._isNormalized)
|
||||
return ResultFs.NotNormalized.Log();
|
||||
|
||||
_length = path.GetLength();
|
||||
|
||||
Result rc = Preallocate(_length + NullTerminatorLength);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
int bytesCopied = StringUtils.Copy(_buffer, path._string, _length + NullTerminatorLength);
|
||||
|
||||
if (bytesCopied != _length)
|
||||
return ResultFs.UnexpectedInPathA.Log();
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public readonly int GetLength() => _length;
|
||||
public readonly ReadOnlySpan<byte> GetString() => _buffer;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="Path"/> from this <see cref="Path.Stored"/>. This <see cref="Stored"/>
|
||||
/// must not be reinitialized or disposed for the lifetime of the created <see cref="Path"/>.
|
||||
/// </summary>
|
||||
/// <returns>The created <see cref="Path"/>.</returns>
|
||||
public readonly Path GetPath()
|
||||
{
|
||||
return new Path
|
||||
{
|
||||
_string = _buffer,
|
||||
_isNormalized = true
|
||||
};
|
||||
}
|
||||
|
||||
private Result Preallocate(int length)
|
||||
{
|
||||
if (_buffer is not null && _buffer.Length > length)
|
||||
return Result.Success;
|
||||
|
||||
int alignedLength = Alignment.AlignUpPow2(length, WriteBufferAlignmentLength);
|
||||
byte[] buffer = ArrayPool<byte>.Shared.Rent(alignedLength);
|
||||
|
||||
byte[] oldBuffer = _buffer;
|
||||
_buffer = buffer;
|
||||
|
||||
if (oldBuffer is not null)
|
||||
ArrayPool<byte>.Shared.Return(oldBuffer);
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public override string ToString() => StringUtils.Utf8ZToString(_buffer);
|
||||
}
|
||||
|
||||
private const int SeparatorLength = 1;
|
||||
private const int NullTerminatorLength = 1;
|
||||
private const int WriteBufferAlignmentLength = 8;
|
||||
@ -39,6 +133,7 @@ namespace LibHac.Fs
|
||||
private int _writeBufferLength;
|
||||
private bool _isNormalized;
|
||||
|
||||
// Todo: Hack around "using" variables being read only
|
||||
public void Dispose()
|
||||
{
|
||||
byte[] writeBuffer = Shared.Move(ref _writeBuffer);
|
||||
@ -48,27 +143,50 @@ namespace LibHac.Fs
|
||||
}
|
||||
}
|
||||
|
||||
private Span<byte> GetWriteBuffer()
|
||||
/// <summary>
|
||||
/// Gets the current write buffer.
|
||||
/// </summary>
|
||||
/// <returns>The write buffer.</returns>
|
||||
internal Span<byte> GetWriteBuffer()
|
||||
{
|
||||
Assert.SdkRequires(_writeBuffer is not null);
|
||||
return _writeBuffer.AsSpan();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current length of the write buffer.
|
||||
/// </summary>
|
||||
/// <returns>The write buffer length.</returns>
|
||||
internal readonly long GetWriteBufferLength()
|
||||
{
|
||||
return _writeBufferLength;
|
||||
}
|
||||
|
||||
private readonly int GetLength()
|
||||
/// <summary>
|
||||
/// Calculates the length of the current string.
|
||||
/// </summary>
|
||||
/// <returns>The length of the current string></returns>
|
||||
public readonly int GetLength()
|
||||
{
|
||||
return StringUtils.GetLength(GetString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns <see langword="true"/> if
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public readonly bool IsEmpty()
|
||||
{
|
||||
return _string.At(0) == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates if the first "<paramref name="length"/>" characters of the
|
||||
/// current path and <paramref name="value"/> are the same.
|
||||
/// </summary>
|
||||
/// <param name="value">The string to compare to this <see cref="Path"/>.</param>
|
||||
/// <param name="length">The maximum number of characters to compare.</param>
|
||||
/// <returns><see langword="true"/> if the strings are the same; otherwise <see langword="false"/>.</returns>
|
||||
public readonly bool IsMatchHead(ReadOnlySpan<byte> value, int length)
|
||||
{
|
||||
return StringUtils.Compare(GetString(), value, length) == 0;
|
||||
@ -94,6 +212,10 @@ namespace LibHac.Fs
|
||||
return StringUtils.Compare(left.GetString(), right) == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases this <see cref="Path"/>'s write buffer and returns it to the caller.
|
||||
/// </summary>
|
||||
/// <returns>The write buffer if the <see cref="Path"/> had one; otherwise <see langword="null"/>.</returns>
|
||||
public byte[] ReleaseBuffer()
|
||||
{
|
||||
Assert.SdkRequires(_writeBuffer is not null);
|
||||
@ -104,6 +226,9 @@ namespace LibHac.Fs
|
||||
return Shared.Move(ref _writeBuffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases any current write buffer and sets this <see cref="Path"/> to an empty string.
|
||||
/// </summary>
|
||||
private void ClearBuffer()
|
||||
{
|
||||
byte[] oldBuffer = Shared.Move(ref _writeBuffer);
|
||||
@ -115,6 +240,12 @@ namespace LibHac.Fs
|
||||
_string = EmptyPath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases any current write buffer and sets the provided buffer as the new write buffer.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The new write buffer.</param>
|
||||
/// <param name="length">The length of the write buffer.
|
||||
/// Must be a multiple of <see cref="WriteBufferAlignmentLength"/>.</param>
|
||||
private void SetModifiableBuffer(byte[] buffer, int length)
|
||||
{
|
||||
Assert.SdkRequiresNotNull(buffer);
|
||||
@ -131,6 +262,10 @@ namespace LibHac.Fs
|
||||
_string = buffer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases any current write buffer and sets <paramref name="buffer"/> as this <see cref="Path"/>'s string.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The buffer containing the new path.</param>
|
||||
private void SetReadOnlyBuffer(ReadOnlySpan<byte> buffer)
|
||||
{
|
||||
_string = buffer;
|
||||
@ -143,6 +278,11 @@ namespace LibHac.Fs
|
||||
_writeBufferLength = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures the write buffer is the specified <paramref name="length"/> or larger.
|
||||
/// </summary>
|
||||
/// <param name="length">The minimum desired length.</param>
|
||||
/// <returns><see cref="Result.Success"/>: The operation was successful.</returns>
|
||||
private Result Preallocate(int length)
|
||||
{
|
||||
if (_writeBufferLength > length)
|
||||
@ -155,6 +295,14 @@ namespace LibHac.Fs
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases any current write buffer and sets <paramref name="buffer"/> as this <see cref="Path"/>'s string.<br/>
|
||||
/// The path contained by <paramref name="buffer"/> must be normalized.
|
||||
/// </summary>
|
||||
/// <remarks>It is up to the caller to ensure the path contained by <paramref name="buffer"/> is normalized.
|
||||
/// This function will always set the <c>IsNormalized</c> flag to <see langword="true"/>.</remarks>
|
||||
/// <param name="buffer">The buffer containing the new path.</param>
|
||||
/// <returns><see cref="Result.Success"/>: The operation was successful.</returns>
|
||||
public Result SetShallowBuffer(ReadOnlySpan<byte> buffer)
|
||||
{
|
||||
Assert.SdkRequires(_writeBufferLength == 0);
|
||||
@ -164,6 +312,12 @@ namespace LibHac.Fs
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the buffer containing the current path.
|
||||
/// </summary>
|
||||
/// <remarks>This <see cref="Path"/>'s <c>IsNormalized</c> flag should be
|
||||
/// <see langword="true"/> before calling this function.</remarks>
|
||||
/// <returns>The buffer containing the current path.</returns>
|
||||
public readonly ReadOnlySpan<byte> GetString()
|
||||
{
|
||||
Assert.SdkAssert(_isNormalized);
|
||||
@ -171,6 +325,16 @@ namespace LibHac.Fs
|
||||
return _string;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes this <see cref="Path"/> with the data from another Path.<br/>
|
||||
/// <paramref name="other"/> must be normalized.
|
||||
/// </summary>
|
||||
/// <remarks>This <see cref="Path"/>'s <c>IsNormalized</c> flag will be set to
|
||||
/// the value of <paramref name="other"/>'s flag.</remarks>
|
||||
/// <param name="other">The <see cref="Path"/> used to initialize this one.</param>
|
||||
/// <returns><see cref="Result.Success"/>: The operation was successful.<br/>
|
||||
/// <see cref="ResultFs.NotNormalized"/>: The <c>IsNormalized</c> flag of
|
||||
/// <paramref name="other"/> is not <see langword="true"/>.</returns>
|
||||
public Result Initialize(in Path other)
|
||||
{
|
||||
if (!other._isNormalized)
|
||||
@ -190,6 +354,39 @@ namespace LibHac.Fs
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes this <see cref="Path"/> with the data from a <see cref="Stored"/> path.
|
||||
/// </summary>
|
||||
/// <remarks>Ensures we have a large enough write buffer and copies the path to it.
|
||||
/// This function always sets the <c>IsNormalized</c> flag to <see langword="true"/>
|
||||
/// because <see cref="Stored"/> paths are always normalized upon initialization.</remarks>
|
||||
/// <param name="other">The <see cref="Stored"/> path used to initialize this <see cref="Path"/>.</param>
|
||||
/// <returns><see cref="Result.Success"/>: The operation was successful.</returns>
|
||||
public Result Initialize(in Stored other)
|
||||
{
|
||||
int otherLength = other.GetLength();
|
||||
|
||||
Result rc = Preallocate(otherLength + NullTerminatorLength);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
int bytesCopied = StringUtils.Copy(_writeBuffer, other.GetString(), otherLength + NullTerminatorLength);
|
||||
|
||||
if (bytesCopied != otherLength)
|
||||
return ResultFs.UnexpectedInPathA.Log();
|
||||
|
||||
_isNormalized = true;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes this <see cref="Path"/> using the path in the provided buffer.
|
||||
/// </summary>
|
||||
/// <remarks>Ensures the write buffer is large enough to hold <paramref name="path"/>
|
||||
/// and copies <paramref name="path"/> to the write buffer.<br/>
|
||||
/// This function does not modify the <c>IsNormalized</c> flag.</remarks>
|
||||
/// <param name="path">The buffer containing the path to use.</param>
|
||||
/// <param name="length">The length of the provided path.</param>
|
||||
/// <returns><see cref="Result.Success"/>: The operation was successful.</returns>
|
||||
private Result InitializeImpl(ReadOnlySpan<byte> path, int length)
|
||||
{
|
||||
if (length == 0 || path.At(0) == NullTerminator)
|
||||
@ -209,6 +406,14 @@ namespace LibHac.Fs
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes this <see cref="Path"/> using the path in the provided buffer.
|
||||
/// </summary>
|
||||
/// <remarks>Ensures the write buffer is large enough to hold <paramref name="path"/>
|
||||
/// and copies <paramref name="path"/> to the write buffer.<br/>
|
||||
/// This function will always set the <c>IsNormalized</c> flag to <see langword="false"/>.</remarks>
|
||||
/// <param name="path">The buffer containing the path to use.</param>
|
||||
/// <returns><see cref="Result.Success"/>: The operation was successful.</returns>
|
||||
public Result Initialize(ReadOnlySpan<byte> path)
|
||||
{
|
||||
Result rc = InitializeImpl(path, StringUtils.GetLength(path));
|
||||
@ -218,12 +423,25 @@ namespace LibHac.Fs
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes this <see cref="Path"/> using the path in the provided buffer and
|
||||
/// normalizes it if the path is a relative path or a Windows path.
|
||||
/// </summary>
|
||||
/// <remarks>This function normalizes relative paths and Windows paths but does not normalize any other paths,
|
||||
/// although all paths are checked for invalid characters and if the path is in a valid format.<br/>
|
||||
/// The <c>IsNormalized</c> flag will always be set to <see langword="true"/> even if the incoming path
|
||||
/// is not normalized. This can lead to a situation where the path is not normalized yet the
|
||||
/// <c>IsNormalized</c> flag is still <see langword="true"/>.</remarks>
|
||||
/// <param name="path">The buffer containing the path to use.</param>
|
||||
/// <returns><see cref="Result.Success"/>: The operation was successful.<br/>
|
||||
/// <see cref="ResultFs.InvalidCharacter"/>: The path contains an invalid character.<br/>
|
||||
/// <see cref="ResultFs.InvalidPathFormat"/>: The path is not in a valid format.</returns>
|
||||
public Result InitializeWithNormalization(ReadOnlySpan<byte> path)
|
||||
{
|
||||
Result rc = Initialize(path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (_string.At(0) != NullTerminator && !WindowsPath12.IsWindowsPath(_string, false) &&
|
||||
if (_string.At(0) != NullTerminator && !WindowsPath.IsWindowsPath(_string, false) &&
|
||||
_string.At(0) != DirectorySeparator)
|
||||
{
|
||||
var flags = new PathFlags();
|
||||
@ -232,7 +450,7 @@ namespace LibHac.Fs
|
||||
rc = Normalize(flags);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
else if (WindowsPath12.IsWindowsPath(_string, true))
|
||||
else if (WindowsPath.IsWindowsPath(_string, true))
|
||||
{
|
||||
var flags = new PathFlags();
|
||||
flags.AllowWindowsPath();
|
||||
@ -242,7 +460,7 @@ namespace LibHac.Fs
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = PathNormalizer12.IsNormalized(out _isNormalized, out _, _string);
|
||||
rc = PathNormalizer.IsNormalized(out _isNormalized, out _, _string);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
|
||||
@ -253,6 +471,15 @@ namespace LibHac.Fs
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes this <see cref="Path"/> using the path in the provided buffer.
|
||||
/// </summary>
|
||||
/// <remarks>Ensures the write buffer is large enough to hold <paramref name="path"/>
|
||||
/// and copies <paramref name="path"/> to the write buffer.<br/>
|
||||
/// This function will always set the <c>IsNormalized</c> flag to <see langword="false"/>.</remarks>
|
||||
/// <param name="path">The buffer containing the path to use.</param>
|
||||
/// <param name="length">The length of the provided path.</param>
|
||||
/// <returns><see cref="Result.Success"/>: The operation was successful.</returns>
|
||||
public Result Initialize(ReadOnlySpan<byte> path, int length)
|
||||
{
|
||||
Result rc = InitializeImpl(path, length);
|
||||
@ -262,12 +489,26 @@ namespace LibHac.Fs
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes this <see cref="Path"/> using the path in the provided buffer and
|
||||
/// normalizes it if the path is a relative path or a Windows path.
|
||||
/// </summary>
|
||||
/// <remarks>This function normalizes relative paths and Windows paths but does not normalize any other paths,
|
||||
/// although all paths are checked for invalid characters and if the path is in a valid format.<br/>
|
||||
/// The <c>IsNormalized</c> flag will always be set to <see langword="true"/> even if the incoming path
|
||||
/// is not normalized. This can lead to a situation where the path is not normalized yet the
|
||||
/// <c>IsNormalized</c> flag is still <see langword="true"/>.</remarks>
|
||||
/// <param name="path">The buffer containing the path to use.</param>
|
||||
/// <param name="length">The length of the provided path.</param>
|
||||
/// <returns><see cref="Result.Success"/>: The operation was successful.<br/>
|
||||
/// <see cref="ResultFs.InvalidCharacter"/>: The path contains an invalid character.<br/>
|
||||
/// <see cref="ResultFs.InvalidPathFormat"/>: The path is not in a valid format.</returns>
|
||||
public Result InitializeWithNormalization(ReadOnlySpan<byte> path, int length)
|
||||
{
|
||||
Result rc = Initialize(path, length);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (_string.At(0) != NullTerminator && !WindowsPath12.IsWindowsPath(_string, false) &&
|
||||
if (_string.At(0) != NullTerminator && !WindowsPath.IsWindowsPath(_string, false) &&
|
||||
_string.At(0) != DirectorySeparator)
|
||||
{
|
||||
var flags = new PathFlags();
|
||||
@ -276,7 +517,7 @@ namespace LibHac.Fs
|
||||
rc = Normalize(flags);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
else if (WindowsPath12.IsWindowsPath(_string, true))
|
||||
else if (WindowsPath.IsWindowsPath(_string, true))
|
||||
{
|
||||
var flags = new PathFlags();
|
||||
flags.AllowWindowsPath();
|
||||
@ -286,7 +527,7 @@ namespace LibHac.Fs
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = PathNormalizer12.IsNormalized(out _isNormalized, out _, _string);
|
||||
rc = PathNormalizer.IsNormalized(out _isNormalized, out _, _string);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
|
||||
@ -297,6 +538,14 @@ namespace LibHac.Fs
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initializes this <see cref="Path"/> using the path in the provided buffer and
|
||||
/// replaces any backslashes in the path with forward slashes.
|
||||
/// </summary>
|
||||
/// <remarks>This function will always set the <c>IsNormalized</c> flag to <see langword="false"/>.</remarks>
|
||||
/// <param name="path">The buffer containing the path to use.</param>
|
||||
/// <returns><see cref="Result.Success"/>: The operation was successful.</returns>
|
||||
public Result InitializeWithReplaceBackslash(ReadOnlySpan<byte> path)
|
||||
{
|
||||
Result rc = InitializeImpl(path, StringUtils.GetLength(path));
|
||||
@ -304,7 +553,7 @@ namespace LibHac.Fs
|
||||
|
||||
if (_writeBufferLength > 1)
|
||||
{
|
||||
PathUtility12.Replace(GetWriteBuffer().Slice(0, _writeBufferLength - 1), AltDirectorySeparator,
|
||||
PathUtility.Replace(GetWriteBuffer().Slice(0, _writeBufferLength - 1), AltDirectorySeparator,
|
||||
DirectorySeparator);
|
||||
}
|
||||
|
||||
@ -312,6 +561,13 @@ namespace LibHac.Fs
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes this <see cref="Path"/> using the path in the provided buffer. If the path begins with two
|
||||
/// forward slashes (<c>//</c>), those two forward slashes will be replaced with two backslashes (<c>\\</c>).
|
||||
/// </summary>
|
||||
/// <remarks>This function will always set the <c>IsNormalized</c> flag to <see langword="false"/>.</remarks>
|
||||
/// <param name="path">The buffer containing the path to use.</param>
|
||||
/// <returns><see cref="Result.Success"/>: The operation was successful.</returns>
|
||||
public Result InitializeWithReplaceForwardSlashes(ReadOnlySpan<byte> path)
|
||||
{
|
||||
Result rc = InitializeImpl(path, StringUtils.GetLength(path));
|
||||
@ -331,6 +587,18 @@ namespace LibHac.Fs
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes this <see cref="Path"/> using the path in the provided buffer
|
||||
/// and makes various UNC path-related replacements.
|
||||
/// </summary>
|
||||
/// <remarks>The following replacements are made:<br/>
|
||||
/// <c>:///</c> located anywhere in the path is replaced with <c>:/\\</c><br/>
|
||||
/// <c>@Host://</c> located at the beginning of the path is replaced with <c>@Host:\\</c><br/>
|
||||
/// <c>//</c> located at the beginning of the path is replaced with <c>\\</c>
|
||||
/// <para>This function does not modify the <c>IsNormalized</c> flag.</para>
|
||||
/// </remarks>
|
||||
/// <param name="path">The buffer containing the path to use.</param>
|
||||
/// <returns><see cref="Result.Success"/>: The operation was successful.</returns>
|
||||
public Result InitializeWithReplaceUnc(ReadOnlySpan<byte> path)
|
||||
{
|
||||
Result rc = InitializeImpl(path, StringUtils.GetLength(path));
|
||||
@ -368,6 +636,11 @@ namespace LibHac.Fs
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the <see cref="Path"/> as an empty string.
|
||||
/// </summary>
|
||||
/// <remarks>This function will always set the <c>IsNormalized</c> flag to <see langword="true"/>.</remarks>
|
||||
/// <returns><see cref="Result.Success"/>: The operation was successful.</returns>
|
||||
public Result InitializeAsEmpty()
|
||||
{
|
||||
ClearBuffer();
|
||||
@ -376,12 +649,21 @@ namespace LibHac.Fs
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates this <see cref="Path"/> by prepending <paramref name="parent"/> to the current path.
|
||||
/// </summary>
|
||||
/// <remarks>This function does not modify the <c>IsNormalized</c> flag.
|
||||
/// If <paramref name="parent"/> is not normalized, this can lead to a situation where the resulting
|
||||
/// path is not normalized yet the <c>IsNormalized</c> flag is still <see langword="true"/>.</remarks>
|
||||
/// <param name="parent">The buffer containing the path to insert.</param>
|
||||
/// <returns><see cref="Result.Success"/>: The operation was successful.<br/>
|
||||
/// <see cref="ResultFs.NotImplemented"/>: The path provided in <paramref name="parent"/> is a Windows path.</returns>
|
||||
public Result InsertParent(ReadOnlySpan<byte> parent)
|
||||
{
|
||||
if (parent.Length == 0 || parent[0] == NullTerminator)
|
||||
return Result.Success;
|
||||
|
||||
if (WindowsPath12.IsWindowsPath(_string, false))
|
||||
if (WindowsPath.IsWindowsPath(_string, false))
|
||||
return ResultFs.NotImplemented.Log();
|
||||
|
||||
// Remove a trailing separator from the parent and a leading one from the child so we can
|
||||
@ -451,12 +733,12 @@ namespace LibHac.Fs
|
||||
int parentBytesCopied = StringUtils.Copy(destBuffer, parent, parentLength + SeparatorLength);
|
||||
|
||||
// Make sure we copied the expected number of parent bytes.
|
||||
if (parentHasTrailingSlash)
|
||||
if (!parentHasTrailingSlash)
|
||||
{
|
||||
if (parentBytesCopied != parentLength + SeparatorLength)
|
||||
if (parentBytesCopied != parentLength)
|
||||
return ResultFs.UnexpectedInPathA.Log();
|
||||
}
|
||||
else if (parentBytesCopied != parentLength)
|
||||
else if (parentBytesCopied != parentLength + SeparatorLength)
|
||||
{
|
||||
return ResultFs.UnexpectedInPathA.Log();
|
||||
}
|
||||
@ -479,11 +761,28 @@ namespace LibHac.Fs
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates this <see cref="Path"/> by prepending <paramref name="parent"/> to the current path.
|
||||
/// </summary>
|
||||
/// <remarks>This function does not modify the <c>IsNormalized</c> flag.
|
||||
/// If <paramref name="parent"/> is not normalized, this can lead to a situation where the resulting
|
||||
/// path is not normalized yet the <c>IsNormalized</c> flag is still <see langword="true"/>.</remarks>
|
||||
/// <param name="parent">The <see cref="Path"/> to insert.</param>
|
||||
/// <returns><see cref="Result.Success"/>: The operation was successful.<br/>
|
||||
/// <see cref="ResultFs.NotImplemented"/>: The path provided in <paramref name="parent"/> is a Windows path.</returns>
|
||||
public Result InsertParent(in Path parent)
|
||||
{
|
||||
return InsertParent(parent.GetString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates this <see cref="Path"/> by appending <paramref name="child"/> to the current path.
|
||||
/// </summary>
|
||||
/// <remarks>This function does not modify the <c>IsNormalized</c> flag.
|
||||
/// If <paramref name="child"/> is not normalized, this can lead to a situation where the resulting
|
||||
/// path is not normalized yet the <c>IsNormalized</c> flag is still <see langword="true"/>.</remarks>
|
||||
/// <param name="child">The buffer containing the child path to append to the current path.</param>
|
||||
/// <returns><see cref="Result.Success"/>: The operation was successful.</returns>
|
||||
public Result AppendChild(ReadOnlySpan<byte> child)
|
||||
{
|
||||
ReadOnlySpan<byte> trimmedChild = child;
|
||||
@ -515,7 +814,7 @@ namespace LibHac.Fs
|
||||
if (_string[parentLength - 1] == DirectorySeparator || _string[parentLength - 1] == AltDirectorySeparator)
|
||||
parentLength--;
|
||||
|
||||
int childLength = StringUtils.GetLength(child);
|
||||
int childLength = StringUtils.GetLength(trimmedChild);
|
||||
|
||||
byte[] parentBuffer = null;
|
||||
try
|
||||
@ -543,22 +842,43 @@ namespace LibHac.Fs
|
||||
|
||||
if (childBytesCopied != childLength)
|
||||
return ResultFs.UnexpectedInPathA.Log();
|
||||
|
||||
// Note: Nintendo does not reset the "_isNormalized" field on the Path.
|
||||
// This can result in the field and the actual normalization state being out of sync.
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (parentBuffer is not null)
|
||||
ArrayPool<byte>.Shared.Return(parentBuffer);
|
||||
}
|
||||
|
||||
_isNormalized = false;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates this <see cref="Path"/> by appending <paramref name="child"/> to the current path.
|
||||
/// </summary>
|
||||
/// <remarks>This function does not modify the <c>IsNormalized</c> flag.
|
||||
/// If <paramref name="child"/> is not normalized, this can lead to a situation where the resulting
|
||||
/// path is not normalized yet the <c>IsNormalized</c> flag is still <see langword="true"/>.</remarks>
|
||||
/// <param name="child">The child <see cref="Path"/> to append to the current path.</param>
|
||||
/// <returns><see cref="Result.Success"/>: The operation was successful.</returns>
|
||||
public Result AppendChild(in Path child)
|
||||
{
|
||||
return AppendChild(child.GetString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Combines 2 <see cref="Path"/>s into a single path.
|
||||
/// </summary>
|
||||
/// <remarks>If <paramref name="path1"/> is empty, this <see cref="Path"/>'s <c>IsNormalized</c> flag will
|
||||
/// be set to the value of <paramref name="path2"/>'s flag.
|
||||
/// Otherwise the flag will be set to the value of <paramref name="path1"/>'s flag.</remarks>
|
||||
/// <param name="path1">The first path to combine.</param>
|
||||
/// <param name="path2">The second path to combine.</param>
|
||||
/// <returns><see cref="Result.Success"/>: The operation was successful.<br/>
|
||||
/// <see cref="ResultFs.NotNormalized"/>: The <c>IsNormalized</c> flag of either
|
||||
/// <paramref name="path1"/> or <paramref name="path2"/> is not <see langword="true"/>.</returns>
|
||||
public Result Combine(in Path path1, in Path path2)
|
||||
{
|
||||
int path1Length = path1.GetLength();
|
||||
@ -567,23 +887,30 @@ namespace LibHac.Fs
|
||||
Result rc = Preallocate(path1Length + SeparatorLength + path2Length + NullTerminatorLength);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = Initialize(path1);
|
||||
rc = Initialize(in path1);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (IsEmpty())
|
||||
{
|
||||
rc = Initialize(path2);
|
||||
rc = Initialize(in path2);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = AppendChild(path2);
|
||||
rc = AppendChild(in path2);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the last entry from this <see cref="Path"/>.
|
||||
/// </summary>
|
||||
/// <remarks>This function does not modify the <c>IsNormalized</c> flag.</remarks>
|
||||
/// <returns><see cref="Result.Success"/>: The operation was successful.<br/>
|
||||
/// <see cref="ResultFs.NotImplemented"/>: The path before calling this function was
|
||||
/// one of "<c>.</c>", "<c>..</c>", "<c>/</c>" or "<c>\</c>".</returns>
|
||||
public Result RemoveChild()
|
||||
{
|
||||
// Make sure the Path has a buffer that we can write to.
|
||||
@ -650,10 +977,18 @@ namespace LibHac.Fs
|
||||
if (currentPos <= 0)
|
||||
return ResultFs.NotImplemented.Log();
|
||||
|
||||
_isNormalized = false;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Normalizes the current path according to the provided <paramref name="flags"/>.
|
||||
/// </summary>
|
||||
/// <remarks>If this <see cref="Path"/>'s <c>IsNormalized</c> flag is set, this function does nothing.
|
||||
/// The <c>IsNormalized</c> flag will be set if this function returns successfully.</remarks>
|
||||
/// <param name="flags">Flags that specify what types of paths are allowed.</param>
|
||||
/// <returns><see cref="Result.Success"/>: The operation was successful.<br/>
|
||||
/// <see cref="ResultFs.InvalidCharacter"/>: The path contains an invalid character.<br/>
|
||||
/// <see cref="ResultFs.InvalidPathFormat"/>: The path is in an invalid format for the specified <paramref name="flags"/>.</returns>
|
||||
public Result Normalize(PathFlags flags)
|
||||
{
|
||||
if (_isNormalized)
|
||||
@ -670,10 +1005,10 @@ namespace LibHac.Fs
|
||||
|
||||
int bufferLength = _writeBufferLength;
|
||||
|
||||
if (flags.IsRelativePathAllowed() && PathUtility12.IsPathRelative(_string))
|
||||
if (flags.IsRelativePathAllowed() && PathUtility.IsPathRelative(_string))
|
||||
bufferLength += 2;
|
||||
|
||||
if (flags.IsWindowsPathAllowed() && WindowsPath12.IsWindowsPath(_string, true))
|
||||
if (flags.IsWindowsPathAllowed() && WindowsPath.IsWindowsPath(_string, true))
|
||||
bufferLength += 1;
|
||||
|
||||
int alignedBufferLength = Alignment.AlignUpPow2(bufferLength, WriteBufferAlignmentLength);
|
||||
@ -709,7 +1044,7 @@ namespace LibHac.Fs
|
||||
{
|
||||
public static Result SetUpFixedPath(ref Path path, ReadOnlySpan<byte> pathBuffer)
|
||||
{
|
||||
Result rc = PathNormalizer12.IsNormalized(out bool isNormalized, out _, pathBuffer);
|
||||
Result rc = PathNormalizer.IsNormalized(out bool isNormalized, out _, pathBuffer);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (!isNormalized)
|
||||
@ -720,5 +1055,64 @@ namespace LibHac.Fs
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
// Only a small number of format strings are used with these functions, so we can hard code them all easily.
|
||||
|
||||
// /%s
|
||||
internal static Result SetUpFixedPathSingleEntry(ref Path path, Span<byte> pathBuffer,
|
||||
ReadOnlySpan<byte> entryName)
|
||||
{
|
||||
var sb = new U8StringBuilder(pathBuffer);
|
||||
sb.Append((byte)'/').Append(entryName);
|
||||
|
||||
if (sb.Overflowed)
|
||||
return ResultFs.InvalidArgument.Log();
|
||||
|
||||
return SetUpFixedPath(ref path, pathBuffer);
|
||||
}
|
||||
|
||||
// /%016llx
|
||||
internal static Result SetUpFixedPathSaveId(ref Path path, Span<byte> pathBuffer, ulong saveDataId)
|
||||
{
|
||||
var sb = new U8StringBuilder(pathBuffer);
|
||||
sb.Append((byte)'/').AppendFormat(saveDataId, 'x', 16);
|
||||
|
||||
if (sb.Overflowed)
|
||||
return ResultFs.InvalidArgument.Log();
|
||||
|
||||
return SetUpFixedPath(ref path, pathBuffer);
|
||||
}
|
||||
|
||||
// /%08x.meta
|
||||
internal static Result SetUpFixedPathSaveMetaName(ref Path path, Span<byte> pathBuffer, uint metaType)
|
||||
{
|
||||
ReadOnlySpan<byte> metaExtension = new[] { (byte)'.', (byte)'m', (byte)'e', (byte)'t', (byte)'a' }; // ".meta"
|
||||
|
||||
var sb = new U8StringBuilder(pathBuffer);
|
||||
sb.Append((byte)'/').AppendFormat(metaType, 'x', 8).Append(metaExtension);
|
||||
|
||||
if (sb.Overflowed)
|
||||
return ResultFs.InvalidArgument.Log();
|
||||
|
||||
return SetUpFixedPath(ref path, pathBuffer);
|
||||
}
|
||||
|
||||
// /saveMeta/%016llx
|
||||
internal static Result SetUpFixedPathSaveMetaDir(ref Path path, Span<byte> pathBuffer, ulong saveDataId)
|
||||
{
|
||||
ReadOnlySpan<byte> metaDirectoryName = new[]
|
||||
{
|
||||
(byte)'/', (byte)'s', (byte)'a', (byte)'v', (byte)'e', (byte)'M', (byte)'e', (byte)'t',
|
||||
(byte)'a', (byte)'/'
|
||||
};
|
||||
|
||||
var sb = new U8StringBuilder(pathBuffer);
|
||||
sb.Append(metaDirectoryName).AppendFormat(saveDataId, 'x', 16);
|
||||
|
||||
if (sb.Overflowed)
|
||||
return ResultFs.InvalidArgument.Log();
|
||||
|
||||
return SetUpFixedPath(ref path, pathBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,10 @@ using static LibHac.Fs.StringTraits;
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains functions for working with path formatting and normalization.
|
||||
/// </summary>
|
||||
/// <remarks>Based on FS 12.0.3 (nnSdk 12.3.1)</remarks>
|
||||
public static class PathFormatter
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@ -133,7 +137,7 @@ namespace LibHac.Fs
|
||||
|
||||
currentPath = path.Slice(1);
|
||||
}
|
||||
else if (path.Length != 0 && WindowsPath12.IsWindowsDrive(path.Slice(1)))
|
||||
else if (path.Length != 0 && WindowsPath.IsWindowsDrive(path.Slice(1)))
|
||||
{
|
||||
if (normalizeBuffer.Length == 0)
|
||||
return ResultFs.NotNormalized.Log();
|
||||
@ -142,7 +146,7 @@ namespace LibHac.Fs
|
||||
}
|
||||
}
|
||||
|
||||
if (WindowsPath12.IsWindowsDrive(currentPath))
|
||||
if (WindowsPath.IsWindowsDrive(currentPath))
|
||||
{
|
||||
int winPathLength;
|
||||
for (winPathLength = 2; currentPath.At(winPathLength) != NullTerminator; winPathLength++)
|
||||
@ -170,7 +174,7 @@ namespace LibHac.Fs
|
||||
|
||||
currentPath.Slice(0, winPathLength).CopyTo(normalizeBuffer);
|
||||
normalizeBuffer[winPathLength] = NullTerminator;
|
||||
PathUtility12.Replace(normalizeBuffer.Slice(0, winPathLength), AltDirectorySeparator,
|
||||
PathUtility.Replace(normalizeBuffer.Slice(0, winPathLength), AltDirectorySeparator,
|
||||
DirectorySeparator);
|
||||
}
|
||||
|
||||
@ -179,11 +183,11 @@ namespace LibHac.Fs
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
if (WindowsPath12.IsDosDevicePath(currentPath))
|
||||
if (WindowsPath.IsDosDevicePath(currentPath))
|
||||
{
|
||||
int dosPathLength = WindowsPath12.GetDosDevicePathPrefixLength();
|
||||
int dosPathLength = WindowsPath.GetDosDevicePathPrefixLength();
|
||||
|
||||
if (WindowsPath12.IsWindowsDrive(currentPath.Slice(dosPathLength)))
|
||||
if (WindowsPath.IsWindowsDrive(currentPath.Slice(dosPathLength)))
|
||||
{
|
||||
dosPathLength += 2;
|
||||
}
|
||||
@ -199,7 +203,7 @@ namespace LibHac.Fs
|
||||
|
||||
currentPath.Slice(0, dosPathLength).CopyTo(normalizeBuffer);
|
||||
normalizeBuffer[dosPathLength] = NullTerminator;
|
||||
PathUtility12.Replace(normalizeBuffer.Slice(0, dosPathLength), DirectorySeparator,
|
||||
PathUtility.Replace(normalizeBuffer.Slice(0, dosPathLength), DirectorySeparator,
|
||||
AltDirectorySeparator);
|
||||
}
|
||||
|
||||
@ -208,7 +212,7 @@ namespace LibHac.Fs
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
if (WindowsPath12.IsUncPath(currentPath, false, true))
|
||||
if (WindowsPath.IsUncPath(currentPath, false, true))
|
||||
{
|
||||
Result rc;
|
||||
|
||||
@ -274,7 +278,7 @@ namespace LibHac.Fs
|
||||
|
||||
currentPath.Slice(0, uncPrefixLength).CopyTo(normalizeBuffer);
|
||||
normalizeBuffer[uncPrefixLength] = NullTerminator;
|
||||
PathUtility12.Replace(normalizeBuffer.Slice(0, uncPrefixLength), DirectorySeparator, AltDirectorySeparator);
|
||||
PathUtility.Replace(normalizeBuffer.Slice(0, uncPrefixLength), DirectorySeparator, AltDirectorySeparator);
|
||||
}
|
||||
|
||||
newPath = finalPath;
|
||||
@ -364,7 +368,7 @@ namespace LibHac.Fs
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out isNormalized, out normalizedLength);
|
||||
|
||||
Result rc = PathUtility12.CheckUtf8(path);
|
||||
Result rc = PathUtility.CheckUtf8(path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
ReadOnlySpan<byte> buffer = path;
|
||||
@ -388,7 +392,7 @@ namespace LibHac.Fs
|
||||
return ResultFs.InvalidPathFormat.Log();
|
||||
}
|
||||
|
||||
if (WindowsPath12.IsWindowsPath(path, false) && !flags.IsWindowsPathAllowed())
|
||||
if (WindowsPath.IsWindowsPath(path, false) && !flags.IsWindowsPathAllowed())
|
||||
return ResultFs.InvalidPathFormat.Log();
|
||||
|
||||
bool hasMountName = false;
|
||||
@ -405,10 +409,10 @@ namespace LibHac.Fs
|
||||
hasMountName = true;
|
||||
}
|
||||
|
||||
if (buffer.At(0) != DirectorySeparator && !PathUtility12.IsPathStartWithCurrentDirectory(buffer) &&
|
||||
!WindowsPath12.IsWindowsPath(buffer, false))
|
||||
if (buffer.At(0) != DirectorySeparator && !PathUtility.IsPathStartWithCurrentDirectory(buffer) &&
|
||||
!WindowsPath.IsWindowsPath(buffer, false))
|
||||
{
|
||||
if (!flags.IsRelativePathAllowed() || !PathUtility12.CheckInvalidCharacter(buffer.At(0)).IsSuccess())
|
||||
if (!flags.IsRelativePathAllowed() || !PathUtility.CheckInvalidCharacter(buffer.At(0)).IsSuccess())
|
||||
return ResultFs.InvalidPathFormat.Log();
|
||||
|
||||
isNormalized = false;
|
||||
@ -475,10 +479,10 @@ namespace LibHac.Fs
|
||||
}
|
||||
}
|
||||
|
||||
if (PathNormalizer12.IsParentDirectoryPathReplacementNeeded(buffer))
|
||||
if (PathNormalizer.IsParentDirectoryPathReplacementNeeded(buffer))
|
||||
return ResultFs.DirectoryUnobtainable.Log();
|
||||
|
||||
rc = PathUtility12.CheckInvalidBackslash(out bool isBackslashContained, buffer,
|
||||
rc = PathUtility.CheckInvalidBackslash(out bool isBackslashContained, buffer,
|
||||
flags.IsWindowsPathAllowed() || flags.IsBackslashAllowed());
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
@ -488,7 +492,7 @@ namespace LibHac.Fs
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
rc = PathNormalizer12.IsNormalized(out isNormalized, out int length, buffer);
|
||||
rc = PathNormalizer.IsNormalized(out isNormalized, out int length, buffer);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
totalLength += length;
|
||||
@ -528,10 +532,10 @@ namespace LibHac.Fs
|
||||
|
||||
bool isDriveRelative = false;
|
||||
|
||||
if (src.At(0) != DirectorySeparator && !PathUtility12.IsPathStartWithCurrentDirectory(src) &&
|
||||
!WindowsPath12.IsWindowsPath(src, false))
|
||||
if (src.At(0) != DirectorySeparator && !PathUtility.IsPathStartWithCurrentDirectory(src) &&
|
||||
!WindowsPath.IsWindowsPath(src, false))
|
||||
{
|
||||
if (!flags.IsRelativePathAllowed() || !PathUtility12.CheckInvalidCharacter(src.At(0)).IsSuccess())
|
||||
if (!flags.IsRelativePathAllowed() || !PathUtility.CheckInvalidCharacter(src.At(0)).IsSuccess())
|
||||
return ResultFs.InvalidPathFormat.Log();
|
||||
|
||||
outputBuffer[currentPos++] = Dot;
|
||||
@ -589,7 +593,7 @@ namespace LibHac.Fs
|
||||
isWindowsPath = true;
|
||||
}
|
||||
|
||||
rc = PathUtility12.CheckInvalidBackslash(out bool isBackslashContained, src,
|
||||
rc = PathUtility.CheckInvalidBackslash(out bool isBackslashContained, src,
|
||||
flags.IsWindowsPathAllowed() || flags.IsBackslashAllowed());
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
@ -601,7 +605,7 @@ namespace LibHac.Fs
|
||||
srcBufferSlashReplaced = ArrayPool<byte>.Shared.Rent(path.Length);
|
||||
|
||||
StringUtils.Copy(srcBufferSlashReplaced, path);
|
||||
PathUtility12.Replace(srcBufferSlashReplaced, AltDirectorySeparator, DirectorySeparator);
|
||||
PathUtility.Replace(srcBufferSlashReplaced, AltDirectorySeparator, DirectorySeparator);
|
||||
|
||||
int srcOffset = (int)Unsafe.ByteOffset(ref MemoryMarshal.GetReference(path),
|
||||
ref MemoryMarshal.GetReference(src));
|
||||
@ -609,7 +613,7 @@ namespace LibHac.Fs
|
||||
src = srcBufferSlashReplaced.AsSpan(srcOffset);
|
||||
}
|
||||
|
||||
rc = PathNormalizer12.Normalize(outputBuffer.Slice(currentPos), out _, src, isWindowsPath, isDriveRelative);
|
||||
rc = PathNormalizer.Normalize(outputBuffer.Slice(currentPos), out _, src, isWindowsPath, isDriveRelative);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return Result.Success;
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,368 +0,0 @@
|
||||
using System;
|
||||
using LibHac.Common;
|
||||
using LibHac.Diag;
|
||||
using LibHac.FsSystem;
|
||||
using static LibHac.Fs.PathUtility12;
|
||||
using static LibHac.Fs.StringTraits;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public static class PathNormalizer12
|
||||
{
|
||||
private enum PathState
|
||||
{
|
||||
Initial,
|
||||
Normal,
|
||||
FirstSeparator,
|
||||
Separator,
|
||||
CurrentDir,
|
||||
ParentDir
|
||||
}
|
||||
|
||||
public static Result Normalize(Span<byte> outputBuffer, out int length, ReadOnlySpan<byte> path, bool isWindowsPath,
|
||||
bool isDriveRelativePath)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out length);
|
||||
|
||||
ReadOnlySpan<byte> currentPath = path;
|
||||
int totalLength = 0;
|
||||
int i = 0;
|
||||
|
||||
if (!IsSeparator(path.At(0)))
|
||||
{
|
||||
if (!isDriveRelativePath)
|
||||
return ResultFs.InvalidPathFormat.Log();
|
||||
|
||||
outputBuffer[totalLength++] = DirectorySeparator;
|
||||
}
|
||||
|
||||
var convertedPath = new RentedArray<byte>();
|
||||
try
|
||||
{
|
||||
// Check if parent directory path replacement is needed.
|
||||
if (IsParentDirectoryPathReplacementNeeded(currentPath))
|
||||
{
|
||||
// Allocate a buffer to hold the replacement path.
|
||||
convertedPath = new RentedArray<byte>(PathTools.MaxPathLength + 1);
|
||||
|
||||
// Replace the path.
|
||||
ReplaceParentDirectoryPath(convertedPath.Span, currentPath);
|
||||
|
||||
// Set current path to be the replacement path.
|
||||
currentPath = new U8Span(convertedPath.Span);
|
||||
}
|
||||
|
||||
bool skipNextSeparator = false;
|
||||
|
||||
while (!IsNul(currentPath.At(i)))
|
||||
{
|
||||
if (IsSeparator(currentPath[i]))
|
||||
{
|
||||
do
|
||||
{
|
||||
i++;
|
||||
} while (IsSeparator(currentPath.At(i)));
|
||||
|
||||
if (IsNul(currentPath.At(i)))
|
||||
break;
|
||||
|
||||
if (!skipNextSeparator)
|
||||
{
|
||||
if (totalLength + 1 == outputBuffer.Length)
|
||||
{
|
||||
outputBuffer[totalLength] = NullTerminator;
|
||||
length = totalLength;
|
||||
|
||||
return ResultFs.TooLongPath.Log();
|
||||
}
|
||||
|
||||
outputBuffer[totalLength++] = DirectorySeparator;
|
||||
}
|
||||
|
||||
skipNextSeparator = false;
|
||||
}
|
||||
|
||||
int dirLen = 0;
|
||||
while (!IsSeparator(currentPath.At(i + dirLen)) && !IsNul(currentPath.At(i + dirLen)))
|
||||
{
|
||||
dirLen++;
|
||||
}
|
||||
|
||||
if (IsCurrentDirectory(currentPath.Slice(i)))
|
||||
{
|
||||
skipNextSeparator = true;
|
||||
}
|
||||
else if (IsParentDirectory(currentPath.Slice(i)))
|
||||
{
|
||||
Assert.SdkAssert(outputBuffer[totalLength - 1] == DirectorySeparator);
|
||||
|
||||
if (!isWindowsPath)
|
||||
Assert.SdkAssert(outputBuffer[0] == DirectorySeparator);
|
||||
|
||||
if (totalLength == 1)
|
||||
{
|
||||
if (!isWindowsPath)
|
||||
return ResultFs.DirectoryUnobtainable.Log();
|
||||
|
||||
totalLength--;
|
||||
}
|
||||
else
|
||||
{
|
||||
totalLength -= 2;
|
||||
|
||||
do
|
||||
{
|
||||
if (outputBuffer[totalLength] == DirectorySeparator)
|
||||
break;
|
||||
|
||||
totalLength--;
|
||||
} while (totalLength != 0);
|
||||
}
|
||||
|
||||
if (!isWindowsPath)
|
||||
Assert.SdkAssert(outputBuffer[totalLength] == DirectorySeparator);
|
||||
|
||||
Assert.SdkAssert(totalLength < outputBuffer.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (totalLength + dirLen + 1 > outputBuffer.Length)
|
||||
{
|
||||
int copyLen = outputBuffer.Length - 1 - totalLength;
|
||||
|
||||
for (int j = 0; j < copyLen; j++)
|
||||
{
|
||||
outputBuffer[totalLength++] = currentPath[i + j];
|
||||
}
|
||||
|
||||
outputBuffer[totalLength] = NullTerminator;
|
||||
length = totalLength;
|
||||
return ResultFs.TooLongPath.Log();
|
||||
}
|
||||
|
||||
for (int j = 0; j < dirLen; j++)
|
||||
{
|
||||
outputBuffer[totalLength++] = currentPath[i + j];
|
||||
}
|
||||
}
|
||||
|
||||
i += dirLen;
|
||||
}
|
||||
|
||||
if (skipNextSeparator)
|
||||
totalLength--;
|
||||
|
||||
if (totalLength == 0 && outputBuffer.Length != 0)
|
||||
{
|
||||
totalLength = 1;
|
||||
outputBuffer[0] = DirectorySeparator;
|
||||
}
|
||||
|
||||
// Note: This bug is in the original code. They probably meant to put "totalLength + 1"
|
||||
if (totalLength - 1 > outputBuffer.Length)
|
||||
return ResultFs.TooLongPath.Log();
|
||||
|
||||
outputBuffer[totalLength] = NullTerminator;
|
||||
|
||||
Result rc = IsNormalized(out bool isNormalized, out _, outputBuffer);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
Assert.SdkAssert(isNormalized);
|
||||
|
||||
length = totalLength;
|
||||
return Result.Success;
|
||||
}
|
||||
finally
|
||||
{
|
||||
convertedPath.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a given path is normalized. Path must be a basic path, starting with a directory separator
|
||||
/// and not containing any sort of prefix such as a mount name.
|
||||
/// </summary>
|
||||
/// <param name="isNormalized">When this function returns <see cref="Result.Success"/>,
|
||||
/// contains <see langword="true"/> if the path is normalized or <see langword="false"/> if it is not.
|
||||
/// Contents are undefined if the function does not return <see cref="Result.Success"/>.
|
||||
/// </param>
|
||||
/// <param name="length">When this function returns <see cref="Result.Success"/> and
|
||||
/// <paramref name="isNormalized"/> is <see langword="true"/>, contains the length of the normalized path.
|
||||
/// Contents are undefined if the function does not return <see cref="Result.Success"/>
|
||||
/// or <paramref name="isNormalized"/> is <see langword="false"/>.
|
||||
/// </param>
|
||||
/// <param name="path">The path to check.</param>
|
||||
/// <returns><see cref="Result.Success"/>: The operation was successful.<br/>
|
||||
/// <see cref="ResultFs.InvalidCharacter"/>: The path contains an invalid character.<br/>
|
||||
/// <see cref="ResultFs.InvalidPathFormat"/>: The path is not in a valid format.</returns>
|
||||
public static Result IsNormalized(out bool isNormalized, out int length, ReadOnlySpan<byte> path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out isNormalized, out length);
|
||||
|
||||
var state = PathState.Initial;
|
||||
int pathLength = 0;
|
||||
|
||||
for (int i = 0; i < path.Length; i++)
|
||||
{
|
||||
byte c = path[i];
|
||||
if (c == NullTerminator) break;
|
||||
|
||||
pathLength++;
|
||||
|
||||
if (state != PathState.Initial)
|
||||
{
|
||||
Result rc = CheckInvalidCharacter(c);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case PathState.Initial:
|
||||
if (c != DirectorySeparator)
|
||||
return ResultFs.InvalidPathFormat.Log();
|
||||
|
||||
state = PathState.FirstSeparator;
|
||||
|
||||
break;
|
||||
case PathState.Normal:
|
||||
|
||||
if (c == DirectorySeparator)
|
||||
state = PathState.Separator;
|
||||
|
||||
break;
|
||||
case PathState.FirstSeparator:
|
||||
case PathState.Separator:
|
||||
if (c == DirectorySeparator)
|
||||
{
|
||||
isNormalized = false;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
state = c == Dot ? PathState.CurrentDir : PathState.Normal;
|
||||
break;
|
||||
case PathState.CurrentDir:
|
||||
if (c == DirectorySeparator)
|
||||
{
|
||||
isNormalized = false;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
state = c == Dot ? PathState.ParentDir : PathState.Normal;
|
||||
break;
|
||||
case PathState.ParentDir:
|
||||
if (c == DirectorySeparator)
|
||||
{
|
||||
isNormalized = false;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
state = PathState.Normal;
|
||||
break;
|
||||
// ReSharper disable once UnreachableSwitchCaseDueToIntegerAnalysis
|
||||
default:
|
||||
Abort.UnexpectedDefault();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case PathState.Initial:
|
||||
return ResultFs.InvalidPathFormat.Log();
|
||||
case PathState.Normal:
|
||||
case PathState.FirstSeparator:
|
||||
isNormalized = true;
|
||||
break;
|
||||
case PathState.Separator:
|
||||
case PathState.CurrentDir:
|
||||
case PathState.ParentDir:
|
||||
isNormalized = false;
|
||||
break;
|
||||
// ReSharper disable once UnreachableSwitchCaseDueToIntegerAnalysis
|
||||
default:
|
||||
Abort.UnexpectedDefault();
|
||||
break;
|
||||
}
|
||||
|
||||
length = pathLength;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a path begins with / or \ and contains any of these patterns:
|
||||
/// "/..\", "\..\", "\../", "\..0" where '0' is the null terminator.
|
||||
/// </summary>
|
||||
public static bool IsParentDirectoryPathReplacementNeeded(ReadOnlySpan<byte> path)
|
||||
{
|
||||
if (path.Length == 0 || (path[0] != DirectorySeparator && path[0] != AltDirectorySeparator))
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < path.Length - 2 && path[i] != NullTerminator; i++)
|
||||
{
|
||||
byte c3 = path.At(i + 3);
|
||||
|
||||
if (path[i] == AltDirectorySeparator &&
|
||||
path[i + 1] == Dot &&
|
||||
path[i + 2] == Dot &&
|
||||
(c3 == DirectorySeparator || c3 == AltDirectorySeparator || c3 == NullTerminator))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((path[i] == DirectorySeparator || path[i] == AltDirectorySeparator) &&
|
||||
path[i + 1] == Dot &&
|
||||
path[i + 2] == Dot &&
|
||||
c3 == AltDirectorySeparator)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void ReplaceParentDirectoryPath(Span<byte> dest, ReadOnlySpan<byte> source)
|
||||
{
|
||||
dest[0] = DirectorySeparator;
|
||||
|
||||
int i = 1;
|
||||
while (source.Length > i && source[i] != NullTerminator)
|
||||
{
|
||||
if (source.Length > i + 2 &&
|
||||
(source[i - 1] == DirectorySeparator || source[i - 1] == AltDirectorySeparator) &&
|
||||
source[i + 0] == Dot &&
|
||||
source[i + 1] == Dot &&
|
||||
(source[i + 2] == DirectorySeparator || source[i + 2] == AltDirectorySeparator))
|
||||
{
|
||||
dest[i - 1] = DirectorySeparator;
|
||||
dest[i + 0] = Dot;
|
||||
dest[i + 1] = Dot;
|
||||
dest[i + 2] = DirectorySeparator;
|
||||
i += 3;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (source.Length > i + 1 &&
|
||||
source[i - 1] == AltDirectorySeparator &&
|
||||
source[i + 0] == Dot &&
|
||||
source[i + 1] == Dot &&
|
||||
(source.Length == i + 2 || source[i + 2] == NullTerminator))
|
||||
{
|
||||
dest[i - 1] = DirectorySeparator;
|
||||
dest[i + 0] = Dot;
|
||||
dest[i + 1] = Dot;
|
||||
i += 2;
|
||||
break;
|
||||
}
|
||||
|
||||
dest[i] = source[i];
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
dest[i] = NullTerminator;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,58 +1,13 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using LibHac.Common;
|
||||
using LibHac.Diag;
|
||||
using LibHac.FsSrv.Sf;
|
||||
using LibHac.Util;
|
||||
using static LibHac.Fs.StringTraits;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
internal struct PathUtilityGlobals
|
||||
{
|
||||
public PathVerifier PathVerifier;
|
||||
|
||||
public void Initialize(FileSystemClient _)
|
||||
{
|
||||
PathVerifier.Initialize();
|
||||
}
|
||||
}
|
||||
|
||||
internal struct PathVerifier
|
||||
{
|
||||
public void Initialize()
|
||||
{
|
||||
// Todo
|
||||
}
|
||||
|
||||
public static Result Verify(U8Span path, int maxPathLength, int maxNameLength)
|
||||
{
|
||||
Debug.Assert(!path.IsNull());
|
||||
|
||||
int nameLength = 0;
|
||||
|
||||
for (int i = 0; i < path.Length && i <= maxPathLength && nameLength <= maxNameLength; i++)
|
||||
{
|
||||
byte c = path[i];
|
||||
|
||||
if (c == 0)
|
||||
return Result.Success;
|
||||
|
||||
// todo: Compare path based on their Unicode code points
|
||||
|
||||
if (c == ':' || c == '*' || c == '?' || c == '<' || c == '>' || c == '|')
|
||||
return ResultFs.InvalidCharacter.Log();
|
||||
|
||||
nameLength++;
|
||||
if (c == '\\' || c == '/')
|
||||
{
|
||||
nameLength = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return ResultFs.TooLongPath.Log();
|
||||
}
|
||||
}
|
||||
|
||||
public static class PathUtility
|
||||
{
|
||||
public static void Replace(Span<byte> buffer, byte currentChar, byte newChar)
|
||||
@ -68,62 +23,223 @@ namespace LibHac.Fs
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs the extra functions that nn::fs::FspPathPrintf does on the string buffer.
|
||||
/// </summary>
|
||||
/// <param name="builder">The string builder to process.</param>
|
||||
/// <returns>The <see cref="Result"/> of the operation.</returns>
|
||||
public static Result ToSfPath(in this U8StringBuilder builder)
|
||||
public static bool IsCurrentDirectory(ReadOnlySpan<byte> path)
|
||||
{
|
||||
if (builder.Overflowed)
|
||||
if (path.Length < 1)
|
||||
return false;
|
||||
|
||||
return path[0] == Dot &&
|
||||
(path.Length < 2 || path[1] == NullTerminator || path[1] == DirectorySeparator);
|
||||
}
|
||||
|
||||
public static bool IsParentDirectory(ReadOnlySpan<byte> path)
|
||||
{
|
||||
if (path.Length < 2)
|
||||
return false;
|
||||
|
||||
return path[0] == Dot &&
|
||||
path[1] == Dot &&
|
||||
(path.Length < 3 || path[2] == NullTerminator || path[2] == DirectorySeparator);
|
||||
}
|
||||
|
||||
public static bool IsSeparator(byte c)
|
||||
{
|
||||
return c == DirectorySeparator;
|
||||
}
|
||||
|
||||
public static bool IsNul(byte c)
|
||||
{
|
||||
return c == NullTerminator;
|
||||
}
|
||||
|
||||
public static Result ConvertToFspPath(out FspPath fspPath, ReadOnlySpan<byte> path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out fspPath);
|
||||
|
||||
int length = StringUtils.Copy(SpanHelpers.AsByteSpan(ref fspPath), path, PathTool.EntryNameLengthMax + 1);
|
||||
|
||||
if (length >= PathTool.EntryNameLengthMax + 1)
|
||||
return ResultFs.TooLongPath.Log();
|
||||
|
||||
Replace(builder.Buffer.Slice(builder.Capacity),
|
||||
AltDirectorySeparator,
|
||||
DirectorySeparator);
|
||||
Result rc = PathFormatter.SkipMountName(out ReadOnlySpan<byte> pathWithoutMountName, out _,
|
||||
new U8Span(path));
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (!WindowsPath.IsWindowsPath(pathWithoutMountName, true))
|
||||
{
|
||||
Replace(SpanHelpers.AsByteSpan(ref fspPath).Slice(0, 0x300), AltDirectorySeparator, DirectorySeparator);
|
||||
}
|
||||
else if (fspPath.Str[0] == DirectorySeparator && fspPath.Str[1] == DirectorySeparator)
|
||||
{
|
||||
SpanHelpers.AsByteSpan(ref fspPath)[0] = AltDirectorySeparator;
|
||||
SpanHelpers.AsByteSpan(ref fspPath)[1] = AltDirectorySeparator;
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public static Result VerifyPath(this FileSystemClient fs, U8Span path, int maxPathLength, int maxNameLength)
|
||||
public static bool IsDirectoryPath(ReadOnlySpan<byte> path)
|
||||
{
|
||||
return PathVerifier.Verify(path, maxPathLength, maxNameLength);
|
||||
}
|
||||
|
||||
public static bool IsSubPath(U8Span lhs, U8Span rhs)
|
||||
{
|
||||
Assert.SdkRequires(!lhs.IsNull());
|
||||
Assert.SdkRequires(!rhs.IsNull());
|
||||
|
||||
bool isUncLhs = WindowsPath.IsUnc(lhs);
|
||||
bool isUncRhs = WindowsPath.IsUnc(rhs);
|
||||
|
||||
if (isUncLhs && !isUncRhs || !isUncLhs && isUncRhs)
|
||||
if (path.Length < 1 || path[0] == NullTerminator)
|
||||
return false;
|
||||
|
||||
if (lhs.GetOrNull(0) == DirectorySeparator && lhs.GetOrNull(1) == NullTerminator &&
|
||||
rhs.GetOrNull(0) == DirectorySeparator && rhs.GetOrNull(1) != NullTerminator)
|
||||
int length = StringUtils.GetLength(path);
|
||||
return path[length - 1] == DirectorySeparator || path[length - 1] == AltDirectorySeparator;
|
||||
}
|
||||
|
||||
public static bool IsDirectoryPath(in FspPath path)
|
||||
{
|
||||
return IsDirectoryPath(SpanHelpers.AsReadOnlyByteSpan(in path));
|
||||
}
|
||||
|
||||
public static Result CheckUtf8(ReadOnlySpan<byte> path)
|
||||
{
|
||||
Assert.SdkRequiresNotNull(path);
|
||||
|
||||
uint utf8Buffer = 0;
|
||||
Span<byte> utf8BufferSpan = SpanHelpers.AsByteSpan(ref utf8Buffer);
|
||||
|
||||
ReadOnlySpan<byte> currentChar = path;
|
||||
|
||||
while (currentChar.Length > 0 && currentChar[0] != NullTerminator)
|
||||
{
|
||||
utf8BufferSpan.Clear();
|
||||
|
||||
CharacterEncodingResult result =
|
||||
CharacterEncoding.PickOutCharacterFromUtf8String(utf8BufferSpan, ref currentChar);
|
||||
|
||||
if (result != CharacterEncodingResult.Success)
|
||||
return ResultFs.InvalidPathFormat.Log();
|
||||
|
||||
result = CharacterEncoding.ConvertCharacterUtf8ToUtf32(out _, utf8BufferSpan);
|
||||
|
||||
if (result != CharacterEncodingResult.Success)
|
||||
return ResultFs.InvalidPathFormat.Log();
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public static Result CheckInvalidCharacter(byte c)
|
||||
{
|
||||
/*
|
||||
The optimized code is equivalent to this:
|
||||
|
||||
ReadOnlySpan<byte> invalidChars = new[]
|
||||
{(byte) ':', (byte) '*', (byte) '?', (byte) '<', (byte) '>', (byte) '|'};
|
||||
|
||||
for (int i = 0; i < invalidChars.Length; i++)
|
||||
{
|
||||
if (c == invalidChars[i])
|
||||
return ResultFs.InvalidCharacter.Log();
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
*/
|
||||
|
||||
const ulong mask = (1ul << (byte)':') |
|
||||
(1ul << (byte)'*') |
|
||||
(1ul << (byte)'?') |
|
||||
(1ul << (byte)'<') |
|
||||
(1ul << (byte)'>');
|
||||
|
||||
if (c <= 0x3Fu && ((1ul << c) & mask) != 0 || c == (byte)'|')
|
||||
return ResultFs.InvalidCharacter.Log();
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public static Result CheckInvalidBackslash(out bool containsBackslash, ReadOnlySpan<byte> path, bool allowBackslash)
|
||||
{
|
||||
containsBackslash = false;
|
||||
|
||||
for (int i = 0; i < path.Length && path[i] != NullTerminator; i++)
|
||||
{
|
||||
if (path[i] == '\\')
|
||||
{
|
||||
containsBackslash = true;
|
||||
|
||||
if (!allowBackslash)
|
||||
return ResultFs.InvalidCharacter.Log();
|
||||
}
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public static Result CheckEntryNameBytes(ReadOnlySpan<byte> path, int maxEntryLength)
|
||||
{
|
||||
Assert.SdkRequiresNotNull(path);
|
||||
|
||||
int currentEntryLength = 0;
|
||||
|
||||
for (int i = 0; i < path.Length && path[i] != NullTerminator; i++)
|
||||
{
|
||||
currentEntryLength++;
|
||||
|
||||
if (path[i] == DirectorySeparator || path[i] == AltDirectorySeparator)
|
||||
currentEntryLength = 0;
|
||||
|
||||
// Note: The original does use >= instead of >
|
||||
if (currentEntryLength >= maxEntryLength)
|
||||
return ResultFs.TooLongPath.Log();
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public static bool IsSubPath(ReadOnlySpan<byte> lhs, ReadOnlySpan<byte> rhs)
|
||||
{
|
||||
Assert.SdkRequiresNotNull(lhs);
|
||||
Assert.SdkRequiresNotNull(rhs);
|
||||
|
||||
if (WindowsPath.IsUncPath(lhs) && !WindowsPath.IsUncPath(rhs))
|
||||
return false;
|
||||
|
||||
if (!WindowsPath.IsUncPath(lhs) && WindowsPath.IsUncPath(rhs))
|
||||
return false;
|
||||
|
||||
if (lhs.At(0) == DirectorySeparator && lhs.At(1) == NullTerminator &&
|
||||
rhs.At(0) == DirectorySeparator && rhs.At(1) != NullTerminator)
|
||||
return true;
|
||||
|
||||
if (rhs.GetOrNull(0) == DirectorySeparator && rhs.GetOrNull(1) == NullTerminator &&
|
||||
lhs.GetOrNull(0) == DirectorySeparator && lhs.GetOrNull(1) != NullTerminator)
|
||||
if (rhs.At(0) == DirectorySeparator && rhs.At(1) == NullTerminator &&
|
||||
lhs.At(0) == DirectorySeparator && lhs.At(1) != NullTerminator)
|
||||
return true;
|
||||
|
||||
for (int i = 0; ; i++)
|
||||
{
|
||||
if (lhs.GetOrNull(i) == NullTerminator)
|
||||
if (lhs.At(i) == NullTerminator)
|
||||
{
|
||||
return rhs.GetOrNull(i) == DirectorySeparator;
|
||||
return rhs.At(i) == DirectorySeparator;
|
||||
}
|
||||
else if (rhs.GetOrNull(i) == NullTerminator)
|
||||
else if (rhs.At(i) == NullTerminator)
|
||||
{
|
||||
return lhs.GetOrNull(i) == DirectorySeparator;
|
||||
return lhs.At(i) == DirectorySeparator;
|
||||
}
|
||||
else if (lhs.GetOrNull(i) != rhs.GetOrNull(i))
|
||||
else if (lhs.At(i) != rhs.At(i))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsPathAbsolute(ReadOnlySpan<byte> path)
|
||||
{
|
||||
if (WindowsPath.IsWindowsPath(path, false))
|
||||
return true;
|
||||
|
||||
return path.At(0) == DirectorySeparator;
|
||||
}
|
||||
|
||||
public static bool IsPathRelative(ReadOnlySpan<byte> path)
|
||||
{
|
||||
return path.At(0) != NullTerminator && !IsPathAbsolute(path);
|
||||
}
|
||||
|
||||
public static bool IsPathStartWithCurrentDirectory(ReadOnlySpan<byte> path)
|
||||
{
|
||||
return IsCurrentDirectory(path) || IsParentDirectory(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,245 +0,0 @@
|
||||
using System;
|
||||
using LibHac.Common;
|
||||
using LibHac.Diag;
|
||||
using LibHac.FsSrv.Sf;
|
||||
using LibHac.Util;
|
||||
using static LibHac.Fs.StringTraits;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public static class PathUtility12
|
||||
{
|
||||
public static void Replace(Span<byte> buffer, byte currentChar, byte newChar)
|
||||
{
|
||||
Assert.SdkRequiresNotNull(buffer);
|
||||
|
||||
for (int i = 0; i < buffer.Length; i++)
|
||||
{
|
||||
if (buffer[i] == currentChar)
|
||||
{
|
||||
buffer[i] = newChar;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsCurrentDirectory(ReadOnlySpan<byte> path)
|
||||
{
|
||||
if (path.Length < 1)
|
||||
return false;
|
||||
|
||||
return path[0] == Dot &&
|
||||
(path.Length < 2 || path[1] == NullTerminator || path[1] == DirectorySeparator);
|
||||
}
|
||||
|
||||
public static bool IsParentDirectory(ReadOnlySpan<byte> path)
|
||||
{
|
||||
if (path.Length < 2)
|
||||
return false;
|
||||
|
||||
return path[0] == Dot &&
|
||||
path[1] == Dot &&
|
||||
(path.Length < 3 || path[2] == NullTerminator || path[2] == DirectorySeparator);
|
||||
}
|
||||
|
||||
public static bool IsSeparator(byte c)
|
||||
{
|
||||
return c == DirectorySeparator;
|
||||
}
|
||||
|
||||
public static bool IsNul(byte c)
|
||||
{
|
||||
return c == NullTerminator;
|
||||
}
|
||||
|
||||
public static Result ConvertToFspPath(out FspPath fspPath, ReadOnlySpan<byte> path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out fspPath);
|
||||
|
||||
int length = StringUtils.Copy(SpanHelpers.AsByteSpan(ref fspPath), path, PathTool.EntryNameLengthMax + 1);
|
||||
|
||||
if (length >= PathTool.EntryNameLengthMax + 1)
|
||||
return ResultFs.TooLongPath.Log();
|
||||
|
||||
Result rc = PathFormatter.SkipMountName(out ReadOnlySpan<byte> pathWithoutMountName, out _,
|
||||
new U8Span(path));
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (!WindowsPath12.IsWindowsPath(pathWithoutMountName, true))
|
||||
{
|
||||
Replace(SpanHelpers.AsByteSpan(ref fspPath).Slice(0, 0x300), AltDirectorySeparator, DirectorySeparator);
|
||||
}
|
||||
else if (fspPath.Str[0] == DirectorySeparator && fspPath.Str[1] == DirectorySeparator)
|
||||
{
|
||||
SpanHelpers.AsByteSpan(ref fspPath)[0] = AltDirectorySeparator;
|
||||
SpanHelpers.AsByteSpan(ref fspPath)[1] = AltDirectorySeparator;
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public static bool IsDirectoryPath(ReadOnlySpan<byte> path)
|
||||
{
|
||||
if (path.Length < 1 || path[0] == NullTerminator)
|
||||
return false;
|
||||
|
||||
int length = StringUtils.GetLength(path);
|
||||
return path[length - 1] == DirectorySeparator || path[length - 1] == AltDirectorySeparator;
|
||||
}
|
||||
|
||||
public static bool IsDirectoryPath(in FspPath path)
|
||||
{
|
||||
return IsDirectoryPath(SpanHelpers.AsReadOnlyByteSpan(in path));
|
||||
}
|
||||
|
||||
public static Result CheckUtf8(ReadOnlySpan<byte> path)
|
||||
{
|
||||
Assert.SdkRequiresNotNull(path);
|
||||
|
||||
uint utf8Buffer = 0;
|
||||
Span<byte> utf8BufferSpan = SpanHelpers.AsByteSpan(ref utf8Buffer);
|
||||
|
||||
ReadOnlySpan<byte> currentChar = path;
|
||||
|
||||
while (currentChar.Length > 0 && currentChar[0] != NullTerminator)
|
||||
{
|
||||
utf8BufferSpan.Clear();
|
||||
|
||||
CharacterEncodingResult result =
|
||||
CharacterEncoding.PickOutCharacterFromUtf8String(utf8BufferSpan, ref currentChar);
|
||||
|
||||
if (result != CharacterEncodingResult.Success)
|
||||
return ResultFs.InvalidPathFormat.Log();
|
||||
|
||||
result = CharacterEncoding.ConvertCharacterUtf8ToUtf32(out _, utf8BufferSpan);
|
||||
|
||||
if (result != CharacterEncodingResult.Success)
|
||||
return ResultFs.InvalidPathFormat.Log();
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public static Result CheckInvalidCharacter(byte c)
|
||||
{
|
||||
/*
|
||||
The optimized code is equivalent to this:
|
||||
|
||||
ReadOnlySpan<byte> invalidChars = new[]
|
||||
{(byte) ':', (byte) '*', (byte) '?', (byte) '<', (byte) '>', (byte) '|'};
|
||||
|
||||
for (int i = 0; i < invalidChars.Length; i++)
|
||||
{
|
||||
if (c == invalidChars[i])
|
||||
return ResultFs.InvalidCharacter.Log();
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
*/
|
||||
|
||||
const ulong mask = (1ul << (byte)':') |
|
||||
(1ul << (byte)'*') |
|
||||
(1ul << (byte)'?') |
|
||||
(1ul << (byte)'<') |
|
||||
(1ul << (byte)'>');
|
||||
|
||||
if (c <= 0x3Fu && ((1ul << c) & mask) != 0 || c == (byte)'|')
|
||||
return ResultFs.InvalidCharacter.Log();
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public static Result CheckInvalidBackslash(out bool containsBackslash, ReadOnlySpan<byte> path, bool allowBackslash)
|
||||
{
|
||||
containsBackslash = false;
|
||||
|
||||
for (int i = 0; i < path.Length && path[i] != NullTerminator; i++)
|
||||
{
|
||||
if (path[i] == '\\')
|
||||
{
|
||||
containsBackslash = true;
|
||||
|
||||
if (!allowBackslash)
|
||||
return ResultFs.InvalidCharacter.Log();
|
||||
}
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public static Result CheckEntryNameBytes(ReadOnlySpan<byte> path, int maxEntryLength)
|
||||
{
|
||||
Assert.SdkRequiresNotNull(path);
|
||||
|
||||
int currentEntryLength = 0;
|
||||
|
||||
for (int i = 0; i < path.Length && path[i] != NullTerminator; i++)
|
||||
{
|
||||
currentEntryLength++;
|
||||
|
||||
if (path[i] == DirectorySeparator || path[i] == AltDirectorySeparator)
|
||||
currentEntryLength = 0;
|
||||
|
||||
// Note: The original does use >= instead of >
|
||||
if (currentEntryLength >= maxEntryLength)
|
||||
return ResultFs.TooLongPath.Log();
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public static bool IsSubPath(ReadOnlySpan<byte> lhs, ReadOnlySpan<byte> rhs)
|
||||
{
|
||||
Assert.SdkRequiresNotNull(lhs);
|
||||
Assert.SdkRequiresNotNull(rhs);
|
||||
|
||||
if (WindowsPath12.IsUncPath(lhs) && !WindowsPath12.IsUncPath(rhs))
|
||||
return false;
|
||||
|
||||
if (!WindowsPath12.IsUncPath(lhs) && WindowsPath12.IsUncPath(rhs))
|
||||
return false;
|
||||
|
||||
if (lhs.At(0) == DirectorySeparator && lhs.At(1) == NullTerminator &&
|
||||
rhs.At(0) == DirectorySeparator && rhs.At(1) != NullTerminator)
|
||||
return true;
|
||||
|
||||
if (rhs.At(0) == DirectorySeparator && rhs.At(1) == NullTerminator &&
|
||||
lhs.At(0) == DirectorySeparator && lhs.At(1) != NullTerminator)
|
||||
return true;
|
||||
|
||||
for (int i = 0; ; i++)
|
||||
{
|
||||
if (lhs.At(i) == NullTerminator)
|
||||
{
|
||||
return rhs.At(i) == DirectorySeparator;
|
||||
}
|
||||
else if (rhs.At(i) == NullTerminator)
|
||||
{
|
||||
return lhs.At(i) == DirectorySeparator;
|
||||
}
|
||||
else if (lhs.At(i) != rhs.At(i))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsPathAbsolute(ReadOnlySpan<byte> path)
|
||||
{
|
||||
if (WindowsPath12.IsWindowsPath(path, false))
|
||||
return true;
|
||||
|
||||
return path.At(0) == DirectorySeparator;
|
||||
}
|
||||
|
||||
public static bool IsPathRelative(ReadOnlySpan<byte> path)
|
||||
{
|
||||
return path.At(0) != NullTerminator && !IsPathAbsolute(path);
|
||||
}
|
||||
|
||||
public static bool IsPathStartWithCurrentDirectory(ReadOnlySpan<byte> path)
|
||||
{
|
||||
return IsCurrentDirectory(path) || IsParentDirectory(path);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,82 +1,157 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using LibHac.Common;
|
||||
using LibHac.Diag;
|
||||
using static LibHac.Fs.StringTraits;
|
||||
using LibHac.Util;
|
||||
using static LibHac.Util.CharacterEncoding;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains functions for working with Windows paths.
|
||||
/// </summary>
|
||||
/// <remarks>Based on FS 12.0.3 (nnSdk 12.3.1)</remarks>
|
||||
public static class WindowsPath
|
||||
{
|
||||
private const int WindowsDriveLength = 2;
|
||||
private const int UncPathPrefixLength = 2;
|
||||
private const int DosDevicePathPrefixLength = 4;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsWindowsDrive(U8Span path)
|
||||
public static int GetCodePointByteLength(byte firstCodeUnit)
|
||||
{
|
||||
Assert.SdkRequires(!path.IsNull());
|
||||
|
||||
return (uint)path.Length > 1 &&
|
||||
IsWindowsDriveCharacter(path[0]) &&
|
||||
path[1] == DriveSeparator;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsWindowsDriveCharacter(byte c)
|
||||
{
|
||||
// Mask lowercase letters to uppercase and check if it's in range
|
||||
return (0b1101_1111 & c) - 'A' <= 'Z' - 'A';
|
||||
// return 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z';
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsUnc(U8Span path)
|
||||
{
|
||||
return (uint)path.Length >= UncPathPrefixLength &&
|
||||
(path.GetUnsafe(0) == DirectorySeparator && path.GetUnsafe(1) == DirectorySeparator ||
|
||||
path.GetUnsafe(0) == AltDirectorySeparator && path.GetUnsafe(1) == AltDirectorySeparator);
|
||||
}
|
||||
|
||||
public static bool IsUnc(string path)
|
||||
{
|
||||
return (uint)path.Length >= UncPathPrefixLength &&
|
||||
(path[0] == DirectorySeparator && path[1] == DirectorySeparator ||
|
||||
path[0] == AltDirectorySeparator && path[1] == AltDirectorySeparator);
|
||||
}
|
||||
|
||||
public static int GetWindowsPathSkipLength(U8Span path)
|
||||
{
|
||||
if (IsWindowsDrive(path))
|
||||
return WindowsDriveLength;
|
||||
|
||||
if (!IsUnc(path))
|
||||
return 0;
|
||||
|
||||
for (int i = UncPathPrefixLength; i < path.Length && path[i] != NullTerminator; i++)
|
||||
{
|
||||
if (path[i] == (byte)'$' || path[i] == DriveSeparator)
|
||||
{
|
||||
return i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if ((firstCodeUnit & 0x80) == 0x00) return 1;
|
||||
if ((firstCodeUnit & 0xE0) == 0xC0) return 2;
|
||||
if ((firstCodeUnit & 0xF0) == 0xE0) return 3;
|
||||
if ((firstCodeUnit & 0xF8) == 0xF0) return 4;
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static bool IsDosDelimiter(char c)
|
||||
private static bool IsUncPathImpl(ReadOnlySpan<byte> path, bool checkForwardSlash, bool checkBackSlash)
|
||||
{
|
||||
return c == '/' || c == '\\';
|
||||
Assert.SdkRequiresNotNull(path);
|
||||
|
||||
if ((uint)path.Length < UncPathPrefixLength)
|
||||
return false;
|
||||
|
||||
if (checkForwardSlash && path[0] == '/' && path[1] == '/')
|
||||
return true;
|
||||
|
||||
return checkBackSlash && path[0] == '\\' && path[1] == '\\';
|
||||
}
|
||||
|
||||
public static bool IsDosDevicePath(ReadOnlySpan<char> path)
|
||||
private static bool IsUncPathImpl(ReadOnlySpan<char> path, bool checkForwardSlash, bool checkBackSlash)
|
||||
{
|
||||
return path.Length >= DosDevicePathPrefixLength
|
||||
&& IsDosDelimiter(path[0])
|
||||
&& path[1] == '\\'
|
||||
&& (path[2] == '.' || path[2] == '?')
|
||||
&& IsDosDelimiter(path[3]);
|
||||
Assert.SdkRequiresNotNull(path);
|
||||
|
||||
if ((uint)path.Length < UncPathPrefixLength)
|
||||
return false;
|
||||
|
||||
if (checkForwardSlash && path[0] == '/' && path[1] == '/')
|
||||
return true;
|
||||
|
||||
return checkBackSlash && path[0] == '\\' && path[1] == '\\';
|
||||
}
|
||||
|
||||
private static int GetUncPathPrefixLengthImpl(ReadOnlySpan<byte> path, bool checkForwardSlash)
|
||||
{
|
||||
Assert.SdkRequiresNotNull(path);
|
||||
|
||||
int length;
|
||||
int separatorCount = 0;
|
||||
|
||||
for (length = 0; length < path.Length && path[length] != 0; length++)
|
||||
{
|
||||
if (checkForwardSlash && path[length] == '/')
|
||||
++separatorCount;
|
||||
|
||||
if (path[length] == '\\')
|
||||
++separatorCount;
|
||||
|
||||
if (separatorCount == 4)
|
||||
return length;
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
private static int GetUncPathPrefixLengthImpl(ReadOnlySpan<char> path, bool checkForwardSlash)
|
||||
{
|
||||
Assert.SdkRequiresNotNull(path);
|
||||
|
||||
int length;
|
||||
int separatorCount = 0;
|
||||
|
||||
for (length = 0; length < path.Length && path[length] != 0; length++)
|
||||
{
|
||||
if (checkForwardSlash && path[length] == '/')
|
||||
++separatorCount;
|
||||
|
||||
if (path[length] == '\\')
|
||||
++separatorCount;
|
||||
|
||||
if (separatorCount == 4)
|
||||
return length;
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
private static bool IsDosDevicePathImpl(ReadOnlySpan<byte> path)
|
||||
{
|
||||
Assert.SdkRequiresNotNull(path);
|
||||
|
||||
if ((uint)path.Length < DosDevicePathPrefixLength)
|
||||
return false;
|
||||
|
||||
return path[0] == '\\' &&
|
||||
path[1] == '\\' &&
|
||||
(path[2] == '.' || path[2] == '?') &&
|
||||
(path[3] == '/' || path[3] == '\\');
|
||||
}
|
||||
|
||||
private static bool IsDosDevicePathImpl(ReadOnlySpan<char> path)
|
||||
{
|
||||
Assert.SdkRequiresNotNull(path);
|
||||
|
||||
if ((uint)path.Length < DosDevicePathPrefixLength)
|
||||
return false;
|
||||
|
||||
return path[0] == '\\' &&
|
||||
path[1] == '\\' &&
|
||||
(path[2] == '.' || path[2] == '?') &&
|
||||
(path[3] == '/' || path[3] == '\\');
|
||||
}
|
||||
|
||||
public static bool IsWindowsDrive(ReadOnlySpan<byte> path)
|
||||
{
|
||||
Assert.SdkRequiresNotNull(path);
|
||||
|
||||
if ((uint)path.Length < WindowsDriveLength)
|
||||
return false;
|
||||
|
||||
// Mask lowercase letters to uppercase and check if it's in range
|
||||
return ((0b1101_1111 & path[0]) - 'A' <= 'Z' - 'A') && path[1] == ':';
|
||||
// return ('a' <= c && c <= 'z' || 'A' <= c && c <= 'Z') && path[1] == ':';
|
||||
}
|
||||
|
||||
public static bool IsUncPath(ReadOnlySpan<byte> path)
|
||||
{
|
||||
return IsUncPathImpl(path, true, true);
|
||||
}
|
||||
|
||||
public static bool IsUncPath(ReadOnlySpan<byte> path, bool checkForwardSlash, bool checkBackSlash)
|
||||
{
|
||||
return IsUncPathImpl(path, checkForwardSlash, checkBackSlash);
|
||||
}
|
||||
|
||||
public static int GetUncPathPrefixLength(ReadOnlySpan<byte> path)
|
||||
{
|
||||
return GetUncPathPrefixLengthImpl(path, true);
|
||||
}
|
||||
|
||||
public static bool IsDosDevicePath(ReadOnlySpan<byte> path)
|
||||
{
|
||||
return IsDosDevicePathImpl(path);
|
||||
}
|
||||
|
||||
public static int GetDosDevicePathPrefixLength()
|
||||
@ -84,37 +159,100 @@ namespace LibHac.Fs
|
||||
return DosDevicePathPrefixLength;
|
||||
}
|
||||
|
||||
public static bool IsDriveName(ReadOnlySpan<char> path)
|
||||
public static bool IsWindowsPath(ReadOnlySpan<byte> path, bool checkForwardSlash)
|
||||
{
|
||||
return path.Length == WindowsDriveLength && path[1] == ':';
|
||||
return IsWindowsDrive(path) || IsDosDevicePath(path) || IsUncPath(path, checkForwardSlash, true);
|
||||
}
|
||||
|
||||
public static bool IsUncPath(ReadOnlySpan<char> path)
|
||||
public static int GetWindowsSkipLength(ReadOnlySpan<byte> path)
|
||||
{
|
||||
return !IsDosDevicePath(path) && path.Length >= UncPathPrefixLength && IsDosDelimiter(path[0]) &&
|
||||
IsDosDelimiter(path[1]);
|
||||
if (IsDosDevicePath(path))
|
||||
return GetDosDevicePathPrefixLength();
|
||||
|
||||
if (IsWindowsDrive(path))
|
||||
return WindowsDriveLength;
|
||||
|
||||
if (IsUncPath(path))
|
||||
return GetUncPathPrefixLength(path);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static int GetUncPathPrefixLength(ReadOnlySpan<char> path)
|
||||
public static bool IsDosDelimiterW(char c)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < path.Length; i++)
|
||||
return c == '/' || c == '\\';
|
||||
}
|
||||
|
||||
public static bool IsWindowsDriveW(ReadOnlySpan<char> path)
|
||||
{
|
||||
Assert.SdkRequiresNotNull(path);
|
||||
|
||||
if ((uint)path.Length < WindowsDriveLength)
|
||||
return false;
|
||||
|
||||
// Mask lowercase letters to uppercase and check if it's in range
|
||||
return ((0b1101_1111 & path[0]) - 'A' <= 'Z' - 'A') && path[1] == ':';
|
||||
// return ('a' <= c && c <= 'z' || 'A' <= c && c <= 'Z') && path[1] == ':';
|
||||
}
|
||||
|
||||
public static bool IsUncPathW(ReadOnlySpan<char> path)
|
||||
{
|
||||
return IsUncPathImpl(path, true, true);
|
||||
}
|
||||
|
||||
public static int GetUncPathPrefixLengthW(ReadOnlySpan<char> path)
|
||||
{
|
||||
return GetUncPathPrefixLengthImpl(path, true);
|
||||
}
|
||||
|
||||
public static bool IsDosDevicePathW(ReadOnlySpan<char> path)
|
||||
{
|
||||
return IsDosDevicePathImpl(path);
|
||||
}
|
||||
|
||||
public static bool IsWindowsPathW(ReadOnlySpan<char> path)
|
||||
{
|
||||
return IsWindowsDriveW(path) || IsUncPathW(path) || IsDosDevicePathW(path);
|
||||
}
|
||||
|
||||
public static Result CheckCharacterCountForWindows(ReadOnlySpan<byte> path, int maxNameLength, int maxPathLength)
|
||||
{
|
||||
Assert.SdkRequiresNotNull(path);
|
||||
|
||||
ReadOnlySpan<byte> currentChar = path;
|
||||
int currentNameLength = 0;
|
||||
int currentPathLength = 0;
|
||||
|
||||
while (currentChar.Length > 1 && currentChar[0] != 0)
|
||||
{
|
||||
if (path[i] == '\0')
|
||||
break;
|
||||
int utf16CodeUnitCount = GetCodePointByteLength(currentChar[0]) < 4 ? 1 : 2;
|
||||
|
||||
if (IsDosDelimiter(path[i]) && i + 1 == DosDevicePathPrefixLength)
|
||||
break;
|
||||
int utf8Buffer = 0;
|
||||
CharacterEncodingResult result =
|
||||
PickOutCharacterFromUtf8String(SpanHelpers.AsByteSpan(ref utf8Buffer), ref currentChar);
|
||||
|
||||
if (result != CharacterEncodingResult.Success)
|
||||
return ResultFs.InvalidPathFormat.Log();
|
||||
|
||||
result = ConvertCharacterUtf8ToUtf32(out uint pathChar, SpanHelpers.AsReadOnlyByteSpan(in utf8Buffer));
|
||||
|
||||
if (result != CharacterEncodingResult.Success)
|
||||
return ResultFs.InvalidPathFormat.Log();
|
||||
|
||||
currentNameLength += utf16CodeUnitCount;
|
||||
currentPathLength += utf16CodeUnitCount;
|
||||
|
||||
if (pathChar == '/' || pathChar == '\\')
|
||||
currentNameLength = 0;
|
||||
|
||||
if (maxNameLength > 0 && currentNameLength > maxNameLength)
|
||||
return ResultFs.TooLongPath.Log();
|
||||
|
||||
if (maxPathLength > 0 && currentPathLength > maxPathLength)
|
||||
return ResultFs.TooLongPath.Log();
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
public static bool IsPathRooted(ReadOnlySpan<char> path)
|
||||
{
|
||||
return IsDriveName(path.Slice(0, Math.Min(WindowsDriveLength, path.Length)))
|
||||
|| IsDosDevicePath(path.Slice(0, Math.Min(DosDevicePathPrefixLength, path.Length)))
|
||||
|| IsUncPath(path.Slice(0, Math.Min(DosDevicePathPrefixLength, path.Length)));
|
||||
return Result.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,250 +0,0 @@
|
||||
using System;
|
||||
using LibHac.Common;
|
||||
using LibHac.Diag;
|
||||
using LibHac.Util;
|
||||
using static LibHac.Util.CharacterEncoding;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public static class WindowsPath12
|
||||
{
|
||||
public static int GetCodePointByteLength(byte firstCodeUnit)
|
||||
{
|
||||
if ((firstCodeUnit & 0x80) == 0x00) return 1;
|
||||
if ((firstCodeUnit & 0xE0) == 0xC0) return 2;
|
||||
if ((firstCodeUnit & 0xF0) == 0xE0) return 3;
|
||||
if ((firstCodeUnit & 0xF8) == 0xF0) return 4;
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static bool IsUncPathImpl(ReadOnlySpan<byte> path, bool checkForwardSlash, bool checkBackSlash)
|
||||
{
|
||||
Assert.SdkRequiresNotNull(path);
|
||||
|
||||
if ((uint)path.Length < 2)
|
||||
return false;
|
||||
|
||||
if (checkForwardSlash && path[0] == '/' && path[1] == '/')
|
||||
return true;
|
||||
|
||||
return checkBackSlash && path[0] == '\\' && path[1] == '\\';
|
||||
}
|
||||
|
||||
private static bool IsUncPathImpl(ReadOnlySpan<char> path, bool checkForwardSlash, bool checkBackSlash)
|
||||
{
|
||||
Assert.SdkRequiresNotNull(path);
|
||||
|
||||
if ((uint)path.Length < 2)
|
||||
return false;
|
||||
|
||||
if (checkForwardSlash && path[0] == '/' && path[1] == '/')
|
||||
return true;
|
||||
|
||||
return checkBackSlash && path[0] == '\\' && path[1] == '\\';
|
||||
}
|
||||
|
||||
private static int GetUncPathPrefixLengthImpl(ReadOnlySpan<byte> path, bool checkForwardSlash)
|
||||
{
|
||||
Assert.SdkRequiresNotNull(path);
|
||||
|
||||
int length;
|
||||
int separatorCount = 0;
|
||||
|
||||
for (length = 0; length < path.Length && path[length] != 0; length++)
|
||||
{
|
||||
if (checkForwardSlash && path[length] == '/')
|
||||
++separatorCount;
|
||||
|
||||
if (path[length] == '\\')
|
||||
++separatorCount;
|
||||
|
||||
if (separatorCount == 4)
|
||||
return length;
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
private static int GetUncPathPrefixLengthImpl(ReadOnlySpan<char> path, bool checkForwardSlash)
|
||||
{
|
||||
Assert.SdkRequiresNotNull(path);
|
||||
|
||||
int length;
|
||||
int separatorCount = 0;
|
||||
|
||||
for (length = 0; length < path.Length && path[length] != 0; length++)
|
||||
{
|
||||
if (checkForwardSlash && path[length] == '/')
|
||||
++separatorCount;
|
||||
|
||||
if (path[length] == '\\')
|
||||
++separatorCount;
|
||||
|
||||
if (separatorCount == 4)
|
||||
return length;
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
private static bool IsDosDevicePathImpl(ReadOnlySpan<byte> path)
|
||||
{
|
||||
Assert.SdkRequiresNotNull(path);
|
||||
|
||||
if ((uint)path.Length < 4)
|
||||
return false;
|
||||
|
||||
return path[0] == '\\' &&
|
||||
path[1] == '\\' &&
|
||||
(path[2] == '.' || path[2] == '?') &&
|
||||
(path[3] == '/' || path[3] == '\\');
|
||||
}
|
||||
|
||||
private static bool IsDosDevicePathImpl(ReadOnlySpan<char> path)
|
||||
{
|
||||
Assert.SdkRequiresNotNull(path);
|
||||
|
||||
if ((uint)path.Length < 4)
|
||||
return false;
|
||||
|
||||
return path[0] == '\\' &&
|
||||
path[1] == '\\' &&
|
||||
(path[2] == '.' || path[2] == '?') &&
|
||||
(path[3] == '/' || path[3] == '\\');
|
||||
}
|
||||
|
||||
public static bool IsWindowsDrive(ReadOnlySpan<byte> path)
|
||||
{
|
||||
Assert.SdkRequiresNotNull(path);
|
||||
|
||||
if ((uint)path.Length < 2)
|
||||
return false;
|
||||
|
||||
// Mask lowercase letters to uppercase and check if it's in range
|
||||
return ((0b1101_1111 & path[0]) - 'A' <= 'Z' - 'A') && path[1] == ':';
|
||||
// return ('a' <= c && c <= 'z' || 'A' <= c && c <= 'Z') && path[1] == ':';
|
||||
}
|
||||
|
||||
public static bool IsUncPath(ReadOnlySpan<byte> path)
|
||||
{
|
||||
return IsUncPathImpl(path, true, true);
|
||||
}
|
||||
|
||||
public static bool IsUncPath(ReadOnlySpan<byte> path, bool checkForwardSlash, bool checkBackSlash)
|
||||
{
|
||||
return IsUncPathImpl(path, checkForwardSlash, checkBackSlash);
|
||||
}
|
||||
|
||||
public static int GetUncPathPrefixLength(ReadOnlySpan<byte> path)
|
||||
{
|
||||
return GetUncPathPrefixLengthImpl(path, true);
|
||||
}
|
||||
|
||||
public static bool IsDosDevicePath(ReadOnlySpan<byte> path)
|
||||
{
|
||||
return IsDosDevicePathImpl(path);
|
||||
}
|
||||
|
||||
public static int GetDosDevicePathPrefixLength()
|
||||
{
|
||||
return 4;
|
||||
}
|
||||
|
||||
public static bool IsWindowsPath(ReadOnlySpan<byte> path, bool checkForwardSlash)
|
||||
{
|
||||
return IsWindowsDrive(path) || IsDosDevicePath(path) || IsUncPath(path, checkForwardSlash, true);
|
||||
}
|
||||
|
||||
public static int GetWindowsSkipLength(ReadOnlySpan<byte> path)
|
||||
{
|
||||
if (IsDosDevicePath(path))
|
||||
return GetDosDevicePathPrefixLength();
|
||||
|
||||
if (IsWindowsDrive(path))
|
||||
return 2;
|
||||
|
||||
if (IsUncPath(path))
|
||||
return GetUncPathPrefixLength(path);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static bool IsDosDelimiterW(char c)
|
||||
{
|
||||
return c == '/' || c == '\\';
|
||||
}
|
||||
|
||||
public static bool IsWindowsDriveW(ReadOnlySpan<char> path)
|
||||
{
|
||||
Assert.SdkRequiresNotNull(path);
|
||||
|
||||
if ((uint)path.Length < 2)
|
||||
return false;
|
||||
|
||||
// Mask lowercase letters to uppercase and check if it's in range
|
||||
return ((0b1101_1111 & path[0]) - 'A' <= 'Z' - 'A') && path[1] == ':';
|
||||
// return ('a' <= c && c <= 'z' || 'A' <= c && c <= 'Z') && path[1] == ':';
|
||||
}
|
||||
|
||||
public static bool IsUncPathW(ReadOnlySpan<char> path)
|
||||
{
|
||||
return IsUncPathImpl(path, true, true);
|
||||
}
|
||||
|
||||
public static int GetUncPathPrefixLengthW(ReadOnlySpan<char> path)
|
||||
{
|
||||
return GetUncPathPrefixLengthImpl(path, true);
|
||||
}
|
||||
|
||||
public static bool IsDosDevicePathW(ReadOnlySpan<char> path)
|
||||
{
|
||||
return IsDosDevicePathImpl(path);
|
||||
}
|
||||
|
||||
public static bool IsWindowsPathW(ReadOnlySpan<char> path)
|
||||
{
|
||||
return IsWindowsDriveW(path) || IsUncPathW(path) || IsDosDevicePathW(path);
|
||||
}
|
||||
|
||||
public static Result CheckCharacterCountForWindows(ReadOnlySpan<byte> path, int maxNameLength, int maxPathLength)
|
||||
{
|
||||
Assert.SdkRequiresNotNull(path);
|
||||
|
||||
ReadOnlySpan<byte> currentChar = path;
|
||||
int currentNameLength = 0;
|
||||
int currentPathLength = 0;
|
||||
|
||||
while (currentChar.Length > 1 && currentChar[0] != 0)
|
||||
{
|
||||
int utf16CodeUnitCount = GetCodePointByteLength(currentChar[0]) < 4 ? 1 : 2;
|
||||
|
||||
int utf8Buffer = 0;
|
||||
CharacterEncodingResult result =
|
||||
PickOutCharacterFromUtf8String(SpanHelpers.AsByteSpan(ref utf8Buffer), ref currentChar);
|
||||
|
||||
if (result != CharacterEncodingResult.Success)
|
||||
return ResultFs.InvalidPathFormat.Log();
|
||||
|
||||
result = ConvertCharacterUtf8ToUtf32(out uint pathChar, SpanHelpers.AsReadOnlyByteSpan(in utf8Buffer));
|
||||
|
||||
if (result != CharacterEncodingResult.Success)
|
||||
return ResultFs.InvalidPathFormat.Log();
|
||||
|
||||
currentNameLength += utf16CodeUnitCount;
|
||||
currentPathLength += utf16CodeUnitCount;
|
||||
|
||||
if (pathChar == '/' || pathChar == '\\')
|
||||
currentNameLength = 0;
|
||||
|
||||
if (maxNameLength > 0 && currentNameLength > maxNameLength)
|
||||
return ResultFs.TooLongPath.Log();
|
||||
|
||||
if (maxPathLength > 0 && currentPathLength > maxPathLength)
|
||||
return ResultFs.TooLongPath.Log();
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
}
|
||||
}
|
@ -18,7 +18,7 @@ namespace LibHac.Fs
|
||||
{
|
||||
if (IsValid)
|
||||
{
|
||||
Directory.GetParent().FsClient.CloseDirectory(this);
|
||||
Directory.GetParent().Hos.Fs.CloseDirectory(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ namespace LibHac.Fs
|
||||
{
|
||||
if (IsValid)
|
||||
{
|
||||
File.FsClient.CloseFile(this);
|
||||
File.Hos.Fs.CloseFile(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,6 @@ namespace LibHac.Fs
|
||||
public FileSystemProxyServiceObjectGlobals FileSystemProxyServiceObject;
|
||||
public FsContextHandlerGlobals FsContextHandler;
|
||||
public ResultHandlingUtilityGlobals ResultHandlingUtility;
|
||||
public PathUtilityGlobals PathUtility;
|
||||
public DirectorySaveDataFileSystemGlobals DirectorySaveDataFileSystem;
|
||||
|
||||
public void Initialize(FileSystemClient fsClient, HorizonClient horizonClient)
|
||||
@ -36,7 +35,6 @@ namespace LibHac.Fs
|
||||
AccessLog.Initialize(fsClient);
|
||||
UserMountTable.Initialize(fsClient);
|
||||
FsContextHandler.Initialize(fsClient);
|
||||
PathUtility.Initialize(fsClient);
|
||||
DirectorySaveDataFileSystem.Initialize(fsClient);
|
||||
}
|
||||
}
|
||||
|
@ -143,10 +143,14 @@ namespace LibHac.Fs
|
||||
|
||||
public enum OperationId
|
||||
{
|
||||
Clear = 0,
|
||||
ClearSignature = 1,
|
||||
FillZero = 0,
|
||||
DestroySignature = 1,
|
||||
InvalidateCache = 2,
|
||||
QueryRange = 3
|
||||
QueryRange = 3,
|
||||
QueryUnpreparedRange = 4,
|
||||
QueryLazyLoadCompletionRate = 5,
|
||||
SetLazyLoadPriority = 6,
|
||||
ReadyLazyLoadFile = 10001
|
||||
}
|
||||
|
||||
public enum SaveDataType : byte
|
||||
|
@ -18,8 +18,8 @@ namespace LibHac.Fs.Impl
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_directory?.Dispose();
|
||||
_directory = null;
|
||||
IDirectory directory = Shared.Move(ref _directory);
|
||||
directory?.Dispose();
|
||||
|
||||
_parentFileSystem.NotifyCloseDirectory(this);
|
||||
}
|
||||
|
@ -17,6 +17,8 @@ namespace LibHac.Fs.Impl
|
||||
|
||||
internal class FileAccessor : IDisposable
|
||||
{
|
||||
private const string NeedFlushMessage = "Error: nn::fs::CloseFile() failed because the file was not flushed.\n";
|
||||
|
||||
private IFile _file;
|
||||
private FileSystemAccessor _parentFileSystem;
|
||||
private WriteState _writeState;
|
||||
@ -26,15 +28,17 @@ namespace LibHac.Fs.Impl
|
||||
// ReSharper disable once NotAccessedField.Local
|
||||
private int _pathHashIndex;
|
||||
|
||||
internal FileSystemClient FsClient { get; }
|
||||
internal HorizonClient Hos { get; }
|
||||
|
||||
public FileAccessor(FileSystemClient fsClient, ref IFile file, FileSystemAccessor parentFileSystem,
|
||||
public FileAccessor(HorizonClient hosClient, ref IFile file, FileSystemAccessor parentFileSystem,
|
||||
OpenMode mode)
|
||||
{
|
||||
FsClient = fsClient;
|
||||
Hos = hosClient;
|
||||
|
||||
_file = Shared.Move(ref file);
|
||||
_parentFileSystem = parentFileSystem;
|
||||
_writeState = WriteState.None;
|
||||
_lastResult = Result.Success;
|
||||
_openMode = mode;
|
||||
}
|
||||
|
||||
@ -42,13 +46,14 @@ namespace LibHac.Fs.Impl
|
||||
{
|
||||
if (_lastResult.IsSuccess() && _writeState == WriteState.NeedsFlush)
|
||||
{
|
||||
Abort.DoAbort(ResultFs.NeedFlush.Log(), "File needs flush before closing.");
|
||||
Hos.Fs.Impl.LogErrorMessage(ResultFs.NeedFlush.Value, NeedFlushMessage);
|
||||
Abort.DoAbort(ResultFs.NeedFlush.Value);
|
||||
}
|
||||
|
||||
_parentFileSystem?.NotifyCloseFile(this);
|
||||
_file?.Dispose();
|
||||
|
||||
_file = null;
|
||||
IFile file = Shared.Move(ref _file);
|
||||
file?.Dispose();
|
||||
}
|
||||
|
||||
public OpenMode GetOpenMode() => _openMode;
|
||||
@ -91,18 +96,18 @@ namespace LibHac.Fs.Impl
|
||||
|
||||
if (_lastResult.IsFailure())
|
||||
{
|
||||
if (FsClient.Impl.IsEnabledAccessLog() && FsClient.Impl.IsEnabledHandleAccessLog(handle))
|
||||
if (Hos.Fs.Impl.IsEnabledAccessLog() && Hos.Fs.Impl.IsEnabledHandleAccessLog(handle))
|
||||
{
|
||||
Tick start = FsClient.Hos.Os.GetSystemTick();
|
||||
Tick start = Hos.Os.GetSystemTick();
|
||||
rc = _lastResult;
|
||||
Tick end = FsClient.Hos.Os.GetSystemTick();
|
||||
Tick end = Hos.Os.GetSystemTick();
|
||||
|
||||
var sb = new U8StringBuilder(logBuffer, true);
|
||||
sb.Append(LogOffset).AppendFormat(offset)
|
||||
.Append(LogSize).AppendFormat(destination.Length)
|
||||
.Append(LogReadSize).AppendFormat(AccessLogImpl.DereferenceOutValue(in bytesRead, rc));
|
||||
|
||||
FsClient.Impl.OutputAccessLog(rc, start, end, handle, new U8Span(logBuffer),
|
||||
Hos.Fs.Impl.OutputAccessLog(rc, start, end, handle, new U8Span(logBuffer),
|
||||
nameof(UserFile.ReadFile));
|
||||
}
|
||||
|
||||
@ -123,18 +128,18 @@ namespace LibHac.Fs.Impl
|
||||
}
|
||||
else
|
||||
{
|
||||
if (FsClient.Impl.IsEnabledAccessLog() && FsClient.Impl.IsEnabledHandleAccessLog(handle))
|
||||
if (Hos.Fs.Impl.IsEnabledAccessLog() && Hos.Fs.Impl.IsEnabledHandleAccessLog(handle))
|
||||
{
|
||||
Tick start = FsClient.Hos.Os.GetSystemTick();
|
||||
Tick start = Hos.Os.GetSystemTick();
|
||||
rc = ReadWithoutCacheAccessLog(out bytesRead, offset, destination, in option);
|
||||
Tick end = FsClient.Hos.Os.GetSystemTick();
|
||||
Tick end = Hos.Os.GetSystemTick();
|
||||
|
||||
var sb = new U8StringBuilder(logBuffer, true);
|
||||
sb.Append(LogOffset).AppendFormat(offset)
|
||||
.Append(LogSize).AppendFormat(destination.Length)
|
||||
.Append(LogReadSize).AppendFormat(AccessLogImpl.DereferenceOutValue(in bytesRead, rc));
|
||||
|
||||
FsClient.Impl.OutputAccessLog(rc, start, end, handle, new U8Span(logBuffer),
|
||||
Hos.Fs.Impl.OutputAccessLog(rc, start, end, handle, new U8Span(logBuffer),
|
||||
nameof(UserFile.ReadFile));
|
||||
}
|
||||
else
|
||||
|
@ -12,6 +12,12 @@ namespace LibHac.Fs.Impl
|
||||
{
|
||||
internal class FileSystemAccessor : IDisposable
|
||||
{
|
||||
private const string EmptyMountNameMessage = "Error: Mount failed because the mount name was empty.\n";
|
||||
private const string TooLongMountNameMessage = "Error: Mount failed because the mount name was too long. The mount name was \"{0}\".\n";
|
||||
private const string FileNotClosedMessage = "Error: Unmount failed because not all files were closed.\n";
|
||||
private const string DirectoryNotClosedMessage = "Error: Unmount failed because not all directories were closed.\n";
|
||||
private const string InvalidFsEntryObjectMessage = "Invalid file or directory object.";
|
||||
|
||||
private MountName _mountName;
|
||||
private IFileSystem _fileSystem;
|
||||
private LinkedList<FileAccessor> _openFiles;
|
||||
@ -24,14 +30,16 @@ namespace LibHac.Fs.Impl
|
||||
private bool _isPathCacheAttachable;
|
||||
private bool _isPathCacheAttached;
|
||||
private IMultiCommitTarget _multiCommitTarget;
|
||||
private PathFlags _pathFlags;
|
||||
private Optional<Ncm.DataId> _dataId;
|
||||
|
||||
internal FileSystemClient FsClient { get; }
|
||||
internal HorizonClient Hos { get; }
|
||||
|
||||
public FileSystemAccessor(FileSystemClient fsClient, U8Span name, IMultiCommitTarget multiCommitTarget,
|
||||
public FileSystemAccessor(HorizonClient hosClient, U8Span name, IMultiCommitTarget multiCommitTarget,
|
||||
IFileSystem fileSystem, ICommonMountNameGenerator mountNameGenerator,
|
||||
ISaveDataAttributeGetter saveAttributeGetter)
|
||||
{
|
||||
FsClient = fsClient;
|
||||
Hos = hosClient;
|
||||
|
||||
_fileSystem = fileSystem;
|
||||
_openFiles = new LinkedList<FileAccessor>();
|
||||
@ -42,32 +50,43 @@ namespace LibHac.Fs.Impl
|
||||
_multiCommitTarget = multiCommitTarget;
|
||||
|
||||
if (name.IsEmpty())
|
||||
Abort.DoAbort(ResultFs.InvalidMountName.Log());
|
||||
{
|
||||
Hos.Fs.Impl.LogErrorMessage(ResultFs.InvalidMountName.Value, EmptyMountNameMessage);
|
||||
Abort.DoAbort(ResultFs.InvalidMountName.Value);
|
||||
}
|
||||
|
||||
if (StringUtils.GetLength(name, PathTool.MountNameLengthMax + 1) > PathTool.MountNameLengthMax)
|
||||
Abort.DoAbort(ResultFs.InvalidMountName.Log());
|
||||
int mountLength = StringUtils.Copy(_mountName.Name, name, PathTool.MountNameLengthMax + 1);
|
||||
|
||||
StringUtils.Copy(_mountName.Name.Slice(0, PathTool.MountNameLengthMax), name);
|
||||
_mountName.Name[PathTool.MountNameLengthMax] = 0;
|
||||
if (mountLength > PathTool.MountNameLengthMax)
|
||||
{
|
||||
Hos.Fs.Impl.LogErrorMessage(ResultFs.InvalidMountName.Value, TooLongMountNameMessage,
|
||||
name.ToString());
|
||||
Abort.DoAbort(ResultFs.InvalidMountName.Value);
|
||||
}
|
||||
|
||||
if (StringUtils.Compare(_mountName.Name, CommonMountNames.HostRootFileSystemMountName) == 0)
|
||||
{
|
||||
_pathFlags.AllowWindowsPath();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposing) return;
|
||||
|
||||
using (ScopedLock.Lock(ref _openListLock))
|
||||
{
|
||||
Abort.DoAbortUnless(_openFiles.Count == 0, ResultFs.FileNotClosed.Value,
|
||||
"All files must be closed before unmounting.");
|
||||
DumpUnclosedAccessorList(OpenMode.All, OpenDirectoryMode.All);
|
||||
|
||||
Abort.DoAbortUnless(_openDirectories.Count == 0, ResultFs.DirectoryNotClosed.Value,
|
||||
"All directories must be closed before unmounting.");
|
||||
if (_openFiles.Count != 0)
|
||||
{
|
||||
Hos.Fs.Impl.LogErrorMessage(ResultFs.FileNotClosed.Value, FileNotClosedMessage);
|
||||
Abort.DoAbort(ResultFs.FileNotClosed.Value);
|
||||
}
|
||||
|
||||
if (_openDirectories.Count != 0)
|
||||
{
|
||||
Hos.Fs.Impl.LogErrorMessage(ResultFs.DirectoryNotClosed.Value, DirectoryNotClosedMessage);
|
||||
Abort.DoAbort(ResultFs.DirectoryNotClosed.Value);
|
||||
}
|
||||
|
||||
if (_isPathCacheAttached)
|
||||
{
|
||||
@ -75,46 +94,27 @@ namespace LibHac.Fs.Impl
|
||||
}
|
||||
}
|
||||
|
||||
_saveDataAttributeGetter?.Dispose();
|
||||
_saveDataAttributeGetter = null;
|
||||
ISaveDataAttributeGetter saveDataAttributeGetter = Shared.Move(ref _saveDataAttributeGetter);
|
||||
saveDataAttributeGetter?.Dispose();
|
||||
|
||||
_mountNameGenerator?.Dispose();
|
||||
_mountNameGenerator = null;
|
||||
ICommonMountNameGenerator mountNameGenerator = Shared.Move(ref _mountNameGenerator);
|
||||
mountNameGenerator?.Dispose();
|
||||
|
||||
_fileSystem?.Dispose();
|
||||
_fileSystem = null;
|
||||
IFileSystem fileSystem = Shared.Move(ref _fileSystem);
|
||||
fileSystem?.Dispose();
|
||||
}
|
||||
|
||||
private static void Remove<T>(LinkedList<T> list, T item)
|
||||
{
|
||||
LinkedListNode<T> node = list.Find(item);
|
||||
Abort.DoAbortUnless(node is not null, "Invalid file or directory object.");
|
||||
|
||||
list.Remove(node);
|
||||
}
|
||||
|
||||
private static Result CheckPath(U8Span mountName, U8Span path)
|
||||
{
|
||||
int mountNameLength = StringUtils.GetLength(mountName, PathTool.MountNameLengthMax);
|
||||
int pathLength = StringUtils.GetLength(path, PathTool.EntryNameLengthMax);
|
||||
|
||||
if (mountNameLength + 1 + pathLength > PathTool.EntryNameLengthMax)
|
||||
return ResultFs.TooLongPath.Log();
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
private static bool HasOpenWriteModeFiles(LinkedList<FileAccessor> list)
|
||||
{
|
||||
for (LinkedListNode<FileAccessor> file = list.First; file is not null; file = file.Next)
|
||||
if (node is not null)
|
||||
{
|
||||
if (file.Value.GetOpenMode().HasFlag(OpenMode.Write))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
list.Remove(node);
|
||||
return;
|
||||
}
|
||||
|
||||
return false;
|
||||
Assert.SdkAssert(false, InvalidFsEntryObjectMessage);
|
||||
}
|
||||
|
||||
public void SetAccessLog(bool isEnabled) => _isAccessLogEnabled = isEnabled;
|
||||
@ -131,9 +131,44 @@ namespace LibHac.Fs.Impl
|
||||
_isPathCacheAttached = true;
|
||||
}
|
||||
|
||||
public Optional<Ncm.DataId> GetDataId() => _dataId;
|
||||
public void SetDataId(Ncm.DataId dataId) => _dataId.Set(dataId);
|
||||
|
||||
public Result SetUpPath(ref Path path, U8Span pathBuffer)
|
||||
{
|
||||
Result rc = PathFormatter.IsNormalized(out bool isNormalized, out _, pathBuffer, _pathFlags);
|
||||
|
||||
if (rc.IsSuccess() && isNormalized)
|
||||
{
|
||||
path.SetShallowBuffer(pathBuffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_pathFlags.IsWindowsPathAllowed())
|
||||
{
|
||||
rc = path.InitializeWithReplaceForwardSlashes(pathBuffer);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = path.InitializeWithReplaceBackslash(pathBuffer);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
|
||||
rc = path.Normalize(_pathFlags);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
|
||||
if (path.GetLength() > PathTool.EntryNameLengthMax)
|
||||
return ResultFs.TooLongPath.Log();
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result CreateFile(U8Span path, long size, CreateFileOptions option)
|
||||
{
|
||||
Result rc = CheckPath(new U8Span(_mountName.Name), path);
|
||||
var pathNormalized = new Path();
|
||||
Result rc = SetUpPath(ref pathNormalized, path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (_isPathCacheAttached)
|
||||
@ -142,80 +177,87 @@ namespace LibHac.Fs.Impl
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = _fileSystem.CreateFile(path, size, option);
|
||||
rc = _fileSystem.CreateFile(in pathNormalized, size, option);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
|
||||
pathNormalized.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result DeleteFile(U8Span path)
|
||||
{
|
||||
Result rc = CheckPath(new U8Span(_mountName.Name), path);
|
||||
var pathNormalized = new Path();
|
||||
Result rc = SetUpPath(ref pathNormalized, path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return _fileSystem.DeleteFile(path);
|
||||
rc = _fileSystem.DeleteFile(in pathNormalized);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
pathNormalized.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result CreateDirectory(U8Span path)
|
||||
{
|
||||
Result rc = CheckPath(new U8Span(_mountName.Name), path);
|
||||
var pathNormalized = new Path();
|
||||
Result rc = SetUpPath(ref pathNormalized, path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return _fileSystem.CreateDirectory(path);
|
||||
rc = _fileSystem.CreateDirectory(in pathNormalized);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
pathNormalized.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result DeleteDirectory(U8Span path)
|
||||
{
|
||||
Result rc = CheckPath(new U8Span(_mountName.Name), path);
|
||||
var pathNormalized = new Path();
|
||||
Result rc = SetUpPath(ref pathNormalized, path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return _fileSystem.DeleteDirectory(path);
|
||||
rc = _fileSystem.CreateDirectory(in pathNormalized);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
pathNormalized.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result DeleteDirectoryRecursively(U8Span path)
|
||||
{
|
||||
Result rc = CheckPath(new U8Span(_mountName.Name), path);
|
||||
var pathNormalized = new Path();
|
||||
Result rc = SetUpPath(ref pathNormalized, path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return _fileSystem.DeleteDirectoryRecursively(path);
|
||||
rc = _fileSystem.DeleteDirectoryRecursively(in pathNormalized);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
pathNormalized.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result CleanDirectoryRecursively(U8Span path)
|
||||
{
|
||||
Result rc = CheckPath(new U8Span(_mountName.Name), path);
|
||||
var pathNormalized = new Path();
|
||||
Result rc = SetUpPath(ref pathNormalized, path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return _fileSystem.CleanDirectoryRecursively(path);
|
||||
}
|
||||
|
||||
public Result RenameFile(U8Span oldPath, U8Span newPath)
|
||||
{
|
||||
Result rc = CheckPath(new U8Span(_mountName.Name), oldPath);
|
||||
rc = _fileSystem.CleanDirectoryRecursively(in pathNormalized);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = CheckPath(new U8Span(_mountName.Name), newPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (_isPathCacheAttached)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = _fileSystem.RenameFile(oldPath, newPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
|
||||
pathNormalized.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result RenameDirectory(U8Span oldPath, U8Span newPath)
|
||||
public Result RenameFile(U8Span currentPath, U8Span newPath)
|
||||
{
|
||||
Result rc = CheckPath(new U8Span(_mountName.Name), oldPath);
|
||||
var currentPathNormalized = new Path();
|
||||
Result rc = SetUpPath(ref currentPathNormalized, currentPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = CheckPath(new U8Span(_mountName.Name), newPath);
|
||||
var newPathNormalized = new Path();
|
||||
rc = SetUpPath(ref newPathNormalized, newPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (_isPathCacheAttached)
|
||||
@ -224,10 +266,37 @@ namespace LibHac.Fs.Impl
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = _fileSystem.RenameDirectory(oldPath, newPath);
|
||||
rc = _fileSystem.RenameFile(in currentPathNormalized, in newPathNormalized);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
|
||||
currentPathNormalized.Dispose();
|
||||
newPathNormalized.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result RenameDirectory(U8Span currentPath, U8Span newPath)
|
||||
{
|
||||
var currentPathNormalized = new Path();
|
||||
Result rc = SetUpPath(ref currentPathNormalized, currentPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
var newPathNormalized = new Path();
|
||||
rc = SetUpPath(ref newPathNormalized, newPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (_isPathCacheAttached)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = _fileSystem.RenameDirectory(in currentPathNormalized, in newPathNormalized);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
|
||||
currentPathNormalized.Dispose();
|
||||
newPathNormalized.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
@ -235,46 +304,62 @@ namespace LibHac.Fs.Impl
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out entryType);
|
||||
|
||||
Result rc = CheckPath(new U8Span(_mountName.Name), path);
|
||||
var pathNormalized = new Path();
|
||||
Result rc = SetUpPath(ref pathNormalized, path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return _fileSystem.GetEntryType(out entryType, path);
|
||||
rc = _fileSystem.GetEntryType(out entryType, in pathNormalized);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
pathNormalized.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result GetFreeSpaceSize(out long freeSpace, U8Span path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out freeSpace);
|
||||
|
||||
Result rc = CheckPath(new U8Span(_mountName.Name), path);
|
||||
var pathNormalized = new Path();
|
||||
Result rc = SetUpPath(ref pathNormalized, path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return _fileSystem.GetFreeSpaceSize(out freeSpace, path);
|
||||
rc = _fileSystem.GetFreeSpaceSize(out freeSpace, in pathNormalized);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
pathNormalized.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result GetTotalSpaceSize(out long totalSpace, U8Span path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out totalSpace);
|
||||
|
||||
Result rc = CheckPath(new U8Span(_mountName.Name), path);
|
||||
var pathNormalized = new Path();
|
||||
Result rc = SetUpPath(ref pathNormalized, path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return _fileSystem.GetTotalSpaceSize(out totalSpace, path);
|
||||
rc = _fileSystem.GetTotalSpaceSize(out totalSpace, in pathNormalized);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
pathNormalized.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result OpenFile(out FileAccessor file, U8Span path, OpenMode mode)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out file);
|
||||
|
||||
Result rc = CheckPath(new U8Span(_mountName.Name), path);
|
||||
var pathNormalized = new Path();
|
||||
Result rc = SetUpPath(ref pathNormalized, path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
IFile iFile = null;
|
||||
try
|
||||
{
|
||||
rc = _fileSystem.OpenFile(out iFile, path, mode);
|
||||
rc = _fileSystem.OpenFile(out iFile, in pathNormalized, mode);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
var fileAccessor = new FileAccessor(FsClient, ref iFile, this, mode);
|
||||
var fileAccessor = new FileAccessor(Hos, ref iFile, this, mode);
|
||||
|
||||
using (ScopedLock.Lock(ref _openListLock))
|
||||
{
|
||||
@ -299,6 +384,7 @@ namespace LibHac.Fs.Impl
|
||||
finally
|
||||
{
|
||||
iFile?.Dispose();
|
||||
pathNormalized.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@ -306,13 +392,14 @@ namespace LibHac.Fs.Impl
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out directory);
|
||||
|
||||
Result rc = CheckPath(new U8Span(_mountName.Name), path);
|
||||
var pathNormalized = new Path();
|
||||
Result rc = SetUpPath(ref pathNormalized, path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
IDirectory iDirectory = null;
|
||||
try
|
||||
{
|
||||
rc = _fileSystem.OpenDirectory(out iDirectory, path, mode);
|
||||
rc = _fileSystem.OpenDirectory(out iDirectory, in pathNormalized, mode);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
var directoryAccessor = new DirectoryAccessor(ref iDirectory, this);
|
||||
@ -328,11 +415,25 @@ namespace LibHac.Fs.Impl
|
||||
finally
|
||||
{
|
||||
iDirectory?.Dispose();
|
||||
pathNormalized.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public Result Commit()
|
||||
{
|
||||
static bool HasOpenWriteModeFiles(LinkedList<FileAccessor> list)
|
||||
{
|
||||
for (LinkedListNode<FileAccessor> file = list.First; file is not null; file = file.Next)
|
||||
{
|
||||
if (file.Value.GetOpenMode().HasFlag(OpenMode.Write))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
using (ScopedLock.Lock(ref _openListLock))
|
||||
{
|
||||
DumpUnclosedAccessorList(OpenMode.Write, 0);
|
||||
@ -346,12 +447,30 @@ namespace LibHac.Fs.Impl
|
||||
|
||||
public Result GetFileTimeStampRaw(out FileTimeStampRaw timeStamp, U8Span path)
|
||||
{
|
||||
return _fileSystem.GetFileTimeStampRaw(out timeStamp, path);
|
||||
UnsafeHelpers.SkipParamInit(out timeStamp);
|
||||
|
||||
var pathNormalized = new Path();
|
||||
Result rc = SetUpPath(ref pathNormalized, path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = _fileSystem.GetFileTimeStampRaw(out timeStamp, in pathNormalized);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
pathNormalized.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, QueryId queryId, U8Span path)
|
||||
{
|
||||
return _fileSystem.QueryEntry(outBuffer, inBuffer, queryId, path);
|
||||
var pathNormalized = new Path();
|
||||
Result rc = SetUpPath(ref pathNormalized, path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = _fileSystem.QueryEntry(outBuffer, inBuffer, queryId, in pathNormalized);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
pathNormalized.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public U8Span GetName()
|
||||
@ -374,7 +493,10 @@ namespace LibHac.Fs.Impl
|
||||
if (_saveDataAttributeGetter is null)
|
||||
return ResultFs.PreconditionViolation.Log();
|
||||
|
||||
return _saveDataAttributeGetter.GetSaveDataAttribute(out attribute);
|
||||
Result rc = _saveDataAttributeGetter.GetSaveDataAttribute(out attribute);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public ReferenceCountedDisposable<IFileSystemSf> GetMultiCommitTarget()
|
||||
@ -387,21 +509,203 @@ namespace LibHac.Fs.Impl
|
||||
cacheAccessor.Purge(_fileSystem);
|
||||
}
|
||||
|
||||
public void NotifyCloseFile(FileAccessor file)
|
||||
internal void NotifyCloseFile(FileAccessor file)
|
||||
{
|
||||
using ScopedLock<SdkMutexType> lk = ScopedLock.Lock(ref _openListLock);
|
||||
Remove(_openFiles, file);
|
||||
}
|
||||
|
||||
public void NotifyCloseDirectory(DirectoryAccessor directory)
|
||||
internal void NotifyCloseDirectory(DirectoryAccessor directory)
|
||||
{
|
||||
using ScopedLock<SdkMutexType> lk = ScopedLock.Lock(ref _openListLock);
|
||||
Remove(_openDirectories, directory);
|
||||
}
|
||||
|
||||
private static ReadOnlySpan<byte> LogFsModuleName => new[] { (byte)'$', (byte)'f', (byte)'s' }; // "$fs"
|
||||
|
||||
private static ReadOnlySpan<byte> LogFsErrorInfo => // "------ FS ERROR INFORMATION ------\n"
|
||||
new[]
|
||||
{
|
||||
(byte)'-', (byte)'-', (byte)'-', (byte)'-', (byte)'-', (byte)'-', (byte)' ', (byte)'F',
|
||||
(byte)'S', (byte)' ', (byte)'E', (byte)'R', (byte)'R', (byte)'O', (byte)'R', (byte)' ',
|
||||
(byte)'I', (byte)'N', (byte)'F', (byte)'O', (byte)'R', (byte)'M', (byte)'A', (byte)'T',
|
||||
(byte)'I', (byte)'O', (byte)'N', (byte)' ', (byte)'-', (byte)'-', (byte)'-', (byte)'-',
|
||||
(byte)'-', (byte)'-', (byte)'\\', (byte)'n'
|
||||
};
|
||||
|
||||
private static ReadOnlySpan<byte> LogFileNotClosed => // "Error: File not closed"
|
||||
new[]
|
||||
{
|
||||
(byte)'E', (byte)'r', (byte)'r', (byte)'o', (byte)'r', (byte)':', (byte)' ', (byte)'F',
|
||||
(byte)'i', (byte)'l', (byte)'e', (byte)' ', (byte)'n', (byte)'o', (byte)'t', (byte)' ',
|
||||
(byte)'c', (byte)'l', (byte)'o', (byte)'s', (byte)'e', (byte)'d'
|
||||
};
|
||||
|
||||
private static ReadOnlySpan<byte> LogDirectoryNotClosed => // "Error: Directory not closed"
|
||||
new[]
|
||||
{
|
||||
(byte)'E', (byte)'r', (byte)'r', (byte)'o', (byte)'r', (byte)':', (byte)' ', (byte)'D',
|
||||
(byte)'i', (byte)'r', (byte)'e', (byte)'c', (byte)'t', (byte)'o', (byte)'r', (byte)'y',
|
||||
(byte)' ', (byte)'n', (byte)'o', (byte)'t', (byte)' ', (byte)'c', (byte)'l', (byte)'o',
|
||||
(byte)'s', (byte)'e', (byte)'d'
|
||||
};
|
||||
|
||||
private static ReadOnlySpan<byte> LogMountName => // " (mount_name: ""
|
||||
new[]
|
||||
{
|
||||
(byte)' ', (byte)'(', (byte)'m', (byte)'o', (byte)'u', (byte)'n', (byte)'t', (byte)'_',
|
||||
(byte)'n', (byte)'a', (byte)'m', (byte)'e', (byte)':', (byte)' ', (byte)'"'
|
||||
};
|
||||
|
||||
private static ReadOnlySpan<byte> LogCount => // "", count: "
|
||||
new[]
|
||||
{
|
||||
(byte)'"', (byte)',', (byte)' ', (byte)'c', (byte)'o', (byte)'u', (byte)'n', (byte)'t',
|
||||
(byte)':', (byte)' '
|
||||
};
|
||||
|
||||
public static ReadOnlySpan<byte> LogLineEnd => new[] { (byte)')', (byte)'\\', (byte)'n' }; // ")\n"
|
||||
|
||||
public static ReadOnlySpan<byte> LogOrOperator => new[] { (byte)' ', (byte)'|', (byte)' ' }; // " | "
|
||||
|
||||
private static ReadOnlySpan<byte> LogOpenModeRead => // "OpenMode_Read"
|
||||
new[]
|
||||
{
|
||||
(byte)'O', (byte)'p', (byte)'e', (byte)'n', (byte)'M', (byte)'o', (byte)'d', (byte)'e',
|
||||
(byte)'_', (byte)'R', (byte)'e', (byte)'a', (byte)'d'
|
||||
};
|
||||
|
||||
private static ReadOnlySpan<byte> LogOpenModeWrite => // "OpenMode_Write"
|
||||
new[]
|
||||
{
|
||||
(byte)'O', (byte)'p', (byte)'e', (byte)'n', (byte)'M', (byte)'o', (byte)'d', (byte)'e',
|
||||
(byte)'_', (byte)'W', (byte)'r', (byte)'i', (byte)'t', (byte)'e'
|
||||
};
|
||||
|
||||
private static ReadOnlySpan<byte> LogOpenModeAppend => // "OpenMode_AllowAppend"
|
||||
new[]
|
||||
{
|
||||
(byte)'O', (byte)'p', (byte)'e', (byte)'n', (byte)'M', (byte)'o', (byte)'d', (byte)'e',
|
||||
(byte)'_', (byte)'A', (byte)'l', (byte)'l', (byte)'o', (byte)'w', (byte)'A', (byte)'p',
|
||||
(byte)'p', (byte)'e', (byte)'n', (byte)'d'
|
||||
};
|
||||
|
||||
private static ReadOnlySpan<byte> LogHandle => // " handle: 0x"
|
||||
new[]
|
||||
{
|
||||
(byte)' ', (byte)' ', (byte)' ', (byte)' ', (byte)' ', (byte)'h', (byte)'a', (byte)'n',
|
||||
(byte)'d', (byte)'l', (byte)'e', (byte)':', (byte)' ', (byte)'0', (byte)'x'
|
||||
};
|
||||
|
||||
private static ReadOnlySpan<byte> LogOpenMode => // ", open_mode: "
|
||||
new[]
|
||||
{
|
||||
(byte)',', (byte)' ', (byte)'o', (byte)'p', (byte)'e', (byte)'n', (byte)'_', (byte)'m',
|
||||
(byte)'o', (byte)'d', (byte)'e', (byte)':', (byte)' '
|
||||
};
|
||||
|
||||
private static ReadOnlySpan<byte> LogSize => // ", size: "
|
||||
new[]
|
||||
{
|
||||
(byte)',', (byte)' ', (byte)'s', (byte)'i', (byte)'z', (byte)'e', (byte)':', (byte)' '
|
||||
};
|
||||
|
||||
private void DumpUnclosedAccessorList(OpenMode fileOpenModeMask, OpenDirectoryMode directoryOpenModeMask)
|
||||
{
|
||||
// Todo: Implement
|
||||
static int GetOpenFileCount(LinkedList<FileAccessor> list, OpenMode mask)
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
for (LinkedListNode<FileAccessor> file = list.First; file is not null; file = file.Next)
|
||||
{
|
||||
if ((file.Value.GetOpenMode() & mask) != 0)
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
Span<byte> stringBuffer = stackalloc byte[0xA0];
|
||||
Span<byte> openModeStringBuffer = stackalloc byte[0x40];
|
||||
|
||||
int openFileCount = GetOpenFileCount(_openFiles, fileOpenModeMask);
|
||||
|
||||
if (openFileCount > 0 || directoryOpenModeMask != 0 && _openDirectories.Count != 0)
|
||||
{
|
||||
Hos.Diag.Impl.LogImpl(LogFsModuleName, LogSeverity.Error, LogFsErrorInfo);
|
||||
}
|
||||
|
||||
if (openFileCount > 0)
|
||||
{
|
||||
var sb = new U8StringBuilder(stringBuffer, true);
|
||||
sb.Append(LogFileNotClosed).Append(LogMountName).Append(GetName()).Append(LogCount)
|
||||
.AppendFormat(openFileCount).Append(LogLineEnd);
|
||||
|
||||
Hos.Diag.Impl.LogImpl(LogFsModuleName, LogSeverity.Error, sb.Buffer);
|
||||
sb.Dispose();
|
||||
|
||||
for (LinkedListNode<FileAccessor> file = _openFiles.First; file is not null; file = file.Next)
|
||||
{
|
||||
OpenMode openMode = file.Value.GetOpenMode();
|
||||
|
||||
if ((openMode & fileOpenModeMask) == 0)
|
||||
continue;
|
||||
|
||||
Result rc = file.Value.GetSize(out long fileSize);
|
||||
if (rc.IsFailure())
|
||||
fileSize = -1;
|
||||
|
||||
var openModeString = new U8StringBuilder(openModeStringBuffer);
|
||||
|
||||
ReadOnlySpan<byte> readModeString = openMode.HasFlag(OpenMode.Read) ? LogOpenModeRead : default;
|
||||
openModeString.Append(readModeString);
|
||||
Assert.SdkAssert(!openModeString.Overflowed);
|
||||
|
||||
if (openMode.HasFlag(OpenMode.Write))
|
||||
{
|
||||
if (openModeString.Length > 0)
|
||||
sb.Append(LogOrOperator);
|
||||
|
||||
openModeString.Append(LogOpenModeWrite);
|
||||
Assert.SdkAssert(!openModeString.Overflowed);
|
||||
}
|
||||
|
||||
if (openMode.HasFlag(OpenMode.AllowAppend))
|
||||
{
|
||||
if (openModeString.Length > 0)
|
||||
sb.Append(LogOrOperator);
|
||||
|
||||
openModeString.Append(LogOpenModeAppend);
|
||||
Assert.SdkAssert(!openModeString.Overflowed);
|
||||
}
|
||||
|
||||
var fileInfoString = new U8StringBuilder(stringBuffer, true);
|
||||
fileInfoString.Append(LogHandle).AppendFormat(file.Value.GetHashCode(), 'x', 16).Append(LogOpenMode)
|
||||
.Append(openModeString.Buffer).Append(LogSize).AppendFormat(fileSize).Append((byte) '\n');
|
||||
|
||||
Hos.Diag.Impl.LogImpl(LogFsModuleName, LogSeverity.Error, fileInfoString.Buffer);
|
||||
fileInfoString.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
if (directoryOpenModeMask != 0 && _openDirectories.Count != 0)
|
||||
{
|
||||
var sb = new U8StringBuilder(stringBuffer, true);
|
||||
sb.Append(LogDirectoryNotClosed).Append(LogMountName).Append(GetName()).Append(LogCount)
|
||||
.AppendFormat(_openDirectories.Count).Append(LogLineEnd);
|
||||
|
||||
Hos.Diag.Impl.LogImpl(LogFsModuleName, LogSeverity.Error, sb.Buffer);
|
||||
sb.Dispose();
|
||||
|
||||
for (LinkedListNode<DirectoryAccessor> dir = _openDirectories.First; dir is not null; dir = dir.Next)
|
||||
{
|
||||
var dirInfoString = new U8StringBuilder(stringBuffer, true);
|
||||
dirInfoString.Append(LogHandle).AppendFormat(dir.Value.GetHashCode(), 'x', 16).Append((byte)'\n');
|
||||
|
||||
Hos.Diag.Impl.LogImpl(LogFsModuleName, LogSeverity.Error, dirInfoString.Buffer);
|
||||
dirInfoString.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,49 +1,31 @@
|
||||
using LibHac.Common;
|
||||
|
||||
namespace LibHac.Fs.Fsa
|
||||
namespace LibHac.Fs.Fsa
|
||||
{
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public abstract class IAttributeFileSystem : IFileSystem
|
||||
{
|
||||
public Result CreateDirectory(U8Span path, NxFileAttributes archiveAttribute)
|
||||
public Result CreateDirectory(in Path path, NxFileAttributes archiveAttribute)
|
||||
{
|
||||
if (path.IsNull())
|
||||
return ResultFs.NullptrArgument.Log();
|
||||
|
||||
return DoCreateDirectory(path, archiveAttribute);
|
||||
return DoCreateDirectory(in path, archiveAttribute);
|
||||
}
|
||||
|
||||
public Result GetFileAttributes(out NxFileAttributes attributes, U8Span path)
|
||||
public Result GetFileAttributes(out NxFileAttributes attributes, in Path path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out attributes);
|
||||
|
||||
if (path.IsNull())
|
||||
return ResultFs.NullptrArgument.Log();
|
||||
|
||||
return DoGetFileAttributes(out attributes, path);
|
||||
return DoGetFileAttributes(out attributes, in path);
|
||||
}
|
||||
|
||||
public Result SetFileAttributes(U8Span path, NxFileAttributes attributes)
|
||||
public Result SetFileAttributes(in Path path, NxFileAttributes attributes)
|
||||
{
|
||||
if (path.IsNull())
|
||||
return ResultFs.NullptrArgument.Log();
|
||||
|
||||
return DoSetFileAttributes(path, attributes);
|
||||
return DoSetFileAttributes(in path, attributes);
|
||||
}
|
||||
|
||||
public Result GetFileSize(out long fileSize, U8Span path)
|
||||
public Result GetFileSize(out long fileSize, in Path path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out fileSize);
|
||||
|
||||
if (path.IsNull())
|
||||
return ResultFs.NullptrArgument.Log();
|
||||
|
||||
return DoGetFileSize(out fileSize, path);
|
||||
return DoGetFileSize(out fileSize, in path);
|
||||
}
|
||||
|
||||
protected abstract Result DoCreateDirectory(U8Span path, NxFileAttributes archiveAttribute);
|
||||
protected abstract Result DoGetFileAttributes(out NxFileAttributes attributes, U8Span path);
|
||||
protected abstract Result DoSetFileAttributes(U8Span path, NxFileAttributes attributes);
|
||||
protected abstract Result DoGetFileSize(out long fileSize, U8Span path);
|
||||
protected abstract Result DoCreateDirectory(in Path path, NxFileAttributes archiveAttribute);
|
||||
protected abstract Result DoGetFileAttributes(out NxFileAttributes attributes, in Path path);
|
||||
protected abstract Result DoSetFileAttributes(in Path path, NxFileAttributes attributes);
|
||||
protected abstract Result DoGetFileSize(out long fileSize, in Path path);
|
||||
}
|
||||
}
|
||||
|
@ -44,12 +44,6 @@ namespace LibHac.Fs.Fsa
|
||||
protected abstract Result DoRead(out long entriesRead, Span<DirectoryEntry> entryBuffer);
|
||||
protected abstract Result DoGetEntryCount(out long entryCount);
|
||||
|
||||
protected virtual void Dispose(bool disposing) { }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
public virtual void Dispose() { }
|
||||
}
|
||||
}
|
||||
|
@ -219,12 +219,6 @@ namespace LibHac.Fs.Fsa
|
||||
protected abstract Result DoOperateRange(Span<byte> outBuffer, OperationId operationId, long offset, long size,
|
||||
ReadOnlySpan<byte> inBuffer);
|
||||
|
||||
protected virtual void Dispose(bool disposing) { }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
public virtual void Dispose() { }
|
||||
}
|
||||
}
|
||||
|
@ -17,23 +17,16 @@ namespace LibHac.Fs.Fsa
|
||||
/// <param name="size">The initial size of the created file.</param>
|
||||
/// <param name="option">Flags to control how the file is created.
|
||||
/// Should usually be <see cref="CreateFileOptions.None"/></param>
|
||||
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
|
||||
/// <remarks>
|
||||
/// The following <see cref="Result"/> codes may be returned under certain conditions:
|
||||
///
|
||||
/// The parent directory of the specified path does not exist: <see cref="ResultFs.PathNotFound"/>
|
||||
/// Specified path already exists as either a file or directory: <see cref="ResultFs.PathAlreadyExists"/>
|
||||
/// Insufficient free space to create the file: <see cref="ResultFs.UsableSpaceNotEnough"/>
|
||||
/// </remarks>
|
||||
public Result CreateFile(U8Span path, long size, CreateFileOptions option)
|
||||
/// <returns><see cref="Result.Success"/>: The operation was successful.<br/>
|
||||
/// <see cref="ResultFs.PathNotFound"/>: The parent directory of the specified path does not exist.<br/>
|
||||
/// <see cref="ResultFs.PathAlreadyExists"/>: Specified path already exists as either a file or directory.<br/>
|
||||
/// <see cref="ResultFs.UsableSpaceNotEnough"/>: Insufficient free space to create the file.</returns>
|
||||
public Result CreateFile(in Path path, long size, CreateFileOptions option)
|
||||
{
|
||||
if (path.IsNull())
|
||||
return ResultFs.NullptrArgument.Log();
|
||||
|
||||
if (size < 0)
|
||||
return ResultFs.OutOfRange.Log();
|
||||
|
||||
return DoCreateFile(path, size, option);
|
||||
return DoCreateFile(in path, size, option);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -42,175 +35,127 @@ namespace LibHac.Fs.Fsa
|
||||
/// <param name="path">The full path of the file to create.</param>
|
||||
/// <param name="size">The initial size of the created file.
|
||||
/// Should usually be <see cref="CreateFileOptions.None"/></param>
|
||||
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
|
||||
/// <remarks>
|
||||
/// The following <see cref="Result"/> codes may be returned under certain conditions:
|
||||
///
|
||||
/// The parent directory of the specified path does not exist: <see cref="ResultFs.PathNotFound"/>
|
||||
/// Specified path already exists as either a file or directory: <see cref="ResultFs.PathAlreadyExists"/>
|
||||
/// Insufficient free space to create the file: <see cref="ResultFs.UsableSpaceNotEnough"/>
|
||||
/// </remarks>
|
||||
public Result CreateFile(U8Span path, long size)
|
||||
/// <returns><see cref="Result.Success"/>: The operation was successful.<br/>
|
||||
/// <see cref="ResultFs.PathNotFound"/>: The parent directory of the specified path does not exist.<br/>
|
||||
/// <see cref="ResultFs.PathAlreadyExists"/>: Specified path already exists as either a file or directory.<br/>
|
||||
/// <see cref="ResultFs.UsableSpaceNotEnough"/>: Insufficient free space to create the file.</returns>
|
||||
public Result CreateFile(in Path path, long size)
|
||||
{
|
||||
if (path.IsNull())
|
||||
return ResultFs.NullptrArgument.Log();
|
||||
|
||||
if (size < 0)
|
||||
return ResultFs.OutOfRange.Log();
|
||||
|
||||
return DoCreateFile(path, size, CreateFileOptions.None);
|
||||
return CreateFile(in path, size, CreateFileOptions.None);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the specified file.
|
||||
/// </summary>
|
||||
/// <param name="path">The full path of the file to delete.</param>
|
||||
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
|
||||
/// <remarks>
|
||||
/// The following <see cref="Result"/> codes may be returned under certain conditions:
|
||||
///
|
||||
/// The specified path does not exist or is a directory: <see cref="ResultFs.PathNotFound"/>
|
||||
/// </remarks>
|
||||
public Result DeleteFile(U8Span path)
|
||||
/// <returns><see cref="Result.Success"/>: The operation was successful.<br/>
|
||||
/// <see cref="ResultFs.PathNotFound"/>: The specified path does not exist or is a directory.</returns>
|
||||
public Result DeleteFile(in Path path)
|
||||
{
|
||||
if (path.IsNull())
|
||||
return ResultFs.NullptrArgument.Log();
|
||||
|
||||
return DoDeleteFile(path);
|
||||
return DoDeleteFile(in path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates all directories and subdirectories in the specified path unless they already exist.
|
||||
/// </summary>
|
||||
/// <param name="path">The full path of the directory to create.</param>
|
||||
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
|
||||
/// <remarks>
|
||||
/// The following <see cref="Result"/> codes may be returned under certain conditions:
|
||||
///
|
||||
/// The parent directory of the specified path does not exist: <see cref="ResultFs.PathNotFound"/>
|
||||
/// Specified path already exists as either a file or directory: <see cref="ResultFs.PathAlreadyExists"/>
|
||||
/// Insufficient free space to create the directory: <see cref="ResultFs.UsableSpaceNotEnough"/>
|
||||
/// </remarks>
|
||||
public Result CreateDirectory(U8Span path)
|
||||
/// <returns><see cref="Result.Success"/>: The operation was successful.<br/>
|
||||
/// <see cref="ResultFs.PathNotFound"/>: The parent directory of the specified path does not exist.<br/>
|
||||
/// <see cref="ResultFs.PathAlreadyExists"/>: Specified path already exists as either a file or directory.<br/>
|
||||
/// <see cref="ResultFs.UsableSpaceNotEnough"/>: Insufficient free space to create the directory.</returns>
|
||||
public Result CreateDirectory(in Path path)
|
||||
{
|
||||
if (path.IsNull())
|
||||
return ResultFs.NullptrArgument.Log();
|
||||
|
||||
return DoCreateDirectory(path);
|
||||
return DoCreateDirectory(in path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the specified directory.
|
||||
/// </summary>
|
||||
/// <param name="path">The full path of the directory to delete.</param>
|
||||
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
|
||||
/// <remarks>
|
||||
/// The following <see cref="Result"/> codes may be returned under certain conditions:
|
||||
///
|
||||
/// The specified path does not exist or is a file: <see cref="ResultFs.PathNotFound"/>
|
||||
/// The specified directory is not empty: <see cref="ResultFs.DirectoryNotEmpty"/>
|
||||
/// </remarks>
|
||||
public Result DeleteDirectory(U8Span path)
|
||||
/// <returns><see cref="Result.Success"/>: The operation was successful.<br/>
|
||||
/// <see cref="ResultFs.PathNotFound"/>: The specified path does not exist or is a file.<br/>
|
||||
/// <see cref="ResultFs.DirectoryNotEmpty"/>: The specified directory is not empty.</returns>
|
||||
public Result DeleteDirectory(in Path path)
|
||||
{
|
||||
if (path.IsNull())
|
||||
return ResultFs.NullptrArgument.Log();
|
||||
|
||||
return DoDeleteDirectory(path);
|
||||
return DoDeleteDirectory(in path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the specified directory and any subdirectories and files in the directory.
|
||||
/// </summary>
|
||||
/// <param name="path">The full path of the directory to delete.</param>
|
||||
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
|
||||
/// <remarks>
|
||||
/// The following <see cref="Result"/> codes may be returned under certain conditions:
|
||||
///
|
||||
/// The specified path does not exist or is a file: <see cref="ResultFs.PathNotFound"/>
|
||||
/// </remarks>
|
||||
public Result DeleteDirectoryRecursively(U8Span path)
|
||||
/// <returns><see cref="Result.Success"/>: The operation was successful.<br/>
|
||||
/// <see cref="ResultFs.PathNotFound"/>: The specified path does not exist or is a file.</returns>
|
||||
public Result DeleteDirectoryRecursively(in Path path)
|
||||
{
|
||||
if (path.IsNull())
|
||||
return ResultFs.NullptrArgument.Log();
|
||||
|
||||
return DoDeleteDirectoryRecursively(path);
|
||||
return DoDeleteDirectoryRecursively(in path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes any subdirectories and files in the specified directory.
|
||||
/// </summary>
|
||||
/// <param name="path">The full path of the directory to clean.</param>
|
||||
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
|
||||
/// <remarks>
|
||||
/// The following <see cref="Result"/> codes may be returned under certain conditions:
|
||||
///
|
||||
/// The specified path does not exist or is a file: <see cref="ResultFs.PathNotFound"/>
|
||||
/// </remarks>
|
||||
public Result CleanDirectoryRecursively(U8Span path)
|
||||
/// <returns><see cref="Result.Success"/>: The operation was successful.<br/>
|
||||
/// <see cref="ResultFs.PathNotFound"/>: The specified path does not exist or is a file.</returns>
|
||||
public Result CleanDirectoryRecursively(in Path path)
|
||||
{
|
||||
if (path.IsNull())
|
||||
return ResultFs.NullptrArgument.Log();
|
||||
|
||||
return DoCleanDirectoryRecursively(path);
|
||||
return DoCleanDirectoryRecursively(in path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renames or moves a file to a new location.
|
||||
/// </summary>
|
||||
/// <param name="oldPath">The full path of the file to rename.</param>
|
||||
/// <param name="currentPath">The current full path of the file to rename.</param>
|
||||
/// <param name="newPath">The new full path of the file.</param>
|
||||
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
|
||||
/// <returns><see cref="Result.Success"/>: The operation was successful.<br/>
|
||||
/// <see cref="ResultFs.PathNotFound"/>: <paramref name="currentPath"/> does not exist or is a directory.<br/>
|
||||
/// <see cref="ResultFs.PathNotFound"/>: <paramref name="newPath"/>'s parent directory does not exist.<br/>
|
||||
/// <see cref="ResultFs.PathAlreadyExists"/>: <paramref name="newPath"/> already exists as either a file or directory.</returns>
|
||||
/// <remarks>
|
||||
/// If <paramref name="oldPath"/> and <paramref name="newPath"/> are the same, this function does nothing and returns successfully.
|
||||
/// The following <see cref="Result"/> codes may be returned under certain conditions:
|
||||
///
|
||||
/// <paramref name="oldPath"/> does not exist or is a directory: <see cref="ResultFs.PathNotFound"/>
|
||||
/// <paramref name="newPath"/>'s parent directory does not exist: <see cref="ResultFs.PathNotFound"/>
|
||||
/// <paramref name="newPath"/> already exists as either a file or directory: <see cref="ResultFs.PathAlreadyExists"/>
|
||||
/// If <paramref name="currentPath"/> and <paramref name="newPath"/> are the same, this function does nothing and returns successfully.
|
||||
/// </remarks>
|
||||
public Result RenameFile(U8Span oldPath, U8Span newPath)
|
||||
public Result RenameFile(in Path currentPath, in Path newPath)
|
||||
{
|
||||
if (oldPath.IsNull())
|
||||
return ResultFs.NullptrArgument.Log();
|
||||
|
||||
if (newPath.IsNull())
|
||||
return ResultFs.NullptrArgument.Log();
|
||||
|
||||
return DoRenameFile(oldPath, newPath);
|
||||
return DoRenameFile(in currentPath, in newPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renames or moves a directory to a new location.
|
||||
/// </summary>
|
||||
/// <param name="oldPath">The full path of the directory to rename.</param>
|
||||
/// <param name="currentPath">The full path of the directory to rename.</param>
|
||||
/// <param name="newPath">The new full path of the directory.</param>
|
||||
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
|
||||
/// <returns><see cref="Result.Success"/>: The operation was successful.<br/>
|
||||
/// <see cref="ResultFs.PathNotFound"/>: <paramref name="currentPath"/> does not exist or is a file.<br/>
|
||||
/// <see cref="ResultFs.PathNotFound"/>: <paramref name="newPath"/>'s parent directory does not exist.<br/>
|
||||
/// <see cref="ResultFs.PathAlreadyExists"/>: <paramref name="newPath"/> already exists as either a file or directory.<br/>
|
||||
/// <see cref="ResultFs.DirectoryNotRenamable"/>: Either <paramref name="currentPath"/> or <paramref name="newPath"/> is a subpath of the other.</returns>
|
||||
/// <remarks>
|
||||
/// If <paramref name="oldPath"/> and <paramref name="newPath"/> are the same, this function does nothing and returns <see cref="Result.Success"/>.
|
||||
/// The following <see cref="Result"/> codes may be returned under certain conditions:
|
||||
///
|
||||
/// <paramref name="oldPath"/> does not exist or is a file: <see cref="ResultFs.PathNotFound"/>
|
||||
/// <paramref name="newPath"/>'s parent directory does not exist: <see cref="ResultFs.PathNotFound"/>
|
||||
/// <paramref name="newPath"/> already exists as either a file or directory: <see cref="ResultFs.PathAlreadyExists"/>
|
||||
/// Either <paramref name="oldPath"/> or <paramref name="newPath"/> is a subpath of the other: <see cref="ResultFs.DirectoryNotRenamable"/>
|
||||
/// If <paramref name="currentPath"/> and <paramref name="newPath"/> are the same, this function does nothing and returns <see cref="Result.Success"/>.
|
||||
/// </remarks>
|
||||
public Result RenameDirectory(U8Span oldPath, U8Span newPath)
|
||||
public Result RenameDirectory(in Path currentPath, in Path newPath)
|
||||
{
|
||||
if (oldPath.IsNull())
|
||||
return ResultFs.NullptrArgument.Log();
|
||||
|
||||
if (newPath.IsNull())
|
||||
return ResultFs.NullptrArgument.Log();
|
||||
|
||||
return DoRenameDirectory(oldPath, newPath);
|
||||
return DoRenameDirectory(in currentPath, in newPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified path is a file or directory, or does not exist.
|
||||
/// </summary>
|
||||
/// <param name="entryType">If the operation returns successfully, the <see cref="DirectoryEntryType"/> of the file.</param>
|
||||
/// <param name="entryType">If the operation returns successfully, contains the <see cref="DirectoryEntryType"/> of the file.</param>
|
||||
/// <param name="path">The full path to check.</param>
|
||||
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
|
||||
/// <returns><see cref="Result.Success"/>: The operation was successful.<br/>
|
||||
/// <see cref="ResultFs.PathNotFound"/>: The specified path does not exist.</returns>
|
||||
public Result GetEntryType(out DirectoryEntryType entryType, in Path path)
|
||||
{
|
||||
return DoGetEntryType(out entryType, in path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified path is a file or directory, or does not exist.
|
||||
/// </summary>
|
||||
/// <param name="entryType">If the operation returns successfully, contains the <see cref="DirectoryEntryType"/> of the file.</param>
|
||||
/// <param name="path">The full path to check.</param>
|
||||
/// <returns><see cref="Result.Success"/>: The operation was successful.<br/>
|
||||
/// <see cref="ResultFs.PathNotFound"/>: The specified path does not exist.</returns>
|
||||
public Result GetEntryType(out DirectoryEntryType entryType, U8Span path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out entryType);
|
||||
@ -218,7 +163,11 @@ namespace LibHac.Fs.Fsa
|
||||
if (path.IsNull())
|
||||
return ResultFs.NullptrArgument.Log();
|
||||
|
||||
return DoGetEntryType(out entryType, path);
|
||||
var pathNormalized = new Path();
|
||||
Result rs = pathNormalized.InitializeWithNormalization(path);
|
||||
if (rs.IsFailure()) return rs;
|
||||
|
||||
return DoGetEntryType(out entryType, in pathNormalized);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -227,14 +176,9 @@ namespace LibHac.Fs.Fsa
|
||||
/// <param name="freeSpace">If the operation returns successfully, the amount of free space available on the drive, in bytes.</param>
|
||||
/// <param name="path">The path of the drive to query. Unused in almost all cases.</param>
|
||||
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
|
||||
public Result GetFreeSpaceSize(out long freeSpace, U8Span path)
|
||||
public Result GetFreeSpaceSize(out long freeSpace, in Path path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out freeSpace);
|
||||
|
||||
if (path.IsNull())
|
||||
return ResultFs.NullptrArgument.Log();
|
||||
|
||||
return DoGetFreeSpaceSize(out freeSpace, path);
|
||||
return DoGetFreeSpaceSize(out freeSpace, in path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -243,14 +187,9 @@ namespace LibHac.Fs.Fsa
|
||||
/// <param name="totalSpace">If the operation returns successfully, the total size of the drive, in bytes.</param>
|
||||
/// <param name="path">The path of the drive to query. Unused in almost all cases.</param>
|
||||
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
|
||||
public Result GetTotalSpaceSize(out long totalSpace, U8Span path)
|
||||
public Result GetTotalSpaceSize(out long totalSpace, in Path path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out totalSpace);
|
||||
|
||||
if (path.IsNull())
|
||||
return ResultFs.NullptrArgument.Log();
|
||||
|
||||
return DoGetTotalSpaceSize(out totalSpace, path);
|
||||
return DoGetTotalSpaceSize(out totalSpace, in path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -260,33 +199,44 @@ namespace LibHac.Fs.Fsa
|
||||
/// An <see cref="IFile"/> instance for the specified path.</param>
|
||||
/// <param name="path">The full path of the file to open.</param>
|
||||
/// <param name="mode">Specifies the access permissions of the created <see cref="IFile"/>.</param>
|
||||
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
|
||||
/// <remarks>
|
||||
/// The following <see cref="Result"/> codes may be returned under certain conditions:
|
||||
///
|
||||
/// The specified path does not exist or is a directory: <see cref="ResultFs.PathNotFound"/>
|
||||
/// </remarks>
|
||||
/// <returns><see cref="Result.Success"/>: The operation was successful.<br/>
|
||||
/// <see cref="ResultFs.PathNotFound"/>: The specified path does not exist or is a directory.<br/>
|
||||
/// <see cref="ResultFs.TargetLocked"/>: When opening as <see cref="OpenMode.Write"/>,
|
||||
/// the file is already opened as <see cref="OpenMode.Write"/>.</returns>
|
||||
public Result OpenFile(out IFile file, U8Span path, OpenMode mode)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out file);
|
||||
|
||||
if (path.IsNull())
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out file);
|
||||
return ResultFs.NullptrArgument.Log();
|
||||
}
|
||||
|
||||
if ((mode & OpenMode.ReadWrite) == 0)
|
||||
var pathNormalized = new Path();
|
||||
Result rs = pathNormalized.InitializeWithNormalization(path);
|
||||
if (rs.IsFailure()) return rs;
|
||||
|
||||
return DoOpenFile(out file, in pathNormalized, mode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens an <see cref="IFile"/> instance for the specified path.
|
||||
/// </summary>
|
||||
/// <param name="file">If the operation returns successfully,
|
||||
/// An <see cref="IFile"/> instance for the specified path.</param>
|
||||
/// <param name="path">The full path of the file to open.</param>
|
||||
/// <param name="mode">Specifies the access permissions of the created <see cref="IFile"/>.</param>
|
||||
/// <returns><see cref="Result.Success"/>: The operation was successful.<br/>
|
||||
/// <see cref="ResultFs.PathNotFound"/>: The specified path does not exist or is a directory.<br/>
|
||||
/// <see cref="ResultFs.TargetLocked"/>: When opening as <see cref="OpenMode.Write"/>,
|
||||
/// the file is already opened as <see cref="OpenMode.Write"/>.</returns>
|
||||
public Result OpenFile(out IFile file, in Path path, OpenMode mode)
|
||||
{
|
||||
if ((mode & OpenMode.ReadWrite) == 0 || (mode & ~OpenMode.All) != 0)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out file);
|
||||
return ResultFs.InvalidOpenMode.Log();
|
||||
}
|
||||
|
||||
if ((mode & ~OpenMode.All) != 0)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out file);
|
||||
return ResultFs.InvalidOpenMode.Log();
|
||||
}
|
||||
|
||||
return DoOpenFile(out file, path, mode);
|
||||
return DoOpenFile(out file, in path, mode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -296,33 +246,18 @@ namespace LibHac.Fs.Fsa
|
||||
/// An <see cref="IDirectory"/> instance for the specified directory.</param>
|
||||
/// <param name="path">The directory's full path.</param>
|
||||
/// <param name="mode">Specifies which sub-entries should be enumerated.</param>
|
||||
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
|
||||
/// <remarks>
|
||||
/// The following <see cref="Result"/> codes may be returned under certain conditions:
|
||||
///
|
||||
/// The specified path does not exist or is a file: <see cref="ResultFs.PathNotFound"/>
|
||||
/// </remarks>
|
||||
public Result OpenDirectory(out IDirectory directory, U8Span path, OpenDirectoryMode mode)
|
||||
/// <returns><see cref="Result.Success"/>: The operation was successful.<br/>
|
||||
/// <see cref="ResultFs.PathNotFound"/>: The specified path does not exist or is a file.</returns>
|
||||
public Result OpenDirectory(out IDirectory directory, in Path path, OpenDirectoryMode mode)
|
||||
{
|
||||
if (path.IsNull())
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out directory);
|
||||
return ResultFs.NullptrArgument.Log();
|
||||
}
|
||||
|
||||
if ((mode & OpenDirectoryMode.All) == 0)
|
||||
if ((mode & OpenDirectoryMode.All) == 0 ||
|
||||
(mode & ~(OpenDirectoryMode.All | OpenDirectoryMode.NoFileSize)) != 0)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out directory);
|
||||
return ResultFs.InvalidOpenMode.Log();
|
||||
}
|
||||
|
||||
if ((mode & ~(OpenDirectoryMode.All | OpenDirectoryMode.NoFileSize)) != 0)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out directory);
|
||||
return ResultFs.InvalidOpenMode.Log();
|
||||
}
|
||||
|
||||
return DoOpenDirectory(out directory, path, mode);
|
||||
return DoOpenDirectory(out directory, in path, mode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -344,21 +279,11 @@ namespace LibHac.Fs.Fsa
|
||||
/// <param name="timeStamp">If the operation returns successfully, the timestamps for the specified file or directory.
|
||||
/// These value are expressed as Unix timestamps.</param>
|
||||
/// <param name="path">The path of the file or directory.</param>
|
||||
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
|
||||
/// <remarks>
|
||||
/// The following <see cref="Result"/> codes may be returned under certain conditions:
|
||||
///
|
||||
/// The specified path does not exist: <see cref="ResultFs.PathNotFound"/>
|
||||
/// </remarks>
|
||||
public Result GetFileTimeStampRaw(out FileTimeStampRaw timeStamp, U8Span path)
|
||||
/// <returns><see cref="Result.Success"/>: The operation was successful.<br/>
|
||||
/// <see cref="ResultFs.PathNotFound"/>: The specified path does not exist.</returns>
|
||||
public Result GetFileTimeStampRaw(out FileTimeStampRaw timeStamp, in Path path)
|
||||
{
|
||||
if (path.IsNull())
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out timeStamp);
|
||||
return ResultFs.NullptrArgument.Log();
|
||||
}
|
||||
|
||||
return DoGetFileTimeStampRaw(out timeStamp, path);
|
||||
return DoGetFileTimeStampRaw(out timeStamp, in path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -373,60 +298,51 @@ namespace LibHac.Fs.Fsa
|
||||
/// <param name="queryId">The type of query to perform.</param>
|
||||
/// <param name="path">The full path of the file to query.</param>
|
||||
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
|
||||
public Result QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, QueryId queryId, U8Span path)
|
||||
public Result QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, QueryId queryId, in Path path)
|
||||
{
|
||||
if (path.IsNull())
|
||||
return ResultFs.NullptrArgument.Log();
|
||||
|
||||
return DoQueryEntry(outBuffer, inBuffer, queryId, path);
|
||||
}
|
||||
|
||||
protected abstract Result DoCreateFile(U8Span path, long size, CreateFileOptions option);
|
||||
protected abstract Result DoDeleteFile(U8Span path);
|
||||
protected abstract Result DoCreateDirectory(U8Span path);
|
||||
protected abstract Result DoDeleteDirectory(U8Span path);
|
||||
protected abstract Result DoDeleteDirectoryRecursively(U8Span path);
|
||||
protected abstract Result DoCleanDirectoryRecursively(U8Span path);
|
||||
protected abstract Result DoRenameFile(U8Span oldPath, U8Span newPath);
|
||||
protected abstract Result DoRenameDirectory(U8Span oldPath, U8Span newPath);
|
||||
protected abstract Result DoGetEntryType(out DirectoryEntryType entryType, U8Span path);
|
||||
protected abstract Result DoCreateFile(in Path path, long size, CreateFileOptions option);
|
||||
protected abstract Result DoDeleteFile(in Path path);
|
||||
protected abstract Result DoCreateDirectory(in Path path);
|
||||
protected abstract Result DoDeleteDirectory(in Path path);
|
||||
protected abstract Result DoDeleteDirectoryRecursively(in Path path);
|
||||
protected abstract Result DoCleanDirectoryRecursively(in Path path);
|
||||
protected abstract Result DoRenameFile(in Path currentPath, in Path newPath);
|
||||
protected abstract Result DoRenameDirectory(in Path currentPath, in Path newPath);
|
||||
protected abstract Result DoGetEntryType(out DirectoryEntryType entryType, in Path path);
|
||||
|
||||
protected virtual Result DoGetFreeSpaceSize(out long freeSpace, U8Span path)
|
||||
protected virtual Result DoGetFreeSpaceSize(out long freeSpace, in Path path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out freeSpace);
|
||||
return ResultFs.NotImplemented.Log();
|
||||
}
|
||||
|
||||
protected virtual Result DoGetTotalSpaceSize(out long totalSpace, U8Span path)
|
||||
protected virtual Result DoGetTotalSpaceSize(out long totalSpace, in Path path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out totalSpace);
|
||||
return ResultFs.NotImplemented.Log();
|
||||
}
|
||||
|
||||
protected abstract Result DoOpenFile(out IFile file, U8Span path, OpenMode mode);
|
||||
protected abstract Result DoOpenDirectory(out IDirectory directory, U8Span path, OpenDirectoryMode mode);
|
||||
protected abstract Result DoOpenFile(out IFile file, in Path path, OpenMode mode);
|
||||
protected abstract Result DoOpenDirectory(out IDirectory directory, in Path path, OpenDirectoryMode mode);
|
||||
protected abstract Result DoCommit();
|
||||
|
||||
protected virtual Result DoCommitProvisionally(long counter) => ResultFs.NotImplemented.Log();
|
||||
protected virtual Result DoRollback() => ResultFs.NotImplemented.Log();
|
||||
protected virtual Result DoFlush() => ResultFs.NotImplemented.Log();
|
||||
|
||||
protected virtual Result DoGetFileTimeStampRaw(out FileTimeStampRaw timeStamp, U8Span path)
|
||||
protected virtual Result DoGetFileTimeStampRaw(out FileTimeStampRaw timeStamp, in Path path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out timeStamp);
|
||||
return ResultFs.NotImplemented.Log();
|
||||
}
|
||||
|
||||
protected virtual Result DoQueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, QueryId queryId,
|
||||
U8Span path) => ResultFs.NotImplemented.Log();
|
||||
in Path path) => ResultFs.NotImplemented.Log();
|
||||
|
||||
protected virtual void Dispose(bool disposing) { }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
public virtual void Dispose() { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -460,6 +376,9 @@ namespace LibHac.Fs.Fsa
|
||||
/// Turns a folder in a <see cref="ConcatenationFileSystem"/> into a concatenation file by
|
||||
/// setting the directory's archive flag.
|
||||
/// </summary>
|
||||
MakeConcatFile = 0
|
||||
SetConcatenationFileAttribute = 0,
|
||||
UpdateMac = 1,
|
||||
IsSignedSystemPartitionOnSdCardValid = 2,
|
||||
QueryUnpreparedFileInformation = 3
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ namespace LibHac.Fs.Fsa
|
||||
int mountLen = 0;
|
||||
int maxMountLen = Math.Min(path.Length, PathTools.MountNameLengthMax);
|
||||
|
||||
if (WindowsPath.IsWindowsDrive(path) || WindowsPath.IsUnc(path))
|
||||
if (WindowsPath.IsWindowsDrive(path) || WindowsPath.IsUncPath(path))
|
||||
{
|
||||
StringUtils.Copy(mountName.Name, CommonPaths.HostRootFileSystemMountName);
|
||||
mountName.Name[PathTools.MountNameLengthMax] = StringTraits.NullTerminator;
|
||||
@ -192,7 +192,7 @@ namespace LibHac.Fs.Fsa
|
||||
{
|
||||
rc = fs.Impl.Unmount(mountName);
|
||||
}
|
||||
fs.Impl.LogErrorMessage(rc);
|
||||
fs.Impl.LogResultErrorMessage(rc);
|
||||
Abort.DoAbortUnless(rc.IsSuccess());
|
||||
}
|
||||
|
||||
@ -218,7 +218,7 @@ namespace LibHac.Fs.Fsa
|
||||
{
|
||||
rc = fs.Impl.IsMounted(out isMounted, mountName);
|
||||
}
|
||||
fs.Impl.LogErrorMessage(rc);
|
||||
fs.Impl.LogResultErrorMessage(rc);
|
||||
Abort.DoAbortUnless(rc.IsSuccess());
|
||||
|
||||
return isMounted;
|
||||
|
@ -18,7 +18,7 @@ namespace LibHac.Fs.Fsa
|
||||
{
|
||||
public static Result Register(this FileSystemClient fs, U8Span name, IFileSystem fileSystem)
|
||||
{
|
||||
var accessor = new FileSystemAccessor(fs, name, null, fileSystem, null, null);
|
||||
var accessor = new FileSystemAccessor(fs.Hos, name, null, fileSystem, null, null);
|
||||
fs.Impl.Register(accessor);
|
||||
|
||||
return Result.Success;
|
||||
@ -27,7 +27,7 @@ namespace LibHac.Fs.Fsa
|
||||
public static Result Register(this FileSystemClient fs, U8Span name, IFileSystem fileSystem,
|
||||
ICommonMountNameGenerator mountNameGenerator)
|
||||
{
|
||||
var accessor = new FileSystemAccessor(fs, name, null, fileSystem, mountNameGenerator, null);
|
||||
var accessor = new FileSystemAccessor(fs.Hos, name, null, fileSystem, mountNameGenerator, null);
|
||||
fs.Impl.Register(accessor);
|
||||
|
||||
return Result.Success;
|
||||
@ -44,7 +44,7 @@ namespace LibHac.Fs.Fsa
|
||||
IFileSystem fileSystem, ICommonMountNameGenerator mountNameGenerator,
|
||||
ISaveDataAttributeGetter saveAttributeGetter, bool useDataCache, bool usePathCache)
|
||||
{
|
||||
var accessor = new FileSystemAccessor(fs, name, multiCommitTarget, fileSystem, mountNameGenerator,
|
||||
var accessor = new FileSystemAccessor(fs.Hos, name, multiCommitTarget, fileSystem, mountNameGenerator,
|
||||
saveAttributeGetter);
|
||||
|
||||
accessor.SetFileDataCacheAttachable(useDataCache);
|
||||
|
@ -530,7 +530,7 @@ namespace LibHac.Fs.Fsa
|
||||
|
||||
public static Result OpenFile(this FileSystemClient fs, out FileHandle handle, IFile file, OpenMode mode)
|
||||
{
|
||||
var accessor = new FileAccessor(fs, ref file, null, mode);
|
||||
var accessor = new FileAccessor(fs.Hos, ref file, null, mode);
|
||||
handle = new FileHandle(accessor);
|
||||
|
||||
return Result.Success;
|
||||
|
@ -73,7 +73,7 @@ namespace LibHac.Fs.Fsa
|
||||
fs.Impl.AbortIfNeeded(rc);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = fileSystem.QueryEntry(Span<byte>.Empty, ReadOnlySpan<byte>.Empty, QueryId.MakeConcatFile, subPath);
|
||||
rc = fileSystem.QueryEntry(Span<byte>.Empty, ReadOnlySpan<byte>.Empty, QueryId.SetConcatenationFileAttribute, subPath);
|
||||
fs.Impl.AbortIfNeeded(rc);
|
||||
return rc;
|
||||
}
|
||||
|
@ -1,43 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using LibHac.Sf;
|
||||
using IDirectory = LibHac.Fs.Fsa.IDirectory;
|
||||
using IDirectorySf = LibHac.FsSrv.Sf.IDirectory;
|
||||
|
||||
namespace LibHac.Fs.Impl
|
||||
{
|
||||
/// <summary>
|
||||
/// An adapter for using an <see cref="IDirectorySf"/> service object as an <see cref="IDirectory"/>. Used
|
||||
/// when receiving a Horizon IPC directory object so it can be used as an <see cref="IDirectory"/> locally.
|
||||
/// </summary>
|
||||
internal class DirectoryServiceObjectAdapter : IDirectory
|
||||
{
|
||||
private ReferenceCountedDisposable<IDirectorySf> BaseDirectory { get; }
|
||||
|
||||
public DirectoryServiceObjectAdapter(ReferenceCountedDisposable<IDirectorySf> baseDirectory)
|
||||
{
|
||||
BaseDirectory = baseDirectory.AddReference();
|
||||
}
|
||||
|
||||
protected override Result DoRead(out long entriesRead, Span<DirectoryEntry> entryBuffer)
|
||||
{
|
||||
Span<byte> buffer = MemoryMarshal.Cast<DirectoryEntry, byte>(entryBuffer);
|
||||
return BaseDirectory.Target.Read(out entriesRead, new OutBuffer(buffer));
|
||||
}
|
||||
|
||||
protected override Result DoGetEntryCount(out long entryCount)
|
||||
{
|
||||
return BaseDirectory.Target.GetEntryCount(out entryCount);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
BaseDirectory?.Dispose();
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,77 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using LibHac.Common;
|
||||
using LibHac.Sf;
|
||||
using IFile = LibHac.Fs.Fsa.IFile;
|
||||
using IFileSf = LibHac.FsSrv.Sf.IFile;
|
||||
|
||||
namespace LibHac.Fs.Impl
|
||||
{
|
||||
/// <summary>
|
||||
/// An adapter for using an <see cref="IFileSf"/> service object as an <see cref="IFile"/>. Used
|
||||
/// when receiving a Horizon IPC file object so it can be used as an <see cref="IFile"/> locally.
|
||||
/// </summary>
|
||||
internal class FileServiceObjectAdapter : IFile
|
||||
{
|
||||
private ReferenceCountedDisposable<IFileSf> BaseFile { get; }
|
||||
|
||||
public FileServiceObjectAdapter(ReferenceCountedDisposable<IFileSf> baseFile)
|
||||
{
|
||||
BaseFile = baseFile.AddReference();
|
||||
}
|
||||
|
||||
protected override Result DoRead(out long bytesRead, long offset, Span<byte> destination, in ReadOption option)
|
||||
{
|
||||
return BaseFile.Target.Read(out bytesRead, offset, new OutBuffer(destination), destination.Length, option);
|
||||
}
|
||||
|
||||
protected override Result DoWrite(long offset, ReadOnlySpan<byte> source, in WriteOption option)
|
||||
{
|
||||
return BaseFile.Target.Write(offset, new InBuffer(source), source.Length, option);
|
||||
}
|
||||
|
||||
protected override Result DoFlush()
|
||||
{
|
||||
return BaseFile.Target.Flush();
|
||||
}
|
||||
|
||||
protected override Result DoSetSize(long size)
|
||||
{
|
||||
return BaseFile.Target.SetSize(size);
|
||||
}
|
||||
|
||||
protected override Result DoGetSize(out long size)
|
||||
{
|
||||
return BaseFile.Target.GetSize(out size);
|
||||
}
|
||||
|
||||
protected override Result DoOperateRange(Span<byte> outBuffer, OperationId operationId, long offset, long size,
|
||||
ReadOnlySpan<byte> inBuffer)
|
||||
{
|
||||
switch (operationId)
|
||||
{
|
||||
case OperationId.InvalidateCache:
|
||||
return BaseFile.Target.OperateRange(out _, (int)OperationId.InvalidateCache, offset, size);
|
||||
case OperationId.QueryRange:
|
||||
if (outBuffer.Length != Unsafe.SizeOf<QueryRangeInfo>())
|
||||
return ResultFs.InvalidSize.Log();
|
||||
|
||||
ref QueryRangeInfo info = ref SpanHelpers.AsStruct<QueryRangeInfo>(outBuffer);
|
||||
|
||||
return BaseFile.Target.OperateRange(out info, (int)OperationId.QueryRange, offset, size);
|
||||
default:
|
||||
return ResultFs.UnsupportedOperateRangeForFileServiceObjectAdapter.Log();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
BaseFile?.Dispose();
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,231 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.Util;
|
||||
using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem;
|
||||
using IFileSf = LibHac.FsSrv.Sf.IFile;
|
||||
using IDirectorySf = LibHac.FsSrv.Sf.IDirectory;
|
||||
using PathSf = LibHac.FsSrv.Sf.Path;
|
||||
|
||||
namespace LibHac.Fs.Impl
|
||||
{
|
||||
/// <summary>
|
||||
/// An adapter for using an <see cref="IFileSystemSf"/> service object as an <see cref="Fsa.IFileSystem"/>. Used
|
||||
/// when receiving a Horizon IPC file system object so it can be used as an <see cref="Fsa.IFileSystem"/> locally.
|
||||
/// </summary>
|
||||
internal class FileSystemServiceObjectAdapter : IFileSystem, IMultiCommitTarget
|
||||
{
|
||||
private ReferenceCountedDisposable<IFileSystemSf> BaseFs { get; }
|
||||
|
||||
public FileSystemServiceObjectAdapter(ReferenceCountedDisposable<IFileSystemSf> baseFileSystem)
|
||||
{
|
||||
BaseFs = baseFileSystem.AddReference();
|
||||
}
|
||||
|
||||
protected override Result DoCreateFile(U8Span path, long size, CreateFileOptions option)
|
||||
{
|
||||
Result rc = GetPathForServiceObject(out PathSf sfPath, path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return BaseFs.Target.CreateFile(in sfPath, size, (int)option);
|
||||
}
|
||||
|
||||
protected override Result DoDeleteFile(U8Span path)
|
||||
{
|
||||
Result rc = GetPathForServiceObject(out PathSf sfPath, path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return BaseFs.Target.DeleteFile(in sfPath);
|
||||
}
|
||||
|
||||
protected override Result DoCreateDirectory(U8Span path)
|
||||
{
|
||||
Result rc = GetPathForServiceObject(out PathSf sfPath, path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return BaseFs.Target.DeleteFile(in sfPath);
|
||||
}
|
||||
|
||||
protected override Result DoDeleteDirectory(U8Span path)
|
||||
{
|
||||
Result rc = GetPathForServiceObject(out PathSf sfPath, path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return BaseFs.Target.DeleteDirectory(in sfPath);
|
||||
}
|
||||
|
||||
protected override Result DoDeleteDirectoryRecursively(U8Span path)
|
||||
{
|
||||
Result rc = GetPathForServiceObject(out PathSf sfPath, path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return BaseFs.Target.DeleteDirectoryRecursively(in sfPath);
|
||||
}
|
||||
|
||||
protected override Result DoCleanDirectoryRecursively(U8Span path)
|
||||
{
|
||||
Result rc = GetPathForServiceObject(out PathSf sfPath, path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return BaseFs.Target.CleanDirectoryRecursively(in sfPath);
|
||||
}
|
||||
|
||||
protected override Result DoRenameFile(U8Span oldPath, U8Span newPath)
|
||||
{
|
||||
Result rc = GetPathForServiceObject(out PathSf oldSfPath, oldPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = GetPathForServiceObject(out PathSf newSfPath, newPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return BaseFs.Target.RenameFile(in oldSfPath, in newSfPath);
|
||||
}
|
||||
|
||||
protected override Result DoRenameDirectory(U8Span oldPath, U8Span newPath)
|
||||
{
|
||||
Result rc = GetPathForServiceObject(out PathSf oldSfPath, oldPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = GetPathForServiceObject(out PathSf newSfPath, newPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return BaseFs.Target.RenameDirectory(in oldSfPath, in newSfPath);
|
||||
}
|
||||
|
||||
protected override Result DoGetEntryType(out DirectoryEntryType entryType, U8Span path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out entryType);
|
||||
|
||||
Result rc = GetPathForServiceObject(out PathSf sfPath, path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
ref uint sfEntryType = ref Unsafe.As<DirectoryEntryType, uint>(ref entryType);
|
||||
|
||||
return BaseFs.Target.GetEntryType(out sfEntryType, in sfPath);
|
||||
}
|
||||
|
||||
protected override Result DoGetFreeSpaceSize(out long freeSpace, U8Span path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out freeSpace);
|
||||
|
||||
Result rc = GetPathForServiceObject(out PathSf sfPath, path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return BaseFs.Target.GetFreeSpaceSize(out freeSpace, in sfPath);
|
||||
}
|
||||
|
||||
protected override Result DoGetTotalSpaceSize(out long totalSpace, U8Span path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out totalSpace);
|
||||
|
||||
Result rc = GetPathForServiceObject(out PathSf sfPath, path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return BaseFs.Target.GetTotalSpaceSize(out totalSpace, in sfPath);
|
||||
}
|
||||
|
||||
protected override Result DoOpenFile(out IFile file, U8Span path, OpenMode mode)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out file);
|
||||
|
||||
Result rc = GetPathForServiceObject(out PathSf sfPath, path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
ReferenceCountedDisposable<IFileSf> sfFile = null;
|
||||
try
|
||||
{
|
||||
rc = BaseFs.Target.OpenFile(out sfFile, in sfPath, (uint)mode);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
file = new FileServiceObjectAdapter(sfFile);
|
||||
return Result.Success;
|
||||
}
|
||||
finally
|
||||
{
|
||||
sfFile?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
protected override Result DoOpenDirectory(out IDirectory directory, U8Span path, OpenDirectoryMode mode)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out directory);
|
||||
|
||||
Result rc = GetPathForServiceObject(out PathSf sfPath, path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
ReferenceCountedDisposable<IDirectorySf> sfDir = null;
|
||||
try
|
||||
{
|
||||
rc = BaseFs.Target.OpenDirectory(out sfDir, in sfPath, (uint)mode);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
directory = new DirectoryServiceObjectAdapter(sfDir);
|
||||
return Result.Success;
|
||||
}
|
||||
finally
|
||||
{
|
||||
sfDir?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
protected override Result DoCommit()
|
||||
{
|
||||
return BaseFs.Target.Commit();
|
||||
}
|
||||
|
||||
protected override Result DoGetFileTimeStampRaw(out FileTimeStampRaw timeStamp, U8Span path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out timeStamp);
|
||||
|
||||
Result rc = GetPathForServiceObject(out PathSf sfPath, path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return BaseFs.Target.GetFileTimeStampRaw(out timeStamp, in sfPath);
|
||||
}
|
||||
|
||||
protected override Result DoQueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, QueryId queryId,
|
||||
U8Span path)
|
||||
{
|
||||
Result rc = GetPathForServiceObject(out PathSf sfPath, path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return BaseFs.Target.QueryEntry(outBuffer, inBuffer, (int)queryId, in sfPath);
|
||||
}
|
||||
|
||||
public ReferenceCountedDisposable<IFileSystemSf> GetMultiCommitTarget()
|
||||
{
|
||||
return BaseFs.AddReference();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
BaseFs?.Dispose();
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
private Result GetPathForServiceObject(out PathSf sfPath, U8Span path)
|
||||
{
|
||||
// This is the function used to create Sf.Path structs. Get an unsafe byte span for init only.
|
||||
UnsafeHelpers.SkipParamInit(out sfPath);
|
||||
Span<byte> outPath = SpanHelpers.AsByteSpan(ref sfPath);
|
||||
|
||||
// Copy and null terminate
|
||||
StringUtils.Copy(outPath, path);
|
||||
outPath[Unsafe.SizeOf<PathSf>() - 1] = StringTraits.NullTerminator;
|
||||
|
||||
// Replace directory separators
|
||||
PathUtility.Replace(outPath, StringTraits.AltDirectorySeparator, StringTraits.DirectorySeparator);
|
||||
|
||||
// Get lengths
|
||||
int windowsSkipLength = WindowsPath.GetWindowsPathSkipLength(path);
|
||||
var nonWindowsPath = new U8Span(sfPath.Str.Slice(windowsSkipLength));
|
||||
int maxLength = PathTool.EntryNameLengthMax - windowsSkipLength;
|
||||
return PathUtility.VerifyPath(null, nonWindowsPath, maxLength, maxLength);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.FsSystem;
|
||||
@ -21,106 +20,70 @@ namespace LibHac.Fs
|
||||
FsTable = new FileTable();
|
||||
}
|
||||
|
||||
protected override Result DoCreateDirectory(U8Span path)
|
||||
protected override Result DoCreateDirectory(in Path path)
|
||||
{
|
||||
Unsafe.SkipInit(out FsPath normalizedPath);
|
||||
Result rc = PathNormalizer.Normalize(normalizedPath.Str, out _, path, false, false);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return FsTable.AddDirectory(normalizedPath);
|
||||
return FsTable.AddDirectory(new U8Span(path.GetString()));
|
||||
}
|
||||
|
||||
protected override Result DoCreateDirectory(U8Span path, NxFileAttributes archiveAttribute)
|
||||
protected override Result DoCreateDirectory(in Path path, NxFileAttributes archiveAttribute)
|
||||
{
|
||||
Unsafe.SkipInit(out FsPath normalizedPath);
|
||||
Result rc = PathNormalizer.Normalize(normalizedPath.Str, out _, path, false, false);
|
||||
Result rc = FsTable.AddDirectory(new U8Span(path.GetString()));
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = FsTable.AddDirectory(normalizedPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = FsTable.GetDirectory(normalizedPath, out DirectoryNode dir);
|
||||
rc = FsTable.GetDirectory(new U8Span(path.GetString()), out DirectoryNode dir);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
dir.Attributes = archiveAttribute;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoCreateFile(U8Span path, long size, CreateFileOptions options)
|
||||
protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option)
|
||||
{
|
||||
Unsafe.SkipInit(out FsPath normalizedPath);
|
||||
Result rc = PathNormalizer.Normalize(normalizedPath.Str, out _, path, false, false);
|
||||
Result rc = FsTable.AddFile(new U8Span(path.GetString()));
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = FsTable.AddFile(normalizedPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = FsTable.GetFile(normalizedPath, out FileNode file);
|
||||
rc = FsTable.GetFile(new U8Span(path.GetString()), out FileNode file);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return file.File.SetSize(size);
|
||||
}
|
||||
|
||||
protected override Result DoDeleteDirectory(U8Span path)
|
||||
protected override Result DoDeleteDirectory(in Path path)
|
||||
{
|
||||
Unsafe.SkipInit(out FsPath normalizedPath);
|
||||
Result rc = PathNormalizer.Normalize(normalizedPath.Str, out _, path, false, false);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return FsTable.DeleteDirectory(normalizedPath, false);
|
||||
return FsTable.DeleteDirectory(new U8Span(path.GetString()), false);
|
||||
}
|
||||
|
||||
protected override Result DoDeleteDirectoryRecursively(U8Span path)
|
||||
protected override Result DoDeleteDirectoryRecursively(in Path path)
|
||||
{
|
||||
Unsafe.SkipInit(out FsPath normalizedPath);
|
||||
Result rc = PathNormalizer.Normalize(normalizedPath.Str, out _, path, false, false);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return FsTable.DeleteDirectory(normalizedPath, true);
|
||||
return FsTable.DeleteDirectory(new U8Span(path.GetString()), true);
|
||||
}
|
||||
|
||||
protected override Result DoCleanDirectoryRecursively(U8Span path)
|
||||
protected override Result DoCleanDirectoryRecursively(in Path path)
|
||||
{
|
||||
Unsafe.SkipInit(out FsPath normalizedPath);
|
||||
Result rc = PathNormalizer.Normalize(normalizedPath.Str, out _, path, false, false);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return FsTable.CleanDirectory(normalizedPath);
|
||||
return FsTable.CleanDirectory(new U8Span(path.GetString()));
|
||||
}
|
||||
|
||||
protected override Result DoDeleteFile(U8Span path)
|
||||
protected override Result DoDeleteFile(in Path path)
|
||||
{
|
||||
Unsafe.SkipInit(out FsPath normalizedPath);
|
||||
Result rc = PathNormalizer.Normalize(normalizedPath.Str, out _, path, false, false);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return FsTable.DeleteFile(normalizedPath);
|
||||
return FsTable.DeleteFile(new U8Span(path.GetString()));
|
||||
}
|
||||
|
||||
protected override Result DoOpenDirectory(out IDirectory directory, U8Span path, OpenDirectoryMode mode)
|
||||
protected override Result DoOpenDirectory(out IDirectory directory, in Path path, OpenDirectoryMode mode)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out directory);
|
||||
|
||||
Unsafe.SkipInit(out FsPath normalizedPath);
|
||||
Result rc = PathNormalizer.Normalize(normalizedPath.Str, out _, path, false, false);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = FsTable.GetDirectory(normalizedPath, out DirectoryNode dirNode);
|
||||
Result rc = FsTable.GetDirectory(new U8Span(path.GetString()), out DirectoryNode dirNode);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
directory = new MemoryDirectory(dirNode, mode);
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoOpenFile(out IFile file, U8Span path, OpenMode mode)
|
||||
protected override Result DoOpenFile(out IFile file, in Path path, OpenMode mode)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out file);
|
||||
|
||||
Unsafe.SkipInit(out FsPath normalizedPath);
|
||||
Result rc = PathNormalizer.Normalize(normalizedPath.Str, out _, path, false, false);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = FsTable.GetFile(normalizedPath, out FileNode fileNode);
|
||||
Result rc = FsTable.GetFile(new U8Span(path.GetString()), out FileNode fileNode);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
file = new MemoryFile(mode, fileNode.File);
|
||||
@ -128,49 +91,27 @@ namespace LibHac.Fs
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoRenameDirectory(U8Span currentPath, U8Span newPath)
|
||||
protected override Result DoRenameDirectory(in Path currentPath, in Path newPath)
|
||||
{
|
||||
Unsafe.SkipInit(out FsPath normalizedCurrentPath);
|
||||
Unsafe.SkipInit(out FsPath normalizedNewPath);
|
||||
|
||||
Result rc = PathNormalizer.Normalize(normalizedCurrentPath.Str, out _, currentPath, false, false);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = PathNormalizer.Normalize(normalizedNewPath.Str, out _, newPath, false, false);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return FsTable.RenameDirectory(normalizedCurrentPath, normalizedNewPath);
|
||||
return FsTable.RenameDirectory(new U8Span(currentPath.GetString()), new U8Span(newPath.GetString()));
|
||||
}
|
||||
|
||||
protected override Result DoRenameFile(U8Span currentPath, U8Span newPath)
|
||||
protected override Result DoRenameFile(in Path currentPath, in Path newPath)
|
||||
{
|
||||
Unsafe.SkipInit(out FsPath normalizedCurrentPath);
|
||||
Unsafe.SkipInit(out FsPath normalizedNewPath);
|
||||
|
||||
Result rc = PathNormalizer.Normalize(normalizedCurrentPath.Str, out _, currentPath, false, false);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = PathNormalizer.Normalize(normalizedNewPath.Str, out _, newPath, false, false);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return FsTable.RenameFile(normalizedCurrentPath, normalizedNewPath);
|
||||
return FsTable.RenameFile(new U8Span(currentPath.GetString()), new U8Span(newPath.GetString()));
|
||||
}
|
||||
|
||||
protected override Result DoGetEntryType(out DirectoryEntryType entryType, U8Span path)
|
||||
protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out entryType);
|
||||
|
||||
Unsafe.SkipInit(out FsPath normalizedPath);
|
||||
Result rc = PathNormalizer.Normalize(normalizedPath.Str, out _, path, false, false);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (FsTable.GetFile(normalizedPath, out _).IsSuccess())
|
||||
if (FsTable.GetFile(new U8Span(path.GetString()), out _).IsSuccess())
|
||||
{
|
||||
entryType = DirectoryEntryType.File;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
if (FsTable.GetDirectory(normalizedPath, out _).IsSuccess())
|
||||
if (FsTable.GetDirectory(new U8Span(path.GetString()), out _).IsSuccess())
|
||||
{
|
||||
entryType = DirectoryEntryType.Directory;
|
||||
return Result.Success;
|
||||
@ -184,21 +125,17 @@ namespace LibHac.Fs
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoGetFileAttributes(out NxFileAttributes attributes, U8Span path)
|
||||
protected override Result DoGetFileAttributes(out NxFileAttributes attributes, in Path path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out attributes);
|
||||
|
||||
Unsafe.SkipInit(out FsPath normalizedPath);
|
||||
Result rc = PathNormalizer.Normalize(normalizedPath.Str, out _, path, false, false);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (FsTable.GetFile(normalizedPath, out FileNode file).IsSuccess())
|
||||
if (FsTable.GetFile(new U8Span(path.GetString()), out FileNode file).IsSuccess())
|
||||
{
|
||||
attributes = file.Attributes;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
if (FsTable.GetDirectory(normalizedPath, out DirectoryNode dir).IsSuccess())
|
||||
if (FsTable.GetDirectory(new U8Span(path.GetString()), out DirectoryNode dir).IsSuccess())
|
||||
{
|
||||
attributes = dir.Attributes;
|
||||
return Result.Success;
|
||||
@ -207,19 +144,15 @@ namespace LibHac.Fs
|
||||
return ResultFs.PathNotFound.Log();
|
||||
}
|
||||
|
||||
protected override Result DoSetFileAttributes(U8Span path, NxFileAttributes attributes)
|
||||
protected override Result DoSetFileAttributes(in Path path, NxFileAttributes attributes)
|
||||
{
|
||||
Unsafe.SkipInit(out FsPath normalizedPath);
|
||||
Result rc = PathNormalizer.Normalize(normalizedPath.Str, out _, path, false, false);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (FsTable.GetFile(normalizedPath, out FileNode file).IsSuccess())
|
||||
if (FsTable.GetFile(new U8Span(path.GetString()), out FileNode file).IsSuccess())
|
||||
{
|
||||
file.Attributes = attributes;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
if (FsTable.GetDirectory(normalizedPath, out DirectoryNode dir).IsSuccess())
|
||||
if (FsTable.GetDirectory(new U8Span(path.GetString()), out DirectoryNode dir).IsSuccess())
|
||||
{
|
||||
dir.Attributes = attributes;
|
||||
return Result.Success;
|
||||
@ -228,15 +161,11 @@ namespace LibHac.Fs
|
||||
return ResultFs.PathNotFound.Log();
|
||||
}
|
||||
|
||||
protected override Result DoGetFileSize(out long fileSize, U8Span path)
|
||||
protected override Result DoGetFileSize(out long fileSize, in Path path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out fileSize);
|
||||
|
||||
Unsafe.SkipInit(out FsPath normalizedPath);
|
||||
Result rc = PathNormalizer.Normalize(normalizedPath.Str, out _, path, false, false);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (FsTable.GetFile(normalizedPath, out FileNode file).IsSuccess())
|
||||
if (FsTable.GetFile(new U8Span(path.GetString()), out FileNode file).IsSuccess())
|
||||
{
|
||||
return file.File.GetSize(out fileSize);
|
||||
}
|
||||
|
@ -42,8 +42,18 @@ namespace LibHac.Fs
|
||||
}
|
||||
}
|
||||
|
||||
public static void LogErrorMessage(this FileSystemClientImpl fs, Result result,
|
||||
[CallerMemberName] string functionName = "")
|
||||
public static void LogErrorMessage(this FileSystemClientImpl fs, Result result, string message)
|
||||
{
|
||||
// Todo
|
||||
}
|
||||
|
||||
public static void LogErrorMessage(this FileSystemClientImpl fs, Result result, string format, object arg0)
|
||||
{
|
||||
// Todo
|
||||
}
|
||||
|
||||
public static void LogErrorMessage(this FileSystemClientImpl fs, Result result, string format,
|
||||
params object[] args)
|
||||
{
|
||||
// Todo
|
||||
}
|
||||
|
@ -157,13 +157,13 @@ namespace LibHac.Fs.Shim
|
||||
|
||||
public static Result SetBisRootForHost(this FileSystemClient fs, BisPartitionId partitionId, U8Span rootPath)
|
||||
{
|
||||
Unsafe.SkipInit(out FsPath path);
|
||||
Unsafe.SkipInit(out FsPath pathBuffer);
|
||||
Result rc;
|
||||
|
||||
int pathLen = StringUtils.GetLength(rootPath, PathTools.MaxPathLength + 1);
|
||||
if (pathLen > PathTools.MaxPathLength)
|
||||
{
|
||||
fs.Impl.LogErrorMessage(ResultFs.TooLongPath.Value);
|
||||
fs.Impl.LogResultErrorMessage(ResultFs.TooLongPath.Value);
|
||||
return ResultFs.TooLongPath.Log();
|
||||
}
|
||||
|
||||
@ -173,21 +173,24 @@ namespace LibHac.Fs.Shim
|
||||
? StringTraits.NullTerminator
|
||||
: StringTraits.DirectorySeparator;
|
||||
|
||||
var sb = new U8StringBuilder(path.Str);
|
||||
rc = sb.Append(rootPath).Append(endingSeparator).ToSfPath();
|
||||
if (rc.IsFailure()) return rc;
|
||||
var sb = new U8StringBuilder(pathBuffer.Str);
|
||||
sb.Append(rootPath).Append(endingSeparator);
|
||||
|
||||
if (sb.Overflowed)
|
||||
return ResultFs.TooLongPath.Log();
|
||||
}
|
||||
else
|
||||
{
|
||||
path.Str[0] = StringTraits.NullTerminator;
|
||||
pathBuffer.Str[0] = StringTraits.NullTerminator;
|
||||
}
|
||||
|
||||
FspPath.FromSpan(out FspPath sfPath, path.Str);
|
||||
rc = PathUtility.ConvertToFspPath(out FspPath sfPath, pathBuffer.Str);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
using ReferenceCountedDisposable<IFileSystemProxy> fsProxy = fs.Impl.GetFileSystemProxyServiceObject();
|
||||
|
||||
rc = fsProxy.Target.SetBisRootForHost(partitionId, in sfPath);
|
||||
fs.Impl.LogErrorMessage(rc);
|
||||
fs.Impl.LogResultErrorMessage(rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
333
src/LibHac/Fs/Shim/FileSystemServiceObjectAdapter.cs
Normal file
333
src/LibHac/Fs/Shim/FileSystemServiceObjectAdapter.cs
Normal file
@ -0,0 +1,333 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.Sf;
|
||||
using LibHac.Util;
|
||||
|
||||
using IFile = LibHac.Fs.Fsa.IFile;
|
||||
using IFileSf = LibHac.FsSrv.Sf.IFile;
|
||||
using IDirectory = LibHac.Fs.Fsa.IDirectory;
|
||||
using IDirectorySf = LibHac.FsSrv.Sf.IDirectory;
|
||||
using IFileSystem = LibHac.Fs.Fsa.IFileSystem;
|
||||
using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem;
|
||||
using PathSf = LibHac.FsSrv.Sf.Path;
|
||||
|
||||
// ReSharper disable CheckNamespace
|
||||
namespace LibHac.Fs.Impl
|
||||
{
|
||||
/// <summary>
|
||||
/// An adapter for using an <see cref="IFileSf"/> service object as an <see cref="IFile"/>. Used
|
||||
/// when receiving a Horizon IPC file object so it can be used as an <see cref="IFile"/> locally.
|
||||
/// </summary>
|
||||
/// <remarks>Based on FS 12.0.3 (nnSdk 12.3.1)</remarks>
|
||||
internal class FileServiceObjectAdapter : IFile
|
||||
{
|
||||
private ReferenceCountedDisposable<IFileSf> BaseFile { get; }
|
||||
|
||||
public FileServiceObjectAdapter(ReferenceCountedDisposable<IFileSf> baseFile)
|
||||
{
|
||||
BaseFile = baseFile.AddReference();
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
BaseFile?.Dispose();
|
||||
|
||||
base.Dispose();
|
||||
}
|
||||
|
||||
protected override Result DoRead(out long bytesRead, long offset, Span<byte> destination, in ReadOption option)
|
||||
{
|
||||
return BaseFile.Target.Read(out bytesRead, offset, new OutBuffer(destination), destination.Length, option);
|
||||
}
|
||||
|
||||
protected override Result DoWrite(long offset, ReadOnlySpan<byte> source, in WriteOption option)
|
||||
{
|
||||
return BaseFile.Target.Write(offset, new InBuffer(source), source.Length, option);
|
||||
}
|
||||
|
||||
protected override Result DoFlush()
|
||||
{
|
||||
return BaseFile.Target.Flush();
|
||||
}
|
||||
|
||||
protected override Result DoSetSize(long size)
|
||||
{
|
||||
return BaseFile.Target.SetSize(size);
|
||||
}
|
||||
|
||||
protected override Result DoGetSize(out long size)
|
||||
{
|
||||
return BaseFile.Target.GetSize(out size);
|
||||
}
|
||||
|
||||
protected override Result DoOperateRange(Span<byte> outBuffer, OperationId operationId, long offset, long size,
|
||||
ReadOnlySpan<byte> inBuffer)
|
||||
{
|
||||
switch (operationId)
|
||||
{
|
||||
case OperationId.InvalidateCache:
|
||||
return BaseFile.Target.OperateRange(out _, (int)OperationId.InvalidateCache, offset, size);
|
||||
case OperationId.QueryRange:
|
||||
if (outBuffer.Length != Unsafe.SizeOf<QueryRangeInfo>())
|
||||
return ResultFs.InvalidSize.Log();
|
||||
|
||||
ref QueryRangeInfo info = ref SpanHelpers.AsStruct<QueryRangeInfo>(outBuffer);
|
||||
|
||||
return BaseFile.Target.OperateRange(out info, (int)OperationId.QueryRange, offset, size);
|
||||
default:
|
||||
return BaseFile.Target.OperateRangeWithBuffer(new OutBuffer(outBuffer), new InBuffer(inBuffer),
|
||||
(int)operationId, offset, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An adapter for using an <see cref="IDirectorySf"/> service object as an <see cref="IDirectory"/>. Used
|
||||
/// when receiving a Horizon IPC directory object so it can be used as an <see cref="IDirectory"/> locally.
|
||||
/// </summary>
|
||||
/// <remarks>Based on FS 12.0.3 (nnSdk 12.3.1)</remarks>
|
||||
internal class DirectoryServiceObjectAdapter : IDirectory
|
||||
{
|
||||
private ReferenceCountedDisposable<IDirectorySf> BaseDirectory { get; }
|
||||
|
||||
public DirectoryServiceObjectAdapter(ReferenceCountedDisposable<IDirectorySf> baseDirectory)
|
||||
{
|
||||
BaseDirectory = baseDirectory.AddReference();
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
BaseDirectory?.Dispose();
|
||||
|
||||
base.Dispose();
|
||||
}
|
||||
|
||||
protected override Result DoRead(out long entriesRead, Span<DirectoryEntry> entryBuffer)
|
||||
{
|
||||
Span<byte> buffer = MemoryMarshal.Cast<DirectoryEntry, byte>(entryBuffer);
|
||||
return BaseDirectory.Target.Read(out entriesRead, new OutBuffer(buffer));
|
||||
}
|
||||
|
||||
protected override Result DoGetEntryCount(out long entryCount)
|
||||
{
|
||||
return BaseDirectory.Target.GetEntryCount(out entryCount);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An adapter for using an <see cref="IFileSystemSf"/> service object as an <see cref="IFileSystem"/>. Used
|
||||
/// when receiving a Horizon IPC file system object so it can be used as an <see cref="IFileSystem"/> locally.
|
||||
/// </summary>
|
||||
/// <remarks>Based on FS 12.0.3 (nnSdk 12.3.1)</remarks>
|
||||
internal class FileSystemServiceObjectAdapter : IFileSystem, IMultiCommitTarget
|
||||
{
|
||||
private ReferenceCountedDisposable<IFileSystemSf> BaseFs { get; }
|
||||
|
||||
private static Result GetPathForServiceObject(out PathSf sfPath, in Path path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out sfPath);
|
||||
|
||||
int length = StringUtils.Copy(SpanHelpers.AsByteSpan(ref sfPath), path.GetString(),
|
||||
PathTool.EntryNameLengthMax + 1);
|
||||
|
||||
if (length > PathTool.EntryNameLengthMax)
|
||||
return ResultFs.TooLongPath.Log();
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public FileSystemServiceObjectAdapter(ReferenceCountedDisposable<IFileSystemSf> baseFileSystem)
|
||||
{
|
||||
BaseFs = baseFileSystem.AddReference();
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
BaseFs?.Dispose();
|
||||
base.Dispose();
|
||||
}
|
||||
|
||||
protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option)
|
||||
{
|
||||
Result rc = GetPathForServiceObject(out PathSf sfPath, path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return BaseFs.Target.CreateFile(in sfPath, size, (int)option);
|
||||
}
|
||||
|
||||
protected override Result DoDeleteFile(in Path path)
|
||||
{
|
||||
Result rc = GetPathForServiceObject(out PathSf sfPath, path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return BaseFs.Target.DeleteFile(in sfPath);
|
||||
}
|
||||
|
||||
protected override Result DoCreateDirectory(in Path path)
|
||||
{
|
||||
Result rc = GetPathForServiceObject(out PathSf sfPath, path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return BaseFs.Target.DeleteFile(in sfPath);
|
||||
}
|
||||
|
||||
protected override Result DoDeleteDirectory(in Path path)
|
||||
{
|
||||
Result rc = GetPathForServiceObject(out PathSf sfPath, path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return BaseFs.Target.DeleteDirectory(in sfPath);
|
||||
}
|
||||
|
||||
protected override Result DoDeleteDirectoryRecursively(in Path path)
|
||||
{
|
||||
Result rc = GetPathForServiceObject(out PathSf sfPath, path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return BaseFs.Target.DeleteDirectoryRecursively(in sfPath);
|
||||
}
|
||||
|
||||
protected override Result DoCleanDirectoryRecursively(in Path path)
|
||||
{
|
||||
Result rc = GetPathForServiceObject(out PathSf sfPath, path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return BaseFs.Target.CleanDirectoryRecursively(in sfPath);
|
||||
}
|
||||
|
||||
protected override Result DoRenameFile(in Path currentPath, in Path newPath)
|
||||
{
|
||||
Result rc = GetPathForServiceObject(out PathSf currentSfPath, currentPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = GetPathForServiceObject(out PathSf newSfPath, newPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return BaseFs.Target.RenameFile(in currentSfPath, in newSfPath);
|
||||
}
|
||||
|
||||
protected override Result DoRenameDirectory(in Path currentPath, in Path newPath)
|
||||
{
|
||||
Result rc = GetPathForServiceObject(out PathSf currentSfPath, currentPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = GetPathForServiceObject(out PathSf newSfPath, newPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return BaseFs.Target.RenameDirectory(in currentSfPath, in newSfPath);
|
||||
}
|
||||
|
||||
protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out entryType);
|
||||
|
||||
Result rc = GetPathForServiceObject(out PathSf sfPath, path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
ref uint sfEntryType = ref Unsafe.As<DirectoryEntryType, uint>(ref entryType);
|
||||
|
||||
return BaseFs.Target.GetEntryType(out sfEntryType, in sfPath);
|
||||
}
|
||||
|
||||
protected override Result DoGetFreeSpaceSize(out long freeSpace, in Path path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out freeSpace);
|
||||
|
||||
Result rc = GetPathForServiceObject(out PathSf sfPath, path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return BaseFs.Target.GetFreeSpaceSize(out freeSpace, in sfPath);
|
||||
}
|
||||
|
||||
protected override Result DoGetTotalSpaceSize(out long totalSpace, in Path path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out totalSpace);
|
||||
|
||||
Result rc = GetPathForServiceObject(out PathSf sfPath, path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return BaseFs.Target.GetTotalSpaceSize(out totalSpace, in sfPath);
|
||||
}
|
||||
|
||||
protected override Result DoOpenFile(out IFile file, in Path path, OpenMode mode)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out file);
|
||||
|
||||
Result rc = GetPathForServiceObject(out PathSf sfPath, path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
ReferenceCountedDisposable<IFileSf> sfFile = null;
|
||||
try
|
||||
{
|
||||
rc = BaseFs.Target.OpenFile(out sfFile, in sfPath, (uint)mode);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
file = new FileServiceObjectAdapter(sfFile);
|
||||
return Result.Success;
|
||||
}
|
||||
finally
|
||||
{
|
||||
sfFile?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
protected override Result DoOpenDirectory(out IDirectory directory, in Path path, OpenDirectoryMode mode)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out directory);
|
||||
|
||||
Result rc = GetPathForServiceObject(out PathSf sfPath, path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
ReferenceCountedDisposable<IDirectorySf> sfDir = null;
|
||||
try
|
||||
{
|
||||
rc = BaseFs.Target.OpenDirectory(out sfDir, in sfPath, (uint)mode);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
directory = new DirectoryServiceObjectAdapter(sfDir);
|
||||
return Result.Success;
|
||||
}
|
||||
finally
|
||||
{
|
||||
sfDir?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
protected override Result DoCommit()
|
||||
{
|
||||
return BaseFs.Target.Commit();
|
||||
}
|
||||
|
||||
protected override Result DoGetFileTimeStampRaw(out FileTimeStampRaw timeStamp, in Path path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out timeStamp);
|
||||
|
||||
Result rc = GetPathForServiceObject(out PathSf sfPath, path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return BaseFs.Target.GetFileTimeStampRaw(out timeStamp, in sfPath);
|
||||
}
|
||||
|
||||
protected override Result DoQueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, QueryId queryId,
|
||||
in Path path)
|
||||
{
|
||||
Result rc = GetPathForServiceObject(out PathSf sfPath, path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return BaseFs.Target.QueryEntry(new OutBuffer(outBuffer), new InBuffer(inBuffer), (int)queryId, in sfPath);
|
||||
}
|
||||
|
||||
public ReferenceCountedDisposable<IFileSystemSf> GetFileSystem()
|
||||
{
|
||||
return BaseFs.AddReference();
|
||||
}
|
||||
|
||||
public ReferenceCountedDisposable<IFileSystemSf> GetMultiCommitTarget()
|
||||
{
|
||||
return GetFileSystem();
|
||||
}
|
||||
}
|
||||
}
|
@ -157,11 +157,11 @@ namespace LibHac.Fs.Shim
|
||||
try
|
||||
{
|
||||
Result rc = fsProxy.Target.OpenDeviceOperator(out deviceOperator);
|
||||
fs.Impl.LogErrorMessage(rc);
|
||||
fs.Impl.LogResultErrorMessage(rc);
|
||||
Abort.DoAbortUnless(rc.IsSuccess());
|
||||
|
||||
rc = deviceOperator.Target.IsGameCardInserted(out bool isInserted);
|
||||
fs.Impl.LogErrorMessage(rc);
|
||||
fs.Impl.LogResultErrorMessage(rc);
|
||||
Abort.DoAbortUnless(rc.IsSuccess());
|
||||
|
||||
return isInserted;
|
||||
|
@ -508,7 +508,7 @@ namespace LibHac.Fs.Shim
|
||||
{
|
||||
rc = fs.Impl.Unmount(mountName);
|
||||
}
|
||||
fs.Impl.LogErrorMessage(rc);
|
||||
fs.Impl.LogResultErrorMessage(rc);
|
||||
Abort.DoAbortUnless(rc.IsSuccess());
|
||||
}
|
||||
}
|
||||
|
@ -1788,7 +1788,7 @@ namespace LibHac.Fs.Shim
|
||||
|
||||
Result rc = fsProxy.Target.DisableAutoSaveDataCreation();
|
||||
|
||||
fs.Impl.LogErrorMessage(rc);
|
||||
fs.Impl.LogResultErrorMessage(rc);
|
||||
Abort.DoAbortUnless(rc.IsSuccess());
|
||||
}
|
||||
|
||||
|
@ -136,11 +136,11 @@ namespace LibHac.Fs.Shim
|
||||
try
|
||||
{
|
||||
Result rc = fsProxy.Target.OpenDeviceOperator(out deviceOperator);
|
||||
fs.Impl.LogErrorMessage(rc);
|
||||
fs.Impl.LogResultErrorMessage(rc);
|
||||
Abort.DoAbortUnless(rc.IsSuccess());
|
||||
|
||||
rc = CheckIfInserted(fs, deviceOperator, out bool isInserted);
|
||||
fs.Impl.LogErrorMessage(rc);
|
||||
fs.Impl.LogResultErrorMessage(rc);
|
||||
Abort.DoAbortUnless(rc.IsSuccess());
|
||||
|
||||
return isInserted;
|
||||
@ -191,7 +191,7 @@ namespace LibHac.Fs.Shim
|
||||
public static void SetSdCardAccessibility(this FileSystemClient fs, bool isAccessible)
|
||||
{
|
||||
Result rc = fs.Impl.SetSdCardAccessibility(isAccessible);
|
||||
fs.Impl.LogErrorMessage(rc);
|
||||
fs.Impl.LogResultErrorMessage(rc);
|
||||
Abort.DoAbortUnless(rc.IsSuccess());
|
||||
}
|
||||
|
||||
@ -200,7 +200,7 @@ namespace LibHac.Fs.Shim
|
||||
using ReferenceCountedDisposable<IFileSystemProxy> fsProxy = fs.Impl.GetFileSystemProxyServiceObject();
|
||||
|
||||
Result rc = fsProxy.Target.IsSdCardAccessible(out bool isAccessible);
|
||||
fs.Impl.LogErrorMessage(rc);
|
||||
fs.Impl.LogResultErrorMessage(rc);
|
||||
Abort.DoAbortUnless(rc.IsSuccess());
|
||||
|
||||
return isAccessible;
|
||||
|
@ -3,10 +3,11 @@ using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.FsSrv.Impl;
|
||||
using LibHac.FsSrv.Sf;
|
||||
using LibHac.FsSystem;
|
||||
using LibHac.Sf;
|
||||
using IFileSystem = LibHac.Fs.Fsa.IFileSystem;
|
||||
using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem;
|
||||
using PathNormalizer = LibHac.FsSrv.Impl.PathNormalizer;
|
||||
using Path = LibHac.Fs.Path;
|
||||
|
||||
namespace LibHac.FsSrv
|
||||
{
|
||||
@ -73,7 +74,7 @@ namespace LibHac.FsSrv
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
// Create an SF adapter for the file system
|
||||
fileSystem = FileSystemInterfaceAdapter.CreateShared(ref fs);
|
||||
fileSystem = FileSystemInterfaceAdapter.CreateShared(ref fs, false);
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
@ -83,10 +84,10 @@ namespace LibHac.FsSrv
|
||||
}
|
||||
}
|
||||
|
||||
public Result OpenBisFileSystem(out ReferenceCountedDisposable<IFileSystemSf> fileSystem, in FspPath rootPath,
|
||||
public Result OpenBisFileSystem(out ReferenceCountedDisposable<IFileSystemSf> outFileSystem, in FspPath rootPath,
|
||||
BisPartitionId partitionId)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out fileSystem);
|
||||
UnsafeHelpers.SkipParamInit(out outFileSystem);
|
||||
|
||||
Result rc = GetProgramInfo(out ProgramInfo programInfo);
|
||||
if (rc.IsFailure()) return rc;
|
||||
@ -112,27 +113,44 @@ namespace LibHac.FsSrv
|
||||
if (!accessibility.CanRead || !accessibility.CanWrite)
|
||||
return ResultFs.PermissionDenied.Log();
|
||||
|
||||
// Normalize the path
|
||||
using var normalizer = new PathNormalizer(rootPath, PathNormalizer.Option.AcceptEmpty);
|
||||
if (normalizer.Result.IsFailure()) return normalizer.Result;
|
||||
const StorageType storageFlag = StorageType.Bis;
|
||||
using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag);
|
||||
|
||||
ReferenceCountedDisposable<IFileSystem> fs = null;
|
||||
// Normalize the path
|
||||
var pathNormalized = new Path();
|
||||
rc = pathNormalized.Initialize(rootPath.Str);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
var pathFlags = new PathFlags();
|
||||
pathFlags.AllowEmptyPath();
|
||||
rc = pathNormalized.Normalize(pathFlags);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
ReferenceCountedDisposable<IFileSystem> baseFileSystem = null;
|
||||
ReferenceCountedDisposable<IFileSystem> fileSystem = null;
|
||||
|
||||
try
|
||||
{
|
||||
// Open the file system
|
||||
rc = _serviceImpl.OpenBisFileSystem(out fs, normalizer.Path,
|
||||
partitionId);
|
||||
rc = _serviceImpl.OpenBisFileSystem(out baseFileSystem, partitionId, false);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = Utility.CreateSubDirectoryFileSystem(out fileSystem, ref baseFileSystem, in pathNormalized);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
// Add all the file system wrappers
|
||||
fileSystem = StorageLayoutTypeSetFileSystem.CreateShared(ref fileSystem, storageFlag);
|
||||
fileSystem = AsynchronousAccessFileSystem.CreateShared(ref fileSystem);
|
||||
|
||||
// Create an SF adapter for the file system
|
||||
fileSystem = FileSystemInterfaceAdapter.CreateShared(ref fs);
|
||||
outFileSystem = FileSystemInterfaceAdapter.CreateShared(ref fileSystem, false);
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
finally
|
||||
{
|
||||
fs?.Dispose();
|
||||
baseFileSystem?.Dispose();
|
||||
fileSystem?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@ -188,7 +206,7 @@ namespace LibHac.FsSrv
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
// Create an SF adapter for the file system
|
||||
fileSystem = FileSystemInterfaceAdapter.CreateShared(ref fs);
|
||||
fileSystem = FileSystemInterfaceAdapter.CreateShared(ref fs, false);
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
@ -218,7 +236,7 @@ namespace LibHac.FsSrv
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
// Create an SF adapter for the file system
|
||||
fileSystem = FileSystemInterfaceAdapter.CreateShared(ref fs);
|
||||
fileSystem = FileSystemInterfaceAdapter.CreateShared(ref fs, false);
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
@ -291,7 +309,7 @@ namespace LibHac.FsSrv
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
// Create an SF adapter for the file system
|
||||
fileSystem = FileSystemInterfaceAdapter.CreateShared(ref fs);
|
||||
fileSystem = FileSystemInterfaceAdapter.CreateShared(ref fs, false);
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.FsSrv.FsCreator;
|
||||
using LibHac.FsSrv.Impl;
|
||||
@ -41,21 +40,18 @@ namespace LibHac.FsSrv
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Result OpenBisFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem, U8Span rootPath,
|
||||
public Result OpenBisFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem,
|
||||
BisPartitionId partitionId)
|
||||
{
|
||||
return OpenBisFileSystem(out fileSystem, rootPath, partitionId, false);
|
||||
return OpenBisFileSystem(out fileSystem, partitionId, false);
|
||||
}
|
||||
|
||||
public Result OpenBisFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem, U8Span rootPath,
|
||||
public Result OpenBisFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem,
|
||||
BisPartitionId partitionId, bool caseSensitive)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out fileSystem);
|
||||
|
||||
Result rc = _config.BisFileSystemCreator.Create(out IFileSystem fs, rootPath.ToString(), partitionId);
|
||||
Result rc = _config.BisFileSystemCreator.Create(out fileSystem, partitionId, caseSensitive);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
fileSystem = new ReferenceCountedDisposable<IFileSystem>(fs);
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
@ -95,14 +91,7 @@ namespace LibHac.FsSrv
|
||||
|
||||
public Result OpenSdCardProxyFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem, bool openCaseSensitive)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out fileSystem);
|
||||
|
||||
// Todo: Shared
|
||||
Result rc = _config.SdCardFileSystemCreator.Create(out IFileSystem fs, openCaseSensitive);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
fileSystem = new ReferenceCountedDisposable<IFileSystem>(fs);
|
||||
return Result.Success;
|
||||
return _config.SdCardFileSystemCreator.Create(out fileSystem, openCaseSensitive);
|
||||
}
|
||||
|
||||
public Result FormatSdCardProxyFileSystem()
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
@ -26,118 +27,119 @@ namespace LibHac.FsSrv
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Result OpenCustomStorageFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem,
|
||||
public Result OpenCustomStorageFileSystem(out ReferenceCountedDisposable<IFileSystem> outFileSystem,
|
||||
CustomStorageId storageId)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out fileSystem);
|
||||
UnsafeHelpers.SkipParamInit(out outFileSystem);
|
||||
|
||||
ReferenceCountedDisposable<IFileSystem> fileSystem = null;
|
||||
ReferenceCountedDisposable<IFileSystem> tempFs = null;
|
||||
ReferenceCountedDisposable<IFileSystem> encryptedFs = null;
|
||||
try
|
||||
{
|
||||
Span<byte> path = stackalloc byte[0x40];
|
||||
const int pathBufferLength = 0x40;
|
||||
|
||||
switch (storageId)
|
||||
// Hack around error CS8350.
|
||||
Span<byte> buffer = stackalloc byte[pathBufferLength];
|
||||
ref byte bufferRef = ref MemoryMarshal.GetReference(buffer);
|
||||
Span<byte> pathBuffer = MemoryMarshal.CreateSpan(ref bufferRef, pathBufferLength);
|
||||
|
||||
if (storageId == CustomStorageId.System)
|
||||
{
|
||||
case CustomStorageId.SdCard:
|
||||
{
|
||||
Result rc = BaseFileSystemService.OpenSdCardProxyFileSystem(out tempFs);
|
||||
if (rc.IsFailure()) return rc;
|
||||
Result rc = BaseFileSystemService.OpenBisFileSystem(out fileSystem, BisPartitionId.User);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
U8Span customStorageDir = CustomStorage.GetCustomStorageDirectoryName(CustomStorageId.SdCard);
|
||||
var sb = new U8StringBuilder(path);
|
||||
sb.Append((byte)'/')
|
||||
.Append(CommonPaths.SdCardNintendoRootDirectoryName)
|
||||
.Append((byte)'/')
|
||||
.Append(customStorageDir);
|
||||
var path = new Path();
|
||||
var sb = new U8StringBuilder(pathBuffer);
|
||||
sb.Append((byte)'/')
|
||||
.Append(CustomStorage.GetCustomStorageDirectoryName(CustomStorageId.System));
|
||||
|
||||
rc = Utility.WrapSubDirectory(out tempFs, ref tempFs, new U8Span(path), true);
|
||||
if (rc.IsFailure()) return rc;
|
||||
rc = PathFunctions.SetUpFixedPath(ref path, pathBuffer);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = FsCreators.EncryptedFileSystemCreator.Create(out encryptedFs, tempFs,
|
||||
EncryptedFsKeyId.CustomStorage, SdEncryptionSeed);
|
||||
if (rc.IsFailure()) return rc;
|
||||
tempFs = Shared.Move(ref fileSystem);
|
||||
rc = Utility.WrapSubDirectory(out fileSystem, ref tempFs, in path, true);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
case CustomStorageId.System:
|
||||
{
|
||||
Result rc = BaseFileSystemService.OpenBisFileSystem(out tempFs, U8Span.Empty,
|
||||
BisPartitionId.User);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
U8Span customStorageDir = CustomStorage.GetCustomStorageDirectoryName(CustomStorageId.System);
|
||||
var sb = new U8StringBuilder(path);
|
||||
sb.Append((byte)'/')
|
||||
.Append(customStorageDir);
|
||||
|
||||
rc = Utility.WrapSubDirectory(out tempFs, ref tempFs, new U8Span(path), true);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
fileSystem = Shared.Move(ref tempFs);
|
||||
return Result.Success;
|
||||
}
|
||||
default:
|
||||
return ResultFs.InvalidArgument.Log();
|
||||
path.Dispose();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
tempFs?.Dispose();
|
||||
encryptedFs?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public Result OpenHostFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem, U8Span path,
|
||||
bool openCaseSensitive)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out fileSystem);
|
||||
Result rc;
|
||||
|
||||
if (!path.IsEmpty())
|
||||
{
|
||||
rc = Util.VerifyHostPath(path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
|
||||
// Todo: Return shared fs from Create
|
||||
rc = FsCreators.TargetManagerFileSystemCreator.Create(out IFileSystem hostFs, openCaseSensitive);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
ReferenceCountedDisposable<IFileSystem> sharedHostFs = null;
|
||||
ReferenceCountedDisposable<IFileSystem> subDirFs = null;
|
||||
|
||||
try
|
||||
{
|
||||
sharedHostFs = new ReferenceCountedDisposable<IFileSystem>(hostFs);
|
||||
|
||||
if (path.IsEmpty())
|
||||
else if (storageId == CustomStorageId.SdCard)
|
||||
{
|
||||
ReadOnlySpan<byte> rootHostPath = new[] { (byte)'C', (byte)':', (byte)'/' };
|
||||
rc = sharedHostFs.Target.GetEntryType(out _, new U8Span(rootHostPath));
|
||||
Result rc = BaseFileSystemService.OpenSdCardProxyFileSystem(out fileSystem);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
// Nintendo ignores all results other than this one
|
||||
if (ResultFs.TargetNotFound.Includes(rc))
|
||||
return rc;
|
||||
var path = new Path();
|
||||
var sb = new U8StringBuilder(pathBuffer);
|
||||
sb.Append((byte)'/')
|
||||
.Append(CommonPaths.SdCardNintendoRootDirectoryName)
|
||||
.Append((byte)'/')
|
||||
.Append(CustomStorage.GetCustomStorageDirectoryName(CustomStorageId.SdCard));
|
||||
|
||||
Shared.Move(out fileSystem, ref sharedHostFs);
|
||||
return Result.Success;
|
||||
rc = PathFunctions.SetUpFixedPath(ref path, pathBuffer);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
tempFs = Shared.Move(ref fileSystem);
|
||||
rc = Utility.WrapSubDirectory(out fileSystem, ref tempFs, in path, true);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
tempFs = Shared.Move(ref fileSystem);
|
||||
rc = FsCreators.EncryptedFileSystemCreator.Create(out fileSystem, ref tempFs,
|
||||
IEncryptedFileSystemCreator.KeyId.CustomStorage, SdEncryptionSeed);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
path.Dispose();
|
||||
}
|
||||
else
|
||||
{
|
||||
return ResultFs.InvalidArgument.Log();
|
||||
}
|
||||
|
||||
rc = FsCreators.SubDirectoryFileSystemCreator.Create(out subDirFs, ref sharedHostFs, path,
|
||||
preserveUnc: true);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
fileSystem = subDirFs;
|
||||
outFileSystem = Shared.Move(ref fileSystem);
|
||||
return Result.Success;
|
||||
}
|
||||
finally
|
||||
{
|
||||
sharedHostFs?.Dispose();
|
||||
subDirFs?.Dispose();
|
||||
fileSystem?.Dispose();
|
||||
tempFs?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private Result OpenHostFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem, in Path path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out fileSystem);
|
||||
|
||||
var pathHost = new Path();
|
||||
Result rc = pathHost.Initialize(in path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = FsCreators.TargetManagerFileSystemCreator.NormalizeCaseOfPath(out bool isSupported, ref pathHost);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = FsCreators.TargetManagerFileSystemCreator.Create(out fileSystem, in pathHost, isSupported, false,
|
||||
Result.Success);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
pathHost.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result OpenHostFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem, in Path path,
|
||||
bool openCaseSensitive)
|
||||
{
|
||||
if (!path.IsEmpty() && openCaseSensitive)
|
||||
{
|
||||
Result rc = OpenHostFileSystem(out fileSystem, in path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
else
|
||||
{
|
||||
Result rc = FsCreators.TargetManagerFileSystemCreator.Create(out fileSystem, in path, openCaseSensitive,
|
||||
false, Result.Success);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result SetSdCardEncryptionSeed(in EncryptionSeed seed)
|
||||
{
|
||||
SdEncryptionSeed = seed;
|
||||
|
@ -11,6 +11,8 @@ using IFileSystem = LibHac.Fs.Fsa.IFileSystem;
|
||||
using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem;
|
||||
using IFileSf = LibHac.FsSrv.Sf.IFile;
|
||||
using IStorageSf = LibHac.FsSrv.Sf.IStorage;
|
||||
using Path = LibHac.Fs.Path;
|
||||
using static LibHac.Fs.StringTraits;
|
||||
|
||||
namespace LibHac.FsSrv
|
||||
{
|
||||
@ -525,25 +527,47 @@ namespace LibHac.FsSrv
|
||||
if (!accessibility.CanRead || !accessibility.CanWrite)
|
||||
return ResultFs.PermissionDenied.Log();
|
||||
|
||||
ReferenceCountedDisposable<IFileSystem> hostFs = null;
|
||||
ReferenceCountedDisposable<IFileSystem> hostFileSystem = null;
|
||||
try
|
||||
{
|
||||
rc = FsProxyCore.OpenHostFileSystem(out hostFs, new U8Span(path.Str),
|
||||
option.Flags.HasFlag(MountHostOptionFlag.PseudoCaseSensitive));
|
||||
var pathNormalized = new Path();
|
||||
|
||||
if (path.Str.At(0) == DirectorySeparator && path.Str.At(1) != DirectorySeparator)
|
||||
{
|
||||
rc = pathNormalized.Initialize(path.Str.Slice(1));
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = pathNormalized.InitializeWithReplaceUnc(path.Str);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
|
||||
var flags = new PathFlags();
|
||||
flags.AllowWindowsPath();
|
||||
flags.AllowRelativePath();
|
||||
flags.AllowEmptyPath();
|
||||
|
||||
rc = pathNormalized.Normalize(flags);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
bool isRootPath = path.Str[0] == 0;
|
||||
bool isCaseSensitive = option.Flags.HasFlag(MountHostOptionFlag.PseudoCaseSensitive);
|
||||
|
||||
fileSystem = FileSystemInterfaceAdapter.CreateShared(ref hostFs, isRootPath);
|
||||
rc = FsProxyCore.OpenHostFileSystem(out hostFileSystem, in pathNormalized, isCaseSensitive);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (fileSystem is null)
|
||||
return ResultFs.AllocationMemoryFailedCreateShared.Log();
|
||||
var adapterFlags = new PathFlags();
|
||||
if (path.Str.At(0) == NullTerminator)
|
||||
adapterFlags.AllowWindowsPath();
|
||||
|
||||
fileSystem = FileSystemInterfaceAdapter.CreateShared(ref hostFileSystem, adapterFlags, false);
|
||||
|
||||
pathNormalized.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
finally
|
||||
{
|
||||
hostFs?.Dispose();
|
||||
hostFileSystem?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@ -878,7 +902,7 @@ namespace LibHac.FsSrv
|
||||
|
||||
tempFs = StorageLayoutTypeSetFileSystem.CreateShared(ref tempFs, storageFlag);
|
||||
tempFs = AsynchronousAccessFileSystem.CreateShared(ref tempFs);
|
||||
fileSystem = FileSystemInterfaceAdapter.CreateShared(ref tempFs);
|
||||
fileSystem = FileSystemInterfaceAdapter.CreateShared(ref tempFs, false);
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
@ -915,7 +939,7 @@ namespace LibHac.FsSrv
|
||||
|
||||
customFs = StorageLayoutTypeSetFileSystem.CreateShared(ref customFs, storageFlag);
|
||||
customFs = AsynchronousAccessFileSystem.CreateShared(ref customFs);
|
||||
fileSystem = FileSystemInterfaceAdapter.CreateShared(ref customFs);
|
||||
fileSystem = FileSystemInterfaceAdapter.CreateShared(ref customFs, false);
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
@ -31,6 +31,8 @@ namespace LibHac.FsSrv
|
||||
public AccessControlGlobals AccessControl;
|
||||
public StorageDeviceManagerFactoryGlobals StorageDeviceManagerFactory;
|
||||
public SaveDataSharedFileStorageGlobals SaveDataSharedFileStorage;
|
||||
public MultiCommitManagerGlobals MultiCommitManager;
|
||||
public LocationResolverSetGlobals LocationResolverSet;
|
||||
|
||||
public void Initialize(HorizonClient horizonClient, FileSystemServer fsServer)
|
||||
{
|
||||
@ -38,6 +40,8 @@ namespace LibHac.FsSrv
|
||||
InitMutex = new object();
|
||||
|
||||
SaveDataSharedFileStorage.Initialize(fsServer);
|
||||
MultiCommitManager.Initialize();
|
||||
LocationResolverSet.Initialize();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -54,47 +54,13 @@ namespace LibHac.FsSrv.FsCreator
|
||||
Config = config;
|
||||
}
|
||||
|
||||
public Result Create(out IFileSystem fileSystem, string rootPath, BisPartitionId partitionId)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out fileSystem);
|
||||
|
||||
if (!IsValidPartitionId(partitionId)) return ResultFs.InvalidArgument.Log();
|
||||
if (rootPath == null) return ResultFs.NullptrArgument.Log();
|
||||
|
||||
if (Config.TryGetFileSystem(out fileSystem, partitionId))
|
||||
{
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
if (Config.RootFileSystem == null)
|
||||
{
|
||||
return ResultFs.PreconditionViolation.Log();
|
||||
}
|
||||
|
||||
string partitionPath = GetPartitionPath(partitionId);
|
||||
|
||||
Result rc =
|
||||
Util.CreateSubFileSystem(out IFileSystem subFileSystem, Config.RootFileSystem, partitionPath, true);
|
||||
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (rootPath == string.Empty)
|
||||
{
|
||||
fileSystem = subFileSystem;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
return Util.CreateSubFileSystemImpl(out fileSystem, subFileSystem, rootPath);
|
||||
}
|
||||
|
||||
// Todo: Make case sensitive
|
||||
public Result Create(out ReferenceCountedDisposable<IFileSystem> fileSystem, U8Span rootPath,
|
||||
BisPartitionId partitionId, bool caseSensitive)
|
||||
public Result Create(out ReferenceCountedDisposable<IFileSystem> fileSystem, BisPartitionId partitionId,
|
||||
bool caseSensitive)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out fileSystem);
|
||||
|
||||
if (!IsValidPartitionId(partitionId)) return ResultFs.InvalidArgument.Log();
|
||||
if (rootPath.IsNull()) return ResultFs.NullptrArgument.Log();
|
||||
|
||||
if (Config.TryGetFileSystem(out IFileSystem fs, partitionId))
|
||||
{
|
||||
@ -107,7 +73,14 @@ namespace LibHac.FsSrv.FsCreator
|
||||
return ResultFs.PreconditionViolation.Log();
|
||||
}
|
||||
|
||||
var partitionPath = GetPartitionPath(partitionId).ToU8String();
|
||||
var bisRootPath = new Path();
|
||||
Result rc = bisRootPath.Initialize(GetPartitionPath(partitionId).ToU8String());
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
var pathFlags = new PathFlags();
|
||||
pathFlags.AllowEmptyPath();
|
||||
rc = bisRootPath.Normalize(pathFlags);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
ReferenceCountedDisposable<IFileSystem> partitionFileSystem = null;
|
||||
ReferenceCountedDisposable<IFileSystem> sharedRootFs = null;
|
||||
@ -115,17 +88,11 @@ namespace LibHac.FsSrv.FsCreator
|
||||
{
|
||||
sharedRootFs = new ReferenceCountedDisposable<IFileSystem>(Config.RootFileSystem);
|
||||
|
||||
Result rc = Utility.WrapSubDirectory(out partitionFileSystem, ref sharedRootFs, partitionPath, true);
|
||||
|
||||
rc = Utility.WrapSubDirectory(out partitionFileSystem, ref sharedRootFs, in bisRootPath, true);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (rootPath.IsEmpty())
|
||||
{
|
||||
Shared.Move(out fileSystem, ref partitionFileSystem);
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
return Utility.CreateSubDirectoryFileSystem(out fileSystem, ref partitionFileSystem, rootPath);
|
||||
Shared.Move(out fileSystem, ref partitionFileSystem);
|
||||
return Result.Success;
|
||||
}
|
||||
finally
|
||||
{
|
||||
@ -134,13 +101,7 @@ namespace LibHac.FsSrv.FsCreator
|
||||
}
|
||||
}
|
||||
|
||||
public Result CreateFatFileSystem(out IFileSystem fileSystem, BisPartitionId partitionId)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out fileSystem);
|
||||
return ResultFs.NotImplemented.Log();
|
||||
}
|
||||
|
||||
public Result SetBisRootForHost(BisPartitionId partitionId, string rootPath)
|
||||
public Result SetBisRoot(BisPartitionId partitionId, string rootPath)
|
||||
{
|
||||
return Config.SetPath(rootPath, partitionId);
|
||||
}
|
||||
|
@ -2,64 +2,98 @@
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.FsSrv.Impl;
|
||||
using LibHac.Util;
|
||||
|
||||
namespace LibHac.FsSrv.FsCreator
|
||||
{
|
||||
public class EmulatedSdCardFileSystemCreator : ISdCardProxyFileSystemCreator
|
||||
public class EmulatedSdCardFileSystemCreator : ISdCardProxyFileSystemCreator, IDisposable
|
||||
{
|
||||
private const string DefaultPath = "/sdcard";
|
||||
|
||||
private EmulatedSdCard SdCard { get; }
|
||||
private IFileSystem RootFileSystem { get; }
|
||||
private ReferenceCountedDisposable<IFileSystem> _rootFileSystem;
|
||||
private string Path { get; }
|
||||
|
||||
private IFileSystem SdCardFileSystem { get; set; }
|
||||
private ReferenceCountedDisposable<IFileSystem> _sdCardFileSystem;
|
||||
|
||||
public EmulatedSdCardFileSystemCreator(EmulatedSdCard sdCard, IFileSystem rootFileSystem)
|
||||
{
|
||||
SdCard = sdCard;
|
||||
RootFileSystem = rootFileSystem;
|
||||
_rootFileSystem = new ReferenceCountedDisposable<IFileSystem>(rootFileSystem);
|
||||
}
|
||||
|
||||
public EmulatedSdCardFileSystemCreator(EmulatedSdCard sdCard, IFileSystem rootFileSystem, string path)
|
||||
{
|
||||
SdCard = sdCard;
|
||||
RootFileSystem = rootFileSystem;
|
||||
_rootFileSystem = new ReferenceCountedDisposable<IFileSystem>(rootFileSystem);
|
||||
Path = path;
|
||||
}
|
||||
|
||||
public Result Create(out IFileSystem fileSystem, bool isCaseSensitive)
|
||||
public void Dispose()
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out fileSystem);
|
||||
if (_rootFileSystem is not null)
|
||||
{
|
||||
_rootFileSystem.Dispose();
|
||||
_rootFileSystem = null;
|
||||
}
|
||||
|
||||
if (_sdCardFileSystem is not null)
|
||||
{
|
||||
_sdCardFileSystem.Dispose();
|
||||
_sdCardFileSystem = null;
|
||||
}
|
||||
}
|
||||
|
||||
public Result Create(out ReferenceCountedDisposable<IFileSystem> outFileSystem, bool isCaseSensitive)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out outFileSystem);
|
||||
|
||||
if (!SdCard.IsSdCardInserted())
|
||||
{
|
||||
return ResultFs.PortSdCardNoDevice.Log();
|
||||
}
|
||||
|
||||
if (SdCardFileSystem != null)
|
||||
if (_sdCardFileSystem is not null)
|
||||
{
|
||||
fileSystem = SdCardFileSystem;
|
||||
outFileSystem = _sdCardFileSystem.AddReference();
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
if (RootFileSystem == null)
|
||||
if (_rootFileSystem is null)
|
||||
{
|
||||
return ResultFs.PreconditionViolation.Log();
|
||||
}
|
||||
|
||||
string path = Path ?? DefaultPath;
|
||||
|
||||
// Todo: Add ProxyFileSystem?
|
||||
|
||||
Result rc = Util.CreateSubFileSystem(out IFileSystem sdFileSystem, RootFileSystem, path, true);
|
||||
var sdCardPath = new Path();
|
||||
Result rc = sdCardPath.Initialize(StringUtils.StringToUtf8(path));
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
SdCardFileSystem = sdFileSystem;
|
||||
fileSystem = sdFileSystem;
|
||||
var pathFlags = new PathFlags();
|
||||
pathFlags.AllowEmptyPath();
|
||||
rc = sdCardPath.Normalize(pathFlags);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return Result.Success;
|
||||
// Todo: Add ProxyFileSystem?
|
||||
|
||||
ReferenceCountedDisposable<IFileSystem> tempFs = null;
|
||||
try
|
||||
{
|
||||
tempFs = _rootFileSystem.AddReference();
|
||||
rc = Utility.WrapSubDirectory(out _sdCardFileSystem, ref tempFs, in sdCardPath, true);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
outFileSystem = _sdCardFileSystem.AddReference();
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
finally
|
||||
{
|
||||
tempFs?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public Result Format(bool removeFromFatFsCache)
|
||||
|
@ -3,6 +3,7 @@ using LibHac.Common.Keys;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.FsSystem;
|
||||
using static LibHac.FsSrv.FsCreator.IEncryptedFileSystemCreator;
|
||||
|
||||
namespace LibHac.FsSrv.FsCreator
|
||||
{
|
||||
@ -15,12 +16,13 @@ namespace LibHac.FsSrv.FsCreator
|
||||
KeySet = keySet;
|
||||
}
|
||||
|
||||
public Result Create(out ReferenceCountedDisposable<IFileSystem> encryptedFileSystem, ReferenceCountedDisposable<IFileSystem> baseFileSystem,
|
||||
EncryptedFsKeyId keyId, in EncryptionSeed encryptionSeed)
|
||||
public Result Create(out ReferenceCountedDisposable<IFileSystem> encryptedFileSystem,
|
||||
ref ReferenceCountedDisposable<IFileSystem> baseFileSystem, KeyId idIndex,
|
||||
in EncryptionSeed encryptionSeed)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out encryptedFileSystem);
|
||||
|
||||
if (keyId < EncryptedFsKeyId.Save || keyId > EncryptedFsKeyId.CustomStorage)
|
||||
if (idIndex < KeyId.Save || idIndex > KeyId.CustomStorage)
|
||||
{
|
||||
return ResultFs.InvalidArgument.Log();
|
||||
}
|
||||
@ -29,7 +31,8 @@ namespace LibHac.FsSrv.FsCreator
|
||||
KeySet.SetSdSeed(encryptionSeed.Value);
|
||||
|
||||
// Todo: pass ReferenceCountedDisposable to AesXtsFileSystem
|
||||
var fs = new AesXtsFileSystem(baseFileSystem, KeySet.SdCardEncryptionKeys[(int)keyId].DataRo.ToArray(), 0x4000);
|
||||
var fs = new AesXtsFileSystem(baseFileSystem, KeySet.SdCardEncryptionKeys[(int)idIndex].DataRo.ToArray(),
|
||||
0x4000);
|
||||
encryptedFileSystem = new ReferenceCountedDisposable<IFileSystem>(fs);
|
||||
|
||||
return Result.Success;
|
||||
|
@ -1,15 +1,10 @@
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
|
||||
namespace LibHac.FsSrv.FsCreator
|
||||
{
|
||||
public interface IBuiltInStorageFileSystemCreator
|
||||
{
|
||||
// Todo: Remove raw IFileSystem overload
|
||||
Result Create(out IFileSystem fileSystem, string rootPath, BisPartitionId partitionId);
|
||||
Result Create(out ReferenceCountedDisposable<IFileSystem> fileSystem, U8Span rootPath, BisPartitionId partitionId, bool caseSensitive);
|
||||
Result CreateFatFileSystem(out IFileSystem fileSystem, BisPartitionId partitionId);
|
||||
Result SetBisRootForHost(BisPartitionId partitionId, string rootPath);
|
||||
Result Create(out ReferenceCountedDisposable<IFileSystem> fileSystem, BisPartitionId partitionId, bool caseSensitive);
|
||||
}
|
||||
}
|
@ -5,8 +5,15 @@ namespace LibHac.FsSrv.FsCreator
|
||||
{
|
||||
public interface IEncryptedFileSystemCreator
|
||||
{
|
||||
public enum KeyId
|
||||
{
|
||||
Save = 0,
|
||||
Content = 1,
|
||||
CustomStorage = 2
|
||||
}
|
||||
|
||||
Result Create(out ReferenceCountedDisposable<IFileSystem> encryptedFileSystem,
|
||||
ReferenceCountedDisposable<IFileSystem> baseFileSystem, EncryptedFsKeyId keyId,
|
||||
ref ReferenceCountedDisposable<IFileSystem> baseFileSystem, KeyId idIndex,
|
||||
in EncryptionSeed encryptionSeed);
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,7 @@ namespace LibHac.FsSrv.FsCreator
|
||||
{
|
||||
public interface ISdCardProxyFileSystemCreator
|
||||
{
|
||||
Result Create(out IFileSystem fileSystem, bool isCaseSensitive);
|
||||
Result Create(out ReferenceCountedDisposable<IFileSystem> outFileSystem, bool isCaseSensitive);
|
||||
|
||||
/// <summary>
|
||||
/// Formats the SD card.
|
||||
|
@ -1,11 +1,10 @@
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
|
||||
namespace LibHac.FsSrv.FsCreator
|
||||
{
|
||||
public interface ISubDirectoryFileSystemCreator
|
||||
{
|
||||
Result Create(out ReferenceCountedDisposable<IFileSystem> subDirFileSystem, ref ReferenceCountedDisposable<IFileSystem> baseFileSystem, U8Span path);
|
||||
Result Create(out ReferenceCountedDisposable<IFileSystem> subDirFileSystem, ref ReferenceCountedDisposable<IFileSystem> baseFileSystem, U8Span path, bool preserveUnc);
|
||||
Result Create(out ReferenceCountedDisposable<IFileSystem> subDirFileSystem, ref ReferenceCountedDisposable<IFileSystem> baseFileSystem, in Path path);
|
||||
}
|
||||
}
|
@ -1,13 +1,11 @@
|
||||
using System;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
|
||||
namespace LibHac.FsSrv.FsCreator
|
||||
{
|
||||
public interface ITargetManagerFileSystemCreator
|
||||
{
|
||||
// Todo: Remove raw IFilesystem function
|
||||
Result Create(out IFileSystem fileSystem, bool openCaseSensitive);
|
||||
Result Create(out ReferenceCountedDisposable<IFileSystem> fileSystem, bool openCaseSensitive);
|
||||
Result NormalizeCaseOfPath(out bool isSupported, Span<byte> path);
|
||||
Result Create(out ReferenceCountedDisposable<IFileSystem> fileSystem, in Path rootPath, bool openCaseSensitive, bool ensureRootPathExists, Result pathNotFoundResult);
|
||||
Result NormalizeCaseOfPath(out bool isSupported, ref Path path);
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using LibHac.Common;
|
||||
using LibHac.Common.Keys;
|
||||
using LibHac.Diag;
|
||||
@ -42,15 +43,20 @@ namespace LibHac.FsSrv.FsCreator
|
||||
bool isJournalingSupported, bool isMultiCommitSupported, bool openReadOnly, bool openShared,
|
||||
ISaveDataCommitTimeStampGetter timeStampGetter)
|
||||
{
|
||||
Span<byte> saveImageName = stackalloc byte[0x12];
|
||||
// Hack around error CS8350.
|
||||
Span<byte> buffer = stackalloc byte[0x12];
|
||||
ref byte bufferRef = ref MemoryMarshal.GetReference(buffer);
|
||||
Span<byte> saveImageNameBuffer = MemoryMarshal.CreateSpan(ref bufferRef, 0x12);
|
||||
|
||||
UnsafeHelpers.SkipParamInit(out fileSystem, out extraDataAccessor);
|
||||
|
||||
Assert.SdkRequiresNotNull(cacheManager);
|
||||
|
||||
var sb = new U8StringBuilder(saveImageName);
|
||||
sb.Append((byte)'/').AppendFormat(saveDataId, 'x', 16);
|
||||
var saveImageName = new Path();
|
||||
Result rc = PathFunctions.SetUpFixedPathSaveId(ref saveImageName, saveImageNameBuffer, saveDataId);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
Result rc = baseFileSystem.Target.GetEntryType(out DirectoryEntryType type, new U8Span(saveImageName));
|
||||
rc = baseFileSystem.Target.GetEntryType(out DirectoryEntryType type, in saveImageName);
|
||||
|
||||
if (rc.IsFailure())
|
||||
{
|
||||
@ -68,7 +74,7 @@ namespace LibHac.FsSrv.FsCreator
|
||||
{
|
||||
subDirFs = new SubdirectoryFileSystem(ref baseFileSystem);
|
||||
|
||||
rc = subDirFs.Initialize(new U8Span(saveImageName));
|
||||
rc = subDirFs.Initialize(in saveImageName);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
saveFs = DirectorySaveDataFileSystem.CreateShared(Shared.Move(ref subDirFs), _fsServer.Hos.Fs);
|
||||
@ -80,6 +86,7 @@ namespace LibHac.FsSrv.FsCreator
|
||||
fileSystem = saveFs.AddReference<IFileSystem>();
|
||||
extraDataAccessor = saveFs.AddReference<ISaveDataExtraDataAccessor>();
|
||||
|
||||
saveImageName.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
finally
|
||||
|
@ -1,4 +1,5 @@
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.FsSystem;
|
||||
|
||||
@ -7,29 +8,31 @@ namespace LibHac.FsSrv.FsCreator
|
||||
public class SubDirectoryFileSystemCreator : ISubDirectoryFileSystemCreator
|
||||
{
|
||||
public Result Create(out ReferenceCountedDisposable<IFileSystem> subDirFileSystem,
|
||||
ref ReferenceCountedDisposable<IFileSystem> baseFileSystem, U8Span path)
|
||||
{
|
||||
return Create(out subDirFileSystem, ref baseFileSystem, path, false);
|
||||
}
|
||||
|
||||
public Result Create(out ReferenceCountedDisposable<IFileSystem> subDirFileSystem,
|
||||
ref ReferenceCountedDisposable<IFileSystem> baseFileSystem, U8Span path, bool preserveUnc)
|
||||
ref ReferenceCountedDisposable<IFileSystem> baseFileSystem, in Path path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out subDirFileSystem);
|
||||
|
||||
// Verify the sub-path exists
|
||||
Result rc = baseFileSystem.Target.OpenDirectory(out IDirectory _, path, OpenDirectoryMode.Directory);
|
||||
Result rc = baseFileSystem.Target.OpenDirectory(out IDirectory dir, in path, OpenDirectoryMode.Directory);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
// Initialize the SubdirectoryFileSystem
|
||||
var subDir = new SubdirectoryFileSystem(ref baseFileSystem, preserveUnc);
|
||||
using var subDirShared = new ReferenceCountedDisposable<SubdirectoryFileSystem>(subDir);
|
||||
dir.Dispose();
|
||||
|
||||
rc = subDirShared.Target.Initialize(path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
ReferenceCountedDisposable<SubdirectoryFileSystem> subFs = null;
|
||||
try
|
||||
{
|
||||
subFs = new ReferenceCountedDisposable<SubdirectoryFileSystem>(
|
||||
new SubdirectoryFileSystem(ref baseFileSystem));
|
||||
|
||||
subDirFileSystem = subDirShared.AddReference<IFileSystem>();
|
||||
return Result.Success;
|
||||
rc = subFs.Target.Initialize(in path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
subDirFileSystem = subFs.AddReference<IFileSystem>();
|
||||
return Result.Success;
|
||||
}
|
||||
finally
|
||||
{
|
||||
subFs?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,21 +1,17 @@
|
||||
using System;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
|
||||
namespace LibHac.FsSrv.FsCreator
|
||||
{
|
||||
public class TargetManagerFileSystemCreator : ITargetManagerFileSystemCreator
|
||||
{
|
||||
public Result Create(out IFileSystem fileSystem, bool openCaseSensitive)
|
||||
public Result Create(out ReferenceCountedDisposable<IFileSystem> fileSystem, in Path rootPath, bool openCaseSensitive, bool ensureRootPathExists, Result pathNotFoundResult)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Result Create(out ReferenceCountedDisposable<IFileSystem> fileSystem, bool openCaseSensitive)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Result NormalizeCaseOfPath(out bool isSupported, Span<byte> path)
|
||||
public Result NormalizeCaseOfPath(out bool isSupported, ref Path path)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.FsSystem;
|
||||
|
||||
@ -18,7 +17,7 @@ namespace LibHac.FsSrv.Impl
|
||||
}
|
||||
|
||||
// ReSharper disable once RedundantOverriddenMember
|
||||
protected override Result DoOpenFile(out IFile file, U8Span path, OpenMode mode)
|
||||
protected override Result DoOpenFile(out IFile file, in Path path, OpenMode mode)
|
||||
{
|
||||
// Todo: Implement
|
||||
return base.DoOpenFile(out file, path, mode);
|
||||
|
@ -39,18 +39,14 @@ namespace LibHac.FsSrv.Impl
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
public override void Dispose()
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
AccessFailureManager?.Dispose();
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
AccessFailureManager?.Dispose();
|
||||
base.Dispose();
|
||||
}
|
||||
|
||||
// ReSharper disable once RedundantOverriddenMember
|
||||
protected override Result DoOpenFile(out IFile file, U8Span path, OpenMode mode)
|
||||
protected override Result DoOpenFile(out IFile file, in Path path, OpenMode mode)
|
||||
{
|
||||
// Todo: Implement
|
||||
return base.DoOpenFile(out file, path, mode);
|
||||
|
@ -1,66 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Sf;
|
||||
using IDirectory = LibHac.Fs.Fsa.IDirectory;
|
||||
using IDirectorySf = LibHac.FsSrv.Sf.IDirectory;
|
||||
|
||||
namespace LibHac.FsSrv.Impl
|
||||
{
|
||||
public class DirectoryInterfaceAdapter : IDirectorySf
|
||||
{
|
||||
private ReferenceCountedDisposable<FileSystemInterfaceAdapter> ParentFs { get; }
|
||||
private IDirectory BaseDirectory { get; }
|
||||
|
||||
public DirectoryInterfaceAdapter(IDirectory baseDirectory,
|
||||
ref ReferenceCountedDisposable<FileSystemInterfaceAdapter> parentFileSystem)
|
||||
{
|
||||
BaseDirectory = baseDirectory;
|
||||
ParentFs = parentFileSystem;
|
||||
parentFileSystem = null;
|
||||
}
|
||||
|
||||
public Result Read(out long entriesRead, OutBuffer entryBuffer)
|
||||
{
|
||||
const int maxTryCount = 2;
|
||||
UnsafeHelpers.SkipParamInit(out entriesRead);
|
||||
|
||||
Span<DirectoryEntry> entries = MemoryMarshal.Cast<byte, DirectoryEntry>(entryBuffer.Buffer);
|
||||
|
||||
Result rc = Result.Success;
|
||||
long tmpEntriesRead = 0;
|
||||
|
||||
for (int tryNum = 0; tryNum < maxTryCount; tryNum++)
|
||||
{
|
||||
rc = BaseDirectory.Read(out tmpEntriesRead, entries);
|
||||
|
||||
// Retry on ResultDataCorrupted
|
||||
if (!ResultFs.DataCorrupted.Includes(rc))
|
||||
break;
|
||||
}
|
||||
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
entriesRead = tmpEntriesRead;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result GetEntryCount(out long entryCount)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out entryCount);
|
||||
|
||||
Result rc = BaseDirectory.GetEntryCount(out long tmpEntryCount);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
entryCount = tmpEntryCount;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
BaseDirectory?.Dispose();
|
||||
ParentFs?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,133 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Sf;
|
||||
using IFile = LibHac.Fs.Fsa.IFile;
|
||||
using IFileSf = LibHac.FsSrv.Sf.IFile;
|
||||
|
||||
namespace LibHac.FsSrv.Impl
|
||||
{
|
||||
public class FileInterfaceAdapter : IFileSf
|
||||
{
|
||||
private ReferenceCountedDisposable<FileSystemInterfaceAdapter> ParentFs { get; }
|
||||
private IFile BaseFile { get; }
|
||||
|
||||
public FileInterfaceAdapter(IFile baseFile,
|
||||
ref ReferenceCountedDisposable<FileSystemInterfaceAdapter> parentFileSystem)
|
||||
{
|
||||
BaseFile = baseFile;
|
||||
ParentFs = parentFileSystem;
|
||||
parentFileSystem = null;
|
||||
}
|
||||
|
||||
public Result Read(out long bytesRead, long offset, OutBuffer destination, long size, ReadOption option)
|
||||
{
|
||||
const int maxTryCount = 2;
|
||||
UnsafeHelpers.SkipParamInit(out bytesRead);
|
||||
|
||||
if (offset < 0)
|
||||
return ResultFs.InvalidOffset.Log();
|
||||
|
||||
if (destination.Size < 0)
|
||||
return ResultFs.InvalidSize.Log();
|
||||
|
||||
Result rc = Result.Success;
|
||||
long tmpBytesRead = 0;
|
||||
|
||||
for (int tryNum = 0; tryNum < maxTryCount; tryNum++)
|
||||
{
|
||||
rc = BaseFile.Read(out tmpBytesRead, offset, destination.Buffer.Slice(0, (int)size), option);
|
||||
|
||||
// Retry on ResultDataCorrupted
|
||||
if (!ResultFs.DataCorrupted.Includes(rc))
|
||||
break;
|
||||
}
|
||||
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
bytesRead = tmpBytesRead;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result Write(long offset, InBuffer source, long size, WriteOption option)
|
||||
{
|
||||
if (offset < 0)
|
||||
return ResultFs.InvalidOffset.Log();
|
||||
|
||||
if (source.Size < 0)
|
||||
return ResultFs.InvalidSize.Log();
|
||||
|
||||
// Note: Thread priority is temporarily increased when writing in FS
|
||||
|
||||
return BaseFile.Write(offset, source.Buffer.Slice(0, (int)size), option);
|
||||
}
|
||||
|
||||
public Result Flush()
|
||||
{
|
||||
return BaseFile.Flush();
|
||||
}
|
||||
|
||||
public Result SetSize(long size)
|
||||
{
|
||||
if (size < 0)
|
||||
return ResultFs.InvalidSize.Log();
|
||||
|
||||
return BaseFile.SetSize(size);
|
||||
}
|
||||
|
||||
public Result GetSize(out long size)
|
||||
{
|
||||
const int maxTryCount = 2;
|
||||
UnsafeHelpers.SkipParamInit(out size);
|
||||
|
||||
Result rc = Result.Success;
|
||||
long tmpSize = 0;
|
||||
|
||||
for (int tryNum = 0; tryNum < maxTryCount; tryNum++)
|
||||
{
|
||||
rc = BaseFile.GetSize(out tmpSize);
|
||||
|
||||
// Retry on ResultDataCorrupted
|
||||
if (!ResultFs.DataCorrupted.Includes(rc))
|
||||
break;
|
||||
}
|
||||
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
size = tmpSize;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result OperateRange(out QueryRangeInfo rangeInfo, int operationId, long offset, long size)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out rangeInfo);
|
||||
rangeInfo.Clear();
|
||||
|
||||
if (operationId == (int)OperationId.InvalidateCache)
|
||||
{
|
||||
Result rc = BaseFile.OperateRange(Span<byte>.Empty, OperationId.InvalidateCache, offset, size,
|
||||
ReadOnlySpan<byte>.Empty);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
else if (operationId == (int)OperationId.QueryRange)
|
||||
{
|
||||
Unsafe.SkipInit(out QueryRangeInfo info);
|
||||
|
||||
Result rc = BaseFile.OperateRange(SpanHelpers.AsByteSpan(ref info), OperationId.QueryRange, offset,
|
||||
size, ReadOnlySpan<byte>.Empty);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rangeInfo.Merge(in info);
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
BaseFile?.Dispose();
|
||||
ParentFs?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,292 +1,644 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.Util;
|
||||
using LibHac.FsSystem;
|
||||
using LibHac.Sf;
|
||||
|
||||
using IFile = LibHac.Fs.Fsa.IFile;
|
||||
using IFileSf = LibHac.FsSrv.Sf.IFile;
|
||||
using IDirectory = LibHac.Fs.Fsa.IDirectory;
|
||||
using IDirectorySf = LibHac.FsSrv.Sf.IDirectory;
|
||||
using IFileSystem = LibHac.Fs.Fsa.IFileSystem;
|
||||
using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem;
|
||||
using IFileSf = LibHac.FsSrv.Sf.IFile;
|
||||
using IDirectorySf = LibHac.FsSrv.Sf.IDirectory;
|
||||
using Path = LibHac.FsSrv.Sf.Path;
|
||||
using PathSf = LibHac.FsSrv.Sf.Path;
|
||||
|
||||
namespace LibHac.FsSrv.Impl
|
||||
{
|
||||
/// <summary>
|
||||
/// Wraps an <see cref="IFile"/> to allow interfacing with it via the <see cref="IFileSf"/> interface over IPC.
|
||||
/// </summary>
|
||||
/// <remarks>Based on FS 12.0.3 (nnSdk 12.3.1)</remarks>
|
||||
public class FileInterfaceAdapter : IFileSf
|
||||
{
|
||||
private ReferenceCountedDisposable<FileSystemInterfaceAdapter> _parentFs;
|
||||
private IFile _baseFile;
|
||||
private bool _allowAllOperations;
|
||||
|
||||
public FileInterfaceAdapter(IFile baseFile,
|
||||
ref ReferenceCountedDisposable<FileSystemInterfaceAdapter> parentFileSystem, bool allowAllOperations)
|
||||
{
|
||||
_baseFile = baseFile;
|
||||
_parentFs = Shared.Move(ref parentFileSystem);
|
||||
_allowAllOperations = allowAllOperations;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_baseFile?.Dispose();
|
||||
_parentFs?.Dispose();
|
||||
}
|
||||
|
||||
public Result Read(out long bytesRead, long offset, OutBuffer destination, long size, ReadOption option)
|
||||
{
|
||||
const int maxTryCount = 2;
|
||||
UnsafeHelpers.SkipParamInit(out bytesRead);
|
||||
|
||||
if (offset < 0)
|
||||
return ResultFs.InvalidOffset.Log();
|
||||
|
||||
if (destination.Size < 0 || destination.Size < (int)size)
|
||||
return ResultFs.InvalidSize.Log();
|
||||
|
||||
Result rc = Result.Success;
|
||||
long tmpBytesRead = 0;
|
||||
|
||||
for (int tryNum = 0; tryNum < maxTryCount; tryNum++)
|
||||
{
|
||||
rc = _baseFile.Read(out tmpBytesRead, offset, destination.Buffer.Slice(0, (int)size), option);
|
||||
|
||||
// Retry on ResultDataCorrupted
|
||||
if (!ResultFs.DataCorrupted.Includes(rc))
|
||||
break;
|
||||
}
|
||||
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
bytesRead = tmpBytesRead;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result Write(long offset, InBuffer source, long size, WriteOption option)
|
||||
{
|
||||
if (offset < 0)
|
||||
return ResultFs.InvalidOffset.Log();
|
||||
|
||||
if (source.Size < 0 || source.Size < (int)size)
|
||||
return ResultFs.InvalidSize.Log();
|
||||
|
||||
using var scopedPriorityChanger =
|
||||
new ScopedThreadPriorityChangerByAccessPriority(ScopedThreadPriorityChangerByAccessPriority.AccessMode.Write);
|
||||
|
||||
return _baseFile.Write(offset, source.Buffer.Slice(0, (int)size), option);
|
||||
}
|
||||
|
||||
public Result Flush()
|
||||
{
|
||||
return _baseFile.Flush();
|
||||
}
|
||||
|
||||
public Result SetSize(long size)
|
||||
{
|
||||
if (size < 0)
|
||||
return ResultFs.InvalidSize.Log();
|
||||
|
||||
return _baseFile.SetSize(size);
|
||||
}
|
||||
|
||||
public Result GetSize(out long size)
|
||||
{
|
||||
const int maxTryCount = 2;
|
||||
UnsafeHelpers.SkipParamInit(out size);
|
||||
|
||||
Result rc = Result.Success;
|
||||
long tmpSize = 0;
|
||||
|
||||
for (int tryNum = 0; tryNum < maxTryCount; tryNum++)
|
||||
{
|
||||
rc = _baseFile.GetSize(out tmpSize);
|
||||
|
||||
// Retry on ResultDataCorrupted
|
||||
if (!ResultFs.DataCorrupted.Includes(rc))
|
||||
break;
|
||||
}
|
||||
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
size = tmpSize;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result OperateRange(out QueryRangeInfo rangeInfo, int operationId, long offset, long size)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out rangeInfo);
|
||||
rangeInfo.Clear();
|
||||
|
||||
if (operationId == (int)OperationId.QueryRange)
|
||||
{
|
||||
Unsafe.SkipInit(out QueryRangeInfo info);
|
||||
|
||||
Result rc = _baseFile.OperateRange(SpanHelpers.AsByteSpan(ref info), OperationId.QueryRange, offset,
|
||||
size, ReadOnlySpan<byte>.Empty);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rangeInfo.Merge(in info);
|
||||
}
|
||||
else if (operationId == (int)OperationId.InvalidateCache)
|
||||
{
|
||||
Result rc = _baseFile.OperateRange(Span<byte>.Empty, OperationId.InvalidateCache, offset, size,
|
||||
ReadOnlySpan<byte>.Empty);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result OperateRangeWithBuffer(OutBuffer outBuffer, InBuffer inBuffer, int operationId, long offset, long size)
|
||||
{
|
||||
static Result PermissionCheck(OperationId operationId, FileInterfaceAdapter fileAdapter)
|
||||
{
|
||||
if (operationId == OperationId.QueryUnpreparedRange ||
|
||||
operationId == OperationId.QueryLazyLoadCompletionRate ||
|
||||
operationId == OperationId.SetLazyLoadPriority)
|
||||
{
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
if (!fileAdapter._allowAllOperations)
|
||||
return ResultFs.PermissionDenied.Log();
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
Result rc = PermissionCheck((OperationId)operationId, this);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = _baseFile.OperateRange(outBuffer.Buffer, (OperationId)operationId, offset, size, inBuffer.Buffer);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wraps an <see cref="IDirectory"/> to allow interfacing with it via the <see cref="IDirectorySf"/> interface over IPC.
|
||||
/// </summary>
|
||||
/// <remarks>Based on FS 12.0.3 (nnSdk 12.3.1)</remarks>
|
||||
public class DirectoryInterfaceAdapter : IDirectorySf
|
||||
{
|
||||
private ReferenceCountedDisposable<FileSystemInterfaceAdapter> _parentFs;
|
||||
private IDirectory _baseDirectory;
|
||||
|
||||
public DirectoryInterfaceAdapter(IDirectory baseDirectory,
|
||||
ref ReferenceCountedDisposable<FileSystemInterfaceAdapter> parentFileSystem)
|
||||
{
|
||||
_baseDirectory = baseDirectory;
|
||||
_parentFs = Shared.Move(ref parentFileSystem);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_baseDirectory?.Dispose();
|
||||
_parentFs?.Dispose();
|
||||
}
|
||||
|
||||
public Result Read(out long entriesRead, OutBuffer entryBuffer)
|
||||
{
|
||||
const int maxTryCount = 2;
|
||||
UnsafeHelpers.SkipParamInit(out entriesRead);
|
||||
|
||||
Span<DirectoryEntry> entries = MemoryMarshal.Cast<byte, DirectoryEntry>(entryBuffer.Buffer);
|
||||
|
||||
Result rc = Result.Success;
|
||||
long numRead = 0;
|
||||
|
||||
for (int tryNum = 0; tryNum < maxTryCount; tryNum++)
|
||||
{
|
||||
rc = _baseDirectory.Read(out numRead, entries);
|
||||
|
||||
// Retry on ResultDataCorrupted
|
||||
if (!ResultFs.DataCorrupted.Includes(rc))
|
||||
break;
|
||||
}
|
||||
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
entriesRead = numRead;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result GetEntryCount(out long entryCount)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out entryCount);
|
||||
|
||||
Result rc = _baseDirectory.GetEntryCount(out long count);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
entryCount = count;
|
||||
return Result.Success;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wraps an <see cref="IFileSystem"/> to allow interfacing with it via the <see cref="IFileSystemSf"/> interface over IPC.
|
||||
/// All incoming paths are normalized before they are passed to the base <see cref="IFileSystem"/>.
|
||||
/// </summary>
|
||||
/// <remarks>Based on FS 12.0.3 (nnSdk 12.3.1)</remarks>
|
||||
public class FileSystemInterfaceAdapter : IFileSystemSf
|
||||
{
|
||||
private ReferenceCountedDisposable<IFileSystem> BaseFileSystem { get; }
|
||||
private bool IsHostFsRoot { get; }
|
||||
private ReferenceCountedDisposable<IFileSystem> _baseFileSystem;
|
||||
private PathFlags _pathFlags;
|
||||
|
||||
// This field is always false in FS 12.0.0. Not sure what it's actually used for.
|
||||
private bool _allowAllOperations;
|
||||
|
||||
// In FS, FileSystemInterfaceAdapter is derived from ISharedObject, so that's used for ref-counting when
|
||||
// creating files and directories. We don't have an ISharedObject, so a self-reference is used instead.
|
||||
private ReferenceCountedDisposable<FileSystemInterfaceAdapter>.WeakReference _selfReference;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <see cref="FileSystemInterfaceAdapter"/> by creating
|
||||
/// a new reference to <paramref name="fileSystem"/>.
|
||||
/// </summary>
|
||||
/// <param name="fileSystem">The base file system.</param>
|
||||
/// <param name="isHostFsRoot">Does the base file system come from the root directory of a host file system?</param>
|
||||
private FileSystemInterfaceAdapter(ReferenceCountedDisposable<IFileSystem> fileSystem,
|
||||
bool isHostFsRoot = false)
|
||||
{
|
||||
BaseFileSystem = fileSystem.AddReference();
|
||||
IsHostFsRoot = isHostFsRoot;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <see cref="FileSystemInterfaceAdapter"/> by moving the file system object.
|
||||
/// Avoids allocations from incrementing and then decrementing the ref-count.
|
||||
/// </summary>
|
||||
/// <param name="fileSystem">The base file system. Will be null upon returning.</param>
|
||||
/// <param name="isHostFsRoot">Does the base file system come from the root directory of a host file system?</param>
|
||||
private FileSystemInterfaceAdapter(ref ReferenceCountedDisposable<IFileSystem> fileSystem,
|
||||
bool isHostFsRoot = false)
|
||||
bool allowAllOperations)
|
||||
{
|
||||
BaseFileSystem = fileSystem;
|
||||
fileSystem = null;
|
||||
IsHostFsRoot = isHostFsRoot;
|
||||
_baseFileSystem = Shared.Move(ref fileSystem);
|
||||
_allowAllOperations = allowAllOperations;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <see cref="FileSystemInterfaceAdapter"/>, creating a copy of the input file system object.
|
||||
/// </summary>
|
||||
/// <param name="baseFileSystem">The base file system.</param>
|
||||
/// <param name="isHostFsRoot">Does the base file system come from the root directory of a host file system?</param>
|
||||
public static ReferenceCountedDisposable<FileSystemInterfaceAdapter> CreateShared(
|
||||
ReferenceCountedDisposable<IFileSystem> baseFileSystem, bool isHostFsRoot = false)
|
||||
private FileSystemInterfaceAdapter(ref ReferenceCountedDisposable<IFileSystem> fileSystem, PathFlags flags,
|
||||
bool allowAllOperations)
|
||||
{
|
||||
var adapter = new FileSystemInterfaceAdapter(baseFileSystem, isHostFsRoot);
|
||||
|
||||
return ReferenceCountedDisposable<FileSystemInterfaceAdapter>.Create(adapter, out adapter._selfReference);
|
||||
_baseFileSystem = Shared.Move(ref fileSystem);
|
||||
_pathFlags = flags;
|
||||
_allowAllOperations = allowAllOperations;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <see cref="FileSystemInterfaceAdapter"/> cast to an <see cref="IFileSystemSf"/>
|
||||
/// by moving the input file system object. Avoids allocations from incrementing and then decrementing the ref-count.
|
||||
/// </summary>
|
||||
/// <param name="baseFileSystem">The base file system. Will be null upon returning.</param>
|
||||
/// <param name="isHostFsRoot">Does the base file system come from the root directory of a host file system?</param>
|
||||
public static ReferenceCountedDisposable<IFileSystemSf> CreateShared(
|
||||
ref ReferenceCountedDisposable<IFileSystem> baseFileSystem, bool isHostFsRoot = false)
|
||||
ref ReferenceCountedDisposable<IFileSystem> baseFileSystem, bool allowAllOperations)
|
||||
{
|
||||
var adapter = new FileSystemInterfaceAdapter(ref baseFileSystem, isHostFsRoot);
|
||||
|
||||
return ReferenceCountedDisposable<IFileSystemSf>.Create(adapter, out adapter._selfReference);
|
||||
}
|
||||
|
||||
private static ReadOnlySpan<byte> RootDir => new[] { (byte)'/' };
|
||||
|
||||
public Result GetImpl(out ReferenceCountedDisposable<IFileSystem> fileSystem)
|
||||
{
|
||||
fileSystem = BaseFileSystem.AddReference();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result CreateFile(in Path path, long size, int option)
|
||||
{
|
||||
if (size < 0)
|
||||
return ResultFs.InvalidSize.Log();
|
||||
|
||||
using var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption());
|
||||
if (normalizer.Result.IsFailure()) return normalizer.Result;
|
||||
|
||||
return BaseFileSystem.Target.CreateFile(normalizer.Path, size, (CreateFileOptions)option);
|
||||
}
|
||||
|
||||
public Result DeleteFile(in Path path)
|
||||
{
|
||||
using var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption());
|
||||
if (normalizer.Result.IsFailure()) return normalizer.Result;
|
||||
|
||||
return BaseFileSystem.Target.DeleteFile(normalizer.Path);
|
||||
}
|
||||
|
||||
public Result CreateDirectory(in Path path)
|
||||
{
|
||||
using var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption());
|
||||
if (normalizer.Result.IsFailure()) return normalizer.Result;
|
||||
|
||||
if (StringUtils.Compare(RootDir, normalizer.Path) == 0)
|
||||
return ResultFs.PathAlreadyExists.Log();
|
||||
|
||||
return BaseFileSystem.Target.CreateDirectory(normalizer.Path);
|
||||
}
|
||||
|
||||
public Result DeleteDirectory(in Path path)
|
||||
{
|
||||
using var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption());
|
||||
if (normalizer.Result.IsFailure()) return normalizer.Result;
|
||||
|
||||
if (StringUtils.Compare(RootDir, normalizer.Path) == 0)
|
||||
return ResultFs.DirectoryNotDeletable.Log();
|
||||
|
||||
return BaseFileSystem.Target.DeleteDirectory(normalizer.Path);
|
||||
}
|
||||
|
||||
public Result DeleteDirectoryRecursively(in Path path)
|
||||
{
|
||||
using var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption());
|
||||
if (normalizer.Result.IsFailure()) return normalizer.Result;
|
||||
|
||||
if (StringUtils.Compare(RootDir, normalizer.Path) == 0)
|
||||
return ResultFs.DirectoryNotDeletable.Log();
|
||||
|
||||
return BaseFileSystem.Target.DeleteDirectoryRecursively(normalizer.Path);
|
||||
}
|
||||
|
||||
public Result RenameFile(in Path oldPath, in Path newPath)
|
||||
{
|
||||
using var normalizerOldPath = new PathNormalizer(new U8Span(oldPath.Str), GetPathNormalizerOption());
|
||||
if (normalizerOldPath.Result.IsFailure()) return normalizerOldPath.Result;
|
||||
|
||||
using var normalizerNewPath = new PathNormalizer(new U8Span(newPath.Str), GetPathNormalizerOption());
|
||||
if (normalizerNewPath.Result.IsFailure()) return normalizerNewPath.Result;
|
||||
|
||||
return BaseFileSystem.Target.RenameFile(new U8Span(normalizerOldPath.Path),
|
||||
new U8Span(normalizerNewPath.Path));
|
||||
}
|
||||
|
||||
public Result RenameDirectory(in Path oldPath, in Path newPath)
|
||||
{
|
||||
using var normalizerOldPath = new PathNormalizer(new U8Span(oldPath.Str), GetPathNormalizerOption());
|
||||
if (normalizerOldPath.Result.IsFailure()) return normalizerOldPath.Result;
|
||||
|
||||
using var normalizerNewPath = new PathNormalizer(new U8Span(newPath.Str), GetPathNormalizerOption());
|
||||
if (normalizerNewPath.Result.IsFailure()) return normalizerNewPath.Result;
|
||||
|
||||
if (PathUtility.IsSubPath(normalizerOldPath.Path, normalizerNewPath.Path))
|
||||
return ResultFs.DirectoryNotRenamable.Log();
|
||||
|
||||
return BaseFileSystem.Target.RenameDirectory(normalizerOldPath.Path, normalizerNewPath.Path);
|
||||
}
|
||||
|
||||
public Result GetEntryType(out uint entryType, in Path path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out entryType);
|
||||
|
||||
using var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption());
|
||||
if (normalizer.Result.IsFailure()) return normalizer.Result;
|
||||
|
||||
ref DirectoryEntryType type = ref Unsafe.As<uint, DirectoryEntryType>(ref entryType);
|
||||
|
||||
return BaseFileSystem.Target.GetEntryType(out type, new U8Span(normalizer.Path));
|
||||
}
|
||||
|
||||
public Result OpenFile(out ReferenceCountedDisposable<IFileSf> file, in Path path, uint mode)
|
||||
{
|
||||
const int maxTryCount = 2;
|
||||
UnsafeHelpers.SkipParamInit(out file);
|
||||
|
||||
using var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption());
|
||||
if (normalizer.Result.IsFailure()) return normalizer.Result;
|
||||
|
||||
Result rc = Result.Success;
|
||||
IFile fileInterface = null;
|
||||
|
||||
for (int tryNum = 0; tryNum < maxTryCount; tryNum++)
|
||||
ReferenceCountedDisposable<FileSystemInterfaceAdapter> sharedAdapter = null;
|
||||
try
|
||||
{
|
||||
rc = BaseFileSystem.Target.OpenFile(out fileInterface, new U8Span(normalizer.Path), (OpenMode)mode);
|
||||
var adapter = new FileSystemInterfaceAdapter(ref baseFileSystem, allowAllOperations);
|
||||
sharedAdapter = new ReferenceCountedDisposable<FileSystemInterfaceAdapter>(adapter);
|
||||
|
||||
// Retry on ResultDataCorrupted
|
||||
if (!ResultFs.DataCorrupted.Includes(rc))
|
||||
break;
|
||||
adapter._selfReference =
|
||||
new ReferenceCountedDisposable<FileSystemInterfaceAdapter>.WeakReference(sharedAdapter);
|
||||
|
||||
return sharedAdapter.AddReference<IFileSystemSf>();
|
||||
}
|
||||
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
ReferenceCountedDisposable<FileSystemInterfaceAdapter> selfReference = _selfReference.TryAddReference();
|
||||
var adapter = new FileInterfaceAdapter(fileInterface, ref selfReference);
|
||||
file = new ReferenceCountedDisposable<IFileSf>(adapter);
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result OpenDirectory(out ReferenceCountedDisposable<IDirectorySf> directory, in Path path, uint mode)
|
||||
{
|
||||
const int maxTryCount = 2;
|
||||
UnsafeHelpers.SkipParamInit(out directory);
|
||||
|
||||
using var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption());
|
||||
if (normalizer.Result.IsFailure()) return normalizer.Result;
|
||||
|
||||
Result rc = Result.Success;
|
||||
IDirectory dirInterface = null;
|
||||
|
||||
for (int tryNum = 0; tryNum < maxTryCount; tryNum++)
|
||||
finally
|
||||
{
|
||||
rc = BaseFileSystem.Target.OpenDirectory(out dirInterface, new U8Span(normalizer.Path), (OpenDirectoryMode)mode);
|
||||
|
||||
// Retry on ResultDataCorrupted
|
||||
if (!ResultFs.DataCorrupted.Includes(rc))
|
||||
break;
|
||||
sharedAdapter?.Dispose();
|
||||
}
|
||||
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
ReferenceCountedDisposable<FileSystemInterfaceAdapter> selfReference = _selfReference.TryAddReference();
|
||||
var adapter = new DirectoryInterfaceAdapter(dirInterface, ref selfReference);
|
||||
directory = new ReferenceCountedDisposable<IDirectorySf>(adapter);
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result Commit()
|
||||
public static ReferenceCountedDisposable<IFileSystemSf> CreateShared(
|
||||
ref ReferenceCountedDisposable<IFileSystem> baseFileSystem, PathFlags flags, bool allowAllOperations)
|
||||
{
|
||||
return BaseFileSystem.Target.Commit();
|
||||
}
|
||||
ReferenceCountedDisposable<FileSystemInterfaceAdapter> sharedAdapter = null;
|
||||
try
|
||||
{
|
||||
var adapter = new FileSystemInterfaceAdapter(ref baseFileSystem, flags, allowAllOperations);
|
||||
sharedAdapter = new ReferenceCountedDisposable<FileSystemInterfaceAdapter>(adapter);
|
||||
|
||||
public Result GetFreeSpaceSize(out long freeSpace, in Path path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out freeSpace);
|
||||
adapter._selfReference =
|
||||
new ReferenceCountedDisposable<FileSystemInterfaceAdapter>.WeakReference(sharedAdapter);
|
||||
|
||||
using var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption());
|
||||
if (normalizer.Result.IsFailure()) return normalizer.Result;
|
||||
|
||||
return BaseFileSystem.Target.GetFreeSpaceSize(out freeSpace, normalizer.Path);
|
||||
}
|
||||
|
||||
public Result GetTotalSpaceSize(out long totalSpace, in Path path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out totalSpace);
|
||||
|
||||
using var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption());
|
||||
if (normalizer.Result.IsFailure()) return normalizer.Result;
|
||||
|
||||
return BaseFileSystem.Target.GetTotalSpaceSize(out totalSpace, normalizer.Path);
|
||||
}
|
||||
|
||||
public Result CleanDirectoryRecursively(in Path path)
|
||||
{
|
||||
using var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption());
|
||||
if (normalizer.Result.IsFailure()) return normalizer.Result;
|
||||
|
||||
return BaseFileSystem.Target.CleanDirectoryRecursively(normalizer.Path);
|
||||
}
|
||||
|
||||
public Result GetFileTimeStampRaw(out FileTimeStampRaw timeStamp, in Path path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out timeStamp);
|
||||
|
||||
using var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption());
|
||||
if (normalizer.Result.IsFailure()) return normalizer.Result;
|
||||
|
||||
return BaseFileSystem.Target.GetFileTimeStampRaw(out timeStamp, normalizer.Path);
|
||||
}
|
||||
|
||||
public Result QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, int queryId, in Path path)
|
||||
{
|
||||
return BaseFileSystem.Target.QueryEntry(outBuffer, inBuffer, (QueryId)queryId, new U8Span(path.Str));
|
||||
return sharedAdapter.AddReference<IFileSystemSf>();
|
||||
}
|
||||
finally
|
||||
{
|
||||
sharedAdapter?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
BaseFileSystem?.Dispose();
|
||||
ReferenceCountedDisposable<IFileSystem> tempFs = Shared.Move(ref _baseFileSystem);
|
||||
tempFs?.Dispose();
|
||||
}
|
||||
|
||||
private PathNormalizer.Option GetPathNormalizerOption()
|
||||
private static ReadOnlySpan<byte> RootDir => new[] { (byte)'/' };
|
||||
|
||||
private Result SetUpPath(ref Path fsPath, in PathSf sfPath)
|
||||
{
|
||||
return IsHostFsRoot ? PathNormalizer.Option.PreserveUnc : PathNormalizer.Option.None;
|
||||
Result rc;
|
||||
|
||||
if (_pathFlags.IsWindowsPathAllowed())
|
||||
{
|
||||
rc = fsPath.InitializeWithReplaceUnc(sfPath.Str);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = fsPath.Initialize(sfPath.Str);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
|
||||
rc = fsPath.Normalize(_pathFlags);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result CreateFile(in PathSf path, long size, int option)
|
||||
{
|
||||
if (size < 0)
|
||||
return ResultFs.InvalidSize.Log();
|
||||
|
||||
var pathNormalized = new Path();
|
||||
Result rc = SetUpPath(ref pathNormalized, in path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = _baseFileSystem.Target.CreateFile(in pathNormalized, size, (CreateFileOptions)option);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
pathNormalized.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result DeleteFile(in PathSf path)
|
||||
{
|
||||
var pathNormalized = new Path();
|
||||
Result rc = SetUpPath(ref pathNormalized, in path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = _baseFileSystem.Target.DeleteFile(in pathNormalized);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
pathNormalized.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result CreateDirectory(in PathSf path)
|
||||
{
|
||||
var pathNormalized = new Path();
|
||||
Result rc = SetUpPath(ref pathNormalized, in path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (pathNormalized == RootDir)
|
||||
return ResultFs.PathAlreadyExists.Log();
|
||||
|
||||
rc = _baseFileSystem.Target.CreateDirectory(in pathNormalized);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
pathNormalized.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result DeleteDirectory(in PathSf path)
|
||||
{
|
||||
var pathNormalized = new Path();
|
||||
Result rc = SetUpPath(ref pathNormalized, in path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (pathNormalized == RootDir)
|
||||
return ResultFs.DirectoryNotDeletable.Log();
|
||||
|
||||
rc = _baseFileSystem.Target.DeleteDirectory(in pathNormalized);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
pathNormalized.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result DeleteDirectoryRecursively(in PathSf path)
|
||||
{
|
||||
var pathNormalized = new Path();
|
||||
Result rc = SetUpPath(ref pathNormalized, in path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (pathNormalized == RootDir)
|
||||
return ResultFs.DirectoryNotDeletable.Log();
|
||||
|
||||
rc = _baseFileSystem.Target.DeleteDirectoryRecursively(in pathNormalized);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
pathNormalized.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result CleanDirectoryRecursively(in PathSf path)
|
||||
{
|
||||
var pathNormalized = new Path();
|
||||
Result rc = SetUpPath(ref pathNormalized, in path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = _baseFileSystem.Target.CleanDirectoryRecursively(in pathNormalized);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
pathNormalized.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result RenameFile(in PathSf currentPath, in PathSf newPath)
|
||||
{
|
||||
var currentPathNormalized = new Path();
|
||||
Result rc = SetUpPath(ref currentPathNormalized, in currentPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
var newPathNormalized = new Path();
|
||||
rc = SetUpPath(ref newPathNormalized, in newPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = _baseFileSystem.Target.RenameFile(in currentPathNormalized, in newPathNormalized);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
currentPathNormalized.Dispose();
|
||||
newPathNormalized.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result RenameDirectory(in PathSf currentPath, in PathSf newPath)
|
||||
{
|
||||
var currentPathNormalized = new Path();
|
||||
Result rc = SetUpPath(ref currentPathNormalized, in currentPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
var newPathNormalized = new Path();
|
||||
rc = SetUpPath(ref newPathNormalized, in newPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (PathUtility.IsSubPath(currentPathNormalized.GetString(), newPathNormalized.GetString()))
|
||||
return ResultFs.DirectoryNotRenamable.Log();
|
||||
|
||||
rc = _baseFileSystem.Target.RenameDirectory(in currentPathNormalized, in newPathNormalized);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
currentPathNormalized.Dispose();
|
||||
newPathNormalized.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result GetEntryType(out uint entryType, in PathSf path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out entryType);
|
||||
|
||||
var pathNormalized = new Path();
|
||||
Result rc = SetUpPath(ref pathNormalized, in path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = _baseFileSystem.Target.GetEntryType(out DirectoryEntryType type, in pathNormalized);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
entryType = (uint)type;
|
||||
pathNormalized.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result GetFreeSpaceSize(out long freeSpace, in PathSf path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out freeSpace);
|
||||
|
||||
var pathNormalized = new Path();
|
||||
Result rc = SetUpPath(ref pathNormalized, in path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = _baseFileSystem.Target.GetFreeSpaceSize(out long space, in pathNormalized);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
freeSpace = space;
|
||||
pathNormalized.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result GetTotalSpaceSize(out long totalSpace, in PathSf path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out totalSpace);
|
||||
|
||||
var pathNormalized = new Path();
|
||||
Result rc = SetUpPath(ref pathNormalized, in path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = _baseFileSystem.Target.GetTotalSpaceSize(out long space, in pathNormalized);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
totalSpace = space;
|
||||
pathNormalized.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result OpenFile(out ReferenceCountedDisposable<IFileSf> file, in PathSf path, uint mode)
|
||||
{
|
||||
const int maxTryCount = 2;
|
||||
UnsafeHelpers.SkipParamInit(out file);
|
||||
|
||||
var pathNormalized = new Path();
|
||||
Result rc = SetUpPath(ref pathNormalized, in path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
IFile fileInterface = null;
|
||||
try
|
||||
{
|
||||
for (int tryNum = 0; tryNum < maxTryCount; tryNum++)
|
||||
{
|
||||
rc = _baseFileSystem.Target.OpenFile(out fileInterface, in pathNormalized, (OpenMode)mode);
|
||||
|
||||
// Retry on ResultDataCorrupted
|
||||
if (!ResultFs.DataCorrupted.Includes(rc))
|
||||
break;
|
||||
}
|
||||
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
ReferenceCountedDisposable<FileSystemInterfaceAdapter> selfReference = _selfReference.AddReference();
|
||||
var adapter = new FileInterfaceAdapter(Shared.Move(ref fileInterface), ref selfReference, _allowAllOperations);
|
||||
file = new ReferenceCountedDisposable<IFileSf>(adapter);
|
||||
|
||||
pathNormalized.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
finally
|
||||
{
|
||||
fileInterface?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public Result OpenDirectory(out ReferenceCountedDisposable<IDirectorySf> directory, in PathSf path, uint mode)
|
||||
{
|
||||
const int maxTryCount = 2;
|
||||
UnsafeHelpers.SkipParamInit(out directory);
|
||||
|
||||
var pathNormalized = new Path();
|
||||
Result rc = SetUpPath(ref pathNormalized, in path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
IDirectory dirInterface = null;
|
||||
try
|
||||
{
|
||||
for (int tryNum = 0; tryNum < maxTryCount; tryNum++)
|
||||
{
|
||||
rc = _baseFileSystem.Target.OpenDirectory(out dirInterface, in pathNormalized,
|
||||
(OpenDirectoryMode)mode);
|
||||
|
||||
// Retry on ResultDataCorrupted
|
||||
if (!ResultFs.DataCorrupted.Includes(rc))
|
||||
break;
|
||||
}
|
||||
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
ReferenceCountedDisposable<FileSystemInterfaceAdapter> selfReference = _selfReference.AddReference();
|
||||
var adapter = new DirectoryInterfaceAdapter(dirInterface, ref selfReference);
|
||||
directory = new ReferenceCountedDisposable<IDirectorySf>(adapter);
|
||||
|
||||
pathNormalized.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
finally
|
||||
{
|
||||
dirInterface?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public Result Commit()
|
||||
{
|
||||
return _baseFileSystem.Target.Commit();
|
||||
}
|
||||
|
||||
public Result GetFileTimeStampRaw(out FileTimeStampRaw timeStamp, in PathSf path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out timeStamp);
|
||||
|
||||
var pathNormalized = new Path();
|
||||
Result rc = SetUpPath(ref pathNormalized, in path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = _baseFileSystem.Target.GetFileTimeStampRaw(out FileTimeStampRaw tempTimeStamp, in pathNormalized);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
timeStamp = tempTimeStamp;
|
||||
pathNormalized.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result QueryEntry(OutBuffer outBuffer, InBuffer inBuffer, int queryId, in PathSf path)
|
||||
{
|
||||
static Result PermissionCheck(QueryId queryId, FileSystemInterfaceAdapter fsAdapter)
|
||||
{
|
||||
if (queryId == QueryId.SetConcatenationFileAttribute ||
|
||||
queryId == QueryId.IsSignedSystemPartitionOnSdCardValid ||
|
||||
queryId == QueryId.QueryUnpreparedFileInformation)
|
||||
{
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
if (!fsAdapter._allowAllOperations)
|
||||
return ResultFs.PermissionDenied.Log();
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
Result rc = PermissionCheck((QueryId)queryId, this);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
var pathNormalized = new Path();
|
||||
rc = SetUpPath(ref pathNormalized, in path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = _baseFileSystem.Target.QueryEntry(outBuffer.Buffer, inBuffer.Buffer, (QueryId)queryId,
|
||||
in pathNormalized);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
pathNormalized.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result GetImpl(out ReferenceCountedDisposable<IFileSystem> fileSystem)
|
||||
{
|
||||
fileSystem = _baseFileSystem.AddReference();
|
||||
return Result.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,15 +15,12 @@ namespace LibHac.FsSrv.Impl
|
||||
BaseFile = baseFile;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
public override void Dispose()
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
BaseFile?.Dispose();
|
||||
BaseFile = null;
|
||||
}
|
||||
BaseFile?.Dispose();
|
||||
BaseFile = null;
|
||||
|
||||
base.Dispose(disposing);
|
||||
base.Dispose();
|
||||
}
|
||||
|
||||
protected override Result DoRead(out long bytesRead, long offset, Span<byte> destination, in ReadOption option)
|
||||
@ -70,15 +67,12 @@ namespace LibHac.FsSrv.Impl
|
||||
BaseDirectory = baseDirectory;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
public override void Dispose()
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
BaseDirectory?.Dispose();
|
||||
BaseDirectory = null;
|
||||
}
|
||||
BaseDirectory?.Dispose();
|
||||
BaseDirectory = null;
|
||||
|
||||
base.Dispose(disposing);
|
||||
base.Dispose();
|
||||
}
|
||||
|
||||
protected override Result DoRead(out long entriesRead, Span<DirectoryEntry> entryBuffer)
|
||||
@ -104,65 +98,61 @@ namespace LibHac.FsSrv.Impl
|
||||
BaseFileSystem = Shared.Move(ref baseFileSystem);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
public override void Dispose()
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
BaseFileSystem?.Dispose();
|
||||
BaseFileSystem = null;
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
BaseFileSystem?.Dispose();
|
||||
BaseFileSystem = null;
|
||||
base.Dispose();
|
||||
}
|
||||
|
||||
protected override Result DoCreateFile(U8Span path, long size, CreateFileOptions option)
|
||||
protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option)
|
||||
{
|
||||
return ConvertResult(BaseFileSystem.Target.CreateFile(path, size, option));
|
||||
}
|
||||
|
||||
protected override Result DoDeleteFile(U8Span path)
|
||||
protected override Result DoDeleteFile(in Path path)
|
||||
{
|
||||
return ConvertResult(BaseFileSystem.Target.DeleteFile(path));
|
||||
}
|
||||
|
||||
protected override Result DoCreateDirectory(U8Span path)
|
||||
protected override Result DoCreateDirectory(in Path path)
|
||||
{
|
||||
return ConvertResult(BaseFileSystem.Target.CreateDirectory(path));
|
||||
}
|
||||
|
||||
protected override Result DoDeleteDirectory(U8Span path)
|
||||
protected override Result DoDeleteDirectory(in Path path)
|
||||
{
|
||||
return ConvertResult(BaseFileSystem.Target.DeleteDirectory(path));
|
||||
}
|
||||
|
||||
protected override Result DoDeleteDirectoryRecursively(U8Span path)
|
||||
protected override Result DoDeleteDirectoryRecursively(in Path path)
|
||||
{
|
||||
return ConvertResult(BaseFileSystem.Target.DeleteDirectoryRecursively(path));
|
||||
}
|
||||
|
||||
protected override Result DoCleanDirectoryRecursively(U8Span path)
|
||||
protected override Result DoCleanDirectoryRecursively(in Path path)
|
||||
{
|
||||
return ConvertResult(BaseFileSystem.Target.CleanDirectoryRecursively(path));
|
||||
}
|
||||
|
||||
protected override Result DoRenameFile(U8Span oldPath, U8Span newPath)
|
||||
protected override Result DoRenameFile(in Path currentPath, in Path newPath)
|
||||
{
|
||||
return ConvertResult(BaseFileSystem.Target.RenameFile(oldPath, newPath));
|
||||
return ConvertResult(BaseFileSystem.Target.RenameFile(currentPath, newPath));
|
||||
}
|
||||
|
||||
protected override Result DoRenameDirectory(U8Span oldPath, U8Span newPath)
|
||||
protected override Result DoRenameDirectory(in Path currentPath, in Path newPath)
|
||||
{
|
||||
return ConvertResult(BaseFileSystem.Target.RenameDirectory(oldPath, newPath));
|
||||
return ConvertResult(BaseFileSystem.Target.RenameDirectory(currentPath, newPath));
|
||||
}
|
||||
|
||||
protected override Result DoGetEntryType(out DirectoryEntryType entryType, U8Span path)
|
||||
protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path)
|
||||
{
|
||||
return ConvertResult(BaseFileSystem.Target.GetEntryType(out entryType, path));
|
||||
}
|
||||
|
||||
protected abstract override Result DoOpenFile(out IFile file, U8Span path, OpenMode mode);
|
||||
protected abstract override Result DoOpenFile(out IFile file, in Path path, OpenMode mode);
|
||||
|
||||
protected abstract override Result DoOpenDirectory(out IDirectory directory, U8Span path,
|
||||
protected abstract override Result DoOpenDirectory(out IDirectory directory, in Path path,
|
||||
OpenDirectoryMode mode);
|
||||
|
||||
protected override Result DoCommit()
|
||||
@ -185,23 +175,23 @@ namespace LibHac.FsSrv.Impl
|
||||
return ConvertResult(BaseFileSystem.Target.Flush());
|
||||
}
|
||||
|
||||
protected override Result DoGetFileTimeStampRaw(out FileTimeStampRaw timeStamp, U8Span path)
|
||||
protected override Result DoGetFileTimeStampRaw(out FileTimeStampRaw timeStamp, in Path path)
|
||||
{
|
||||
return ConvertResult(BaseFileSystem.Target.GetFileTimeStampRaw(out timeStamp, path));
|
||||
}
|
||||
|
||||
protected override Result DoQueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, QueryId queryId,
|
||||
U8Span path)
|
||||
in Path path)
|
||||
{
|
||||
return ConvertResult(BaseFileSystem.Target.QueryEntry(outBuffer, inBuffer, queryId, path));
|
||||
}
|
||||
|
||||
protected override Result DoGetFreeSpaceSize(out long freeSpace, U8Span path)
|
||||
protected override Result DoGetFreeSpaceSize(out long freeSpace, in Path path)
|
||||
{
|
||||
return ConvertResult(BaseFileSystem.Target.GetFreeSpaceSize(out freeSpace, path));
|
||||
}
|
||||
|
||||
protected override Result DoGetTotalSpaceSize(out long totalSpace, U8Span path)
|
||||
protected override Result DoGetTotalSpaceSize(out long totalSpace, in Path path)
|
||||
{
|
||||
return ConvertResult(BaseFileSystem.Target.GetTotalSpaceSize(out totalSpace, path));
|
||||
}
|
||||
|
@ -5,14 +5,44 @@ using LibHac.Fs;
|
||||
using LibHac.Lr;
|
||||
using LibHac.Ncm;
|
||||
using LibHac.Os;
|
||||
using Path = LibHac.Lr.Path;
|
||||
|
||||
namespace LibHac.FsSrv.Impl
|
||||
{
|
||||
public static class LocationResolverSetGlobalMethods
|
||||
{
|
||||
public static void InitializeLocationResolverSet(this FileSystemServer fsSrv)
|
||||
{
|
||||
ref LocationResolverSetGlobals globals = ref fsSrv.Globals.LocationResolverSet;
|
||||
using ScopedLock<SdkMutexType> scopedLock = ScopedLock.Lock(ref globals.Mutex);
|
||||
|
||||
if (!globals.IsLrInitialized)
|
||||
{
|
||||
fsSrv.Hos.Lr.Initialize();
|
||||
globals.IsLrInitialized = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal struct LocationResolverSetGlobals
|
||||
{
|
||||
public SdkMutexType Mutex;
|
||||
public bool IsLrInitialized;
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
Mutex.Initialize();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Manages resolving the location of NCAs via the <c>lr</c> service.
|
||||
/// </summary>
|
||||
/// <remarks>Based on FS 12.0.3 (nnSdk 12.3.1)</remarks>
|
||||
internal class LocationResolverSet : IDisposable
|
||||
{
|
||||
private const int LocationResolverCount = 5;
|
||||
|
||||
// Todo: Use Optional<T>
|
||||
private LocationResolver[] _resolvers;
|
||||
private AddOnContentLocationResolver _aocResolver;
|
||||
private SdkMutexType _mutex;
|
||||
@ -27,16 +57,49 @@ namespace LibHac.FsSrv.Impl
|
||||
_fsServer = fsServer;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (LocationResolver resolver in _resolvers)
|
||||
{
|
||||
resolver?.Dispose();
|
||||
}
|
||||
|
||||
_aocResolver?.Dispose();
|
||||
}
|
||||
|
||||
private static Result SetUpFsPath(ref Fs.Path outPath, in Lr.Path lrPath)
|
||||
{
|
||||
var pathFlags = new PathFlags();
|
||||
pathFlags.AllowMountName();
|
||||
|
||||
if (Utility.IsHostFsMountName(lrPath.Str))
|
||||
pathFlags.AllowWindowsPath();
|
||||
|
||||
Result rc = outPath.InitializeWithReplaceUnc(lrPath.Str);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = outPath.Normalize(pathFlags);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
private Result GetLocationResolver(out LocationResolver resolver, StorageId storageId)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out resolver);
|
||||
|
||||
_fsServer.InitializeLocationResolverSet();
|
||||
|
||||
if (!IsValidStorageId(storageId))
|
||||
return ResultLr.LocationResolverNotFound.Log();
|
||||
|
||||
using ScopedLock<SdkMutexType> lk = ScopedLock.Lock(ref _mutex);
|
||||
|
||||
int index = GetResolverIndexFromStorageId(storageId);
|
||||
|
||||
if (index < 0)
|
||||
return ResultLr.LocationResolverNotFound.Log();
|
||||
|
||||
ref LocationResolver lr = ref _resolvers[index];
|
||||
|
||||
// Open the location resolver if it hasn't been already
|
||||
@ -64,6 +127,8 @@ namespace LibHac.FsSrv.Impl
|
||||
|
||||
private Result GetAddOnContentLocationResolver(out AddOnContentLocationResolver resolver)
|
||||
{
|
||||
_fsServer.InitializeLocationResolverSet();
|
||||
|
||||
using ScopedLock<SdkMutexType> lk = ScopedLock.Lock(ref _mutex);
|
||||
|
||||
if (_aocResolver is null)
|
||||
@ -82,108 +147,126 @@ namespace LibHac.FsSrv.Impl
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result ResolveApplicationControlPath(out Path path, Ncm.ApplicationId applicationId, StorageId storageId)
|
||||
public Result ResolveApplicationControlPath(ref Fs.Path outPath, Ncm.ApplicationId applicationId, StorageId storageId)
|
||||
{
|
||||
Path.InitEmpty(out path);
|
||||
Result rc = GetLocationResolver(out LocationResolver resolver, storageId);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = resolver.ResolveApplicationControlPath(out Lr.Path path, applicationId);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return SetUpFsPath(ref outPath, in path);
|
||||
}
|
||||
|
||||
public Result ResolveApplicationHtmlDocumentPath(out bool isDirectory, ref Fs.Path outPath, Ncm.ApplicationId applicationId, StorageId storageId)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out isDirectory);
|
||||
|
||||
Result rc = GetLocationResolver(out LocationResolver resolver, storageId);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = resolver.ResolveApplicationControlPath(out path, applicationId);
|
||||
rc = resolver.ResolveApplicationHtmlDocumentPath(out Lr.Path path, applicationId);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
PathUtility.Replace(path.StrMutable, (byte)'\\', (byte)'/');
|
||||
return Result.Success;
|
||||
isDirectory = PathUtility.IsDirectoryPath(path.Str);
|
||||
|
||||
return SetUpFsPath(ref outPath, in path);
|
||||
}
|
||||
|
||||
public Result ResolveApplicationHtmlDocumentPath(out Path path, Ncm.ApplicationId applicationId, StorageId storageId)
|
||||
public Result ResolveProgramPath(out bool isDirectory, ref Fs.Path outPath, ProgramId programId, StorageId storageId)
|
||||
{
|
||||
Path.InitEmpty(out path);
|
||||
UnsafeHelpers.SkipParamInit(out isDirectory);
|
||||
|
||||
Result rc = GetLocationResolver(out LocationResolver resolver, storageId);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = resolver.ResolveApplicationHtmlDocumentPath(out path, applicationId);
|
||||
rc = resolver.ResolveProgramPath(out Lr.Path path, programId);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
PathUtility.Replace(path.StrMutable, (byte)'\\', (byte)'/');
|
||||
return Result.Success;
|
||||
isDirectory = PathUtility.IsDirectoryPath(path.Str);
|
||||
|
||||
return SetUpFsPath(ref outPath, in path);
|
||||
}
|
||||
|
||||
public virtual Result ResolveProgramPath(out Path path, ulong id, StorageId storageId)
|
||||
public Result ResolveRomPath(out bool isDirectory, ref Fs.Path outPath, ProgramId programId, StorageId storageId)
|
||||
{
|
||||
Path.InitEmpty(out path);
|
||||
UnsafeHelpers.SkipParamInit(out isDirectory);
|
||||
|
||||
Result rc = GetLocationResolver(out LocationResolver resolver, storageId);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = resolver.ResolveProgramPath(out path, new ProgramId(id));
|
||||
rc = resolver.ResolveProgramPathForDebug(out Lr.Path path, programId);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
PathUtility.Replace(path.StrMutable, (byte)'\\', (byte)'/');
|
||||
return Result.Success;
|
||||
isDirectory = PathUtility.IsDirectoryPath(path.Str);
|
||||
|
||||
return SetUpFsPath(ref outPath, in path);
|
||||
}
|
||||
|
||||
public Result ResolveRomPath(out Path path, ulong id, StorageId storageId)
|
||||
public Result ResolveAddOnContentPath(ref Fs.Path outPath, DataId dataId)
|
||||
{
|
||||
Path.InitEmpty(out path);
|
||||
|
||||
Result rc = GetLocationResolver(out LocationResolver resolver, storageId);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = resolver.ResolveProgramPath(out path, new ProgramId(id));
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
PathUtility.Replace(path.StrMutable, (byte)'\\', (byte)'/');
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result ResolveAddOnContentPath(out Path path, DataId dataId)
|
||||
{
|
||||
Path.InitEmpty(out path);
|
||||
|
||||
Result rc = GetAddOnContentLocationResolver(out AddOnContentLocationResolver resolver);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return resolver.ResolveAddOnContentPath(out path, dataId);
|
||||
rc = resolver.ResolveAddOnContentPath(out Lr.Path path, dataId);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return SetUpFsPath(ref outPath, in path);
|
||||
}
|
||||
|
||||
public Result ResolveDataPath(out Path path, DataId dataId, StorageId storageId)
|
||||
public Result ResolveDataPath(ref Fs.Path outPath, DataId dataId, StorageId storageId)
|
||||
{
|
||||
Path.InitEmpty(out path);
|
||||
|
||||
if (storageId == StorageId.None)
|
||||
return ResultFs.InvalidAlignment.Log();
|
||||
|
||||
Result rc = GetLocationResolver(out LocationResolver resolver, storageId);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return resolver.ResolveDataPath(out path, dataId);
|
||||
}
|
||||
|
||||
public Result ResolveRegisteredProgramPath(out Path path, ulong id)
|
||||
{
|
||||
Path.InitEmpty(out path);
|
||||
|
||||
Result rc = GetRegisteredLocationResolver(out RegisteredLocationResolver resolver);
|
||||
rc = resolver.ResolveDataPath(out Lr.Path path, dataId);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
using (resolver)
|
||||
return SetUpFsPath(ref outPath, in path);
|
||||
}
|
||||
|
||||
public Result ResolveRegisteredProgramPath(ref Fs.Path outPath, ulong id)
|
||||
{
|
||||
_fsServer.InitializeLocationResolverSet();
|
||||
|
||||
RegisteredLocationResolver resolver = null;
|
||||
try
|
||||
{
|
||||
return resolver.ResolveProgramPath(out path, new ProgramId(id));
|
||||
Result rc = GetRegisteredLocationResolver(out resolver);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = resolver.ResolveProgramPath(out Lr.Path path, new ProgramId(id));
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return SetUpFsPath(ref outPath, in path);
|
||||
}
|
||||
finally
|
||||
{
|
||||
resolver?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public Result ResolveRegisteredHtmlDocumentPath(out Path path, ulong id)
|
||||
public Result ResolveRegisteredHtmlDocumentPath(ref Fs.Path outPath, ulong id)
|
||||
{
|
||||
Path.InitEmpty(out path);
|
||||
_fsServer.InitializeLocationResolverSet();
|
||||
|
||||
Result rc = GetRegisteredLocationResolver(out RegisteredLocationResolver resolver);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
using (resolver)
|
||||
RegisteredLocationResolver resolver = null;
|
||||
try
|
||||
{
|
||||
return resolver.ResolveHtmlDocumentPath(out path, new ProgramId(id));
|
||||
Result rc = GetRegisteredLocationResolver(out resolver);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = resolver.ResolveHtmlDocumentPath(out Lr.Path path, new ProgramId(id));
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return SetUpFsPath(ref outPath, in path);
|
||||
}
|
||||
finally
|
||||
{
|
||||
resolver?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@ -210,15 +293,5 @@ namespace LibHac.FsSrv.Impl
|
||||
_ => -1
|
||||
};
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (LocationResolver resolver in _resolvers)
|
||||
{
|
||||
resolver?.Dispose();
|
||||
}
|
||||
|
||||
_aocResolver?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,49 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using LibHac.Common;
|
||||
using LibHac.Diag;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.Fs.Shim;
|
||||
using LibHac.FsSrv.Sf;
|
||||
using LibHac.Os;
|
||||
using LibHac.Sf;
|
||||
using IFileSystem = LibHac.Fs.Fsa.IFileSystem;
|
||||
|
||||
using IFile = LibHac.Fs.Fsa.IFile;
|
||||
using IFileSystem = LibHac.Fs.Fsa.IFileSystem;
|
||||
using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem;
|
||||
|
||||
namespace LibHac.FsSrv.Impl
|
||||
{
|
||||
internal struct MultiCommitManagerGlobals
|
||||
{
|
||||
public SdkMutexType MultiCommitMutex;
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
MultiCommitMutex.Initialize();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Manages atomically committing a group of file systems.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The commit process is as follows:<br/>
|
||||
/// 1. Create a commit context file that tracks the progress of the commit in case it is interrupted.<br/>
|
||||
/// 2. Provisionally commit each file system individually. If any fail, rollback the file systems that were provisionally committed.<br/>
|
||||
/// 3. Update the commit context file to note that the file systems have been provisionally committed.
|
||||
/// If the multi-commit is interrupted past this point, the file systems will be fully committed during recovery.<br/>
|
||||
/// 4. Fully commit each file system individually.<br/>
|
||||
/// 5. Delete the commit context file.<br/>
|
||||
///<br/>
|
||||
/// Even though multi-commits are supposed to be atomic, issues can arise from errors during the process of fully committing the save data.
|
||||
/// Save data image files are designed so that minimal changes are made when fully committing a provisionally committed save.
|
||||
/// However if any commit fails for any reason, all other saves in the multi-commit will still be committed.
|
||||
/// This can especially cause issues with directory save data where finishing a commit is much more involved.<br/>
|
||||
/// <br/>
|
||||
/// Based on FS 12.0.3 (nnSdk 12.3.1)
|
||||
/// </remarks>
|
||||
internal class MultiCommitManager : IMultiCommitManager
|
||||
{
|
||||
private const int MaxFileSystemCount = 10;
|
||||
@ -27,42 +57,43 @@ namespace LibHac.FsSrv.Impl
|
||||
private const long CommitContextFileSize = 0x200;
|
||||
|
||||
// /commitinfo
|
||||
private static U8Span CommitContextFileName =>
|
||||
new U8Span(new[] { (byte)'/', (byte)'c', (byte)'o', (byte)'m', (byte)'m', (byte)'i', (byte)'t', (byte)'i', (byte)'n', (byte)'f', (byte)'o' });
|
||||
private static ReadOnlySpan<byte> CommitContextFileName =>
|
||||
new[] { (byte)'/', (byte)'c', (byte)'o', (byte)'m', (byte)'m', (byte)'i', (byte)'t', (byte)'i', (byte)'n', (byte)'f', (byte)'o' };
|
||||
|
||||
// Todo: Don't use global lock object
|
||||
private static readonly object Locker = new object();
|
||||
private ReferenceCountedDisposable<ISaveDataMultiCommitCoreInterface> _multiCommitInterface;
|
||||
private readonly ReferenceCountedDisposable<IFileSystem>[] _fileSystems;
|
||||
private int _fileSystemCount;
|
||||
private long _counter;
|
||||
|
||||
private ReferenceCountedDisposable<ISaveDataMultiCommitCoreInterface> MultiCommitInterface { get; }
|
||||
// Extra field used in LibHac
|
||||
private readonly FileSystemServer _fsServer;
|
||||
private ref MultiCommitManagerGlobals Globals => ref _fsServer.Globals.MultiCommitManager;
|
||||
|
||||
private List<ReferenceCountedDisposable<IFileSystem>> FileSystems { get; } =
|
||||
new List<ReferenceCountedDisposable<IFileSystem>>(MaxFileSystemCount);
|
||||
|
||||
private long Counter { get; set; }
|
||||
private HorizonClient Hos { get; }
|
||||
|
||||
public MultiCommitManager(
|
||||
ref ReferenceCountedDisposable<ISaveDataMultiCommitCoreInterface> multiCommitInterface,
|
||||
HorizonClient client)
|
||||
public MultiCommitManager(FileSystemServer fsServer, ref ReferenceCountedDisposable<ISaveDataMultiCommitCoreInterface> multiCommitInterface)
|
||||
{
|
||||
Hos = client;
|
||||
MultiCommitInterface = Shared.Move(ref multiCommitInterface);
|
||||
_fsServer = fsServer;
|
||||
|
||||
_multiCommitInterface = Shared.Move(ref multiCommitInterface);
|
||||
_fileSystems = new ReferenceCountedDisposable<IFileSystem>[MaxFileSystemCount];
|
||||
_fileSystemCount = 0;
|
||||
_counter = 0;
|
||||
}
|
||||
|
||||
public static ReferenceCountedDisposable<IMultiCommitManager> CreateShared(
|
||||
ref ReferenceCountedDisposable<ISaveDataMultiCommitCoreInterface> multiCommitInterface,
|
||||
HorizonClient client)
|
||||
public static ReferenceCountedDisposable<IMultiCommitManager> CreateShared(FileSystemServer fsServer,
|
||||
ref ReferenceCountedDisposable<ISaveDataMultiCommitCoreInterface> multiCommitInterface)
|
||||
{
|
||||
var manager = new MultiCommitManager(ref multiCommitInterface, client);
|
||||
var manager = new MultiCommitManager(fsServer, ref multiCommitInterface);
|
||||
return new ReferenceCountedDisposable<IMultiCommitManager>(manager);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (ReferenceCountedDisposable<IFileSystem> fs in FileSystems)
|
||||
foreach (ReferenceCountedDisposable<IFileSystem> fs in _fileSystems)
|
||||
{
|
||||
fs.Dispose();
|
||||
fs?.Dispose();
|
||||
}
|
||||
|
||||
_multiCommitInterface?.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -71,20 +102,26 @@ namespace LibHac.FsSrv.Impl
|
||||
/// <returns>The <see cref="Result"/> of the operation.</returns>
|
||||
private Result EnsureSaveDataForContext()
|
||||
{
|
||||
Result rc = MultiCommitInterface.Target.OpenMultiCommitContext(
|
||||
out ReferenceCountedDisposable<IFileSystem> contextFs);
|
||||
|
||||
if (rc.IsFailure())
|
||||
ReferenceCountedDisposable<IFileSystem> contextFs = null;
|
||||
try
|
||||
{
|
||||
if (!ResultFs.TargetNotFound.Includes(rc))
|
||||
return rc;
|
||||
Result rc = _multiCommitInterface.Target.OpenMultiCommitContext(out contextFs);
|
||||
|
||||
rc = Hos.Fs.CreateSystemSaveData(SaveDataId, SaveDataSize, SaveJournalSize, SaveDataFlags.None);
|
||||
if (rc.IsFailure()) return rc;
|
||||
if (rc.IsFailure())
|
||||
{
|
||||
if (!ResultFs.TargetNotFound.Includes(rc))
|
||||
return rc;
|
||||
|
||||
rc = _fsServer.Hos.Fs.CreateSystemSaveData(SaveDataId, SaveDataSize, SaveJournalSize, SaveDataFlags.None);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
finally
|
||||
{
|
||||
contextFs?.Dispose();
|
||||
}
|
||||
|
||||
contextFs?.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -97,7 +134,7 @@ namespace LibHac.FsSrv.Impl
|
||||
/// <see cref="ResultFs.MultiCommitFileSystemAlreadyAdded"/>: The provided file system has already been added.</returns>
|
||||
public Result Add(ReferenceCountedDisposable<IFileSystemSf> fileSystem)
|
||||
{
|
||||
if (FileSystems.Count >= MaxFileSystemCount)
|
||||
if (_fileSystemCount >= MaxFileSystemCount)
|
||||
return ResultFs.MultiCommitFileSystemLimit.Log();
|
||||
|
||||
ReferenceCountedDisposable<IFileSystem> fsaFileSystem = null;
|
||||
@ -107,14 +144,14 @@ namespace LibHac.FsSrv.Impl
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
// Check that the file system hasn't already been added
|
||||
foreach (ReferenceCountedDisposable<IFileSystem> fs in FileSystems)
|
||||
for (int i = 0; i < _fileSystemCount; i++)
|
||||
{
|
||||
if (ReferenceEquals(fs.Target, fsaFileSystem.Target))
|
||||
if (ReferenceEquals(fsaFileSystem.Target, _fileSystems[i].Target))
|
||||
return ResultFs.MultiCommitFileSystemAlreadyAdded.Log();
|
||||
}
|
||||
|
||||
FileSystems.Add(fsaFileSystem);
|
||||
fsaFileSystem = null;
|
||||
_fileSystems[_fileSystemCount] = Shared.Move(ref fsaFileSystem);
|
||||
_fileSystemCount++;
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
@ -132,32 +169,23 @@ namespace LibHac.FsSrv.Impl
|
||||
/// <returns>The <see cref="Result"/> of the operation.</returns>
|
||||
private Result Commit(IFileSystem contextFileSystem)
|
||||
{
|
||||
ContextUpdater context = default;
|
||||
_counter = 1;
|
||||
|
||||
try
|
||||
{
|
||||
Counter = 1;
|
||||
using var contextUpdater = new ContextUpdater(contextFileSystem);
|
||||
Result rc = contextUpdater.Create(_counter, _fileSystemCount);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
context = new ContextUpdater(contextFileSystem);
|
||||
Result rc = context.Create(Counter, FileSystems.Count);
|
||||
if (rc.IsFailure()) return rc;
|
||||
rc = CommitProvisionallyFileSystem(_counter);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = CommitProvisionallyFileSystem(Counter);
|
||||
if (rc.IsFailure()) return rc;
|
||||
rc = contextUpdater.CommitProvisionallyDone();
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = context.CommitProvisionallyDone();
|
||||
if (rc.IsFailure()) return rc;
|
||||
rc = CommitFileSystem();
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = CommitFileSystem();
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = context.CommitDone();
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
finally
|
||||
{
|
||||
context.Dispose();
|
||||
}
|
||||
rc = contextUpdater.CommitDone();
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
@ -168,23 +196,22 @@ namespace LibHac.FsSrv.Impl
|
||||
/// <returns>The <see cref="Result"/> of the operation.</returns>
|
||||
public Result Commit()
|
||||
{
|
||||
lock (Locker)
|
||||
using ScopedLock<SdkMutexType> scopedLock = ScopedLock.Lock(ref Globals.MultiCommitMutex);
|
||||
|
||||
ReferenceCountedDisposable<IFileSystem> contextFs = null;
|
||||
try
|
||||
{
|
||||
Result rc = EnsureSaveDataForContext();
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
ReferenceCountedDisposable<IFileSystem> contextFs = null;
|
||||
try
|
||||
{
|
||||
rc = MultiCommitInterface.Target.OpenMultiCommitContext(out contextFs);
|
||||
if (rc.IsFailure()) return rc;
|
||||
rc = _multiCommitInterface.Target.OpenMultiCommitContext(out contextFs);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return Commit(contextFs.Target);
|
||||
}
|
||||
finally
|
||||
{
|
||||
contextFs?.Dispose();
|
||||
}
|
||||
return Commit(contextFs.Target);
|
||||
}
|
||||
finally
|
||||
{
|
||||
contextFs?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@ -198,9 +225,11 @@ namespace LibHac.FsSrv.Impl
|
||||
Result rc = Result.Success;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < FileSystems.Count; i++)
|
||||
for (i = 0; i < _fileSystemCount; i++)
|
||||
{
|
||||
rc = FileSystems[i].Target.CommitProvisionally(counter);
|
||||
Assert.SdkNotNull(_fileSystems[i]);
|
||||
|
||||
rc = _fileSystems[i].Target.CommitProvisionally(counter);
|
||||
|
||||
if (rc.IsFailure())
|
||||
break;
|
||||
@ -211,7 +240,9 @@ namespace LibHac.FsSrv.Impl
|
||||
// Rollback all provisional commits including the failed commit
|
||||
for (int j = 0; j <= i; j++)
|
||||
{
|
||||
FileSystems[j].Target.Rollback().IgnoreResult();
|
||||
Assert.SdkNotNull(_fileSystems[j]);
|
||||
|
||||
_fileSystems[j].Target.Rollback().IgnoreResult();
|
||||
}
|
||||
}
|
||||
|
||||
@ -224,14 +255,17 @@ namespace LibHac.FsSrv.Impl
|
||||
/// <returns>The <see cref="Result"/> of the operation.</returns>
|
||||
private Result CommitFileSystem()
|
||||
{
|
||||
// All file systems will try to be recovered committed, even if one fails.
|
||||
// Try to commit all file systems even if one fails.
|
||||
// If any commits fail, the result from the first failed recovery will be returned.
|
||||
Result result = Result.Success;
|
||||
|
||||
foreach (ReferenceCountedDisposable<IFileSystem> fs in FileSystems)
|
||||
for (int i = 0; i < _fileSystemCount; i++)
|
||||
{
|
||||
Result rc = fs.Target.Commit();
|
||||
Assert.SdkNotNull(_fileSystems[i]);
|
||||
|
||||
Result rc = _fileSystems[i].Target.Commit();
|
||||
|
||||
// If the commit failed, set the overall result if it hasn't been set yet.
|
||||
if (result.IsSuccess() && rc.IsFailure())
|
||||
{
|
||||
result = rc;
|
||||
@ -256,11 +290,15 @@ namespace LibHac.FsSrv.Impl
|
||||
private static Result RecoverCommit(ISaveDataMultiCommitCoreInterface multiCommitInterface,
|
||||
IFileSystem contextFs, SaveDataFileSystemServiceImpl saveService)
|
||||
{
|
||||
var contextFilePath = new Fs.Path();
|
||||
Result rc = PathFunctions.SetUpFixedPath(ref contextFilePath, CommitContextFileName);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
IFile contextFile = null;
|
||||
try
|
||||
{
|
||||
// Read the multi-commit context
|
||||
Result rc = contextFs.OpenFile(out contextFile, CommitContextFileName, OpenMode.ReadWrite);
|
||||
rc = contextFs.OpenFile(out contextFile, in contextFilePath, OpenMode.ReadWrite);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
Unsafe.SkipInit(out Context context);
|
||||
@ -330,12 +368,13 @@ namespace LibHac.FsSrv.Impl
|
||||
{
|
||||
rc = multiCommitInterface.RecoverProvisionallyCommittedSaveData(in savesToRecover[i], false);
|
||||
|
||||
if (recoveryResult.IsSuccess() && rc.IsFailure())
|
||||
if (rc.IsFailure() && !recoveryResult.IsFailure())
|
||||
{
|
||||
recoveryResult = rc;
|
||||
}
|
||||
}
|
||||
|
||||
contextFilePath.Dispose();
|
||||
return recoveryResult;
|
||||
}
|
||||
finally
|
||||
@ -426,13 +465,18 @@ namespace LibHac.FsSrv.Impl
|
||||
}
|
||||
}
|
||||
|
||||
var contextFilePath = new Fs.Path();
|
||||
rc = PathFunctions.SetUpFixedPath(ref contextFilePath, CommitContextFileName);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
// Delete the commit context file
|
||||
rc = contextFs.DeleteFile(CommitContextFileName);
|
||||
rc = contextFs.DeleteFile(in contextFilePath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = contextFs.Commit();
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
contextFilePath.Dispose();
|
||||
return recoveryResult;
|
||||
}
|
||||
|
||||
@ -440,55 +484,62 @@ namespace LibHac.FsSrv.Impl
|
||||
/// Recovers an interrupted multi-commit. The commit will either be completed or rolled back depending on
|
||||
/// where in the commit process it was interrupted. Does nothing if there is no commit to recover.
|
||||
/// </summary>
|
||||
/// <param name="fsServer">The <see cref="FileSystemServer"/> that contains the save data to recover.</param>
|
||||
/// <param name="multiCommitInterface">The core interface used for multi-commits.</param>
|
||||
/// <param name="saveService">The save data service.</param>
|
||||
/// <returns>The <see cref="Result"/> of the operation.<br/>
|
||||
/// <see cref="Result.Success"/>: The recovery was successful or there was no multi-commit to recover.</returns>
|
||||
public static Result Recover(ISaveDataMultiCommitCoreInterface multiCommitInterface,
|
||||
public static Result Recover(FileSystemServer fsServer, ISaveDataMultiCommitCoreInterface multiCommitInterface,
|
||||
SaveDataFileSystemServiceImpl saveService)
|
||||
{
|
||||
lock (Locker)
|
||||
{
|
||||
bool needsRecover = true;
|
||||
ReferenceCountedDisposable<IFileSystem> fileSystem = null;
|
||||
ref MultiCommitManagerGlobals globals = ref fsServer.Globals.MultiCommitManager;
|
||||
using ScopedLock<SdkMutexType> scopedLock = ScopedLock.Lock(ref globals.MultiCommitMutex);
|
||||
|
||||
try
|
||||
bool needsRecovery = true;
|
||||
ReferenceCountedDisposable<IFileSystem> fileSystem = null;
|
||||
|
||||
try
|
||||
{
|
||||
// Check if a multi-commit was interrupted by checking if there's a commit context file.
|
||||
Result rc = multiCommitInterface.OpenMultiCommitContext(out fileSystem);
|
||||
|
||||
if (rc.IsFailure())
|
||||
{
|
||||
// Check if a multi-commit was interrupted by checking if there's a commit context file.
|
||||
Result rc = multiCommitInterface.OpenMultiCommitContext(out fileSystem);
|
||||
if (!ResultFs.PathNotFound.Includes(rc) && !ResultFs.TargetNotFound.Includes(rc))
|
||||
return rc;
|
||||
|
||||
// Unable to open the multi-commit context file system, so there's nothing to recover
|
||||
needsRecovery = false;
|
||||
}
|
||||
|
||||
if (needsRecovery)
|
||||
{
|
||||
var contextFilePath = new Fs.Path();
|
||||
rc = PathFunctions.SetUpFixedPath(ref contextFilePath, CommitContextFileName);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = fileSystem.Target.OpenFile(out IFile file, in contextFilePath, OpenMode.Read);
|
||||
file?.Dispose();
|
||||
|
||||
if (rc.IsFailure())
|
||||
{
|
||||
if (!ResultFs.PathNotFound.Includes(rc) && !ResultFs.TargetNotFound.Includes(rc))
|
||||
return rc;
|
||||
|
||||
// Unable to open the multi-commit context file system, so there's nothing to recover
|
||||
needsRecover = false;
|
||||
// Unable to open the context file. No multi-commit to recover.
|
||||
if (ResultFs.PathNotFound.Includes(rc))
|
||||
needsRecovery = false;
|
||||
}
|
||||
|
||||
if (needsRecover)
|
||||
{
|
||||
rc = fileSystem.Target.OpenFile(out IFile file, CommitContextFileName, OpenMode.Read);
|
||||
file?.Dispose();
|
||||
|
||||
if (rc.IsFailure())
|
||||
{
|
||||
// Unable to open the context file. No multi-commit to recover.
|
||||
if (ResultFs.PathNotFound.Includes(rc))
|
||||
needsRecover = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!needsRecover)
|
||||
return Result.Success;
|
||||
|
||||
// There was a context file. Recover the unfinished commit.
|
||||
return Recover(multiCommitInterface, fileSystem.Target, saveService);
|
||||
}
|
||||
finally
|
||||
{
|
||||
fileSystem?.Dispose();
|
||||
contextFilePath.Dispose();
|
||||
}
|
||||
|
||||
if (!needsRecovery)
|
||||
return Result.Success;
|
||||
|
||||
// There was a context file. Recover the unfinished commit.
|
||||
return Recover(multiCommitInterface, fileSystem.Target, saveService);
|
||||
}
|
||||
finally
|
||||
{
|
||||
fileSystem?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@ -509,41 +560,58 @@ namespace LibHac.FsSrv.Impl
|
||||
ProvisionallyCommitted = 2
|
||||
}
|
||||
|
||||
private struct ContextUpdater
|
||||
private struct ContextUpdater : IDisposable
|
||||
{
|
||||
private IFileSystem _fileSystem;
|
||||
private Context _context;
|
||||
private IFileSystem _fileSystem;
|
||||
|
||||
public ContextUpdater(IFileSystem contextFileSystem)
|
||||
{
|
||||
_fileSystem = contextFileSystem;
|
||||
_context = default;
|
||||
_fileSystem = contextFileSystem;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_fileSystem is null) return;
|
||||
|
||||
var contextFilePath = new Fs.Path();
|
||||
PathFunctions.SetUpFixedPath(ref contextFilePath, CommitContextFileName).IgnoreResult();
|
||||
_fileSystem.DeleteFile(in contextFilePath).IgnoreResult();
|
||||
_fileSystem.Commit().IgnoreResult();
|
||||
|
||||
_fileSystem = null;
|
||||
contextFilePath.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates and writes the initial commit context to a file.
|
||||
/// </summary>
|
||||
/// <param name="commitCount">The counter.</param>
|
||||
/// <param name="counter">The counter.</param>
|
||||
/// <param name="fileSystemCount">The number of file systems being committed.</param>
|
||||
/// <returns>The <see cref="Result"/> of the operation.</returns>
|
||||
public Result Create(long commitCount, int fileSystemCount)
|
||||
public Result Create(long counter, int fileSystemCount)
|
||||
{
|
||||
var contextFilePath = new Fs.Path();
|
||||
Result rc = PathFunctions.SetUpFixedPath(ref contextFilePath, CommitContextFileName);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
IFile contextFile = null;
|
||||
|
||||
try
|
||||
{
|
||||
// Open context file and create if it doesn't exist
|
||||
Result rc = _fileSystem.OpenFile(out contextFile, CommitContextFileName, OpenMode.Read);
|
||||
rc = _fileSystem.OpenFile(out contextFile, in contextFilePath, OpenMode.Read);
|
||||
|
||||
if (rc.IsFailure())
|
||||
{
|
||||
if (!ResultFs.PathNotFound.Includes(rc))
|
||||
return rc;
|
||||
|
||||
rc = _fileSystem.CreateFile(CommitContextFileName, CommitContextFileSize, CreateFileOptions.None);
|
||||
rc = _fileSystem.CreateFile(in contextFilePath, CommitContextFileSize);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = _fileSystem.OpenFile(out contextFile, CommitContextFileName, OpenMode.Read);
|
||||
rc = _fileSystem.OpenFile(out contextFile, in contextFilePath, OpenMode.Read);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
}
|
||||
@ -554,13 +622,13 @@ namespace LibHac.FsSrv.Impl
|
||||
|
||||
try
|
||||
{
|
||||
Result rc = _fileSystem.OpenFile(out contextFile, CommitContextFileName, OpenMode.ReadWrite);
|
||||
rc = _fileSystem.OpenFile(out contextFile, in contextFilePath, OpenMode.ReadWrite);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
_context.Version = CurrentCommitContextVersion;
|
||||
_context.State = CommitState.NotCommitted;
|
||||
_context.FileSystemCount = fileSystemCount;
|
||||
_context.Counter = commitCount;
|
||||
_context.Counter = counter;
|
||||
|
||||
// Write the initial context to the file
|
||||
rc = contextFile.Write(0, SpanHelpers.AsByteSpan(ref _context), WriteOption.None);
|
||||
@ -574,7 +642,11 @@ namespace LibHac.FsSrv.Impl
|
||||
contextFile?.Dispose();
|
||||
}
|
||||
|
||||
return _fileSystem.Commit();
|
||||
rc = _fileSystem.Commit();
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
contextFilePath.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -584,11 +656,15 @@ namespace LibHac.FsSrv.Impl
|
||||
/// <returns>The <see cref="Result"/> of the operation.</returns>
|
||||
public Result CommitProvisionallyDone()
|
||||
{
|
||||
var contextFilePath = new Fs.Path();
|
||||
Result rc = PathFunctions.SetUpFixedPath(ref contextFilePath, CommitContextFileName);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
IFile contextFile = null;
|
||||
|
||||
try
|
||||
{
|
||||
Result rc = _fileSystem.OpenFile(out contextFile, CommitContextFileName, OpenMode.ReadWrite);
|
||||
rc = _fileSystem.OpenFile(out contextFile, in contextFilePath, OpenMode.ReadWrite);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
_context.State = CommitState.ProvisionallyCommitted;
|
||||
@ -604,6 +680,7 @@ namespace LibHac.FsSrv.Impl
|
||||
contextFile?.Dispose();
|
||||
}
|
||||
|
||||
contextFilePath.Dispose();
|
||||
return _fileSystem.Commit();
|
||||
}
|
||||
|
||||
@ -613,25 +690,20 @@ namespace LibHac.FsSrv.Impl
|
||||
/// <returns>The <see cref="Result"/> of the operation.</returns>
|
||||
public Result CommitDone()
|
||||
{
|
||||
Result rc = _fileSystem.DeleteFile(CommitContextFileName);
|
||||
var contextFilePath = new Fs.Path();
|
||||
Result rc = PathFunctions.SetUpFixedPath(ref contextFilePath, CommitContextFileName);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = _fileSystem.DeleteFile(in contextFilePath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = _fileSystem.Commit();
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
_fileSystem = null;
|
||||
contextFilePath.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_fileSystem is null) return;
|
||||
|
||||
_fileSystem.DeleteFile(CommitContextFileName).IgnoreResult();
|
||||
_fileSystem.Commit().IgnoreResult();
|
||||
|
||||
_fileSystem = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -47,28 +47,24 @@ namespace LibHac.FsSrv.Impl
|
||||
}
|
||||
|
||||
// ReSharper disable once RedundantOverriddenMember
|
||||
protected override Result DoOpenFile(out IFile file, U8Span path, OpenMode mode)
|
||||
protected override Result DoOpenFile(out IFile file, in Path path, OpenMode mode)
|
||||
{
|
||||
// Todo: Implement
|
||||
return base.DoOpenFile(out file, path, mode);
|
||||
}
|
||||
|
||||
// ReSharper disable once RedundantOverriddenMember
|
||||
protected override Result DoOpenDirectory(out IDirectory directory, U8Span path, OpenDirectoryMode mode)
|
||||
protected override Result DoOpenDirectory(out IDirectory directory, in Path path, OpenDirectoryMode mode)
|
||||
{
|
||||
// Todo: Implement
|
||||
return base.DoOpenDirectory(out directory, path, mode);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
public override void Dispose()
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_entryCountSemaphore?.Dispose();
|
||||
_mountCountSemaphore?.Dispose();
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
_entryCountSemaphore?.Dispose();
|
||||
_mountCountSemaphore?.Dispose();
|
||||
base.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,106 +0,0 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using LibHac.Common;
|
||||
using LibHac.Diag;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Util;
|
||||
|
||||
namespace LibHac.FsSrv.Impl
|
||||
{
|
||||
public ref struct PathNormalizer
|
||||
{
|
||||
private readonly U8Span _path;
|
||||
private byte[] _rentedArray;
|
||||
|
||||
public U8Span Path => _path;
|
||||
|
||||
public Result Result { get; }
|
||||
|
||||
public PathNormalizer(U8Span path, Option option)
|
||||
{
|
||||
|
||||
if (option.HasFlag(Option.AcceptEmpty) && path.IsEmpty())
|
||||
{
|
||||
_path = path;
|
||||
_rentedArray = null;
|
||||
Result = Result.Success;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool preserveUnc = option.HasFlag(Option.PreserveUnc);
|
||||
bool preserveTrailingSeparator = option.HasFlag(Option.PreserveTrailingSeparator);
|
||||
bool hasMountName = option.HasFlag(Option.HasMountName);
|
||||
Result = Normalize(out _path, out _rentedArray, path, preserveUnc, preserveTrailingSeparator,
|
||||
hasMountName);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_rentedArray is not null)
|
||||
ArrayPool<byte>.Shared.Return(_rentedArray);
|
||||
}
|
||||
|
||||
private static Result Normalize(out U8Span normalizedPath, out byte[] rentedBuffer, U8Span path,
|
||||
bool preserveUnc, bool preserveTailSeparator, bool hasMountName)
|
||||
{
|
||||
Assert.SdkRequiresNotNullOut(out rentedBuffer);
|
||||
|
||||
normalizedPath = default;
|
||||
rentedBuffer = null;
|
||||
|
||||
Result rc = Fs.PathNormalizer.IsNormalized(out bool isNormalized, path, preserveUnc, hasMountName);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (isNormalized)
|
||||
{
|
||||
normalizedPath = path;
|
||||
}
|
||||
else
|
||||
{
|
||||
byte[] buffer = null;
|
||||
try
|
||||
{
|
||||
buffer = ArrayPool<byte>.Shared.Rent(PathTool.EntryNameLengthMax + 1);
|
||||
|
||||
rc = Fs.PathNormalizer.Normalize(buffer.AsSpan(0, PathTool.EntryNameLengthMax + 1),
|
||||
out long normalizedLength, path, preserveUnc, hasMountName);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
// Add the tail separator if needed
|
||||
if (preserveTailSeparator)
|
||||
{
|
||||
int pathLength = StringUtils.GetLength(path, PathTool.EntryNameLengthMax);
|
||||
if (Fs.PathNormalizer.IsSeparator(path[pathLength - 1]) &&
|
||||
!Fs.PathNormalizer.IsSeparator(buffer[normalizedLength - 1]))
|
||||
{
|
||||
Assert.SdkLess(normalizedLength, PathTool.EntryNameLengthMax);
|
||||
|
||||
buffer[(int)normalizedLength] = StringTraits.DirectorySeparator;
|
||||
buffer[(int)normalizedLength + 1] = StringTraits.NullTerminator;
|
||||
}
|
||||
}
|
||||
|
||||
normalizedPath = new U8Span(Shared.Move(ref buffer));
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (buffer is not null)
|
||||
ArrayPool<byte>.Shared.Return(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum Option
|
||||
{
|
||||
None = 0,
|
||||
PreserveUnc = (1 << 0),
|
||||
PreserveTrailingSeparator = (1 << 1),
|
||||
HasMountName = (1 << 2),
|
||||
AcceptEmpty = (1 << 3)
|
||||
}
|
||||
}
|
||||
}
|
@ -176,7 +176,7 @@ namespace LibHac.FsSrv.Impl
|
||||
return new ReferenceCountedDisposable<IFileSystem>(resultConvertFileSystem);
|
||||
}
|
||||
|
||||
protected override Result DoOpenFile(out IFile file, U8Span path, OpenMode mode)
|
||||
protected override Result DoOpenFile(out IFile file, in Path path, OpenMode mode)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out file);
|
||||
|
||||
@ -187,7 +187,7 @@ namespace LibHac.FsSrv.Impl
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoOpenDirectory(out IDirectory directory, U8Span path, OpenDirectoryMode mode)
|
||||
protected override Result DoOpenDirectory(out IDirectory directory, in Path path, OpenDirectoryMode mode)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out directory);
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
using System;
|
||||
using LibHac.Common;
|
||||
using LibHac.Diag;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.Fs.Impl;
|
||||
using LibHac.FsSystem;
|
||||
using LibHac.Util;
|
||||
|
||||
@ -10,132 +10,32 @@ namespace LibHac.FsSrv.Impl
|
||||
{
|
||||
internal static class Utility
|
||||
{
|
||||
public static Result EnsureDirectory(IFileSystem fileSystem, U8Span path)
|
||||
public static bool IsHostFsMountName(ReadOnlySpan<byte> name)
|
||||
{
|
||||
FsPath.FromSpan(out FsPath pathBuffer, path.Value).IgnoreResult();
|
||||
|
||||
int pathLength = StringUtils.GetLength(path);
|
||||
|
||||
if (pathLength > 0)
|
||||
{
|
||||
// Remove any trailing directory separators
|
||||
while (pathLength > 0 && path[pathLength - 1] == StringTraits.DirectorySeparator)
|
||||
{
|
||||
pathLength--;
|
||||
}
|
||||
|
||||
// Copy the path to a mutable buffer
|
||||
path.Value.Slice(0, pathLength).CopyTo(pathBuffer.Str);
|
||||
}
|
||||
|
||||
pathBuffer.Str[pathLength] = StringTraits.NullTerminator;
|
||||
|
||||
return EnsureDirectoryImpl(fileSystem, pathBuffer.Str.Slice(0, pathLength));
|
||||
}
|
||||
|
||||
private static Result EnsureDirectoryImpl(IFileSystem fileSystem, Span<byte> path)
|
||||
{
|
||||
// Double check the trailing separators have been trimmed
|
||||
Assert.SdkRequires(path.Length <= 1 || path[path.Length - 1] != StringTraits.DirectorySeparator);
|
||||
|
||||
// Use the root path if the input path is empty
|
||||
var pathToCheck = new U8Span(path.IsEmpty ? FileSystemRootPath : path);
|
||||
|
||||
// Check if the path exists
|
||||
Result rc = fileSystem.GetEntryType(out DirectoryEntryType entryType, pathToCheck);
|
||||
|
||||
if (rc.IsFailure())
|
||||
{
|
||||
// Something went wrong if we get a result other than PathNotFound
|
||||
if (!ResultFs.PathNotFound.Includes(rc))
|
||||
return rc;
|
||||
|
||||
if (path.Length <= 0)
|
||||
{
|
||||
// The file system either reported that the root directory doesn't exist,
|
||||
// or the input path had a negative length
|
||||
return ResultFs.PathNotFound.Log();
|
||||
}
|
||||
|
||||
// The path does not exist. Ensure its parent directory exists
|
||||
rc = EnsureParentDirectoryImpl(fileSystem, path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
// The parent directory exists, we can now create a directory at the input path
|
||||
rc = fileSystem.CreateDirectory(new U8Span(path));
|
||||
|
||||
if (rc.IsSuccess())
|
||||
{
|
||||
// The directory was successfully created
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
if (!ResultFs.PathAlreadyExists.Includes(rc))
|
||||
return rc;
|
||||
|
||||
// Someone else created a file system entry at the input path after we checked
|
||||
// if the path existed. Get the entry type to check if it's a directory.
|
||||
rc = fileSystem.GetEntryType(out entryType, new U8Span(path));
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
|
||||
// We want the entry that exists at the input path to be a directory
|
||||
// Return PathAlreadyExists if it's a file
|
||||
if (entryType == DirectoryEntryType.File)
|
||||
return ResultFs.PathAlreadyExists.Log();
|
||||
|
||||
// A directory exists at the input path. Success
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
private static Result EnsureParentDirectoryImpl(IFileSystem fileSystem, Span<byte> path)
|
||||
{
|
||||
// The path should not be empty or have a trailing directory separator
|
||||
Assert.SdkRequiresLess(0, path.Length);
|
||||
Assert.SdkRequiresNotEqual(path[path.Length - 1], StringTraits.DirectorySeparator);
|
||||
|
||||
// Make sure the path's not too long
|
||||
if (path.Length > PathTool.EntryNameLengthMax)
|
||||
return ResultFs.TooLongPath.Log();
|
||||
|
||||
// Iterate until we run out of path or find the next separator
|
||||
int length = path.Length - 1;
|
||||
while (length > 0 && path[length] != StringTraits.DirectorySeparator)
|
||||
{
|
||||
length--;
|
||||
}
|
||||
|
||||
if (length == 0)
|
||||
{
|
||||
// We hit the beginning of the path. Ensure the root directory exists and return
|
||||
return EnsureDirectoryImpl(fileSystem, Span<byte>.Empty);
|
||||
}
|
||||
|
||||
// We found the length of the parent directory. Ensure it exists
|
||||
path[length] = StringTraits.NullTerminator;
|
||||
Result rc = EnsureDirectoryImpl(fileSystem, path.Slice(0, length));
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
// Restore the separator
|
||||
path[length] = StringTraits.DirectorySeparator;
|
||||
return Result.Success;
|
||||
return StringUtils.Compare(name, CommonMountNames.HostRootFileSystemMountName) == 0;
|
||||
}
|
||||
|
||||
public static Result CreateSubDirectoryFileSystem(out ReferenceCountedDisposable<IFileSystem> subDirFileSystem,
|
||||
ref ReferenceCountedDisposable<IFileSystem> baseFileSystem, U8Span subPath, bool preserveUnc = false)
|
||||
ref ReferenceCountedDisposable<IFileSystem> baseFileSystem, in Path rootPath)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out subDirFileSystem);
|
||||
|
||||
if (rootPath.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);
|
||||
Result rc = baseFileSystem.Target.OpenDirectory(out IDirectory dir, rootPath, 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 rootPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
subDirFileSystem = subDirFs.AddReference<IFileSystem>();
|
||||
@ -144,22 +44,22 @@ namespace LibHac.FsSrv.Impl
|
||||
}
|
||||
|
||||
public static Result WrapSubDirectory(out ReferenceCountedDisposable<IFileSystem> fileSystem,
|
||||
ref ReferenceCountedDisposable<IFileSystem> baseFileSystem, U8Span path, bool createIfMissing)
|
||||
ref ReferenceCountedDisposable<IFileSystem> baseFileSystem, in Path rootPath, bool createIfMissing)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out fileSystem);
|
||||
|
||||
// The path must already exist if we're not automatically creating it
|
||||
if (!createIfMissing)
|
||||
{
|
||||
Result result = baseFileSystem.Target.GetEntryType(out _, path);
|
||||
Result result = baseFileSystem.Target.GetEntryType(out _, in rootPath);
|
||||
if (result.IsFailure()) return result;
|
||||
}
|
||||
|
||||
// Ensure the path exists or check if it's a directory
|
||||
Result rc = EnsureDirectory(baseFileSystem.Target, path);
|
||||
Result rc = Utility12.EnsureDirectory(baseFileSystem.Target, in rootPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return CreateSubDirectoryFileSystem(out fileSystem, ref baseFileSystem, path);
|
||||
return CreateSubDirectoryFileSystem(out fileSystem, ref baseFileSystem, rootPath);
|
||||
}
|
||||
|
||||
public static long ConvertZeroCommitId(in SaveDataExtraData extraData)
|
||||
@ -172,11 +72,5 @@ namespace LibHac.FsSrv.Impl
|
||||
Crypto.Sha256.GenerateSha256Hash(SpanHelpers.AsReadOnlyByteSpan(in extraData), hash);
|
||||
return BitConverter.ToInt64(hash);
|
||||
}
|
||||
|
||||
private static ReadOnlySpan<byte> FileSystemRootPath => // /
|
||||
new[]
|
||||
{
|
||||
(byte) '/'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using LibHac.Common;
|
||||
using LibHac.Diag;
|
||||
using LibHac.Fs;
|
||||
using LibHac.FsSrv.Impl;
|
||||
using LibHac.FsSrv.Sf;
|
||||
@ -9,13 +8,12 @@ using LibHac.FsSystem;
|
||||
using LibHac.Lr;
|
||||
using LibHac.Ncm;
|
||||
using LibHac.Spl;
|
||||
using LibHac.Util;
|
||||
using IFileSystem = LibHac.Fs.Fsa.IFileSystem;
|
||||
using IStorage = LibHac.Fs.IStorage;
|
||||
using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem;
|
||||
using IStorageSf = LibHac.FsSrv.Sf.IStorage;
|
||||
using Path = LibHac.Lr.Path;
|
||||
using PathNormalizer = LibHac.FsSrv.Impl.PathNormalizer;
|
||||
using Path = LibHac.Fs.Path;
|
||||
using Utility = LibHac.FsSrv.Impl.Utility;
|
||||
|
||||
namespace LibHac.FsSrv
|
||||
{
|
||||
@ -27,15 +25,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(
|
||||
@ -72,10 +70,17 @@ namespace LibHac.FsSrv
|
||||
|
||||
if (fsType != FileSystemProxyType.Manual)
|
||||
{
|
||||
if (fsType == FileSystemProxyType.Logo || fsType == FileSystemProxyType.Control)
|
||||
return ResultFs.NotImplemented.Log();
|
||||
else
|
||||
return ResultFs.InvalidArgument.Log();
|
||||
switch (fsType)
|
||||
{
|
||||
case FileSystemProxyType.Logo:
|
||||
case FileSystemProxyType.Control:
|
||||
case FileSystemProxyType.Meta:
|
||||
case FileSystemProxyType.Data:
|
||||
case FileSystemProxyType.Package:
|
||||
return ResultFs.NotImplemented.Log();
|
||||
default:
|
||||
return ResultFs.InvalidArgument.Log();
|
||||
}
|
||||
}
|
||||
|
||||
Accessibility accessibility =
|
||||
@ -89,28 +94,17 @@ namespace LibHac.FsSrv
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
// Try to find the path to the original version of the file system
|
||||
Result originalResult = ServiceImpl.ResolveApplicationHtmlDocumentPath(out Path originalPath,
|
||||
new Ncm.ApplicationId(programId.Value), ownerProgramInfo.StorageId);
|
||||
var originalPath = new Path();
|
||||
Result originalResult = ServiceImpl.ResolveApplicationHtmlDocumentPath(out bool isDirectory,
|
||||
ref originalPath, new Ncm.ApplicationId(programId.Value), ownerProgramInfo.StorageId);
|
||||
|
||||
// The file system might have a patch version with no original version, so continue if not found
|
||||
if (originalResult.IsFailure() && !ResultLr.HtmlDocumentNotFound.Includes(originalResult))
|
||||
return originalResult;
|
||||
|
||||
// Use a separate bool because ref structs can't be used as type parameters
|
||||
bool originalPathNormalizerHasValue = false;
|
||||
PathNormalizer originalPathNormalizer = default;
|
||||
|
||||
// Normalize the original version path if found
|
||||
if (originalResult.IsSuccess())
|
||||
{
|
||||
originalPathNormalizer = new PathNormalizer(originalPath, GetPathNormalizerOptions(originalPath));
|
||||
if (originalPathNormalizer.Result.IsFailure()) return originalPathNormalizer.Result;
|
||||
|
||||
originalPathNormalizerHasValue = true;
|
||||
}
|
||||
|
||||
// Try to find the path to the patch file system
|
||||
Result patchResult = ServiceImpl.ResolveRegisteredHtmlDocumentPath(out Path patchPath, programId.Value);
|
||||
var patchPath = new Path();
|
||||
Result patchResult = ServiceImpl.ResolveRegisteredHtmlDocumentPath(ref patchPath, programId.Value);
|
||||
|
||||
ReferenceCountedDisposable<IFileSystem> tempFileSystem = null;
|
||||
ReferenceCountedDisposable<IRomFileSystemAccessFailureManager> accessFailureManager = null;
|
||||
@ -122,37 +116,25 @@ namespace LibHac.FsSrv
|
||||
if (originalResult.IsFailure())
|
||||
return originalResult;
|
||||
|
||||
Assert.SdkAssert(originalPathNormalizerHasValue);
|
||||
|
||||
// There is an original version and no patch version. Open the original directly
|
||||
rc = ServiceImpl.OpenFileSystem(out tempFileSystem, originalPathNormalizer.Path, fsType, programId.Value);
|
||||
rc = ServiceImpl.OpenFileSystem(out tempFileSystem, in originalPath, fsType, programId.Value,
|
||||
isDirectory);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Get the normalized path to the original file system
|
||||
U8Span normalizedOriginalPath;
|
||||
if (originalPathNormalizerHasValue)
|
||||
{
|
||||
normalizedOriginalPath = originalPathNormalizer.Path;
|
||||
}
|
||||
else
|
||||
{
|
||||
normalizedOriginalPath = U8Span.Empty;
|
||||
}
|
||||
|
||||
// Normalize the path to the patch file system
|
||||
using var patchPathNormalizer = new PathNormalizer(patchPath, GetPathNormalizerOptions(patchPath));
|
||||
if (patchPathNormalizer.Result.IsFailure()) return patchPathNormalizer.Result;
|
||||
|
||||
if (patchResult.IsFailure())
|
||||
return patchResult;
|
||||
|
||||
U8Span normalizedPatchPath = patchPathNormalizer.Path;
|
||||
var emptyPath = new Path();
|
||||
rc = emptyPath.InitializeAsEmpty();
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
ref Path originalNcaPath = ref originalResult.IsSuccess() ? ref originalPath : ref emptyPath;
|
||||
|
||||
// Open the file system using both the original and patch versions
|
||||
rc = ServiceImpl.OpenFileSystemWithPatch(out tempFileSystem, normalizedOriginalPath,
|
||||
normalizedPatchPath, fsType, programId.Value);
|
||||
rc = ServiceImpl.OpenFileSystemWithPatch(out tempFileSystem, in originalNcaPath, in patchPath,
|
||||
fsType, programId.Value);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
|
||||
@ -163,7 +145,7 @@ namespace LibHac.FsSrv
|
||||
accessFailureManager = SelfReference.AddReference<IRomFileSystemAccessFailureManager>();
|
||||
tempFileSystem = DeepRetryFileSystem.CreateShared(ref tempFileSystem, ref accessFailureManager);
|
||||
|
||||
fileSystem = FileSystemInterfaceAdapter.CreateShared(ref tempFileSystem);
|
||||
fileSystem = FileSystemInterfaceAdapter.CreateShared(ref tempFileSystem, false);
|
||||
return Result.Success;
|
||||
}
|
||||
finally
|
||||
@ -200,10 +182,10 @@ namespace LibHac.FsSrv
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Result OpenFileSystemWithId(out ReferenceCountedDisposable<IFileSystemSf> fileSystem, in FspPath path,
|
||||
public Result OpenFileSystemWithId(out ReferenceCountedDisposable<IFileSystemSf> outFileSystem, in FspPath path,
|
||||
ulong id, FileSystemProxyType fsType)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out fileSystem);
|
||||
UnsafeHelpers.SkipParamInit(out outFileSystem);
|
||||
|
||||
Result rc = GetProgramInfo(out ProgramInfo programInfo);
|
||||
if (rc.IsFailure()) return rc;
|
||||
@ -251,25 +233,34 @@ namespace LibHac.FsSrv
|
||||
|
||||
bool canMountSystemDataPrivate = ac.GetAccessibilityFor(AccessibilityType.MountSystemDataPrivate).CanRead;
|
||||
|
||||
using var normalizer = new PathNormalizer(path, GetPathNormalizerOptions(path));
|
||||
if (normalizer.Result.IsFailure()) return normalizer.Result;
|
||||
var pathNormalized = new Path();
|
||||
rc = pathNormalized.InitializeWithReplaceUnc(path.Str);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
ReferenceCountedDisposable<IFileSystem> fs = null;
|
||||
var pathFlags = new PathFlags();
|
||||
pathFlags.AllowWindowsPath();
|
||||
pathFlags.AllowMountName();
|
||||
rc = pathNormalized.Normalize(pathFlags);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
bool isDirectory = PathUtility.IsDirectoryPath(in path);
|
||||
|
||||
ReferenceCountedDisposable<IFileSystem> fileSystem = null;
|
||||
|
||||
try
|
||||
{
|
||||
rc = ServiceImpl.OpenFileSystem(out fs, out _, path, fsType,
|
||||
canMountSystemDataPrivate, id);
|
||||
rc = ServiceImpl.OpenFileSystem(out fileSystem, in pathNormalized, fsType, canMountSystemDataPrivate,
|
||||
id, isDirectory);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
// Create an SF adapter for the file system
|
||||
fileSystem = FileSystemInterfaceAdapter.CreateShared(ref fs);
|
||||
outFileSystem = FileSystemInterfaceAdapter.CreateShared(ref fileSystem, false);
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
finally
|
||||
{
|
||||
fs?.Dispose();
|
||||
fileSystem?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@ -316,7 +307,7 @@ namespace LibHac.FsSrv
|
||||
if (tempFileSystem is null)
|
||||
return ResultFs.AllocationMemoryFailedAllocateShared.Log();
|
||||
|
||||
fileSystem = FileSystemInterfaceAdapter.CreateShared(ref tempFileSystem);
|
||||
fileSystem = FileSystemInterfaceAdapter.CreateShared(ref tempFileSystem, false);
|
||||
if (fileSystem is null)
|
||||
return ResultFs.AllocationMemoryFailedCreateShared.Log();
|
||||
|
||||
@ -334,11 +325,11 @@ namespace LibHac.FsSrv
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Result GetRightsId(out RightsId rightsId, ProgramId programId, StorageId storageId)
|
||||
public Result GetRightsId(out RightsId outRightsId, ProgramId programId, StorageId storageId)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out rightsId);
|
||||
UnsafeHelpers.SkipParamInit(out outRightsId);
|
||||
|
||||
using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageType.All);
|
||||
using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageType.All);
|
||||
|
||||
Result rc = GetProgramInfo(out ProgramInfo programInfo);
|
||||
if (rc.IsFailure()) return rc;
|
||||
@ -346,24 +337,27 @@ namespace LibHac.FsSrv
|
||||
if (!programInfo.AccessControl.CanCall(OperationType.GetRightsId))
|
||||
return ResultFs.PermissionDenied.Log();
|
||||
|
||||
rc = ServiceImpl.ResolveProgramPath(out Path programPath, programId, storageId);
|
||||
var programPath = new Path();
|
||||
rc = ServiceImpl.ResolveProgramPath(out bool isDirectory, ref programPath, programId, storageId);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
using var normalizer = new PathNormalizer(programPath, GetPathNormalizerOptions(programPath));
|
||||
if (normalizer.Result.IsFailure()) return normalizer.Result;
|
||||
if (isDirectory)
|
||||
return ResultFs.TargetNotFound.Log();
|
||||
|
||||
rc = ServiceImpl.GetRightsId(out RightsId tempRightsId, out _, normalizer.Path, programId);
|
||||
rc = ServiceImpl.GetRightsId(out RightsId rightsId, out _, in programPath, programId);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rightsId = tempRightsId;
|
||||
outRightsId = rightsId;
|
||||
|
||||
programPath.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result GetRightsIdAndKeyGenerationByPath(out RightsId rightsId, out byte keyGeneration, in FspPath path)
|
||||
public Result GetRightsIdAndKeyGenerationByPath(out RightsId outRightsId, out byte outKeyGeneration, in FspPath path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out rightsId, out keyGeneration);
|
||||
UnsafeHelpers.SkipParamInit(out outRightsId, out outKeyGeneration);
|
||||
|
||||
using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageType.All);
|
||||
using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageType.All);
|
||||
|
||||
Result rc = GetProgramInfo(out ProgramInfo programInfo);
|
||||
if (rc.IsFailure()) return rc;
|
||||
@ -371,52 +365,61 @@ namespace LibHac.FsSrv
|
||||
if (!programInfo.AccessControl.CanCall(OperationType.GetRightsId))
|
||||
return ResultFs.PermissionDenied.Log();
|
||||
|
||||
using var normalizer = new PathNormalizer(path, GetPathNormalizerOptions(path));
|
||||
if (normalizer.Result.IsFailure()) return normalizer.Result;
|
||||
var pathNormalized = new Path();
|
||||
rc = pathNormalized.Initialize(path.Str);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = ServiceImpl.GetRightsId(out RightsId tempRightsId, out byte tempKeyGeneration, normalizer.Path,
|
||||
var pathFlags = new PathFlags();
|
||||
pathFlags.AllowWindowsPath();
|
||||
pathFlags.AllowMountName();
|
||||
rc = pathNormalized.Normalize(pathFlags);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (PathUtility.IsDirectoryPath(in path))
|
||||
return ResultFs.TargetNotFound.Log();
|
||||
|
||||
rc = ServiceImpl.GetRightsId(out RightsId rightsId, out byte keyGeneration, in pathNormalized,
|
||||
new ProgramId(ulong.MaxValue));
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rightsId = tempRightsId;
|
||||
keyGeneration = tempKeyGeneration;
|
||||
outRightsId = rightsId;
|
||||
outKeyGeneration = keyGeneration;
|
||||
|
||||
pathNormalized.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
private Result OpenDataFileSystemCore(out ReferenceCountedDisposable<IFileSystem> fileSystem, out bool isHostFs,
|
||||
private Result OpenDataFileSystemCore(out ReferenceCountedDisposable<IFileSystem> outFileSystem, out bool isHostFs,
|
||||
ulong programId, StorageId storageId)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out fileSystem, out isHostFs);
|
||||
UnsafeHelpers.SkipParamInit(out outFileSystem, out isHostFs);
|
||||
|
||||
if (Unsafe.IsNullRef(ref isHostFs))
|
||||
return ResultFs.NullptrArgument.Log();
|
||||
|
||||
StorageType storageFlag = ServiceImpl.GetStorageFlag(programId);
|
||||
using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(storageFlag);
|
||||
using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag);
|
||||
|
||||
Result rc = ServiceImpl.ResolveRomPath(out Path romPath, programId, storageId);
|
||||
var programPath = new Path();
|
||||
Result rc = ServiceImpl.ResolveRomPath(out bool isDirectory, ref programPath, programId, storageId);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
using var normalizer = new PathNormalizer(romPath, GetPathNormalizerOptions(romPath));
|
||||
if (normalizer.Result.IsFailure()) return normalizer.Result;
|
||||
isHostFs = Utility.IsHostFsMountName(programPath.GetString());
|
||||
|
||||
isHostFs = IsHostFs(romPath);
|
||||
|
||||
ReferenceCountedDisposable<IFileSystem> tempFileSystem = null;
|
||||
ReferenceCountedDisposable<IFileSystem> fileSystem = null;
|
||||
try
|
||||
{
|
||||
rc = ServiceImpl.OpenDataFileSystem(out tempFileSystem, normalizer.Path, FileSystemProxyType.Rom,
|
||||
programId);
|
||||
rc = ServiceImpl.OpenDataFileSystem(out fileSystem, in programPath, FileSystemProxyType.Rom, programId, isDirectory);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
tempFileSystem = StorageLayoutTypeSetFileSystem.CreateShared(ref tempFileSystem, storageFlag);
|
||||
fileSystem = StorageLayoutTypeSetFileSystem.CreateShared(ref fileSystem, storageFlag);
|
||||
|
||||
Shared.Move(out fileSystem, ref tempFileSystem);
|
||||
outFileSystem = Shared.Move(ref fileSystem);
|
||||
return Result.Success;
|
||||
}
|
||||
finally
|
||||
{
|
||||
tempFileSystem?.Dispose();
|
||||
fileSystem?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@ -448,7 +451,7 @@ namespace LibHac.FsSrv
|
||||
tempFileSystem = AsynchronousAccessFileSystem.CreateShared(ref tempFileSystem);
|
||||
|
||||
// Create an SF adapter for the file system
|
||||
fileSystem = FileSystemInterfaceAdapter.CreateShared(ref tempFileSystem);
|
||||
fileSystem = FileSystemInterfaceAdapter.CreateShared(ref tempFileSystem, false);
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
@ -499,13 +502,11 @@ namespace LibHac.FsSrv
|
||||
if (!programInfo.AccessControl.CanCall(OperationType.RegisterUpdatePartition))
|
||||
return ResultFs.PermissionDenied.Log();
|
||||
|
||||
rc = ServiceImpl.ResolveRomPath(out Path romPath, programInfo.ProgramIdValue, programInfo.StorageId);
|
||||
var programPath = new Path();
|
||||
rc = ServiceImpl.ResolveRomPath(out _, ref programPath, programInfo.ProgramIdValue, programInfo.StorageId);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
using var normalizer = new PathNormalizer(romPath, GetPathNormalizerOptions(romPath));
|
||||
if (normalizer.Result.IsFailure()) return normalizer.Result;
|
||||
|
||||
return ServiceImpl.RegisterUpdatePartition(programInfo.ProgramIdValue, normalizer.Path);
|
||||
return ServiceImpl.RegisterUpdatePartition(programInfo.ProgramIdValue, in programPath);
|
||||
}
|
||||
|
||||
public Result OpenRegisteredUpdatePartition(out ReferenceCountedDisposable<IFileSystemSf> fileSystem)
|
||||
@ -537,7 +538,7 @@ namespace LibHac.FsSrv
|
||||
if (tempFileSystem is null)
|
||||
return ResultFs.AllocationMemoryFailedAllocateShared.Log();
|
||||
|
||||
fileSystem = FileSystemInterfaceAdapter.CreateShared(ref tempFileSystem);
|
||||
fileSystem = FileSystemInterfaceAdapter.CreateShared(ref tempFileSystem, false);
|
||||
if (fileSystem is null)
|
||||
return ResultFs.AllocationMemoryFailedCreateShared.Log();
|
||||
|
||||
@ -620,21 +621,6 @@ namespace LibHac.FsSrv
|
||||
return ServiceImpl.GetProgramInfoByProgramId(out programInfo, programId);
|
||||
}
|
||||
|
||||
private PathNormalizer.Option GetPathNormalizerOptions(U8Span path)
|
||||
{
|
||||
// Set the PreserveUnc flag if the path is on the host file system
|
||||
PathNormalizer.Option hostOption = IsHostFs(path) ? PathNormalizer.Option.PreserveUnc : PathNormalizer.Option.None;
|
||||
return PathNormalizer.Option.HasMountName | PathNormalizer.Option.PreserveTrailingSeparator | hostOption;
|
||||
}
|
||||
|
||||
private bool IsHostFs(U8Span path)
|
||||
{
|
||||
int hostMountLength = StringUtils.GetLength(CommonPaths.HostRootFileSystemMountName,
|
||||
PathTools.MountNameLengthMax);
|
||||
|
||||
return StringUtils.Compare(path, CommonPaths.HostRootFileSystemMountName, hostMountLength) == 0;
|
||||
}
|
||||
|
||||
Result IRomFileSystemAccessFailureManager.OpenDataStorageCore(out ReferenceCountedDisposable<IStorage> storage,
|
||||
out Hash ncaHeaderDigest, ulong id, StorageId storageId)
|
||||
{
|
||||
|
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Buffers.Text;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
@ -13,8 +14,6 @@ using LibHac.Os;
|
||||
using LibHac.Spl;
|
||||
using LibHac.Util;
|
||||
using RightsId = LibHac.Fs.RightsId;
|
||||
using Utility = LibHac.FsSrv.Impl.Utility;
|
||||
using PathLr = LibHac.Lr.Path;
|
||||
|
||||
namespace LibHac.FsSrv
|
||||
{
|
||||
@ -65,15 +64,23 @@ namespace LibHac.FsSrv
|
||||
public bool CanMountNca;
|
||||
}
|
||||
|
||||
public Result OpenFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem, U8Span path,
|
||||
FileSystemProxyType type, ulong id)
|
||||
public Result OpenFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem, in Path path,
|
||||
FileSystemProxyType type, ulong id, bool isDirectory)
|
||||
{
|
||||
return OpenFileSystem(out fileSystem, out Unsafe.NullRef<CodeVerificationData>(), path, type, false, id);
|
||||
return OpenFileSystem(out fileSystem, out Unsafe.NullRef<CodeVerificationData>(), in path, type, false, id,
|
||||
isDirectory);
|
||||
}
|
||||
|
||||
public Result OpenFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem, in Path path,
|
||||
FileSystemProxyType type, bool canMountSystemDataPrivate, ulong id, bool isDirectory)
|
||||
{
|
||||
return OpenFileSystem(out fileSystem, out Unsafe.NullRef<CodeVerificationData>(), in path, type,
|
||||
canMountSystemDataPrivate, id, isDirectory);
|
||||
}
|
||||
|
||||
public Result OpenFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem,
|
||||
out CodeVerificationData verificationData, U8Span path, FileSystemProxyType type,
|
||||
bool canMountSystemDataPrivate, ulong id)
|
||||
out CodeVerificationData verificationData, in Path path, FileSystemProxyType type,
|
||||
bool canMountSystemDataPrivate, ulong id, bool isDirectory)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out fileSystem, out verificationData);
|
||||
|
||||
@ -81,7 +88,7 @@ namespace LibHac.FsSrv
|
||||
verificationData.IsValid = false;
|
||||
|
||||
// Get a reference to the path that will be advanced as each part of the path is parsed
|
||||
U8Span currentPath = path.Slice(0, StringUtils.GetLength(path));
|
||||
var currentPath = new U8Span(path.GetString());
|
||||
|
||||
// Open the root filesystem based on the path's mount name
|
||||
Result rc = ParseMountName(ref currentPath,
|
||||
@ -106,7 +113,7 @@ namespace LibHac.FsSrv
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = CheckDirOrNcaOrNsp(ref currentPath, out bool isDirectory);
|
||||
rc = CheckDirOrNcaOrNsp(ref currentPath, out isDirectory);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (isDirectory)
|
||||
@ -114,17 +121,21 @@ namespace LibHac.FsSrv
|
||||
if (!mountNameInfo.IsHostFs)
|
||||
return ResultFs.PermissionDenied.Log();
|
||||
|
||||
var directoryPath = new Path();
|
||||
rc = directoryPath.InitializeWithNormalization(currentPath.Value);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (type == FileSystemProxyType.Manual)
|
||||
{
|
||||
ReferenceCountedDisposable<IFileSystem> manualFileSystem = null;
|
||||
ReferenceCountedDisposable<IFileSystem> hostFileSystem = null;
|
||||
ReferenceCountedDisposable<IFileSystem> readOnlyFileSystem = null;
|
||||
try
|
||||
{
|
||||
rc = ParseDirWithPathCaseNormalizationOnCaseSensitiveHostFs(out manualFileSystem, ref baseFileSystem,
|
||||
currentPath);
|
||||
rc = ParseDirWithPathCaseNormalizationOnCaseSensitiveHostFs(out hostFileSystem,
|
||||
in directoryPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
readOnlyFileSystem = ReadOnlyFileSystem.CreateShared(ref manualFileSystem);
|
||||
readOnlyFileSystem = ReadOnlyFileSystem.CreateShared(ref hostFileSystem);
|
||||
if (readOnlyFileSystem?.Target is null)
|
||||
return ResultFs.AllocationMemoryFailedAllocateShared.Log();
|
||||
|
||||
@ -133,12 +144,12 @@ namespace LibHac.FsSrv
|
||||
}
|
||||
finally
|
||||
{
|
||||
manualFileSystem?.Dispose();
|
||||
hostFileSystem?.Dispose();
|
||||
readOnlyFileSystem?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
return ParseDir(currentPath, out fileSystem, ref baseFileSystem, type, true);
|
||||
return ParseDir(in directoryPath, out fileSystem, ref baseFileSystem, type, true);
|
||||
}
|
||||
|
||||
rc = ParseNsp(ref currentPath, out ReferenceCountedDisposable<IFileSystem> nspFileSystem, baseFileSystem);
|
||||
@ -146,7 +157,7 @@ namespace LibHac.FsSrv
|
||||
if (rc.IsSuccess())
|
||||
{
|
||||
// Must be the end of the path to open Application Package FS type
|
||||
if (currentPath.Length == 0 || currentPath[0] == 0)
|
||||
if (currentPath.Value.At(0) == 0)
|
||||
{
|
||||
if (type == FileSystemProxyType.Package)
|
||||
{
|
||||
@ -185,28 +196,28 @@ namespace LibHac.FsSrv
|
||||
}
|
||||
}
|
||||
|
||||
public Result OpenDataFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem, U8Span path,
|
||||
FileSystemProxyType fsType, ulong programId)
|
||||
public Result OpenDataFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem, in Path path,
|
||||
FileSystemProxyType fsType, ulong programId, bool isDirectory)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Result OpenStorageWithPatch(out ReferenceCountedDisposable<IStorage> storage, out Hash ncaHeaderDigest,
|
||||
U8Span originalNcaPath, U8Span currentNcaPath, FileSystemProxyType fsType, ulong id)
|
||||
in Path originalNcaPath, in Path currentNcaPath, FileSystemProxyType fsType, ulong id)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Result OpenFileSystemWithPatch(out ReferenceCountedDisposable<IFileSystem> fileSystem,
|
||||
U8Span originalNcaPath, U8Span currentNcaPath, FileSystemProxyType fsType, ulong id)
|
||||
in Path originalNcaPath, in Path currentNcaPath, FileSystemProxyType fsType, ulong id)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out fileSystem);
|
||||
|
||||
ReferenceCountedDisposable<IStorage> romFsStorage = null;
|
||||
try
|
||||
{
|
||||
Result rc = OpenStorageWithPatch(out romFsStorage, out Unsafe.NullRef<Hash>(), originalNcaPath,
|
||||
currentNcaPath, fsType, id);
|
||||
Result rc = OpenStorageWithPatch(out romFsStorage, out Unsafe.NullRef<Hash>(), in originalNcaPath,
|
||||
in currentNcaPath, fsType, id);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return _config.RomFsCreator.Create(out fileSystem, romFsStorage);
|
||||
@ -220,13 +231,14 @@ namespace LibHac.FsSrv
|
||||
public Result OpenContentStorageFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem,
|
||||
ContentStorageId contentStorageId)
|
||||
{
|
||||
const int storagePathMaxLen = 0x40;
|
||||
const int pathBufferLength = 0x40;
|
||||
|
||||
UnsafeHelpers.SkipParamInit(out fileSystem);
|
||||
|
||||
ReferenceCountedDisposable<IFileSystem> baseFileSystem = null;
|
||||
ReferenceCountedDisposable<IFileSystem> subDirFileSystem = null;
|
||||
ReferenceCountedDisposable<IFileSystem> encryptedFileSystem = null;
|
||||
ReferenceCountedDisposable<IFileSystem> tempFileSystem = null;
|
||||
Path contentStoragePath = default;
|
||||
try
|
||||
{
|
||||
Result rc;
|
||||
@ -235,62 +247,62 @@ namespace LibHac.FsSrv
|
||||
switch (contentStorageId)
|
||||
{
|
||||
case ContentStorageId.System:
|
||||
rc = _config.BaseFsService.OpenBisFileSystem(out baseFileSystem, U8Span.Empty,
|
||||
BisPartitionId.System);
|
||||
rc = _config.BaseFsService.OpenBisFileSystem(out baseFileSystem, BisPartitionId.System);
|
||||
if (rc.IsFailure()) return rc;
|
||||
break;
|
||||
case ContentStorageId.User:
|
||||
rc = _config.BaseFsService.OpenBisFileSystem(out baseFileSystem, U8Span.Empty,
|
||||
BisPartitionId.User);
|
||||
rc = _config.BaseFsService.OpenBisFileSystem(out baseFileSystem, BisPartitionId.User);
|
||||
if (rc.IsFailure()) return rc;
|
||||
break;
|
||||
case ContentStorageId.SdCard:
|
||||
rc = _config.BaseFsService.OpenSdCardProxyFileSystem(out baseFileSystem);
|
||||
if (rc.IsFailure()) return rc;
|
||||
break;
|
||||
default:
|
||||
rc = ResultFs.InvalidArgument.Log();
|
||||
break;
|
||||
return ResultFs.InvalidArgument.Log();
|
||||
}
|
||||
|
||||
if (rc.IsFailure()) return rc;
|
||||
// Hack around error CS8350.
|
||||
Span<byte> buffer = stackalloc byte[pathBufferLength];
|
||||
ref byte bufferRef = ref MemoryMarshal.GetReference(buffer);
|
||||
Span<byte> contentStoragePathBuffer = MemoryMarshal.CreateSpan(ref bufferRef, pathBufferLength);
|
||||
|
||||
// Build the appropriate path for the content storage ID
|
||||
Span<byte> contentStoragePath = stackalloc byte[storagePathMaxLen];
|
||||
|
||||
if (contentStorageId == ContentStorageId.SdCard)
|
||||
{
|
||||
var sb = new U8StringBuilder(contentStoragePath);
|
||||
var sb = new U8StringBuilder(contentStoragePathBuffer);
|
||||
sb.Append(StringTraits.DirectorySeparator).Append(SdCardNintendoRootDirectoryName);
|
||||
sb.Append(StringTraits.DirectorySeparator).Append(ContentStorageDirectoryName);
|
||||
}
|
||||
else
|
||||
{
|
||||
var sb = new U8StringBuilder(contentStoragePath);
|
||||
var sb = new U8StringBuilder(contentStoragePathBuffer);
|
||||
sb.Append(StringTraits.DirectorySeparator).Append(ContentStorageDirectoryName);
|
||||
}
|
||||
|
||||
contentStoragePath = new Path();
|
||||
rc = PathFunctions.SetUpFixedPath(ref contentStoragePath, contentStoragePathBuffer);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
// Make sure the content storage path exists
|
||||
// ReSharper disable once PossibleNullReferenceException
|
||||
rc = Utility.EnsureDirectory(baseFileSystem.Target, new U8Span(contentStoragePath));
|
||||
rc = Utility12.EnsureDirectory(baseFileSystem.Target, in contentStoragePath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = _config.SubDirectoryFsCreator.Create(out subDirFileSystem, ref baseFileSystem,
|
||||
new U8Span(contentStoragePath));
|
||||
in contentStoragePath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
// Only content on the SD card is encrypted
|
||||
if (contentStorageId != ContentStorageId.SdCard)
|
||||
if (contentStorageId == ContentStorageId.SdCard)
|
||||
{
|
||||
// Move the shared reference to the out variable
|
||||
Shared.Move(out fileSystem, ref subDirFileSystem);
|
||||
tempFileSystem = Shared.Move(ref subDirFileSystem);
|
||||
|
||||
return Result.Success;
|
||||
rc = _config.EncryptedFsCreator.Create(out subDirFileSystem, ref tempFileSystem,
|
||||
IEncryptedFileSystemCreator.KeyId.Content, in _encryptionSeed);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
|
||||
// Create an encrypted file system for SD card content storage
|
||||
rc = _config.EncryptedFsCreator.Create(out encryptedFileSystem, subDirFileSystem,
|
||||
EncryptedFsKeyId.Content, in _encryptionSeed);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
Shared.Move(out fileSystem, ref encryptedFileSystem);
|
||||
Shared.Move(out fileSystem, ref subDirFileSystem);
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
@ -298,11 +310,12 @@ namespace LibHac.FsSrv
|
||||
{
|
||||
baseFileSystem?.Dispose();
|
||||
subDirFileSystem?.Dispose();
|
||||
encryptedFileSystem?.Dispose();
|
||||
tempFileSystem?.Dispose();
|
||||
contentStoragePath.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public Result GetRightsId(out RightsId rightsId, out byte keyGeneration, U8Span path, ProgramId programId)
|
||||
public Result GetRightsId(out RightsId rightsId, out byte keyGeneration, in Path path, ProgramId programId)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
@ -326,7 +339,7 @@ namespace LibHac.FsSrv
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result RegisterUpdatePartition(ulong programId, U8Span path)
|
||||
public Result RegisterUpdatePartition(ulong programId, in Path path)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
@ -423,8 +436,7 @@ namespace LibHac.FsSrv
|
||||
{
|
||||
path = path.Slice(CommonPaths.BisCalibrationFilePartitionMountName.Length);
|
||||
|
||||
Result rc = _config.BaseFsService.OpenBisFileSystem(out fileSystem, U8Span.Empty,
|
||||
BisPartitionId.CalibrationFile);
|
||||
Result rc = _config.BaseFsService.OpenBisFileSystem(out fileSystem, BisPartitionId.CalibrationFile);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
|
||||
@ -433,8 +445,7 @@ namespace LibHac.FsSrv
|
||||
{
|
||||
path = path.Slice(CommonPaths.BisSafeModePartitionMountName.Length);
|
||||
|
||||
Result rc = _config.BaseFsService.OpenBisFileSystem(out fileSystem, U8Span.Empty,
|
||||
BisPartitionId.SafeMode);
|
||||
Result rc = _config.BaseFsService.OpenBisFileSystem(out fileSystem, BisPartitionId.SafeMode);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
|
||||
@ -443,7 +454,7 @@ namespace LibHac.FsSrv
|
||||
{
|
||||
path = path.Slice(CommonPaths.BisUserPartitionMountName.Length);
|
||||
|
||||
Result rc = _config.BaseFsService.OpenBisFileSystem(out fileSystem, U8Span.Empty, BisPartitionId.User);
|
||||
Result rc = _config.BaseFsService.OpenBisFileSystem(out fileSystem, BisPartitionId.User);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
|
||||
@ -452,8 +463,7 @@ namespace LibHac.FsSrv
|
||||
{
|
||||
path = path.Slice(CommonPaths.BisSystemPartitionMountName.Length);
|
||||
|
||||
Result rc = _config.BaseFsService.OpenBisFileSystem(out fileSystem, U8Span.Empty,
|
||||
BisPartitionId.System);
|
||||
Result rc = _config.BaseFsService.OpenBisFileSystem(out fileSystem, BisPartitionId.System);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
|
||||
@ -471,10 +481,14 @@ namespace LibHac.FsSrv
|
||||
{
|
||||
path = path.Slice(CommonPaths.HostRootFileSystemMountName.Length);
|
||||
|
||||
var rootPathEmpty = new Path();
|
||||
Result rc = rootPathEmpty.InitializeAsEmpty();
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
info.IsHostFs = true;
|
||||
info.CanMountNca = true;
|
||||
|
||||
Result rc = OpenHostFileSystem(out fileSystem, U8Span.Empty, openCaseSensitive: false);
|
||||
rc = OpenHostFileSystem(out fileSystem, in rootPathEmpty, openCaseSensitive: false);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
|
||||
@ -540,7 +554,7 @@ namespace LibHac.FsSrv
|
||||
return ResultFs.PathNotFound.Log();
|
||||
}
|
||||
|
||||
private Result ParseDir(U8Span path,
|
||||
private Result ParseDir(in Path path,
|
||||
out ReferenceCountedDisposable<IFileSystem> contentFileSystem,
|
||||
ref ReferenceCountedDisposable<IFileSystem> baseFileSystem, FileSystemProxyType fsType, bool preserveUnc)
|
||||
{
|
||||
@ -549,7 +563,7 @@ namespace LibHac.FsSrv
|
||||
ReferenceCountedDisposable<IFileSystem> subDirFs = null;
|
||||
try
|
||||
{
|
||||
Result rc = _config.SubDirectoryFsCreator.Create(out subDirFs, ref baseFileSystem, path, preserveUnc);
|
||||
Result rc = _config.SubDirectoryFsCreator.Create(out subDirFs, ref baseFileSystem, in path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return ParseContentTypeForDirectory(out contentFileSystem, ref subDirFs, fsType);
|
||||
@ -561,32 +575,29 @@ namespace LibHac.FsSrv
|
||||
}
|
||||
|
||||
private Result ParseDirWithPathCaseNormalizationOnCaseSensitiveHostFs(
|
||||
out ReferenceCountedDisposable<IFileSystem> contentFileSystem,
|
||||
ref ReferenceCountedDisposable<IFileSystem> baseFileSystem, U8Span path)
|
||||
out ReferenceCountedDisposable<IFileSystem> fileSystem, in Path path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out contentFileSystem);
|
||||
Unsafe.SkipInit(out FsPath fullPath);
|
||||
UnsafeHelpers.SkipParamInit(out fileSystem);
|
||||
|
||||
var sb = new U8StringBuilder(fullPath.Str);
|
||||
sb.Append(path)
|
||||
.Append(new[] { (byte)'d', (byte)'a', (byte)'t', (byte)'a', (byte)'/' });
|
||||
var pathRoot = new Path();
|
||||
var pathData = new Path();
|
||||
|
||||
if (sb.Overflowed)
|
||||
return ResultFs.TooLongPath.Log();
|
||||
|
||||
Result rc = _config.TargetManagerFsCreator.NormalizeCaseOfPath(out bool success, fullPath.Str);
|
||||
Result rc = PathFunctions.SetUpFixedPath(ref pathData,
|
||||
new[] { (byte)'/', (byte)'d', (byte)'a', (byte)'t', (byte)'a' });
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
// Reopen the host filesystem as case sensitive
|
||||
if (success)
|
||||
{
|
||||
baseFileSystem.Dispose();
|
||||
rc = pathRoot.Combine(in path, in pathData);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = OpenHostFileSystem(out baseFileSystem, U8Span.Empty, openCaseSensitive: true);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
rc = _config.TargetManagerFsCreator.NormalizeCaseOfPath(out bool isSupported, ref pathRoot);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return _config.SubDirectoryFsCreator.Create(out contentFileSystem, ref baseFileSystem, fullPath);
|
||||
rc = _config.TargetManagerFsCreator.Create(out fileSystem, in pathRoot, isSupported, false, Result.Success);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
pathData.Dispose();
|
||||
pathRoot.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
private Result ParseNsp(ref U8Span path, out ReferenceCountedDisposable<IFileSystem> fileSystem,
|
||||
@ -710,17 +721,18 @@ namespace LibHac.FsSrv
|
||||
ReferenceCountedDisposable<IFileSystem> subDirFs = null;
|
||||
try
|
||||
{
|
||||
// Open the subdirectory filesystem
|
||||
Result rc = _config.SubDirectoryFsCreator.Create(out subDirFs, ref baseFileSystem, new U8Span(dirName));
|
||||
var directoryPath = new Path();
|
||||
Result rc = PathFunctions.SetUpFixedPath(ref directoryPath, dirName);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (fsType == FileSystemProxyType.Code)
|
||||
{
|
||||
rc = _config.StorageOnNcaCreator.VerifyAcidSignature(subDirFs.Target, null);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
if (directoryPath.IsEmpty())
|
||||
return ResultFs.InvalidArgument.Log();
|
||||
|
||||
Shared.Move(out fileSystem, ref subDirFs);
|
||||
// Open the subdirectory filesystem
|
||||
rc = _config.SubDirectoryFsCreator.Create(out subDirFs, ref baseFileSystem, in directoryPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
fileSystem = Shared.Move(ref subDirFs);
|
||||
return Result.Success;
|
||||
}
|
||||
finally
|
||||
@ -828,35 +840,36 @@ namespace LibHac.FsSrv
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result ResolveProgramPath(out PathLr path, ProgramId programId, StorageId storageId)
|
||||
public Result ResolveProgramPath(out bool isDirectory, ref Path path, ProgramId programId, StorageId storageId)
|
||||
{
|
||||
Result rc = _locationResolverSet.ResolveProgramPath(out path, programId.Value, storageId);
|
||||
Result rc = _locationResolverSet.ResolveProgramPath(out isDirectory, ref path, programId, storageId);
|
||||
if (rc.IsSuccess())
|
||||
return Result.Success;
|
||||
|
||||
var dataId = new DataId(programId.Value);
|
||||
isDirectory = false;
|
||||
|
||||
rc = _locationResolverSet.ResolveDataPath(out path, dataId, storageId);
|
||||
rc = _locationResolverSet.ResolveDataPath(ref path, new DataId(programId.Value), storageId);
|
||||
if (rc.IsSuccess())
|
||||
return Result.Success;
|
||||
|
||||
return ResultFs.TargetNotFound.Log();
|
||||
}
|
||||
|
||||
public Result ResolveRomPath(out PathLr path, ulong id, StorageId storageId)
|
||||
public Result ResolveRomPath(out bool isDirectory, ref Path path, ulong id, StorageId storageId)
|
||||
{
|
||||
return _locationResolverSet.ResolveRomPath(out path, id, storageId);
|
||||
return _locationResolverSet.ResolveRomPath(out isDirectory, ref path, new ProgramId(id), storageId);
|
||||
}
|
||||
|
||||
public Result ResolveApplicationHtmlDocumentPath(out PathLr path, Ncm.ApplicationId applicationId,
|
||||
StorageId storageId)
|
||||
public Result ResolveApplicationHtmlDocumentPath(out bool isDirectory, ref Path path,
|
||||
Ncm.ApplicationId applicationId, StorageId storageId)
|
||||
{
|
||||
return _locationResolverSet.ResolveApplicationHtmlDocumentPath(out path, applicationId, storageId);
|
||||
return _locationResolverSet.ResolveApplicationHtmlDocumentPath(out isDirectory, ref path, applicationId,
|
||||
storageId);
|
||||
}
|
||||
|
||||
public Result ResolveRegisteredHtmlDocumentPath(out PathLr path, ulong id)
|
||||
public Result ResolveRegisteredHtmlDocumentPath(ref Path path, ulong id)
|
||||
{
|
||||
return _locationResolverSet.ResolveRegisteredHtmlDocumentPath(out path, id);
|
||||
return _locationResolverSet.ResolveRegisteredHtmlDocumentPath(ref path, id);
|
||||
}
|
||||
|
||||
internal StorageType GetStorageFlag(ulong programId)
|
||||
@ -909,48 +922,12 @@ namespace LibHac.FsSrv
|
||||
_romFsRecoveredByInvalidateCacheCount = 0;
|
||||
}
|
||||
|
||||
public Result OpenHostFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem, U8Span path, bool openCaseSensitive)
|
||||
public Result OpenHostFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem, in Path rootPath, bool openCaseSensitive)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out fileSystem);
|
||||
Result rc;
|
||||
|
||||
if (!path.IsEmpty())
|
||||
{
|
||||
rc = Util.VerifyHostPath(path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
|
||||
ReferenceCountedDisposable<IFileSystem> hostFs = null;
|
||||
ReferenceCountedDisposable<IFileSystem> subDirFs = null;
|
||||
try
|
||||
{
|
||||
rc = _config.TargetManagerFsCreator.Create(out hostFs, openCaseSensitive);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (path.IsEmpty())
|
||||
{
|
||||
ReadOnlySpan<byte> rootHostPath = new[] { (byte)'C', (byte)':', (byte)'/' };
|
||||
rc = hostFs.Target.GetEntryType(out _, new U8Span(rootHostPath));
|
||||
|
||||
// Nintendo ignores all results other than this one
|
||||
if (ResultFs.TargetNotFound.Includes(rc))
|
||||
return rc;
|
||||
|
||||
Shared.Move(out fileSystem, ref hostFs);
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
rc = _config.SubDirectoryFsCreator.Create(out subDirFs, ref hostFs, path, preserveUnc: true);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
Shared.Move(out fileSystem, ref subDirFs);
|
||||
return Result.Success;
|
||||
}
|
||||
finally
|
||||
{
|
||||
hostFs?.Dispose();
|
||||
subDirFs?.Dispose();
|
||||
}
|
||||
return _config.TargetManagerFsCreator.Create(out fileSystem, in rootPath, openCaseSensitive, false,
|
||||
Result.Success);
|
||||
}
|
||||
|
||||
internal Result GetProgramInfoByProcessId(out ProgramInfo programInfo, ulong processId)
|
||||
|
@ -15,9 +15,9 @@ using IFileSystem = LibHac.Fs.Fsa.IFileSystem;
|
||||
using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem;
|
||||
using IFile = LibHac.Fs.Fsa.IFile;
|
||||
using IFileSf = LibHac.FsSrv.Sf.IFile;
|
||||
using PathNormalizer = LibHac.FsSrv.Impl.PathNormalizer;
|
||||
using Path = LibHac.Fs.Path;
|
||||
using SaveData = LibHac.Fs.SaveData;
|
||||
using Utility = LibHac.FsSystem.Utility;
|
||||
using static LibHac.Fs.StringTraits;
|
||||
|
||||
namespace LibHac.FsSrv
|
||||
{
|
||||
@ -34,21 +34,21 @@ namespace LibHac.FsSrv
|
||||
|
||||
private const int SaveDataBlockSize = 0x4000;
|
||||
|
||||
private ReferenceCountedDisposable<SaveDataFileSystemService>.WeakReference SelfReference { get; set; }
|
||||
private SaveDataFileSystemServiceImpl ServiceImpl { get; }
|
||||
private ulong ProcessId { get; }
|
||||
private FsPath SaveDataRootPath { get; set; }
|
||||
private SemaphoreAdaptor OpenEntryCountSemaphore { get; }
|
||||
private SemaphoreAdaptor SaveDataMountCountSemaphore { get; }
|
||||
private ReferenceCountedDisposable<SaveDataFileSystemService>.WeakReference _selfReference;
|
||||
private SaveDataFileSystemServiceImpl _serviceImpl;
|
||||
private ulong _processId;
|
||||
private Path.Stored _saveDataRootPath;
|
||||
private SemaphoreAdapter _openEntryCountSemaphore;
|
||||
private SemaphoreAdapter _saveDataMountCountSemaphore;
|
||||
|
||||
private HorizonClient Hos => ServiceImpl.Hos;
|
||||
private HorizonClient Hos => _serviceImpl.Hos;
|
||||
|
||||
public SaveDataFileSystemService(SaveDataFileSystemServiceImpl serviceImpl, ulong processId)
|
||||
{
|
||||
ServiceImpl = serviceImpl;
|
||||
ProcessId = processId;
|
||||
OpenEntryCountSemaphore = new SemaphoreAdaptor(OpenEntrySemaphoreCount, OpenEntrySemaphoreCount);
|
||||
SaveDataMountCountSemaphore = new SemaphoreAdaptor(SaveMountSemaphoreCount, SaveMountSemaphoreCount);
|
||||
_serviceImpl = serviceImpl;
|
||||
_processId = processId;
|
||||
_openEntryCountSemaphore = new SemaphoreAdapter(OpenEntrySemaphoreCount, OpenEntrySemaphoreCount);
|
||||
_saveDataMountCountSemaphore = new SemaphoreAdapter(SaveMountSemaphoreCount, SaveMountSemaphoreCount);
|
||||
}
|
||||
|
||||
public static ReferenceCountedDisposable<SaveDataFileSystemService> CreateShared(
|
||||
@ -59,7 +59,7 @@ namespace LibHac.FsSrv
|
||||
|
||||
// Wrap the service in a ref-counter and give the service a weak self-reference
|
||||
var sharedService = new ReferenceCountedDisposable<SaveDataFileSystemService>(saveService);
|
||||
saveService.SelfReference =
|
||||
saveService._selfReference =
|
||||
new ReferenceCountedDisposable<SaveDataFileSystemService>.WeakReference(sharedService);
|
||||
|
||||
return sharedService;
|
||||
@ -457,12 +457,13 @@ namespace LibHac.FsSrv
|
||||
private Result DeleteSaveDataFileSystemCore(SaveDataSpaceId spaceId, ulong saveDataId, bool wipeSaveFile)
|
||||
{
|
||||
// Delete the save data's meta files
|
||||
Result rc = ServiceImpl.DeleteAllSaveDataMetas(saveDataId, spaceId);
|
||||
Result rc = _serviceImpl.DeleteAllSaveDataMetas(saveDataId, spaceId);
|
||||
if (rc.IsFailure() && !ResultFs.PathNotFound.Includes(rc))
|
||||
return rc;
|
||||
|
||||
// Delete the actual save data.
|
||||
rc = ServiceImpl.DeleteSaveDataFileSystem(spaceId, saveDataId, wipeSaveFile, SaveDataRootPath);
|
||||
Path saveDataRootPath = _saveDataRootPath.GetPath();
|
||||
rc = _serviceImpl.DeleteSaveDataFileSystem(spaceId, saveDataId, wipeSaveFile, in saveDataRootPath);
|
||||
if (rc.IsFailure() && !ResultFs.PathNotFound.Includes(rc))
|
||||
return rc;
|
||||
|
||||
@ -526,7 +527,7 @@ namespace LibHac.FsSrv
|
||||
// Only the FS process may delete the save indexer's save data.
|
||||
if (saveDataId == SaveData.SaveIndexerId)
|
||||
{
|
||||
if (!IsCurrentProcess(ProcessId))
|
||||
if (!IsCurrentProcess(_processId))
|
||||
return ResultFs.PermissionDenied.Log();
|
||||
|
||||
actualSpaceId = spaceId;
|
||||
@ -554,8 +555,8 @@ namespace LibHac.FsSrv
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
Result GetExtraData(out SaveDataExtraData data) =>
|
||||
ServiceImpl.ReadSaveDataFileSystemExtraData(out data, actualSpaceId, saveDataId, key.Type,
|
||||
SaveDataRootPath);
|
||||
_serviceImpl.ReadSaveDataFileSystemExtraData(out data, actualSpaceId, saveDataId, key.Type,
|
||||
_saveDataRootPath.GetPath());
|
||||
|
||||
rc = SaveDataAccessibilityChecker.CheckDelete(in key, programInfo, GetExtraData);
|
||||
if (rc.IsFailure()) return rc;
|
||||
@ -625,28 +626,30 @@ namespace LibHac.FsSrv
|
||||
if (!programInfo.AccessControl.CanCall(OperationType.DebugSaveData))
|
||||
return ResultFs.PermissionDenied.Log();
|
||||
|
||||
if (StringUtils.GetLength(path.Str, PathTool.EntryNameLengthMax + 1) > PathTool.EntryNameLengthMax)
|
||||
return ResultFs.TooLongPath.Log();
|
||||
var saveDataRootPath = new Path();
|
||||
|
||||
if (path.Str[0] == 0)
|
||||
SaveDataRootPath.Str[0] = 0;
|
||||
|
||||
var sb = new U8StringBuilder(SaveDataRootPath.Str);
|
||||
sb.Append((byte)'/').Append(path.Str);
|
||||
|
||||
if (StringUtils.Compare(SaveDataRootPath.Str.Slice(1), new[] { (byte)'/', (byte)'/' }, 2) == 0)
|
||||
if (path.Str[0] == NullTerminator)
|
||||
{
|
||||
for (int i = 0; SaveDataRootPath.Str[i] == '/'; i++)
|
||||
{
|
||||
SaveDataRootPath.Str[i] = (byte)'\\';
|
||||
}
|
||||
rc = saveDataRootPath.Initialize(new[] { (byte)'/' });
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = saveDataRootPath.InitializeWithReplaceUnc(path.Str);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
|
||||
using var normalizer = new PathNormalizer(SaveDataRootPath, PathNormalizer.Option.PreserveUnc);
|
||||
if (normalizer.Result.IsFailure())
|
||||
return normalizer.Result;
|
||||
var pathFlags = new PathFlags();
|
||||
pathFlags.AllowWindowsPath();
|
||||
pathFlags.AllowRelativePath();
|
||||
pathFlags.AllowEmptyPath();
|
||||
|
||||
rc = saveDataRootPath.Normalize(pathFlags);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
_saveDataRootPath.Initialize(in saveDataRootPath);
|
||||
saveDataRootPath.Dispose();
|
||||
|
||||
StringUtils.Copy(SaveDataRootPath.Str, normalizer.Path);
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
@ -658,7 +661,13 @@ namespace LibHac.FsSrv
|
||||
if (!programInfo.AccessControl.CanCall(OperationType.DebugSaveData))
|
||||
return ResultFs.PermissionDenied.Log();
|
||||
|
||||
SaveDataRootPath.Str.Clear();
|
||||
var saveDataRootPath = new Path();
|
||||
rc = saveDataRootPath.InitializeAsEmpty();
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
_saveDataRootPath.Initialize(in saveDataRootPath);
|
||||
saveDataRootPath.Dispose();
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
@ -709,7 +718,7 @@ namespace LibHac.FsSrv
|
||||
{
|
||||
// The save indexer doesn't index itself
|
||||
saveDataId = SaveData.SaveIndexerId;
|
||||
rc = ServiceImpl.DoesSaveDataEntityExist(out bool saveExists, creationInfo.SpaceId, saveDataId);
|
||||
rc = _serviceImpl.DoesSaveDataEntityExist(out bool saveExists, creationInfo.SpaceId, saveDataId);
|
||||
|
||||
if (rc.IsSuccess() && saveExists)
|
||||
{
|
||||
@ -780,8 +789,9 @@ namespace LibHac.FsSrv
|
||||
}
|
||||
|
||||
// After the new save was added to the save indexer, create the save data file or directory.
|
||||
rc = ServiceImpl.CreateSaveDataFileSystem(saveDataId, in attribute, in creationInfo, SaveDataRootPath,
|
||||
in hashSalt, false);
|
||||
Path saveDataRootPath = _saveDataRootPath.GetPath();
|
||||
rc = _serviceImpl.CreateSaveDataFileSystem(saveDataId, in attribute, in creationInfo,
|
||||
in saveDataRootPath, in hashSalt, false);
|
||||
|
||||
if (rc.IsFailure())
|
||||
{
|
||||
@ -792,21 +802,21 @@ namespace LibHac.FsSrv
|
||||
rc = DeleteSaveDataFileSystemCore(creationInfo.SpaceId, saveDataId, false);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = ServiceImpl.CreateSaveDataFileSystem(saveDataId, in attribute, in creationInfo,
|
||||
SaveDataRootPath, in hashSalt, false);
|
||||
rc = _serviceImpl.CreateSaveDataFileSystem(saveDataId, in attribute, in creationInfo,
|
||||
in saveDataRootPath, in hashSalt, false);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
|
||||
if (metaInfo.Type != SaveDataMetaType.None)
|
||||
{
|
||||
// Create the requested save data meta file.
|
||||
rc = ServiceImpl.CreateSaveDataMeta(saveDataId, creationInfo.SpaceId, metaInfo.Type,
|
||||
rc = _serviceImpl.CreateSaveDataMeta(saveDataId, creationInfo.SpaceId, metaInfo.Type,
|
||||
metaInfo.Size);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (metaInfo.Type == SaveDataMetaType.Thumbnail)
|
||||
{
|
||||
rc = ServiceImpl.OpenSaveDataMeta(out IFile metaFile, saveDataId, creationInfo.SpaceId,
|
||||
rc = _serviceImpl.OpenSaveDataMeta(out IFile metaFile, saveDataId, creationInfo.SpaceId,
|
||||
metaInfo.Type);
|
||||
|
||||
using (metaFile)
|
||||
@ -866,7 +876,6 @@ namespace LibHac.FsSrv
|
||||
}
|
||||
|
||||
accessor?.Dispose();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -896,7 +905,7 @@ namespace LibHac.FsSrv
|
||||
if (dataSize < 0 || journalSize < 0)
|
||||
return ResultFs.InvalidSize.Log();
|
||||
|
||||
return ServiceImpl.QuerySaveDataTotalSize(out totalSize, SaveDataBlockSize, dataSize, journalSize);
|
||||
return _serviceImpl.QuerySaveDataTotalSize(out totalSize, SaveDataBlockSize, dataSize, journalSize);
|
||||
}
|
||||
|
||||
public Result CreateSaveDataFileSystem(in SaveDataAttribute attribute, in SaveDataCreationInfo creationInfo,
|
||||
@ -1037,8 +1046,9 @@ namespace LibHac.FsSrv
|
||||
}
|
||||
|
||||
// Open the save data using its ID
|
||||
Result saveFsResult = ServiceImpl.OpenSaveDataFileSystem(out fileSystem, spaceId, tempSaveDataId,
|
||||
SaveDataRootPath, openReadOnly, attribute.Type, cacheExtraData);
|
||||
Path saveDataRootPath = _saveDataRootPath.GetPath();
|
||||
Result saveFsResult = _serviceImpl.OpenSaveDataFileSystem(out fileSystem, spaceId, tempSaveDataId,
|
||||
in saveDataRootPath, openReadOnly, attribute.Type, cacheExtraData);
|
||||
|
||||
if (saveFsResult.IsSuccess())
|
||||
{
|
||||
@ -1117,7 +1127,8 @@ namespace LibHac.FsSrv
|
||||
Result rc = TryAcquireSaveDataMountCountSemaphore(out mountCountSemaphore);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
bool useAsyncFileSystem = !ServiceImpl.IsAllowedDirectorySaveData(spaceId, SaveDataRootPath);
|
||||
Path saveDataRootPath = _saveDataRootPath.GetPath();
|
||||
bool useAsyncFileSystem = !_serviceImpl.IsAllowedDirectorySaveData(spaceId, in saveDataRootPath);
|
||||
|
||||
// Open the file system
|
||||
rc = OpenSaveDataFileSystemCore(out tempFileSystem, out ulong saveDataId, spaceId, in attribute,
|
||||
@ -1127,8 +1138,12 @@ namespace LibHac.FsSrv
|
||||
// Can't use attribute in a closure, so copy the needed field
|
||||
SaveDataType type = attribute.Type;
|
||||
|
||||
Result ReadExtraData(out SaveDataExtraData data) =>
|
||||
ServiceImpl.ReadSaveDataFileSystemExtraData(out data, spaceId, saveDataId, type, SaveDataRootPath);
|
||||
Result ReadExtraData(out SaveDataExtraData data)
|
||||
{
|
||||
Path savePath = _saveDataRootPath.GetPath();
|
||||
return _serviceImpl.ReadSaveDataFileSystemExtraData(out data, spaceId, saveDataId, type,
|
||||
in savePath);
|
||||
}
|
||||
|
||||
// Check if we have permissions to open this save data
|
||||
rc = SaveDataAccessibilityChecker.CheckOpen(in attribute, programInfo, ReadExtraData);
|
||||
@ -1142,13 +1157,15 @@ namespace LibHac.FsSrv
|
||||
tempFileSystem = AsynchronousAccessFileSystem.CreateShared(ref tempFileSystem);
|
||||
}
|
||||
|
||||
saveService = SelfReference.AddReference();
|
||||
saveService = _selfReference.AddReference();
|
||||
openEntryCountAdapter = SaveDataOpenCountAdapter.CreateShared(ref saveService);
|
||||
|
||||
tempFileSystem = OpenCountFileSystem.CreateShared(ref tempFileSystem, ref openEntryCountAdapter,
|
||||
ref mountCountSemaphore);
|
||||
|
||||
fileSystem = FileSystemInterfaceAdapter.CreateShared(ref tempFileSystem);
|
||||
var pathFlags = new PathFlags();
|
||||
pathFlags.AllowBackslash();
|
||||
fileSystem = FileSystemInterfaceAdapter.CreateShared(ref tempFileSystem, pathFlags, false);
|
||||
return Result.Success;
|
||||
}
|
||||
finally
|
||||
@ -1223,7 +1240,8 @@ namespace LibHac.FsSrv
|
||||
if (!accessibility.CanRead || !accessibility.CanWrite)
|
||||
return ResultFs.PermissionDenied.Log();
|
||||
|
||||
bool useAsyncFileSystem = !ServiceImpl.IsAllowedDirectorySaveData(spaceId, SaveDataRootPath);
|
||||
Path saveDataRootPath = _saveDataRootPath.GetPath();
|
||||
bool useAsyncFileSystem = !_serviceImpl.IsAllowedDirectorySaveData(spaceId, in saveDataRootPath);
|
||||
|
||||
ReferenceCountedDisposable<IFileSystem> tempFileSystem = null;
|
||||
ReferenceCountedDisposable<SaveDataFileSystemService> saveService = null;
|
||||
@ -1240,7 +1258,8 @@ namespace LibHac.FsSrv
|
||||
SaveDataType type = attribute.Type;
|
||||
|
||||
Result ReadExtraData(out SaveDataExtraData data) =>
|
||||
ServiceImpl.ReadSaveDataFileSystemExtraData(out data, spaceId, saveDataId, type, SaveDataRootPath);
|
||||
_serviceImpl.ReadSaveDataFileSystemExtraData(out data, spaceId, saveDataId, type,
|
||||
_saveDataRootPath.GetPath());
|
||||
|
||||
// Check if we have permissions to open this save data
|
||||
rc = SaveDataAccessibilityChecker.CheckOpen(in attribute, programInfo, ReadExtraData);
|
||||
@ -1254,12 +1273,14 @@ namespace LibHac.FsSrv
|
||||
tempFileSystem = AsynchronousAccessFileSystem.CreateShared(ref tempFileSystem);
|
||||
}
|
||||
|
||||
saveService = SelfReference.AddReference();
|
||||
saveService = _selfReference.AddReference();
|
||||
openEntryCountAdapter = SaveDataOpenCountAdapter.CreateShared(ref saveService);
|
||||
|
||||
tempFileSystem = OpenCountFileSystem.CreateShared(ref tempFileSystem, ref openEntryCountAdapter);
|
||||
|
||||
fileSystem = FileSystemInterfaceAdapter.CreateShared(ref tempFileSystem);
|
||||
var pathFlags = new PathFlags();
|
||||
pathFlags.AllowBackslash();
|
||||
fileSystem = FileSystemInterfaceAdapter.CreateShared(ref tempFileSystem, pathFlags, false);
|
||||
return Result.Success;
|
||||
}
|
||||
finally
|
||||
@ -1288,8 +1309,9 @@ namespace LibHac.FsSrv
|
||||
rc = accessor.Indexer.GetKey(out SaveDataAttribute key, saveDataId);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return ServiceImpl.ReadSaveDataFileSystemExtraData(out extraData, spaceId, saveDataId, key.Type,
|
||||
SaveDataRootPath);
|
||||
Path saveDataRootPath = _saveDataRootPath.GetPath();
|
||||
return _serviceImpl.ReadSaveDataFileSystemExtraData(out extraData, spaceId, saveDataId, key.Type,
|
||||
in saveDataRootPath);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@ -1361,15 +1383,16 @@ namespace LibHac.FsSrv
|
||||
}
|
||||
}
|
||||
|
||||
Result ReadExtraData(out SaveDataExtraData data) => ServiceImpl.ReadSaveDataFileSystemExtraData(out data,
|
||||
resolvedSpaceId, saveDataId, key.Type, new U8Span(SaveDataRootPath.Str));
|
||||
Result ReadExtraData(out SaveDataExtraData data) => _serviceImpl.ReadSaveDataFileSystemExtraData(out data,
|
||||
resolvedSpaceId, saveDataId, key.Type, _saveDataRootPath.GetPath());
|
||||
|
||||
rc = SaveDataAccessibilityChecker.CheckReadExtraData(in key, in extraDataMask, programInfo,
|
||||
ReadExtraData);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = ServiceImpl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData tempExtraData, resolvedSpaceId,
|
||||
saveDataId, key.Type, new U8Span(SaveDataRootPath.Str));
|
||||
Path saveDataRootPath = _saveDataRootPath.GetPath();
|
||||
rc = _serviceImpl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData tempExtraData, resolvedSpaceId,
|
||||
saveDataId, key.Type, in saveDataRootPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
MaskExtraData(ref tempExtraData, in extraDataMask);
|
||||
@ -1469,7 +1492,8 @@ namespace LibHac.FsSrv
|
||||
{
|
||||
using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageType.NonGameCard);
|
||||
|
||||
return ServiceImpl.WriteSaveDataFileSystemExtraData(spaceId, saveDataId, in extraData, SaveDataRootPath,
|
||||
Path saveDataRootPath = _saveDataRootPath.GetPath();
|
||||
return _serviceImpl.WriteSaveDataFileSystemExtraData(spaceId, saveDataId, in extraData, in saveDataRootPath,
|
||||
saveType, updateTimeStamp);
|
||||
}
|
||||
|
||||
@ -1490,21 +1514,22 @@ namespace LibHac.FsSrv
|
||||
rc = accessor.Indexer.GetKey(out SaveDataAttribute key, saveDataId);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
Result ReadExtraData(out SaveDataExtraData data) => ServiceImpl.ReadSaveDataFileSystemExtraData(out data,
|
||||
spaceId, saveDataId, key.Type, new U8Span(SaveDataRootPath.Str));
|
||||
Result ReadExtraData(out SaveDataExtraData data) => _serviceImpl.ReadSaveDataFileSystemExtraData(out data,
|
||||
spaceId, saveDataId, key.Type, _saveDataRootPath.GetPath());
|
||||
|
||||
rc = SaveDataAccessibilityChecker.CheckWriteExtraData(in key, in extraDataMask, programInfo,
|
||||
ReadExtraData);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = ServiceImpl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraDataModify, spaceId,
|
||||
saveDataId, key.Type, SaveDataRootPath);
|
||||
Path saveDataRootPath = _saveDataRootPath.GetPath();
|
||||
rc = _serviceImpl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraDataModify, spaceId,
|
||||
saveDataId, key.Type, in saveDataRootPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
ModifySaveDataExtraData(ref extraDataModify, in extraData, in extraDataMask);
|
||||
|
||||
return ServiceImpl.WriteSaveDataFileSystemExtraData(spaceId, saveDataId, in extraDataModify,
|
||||
SaveDataRootPath, key.Type, false);
|
||||
return _serviceImpl.WriteSaveDataFileSystemExtraData(spaceId, saveDataId, in extraDataModify,
|
||||
in saveDataRootPath, key.Type, false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@ -1775,7 +1800,7 @@ namespace LibHac.FsSrv
|
||||
saveDataId);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
commitId = Impl.Utility.ConvertZeroCommitId(in extraData);
|
||||
commitId = Utility.ConvertZeroCommitId(in extraData);
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
@ -1872,7 +1897,7 @@ namespace LibHac.FsSrv
|
||||
Result rc;
|
||||
|
||||
// Cache storage on the SD card will always take priority over case storage in NAND
|
||||
if (ServiceImpl.IsSdCardAccessible())
|
||||
if (_serviceImpl.IsSdCardAccessible())
|
||||
{
|
||||
rc = SaveExists(out bool existsOnSdCard, SaveDataSpaceId.SdCache);
|
||||
if (rc.IsFailure()) return rc;
|
||||
@ -1957,8 +1982,9 @@ namespace LibHac.FsSrv
|
||||
Result rc = FindCacheStorage(out SaveDataInfo saveInfo, out SaveDataSpaceId spaceId, index);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = ServiceImpl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraData, spaceId,
|
||||
saveInfo.SaveDataId, saveInfo.Type, new U8Span(SaveDataRootPath.Str));
|
||||
Path saveDataRootPath = _saveDataRootPath.GetPath();
|
||||
rc = _serviceImpl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraData, spaceId,
|
||||
saveInfo.SaveDataId, saveInfo.Type, in saveDataRootPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
usableDataSize = extraData.DataSize;
|
||||
@ -2005,7 +2031,7 @@ namespace LibHac.FsSrv
|
||||
|
||||
public Result SetSdCardEncryptionSeed(in EncryptionSeed seed)
|
||||
{
|
||||
return ServiceImpl.SetSdCardEncryptionSeed(in seed);
|
||||
return _serviceImpl.SetSdCardEncryptionSeed(in seed);
|
||||
}
|
||||
|
||||
public Result ListAccessibleSaveDataOwnerId(out int readCount, OutBuffer idBuffer, ProgramId programId,
|
||||
@ -2016,7 +2042,7 @@ namespace LibHac.FsSrv
|
||||
|
||||
private ProgramId ResolveDefaultSaveDataReferenceProgramId(ProgramId programId)
|
||||
{
|
||||
return ServiceImpl.ResolveDefaultSaveDataReferenceProgramId(programId);
|
||||
return _serviceImpl.ResolveDefaultSaveDataReferenceProgramId(programId);
|
||||
}
|
||||
|
||||
public Result VerifySaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId spaceId, ulong saveDataId,
|
||||
@ -2085,15 +2111,17 @@ namespace LibHac.FsSrv
|
||||
ReferenceCountedDisposable<IFileSystem> fileSystem = null;
|
||||
try
|
||||
{
|
||||
Result rc = ServiceImpl.OpenSaveDataDirectoryFileSystem(out fileSystem, SaveDataSpaceId.Temporary,
|
||||
U8Span.Empty, false);
|
||||
Result rc = _serviceImpl.OpenSaveDataDirectoryFileSystem(out fileSystem, SaveDataSpaceId.Temporary);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
var rootPath = new U8Span(new[] { (byte)'/' });
|
||||
rc = fileSystem.Target.CleanDirectoryRecursively(rootPath);
|
||||
var pathRoot = new Path();
|
||||
rc = PathFunctions.SetUpFixedPath(ref pathRoot, new[] { (byte)'/' });
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
ServiceImpl.ResetTemporaryStorageIndexer();
|
||||
rc = fileSystem.Target.CleanDirectoryRecursively(in pathRoot);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
_serviceImpl.ResetTemporaryStorageIndexer();
|
||||
return Result.Success;
|
||||
}
|
||||
finally
|
||||
@ -2116,10 +2144,10 @@ namespace LibHac.FsSrv
|
||||
ReferenceCountedDisposable<ISaveDataMultiCommitCoreInterface> commitInterface = null;
|
||||
try
|
||||
{
|
||||
saveService = SelfReference.AddReference();
|
||||
saveService = _selfReference.AddReference();
|
||||
commitInterface = saveService.AddReference<ISaveDataMultiCommitCoreInterface>();
|
||||
|
||||
commitManager = MultiCommitManager.CreateShared(ref commitInterface, Hos);
|
||||
commitManager = MultiCommitManager.CreateShared(_serviceImpl.FsServer, ref commitInterface);
|
||||
return Result.Success;
|
||||
}
|
||||
finally
|
||||
@ -2146,12 +2174,12 @@ namespace LibHac.FsSrv
|
||||
|
||||
public Result RecoverMultiCommit()
|
||||
{
|
||||
return MultiCommitManager.Recover(this, ServiceImpl);
|
||||
return MultiCommitManager.Recover(_serviceImpl.FsServer, this, _serviceImpl);
|
||||
}
|
||||
|
||||
public Result IsProvisionallyCommittedSaveData(out bool isProvisionallyCommitted, in SaveDataInfo saveInfo)
|
||||
{
|
||||
return ServiceImpl.IsProvisionallyCommittedSaveData(out isProvisionallyCommitted, in saveInfo);
|
||||
return _serviceImpl.IsProvisionallyCommittedSaveData(out isProvisionallyCommitted, in saveInfo);
|
||||
}
|
||||
|
||||
public Result RecoverProvisionallyCommittedSaveData(in SaveDataInfo saveInfo, bool doRollback)
|
||||
@ -2197,9 +2225,9 @@ namespace LibHac.FsSrv
|
||||
IUniqueLock uniqueLock = null;
|
||||
try
|
||||
{
|
||||
saveService = SelfReference.AddReference();
|
||||
saveService = _selfReference.AddReference();
|
||||
|
||||
Result rc = Utility.MakeUniqueLockWithPin(out uniqueLock, OpenEntryCountSemaphore,
|
||||
Result rc = Utility12.MakeUniqueLockWithPin(out uniqueLock, _openEntryCountSemaphore,
|
||||
ref saveService);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
@ -2221,9 +2249,9 @@ namespace LibHac.FsSrv
|
||||
IUniqueLock uniqueLock = null;
|
||||
try
|
||||
{
|
||||
saveService = SelfReference.AddReference();
|
||||
saveService = _selfReference.AddReference();
|
||||
|
||||
Result rc = Utility.MakeUniqueLockWithPin(out uniqueLock, SaveDataMountCountSemaphore,
|
||||
Result rc = Utility12.MakeUniqueLockWithPin(out uniqueLock, _saveDataMountCountSemaphore,
|
||||
ref saveService);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
@ -2250,13 +2278,13 @@ namespace LibHac.FsSrv
|
||||
if (!programInfo.AccessControl.CanCall(OperationType.SetSdCardAccessibility))
|
||||
return ResultFs.PermissionDenied.Log();
|
||||
|
||||
ServiceImpl.SetSdCardAccessibility(isAccessible);
|
||||
_serviceImpl.SetSdCardAccessibility(isAccessible);
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result IsSdCardAccessible(out bool isAccessible)
|
||||
{
|
||||
isAccessible = ServiceImpl.IsSdCardAccessible();
|
||||
isAccessible = _serviceImpl.IsSdCardAccessible();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
@ -2267,7 +2295,7 @@ namespace LibHac.FsSrv
|
||||
SaveDataIndexerAccessor accessorTemp = null;
|
||||
try
|
||||
{
|
||||
Result rc = ServiceImpl.OpenSaveDataIndexerAccessor(out accessorTemp, out bool neededInit, spaceId);
|
||||
Result rc = _serviceImpl.OpenSaveDataIndexerAccessor(out accessorTemp, out bool neededInit, spaceId);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (neededInit)
|
||||
@ -2287,7 +2315,7 @@ namespace LibHac.FsSrv
|
||||
|
||||
private Result GetProgramInfo(out ProgramInfo programInfo)
|
||||
{
|
||||
return ServiceImpl.GetProgramInfo(out programInfo, ProcessId);
|
||||
return _serviceImpl.GetProgramInfo(out programInfo, _processId);
|
||||
}
|
||||
|
||||
private bool IsCurrentProcess(ulong processId)
|
||||
@ -2387,8 +2415,8 @@ namespace LibHac.FsSrv
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
OpenEntryCountSemaphore.Dispose();
|
||||
SaveDataMountCountSemaphore.Dispose();
|
||||
_openEntryCountSemaphore.Dispose();
|
||||
_saveDataMountCountSemaphore.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using LibHac.Common;
|
||||
using LibHac.Diag;
|
||||
using LibHac.Fs;
|
||||
@ -26,6 +26,7 @@ namespace LibHac.FsSrv
|
||||
private TimeStampGetter _timeStampGetter;
|
||||
|
||||
internal HorizonClient Hos => _config.FsServer.Hos;
|
||||
internal FileSystemServer FsServer => _config.FsServer;
|
||||
|
||||
private class TimeStampGetter : ISaveDataCommitTimeStampGetter
|
||||
{
|
||||
@ -87,15 +88,21 @@ namespace LibHac.FsSrv
|
||||
ReferenceCountedDisposable<IFileSystem> fileSystem = null;
|
||||
try
|
||||
{
|
||||
Result rc = OpenSaveDataDirectoryFileSystem(out fileSystem, spaceId, U8Span.Empty, true);
|
||||
Result rc = OpenSaveDataDirectoryFileSystem(out fileSystem, spaceId);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
// Get the path of the save data
|
||||
Span<byte> saveDataPath = stackalloc byte[0x12];
|
||||
var sb = new U8StringBuilder(saveDataPath);
|
||||
sb.Append((byte)'/').AppendFormat(saveDataId, 'x', 16);
|
||||
// Hack around error CS8350.
|
||||
const int bufferLength = 0x12;
|
||||
Span<byte> buffer = stackalloc byte[bufferLength];
|
||||
ref byte bufferRef = ref MemoryMarshal.GetReference(buffer);
|
||||
Span<byte> saveImageNameBuffer = MemoryMarshal.CreateSpan(ref bufferRef, bufferLength);
|
||||
|
||||
rc = fileSystem.Target.GetEntryType(out _, new U8Span(saveDataPath));
|
||||
var saveImageName = new Path();
|
||||
rc = PathFunctions.SetUpFixedPathSaveId(ref saveImageName, saveImageNameBuffer, saveDataId);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = fileSystem.Target.GetEntryType(out _, in saveImageName);
|
||||
|
||||
if (rc.IsSuccess())
|
||||
{
|
||||
@ -119,7 +126,7 @@ namespace LibHac.FsSrv
|
||||
}
|
||||
|
||||
public Result OpenSaveDataFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem,
|
||||
SaveDataSpaceId spaceId, ulong saveDataId, U8Span saveDataRootPath, bool openReadOnly, SaveDataType type,
|
||||
SaveDataSpaceId spaceId, ulong saveDataId, in Path saveDataRootPath, bool openReadOnly, SaveDataType type,
|
||||
bool cacheExtraData)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out fileSystem);
|
||||
@ -129,10 +136,10 @@ namespace LibHac.FsSrv
|
||||
ReferenceCountedDisposable<IFileSystem> saveDataFs = null;
|
||||
try
|
||||
{
|
||||
Result rc = OpenSaveDataDirectoryFileSystem(out saveDirectoryFs, spaceId, saveDataRootPath, true);
|
||||
Result rc = OpenSaveDataDirectoryFileSystem(out saveDirectoryFs, spaceId, in saveDataRootPath, true);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
bool allowDirectorySaveData = IsAllowedDirectorySaveData2(spaceId, saveDataRootPath);
|
||||
bool allowDirectorySaveData = IsAllowedDirectorySaveData2(spaceId, in saveDataRootPath);
|
||||
|
||||
// Note: When directory save data is allowed, Nintendo creates the save directory if it doesn't exist.
|
||||
// This bypasses normal save data creation, leaving the save with empty extra data.
|
||||
@ -208,22 +215,24 @@ namespace LibHac.FsSrv
|
||||
public Result OpenSaveDataMetaDirectoryFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem,
|
||||
SaveDataSpaceId spaceId, ulong saveDataId)
|
||||
{
|
||||
var metaDirPath = // /saveMeta/
|
||||
new U8Span(new[]
|
||||
{
|
||||
(byte)'/', (byte)'s', (byte)'a', (byte)'v', (byte)'e', (byte)'M', (byte)'e', (byte)'t',
|
||||
(byte)'a', (byte)'/'
|
||||
});
|
||||
UnsafeHelpers.SkipParamInit(out fileSystem);
|
||||
|
||||
Span<byte> directoryName = stackalloc byte[0x1B];
|
||||
var sb = new U8StringBuilder(directoryName);
|
||||
sb.Append(metaDirPath).AppendFormat(saveDataId, 'x', 16);
|
||||
// Hack around error CS8350.
|
||||
const int bufferLength = 0x1B;
|
||||
Span<byte> buffer = stackalloc byte[bufferLength];
|
||||
ref byte bufferRef = ref MemoryMarshal.GetReference(buffer);
|
||||
Span<byte> saveDataMetaIdDirectoryNameBuffer = MemoryMarshal.CreateSpan(ref bufferRef, bufferLength);
|
||||
|
||||
return OpenSaveDataDirectoryFileSystemImpl(out fileSystem, spaceId, new U8Span(directoryName));
|
||||
var saveDataMetaIdDirectoryName = new Path();
|
||||
Result rc = PathFunctions.SetUpFixedPathSaveMetaDir(ref saveDataMetaIdDirectoryName,
|
||||
saveDataMetaIdDirectoryNameBuffer, saveDataId);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return OpenSaveDataDirectoryFileSystemImpl(out fileSystem, spaceId, in saveDataMetaIdDirectoryName);
|
||||
}
|
||||
|
||||
public Result OpenSaveDataInternalStorageFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem,
|
||||
SaveDataSpaceId spaceId, ulong saveDataId, U8Span saveDataRootPath, bool useSecondMacKey)
|
||||
SaveDataSpaceId spaceId, ulong saveDataId, in Path saveDataRootPath, bool useSecondMacKey)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
@ -244,11 +253,18 @@ namespace LibHac.FsSrv
|
||||
Result rc = OpenSaveDataMetaDirectoryFileSystem(out metaDirFs, spaceId, saveDataId);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
Span<byte> saveMetaPathBuffer = stackalloc byte[0xF];
|
||||
var sb = new U8StringBuilder(saveMetaPathBuffer);
|
||||
sb.Append((byte)'/').AppendFormat((int)metaType, 'x', 8);
|
||||
// Hack around error CS8350.
|
||||
const int bufferLength = 0xF;
|
||||
Span<byte> buffer = stackalloc byte[bufferLength];
|
||||
ref byte bufferRef = ref MemoryMarshal.GetReference(buffer);
|
||||
Span<byte> saveDataMetaNameBuffer = MemoryMarshal.CreateSpan(ref bufferRef, bufferLength);
|
||||
|
||||
return metaDirFs.Target.CreateFile(new U8Span(saveMetaPathBuffer), metaSize);
|
||||
var saveDataMetaName = new Path();
|
||||
rc = PathFunctions.SetUpFixedPathSaveMetaName(ref saveDataMetaName, saveDataMetaNameBuffer,
|
||||
(uint)metaType);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return metaDirFs.Target.CreateFile(in saveDataMetaName, metaSize);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@ -264,11 +280,18 @@ namespace LibHac.FsSrv
|
||||
Result rc = OpenSaveDataMetaDirectoryFileSystem(out metaDirFs, spaceId, saveDataId);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
Span<byte> saveMetaPathBuffer = stackalloc byte[0xF];
|
||||
var sb = new U8StringBuilder(saveMetaPathBuffer);
|
||||
sb.Append((byte)'/').AppendFormat((int)metaType, 'x', 8);
|
||||
// Hack around error CS8350.
|
||||
const int bufferLength = 0xF;
|
||||
Span<byte> buffer = stackalloc byte[bufferLength];
|
||||
ref byte bufferRef = ref MemoryMarshal.GetReference(buffer);
|
||||
Span<byte> saveDataMetaNameBuffer = MemoryMarshal.CreateSpan(ref bufferRef, bufferLength);
|
||||
|
||||
return metaDirFs.Target.DeleteFile(new U8Span(saveMetaPathBuffer));
|
||||
var saveDataMetaName = new Path();
|
||||
rc = PathFunctions.SetUpFixedPathSaveMetaName(ref saveDataMetaName, saveDataMetaNameBuffer,
|
||||
(uint)metaType);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return metaDirFs.Target.DeleteFile(in saveDataMetaName);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@ -278,30 +301,43 @@ namespace LibHac.FsSrv
|
||||
|
||||
public Result DeleteAllSaveDataMetas(ulong saveDataId, SaveDataSpaceId spaceId)
|
||||
{
|
||||
var metaDirPath = // /saveMeta
|
||||
new U8Span(new[]
|
||||
ReadOnlySpan<byte> metaDirName = // /saveMeta
|
||||
new[]
|
||||
{
|
||||
(byte)'/', (byte)'s', (byte)'a', (byte)'v', (byte)'e', (byte)'M', (byte)'e', (byte)'t',
|
||||
(byte)'a'
|
||||
});
|
||||
(byte) '/', (byte) 's', (byte) 'a', (byte) 'v', (byte) 'e', (byte) 'M', (byte) 'e', (byte) 't',
|
||||
(byte) 'a'
|
||||
};
|
||||
|
||||
// Hack around error CS8350.
|
||||
const int bufferLength = 0x12;
|
||||
Span<byte> buffer = stackalloc byte[bufferLength];
|
||||
ref byte bufferRef = ref MemoryMarshal.GetReference(buffer);
|
||||
Span<byte> saveDataIdDirectoryNameBuffer = MemoryMarshal.CreateSpan(ref bufferRef, bufferLength);
|
||||
|
||||
ReferenceCountedDisposable<IFileSystem> fileSystem = null;
|
||||
try
|
||||
{
|
||||
Result rc = OpenSaveDataDirectoryFileSystemImpl(out fileSystem, spaceId, metaDirPath, false);
|
||||
var saveDataMetaDirectoryName = new Path();
|
||||
Result rc = PathFunctions.SetUpFixedPath(ref saveDataMetaDirectoryName, metaDirName);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
// Get the path of the save data meta
|
||||
Span<byte> saveDataPathBuffer = stackalloc byte[0x12];
|
||||
var sb = new U8StringBuilder(saveDataPathBuffer);
|
||||
sb.Append((byte)'/').AppendFormat(saveDataId, 'x', 16);
|
||||
rc = OpenSaveDataDirectoryFileSystemImpl(out fileSystem, spaceId, in saveDataMetaDirectoryName, false);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
var saveDataIdDirectoryName = new Path();
|
||||
PathFunctions.SetUpFixedPathSaveId(ref saveDataIdDirectoryName, saveDataIdDirectoryNameBuffer,
|
||||
saveDataId);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
// Delete the save data's meta directory, ignoring the error if the directory is already gone
|
||||
rc = fileSystem.Target.DeleteDirectoryRecursively(new U8Span(saveDataPathBuffer));
|
||||
rc = fileSystem.Target.DeleteDirectoryRecursively(in saveDataIdDirectoryName);
|
||||
|
||||
if (rc.IsFailure() && !ResultFs.PathNotFound.Includes(rc))
|
||||
return rc;
|
||||
|
||||
saveDataMetaDirectoryName.Dispose();
|
||||
saveDataIdDirectoryName.Dispose();
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
finally
|
||||
@ -321,11 +357,18 @@ namespace LibHac.FsSrv
|
||||
Result rc = OpenSaveDataMetaDirectoryFileSystem(out metaDirFs, spaceId, saveDataId);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
Span<byte> saveMetaPathBuffer = stackalloc byte[0xF];
|
||||
var sb = new U8StringBuilder(saveMetaPathBuffer);
|
||||
sb.Append((byte)'/').AppendFormat((int)metaType, 'x', 8);
|
||||
// Hack around error CS8350.
|
||||
const int bufferLength = 0xF;
|
||||
Span<byte> buffer = stackalloc byte[bufferLength];
|
||||
ref byte bufferRef = ref MemoryMarshal.GetReference(buffer);
|
||||
Span<byte> saveDataMetaNameBuffer = MemoryMarshal.CreateSpan(ref bufferRef, bufferLength);
|
||||
|
||||
return metaDirFs.Target.OpenFile(out metaFile, new U8Span(saveMetaPathBuffer), OpenMode.ReadWrite);
|
||||
var saveDataMetaName = new Path();
|
||||
rc = PathFunctions.SetUpFixedPathSaveMetaName(ref saveDataMetaName, saveDataMetaNameBuffer,
|
||||
(uint)metaType);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return metaDirFs.Target.OpenFile(out metaFile, in saveDataMetaName, OpenMode.ReadWrite);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@ -334,25 +377,31 @@ namespace LibHac.FsSrv
|
||||
}
|
||||
|
||||
public Result CreateSaveDataFileSystem(ulong saveDataId, in SaveDataAttribute attribute,
|
||||
in SaveDataCreationInfo creationInfo, U8Span saveDataRootPath, in Optional<HashSalt> hashSalt,
|
||||
in SaveDataCreationInfo creationInfo, in Path saveDataRootPath, in Optional<HashSalt> hashSalt,
|
||||
bool skipFormat)
|
||||
{
|
||||
// Use directory save data for now
|
||||
|
||||
// Hack around error CS8350.
|
||||
const int bufferLength = 0x12;
|
||||
Span<byte> buffer = stackalloc byte[bufferLength];
|
||||
ref byte bufferRef = ref MemoryMarshal.GetReference(buffer);
|
||||
Span<byte> saveImageNameBuffer = MemoryMarshal.CreateSpan(ref bufferRef, bufferLength);
|
||||
|
||||
ReferenceCountedDisposable<IFileSystem> saveDirectoryFs = null;
|
||||
try
|
||||
{
|
||||
Result rc = OpenSaveDataDirectoryFileSystem(out saveDirectoryFs, creationInfo.SpaceId, saveDataRootPath,
|
||||
false);
|
||||
Result rc = OpenSaveDataDirectoryFileSystem(out saveDirectoryFs, creationInfo.SpaceId,
|
||||
in saveDataRootPath, false);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
Span<byte> saveDataPathBuffer = stackalloc byte[0x12];
|
||||
var sb = new U8StringBuilder(saveDataPathBuffer);
|
||||
sb.Append((byte)'/').AppendFormat(saveDataId, 'x', 16);
|
||||
var saveImageName = new Path();
|
||||
rc = PathFunctions.SetUpFixedPathSaveId(ref saveImageName, saveImageNameBuffer, saveDataId);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (_config.IsPseudoSaveData())
|
||||
{
|
||||
rc = Utility.EnsureDirectory(saveDirectoryFs.Target, new U8Span(saveDataPathBuffer));
|
||||
rc = Utility12.EnsureDirectory(saveDirectoryFs.Target, in saveImageName);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
ReferenceCountedDisposable<IFileSystem> saveFs = null;
|
||||
@ -404,51 +453,55 @@ namespace LibHac.FsSrv
|
||||
}
|
||||
}
|
||||
|
||||
private Result WipeData(IFileSystem fileSystem, U8Span filePath, RandomDataGenerator random)
|
||||
private Result WipeData(IFileSystem fileSystem, in Path filePath, RandomDataGenerator random)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Result DeleteSaveDataFileSystem(SaveDataSpaceId spaceId, ulong saveDataId, bool wipeSaveFile,
|
||||
U8Span saveDataRootPath)
|
||||
in Path saveDataRootPath)
|
||||
{
|
||||
// Hack around error CS8350.
|
||||
const int bufferLength = 0x12;
|
||||
Span<byte> buffer = stackalloc byte[bufferLength];
|
||||
ref byte bufferRef = ref MemoryMarshal.GetReference(buffer);
|
||||
Span<byte> saveImageNameBuffer = MemoryMarshal.CreateSpan(ref bufferRef, bufferLength);
|
||||
|
||||
ReferenceCountedDisposable<IFileSystem> fileSystem = null;
|
||||
try
|
||||
{
|
||||
_saveDataFsCacheManager.Unregister(spaceId, saveDataId);
|
||||
|
||||
// Open the directory containing the save data
|
||||
Result rc = OpenSaveDataDirectoryFileSystem(out fileSystem, spaceId, saveDataRootPath, false);
|
||||
Result rc = OpenSaveDataDirectoryFileSystem(out fileSystem, spaceId, in saveDataRootPath, false);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
// Get the path of the save data
|
||||
Span<byte> saveDataPathBuffer = stackalloc byte[0x12];
|
||||
var saveDataPath = new U8Span(saveDataPathBuffer);
|
||||
|
||||
var sb = new U8StringBuilder(saveDataPathBuffer);
|
||||
sb.Append((byte)'/').AppendFormat(saveDataId, 'x', 16);
|
||||
var saveImageName = new Path();
|
||||
rc = PathFunctions.SetUpFixedPathSaveId(ref saveImageName, saveImageNameBuffer, saveDataId);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
// Check if the save data is a file or a directory
|
||||
rc = fileSystem.Target.GetEntryType(out DirectoryEntryType entryType, saveDataPath);
|
||||
rc = fileSystem.Target.GetEntryType(out DirectoryEntryType entryType, in saveImageName);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
// Delete the save data, wiping the file if needed
|
||||
if (entryType == DirectoryEntryType.Directory)
|
||||
{
|
||||
rc = fileSystem.Target.DeleteDirectoryRecursively(saveDataPath);
|
||||
rc = fileSystem.Target.DeleteDirectoryRecursively(in saveImageName);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (wipeSaveFile)
|
||||
{
|
||||
WipeData(fileSystem.Target, saveDataPath, _config.GenerateRandomData).IgnoreResult();
|
||||
WipeData(fileSystem.Target, in saveImageName, _config.GenerateRandomData).IgnoreResult();
|
||||
}
|
||||
|
||||
rc = fileSystem.Target.DeleteDirectoryRecursively(saveDataPath);
|
||||
rc = fileSystem.Target.DeleteFile(in saveImageName);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
|
||||
saveImageName.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
finally
|
||||
@ -458,7 +511,7 @@ namespace LibHac.FsSrv
|
||||
}
|
||||
|
||||
public Result ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraData, SaveDataSpaceId spaceId,
|
||||
ulong saveDataId, SaveDataType type, U8Span saveDataRootPath)
|
||||
ulong saveDataId, SaveDataType type, in Path saveDataRootPath)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out extraData);
|
||||
|
||||
@ -514,7 +567,7 @@ namespace LibHac.FsSrv
|
||||
}
|
||||
|
||||
public Result WriteSaveDataFileSystemExtraData(SaveDataSpaceId spaceId, ulong saveDataId,
|
||||
in SaveDataExtraData extraData, U8Span saveDataRootPath, SaveDataType type, bool updateTimeStamp)
|
||||
in SaveDataExtraData extraData, in Path saveDataRootPath, SaveDataType type, bool updateTimeStamp)
|
||||
{
|
||||
// Nintendo does nothing when writing directory save data extra data.
|
||||
// We've extended directory save data to store extra data so we don't return early.
|
||||
@ -572,7 +625,7 @@ namespace LibHac.FsSrv
|
||||
}
|
||||
|
||||
public Result CorruptSaveDataFileSystem(SaveDataSpaceId spaceId, ulong saveDataId, long offset,
|
||||
U8Span saveDataRootPath)
|
||||
in Path saveDataRootPath)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
@ -582,43 +635,51 @@ namespace LibHac.FsSrv
|
||||
return _config.TimeService.GetCurrentPosixTime(out timeStamp);
|
||||
}
|
||||
|
||||
private bool IsSaveEmulated(U8Span saveDataRootPath)
|
||||
private bool IsSaveEmulated(in Path saveDataRootPath)
|
||||
{
|
||||
return !saveDataRootPath.IsEmpty();
|
||||
}
|
||||
|
||||
public Result OpenSaveDataDirectoryFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem,
|
||||
SaveDataSpaceId spaceId, U8Span saveDataRootPath, bool allowEmulatedSave)
|
||||
SaveDataSpaceId spaceId)
|
||||
{
|
||||
if (allowEmulatedSave && IsAllowedDirectorySaveData(spaceId, saveDataRootPath))
|
||||
var rootPath = new Path();
|
||||
|
||||
return OpenSaveDataDirectoryFileSystem(out fileSystem, spaceId, in rootPath, true);
|
||||
}
|
||||
|
||||
public Result OpenSaveDataDirectoryFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem,
|
||||
SaveDataSpaceId spaceId, in Path saveDataRootPath, bool allowEmulatedSave)
|
||||
{
|
||||
Result rc;
|
||||
UnsafeHelpers.SkipParamInit(out fileSystem);
|
||||
|
||||
if (allowEmulatedSave && IsAllowedDirectorySaveData(spaceId, in saveDataRootPath))
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out fileSystem);
|
||||
ReferenceCountedDisposable<IFileSystem> tmFs = null;
|
||||
|
||||
try
|
||||
{
|
||||
// Ensure the target save data directory exists
|
||||
Result rc = _config.TargetManagerFsCreator.Create(out tmFs, false);
|
||||
rc = _config.TargetManagerFsCreator.Create(out tmFs, in saveDataRootPath, false, true,
|
||||
ResultFs.SaveDataRootPathUnavailable.Value);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = Utility.EnsureDirectory(tmFs.Target, saveDataRootPath);
|
||||
if (rc.IsFailure())
|
||||
return ResultFs.SaveDataRootPathUnavailable.LogConverted(rc);
|
||||
|
||||
tmFs.Dispose();
|
||||
|
||||
// Nintendo does a straight copy here without checking the input path length,
|
||||
// stopping before the null terminator.
|
||||
// FsPath used instead of stackalloc for convenience
|
||||
FsPath.FromSpan(out FsPath path, saveDataRootPath).IgnoreResult();
|
||||
|
||||
rc = _config.TargetManagerFsCreator.NormalizeCaseOfPath(out bool isTargetFsCaseSensitive, path.Str);
|
||||
var path = new Path();
|
||||
rc = path.Initialize(in saveDataRootPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = _config.TargetManagerFsCreator.Create(out tmFs, isTargetFsCaseSensitive);
|
||||
rc = _config.TargetManagerFsCreator.NormalizeCaseOfPath(out bool isTargetFsCaseSensitive, ref path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return Utility.CreateSubDirectoryFileSystem(out fileSystem, ref tmFs, new U8Span(path.Str), true);
|
||||
rc = _config.TargetManagerFsCreator.Create(out tmFs, in path, isTargetFsCaseSensitive, false,
|
||||
ResultFs.SaveDataRootPathUnavailable.Value);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
path.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
finally
|
||||
{
|
||||
@ -626,6 +687,7 @@ namespace LibHac.FsSrv
|
||||
}
|
||||
}
|
||||
|
||||
var saveDataDirPath = new Path();
|
||||
ReadOnlySpan<byte> saveDirName;
|
||||
|
||||
if (spaceId == SaveDataSpaceId.Temporary)
|
||||
@ -637,22 +699,29 @@ namespace LibHac.FsSrv
|
||||
saveDirName = new[] { (byte)'/', (byte)'s', (byte)'a', (byte)'v', (byte)'e' }; // /save
|
||||
}
|
||||
|
||||
return OpenSaveDataDirectoryFileSystemImpl(out fileSystem, spaceId, new U8Span(saveDirName), true);
|
||||
rc = PathFunctions.SetUpFixedPath(ref saveDataDirPath, saveDirName);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = OpenSaveDataDirectoryFileSystemImpl(out fileSystem, spaceId, in saveDataDirPath, true);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
saveDataDirPath.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result OpenSaveDataDirectoryFileSystemImpl(out ReferenceCountedDisposable<IFileSystem> fileSystem,
|
||||
SaveDataSpaceId spaceId, U8Span basePath)
|
||||
SaveDataSpaceId spaceId, in Path basePath)
|
||||
{
|
||||
return OpenSaveDataDirectoryFileSystemImpl(out fileSystem, spaceId, basePath, true);
|
||||
return OpenSaveDataDirectoryFileSystemImpl(out fileSystem, spaceId, in basePath, true);
|
||||
}
|
||||
|
||||
public Result OpenSaveDataDirectoryFileSystemImpl(out ReferenceCountedDisposable<IFileSystem> fileSystem,
|
||||
SaveDataSpaceId spaceId, U8Span basePath, bool createIfMissing)
|
||||
SaveDataSpaceId spaceId, in Path basePath, bool createIfMissing)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out fileSystem);
|
||||
|
||||
ReferenceCountedDisposable<IFileSystem> tempFs = null;
|
||||
ReferenceCountedDisposable<IFileSystem> tempSubFs = null;
|
||||
ReferenceCountedDisposable<IFileSystem> baseFileSystem = null;
|
||||
ReferenceCountedDisposable<IFileSystem> tempFileSystem = null;
|
||||
try
|
||||
{
|
||||
Result rc;
|
||||
@ -660,51 +729,63 @@ namespace LibHac.FsSrv
|
||||
switch (spaceId)
|
||||
{
|
||||
case SaveDataSpaceId.System:
|
||||
rc = _config.BaseFsService.OpenBisFileSystem(out tempFs, U8Span.Empty, BisPartitionId.System,
|
||||
true);
|
||||
rc = _config.BaseFsService.OpenBisFileSystem(out baseFileSystem, BisPartitionId.System, true);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return Utility.WrapSubDirectory(out fileSystem, ref tempFs, basePath, createIfMissing);
|
||||
return Utility.WrapSubDirectory(out fileSystem, ref baseFileSystem, in basePath, createIfMissing);
|
||||
|
||||
case SaveDataSpaceId.User:
|
||||
case SaveDataSpaceId.Temporary:
|
||||
rc = _config.BaseFsService.OpenBisFileSystem(out tempFs, U8Span.Empty, BisPartitionId.User,
|
||||
true);
|
||||
rc = _config.BaseFsService.OpenBisFileSystem(out baseFileSystem, BisPartitionId.User, true);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return Utility.WrapSubDirectory(out fileSystem, ref tempFs, basePath, createIfMissing);
|
||||
return Utility.WrapSubDirectory(out fileSystem, ref baseFileSystem, in basePath, createIfMissing);
|
||||
|
||||
case SaveDataSpaceId.SdSystem:
|
||||
case SaveDataSpaceId.SdCache:
|
||||
rc = _config.BaseFsService.OpenSdCardProxyFileSystem(out tempFs, true);
|
||||
rc = _config.BaseFsService.OpenSdCardProxyFileSystem(out baseFileSystem, true);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
Unsafe.SkipInit(out FsPath path);
|
||||
var sb = new U8StringBuilder(path.Str);
|
||||
sb.Append((byte)'/')
|
||||
.Append(basePath.Value)
|
||||
.Append((byte)'/')
|
||||
.Append(CommonPaths.SdCardNintendoRootDirectoryName);
|
||||
// Hack around error CS8350.
|
||||
const int bufferLength = 0x40;
|
||||
Span<byte> buffer = stackalloc byte[bufferLength];
|
||||
ref byte bufferRef = ref MemoryMarshal.GetReference(buffer);
|
||||
Span<byte> pathParentBuffer = MemoryMarshal.CreateSpan(ref bufferRef, bufferLength);
|
||||
|
||||
rc = Utility.WrapSubDirectory(out tempSubFs, ref tempFs, path, createIfMissing);
|
||||
var parentPath = new Path();
|
||||
rc = PathFunctions.SetUpFixedPathSingleEntry(ref parentPath, pathParentBuffer,
|
||||
CommonPaths.SdCardNintendoRootDirectoryName);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return _config.EncryptedFsCreator.Create(out fileSystem, tempSubFs, EncryptedFsKeyId.Save,
|
||||
in _encryptionSeed);
|
||||
var pathSdRoot = new Path();
|
||||
rc = pathSdRoot.Combine(in parentPath, in basePath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
tempFileSystem = Shared.Move(ref baseFileSystem);
|
||||
rc = Utility.WrapSubDirectory(out baseFileSystem, ref tempFileSystem, in pathSdRoot,
|
||||
createIfMissing);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = _config.EncryptedFsCreator.Create(out fileSystem, ref baseFileSystem,
|
||||
IEncryptedFileSystemCreator.KeyId.Save, in _encryptionSeed);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
parentPath.Dispose();
|
||||
pathSdRoot.Dispose();
|
||||
return Result.Success;
|
||||
|
||||
case SaveDataSpaceId.ProperSystem:
|
||||
rc = _config.BaseFsService.OpenBisFileSystem(out tempFs, U8Span.Empty,
|
||||
rc = _config.BaseFsService.OpenBisFileSystem(out baseFileSystem,
|
||||
BisPartitionId.SystemProperPartition, true);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return Utility.WrapSubDirectory(out fileSystem, ref tempFs, basePath, createIfMissing);
|
||||
return Utility.WrapSubDirectory(out fileSystem, ref baseFileSystem, in basePath, createIfMissing);
|
||||
|
||||
case SaveDataSpaceId.SafeMode:
|
||||
rc = _config.BaseFsService.OpenBisFileSystem(out tempFs, U8Span.Empty, BisPartitionId.SafeMode,
|
||||
true);
|
||||
rc = _config.BaseFsService.OpenBisFileSystem(out baseFileSystem, BisPartitionId.SafeMode, true);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return Utility.WrapSubDirectory(out fileSystem, ref tempFs, basePath, createIfMissing);
|
||||
return Utility.WrapSubDirectory(out fileSystem, ref baseFileSystem, in basePath, createIfMissing);
|
||||
|
||||
default:
|
||||
return ResultFs.InvalidArgument.Log();
|
||||
@ -712,8 +793,8 @@ namespace LibHac.FsSrv
|
||||
}
|
||||
finally
|
||||
{
|
||||
tempFs?.Dispose();
|
||||
tempSubFs?.Dispose();
|
||||
baseFileSystem?.Dispose();
|
||||
tempFileSystem?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@ -733,18 +814,18 @@ namespace LibHac.FsSrv
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public bool IsAllowedDirectorySaveData(SaveDataSpaceId spaceId, U8Span saveDataRootPath)
|
||||
public bool IsAllowedDirectorySaveData(SaveDataSpaceId spaceId, in Path saveDataRootPath)
|
||||
{
|
||||
return spaceId == SaveDataSpaceId.User && IsSaveEmulated(saveDataRootPath);
|
||||
return spaceId == SaveDataSpaceId.User && IsSaveEmulated(in saveDataRootPath);
|
||||
}
|
||||
|
||||
// Todo: remove once file save data is supported
|
||||
// Used to always allow directory save data in OpenSaveDataFileSystem
|
||||
public bool IsAllowedDirectorySaveData2(SaveDataSpaceId spaceId, U8Span saveDataRootPath)
|
||||
public bool IsAllowedDirectorySaveData2(SaveDataSpaceId spaceId, in Path saveDataRootPath)
|
||||
{
|
||||
// Todo: remove "|| true" once file save data is supported
|
||||
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
|
||||
return spaceId == SaveDataSpaceId.User && IsSaveEmulated(saveDataRootPath) || true;
|
||||
return spaceId == SaveDataSpaceId.User && IsSaveEmulated(in saveDataRootPath) || true;
|
||||
}
|
||||
|
||||
public bool IsDeviceUniqueMac(SaveDataSpaceId spaceId)
|
||||
|
@ -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;
|
||||
|
@ -12,5 +12,6 @@ namespace LibHac.FsSrv.Sf
|
||||
Result SetSize(long size);
|
||||
Result GetSize(out long size);
|
||||
Result OperateRange(out QueryRangeInfo rangeInfo, int operationId, long offset, long size);
|
||||
Result OperateRangeWithBuffer(OutBuffer outBuffer, InBuffer inBuffer, int operationId, long offset, long size);
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Sf;
|
||||
using IFileSf = LibHac.FsSrv.Sf.IFile;
|
||||
using IDirectorySf = LibHac.FsSrv.Sf.IDirectory;
|
||||
|
||||
@ -13,8 +14,8 @@ namespace LibHac.FsSrv.Sf
|
||||
Result CreateDirectory(in Path path);
|
||||
Result DeleteDirectory(in Path path);
|
||||
Result DeleteDirectoryRecursively(in Path path);
|
||||
Result RenameFile(in Path oldPath, in Path newPath);
|
||||
Result RenameDirectory(in Path oldPath, in Path newPath);
|
||||
Result RenameFile(in Path currentPath, in Path newPath);
|
||||
Result RenameDirectory(in Path currentPath, in Path newPath);
|
||||
Result GetEntryType(out uint entryType, in Path path);
|
||||
Result OpenFile(out ReferenceCountedDisposable<IFileSf> file, in Path path, uint mode);
|
||||
Result OpenDirectory(out ReferenceCountedDisposable<IDirectorySf> directory, in Path path, uint mode);
|
||||
@ -23,6 +24,6 @@ namespace LibHac.FsSrv.Sf
|
||||
Result GetTotalSpaceSize(out long totalSpace, in Path path);
|
||||
Result CleanDirectoryRecursively(in Path path);
|
||||
Result GetFileTimeStampRaw(out FileTimeStampRaw timeStamp, in Path path);
|
||||
Result QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, int queryId, in Path path);
|
||||
Result QueryEntry(OutBuffer outBuffer, InBuffer inBuffer, int queryId, in Path path);
|
||||
}
|
||||
}
|
||||
|
@ -1,81 +0,0 @@
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.FsSystem;
|
||||
using PathNormalizer = LibHac.FsSrv.Impl.PathNormalizer;
|
||||
|
||||
namespace LibHac.FsSrv
|
||||
{
|
||||
public static class Util
|
||||
{
|
||||
public static Result CreateSubFileSystem(out IFileSystem subFileSystem, IFileSystem baseFileSystem, string path,
|
||||
bool createPathIfMissing)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out subFileSystem);
|
||||
Result rc;
|
||||
|
||||
if (!createPathIfMissing)
|
||||
{
|
||||
if (path == null) return ResultFs.NullptrArgument.Log();
|
||||
|
||||
rc = baseFileSystem.GetEntryType(out DirectoryEntryType entryType, path.ToU8Span());
|
||||
|
||||
if (rc.IsFailure() || entryType != DirectoryEntryType.Directory)
|
||||
{
|
||||
return ResultFs.PathNotFound.Log();
|
||||
}
|
||||
}
|
||||
|
||||
rc = baseFileSystem.EnsureDirectoryExists(path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return CreateSubFileSystemImpl(out subFileSystem, baseFileSystem, path);
|
||||
}
|
||||
|
||||
public static Result CreateSubFileSystemImpl(out IFileSystem subFileSystem, IFileSystem baseFileSystem, string path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out subFileSystem);
|
||||
|
||||
if (path == null) return ResultFs.NullptrArgument.Log();
|
||||
|
||||
Result rc = baseFileSystem.OpenDirectory(out IDirectory _, path.ToU8Span(), OpenDirectoryMode.Directory);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = SubdirectoryFileSystem.CreateNew(out SubdirectoryFileSystem fs, baseFileSystem, path.ToU8String());
|
||||
subFileSystem = fs;
|
||||
return rc;
|
||||
}
|
||||
|
||||
public static Result VerifyHostPath(U8Span path)
|
||||
{
|
||||
if (path.IsEmpty())
|
||||
return Result.Success;
|
||||
|
||||
if (path[0] != StringTraits.DirectorySeparator)
|
||||
return ResultFs.InvalidPathFormat.Log();
|
||||
|
||||
U8Span path2 = path.Slice(1);
|
||||
|
||||
if (path2.IsEmpty())
|
||||
return Result.Success;
|
||||
|
||||
int skipLength = WindowsPath.GetWindowsPathSkipLength(path2);
|
||||
int remainingLength = PathTools.MaxPathLength - skipLength;
|
||||
|
||||
Result rc = PathUtility.VerifyPath(null, path2.Slice(skipLength), remainingLength, remainingLength);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
using var normalizer = new PathNormalizer(path, PathNormalizer.Option.PreserveUnc);
|
||||
return normalizer.Result;
|
||||
}
|
||||
|
||||
public static bool UseDeviceUniqueSaveMac(SaveDataSpaceId spaceId)
|
||||
{
|
||||
return spaceId == SaveDataSpaceId.System ||
|
||||
spaceId == SaveDataSpaceId.User ||
|
||||
spaceId == SaveDataSpaceId.Temporary ||
|
||||
spaceId == SaveDataSpaceId.ProperSystem ||
|
||||
spaceId == SaveDataSpaceId.SafeMode;
|
||||
}
|
||||
}
|
||||
}
|
@ -122,14 +122,13 @@ namespace LibHac.FsSystem
|
||||
return BaseStorage.SetSize(Alignment.AlignUp(size, 0x10));
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
public override void Dispose()
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
BaseStorage.Flush();
|
||||
BaseStorage.Dispose();
|
||||
BaseFile?.Dispose();
|
||||
}
|
||||
BaseStorage.Flush();
|
||||
BaseStorage.Dispose();
|
||||
BaseFile?.Dispose();
|
||||
|
||||
base.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,24 +33,20 @@ namespace LibHac.FsSystem
|
||||
BlockSize = blockSize;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
public override void Dispose()
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
SharedBaseFileSystem?.Dispose();
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
SharedBaseFileSystem?.Dispose();
|
||||
base.Dispose();
|
||||
}
|
||||
|
||||
protected override Result DoCreateDirectory(U8Span path)
|
||||
protected override Result DoCreateDirectory(in Path path)
|
||||
{
|
||||
return BaseFileSystem.CreateDirectory(path);
|
||||
}
|
||||
|
||||
protected override Result DoCreateFile(U8Span path, long size, CreateFileOptions options)
|
||||
protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option)
|
||||
{
|
||||
return CreateFile(path, size, options, new byte[0x20]);
|
||||
return CreateFile(path, size, option, new byte[0x20]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -61,16 +57,16 @@ namespace LibHac.FsSystem
|
||||
/// <param name="options">Flags to control how the file is created.
|
||||
/// Should usually be <see cref="CreateFileOptions.None"/></param>
|
||||
/// <param name="key">The 256-bit key containing a 128-bit data key followed by a 128-bit tweak key.</param>
|
||||
public Result CreateFile(U8Span path, long size, CreateFileOptions options, byte[] key)
|
||||
public Result CreateFile(in Path path, long size, CreateFileOptions options, byte[] key)
|
||||
{
|
||||
long containerSize = AesXtsFile.HeaderLength + Alignment.AlignUp(size, 0x10);
|
||||
|
||||
Result rc = BaseFileSystem.CreateFile(path, containerSize, options);
|
||||
Result rc = BaseFileSystem.CreateFile(in path, containerSize, options);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
var header = new AesXtsFileHeader(key, size, path.ToString(), KekSource, ValidationKey);
|
||||
|
||||
rc = BaseFileSystem.OpenFile(out IFile baseFile, path, OpenMode.Write);
|
||||
rc = BaseFileSystem.OpenFile(out IFile baseFile, in path, OpenMode.Write);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
using (baseFile)
|
||||
@ -82,51 +78,52 @@ namespace LibHac.FsSystem
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoDeleteDirectory(U8Span path)
|
||||
protected override Result DoDeleteDirectory(in Path path)
|
||||
{
|
||||
return BaseFileSystem.DeleteDirectory(path);
|
||||
}
|
||||
|
||||
protected override Result DoDeleteDirectoryRecursively(U8Span path)
|
||||
protected override Result DoDeleteDirectoryRecursively(in Path path)
|
||||
{
|
||||
return BaseFileSystem.DeleteDirectoryRecursively(path);
|
||||
}
|
||||
|
||||
protected override Result DoCleanDirectoryRecursively(U8Span path)
|
||||
protected override Result DoCleanDirectoryRecursively(in Path path)
|
||||
{
|
||||
return BaseFileSystem.CleanDirectoryRecursively(path);
|
||||
}
|
||||
|
||||
protected override Result DoDeleteFile(U8Span path)
|
||||
protected override Result DoDeleteFile(in Path path)
|
||||
{
|
||||
return BaseFileSystem.DeleteFile(path);
|
||||
}
|
||||
|
||||
protected override Result DoOpenDirectory(out IDirectory directory, U8Span path, OpenDirectoryMode mode)
|
||||
protected override Result DoOpenDirectory(out IDirectory directory, in Path path, OpenDirectoryMode mode)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out directory);
|
||||
|
||||
Result rc = BaseFileSystem.OpenDirectory(out IDirectory baseDir, path, mode);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
directory = new AesXtsDirectory(BaseFileSystem, baseDir, path.ToU8String(), mode);
|
||||
directory = new AesXtsDirectory(BaseFileSystem, baseDir, new U8String(path.GetString().ToArray()), mode);
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoOpenFile(out IFile file, U8Span path, OpenMode mode)
|
||||
protected override Result DoOpenFile(out IFile file, in Path path, OpenMode mode)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out file);
|
||||
|
||||
Result rc = BaseFileSystem.OpenFile(out IFile baseFile, path, mode | OpenMode.Read);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
var xtsFile = new AesXtsFile(mode, baseFile, path.ToU8String(), KekSource, ValidationKey, BlockSize);
|
||||
var xtsFile = new AesXtsFile(mode, baseFile, new U8String(path.GetString().ToArray()), KekSource,
|
||||
ValidationKey, BlockSize);
|
||||
|
||||
file = xtsFile;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoRenameDirectory(U8Span oldPath, U8Span newPath)
|
||||
protected override Result DoRenameDirectory(in Path currentPath, in Path newPath)
|
||||
{
|
||||
// todo: Return proper result codes
|
||||
|
||||
@ -138,17 +135,17 @@ namespace LibHac.FsSystem
|
||||
// Reencrypt any modified file headers with the old path
|
||||
// Rename directory to the old path
|
||||
|
||||
Result rc = BaseFileSystem.RenameDirectory(oldPath, newPath);
|
||||
Result rc = BaseFileSystem.RenameDirectory(currentPath, newPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
try
|
||||
{
|
||||
RenameDirectoryImpl(oldPath.ToString(), newPath.ToString(), false);
|
||||
RenameDirectoryImpl(currentPath.ToString(), newPath.ToString(), false);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
RenameDirectoryImpl(oldPath.ToString(), newPath.ToString(), true);
|
||||
BaseFileSystem.RenameDirectory(oldPath, newPath);
|
||||
RenameDirectoryImpl(currentPath.ToString(), newPath.ToString(), true);
|
||||
BaseFileSystem.RenameDirectory(currentPath, newPath);
|
||||
|
||||
throw;
|
||||
}
|
||||
@ -186,13 +183,13 @@ namespace LibHac.FsSystem
|
||||
}
|
||||
}
|
||||
|
||||
protected override Result DoRenameFile(U8Span oldPath, U8Span newPath)
|
||||
protected override Result DoRenameFile(in Path currentPath, in Path newPath)
|
||||
{
|
||||
// todo: Return proper result codes
|
||||
|
||||
AesXtsFileHeader header = ReadXtsHeader(oldPath.ToString(), oldPath.ToString());
|
||||
AesXtsFileHeader header = ReadXtsHeader(currentPath.ToString(), currentPath.ToString());
|
||||
|
||||
Result rc = BaseFileSystem.RenameFile(oldPath, newPath);
|
||||
Result rc = BaseFileSystem.RenameFile(currentPath, newPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
try
|
||||
@ -201,8 +198,8 @@ namespace LibHac.FsSystem
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
BaseFileSystem.RenameFile(newPath, oldPath);
|
||||
WriteXtsHeader(header, oldPath.ToString(), oldPath.ToString());
|
||||
BaseFileSystem.RenameFile(newPath, currentPath);
|
||||
WriteXtsHeader(header, currentPath.ToString(), currentPath.ToString());
|
||||
|
||||
throw;
|
||||
}
|
||||
@ -210,22 +207,22 @@ namespace LibHac.FsSystem
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoGetEntryType(out DirectoryEntryType entryType, U8Span path)
|
||||
protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path)
|
||||
{
|
||||
return BaseFileSystem.GetEntryType(out entryType, path);
|
||||
}
|
||||
|
||||
protected override Result DoGetFileTimeStampRaw(out FileTimeStampRaw timeStamp, U8Span path)
|
||||
protected override Result DoGetFileTimeStampRaw(out FileTimeStampRaw timeStamp, in Path path)
|
||||
{
|
||||
return BaseFileSystem.GetFileTimeStampRaw(out timeStamp, path);
|
||||
}
|
||||
|
||||
protected override Result DoGetFreeSpaceSize(out long freeSpace, U8Span path)
|
||||
protected override Result DoGetFreeSpaceSize(out long freeSpace, in Path path)
|
||||
{
|
||||
return BaseFileSystem.GetFreeSpaceSize(out freeSpace, path);
|
||||
}
|
||||
|
||||
protected override Result DoGetTotalSpaceSize(out long totalSpace, U8Span path)
|
||||
protected override Result DoGetTotalSpaceSize(out long totalSpace, in Path path)
|
||||
{
|
||||
return BaseFileSystem.GetTotalSpaceSize(out totalSpace, path);
|
||||
}
|
||||
@ -246,7 +243,7 @@ namespace LibHac.FsSystem
|
||||
}
|
||||
|
||||
protected override Result DoQueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, QueryId queryId,
|
||||
U8Span path)
|
||||
in Path path)
|
||||
{
|
||||
return BaseFileSystem.QueryEntry(outBuffer, inBuffer, queryId, path);
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
|
||||
@ -7,57 +6,57 @@ namespace LibHac.FsSystem
|
||||
{
|
||||
public class ApplicationTemporaryFileSystem : IFileSystem, ISaveDataExtraDataAccessor
|
||||
{
|
||||
protected override Result DoCreateFile(U8Span path, long size, CreateFileOptions option)
|
||||
protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override Result DoDeleteFile(U8Span path)
|
||||
protected override Result DoDeleteFile(in Path path)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override Result DoCreateDirectory(U8Span path)
|
||||
protected override Result DoCreateDirectory(in Path path)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override Result DoDeleteDirectory(U8Span path)
|
||||
protected override Result DoDeleteDirectory(in Path path)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override Result DoDeleteDirectoryRecursively(U8Span path)
|
||||
protected override Result DoDeleteDirectoryRecursively(in Path path)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override Result DoCleanDirectoryRecursively(U8Span path)
|
||||
protected override Result DoCleanDirectoryRecursively(in Path path)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override Result DoRenameFile(U8Span oldPath, U8Span newPath)
|
||||
protected override Result DoRenameFile(in Path currentPath, in Path newPath)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override Result DoRenameDirectory(U8Span oldPath, U8Span newPath)
|
||||
protected override Result DoRenameDirectory(in Path currentPath, in Path newPath)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override Result DoGetEntryType(out DirectoryEntryType entryType, U8Span path)
|
||||
protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override Result DoOpenFile(out IFile file, U8Span path, OpenMode mode)
|
||||
protected override Result DoOpenFile(out IFile file, in Path path, OpenMode mode)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override Result DoOpenDirectory(out IDirectory directory, U8Span path, OpenDirectoryMode mode)
|
||||
protected override Result DoOpenDirectory(out IDirectory directory, in Path path, OpenDirectoryMode mode)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
@ -1,133 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.Util;
|
||||
|
||||
namespace LibHac.FsSystem
|
||||
{
|
||||
public class ConcatenationDirectory : IDirectory
|
||||
{
|
||||
private OpenDirectoryMode Mode { get; }
|
||||
private IDirectory ParentDirectory { get; }
|
||||
private IFileSystem BaseFileSystem { get; }
|
||||
private ConcatenationFileSystem ParentFileSystem { get; }
|
||||
|
||||
private FsPath _path;
|
||||
|
||||
public ConcatenationDirectory(ConcatenationFileSystem fs, IFileSystem baseFs, IDirectory parentDirectory, OpenDirectoryMode mode, U8Span path)
|
||||
{
|
||||
ParentFileSystem = fs;
|
||||
BaseFileSystem = baseFs;
|
||||
ParentDirectory = parentDirectory;
|
||||
Mode = mode;
|
||||
|
||||
StringUtils.Copy(_path.Str, path);
|
||||
_path.Str[PathTools.MaxPathLength] = StringTraits.NullTerminator;
|
||||
|
||||
// Ensure the path ends with a separator
|
||||
int pathLength = StringUtils.GetLength(path, PathTools.MaxPathLength + 1);
|
||||
|
||||
if (pathLength != 0 && _path.Str[pathLength - 1] == StringTraits.DirectorySeparator)
|
||||
return;
|
||||
|
||||
if (pathLength >= PathTools.MaxPathLength)
|
||||
throw new HorizonResultException(ResultFs.TooLongPath.Value, "abort");
|
||||
|
||||
_path.Str[pathLength] = StringTraits.DirectorySeparator;
|
||||
_path.Str[pathLength + 1] = StringTraits.NullTerminator;
|
||||
_path.Str[PathTools.MaxPathLength] = StringTraits.NullTerminator;
|
||||
}
|
||||
|
||||
protected override Result DoRead(out long entriesRead, Span<DirectoryEntry> entryBuffer)
|
||||
{
|
||||
entriesRead = 0;
|
||||
var entry = new DirectoryEntry();
|
||||
Span<DirectoryEntry> entrySpan = SpanHelpers.AsSpan(ref entry);
|
||||
|
||||
int i;
|
||||
for (i = 0; i < entryBuffer.Length; i++)
|
||||
{
|
||||
Result rc = ParentDirectory.Read(out long baseEntriesRead, entrySpan);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (baseEntriesRead == 0) break;
|
||||
|
||||
// Check if the current open mode says we should return the entry
|
||||
bool isConcatFile = IsConcatenationFile(entry);
|
||||
if (!CanReturnEntry(entry, isConcatFile)) continue;
|
||||
|
||||
if (isConcatFile)
|
||||
{
|
||||
entry.Type = DirectoryEntryType.File;
|
||||
|
||||
if (!Mode.HasFlag(OpenDirectoryMode.NoFileSize))
|
||||
{
|
||||
string entryName = StringUtils.NullTerminatedUtf8ToString(entry.Name);
|
||||
string entryFullPath = PathTools.Combine(_path.ToString(), entryName);
|
||||
|
||||
rc = ParentFileSystem.GetConcatenationFileSize(out long fileSize, entryFullPath.ToU8Span());
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
entry.Size = fileSize;
|
||||
}
|
||||
}
|
||||
|
||||
entry.Attributes = NxFileAttributes.None;
|
||||
|
||||
entryBuffer[i] = entry;
|
||||
}
|
||||
|
||||
entriesRead = i;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoGetEntryCount(out long entryCount)
|
||||
{
|
||||
entryCount = 0;
|
||||
long count = 0;
|
||||
|
||||
Result rc = BaseFileSystem.OpenDirectory(out IDirectory _, _path,
|
||||
OpenDirectoryMode.All | OpenDirectoryMode.NoFileSize);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
var entry = new DirectoryEntry();
|
||||
Span<DirectoryEntry> entrySpan = SpanHelpers.AsSpan(ref entry);
|
||||
|
||||
while (true)
|
||||
{
|
||||
rc = ParentDirectory.Read(out long baseEntriesRead, entrySpan);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (baseEntriesRead == 0) break;
|
||||
|
||||
if (CanReturnEntry(entry, IsConcatenationFile(entry))) count++;
|
||||
}
|
||||
|
||||
entryCount = count;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
private bool CanReturnEntry(DirectoryEntry entry, bool isConcatFile)
|
||||
{
|
||||
return Mode.HasFlag(OpenDirectoryMode.File) && (entry.Type == DirectoryEntryType.File || isConcatFile) ||
|
||||
Mode.HasFlag(OpenDirectoryMode.Directory) && entry.Type == DirectoryEntryType.Directory && !isConcatFile;
|
||||
}
|
||||
|
||||
private bool IsConcatenationFile(DirectoryEntry entry)
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
return ConcatenationFileSystem.HasConcatenationFileAttribute(entry.Attributes);
|
||||
}
|
||||
else
|
||||
{
|
||||
string name = StringUtils.NullTerminatedUtf8ToString(entry.Name);
|
||||
var fullPath = PathTools.Combine(_path.ToString(), name).ToU8Span();
|
||||
|
||||
return ParentFileSystem.IsConcatenationFile(fullPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,249 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.Util;
|
||||
|
||||
namespace LibHac.FsSystem
|
||||
{
|
||||
public class ConcatenationFile : IFile
|
||||
{
|
||||
private IFileSystem BaseFileSystem { get; }
|
||||
private U8String FilePath { get; }
|
||||
private List<IFile> Sources { get; }
|
||||
private long SubFileSize { get; }
|
||||
private OpenMode Mode { get; }
|
||||
|
||||
internal ConcatenationFile(IFileSystem baseFileSystem, U8Span path, IEnumerable<IFile> sources, long subFileSize, OpenMode mode)
|
||||
{
|
||||
BaseFileSystem = baseFileSystem;
|
||||
FilePath = path.ToU8String();
|
||||
Sources = sources.ToList();
|
||||
SubFileSize = subFileSize;
|
||||
Mode = mode;
|
||||
|
||||
for (int i = 0; i < Sources.Count - 1; i++)
|
||||
{
|
||||
Sources[i].GetSize(out long actualSubFileSize).ThrowIfFailure();
|
||||
|
||||
if (actualSubFileSize != SubFileSize)
|
||||
{
|
||||
throw new ArgumentException($"Source file must have size {subFileSize}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override Result DoRead(out long bytesRead, long offset, Span<byte> destination,
|
||||
in ReadOption option)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out bytesRead);
|
||||
|
||||
long inPos = offset;
|
||||
int outPos = 0;
|
||||
|
||||
Result rc = DryRead(out long remaining, offset, destination.Length, in option, Mode);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
GetSize(out long fileSize).ThrowIfFailure();
|
||||
|
||||
while (remaining > 0)
|
||||
{
|
||||
int fileIndex = GetSubFileIndexFromOffset(offset);
|
||||
IFile file = Sources[fileIndex];
|
||||
long fileOffset = offset - fileIndex * SubFileSize;
|
||||
|
||||
long fileEndOffset = Math.Min((fileIndex + 1) * SubFileSize, fileSize);
|
||||
int bytesToRead = (int)Math.Min(fileEndOffset - inPos, remaining);
|
||||
|
||||
rc = file.Read(out long subFileBytesRead, fileOffset, destination.Slice(outPos, bytesToRead), option);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
outPos += (int)subFileBytesRead;
|
||||
inPos += subFileBytesRead;
|
||||
remaining -= subFileBytesRead;
|
||||
|
||||
if (bytesRead < bytesToRead) break;
|
||||
}
|
||||
|
||||
bytesRead = outPos;
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoWrite(long offset, ReadOnlySpan<byte> source, in WriteOption option)
|
||||
{
|
||||
Result rc = DryWrite(out _, offset, source.Length, in option, Mode);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
int inPos = 0;
|
||||
long outPos = offset;
|
||||
int remaining = source.Length;
|
||||
|
||||
rc = GetSize(out long fileSize);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
while (remaining > 0)
|
||||
{
|
||||
int fileIndex = GetSubFileIndexFromOffset(outPos);
|
||||
IFile file = Sources[fileIndex];
|
||||
long fileOffset = outPos - fileIndex * SubFileSize;
|
||||
|
||||
long fileEndOffset = Math.Min((fileIndex + 1) * SubFileSize, fileSize);
|
||||
int bytesToWrite = (int)Math.Min(fileEndOffset - outPos, remaining);
|
||||
|
||||
rc = file.Write(fileOffset, source.Slice(inPos, bytesToWrite), option);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
outPos += bytesToWrite;
|
||||
inPos += bytesToWrite;
|
||||
remaining -= bytesToWrite;
|
||||
}
|
||||
|
||||
if (option.HasFlushFlag())
|
||||
{
|
||||
return Flush();
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoFlush()
|
||||
{
|
||||
foreach (IFile file in Sources)
|
||||
{
|
||||
Result rc = file.Flush();
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoGetSize(out long size)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out size);
|
||||
|
||||
foreach (IFile file in Sources)
|
||||
{
|
||||
Result rc = file.GetSize(out long subFileSize);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
size += subFileSize;
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoOperateRange(Span<byte> outBuffer, OperationId operationId, long offset, long size, ReadOnlySpan<byte> inBuffer)
|
||||
{
|
||||
return ResultFs.NotImplemented.Log();
|
||||
}
|
||||
|
||||
protected override Result DoSetSize(long size)
|
||||
{
|
||||
Result rc = GetSize(out long currentSize);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (currentSize == size) return Result.Success;
|
||||
|
||||
int currentSubFileCount = QuerySubFileCount(currentSize, SubFileSize);
|
||||
int newSubFileCount = QuerySubFileCount(size, SubFileSize);
|
||||
|
||||
if (size > currentSize)
|
||||
{
|
||||
IFile currentLastSubFile = Sources[currentSubFileCount - 1];
|
||||
long newSubFileSize = QuerySubFileSize(currentSubFileCount - 1, size, SubFileSize);
|
||||
|
||||
rc = currentLastSubFile.SetSize(newSubFileSize);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
for (int i = currentSubFileCount; i < newSubFileCount; i++)
|
||||
{
|
||||
Unsafe.SkipInit(out FsPath newSubFilePath);
|
||||
|
||||
rc = ConcatenationFileSystem.GetSubFilePath(newSubFilePath.Str, FilePath, i);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
newSubFileSize = QuerySubFileSize(i, size, SubFileSize);
|
||||
|
||||
rc = BaseFileSystem.CreateFile(newSubFilePath, newSubFileSize, CreateFileOptions.None);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = BaseFileSystem.OpenFile(out IFile newSubFile, newSubFilePath, Mode);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
Sources.Add(newSubFile);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = currentSubFileCount - 1; i > newSubFileCount - 1; i--)
|
||||
{
|
||||
Sources[i].Dispose();
|
||||
Sources.RemoveAt(i);
|
||||
|
||||
Unsafe.SkipInit(out FsPath subFilePath);
|
||||
|
||||
rc = ConcatenationFileSystem.GetSubFilePath(subFilePath.Str, FilePath, i);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = BaseFileSystem.DeleteFile(subFilePath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
|
||||
long newLastFileSize = QuerySubFileSize(newSubFileCount - 1, size, SubFileSize);
|
||||
|
||||
rc = Sources[newSubFileCount - 1].SetSize(newLastFileSize);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
foreach (IFile file in Sources)
|
||||
{
|
||||
file?.Dispose();
|
||||
}
|
||||
|
||||
Sources.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private int GetSubFileIndexFromOffset(long offset)
|
||||
{
|
||||
return (int)(offset / SubFileSize);
|
||||
}
|
||||
|
||||
private static int QuerySubFileCount(long size, long subFileSize)
|
||||
{
|
||||
Debug.Assert(size >= 0);
|
||||
Debug.Assert(subFileSize > 0);
|
||||
|
||||
if (size == 0) return 1;
|
||||
|
||||
return (int)BitUtil.DivideUp(size, subFileSize);
|
||||
}
|
||||
|
||||
private static long QuerySubFileSize(int subFileIndex, long totalSize, long subFileSize)
|
||||
{
|
||||
int subFileCount = QuerySubFileCount(totalSize, subFileSize);
|
||||
|
||||
Debug.Assert(subFileIndex < subFileCount);
|
||||
|
||||
if (subFileIndex + 1 == subFileCount)
|
||||
{
|
||||
long remainder = totalSize % subFileSize;
|
||||
return remainder == 0 ? subFileSize : remainder;
|
||||
}
|
||||
|
||||
return subFileSize;
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,86 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.Util;
|
||||
|
||||
namespace LibHac.FsSystem
|
||||
{
|
||||
public static class DirectoryUtils
|
||||
{
|
||||
public delegate Result Blah(ReadOnlySpan<byte> path, ref DirectoryEntry entry);
|
||||
|
||||
public static Result IterateDirectoryRecursivelyInternal(IFileSystem fs, Span<byte> workPath,
|
||||
ref DirectoryEntry entry, Blah onEnterDir, Blah onExitDir, Blah onFile)
|
||||
{
|
||||
Result rc = fs.OpenDirectory(out IDirectory _, new U8Span(workPath), OpenDirectoryMode.All);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
onFile(workPath, ref entry);
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public static Result IterateDirectoryRecursively(IFileSystem fs, ReadOnlySpan<byte> path, Blah onEnterDir, Blah onExitDir, Blah onFile)
|
||||
{
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public static Result CopyDirectoryRecursively(IFileSystem sourceFs, IFileSystem destFs, string sourcePath,
|
||||
string destPath)
|
||||
{
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public static Result CopyFile(IFileSystem destFs, IFileSystem sourceFs, ReadOnlySpan<byte> destParentPath,
|
||||
ReadOnlySpan<byte> sourcePath, ref DirectoryEntry dirEntry, Span<byte> copyBuffer)
|
||||
{
|
||||
IFile srcFile = null;
|
||||
IFile dstFile = null;
|
||||
|
||||
try
|
||||
{
|
||||
Result rc = sourceFs.OpenFile(out srcFile, new U8Span(sourcePath), OpenMode.Read);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
Unsafe.SkipInit(out FsPath dstPath);
|
||||
dstPath.Str[0] = 0;
|
||||
int dstPathLen = StringUtils.Concat(dstPath.Str, destParentPath);
|
||||
dstPathLen = StringUtils.Concat(dstPath.Str, dirEntry.Name, dstPathLen);
|
||||
|
||||
if (dstPathLen > FsPath.MaxLength)
|
||||
{
|
||||
throw new ArgumentException();
|
||||
}
|
||||
|
||||
rc = destFs.CreateFile(dstPath, dirEntry.Size, CreateFileOptions.None);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = destFs.OpenFile(out dstFile, dstPath, OpenMode.Write);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
long fileSize = dirEntry.Size;
|
||||
long offset = 0;
|
||||
|
||||
while (offset < fileSize)
|
||||
{
|
||||
rc = srcFile.Read(out long bytesRead, offset, copyBuffer, ReadOption.None);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = dstFile.Write(offset, copyBuffer.Slice(0, (int)bytesRead), WriteOption.None);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
offset += bytesRead;
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
finally
|
||||
{
|
||||
srcFile?.Dispose();
|
||||
dstFile?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -7,6 +7,7 @@ using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.Util;
|
||||
using Path = LibHac.Fs.Path;
|
||||
|
||||
namespace LibHac.FsSystem
|
||||
{
|
||||
@ -15,38 +16,119 @@ namespace LibHac.FsSystem
|
||||
public static Result CopyDirectory(this IFileSystem sourceFs, IFileSystem destFs, string sourcePath, string destPath,
|
||||
IProgressReport logger = null, CreateFileOptions options = CreateFileOptions.None)
|
||||
{
|
||||
Result rc;
|
||||
const int bufferSize = 0x100000;
|
||||
|
||||
foreach (DirectoryEntryEx entry in sourceFs.EnumerateEntries(sourcePath, "*", SearchOptions.Default))
|
||||
var directoryEntryBuffer = new DirectoryEntry();
|
||||
|
||||
var sourcePathNormalized = new Path();
|
||||
Result rc = InitializeFromString(ref sourcePathNormalized, sourcePath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
var destPathNormalized = new Path();
|
||||
rc = InitializeFromString(ref destPathNormalized, destPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
byte[] workBuffer = ArrayPool<byte>.Shared.Rent(bufferSize);
|
||||
try
|
||||
{
|
||||
string subSrcPath = PathTools.Normalize(PathTools.Combine(sourcePath, entry.Name));
|
||||
string subDstPath = PathTools.Normalize(PathTools.Combine(destPath, entry.Name));
|
||||
return CopyDirectoryRecursively(destFs, sourceFs, in destPathNormalized, in sourcePathNormalized,
|
||||
ref directoryEntryBuffer, workBuffer, logger, options);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(workBuffer);
|
||||
logger?.SetTotal(0);
|
||||
}
|
||||
}
|
||||
|
||||
if (entry.Type == DirectoryEntryType.Directory)
|
||||
public static Result CopyDirectoryRecursively(IFileSystem destinationFileSystem, IFileSystem sourceFileSystem,
|
||||
in Path destinationPath, in Path sourcePath, ref DirectoryEntry dirEntry, Span<byte> workBuffer,
|
||||
IProgressReport logger = null, CreateFileOptions option = CreateFileOptions.None)
|
||||
{
|
||||
static Result OnEnterDir(in Path path, in DirectoryEntry entry,
|
||||
ref Utility12.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 Utility12.FsIterationTaskClosure closure)
|
||||
{
|
||||
return closure.DestinationPathBuffer.RemoveChild();
|
||||
}
|
||||
|
||||
Result OnFile(in Path path, in DirectoryEntry entry, ref Utility12.FsIterationTaskClosure closure)
|
||||
{
|
||||
logger?.LogMessage(path.ToString());
|
||||
|
||||
Result result = closure.DestinationPathBuffer.AppendChild(entry.Name);
|
||||
if (result.IsFailure()) return result;
|
||||
|
||||
result = CopyFile(closure.DestFileSystem, closure.SourceFileSystem, in closure.DestinationPathBuffer,
|
||||
in path, closure.Buffer, logger, option);
|
||||
if (result.IsFailure()) return result;
|
||||
|
||||
return closure.DestinationPathBuffer.RemoveChild();
|
||||
}
|
||||
|
||||
var taskClosure = new Utility12.FsIterationTaskClosure();
|
||||
taskClosure.Buffer = workBuffer;
|
||||
taskClosure.SourceFileSystem = sourceFileSystem;
|
||||
taskClosure.DestFileSystem = destinationFileSystem;
|
||||
|
||||
Result rc = taskClosure.DestinationPathBuffer.Initialize(destinationPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = Utility12.IterateDirectoryRecursively(sourceFileSystem, in sourcePath, ref dirEntry, OnEnterDir,
|
||||
OnExitDir, OnFile, ref taskClosure);
|
||||
|
||||
taskClosure.DestinationPathBuffer.Dispose();
|
||||
return rc;
|
||||
}
|
||||
|
||||
public static Result CopyFile(IFileSystem destFileSystem, IFileSystem sourceFileSystem, in Path destPath,
|
||||
in Path sourcePath, Span<byte> workBuffer, IProgressReport logger = null,
|
||||
CreateFileOptions option = CreateFileOptions.None)
|
||||
{
|
||||
logger?.LogMessage(sourcePath.ToString());
|
||||
|
||||
// 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 = CreateOrOverwriteFile(destFileSystem, in destPath, fileSize, option);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = destFileSystem.OpenFile(out IFile destFile, in destPath, OpenMode.Write);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
using (destFile)
|
||||
{
|
||||
destFs.EnsureDirectoryExists(subDstPath);
|
||||
// Read/Write file in work buffer sized chunks.
|
||||
long remaining = fileSize;
|
||||
long offset = 0;
|
||||
|
||||
rc = sourceFs.CopyDirectory(destFs, subSrcPath, subDstPath, logger, options);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
logger?.SetTotal(fileSize);
|
||||
|
||||
if (entry.Type == DirectoryEntryType.File)
|
||||
{
|
||||
destFs.CreateOrOverwriteFile(subDstPath, entry.Size, options);
|
||||
|
||||
rc = sourceFs.OpenFile(out IFile srcFile, subSrcPath.ToU8Span(), OpenMode.Read);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
using (srcFile)
|
||||
while (remaining > 0)
|
||||
{
|
||||
rc = destFs.OpenFile(out IFile dstFile, subDstPath.ToU8Span(), OpenMode.Write | OpenMode.AllowAppend);
|
||||
rc = sourceFile.Read(out long bytesRead, offset, workBuffer, ReadOption.None);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
using (dstFile)
|
||||
{
|
||||
logger?.LogMessage(subSrcPath);
|
||||
srcFile.CopyTo(dstFile, logger);
|
||||
}
|
||||
rc = destFile.Write(offset, workBuffer.Slice(0, (int)bytesRead), WriteOption.None);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
remaining -= bytesRead;
|
||||
offset += bytesRead;
|
||||
|
||||
logger?.ReportAdd(bytesRead);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -81,9 +163,10 @@ namespace LibHac.FsSystem
|
||||
bool ignoreCase = searchOptions.HasFlag(SearchOptions.CaseInsensitive);
|
||||
bool recurse = searchOptions.HasFlag(SearchOptions.RecurseSubdirectories);
|
||||
|
||||
IFileSystem fs = fileSystem;
|
||||
var pathNormalized = new Path();
|
||||
InitializeFromString(ref pathNormalized, path).ThrowIfFailure();
|
||||
|
||||
fileSystem.OpenDirectory(out IDirectory directory, path.ToU8Span(), OpenDirectoryMode.All).ThrowIfFailure();
|
||||
fileSystem.OpenDirectory(out IDirectory directory, in pathNormalized, OpenDirectoryMode.All).ThrowIfFailure();
|
||||
|
||||
while (true)
|
||||
{
|
||||
@ -102,7 +185,7 @@ namespace LibHac.FsSystem
|
||||
if (entry.Type != DirectoryEntryType.Directory || !recurse) continue;
|
||||
|
||||
IEnumerable<DirectoryEntryEx> subEntries =
|
||||
fs.EnumerateEntries(PathTools.Combine(path, entry.Name), searchPattern,
|
||||
fileSystem.EnumerateEntries(PathTools.Combine(path, entry.Name), searchPattern,
|
||||
searchOptions);
|
||||
|
||||
foreach (DirectoryEntryEx subEntry in subEntries)
|
||||
@ -202,7 +285,10 @@ namespace LibHac.FsSystem
|
||||
|
||||
public static void SetConcatenationFileAttribute(this IFileSystem fs, string path)
|
||||
{
|
||||
fs.QueryEntry(Span<byte>.Empty, Span<byte>.Empty, QueryId.MakeConcatFile, path.ToU8Span());
|
||||
var pathNormalized = new Path();
|
||||
InitializeFromString(ref pathNormalized, path).ThrowIfFailure();
|
||||
|
||||
fs.QueryEntry(Span<byte>.Empty, Span<byte>.Empty, QueryId.SetConcatenationFileAttribute, in pathNormalized);
|
||||
}
|
||||
|
||||
public static void CleanDirectoryRecursivelyGeneric(IFileSystem fileSystem, string path)
|
||||
@ -213,14 +299,17 @@ namespace LibHac.FsSystem
|
||||
{
|
||||
string subPath = PathTools.Combine(path, entry.Name);
|
||||
|
||||
var subPathNormalized = new Path();
|
||||
InitializeFromString(ref subPathNormalized, subPath).ThrowIfFailure();
|
||||
|
||||
if (entry.Type == DirectoryEntryType.Directory)
|
||||
{
|
||||
CleanDirectoryRecursivelyGeneric(fileSystem, subPath);
|
||||
fs.DeleteDirectory(subPath.ToU8Span());
|
||||
fs.DeleteDirectory(in subPathNormalized);
|
||||
}
|
||||
else if (entry.Type == DirectoryEntryType.File)
|
||||
{
|
||||
fs.DeleteFile(subPath.ToU8Span());
|
||||
fs.DeleteFile(in subPathNormalized);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -251,55 +340,46 @@ namespace LibHac.FsSystem
|
||||
|
||||
public static Result EnsureDirectoryExists(this IFileSystem fs, string path)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
if (fs.DirectoryExists(path)) return Result.Success;
|
||||
var pathNormalized = new Path();
|
||||
Result rc = InitializeFromString(ref pathNormalized, path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
// Find the first subdirectory in the chain that doesn't exist
|
||||
int i;
|
||||
for (i = path.Length - 1; i > 0; i--)
|
||||
{
|
||||
if (path[i] == '/')
|
||||
{
|
||||
string subPath = path.Substring(0, i);
|
||||
|
||||
if (fs.DirectoryExists(subPath))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// path[i] will be a '/', so skip that character
|
||||
i++;
|
||||
|
||||
// loop until `path.Length - 1` so CreateDirectory won't be called multiple
|
||||
// times on path if the last character in the path is a '/'
|
||||
for (; i < path.Length - 1; i++)
|
||||
{
|
||||
if (path[i] == '/')
|
||||
{
|
||||
string subPath = path.Substring(0, i);
|
||||
|
||||
Result rc = fs.CreateDirectory(subPath.ToU8Span());
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
}
|
||||
|
||||
return fs.CreateDirectory(path.ToU8Span());
|
||||
return Utility12.EnsureDirectory(fs, in pathNormalized);
|
||||
}
|
||||
|
||||
public static void CreateOrOverwriteFile(this IFileSystem fs, string path, long size)
|
||||
public static Result CreateOrOverwriteFile(IFileSystem fileSystem, in Path path, long size,
|
||||
CreateFileOptions option = CreateFileOptions.None)
|
||||
{
|
||||
fs.CreateOrOverwriteFile(path, size, CreateFileOptions.None);
|
||||
Result rc = fileSystem.CreateFile(in path, size, option);
|
||||
|
||||
if (rc.IsFailure())
|
||||
{
|
||||
if (!ResultFs.PathAlreadyExists.Includes(rc))
|
||||
return rc;
|
||||
|
||||
rc = fileSystem.DeleteFile(in path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = fileSystem.CreateFile(in path, size, option);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public static void CreateOrOverwriteFile(this IFileSystem fs, string path, long size, CreateFileOptions options)
|
||||
private static Result InitializeFromString(ref Path outPath, string path)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
ReadOnlySpan<byte> utf8Path = StringUtils.StringToUtf8(path);
|
||||
|
||||
if (fs.FileExists(path)) fs.DeleteFile(path.ToU8Span());
|
||||
Result rc = outPath.Initialize(utf8Path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
fs.CreateFile(path.ToU8Span(), size, CreateFileOptions.None);
|
||||
var pathFlags = new PathFlags();
|
||||
pathFlags.AllowEmptyPath();
|
||||
outPath.Normalize(pathFlags);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19,50 +19,46 @@ namespace LibHac.FsSystem
|
||||
BaseFileSystem = Shared.Move(ref baseFileSystem);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
public override void Dispose()
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
BaseFileSystem?.Dispose();
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
BaseFileSystem?.Dispose();
|
||||
base.Dispose();
|
||||
}
|
||||
|
||||
protected override Result DoCreateFile(U8Span path, long size, CreateFileOptions option) =>
|
||||
protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option) =>
|
||||
BaseFileSystem.Target.CreateFile(path, size, option);
|
||||
|
||||
protected override Result DoDeleteFile(U8Span path) => BaseFileSystem.Target.DeleteFile(path);
|
||||
protected override Result DoDeleteFile(in Path path) => BaseFileSystem.Target.DeleteFile(path);
|
||||
|
||||
protected override Result DoCreateDirectory(U8Span path) => BaseFileSystem.Target.CreateDirectory(path);
|
||||
protected override Result DoCreateDirectory(in Path path) => BaseFileSystem.Target.CreateDirectory(path);
|
||||
|
||||
protected override Result DoDeleteDirectory(U8Span path) => BaseFileSystem.Target.DeleteDirectory(path);
|
||||
protected override Result DoDeleteDirectory(in Path path) => BaseFileSystem.Target.DeleteDirectory(path);
|
||||
|
||||
protected override Result DoDeleteDirectoryRecursively(U8Span path) =>
|
||||
protected override Result DoDeleteDirectoryRecursively(in Path path) =>
|
||||
BaseFileSystem.Target.DeleteDirectoryRecursively(path);
|
||||
|
||||
protected override Result DoCleanDirectoryRecursively(U8Span path) =>
|
||||
protected override Result DoCleanDirectoryRecursively(in Path path) =>
|
||||
BaseFileSystem.Target.CleanDirectoryRecursively(path);
|
||||
|
||||
protected override Result DoRenameFile(U8Span oldPath, U8Span newPath) =>
|
||||
BaseFileSystem.Target.RenameFile(oldPath, newPath);
|
||||
protected override Result DoRenameFile(in Path currentPath, in Path newPath) =>
|
||||
BaseFileSystem.Target.RenameFile(currentPath, newPath);
|
||||
|
||||
protected override Result DoRenameDirectory(U8Span oldPath, U8Span newPath) =>
|
||||
BaseFileSystem.Target.RenameDirectory(oldPath, newPath);
|
||||
protected override Result DoRenameDirectory(in Path currentPath, in Path newPath) =>
|
||||
BaseFileSystem.Target.RenameDirectory(currentPath, newPath);
|
||||
|
||||
protected override Result DoGetEntryType(out DirectoryEntryType entryType, U8Span path) =>
|
||||
protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path) =>
|
||||
BaseFileSystem.Target.GetEntryType(out entryType, path);
|
||||
|
||||
protected override Result DoGetFreeSpaceSize(out long freeSpace, U8Span path) =>
|
||||
protected override Result DoGetFreeSpaceSize(out long freeSpace, in Path path) =>
|
||||
BaseFileSystem.Target.GetFreeSpaceSize(out freeSpace, path);
|
||||
|
||||
protected override Result DoGetTotalSpaceSize(out long totalSpace, U8Span path) =>
|
||||
protected override Result DoGetTotalSpaceSize(out long totalSpace, in Path path) =>
|
||||
BaseFileSystem.Target.GetTotalSpaceSize(out totalSpace, path);
|
||||
|
||||
protected override Result DoOpenFile(out IFile file, U8Span path, OpenMode mode) =>
|
||||
protected override Result DoOpenFile(out IFile file, in Path path, OpenMode mode) =>
|
||||
BaseFileSystem.Target.OpenFile(out file, path, mode);
|
||||
|
||||
protected override Result DoOpenDirectory(out IDirectory directory, U8Span path, OpenDirectoryMode mode) =>
|
||||
protected override Result DoOpenDirectory(out IDirectory directory, in Path path, OpenDirectoryMode mode) =>
|
||||
BaseFileSystem.Target.OpenDirectory(out directory, path, mode);
|
||||
|
||||
protected override Result DoCommit() => BaseFileSystem.Target.Commit();
|
||||
@ -74,10 +70,10 @@ namespace LibHac.FsSystem
|
||||
|
||||
protected override Result DoFlush() => BaseFileSystem.Target.Flush();
|
||||
|
||||
protected override Result DoGetFileTimeStampRaw(out FileTimeStampRaw timeStamp, U8Span path) =>
|
||||
protected override Result DoGetFileTimeStampRaw(out FileTimeStampRaw timeStamp, in Path path) =>
|
||||
BaseFileSystem.Target.GetFileTimeStampRaw(out timeStamp, path);
|
||||
|
||||
protected override Result DoQueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, QueryId queryId,
|
||||
U8Span path) => BaseFileSystem.Target.QueryEntry(outBuffer, inBuffer, queryId, path);
|
||||
in Path path) => BaseFileSystem.Target.QueryEntry(outBuffer, inBuffer, queryId, path);
|
||||
}
|
||||
}
|
||||
|
31
src/LibHac/FsSystem/IUniqueLock.cs
Normal file
31
src/LibHac/FsSystem/IUniqueLock.cs
Normal file
@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using LibHac.Common;
|
||||
using LibHac.Os;
|
||||
|
||||
namespace LibHac.FsSystem
|
||||
{
|
||||
public interface IUniqueLock : IDisposable { }
|
||||
|
||||
public class UniqueLockWithPin<T> : IUniqueLock where T : class, IDisposable
|
||||
{
|
||||
private UniqueLock<SemaphoreAdapter> _semaphore;
|
||||
private 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);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_pinnedObject != null)
|
||||
{
|
||||
_semaphore.Dispose();
|
||||
_pinnedObject.Dispose();
|
||||
|
||||
_pinnedObject = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -37,7 +37,7 @@ namespace LibHac.FsSystem
|
||||
Sources.AddRange(sourceFileSystems);
|
||||
}
|
||||
|
||||
protected override Result DoOpenDirectory(out IDirectory directory, U8Span path, OpenDirectoryMode mode)
|
||||
protected override Result DoOpenDirectory(out IDirectory directory, in Path path, OpenDirectoryMode mode)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out directory);
|
||||
|
||||
@ -48,7 +48,7 @@ namespace LibHac.FsSystem
|
||||
|
||||
foreach (IFileSystem fs in Sources)
|
||||
{
|
||||
Result rc = fs.GetEntryType(out DirectoryEntryType entryType, path);
|
||||
Result rc = fs.GetEntryType(out DirectoryEntryType entryType, in path);
|
||||
|
||||
if (rc.IsSuccess())
|
||||
{
|
||||
@ -82,8 +82,8 @@ namespace LibHac.FsSystem
|
||||
|
||||
if (!(multipleSources is null))
|
||||
{
|
||||
var dir = new MergedDirectory(multipleSources, path, mode);
|
||||
Result rc = dir.Initialize();
|
||||
var dir = new MergedDirectory(multipleSources, mode);
|
||||
Result rc = dir.Initialize(in path);
|
||||
|
||||
if (rc.IsSuccess())
|
||||
{
|
||||
@ -108,7 +108,7 @@ namespace LibHac.FsSystem
|
||||
return ResultFs.PathNotFound.Log();
|
||||
}
|
||||
|
||||
protected override Result DoOpenFile(out IFile file, U8Span path, OpenMode mode)
|
||||
protected override Result DoOpenFile(out IFile file, in Path path, OpenMode mode)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out file);
|
||||
|
||||
@ -137,7 +137,7 @@ namespace LibHac.FsSystem
|
||||
return ResultFs.PathNotFound.Log();
|
||||
}
|
||||
|
||||
protected override Result DoGetEntryType(out DirectoryEntryType entryType, U8Span path)
|
||||
protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out entryType);
|
||||
|
||||
@ -155,7 +155,7 @@ namespace LibHac.FsSystem
|
||||
return ResultFs.PathNotFound.Log();
|
||||
}
|
||||
|
||||
protected override Result DoGetFileTimeStampRaw(out FileTimeStampRaw timeStamp, U8Span path)
|
||||
protected override Result DoGetFileTimeStampRaw(out FileTimeStampRaw timeStamp, in Path path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out timeStamp);
|
||||
|
||||
@ -173,7 +173,7 @@ namespace LibHac.FsSystem
|
||||
}
|
||||
|
||||
protected override Result DoQueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, QueryId queryId,
|
||||
U8Span path)
|
||||
in Path path)
|
||||
{
|
||||
foreach (IFileSystem fs in Sources)
|
||||
{
|
||||
@ -193,39 +193,41 @@ namespace LibHac.FsSystem
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoCreateDirectory(U8Span path) => ResultFs.UnsupportedOperation.Log();
|
||||
protected override Result DoCreateFile(U8Span path, long size, CreateFileOptions options) => ResultFs.UnsupportedOperation.Log();
|
||||
protected override Result DoDeleteDirectory(U8Span path) => ResultFs.UnsupportedOperation.Log();
|
||||
protected override Result DoDeleteDirectoryRecursively(U8Span path) => ResultFs.UnsupportedOperation.Log();
|
||||
protected override Result DoCleanDirectoryRecursively(U8Span path) => ResultFs.UnsupportedOperation.Log();
|
||||
protected override Result DoDeleteFile(U8Span path) => ResultFs.UnsupportedOperation.Log();
|
||||
protected override Result DoRenameDirectory(U8Span oldPath, U8Span newPath) => ResultFs.UnsupportedOperation.Log();
|
||||
protected override Result DoRenameFile(U8Span oldPath, U8Span newPath) => ResultFs.UnsupportedOperation.Log();
|
||||
protected override Result DoCreateDirectory(in Path path) => ResultFs.UnsupportedOperation.Log();
|
||||
protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option) => ResultFs.UnsupportedOperation.Log();
|
||||
protected override Result DoDeleteDirectory(in Path path) => ResultFs.UnsupportedOperation.Log();
|
||||
protected override Result DoDeleteDirectoryRecursively(in Path path) => ResultFs.UnsupportedOperation.Log();
|
||||
protected override Result DoCleanDirectoryRecursively(in Path path) => ResultFs.UnsupportedOperation.Log();
|
||||
protected override Result DoDeleteFile(in Path path) => ResultFs.UnsupportedOperation.Log();
|
||||
protected override Result DoRenameDirectory(in Path currentPath, in Path newPath) => ResultFs.UnsupportedOperation.Log();
|
||||
protected override Result DoRenameFile(in Path currentPath, in Path newPath) => ResultFs.UnsupportedOperation.Log();
|
||||
|
||||
private class MergedDirectory : IDirectory
|
||||
{
|
||||
// Needed to open new directories for GetEntryCount
|
||||
private List<IFileSystem> SourceFileSystems { get; }
|
||||
private List<IDirectory> SourceDirs { get; }
|
||||
private U8String Path { get; }
|
||||
private Path.Stored _path;
|
||||
private OpenDirectoryMode Mode { get; }
|
||||
|
||||
// todo: Efficient way to remove duplicates
|
||||
private HashSet<string> Names { get; } = new HashSet<string>();
|
||||
|
||||
public MergedDirectory(List<IFileSystem> sourceFileSystems, U8Span path, OpenDirectoryMode mode)
|
||||
public MergedDirectory(List<IFileSystem> sourceFileSystems, OpenDirectoryMode mode)
|
||||
{
|
||||
SourceFileSystems = sourceFileSystems;
|
||||
SourceDirs = new List<IDirectory>(sourceFileSystems.Count);
|
||||
Path = path.ToU8String();
|
||||
Mode = mode;
|
||||
}
|
||||
|
||||
public Result Initialize()
|
||||
public Result Initialize(in Path path)
|
||||
{
|
||||
Result rc = _path.Initialize(in path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
foreach (IFileSystem fs in SourceFileSystems)
|
||||
{
|
||||
Result rc = fs.OpenDirectory(out IDirectory dir, Path, Mode);
|
||||
rc = fs.OpenDirectory(out IDirectory dir, in path, Mode);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
SourceDirs.Add(dir);
|
||||
@ -268,10 +270,12 @@ namespace LibHac.FsSystem
|
||||
// todo: Efficient way to remove duplicates
|
||||
var names = new HashSet<string>();
|
||||
|
||||
Path path = _path.GetPath();
|
||||
|
||||
// Open new directories for each source because we need to remove duplicate entries
|
||||
foreach (IFileSystem fs in SourceFileSystems)
|
||||
{
|
||||
Result rc = fs.OpenDirectory(out IDirectory dir, Path, Mode);
|
||||
Result rc = fs.OpenDirectory(out IDirectory dir, in path, Mode);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
long entriesRead;
|
||||
|
@ -92,14 +92,12 @@ namespace LibHac.FsSystem
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
public override void Dispose()
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
File?.Dispose();
|
||||
}
|
||||
|
||||
File?.Dispose();
|
||||
Stream?.Dispose();
|
||||
|
||||
base.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,6 @@
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.Unicode;
|
||||
using System.Threading;
|
||||
using LibHac.Common;
|
||||
@ -11,6 +10,7 @@ using LibHac.Fs.Fsa;
|
||||
using LibHac.FsSystem.Impl;
|
||||
using LibHac.Util;
|
||||
using static LibHac.Fs.StringTraits;
|
||||
using Path = LibHac.Fs.Path;
|
||||
|
||||
namespace LibHac.FsSystem
|
||||
{
|
||||
@ -32,7 +32,8 @@ namespace LibHac.FsSystem
|
||||
CaseSensitive
|
||||
}
|
||||
|
||||
private string _rootPath;
|
||||
private Path.Stored _rootPath;
|
||||
private string _rootPathUtf16;
|
||||
private readonly FileSystemClient _fsClient;
|
||||
private PathMode _mode;
|
||||
private readonly bool _useUnixTime;
|
||||
@ -56,17 +57,9 @@ namespace LibHac.FsSystem
|
||||
/// <param name="rootPath">The path that will be the root of the <see cref="LocalFileSystem"/>.</param>
|
||||
public LocalFileSystem(string rootPath)
|
||||
{
|
||||
_rootPath = System.IO.Path.GetFullPath(rootPath);
|
||||
|
||||
if (!Directory.Exists(_rootPath))
|
||||
{
|
||||
if (File.Exists(_rootPath))
|
||||
{
|
||||
throw new DirectoryNotFoundException($"The specified path is a file. ({rootPath})");
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(_rootPath);
|
||||
}
|
||||
Result rc = Initialize(rootPath, PathMode.DefaultCaseSensitivity, true);
|
||||
if (rc.IsFailure())
|
||||
throw new HorizonResultException(rc, "Error creating LocalFileSystem.");
|
||||
}
|
||||
|
||||
public static Result Create(out LocalFileSystem fileSystem, string rootPath,
|
||||
@ -84,6 +77,8 @@ namespace LibHac.FsSystem
|
||||
|
||||
public Result Initialize(string rootPath, PathMode pathMode, bool ensurePathExists)
|
||||
{
|
||||
Result rc;
|
||||
|
||||
if (rootPath == null)
|
||||
return ResultFs.NullptrArgument.Log();
|
||||
|
||||
@ -92,13 +87,21 @@ namespace LibHac.FsSystem
|
||||
// If the root path is empty, we interpret any incoming paths as rooted paths.
|
||||
if (rootPath == string.Empty)
|
||||
{
|
||||
_rootPath = rootPath;
|
||||
var path = new Path();
|
||||
rc = path.InitializeAsEmpty();
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = _rootPath.Initialize(in path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
string rootPathNormalized;
|
||||
|
||||
try
|
||||
{
|
||||
_rootPath = System.IO.Path.GetFullPath(rootPath);
|
||||
rootPathNormalized = System.IO.Path.GetFullPath(rootPath);
|
||||
}
|
||||
catch (PathTooLongException)
|
||||
{
|
||||
@ -109,14 +112,14 @@ namespace LibHac.FsSystem
|
||||
return ResultFs.InvalidCharacter.Log();
|
||||
}
|
||||
|
||||
if (!Directory.Exists(_rootPath))
|
||||
if (!Directory.Exists(rootPathNormalized))
|
||||
{
|
||||
if (!ensurePathExists || File.Exists(_rootPath))
|
||||
if (!ensurePathExists || File.Exists(rootPathNormalized))
|
||||
return ResultFs.PathNotFound.Log();
|
||||
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(_rootPath);
|
||||
Directory.CreateDirectory(rootPathNormalized);
|
||||
}
|
||||
catch (Exception ex) when (ex.HResult < 0)
|
||||
{
|
||||
@ -124,53 +127,78 @@ namespace LibHac.FsSystem
|
||||
}
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
ReadOnlySpan<byte> utf8Path = StringUtils.StringToUtf8(rootPathNormalized);
|
||||
var pathNormalized = new Path();
|
||||
|
||||
private Result ResolveFullPath(out string fullPath, U8Span path, bool checkCaseSensitivity)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out fullPath);
|
||||
|
||||
Unsafe.SkipInit(out FsPath normalizedPath);
|
||||
|
||||
Result rc = PathNormalizer.Normalize(normalizedPath.Str, out _, path, false, false);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
fullPath = PathTools.Combine(_rootPath, normalizedPath.ToString());
|
||||
|
||||
if (_mode == PathMode.CaseSensitive && checkCaseSensitivity)
|
||||
if (utf8Path.At(0) == DirectorySeparator && utf8Path.At(1) != DirectorySeparator)
|
||||
{
|
||||
rc = CheckPathCaseSensitively(fullPath);
|
||||
rc = pathNormalized.Initialize(utf8Path.Slice(1));
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = pathNormalized.InitializeWithReplaceUnc(utf8Path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
|
||||
var flags = new PathFlags();
|
||||
flags.AllowWindowsPath();
|
||||
flags.AllowRelativePath();
|
||||
flags.AllowEmptyPath();
|
||||
|
||||
rc = pathNormalized.Normalize(flags);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = _rootPath.Initialize(in pathNormalized);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
_rootPathUtf16 = _rootPath.ToString();
|
||||
|
||||
pathNormalized.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
private Result CheckSubPath(U8Span path1, U8Span path2)
|
||||
private Result ResolveFullPath(out string outFullPath, in Path path, bool checkCaseSensitivity)
|
||||
{
|
||||
Unsafe.SkipInit(out FsPath normalizedPath1);
|
||||
Unsafe.SkipInit(out FsPath normalizedPath2);
|
||||
UnsafeHelpers.SkipParamInit(out outFullPath);
|
||||
|
||||
Result rc = PathNormalizer.Normalize(normalizedPath1.Str, out _, path1, false, false);
|
||||
// Always normalize the incoming path even if it claims to already be normalized
|
||||
// because we don't want to allow access to anything outside the root path.
|
||||
|
||||
var pathNormalized = new Path();
|
||||
Result rc = pathNormalized.Initialize(path.GetString());
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = PathNormalizer.Normalize(normalizedPath2.Str, out _, path2, false, false);
|
||||
var pathFlags = new PathFlags();
|
||||
pathFlags.AllowWindowsPath();
|
||||
pathFlags.AllowRelativePath();
|
||||
pathFlags.AllowEmptyPath();
|
||||
rc = pathNormalized.Normalize(pathFlags);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (PathUtility.IsSubPath(normalizedPath1, normalizedPath2))
|
||||
Path rootPath = _rootPath.GetPath();
|
||||
|
||||
var fullPath = new Path();
|
||||
rc = fullPath.Combine(in rootPath, in pathNormalized);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
string utf16FullPath = fullPath.ToString();
|
||||
|
||||
if (_mode == PathMode.CaseSensitive && checkCaseSensitivity)
|
||||
{
|
||||
return ResultFs.DirectoryNotRenamable.Log();
|
||||
rc = CheckPathCaseSensitively(utf16FullPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
|
||||
outFullPath = utf16FullPath;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoGetFileAttributes(out NxFileAttributes attributes, U8Span path)
|
||||
protected override Result DoGetFileAttributes(out NxFileAttributes attributes, in Path path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out attributes);
|
||||
|
||||
Result rc = ResolveFullPath(out string fullPath, path, true);
|
||||
Result rc = ResolveFullPath(out string fullPath, in path, true);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = GetFileInfo(out FileInfo info, fullPath);
|
||||
@ -185,9 +213,9 @@ namespace LibHac.FsSystem
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoSetFileAttributes(U8Span path, NxFileAttributes attributes)
|
||||
protected override Result DoSetFileAttributes(in Path path, NxFileAttributes attributes)
|
||||
{
|
||||
Result rc = ResolveFullPath(out string fullPath, path, true);
|
||||
Result rc = ResolveFullPath(out string fullPath, in path, true);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = GetFileInfo(out FileInfo info, fullPath);
|
||||
@ -213,11 +241,11 @@ namespace LibHac.FsSystem
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoGetFileSize(out long fileSize, U8Span path)
|
||||
protected override Result DoGetFileSize(out long fileSize, in Path path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out fileSize);
|
||||
|
||||
Result rc = ResolveFullPath(out string fullPath, path, true);
|
||||
Result rc = ResolveFullPath(out string fullPath, in path, true);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = GetFileInfo(out FileInfo info, fullPath);
|
||||
@ -226,14 +254,14 @@ namespace LibHac.FsSystem
|
||||
return GetSizeInternal(out fileSize, info);
|
||||
}
|
||||
|
||||
protected override Result DoCreateDirectory(U8Span path)
|
||||
protected override Result DoCreateDirectory(in Path path)
|
||||
{
|
||||
return DoCreateDirectory(path, NxFileAttributes.None);
|
||||
}
|
||||
|
||||
protected override Result DoCreateDirectory(U8Span path, NxFileAttributes archiveAttribute)
|
||||
protected override Result DoCreateDirectory(in Path path, NxFileAttributes archiveAttribute)
|
||||
{
|
||||
Result rc = ResolveFullPath(out string fullPath, path, false);
|
||||
Result rc = ResolveFullPath(out string fullPath, in path, false);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = GetDirInfo(out DirectoryInfo dir, fullPath);
|
||||
@ -252,9 +280,9 @@ namespace LibHac.FsSystem
|
||||
return CreateDirInternal(dir, archiveAttribute);
|
||||
}
|
||||
|
||||
protected override Result DoCreateFile(U8Span path, long size, CreateFileOptions options)
|
||||
protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option)
|
||||
{
|
||||
Result rc = ResolveFullPath(out string fullPath, path, false);
|
||||
Result rc = ResolveFullPath(out string fullPath, in path, false);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = GetFileInfo(out FileInfo file, fullPath);
|
||||
@ -280,9 +308,9 @@ namespace LibHac.FsSystem
|
||||
}
|
||||
}
|
||||
|
||||
protected override Result DoDeleteDirectory(U8Span path)
|
||||
protected override Result DoDeleteDirectory(in Path path)
|
||||
{
|
||||
Result rc = ResolveFullPath(out string fullPath, path, true);
|
||||
Result rc = ResolveFullPath(out string fullPath, in path, true);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = GetDirInfo(out DirectoryInfo dir, fullPath);
|
||||
@ -292,9 +320,9 @@ namespace LibHac.FsSystem
|
||||
() => DeleteDirectoryInternal(dir, false), _fsClient);
|
||||
}
|
||||
|
||||
protected override Result DoDeleteDirectoryRecursively(U8Span path)
|
||||
protected override Result DoDeleteDirectoryRecursively(in Path path)
|
||||
{
|
||||
Result rc = ResolveFullPath(out string fullPath, path, true);
|
||||
Result rc = ResolveFullPath(out string fullPath, in path, true);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = GetDirInfo(out DirectoryInfo dir, fullPath);
|
||||
@ -304,9 +332,9 @@ namespace LibHac.FsSystem
|
||||
() => DeleteDirectoryInternal(dir, true), _fsClient);
|
||||
}
|
||||
|
||||
protected override Result DoCleanDirectoryRecursively(U8Span path)
|
||||
protected override Result DoCleanDirectoryRecursively(in Path path)
|
||||
{
|
||||
Result rc = ResolveFullPath(out string fullPath, path, true);
|
||||
Result rc = ResolveFullPath(out string fullPath, in path, true);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
foreach (string file in Directory.EnumerateFiles(fullPath))
|
||||
@ -340,9 +368,9 @@ namespace LibHac.FsSystem
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoDeleteFile(U8Span path)
|
||||
protected override Result DoDeleteFile(in Path path)
|
||||
{
|
||||
Result rc = ResolveFullPath(out string fullPath, path, true);
|
||||
Result rc = ResolveFullPath(out string fullPath, in path, true);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = GetFileInfo(out FileInfo file, fullPath);
|
||||
@ -352,10 +380,10 @@ namespace LibHac.FsSystem
|
||||
() => DeleteFileInternal(file), _fsClient);
|
||||
}
|
||||
|
||||
protected override Result DoOpenDirectory(out IDirectory directory, U8Span path, OpenDirectoryMode mode)
|
||||
protected override Result DoOpenDirectory(out IDirectory directory, in Path path, OpenDirectoryMode mode)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out directory);
|
||||
Result rc = ResolveFullPath(out string fullPath, path, true);
|
||||
Result rc = ResolveFullPath(out string fullPath, in path, true);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = GetDirInfo(out DirectoryInfo dirInfo, fullPath);
|
||||
@ -375,11 +403,11 @@ namespace LibHac.FsSystem
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoOpenFile(out IFile file, U8Span path, OpenMode mode)
|
||||
protected override Result DoOpenFile(out IFile file, in Path path, OpenMode mode)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out file);
|
||||
|
||||
Result rc = ResolveFullPath(out string fullPath, path, true);
|
||||
Result rc = ResolveFullPath(out string fullPath, in path, true);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = GetEntryType(out DirectoryEntryType entryType, path);
|
||||
@ -400,15 +428,12 @@ namespace LibHac.FsSystem
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoRenameDirectory(U8Span oldPath, U8Span newPath)
|
||||
protected override Result DoRenameDirectory(in Path currentPath, in Path newPath)
|
||||
{
|
||||
Result rc = CheckSubPath(oldPath, newPath);
|
||||
Result rc = ResolveFullPath(out string fullCurrentPath, in currentPath, true);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = ResolveFullPath(out string fullCurrentPath, oldPath, true);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = ResolveFullPath(out string fullNewPath, newPath, false);
|
||||
rc = ResolveFullPath(out string fullNewPath, in newPath, false);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
// Official FS behavior is to do nothing in this case
|
||||
@ -424,12 +449,12 @@ namespace LibHac.FsSystem
|
||||
() => RenameDirInternal(currentDirInfo, newDirInfo), _fsClient);
|
||||
}
|
||||
|
||||
protected override Result DoRenameFile(U8Span oldPath, U8Span newPath)
|
||||
protected override Result DoRenameFile(in Path currentPath, in Path newPath)
|
||||
{
|
||||
Result rc = ResolveFullPath(out string fullCurrentPath, oldPath, true);
|
||||
Result rc = ResolveFullPath(out string fullCurrentPath, in currentPath, true);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = ResolveFullPath(out string fullNewPath, newPath, false);
|
||||
rc = ResolveFullPath(out string fullNewPath, in newPath, false);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
// Official FS behavior is to do nothing in this case
|
||||
@ -445,11 +470,11 @@ namespace LibHac.FsSystem
|
||||
() => RenameFileInternal(currentFileInfo, newFileInfo), _fsClient);
|
||||
}
|
||||
|
||||
protected override Result DoGetEntryType(out DirectoryEntryType entryType, U8Span path)
|
||||
protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out entryType);
|
||||
|
||||
Result rc = ResolveFullPath(out string fullPath, path, true);
|
||||
Result rc = ResolveFullPath(out string fullPath, in path, true);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = GetDirInfo(out DirectoryInfo dir, fullPath);
|
||||
@ -473,11 +498,11 @@ namespace LibHac.FsSystem
|
||||
return ResultFs.PathNotFound.Log();
|
||||
}
|
||||
|
||||
protected override Result DoGetFileTimeStampRaw(out FileTimeStampRaw timeStamp, U8Span path)
|
||||
protected override Result DoGetFileTimeStampRaw(out FileTimeStampRaw timeStamp, in Path path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out timeStamp);
|
||||
|
||||
Result rc = ResolveFullPath(out string fullPath, path, true);
|
||||
Result rc = ResolveFullPath(out string fullPath, in path, true);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = GetFileInfo(out FileInfo file, fullPath);
|
||||
@ -503,22 +528,22 @@ namespace LibHac.FsSystem
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoGetFreeSpaceSize(out long freeSpace, U8Span path)
|
||||
protected override Result DoGetFreeSpaceSize(out long freeSpace, in Path path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out freeSpace);
|
||||
|
||||
Result rc = ResolveFullPath(out string fullPath, path, true);
|
||||
Result rc = ResolveFullPath(out string fullPath, in path, true);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
freeSpace = new DriveInfo(fullPath).AvailableFreeSpace;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoGetTotalSpaceSize(out long totalSpace, U8Span path)
|
||||
protected override Result DoGetTotalSpaceSize(out long totalSpace, in Path path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out totalSpace);
|
||||
|
||||
Result rc = ResolveFullPath(out string fullPath, path, true);
|
||||
Result rc = ResolveFullPath(out string fullPath, in path, true);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
totalSpace = new DriveInfo(fullPath).TotalSize;
|
||||
@ -531,7 +556,7 @@ namespace LibHac.FsSystem
|
||||
}
|
||||
|
||||
protected override Result DoQueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, QueryId queryId,
|
||||
U8Span path)
|
||||
in Path path)
|
||||
{
|
||||
return ResultFs.UnsupportedOperation.Log();
|
||||
}
|
||||
@ -801,7 +826,7 @@ namespace LibHac.FsSystem
|
||||
|
||||
private Result CheckPathCaseSensitively(string path)
|
||||
{
|
||||
Result rc = GetCaseSensitivePathFull(out string caseSensitivePath, out _, path, _rootPath);
|
||||
Result rc = GetCaseSensitivePathFull(out string caseSensitivePath, out _, path, _rootPathUtf16);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (path.Length != caseSensitivePath.Length)
|
||||
@ -809,8 +834,8 @@ namespace LibHac.FsSystem
|
||||
|
||||
for (int i = 0; i < path.Length; i++)
|
||||
{
|
||||
if (!(path[i] == caseSensitivePath[i] || WindowsPath.IsDosDelimiter(path[i]) &&
|
||||
WindowsPath.IsDosDelimiter(caseSensitivePath[i])))
|
||||
if (!(path[i] == caseSensitivePath[i] || WindowsPath.IsDosDelimiterW(path[i]) &&
|
||||
WindowsPath.IsDosDelimiterW(caseSensitivePath[i])))
|
||||
{
|
||||
return ResultFs.PathNotFound.Log();
|
||||
}
|
||||
@ -828,7 +853,7 @@ namespace LibHac.FsSystem
|
||||
string fullPath;
|
||||
int workingDirectoryPathLength;
|
||||
|
||||
if (WindowsPath.IsPathRooted(path))
|
||||
if (WindowsPath.IsWindowsPathW(path))
|
||||
{
|
||||
fullPath = path;
|
||||
workingDirectoryPathLength = 0;
|
||||
@ -837,7 +862,7 @@ namespace LibHac.FsSystem
|
||||
{
|
||||
// We only want to send back the relative part of the path starting with a '/', so
|
||||
// track where the root path ends.
|
||||
if (WindowsPath.IsDosDelimiter(workingDirectoryPath[^1]))
|
||||
if (WindowsPath.IsDosDelimiterW(workingDirectoryPath[^1]))
|
||||
{
|
||||
workingDirectoryPathLength = workingDirectoryPath.Length - 1;
|
||||
}
|
||||
@ -863,8 +888,8 @@ namespace LibHac.FsSystem
|
||||
if (string.IsNullOrEmpty(path1)) return path2;
|
||||
if (string.IsNullOrEmpty(path2)) return path1;
|
||||
|
||||
bool path1HasSeparator = WindowsPath.IsDosDelimiter(path1[path1.Length - 1]);
|
||||
bool path2HasSeparator = WindowsPath.IsDosDelimiter(path2[0]);
|
||||
bool path1HasSeparator = WindowsPath.IsDosDelimiterW(path1[path1.Length - 1]);
|
||||
bool path2HasSeparator = WindowsPath.IsDosDelimiterW(path2[0]);
|
||||
|
||||
if (!path1HasSeparator && !path2HasSeparator)
|
||||
{
|
||||
@ -889,7 +914,7 @@ namespace LibHac.FsSystem
|
||||
|
||||
string exactPath = string.Empty;
|
||||
int itemsToSkip = 0;
|
||||
if (WindowsPath.IsUnc(path))
|
||||
if (WindowsPath.IsUncPathW(path))
|
||||
{
|
||||
// With the Split method, a UNC path like \\server\share, we need to skip
|
||||
// trying to enumerate the server and share, so skip the first two empty
|
||||
|
@ -7,6 +7,7 @@ using LibHac.Common;
|
||||
using LibHac.Crypto;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
using Path = LibHac.Fs.Path;
|
||||
|
||||
namespace LibHac.FsSystem
|
||||
{
|
||||
@ -33,17 +34,17 @@ namespace LibHac.FsSystem
|
||||
BaseStorage = storage;
|
||||
}
|
||||
|
||||
protected override Result DoOpenDirectory(out IDirectory directory, U8Span path, OpenDirectoryMode mode)
|
||||
protected override Result DoOpenDirectory(out IDirectory directory, in Path path, OpenDirectoryMode mode)
|
||||
{
|
||||
directory = new PartitionDirectory(this, path.ToString(), mode);
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoOpenFile(out IFile file, U8Span path, OpenMode mode)
|
||||
protected override Result DoOpenFile(out IFile file, in Path path, OpenMode mode)
|
||||
{
|
||||
path = PathTools.Normalize(path.ToString()).TrimStart('/').ToU8Span();
|
||||
string pathNormalized = PathTools.Normalize(path.ToString()).TrimStart('/');
|
||||
|
||||
if (!FileDict.TryGetValue(path.ToString(), out PartitionFileEntry entry))
|
||||
if (!FileDict.TryGetValue(pathNormalized, out PartitionFileEntry entry))
|
||||
{
|
||||
ThrowHelper.ThrowResult(ResultFs.PathNotFound.Value);
|
||||
}
|
||||
@ -57,7 +58,7 @@ namespace LibHac.FsSystem
|
||||
return new PartitionFile(BaseStorage, HeaderSize + entry.Offset, entry.Size, mode);
|
||||
}
|
||||
|
||||
protected override Result DoGetEntryType(out DirectoryEntryType entryType, U8Span path)
|
||||
protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out entryType);
|
||||
|
||||
@ -76,14 +77,14 @@ namespace LibHac.FsSystem
|
||||
return ResultFs.PathNotFound.Log();
|
||||
}
|
||||
|
||||
protected override Result DoCreateDirectory(U8Span path) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log();
|
||||
protected override Result DoCreateFile(U8Span path, long size, CreateFileOptions options) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log();
|
||||
protected override Result DoDeleteDirectory(U8Span path) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log();
|
||||
protected override Result DoDeleteDirectoryRecursively(U8Span path) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log();
|
||||
protected override Result DoCleanDirectoryRecursively(U8Span path) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log();
|
||||
protected override Result DoDeleteFile(U8Span path) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log();
|
||||
protected override Result DoRenameDirectory(U8Span oldPath, U8Span newPath) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log();
|
||||
protected override Result DoRenameFile(U8Span oldPath, U8Span newPath) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log();
|
||||
protected override Result DoCreateDirectory(in Path path) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log();
|
||||
protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log();
|
||||
protected override Result DoDeleteDirectory(in Path path) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log();
|
||||
protected override Result DoDeleteDirectoryRecursively(in Path path) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log();
|
||||
protected override Result DoCleanDirectoryRecursively(in Path path) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log();
|
||||
protected override Result DoDeleteFile(in Path path) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log();
|
||||
protected override Result DoRenameDirectory(in Path currentPath, in Path newPath) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log();
|
||||
protected override Result DoRenameFile(in Path currentPath, in Path newPath) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log();
|
||||
|
||||
protected override Result DoCommit()
|
||||
{
|
||||
|
@ -43,17 +43,13 @@ namespace LibHac.FsSystem
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
public override void Dispose()
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
BaseStorageShared?.Dispose();
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
BaseStorageShared?.Dispose();
|
||||
base.Dispose();
|
||||
}
|
||||
|
||||
protected override Result DoOpenDirectory(out IDirectory directory, U8Span path, OpenDirectoryMode mode)
|
||||
protected override Result DoOpenDirectory(out IDirectory directory, in Path path, OpenDirectoryMode mode)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out directory);
|
||||
|
||||
@ -62,7 +58,7 @@ namespace LibHac.FsSystem
|
||||
|
||||
ReadOnlySpan<byte> rootPath = new[] { (byte)'/' };
|
||||
|
||||
if (StringUtils.Compare(rootPath, path, 2) != 0)
|
||||
if (path == rootPath)
|
||||
return ResultFs.PathNotFound.Log();
|
||||
|
||||
directory = new PartitionDirectory(this, mode);
|
||||
@ -70,7 +66,7 @@ namespace LibHac.FsSystem
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoOpenFile(out IFile file, U8Span path, OpenMode mode)
|
||||
protected override Result DoOpenFile(out IFile file, in Path path, OpenMode mode)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out file);
|
||||
|
||||
@ -80,7 +76,7 @@ namespace LibHac.FsSystem
|
||||
if (!mode.HasFlag(OpenMode.Read) && !mode.HasFlag(OpenMode.Write))
|
||||
return ResultFs.InvalidArgument.Log();
|
||||
|
||||
int entryIndex = MetaData.FindEntry(path.Slice(1));
|
||||
int entryIndex = MetaData.FindEntry(new U8Span(path.GetString().Slice(1)));
|
||||
if (entryIndex < 0) return ResultFs.PathNotFound.Log();
|
||||
|
||||
ref T entry = ref MetaData.GetEntry(entryIndex);
|
||||
@ -90,25 +86,27 @@ namespace LibHac.FsSystem
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoGetEntryType(out DirectoryEntryType entryType, U8Span path)
|
||||
protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out entryType);
|
||||
|
||||
if (!IsInitialized)
|
||||
return ResultFs.PreconditionViolation.Log();
|
||||
|
||||
if (path.IsEmpty() || path[0] != '/')
|
||||
ReadOnlySpan<byte> pathStr = path.GetString();
|
||||
|
||||
if (path.IsEmpty() || pathStr[0] != '/')
|
||||
return ResultFs.InvalidPathFormat.Log();
|
||||
|
||||
ReadOnlySpan<byte> rootPath = new[] { (byte)'/' };
|
||||
|
||||
if (StringUtils.Compare(rootPath, path, 2) == 0)
|
||||
if (StringUtils.Compare(rootPath, pathStr, 2) == 0)
|
||||
{
|
||||
entryType = DirectoryEntryType.Directory;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
if (MetaData.FindEntry(path.Slice(1)) >= 0)
|
||||
if (MetaData.FindEntry(new U8Span(pathStr.Slice(1))) >= 0)
|
||||
{
|
||||
entryType = DirectoryEntryType.File;
|
||||
return Result.Success;
|
||||
@ -122,14 +120,14 @@ namespace LibHac.FsSystem
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoCreateDirectory(U8Span path) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log();
|
||||
protected override Result DoCreateFile(U8Span path, long size, CreateFileOptions options) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log();
|
||||
protected override Result DoDeleteDirectory(U8Span path) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log();
|
||||
protected override Result DoDeleteDirectoryRecursively(U8Span path) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log();
|
||||
protected override Result DoCleanDirectoryRecursively(U8Span path) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log();
|
||||
protected override Result DoDeleteFile(U8Span path) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log();
|
||||
protected override Result DoRenameDirectory(U8Span oldPath, U8Span newPath) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log();
|
||||
protected override Result DoRenameFile(U8Span oldPath, U8Span newPath) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log();
|
||||
protected override Result DoCreateDirectory(in Path path) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log();
|
||||
protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log();
|
||||
protected override Result DoDeleteDirectory(in Path path) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log();
|
||||
protected override Result DoDeleteDirectoryRecursively(in Path path) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log();
|
||||
protected override Result DoCleanDirectoryRecursively(in Path path) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log();
|
||||
protected override Result DoDeleteFile(in Path path) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log();
|
||||
protected override Result DoRenameDirectory(in Path currentPath, in Path newPath) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log();
|
||||
protected override Result DoRenameFile(in Path currentPath, in Path newPath) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log();
|
||||
protected override Result DoCommitProvisionally(long counter) => ResultFs.UnsupportedCommitProvisionallyForPartitionFileSystem.Log();
|
||||
|
||||
private class PartitionFile : IFile
|
||||
|
@ -3,6 +3,7 @@ using System.Diagnostics;
|
||||
using System.IO.Enumeration;
|
||||
using System.Runtime.CompilerServices;
|
||||
using LibHac.Common;
|
||||
using LibHac.Diag;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Util;
|
||||
|
||||
@ -235,7 +236,7 @@ namespace LibHac.FsSystem
|
||||
|
||||
public static ReadOnlySpan<byte> GetParentDirectory(ReadOnlySpan<byte> path)
|
||||
{
|
||||
Debug.Assert(IsNormalized(path));
|
||||
Assert.SdkAssert(IsNormalized(path));
|
||||
|
||||
int i = StringUtils.GetLength(path) - 1;
|
||||
|
||||
@ -288,6 +289,9 @@ namespace LibHac.FsSystem
|
||||
|
||||
foreach (char c in path)
|
||||
{
|
||||
if (c == 0)
|
||||
break;
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case NormalizeState.Initial when c == '/': state = NormalizeState.Delimiter; break;
|
||||
@ -325,6 +329,9 @@ namespace LibHac.FsSystem
|
||||
|
||||
foreach (byte c in path)
|
||||
{
|
||||
if (c == 0)
|
||||
break;
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case NormalizeState.Initial when c == '/': state = NormalizeState.Delimiter; break;
|
||||
|
@ -28,12 +28,12 @@ namespace LibHac.FsSystem
|
||||
return new ReferenceCountedDisposable<IFileSystem>(fs);
|
||||
}
|
||||
|
||||
protected override Result DoOpenDirectory(out IDirectory directory, U8Span path, OpenDirectoryMode mode)
|
||||
protected override Result DoOpenDirectory(out IDirectory directory, in Path path, OpenDirectoryMode mode)
|
||||
{
|
||||
return BaseFs.OpenDirectory(out directory, path, mode);
|
||||
}
|
||||
|
||||
protected override Result DoOpenFile(out IFile file, U8Span path, OpenMode mode)
|
||||
protected override Result DoOpenFile(out IFile file, in Path path, OpenMode mode)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out file);
|
||||
|
||||
@ -44,17 +44,17 @@ namespace LibHac.FsSystem
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoGetEntryType(out DirectoryEntryType entryType, U8Span path)
|
||||
protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path)
|
||||
{
|
||||
return BaseFs.GetEntryType(out entryType, path);
|
||||
}
|
||||
|
||||
protected override Result DoGetFreeSpaceSize(out long freeSpace, U8Span path)
|
||||
protected override Result DoGetFreeSpaceSize(out long freeSpace, in Path path)
|
||||
{
|
||||
return BaseFs.GetFreeSpaceSize(out freeSpace, path);
|
||||
}
|
||||
|
||||
protected override Result DoGetTotalSpaceSize(out long totalSpace, U8Span path)
|
||||
protected override Result DoGetTotalSpaceSize(out long totalSpace, in Path path)
|
||||
{
|
||||
return BaseFs.GetTotalSpaceSize(out totalSpace, path);
|
||||
|
||||
@ -62,7 +62,7 @@ namespace LibHac.FsSystem
|
||||
// return ResultFs.UnsupportedOperationReadOnlyFileSystemGetSpace.Log();
|
||||
}
|
||||
|
||||
protected override Result DoGetFileTimeStampRaw(out FileTimeStampRaw timeStamp, U8Span path)
|
||||
protected override Result DoGetFileTimeStampRaw(out FileTimeStampRaw timeStamp, in Path path)
|
||||
{
|
||||
return BaseFs.GetFileTimeStampRaw(out timeStamp, path);
|
||||
|
||||
@ -75,30 +75,26 @@ namespace LibHac.FsSystem
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoCreateDirectory(U8Span path) => ResultFs.UnsupportedWriteForReadOnlyFileSystem.Log();
|
||||
protected override Result DoCreateDirectory(in Path path) => ResultFs.UnsupportedWriteForReadOnlyFileSystem.Log();
|
||||
|
||||
protected override Result DoCreateFile(U8Span path, long size, CreateFileOptions options) => ResultFs.UnsupportedWriteForReadOnlyFileSystem.Log();
|
||||
protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option) => ResultFs.UnsupportedWriteForReadOnlyFileSystem.Log();
|
||||
|
||||
protected override Result DoDeleteDirectory(U8Span path) => ResultFs.UnsupportedWriteForReadOnlyFileSystem.Log();
|
||||
protected override Result DoDeleteDirectory(in Path path) => ResultFs.UnsupportedWriteForReadOnlyFileSystem.Log();
|
||||
|
||||
protected override Result DoDeleteDirectoryRecursively(U8Span path) => ResultFs.UnsupportedWriteForReadOnlyFileSystem.Log();
|
||||
protected override Result DoDeleteDirectoryRecursively(in Path path) => ResultFs.UnsupportedWriteForReadOnlyFileSystem.Log();
|
||||
|
||||
protected override Result DoCleanDirectoryRecursively(U8Span path) => ResultFs.UnsupportedWriteForReadOnlyFileSystem.Log();
|
||||
protected override Result DoCleanDirectoryRecursively(in Path path) => ResultFs.UnsupportedWriteForReadOnlyFileSystem.Log();
|
||||
|
||||
protected override Result DoDeleteFile(U8Span path) => ResultFs.UnsupportedWriteForReadOnlyFileSystem.Log();
|
||||
protected override Result DoDeleteFile(in Path path) => ResultFs.UnsupportedWriteForReadOnlyFileSystem.Log();
|
||||
|
||||
protected override Result DoRenameDirectory(U8Span oldPath, U8Span newPath) => ResultFs.UnsupportedWriteForReadOnlyFileSystem.Log();
|
||||
protected override Result DoRenameDirectory(in Path currentPath, in Path newPath) => ResultFs.UnsupportedWriteForReadOnlyFileSystem.Log();
|
||||
|
||||
protected override Result DoRenameFile(U8Span oldPath, U8Span newPath) => ResultFs.UnsupportedWriteForReadOnlyFileSystem.Log();
|
||||
protected override Result DoRenameFile(in Path currentPath, in Path newPath) => ResultFs.UnsupportedWriteForReadOnlyFileSystem.Log();
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
public override void Dispose()
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
BaseFsShared?.Dispose();
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
BaseFsShared?.Dispose();
|
||||
base.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ namespace LibHac.FsSystem.RomFs
|
||||
FileTable = new HierarchicalRomFileTable<RomFileInfo>(dirHashTable, dirEntryTable, fileHashTable, fileEntryTable);
|
||||
}
|
||||
|
||||
protected override Result DoGetEntryType(out DirectoryEntryType entryType, U8Span path)
|
||||
protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out entryType);
|
||||
|
||||
@ -49,7 +49,7 @@ namespace LibHac.FsSystem.RomFs
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoOpenDirectory(out IDirectory directory, U8Span path, OpenDirectoryMode mode)
|
||||
protected override Result DoOpenDirectory(out IDirectory directory, in Path path, OpenDirectoryMode mode)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out directory);
|
||||
|
||||
@ -62,7 +62,7 @@ namespace LibHac.FsSystem.RomFs
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoOpenFile(out IFile file, U8Span path, OpenMode mode)
|
||||
protected override Result DoOpenFile(out IFile file, in Path path, OpenMode mode)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out file);
|
||||
|
||||
@ -86,23 +86,23 @@ namespace LibHac.FsSystem.RomFs
|
||||
return BaseStorage;
|
||||
}
|
||||
|
||||
protected override Result DoCreateDirectory(U8Span path) => ResultFs.UnsupportedWriteForRomFsFileSystem.Log();
|
||||
protected override Result DoCreateFile(U8Span path, long size, CreateFileOptions options) => ResultFs.UnsupportedWriteForRomFsFileSystem.Log();
|
||||
protected override Result DoDeleteDirectory(U8Span path) => ResultFs.UnsupportedWriteForRomFsFileSystem.Log();
|
||||
protected override Result DoDeleteDirectoryRecursively(U8Span path) => ResultFs.UnsupportedWriteForRomFsFileSystem.Log();
|
||||
protected override Result DoCleanDirectoryRecursively(U8Span path) => ResultFs.UnsupportedWriteForRomFsFileSystem.Log();
|
||||
protected override Result DoDeleteFile(U8Span path) => ResultFs.UnsupportedWriteForRomFsFileSystem.Log();
|
||||
protected override Result DoRenameDirectory(U8Span oldPath, U8Span newPath) => ResultFs.UnsupportedWriteForRomFsFileSystem.Log();
|
||||
protected override Result DoRenameFile(U8Span oldPath, U8Span newPath) => ResultFs.UnsupportedWriteForRomFsFileSystem.Log();
|
||||
protected override Result DoCreateDirectory(in Path path) => ResultFs.UnsupportedWriteForRomFsFileSystem.Log();
|
||||
protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option) => ResultFs.UnsupportedWriteForRomFsFileSystem.Log();
|
||||
protected override Result DoDeleteDirectory(in Path path) => ResultFs.UnsupportedWriteForRomFsFileSystem.Log();
|
||||
protected override Result DoDeleteDirectoryRecursively(in Path path) => ResultFs.UnsupportedWriteForRomFsFileSystem.Log();
|
||||
protected override Result DoCleanDirectoryRecursively(in Path path) => ResultFs.UnsupportedWriteForRomFsFileSystem.Log();
|
||||
protected override Result DoDeleteFile(in Path path) => ResultFs.UnsupportedWriteForRomFsFileSystem.Log();
|
||||
protected override Result DoRenameDirectory(in Path currentPath, in Path newPath) => ResultFs.UnsupportedWriteForRomFsFileSystem.Log();
|
||||
protected override Result DoRenameFile(in Path currentPath, in Path newPath) => ResultFs.UnsupportedWriteForRomFsFileSystem.Log();
|
||||
protected override Result DoCommitProvisionally(long counter) => ResultFs.UnsupportedCommitProvisionallyForRomFsFileSystem.Log();
|
||||
|
||||
protected override Result DoGetFreeSpaceSize(out long freeSpace, U8Span path)
|
||||
protected override Result DoGetFreeSpaceSize(out long freeSpace, in Path path)
|
||||
{
|
||||
freeSpace = 0;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoGetTotalSpaceSize(out long totalSpace, U8Span path)
|
||||
protected override Result DoGetTotalSpaceSize(out long totalSpace, in Path path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out totalSpace);
|
||||
return ResultFs.UnsupportedGetTotalSpaceSizeForRomFsFileSystem.Log();
|
||||
|
@ -1,10 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using LibHac.Common;
|
||||
using LibHac.Common.Keys;
|
||||
using LibHac.Crypto;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
using Path = LibHac.Fs.Path;
|
||||
|
||||
namespace LibHac.FsSystem.Save
|
||||
{
|
||||
@ -146,91 +146,91 @@ namespace LibHac.FsSystem.Save
|
||||
IntegrityStorageType.Save, integrityCheckLevel, LeaveOpen);
|
||||
}
|
||||
|
||||
protected override Result DoCreateDirectory(U8Span path)
|
||||
protected override Result DoCreateDirectory(in Path path)
|
||||
{
|
||||
Result result = SaveDataFileSystemCore.CreateDirectory(path);
|
||||
|
||||
return SaveResults.ConvertToExternalResult(result).LogConverted(result);
|
||||
}
|
||||
|
||||
protected override Result DoCreateFile(U8Span path, long size, CreateFileOptions options)
|
||||
protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option)
|
||||
{
|
||||
Result result = SaveDataFileSystemCore.CreateFile(path, size, options);
|
||||
Result result = SaveDataFileSystemCore.CreateFile(path, size, option);
|
||||
|
||||
return SaveResults.ConvertToExternalResult(result).LogConverted(result);
|
||||
}
|
||||
|
||||
protected override Result DoDeleteDirectory(U8Span path)
|
||||
protected override Result DoDeleteDirectory(in Path path)
|
||||
{
|
||||
Result result = SaveDataFileSystemCore.DeleteDirectory(path);
|
||||
|
||||
return SaveResults.ConvertToExternalResult(result).LogConverted(result);
|
||||
}
|
||||
|
||||
protected override Result DoDeleteDirectoryRecursively(U8Span path)
|
||||
protected override Result DoDeleteDirectoryRecursively(in Path path)
|
||||
{
|
||||
Result result = SaveDataFileSystemCore.DeleteDirectoryRecursively(path);
|
||||
|
||||
return SaveResults.ConvertToExternalResult(result).LogConverted(result);
|
||||
}
|
||||
|
||||
protected override Result DoCleanDirectoryRecursively(U8Span path)
|
||||
protected override Result DoCleanDirectoryRecursively(in Path path)
|
||||
{
|
||||
Result result = SaveDataFileSystemCore.CleanDirectoryRecursively(path);
|
||||
|
||||
return SaveResults.ConvertToExternalResult(result).LogConverted(result);
|
||||
}
|
||||
|
||||
protected override Result DoDeleteFile(U8Span path)
|
||||
protected override Result DoDeleteFile(in Path path)
|
||||
{
|
||||
Result result = SaveDataFileSystemCore.DeleteFile(path);
|
||||
|
||||
return SaveResults.ConvertToExternalResult(result).LogConverted(result);
|
||||
}
|
||||
|
||||
protected override Result DoOpenDirectory(out IDirectory directory, U8Span path, OpenDirectoryMode mode)
|
||||
protected override Result DoOpenDirectory(out IDirectory directory, in Path path, OpenDirectoryMode mode)
|
||||
{
|
||||
Result result = SaveDataFileSystemCore.OpenDirectory(out directory, path, mode);
|
||||
|
||||
return SaveResults.ConvertToExternalResult(result).LogConverted(result);
|
||||
}
|
||||
|
||||
protected override Result DoOpenFile(out IFile file, U8Span path, OpenMode mode)
|
||||
protected override Result DoOpenFile(out IFile file, in Path path, OpenMode mode)
|
||||
{
|
||||
Result result = SaveDataFileSystemCore.OpenFile(out file, path, mode);
|
||||
|
||||
return SaveResults.ConvertToExternalResult(result).LogConverted(result);
|
||||
}
|
||||
|
||||
protected override Result DoRenameDirectory(U8Span oldPath, U8Span newPath)
|
||||
protected override Result DoRenameDirectory(in Path currentPath, in Path newPath)
|
||||
{
|
||||
Result result = SaveDataFileSystemCore.RenameDirectory(oldPath, newPath);
|
||||
Result result = SaveDataFileSystemCore.RenameDirectory(currentPath, newPath);
|
||||
|
||||
return SaveResults.ConvertToExternalResult(result).LogConverted(result);
|
||||
}
|
||||
|
||||
protected override Result DoRenameFile(U8Span oldPath, U8Span newPath)
|
||||
protected override Result DoRenameFile(in Path currentPath, in Path newPath)
|
||||
{
|
||||
Result result = SaveDataFileSystemCore.RenameFile(oldPath, newPath);
|
||||
Result result = SaveDataFileSystemCore.RenameFile(currentPath, newPath);
|
||||
|
||||
return SaveResults.ConvertToExternalResult(result).LogConverted(result);
|
||||
}
|
||||
|
||||
protected override Result DoGetEntryType(out DirectoryEntryType entryType, U8Span path)
|
||||
protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path)
|
||||
{
|
||||
Result result = SaveDataFileSystemCore.GetEntryType(out entryType, path);
|
||||
|
||||
return SaveResults.ConvertToExternalResult(result).LogConverted(result);
|
||||
}
|
||||
|
||||
protected override Result DoGetFreeSpaceSize(out long freeSpace, U8Span path)
|
||||
protected override Result DoGetFreeSpaceSize(out long freeSpace, in Path path)
|
||||
{
|
||||
Result result = SaveDataFileSystemCore.GetFreeSpaceSize(out freeSpace, path);
|
||||
|
||||
return SaveResults.ConvertToExternalResult(result).LogConverted(result);
|
||||
}
|
||||
|
||||
protected override Result DoGetTotalSpaceSize(out long totalSpace, U8Span path)
|
||||
protected override Result DoGetTotalSpaceSize(out long totalSpace, in Path path)
|
||||
{
|
||||
Result result = SaveDataFileSystemCore.GetTotalSpaceSize(out totalSpace, path);
|
||||
|
||||
@ -309,15 +309,14 @@ namespace LibHac.FsSystem.Save
|
||||
return journalValidity;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
public override void Dispose()
|
||||
{
|
||||
if (disposing)
|
||||
if (!LeaveOpen)
|
||||
{
|
||||
if (!LeaveOpen)
|
||||
{
|
||||
BaseStorage?.Dispose();
|
||||
}
|
||||
BaseStorage?.Dispose();
|
||||
}
|
||||
|
||||
base.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.Util;
|
||||
using Path = LibHac.Fs.Path;
|
||||
|
||||
namespace LibHac.FsSystem.Save
|
||||
{
|
||||
@ -31,29 +31,36 @@ namespace LibHac.FsSystem.Save
|
||||
FileTable = new HierarchicalSaveFileTable(dirTableStorage, fileTableStorage);
|
||||
}
|
||||
|
||||
protected override Result DoCreateDirectory(U8Span path)
|
||||
private Result CheckIfNormalized(in Path path)
|
||||
{
|
||||
Unsafe.SkipInit(out FsPath normalizedPath);
|
||||
|
||||
Result rc = PathNormalizer.Normalize(normalizedPath.Str, out _, path, false, false);
|
||||
Result rc = PathNormalizer.IsNormalized(out bool isNormalized, out _, path.GetString());
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
FileTable.AddDirectory(normalizedPath);
|
||||
if (!isNormalized)
|
||||
return ResultFs.NotNormalized.Log();
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoCreateFile(U8Span path, long size, CreateFileOptions options)
|
||||
protected override Result DoCreateDirectory(in Path path)
|
||||
{
|
||||
Unsafe.SkipInit(out FsPath normalizedPath);
|
||||
Result rc = CheckIfNormalized(in path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
Result rc = PathNormalizer.Normalize(normalizedPath.Str, out _, path, false, false);
|
||||
FileTable.AddDirectory(new U8Span(path.GetString()));
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option)
|
||||
{
|
||||
Result rc = CheckIfNormalized(in path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (size == 0)
|
||||
{
|
||||
var emptyFileEntry = new SaveFileInfo { StartBlock = int.MinValue, Length = size };
|
||||
FileTable.AddFile(normalizedPath, ref emptyFileEntry);
|
||||
FileTable.AddFile(new U8Span(path.GetString()), ref emptyFileEntry);
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
@ -68,59 +75,51 @@ namespace LibHac.FsSystem.Save
|
||||
|
||||
var fileEntry = new SaveFileInfo { StartBlock = startBlock, Length = size };
|
||||
|
||||
FileTable.AddFile(normalizedPath, ref fileEntry);
|
||||
FileTable.AddFile(new U8Span(path.GetString()), ref fileEntry);
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoDeleteDirectory(U8Span path)
|
||||
protected override Result DoDeleteDirectory(in Path path)
|
||||
{
|
||||
Unsafe.SkipInit(out FsPath normalizedPath);
|
||||
|
||||
Result rc = PathNormalizer.Normalize(normalizedPath.Str, out _, path, false, false);
|
||||
Result rc = CheckIfNormalized(in path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
FileTable.DeleteDirectory(normalizedPath);
|
||||
FileTable.DeleteDirectory(new U8Span(path.GetString()));
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoDeleteDirectoryRecursively(U8Span path)
|
||||
protected override Result DoDeleteDirectoryRecursively(in Path path)
|
||||
{
|
||||
Unsafe.SkipInit(out FsPath normalizedPath);
|
||||
|
||||
Result rc = PathNormalizer.Normalize(normalizedPath.Str, out _, path, false, false);
|
||||
Result rc = CheckIfNormalized(in path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = CleanDirectoryRecursively(normalizedPath);
|
||||
rc = CleanDirectoryRecursively(in path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = DeleteDirectory(normalizedPath);
|
||||
rc = DeleteDirectory(in path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoCleanDirectoryRecursively(U8Span path)
|
||||
protected override Result DoCleanDirectoryRecursively(in Path path)
|
||||
{
|
||||
Unsafe.SkipInit(out FsPath normalizedPath);
|
||||
|
||||
Result rc = PathNormalizer.Normalize(normalizedPath.Str, out _, path, false, false);
|
||||
Result rc = CheckIfNormalized(in path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
FileSystemExtensions.CleanDirectoryRecursivelyGeneric(this, normalizedPath.ToString());
|
||||
FileSystemExtensions.CleanDirectoryRecursivelyGeneric(this, new U8Span(path.GetString()).ToString());
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoDeleteFile(U8Span path)
|
||||
protected override Result DoDeleteFile(in Path path)
|
||||
{
|
||||
Unsafe.SkipInit(out FsPath normalizedPath);
|
||||
|
||||
Result rc = PathNormalizer.Normalize(normalizedPath.Str, out _, path, false, false);
|
||||
Result rc = CheckIfNormalized(in path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (!FileTable.TryOpenFile(normalizedPath, out SaveFileInfo fileInfo))
|
||||
if (!FileTable.TryOpenFile(new U8Span(path.GetString()), out SaveFileInfo fileInfo))
|
||||
{
|
||||
return ResultFs.PathNotFound.Log();
|
||||
}
|
||||
@ -130,21 +129,19 @@ namespace LibHac.FsSystem.Save
|
||||
AllocationTable.Free(fileInfo.StartBlock);
|
||||
}
|
||||
|
||||
FileTable.DeleteFile(normalizedPath);
|
||||
FileTable.DeleteFile(new U8Span(path.GetString()));
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoOpenDirectory(out IDirectory directory, U8Span path, OpenDirectoryMode mode)
|
||||
protected override Result DoOpenDirectory(out IDirectory directory, in Path path, OpenDirectoryMode mode)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out directory);
|
||||
|
||||
Unsafe.SkipInit(out FsPath normalizedPath);
|
||||
|
||||
Result rc = PathNormalizer.Normalize(normalizedPath.Str, out _, path, false, false);
|
||||
Result rc = CheckIfNormalized(in path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (!FileTable.TryOpenDirectory(normalizedPath, out SaveFindPosition position))
|
||||
if (!FileTable.TryOpenDirectory(new U8Span(path.GetString()), out SaveFindPosition position))
|
||||
{
|
||||
return ResultFs.PathNotFound.Log();
|
||||
}
|
||||
@ -154,73 +151,63 @@ namespace LibHac.FsSystem.Save
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoOpenFile(out IFile file, U8Span path, OpenMode mode)
|
||||
protected override Result DoOpenFile(out IFile file, in Path path, OpenMode mode)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out file);
|
||||
|
||||
Unsafe.SkipInit(out FsPath normalizedPath);
|
||||
|
||||
Result rc = PathNormalizer.Normalize(normalizedPath.Str, out _, path, false, false);
|
||||
Result rc = CheckIfNormalized(in path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (!FileTable.TryOpenFile(normalizedPath, out SaveFileInfo fileInfo))
|
||||
if (!FileTable.TryOpenFile(new U8Span(path.GetString()), out SaveFileInfo fileInfo))
|
||||
{
|
||||
return ResultFs.PathNotFound.Log();
|
||||
}
|
||||
|
||||
AllocationTableStorage storage = OpenFatStorage(fileInfo.StartBlock);
|
||||
|
||||
file = new SaveDataFile(storage, normalizedPath, FileTable, fileInfo.Length, mode);
|
||||
file = new SaveDataFile(storage, new U8Span(path.GetString()), FileTable, fileInfo.Length, mode);
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoRenameDirectory(U8Span oldPath, U8Span newPath)
|
||||
protected override Result DoRenameDirectory(in Path currentPath, in Path newPath)
|
||||
{
|
||||
Unsafe.SkipInit(out FsPath normalizedCurrentPath);
|
||||
Unsafe.SkipInit(out FsPath normalizedNewPath);
|
||||
|
||||
Result rc = PathNormalizer.Normalize(normalizedCurrentPath.Str, out _, oldPath, false, false);
|
||||
Result rc = CheckIfNormalized(in currentPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = PathNormalizer.Normalize(normalizedNewPath.Str, out _, newPath, false, false);
|
||||
rc = CheckIfNormalized(in newPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return FileTable.RenameDirectory(normalizedCurrentPath, normalizedNewPath);
|
||||
return FileTable.RenameDirectory(new U8Span(currentPath.GetString()), new U8Span(newPath.GetString()));
|
||||
}
|
||||
|
||||
protected override Result DoRenameFile(U8Span oldPath, U8Span newPath)
|
||||
protected override Result DoRenameFile(in Path currentPath, in Path newPath)
|
||||
{
|
||||
Unsafe.SkipInit(out FsPath normalizedCurrentPath);
|
||||
Unsafe.SkipInit(out FsPath normalizedNewPath);
|
||||
|
||||
Result rc = PathNormalizer.Normalize(normalizedCurrentPath.Str, out _, oldPath, false, false);
|
||||
Result rc = CheckIfNormalized(in currentPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = PathNormalizer.Normalize(normalizedNewPath.Str, out _, newPath, false, false);
|
||||
rc = CheckIfNormalized(in newPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
FileTable.RenameFile(normalizedCurrentPath, normalizedNewPath);
|
||||
FileTable.RenameFile(new U8Span(currentPath.GetString()), new U8Span(newPath.GetString()));
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoGetEntryType(out DirectoryEntryType entryType, U8Span path)
|
||||
protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out entryType);
|
||||
|
||||
Unsafe.SkipInit(out FsPath normalizedPath);
|
||||
|
||||
Result rc = PathNormalizer.Normalize(normalizedPath.Str, out _, path, false, false);
|
||||
Result rc = CheckIfNormalized(in path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (FileTable.TryOpenFile(normalizedPath, out SaveFileInfo _))
|
||||
if (FileTable.TryOpenFile(new U8Span(path.GetString()), out SaveFileInfo _))
|
||||
{
|
||||
entryType = DirectoryEntryType.File;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
if (FileTable.TryOpenDirectory(normalizedPath, out SaveFindPosition _))
|
||||
if (FileTable.TryOpenDirectory(new U8Span(path.GetString()), out SaveFindPosition _))
|
||||
{
|
||||
entryType = DirectoryEntryType.Directory;
|
||||
return Result.Success;
|
||||
@ -229,7 +216,7 @@ namespace LibHac.FsSystem.Save
|
||||
return ResultFs.PathNotFound.Log();
|
||||
}
|
||||
|
||||
protected override Result DoGetFreeSpaceSize(out long freeSpace, U8Span path)
|
||||
protected override Result DoGetFreeSpaceSize(out long freeSpace, in Path path)
|
||||
{
|
||||
int freeBlockCount = AllocationTable.GetFreeListLength();
|
||||
freeSpace = Header.BlockSize * freeBlockCount;
|
||||
@ -237,7 +224,7 @@ namespace LibHac.FsSystem.Save
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoGetTotalSpaceSize(out long totalSpace, U8Span path)
|
||||
protected override Result DoGetTotalSpaceSize(out long totalSpace, in Path path)
|
||||
{
|
||||
totalSpace = Header.BlockSize * Header.BlockCount;
|
||||
|
||||
|
@ -68,12 +68,9 @@ namespace LibHac.FsSystem
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
public override void Dispose()
|
||||
{
|
||||
if (_baseFileSystem is null)
|
||||
return;
|
||||
|
||||
if (disposing)
|
||||
if (_baseFileSystem is not null)
|
||||
{
|
||||
if (typeof(T) == typeof(SaveDataFileSystem))
|
||||
{
|
||||
@ -95,62 +92,62 @@ namespace LibHac.FsSystem
|
||||
_baseFileSystem = null;
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
base.Dispose();
|
||||
}
|
||||
|
||||
protected override Result DoOpenFile(out IFile file, U8Span path, OpenMode mode)
|
||||
protected override Result DoOpenFile(out IFile file, in Path path, OpenMode mode)
|
||||
{
|
||||
return _baseFileSystem.Target.OpenFile(out file, path, mode);
|
||||
}
|
||||
|
||||
protected override Result DoOpenDirectory(out IDirectory directory, U8Span path, OpenDirectoryMode mode)
|
||||
protected override Result DoOpenDirectory(out IDirectory directory, in Path path, OpenDirectoryMode mode)
|
||||
{
|
||||
return _baseFileSystem.Target.OpenDirectory(out directory, path, mode);
|
||||
}
|
||||
|
||||
protected override Result DoGetEntryType(out DirectoryEntryType entryType, U8Span path)
|
||||
protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path)
|
||||
{
|
||||
return _baseFileSystem.Target.GetEntryType(out entryType, path);
|
||||
}
|
||||
|
||||
protected override Result DoCreateFile(U8Span path, long size, CreateFileOptions option)
|
||||
protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option)
|
||||
{
|
||||
return _baseFileSystem.Target.CreateFile(path, size, option);
|
||||
}
|
||||
|
||||
protected override Result DoDeleteFile(U8Span path)
|
||||
protected override Result DoDeleteFile(in Path path)
|
||||
{
|
||||
return _baseFileSystem.Target.DeleteFile(path);
|
||||
}
|
||||
|
||||
protected override Result DoCreateDirectory(U8Span path)
|
||||
protected override Result DoCreateDirectory(in Path path)
|
||||
{
|
||||
return _baseFileSystem.Target.CreateDirectory(path);
|
||||
}
|
||||
|
||||
protected override Result DoDeleteDirectory(U8Span path)
|
||||
protected override Result DoDeleteDirectory(in Path path)
|
||||
{
|
||||
return _baseFileSystem.Target.DeleteDirectory(path);
|
||||
}
|
||||
|
||||
protected override Result DoDeleteDirectoryRecursively(U8Span path)
|
||||
protected override Result DoDeleteDirectoryRecursively(in Path path)
|
||||
{
|
||||
return _baseFileSystem.Target.DeleteDirectoryRecursively(path);
|
||||
}
|
||||
|
||||
protected override Result DoCleanDirectoryRecursively(U8Span path)
|
||||
protected override Result DoCleanDirectoryRecursively(in Path path)
|
||||
{
|
||||
return _baseFileSystem.Target.CleanDirectoryRecursively(path);
|
||||
}
|
||||
|
||||
protected override Result DoRenameFile(U8Span oldPath, U8Span newPath)
|
||||
protected override Result DoRenameFile(in Path currentPath, in Path newPath)
|
||||
{
|
||||
return _baseFileSystem.Target.RenameFile(oldPath, newPath);
|
||||
return _baseFileSystem.Target.RenameFile(currentPath, newPath);
|
||||
}
|
||||
|
||||
protected override Result DoRenameDirectory(U8Span oldPath, U8Span newPath)
|
||||
protected override Result DoRenameDirectory(in Path currentPath, in Path newPath)
|
||||
{
|
||||
return _baseFileSystem.Target.RenameDirectory(oldPath, newPath);
|
||||
return _baseFileSystem.Target.RenameDirectory(currentPath, newPath);
|
||||
}
|
||||
|
||||
protected override Result DoCommit()
|
||||
@ -168,12 +165,12 @@ namespace LibHac.FsSystem
|
||||
return _baseFileSystem.Target.Rollback();
|
||||
}
|
||||
|
||||
protected override Result DoGetFreeSpaceSize(out long freeSpace, U8Span path)
|
||||
protected override Result DoGetFreeSpaceSize(out long freeSpace, in Path path)
|
||||
{
|
||||
return _baseFileSystem.Target.GetFreeSpaceSize(out freeSpace, path);
|
||||
}
|
||||
|
||||
protected override Result DoGetTotalSpaceSize(out long totalSpace, U8Span path)
|
||||
protected override Result DoGetTotalSpaceSize(out long totalSpace, in Path path)
|
||||
{
|
||||
return _baseFileSystem.Target.GetTotalSpaceSize(out totalSpace, path);
|
||||
}
|
||||
|
@ -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();
|
@ -38,90 +38,86 @@ namespace LibHac.FsSystem
|
||||
new StorageLayoutTypeSetFileSystem(ref baseFileSystem, storageFlag));
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
public override void Dispose()
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag);
|
||||
BaseFileSystem?.Dispose();
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag);
|
||||
BaseFileSystem?.Dispose();
|
||||
base.Dispose();
|
||||
}
|
||||
|
||||
protected override Result DoCreateFile(U8Span path, long size, CreateFileOptions option)
|
||||
protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option)
|
||||
{
|
||||
using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag);
|
||||
return BaseFileSystem.Target.CreateFile(path, size, option);
|
||||
}
|
||||
|
||||
protected override Result DoDeleteFile(U8Span path)
|
||||
protected override Result DoDeleteFile(in Path path)
|
||||
{
|
||||
using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag);
|
||||
return BaseFileSystem.Target.DeleteFile(path);
|
||||
}
|
||||
|
||||
protected override Result DoCreateDirectory(U8Span path)
|
||||
protected override Result DoCreateDirectory(in Path path)
|
||||
{
|
||||
using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag);
|
||||
return BaseFileSystem.Target.CreateDirectory(path);
|
||||
}
|
||||
|
||||
protected override Result DoDeleteDirectory(U8Span path)
|
||||
protected override Result DoDeleteDirectory(in Path path)
|
||||
{
|
||||
using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag);
|
||||
return BaseFileSystem.Target.DeleteDirectory(path);
|
||||
}
|
||||
|
||||
protected override Result DoDeleteDirectoryRecursively(U8Span path)
|
||||
protected override Result DoDeleteDirectoryRecursively(in Path path)
|
||||
{
|
||||
using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag);
|
||||
return BaseFileSystem.Target.DeleteDirectoryRecursively(path);
|
||||
}
|
||||
|
||||
protected override Result DoCleanDirectoryRecursively(U8Span path)
|
||||
protected override Result DoCleanDirectoryRecursively(in Path path)
|
||||
{
|
||||
using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag);
|
||||
return BaseFileSystem.Target.CleanDirectoryRecursively(path);
|
||||
}
|
||||
|
||||
protected override Result DoRenameFile(U8Span oldPath, U8Span newPath)
|
||||
protected override Result DoRenameFile(in Path currentPath, in Path newPath)
|
||||
{
|
||||
using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag);
|
||||
return BaseFileSystem.Target.RenameFile(oldPath, newPath);
|
||||
return BaseFileSystem.Target.RenameFile(currentPath, newPath);
|
||||
}
|
||||
|
||||
protected override Result DoRenameDirectory(U8Span oldPath, U8Span newPath)
|
||||
protected override Result DoRenameDirectory(in Path currentPath, in Path newPath)
|
||||
{
|
||||
using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag);
|
||||
return BaseFileSystem.Target.RenameDirectory(oldPath, newPath);
|
||||
return BaseFileSystem.Target.RenameDirectory(currentPath, newPath);
|
||||
}
|
||||
|
||||
protected override Result DoGetEntryType(out DirectoryEntryType entryType, U8Span path)
|
||||
protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path)
|
||||
{
|
||||
using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag);
|
||||
return BaseFileSystem.Target.GetEntryType(out entryType, path);
|
||||
}
|
||||
|
||||
protected override Result DoGetFreeSpaceSize(out long freeSpace, U8Span path)
|
||||
protected override Result DoGetFreeSpaceSize(out long freeSpace, in Path path)
|
||||
{
|
||||
using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag);
|
||||
return BaseFileSystem.Target.GetFreeSpaceSize(out freeSpace, path);
|
||||
}
|
||||
|
||||
protected override Result DoGetTotalSpaceSize(out long totalSpace, U8Span path)
|
||||
protected override Result DoGetTotalSpaceSize(out long totalSpace, in Path path)
|
||||
{
|
||||
using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag);
|
||||
return BaseFileSystem.Target.GetTotalSpaceSize(out totalSpace, path);
|
||||
}
|
||||
|
||||
protected override Result DoOpenFile(out IFile file, U8Span path, OpenMode mode)
|
||||
protected override Result DoOpenFile(out IFile file, in Path path, OpenMode mode)
|
||||
{
|
||||
using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag);
|
||||
return BaseFileSystem.Target.OpenFile(out file, path, mode);
|
||||
}
|
||||
|
||||
protected override Result DoOpenDirectory(out IDirectory directory, U8Span path, OpenDirectoryMode mode)
|
||||
protected override Result DoOpenDirectory(out IDirectory directory, in Path path, OpenDirectoryMode mode)
|
||||
{
|
||||
using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag);
|
||||
return BaseFileSystem.Target.OpenDirectory(out directory, path, mode);
|
||||
@ -151,13 +147,14 @@ namespace LibHac.FsSystem
|
||||
return BaseFileSystem.Target.Flush();
|
||||
}
|
||||
|
||||
protected override Result DoGetFileTimeStampRaw(out FileTimeStampRaw timeStamp, U8Span path)
|
||||
protected override Result DoGetFileTimeStampRaw(out FileTimeStampRaw timeStamp, in Path path)
|
||||
{
|
||||
using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag);
|
||||
return BaseFileSystem.Target.GetFileTimeStampRaw(out timeStamp, path);
|
||||
}
|
||||
|
||||
protected override Result DoQueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, QueryId queryId, U8Span path)
|
||||
protected override Result DoQueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, QueryId queryId,
|
||||
in Path path)
|
||||
{
|
||||
using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag);
|
||||
return BaseFileSystem.Target.QueryEntry(outBuffer, inBuffer, queryId, path);
|
||||
|
@ -1,270 +1,280 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.Util;
|
||||
|
||||
namespace LibHac.FsSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// An <see cref="IFileSystem"/> that uses a directory of another <see cref="IFileSystem"/> as its root directory.
|
||||
/// </summary>
|
||||
/// <remarks>Based on FS 12.0.3 (nnSdk 12.3.1)</remarks>
|
||||
public class SubdirectoryFileSystem : IFileSystem
|
||||
{
|
||||
private IFileSystem BaseFileSystem { get; }
|
||||
private ReferenceCountedDisposable<IFileSystem> BaseFileSystemShared { get; }
|
||||
private U8String RootPath { get; set; }
|
||||
private bool PreserveUnc { get; }
|
||||
private IFileSystem _baseFileSystem;
|
||||
private ReferenceCountedDisposable<IFileSystem> _baseFileSystemShared;
|
||||
private Path.Stored _rootPath;
|
||||
|
||||
public static Result CreateNew(out SubdirectoryFileSystem created, IFileSystem baseFileSystem, U8Span rootPath, bool preserveUnc = false)
|
||||
public SubdirectoryFileSystem(IFileSystem baseFileSystem)
|
||||
{
|
||||
var obj = new SubdirectoryFileSystem(baseFileSystem, preserveUnc);
|
||||
Result rc = obj.Initialize(rootPath);
|
||||
|
||||
if (rc.IsSuccess())
|
||||
{
|
||||
created = obj;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
obj.Dispose();
|
||||
UnsafeHelpers.SkipParamInit(out created);
|
||||
return rc;
|
||||
_baseFileSystem = baseFileSystem;
|
||||
}
|
||||
|
||||
public SubdirectoryFileSystem(IFileSystem baseFileSystem, bool preserveUnc = false)
|
||||
public SubdirectoryFileSystem(ref ReferenceCountedDisposable<IFileSystem> baseFileSystem)
|
||||
{
|
||||
BaseFileSystem = baseFileSystem;
|
||||
PreserveUnc = preserveUnc;
|
||||
_baseFileSystemShared = Shared.Move(ref baseFileSystem);
|
||||
_baseFileSystem = _baseFileSystemShared.Target;
|
||||
}
|
||||
|
||||
public SubdirectoryFileSystem(ref ReferenceCountedDisposable<IFileSystem> baseFileSystem, bool preserveUnc = false)
|
||||
public override void Dispose()
|
||||
{
|
||||
BaseFileSystemShared = Shared.Move(ref baseFileSystem);
|
||||
BaseFileSystem = BaseFileSystemShared.Target;
|
||||
PreserveUnc = preserveUnc;
|
||||
ReferenceCountedDisposable<IFileSystem> sharedFs = Shared.Move(ref _baseFileSystemShared);
|
||||
sharedFs?.Dispose();
|
||||
base.Dispose();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
public Result Initialize(in Path rootPath)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
BaseFileSystemShared?.Dispose();
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
return _rootPath.Initialize(in rootPath);
|
||||
}
|
||||
|
||||
public Result Initialize(U8Span rootPath)
|
||||
private Result ResolveFullPath(ref Path outPath, in Path relativePath)
|
||||
{
|
||||
if (StringUtils.GetLength(rootPath, PathTools.MaxPathLength + 1) > PathTools.MaxPathLength)
|
||||
return ResultFs.TooLongPath.Log();
|
||||
|
||||
Span<byte> normalizedPath = stackalloc byte[PathTools.MaxPathLength + 2];
|
||||
|
||||
Result rc = PathNormalizer.Normalize(normalizedPath, out long normalizedPathLen, rootPath, PreserveUnc, false);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
// Ensure a trailing separator
|
||||
if (!PathNormalizer.IsSeparator(normalizedPath[(int)normalizedPathLen - 1]))
|
||||
{
|
||||
Debug.Assert(normalizedPathLen + 2 <= normalizedPath.Length);
|
||||
|
||||
normalizedPath[(int)normalizedPathLen] = StringTraits.DirectorySeparator;
|
||||
normalizedPath[(int)normalizedPathLen + 1] = StringTraits.NullTerminator;
|
||||
normalizedPathLen++;
|
||||
}
|
||||
|
||||
byte[] buffer = new byte[normalizedPathLen + 1];
|
||||
normalizedPath.Slice(0, (int)normalizedPathLen).CopyTo(buffer);
|
||||
RootPath = new U8String(buffer);
|
||||
|
||||
return Result.Success;
|
||||
Path rootPath = _rootPath.GetPath();
|
||||
return outPath.Combine(in rootPath, in relativePath);
|
||||
}
|
||||
|
||||
private Result ResolveFullPath(Span<byte> outPath, U8Span relativePath)
|
||||
{
|
||||
if (RootPath.Length + StringUtils.GetLength(relativePath, PathTools.MaxPathLength + 1) > outPath.Length)
|
||||
return ResultFs.TooLongPath.Log();
|
||||
|
||||
// Copy root path to the output
|
||||
RootPath.Value.CopyTo(outPath);
|
||||
|
||||
// Copy the normalized relative path to the output
|
||||
return PathNormalizer.Normalize(outPath.Slice(RootPath.Length - 2), out _, relativePath, PreserveUnc, false);
|
||||
}
|
||||
|
||||
protected override Result DoCreateDirectory(U8Span path)
|
||||
{
|
||||
Span<byte> fullPath = stackalloc byte[PathTools.MaxPathLength + 1];
|
||||
Result rc = ResolveFullPath(fullPath, path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return BaseFileSystem.CreateDirectory(new U8Span(fullPath));
|
||||
}
|
||||
|
||||
protected override Result DoCreateFile(U8Span path, long size, CreateFileOptions options)
|
||||
{
|
||||
Span<byte> fullPath = stackalloc byte[PathTools.MaxPathLength + 1];
|
||||
Result rc = ResolveFullPath(fullPath, path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return BaseFileSystem.CreateFile(new U8Span(fullPath), size, options);
|
||||
}
|
||||
|
||||
protected override Result DoDeleteDirectory(U8Span path)
|
||||
{
|
||||
Span<byte> fullPath = stackalloc byte[PathTools.MaxPathLength + 1];
|
||||
Result rc = ResolveFullPath(fullPath, path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return BaseFileSystem.DeleteDirectory(new U8Span(fullPath));
|
||||
}
|
||||
|
||||
protected override Result DoDeleteDirectoryRecursively(U8Span path)
|
||||
{
|
||||
Span<byte> fullPath = stackalloc byte[PathTools.MaxPathLength + 1];
|
||||
Result rc = ResolveFullPath(fullPath, path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return BaseFileSystem.DeleteDirectoryRecursively(new U8Span(fullPath));
|
||||
}
|
||||
|
||||
protected override Result DoCleanDirectoryRecursively(U8Span path)
|
||||
{
|
||||
Span<byte> fullPath = stackalloc byte[PathTools.MaxPathLength + 1];
|
||||
Result rc = ResolveFullPath(fullPath, path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return BaseFileSystem.CleanDirectoryRecursively(new U8Span(fullPath));
|
||||
}
|
||||
|
||||
protected override Result DoDeleteFile(U8Span path)
|
||||
{
|
||||
Span<byte> fullPath = stackalloc byte[PathTools.MaxPathLength + 1];
|
||||
Result rc = ResolveFullPath(fullPath, path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return BaseFileSystem.DeleteFile(new U8Span(fullPath));
|
||||
}
|
||||
|
||||
protected override Result DoOpenDirectory(out IDirectory directory, U8Span path, OpenDirectoryMode mode)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out directory);
|
||||
|
||||
Span<byte> fullPath = stackalloc byte[PathTools.MaxPathLength + 1];
|
||||
Result rc = ResolveFullPath(fullPath, path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return BaseFileSystem.OpenDirectory(out directory, new U8Span(fullPath), mode);
|
||||
}
|
||||
|
||||
protected override Result DoOpenFile(out IFile file, U8Span path, OpenMode mode)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out file);
|
||||
|
||||
Span<byte> fullPath = stackalloc byte[PathTools.MaxPathLength + 1];
|
||||
Result rc = ResolveFullPath(fullPath, path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return BaseFileSystem.OpenFile(out file, new U8Span(fullPath), mode);
|
||||
}
|
||||
|
||||
protected override Result DoRenameDirectory(U8Span oldPath, U8Span newPath)
|
||||
{
|
||||
Span<byte> fullOldPath = stackalloc byte[PathTools.MaxPathLength + 1];
|
||||
Span<byte> fullNewPath = stackalloc byte[PathTools.MaxPathLength + 1];
|
||||
|
||||
Result rc = ResolveFullPath(fullOldPath, oldPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = ResolveFullPath(fullNewPath, newPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return BaseFileSystem.RenameDirectory(new U8Span(fullOldPath), new U8Span(fullNewPath));
|
||||
}
|
||||
|
||||
protected override Result DoRenameFile(U8Span oldPath, U8Span newPath)
|
||||
{
|
||||
Span<byte> fullOldPath = stackalloc byte[PathTools.MaxPathLength + 1];
|
||||
Span<byte> fullNewPath = stackalloc byte[PathTools.MaxPathLength + 1];
|
||||
|
||||
Result rc = ResolveFullPath(fullOldPath, oldPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = ResolveFullPath(fullNewPath, newPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return BaseFileSystem.RenameFile(new U8Span(fullOldPath), new U8Span(fullNewPath));
|
||||
}
|
||||
|
||||
protected override Result DoGetEntryType(out DirectoryEntryType entryType, U8Span path)
|
||||
protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out entryType);
|
||||
|
||||
Unsafe.SkipInit(out FsPath fullPath);
|
||||
|
||||
Result rc = ResolveFullPath(fullPath.Str, path);
|
||||
var fullPath = new Path();
|
||||
Result rc = ResolveFullPath(ref fullPath, in path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return BaseFileSystem.GetEntryType(out entryType, fullPath);
|
||||
rc = _baseFileSystem.GetEntryType(out entryType, in fullPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
fullPath.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoGetFreeSpaceSize(out long freeSpace, in Path path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out freeSpace);
|
||||
|
||||
var fullPath = new Path();
|
||||
Result rc = ResolveFullPath(ref fullPath, in path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = _baseFileSystem.GetFreeSpaceSize(out freeSpace, in fullPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
fullPath.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoGetTotalSpaceSize(out long totalSpace, in Path path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out totalSpace);
|
||||
|
||||
var fullPath = new Path();
|
||||
Result rc = ResolveFullPath(ref fullPath, in path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = _baseFileSystem.GetTotalSpaceSize(out totalSpace, in fullPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
fullPath.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoGetFileTimeStampRaw(out FileTimeStampRaw timeStamp, in Path path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out timeStamp);
|
||||
|
||||
var fullPath = new Path();
|
||||
Result rc = ResolveFullPath(ref fullPath, in path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = _baseFileSystem.GetFileTimeStampRaw(out timeStamp, in fullPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
fullPath.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoOpenFile(out IFile file, in Path path, OpenMode mode)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out file);
|
||||
|
||||
var fullPath = new Path();
|
||||
Result rc = ResolveFullPath(ref fullPath, in path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = _baseFileSystem.OpenFile(out file, in fullPath, mode);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
fullPath.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoOpenDirectory(out IDirectory directory, in Path path, OpenDirectoryMode mode)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out directory);
|
||||
|
||||
var fullPath = new Path();
|
||||
Result rc = ResolveFullPath(ref fullPath, in path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = _baseFileSystem.OpenDirectory(out directory, in fullPath, mode);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
fullPath.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option)
|
||||
{
|
||||
var fullPath = new Path();
|
||||
Result rc = ResolveFullPath(ref fullPath, in path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = _baseFileSystem.CreateFile(in fullPath, size, option);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
fullPath.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoDeleteFile(in Path path)
|
||||
{
|
||||
var fullPath = new Path();
|
||||
Result rc = ResolveFullPath(ref fullPath, in path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = _baseFileSystem.DeleteFile(in fullPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
fullPath.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoCreateDirectory(in Path path)
|
||||
{
|
||||
var fullPath = new Path();
|
||||
Result rc = ResolveFullPath(ref fullPath, in path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = _baseFileSystem.CreateDirectory(in fullPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
fullPath.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoDeleteDirectory(in Path path)
|
||||
{
|
||||
var fullPath = new Path();
|
||||
Result rc = ResolveFullPath(ref fullPath, in path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = _baseFileSystem.DeleteDirectory(in fullPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
fullPath.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoDeleteDirectoryRecursively(in Path path)
|
||||
{
|
||||
var fullPath = new Path();
|
||||
Result rc = ResolveFullPath(ref fullPath, in path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = _baseFileSystem.DeleteDirectoryRecursively(in fullPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
fullPath.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoCleanDirectoryRecursively(in Path path)
|
||||
{
|
||||
var fullPath = new Path();
|
||||
Result rc = ResolveFullPath(ref fullPath, in path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = _baseFileSystem.CleanDirectoryRecursively(in fullPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
fullPath.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoRenameFile(in Path currentPath, in Path newPath)
|
||||
{
|
||||
var currentFullPath = new Path();
|
||||
Result rc = ResolveFullPath(ref currentFullPath, in currentPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
var newFullPath = new Path();
|
||||
rc = ResolveFullPath(ref newFullPath, in newPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = _baseFileSystem.RenameFile(in currentFullPath, in newFullPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
currentFullPath.Dispose();
|
||||
newFullPath.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoRenameDirectory(in Path currentPath, in Path newPath)
|
||||
{
|
||||
var currentFullPath = new Path();
|
||||
Result rc = ResolveFullPath(ref currentFullPath, in currentPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
var newFullPath = new Path();
|
||||
rc = ResolveFullPath(ref newFullPath, in newPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = _baseFileSystem.RenameDirectory(in currentFullPath, in newFullPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
currentFullPath.Dispose();
|
||||
newFullPath.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoQueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, QueryId queryId,
|
||||
in Path path)
|
||||
{
|
||||
var fullPath = new Path();
|
||||
Result rc = ResolveFullPath(ref fullPath, in path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = _baseFileSystem.QueryEntry(outBuffer, inBuffer, queryId, in fullPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
fullPath.Dispose();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoCommit()
|
||||
{
|
||||
return BaseFileSystem.Commit();
|
||||
return _baseFileSystem.Commit();
|
||||
}
|
||||
|
||||
protected override Result DoCommitProvisionally(long counter)
|
||||
{
|
||||
return BaseFileSystem.CommitProvisionally(counter);
|
||||
return _baseFileSystem.CommitProvisionally(counter);
|
||||
}
|
||||
|
||||
protected override Result DoRollback()
|
||||
{
|
||||
return BaseFileSystem.Rollback();
|
||||
}
|
||||
|
||||
protected override Result DoGetFreeSpaceSize(out long freeSpace, U8Span path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out freeSpace);
|
||||
|
||||
Span<byte> fullPath = stackalloc byte[PathTools.MaxPathLength + 1];
|
||||
Result rc = ResolveFullPath(fullPath, path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return BaseFileSystem.GetFreeSpaceSize(out freeSpace, new U8Span(fullPath));
|
||||
}
|
||||
|
||||
protected override Result DoGetTotalSpaceSize(out long totalSpace, U8Span path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out totalSpace);
|
||||
|
||||
Span<byte> fullPath = stackalloc byte[PathTools.MaxPathLength + 1];
|
||||
Result rc = ResolveFullPath(fullPath, path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return BaseFileSystem.GetTotalSpaceSize(out totalSpace, new U8Span(fullPath));
|
||||
}
|
||||
|
||||
protected override Result DoGetFileTimeStampRaw(out FileTimeStampRaw timeStamp, U8Span path)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out timeStamp);
|
||||
|
||||
Span<byte> fullPath = stackalloc byte[PathTools.MaxPathLength + 1];
|
||||
Result rc = ResolveFullPath(fullPath, path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return BaseFileSystem.GetFileTimeStampRaw(out timeStamp, new U8Span(fullPath));
|
||||
}
|
||||
|
||||
protected override Result DoQueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, QueryId queryId,
|
||||
U8Span path)
|
||||
{
|
||||
Span<byte> fullPath = stackalloc byte[PathTools.MaxPathLength + 1];
|
||||
Result rc = ResolveFullPath(fullPath, path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return BaseFileSystem.QueryEntry(outBuffer, inBuffer, queryId, new U8Span(fullPath));
|
||||
return _baseFileSystem.Rollback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
51
src/LibHac/FsSystem/ThreadPriorityChanger.cs
Normal file
51
src/LibHac/FsSystem/ThreadPriorityChanger.cs
Normal file
@ -0,0 +1,51 @@
|
||||
using System;
|
||||
|
||||
namespace LibHac.FsSystem
|
||||
{
|
||||
// Todo: Actually implement both of these structs
|
||||
public struct ScopedThreadPriorityChanger : IDisposable
|
||||
{
|
||||
public enum Mode
|
||||
{
|
||||
Absolute,
|
||||
Relative
|
||||
}
|
||||
|
||||
public ScopedThreadPriorityChanger(int priority, Mode mode)
|
||||
{
|
||||
// Change the current thread priority
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Change thread priority back
|
||||
}
|
||||
}
|
||||
|
||||
public struct ScopedThreadPriorityChangerByAccessPriority : IDisposable
|
||||
{
|
||||
public enum AccessMode
|
||||
{
|
||||
Read,
|
||||
Write
|
||||
}
|
||||
|
||||
private ScopedThreadPriorityChanger _scopedChanger;
|
||||
|
||||
public ScopedThreadPriorityChangerByAccessPriority(AccessMode mode)
|
||||
{
|
||||
_scopedChanger = new ScopedThreadPriorityChanger(GetThreadPriorityByAccessPriority(mode),
|
||||
ScopedThreadPriorityChanger.Mode.Absolute);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_scopedChanger.Dispose();
|
||||
}
|
||||
|
||||
private static int GetThreadPriorityByAccessPriority(AccessMode mode)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,97 +0,0 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using LibHac.Common;
|
||||
using LibHac.Diag;
|
||||
|
||||
namespace LibHac.FsSystem
|
||||
{
|
||||
public interface IUniqueLock : IDisposable
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a lock that may be passed between functions or objects.
|
||||
/// </summary>
|
||||
/// <remarks>This struct must never be copied. It must always be passed by
|
||||
/// reference or moved via the move constructor.</remarks>
|
||||
public struct UniqueLockSemaphore : IDisposable
|
||||
{
|
||||
private SemaphoreAdaptor _semaphore;
|
||||
private bool _isLocked;
|
||||
|
||||
public UniqueLockSemaphore(SemaphoreAdaptor semaphore)
|
||||
{
|
||||
_semaphore = semaphore;
|
||||
_isLocked = false;
|
||||
}
|
||||
|
||||
public UniqueLockSemaphore(ref UniqueLockSemaphore other)
|
||||
{
|
||||
_semaphore = other._semaphore;
|
||||
_isLocked = other._isLocked;
|
||||
|
||||
other._isLocked = false;
|
||||
other._semaphore = null;
|
||||
}
|
||||
|
||||
public bool IsLocked => _isLocked;
|
||||
|
||||
public bool TryLock()
|
||||
{
|
||||
if (_isLocked)
|
||||
{
|
||||
throw new SynchronizationLockException("Attempted to lock a UniqueLock that was already locked.");
|
||||
}
|
||||
|
||||
_isLocked = _semaphore.TryLock();
|
||||
return _isLocked;
|
||||
}
|
||||
|
||||
public void Unlock()
|
||||
{
|
||||
if (!_isLocked)
|
||||
{
|
||||
throw new SynchronizationLockException("Attempted to unlock a UniqueLock that was not locked.");
|
||||
}
|
||||
|
||||
_semaphore.Unlock();
|
||||
_isLocked = false;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isLocked)
|
||||
{
|
||||
_semaphore.Unlock();
|
||||
|
||||
_isLocked = false;
|
||||
_semaphore = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class UniqueLockWithPin<T> : IUniqueLock where T : class, IDisposable
|
||||
{
|
||||
private UniqueLockSemaphore _semaphore;
|
||||
private ReferenceCountedDisposable<T> _pinnedObject;
|
||||
|
||||
public UniqueLockWithPin(ref UniqueLockSemaphore semaphore, ref ReferenceCountedDisposable<T> pinnedObject)
|
||||
{
|
||||
Shared.Move(out _semaphore, ref semaphore);
|
||||
Shared.Move(out _pinnedObject, ref pinnedObject);
|
||||
|
||||
Assert.SdkAssert(_semaphore.IsLocked);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_pinnedObject != null)
|
||||
{
|
||||
_semaphore.Dispose();
|
||||
_pinnedObject.Dispose();
|
||||
|
||||
_pinnedObject = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user