Add DebugConfigurationService

This commit is contained in:
Alex Barney 2022-01-19 13:17:10 -07:00
parent 37b8249a8b
commit ed6a34d857
7 changed files with 359 additions and 3 deletions

View file

@ -0,0 +1,74 @@
using System;
using LibHac.Common;
using LibHac.FsSrv.Sf;
using LibHac.Sf;
namespace LibHac.Fs.Shim;
public enum DebugOptionKey : uint
{
SaveDataEncryption = 0x20454453 // "SDE "
}
/// <summary>
/// Contains debug-related functions for the FS service.
/// </summary>
/// <remarks>Based on nnSdk 13.4.0</remarks>
public static class DebugShim
{
public static Result CreatePaddingFile(this FileSystemClient fs, long size)
{
using SharedRef<IFileSystemProxy> fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject();
Result rc = fileSystemProxy.Get.CreatePaddingFile(size);
fs.Impl.AbortIfNeeded(rc);
if (rc.IsFailure()) return rc.Miss();
return Result.Success;
}
public static Result DeleteAllPaddingFiles(this FileSystemClient fs)
{
using SharedRef<IFileSystemProxy> fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject();
Result rc = fileSystemProxy.Get.DeleteAllPaddingFiles();
fs.Impl.AbortIfNeeded(rc);
if (rc.IsFailure()) return rc.Miss();
return Result.Success;
}
public static Result OverrideSaveDataTransferTokenSignVerificationKey(this FileSystemClient fs,
ReadOnlySpan<byte> keyBuffer)
{
using SharedRef<IFileSystemProxy> fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject();
Result rc = fileSystemProxy.Get.OverrideSaveDataTransferTokenSignVerificationKey(new InBuffer(keyBuffer));
fs.Impl.AbortIfNeeded(rc);
if (rc.IsFailure()) return rc.Miss();
return Result.Success;
}
public static Result SetDebugOption(this FileSystemClient fs, DebugOptionKey key, long value)
{
using SharedRef<IFileSystemProxy> fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject();
Result rc = fileSystemProxy.Get.RegisterDebugConfiguration((uint)key, value);
fs.Impl.AbortIfNeeded(rc);
if (rc.IsFailure()) return rc.Miss();
return Result.Success;
}
public static Result UnsetDebugOption(this FileSystemClient fs, DebugOptionKey key)
{
using SharedRef<IFileSystemProxy> fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject();
Result rc = fileSystemProxy.Get.UnregisterDebugConfiguration((uint)key);
fs.Impl.AbortIfNeeded(rc);
if (rc.IsFailure()) return rc.Miss();
return Result.Success;
}
}

View file

@ -0,0 +1,163 @@
using System;
using LibHac.Common.FixedArrays;
using LibHac.Diag;
using LibHac.Fs;
using LibHac.Fs.Shim;
using LibHac.FsSrv.Impl;
using LibHac.Os;
namespace LibHac.FsSrv;
/// <summary>
/// Handles debug configuration calls for <see cref="FileSystemProxyImpl"/>.
/// </summary>
/// <remarks>Based on FS 13.1.0 (nnSdk 13.4.0)</remarks>
public struct DebugConfigurationService
{
private DebugConfigurationServiceImpl _serviceImpl;
private ulong _processId;
// LibHac addition
private readonly FileSystemServer _fsServer;
public DebugConfigurationService(FileSystemServer fsServer, DebugConfigurationServiceImpl serviceImpl,
ulong processId)
{
_serviceImpl = serviceImpl;
_processId = processId;
_fsServer = fsServer;
}
private Result GetProgramInfo(out ProgramInfo programInfo, ulong processId)
{
using var programRegistry = new ProgramRegistryImpl(_fsServer);
return programRegistry.GetProgramInfo(out programInfo, processId);
}
public Result Register(uint key, long value)
{
Result rc = GetProgramInfo(out ProgramInfo programInfo, _processId);
if (rc.IsFailure()) return rc.Miss();
if (!programInfo.AccessControl.CanCall(OperationType.SetDebugConfiguration))
return ResultFs.PermissionDenied.Log();
_serviceImpl.Register(key, value);
return Result.Success;
}
public Result Unregister(uint key)
{
Result rc = GetProgramInfo(out ProgramInfo programInfo, _processId);
if (rc.IsFailure()) return rc.Miss();
if (!programInfo.AccessControl.CanCall(OperationType.SetDebugConfiguration))
return ResultFs.PermissionDenied.Log();
_serviceImpl.Unregister(key);
return Result.Success;
}
}
/// <summary>
/// Manages a key-value list of debug settings.
/// </summary>
/// <remarks>Based on FS 13.1.0 (nnSdk 13.4.0)</remarks>
public class DebugConfigurationServiceImpl : IDisposable
{
private Configuration _config;
private Array4<Entry> _entries;
private SdkMutexType _mutex;
public struct Configuration
{
public bool IsDisabled;
}
private struct Entry
{
public uint Key;
public long Value;
}
public DebugConfigurationServiceImpl(in Configuration config)
{
_config = config;
_mutex = new SdkMutexType();
}
public void Dispose() { }
public void Register(uint key, long value)
{
Abort.DoAbortUnless(key != 0);
if (_config.IsDisabled)
return;
using ScopedLock<SdkMutexType> scopedLock = ScopedLock.Lock(ref _mutex);
for (int i = 0; i < _entries.ItemsRo.Length; i++)
{
// Update the existing value if the key is already registered
if (_entries[i].Key == key)
{
_entries[i].Value = value;
return;
}
}
for (int i = 0; i < _entries.ItemsRo.Length; i++)
{
if (_entries[i].Key == 0)
{
_entries[i].Key = key;
_entries[i].Value = value;
return;
}
}
Abort.DoAbort();
}
public void Unregister(uint key)
{
Abort.DoAbortUnless(key != 0);
if (_config.IsDisabled)
return;
using ScopedLock<SdkMutexType> scopedLock = ScopedLock.Lock(ref _mutex);
for (int i = 0; i < _entries.ItemsRo.Length; i++)
{
if (_entries[i].Key == key)
{
_entries[i].Key = 0;
_entries[i].Value = 0;
break;
}
}
}
public long Get(DebugOptionKey key, long defaultValue)
{
Abort.DoAbortUnless(key != 0);
if (_config.IsDisabled)
return defaultValue;
using ScopedLock<SdkMutexType> scopedLock = ScopedLock.Lock(ref _mutex);
for (int i = 0; i < _entries.ItemsRo.Length; i++)
{
if (_entries[i].Key == (uint)key)
{
return _entries[i].Value;
}
}
return defaultValue;
}
}

View file

@ -14,4 +14,5 @@ public class FileSystemProxyConfiguration
public StatusReportServiceImpl StatusReportService { get; set; }
public ProgramRegistryServiceImpl ProgramRegistryService { get; set; }
public AccessLogServiceImpl AccessLogService { get; set; }
public DebugConfigurationServiceImpl DebugConfigurationService { get; set; }
}

View file

@ -35,6 +35,7 @@ public static class FileSystemProxyImplGlobalMethods
g.StatusReportServiceImpl = configuration.StatusReportService;
g.ProgramRegistryServiceImpl = configuration.ProgramRegistryService;
g.AccessLogServiceImpl = configuration.AccessLogService;
g.DebugConfigurationServiceImpl = configuration.DebugConfigurationService;
}
}
@ -49,6 +50,7 @@ internal struct FileSystemProxyImplGlobals
public StatusReportServiceImpl StatusReportServiceImpl;
public ProgramRegistryServiceImpl ProgramRegistryServiceImpl;
public AccessLogServiceImpl AccessLogServiceImpl;
public DebugConfigurationServiceImpl DebugConfigurationServiceImpl;
public Optional<FileSystemProxyCoreImpl> FileSystemProxyCoreImpl;
}
@ -146,6 +148,11 @@ public class FileSystemProxyImpl : IFileSystemProxy, IFileSystemProxyForLoader
return new AccessLogService(Globals.AccessLogServiceImpl, _currentProcess);
}
private DebugConfigurationService GetDebugConfigurationService()
{
return new DebugConfigurationService(_fsServer, Globals.DebugConfigurationServiceImpl, _currentProcess);
}
public Result OpenFileSystemWithId(ref SharedRef<IFileSystemSf> outFileSystem, in FspPath path,
ulong id, FileSystemProxyType fsType)
{
@ -1151,4 +1158,14 @@ public class FileSystemProxyImpl : IFileSystemProxy, IFileSystemProxyForLoader
{
return GetBaseFileSystemService().OpenBisWiper(ref outBisWiper, transferMemoryHandle, transferMemorySize);
}
public Result RegisterDebugConfiguration(uint key, long value)
{
return GetDebugConfigurationService().Register(key, value);
}
public Result UnregisterDebugConfiguration(uint key)
{
return GetDebugConfigurationService().Unregister(key);
}
}

View file

@ -74,6 +74,11 @@ public static class FileSystemServerInitializer
Memory<byte> heapBuffer = GC.AllocateArray<byte>(BufferManagerHeapSize, true);
bufferManager.Initialize(BufferManagerCacheSize, heapBuffer, BufferManagerBlockSize);
// Todo: Assign based on the value of "IsDevelopment"
var debugConfigurationServiceConfig = new DebugConfigurationServiceImpl.Configuration();
debugConfigurationServiceConfig.IsDisabled = false;
var debugConfigurationService = new DebugConfigurationServiceImpl(in debugConfigurationServiceConfig);
var saveDataIndexerManager = new SaveDataIndexerManager(server.Hos.Fs, Fs.SaveData.SaveIndexerId,
new ArrayPoolMemoryResource(), new SdHandleManager(), false);
@ -175,7 +180,8 @@ public static class FileSystemServerInitializer
TimeService = timeService,
StatusReportService = statusReportService,
ProgramRegistryService = programRegistryService,
AccessLogService = accessLogService
AccessLogService = accessLogService,
DebugConfigurationService = debugConfigurationService
};
server.InitializeFileSystemProxy(fspConfig);

View file

@ -124,6 +124,8 @@ public interface IFileSystemProxy : IDisposable
Result FlushAccessLogOnSdCard();
Result OutputApplicationInfoAccessLog(in ApplicationInfo applicationInfo);
Result OutputMultiProgramTagAccessLog();
Result RegisterDebugConfiguration(uint key, long value);
Result UnregisterDebugConfiguration(uint key);
Result OverrideSaveDataTransferTokenSignVerificationKey(InBuffer key);
Result CorruptSaveDataFileSystemByOffset(SaveDataSpaceId spaceId, ulong saveDataId, long offset);
Result OpenMultiCommitManager(ref SharedRef<IMultiCommitManager> outCommitManager);

View file

@ -0,0 +1,93 @@
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Shim;
using LibHac.Ncm;
using Xunit;
namespace LibHac.Tests.Fs.FileSystemClientTests.ShimTests;
public class Debug
{
[Fact]
public void SetDebugOption_KeyIsZero_Aborts()
{
FileSystemClient fs = FileSystemServerFactory.CreateClient(true);
Assert.Throws<HorizonResultException>(() => fs.SetDebugOption(0, 1));
}
[Fact]
public void SetDebugOption_NoPermissions_ReturnsPermissionDenied()
{
Horizon hos = HorizonFactory.CreateBasicHorizon();
HorizonClient client =
hos.CreateHorizonClient(new ProgramLocation(new ProgramId(1), StorageId.BuiltInSystem), 0);
Assert.Result(ResultFs.PermissionDenied, client.Fs.SetDebugOption((DebugOptionKey)1, 0));
}
[Fact]
public void SetDebugOption_DebugConfigIsFull_Aborts()
{
FileSystemClient fs = FileSystemServerFactory.CreateClient(true);
Assert.Success(fs.SetDebugOption((DebugOptionKey)1, 0));
Assert.Success(fs.SetDebugOption((DebugOptionKey)2, 0));
Assert.Success(fs.SetDebugOption((DebugOptionKey)3, 0));
Assert.Success(fs.SetDebugOption((DebugOptionKey)4, 0));
Assert.Throws<LibHacException>(() => fs.SetDebugOption((DebugOptionKey)5, 0));
}
[Fact]
public void SetDebugOption_ReplaceExistingValueWhenFull_ReturnsSuccess()
{
FileSystemClient fs = FileSystemServerFactory.CreateClient(true);
Assert.Success(fs.SetDebugOption((DebugOptionKey)1, 0));
Assert.Success(fs.SetDebugOption((DebugOptionKey)2, 0));
Assert.Success(fs.SetDebugOption((DebugOptionKey)3, 0));
Assert.Success(fs.SetDebugOption((DebugOptionKey)4, 0));
Assert.Success(fs.SetDebugOption((DebugOptionKey)1, 10));
}
[Fact]
public void SetDebugOption_AfterRemovingKeyWhenFull_ReturnsSuccess()
{
FileSystemClient fs = FileSystemServerFactory.CreateClient(true);
Assert.Success(fs.SetDebugOption((DebugOptionKey)1, 0));
Assert.Success(fs.SetDebugOption((DebugOptionKey)2, 0));
Assert.Success(fs.SetDebugOption((DebugOptionKey)3, 0));
Assert.Success(fs.SetDebugOption((DebugOptionKey)4, 0));
Assert.Success(fs.UnsetDebugOption((DebugOptionKey)2));
Assert.Success(fs.SetDebugOption((DebugOptionKey)2, 4));
}
[Fact]
public void UnsetDebugOption_UnsetExistingKey_ReturnsSuccess()
{
const DebugOptionKey key = DebugOptionKey.SaveDataEncryption;
const long value = 0;
FileSystemClient fs = FileSystemServerFactory.CreateClient(true);
Assert.Success(fs.SetDebugOption(key, value));
Assert.Success(fs.UnsetDebugOption(key));
}
[Fact]
public void UnsetDebugOption_NoPermissions_ReturnsPermissionDenied()
{
Horizon hos = HorizonFactory.CreateBasicHorizon();
HorizonClient client =
hos.CreateHorizonClient(new ProgramLocation(new ProgramId(1), StorageId.BuiltInSystem), 0);
Assert.Result(ResultFs.PermissionDenied, client.Fs.UnsetDebugOption((DebugOptionKey)1));
}
}