Add DeviceSaveData mounting functions

This commit is contained in:
Alex Barney 2022-04-30 23:58:19 -07:00
parent c2e4b80bac
commit e70e23492f
4 changed files with 308 additions and 30 deletions

View file

@ -0,0 +1,179 @@
using System;
using LibHac.Common;
using LibHac.Diag;
using LibHac.Fs.Fsa;
using LibHac.Fs.Impl;
using LibHac.FsSrv.Sf;
using LibHac.Ncm;
using LibHac.Os;
using IFileSystem = LibHac.Fs.Fsa.IFileSystem;
using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem;
using static LibHac.Fs.Impl.AccessLogStrings;
using static LibHac.Fs.SaveData;
namespace LibHac.Fs.Shim;
public static class DeviceSaveData
{
private class DeviceSaveDataAttributeGetter : ISaveDataAttributeGetter
{
private ProgramId _programId;
public DeviceSaveDataAttributeGetter(ProgramId programId)
{
_programId = programId;
}
public void Dispose() { }
public Result GetSaveDataAttribute(out SaveDataAttribute attribute)
{
Result rc = SaveDataAttribute.Make(out attribute, _programId, SaveDataType.Device, InvalidUserId,
InvalidSystemSaveDataId);
if (rc.IsFailure()) return rc.Miss();
return Result.Success;
}
}
private const SaveDataSpaceId DeviceSaveDataSpaceId = SaveDataSpaceId.User;
private static Result MountDeviceSaveDataImpl(this FileSystemClientImpl fs, U8Span mountName,
in SaveDataAttribute attribute)
{
Result rc = fs.CheckMountName(mountName);
if (rc.IsFailure()) return rc;
using SharedRef<IFileSystemProxy> fileSystemProxy = fs.GetFileSystemProxyServiceObject();
using var fileSystem = new SharedRef<IFileSystemSf>();
rc = fileSystemProxy.Get.OpenSaveDataFileSystem(ref fileSystem.Ref(), DeviceSaveDataSpaceId, in attribute);
if (rc.IsFailure()) return rc.Miss();
var fileSystemAdapterRaw = new FileSystemServiceObjectAdapter(ref fileSystem.Ref());
using var fileSystemAdapter = new UniqueRef<IFileSystem>(fileSystemAdapterRaw);
if (!fileSystemAdapter.HasValue)
return ResultFs.AllocationMemoryFailedInDeviceSaveDataA.Log();
using var saveDataAttributeGetter =
new UniqueRef<ISaveDataAttributeGetter>(new DeviceSaveDataAttributeGetter(attribute.ProgramId));
using var mountNameGenerator = new UniqueRef<ICommonMountNameGenerator>();
rc = fs.Fs.Register(mountName, fileSystemAdapterRaw, ref fileSystemAdapter.Ref(), ref mountNameGenerator.Ref(),
ref saveDataAttributeGetter.Ref(), useDataCache: false, storageForPurgeFileDataCache: null,
usePathCache: true);
if (rc.IsFailure()) return rc.Miss();
return Result.Success;
}
public static Result MountDeviceSaveData(this FileSystemClient fs, U8Span mountName)
{
Span<byte> logBuffer = stackalloc byte[0x30];
Result rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, InvalidProgramId, SaveDataType.Device,
InvalidUserId, InvalidSystemSaveDataId);
fs.Impl.AbortIfNeeded(rc);
if (rc.IsFailure()) return rc.Miss();
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application))
{
Tick start = fs.Hos.Os.GetSystemTick();
rc = MountDeviceSaveDataImpl(fs.Impl, mountName, in attribute);
Tick end = fs.Hos.Os.GetSystemTick();
var sb = new U8StringBuilder(logBuffer, true);
sb.Append(LogName).Append(mountName).Append(LogQuote);
fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer));
}
else
{
rc = MountDeviceSaveDataImpl(fs.Impl, mountName, in attribute);
}
fs.Impl.AbortIfNeeded(rc);
if (rc.IsFailure()) return rc.Miss();
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application))
fs.Impl.EnableFileSystemAccessorAccessLog(mountName);
return Result.Success;
}
public static Result MountDeviceSaveData(this FileSystemClient fs, U8Span mountName,
Ncm.ApplicationId applicationId)
{
Span<byte> logBuffer = stackalloc byte[0x50];
Result rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, applicationId, SaveDataType.Device,
InvalidUserId, InvalidSystemSaveDataId);
fs.Impl.AbortIfNeeded(rc);
if (rc.IsFailure()) return rc.Miss();
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application))
{
Tick start = fs.Hos.Os.GetSystemTick();
rc = MountDeviceSaveDataImpl(fs.Impl, mountName, in attribute);
Tick end = fs.Hos.Os.GetSystemTick();
var sb = new U8StringBuilder(logBuffer, true);
sb.Append(LogName).Append(mountName).Append(LogQuote)
.Append(LogApplicationId).AppendFormat(applicationId.Value, 'X');
fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer));
}
else
{
rc = MountDeviceSaveDataImpl(fs.Impl, mountName, in attribute);
}
fs.Impl.AbortIfNeeded(rc);
if (rc.IsFailure()) return rc.Miss();
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application))
fs.Impl.EnableFileSystemAccessorAccessLog(mountName);
return Result.Success;
}
public static Result MountDeviceSaveData(this FileSystemClient fs, U8Span mountName, ApplicationId applicationId)
{
return MountDeviceSaveData(fs, mountName, new Ncm.ApplicationId(applicationId.Value));
}
public static bool IsDeviceSaveDataExisting(this FileSystemClient fs, ApplicationId applicationId)
{
Result rc;
Span<byte> logBuffer = stackalloc byte[0x30];
bool exists;
var appId = new Ncm.ApplicationId(applicationId.Value);
if (fs.Impl.IsEnabledAccessLog() && fs.Impl.IsEnabledHandleAccessLog(null))
{
Tick start = fs.Hos.Os.GetSystemTick();
rc = fs.Impl.IsSaveDataExisting(out exists, appId, SaveDataType.Device, InvalidUserId);
Tick end = fs.Hos.Os.GetSystemTick();
var sb = new U8StringBuilder(logBuffer, true);
sb.Append(LogApplicationId).AppendFormat(applicationId.Value, 'X');
fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer));
}
else
{
rc = fs.Impl.IsSaveDataExisting(out exists, appId, SaveDataType.Device, InvalidUserId);
}
fs.Impl.LogResultErrorMessage(rc);
Abort.DoAbortUnless(rc.IsSuccess());
return exists;
}
}

View file

@ -147,6 +147,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
} }
else if (attribute.Type == SaveDataType.Account && attribute.UserId == InvalidUserId) else if (attribute.Type == SaveDataType.Account && attribute.UserId == InvalidUserId)
{ {
// Trying to create a program's debug save.
bool canAccess = bool canAccess =
accessControl.CanCall(OperationType.CreateSaveData) || accessControl.CanCall(OperationType.CreateSaveData) ||
accessControl.CanCall(OperationType.DebugSaveData); accessControl.CanCall(OperationType.DebugSaveData);
@ -193,6 +194,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
} }
else if (attribute.Type == SaveDataType.Account) else if (attribute.Type == SaveDataType.Account)
{ {
// We need debug save data permissions to open a debug save.
bool canAccess = attribute.UserId != InvalidUserId || bool canAccess = attribute.UserId != InvalidUserId ||
accessControl.CanCall(OperationType.DebugSaveData); accessControl.CanCall(OperationType.DebugSaveData);
@ -303,11 +305,11 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
{ {
canAccess |= accessControl.CanCall(OperationType.ExtendOwnSaveData); canAccess |= accessControl.CanCall(OperationType.ExtendOwnSaveData);
bool hasDebugAccess = accessControl.CanCall(OperationType.DebugSaveData) bool canAccessDebugSave = accessControl.CanCall(OperationType.DebugSaveData)
&& attribute.Type == SaveDataType.Account && attribute.Type == SaveDataType.Account
&& attribute.UserId == UserId.InvalidId; && attribute.UserId == UserId.InvalidId;
canAccess |= hasDebugAccess; canAccess |= canAccessDebugSave;
if (!canAccess) if (!canAccess)
return ResultFs.PermissionDenied.Log(); return ResultFs.PermissionDenied.Log();
@ -351,11 +353,11 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
{ {
canAccess |= accessControl.CanCall(OperationType.ReadOwnSaveDataFileSystemExtraData); canAccess |= accessControl.CanCall(OperationType.ReadOwnSaveDataFileSystemExtraData);
bool hasDebugAccess = accessControl.CanCall(OperationType.DebugSaveData) bool canAccessDebugSave = accessControl.CanCall(OperationType.DebugSaveData)
&& attribute.Type == SaveDataType.Account && attribute.Type == SaveDataType.Account
&& attribute.UserId == UserId.InvalidId; && attribute.UserId == UserId.InvalidId;
canAccess |= hasDebugAccess; canAccess |= canAccessDebugSave;
} }
if (!canAccess) if (!canAccess)
@ -442,11 +444,11 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
AccessControl accessControl = programInfo.AccessControl; AccessControl accessControl = programInfo.AccessControl;
canAccess = accessControl.CanCall(OperationType.FindOwnSaveDataWithFilter); canAccess = accessControl.CanCall(OperationType.FindOwnSaveDataWithFilter);
bool hasDebugAccess = accessControl.CanCall(OperationType.DebugSaveData) bool canAccessDebugSave = accessControl.CanCall(OperationType.DebugSaveData)
&& filter.Attribute.Type == SaveDataType.Account && filter.Attribute.Type == SaveDataType.Account
&& filter.Attribute.UserId == UserId.InvalidId; && filter.Attribute.UserId == UserId.InvalidId;
canAccess |= hasDebugAccess; canAccess |= canAccessDebugSave;
} }
else else
{ {

View file

@ -2,27 +2,45 @@
using LibHac.Fs; using LibHac.Fs;
using LibHac.Fs.Fsa; using LibHac.Fs.Fsa;
using LibHac.FsSrv; using LibHac.FsSrv;
using LibHac.FsSrv.Impl;
using LibHac.FsSystem; using LibHac.FsSystem;
using LibHac.Ncm;
using LibHac.Tools.Fs; using LibHac.Tools.Fs;
namespace LibHac.Tests.Fs.FileSystemClientTests; namespace LibHac.Tests.Fs.FileSystemClientTests;
public class HorizonServerSet
{
public Horizon Server { get; set; }
public HorizonClient FsProcessClient { get; set; }
public HorizonClient InitialProcessClient { get; set; }
public HorizonClient Client { get; set; }
public FileSystemServer FsServer { get; set; }
public IFileSystem RootFileSystem { get; set; }
}
public static class FileSystemServerFactory public static class FileSystemServerFactory
{ {
private static Horizon CreateHorizonImpl(bool sdCardInserted, out IFileSystem rootFs) private static HorizonServerSet CreateHorizonImpl(bool sdCardInserted = true,
AccessControlBits.Bits fsAcBits = AccessControlBits.Bits.Debug, ProgramLocation programLocation = default)
{ {
rootFs = new InMemoryFileSystem(); var hos = new HorizonServerSet();
hos.RootFileSystem = new InMemoryFileSystem();
var keySet = new KeySet(); var keySet = new KeySet();
var horizon = new Horizon(new HorizonConfiguration()); hos.Server = new Horizon(new HorizonConfiguration());
HorizonClient fsServerClient = horizon.CreatePrivilegedHorizonClient(); hos.FsProcessClient = hos.Server.CreatePrivilegedHorizonClient();
var fsServer = new FileSystemServer(fsServerClient); hos.FsServer = new FileSystemServer(hos.FsProcessClient);
hos.InitialProcessClient = hos.Server.CreatePrivilegedHorizonClient();
var random = new Random(12345); var random = new Random(12345);
RandomDataGenerator randomGenerator = buffer => random.NextBytes(buffer); RandomDataGenerator randomGenerator = buffer => random.NextBytes(buffer);
var defaultObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(rootFs, keySet, fsServer, randomGenerator); var defaultObjects =
DefaultFsServerObjects.GetDefaultEmulatedCreators(hos.RootFileSystem, keySet, hos.FsServer,
randomGenerator);
defaultObjects.SdCard.SetSdCardInsertionStatus(sdCardInserted); defaultObjects.SdCard.SetSdCardInsertionStatus(sdCardInserted);
@ -32,31 +50,45 @@ public static class FileSystemServerFactory
config.ExternalKeySet = new ExternalKeySet(); config.ExternalKeySet = new ExternalKeySet();
config.RandomGenerator = randomGenerator; config.RandomGenerator = randomGenerator;
FileSystemServerInitializer.InitializeWithConfig(fsServerClient, fsServer, config); FileSystemServerInitializer.InitializeWithConfig(hos.FsProcessClient, hos.FsServer, config);
return horizon; hos.FsServer.SetDebugFlagEnabled(true);
}
private static FileSystemClient CreateClientImpl(bool sdCardInserted, out IFileSystem rootFs) if (programLocation.ProgramId == ProgramId.InvalidId)
{ {
Horizon horizon = CreateHorizonImpl(sdCardInserted, out rootFs); hos.Client = hos.Server.CreateHorizonClient();
}
else
{
hos.Client = hos.Server.CreateHorizonClient(programLocation, fsAcBits);
}
HorizonClient horizonClient = horizon.CreatePrivilegedHorizonClient(); return hos;
return horizonClient.Fs;
} }
public static FileSystemClient CreateClient(bool sdCardInserted) public static FileSystemClient CreateClient(bool sdCardInserted)
{ {
return CreateClientImpl(sdCardInserted, out _); HorizonServerSet hos = CreateHorizonImpl(sdCardInserted: sdCardInserted);
return hos.InitialProcessClient.Fs;
} }
public static FileSystemClient CreateClient(out IFileSystem rootFs) public static FileSystemClient CreateClient(out IFileSystem rootFs)
{ {
return CreateClientImpl(false, out rootFs); HorizonServerSet hos = CreateHorizonImpl(sdCardInserted: true);
rootFs = hos.RootFileSystem;
return hos.InitialProcessClient.Fs;
} }
public static Horizon CreateHorizonServer() public static Horizon CreateHorizonServer()
{ {
return CreateHorizonImpl(true, out _); return CreateHorizonImpl(sdCardInserted: true).Server;
}
public static HorizonServerSet CreateHorizon(ProgramId programId = default, bool sdCardInserted = true,
AccessControlBits.Bits fsAcBits = AccessControlBits.Bits.Debug)
{
var programLocation = new ProgramLocation(programId, StorageId.BuiltInUser);
return CreateHorizonImpl(sdCardInserted, fsAcBits, programLocation);
} }
} }

View file

@ -0,0 +1,65 @@
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Shim;
using LibHac.FsSrv.Impl;
using Xunit;
namespace LibHac.Tests.Fs.FileSystemClientTests.ShimTests;
public class DeviceSaveData
{
[Fact]
public static void MountDeviceSaveData_SaveDoesNotExist_ReturnsTargetNotFound()
{
var applicationId = new Ncm.ApplicationId(1234);
HorizonServerSet hos = FileSystemServerFactory.CreateHorizon(applicationId, fsAcBits: AccessControlBits.Bits.FullPermission);
Assert.Result(ResultFs.TargetNotFound, hos.Client.Fs.MountDeviceSaveData("device".ToU8Span(), applicationId));
Assert.Result(ResultFs.TargetNotFound, hos.Client.Fs.MountDeviceSaveData("device2".ToU8Span()));
}
[Fact]
public static void MountDeviceSaveData_OwnDeviceSaveExists_ReturnsSuccess()
{
var applicationId = new Ncm.ApplicationId(1234);
HorizonServerSet hos = FileSystemServerFactory.CreateHorizon(applicationId, fsAcBits: AccessControlBits.Bits.FullPermission);
Assert.Success(hos.Client.Fs.CreateDeviceSaveData(applicationId, applicationId.Value, 0, 0, SaveDataFlags.None));
Assert.Success(hos.Client.Fs.MountDeviceSaveData("device".ToU8Span()));
Assert.Success(hos.Client.Fs.MountDeviceSaveData("device2".ToU8Span(), applicationId));
}
[Fact]
public static void MountDeviceSaveData_OtherDeviceSaveExists_ReturnsSuccess()
{
var ownApplicationId = new Ncm.ApplicationId(1234);
var otherApplicationId = new Ncm.ApplicationId(12345);
HorizonServerSet hos = FileSystemServerFactory.CreateHorizon(ownApplicationId, fsAcBits: AccessControlBits.Bits.FullPermission);
Assert.Success(hos.Client.Fs.CreateDeviceSaveData(otherApplicationId, otherApplicationId.Value, 0, 0, SaveDataFlags.None));
Assert.Success(hos.Client.Fs.MountDeviceSaveData("device".ToU8Span(), otherApplicationId));
// Try to open missing own device save data
Assert.Result(ResultFs.TargetNotFound, hos.Client.Fs.MountDeviceSaveData("device2".ToU8Span()));
}
[Fact]
public static void IsDeviceSaveDataExisting_ReturnsCorrectState()
{
var applicationId = new ApplicationId(1234);
var ncmApplicationId = new Ncm.ApplicationId(applicationId.Value);
HorizonServerSet hos = FileSystemServerFactory.CreateHorizon(ncmApplicationId);
FileSystemClient fs = hos.InitialProcessClient.Fs;
// Should return false when there aren't any saves.
Assert.False(fs.IsDeviceSaveDataExisting(applicationId));
// Should return true after creating the save.
Assert.Success(fs.CreateDeviceSaveData(ncmApplicationId, applicationId.Value, 0, 0, SaveDataFlags.None));
Assert.True(fs.IsDeviceSaveDataExisting(applicationId));
// Should return false after deleting the save.
Assert.Success(fs.DeleteDeviceSaveData(applicationId));
Assert.False(fs.IsDeviceSaveDataExisting(applicationId));
}
}