mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Rewrite file system accessors
This commit is contained in:
parent
5eb063f4ec
commit
596a8bef7c
18 changed files with 957 additions and 20 deletions
29
src/LibHac/Common/FixedArrays/Array8.cs
Normal file
29
src/LibHac/Common/FixedArrays/Array8.cs
Normal file
|
@ -0,0 +1,29 @@
|
|||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace LibHac.Common.FixedArrays
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct Array8<T>
|
||||
{
|
||||
public const int Length = 8;
|
||||
|
||||
private T _item01;
|
||||
private T _item02;
|
||||
private T _item03;
|
||||
private T _item04;
|
||||
private T _item05;
|
||||
private T _item06;
|
||||
private T _item07;
|
||||
private T _item08;
|
||||
|
||||
public ref T this[int i] => ref Items[i];
|
||||
|
||||
public Span<T> Items => SpanHelpers.CreateSpan(ref _item01, Length);
|
||||
public readonly ReadOnlySpan<T> ItemsRo => SpanHelpers.CreateReadOnlySpan(in _item01, Length);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator ReadOnlySpan<T>(in Array8<T> value) => value.ItemsRo;
|
||||
}
|
||||
}
|
|
@ -6,11 +6,11 @@ namespace LibHac.Diag
|
|||
public static class Abort
|
||||
{
|
||||
[DoesNotReturn]
|
||||
public static void DoAbort(string message = null)
|
||||
public static void DoAbort(Result result, string message = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(message))
|
||||
{
|
||||
throw new LibHacException("Abort.");
|
||||
throw new HorizonResultException(result, "Abort.");
|
||||
}
|
||||
|
||||
throw new LibHacException($"Abort: {message}");
|
||||
|
@ -21,7 +21,15 @@ namespace LibHac.Diag
|
|||
if (condition)
|
||||
return;
|
||||
|
||||
DoAbort(message);
|
||||
DoAbort(default, message);
|
||||
}
|
||||
|
||||
public static void DoAbortUnless([DoesNotReturnIf(false)] bool condition, Result result, string message = null)
|
||||
{
|
||||
if (condition)
|
||||
return;
|
||||
|
||||
DoAbort(result, message);
|
||||
}
|
||||
|
||||
[DoesNotReturn]
|
||||
|
|
|
@ -12,6 +12,23 @@ using IFileSystem = LibHac.Fs.Fsa.IFileSystem;
|
|||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
// Functions in the nn::fssrv::detail namespace use this struct.
|
||||
public readonly struct FileSystemClientImpl
|
||||
{
|
||||
internal readonly FileSystemClient Fs;
|
||||
internal HorizonClient Hos => Fs.Hos;
|
||||
internal ref FileSystemClientGlobals Globals => ref Fs.Globals;
|
||||
|
||||
internal FileSystemClientImpl(FileSystemClient parentClient) => Fs = parentClient;
|
||||
}
|
||||
|
||||
internal struct FileSystemClientGlobals
|
||||
{
|
||||
public HorizonClient Hos;
|
||||
public object InitMutex;
|
||||
public FileSystemProxyServiceObjectGlobals FileSystemProxyServiceObject;
|
||||
}
|
||||
|
||||
public partial class FileSystemClient
|
||||
{
|
||||
internal FileSystemClientGlobals Globals;
|
||||
|
@ -38,13 +55,6 @@ namespace LibHac.Fs
|
|||
Assert.NotNull(Time);
|
||||
}
|
||||
|
||||
internal struct FileSystemClientGlobals
|
||||
{
|
||||
public HorizonClient Hos;
|
||||
public object InitMutex;
|
||||
public FileSystemProxyServiceObjectGlobals FileSystemProxyServiceObject;
|
||||
}
|
||||
|
||||
public bool HasFileSystemServer()
|
||||
{
|
||||
return Hos != null;
|
||||
|
|
39
src/LibHac/Fs/Fsa/DirectoryAccessor.cs
Normal file
39
src/LibHac/Fs/Fsa/DirectoryAccessor.cs
Normal file
|
@ -0,0 +1,39 @@
|
|||
using System;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs.Fsa;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace LibHac.Fs.Impl
|
||||
{
|
||||
internal class DirectoryAccessor : IDisposable
|
||||
{
|
||||
private IDirectory _directory;
|
||||
private FileSystemAccessor _parentFileSystem;
|
||||
|
||||
public DirectoryAccessor(ref IDirectory directory, FileSystemAccessor parentFileSystem)
|
||||
{
|
||||
_directory = Shared.Move(ref directory);
|
||||
_parentFileSystem = parentFileSystem;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_directory?.Dispose();
|
||||
_directory = null;
|
||||
|
||||
_parentFileSystem.NotifyCloseDirectory(this);
|
||||
}
|
||||
|
||||
public FileSystemAccessor GetParent() => _parentFileSystem;
|
||||
|
||||
public Result Read(out long entriesRead, Span<DirectoryEntry> entryBuffer)
|
||||
{
|
||||
return _directory.Read(out entriesRead, entryBuffer);
|
||||
}
|
||||
|
||||
public Result GetEntryCount(out long entryCount)
|
||||
{
|
||||
return _directory.GetEntryCount(out entryCount);
|
||||
}
|
||||
}
|
||||
}
|
181
src/LibHac/Fs/Fsa/FileAccessor.cs
Normal file
181
src/LibHac/Fs/Fsa/FileAccessor.cs
Normal file
|
@ -0,0 +1,181 @@
|
|||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using LibHac.Common;
|
||||
using LibHac.Diag;
|
||||
using LibHac.Fs.Fsa;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace LibHac.Fs.Impl
|
||||
{
|
||||
internal enum WriteState
|
||||
{
|
||||
None,
|
||||
NeedsFlush,
|
||||
Failed,
|
||||
}
|
||||
|
||||
internal class FileAccessor : IDisposable
|
||||
{
|
||||
private IFile _file;
|
||||
private FileSystemAccessor _parentFileSystem;
|
||||
private WriteState _writeState;
|
||||
private Result _lastResult;
|
||||
private OpenMode _openMode;
|
||||
private FilePathHash _filePathHash;
|
||||
// ReSharper disable once NotAccessedField.Local
|
||||
private int _pathHashIndex;
|
||||
|
||||
public FileAccessor(ref IFile file, FileSystemAccessor parentFileSystem, OpenMode mode)
|
||||
{
|
||||
_file = Shared.Move(ref file);
|
||||
_parentFileSystem = parentFileSystem;
|
||||
_openMode = mode;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_lastResult.IsSuccess() && _writeState == WriteState.NeedsFlush)
|
||||
{
|
||||
Abort.DoAbort(ResultFs.NeedFlush.Log(), "File needs flush before closing.");
|
||||
}
|
||||
|
||||
_parentFileSystem?.NotifyCloseFile(this);
|
||||
_file?.Dispose();
|
||||
|
||||
_file = null;
|
||||
}
|
||||
|
||||
public OpenMode GetOpenMode() => _openMode;
|
||||
public WriteState GetWriteState() => _writeState;
|
||||
public FileSystemAccessor GetParent() => _parentFileSystem;
|
||||
|
||||
public void SetFilePathHash(FilePathHash filePathHash, int index)
|
||||
{
|
||||
_filePathHash = filePathHash;
|
||||
_pathHashIndex = index;
|
||||
}
|
||||
|
||||
private Result UpdateLastResult(Result result)
|
||||
{
|
||||
if (!ResultFs.UsableSpaceNotEnough.Includes(result))
|
||||
_lastResult = result;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public Result ReadWithoutCacheAccessLog(out long bytesRead, long offset, Span<byte> destination,
|
||||
in ReadOption option)
|
||||
{
|
||||
return _file.Read(out bytesRead, offset, destination, in option);
|
||||
}
|
||||
|
||||
private Result ReadWithCacheAccessLog(out long bytesRead, long offset, Span<byte> destination,
|
||||
in ReadOption option, bool usePathCache, bool useDataCache)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Result Read(out long bytesRead, long offset, Span<byte> destination, in ReadOption option)
|
||||
{
|
||||
Unsafe.SkipInit(out bytesRead);
|
||||
|
||||
if (_lastResult.IsFailure())
|
||||
{
|
||||
// Todo: Access log
|
||||
return _lastResult;
|
||||
}
|
||||
|
||||
// ReSharper disable ConditionIsAlwaysTrueOrFalse
|
||||
bool usePathCache = _parentFileSystem is not null && _filePathHash.Data != 0;
|
||||
|
||||
// Todo: Call IsGlobalFileDataCacheEnabled
|
||||
#pragma warning disable 162
|
||||
bool useDataCache = false && _parentFileSystem is not null && _parentFileSystem.IsFileDataCacheAttachable();
|
||||
#pragma warning restore 162
|
||||
if (usePathCache || useDataCache)
|
||||
{
|
||||
return ReadWithCacheAccessLog(out bytesRead, offset, destination, in option, usePathCache,
|
||||
useDataCache);
|
||||
}
|
||||
else
|
||||
{
|
||||
return ReadWithoutCacheAccessLog(out bytesRead, offset, destination, in option);
|
||||
}
|
||||
// ReSharper restore ConditionIsAlwaysTrueOrFalse
|
||||
}
|
||||
|
||||
public Result Write(long offset, ReadOnlySpan<byte> source, in WriteOption option)
|
||||
{
|
||||
if (_lastResult.IsFailure())
|
||||
return _lastResult;
|
||||
|
||||
using ScopedSetter<WriteState> setter =
|
||||
ScopedSetter<WriteState>.MakeScopedSetter(ref _writeState, WriteState.Failed);
|
||||
|
||||
if (_filePathHash.Data != 0)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
else
|
||||
{
|
||||
Result rc = UpdateLastResult(_file.Write(offset, source, in option));
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
|
||||
setter.Set(option.HasFlushFlag() ? WriteState.None : WriteState.NeedsFlush);
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result Flush()
|
||||
{
|
||||
if (_lastResult.IsFailure())
|
||||
return _lastResult;
|
||||
|
||||
using ScopedSetter<WriteState> setter =
|
||||
ScopedSetter<WriteState>.MakeScopedSetter(ref _writeState, WriteState.Failed);
|
||||
|
||||
Result rc = UpdateLastResult(_file.Flush());
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
setter.Set(WriteState.None);
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result SetSize(long size)
|
||||
{
|
||||
if (_lastResult.IsFailure())
|
||||
return _lastResult;
|
||||
|
||||
WriteState oldWriteState = _writeState;
|
||||
using ScopedSetter<WriteState> setter =
|
||||
ScopedSetter<WriteState>.MakeScopedSetter(ref _writeState, WriteState.Failed);
|
||||
|
||||
Result rc = UpdateLastResult(_file.SetSize(size));
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (_filePathHash.Data != 0)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
setter.Set(oldWriteState);
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result GetSize(out long size)
|
||||
{
|
||||
Unsafe.SkipInit(out size);
|
||||
|
||||
if (_lastResult.IsFailure())
|
||||
return _lastResult;
|
||||
|
||||
return _file.GetSize(out size);
|
||||
}
|
||||
|
||||
public Result OperateRange(Span<byte> outBuffer, OperationId operationId, long offset, long size,
|
||||
ReadOnlySpan<byte> inBuffer)
|
||||
{
|
||||
return _file.OperateRange(outBuffer, operationId, offset, size, inBuffer);
|
||||
}
|
||||
}
|
||||
}
|
403
src/LibHac/Fs/Fsa/FileSystemAccessor.cs
Normal file
403
src/LibHac/Fs/Fsa/FileSystemAccessor.cs
Normal file
|
@ -0,0 +1,403 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using LibHac.Common;
|
||||
using LibHac.Diag;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.Os;
|
||||
using LibHac.Util;
|
||||
using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace LibHac.Fs.Impl
|
||||
{
|
||||
internal class FileSystemAccessor : IDisposable
|
||||
{
|
||||
private MountName _mountName;
|
||||
private IFileSystem _fileSystem;
|
||||
private LinkedList<FileAccessor> _openFiles;
|
||||
private LinkedList<DirectoryAccessor> _openDirectories;
|
||||
private SdkMutexType _openListLock;
|
||||
private ICommonMountNameGenerator _mountNameGenerator;
|
||||
private ISaveDataAttributeGetter _saveDataAttributeGetter;
|
||||
private bool _isAccessLogEnabled;
|
||||
private bool _isDataCacheAttachable;
|
||||
private bool _isPathCacheAttachable;
|
||||
private bool _isPathCacheAttached;
|
||||
private IMultiCommitTarget _multiCommitTarget;
|
||||
|
||||
public FileSystemAccessor(U8Span name, IMultiCommitTarget multiCommitTarget, IFileSystem fileSystem,
|
||||
ICommonMountNameGenerator mountNameGenerator, ISaveDataAttributeGetter saveAttributeGetter)
|
||||
{
|
||||
_fileSystem = fileSystem;
|
||||
_openFiles = new LinkedList<FileAccessor>();
|
||||
_openDirectories = new LinkedList<DirectoryAccessor>();
|
||||
_openListLock.Initialize();
|
||||
_mountNameGenerator = mountNameGenerator;
|
||||
_saveDataAttributeGetter = saveAttributeGetter;
|
||||
_multiCommitTarget = multiCommitTarget;
|
||||
|
||||
if (name.IsEmpty())
|
||||
Abort.DoAbort(ResultFs.InvalidMountName.Log());
|
||||
|
||||
if (StringUtils.GetLength(name, PathTool.MountNameLengthMax + 1) > PathTool.MountNameLengthMax)
|
||||
Abort.DoAbort(ResultFs.InvalidMountName.Log());
|
||||
|
||||
StringUtils.Copy(_mountName.Name.Slice(0, PathTool.MountNameLengthMax), name);
|
||||
_mountName.Name[PathTool.MountNameLengthMax] = 0;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposing) return;
|
||||
|
||||
using (ScopedLock.Lock(ref _openListLock))
|
||||
{
|
||||
Abort.DoAbortUnless(_openFiles.Count == 0, ResultFs.FileNotClosed.Log(),
|
||||
"All files must be closed before unmounting.");
|
||||
|
||||
Abort.DoAbortUnless(_openDirectories.Count == 0, ResultFs.DirectoryNotClosed.Log(),
|
||||
"All directories must be closed before unmounting.");
|
||||
|
||||
if (_isPathCacheAttached)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
_saveDataAttributeGetter?.Dispose();
|
||||
_saveDataAttributeGetter = null;
|
||||
|
||||
_mountNameGenerator?.Dispose();
|
||||
_mountNameGenerator = null;
|
||||
|
||||
_fileSystem?.Dispose();
|
||||
_fileSystem = null;
|
||||
}
|
||||
|
||||
private static void Remove<T>(LinkedList<T> list, T item)
|
||||
{
|
||||
LinkedListNode<T> node = list.Find(item);
|
||||
Abort.DoAbortUnless(node is not null, "Invalid file or directory object.");
|
||||
|
||||
list.Remove(node);
|
||||
}
|
||||
|
||||
private static Result CheckPath(U8Span mountName, U8Span path)
|
||||
{
|
||||
int mountNameLength = StringUtils.GetLength(mountName, PathTool.MountNameLengthMax);
|
||||
int pathLength = StringUtils.GetLength(path, PathTool.EntryNameLengthMax);
|
||||
|
||||
if (mountNameLength + 1 + pathLength > PathTool.EntryNameLengthMax)
|
||||
return ResultFs.TooLongPath.Log();
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
private static bool HasOpenWriteModeFiles(LinkedList<FileAccessor> list)
|
||||
{
|
||||
for (LinkedListNode<FileAccessor> file = list.First; file is not null; file = file.Next)
|
||||
{
|
||||
if (file.Value.GetOpenMode().HasFlag(OpenMode.Write))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void SetAccessLog(bool isEnabled) => _isAccessLogEnabled = isEnabled;
|
||||
public void SetFileDataCacheAttachable(bool isAttachable) => _isDataCacheAttachable = isAttachable;
|
||||
public void SetPathBasedFileDataCacheAttachable(bool isAttachable) => _isPathCacheAttachable = isAttachable;
|
||||
|
||||
public bool IsEnabledAccessLog() => _isAccessLogEnabled;
|
||||
public bool IsFileDataCacheAttachable() => _isDataCacheAttachable;
|
||||
public bool IsPathBasedFileDataCacheAttachable() => _isPathCacheAttachable;
|
||||
|
||||
public void AttachPathBasedFileDataCache()
|
||||
{
|
||||
if (_isPathCacheAttachable)
|
||||
_isPathCacheAttached = true;
|
||||
}
|
||||
|
||||
public Result CreateFile(U8Span path, long size, CreateFileOptions option)
|
||||
{
|
||||
Result rc = CheckPath(new U8Span(_mountName.Name), path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (_isPathCacheAttached)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = _fileSystem.CreateFile(path, size, option);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result DeleteFile(U8Span path)
|
||||
{
|
||||
Result rc = CheckPath(new U8Span(_mountName.Name), path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return _fileSystem.DeleteDirectory(path);
|
||||
}
|
||||
|
||||
public Result CreateDirectory(U8Span path)
|
||||
{
|
||||
Result rc = CheckPath(new U8Span(_mountName.Name), path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return _fileSystem.CreateDirectory(path);
|
||||
}
|
||||
|
||||
public Result DeleteDirectory(U8Span path)
|
||||
{
|
||||
Result rc = CheckPath(new U8Span(_mountName.Name), path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return _fileSystem.DeleteDirectory(path);
|
||||
}
|
||||
|
||||
public Result DeleteDirectoryRecursively(U8Span path)
|
||||
{
|
||||
Result rc = CheckPath(new U8Span(_mountName.Name), path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return _fileSystem.DeleteDirectoryRecursively(path);
|
||||
}
|
||||
|
||||
public Result CleanDirectoryRecursively(U8Span path)
|
||||
{
|
||||
Result rc = CheckPath(new U8Span(_mountName.Name), path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return _fileSystem.CleanDirectoryRecursively(path);
|
||||
}
|
||||
|
||||
public Result RenameFile(U8Span oldPath, U8Span newPath)
|
||||
{
|
||||
Result rc = CheckPath(new U8Span(_mountName.Name), oldPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = CheckPath(new U8Span(_mountName.Name), newPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (_isPathCacheAttached)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = _fileSystem.RenameFile(oldPath, newPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result RenameDirectory(U8Span oldPath, U8Span newPath)
|
||||
{
|
||||
Result rc = CheckPath(new U8Span(_mountName.Name), oldPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = CheckPath(new U8Span(_mountName.Name), newPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (_isPathCacheAttached)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = _fileSystem.RenameDirectory(oldPath, newPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result GetEntryType(out DirectoryEntryType entryType, U8Span path)
|
||||
{
|
||||
Unsafe.SkipInit(out entryType);
|
||||
|
||||
Result rc = CheckPath(new U8Span(_mountName.Name), path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return _fileSystem.GetEntryType(out entryType, path);
|
||||
}
|
||||
|
||||
public Result GetFreeSpaceSize(out long freeSpace, U8Span path)
|
||||
{
|
||||
Unsafe.SkipInit(out freeSpace);
|
||||
|
||||
Result rc = CheckPath(new U8Span(_mountName.Name), path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return _fileSystem.GetFreeSpaceSize(out freeSpace, path);
|
||||
}
|
||||
|
||||
public Result GetTotalSpaceSize(out long totalSpace, U8Span path)
|
||||
{
|
||||
Unsafe.SkipInit(out totalSpace);
|
||||
|
||||
Result rc = CheckPath(new U8Span(_mountName.Name), path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return _fileSystem.GetTotalSpaceSize(out totalSpace, path);
|
||||
}
|
||||
|
||||
public Result OpenFile(out FileAccessor file, U8Span path, OpenMode mode)
|
||||
{
|
||||
file = default;
|
||||
|
||||
Result rc = CheckPath(new U8Span(_mountName.Name), path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
IFile iFile = null;
|
||||
try
|
||||
{
|
||||
rc = _fileSystem.OpenFile(out iFile, path, mode);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
var fileAccessor = new FileAccessor(ref iFile, this, mode);
|
||||
|
||||
using (ScopedLock.Lock(ref _openListLock))
|
||||
{
|
||||
_openFiles.AddLast(fileAccessor);
|
||||
}
|
||||
|
||||
if (_isPathCacheAttached)
|
||||
{
|
||||
if (mode.HasFlag(OpenMode.AllowAppend))
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
file = Shared.Move(ref fileAccessor);
|
||||
return Result.Success;
|
||||
}
|
||||
finally
|
||||
{
|
||||
iFile?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public Result OpenDirectory(out DirectoryAccessor directory, U8Span path, OpenDirectoryMode mode)
|
||||
{
|
||||
directory = default;
|
||||
|
||||
Result rc = CheckPath(new U8Span(_mountName.Name), path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
IDirectory iDirectory = null;
|
||||
try
|
||||
{
|
||||
rc = _fileSystem.OpenDirectory(out iDirectory, path, mode);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
var directoryAccessor = new DirectoryAccessor(ref iDirectory, this);
|
||||
|
||||
using (ScopedLock.Lock(ref _openListLock))
|
||||
{
|
||||
_openDirectories.AddLast(directoryAccessor);
|
||||
}
|
||||
|
||||
directory = Shared.Move(ref directoryAccessor);
|
||||
return Result.Success;
|
||||
}
|
||||
finally
|
||||
{
|
||||
iDirectory?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public Result Commit()
|
||||
{
|
||||
using (ScopedLock.Lock(ref _openListLock))
|
||||
{
|
||||
DumpUnclosedAccessorList(OpenMode.Write, 0);
|
||||
|
||||
if (HasOpenWriteModeFiles(_openFiles))
|
||||
return ResultFs.WriteModeFileNotClosed.Log();
|
||||
}
|
||||
|
||||
return _fileSystem.Commit();
|
||||
}
|
||||
|
||||
public Result GetFileTimeStampRaw(out FileTimeStampRaw timeStamp, U8Span path)
|
||||
{
|
||||
return _fileSystem.GetFileTimeStampRaw(out timeStamp, path);
|
||||
}
|
||||
|
||||
public Result QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, QueryId queryId, U8Span path)
|
||||
{
|
||||
return _fileSystem.QueryEntry(outBuffer, inBuffer, queryId, path);
|
||||
}
|
||||
|
||||
public U8Span GetName()
|
||||
{
|
||||
return new U8Span(_mountName.Name);
|
||||
}
|
||||
|
||||
public Result GetCommonMountName(Span<byte> nameBuffer)
|
||||
{
|
||||
if (_mountNameGenerator is null)
|
||||
return ResultFs.PreconditionViolation.Log();
|
||||
|
||||
return _mountNameGenerator.GenerateCommonMountName(nameBuffer);
|
||||
}
|
||||
|
||||
public Result GetSaveDataAttribute(out SaveDataAttribute attribute)
|
||||
{
|
||||
Unsafe.SkipInit(out attribute);
|
||||
|
||||
if (_saveDataAttributeGetter is null)
|
||||
return ResultFs.PreconditionViolation.Log();
|
||||
|
||||
return _saveDataAttributeGetter.GetSaveDataAttribute(out attribute);
|
||||
}
|
||||
|
||||
public ReferenceCountedDisposable<IFileSystemSf> GetMultiCommitTarget()
|
||||
{
|
||||
return _multiCommitTarget?.GetMultiCommitTarget();
|
||||
}
|
||||
|
||||
public void PurgeFileDataCache(FileDataCacheAccessor cacheAccessor)
|
||||
{
|
||||
cacheAccessor.Purge(_fileSystem);
|
||||
}
|
||||
|
||||
public void NotifyCloseFile(FileAccessor file)
|
||||
{
|
||||
using ScopedLock<SdkMutexType> lk = ScopedLock.Lock(ref _openListLock);
|
||||
Remove(_openFiles, file);
|
||||
}
|
||||
|
||||
public void NotifyCloseDirectory(DirectoryAccessor directory)
|
||||
{
|
||||
using ScopedLock<SdkMutexType> lk = ScopedLock.Lock(ref _openListLock);
|
||||
Remove(_openDirectories, directory);
|
||||
}
|
||||
|
||||
private void DumpUnclosedAccessorList(OpenMode fileOpenModeMask, OpenDirectoryMode directoryOpenModeMask)
|
||||
{
|
||||
// Todo: Implement
|
||||
}
|
||||
}
|
||||
}
|
15
src/LibHac/Fs/Fsa/Registrar.cs
Normal file
15
src/LibHac/Fs/Fsa/Registrar.cs
Normal file
|
@ -0,0 +1,15 @@
|
|||
using System;
|
||||
|
||||
namespace LibHac.Fs.Fsa
|
||||
{
|
||||
public interface ICommonMountNameGenerator : IDisposable
|
||||
{
|
||||
Result GenerateCommonMountName(Span<byte> nameBuffer);
|
||||
}
|
||||
|
||||
public interface ISaveDataAttributeGetter : IDisposable
|
||||
{
|
||||
Result GetSaveDataAttribute(out SaveDataAttribute attribute);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public interface ICommonMountNameGenerator
|
||||
{
|
||||
Result GenerateCommonMountName(Span<byte> nameBuffer);
|
||||
}
|
||||
}
|
26
src/LibHac/Fs/Impl/FileDataCacheAccessor.cs
Normal file
26
src/LibHac/Fs/Impl/FileDataCacheAccessor.cs
Normal file
|
@ -0,0 +1,26 @@
|
|||
using System;
|
||||
using LibHac.Fs.Fsa;
|
||||
|
||||
namespace LibHac.Fs.Impl
|
||||
{
|
||||
internal class FileDataCacheAccessor
|
||||
{
|
||||
private IFileDataCache _cache;
|
||||
|
||||
public FileDataCacheAccessor(IFileDataCache cache)
|
||||
{
|
||||
_cache = cache;
|
||||
}
|
||||
|
||||
public Result Read(IFile file, out long bytesRead, long offset, Span<byte> destination, in ReadOption option,
|
||||
ref FileDataCacheAccessResult cacheAccessResult)
|
||||
{
|
||||
return _cache.Read(file, out bytesRead, offset, destination, in option, ref cacheAccessResult);
|
||||
}
|
||||
|
||||
public void Purge(IFileSystem fileSystem)
|
||||
{
|
||||
_cache.Purge(fileSystem);
|
||||
}
|
||||
}
|
||||
}
|
7
src/LibHac/Fs/Impl/FilePathHash.cs
Normal file
7
src/LibHac/Fs/Impl/FilePathHash.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
namespace LibHac.Fs.Impl
|
||||
{
|
||||
public struct FilePathHash
|
||||
{
|
||||
public int Data;
|
||||
}
|
||||
}
|
93
src/LibHac/Fs/Impl/FileRegion.cs
Normal file
93
src/LibHac/Fs/Impl/FileRegion.cs
Normal file
|
@ -0,0 +1,93 @@
|
|||
using System;
|
||||
using LibHac.Diag;
|
||||
using LibHac.Util;
|
||||
|
||||
namespace LibHac.Fs.Impl
|
||||
{
|
||||
public readonly struct FileRegion
|
||||
{
|
||||
public readonly long Offset;
|
||||
public readonly long Size;
|
||||
|
||||
public FileRegion(long offset, long size)
|
||||
{
|
||||
Offset = offset;
|
||||
Size = size;
|
||||
|
||||
Abort.DoAbortUnless(size >= 0);
|
||||
}
|
||||
|
||||
public long GetEndOffset()
|
||||
{
|
||||
return Offset + Size;
|
||||
}
|
||||
|
||||
public bool Includes(FileRegion other)
|
||||
{
|
||||
return Offset <= other.Offset && other.GetEndOffset() <= GetEndOffset();
|
||||
}
|
||||
|
||||
public bool Intersects(FileRegion other)
|
||||
{
|
||||
return HasIntersection(this, other);
|
||||
}
|
||||
|
||||
public FileRegion GetIntersection(FileRegion other)
|
||||
{
|
||||
return GetIntersection(this, other);
|
||||
}
|
||||
|
||||
public FileRegion ExpandAndAlign(uint alignment)
|
||||
{
|
||||
long alignedStartOffset = Alignment.AlignDownPow2(Offset, alignment);
|
||||
long alignedEndOffset = Alignment.AlignUpPow2(GetEndOffset(), alignment);
|
||||
long alignedSize = alignedEndOffset - alignedStartOffset;
|
||||
|
||||
return new FileRegion(alignedStartOffset, alignedSize);
|
||||
}
|
||||
|
||||
public FileRegion ShrinkAndAlign(uint alignment)
|
||||
{
|
||||
long alignedStartOffset = Alignment.AlignUpPow2(Offset, alignment);
|
||||
long alignedEndOffset = Alignment.AlignDownPow2(GetEndOffset(), alignment);
|
||||
long alignedSize = alignedEndOffset - alignedStartOffset;
|
||||
|
||||
return new FileRegion(alignedStartOffset, alignedSize);
|
||||
}
|
||||
|
||||
public FileRegion GetEndRegionWithSizeLimit(long size)
|
||||
{
|
||||
if (size >= Size)
|
||||
return this;
|
||||
|
||||
return new FileRegion(GetEndOffset() - size, size);
|
||||
}
|
||||
|
||||
public static bool HasIntersection(FileRegion region1, FileRegion region2)
|
||||
{
|
||||
return region1.GetEndOffset() >= region2.Offset &&
|
||||
region2.GetEndOffset() >= region1.Offset;
|
||||
}
|
||||
|
||||
public static FileRegion GetIntersection(FileRegion region1, FileRegion region2)
|
||||
{
|
||||
if (!region1.Intersects(region2))
|
||||
return new FileRegion();
|
||||
|
||||
long intersectionStartOffset = Math.Max(region1.Offset, region2.Offset);
|
||||
long intersectionEndOffset = Math.Min(region1.GetEndOffset(), region2.GetEndOffset());
|
||||
long intersectionSize = intersectionEndOffset - intersectionStartOffset;
|
||||
|
||||
return new FileRegion(intersectionStartOffset, intersectionSize);
|
||||
}
|
||||
|
||||
public static FileRegion GetInclusion(FileRegion region1, FileRegion region2)
|
||||
{
|
||||
long inclusionStartOffset = Math.Min(region1.Offset, region2.Offset);
|
||||
long inclusionEndOffset = Math.Max(region1.GetEndOffset(), region2.GetEndOffset());
|
||||
long inclusionSize = inclusionEndOffset - inclusionStartOffset;
|
||||
|
||||
return new FileRegion(inclusionStartOffset, inclusionSize);
|
||||
}
|
||||
}
|
||||
}
|
92
src/LibHac/Fs/Impl/IFileDataCache.cs
Normal file
92
src/LibHac/Fs/Impl/IFileDataCache.cs
Normal file
|
@ -0,0 +1,92 @@
|
|||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using LibHac.Common.FixedArrays;
|
||||
using LibHac.Diag;
|
||||
using LibHac.Fs.Fsa;
|
||||
|
||||
namespace LibHac.Fs.Impl
|
||||
{
|
||||
// ReSharper disable once InconsistentNaming
|
||||
internal abstract class IFileDataCache : IDisposable
|
||||
{
|
||||
public abstract void Dispose();
|
||||
|
||||
public abstract void Purge(IFileSystem fileSystem);
|
||||
|
||||
protected abstract Result DoRead(IFile file, out long bytesRead, long offset, Span<byte> destination,
|
||||
in ReadOption option, ref FileDataCacheAccessResult cacheAccessResult);
|
||||
|
||||
public Result Read(IFile file, out long bytesRead, long offset, Span<byte> destination, in ReadOption option,
|
||||
ref FileDataCacheAccessResult cacheAccessResult)
|
||||
{
|
||||
Unsafe.SkipInit(out bytesRead);
|
||||
|
||||
if (destination.Length == 0)
|
||||
{
|
||||
bytesRead = 0;
|
||||
cacheAccessResult.SetFileDataCacheUsed(true);
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
if (offset < 0)
|
||||
return ResultFs.OutOfRange.Log();
|
||||
|
||||
if (destination.Length < 0)
|
||||
return ResultFs.OutOfRange.Log();
|
||||
|
||||
if (long.MaxValue - offset < destination.Length)
|
||||
return ResultFs.OutOfRange.Log();
|
||||
|
||||
return DoRead(file, out bytesRead, offset, destination, in option, ref cacheAccessResult);
|
||||
}
|
||||
}
|
||||
|
||||
internal struct FileDataCacheAccessResult
|
||||
{
|
||||
private const int MaxRegionCount = 8;
|
||||
|
||||
private int _regionCount;
|
||||
private Array8<FileRegion> _regions;
|
||||
private bool _isFileDataCacheUsed;
|
||||
private bool _exceededMaxRegionCount;
|
||||
|
||||
public bool IsFileDataCacheUsed() => _isFileDataCacheUsed;
|
||||
public bool SetFileDataCacheUsed(bool useFileDataCache) => _isFileDataCacheUsed = useFileDataCache;
|
||||
|
||||
public int GetCacheFetchedRegionCount()
|
||||
{
|
||||
Assert.True(_isFileDataCacheUsed);
|
||||
return _regionCount;
|
||||
}
|
||||
|
||||
public bool ExceededMaxCacheFetchedRegionCount() => _exceededMaxRegionCount;
|
||||
|
||||
public FileRegion GetCacheFetchedRegion(int index)
|
||||
{
|
||||
Assert.True(IsFileDataCacheUsed());
|
||||
Assert.True(index >= 0);
|
||||
Assert.True(index < _regionCount);
|
||||
|
||||
return _regions[index];
|
||||
}
|
||||
|
||||
public void AddCacheFetchedRegion(FileRegion region)
|
||||
{
|
||||
_isFileDataCacheUsed = true;
|
||||
|
||||
if (region.Size == 0)
|
||||
return;
|
||||
|
||||
if (_regionCount >= MaxRegionCount)
|
||||
{
|
||||
_regions[MaxRegionCount - 1] = region;
|
||||
_exceededMaxRegionCount = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_regions[_regionCount] = region;
|
||||
_regionCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
29
src/LibHac/Fs/ScopedSetter.cs
Normal file
29
src/LibHac/Fs/ScopedSetter.cs
Normal file
|
@ -0,0 +1,29 @@
|
|||
using LibHac.Common;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public ref struct ScopedSetter<T>
|
||||
{
|
||||
private Ref<T> _ref;
|
||||
private T _value;
|
||||
|
||||
public ScopedSetter(ref T reference, T value)
|
||||
{
|
||||
_ref = new Ref<T>(ref reference);
|
||||
_value = value;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_ref.IsNull)
|
||||
_ref.Value = _value;
|
||||
}
|
||||
|
||||
public void Set(T value) => _value = value;
|
||||
|
||||
public static ScopedSetter<T> MakeScopedSetter(ref T reference, T value)
|
||||
{
|
||||
return new ScopedSetter<T>(ref reference, value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.Fs.Impl;
|
||||
using LibHac.FsSrv.Sf;
|
||||
using LibHac.FsSystem;
|
||||
|
@ -23,6 +24,8 @@ namespace LibHac.Fs.Shim
|
|||
PartitionId = partitionId;
|
||||
}
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
public Result GenerateCommonMountName(Span<byte> nameBuffer)
|
||||
{
|
||||
U8Span mountName = GetBisMountName(PartitionId);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.Fs.Impl;
|
||||
using LibHac.FsSrv.Sf;
|
||||
using LibHac.Util;
|
||||
|
@ -59,6 +60,8 @@ namespace LibHac.Fs.Shim
|
|||
StorageId = storageId;
|
||||
}
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
public Result GenerateCommonMountName(Span<byte> nameBuffer)
|
||||
{
|
||||
U8String mountName = GetContentStorageMountName(StorageId);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.Fs.Impl;
|
||||
using LibHac.FsSrv;
|
||||
using LibHac.FsSrv.Sf;
|
||||
|
@ -105,6 +106,8 @@ namespace LibHac.Fs.Shim
|
|||
PartitionId = partitionId;
|
||||
}
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
public Result GenerateCommonMountName(Span<byte> nameBuffer)
|
||||
{
|
||||
char letter = GetGameCardMountNameSuffix(PartitionId);
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.Fs.Impl;
|
||||
using LibHac.FsSrv.Sf;
|
||||
using LibHac.FsSystem;
|
||||
|
@ -38,6 +39,8 @@ namespace LibHac.Fs.Shim
|
|||
}
|
||||
}
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
public Result GenerateCommonMountName(Span<byte> nameBuffer)
|
||||
{
|
||||
int requiredNameBufferSize = StringUtils.GetLength(_path.Str, FsPath.MaxLength) + HostRootFileSystemPathLength;
|
||||
|
@ -56,6 +59,8 @@ namespace LibHac.Fs.Shim
|
|||
|
||||
private class HostRootCommonMountNameGenerator : ICommonMountNameGenerator
|
||||
{
|
||||
public void Dispose() { }
|
||||
|
||||
public Result GenerateCommonMountName(Span<byte> nameBuffer)
|
||||
{
|
||||
const int requiredNameBufferSize = HostRootFileSystemPathLength;
|
||||
|
|
|
@ -397,7 +397,7 @@ namespace LibHac.Fs.Shim
|
|||
|
||||
if (rc.IsFailure())
|
||||
{
|
||||
Abort.DoAbort();
|
||||
Abort.DoAbort(rc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue