Update common path handling code

- Updates path handling code to system version 11.0.0.
- Changes InMemoryFileSystem to normalize all incoming paths.
This commit is contained in:
Alex Barney 2021-03-28 15:50:29 -07:00
parent 1acdd86e27
commit 0c255e0f49
28 changed files with 1360 additions and 1054 deletions

View file

@ -0,0 +1,796 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using LibHac.Common;
using LibHac.Diag;
using LibHac.FsSystem;
using static LibHac.Fs.StringTraits;
// ReSharper disable once CheckNamespace
namespace LibHac.Fs
{
// Previous normalization code can be found in commit 1acdd86e27de16703fdb1c77f50ed8fd71bd3ad7
public static class PathNormalizer
{
private enum NormalizeState
{
Initial,
Normal,
FirstSeparator,
Separator,
Dot,
DoubleDot
}
/// <summary>
/// Checks if a host-name or share-name in a UNC path are "." or ".."
/// </summary>
/// <param name="path">Up to the first two characters in a segment of a UNC path.</param>
/// <returns><see cref="Result.Success"/>: The operation was successful.<br/>
/// <see cref="ResultFs.BufferAllocationFailed"/>: A buffer could not be allocated.</returns>
private static Result CheckSharedName(U8Span path)
{
if (path.Length == 1 && path.GetUnsafe(0) == Dot)
return ResultFs.InvalidPathFormat.Log();
if (path.Length == 2 && path.GetUnsafe(0) == Dot && path.GetUnsafe(1) == Dot)
return ResultFs.InvalidPathFormat.Log();
return Result.Success;
}
private static Result ParseWindowsPath(out U8Span newPath, Span<byte> 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 (path.GetOrNull(0) == DirectorySeparator && path.GetOrNull(1) == AltDirectorySeparator &&
path.GetOrNull(1) == AltDirectorySeparator)
{
currentPath = currentPath.Slice(1);
skippedMount = true;
}
else
{
int separatorCount = 0;
while (IsSeparator(currentPath.GetOrNull(separatorCount)))
{
separatorCount++;
}
if (separatorCount != 0)
{
if (separatorCount == 1 || WindowsPath.IsWindowsDrive(currentPath.Slice(separatorCount)))
{
currentPath = currentPath.Slice(separatorCount);
skippedMount = true;
}
else
{
if (separatorCount > 2 && !Unsafe.IsNullRef(ref isUncNormalized))
{
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);
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;
}
}
if (trimmedPartOffset != 0 && trimmedPath.Value == path.Value)
{
Result rc = CheckSharedName(currentPath.Slice(trimmedPartOffset, i - trimmedPartOffset));
if (rc.IsFailure()) return rc;
trimmedPath = currentPath.Slice(i);
}
}
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)
{
if (mountLength > buffer.Length)
{
return ResultFs.TooLongPath.Log();
}
Span<byte> currentBuffer = buffer;
if (prependSeparator)
{
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;
}
}
}
newPath = trimmedPath;
windowsPathLength = mountLength + (prependSeparator ? 1 : 0);
return Result.Success;
}
}
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<byte>.Empty, out _, out isUncNormalized, path, hasMountName);
}
private static Result ParseMountName(out U8Span newPath, Span<byte> 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))
{
newPath = path;
mountNameLength = 0;
return Result.Success;
}
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)
return ResultFs.TooLongPath.Log();
path.Value.Slice(0, mountEnd).CopyTo(outMountNameBuffer);
}
newPath = path.Slice(mountEnd);
mountNameLength = mountEnd - mountStart;
return Result.Success;
}
private static Result SkipMountName(out U8Span newPath, U8Span path)
{
return ParseMountName(out newPath, Span<byte>.Empty, out _, path);
}
/// <summary>
/// Checks if a path begins with / or \ and contains any of these patterns:
/// "/..\", "\..\", "\../", "\..0" where '0' is the null terminator.
/// </summary>
private static bool IsParentDirectoryPathReplacementNeeded(U8Span path)
{
if (!IsAnySeparator(path.GetOrNull(0)))
return false;
for (int i = 0; i < path.Length - 2 && path[i] != NullTerminator; i++)
{
byte c3 = path.GetOrNull(i + 3);
if (path[i] == AltDirectorySeparator &&
path[i + 1] == Dot &&
path[i + 2] == Dot &&
(IsAnySeparator(c3) || c3 == NullTerminator))
{
return true;
}
if (IsAnySeparator(path[i]) &&
path[i + 1] == Dot &&
path[i + 2] == Dot &&
c3 == AltDirectorySeparator)
{
return true;
}
}
return false;
}
private static void ReplaceParentDirectoryPath(Span<byte> dest, ReadOnlySpan<byte> source)
{
dest[0] = DirectorySeparator;
int i = 1;
while (source.Length > i && source[i] != NullTerminator)
{
if (source.Length > i + 2 &&
IsAnySeparator(source[i - 1]) &&
source[i + 0] == Dot &&
source[i + 1] == Dot &&
IsAnySeparator(source[i + 2]))
{
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;
}
public static Result Normalize(Span<byte> 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<bool>(), 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<byte>();
try
{
// Check if parent directory path replacement is needed.
if (IsParentDirectoryPathReplacementNeeded(currentPath))
{
// Allocate a buffer to hold the replacement path.
convertedPath = new RentedArray<byte>(PathTools.MaxPathLength + 1);
// Replace the path.
ReplaceParentDirectoryPath(convertedPath.Span, currentPath);
// Set current path to be the replacement path.
currentPath = new U8Span(convertedPath.Span);
}
bool 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<byte> 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<byte> 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;
}
}
}

View file

@ -0,0 +1,129 @@
using System;
using System.Diagnostics;
using LibHac.Common;
using LibHac.Diag;
using static LibHac.Fs.StringTraits;
// ReSharper disable once CheckNamespace
namespace LibHac.Fs
{
internal struct PathUtilityGlobals
{
public PathVerifier PathVerifier;
public void Initialize(FileSystemClient _)
{
PathVerifier.Initialize();
}
}
internal struct PathVerifier
{
public void Initialize()
{
// Todo
}
public static Result Verify(U8Span path, int maxPathLength, int maxNameLength)
{
Debug.Assert(!path.IsNull());
int nameLength = 0;
for (int i = 0; i < path.Length && i <= maxPathLength && nameLength <= maxNameLength; i++)
{
byte c = path[i];
if (c == 0)
return Result.Success;
// todo: Compare path based on their Unicode code points
if (c == ':' || c == '*' || c == '?' || c == '<' || c == '>' || c == '|')
return ResultFs.InvalidCharacter.Log();
nameLength++;
if (c == '\\' || c == '/')
{
nameLength = 0;
}
}
return ResultFs.TooLongPath.Log();
}
}
public static class PathUtility
{
public static void Replace(Span<byte> buffer, byte currentChar, byte newChar)
{
Assert.SdkRequiresNotNull(buffer);
for (int i = 0; i < buffer.Length; i++)
{
if (buffer[i] == currentChar)
{
buffer[i] = newChar;
}
}
}
/// <summary>
/// Performs the extra functions that nn::fs::FspPathPrintf does on the string buffer.
/// </summary>
/// <param name="builder">The string builder to process.</param>
/// <returns>The <see cref="Result"/> of the operation.</returns>
public static Result ToSfPath(in this U8StringBuilder builder)
{
if (builder.Overflowed)
return ResultFs.TooLongPath.Log();
Replace(builder.Buffer.Slice(builder.Capacity),
AltDirectorySeparator,
DirectorySeparator);
return Result.Success;
}
public static Result VerifyPath(this FileSystemClient fs, U8Span path, int maxPathLength, int maxNameLength)
{
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)
return false;
if (lhs.GetOrNull(0) == DirectorySeparator && lhs.GetOrNull(1) == NullTerminator &&
rhs.GetOrNull(0) == DirectorySeparator && rhs.GetOrNull(1) != NullTerminator)
return true;
if (rhs.GetOrNull(0) == DirectorySeparator && rhs.GetOrNull(1) == NullTerminator &&
lhs.GetOrNull(0) == DirectorySeparator && lhs.GetOrNull(1) != NullTerminator)
return true;
for (int i = 0; ; i++)
{
if (lhs.GetOrNull(i) == NullTerminator)
{
return rhs.GetOrNull(i) == DirectorySeparator;
}
else if (rhs.GetOrNull(i) == NullTerminator)
{
return lhs.GetOrNull(i) == DirectorySeparator;
}
else if (lhs.GetOrNull(i) != rhs.GetOrNull(i))
{
return false;
}
}
}
}
}

View file

@ -0,0 +1,113 @@
using System;
using System.Runtime.CompilerServices;
using LibHac.Common;
using LibHac.Diag;
using static LibHac.Fs.StringTraits;
// ReSharper disable once CheckNamespace
namespace LibHac.Fs
{
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)
{
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 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;
}
}
return 0;
}
public static bool IsDosDelimiter(char c)
{
return c == '/' || c == '\\';
}
public static bool IsDosDevicePath(ReadOnlySpan<char> path)
{
return path.Length >= DosDevicePathPrefixLength
&& IsDosDelimiter(path[0])
&& path[1] == '\\'
&& (path[2] == '.' || path[2] == '?')
&& IsDosDelimiter(path[3]);
}
public static int GetDosDevicePathPrefixLength()
{
return DosDevicePathPrefixLength;
}
public static bool IsDriveName(ReadOnlySpan<char> path)
{
return path.Length == WindowsDriveLength && path[1] == ':';
}
public static bool IsUncPath(ReadOnlySpan<char> path)
{
return !IsDosDevicePath(path) && path.Length >= UncPathPrefixLength && IsDosDelimiter(path[0]) &&
IsDosDelimiter(path[1]);
}
public static int GetUncPathPrefixLength(ReadOnlySpan<char> path)
{
int i;
for (i = 0; i < path.Length; i++)
{
if (path[i] == '\0')
break;
if (IsDosDelimiter(path[i]) && i + 1 == DosDevicePathPrefixLength)
break;
}
return i;
}
public static bool IsPathRooted(ReadOnlySpan<char> path)
{
return IsDriveName(path.Slice(0, Math.Min(WindowsDriveLength, path.Length)))
|| IsDosDevicePath(path.Slice(0, Math.Min(DosDevicePathPrefixLength, path.Length)))
|| IsUncPath(path.Slice(0, Math.Min(DosDevicePathPrefixLength, path.Length)));
}
}
}

View file

@ -25,6 +25,7 @@ namespace LibHac.Fs
public FileSystemProxyServiceObjectGlobals FileSystemProxyServiceObject;
public FsContextHandlerGlobals FsContextHandler;
public ResultHandlingUtilityGlobals ResultHandlingUtility;
public PathUtilityGlobals PathUtility;
public void Initialize(FileSystemClient fsClient, HorizonClient horizonClient)
{
@ -33,6 +34,7 @@ namespace LibHac.Fs
AccessLog.Initialize(fsClient);
UserMountTable.Initialize(fsClient);
FsContextHandler.Initialize(fsClient);
PathUtility.Initialize(fsClient);
}
}

View file

@ -5,6 +5,7 @@ using LibHac.Fs.Impl;
using LibHac.FsSystem;
using LibHac.Os;
using LibHac.Util;
using static LibHac.Fs.StringTraits;
using static LibHac.Fs.Impl.AccessLogStrings;
namespace LibHac.Fs.Fsa
@ -19,7 +20,7 @@ namespace LibHac.Fs.Fsa
int mountLen = 0;
int maxMountLen = Math.Min(path.Length, PathTools.MountNameLengthMax);
if (PathUtility.IsWindowsDrive(path) || PathUtility.IsUnc(path))
if (WindowsPath.IsWindowsDrive(path) || WindowsPath.IsUnc(path))
{
StringUtils.Copy(mountName.Name, CommonPaths.HostRootFileSystemMountName);
mountName.Name[PathTools.MountNameLengthMax] = StringTraits.NullTerminator;
@ -48,7 +49,8 @@ namespace LibHac.Fs.Fsa
U8Span subPathTemp = path.Slice(mountLen + 1);
if (subPathTemp.Length == 0 || !PathTool.IsAnySeparator(subPathTemp[0]))
if (subPathTemp.Length == 0 ||
(subPathTemp[0] != DirectorySeparator && subPathTemp[0] != AltDirectorySeparator))
return ResultFs.InvalidPathFormat.Log();
path.Value.Slice(0, mountLen).CopyTo(mountName.Name);
@ -74,7 +76,7 @@ namespace LibHac.Fs.Fsa
int length = 0;
for (int i = 0; i < name.Length && name[i] != 0; i++)
{
if (PathTool.IsDriveSeparator(name[i]) || PathTool.IsSeparator(name[i]))
if (name[i] == DriveSeparator || name[i] == DirectorySeparator)
return false;
if (++length > PathTools.MountNameLengthMax)

View file

@ -222,10 +222,10 @@ namespace LibHac.Fs.Impl
PathUtility.Replace(outPath, StringTraits.AltDirectorySeparator, StringTraits.DirectorySeparator);
// Get lengths
int windowsSkipLength = PathUtility.GetWindowsPathSkipLength(path);
int windowsSkipLength = WindowsPath.GetWindowsPathSkipLength(path);
var nonWindowsPath = new U8Span(sfPath.Str.Slice(windowsSkipLength));
int maxLength = PathTool.EntryNameLengthMax - windowsSkipLength;
return PathUtility.VerifyPath(nonWindowsPath, maxLength, maxLength);
return PathUtility.VerifyPath(null, nonWindowsPath, maxLength, maxLength);
}
}
}

View file

@ -1,6 +1,7 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using LibHac.Common;
using LibHac.Fs.Fsa;
using LibHac.FsSystem;
@ -22,15 +23,23 @@ namespace LibHac.Fs
protected override Result DoCreateDirectory(U8Span path)
{
return FsTable.AddDirectory(new U8Span(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);
}
protected override Result DoCreateDirectory(U8Span path, NxFileAttributes archiveAttribute)
{
Result rc = FsTable.AddDirectory(path);
Unsafe.SkipInit(out FsPath normalizedPath);
Result rc = PathNormalizer.Normalize(normalizedPath.Str, out _, path, false, false);
if (rc.IsFailure()) return rc;
rc = FsTable.GetDirectory(path, out DirectoryNode dir);
rc = FsTable.AddDirectory(normalizedPath);
if (rc.IsFailure()) return rc;
rc = FsTable.GetDirectory(normalizedPath, out DirectoryNode dir);
if (rc.IsFailure()) return rc;
dir.Attributes = archiveAttribute;
@ -39,10 +48,14 @@ namespace LibHac.Fs
protected override Result DoCreateFile(U8Span path, long size, CreateFileOptions options)
{
Result rc = FsTable.AddFile(path);
Unsafe.SkipInit(out FsPath normalizedPath);
Result rc = PathNormalizer.Normalize(normalizedPath.Str, out _, path, false, false);
if (rc.IsFailure()) return rc;
rc = FsTable.GetFile(path, out FileNode file);
rc = FsTable.AddFile(normalizedPath);
if (rc.IsFailure()) return rc;
rc = FsTable.GetFile(normalizedPath, out FileNode file);
if (rc.IsFailure()) return rc;
return file.File.SetSize(size);
@ -50,30 +63,50 @@ namespace LibHac.Fs
protected override Result DoDeleteDirectory(U8Span path)
{
return FsTable.DeleteDirectory(new U8Span(path), false);
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);
}
protected override Result DoDeleteDirectoryRecursively(U8Span path)
{
return FsTable.DeleteDirectory(new U8Span(path), true);
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);
}
protected override Result DoCleanDirectoryRecursively(U8Span path)
{
return FsTable.CleanDirectory(new U8Span(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);
}
protected override Result DoDeleteFile(U8Span path)
{
return FsTable.DeleteFile(new U8Span(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);
}
protected override Result DoOpenDirectory(out IDirectory directory, U8Span path, OpenDirectoryMode mode)
{
UnsafeHelpers.SkipParamInit(out directory);
Result rs = FsTable.GetDirectory(new U8Span(path), out DirectoryNode dirNode);
if (rs.IsFailure()) return rs;
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);
if (rc.IsFailure()) return rc;
directory = new MemoryDirectory(dirNode, mode);
return Result.Success;
@ -83,7 +116,11 @@ namespace LibHac.Fs
{
UnsafeHelpers.SkipParamInit(out file);
Result rc = FsTable.GetFile(path, out FileNode fileNode);
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);
if (rc.IsFailure()) return rc;
file = new MemoryFile(mode, fileNode.File);
@ -91,27 +128,49 @@ namespace LibHac.Fs
return Result.Success;
}
protected override Result DoRenameDirectory(U8Span oldPath, U8Span newPath)
protected override Result DoRenameDirectory(U8Span currentPath, U8Span newPath)
{
return FsTable.RenameDirectory(new U8Span(oldPath), new U8Span(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);
}
protected override Result DoRenameFile(U8Span oldPath, U8Span newPath)
protected override Result DoRenameFile(U8Span currentPath, U8Span newPath)
{
return FsTable.RenameFile(new U8Span(oldPath), new U8Span(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);
}
protected override Result DoGetEntryType(out DirectoryEntryType entryType, U8Span path)
{
UnsafeHelpers.SkipParamInit(out entryType);
if (FsTable.GetFile(path, out _).IsSuccess())
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())
{
entryType = DirectoryEntryType.File;
return Result.Success;
}
if (FsTable.GetDirectory(path, out _).IsSuccess())
if (FsTable.GetDirectory(normalizedPath, out _).IsSuccess())
{
entryType = DirectoryEntryType.Directory;
return Result.Success;
@ -129,13 +188,17 @@ namespace LibHac.Fs
{
UnsafeHelpers.SkipParamInit(out attributes);
if (FsTable.GetFile(path, out FileNode file).IsSuccess())
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())
{
attributes = file.Attributes;
return Result.Success;
}
if (FsTable.GetDirectory(path, out DirectoryNode dir).IsSuccess())
if (FsTable.GetDirectory(normalizedPath, out DirectoryNode dir).IsSuccess())
{
attributes = dir.Attributes;
return Result.Success;
@ -146,13 +209,17 @@ namespace LibHac.Fs
protected override Result DoSetFileAttributes(U8Span path, NxFileAttributes attributes)
{
if (FsTable.GetFile(path, out FileNode file).IsSuccess())
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())
{
file.Attributes = attributes;
return Result.Success;
}
if (FsTable.GetDirectory(path, out DirectoryNode dir).IsSuccess())
if (FsTable.GetDirectory(normalizedPath, out DirectoryNode dir).IsSuccess())
{
dir.Attributes = attributes;
return Result.Success;
@ -165,7 +232,11 @@ namespace LibHac.Fs
{
UnsafeHelpers.SkipParamInit(out fileSize);
if (FsTable.GetFile(path, out FileNode file).IsSuccess())
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())
{
return file.File.GetSize(out fileSize);
}

View file

@ -1,768 +1,9 @@
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using LibHac.Common;
using LibHac.FsSystem;
namespace LibHac.Fs
namespace LibHac.Fs
{
public static class PathTool
{
// These are kept in nn::fs, but C# requires them to be inside a class
internal const int EntryNameLengthMax = 0x300;
internal const int MountNameLengthMax = 15;
public static bool IsSeparator(byte c)
{
return c == StringTraits.DirectorySeparator;
}
public static bool IsAltSeparator(byte c)
{
return c == StringTraits.AltDirectorySeparator;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsAnySeparator(byte c)
{
return IsSeparator(c) || IsAltSeparator(c);
}
public static bool IsNullTerminator(byte c)
{
return c == StringTraits.NullTerminator;
}
public static bool IsDot(byte c)
{
return c == StringTraits.Dot;
}
public static bool IsDriveSeparator(byte c)
{
return c == StringTraits.DriveSeparator;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsCurrentDirectory(ReadOnlySpan<byte> p)
{
if ((uint)p.Length < 1) return false;
ref byte b = ref MemoryMarshal.GetReference(p);
return IsDot(b) && (p.Length == 1 || IsSeparator(Unsafe.Add(ref b, 1)) || IsNullTerminator(Unsafe.Add(ref b, 1)));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsParentDirectory(ReadOnlySpan<byte> p)
{
if ((uint)p.Length < 2) return false;
ref byte b = ref MemoryMarshal.GetReference(p);
return IsDot(b) && IsDot(Unsafe.Add(ref b, 1)) &&
(p.Length == 2 || IsSeparator(Unsafe.Add(ref b, 2)) || IsNullTerminator(Unsafe.Add(ref b, 2)));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsParentDirectoryAlt(ReadOnlySpan<byte> p)
{
if ((uint)p.Length < 3) return false;
ref byte b = ref MemoryMarshal.GetReference(p);
return IsAnySeparator(b) && IsDot(Unsafe.Add(ref b, 1)) && IsDot(Unsafe.Add(ref b, 2)) &&
(p.Length == 3 || IsAnySeparator(Unsafe.Add(ref b, 3)) || IsNullTerminator(Unsafe.Add(ref b, 3)));
}
/// <summary>
/// Checks if a path begins with / or \ and contains any of these patterns:
/// "/..\", "\..\", "\../", "\..0" where '0' is the null terminator.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool ContainsParentDirectoryAlt(U8Span path2)
{
if (!IsAnySeparator(path2.GetOrNull(0))) return false;
for (int i = 0; i < path2.Length - 2; i++)
{
byte c = path2[i];
if (IsSeparator(c) &&
IsDot(path2[i + 1]) &&
IsDot(path2[i + 2]) &&
IsAltSeparator(path2.GetOrNull(i + 3)))
{
return true;
}
if (IsAltSeparator(c) &&
IsDot(path2[i + 1]) &&
IsDot(path2[i + 2]))
{
byte c3 = path2.GetOrNull(i + 3);
if (IsNullTerminator(c3) || IsAnySeparator(c3))
{
return true;
}
}
else if (IsNullTerminator(c))
{
return false;
}
}
return false;
}
public static Result Normalize(Span<byte> outputBuffer, out long normalizedLength, U8Span path,
bool preserveUnc, bool hasMountName)
{
UnsafeHelpers.SkipParamInit(out normalizedLength);
U8Span path2 = path;
int prefixLength = 0;
bool isUncPath = false;
if (hasMountName)
{
Result rc = ParseMountName(out path2, outputBuffer, out long mountNameLength, path2);
if (rc.IsFailure()) return rc;
prefixLength += (int)mountNameLength;
}
if (preserveUnc)
{
U8Span originalPath = path2;
Result rc = ParseWindowsPath(out path2, outputBuffer.Slice(prefixLength), out long windowsPathLength,
out _, false, path2, hasMountName);
if (rc.IsFailure()) return rc;
prefixLength += (int)windowsPathLength;
if (originalPath.Value != path2.Value)
{
isUncPath = true;
}
}
if (prefixLength == 0 && !IsSeparator(path2.GetOrNull(0)))
return ResultFs.InvalidPathFormat.Log();
if (ContainsParentDirectoryAlt(path2))
{
byte[] buffer2 = new byte[PathTools.MaxPathLength + 1];
buffer2[0] = StringTraits.DirectorySeparator;
int j;
for (j = 1; j < path2.Length; j++)
{
byte c = path2[j];
if (IsNullTerminator(c))
break;
// Current char is a dot. Check the surrounding chars for the /../ pattern
if (IsDot(c) && IsParentDirectoryAlt(path2.Value.Slice(j - 1)))
{
buffer2[j - 1] = StringTraits.DirectorySeparator;
buffer2[j] = StringTraits.Dot;
buffer2[j + 1] = StringTraits.Dot;
j += 2;
if (!IsNullTerminator(path2.GetOrNull(j)))
{
buffer2[j] = StringTraits.DirectorySeparator;
}
}
else
{
buffer2[j] = c;
}
}
buffer2[j] = StringTraits.NullTerminator;
path2 = new U8Span(buffer2);
}
int i = 0;
bool skipNextSep = false;
int totalLength = prefixLength;
while (i < path2.Length && !IsNullTerminator(path2[i]))
{
if (IsSeparator(path2[i]))
{
do
{
i++;
} while (i < path2.Length && IsSeparator(path2[i]));
if (i >= path2.Length || IsNullTerminator(path2[i]))
{
break;
}
if (!skipNextSep)
{
if (totalLength + 1 == outputBuffer.Length)
{
outputBuffer[totalLength] = StringTraits.NullTerminator;
normalizedLength = totalLength;
return ResultFs.TooLongPath.Log();
}
outputBuffer[totalLength++] = StringTraits.DirectorySeparator;
}
skipNextSep = false;
}
int dirLen = 0;
while (path2.Length > i + dirLen && !IsNullTerminator(path2[i + dirLen]) && !IsSeparator(path2[i + dirLen]))
{
dirLen++;
}
if (IsCurrentDirectory(path2.Slice(i)))
{
skipNextSep = true;
}
else if (IsParentDirectory(path2.Slice(i)))
{
Debug.Assert(IsSeparator(outputBuffer[totalLength - 1]));
Debug.Assert(IsSeparator(outputBuffer[prefixLength]));
if (totalLength == prefixLength + 1)
{
if (isUncPath)
{
totalLength--;
}
else
{
return ResultFs.DirectoryUnobtainable.Log();
}
}
else
{
totalLength -= 2;
while (!IsSeparator(outputBuffer[totalLength]))
{
totalLength--;
if (totalLength == prefixLength)
{
break;
}
}
}
Debug.Assert(IsSeparator(outputBuffer[totalLength]));
Debug.Assert(totalLength < outputBuffer.Length);
}
else
{
if (totalLength + dirLen + 1 <= outputBuffer.Length)
{
for (int j = 0; j < dirLen; j++)
{
outputBuffer[totalLength++] = path2[i + j];
}
}
else
{
int copyLen = outputBuffer.Length - 1 - totalLength;
for (int j = 0; j < copyLen; j++)
{
outputBuffer[totalLength++] = path2[i + j];
}
outputBuffer[totalLength] = StringTraits.NullTerminator;
normalizedLength = totalLength;
return ResultFs.TooLongPath.Log();
}
}
i += dirLen;
}
if (skipNextSep)
totalLength--;
if (totalLength < outputBuffer.Length && totalLength == prefixLength && !isUncPath)
{
outputBuffer[prefixLength] = StringTraits.DirectorySeparator;
totalLength++;
}
if (totalLength - 1 > outputBuffer.Length)
{
return ResultFs.TooLongPath.Log();
}
outputBuffer[totalLength] = StringTraits.NullTerminator;
Debug.Assert(IsNormalized(out bool isNormalized, new U8Span(outputBuffer), preserveUnc, hasMountName).IsSuccess());
Debug.Assert(isNormalized);
normalizedLength = totalLength;
return Result.Success;
}
public static Result IsNormalized(out bool isNormalized, U8Span path, bool preserveUnc, bool hasMountName)
{
UnsafeHelpers.SkipParamInit(out isNormalized);
U8Span path2 = path;
bool isUncPath = false;
if (hasMountName)
{
Result rc = ParseMountName(out path2, Span<byte>.Empty, out _, path);
if (rc.IsFailure()) return rc;
if (path2.Length == 0 || !IsSeparator(path2[0]))
return ResultFs.InvalidPathFormat.Log();
}
if (preserveUnc)
{
U8Span originalPath = path2;
Result rc = ParseWindowsPath(out path2, Span<byte>.Empty, out _, out bool isNormalizedWin,
true, originalPath, hasMountName);
if (rc.IsFailure()) return rc;
if (!isNormalizedWin)
{
isNormalized = false;
return Result.Success;
}
// Path is a UNC path if the new path skips part of the original
isUncPath = originalPath.Value != path2.Value;
if (isUncPath)
{
if (IsSeparator(originalPath.GetOrNull(0)) && IsSeparator(originalPath.GetOrNull(1)))
{
isNormalized = false;
return Result.Success;
}
if (IsNullTerminator(path2.GetOrNull(0)))
{
isNormalized = true;
return Result.Success;
}
}
}
if (path2.IsEmpty())
return ResultFs.InvalidPathFormat.Log();
if (ContainsParentDirectoryAlt(path2))
{
isNormalized = false;
return Result.Success;
}
bool pathWasSkipped = path.Value != path2.Value;
var state = NormalizeState.Initial;
for (int i = 0; i < path2.Length; i++)
{
byte c = path2[i];
if (IsNullTerminator(c)) break;
switch (state)
{
// I don't think this first case can actually be triggered, but Nintendo has it there anyway
case NormalizeState.Initial when pathWasSkipped && IsDot(c): state = NormalizeState.Dot; break;
case NormalizeState.Initial when IsSeparator(c): state = NormalizeState.FirstSeparator; break;
case NormalizeState.Initial when !pathWasSkipped: return ResultFs.InvalidPathFormat.Log();
case NormalizeState.Initial: state = NormalizeState.Normal; break;
case NormalizeState.Normal when IsSeparator(c): state = NormalizeState.Separator; break;
case NormalizeState.FirstSeparator when IsDot(c):
case NormalizeState.Separator when IsDot(c):
state = NormalizeState.Dot;
break;
case NormalizeState.FirstSeparator when IsSeparator(c):
case NormalizeState.Separator when IsSeparator(c):
isNormalized = false;
return Result.Success;
case NormalizeState.FirstSeparator:
case NormalizeState.Separator:
state = NormalizeState.Normal;
break;
case NormalizeState.Dot when IsSeparator(c):
isNormalized = false;
return Result.Success;
case NormalizeState.Dot when IsDot(c): state = NormalizeState.DoubleDot; break;
case NormalizeState.Dot: state = NormalizeState.Normal; break;
case NormalizeState.DoubleDot when IsSeparator(c):
isNormalized = false;
return Result.Success;
case NormalizeState.DoubleDot: state = NormalizeState.Normal; 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;
}
return Result.Success;
}
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<byte> outMountNameBuffer,
out long mountNameLength, U8Span path)
{
pathAfterMount = default;
UnsafeHelpers.SkipParamInit(out mountNameLength);
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))
{
pathAfterMount = path;
mountNameLength = 0;
return Result.Success;
}
if (IsDriveSeparator(c))
{
mountEnd++;
break;
}
if (IsNullTerminator(c))
{
break;
}
}
if (mountStart >= mountEnd - 1 || !IsDriveSeparator(path[mountEnd - 1]))
return ResultFs.InvalidPathFormat.Log();
for (int i = mountStart; i < mountEnd; i++)
{
if (IsDot(path[i]))
return ResultFs.InvalidCharacter.Log();
}
if (!outMountNameBuffer.IsEmpty)
{
if (mountEnd - mountStart > outMountNameBuffer.Length)
return ResultFs.TooLongPath.Log();
path.Value.Slice(0, mountEnd).CopyTo(outMountNameBuffer);
}
pathAfterMount = path.Slice(mountEnd);
mountNameLength = mountEnd - mountStart;
return Result.Success;
}
private static Result ParseWindowsPath(out U8Span newPath, Span<byte> buffer, out long windowsPathLength,
out bool isNormalized, bool checkIfNormalized, U8Span path, bool hasMountName)
{
newPath = default;
windowsPathLength = 0;
isNormalized = checkIfNormalized;
int winPathLen = 0;
int mountNameLen = 0;
bool skippedMount = false;
bool needsSeparatorFixup = false;
if (hasMountName)
{
if (IsSeparator(path.GetOrNull(0)) && IsAltSeparator(path.GetOrNull(1)) &&
IsAltSeparator(path.GetOrNull(2)))
{
mountNameLen = 1;
skippedMount = true;
}
else
{
int separatorCount = 0;
while (IsSeparator(path.GetOrNull(separatorCount)))
{
separatorCount++;
}
if (separatorCount == 1 || PathUtility.IsWindowsDrive(path.Slice(separatorCount)))
{
mountNameLen = separatorCount;
skippedMount = true;
}
else if (separatorCount > 2 && checkIfNormalized)
{
isNormalized = false;
return Result.Success;
}
else if (separatorCount > 1)
{
mountNameLen = separatorCount - 2;
skippedMount = true;
}
}
}
else
{
if (IsSeparator(path.GetOrNull(0)) && !PathUtility.IsUnc(path))
{
mountNameLen = 1;
skippedMount = true;
}
}
U8Span pathTrimmed = path.Slice(mountNameLen);
if (PathUtility.IsWindowsDrive(pathTrimmed))
{
int i = 2;
while (!IsAnySeparator(pathTrimmed.GetOrNull(i)) && !IsNullTerminator(pathTrimmed.GetOrNull(i)))
{
i++;
}
winPathLen = mountNameLen + i;
if (!buffer.IsEmpty)
{
if (winPathLen > buffer.Length)
{
return ResultFs.TooLongPath.Log();
}
path.Value.Slice(0, winPathLen).CopyTo(buffer);
}
newPath = path.Slice(winPathLen);
windowsPathLength = winPathLen;
return Result.Success;
}
// A UNC path should be in the format "\\" host-name "\" share-name [ "\" object-name ]
if (PathUtility.IsUnc(pathTrimmed))
{
if (IsAnySeparator(pathTrimmed.GetOrNull(2)))
{
return ResultFs.InvalidPathFormat.Log();
}
int currentComponentStart = 2;
for (int i = 2; ; i++)
{
byte c = pathTrimmed.GetOrNull(i);
if (IsAnySeparator(c))
{
// Nintendo feels the need to change the '\' separators to '/'s
if (IsAltSeparator(c))
{
needsSeparatorFixup = true;
if (checkIfNormalized)
{
isNormalized = false;
return Result.Success;
}
}
// make sure share-name is not empty
if (currentComponentStart == 2 && IsSeparator(pathTrimmed.GetOrNull(i + 1)))
{
return ResultFs.InvalidPathFormat.Log();
}
int componentLength = i - currentComponentStart;
// neither host-name nor share-name can be "." or ".."
if (componentLength == 1 && IsDot(pathTrimmed[currentComponentStart]))
{
return ResultFs.InvalidPathFormat.Log();
}
if (componentLength == 2 && IsDot(pathTrimmed[currentComponentStart]) &&
IsDot(pathTrimmed[currentComponentStart + 1]))
{
return ResultFs.InvalidPathFormat.Log();
}
// If we're currently processing the share-name path component
if (currentComponentStart != 2)
{
winPathLen = mountNameLen + i;
break;
}
currentComponentStart = i + 1;
}
else if (c == (byte)'$' || IsDriveSeparator(c))
{
// '$' and ':' are not allowed in the host-name path component
if (currentComponentStart == 2)
{
return ResultFs.InvalidCharacter.Log();
}
// A '$' or ':' must be the last character in share-name
byte nextChar = pathTrimmed.GetOrNull(i + 1);
if (!IsSeparator(nextChar) && !IsNullTerminator(nextChar))
{
return ResultFs.InvalidPathFormat.Log();
}
winPathLen = mountNameLen + i + 1;
break;
}
else if (IsNullTerminator(c))
{
if (currentComponentStart != 2)
{
int componentLength = i - currentComponentStart;
// neither host-name nor share-name can be "." or ".."
if (componentLength == 1 && IsDot(pathTrimmed[currentComponentStart]))
{
return ResultFs.InvalidPathFormat.Log();
}
if (componentLength == 2 && IsDot(pathTrimmed[currentComponentStart]) &&
IsDot(pathTrimmed[currentComponentStart + 1]))
{
return ResultFs.InvalidPathFormat.Log();
}
winPathLen = mountNameLen + i;
}
break;
}
}
if (!buffer.IsEmpty)
{
if (winPathLen - mountNameLen > buffer.Length)
{
return ResultFs.TooLongPath.Log();
}
int outPos = 0;
if (skippedMount)
{
buffer[0] = StringTraits.DirectorySeparator;
outPos++;
}
pathTrimmed.Value.Slice(0, winPathLen - mountNameLen).CopyTo(buffer.Slice(outPos));
buffer[outPos] = StringTraits.AltDirectorySeparator;
buffer[outPos + 1] = StringTraits.AltDirectorySeparator;
if (needsSeparatorFixup)
{
for (int i = mountNameLen + 2; i < winPathLen; i++)
{
if (IsAltSeparator(buffer[i]))
{
buffer[i] = StringTraits.DirectorySeparator;
}
}
}
newPath = path.Slice(winPathLen);
windowsPathLength = outPos + winPathLen - mountNameLen;
return Result.Success;
}
}
newPath = path.Slice(winPathLen);
return Result.Success;
}
private enum NormalizeState
{
Initial,
Normal,
FirstSeparator,
Separator,
Dot,
DoubleDot
}
}
}

View file

@ -1,111 +0,0 @@
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using LibHac.Common;
using static LibHac.Fs.PathTool;
namespace LibHac.Fs
{
public static class PathUtility
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsWindowsDrive(U8Span path)
{
return (uint)path.Length > 1 &&
(IsDriveSeparator(path[1]) &&
IsWindowsDriveCharacter(path[0]));
}
[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 > 1 &&
(IsSeparator(path.GetUnsafe(0)) && IsSeparator(path.GetUnsafe(1)) ||
IsAltSeparator(path.GetUnsafe(0)) && IsAltSeparator(path.GetUnsafe(1)));
}
public static int GetWindowsPathSkipLength(U8Span path)
{
if (IsWindowsDrive(path))
return 2;
if (!IsUnc(path))
return 0;
for (int i = 2; i < path.Length && !IsNullTerminator(path[i]); i++)
{
byte c = path[i];
if (c == (byte)'$' || IsDriveSeparator(c))
{
return i + 1;
}
}
return 0;
}
public static Result VerifyPath(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 (IsNullTerminator(c))
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 void Replace(Span<byte> buffer, byte oldChar, byte newChar)
{
for (int i = 0; i < buffer.Length; i++)
{
if (buffer[i] == oldChar)
{
buffer[i] = newChar;
}
}
}
/// <summary>
/// Performs the extra functions that nn::fs::FspPathPrintf does on the string buffer.
/// </summary>
/// <param name="builder">The string builder to process.</param>
/// <returns>The <see cref="Result"/> of the operation.</returns>
public static Result ToSfPath(in this U8StringBuilder builder)
{
if (builder.Overflowed)
return ResultFs.TooLongPath.Log();
Replace(builder.Buffer.Slice(builder.Capacity),
StringTraits.AltDirectorySeparator,
StringTraits.DirectorySeparator);
return Result.Success;
}
}
}

View file

@ -169,7 +169,7 @@ namespace LibHac.Fs.Shim
if (pathLen > 0)
{
byte endingSeparator = PathTool.IsSeparator(rootPath[pathLen - 1])
byte endingSeparator = rootPath[pathLen - 1] == StringTraits.DirectorySeparator
? StringTraits.NullTerminator
: StringTraits.DirectorySeparator;

View file

@ -8,6 +8,7 @@ using LibHac.FsSrv.Sf;
using LibHac.FsSystem;
using LibHac.Os;
using LibHac.Util;
using static LibHac.Fs.StringTraits;
using static LibHac.Fs.Impl.AccessLogStrings;
using static LibHac.Fs.Impl.CommonMountNames;
using IFileSystem = LibHac.Fs.Fsa.IFileSystem;
@ -74,9 +75,9 @@ namespace LibHac.Fs.Shim
StringUtils.Copy(_path.Str, path);
int pathLength = StringUtils.GetLength(_path.Str);
if (pathLength != 0 && _path.Str[pathLength - 1] == StringTraits.DirectorySeparator)
if (pathLength != 0 && _path.Str[pathLength - 1] == DirectorySeparator)
{
_path.Str[pathLength - 1] = StringTraits.NullTerminator;
_path.Str[pathLength - 1] = NullTerminator;
}
}
@ -138,7 +139,7 @@ namespace LibHac.Fs.Shim
if (path.IsNull())
return ResultFs.NullptrArgument.Log();
if (PathUtility.IsWindowsDrive(mountName))
if (WindowsPath.IsWindowsDrive(mountName))
return ResultFs.InvalidMountName.Log();
if (fs.Impl.IsUsedReservedMountName(mountName))
@ -147,7 +148,7 @@ namespace LibHac.Fs.Shim
bool needsTrailingSeparator = false;
int pathLength = StringUtils.GetLength(path, PathTools.MaxPathLength + 1);
if (pathLength != 0 && PathTool.IsSeparator(path[pathLength - 1]))
if (pathLength != 0 && path[pathLength - 1] == DirectorySeparator)
{
needsTrailingSeparator = true;
pathLength++;
@ -159,22 +160,22 @@ namespace LibHac.Fs.Shim
Unsafe.SkipInit(out FsPath fullPath);
var sb = new U8StringBuilder(fullPath.Str);
sb.Append(StringTraits.DirectorySeparator).Append(path);
sb.Append(DirectorySeparator).Append(path);
if (needsTrailingSeparator)
{
sb.Append(StringTraits.DirectorySeparator);
sb.Append(DirectorySeparator);
}
if (sb.Overflowed)
return ResultFs.TooLongPath.Log();
// If the input path begins with "//", change any leading '/' characters to '\'
if (PathTool.IsSeparator(fullPath.Str[1]) && PathTool.IsSeparator(fullPath.Str[2]))
if (fullPath.Str[1] == DirectorySeparator && fullPath.Str[2] == DirectorySeparator)
{
for (int i = 1; PathTool.IsSeparator(fullPath.Str[i]); i++)
for (int i = 1; fullPath.Str[i] == DirectorySeparator; i++)
{
fullPath.Str[i] = StringTraits.AltDirectorySeparator;
fullPath.Str[i] = AltDirectorySeparator;
}
}

View file

@ -6,6 +6,7 @@ using LibHac.FsSrv.Sf;
using LibHac.Sf;
using IFileSystem = LibHac.Fs.Fsa.IFileSystem;
using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem;
using PathNormalizer = LibHac.FsSrv.Impl.PathNormalizer;
namespace LibHac.FsSrv
{
@ -112,7 +113,7 @@ namespace LibHac.FsSrv
return ResultFs.PermissionDenied.Log();
// Normalize the path
var normalizer = new PathNormalizer(rootPath, PathNormalizer.Option.AcceptEmpty);
using var normalizer = new PathNormalizer(rootPath, PathNormalizer.Option.AcceptEmpty);
if (normalizer.Result.IsFailure()) return normalizer.Result;
ReferenceCountedDisposable<IFileSystem> fs = null;

View file

@ -88,7 +88,7 @@ namespace LibHac.FsSrv.Impl
if (size < 0)
return ResultFs.InvalidSize.Log();
var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption());
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);
@ -96,7 +96,7 @@ namespace LibHac.FsSrv.Impl
public Result DeleteFile(in Path path)
{
var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption());
using var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption());
if (normalizer.Result.IsFailure()) return normalizer.Result;
return BaseFileSystem.Target.DeleteFile(normalizer.Path);
@ -104,7 +104,7 @@ namespace LibHac.FsSrv.Impl
public Result CreateDirectory(in Path path)
{
var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption());
using var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption());
if (normalizer.Result.IsFailure()) return normalizer.Result;
if (StringUtils.Compare(RootDir, normalizer.Path) == 0)
@ -115,7 +115,7 @@ namespace LibHac.FsSrv.Impl
public Result DeleteDirectory(in Path path)
{
var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption());
using var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption());
if (normalizer.Result.IsFailure()) return normalizer.Result;
if (StringUtils.Compare(RootDir, normalizer.Path) == 0)
@ -126,7 +126,7 @@ namespace LibHac.FsSrv.Impl
public Result DeleteDirectoryRecursively(in Path path)
{
var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption());
using var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption());
if (normalizer.Result.IsFailure()) return normalizer.Result;
if (StringUtils.Compare(RootDir, normalizer.Path) == 0)
@ -137,10 +137,10 @@ namespace LibHac.FsSrv.Impl
public Result RenameFile(in Path oldPath, in Path newPath)
{
var normalizerOldPath = new PathNormalizer(new U8Span(oldPath.Str), GetPathNormalizerOption());
using var normalizerOldPath = new PathNormalizer(new U8Span(oldPath.Str), GetPathNormalizerOption());
if (normalizerOldPath.Result.IsFailure()) return normalizerOldPath.Result;
var normalizerNewPath = new PathNormalizer(new U8Span(newPath.Str), GetPathNormalizerOption());
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),
@ -149,13 +149,13 @@ namespace LibHac.FsSrv.Impl
public Result RenameDirectory(in Path oldPath, in Path newPath)
{
var normalizerOldPath = new PathNormalizer(new U8Span(oldPath.Str), GetPathNormalizerOption());
using var normalizerOldPath = new PathNormalizer(new U8Span(oldPath.Str), GetPathNormalizerOption());
if (normalizerOldPath.Result.IsFailure()) return normalizerOldPath.Result;
var normalizerNewPath = new PathNormalizer(new U8Span(newPath.Str), GetPathNormalizerOption());
using var normalizerNewPath = new PathNormalizer(new U8Span(newPath.Str), GetPathNormalizerOption());
if (normalizerNewPath.Result.IsFailure()) return normalizerNewPath.Result;
if (PathTool.IsSubpath(normalizerOldPath.Path, normalizerNewPath.Path))
if (PathUtility.IsSubPath(normalizerOldPath.Path, normalizerNewPath.Path))
return ResultFs.DirectoryNotRenamable.Log();
return BaseFileSystem.Target.RenameDirectory(normalizerOldPath.Path, normalizerNewPath.Path);
@ -165,7 +165,7 @@ namespace LibHac.FsSrv.Impl
{
UnsafeHelpers.SkipParamInit(out entryType);
var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption());
using var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption());
if (normalizer.Result.IsFailure()) return normalizer.Result;
ref DirectoryEntryType type = ref Unsafe.As<uint, DirectoryEntryType>(ref entryType);
@ -178,7 +178,7 @@ namespace LibHac.FsSrv.Impl
const int maxTryCount = 2;
UnsafeHelpers.SkipParamInit(out file);
var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption());
using var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption());
if (normalizer.Result.IsFailure()) return normalizer.Result;
Result rc = Result.Success;
@ -207,7 +207,7 @@ namespace LibHac.FsSrv.Impl
const int maxTryCount = 2;
UnsafeHelpers.SkipParamInit(out directory);
var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption());
using var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption());
if (normalizer.Result.IsFailure()) return normalizer.Result;
Result rc = Result.Success;
@ -240,7 +240,7 @@ namespace LibHac.FsSrv.Impl
{
UnsafeHelpers.SkipParamInit(out freeSpace);
var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption());
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);
@ -250,7 +250,7 @@ namespace LibHac.FsSrv.Impl
{
UnsafeHelpers.SkipParamInit(out totalSpace);
var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption());
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);
@ -258,7 +258,7 @@ namespace LibHac.FsSrv.Impl
public Result CleanDirectoryRecursively(in Path path)
{
var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption());
using var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption());
if (normalizer.Result.IsFailure()) return normalizer.Result;
return BaseFileSystem.Target.CleanDirectoryRecursively(normalizer.Path);
@ -268,7 +268,7 @@ namespace LibHac.FsSrv.Impl
{
UnsafeHelpers.SkipParamInit(out timeStamp);
var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption());
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);

View file

@ -0,0 +1,106 @@
using System;
using System.Buffers;
using LibHac.Common;
using LibHac.Diag;
using LibHac.Fs;
using LibHac.Util;
namespace LibHac.FsSrv.Impl
{
public ref struct PathNormalizer
{
private readonly U8Span _path;
private byte[] _rentedArray;
public U8Span Path => _path;
public Result Result { get; }
public PathNormalizer(U8Span path, Option option)
{
if (option.HasFlag(Option.AcceptEmpty) && path.IsEmpty())
{
_path = path;
_rentedArray = null;
Result = Result.Success;
}
else
{
bool preserveUnc = option.HasFlag(Option.PreserveUnc);
bool preserveTrailingSeparator = option.HasFlag(Option.PreserveTrailingSeparator);
bool hasMountName = option.HasFlag(Option.HasMountName);
Result = Normalize(out _path, out _rentedArray, path, preserveUnc, preserveTrailingSeparator,
hasMountName);
}
}
public void Dispose()
{
if (_rentedArray is not null)
ArrayPool<byte>.Shared.Return(_rentedArray);
}
private static Result Normalize(out U8Span normalizedPath, out byte[] rentedBuffer, U8Span path,
bool preserveUnc, bool preserveTailSeparator, bool hasMountName)
{
Assert.SdkRequiresNotNullOut(out rentedBuffer);
normalizedPath = default;
rentedBuffer = null;
Result rc = Fs.PathNormalizer.IsNormalized(out bool isNormalized, path, preserveUnc, hasMountName);
if (rc.IsFailure()) return rc;
if (isNormalized)
{
normalizedPath = path;
}
else
{
byte[] buffer = null;
try
{
buffer = ArrayPool<byte>.Shared.Rent(PathTool.EntryNameLengthMax + 1);
rc = Fs.PathNormalizer.Normalize(buffer.AsSpan(0, PathTool.EntryNameLengthMax + 1),
out long normalizedLength, path, preserveUnc, hasMountName);
if (rc.IsFailure()) return rc;
// Add the tail separator if needed
if (preserveTailSeparator)
{
int pathLength = StringUtils.GetLength(path, PathTool.EntryNameLengthMax);
if (Fs.PathNormalizer.IsSeparator(path[pathLength - 1]) &&
!Fs.PathNormalizer.IsSeparator(buffer[normalizedLength - 1]))
{
Assert.SdkLess(normalizedLength, PathTool.EntryNameLengthMax);
buffer[(int)normalizedLength] = StringTraits.DirectorySeparator;
buffer[(int)normalizedLength + 1] = StringTraits.NullTerminator;
}
}
normalizedPath = new U8Span(Shared.Move(ref buffer));
}
finally
{
if (buffer is not null)
ArrayPool<byte>.Shared.Return(buffer);
}
}
return Result.Success;
}
[Flags]
public enum Option
{
None = 0,
PreserveUnc = (1 << 0),
PreserveTrailingSeparator = (1 << 1),
HasMountName = (1 << 2),
AcceptEmpty = (1 << 3)
}
}
}

View file

@ -15,6 +15,7 @@ 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;
namespace LibHac.FsSrv
{
@ -141,7 +142,7 @@ namespace LibHac.FsSrv
}
// Normalize the path to the patch file system
var patchPathNormalizer = new PathNormalizer(patchPath, GetPathNormalizerOptions(patchPath));
using var patchPathNormalizer = new PathNormalizer(patchPath, GetPathNormalizerOptions(patchPath));
if (patchPathNormalizer.Result.IsFailure()) return patchPathNormalizer.Result;
if (patchResult.IsFailure())
@ -250,7 +251,7 @@ namespace LibHac.FsSrv
bool canMountSystemDataPrivate = ac.GetAccessibilityFor(AccessibilityType.MountSystemDataPrivate).CanRead;
var normalizer = new PathNormalizer(path, GetPathNormalizerOptions(path));
using var normalizer = new PathNormalizer(path, GetPathNormalizerOptions(path));
if (normalizer.Result.IsFailure()) return normalizer.Result;
ReferenceCountedDisposable<IFileSystem> fs = null;
@ -348,7 +349,7 @@ namespace LibHac.FsSrv
rc = ServiceImpl.ResolveProgramPath(out Path programPath, programId, storageId);
if (rc.IsFailure()) return rc;
var normalizer = new PathNormalizer(programPath, GetPathNormalizerOptions(programPath));
using var normalizer = new PathNormalizer(programPath, GetPathNormalizerOptions(programPath));
if (normalizer.Result.IsFailure()) return normalizer.Result;
rc = ServiceImpl.GetRightsId(out RightsId tempRightsId, out _, normalizer.Path, programId);
@ -370,7 +371,7 @@ namespace LibHac.FsSrv
if (!programInfo.AccessControl.CanCall(OperationType.GetRightsId))
return ResultFs.PermissionDenied.Log();
var normalizer = new PathNormalizer(path, GetPathNormalizerOptions(path));
using var normalizer = new PathNormalizer(path, GetPathNormalizerOptions(path));
if (normalizer.Result.IsFailure()) return normalizer.Result;
rc = ServiceImpl.GetRightsId(out RightsId tempRightsId, out byte tempKeyGeneration, normalizer.Path,
@ -396,7 +397,7 @@ namespace LibHac.FsSrv
Result rc = ServiceImpl.ResolveRomPath(out Path romPath, programId, storageId);
if (rc.IsFailure()) return rc;
var normalizer = new PathNormalizer(romPath, GetPathNormalizerOptions(romPath));
using var normalizer = new PathNormalizer(romPath, GetPathNormalizerOptions(romPath));
if (normalizer.Result.IsFailure()) return normalizer.Result;
isHostFs = IsHostFs(romPath);
@ -501,7 +502,7 @@ namespace LibHac.FsSrv
rc = ServiceImpl.ResolveRomPath(out Path romPath, programInfo.ProgramIdValue, programInfo.StorageId);
if (rc.IsFailure()) return rc;
var normalizer = new PathNormalizer(romPath, GetPathNormalizerOptions(romPath));
using var normalizer = new PathNormalizer(romPath, GetPathNormalizerOptions(romPath));
if (normalizer.Result.IsFailure()) return normalizer.Result;
return ServiceImpl.RegisterUpdatePartition(programInfo.ProgramIdValue, normalizer.Path);
@ -623,7 +624,7 @@ namespace LibHac.FsSrv
{
// 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.PreserveTailSeparator | hostOption;
return PathNormalizer.Option.HasMountName | PathNormalizer.Option.PreserveTrailingSeparator | hostOption;
}
private bool IsHostFs(U8Span path)

View file

@ -1,75 +0,0 @@
using System;
using LibHac.Common;
using LibHac.Fs;
using LibHac.FsSystem;
using LibHac.Util;
namespace LibHac.FsSrv
{
public ref struct PathNormalizer
{
private readonly U8Span _path;
public U8Span Path => _path;
public Result Result { get; }
public PathNormalizer(U8Span path, Option option)
{
if (option.HasFlag(Option.AcceptEmpty) && path.IsEmpty())
{
_path = path;
Result = Result.Success;
}
else
{
bool preserveUnc = option.HasFlag(Option.PreserveUnc);
bool preserveTailSeparator = option.HasFlag(Option.PreserveTailSeparator);
bool hasMountName = option.HasFlag(Option.HasMountName);
Result = Normalize(out _path, path, preserveUnc, preserveTailSeparator, hasMountName);
}
}
private static Result Normalize(out U8Span normalizedPath, U8Span path, bool preserveUnc,
bool preserveTailSeparator, bool hasMountName)
{
normalizedPath = default;
Result rc = PathTool.IsNormalized(out bool isNormalized, path, preserveUnc, hasMountName);
if (rc.IsFailure()) return rc;
if (isNormalized)
{
normalizedPath = path;
}
else
{
byte[] buffer = new byte[PathTools.MaxPathLength + 1];
rc = PathTool.Normalize(buffer, out long normalizedLength, path, preserveUnc, hasMountName);
if (rc.IsFailure()) return rc;
// GetLength is capped at MaxPathLength bytes to leave room for the null terminator
if (preserveTailSeparator &&
PathTool.IsSeparator(path[StringUtils.GetLength(path, PathTools.MaxPathLength) - 1]))
{
buffer[(int)normalizedLength] = StringTraits.DirectorySeparator;
buffer[(int)normalizedLength + 1] = StringTraits.NullTerminator;
}
normalizedPath = new U8Span(buffer);
}
return Result.Success;
}
[Flags]
public enum Option
{
None = 0,
PreserveUnc = (1 << 0),
PreserveTailSeparator = (1 << 1),
HasMountName = (1 << 2),
AcceptEmpty = (1 << 3)
}
}
}

View file

@ -14,6 +14,7 @@ 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 Utility = LibHac.FsSystem.Utility;
namespace LibHac.FsSrv
@ -567,7 +568,7 @@ namespace LibHac.FsSrv
}
}
var normalizer = new PathNormalizer(SaveDataRootPath, PathNormalizer.Option.PreserveUnc);
using var normalizer = new PathNormalizer(SaveDataRootPath, PathNormalizer.Option.PreserveUnc);
if (normalizer.Result.IsFailure())
return normalizer.Result;

View file

@ -2,6 +2,7 @@
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.FsSystem;
using PathNormalizer = LibHac.FsSrv.Impl.PathNormalizer;
namespace LibHac.FsSrv
{
@ -58,13 +59,13 @@ namespace LibHac.FsSrv
if (path2.IsEmpty())
return Result.Success;
int skipLength = PathUtility.GetWindowsPathSkipLength(path2);
int skipLength = WindowsPath.GetWindowsPathSkipLength(path2);
int remainingLength = PathTools.MaxPathLength - skipLength;
Result rc = PathUtility.VerifyPath(path2.Slice(skipLength), remainingLength, remainingLength);
Result rc = PathUtility.VerifyPath(null, path2.Slice(skipLength), remainingLength, remainingLength);
if (rc.IsFailure()) return rc;
var normalizer = new PathNormalizer(path, PathNormalizer.Option.PreserveUnc);
using var normalizer = new PathNormalizer(path, PathNormalizer.Option.PreserveUnc);
return normalizer.Result;
}

View file

@ -360,7 +360,7 @@ namespace LibHac.FsSystem
StringUtils.Copy(outPath, WorkingDirectoryBytes);
outPath[outPath.Length - 1] = StringTraits.NullTerminator;
return PathTool.Normalize(outPath.Slice(2), out _, relativePath, false, false);
return PathNormalizer.Normalize(outPath.Slice(2), out _, relativePath, false, false);
}
/// <summary>

View file

@ -39,7 +39,7 @@ namespace LibHac.FsSystem
Unsafe.SkipInit(out FsPath normalizedPath);
Result rc = PathTool.Normalize(normalizedPath.Str, out _, path, false, false);
Result rc = PathNormalizer.Normalize(normalizedPath.Str, out _, path, false, false);
if (rc.IsFailure()) return rc;
fullPath = PathTools.Combine(BasePath, normalizedPath.ToString());
@ -51,13 +51,13 @@ namespace LibHac.FsSystem
Unsafe.SkipInit(out FsPath normalizedPath1);
Unsafe.SkipInit(out FsPath normalizedPath2);
Result rc = PathTool.Normalize(normalizedPath1.Str, out _, path1, false, false);
Result rc = PathNormalizer.Normalize(normalizedPath1.Str, out _, path1, false, false);
if (rc.IsFailure()) return rc;
rc = PathTool.Normalize(normalizedPath2.Str, out _, path2, false, false);
rc = PathNormalizer.Normalize(normalizedPath2.Str, out _, path2, false, false);
if (rc.IsFailure()) return rc;
if (PathTool.IsSubpath(normalizedPath1, normalizedPath2))
if (PathUtility.IsSubPath(normalizedPath1, normalizedPath2))
{
return ResultFs.DirectoryNotRenamable.Log();
}

View file

@ -35,7 +35,7 @@ namespace LibHac.FsSystem.Save
{
Unsafe.SkipInit(out FsPath normalizedPath);
Result rc = PathTool.Normalize(normalizedPath.Str, out _, path, false, false);
Result rc = PathNormalizer.Normalize(normalizedPath.Str, out _, path, false, false);
if (rc.IsFailure()) return rc;
FileTable.AddDirectory(normalizedPath);
@ -47,7 +47,7 @@ namespace LibHac.FsSystem.Save
{
Unsafe.SkipInit(out FsPath normalizedPath);
Result rc = PathTool.Normalize(normalizedPath.Str, out _, path, false, false);
Result rc = PathNormalizer.Normalize(normalizedPath.Str, out _, path, false, false);
if (rc.IsFailure()) return rc;
if (size == 0)
@ -77,7 +77,7 @@ namespace LibHac.FsSystem.Save
{
Unsafe.SkipInit(out FsPath normalizedPath);
Result rc = PathTool.Normalize(normalizedPath.Str, out _, path, false, false);
Result rc = PathNormalizer.Normalize(normalizedPath.Str, out _, path, false, false);
if (rc.IsFailure()) return rc;
FileTable.DeleteDirectory(normalizedPath);
@ -89,7 +89,7 @@ namespace LibHac.FsSystem.Save
{
Unsafe.SkipInit(out FsPath normalizedPath);
Result rc = PathTool.Normalize(normalizedPath.Str, out _, path, false, false);
Result rc = PathNormalizer.Normalize(normalizedPath.Str, out _, path, false, false);
if (rc.IsFailure()) return rc;
rc = CleanDirectoryRecursively(normalizedPath);
@ -105,7 +105,7 @@ namespace LibHac.FsSystem.Save
{
Unsafe.SkipInit(out FsPath normalizedPath);
Result rc = PathTool.Normalize(normalizedPath.Str, out _, path, false, false);
Result rc = PathNormalizer.Normalize(normalizedPath.Str, out _, path, false, false);
if (rc.IsFailure()) return rc;
FileSystemExtensions.CleanDirectoryRecursivelyGeneric(this, normalizedPath.ToString());
@ -117,7 +117,7 @@ namespace LibHac.FsSystem.Save
{
Unsafe.SkipInit(out FsPath normalizedPath);
Result rc = PathTool.Normalize(normalizedPath.Str, out _, path, false, false);
Result rc = PathNormalizer.Normalize(normalizedPath.Str, out _, path, false, false);
if (rc.IsFailure()) return rc;
if (!FileTable.TryOpenFile(normalizedPath, out SaveFileInfo fileInfo))
@ -141,7 +141,7 @@ namespace LibHac.FsSystem.Save
Unsafe.SkipInit(out FsPath normalizedPath);
Result rc = PathTool.Normalize(normalizedPath.Str, out _, path, false, false);
Result rc = PathNormalizer.Normalize(normalizedPath.Str, out _, path, false, false);
if (rc.IsFailure()) return rc;
if (!FileTable.TryOpenDirectory(normalizedPath, out SaveFindPosition position))
@ -160,7 +160,7 @@ namespace LibHac.FsSystem.Save
Unsafe.SkipInit(out FsPath normalizedPath);
Result rc = PathTool.Normalize(normalizedPath.Str, out _, path, false, false);
Result rc = PathNormalizer.Normalize(normalizedPath.Str, out _, path, false, false);
if (rc.IsFailure()) return rc;
if (!FileTable.TryOpenFile(normalizedPath, out SaveFileInfo fileInfo))
@ -180,10 +180,10 @@ namespace LibHac.FsSystem.Save
Unsafe.SkipInit(out FsPath normalizedCurrentPath);
Unsafe.SkipInit(out FsPath normalizedNewPath);
Result rc = PathTool.Normalize(normalizedCurrentPath.Str, out _, oldPath, false, false);
Result rc = PathNormalizer.Normalize(normalizedCurrentPath.Str, out _, oldPath, false, false);
if (rc.IsFailure()) return rc;
rc = PathTool.Normalize(normalizedNewPath.Str, out _, newPath, false, false);
rc = PathNormalizer.Normalize(normalizedNewPath.Str, out _, newPath, false, false);
if (rc.IsFailure()) return rc;
return FileTable.RenameDirectory(normalizedCurrentPath, normalizedNewPath);
@ -194,10 +194,10 @@ namespace LibHac.FsSystem.Save
Unsafe.SkipInit(out FsPath normalizedCurrentPath);
Unsafe.SkipInit(out FsPath normalizedNewPath);
Result rc = PathTool.Normalize(normalizedCurrentPath.Str, out _, oldPath, false, false);
Result rc = PathNormalizer.Normalize(normalizedCurrentPath.Str, out _, oldPath, false, false);
if (rc.IsFailure()) return rc;
rc = PathTool.Normalize(normalizedNewPath.Str, out _, newPath, false, false);
rc = PathNormalizer.Normalize(normalizedNewPath.Str, out _, newPath, false, false);
if (rc.IsFailure()) return rc;
FileTable.RenameFile(normalizedCurrentPath, normalizedNewPath);
@ -211,7 +211,7 @@ namespace LibHac.FsSystem.Save
Unsafe.SkipInit(out FsPath normalizedPath);
Result rc = PathTool.Normalize(normalizedPath.Str, out _, path, false, false);
Result rc = PathNormalizer.Normalize(normalizedPath.Str, out _, path, false, false);
if (rc.IsFailure()) return rc;
if (FileTable.TryOpenFile(normalizedPath, out SaveFileInfo _))

View file

@ -61,11 +61,11 @@ namespace LibHac.FsSystem
Span<byte> normalizedPath = stackalloc byte[PathTools.MaxPathLength + 2];
Result rc = PathTool.Normalize(normalizedPath, out long normalizedPathLen, rootPath, PreserveUnc, false);
Result rc = PathNormalizer.Normalize(normalizedPath, out long normalizedPathLen, rootPath, PreserveUnc, false);
if (rc.IsFailure()) return rc;
// Ensure a trailing separator
if (!PathTool.IsSeparator(normalizedPath[(int)normalizedPathLen - 1]))
if (!PathNormalizer.IsSeparator(normalizedPath[(int)normalizedPathLen - 1]))
{
Debug.Assert(normalizedPathLen + 2 <= normalizedPath.Length);
@ -90,7 +90,7 @@ namespace LibHac.FsSystem
RootPath.Value.CopyTo(outPath);
// Copy the normalized relative path to the output
return PathTool.Normalize(outPath.Slice(RootPath.Length - 2), out _, relativePath, PreserveUnc, false);
return PathNormalizer.Normalize(outPath.Slice(RootPath.Length - 2), out _, relativePath, PreserveUnc, false);
}
protected override Result DoCreateDirectory(U8Span path)

View file

@ -28,7 +28,7 @@ namespace LibHac.FsSystem
// Copy root path in, add a / if necessary.
rootPath.Value.Slice(0, rootPathLen).CopyTo(workPath);
if (!PathTool.IsSeparator(workPath[rootPathLen - 1]))
if (workPath[rootPathLen - 1] != StringTraits.DirectorySeparator)
{
workPath[rootPathLen++] = StringTraits.DirectorySeparator;
}
@ -158,7 +158,7 @@ namespace LibHac.FsSystem
// Find previous separator, add null terminator
int cur = len - 2;
while (!PathTool.IsSeparator(SpanHelpers.AsByteSpan(ref destPathBuf)[cur]) && cur > 0)
while (SpanHelpers.AsByteSpan(ref destPathBuf)[cur] != StringTraits.DirectorySeparator && cur > 0)
{
cur--;
}

View file

@ -6,7 +6,7 @@ namespace LibHac.Tests
{
public override void Fail(string message, string detailMessage)
{
// throw new System.NotImplementedException(message + detailMessage);
throw new LibHacException(message + detailMessage);
}
}
}

View file

@ -15,13 +15,24 @@ namespace nn::fs::PathTool {
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
// 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);
@ -67,7 +78,7 @@ void CreateNormalizeTestItem(char const* path, bool preserveUnc, bool hasMountNa
//svcOutputDebugString(path, strnlen(path, 0x200));
nn::Result result = nn::fs::PathTool::Normalize(normalized, &normalizedLen, path, 0x200, preserveUnc, hasMountName);
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";
@ -78,7 +89,7 @@ void CreateNormalizeTestItem(char const* path, bool preserveUnc, bool hasMountNa
void CreateIsNormalizedTestItem(char const* path, bool preserveUnc, bool hasMountName) {
bool isNormalized = false;
nn::Result result = nn::fs::PathTool::IsNormalized(&isNormalized, path, preserveUnc, hasMountName);
nn::Result result = nn::fs::PathNormalizer::IsNormalized(&isNormalized, path, preserveUnc, hasMountName);
const char* preserveUncStr = preserveUnc ? "true" : "false";
const char* hasMountNameStr = hasMountName ? "true" : "false";
@ -88,7 +99,7 @@ void CreateIsNormalizedTestItem(char const* path, bool preserveUnc, bool hasMoun
}
void CreateIsSubpathTestItem(const char* path1, const char* path2) {
bool result = nn::fs::PathTool::IsSubpath(path1, path2);
bool result = nn::fs::IsSubPath(path1, path2);
const char* resultStr = result ? "true" : "false";
BufPos += sprintf(&Buf[BufPos], "new object[] {@\"%s\", @\"%s\", %s},\n",

View file

@ -14,7 +14,7 @@ namespace LibHac.Tests.Fs
{
byte[] buffer = new byte[0x301];
Result result = PathTool.Normalize(buffer, out long normalizedLength, path.ToU8Span(), preserveUnc,
Result result = PathNormalizer.Normalize(buffer, out long normalizedLength, path.ToU8Span(), preserveUnc,
hasMountName);
Assert.Equal(expectedResult, result);
@ -27,7 +27,7 @@ namespace LibHac.Tests.Fs
public static void IsNormalized(string path, bool preserveUnc, bool hasMountName, bool expectedIsNormalized,
Result expectedResult)
{
Result result = PathTool.IsNormalized(out bool isNormalized, path.ToU8Span(), preserveUnc, hasMountName);
Result result = PathNormalizer.IsNormalized(out bool isNormalized, path.ToU8Span(), preserveUnc, hasMountName);
Assert.Equal(expectedResult, result);
@ -39,9 +39,9 @@ namespace LibHac.Tests.Fs
[Theory]
[MemberData(nameof(SubpathTestItems))]
public static void IsSubpath(string path1, string path2, bool expectedResult)
public static void IsSubPath(string path1, string path2, bool expectedResult)
{
bool result = PathTool.IsSubpath(path1.ToU8Span(), path2.ToU8Span());
bool result = PathUtility.IsSubPath(path1.ToU8Span(), path2.ToU8Span());
Assert.Equal(expectedResult, result);
}

View file

@ -1,7 +1,7 @@
using LibHac.Common;
using LibHac.Fs;
using LibHac.FsSrv;
using Xunit;
using PathNormalizer = LibHac.FsSrv.Impl.PathNormalizer;
namespace LibHac.Tests.FsSrv
{
@ -10,7 +10,7 @@ namespace LibHac.Tests.FsSrv
[Fact]
public static void Ctor_EmptyPathWithAcceptEmptyOption_ReturnsEmptyPathWithSuccess()
{
var normalizer = new PathNormalizer("".ToU8Span(), PathNormalizer.Option.AcceptEmpty);
using var normalizer = new PathNormalizer("".ToU8Span(), PathNormalizer.Option.AcceptEmpty);
Assert.Equal(Result.Success, normalizer.Result);
Assert.True(normalizer.Path.IsEmpty());
@ -19,7 +19,7 @@ namespace LibHac.Tests.FsSrv
[Fact]
public static void Normalize_PreserveTailSeparatorOption_KeepsExistingTailSeparator()
{
var normalizer = new PathNormalizer("/a/./b/".ToU8Span(), PathNormalizer.Option.PreserveTailSeparator);
using var normalizer = new PathNormalizer("/a/./b/".ToU8Span(), PathNormalizer.Option.PreserveTrailingSeparator);
Assert.Equal(Result.Success, normalizer.Result);
Assert.Equal("/a/b/", normalizer.Path.ToString());
@ -28,7 +28,7 @@ namespace LibHac.Tests.FsSrv
[Fact]
public static void Normalize_PreserveTailSeparatorOption_IgnoresMissingTailSeparator()
{
var normalizer = new PathNormalizer("/a/./b".ToU8Span(), PathNormalizer.Option.PreserveTailSeparator);
using var normalizer = new PathNormalizer("/a/./b".ToU8Span(), PathNormalizer.Option.PreserveTrailingSeparator);
Assert.Equal(Result.Success, normalizer.Result);
Assert.Equal("/a/b", normalizer.Path.ToString());
@ -38,7 +38,7 @@ namespace LibHac.Tests.FsSrv
public static void Normalize_PathAlreadyNormalized_ReturnsSameBuffer()
{
var originalPath = "/a/b".ToU8Span();
var normalizer = new PathNormalizer(originalPath, PathNormalizer.Option.PreserveTailSeparator);
using var normalizer = new PathNormalizer(originalPath, PathNormalizer.Option.PreserveTrailingSeparator);
Assert.Equal(Result.Success, normalizer.Result);
@ -49,7 +49,7 @@ namespace LibHac.Tests.FsSrv
[Fact]
public static void Normalize_PreserveUncOptionOn_PreservesUncPath()
{
var normalizer = new PathNormalizer("//aa/bb/..".ToU8Span(), PathNormalizer.Option.PreserveUnc);
using var normalizer = new PathNormalizer("//aa/bb/..".ToU8Span(), PathNormalizer.Option.PreserveUnc);
Assert.Equal(Result.Success, normalizer.Result);
Assert.Equal(@"\\aa/bb", normalizer.Path.ToString());
@ -58,7 +58,7 @@ namespace LibHac.Tests.FsSrv
[Fact]
public static void Normalize_PreserveUncOptionOff_DoesNotPreserveUncPath()
{
var normalizer = new PathNormalizer("//aa/bb/..".ToU8Span(), PathNormalizer.Option.None);
using var normalizer = new PathNormalizer("//aa/bb/..".ToU8Span(), PathNormalizer.Option.None);
Assert.Equal(Result.Success, normalizer.Result);
Assert.Equal(@"/aa", normalizer.Path.ToString());
@ -67,7 +67,7 @@ namespace LibHac.Tests.FsSrv
[Fact]
public static void Normalize_MountNameOptionOn_ParsesMountName()
{
var normalizer = new PathNormalizer("mount:/a/./b".ToU8Span(), PathNormalizer.Option.HasMountName);
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());
@ -76,7 +76,7 @@ namespace LibHac.Tests.FsSrv
[Fact]
public static void Normalize_MountNameOptionOff_DoesNotParseMountName()
{
var normalizer = new PathNormalizer("mount:/a/./b".ToU8Span(), PathNormalizer.Option.None);
using var normalizer = new PathNormalizer("mount:/a/./b".ToU8Span(), PathNormalizer.Option.None);
Assert.Equal(ResultFs.InvalidPathFormat.Value, normalizer.Result);
}

View file

@ -1,4 +1,6 @@
using System.Diagnostics;
using System.Linq;
using LibHac.Diag;
using LibHac.Tests;
using Xunit.Abstractions;
using Xunit.Sdk;
@ -16,8 +18,22 @@ namespace LibHac.Tests
SetResultNames();
}
// Todo: Catch assertions in PathToolTestGenerator.cpp
private static readonly string[] SkipAbortFunctions = { "Normalize" };
private static void SetDebugHandler()
{
AssertionFailureHandler handler = (in AssertionInfo info) =>
{
if (SkipAbortFunctions.Contains(info.FunctionName))
{
return AssertionFailureOperation.Continue;
}
return AssertionFailureOperation.Abort;
};
Assert.SetAssertionFailureHandler(handler);
Trace.Listeners.Clear();
Trace.Listeners.Add(new DebugAssertHandler());
}