Update the FsSystem namespace to use Fs.Path

This commit is contained in:
Alex Barney 2021-08-05 11:59:05 -07:00
parent 6ba10074a3
commit aad87ec845
38 changed files with 814 additions and 960 deletions

View File

@ -88,6 +88,7 @@ namespace LibHac.Fs.Common
{
_replacedChar = _buffer[1];
_buffer[1] = 0;
_position = 1;
return _buffer;
}

View File

@ -538,12 +538,12 @@ namespace LibHac.Fs
int parentBytesCopied = StringUtils.Copy(destBuffer, parent, parentLength + SeparatorLength);
// Make sure we copied the expected number of parent bytes.
if (parentHasTrailingSlash)
if (!parentHasTrailingSlash)
{
if (parentBytesCopied != parentLength + SeparatorLength)
if (parentBytesCopied != parentLength)
return ResultFs.UnexpectedInPathA.Log();
}
else if (parentBytesCopied != parentLength)
else if (parentBytesCopied != parentLength + SeparatorLength)
{
return ResultFs.UnexpectedInPathA.Log();
}
@ -602,7 +602,7 @@ namespace LibHac.Fs
if (_string[parentLength - 1] == DirectorySeparator || _string[parentLength - 1] == AltDirectorySeparator)
parentLength--;
int childLength = StringUtils.GetLength(child);
int childLength = StringUtils.GetLength(trimmedChild);
byte[] parentBuffer = null;
try
@ -630,15 +630,14 @@ namespace LibHac.Fs
if (childBytesCopied != childLength)
return ResultFs.UnexpectedInPathA.Log();
return Result.Success;
}
finally
{
if (parentBuffer is not null)
ArrayPool<byte>.Shared.Return(parentBuffer);
}
_isNormalized = false;
return Result.Success;
}
public Result AppendChild(in Path child)
@ -737,7 +736,6 @@ namespace LibHac.Fs
if (currentPos <= 0)
return ResultFs.NotImplemented.Log();
_isNormalized = false;
return Result.Success;
}
@ -809,7 +807,7 @@ namespace LibHac.Fs
}
// Only a small number of format strings are used with these functions, so we can hard code them all easily.
// /%s
internal static Result SetUpFixedPathSingleEntry(ref Path path, Span<byte> pathBuffer,
ReadOnlySpan<byte> entryName)

View File

@ -44,12 +44,6 @@ namespace LibHac.Fs.Fsa
protected abstract Result DoRead(out long entriesRead, Span<DirectoryEntry> entryBuffer);
protected abstract Result DoGetEntryCount(out long entryCount);
protected virtual void Dispose(bool disposing) { }
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public virtual void Dispose() { }
}
}

View File

@ -219,12 +219,6 @@ namespace LibHac.Fs.Fsa
protected abstract Result DoOperateRange(Span<byte> outBuffer, OperationId operationId, long offset, long size,
ReadOnlySpan<byte> inBuffer);
protected virtual void Dispose(bool disposing) { }
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public virtual void Dispose() { }
}
}

View File

@ -31,14 +31,11 @@ namespace LibHac.Fs.Impl
BaseFile = baseFile.AddReference();
}
protected override void Dispose(bool disposing)
public override void Dispose()
{
if (disposing)
{
BaseFile?.Dispose();
}
BaseFile?.Dispose();
base.Dispose(disposing);
base.Dispose();
}
protected override Result DoRead(out long bytesRead, long offset, Span<byte> destination, in ReadOption option)
@ -101,14 +98,11 @@ namespace LibHac.Fs.Impl
BaseDirectory = baseDirectory.AddReference();
}
protected override void Dispose(bool disposing)
public override void Dispose()
{
if (disposing)
{
BaseDirectory?.Dispose();
}
BaseDirectory?.Dispose();
base.Dispose(disposing);
base.Dispose();
}
protected override Result DoRead(out long entriesRead, Span<DirectoryEntry> entryBuffer)

View File

@ -7,7 +7,7 @@ using LibHac.FsSystem;
using LibHac.Sf;
using IFileSystem = LibHac.Fs.Fsa.IFileSystem;
using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem;
using PathNormalizer = LibHac.FsSrv.Impl.PathNormalizer;
using Path = LibHac.Fs.Path;
namespace LibHac.FsSrv
{
@ -117,7 +117,7 @@ namespace LibHac.FsSrv
using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag);
// Normalize the path
var pathNormalized = new Fs.Path();
var pathNormalized = new Path();
rc = pathNormalized.Initialize(rootPath.Str);
if (rc.IsFailure()) return rc;
@ -135,7 +135,7 @@ namespace LibHac.FsSrv
rc = _serviceImpl.OpenBisFileSystem(out baseFileSystem, partitionId, false);
if (rc.IsFailure()) return rc;
rc = Impl.Utility.CreateSubDirectoryFileSystem(out fileSystem, ref baseFileSystem, in pathNormalized);
rc = Utility.CreateSubDirectoryFileSystem(out fileSystem, ref baseFileSystem, in pathNormalized);
if (rc.IsFailure()) return rc;
// Add all the file system wrappers

View File

@ -1,5 +1,4 @@
using System;
using LibHac.Common;
using LibHac.Fs;
using LibHac.FsSrv.FsCreator;
using LibHac.FsSrv.Impl;

View File

@ -77,6 +77,11 @@ namespace LibHac.FsSrv.FsCreator
Result rc = bisRootPath.Initialize(GetPartitionPath(partitionId).ToU8String());
if (rc.IsFailure()) return rc;
var pathFlags = new PathFlags();
pathFlags.AllowEmptyPath();
rc = bisRootPath.Normalize(pathFlags);
if (rc.IsFailure()) return rc;
ReferenceCountedDisposable<IFileSystem> partitionFileSystem = null;
ReferenceCountedDisposable<IFileSystem> sharedRootFs = null;
try

View File

@ -83,7 +83,7 @@ namespace LibHac.FsSrv.FsCreator
try
{
tempFs = _rootFileSystem.AddReference();
rc = Utility.CreateSubDirectoryFileSystem(out _sdCardFileSystem, ref tempFs, in sdCardPath);
rc = Utility.WrapSubDirectory(out _sdCardFileSystem, ref tempFs, in sdCardPath, true);
if (rc.IsFailure()) return rc;
outFileSystem = _sdCardFileSystem.AddReference();

View File

@ -1,13 +1,10 @@
using System;
using LibHac.Fs;
using LibHac.Fs;
using LibHac.Fs.Fsa;
namespace LibHac.FsSrv.FsCreator
{
public interface ITargetManagerFileSystemCreator
{
// Todo: Remove raw IFilesystem function
Result Create(out IFileSystem fileSystem, bool openCaseSensitive);
Result Create(out ReferenceCountedDisposable<IFileSystem> fileSystem, in Path rootPath, bool openCaseSensitive, bool ensureRootPathExists, Result pathNotFoundResult);
Result NormalizeCaseOfPath(out bool isSupported, ref Path path);
}

View File

@ -6,11 +6,6 @@ namespace LibHac.FsSrv.FsCreator
{
public class TargetManagerFileSystemCreator : ITargetManagerFileSystemCreator
{
public Result Create(out IFileSystem fileSystem, bool openCaseSensitive)
{
throw new NotImplementedException();
}
public Result Create(out ReferenceCountedDisposable<IFileSystem> fileSystem, in Path rootPath, bool openCaseSensitive, bool ensureRootPathExists, Result pathNotFoundResult)
{
throw new NotImplementedException();

View File

@ -15,15 +15,12 @@ namespace LibHac.FsSrv.Impl
BaseFile = baseFile;
}
protected override void Dispose(bool disposing)
public override void Dispose()
{
if (disposing)
{
BaseFile?.Dispose();
BaseFile = null;
}
BaseFile?.Dispose();
BaseFile = null;
base.Dispose(disposing);
base.Dispose();
}
protected override Result DoRead(out long bytesRead, long offset, Span<byte> destination, in ReadOption option)
@ -70,15 +67,12 @@ namespace LibHac.FsSrv.Impl
BaseDirectory = baseDirectory;
}
protected override void Dispose(bool disposing)
public override void Dispose()
{
if (disposing)
{
BaseDirectory?.Dispose();
BaseDirectory = null;
}
BaseDirectory?.Dispose();
BaseDirectory = null;
base.Dispose(disposing);
base.Dispose();
}
protected override Result DoRead(out long entriesRead, Span<DirectoryEntry> entryBuffer)

View File

@ -8,13 +8,11 @@ using LibHac.FsSystem;
using LibHac.Lr;
using LibHac.Ncm;
using LibHac.Spl;
using LibHac.Util;
using IFileSystem = LibHac.Fs.Fsa.IFileSystem;
using IStorage = LibHac.Fs.IStorage;
using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem;
using IStorageSf = LibHac.FsSrv.Sf.IStorage;
using Path = LibHac.Fs.Path;
using PathNormalizer = LibHac.FsSrv.Impl.PathNormalizer;
using Utility = LibHac.FsSrv.Impl.Utility;
namespace LibHac.FsSrv
@ -96,7 +94,7 @@ namespace LibHac.FsSrv
if (rc.IsFailure()) return rc;
// Try to find the path to the original version of the file system
var originalPath = new Fs.Path();
var originalPath = new Path();
Result originalResult = ServiceImpl.ResolveApplicationHtmlDocumentPath(out bool isDirectory,
ref originalPath, new Ncm.ApplicationId(programId.Value), ownerProgramInfo.StorageId);
@ -105,7 +103,7 @@ namespace LibHac.FsSrv
return originalResult;
// Try to find the path to the patch file system
var patchPath = new Fs.Path();
var patchPath = new Path();
Result patchResult = ServiceImpl.ResolveRegisteredHtmlDocumentPath(ref patchPath, programId.Value);
ReferenceCountedDisposable<IFileSystem> tempFileSystem = null;
@ -128,11 +126,11 @@ namespace LibHac.FsSrv
if (patchResult.IsFailure())
return patchResult;
var emptyPath = new Fs.Path();
var emptyPath = new Path();
rc = emptyPath.InitializeAsEmpty();
if (rc.IsFailure()) return rc;
ref Fs.Path originalNcaPath = ref originalResult.IsSuccess() ? ref originalPath : ref emptyPath;
ref Path originalNcaPath = ref originalResult.IsSuccess() ? ref originalPath : ref emptyPath;
// Open the file system using both the original and patch versions
rc = ServiceImpl.OpenFileSystemWithPatch(out tempFileSystem, in originalNcaPath, in patchPath,
@ -235,7 +233,7 @@ namespace LibHac.FsSrv
bool canMountSystemDataPrivate = ac.GetAccessibilityFor(AccessibilityType.MountSystemDataPrivate).CanRead;
var pathNormalized = new Fs.Path();
var pathNormalized = new Path();
rc = pathNormalized.InitializeWithReplaceUnc(path.Str);
if (rc.IsFailure()) return rc;

View File

@ -17,7 +17,6 @@ using IFile = LibHac.Fs.Fsa.IFile;
using IFileSf = LibHac.FsSrv.Sf.IFile;
using Path = LibHac.Fs.Path;
using SaveData = LibHac.Fs.SaveData;
using Utility = LibHac.FsSystem.Utility;
using static LibHac.Fs.StringTraits;
namespace LibHac.FsSrv
@ -1801,7 +1800,7 @@ namespace LibHac.FsSrv
saveDataId);
if (rc.IsFailure()) return rc;
commitId = Impl.Utility.ConvertZeroCommitId(in extraData);
commitId = Utility.ConvertZeroCommitId(in extraData);
return Result.Success;
}
@ -2228,7 +2227,7 @@ namespace LibHac.FsSrv
{
saveService = _selfReference.AddReference();
Result rc = Utility.MakeUniqueLockWithPin(out uniqueLock, _openEntryCountSemaphore,
Result rc = Utility12.MakeUniqueLockWithPin(out uniqueLock, _openEntryCountSemaphore,
ref saveService);
if (rc.IsFailure()) return rc;
@ -2252,7 +2251,7 @@ namespace LibHac.FsSrv
{
saveService = _selfReference.AddReference();
Result rc = Utility.MakeUniqueLockWithPin(out uniqueLock, _saveDataMountCountSemaphore,
Result rc = Utility12.MakeUniqueLockWithPin(out uniqueLock, _saveDataMountCountSemaphore,
ref saveService);
if (rc.IsFailure()) return rc;

View File

@ -218,7 +218,7 @@ namespace LibHac.FsSrv
UnsafeHelpers.SkipParamInit(out fileSystem);
// Hack around error CS8350.
const int bufferLength = 0xF;
const int bufferLength = 0x1B;
Span<byte> buffer = stackalloc byte[bufferLength];
ref byte bufferRef = ref MemoryMarshal.GetReference(buffer);
Span<byte> saveDataMetaIdDirectoryNameBuffer = MemoryMarshal.CreateSpan(ref bufferRef, bufferLength);

View File

@ -122,14 +122,13 @@ namespace LibHac.FsSystem
return BaseStorage.SetSize(Alignment.AlignUp(size, 0x10));
}
protected override void Dispose(bool disposing)
public override void Dispose()
{
if (disposing)
{
BaseStorage.Flush();
BaseStorage.Dispose();
BaseFile?.Dispose();
}
BaseStorage.Flush();
BaseStorage.Dispose();
BaseFile?.Dispose();
base.Dispose();
}
}
}

View File

@ -57,16 +57,16 @@ namespace LibHac.FsSystem
/// <param name="options">Flags to control how the file is created.
/// Should usually be <see cref="CreateFileOptions.None"/></param>
/// <param name="key">The 256-bit key containing a 128-bit data key followed by a 128-bit tweak key.</param>
public Result CreateFile(U8Span path, long size, CreateFileOptions options, byte[] key)
public Result CreateFile(in Path path, long size, CreateFileOptions options, byte[] key)
{
long containerSize = AesXtsFile.HeaderLength + Alignment.AlignUp(size, 0x10);
Result rc = BaseFileSystem.CreateFile(path, containerSize, options);
Result rc = BaseFileSystem.CreateFile(in path, containerSize, options);
if (rc.IsFailure()) return rc;
var header = new AesXtsFileHeader(key, size, path.ToString(), KekSource, ValidationKey);
rc = BaseFileSystem.OpenFile(out IFile baseFile, path, OpenMode.Write);
rc = BaseFileSystem.OpenFile(out IFile baseFile, in path, OpenMode.Write);
if (rc.IsFailure()) return rc;
using (baseFile)
@ -105,7 +105,7 @@ namespace LibHac.FsSystem
Result rc = BaseFileSystem.OpenDirectory(out IDirectory baseDir, path, mode);
if (rc.IsFailure()) return rc;
directory = new AesXtsDirectory(BaseFileSystem, baseDir, path.ToU8String(), mode);
directory = new AesXtsDirectory(BaseFileSystem, baseDir, new U8String(path.GetString().ToArray()), mode);
return Result.Success;
}
@ -116,7 +116,8 @@ namespace LibHac.FsSystem
Result rc = BaseFileSystem.OpenFile(out IFile baseFile, path, mode | OpenMode.Read);
if (rc.IsFailure()) return rc;
var xtsFile = new AesXtsFile(mode, baseFile, path.ToU8String(), KekSource, ValidationKey, BlockSize);
var xtsFile = new AesXtsFile(mode, baseFile, new U8String(path.GetString().ToArray()), KekSource,
ValidationKey, BlockSize);
file = xtsFile;
return Result.Success;

View File

@ -46,19 +46,18 @@ namespace LibHac.FsSystem
_path = new Path.Stored();
}
protected override void Dispose(bool disposing)
public override void Dispose()
{
_path.Dispose();
if (disposing)
foreach (IFile file in _files)
{
foreach (IFile file in _files)
{
file?.Dispose();
}
_files.Clear();
file?.Dispose();
}
_files.Clear();
base.Dispose();
}
public Result Initialize(in Path path)
@ -391,15 +390,12 @@ namespace LibHac.FsSystem
_concatenationFileSystem = concatFileSystem;
}
protected override void Dispose(bool disposing)
public override void Dispose()
{
if (disposing)
{
_path.Dispose();
_baseDirectory.Dispose();
}
_path.Dispose();
_baseDirectory.Dispose();
base.Dispose(disposing);
base.Dispose();
}
public Result Initialize(in Path path)

File diff suppressed because it is too large Load Diff

View File

@ -1,86 +0,0 @@
using System;
using System.Runtime.CompilerServices;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.Util;
namespace LibHac.FsSystem
{
public static class DirectoryUtils
{
public delegate Result Blah(ReadOnlySpan<byte> path, ref DirectoryEntry entry);
public static Result IterateDirectoryRecursivelyInternal(IFileSystem fs, Span<byte> workPath,
ref DirectoryEntry entry, Blah onEnterDir, Blah onExitDir, Blah onFile)
{
Result rc = fs.OpenDirectory(out IDirectory _, new U8Span(workPath), OpenDirectoryMode.All);
if (rc.IsFailure()) return rc;
onFile(workPath, ref entry);
return Result.Success;
}
public static Result IterateDirectoryRecursively(IFileSystem fs, ReadOnlySpan<byte> path, Blah onEnterDir, Blah onExitDir, Blah onFile)
{
return Result.Success;
}
public static Result CopyDirectoryRecursively(IFileSystem sourceFs, IFileSystem destFs, string sourcePath,
string destPath)
{
return Result.Success;
}
public static Result CopyFile(IFileSystem destFs, IFileSystem sourceFs, ReadOnlySpan<byte> destParentPath,
ReadOnlySpan<byte> sourcePath, ref DirectoryEntry dirEntry, Span<byte> copyBuffer)
{
IFile srcFile = null;
IFile dstFile = null;
try
{
Result rc = sourceFs.OpenFile(out srcFile, new U8Span(sourcePath), OpenMode.Read);
if (rc.IsFailure()) return rc;
Unsafe.SkipInit(out FsPath dstPath);
dstPath.Str[0] = 0;
int dstPathLen = StringUtils.Concat(dstPath.Str, destParentPath);
dstPathLen = StringUtils.Concat(dstPath.Str, dirEntry.Name, dstPathLen);
if (dstPathLen > FsPath.MaxLength)
{
throw new ArgumentException();
}
rc = destFs.CreateFile(dstPath, dirEntry.Size, CreateFileOptions.None);
if (rc.IsFailure()) return rc;
rc = destFs.OpenFile(out dstFile, dstPath, OpenMode.Write);
if (rc.IsFailure()) return rc;
long fileSize = dirEntry.Size;
long offset = 0;
while (offset < fileSize)
{
rc = srcFile.Read(out long bytesRead, offset, copyBuffer, ReadOption.None);
if (rc.IsFailure()) return rc;
rc = dstFile.Write(offset, copyBuffer.Slice(0, (int)bytesRead), WriteOption.None);
if (rc.IsFailure()) return rc;
offset += bytesRead;
}
return Result.Success;
}
finally
{
srcFile?.Dispose();
dstFile?.Dispose();
}
}
}
}

View File

@ -7,6 +7,7 @@ using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.Util;
using Path = LibHac.Fs.Path;
namespace LibHac.FsSystem
{
@ -15,38 +16,119 @@ namespace LibHac.FsSystem
public static Result CopyDirectory(this IFileSystem sourceFs, IFileSystem destFs, string sourcePath, string destPath,
IProgressReport logger = null, CreateFileOptions options = CreateFileOptions.None)
{
Result rc;
const int bufferSize = 0x100000;
foreach (DirectoryEntryEx entry in sourceFs.EnumerateEntries(sourcePath, "*", SearchOptions.Default))
var directoryEntryBuffer = new DirectoryEntry();
var sourcePathNormalized = new Path();
Result rc = InitializeFromString(ref sourcePathNormalized, sourcePath);
if (rc.IsFailure()) return rc;
var destPathNormalized = new Path();
rc = InitializeFromString(ref destPathNormalized, destPath);
if (rc.IsFailure()) return rc;
byte[] workBuffer = ArrayPool<byte>.Shared.Rent(bufferSize);
try
{
string subSrcPath = PathTools.Normalize(PathTools.Combine(sourcePath, entry.Name));
string subDstPath = PathTools.Normalize(PathTools.Combine(destPath, entry.Name));
return CopyDirectoryRecursively(destFs, sourceFs, in destPathNormalized, in sourcePathNormalized,
ref directoryEntryBuffer, workBuffer, logger, options);
}
finally
{
ArrayPool<byte>.Shared.Return(workBuffer);
logger?.SetTotal(0);
}
}
if (entry.Type == DirectoryEntryType.Directory)
public static Result CopyDirectoryRecursively(IFileSystem destinationFileSystem, IFileSystem sourceFileSystem,
in Path destinationPath, in Path sourcePath, ref DirectoryEntry dirEntry, Span<byte> workBuffer,
IProgressReport logger = null, CreateFileOptions option = CreateFileOptions.None)
{
static Result OnEnterDir(in Path path, in DirectoryEntry entry,
ref Utility12.FsIterationTaskClosure closure)
{
Result rc = closure.DestinationPathBuffer.AppendChild(entry.Name);
if (rc.IsFailure()) return rc;
return closure.SourceFileSystem.CreateDirectory(in closure.DestinationPathBuffer);
}
static Result OnExitDir(in Path path, in DirectoryEntry entry, ref Utility12.FsIterationTaskClosure closure)
{
return closure.DestinationPathBuffer.RemoveChild();
}
Result OnFile(in Path path, in DirectoryEntry entry, ref Utility12.FsIterationTaskClosure closure)
{
logger?.LogMessage(path.ToString());
Result result = closure.DestinationPathBuffer.AppendChild(entry.Name);
if (result.IsFailure()) return result;
result = CopyFile(closure.DestFileSystem, closure.SourceFileSystem, in closure.DestinationPathBuffer,
in path, closure.Buffer, logger, option);
if (result.IsFailure()) return result;
return closure.DestinationPathBuffer.RemoveChild();
}
var taskClosure = new Utility12.FsIterationTaskClosure();
taskClosure.Buffer = workBuffer;
taskClosure.SourceFileSystem = sourceFileSystem;
taskClosure.DestFileSystem = destinationFileSystem;
Result rc = taskClosure.DestinationPathBuffer.Initialize(destinationPath);
if (rc.IsFailure()) return rc;
rc = Utility12.IterateDirectoryRecursively(sourceFileSystem, in sourcePath, ref dirEntry, OnEnterDir,
OnExitDir, OnFile, ref taskClosure);
taskClosure.DestinationPathBuffer.Dispose();
return rc;
}
public static Result CopyFile(IFileSystem destFileSystem, IFileSystem sourceFileSystem, in Path destPath,
in Path sourcePath, Span<byte> workBuffer, IProgressReport logger = null,
CreateFileOptions option = CreateFileOptions.None)
{
logger?.LogMessage(sourcePath.ToString());
// Open source file.
Result rc = sourceFileSystem.OpenFile(out IFile sourceFile, sourcePath, OpenMode.Read);
if (rc.IsFailure()) return rc;
using (sourceFile)
{
rc = sourceFile.GetSize(out long fileSize);
if (rc.IsFailure()) return rc;
rc = CreateOrOverwriteFile(destFileSystem, in destPath, fileSize, option);
if (rc.IsFailure()) return rc;
rc = destFileSystem.OpenFile(out IFile destFile, in destPath, OpenMode.Write);
if (rc.IsFailure()) return rc;
using (destFile)
{
destFs.EnsureDirectoryExists(subDstPath);
// Read/Write file in work buffer sized chunks.
long remaining = fileSize;
long offset = 0;
rc = sourceFs.CopyDirectory(destFs, subSrcPath, subDstPath, logger, options);
if (rc.IsFailure()) return rc;
}
logger?.SetTotal(fileSize);
if (entry.Type == DirectoryEntryType.File)
{
destFs.CreateOrOverwriteFile(subDstPath, entry.Size, options);
rc = sourceFs.OpenFile(out IFile srcFile, subSrcPath.ToU8Span(), OpenMode.Read);
if (rc.IsFailure()) return rc;
using (srcFile)
while (remaining > 0)
{
rc = destFs.OpenFile(out IFile dstFile, subDstPath.ToU8Span(), OpenMode.Write | OpenMode.AllowAppend);
rc = sourceFile.Read(out long bytesRead, offset, workBuffer, ReadOption.None);
if (rc.IsFailure()) return rc;
using (dstFile)
{
logger?.LogMessage(subSrcPath);
srcFile.CopyTo(dstFile, logger);
}
rc = destFile.Write(offset, workBuffer.Slice(0, (int)bytesRead), WriteOption.None);
if (rc.IsFailure()) return rc;
remaining -= bytesRead;
offset += bytesRead;
logger?.ReportAdd(bytesRead);
}
}
}
@ -81,9 +163,10 @@ namespace LibHac.FsSystem
bool ignoreCase = searchOptions.HasFlag(SearchOptions.CaseInsensitive);
bool recurse = searchOptions.HasFlag(SearchOptions.RecurseSubdirectories);
IFileSystem fs = fileSystem;
var pathNormalized = new Path();
InitializeFromString(ref pathNormalized, path).ThrowIfFailure();
fileSystem.OpenDirectory(out IDirectory directory, path.ToU8Span(), OpenDirectoryMode.All).ThrowIfFailure();
fileSystem.OpenDirectory(out IDirectory directory, in pathNormalized, OpenDirectoryMode.All).ThrowIfFailure();
while (true)
{
@ -102,7 +185,7 @@ namespace LibHac.FsSystem
if (entry.Type != DirectoryEntryType.Directory || !recurse) continue;
IEnumerable<DirectoryEntryEx> subEntries =
fs.EnumerateEntries(PathTools.Combine(path, entry.Name), searchPattern,
fileSystem.EnumerateEntries(PathTools.Combine(path, entry.Name), searchPattern,
searchOptions);
foreach (DirectoryEntryEx subEntry in subEntries)
@ -202,7 +285,10 @@ namespace LibHac.FsSystem
public static void SetConcatenationFileAttribute(this IFileSystem fs, string path)
{
fs.QueryEntry(Span<byte>.Empty, Span<byte>.Empty, QueryId.SetConcatenationFileAttribute, path.ToU8Span());
var pathNormalized = new Path();
InitializeFromString(ref pathNormalized, path).ThrowIfFailure();
fs.QueryEntry(Span<byte>.Empty, Span<byte>.Empty, QueryId.SetConcatenationFileAttribute, in pathNormalized);
}
public static void CleanDirectoryRecursivelyGeneric(IFileSystem fileSystem, string path)
@ -213,14 +299,17 @@ namespace LibHac.FsSystem
{
string subPath = PathTools.Combine(path, entry.Name);
var subPathNormalized = new Path();
InitializeFromString(ref subPathNormalized, subPath).ThrowIfFailure();
if (entry.Type == DirectoryEntryType.Directory)
{
CleanDirectoryRecursivelyGeneric(fileSystem, subPath);
fs.DeleteDirectory(subPath.ToU8Span());
fs.DeleteDirectory(in subPathNormalized);
}
else if (entry.Type == DirectoryEntryType.File)
{
fs.DeleteFile(subPath.ToU8Span());
fs.DeleteFile(in subPathNormalized);
}
}
}
@ -251,55 +340,46 @@ namespace LibHac.FsSystem
public static Result EnsureDirectoryExists(this IFileSystem fs, string path)
{
path = PathTools.Normalize(path);
if (fs.DirectoryExists(path)) return Result.Success;
var pathNormalized = new Path();
Result rc = InitializeFromString(ref pathNormalized, path);
if (rc.IsFailure()) return rc;
// Find the first subdirectory in the chain that doesn't exist
int i;
for (i = path.Length - 1; i > 0; i--)
{
if (path[i] == '/')
{
string subPath = path.Substring(0, i);
if (fs.DirectoryExists(subPath))
{
break;
}
}
}
// path[i] will be a '/', so skip that character
i++;
// loop until `path.Length - 1` so CreateDirectory won't be called multiple
// times on path if the last character in the path is a '/'
for (; i < path.Length - 1; i++)
{
if (path[i] == '/')
{
string subPath = path.Substring(0, i);
Result rc = fs.CreateDirectory(subPath.ToU8Span());
if (rc.IsFailure()) return rc;
}
}
return fs.CreateDirectory(path.ToU8Span());
return Utility12.EnsureDirectory(fs, in pathNormalized);
}
public static void CreateOrOverwriteFile(this IFileSystem fs, string path, long size)
public static Result CreateOrOverwriteFile(IFileSystem fileSystem, in Path path, long size,
CreateFileOptions option = CreateFileOptions.None)
{
fs.CreateOrOverwriteFile(path, size, CreateFileOptions.None);
Result rc = fileSystem.CreateFile(in path, size, option);
if (rc.IsFailure())
{
if (!ResultFs.PathAlreadyExists.Includes(rc))
return rc;
rc = fileSystem.DeleteFile(in path);
if (rc.IsFailure()) return rc;
rc = fileSystem.CreateFile(in path, size, option);
if (rc.IsFailure()) return rc;
}
return Result.Success;
}
public static void CreateOrOverwriteFile(this IFileSystem fs, string path, long size, CreateFileOptions options)
private static Result InitializeFromString(ref Path outPath, string path)
{
path = PathTools.Normalize(path);
ReadOnlySpan<byte> utf8Path = StringUtils.StringToUtf8(path);
if (fs.FileExists(path)) fs.DeleteFile(path.ToU8Span());
Result rc = outPath.Initialize(utf8Path);
if (rc.IsFailure()) return rc;
fs.CreateFile(path.ToU8Span(), size, CreateFileOptions.None);
var pathFlags = new PathFlags();
pathFlags.AllowEmptyPath();
outPath.Normalize(pathFlags);
if (rc.IsFailure()) return rc;
return Result.Success;
}
}

View File

@ -0,0 +1,31 @@
using System;
using LibHac.Common;
using LibHac.Os;
namespace LibHac.FsSystem
{
public interface IUniqueLock : IDisposable { }
public class UniqueLockWithPin<T> : IUniqueLock where T : class, IDisposable
{
private UniqueLock<SemaphoreAdapter> _semaphore;
private ReferenceCountedDisposable<T> _pinnedObject;
public UniqueLockWithPin(ref UniqueLock<SemaphoreAdapter> semaphore, ref ReferenceCountedDisposable<T> pinnedObject)
{
Shared.Move(out _semaphore, ref semaphore);
Shared.Move(out _pinnedObject, ref pinnedObject);
}
public void Dispose()
{
if (_pinnedObject != null)
{
_semaphore.Dispose();
_pinnedObject.Dispose();
_pinnedObject = null;
}
}
}
}

View File

@ -48,7 +48,7 @@ namespace LibHac.FsSystem
foreach (IFileSystem fs in Sources)
{
Result rc = fs.GetEntryType(out DirectoryEntryType entryType, path);
Result rc = fs.GetEntryType(out DirectoryEntryType entryType, in path);
if (rc.IsSuccess())
{
@ -82,8 +82,8 @@ namespace LibHac.FsSystem
if (!(multipleSources is null))
{
var dir = new MergedDirectory(multipleSources, path, mode);
Result rc = dir.Initialize();
var dir = new MergedDirectory(multipleSources, mode);
Result rc = dir.Initialize(in path);
if (rc.IsSuccess())
{
@ -207,25 +207,27 @@ namespace LibHac.FsSystem
// Needed to open new directories for GetEntryCount
private List<IFileSystem> SourceFileSystems { get; }
private List<IDirectory> SourceDirs { get; }
private U8String Path { get; }
private Path.Stored _path;
private OpenDirectoryMode Mode { get; }
// todo: Efficient way to remove duplicates
private HashSet<string> Names { get; } = new HashSet<string>();
public MergedDirectory(List<IFileSystem> sourceFileSystems, U8Span path, OpenDirectoryMode mode)
public MergedDirectory(List<IFileSystem> sourceFileSystems, OpenDirectoryMode mode)
{
SourceFileSystems = sourceFileSystems;
SourceDirs = new List<IDirectory>(sourceFileSystems.Count);
Path = path.ToU8String();
Mode = mode;
}
public Result Initialize()
public Result Initialize(in Path path)
{
Result rc = _path.Initialize(in path);
if (rc.IsFailure()) return rc;
foreach (IFileSystem fs in SourceFileSystems)
{
Result rc = fs.OpenDirectory(out IDirectory dir, Path, Mode);
rc = fs.OpenDirectory(out IDirectory dir, in path, Mode);
if (rc.IsFailure()) return rc;
SourceDirs.Add(dir);
@ -268,10 +270,12 @@ namespace LibHac.FsSystem
// todo: Efficient way to remove duplicates
var names = new HashSet<string>();
Path path = _path.GetPath();
// Open new directories for each source because we need to remove duplicate entries
foreach (IFileSystem fs in SourceFileSystems)
{
Result rc = fs.OpenDirectory(out IDirectory dir, Path, Mode);
Result rc = fs.OpenDirectory(out IDirectory dir, in path, Mode);
if (rc.IsFailure()) return rc;
long entriesRead;

View File

@ -92,14 +92,12 @@ namespace LibHac.FsSystem
return Result.Success;
}
protected override void Dispose(bool disposing)
public override void Dispose()
{
if (disposing)
{
File?.Dispose();
}
File?.Dispose();
Stream?.Dispose();
base.Dispose();
}
}
}

View File

@ -2,7 +2,6 @@
using System.Buffers;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text.Unicode;
using System.Threading;
using LibHac.Common;
@ -33,7 +32,8 @@ namespace LibHac.FsSystem
CaseSensitive
}
private string _rootPath;
private Path.Stored _rootPath;
private string _rootPathUtf16;
private readonly FileSystemClient _fsClient;
private PathMode _mode;
private readonly bool _useUnixTime;
@ -57,17 +57,9 @@ namespace LibHac.FsSystem
/// <param name="rootPath">The path that will be the root of the <see cref="LocalFileSystem"/>.</param>
public LocalFileSystem(string rootPath)
{
_rootPath = System.IO.Path.GetFullPath(rootPath);
if (!Directory.Exists(_rootPath))
{
if (File.Exists(_rootPath))
{
throw new DirectoryNotFoundException($"The specified path is a file. ({rootPath})");
}
Directory.CreateDirectory(_rootPath);
}
Result rc = Initialize(rootPath, PathMode.DefaultCaseSensitivity, true);
if (rc.IsFailure())
throw new HorizonResultException(rc, "Error creating LocalFileSystem.");
}
public static Result Create(out LocalFileSystem fileSystem, string rootPath,
@ -85,6 +77,8 @@ namespace LibHac.FsSystem
public Result Initialize(string rootPath, PathMode pathMode, bool ensurePathExists)
{
Result rc;
if (rootPath == null)
return ResultFs.NullptrArgument.Log();
@ -93,13 +87,21 @@ namespace LibHac.FsSystem
// If the root path is empty, we interpret any incoming paths as rooted paths.
if (rootPath == string.Empty)
{
_rootPath = rootPath;
var path = new Path();
rc = path.InitializeAsEmpty();
if (rc.IsFailure()) return rc;
rc = _rootPath.Initialize(in path);
if (rc.IsFailure()) return rc;
return Result.Success;
}
string rootPathNormalized;
try
{
_rootPath = System.IO.Path.GetFullPath(rootPath);
rootPathNormalized = System.IO.Path.GetFullPath(rootPath);
}
catch (PathTooLongException)
{
@ -110,14 +112,14 @@ namespace LibHac.FsSystem
return ResultFs.InvalidCharacter.Log();
}
if (!Directory.Exists(_rootPath))
if (!Directory.Exists(rootPathNormalized))
{
if (!ensurePathExists || File.Exists(_rootPath))
if (!ensurePathExists || File.Exists(rootPathNormalized))
return ResultFs.PathNotFound.Log();
try
{
Directory.CreateDirectory(_rootPath);
Directory.CreateDirectory(rootPathNormalized);
}
catch (Exception ex) when (ex.HResult < 0)
{
@ -125,53 +127,78 @@ namespace LibHac.FsSystem
}
}
return Result.Success;
}
ReadOnlySpan<byte> utf8Path = StringUtils.StringToUtf8(rootPathNormalized);
var pathNormalized = new Path();
private Result ResolveFullPath(out string fullPath, U8Span path, bool checkCaseSensitivity)
{
UnsafeHelpers.SkipParamInit(out fullPath);
Unsafe.SkipInit(out FsPath normalizedPath);
Result rc = PathNormalizer.Normalize(normalizedPath.Str, out _, path, false, false);
if (rc.IsFailure()) return rc;
fullPath = PathTools.Combine(_rootPath, normalizedPath.ToString());
if (_mode == PathMode.CaseSensitive && checkCaseSensitivity)
if (utf8Path.At(0) == DirectorySeparator && utf8Path.At(1) != DirectorySeparator)
{
rc = CheckPathCaseSensitively(fullPath);
rc = pathNormalized.Initialize(utf8Path.Slice(1));
if (rc.IsFailure()) return rc;
}
else
{
rc = pathNormalized.InitializeWithReplaceUnc(utf8Path);
if (rc.IsFailure()) return rc;
}
var flags = new PathFlags();
flags.AllowWindowsPath();
flags.AllowRelativePath();
flags.AllowEmptyPath();
rc = pathNormalized.Normalize(flags);
if (rc.IsFailure()) return rc;
rc = _rootPath.Initialize(in pathNormalized);
if (rc.IsFailure()) return rc;
_rootPathUtf16 = _rootPath.ToString();
pathNormalized.Dispose();
return Result.Success;
}
private Result CheckSubPath(U8Span path1, U8Span path2)
private Result ResolveFullPath(out string outFullPath, in Path path, bool checkCaseSensitivity)
{
Unsafe.SkipInit(out FsPath normalizedPath1);
Unsafe.SkipInit(out FsPath normalizedPath2);
UnsafeHelpers.SkipParamInit(out outFullPath);
Result rc = PathNormalizer.Normalize(normalizedPath1.Str, out _, path1, false, false);
// Always normalize the incoming path even if it claims to already be normalized
// because we don't want to allow access to anything outside the root path.
var pathNormalized = new Path();
Result rc = pathNormalized.Initialize(path.GetString());
if (rc.IsFailure()) return rc;
rc = PathNormalizer.Normalize(normalizedPath2.Str, out _, path2, false, false);
var pathFlags = new PathFlags();
pathFlags.AllowWindowsPath();
pathFlags.AllowRelativePath();
pathFlags.AllowEmptyPath();
rc = pathNormalized.Normalize(pathFlags);
if (rc.IsFailure()) return rc;
if (PathUtility.IsSubPath(normalizedPath1, normalizedPath2))
Path rootPath = _rootPath.GetPath();
var fullPath = new Path();
rc = fullPath.Combine(in rootPath, in pathNormalized);
if (rc.IsFailure()) return rc;
string utf16FullPath = fullPath.ToString();
if (_mode == PathMode.CaseSensitive && checkCaseSensitivity)
{
return ResultFs.DirectoryNotRenamable.Log();
rc = CheckPathCaseSensitively(utf16FullPath);
if (rc.IsFailure()) return rc;
}
outFullPath = utf16FullPath;
return Result.Success;
}
protected override Result DoGetFileAttributes(out NxFileAttributes attributes, U8Span path)
protected override Result DoGetFileAttributes(out NxFileAttributes attributes, in Path path)
{
UnsafeHelpers.SkipParamInit(out attributes);
Result rc = ResolveFullPath(out string fullPath, path, true);
Result rc = ResolveFullPath(out string fullPath, in path, true);
if (rc.IsFailure()) return rc;
rc = GetFileInfo(out FileInfo info, fullPath);
@ -186,9 +213,9 @@ namespace LibHac.FsSystem
return Result.Success;
}
protected override Result DoSetFileAttributes(U8Span path, NxFileAttributes attributes)
protected override Result DoSetFileAttributes(in Path path, NxFileAttributes attributes)
{
Result rc = ResolveFullPath(out string fullPath, path, true);
Result rc = ResolveFullPath(out string fullPath, in path, true);
if (rc.IsFailure()) return rc;
rc = GetFileInfo(out FileInfo info, fullPath);
@ -214,11 +241,11 @@ namespace LibHac.FsSystem
return Result.Success;
}
protected override Result DoGetFileSize(out long fileSize, U8Span path)
protected override Result DoGetFileSize(out long fileSize, in Path path)
{
UnsafeHelpers.SkipParamInit(out fileSize);
Result rc = ResolveFullPath(out string fullPath, path, true);
Result rc = ResolveFullPath(out string fullPath, in path, true);
if (rc.IsFailure()) return rc;
rc = GetFileInfo(out FileInfo info, fullPath);
@ -232,9 +259,9 @@ namespace LibHac.FsSystem
return DoCreateDirectory(path, NxFileAttributes.None);
}
protected override Result DoCreateDirectory(U8Span path, NxFileAttributes archiveAttribute)
protected override Result DoCreateDirectory(in Path path, NxFileAttributes archiveAttribute)
{
Result rc = ResolveFullPath(out string fullPath, path, false);
Result rc = ResolveFullPath(out string fullPath, in path, false);
if (rc.IsFailure()) return rc;
rc = GetDirInfo(out DirectoryInfo dir, fullPath);
@ -255,7 +282,7 @@ namespace LibHac.FsSystem
protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option)
{
Result rc = ResolveFullPath(out string fullPath, path, false);
Result rc = ResolveFullPath(out string fullPath, in path, false);
if (rc.IsFailure()) return rc;
rc = GetFileInfo(out FileInfo file, fullPath);
@ -283,7 +310,7 @@ namespace LibHac.FsSystem
protected override Result DoDeleteDirectory(in Path path)
{
Result rc = ResolveFullPath(out string fullPath, path, true);
Result rc = ResolveFullPath(out string fullPath, in path, true);
if (rc.IsFailure()) return rc;
rc = GetDirInfo(out DirectoryInfo dir, fullPath);
@ -295,7 +322,7 @@ namespace LibHac.FsSystem
protected override Result DoDeleteDirectoryRecursively(in Path path)
{
Result rc = ResolveFullPath(out string fullPath, path, true);
Result rc = ResolveFullPath(out string fullPath, in path, true);
if (rc.IsFailure()) return rc;
rc = GetDirInfo(out DirectoryInfo dir, fullPath);
@ -307,7 +334,7 @@ namespace LibHac.FsSystem
protected override Result DoCleanDirectoryRecursively(in Path path)
{
Result rc = ResolveFullPath(out string fullPath, path, true);
Result rc = ResolveFullPath(out string fullPath, in path, true);
if (rc.IsFailure()) return rc;
foreach (string file in Directory.EnumerateFiles(fullPath))
@ -343,7 +370,7 @@ namespace LibHac.FsSystem
protected override Result DoDeleteFile(in Path path)
{
Result rc = ResolveFullPath(out string fullPath, path, true);
Result rc = ResolveFullPath(out string fullPath, in path, true);
if (rc.IsFailure()) return rc;
rc = GetFileInfo(out FileInfo file, fullPath);
@ -356,7 +383,7 @@ namespace LibHac.FsSystem
protected override Result DoOpenDirectory(out IDirectory directory, in Path path, OpenDirectoryMode mode)
{
UnsafeHelpers.SkipParamInit(out directory);
Result rc = ResolveFullPath(out string fullPath, path, true);
Result rc = ResolveFullPath(out string fullPath, in path, true);
if (rc.IsFailure()) return rc;
rc = GetDirInfo(out DirectoryInfo dirInfo, fullPath);
@ -380,7 +407,7 @@ namespace LibHac.FsSystem
{
UnsafeHelpers.SkipParamInit(out file);
Result rc = ResolveFullPath(out string fullPath, path, true);
Result rc = ResolveFullPath(out string fullPath, in path, true);
if (rc.IsFailure()) return rc;
rc = GetEntryType(out DirectoryEntryType entryType, path);
@ -403,13 +430,10 @@ namespace LibHac.FsSystem
protected override Result DoRenameDirectory(in Path currentPath, in Path newPath)
{
Result rc = CheckSubPath(currentPath, newPath);
Result rc = ResolveFullPath(out string fullCurrentPath, in currentPath, true);
if (rc.IsFailure()) return rc;
rc = ResolveFullPath(out string fullCurrentPath, currentPath, true);
if (rc.IsFailure()) return rc;
rc = ResolveFullPath(out string fullNewPath, newPath, false);
rc = ResolveFullPath(out string fullNewPath, in newPath, false);
if (rc.IsFailure()) return rc;
// Official FS behavior is to do nothing in this case
@ -427,10 +451,10 @@ namespace LibHac.FsSystem
protected override Result DoRenameFile(in Path currentPath, in Path newPath)
{
Result rc = ResolveFullPath(out string fullCurrentPath, currentPath, true);
Result rc = ResolveFullPath(out string fullCurrentPath, in currentPath, true);
if (rc.IsFailure()) return rc;
rc = ResolveFullPath(out string fullNewPath, newPath, false);
rc = ResolveFullPath(out string fullNewPath, in newPath, false);
if (rc.IsFailure()) return rc;
// Official FS behavior is to do nothing in this case
@ -450,7 +474,7 @@ namespace LibHac.FsSystem
{
UnsafeHelpers.SkipParamInit(out entryType);
Result rc = ResolveFullPath(out string fullPath, path, true);
Result rc = ResolveFullPath(out string fullPath, in path, true);
if (rc.IsFailure()) return rc;
rc = GetDirInfo(out DirectoryInfo dir, fullPath);
@ -478,7 +502,7 @@ namespace LibHac.FsSystem
{
UnsafeHelpers.SkipParamInit(out timeStamp);
Result rc = ResolveFullPath(out string fullPath, path, true);
Result rc = ResolveFullPath(out string fullPath, in path, true);
if (rc.IsFailure()) return rc;
rc = GetFileInfo(out FileInfo file, fullPath);
@ -508,7 +532,7 @@ namespace LibHac.FsSystem
{
UnsafeHelpers.SkipParamInit(out freeSpace);
Result rc = ResolveFullPath(out string fullPath, path, true);
Result rc = ResolveFullPath(out string fullPath, in path, true);
if (rc.IsFailure()) return rc;
freeSpace = new DriveInfo(fullPath).AvailableFreeSpace;
@ -519,7 +543,7 @@ namespace LibHac.FsSystem
{
UnsafeHelpers.SkipParamInit(out totalSpace);
Result rc = ResolveFullPath(out string fullPath, path, true);
Result rc = ResolveFullPath(out string fullPath, in path, true);
if (rc.IsFailure()) return rc;
totalSpace = new DriveInfo(fullPath).TotalSize;
@ -802,7 +826,7 @@ namespace LibHac.FsSystem
private Result CheckPathCaseSensitively(string path)
{
Result rc = GetCaseSensitivePathFull(out string caseSensitivePath, out _, path, _rootPath);
Result rc = GetCaseSensitivePathFull(out string caseSensitivePath, out _, path, _rootPathUtf16);
if (rc.IsFailure()) return rc;
if (path.Length != caseSensitivePath.Length)

View File

@ -42,9 +42,9 @@ namespace LibHac.FsSystem
protected override Result DoOpenFile(out IFile file, in Path path, OpenMode mode)
{
path = PathTools.Normalize(path.ToString()).TrimStart('/').ToU8Span();
string pathNormalized = PathTools.Normalize(path.ToString()).TrimStart('/');
if (!FileDict.TryGetValue(path.ToString(), out PartitionFileEntry entry))
if (!FileDict.TryGetValue(pathNormalized, out PartitionFileEntry entry))
{
ThrowHelper.ThrowResult(ResultFs.PathNotFound.Value);
}

View File

@ -58,7 +58,7 @@ namespace LibHac.FsSystem
ReadOnlySpan<byte> rootPath = new[] { (byte)'/' };
if (StringUtils.Compare(rootPath, path, 2) != 0)
if (path == rootPath)
return ResultFs.PathNotFound.Log();
directory = new PartitionDirectory(this, mode);
@ -76,7 +76,7 @@ namespace LibHac.FsSystem
if (!mode.HasFlag(OpenMode.Read) && !mode.HasFlag(OpenMode.Write))
return ResultFs.InvalidArgument.Log();
int entryIndex = MetaData.FindEntry(path.Slice(1));
int entryIndex = MetaData.FindEntry(new U8Span(path.GetString().Slice(1)));
if (entryIndex < 0) return ResultFs.PathNotFound.Log();
ref T entry = ref MetaData.GetEntry(entryIndex);
@ -93,18 +93,20 @@ namespace LibHac.FsSystem
if (!IsInitialized)
return ResultFs.PreconditionViolation.Log();
if (path.IsEmpty() || path[0] != '/')
ReadOnlySpan<byte> pathStr = path.GetString();
if (path.IsEmpty() || pathStr[0] != '/')
return ResultFs.InvalidPathFormat.Log();
ReadOnlySpan<byte> rootPath = new[] { (byte)'/' };
if (StringUtils.Compare(rootPath, path, 2) == 0)
if (StringUtils.Compare(rootPath, pathStr, 2) == 0)
{
entryType = DirectoryEntryType.Directory;
return Result.Success;
}
if (MetaData.FindEntry(path.Slice(1)) >= 0)
if (MetaData.FindEntry(new U8Span(pathStr.Slice(1))) >= 0)
{
entryType = DirectoryEntryType.File;
return Result.Success;

View File

@ -3,6 +3,7 @@ using System.Diagnostics;
using System.IO.Enumeration;
using System.Runtime.CompilerServices;
using LibHac.Common;
using LibHac.Diag;
using LibHac.Fs;
using LibHac.Util;
@ -235,7 +236,7 @@ namespace LibHac.FsSystem
public static ReadOnlySpan<byte> GetParentDirectory(ReadOnlySpan<byte> path)
{
Debug.Assert(IsNormalized(path));
Assert.SdkAssert(IsNormalized(path));
int i = StringUtils.GetLength(path) - 1;
@ -288,6 +289,9 @@ namespace LibHac.FsSystem
foreach (char c in path)
{
if (c == 0)
break;
switch (state)
{
case NormalizeState.Initial when c == '/': state = NormalizeState.Delimiter; break;
@ -325,6 +329,9 @@ namespace LibHac.FsSystem
foreach (byte c in path)
{
if (c == 0)
break;
switch (state)
{
case NormalizeState.Initial when c == '/': state = NormalizeState.Delimiter; break;

View File

@ -1,96 +0,0 @@
using System;
using System.Threading;
using LibHac.Common;
using LibHac.Diag;
using LibHac.Os;
namespace LibHac.FsSystem
{
public interface IUniqueLock : IDisposable
{
}
/// <summary>
/// Represents a lock that may be passed between functions or objects.
/// </summary>
/// <remarks>This struct must never be copied. It must always be passed by
/// reference or moved via the move constructor.</remarks>
public struct UniqueLockSemaphore : IDisposable
{
private SemaphoreAdapter _semaphore;
private bool _isLocked;
public UniqueLockSemaphore(SemaphoreAdapter semaphore)
{
_semaphore = semaphore;
_isLocked = false;
}
public UniqueLockSemaphore(ref UniqueLockSemaphore other)
{
_semaphore = other._semaphore;
_isLocked = other._isLocked;
other._isLocked = false;
other._semaphore = null;
}
public bool IsLocked => _isLocked;
public bool TryLock()
{
if (_isLocked)
{
throw new SynchronizationLockException("Attempted to lock a UniqueLock that was already locked.");
}
_isLocked = _semaphore.TryLock();
return _isLocked;
}
public void Unlock()
{
if (!_isLocked)
{
throw new SynchronizationLockException("Attempted to unlock a UniqueLock that was not locked.");
}
_semaphore.Unlock();
_isLocked = false;
}
public void Dispose()
{
if (_isLocked)
{
_semaphore.Unlock();
_isLocked = false;
_semaphore = null;
}
}
}
public class UniqueLockWithPin<T> : IUniqueLock where T : class, IDisposable
{
private UniqueLock<SemaphoreAdapter> _semaphore;
private ReferenceCountedDisposable<T> _pinnedObject;
public UniqueLockWithPin(ref UniqueLock<SemaphoreAdapter> semaphore, ref ReferenceCountedDisposable<T> pinnedObject)
{
Shared.Move(out _semaphore, ref semaphore);
Shared.Move(out _pinnedObject, ref pinnedObject);
}
public void Dispose()
{
if (_pinnedObject != null)
{
_semaphore.Dispose();
_pinnedObject.Dispose();
_pinnedObject = null;
}
}
}
}

View File

@ -1,270 +0,0 @@
using System;
using System.Runtime.CompilerServices;
using LibHac.Common;
using LibHac.Diag;
using LibHac.Fs;
using LibHac.Fs.Common;
using LibHac.Fs.Fsa;
using LibHac.Util;
namespace LibHac.FsSystem
{
internal static class Utility
{
public delegate Result FsIterationTask(U8Span path, ref DirectoryEntry entry);
private static U8Span RootPath => new U8Span(new[] { (byte)'/' });
private static U8Span DirectorySeparator => RootPath;
public static Result IterateDirectoryRecursively(IFileSystem fs, U8Span rootPath, Span<byte> workPath,
ref DirectoryEntry dirEntry, FsIterationTask onEnterDir, FsIterationTask onExitDir, FsIterationTask onFile)
{
Abort.DoAbortUnless(workPath.Length >= PathTool.EntryNameLengthMax + 1);
// Get size of the root path.
int rootPathLen = StringUtils.GetLength(rootPath, PathTool.EntryNameLengthMax + 1);
if (rootPathLen > PathTool.EntryNameLengthMax)
return ResultFs.TooLongPath.Log();
// Copy root path in, add a / if necessary.
rootPath.Value.Slice(0, rootPathLen).CopyTo(workPath);
if (workPath[rootPathLen - 1] != StringTraits.DirectorySeparator)
{
workPath[rootPathLen++] = StringTraits.DirectorySeparator;
}
// Make sure the result path is still valid.
if (rootPathLen > PathTool.EntryNameLengthMax)
return ResultFs.TooLongPath.Log();
workPath[rootPathLen] = StringTraits.NullTerminator;
return IterateDirectoryRecursivelyImpl(fs, workPath, ref dirEntry, onEnterDir, onExitDir, onFile);
}
public static Result IterateDirectoryRecursively(IFileSystem fs, U8Span rootPath, FsIterationTask onEnterDir,
FsIterationTask onExitDir, FsIterationTask onFile)
{
var entry = new DirectoryEntry();
Span<byte> workPath = stackalloc byte[PathTools.MaxPathLength + 1];
return IterateDirectoryRecursively(fs, rootPath, workPath, ref entry, onEnterDir, onExitDir,
onFile);
}
public static Result IterateDirectoryRecursively(IFileSystem fs, FsIterationTask onEnterDir,
FsIterationTask onExitDir, FsIterationTask onFile)
{
return IterateDirectoryRecursively(fs, RootPath, onEnterDir, onExitDir, onFile);
}
private static Result IterateDirectoryRecursivelyImpl(IFileSystem fs, Span<byte> workPath,
ref DirectoryEntry dirEntry, FsIterationTask onEnterDir, FsIterationTask onExitDir, FsIterationTask onFile)
{
Result rc = fs.OpenDirectory(out IDirectory dir, new U8Span(workPath), OpenDirectoryMode.All);
if (rc.IsFailure()) return rc;
int parentLen = StringUtils.GetLength(workPath);
// Read and handle entries.
while (true)
{
// Read a single entry.
rc = dir.Read(out long readCount, SpanHelpers.AsSpan(ref dirEntry));
if (rc.IsFailure()) return rc;
// If we're out of entries, we're done.
if (readCount == 0)
break;
// Validate child path size.
int childNameLen = StringUtils.GetLength(dirEntry.Name);
bool isDir = dirEntry.Type == DirectoryEntryType.Directory;
int separatorSize = isDir ? 1 : 0;
if (parentLen + childNameLen + separatorSize >= workPath.Length)
return ResultFs.TooLongPath.Log();
// Set child path.
StringUtils.Concat(workPath, dirEntry.Name);
{
if (isDir)
{
// Enter directory.
rc = onEnterDir(new U8Span(workPath), ref dirEntry);
if (rc.IsFailure()) return rc;
// Append separator, recurse.
StringUtils.Concat(workPath, DirectorySeparator);
rc = IterateDirectoryRecursivelyImpl(fs, workPath, ref dirEntry, onEnterDir, onExitDir, onFile);
if (rc.IsFailure()) return rc;
// Exit directory.
rc = onExitDir(new U8Span(workPath), ref dirEntry);
if (rc.IsFailure()) return rc;
}
else
{
// Call file handler.
rc = onFile(new U8Span(workPath), ref dirEntry);
if (rc.IsFailure()) return rc;
}
}
// Restore parent path.
workPath[parentLen] = StringTraits.NullTerminator;
}
return Result.Success;
}
public static Result CopyDirectoryRecursively(IFileSystem fileSystem, U8Span destPath, U8Span sourcePath,
Span<byte> workBuffer)
{
return CopyDirectoryRecursively(fileSystem, fileSystem, destPath, sourcePath, workBuffer);
}
public static unsafe Result CopyDirectoryRecursively(IFileSystem destFileSystem, IFileSystem sourceFileSystem,
U8Span destPath, U8Span sourcePath, Span<byte> workBuffer)
{
var destPathBuf = new FsPath();
int originalSize = StringUtils.Copy(destPathBuf.Str, destPath);
Abort.DoAbortUnless(originalSize < Unsafe.SizeOf<FsPath>());
// Pin and recreate the span because C# can't use byref-like types in a closure
int workBufferSize = workBuffer.Length;
fixed (byte* pWorkBuffer = workBuffer)
{
// Copy the pointer to workaround CS1764.
// IterateDirectoryRecursively won't store the delegate anywhere, so it should be safe
byte* pWorkBuffer2 = pWorkBuffer;
Result OnEnterDir(U8Span path, ref DirectoryEntry entry)
{
// Update path, create new dir.
StringUtils.Concat(SpanHelpers.AsByteSpan(ref destPathBuf), entry.Name);
StringUtils.Concat(SpanHelpers.AsByteSpan(ref destPathBuf), DirectorySeparator);
return destFileSystem.CreateDirectory(destPathBuf);
}
Result OnExitDir(U8Span path, ref DirectoryEntry entry)
{
// Check we have a parent directory.
int len = StringUtils.GetLength(SpanHelpers.AsByteSpan(ref destPathBuf));
if (len < 2)
return ResultFs.InvalidPathFormat.Log();
// Find previous separator, add null terminator
int cur = len - 2;
while (SpanHelpers.AsByteSpan(ref destPathBuf)[cur] != StringTraits.DirectorySeparator && cur > 0)
{
cur--;
}
SpanHelpers.AsByteSpan(ref destPathBuf)[cur + 1] = StringTraits.NullTerminator;
return Result.Success;
}
Result OnFile(U8Span path, ref DirectoryEntry entry)
{
var buffer = new Span<byte>(pWorkBuffer2, workBufferSize);
return CopyFile(destFileSystem, sourceFileSystem, destPathBuf, path, ref entry, buffer);
}
return IterateDirectoryRecursively(sourceFileSystem, sourcePath, OnEnterDir, OnExitDir, OnFile);
}
}
public static Result CopyFile(IFileSystem destFileSystem, IFileSystem sourceFileSystem, U8Span destParentPath,
U8Span sourcePath, ref DirectoryEntry entry, Span<byte> workBuffer)
{
// Open source file.
Result rc = sourceFileSystem.OpenFile(out IFile sourceFile, sourcePath, OpenMode.Read);
if (rc.IsFailure()) return rc;
using (sourceFile)
{
// Open dest file.
Unsafe.SkipInit(out FsPath destPath);
var sb = new U8StringBuilder(destPath.Str);
sb.Append(destParentPath).Append(entry.Name);
Assert.SdkLess(sb.Length, Unsafe.SizeOf<FsPath>());
rc = destFileSystem.CreateFile(new U8Span(destPath.Str), entry.Size);
if (rc.IsFailure()) return rc;
rc = destFileSystem.OpenFile(out IFile destFile, new U8Span(destPath.Str), OpenMode.Write);
if (rc.IsFailure()) return rc;
using (destFile)
{
// Read/Write file in work buffer sized chunks.
long remaining = entry.Size;
long offset = 0;
while (remaining > 0)
{
rc = sourceFile.Read(out long bytesRead, offset, workBuffer, ReadOption.None);
if (rc.IsFailure()) return rc;
rc = destFile.Write(offset, workBuffer.Slice(0, (int)bytesRead), WriteOption.None);
if (rc.IsFailure()) return rc;
remaining -= bytesRead;
offset += bytesRead;
}
}
}
return Result.Success;
}
public static Result TryAcquireCountSemaphore(out UniqueLockSemaphore uniqueLock, SemaphoreAdapter semaphore)
{
UniqueLockSemaphore tempUniqueLock = default;
try
{
tempUniqueLock = new UniqueLockSemaphore(semaphore);
if (!tempUniqueLock.TryLock())
{
UnsafeHelpers.SkipParamInit(out uniqueLock);
return ResultFs.OpenCountLimit.Log();
}
uniqueLock = new UniqueLockSemaphore(ref tempUniqueLock);
return Result.Success;
}
finally
{
tempUniqueLock.Dispose();
}
}
public static Result MakeUniqueLockWithPin<T>(out IUniqueLock uniqueLock, SemaphoreAdapter semaphore,
ref ReferenceCountedDisposable<T> objectToPin) where T : class, IDisposable
{
UnsafeHelpers.SkipParamInit(out uniqueLock);
UniqueLockSemaphore tempUniqueLock = default;
try
{
Result rc = TryAcquireCountSemaphore(out tempUniqueLock, semaphore);
if (rc.IsFailure()) return rc;
uniqueLock = new UniqueLockWithPin<T>(ref tempUniqueLock, ref objectToPin);
return Result.Success;
}
finally
{
tempUniqueLock.Dispose();
}
}
}
}

View File

@ -433,7 +433,7 @@ namespace LibHac.FsSystem
UniqueLock<SemaphoreAdapter> tempUniqueLock = default;
try
{
tempUniqueLock = new UniqueLock<SemaphoreAdapter>(semaphore);
tempUniqueLock = new UniqueLock<SemaphoreAdapter>(semaphore, new DeferLock());
if (!tempUniqueLock.TryLock())
{

View File

@ -8,7 +8,7 @@ namespace LibHac.Lr
internal struct LrServiceGlobals
{
public ILocationResolverManager LocationResolver;
public SdkMutex InitializationMutex;
public SdkMutexType InitializationMutex;
public void Initialize()
{
@ -34,7 +34,7 @@ namespace LibHac.Lr
Assert.SdkRequiresNotNull(globals.LocationResolver);
// The lock over getting the service object is a LibHac addition.
using ScopedLock<SdkMutex> scopedLock = ScopedLock.Lock(ref lr.Globals.LrService.InitializationMutex);
using ScopedLock<SdkMutexType> scopedLock = ScopedLock.Lock(ref lr.Globals.LrService.InitializationMutex);
if (globals.LocationResolver is not null)
return;

View File

@ -5,6 +5,12 @@ using LibHac.Common;
namespace LibHac.Os
{
/// <summary>
/// Specifies that a constructed <see cref="UniqueLock{TMutex}"/> should not be automatically locked upon construction.<br/>
/// Used only to differentiate between <see cref="UniqueLock{TMutex}"/> constructor signatures.
/// </summary>
public struct DeferLock { }
public static class UniqueLock
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -33,6 +39,13 @@ namespace LibHac.Os
_ownsLock = true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public UniqueLockRef(ref TMutex mutex, DeferLock tag)
{
_mutex = new Ref<TMutex>(ref mutex);
_ownsLock = false;
}
public UniqueLockRef(ref UniqueLockRef<TMutex> other)
{
this = other;
@ -99,11 +112,18 @@ namespace LibHac.Os
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public UniqueLock(TMutex mutex)
{
_mutex = new Ref<TMutex>(ref mutex);
_mutex = mutex;
mutex.Lock();
_ownsLock = true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public UniqueLock(TMutex mutex, DeferLock tag)
{
_mutex = mutex;
_ownsLock = false;
}
public UniqueLock(ref UniqueLock<TMutex> other)
{
this = other;

View File

@ -44,14 +44,20 @@ namespace LibHac
{
var concatFs = new ConcatenationFileSystem(fileSystem);
var contentDirPath = new Fs.Path();
PathFunctions.SetUpFixedPath(ref contentDirPath, "/Nintendo/Contents".ToU8String()).ThrowIfFailure();
var saveDirPath = new Fs.Path();
PathFunctions.SetUpFixedPath(ref saveDirPath, "/Nintendo/save".ToU8String()).ThrowIfFailure();
var contentDirFs = new SubdirectoryFileSystem(concatFs);
contentDirFs.Initialize("/Nintendo/Contents".ToU8String()).ThrowIfFailure();
contentDirFs.Initialize(in contentDirPath).ThrowIfFailure();
AesXtsFileSystem encSaveFs = null;
if (fileSystem.DirectoryExists("/Nintendo/save"))
{
var saveDirFs = new SubdirectoryFileSystem(concatFs);
saveDirFs.Initialize("/Nintendo/save".ToU8String()).ThrowIfFailure();
saveDirFs.Initialize(in saveDirPath).ThrowIfFailure();
encSaveFs = new AesXtsFileSystem(saveDirFs, keySet.SdCardEncryptionKeys[0].DataRo.ToArray(), 0x4000);
}
@ -65,7 +71,7 @@ namespace LibHac
{
var concatFs = new ConcatenationFileSystem(fileSystem);
SubdirectoryFileSystem saveDirFs = null;
SubdirectoryFileSystem contentDirFs = null;
SubdirectoryFileSystem contentDirFs;
if (concatFs.DirectoryExists("/save"))
{

View File

@ -15,7 +15,7 @@ namespace hactoolnet
return;
}
var localFs = new LocalFileSystem(ctx.Options.InFile);
LocalFileSystem.Create(out LocalFileSystem localFs, ctx.Options.InFile).ThrowIfFailure();
var builder = new RomFsBuilder(localFs);
IStorage romFs = builder.Build();

View File

@ -43,8 +43,8 @@ namespace LibHac.Tests.Fs.FileSystemClientTests.ShimTests
var applicationId = new Ncm.ApplicationId(1);
FileSystemClient fs = FileSystemServerFactory.CreateClient(true);
fs.CreateCacheStorage(applicationId, SaveDataSpaceId.SdCache, applicationId.Value, 0, 0, SaveDataFlags.None);
fs.MountCacheStorage("cache".ToU8Span(), applicationId);
Assert.Success(fs.CreateCacheStorage(applicationId, SaveDataSpaceId.SdCache, applicationId.Value, 0, 0, SaveDataFlags.None));
Assert.Success(fs.MountCacheStorage("cache".ToU8Span(), applicationId));
fs.CreateFile("cache:/sd".ToU8Span(), 0);
fs.Commit("cache".ToU8Span());
fs.Unmount("cache".ToU8Span());

View File

@ -1,13 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LibHac.Tests.FsSystem
namespace LibHac.Tests.FsSystem
{
class ConcatenationFileSystemTests
public class ConcatenationFileSystemTests
{
asdf
//asdf
}
}

View File

@ -1,6 +1,7 @@
using System;
using Xunit.Sdk;
// ReSharper disable once CheckNamespace
namespace Xunit
{
public partial class Assert