diff --git a/src/LibHac/FsSystem/ConcatenationFileSystem.cs b/src/LibHac/FsSystem/ConcatenationFileSystem.cs
index 2212404c..2ded58e4 100644
--- a/src/LibHac/FsSystem/ConcatenationFileSystem.cs
+++ b/src/LibHac/FsSystem/ConcatenationFileSystem.cs
@@ -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 that stores large files as smaller, separate sub-files.
///
///
-/// This filesystem is mainly used to allow storing large files on filesystems that have low
+/// 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.
-///
-/// A may contain both standard files or Concatenation files.
+/// support for the "Archive" file attribute found in FAT or NTFS filesystems.
+///
+/// A 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 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 .
-///
Based on FS 12.1.0 (nnSdk 12.3.1)
+///
+/// Based on FS 12.1.0 (nnSdk 12.3.1)
///
public class ConcatenationFileSystem : IFileSystem
{
private class ConcatenationFile : IFile
{
private OpenMode _mode;
- private List _files;
+ private List _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 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 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();
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 outBuffer, OperationId operationId, long offset, long size,
ReadOnlySpan 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())
+ 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())
- 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(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 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 _baseFileSystem;
+ private long _internalFileSize;
///
/// Initializes a new with an internal file size of .
///
/// The base for the
/// new .
- public ConcatenationFileSystem(IAttributeFileSystem baseFileSystem) : this(baseFileSystem, DefaultInternalFileSize) { }
+ public ConcatenationFileSystem(ref UniqueRef baseFileSystem) : this(ref baseFileSystem,
+ DefaultInternalFileSize)
+ { }
///
/// Initializes a new .
@@ -508,41 +512,41 @@ public class ConcatenationFileSystem : IFileSystem
/// The base for the
/// new .
/// The size of each internal file. Once a file exceeds this size, a new internal file will be created
- public ConcatenationFileSystem(IAttributeFileSystem baseFileSystem, long internalFileSize)
+ public ConcatenationFileSystem(ref UniqueRef baseFileSystem, long internalFileSize)
{
- _baseFileSystem = baseFileSystem;
- _InternalFileSize = internalFileSize;
+ _baseFileSystem = new UniqueRef(ref baseFileSystem);
+ _internalFileSize = internalFileSize;
}
public override void Dispose()
{
- _baseFileSystem?.Dispose();
- _baseFileSystem = null;
+ _baseFileSystem.Destroy();
base.Dispose();
}
private static ReadOnlySpan RootPath => new[] { (byte)'/' };
+ ///
+ /// Appends the two-digit-padded to the given .
+ ///
+ /// The to be modified.
+ /// The index to append to the .
+ /// : The operation was successful.
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();
+ 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 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(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();
- 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(
- 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();
- 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(
- 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();
}
-}
+}
\ No newline at end of file
diff --git a/src/LibHac/Result.cs b/src/LibHac/Result.cs
index fc12ec98..c096ed4b 100644
--- a/src/LibHac/Result.cs
+++ b/src/LibHac/Result.cs
@@ -132,6 +132,11 @@ public readonly struct Result : IEquatable
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
{
public bool TryResolveName(Result result, out string name);
}
-}
+}
\ No newline at end of file
diff --git a/src/LibHac/Tools/Fs/SwitchFs.cs b/src/LibHac/Tools/Fs/SwitchFs.cs
index 3713029d..0eaf2d74 100644
--- a/src/LibHac/Tools/Fs/SwitchFs.cs
+++ b/src/LibHac/Tools/Fs/SwitchFs.cs
@@ -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 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 fileSystem)
{
- var concatFs = new ConcatenationFileSystem(fileSystem);
+ var concatFs = new ConcatenationFileSystem(ref fileSystem);
SubdirectoryFileSystem saveDirFs = null;
SubdirectoryFileSystem contentDirFs;
diff --git a/src/hactoolnet/ProcessSwitchFs.cs b/src/hactoolnet/ProcessSwitchFs.cs
index 2eb5a186..5ffe7a51 100644
--- a/src/hactoolnet/ProcessSwitchFs.cs
+++ b/src/hactoolnet/ProcessSwitchFs.cs
@@ -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(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)
diff --git a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.OpenDirectory.cs b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.OpenDirectory.cs
index 8d2aff1c..e6147867 100644
--- a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.OpenDirectory.cs
+++ b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.OpenDirectory.cs
@@ -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();
+ Result rc = fs.OpenDirectory(ref directory.Ref(), "/dir", OpenDirectoryMode.All);
+
+ Assert.Result(ResultFs.PathNotFound, rc);
+ }
+}
\ No newline at end of file
diff --git a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.OpenFile.cs b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.OpenFile.cs
index 35f5f2c5..a50e1d71 100644
--- a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.OpenFile.cs
+++ b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.OpenFile.cs
@@ -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();
+ Result rc = fs.OpenFile(ref file.Ref(), "/file", OpenMode.All);
+
+ Assert.Result(ResultFs.PathNotFound, rc);
+ }
+}
\ No newline at end of file
diff --git a/tests/LibHac.Tests/FsSystem/ConcatenationFileSystemTests.cs b/tests/LibHac.Tests/FsSystem/ConcatenationFileSystemTests.cs
index 0222ef6d..9a4ce758 100644
--- a/tests/LibHac.Tests/FsSystem/ConcatenationFileSystemTests.cs
+++ b/tests/LibHac.Tests/FsSystem/ConcatenationFileSystemTests.cs
@@ -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(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();
+ 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();
+ 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();
+ 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();
+ 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();
+
+ // 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();
+
+ // 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();
+
+ // 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();
+
+ // 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();
+
+ // 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();
+
+ // 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();
+
+ // 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())
+ {
+ // 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())
+ {
+ 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())
+ {
+ 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);
+ }
+}
\ No newline at end of file