diff --git a/src/LibHac/Common/U8Span.cs b/src/LibHac/Common/U8Span.cs index 03705d98..9e4cb0ee 100644 --- a/src/LibHac/Common/U8Span.cs +++ b/src/LibHac/Common/U8Span.cs @@ -1,13 +1,12 @@ using System; using System.Diagnostics; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Text; namespace LibHac.Common { [DebuggerDisplay("{ToString()}")] - public ref struct U8Span + public readonly ref struct U8Span { private readonly ReadOnlySpan _buffer; @@ -33,11 +32,10 @@ namespace LibHac.Common public byte GetOrNull(int i) { byte value = 0; - ReadOnlySpan b = _buffer; - if ((uint)i < (uint)b.Length) + if ((uint)i < (uint)_buffer.Length) { - value = b[i]; + value = GetUnsafe(i); } return value; @@ -46,7 +44,11 @@ namespace LibHac.Common [MethodImpl(MethodImplOptions.AggressiveInlining)] public byte GetUnsafe(int i) { +#if DEBUG + return _buffer[i]; +#else return Unsafe.Add(ref MemoryMarshal.GetReference(_buffer), i); +#endif } public U8Span Slice(int start) diff --git a/src/LibHac/Fs/PathTool.cs b/src/LibHac/Fs/PathTool.cs index d7d511bd..91d9a201 100644 --- a/src/LibHac/Fs/PathTool.cs +++ b/src/LibHac/Fs/PathTool.cs @@ -437,7 +437,40 @@ namespace LibHac.Fs return Result.Success; } - public static Result ParseMountName(out U8Span pathAfterMount, Span outMountNameBuffer, + public static bool IsSubpath(U8Span lhs, U8Span rhs) + { + bool isUncLhs = PathUtility.IsUnc(lhs); + bool isUncRhs = PathUtility.IsUnc(rhs); + + if (isUncLhs && !isUncRhs || !isUncLhs && isUncRhs) + return false; + + if (IsSeparator(lhs.GetOrNull(0)) && IsNullTerminator(lhs.GetOrNull(1)) && + IsSeparator(rhs.GetOrNull(0)) && !IsNullTerminator(rhs.GetOrNull(1))) + return true; + + if (IsSeparator(rhs.GetOrNull(0)) && IsNullTerminator(rhs.GetOrNull(1)) && + IsSeparator(lhs.GetOrNull(0)) && !IsNullTerminator(lhs.GetOrNull(1))) + return true; + + for (int i = 0; ; i++) + { + if (IsNullTerminator(lhs.GetOrNull(i))) + { + return IsSeparator(rhs.GetOrNull(i)); + } + else if (IsNullTerminator(rhs.GetOrNull(i))) + { + return IsSeparator(lhs.GetOrNull(i)); + } + else if (lhs.GetOrNull(i) != rhs.GetOrNull(i)) + { + return false; + } + } + } + + private static Result ParseMountName(out U8Span pathAfterMount, Span outMountNameBuffer, out long mountNameLength, U8Span path) { pathAfterMount = default; diff --git a/src/LibHac/Fs/PathUtility.cs b/src/LibHac/Fs/PathUtility.cs index 679126de..b142d309 100644 --- a/src/LibHac/Fs/PathUtility.cs +++ b/src/LibHac/Fs/PathUtility.cs @@ -25,8 +25,8 @@ namespace LibHac.Fs public static bool IsUnc(U8Span path) { return (uint)path.Length > 1 && - (IsSeparator(path[0]) && IsSeparator(path[1]) || - IsAltSeparator(path[0]) && IsAltSeparator(path[1])); + (IsSeparator(path.GetUnsafe(0)) && IsSeparator(path.GetUnsafe(1)) || + IsAltSeparator(path.GetUnsafe(0)) && IsAltSeparator(path.GetUnsafe(1))); } } } diff --git a/tests/LibHac.Tests/Fs/PathToolTestGenerator.cpp b/tests/LibHac.Tests/Fs/PathToolTestGenerator.cpp index fbace91e..ffa8ffa9 100644 --- a/tests/LibHac.Tests/Fs/PathToolTestGenerator.cpp +++ b/tests/LibHac.Tests/Fs/PathToolTestGenerator.cpp @@ -18,6 +18,8 @@ namespace nn::fs::PathTool { // SDK >= 7 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); } void* allocate(size_t size) @@ -58,7 +60,7 @@ const char* GetResultName(nn::Result result) { } } -void CreateNormalizeTestData(char const* path, bool preserveUnc, bool hasMountName) { +void CreateNormalizeTestItem(char const* path, bool preserveUnc, bool hasMountName) { char normalized[0x200]; uint64_t normalizedLen = 0; memset(normalized, 0, 0x200); @@ -73,7 +75,7 @@ void CreateNormalizeTestData(char const* path, bool preserveUnc, bool hasMountNa path, preserveUncStr, hasMountNameStr, normalized, normalizedLen, GetResultName(result)); } -void CreateIsNormalizedTestData(char const* path, bool preserveUnc, bool hasMountName) { +void CreateIsNormalizedTestItem(char const* path, bool preserveUnc, bool hasMountName) { bool isNormalized = false; nn::Result result = nn::fs::PathTool::IsNormalized(&isNormalized, path, preserveUnc, hasMountName); @@ -85,7 +87,15 @@ void CreateIsNormalizedTestData(char const* path, bool preserveUnc, bool hasMoun path, preserveUncStr, hasMountNameStr, isNormalizedStr, GetResultName(result)); } -void CreateTestDataWithParentDirs(char const* path, bool preserveUnc, bool hasMountName, void (*func)(char const*, bool, bool), int parentCount) { +void CreateIsSubpathTestItem(const char* path1, const char* path2) { + bool result = nn::fs::PathTool::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)); @@ -98,11 +108,11 @@ void CreateTestDataWithParentDirs(char const* path, bool preserveUnc, bool hasMo } } -void CreateTestDataWithParentDirs(char const* path, bool preserveUnc, bool hasMountName, void (*func)(char const*, bool, bool)) { - CreateTestDataWithParentDirs(path, preserveUnc, hasMountName, func, 3); +void CreateTestItemWithParentDirs(char const* path, bool preserveUnc, bool hasMountName, void (*func)(char const*, bool, bool)) { + CreateTestItemWithParentDirs(path, preserveUnc, hasMountName, func, 3); } -void CreateTestData(void (*func)(char const*, bool, bool)) { +void CreateNormalizationTestData(void (*func)(char const*, bool, bool)) { Buf[0] = '\n'; BufPos = 1; @@ -190,90 +200,116 @@ void CreateTestData(void (*func)(char const*, bool, bool)) { for (int i = 0; i < 2; i++) { preserveUnc = (bool)i; - CreateTestDataWithParentDirs("//$abc/bb", preserveUnc, false, func, 0); - CreateTestDataWithParentDirs("//:abc/bb", preserveUnc, false, func, 0); - CreateTestDataWithParentDirs("\\\\\\asd", preserveUnc, false, func, 0); - CreateTestDataWithParentDirs("\\\\/asd", preserveUnc, false, func, 0); - CreateTestDataWithParentDirs("\\\\//asd", preserveUnc, false, func, 0); - CreateTestDataWithParentDirs("//", preserveUnc, false, func, 1); - CreateTestDataWithParentDirs("\\\\a/b/cc/../d", preserveUnc, false, func); - CreateTestDataWithParentDirs("c:/aa/bb", preserveUnc, true, func); - CreateTestDataWithParentDirs("mount:\\c:/aa", preserveUnc, true, func); - CreateTestDataWithParentDirs("mount:/c:\\aa/bb", preserveUnc, true, func); - CreateTestDataWithParentDirs("mount:////c:\\aa/bb", preserveUnc, true, func); - CreateTestDataWithParentDirs("mount:/\\aa/bb", preserveUnc, true, func); - CreateTestDataWithParentDirs("mount:/c:/aa/bb", preserveUnc, false, func, 0); - CreateTestDataWithParentDirs("mount:c:/aa/bb", preserveUnc, false, func, 0); - CreateTestDataWithParentDirs("mount:c:/aa/bb", preserveUnc, true, func, 0); - CreateTestDataWithParentDirs("mount:/\\aa/../b", preserveUnc, true, func, 2); - CreateTestDataWithParentDirs("mount://aa/bb", preserveUnc, true, func, 1); - CreateTestDataWithParentDirs("//aa/bb", preserveUnc, true, func, 1); - CreateTestDataWithParentDirs("//aa/bb", preserveUnc, false, func, 1); - CreateTestDataWithParentDirs("/aa/bb", preserveUnc, false, func); - CreateTestDataWithParentDirs("c:/aa", preserveUnc, false, func, 2); - CreateTestDataWithParentDirs("c:abcde/aa/bb", preserveUnc, false, func); - CreateTestDataWithParentDirs("c:abcde", preserveUnc, false, func, 1); - CreateTestDataWithParentDirs("c:abcde/", preserveUnc, false, func, 0); - CreateTestDataWithParentDirs("///aa", preserveUnc, false, func, 0); - CreateTestDataWithParentDirs("//aa//bb", preserveUnc, false, func, 1); - CreateTestDataWithParentDirs("//./bb", preserveUnc, false, func, 0); - CreateTestDataWithParentDirs("//../bb", preserveUnc, false, func, 0); - CreateTestDataWithParentDirs("//.../bb", preserveUnc, false, func, 0); - CreateTestDataWithParentDirs("//aa$abc/bb", preserveUnc, false, func, 0); - CreateTestDataWithParentDirs("//aa$/bb", preserveUnc, false, func, 0); - CreateTestDataWithParentDirs("//aa:/bb", preserveUnc, false, func, 0); - CreateTestDataWithParentDirs("//aa/bb$b/cc$", preserveUnc, false, func, 0); - CreateTestDataWithParentDirs("//aa/bb/cc$c", preserveUnc, false, func, 1); - CreateTestDataWithParentDirs("//aa/bb/cc$c/dd", preserveUnc, false, func, 0); - CreateTestDataWithParentDirs("//aa/bb", preserveUnc, false, func, 0); - CreateTestDataWithParentDirs("//aa/bb/cc//dd", preserveUnc, false, func); - CreateTestDataWithParentDirs("//aa/bb/cc\\/dd", preserveUnc, false, func, 0); - CreateTestDataWithParentDirs("//aa/bb/cc//dd", preserveUnc, false, func, 0); - CreateTestDataWithParentDirs("//aa/bb/cc/dd", preserveUnc, false, func); - CreateTestDataWithParentDirs("//aa/bb/cc/\\dd", preserveUnc, false, func); - CreateTestDataWithParentDirs("//aa/../", preserveUnc, false, func, 0); - CreateTestDataWithParentDirs("//aa//", preserveUnc, false, func, 0); - CreateTestDataWithParentDirs("//aa/bb..", preserveUnc, false, func, 1); - CreateTestDataWithParentDirs("//aa/bb../", preserveUnc, false, func, 1); - CreateTestDataWithParentDirs("/\\\\aa/bb/cc/..", preserveUnc, true, func); + 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); - CreateTestDataWithParentDirs("c:aa\\bb/cc", preserveUnc, false, func); - CreateTestDataWithParentDirs("c:\\//\\aa\\bb", preserveUnc, false, func, 1); + CreateTestItemWithParentDirs("c:aa\\bb/cc", preserveUnc, false, func); + CreateTestItemWithParentDirs("c:\\//\\aa\\bb", preserveUnc, false, func, 1); - CreateTestDataWithParentDirs("mount://////a/bb/c", preserveUnc, true, func, 2); + CreateTestItemWithParentDirs("mount://////a/bb/c", preserveUnc, true, func, 2); - CreateTestDataWithParentDirs("//", preserveUnc, false, func, 1); - CreateTestDataWithParentDirs("//a", preserveUnc, false, func, 1); - CreateTestDataWithParentDirs("//a", preserveUnc, false, func, 1); - CreateTestDataWithParentDirs("//a/", preserveUnc, false, func, 1); - CreateTestDataWithParentDirs("//a/b", preserveUnc, false, func, 1); - CreateTestDataWithParentDirs("//a/b/", preserveUnc, false, func, 1); - CreateTestDataWithParentDirs("//a/b/c", preserveUnc, false, func, 2); - CreateTestDataWithParentDirs("//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/", 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); - CreateTestDataWithParentDirs("\\\\", preserveUnc, false, func, 1); - CreateTestDataWithParentDirs("\\\\a", preserveUnc, false, func, 1); - CreateTestDataWithParentDirs("\\\\a/", preserveUnc, false, func, 1); - CreateTestDataWithParentDirs("\\\\a/b", preserveUnc, false, func, 1); - CreateTestDataWithParentDirs("\\\\a/b/", preserveUnc, false, func, 1); - CreateTestDataWithParentDirs("\\\\a/b/c", preserveUnc, false, func, 2); - CreateTestDataWithParentDirs("\\\\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); - CreateTestDataWithParentDirs("\\\\", preserveUnc, false, func, 1); - CreateTestDataWithParentDirs("\\\\a", preserveUnc, false, func, 1); - CreateTestDataWithParentDirs("\\\\a\\", preserveUnc, false, func, 1); - CreateTestDataWithParentDirs("\\\\a\\b", preserveUnc, false, func, 1); - CreateTestDataWithParentDirs("\\\\a\\b\\", preserveUnc, false, func, 1); // "\\a\b\/../.." crashes nnsdk - CreateTestDataWithParentDirs("\\\\a\\b\\c", preserveUnc, false, func, 2); - CreateTestDataWithParentDirs("\\\\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 - CreateTestData(CreateNormalizeTestData); - CreateTestData(CreateIsNormalizedTestData); + 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 index 1e12a840..470d8602 100644 --- a/tests/LibHac.Tests/Fs/PathToolTests.cs +++ b/tests/LibHac.Tests/Fs/PathToolTests.cs @@ -36,6 +36,15 @@ namespace LibHac.Tests.Fs } } + [Theory] + [MemberData(nameof(SubpathTestItems))] + public static void IsSubpath(string path1, string path2, bool expectedResult) + { + bool result = PathTool.IsSubpath(path1.ToU8Span(), path2.ToU8Span()); + + Assert.Equal(expectedResult, result); + } + public static object[][] NormalizeTestItems = { new object[] {@"", false, false, @"", 0, ResultFs.InvalidPathFormat.Value}, @@ -813,5 +822,26 @@ namespace LibHac.Tests.Fs 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} + }; } }