Fixup ConcatenationFileSystem

- Experiment with using Catch/Handle/Rethrow for logging Results
- Try adding a new Ret function for logging results
- Misc tweaks
This commit is contained in:
Alex Barney 2021-12-21 17:45:27 -07:00
parent c9a2056844
commit feef0ff63f
7 changed files with 831 additions and 215 deletions

View File

@ -4,6 +4,7 @@ using System.Buffers.Text;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using LibHac.Common;
using LibHac.Common.FixedArrays;
using LibHac.Diag;
using LibHac.Fs;
using LibHac.Fs.Fsa;
@ -16,23 +17,24 @@ namespace LibHac.FsSystem;
/// An <see cref="IFileSystem"/> that stores large files as smaller, separate sub-files.
/// </summary>
/// <remarks>
/// This filesystem is mainly used to allow storing large files on filesystems that have low
/// <para>This filesystem is mainly used to allow storing large files on filesystems that have low
/// limits on file size such as FAT filesystems. The underlying base filesystem must have
/// support for the "Archive" file attribute found in FAT or NTFS filesystems.<br/>
///<br/>
/// A <see cref="ConcatenationFileSystem"/> may contain both standard files or Concatenation files.
/// support for the "Archive" file attribute found in FAT or NTFS filesystems.
/// </para>
/// <para>A <see cref="ConcatenationFileSystem"/> may contain both standard files or Concatenation files.
/// If a directory has the archive attribute set, its contents will be concatenated and treated
/// as a single file. These sub-files must follow the naming scheme "00", "01", "02", ...
/// Each sub-file except the final one must have the size <see cref="_InternalFileSize"/> that was specified
/// as a single file. These internal files must follow the naming scheme "00", "01", "02", ...
/// Each internal file except the final one must have the internal file size that was specified
/// at the creation of the <see cref="ConcatenationFileSystem"/>.
/// <br/>Based on FS 12.1.0 (nnSdk 12.3.1)
/// </para>
/// <para>Based on FS 12.1.0 (nnSdk 12.3.1)</para>
/// </remarks>
public class ConcatenationFileSystem : IFileSystem
{
private class ConcatenationFile : IFile
{
private OpenMode _mode;
private List<IFile> _files;
private List<IFile> _fileArray;
private long _internalFileSize;
private IFileSystem _baseFileSystem;
private Path.Stored _path;
@ -40,7 +42,7 @@ public class ConcatenationFileSystem : IFileSystem
public ConcatenationFile(OpenMode mode, ref List<IFile> internalFiles, long internalFileSize, IFileSystem baseFileSystem)
{
_mode = mode;
_files = Shared.Move(ref internalFiles);
_fileArray = Shared.Move(ref internalFiles);
_internalFileSize = internalFileSize;
_baseFileSystem = baseFileSystem;
_path = new Path.Stored();
@ -50,19 +52,19 @@ public class ConcatenationFileSystem : IFileSystem
{
_path.Dispose();
foreach (IFile file in _files)
foreach (IFile file in _fileArray)
{
file?.Dispose();
}
_files.Clear();
_fileArray.Clear();
base.Dispose();
}
public Result Initialize(in Path path)
{
return _path.Initialize(in path);
return _path.Initialize(in path).Ret();
}
private int GetInternalFileIndex(long offset)
@ -96,10 +98,11 @@ public class ConcatenationFileSystem : IFileSystem
UnsafeHelpers.SkipParamInit(out bytesRead);
long fileOffset = offset;
int bufferOffset = 0;
Result rc = DryRead(out long remaining, offset, destination.Length, in option, _mode);
if (rc.IsFailure()) return rc;
if (rc.IsFailure()) return rc.Miss();
int bufferOffset = 0;
while (remaining > 0)
{
@ -109,11 +112,11 @@ public class ConcatenationFileSystem : IFileSystem
int bytesToRead = (int)Math.Min(remaining, internalFileRemaining);
Assert.SdkAssert(fileIndex < _files.Count);
Assert.SdkAssert(fileIndex < _fileArray.Count);
rc = _files[fileIndex].Read(out long internalFileBytesRead, internalFileOffset,
rc = _fileArray[fileIndex].Read(out long internalFileBytesRead, internalFileOffset,
destination.Slice(bufferOffset, bytesToRead), in option);
if (rc.IsFailure()) return rc;
if (rc.IsFailure()) return rc.Miss();
remaining -= internalFileBytesRead;
bufferOffset += (int)internalFileBytesRead;
@ -130,12 +133,12 @@ public class ConcatenationFileSystem : IFileSystem
protected override Result DoWrite(long offset, ReadOnlySpan<byte> source, in WriteOption option)
{
Result rc = DryWrite(out bool needsAppend, offset, source.Length, in option, _mode);
if (rc.IsFailure()) return rc;
if (rc.IsFailure()) return rc.Miss();
if (source.Length > 0 && needsAppend)
{
rc = SetSize(offset + source.Length);
if (rc.IsFailure()) return rc;
if (rc.IsFailure()) return rc.Miss();
}
int remaining = source.Length;
@ -153,11 +156,11 @@ public class ConcatenationFileSystem : IFileSystem
int bytesToWrite = (int)Math.Min(remaining, internalFileRemaining);
Assert.SdkAssert(fileIndex < _files.Count);
Assert.SdkAssert(fileIndex < _fileArray.Count);
rc = _files[fileIndex].Write(internalFileOffset, source.Slice(bufferOffset, bytesToWrite),
rc = _fileArray[fileIndex].Write(internalFileOffset, source.Slice(bufferOffset, bytesToWrite),
in internalFileOption);
if (rc.IsFailure()) return rc;
if (rc.IsFailure()) return rc.Miss();
remaining -= bytesToWrite;
bufferOffset += bytesToWrite;
@ -167,7 +170,7 @@ public class ConcatenationFileSystem : IFileSystem
if (option.HasFlushFlag())
{
rc = Flush();
if (rc.IsFailure()) return rc;
if (rc.IsFailure()) return rc.Miss();
}
return Result.Success;
@ -178,10 +181,12 @@ public class ConcatenationFileSystem : IFileSystem
if (!_mode.HasFlag(OpenMode.Write))
return Result.Success;
foreach (IFile file in _files)
for (int index = 0; index < _fileArray.Count; index++)
{
Result rc = file.Flush();
if (rc.IsFailure()) return rc;
Assert.SdkNotNull(_fileArray[index]);
Result rc = _fileArray[index].Flush();
if (rc.IsFailure()) return rc.Miss();
}
return Result.Success;
@ -190,10 +195,10 @@ public class ConcatenationFileSystem : IFileSystem
protected override Result DoSetSize(long size)
{
Result rc = DrySetSize(size, _mode);
if (rc.IsFailure()) return rc;
if (rc.IsFailure()) return rc.Miss();
rc = GetSize(out long currentSize);
if (rc.IsFailure()) return rc;
if (rc.IsFailure()) return rc.Miss();
if (currentSize == size) return Result.Success;
@ -202,51 +207,51 @@ public class ConcatenationFileSystem : IFileSystem
using var internalFilePath = new Path();
rc = internalFilePath.Initialize(in _path);
if (rc.IsFailure()) return rc;
if (rc.IsFailure()) return rc.Miss();
if (size > currentSize)
{
rc = _files[currentTailIndex].SetSize(GetInternalFileSize(size, currentTailIndex));
if (rc.IsFailure()) return rc;
rc = _fileArray[currentTailIndex].SetSize(GetInternalFileSize(size, currentTailIndex));
if (rc.IsFailure()) return rc.Miss();
for (int i = currentTailIndex + 1; i < newTailIndex; i++)
for (int i = currentTailIndex + 1; i <= newTailIndex; i++)
{
rc = AppendInternalFilePath(ref internalFilePath.Ref(), i);
if (rc.IsFailure()) return rc;
if (rc.IsFailure()) return rc.Miss();
rc = _baseFileSystem.CreateFile(in internalFilePath, GetInternalFileSize(size, i),
CreateFileOptions.None);
if (rc.IsFailure()) return rc;
if (rc.IsFailure()) return rc.Miss();
using var newInternalFile = new UniqueRef<IFile>();
rc = _baseFileSystem.OpenFile(ref newInternalFile.Ref(), in internalFilePath, _mode);
if (rc.IsFailure()) return rc;
if (rc.IsFailure()) return rc.Miss();
_files.Add(newInternalFile.Release());
_fileArray.Add(newInternalFile.Release());
rc = internalFilePath.RemoveChild();
if (rc.IsFailure()) return rc;
if (rc.IsFailure()) return rc.Miss();
}
}
else
{
for (int i = currentTailIndex - 1; i > newTailIndex; i--)
for (int i = currentTailIndex; i > newTailIndex; i--)
{
_files[i].Dispose();
_files.RemoveAt(i);
_fileArray[i].Dispose();
_fileArray.RemoveAt(i);
rc = AppendInternalFilePath(ref internalFilePath.Ref(), i);
if (rc.IsFailure()) return rc;
if (rc.IsFailure()) return rc.Miss();
rc = _baseFileSystem.DeleteFile(in internalFilePath);
if (rc.IsFailure()) return rc;
if (rc.IsFailure()) return rc.Miss();
rc = internalFilePath.RemoveChild();
if (rc.IsFailure()) return rc;
if (rc.IsFailure()) return rc.Miss();
}
rc = _files[newTailIndex].SetSize(GetInternalFileSize(size, newTailIndex));
if (rc.IsFailure()) return rc;
rc = _fileArray[newTailIndex].SetSize(GetInternalFileSize(size, newTailIndex));
if (rc.IsFailure()) return rc.Miss();
}
return Result.Success;
@ -258,10 +263,10 @@ public class ConcatenationFileSystem : IFileSystem
long totalSize = 0;
foreach (IFile file in _files)
foreach (IFile file in _fileArray)
{
Result rc = file.GetSize(out long internalFileSize);
if (rc.IsFailure()) return rc;
if (rc.IsFailure()) return rc.Miss();
totalSize += internalFileSize;
}
@ -273,44 +278,43 @@ public class ConcatenationFileSystem : IFileSystem
protected override Result DoOperateRange(Span<byte> outBuffer, OperationId operationId, long offset, long size,
ReadOnlySpan<byte> inBuffer)
{
if (operationId == OperationId.InvalidateCache)
switch (operationId)
{
if (!_mode.HasFlag(OpenMode.Read))
return ResultFs.ReadUnpermitted.Log();
case OperationId.InvalidateCache:
{
if (!_mode.HasFlag(OpenMode.Read))
return ResultFs.ReadUnpermitted.Log();
var closure = new OperateRangeClosure();
closure.OutBuffer = outBuffer;
closure.InBuffer = inBuffer;
closure.OperationId = operationId;
var closure = new OperateRangeClosure();
closure.OutBuffer = outBuffer;
closure.InBuffer = inBuffer;
closure.OperationId = operationId;
Result rc = DoOperateRangeImpl(offset, size, InvalidateCacheImpl, ref closure);
if (rc.IsFailure()) return rc;
return DoOperateRangeImpl(offset, size, InvalidateCacheImpl, ref closure).Ret();
}
case OperationId.QueryRange:
{
if (outBuffer.Length != Unsafe.SizeOf<QueryRangeInfo>())
return ResultFs.InvalidSize.Log();
var closure = new OperateRangeClosure();
closure.InBuffer = inBuffer;
closure.OperationId = operationId;
closure.InfoMerged.Clear();
Result rc = DoOperateRangeImpl(offset, size, QueryRangeImpl, ref closure);
if (rc.IsFailure()) return rc.Miss();
SpanHelpers.AsByteSpan(ref closure.InfoMerged).CopyTo(outBuffer);
return Result.Success;
}
default:
return ResultFs.UnsupportedOperateRangeForConcatenationFile.Log();
}
else if (operationId == OperationId.QueryRange)
{
if (outBuffer.Length != Unsafe.SizeOf<QueryRangeInfo>())
return ResultFs.InvalidSize.Log();
var closure = new OperateRangeClosure();
closure.InBuffer = inBuffer;
closure.OperationId = operationId;
closure.InfoMerged.Clear();
Result rc = DoOperateRangeImpl(offset, size, QueryRangeImpl, ref closure);
if (rc.IsFailure()) return rc;
SpanHelpers.AsByteSpan(ref closure.InfoMerged).CopyTo(outBuffer);
}
else
{
return ResultFs.UnsupportedOperateRangeForConcatenationFile.Log();
}
return Result.Success;
static Result InvalidateCacheImpl(IFile file, long offset, long size, ref OperateRangeClosure closure)
{
return file.OperateRange(closure.OutBuffer, closure.OperationId, offset, size, closure.InBuffer);
return file.OperateRange(closure.OutBuffer, closure.OperationId, offset, size, closure.InBuffer).Ret();
}
static Result QueryRangeImpl(IFile file, long offset, long size, ref OperateRangeClosure closure)
@ -319,7 +323,7 @@ public class ConcatenationFileSystem : IFileSystem
Result rc = file.OperateRange(SpanHelpers.AsByteSpan(ref infoEntry), closure.OperationId, offset, size,
closure.InBuffer);
if (rc.IsFailure()) return rc;
if (rc.IsFailure()) return rc.Miss();
closure.InfoMerged.Merge(in infoEntry);
return Result.Success;
@ -333,7 +337,7 @@ public class ConcatenationFileSystem : IFileSystem
return ResultFs.OutOfRange.Log();
Result rc = GetSize(out long currentSize);
if (rc.IsFailure()) return rc;
if (rc.IsFailure()) return rc.Miss();
if (offset > currentSize)
return ResultFs.OutOfRange.Log();
@ -350,10 +354,10 @@ public class ConcatenationFileSystem : IFileSystem
long sizeToOperate = Math.Min(remaining, internalFileRemaining);
Assert.SdkAssert(fileIndex < _files.Count);
Assert.SdkAssert(fileIndex < _fileArray.Count);
rc = func(_files[fileIndex], internalFileOffset, sizeToOperate, ref closure);
if (rc.IsFailure()) return rc;
rc = func(_fileArray[fileIndex], internalFileOffset, sizeToOperate, ref closure);
if (rc.IsFailure()) return rc.Miss();
remaining -= sizeToOperate;
currentOffset += sizeToOperate;
@ -386,6 +390,7 @@ public class ConcatenationFileSystem : IFileSystem
{
_mode = mode;
_baseDirectory = new UniqueRef<IDirectory>(ref baseDirectory);
_path = new Path.Stored();
_baseFileSystem = baseFileSystem;
_concatenationFileSystem = concatFileSystem;
}
@ -400,10 +405,7 @@ public class ConcatenationFileSystem : IFileSystem
public Result Initialize(in Path path)
{
Result rc = _path.Initialize(in path);
if (rc.IsFailure()) return rc;
return Result.Success;
return _path.Initialize(in path).Ret();
}
protected override Result DoRead(out long entriesRead, Span<DirectoryEntry> entryBuffer)
@ -416,7 +418,7 @@ public class ConcatenationFileSystem : IFileSystem
while (readCountTotal < entryBuffer.Length)
{
Result rc = _baseDirectory.Get.Read(out long readCount, SpanHelpers.AsSpan(ref entry));
if (rc.IsFailure()) return rc;
if (rc.IsFailure()) return rc.Miss();
if (readCount == 0)
break;
@ -432,13 +434,13 @@ public class ConcatenationFileSystem : IFileSystem
{
using var internalFilePath = new Path();
rc = internalFilePath.Initialize(in _path);
if (rc.IsFailure()) return rc;
if (rc.IsFailure()) return rc.Miss();
rc = internalFilePath.AppendChild(entry.Name);
if (rc.IsFailure()) return rc;
if (rc.IsFailure()) return rc.Miss();
rc = _concatenationFileSystem.GetFileSize(out entry.Size, in internalFilePath);
if (rc.IsFailure()) return rc;
if (rc.IsFailure()) return rc.Miss();
}
}
@ -461,14 +463,14 @@ public class ConcatenationFileSystem : IFileSystem
Result rc = _baseFileSystem.OpenDirectory(ref directory.Ref(), in path,
OpenDirectoryMode.All | OpenDirectoryMode.NoFileSize);
if (rc.IsFailure()) return rc;
if (rc.IsFailure()) return rc.Miss();
long entryCountTotal = 0;
while (true)
{
directory.Get.Read(out long readCount, SpanHelpers.AsSpan(ref entry));
if (rc.IsFailure()) return rc;
if (rc.IsFailure()) return rc.Miss();
if (readCount == 0)
break;
@ -492,15 +494,17 @@ public class ConcatenationFileSystem : IFileSystem
public static readonly long DefaultInternalFileSize = 0xFFFF0000; // Hard-coded value used by FS
private IAttributeFileSystem _baseFileSystem;
private long _InternalFileSize;
private UniqueRef<IAttributeFileSystem> _baseFileSystem;
private long _internalFileSize;
/// <summary>
/// Initializes a new <see cref="ConcatenationFileSystem"/> with an internal file size of <see cref="DefaultInternalFileSize"/>.
/// </summary>
/// <param name="baseFileSystem">The base <see cref="IAttributeFileSystem"/> for the
/// new <see cref="ConcatenationFileSystem"/>.</param>
public ConcatenationFileSystem(IAttributeFileSystem baseFileSystem) : this(baseFileSystem, DefaultInternalFileSize) { }
public ConcatenationFileSystem(ref UniqueRef<IAttributeFileSystem> baseFileSystem) : this(ref baseFileSystem,
DefaultInternalFileSize)
{ }
/// <summary>
/// Initializes a new <see cref="ConcatenationFileSystem"/>.
@ -508,41 +512,41 @@ public class ConcatenationFileSystem : IFileSystem
/// <param name="baseFileSystem">The base <see cref="IAttributeFileSystem"/> for the
/// new <see cref="ConcatenationFileSystem"/>.</param>
/// <param name="internalFileSize">The size of each internal file. Once a file exceeds this size, a new internal file will be created</param>
public ConcatenationFileSystem(IAttributeFileSystem baseFileSystem, long internalFileSize)
public ConcatenationFileSystem(ref UniqueRef<IAttributeFileSystem> baseFileSystem, long internalFileSize)
{
_baseFileSystem = baseFileSystem;
_InternalFileSize = internalFileSize;
_baseFileSystem = new UniqueRef<IAttributeFileSystem>(ref baseFileSystem);
_internalFileSize = internalFileSize;
}
public override void Dispose()
{
_baseFileSystem?.Dispose();
_baseFileSystem = null;
_baseFileSystem.Destroy();
base.Dispose();
}
private static ReadOnlySpan<byte> RootPath => new[] { (byte)'/' };
/// <summary>
/// Appends the two-digit-padded <paramref name="index"/> to the given <see cref="Path"/>.
/// </summary>
/// <param name="path">The <see cref="Path"/> to be modified.</param>
/// <param name="index">The index to append to the <see cref="Path"/>.</param>
/// <returns><see cref="Result.Success"/>: The operation was successful.</returns>
private static Result AppendInternalFilePath(ref Path path, int index)
{
// Use an int as the buffer instead of a stackalloc byte[3] to workaround CS8350.
// Path.AppendChild will not save the span passed to it so this should be safe.
int bufferInt = 0;
Utf8Formatter.TryFormat(index, SpanHelpers.AsByteSpan(ref bufferInt), out _, new StandardFormat('d', 2));
var buffer = new Array3<byte>();
Utf8Formatter.TryFormat(index, buffer.Items, out _, new StandardFormat('d', 2));
return path.AppendChild(SpanHelpers.AsByteSpan(ref bufferInt));
return path.AppendChild(buffer.ItemsRo).Ret();
}
private static Result GenerateInternalFilePath(ref Path outPath, int index, in Path basePath)
{
Result rc = outPath.Initialize(in basePath);
if (rc.IsFailure()) return rc;
if (rc.IsFailure()) return rc.Miss();
rc = AppendInternalFilePath(ref outPath, index);
if (rc.IsFailure()) return rc;
return Result.Success;
return AppendInternalFilePath(ref outPath, index).Ret();
}
private static Result GenerateParentPath(ref Path outParentPath, in Path path)
@ -551,12 +555,9 @@ public class ConcatenationFileSystem : IFileSystem
return ResultFs.PathNotFound.Log();
Result rc = outParentPath.Initialize(in path);
if (rc.IsFailure()) return rc;
if (rc.IsFailure()) return rc.Miss();
rc = outParentPath.RemoveChild();
if (rc.IsFailure()) return rc;
return Result.Success;
return outParentPath.RemoveChild().Ret();
}
private static bool IsConcatenationFileAttribute(NxFileAttributes attribute)
@ -566,7 +567,7 @@ public class ConcatenationFileSystem : IFileSystem
private bool IsConcatenationFile(in Path path)
{
Result rc = _baseFileSystem.GetFileAttributes(out NxFileAttributes attribute, in path);
Result rc = _baseFileSystem.Get.GetFileAttributes(out NxFileAttributes attribute, in path);
if (rc.IsFailure())
return false;
@ -579,29 +580,32 @@ public class ConcatenationFileSystem : IFileSystem
using var internalFilePath = new Path();
Result rc = internalFilePath.Initialize(in path);
if (rc.IsFailure()) return rc;
if (rc.IsFailure()) return rc.Miss();
for (int i = 0; ; i++)
{
rc = AppendInternalFilePath(ref internalFilePath.Ref(), i);
if (rc.IsFailure()) return rc;
if (rc.IsFailure()) return rc.Miss();
rc = _baseFileSystem.GetEntryType(out _, in internalFilePath);
rc = _baseFileSystem.Get.GetEntryType(out _, in internalFilePath);
if (rc.IsFailure())
{
// We've passed the last internal file of the concatenation file
// once the next internal file doesn't exist.
if (ResultFs.PathNotFound.Includes(rc))
{
rc.Catch();
count = i;
rc.Handle();
return Result.Success;
}
return rc;
return rc.Miss();
}
rc = internalFilePath.RemoveChild();
if (rc.IsFailure()) return rc;
if (rc.IsFailure()) return rc.Miss();
}
}
@ -613,67 +617,70 @@ public class ConcatenationFileSystem : IFileSystem
return Result.Success;
}
return _baseFileSystem.GetEntryType(out entryType, path);
return _baseFileSystem.Get.GetEntryType(out entryType, path).Ret();
}
protected override Result DoGetFreeSpaceSize(out long freeSpace, in Path path)
{
return _baseFileSystem.GetFreeSpaceSize(out freeSpace, path);
return _baseFileSystem.Get.GetFreeSpaceSize(out freeSpace, path).Ret();
}
protected override Result DoGetTotalSpaceSize(out long totalSpace, in Path path)
{
return _baseFileSystem.GetTotalSpaceSize(out totalSpace, path);
return _baseFileSystem.Get.GetTotalSpaceSize(out totalSpace, path).Ret();
}
protected override Result DoGetFileTimeStampRaw(out FileTimeStampRaw timeStamp, in Path path)
{
return _baseFileSystem.GetFileTimeStampRaw(out timeStamp, path);
return _baseFileSystem.Get.GetFileTimeStampRaw(out timeStamp, path).Ret();
}
protected override Result DoFlush()
{
return _baseFileSystem.Flush();
return _baseFileSystem.Get.Flush().Ret();
}
protected override Result DoOpenFile(ref UniqueRef<IFile> outFile, in Path path, OpenMode mode)
{
if (!IsConcatenationFile(in path))
{
return _baseFileSystem.OpenFile(ref outFile, in path, mode);
return _baseFileSystem.Get.OpenFile(ref outFile, in path, mode).Ret();
}
Result rc = GetInternalFileCount(out int fileCount, in path);
if (rc.IsFailure()) return rc;
if (rc.IsFailure()) return rc.Miss();
if (fileCount <= 0)
return ResultFs.ConcatenationFsInvalidInternalFileCount.Log();
var internalFiles = new List<IFile>(fileCount);
using var filePath = new Path();
filePath.Initialize(in path);
if (rc.IsFailure()) return rc;
if (rc.IsFailure()) return rc.Miss();
try
{
for (int i = 0; i < fileCount; i++)
{
rc = AppendInternalFilePath(ref filePath.Ref(), i);
if (rc.IsFailure()) return rc;
if (rc.IsFailure()) return rc.Miss();
using var internalFile = new UniqueRef<IFile>();
rc = _baseFileSystem.OpenFile(ref internalFile.Ref(), in filePath, mode);
if (rc.IsFailure()) return rc;
rc = _baseFileSystem.Get.OpenFile(ref internalFile.Ref(), in filePath, mode);
if (rc.IsFailure()) return rc.Miss();
internalFiles.Add(internalFile.Release());
rc = filePath.RemoveChild();
if (rc.IsFailure()) return rc;
if (rc.IsFailure()) return rc.Miss();
}
using var concatFile = new UniqueRef<ConcatenationFile>(
new ConcatenationFile(mode, ref internalFiles, _InternalFileSize, _baseFileSystem));
new ConcatenationFile(mode, ref internalFiles, _internalFileSize, _baseFileSystem.Get));
rc = concatFile.Get.Initialize(in path);
if (rc.IsFailure()) return rc;
if (rc.IsFailure()) return rc.Miss();
outFile.Set(ref concatFile.Ref());
return Result.Success;
@ -699,13 +706,13 @@ public class ConcatenationFileSystem : IFileSystem
}
using var baseDirectory = new UniqueRef<IDirectory>();
Result rc = _baseFileSystem.OpenDirectory(ref baseDirectory.Ref(), path, OpenDirectoryMode.All);
if (rc.IsFailure()) return rc;
Result rc = _baseFileSystem.Get.OpenDirectory(ref baseDirectory.Ref(), path, OpenDirectoryMode.All);
if (rc.IsFailure()) return rc.Miss();
using var concatDirectory = new UniqueRef<ConcatenationDirectory>(
new ConcatenationDirectory(mode, ref baseDirectory.Ref(), this, _baseFileSystem));
new ConcatenationDirectory(mode, ref baseDirectory.Ref(), this, _baseFileSystem.Get));
rc = concatDirectory.Get.Initialize(in path);
if (rc.IsFailure()) return rc;
if (rc.IsFailure()) return rc.Miss();
outDirectory.Set(ref concatDirectory.Ref());
return Result.Success;
@ -718,12 +725,12 @@ public class ConcatenationFileSystem : IFileSystem
// Create a normal file if the concatenation file flag isn't set
if (!option.HasFlag(CreateFileOptions.CreateConcatenationFile))
{
return _baseFileSystem.CreateFile(path, size, newOption);
return _baseFileSystem.Get.CreateFile(path, size, newOption).Ret();
}
using var parentPath = new Path();
Result rc = GenerateParentPath(ref parentPath.Ref(), in path);
if (rc.IsFailure()) return rc;
if (rc.IsFailure()) return rc.Miss();
if (IsConcatenationFile(in parentPath))
{
@ -731,18 +738,18 @@ public class ConcatenationFileSystem : IFileSystem
return ResultFs.PathNotFound.Log();
}
rc = _baseFileSystem.CreateDirectory(in path, NxFileAttributes.Archive);
if (rc.IsFailure()) return rc;
rc = _baseFileSystem.Get.CreateDirectory(in path, NxFileAttributes.Archive);
if (rc.IsFailure()) return rc.Miss();
// Handle the empty file case by manually creating a single empty internal file
if (size == 0)
{
using var emptyFilePath = new Path();
rc = GenerateInternalFilePath(ref emptyFilePath.Ref(), 0, in path);
if (rc.IsFailure()) return rc;
if (rc.IsFailure()) return rc.Miss();
rc = _baseFileSystem.CreateFile(in emptyFilePath, 0, newOption);
if (rc.IsFailure()) return rc;
rc = _baseFileSystem.Get.CreateFile(in emptyFilePath, 0, newOption);
if (rc.IsFailure()) return rc.Miss();
return Result.Success;
}
@ -750,38 +757,42 @@ public class ConcatenationFileSystem : IFileSystem
long remaining = size;
using var filePath = new Path();
filePath.Initialize(in path);
if (rc.IsFailure()) return rc;
if (rc.IsFailure()) return rc.Miss();
for (int i = 0; remaining > 0; i++)
{
rc = AppendInternalFilePath(ref filePath.Ref(), i);
if (rc.IsFailure()) return rc;
if (rc.IsFailure()) return rc.Miss();
long fileSize = Math.Min(remaining, _InternalFileSize);
Result createInternalFileResult = _baseFileSystem.CreateFile(in filePath, fileSize, newOption);
long fileSize = Math.Min(remaining, _internalFileSize);
Result createInternalFileResult = _baseFileSystem.Get.CreateFile(in filePath, fileSize, newOption);
// If something goes wrong when creating an internal file, delete all the
// internal files we've created so far and delete the directory.
// This will allow results like insufficient space results to be returned properly.
if (createInternalFileResult.IsFailure())
{
createInternalFileResult.Catch();
for (int index = i - 1; index >= 0; index--)
{
rc = GenerateInternalFilePath(ref filePath.Ref(), index, in path);
if (rc.IsFailure()) return rc;
rc = _baseFileSystem.DeleteFile(in filePath);
if (rc.IsFailure())
{
createInternalFileResult.Handle();
return rc.Miss();
}
if (_baseFileSystem.Get.DeleteFile(in filePath).IsFailure())
break;
}
_baseFileSystem.DeleteDirectoryRecursively(in path).IgnoreResult();
return createInternalFileResult;
_baseFileSystem.Get.DeleteDirectoryRecursively(in path).IgnoreResult();
return createInternalFileResult.Rethrow();
}
rc = filePath.RemoveChild();
if (rc.IsFailure()) return rc;
if (rc.IsFailure()) return rc.Miss();
remaining -= fileSize;
}
@ -793,30 +804,30 @@ public class ConcatenationFileSystem : IFileSystem
{
if (!IsConcatenationFile(in path))
{
return _baseFileSystem.DeleteFile(in path);
return _baseFileSystem.Get.DeleteFile(in path).Ret();
}
Result rc = GetInternalFileCount(out int count, path);
if (rc.IsFailure()) return rc;
if (rc.IsFailure()) return rc.Miss();
using var filePath = new Path();
rc = filePath.Initialize(in path);
if (rc.IsFailure()) return rc;
if (rc.IsFailure()) return rc.Miss();
for (int i = count - 1; i >= 0; i--)
{
rc = AppendInternalFilePath(ref filePath.Ref(), i);
if (rc.IsFailure()) return rc;
if (rc.IsFailure()) return rc.Miss();
rc = _baseFileSystem.DeleteFile(in filePath);
if (rc.IsFailure()) return rc;
rc = _baseFileSystem.Get.DeleteFile(in filePath);
if (rc.IsFailure()) return rc.Miss();
rc = filePath.RemoveChild();
if (rc.IsFailure()) return rc;
if (rc.IsFailure()) return rc.Miss();
}
rc = _baseFileSystem.DeleteDirectoryRecursively(in path);
if (rc.IsFailure()) return rc;
rc = _baseFileSystem.Get.DeleteDirectoryRecursively(in path);
if (rc.IsFailure()) return rc.Miss();
return Result.Success;
}
@ -826,15 +837,12 @@ public class ConcatenationFileSystem : IFileSystem
// Check if the parent path is a concatenation file because we can't create a directory inside one.
using var parentPath = new Path();
Result rc = GenerateParentPath(ref parentPath.Ref(), in path);
if (rc.IsFailure()) return rc;
if (rc.IsFailure()) return rc.Miss();
if (IsConcatenationFile(in parentPath))
return ResultFs.PathNotFound.Log();
rc = _baseFileSystem.CreateDirectory(in path);
if (rc.IsFailure()) return rc;
return Result.Success;
return _baseFileSystem.Get.CreateDirectory(in path).Ret();
}
protected override Result DoDeleteDirectory(in Path path)
@ -843,7 +851,7 @@ public class ConcatenationFileSystem : IFileSystem
if (IsConcatenationFile(path))
return ResultFs.PathNotFound.Log();
return _baseFileSystem.DeleteDirectory(path);
return _baseFileSystem.Get.DeleteDirectory(path).Ret();
}
private Result CleanDirectoryRecursivelyImpl(in Path path)
@ -852,17 +860,17 @@ public class ConcatenationFileSystem : IFileSystem
Result.Success;
static Result OnExitDir(in Path path, in DirectoryEntry entry, ref FsIterationTaskClosure closure) =>
closure.SourceFileSystem.DeleteDirectory(in path);
closure.SourceFileSystem.DeleteDirectory(in path).Ret();
static Result OnFile(in Path path, in DirectoryEntry entry, ref FsIterationTaskClosure closure) =>
closure.SourceFileSystem.DeleteFile(in path);
closure.SourceFileSystem.DeleteFile(in path).Ret();
var closure = new FsIterationTaskClosure();
closure.SourceFileSystem = this;
var directoryEntry = new DirectoryEntry();
return CleanupDirectoryRecursively(this, in path, ref directoryEntry, OnEnterDir, OnExitDir, OnFile,
ref closure);
ref closure).Ret();
}
protected override Result DoDeleteDirectoryRecursively(in Path path)
@ -871,12 +879,9 @@ public class ConcatenationFileSystem : IFileSystem
return ResultFs.PathNotFound.Log();
Result rc = CleanDirectoryRecursivelyImpl(in path);
if (rc.IsFailure()) return rc;
if (rc.IsFailure()) return rc.Miss();
rc = _baseFileSystem.DeleteDirectory(in path);
if (rc.IsFailure()) return rc;
return Result.Success;
return _baseFileSystem.Get.DeleteDirectory(in path).Ret();
}
protected override Result DoCleanDirectoryRecursively(in Path path)
@ -884,20 +889,15 @@ public class ConcatenationFileSystem : IFileSystem
if (IsConcatenationFile(in path))
return ResultFs.PathNotFound.Log();
Result rc = CleanDirectoryRecursivelyImpl(in path);
if (rc.IsFailure()) return rc;
return Result.Success;
return CleanDirectoryRecursivelyImpl(in path).Ret();
}
protected override Result DoRenameFile(in Path currentPath, in Path newPath)
{
if (IsConcatenationFile(in currentPath))
{
return _baseFileSystem.RenameDirectory(in currentPath, in newPath);
}
return _baseFileSystem.Get.RenameDirectory(in currentPath, in newPath).Ret();
return _baseFileSystem.RenameFile(in currentPath, in newPath);
return _baseFileSystem.Get.RenameFile(in currentPath, in newPath).Ret();
}
protected override Result DoRenameDirectory(in Path currentPath, in Path newPath)
@ -905,7 +905,7 @@ public class ConcatenationFileSystem : IFileSystem
if (IsConcatenationFile(in currentPath))
return ResultFs.PathNotFound.Log();
return _baseFileSystem.RenameDirectory(in currentPath, in newPath);
return _baseFileSystem.Get.RenameDirectory(in currentPath, in newPath).Ret();
}
public Result GetFileSize(out long size, in Path path)
@ -914,31 +914,34 @@ public class ConcatenationFileSystem : IFileSystem
using var internalFilePath = new Path();
Result rc = internalFilePath.Initialize(in path);
if (rc.IsFailure()) return rc;
if (rc.IsFailure()) return rc.Miss();
long sizeTotal = 0;
for (int i = 0; ; i++)
{
rc = AppendInternalFilePath(ref internalFilePath.Ref(), i);
if (rc.IsFailure()) return rc;
if (rc.IsFailure()) return rc.Miss();
rc = _baseFileSystem.GetFileSize(out long internalFileSize, in internalFilePath);
rc = _baseFileSystem.Get.GetFileSize(out long internalFileSize, in internalFilePath);
if (rc.IsFailure())
{
// We've passed the last internal file of the concatenation file
// once the next internal file doesn't exist.
if (ResultFs.PathNotFound.Includes(rc))
{
rc.Catch();
size = sizeTotal;
rc.Handle();
return Result.Success;
}
return rc;
return rc.Miss();
}
rc = internalFilePath.RemoveChild();
if (rc.IsFailure()) return rc;
if (rc.IsFailure()) return rc.Miss();
sizeTotal += internalFileSize;
}
@ -950,16 +953,16 @@ public class ConcatenationFileSystem : IFileSystem
if (queryId != QueryId.SetConcatenationFileAttribute)
return ResultFs.UnsupportedQueryEntryForConcatenationFileSystem.Log();
return _baseFileSystem.SetFileAttributes(in path, NxFileAttributes.Archive);
return _baseFileSystem.Get.SetFileAttributes(in path, NxFileAttributes.Archive).Ret();
}
protected override Result DoCommit()
{
return _baseFileSystem.Commit();
return _baseFileSystem.Get.Commit().Ret();
}
protected override Result DoCommitProvisionally(long counter)
{
return _baseFileSystem.CommitProvisionally(counter);
return _baseFileSystem.Get.CommitProvisionally(counter).Ret();
}
}
}

View File

@ -132,6 +132,11 @@ public readonly struct Result : IEquatable<Result>
return this;
}
public Result Ret()
{
return this;
}
public bool TryGetResultName(out string name)
{
IResultNameResolver resolver = NameResolver;
@ -419,4 +424,4 @@ public readonly struct Result : IEquatable<Result>
{
public bool TryResolveName(Result result, out string name);
}
}
}

View File

@ -42,9 +42,9 @@ public class SwitchFs : IDisposable
CreateApplications();
}
public static SwitchFs OpenSdCard(KeySet keySet, IAttributeFileSystem fileSystem)
public static SwitchFs OpenSdCard(KeySet keySet, ref UniqueRef<IAttributeFileSystem> fileSystem)
{
var concatFs = new ConcatenationFileSystem(fileSystem);
var concatFs = new ConcatenationFileSystem(ref fileSystem);
using var contentDirPath = new LibHac.Fs.Path();
PathFunctions.SetUpFixedPath(ref contentDirPath.Ref(), "/Nintendo/Contents".ToU8String()).ThrowIfFailure();
@ -56,7 +56,7 @@ public class SwitchFs : IDisposable
contentDirFs.Initialize(in contentDirPath).ThrowIfFailure();
AesXtsFileSystem encSaveFs = null;
if (fileSystem.DirectoryExists("/Nintendo/save"))
if (concatFs.DirectoryExists("/Nintendo/save"))
{
var saveDirFs = new SubdirectoryFileSystem(concatFs);
saveDirFs.Initialize(in saveDirPath).ThrowIfFailure();
@ -69,9 +69,9 @@ public class SwitchFs : IDisposable
return new SwitchFs(keySet, encContentFs, encSaveFs);
}
public static SwitchFs OpenNandPartition(KeySet keySet, IAttributeFileSystem fileSystem)
public static SwitchFs OpenNandPartition(KeySet keySet, ref UniqueRef<IAttributeFileSystem> fileSystem)
{
var concatFs = new ConcatenationFileSystem(fileSystem);
var concatFs = new ConcatenationFileSystem(ref fileSystem);
SubdirectoryFileSystem saveDirFs = null;
SubdirectoryFileSystem contentDirFs;

View File

@ -21,26 +21,26 @@ internal static class ProcessSwitchFs
public static void Process(Context ctx)
{
SwitchFs switchFs;
var baseFs = new LocalFileSystem(ctx.Options.InFile);
using var baseFs = new UniqueRef<IAttributeFileSystem>(new LocalFileSystem(ctx.Options.InFile));
if (Directory.Exists(Path.Combine(ctx.Options.InFile, "Nintendo", "Contents", "registered")))
{
ctx.Logger.LogMessage("Treating path as SD card storage");
switchFs = SwitchFs.OpenSdCard(ctx.KeySet, baseFs);
switchFs = SwitchFs.OpenSdCard(ctx.KeySet, ref baseFs.Ref());
CheckForNcaFolders(ctx, switchFs);
}
else if (Directory.Exists(Path.Combine(ctx.Options.InFile, "Contents", "registered")))
{
ctx.Logger.LogMessage("Treating path as NAND storage");
switchFs = SwitchFs.OpenNandPartition(ctx.KeySet, baseFs);
switchFs = SwitchFs.OpenNandPartition(ctx.KeySet, ref baseFs.Ref());
CheckForNcaFolders(ctx, switchFs);
}
else
{
ctx.Logger.LogMessage("Treating path as a directory of loose NCAs");
switchFs = SwitchFs.OpenNcaDirectory(ctx.KeySet, baseFs);
switchFs = SwitchFs.OpenNcaDirectory(ctx.KeySet, baseFs.Get);
}
if (ctx.Options.ListNcas)

View File

@ -19,4 +19,15 @@ public abstract partial class IFileSystemTests
Assert.Result(ResultFs.PathNotFound, rc);
}
}
[Fact]
public void OpenDirectory_PathDoesNotExist_ReturnsPathNotFound()
{
IFileSystem fs = CreateFileSystem();
using var directory = new UniqueRef<IDirectory>();
Result rc = fs.OpenDirectory(ref directory.Ref(), "/dir", OpenDirectoryMode.All);
Assert.Result(ResultFs.PathNotFound, rc);
}
}

View File

@ -19,4 +19,15 @@ public abstract partial class IFileSystemTests
Assert.Result(ResultFs.PathNotFound, rc);
}
}
[Fact]
public void OpenFile_PathDoesNotExist_ReturnsPathNotFound()
{
IFileSystem fs = CreateFileSystem();
using var file = new UniqueRef<IFile>();
Result rc = fs.OpenFile(ref file.Ref(), "/file", OpenMode.All);
Assert.Result(ResultFs.PathNotFound, rc);
}
}

View File

@ -1,6 +1,592 @@
namespace LibHac.Tests.FsSystem;
using System;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.FsSystem;
using LibHac.Tests.Fs;
using LibHac.Tests.Fs.IFileSystemTestBase;
using LibHac.Tools.Fs;
using Xunit;
public class ConcatenationFileSystemTests
namespace LibHac.Tests.FsSystem;
public class ConcatenationFileSystemTests : IFileSystemTests
{
//asdf
}
private const int InternalFileSize = 0x10000;
protected override IFileSystem CreateFileSystem()
{
return CreateFileSystemInternal().concatFs;
}
private (InMemoryFileSystem baseFs, ConcatenationFileSystem concatFs) CreateFileSystemInternal()
{
var baseFs = new InMemoryFileSystem();
using var uniqueBaseFs = new UniqueRef<IAttributeFileSystem>(baseFs);
var concatFs = new ConcatenationFileSystem(ref uniqueBaseFs.Ref(), InternalFileSize);
return (baseFs, concatFs);
}
[Fact]
public void OpenFile_OpenInternalFile_OpensSuccessfully()
{
IFileSystem fs = CreateFileSystem();
Assert.Success(fs.CreateFile("/file", InternalFileSize * 3, CreateFileOptions.CreateConcatenationFile));
using var file = new UniqueRef<IFile>();
Assert.Success(fs.OpenFile(ref file.Ref(), "/file/01", OpenMode.All));
}
[Fact]
public void OpenFile_ConcatFileWithNoInternalFiles_ReturnsConcatenationFsInvalidInternalFileCount()
{
(IAttributeFileSystem baseFs, IFileSystem concatFs) = CreateFileSystemInternal();
Assert.Success(concatFs.CreateFile("/file", InternalFileSize * 3, CreateFileOptions.CreateConcatenationFile));
Assert.Success(baseFs.DeleteFile("/file/00"));
using var file = new UniqueRef<IFile>();
Assert.Result(ResultFs.ConcatenationFsInvalidInternalFileCount, concatFs.OpenFile(ref file.Ref(), "/file", OpenMode.All));
}
[Fact]
public void OpenDirectory_OpenConcatFile_ReturnsPathNotFound()
{
IFileSystem fs = CreateFileSystem();
Assert.Success(fs.CreateFile("/file", InternalFileSize * 3, CreateFileOptions.CreateConcatenationFile));
using var dir = new UniqueRef<IDirectory>();
Assert.Result(ResultFs.PathNotFound, fs.OpenDirectory(ref dir.Ref(), "/file", OpenDirectoryMode.All));
}
[Fact]
public void CreateFile_ConcatenationFile_GetEntryTypeReturnsFile()
{
IFileSystem concatFs = CreateFileSystem();
Assert.Success(concatFs.CreateFile("/file", 0, CreateFileOptions.CreateConcatenationFile));
Assert.Success(concatFs.GetEntryType(out DirectoryEntryType type, "/file"));
Assert.Equal(DirectoryEntryType.File, type);
}
[Fact]
public void CreateFile_EmptyConcatenationFile_BaseDirHasCorrectStructure()
{
(IAttributeFileSystem baseFs, IFileSystem concatFs) = CreateFileSystemInternal();
Assert.Success(concatFs.CreateFile("/file", 0, CreateFileOptions.CreateConcatenationFile));
// Ensure the directory exists with the archive bit set
Assert.Success(baseFs.GetEntryType(out DirectoryEntryType dirType, "/file"));
Assert.Success(baseFs.GetFileAttributes(out NxFileAttributes dirAttributes, "/file"));
Assert.Equal(DirectoryEntryType.Directory, dirType);
Assert.Equal(NxFileAttributes.Directory | NxFileAttributes.Archive, dirAttributes);
// Ensure the internal files exist
Assert.Success(baseFs.GetEntryType(out DirectoryEntryType internalFile00Type, "/file/00"));
Assert.Equal(DirectoryEntryType.File, internalFile00Type);
// Ensure no additional internal files exist
Assert.Result(ResultFs.PathNotFound, baseFs.GetEntryType(out _, "/file/01"));
// Ensure the internal file sizes are correct
Assert.Success(baseFs.GetFileSize(out long internalFile00Size, "/file/00"));
Assert.Equal(0, internalFile00Size);
}
[Fact]
public void CreateFile_SizeIsMultipleOfInternalFile_BaseDirHasCorrectStructure()
{
(IAttributeFileSystem baseFs, IFileSystem concatFs) = CreateFileSystemInternal();
Assert.Success(concatFs.CreateFile("/file", InternalFileSize * 2, CreateFileOptions.CreateConcatenationFile));
// Ensure the directory exists with the archive bit set
Assert.Success(baseFs.GetEntryType(out DirectoryEntryType dirType, "/file"));
Assert.Success(baseFs.GetFileAttributes(out NxFileAttributes dirAttributes, "/file"));
Assert.Equal(DirectoryEntryType.Directory, dirType);
Assert.Equal(NxFileAttributes.Directory | NxFileAttributes.Archive, dirAttributes);
// Ensure no additional internal files exist
Assert.Result(ResultFs.PathNotFound, baseFs.GetEntryType(out _, "/file/02"));
// Ensure the internal file sizes are correct
Assert.Success(baseFs.GetFileSize(out long internalFile00Size, "/file/00"));
Assert.Success(baseFs.GetFileSize(out long internalFile01Size, "/file/01"));
Assert.Equal(InternalFileSize, internalFile00Size);
Assert.Equal(InternalFileSize, internalFile01Size);
}
[Fact]
public void CreateFile_NormalFileInsideConcatFile_CreatesSuccessfully()
{
IFileSystem fs = CreateFileSystem();
Assert.Success(fs.CreateFile("/file", 0, CreateFileOptions.CreateConcatenationFile));
Assert.Success(fs.CreateFile("/file/file", 0));
Assert.Success(fs.GetEntryType(out DirectoryEntryType type, "/file/file"));
Assert.Equal(DirectoryEntryType.File, type);
}
[Fact]
public void CreateFile_ConcatFileInsideConcatFile_ReturnsPathNotFound()
{
IFileSystem fs = CreateFileSystem();
Assert.Success(fs.CreateFile("/file", 0, CreateFileOptions.CreateConcatenationFile));
Assert.Result(ResultFs.PathNotFound, fs.CreateFile("/file/file", 0, CreateFileOptions.CreateConcatenationFile));
}
[Fact]
public void DeleteFile_DeleteConcatFile_DeletesSuccessfully()
{
(IAttributeFileSystem baseFs, IFileSystem concatFs) = CreateFileSystemInternal();
Assert.Success(concatFs.CreateFile("/file", 0, CreateFileOptions.CreateConcatenationFile));
Assert.Success(concatFs.DeleteFile("/file"));
Assert.Result(ResultFs.PathNotFound, baseFs.GetEntryType(out _, "/file"));
}
[Fact]
public void CreateDirectory_InsideConcatFile_ReturnsPathNotFound()
{
IFileSystem fs = CreateFileSystem();
Assert.Success(fs.CreateFile("/file", 0, CreateFileOptions.CreateConcatenationFile));
Assert.Result(ResultFs.PathNotFound, fs.CreateDirectory("/file/dir"));
}
[Fact]
public void DeleteDirectory_DeleteConcatFile_ReturnsPathNotFound()
{
IFileSystem fs = CreateFileSystem();
Assert.Success(fs.CreateFile("/file", 0, CreateFileOptions.CreateConcatenationFile));
Assert.Result(ResultFs.PathNotFound, fs.DeleteDirectory("/file"));
}
[Fact]
public void CleanDirectoryRecursively_CleanConcatFile_ReturnsPathNotFound()
{
IFileSystem fs = CreateFileSystem();
Assert.Success(fs.CreateFile("/file", 0, CreateFileOptions.CreateConcatenationFile));
Assert.Result(ResultFs.PathNotFound, fs.CleanDirectoryRecursively("/file"));
}
[Fact]
public void RenameFile_RenameConcatFile_RenamesSuccessfully()
{
(IAttributeFileSystem baseFs, IFileSystem concatFs) = CreateFileSystemInternal();
Assert.Success(concatFs.CreateFile("/file", 0, CreateFileOptions.CreateConcatenationFile));
Assert.Success(concatFs.RenameFile("/file", "/file2"));
Assert.Success(baseFs.GetEntryType(out DirectoryEntryType type, "/file2"));
Assert.Equal(DirectoryEntryType.Directory, type);
}
[Fact]
public void RenameDirectory_RenameConcatFile_ReturnsPathNotFound()
{
IFileSystem fs = CreateFileSystem();
Assert.Success(fs.CreateFile("/file", 0, CreateFileOptions.CreateConcatenationFile));
Assert.Result(ResultFs.PathNotFound, fs.RenameDirectory("/file", "/file2"));
}
[Fact]
public void Write_ConcatFileWithMultipleInternalFiles_CanReadBackWrittenData()
{
const long fileSize = InternalFileSize * 5 - 5;
byte[] data = new byte[fileSize];
new Random(1234).NextBytes(data);
IFileSystem fs = CreateFileSystem();
fs.CreateFile("/file", fileSize, CreateFileOptions.CreateConcatenationFile);
using var file = new UniqueRef<IFile>();
fs.OpenFile(ref file.Ref(), "/file", OpenMode.Write);
file.Get.Write(0, data, WriteOption.None);
file.Reset();
byte[] readData = new byte[data.Length];
fs.OpenFile(ref file.Ref(), "/file", OpenMode.Read);
Assert.Success(file.Get.Read(out long bytesRead, 0, readData, ReadOption.None));
Assert.Equal(data.Length, bytesRead);
Assert.Equal(data, readData);
}
[Fact]
public void SetSize_ResizeToHigherMultipleOfInternalFile_BaseDirHasCorrectStructure()
{
const long originalSize = InternalFileSize;
const long newSize = InternalFileSize * 2;
(IAttributeFileSystem baseFs, IFileSystem concatFs) = CreateFileSystemInternal();
using var file = new UniqueRef<IFile>();
// Create the file and then resize it
Assert.Success(concatFs.CreateFile("/file", originalSize, CreateFileOptions.CreateConcatenationFile));
Assert.Success(concatFs.OpenFile(ref file.Ref(), "/file", OpenMode.All));
Assert.Success(file.Get.SetSize(newSize));
Assert.Success(file.Get.GetSize(out long concatFileSize));
Assert.Equal(newSize, concatFileSize);
// Ensure the directory exists with the archive bit set
Assert.Success(baseFs.GetEntryType(out DirectoryEntryType dirType, "/file"));
Assert.Success(baseFs.GetFileAttributes(out NxFileAttributes dirAttributes, "/file"));
Assert.Equal(DirectoryEntryType.Directory, dirType);
Assert.Equal(NxFileAttributes.Directory | NxFileAttributes.Archive, dirAttributes);
// Ensure the internal files exist
Assert.Success(baseFs.GetEntryType(out DirectoryEntryType internalFile00Type, "/file/00"));
Assert.Success(baseFs.GetEntryType(out DirectoryEntryType internalFile01Type, "/file/01"));
Assert.Equal(DirectoryEntryType.File, internalFile00Type);
Assert.Equal(DirectoryEntryType.File, internalFile01Type);
// Ensure no additional internal files exist
Assert.Result(ResultFs.PathNotFound, baseFs.GetEntryType(out _, "/file/02"));
// Ensure the internal file sizes are correct
Assert.Success(baseFs.GetFileSize(out long internalFile00Size, "/file/00"));
Assert.Success(baseFs.GetFileSize(out long internalFile01Size, "/file/01"));
Assert.Equal(InternalFileSize, internalFile00Size);
Assert.Equal(InternalFileSize, internalFile01Size);
}
[Fact]
public void SetSize_ResizeToLowerMultipleOfInternalFile_BaseDirHasCorrectStructure()
{
const long originalSize = InternalFileSize * 5;
const long newSize = InternalFileSize * 2;
(IAttributeFileSystem baseFs, IFileSystem concatFs) = CreateFileSystemInternal();
using var file = new UniqueRef<IFile>();
// Create the file and then resize it
Assert.Success(concatFs.CreateFile("/file", originalSize, CreateFileOptions.CreateConcatenationFile));
Assert.Success(concatFs.OpenFile(ref file.Ref(), "/file", OpenMode.All));
Assert.Success(file.Get.SetSize(newSize));
Assert.Success(file.Get.GetSize(out long concatFileSize));
Assert.Equal(newSize, concatFileSize);
// Ensure the directory exists with the archive bit set
Assert.Success(baseFs.GetEntryType(out DirectoryEntryType dirType, "/file"));
Assert.Success(baseFs.GetFileAttributes(out NxFileAttributes dirAttributes, "/file"));
Assert.Equal(DirectoryEntryType.Directory, dirType);
Assert.Equal(NxFileAttributes.Directory | NxFileAttributes.Archive, dirAttributes);
// Ensure the internal files exist
Assert.Success(baseFs.GetEntryType(out DirectoryEntryType internalFile00Type, "/file/00"));
Assert.Success(baseFs.GetEntryType(out DirectoryEntryType internalFile01Type, "/file/01"));
Assert.Equal(DirectoryEntryType.File, internalFile00Type);
Assert.Equal(DirectoryEntryType.File, internalFile01Type);
// Ensure no additional internal files exist
Assert.Result(ResultFs.PathNotFound, baseFs.GetEntryType(out _, "/file/02"));
Assert.Result(ResultFs.PathNotFound, baseFs.GetEntryType(out _, "/file/03"));
Assert.Result(ResultFs.PathNotFound, baseFs.GetEntryType(out _, "/file/04"));
Assert.Result(ResultFs.PathNotFound, baseFs.GetEntryType(out _, "/file/05"));
// Ensure the internal file sizes are correct
Assert.Success(baseFs.GetFileSize(out long internalFile00Size, "/file/00"));
Assert.Success(baseFs.GetFileSize(out long internalFile01Size, "/file/01"));
Assert.Equal(InternalFileSize, internalFile00Size);
Assert.Equal(InternalFileSize, internalFile01Size);
}
[Fact]
public void SetSize_ResizeSmallerWithoutChangingInternalFileCount_BaseDirHasCorrectStructure()
{
const long originalSize = InternalFileSize * 2;
const long newSize = InternalFileSize * 2 - 5;
(IAttributeFileSystem baseFs, IFileSystem concatFs) = CreateFileSystemInternal();
using var file = new UniqueRef<IFile>();
// Create the file and then resize it
Assert.Success(concatFs.CreateFile("/file", originalSize, CreateFileOptions.CreateConcatenationFile));
Assert.Success(concatFs.OpenFile(ref file.Ref(), "/file", OpenMode.All));
Assert.Success(file.Get.SetSize(newSize));
Assert.Success(file.Get.GetSize(out long concatFileSize));
Assert.Equal(newSize, concatFileSize);
// Ensure the directory exists with the archive bit set
Assert.Success(baseFs.GetEntryType(out DirectoryEntryType dirType, "/file"));
Assert.Success(baseFs.GetFileAttributes(out NxFileAttributes dirAttributes, "/file"));
Assert.Equal(DirectoryEntryType.Directory, dirType);
Assert.Equal(NxFileAttributes.Directory | NxFileAttributes.Archive, dirAttributes);
// Ensure the internal files exist
Assert.Success(baseFs.GetEntryType(out DirectoryEntryType internalFile00Type, "/file/00"));
Assert.Success(baseFs.GetEntryType(out DirectoryEntryType internalFile01Type, "/file/01"));
Assert.Equal(DirectoryEntryType.File, internalFile00Type);
Assert.Equal(DirectoryEntryType.File, internalFile01Type);
// Ensure no additional internal files exist
Assert.Result(ResultFs.PathNotFound, baseFs.GetEntryType(out _, "/file/02"));
// Ensure the internal file sizes are correct
Assert.Success(baseFs.GetFileSize(out long internalFile00Size, "/file/00"));
Assert.Success(baseFs.GetFileSize(out long internalFile01Size, "/file/01"));
Assert.Equal(InternalFileSize, internalFile00Size);
Assert.Equal(InternalFileSize - 5, internalFile01Size);
}
[Fact]
public void SetSize_ResizeLargerWithoutChangingInternalFileCount_BaseDirHasCorrectStructure()
{
const long originalSize = InternalFileSize + 5;
const long newSize = InternalFileSize * 2 - 5;
(IAttributeFileSystem baseFs, IFileSystem concatFs) = CreateFileSystemInternal();
using var file = new UniqueRef<IFile>();
// Create the file and then resize it
Assert.Success(concatFs.CreateFile("/file", originalSize, CreateFileOptions.CreateConcatenationFile));
Assert.Success(concatFs.OpenFile(ref file.Ref(), "/file", OpenMode.All));
Assert.Success(file.Get.SetSize(newSize));
Assert.Success(file.Get.GetSize(out long concatFileSize));
Assert.Equal(newSize, concatFileSize);
// Ensure the directory exists with the archive bit set
Assert.Success(baseFs.GetEntryType(out DirectoryEntryType dirType, "/file"));
Assert.Success(baseFs.GetFileAttributes(out NxFileAttributes dirAttributes, "/file"));
Assert.Equal(DirectoryEntryType.Directory, dirType);
Assert.Equal(NxFileAttributes.Directory | NxFileAttributes.Archive, dirAttributes);
// Ensure the internal files exist
Assert.Success(baseFs.GetEntryType(out DirectoryEntryType internalFile00Type, "/file/00"));
Assert.Success(baseFs.GetEntryType(out DirectoryEntryType internalFile01Type, "/file/01"));
Assert.Equal(DirectoryEntryType.File, internalFile00Type);
Assert.Equal(DirectoryEntryType.File, internalFile01Type);
// Ensure no additional internal files exist
Assert.Result(ResultFs.PathNotFound, baseFs.GetEntryType(out _, "/file/02"));
// Ensure the internal file sizes are correct
Assert.Success(baseFs.GetFileSize(out long internalFile00Size, "/file/00"));
Assert.Success(baseFs.GetFileSize(out long internalFile01Size, "/file/01"));
Assert.Equal(InternalFileSize, internalFile00Size);
Assert.Equal(InternalFileSize - 5, internalFile01Size);
}
[Fact]
public void SetSize_ResizeSmallerChangingInternalFileCount_BaseDirHasCorrectStructure()
{
const long originalSize = InternalFileSize * 4 + 5;
const long newSize = InternalFileSize * 2 - 5;
(IAttributeFileSystem baseFs, IFileSystem concatFs) = CreateFileSystemInternal();
using var file = new UniqueRef<IFile>();
// Create the file and then resize it
Assert.Success(concatFs.CreateFile("/file", originalSize, CreateFileOptions.CreateConcatenationFile));
Assert.Success(concatFs.OpenFile(ref file.Ref(), "/file", OpenMode.All));
Assert.Success(file.Get.SetSize(newSize));
Assert.Success(file.Get.GetSize(out long concatFileSize));
Assert.Equal(newSize, concatFileSize);
// Ensure the directory exists with the archive bit set
Assert.Success(baseFs.GetEntryType(out DirectoryEntryType dirType, "/file"));
Assert.Success(baseFs.GetFileAttributes(out NxFileAttributes dirAttributes, "/file"));
Assert.Equal(DirectoryEntryType.Directory, dirType);
Assert.Equal(NxFileAttributes.Directory | NxFileAttributes.Archive, dirAttributes);
// Ensure the internal files exist
Assert.Success(baseFs.GetEntryType(out DirectoryEntryType internalFile00Type, "/file/00"));
Assert.Success(baseFs.GetEntryType(out DirectoryEntryType internalFile01Type, "/file/01"));
Assert.Equal(DirectoryEntryType.File, internalFile00Type);
Assert.Equal(DirectoryEntryType.File, internalFile01Type);
// Ensure no additional internal files exist
Assert.Result(ResultFs.PathNotFound, baseFs.GetEntryType(out _, "/file/02"));
Assert.Result(ResultFs.PathNotFound, baseFs.GetEntryType(out _, "/file/03"));
Assert.Result(ResultFs.PathNotFound, baseFs.GetEntryType(out _, "/file/04"));
// Ensure the internal file sizes are correct
Assert.Success(baseFs.GetFileSize(out long internalFile00Size, "/file/00"));
Assert.Success(baseFs.GetFileSize(out long internalFile01Size, "/file/01"));
Assert.Equal(InternalFileSize, internalFile00Size);
Assert.Equal(InternalFileSize - 5, internalFile01Size);
}
[Fact]
public void SetSize_ResizeLargerChangingInternalFileCount_BaseDirHasCorrectStructure()
{
const long originalSize = InternalFileSize - 5;
const long newSize = InternalFileSize * 2 - 5;
(IAttributeFileSystem baseFs, IFileSystem concatFs) = CreateFileSystemInternal();
using var file = new UniqueRef<IFile>();
// Create the file and then resize it
Assert.Success(concatFs.CreateFile("/file", originalSize, CreateFileOptions.CreateConcatenationFile));
Assert.Success(concatFs.OpenFile(ref file.Ref(), "/file", OpenMode.All));
Assert.Success(file.Get.SetSize(newSize));
Assert.Success(file.Get.GetSize(out long concatFileSize));
Assert.Equal(newSize, concatFileSize);
// Ensure the directory exists with the archive bit set
Assert.Success(baseFs.GetEntryType(out DirectoryEntryType dirType, "/file"));
Assert.Success(baseFs.GetFileAttributes(out NxFileAttributes dirAttributes, "/file"));
Assert.Equal(DirectoryEntryType.Directory, dirType);
Assert.Equal(NxFileAttributes.Directory | NxFileAttributes.Archive, dirAttributes);
// Ensure the internal files exist
Assert.Success(baseFs.GetEntryType(out DirectoryEntryType internalFile00Type, "/file/00"));
Assert.Success(baseFs.GetEntryType(out DirectoryEntryType internalFile01Type, "/file/01"));
Assert.Equal(DirectoryEntryType.File, internalFile00Type);
Assert.Equal(DirectoryEntryType.File, internalFile01Type);
// Ensure no additional internal files exist
Assert.Result(ResultFs.PathNotFound, baseFs.GetEntryType(out _, "/file/02"));
// Ensure the internal file sizes are correct
Assert.Success(baseFs.GetFileSize(out long internalFile00Size, "/file/00"));
Assert.Success(baseFs.GetFileSize(out long internalFile01Size, "/file/01"));
Assert.Equal(InternalFileSize, internalFile00Size);
Assert.Equal(InternalFileSize - 5, internalFile01Size);
}
[Fact]
public void SetSize_ResizeToEmpty_BaseDirHasCorrectStructure()
{
const long originalSize = InternalFileSize * 2 + 5;
const long newSize = 0;
(IAttributeFileSystem baseFs, IFileSystem concatFs) = CreateFileSystemInternal();
using var file = new UniqueRef<IFile>();
// Create the file and then resize it
Assert.Success(concatFs.CreateFile("/file", originalSize, CreateFileOptions.CreateConcatenationFile));
Assert.Success(concatFs.OpenFile(ref file.Ref(), "/file", OpenMode.All));
Assert.Success(file.Get.SetSize(newSize));
Assert.Success(file.Get.GetSize(out long concatFileSize));
Assert.Equal(newSize, concatFileSize);
// Ensure the directory exists with the archive bit set
Assert.Success(baseFs.GetEntryType(out DirectoryEntryType dirType, "/file"));
Assert.Success(baseFs.GetFileAttributes(out NxFileAttributes dirAttributes, "/file"));
Assert.Equal(DirectoryEntryType.Directory, dirType);
Assert.Equal(NxFileAttributes.Directory | NxFileAttributes.Archive, dirAttributes);
// Ensure the internal files exist
Assert.Success(baseFs.GetEntryType(out DirectoryEntryType internalFile00Type, "/file/00"));
Assert.Equal(DirectoryEntryType.File, internalFile00Type);
// Ensure no additional internal files exist
Assert.Result(ResultFs.PathNotFound, baseFs.GetEntryType(out _, "/file/01"));
Assert.Result(ResultFs.PathNotFound, baseFs.GetEntryType(out _, "/file/02"));
// Ensure the internal file sizes are correct
Assert.Success(baseFs.GetFileSize(out long internalFile00Size, "/file/00"));
Assert.Equal(0, internalFile00Size);
}
[Fact]
public void SetSize_ResizeToHigherMultipleOfInternalFile_FileContentsAreRetained()
{
EnsureContentsAreRetainedOnResize(InternalFileSize, InternalFileSize * 2);
}
[Fact]
public void SetSize_ResizeToLowerMultipleOfInternalFile_FileContentsAreRetained()
{
EnsureContentsAreRetainedOnResize(InternalFileSize * 5, InternalFileSize * 2);
}
[Fact]
public void SetSize__ResizeSmallerWithoutChangingInternalFileCount_FileContentsAreRetained()
{
EnsureContentsAreRetainedOnResize(InternalFileSize * 2, InternalFileSize * 2 - 5);
}
[Fact]
public void SetSize_ResizeLargerWithoutChangingInternalFileCount_FileContentsAreRetained()
{
EnsureContentsAreRetainedOnResize(InternalFileSize + 5, InternalFileSize * 2 - 5);
}
[Fact]
public void SetSize_ResizeSmallerChangingInternalFileCount_FileContentsAreRetained()
{
EnsureContentsAreRetainedOnResize(InternalFileSize * 4 + 5, InternalFileSize * 2 - 5);
}
[Fact]
public void SetSize_ResizeLargerChangingInternalFileCount_FileContentsAreRetained()
{
EnsureContentsAreRetainedOnResize(InternalFileSize - 5, InternalFileSize * 2 - 5);
}
private void EnsureContentsAreRetainedOnResize(int originalSize, int newSize)
{
const string fileName = "/file";
byte[] originalData = new byte[originalSize];
new Random(1234).NextBytes(originalData);
byte[] newData = new byte[newSize];
byte[] actualNewData = new byte[newSize];
originalData.AsSpan(0, Math.Min(originalSize, newSize)).CopyTo(newData);
IFileSystem fs = CreateFileSystem();
// Create the file and write the data to it
using (var file = new UniqueRef<IFile>())
{
// Create the file and then write the data to it
Assert.Success(fs.CreateFile(fileName, originalSize, CreateFileOptions.CreateConcatenationFile));
Assert.Success(fs.OpenFile(ref file.Ref(), fileName, OpenMode.Write));
Assert.Success(file.Get.Write(0, originalData, WriteOption.None));
}
// Resize the file
using (var file = new UniqueRef<IFile>())
{
Assert.Success(fs.OpenFile(ref file.Ref(), fileName, OpenMode.Write));
Assert.Success(file.Get.SetSize(newSize));
}
// Read back the entire resized file and ensure the contents are as expected
using (var file = new UniqueRef<IFile>())
{
Assert.Success(fs.OpenFile(ref file.Ref(), fileName, OpenMode.Read));
Assert.Success(file.Get.Read(out long bytesRead, 0, actualNewData));
Assert.Equal(newSize, bytesRead);
}
Assert.Equal(newData, actualNewData);
}
}