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()
{
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>

View file

@ -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;

View file

@ -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;
}
}
}

View file

@ -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)

View file

@ -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<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)
{
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)
{
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)
{
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)
{
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)
{
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)
{
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)
{
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)
{
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)
{
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<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)
{
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<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)
{
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()
{
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<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)
{
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)
{
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)
{
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.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);
}

View file

@ -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;
}
}