diff --git a/src/LibHac/Fs/Accessors/DirectoryAccessor.cs b/src/LibHac/Fs/Accessors/DirectoryAccessor.cs index 6715c7ee..e3b3141e 100644 --- a/src/LibHac/Fs/Accessors/DirectoryAccessor.cs +++ b/src/LibHac/Fs/Accessors/DirectoryAccessor.cs @@ -1,10 +1,11 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace LibHac.Fs.Accessors { - public class DirectoryAccessor + public class DirectoryAccessor : IDisposable { - private IDirectory Directory { get; } + private IDirectory Directory { get; set; } public FileSystemAccessor Parent { get; } @@ -16,12 +17,30 @@ namespace LibHac.Fs.Accessors public IEnumerable Read() { + CheckIfDisposed(); + return Directory.Read(); } public int GetEntryCount() { + CheckIfDisposed(); + return Directory.GetEntryCount(); } + + public void Dispose() + { + if (Directory == null) return; + + Parent?.NotifyCloseDirectory(this); + + Directory = null; + } + + private void CheckIfDisposed() + { + if (Directory == null) throw new ObjectDisposedException(null, "Cannot access closed directory."); + } } } diff --git a/src/LibHac/Fs/Accessors/DirectoryHandle.cs b/src/LibHac/Fs/Accessors/DirectoryHandle.cs new file mode 100644 index 00000000..798ec4b6 --- /dev/null +++ b/src/LibHac/Fs/Accessors/DirectoryHandle.cs @@ -0,0 +1,19 @@ +using System; + +namespace LibHac.Fs.Accessors +{ + public struct DirectoryHandle : IDisposable + { + internal readonly DirectoryAccessor Directory; + + internal DirectoryHandle(DirectoryAccessor directory) + { + Directory = directory; + } + + public void Dispose() + { + Directory.Dispose(); + } + } +} diff --git a/src/LibHac/Fs/Accessors/FileAccessor.cs b/src/LibHac/Fs/Accessors/FileAccessor.cs index b3df935b..a269f639 100644 --- a/src/LibHac/Fs/Accessors/FileAccessor.cs +++ b/src/LibHac/Fs/Accessors/FileAccessor.cs @@ -2,14 +2,17 @@ namespace LibHac.Fs.Accessors { - public class FileAccessor + public class FileAccessor : IFile { - private IFile File { get; } + private IFile File { get; set; } public FileSystemAccessor Parent { get; } public WriteState WriteState { get; private set; } public OpenMode OpenMode { get; } + // Todo: Consider removing Mode from interface because OpenMode is in FileAccessor + OpenMode IFile.Mode => OpenMode; + public FileAccessor(IFile baseFile, FileSystemAccessor parent, OpenMode mode) { File = baseFile; @@ -19,11 +22,15 @@ namespace LibHac.Fs.Accessors public int Read(Span destination, long offset, ReadOption options) { + CheckIfDisposed(); + return File.Read(destination, offset, options); } public void Write(ReadOnlySpan source, long offset, WriteOption options) { + CheckIfDisposed(); + if (source.Length == 0) { WriteState = (WriteState)(~options & WriteOption.Flush); @@ -38,6 +45,8 @@ namespace LibHac.Fs.Accessors public void Flush() { + CheckIfDisposed(); + File.Flush(); WriteState = WriteState.None; @@ -45,13 +54,40 @@ namespace LibHac.Fs.Accessors public long GetSize() { + CheckIfDisposed(); + return File.GetSize(); } public void SetSize(long size) { + CheckIfDisposed(); + File.SetSize(size); } + + public void Dispose() + { + if (File == null) return; + + if (WriteState == WriteState.Unflushed) + { + // Original FS code would return an error: + // ThrowHelper.ThrowResult(ResultsFs.ResultFsWriteStateUnflushed); + + Flush(); + } + + File.Dispose(); + Parent?.NotifyCloseFile(this); + + File = null; + } + + private void CheckIfDisposed() + { + if (File == null) throw new ObjectDisposedException(null, "Cannot access closed file."); + } } public enum WriteState diff --git a/src/LibHac/Fs/Accessors/FileHandle.cs b/src/LibHac/Fs/Accessors/FileHandle.cs new file mode 100644 index 00000000..b4b94c70 --- /dev/null +++ b/src/LibHac/Fs/Accessors/FileHandle.cs @@ -0,0 +1,19 @@ +using System; + +namespace LibHac.Fs.Accessors +{ + public struct FileHandle : IDisposable + { + internal readonly FileAccessor File; + + internal FileHandle(FileAccessor file) + { + File = file; + } + + public void Dispose() + { + File.Dispose(); + } + } +} diff --git a/src/LibHac/Fs/Accessors/FileSystemAccessor.cs b/src/LibHac/Fs/Accessors/FileSystemAccessor.cs index 6614870e..b0b65af3 100644 --- a/src/LibHac/Fs/Accessors/FileSystemAccessor.cs +++ b/src/LibHac/Fs/Accessors/FileSystemAccessor.cs @@ -11,6 +11,8 @@ namespace LibHac.Fs.Accessors public string Name { get; } private IFileSystem FileSystem { get; } + private IAccessLogger Logger { get; } + private ITimeSpanGenerator Timer { get; } private HashSet OpenFiles { get; } = new HashSet(); private HashSet OpenDirectories { get; } = new HashSet(); @@ -23,6 +25,14 @@ namespace LibHac.Fs.Accessors FileSystem = baseFileSystem; } + public FileSystemAccessor(string name, IFileSystem baseFileSystem, IAccessLogger logger, ITimeSpanGenerator timer) + { + Name = name; + FileSystem = baseFileSystem; + Logger = logger; + Timer = timer; + } + public void CreateDirectory(string path) { FileSystem.CreateDirectory(path); diff --git a/src/LibHac/Fs/FileSystemManager.cs b/src/LibHac/Fs/FileSystemManager.cs index b17a60b7..67ec628b 100644 --- a/src/LibHac/Fs/FileSystemManager.cs +++ b/src/LibHac/Fs/FileSystemManager.cs @@ -1,4 +1,8 @@ -using LibHac.Fs.Accessors; +using System; +using LibHac.Fs.Accessors; + +using static LibHac.Results; +using static LibHac.Fs.ResultsFs; namespace LibHac.Fs { @@ -12,5 +16,183 @@ namespace LibHac.Fs { Os = os; } + + public void Register(string mountName, IFileSystem fileSystem) + { + var accessor = new FileSystemAccessor(mountName, fileSystem); + + MountTable.Mount(accessor); + } + + public void CreateDirectory(string path) + { + throw new NotImplementedException(); + } + + public void CreateFile(string path, long size) + { + CreateFile(path, size, CreateFileOptions.None); + } + + public void CreateFile(string path, long size, CreateFileOptions options) + { + FindFileSystem(path.AsSpan(), out FileSystemAccessor fileSystem, out ReadOnlySpan subPath) + .ThrowIfFailure(); + + fileSystem.CreateFile(subPath.ToString(), size, options); + } + + public void DeleteDirectory(string path) + { + throw new NotImplementedException(); + } + + public void DeleteDirectoryRecursively(string path) + { + throw new NotImplementedException(); + } + + public void CleanDirectoryRecursively(string path) + { + throw new NotImplementedException(); + } + + public void DeleteFile(string path) + { + throw new NotImplementedException(); + } + + public void RenameDirectory(string oldPath, string newPath) + { + throw new NotImplementedException(); + } + + public void RenameFile(string oldPath, string newPath) + { + throw new NotImplementedException(); + } + + // How to report when entry isn't found? + public DirectoryEntryType GetEntryType(string path) + { + throw new NotImplementedException(); + } + + public FileHandle OpenFile(string path, OpenMode mode) + { + FindFileSystem(path.AsSpan(), out FileSystemAccessor fileSystem, out ReadOnlySpan subPath) + .ThrowIfFailure(); + + FileAccessor file = fileSystem.OpenFile(subPath.ToString(), mode); + + return new FileHandle(file); + } + + public DirectoryHandle OpenDirectory(string path, OpenDirectoryMode mode) + { + FindFileSystem(path.AsSpan(), out FileSystemAccessor fileSystem, out ReadOnlySpan subPath) + .ThrowIfFailure(); + + DirectoryAccessor dir = fileSystem.OpenDirectory(subPath.ToString(), mode); + + return new DirectoryHandle(dir); + } + + // ========================== + // Operations on file handles + // ========================== + public int ReadFile(FileHandle handle, Span destination, long offset) + { + return ReadFile(handle, destination, offset, ReadOption.None); + } + + public int ReadFile(FileHandle handle, Span destination, long offset, ReadOption option) + { + return handle.File.Read(destination, offset, option); + } + + public void WriteFile(FileHandle handle, ReadOnlySpan source, long offset) + { + WriteFile(handle, source, offset, WriteOption.None); + } + + public void WriteFile(FileHandle handle, ReadOnlySpan source, long offset, WriteOption option) + { + handle.File.Write(source, offset, option); + } + + public void FlushFile(FileHandle handle) + { + handle.File.Flush(); + } + + public long GetFileSize(FileHandle handle) + { + return handle.File.GetSize(); + } + + public void SetFileSize(FileHandle handle, long size) + { + handle.File.SetSize(size); + } + + public OpenMode GetFileOpenMode(FileHandle handle) + { + return handle.File.OpenMode; + } + + public void CloseFile(FileHandle handle) + { + handle.File.Dispose(); + } + + internal Result FindFileSystem(ReadOnlySpan path, out FileSystemAccessor fileSystem, out ReadOnlySpan subPath) + { + fileSystem = default; + + Result result = GetMountName(path, out ReadOnlySpan mountName, out subPath); + if (result.IsFailure()) return result; + + result = MountTable.Find(mountName.ToString(), out fileSystem); + if (result.IsFailure()) return result; + + return ResultSuccess; + } + + internal Result GetMountName(ReadOnlySpan path, out ReadOnlySpan mountName, out ReadOnlySpan subPath) + { + int mountLen = 0; + int maxMountLen = Math.Min(path.Length, PathTools.MountNameLength); + + for (int i = 0; i < maxMountLen; i++) + { + if (path[i] == PathTools.MountSeparator) + { + mountLen = i; + break; + } + } + + if (mountLen == 0) + { + mountName = default; + subPath = default; + + return ResultFsInvalidMountName; + } + + mountName = path.Slice(0, mountLen); + + if (mountLen + 2 < path.Length) + { + subPath = path.Slice(mountLen + 2); + } + else + { + subPath = default; + } + + return ResultSuccess; + } } } diff --git a/src/LibHac/Fs/PathTools.cs b/src/LibHac/Fs/PathTools.cs index a9780b64..45b2e26d 100644 --- a/src/LibHac/Fs/PathTools.cs +++ b/src/LibHac/Fs/PathTools.cs @@ -92,6 +92,10 @@ namespace LibHac.Fs sb.Append(c); break; + case NormalizeState.Normal: + sb.Append(c); + break; + case NormalizeState.Delimiter when IsDirectorySeparator(c): isNormalized = false; break; diff --git a/src/LibHac/Fs/ResultsFs.cs b/src/LibHac/Fs/ResultsFs.cs index 511d28af..0b18cfd7 100644 --- a/src/LibHac/Fs/ResultsFs.cs +++ b/src/LibHac/Fs/ResultsFs.cs @@ -6,6 +6,7 @@ public static Result ResultFsMountNameAlreadyExists => new Result(ModuleFs, 60); public static Result ResultFsInvalidMountName => new Result(ModuleFs, 6065); + public static Result ResultFsWriteStateUnflushed => new Result(ModuleFs, 6454); public static Result ResultFsWritableFileOpen => new Result(ModuleFs, 6457); public static Result ResultFsMountNameNotFound => new Result(ModuleFs, 6905); } diff --git a/src/LibHac/Result.cs b/src/LibHac/Result.cs index a9bf220d..79121a7e 100644 --- a/src/LibHac/Result.cs +++ b/src/LibHac/Result.cs @@ -23,6 +23,14 @@ namespace LibHac public bool IsSuccess() => Value == 0; public bool IsFailure() => Value != 0; + + public void ThrowIfFailure() + { + if (IsFailure()) + { + ThrowHelper.ThrowResult(this); + } + } } public static class Results diff --git a/tests/LibHac.Tests/PathToolsTests.cs b/tests/LibHac.Tests/PathToolsTests.cs index 9b4961b4..1b95bbd8 100644 --- a/tests/LibHac.Tests/PathToolsTests.cs +++ b/tests/LibHac.Tests/PathToolsTests.cs @@ -19,10 +19,10 @@ namespace LibHac.Tests new object[] {"/a/../../../a/b/c", "/a/b/c"}, new object[] {"//a/b//.//c", "/a/b/c"}, new object[] {"/../a/b/c/.", "/a/b/c"}, - new object[] {"/./a/b/c/.", "/a/b/c"}, + new object[] {"/./aaa/bbb/ccc/.", "/aaa/bbb/ccc"}, new object[] {"/a/b/c/", "/a/b/c/"}, - new object[] {"/a/./b/../c/", "/a/c/"}, + new object[] {"/aa/./bb/../cc/", "/aa/cc/"}, new object[] {"/./b/../c/", "/c/"}, new object[] {"/a/../../../", "/"}, new object[] {"//a/b//.//c/", "/a/b/c/"}, @@ -46,6 +46,7 @@ namespace LibHac.Tests new object[] {"abc:/", "abc:/"}, new object[] {"abc://a/b//.//c", "abc:/a/b/c"}, new object[] {"abc:/././/././a/b//.//c", "abc:/a/b/c"}, + new object[] {"mount:/d./aa", "mount:/d./aa"}, }; public static object[][] SubPathTestItems =