diff --git a/src/LibHac/FsSrv/FileSystemServer.cs b/src/LibHac/FsSrv/FileSystemServer.cs index 1c6adecd..527ef384 100644 --- a/src/LibHac/FsSrv/FileSystemServer.cs +++ b/src/LibHac/FsSrv/FileSystemServer.cs @@ -29,6 +29,7 @@ internal struct FileSystemServerGlobals public ProgramRegistryImplGlobals ProgramRegistryImpl; public DeviceEventSimulatorGlobals DeviceEventSimulator; public AccessControlGlobals AccessControl; + public ProgramInfoGlobals ProgramInfo; public StorageDeviceManagerFactoryGlobals StorageDeviceManagerFactory; public SaveDataSharedFileStorageGlobals SaveDataSharedFileStorage; public MultiCommitManagerGlobals MultiCommitManager; @@ -63,4 +64,4 @@ public readonly struct FileSystemServerImpl internal ref FileSystemServerGlobals Globals => ref FsSrv.Globals; internal FileSystemServerImpl(FileSystemServer parentServer) => FsSrv = parentServer; -} +} \ No newline at end of file diff --git a/src/LibHac/FsSrv/Impl/ProgramInfo.cs b/src/LibHac/FsSrv/Impl/ProgramInfo.cs new file mode 100644 index 00000000..70ad6f6d --- /dev/null +++ b/src/LibHac/FsSrv/Impl/ProgramInfo.cs @@ -0,0 +1,120 @@ +using System; +using LibHac.Common; +using LibHac.Diag; +using LibHac.Ncm; +using LibHac.Os; + +namespace LibHac.FsSrv.Impl; + +public static class ProgramInfoGlobalMethods +{ + public static bool IsInitialProgram(this FileSystemServer fsSrv, ulong processId) + { + ref ProgramInfoGlobals g = ref fsSrv.Globals.ProgramInfo; + using (var guard = new InitializationGuard(ref g.InitialProcessIdRangeInitGuard, fsSrv.Globals.InitMutex)) + { + if (!guard.IsInitialized) + { + // Todo: We have no kernel to call into, so use hardcoded values for now + g.InitialProcessIdMin = OsState.InitialProcessCountMin; + g.InitialProcessIdMax = OsState.InitialProcessCountMax; + + Abort.DoAbortUnless(0 < g.InitialProcessIdMin && g.InitialProcessIdMin <= g.InitialProcessIdMax, + "Invalid initial process ID range"); + } + } + + Abort.DoAbortUnless(g.InitialProcessIdMin != 0); + + return g.InitialProcessIdMin <= processId && processId <= g.InitialProcessIdMax; + } + + public static bool IsCurrentProcess(this FileSystemServer fsSrv, ulong processId) + { + ref ProgramInfoGlobals g = ref fsSrv.Globals.ProgramInfo; + using (var guard = new InitializationGuard(ref g.CurrentProcessIdInitGuard, fsSrv.Globals.InitMutex)) + { + if (!guard.IsInitialized) + { + g.CurrentProcessId = fsSrv.Hos.Os.GetCurrentProcessId().Value; + } + } + + return g.CurrentProcessId == processId; + } +} + +internal struct ProgramInfoGlobals +{ + public nint CurrentProcessIdInitGuard; + public ulong CurrentProcessId; + + public nint InitialProcessIdRangeInitGuard; + public ulong InitialProcessIdMin; + public ulong InitialProcessIdMax; + + public nint ProgramInfoForInitialProcessInitGuard; + public ProgramInfo ProgramInfoForInitialProcess; +} + +/// +/// Contains the program ID, storage location and FS permissions of a running process. +/// +/// Based on FS 13.1.0 (nnSdk 13.4.0) +public class ProgramInfo +{ + private readonly ulong _processId; + public ProgramId ProgramId { get; } + public StorageId StorageId { get; } + public AccessControl AccessControl { get; } + + public ulong ProgramIdValue => ProgramId.Value; + + private ProgramInfo(FileSystemServer fsServer, ReadOnlySpan accessControlData, + ReadOnlySpan accessControlDescriptor) + { + _processId = 0; + AccessControl = new AccessControl(fsServer, accessControlData, accessControlDescriptor, ulong.MaxValue); + ProgramId = default; + StorageId = 0; + } + + public ProgramInfo(FileSystemServer fsServer, ulong processId, ProgramId programId, StorageId storageId, + ReadOnlySpan accessControlData, ReadOnlySpan accessControlDescriptor) + { + _processId = processId; + AccessControl = new AccessControl(fsServer, accessControlData, accessControlDescriptor); + ProgramId = programId; + StorageId = storageId; + } + + public bool Contains(ulong processId) => _processId == processId; + + public static ProgramInfo GetProgramInfoForInitialProcess(FileSystemServer fsSrv) + { + ref ProgramInfoGlobals g = ref fsSrv.Globals.ProgramInfo; + using (var guard = new InitializationGuard(ref g.ProgramInfoForInitialProcessInitGuard, fsSrv.Globals.InitMutex)) + { + if (!guard.IsInitialized) + { + g.ProgramInfoForInitialProcess = new ProgramInfo(fsSrv, InitialProcessAccessControlDataHeader, + InitialProcessAccessControlDescriptor); + } + } + + return g.ProgramInfoForInitialProcess; + } + + private static ReadOnlySpan InitialProcessAccessControlDataHeader => new byte[] + { + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x1C, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + private static ReadOnlySpan InitialProcessAccessControlDescriptor => new byte[] + { + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF + }; +} \ No newline at end of file diff --git a/src/LibHac/FsSrv/Impl/ProgramRegistryManager.cs b/src/LibHac/FsSrv/Impl/ProgramRegistryManager.cs index 6e722f71..be69cc92 100644 --- a/src/LibHac/FsSrv/Impl/ProgramRegistryManager.cs +++ b/src/LibHac/FsSrv/Impl/ProgramRegistryManager.cs @@ -3,43 +3,31 @@ using System.Collections.Generic; using LibHac.Common; using LibHac.Fs; using LibHac.Ncm; +using LibHac.Os; namespace LibHac.FsSrv.Impl; /// /// Handles adding, removing, and accessing from the . /// -/// Based on FS 10.0.0 (nnSdk 10.4.0) -internal class ProgramRegistryManager +/// Based on FS 13.1.0 (nnSdk 13.4.0) +internal class ProgramRegistryManager : IDisposable { // Note: FS keeps each ProgramInfo in a shared_ptr, but there aren't any non-memory resources // that need to be freed, so we use plain ProgramInfos - private LinkedList ProgramInfoList { get; } - private FileSystemServer FsServer { get; } + private LinkedList _programInfoList; + private SdkMutexType _mutex; - // Note: This variable is global in FS. It's moved to ProgramRegistryManager here because it - // relies on some state kept in FileSystemServer, and it's only used by ProgramRegistryManager - private ProgramInfo _programInfoForInitialProcess; - private readonly object _programInfoForInitialProcessGuard = new object(); + private FileSystemServer _fsServer; public ProgramRegistryManager(FileSystemServer fsServer) { - ProgramInfoList = new LinkedList(); - FsServer = fsServer; + _programInfoList = new LinkedList(); + _mutex = new SdkMutexType(); + _fsServer = fsServer; } - private ProgramInfo GetProgramInfoForInitialProcess() - { - if (_programInfoForInitialProcess == null) - { - lock (_programInfoForInitialProcessGuard) - { - _programInfoForInitialProcess ??= ProgramInfo.CreateProgramInfoForInitialProcess(FsServer); - } - } - - return _programInfoForInitialProcess; - } + public void Dispose() { } /// /// Registers a program with information about that program in the program registry. @@ -54,20 +42,19 @@ internal class ProgramRegistryManager public Result RegisterProgram(ulong processId, ProgramId programId, StorageId storageId, ReadOnlySpan accessControlData, ReadOnlySpan accessControlDescriptor) { - var programInfo = new ProgramInfo(FsServer, processId, programId, storageId, accessControlData, + var programInfo = new ProgramInfo(_fsServer, processId, programId, storageId, accessControlData, accessControlDescriptor); - lock (ProgramInfoList) - { - foreach (ProgramInfo info in ProgramInfoList) - { - if (info.Contains(processId)) - return ResultFs.InvalidArgument.Log(); - } + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - ProgramInfoList.AddLast(programInfo); - return Result.Success; + foreach (ProgramInfo info in _programInfoList) + { + if (info.Contains(processId)) + return ResultFs.InvalidArgument.Log(); } + + _programInfoList.AddLast(programInfo); + return Result.Success; } /// @@ -78,19 +65,18 @@ internal class ProgramRegistryManager /// : The process ID is not registered. public Result UnregisterProgram(ulong processId) { - lock (ProgramInfoList) - { - for (LinkedListNode node = ProgramInfoList.First; node != null; node = node.Next) - { - if (node.Value.Contains(processId)) - { - ProgramInfoList.Remove(node); - return Result.Success; - } - } + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - return ResultFs.InvalidArgument.Log(); + for (LinkedListNode node = _programInfoList.First; node != null; node = node.Next) + { + if (node.Value.Contains(processId)) + { + _programInfoList.Remove(node); + return Result.Success; + } } + + return ResultFs.InvalidArgument.Log(); } /// @@ -103,26 +89,25 @@ internal class ProgramRegistryManager /// : The was not found. public Result GetProgramInfo(out ProgramInfo programInfo, ulong processId) { - lock (ProgramInfoList) + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + if (_fsServer.IsInitialProgram(processId)) { - if (ProgramInfo.IsInitialProgram(processId)) + programInfo = ProgramInfo.GetProgramInfoForInitialProcess(_fsServer); + return Result.Success; + } + + foreach (ProgramInfo info in _programInfoList) + { + if (info.Contains(processId)) { - programInfo = GetProgramInfoForInitialProcess(); + programInfo = info; return Result.Success; } - - foreach (ProgramInfo info in ProgramInfoList) - { - if (info.Contains(processId)) - { - programInfo = info; - return Result.Success; - } - } - - UnsafeHelpers.SkipParamInit(out programInfo); - return ResultFs.ProgramInfoNotFound.Log(); } + + UnsafeHelpers.SkipParamInit(out programInfo); + return ResultFs.ProgramInfoNotFound.Log(); } /// @@ -135,77 +120,18 @@ internal class ProgramRegistryManager /// : The was not found. public Result GetProgramInfoByProgramId(out ProgramInfo programInfo, ulong programId) { - lock (ProgramInfoList) + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + foreach (ProgramInfo info in _programInfoList) { - foreach (ProgramInfo info in ProgramInfoList) + if (info.ProgramId.Value == programId) { - if (info.ProgramId.Value == programId) - { - programInfo = info; - return Result.Success; - } + programInfo = info; + return Result.Success; } - - UnsafeHelpers.SkipParamInit(out programInfo); - return ResultFs.ProgramInfoNotFound.Log(); } + + UnsafeHelpers.SkipParamInit(out programInfo); + return ResultFs.ProgramInfoNotFound.Log(); } -} - -public class ProgramInfo -{ - private ulong ProcessId { get; } - public ProgramId ProgramId { get; } - public StorageId StorageId { get; } - public AccessControl AccessControl { get; } - - public ulong ProgramIdValue => ProgramId.Value; - - public ProgramInfo(FileSystemServer fsServer, ulong processId, ProgramId programId, StorageId storageId, - ReadOnlySpan accessControlData, ReadOnlySpan accessControlDescriptor) - { - ProcessId = processId; - AccessControl = new AccessControl(fsServer, accessControlData, accessControlDescriptor); - ProgramId = programId; - StorageId = storageId; - } - - private ProgramInfo(FileSystemServer fsServer, ReadOnlySpan accessControlData, - ReadOnlySpan accessControlDescriptor) - { - ProcessId = 0; - AccessControl = new AccessControl(fsServer, accessControlData, accessControlDescriptor, ulong.MaxValue); - ProgramId = default; - StorageId = 0; - } - - public bool Contains(ulong processId) => ProcessId == processId; - - public static bool IsInitialProgram(ulong processId) - { - // Todo: We have no kernel to call into, so use hardcoded values for now - const int initialProcessIdLowerBound = 1; - const int initialProcessIdUpperBound = 0x50; - - return initialProcessIdLowerBound <= processId && processId <= initialProcessIdUpperBound; - } - - internal static ProgramInfo CreateProgramInfoForInitialProcess(FileSystemServer fsServer) - { - return new ProgramInfo(fsServer, InitialProcessAccessControlDataHeader, - InitialProcessAccessControlDescriptor); - } - - private static ReadOnlySpan InitialProcessAccessControlDataHeader => new byte[] - { - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x1C, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 - }; - - private static ReadOnlySpan InitialProcessAccessControlDescriptor => new byte[] - { - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF - }; -} +} \ No newline at end of file diff --git a/src/LibHac/FsSrv/ProgramRegistryImpl.cs b/src/LibHac/FsSrv/ProgramRegistryImpl.cs index 973714ce..fc7c3633 100644 --- a/src/LibHac/FsSrv/ProgramRegistryImpl.cs +++ b/src/LibHac/FsSrv/ProgramRegistryImpl.cs @@ -50,7 +50,7 @@ public class ProgramRegistryImpl : IProgramRegistry public Result RegisterProgram(ulong processId, ProgramId programId, StorageId storageId, InBuffer accessControlData, InBuffer accessControlDescriptor) { - if (!ProgramInfo.IsInitialProgram(_processId)) + if (!_fsServer.IsInitialProgram(_processId)) return ResultFs.PermissionDenied.Log(); return Globals.ServiceImpl.RegisterProgramInfo(processId, programId, storageId, accessControlData.Buffer, @@ -63,7 +63,7 @@ public class ProgramRegistryImpl : IProgramRegistry /// public Result UnregisterProgram(ulong processId) { - if (!ProgramInfo.IsInitialProgram(_processId)) + if (!_fsServer.IsInitialProgram(_processId)) return ResultFs.PermissionDenied.Log(); return Globals.ServiceImpl.UnregisterProgramInfo(processId); @@ -91,4 +91,4 @@ public class ProgramRegistryImpl : IProgramRegistry _processId = processId; return Result.Success; } -} +} \ No newline at end of file diff --git a/src/LibHac/FsSrv/SaveDataFileSystemService.cs b/src/LibHac/FsSrv/SaveDataFileSystemService.cs index bf148599..fb1287c6 100644 --- a/src/LibHac/FsSrv/SaveDataFileSystemService.cs +++ b/src/LibHac/FsSrv/SaveDataFileSystemService.cs @@ -517,7 +517,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave // Only the FS process may delete the save indexer's save data. if (saveDataId == SaveData.SaveIndexerId) { - if (!IsCurrentProcess(_processId)) + if (!_serviceImpl.FsServer.IsCurrentProcess(_processId)) return ResultFs.PermissionDenied.Log(); actualSpaceId = spaceId; @@ -2157,13 +2157,6 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave return _serviceImpl.GetProgramInfo(out programInfo, _processId); } - private bool IsCurrentProcess(ulong processId) - { - ulong currentId = Hos.Os.GetCurrentProcessId().Value; - - return processId == currentId; - } - private SaveDataSpaceId ConvertToRealSpaceId(SaveDataSpaceId spaceId) { return spaceId == SaveDataSpaceId.ProperSystem || spaceId == SaveDataSpaceId.SafeMode diff --git a/src/LibHac/Horizon.cs b/src/LibHac/Horizon.cs index e1756c0f..1d58acb1 100644 --- a/src/LibHac/Horizon.cs +++ b/src/LibHac/Horizon.cs @@ -12,8 +12,6 @@ namespace LibHac; public class Horizon { - private const int InitialProcessCountMax = 0x50; - internal ITickGenerator TickGenerator { get; } internal ServiceManager ServiceManager { get; } private HorizonClient LoaderClient { get; } @@ -23,7 +21,7 @@ public class Horizon public Horizon(HorizonConfiguration config) { - _currentProcessId = InitialProcessCountMax; + _currentProcessId = OsState.InitialProcessCountMax + 1; TickGenerator = config.TickGenerator ?? new DefaultTickGenerator(); ServiceManager = new ServiceManager(); @@ -35,7 +33,7 @@ public class Horizon { ulong processId = Interlocked.Increment(ref _currentInitialProcessId); - Abort.DoAbortUnless(processId <= InitialProcessCountMax, "Created too many privileged clients."); + Abort.DoAbortUnless(processId <= OsState.InitialProcessCountMax, "Created too many privileged clients."); // Todo: Register process with FS @@ -82,4 +80,4 @@ public class Horizon return client; } -} +} \ No newline at end of file diff --git a/src/LibHac/Os/OsState.cs b/src/LibHac/Os/OsState.cs index 3a1d27a5..1322c9db 100644 --- a/src/LibHac/Os/OsState.cs +++ b/src/LibHac/Os/OsState.cs @@ -5,6 +5,9 @@ namespace LibHac.Os; public class OsState : IDisposable { + internal const int InitialProcessCountMin = 1; + internal const int InitialProcessCountMax = 0x50; + public OsStateImpl Impl => new OsStateImpl(this); internal HorizonClient Hos { get; } internal OsResourceManager ResourceManager { get; } @@ -34,4 +37,4 @@ public readonly struct OsStateImpl internal HorizonClient Hos => Os.Hos; internal OsStateImpl(OsState parent) => Os = parent; -} +} \ No newline at end of file