Merge pull request #204 from Thealexbarney/ifilesystem-path

Use nn::fs::Path in IFileSystem and other places
This commit is contained in:
Alex Barney 2021-08-06 22:46:34 -07:00 committed by GitHub
commit 373ba8ea20
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
139 changed files with 7681 additions and 8241 deletions

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -18,7 +18,7 @@ namespace LibHac.Fs
{
if (IsValid)
{
Directory.GetParent().FsClient.CloseDirectory(this);
Directory.GetParent().Hos.Fs.CloseDirectory(this);
}
}
}

View File

@ -18,7 +18,7 @@ namespace LibHac.Fs
{
if (IsValid)
{
File.FsClient.CloseFile(this);
File.Hos.Fs.CloseFile(this);
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -139,9 +139,9 @@ namespace LibHac.FsSrv
return _isNormalStorageOpened || _isInternalStorageOpened;
}
public UniqueLock<SdkMutexType> GetLock()
public UniqueLockRef<SdkMutexType> GetLock()
{
return new UniqueLock<SdkMutexType>(ref _mutex);
return new UniqueLockRef<SdkMutexType>(ref _mutex);
}
}
@ -199,7 +199,7 @@ namespace LibHac.FsSrv
protected override Result DoRead(long offset, Span<byte> destination)
{
using UniqueLock<SdkMutexType> scopedLock = _baseStorage.Target.GetLock();
using UniqueLockRef<SdkMutexType> scopedLock = _baseStorage.Target.GetLock();
Result rc = AccessCheck(isWriteAccess: false);
if (rc.IsFailure()) return rc;
@ -209,7 +209,7 @@ namespace LibHac.FsSrv
protected override Result DoWrite(long offset, ReadOnlySpan<byte> source)
{
using UniqueLock<SdkMutexType> scopedLock = _baseStorage.Target.GetLock();
using UniqueLockRef<SdkMutexType> scopedLock = _baseStorage.Target.GetLock();
Result rc = AccessCheck(isWriteAccess: true);
if (rc.IsFailure()) return rc;
@ -219,7 +219,7 @@ namespace LibHac.FsSrv
protected override Result DoFlush()
{
using UniqueLock<SdkMutexType> scopedLock = _baseStorage.Target.GetLock();
using UniqueLockRef<SdkMutexType> scopedLock = _baseStorage.Target.GetLock();
Result rc = AccessCheck(isWriteAccess: true);
if (rc.IsFailure()) return rc;
@ -229,7 +229,7 @@ namespace LibHac.FsSrv
protected override Result DoSetSize(long size)
{
using UniqueLock<SdkMutexType> scopedLock = _baseStorage.Target.GetLock();
using UniqueLockRef<SdkMutexType> scopedLock = _baseStorage.Target.GetLock();
Result rc = AccessCheck(isWriteAccess: true);
if (rc.IsFailure()) return rc;
@ -241,7 +241,7 @@ namespace LibHac.FsSrv
{
Unsafe.SkipInit(out size);
using UniqueLock<SdkMutexType> scopedLock = _baseStorage.Target.GetLock();
using UniqueLockRef<SdkMutexType> scopedLock = _baseStorage.Target.GetLock();
Result rc = AccessCheck(isWriteAccess: false);
if (rc.IsFailure()) return rc;
@ -252,7 +252,7 @@ namespace LibHac.FsSrv
protected override Result DoOperateRange(Span<byte> outBuffer, OperationId operationId, long offset, long size,
ReadOnlySpan<byte> inBuffer)
{
using UniqueLock<SdkMutexType> scopedLock = _baseStorage.Target.GetLock();
using UniqueLockRef<SdkMutexType> scopedLock = _baseStorage.Target.GetLock();
Result rc = AccessCheck(isWriteAccess: true);
if (rc.IsFailure()) return rc;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,13 +1,14 @@
using System;
using System.Threading;
using LibHac.Os;
namespace LibHac.FsSystem
{
public class SemaphoreAdaptor : IDisposable
public class SemaphoreAdapter : IDisposable, ILockable
{
private SemaphoreSlim _semaphore;
public SemaphoreAdaptor(int initialCount, int maxCount)
public SemaphoreAdapter(int initialCount, int maxCount)
{
_semaphore = new SemaphoreSlim(initialCount, maxCount);
}
@ -17,6 +18,11 @@ namespace LibHac.FsSystem
return _semaphore.Wait(System.TimeSpan.Zero);
}
public void Lock()
{
_semaphore.Wait();
}
public void Unlock()
{
_semaphore.Release();

View File

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

View File

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

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

View File

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