diff --git a/src/LibHac/Common/U8StringBuilder.cs b/src/LibHac/Common/U8StringBuilder.cs index 22614c1f..0cf367a3 100644 --- a/src/LibHac/Common/U8StringBuilder.cs +++ b/src/LibHac/Common/U8StringBuilder.cs @@ -14,6 +14,10 @@ namespace LibHac.Common private const int NullTerminatorLength = 1; public Span Buffer { get; private set; } + + /// + /// The current length of the string not including the null terminator. + /// public int Length { get; private set; } public bool Overflowed { get; private set; } public bool AutoExpand { get; } diff --git a/src/LibHac/Fs/AccessLog.cs b/src/LibHac/Fs/AccessLog.cs index 8d3a21ee..b18fa2f9 100644 --- a/src/LibHac/Fs/AccessLog.cs +++ b/src/LibHac/Fs/AccessLog.cs @@ -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); diff --git a/src/LibHac/Fs/Common/DirectoryPathParser.cs b/src/LibHac/Fs/Common/DirectoryPathParser.cs new file mode 100644 index 00000000..49f7cfa0 --- /dev/null +++ b/src/LibHac/Fs/Common/DirectoryPathParser.cs @@ -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 _buffer; + private byte _replacedChar; + private int _position; + + // Todo: Make private so we can use the GetCurrentPath method once lifetime tracking is better + public Path CurrentPath; + + public void Dispose() + { + CurrentPath.Dispose(); + } + + public Result Initialize(ref Path path) + { + Span pathBuffer = path.GetWriteBufferLength() != 0 ? path.GetWriteBuffer() : Span.Empty; + + int windowsSkipLength = 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 initialPath = ReadNextImpl(); + + if (!initialPath.IsEmpty) + { + Result rc = CurrentPath.InitializeWithNormalization(initialPath); + if (rc.IsFailure()) return rc; + } + } + + return Result.Success; + } + + // Todo: Return reference when escape semantics are better + public readonly Path GetCurrentPath() + { + return CurrentPath; + } + + public Result ReadNext(out bool isFinished) + { + isFinished = false; + + Span nextEntry = ReadNextImpl(); + + if (nextEntry.IsEmpty) + { + isFinished = true; + return Result.Success; + } + + return CurrentPath.AppendChild(nextEntry); + } + + private Span ReadNextImpl() + { + // Check if we've already hit the end of the path. + if (_position < 0 || _buffer.At(0) == 0) + return Span.Empty; + + // Restore the character we previously replaced with a null terminator. + if (_replacedChar != 0) + { + _buffer[_position] = _replacedChar; + + if (_replacedChar == DirectorySeparator) + _position++; + } + + // If the path is rooted, the first entry should be the root directory. + if (_position == 0 && _buffer.At(0) == DirectorySeparator) + { + _replacedChar = _buffer[1]; + _buffer[1] = 0; + _position = 1; + return _buffer; + } + + // Find the end of the next entry, replacing the directory separator with a null terminator. + Span entry = _buffer.Slice(_position); + + int i; + for (i = _position; _buffer.At(i) != DirectorySeparator; i++) + { + if (_buffer.At(i) == 0) + { + if (i == _position) + entry = Span.Empty; + + _position = -1; + return entry; + } + } + + Assert.SdkAssert(_buffer.At(i + 1) != NullTerminator); + + _replacedChar = DirectorySeparator; + _buffer[i] = 0; + _position = i; + return entry; + } + } +} diff --git a/src/LibHac/Fs/Common/Path.cs b/src/LibHac/Fs/Common/Path.cs index d5772660..ad12394e 100644 --- a/src/LibHac/Fs/Common/Path.cs +++ b/src/LibHac/Fs/Common/Path.cs @@ -26,9 +26,103 @@ namespace LibHac.Fs public bool IsBackslashAllowed() => (_value & (1 << 4)) != 0; } + /// + /// Represents a file path stored as a UTF-8 string. + /// + /// + /// A has three parts to it:
+ /// 1. A that points to the current path string.
+ /// 2. A write buffer that can be allocated if operations need to be done on the path.
+ /// 3. An IsNormalized flag that tracks the path normalization status of the current path.
+ /// There are two different ways to initialize a . The "Initialize*" methods will + /// ensure a write buffer is allocated and copy the input path to it. 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 . + ///
Based on FS 12.0.3 (nnSdk 12.3.1)
[DebuggerDisplay("{" + nameof(ToString) + "(),nq}")] public ref struct Path { + /// + /// Used to store a path in a non-ref struct. + /// + [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.Shared.Return(buffer); + } + } + + /// + /// Initializes this path with the data from a standard . + /// must be normalized. + /// + /// The used to initialize this one. + /// : The operation was successful.
+ /// : The IsNormalized flag of + /// is not .
+ 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 GetString() => _buffer; + + /// + /// Creates a from this . This + /// must not be reinitialized or disposed for the lifetime of the created . + /// + /// The created . + 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.Shared.Rent(alignedLength); + + byte[] oldBuffer = _buffer; + _buffer = buffer; + + if (oldBuffer is not null) + ArrayPool.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 GetWriteBuffer() + /// + /// Gets the current write buffer. + /// + /// The write buffer. + internal Span GetWriteBuffer() { Assert.SdkRequires(_writeBuffer is not null); return _writeBuffer.AsSpan(); } + /// + /// Gets the current length of the write buffer. + /// + /// The write buffer length. internal readonly long GetWriteBufferLength() { return _writeBufferLength; } - private readonly int GetLength() + /// + /// Calculates the length of the current string. + /// + /// The length of the current string> + public readonly int GetLength() { return StringUtils.GetLength(GetString()); } + /// + /// Returns if + /// + /// public readonly bool IsEmpty() { return _string.At(0) == 0; } + /// + /// Calculates if the first "" characters of the + /// current path and are the same. + /// + /// The string to compare to this . + /// The maximum number of characters to compare. + /// if the strings are the same; otherwise . public readonly bool IsMatchHead(ReadOnlySpan value, int length) { return StringUtils.Compare(GetString(), value, length) == 0; @@ -94,6 +212,10 @@ namespace LibHac.Fs return StringUtils.Compare(left.GetString(), right) == 0; } + /// + /// Releases this 's write buffer and returns it to the caller. + /// + /// The write buffer if the had one; otherwise . public byte[] ReleaseBuffer() { Assert.SdkRequires(_writeBuffer is not null); @@ -104,6 +226,9 @@ namespace LibHac.Fs return Shared.Move(ref _writeBuffer); } + /// + /// Releases any current write buffer and sets this to an empty string. + /// private void ClearBuffer() { byte[] oldBuffer = Shared.Move(ref _writeBuffer); @@ -115,6 +240,12 @@ namespace LibHac.Fs _string = EmptyPath; } + /// + /// Releases any current write buffer and sets the provided buffer as the new write buffer. + /// + /// The new write buffer. + /// The length of the write buffer. + /// Must be a multiple of . private void SetModifiableBuffer(byte[] buffer, int length) { Assert.SdkRequiresNotNull(buffer); @@ -131,6 +262,10 @@ namespace LibHac.Fs _string = buffer; } + /// + /// Releases any current write buffer and sets as this 's string. + /// + /// The buffer containing the new path. private void SetReadOnlyBuffer(ReadOnlySpan buffer) { _string = buffer; @@ -143,6 +278,11 @@ namespace LibHac.Fs _writeBufferLength = 0; } + /// + /// Ensures the write buffer is the specified or larger. + /// + /// The minimum desired length. + /// : The operation was successful. private Result Preallocate(int length) { if (_writeBufferLength > length) @@ -155,6 +295,14 @@ namespace LibHac.Fs return Result.Success; } + /// + /// Releases any current write buffer and sets as this 's string.
+ /// The path contained by must be normalized. + ///
+ /// It is up to the caller to ensure the path contained by is normalized. + /// This function will always set the IsNormalized flag to . + /// The buffer containing the new path. + /// : The operation was successful. public Result SetShallowBuffer(ReadOnlySpan buffer) { Assert.SdkRequires(_writeBufferLength == 0); @@ -164,6 +312,12 @@ namespace LibHac.Fs return Result.Success; } + /// + /// Gets the buffer containing the current path. + /// + /// This 's IsNormalized flag should be + /// before calling this function. + /// The buffer containing the current path. public readonly ReadOnlySpan GetString() { Assert.SdkAssert(_isNormalized); @@ -171,6 +325,16 @@ namespace LibHac.Fs return _string; } + /// + /// Initializes this with the data from another Path.
+ /// must be normalized. + ///
+ /// This 's IsNormalized flag will be set to + /// the value of 's flag. + /// The used to initialize this one. + /// : The operation was successful.
+ /// : The IsNormalized flag of + /// is not .
public Result Initialize(in Path other) { if (!other._isNormalized) @@ -190,6 +354,39 @@ namespace LibHac.Fs return Result.Success; } + /// + /// Initializes this with the data from a path. + /// + /// Ensures we have a large enough write buffer and copies the path to it. + /// This function always sets the IsNormalized flag to + /// because paths are always normalized upon initialization. + /// The path used to initialize this . + /// : The operation was successful. + 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; + } + + /// + /// Initializes this using the path in the provided buffer. + /// + /// Ensures the write buffer is large enough to hold + /// and copies to the write buffer.
+ /// This function does not modify the IsNormalized flag.
+ /// The buffer containing the path to use. + /// The length of the provided path. + /// : The operation was successful. private Result InitializeImpl(ReadOnlySpan path, int length) { if (length == 0 || path.At(0) == NullTerminator) @@ -209,6 +406,14 @@ namespace LibHac.Fs return Result.Success; } + /// + /// Initializes this using the path in the provided buffer. + /// + /// Ensures the write buffer is large enough to hold + /// and copies to the write buffer.
+ /// This function will always set the IsNormalized flag to .
+ /// The buffer containing the path to use. + /// : The operation was successful. public Result Initialize(ReadOnlySpan path) { Result rc = InitializeImpl(path, StringUtils.GetLength(path)); @@ -218,12 +423,25 @@ namespace LibHac.Fs return Result.Success; } + /// + /// Initializes this using the path in the provided buffer and + /// normalizes it if the path is a relative path or a Windows path. + /// + /// 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.
+ /// The IsNormalized flag will always be set to even if the incoming path + /// is not normalized. This can lead to a situation where the path is not normalized yet the + /// IsNormalized flag is still .
+ /// The buffer containing the path to use. + /// : The operation was successful.
+ /// : The path contains an invalid character.
+ /// : The path is not in a valid format.
public Result InitializeWithNormalization(ReadOnlySpan 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; } + /// + /// Initializes this using the path in the provided buffer. + /// + /// Ensures the write buffer is large enough to hold + /// and copies to the write buffer.
+ /// This function will always set the IsNormalized flag to .
+ /// The buffer containing the path to use. + /// The length of the provided path. + /// : The operation was successful. public Result Initialize(ReadOnlySpan path, int length) { Result rc = InitializeImpl(path, length); @@ -262,12 +489,26 @@ namespace LibHac.Fs return Result.Success; } + /// + /// Initializes this using the path in the provided buffer and + /// normalizes it if the path is a relative path or a Windows path. + /// + /// 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.
+ /// The IsNormalized flag will always be set to even if the incoming path + /// is not normalized. This can lead to a situation where the path is not normalized yet the + /// IsNormalized flag is still .
+ /// The buffer containing the path to use. + /// The length of the provided path. + /// : The operation was successful.
+ /// : The path contains an invalid character.
+ /// : The path is not in a valid format.
public Result InitializeWithNormalization(ReadOnlySpan 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; } + + /// + /// Initializes this using the path in the provided buffer and + /// replaces any backslashes in the path with forward slashes. + /// + /// This function will always set the IsNormalized flag to . + /// The buffer containing the path to use. + /// : The operation was successful. public Result InitializeWithReplaceBackslash(ReadOnlySpan 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; } + /// + /// Initializes this using the path in the provided buffer. If the path begins with two + /// forward slashes (//), those two forward slashes will be replaced with two backslashes (\\). + /// + /// This function will always set the IsNormalized flag to . + /// The buffer containing the path to use. + /// : The operation was successful. public Result InitializeWithReplaceForwardSlashes(ReadOnlySpan path) { Result rc = InitializeImpl(path, StringUtils.GetLength(path)); @@ -331,6 +587,18 @@ namespace LibHac.Fs return Result.Success; } + /// + /// Initializes this using the path in the provided buffer + /// and makes various UNC path-related replacements. + /// + /// The following replacements are made:
+ /// :/// located anywhere in the path is replaced with :/\\
+ /// @Host:// located at the beginning of the path is replaced with @Host:\\
+ /// // located at the beginning of the path is replaced with \\ + /// This function does not modify the IsNormalized flag. + ///
+ /// The buffer containing the path to use. + /// : The operation was successful. public Result InitializeWithReplaceUnc(ReadOnlySpan path) { Result rc = InitializeImpl(path, StringUtils.GetLength(path)); @@ -368,6 +636,11 @@ namespace LibHac.Fs return Result.Success; } + /// + /// Initializes the as an empty string. + /// + /// This function will always set the IsNormalized flag to . + /// : The operation was successful. public Result InitializeAsEmpty() { ClearBuffer(); @@ -376,12 +649,21 @@ namespace LibHac.Fs return Result.Success; } + /// + /// Updates this by prepending to the current path. + /// + /// This function does not modify the IsNormalized flag. + /// If is not normalized, this can lead to a situation where the resulting + /// path is not normalized yet the IsNormalized flag is still . + /// The buffer containing the path to insert. + /// : The operation was successful.
+ /// : The path provided in is a Windows path.
public Result InsertParent(ReadOnlySpan 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 } } + /// + /// Updates this by prepending to the current path. + /// + /// This function does not modify the IsNormalized flag. + /// If is not normalized, this can lead to a situation where the resulting + /// path is not normalized yet the IsNormalized flag is still . + /// The to insert. + /// : The operation was successful.
+ /// : The path provided in is a Windows path.
public Result InsertParent(in Path parent) { return InsertParent(parent.GetString()); } + /// + /// Updates this by appending to the current path. + /// + /// This function does not modify the IsNormalized flag. + /// If is not normalized, this can lead to a situation where the resulting + /// path is not normalized yet the IsNormalized flag is still . + /// The buffer containing the child path to append to the current path. + /// : The operation was successful. public Result AppendChild(ReadOnlySpan child) { ReadOnlySpan 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.Shared.Return(parentBuffer); } - - _isNormalized = false; - return Result.Success; } + /// + /// Updates this by appending to the current path. + /// + /// This function does not modify the IsNormalized flag. + /// If is not normalized, this can lead to a situation where the resulting + /// path is not normalized yet the IsNormalized flag is still . + /// The child to append to the current path. + /// : The operation was successful. public Result AppendChild(in Path child) { return AppendChild(child.GetString()); } + /// + /// Combines 2 s into a single path. + /// + /// If is empty, this 's IsNormalized flag will + /// be set to the value of 's flag. + /// Otherwise the flag will be set to the value of 's flag. + /// The first path to combine. + /// The second path to combine. + /// : The operation was successful.
+ /// : The IsNormalized flag of either + /// or is not .
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; } + /// + /// Removes the last entry from this . + /// + /// This function does not modify the IsNormalized flag. + /// : The operation was successful.
+ /// : The path before calling this function was + /// one of ".", "..", "/" or "\".
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; } + /// + /// Normalizes the current path according to the provided . + /// + /// If this 's IsNormalized flag is set, this function does nothing. + /// The IsNormalized flag will be set if this function returns successfully. + /// Flags that specify what types of paths are allowed. + /// : The operation was successful.
+ /// : The path contains an invalid character.
+ /// : The path is in an invalid format for the specified .
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 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 pathBuffer, + ReadOnlySpan 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 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 pathBuffer, uint metaType) + { + ReadOnlySpan 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 pathBuffer, ulong saveDataId) + { + ReadOnlySpan 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); + } } } diff --git a/src/LibHac/Fs/Common/PathFormatter.cs b/src/LibHac/Fs/Common/PathFormatter.cs index 4079c15c..42899eaf 100644 --- a/src/LibHac/Fs/Common/PathFormatter.cs +++ b/src/LibHac/Fs/Common/PathFormatter.cs @@ -11,6 +11,10 @@ using static LibHac.Fs.StringTraits; // ReSharper disable once CheckNamespace namespace LibHac.Fs { + /// + /// Contains functions for working with path formatting and normalization. + /// + /// Based on FS 12.0.3 (nnSdk 12.3.1) 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 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.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; diff --git a/src/LibHac/Fs/Common/PathNormalizer.cs b/src/LibHac/Fs/Common/PathNormalizer.cs index f0ab0d96..6b42698a 100644 --- a/src/LibHac/Fs/Common/PathNormalizer.cs +++ b/src/LibHac/Fs/Common/PathNormalizer.cs @@ -1,380 +1,321 @@ using System; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using LibHac.Common; using LibHac.Diag; using LibHac.FsSystem; +using static LibHac.Fs.PathUtility; using static LibHac.Fs.StringTraits; // ReSharper disable once CheckNamespace namespace LibHac.Fs { - // Previous normalization code can be found in commit 1acdd86e27de16703fdb1c77f50ed8fd71bd3ad7 + /// + /// Contains functions for doing with basic path normalization. + /// + /// Based on FS 12.0.3 (nnSdk 12.3.1) public static class PathNormalizer { - private enum NormalizeState + private enum PathState { Initial, Normal, FirstSeparator, Separator, - Dot, - DoubleDot + CurrentDir, + ParentDir } - /// - /// Checks if a host-name or share-name in a UNC path are "." or ".." - /// - /// Up to the first two characters in a segment of a UNC path. - /// : The operation was successful.
- /// : A buffer could not be allocated.
- private static Result CheckSharedName(U8Span path) + public static Result Normalize(Span outputBuffer, out int length, ReadOnlySpan path, bool isWindowsPath, + bool isDriveRelativePath) { - if (path.Length == 1 && path.GetUnsafe(0) == Dot) - return ResultFs.InvalidPathFormat.Log(); + UnsafeHelpers.SkipParamInit(out length); - if (path.Length == 2 && path.GetUnsafe(0) == Dot && path.GetUnsafe(1) == Dot) - return ResultFs.InvalidPathFormat.Log(); + ReadOnlySpan currentPath = path; + int totalLength = 0; + int i = 0; - return Result.Success; - } - - private static Result ParseWindowsPath(out U8Span newPath, Span buffer, out long windowsPathLength, - out bool isUncNormalized, U8Span path, bool hasMountName) - { - UnsafeHelpers.SkipParamInit(out windowsPathLength, out isUncNormalized); - newPath = default; - - U8Span currentPath = path; - - if (!Unsafe.IsNullRef(ref isUncNormalized)) - isUncNormalized = true; - - bool skippedMount = false; - int prefixLength = 0; - - if (hasMountName) + if (!IsSeparator(path.At(0))) { - if (path.GetOrNull(0) == DirectorySeparator && path.GetOrNull(1) == AltDirectorySeparator && - path.GetOrNull(1) == AltDirectorySeparator) - { - currentPath = currentPath.Slice(1); - skippedMount = true; - } - else - { - int separatorCount = 0; + if (!isDriveRelativePath) + return ResultFs.InvalidPathFormat.Log(); - while (IsSeparator(currentPath.GetOrNull(separatorCount))) + outputBuffer[totalLength++] = DirectorySeparator; + } + + var convertedPath = new RentedArray(); + try + { + // Check if parent directory path replacement is needed. + if (IsParentDirectoryPathReplacementNeeded(currentPath)) + { + // Allocate a buffer to hold the replacement path. + convertedPath = new RentedArray(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])) { - separatorCount++; + 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; } - if (separatorCount != 0) + int dirLen = 0; + while (!IsSeparator(currentPath.At(i + dirLen)) && !IsNul(currentPath.At(i + dirLen))) { - if (separatorCount == 1 || WindowsPath.IsWindowsDrive(currentPath.Slice(separatorCount))) + 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) { - currentPath = currentPath.Slice(separatorCount); - skippedMount = true; + if (!isWindowsPath) + return ResultFs.DirectoryUnobtainable.Log(); + + totalLength--; } else { - if (separatorCount > 2 && !Unsafe.IsNullRef(ref isUncNormalized)) + totalLength -= 2; + + do { - isUncNormalized = false; - return Result.Success; - } - - currentPath = currentPath.Slice(separatorCount - 2); - prefixLength = 1; - } - } - } - } - else if (path.GetOrNull(0) == DirectorySeparator && !WindowsPath.IsUnc(path)) - { - currentPath = currentPath.Slice(1); - skippedMount = true; - } - - U8Span trimmedPath = path; - - if (WindowsPath.IsWindowsDrive(currentPath)) - { - int i; - for (i = 2; currentPath.GetOrNull(i) != NullTerminator; i++) - { - if (currentPath[i] == DirectorySeparator || currentPath[i] == AltDirectorySeparator) - { - trimmedPath = currentPath.Slice(i); - break; - } - } - - if (trimmedPath.Value == path.Value) - trimmedPath = currentPath.Slice(i); - - ref byte pathStart = ref MemoryMarshal.GetReference(path.Value); - ref byte trimmedPathStart = ref MemoryMarshal.GetReference(trimmedPath.Value); - int winPathLength = (int)Unsafe.ByteOffset(ref pathStart, ref trimmedPathStart); - - if (!buffer.IsEmpty) - { - if (winPathLength > buffer.Length) - return ResultFs.TooLongPath.Log(); - - path.Value.Slice(0, winPathLength).CopyTo(buffer); - } - - newPath = trimmedPath; - windowsPathLength = winPathLength; - return Result.Success; - } - // A UNC path should be in the format "\\" host-name "\" share-name [ "\" object-name ] - else if (WindowsPath.IsUnc(currentPath)) - { - if (currentPath.GetOrNull(2) == DirectorySeparator || currentPath.GetOrNull(2) == AltDirectorySeparator) - { - Assert.SdkAssert(!hasMountName); - return ResultFs.InvalidPathFormat.Log(); - } - else - { - bool needsSeparatorFix = false; - int currentComponentOffset = 0; - - for (int i = 2; currentPath.GetOrNull(i) != NullTerminator; i++) - { - byte c = currentPath.GetUnsafe(i); - - // Check if we need to fix the separators - if (currentComponentOffset == 0 && c == AltDirectorySeparator) - { - needsSeparatorFix = true; - - if (!Unsafe.IsNullRef(ref isUncNormalized)) - { - isUncNormalized = false; - return Result.Success; - } - } - - if (c == DirectorySeparator || c == AltDirectorySeparator) - { - if (c == AltDirectorySeparator) - needsSeparatorFix = true; - - if (currentComponentOffset != 0) - break; - - if (IsSeparator(currentPath.GetOrNull(i + 1))) - return ResultFs.InvalidPathFormat.Log(); - - Result rc = CheckSharedName(currentPath.Slice(2, i - 2)); - if (rc.IsFailure()) return rc; - - currentComponentOffset = i + 1; - } - - if (c == (byte)'$' || c == DriveSeparator) - { - if (currentComponentOffset == 0) - return ResultFs.InvalidCharacter.Log(); - - // A '$' or ':' must be the last character in share-name - byte nextChar = currentPath.GetOrNull(i + 1); - if (nextChar != DirectorySeparator && nextChar != AltDirectorySeparator && - nextChar != NullTerminator) - { - return ResultFs.InvalidPathFormat.Log(); - } - - trimmedPath = currentPath.Slice(i + 1); - break; - } - } - - if (trimmedPath.Value == path.Value) - { - int trimmedPartOffset = 0; - - int i; - for (i = 2; currentPath.GetOrNull(i) != NullTerminator; i++) - { - byte c = currentPath.GetUnsafe(i); - - if (c == DirectorySeparator || c == AltDirectorySeparator) - { - Result rc; - - if (trimmedPartOffset != 0) - { - rc = CheckSharedName(currentPath.Slice(trimmedPartOffset, i - trimmedPartOffset)); - if (rc.IsFailure()) return rc; - - trimmedPath = currentPath.Slice(i); + if (outputBuffer[totalLength] == DirectorySeparator) break; - } - if (IsSeparator(currentPath.GetOrNull(i + 1))) - { - return ResultFs.InvalidPathFormat.Log(); - } - - rc = CheckSharedName(currentPath.Slice(2, i - 2)); - if (rc.IsFailure()) return rc; - - trimmedPartOffset = i + 1; - } + totalLength--; + } while (totalLength != 0); } - if (trimmedPartOffset != 0 && trimmedPath.Value == path.Value) - { - Result rc = CheckSharedName(currentPath.Slice(trimmedPartOffset, i - trimmedPartOffset)); - if (rc.IsFailure()) return rc; + if (!isWindowsPath) + Assert.SdkAssert(outputBuffer[totalLength] == DirectorySeparator); - trimmedPath = currentPath.Slice(i); - } + Assert.SdkAssert(totalLength < outputBuffer.Length); } - - ref byte trimmedPathStart = ref MemoryMarshal.GetReference(trimmedPath.Value); - ref byte currentPathStart = ref MemoryMarshal.GetReference(currentPath.Value); - int mountLength = (int)Unsafe.ByteOffset(ref currentPathStart, ref trimmedPathStart); - bool prependSeparator = prefixLength != 0 || skippedMount; - - if (!buffer.IsEmpty) + else { - if (mountLength > buffer.Length) + 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(); } - Span currentBuffer = buffer; - if (prependSeparator) + for (int j = 0; j < dirLen; j++) { - currentBuffer[0] = DirectorySeparator; - currentBuffer = currentBuffer.Slice(1); - } - - currentPath.Value.Slice(0, mountLength).CopyTo(currentBuffer); - currentBuffer[0] = AltDirectorySeparator; - currentBuffer[1] = AltDirectorySeparator; - - if (needsSeparatorFix) - { - for (int i = 2; i < mountLength; i++) - { - if (currentBuffer[i] == AltDirectorySeparator) - currentBuffer[i] = DirectorySeparator; - } + outputBuffer[totalLength++] = currentPath[i + j]; } } - newPath = trimmedPath; - windowsPathLength = mountLength + (prependSeparator ? 1 : 0); - return Result.Success; + i += dirLen; } - } - else - { - newPath = trimmedPath; - return Result.Success; - } - } - private static Result SkipWindowsPath(out U8Span newPath, out bool isUncNormalized, U8Span path, - bool hasMountName) - { - return ParseWindowsPath(out newPath, Span.Empty, out _, out isUncNormalized, path, hasMountName); - } + if (skipNextSeparator) + totalLength--; - private static Result ParseMountName(out U8Span newPath, Span outMountNameBuffer, - out long mountNameLength, U8Span path) - { - UnsafeHelpers.SkipParamInit(out mountNameLength); - newPath = default; - - int mountStart = IsSeparator(path.GetOrNull(0)) ? 1 : 0; - int mountEnd; - - int maxMountLength = Math.Min(PathTools.MountNameLengthMax, path.Length - mountStart); - - for (mountEnd = mountStart; mountEnd <= maxMountLength; mountEnd++) - { - byte c = path[mountEnd]; - - if (IsSeparator(c)) + if (totalLength == 0 && outputBuffer.Length != 0) { - newPath = path; - mountNameLength = 0; - - return Result.Success; + totalLength = 1; + outputBuffer[0] = DirectorySeparator; } - if (c == DriveSeparator) - { - mountEnd++; - break; - } - - if (c == NullTerminator) - { - break; - } - } - - if (mountStart >= mountEnd - 1 || path[mountEnd - 1] != DriveSeparator) - return ResultFs.InvalidPathFormat.Log(); - - if (mountEnd != mountStart) - { - for (int i = mountStart; i < mountEnd; i++) - { - if (path[i] == Dot) - return ResultFs.InvalidCharacter.Log(); - } - } - - if (!outMountNameBuffer.IsEmpty) - { - if (mountEnd - mountStart > outMountNameBuffer.Length) + // Note: This bug is in the original code. They probably meant to put "totalLength + 1" + if (totalLength - 1 > outputBuffer.Length) return ResultFs.TooLongPath.Log(); - path.Value.Slice(0, mountEnd).CopyTo(outMountNameBuffer); + 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(); + } + } + + /// + /// 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. + /// + /// When this function returns , + /// contains if the path is normalized or if it is not. + /// Contents are undefined if the function does not return . + /// + /// When this function returns and + /// is , contains the length of the normalized path. + /// Contents are undefined if the function does not return + /// or is . + /// + /// The path to check. + /// : The operation was successful.
+ /// : The path contains an invalid character.
+ /// : The path is not in a valid format.
+ public static Result IsNormalized(out bool isNormalized, out int length, ReadOnlySpan 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; + } } - newPath = path.Slice(mountEnd); - mountNameLength = mountEnd - mountStart; + 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; } - private static Result SkipMountName(out U8Span newPath, U8Span path) - { - return ParseMountName(out newPath, Span.Empty, out _, path); - } /// /// Checks if a path begins with / or \ and contains any of these patterns: /// "/..\", "\..\", "\../", "\..0" where '0' is the null terminator. /// - private static bool IsParentDirectoryPathReplacementNeeded(U8Span path) + public static bool IsParentDirectoryPathReplacementNeeded(ReadOnlySpan path) { - if (!IsAnySeparator(path.GetOrNull(0))) + 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.GetOrNull(i + 3); + byte c3 = path.At(i + 3); if (path[i] == AltDirectorySeparator && path[i + 1] == Dot && path[i + 2] == Dot && - (IsAnySeparator(c3) || c3 == NullTerminator)) + (c3 == DirectorySeparator || c3 == AltDirectorySeparator || c3 == NullTerminator)) { return true; } - if (IsAnySeparator(path[i]) && + if ((path[i] == DirectorySeparator || path[i] == AltDirectorySeparator) && path[i + 1] == Dot && path[i + 2] == Dot && c3 == AltDirectorySeparator) @@ -394,10 +335,10 @@ namespace LibHac.Fs while (source.Length > i && source[i] != NullTerminator) { if (source.Length > i + 2 && - IsAnySeparator(source[i - 1]) && - source[i + 0] == Dot && - source[i + 1] == Dot && - IsAnySeparator(source[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; @@ -427,370 +368,5 @@ namespace LibHac.Fs dest[i] = NullTerminator; } - - public static Result Normalize(Span outputBuffer, out long normalizedLength, U8Span path, - bool preserveUnc, bool hasMountName) - { - UnsafeHelpers.SkipParamInit(out normalizedLength); - - U8Span currentPath = path; - int prefixLength = 0; - bool isUncPath = false; - - if (hasMountName) - { - Result rc = ParseMountName(out currentPath, outputBuffer, out long mountNameLength, currentPath); - if (rc.IsFailure()) return rc; - - prefixLength += (int)mountNameLength; - } - - if (preserveUnc) - { - U8Span originalPath = currentPath; - - Result rc = ParseWindowsPath(out currentPath, outputBuffer.Slice(prefixLength), - out long windowsPathLength, out Unsafe.NullRef(), currentPath, hasMountName); - if (rc.IsFailure()) return rc; - - prefixLength += (int)windowsPathLength; - if (originalPath.Value != currentPath.Value) - { - isUncPath = true; - } - } - - // Paths must start with / - if (prefixLength == 0 && !IsSeparator(currentPath.GetOrNull(0))) - return ResultFs.InvalidPathFormat.Log(); - - var convertedPath = new RentedArray(); - try - { - // Check if parent directory path replacement is needed. - if (IsParentDirectoryPathReplacementNeeded(currentPath)) - { - // Allocate a buffer to hold the replacement path. - convertedPath = new RentedArray(PathTools.MaxPathLength + 1); - - // Replace the path. - ReplaceParentDirectoryPath(convertedPath.Span, currentPath); - - // Set current path to be the replacement path. - currentPath = new U8Span(convertedPath.Span); - } - - bool skipNextSep = false; - int i = 0; - int totalLength = prefixLength; - - while (!IsNul(currentPath.GetOrNull(i))) - { - if (IsSeparator(currentPath[i])) - { - do - { - i++; - } while (IsSeparator(currentPath.GetOrNull(i))); - - if (IsNul(currentPath.GetOrNull(i))) - break; - - if (!skipNextSep) - { - if (totalLength + 1 == outputBuffer.Length) - { - outputBuffer[totalLength] = NullTerminator; - normalizedLength = totalLength; - - return ResultFs.TooLongPath.Log(); - } - - outputBuffer[totalLength++] = DirectorySeparator; - } - - skipNextSep = false; - } - - int dirLen = 0; - while (!IsSeparator(currentPath.GetOrNull(i + dirLen)) && !IsNul(currentPath.GetOrNull(i + dirLen))) - { - dirLen++; - } - - if (IsCurrentDirectory(currentPath.Slice(i))) - { - skipNextSep = true; - } - else if (IsParentDirectory(currentPath.Slice(i))) - { - Assert.SdkAssert(outputBuffer[totalLength - 1] == DirectorySeparator); - Assert.SdkAssert(outputBuffer[prefixLength] == DirectorySeparator); - - if (totalLength == prefixLength + 1) - { - if (!isUncPath) - return ResultFs.DirectoryUnobtainable.Log(); - - totalLength--; - } - else - { - totalLength -= 2; - - do - { - if (outputBuffer[totalLength] == DirectorySeparator) - break; - - totalLength--; - } while (totalLength != prefixLength); - } - - if (!isUncPath) - 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; - normalizedLength = totalLength; - return ResultFs.TooLongPath.Log(); - } - else - { - for (int j = 0; j < dirLen; j++) - { - outputBuffer[totalLength++] = currentPath[i + j]; - } - } - } - - i += dirLen; - } - - if (skipNextSep) - totalLength--; - - if (!isUncPath && totalLength == prefixLength && totalLength < outputBuffer.Length) - { - outputBuffer[prefixLength] = DirectorySeparator; - totalLength++; - } - - if (totalLength - 1 > outputBuffer.Length) - { - return ResultFs.TooLongPath.Log(); - } - else - { - outputBuffer[totalLength] = NullTerminator; - normalizedLength = totalLength; - - Assert.SdkAssert(IsNormalized(out bool normalized, new U8Span(outputBuffer), preserveUnc, hasMountName).IsSuccess()); - Assert.SdkAssert(normalized); - } - - return Result.Success; - } - finally - { - convertedPath.Dispose(); - } - } - - public static Result IsNormalized(out bool isNormalized, U8Span path, bool preserveUnc, bool hasMountName) - { - UnsafeHelpers.SkipParamInit(out isNormalized); - - U8Span currentPath = path; - U8Span originalPath = path; - bool isUncPath = false; - - if (hasMountName) - { - Result rc = SkipMountName(out currentPath, originalPath); - if (rc.IsFailure()) return rc; - - if (currentPath.GetOrNull(0) != DirectorySeparator) - return ResultFs.InvalidPathFormat.Log(); - } - - if (preserveUnc) - { - originalPath = currentPath; - - Result rc = SkipWindowsPath(out currentPath, out bool isUncNormalized, currentPath, hasMountName); - if (rc.IsFailure()) return rc; - - if (!isUncNormalized) - { - isNormalized = false; - return Result.Success; - } - - // Path is a UNC path if the new path skips part of the original - isUncPath = originalPath.Value != currentPath.Value; - - if (isUncPath) - { - if (IsSeparator(originalPath.GetOrNull(0)) && IsSeparator(originalPath.GetOrNull(1))) - { - isNormalized = false; - return Result.Success; - } - - if (IsNul(currentPath.GetOrNull(0))) - { - isNormalized = true; - return Result.Success; - } - } - } - - if (IsParentDirectoryPathReplacementNeeded(currentPath)) - { - isNormalized = false; - return Result.Success; - } - - var state = NormalizeState.Initial; - - for (int i = 0; i < currentPath.Length; i++) - { - byte c = currentPath[i]; - if (c == NullTerminator) break; - - switch (state) - { - case NormalizeState.Initial: - if (c == DirectorySeparator) - { - state = NormalizeState.FirstSeparator; - } - else - { - if (currentPath.Value == originalPath.Value) - return ResultFs.InvalidPathFormat.Log(); - - state = c == Dot ? NormalizeState.Dot : NormalizeState.Normal; - } - - break; - case NormalizeState.Normal: - if (c == DirectorySeparator) - { - state = NormalizeState.Separator; - } - - break; - case NormalizeState.FirstSeparator: - case NormalizeState.Separator: - if (c == DirectorySeparator) - { - isNormalized = false; - return Result.Success; - } - - state = c == Dot ? NormalizeState.Dot : NormalizeState.Normal; - break; - case NormalizeState.Dot: - if (c == DirectorySeparator) - { - isNormalized = false; - return Result.Success; - } - - state = c == Dot ? NormalizeState.DoubleDot : NormalizeState.Normal; - break; - case NormalizeState.DoubleDot: - if (c == DirectorySeparator) - { - isNormalized = false; - return Result.Success; - } - - state = NormalizeState.Normal; - break; - // ReSharper disable once UnreachableSwitchCaseDueToIntegerAnalysis - default: - Abort.UnexpectedDefault(); - break; - } - } - - switch (state) - { - case NormalizeState.Initial: - return ResultFs.InvalidPathFormat.Log(); - case NormalizeState.Normal: - isNormalized = true; - break; - case NormalizeState.FirstSeparator: - isNormalized = !isUncPath; - break; - case NormalizeState.Separator: - case NormalizeState.Dot: - case NormalizeState.DoubleDot: - isNormalized = false; - break; - // ReSharper disable once UnreachableSwitchCaseDueToIntegerAnalysis - default: - Abort.UnexpectedDefault(); - break; - } - - return Result.Success; - } - - public static bool IsCurrentDirectory(ReadOnlySpan p) - { - if (p.Length < 1) - return false; - - ref byte b = ref MemoryMarshal.GetReference(p); - - return b == Dot && - (p.Length == 1 || Unsafe.Add(ref b, 1) == NullTerminator || - Unsafe.Add(ref b, 1) == DirectorySeparator); - } - - public static bool IsParentDirectory(ReadOnlySpan p) - { - if (p.Length < 2) - return false; - - ref byte b = ref MemoryMarshal.GetReference(p); - - return b == Dot && - Unsafe.Add(ref b, 1) == Dot && - (p.Length == 2 || Unsafe.Add(ref b, 2) == NullTerminator || - Unsafe.Add(ref b, 2) == DirectorySeparator); - } - - public static bool IsNul(byte c) - { - return c == NullTerminator; - } - - public static bool IsSeparator(byte c) - { - return c == DirectorySeparator; - } - - public static bool IsAnySeparator(byte c) - { - return c == DirectorySeparator || c == AltDirectorySeparator; - } } } diff --git a/src/LibHac/Fs/Common/PathNormalizer12.cs b/src/LibHac/Fs/Common/PathNormalizer12.cs deleted file mode 100644 index 298f2c27..00000000 --- a/src/LibHac/Fs/Common/PathNormalizer12.cs +++ /dev/null @@ -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 outputBuffer, out int length, ReadOnlySpan path, bool isWindowsPath, - bool isDriveRelativePath) - { - UnsafeHelpers.SkipParamInit(out length); - - ReadOnlySpan 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(); - try - { - // Check if parent directory path replacement is needed. - if (IsParentDirectoryPathReplacementNeeded(currentPath)) - { - // Allocate a buffer to hold the replacement path. - convertedPath = new RentedArray(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(); - } - } - - /// - /// 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. - /// - /// When this function returns , - /// contains if the path is normalized or if it is not. - /// Contents are undefined if the function does not return . - /// - /// When this function returns and - /// is , contains the length of the normalized path. - /// Contents are undefined if the function does not return - /// or is . - /// - /// The path to check. - /// : The operation was successful.
- /// : The path contains an invalid character.
- /// : The path is not in a valid format.
- public static Result IsNormalized(out bool isNormalized, out int length, ReadOnlySpan 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; - } - - - /// - /// Checks if a path begins with / or \ and contains any of these patterns: - /// "/..\", "\..\", "\../", "\..0" where '0' is the null terminator. - /// - public static bool IsParentDirectoryPathReplacementNeeded(ReadOnlySpan 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 dest, ReadOnlySpan 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; - } - } -} diff --git a/src/LibHac/Fs/Common/PathUtility.cs b/src/LibHac/Fs/Common/PathUtility.cs index 0b27dcee..8fbbbde8 100644 --- a/src/LibHac/Fs/Common/PathUtility.cs +++ b/src/LibHac/Fs/Common/PathUtility.cs @@ -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 buffer, byte currentChar, byte newChar) @@ -68,62 +23,223 @@ namespace LibHac.Fs } } - /// - /// Performs the extra functions that nn::fs::FspPathPrintf does on the string buffer. - /// - /// The string builder to process. - /// The of the operation. - public static Result ToSfPath(in this U8StringBuilder builder) + public static bool IsCurrentDirectory(ReadOnlySpan 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 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 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 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 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 path) + { + Assert.SdkRequiresNotNull(path); + + uint utf8Buffer = 0; + Span utf8BufferSpan = SpanHelpers.AsByteSpan(ref utf8Buffer); + + ReadOnlySpan 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 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 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 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 lhs, ReadOnlySpan 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 path) + { + if (WindowsPath.IsWindowsPath(path, false)) + return true; + + return path.At(0) == DirectorySeparator; + } + + public static bool IsPathRelative(ReadOnlySpan path) + { + return path.At(0) != NullTerminator && !IsPathAbsolute(path); + } + + public static bool IsPathStartWithCurrentDirectory(ReadOnlySpan path) + { + return IsCurrentDirectory(path) || IsParentDirectory(path); + } } } diff --git a/src/LibHac/Fs/Common/PathUtility12.cs b/src/LibHac/Fs/Common/PathUtility12.cs deleted file mode 100644 index ebc41f87..00000000 --- a/src/LibHac/Fs/Common/PathUtility12.cs +++ /dev/null @@ -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 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 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 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 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 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 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 path) - { - Assert.SdkRequiresNotNull(path); - - uint utf8Buffer = 0; - Span utf8BufferSpan = SpanHelpers.AsByteSpan(ref utf8Buffer); - - ReadOnlySpan 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 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 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 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 lhs, ReadOnlySpan 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 path) - { - if (WindowsPath12.IsWindowsPath(path, false)) - return true; - - return path.At(0) == DirectorySeparator; - } - - public static bool IsPathRelative(ReadOnlySpan path) - { - return path.At(0) != NullTerminator && !IsPathAbsolute(path); - } - - public static bool IsPathStartWithCurrentDirectory(ReadOnlySpan path) - { - return IsCurrentDirectory(path) || IsParentDirectory(path); - } - } -} diff --git a/src/LibHac/Fs/Common/WindowsPath.cs b/src/LibHac/Fs/Common/WindowsPath.cs index 78eeae5a..0d0337e9 100644 --- a/src/LibHac/Fs/Common/WindowsPath.cs +++ b/src/LibHac/Fs/Common/WindowsPath.cs @@ -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 { + /// + /// Contains functions for working with Windows paths. + /// + /// Based on FS 12.0.3 (nnSdk 12.3.1) 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 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 path) + private static bool IsUncPathImpl(ReadOnlySpan 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 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 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 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 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 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 path) + { + return IsUncPathImpl(path, true, true); + } + + public static bool IsUncPath(ReadOnlySpan path, bool checkForwardSlash, bool checkBackSlash) + { + return IsUncPathImpl(path, checkForwardSlash, checkBackSlash); + } + + public static int GetUncPathPrefixLength(ReadOnlySpan path) + { + return GetUncPathPrefixLengthImpl(path, true); + } + + public static bool IsDosDevicePath(ReadOnlySpan path) + { + return IsDosDevicePathImpl(path); } public static int GetDosDevicePathPrefixLength() @@ -84,37 +159,100 @@ namespace LibHac.Fs return DosDevicePathPrefixLength; } - public static bool IsDriveName(ReadOnlySpan path) + public static bool IsWindowsPath(ReadOnlySpan path, bool checkForwardSlash) { - return path.Length == WindowsDriveLength && path[1] == ':'; + return IsWindowsDrive(path) || IsDosDevicePath(path) || IsUncPath(path, checkForwardSlash, true); } - public static bool IsUncPath(ReadOnlySpan path) + public static int GetWindowsSkipLength(ReadOnlySpan 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 path) + public static bool IsDosDelimiterW(char c) { - int i; - for (i = 0; i < path.Length; i++) + return c == '/' || c == '\\'; + } + + public static bool IsWindowsDriveW(ReadOnlySpan 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 path) + { + return IsUncPathImpl(path, true, true); + } + + public static int GetUncPathPrefixLengthW(ReadOnlySpan path) + { + return GetUncPathPrefixLengthImpl(path, true); + } + + public static bool IsDosDevicePathW(ReadOnlySpan path) + { + return IsDosDevicePathImpl(path); + } + + public static bool IsWindowsPathW(ReadOnlySpan path) + { + return IsWindowsDriveW(path) || IsUncPathW(path) || IsDosDevicePathW(path); + } + + public static Result CheckCharacterCountForWindows(ReadOnlySpan path, int maxNameLength, int maxPathLength) + { + Assert.SdkRequiresNotNull(path); + + ReadOnlySpan 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 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; } } } diff --git a/src/LibHac/Fs/Common/WindowsPath12.cs b/src/LibHac/Fs/Common/WindowsPath12.cs deleted file mode 100644 index 02ce5bb6..00000000 --- a/src/LibHac/Fs/Common/WindowsPath12.cs +++ /dev/null @@ -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 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 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 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 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 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 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 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 path) - { - return IsUncPathImpl(path, true, true); - } - - public static bool IsUncPath(ReadOnlySpan path, bool checkForwardSlash, bool checkBackSlash) - { - return IsUncPathImpl(path, checkForwardSlash, checkBackSlash); - } - - public static int GetUncPathPrefixLength(ReadOnlySpan path) - { - return GetUncPathPrefixLengthImpl(path, true); - } - - public static bool IsDosDevicePath(ReadOnlySpan path) - { - return IsDosDevicePathImpl(path); - } - - public static int GetDosDevicePathPrefixLength() - { - return 4; - } - - public static bool IsWindowsPath(ReadOnlySpan path, bool checkForwardSlash) - { - return IsWindowsDrive(path) || IsDosDevicePath(path) || IsUncPath(path, checkForwardSlash, true); - } - - public static int GetWindowsSkipLength(ReadOnlySpan 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 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 path) - { - return IsUncPathImpl(path, true, true); - } - - public static int GetUncPathPrefixLengthW(ReadOnlySpan path) - { - return GetUncPathPrefixLengthImpl(path, true); - } - - public static bool IsDosDevicePathW(ReadOnlySpan path) - { - return IsDosDevicePathImpl(path); - } - - public static bool IsWindowsPathW(ReadOnlySpan path) - { - return IsWindowsDriveW(path) || IsUncPathW(path) || IsDosDevicePathW(path); - } - - public static Result CheckCharacterCountForWindows(ReadOnlySpan path, int maxNameLength, int maxPathLength) - { - Assert.SdkRequiresNotNull(path); - - ReadOnlySpan 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; - } - } -} diff --git a/src/LibHac/Fs/DirectoryHandle.cs b/src/LibHac/Fs/DirectoryHandle.cs index 71cd02b5..cea5b81b 100644 --- a/src/LibHac/Fs/DirectoryHandle.cs +++ b/src/LibHac/Fs/DirectoryHandle.cs @@ -18,7 +18,7 @@ namespace LibHac.Fs { if (IsValid) { - Directory.GetParent().FsClient.CloseDirectory(this); + Directory.GetParent().Hos.Fs.CloseDirectory(this); } } } diff --git a/src/LibHac/Fs/FileHandle.cs b/src/LibHac/Fs/FileHandle.cs index 2ded399b..45bfbd34 100644 --- a/src/LibHac/Fs/FileHandle.cs +++ b/src/LibHac/Fs/FileHandle.cs @@ -18,7 +18,7 @@ namespace LibHac.Fs { if (IsValid) { - File.FsClient.CloseFile(this); + File.Hos.Fs.CloseFile(this); } } } diff --git a/src/LibHac/Fs/FileSystemClient.cs b/src/LibHac/Fs/FileSystemClient.cs index 99f3ec0e..d61cf1ca 100644 --- a/src/LibHac/Fs/FileSystemClient.cs +++ b/src/LibHac/Fs/FileSystemClient.cs @@ -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); } } diff --git a/src/LibHac/Fs/FsEnums.cs b/src/LibHac/Fs/FsEnums.cs index 236998d1..ea0ca1ad 100644 --- a/src/LibHac/Fs/FsEnums.cs +++ b/src/LibHac/Fs/FsEnums.cs @@ -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 diff --git a/src/LibHac/Fs/Fsa/DirectoryAccessor.cs b/src/LibHac/Fs/Fsa/DirectoryAccessor.cs index e5f282cf..1d88fe45 100644 --- a/src/LibHac/Fs/Fsa/DirectoryAccessor.cs +++ b/src/LibHac/Fs/Fsa/DirectoryAccessor.cs @@ -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); } diff --git a/src/LibHac/Fs/Fsa/FileAccessor.cs b/src/LibHac/Fs/Fsa/FileAccessor.cs index c0541819..9178cdf4 100644 --- a/src/LibHac/Fs/Fsa/FileAccessor.cs +++ b/src/LibHac/Fs/Fsa/FileAccessor.cs @@ -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 diff --git a/src/LibHac/Fs/Fsa/FileSystemAccessor.cs b/src/LibHac/Fs/Fsa/FileSystemAccessor.cs index 544e88ba..102091fd 100644 --- a/src/LibHac/Fs/Fsa/FileSystemAccessor.cs +++ b/src/LibHac/Fs/Fsa/FileSystemAccessor.cs @@ -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 _openFiles; @@ -24,14 +30,16 @@ namespace LibHac.Fs.Impl private bool _isPathCacheAttachable; private bool _isPathCacheAttached; private IMultiCommitTarget _multiCommitTarget; + private PathFlags _pathFlags; + private Optional _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(); @@ -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(LinkedList list, T item) { LinkedListNode 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 list) - { - for (LinkedListNode 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 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 list) + { + for (LinkedListNode 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 outBuffer, ReadOnlySpan 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 GetMultiCommitTarget() @@ -387,21 +509,203 @@ namespace LibHac.Fs.Impl cacheAccessor.Purge(_fileSystem); } - public void NotifyCloseFile(FileAccessor file) + internal void NotifyCloseFile(FileAccessor file) { using ScopedLock lk = ScopedLock.Lock(ref _openListLock); Remove(_openFiles, file); } - public void NotifyCloseDirectory(DirectoryAccessor directory) + internal void NotifyCloseDirectory(DirectoryAccessor directory) { using ScopedLock lk = ScopedLock.Lock(ref _openListLock); Remove(_openDirectories, directory); } + private static ReadOnlySpan LogFsModuleName => new[] { (byte)'$', (byte)'f', (byte)'s' }; // "$fs" + + private static ReadOnlySpan 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 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 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 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 LogCount => // "", count: " + new[] + { + (byte)'"', (byte)',', (byte)' ', (byte)'c', (byte)'o', (byte)'u', (byte)'n', (byte)'t', + (byte)':', (byte)' ' + }; + + public static ReadOnlySpan LogLineEnd => new[] { (byte)')', (byte)'\\', (byte)'n' }; // ")\n" + + public static ReadOnlySpan LogOrOperator => new[] { (byte)' ', (byte)'|', (byte)' ' }; // " | " + + private static ReadOnlySpan 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 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 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 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 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 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 list, OpenMode mask) + { + int count = 0; + + for (LinkedListNode file = list.First; file is not null; file = file.Next) + { + if ((file.Value.GetOpenMode() & mask) != 0) + count++; + } + + return count; + } + + Span stringBuffer = stackalloc byte[0xA0]; + Span 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 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 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 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(); + } + } } } } diff --git a/src/LibHac/Fs/Fsa/IAttributeFileSystem.cs b/src/LibHac/Fs/Fsa/IAttributeFileSystem.cs index 85f0bc39..95bb183c 100644 --- a/src/LibHac/Fs/Fsa/IAttributeFileSystem.cs +++ b/src/LibHac/Fs/Fsa/IAttributeFileSystem.cs @@ -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); } } diff --git a/src/LibHac/Fs/Fsa/IDirectory.cs b/src/LibHac/Fs/Fsa/IDirectory.cs index 3d5012fa..dd90bc37 100644 --- a/src/LibHac/Fs/Fsa/IDirectory.cs +++ b/src/LibHac/Fs/Fsa/IDirectory.cs @@ -44,12 +44,6 @@ namespace LibHac.Fs.Fsa protected abstract Result DoRead(out long entriesRead, Span 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() { } } } diff --git a/src/LibHac/Fs/Fsa/IFile.cs b/src/LibHac/Fs/Fsa/IFile.cs index af908b79..2660128c 100644 --- a/src/LibHac/Fs/Fsa/IFile.cs +++ b/src/LibHac/Fs/Fsa/IFile.cs @@ -219,12 +219,6 @@ namespace LibHac.Fs.Fsa protected abstract Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, ReadOnlySpan inBuffer); - protected virtual void Dispose(bool disposing) { } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } + public virtual void Dispose() { } } } diff --git a/src/LibHac/Fs/Fsa/IFileSystem.cs b/src/LibHac/Fs/Fsa/IFileSystem.cs index 9eb3f479..ad6fd1f0 100644 --- a/src/LibHac/Fs/Fsa/IFileSystem.cs +++ b/src/LibHac/Fs/Fsa/IFileSystem.cs @@ -17,23 +17,16 @@ namespace LibHac.Fs.Fsa /// The initial size of the created file. /// Flags to control how the file is created. /// Should usually be - /// The of the requested operation. - /// - /// The following codes may be returned under certain conditions: - /// - /// The parent directory of the specified path does not exist: - /// Specified path already exists as either a file or directory: - /// Insufficient free space to create the file: - /// - public Result CreateFile(U8Span path, long size, CreateFileOptions option) + /// : The operation was successful.
+ /// : The parent directory of the specified path does not exist.
+ /// : Specified path already exists as either a file or directory.
+ /// : Insufficient free space to create the file.
+ 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); } /// @@ -42,175 +35,127 @@ namespace LibHac.Fs.Fsa /// The full path of the file to create. /// The initial size of the created file. /// Should usually be - /// The of the requested operation. - /// - /// The following codes may be returned under certain conditions: - /// - /// The parent directory of the specified path does not exist: - /// Specified path already exists as either a file or directory: - /// Insufficient free space to create the file: - /// - public Result CreateFile(U8Span path, long size) + /// : The operation was successful.
+ /// : The parent directory of the specified path does not exist.
+ /// : Specified path already exists as either a file or directory.
+ /// : Insufficient free space to create the file.
+ 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); } /// /// Deletes the specified file. /// /// The full path of the file to delete. - /// The of the requested operation. - /// - /// The following codes may be returned under certain conditions: - /// - /// The specified path does not exist or is a directory: - /// - public Result DeleteFile(U8Span path) + /// : The operation was successful.
+ /// : The specified path does not exist or is a directory.
+ public Result DeleteFile(in Path path) { - if (path.IsNull()) - return ResultFs.NullptrArgument.Log(); - - return DoDeleteFile(path); + return DoDeleteFile(in path); } /// /// Creates all directories and subdirectories in the specified path unless they already exist. /// /// The full path of the directory to create. - /// The of the requested operation. - /// - /// The following codes may be returned under certain conditions: - /// - /// The parent directory of the specified path does not exist: - /// Specified path already exists as either a file or directory: - /// Insufficient free space to create the directory: - /// - public Result CreateDirectory(U8Span path) + /// : The operation was successful.
+ /// : The parent directory of the specified path does not exist.
+ /// : Specified path already exists as either a file or directory.
+ /// : Insufficient free space to create the directory.
+ public Result CreateDirectory(in Path path) { - if (path.IsNull()) - return ResultFs.NullptrArgument.Log(); - - return DoCreateDirectory(path); + return DoCreateDirectory(in path); } /// /// Deletes the specified directory. /// /// The full path of the directory to delete. - /// The of the requested operation. - /// - /// The following codes may be returned under certain conditions: - /// - /// The specified path does not exist or is a file: - /// The specified directory is not empty: - /// - public Result DeleteDirectory(U8Span path) + /// : The operation was successful.
+ /// : The specified path does not exist or is a file.
+ /// : The specified directory is not empty.
+ public Result DeleteDirectory(in Path path) { - if (path.IsNull()) - return ResultFs.NullptrArgument.Log(); - - return DoDeleteDirectory(path); + return DoDeleteDirectory(in path); } /// /// Deletes the specified directory and any subdirectories and files in the directory. /// /// The full path of the directory to delete. - /// The of the requested operation. - /// - /// The following codes may be returned under certain conditions: - /// - /// The specified path does not exist or is a file: - /// - public Result DeleteDirectoryRecursively(U8Span path) + /// : The operation was successful.
+ /// : The specified path does not exist or is a file.
+ public Result DeleteDirectoryRecursively(in Path path) { - if (path.IsNull()) - return ResultFs.NullptrArgument.Log(); - - return DoDeleteDirectoryRecursively(path); + return DoDeleteDirectoryRecursively(in path); } /// /// Deletes any subdirectories and files in the specified directory. /// /// The full path of the directory to clean. - /// The of the requested operation. - /// - /// The following codes may be returned under certain conditions: - /// - /// The specified path does not exist or is a file: - /// - public Result CleanDirectoryRecursively(U8Span path) + /// : The operation was successful.
+ /// : The specified path does not exist or is a file.
+ public Result CleanDirectoryRecursively(in Path path) { - if (path.IsNull()) - return ResultFs.NullptrArgument.Log(); - - return DoCleanDirectoryRecursively(path); + return DoCleanDirectoryRecursively(in path); } /// /// Renames or moves a file to a new location. /// - /// The full path of the file to rename. + /// The current full path of the file to rename. /// The new full path of the file. - /// The of the requested operation. + /// : The operation was successful.
+ /// : does not exist or is a directory.
+ /// : 's parent directory does not exist.
+ /// : already exists as either a file or directory.
/// - /// If and are the same, this function does nothing and returns successfully. - /// The following codes may be returned under certain conditions: - /// - /// does not exist or is a directory: - /// 's parent directory does not exist: - /// already exists as either a file or directory: + /// If and are the same, this function does nothing and returns successfully. /// - 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); } /// /// Renames or moves a directory to a new location. /// - /// The full path of the directory to rename. + /// The full path of the directory to rename. /// The new full path of the directory. - /// The of the requested operation. + /// : The operation was successful.
+ /// : does not exist or is a file.
+ /// : 's parent directory does not exist.
+ /// : already exists as either a file or directory.
+ /// : Either or is a subpath of the other.
/// - /// If and are the same, this function does nothing and returns . - /// The following codes may be returned under certain conditions: - /// - /// does not exist or is a file: - /// 's parent directory does not exist: - /// already exists as either a file or directory: - /// Either or is a subpath of the other: + /// If and are the same, this function does nothing and returns . /// - 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); } /// /// Determines whether the specified path is a file or directory, or does not exist. /// - /// If the operation returns successfully, the of the file. + /// If the operation returns successfully, contains the of the file. /// The full path to check. - /// The of the requested operation. + /// : The operation was successful.
+ /// : The specified path does not exist.
+ public Result GetEntryType(out DirectoryEntryType entryType, in Path path) + { + return DoGetEntryType(out entryType, in path); + } + + /// + /// Determines whether the specified path is a file or directory, or does not exist. + /// + /// If the operation returns successfully, contains the of the file. + /// The full path to check. + /// : The operation was successful.
+ /// : The specified path does not exist.
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); } /// @@ -227,14 +176,9 @@ namespace LibHac.Fs.Fsa /// If the operation returns successfully, the amount of free space available on the drive, in bytes. /// The path of the drive to query. Unused in almost all cases. /// The of the requested operation. - 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); } /// @@ -243,14 +187,9 @@ namespace LibHac.Fs.Fsa /// If the operation returns successfully, the total size of the drive, in bytes. /// The path of the drive to query. Unused in almost all cases. /// The of the requested operation. - 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); } /// @@ -260,33 +199,44 @@ namespace LibHac.Fs.Fsa /// An instance for the specified path. /// The full path of the file to open. /// Specifies the access permissions of the created . - /// The of the requested operation. - /// - /// The following codes may be returned under certain conditions: - /// - /// The specified path does not exist or is a directory: - /// + /// : The operation was successful.
+ /// : The specified path does not exist or is a directory.
+ /// : When opening as , + /// the file is already opened as .
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); + } + + /// + /// Opens an instance for the specified path. + /// + /// If the operation returns successfully, + /// An instance for the specified path. + /// The full path of the file to open. + /// Specifies the access permissions of the created . + /// : The operation was successful.
+ /// : The specified path does not exist or is a directory.
+ /// : When opening as , + /// the file is already opened as .
+ 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); } /// @@ -296,33 +246,18 @@ namespace LibHac.Fs.Fsa /// An instance for the specified directory. /// The directory's full path. /// Specifies which sub-entries should be enumerated. - /// The of the requested operation. - /// - /// The following codes may be returned under certain conditions: - /// - /// The specified path does not exist or is a file: - /// - public Result OpenDirectory(out IDirectory directory, U8Span path, OpenDirectoryMode mode) + /// : The operation was successful.
+ /// : The specified path does not exist or is a file.
+ 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); } /// @@ -344,21 +279,11 @@ namespace LibHac.Fs.Fsa /// If the operation returns successfully, the timestamps for the specified file or directory. /// These value are expressed as Unix timestamps. /// The path of the file or directory. - /// The of the requested operation. - /// - /// The following codes may be returned under certain conditions: - /// - /// The specified path does not exist: - /// - public Result GetFileTimeStampRaw(out FileTimeStampRaw timeStamp, U8Span path) + /// : The operation was successful.
+ /// : The specified path does not exist.
+ 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); } /// @@ -373,60 +298,51 @@ namespace LibHac.Fs.Fsa /// The type of query to perform. /// The full path of the file to query. /// The of the requested operation. - public Result QueryEntry(Span outBuffer, ReadOnlySpan inBuffer, QueryId queryId, U8Span path) + public Result QueryEntry(Span outBuffer, ReadOnlySpan 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 outBuffer, ReadOnlySpan 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() { } } /// @@ -460,6 +376,9 @@ namespace LibHac.Fs.Fsa /// Turns a folder in a into a concatenation file by /// setting the directory's archive flag. /// - MakeConcatFile = 0 + SetConcatenationFileAttribute = 0, + UpdateMac = 1, + IsSignedSystemPartitionOnSdCardValid = 2, + QueryUnpreparedFileInformation = 3 } } diff --git a/src/LibHac/Fs/Fsa/MountUtility.cs b/src/LibHac/Fs/Fsa/MountUtility.cs index c3b02763..8b836121 100644 --- a/src/LibHac/Fs/Fsa/MountUtility.cs +++ b/src/LibHac/Fs/Fsa/MountUtility.cs @@ -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; diff --git a/src/LibHac/Fs/Fsa/Registrar.cs b/src/LibHac/Fs/Fsa/Registrar.cs index f0f1e0f6..eef296b3 100644 --- a/src/LibHac/Fs/Fsa/Registrar.cs +++ b/src/LibHac/Fs/Fsa/Registrar.cs @@ -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); diff --git a/src/LibHac/Fs/Fsa/UserFileSystem.cs b/src/LibHac/Fs/Fsa/UserFileSystem.cs index dab41e8f..945e4899 100644 --- a/src/LibHac/Fs/Fsa/UserFileSystem.cs +++ b/src/LibHac/Fs/Fsa/UserFileSystem.cs @@ -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; diff --git a/src/LibHac/Fs/Fsa/UserFileSystemPrivate.cs b/src/LibHac/Fs/Fsa/UserFileSystemPrivate.cs index f32afd5a..af078567 100644 --- a/src/LibHac/Fs/Fsa/UserFileSystemPrivate.cs +++ b/src/LibHac/Fs/Fsa/UserFileSystemPrivate.cs @@ -73,7 +73,7 @@ namespace LibHac.Fs.Fsa fs.Impl.AbortIfNeeded(rc); if (rc.IsFailure()) return rc; - rc = fileSystem.QueryEntry(Span.Empty, ReadOnlySpan.Empty, QueryId.MakeConcatFile, subPath); + rc = fileSystem.QueryEntry(Span.Empty, ReadOnlySpan.Empty, QueryId.SetConcatenationFileAttribute, subPath); fs.Impl.AbortIfNeeded(rc); return rc; } diff --git a/src/LibHac/Fs/Impl/DirectoryServiceObjectAdapter.cs b/src/LibHac/Fs/Impl/DirectoryServiceObjectAdapter.cs deleted file mode 100644 index e9312614..00000000 --- a/src/LibHac/Fs/Impl/DirectoryServiceObjectAdapter.cs +++ /dev/null @@ -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 -{ - /// - /// An adapter for using an service object as an . Used - /// when receiving a Horizon IPC directory object so it can be used as an locally. - /// - internal class DirectoryServiceObjectAdapter : IDirectory - { - private ReferenceCountedDisposable BaseDirectory { get; } - - public DirectoryServiceObjectAdapter(ReferenceCountedDisposable baseDirectory) - { - BaseDirectory = baseDirectory.AddReference(); - } - - protected override Result DoRead(out long entriesRead, Span entryBuffer) - { - Span buffer = MemoryMarshal.Cast(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); - } - } -} diff --git a/src/LibHac/Fs/Impl/FileServiceObjectAdapter.cs b/src/LibHac/Fs/Impl/FileServiceObjectAdapter.cs deleted file mode 100644 index 3781c2ae..00000000 --- a/src/LibHac/Fs/Impl/FileServiceObjectAdapter.cs +++ /dev/null @@ -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 -{ - /// - /// An adapter for using an service object as an . Used - /// when receiving a Horizon IPC file object so it can be used as an locally. - /// - internal class FileServiceObjectAdapter : IFile - { - private ReferenceCountedDisposable BaseFile { get; } - - public FileServiceObjectAdapter(ReferenceCountedDisposable baseFile) - { - BaseFile = baseFile.AddReference(); - } - - protected override Result DoRead(out long bytesRead, long offset, Span destination, in ReadOption option) - { - return BaseFile.Target.Read(out bytesRead, offset, new OutBuffer(destination), destination.Length, option); - } - - protected override Result DoWrite(long offset, ReadOnlySpan 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 outBuffer, OperationId operationId, long offset, long size, - ReadOnlySpan inBuffer) - { - switch (operationId) - { - case OperationId.InvalidateCache: - return BaseFile.Target.OperateRange(out _, (int)OperationId.InvalidateCache, offset, size); - case OperationId.QueryRange: - if (outBuffer.Length != Unsafe.SizeOf()) - return ResultFs.InvalidSize.Log(); - - ref QueryRangeInfo info = ref SpanHelpers.AsStruct(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); - } - } -} \ No newline at end of file diff --git a/src/LibHac/Fs/Impl/FileSystemServiceObjectAdapter.cs b/src/LibHac/Fs/Impl/FileSystemServiceObjectAdapter.cs deleted file mode 100644 index 6901fe51..00000000 --- a/src/LibHac/Fs/Impl/FileSystemServiceObjectAdapter.cs +++ /dev/null @@ -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 -{ - /// - /// An adapter for using an service object as an . Used - /// when receiving a Horizon IPC file system object so it can be used as an locally. - /// - internal class FileSystemServiceObjectAdapter : IFileSystem, IMultiCommitTarget - { - private ReferenceCountedDisposable BaseFs { get; } - - public FileSystemServiceObjectAdapter(ReferenceCountedDisposable 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(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 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 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 outBuffer, ReadOnlySpan 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 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 outPath = SpanHelpers.AsByteSpan(ref sfPath); - - // Copy and null terminate - StringUtils.Copy(outPath, path); - outPath[Unsafe.SizeOf() - 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); - } - } -} diff --git a/src/LibHac/Fs/InMemoryFileSystem.cs b/src/LibHac/Fs/InMemoryFileSystem.cs index 7fa0fd7c..cacf15e6 100644 --- a/src/LibHac/Fs/InMemoryFileSystem.cs +++ b/src/LibHac/Fs/InMemoryFileSystem.cs @@ -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); } diff --git a/src/LibHac/Fs/ResultHandlingUtility.cs b/src/LibHac/Fs/ResultHandlingUtility.cs index c880a4a3..753cf9b8 100644 --- a/src/LibHac/Fs/ResultHandlingUtility.cs +++ b/src/LibHac/Fs/ResultHandlingUtility.cs @@ -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 } diff --git a/src/LibHac/Fs/Shim/Bis.cs b/src/LibHac/Fs/Shim/Bis.cs index b7c6e3dc..3387f294 100644 --- a/src/LibHac/Fs/Shim/Bis.cs +++ b/src/LibHac/Fs/Shim/Bis.cs @@ -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 fsProxy = fs.Impl.GetFileSystemProxyServiceObject(); rc = fsProxy.Target.SetBisRootForHost(partitionId, in sfPath); - fs.Impl.LogErrorMessage(rc); + fs.Impl.LogResultErrorMessage(rc); return rc; } diff --git a/src/LibHac/Fs/Shim/FileSystemServiceObjectAdapter.cs b/src/LibHac/Fs/Shim/FileSystemServiceObjectAdapter.cs new file mode 100644 index 00000000..d127aec4 --- /dev/null +++ b/src/LibHac/Fs/Shim/FileSystemServiceObjectAdapter.cs @@ -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 +{ + /// + /// An adapter for using an service object as an . Used + /// when receiving a Horizon IPC file object so it can be used as an locally. + /// + /// Based on FS 12.0.3 (nnSdk 12.3.1) + internal class FileServiceObjectAdapter : IFile + { + private ReferenceCountedDisposable BaseFile { get; } + + public FileServiceObjectAdapter(ReferenceCountedDisposable baseFile) + { + BaseFile = baseFile.AddReference(); + } + + public override void Dispose() + { + BaseFile?.Dispose(); + + base.Dispose(); + } + + protected override Result DoRead(out long bytesRead, long offset, Span destination, in ReadOption option) + { + return BaseFile.Target.Read(out bytesRead, offset, new OutBuffer(destination), destination.Length, option); + } + + protected override Result DoWrite(long offset, ReadOnlySpan 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 outBuffer, OperationId operationId, long offset, long size, + ReadOnlySpan inBuffer) + { + switch (operationId) + { + case OperationId.InvalidateCache: + return BaseFile.Target.OperateRange(out _, (int)OperationId.InvalidateCache, offset, size); + case OperationId.QueryRange: + if (outBuffer.Length != Unsafe.SizeOf()) + return ResultFs.InvalidSize.Log(); + + ref QueryRangeInfo info = ref SpanHelpers.AsStruct(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); + } + } + } + + /// + /// An adapter for using an service object as an . Used + /// when receiving a Horizon IPC directory object so it can be used as an locally. + /// + /// Based on FS 12.0.3 (nnSdk 12.3.1) + internal class DirectoryServiceObjectAdapter : IDirectory + { + private ReferenceCountedDisposable BaseDirectory { get; } + + public DirectoryServiceObjectAdapter(ReferenceCountedDisposable baseDirectory) + { + BaseDirectory = baseDirectory.AddReference(); + } + + public override void Dispose() + { + BaseDirectory?.Dispose(); + + base.Dispose(); + } + + protected override Result DoRead(out long entriesRead, Span entryBuffer) + { + Span buffer = MemoryMarshal.Cast(entryBuffer); + return BaseDirectory.Target.Read(out entriesRead, new OutBuffer(buffer)); + } + + protected override Result DoGetEntryCount(out long entryCount) + { + return BaseDirectory.Target.GetEntryCount(out entryCount); + } + } + + /// + /// An adapter for using an service object as an . Used + /// when receiving a Horizon IPC file system object so it can be used as an locally. + /// + /// Based on FS 12.0.3 (nnSdk 12.3.1) + internal class FileSystemServiceObjectAdapter : IFileSystem, IMultiCommitTarget + { + private ReferenceCountedDisposable 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 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(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 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 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 outBuffer, ReadOnlySpan 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 GetFileSystem() + { + return BaseFs.AddReference(); + } + + public ReferenceCountedDisposable GetMultiCommitTarget() + { + return GetFileSystem(); + } + } +} diff --git a/src/LibHac/Fs/Shim/GameCard.cs b/src/LibHac/Fs/Shim/GameCard.cs index 2ced0cf8..0905545d 100644 --- a/src/LibHac/Fs/Shim/GameCard.cs +++ b/src/LibHac/Fs/Shim/GameCard.cs @@ -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; diff --git a/src/LibHac/Fs/Shim/Host.cs b/src/LibHac/Fs/Shim/Host.cs index 0695365c..b4ee10e4 100644 --- a/src/LibHac/Fs/Shim/Host.cs +++ b/src/LibHac/Fs/Shim/Host.cs @@ -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()); } } diff --git a/src/LibHac/Fs/Shim/SaveDataManagement.cs b/src/LibHac/Fs/Shim/SaveDataManagement.cs index df5a88ed..813bdb7f 100644 --- a/src/LibHac/Fs/Shim/SaveDataManagement.cs +++ b/src/LibHac/Fs/Shim/SaveDataManagement.cs @@ -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()); } diff --git a/src/LibHac/Fs/Shim/SdCard.cs b/src/LibHac/Fs/Shim/SdCard.cs index cc8e0036..5640ce45 100644 --- a/src/LibHac/Fs/Shim/SdCard.cs +++ b/src/LibHac/Fs/Shim/SdCard.cs @@ -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 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; diff --git a/src/LibHac/FsSrv/BaseFileSystemService.cs b/src/LibHac/FsSrv/BaseFileSystemService.cs index 2ae389a8..0b0e5fe0 100644 --- a/src/LibHac/FsSrv/BaseFileSystemService.cs +++ b/src/LibHac/FsSrv/BaseFileSystemService.cs @@ -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 fileSystem, in FspPath rootPath, + public Result OpenBisFileSystem(out ReferenceCountedDisposable 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 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 baseFileSystem = null; + ReferenceCountedDisposable 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; } diff --git a/src/LibHac/FsSrv/BaseFileSystemServiceImpl.cs b/src/LibHac/FsSrv/BaseFileSystemServiceImpl.cs index fad27a33..265a441a 100644 --- a/src/LibHac/FsSrv/BaseFileSystemServiceImpl.cs +++ b/src/LibHac/FsSrv/BaseFileSystemServiceImpl.cs @@ -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 fileSystem, U8Span rootPath, + public Result OpenBisFileSystem(out ReferenceCountedDisposable fileSystem, BisPartitionId partitionId) { - return OpenBisFileSystem(out fileSystem, rootPath, partitionId, false); + return OpenBisFileSystem(out fileSystem, partitionId, false); } - public Result OpenBisFileSystem(out ReferenceCountedDisposable fileSystem, U8Span rootPath, + public Result OpenBisFileSystem(out ReferenceCountedDisposable 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(fs); return Result.Success; } @@ -95,14 +91,7 @@ namespace LibHac.FsSrv public Result OpenSdCardProxyFileSystem(out ReferenceCountedDisposable 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(fs); - return Result.Success; + return _config.SdCardFileSystemCreator.Create(out fileSystem, openCaseSensitive); } public Result FormatSdCardProxyFileSystem() diff --git a/src/LibHac/FsSrv/FileSystemProxyCoreImpl.cs b/src/LibHac/FsSrv/FileSystemProxyCoreImpl.cs index 28509a22..9dcad3d4 100644 --- a/src/LibHac/FsSrv/FileSystemProxyCoreImpl.cs +++ b/src/LibHac/FsSrv/FileSystemProxyCoreImpl.cs @@ -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 fileSystem, + public Result OpenCustomStorageFileSystem(out ReferenceCountedDisposable outFileSystem, CustomStorageId storageId) { - UnsafeHelpers.SkipParamInit(out fileSystem); + UnsafeHelpers.SkipParamInit(out outFileSystem); + ReferenceCountedDisposable fileSystem = null; ReferenceCountedDisposable tempFs = null; - ReferenceCountedDisposable encryptedFs = null; try { - Span path = stackalloc byte[0x40]; + const int pathBufferLength = 0x40; - switch (storageId) + // Hack around error CS8350. + Span buffer = stackalloc byte[pathBufferLength]; + ref byte bufferRef = ref MemoryMarshal.GetReference(buffer); + Span 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 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 sharedHostFs = null; - ReferenceCountedDisposable subDirFs = null; - - try - { - sharedHostFs = new ReferenceCountedDisposable(hostFs); - - if (path.IsEmpty()) + else if (storageId == CustomStorageId.SdCard) { - ReadOnlySpan 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 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 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; diff --git a/src/LibHac/FsSrv/FileSystemProxyImpl.cs b/src/LibHac/FsSrv/FileSystemProxyImpl.cs index 5971771d..ee8812dd 100644 --- a/src/LibHac/FsSrv/FileSystemProxyImpl.cs +++ b/src/LibHac/FsSrv/FileSystemProxyImpl.cs @@ -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 hostFs = null; + ReferenceCountedDisposable 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; } diff --git a/src/LibHac/FsSrv/FileSystemServer.cs b/src/LibHac/FsSrv/FileSystemServer.cs index 8a6ed7cb..4b9f615d 100644 --- a/src/LibHac/FsSrv/FileSystemServer.cs +++ b/src/LibHac/FsSrv/FileSystemServer.cs @@ -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(); } } diff --git a/src/LibHac/FsSrv/FsCreator/EmulatedBisFileSystemCreator.cs b/src/LibHac/FsSrv/FsCreator/EmulatedBisFileSystemCreator.cs index 9d4d8328..6b77cb0b 100644 --- a/src/LibHac/FsSrv/FsCreator/EmulatedBisFileSystemCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/EmulatedBisFileSystemCreator.cs @@ -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 fileSystem, U8Span rootPath, - BisPartitionId partitionId, bool caseSensitive) + public Result Create(out ReferenceCountedDisposable 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 partitionFileSystem = null; ReferenceCountedDisposable sharedRootFs = null; @@ -115,17 +88,11 @@ namespace LibHac.FsSrv.FsCreator { sharedRootFs = new ReferenceCountedDisposable(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); } diff --git a/src/LibHac/FsSrv/FsCreator/EmulatedSdCardFileSystemCreator.cs b/src/LibHac/FsSrv/FsCreator/EmulatedSdCardFileSystemCreator.cs index 052fc865..1c2150e6 100644 --- a/src/LibHac/FsSrv/FsCreator/EmulatedSdCardFileSystemCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/EmulatedSdCardFileSystemCreator.cs @@ -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 _rootFileSystem; private string Path { get; } - private IFileSystem SdCardFileSystem { get; set; } + private ReferenceCountedDisposable _sdCardFileSystem; public EmulatedSdCardFileSystemCreator(EmulatedSdCard sdCard, IFileSystem rootFileSystem) { SdCard = sdCard; - RootFileSystem = rootFileSystem; + _rootFileSystem = new ReferenceCountedDisposable(rootFileSystem); } public EmulatedSdCardFileSystemCreator(EmulatedSdCard sdCard, IFileSystem rootFileSystem, string path) { SdCard = sdCard; - RootFileSystem = rootFileSystem; + _rootFileSystem = new ReferenceCountedDisposable(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 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 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) diff --git a/src/LibHac/FsSrv/FsCreator/EncryptedFileSystemCreator.cs b/src/LibHac/FsSrv/FsCreator/EncryptedFileSystemCreator.cs index 8a3a4083..638ca9f9 100644 --- a/src/LibHac/FsSrv/FsCreator/EncryptedFileSystemCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/EncryptedFileSystemCreator.cs @@ -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 encryptedFileSystem, ReferenceCountedDisposable baseFileSystem, - EncryptedFsKeyId keyId, in EncryptionSeed encryptionSeed) + public Result Create(out ReferenceCountedDisposable encryptedFileSystem, + ref ReferenceCountedDisposable 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(fs); return Result.Success; diff --git a/src/LibHac/FsSrv/FsCreator/IBuiltInStorageFileSystemCreator.cs b/src/LibHac/FsSrv/FsCreator/IBuiltInStorageFileSystemCreator.cs index dc7782df..c61326b8 100644 --- a/src/LibHac/FsSrv/FsCreator/IBuiltInStorageFileSystemCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/IBuiltInStorageFileSystemCreator.cs @@ -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 fileSystem, U8Span rootPath, BisPartitionId partitionId, bool caseSensitive); - Result CreateFatFileSystem(out IFileSystem fileSystem, BisPartitionId partitionId); - Result SetBisRootForHost(BisPartitionId partitionId, string rootPath); + Result Create(out ReferenceCountedDisposable fileSystem, BisPartitionId partitionId, bool caseSensitive); } } \ No newline at end of file diff --git a/src/LibHac/FsSrv/FsCreator/IEncryptedFileSystemCreator.cs b/src/LibHac/FsSrv/FsCreator/IEncryptedFileSystemCreator.cs index a8337b05..aaed2322 100644 --- a/src/LibHac/FsSrv/FsCreator/IEncryptedFileSystemCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/IEncryptedFileSystemCreator.cs @@ -5,8 +5,15 @@ namespace LibHac.FsSrv.FsCreator { public interface IEncryptedFileSystemCreator { + public enum KeyId + { + Save = 0, + Content = 1, + CustomStorage = 2 + } + Result Create(out ReferenceCountedDisposable encryptedFileSystem, - ReferenceCountedDisposable baseFileSystem, EncryptedFsKeyId keyId, + ref ReferenceCountedDisposable baseFileSystem, KeyId idIndex, in EncryptionSeed encryptionSeed); } diff --git a/src/LibHac/FsSrv/FsCreator/ISdCardProxyFileSystemCreator.cs b/src/LibHac/FsSrv/FsCreator/ISdCardProxyFileSystemCreator.cs index ecd20873..42acf186 100644 --- a/src/LibHac/FsSrv/FsCreator/ISdCardProxyFileSystemCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/ISdCardProxyFileSystemCreator.cs @@ -4,7 +4,7 @@ namespace LibHac.FsSrv.FsCreator { public interface ISdCardProxyFileSystemCreator { - Result Create(out IFileSystem fileSystem, bool isCaseSensitive); + Result Create(out ReferenceCountedDisposable outFileSystem, bool isCaseSensitive); /// /// Formats the SD card. diff --git a/src/LibHac/FsSrv/FsCreator/ISubDirectoryFileSystemCreator.cs b/src/LibHac/FsSrv/FsCreator/ISubDirectoryFileSystemCreator.cs index 9e4c4139..42fc19ef 100644 --- a/src/LibHac/FsSrv/FsCreator/ISubDirectoryFileSystemCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/ISubDirectoryFileSystemCreator.cs @@ -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 subDirFileSystem, ref ReferenceCountedDisposable baseFileSystem, U8Span path); - Result Create(out ReferenceCountedDisposable subDirFileSystem, ref ReferenceCountedDisposable baseFileSystem, U8Span path, bool preserveUnc); + Result Create(out ReferenceCountedDisposable subDirFileSystem, ref ReferenceCountedDisposable baseFileSystem, in Path path); } } \ No newline at end of file diff --git a/src/LibHac/FsSrv/FsCreator/ITargetManagerFileSystemCreator.cs b/src/LibHac/FsSrv/FsCreator/ITargetManagerFileSystemCreator.cs index 63c300e6..9e0b6102 100644 --- a/src/LibHac/FsSrv/FsCreator/ITargetManagerFileSystemCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/ITargetManagerFileSystemCreator.cs @@ -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 fileSystem, bool openCaseSensitive); - Result NormalizeCaseOfPath(out bool isSupported, Span path); + Result Create(out ReferenceCountedDisposable fileSystem, in Path rootPath, bool openCaseSensitive, bool ensureRootPathExists, Result pathNotFoundResult); + Result NormalizeCaseOfPath(out bool isSupported, ref Path path); } } \ No newline at end of file diff --git a/src/LibHac/FsSrv/FsCreator/SaveDataFileSystemCreator.cs b/src/LibHac/FsSrv/FsCreator/SaveDataFileSystemCreator.cs index fa8f1f9f..83754417 100644 --- a/src/LibHac/FsSrv/FsCreator/SaveDataFileSystemCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/SaveDataFileSystemCreator.cs @@ -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 saveImageName = stackalloc byte[0x12]; + // Hack around error CS8350. + Span buffer = stackalloc byte[0x12]; + ref byte bufferRef = ref MemoryMarshal.GetReference(buffer); + Span 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(); extraDataAccessor = saveFs.AddReference(); + saveImageName.Dispose(); return Result.Success; } finally diff --git a/src/LibHac/FsSrv/FsCreator/SubDirectoryFileSystemCreator.cs b/src/LibHac/FsSrv/FsCreator/SubDirectoryFileSystemCreator.cs index 16e2afa6..e09ceda0 100644 --- a/src/LibHac/FsSrv/FsCreator/SubDirectoryFileSystemCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/SubDirectoryFileSystemCreator.cs @@ -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 subDirFileSystem, - ref ReferenceCountedDisposable baseFileSystem, U8Span path) - { - return Create(out subDirFileSystem, ref baseFileSystem, path, false); - } - - public Result Create(out ReferenceCountedDisposable subDirFileSystem, - ref ReferenceCountedDisposable baseFileSystem, U8Span path, bool preserveUnc) + ref ReferenceCountedDisposable 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(subDir); + dir.Dispose(); - rc = subDirShared.Target.Initialize(path); - if (rc.IsFailure()) return rc; + ReferenceCountedDisposable subFs = null; + try + { + subFs = new ReferenceCountedDisposable( + new SubdirectoryFileSystem(ref baseFileSystem)); - subDirFileSystem = subDirShared.AddReference(); - return Result.Success; + rc = subFs.Target.Initialize(in path); + if (rc.IsFailure()) return rc; + + subDirFileSystem = subFs.AddReference(); + return Result.Success; + } + finally + { + subFs?.Dispose(); + } } } } diff --git a/src/LibHac/FsSrv/FsCreator/TargetManagerFileSystemCreator.cs b/src/LibHac/FsSrv/FsCreator/TargetManagerFileSystemCreator.cs index 653fbacb..beef48c8 100644 --- a/src/LibHac/FsSrv/FsCreator/TargetManagerFileSystemCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/TargetManagerFileSystemCreator.cs @@ -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 fileSystem, in Path rootPath, bool openCaseSensitive, bool ensureRootPathExists, Result pathNotFoundResult) { throw new NotImplementedException(); } - public Result Create(out ReferenceCountedDisposable fileSystem, bool openCaseSensitive) - { - throw new NotImplementedException(); - } - - public Result NormalizeCaseOfPath(out bool isSupported, Span path) + public Result NormalizeCaseOfPath(out bool isSupported, ref Path path) { throw new NotImplementedException(); } diff --git a/src/LibHac/FsSrv/Impl/AsynchronousAccessFileSystem.cs b/src/LibHac/FsSrv/Impl/AsynchronousAccessFileSystem.cs index 98283ad9..4ce75b8e 100644 --- a/src/LibHac/FsSrv/Impl/AsynchronousAccessFileSystem.cs +++ b/src/LibHac/FsSrv/Impl/AsynchronousAccessFileSystem.cs @@ -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); diff --git a/src/LibHac/FsSrv/Impl/DeepRetryFileSystem.cs b/src/LibHac/FsSrv/Impl/DeepRetryFileSystem.cs index 68cbefc6..bfcee098 100644 --- a/src/LibHac/FsSrv/Impl/DeepRetryFileSystem.cs +++ b/src/LibHac/FsSrv/Impl/DeepRetryFileSystem.cs @@ -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); diff --git a/src/LibHac/FsSrv/Impl/DirectoryInterfaceAdapter.cs b/src/LibHac/FsSrv/Impl/DirectoryInterfaceAdapter.cs deleted file mode 100644 index c3872548..00000000 --- a/src/LibHac/FsSrv/Impl/DirectoryInterfaceAdapter.cs +++ /dev/null @@ -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 ParentFs { get; } - private IDirectory BaseDirectory { get; } - - public DirectoryInterfaceAdapter(IDirectory baseDirectory, - ref ReferenceCountedDisposable parentFileSystem) - { - BaseDirectory = baseDirectory; - ParentFs = parentFileSystem; - parentFileSystem = null; - } - - public Result Read(out long entriesRead, OutBuffer entryBuffer) - { - const int maxTryCount = 2; - UnsafeHelpers.SkipParamInit(out entriesRead); - - Span entries = MemoryMarshal.Cast(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(); - } - } -} diff --git a/src/LibHac/FsSrv/Impl/FileInterfaceAdapter.cs b/src/LibHac/FsSrv/Impl/FileInterfaceAdapter.cs deleted file mode 100644 index 5196b0ef..00000000 --- a/src/LibHac/FsSrv/Impl/FileInterfaceAdapter.cs +++ /dev/null @@ -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 ParentFs { get; } - private IFile BaseFile { get; } - - public FileInterfaceAdapter(IFile baseFile, - ref ReferenceCountedDisposable 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.Empty, OperationId.InvalidateCache, offset, size, - ReadOnlySpan.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.Empty); - if (rc.IsFailure()) return rc; - - rangeInfo.Merge(in info); - } - - return Result.Success; - } - - public void Dispose() - { - BaseFile?.Dispose(); - ParentFs?.Dispose(); - } - } -} \ No newline at end of file diff --git a/src/LibHac/FsSrv/Impl/FileSystemInterfaceAdapter.cs b/src/LibHac/FsSrv/Impl/FileSystemInterfaceAdapter.cs index 5cbc915d..14a63df7 100644 --- a/src/LibHac/FsSrv/Impl/FileSystemInterfaceAdapter.cs +++ b/src/LibHac/FsSrv/Impl/FileSystemInterfaceAdapter.cs @@ -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 { + /// + /// Wraps an to allow interfacing with it via the interface over IPC. + /// + /// Based on FS 12.0.3 (nnSdk 12.3.1) + public class FileInterfaceAdapter : IFileSf + { + private ReferenceCountedDisposable _parentFs; + private IFile _baseFile; + private bool _allowAllOperations; + + public FileInterfaceAdapter(IFile baseFile, + ref ReferenceCountedDisposable 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.Empty); + if (rc.IsFailure()) return rc; + + rangeInfo.Merge(in info); + } + else if (operationId == (int)OperationId.InvalidateCache) + { + Result rc = _baseFile.OperateRange(Span.Empty, OperationId.InvalidateCache, offset, size, + ReadOnlySpan.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; + } + } + + /// + /// Wraps an to allow interfacing with it via the interface over IPC. + /// + /// Based on FS 12.0.3 (nnSdk 12.3.1) + public class DirectoryInterfaceAdapter : IDirectorySf + { + private ReferenceCountedDisposable _parentFs; + private IDirectory _baseDirectory; + + public DirectoryInterfaceAdapter(IDirectory baseDirectory, + ref ReferenceCountedDisposable 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 entries = MemoryMarshal.Cast(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; + } + } + + /// + /// Wraps an to allow interfacing with it via the interface over IPC. + /// All incoming paths are normalized before they are passed to the base . + /// + /// Based on FS 12.0.3 (nnSdk 12.3.1) public class FileSystemInterfaceAdapter : IFileSystemSf { - private ReferenceCountedDisposable BaseFileSystem { get; } - private bool IsHostFsRoot { get; } + private ReferenceCountedDisposable _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.WeakReference _selfReference; - /// - /// Initializes a new by creating - /// a new reference to . - /// - /// The base file system. - /// Does the base file system come from the root directory of a host file system? - private FileSystemInterfaceAdapter(ReferenceCountedDisposable fileSystem, - bool isHostFsRoot = false) - { - BaseFileSystem = fileSystem.AddReference(); - IsHostFsRoot = isHostFsRoot; - } - - /// - /// Initializes a new by moving the file system object. - /// Avoids allocations from incrementing and then decrementing the ref-count. - /// - /// The base file system. Will be null upon returning. - /// Does the base file system come from the root directory of a host file system? private FileSystemInterfaceAdapter(ref ReferenceCountedDisposable fileSystem, - bool isHostFsRoot = false) + bool allowAllOperations) { - BaseFileSystem = fileSystem; - fileSystem = null; - IsHostFsRoot = isHostFsRoot; + _baseFileSystem = Shared.Move(ref fileSystem); + _allowAllOperations = allowAllOperations; } - /// - /// Initializes a new , creating a copy of the input file system object. - /// - /// The base file system. - /// Does the base file system come from the root directory of a host file system? - public static ReferenceCountedDisposable CreateShared( - ReferenceCountedDisposable baseFileSystem, bool isHostFsRoot = false) + private FileSystemInterfaceAdapter(ref ReferenceCountedDisposable fileSystem, PathFlags flags, + bool allowAllOperations) { - var adapter = new FileSystemInterfaceAdapter(baseFileSystem, isHostFsRoot); - - return ReferenceCountedDisposable.Create(adapter, out adapter._selfReference); + _baseFileSystem = Shared.Move(ref fileSystem); + _pathFlags = flags; + _allowAllOperations = allowAllOperations; } - /// - /// Initializes a new cast to an - /// by moving the input file system object. Avoids allocations from incrementing and then decrementing the ref-count. - /// - /// The base file system. Will be null upon returning. - /// Does the base file system come from the root directory of a host file system? public static ReferenceCountedDisposable CreateShared( - ref ReferenceCountedDisposable baseFileSystem, bool isHostFsRoot = false) + ref ReferenceCountedDisposable baseFileSystem, bool allowAllOperations) { - var adapter = new FileSystemInterfaceAdapter(ref baseFileSystem, isHostFsRoot); - - return ReferenceCountedDisposable.Create(adapter, out adapter._selfReference); - } - - private static ReadOnlySpan RootDir => new[] { (byte)'/' }; - - public Result GetImpl(out ReferenceCountedDisposable 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(ref entryType); - - return BaseFileSystem.Target.GetEntryType(out type, new U8Span(normalizer.Path)); - } - - public Result OpenFile(out ReferenceCountedDisposable 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 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(adapter); - // Retry on ResultDataCorrupted - if (!ResultFs.DataCorrupted.Includes(rc)) - break; + adapter._selfReference = + new ReferenceCountedDisposable.WeakReference(sharedAdapter); + + return sharedAdapter.AddReference(); } - - if (rc.IsFailure()) return rc; - - ReferenceCountedDisposable selfReference = _selfReference.TryAddReference(); - var adapter = new FileInterfaceAdapter(fileInterface, ref selfReference); - file = new ReferenceCountedDisposable(adapter); - - return Result.Success; - } - - public Result OpenDirectory(out ReferenceCountedDisposable 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 selfReference = _selfReference.TryAddReference(); - var adapter = new DirectoryInterfaceAdapter(dirInterface, ref selfReference); - directory = new ReferenceCountedDisposable(adapter); - - return Result.Success; } - public Result Commit() + public static ReferenceCountedDisposable CreateShared( + ref ReferenceCountedDisposable baseFileSystem, PathFlags flags, bool allowAllOperations) { - return BaseFileSystem.Target.Commit(); - } + ReferenceCountedDisposable sharedAdapter = null; + try + { + var adapter = new FileSystemInterfaceAdapter(ref baseFileSystem, flags, allowAllOperations); + sharedAdapter = new ReferenceCountedDisposable(adapter); - public Result GetFreeSpaceSize(out long freeSpace, in Path path) - { - UnsafeHelpers.SkipParamInit(out freeSpace); + adapter._selfReference = + new ReferenceCountedDisposable.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 outBuffer, ReadOnlySpan inBuffer, int queryId, in Path path) - { - return BaseFileSystem.Target.QueryEntry(outBuffer, inBuffer, (QueryId)queryId, new U8Span(path.Str)); + return sharedAdapter.AddReference(); + } + finally + { + sharedAdapter?.Dispose(); + } } public void Dispose() { - BaseFileSystem?.Dispose(); + ReferenceCountedDisposable tempFs = Shared.Move(ref _baseFileSystem); + tempFs?.Dispose(); } - private PathNormalizer.Option GetPathNormalizerOption() + private static ReadOnlySpan 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 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 selfReference = _selfReference.AddReference(); + var adapter = new FileInterfaceAdapter(Shared.Move(ref fileInterface), ref selfReference, _allowAllOperations); + file = new ReferenceCountedDisposable(adapter); + + pathNormalized.Dispose(); + return Result.Success; + } + finally + { + fileInterface?.Dispose(); + } + } + + public Result OpenDirectory(out ReferenceCountedDisposable 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 selfReference = _selfReference.AddReference(); + var adapter = new DirectoryInterfaceAdapter(dirInterface, ref selfReference); + directory = new ReferenceCountedDisposable(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 fileSystem) + { + fileSystem = _baseFileSystem.AddReference(); + return Result.Success; } } } diff --git a/src/LibHac/FsSrv/Impl/IResultConvertFileSystem.cs b/src/LibHac/FsSrv/Impl/IResultConvertFileSystem.cs index f5f516ef..2900f20c 100644 --- a/src/LibHac/FsSrv/Impl/IResultConvertFileSystem.cs +++ b/src/LibHac/FsSrv/Impl/IResultConvertFileSystem.cs @@ -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 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 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 outBuffer, ReadOnlySpan 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)); } diff --git a/src/LibHac/FsSrv/Impl/LocationResolverSet.cs b/src/LibHac/FsSrv/Impl/LocationResolverSet.cs index 2c81d309..37f4f458 100644 --- a/src/LibHac/FsSrv/Impl/LocationResolverSet.cs +++ b/src/LibHac/FsSrv/Impl/LocationResolverSet.cs @@ -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 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(); + } + } + + /// + /// Manages resolving the location of NCAs via the lr service. + /// + /// Based on FS 12.0.3 (nnSdk 12.3.1) internal class LocationResolverSet : IDisposable { private const int LocationResolverCount = 5; + // Todo: Use Optional 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 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 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(); - } } } diff --git a/src/LibHac/FsSrv/Impl/MultiCommitManager.cs b/src/LibHac/FsSrv/Impl/MultiCommitManager.cs index 32320b21..955d7d3a 100644 --- a/src/LibHac/FsSrv/Impl/MultiCommitManager.cs +++ b/src/LibHac/FsSrv/Impl/MultiCommitManager.cs @@ -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(); + } + } + + /// + /// Manages atomically committing a group of file systems. + /// + /// + /// The commit process is as follows:
+ /// 1. Create a commit context file that tracks the progress of the commit in case it is interrupted.
+ /// 2. Provisionally commit each file system individually. If any fail, rollback the file systems that were provisionally committed.
+ /// 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.
+ /// 4. Fully commit each file system individually.
+ /// 5. Delete the commit context file.
+ ///
+ /// 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.
+ ///
+ /// Based on FS 12.0.3 (nnSdk 12.3.1) + ///
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 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 _multiCommitInterface; + private readonly ReferenceCountedDisposable[] _fileSystems; + private int _fileSystemCount; + private long _counter; - private ReferenceCountedDisposable MultiCommitInterface { get; } + // Extra field used in LibHac + private readonly FileSystemServer _fsServer; + private ref MultiCommitManagerGlobals Globals => ref _fsServer.Globals.MultiCommitManager; - private List> FileSystems { get; } = - new List>(MaxFileSystemCount); - - private long Counter { get; set; } - private HorizonClient Hos { get; } - - public MultiCommitManager( - ref ReferenceCountedDisposable multiCommitInterface, - HorizonClient client) + public MultiCommitManager(FileSystemServer fsServer, ref ReferenceCountedDisposable multiCommitInterface) { - Hos = client; - MultiCommitInterface = Shared.Move(ref multiCommitInterface); + _fsServer = fsServer; + + _multiCommitInterface = Shared.Move(ref multiCommitInterface); + _fileSystems = new ReferenceCountedDisposable[MaxFileSystemCount]; + _fileSystemCount = 0; + _counter = 0; } - public static ReferenceCountedDisposable CreateShared( - ref ReferenceCountedDisposable multiCommitInterface, - HorizonClient client) + public static ReferenceCountedDisposable CreateShared(FileSystemServer fsServer, + ref ReferenceCountedDisposable multiCommitInterface) { - var manager = new MultiCommitManager(ref multiCommitInterface, client); + var manager = new MultiCommitManager(fsServer, ref multiCommitInterface); return new ReferenceCountedDisposable(manager); } public void Dispose() { - foreach (ReferenceCountedDisposable fs in FileSystems) + foreach (ReferenceCountedDisposable fs in _fileSystems) { - fs.Dispose(); + fs?.Dispose(); } + + _multiCommitInterface?.Dispose(); } /// @@ -71,20 +102,26 @@ namespace LibHac.FsSrv.Impl /// The of the operation. private Result EnsureSaveDataForContext() { - Result rc = MultiCommitInterface.Target.OpenMultiCommitContext( - out ReferenceCountedDisposable contextFs); - - if (rc.IsFailure()) + ReferenceCountedDisposable 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; } /// @@ -97,7 +134,7 @@ namespace LibHac.FsSrv.Impl /// : The provided file system has already been added. public Result Add(ReferenceCountedDisposable fileSystem) { - if (FileSystems.Count >= MaxFileSystemCount) + if (_fileSystemCount >= MaxFileSystemCount) return ResultFs.MultiCommitFileSystemLimit.Log(); ReferenceCountedDisposable 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 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 /// The of the operation. 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 /// The of the operation. public Result Commit() { - lock (Locker) + using ScopedLock scopedLock = ScopedLock.Lock(ref Globals.MultiCommitMutex); + + ReferenceCountedDisposable contextFs = null; + try { Result rc = EnsureSaveDataForContext(); if (rc.IsFailure()) return rc; - ReferenceCountedDisposable 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 /// The of the operation. 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 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. /// + /// The that contains the save data to recover. /// The core interface used for multi-commits. /// The save data service. /// The of the operation.
/// : The recovery was successful or there was no multi-commit to recover.
- public static Result Recover(ISaveDataMultiCommitCoreInterface multiCommitInterface, + public static Result Recover(FileSystemServer fsServer, ISaveDataMultiCommitCoreInterface multiCommitInterface, SaveDataFileSystemServiceImpl saveService) { - lock (Locker) - { - bool needsRecover = true; - ReferenceCountedDisposable fileSystem = null; + ref MultiCommitManagerGlobals globals = ref fsServer.Globals.MultiCommitManager; + using ScopedLock scopedLock = ScopedLock.Lock(ref globals.MultiCommitMutex); - try + bool needsRecovery = true; + ReferenceCountedDisposable 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(); } /// /// Creates and writes the initial commit context to a file. /// - /// The counter. + /// The counter. /// The number of file systems being committed. /// The of the operation. - 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; } /// @@ -584,11 +656,15 @@ namespace LibHac.FsSrv.Impl /// The of the operation. 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 /// The of the operation. 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; - } } } } diff --git a/src/LibHac/FsSrv/Impl/OpenCountFileSystem.cs b/src/LibHac/FsSrv/Impl/OpenCountFileSystem.cs index 080c84b9..bfd4c76a 100644 --- a/src/LibHac/FsSrv/Impl/OpenCountFileSystem.cs +++ b/src/LibHac/FsSrv/Impl/OpenCountFileSystem.cs @@ -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(); } } } diff --git a/src/LibHac/FsSrv/Impl/PathNormalizer.cs b/src/LibHac/FsSrv/Impl/PathNormalizer.cs deleted file mode 100644 index ffc33d54..00000000 --- a/src/LibHac/FsSrv/Impl/PathNormalizer.cs +++ /dev/null @@ -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.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.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.Shared.Return(buffer); - } - } - - return Result.Success; - } - - [Flags] - public enum Option - { - None = 0, - PreserveUnc = (1 << 0), - PreserveTrailingSeparator = (1 << 1), - HasMountName = (1 << 2), - AcceptEmpty = (1 << 3) - } - } -} diff --git a/src/LibHac/FsSrv/Impl/SaveDataResultConvertFileSystem.cs b/src/LibHac/FsSrv/Impl/SaveDataResultConvertFileSystem.cs index 0b2741f9..6ff3f2ca 100644 --- a/src/LibHac/FsSrv/Impl/SaveDataResultConvertFileSystem.cs +++ b/src/LibHac/FsSrv/Impl/SaveDataResultConvertFileSystem.cs @@ -176,7 +176,7 @@ namespace LibHac.FsSrv.Impl return new ReferenceCountedDisposable(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); diff --git a/src/LibHac/FsSrv/Impl/Utility.cs b/src/LibHac/FsSrv/Impl/Utility.cs index 2526629d..352d9f89 100644 --- a/src/LibHac/FsSrv/Impl/Utility.cs +++ b/src/LibHac/FsSrv/Impl/Utility.cs @@ -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 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 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 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.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 subDirFileSystem, - ref ReferenceCountedDisposable baseFileSystem, U8Span subPath, bool preserveUnc = false) + ref ReferenceCountedDisposable 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(fs)) { - rc = subDirFs.Target.Initialize(subPath); + rc = subDirFs.Target.Initialize(in rootPath); if (rc.IsFailure()) return rc; subDirFileSystem = subDirFs.AddReference(); @@ -144,22 +44,22 @@ namespace LibHac.FsSrv.Impl } public static Result WrapSubDirectory(out ReferenceCountedDisposable fileSystem, - ref ReferenceCountedDisposable baseFileSystem, U8Span path, bool createIfMissing) + ref ReferenceCountedDisposable 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 FileSystemRootPath => // / - new[] - { - (byte) '/' - }; } } diff --git a/src/LibHac/FsSrv/NcaFileSystemService.cs b/src/LibHac/FsSrv/NcaFileSystemService.cs index 121661b8..cd8aeb50 100644 --- a/src/LibHac/FsSrv/NcaFileSystemService.cs +++ b/src/LibHac/FsSrv/NcaFileSystemService.cs @@ -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.WeakReference SelfReference { get; set; } private NcaFileSystemServiceImpl ServiceImpl { get; } private ulong ProcessId { get; } - private SemaphoreAdaptor AocMountCountSemaphore { get; } - private SemaphoreAdaptor RomMountCountSemaphore { get; } + private SemaphoreAdapter AocMountCountSemaphore { get; } + private SemaphoreAdapter RomMountCountSemaphore { get; } private NcaFileSystemService(NcaFileSystemServiceImpl serviceImpl, ulong processId) { ServiceImpl = serviceImpl; ProcessId = processId; - AocMountCountSemaphore = new SemaphoreAdaptor(AocSemaphoreCount, AocSemaphoreCount); - RomMountCountSemaphore = new SemaphoreAdaptor(RomSemaphoreCount, RomSemaphoreCount); + AocMountCountSemaphore = new SemaphoreAdapter(AocSemaphoreCount, AocSemaphoreCount); + RomMountCountSemaphore = new SemaphoreAdapter(RomSemaphoreCount, RomSemaphoreCount); } public static ReferenceCountedDisposable CreateShared( @@ -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 tempFileSystem = null; ReferenceCountedDisposable 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(); 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 fileSystem, in FspPath path, + public Result OpenFileSystemWithId(out ReferenceCountedDisposable 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 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 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 fileSystem, out bool isHostFs, + private Result OpenDataFileSystemCore(out ReferenceCountedDisposable 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 tempFileSystem = null; + ReferenceCountedDisposable 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 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 storage, out Hash ncaHeaderDigest, ulong id, StorageId storageId) { diff --git a/src/LibHac/FsSrv/NcaFileSystemServiceImpl.cs b/src/LibHac/FsSrv/NcaFileSystemServiceImpl.cs index 57c61849..e4e95d53 100644 --- a/src/LibHac/FsSrv/NcaFileSystemServiceImpl.cs +++ b/src/LibHac/FsSrv/NcaFileSystemServiceImpl.cs @@ -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 fileSystem, U8Span path, - FileSystemProxyType type, ulong id) + public Result OpenFileSystem(out ReferenceCountedDisposable fileSystem, in Path path, + FileSystemProxyType type, ulong id, bool isDirectory) { - return OpenFileSystem(out fileSystem, out Unsafe.NullRef(), path, type, false, id); + return OpenFileSystem(out fileSystem, out Unsafe.NullRef(), in path, type, false, id, + isDirectory); + } + + public Result OpenFileSystem(out ReferenceCountedDisposable fileSystem, in Path path, + FileSystemProxyType type, bool canMountSystemDataPrivate, ulong id, bool isDirectory) + { + return OpenFileSystem(out fileSystem, out Unsafe.NullRef(), in path, type, + canMountSystemDataPrivate, id, isDirectory); } public Result OpenFileSystem(out ReferenceCountedDisposable 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 manualFileSystem = null; + ReferenceCountedDisposable hostFileSystem = null; ReferenceCountedDisposable 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 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 fileSystem, U8Span path, - FileSystemProxyType fsType, ulong programId) + public Result OpenDataFileSystem(out ReferenceCountedDisposable fileSystem, in Path path, + FileSystemProxyType fsType, ulong programId, bool isDirectory) { throw new NotImplementedException(); } public Result OpenStorageWithPatch(out ReferenceCountedDisposable 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 fileSystem, - U8Span originalNcaPath, U8Span currentNcaPath, FileSystemProxyType fsType, ulong id) + in Path originalNcaPath, in Path currentNcaPath, FileSystemProxyType fsType, ulong id) { UnsafeHelpers.SkipParamInit(out fileSystem); ReferenceCountedDisposable romFsStorage = null; try { - Result rc = OpenStorageWithPatch(out romFsStorage, out Unsafe.NullRef(), originalNcaPath, - currentNcaPath, fsType, id); + Result rc = OpenStorageWithPatch(out romFsStorage, out Unsafe.NullRef(), 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 fileSystem, ContentStorageId contentStorageId) { - const int storagePathMaxLen = 0x40; + const int pathBufferLength = 0x40; UnsafeHelpers.SkipParamInit(out fileSystem); ReferenceCountedDisposable baseFileSystem = null; ReferenceCountedDisposable subDirFileSystem = null; - ReferenceCountedDisposable encryptedFileSystem = null; + ReferenceCountedDisposable 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 buffer = stackalloc byte[pathBufferLength]; + ref byte bufferRef = ref MemoryMarshal.GetReference(buffer); + Span contentStoragePathBuffer = MemoryMarshal.CreateSpan(ref bufferRef, pathBufferLength); // Build the appropriate path for the content storage ID - Span 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 contentFileSystem, ref ReferenceCountedDisposable baseFileSystem, FileSystemProxyType fsType, bool preserveUnc) { @@ -549,7 +563,7 @@ namespace LibHac.FsSrv ReferenceCountedDisposable 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 contentFileSystem, - ref ReferenceCountedDisposable baseFileSystem, U8Span path) + out ReferenceCountedDisposable 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 fileSystem, @@ -710,17 +721,18 @@ namespace LibHac.FsSrv ReferenceCountedDisposable 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 fileSystem, U8Span path, bool openCaseSensitive) + public Result OpenHostFileSystem(out ReferenceCountedDisposable 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 hostFs = null; - ReferenceCountedDisposable subDirFs = null; - try - { - rc = _config.TargetManagerFsCreator.Create(out hostFs, openCaseSensitive); - if (rc.IsFailure()) return rc; - - if (path.IsEmpty()) - { - ReadOnlySpan 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) diff --git a/src/LibHac/FsSrv/SaveDataFileSystemService.cs b/src/LibHac/FsSrv/SaveDataFileSystemService.cs index 354d02b7..39a23bfd 100644 --- a/src/LibHac/FsSrv/SaveDataFileSystemService.cs +++ b/src/LibHac/FsSrv/SaveDataFileSystemService.cs @@ -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.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.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 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(saveService); - saveService.SelfReference = + saveService._selfReference = new ReferenceCountedDisposable.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 tempFileSystem = null; ReferenceCountedDisposable 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 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 commitInterface = null; try { - saveService = SelfReference.AddReference(); + saveService = _selfReference.AddReference(); commitInterface = saveService.AddReference(); - 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(); } } } diff --git a/src/LibHac/FsSrv/SaveDataFileSystemServiceImpl.cs b/src/LibHac/FsSrv/SaveDataFileSystemServiceImpl.cs index 0378549f..8e105c63 100644 --- a/src/LibHac/FsSrv/SaveDataFileSystemServiceImpl.cs +++ b/src/LibHac/FsSrv/SaveDataFileSystemServiceImpl.cs @@ -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 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 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 buffer = stackalloc byte[bufferLength]; + ref byte bufferRef = ref MemoryMarshal.GetReference(buffer); + Span 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 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 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 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 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 buffer = stackalloc byte[bufferLength]; + ref byte bufferRef = ref MemoryMarshal.GetReference(buffer); + Span 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 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 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 buffer = stackalloc byte[bufferLength]; + ref byte bufferRef = ref MemoryMarshal.GetReference(buffer); + Span 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 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 buffer = stackalloc byte[bufferLength]; + ref byte bufferRef = ref MemoryMarshal.GetReference(buffer); + Span 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 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 buffer = stackalloc byte[bufferLength]; + ref byte bufferRef = ref MemoryMarshal.GetReference(buffer); + Span saveDataIdDirectoryNameBuffer = MemoryMarshal.CreateSpan(ref bufferRef, bufferLength); ReferenceCountedDisposable 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 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 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 buffer = stackalloc byte[bufferLength]; + ref byte bufferRef = ref MemoryMarshal.GetReference(buffer); + Span 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, + in SaveDataCreationInfo creationInfo, in Path saveDataRootPath, in Optional hashSalt, bool skipFormat) { // Use directory save data for now + // Hack around error CS8350. + const int bufferLength = 0x12; + Span buffer = stackalloc byte[bufferLength]; + ref byte bufferRef = ref MemoryMarshal.GetReference(buffer); + Span saveImageNameBuffer = MemoryMarshal.CreateSpan(ref bufferRef, bufferLength); + ReferenceCountedDisposable 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 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 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 buffer = stackalloc byte[bufferLength]; + ref byte bufferRef = ref MemoryMarshal.GetReference(buffer); + Span saveImageNameBuffer = MemoryMarshal.CreateSpan(ref bufferRef, bufferLength); + ReferenceCountedDisposable 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 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 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 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 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 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 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 fileSystem, - SaveDataSpaceId spaceId, U8Span basePath, bool createIfMissing) + SaveDataSpaceId spaceId, in Path basePath, bool createIfMissing) { UnsafeHelpers.SkipParamInit(out fileSystem); - ReferenceCountedDisposable tempFs = null; - ReferenceCountedDisposable tempSubFs = null; + ReferenceCountedDisposable baseFileSystem = null; + ReferenceCountedDisposable 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 buffer = stackalloc byte[bufferLength]; + ref byte bufferRef = ref MemoryMarshal.GetReference(buffer); + Span 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) diff --git a/src/LibHac/FsSrv/SaveDataSharedFileStorage.cs b/src/LibHac/FsSrv/SaveDataSharedFileStorage.cs index 76358950..c7d76b90 100644 --- a/src/LibHac/FsSrv/SaveDataSharedFileStorage.cs +++ b/src/LibHac/FsSrv/SaveDataSharedFileStorage.cs @@ -139,9 +139,9 @@ namespace LibHac.FsSrv return _isNormalStorageOpened || _isInternalStorageOpened; } - public UniqueLock GetLock() + public UniqueLockRef GetLock() { - return new UniqueLock(ref _mutex); + return new UniqueLockRef(ref _mutex); } } @@ -199,7 +199,7 @@ namespace LibHac.FsSrv protected override Result DoRead(long offset, Span destination) { - using UniqueLock scopedLock = _baseStorage.Target.GetLock(); + using UniqueLockRef scopedLock = _baseStorage.Target.GetLock(); Result rc = AccessCheck(isWriteAccess: false); if (rc.IsFailure()) return rc; @@ -209,7 +209,7 @@ namespace LibHac.FsSrv protected override Result DoWrite(long offset, ReadOnlySpan source) { - using UniqueLock scopedLock = _baseStorage.Target.GetLock(); + using UniqueLockRef scopedLock = _baseStorage.Target.GetLock(); Result rc = AccessCheck(isWriteAccess: true); if (rc.IsFailure()) return rc; @@ -219,7 +219,7 @@ namespace LibHac.FsSrv protected override Result DoFlush() { - using UniqueLock scopedLock = _baseStorage.Target.GetLock(); + using UniqueLockRef scopedLock = _baseStorage.Target.GetLock(); Result rc = AccessCheck(isWriteAccess: true); if (rc.IsFailure()) return rc; @@ -229,7 +229,7 @@ namespace LibHac.FsSrv protected override Result DoSetSize(long size) { - using UniqueLock scopedLock = _baseStorage.Target.GetLock(); + using UniqueLockRef scopedLock = _baseStorage.Target.GetLock(); Result rc = AccessCheck(isWriteAccess: true); if (rc.IsFailure()) return rc; @@ -241,7 +241,7 @@ namespace LibHac.FsSrv { Unsafe.SkipInit(out size); - using UniqueLock scopedLock = _baseStorage.Target.GetLock(); + using UniqueLockRef scopedLock = _baseStorage.Target.GetLock(); Result rc = AccessCheck(isWriteAccess: false); if (rc.IsFailure()) return rc; @@ -252,7 +252,7 @@ namespace LibHac.FsSrv protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, ReadOnlySpan inBuffer) { - using UniqueLock scopedLock = _baseStorage.Target.GetLock(); + using UniqueLockRef scopedLock = _baseStorage.Target.GetLock(); Result rc = AccessCheck(isWriteAccess: true); if (rc.IsFailure()) return rc; diff --git a/src/LibHac/FsSrv/Sf/IFile.cs b/src/LibHac/FsSrv/Sf/IFile.cs index 5236bf59..95d086df 100644 --- a/src/LibHac/FsSrv/Sf/IFile.cs +++ b/src/LibHac/FsSrv/Sf/IFile.cs @@ -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); } } \ No newline at end of file diff --git a/src/LibHac/FsSrv/Sf/IFileSystem.cs b/src/LibHac/FsSrv/Sf/IFileSystem.cs index 268cd40a..cce57d62 100644 --- a/src/LibHac/FsSrv/Sf/IFileSystem.cs +++ b/src/LibHac/FsSrv/Sf/IFileSystem.cs @@ -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 file, in Path path, uint mode); Result OpenDirectory(out ReferenceCountedDisposable 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 outBuffer, ReadOnlySpan inBuffer, int queryId, in Path path); + Result QueryEntry(OutBuffer outBuffer, InBuffer inBuffer, int queryId, in Path path); } } diff --git a/src/LibHac/FsSrv/Util.cs b/src/LibHac/FsSrv/Util.cs deleted file mode 100644 index 356bb92f..00000000 --- a/src/LibHac/FsSrv/Util.cs +++ /dev/null @@ -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; - } - } -} diff --git a/src/LibHac/FsSystem/AesXtsFile.cs b/src/LibHac/FsSystem/AesXtsFile.cs index 55395261..3480d9e7 100644 --- a/src/LibHac/FsSystem/AesXtsFile.cs +++ b/src/LibHac/FsSystem/AesXtsFile.cs @@ -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(); } } } diff --git a/src/LibHac/FsSystem/AesXtsFileSystem.cs b/src/LibHac/FsSystem/AesXtsFileSystem.cs index 55702af7..4e535655 100644 --- a/src/LibHac/FsSystem/AesXtsFileSystem.cs +++ b/src/LibHac/FsSystem/AesXtsFileSystem.cs @@ -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]); } /// @@ -61,16 +57,16 @@ namespace LibHac.FsSystem /// Flags to control how the file is created. /// Should usually be /// The 256-bit key containing a 128-bit data key followed by a 128-bit tweak key. - 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 outBuffer, ReadOnlySpan inBuffer, QueryId queryId, - U8Span path) + in Path path) { return BaseFileSystem.QueryEntry(outBuffer, inBuffer, queryId, path); } diff --git a/src/LibHac/FsSystem/ApplicationTemporaryFileSystem.cs b/src/LibHac/FsSystem/ApplicationTemporaryFileSystem.cs index 831663cc..bd489b2c 100644 --- a/src/LibHac/FsSystem/ApplicationTemporaryFileSystem.cs +++ b/src/LibHac/FsSystem/ApplicationTemporaryFileSystem.cs @@ -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(); } diff --git a/src/LibHac/FsSystem/ConcatenationDirectory.cs b/src/LibHac/FsSystem/ConcatenationDirectory.cs deleted file mode 100644 index e8883b8e..00000000 --- a/src/LibHac/FsSystem/ConcatenationDirectory.cs +++ /dev/null @@ -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 entryBuffer) - { - entriesRead = 0; - var entry = new DirectoryEntry(); - Span 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 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); - } - } - } -} diff --git a/src/LibHac/FsSystem/ConcatenationFile.cs b/src/LibHac/FsSystem/ConcatenationFile.cs deleted file mode 100644 index 7794e0e5..00000000 --- a/src/LibHac/FsSystem/ConcatenationFile.cs +++ /dev/null @@ -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 Sources { get; } - private long SubFileSize { get; } - private OpenMode Mode { get; } - - internal ConcatenationFile(IFileSystem baseFileSystem, U8Span path, IEnumerable 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 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 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 outBuffer, OperationId operationId, long offset, long size, ReadOnlySpan 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; - } - } -} diff --git a/src/LibHac/FsSystem/ConcatenationFileSystem.cs b/src/LibHac/FsSystem/ConcatenationFileSystem.cs index 1b4bc777..aa2d281d 100644 --- a/src/LibHac/FsSystem/ConcatenationFileSystem.cs +++ b/src/LibHac/FsSystem/ConcatenationFileSystem.cs @@ -3,11 +3,12 @@ using System.Buffers; using System.Buffers.Text; 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.Util; +using static LibHac.FsSystem.Utility12; namespace LibHac.FsSystem { @@ -17,196 +18,693 @@ namespace LibHac.FsSystem /// /// This filesystem is mainly used to allow storing large files on filesystems that have low /// limits on file size such as FAT filesystems. The underlying base filesystem must have - /// support for the "Archive" file attribute found in FAT or NTFS filesystems. - /// + /// support for the "Archive" file attribute found in FAT or NTFS filesystems.
+ ///
/// A may contain both standard files or Concatenation files. /// If a directory has the archive attribute set, its contents will be concatenated and treated /// as a single file. These sub-files must follow the naming scheme "00", "01", "02", ... - /// Each sub-file except the final one must have the size that was specified + /// Each sub-file except the final one must have the size that was specified /// at the creation of the . + ///
Based on FS 12.0.3 (nnSdk 12.3.1) ///
public class ConcatenationFileSystem : IFileSystem { - private const long DefaultSubFileSize = 0xFFFF0000; // Hard-coded value used by FS - private IAttributeFileSystem BaseFileSystem { get; } - private long SubFileSize { get; } - - /// - /// Initializes a new . - /// - /// The base for the - /// new . - public ConcatenationFileSystem(IAttributeFileSystem baseFileSystem) : this(baseFileSystem, DefaultSubFileSize) { } - - /// - /// Initializes a new . - /// - /// The base for the - /// new . - /// The size of each sub-file. Once a file exceeds this size, a new sub-file will be created - public ConcatenationFileSystem(IAttributeFileSystem baseFileSystem, long subFileSize) + private class ConcatenationFile : IFile { - BaseFileSystem = baseFileSystem; - SubFileSize = subFileSize; - } + private OpenMode _mode; + private List _files; + private long _internalFileSize; + private IFileSystem _baseFileSystem; + private Path.Stored _path; - // .NET Core on platforms other than Windows doesn't support getting the - // archive flag in FAT file systems. Try to work around that for now for reading, - // but writing still won't work properly on those platforms - internal bool IsConcatenationFile(U8Span path) - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + public ConcatenationFile(OpenMode mode, ref List internalFiles, long internalFileSize, IFileSystem baseFileSystem) { - Result rc = BaseFileSystem.GetFileAttributes(out NxFileAttributes attributes, path); - if (rc.IsFailure()) return false; - - return HasConcatenationFileAttribute(attributes); - } - else - { - return IsConcatenationFileHeuristic(path); - } - } - - private bool IsConcatenationFileHeuristic(U8Span path) - { - // Check if the path is a directory - Result getTypeResult = BaseFileSystem.GetEntryType(out DirectoryEntryType pathType, path); - if (getTypeResult.IsFailure() || pathType != DirectoryEntryType.Directory) return false; - - // Check if the directory contains at least one subfile - getTypeResult = BaseFileSystem.GetEntryType(out DirectoryEntryType subFileType, PathTools.Combine(path.ToString(), "00").ToU8Span()); - if (getTypeResult.IsFailure() || subFileType != DirectoryEntryType.File) return false; - - // Make sure the directory contains no subdirectories - Result rc = BaseFileSystem.OpenDirectory(out IDirectory dir, path, OpenDirectoryMode.Directory); - if (rc.IsFailure()) return false; - - rc = dir.GetEntryCount(out long subDirCount); - if (rc.IsFailure() || subDirCount > 0) return false; - - // Should be enough checks to avoid most false positives. Maybe - return true; - } - - internal static bool HasConcatenationFileAttribute(NxFileAttributes attributes) - { - return (attributes & NxFileAttributes.Directory) != 0 && (attributes & NxFileAttributes.Archive) != 0; - } - - private Result SetConcatenationFileAttribute(U8Span path) - { - return BaseFileSystem.SetFileAttributes(path, NxFileAttributes.Archive); - } - - protected override Result DoCreateDirectory(U8Span path) - { - var parent = new U8Span(PathTools.GetParentDirectory(path)); - - if (IsConcatenationFile(parent)) - { - // Cannot create a directory inside of a concatenation file - return ResultFs.PathNotFound.Log(); + _mode = mode; + _files = Shared.Move(ref internalFiles); + _internalFileSize = internalFileSize; + _baseFileSystem = baseFileSystem; + _path = new Path.Stored(); } - return BaseFileSystem.CreateDirectory(path); - } - - protected override Result DoCreateFile(U8Span path, long size, CreateFileOptions options) - { - CreateFileOptions newOptions = options & ~CreateFileOptions.CreateConcatenationFile; - - if (!options.HasFlag(CreateFileOptions.CreateConcatenationFile)) + public override void Dispose() { - return BaseFileSystem.CreateFile(path, size, newOptions); - } + _path.Dispose(); - // A concatenation file directory can't contain normal files - ReadOnlySpan parentDir = PathTools.GetParentDirectory(path); - - if (IsConcatenationFile(new U8Span(parentDir))) - { - // Cannot create a file inside of a concatenation file - return ResultFs.PathNotFound.Log(); - } - - Result rc = BaseFileSystem.CreateDirectory(path, NxFileAttributes.Archive); - if (rc.IsFailure()) return rc; - - long remaining = size; - - for (int i = 0; remaining > 0; i++) - { - long fileSize = Math.Min(SubFileSize, remaining); - - Unsafe.SkipInit(out FsPath fileName); - - rc = GetSubFilePath(fileName.Str, path, i); - if (rc.IsFailure()) return rc; - - Result createSubFileResult = BaseFileSystem.CreateFile(fileName, fileSize, CreateFileOptions.None); - - if (createSubFileResult.IsFailure()) + foreach (IFile file in _files) { - BaseFileSystem.DeleteDirectoryRecursively(path); - return createSubFileResult; + file?.Dispose(); } - remaining -= fileSize; + _files.Clear(); + + base.Dispose(); } + public Result Initialize(in Path path) + { + return _path.Initialize(in path); + } + + private int GetInternalFileIndex(long offset) + { + return (int)(offset / _internalFileSize); + } + + private int GetInternalFileCount(long size) + { + if (size == 0) + return 1; + + return (int)BitUtil.DivideUp(size, _internalFileSize); + } + + private long GetInternalFileSize(long offset, int tailIndex) + { + int index = GetInternalFileIndex(offset); + + if (tailIndex < index) + return _internalFileSize; + + Assert.SdkAssert(index == tailIndex); + + return offset % _internalFileSize; + } + + protected override Result DoRead(out long bytesRead, long offset, Span destination, + in ReadOption option) + { + UnsafeHelpers.SkipParamInit(out bytesRead); + + long fileOffset = offset; + int bufferOffset = 0; + + Result rc = DryRead(out long remaining, offset, destination.Length, in option, _mode); + if (rc.IsFailure()) return rc; + + while (remaining > 0) + { + int fileIndex = GetInternalFileIndex(fileOffset); + long internalFileRemaining = _internalFileSize - GetInternalFileSize(fileOffset, fileIndex); + long internalFileOffset = fileOffset - _internalFileSize * fileIndex; + + int bytesToRead = (int)Math.Min(remaining, internalFileRemaining); + + Assert.SdkAssert(fileIndex < _files.Count); + + rc = _files[fileIndex].Read(out long internalFileBytesRead, internalFileOffset, + destination.Slice(bufferOffset, bytesToRead), in option); + if (rc.IsFailure()) return rc; + + remaining -= internalFileBytesRead; + bufferOffset += (int)internalFileBytesRead; + fileOffset += internalFileBytesRead; + + if (internalFileBytesRead < bytesToRead) + break; + } + + bytesRead = bufferOffset; + return Result.Success; + } + + protected override Result DoWrite(long offset, ReadOnlySpan source, in WriteOption option) + { + Result rc = DryWrite(out bool needsAppend, offset, source.Length, in option, _mode); + if (rc.IsFailure()) return rc; + + if (source.Length > 0 && needsAppend) + { + rc = SetSize(offset + source.Length); + if (rc.IsFailure()) return rc; + } + + int remaining = source.Length; + int bufferOffset = 0; + long fileOffset = offset; + + // No need to send the flush option to the internal files. We'll flush them after all the writes are done. + var internalFileOption = new WriteOption(option.Flags & ~WriteOptionFlag.Flush); + + while (remaining > 0) + { + int fileIndex = GetInternalFileIndex(fileOffset); + long internalFileRemaining = _internalFileSize - GetInternalFileSize(fileOffset, fileIndex); + long internalFileOffset = fileOffset - _internalFileSize * fileIndex; + + int bytesToWrite = (int)Math.Min(remaining, internalFileRemaining); + + Assert.SdkAssert(fileIndex < _files.Count); + + rc = _files[fileIndex].Write(internalFileOffset, source.Slice(bufferOffset, bytesToWrite), + in internalFileOption); + if (rc.IsFailure()) return rc; + + remaining -= bytesToWrite; + bufferOffset += bytesToWrite; + fileOffset += bytesToWrite; + } + + if (option.HasFlushFlag()) + { + rc = Flush(); + if (rc.IsFailure()) return rc; + } + + return Result.Success; + } + + protected override Result DoFlush() + { + if (!_mode.HasFlag(OpenMode.Write)) + return Result.Success; + + foreach (IFile file in _files) + { + Result rc = file.Flush(); + if (rc.IsFailure()) return rc; + } + + return Result.Success; + } + + protected override Result DoSetSize(long size) + { + Result rc = DrySetSize(size, _mode); + if (rc.IsFailure()) return rc; + + rc = GetSize(out long currentSize); + if (rc.IsFailure()) return rc; + + if (currentSize == size) return Result.Success; + + int currentTailIndex = GetInternalFileCount(currentSize) - 1; + int newTailIndex = GetInternalFileCount(size) - 1; + + var internalFilePath = new Path(); + rc = internalFilePath.Initialize(in _path); + if (rc.IsFailure()) return rc; + + if (size > currentSize) + { + rc = _files[currentTailIndex].SetSize(GetInternalFileSize(size, currentTailIndex)); + if (rc.IsFailure()) return rc; + + for (int i = currentTailIndex + 1; i < newTailIndex; i++) + { + rc = AppendInternalFilePath(ref internalFilePath, i); + if (rc.IsFailure()) return rc; + + rc = _baseFileSystem.CreateFile(in internalFilePath, GetInternalFileSize(size, i), + CreateFileOptions.None); + if (rc.IsFailure()) return rc; + + rc = _baseFileSystem.OpenFile(out IFile newInternalFile, in internalFilePath, _mode); + if (rc.IsFailure()) return rc; + + _files.Add(newInternalFile); + + rc = internalFilePath.RemoveChild(); + if (rc.IsFailure()) return rc; + } + } + else + { + for (int i = currentTailIndex - 1; i > newTailIndex; i--) + { + _files[i].Dispose(); + _files.RemoveAt(i); + + rc = AppendInternalFilePath(ref internalFilePath, i); + if (rc.IsFailure()) return rc; + + rc = _baseFileSystem.DeleteFile(in internalFilePath); + if (rc.IsFailure()) return rc; + + rc = internalFilePath.RemoveChild(); + if (rc.IsFailure()) return rc; + } + + rc = _files[newTailIndex].SetSize(GetInternalFileSize(size, newTailIndex)); + if (rc.IsFailure()) return rc; + } + + internalFilePath.Dispose(); + return Result.Success; + } + + protected override Result DoGetSize(out long size) + { + UnsafeHelpers.SkipParamInit(out size); + + long totalSize = 0; + + foreach (IFile file in _files) + { + Result rc = file.GetSize(out long internalFileSize); + if (rc.IsFailure()) return rc; + + totalSize += internalFileSize; + } + + size = totalSize; + return Result.Success; + } + + protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, + ReadOnlySpan inBuffer) + { + if (operationId == OperationId.InvalidateCache) + { + if (!_mode.HasFlag(OpenMode.Read)) + return ResultFs.ReadUnpermitted.Log(); + + var closure = new OperateRangeClosure(); + closure.OutBuffer = outBuffer; + closure.InBuffer = inBuffer; + closure.OperationId = operationId; + + Result rc = DoOperateRangeImpl(offset, size, InvalidateCacheImpl, ref closure); + if (rc.IsFailure()) return rc; + } + else if (operationId == OperationId.QueryRange) + { + if (outBuffer.Length != Unsafe.SizeOf()) + return ResultFs.InvalidSize.Log(); + + var closure = new OperateRangeClosure(); + closure.InBuffer = inBuffer; + closure.OperationId = operationId; + closure.InfoMerged.Clear(); + + Result rc = DoOperateRangeImpl(offset, size, QueryRangeImpl, ref closure); + if (rc.IsFailure()) return rc; + + SpanHelpers.AsByteSpan(ref closure.InfoMerged).CopyTo(outBuffer); + } + else + { + return ResultFs.UnsupportedOperateRangeForConcatenationFile.Log(); + } + + return Result.Success; + + static Result InvalidateCacheImpl(IFile file, long offset, long size, ref OperateRangeClosure closure) + { + return file.OperateRange(closure.OutBuffer, closure.OperationId, offset, size, closure.InBuffer); + } + + static Result QueryRangeImpl(IFile file, long offset, long size, ref OperateRangeClosure closure) + { + Unsafe.SkipInit(out QueryRangeInfo infoEntry); + + Result rc = file.OperateRange(SpanHelpers.AsByteSpan(ref infoEntry), closure.OperationId, offset, size, + closure.InBuffer); + if (rc.IsFailure()) return rc; + + closure.InfoMerged.Merge(in infoEntry); + return Result.Success; + } + } + + private Result DoOperateRangeImpl(long offset, long size, OperateRangeTask func, + ref OperateRangeClosure closure) + { + if (offset < 0) + return ResultFs.OutOfRange.Log(); + + Result rc = GetSize(out long currentSize); + if (rc.IsFailure()) return rc; + + if (offset > currentSize) + return ResultFs.OutOfRange.Log(); + + long currentOffset = offset; + long availableSize = currentSize - offset; + long remaining = Math.Min(size, availableSize); + + while (remaining > 0) + { + int fileIndex = GetInternalFileIndex(currentOffset); + long internalFileRemaining = _internalFileSize - GetInternalFileSize(currentOffset, fileIndex); + long internalFileOffset = currentOffset - _internalFileSize * fileIndex; + + long sizeToOperate = Math.Min(remaining, internalFileRemaining); + + Assert.SdkAssert(fileIndex < _files.Count); + + rc = func(_files[fileIndex], internalFileOffset, sizeToOperate, ref closure); + if (rc.IsFailure()) return rc; + + remaining -= sizeToOperate; + currentOffset += sizeToOperate; + } + + return Result.Success; + } + + private delegate Result OperateRangeTask(IFile file, long offset, long size, ref OperateRangeClosure closure); + + private ref struct OperateRangeClosure + { + public Span OutBuffer; + public ReadOnlySpan InBuffer; + public OperationId OperationId; + public QueryRangeInfo InfoMerged; + } + } + + private class ConcatenationDirectory : IDirectory + { + private OpenDirectoryMode _mode; + private IDirectory _baseDirectory; + private Path.Stored _path; + private IFileSystem _baseFileSystem; + private ConcatenationFileSystem _concatenationFileSystem; + + public ConcatenationDirectory(OpenDirectoryMode mode, IDirectory baseDirectory, + ConcatenationFileSystem concatFileSystem, IFileSystem baseFileSystem) + { + _mode = mode; + _baseDirectory = baseDirectory; + _baseFileSystem = baseFileSystem; + _concatenationFileSystem = concatFileSystem; + } + + public override void Dispose() + { + _path.Dispose(); + _baseDirectory.Dispose(); + + base.Dispose(); + } + + public Result Initialize(in Path path) + { + Result rc = _path.Initialize(in path); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + protected override Result DoRead(out long entriesRead, Span entryBuffer) + { + UnsafeHelpers.SkipParamInit(out entriesRead); + + Unsafe.SkipInit(out DirectoryEntry entry); + int readCountTotal = 0; + + while (readCountTotal < entryBuffer.Length) + { + Result rc = _baseDirectory.Read(out long readCount, SpanHelpers.AsSpan(ref entry)); + if (rc.IsFailure()) return rc; + + if (readCount == 0) + break; + + if (!IsReadTarget(in entry)) + continue; + + if (IsConcatenationFileAttribute(entry.Attributes)) + { + entry.Type = DirectoryEntryType.File; + + if (!_mode.HasFlag(OpenDirectoryMode.NoFileSize)) + { + var internalFilePath = new Path(); + rc = internalFilePath.Initialize(in _path); + if (rc.IsFailure()) return rc; + + rc = internalFilePath.AppendChild(entry.Name); + if (rc.IsFailure()) return rc; + + rc = _concatenationFileSystem.GetFileSize(out entry.Size, in internalFilePath); + if (rc.IsFailure()) return rc; + + internalFilePath.Dispose(); + } + } + + entry.Attributes = NxFileAttributes.None; + entryBuffer[readCountTotal++] = entry; + } + + entriesRead = readCountTotal; + return Result.Success; + } + + protected override Result DoGetEntryCount(out long entryCount) + { + UnsafeHelpers.SkipParamInit(out entryCount); + + Unsafe.SkipInit(out DirectoryEntry entry); + IDirectory directory = null; + + try + { + Path path = _path.GetPath(); + + Result rc = _baseFileSystem.OpenDirectory(out directory, in path, + OpenDirectoryMode.All | OpenDirectoryMode.NoFileSize); + if (rc.IsFailure()) return rc; + + long entryCountTotal = 0; + + while (true) + { + directory.Read(out long readCount, SpanHelpers.AsSpan(ref entry)); + if (rc.IsFailure()) return rc; + + if (readCount == 0) + break; + + if (IsReadTarget(in entry)) + entryCountTotal++; + } + + entryCount = entryCountTotal; + return Result.Success; + } + finally + { + directory?.Dispose(); + } + } + + private bool IsReadTarget(in DirectoryEntry entry) + { + bool hasConcatAttribute = IsConcatenationFileAttribute(entry.Attributes); + + return _mode.HasFlag(OpenDirectoryMode.File) && (entry.Type == DirectoryEntryType.File || hasConcatAttribute) || + _mode.HasFlag(OpenDirectoryMode.Directory) && entry.Type == DirectoryEntryType.Directory && !hasConcatAttribute; + } + } + + public static readonly long DefaultInternalFileSize = 0xFFFF0000; // Hard-coded value used by FS + + private IAttributeFileSystem _baseFileSystem; + private long _InternalFileSize; + + /// + /// Initializes a new with an internal file size of . + /// + /// The base for the + /// new . + public ConcatenationFileSystem(IAttributeFileSystem baseFileSystem) : this(baseFileSystem, DefaultInternalFileSize) { } + + /// + /// Initializes a new . + /// + /// The base for the + /// new . + /// The size of each internal file. Once a file exceeds this size, a new internal file will be created + public ConcatenationFileSystem(IAttributeFileSystem baseFileSystem, long internalFileSize) + { + _baseFileSystem = baseFileSystem; + _InternalFileSize = internalFileSize; + } + + public override void Dispose() + { + _baseFileSystem?.Dispose(); + _baseFileSystem = null; + + base.Dispose(); + } + + private static ReadOnlySpan RootPath => new[] { (byte)'/' }; + + private static Result AppendInternalFilePath(ref Path path, int index) + { + // Use an int as the buffer instead of a stackalloc byte[3] to workaround CS8350. + // Path.AppendChild will not save the span passed to it so this should be safe. + int bufferInt = 0; + Utf8Formatter.TryFormat(index, SpanHelpers.AsByteSpan(ref bufferInt), out _, new StandardFormat('d', 2)); + + return path.AppendChild(SpanHelpers.AsByteSpan(ref bufferInt)); + } + + private static Result GenerateInternalFilePath(ref Path outPath, int index, in Path basePath) + { + Result rc = outPath.Initialize(in basePath); + if (rc.IsFailure()) return rc; + + rc = AppendInternalFilePath(ref outPath, index); + if (rc.IsFailure()) return rc; + return Result.Success; } - protected override Result DoDeleteDirectory(U8Span path) + private static Result GenerateParentPath(ref Path outParentPath, in Path path) { - if (IsConcatenationFile(path)) - { + if (path == RootPath) return ResultFs.PathNotFound.Log(); - } - return BaseFileSystem.DeleteDirectory(path); - } - - protected override Result DoDeleteDirectoryRecursively(U8Span path) - { - if (IsConcatenationFile(path)) return ResultFs.PathNotFound.Log(); - - return BaseFileSystem.DeleteDirectoryRecursively(path); - } - - protected override Result DoCleanDirectoryRecursively(U8Span path) - { - if (IsConcatenationFile(path)) return ResultFs.PathNotFound.Log(); - - return BaseFileSystem.CleanDirectoryRecursively(path); - } - - protected override Result DoDeleteFile(U8Span path) - { - if (!IsConcatenationFile(path)) - { - return BaseFileSystem.DeleteFile(path); - } - - Result rc = GetSubFileCount(out int count, path); + Result rc = outParentPath.Initialize(in path); if (rc.IsFailure()) return rc; - for (int i = 0; i < count; i++) - { - Unsafe.SkipInit(out FsPath subFilePath); + rc = outParentPath.RemoveChild(); + if (rc.IsFailure()) return rc; - rc = GetSubFilePath(subFilePath.Str, path, i); - if (rc.IsFailure()) return rc; - - rc = BaseFileSystem.DeleteFile(subFilePath); - if (rc.IsFailure()) return rc; - } - - return BaseFileSystem.DeleteDirectory(path); + return Result.Success; } - protected override Result DoOpenDirectory(out IDirectory directory, U8Span path, OpenDirectoryMode mode) + private static bool IsConcatenationFileAttribute(NxFileAttributes attribute) + { + return attribute.HasFlag(NxFileAttributes.Directory | NxFileAttributes.Archive); + } + + private bool IsConcatenationFile(in Path path) + { + Result rc = _baseFileSystem.GetFileAttributes(out NxFileAttributes attribute, in path); + if (rc.IsFailure()) + return false; + + return IsConcatenationFileAttribute(attribute); + } + + private Result GetInternalFileCount(out int count, in Path path) + { + UnsafeHelpers.SkipParamInit(out count); + + var internalFilePath = new Path(); + Result rc = internalFilePath.Initialize(in path); + if (rc.IsFailure()) return rc; + + for (int i = 0; ; i++) + { + rc = AppendInternalFilePath(ref internalFilePath, i); + if (rc.IsFailure()) return rc; + + rc = _baseFileSystem.GetEntryType(out _, in internalFilePath); + if (rc.IsFailure()) + { + // We've passed the last internal file of the concatenation file + // once the next internal file doesn't exist. + if (ResultFs.PathNotFound.Includes(rc)) + { + count = i; + internalFilePath.Dispose(); + return Result.Success; + } + + return rc; + } + + rc = internalFilePath.RemoveChild(); + if (rc.IsFailure()) return rc; + } + } + + protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path) + { + if (IsConcatenationFile(in path)) + { + entryType = DirectoryEntryType.File; + return Result.Success; + } + + return _baseFileSystem.GetEntryType(out entryType, path); + } + + protected override Result DoGetFreeSpaceSize(out long freeSpace, in Path path) + { + return _baseFileSystem.GetFreeSpaceSize(out freeSpace, path); + } + + protected override Result DoGetTotalSpaceSize(out long totalSpace, in Path path) + { + return _baseFileSystem.GetTotalSpaceSize(out totalSpace, path); + } + + protected override Result DoGetFileTimeStampRaw(out FileTimeStampRaw timeStamp, in Path path) + { + return _baseFileSystem.GetFileTimeStampRaw(out timeStamp, path); + } + + protected override Result DoFlush() + { + return _baseFileSystem.Flush(); + } + + protected override Result DoOpenFile(out IFile file, in Path path, OpenMode mode) + { + UnsafeHelpers.SkipParamInit(out file); + + if (!IsConcatenationFile(in path)) + { + return _baseFileSystem.OpenFile(out file, in path, mode); + } + + Result rc = GetInternalFileCount(out int fileCount, in path); + if (rc.IsFailure()) return rc; + + ConcatenationFile concatFile = null; + var internalFiles = new List(fileCount); + + var filePath = new Path(); + filePath.Initialize(in path); + if (rc.IsFailure()) return rc; + + try + { + for (int i = 0; i < fileCount; i++) + { + rc = AppendInternalFilePath(ref filePath, i); + if (rc.IsFailure()) return rc; + + rc = _baseFileSystem.OpenFile(out IFile internalFile, in filePath, mode); + if (rc.IsFailure()) return rc; + + internalFiles.Add(internalFile); + + rc = filePath.RemoveChild(); + if (rc.IsFailure()) return rc; + } + + concatFile = new ConcatenationFile(mode, ref internalFiles, _InternalFileSize, _baseFileSystem); + + rc = concatFile.Initialize(in path); + if (rc.IsFailure()) return rc; + + file = Shared.Move(ref concatFile); + return Result.Success; + } + finally + { + filePath.Dispose(); + concatFile?.Dispose(); + + if (internalFiles is not null) + { + foreach (IFile internalFile in internalFiles) + { + internalFile?.Dispose(); + } + } + } + } + + protected override Result DoOpenDirectory(out IDirectory directory, in Path path, OpenDirectoryMode mode) { UnsafeHelpers.SkipParamInit(out directory); @@ -215,199 +713,271 @@ namespace LibHac.FsSystem return ResultFs.PathNotFound.Log(); } - Result rc = BaseFileSystem.OpenDirectory(out IDirectory parentDir, path, OpenDirectoryMode.All); + Result rc = _baseFileSystem.OpenDirectory(out IDirectory baseDirectory, path, OpenDirectoryMode.All); if (rc.IsFailure()) return rc; - directory = new ConcatenationDirectory(this, BaseFileSystem, parentDir, mode, path); + var concatDirectory = new ConcatenationDirectory(mode, baseDirectory, this, _baseFileSystem); + rc = concatDirectory.Initialize(in path); + if (rc.IsFailure()) return rc; + + directory = concatDirectory; return Result.Success; } - protected override Result DoOpenFile(out IFile file, U8Span path, OpenMode mode) + protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option) { - UnsafeHelpers.SkipParamInit(out file); + CreateFileOptions newOption = option & ~CreateFileOptions.CreateConcatenationFile; - if (!IsConcatenationFile(path)) + // Create a normal file if the concatenation file flag isn't set + if (!option.HasFlag(CreateFileOptions.CreateConcatenationFile)) { - return BaseFileSystem.OpenFile(out file, path, mode); + return _baseFileSystem.CreateFile(path, size, newOption); } - Result rc = GetSubFileCount(out int fileCount, path); + var parentPath = new Path(); + Result rc = GenerateParentPath(ref parentPath, in path); if (rc.IsFailure()) return rc; - var files = new List(fileCount); - - for (int i = 0; i < fileCount; i++) - { - Unsafe.SkipInit(out FsPath subFilePath); - - rc = GetSubFilePath(subFilePath.Str, path, i); - if (rc.IsFailure()) return rc; - - rc = BaseFileSystem.OpenFile(out IFile subFile, subFilePath, mode); - if (rc.IsFailure()) return rc; - - files.Add(subFile); - } - - file = new ConcatenationFile(BaseFileSystem, path, files, SubFileSize, mode); - return Result.Success; - } - - protected override Result DoRenameDirectory(U8Span oldPath, U8Span newPath) - { - if (IsConcatenationFile(oldPath)) + if (IsConcatenationFile(in parentPath)) { + // Cannot create a file inside of a concatenation file return ResultFs.PathNotFound.Log(); } - return BaseFileSystem.RenameDirectory(oldPath, newPath); - } + rc = _baseFileSystem.CreateDirectory(in path, NxFileAttributes.Archive); + if (rc.IsFailure()) return rc; - protected override Result DoRenameFile(U8Span oldPath, U8Span newPath) - { - if (IsConcatenationFile(oldPath)) + // Handle the empty file case by manually creating a single empty internal file + if (size == 0) { - return BaseFileSystem.RenameDirectory(oldPath, newPath); - } - else - { - return BaseFileSystem.RenameFile(oldPath, newPath); - } - } + var emptyFilePath = new Path(); + rc = GenerateInternalFilePath(ref emptyFilePath, 0, in path); + if (rc.IsFailure()) return rc; - protected override Result DoGetEntryType(out DirectoryEntryType entryType, U8Span path) - { - if (IsConcatenationFile(path)) - { - entryType = DirectoryEntryType.File; + rc = _baseFileSystem.CreateFile(in emptyFilePath, 0, newOption); + if (rc.IsFailure()) return rc; + + emptyFilePath.Dispose(); return Result.Success; } - return BaseFileSystem.GetEntryType(out entryType, path); + long remaining = size; + var filePath = new Path(); + filePath.Initialize(in path); + if (rc.IsFailure()) return rc; + + for (int i = 0; remaining > 0; i++) + { + rc = AppendInternalFilePath(ref filePath, i); + if (rc.IsFailure()) return rc; + + long fileSize = Math.Min(remaining, _InternalFileSize); + Result createInternalFileResult = _baseFileSystem.CreateFile(in filePath, fileSize, newOption); + + // If something goes wrong when creating an internal file, delete all the + // internal files we've created so far and delete the directory. + // This will allow results like insufficient space results to be returned properly. + if (createInternalFileResult.IsFailure()) + { + for (int index = i - 1; index >= 0; index--) + { + rc = GenerateInternalFilePath(ref filePath, index, in path); + if (rc.IsFailure()) return rc; + + rc = _baseFileSystem.DeleteFile(in filePath); + + if (rc.IsFailure()) + break; + } + + _baseFileSystem.DeleteDirectoryRecursively(in path).IgnoreResult(); + return createInternalFileResult; + } + + rc = filePath.RemoveChild(); + if (rc.IsFailure()) return rc; + + remaining -= fileSize; + } + + filePath.Dispose(); + return Result.Success; } - protected override Result DoGetFreeSpaceSize(out long freeSpace, U8Span path) + protected override Result DoDeleteFile(in Path path) { - return BaseFileSystem.GetFreeSpaceSize(out freeSpace, path); + if (!IsConcatenationFile(in path)) + { + return _baseFileSystem.DeleteFile(in path); + } + + Result rc = GetInternalFileCount(out int count, path); + if (rc.IsFailure()) return rc; + + var filePath = new Path(); + rc = filePath.Initialize(in path); + if (rc.IsFailure()) return rc; + + for (int i = count - 1; i >= 0; i--) + { + rc = AppendInternalFilePath(ref filePath, i); + if (rc.IsFailure()) return rc; + + rc = _baseFileSystem.DeleteFile(in filePath); + if (rc.IsFailure()) return rc; + + rc = filePath.RemoveChild(); + if (rc.IsFailure()) return rc; + } + + rc = _baseFileSystem.DeleteDirectoryRecursively(in path); + if (rc.IsFailure()) return rc; + + filePath.Dispose(); + return Result.Success; } - protected override Result DoGetTotalSpaceSize(out long totalSpace, U8Span path) + protected override Result DoCreateDirectory(in Path path) { - return BaseFileSystem.GetTotalSpaceSize(out totalSpace, path); + // Check if the parent path is a concatenation file because we can't create a directory inside one. + var parentPath = new Path(); + Result rc = GenerateParentPath(ref parentPath, in path); + if (rc.IsFailure()) return rc; + + if (IsConcatenationFile(in parentPath)) + return ResultFs.PathNotFound.Log(); + + rc = _baseFileSystem.CreateDirectory(in path); + if (rc.IsFailure()) return rc; + + return Result.Success; } - protected override Result DoGetFileTimeStampRaw(out FileTimeStampRaw timeStamp, U8Span path) + protected override Result DoDeleteDirectory(in Path path) { - return BaseFileSystem.GetFileTimeStampRaw(out timeStamp, path); + // Make sure the directory isn't a concatenation file. + if (IsConcatenationFile(path)) + return ResultFs.PathNotFound.Log(); + + return _baseFileSystem.DeleteDirectory(path); + } + + private Result CleanDirectoryRecursivelyImpl(in Path path) + { + static Result OnEnterDir(in Path path, in DirectoryEntry entry, ref FsIterationTaskClosure closure) => + Result.Success; + + static Result OnExitDir(in Path path, in DirectoryEntry entry, ref FsIterationTaskClosure closure) => + closure.SourceFileSystem.DeleteDirectory(in path); + + static Result OnFile(in Path path, in DirectoryEntry entry, ref FsIterationTaskClosure closure) => + closure.SourceFileSystem.DeleteFile(in path); + + var closure = new FsIterationTaskClosure(); + closure.SourceFileSystem = this; + + var directoryEntry = new DirectoryEntry(); + return CleanupDirectoryRecursively(this, in path, ref directoryEntry, OnEnterDir, OnExitDir, OnFile, + ref closure); + } + + protected override Result DoDeleteDirectoryRecursively(in Path path) + { + if (IsConcatenationFile(in path)) + return ResultFs.PathNotFound.Log(); + + Result rc = CleanDirectoryRecursivelyImpl(in path); + if (rc.IsFailure()) return rc; + + rc = _baseFileSystem.DeleteDirectory(in path); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + protected override Result DoCleanDirectoryRecursively(in Path path) + { + if (IsConcatenationFile(in path)) + return ResultFs.PathNotFound.Log(); + + Result rc = CleanDirectoryRecursivelyImpl(in path); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + protected override Result DoRenameFile(in Path currentPath, in Path newPath) + { + if (IsConcatenationFile(in currentPath)) + { + return _baseFileSystem.RenameDirectory(in currentPath, in newPath); + } + + return _baseFileSystem.RenameFile(in currentPath, in newPath); + } + + protected override Result DoRenameDirectory(in Path currentPath, in Path newPath) + { + if (IsConcatenationFile(in currentPath)) + return ResultFs.PathNotFound.Log(); + + return _baseFileSystem.RenameDirectory(in currentPath, in newPath); + } + + public Result GetFileSize(out long size, in Path path) + { + UnsafeHelpers.SkipParamInit(out size); + + var internalFilePath = new Path(); + Result rc = internalFilePath.Initialize(in path); + if (rc.IsFailure()) return rc; + + long sizeTotal = 0; + + for (int i = 0; ; i++) + { + rc = AppendInternalFilePath(ref internalFilePath, i); + if (rc.IsFailure()) return rc; + + rc = _baseFileSystem.GetFileSize(out long internalFileSize, in internalFilePath); + if (rc.IsFailure()) + { + // We've passed the last internal file of the concatenation file + // once the next internal file doesn't exist. + if (ResultFs.PathNotFound.Includes(rc)) + { + size = sizeTotal; + internalFilePath.Dispose(); + return Result.Success; + } + + return rc; + } + + rc = internalFilePath.RemoveChild(); + if (rc.IsFailure()) return rc; + + sizeTotal += internalFileSize; + } + } + + protected override Result DoQueryEntry(Span outBuffer, ReadOnlySpan inBuffer, QueryId queryId, + in Path path) + { + if (queryId != QueryId.SetConcatenationFileAttribute) + return ResultFs.UnsupportedQueryEntryForConcatenationFileSystem.Log(); + + return _baseFileSystem.SetFileAttributes(in path, NxFileAttributes.Archive); } protected override Result DoCommit() { - return BaseFileSystem.Commit(); + return _baseFileSystem.Commit(); } protected override Result DoCommitProvisionally(long counter) { - return BaseFileSystem.CommitProvisionally(counter); - } - - protected override Result DoFlush() - { - return BaseFileSystem.Flush(); - } - - protected override Result DoQueryEntry(Span outBuffer, ReadOnlySpan inBuffer, QueryId queryId, - U8Span path) - { - if (queryId != QueryId.MakeConcatFile) return ResultFs.UnsupportedQueryEntryForConcatenationFileSystem.Log(); - - return SetConcatenationFileAttribute(path); - } - - private Result GetSubFileCount(out int fileCount, U8Span dirPath) - { - UnsafeHelpers.SkipParamInit(out fileCount); - - Unsafe.SkipInit(out FsPath buffer); - - int pathLen = StringUtils.Copy(buffer.Str, dirPath); - - // Make sure we have at least 3 bytes for the sub file name - if (pathLen + 3 > PathTools.MaxPathLength) - return ResultFs.TooLongPath.Log(); - - buffer.Str[pathLen] = StringTraits.DirectorySeparator; - Span subFileName = buffer.Str.Slice(pathLen + 1); - - Result rc; - int count; - - for (count = 0; ; count++) - { - Utf8Formatter.TryFormat(count, subFileName, out _, new StandardFormat('D', 2)); - - rc = BaseFileSystem.GetEntryType(out _, buffer); - if (rc.IsFailure()) break; - } - - if (!ResultFs.PathNotFound.Includes(rc)) - { - return rc; - } - - fileCount = count; - return Result.Success; - } - - internal static Result GetSubFilePath(Span subFilePathBuffer, ReadOnlySpan basePath, int index) - { - int basePathLen = StringUtils.Copy(subFilePathBuffer, basePath); - - // Make sure we have at least 3 bytes for the sub file name - if (basePathLen + 3 > PathTools.MaxPathLength) - return ResultFs.TooLongPath.Log(); - - subFilePathBuffer[basePathLen] = StringTraits.DirectorySeparator; - - Utf8Formatter.TryFormat(index, subFilePathBuffer.Slice(basePathLen + 1), out _, new StandardFormat('D', 2)); - - return Result.Success; - } - - internal Result GetConcatenationFileSize(out long size, ReadOnlySpan path) - { - UnsafeHelpers.SkipParamInit(out size); - Unsafe.SkipInit(out FsPath buffer); - - int pathLen = StringUtils.Copy(buffer.Str, path); - - // Make sure we have at least 3 bytes for the sub file name - if (pathLen + 3 > PathTools.MaxPathLength) - return ResultFs.TooLongPath.Log(); - - buffer.Str[pathLen] = StringTraits.DirectorySeparator; - Span subFileName = buffer.Str.Slice(pathLen + 1); - - Result rc; - long totalSize = 0; - - for (int i = 0; ; i++) - { - Utf8Formatter.TryFormat(i, subFileName, out _, new StandardFormat('D', 2)); - - rc = BaseFileSystem.GetFileSize(out long fileSize, buffer); - if (rc.IsFailure()) break; - - totalSize += fileSize; - } - - if (!ResultFs.PathNotFound.Includes(rc)) - { - return rc; - } - - size = totalSize; - return Result.Success; + return _baseFileSystem.CommitProvisionally(counter); } } } diff --git a/src/LibHac/FsSystem/DirectorySaveDataFileSystem.cs b/src/LibHac/FsSystem/DirectorySaveDataFileSystem.cs index b0594734..22bf00d9 100644 --- a/src/LibHac/FsSystem/DirectorySaveDataFileSystem.cs +++ b/src/LibHac/FsSystem/DirectorySaveDataFileSystem.cs @@ -6,7 +6,6 @@ using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.FsSrv; using LibHac.Os; -using LibHac.Util; namespace LibHac.FsSystem { @@ -26,21 +25,16 @@ namespace LibHac.FsSystem /// /// Transactional commits should be atomic as long as the function of the /// underlying is atomic. - ///
Based on FS 11.0.0 (nnSdk 11.4.0) + ///
Based on FS 12.0.3 (nnSdk 12.3.1) ///
public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccessor { private const int IdealWorkBufferSize = 0x100000; // 1 MiB - private static ReadOnlySpan CommittedDirectoryBytes => new[] { (byte)'/', (byte)'0', (byte)'/' }; - private static ReadOnlySpan WorkingDirectoryBytes => new[] { (byte)'/', (byte)'1', (byte)'/' }; - private static ReadOnlySpan SynchronizingDirectoryBytes => new[] { (byte)'/', (byte)'_', (byte)'/' }; - private static ReadOnlySpan LockFileBytes => new[] { (byte)'/', (byte)'.', (byte)'l', (byte)'o', (byte)'c', (byte)'k' }; - - private static U8Span CommittedDirectoryPath => new U8Span(CommittedDirectoryBytes); - private static U8Span WorkingDirectoryPath => new U8Span(WorkingDirectoryBytes); - private static U8Span SynchronizingDirectoryPath => new U8Span(SynchronizingDirectoryBytes); - private static U8Span LockFilePath => new U8Span(LockFileBytes); + private static ReadOnlySpan CommittedDirectoryName => new[] { (byte)'/', (byte)'0' }; + private static ReadOnlySpan ModifiedDirectoryName => new[] { (byte)'/', (byte)'1' }; + private static ReadOnlySpan SynchronizingDirectoryName => new[] { (byte)'/', (byte)'_' }; + private static ReadOnlySpan LockFileName => new[] { (byte)'/', (byte)'.', (byte)'l', (byte)'o', (byte)'c', (byte)'k' }; private FileSystemClient _fsClient; private IFileSystem _baseFs; @@ -71,13 +65,26 @@ namespace LibHac.FsSystem private DirectorySaveDataFileSystem _parentFs; private OpenMode _mode; - public DirectorySaveDataFile(DirectorySaveDataFileSystem parentFs, IFile baseFile, OpenMode mode) + public DirectorySaveDataFile(IFile baseFile, DirectorySaveDataFileSystem parentFs, OpenMode mode) { - _parentFs = parentFs; _baseFile = baseFile; + _parentFs = parentFs; _mode = mode; } + public override void Dispose() + { + _baseFile?.Dispose(); + + if (_mode.HasFlag(OpenMode.Write)) + { + _parentFs.DecrementWriteOpenFileCount(); + _mode = default; + } + + base.Dispose(); + } + protected override Result DoRead(out long bytesRead, long offset, Span destination, in ReadOption option) { @@ -109,22 +116,6 @@ namespace LibHac.FsSystem { return _baseFile.OperateRange(outBuffer, operationId, offset, size, inBuffer); } - - protected override void Dispose(bool disposing) - { - if (disposing) - { - _baseFile?.Dispose(); - - if (_mode.HasFlag(OpenMode.Write)) - { - _parentFs.DecrementWriteOpenFileCount(); - _mode = default; - } - } - - base.Dispose(disposing); - } } public static Result CreateNew(out DirectorySaveDataFileSystem created, IFileSystem baseFileSystem, @@ -186,14 +177,64 @@ namespace LibHac.FsSystem _fsClient = fsClient; } - protected override void Dispose(bool disposing) + public override void Dispose() { _lockFile?.Dispose(); _lockFile = null; _cacheObserver?.Unregister(_spaceId, _saveDataId); _baseFs?.Dispose(); - base.Dispose(disposing); + base.Dispose(); + } + + private ref struct RetryClosure + { + public DirectorySaveDataFileSystem This; + public Path CommittedPath; + public Path ModifiedPath; + public Path SynchronizingPath; + + public void Dispose() + { + CommittedPath.Dispose(); + ModifiedPath.Dispose(); + SynchronizingPath.Dispose(); + } + } + + private delegate Result RetryDelegate(in RetryClosure closure); + + private Result RetryFinitelyForTargetLocked(RetryDelegate function, in RetryClosure closure) + { + const int maxRetryCount = 10; + const int retryWaitTimeMs = 100; + + int remainingRetries = maxRetryCount; + + while (true) + { + Result rc = function(in closure); + + if (rc.IsSuccess()) + return rc; + + if (!ResultFs.TargetLocked.Includes(rc)) + return rc; + + if (remainingRetries <= 0) + return rc; + + remainingRetries--; + + if (_fsClient is not null) + { + _fsClient.Hos.Os.SleepThread(TimeSpan.FromMilliSeconds(retryWaitTimeMs)); + } + else + { + System.Threading.Thread.Sleep(retryWaitTimeMs); + } + } } public Result Initialize(bool isJournalingSupported, bool isMultiCommitSupported, bool isJournalingEnabled) @@ -204,8 +245,6 @@ namespace LibHac.FsSystem public Result Initialize(ISaveDataCommitTimeStampGetter timeStampGetter, RandomDataGenerator randomGenerator, bool isJournalingSupported, bool isMultiCommitSupported, bool isJournalingEnabled) { - Result rc; - _isJournalingSupported = isJournalingSupported; _isMultiCommitSupported = isMultiCommitSupported; _isJournalingEnabled = isJournalingEnabled; @@ -213,248 +252,322 @@ namespace LibHac.FsSystem _randomGenerator = randomGenerator ?? _randomGenerator; // Open the lock file - if (_lockFile is null) - { - rc = _baseFs.OpenFile(out _lockFile, LockFilePath, OpenMode.ReadWrite); + Result rc = GetFileSystemLock(); + if (rc.IsFailure()) return rc; - if (rc.IsFailure()) - { - if (!ResultFs.PathNotFound.Includes(rc)) return rc; + var pathModifiedDirectory = new Path(); + rc = PathFunctions.SetUpFixedPath(ref pathModifiedDirectory, ModifiedDirectoryName); + if (rc.IsFailure()) return rc; - rc = _baseFs.CreateFile(LockFilePath, 0); - if (rc.IsFailure()) return rc; + var pathCommittedDirectory = new Path(); + rc = PathFunctions.SetUpFixedPath(ref pathCommittedDirectory, CommittedDirectoryName); + if (rc.IsFailure()) return rc; - rc = _baseFs.OpenFile(out _lockFile, LockFilePath, OpenMode.ReadWrite); - if (rc.IsFailure()) return rc; - } - } + var pathSynchronizingDirectory = new Path(); + rc = PathFunctions.SetUpFixedPath(ref pathSynchronizingDirectory, SynchronizingDirectoryName); + if (rc.IsFailure()) return rc; // Ensure the working directory exists - rc = _baseFs.GetEntryType(out _, WorkingDirectoryPath); + rc = _baseFs.GetEntryType(out _, in pathModifiedDirectory); if (rc.IsFailure()) { - if (!ResultFs.PathNotFound.Includes(rc)) return rc; + if (!ResultFs.PathNotFound.Includes(rc)) + return rc; - rc = _baseFs.CreateDirectory(WorkingDirectoryPath); + rc = _baseFs.CreateDirectory(in pathModifiedDirectory); if (rc.IsFailure()) return rc; if (_isJournalingSupported) { - rc = _baseFs.CreateDirectory(CommittedDirectoryPath); + rc = _baseFs.CreateDirectory(in pathCommittedDirectory); // Nintendo returns on all failures, but we'll keep going if committed already exists // to avoid confusing people manually creating savedata in emulators - if (rc.IsFailure() && !ResultFs.PathAlreadyExists.Includes(rc)) return rc; + if (rc.IsFailure() && !ResultFs.PathAlreadyExists.Includes(rc)) + return rc; } } // Only the working directory is needed for non-journaling savedata - if (!_isJournalingSupported) - return InitializeExtraData(); - - rc = _baseFs.GetEntryType(out _, CommittedDirectoryPath); - - if (rc.IsSuccess()) + if (_isJournalingSupported) { - if (!_isJournalingEnabled) - return InitializeExtraData(); + rc = _baseFs.GetEntryType(out _, in pathCommittedDirectory); - rc = SynchronizeDirectory(WorkingDirectoryPath, CommittedDirectoryPath); - if (rc.IsFailure()) return rc; + if (rc.IsSuccess()) + { + // The previous commit successfully completed. Copy the committed dir to the working dir. + if (_isJournalingEnabled) + { + rc = SynchronizeDirectory(in pathModifiedDirectory, in pathCommittedDirectory); + if (rc.IsFailure()) return rc; + } + } + else if (ResultFs.PathNotFound.Includes(rc)) + { + // If a previous commit failed, the committed dir may be missing. + // Finish that commit by copying the working dir to the committed dir + rc = SynchronizeDirectory(in pathSynchronizingDirectory, in pathModifiedDirectory); + if (rc.IsFailure()) return rc; - return InitializeExtraData(); + rc = _baseFs.RenameDirectory(in pathSynchronizingDirectory, in pathCommittedDirectory); + if (rc.IsFailure()) return rc; + } + else + { + return rc; + } } - if (!ResultFs.PathNotFound.Includes(rc)) return rc; - - // If a previous commit failed, the committed dir may be missing. - // Finish that commit by copying the working dir to the committed dir - - rc = SynchronizeDirectory(SynchronizingDirectoryPath, WorkingDirectoryPath); - if (rc.IsFailure()) return rc; - - rc = _baseFs.RenameDirectory(SynchronizingDirectoryPath, CommittedDirectoryPath); - if (rc.IsFailure()) return rc; - rc = InitializeExtraData(); if (rc.IsFailure()) return rc; + pathModifiedDirectory.Dispose(); + pathCommittedDirectory.Dispose(); + pathSynchronizingDirectory.Dispose(); + return Result.Success; } - private Result ResolveFullPath(Span outPath, U8Span relativePath) + private Result GetFileSystemLock() { - if (StringUtils.GetLength(relativePath, PathTools.MaxPathLength + 1) > PathTools.MaxPathLength) - return ResultFs.TooLongPath.Log(); + // Having an open lock file means we already have the lock for the file system. + if (_lockFile is not null) + return Result.Success; - // Use the committed directory directly if journaling is supported but not enabled - U8Span workingPath = _isJournalingSupported && !_isJournalingEnabled - ? CommittedDirectoryPath - : WorkingDirectoryPath; - - StringUtils.Copy(outPath, workingPath); - outPath[outPath.Length - 1] = StringTraits.NullTerminator; - - return PathNormalizer.Normalize(outPath.Slice(2), out _, relativePath, false, false); - } - - protected override Result DoCreateFile(U8Span path, long size, CreateFileOptions options) - { - Unsafe.SkipInit(out FsPath fullPath); - - Result rc = ResolveFullPath(fullPath.Str, path); + var pathLockFile = new Path(); + Result rc = PathFunctions.SetUpFixedPath(ref pathLockFile, LockFileName); if (rc.IsFailure()) return rc; - using ScopedLock lk = ScopedLock.Lock(ref _mutex); + rc = _baseFs.OpenFile(out _lockFile, in pathLockFile, OpenMode.ReadWrite); - return _baseFs.CreateFile(fullPath, size, options); - } - - protected override Result DoDeleteFile(U8Span path) - { - Unsafe.SkipInit(out FsPath fullPath); - - Result rc = ResolveFullPath(fullPath.Str, path); - if (rc.IsFailure()) return rc; - - using ScopedLock lk = ScopedLock.Lock(ref _mutex); - - return _baseFs.DeleteFile(fullPath); - } - - protected override Result DoCreateDirectory(U8Span path) - { - Unsafe.SkipInit(out FsPath fullPath); - - Result rc = ResolveFullPath(fullPath.Str, path); - if (rc.IsFailure()) return rc; - - using ScopedLock lk = ScopedLock.Lock(ref _mutex); - - return _baseFs.CreateDirectory(fullPath); - } - - protected override Result DoDeleteDirectory(U8Span path) - { - Unsafe.SkipInit(out FsPath fullPath); - - Result rc = ResolveFullPath(fullPath.Str, path); - if (rc.IsFailure()) return rc; - - using ScopedLock lk = ScopedLock.Lock(ref _mutex); - - return _baseFs.DeleteDirectory(fullPath); - } - - protected override Result DoDeleteDirectoryRecursively(U8Span path) - { - Unsafe.SkipInit(out FsPath fullPath); - - Result rc = ResolveFullPath(fullPath.Str, path); - if (rc.IsFailure()) return rc; - - using ScopedLock lk = ScopedLock.Lock(ref _mutex); - - return _baseFs.DeleteDirectoryRecursively(fullPath); - } - - protected override Result DoCleanDirectoryRecursively(U8Span path) - { - Unsafe.SkipInit(out FsPath fullPath); - - Result rc = ResolveFullPath(fullPath.Str, path); - if (rc.IsFailure()) return rc; - - using ScopedLock lk = ScopedLock.Lock(ref _mutex); - - return _baseFs.CleanDirectoryRecursively(fullPath); - } - - protected override Result DoRenameFile(U8Span oldPath, U8Span newPath) - { - Unsafe.SkipInit(out FsPath fullCurrentPath); - Unsafe.SkipInit(out FsPath fullNewPath); - - Result rc = ResolveFullPath(fullCurrentPath.Str, oldPath); - if (rc.IsFailure()) return rc; - - rc = ResolveFullPath(fullNewPath.Str, newPath); - if (rc.IsFailure()) return rc; - - using ScopedLock lk = ScopedLock.Lock(ref _mutex); - - return _baseFs.RenameFile(fullCurrentPath, fullNewPath); - } - - protected override Result DoRenameDirectory(U8Span oldPath, U8Span newPath) - { - Unsafe.SkipInit(out FsPath fullCurrentPath); - Unsafe.SkipInit(out FsPath fullNewPath); - - Result rc = ResolveFullPath(fullCurrentPath.Str, oldPath); - if (rc.IsFailure()) return rc; - - rc = ResolveFullPath(fullNewPath.Str, newPath); - if (rc.IsFailure()) return rc; - - using ScopedLock lk = ScopedLock.Lock(ref _mutex); - - return _baseFs.RenameDirectory(fullCurrentPath, fullNewPath); - } - - protected override Result DoGetEntryType(out DirectoryEntryType entryType, U8Span path) - { - Unsafe.SkipInit(out FsPath fullPath); - - Result rc = ResolveFullPath(fullPath.Str, path); if (rc.IsFailure()) { - UnsafeHelpers.SkipParamInit(out entryType); - return rc; + if (ResultFs.PathNotFound.Includes(rc)) + { + rc = _baseFs.CreateFile(in pathLockFile, 0); + if (rc.IsFailure()) return rc; + + rc = _baseFs.OpenFile(out _lockFile, in pathLockFile, OpenMode.ReadWrite); + if (rc.IsFailure()) return rc; + } + else + { + return rc; + } } - using ScopedLock lk = ScopedLock.Lock(ref _mutex); - - return _baseFs.GetEntryType(out entryType, fullPath); + pathLockFile.Dispose(); + return Result.Success; } - protected override Result DoOpenFile(out IFile file, U8Span path, OpenMode mode) + private Result ResolvePath(ref Path outFullPath, in Path path) + { + var pathDirectoryName = new Path(); + + // Use the committed directory directly if journaling is supported but not enabled + ReadOnlySpan directoryName = _isJournalingSupported && !_isJournalingEnabled + ? CommittedDirectoryName + : ModifiedDirectoryName; + + Result rc = PathFunctions.SetUpFixedPath(ref pathDirectoryName, directoryName); + if (rc.IsFailure()) return rc; + + rc = outFullPath.Combine(in pathDirectoryName, in path); + if (rc.IsFailure()) return rc; + + pathDirectoryName.Dispose(); + return Result.Success; + } + + protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option) + { + var fullPath = new Path(); + Result rc = ResolvePath(ref fullPath, in path); + if (rc.IsFailure()) return rc; + + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + rc = _baseFs.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 = ResolvePath(ref fullPath, in path); + if (rc.IsFailure()) return rc; + + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + rc = _baseFs.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 = ResolvePath(ref fullPath, in path); + if (rc.IsFailure()) return rc; + + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + rc = _baseFs.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 = ResolvePath(ref fullPath, in path); + if (rc.IsFailure()) return rc; + + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + rc = _baseFs.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 = ResolvePath(ref fullPath, in path); + if (rc.IsFailure()) return rc; + + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + rc = _baseFs.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 = ResolvePath(ref fullPath, in path); + if (rc.IsFailure()) return rc; + + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + rc = _baseFs.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(); + var newFullPath = new Path(); + + Result rc = ResolvePath(ref currentFullPath, in currentPath); + if (rc.IsFailure()) return rc; + + rc = ResolvePath(ref newFullPath, in newPath); + if (rc.IsFailure()) return rc; + + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + rc = _baseFs.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(); + var newFullPath = new Path(); + + Result rc = ResolvePath(ref currentFullPath, in currentPath); + if (rc.IsFailure()) return rc; + + rc = ResolvePath(ref newFullPath, in newPath); + if (rc.IsFailure()) return rc; + + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + rc = _baseFs.RenameDirectory(in currentFullPath, in newFullPath); + if (rc.IsFailure()) return rc; + + currentFullPath.Dispose(); + newFullPath.Dispose(); + return Result.Success; + } + + protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path) + { + UnsafeHelpers.SkipParamInit(out entryType); + + var fullPath = new Path(); + Result rc = ResolvePath(ref fullPath, in path); + if (rc.IsFailure()) return rc; + + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + rc = _baseFs.GetEntryType(out entryType, 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); - Unsafe.SkipInit(out FsPath fullPath); - - Result rc = ResolveFullPath(fullPath.Str, path); + var fullPath = new Path(); + Result rc = ResolvePath(ref fullPath, in path); if (rc.IsFailure()) return rc; - using ScopedLock lk = ScopedLock.Lock(ref _mutex); + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - rc = _baseFs.OpenFile(out IFile baseFile, fullPath, mode); + rc = _baseFs.OpenFile(out IFile baseFile, in fullPath, mode); if (rc.IsFailure()) return rc; - file = new DirectorySaveDataFile(this, baseFile, mode); + file = new DirectorySaveDataFile(baseFile, this, mode); if (mode.HasFlag(OpenMode.Write)) { _openWritableFileCount++; } + fullPath.Dispose(); 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 fullPath); - - Result rc = ResolveFullPath(fullPath.Str, path); + var fullPath = new Path(); + Result rc = ResolvePath(ref fullPath, in path); if (rc.IsFailure()) return rc; - using ScopedLock lk = ScopedLock.Lock(ref _mutex); + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - return _baseFs.OpenDirectory(out directory, fullPath, mode); + rc = _baseFs.OpenDirectory(out directory, in fullPath, mode); + if (rc.IsFailure()) return rc; + + fullPath.Dispose(); + return Result.Success; } /// @@ -463,12 +576,12 @@ namespace LibHac.FsSystem /// The path of the destination directory. /// The path of the source directory. /// The of the operation. - private Result SynchronizeDirectory(U8Span destPath, U8Span sourcePath) + private Result SynchronizeDirectory(in Path destPath, in Path sourcePath) { // Delete destination dir and recreate it. Result rc = _baseFs.DeleteDirectoryRecursively(destPath); - // Nintendo returns error unconditionally because SynchronizeDirectory is always called in situations + // Nintendo returns all errors unconditionally because SynchronizeDirectory is always called in situations // where a PathNotFound error would mean the save directory was in an invalid state. // We'll ignore PathNotFound errors to be more user-friendly to users who might accidentally // put the save directory in an invalid state. @@ -477,22 +590,26 @@ namespace LibHac.FsSystem rc = _baseFs.CreateDirectory(destPath); if (rc.IsFailure()) return rc; + var directoryEntry = new DirectoryEntry(); + // Lock only if initialized with a client if (_fsClient is not null) { - using ScopedLock lk = + using ScopedLock scopedLock = ScopedLock.Lock(ref _fsClient.Globals.DirectorySaveDataFileSystem.SynchronizeDirectoryMutex); using (var buffer = new RentedArray(IdealWorkBufferSize)) { - return Utility.CopyDirectoryRecursively(_baseFs, destPath, sourcePath, buffer.Span); + return Utility12.CopyDirectoryRecursively(_baseFs, in destPath, in sourcePath, ref directoryEntry, + buffer.Span); } } else { using (var buffer = new RentedArray(IdealWorkBufferSize)) { - return Utility.CopyDirectoryRecursively(_baseFs, destPath, sourcePath, buffer.Span); + return Utility12.CopyDirectoryRecursively(_baseFs, in destPath, in sourcePath, ref directoryEntry, + buffer.Span); } } } @@ -504,31 +621,56 @@ namespace LibHac.FsSystem if (!_isJournalingEnabled || !_isJournalingSupported) return Result.Success; + var closure = new RetryClosure(); + closure.This = this; + + Result rc = PathFunctions.SetUpFixedPath(ref closure.ModifiedPath, ModifiedDirectoryName); + if (rc.IsFailure()) return rc; + + rc = PathFunctions.SetUpFixedPath(ref closure.CommittedPath, CommittedDirectoryName); + if (rc.IsFailure()) return rc; + + rc = PathFunctions.SetUpFixedPath(ref closure.SynchronizingPath, SynchronizingDirectoryName); + if (rc.IsFailure()) return rc; + if (_openWritableFileCount > 0) { // All files must be closed before commiting save data. return ResultFs.WriteModeFileNotClosed.Log(); } - Result RenameCommittedDir() => _baseFs.RenameDirectory(CommittedDirectoryPath, SynchronizingDirectoryPath); - Result SynchronizeWorkingDir() => SynchronizeDirectory(SynchronizingDirectoryPath, WorkingDirectoryPath); + static Result RenameCommittedDir(in RetryClosure closure) + { + return closure.This._baseFs.RenameDirectory(in closure.CommittedPath, + in closure.SynchronizingPath); + } - Result RenameSynchronizingDir() => - _baseFs.RenameDirectory(SynchronizingDirectoryPath, CommittedDirectoryPath); + static Result SynchronizeWorkingDir(in RetryClosure closure) + { + return closure.This.SynchronizeDirectory(in closure.SynchronizingPath, + in closure.ModifiedPath); + } - // Get rid of the previous commit by renaming the folder - Result rc = Utility.RetryFinitelyForTargetLocked(RenameCommittedDir); + static Result RenameSynchronizingDir(in RetryClosure closure) + { + return closure.This._baseFs.RenameDirectory(in closure.SynchronizingPath, + in closure.CommittedPath); + } + + // Get rid of the previous commit by renaming the folder. + rc = RetryFinitelyForTargetLocked(RenameCommittedDir, in closure); if (rc.IsFailure()) return rc; // If something goes wrong beyond this point, the commit will be - // completed the next time the savedata is opened + // completed the next time the savedata is opened. - rc = Utility.RetryFinitelyForTargetLocked(SynchronizeWorkingDir); + rc = RetryFinitelyForTargetLocked(SynchronizeWorkingDir, in closure); if (rc.IsFailure()) return rc; - rc = Utility.RetryFinitelyForTargetLocked(RenameSynchronizingDir); + rc = RetryFinitelyForTargetLocked(RenameSynchronizingDir, in closure); if (rc.IsFailure()) return rc; + closure.Dispose(); return Result.Success; } @@ -549,37 +691,43 @@ namespace LibHac.FsSystem return Initialize(_isJournalingSupported, _isMultiCommitSupported, _isJournalingEnabled); } - protected override Result DoGetFreeSpaceSize(out long freeSpace, U8Span path) + protected override Result DoGetFreeSpaceSize(out long freeSpace, in Path path) { UnsafeHelpers.SkipParamInit(out freeSpace); - Unsafe.SkipInit(out FsPath fullPath); + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - Result rc = ResolveFullPath(fullPath.Str, path); + var pathModifiedDirectory = new Path(); + Result rc = PathFunctions.SetUpFixedPath(ref pathModifiedDirectory, ModifiedDirectoryName); if (rc.IsFailure()) return rc; - using ScopedLock lk = ScopedLock.Lock(ref _mutex); + rc = _baseFs.GetFreeSpaceSize(out freeSpace, in pathModifiedDirectory); + if (rc.IsFailure()) return rc; - return _baseFs.GetFreeSpaceSize(out freeSpace, fullPath); + pathModifiedDirectory.Dispose(); + 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); - Unsafe.SkipInit(out FsPath fullPath); + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - Result rc = ResolveFullPath(fullPath.Str, path); + var pathModifiedDirectory = new Path(); + Result rc = PathFunctions.SetUpFixedPath(ref pathModifiedDirectory, ModifiedDirectoryName); if (rc.IsFailure()) return rc; - using ScopedLock lk = ScopedLock.Lock(ref _mutex); + rc = _baseFs.GetTotalSpaceSize(out totalSpace, in pathModifiedDirectory); + if (rc.IsFailure()) return rc; - return _baseFs.GetTotalSpaceSize(out totalSpace, fullPath); + pathModifiedDirectory.Dispose(); + return Result.Success; } internal void DecrementWriteOpenFileCount() { - using ScopedLock lk = ScopedLock.Lock(ref _mutex); + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); _openWritableFileCount--; } @@ -587,93 +735,111 @@ namespace LibHac.FsSystem // The original class doesn't support extra data. // Everything below this point is a LibHac extension. - private static ReadOnlySpan CommittedExtraDataBytes => // "/ExtraData0" + private static ReadOnlySpan CommittedExtraDataName => // "/ExtraData0" new[] { (byte)'/', (byte)'E', (byte)'x', (byte)'t', (byte)'r', (byte)'a', (byte)'D', (byte)'a', (byte)'t', (byte)'a', (byte)'0' }; - private static ReadOnlySpan WorkingExtraDataBytes => // "/ExtraData1" + private static ReadOnlySpan ModifiedExtraDataName => // "/ExtraData1" new[] { (byte)'/', (byte)'E', (byte)'x', (byte)'t', (byte)'r', (byte)'a', (byte)'D', (byte)'a', (byte)'t', (byte)'a', (byte)'1' }; - private static ReadOnlySpan SynchronizingExtraDataBytes => // "/ExtraData_" + private static ReadOnlySpan SynchronizingExtraDataName => // "/ExtraData_" new[] { (byte)'/', (byte)'E', (byte)'x', (byte)'t', (byte)'r', (byte)'a', (byte)'D', (byte)'a', (byte)'t', (byte)'a', (byte)'_' }; - private U8Span CommittedExtraDataPath => new U8Span(CommittedExtraDataBytes); - private U8Span WorkingExtraDataPath => new U8Span(WorkingExtraDataBytes); - private U8Span SynchronizingExtraDataPath => new U8Span(SynchronizingExtraDataBytes); - private Result InitializeExtraData() { + var pathModifiedExtraData = new Path(); + Result rc = PathFunctions.SetUpFixedPath(ref pathModifiedExtraData, ModifiedExtraDataName); + if (rc.IsFailure()) return rc; + + var pathCommittedExtraData = new Path(); + rc = PathFunctions.SetUpFixedPath(ref pathCommittedExtraData, CommittedExtraDataName); + if (rc.IsFailure()) return rc; + + var pathSynchronizingExtraData = new Path(); + rc = PathFunctions.SetUpFixedPath(ref pathSynchronizingExtraData, SynchronizingExtraDataName); + if (rc.IsFailure()) return rc; + // Ensure the extra data files exist - Result rc = _baseFs.GetEntryType(out _, WorkingExtraDataPath); + rc = _baseFs.GetEntryType(out _, in pathModifiedExtraData); if (rc.IsFailure()) { - if (!ResultFs.PathNotFound.Includes(rc)) return rc; + if (!ResultFs.PathNotFound.Includes(rc)) + return rc; - rc = _baseFs.CreateFile(WorkingExtraDataPath, Unsafe.SizeOf()); + rc = _baseFs.CreateFile(in pathModifiedExtraData, Unsafe.SizeOf()); if (rc.IsFailure()) return rc; if (_isJournalingSupported) { - rc = _baseFs.CreateFile(CommittedExtraDataPath, Unsafe.SizeOf()); - if (rc.IsFailure() && !ResultFs.PathAlreadyExists.Includes(rc)) return rc; + rc = _baseFs.CreateFile(in pathCommittedExtraData, Unsafe.SizeOf()); + if (rc.IsFailure() && !ResultFs.PathAlreadyExists.Includes(rc)) + return rc; } } else { // If the working file exists make sure it's the right size - rc = EnsureExtraDataSize(WorkingExtraDataPath); + rc = EnsureExtraDataSize(in pathModifiedExtraData); if (rc.IsFailure()) return rc; } // Only the working extra data is needed for non-journaling savedata - if (!_isJournalingSupported) - return Result.Success; - - rc = _baseFs.GetEntryType(out _, CommittedExtraDataPath); - - if (rc.IsSuccess()) + if (_isJournalingSupported) { - rc = EnsureExtraDataSize(CommittedExtraDataPath); - if (rc.IsFailure()) return rc; + rc = _baseFs.GetEntryType(out _, in pathCommittedExtraData); - if (!_isJournalingEnabled) - return Result.Success; + if (rc.IsSuccess()) + { + rc = EnsureExtraDataSize(in pathCommittedExtraData); + if (rc.IsFailure()) return rc; - return SynchronizeExtraData(WorkingExtraDataPath, CommittedExtraDataPath); + if (_isJournalingEnabled) + { + rc = SynchronizeExtraData(in pathModifiedExtraData, in pathCommittedExtraData); + if (rc.IsFailure()) return rc; + } + } + else if (ResultFs.PathNotFound.Includes(rc)) + { + // If a previous commit failed, the committed extra data may be missing. + // Finish that commit by copying the working extra data to the committed extra data + rc = SynchronizeExtraData(in pathSynchronizingExtraData, in pathModifiedExtraData); + if (rc.IsFailure()) return rc; + + rc = _baseFs.RenameFile(in pathSynchronizingExtraData, in pathCommittedExtraData); + if (rc.IsFailure()) return rc; + } + else + { + return rc; + } } - if (!ResultFs.PathNotFound.Includes(rc)) return rc; - - // If a previous commit failed, the committed extra data may be missing. - // Finish that commit by copying the working extra data to the committed extra data - - rc = SynchronizeExtraData(SynchronizingExtraDataPath, WorkingExtraDataPath); - if (rc.IsFailure()) return rc; - - rc = _baseFs.RenameFile(SynchronizingExtraDataPath, CommittedExtraDataPath); - if (rc.IsFailure()) return rc; + pathModifiedExtraData.Dispose(); + pathCommittedExtraData.Dispose(); + pathSynchronizingExtraData.Dispose(); return Result.Success; } - private Result EnsureExtraDataSize(U8Span path) + private Result EnsureExtraDataSize(in Path path) { IFile file = null; try { - Result rc = _baseFs.OpenFile(out file, path, OpenMode.ReadWrite); + Result rc = _baseFs.OpenFile(out file, in path, OpenMode.ReadWrite); if (rc.IsFailure()) return rc; rc = file.GetSize(out long fileSize); @@ -690,11 +856,11 @@ namespace LibHac.FsSystem } } - private Result SynchronizeExtraData(U8Span destPath, U8Span sourcePath) + private Result SynchronizeExtraData(in Path destPath, in Path sourcePath) { Span workBuffer = stackalloc byte[Unsafe.SizeOf()]; - Result rc = _baseFs.OpenFile(out IFile sourceFile, sourcePath, OpenMode.Read); + Result rc = _baseFs.OpenFile(out IFile sourceFile, in sourcePath, OpenMode.Read); if (rc.IsFailure()) return rc; using (sourceFile) @@ -705,7 +871,7 @@ namespace LibHac.FsSystem Assert.SdkEqual(bytesRead, Unsafe.SizeOf()); } - rc = _baseFs.OpenFile(out IFile destFile, destPath, OpenMode.Write); + rc = _baseFs.OpenFile(out IFile destFile, in destPath, OpenMode.Write); if (rc.IsFailure()) return rc; using (destFile) @@ -717,23 +883,25 @@ namespace LibHac.FsSystem return Result.Success; } - private U8Span GetExtraDataPath() + private Result GetExtraDataPath(ref Path path) { - return _isJournalingSupported && !_isJournalingEnabled - ? CommittedExtraDataPath - : WorkingExtraDataPath; + ReadOnlySpan extraDataName = _isJournalingSupported && !_isJournalingEnabled + ? CommittedExtraDataName + : ModifiedExtraDataName; + + return PathFunctions.SetUpFixedPath(ref path, extraDataName); } public Result WriteExtraData(in SaveDataExtraData extraData) { - using ScopedLock lk = ScopedLock.Lock(ref _mutex); + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); return WriteExtraDataImpl(in extraData); } public Result CommitExtraData(bool updateTimeStamp) { - using ScopedLock lk = ScopedLock.Lock(ref _mutex); + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); if (updateTimeStamp && _timeStampGetter is not null && _randomGenerator is not null) { @@ -746,7 +914,7 @@ namespace LibHac.FsSystem public Result ReadExtraData(out SaveDataExtraData extraData) { - using ScopedLock lk = ScopedLock.Lock(ref _mutex); + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); return ReadExtraDataImpl(out extraData); } @@ -779,13 +947,21 @@ namespace LibHac.FsSystem { Assert.SdkRequires(_mutex.IsLockedByCurrentThread()); - Result rc = _baseFs.OpenFile(out IFile file, GetExtraDataPath(), OpenMode.Write); + var pathExtraData = new Path(); + Result rc = GetExtraDataPath(ref pathExtraData); + if (rc.IsFailure()) return rc; + + rc = _baseFs.OpenFile(out IFile file, in pathExtraData, OpenMode.Write); if (rc.IsFailure()) return rc; using (file) { - return file.Write(0, SpanHelpers.AsReadOnlyByteSpan(in extraData), WriteOption.Flush); + rc = file.Write(0, SpanHelpers.AsReadOnlyByteSpan(in extraData), WriteOption.Flush); + if (rc.IsFailure()) return rc; } + + pathExtraData.Dispose(); + return Result.Success; } private Result CommitExtraDataImpl() @@ -795,33 +971,64 @@ namespace LibHac.FsSystem if (!_isJournalingSupported || !_isJournalingEnabled) return Result.Success; - Result RenameCommittedFile() => _baseFs.RenameFile(CommittedExtraDataPath, SynchronizingExtraDataPath); - Result SynchronizeWorkingFile() => SynchronizeExtraData(SynchronizingExtraDataPath, WorkingExtraDataPath); - Result RenameSynchronizingFile() => _baseFs.RenameFile(SynchronizingExtraDataPath, CommittedExtraDataPath); + var closure = new RetryClosure(); + closure.This = this; - // Get rid of the previous commit by renaming the folder - Result rc = Utility.RetryFinitelyForTargetLocked(RenameCommittedFile); + Result rc = PathFunctions.SetUpFixedPath(ref closure.ModifiedPath, ModifiedExtraDataName); + if (rc.IsFailure()) return rc; + + rc = PathFunctions.SetUpFixedPath(ref closure.CommittedPath, CommittedExtraDataName); + if (rc.IsFailure()) return rc; + + rc = PathFunctions.SetUpFixedPath(ref closure.SynchronizingPath, SynchronizingExtraDataName); + if (rc.IsFailure()) return rc; + + static Result RenameCommittedFile(in RetryClosure closure) + { + return closure.This._baseFs.RenameFile(in closure.CommittedPath, + in closure.SynchronizingPath); + } + + static Result SynchronizeWorkingFile(in RetryClosure closure) + { + return closure.This.SynchronizeExtraData(in closure.SynchronizingPath, + in closure.ModifiedPath); + } + + static Result RenameSynchronizingFile(in RetryClosure closure) + { + return closure.This._baseFs.RenameFile(in closure.SynchronizingPath, + in closure.CommittedPath); + } + + // Get rid of the previous commit by renaming the file. + rc = RetryFinitelyForTargetLocked(RenameCommittedFile, in closure); if (rc.IsFailure()) return rc; // If something goes wrong beyond this point, the commit will be - // completed the next time the savedata is opened + // completed the next time the savedata is opened. - rc = Utility.RetryFinitelyForTargetLocked(SynchronizeWorkingFile); + rc = RetryFinitelyForTargetLocked(SynchronizeWorkingFile, in closure); if (rc.IsFailure()) return rc; - rc = Utility.RetryFinitelyForTargetLocked(RenameSynchronizingFile); + rc = RetryFinitelyForTargetLocked(RenameSynchronizingFile, in closure); if (rc.IsFailure()) return rc; + closure.Dispose(); return Result.Success; } private Result ReadExtraDataImpl(out SaveDataExtraData extraData) { - Assert.SdkRequires(_mutex.IsLockedByCurrentThread()); - UnsafeHelpers.SkipParamInit(out extraData); - Result rc = _baseFs.OpenFile(out IFile file, GetExtraDataPath(), OpenMode.Read); + Assert.SdkRequires(_mutex.IsLockedByCurrentThread()); + + var pathExtraData = new Path(); + Result rc = GetExtraDataPath(ref pathExtraData); + if (rc.IsFailure()) return rc; + + rc = _baseFs.OpenFile(out IFile file, in pathExtraData, OpenMode.Read); if (rc.IsFailure()) return rc; using (file) @@ -830,9 +1037,10 @@ namespace LibHac.FsSystem if (rc.IsFailure()) return rc; Assert.SdkEqual(bytesRead, Unsafe.SizeOf()); - - return Result.Success; } + + pathExtraData.Dispose(); + return Result.Success; } public void RegisterCacheObserver(ISaveDataExtraDataAccessorCacheObserver observer, SaveDataSpaceId spaceId, @@ -846,4 +1054,4 @@ namespace LibHac.FsSystem public SaveDataSpaceId GetSaveDataSpaceId() => _spaceId; public ulong GetSaveDataId() => _saveDataId; } -} \ No newline at end of file +} diff --git a/src/LibHac/FsSystem/DirectoryUtils.cs b/src/LibHac/FsSystem/DirectoryUtils.cs deleted file mode 100644 index 260eb9ae..00000000 --- a/src/LibHac/FsSystem/DirectoryUtils.cs +++ /dev/null @@ -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 path, ref DirectoryEntry entry); - - public static Result IterateDirectoryRecursivelyInternal(IFileSystem fs, Span 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 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 destParentPath, - ReadOnlySpan sourcePath, ref DirectoryEntry dirEntry, Span 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(); - } - } - } -} diff --git a/src/LibHac/FsSystem/FileSystemExtensions.cs b/src/LibHac/FsSystem/FileSystemExtensions.cs index e184af4d..30c9e821 100644 --- a/src/LibHac/FsSystem/FileSystemExtensions.cs +++ b/src/LibHac/FsSystem/FileSystemExtensions.cs @@ -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.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.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 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 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 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.Empty, Span.Empty, QueryId.MakeConcatFile, path.ToU8Span()); + var pathNormalized = new Path(); + InitializeFromString(ref pathNormalized, path).ThrowIfFailure(); + + fs.QueryEntry(Span.Empty, Span.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 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; } } diff --git a/src/LibHac/FsSystem/ForwardingFileSystem.cs b/src/LibHac/FsSystem/ForwardingFileSystem.cs index a230168c..2857923d 100644 --- a/src/LibHac/FsSystem/ForwardingFileSystem.cs +++ b/src/LibHac/FsSystem/ForwardingFileSystem.cs @@ -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 outBuffer, ReadOnlySpan inBuffer, QueryId queryId, - U8Span path) => BaseFileSystem.Target.QueryEntry(outBuffer, inBuffer, queryId, path); + in Path path) => BaseFileSystem.Target.QueryEntry(outBuffer, inBuffer, queryId, path); } } diff --git a/src/LibHac/FsSystem/IUniqueLock.cs b/src/LibHac/FsSystem/IUniqueLock.cs new file mode 100644 index 00000000..e429c909 --- /dev/null +++ b/src/LibHac/FsSystem/IUniqueLock.cs @@ -0,0 +1,31 @@ +using System; +using LibHac.Common; +using LibHac.Os; + +namespace LibHac.FsSystem +{ + public interface IUniqueLock : IDisposable { } + + public class UniqueLockWithPin : IUniqueLock where T : class, IDisposable + { + private UniqueLock _semaphore; + private ReferenceCountedDisposable _pinnedObject; + + public UniqueLockWithPin(ref UniqueLock semaphore, ref ReferenceCountedDisposable 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; + } + } + } +} diff --git a/src/LibHac/FsSystem/LayeredFileSystem.cs b/src/LibHac/FsSystem/LayeredFileSystem.cs index f53059c3..ec089b70 100644 --- a/src/LibHac/FsSystem/LayeredFileSystem.cs +++ b/src/LibHac/FsSystem/LayeredFileSystem.cs @@ -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 outBuffer, ReadOnlySpan 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 SourceFileSystems { get; } private List SourceDirs { get; } - private U8String Path { get; } + private Path.Stored _path; private OpenDirectoryMode Mode { get; } // todo: Efficient way to remove duplicates private HashSet Names { get; } = new HashSet(); - public MergedDirectory(List sourceFileSystems, U8Span path, OpenDirectoryMode mode) + public MergedDirectory(List sourceFileSystems, OpenDirectoryMode mode) { SourceFileSystems = sourceFileSystems; SourceDirs = new List(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(); + 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; diff --git a/src/LibHac/FsSystem/LocalFile.cs b/src/LibHac/FsSystem/LocalFile.cs index e4faf0d8..a524c5f1 100644 --- a/src/LibHac/FsSystem/LocalFile.cs +++ b/src/LibHac/FsSystem/LocalFile.cs @@ -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(); } } } diff --git a/src/LibHac/FsSystem/LocalFileSystem.cs b/src/LibHac/FsSystem/LocalFileSystem.cs index 5a4686a8..b50d60ec 100644 --- a/src/LibHac/FsSystem/LocalFileSystem.cs +++ b/src/LibHac/FsSystem/LocalFileSystem.cs @@ -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 /// The path that will be the root of the . 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 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 outBuffer, ReadOnlySpan 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 diff --git a/src/LibHac/FsSystem/PartitionFileSystem.cs b/src/LibHac/FsSystem/PartitionFileSystem.cs index f453c642..9aaf4707 100644 --- a/src/LibHac/FsSystem/PartitionFileSystem.cs +++ b/src/LibHac/FsSystem/PartitionFileSystem.cs @@ -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() { diff --git a/src/LibHac/FsSystem/PartitionFileSystemCore.cs b/src/LibHac/FsSystem/PartitionFileSystemCore.cs index 480da5d4..1c6726f5 100644 --- a/src/LibHac/FsSystem/PartitionFileSystemCore.cs +++ b/src/LibHac/FsSystem/PartitionFileSystemCore.cs @@ -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 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 pathStr = path.GetString(); + + if (path.IsEmpty() || pathStr[0] != '/') return ResultFs.InvalidPathFormat.Log(); ReadOnlySpan 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 diff --git a/src/LibHac/FsSystem/PathTools.cs b/src/LibHac/FsSystem/PathTools.cs index 39aee040..34f7ed24 100644 --- a/src/LibHac/FsSystem/PathTools.cs +++ b/src/LibHac/FsSystem/PathTools.cs @@ -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 GetParentDirectory(ReadOnlySpan 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; diff --git a/src/LibHac/FsSystem/ReadOnlyFileSystem.cs b/src/LibHac/FsSystem/ReadOnlyFileSystem.cs index 80fed8ba..475c9152 100644 --- a/src/LibHac/FsSystem/ReadOnlyFileSystem.cs +++ b/src/LibHac/FsSystem/ReadOnlyFileSystem.cs @@ -28,12 +28,12 @@ namespace LibHac.FsSystem return new ReferenceCountedDisposable(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(); } } } diff --git a/src/LibHac/FsSystem/RomFs/RomFsFileSystem.cs b/src/LibHac/FsSystem/RomFs/RomFsFileSystem.cs index ed228226..f0f091d2 100644 --- a/src/LibHac/FsSystem/RomFs/RomFsFileSystem.cs +++ b/src/LibHac/FsSystem/RomFs/RomFsFileSystem.cs @@ -25,7 +25,7 @@ namespace LibHac.FsSystem.RomFs FileTable = new HierarchicalRomFileTable(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(); diff --git a/src/LibHac/FsSystem/Save/SaveDataFileSystem.cs b/src/LibHac/FsSystem/Save/SaveDataFileSystem.cs index 01d2e10a..cb5bbe3a 100644 --- a/src/LibHac/FsSystem/Save/SaveDataFileSystem.cs +++ b/src/LibHac/FsSystem/Save/SaveDataFileSystem.cs @@ -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(); } } } diff --git a/src/LibHac/FsSystem/Save/SaveDataFileSystemCore.cs b/src/LibHac/FsSystem/Save/SaveDataFileSystemCore.cs index b6110e81..da09f40d 100644 --- a/src/LibHac/FsSystem/Save/SaveDataFileSystemCore.cs +++ b/src/LibHac/FsSystem/Save/SaveDataFileSystemCore.cs @@ -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; diff --git a/src/LibHac/FsSystem/SaveDataFileSystemCacheRegisterBase.cs b/src/LibHac/FsSystem/SaveDataFileSystemCacheRegisterBase.cs index 20d7f42e..013de88b 100644 --- a/src/LibHac/FsSystem/SaveDataFileSystemCacheRegisterBase.cs +++ b/src/LibHac/FsSystem/SaveDataFileSystemCacheRegisterBase.cs @@ -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); } diff --git a/src/LibHac/FsSystem/SemaphoreAdaptor.cs b/src/LibHac/FsSystem/SemaphoreAdapter.cs similarity index 69% rename from src/LibHac/FsSystem/SemaphoreAdaptor.cs rename to src/LibHac/FsSystem/SemaphoreAdapter.cs index d787d923..46f94dcf 100644 --- a/src/LibHac/FsSystem/SemaphoreAdaptor.cs +++ b/src/LibHac/FsSystem/SemaphoreAdapter.cs @@ -1,13 +1,14 @@ using System; using System.Threading; +using LibHac.Os; namespace LibHac.FsSystem { - public class SemaphoreAdaptor : IDisposable + public class SemaphoreAdapter : IDisposable, ILockable { private SemaphoreSlim _semaphore; - public SemaphoreAdaptor(int initialCount, int maxCount) + public SemaphoreAdapter(int initialCount, int maxCount) { _semaphore = new SemaphoreSlim(initialCount, maxCount); } @@ -17,6 +18,11 @@ namespace LibHac.FsSystem return _semaphore.Wait(System.TimeSpan.Zero); } + public void Lock() + { + _semaphore.Wait(); + } + public void Unlock() { _semaphore.Release(); diff --git a/src/LibHac/FsSystem/StorageLayoutTypeSetFileSystem.cs b/src/LibHac/FsSystem/StorageLayoutTypeSetFileSystem.cs index fb089897..28d50bc3 100644 --- a/src/LibHac/FsSystem/StorageLayoutTypeSetFileSystem.cs +++ b/src/LibHac/FsSystem/StorageLayoutTypeSetFileSystem.cs @@ -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 outBuffer, ReadOnlySpan inBuffer, QueryId queryId, U8Span path) + protected override Result DoQueryEntry(Span outBuffer, ReadOnlySpan inBuffer, QueryId queryId, + in Path path) { using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag); return BaseFileSystem.Target.QueryEntry(outBuffer, inBuffer, queryId, path); diff --git a/src/LibHac/FsSystem/SubdirectoryFileSystem.cs b/src/LibHac/FsSystem/SubdirectoryFileSystem.cs index a3bc8e69..e527a018 100644 --- a/src/LibHac/FsSystem/SubdirectoryFileSystem.cs +++ b/src/LibHac/FsSystem/SubdirectoryFileSystem.cs @@ -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 { + /// + /// An that uses a directory of another as its root directory. + /// + /// Based on FS 12.0.3 (nnSdk 12.3.1) public class SubdirectoryFileSystem : IFileSystem { - private IFileSystem BaseFileSystem { get; } - private ReferenceCountedDisposable BaseFileSystemShared { get; } - private U8String RootPath { get; set; } - private bool PreserveUnc { get; } + private IFileSystem _baseFileSystem; + private ReferenceCountedDisposable _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 baseFileSystem) { - BaseFileSystem = baseFileSystem; - PreserveUnc = preserveUnc; + _baseFileSystemShared = Shared.Move(ref baseFileSystem); + _baseFileSystem = _baseFileSystemShared.Target; } - public SubdirectoryFileSystem(ref ReferenceCountedDisposable baseFileSystem, bool preserveUnc = false) + public override void Dispose() { - BaseFileSystemShared = Shared.Move(ref baseFileSystem); - BaseFileSystem = BaseFileSystemShared.Target; - PreserveUnc = preserveUnc; + ReferenceCountedDisposable 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 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 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 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 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 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 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 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 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 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 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 fullOldPath = stackalloc byte[PathTools.MaxPathLength + 1]; - Span 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 fullOldPath = stackalloc byte[PathTools.MaxPathLength + 1]; - Span 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 outBuffer, ReadOnlySpan 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 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 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 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 outBuffer, ReadOnlySpan inBuffer, QueryId queryId, - U8Span path) - { - Span 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(); } } } diff --git a/src/LibHac/FsSystem/ThreadPriorityChanger.cs b/src/LibHac/FsSystem/ThreadPriorityChanger.cs new file mode 100644 index 00000000..14c96d9c --- /dev/null +++ b/src/LibHac/FsSystem/ThreadPriorityChanger.cs @@ -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; + } + } +} diff --git a/src/LibHac/FsSystem/UniqueLockSemaphore.cs b/src/LibHac/FsSystem/UniqueLockSemaphore.cs deleted file mode 100644 index 766dab15..00000000 --- a/src/LibHac/FsSystem/UniqueLockSemaphore.cs +++ /dev/null @@ -1,97 +0,0 @@ -using System; -using System.Threading; -using LibHac.Common; -using LibHac.Diag; - -namespace LibHac.FsSystem -{ - public interface IUniqueLock : IDisposable - { - } - - /// - /// Represents a lock that may be passed between functions or objects. - /// - /// This struct must never be copied. It must always be passed by - /// reference or moved via the move constructor. - 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 : IUniqueLock where T : class, IDisposable - { - private UniqueLockSemaphore _semaphore; - private ReferenceCountedDisposable _pinnedObject; - - public UniqueLockWithPin(ref UniqueLockSemaphore semaphore, ref ReferenceCountedDisposable 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; - } - } - } -} diff --git a/src/LibHac/FsSystem/Utility.cs b/src/LibHac/FsSystem/Utility.cs deleted file mode 100644 index e7a1c060..00000000 --- a/src/LibHac/FsSystem/Utility.cs +++ /dev/null @@ -1,295 +0,0 @@ -using System; -using System.Runtime.CompilerServices; -using System.Threading; -using LibHac.Common; -using LibHac.Diag; -using LibHac.Fs; -using LibHac.Fs.Fsa; -using LibHac.Util; - -namespace LibHac.FsSystem -{ - internal static class Utility - { - public delegate Result FsIterationTask(U8Span path, ref DirectoryEntry entry); - - private static U8Span RootPath => new U8Span(new[] { (byte)'/' }); - private static U8Span DirectorySeparator => RootPath; - - public static Result IterateDirectoryRecursively(IFileSystem fs, U8Span rootPath, Span workPath, - ref DirectoryEntry dirEntry, FsIterationTask onEnterDir, FsIterationTask onExitDir, FsIterationTask onFile) - { - Abort.DoAbortUnless(workPath.Length >= PathTool.EntryNameLengthMax + 1); - - // Get size of the root path. - int rootPathLen = StringUtils.GetLength(rootPath, PathTool.EntryNameLengthMax + 1); - if (rootPathLen > PathTool.EntryNameLengthMax) - return ResultFs.TooLongPath.Log(); - - // Copy root path in, add a / if necessary. - rootPath.Value.Slice(0, rootPathLen).CopyTo(workPath); - if (workPath[rootPathLen - 1] != StringTraits.DirectorySeparator) - { - workPath[rootPathLen++] = StringTraits.DirectorySeparator; - } - - // Make sure the result path is still valid. - if (rootPathLen > PathTool.EntryNameLengthMax) - return ResultFs.TooLongPath.Log(); - - workPath[rootPathLen] = StringTraits.NullTerminator; - - return IterateDirectoryRecursivelyImpl(fs, workPath, ref dirEntry, onEnterDir, onExitDir, onFile); - } - - public static Result IterateDirectoryRecursively(IFileSystem fs, U8Span rootPath, FsIterationTask onEnterDir, - FsIterationTask onExitDir, FsIterationTask onFile) - { - var entry = new DirectoryEntry(); - Span workPath = stackalloc byte[PathTools.MaxPathLength + 1]; - - return IterateDirectoryRecursively(fs, rootPath, workPath, ref entry, onEnterDir, onExitDir, - onFile); - } - - public static Result IterateDirectoryRecursively(IFileSystem fs, FsIterationTask onEnterDir, - FsIterationTask onExitDir, FsIterationTask onFile) - { - return IterateDirectoryRecursively(fs, RootPath, onEnterDir, onExitDir, onFile); - } - - private static Result IterateDirectoryRecursivelyImpl(IFileSystem fs, Span workPath, - ref DirectoryEntry dirEntry, FsIterationTask onEnterDir, FsIterationTask onExitDir, FsIterationTask onFile) - { - Result rc = fs.OpenDirectory(out IDirectory dir, new U8Span(workPath), OpenDirectoryMode.All); - if (rc.IsFailure()) return rc; - - int parentLen = StringUtils.GetLength(workPath); - - // Read and handle entries. - while (true) - { - // Read a single entry. - rc = dir.Read(out long readCount, SpanHelpers.AsSpan(ref dirEntry)); - if (rc.IsFailure()) return rc; - - // If we're out of entries, we're done. - if (readCount == 0) - break; - - // Validate child path size. - int childNameLen = StringUtils.GetLength(dirEntry.Name); - bool isDir = dirEntry.Type == DirectoryEntryType.Directory; - int separatorSize = isDir ? 1 : 0; - - if (parentLen + childNameLen + separatorSize >= workPath.Length) - return ResultFs.TooLongPath.Log(); - - // Set child path. - StringUtils.Concat(workPath, dirEntry.Name); - { - if (isDir) - { - // Enter directory. - rc = onEnterDir(new U8Span(workPath), ref dirEntry); - if (rc.IsFailure()) return rc; - - // Append separator, recurse. - StringUtils.Concat(workPath, DirectorySeparator); - - rc = IterateDirectoryRecursivelyImpl(fs, workPath, ref dirEntry, onEnterDir, onExitDir, onFile); - if (rc.IsFailure()) return rc; - - // Exit directory. - rc = onExitDir(new U8Span(workPath), ref dirEntry); - if (rc.IsFailure()) return rc; - } - else - { - // Call file handler. - rc = onFile(new U8Span(workPath), ref dirEntry); - if (rc.IsFailure()) return rc; - } - } - - // Restore parent path. - workPath[parentLen] = StringTraits.NullTerminator; - } - - return Result.Success; - } - - public static Result CopyDirectoryRecursively(IFileSystem fileSystem, U8Span destPath, U8Span sourcePath, - Span workBuffer) - { - return CopyDirectoryRecursively(fileSystem, fileSystem, destPath, sourcePath, workBuffer); - } - - public static unsafe Result CopyDirectoryRecursively(IFileSystem destFileSystem, IFileSystem sourceFileSystem, - U8Span destPath, U8Span sourcePath, Span workBuffer) - { - var destPathBuf = new FsPath(); - int originalSize = StringUtils.Copy(destPathBuf.Str, destPath); - Abort.DoAbortUnless(originalSize < Unsafe.SizeOf()); - - // Pin and recreate the span because C# can't use byref-like types in a closure - int workBufferSize = workBuffer.Length; - fixed (byte* pWorkBuffer = workBuffer) - { - // Copy the pointer to workaround CS1764. - // IterateDirectoryRecursively won't store the delegate anywhere, so it should be safe - byte* pWorkBuffer2 = pWorkBuffer; - - Result OnEnterDir(U8Span path, ref DirectoryEntry entry) - { - // Update path, create new dir. - StringUtils.Concat(SpanHelpers.AsByteSpan(ref destPathBuf), entry.Name); - StringUtils.Concat(SpanHelpers.AsByteSpan(ref destPathBuf), DirectorySeparator); - - return destFileSystem.CreateDirectory(destPathBuf); - } - - Result OnExitDir(U8Span path, ref DirectoryEntry entry) - { - // Check we have a parent directory. - int len = StringUtils.GetLength(SpanHelpers.AsByteSpan(ref destPathBuf)); - if (len < 2) - return ResultFs.InvalidPathFormat.Log(); - - // Find previous separator, add null terminator - int cur = len - 2; - while (SpanHelpers.AsByteSpan(ref destPathBuf)[cur] != StringTraits.DirectorySeparator && cur > 0) - { - cur--; - } - - SpanHelpers.AsByteSpan(ref destPathBuf)[cur + 1] = StringTraits.NullTerminator; - - return Result.Success; - } - - Result OnFile(U8Span path, ref DirectoryEntry entry) - { - var buffer = new Span(pWorkBuffer2, workBufferSize); - - return CopyFile(destFileSystem, sourceFileSystem, destPathBuf, path, ref entry, buffer); - } - - return IterateDirectoryRecursively(sourceFileSystem, sourcePath, OnEnterDir, OnExitDir, OnFile); - } - } - - public static Result CopyFile(IFileSystem destFileSystem, IFileSystem sourceFileSystem, U8Span destParentPath, - U8Span sourcePath, ref DirectoryEntry entry, Span workBuffer) - { - // Open source file. - Result rc = sourceFileSystem.OpenFile(out IFile sourceFile, sourcePath, OpenMode.Read); - if (rc.IsFailure()) return rc; - - using (sourceFile) - { - // Open dest file. - Unsafe.SkipInit(out FsPath destPath); - - var sb = new U8StringBuilder(destPath.Str); - sb.Append(destParentPath).Append(entry.Name); - - Assert.SdkLess(sb.Length, Unsafe.SizeOf()); - - rc = destFileSystem.CreateFile(new U8Span(destPath.Str), entry.Size); - if (rc.IsFailure()) return rc; - - rc = destFileSystem.OpenFile(out IFile destFile, new U8Span(destPath.Str), OpenMode.Write); - if (rc.IsFailure()) return rc; - - using (destFile) - { - // Read/Write file in work buffer sized chunks. - long remaining = entry.Size; - long offset = 0; - - while (remaining > 0) - { - rc = sourceFile.Read(out long bytesRead, offset, workBuffer, ReadOption.None); - if (rc.IsFailure()) return rc; - - rc = destFile.Write(offset, workBuffer.Slice(0, (int)bytesRead), WriteOption.None); - if (rc.IsFailure()) return rc; - - remaining -= bytesRead; - offset += bytesRead; - } - } - } - - return Result.Success; - } - - public static Result TryAcquireCountSemaphore(out UniqueLockSemaphore uniqueLock, SemaphoreAdaptor semaphore) - { - UniqueLockSemaphore tempUniqueLock = default; - try - { - tempUniqueLock = new UniqueLockSemaphore(semaphore); - - if (!tempUniqueLock.TryLock()) - { - UnsafeHelpers.SkipParamInit(out uniqueLock); - return ResultFs.OpenCountLimit.Log(); - } - - uniqueLock = new UniqueLockSemaphore(ref tempUniqueLock); - return Result.Success; - } - finally - { - tempUniqueLock.Dispose(); - } - } - - public static Result MakeUniqueLockWithPin(out IUniqueLock uniqueLock, SemaphoreAdaptor semaphore, - ref ReferenceCountedDisposable objectToPin) where T : class, IDisposable - { - UnsafeHelpers.SkipParamInit(out uniqueLock); - - UniqueLockSemaphore tempUniqueLock = default; - try - { - Result rc = TryAcquireCountSemaphore(out tempUniqueLock, semaphore); - if (rc.IsFailure()) return rc; - - uniqueLock = new UniqueLockWithPin(ref tempUniqueLock, ref objectToPin); - return Result.Success; - } - finally - { - tempUniqueLock.Dispose(); - } - } - - public static Result RetryFinitelyForTargetLocked(Func function) - { - const int maxRetryCount = 10; - const int retryWaitTimeMs = 100; - - int remainingRetries = maxRetryCount; - - while (true) - { - Result rc = function(); - - if (rc.IsSuccess()) - return rc; - - if (!ResultFs.TargetLocked.Includes(rc)) - return rc; - - if (remainingRetries <= 0) - return rc; - - remainingRetries--; - Thread.Sleep(retryWaitTimeMs); - } - } - } -} diff --git a/src/LibHac/FsSystem/Utility12.cs b/src/LibHac/FsSystem/Utility12.cs new file mode 100644 index 00000000..918852a1 --- /dev/null +++ b/src/LibHac/FsSystem/Utility12.cs @@ -0,0 +1,473 @@ +using System; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Common; +using LibHac.Fs.Fsa; +using LibHac.Os; + +namespace LibHac.FsSystem +{ + /// + /// Various utility functions used by the namespace. + /// + /// Based on FS 12.0.3 (nnSdk 12.3.1) + internal static class Utility12 + { + public delegate Result FsIterationTask(in Path path, in DirectoryEntry entry, ref FsIterationTaskClosure closure); + + /// + /// Used to pass various ref structs to an . + /// + /// + /// C# does not allow closures over byref-like types. This struct is used as a sort of manual imitation of a closure struct. + /// It contains various fields that can used if needed to pass references to methods. + /// The main shortcomings are that every type that might possibly be passed must have a field in the struct. + /// The struct must also be manually passed through the method. + /// And because ref fields aren't as thing as of C# 10, some ref structs may have to be copied into the closure struct. + /// + public ref struct FsIterationTaskClosure + { + public Span Buffer; + public Path DestinationPathBuffer; + public IFileSystem SourceFileSystem; + public IFileSystem DestFileSystem; + } + + private static ReadOnlySpan RootPath => new[] { (byte)'/' }; + + private static Result IterateDirectoryRecursivelyInternal(IFileSystem fs, ref Path workPath, + ref DirectoryEntry dirEntry, FsIterationTask onEnterDir, FsIterationTask onExitDir, FsIterationTask onFile, + ref FsIterationTaskClosure closure) + { + IDirectory directory = null; + try + { + Result rc = fs.OpenDirectory(out directory, in workPath, OpenDirectoryMode.All); + if (rc.IsFailure()) return rc; + + while (true) + { + rc = directory.Read(out long entriesRead, SpanHelpers.AsSpan(ref dirEntry)); + if (rc.IsFailure()) return rc; + + if (entriesRead == 0) + break; + + workPath.AppendChild(dirEntry.Name); + if (rc.IsFailure()) return rc; + + if (dirEntry.Type == DirectoryEntryType.Directory) + { + rc = onEnterDir(in workPath, in dirEntry, ref closure); + if (rc.IsFailure()) return rc; + + rc = IterateDirectoryRecursivelyInternal(fs, ref workPath, ref dirEntry, onEnterDir, onExitDir, + onFile, ref closure); + if (rc.IsFailure()) return rc; + + rc = onExitDir(in workPath, in dirEntry, ref closure); + if (rc.IsFailure()) return rc; + } + else + { + rc = onFile(in workPath, in dirEntry, ref closure); + if (rc.IsFailure()) return rc; + } + + rc = workPath.RemoveChild(); + if (rc.IsFailure()) return rc; + } + + return Result.Success; + } + finally + { + directory?.Dispose(); + } + } + + private static Result CleanupDirectoryRecursivelyInternal(IFileSystem fs, ref Path workPath, + ref DirectoryEntry dirEntry, FsIterationTask onEnterDir, FsIterationTask onExitDir, FsIterationTask onFile, + ref FsIterationTaskClosure closure) + { + IDirectory directory = null; + try + { + while (true) + { + Result rc = fs.OpenDirectory(out directory, in workPath, OpenDirectoryMode.All); + if (rc.IsFailure()) return rc; + + rc = directory.Read(out long entriesRead, SpanHelpers.AsSpan(ref dirEntry)); + if (rc.IsFailure()) return rc; + + directory.Dispose(); + directory = null; + + if (entriesRead == 0) + break; + + rc = workPath.AppendChild(dirEntry.Name); + if (rc.IsFailure()) return rc; + + if (dirEntry.Type == DirectoryEntryType.Directory) + { + rc = onEnterDir(in workPath, in dirEntry, ref closure); + if (rc.IsFailure()) return rc; + + rc = CleanupDirectoryRecursivelyInternal(fs, ref workPath, ref dirEntry, onEnterDir, onExitDir, + onFile, ref closure); + if (rc.IsFailure()) return rc; + + rc = onExitDir(in workPath, in dirEntry, ref closure); + if (rc.IsFailure()) return rc; + } + else + { + rc = onFile(in workPath, in dirEntry, ref closure); + if (rc.IsFailure()) return rc; + } + + rc = workPath.RemoveChild(); + if (rc.IsFailure()) return rc; + } + + return Result.Success; + } + finally + { + directory?.Dispose(); + } + } + + public static Result IterateDirectoryRecursively(IFileSystem fs, in Path rootPath, ref DirectoryEntry dirEntry, + FsIterationTask onEnterDir, FsIterationTask onExitDir, FsIterationTask onFile, + ref FsIterationTaskClosure closure) + { + var pathBuffer = new Path(); + Result rc = pathBuffer.Initialize(in rootPath); + if (rc.IsFailure()) return rc; + + rc = IterateDirectoryRecursivelyInternal(fs, ref pathBuffer, ref dirEntry, onEnterDir, onExitDir, onFile, + ref closure); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + public static Result CleanupDirectoryRecursively(IFileSystem fs, in Path rootPath, ref DirectoryEntry dirEntry, + FsIterationTask onEnterDir, FsIterationTask onExitDir, FsIterationTask onFile, + ref FsIterationTaskClosure closure) + { + var pathBuffer = new Path(); + Result rc = pathBuffer.Initialize(in rootPath); + if (rc.IsFailure()) return rc; + + return CleanupDirectoryRecursivelyInternal(fs, ref pathBuffer, ref dirEntry, onEnterDir, onExitDir, onFile, + ref closure); + } + + public static Result CopyFile(IFileSystem destFileSystem, IFileSystem sourceFileSystem, in Path destPath, + in Path sourcePath, Span workBuffer) + { + // Open source file. + Result rc = sourceFileSystem.OpenFile(out IFile sourceFile, sourcePath, OpenMode.Read); + if (rc.IsFailure()) return rc; + + using (sourceFile) + { + rc = sourceFile.GetSize(out long fileSize); + if (rc.IsFailure()) return rc; + + rc = destFileSystem.CreateFile(in destPath, fileSize); + if (rc.IsFailure()) return rc; + + rc = destFileSystem.OpenFile(out IFile destFile, in destPath, OpenMode.Write); + if (rc.IsFailure()) return rc; + + using (destFile) + { + // Read/Write file in work buffer sized chunks. + long remaining = fileSize; + long offset = 0; + + while (remaining > 0) + { + rc = sourceFile.Read(out long bytesRead, offset, workBuffer, ReadOption.None); + if (rc.IsFailure()) return rc; + + rc = destFile.Write(offset, workBuffer.Slice(0, (int)bytesRead), WriteOption.None); + if (rc.IsFailure()) return rc; + + remaining -= bytesRead; + offset += bytesRead; + } + } + } + + return Result.Success; + } + + public static Result CopyDirectoryRecursively(IFileSystem destinationFileSystem, IFileSystem sourceFileSystem, + in Path destinationPath, in Path sourcePath, ref DirectoryEntry dirEntry, Span workBuffer) + { + static Result OnEnterDir(in Path path, in DirectoryEntry entry, ref FsIterationTaskClosure closure) + { + Result rc = closure.DestinationPathBuffer.AppendChild(entry.Name); + if (rc.IsFailure()) return rc; + + return closure.SourceFileSystem.CreateDirectory(in closure.DestinationPathBuffer); + } + + static Result OnExitDir(in Path path, in DirectoryEntry entry, ref FsIterationTaskClosure closure) + { + return closure.DestinationPathBuffer.RemoveChild(); + } + + static Result OnFile(in Path path, in DirectoryEntry entry, ref FsIterationTaskClosure closure) + { + Result rc = closure.DestinationPathBuffer.AppendChild(entry.Name); + if (rc.IsFailure()) return rc; + + rc = CopyFile(closure.DestFileSystem, closure.SourceFileSystem, in closure.DestinationPathBuffer, + in path, closure.Buffer); + if (rc.IsFailure()) return rc; + + return closure.DestinationPathBuffer.RemoveChild(); + } + + var closure = new FsIterationTaskClosure(); + closure.Buffer = workBuffer; + closure.SourceFileSystem = sourceFileSystem; + closure.DestFileSystem = destinationFileSystem; + + Result rc = closure.DestinationPathBuffer.Initialize(destinationPath); + if (rc.IsFailure()) return rc; + + rc = IterateDirectoryRecursively(sourceFileSystem, in sourcePath, ref dirEntry, OnEnterDir, OnExitDir, + OnFile, ref closure); + + closure.DestinationPathBuffer.Dispose(); + return rc; + } + + public static Result CopyDirectoryRecursively(IFileSystem fileSystem, in Path destinationPath, + in Path sourcePath, ref DirectoryEntry dirEntry, Span workBuffer) + { + var closure = new FsIterationTaskClosure(); + closure.Buffer = workBuffer; + closure.SourceFileSystem = fileSystem; + + Result rc = closure.DestinationPathBuffer.Initialize(destinationPath); + if (rc.IsFailure()) return rc; + + static Result OnEnterDir(in Path path, in DirectoryEntry entry, ref FsIterationTaskClosure closure) + { + Result rc = closure.DestinationPathBuffer.AppendChild(entry.Name); + if (rc.IsFailure()) return rc; + + return closure.SourceFileSystem.CreateDirectory(in closure.DestinationPathBuffer); + } + + static Result OnExitDir(in Path path, in DirectoryEntry entry, ref FsIterationTaskClosure closure) + { + return closure.DestinationPathBuffer.RemoveChild(); + } + + static Result OnFile(in Path path, in DirectoryEntry entry, ref FsIterationTaskClosure closure) + { + Result rc = closure.DestinationPathBuffer.AppendChild(entry.Name); + if (rc.IsFailure()) return rc; + + rc = CopyFile(closure.SourceFileSystem, closure.SourceFileSystem, in closure.DestinationPathBuffer, + in path, closure.Buffer); + if (rc.IsFailure()) return rc; + + return closure.DestinationPathBuffer.RemoveChild(); + } + + rc = IterateDirectoryRecursively(fileSystem, in sourcePath, ref dirEntry, OnEnterDir, OnExitDir, OnFile, + ref closure); + + closure.DestinationPathBuffer.Dispose(); + return rc; + } + + public static Result VerifyDirectoryRecursively(IFileSystem fileSystem, Span workBuffer) + { + static Result OnEnterAndExitDir(in Path path, in DirectoryEntry entry, ref FsIterationTaskClosure closure) => + Result.Success; + + static Result OnFile(in Path path, in DirectoryEntry entry, ref FsIterationTaskClosure closure) + { + IFile file = null; + try + { + Result rc = closure.SourceFileSystem.OpenFile(out file, in path, OpenMode.Read); + if (rc.IsFailure()) return rc; + + long offset = 0; + + while (true) + { + rc = file.Read(out long bytesRead, offset, closure.Buffer, ReadOption.None); + if (rc.IsFailure()) return rc; + + if (bytesRead < closure.Buffer.Length) + break; + + offset += bytesRead; + } + + return Result.Success; + } + finally + { + file?.Dispose(); + } + } + + var rootPath = new Path(); + Result rc = PathFunctions.SetUpFixedPath(ref rootPath, RootPath); + if (rc.IsFailure()) return rc; + + var closure = new FsIterationTaskClosure(); + closure.Buffer = workBuffer; + closure.SourceFileSystem = fileSystem; + + var dirEntryBuffer = new DirectoryEntry(); + + return IterateDirectoryRecursively(fileSystem, in rootPath, ref dirEntryBuffer, OnEnterAndExitDir, + OnEnterAndExitDir, OnFile, ref closure); + } + + private static Result EnsureDirectoryImpl(IFileSystem fileSystem, in Path path) + { + var pathCopy = new Path(); + + try + { + bool isFinished; + + Result rc = pathCopy.Initialize(in path); + if (rc.IsFailure()) return rc; + + using var parser = new DirectoryPathParser(); + rc = parser.Initialize(ref pathCopy); + if (rc.IsFailure()) return rc; + + do + { + // Check if the path exists + rc = fileSystem.GetEntryType(out DirectoryEntryType type, in parser.CurrentPath); + if (!rc.IsSuccess()) + { + // Something went wrong if we get a result other than PathNotFound + if (!ResultFs.PathNotFound.Includes(rc)) + return rc; + + // Create the directory + rc = fileSystem.CreateDirectory(in parser.CurrentPath); + if (rc.IsFailure() && !ResultFs.PathAlreadyExists.Includes(rc)) + return rc; + + // Check once more if the path exists + rc = fileSystem.GetEntryType(out type, in parser.CurrentPath); + if (rc.IsFailure()) return rc; + } + + if (type == DirectoryEntryType.File) + return ResultFs.PathAlreadyExists.Log(); + + rc = parser.ReadNext(out isFinished); + if (rc.IsFailure()) return rc; + } while (!isFinished); + + return Result.Success; + } + finally + { + pathCopy.Dispose(); + } + } + + public static Result EnsureDirectory(IFileSystem fileSystem, in Path path) + { + Result rc = fileSystem.GetEntryType(out _, in path); + + if (!rc.IsSuccess()) + { + if (!ResultFs.PathNotFound.Includes(rc)) + return rc; + + rc = EnsureDirectoryImpl(fileSystem, in path); + if (rc.IsFailure()) return rc; + } + + return Result.Success; + } + + public static void AddCounter(Span counter, ulong value) + { + const int bitsPerByte = 8; + + ulong remaining = value; + byte carry = 0; + + for (int i = 0; i < counter.Length; i++) + { + int sum = counter[counter.Length - 1 - i] + (byte)remaining + carry; + carry = (byte)(sum >> bitsPerByte); + + counter[counter.Length - 1 - i] = (byte)sum; + + remaining >>= bitsPerByte; + + if (carry == 0 && remaining == 0) + break; + } + } + + public static Result TryAcquireCountSemaphore(out UniqueLock uniqueLock, SemaphoreAdapter semaphore) + { + UniqueLock tempUniqueLock = default; + try + { + tempUniqueLock = new UniqueLock(semaphore, new DeferLock()); + + if (!tempUniqueLock.TryLock()) + { + uniqueLock = default; + return ResultFs.OpenCountLimit.Log(); + } + + uniqueLock = Shared.Move(ref tempUniqueLock); + return Result.Success; + } + finally + { + tempUniqueLock.Dispose(); + } + } + + public static Result MakeUniqueLockWithPin(out IUniqueLock uniqueLock, SemaphoreAdapter semaphore, + ref ReferenceCountedDisposable objectToPin) where T : class, IDisposable + { + UnsafeHelpers.SkipParamInit(out uniqueLock); + + UniqueLock tempUniqueLock = default; + try + { + Result rc = TryAcquireCountSemaphore(out tempUniqueLock, semaphore); + if (rc.IsFailure()) return rc; + + uniqueLock = new UniqueLockWithPin(ref tempUniqueLock, ref objectToPin); + return Result.Success; + } + finally + { + tempUniqueLock.Dispose(); + } + } + } +} diff --git a/src/LibHac/Lr/AddOnContentLocationResolver.cs b/src/LibHac/Lr/AddOnContentLocationResolver.cs index fead88b6..7a6a163c 100644 --- a/src/LibHac/Lr/AddOnContentLocationResolver.cs +++ b/src/LibHac/Lr/AddOnContentLocationResolver.cs @@ -1,4 +1,5 @@ using System; +using LibHac.Common; using LibHac.Ncm; using LibHac.Sf; @@ -8,9 +9,14 @@ namespace LibHac.Lr { private ReferenceCountedDisposable _interface; - public AddOnContentLocationResolver(ReferenceCountedDisposable baseInterface) + public AddOnContentLocationResolver(ref ReferenceCountedDisposable baseInterface) { - _interface = baseInterface.AddReference(); + _interface = Shared.Move(ref baseInterface); + } + + public void Dispose() + { + _interface?.Dispose(); } public Result ResolveAddOnContentPath(out Path path, DataId id) => @@ -27,10 +33,5 @@ namespace LibHac.Lr public Result UnregisterApplicationAddOnContent(Ncm.ApplicationId id) => _interface.Target.UnregisterApplicationAddOnContent(id); - - public void Dispose() - { - _interface?.Dispose(); - } } } diff --git a/src/LibHac/Lr/LocationResolver.cs b/src/LibHac/Lr/LocationResolver.cs index 5dce2624..1f1778b6 100644 --- a/src/LibHac/Lr/LocationResolver.cs +++ b/src/LibHac/Lr/LocationResolver.cs @@ -1,4 +1,5 @@ using System; +using LibHac.Common; using LibHac.Ncm; using LibHac.Sf; @@ -8,9 +9,14 @@ namespace LibHac.Lr { private ReferenceCountedDisposable _interface; - public LocationResolver(ReferenceCountedDisposable baseInterface) + public LocationResolver(ref ReferenceCountedDisposable baseInterface) { - _interface = baseInterface.AddReference(); + _interface = Shared.Move(ref baseInterface); + } + + public void Dispose() + { + _interface?.Dispose(); } public Result ResolveProgramPath(out Path path, ProgramId id) => @@ -72,10 +78,5 @@ namespace LibHac.Lr public Result EraseProgramRedirectionForDebug(ProgramId id) => _interface.Target.EraseProgramRedirectionForDebug(id); - - public void Dispose() - { - _interface?.Dispose(); - } } } diff --git a/src/LibHac/Lr/LrClient.cs b/src/LibHac/Lr/LrClient.cs index c27bdaaa..17e600e0 100644 --- a/src/LibHac/Lr/LrClient.cs +++ b/src/LibHac/Lr/LrClient.cs @@ -1,103 +1,37 @@ using System; -using LibHac.Common; -using LibHac.Ncm; namespace LibHac.Lr { public class LrClient : IDisposable { - private HorizonClient Hos { get; } - - private ILocationResolverManager LrManager { get; set; } - private readonly object _lrInitLocker = new object(); + internal LrClientGlobals Globals; + internal HorizonClient Hos => Globals.Hos; public LrClient(HorizonClient horizonClient) { - Hos = horizonClient; - } - - public Result OpenLocationResolver(out LocationResolver resolver, StorageId storageId) - { - UnsafeHelpers.SkipParamInit(out resolver); - EnsureInitialized(); - - Result rc = LrManager.OpenLocationResolver(out ReferenceCountedDisposable baseResolver, - storageId); - if (rc.IsFailure()) return rc; - - using (baseResolver) - { - resolver = new LocationResolver(baseResolver); - return Result.Success; - } - } - - public Result OpenRegisteredLocationResolver(out RegisteredLocationResolver resolver) - { - UnsafeHelpers.SkipParamInit(out resolver); - EnsureInitialized(); - - Result rc = LrManager.OpenRegisteredLocationResolver( - out ReferenceCountedDisposable baseResolver); - if (rc.IsFailure()) return rc; - - using (baseResolver) - { - resolver = new RegisteredLocationResolver(baseResolver); - return Result.Success; - } - } - - public Result OpenAddOnContentLocationResolver(out AddOnContentLocationResolver resolver) - { - UnsafeHelpers.SkipParamInit(out resolver); - EnsureInitialized(); - - Result rc = LrManager.OpenAddOnContentLocationResolver( - out ReferenceCountedDisposable baseResolver); - if (rc.IsFailure()) return rc; - - using (baseResolver) - { - resolver = new AddOnContentLocationResolver(baseResolver); - return Result.Success; - } - } - - public Result RefreshLocationResolver(StorageId storageId) - { - EnsureInitialized(); - - Result rc = LrManager.RefreshLocationResolver(storageId); - if (rc.IsFailure()) return rc; - - return Result.Success; - } - - private void EnsureInitialized() - { - if (LrManager != null) - return; - - lock (_lrInitLocker) - { - if (LrManager != null) - return; - - Result rc = Hos.Sm.GetService(out ILocationResolverManager manager, "lr"); - - if (rc.IsFailure()) - { - throw new HorizonResultException(rc, "Failed to initialize lr client."); - } - - LrManager = manager; - } + Globals.Initialize(this, horizonClient); } public void Dispose() { - LrManager?.Dispose(); + Globals.Dispose(); + } + } + + internal struct LrClientGlobals + { + public HorizonClient Hos; + public LrServiceGlobals LrService; + + public void Initialize(LrClient lrClient, HorizonClient horizonClient) + { + Hos = horizonClient; + LrService.Initialize(); + } + + public void Dispose() + { + LrService.Dispose(); } } } diff --git a/src/LibHac/Lr/LrService.cs b/src/LibHac/Lr/LrService.cs new file mode 100644 index 00000000..6057dd91 --- /dev/null +++ b/src/LibHac/Lr/LrService.cs @@ -0,0 +1,104 @@ +using LibHac.Common; +using LibHac.Diag; +using LibHac.Ncm; +using LibHac.Os; + +namespace LibHac.Lr +{ + internal struct LrServiceGlobals + { + public ILocationResolverManager LocationResolver; + public SdkMutexType InitializationMutex; + + public void Initialize() + { + LocationResolver = null; + InitializationMutex.Initialize(); + } + + public void Dispose() + { + if (LocationResolver is not null) + { + LocationResolver.Dispose(); + LocationResolver = null; + } + } + } + + public static class LrService + { + public static void Initialize(this LrClient lr) + { + ref LrServiceGlobals globals = ref lr.Globals.LrService; + Assert.SdkRequiresNotNull(globals.LocationResolver); + + // The lock over getting the service object is a LibHac addition. + using ScopedLock scopedLock = ScopedLock.Lock(ref lr.Globals.LrService.InitializationMutex); + + if (globals.LocationResolver is not null) + return; + + ILocationResolverManager serviceObject = lr.GetLocationResolverManagerServiceObject(); + globals.LocationResolver = serviceObject; + } + + public static Result OpenLocationResolver(this LrClient lr, out LocationResolver resolver, StorageId storageId) + { + UnsafeHelpers.SkipParamInit(out resolver); + + Result rc = lr.Globals.LrService.LocationResolver.OpenLocationResolver( + out ReferenceCountedDisposable baseResolver, storageId); + if (rc.IsFailure()) return rc; + + resolver = new LocationResolver(ref baseResolver); + return Result.Success; + } + + public static Result OpenRegisteredLocationResolver(this LrClient lr, out RegisteredLocationResolver resolver) + { + UnsafeHelpers.SkipParamInit(out resolver); + + Result rc = lr.Globals.LrService.LocationResolver.OpenRegisteredLocationResolver( + out ReferenceCountedDisposable baseResolver); + if (rc.IsFailure()) return rc; + + resolver = new RegisteredLocationResolver(ref baseResolver); + return Result.Success; + } + + public static Result OpenAddOnContentLocationResolver(this LrClient lr, out AddOnContentLocationResolver resolver) + { + UnsafeHelpers.SkipParamInit(out resolver); + + Result rc = lr.Globals.LrService.LocationResolver.OpenAddOnContentLocationResolver( + out ReferenceCountedDisposable baseResolver); + if (rc.IsFailure()) return rc; + + resolver = new AddOnContentLocationResolver(ref baseResolver); + return Result.Success; + } + + public static Result RefreshLocationResolver(this LrClient lr, StorageId storageId) + { + Result rc = lr.Globals.LrService.LocationResolver.RefreshLocationResolver(storageId); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + // Official lr puts this function along with memory allocation for + // lr IPC objects into a separate file, LocationResolverManagerFactory. + private static ILocationResolverManager GetLocationResolverManagerServiceObject(this LrClient lr) + { + Result rc = lr.Hos.Sm.GetService(out ILocationResolverManager manager, "lr"); + + if (rc.IsFailure()) + { + throw new HorizonResultException(rc, "Failed to get lr service object."); + } + + return manager; + } + } +} diff --git a/src/LibHac/Lr/RegisteredLocationResolver.cs b/src/LibHac/Lr/RegisteredLocationResolver.cs index 904b268a..6f4855d0 100644 --- a/src/LibHac/Lr/RegisteredLocationResolver.cs +++ b/src/LibHac/Lr/RegisteredLocationResolver.cs @@ -1,4 +1,5 @@ using System; +using LibHac.Common; using LibHac.Ncm; namespace LibHac.Lr @@ -7,9 +8,14 @@ namespace LibHac.Lr { private ReferenceCountedDisposable _interface; - public RegisteredLocationResolver(ReferenceCountedDisposable baseInterface) + public RegisteredLocationResolver(ref ReferenceCountedDisposable baseInterface) { - _interface = baseInterface.AddReference(); + _interface = Shared.Move(ref baseInterface); + } + + public void Dispose() + { + _interface?.Dispose(); } public Result ResolveProgramPath(out Path path, ProgramId id) => @@ -41,10 +47,5 @@ namespace LibHac.Lr public Result RefreshExcluding(ReadOnlySpan ids) => _interface.Target.RefreshExcluding(ids); - - public void Dispose() - { - _interface?.Dispose(); - } } } diff --git a/src/LibHac/Os/UniqueLock.cs b/src/LibHac/Os/UniqueLock.cs index 47ce3352..3712605c 100644 --- a/src/LibHac/Os/UniqueLock.cs +++ b/src/LibHac/Os/UniqueLock.cs @@ -1,38 +1,58 @@ -using System.Runtime.CompilerServices; +using System; +using System.Runtime.CompilerServices; using System.Threading; using LibHac.Common; namespace LibHac.Os { + /// + /// Specifies that a constructed should not be automatically locked upon construction.
+ /// Used only to differentiate between constructor signatures. + ///
+ public struct DeferLock { } + public static class UniqueLock { [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static UniqueLock Lock(ref TMutex lockable) where TMutex : ILockable + public static UniqueLockRef Lock(ref TMutex lockable) where TMutex : struct, ILockable { - return new UniqueLock(ref lockable); + return new UniqueLockRef(ref lockable); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static UniqueLock Lock(TMutex lockable) where TMutex : class, ILockable + { + return new UniqueLock(lockable); } } - public ref struct UniqueLock where TMutex : ILockable + public ref struct UniqueLockRef where TMutex : struct, ILockable { private Ref _mutex; private bool _ownsLock; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public UniqueLock(ref TMutex mutex) + public UniqueLockRef(ref TMutex mutex) { _mutex = new Ref(ref mutex); mutex.Lock(); _ownsLock = true; } - public UniqueLock(ref UniqueLock other) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public UniqueLockRef(ref TMutex mutex, DeferLock tag) + { + _mutex = new Ref(ref mutex); + _ownsLock = false; + } + + public UniqueLockRef(ref UniqueLockRef other) { this = other; other = default; } - public void Set(ref UniqueLock other) + public void Set(ref UniqueLockRef other) { if (_ownsLock) _mutex.Value.Unlock(); @@ -83,4 +103,82 @@ namespace LibHac.Os this = default; } } -} \ No newline at end of file + + public struct UniqueLock : IDisposable where TMutex : class, ILockable + { + private TMutex _mutex; + private bool _ownsLock; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public UniqueLock(TMutex mutex) + { + _mutex = mutex; + mutex.Lock(); + _ownsLock = true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public UniqueLock(TMutex mutex, DeferLock tag) + { + _mutex = mutex; + _ownsLock = false; + } + + public UniqueLock(ref UniqueLock other) + { + this = other; + other = default; + } + + public void Set(ref UniqueLock other) + { + if (_ownsLock) + _mutex.Unlock(); + + this = other; + other = default; + } + + public void Lock() + { + if (_mutex is null) + throw new SynchronizationLockException("UniqueLock.Lock: References null mutex"); + + if (_ownsLock) + throw new SynchronizationLockException("UniqueLock.Lock: Already locked"); + + _mutex.Lock(); + _ownsLock = true; + } + + public bool TryLock() + { + if (_mutex is null) + throw new SynchronizationLockException("UniqueLock.TryLock: References null mutex"); + + if (_ownsLock) + throw new SynchronizationLockException("UniqueLock.TryLock: Already locked"); + + _ownsLock = _mutex.TryLock(); + return _ownsLock; + } + + public void Unlock() + { + if (_ownsLock) + throw new SynchronizationLockException("UniqueLock.Unlock: Not locked"); + + _mutex.Unlock(); + _ownsLock = false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Dispose() + { + if (_ownsLock) + _mutex.Unlock(); + + this = default; + } + } +} diff --git a/src/LibHac/SwitchFs.cs b/src/LibHac/SwitchFs.cs index 520f524c..ede41179 100644 --- a/src/LibHac/SwitchFs.cs +++ b/src/LibHac/SwitchFs.cs @@ -44,14 +44,20 @@ namespace LibHac { var concatFs = new ConcatenationFileSystem(fileSystem); + var contentDirPath = new Fs.Path(); + PathFunctions.SetUpFixedPath(ref contentDirPath, "/Nintendo/Contents".ToU8String()).ThrowIfFailure(); + + var saveDirPath = new Fs.Path(); + PathFunctions.SetUpFixedPath(ref saveDirPath, "/Nintendo/save".ToU8String()).ThrowIfFailure(); + var contentDirFs = new SubdirectoryFileSystem(concatFs); - contentDirFs.Initialize("/Nintendo/Contents".ToU8String()).ThrowIfFailure(); + contentDirFs.Initialize(in contentDirPath).ThrowIfFailure(); AesXtsFileSystem encSaveFs = null; if (fileSystem.DirectoryExists("/Nintendo/save")) { var saveDirFs = new SubdirectoryFileSystem(concatFs); - saveDirFs.Initialize("/Nintendo/save".ToU8String()).ThrowIfFailure(); + saveDirFs.Initialize(in saveDirPath).ThrowIfFailure(); encSaveFs = new AesXtsFileSystem(saveDirFs, keySet.SdCardEncryptionKeys[0].DataRo.ToArray(), 0x4000); } @@ -65,13 +71,22 @@ namespace LibHac { var concatFs = new ConcatenationFileSystem(fileSystem); SubdirectoryFileSystem saveDirFs = null; + SubdirectoryFileSystem contentDirFs; if (concatFs.DirectoryExists("/save")) { - SubdirectoryFileSystem.CreateNew(out saveDirFs, concatFs, "/save".ToU8String()).ThrowIfFailure(); + var savePath = new Fs.Path(); + PathFunctions.SetUpFixedPath(ref savePath, "/save".ToU8String()); + + saveDirFs = new SubdirectoryFileSystem(concatFs); + saveDirFs.Initialize(in savePath).ThrowIfFailure(); } - SubdirectoryFileSystem.CreateNew(out SubdirectoryFileSystem contentDirFs, concatFs, "/Contents".ToU8String()).ThrowIfFailure(); + var contentsPath = new Fs.Path(); + PathFunctions.SetUpFixedPath(ref contentsPath, "/Contents".ToU8String()); + + contentDirFs = new SubdirectoryFileSystem(concatFs); + contentDirFs.Initialize(in contentsPath).ThrowIfFailure(); return new SwitchFs(keySet, contentDirFs, saveDirFs); } diff --git a/src/hactoolnet/ProcessFsBuild.cs b/src/hactoolnet/ProcessFsBuild.cs index 3f56759a..521a69aa 100644 --- a/src/hactoolnet/ProcessFsBuild.cs +++ b/src/hactoolnet/ProcessFsBuild.cs @@ -15,7 +15,7 @@ namespace hactoolnet return; } - var localFs = new LocalFileSystem(ctx.Options.InFile); + LocalFileSystem.Create(out LocalFileSystem localFs, ctx.Options.InFile).ThrowIfFailure(); var builder = new RomFsBuilder(localFs); IStorage romFs = builder.Build(); diff --git a/src/hactoolnet/ProcessSave.cs b/src/hactoolnet/ProcessSave.cs index bedca09c..cc542319 100644 --- a/src/hactoolnet/ProcessSave.cs +++ b/src/hactoolnet/ProcessSave.cs @@ -330,7 +330,9 @@ namespace hactoolnet var sb = new StringBuilder(); sb.AppendLine(); - save.GetFreeSpaceSize(out long freeSpace, "".ToU8String()).ThrowIfFailure(); + var emptyPath = new LibHac.Fs.Path(); + emptyPath.InitializeAsEmpty().ThrowIfFailure(); + save.GetFreeSpaceSize(out long freeSpace, in emptyPath).ThrowIfFailure(); sb.AppendLine("Savefile:"); PrintItem(sb, colLen, "CMAC Key Used:", keySet.DeviceUniqueSaveMacKeys[0].DataRo.ToArray()); diff --git a/tests/LibHac.Tests/Fs/DirectorySaveDataFileSystemTests.cs b/tests/LibHac.Tests/Fs/DirectorySaveDataFileSystemTests.cs index ec460090..c9d525e1 100644 --- a/tests/LibHac.Tests/Fs/DirectorySaveDataFileSystemTests.cs +++ b/tests/LibHac.Tests/Fs/DirectorySaveDataFileSystemTests.cs @@ -57,9 +57,9 @@ namespace LibHac.Tests.Fs { (IFileSystem baseFs, IFileSystem saveFs) = CreateFileSystemInternal(); - saveFs.CreateFile("/file".ToU8Span(), 0, CreateFileOptions.None); + saveFs.CreateFile("/file", 0, CreateFileOptions.None); - Assert.Success(baseFs.GetEntryType(out DirectoryEntryType type, "/1/file".ToU8Span())); + Assert.Success(baseFs.GetEntryType(out DirectoryEntryType type, "/1/file")); Assert.Equal(DirectoryEntryType.File, type); } @@ -68,9 +68,9 @@ namespace LibHac.Tests.Fs { (IFileSystem baseFs, IFileSystem saveFs) = CreateFileSystemInternal(); - saveFs.CreateFile("/file".ToU8Span(), 0, CreateFileOptions.None); + saveFs.CreateFile("/file", 0, CreateFileOptions.None); - Assert.Result(ResultFs.PathNotFound, baseFs.GetEntryType(out _, "/0/file".ToU8Span())); + Assert.Result(ResultFs.PathNotFound, baseFs.GetEntryType(out _, "/0/file")); } [Fact] @@ -78,11 +78,11 @@ namespace LibHac.Tests.Fs { (IFileSystem baseFs, IFileSystem saveFs) = CreateFileSystemInternal(); - saveFs.CreateFile("/file".ToU8Span(), 0, CreateFileOptions.None); + saveFs.CreateFile("/file", 0, CreateFileOptions.None); Assert.Success(saveFs.Commit()); - Assert.Success(baseFs.GetEntryType(out DirectoryEntryType type, "/0/file".ToU8Span())); + Assert.Success(baseFs.GetEntryType(out DirectoryEntryType type, "/0/file")); Assert.Equal(DirectoryEntryType.File, type); } @@ -91,15 +91,15 @@ namespace LibHac.Tests.Fs { (IFileSystem baseFs, IFileSystem saveFs) = CreateFileSystemInternal(); - saveFs.CreateFile("/file".ToU8Span(), 0, CreateFileOptions.None); + saveFs.CreateFile("/file", 0, CreateFileOptions.None); // Rollback should succeed Assert.Success(saveFs.Rollback()); // Make sure all the files are gone - Assert.Result(ResultFs.PathNotFound, saveFs.GetEntryType(out _, "/file".ToU8Span())); - Assert.Result(ResultFs.PathNotFound, baseFs.GetEntryType(out _, "/0/file".ToU8Span())); - Assert.Result(ResultFs.PathNotFound, baseFs.GetEntryType(out _, "/1/file".ToU8Span())); + Assert.Result(ResultFs.PathNotFound, saveFs.GetEntryType(out _, "/file")); + Assert.Result(ResultFs.PathNotFound, baseFs.GetEntryType(out _, "/0/file")); + Assert.Result(ResultFs.PathNotFound, baseFs.GetEntryType(out _, "/1/file")); } [Fact] @@ -107,17 +107,17 @@ namespace LibHac.Tests.Fs { (IFileSystem baseFs, IFileSystem saveFs) = CreateFileSystemInternal(); - saveFs.CreateFile("/file".ToU8Span(), 0, CreateFileOptions.None); + saveFs.CreateFile("/file", 0, CreateFileOptions.None); saveFs.Commit(); - saveFs.DeleteFile("/file".ToU8Span()); + saveFs.DeleteFile("/file"); // Rollback should succeed Assert.Success(saveFs.Rollback()); // Make sure all the files are restored - Assert.Success(saveFs.GetEntryType(out _, "/file".ToU8Span())); - Assert.Success(baseFs.GetEntryType(out _, "/0/file".ToU8Span())); - Assert.Success(baseFs.GetEntryType(out _, "/1/file".ToU8Span())); + Assert.Success(saveFs.GetEntryType(out _, "/file")); + Assert.Success(baseFs.GetEntryType(out _, "/0/file")); + Assert.Success(baseFs.GetEntryType(out _, "/1/file")); } [Fact] @@ -125,18 +125,18 @@ namespace LibHac.Tests.Fs { var baseFs = new InMemoryFileSystem(); - baseFs.CreateDirectory("/0".ToU8Span()).ThrowIfFailure(); - baseFs.CreateDirectory("/1".ToU8Span()).ThrowIfFailure(); + baseFs.CreateDirectory("/0").ThrowIfFailure(); + baseFs.CreateDirectory("/1").ThrowIfFailure(); // Set the existing files before initializing the save FS - baseFs.CreateFile("/0/file1".ToU8Span(), 0, CreateFileOptions.None).ThrowIfFailure(); - baseFs.CreateFile("/1/file2".ToU8Span(), 0, CreateFileOptions.None).ThrowIfFailure(); + baseFs.CreateFile("/0/file1", 0, CreateFileOptions.None).ThrowIfFailure(); + baseFs.CreateFile("/1/file2", 0, CreateFileOptions.None).ThrowIfFailure(); DirectorySaveDataFileSystem.CreateNew(out DirectorySaveDataFileSystem saveFs, baseFs, true, true, true) .ThrowIfFailure(); - Assert.Success(saveFs.GetEntryType(out _, "/file1".ToU8Span())); - Assert.Result(ResultFs.PathNotFound, saveFs.GetEntryType(out _, "/file2".ToU8Span())); + Assert.Success(saveFs.GetEntryType(out _, "/file1")); + Assert.Result(ResultFs.PathNotFound, saveFs.GetEntryType(out _, "/file2")); } [Fact] @@ -144,18 +144,18 @@ namespace LibHac.Tests.Fs { var baseFs = new InMemoryFileSystem(); - baseFs.CreateDirectory("/_".ToU8Span()).ThrowIfFailure(); - baseFs.CreateDirectory("/1".ToU8Span()).ThrowIfFailure(); + baseFs.CreateDirectory("/_").ThrowIfFailure(); + baseFs.CreateDirectory("/1").ThrowIfFailure(); // Set the existing files before initializing the save FS - baseFs.CreateFile("/_/file1".ToU8Span(), 0, CreateFileOptions.None).ThrowIfFailure(); - baseFs.CreateFile("/1/file2".ToU8Span(), 0, CreateFileOptions.None).ThrowIfFailure(); + baseFs.CreateFile("/_/file1", 0, CreateFileOptions.None).ThrowIfFailure(); + baseFs.CreateFile("/1/file2", 0, CreateFileOptions.None).ThrowIfFailure(); DirectorySaveDataFileSystem.CreateNew(out DirectorySaveDataFileSystem saveFs, baseFs, true, true, true) .ThrowIfFailure(); - Assert.Result(ResultFs.PathNotFound, saveFs.GetEntryType(out _, "/file1".ToU8Span())); - Assert.Success(saveFs.GetEntryType(out _, "/file2".ToU8Span())); + Assert.Result(ResultFs.PathNotFound, saveFs.GetEntryType(out _, "/file1")); + Assert.Success(saveFs.GetEntryType(out _, "/file2")); } [Fact] @@ -163,16 +163,16 @@ namespace LibHac.Tests.Fs { var baseFs = new InMemoryFileSystem(); - baseFs.CreateDirectory("/1".ToU8Span()).ThrowIfFailure(); + baseFs.CreateDirectory("/1").ThrowIfFailure(); // Set the existing files before initializing the save FS - baseFs.CreateFile("/1/file2".ToU8Span(), 0, CreateFileOptions.None).ThrowIfFailure(); + baseFs.CreateFile("/1/file2", 0, CreateFileOptions.None).ThrowIfFailure(); DirectorySaveDataFileSystem.CreateNew(out DirectorySaveDataFileSystem saveFs, baseFs, true, true, true) .ThrowIfFailure(); - Assert.Result(ResultFs.PathNotFound, saveFs.GetEntryType(out _, "/file1".ToU8Span())); - Assert.Success(saveFs.GetEntryType(out _, "/file2".ToU8Span())); + Assert.Result(ResultFs.PathNotFound, saveFs.GetEntryType(out _, "/file1")); + Assert.Success(saveFs.GetEntryType(out _, "/file2")); } [Fact] @@ -283,14 +283,14 @@ namespace LibHac.Tests.Fs { var baseFs = new InMemoryFileSystem(); - CreateExtraDataForTest(baseFs, "/ExtraData_".ToU8Span(), 0x12345).ThrowIfFailure(); - CreateExtraDataForTest(baseFs, "/ExtraData1".ToU8Span(), 0x67890).ThrowIfFailure(); + CreateExtraDataForTest(baseFs, "/ExtraData_", 0x12345).ThrowIfFailure(); + CreateExtraDataForTest(baseFs, "/ExtraData1", 0x67890).ThrowIfFailure(); DirectorySaveDataFileSystem.CreateNew(out DirectorySaveDataFileSystem saveFs, baseFs, true, true, true) .ThrowIfFailure(); - + saveFs.ReadExtraData(out SaveDataExtraData extraData).ThrowIfFailure(); - + Assert.Equal(0x67890, extraData.DataSize); } @@ -300,7 +300,7 @@ namespace LibHac.Tests.Fs var random = new RandomGenerator(); RandomDataGenerator randomGeneratorFunc = buffer => random.GenerateRandom(buffer); var timeStampGetter = new TimeStampGetter(); - + var baseFs = new InMemoryFileSystem(); DirectorySaveDataFileSystem.CreateNew(out DirectorySaveDataFileSystem saveFs, baseFs, timeStampGetter, randomGeneratorFunc, true, true, true, null).ThrowIfFailure(); @@ -328,7 +328,7 @@ namespace LibHac.Tests.Fs var random = new RandomGenerator(); RandomDataGenerator randomGeneratorFunc = buffer => random.GenerateRandom(buffer); var timeStampGetter = new TimeStampGetter(); - + var baseFs = new InMemoryFileSystem(); DirectorySaveDataFileSystem.CreateNew(out DirectorySaveDataFileSystem saveFs, baseFs, timeStampGetter, randomGeneratorFunc, true, true, true, null).ThrowIfFailure(); @@ -356,7 +356,7 @@ namespace LibHac.Tests.Fs var random = new RandomGenerator(); RandomDataGenerator randomGeneratorFunc = buffer => random.GenerateRandom(buffer); var timeStampGetter = new TimeStampGetter(); - + var baseFs = new InMemoryFileSystem(); DirectorySaveDataFileSystem.CreateNew(out DirectorySaveDataFileSystem saveFs, baseFs, timeStampGetter, randomGeneratorFunc, true, true, true, null).ThrowIfFailure(); @@ -385,7 +385,7 @@ namespace LibHac.Tests.Fs private class TimeStampGetter : ISaveDataCommitTimeStampGetter { private long _currentTimeStamp = 1; - + public Result Get(out long timeStamp) { timeStamp = _currentTimeStamp++; @@ -411,7 +411,7 @@ namespace LibHac.Tests.Fs } } - private Result CreateExtraDataForTest(IFileSystem fileSystem, U8Span path, int saveDataSize) + private Result CreateExtraDataForTest(IFileSystem fileSystem, string path, int saveDataSize) { fileSystem.DeleteFile(path).IgnoreResult(); diff --git a/tests/LibHac.Tests/Fs/FileSystemClientTests/ShimTests/SaveData.cs b/tests/LibHac.Tests/Fs/FileSystemClientTests/ShimTests/SaveData.cs index 65d4eeab..e7f557db 100644 --- a/tests/LibHac.Tests/Fs/FileSystemClientTests/ShimTests/SaveData.cs +++ b/tests/LibHac.Tests/Fs/FileSystemClientTests/ShimTests/SaveData.cs @@ -43,8 +43,8 @@ namespace LibHac.Tests.Fs.FileSystemClientTests.ShimTests var applicationId = new Ncm.ApplicationId(1); FileSystemClient fs = FileSystemServerFactory.CreateClient(true); - fs.CreateCacheStorage(applicationId, SaveDataSpaceId.SdCache, applicationId.Value, 0, 0, SaveDataFlags.None); - fs.MountCacheStorage("cache".ToU8Span(), applicationId); + Assert.Success(fs.CreateCacheStorage(applicationId, SaveDataSpaceId.SdCache, applicationId.Value, 0, 0, SaveDataFlags.None)); + Assert.Success(fs.MountCacheStorage("cache".ToU8Span(), applicationId)); fs.CreateFile("cache:/sd".ToU8Span(), 0); fs.Commit("cache".ToU8Span()); fs.Unmount("cache".ToU8Span()); diff --git a/tests/LibHac.Tests/Fs/FsaExtensions.cs b/tests/LibHac.Tests/Fs/FsaExtensions.cs new file mode 100644 index 00000000..dab65e7e --- /dev/null +++ b/tests/LibHac.Tests/Fs/FsaExtensions.cs @@ -0,0 +1,265 @@ +using System; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.Util; + +namespace LibHac.Tests.Fs +{ + public static class FsaExtensions + { + private static Result SetUpPath(out Path path, string value) + { + path = new Path(); + + if (value is null) + return ResultFs.NullptrArgument.Log(); + + Result rc = path.Initialize(StringUtils.StringToUtf8(value)); + if (rc.IsFailure()) return rc; + + rc = path.Normalize(new PathFlags()); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + public static Result CreateFile(this IFileSystem fs, string path, long size, CreateFileOptions option) + { + Result rc = SetUpPath(out Path pathNormalized, path); + if (rc.IsFailure()) return rc; + + rc = fs.CreateFile(in pathNormalized, size, option); + + pathNormalized.Dispose(); + return rc; + } + + public static Result CreateFile(this IFileSystem fs, string path, long size) + { + return CreateFile(fs, path, size, CreateFileOptions.None); + } + + public static Result DeleteFile(this IFileSystem fs, string path) + { + Result rc = SetUpPath(out Path pathNormalized, path); + if (rc.IsFailure()) return rc; + + rc = fs.DeleteFile(in pathNormalized); + + pathNormalized.Dispose(); + return rc; + } + + public static Result CreateDirectory(this IFileSystem fs, string path) + { + Result rc = SetUpPath(out Path pathNormalized, path); + if (rc.IsFailure()) return rc; + + rc = fs.CreateDirectory(in pathNormalized); + + pathNormalized.Dispose(); + return rc; + } + + public static Result DeleteDirectory(this IFileSystem fs, string path) + { + Result rc = SetUpPath(out Path pathNormalized, path); + if (rc.IsFailure()) return rc; + + rc = fs.DeleteDirectory(in pathNormalized); + + pathNormalized.Dispose(); + return rc; + } + + public static Result DeleteDirectoryRecursively(this IFileSystem fs, string path) + { + Result rc = SetUpPath(out Path pathNormalized, path); + if (rc.IsFailure()) return rc; + + rc = fs.DeleteDirectoryRecursively(in pathNormalized); + + pathNormalized.Dispose(); + return rc; + } + + public static Result CleanDirectoryRecursively(this IFileSystem fs, string path) + { + Result rc = SetUpPath(out Path pathNormalized, path); + if (rc.IsFailure()) return rc; + + rc = fs.CleanDirectoryRecursively(in pathNormalized); + + pathNormalized.Dispose(); + return rc; + } + + public static Result RenameFile(this IFileSystem fs, string currentPath, string newPath) + { + Result rc = SetUpPath(out Path currentPathNormalized, currentPath); + if (rc.IsFailure()) return rc; + + rc = SetUpPath(out Path newPathNormalized, newPath); + if (rc.IsFailure()) return rc; + + rc = fs.RenameFile(in currentPathNormalized, in newPathNormalized); + + currentPathNormalized.Dispose(); + newPathNormalized.Dispose(); + return rc; + } + + public static Result RenameDirectory(this IFileSystem fs, string currentPath, string newPath) + { + Result rc = SetUpPath(out Path currentPathNormalized, currentPath); + if (rc.IsFailure()) return rc; + + rc = SetUpPath(out Path newPathNormalized, newPath); + if (rc.IsFailure()) return rc; + + rc = fs.RenameDirectory(in currentPathNormalized, in newPathNormalized); + + currentPathNormalized.Dispose(); + newPathNormalized.Dispose(); + return rc; + } + + public static Result GetEntryType(this IFileSystem fs, out DirectoryEntryType entryType, string path) + { + UnsafeHelpers.SkipParamInit(out entryType); + + Result rc = SetUpPath(out Path pathNormalized, path); + if (rc.IsFailure()) return rc; + + rc = fs.GetEntryType(out entryType, in pathNormalized); + + pathNormalized.Dispose(); + return rc; + } + + public static Result GetFreeSpaceSize(this IFileSystem fs, out long freeSpace, string path) + { + UnsafeHelpers.SkipParamInit(out freeSpace); + + Result rc = SetUpPath(out Path pathNormalized, path); + if (rc.IsFailure()) return rc; + + rc = fs.GetFreeSpaceSize(out freeSpace, in pathNormalized); + + pathNormalized.Dispose(); + return rc; + } + + public static Result GetTotalSpaceSize(this IFileSystem fs, out long totalSpace, string path) + { + UnsafeHelpers.SkipParamInit(out totalSpace); + + Result rc = SetUpPath(out Path pathNormalized, path); + if (rc.IsFailure()) return rc; + + rc = fs.GetTotalSpaceSize(out totalSpace, in pathNormalized); + + pathNormalized.Dispose(); + return rc; + } + + public static Result OpenFile(this IFileSystem fs, out IFile file, string path, OpenMode mode) + { + UnsafeHelpers.SkipParamInit(out file); + + Result rc = SetUpPath(out Path pathNormalized, path); + if (rc.IsFailure()) return rc; + + rc = fs.OpenFile(out file, in pathNormalized, mode); + + pathNormalized.Dispose(); + return rc; + } + + public static Result OpenDirectory(this IFileSystem fs, out IDirectory directory, string path, OpenDirectoryMode mode) + { + UnsafeHelpers.SkipParamInit(out directory); + + Result rc = SetUpPath(out Path pathNormalized, path); + if (rc.IsFailure()) return rc; + + rc = fs.OpenDirectory(out directory, in pathNormalized, mode); + + pathNormalized.Dispose(); + return rc; + } + + public static Result GetFileTimeStampRaw(this IFileSystem fs, out FileTimeStampRaw timeStamp, string path) + { + UnsafeHelpers.SkipParamInit(out timeStamp); + + Result rc = SetUpPath(out Path pathNormalized, path); + if (rc.IsFailure()) return rc; + + rc = fs.GetFileTimeStampRaw(out timeStamp, in pathNormalized); + + pathNormalized.Dispose(); + return rc; + } + + public static Result QueryEntry(this IFileSystem fs, Span outBuffer, ReadOnlySpan inBuffer, QueryId queryId, string path) + { + Result rc = SetUpPath(out Path pathNormalized, path); + if (rc.IsFailure()) return rc; + + rc = fs.QueryEntry(outBuffer, inBuffer, queryId, in pathNormalized); + + pathNormalized.Dispose(); + return rc; + } + + public static Result CreateDirectory(this IAttributeFileSystem fs, string path, NxFileAttributes archiveAttribute) + { + Result rc = SetUpPath(out Path pathNormalized, path); + if (rc.IsFailure()) return rc; + + rc = fs.CreateDirectory(in pathNormalized, archiveAttribute); + + pathNormalized.Dispose(); + return rc; + } + + public static Result GetFileAttributes(this IAttributeFileSystem fs, out NxFileAttributes attributes, string path) + { + UnsafeHelpers.SkipParamInit(out attributes); + + Result rc = SetUpPath(out Path pathNormalized, path); + if (rc.IsFailure()) return rc; + + rc = fs.GetFileAttributes(out attributes, in pathNormalized); + + pathNormalized.Dispose(); + return rc; + } + + public static Result SetFileAttributes(this IAttributeFileSystem fs, string path, NxFileAttributes attributes) + { + Result rc = SetUpPath(out Path pathNormalized, path); + if (rc.IsFailure()) return rc; + + rc = fs.SetFileAttributes(in pathNormalized, attributes); + + pathNormalized.Dispose(); + return rc; + } + + public static Result GetFileSize(this IAttributeFileSystem fs, out long fileSize, string path) + { + UnsafeHelpers.SkipParamInit(out fileSize); + + Result rc = SetUpPath(out Path pathNormalized, path); + if (rc.IsFailure()) return rc; + + rc = fs.GetFileSize(out fileSize, in pathNormalized); + + pathNormalized.Dispose(); + return rc; + } + } +} diff --git a/tests/LibHac.Tests/Fs/IFileSystemTestBase/CommittableIFileSystemTests.Commit.cs b/tests/LibHac.Tests/Fs/IFileSystemTestBase/CommittableIFileSystemTests.Commit.cs index 3ebc76f5..c9fb8e6e 100644 --- a/tests/LibHac.Tests/Fs/IFileSystemTestBase/CommittableIFileSystemTests.Commit.cs +++ b/tests/LibHac.Tests/Fs/IFileSystemTestBase/CommittableIFileSystemTests.Commit.cs @@ -1,5 +1,4 @@ -using LibHac.Common; -using LibHac.Fs; +using LibHac.Fs; using LibHac.Fs.Fsa; using Xunit; @@ -18,14 +17,14 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase IFileSystem fs = fsCreator.Create(); // Make sure to test both directories and files - fs.CreateDirectory("/dir1".ToU8Span()).ThrowIfFailure(); - fs.CreateDirectory("/dir2".ToU8Span()).ThrowIfFailure(); + fs.CreateDirectory("/dir1").ThrowIfFailure(); + fs.CreateDirectory("/dir2").ThrowIfFailure(); - fs.CreateFile("/dir1/file".ToU8Span(), data1.Length, CreateFileOptions.None).ThrowIfFailure(); - fs.CreateFile("/dir2/file".ToU8Span(), data2.Length, CreateFileOptions.None).ThrowIfFailure(); + fs.CreateFile("/dir1/file", data1.Length, CreateFileOptions.None).ThrowIfFailure(); + fs.CreateFile("/dir2/file", data2.Length, CreateFileOptions.None).ThrowIfFailure(); - fs.OpenFile(out IFile file1, "/dir1/file".ToU8Span(), OpenMode.Write).ThrowIfFailure(); - fs.OpenFile(out IFile file2, "/dir2/file".ToU8Span(), OpenMode.Write).ThrowIfFailure(); + fs.OpenFile(out IFile file1, "/dir1/file", OpenMode.Write).ThrowIfFailure(); + fs.OpenFile(out IFile file2, "/dir2/file", OpenMode.Write).ThrowIfFailure(); file1.Write(0, data1, WriteOption.Flush).ThrowIfFailure(); file2.Write(0, data2, WriteOption.Flush).ThrowIfFailure(); @@ -42,7 +41,7 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase byte[] readData1 = new byte[data1.Length]; byte[] readData2 = new byte[data2.Length]; - Assert.Success(fs.OpenFile(out file1, "/dir1/file".ToU8Span(), OpenMode.Read)); + Assert.Success(fs.OpenFile(out file1, "/dir1/file", OpenMode.Read)); using (file1) { @@ -52,7 +51,7 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase Assert.Equal(data1, readData1); - Assert.Success(fs.OpenFile(out file2, "/dir2/file".ToU8Span(), OpenMode.Read)); + Assert.Success(fs.OpenFile(out file2, "/dir2/file", OpenMode.Read)); using (file2) { @@ -68,15 +67,15 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IFileSystem fs = CreateFileSystem(); - fs.CreateDirectory("/dir".ToU8Span()).ThrowIfFailure(); - fs.CreateFile("/dir/file".ToU8Span(), 0, CreateFileOptions.None).ThrowIfFailure(); + fs.CreateDirectory("/dir").ThrowIfFailure(); + fs.CreateFile("/dir/file", 0, CreateFileOptions.None).ThrowIfFailure(); // Rollback should succeed Assert.Success(fs.Rollback()); // Make sure the file and directory are gone - Assert.Result(ResultFs.PathNotFound, fs.GetEntryType(out _, "/dir".ToU8Span())); - Assert.Result(ResultFs.PathNotFound, fs.GetEntryType(out _, "/dir/file".ToU8Span())); + Assert.Result(ResultFs.PathNotFound, fs.GetEntryType(out _, "/dir")); + Assert.Result(ResultFs.PathNotFound, fs.GetEntryType(out _, "/dir/file")); } [Fact] @@ -85,16 +84,16 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase IReopenableFileSystemCreator fsCreator = GetFileSystemCreator(); IFileSystem fs = fsCreator.Create(); - fs.CreateDirectory("/dir".ToU8Span()).ThrowIfFailure(); - fs.CreateFile("/dir/file".ToU8Span(), 0, CreateFileOptions.None).ThrowIfFailure(); + fs.CreateDirectory("/dir").ThrowIfFailure(); + fs.CreateFile("/dir/file", 0, CreateFileOptions.None).ThrowIfFailure(); // Close without committing and reopen the file system fs.Dispose(); fs = fsCreator.Create(); // Make sure the file and directory are gone - Assert.Result(ResultFs.PathNotFound, fs.GetEntryType(out _, "/dir".ToU8Span())); - Assert.Result(ResultFs.PathNotFound, fs.GetEntryType(out _, "/dir/file".ToU8Span())); + Assert.Result(ResultFs.PathNotFound, fs.GetEntryType(out _, "/dir")); + Assert.Result(ResultFs.PathNotFound, fs.GetEntryType(out _, "/dir/file")); } [Fact] @@ -107,10 +106,10 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase IReopenableFileSystemCreator fsCreator = GetFileSystemCreator(); IFileSystem fs = fsCreator.Create(); - fs.CreateDirectory("/dir".ToU8Span()).ThrowIfFailure(); - fs.CreateFile("/dir/file".ToU8Span(), data1.Length, CreateFileOptions.None).ThrowIfFailure(); + fs.CreateDirectory("/dir").ThrowIfFailure(); + fs.CreateFile("/dir/file", data1.Length, CreateFileOptions.None).ThrowIfFailure(); - fs.OpenFile(out IFile file, "/dir/file".ToU8Span(), OpenMode.Write).ThrowIfFailure(); + fs.OpenFile(out IFile file, "/dir/file", OpenMode.Write).ThrowIfFailure(); file.Write(0, data1, WriteOption.Flush).ThrowIfFailure(); file.Dispose(); @@ -121,7 +120,7 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase fs = fsCreator.Create(); // Make changes to the file - fs.OpenFile(out file, "/dir/file".ToU8Span(), OpenMode.Write).ThrowIfFailure(); + fs.OpenFile(out file, "/dir/file", OpenMode.Write).ThrowIfFailure(); file.Write(0, data2, WriteOption.Flush).ThrowIfFailure(); file.Dispose(); @@ -130,7 +129,7 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase // The file should contain the original data after the rollback byte[] readData = new byte[data1.Length]; - Assert.Success(fs.OpenFile(out file, "/dir/file".ToU8Span(), OpenMode.Read)); + Assert.Success(fs.OpenFile(out file, "/dir/file", OpenMode.Read)); using (file) { diff --git a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IAttributeFileSystemTests.cs b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IAttributeFileSystemTests.cs index 3c42e8a7..ca32f355 100644 --- a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IAttributeFileSystemTests.cs +++ b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IAttributeFileSystemTests.cs @@ -1,5 +1,4 @@ -using LibHac.Common; -using LibHac.Fs; +using LibHac.Fs; using LibHac.Fs.Fsa; using Xunit; @@ -14,9 +13,9 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IAttributeFileSystem fs = CreateAttributeFileSystem(); - Assert.Success(fs.CreateDirectory("/dir".ToU8Span(), NxFileAttributes.None)); + Assert.Success(fs.CreateDirectory("/dir", NxFileAttributes.None)); - Assert.Success(fs.GetFileAttributes(out NxFileAttributes attributes, "/dir".ToU8Span())); + Assert.Success(fs.GetFileAttributes(out NxFileAttributes attributes, "/dir")); Assert.Equal(NxFileAttributes.Directory, attributes); } @@ -25,9 +24,9 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IAttributeFileSystem fs = CreateAttributeFileSystem(); - Assert.Success(fs.CreateDirectory("/dir".ToU8Span(), NxFileAttributes.Archive)); + Assert.Success(fs.CreateDirectory("/dir", NxFileAttributes.Archive)); - Assert.Success(fs.GetFileAttributes(out NxFileAttributes attributes, "/dir".ToU8Span())); + Assert.Success(fs.GetFileAttributes(out NxFileAttributes attributes, "/dir")); Assert.Equal(NxFileAttributes.Directory | NxFileAttributes.Archive, attributes); } @@ -35,9 +34,9 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase public void GetFileAttributes_AttributesOnNewFileAreEmpty() { IAttributeFileSystem fs = CreateAttributeFileSystem(); - fs.CreateFile("/file".ToU8Span(), 0, CreateFileOptions.None); + fs.CreateFile("/file", 0, CreateFileOptions.None); - Assert.Success(fs.GetFileAttributes(out NxFileAttributes attributes, "/file".ToU8Span())); + Assert.Success(fs.GetFileAttributes(out NxFileAttributes attributes, "/file")); Assert.Equal(NxFileAttributes.None, attributes); } @@ -45,9 +44,9 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase public void GetFileAttributes_AttributesOnNewDirHaveOnlyDirFlagSet() { IAttributeFileSystem fs = CreateAttributeFileSystem(); - fs.CreateDirectory("/dir".ToU8Span()); + fs.CreateDirectory("/dir"); - Assert.Success(fs.GetFileAttributes(out NxFileAttributes attributes, "/dir".ToU8Span())); + Assert.Success(fs.GetFileAttributes(out NxFileAttributes attributes, "/dir")); Assert.Equal(NxFileAttributes.Directory, attributes); } @@ -56,7 +55,7 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IAttributeFileSystem fs = CreateAttributeFileSystem(); - Result rc = fs.GetFileAttributes(out _, "/path".ToU8Span()); + Result rc = fs.GetFileAttributes(out _, "/path"); Assert.Result(ResultFs.PathNotFound, rc); } @@ -66,7 +65,7 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IAttributeFileSystem fs = CreateAttributeFileSystem(); - Result rc = fs.SetFileAttributes("/path".ToU8Span(), NxFileAttributes.None); + Result rc = fs.SetFileAttributes("/path", NxFileAttributes.None); Assert.Result(ResultFs.PathNotFound, rc); } @@ -75,10 +74,10 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase public void SetFileAttributes_SetAttributeOnFile() { IAttributeFileSystem fs = CreateAttributeFileSystem(); - fs.CreateFile("/file".ToU8Span(), 0, CreateFileOptions.None); + fs.CreateFile("/file", 0, CreateFileOptions.None); - Result rcSet = fs.SetFileAttributes("/file".ToU8Span(), NxFileAttributes.Archive); - Result rcGet = fs.GetFileAttributes(out NxFileAttributes attributes, "/file".ToU8Span()); + Result rcSet = fs.SetFileAttributes("/file", NxFileAttributes.Archive); + Result rcGet = fs.GetFileAttributes(out NxFileAttributes attributes, "/file"); Assert.Success(rcSet); Assert.Success(rcGet); @@ -89,10 +88,10 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase public void SetFileAttributes_SetAttributeOnDirectory() { IAttributeFileSystem fs = CreateAttributeFileSystem(); - fs.CreateDirectory("/dir".ToU8Span()); + fs.CreateDirectory("/dir"); - Result rcSet = fs.SetFileAttributes("/dir".ToU8Span(), NxFileAttributes.Archive); - Result rcGet = fs.GetFileAttributes(out NxFileAttributes attributes, "/dir".ToU8Span()); + Result rcSet = fs.SetFileAttributes("/dir", NxFileAttributes.Archive); + Result rcGet = fs.GetFileAttributes(out NxFileAttributes attributes, "/dir"); Assert.Success(rcSet); Assert.Success(rcGet); @@ -104,9 +103,9 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IAttributeFileSystem fs = CreateAttributeFileSystem(); - fs.CreateFile("/file".ToU8Span(), 845, CreateFileOptions.None); + fs.CreateFile("/file", 845, CreateFileOptions.None); - Assert.Success(fs.GetFileSize(out long fileSize, "/file".ToU8Span())); + Assert.Success(fs.GetFileSize(out long fileSize, "/file")); Assert.Equal(845, fileSize); } @@ -115,7 +114,7 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IAttributeFileSystem fs = CreateAttributeFileSystem(); - Result rc = fs.GetFileSize(out _, "/path".ToU8Span()); + Result rc = fs.GetFileSize(out _, "/path"); Assert.Result(ResultFs.PathNotFound, rc); } @@ -124,9 +123,9 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase public void GetFileSize_PathIsDirectory_ReturnsPathNotFound() { IAttributeFileSystem fs = CreateAttributeFileSystem(); - fs.CreateDirectory("/dir".ToU8Span()); + fs.CreateDirectory("/dir"); - Result rc = fs.GetFileSize(out _, "/dir".ToU8Span()); + Result rc = fs.GetFileSize(out _, "/dir"); Assert.Result(ResultFs.PathNotFound, rc); } diff --git a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.CleanDirectoryRecursively.cs b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.CleanDirectoryRecursively.cs index 0540c57c..8543fd81 100644 --- a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.CleanDirectoryRecursively.cs +++ b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.CleanDirectoryRecursively.cs @@ -1,5 +1,4 @@ -using LibHac.Common; -using LibHac.Fs; +using LibHac.Fs; using LibHac.Fs.Fsa; using Xunit; @@ -12,15 +11,15 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IFileSystem fs = CreateFileSystem(); - fs.CreateDirectory("/dir".ToU8Span()); - fs.CreateDirectory("/dir/dir2".ToU8Span()); - fs.CreateFile("/dir/file1".ToU8Span(), 0, CreateFileOptions.None); + fs.CreateDirectory("/dir"); + fs.CreateDirectory("/dir/dir2"); + fs.CreateFile("/dir/file1", 0, CreateFileOptions.None); - Result rcDelete = fs.CleanDirectoryRecursively("/dir".ToU8Span()); + Result rcDelete = fs.CleanDirectoryRecursively("/dir"); - Result rcDir1Type = fs.GetEntryType(out DirectoryEntryType dir1Type, "/dir".ToU8Span()); - Result rcDir2Type = fs.GetEntryType(out _, "/dir/dir2".ToU8Span()); - Result rcFileType = fs.GetEntryType(out _, "/dir/file1".ToU8Span()); + Result rcDir1Type = fs.GetEntryType(out DirectoryEntryType dir1Type, "/dir"); + Result rcDir2Type = fs.GetEntryType(out _, "/dir/dir2"); + Result rcFileType = fs.GetEntryType(out _, "/dir/file1"); Assert.Success(rcDelete); diff --git a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.CreateDirectory.cs b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.CreateDirectory.cs index 9b469ca9..91bda4b1 100644 --- a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.CreateDirectory.cs +++ b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.CreateDirectory.cs @@ -1,5 +1,4 @@ -using LibHac.Common; -using LibHac.Fs; +using LibHac.Fs; using LibHac.Fs.Fsa; using Xunit; @@ -12,9 +11,9 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IFileSystem fs = CreateFileSystem(); - fs.CreateDirectory("/dir".ToU8Span()); + fs.CreateDirectory("/dir"); - Assert.Success(fs.GetEntryType(out DirectoryEntryType type, "/dir".ToU8Span())); + Assert.Success(fs.GetEntryType(out DirectoryEntryType type, "/dir")); Assert.Equal(DirectoryEntryType.Directory, type); } @@ -23,9 +22,9 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IFileSystem fs = CreateFileSystem(); - fs.CreateDirectory("/dir".ToU8Span()); + fs.CreateDirectory("/dir"); - Result rc = fs.CreateDirectory("/dir".ToU8Span()); + Result rc = fs.CreateDirectory("/dir"); Assert.Result(ResultFs.PathAlreadyExists, rc); } @@ -35,9 +34,9 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IFileSystem fs = CreateFileSystem(); - fs.CreateFile("/file".ToU8Span(), 0, CreateFileOptions.None); + fs.CreateFile("/file", 0, CreateFileOptions.None); - Result rc = fs.CreateDirectory("/file".ToU8Span()); + Result rc = fs.CreateDirectory("/file"); Assert.Result(ResultFs.PathAlreadyExists, rc); } @@ -47,7 +46,7 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IFileSystem fs = CreateFileSystem(); - Result rc = fs.CreateFile("/dir1/dir2".ToU8Span(), 0, CreateFileOptions.None); + Result rc = fs.CreateFile("/dir1/dir2", 0, CreateFileOptions.None); Assert.Result(ResultFs.PathNotFound, rc); } @@ -57,8 +56,8 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IFileSystem fs = CreateFileSystem(); - fs.CreateDirectory("/dir/".ToU8Span()); - Result rc = fs.GetEntryType(out DirectoryEntryType type, "/dir/".ToU8Span()); + fs.CreateDirectory("/dir/"); + Result rc = fs.GetEntryType(out DirectoryEntryType type, "/dir/"); Assert.Success(rc); Assert.Equal(DirectoryEntryType.Directory, type); @@ -69,11 +68,11 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IFileSystem fs = CreateFileSystem(); - fs.CreateDirectory("/dir1".ToU8Span()); - fs.CreateDirectory("/dir2".ToU8Span()); + fs.CreateDirectory("/dir1"); + fs.CreateDirectory("/dir2"); - Result rc1 = fs.GetEntryType(out DirectoryEntryType type1, "/dir1".ToU8Span()); - Result rc2 = fs.GetEntryType(out DirectoryEntryType type2, "/dir2".ToU8Span()); + Result rc1 = fs.GetEntryType(out DirectoryEntryType type1, "/dir1"); + Result rc2 = fs.GetEntryType(out DirectoryEntryType type2, "/dir2"); Assert.Success(rc1); Assert.Success(rc2); @@ -86,14 +85,14 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IFileSystem fs = CreateFileSystem(); - fs.CreateDirectory("/dir1".ToU8Span()); - fs.CreateDirectory("/dir2".ToU8Span()); + fs.CreateDirectory("/dir1"); + fs.CreateDirectory("/dir2"); - fs.CreateDirectory("/dir1/dir1a".ToU8Span()); - fs.CreateDirectory("/dir2/dir2a".ToU8Span()); + fs.CreateDirectory("/dir1/dir1a"); + fs.CreateDirectory("/dir2/dir2a"); - Result rc1 = fs.GetEntryType(out DirectoryEntryType type1, "/dir1/dir1a".ToU8Span()); - Result rc2 = fs.GetEntryType(out DirectoryEntryType type2, "/dir2/dir2a".ToU8Span()); + Result rc1 = fs.GetEntryType(out DirectoryEntryType type1, "/dir1/dir1a"); + Result rc2 = fs.GetEntryType(out DirectoryEntryType type2, "/dir2/dir2a"); Assert.Success(rc1); Assert.Success(rc2); diff --git a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.CreateFile.cs b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.CreateFile.cs index 2755c6eb..dc65ecf9 100644 --- a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.CreateFile.cs +++ b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.CreateFile.cs @@ -1,5 +1,4 @@ -using LibHac.Common; -using LibHac.Fs; +using LibHac.Fs; using LibHac.Fs.Fsa; using Xunit; @@ -12,8 +11,8 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IFileSystem fs = CreateFileSystem(); - fs.CreateFile("/file".ToU8Span(), 0, CreateFileOptions.None); - Result rc = fs.GetEntryType(out DirectoryEntryType type, "/file".ToU8Span()); + fs.CreateFile("/file", 0, CreateFileOptions.None); + Result rc = fs.GetEntryType(out DirectoryEntryType type, "/file"); Assert.Success(rc); Assert.Equal(DirectoryEntryType.File, type); @@ -24,9 +23,9 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IFileSystem fs = CreateFileSystem(); - fs.CreateDirectory("/dir".ToU8Span()); + fs.CreateDirectory("/dir"); - Result rc = fs.CreateFile("/dir".ToU8Span(), 0, CreateFileOptions.None); + Result rc = fs.CreateFile("/dir", 0, CreateFileOptions.None); Assert.Result(ResultFs.PathAlreadyExists, rc); } @@ -36,9 +35,9 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IFileSystem fs = CreateFileSystem(); - fs.CreateFile("/file".ToU8Span(), 0, CreateFileOptions.None); + fs.CreateFile("/file", 0, CreateFileOptions.None); - Result rc = fs.CreateFile("/file".ToU8Span(), 0, CreateFileOptions.None); + Result rc = fs.CreateFile("/file", 0, CreateFileOptions.None); Assert.Result(ResultFs.PathAlreadyExists, rc); } @@ -48,7 +47,7 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IFileSystem fs = CreateFileSystem(); - Result rc = fs.CreateFile("/dir/file".ToU8Span(), 0, CreateFileOptions.None); + Result rc = fs.CreateFile("/dir/file", 0, CreateFileOptions.None); Assert.Result(ResultFs.PathNotFound, rc); } @@ -58,9 +57,9 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IFileSystem fs = CreateFileSystem(); - fs.CreateFile("/file/".ToU8Span(), 0, CreateFileOptions.None); + fs.CreateFile("/file/", 0, CreateFileOptions.None); - Assert.Success(fs.GetEntryType(out DirectoryEntryType type, "/file/".ToU8Span())); + Assert.Success(fs.GetEntryType(out DirectoryEntryType type, "/file/")); Assert.Equal(DirectoryEntryType.File, type); } @@ -71,9 +70,9 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase IFileSystem fs = CreateFileSystem(); - fs.CreateFile("/file".ToU8Span(), expectedSize, CreateFileOptions.None); + fs.CreateFile("/file", expectedSize, CreateFileOptions.None); - fs.OpenFile(out IFile file, "/file".ToU8Span(), OpenMode.Read); + fs.OpenFile(out IFile file, "/file", OpenMode.Read); Assert.Success(file.GetSize(out long fileSize)); Assert.Equal(expectedSize, fileSize); @@ -84,11 +83,11 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IFileSystem fs = CreateFileSystem(); - fs.CreateFile("/file1".ToU8Span(), 0, CreateFileOptions.None); - fs.CreateFile("/file2".ToU8Span(), 0, CreateFileOptions.None); + fs.CreateFile("/file1", 0, CreateFileOptions.None); + fs.CreateFile("/file2", 0, CreateFileOptions.None); - Result rc1 = fs.GetEntryType(out DirectoryEntryType type1, "/file1".ToU8Span()); - Result rc2 = fs.GetEntryType(out DirectoryEntryType type2, "/file2".ToU8Span()); + Result rc1 = fs.GetEntryType(out DirectoryEntryType type1, "/file1"); + Result rc2 = fs.GetEntryType(out DirectoryEntryType type2, "/file2"); Assert.Success(rc1); Assert.Success(rc2); @@ -101,14 +100,14 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IFileSystem fs = CreateFileSystem(); - fs.CreateDirectory("/dir1".ToU8Span()); - fs.CreateDirectory("/dir2".ToU8Span()); + fs.CreateDirectory("/dir1"); + fs.CreateDirectory("/dir2"); - fs.CreateFile("/dir1/file1".ToU8Span(), 0, CreateFileOptions.None); - fs.CreateFile("/dir2/file2".ToU8Span(), 0, CreateFileOptions.None); + fs.CreateFile("/dir1/file1", 0, CreateFileOptions.None); + fs.CreateFile("/dir2/file2", 0, CreateFileOptions.None); - Result rc1 = fs.GetEntryType(out DirectoryEntryType type1, "/dir1/file1".ToU8Span()); - Result rc2 = fs.GetEntryType(out DirectoryEntryType type2, "/dir2/file2".ToU8Span()); + Result rc1 = fs.GetEntryType(out DirectoryEntryType type1, "/dir1/file1"); + Result rc2 = fs.GetEntryType(out DirectoryEntryType type2, "/dir2/file2"); Assert.Success(rc1); Assert.Success(rc2); diff --git a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.DeleteDirectory.cs b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.DeleteDirectory.cs index 3abdcdf8..bc01ecb5 100644 --- a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.DeleteDirectory.cs +++ b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.DeleteDirectory.cs @@ -1,5 +1,4 @@ -using LibHac.Common; -using LibHac.Fs; +using LibHac.Fs; using LibHac.Fs.Fsa; using Xunit; @@ -12,7 +11,7 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IFileSystem fs = CreateFileSystem(); - Result rc = fs.DeleteDirectory("/dir".ToU8Span()); + Result rc = fs.DeleteDirectory("/dir"); Assert.Result(ResultFs.PathNotFound, rc); } @@ -22,10 +21,10 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IFileSystem fs = CreateFileSystem(); - fs.CreateDirectory("/dir".ToU8Span()); + fs.CreateDirectory("/dir"); - Result rcDelete = fs.DeleteDirectory("/dir".ToU8Span()); - Result rcEntry = fs.GetEntryType(out _, "/dir".ToU8Span()); + Result rcDelete = fs.DeleteDirectory("/dir"); + Result rcEntry = fs.GetEntryType(out _, "/dir"); Assert.Success(rcDelete); Assert.Result(ResultFs.PathNotFound, rcEntry); @@ -36,9 +35,9 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IFileSystem fs = CreateFileSystem(); - fs.CreateFile("/file".ToU8Span(), 0, CreateFileOptions.None); + fs.CreateFile("/file", 0, CreateFileOptions.None); - Result rc = fs.DeleteDirectory("/file".ToU8Span()); + Result rc = fs.DeleteDirectory("/file"); Assert.Result(ResultFs.PathNotFound, rc); } @@ -48,12 +47,12 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IFileSystem fs = CreateFileSystem(); - fs.CreateDirectory("/dir1".ToU8Span()); - fs.CreateDirectory("/dir2".ToU8Span()); + fs.CreateDirectory("/dir1"); + fs.CreateDirectory("/dir2"); - Result rcDelete = fs.DeleteDirectory("/dir2".ToU8Span()); - Result rcEntry1 = fs.GetEntryType(out DirectoryEntryType dir1Type, "/dir1".ToU8Span()); - Result rcEntry2 = fs.GetEntryType(out _, "/dir2".ToU8Span()); + Result rcDelete = fs.DeleteDirectory("/dir2"); + Result rcEntry1 = fs.GetEntryType(out DirectoryEntryType dir1Type, "/dir1"); + Result rcEntry2 = fs.GetEntryType(out _, "/dir2"); Assert.Success(rcDelete); Assert.Success(rcEntry1); @@ -67,12 +66,12 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IFileSystem fs = CreateFileSystem(); - fs.CreateDirectory("/dir2".ToU8Span()); - fs.CreateDirectory("/dir1".ToU8Span()); + fs.CreateDirectory("/dir2"); + fs.CreateDirectory("/dir1"); - Result rcDelete = fs.DeleteDirectory("/dir2".ToU8Span()); - Result rcEntry1 = fs.GetEntryType(out DirectoryEntryType dir1Type, "/dir1".ToU8Span()); - Result rcEntry2 = fs.GetEntryType(out _, "/dir2".ToU8Span()); + Result rcDelete = fs.DeleteDirectory("/dir2"); + Result rcEntry1 = fs.GetEntryType(out DirectoryEntryType dir1Type, "/dir1"); + Result rcEntry2 = fs.GetEntryType(out _, "/dir2"); Assert.Success(rcDelete); Assert.Success(rcEntry1); @@ -86,10 +85,10 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IFileSystem fs = CreateFileSystem(); - fs.CreateDirectory("/dir".ToU8Span()); - fs.CreateFile("/dir/file".ToU8Span(), 0, CreateFileOptions.None); + fs.CreateDirectory("/dir"); + fs.CreateFile("/dir/file", 0, CreateFileOptions.None); - Result rc = fs.DeleteDirectory("/dir".ToU8Span()); + Result rc = fs.DeleteDirectory("/dir"); Assert.Result(ResultFs.DirectoryNotEmpty, rc); } diff --git a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.DeleteDirectoryRecursively.cs b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.DeleteDirectoryRecursively.cs index d66b0152..fde7613e 100644 --- a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.DeleteDirectoryRecursively.cs +++ b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.DeleteDirectoryRecursively.cs @@ -1,5 +1,4 @@ -using LibHac.Common; -using LibHac.Fs; +using LibHac.Fs; using LibHac.Fs.Fsa; using Xunit; @@ -12,15 +11,15 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IFileSystem fs = CreateFileSystem(); - fs.CreateDirectory("/dir".ToU8Span()); - fs.CreateDirectory("/dir/dir2".ToU8Span()); - fs.CreateFile("/dir/file1".ToU8Span(), 0, CreateFileOptions.None); + fs.CreateDirectory("/dir"); + fs.CreateDirectory("/dir/dir2"); + fs.CreateFile("/dir/file1", 0, CreateFileOptions.None); - Result rcDelete = fs.DeleteDirectoryRecursively("/dir".ToU8Span()); + Result rcDelete = fs.DeleteDirectoryRecursively("/dir"); - Result rcDir1Type = fs.GetEntryType(out _, "/dir".ToU8Span()); - Result rcDir2Type = fs.GetEntryType(out _, "/dir/dir2".ToU8Span()); - Result rcFileType = fs.GetEntryType(out _, "/dir/file1".ToU8Span()); + Result rcDir1Type = fs.GetEntryType(out _, "/dir"); + Result rcDir2Type = fs.GetEntryType(out _, "/dir/dir2"); + Result rcFileType = fs.GetEntryType(out _, "/dir/file1"); Assert.Success(rcDelete); diff --git a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.DeleteFile.cs b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.DeleteFile.cs index bf501862..06e87839 100644 --- a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.DeleteFile.cs +++ b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.DeleteFile.cs @@ -1,5 +1,4 @@ -using LibHac.Common; -using LibHac.Fs; +using LibHac.Fs; using LibHac.Fs.Fsa; using Xunit; @@ -12,7 +11,7 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IFileSystem fs = CreateFileSystem(); - Result rc = fs.DeleteFile("/file".ToU8Span()); + Result rc = fs.DeleteFile("/file"); Assert.Result(ResultFs.PathNotFound, rc); } @@ -21,10 +20,10 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IFileSystem fs = CreateFileSystem(); - fs.CreateFile("/file".ToU8Span(), 0, CreateFileOptions.None); + fs.CreateFile("/file", 0, CreateFileOptions.None); - Result rcDelete = fs.DeleteFile("/file".ToU8Span()); - Result rcEntry = fs.GetEntryType(out _, "/file".ToU8Span()); + Result rcDelete = fs.DeleteFile("/file"); + Result rcEntry = fs.GetEntryType(out _, "/file"); Assert.Success(rcDelete); Assert.Result(ResultFs.PathNotFound, rcEntry); @@ -35,9 +34,9 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IFileSystem fs = CreateFileSystem(); - fs.CreateDirectory("/dir".ToU8Span()); + fs.CreateDirectory("/dir"); - Result rc = fs.DeleteFile("/dir".ToU8Span()); + Result rc = fs.DeleteFile("/dir"); Assert.Result(ResultFs.PathNotFound, rc); } @@ -47,12 +46,12 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IFileSystem fs = CreateFileSystem(); - fs.CreateFile("/file1".ToU8Span(), 0, CreateFileOptions.None); - fs.CreateFile("/file2".ToU8Span(), 0, CreateFileOptions.None); + fs.CreateFile("/file1", 0, CreateFileOptions.None); + fs.CreateFile("/file2", 0, CreateFileOptions.None); - Result rcDelete = fs.DeleteFile("/file2".ToU8Span()); - Result rcEntry1 = fs.GetEntryType(out DirectoryEntryType dir1Type, "/file1".ToU8Span()); - Result rcEntry2 = fs.GetEntryType(out _, "/file2".ToU8Span()); + Result rcDelete = fs.DeleteFile("/file2"); + Result rcEntry1 = fs.GetEntryType(out DirectoryEntryType dir1Type, "/file1"); + Result rcEntry2 = fs.GetEntryType(out _, "/file2"); Assert.Success(rcDelete); Assert.Success(rcEntry1); @@ -66,12 +65,12 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IFileSystem fs = CreateFileSystem(); - fs.CreateFile("/file2".ToU8Span(), 0, CreateFileOptions.None); - fs.CreateFile("/file1".ToU8Span(), 0, CreateFileOptions.None); + fs.CreateFile("/file2", 0, CreateFileOptions.None); + fs.CreateFile("/file1", 0, CreateFileOptions.None); - Result rcDelete = fs.DeleteFile("/file2".ToU8Span()); - Result rcEntry1 = fs.GetEntryType(out DirectoryEntryType dir1Type, "/file1".ToU8Span()); - Result rcEntry2 = fs.GetEntryType(out _, "/file2".ToU8Span()); + Result rcDelete = fs.DeleteFile("/file2"); + Result rcEntry1 = fs.GetEntryType(out DirectoryEntryType dir1Type, "/file1"); + Result rcEntry2 = fs.GetEntryType(out _, "/file2"); Assert.Success(rcDelete); Assert.Success(rcEntry1); diff --git a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.IDirectory.cs b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.IDirectory.cs index 0d3c9bc5..d4fed187 100644 --- a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.IDirectory.cs +++ b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.IDirectory.cs @@ -15,7 +15,7 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase IFileSystem fs = CreateFileSystem(); Span entries = stackalloc DirectoryEntry[1]; - Assert.Success(fs.OpenDirectory(out IDirectory directory, "/".ToU8Span(), OpenDirectoryMode.All)); + Assert.Success(fs.OpenDirectory(out IDirectory directory, "/", OpenDirectoryMode.All)); Assert.Success(directory.Read(out long entriesRead, entries)); Assert.Equal(0, entriesRead); @@ -26,7 +26,7 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IFileSystem fs = CreateFileSystem(); - Assert.Success(fs.OpenDirectory(out IDirectory directory, "/".ToU8Span(), OpenDirectoryMode.All)); + Assert.Success(fs.OpenDirectory(out IDirectory directory, "/", OpenDirectoryMode.All)); Assert.Success(directory.GetEntryCount(out long entryCount)); Assert.Equal(0, entryCount); @@ -36,13 +36,13 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase public void IDirectoryRead_AllEntriesAreReturned() { IFileSystem fs = CreateFileSystem(); - fs.CreateDirectory("/dir".ToU8Span()); - fs.CreateDirectory("/dir/dir1".ToU8Span()); - fs.CreateFile("/dir/dir1/file1".ToU8Span(), 0, CreateFileOptions.None); - fs.CreateFile("/dir/file1".ToU8Span(), 0, CreateFileOptions.None); - fs.CreateFile("/dir/file2".ToU8Span(), 0, CreateFileOptions.None); + fs.CreateDirectory("/dir"); + fs.CreateDirectory("/dir/dir1"); + fs.CreateFile("/dir/dir1/file1", 0, CreateFileOptions.None); + fs.CreateFile("/dir/file1", 0, CreateFileOptions.None); + fs.CreateFile("/dir/file2", 0, CreateFileOptions.None); - Assert.Success(fs.OpenDirectory(out IDirectory dir, "/dir".ToU8Span(), OpenDirectoryMode.All)); + Assert.Success(fs.OpenDirectory(out IDirectory dir, "/dir", OpenDirectoryMode.All)); var entry1 = new DirectoryEntry(); var entry2 = new DirectoryEntry(); diff --git a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.IFile.Read.cs b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.IFile.Read.cs index 62dd59fa..ee08d40a 100644 --- a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.IFile.Read.cs +++ b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.IFile.Read.cs @@ -1,5 +1,4 @@ using System; -using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; using Xunit; @@ -13,10 +12,10 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IFileSystem fs = CreateFileSystem(); - fs.CreateFile("/file".ToU8Span(), 100, CreateFileOptions.None); + fs.CreateFile("/file", 100, CreateFileOptions.None); byte[] buffer = new byte[20]; - fs.OpenFile(out IFile file, "/file".ToU8Span(), OpenMode.Read); + fs.OpenFile(out IFile file, "/file", OpenMode.Read); using (file) { Assert.Success(file.Read(out long bytesRead, 50, buffer, ReadOption.None)); @@ -29,10 +28,10 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IFileSystem fs = CreateFileSystem(); - fs.CreateFile("/file".ToU8Span(), 0, CreateFileOptions.None); + fs.CreateFile("/file", 0, CreateFileOptions.None); byte[] buffer = new byte[10]; - fs.OpenFile(out IFile file, "/file".ToU8Span(), OpenMode.Read); + fs.OpenFile(out IFile file, "/file", OpenMode.Read); using (file) { Result rc = file.Read(out _, 1, buffer, ReadOption.None); @@ -45,10 +44,10 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IFileSystem fs = CreateFileSystem(); - fs.CreateFile("/file".ToU8Span(), 0, CreateFileOptions.None); + fs.CreateFile("/file", 0, CreateFileOptions.None); byte[] buffer = new byte[10]; - fs.OpenFile(out IFile file, "/file".ToU8Span(), OpenMode.Write); + fs.OpenFile(out IFile file, "/file", OpenMode.Write); using (file) { Result rc = file.Read(out _, 0, buffer, ReadOption.None); @@ -61,10 +60,10 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IFileSystem fs = CreateFileSystem(); - fs.CreateFile("/file".ToU8Span(), 0, CreateFileOptions.None); + fs.CreateFile("/file", 0, CreateFileOptions.None); byte[] buffer = new byte[10]; - fs.OpenFile(out IFile file, "/file".ToU8Span(), OpenMode.Write); + fs.OpenFile(out IFile file, "/file", OpenMode.Write); using (file) { Result rc = file.Read(out _, -5, buffer, ReadOption.None); @@ -77,10 +76,10 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IFileSystem fs = CreateFileSystem(); - fs.CreateFile("/file".ToU8Span(), 0, CreateFileOptions.None); + fs.CreateFile("/file", 0, CreateFileOptions.None); byte[] buffer = new byte[10]; - fs.OpenFile(out IFile file, "/file".ToU8Span(), OpenMode.Write); + fs.OpenFile(out IFile file, "/file", OpenMode.Write); using (file) { Result rc = file.Read(out _, long.MaxValue - 5, buffer, ReadOption.None); @@ -93,10 +92,10 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IFileSystem fs = CreateFileSystem(); - fs.CreateFile("/file".ToU8Span(), 100, CreateFileOptions.None); + fs.CreateFile("/file", 100, CreateFileOptions.None); byte[] buffer = new byte[200]; - fs.OpenFile(out IFile file, "/file".ToU8Span(), OpenMode.Read); + fs.OpenFile(out IFile file, "/file", OpenMode.Read); using (file) { Assert.Success(file.Read(out long bytesRead, 90, buffer, ReadOption.None)); @@ -109,10 +108,10 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IFileSystem fs = CreateFileSystem(); - fs.CreateFile("/file".ToU8Span(), 100, CreateFileOptions.None); + fs.CreateFile("/file", 100, CreateFileOptions.None); // The contents of a created file are undefined, so zero the file - fs.OpenFile(out IFile file, "/file".ToU8Span(), OpenMode.Write); + fs.OpenFile(out IFile file, "/file", OpenMode.Write); using (file) { file.Write(0, new byte[100], WriteOption.None); @@ -124,7 +123,7 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase byte[] buffer = new byte[200]; buffer.AsSpan().Fill(0xCC); - fs.OpenFile(out file, "/file".ToU8Span(), OpenMode.Read); + fs.OpenFile(out file, "/file", OpenMode.Read); using (file) { Assert.Success(file.Read(out _, 90, buffer, ReadOption.None)); diff --git a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.IFile.Size.cs b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.IFile.Size.cs index c6f6c710..61dbfe17 100644 --- a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.IFile.Size.cs +++ b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.IFile.Size.cs @@ -1,5 +1,4 @@ -using LibHac.Common; -using LibHac.Fs; +using LibHac.Fs; using LibHac.Fs.Fsa; using Xunit; @@ -11,13 +10,13 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase public void SetSize_FileSizeModified() { IFileSystem fs = CreateFileSystem(); - fs.CreateFile("/file".ToU8Span(), 0, CreateFileOptions.None); + fs.CreateFile("/file", 0, CreateFileOptions.None); - fs.OpenFile(out IFile file, "/file".ToU8Span(), OpenMode.All); + fs.OpenFile(out IFile file, "/file", OpenMode.All); Result rc = file.SetSize(54321); file.Dispose(); - fs.OpenFile(out file, "/file".ToU8Span(), OpenMode.All); + fs.OpenFile(out file, "/file", OpenMode.All); file.GetSize(out long fileSize); file.Dispose(); diff --git a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.IFile.Write.cs b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.IFile.Write.cs index ac4a740d..550433d5 100644 --- a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.IFile.Write.cs +++ b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.IFile.Write.cs @@ -1,5 +1,4 @@ using System; -using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; using Xunit; @@ -15,15 +14,15 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase IFileSystem fs = CreateFileSystem(); - fs.CreateFile("/file".ToU8Span(), data.Length, CreateFileOptions.None); + fs.CreateFile("/file", data.Length, CreateFileOptions.None); - fs.OpenFile(out IFile file, "/file".ToU8Span(), OpenMode.Write); + fs.OpenFile(out IFile file, "/file", OpenMode.Write); file.Write(0, data, WriteOption.None); file.Dispose(); byte[] readData = new byte[data.Length]; - fs.OpenFile(out file, "/file".ToU8Span(), OpenMode.Read); + fs.OpenFile(out file, "/file", OpenMode.Read); using (file) { Assert.Success(file.Read(out long bytesRead, 0, readData, ReadOption.None)); @@ -38,10 +37,10 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IFileSystem fs = CreateFileSystem(); - fs.CreateFile("/file".ToU8Span(), 10, CreateFileOptions.None); + fs.CreateFile("/file", 10, CreateFileOptions.None); byte[] buffer = new byte[10]; - fs.OpenFile(out IFile file, "/file".ToU8Span(), OpenMode.Write); + fs.OpenFile(out IFile file, "/file", OpenMode.Write); using (file) { Result rc = file.Write(5, buffer, WriteOption.None); @@ -54,10 +53,10 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IFileSystem fs = CreateFileSystem(); - fs.CreateFile("/file".ToU8Span(), 10, CreateFileOptions.None); + fs.CreateFile("/file", 10, CreateFileOptions.None); byte[] buffer = new byte[10]; - fs.OpenFile(out IFile file, "/file".ToU8Span(), OpenMode.Read); + fs.OpenFile(out IFile file, "/file", OpenMode.Read); using (file) { Result rc = file.Write(5, buffer, WriteOption.None); @@ -70,10 +69,10 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IFileSystem fs = CreateFileSystem(); - fs.CreateFile("/file".ToU8Span(), 10, CreateFileOptions.None); + fs.CreateFile("/file", 10, CreateFileOptions.None); byte[] buffer = new byte[10]; - fs.OpenFile(out IFile file, "/file".ToU8Span(), OpenMode.Read); + fs.OpenFile(out IFile file, "/file", OpenMode.Read); using (file) { Result rc = file.Write(-5, buffer, WriteOption.None); @@ -86,10 +85,10 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IFileSystem fs = CreateFileSystem(); - fs.CreateFile("/file".ToU8Span(), 10, CreateFileOptions.None); + fs.CreateFile("/file", 10, CreateFileOptions.None); byte[] buffer = new byte[10]; - fs.OpenFile(out IFile file, "/file".ToU8Span(), OpenMode.Read); + fs.OpenFile(out IFile file, "/file", OpenMode.Read); using (file) { Result rc = file.Write(long.MaxValue - 5, buffer, WriteOption.None); @@ -102,10 +101,10 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IFileSystem fs = CreateFileSystem(); - fs.CreateFile("/file".ToU8Span(), 10, CreateFileOptions.None); + fs.CreateFile("/file", 10, CreateFileOptions.None); byte[] buffer = new byte[10]; - fs.OpenFile(out IFile file, "/file".ToU8Span(), OpenMode.All); + fs.OpenFile(out IFile file, "/file", OpenMode.All); using (file) { Assert.Success(file.Write(5, buffer, WriteOption.None)); @@ -120,10 +119,10 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IFileSystem fs = CreateFileSystem(); - fs.CreateFile("/file".ToU8Span(), 10, CreateFileOptions.None); + fs.CreateFile("/file", 10, CreateFileOptions.None); byte[] buffer = new byte[10]; - fs.OpenFile(out IFile file, "/file".ToU8Span(), OpenMode.All); + fs.OpenFile(out IFile file, "/file", OpenMode.All); using (file) { Assert.Success(file.Write(15, buffer, WriteOption.None)); @@ -138,7 +137,7 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IFileSystem fs = CreateFileSystem(); - fs.CreateFile("/file".ToU8Span(), 10, CreateFileOptions.None); + fs.CreateFile("/file", 10, CreateFileOptions.None); byte[] bufferExpected = new byte[25]; bufferExpected.AsSpan(15).Fill(0xCC); @@ -146,7 +145,7 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase byte[] writeBuffer = new byte[10]; writeBuffer.AsSpan().Fill(0xCC); - fs.OpenFile(out IFile file, "/file".ToU8Span(), OpenMode.All); + fs.OpenFile(out IFile file, "/file", OpenMode.All); using (file) { Assert.Success(file.Write(15, writeBuffer, WriteOption.None)); @@ -157,7 +156,7 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase byte[] readBuffer = new byte[25]; - fs.OpenFile(out file, "/file".ToU8Span(), OpenMode.Read); + fs.OpenFile(out file, "/file", OpenMode.Read); using (file) { file.Read(out _, 0, readBuffer, ReadOption.None); diff --git a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.OpenDirectory.cs b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.OpenDirectory.cs index acbea17b..c8c3b669 100644 --- a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.OpenDirectory.cs +++ b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.OpenDirectory.cs @@ -1,5 +1,4 @@ -using LibHac.Common; -using LibHac.Fs; +using LibHac.Fs; using LibHac.Fs.Fsa; using Xunit; @@ -12,9 +11,9 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IFileSystem fs = CreateFileSystem(); - fs.CreateFile("/file".ToU8Span(), 0, CreateFileOptions.None); + fs.CreateFile("/file", 0, CreateFileOptions.None); - Result rc = fs.OpenDirectory(out _, "/file".ToU8Span(), OpenDirectoryMode.All); + Result rc = fs.OpenDirectory(out _, "/file", OpenDirectoryMode.All); Assert.Result(ResultFs.PathNotFound, rc); } diff --git a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.OpenFile.cs b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.OpenFile.cs index c8f5a8c2..aee7ee75 100644 --- a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.OpenFile.cs +++ b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.OpenFile.cs @@ -1,5 +1,4 @@ -using LibHac.Common; -using LibHac.Fs; +using LibHac.Fs; using LibHac.Fs.Fsa; using Xunit; @@ -12,9 +11,9 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IFileSystem fs = CreateFileSystem(); - fs.CreateDirectory("/dir".ToU8Span()); + fs.CreateDirectory("/dir"); - Result rc = fs.OpenFile(out _, "/dir".ToU8Span(), OpenMode.All); + Result rc = fs.OpenFile(out _, "/dir", OpenMode.All); Assert.Result(ResultFs.PathNotFound, rc); } diff --git a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.RenameDirectory.cs b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.RenameDirectory.cs index 861483ff..0a72ebbe 100644 --- a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.RenameDirectory.cs +++ b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.RenameDirectory.cs @@ -1,5 +1,4 @@ -using LibHac.Common; -using LibHac.Fs; +using LibHac.Fs; using LibHac.Fs.Fsa; using Xunit; @@ -12,11 +11,11 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IFileSystem fs = CreateFileSystem(); - fs.CreateDirectory("/dir1".ToU8Span()); - Result rcRename = fs.RenameDirectory("/dir1".ToU8Span(), "/dir2".ToU8Span()); + fs.CreateDirectory("/dir1"); + Result rcRename = fs.RenameDirectory("/dir1", "/dir2"); - Result rcDir2 = fs.GetEntryType(out DirectoryEntryType dir2Type, "/dir2".ToU8Span()); - Result rcDir1 = fs.GetEntryType(out _, "/dir1".ToU8Span()); + Result rcDir2 = fs.GetEntryType(out DirectoryEntryType dir2Type, "/dir2"); + Result rcDir1 = fs.GetEntryType(out _, "/dir1"); Assert.Success(rcRename); @@ -31,21 +30,21 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IFileSystem fs = CreateFileSystem(); - fs.CreateDirectory("/dir1".ToU8Span()); - fs.CreateDirectory("/dir1/dirC".ToU8Span()); - fs.CreateFile("/dir1/file1".ToU8Span(), 0, CreateFileOptions.None); + fs.CreateDirectory("/dir1"); + fs.CreateDirectory("/dir1/dirC"); + fs.CreateFile("/dir1/file1", 0, CreateFileOptions.None); - Result rcRename = fs.RenameDirectory("/dir1".ToU8Span(), "/dir2".ToU8Span()); + Result rcRename = fs.RenameDirectory("/dir1", "/dir2"); // Check that renamed structure exists - Result rcDir2 = fs.GetEntryType(out DirectoryEntryType dir2Type, "/dir2".ToU8Span()); - Result rcDirC = fs.GetEntryType(out DirectoryEntryType dir1CType, "/dir2/dirC".ToU8Span()); - Result rcFile1 = fs.GetEntryType(out DirectoryEntryType file1Type, "/dir2/file1".ToU8Span()); + Result rcDir2 = fs.GetEntryType(out DirectoryEntryType dir2Type, "/dir2"); + Result rcDirC = fs.GetEntryType(out DirectoryEntryType dir1CType, "/dir2/dirC"); + Result rcFile1 = fs.GetEntryType(out DirectoryEntryType file1Type, "/dir2/file1"); // Check that old structure doesn't exist - Result rcDir1 = fs.GetEntryType(out _, "/dir1".ToU8Span()); - Result rcDirCOld = fs.GetEntryType(out _, "/dir1/dirC".ToU8Span()); - Result rcFile1Old = fs.GetEntryType(out _, "/dir1/file1".ToU8Span()); + Result rcDir1 = fs.GetEntryType(out _, "/dir1"); + Result rcDirCOld = fs.GetEntryType(out _, "/dir1/dirC"); + Result rcFile1Old = fs.GetEntryType(out _, "/dir1/file1"); Assert.Success(rcRename); @@ -67,14 +66,14 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IFileSystem fs = CreateFileSystem(); - fs.CreateDirectory("/parent1".ToU8Span()); - fs.CreateDirectory("/parent2".ToU8Span()); - fs.CreateDirectory("/parent1/dir1".ToU8Span()); + fs.CreateDirectory("/parent1"); + fs.CreateDirectory("/parent2"); + fs.CreateDirectory("/parent1/dir1"); - Result rcRename = fs.RenameDirectory("/parent1/dir1".ToU8Span(), "/parent2/dir2".ToU8Span()); + Result rcRename = fs.RenameDirectory("/parent1/dir1", "/parent2/dir2"); - Result rcDir2 = fs.GetEntryType(out DirectoryEntryType dir2Type, "/parent2/dir2".ToU8Span()); - Result rcDir1 = fs.GetEntryType(out _, "/parent1/dir1".ToU8Span()); + Result rcDir2 = fs.GetEntryType(out DirectoryEntryType dir2Type, "/parent2/dir2"); + Result rcDir1 = fs.GetEntryType(out _, "/parent1/dir1"); Assert.Success(rcRename); @@ -90,13 +89,13 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IFileSystem fs = CreateFileSystem(); - fs.CreateDirectory("/dir1".ToU8Span()); - fs.CreateDirectory("/dir2".ToU8Span()); + fs.CreateDirectory("/dir1"); + fs.CreateDirectory("/dir2"); - Result rcRename = fs.RenameDirectory("/dir1".ToU8Span(), "/dir2".ToU8Span()); + Result rcRename = fs.RenameDirectory("/dir1", "/dir2"); - Result rcDir1 = fs.GetEntryType(out DirectoryEntryType dir1Type, "/dir1".ToU8Span()); - Result rcDir2 = fs.GetEntryType(out DirectoryEntryType dir2Type, "/dir2".ToU8Span()); + Result rcDir1 = fs.GetEntryType(out DirectoryEntryType dir1Type, "/dir1"); + Result rcDir2 = fs.GetEntryType(out DirectoryEntryType dir2Type, "/dir2"); Assert.Result(ResultFs.PathAlreadyExists, rcRename); diff --git a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.RenameFile.cs b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.RenameFile.cs index acbc4981..669bdc3a 100644 --- a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.RenameFile.cs +++ b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.RenameFile.cs @@ -1,5 +1,4 @@ -using LibHac.Common; -using LibHac.Fs; +using LibHac.Fs; using LibHac.Fs.Fsa; using Xunit; @@ -12,12 +11,12 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IFileSystem fs = CreateFileSystem(); - fs.CreateFile("/file1".ToU8Span(), 0, CreateFileOptions.None); + fs.CreateFile("/file1", 0, CreateFileOptions.None); - Assert.Success(fs.RenameFile("/file1".ToU8Span(), "/file2".ToU8Span())); + Assert.Success(fs.RenameFile("/file1", "/file2")); - Assert.Success(fs.GetEntryType(out DirectoryEntryType type, "/file2".ToU8Span())); - Result rc = fs.GetEntryType(out _, "/file1".ToU8Span()); + Assert.Success(fs.GetEntryType(out DirectoryEntryType type, "/file2")); + Result rc = fs.GetEntryType(out _, "/file1"); Assert.Equal(DirectoryEntryType.File, type); Assert.Result(ResultFs.PathNotFound, rc); @@ -27,13 +26,13 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IFileSystem fs = CreateFileSystem(); - fs.CreateFile("/file1".ToU8Span(), 0, CreateFileOptions.None); - fs.CreateDirectory("/dir".ToU8Span()); + fs.CreateFile("/file1", 0, CreateFileOptions.None); + fs.CreateDirectory("/dir"); - Assert.Success(fs.RenameFile("/file1".ToU8Span(), "/dir/file2".ToU8Span())); + Assert.Success(fs.RenameFile("/file1", "/dir/file2")); - Assert.Success(fs.GetEntryType(out DirectoryEntryType type, "/dir/file2".ToU8Span())); - Result rc = fs.GetEntryType(out _, "/file1".ToU8Span()); + Assert.Success(fs.GetEntryType(out DirectoryEntryType type, "/dir/file2")); + Result rc = fs.GetEntryType(out _, "/file1"); Assert.Equal(DirectoryEntryType.File, type); Assert.Result(ResultFs.PathNotFound, rc); @@ -44,10 +43,10 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IFileSystem fs = CreateFileSystem(); - fs.CreateFile("/file1".ToU8Span(), 0, CreateFileOptions.None); - fs.CreateFile("/file2".ToU8Span(), 0, CreateFileOptions.None); + fs.CreateFile("/file1", 0, CreateFileOptions.None); + fs.CreateFile("/file2", 0, CreateFileOptions.None); - Result rc = fs.RenameFile("/file1".ToU8Span(), "/file2".ToU8Span()); + Result rc = fs.RenameFile("/file1", "/file2"); Assert.Result(ResultFs.PathAlreadyExists, rc); } @@ -57,10 +56,10 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IFileSystem fs = CreateFileSystem(); - fs.CreateFile("/file".ToU8Span(), 0, CreateFileOptions.None); - fs.CreateDirectory("/dir".ToU8Span()); + fs.CreateFile("/file", 0, CreateFileOptions.None); + fs.CreateDirectory("/dir"); - Result rc = fs.RenameFile("/file".ToU8Span(), "/dir".ToU8Span()); + Result rc = fs.RenameFile("/file", "/dir"); Assert.Result(ResultFs.PathAlreadyExists, rc); } @@ -70,13 +69,13 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase { IFileSystem fs = CreateFileSystem(); - fs.CreateFile("/file1".ToU8Span(), 54321, CreateFileOptions.None); - fs.CreateFile("/file2".ToU8Span(), 12345, CreateFileOptions.None); + fs.CreateFile("/file1", 54321, CreateFileOptions.None); + fs.CreateFile("/file2", 12345, CreateFileOptions.None); - fs.RenameFile("/file1".ToU8Span(), "/file2".ToU8Span()); + fs.RenameFile("/file1", "/file2"); - Assert.Success(fs.OpenFile(out IFile file1, "/file1".ToU8Span(), OpenMode.Read)); - Assert.Success(fs.OpenFile(out IFile file2, "/file2".ToU8Span(), OpenMode.Read)); + Assert.Success(fs.OpenFile(out IFile file1, "/file1", OpenMode.Read)); + Assert.Success(fs.OpenFile(out IFile file2, "/file2", OpenMode.Read)); using (file1) using (file2) @@ -96,17 +95,17 @@ namespace LibHac.Tests.Fs.IFileSystemTestBase IFileSystem fs = CreateFileSystem(); - fs.CreateFile("/file".ToU8Span(), data.Length, CreateFileOptions.None); + fs.CreateFile("/file", data.Length, CreateFileOptions.None); - fs.OpenFile(out IFile file, "/file".ToU8Span(), OpenMode.Write); + fs.OpenFile(out IFile file, "/file", OpenMode.Write); file.Write(0, data, WriteOption.None); file.Dispose(); - fs.RenameFile("/file".ToU8Span(), "/renamed".ToU8Span()); + fs.RenameFile("/file", "/renamed"); byte[] readData = new byte[data.Length]; - fs.OpenFile(out file, "/renamed".ToU8Span(), OpenMode.Read); + fs.OpenFile(out file, "/renamed", OpenMode.Read); Result rc = file.Read(out long bytesRead, 0, readData, ReadOption.None); file.Dispose(); diff --git a/tests/LibHac.Tests/Fs/LayeredFileSystemTests.cs b/tests/LibHac.Tests/Fs/LayeredFileSystemTests.cs index 7544595b..b693c0e8 100644 --- a/tests/LibHac.Tests/Fs/LayeredFileSystemTests.cs +++ b/tests/LibHac.Tests/Fs/LayeredFileSystemTests.cs @@ -17,30 +17,30 @@ namespace LibHac.Tests.Fs var layeredFs = new LayeredFileSystem(lowerLayerFs, upperLayerFs); - lowerLayerFs.CreateDirectory("/dir".ToU8Span()).ThrowIfFailure(); - upperLayerFs.CreateDirectory("/dir".ToU8Span()).ThrowIfFailure(); - lowerLayerFs.CreateDirectory("/dir2".ToU8Span()).ThrowIfFailure(); - upperLayerFs.CreateDirectory("/dir2".ToU8Span()).ThrowIfFailure(); - lowerLayerFs.CreateDirectory("/dir3".ToU8Span()).ThrowIfFailure(); - upperLayerFs.CreateDirectory("/dir3".ToU8Span()).ThrowIfFailure(); + lowerLayerFs.CreateDirectory("/dir").ThrowIfFailure(); + upperLayerFs.CreateDirectory("/dir").ThrowIfFailure(); + lowerLayerFs.CreateDirectory("/dir2").ThrowIfFailure(); + upperLayerFs.CreateDirectory("/dir2").ThrowIfFailure(); + lowerLayerFs.CreateDirectory("/dir3").ThrowIfFailure(); + upperLayerFs.CreateDirectory("/dir3").ThrowIfFailure(); - lowerLayerFs.CreateDirectory("/lowerDir".ToU8Span()).ThrowIfFailure(); - upperLayerFs.CreateDirectory("/upperDir".ToU8Span()).ThrowIfFailure(); + lowerLayerFs.CreateDirectory("/lowerDir").ThrowIfFailure(); + upperLayerFs.CreateDirectory("/upperDir").ThrowIfFailure(); - lowerLayerFs.CreateFile("/dir/replacedFile".ToU8Span(), 1, CreateFileOptions.None).ThrowIfFailure(); - upperLayerFs.CreateFile("/dir/replacedFile".ToU8Span(), 2, CreateFileOptions.None).ThrowIfFailure(); + lowerLayerFs.CreateFile("/dir/replacedFile", 1, CreateFileOptions.None).ThrowIfFailure(); + upperLayerFs.CreateFile("/dir/replacedFile", 2, CreateFileOptions.None).ThrowIfFailure(); - lowerLayerFs.CreateFile("/dir2/lowerFile".ToU8Span(), 0, CreateFileOptions.None).ThrowIfFailure(); - upperLayerFs.CreateFile("/dir2/upperFile".ToU8Span(), 0, CreateFileOptions.None).ThrowIfFailure(); + lowerLayerFs.CreateFile("/dir2/lowerFile", 0, CreateFileOptions.None).ThrowIfFailure(); + upperLayerFs.CreateFile("/dir2/upperFile", 0, CreateFileOptions.None).ThrowIfFailure(); - lowerLayerFs.CreateFile("/dir3/lowerFile".ToU8Span(), 0, CreateFileOptions.None).ThrowIfFailure(); - upperLayerFs.CreateFile("/dir3/upperFile".ToU8Span(), 2, CreateFileOptions.None).ThrowIfFailure(); - lowerLayerFs.CreateFile("/dir3/replacedFile".ToU8Span(), 1, CreateFileOptions.None).ThrowIfFailure(); - upperLayerFs.CreateFile("/dir3/replacedFile".ToU8Span(), 2, CreateFileOptions.None).ThrowIfFailure(); + lowerLayerFs.CreateFile("/dir3/lowerFile", 0, CreateFileOptions.None).ThrowIfFailure(); + upperLayerFs.CreateFile("/dir3/upperFile", 2, CreateFileOptions.None).ThrowIfFailure(); + lowerLayerFs.CreateFile("/dir3/replacedFile", 1, CreateFileOptions.None).ThrowIfFailure(); + upperLayerFs.CreateFile("/dir3/replacedFile", 2, CreateFileOptions.None).ThrowIfFailure(); - lowerLayerFs.CreateFile("/replacedWithDir".ToU8Span(), 0, CreateFileOptions.None).ThrowIfFailure(); - upperLayerFs.CreateDirectory("/replacedWithDir".ToU8Span()).ThrowIfFailure(); - upperLayerFs.CreateFile("/replacedWithDir/subFile".ToU8Span(), 0, CreateFileOptions.None).ThrowIfFailure(); + lowerLayerFs.CreateFile("/replacedWithDir", 0, CreateFileOptions.None).ThrowIfFailure(); + upperLayerFs.CreateDirectory("/replacedWithDir").ThrowIfFailure(); + upperLayerFs.CreateFile("/replacedWithDir/subFile", 0, CreateFileOptions.None).ThrowIfFailure(); return layeredFs; } @@ -58,7 +58,7 @@ namespace LibHac.Tests.Fs { IFileSystem fs = CreateFileSystem(); - Assert.Result(ResultFs.PathNotFound, fs.OpenFile(out _, "/fakefile".ToU8Span(), OpenMode.All)); + Assert.Result(ResultFs.PathNotFound, fs.OpenFile(out _, "/fakefile", OpenMode.All)); } [Fact] @@ -66,7 +66,7 @@ namespace LibHac.Tests.Fs { IFileSystem fs = CreateFileSystem(); - Assert.Success(fs.OpenFile(out IFile file, "/dir/replacedFile".ToU8Span(), OpenMode.All)); + Assert.Success(fs.OpenFile(out IFile file, "/dir/replacedFile", OpenMode.All)); Assert.Success(file.GetSize(out long fileSize)); Assert.Equal(2, fileSize); @@ -77,8 +77,8 @@ namespace LibHac.Tests.Fs { IFileSystem fs = CreateFileSystem(); - Assert.Success(fs.OpenFile(out _, "/dir2/lowerFile".ToU8Span(), OpenMode.All)); - Assert.Success(fs.OpenFile(out _, "/dir2/upperFile".ToU8Span(), OpenMode.All)); + Assert.Success(fs.OpenFile(out _, "/dir2/lowerFile", OpenMode.All)); + Assert.Success(fs.OpenFile(out _, "/dir2/upperFile", OpenMode.All)); } [Fact] @@ -86,7 +86,7 @@ namespace LibHac.Tests.Fs { IFileSystem fs = CreateFileSystem(); - Assert.Result(ResultFs.PathNotFound, fs.OpenDirectory(out _, "/fakedir".ToU8Span(), OpenDirectoryMode.All)); + Assert.Result(ResultFs.PathNotFound, fs.OpenDirectory(out _, "/fakedir", OpenDirectoryMode.All)); } [Fact] @@ -94,7 +94,7 @@ namespace LibHac.Tests.Fs { IFileSystem fs = CreateFileSystem(); - Assert.Success(fs.OpenDirectory(out IDirectory dir, "/lowerDir".ToU8Span(), OpenDirectoryMode.All)); + Assert.Success(fs.OpenDirectory(out IDirectory dir, "/lowerDir", OpenDirectoryMode.All)); Assert.Equal(typeof(InMemoryFileSystem), dir.GetType().DeclaringType); } @@ -103,7 +103,7 @@ namespace LibHac.Tests.Fs { IFileSystem fs = CreateFileSystem(); - Assert.Success(fs.OpenDirectory(out IDirectory dir, "/dir".ToU8Span(), OpenDirectoryMode.All)); + Assert.Success(fs.OpenDirectory(out IDirectory dir, "/dir", OpenDirectoryMode.All)); Assert.Equal(typeof(LayeredFileSystem), dir.GetType().DeclaringType); } @@ -112,8 +112,8 @@ namespace LibHac.Tests.Fs { IFileSystem fs = CreateFileSystem(); - Assert.Success(fs.GetEntryType(out _, "/dir2/lowerFile".ToU8Span())); - Assert.Success(fs.GetEntryType(out _, "/dir2/upperFile".ToU8Span())); + Assert.Success(fs.GetEntryType(out _, "/dir2/lowerFile")); + Assert.Success(fs.GetEntryType(out _, "/dir2/upperFile")); } [Fact] @@ -122,7 +122,7 @@ namespace LibHac.Tests.Fs IFileSystem fs = CreateFileSystem(); Span entries = stackalloc DirectoryEntry[4]; - Assert.Success(fs.OpenDirectory(out IDirectory directory, "/dir3".ToU8Span(), OpenDirectoryMode.All)); + Assert.Success(fs.OpenDirectory(out IDirectory directory, "/dir3", OpenDirectoryMode.All)); Assert.Success(directory.Read(out long entriesRead, entries)); Assert.Equal(3, entriesRead); @@ -134,7 +134,7 @@ namespace LibHac.Tests.Fs IFileSystem fs = CreateFileSystem(); var entry = new DirectoryEntry(); - Assert.Success(fs.OpenDirectory(out IDirectory directory, "/dir".ToU8Span(), OpenDirectoryMode.All)); + Assert.Success(fs.OpenDirectory(out IDirectory directory, "/dir", OpenDirectoryMode.All)); Assert.Success(directory.Read(out _, SpanHelpers.AsSpan(ref entry))); Assert.Equal("replacedFile", StringUtils.Utf8ZToString(entry.Name)); @@ -147,7 +147,7 @@ namespace LibHac.Tests.Fs IFileSystem fs = CreateEmptyFileSystem(); var entry = new DirectoryEntry(); - Assert.Success(fs.OpenDirectory(out IDirectory directory, "/".ToU8Span(), OpenDirectoryMode.All)); + Assert.Success(fs.OpenDirectory(out IDirectory directory, "/", OpenDirectoryMode.All)); Assert.Success(directory.Read(out long entriesRead, SpanHelpers.AsSpan(ref entry))); Assert.Equal(0, entriesRead); @@ -158,7 +158,7 @@ namespace LibHac.Tests.Fs { IFileSystem fs = CreateFileSystem(); - Assert.Success(fs.OpenDirectory(out IDirectory directory, "/dir3".ToU8Span(), OpenDirectoryMode.All)); + Assert.Success(fs.OpenDirectory(out IDirectory directory, "/dir3", OpenDirectoryMode.All)); Assert.Success(directory.GetEntryCount(out long entryCount)); Assert.Equal(3, entryCount); @@ -170,7 +170,7 @@ namespace LibHac.Tests.Fs IFileSystem fs = CreateFileSystem(); var entry = new DirectoryEntry(); - Assert.Success(fs.OpenDirectory(out IDirectory directory, "/dir3".ToU8Span(), OpenDirectoryMode.All)); + Assert.Success(fs.OpenDirectory(out IDirectory directory, "/dir3", OpenDirectoryMode.All)); // Read all entries long entriesRead; @@ -188,7 +188,7 @@ namespace LibHac.Tests.Fs { IFileSystem fs = CreateEmptyFileSystem(); - Assert.Success(fs.OpenDirectory(out IDirectory directory, "/".ToU8Span(), OpenDirectoryMode.All)); + Assert.Success(fs.OpenDirectory(out IDirectory directory, "/", OpenDirectoryMode.All)); Assert.Success(directory.GetEntryCount(out long entryCount)); Assert.Equal(0, entryCount); diff --git a/tests/LibHac.Tests/Fs/PathNormalizerTests.cs b/tests/LibHac.Tests/Fs/PathNormalizerTests.cs index 4b1c8d2e..595f6781 100644 --- a/tests/LibHac.Tests/Fs/PathNormalizerTests.cs +++ b/tests/LibHac.Tests/Fs/PathNormalizerTests.cs @@ -52,7 +52,7 @@ namespace LibHac.Tests.Fs { byte[] buffer = new byte[0x301]; - Result result = PathNormalizer12.Normalize(buffer, out int normalizedLength, path.ToU8Span(), isWindowsPath, + Result result = PathNormalizer.Normalize(buffer, out int normalizedLength, path.ToU8Span(), isWindowsPath, isDriveRelativePath); Assert.Equal(expectedResult, result); @@ -86,7 +86,7 @@ namespace LibHac.Tests.Fs { byte[] buffer = new byte[bufferLength]; - Result result = PathNormalizer12.Normalize(buffer, out int normalizedLength, path.ToU8Span(), false, false); + Result result = PathNormalizer.Normalize(buffer, out int normalizedLength, path.ToU8Span(), false, false); Assert.Equal(expectedResult, result); Assert.Equal(expectedNormalized, StringUtils.Utf8ZToString(buffer)); @@ -133,7 +133,7 @@ namespace LibHac.Tests.Fs [Theory, MemberData(nameof(TestData_IsNormalized))] public static void IsNormalized(string path, bool expectedIsNormalized, long expectedLength, Result expectedResult) { - Result result = PathNormalizer12.IsNormalized(out bool isNormalized, out int length, path.ToU8Span()); + Result result = PathNormalizer.IsNormalized(out bool isNormalized, out int length, path.ToU8Span()); Assert.Equal(expectedResult, result); Assert.Equal(expectedLength, length); diff --git a/tests/LibHac.Tests/Fs/PathToolTestGenerator.cpp b/tests/LibHac.Tests/Fs/PathToolTestGenerator.cpp deleted file mode 100644 index d873f840..00000000 --- a/tests/LibHac.Tests/Fs/PathToolTestGenerator.cpp +++ /dev/null @@ -1,327 +0,0 @@ -// Uses GLoat to run code in nnsdk https://github.com/h1k421/GLoat -#include - -static char Buf[0x80000]; -static int BufPos = 0; - -static char ResultNameBuf[0x100]; - -namespace nn::fs::detail { - bool IsEnabledAccessLog(); -} - -namespace nn::fs::PathTool { - // SDK < 7 - nn::Result Normalize(char* buffer, uint64_t* outNormalizedPathLength, const char* path, uint64_t bufferLength, bool preserveUnc); - nn::Result IsNormalized(bool* outIsNormalized, const char* path); - - // SDK >= 7 < 11 - nn::Result Normalize(char* buffer, uint64_t* outNormalizedPathLength, const char* path, uint64_t bufferLength, bool preserveUnc, bool hasMountName); - nn::Result IsNormalized(bool* outIsNormalized, const char* path, bool preserveUnc, bool hasMountName); - - bool IsSubpath(const char* path1, const char* path2); -} - -namespace nn::fs { - // SDK >= 11 - bool IsSubPath(const char* path1, const char* path2); -} - -namespace nn::fs::PathNormalizer { - // SDK >= 11 - nn::Result Normalize(char* buffer, uint64_t* outNormalizedPathLength, const char* path, uint64_t bufferLength, bool preserveUnc, bool hasMountName); - nn::Result IsNormalized(bool* outIsNormalized, const char* path, bool preserveUnc, bool hasMountName); -} - -void* allocate(size_t size) -{ - void* ptr = malloc(size); - - char buffer[0x40]; - sprintf(buffer, "Allocating %ld. 0x%p", size, ptr); - svcOutputDebugString(buffer, sizeof(buffer)); - - return ptr; -} - -void deallocate(void* ptr, size_t size) -{ - char buffer[0x40]; - sprintf(buffer, "Deallocating %ld. 0x%p", size, ptr); - svcOutputDebugString(buffer, sizeof(buffer)); - - free(ptr); -} - -void setAllocators(void) { - nn::fs::SetAllocator(allocate, deallocate); -} - -const char* GetResultName(nn::Result result) { - switch (result.GetValue()) { - case 0: return "Result.Success"; - case 0x2EE402: return "ResultFs.InvalidPath.Value"; - case 0x2EE602: return "ResultFs.TooLongPath.Value"; - case 0x2EE802: return "ResultFs.InvalidCharacter.Value"; - case 0x2EEA02: return "ResultFs.InvalidPathFormat.Value"; - case 0x2EEC02: return "ResultFs.DirectoryUnobtainable.Value"; - default: - sprintf(ResultNameBuf, "0x%x", result.GetValue()); - return ResultNameBuf; - } -} - -void CreateNormalizeTestItem(char const* path, bool preserveUnc, bool hasMountName) { - char normalized[0x200]; - uint64_t normalizedLen = 0; - memset(normalized, 0, 0x200); - - //svcOutputDebugString(path, strnlen(path, 0x200)); - - nn::Result result = nn::fs::PathNormalizer::Normalize(normalized, &normalizedLen, path, 0x200, preserveUnc, hasMountName); - - const char* preserveUncStr = preserveUnc ? "true" : "false"; - const char* hasMountNameStr = hasMountName ? "true" : "false"; - BufPos += sprintf(&Buf[BufPos], "new object[] {@\"%s\", %s, %s, @\"%s\", %ld, %s},\n", - path, preserveUncStr, hasMountNameStr, normalized, normalizedLen, GetResultName(result)); -} - -void CreateIsNormalizedTestItem(char const* path, bool preserveUnc, bool hasMountName) { - bool isNormalized = false; - - nn::Result result = nn::fs::PathNormalizer::IsNormalized(&isNormalized, path, preserveUnc, hasMountName); - - const char* preserveUncStr = preserveUnc ? "true" : "false"; - const char* hasMountNameStr = hasMountName ? "true" : "false"; - const char* isNormalizedStr = isNormalized ? "true" : "false"; - BufPos += sprintf(&Buf[BufPos], "new object[] {@\"%s\", %s, %s, %s, %s},\n", - path, preserveUncStr, hasMountNameStr, isNormalizedStr, GetResultName(result)); -} - -void CreateIsSubpathTestItem(const char* path1, const char* path2) { - bool result = nn::fs::IsSubPath(path1, path2); - - const char* resultStr = result ? "true" : "false"; - BufPos += sprintf(&Buf[BufPos], "new object[] {@\"%s\", @\"%s\", %s},\n", - path1, path2, resultStr); -} - -void CreateTestItemWithParentDirs(char const* path, bool preserveUnc, bool hasMountName, void (*func)(char const*, bool, bool), int parentCount) { - char parentPath[0x200]; - memset(parentPath, 0, sizeof(parentPath)); - - strcpy(parentPath, path); - func(parentPath, preserveUnc, hasMountName); - - for (int i = 0; i < parentCount; i++) { - strcat(parentPath, "/.."); - func(parentPath, preserveUnc, hasMountName); - } -} - -void CreateTestItemWithParentDirs(char const* path, bool preserveUnc, bool hasMountName, void (*func)(char const*, bool, bool)) { - CreateTestItemWithParentDirs(path, preserveUnc, hasMountName, func, 3); -} - -void CreateNormalizationTestData(void (*func)(char const*, bool, bool)) { - Buf[0] = '\n'; - BufPos = 1; - - bool preserveUnc = false; - - func("", preserveUnc, false); - func("/", preserveUnc, false); - func("/.", preserveUnc, false); - func("/a/b/c", preserveUnc, false); - func("/a/b/../c", preserveUnc, false); - func("/a/b/c/..", preserveUnc, false); - func("/a/b/c/.", preserveUnc, false); - func("/a/../../..", preserveUnc, false); - func("/a/../../../a/b/c", preserveUnc, false); - func("//a/b//.//c", preserveUnc, false); - func("/../a/b/c/.", preserveUnc, false); - func("/./aaa/bbb/ccc/.", preserveUnc, false); - func("/a/b/c/", preserveUnc, false); - func("a/b/c/", preserveUnc, false); - func("/aa/./bb/../cc/", preserveUnc, false); - func("/./b/../c/", preserveUnc, false); - func("/a/../../../", preserveUnc, false); - func("//a/b//.//c/", preserveUnc, false); - func("/tmp/../", preserveUnc, false); - func("a", preserveUnc, false); - func("a/../../../a/b/c", preserveUnc, false); - func("./b/../c/", preserveUnc, false); - func(".", preserveUnc, false); - func("..", preserveUnc, false); - func("../a/b/c/.", preserveUnc, false); - func("./a/b/c/.", preserveUnc, false); - func("abc", preserveUnc, false); - func("mount:/a/b/../c", preserveUnc, true); - func("a:/a/b/c", preserveUnc, true); - func("mount:/a/b/../c", preserveUnc, true); - func("mount:/a/b/../c", preserveUnc, false); - func("mount:\\a/b/../c", preserveUnc, true); - func("mount:\\a/b\\../c", preserveUnc, true); - func("mount:\\a/b/c", preserveUnc, true); - func("mount:/a\\../b\\..c", preserveUnc, true); - func("mount:/a\\../b/..cd", preserveUnc, true); - func("mount:/a\\..d/b/c\\..", preserveUnc, true); - func("mount:", preserveUnc, true); - func("abc:/a/../../../a/b/c", preserveUnc, true); - func("abc:/./b/../c/", preserveUnc, true); - func("abc:/.", preserveUnc, true); - func("abc:/..", preserveUnc, true); - func("abc:/", preserveUnc, true); - func("abc://a/b//.//c", preserveUnc, true); - func("abc:/././/././a/b//.//c", preserveUnc, true); - func("mount:/d./aa", preserveUnc, true); - func("mount:/d/..", preserveUnc, true); - func("/path/aaa/bbb\\..\\h/ddd", preserveUnc, false); - func("/path/aaa/bbb/../h/ddd", preserveUnc, false); - func("/path/aaa/bbb\\.\\h/ddd", preserveUnc, false); - func("/path/aaa/bbb\\./h/ddd", preserveUnc, false); - func("/path/aaa/bbb/./h/ddd", preserveUnc, false); - func("mount:abcd", preserveUnc, true); - func("mount:", preserveUnc, true); - func("mount:/", preserveUnc, true); - func("mount:\\..", preserveUnc, true); - func("mount:/a/b\\..", preserveUnc, true); - func("mount:/dir", preserveUnc, true); - func("mount:/dir/", preserveUnc, true); - func("mount:\\", preserveUnc, true); - func("mo.unt:\\", preserveUnc, true); - func("mount.:\\", preserveUnc, true); - func("mount:./aa/bb", preserveUnc, true); - //func("mount:../aa/bb", preserveUnc, true); // crashes nnsdk - func("mount:.../aa/bb", preserveUnc, true); - func("mount:...aa/bb", preserveUnc, true); - func("...aa/bb", preserveUnc, false); - func("mount01234567890/aa/bb", preserveUnc, true); - func("mount01234567890:/aa/bb", preserveUnc, true); - func("mount0123456789:/aa/bb", preserveUnc, true); - func("mount012345678:/aa/bb", preserveUnc, true); - func("mount:aa/..\\bb", preserveUnc, true); - func("mount:..\\bb", preserveUnc, true); - func("mount:/..\\bb", preserveUnc, true); - func("mount:/.\\bb", preserveUnc, true); - func("mount:\\..\\bb", preserveUnc, true); - func("mount:\\.\\bb", preserveUnc, true); - func("mount:/a\\..\\bb", preserveUnc, true); - func("mount:/a\\.\\bb", preserveUnc, true); - - for (int i = 0; i < 2; i++) { - preserveUnc = (bool)i; - - CreateTestItemWithParentDirs("//$abc/bb", preserveUnc, false, func, 0); - CreateTestItemWithParentDirs("//:abc/bb", preserveUnc, false, func, 0); - CreateTestItemWithParentDirs("\\\\\\asd", preserveUnc, false, func, 0); - CreateTestItemWithParentDirs("\\\\/asd", preserveUnc, false, func, 0); - CreateTestItemWithParentDirs("\\\\//asd", preserveUnc, false, func, 0); - CreateTestItemWithParentDirs("//", preserveUnc, false, func, 1); - CreateTestItemWithParentDirs("\\\\a/b/cc/../d", preserveUnc, false, func); - CreateTestItemWithParentDirs("c:/aa/bb", preserveUnc, true, func); - CreateTestItemWithParentDirs("mount:\\c:/aa", preserveUnc, true, func); - CreateTestItemWithParentDirs("mount:/c:\\aa/bb", preserveUnc, true, func); - CreateTestItemWithParentDirs("mount:////c:\\aa/bb", preserveUnc, true, func); - CreateTestItemWithParentDirs("mount:/\\aa/bb", preserveUnc, true, func); - CreateTestItemWithParentDirs("mount:/c:/aa/bb", preserveUnc, false, func, 0); - CreateTestItemWithParentDirs("mount:c:/aa/bb", preserveUnc, false, func, 0); - CreateTestItemWithParentDirs("mount:c:/aa/bb", preserveUnc, true, func, 0); - CreateTestItemWithParentDirs("mount:/\\aa/../b", preserveUnc, true, func, 2); - CreateTestItemWithParentDirs("mount://aa/bb", preserveUnc, true, func, 1); - CreateTestItemWithParentDirs("//aa/bb", preserveUnc, true, func, 1); - CreateTestItemWithParentDirs("//aa/bb", preserveUnc, false, func, 1); - CreateTestItemWithParentDirs("/aa/bb", preserveUnc, false, func); - CreateTestItemWithParentDirs("c:/aa", preserveUnc, false, func, 2); - CreateTestItemWithParentDirs("c:abcde/aa/bb", preserveUnc, false, func); - CreateTestItemWithParentDirs("c:abcde", preserveUnc, false, func, 1); - CreateTestItemWithParentDirs("c:abcde/", preserveUnc, false, func, 0); - CreateTestItemWithParentDirs("///aa", preserveUnc, false, func, 0); - CreateTestItemWithParentDirs("//aa//bb", preserveUnc, false, func, 1); - CreateTestItemWithParentDirs("//./bb", preserveUnc, false, func, 0); - CreateTestItemWithParentDirs("//../bb", preserveUnc, false, func, 0); - CreateTestItemWithParentDirs("//.../bb", preserveUnc, false, func, 0); - CreateTestItemWithParentDirs("//aa$abc/bb", preserveUnc, false, func, 0); - CreateTestItemWithParentDirs("//aa$/bb", preserveUnc, false, func, 0); - CreateTestItemWithParentDirs("//aa:/bb", preserveUnc, false, func, 0); - CreateTestItemWithParentDirs("//aa/bb$b/cc$", preserveUnc, false, func, 0); - CreateTestItemWithParentDirs("//aa/bb/cc$c", preserveUnc, false, func, 1); - CreateTestItemWithParentDirs("//aa/bb/cc$c/dd", preserveUnc, false, func, 0); - CreateTestItemWithParentDirs("//aa/bb", preserveUnc, false, func, 0); - CreateTestItemWithParentDirs("//aa/bb/cc//dd", preserveUnc, false, func); - CreateTestItemWithParentDirs("//aa/bb/cc\\/dd", preserveUnc, false, func, 0); - CreateTestItemWithParentDirs("//aa/bb/cc//dd", preserveUnc, false, func, 0); - CreateTestItemWithParentDirs("//aa/bb/cc/dd", preserveUnc, false, func); - CreateTestItemWithParentDirs("//aa/bb/cc/\\dd", preserveUnc, false, func); - CreateTestItemWithParentDirs("//aa/../", preserveUnc, false, func, 0); - CreateTestItemWithParentDirs("//aa//", preserveUnc, false, func, 0); - CreateTestItemWithParentDirs("//aa/bb..", preserveUnc, false, func, 1); - CreateTestItemWithParentDirs("//aa/bb../", preserveUnc, false, func, 1); - CreateTestItemWithParentDirs("/\\\\aa/bb/cc/..", preserveUnc, true, func); - - CreateTestItemWithParentDirs("c:aa\\bb/cc", preserveUnc, false, func); - CreateTestItemWithParentDirs("c:\\//\\aa\\bb", preserveUnc, false, func, 1); - - CreateTestItemWithParentDirs("mount://////a/bb/c", preserveUnc, true, func, 2); - - CreateTestItemWithParentDirs("//", preserveUnc, false, func, 1); - CreateTestItemWithParentDirs("//a", preserveUnc, false, func, 1); - CreateTestItemWithParentDirs("//a", preserveUnc, false, func, 1); - CreateTestItemWithParentDirs("//a/", preserveUnc, false, func, 1); - CreateTestItemWithParentDirs("//a/b", preserveUnc, false, func, 1); - CreateTestItemWithParentDirs("//a/b/", preserveUnc, false, func, 1); - CreateTestItemWithParentDirs("//a/b/c", preserveUnc, false, func, 2); - CreateTestItemWithParentDirs("//a/b/c/", preserveUnc, false, func, 2); - - CreateTestItemWithParentDirs("\\\\", preserveUnc, false, func, 1); - CreateTestItemWithParentDirs("\\\\a", preserveUnc, false, func, 1); - CreateTestItemWithParentDirs("\\\\a/", preserveUnc, false, func, 1); - CreateTestItemWithParentDirs("\\\\a/b", preserveUnc, false, func, 1); - CreateTestItemWithParentDirs("\\\\a/b/", preserveUnc, false, func, 1); - CreateTestItemWithParentDirs("\\\\a/b/c", preserveUnc, false, func, 2); - CreateTestItemWithParentDirs("\\\\a/b/c/", preserveUnc, false, func, 2); - - CreateTestItemWithParentDirs("\\\\", preserveUnc, false, func, 1); - CreateTestItemWithParentDirs("\\\\a", preserveUnc, false, func, 1); - CreateTestItemWithParentDirs("\\\\a\\", preserveUnc, false, func, 1); - CreateTestItemWithParentDirs("\\\\a\\b", preserveUnc, false, func, 1); - CreateTestItemWithParentDirs("\\\\a\\b\\", preserveUnc, false, func, 1); // "\\a\b\/../.." crashes nnsdk - CreateTestItemWithParentDirs("\\\\a\\b\\c", preserveUnc, false, func, 2); - CreateTestItemWithParentDirs("\\\\a\\b\\c\\", preserveUnc, false, func, 2); - } - - svcOutputDebugString(Buf, BufPos); -} - -void CreateSubpathTestData() { - Buf[0] = '\n'; - BufPos = 1; - - CreateIsSubpathTestItem("//a/b", "/a"); - CreateIsSubpathTestItem("/a", "//a/b"); - CreateIsSubpathTestItem("//a/b", "\\\\a"); - CreateIsSubpathTestItem("//a/b", "//a"); - CreateIsSubpathTestItem("/", "/a"); - CreateIsSubpathTestItem("/a", "/"); - CreateIsSubpathTestItem("/", "/"); - CreateIsSubpathTestItem("", ""); - CreateIsSubpathTestItem("/", ""); - CreateIsSubpathTestItem("/", "mount:/a"); - CreateIsSubpathTestItem("mount:/", "mount:/"); - CreateIsSubpathTestItem("mount:/a/b", "mount:/a/b"); - CreateIsSubpathTestItem("mount:/a/b", "mount:/a/b/c"); - CreateIsSubpathTestItem("/a/b", "/a/b/c"); - CreateIsSubpathTestItem("/a/b/c", "/a/b"); - CreateIsSubpathTestItem("/a/b", "/a/b"); - CreateIsSubpathTestItem("/a/b", "/a/b\\c"); - - svcOutputDebugString(Buf, BufPos); -} - -extern "C" void nnMain(void) { - //setAllocators(); - nn::fs::detail::IsEnabledAccessLog(); // Adds the sdk version to the output when not calling setAllocators - CreateNormalizationTestData(CreateNormalizeTestItem); - CreateNormalizationTestData(CreateIsNormalizedTestItem); - CreateSubpathTestData(); -} \ No newline at end of file diff --git a/tests/LibHac.Tests/Fs/PathToolTests.cs b/tests/LibHac.Tests/Fs/PathToolTests.cs deleted file mode 100644 index e2e6dd2c..00000000 --- a/tests/LibHac.Tests/Fs/PathToolTests.cs +++ /dev/null @@ -1,850 +0,0 @@ -using LibHac.Common; -using LibHac.Fs; -using LibHac.Util; -using Xunit; - -namespace LibHac.Tests.Fs -{ - public class PathToolTests - { - [Theory] - [MemberData(nameof(NormalizeTestItems))] - public static void Normalize(string path, bool preserveUnc, bool hasMountName, string expectedNormalized, - long expectedLength, Result expectedResult) - { - byte[] buffer = new byte[0x301]; - - Result result = PathNormalizer.Normalize(buffer, out long normalizedLength, path.ToU8Span(), preserveUnc, - hasMountName); - - Assert.Equal(expectedResult, result); - Assert.Equal(expectedNormalized, StringUtils.Utf8ZToString(buffer)); - Assert.Equal(expectedLength, normalizedLength); - } - - [Theory] - [MemberData(nameof(IsNormalizedTestItems))] - public static void IsNormalized(string path, bool preserveUnc, bool hasMountName, bool expectedIsNormalized, - Result expectedResult) - { - Result result = PathNormalizer.IsNormalized(out bool isNormalized, path.ToU8Span(), preserveUnc, hasMountName); - - Assert.Equal(expectedResult, result); - - if (result.IsSuccess()) - { - Assert.Equal(expectedIsNormalized, isNormalized); - } - } - - [Theory] - [MemberData(nameof(SubpathTestItems))] - public static void IsSubPath(string path1, string path2, bool expectedResult) - { - bool result = PathUtility.IsSubPath(path1.ToU8Span(), path2.ToU8Span()); - - Assert.Equal(expectedResult, result); - } - - public static object[][] NormalizeTestItems = - { - new object[] {@"", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"/", false, false, @"/", 1, Result.Success}, - new object[] {@"/.", false, false, @"/", 1, Result.Success}, - new object[] {@"/a/b/c", false, false, @"/a/b/c", 6, Result.Success}, - new object[] {@"/a/b/../c", false, false, @"/a/c", 4, Result.Success}, - new object[] {@"/a/b/c/..", false, false, @"/a/b", 4, Result.Success}, - new object[] {@"/a/b/c/.", false, false, @"/a/b/c", 6, Result.Success}, - new object[] {@"/a/../../..", false, false, @"/a/", 0, ResultFs.DirectoryUnobtainable.Value}, - new object[] {@"/a/../../../a/b/c", false, false, @"/a/", 0, ResultFs.DirectoryUnobtainable.Value}, - new object[] {@"//a/b//.//c", false, false, @"/a/b/c", 6, Result.Success}, - new object[] {@"/../a/b/c/.", false, false, @"/", 0, ResultFs.DirectoryUnobtainable.Value}, - new object[] {@"/./aaa/bbb/ccc/.", false, false, @"/aaa/bbb/ccc", 12, Result.Success}, - new object[] {@"/a/b/c/", false, false, @"/a/b/c", 6, Result.Success}, - new object[] {@"a/b/c/", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"/aa/./bb/../cc/", false, false, @"/aa/cc", 6, Result.Success}, - new object[] {@"/./b/../c/", false, false, @"/c", 2, Result.Success}, - new object[] {@"/a/../../../", false, false, @"/a/", 0, ResultFs.DirectoryUnobtainable.Value}, - new object[] {@"//a/b//.//c/", false, false, @"/a/b/c", 6, Result.Success}, - new object[] {@"/tmp/../", false, false, @"/", 1, Result.Success}, - new object[] {@"a", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"a/../../../a/b/c", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"./b/../c/", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@".", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"../a/b/c/.", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"./a/b/c/.", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"abc", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"mount:/a/b/../c", false, true, @"mount:/a/c", 10, Result.Success}, - new object[] {@"mount:/a/b/../c", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"a:/a/b/c", false, true, @"a:/a/b/c", 8, Result.Success}, - new object[] {@"mount:/a/b/../c", false, true, @"mount:/a/c", 10, Result.Success}, - new object[] {@"mount:\a/b/../c", false, true, @"mount:\a/c", 10, Result.Success}, - new object[] {@"mount:\a/b\../c", false, true, @"mount:/a/c", 10, Result.Success}, - new object[] {@"mount:\a/b/c", false, true, @"mount:\a/b/c", 12, Result.Success}, - new object[] {@"mount:/a\../b\..c", false, true, @"mount:/b\..c", 12, Result.Success}, - new object[] {@"mount:/a\../b/..cd", false, true, @"mount:/b/..cd", 13, Result.Success}, - new object[] {@"mount:/a\..d/b/c\..", false, true, @"mount:/a\..d/b", 14, Result.Success}, - new object[] {@"mount:", false, true, @"mount:/", 7, Result.Success}, - new object[] {@"abc:/a/../../../a/b/c", false, true, @"abc:/a/", 0, ResultFs.DirectoryUnobtainable.Value}, - new object[] {@"abc:/./b/../c/", false, true, @"abc:/c", 6, Result.Success}, - new object[] {@"abc:/.", false, true, @"abc:/", 5, Result.Success}, - new object[] {@"abc:/..", false, true, @"abc:/", 0, ResultFs.DirectoryUnobtainable.Value}, - new object[] {@"abc:/", false, true, @"abc:/", 5, Result.Success}, - new object[] {@"abc://a/b//.//c", false, true, @"abc:/a/b/c", 10, Result.Success}, - new object[] {@"abc:/././/././a/b//.//c", false, true, @"abc:/a/b/c", 10, Result.Success}, - new object[] {@"mount:/d./aa", false, true, @"mount:/d./aa", 12, Result.Success}, - new object[] {@"mount:/d/..", false, true, @"mount:/", 7, Result.Success}, - new object[] {@"/path/aaa/bbb\..\h/ddd", false, false, @"/path/aaa/h/ddd", 15, Result.Success}, - new object[] {@"/path/aaa/bbb/../h/ddd", false, false, @"/path/aaa/h/ddd", 15, Result.Success}, - new object[] {@"/path/aaa/bbb\.\h/ddd", false, false, @"/path/aaa/bbb\.\h/ddd", 21, Result.Success}, - new object[] {@"/path/aaa/bbb\./h/ddd", false, false, @"/path/aaa/bbb\./h/ddd", 21, Result.Success}, - new object[] {@"/path/aaa/bbb/./h/ddd", false, false, @"/path/aaa/bbb/h/ddd", 19, Result.Success}, - new object[] {@"mount:abcd", false, true, @"mount:abcd", 10, Result.Success}, - new object[] {@"mount:", false, true, @"mount:/", 7, Result.Success}, - new object[] {@"mount:/", false, true, @"mount:/", 7, Result.Success}, - new object[] {@"mount:\..", false, true, @"mount:/", 0, ResultFs.DirectoryUnobtainable.Value}, - new object[] {@"mount:/a/b\..", false, true, @"mount:/a", 8, Result.Success}, - new object[] {@"mount:/dir", false, true, @"mount:/dir", 10, Result.Success}, - new object[] {@"mount:/dir/", false, true, @"mount:/dir", 10, Result.Success}, - new object[] {@"mount:\", false, true, @"mount:\", 7, Result.Success}, - new object[] {@"mo.unt:\", false, true, @"", 0, ResultFs.InvalidCharacter.Value}, - new object[] {@"mount.:\", false, true, @"", 0, ResultFs.InvalidCharacter.Value}, - new object[] {@"mount:./aa/bb", false, true, @"mount:aa/bb", 11, Result.Success}, - new object[] {@"mount:.../aa/bb", false, true, @"mount:.../aa/bb", 15, Result.Success}, - new object[] {@"mount:...aa/bb", false, true, @"mount:...aa/bb", 14, Result.Success}, - new object[] {@"...aa/bb", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"mount01234567890/aa/bb", false, true, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"mount01234567890:/aa/bb", false, true, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"mount0123456789:/aa/bb", false, true, @"mount0123456789:/aa/bb", 22, Result.Success}, - new object[] {@"mount012345678:/aa/bb", false, true, @"mount012345678:/aa/bb", 21, Result.Success}, - new object[] {@"mount:aa/..\bb", false, true, @"mount:aa/..\bb", 14, Result.Success}, - new object[] {@"mount:..\bb", false, true, @"mount:..\bb", 11, Result.Success}, - new object[] {@"mount:/..\bb", false, true, @"mount:/", 0, ResultFs.DirectoryUnobtainable.Value}, - new object[] {@"mount:/.\bb", false, true, @"mount:/.\bb", 11, Result.Success}, - new object[] {@"mount:\..\bb", false, true, @"mount:/", 0, ResultFs.DirectoryUnobtainable.Value}, - new object[] {@"mount:\.\bb", false, true, @"mount:\.\bb", 11, Result.Success}, - new object[] {@"mount:/a\..\bb", false, true, @"mount:/bb", 9, Result.Success}, - new object[] {@"mount:/a\.\bb", false, true, @"mount:/a\.\bb", 13, Result.Success}, - new object[] {@"//$abc/bb", false, false, @"/$abc/bb", 8, Result.Success}, - new object[] {@"//:abc/bb", false, false, @"/:abc/bb", 8, Result.Success}, - new object[] {@"\\\asd", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\/asd", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\//asd", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"//", false, false, @"/", 1, Result.Success}, - new object[] {@"///..", false, false, @"/", 0, ResultFs.DirectoryUnobtainable.Value}, - new object[] {@"\\a/b/cc/../d", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a/b/cc/../d/..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a/b/cc/../d/../..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a/b/cc/../d/../../..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"c:/aa/bb", false, true, @"c:/aa/bb", 8, Result.Success}, - new object[] {@"c:/aa/bb/..", false, true, @"c:/aa", 5, Result.Success}, - new object[] {@"c:/aa/bb/../..", false, true, @"c:/", 3, Result.Success}, - new object[] {@"c:/aa/bb/../../..", false, true, @"c:/aa/bb/", 0, ResultFs.DirectoryUnobtainable.Value}, - new object[] {@"mount:\c:/aa", false, true, @"mount:\c:/aa", 12, Result.Success}, - new object[] {@"mount:\c:/aa/..", false, true, @"mount:\c:", 9, Result.Success}, - new object[] {@"mount:\c:/aa/../..", false, true, @"mount:/", 7, Result.Success}, - new object[] {@"mount:\c:/aa/../../..", false, true, @"mount:/c:/aa/", 0, ResultFs.DirectoryUnobtainable.Value}, - new object[] {@"mount:/c:\aa/bb", false, true, @"mount:/c:\aa/bb", 15, Result.Success}, - new object[] {@"mount:/c:\aa/bb/..", false, true, @"mount:/c:\aa", 12, Result.Success}, - new object[] {@"mount:/c:\aa/bb/../..", false, true, @"mount:/", 7, Result.Success}, - new object[] {@"mount:/c:\aa/bb/../../..", false, true, @"mount:/c:\aa/bb/", 0, ResultFs.DirectoryUnobtainable.Value}, - new object[] {@"mount:////c:\aa/bb", false, true, @"mount:/c:\aa/bb", 15, Result.Success}, - new object[] {@"mount:////c:\aa/bb/..", false, true, @"mount:/c:\aa", 12, Result.Success}, - new object[] {@"mount:////c:\aa/bb/../..", false, true, @"mount:/", 7, Result.Success}, - new object[] {@"mount:////c:\aa/bb/../../..", false, true, @"mount:/c:\aa/bb/", 0, ResultFs.DirectoryUnobtainable.Value}, - new object[] {@"mount:/\aa/bb", false, true, @"mount:/\aa/bb", 13, Result.Success}, - new object[] {@"mount:/\aa/bb/..", false, true, @"mount:/\aa", 10, Result.Success}, - new object[] {@"mount:/\aa/bb/../..", false, true, @"mount:/", 7, Result.Success}, - new object[] {@"mount:/\aa/bb/../../..", false, true, @"mount:/\aa/bb/", 0, ResultFs.DirectoryUnobtainable.Value}, - new object[] {@"mount:/c:/aa/bb", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"mount:c:/aa/bb", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"mount:c:/aa/bb", false, true, @"mount:c:/aa/bb", 14, Result.Success}, - new object[] {@"mount:/\aa/../b", false, true, @"mount:/b", 8, Result.Success}, - new object[] {@"mount:/\aa/../b/..", false, true, @"mount:/", 7, Result.Success}, - new object[] {@"mount:/\aa/../b/../..", false, true, @"mount:/b/a/", 0, ResultFs.DirectoryUnobtainable.Value}, - new object[] {@"mount://aa/bb", false, true, @"mount:/aa/bb", 12, Result.Success}, - new object[] {@"mount://aa/bb/..", false, true, @"mount:/aa", 9, Result.Success}, - new object[] {@"//aa/bb", false, true, @"/aa/bb", 6, Result.Success}, - new object[] {@"//aa/bb/..", false, true, @"/aa", 3, Result.Success}, - new object[] {@"//aa/bb", false, false, @"/aa/bb", 6, Result.Success}, - new object[] {@"//aa/bb/..", false, false, @"/aa", 3, Result.Success}, - new object[] {@"/aa/bb", false, false, @"/aa/bb", 6, Result.Success}, - new object[] {@"/aa/bb/..", false, false, @"/aa", 3, Result.Success}, - new object[] {@"/aa/bb/../..", false, false, @"/", 1, Result.Success}, - new object[] {@"/aa/bb/../../..", false, false, @"/aa/bb/", 0, ResultFs.DirectoryUnobtainable.Value}, - new object[] {@"c:/aa", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"c:/aa/..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"c:/aa/../..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"c:abcde/aa/bb", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"c:abcde/aa/bb/..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"c:abcde/aa/bb/../..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"c:abcde/aa/bb/../../..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"c:abcde", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"c:abcde/..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"c:abcde/", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"///aa", false, false, @"/aa", 3, Result.Success}, - new object[] {@"//aa//bb", false, false, @"/aa/bb", 6, Result.Success}, - new object[] {@"//aa//bb/..", false, false, @"/aa", 3, Result.Success}, - new object[] {@"//./bb", false, false, @"/bb", 3, Result.Success}, - new object[] {@"//../bb", false, false, @"/", 0, ResultFs.DirectoryUnobtainable.Value}, - new object[] {@"//.../bb", false, false, @"/.../bb", 7, Result.Success}, - new object[] {@"//aa$abc/bb", false, false, @"/aa$abc/bb", 10, Result.Success}, - new object[] {@"//aa$/bb", false, false, @"/aa$/bb", 7, Result.Success}, - new object[] {@"//aa:/bb", false, false, @"/aa:/bb", 7, Result.Success}, - new object[] {@"//aa/bb$b/cc$", false, false, @"/aa/bb$b/cc$", 12, Result.Success}, - new object[] {@"//aa/bb/cc$c", false, false, @"/aa/bb/cc$c", 11, Result.Success}, - new object[] {@"//aa/bb/cc$c/..", false, false, @"/aa/bb", 6, Result.Success}, - new object[] {@"//aa/bb/cc$c/dd", false, false, @"/aa/bb/cc$c/dd", 14, Result.Success}, - new object[] {@"//aa/bb", false, false, @"/aa/bb", 6, Result.Success}, - new object[] {@"//aa/bb/cc//dd", false, false, @"/aa/bb/cc/dd", 12, Result.Success}, - new object[] {@"//aa/bb/cc//dd/..", false, false, @"/aa/bb/cc", 9, Result.Success}, - new object[] {@"//aa/bb/cc//dd/../..", false, false, @"/aa/bb", 6, Result.Success}, - new object[] {@"//aa/bb/cc//dd/../../..", false, false, @"/aa", 3, Result.Success}, - new object[] {@"//aa/bb/cc\/dd", false, false, @"/aa/bb/cc\/dd", 13, Result.Success}, - new object[] {@"//aa/bb/cc//dd", false, false, @"/aa/bb/cc/dd", 12, Result.Success}, - new object[] {@"//aa/bb/cc/dd", false, false, @"/aa/bb/cc/dd", 12, Result.Success}, - new object[] {@"//aa/bb/cc/dd/..", false, false, @"/aa/bb/cc", 9, Result.Success}, - new object[] {@"//aa/bb/cc/dd/../..", false, false, @"/aa/bb", 6, Result.Success}, - new object[] {@"//aa/bb/cc/dd/../../..", false, false, @"/aa", 3, Result.Success}, - new object[] {@"//aa/bb/cc/\dd", false, false, @"/aa/bb/cc/\dd", 13, Result.Success}, - new object[] {@"//aa/bb/cc/\dd/..", false, false, @"/aa/bb/cc", 9, Result.Success}, - new object[] {@"//aa/bb/cc/\dd/../..", false, false, @"/aa/bb", 6, Result.Success}, - new object[] {@"//aa/bb/cc/\dd/../../..", false, false, @"/aa", 3, Result.Success}, - new object[] {@"//aa/../", false, false, @"/", 1, Result.Success}, - new object[] {@"//aa//", false, false, @"/aa", 3, Result.Success}, - new object[] {@"//aa/bb..", false, false, @"/aa/bb..", 8, Result.Success}, - new object[] {@"//aa/bb../..", false, false, @"/aa", 3, Result.Success}, - new object[] {@"//aa/bb../", false, false, @"/aa/bb..", 8, Result.Success}, - new object[] {@"//aa/bb..//..", false, false, @"/aa", 3, Result.Success}, - new object[] {@"/\\aa/bb/cc/..", false, true, @"/\\aa/bb", 8, Result.Success}, - new object[] {@"/\\aa/bb/cc/../..", false, true, @"/\\aa", 5, Result.Success}, - new object[] {@"/\\aa/bb/cc/../../..", false, true, @"/", 1, Result.Success}, - new object[] {@"/\\aa/bb/cc/../../../..", false, true, @"/\\aa/bb/cc/", 0, ResultFs.DirectoryUnobtainable.Value}, - new object[] {@"c:aa\bb/cc", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"c:aa\bb/cc/..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"c:aa\bb/cc/../..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"c:aa\bb/cc/../../..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"c:\//\aa\bb", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"c:\//\aa\bb/..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"mount://////a/bb/c", false, true, @"mount:/a/bb/c", 13, Result.Success}, - new object[] {@"mount://////a/bb/c/..", false, true, @"mount:/a/bb", 11, Result.Success}, - new object[] {@"mount://////a/bb/c/../..", false, true, @"mount:/a", 8, Result.Success}, - new object[] {@"//", false, false, @"/", 1, Result.Success}, - new object[] {@"///..", false, false, @"/", 0, ResultFs.DirectoryUnobtainable.Value}, - new object[] {@"//a", false, false, @"/a", 2, Result.Success}, - new object[] {@"//a/..", false, false, @"/", 1, Result.Success}, - new object[] {@"//a", false, false, @"/a", 2, Result.Success}, - new object[] {@"//a/..", false, false, @"/", 1, Result.Success}, - new object[] {@"//a/", false, false, @"/a", 2, Result.Success}, - new object[] {@"//a//..", false, false, @"/", 1, Result.Success}, - new object[] {@"//a/b", false, false, @"/a/b", 4, Result.Success}, - new object[] {@"//a/b/..", false, false, @"/a", 2, Result.Success}, - new object[] {@"//a/b/", false, false, @"/a/b", 4, Result.Success}, - new object[] {@"//a/b//..", false, false, @"/a", 2, Result.Success}, - new object[] {@"//a/b/c", false, false, @"/a/b/c", 6, Result.Success}, - new object[] {@"//a/b/c/..", false, false, @"/a/b", 4, Result.Success}, - new object[] {@"//a/b/c/../..", false, false, @"/a", 2, Result.Success}, - new object[] {@"//a/b/c/", false, false, @"/a/b/c", 6, Result.Success}, - new object[] {@"//a/b/c//..", false, false, @"/a/b", 4, Result.Success}, - new object[] {@"//a/b/c//../..", false, false, @"/a", 2, Result.Success}, - new object[] {@"\\", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\/..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a/..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a/", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a//..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a/b", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a/b/..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a/b/", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a/b//..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a/b/c", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a/b/c/..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a/b/c/../..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a/b/c/", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a/b/c//..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a/b/c//../..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\/..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a/..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a\", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a\/..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a\b", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a\b/..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a\b\", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a\b\/..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a\b\c", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a\b\c/..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a\b\c/../..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a\b\c\", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a\b\c\/..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a\b\c\/../..", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"//$abc/bb", true, false, @"", 0, ResultFs.InvalidCharacter.Value}, - new object[] {@"//:abc/bb", true, false, @"", 0, ResultFs.InvalidCharacter.Value}, - new object[] {@"\\\asd", true, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\/asd", true, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\//asd", true, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"//", true, false, @"/", 1, Result.Success}, - new object[] {@"///..", true, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a/b/cc/../d", true, false, @"\\a/b/d", 7, Result.Success}, - new object[] {@"\\a/b/cc/../d/..", true, false, @"\\a/b", 5, Result.Success}, - new object[] {@"\\a/b/cc/../d/../..", true, false, @"\\a/b", 5, Result.Success}, - new object[] {@"\\a/b/cc/../d/../../..", true, false, @"\\a/b", 5, Result.Success}, - new object[] {@"c:/aa/bb", true, true, @"c:/aa/bb", 8, Result.Success}, - new object[] {@"c:/aa/bb/..", true, true, @"c:/aa", 5, Result.Success}, - new object[] {@"c:/aa/bb/../..", true, true, @"c:/", 3, Result.Success}, - new object[] {@"c:/aa/bb/../../..", true, true, @"c:/aa/bb/", 0, ResultFs.DirectoryUnobtainable.Value}, - new object[] {@"mount:\c:/aa", true, true, @"mount:\c:/aa", 12, Result.Success}, - new object[] {@"mount:\c:/aa/..", true, true, @"mount:\c:", 9, Result.Success}, - new object[] {@"mount:\c:/aa/../..", true, true, @"mount:/", 7, Result.Success}, - new object[] {@"mount:\c:/aa/../../..", true, true, @"mount:/c:/aa/", 0, ResultFs.DirectoryUnobtainable.Value}, - new object[] {@"mount:/c:\aa/bb", true, true, @"mount:/c:\aa/bb", 15, Result.Success}, - new object[] {@"mount:/c:\aa/bb/..", true, true, @"mount:/c:\aa", 12, Result.Success}, - new object[] {@"mount:/c:\aa/bb/../..", true, true, @"mount:/c:", 9, Result.Success}, - new object[] {@"mount:/c:\aa/bb/../../..", true, true, @"mount:/c:", 9, Result.Success}, - new object[] {@"mount:////c:\aa/bb", true, true, @"mount:////c:\aa/bb", 18, Result.Success}, - new object[] {@"mount:////c:\aa/bb/..", true, true, @"mount:////c:\aa", 15, Result.Success}, - new object[] {@"mount:////c:\aa/bb/../..", true, true, @"mount:////c:", 12, Result.Success}, - new object[] {@"mount:////c:\aa/bb/../../..", true, true, @"mount:////c:", 12, Result.Success}, - new object[] {@"mount:/\aa/bb", true, true, @"mount:/\aa/bb", 13, Result.Success}, - new object[] {@"mount:/\aa/bb/..", true, true, @"mount:/\aa", 10, Result.Success}, - new object[] {@"mount:/\aa/bb/../..", true, true, @"mount:/", 7, Result.Success}, - new object[] {@"mount:/\aa/bb/../../..", true, true, @"mount:/\aa/bb/", 0, ResultFs.DirectoryUnobtainable.Value}, - new object[] {@"mount:/c:/aa/bb", true, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"mount:c:/aa/bb", true, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"mount:c:/aa/bb", true, true, @"mount:c:/aa/bb", 14, Result.Success}, - new object[] {@"mount:/\aa/../b", true, true, @"mount:/b", 8, Result.Success}, - new object[] {@"mount:/\aa/../b/..", true, true, @"mount:/", 7, Result.Success}, - new object[] {@"mount:/\aa/../b/../..", true, true, @"mount:/b/a/", 0, ResultFs.DirectoryUnobtainable.Value}, - new object[] {@"mount://aa/bb", true, true, @"mount:/\\aa/bb", 14, Result.Success}, - new object[] {@"mount://aa/bb/..", true, true, @"mount:/\\aa/bb", 14, Result.Success}, - new object[] {@"//aa/bb", true, true, @"/\\aa/bb", 8, Result.Success}, - new object[] {@"//aa/bb/..", true, true, @"/\\aa/bb", 8, Result.Success}, - new object[] {@"//aa/bb", true, false, @"\\aa/bb", 7, Result.Success}, - new object[] {@"//aa/bb/..", true, false, @"\\aa/bb", 7, Result.Success}, - new object[] {@"/aa/bb", true, false, @"/aa/bb", 6, Result.Success}, - new object[] {@"/aa/bb/..", true, false, @"/aa", 3, Result.Success}, - new object[] {@"/aa/bb/../..", true, false, @"/", 1, Result.Success}, - new object[] {@"/aa/bb/../../..", true, false, @"/aa/bb/", 0, ResultFs.DirectoryUnobtainable.Value}, - new object[] {@"c:/aa", true, false, @"c:/aa", 5, Result.Success}, - new object[] {@"c:/aa/..", true, false, @"c:", 2, Result.Success}, - new object[] {@"c:/aa/../..", true, false, @"c:", 2, Result.Success}, - new object[] {@"c:abcde/aa/bb", true, false, @"c:abcde/aa/bb", 13, Result.Success}, - new object[] {@"c:abcde/aa/bb/..", true, false, @"c:abcde/aa", 10, Result.Success}, - new object[] {@"c:abcde/aa/bb/../..", true, false, @"c:abcde", 7, Result.Success}, - new object[] {@"c:abcde/aa/bb/../../..", true, false, @"c:abcde", 7, Result.Success}, - new object[] {@"c:abcde", true, false, @"c:abcde", 7, Result.Success}, - new object[] {@"c:abcde/..", true, false, @"c:abcde", 7, Result.Success}, - new object[] {@"c:abcde/", true, false, @"c:abcde", 7, Result.Success}, - new object[] {@"///aa", true, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"//aa//bb", true, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"//aa//bb/..", true, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"//./bb", true, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"//../bb", true, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"//.../bb", true, false, @"\\.../bb", 8, Result.Success}, - new object[] {@"//aa$abc/bb", true, false, @"", 0, ResultFs.InvalidCharacter.Value}, - new object[] {@"//aa$/bb", true, false, @"", 0, ResultFs.InvalidCharacter.Value}, - new object[] {@"//aa:/bb", true, false, @"", 0, ResultFs.InvalidCharacter.Value}, - new object[] {@"//aa/bb$b/cc$", true, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"//aa/bb/cc$c", true, false, @"\\aa/bb/cc$c", 12, Result.Success}, - new object[] {@"//aa/bb/cc$c/..", true, false, @"\\aa/bb", 7, Result.Success}, - new object[] {@"//aa/bb/cc$c/dd", true, false, @"\\aa/bb/cc$c/dd", 15, Result.Success}, - new object[] {@"//aa/bb", true, false, @"\\aa/bb", 7, Result.Success}, - new object[] {@"//aa/bb/cc//dd", true, false, @"\\aa/bb/cc/dd", 13, Result.Success}, - new object[] {@"//aa/bb/cc//dd/..", true, false, @"\\aa/bb/cc", 10, Result.Success}, - new object[] {@"//aa/bb/cc//dd/../..", true, false, @"\\aa/bb", 7, Result.Success}, - new object[] {@"//aa/bb/cc//dd/../../..", true, false, @"\\aa/bb", 7, Result.Success}, - new object[] {@"//aa/bb/cc\/dd", true, false, @"\\aa/bb/cc\/dd", 14, Result.Success}, - new object[] {@"//aa/bb/cc//dd", true, false, @"\\aa/bb/cc/dd", 13, Result.Success}, - new object[] {@"//aa/bb/cc/dd", true, false, @"\\aa/bb/cc/dd", 13, Result.Success}, - new object[] {@"//aa/bb/cc/dd/..", true, false, @"\\aa/bb/cc", 10, Result.Success}, - new object[] {@"//aa/bb/cc/dd/../..", true, false, @"\\aa/bb", 7, Result.Success}, - new object[] {@"//aa/bb/cc/dd/../../..", true, false, @"\\aa/bb", 7, Result.Success}, - new object[] {@"//aa/bb/cc/\dd", true, false, @"\\aa/bb/cc/\dd", 14, Result.Success}, - new object[] {@"//aa/bb/cc/\dd/..", true, false, @"\\aa/bb/cc", 10, Result.Success}, - new object[] {@"//aa/bb/cc/\dd/../..", true, false, @"\\aa/bb", 7, Result.Success}, - new object[] {@"//aa/bb/cc/\dd/../../..", true, false, @"\\aa/bb", 7, Result.Success}, - new object[] {@"//aa/../", true, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"//aa//", true, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"//aa/bb..", true, false, @"\\aa/bb..", 9, Result.Success}, - new object[] {@"//aa/bb../..", true, false, @"\\aa/bb..", 9, Result.Success}, - new object[] {@"//aa/bb../", true, false, @"\\aa/bb..", 9, Result.Success}, - new object[] {@"//aa/bb..//..", true, false, @"\\aa/bb..", 9, Result.Success}, - new object[] {@"/\\aa/bb/cc/..", true, true, @"/\\aa/bb", 8, Result.Success}, - new object[] {@"/\\aa/bb/cc/../..", true, true, @"/\\aa/bb", 8, Result.Success}, - new object[] {@"/\\aa/bb/cc/../../..", true, true, @"/\\aa/bb", 8, Result.Success}, - new object[] {@"/\\aa/bb/cc/../../../..", true, true, @"/\\aa/bb", 8, Result.Success}, - new object[] {@"c:aa\bb/cc", true, false, @"c:aa\bb/cc", 10, Result.Success}, - new object[] {@"c:aa\bb/cc/..", true, false, @"c:aa\bb", 7, Result.Success}, - new object[] {@"c:aa\bb/cc/../..", true, false, @"c:aa", 4, Result.Success}, - new object[] {@"c:aa\bb/cc/../../..", true, false, @"c:aa", 4, Result.Success}, - new object[] {@"c:\//\aa\bb", true, false, @"c:\/\aa\bb", 10, Result.Success}, - new object[] {@"c:\//\aa\bb/..", true, false, @"c:\", 3, Result.Success}, - new object[] {@"mount://////a/bb/c", true, true, @"mount:/\\a/bb/c", 15, Result.Success}, - new object[] {@"mount://////a/bb/c/..", true, true, @"mount:/\\a/bb", 13, Result.Success}, - new object[] {@"mount://////a/bb/c/../..", true, true, @"mount:/\\a/bb", 13, Result.Success}, - new object[] {@"//", true, false, @"/", 1, Result.Success}, - new object[] {@"///..", true, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"//a", true, false, @"/a", 2, Result.Success}, - new object[] {@"//a/..", true, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"//a", true, false, @"/a", 2, Result.Success}, - new object[] {@"//a/..", true, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"//a/", true, false, @"\\a/", 4, Result.Success}, - new object[] {@"//a//..", true, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"//a/b", true, false, @"\\a/b", 5, Result.Success}, - new object[] {@"//a/b/..", true, false, @"\\a/b", 5, Result.Success}, - new object[] {@"//a/b/", true, false, @"\\a/b", 5, Result.Success}, - new object[] {@"//a/b//..", true, false, @"\\a/b", 5, Result.Success}, - new object[] {@"//a/b/c", true, false, @"\\a/b/c", 7, Result.Success}, - new object[] {@"//a/b/c/..", true, false, @"\\a/b", 5, Result.Success}, - new object[] {@"//a/b/c/../..", true, false, @"\\a/b", 5, Result.Success}, - new object[] {@"//a/b/c/", true, false, @"\\a/b/c", 7, Result.Success}, - new object[] {@"//a/b/c//..", true, false, @"\\a/b", 5, Result.Success}, - new object[] {@"//a/b/c//../..", true, false, @"\\a/b", 5, Result.Success}, - new object[] {@"\\", true, false, @"\\", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\/..", true, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a", true, false, @"\\", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a/..", true, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a/", true, false, @"\\a/", 4, Result.Success}, - new object[] {@"\\a//..", true, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a/b", true, false, @"\\a/b", 5, Result.Success}, - new object[] {@"\\a/b/..", true, false, @"\\a/b", 5, Result.Success}, - new object[] {@"\\a/b/", true, false, @"\\a/b", 5, Result.Success}, - new object[] {@"\\a/b//..", true, false, @"\\a/b", 5, Result.Success}, - new object[] {@"\\a/b/c", true, false, @"\\a/b/c", 7, Result.Success}, - new object[] {@"\\a/b/c/..", true, false, @"\\a/b", 5, Result.Success}, - new object[] {@"\\a/b/c/../..", true, false, @"\\a/b", 5, Result.Success}, - new object[] {@"\\a/b/c/", true, false, @"\\a/b/c", 7, Result.Success}, - new object[] {@"\\a/b/c//..", true, false, @"\\a/b", 5, Result.Success}, - new object[] {@"\\a/b/c//../..", true, false, @"\\a/b", 5, Result.Success}, - new object[] {@"\\", true, false, @"\\", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\/..", true, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a", true, false, @"\\", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a/..", true, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a\", true, false, @"\\a/", 4, Result.Success}, - new object[] {@"\\a\/..", true, false, @"", 0, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a\b", true, false, @"\\a/b", 5, Result.Success}, - new object[] {@"\\a\b/..", true, false, @"\\a/b", 5, Result.Success}, - new object[] {@"\\a\b\", true, false, @"\\a/b\", 6, Result.Success}, - new object[] {@"\\a\b\/..", true, false, @"\\a", 3, Result.Success}, - new object[] {@"\\a\b\c", true, false, @"\\a/b\c", 7, Result.Success}, - new object[] {@"\\a\b\c/..", true, false, @"\\a/b", 5, Result.Success}, - new object[] {@"\\a\b\c/../..", true, false, @"\\a/b", 5, Result.Success}, - new object[] {@"\\a\b\c\", true, false, @"\\a/b\c\", 8, Result.Success}, - new object[] {@"\\a\b\c\/..", true, false, @"\\a/b", 5, Result.Success}, - new object[] {@"\\a\b\c\/../..", true, false, @"\\a/b", 5, Result.Success} - }; - - public static object[][] IsNormalizedTestItems = - { - new object[] {@"", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"/", false, false, true, Result.Success}, - new object[] {@"/.", false, false, false, Result.Success}, - new object[] {@"/a/b/c", false, false, true, Result.Success}, - new object[] {@"/a/b/../c", false, false, false, Result.Success}, - new object[] {@"/a/b/c/..", false, false, false, Result.Success}, - new object[] {@"/a/b/c/.", false, false, false, Result.Success}, - new object[] {@"/a/../../..", false, false, false, Result.Success}, - new object[] {@"/a/../../../a/b/c", false, false, false, Result.Success}, - new object[] {@"//a/b//.//c", false, false, false, Result.Success}, - new object[] {@"/../a/b/c/.", false, false, false, Result.Success}, - new object[] {@"/./aaa/bbb/ccc/.", false, false, false, Result.Success}, - new object[] {@"/a/b/c/", false, false, false, Result.Success}, - new object[] {@"a/b/c/", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"/aa/./bb/../cc/", false, false, false, Result.Success}, - new object[] {@"/./b/../c/", false, false, false, Result.Success}, - new object[] {@"/a/../../../", false, false, false, Result.Success}, - new object[] {@"//a/b//.//c/", false, false, false, Result.Success}, - new object[] {@"/tmp/../", false, false, false, Result.Success}, - new object[] {@"a", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"a/../../../a/b/c", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"./b/../c/", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@".", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"..", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"../a/b/c/.", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"./a/b/c/.", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"abc", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"mount:/a/b/../c", false, true, false, Result.Success}, - new object[] {@"mount:/a/b/../c", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"a:/a/b/c", false, true, true, Result.Success}, - new object[] {@"mount:/a/b/../c", false, true, false, Result.Success}, - new object[] {@"mount:\a/b/../c", false, true, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"mount:\a/b\../c", false, true, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"mount:\a/b/c", false, true, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"mount:/a\../b\..c", false, true, false, Result.Success}, - new object[] {@"mount:/a\../b/..cd", false, true, false, Result.Success}, - new object[] {@"mount:/a\..d/b/c\..", false, true, false, Result.Success}, - new object[] {@"mount:", false, true, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"abc:/a/../../../a/b/c", false, true, false, Result.Success}, - new object[] {@"abc:/./b/../c/", false, true, false, Result.Success}, - new object[] {@"abc:/.", false, true, false, Result.Success}, - new object[] {@"abc:/..", false, true, false, Result.Success}, - new object[] {@"abc:/", false, true, true, Result.Success}, - new object[] {@"abc://a/b//.//c", false, true, false, Result.Success}, - new object[] {@"abc:/././/././a/b//.//c", false, true, false, Result.Success}, - new object[] {@"mount:/d./aa", false, true, true, Result.Success}, - new object[] {@"mount:/d/..", false, true, false, Result.Success}, - new object[] {@"/path/aaa/bbb\..\h/ddd", false, false, false, Result.Success}, - new object[] {@"/path/aaa/bbb/../h/ddd", false, false, false, Result.Success}, - new object[] {@"/path/aaa/bbb\.\h/ddd", false, false, true, Result.Success}, - new object[] {@"/path/aaa/bbb\./h/ddd", false, false, true, Result.Success}, - new object[] {@"/path/aaa/bbb/./h/ddd", false, false, false, Result.Success}, - new object[] {@"mount:abcd", false, true, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"mount:", false, true, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"mount:/", false, true, true, Result.Success}, - new object[] {@"mount:\..", false, true, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"mount:/a/b\..", false, true, false, Result.Success}, - new object[] {@"mount:/dir", false, true, true, Result.Success}, - new object[] {@"mount:/dir/", false, true, false, Result.Success}, - new object[] {@"mount:\", false, true, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"mo.unt:\", false, true, false, ResultFs.InvalidCharacter.Value}, - new object[] {@"mount.:\", false, true, false, ResultFs.InvalidCharacter.Value}, - new object[] {@"mount:./aa/bb", false, true, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"mount:.../aa/bb", false, true, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"mount:...aa/bb", false, true, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"...aa/bb", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"mount01234567890/aa/bb", false, true, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"mount01234567890:/aa/bb", false, true, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"mount0123456789:/aa/bb", false, true, true, Result.Success}, - new object[] {@"mount012345678:/aa/bb", false, true, true, Result.Success}, - new object[] {@"mount:aa/..\bb", false, true, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"mount:..\bb", false, true, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"mount:/..\bb", false, true, false, Result.Success}, - new object[] {@"mount:/.\bb", false, true, true, Result.Success}, - new object[] {@"mount:\..\bb", false, true, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"mount:\.\bb", false, true, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"mount:/a\..\bb", false, true, false, Result.Success}, - new object[] {@"mount:/a\.\bb", false, true, true, Result.Success}, - new object[] {@"//$abc/bb", false, false, false, Result.Success}, - new object[] {@"//:abc/bb", false, false, false, Result.Success}, - new object[] {@"\\\asd", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\/asd", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\//asd", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"//", false, false, false, Result.Success}, - new object[] {@"///..", false, false, false, Result.Success}, - new object[] {@"\\a/b/cc/../d", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a/b/cc/../d/..", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a/b/cc/../d/../..", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a/b/cc/../d/../../..", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"c:/aa/bb", false, true, true, Result.Success}, - new object[] {@"c:/aa/bb/..", false, true, false, Result.Success}, - new object[] {@"c:/aa/bb/../..", false, true, false, Result.Success}, - new object[] {@"c:/aa/bb/../../..", false, true, false, Result.Success}, - new object[] {@"mount:\c:/aa", false, true, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"mount:\c:/aa/..", false, true, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"mount:\c:/aa/../..", false, true, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"mount:\c:/aa/../../..", false, true, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"mount:/c:\aa/bb", false, true, true, Result.Success}, - new object[] {@"mount:/c:\aa/bb/..", false, true, false, Result.Success}, - new object[] {@"mount:/c:\aa/bb/../..", false, true, false, Result.Success}, - new object[] {@"mount:/c:\aa/bb/../../..", false, true, false, Result.Success}, - new object[] {@"mount:////c:\aa/bb", false, true, false, Result.Success}, - new object[] {@"mount:////c:\aa/bb/..", false, true, false, Result.Success}, - new object[] {@"mount:////c:\aa/bb/../..", false, true, false, Result.Success}, - new object[] {@"mount:////c:\aa/bb/../../..", false, true, false, Result.Success}, - new object[] {@"mount:/\aa/bb", false, true, true, Result.Success}, - new object[] {@"mount:/\aa/bb/..", false, true, false, Result.Success}, - new object[] {@"mount:/\aa/bb/../..", false, true, false, Result.Success}, - new object[] {@"mount:/\aa/bb/../../..", false, true, false, Result.Success}, - new object[] {@"mount:/c:/aa/bb", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"mount:c:/aa/bb", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"mount:c:/aa/bb", false, true, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"mount:/\aa/../b", false, true, false, Result.Success}, - new object[] {@"mount:/\aa/../b/..", false, true, false, Result.Success}, - new object[] {@"mount:/\aa/../b/../..", false, true, false, Result.Success}, - new object[] {@"mount://aa/bb", false, true, false, Result.Success}, - new object[] {@"mount://aa/bb/..", false, true, false, Result.Success}, - new object[] {@"//aa/bb", false, true, false, Result.Success}, - new object[] {@"//aa/bb/..", false, true, false, Result.Success}, - new object[] {@"//aa/bb", false, false, false, Result.Success}, - new object[] {@"//aa/bb/..", false, false, false, Result.Success}, - new object[] {@"/aa/bb", false, false, true, Result.Success}, - new object[] {@"/aa/bb/..", false, false, false, Result.Success}, - new object[] {@"/aa/bb/../..", false, false, false, Result.Success}, - new object[] {@"/aa/bb/../../..", false, false, false, Result.Success}, - new object[] {@"c:/aa", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"c:/aa/..", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"c:/aa/../..", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"c:abcde/aa/bb", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"c:abcde/aa/bb/..", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"c:abcde/aa/bb/../..", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"c:abcde/aa/bb/../../..", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"c:abcde", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"c:abcde/..", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"c:abcde/", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"///aa", false, false, false, Result.Success}, - new object[] {@"//aa//bb", false, false, false, Result.Success}, - new object[] {@"//aa//bb/..", false, false, false, Result.Success}, - new object[] {@"//./bb", false, false, false, Result.Success}, - new object[] {@"//../bb", false, false, false, Result.Success}, - new object[] {@"//.../bb", false, false, false, Result.Success}, - new object[] {@"//aa$abc/bb", false, false, false, Result.Success}, - new object[] {@"//aa$/bb", false, false, false, Result.Success}, - new object[] {@"//aa:/bb", false, false, false, Result.Success}, - new object[] {@"//aa/bb$b/cc$", false, false, false, Result.Success}, - new object[] {@"//aa/bb/cc$c", false, false, false, Result.Success}, - new object[] {@"//aa/bb/cc$c/..", false, false, false, Result.Success}, - new object[] {@"//aa/bb/cc$c/dd", false, false, false, Result.Success}, - new object[] {@"//aa/bb", false, false, false, Result.Success}, - new object[] {@"//aa/bb/cc//dd", false, false, false, Result.Success}, - new object[] {@"//aa/bb/cc//dd/..", false, false, false, Result.Success}, - new object[] {@"//aa/bb/cc//dd/../..", false, false, false, Result.Success}, - new object[] {@"//aa/bb/cc//dd/../../..", false, false, false, Result.Success}, - new object[] {@"//aa/bb/cc\/dd", false, false, false, Result.Success}, - new object[] {@"//aa/bb/cc//dd", false, false, false, Result.Success}, - new object[] {@"//aa/bb/cc/dd", false, false, false, Result.Success}, - new object[] {@"//aa/bb/cc/dd/..", false, false, false, Result.Success}, - new object[] {@"//aa/bb/cc/dd/../..", false, false, false, Result.Success}, - new object[] {@"//aa/bb/cc/dd/../../..", false, false, false, Result.Success}, - new object[] {@"//aa/bb/cc/\dd", false, false, false, Result.Success}, - new object[] {@"//aa/bb/cc/\dd/..", false, false, false, Result.Success}, - new object[] {@"//aa/bb/cc/\dd/../..", false, false, false, Result.Success}, - new object[] {@"//aa/bb/cc/\dd/../../..", false, false, false, Result.Success}, - new object[] {@"//aa/../", false, false, false, Result.Success}, - new object[] {@"//aa//", false, false, false, Result.Success}, - new object[] {@"//aa/bb..", false, false, false, Result.Success}, - new object[] {@"//aa/bb../..", false, false, false, Result.Success}, - new object[] {@"//aa/bb../", false, false, false, Result.Success}, - new object[] {@"//aa/bb..//..", false, false, false, Result.Success}, - new object[] {@"/\\aa/bb/cc/..", false, true, false, Result.Success}, - new object[] {@"/\\aa/bb/cc/../..", false, true, false, Result.Success}, - new object[] {@"/\\aa/bb/cc/../../..", false, true, false, Result.Success}, - new object[] {@"/\\aa/bb/cc/../../../..", false, true, false, Result.Success}, - new object[] {@"c:aa\bb/cc", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"c:aa\bb/cc/..", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"c:aa\bb/cc/../..", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"c:aa\bb/cc/../../..", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"c:\//\aa\bb", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"c:\//\aa\bb/..", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"mount://////a/bb/c", false, true, false, Result.Success}, - new object[] {@"mount://////a/bb/c/..", false, true, false, Result.Success}, - new object[] {@"mount://////a/bb/c/../..", false, true, false, Result.Success}, - new object[] {@"//", false, false, false, Result.Success}, - new object[] {@"///..", false, false, false, Result.Success}, - new object[] {@"//a", false, false, false, Result.Success}, - new object[] {@"//a/..", false, false, false, Result.Success}, - new object[] {@"//a", false, false, false, Result.Success}, - new object[] {@"//a/..", false, false, false, Result.Success}, - new object[] {@"//a/", false, false, false, Result.Success}, - new object[] {@"//a//..", false, false, false, Result.Success}, - new object[] {@"//a/b", false, false, false, Result.Success}, - new object[] {@"//a/b/..", false, false, false, Result.Success}, - new object[] {@"//a/b/", false, false, false, Result.Success}, - new object[] {@"//a/b//..", false, false, false, Result.Success}, - new object[] {@"//a/b/c", false, false, false, Result.Success}, - new object[] {@"//a/b/c/..", false, false, false, Result.Success}, - new object[] {@"//a/b/c/../..", false, false, false, Result.Success}, - new object[] {@"//a/b/c/", false, false, false, Result.Success}, - new object[] {@"//a/b/c//..", false, false, false, Result.Success}, - new object[] {@"//a/b/c//../..", false, false, false, Result.Success}, - new object[] {@"\\", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\/..", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a/..", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a/", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a//..", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a/b", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a/b/..", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a/b/", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a/b//..", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a/b/c", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a/b/c/..", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a/b/c/../..", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a/b/c/", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a/b/c//..", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a/b/c//../..", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\/..", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a/..", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a\", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a\/..", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a\b", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a\b/..", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a\b\", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a\b\/..", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a\b\c", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a\b\c/..", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a\b\c/../..", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a\b\c\", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a\b\c\/..", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a\b\c\/../..", false, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"//$abc/bb", true, false, false, ResultFs.InvalidCharacter.Value}, - new object[] {@"//:abc/bb", true, false, false, ResultFs.InvalidCharacter.Value}, - new object[] {@"\\\asd", true, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\/asd", true, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\//asd", true, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"//", true, false, false, Result.Success}, - new object[] {@"///..", true, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a/b/cc/../d", true, false, false, Result.Success}, - new object[] {@"\\a/b/cc/../d/..", true, false, false, Result.Success}, - new object[] {@"\\a/b/cc/../d/../..", true, false, false, Result.Success}, - new object[] {@"\\a/b/cc/../d/../../..", true, false, false, Result.Success}, - new object[] {@"c:/aa/bb", true, true, true, Result.Success}, - new object[] {@"c:/aa/bb/..", true, true, false, Result.Success}, - new object[] {@"c:/aa/bb/../..", true, true, false, Result.Success}, - new object[] {@"c:/aa/bb/../../..", true, true, false, Result.Success}, - new object[] {@"mount:\c:/aa", true, true, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"mount:\c:/aa/..", true, true, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"mount:\c:/aa/../..", true, true, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"mount:\c:/aa/../../..", true, true, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"mount:/c:\aa/bb", true, true, true, Result.Success}, - new object[] {@"mount:/c:\aa/bb/..", true, true, false, Result.Success}, - new object[] {@"mount:/c:\aa/bb/../..", true, true, false, Result.Success}, - new object[] {@"mount:/c:\aa/bb/../../..", true, true, false, Result.Success}, - new object[] {@"mount:////c:\aa/bb", true, true, false, Result.Success}, - new object[] {@"mount:////c:\aa/bb/..", true, true, false, Result.Success}, - new object[] {@"mount:////c:\aa/bb/../..", true, true, false, Result.Success}, - new object[] {@"mount:////c:\aa/bb/../../..", true, true, false, Result.Success}, - new object[] {@"mount:/\aa/bb", true, true, true, Result.Success}, - new object[] {@"mount:/\aa/bb/..", true, true, false, Result.Success}, - new object[] {@"mount:/\aa/bb/../..", true, true, false, Result.Success}, - new object[] {@"mount:/\aa/bb/../../..", true, true, false, Result.Success}, - new object[] {@"mount:/c:/aa/bb", true, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"mount:c:/aa/bb", true, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"mount:c:/aa/bb", true, true, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"mount:/\aa/../b", true, true, false, Result.Success}, - new object[] {@"mount:/\aa/../b/..", true, true, false, Result.Success}, - new object[] {@"mount:/\aa/../b/../..", true, true, false, Result.Success}, - new object[] {@"mount://aa/bb", true, true, false, Result.Success}, - new object[] {@"mount://aa/bb/..", true, true, false, Result.Success}, - new object[] {@"//aa/bb", true, true, false, Result.Success}, - new object[] {@"//aa/bb/..", true, true, false, Result.Success}, - new object[] {@"//aa/bb", true, false, false, Result.Success}, - new object[] {@"//aa/bb/..", true, false, false, Result.Success}, - new object[] {@"/aa/bb", true, false, true, Result.Success}, - new object[] {@"/aa/bb/..", true, false, false, Result.Success}, - new object[] {@"/aa/bb/../..", true, false, false, Result.Success}, - new object[] {@"/aa/bb/../../..", true, false, false, Result.Success}, - new object[] {@"c:/aa", true, false, true, Result.Success}, - new object[] {@"c:/aa/..", true, false, false, Result.Success}, - new object[] {@"c:/aa/../..", true, false, false, Result.Success}, - new object[] {@"c:abcde/aa/bb", true, false, true, Result.Success}, - new object[] {@"c:abcde/aa/bb/..", true, false, false, Result.Success}, - new object[] {@"c:abcde/aa/bb/../..", true, false, false, Result.Success}, - new object[] {@"c:abcde/aa/bb/../../..", true, false, false, Result.Success}, - new object[] {@"c:abcde", true, false, true, Result.Success}, - new object[] {@"c:abcde/..", true, false, false, Result.Success}, - new object[] {@"c:abcde/", true, false, false, Result.Success}, - new object[] {@"///aa", true, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"//aa//bb", true, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"//aa//bb/..", true, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"//./bb", true, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"//../bb", true, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"//.../bb", true, false, false, Result.Success}, - new object[] {@"//aa$abc/bb", true, false, false, ResultFs.InvalidCharacter.Value}, - new object[] {@"//aa$/bb", true, false, false, ResultFs.InvalidCharacter.Value}, - new object[] {@"//aa:/bb", true, false, false, ResultFs.InvalidCharacter.Value}, - new object[] {@"//aa/bb$b/cc$", true, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"//aa/bb/cc$c", true, false, false, Result.Success}, - new object[] {@"//aa/bb/cc$c/..", true, false, false, Result.Success}, - new object[] {@"//aa/bb/cc$c/dd", true, false, false, Result.Success}, - new object[] {@"//aa/bb", true, false, false, Result.Success}, - new object[] {@"//aa/bb/cc//dd", true, false, false, Result.Success}, - new object[] {@"//aa/bb/cc//dd/..", true, false, false, Result.Success}, - new object[] {@"//aa/bb/cc//dd/../..", true, false, false, Result.Success}, - new object[] {@"//aa/bb/cc//dd/../../..", true, false, false, Result.Success}, - new object[] {@"//aa/bb/cc\/dd", true, false, false, Result.Success}, - new object[] {@"//aa/bb/cc//dd", true, false, false, Result.Success}, - new object[] {@"//aa/bb/cc/dd", true, false, false, Result.Success}, - new object[] {@"//aa/bb/cc/dd/..", true, false, false, Result.Success}, - new object[] {@"//aa/bb/cc/dd/../..", true, false, false, Result.Success}, - new object[] {@"//aa/bb/cc/dd/../../..", true, false, false, Result.Success}, - new object[] {@"//aa/bb/cc/\dd", true, false, false, Result.Success}, - new object[] {@"//aa/bb/cc/\dd/..", true, false, false, Result.Success}, - new object[] {@"//aa/bb/cc/\dd/../..", true, false, false, Result.Success}, - new object[] {@"//aa/bb/cc/\dd/../../..", true, false, false, Result.Success}, - new object[] {@"//aa/../", true, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"//aa//", true, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"//aa/bb..", true, false, false, Result.Success}, - new object[] {@"//aa/bb../..", true, false, false, Result.Success}, - new object[] {@"//aa/bb../", true, false, false, Result.Success}, - new object[] {@"//aa/bb..//..", true, false, false, Result.Success}, - new object[] {@"/\\aa/bb/cc/..", true, true, false, Result.Success}, - new object[] {@"/\\aa/bb/cc/../..", true, true, false, Result.Success}, - new object[] {@"/\\aa/bb/cc/../../..", true, true, false, Result.Success}, - new object[] {@"/\\aa/bb/cc/../../../..", true, true, false, Result.Success}, - new object[] {@"c:aa\bb/cc", true, false, true, Result.Success}, - new object[] {@"c:aa\bb/cc/..", true, false, false, Result.Success}, - new object[] {@"c:aa\bb/cc/../..", true, false, false, Result.Success}, - new object[] {@"c:aa\bb/cc/../../..", true, false, false, Result.Success}, - new object[] {@"c:\//\aa\bb", true, false, false, Result.Success}, - new object[] {@"c:\//\aa\bb/..", true, false, false, Result.Success}, - new object[] {@"mount://////a/bb/c", true, true, false, Result.Success}, - new object[] {@"mount://////a/bb/c/..", true, true, false, Result.Success}, - new object[] {@"mount://////a/bb/c/../..", true, true, false, Result.Success}, - new object[] {@"//", true, false, false, Result.Success}, - new object[] {@"///..", true, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"//a", true, false, false, Result.Success}, - new object[] {@"//a/..", true, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"//a", true, false, false, Result.Success}, - new object[] {@"//a/..", true, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"//a/", true, false, false, Result.Success}, - new object[] {@"//a//..", true, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"//a/b", true, false, false, Result.Success}, - new object[] {@"//a/b/..", true, false, false, Result.Success}, - new object[] {@"//a/b/", true, false, false, Result.Success}, - new object[] {@"//a/b//..", true, false, false, Result.Success}, - new object[] {@"//a/b/c", true, false, false, Result.Success}, - new object[] {@"//a/b/c/..", true, false, false, Result.Success}, - new object[] {@"//a/b/c/../..", true, false, false, Result.Success}, - new object[] {@"//a/b/c/", true, false, false, Result.Success}, - new object[] {@"//a/b/c//..", true, false, false, Result.Success}, - new object[] {@"//a/b/c//../..", true, false, false, Result.Success}, - new object[] {@"\\", true, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\/..", true, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a", true, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a/..", true, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a/", true, false, true, Result.Success}, - new object[] {@"\\a//..", true, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a/b", true, false, true, Result.Success}, - new object[] {@"\\a/b/..", true, false, false, Result.Success}, - new object[] {@"\\a/b/", true, false, false, Result.Success}, - new object[] {@"\\a/b//..", true, false, false, Result.Success}, - new object[] {@"\\a/b/c", true, false, true, Result.Success}, - new object[] {@"\\a/b/c/..", true, false, false, Result.Success}, - new object[] {@"\\a/b/c/../..", true, false, false, Result.Success}, - new object[] {@"\\a/b/c/", true, false, false, Result.Success}, - new object[] {@"\\a/b/c//..", true, false, false, Result.Success}, - new object[] {@"\\a/b/c//../..", true, false, false, Result.Success}, - new object[] {@"\\", true, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\/..", true, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a", true, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a/..", true, false, false, ResultFs.InvalidPathFormat.Value}, - new object[] {@"\\a\", true, false, false, Result.Success}, - new object[] {@"\\a\/..", true, false, false, Result.Success}, - new object[] {@"\\a\b", true, false, false, Result.Success}, - new object[] {@"\\a\b/..", true, false, false, Result.Success}, - new object[] {@"\\a\b\", true, false, false, Result.Success}, - new object[] {@"\\a\b\/..", true, false, false, Result.Success}, - new object[] {@"\\a\b\c", true, false, false, Result.Success}, - new object[] {@"\\a\b\c/..", true, false, false, Result.Success}, - new object[] {@"\\a\b\c/../..", true, false, false, Result.Success}, - new object[] {@"\\a\b\c\", true, false, false, Result.Success}, - new object[] {@"\\a\b\c\/..", true, false, false, Result.Success}, - new object[] {@"\\a\b\c\/../..", true, false, false, Result.Success} - }; - - public static object[][] SubpathTestItems = - { - new object[] {@"//a/b", @"/a", false}, - new object[] {@"/a", @"//a/b", false}, - new object[] {@"//a/b", @"\\a", false}, - new object[] {@"//a/b", @"//a", true}, - new object[] {@"/", @"/a", true}, - new object[] {@"/a", @"/", true}, - new object[] {@"/", @"/", false}, - new object[] {@"", @"", false}, - new object[] {@"/", @"", true}, - new object[] {@"/", @"mount:/a", false}, - new object[] {@"mount:/", @"mount:/", false}, - new object[] {@"mount:/a/b", @"mount:/a/b", false}, - new object[] {@"mount:/a/b", @"mount:/a/b/c", true}, - new object[] {@"/a/b", @"/a/b/c", true}, - new object[] {@"/a/b/c", @"/a/b", true}, - new object[] {@"/a/b", @"/a/b", false}, - new object[] {@"/a/b", @"/a/b\c", false} - }; - } -} diff --git a/tests/LibHac.Tests/Fs/PathUtilityTests.cs b/tests/LibHac.Tests/Fs/PathUtilityTests.cs index 063172af..3b264acb 100644 --- a/tests/LibHac.Tests/Fs/PathUtilityTests.cs +++ b/tests/LibHac.Tests/Fs/PathUtilityTests.cs @@ -32,7 +32,7 @@ namespace LibHac.Tests.Fs [Theory, MemberData(nameof(TestData_IsSubPath))] public static void IsSubPath(string path1, string path2, bool expectedResult) { - bool result = PathUtility12.IsSubPath(path1.ToU8Span(), path2.ToU8Span()); + bool result = PathUtility.IsSubPath(path1.ToU8Span(), path2.ToU8Span()); Assert.Equal(expectedResult, result); } diff --git a/tests/LibHac.Tests/Fs/SubdirectoryFileSystemTests.cs b/tests/LibHac.Tests/Fs/SubdirectoryFileSystemTests.cs index c49a572e..0626b342 100644 --- a/tests/LibHac.Tests/Fs/SubdirectoryFileSystemTests.cs +++ b/tests/LibHac.Tests/Fs/SubdirectoryFileSystemTests.cs @@ -17,11 +17,14 @@ namespace LibHac.Tests.Fs private (IFileSystem baseFs, IFileSystem subDirFs) CreateFileSystemInternal() { var baseFs = new InMemoryFileSystem(); - baseFs.CreateDirectory("/sub".ToU8Span()); - baseFs.CreateDirectory("/sub/path".ToU8Span()); + baseFs.CreateDirectory("/sub"); + baseFs.CreateDirectory("/sub/path"); + + var rootPath = new Path(); + PathFunctions.SetUpFixedPath(ref rootPath, "/sub/path".ToU8String()); var subFs = new SubdirectoryFileSystem(baseFs); - subFs.Initialize("/sub/path".ToU8String()).ThrowIfFailure(); + subFs.Initialize(in rootPath).ThrowIfFailure(); return (baseFs, subFs); } @@ -31,9 +34,9 @@ namespace LibHac.Tests.Fs { (IFileSystem baseFs, IFileSystem subDirFs) = CreateFileSystemInternal(); - subDirFs.CreateFile("/file".ToU8Span(), 0, CreateFileOptions.None); + subDirFs.CreateFile("/file", 0, CreateFileOptions.None); - Assert.Success(baseFs.GetEntryType(out DirectoryEntryType type, "/sub/path/file".ToU8Span())); + Assert.Success(baseFs.GetEntryType(out DirectoryEntryType type, "/sub/path/file")); Assert.Equal(DirectoryEntryType.File, type); } @@ -42,9 +45,9 @@ namespace LibHac.Tests.Fs { (IFileSystem baseFs, IFileSystem subDirFs) = CreateFileSystemInternal(); - subDirFs.CreateDirectory("/dir".ToU8Span()); + subDirFs.CreateDirectory("/dir"); - Assert.Success(baseFs.GetEntryType(out DirectoryEntryType type, "/sub/path/dir".ToU8Span())); + Assert.Success(baseFs.GetEntryType(out DirectoryEntryType type, "/sub/path/dir")); Assert.Equal(DirectoryEntryType.Directory, type); } } @@ -55,8 +58,11 @@ namespace LibHac.Tests.Fs { var baseFs = new InMemoryFileSystem(); + var rootPath = new Path(); + PathFunctions.SetUpFixedPath(ref rootPath, "/".ToU8String()); + var subFs = new SubdirectoryFileSystem(baseFs); - subFs.Initialize("/".ToU8String()).ThrowIfFailure(); + subFs.Initialize(in rootPath).ThrowIfFailure(); return subFs; } } diff --git a/tests/LibHac.Tests/FsSrv/PathNormalizerTests.cs b/tests/LibHac.Tests/FsSrv/PathNormalizerTests.cs deleted file mode 100644 index 7cfea181..00000000 --- a/tests/LibHac.Tests/FsSrv/PathNormalizerTests.cs +++ /dev/null @@ -1,84 +0,0 @@ -using LibHac.Common; -using LibHac.Fs; -using Xunit; -using PathNormalizer = LibHac.FsSrv.Impl.PathNormalizer; - -namespace LibHac.Tests.FsSrv -{ - public class PathNormalizerTests - { - [Fact] - public static void Ctor_EmptyPathWithAcceptEmptyOption_ReturnsEmptyPathWithSuccess() - { - using var normalizer = new PathNormalizer("".ToU8Span(), PathNormalizer.Option.AcceptEmpty); - - Assert.Equal(Result.Success, normalizer.Result); - Assert.True(normalizer.Path.IsEmpty()); - } - - [Fact] - public static void Normalize_PreserveTailSeparatorOption_KeepsExistingTailSeparator() - { - using var normalizer = new PathNormalizer("/a/./b/".ToU8Span(), PathNormalizer.Option.PreserveTrailingSeparator); - - Assert.Equal(Result.Success, normalizer.Result); - Assert.Equal("/a/b/", normalizer.Path.ToString()); - } - - [Fact] - public static void Normalize_PreserveTailSeparatorOption_IgnoresMissingTailSeparator() - { - using var normalizer = new PathNormalizer("/a/./b".ToU8Span(), PathNormalizer.Option.PreserveTrailingSeparator); - - Assert.Equal(Result.Success, normalizer.Result); - Assert.Equal("/a/b", normalizer.Path.ToString()); - } - - [Fact] - public static void Normalize_PathAlreadyNormalized_ReturnsSameBuffer() - { - var originalPath = "/a/b".ToU8Span(); - using var normalizer = new PathNormalizer(originalPath, PathNormalizer.Option.PreserveTrailingSeparator); - - Assert.Equal(Result.Success, normalizer.Result); - - // Compares addresses and lengths of the buffers - Assert.True(originalPath.Value == normalizer.Path.Value); - } - - [Fact] - public static void Normalize_PreserveUncOptionOn_PreservesUncPath() - { - using var normalizer = new PathNormalizer("//aa/bb/..".ToU8Span(), PathNormalizer.Option.PreserveUnc); - - Assert.Equal(Result.Success, normalizer.Result); - Assert.Equal(@"\\aa/bb", normalizer.Path.ToString()); - } - - [Fact] - public static void Normalize_PreserveUncOptionOff_DoesNotPreserveUncPath() - { - using var normalizer = new PathNormalizer("//aa/bb/..".ToU8Span(), PathNormalizer.Option.None); - - Assert.Equal(Result.Success, normalizer.Result); - Assert.Equal(@"/aa", normalizer.Path.ToString()); - } - - [Fact] - public static void Normalize_MountNameOptionOn_ParsesMountName() - { - using var normalizer = new PathNormalizer("mount:/a/./b".ToU8Span(), PathNormalizer.Option.HasMountName); - - Assert.Equal(Result.Success, normalizer.Result); - Assert.Equal("mount:/a/b", normalizer.Path.ToString()); - } - - [Fact] - public static void Normalize_MountNameOptionOff_DoesNotParseMountName() - { - using var normalizer = new PathNormalizer("mount:/a/./b".ToU8Span(), PathNormalizer.Option.None); - - Assert.Equal(ResultFs.InvalidPathFormat.Value, normalizer.Result); - } - } -} diff --git a/tests/LibHac.Tests/FsSystem/ConcatenationFileSystemTests.cs b/tests/LibHac.Tests/FsSystem/ConcatenationFileSystemTests.cs new file mode 100644 index 00000000..d60d302a --- /dev/null +++ b/tests/LibHac.Tests/FsSystem/ConcatenationFileSystemTests.cs @@ -0,0 +1,7 @@ +namespace LibHac.Tests.FsSystem +{ + public class ConcatenationFileSystemTests + { + //asdf + } +} diff --git a/tests/LibHac.Tests/SpanEqualAsserts.cs b/tests/LibHac.Tests/SpanEqualAsserts.cs index 8bdc74ed..3cd04c66 100644 --- a/tests/LibHac.Tests/SpanEqualAsserts.cs +++ b/tests/LibHac.Tests/SpanEqualAsserts.cs @@ -1,6 +1,7 @@ using System; using Xunit.Sdk; +// ReSharper disable once CheckNamespace namespace Xunit { public partial class Assert