From ecf7bcd8ad8c5b6282ed4a3900b2c6c0a421d538 Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Mon, 10 Feb 2020 14:06:00 -0700 Subject: [PATCH] Rewrite SubdirectoryFileSystem --- src/LibHac/Common/U8Span.cs | 8 +- .../Creators/SaveDataFileSystemCreator.cs | 4 +- .../Creators/SubDirectoryFileSystemCreator.cs | 9 +- src/LibHac/FsService/Util.cs | 9 +- src/LibHac/FsSystem/SubdirectoryFileSystem.cs | 209 ++++++++++++++---- src/LibHac/SwitchFs.cs | 15 +- .../Fs/SubdirectoryFileSystemTests.cs | 41 +++- 7 files changed, 234 insertions(+), 61 deletions(-) diff --git a/src/LibHac/Common/U8Span.cs b/src/LibHac/Common/U8Span.cs index bd336f58..3dedc2ef 100644 --- a/src/LibHac/Common/U8Span.cs +++ b/src/LibHac/Common/U8Span.cs @@ -74,7 +74,13 @@ namespace LibHac.Common public U8String ToU8String() { - return new U8String(_buffer.ToArray()); + int length = StringUtils.GetLength(_buffer); + + // Allocate an extra byte for the null terminator + var buffer = new byte[length + 1]; + _buffer.Slice(0, length).CopyTo(buffer); + + return new U8String(buffer); } /// diff --git a/src/LibHac/FsService/Creators/SaveDataFileSystemCreator.cs b/src/LibHac/FsService/Creators/SaveDataFileSystemCreator.cs index 9e76d802..5e4b0974 100644 --- a/src/LibHac/FsService/Creators/SaveDataFileSystemCreator.cs +++ b/src/LibHac/FsService/Creators/SaveDataFileSystemCreator.cs @@ -41,7 +41,9 @@ namespace LibHac.FsService.Creators // Actual FS does this check // if (!allowDirectorySaveData) return ResultFs.InvalidSaveDataEntryType.Log(); - var subDirFs = new SubdirectoryFileSystem(sourceFileSystem, saveDataPath); + rc = SubdirectoryFileSystem.CreateNew(out SubdirectoryFileSystem subDirFs, sourceFileSystem, saveDataPath.ToU8String()); + if (rc.IsFailure()) return rc; + bool isPersistentSaveData = type != SaveDataType.Temporary; bool isUserSaveData = type == SaveDataType.Account || type == SaveDataType.Device; diff --git a/src/LibHac/FsService/Creators/SubDirectoryFileSystemCreator.cs b/src/LibHac/FsService/Creators/SubDirectoryFileSystemCreator.cs index 06983e72..d0d52853 100644 --- a/src/LibHac/FsService/Creators/SubDirectoryFileSystemCreator.cs +++ b/src/LibHac/FsService/Creators/SubDirectoryFileSystemCreator.cs @@ -1,4 +1,5 @@ -using LibHac.Fs; +using LibHac.Common; +using LibHac.Fs; using LibHac.FsSystem; namespace LibHac.FsService.Creators @@ -12,9 +13,9 @@ namespace LibHac.FsService.Creators Result rc = baseFileSystem.OpenDirectory(out IDirectory _, path, OpenDirectoryMode.Directory); if (rc.IsFailure()) return rc; - subDirFileSystem = new SubdirectoryFileSystem(baseFileSystem, path); - - return Result.Success; + rc = SubdirectoryFileSystem.CreateNew(out SubdirectoryFileSystem fs, baseFileSystem, path.ToU8String()); + subDirFileSystem = fs; + return rc; } } } diff --git a/src/LibHac/FsService/Util.cs b/src/LibHac/FsService/Util.cs index 3b951d0b..4d2a24c9 100644 --- a/src/LibHac/FsService/Util.cs +++ b/src/LibHac/FsService/Util.cs @@ -1,4 +1,5 @@ -using LibHac.Fs; +using LibHac.Common; +using LibHac.Fs; using LibHac.FsSystem; namespace LibHac.FsService @@ -36,9 +37,9 @@ namespace LibHac.FsService Result rc = baseFileSystem.OpenDirectory(out IDirectory _, path, OpenDirectoryMode.Directory); if (rc.IsFailure()) return rc; - subFileSystem = new SubdirectoryFileSystem(baseFileSystem, path); - - return Result.Success; + rc = SubdirectoryFileSystem.CreateNew(out SubdirectoryFileSystem fs, baseFileSystem, path.ToU8String()); + subFileSystem = fs; + return rc; } public static bool UseDeviceUniqueSaveMac(SaveDataSpaceId spaceId) diff --git a/src/LibHac/FsSystem/SubdirectoryFileSystem.cs b/src/LibHac/FsSystem/SubdirectoryFileSystem.cs index 8b00bd71..b8ac9a27 100644 --- a/src/LibHac/FsSystem/SubdirectoryFileSystem.cs +++ b/src/LibHac/FsSystem/SubdirectoryFileSystem.cs @@ -1,134 +1,263 @@ using System; +using System.Diagnostics; +using LibHac.Common; using LibHac.Fs; namespace LibHac.FsSystem { public class SubdirectoryFileSystem : FileSystemBase { - private string RootPath { get; } - private IFileSystem ParentFileSystem { get; } + private IFileSystem BaseFileSystem { get; } + private U8String RootPath { get; set; } + private bool PreserveUnc { get; } - private string ResolveFullPath(string path) + public static Result CreateNew(out SubdirectoryFileSystem created, IFileSystem baseFileSystem, U8Span rootPath, bool preserveUnc = false) { - return PathTools.Combine(RootPath, path); + var obj = new SubdirectoryFileSystem(baseFileSystem, preserveUnc); + Result rc = obj.Initialize(rootPath); + + if (rc.IsSuccess()) + { + created = obj; + return Result.Success; + } + + obj.Dispose(); + created = default; + return rc; } - public SubdirectoryFileSystem(IFileSystem fs, string rootPath) + public SubdirectoryFileSystem(IFileSystem baseFileSystem, bool preserveUnc = false) { - ParentFileSystem = fs; - RootPath = PathTools.Normalize(rootPath); + BaseFileSystem = baseFileSystem; + PreserveUnc = preserveUnc; + } + + private Result Initialize(U8Span rootPath) + { + if (StringUtils.GetLength(rootPath, PathTools.MaxPathLength + 1) > PathTools.MaxPathLength) + return ResultFs.TooLongPath.Log(); + + Span normalizedPath = stackalloc byte[PathTools.MaxPathLength + 2]; + + Result rc = PathTool.Normalize(normalizedPath, out long normalizedPathLen, rootPath, PreserveUnc, false); + if (rc.IsFailure()) return rc; + + // Ensure a trailing separator + if (!PathTool.IsSeparator(normalizedPath[(int)normalizedPathLen - 1])) + { + Debug.Assert(normalizedPathLen + 2 <= normalizedPath.Length); + + normalizedPath[(int)normalizedPathLen] = StringTraits.DirectorySeparator; + normalizedPath[(int)normalizedPathLen + 1] = StringTraits.NullTerminator; + normalizedPathLen++; + } + + var buffer = new byte[normalizedPathLen + 1]; + normalizedPath.Slice(0, (int)normalizedPathLen).CopyTo(buffer); + RootPath = new U8String(buffer); + + return Result.Success; + } + + private Result ResolveFullPath(Span outPath, U8Span relativePath) + { + if (RootPath.Length + StringUtils.GetLength(relativePath, PathTools.MaxPathLength + 1) > outPath.Length) + return ResultFs.TooLongPath.Log(); + + // Copy root path to the output + RootPath.Value.CopyTo(outPath); + + // Copy the normalized relative path to the output + return PathTool.Normalize(outPath.Slice(RootPath.Length - 2), out _, relativePath, PreserveUnc, false); } protected override Result CreateDirectoryImpl(string path) { - string fullPath = ResolveFullPath(PathTools.Normalize(path)); + var u8Path = new U8String(path); - return ParentFileSystem.CreateDirectory(fullPath); + Span fullPath = stackalloc byte[PathTools.MaxPathLength + 1]; + Result rc = ResolveFullPath(fullPath, u8Path); + if (rc.IsFailure()) return rc; + + return BaseFileSystem.CreateDirectory(StringUtils.Utf8ZToString(fullPath)); } protected override Result CreateFileImpl(string path, long size, CreateFileOptions options) { - string fullPath = ResolveFullPath(PathTools.Normalize(path)); + var u8Path = new U8String(path); - return ParentFileSystem.CreateFile(fullPath, size, options); + Span fullPath = stackalloc byte[PathTools.MaxPathLength + 1]; + Result rc = ResolveFullPath(fullPath, u8Path); + if (rc.IsFailure()) return rc; + + return BaseFileSystem.CreateFile(StringUtils.Utf8ZToString(fullPath), size, options); } protected override Result DeleteDirectoryImpl(string path) { - string fullPath = ResolveFullPath(PathTools.Normalize(path)); + var u8Path = new U8String(path); - return ParentFileSystem.DeleteDirectory(fullPath); + Span fullPath = stackalloc byte[PathTools.MaxPathLength + 1]; + Result rc = ResolveFullPath(fullPath, u8Path); + if (rc.IsFailure()) return rc; + + return BaseFileSystem.DeleteDirectory(StringUtils.Utf8ZToString(fullPath)); } protected override Result DeleteDirectoryRecursivelyImpl(string path) { - string fullPath = ResolveFullPath(PathTools.Normalize(path)); + var u8Path = new U8String(path); - return ParentFileSystem.DeleteDirectoryRecursively(fullPath); + Span fullPath = stackalloc byte[PathTools.MaxPathLength + 1]; + Result rc = ResolveFullPath(fullPath, u8Path); + if (rc.IsFailure()) return rc; + + return BaseFileSystem.DeleteDirectoryRecursively(StringUtils.Utf8ZToString(fullPath)); } protected override Result CleanDirectoryRecursivelyImpl(string path) { - string fullPath = ResolveFullPath(PathTools.Normalize(path)); + var u8Path = new U8String(path); - return ParentFileSystem.CleanDirectoryRecursively(fullPath); + Span fullPath = stackalloc byte[PathTools.MaxPathLength + 1]; + Result rc = ResolveFullPath(fullPath, u8Path); + if (rc.IsFailure()) return rc; + + return BaseFileSystem.CleanDirectoryRecursively(StringUtils.Utf8ZToString(fullPath)); } protected override Result DeleteFileImpl(string path) { - string fullPath = ResolveFullPath(PathTools.Normalize(path)); + var u8Path = new U8String(path); - return ParentFileSystem.DeleteFile(fullPath); + Span fullPath = stackalloc byte[PathTools.MaxPathLength + 1]; + Result rc = ResolveFullPath(fullPath, u8Path); + if (rc.IsFailure()) return rc; + + return BaseFileSystem.DeleteFile(StringUtils.Utf8ZToString(fullPath)); } protected override Result OpenDirectoryImpl(out IDirectory directory, string path, OpenDirectoryMode mode) { - string fullPath = ResolveFullPath(PathTools.Normalize(path)); + directory = default; + var u8Path = new U8String(path); - return ParentFileSystem.OpenDirectory(out directory, fullPath, mode); + Span fullPath = stackalloc byte[PathTools.MaxPathLength + 1]; + Result rc = ResolveFullPath(fullPath, u8Path); + if (rc.IsFailure()) return rc; + + return BaseFileSystem.OpenDirectory(out directory, StringUtils.Utf8ZToString(fullPath), mode); } protected override Result OpenFileImpl(out IFile file, string path, OpenMode mode) { - string fullPath = ResolveFullPath(PathTools.Normalize(path)); + file = default; + var u8Path = new U8String(path); - return ParentFileSystem.OpenFile(out file, fullPath, mode); + Span fullPath = stackalloc byte[PathTools.MaxPathLength + 1]; + Result rc = ResolveFullPath(fullPath, u8Path); + if (rc.IsFailure()) return rc; + + return BaseFileSystem.OpenFile(out file, StringUtils.Utf8ZToString(fullPath), mode); } protected override Result RenameDirectoryImpl(string oldPath, string newPath) { - string fullOldPath = ResolveFullPath(PathTools.Normalize(oldPath)); - string fullNewPath = ResolveFullPath(PathTools.Normalize(newPath)); + var u8OldPath = new U8String(oldPath); + var u8NewPath = new U8String(newPath); - return ParentFileSystem.RenameDirectory(fullOldPath, fullNewPath); + Span fullOldPath = stackalloc byte[PathTools.MaxPathLength + 1]; + Span fullNewPath = stackalloc byte[PathTools.MaxPathLength + 1]; + + Result rc = ResolveFullPath(fullOldPath, u8OldPath); + if (rc.IsFailure()) return rc; + + rc = ResolveFullPath(fullNewPath, u8NewPath); + if (rc.IsFailure()) return rc; + + return BaseFileSystem.RenameDirectory(StringUtils.Utf8ZToString(fullOldPath), StringUtils.Utf8ZToString(fullNewPath)); } protected override Result RenameFileImpl(string oldPath, string newPath) { - string fullOldPath = ResolveFullPath(PathTools.Normalize(oldPath)); - string fullNewPath = ResolveFullPath(PathTools.Normalize(newPath)); + var u8OldPath = new U8String(oldPath); + var u8NewPath = new U8String(newPath); - return ParentFileSystem.RenameFile(fullOldPath, fullNewPath); + Span fullOldPath = stackalloc byte[PathTools.MaxPathLength + 1]; + Span fullNewPath = stackalloc byte[PathTools.MaxPathLength + 1]; + + Result rc = ResolveFullPath(fullOldPath, u8OldPath); + if (rc.IsFailure()) return rc; + + rc = ResolveFullPath(fullNewPath, u8NewPath); + if (rc.IsFailure()) return rc; + + return BaseFileSystem.RenameFile(StringUtils.Utf8ZToString(fullOldPath), StringUtils.Utf8ZToString(fullNewPath)); } protected override Result GetEntryTypeImpl(out DirectoryEntryType entryType, string path) { - string fullPath = ResolveFullPath(PathTools.Normalize(path)); + entryType = default; + var u8Path = new U8String(path); - return ParentFileSystem.GetEntryType(out entryType, fullPath); + Span fullPath = stackalloc byte[PathTools.MaxPathLength + 1]; + Result rc = ResolveFullPath(fullPath, u8Path); + if (rc.IsFailure()) return rc; + + return BaseFileSystem.GetEntryType(out entryType, StringUtils.Utf8ZToString(fullPath)); } protected override Result CommitImpl() { - return ParentFileSystem.Commit(); + return BaseFileSystem.Commit(); } protected override Result GetFreeSpaceSizeImpl(out long freeSpace, string path) { - string fullPath = ResolveFullPath(PathTools.Normalize(path)); + freeSpace = default; + var u8Path = new U8String(path); - return ParentFileSystem.GetFreeSpaceSize(out freeSpace, fullPath); + Span fullPath = stackalloc byte[PathTools.MaxPathLength + 1]; + Result rc = ResolveFullPath(fullPath, u8Path); + if (rc.IsFailure()) return rc; + + return BaseFileSystem.GetFreeSpaceSize(out freeSpace, StringUtils.Utf8ZToString(fullPath)); } protected override Result GetTotalSpaceSizeImpl(out long totalSpace, string path) { - string fullPath = ResolveFullPath(PathTools.Normalize(path)); + totalSpace = default; + var u8Path = new U8String(path); - return ParentFileSystem.GetTotalSpaceSize(out totalSpace, fullPath); + Span fullPath = stackalloc byte[PathTools.MaxPathLength + 1]; + Result rc = ResolveFullPath(fullPath, u8Path); + if (rc.IsFailure()) return rc; + + return BaseFileSystem.GetTotalSpaceSize(out totalSpace, StringUtils.Utf8ZToString(fullPath)); } protected override Result GetFileTimeStampRawImpl(out FileTimeStampRaw timeStamp, string path) { - string fullPath = ResolveFullPath(PathTools.Normalize(path)); + timeStamp = default; + var u8Path = new U8String(path); - return ParentFileSystem.GetFileTimeStampRaw(out timeStamp, fullPath); + Span fullPath = stackalloc byte[PathTools.MaxPathLength + 1]; + Result rc = ResolveFullPath(fullPath, u8Path); + if (rc.IsFailure()) return rc; + + return BaseFileSystem.GetFileTimeStampRaw(out timeStamp, StringUtils.Utf8ZToString(fullPath)); } protected override Result QueryEntryImpl(Span outBuffer, ReadOnlySpan inBuffer, QueryId queryId, string path) { - string fullPath = ResolveFullPath(PathTools.Normalize(path)); + var u8Path = new U8String(path); - return ParentFileSystem.QueryEntry(outBuffer, inBuffer, queryId, fullPath); + Span fullPath = stackalloc byte[PathTools.MaxPathLength + 1]; + Result rc = ResolveFullPath(fullPath, u8Path); + if (rc.IsFailure()) return rc; + + return BaseFileSystem.QueryEntry(outBuffer, inBuffer, queryId, StringUtils.Utf8ZToString(fullPath)); } } } diff --git a/src/LibHac/SwitchFs.cs b/src/LibHac/SwitchFs.cs index 49f508b7..9dbc8b74 100644 --- a/src/LibHac/SwitchFs.cs +++ b/src/LibHac/SwitchFs.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; +using LibHac.Common; using LibHac.Fs; using LibHac.FsSystem; using LibHac.FsSystem.NcaUtils; @@ -38,12 +39,12 @@ namespace LibHac public static SwitchFs OpenSdCard(Keyset keyset, IAttributeFileSystem fileSystem) { var concatFs = new ConcatenationFileSystem(fileSystem); - var contentDirFs = new SubdirectoryFileSystem(concatFs, "/Nintendo/Contents"); + SubdirectoryFileSystem.CreateNew(out SubdirectoryFileSystem contentDirFs, concatFs, "/Nintendo/Contents".ToU8String()).ThrowIfFailure(); AesXtsFileSystem encSaveFs = null; if (fileSystem.DirectoryExists("/Nintendo/save")) { - var saveDirFs = new SubdirectoryFileSystem(concatFs, "/Nintendo/save"); + SubdirectoryFileSystem.CreateNew(out SubdirectoryFileSystem saveDirFs, concatFs, "/Nintendo/save".ToU8String()).ThrowIfFailure(); encSaveFs = new AesXtsFileSystem(saveDirFs, keyset.SdCardKeys[0], 0x4000); } @@ -55,8 +56,14 @@ namespace LibHac public static SwitchFs OpenNandPartition(Keyset keyset, IAttributeFileSystem fileSystem) { var concatFs = new ConcatenationFileSystem(fileSystem); - IFileSystem saveDirFs = concatFs.DirectoryExists("/save") ? new SubdirectoryFileSystem(concatFs, "/save") : null; - var contentDirFs = new SubdirectoryFileSystem(concatFs, "/Contents"); + SubdirectoryFileSystem saveDirFs = null; + + if (concatFs.DirectoryExists("/save")) + { + SubdirectoryFileSystem.CreateNew(out saveDirFs, concatFs, "/save".ToU8String()).ThrowIfFailure(); + } + + SubdirectoryFileSystem.CreateNew(out SubdirectoryFileSystem contentDirFs, concatFs, "/Contents".ToU8String()).ThrowIfFailure(); return new SwitchFs(keyset, contentDirFs, saveDirFs); } diff --git a/tests/LibHac.Tests/Fs/SubdirectoryFileSystemTests.cs b/tests/LibHac.Tests/Fs/SubdirectoryFileSystemTests.cs index e033e095..d2112350 100644 --- a/tests/LibHac.Tests/Fs/SubdirectoryFileSystemTests.cs +++ b/tests/LibHac.Tests/Fs/SubdirectoryFileSystemTests.cs @@ -1,7 +1,8 @@ -using System.Diagnostics; +using LibHac.Common; using LibHac.Fs; using LibHac.FsSystem; using LibHac.Tests.Fs.IFileSystemTestBase; +using Xunit; namespace LibHac.Tests.Fs { @@ -9,25 +10,51 @@ namespace LibHac.Tests.Fs { protected override IFileSystem CreateFileSystem() { - Trace.Listeners.Clear(); + return CreateFileSystemInternal().subDirFs; + } + + private (IFileSystem baseFs, IFileSystem subDirFs) CreateFileSystemInternal() + { var baseFs = new InMemoryFileSystem(); baseFs.CreateDirectory("/sub"); baseFs.CreateDirectory("/sub/path"); - var subFs = new SubdirectoryFileSystem(baseFs, "/sub/path"); + SubdirectoryFileSystem.CreateNew(out SubdirectoryFileSystem subFs, baseFs, "/sub/path".ToU8String()).ThrowIfFailure(); + return (baseFs, subFs); + } - return subFs; + [Fact] + public void CreateFile_CreatedInBaseFileSystem() + { + (IFileSystem baseFs, IFileSystem subDirFs) = CreateFileSystemInternal(); + + subDirFs.CreateFile("/file", 0, CreateFileOptions.None); + Result rc = baseFs.GetEntryType(out DirectoryEntryType type, "/sub/path/file"); + + Assert.True(rc.IsSuccess()); + Assert.Equal(DirectoryEntryType.File, type); + } + + [Fact] + public void CreateDirectory_CreatedInBaseFileSystem() + { + (IFileSystem baseFs, IFileSystem subDirFs) = CreateFileSystemInternal(); + + subDirFs.CreateDirectory("/dir"); + Result rc = baseFs.GetEntryType(out DirectoryEntryType type, "/sub/path/dir"); + + Assert.True(rc.IsSuccess()); + Assert.Equal(DirectoryEntryType.Directory, type); } } + public class SubdirectoryFileSystemTestsRoot : IFileSystemTests { protected override IFileSystem CreateFileSystem() { - Trace.Listeners.Clear(); var baseFs = new InMemoryFileSystem(); - var subFs = new SubdirectoryFileSystem(baseFs, "/"); - + SubdirectoryFileSystem.CreateNew(out SubdirectoryFileSystem subFs, baseFs, "/".ToU8String()).ThrowIfFailure(); return subFs; } }