Add Utf8StringUtil and update MountUtility for 13.1.0

This commit is contained in:
Alex Barney 2022-01-05 14:05:10 -07:00
parent 123fc0655d
commit d412c15387
2 changed files with 200 additions and 7 deletions

View file

@ -1,16 +1,35 @@
using System; using System;
using System.Runtime.CompilerServices;
using LibHac.Common; using LibHac.Common;
using LibHac.Diag; using LibHac.Diag;
using LibHac.Fs.Impl; using LibHac.Fs.Impl;
using LibHac.Fs.Shim;
using LibHac.Os; using LibHac.Os;
using LibHac.Util; using LibHac.Util;
using static LibHac.Fs.StringTraits;
using static LibHac.Fs.Impl.AccessLogStrings; using static LibHac.Fs.Impl.AccessLogStrings;
using static LibHac.Fs.StringTraits;
namespace LibHac.Fs.Fsa; namespace LibHac.Fs.Fsa;
/// <summary>
/// Contains functions for managing mounted file systems.
/// </summary>
/// <remarks>Based on FS 13.1.0 (nnSdk 13.4.0)</remarks>
public static class MountUtility public static class MountUtility
{ {
/// <summary>
/// Gets the mount name and non-mounted path components from a path that has a mount name.
/// </summary>
/// <param name="mountName">If the method returns successfully, contains the mount name of the provided path;
/// otherwise the contents are undefined.</param>
/// <param name="subPath">If the method returns successfully, contains the provided path without the
/// mount name; otherwise the contents are undefined.</param>
/// <param name="path">The <see cref="Path"/> to process.</param>
/// <returns><see cref="Result.Success"/>: The operation was successful.<br/>
/// <see cref="ResultFs.InvalidPathFormat"/>: <paramref name="path"/> does not contain a sub path after
/// the mount name that begins with <c>/</c> or <c>\</c>.<br/>
/// <see cref="ResultFs.InvalidMountName"/>: <paramref name="path"/> contains an invalid mount name
/// or does not have a mount name.</returns>
private static Result GetMountNameAndSubPath(out MountName mountName, out U8Span subPath, U8Span path) private static Result GetMountNameAndSubPath(out MountName mountName, out U8Span subPath, U8Span path)
{ {
UnsafeHelpers.SkipParamInit(out mountName); UnsafeHelpers.SkipParamInit(out mountName);
@ -82,8 +101,7 @@ public static class MountUtility
return false; return false;
} }
// Todo: VerifyUtf8String return Utf8StringUtil.VerifyUtf8String(name);
return true;
} }
public static bool IsUsedReservedMountName(this FileSystemClientImpl fs, U8Span name) public static bool IsUsedReservedMountName(this FileSystemClientImpl fs, U8Span name)
@ -144,7 +162,12 @@ public static class MountUtility
if (fileSystem.IsFileDataCacheAttachable()) if (fileSystem.IsFileDataCacheAttachable())
{ {
// Todo: Data cache purge using var fileDataCacheAccessor = new GlobalFileDataCacheAccessorReadableScopedPointer();
if (fs.TryGetGlobalFileDataCacheAccessor(ref Unsafe.AsRef(in fileDataCacheAccessor)))
{
fileSystem.PurgeFileDataCache(fileDataCacheAccessor.Get());
}
} }
fs.Unregister(mountName); fs.Unregister(mountName);
@ -226,13 +249,23 @@ public static class MountUtility
public static Result ConvertToFsCommonPath(this FileSystemClient fs, U8SpanMutable commonPathBuffer, public static Result ConvertToFsCommonPath(this FileSystemClient fs, U8SpanMutable commonPathBuffer,
U8Span path) U8Span path)
{ {
Result rc;
if (commonPathBuffer.IsNull()) if (commonPathBuffer.IsNull())
return ResultFs.NullptrArgument.Log(); {
rc = ResultFs.NullptrArgument.Value;
fs.Impl.AbortIfNeeded(rc);
return rc;
}
if (path.IsNull()) if (path.IsNull())
return ResultFs.NullptrArgument.Log(); {
rc = ResultFs.NullptrArgument.Value;
fs.Impl.AbortIfNeeded(rc);
return rc;
}
Result rc = GetMountNameAndSubPath(out MountName mountName, out U8Span subPath, path); rc = GetMountNameAndSubPath(out MountName mountName, out U8Span subPath, path);
fs.Impl.AbortIfNeeded(rc); fs.Impl.AbortIfNeeded(rc);
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;

View file

@ -0,0 +1,160 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using LibHac.Common;
using LibHac.Diag;
namespace LibHac.Util;
/// <summary>
/// Contains functions for verifying and copying UTF-8 strings.
/// </summary>
/// <remarks>Based on nnSdk 13.4.0</remarks>
public static class Utf8StringUtil
{
private static ReadOnlySpan<byte> CodePointByteLengthTable => new byte[]
{
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
public static bool VerifyUtf8String(U8Span str)
{
return GetCodePointCountOfUtf8String(str) != -1;
}
public static int GetCodePointCountOfUtf8String(U8Span str)
{
Assert.SdkRequiresGreater(str.Length, 0);
ReadOnlySpan<byte> currentStr = str.Value;
int codePointCount = 0;
while (currentStr.Length != 0)
{
int codePointByteLength = GetCodePointByteLength(currentStr[0]);
if (codePointByteLength > currentStr.Length)
return -1;
if (!VerifyCode(currentStr.Slice(0, codePointByteLength)))
return -1;
currentStr = currentStr.Slice(codePointByteLength);
codePointCount++;
}
return codePointCount;
}
public static int CopyUtf8String(Span<byte> output, ReadOnlySpan<byte> input, int maxCount)
{
Assert.SdkRequiresGreater(output.Length, 0);
Assert.SdkRequiresGreater(input.Length, 0);
Assert.SdkRequiresGreater(maxCount, 0);
ReadOnlySpan<byte> currentInput = input;
int remainingCount = maxCount;
while (remainingCount > 0 && currentInput.Length != 0)
{
// Verify the current code point
int codePointLength = GetCodePointByteLength(currentInput[0]);
if (codePointLength > currentInput.Length)
break;
if (!VerifyCode(currentInput.Slice(0, codePointLength)))
break;
// Ensure the output is large enough to hold the additional code point
int currentOutputLength =
Unsafe.ByteOffset(ref MemoryMarshal.GetReference(input), ref MemoryMarshal.GetReference(currentInput))
.ToInt32() + codePointLength;
if (currentOutputLength + 1 > output.Length)
break;
// Advance to the next code point
currentInput = currentInput.Slice(codePointLength);
remainingCount--;
}
// Copy the valid UTF-8 to the output buffer
int byteLength = Unsafe
.ByteOffset(ref MemoryMarshal.GetReference(input), ref MemoryMarshal.GetReference(currentInput)).ToInt32();
Assert.SdkAssert(byteLength + 1 <= output.Length);
if (byteLength != 0)
input.Slice(0, byteLength).CopyTo(output);
output[byteLength] = 0;
return byteLength;
}
private static int GetCodePointByteLength(byte head)
{
return CodePointByteLengthTable[head];
}
private static bool IsValidTail(byte tail)
{
return (tail & 0xC0) == 0x80;
}
private static bool VerifyCode(ReadOnlySpan<byte> str)
{
if (str.Length == 1)
return true;
switch (str.Length)
{
case 2:
if (!IsValidTail(str[1]))
return false;
break;
case 3:
if (str[0] == 0xE0 && (str[1] & 0x20) == 0)
return false;
if (str[0] == 0xED && (str[1] & 0x20) != 0)
return false;
if (!IsValidTail(str[1]) || !IsValidTail(str[2]))
return false;
break;
case 4:
if (str[0] == 0xF0 && (str[1] & 0x30) == 0)
return false;
if (str[0] == 0xFD && (str[1] & 0x30) != 0)
return false;
if (!IsValidTail(str[1]) || !IsValidTail(str[2]) || !IsValidTail(str[3]))
return false;
break;
default:
return false;
}
return true;
}
}