mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
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:
parent
1acdd86e27
commit
0c255e0f49
28 changed files with 1360 additions and 1054 deletions
796
src/LibHac/Fs/Common/PathNormalizer.cs
Normal file
796
src/LibHac/Fs/Common/PathNormalizer.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
129
src/LibHac/Fs/Common/PathUtility.cs
Normal file
129
src/LibHac/Fs/Common/PathUtility.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
113
src/LibHac/Fs/Common/WindowsPath.cs
Normal file
113
src/LibHac/Fs/Common/WindowsPath.cs
Normal 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)));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
106
src/LibHac/FsSrv/Impl/PathNormalizer.cs
Normal file
106
src/LibHac/FsSrv/Impl/PathNormalizer.cs
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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 _))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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--;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue