Rewrite SubdirectoryFileSystem

This commit is contained in:
Alex Barney 2020-02-10 14:06:00 -07:00
parent 1c28c08c94
commit ecf7bcd8ad
7 changed files with 234 additions and 61 deletions

View file

@ -74,7 +74,13 @@ namespace LibHac.Common
public U8String ToU8String() 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);
} }
/// <summary> /// <summary>

View file

@ -41,7 +41,9 @@ namespace LibHac.FsService.Creators
// Actual FS does this check // Actual FS does this check
// if (!allowDirectorySaveData) return ResultFs.InvalidSaveDataEntryType.Log(); // 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 isPersistentSaveData = type != SaveDataType.Temporary;
bool isUserSaveData = type == SaveDataType.Account || type == SaveDataType.Device; bool isUserSaveData = type == SaveDataType.Account || type == SaveDataType.Device;

View file

@ -1,4 +1,5 @@
using LibHac.Fs; using LibHac.Common;
using LibHac.Fs;
using LibHac.FsSystem; using LibHac.FsSystem;
namespace LibHac.FsService.Creators namespace LibHac.FsService.Creators
@ -12,9 +13,9 @@ namespace LibHac.FsService.Creators
Result rc = baseFileSystem.OpenDirectory(out IDirectory _, path, OpenDirectoryMode.Directory); Result rc = baseFileSystem.OpenDirectory(out IDirectory _, path, OpenDirectoryMode.Directory);
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
subDirFileSystem = new SubdirectoryFileSystem(baseFileSystem, path); rc = SubdirectoryFileSystem.CreateNew(out SubdirectoryFileSystem fs, baseFileSystem, path.ToU8String());
subDirFileSystem = fs;
return Result.Success; return rc;
} }
} }
} }

View file

@ -1,4 +1,5 @@
using LibHac.Fs; using LibHac.Common;
using LibHac.Fs;
using LibHac.FsSystem; using LibHac.FsSystem;
namespace LibHac.FsService namespace LibHac.FsService
@ -36,9 +37,9 @@ namespace LibHac.FsService
Result rc = baseFileSystem.OpenDirectory(out IDirectory _, path, OpenDirectoryMode.Directory); Result rc = baseFileSystem.OpenDirectory(out IDirectory _, path, OpenDirectoryMode.Directory);
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
subFileSystem = new SubdirectoryFileSystem(baseFileSystem, path); rc = SubdirectoryFileSystem.CreateNew(out SubdirectoryFileSystem fs, baseFileSystem, path.ToU8String());
subFileSystem = fs;
return Result.Success; return rc;
} }
public static bool UseDeviceUniqueSaveMac(SaveDataSpaceId spaceId) public static bool UseDeviceUniqueSaveMac(SaveDataSpaceId spaceId)

View file

@ -1,134 +1,263 @@
using System; using System;
using System.Diagnostics;
using LibHac.Common;
using LibHac.Fs; using LibHac.Fs;
namespace LibHac.FsSystem namespace LibHac.FsSystem
{ {
public class SubdirectoryFileSystem : FileSystemBase public class SubdirectoryFileSystem : FileSystemBase
{ {
private string RootPath { get; } private IFileSystem BaseFileSystem { get; }
private IFileSystem ParentFileSystem { 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; BaseFileSystem = baseFileSystem;
RootPath = PathTools.Normalize(rootPath); PreserveUnc = preserveUnc;
}
private Result Initialize(U8Span rootPath)
{
if (StringUtils.GetLength(rootPath, PathTools.MaxPathLength + 1) > PathTools.MaxPathLength)
return ResultFs.TooLongPath.Log();
Span<byte> 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<byte> 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) protected override Result CreateDirectoryImpl(string path)
{ {
string fullPath = ResolveFullPath(PathTools.Normalize(path)); var u8Path = new U8String(path);
return ParentFileSystem.CreateDirectory(fullPath); Span<byte> 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) 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<byte> 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) protected override Result DeleteDirectoryImpl(string path)
{ {
string fullPath = ResolveFullPath(PathTools.Normalize(path)); var u8Path = new U8String(path);
return ParentFileSystem.DeleteDirectory(fullPath); Span<byte> 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) protected override Result DeleteDirectoryRecursivelyImpl(string path)
{ {
string fullPath = ResolveFullPath(PathTools.Normalize(path)); var u8Path = new U8String(path);
return ParentFileSystem.DeleteDirectoryRecursively(fullPath); Span<byte> 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) protected override Result CleanDirectoryRecursivelyImpl(string path)
{ {
string fullPath = ResolveFullPath(PathTools.Normalize(path)); var u8Path = new U8String(path);
return ParentFileSystem.CleanDirectoryRecursively(fullPath); Span<byte> 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) protected override Result DeleteFileImpl(string path)
{ {
string fullPath = ResolveFullPath(PathTools.Normalize(path)); var u8Path = new U8String(path);
return ParentFileSystem.DeleteFile(fullPath); Span<byte> 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) 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<byte> 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) 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<byte> 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) protected override Result RenameDirectoryImpl(string oldPath, string newPath)
{ {
string fullOldPath = ResolveFullPath(PathTools.Normalize(oldPath)); var u8OldPath = new U8String(oldPath);
string fullNewPath = ResolveFullPath(PathTools.Normalize(newPath)); var u8NewPath = new U8String(newPath);
return ParentFileSystem.RenameDirectory(fullOldPath, fullNewPath); Span<byte> fullOldPath = stackalloc byte[PathTools.MaxPathLength + 1];
Span<byte> 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) protected override Result RenameFileImpl(string oldPath, string newPath)
{ {
string fullOldPath = ResolveFullPath(PathTools.Normalize(oldPath)); var u8OldPath = new U8String(oldPath);
string fullNewPath = ResolveFullPath(PathTools.Normalize(newPath)); var u8NewPath = new U8String(newPath);
return ParentFileSystem.RenameFile(fullOldPath, fullNewPath); Span<byte> fullOldPath = stackalloc byte[PathTools.MaxPathLength + 1];
Span<byte> 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) 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<byte> 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() protected override Result CommitImpl()
{ {
return ParentFileSystem.Commit(); return BaseFileSystem.Commit();
} }
protected override Result GetFreeSpaceSizeImpl(out long freeSpace, string path) 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<byte> 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) 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<byte> 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) 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<byte> 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<byte> outBuffer, ReadOnlySpan<byte> inBuffer, QueryId queryId, string path) protected override Result QueryEntryImpl(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, QueryId queryId, string path)
{ {
string fullPath = ResolveFullPath(PathTools.Normalize(path)); var u8Path = new U8String(path);
return ParentFileSystem.QueryEntry(outBuffer, inBuffer, queryId, fullPath); Span<byte> 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));
} }
} }
} }

View file

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using LibHac.Common;
using LibHac.Fs; using LibHac.Fs;
using LibHac.FsSystem; using LibHac.FsSystem;
using LibHac.FsSystem.NcaUtils; using LibHac.FsSystem.NcaUtils;
@ -38,12 +39,12 @@ namespace LibHac
public static SwitchFs OpenSdCard(Keyset keyset, IAttributeFileSystem fileSystem) public static SwitchFs OpenSdCard(Keyset keyset, IAttributeFileSystem fileSystem)
{ {
var concatFs = new ConcatenationFileSystem(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; AesXtsFileSystem encSaveFs = null;
if (fileSystem.DirectoryExists("/Nintendo/save")) 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); encSaveFs = new AesXtsFileSystem(saveDirFs, keyset.SdCardKeys[0], 0x4000);
} }
@ -55,8 +56,14 @@ namespace LibHac
public static SwitchFs OpenNandPartition(Keyset keyset, IAttributeFileSystem fileSystem) public static SwitchFs OpenNandPartition(Keyset keyset, IAttributeFileSystem fileSystem)
{ {
var concatFs = new ConcatenationFileSystem(fileSystem); var concatFs = new ConcatenationFileSystem(fileSystem);
IFileSystem saveDirFs = concatFs.DirectoryExists("/save") ? new SubdirectoryFileSystem(concatFs, "/save") : null; SubdirectoryFileSystem saveDirFs = null;
var contentDirFs = new SubdirectoryFileSystem(concatFs, "/Contents");
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); return new SwitchFs(keyset, contentDirFs, saveDirFs);
} }

View file

@ -1,7 +1,8 @@
using System.Diagnostics; using LibHac.Common;
using LibHac.Fs; using LibHac.Fs;
using LibHac.FsSystem; using LibHac.FsSystem;
using LibHac.Tests.Fs.IFileSystemTestBase; using LibHac.Tests.Fs.IFileSystemTestBase;
using Xunit;
namespace LibHac.Tests.Fs namespace LibHac.Tests.Fs
{ {
@ -9,25 +10,51 @@ namespace LibHac.Tests.Fs
{ {
protected override IFileSystem CreateFileSystem() protected override IFileSystem CreateFileSystem()
{ {
Trace.Listeners.Clear(); return CreateFileSystemInternal().subDirFs;
}
private (IFileSystem baseFs, IFileSystem subDirFs) CreateFileSystemInternal()
{
var baseFs = new InMemoryFileSystem(); var baseFs = new InMemoryFileSystem();
baseFs.CreateDirectory("/sub"); baseFs.CreateDirectory("/sub");
baseFs.CreateDirectory("/sub/path"); 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 public class SubdirectoryFileSystemTestsRoot : IFileSystemTests
{ {
protected override IFileSystem CreateFileSystem() protected override IFileSystem CreateFileSystem()
{ {
Trace.Listeners.Clear();
var baseFs = new InMemoryFileSystem(); var baseFs = new InMemoryFileSystem();
var subFs = new SubdirectoryFileSystem(baseFs, "/"); SubdirectoryFileSystem.CreateNew(out SubdirectoryFileSystem subFs, baseFs, "/".ToU8String()).ThrowIfFailure();
return subFs; return subFs;
} }
} }