From 3184e6ca7e6755d35fab991878a934e19f260080 Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Sun, 23 Aug 2020 19:33:25 -0700 Subject: [PATCH] Add IFileSystemProxyForLoader and IProgramRegistry interfaces --- src/LibHac/Fs/CodeVerificationData.cs | 18 +++++ src/LibHac/Fs/FileSystemClient.cs | 65 ++++++++++++++++++- src/LibHac/Fs/Shim/Code.cs | 58 +++++++++++++++++ src/LibHac/Fs/Shim/LoaderApi.cs | 15 +++++ src/LibHac/Fs/Shim/ProgramRegistry.cs | 34 ++++++++++ src/LibHac/FsSrv/FileSystemProxy.cs | 14 +++- src/LibHac/FsSrv/FileSystemServer.cs | 44 +++++++++++++ .../FsSrv/Impl/ProgramRegistryManager.cs | 4 +- src/LibHac/FsSrv/ProgramRegistryImpl.cs | 3 +- src/LibHac/FsSrv/Sf/FspPath.cs | 45 +++++++++++++ .../FsSrv/Sf/IFileSystemProxyForLoader.cs | 15 +++++ src/LibHac/FsSrv/Sf/IProgramRegistry.cs | 14 ++++ src/LibHac/FsSystem/FsPath.cs | 5 +- src/LibHac/Horizon.cs | 18 ++++- 14 files changed, 342 insertions(+), 10 deletions(-) create mode 100644 src/LibHac/Fs/CodeVerificationData.cs create mode 100644 src/LibHac/Fs/Shim/Code.cs create mode 100644 src/LibHac/Fs/Shim/LoaderApi.cs create mode 100644 src/LibHac/Fs/Shim/ProgramRegistry.cs create mode 100644 src/LibHac/FsSrv/Sf/FspPath.cs create mode 100644 src/LibHac/FsSrv/Sf/IFileSystemProxyForLoader.cs create mode 100644 src/LibHac/FsSrv/Sf/IProgramRegistry.cs diff --git a/src/LibHac/Fs/CodeVerificationData.cs b/src/LibHac/Fs/CodeVerificationData.cs new file mode 100644 index 00000000..c2265efc --- /dev/null +++ b/src/LibHac/Fs/CodeVerificationData.cs @@ -0,0 +1,18 @@ +using System; +using System.Runtime.InteropServices; +using LibHac.Common; + +namespace LibHac.Fs +{ + [StructLayout(LayoutKind.Explicit, Size = 0x124)] + public struct CodeVerificationData + { + private const int Signature2Size = 0x100; + + [FieldOffset(0x000)] private byte _signature2; + [FieldOffset(0x100)] public Buffer32 NcaHeaderHash; + [FieldOffset(0x120)] public bool IsValid; + + public Span NcaSignature2 => SpanHelpers.CreateSpan(ref _signature2, Signature2Size); + } +} diff --git a/src/LibHac/Fs/FileSystemClient.cs b/src/LibHac/Fs/FileSystemClient.cs index 1f605a9b..9f4f124f 100644 --- a/src/LibHac/Fs/FileSystemClient.cs +++ b/src/LibHac/Fs/FileSystemClient.cs @@ -6,17 +6,24 @@ using LibHac.Diag; using LibHac.Fs.Accessors; using LibHac.Fs.Fsa; using LibHac.FsSrv; +using LibHac.FsSrv.Sf; using LibHac.FsSystem; namespace LibHac.Fs { public partial class FileSystemClient { - private HorizonClient Hos { get; } - private IFileSystemProxy FsProxy { get; set; } + internal HorizonClient Hos { get; } + private IFileSystemProxy FsProxy { get; set; } private readonly object _fspInitLocker = new object(); + private IFileSystemProxyForLoader FsProxyForLoader { get; set; } + private readonly object _fsplInitLocker = new object(); + + private IProgramRegistry ProgramRegistry { get; set; } + private readonly object _progRegInitLocker = new object(); + internal ITimeSpanGenerator Time { get; } private IAccessLog AccessLog { get; set; } @@ -44,7 +51,7 @@ namespace LibHac.Fs { if (FsProxy != null) return FsProxy; - lock (_fspInitLocker) + lock (_fsplInitLocker) { if (FsProxy != null) return FsProxy; @@ -67,6 +74,58 @@ namespace LibHac.Fs } } + public IFileSystemProxyForLoader GetFileSystemProxyForLoaderServiceObject() + { + if (FsProxyForLoader != null) return FsProxyForLoader; + + lock (_fspInitLocker) + { + if (FsProxyForLoader != null) return FsProxyForLoader; + + if (!HasFileSystemServer()) + { + throw new InvalidOperationException("Client was not initialized with a server object."); + } + + Result rc = Hos.Sm.GetService(out IFileSystemProxyForLoader fsProxy, "fsp-ldr"); + + if (rc.IsFailure()) + { + throw new HorizonResultException(rc, "Failed to create file system proxy service object."); + } + + fsProxy.SetCurrentProcess(Hos.Os.GetCurrentProcessId().Value).IgnoreResult(); + + FsProxyForLoader = fsProxy; + return FsProxyForLoader; + } + } + + public IProgramRegistry GetProgramRegistryServiceObject() + { + if (ProgramRegistry != null) return ProgramRegistry; + + lock (_progRegInitLocker) + { + if (ProgramRegistry != null) return ProgramRegistry; + + if (!HasFileSystemServer()) + { + throw new InvalidOperationException("Client was not initialized with a server object."); + } + + Result rc = Hos.Sm.GetService(out IProgramRegistry registry, "fsp-pr"); + + if (rc.IsFailure()) + { + throw new HorizonResultException(rc, "Failed to create registry service object."); + } + + ProgramRegistry = registry; + return ProgramRegistry; + } + } + public Result Register(U8Span mountName, IFileSystem fileSystem) { return Register(mountName, fileSystem, null); diff --git a/src/LibHac/Fs/Shim/Code.cs b/src/LibHac/Fs/Shim/Code.cs new file mode 100644 index 00000000..08c6df51 --- /dev/null +++ b/src/LibHac/Fs/Shim/Code.cs @@ -0,0 +1,58 @@ +using System; +using System.Runtime.CompilerServices; +using LibHac.Common; +using LibHac.Fs.Fsa; +using LibHac.FsSrv.Sf; +using LibHac.Ncm; + +namespace LibHac.Fs.Shim +{ + public static class Code + { + public static Result MountCode(this FileSystemClient fs, out CodeVerificationData verificationData, + U8Span mountName, U8Span path, ProgramId programId) + { + Result rc; + + if (fs.IsEnabledAccessLog(AccessLogTarget.System)) + { + TimeSpan startTime = fs.Time.GetCurrent(); + rc = MountCodeImpl(fs, out verificationData, mountName, path, programId); + TimeSpan endTime = fs.Time.GetCurrent(); + + fs.OutputAccessLog(rc, startTime, endTime, + $", name: \"{mountName.ToString()}\", name: \"{path.ToString()}\", programid: 0x{programId}"); + } + else + { + rc = MountCodeImpl(fs, out verificationData, mountName, path, programId); + } + + if (rc.IsSuccess() && fs.IsEnabledAccessLog(AccessLogTarget.System)) + { + fs.EnableFileSystemAccessorAccessLog(mountName); + } + + return rc; + } + + private static Result MountCodeImpl(this FileSystemClient fs, out CodeVerificationData verificationData, + U8Span mountName, U8Span path, ProgramId programId) + { + Unsafe.SkipInit(out verificationData); + + Result rc = MountHelpers.CheckMountName(mountName); + if (rc.IsFailure()) return rc; + + rc = FspPath.FromSpan(out FspPath fsPath, path); + if (rc.IsFailure()) return rc; + + IFileSystemProxyForLoader fsProxy = fs.GetFileSystemProxyForLoaderServiceObject(); + + rc = fsProxy.OpenCodeFileSystem(out IFileSystem codeFs, out verificationData, in fsPath, programId); + if (rc.IsFailure()) return rc; + + return fs.Register(mountName, codeFs); + } + } +} diff --git a/src/LibHac/Fs/Shim/LoaderApi.cs b/src/LibHac/Fs/Shim/LoaderApi.cs new file mode 100644 index 00000000..c4e06bba --- /dev/null +++ b/src/LibHac/Fs/Shim/LoaderApi.cs @@ -0,0 +1,15 @@ +using LibHac.FsSrv.Sf; +using LibHac.Os; + +namespace LibHac.Fs.Shim +{ + public static class LoaderApi + { + public static Result IsArchivedProgram(this FileSystemClient fs, out bool isArchived, ProcessId processId) + { + IFileSystemProxyForLoader fsProxy = fs.GetFileSystemProxyForLoaderServiceObject(); + + return fsProxy.IsArchivedProgram(out isArchived, processId.Value); + } + } +} diff --git a/src/LibHac/Fs/Shim/ProgramRegistry.cs b/src/LibHac/Fs/Shim/ProgramRegistry.cs new file mode 100644 index 00000000..3dc37ac0 --- /dev/null +++ b/src/LibHac/Fs/Shim/ProgramRegistry.cs @@ -0,0 +1,34 @@ +using System; +using LibHac.FsSrv; +using LibHac.FsSrv.Sf; +using LibHac.Ncm; + +namespace LibHac.Fs.Shim +{ + public static class ProgramRegistry + { + /// + public static Result RegisterProgram(this FileSystemClient fs, ulong processId, ProgramId programId, + StorageId storageId, ReadOnlySpan accessControlData, ReadOnlySpan accessControlDescriptor) + { + IProgramRegistry registry = fs.GetProgramRegistryServiceObject(); + + Result rc = registry.SetCurrentProcess(fs.Hos.ProcessId.Value); + if (rc.IsFailure()) return rc; + + return registry.RegisterProgram(processId, programId, storageId, accessControlData, + accessControlDescriptor); + } + + /// + public static Result UnregisterProgram(this FileSystemClient fs, ulong processId) + { + IProgramRegistry registry = fs.GetProgramRegistryServiceObject(); + + Result rc = registry.SetCurrentProcess(fs.Hos.ProcessId.Value); + if (rc.IsFailure()) return rc; + + return registry.UnregisterProgram(processId); + } + } +} diff --git a/src/LibHac/FsSrv/FileSystemProxy.cs b/src/LibHac/FsSrv/FileSystemProxy.cs index b38c46d0..3334b3e7 100644 --- a/src/LibHac/FsSrv/FileSystemProxy.cs +++ b/src/LibHac/FsSrv/FileSystemProxy.cs @@ -5,6 +5,7 @@ using LibHac.Diag; using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.FsSrv.Impl; +using LibHac.FsSrv.Sf; using LibHac.FsSystem; using LibHac.Kvdb; using LibHac.Ncm; @@ -12,7 +13,7 @@ using LibHac.Spl; namespace LibHac.FsSrv { - public class FileSystemProxy : IFileSystemProxy + public class FileSystemProxy : IFileSystemProxy, IFileSystemProxyForLoader { private FileSystemProxyCore FsProxyCore { get; } internal HorizonClient Hos { get; } @@ -71,6 +72,17 @@ namespace LibHac.FsSrv throw new NotImplementedException(); } + public Result OpenCodeFileSystem(out IFileSystem fileSystem, out CodeVerificationData verificationData, in FspPath path, + ProgramId programId) + { + throw new NotImplementedException(); + } + + public Result IsArchivedProgram(out bool isArchived, ulong processId) + { + throw new NotImplementedException(); + } + public Result SetCurrentProcess(ulong processId) { CurrentProcess = processId; diff --git a/src/LibHac/FsSrv/FileSystemServer.cs b/src/LibHac/FsSrv/FileSystemServer.cs index c07765d1..3fd57fab 100644 --- a/src/LibHac/FsSrv/FileSystemServer.cs +++ b/src/LibHac/FsSrv/FileSystemServer.cs @@ -55,6 +55,8 @@ namespace LibHac.FsSrv fsProxy.CleanUpTemporaryStorage().IgnoreResult(); Hos.Sm.RegisterService(new FileSystemProxyService(this), "fsp-srv").IgnoreResult(); + Hos.Sm.RegisterService(new FileSystemProxyForLoaderService(this), "fsp-ldr").IgnoreResult(); + Hos.Sm.RegisterService(new ProgramRegistryService(this), "fsp-pr").IgnoreResult(); // NS usually takes care of this if (Hos.Fs.IsSdCardInserted()) @@ -84,6 +86,16 @@ namespace LibHac.FsSrv return new FileSystemProxy(Hos, FsProxyCore); } + private FileSystemProxy GetFileSystemProxyForLoaderServiceObject() + { + return new FileSystemProxy(Hos, FsProxyCore); + } + + private ProgramRegistryImpl GetProgramRegistryServiceObject() + { + return new ProgramRegistryImpl(FsProxyCore.Config.ProgramRegistryServiceImpl); + } + internal bool IsCurrentProcess(ulong processId) { ulong currentId = Hos.Os.GetCurrentProcessId().Value; @@ -106,6 +118,38 @@ namespace LibHac.FsSrv return Result.Success; } } + + private class FileSystemProxyForLoaderService : IServiceObject + { + private readonly FileSystemServer _server; + + public FileSystemProxyForLoaderService(FileSystemServer server) + { + _server = server; + } + + public Result GetServiceObject(out object serviceObject) + { + serviceObject = _server.GetFileSystemProxyForLoaderServiceObject(); + return Result.Success; + } + } + + private class ProgramRegistryService : IServiceObject + { + private readonly FileSystemServer _server; + + public ProgramRegistryService(FileSystemServer server) + { + _server = server; + } + + public Result GetServiceObject(out object serviceObject) + { + serviceObject = _server.GetProgramRegistryServiceObject(); + return Result.Success; + } + } } /// diff --git a/src/LibHac/FsSrv/Impl/ProgramRegistryManager.cs b/src/LibHac/FsSrv/Impl/ProgramRegistryManager.cs index 1f9c5ebf..dc23ca15 100644 --- a/src/LibHac/FsSrv/Impl/ProgramRegistryManager.cs +++ b/src/LibHac/FsSrv/Impl/ProgramRegistryManager.cs @@ -100,7 +100,7 @@ namespace LibHac.FsSrv.Impl { lock (ProgramInfoList) { - if(ProgramInfo.IsInitialProgram(processId)) + if (ProgramInfo.IsInitialProgram(processId)) { programInfo = GetProgramInfoForInitialProcess(); return Result.Success; @@ -178,7 +178,7 @@ namespace LibHac.FsSrv.Impl { // Todo: We have no kernel to call into, so use hardcoded values for now const int initialProcessIdLowerBound = 1; - const int initialProcessIdUpperBound = 10; + const int initialProcessIdUpperBound = 0x50; return initialProcessIdLowerBound >= processId && processId <= initialProcessIdUpperBound; } diff --git a/src/LibHac/FsSrv/ProgramRegistryImpl.cs b/src/LibHac/FsSrv/ProgramRegistryImpl.cs index 649e6299..a757adae 100644 --- a/src/LibHac/FsSrv/ProgramRegistryImpl.cs +++ b/src/LibHac/FsSrv/ProgramRegistryImpl.cs @@ -1,11 +1,12 @@ using System; using LibHac.Fs; using LibHac.FsSrv.Impl; +using LibHac.FsSrv.Sf; using LibHac.Ncm; namespace LibHac.FsSrv { - internal class ProgramRegistryImpl + internal class ProgramRegistryImpl : IProgramRegistry { private ulong _processId; diff --git a/src/LibHac/FsSrv/Sf/FspPath.cs b/src/LibHac/FsSrv/Sf/FspPath.cs new file mode 100644 index 00000000..8b3359be --- /dev/null +++ b/src/LibHac/FsSrv/Sf/FspPath.cs @@ -0,0 +1,45 @@ +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using LibHac.Common; +using LibHac.Fs; + +namespace LibHac.FsSrv.Sf +{ + [DebuggerDisplay("{ToString()}")] + [StructLayout(LayoutKind.Sequential, Size = MaxLength + 1)] + public readonly struct FspPath + { + internal const int MaxLength = 0x300; + +#if DEBUG + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding100 Padding000; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding100 Padding100; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding100 Padding200; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly byte Padding300; +#endif + + public ReadOnlySpan Str => SpanHelpers.AsReadOnlyByteSpan(in this); + + public static Result FromSpan(out FspPath fspPath, ReadOnlySpan path) + { + Unsafe.SkipInit(out fspPath); + + Span str = SpanHelpers.AsByteSpan(ref fspPath); + + // Ensure null terminator even if the creation fails for safety + str[0x301] = 0; + + var sb = new U8StringBuilder(str); + bool overflowed = sb.Append(path).Overflowed; + + return overflowed ? ResultFs.TooLongPath.Log() : Result.Success; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator U8Span(in FspPath value) => new U8Span(SpanHelpers.AsReadOnlyByteSpan(in value)); + + public override string ToString() => StringUtils.Utf8ZToString(Str); + } +} diff --git a/src/LibHac/FsSrv/Sf/IFileSystemProxyForLoader.cs b/src/LibHac/FsSrv/Sf/IFileSystemProxyForLoader.cs new file mode 100644 index 00000000..8b772555 --- /dev/null +++ b/src/LibHac/FsSrv/Sf/IFileSystemProxyForLoader.cs @@ -0,0 +1,15 @@ +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.Ncm; + +namespace LibHac.FsSrv.Sf +{ + public interface IFileSystemProxyForLoader + { + Result OpenCodeFileSystem(out IFileSystem fileSystem, out CodeVerificationData verificationData, + in FspPath path, ProgramId programId); + + Result IsArchivedProgram(out bool isArchived, ulong processId); + Result SetCurrentProcess(ulong processId); + } +} diff --git a/src/LibHac/FsSrv/Sf/IProgramRegistry.cs b/src/LibHac/FsSrv/Sf/IProgramRegistry.cs new file mode 100644 index 00000000..a4724dd7 --- /dev/null +++ b/src/LibHac/FsSrv/Sf/IProgramRegistry.cs @@ -0,0 +1,14 @@ +using System; +using LibHac.Ncm; + +namespace LibHac.FsSrv.Sf +{ + public interface IProgramRegistry + { + Result RegisterProgram(ulong processId, ProgramId programId, StorageId storageId, + ReadOnlySpan accessControlData, ReadOnlySpan accessControlDescriptor); + + Result UnregisterProgram(ulong processId); + Result SetCurrentProcess(ulong processId); + } +} diff --git a/src/LibHac/FsSystem/FsPath.cs b/src/LibHac/FsSystem/FsPath.cs index 25bc9831..b5e24dd3 100644 --- a/src/LibHac/FsSystem/FsPath.cs +++ b/src/LibHac/FsSystem/FsPath.cs @@ -24,7 +24,10 @@ namespace LibHac.FsSystem public static Result FromSpan(out FsPath fsPath, ReadOnlySpan path) { - fsPath = new FsPath(); + Unsafe.SkipInit(out fsPath); + + // Ensure null terminator even if the creation fails for safety + fsPath.Str[0x301] = 0; var sb = new U8StringBuilder(fsPath.Str); bool overflowed = sb.Append(path).Overflowed; diff --git a/src/LibHac/Horizon.cs b/src/LibHac/Horizon.cs index 85c5f110..c655f499 100644 --- a/src/LibHac/Horizon.cs +++ b/src/LibHac/Horizon.cs @@ -1,5 +1,6 @@ using System.Threading; using LibHac.Bcat; +using LibHac.Diag; using LibHac.FsSrv; using LibHac.Os; using LibHac.Sm; @@ -8,26 +9,39 @@ namespace LibHac { public class Horizon { + private const int InitialProcessCountMax = 0x50; internal ITimeSpanGenerator Time { get; } internal ServiceManager ServiceManager { get; } // long instead of ulong because the ulong Interlocked.Increment overload // wasn't added until .NET 5 + private long _currentInitialProcessId; private long _currentProcessId; public Horizon(ITimeSpanGenerator timer, FileSystemServerConfig fsServerConfig) { - _currentProcessId = 0; + _currentProcessId = InitialProcessCountMax; Time = timer ?? new StopWatchTimeSpanGenerator(); ServiceManager = new ServiceManager(); // ReSharper disable ObjectCreationAsStatement - new FileSystemServer(CreateHorizonClient(), fsServerConfig); + new FileSystemServer(CreatePrivilegedHorizonClient(), fsServerConfig); new BcatServer(CreateHorizonClient()); // ReSharper restore ObjectCreationAsStatement } + public HorizonClient CreatePrivilegedHorizonClient() + { + ulong processId = (ulong)Interlocked.Increment(ref _currentInitialProcessId); + + Abort.DoAbortUnless(processId <= InitialProcessCountMax, "Created too many privileged clients."); + + // Todo: Register process with FS + + return new HorizonClient(this, new ProcessId(processId)); + } + public HorizonClient CreateHorizonClient() { ulong processId = (ulong)Interlocked.Increment(ref _currentProcessId);