Add fs_Host shims

This commit is contained in:
Alex Barney 2020-03-14 00:18:42 -07:00
parent a006816a2e
commit 7b4df4671c
8 changed files with 373 additions and 3 deletions

View file

@ -12,6 +12,7 @@ namespace LibHac.Common
private int _length; private int _length;
public bool Overflowed { get; private set; } public bool Overflowed { get; private set; }
public int Length => _length;
public int Capacity => _buffer.Length - NullTerminatorLength; public int Capacity => _buffer.Length - NullTerminatorLength;
public U8StringBuilder(Span<byte> buffer) public U8StringBuilder(Span<byte> buffer)

View file

@ -4,6 +4,8 @@ namespace LibHac.Fs
{ {
internal static class CommonMountNames internal static class CommonMountNames
{ {
public const char ReservedMountNamePrefixCharacter = '@';
public static readonly U8String GameCardFileSystemMountName = new U8String("@Gc"); public static readonly U8String GameCardFileSystemMountName = new U8String("@Gc");
public static readonly U8String ContentStorageSystemMountName = new U8String("@SystemContent"); public static readonly U8String ContentStorageSystemMountName = new U8String("@SystemContent");
public static readonly U8String ContentStorageUserMountName = new U8String("@UserContent"); public static readonly U8String ContentStorageUserMountName = new U8String("@UserContent");

View file

@ -150,6 +150,30 @@ namespace LibHac.Fs
OutputAccessLogImpl(result, startTime, endTime, handle.GetId(), message, caller); OutputAccessLogImpl(result, startTime, endTime, handle.GetId(), message, caller);
} }
internal void OutputAccessLogUnlessResultSuccess(Result result, TimeSpan startTime, TimeSpan endTime, string message, [CallerMemberName] string caller = "")
{
if (result.IsFailure())
{
OutputAccessLogImpl(result, startTime, endTime, 0, message, caller);
}
}
internal void OutputAccessLogUnlessResultSuccess(Result result, TimeSpan startTime, TimeSpan endTime, FileHandle handle, string message, [CallerMemberName] string caller = "")
{
if (result.IsFailure())
{
OutputAccessLogImpl(result, startTime, endTime, handle.GetId(), message, caller);
}
}
internal void OutputAccessLogUnlessResultSuccess(Result result, TimeSpan startTime, TimeSpan endTime, DirectoryHandle handle, string message, [CallerMemberName] string caller = "")
{
if (result.IsFailure())
{
OutputAccessLogImpl(result, startTime, endTime, handle.GetId(), message, caller);
}
}
internal void OutputAccessLogImpl(Result result, TimeSpan startTime, TimeSpan endTime, int handleId, internal void OutputAccessLogImpl(Result result, TimeSpan startTime, TimeSpan endTime, int handleId,
string message, [CallerMemberName] string caller = "") string message, [CallerMemberName] string caller = "")
{ {
@ -208,6 +232,27 @@ namespace LibHac.Fs
return rc; return rc;
} }
public Result RunOperationWithAccessLogOnFailure(AccessLogTarget logTarget, Func<Result> operation,
Func<string> textGenerator, [CallerMemberName] string caller = "")
{
Result rc;
if (IsEnabledAccessLog(logTarget))
{
TimeSpan startTime = Time.GetCurrent();
rc = operation();
TimeSpan endTime = Time.GetCurrent();
OutputAccessLogUnlessResultSuccess(rc, startTime, endTime, textGenerator(), caller);
}
else
{
rc = operation();
}
return rc;
}
} }
[Flags] [Flags]

View file

@ -187,4 +187,10 @@ namespace LibHac.Fs
Nand = 1, Nand = 1,
SdCard = 2 SdCard = 2
} }
public enum MountHostOption
{
None = 0,
PseudoCaseSensitive = 1
}
} }

View file

@ -1,4 +1,5 @@
using LibHac.Common; using LibHac.Common;
using static LibHac.Fs.CommonMountNames;
namespace LibHac.Fs namespace LibHac.Fs
{ {
@ -23,6 +24,11 @@ namespace LibHac.Fs
return Result.Success; return Result.Success;
} }
public static bool IsReservedMountName(U8Span name)
{
return (uint)name.Length > 0 && name[0] == ReservedMountNamePrefixCharacter;
}
// ReSharper disable once UnusedParameter.Local // ReSharper disable once UnusedParameter.Local
private static bool CheckMountNameImpl(U8Span name) private static bool CheckMountNameImpl(U8Span name)
{ {

304
src/LibHac/Fs/Shim/Host.cs Normal file
View file

@ -0,0 +1,304 @@
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using LibHac.Common;
using LibHac.FsService;
using LibHac.FsSystem;
using static LibHac.Fs.CommonMountNames;
namespace LibHac.Fs.Shim
{
public static class Host
{
private static ReadOnlySpan<byte> HostRootFileSystemPath => new[]
{(byte) '@', (byte) 'H', (byte) 'o', (byte) 's', (byte) 't', (byte) ':', (byte) '/'};
private const int HostRootFileSystemPathLength = 8;
private class HostCommonMountNameGenerator : ICommonMountNameGenerator
{
private FsPath _path;
public HostCommonMountNameGenerator(U8Span path)
{
StringUtils.Copy(_path.Str, path);
int pathLength = StringUtils.GetLength(_path.Str);
if (pathLength != 0 && _path.Str[pathLength - 1] == StringTraits.DirectorySeparator)
{
_path.Str[pathLength - 1] = StringTraits.NullTerminator;
}
}
public Result GenerateCommonMountName(Span<byte> nameBuffer)
{
int requiredNameBufferSize = StringUtils.GetLength(_path.Str, FsPath.MaxLength) + HostRootFileSystemPathLength;
if (nameBuffer.Length < requiredNameBufferSize)
return ResultFs.TooLongPath.Log();
int size = new U8StringBuilder(nameBuffer).Append(HostRootFileSystemPath).Append(_path.Str).Length;
Debug.Assert(size == requiredNameBufferSize - 1);
return Result.Success;
}
}
private class HostRootCommonMountNameGenerator : ICommonMountNameGenerator
{
public Result GenerateCommonMountName(Span<byte> nameBuffer)
{
const int requiredNameBufferSize = HostRootFileSystemPathLength;
Debug.Assert(nameBuffer.Length >= requiredNameBufferSize);
int size = StringUtils.Copy(nameBuffer, HostRootFileSystemPath);
Debug.Assert(size == requiredNameBufferSize - 1);
return Result.Success;
}
}
public static Result MountHostRoot(this FileSystemClient fs)
{
IFileSystem hostFileSystem = default;
var path = new FsPath();
path.Str[0] = 0;
static string LogMessageGenerator() => $", name: \"{HostRootFileSystemMountName.ToString()}\"";
Result OpenHostFs() => OpenHostFileSystemImpl(fs, out hostFileSystem, ref path, MountHostOption.None);
Result MountHostFs() => fs.Register(HostRootFileSystemMountName, hostFileSystem,
new HostRootCommonMountNameGenerator());
// Open the host file system
Result result =
fs.RunOperationWithAccessLogOnFailure(AccessLogTarget.Application, OpenHostFs, LogMessageGenerator);
if (result.IsFailure()) return result;
// Mount the host file system
result = fs.RunOperationWithAccessLog(AccessLogTarget.Application, MountHostFs, LogMessageGenerator);
if (result.IsFailure()) return result;
if (fs.IsEnabledAccessLog(AccessLogTarget.Application))
fs.EnableFileSystemAccessorAccessLog(HostRootFileSystemMountName);
return Result.Success;
}
public static Result MountHostRoot(this FileSystemClient fs, MountHostOption option)
{
IFileSystem hostFileSystem = default;
var path = new FsPath();
path.Str[0] = 0;
string LogMessageGenerator() =>
$", name: \"{HostRootFileSystemMountName.ToString()}, mount_host_option: {option}\"";
Result OpenHostFs() => OpenHostFileSystemImpl(fs, out hostFileSystem, ref path, option);
Result MountHostFs() => fs.Register(HostRootFileSystemMountName, hostFileSystem,
new HostRootCommonMountNameGenerator());
// Open the host file system
Result result =
fs.RunOperationWithAccessLogOnFailure(AccessLogTarget.Application, OpenHostFs, LogMessageGenerator);
if (result.IsFailure()) return result;
// Mount the host file system
result = fs.RunOperationWithAccessLog(AccessLogTarget.Application, MountHostFs, LogMessageGenerator);
if (result.IsFailure()) return result;
if (fs.IsEnabledAccessLog(AccessLogTarget.Application))
fs.EnableFileSystemAccessorAccessLog(HostRootFileSystemMountName);
return Result.Success;
}
public static void UnmountHostRoot(this FileSystemClient fs)
{
fs.Unmount(HostRootFileSystemMountName);
}
public static Result MountHost(this FileSystemClient fs, U8Span mountName, U8Span path)
{
return MountHostImpl(fs, mountName, path, null);
}
public static Result MountHost(this FileSystemClient fs, U8Span mountName, U8Span path, MountHostOption option)
{
return MountHostImpl(fs, mountName, path, option);
}
private static Result MountHostImpl(this FileSystemClient fs, U8Span mountName, U8Span path,
MountHostOption? optionalOption, [CallerMemberName] string caller = "")
{
Result rc;
ICommonMountNameGenerator nameGenerator;
string logMessage = null;
var option = MountHostOption.None;
// Set the mount option if it was specified
if (optionalOption.HasValue)
{
option = optionalOption.Value;
}
if (fs.IsEnabledAccessLog(AccessLogTarget.Application))
{
if (optionalOption.HasValue)
{
logMessage = $", name: \"{mountName.ToString()}\", mount_host_option: {option}";
}
else
{
logMessage = $", name: \"{mountName.ToString()}\"";
}
TimeSpan startTime = fs.Time.GetCurrent();
rc = PreMountHost(out nameGenerator, mountName, path);
TimeSpan endTime = fs.Time.GetCurrent();
fs.OutputAccessLogUnlessResultSuccess(rc, startTime, endTime, logMessage, caller);
}
else
{
rc = PreMountHost(out nameGenerator, mountName, path);
}
if (rc.IsFailure()) return rc;
IFileSystem hostFileSystem;
if (fs.IsEnabledAccessLog(AccessLogTarget.Application))
{
TimeSpan startTime = fs.Time.GetCurrent();
rc = OpenHostFileSystem(fs, out hostFileSystem, mountName, path, option);
TimeSpan endTime = fs.Time.GetCurrent();
fs.OutputAccessLogUnlessResultSuccess(rc, startTime, endTime, logMessage, caller);
}
else
{
rc = OpenHostFileSystem(fs, out hostFileSystem, mountName, path, option);
}
if (rc.IsFailure()) return rc;
if (fs.IsEnabledAccessLog(AccessLogTarget.Application))
{
TimeSpan startTime = fs.Time.GetCurrent();
rc = fs.Register(mountName, hostFileSystem, nameGenerator);
TimeSpan endTime = fs.Time.GetCurrent();
fs.OutputAccessLog(rc, startTime, endTime, logMessage, caller);
}
else
{
rc = fs.Register(mountName, hostFileSystem, nameGenerator);
}
if (rc.IsFailure()) return rc;
if (fs.IsEnabledAccessLog(AccessLogTarget.Application))
{
fs.EnableFileSystemAccessorAccessLog(mountName);
}
return Result.Success;
}
private static Result PreMountHost(out ICommonMountNameGenerator nameGenerator, U8Span mountName, U8Span path)
{
nameGenerator = default;
Result rc = MountHelpers.CheckMountName(mountName);
if (rc.IsFailure()) return rc;
if (path.IsNull())
return ResultFs.NullptrArgument.Log();
nameGenerator = new HostCommonMountNameGenerator(path);
return Result.Success;
}
private static Result OpenHostFileSystem(FileSystemClient fs, out IFileSystem fileSystem, U8Span mountName,
U8Span path, MountHostOption option)
{
fileSystem = default;
if (mountName.IsNull())
return ResultFs.NullptrArgument.Log();
if (path.IsNull())
return ResultFs.NullptrArgument.Log();
if (PathUtility.IsWindowsDrive(mountName))
return ResultFs.InvalidMountName.Log();
if (MountHelpers.IsReservedMountName(mountName))
return ResultFs.InvalidMountName.Log();
bool needsTrailingSeparator = false;
int pathLength = StringUtils.GetLength(path, PathTools.MaxPathLength + 1);
if (pathLength != 0 && PathTool.IsSeparator(path[pathLength - 1]))
{
needsTrailingSeparator = true;
pathLength++;
}
if (pathLength + 1 > PathTools.MaxPathLength)
return ResultFs.TooLongPath.Log();
FsPath fullPath;
unsafe { _ = &fullPath; } // workaround for CS0165
var sb = new U8StringBuilder(fullPath.Str);
sb.Append(StringTraits.DirectorySeparator).Append(path);
if (needsTrailingSeparator)
{
sb.Append(StringTraits.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]))
{
for (int i = 1; PathTool.IsSeparator(fullPath.Str[i]); i++)
{
fullPath.Str[i] = StringTraits.AltDirectorySeparator;
}
}
return OpenHostFileSystemImpl(fs, out fileSystem, ref fullPath, option);
}
private static Result OpenHostFileSystemImpl(FileSystemClient fs, out IFileSystem fileSystem, ref FsPath path, MountHostOption option)
{
fileSystem = default;
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
IFileSystem hostFs;
if (option == MountHostOption.None)
{
Result rc = fsProxy.OpenHostFileSystem(out hostFs, ref path);
if (rc.IsFailure()) return rc;
}
else
{
Result rc = fsProxy.OpenHostFileSystemWithOption(out hostFs, ref path, option);
if (rc.IsFailure()) return rc;
}
fileSystem = hostFs;
return Result.Success;
}
}
}

View file

@ -682,7 +682,12 @@ namespace LibHac.FsService
throw new NotImplementedException(); throw new NotImplementedException();
} }
public Result OpenHostFileSystem(out IFileSystem fileSystem, ref FsPath subPath) public Result OpenHostFileSystemWithOption(out IFileSystem fileSystem, ref FsPath path, MountHostOption option)
{
throw new NotImplementedException();
}
public Result OpenHostFileSystem(out IFileSystem fileSystem, ref FsPath path)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }

View file

@ -17,7 +17,8 @@ namespace LibHac.FsService
Result OpenBisFileSystem(out IFileSystem fileSystem, ref FsPath rootPath, BisPartitionId partitionId); Result OpenBisFileSystem(out IFileSystem fileSystem, ref FsPath rootPath, BisPartitionId partitionId);
Result OpenBisStorage(out IStorage storage, BisPartitionId partitionId); Result OpenBisStorage(out IStorage storage, BisPartitionId partitionId);
Result InvalidateBisCache(); Result InvalidateBisCache();
Result OpenHostFileSystem(out IFileSystem fileSystem, ref FsPath subPath); Result OpenHostFileSystemWithOption(out IFileSystem fileSystem, ref FsPath path, MountHostOption option);
Result OpenHostFileSystem(out IFileSystem fileSystem, ref FsPath path);
Result OpenSdCardFileSystem(out IFileSystem fileSystem); Result OpenSdCardFileSystem(out IFileSystem fileSystem);
Result FormatSdCardFileSystem(); Result FormatSdCardFileSystem();
Result DeleteSaveDataFileSystem(ulong saveDataId); Result DeleteSaveDataFileSystem(ulong saveDataId);