From 0dc433d8a25b7a3334b7bb699821fa2ad033356e Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Sat, 20 Feb 2021 22:10:12 -0700 Subject: [PATCH] Implement UserFileSystem and mount registration --- build/CodeGen/results.csv | 1 + src/LibHac/Fs/AccessLog.cs | 14 + .../Fs/ApplicationSaveDataManagement.cs | 12 +- src/LibHac/Fs/FsEnums.cs | 4 +- src/LibHac/Fs/Fsa/MountUtility.cs | 232 +++++- src/LibHac/Fs/Fsa/Registrar.cs | 42 +- src/LibHac/Fs/Fsa/UserFile.cs | 4 +- src/LibHac/Fs/Fsa/UserFileSystem.cs | 740 +++++++++++++++++- src/LibHac/Fs/Fsa/UserFileSystemPrivate.cs | 95 +++ src/LibHac/Fs/Fsa/UserMountTable.cs | 9 +- src/LibHac/Fs/ResultFs.cs | 2 + src/LibHac/Fs/SaveData.cs | 6 +- src/LibHac/Fs/Shim/SaveDataManagement.cs | 355 +++++---- 13 files changed, 1304 insertions(+), 212 deletions(-) create mode 100644 src/LibHac/Fs/Fsa/UserFileSystemPrivate.cs diff --git a/build/CodeGen/results.csv b/build/CodeGen/results.csv index 7979ef1f..31764a44 100644 --- a/build/CodeGen/results.csv +++ b/build/CodeGen/results.csv @@ -533,6 +533,7 @@ Module,DescriptionStart,DescriptionEnd,Flags,Namespace,Name,Summary 2,5307,,,,UnexpectedErrorInHostFileFlush, 2,5308,,,,UnexpectedErrorInHostFileGetSize, 2,5309,,,,UnknownHostFileSystemError, +2,5319,,,,UnexpectedInMountUtilityA, 2,5320,,,,InvalidNcaMountPoint, 2,6000,6499,,,PreconditionViolation, diff --git a/src/LibHac/Fs/AccessLog.cs b/src/LibHac/Fs/AccessLog.cs index 6c430ad8..dcf0139f 100644 --- a/src/LibHac/Fs/AccessLog.cs +++ b/src/LibHac/Fs/AccessLog.cs @@ -435,5 +435,19 @@ namespace LibHac.Fs.Impl { throw new NotImplementedException(); } + + public static ReadOnlySpan ConvertFromBoolToAccessLogBooleanValue(bool value) + { + return value ? LogTrue : LogFalse; + } + + private static ReadOnlySpan LogTrue => // "true" + new[] { (byte)'t', (byte)'r', (byte)'u', (byte)'e' }; + + private static ReadOnlySpan LogFalse => // "false" + new[] + { + (byte)'f', (byte)'a', (byte)'l', (byte)'s', (byte)'e' + }; } } diff --git a/src/LibHac/Fs/ApplicationSaveDataManagement.cs b/src/LibHac/Fs/ApplicationSaveDataManagement.cs index 57a4beb1..aa65dee6 100644 --- a/src/LibHac/Fs/ApplicationSaveDataManagement.cs +++ b/src/LibHac/Fs/ApplicationSaveDataManagement.cs @@ -88,7 +88,7 @@ namespace LibHac.Fs filter.SetProgramId(applicationId); filter.SetSaveDataType(SaveDataType.Temporary); - Result rc = fs.FindSaveDataWithFilter(out _, SaveDataSpaceId.Temporary, ref filter); + Result rc = fs.FindSaveDataWithFilter(out _, SaveDataSpaceId.Temporary, in filter); if (rc.IsFailure()) { @@ -176,7 +176,7 @@ namespace LibHac.Fs private static Result EnsureAndExtendSaveData(FileSystemClient fs, Func createFunc, ref long requiredSize, ref SaveDataFilter filter, long baseSize, long dataSize, long journalSize) { - Result rc = fs.FindSaveDataWithFilter(out SaveDataInfo info, SaveDataSpaceId.User, ref filter); + Result rc = fs.FindSaveDataWithFilter(out SaveDataInfo info, SaveDataSpaceId.User, in filter); if (rc.IsFailure()) { @@ -346,7 +346,7 @@ namespace LibHac.Fs filter.SetIndex(index); filter.SetSaveDataType(SaveDataType.Cache); - Result rc = fs.FindSaveDataWithFilter(out SaveDataInfo info, spaceId, ref filter); + Result rc = fs.FindSaveDataWithFilter(out SaveDataInfo info, spaceId, in filter); if (rc.IsFailure()) { @@ -401,7 +401,7 @@ namespace LibHac.Fs if (fs.IsSdCardAccessible()) { - Result rc = fs.FindSaveDataWithFilter(out _, SaveDataSpaceId.SdCache, ref filter); + Result rc = fs.FindSaveDataWithFilter(out _, SaveDataSpaceId.SdCache, in filter); if (rc.IsFailure() && !ResultFs.TargetNotFound.Includes(rc)) return rc; if (rc.IsSuccess()) @@ -413,7 +413,7 @@ namespace LibHac.Fs // Not on the SD card. Check it it's in NAND if (target == CacheStorageTargetMedia.None) { - Result rc = fs.FindSaveDataWithFilter(out _, SaveDataSpaceId.User, ref filter); + Result rc = fs.FindSaveDataWithFilter(out _, SaveDataSpaceId.User, in filter); if (rc.IsFailure() && !ResultFs.TargetNotFound.Includes(rc)) return rc; if (rc.IsSuccess()) @@ -434,7 +434,7 @@ namespace LibHac.Fs while (true) { - rc = fs.FindSaveDataWithFilter(out SaveDataInfo saveInfo, SaveDataSpaceId.Temporary, ref filter); + rc = fs.FindSaveDataWithFilter(out SaveDataInfo saveInfo, SaveDataSpaceId.Temporary, in filter); if (rc.IsFailure()) break; diff --git a/src/LibHac/Fs/FsEnums.cs b/src/LibHac/Fs/FsEnums.cs index b7f90e48..ea16af3b 100644 --- a/src/LibHac/Fs/FsEnums.cs +++ b/src/LibHac/Fs/FsEnums.cs @@ -177,9 +177,11 @@ namespace LibHac.Fs Restore = 1 << 4 } + [Flags] public enum CommitOptionFlag { - None = 1, + None = 0, + ClearRestoreFlag = 1, SetRestoreFlag = 2 } diff --git a/src/LibHac/Fs/Fsa/MountUtility.cs b/src/LibHac/Fs/Fsa/MountUtility.cs index 302c137d..02ffe8b4 100644 --- a/src/LibHac/Fs/Fsa/MountUtility.cs +++ b/src/LibHac/Fs/Fsa/MountUtility.cs @@ -1,6 +1,11 @@ using System; +using System.Runtime.CompilerServices; using LibHac.Common; +using LibHac.Diag; using LibHac.Fs.Impl; +using LibHac.FsSystem; +using LibHac.Os; +using LibHac.Util; namespace LibHac.Fs.Fsa { @@ -8,59 +13,260 @@ namespace LibHac.Fs.Fsa { internal static Result GetMountNameAndSubPath(out MountName mountName, out U8Span subPath, U8Span path) { - throw new NotImplementedException(); + Unsafe.SkipInit(out mountName); + subPath = default; + + int mountLen = 0; + int maxMountLen = Math.Min(path.Length, PathTools.MountNameLengthMax); + + if (PathUtility.IsWindowsDrive(path) || PathUtility.IsUnc(path)) + { + StringUtils.Copy(mountName.Name, CommonPaths.HostRootFileSystemMountName); + mountName.Name[PathTools.MountNameLengthMax] = StringTraits.NullTerminator; + + subPath = path; + return Result.Success; + } + + for (int i = 0; i <= maxMountLen; i++) + { + if (path[i] == PathTools.MountSeparator) + { + mountLen = i; + break; + } + } + + if (mountLen == 0) + return ResultFs.InvalidMountName.Log(); + + if (mountLen > maxMountLen) + return ResultFs.InvalidMountName.Log(); + + if (mountLen <= 0) + return ResultFs.InvalidMountName.Log(); + + U8Span subPathTemp = path.Slice(mountLen + 1); + + if (subPathTemp.Length == 0 || !PathTool.IsAnySeparator(subPathTemp[0])) + return ResultFs.InvalidPathFormat.Log(); + + path.Value.Slice(0, mountLen).CopyTo(mountName.Name); + mountName.Name[mountLen] = StringTraits.NullTerminator; + subPath = subPathTemp; + + return Result.Success; } public static bool IsValidMountName(this FileSystemClientImpl fs, U8Span name) { - throw new NotImplementedException(); + if (name.IsEmpty()) + return false; + + // Check for a single-letter mount name + if ((name.Length <= 1 || name[1] == 0) && + ('a' <= name[0] && name[0] <= 'z' || 'A' <= name[0] && name[0] <= 'Z')) + { + return false; + } + + // Check for mount or directory separators + int length = 0; + for (int i = 0; i < name.Length && name[i] != 0; i++) + { + if (PathTool.IsDriveSeparator(name[i]) || PathTool.IsSeparator(name[i])) + return false; + + if (++length > PathTools.MountNameLengthMax) + return false; + } + + // Todo: VerifyUtf8String + return true; } public static bool IsUsedReservedMountName(this FileSystemClientImpl fs, U8Span name) { - throw new NotImplementedException(); + return name.Length > 0 && name[0] == CommonPaths.ReservedMountNamePrefixCharacter; } internal static Result FindFileSystem(this FileSystemClientImpl fs, out FileSystemAccessor fileSystem, out U8Span subPath, U8Span path) { - throw new NotImplementedException(); + fileSystem = default; + subPath = default; + + if (path.IsNull()) + return ResultFs.NullptrArgument.Log(); + + int hostMountNameLen = StringUtils.GetLength(CommonPaths.HostRootFileSystemMountName); + if (StringUtils.Compare(path, CommonPaths.HostRootFileSystemMountName, hostMountNameLen) == 0) + { + return ResultFs.NotMounted.Log(); + } + + Result rc = GetMountNameAndSubPath(out MountName mountName, out subPath, path); + if (rc.IsFailure()) return rc; + + return fs.Find(out fileSystem, new U8Span(mountName.Name)); } public static Result CheckMountName(this FileSystemClientImpl fs, U8Span name) { - throw new NotImplementedException(); + if (name.IsNull()) + return ResultFs.NullptrArgument.Log(); + + if (fs.IsUsedReservedMountName(name)) + return ResultFs.InvalidMountName.Log(); + + if (fs.IsValidMountName(name)) + return ResultFs.InvalidMountName.Log(); + + return Result.Success; } public static Result CheckMountNameAcceptingReservedMountName(this FileSystemClientImpl fs, U8Span name) { - throw new NotImplementedException(); + if (name.IsNull()) + return ResultFs.NullptrArgument.Log(); + + if (fs.IsValidMountName(name)) + return ResultFs.InvalidMountName.Log(); + + return Result.Success; } public static Result Unmount(this FileSystemClientImpl fs, U8Span mountName) { - throw new NotImplementedException(); + Result rc = fs.Find(out FileSystemAccessor fileSystem, mountName); + if (rc.IsFailure()) return rc; + + if (fileSystem.IsFileDataCacheAttachable()) + { + // Todo: Data cache purge + } + + fs.Unregister(mountName); + return Result.Success; } public static Result IsMounted(this FileSystemClientImpl fs, out bool isMounted, U8Span mountName) { - throw new NotImplementedException(); + Unsafe.SkipInit(out isMounted); + + Result rc = fs.Find(out _, mountName); + if (rc.IsFailure()) + { + if (!ResultFs.NotMounted.Includes(rc)) + return rc; + + isMounted = false; + } + else + { + isMounted = true; + } + + return Result.Success; } - public static Result Unmount(this FileSystemClient fs, U8Span mountName) + public static void Unmount(this FileSystemClient fs, U8Span mountName) { - throw new NotImplementedException(); + Result rc; + Span logBuffer = stackalloc byte[0x30]; + + if (fs.Impl.IsEnabledAccessLog() && fs.Impl.IsEnabledFileSystemAccessorAccessLog(mountName)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fs.Impl.Unmount(mountName); + Tick end = fs.Hos.Os.GetSystemTick(); + + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogName).Append(mountName).Append((byte)'"'); + logBuffer = sb.Buffer; + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = fs.Impl.Unmount(mountName); + } + fs.Impl.LogErrorMessage(rc); + Abort.DoAbortUnless(rc.IsSuccess()); } - public static Result IsMounted(this FileSystemClient fs, out bool isMounted, U8Span mountName) + public static bool IsMounted(this FileSystemClient fs, U8Span mountName) { - throw new NotImplementedException(); + Result rc; + bool isMounted; + Span logBuffer = stackalloc byte[0x30]; + + if (fs.Impl.IsEnabledAccessLog() && fs.Impl.IsEnabledFileSystemAccessorAccessLog(mountName)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fs.Impl.IsMounted(out isMounted, mountName); + Tick end = fs.Hos.Os.GetSystemTick(); + + var sb = new U8StringBuilder(logBuffer, true); + ReadOnlySpan boolString = AccessLogImpl.ConvertFromBoolToAccessLogBooleanValue(isMounted); + sb.Append(LogName).Append(mountName).Append(LogIsMounted).Append(boolString).Append((byte)'"'); + logBuffer = sb.Buffer; + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = fs.Impl.IsMounted(out isMounted, mountName); + } + fs.Impl.LogErrorMessage(rc); + Abort.DoAbortUnless(rc.IsSuccess()); + + return isMounted; } public static Result ConvertToFsCommonPath(this FileSystemClient fs, U8SpanMutable commonPathBuffer, U8Span path) { - throw new NotImplementedException(); + if (commonPathBuffer.IsNull()) + return ResultFs.NullptrArgument.Log(); + + if (path.IsNull()) + return ResultFs.NullptrArgument.Log(); + + Result rc = GetMountNameAndSubPath(out MountName mountName, out U8Span subPath, path); + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + rc = fs.Impl.Find(out FileSystemAccessor fileSystem, new U8Span(mountName.Name)); + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + rc = fileSystem.GetCommonMountName(commonPathBuffer.Value); + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + int mountNameLength = StringUtils.GetLength(commonPathBuffer); + int commonPathLength = StringUtils.GetLength(subPath); + + if (mountNameLength + commonPathLength > commonPathBuffer.Length) + return ResultFs.TooLongPath.Log(); + + StringUtils.Copy(commonPathBuffer.Slice(commonPathLength), subPath); + return Result.Success; } + + private static ReadOnlySpan LogName => // ", name: "" + new[] + { + (byte)',', (byte)' ', (byte)'n', (byte)'a', (byte)'m', (byte)'e', (byte)':', (byte)' ', + (byte)'"' + }; + + private static ReadOnlySpan LogIsMounted => // "", is_mounted: "" + new[] + { + (byte)'"', (byte)',', (byte)' ', (byte)'i', (byte)'s', (byte)'_', (byte)'m', (byte)'o', + (byte)'u', (byte)'n', (byte)'t', (byte)'e', (byte)'d', (byte)':', (byte)' ', (byte)'"' + }; } } diff --git a/src/LibHac/Fs/Fsa/Registrar.cs b/src/LibHac/Fs/Fsa/Registrar.cs index fd616972..3739b568 100644 --- a/src/LibHac/Fs/Fsa/Registrar.cs +++ b/src/LibHac/Fs/Fsa/Registrar.cs @@ -1,5 +1,6 @@ using System; using LibHac.Common; +using LibHac.Fs.Impl; namespace LibHac.Fs.Fsa { @@ -15,32 +16,47 @@ namespace LibHac.Fs.Fsa internal static class Registrar { - public static Result Register(U8Span name, IFileSystem fileSystem) + public static Result Register(this FileSystemClient fs, U8Span name, IFileSystem fileSystem) { - throw new NotImplementedException(); + var accessor = new FileSystemAccessor(name, null, fileSystem, null, null); + fs.Impl.Register(accessor); + + return Result.Success; } - public static Result Register(U8Span name, IFileSystem fileSystem, ICommonMountNameGenerator mountNameGenerator) + public static Result Register(this FileSystemClient fs, U8Span name, IFileSystem fileSystem, + ICommonMountNameGenerator mountNameGenerator) { - throw new NotImplementedException(); + var accessor = new FileSystemAccessor(name, null, fileSystem, mountNameGenerator, null); + fs.Impl.Register(accessor); + + return Result.Success; } - public static Result Register(U8Span name, IMultiCommitTarget multiCommitTarget, IFileSystem fileSystem, - ICommonMountNameGenerator mountNameGenerator, bool useDataCache, bool usePathCache) + public static Result Register(this FileSystemClient fs, U8Span name, IMultiCommitTarget multiCommitTarget, + IFileSystem fileSystem, ICommonMountNameGenerator mountNameGenerator, bool useDataCache, bool usePathCache) { - throw new NotImplementedException(); + return fs.Register(name, multiCommitTarget, fileSystem, mountNameGenerator, null, useDataCache, + usePathCache); } - public static Result Register(U8Span name, IMultiCommitTarget multiCommitTarget, IFileSystem fileSystem, - ICommonMountNameGenerator mountNameGenerator, ISaveDataAttributeGetter saveAttributeGetter, - bool useDataCache, bool usePathCache) + public static Result Register(this FileSystemClient fs, U8Span name, IMultiCommitTarget multiCommitTarget, + IFileSystem fileSystem, ICommonMountNameGenerator mountNameGenerator, + ISaveDataAttributeGetter saveAttributeGetter, bool useDataCache, bool usePathCache) { - throw new NotImplementedException(); + var accessor = new FileSystemAccessor(name, multiCommitTarget, fileSystem, mountNameGenerator, + saveAttributeGetter); + + accessor.SetFileDataCacheAttachable(useDataCache); + accessor.SetPathBasedFileDataCacheAttachable(usePathCache); + fs.Impl.Register(accessor); + + return Result.Success; } - public static void Unregister(U8Span name) + public static void Unregister(this FileSystemClient fs, U8Span name) { - + fs.Impl.Unregister(name); } } } diff --git a/src/LibHac/Fs/Fsa/UserFile.cs b/src/LibHac/Fs/Fsa/UserFile.cs index c5515a6a..4c1f4db9 100644 --- a/src/LibHac/Fs/Fsa/UserFile.cs +++ b/src/LibHac/Fs/Fsa/UserFile.cs @@ -210,7 +210,7 @@ namespace LibHac.Fs.Fsa Result rc = Get(handle).OperateRange(SpanHelpers.AsByteSpan(ref rangeInfo), OperationId.QueryRange, offset, size, ReadOnlySpan.Empty); - fs.Impl.IsAbortNeeded(rc); + fs.Impl.AbortIfNeeded(rc); return rc; } @@ -219,7 +219,7 @@ namespace LibHac.Fs.Fsa Result rc = Get(handle).OperateRange(Span.Empty, OperationId.InvalidateCache, offset, size, ReadOnlySpan.Empty); - fs.Impl.IsAbortNeeded(rc); + fs.Impl.AbortIfNeeded(rc); return rc; } diff --git a/src/LibHac/Fs/Fsa/UserFileSystem.cs b/src/LibHac/Fs/Fsa/UserFileSystem.cs index 69a977b4..131ce70f 100644 --- a/src/LibHac/Fs/Fsa/UserFileSystem.cs +++ b/src/LibHac/Fs/Fsa/UserFileSystem.cs @@ -1,101 +1,805 @@ using System; using System.Runtime.CompilerServices; using LibHac.Common; +using LibHac.Fs.Impl; +using LibHac.Fs.Shim; +using LibHac.FsSrv.Sf; +using LibHac.Os; +using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem; namespace LibHac.Fs.Fsa { + [SkipLocalsInit] public static class UserFileSystem { public static Result CreateFile(this FileSystemClient fs, U8Span path, long size) { - throw new NotImplementedException(); + return fs.CreateFile(path, size, CreateFileOptions.None); } public static Result DeleteFile(this FileSystemClient fs, U8Span path) { - throw new NotImplementedException(); + Result rc; + U8Span subPath; + FileSystemAccessor fileSystem; + Span logBuffer = stackalloc byte[0x300]; + + if (fs.Impl.IsEnabledAccessLog()) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); + Tick end = fs.Hos.Os.GetSystemTick(); + + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogPath).Append(path).Append((byte)'"'); + logBuffer = sb.Buffer; + + fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); + } + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + if (fs.Impl.IsEnabledAccessLog() && fileSystem.IsEnabledAccessLog()) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fileSystem.DeleteFile(subPath); + Tick end = fs.Hos.Os.GetSystemTick(); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = fileSystem.DeleteFile(subPath); + } + fs.Impl.AbortIfNeeded(rc); + return rc; } public static Result CreateDirectory(this FileSystemClient fs, U8Span path) { - throw new NotImplementedException(); + Result rc; + U8Span subPath; + FileSystemAccessor fileSystem; + Span logBuffer = stackalloc byte[0x300]; + + if (fs.Impl.IsEnabledAccessLog()) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); + Tick end = fs.Hos.Os.GetSystemTick(); + + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogPath).Append(path).Append((byte)'"'); + logBuffer = sb.Buffer; + + fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); + } + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + if (fs.Impl.IsEnabledAccessLog() && fileSystem.IsEnabledAccessLog()) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fileSystem.CreateDirectory(subPath); + Tick end = fs.Hos.Os.GetSystemTick(); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = fileSystem.CreateDirectory(subPath); + } + fs.Impl.AbortIfNeeded(rc); + return rc; } public static Result DeleteDirectory(this FileSystemClient fs, U8Span path) { - throw new NotImplementedException(); + Result rc; + U8Span subPath; + FileSystemAccessor fileSystem; + Span logBuffer = stackalloc byte[0x300]; + + if (fs.Impl.IsEnabledAccessLog()) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); + Tick end = fs.Hos.Os.GetSystemTick(); + + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogPath).Append(path).Append((byte)'"'); + logBuffer = sb.Buffer; + + fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); + } + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + if (fs.Impl.IsEnabledAccessLog() && fileSystem.IsEnabledAccessLog()) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fileSystem.DeleteDirectory(subPath); + Tick end = fs.Hos.Os.GetSystemTick(); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = fileSystem.DeleteDirectory(subPath); + } + fs.Impl.AbortIfNeeded(rc); + return rc; } public static Result DeleteDirectoryRecursively(this FileSystemClient fs, U8Span path) { - throw new NotImplementedException(); + Result rc; + U8Span subPath; + FileSystemAccessor fileSystem; + Span logBuffer = stackalloc byte[0x300]; + + if (fs.Impl.IsEnabledAccessLog()) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); + Tick end = fs.Hos.Os.GetSystemTick(); + + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogPath).Append(path).Append((byte)'"'); + logBuffer = sb.Buffer; + + fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); + } + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + if (fs.Impl.IsEnabledAccessLog() && fileSystem.IsEnabledAccessLog()) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fileSystem.DeleteDirectoryRecursively(subPath); + Tick end = fs.Hos.Os.GetSystemTick(); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = fileSystem.DeleteDirectoryRecursively(subPath); + } + fs.Impl.AbortIfNeeded(rc); + return rc; } public static Result CleanDirectoryRecursively(this FileSystemClient fs, U8Span path) { - throw new NotImplementedException(); + Result rc; + U8Span subPath; + FileSystemAccessor fileSystem; + Span logBuffer = stackalloc byte[0x300]; + + if (fs.Impl.IsEnabledAccessLog()) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); + Tick end = fs.Hos.Os.GetSystemTick(); + + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogPath).Append(path).Append((byte)'"'); + logBuffer = sb.Buffer; + + fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); + } + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + if (fs.Impl.IsEnabledAccessLog() && fileSystem.IsEnabledAccessLog()) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fileSystem.CleanDirectoryRecursively(subPath); + Tick end = fs.Hos.Os.GetSystemTick(); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = fileSystem.CleanDirectoryRecursively(subPath); + } + fs.Impl.AbortIfNeeded(rc); + return rc; } public static Result RenameFile(this FileSystemClient fs, U8Span oldPath, U8Span newPath) { - throw new NotImplementedException(); + Result rc; + U8Span currentSubPath, newSubPath; + FileSystemAccessor currentFileSystem, newFileSystem; + Span logBuffer = stackalloc byte[0x300]; + + // Get the file system accessor for the current path + if (fs.Impl.IsEnabledAccessLog()) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fs.Impl.FindFileSystem(out currentFileSystem, out currentSubPath, oldPath); + Tick end = fs.Hos.Os.GetSystemTick(); + + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogPath).Append(oldPath).Append(LogNewPath).Append(newPath).Append((byte)'"'); + logBuffer = sb.Buffer; + + fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = fs.Impl.FindFileSystem(out currentFileSystem, out currentSubPath, oldPath); + } + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + // Get the file system accessor for the new path + if (fs.Impl.IsEnabledAccessLog()) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fs.Impl.FindFileSystem(out newFileSystem, out newSubPath, newPath); + Tick end = fs.Hos.Os.GetSystemTick(); + + fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = fs.Impl.FindFileSystem(out newFileSystem, out newSubPath, newPath); + } + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + // Rename the file + if (fs.Impl.IsEnabledAccessLog() && currentFileSystem.IsEnabledAccessLog()) + { + Tick start = fs.Hos.Os.GetSystemTick(); + + rc = currentFileSystem != newFileSystem + ? ResultFs.RenameToOtherFileSystem.Log() + : currentFileSystem.RenameFile(currentSubPath, newSubPath); + + Tick end = fs.Hos.Os.GetSystemTick(); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = currentFileSystem != newFileSystem + ? ResultFs.RenameToOtherFileSystem.Log() + : currentFileSystem.RenameFile(currentSubPath, newSubPath); + } + fs.Impl.AbortIfNeeded(rc); + return rc; } public static Result RenameDirectory(this FileSystemClient fs, U8Span oldPath, U8Span newPath) { - throw new NotImplementedException(); + Result rc; + U8Span currentSubPath, newSubPath; + FileSystemAccessor currentFileSystem, newFileSystem; + Span logBuffer = stackalloc byte[0x300]; + + // Get the file system accessor for the current path + if (fs.Impl.IsEnabledAccessLog()) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fs.Impl.FindFileSystem(out currentFileSystem, out currentSubPath, oldPath); + Tick end = fs.Hos.Os.GetSystemTick(); + + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogPath).Append(oldPath).Append(LogNewPath).Append(newPath).Append((byte)'"'); + logBuffer = sb.Buffer; + + fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = fs.Impl.FindFileSystem(out currentFileSystem, out currentSubPath, oldPath); + } + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + // Get the file system accessor for the new path + if (fs.Impl.IsEnabledAccessLog()) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fs.Impl.FindFileSystem(out newFileSystem, out newSubPath, newPath); + Tick end = fs.Hos.Os.GetSystemTick(); + + fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = fs.Impl.FindFileSystem(out newFileSystem, out newSubPath, newPath); + } + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + // Rename the directory + if (fs.Impl.IsEnabledAccessLog() && currentFileSystem.IsEnabledAccessLog()) + { + Tick start = fs.Hos.Os.GetSystemTick(); + + rc = currentFileSystem != newFileSystem + ? ResultFs.RenameToOtherFileSystem.Log() + : currentFileSystem.RenameDirectory(currentSubPath, newSubPath); + + Tick end = fs.Hos.Os.GetSystemTick(); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = currentFileSystem != newFileSystem + ? ResultFs.RenameToOtherFileSystem.Log() + : currentFileSystem.RenameDirectory(currentSubPath, newSubPath); + } + fs.Impl.AbortIfNeeded(rc); + return rc; } public static Result GetEntryType(this FileSystemClient fs, out DirectoryEntryType type, U8Span path) { - throw new NotImplementedException(); + Unsafe.SkipInit(out type); + + Result rc; + U8Span subPath; + FileSystemAccessor fileSystem; + Span logBuffer = stackalloc byte[0x300]; + + if (fs.Impl.IsEnabledAccessLog()) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); + Tick end = fs.Hos.Os.GetSystemTick(); + + var idString = new IdString(); + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogPath).Append(path).Append(LogEntryType) + .Append(idString.ToString(AccessLogImpl.DereferenceOutValue(in type, rc))); + logBuffer = sb.Buffer; + + fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); + } + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + if (fs.Impl.IsEnabledAccessLog() && fileSystem.IsEnabledAccessLog()) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fileSystem.GetEntryType(out type, subPath); + Tick end = fs.Hos.Os.GetSystemTick(); + + var idString = new IdString(); + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogPath).Append(path).Append(LogEntryType) + .Append(idString.ToString(AccessLogImpl.DereferenceOutValue(in type, rc))); + logBuffer = sb.Buffer; + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = fileSystem.GetEntryType(out type, subPath); + } + fs.Impl.AbortIfNeeded(rc); + return rc; } public static Result GetFreeSpaceSize(this FileSystemClient fs, out long freeSpace, U8Span path) { - throw new NotImplementedException(); + Unsafe.SkipInit(out freeSpace); + + Result rc; + var subPath = U8Span.Empty; + FileSystemAccessor fileSystem; + Span logBuffer = stackalloc byte[0x300]; + + if (fs.Impl.IsEnabledAccessLog()) + { + Tick start = fs.Hos.Os.GetSystemTick(); + if (fs.Impl.IsValidMountName(path)) + { + rc = fs.Impl.Find(out fileSystem, path); + if (rc.IsFailure()) return rc; + } + else + { + rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); + if (rc.IsFailure()) return rc; + } + Tick end = fs.Hos.Os.GetSystemTick(); + + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogPath).Append(path).Append(LogSize) + .AppendFormat(AccessLogImpl.DereferenceOutValue(in freeSpace, rc)); + logBuffer = sb.Buffer; + + fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + if (fs.Impl.IsValidMountName(path)) + { + rc = fs.Impl.Find(out fileSystem, path); + if (rc.IsFailure()) return rc; + } + else + { + rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); + if (rc.IsFailure()) return rc; + } + } + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + if (fs.Impl.IsEnabledAccessLog() && fileSystem.IsEnabledAccessLog()) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fileSystem.GetFreeSpaceSize(out freeSpace, subPath); + Tick end = fs.Hos.Os.GetSystemTick(); + + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogPath).Append(path).Append(LogSize) + .AppendFormat(AccessLogImpl.DereferenceOutValue(in freeSpace, rc)); + logBuffer = sb.Buffer; + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = fileSystem.GetFreeSpaceSize(out freeSpace, subPath); + } + fs.Impl.AbortIfNeeded(rc); + return rc; } public static Result OpenFile(this FileSystemClient fs, out FileHandle2 handle, U8Span path, OpenMode mode) { - throw new NotImplementedException(); + handle = default; + + Result rc; + U8Span subPath; + FileSystemAccessor fileSystem; + Span logBuffer = stackalloc byte[0x300]; + + if (fs.Impl.IsEnabledAccessLog()) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); + Tick end = fs.Hos.Os.GetSystemTick(); + + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogPath).Append(path).Append(LogOpenMode).AppendFormat((int)mode, 'X'); + logBuffer = sb.Buffer; + + fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); + } + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + FileAccessor accessor; + if (fs.Impl.IsEnabledAccessLog() && fileSystem.IsEnabledAccessLog()) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fileSystem.OpenFile(out accessor, subPath, mode); + Tick end = fs.Hos.Os.GetSystemTick(); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = fileSystem.OpenFile(out accessor, subPath, mode); + } + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + handle = new FileHandle2(accessor); + return Result.Success; } public static Result OpenFile(this FileSystemClient fs, out FileHandle2 handle, IFile file, OpenMode mode) { - throw new NotImplementedException(); + var accessor = new FileAccessor(ref file, null, mode); + handle = new FileHandle2(accessor); + + return Result.Success; } public static Result OpenDirectory(this FileSystemClient fs, out DirectoryHandle2 handle, U8Span path, OpenDirectoryMode mode) { - throw new NotImplementedException(); + handle = default; + + Result rc; + U8Span subPath; + FileSystemAccessor fileSystem; + Span logBuffer = stackalloc byte[0x300]; + + if (fs.Impl.IsEnabledAccessLog()) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); + Tick end = fs.Hos.Os.GetSystemTick(); + + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogPath).Append(path).Append(LogOpenMode).AppendFormat((int)mode, 'X'); + logBuffer = sb.Buffer; + + fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); + } + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + DirectoryAccessor accessor; + if (fs.Impl.IsEnabledAccessLog() && fileSystem.IsEnabledAccessLog()) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fileSystem.OpenDirectory(out accessor, subPath, mode); + Tick end = fs.Hos.Os.GetSystemTick(); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = fileSystem.OpenDirectory(out accessor, subPath, mode); + } + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + handle = new DirectoryHandle2(accessor); + return Result.Success; } private static Result CommitImpl(FileSystemClient fs, U8Span mountName, [CallerMemberName] string functionName = "") { - throw new NotImplementedException(); + Result rc; + FileSystemAccessor fileSystem; + Span logBuffer = stackalloc byte[0x30]; + + if (fs.Impl.IsEnabledAccessLog()) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fs.Impl.Find(out fileSystem, mountName); + Tick end = fs.Hos.Os.GetSystemTick(); + + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogName).Append(mountName).Append((byte)'"'); + logBuffer = sb.Buffer; + + fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer), functionName); + } + else + { + rc = fs.Impl.Find(out fileSystem, mountName); + } + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + if (fs.Impl.IsEnabledAccessLog() && fileSystem.IsEnabledAccessLog()) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fileSystem.Commit(); + Tick end = fs.Hos.Os.GetSystemTick(); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer), functionName); + } + else + { + rc = fileSystem.Commit(); + } + fs.Impl.AbortIfNeeded(rc); + return rc; } public static Result Commit(this FileSystemClient fs, ReadOnlySpan mountNames) { - throw new NotImplementedException(); + // Todo: Add access log + + if (mountNames.Length > 10) + return ResultFs.InvalidCommitNameCount.Log(); + + if (mountNames.Length == 0) + return Result.Success; + + ReferenceCountedDisposable commitManager = null; + ReferenceCountedDisposable fileSystem = null; + try + { + using ReferenceCountedDisposable fsProxy = fs.Impl.GetFileSystemProxyServiceObject(); + + Result rc = fsProxy.Target.OpenMultiCommitManager(out commitManager); + if (rc.IsFailure()) return rc; + + for (int i = 0; i < mountNames.Length; i++) + { + rc = fs.Impl.Find(out FileSystemAccessor accessor, mountNames[i]); + if (rc.IsFailure()) return rc; + + fileSystem = accessor.GetMultiCommitTarget(); + if (fileSystem is null) + return ResultFs.UnsupportedCommitTarget.Log(); + + rc = commitManager.Target.Add(fileSystem); + if (rc.IsFailure()) return rc; + } + + rc = commitManager.Target.Commit(); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + finally + { + commitManager?.Dispose(); + fileSystem?.Dispose(); + } } public static Result Commit(this FileSystemClient fs, U8Span mountName, CommitOption option) { - throw new NotImplementedException(); + Result rc; + FileSystemAccessor fileSystem; + Span logBuffer = stackalloc byte[0x40]; + + if (fs.Impl.IsEnabledAccessLog()) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fs.Impl.Find(out fileSystem, mountName); + Tick end = fs.Hos.Os.GetSystemTick(); + + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogName).Append(mountName).Append(LogCommitOption).AppendFormat((int)option.Flags, 'X'); + logBuffer = sb.Buffer; + + fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = fs.Impl.Find(out fileSystem, mountName); + } + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + if (fs.Impl.IsEnabledAccessLog() && fileSystem.IsEnabledAccessLog()) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = RunCommit(fs, option, fileSystem); + Tick end = fs.Hos.Os.GetSystemTick(); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = RunCommit(fs, option, fileSystem); + } + fs.Impl.AbortIfNeeded(rc); + return rc; + + static Result RunCommit(FileSystemClient fs, CommitOption option, FileSystemAccessor fileSystem) + { + if ((option.Flags & (CommitOptionFlag.ClearRestoreFlag | CommitOptionFlag.SetRestoreFlag)) == 0) + { + return fileSystem.Commit(); + } + + if (option.Flags != CommitOptionFlag.ClearRestoreFlag && + option.Flags != CommitOptionFlag.SetRestoreFlag) + { + return ResultFs.InvalidCommitOption.Log(); + } + + Result rc = fileSystem.GetSaveDataAttribute(out SaveDataAttribute attribute); + if (rc.IsFailure()) return rc; + + if (attribute.ProgramId == SaveData.InvalidProgramId) + attribute.ProgramId = SaveData.AutoResolveCallerProgramId; + + var extraDataMask = new SaveDataExtraData(); + extraDataMask.Flags = SaveDataFlags.Restore; + + var extraData = new SaveDataExtraData(); + extraDataMask.Flags = option.Flags == CommitOptionFlag.SetRestoreFlag + ? SaveDataFlags.Restore + : SaveDataFlags.None; + + return fs.Impl.WriteSaveDataFileSystemExtraData(SaveDataSpaceId.User, in attribute, in extraData, + in extraDataMask); + } } public static Result Commit(this FileSystemClient fs, U8Span mountName) { - throw new NotImplementedException(); + return CommitImpl(fs, mountName); } public static Result CommitSaveData(this FileSystemClient fs, U8Span mountName) { - throw new NotImplementedException(); + return CommitImpl(fs, mountName); } + + private static ReadOnlySpan LogPath => // ", path: "" + new[] + { + (byte)',', (byte)' ', (byte)'p', (byte)'a', (byte)'t', (byte)'h', (byte)':', (byte)' ', + (byte)'"' + }; + + private static ReadOnlySpan LogNewPath => // "", new_path: "" + new[] + { + (byte)'"', (byte)',', (byte)' ', (byte)'n', (byte)'e', (byte)'w', (byte)'_', (byte)'p', + (byte)'a', (byte)'t', (byte)'h', (byte)':', (byte)' ', (byte)'"' + }; + + private static ReadOnlySpan LogEntryType => // "", entry_type: " + new[] + { + (byte)'"', (byte)',', (byte)' ', (byte)'e', (byte)'n', (byte)'t', (byte)'r', (byte)'y', + (byte)'_', (byte)'t', (byte)'y', (byte)'p', (byte)'e', (byte)':', (byte)' ' + }; + + private static ReadOnlySpan LogSize => // "", size: " + new[] + { + (byte)'"', (byte)',', (byte)' ', (byte)'s', (byte)'i', (byte)'z', (byte)'e', (byte)':', + (byte)' ' + }; + + private static ReadOnlySpan LogOpenMode => // "", open_mode: 0x" + new[] + { + (byte)'"', (byte)',', (byte)' ', (byte)'o', (byte)'p', (byte)'e', (byte)'n', (byte)'_', + (byte)'m', (byte)'o', (byte)'d', (byte)'e', (byte)':', (byte)' ', (byte)'0', (byte)'x' + }; + + private static ReadOnlySpan LogName => // ", name: "" + new[] + { + (byte)',', (byte)' ', (byte)'n', (byte)'a', (byte)'m', (byte)'e', (byte)':', (byte)' ', + (byte)'"' + }; + + private static ReadOnlySpan LogCommitOption => // "", commit_option: 0x" + new[] + { + (byte)'"', (byte)',', (byte)' ', (byte)'c', (byte)'o', (byte)'m', (byte)'m', (byte)'i', + (byte)'t', (byte)'_', (byte)'o', (byte)'p', (byte)'t', (byte)'i', (byte)'o', (byte)'n', + (byte)':', (byte)' ', (byte)'0', (byte)'x' + }; } } diff --git a/src/LibHac/Fs/Fsa/UserFileSystemPrivate.cs b/src/LibHac/Fs/Fsa/UserFileSystemPrivate.cs new file mode 100644 index 00000000..5d95c6a7 --- /dev/null +++ b/src/LibHac/Fs/Fsa/UserFileSystemPrivate.cs @@ -0,0 +1,95 @@ +using System; +using System.Runtime.CompilerServices; +using LibHac.Common; +using LibHac.Fs.Impl; +using LibHac.Os; + +namespace LibHac.Fs.Fsa +{ + public static class UserFileSystemPrivate + { + public static Result CreateFile(this FileSystemClient fs, U8Span path, long size, CreateFileOptions options) + { + Result rc; + U8Span subPath; + FileSystemAccessor fileSystem; + Span logBuffer = stackalloc byte[0x300]; + + if (fs.Impl.IsEnabledAccessLog()) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); + Tick end = fs.Hos.Os.GetSystemTick(); + + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogPath).Append(path).Append((byte)'"'); + logBuffer = sb.Buffer; + + fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); + } + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + if (fs.Impl.IsEnabledAccessLog() && fileSystem.IsEnabledAccessLog()) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fileSystem.CreateFile(subPath, size, options); + Tick end = fs.Hos.Os.GetSystemTick(); + + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogPath).Append(path).Append(LogSize).AppendFormat(size); + logBuffer = sb.Buffer; + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = fileSystem.CreateFile(subPath, size, options); + } + fs.Impl.AbortIfNeeded(rc); + return rc; + } + + public static Result GetTotalSpaceSize(this FileSystemClient fs, out long totalSpace, U8Span path) + { + Unsafe.SkipInit(out totalSpace); + + Result rc = fs.Impl.FindFileSystem(out FileSystemAccessor fileSystem, out U8Span subPath, path); + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + rc = fileSystem.GetFreeSpaceSize(out totalSpace, subPath); + fs.Impl.AbortIfNeeded(rc); + return rc; + } + + public static Result SetConcatenationFileAttribute(this FileSystemClient fs, U8Span path) + { + Result rc = fs.Impl.FindFileSystem(out FileSystemAccessor fileSystem, out U8Span subPath, path); + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + rc = fileSystem.QueryEntry(Span.Empty, ReadOnlySpan.Empty, QueryId.MakeConcatFile, subPath); + fs.Impl.AbortIfNeeded(rc); + return rc; + } + + private static ReadOnlySpan LogPath => // ", path: "" + new[] + { + (byte)',', (byte)' ', (byte)'p', (byte)'a', (byte)'t', (byte)'h', (byte)':', (byte)' ', + (byte)'"' + }; + + private static ReadOnlySpan LogSize => // "", size: " + new[] + { + (byte)'"', (byte)',', (byte)' ', (byte)'s', (byte)'i', (byte)'z', (byte)'e', (byte)':', + (byte)' ' + }; + } +} diff --git a/src/LibHac/Fs/Fsa/UserMountTable.cs b/src/LibHac/Fs/Fsa/UserMountTable.cs index 2ca4a836..a6ebd9d6 100644 --- a/src/LibHac/Fs/Fsa/UserMountTable.cs +++ b/src/LibHac/Fs/Fsa/UserMountTable.cs @@ -1,5 +1,4 @@ -using System; -using LibHac.Common; +using LibHac.Common; using LibHac.Fs.Impl; namespace LibHac.Fs.Fsa @@ -18,17 +17,17 @@ namespace LibHac.Fs.Fsa { public static Result Register(this FileSystemClientImpl fs, FileSystemAccessor fileSystem) { - throw new NotImplementedException(); + return fs.Globals.UserMountTable.MountTable.Mount(fileSystem); } public static Result Find(this FileSystemClientImpl fs, out FileSystemAccessor fileSystem, U8Span name) { - throw new NotImplementedException(); + return fs.Globals.UserMountTable.MountTable.Find(out fileSystem, name); } public static void Unregister(this FileSystemClientImpl fs, U8Span name) { - throw new NotImplementedException(); + fs.Globals.UserMountTable.MountTable.Unmount(name); } } } diff --git a/src/LibHac/Fs/ResultFs.cs b/src/LibHac/Fs/ResultFs.cs index 29394eb8..ed9e5abb 100644 --- a/src/LibHac/Fs/ResultFs.cs +++ b/src/LibHac/Fs/ResultFs.cs @@ -934,6 +934,8 @@ namespace LibHac.Fs public static Result.Base UnexpectedErrorInHostFileGetSize => new Result.Base(ModuleFs, 5308); /// Error code: 2002-5309; Inner value: 0x297a02 public static Result.Base UnknownHostFileSystemError => new Result.Base(ModuleFs, 5309); + /// Error code: 2002-5319; Inner value: 0x298e02 + public static Result.Base UnexpectedInMountUtilityA => new Result.Base(ModuleFs, 5319); /// Error code: 2002-5320; Inner value: 0x299002 public static Result.Base InvalidNcaMountPoint => new Result.Base(ModuleFs, 5320); diff --git a/src/LibHac/Fs/SaveData.cs b/src/LibHac/Fs/SaveData.cs index 1ea6955e..f7be3c2a 100644 --- a/src/LibHac/Fs/SaveData.cs +++ b/src/LibHac/Fs/SaveData.cs @@ -1,7 +1,11 @@ -namespace LibHac.Fs +using LibHac.Ncm; + +namespace LibHac.Fs { public static class SaveData { public const ulong SaveIndexerId = 0x8000000000000000; + public static ProgramId InvalidProgramId => ProgramId.InvalidId; + public static ProgramId AutoResolveCallerProgramId => new ProgramId(ulong.MaxValue - 1); } } \ No newline at end of file diff --git a/src/LibHac/Fs/Shim/SaveDataManagement.cs b/src/LibHac/Fs/Shim/SaveDataManagement.cs index 91608b72..78406c90 100644 --- a/src/LibHac/Fs/Shim/SaveDataManagement.cs +++ b/src/LibHac/Fs/Shim/SaveDataManagement.cs @@ -10,6 +10,55 @@ namespace LibHac.Fs.Shim { public static class SaveDataManagement { + internal static Result ReadSaveDataFileSystemExtraData(this FileSystemClientImpl fs, + out SaveDataExtraData extraData, ulong saveDataId) + { + throw new NotImplementedException(); + } + + internal static Result ReadSaveDataFileSystemExtraData(this FileSystemClientImpl fs, + out SaveDataExtraData extraData, SaveDataSpaceId spaceId, ulong saveDataId) + { + throw new NotImplementedException(); + } + + internal static Result ReadSaveDataFileSystemExtraData(this FileSystemClientImpl fs, + out SaveDataExtraData extraData, SaveDataSpaceId spaceId, in SaveDataAttribute attribute) + { + throw new NotImplementedException(); + } + + internal static Result ReadSaveDataFileSystemExtraData(this FileSystemClientImpl fs, + out SaveDataExtraData extraData, SaveDataSpaceId spaceId, in SaveDataAttribute attribute, + in SaveDataExtraData extraDataMask) + { + throw new NotImplementedException(); + } + + internal static Result WriteSaveDataFileSystemExtraData(this FileSystemClientImpl fs, SaveDataSpaceId spaceId, + ulong saveDataId, in SaveDataExtraData extraData) + { + throw new NotImplementedException(); + } + + internal static Result WriteSaveDataFileSystemExtraData(this FileSystemClientImpl fs, SaveDataSpaceId spaceId, + ulong saveDataId, in SaveDataExtraData extraData, in SaveDataExtraData extraDataMask) + { + throw new NotImplementedException(); + } + + internal static Result WriteSaveDataFileSystemExtraData(this FileSystemClientImpl fs, SaveDataSpaceId spaceId, + in SaveDataAttribute attribute, in SaveDataExtraData extraData, in SaveDataExtraData extraDataMask) + { + throw new NotImplementedException(); + } + + internal static Result FindSaveDataWithFilter(this FileSystemClientImpl fs, out SaveDataInfo saveInfo, + SaveDataSpaceId spaceId, in SaveDataFilter filter) + { + throw new NotImplementedException(); + } + public static Result CreateSaveData(this FileSystemClient fs, Ncm.ApplicationId applicationId, UserId userId, ulong ownerId, long size, long journalSize, SaveDataFlags flags) { @@ -42,39 +91,6 @@ namespace LibHac.Fs.Shim $", applicationid: 0x{applicationId.Value:X}, userid: 0x{userId}, save_data_owner_id: 0x{ownerId:X}, save_data_size: {size}, save_data_journal_size: {journalSize}, save_data_flags: 0x{(int)flags:X8}"); } - public static Result CreateSaveData(this FileSystemClient fs, Ncm.ApplicationId applicationId, UserId userId, - ulong ownerId, long size, long journalSize, HashSalt hashSalt, SaveDataFlags flags) - { - return fs.RunOperationWithAccessLog(AccessLogTarget.System, - () => - { - using ReferenceCountedDisposable fsProxy = fs.Impl.GetFileSystemProxyServiceObject(); - - var attribute = new SaveDataAttribute(applicationId, SaveDataType.Account, userId, 0); - - var createInfo = new SaveDataCreationInfo - { - Size = size, - JournalSize = journalSize, - BlockSize = 0x4000, - OwnerId = ownerId, - Flags = flags, - SpaceId = SaveDataSpaceId.User - }; - - var metaInfo = new SaveDataMetaInfo - { - Type = SaveDataMetaType.Thumbnail, - Size = 0x40060 - }; - - return fsProxy.Target.CreateSaveDataFileSystemWithHashSalt(in attribute, in createInfo, in metaInfo, - in hashSalt); - }, - () => - $", applicationid: 0x{applicationId.Value:X}, userid: 0x{userId}, save_data_owner_id: 0x{ownerId:X}, save_data_size: {size}, save_data_journal_size: {journalSize}, save_data_flags: 0x{(int)flags:X8}"); - } - public static Result CreateBcatSaveData(this FileSystemClient fs, Ncm.ApplicationId applicationId, long size) { return fs.RunOperationWithAccessLog(AccessLogTarget.System, @@ -128,6 +144,159 @@ namespace LibHac.Fs.Shim () => $", applicationid: 0x{applicationId.Value:X}, save_data_owner_id: 0x{ownerId:X}, save_data_size: {size}, save_data_journal_size: {journalSize}, save_data_flags: 0x{(int)flags:X8}"); } + public static Result DeleteSaveData(this FileSystemClient fs, ulong saveDataId) + { + return fs.RunOperationWithAccessLog(AccessLogTarget.System, + () => + { + using ReferenceCountedDisposable fsProxy = fs.Impl.GetFileSystemProxyServiceObject(); + return fsProxy.Target.DeleteSaveDataFileSystem(saveDataId); + }, + () => $", savedataid: 0x{saveDataId:X}"); + } + + public static Result DeleteSaveData(this FileSystemClient fs, SaveDataSpaceId spaceId, ulong saveDataId) + { + return fs.RunOperationWithAccessLog(AccessLogTarget.System, + () => + { + using ReferenceCountedDisposable fsProxy = fs.Impl.GetFileSystemProxyServiceObject(); + return fsProxy.Target.DeleteSaveDataFileSystemBySaveDataSpaceId(spaceId, saveDataId); + }, + () => $", savedataspaceid: {spaceId}, savedataid: 0x{saveDataId:X}"); + } + + public static Result OpenSaveDataIterator(this FileSystemClient fs, out SaveDataIterator iterator, SaveDataSpaceId spaceId) + { + var tempIterator = new SaveDataIterator(); + + try + { + Result result = fs.RunOperationWithAccessLog(AccessLogTarget.System, + () => + { + using ReferenceCountedDisposable fsProxy = fs.Impl.GetFileSystemProxyServiceObject(); + + Result rc = fsProxy.Target.OpenSaveDataInfoReaderBySaveDataSpaceId( + out ReferenceCountedDisposable reader, spaceId); + if (rc.IsFailure()) return rc; + + tempIterator = new SaveDataIterator(fs, reader); + + return Result.Success; + }, + () => $", savedataspaceid: {spaceId}"); + + iterator = result.IsSuccess() ? tempIterator : default; + tempIterator = default; + + return result; + } + finally + { + tempIterator.Dispose(); + } + } + + public static Result OpenSaveDataIterator(this FileSystemClient fs, out SaveDataIterator iterator, SaveDataSpaceId spaceId, in SaveDataFilter filter) + { + ReferenceCountedDisposable reader = null; + var tempIterator = new SaveDataIterator(); + SaveDataFilter tempFilter = filter; + + try + { + Result result = fs.RunOperationWithAccessLog(AccessLogTarget.System, + () => + { + using ReferenceCountedDisposable fsProxy = fs.Impl.GetFileSystemProxyServiceObject(); + + Result rc = fsProxy.Target.OpenSaveDataInfoReaderWithFilter(out reader, spaceId, in tempFilter); + if (rc.IsFailure()) return rc; + + tempIterator = new SaveDataIterator(fs, reader); + + return Result.Success; + }, + () => $", savedataspaceid: {spaceId}"); + + iterator = result.IsSuccess() ? tempIterator : default; + + return result; + } + finally + { + reader?.Dispose(); + } + } + + public static Result FindSaveDataWithFilter(this FileSystemClient fs, out SaveDataInfo info, SaveDataSpaceId spaceId, + in SaveDataFilter filter) + { + info = default; + + SaveDataFilter tempFilter = filter; + var tempInfo = new SaveDataInfo(); + + Result result = fs.RunOperationWithAccessLog(AccessLogTarget.System, + () => + { + using ReferenceCountedDisposable fsProxy = fs.Impl.GetFileSystemProxyServiceObject(); + + tempInfo = new SaveDataInfo(); + + Result rc = fsProxy.Target.FindSaveDataWithFilter(out long count, + OutBuffer.FromStruct(ref tempInfo), spaceId, in tempFilter); + if (rc.IsFailure()) return rc; + + if (count == 0) + return ResultFs.TargetNotFound.Log(); + + return Result.Success; + }, + () => $", savedataspaceid: {spaceId}"); + + if (result.IsSuccess()) + { + info = tempInfo; + } + + return result; + } + + public static Result CreateSaveData(this FileSystemClient fs, Ncm.ApplicationId applicationId, UserId userId, + ulong ownerId, long size, long journalSize, HashSalt hashSalt, SaveDataFlags flags) + { + return fs.RunOperationWithAccessLog(AccessLogTarget.System, + () => + { + using ReferenceCountedDisposable fsProxy = fs.Impl.GetFileSystemProxyServiceObject(); + + var attribute = new SaveDataAttribute(applicationId, SaveDataType.Account, userId, 0); + + var createInfo = new SaveDataCreationInfo + { + Size = size, + JournalSize = journalSize, + BlockSize = 0x4000, + OwnerId = ownerId, + Flags = flags, + SpaceId = SaveDataSpaceId.User + }; + + var metaInfo = new SaveDataMetaInfo + { + Type = SaveDataMetaType.Thumbnail, + Size = 0x40060 + }; + + return fsProxy.Target.CreateSaveDataFileSystemWithHashSalt(in attribute, in createInfo, in metaInfo, + in hashSalt); + }, + () => + $", applicationid: 0x{applicationId.Value:X}, userid: 0x{userId}, save_data_owner_id: 0x{ownerId:X}, save_data_size: {size}, save_data_journal_size: {journalSize}, save_data_flags: 0x{(int)flags:X8}"); + } + public static Result CreateTemporaryStorage(this FileSystemClient fs, Ncm.ApplicationId applicationId, ulong ownerId, long size, SaveDataFlags flags) { return fs.RunOperationWithAccessLog(AccessLogTarget.System, @@ -247,62 +416,6 @@ namespace LibHac.Fs.Shim return CreateSystemSaveData(fs, spaceId, saveDataId, UserId.InvalidId, ownerId, size, journalSize, flags); } - public static Result DeleteSaveData(this FileSystemClient fs, ulong saveDataId) - { - return fs.RunOperationWithAccessLog(AccessLogTarget.System, - () => - { - using ReferenceCountedDisposable fsProxy = fs.Impl.GetFileSystemProxyServiceObject(); - return fsProxy.Target.DeleteSaveDataFileSystem(saveDataId); - }, - () => $", savedataid: 0x{saveDataId:X}"); - } - - public static Result DeleteSaveData(this FileSystemClient fs, SaveDataSpaceId spaceId, ulong saveDataId) - { - return fs.RunOperationWithAccessLog(AccessLogTarget.System, - () => - { - using ReferenceCountedDisposable fsProxy = fs.Impl.GetFileSystemProxyServiceObject(); - return fsProxy.Target.DeleteSaveDataFileSystemBySaveDataSpaceId(spaceId, saveDataId); - }, - () => $", savedataspaceid: {spaceId}, savedataid: 0x{saveDataId:X}"); - } - - public static Result FindSaveDataWithFilter(this FileSystemClient fs, out SaveDataInfo info, SaveDataSpaceId spaceId, - ref SaveDataFilter filter) - { - info = default; - - SaveDataFilter tempFilter = filter; - var tempInfo = new SaveDataInfo(); - - Result result = fs.RunOperationWithAccessLog(AccessLogTarget.System, - () => - { - using ReferenceCountedDisposable fsProxy = fs.Impl.GetFileSystemProxyServiceObject(); - - tempInfo = new SaveDataInfo(); - - Result rc = fsProxy.Target.FindSaveDataWithFilter(out long count, - OutBuffer.FromStruct(ref tempInfo), spaceId, in tempFilter); - if (rc.IsFailure()) return rc; - - if (count == 0) - return ResultFs.TargetNotFound.Log(); - - return Result.Success; - }, - () => $", savedataspaceid: {spaceId}"); - - if (result.IsSuccess()) - { - info = tempInfo; - } - - return result; - } - public static Result QuerySaveDataTotalSize(this FileSystemClient fs, out long totalSize, long size, long journalSize) { totalSize = default; @@ -326,70 +439,6 @@ namespace LibHac.Fs.Shim return result; } - public static Result OpenSaveDataIterator(this FileSystemClient fs, out SaveDataIterator iterator, SaveDataSpaceId spaceId) - { - var tempIterator = new SaveDataIterator(); - - try - { - Result result = fs.RunOperationWithAccessLog(AccessLogTarget.System, - () => - { - using ReferenceCountedDisposable fsProxy = fs.Impl.GetFileSystemProxyServiceObject(); - - Result rc = fsProxy.Target.OpenSaveDataInfoReaderBySaveDataSpaceId( - out ReferenceCountedDisposable reader, spaceId); - if (rc.IsFailure()) return rc; - - tempIterator = new SaveDataIterator(fs, reader); - - return Result.Success; - }, - () => $", savedataspaceid: {spaceId}"); - - iterator = result.IsSuccess() ? tempIterator : default; - tempIterator = default; - - return result; - } - finally - { - tempIterator.Dispose(); - } - } - - public static Result OpenSaveDataIterator(this FileSystemClient fs, out SaveDataIterator iterator, SaveDataSpaceId spaceId, ref SaveDataFilter filter) - { - ReferenceCountedDisposable reader = null; - var tempIterator = new SaveDataIterator(); - SaveDataFilter tempFilter = filter; - - try - { - Result result = fs.RunOperationWithAccessLog(AccessLogTarget.System, - () => - { - using ReferenceCountedDisposable fsProxy = fs.Impl.GetFileSystemProxyServiceObject(); - - Result rc = fsProxy.Target.OpenSaveDataInfoReaderWithFilter(out reader, spaceId, in tempFilter); - if (rc.IsFailure()) return rc; - - tempIterator = new SaveDataIterator(fs, reader); - - return Result.Success; - }, - () => $", savedataspaceid: {spaceId}"); - - iterator = result.IsSuccess() ? tempIterator : default; - - return result; - } - finally - { - reader?.Dispose(); - } - } - public static void DisableAutoSaveDataCreation(this FileSystemClient fsClient) { using ReferenceCountedDisposable fsProxy = fsClient.Impl.GetFileSystemProxyServiceObject();