Make FileSystemProxy implement IFileSystemProxy

This commit is contained in:
Alex Barney 2019-09-23 18:37:23 -05:00
parent fd40b2fd77
commit c89bc1c706
8 changed files with 499 additions and 76 deletions

View file

@ -33,6 +33,18 @@ namespace LibHac.Common
return i; return i;
} }
public static int GetLength(ReadOnlySpan<byte> s, int maxLen)
{
int i = 0;
while (i < maxLen && i < s.Length && s[i] != 0)
{
i++;
}
return i;
}
/// <summary> /// <summary>
/// Concatenates 2 byte strings. /// Concatenates 2 byte strings.
/// </summary> /// </summary>

View file

@ -20,7 +20,7 @@ namespace LibHac.Fs
Result rc = MountHelpers.CheckMountNameAcceptingReservedMountName(mountName); Result rc = MountHelpers.CheckMountNameAcceptingReservedMountName(mountName);
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
FileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject(); IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
rc = fsProxy.OpenContentStorageFileSystem(out IFileSystem contentFs, storageId); rc = fsProxy.OpenContentStorageFileSystem(out IFileSystem contentFs, storageId);
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;

View file

@ -11,7 +11,7 @@ namespace LibHac.Fs
Result rc = MountHelpers.CheckMountName(mountName); Result rc = MountHelpers.CheckMountName(mountName);
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
FileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject(); IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
rc = fsProxy.OpenCustomStorageFileSystem(out IFileSystem customFs, storageId); rc = fsProxy.OpenCustomStorageFileSystem(out IFileSystem customFs, storageId);
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;

View file

@ -6,7 +6,7 @@ namespace LibHac.Fs
public partial class FileSystemClient public partial class FileSystemClient
{ {
private FileSystemServer FsSrv { get; } private FileSystemServer FsSrv { get; }
private FileSystemProxy FsProxy { get; set; } private IFileSystemProxy FsProxy { get; set; }
private FileSystemManager FsManager { get; } private FileSystemManager FsManager { get; }
private readonly object _fspInitLocker = new object(); private readonly object _fspInitLocker = new object();
@ -17,7 +17,7 @@ namespace LibHac.Fs
FsManager = new FileSystemManager(timer); FsManager = new FileSystemManager(timer);
} }
public FileSystemProxy GetFileSystemProxyServiceObject() public IFileSystemProxy GetFileSystemProxyServiceObject()
{ {
if (FsProxy != null) return FsProxy; if (FsProxy != null) return FsProxy;

View file

@ -2,10 +2,12 @@
using LibHac.Common; using LibHac.Common;
using LibHac.Fs; using LibHac.Fs;
using LibHac.FsSystem; using LibHac.FsSystem;
using LibHac.Ncm;
using LibHac.Spl;
namespace LibHac.FsService namespace LibHac.FsService
{ {
public class FileSystemProxy public class FileSystemProxy : IFileSystemProxy
{ {
private FileSystemProxyCore FsProxyCore { get; } private FileSystemProxyCore FsProxyCore { get; }
@ -17,7 +19,7 @@ namespace LibHac.FsService
public long SaveDataSize { get; private set; } public long SaveDataSize { get; private set; }
public long SaveDataJournalSize { get; private set; } public long SaveDataJournalSize { get; private set; }
public string SaveDataRootPath { get; private set; } public FsPath SaveDataRootPath { get; } = default;
public bool AutoCreateSaveData { get; private set; } public bool AutoCreateSaveData { get; private set; }
private const ulong SaveIndexerId = 0x8000000000000000; private const ulong SaveIndexerId = 0x8000000000000000;
@ -30,10 +32,19 @@ namespace LibHac.FsService
CurrentProcess = -1; CurrentProcess = -1;
SaveDataSize = 0x2000000; SaveDataSize = 0x2000000;
SaveDataJournalSize = 0x1000000; SaveDataJournalSize = 0x1000000;
SaveDataRootPath = string.Empty;
AutoCreateSaveData = true; AutoCreateSaveData = true;
} }
public Result OpenFileSystemWithId(out IFileSystem fileSystem, ref FsPath path, TitleId titleId, FileSystemType type)
{
throw new NotImplementedException();
}
public Result OpenFileSystemWithPatch(out IFileSystem fileSystem, TitleId titleId, FileSystemType type)
{
throw new NotImplementedException();
}
public Result SetCurrentProcess(long processId) public Result SetCurrentProcess(long processId)
{ {
CurrentProcess = processId; CurrentProcess = processId;
@ -41,104 +52,100 @@ namespace LibHac.FsService
return Result.Success; return Result.Success;
} }
public Result DisableAutoSaveDataCreation() public Result GetFreeSpaceSizeForSaveData(out long freeSpaceSize, SaveDataSpaceId spaceId)
{ {
AutoCreateSaveData = false; throw new NotImplementedException();
return Result.Success;
} }
public Result SetSaveDataSize(long saveDataSize, long saveDataJournalSize) public Result OpenDataFileSystemByCurrentProcess(out IFileSystem fileSystem)
{ {
if (saveDataSize < 0 || saveDataJournalSize < 0) throw new NotImplementedException();
{
return ResultFs.InvalidSize;
}
SaveDataSize = saveDataSize;
SaveDataJournalSize = saveDataJournalSize;
return Result.Success;
} }
public Result SetSaveDataRootPath(string path) public Result OpenDataFileSystemByProgramId(out IFileSystem fileSystem, TitleId titleId)
{ {
// Missing permission check throw new NotImplementedException();
if (path.Length > PathTools.MaxPathLength)
{
return ResultFs.TooLongPath;
}
SaveDataRootPath = path;
return Result.Success;
} }
public Result OpenBisFileSystem(out IFileSystem fileSystem, string rootPath, BisPartitionId partitionId) public Result OpenDataStorageByCurrentProcess(out IStorage storage)
{ {
// Missing permission check, speed emulation storage type wrapper, and FileSystemInterfaceAdapter throw new NotImplementedException();
return FsProxyCore.OpenBisFileSystem(out fileSystem, rootPath, partitionId);
} }
public Result OpenSdCardFileSystem(out IFileSystem fileSystem) public Result OpenDataStorageByProgramId(out IStorage storage, TitleId programId)
{ {
// Missing permission check, speed emulation storage type wrapper, and FileSystemInterfaceAdapter throw new NotImplementedException();
return FsProxyCore.OpenSdCardFileSystem(out fileSystem);
} }
public Result OpenContentStorageFileSystem(out IFileSystem fileSystem, ContentStorageId storageId) public Result OpenDataStorageByDataId(out IStorage storage, TitleId dataId, StorageId storageId)
{ {
// Missing permission check, speed emulation storage type wrapper, and FileSystemInterfaceAdapter throw new NotImplementedException();
return FsProxyCore.OpenContentStorageFileSystem(out fileSystem, storageId);
} }
public Result OpenCustomStorageFileSystem(out IFileSystem fileSystem, CustomStorageId storageId) public Result OpenPatchDataStorageByCurrentProcess(out IStorage storage)
{ {
// Missing permission check, speed emulation storage type wrapper, and FileSystemInterfaceAdapter throw new NotImplementedException();
return FsProxyCore.OpenCustomStorageFileSystem(out fileSystem, storageId);
} }
public Result OpenSaveDataFileSystemBySystemSaveDataId(out IFileSystem fileSystem, SaveDataSpaceId spaceId, public Result OpenDataFileSystemWithProgramIndex(out IFileSystem fileSystem, byte programIndex)
SaveDataAttribute attribute)
{ {
// Missing permission check, speed emulation storage type wrapper, and FileSystemInterfaceAdapter throw new NotImplementedException();
fileSystem = default;
if (!IsSystemSaveDataId(attribute.SaveId)) return ResultFs.InvalidArgument.Log();
Result rc = OpenSaveDataFileSystemImpl(out IFileSystem saveFs, out _, spaceId,
attribute, false, true);
if (rc.IsFailure()) return rc;
// Missing check if the current title owns the save data or can open it
fileSystem = saveFs;
return Result.Success;
} }
public Result SetSdCardEncryptionSeed(ReadOnlySpan<byte> seed) public Result OpenDataStorageWithProgramIndex(out IStorage storage, byte programIndex)
{ {
// todo: use struct instead of byte span throw new NotImplementedException();
if (seed.Length != 0x10) return ResultFs.InvalidSize; }
// Missing permission check public Result RegisterSaveDataFileSystemAtomicDeletion(ReadOnlySpan<ulong> saveDataIds)
{
throw new NotImplementedException();
}
Result rc = FsProxyCore.SetSdCardEncryptionSeed(seed); public Result DeleteSaveDataFileSystem(ulong saveDataId)
if (rc.IsFailure()) return rc; {
throw new NotImplementedException();
}
// todo: Reset save data indexer public Result DeleteSaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId spaceId, ulong saveDataId)
{
throw new NotImplementedException();
}
return Result.Success; public Result DeleteSaveDataFileSystemBySaveDataAttribute(SaveDataSpaceId spaceId, ref SaveDataAttribute2 attribute)
{
throw new NotImplementedException();
}
public Result UpdateSaveDataMacForDebug(SaveDataSpaceId spaceId, ulong saveDataId)
{
throw new NotImplementedException();
}
public Result CreateSaveDataFileSystem(ref SaveDataAttribute2 attribute, ref SaveDataCreateInfo createInfo,
ref SaveMetaCreateInfo metaCreateInfo)
{
throw new NotImplementedException();
}
public Result CreateSaveDataFileSystemWithHashSalt(ref SaveDataAttribute2 attribute, ref SaveDataCreateInfo createInfo,
ref SaveMetaCreateInfo metaCreateInfo, ref HashSalt hashSalt)
{
throw new NotImplementedException();
}
public Result CreateSaveDataFileSystemBySystemSaveDataId(ref SaveDataAttribute2 attribute, ref SaveDataCreateInfo createInfo)
{
throw new NotImplementedException();
}
public Result ExtendSaveDataFileSystem(SaveDataSpaceId spaceId, ulong saveDataId, long dataSize, long journalSize)
{
throw new NotImplementedException();
} }
private Result OpenSaveDataFileSystemImpl(out IFileSystem fileSystem, out ulong saveDataId, private Result OpenSaveDataFileSystemImpl(out IFileSystem fileSystem, out ulong saveDataId,
SaveDataSpaceId spaceId, SaveDataAttribute attribute, bool openReadOnly, bool cacheExtraData) SaveDataSpaceId spaceId, ref SaveDataAttribute attribute, bool openReadOnly, bool cacheExtraData)
{ {
bool hasFixedId = attribute.SaveId != 0 && attribute.UserId.Id == Id128.InvalidId; bool hasFixedId = attribute.SaveId != 0 && attribute.UserId.Id == Id128.InvalidId;
@ -152,7 +159,7 @@ namespace LibHac.FsService
} }
Result saveFsResult = FsProxyCore.OpenSaveDataFileSystem(out fileSystem, spaceId, saveDataId, Result saveFsResult = FsProxyCore.OpenSaveDataFileSystem(out fileSystem, spaceId, saveDataId,
SaveDataRootPath, openReadOnly, attribute.Type, cacheExtraData); SaveDataRootPath.ToString(), openReadOnly, attribute.Type, cacheExtraData);
if (saveFsResult.IsSuccess()) return Result.Success; if (saveFsResult.IsSuccess()) return Result.Success;
@ -170,7 +177,395 @@ namespace LibHac.FsService
return ResultFs.TargetNotFound; return ResultFs.TargetNotFound;
} }
private bool IsSystemSaveDataId(ulong id) public Result OpenSaveDataFileSystem(out IFileSystem fileSystem, SaveDataSpaceId spaceId, ref SaveDataAttribute attribute)
{
throw new NotImplementedException();
}
public Result OpenReadOnlySaveDataFileSystem(out IFileSystem fileSystem, SaveDataSpaceId spaceId,
ref SaveDataAttribute attribute)
{
throw new NotImplementedException();
}
public Result OpenSaveDataFileSystemBySystemSaveDataId(out IFileSystem fileSystem, SaveDataSpaceId spaceId,
ref SaveDataAttribute attribute)
{
// Missing permission check, speed emulation storage type wrapper, and FileSystemInterfaceAdapter
fileSystem = default;
if (!IsSystemSaveDataId(attribute.SaveId)) return ResultFs.InvalidArgument.Log();
Result rc = OpenSaveDataFileSystemImpl(out IFileSystem saveFs, out _, spaceId,
ref attribute, false, true);
if (rc.IsFailure()) return rc;
// Missing check if the current title owns the save data or can open it
fileSystem = saveFs;
return Result.Success;
}
public Result ReadSaveDataFileSystemExtraData(Span<byte> extraDataBuffer, ulong saveDataId)
{
throw new NotImplementedException();
}
public Result ReadSaveDataFileSystemExtraDataBySaveDataSpaceId(Span<byte> extraDataBuffer, SaveDataSpaceId spaceId,
ulong saveDataId)
{
throw new NotImplementedException();
}
public Result ReadSaveDataFileSystemExtraDataBySaveDataAttribute(Span<byte> extraDataBuffer, SaveDataSpaceId spaceId,
ref SaveDataAttribute2 attribute)
{
throw new NotImplementedException();
}
public Result WriteSaveDataFileSystemExtraData(ulong saveDataId, SaveDataSpaceId spaceId, ReadOnlySpan<byte> extraDataBuffer)
{
throw new NotImplementedException();
}
public Result WriteSaveDataFileSystemExtraDataBySaveDataAttribute(ref SaveDataAttribute2 attribute, SaveDataSpaceId spaceId,
ReadOnlySpan<byte> extraDataBuffer, ReadOnlySpan<byte> maskBuffer)
{
throw new NotImplementedException();
}
public Result WriteSaveDataFileSystemExtraDataWithMask(ulong saveDataId, SaveDataSpaceId spaceId, ReadOnlySpan<byte> extraDataBuffer,
ReadOnlySpan<byte> maskBuffer)
{
throw new NotImplementedException();
}
public Result OpenImageDirectoryFileSystem(out IFileSystem fileSystem, ImageDirectoryId dirId)
{
throw new NotImplementedException();
}
public Result SetBisRootForHost(BisPartitionId partitionId, ref FsPath path)
{
throw new NotImplementedException();
}
public Result OpenBisFileSystem(out IFileSystem fileSystem, ref FsPath rootPath, BisPartitionId partitionId)
{
fileSystem = default;
// Missing permission check, speed emulation storage type wrapper, and FileSystemInterfaceAdapter
Result rc = PathTools.Normalize(out U8Span normalizedPath, rootPath);
if (rc.IsFailure()) return rc;
return FsProxyCore.OpenBisFileSystem(out fileSystem, normalizedPath.ToString(), partitionId);
}
public Result OpenBisStorage(out IStorage storage, BisPartitionId partitionId)
{
throw new NotImplementedException();
}
public Result InvalidateBisCache()
{
throw new NotImplementedException();
}
public Result OpenHostFileSystem(out IFileSystem fileSystem, ref FsPath subPath)
{
throw new NotImplementedException();
}
public Result OpenSdCardFileSystem(out IFileSystem fileSystem)
{
// Missing permission check, speed emulation storage type wrapper, and FileSystemInterfaceAdapter
return FsProxyCore.OpenSdCardFileSystem(out fileSystem);
}
public Result FormatSdCardFileSystem()
{
throw new NotImplementedException();
}
public Result FormatSdCardDryRun()
{
throw new NotImplementedException();
}
public Result IsExFatSupported(out bool isSupported)
{
throw new NotImplementedException();
}
public Result OpenGameCardStorage(out IStorage storage, GameCardHandle handle, GameCardPartitionRaw partitionId)
{
throw new NotImplementedException();
}
public Result OpenDeviceOperator(out IDeviceOperator deviceOperator)
{
throw new NotImplementedException();
}
public Result OpenSaveDataInfoReader(out ISaveDataInfoReader infoReader)
{
throw new NotImplementedException();
}
public Result OpenSaveDataInfoReaderBySaveDataSpaceId(out ISaveDataInfoReader infoReader, SaveDataSpaceId spaceId)
{
throw new NotImplementedException();
}
public Result OpenSaveDataInfoReaderWithFilter(out ISaveDataInfoReader infoReader, SaveDataSpaceId spaceId,
ref SaveDataFilter filter)
{
throw new NotImplementedException();
}
public Result FindSaveDataWithFilter(out long count, Span<byte> saveDataInfoBuffer, SaveDataSpaceId spaceId,
ref SaveDataFilter filter)
{
throw new NotImplementedException();
}
public Result OpenSaveDataInternalStorageFileSystem(out IFileSystem fileSystem, SaveDataSpaceId spaceId, ulong saveDataId)
{
throw new NotImplementedException();
}
public Result QuerySaveDataInternalStorageTotalSize(out long size, SaveDataSpaceId spaceId, ulong saveDataId)
{
throw new NotImplementedException();
}
public Result GetSaveDataCommitId(out long commitId, SaveDataSpaceId spaceId, ulong saveDataId)
{
throw new NotImplementedException();
}
public Result OpenSaveDataInfoReaderOnlyCacheStorage(out ISaveDataInfoReader infoReader)
{
throw new NotImplementedException();
}
public Result OpenSaveDataMetaFile(out IFile file, SaveDataSpaceId spaceId, ref SaveDataAttribute2 attribute,
SaveMetaType type)
{
throw new NotImplementedException();
}
public Result DeleteCacheStorage(short index)
{
throw new NotImplementedException();
}
public Result GetCacheStorageSize(out long dataSize, out long journalSize, short index)
{
throw new NotImplementedException();
}
public Result ListAccessibleSaveDataOwnerId(out int readCount, Span<TitleId> idBuffer, TitleId programId, int startIndex,
int bufferIdCount)
{
throw new NotImplementedException();
}
public Result SetSaveDataSize(long saveDataSize, long saveDataJournalSize)
{
if (saveDataSize < 0 || saveDataJournalSize < 0)
{
return ResultFs.InvalidSize;
}
SaveDataSize = saveDataSize;
SaveDataJournalSize = saveDataJournalSize;
return Result.Success;
}
public Result SetSaveDataRootPath(ref FsPath path)
{
// Missing permission check
if (StringUtils.GetLength(path.Str, FsPath.MaxLength + 1) > FsPath.MaxLength)
{
return ResultFs.TooLongPath;
}
StringUtils.Copy(SaveDataRootPath.Str, path.Str);
return Result.Success;
}
public Result OpenContentStorageFileSystem(out IFileSystem fileSystem, ContentStorageId storageId)
{
// Missing permission check, speed emulation storage type wrapper, and FileSystemInterfaceAdapter
return FsProxyCore.OpenContentStorageFileSystem(out fileSystem, storageId);
}
public Result OpenCloudBackupWorkStorageFileSystem(out IFileSystem fileSystem, CloudBackupWorkStorageId storageId)
{
throw new NotImplementedException();
}
public Result OpenCustomStorageFileSystem(out IFileSystem fileSystem, CustomStorageId storageId)
{
// Missing permission check, speed emulation storage type wrapper, and FileSystemInterfaceAdapter
return FsProxyCore.OpenCustomStorageFileSystem(out fileSystem, storageId);
}
public Result OpenGameCardFileSystem(out IFileSystem fileSystem, GameCardHandle handle, GameCardPartition partitionId)
{
throw new NotImplementedException();
}
public Result QuerySaveDataTotalSize(out long totalSize, long dataSize, long journalSize)
{
throw new NotImplementedException();
}
public Result SetCurrentPosixTimeWithTimeDifference(long time, int difference)
{
throw new NotImplementedException();
}
public Result GetRightsId(out RightsId rightsId, TitleId programId, StorageId storageId)
{
throw new NotImplementedException();
}
public Result GetRightsIdByPath(out RightsId rightsId, ref FsPath path)
{
throw new NotImplementedException();
}
public Result GetRightsIdAndKeyGenerationByPath(out RightsId rightsId, out byte keyGeneration, ref FsPath path)
{
throw new NotImplementedException();
}
public Result RegisterExternalKey(ref RightsId rightsId, ref AccessKey externalKey)
{
throw new NotImplementedException();
}
public Result UnregisterExternalKey(ref RightsId rightsId)
{
throw new NotImplementedException();
}
public Result UnregisterAllExternalKey()
{
throw new NotImplementedException();
}
public Result SetSdCardEncryptionSeed(ReadOnlySpan<byte> seed)
{
// todo: use struct instead of byte span
if (seed.Length != 0x10) return ResultFs.InvalidSize;
// Missing permission check
Result rc = FsProxyCore.SetSdCardEncryptionSeed(seed);
if (rc.IsFailure()) return rc;
// todo: Reset save data indexer
return Result.Success;
}
public Result VerifySaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId spaceId, ulong saveDataId, Span<byte> readBuffer)
{
throw new NotImplementedException();
}
public Result VerifySaveDataFileSystem(ulong saveDataId, Span<byte> readBuffer)
{
throw new NotImplementedException();
}
public Result CorruptSaveDataFileSystemByOffset(SaveDataSpaceId spaceId, ulong saveDataId, long offset)
{
throw new NotImplementedException();
}
public Result CorruptSaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId spaceId, ulong saveDataId)
{
throw new NotImplementedException();
}
public Result CorruptSaveDataFileSystem(ulong saveDataId)
{
throw new NotImplementedException();
}
public Result CreatePaddingFile(long size)
{
throw new NotImplementedException();
}
public Result DeleteAllPaddingFiles()
{
throw new NotImplementedException();
}
public Result DisableAutoSaveDataCreation()
{
AutoCreateSaveData = false;
return Result.Success;
}
public Result SetGlobalAccessLogMode(int mode)
{
throw new NotImplementedException();
}
public Result GetGlobalAccessLogMode(out int mode)
{
throw new NotImplementedException();
}
public Result GetProgramIndexForAccessLog(out int programIndex, out int programCount)
{
throw new NotImplementedException();
}
public Result OutputAccessLogToSdCard(U8Span logString)
{
throw new NotImplementedException();
}
public Result RegisterUpdatePartition()
{
throw new NotImplementedException();
}
public Result OpenRegisteredUpdatePartition(out IFileSystem fileSystem)
{
throw new NotImplementedException();
}
public Result OverrideSaveDataTransferTokenSignVerificationKey(ReadOnlySpan<byte> key)
{
throw new NotImplementedException();
}
public Result SetSdCardAccessibility(bool isAccessible)
{
throw new NotImplementedException();
}
public Result IsSdCardAccessible(out bool isAccessible)
{
throw new NotImplementedException();
}
private static bool IsSystemSaveDataId(ulong id)
{ {
return (long)id < 0; return (long)id < 0;
} }

View file

@ -47,7 +47,7 @@ namespace LibHac.FsService
return new FileSystemClient(this, timer); return new FileSystemClient(this, timer);
} }
public FileSystemProxy CreateFileSystemProxyService() public IFileSystemProxy CreateFileSystemProxyService()
{ {
return new FileSystemProxy(FsProxyCore, FsClient); return new FileSystemProxy(FsProxyCore, FsClient);
} }

View file

@ -15,6 +15,7 @@ namespace LibHac.FsSystem
public Span<byte> Str => SpanHelpers.CreateSpan(ref _str, MaxLength + 1); public Span<byte> Str => SpanHelpers.CreateSpan(ref _str, MaxLength + 1);
public static implicit operator U8Span(FsPath value) => new U8Span(value.Str);
public override string ToString() => StringUtils.Utf8ZToString(Str); public override string ToString() => StringUtils.Utf8ZToString(Str);
} }
} }

View file

@ -1,6 +1,7 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using LibHac.Common;
using LibHac.Fs; using LibHac.Fs;
#if HAS_FILE_SYSTEM_NAME #if HAS_FILE_SYSTEM_NAME
@ -51,6 +52,20 @@ namespace LibHac.FsSystem
return normalized; return normalized;
} }
public static Result Normalize(out U8Span normalizedPath, U8Span path)
{
if (path.Length == 0)
{
normalizedPath = path;
return Result.Success;
}
// Todo: optimize
normalizedPath = new U8Span(Normalize(path.ToString()));
return Result.Success;
}
// Licensed to the .NET Foundation under one or more agreements. // Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.