From b51d4397e9d646ad47fa0fc86256547abfd700cb Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Fri, 21 Jun 2019 00:30:39 -0500 Subject: [PATCH] Add close and unmount methods. Add helpers for common fs tasks --- src/LibHac/Fs/Accessors/DirectoryHandle.cs | 2 +- src/LibHac/Fs/Accessors/FileHandle.cs | 2 +- src/LibHac/Fs/Accessors/FileSystemAccessor.cs | 11 +- .../{IAccessLogger.cs => IAccessLog.cs} | 2 +- src/LibHac/Fs/Accessors/MountTable.cs | 5 +- src/LibHac/Fs/FileSystemExtensions.cs | 17 +-- src/LibHac/Fs/FileSystemManager.cs | 64 ++++++++-- src/LibHac/Fs/FileSystemManagerUtils.cs | 113 ++++++++++++++++++ src/LibHac/Fs/PathTools.cs | 15 +++ src/LibHac/Horizon.cs | 5 +- 10 files changed, 200 insertions(+), 36 deletions(-) rename src/LibHac/Fs/Accessors/{IAccessLogger.cs => IAccessLog.cs} (86%) create mode 100644 src/LibHac/Fs/FileSystemManagerUtils.cs diff --git a/src/LibHac/Fs/Accessors/DirectoryHandle.cs b/src/LibHac/Fs/Accessors/DirectoryHandle.cs index 044509ae..d4804306 100644 --- a/src/LibHac/Fs/Accessors/DirectoryHandle.cs +++ b/src/LibHac/Fs/Accessors/DirectoryHandle.cs @@ -15,7 +15,7 @@ namespace LibHac.Fs.Accessors public void Dispose() { - Directory.Dispose(); + Directory.Parent.FsManager.CloseDirectory(this); } } } diff --git a/src/LibHac/Fs/Accessors/FileHandle.cs b/src/LibHac/Fs/Accessors/FileHandle.cs index be3042d2..da454f4d 100644 --- a/src/LibHac/Fs/Accessors/FileHandle.cs +++ b/src/LibHac/Fs/Accessors/FileHandle.cs @@ -15,7 +15,7 @@ namespace LibHac.Fs.Accessors public void Dispose() { - File.Dispose(); + File.Parent.FsManager.CloseFile(this); } } } diff --git a/src/LibHac/Fs/Accessors/FileSystemAccessor.cs b/src/LibHac/Fs/Accessors/FileSystemAccessor.cs index ba0ab22a..4276e6cb 100644 --- a/src/LibHac/Fs/Accessors/FileSystemAccessor.cs +++ b/src/LibHac/Fs/Accessors/FileSystemAccessor.cs @@ -11,6 +11,7 @@ namespace LibHac.Fs.Accessors public string Name { get; } private IFileSystem FileSystem { get; } + internal FileSystemManager FsManager { get; } private HashSet OpenFiles { get; } = new HashSet(); private HashSet OpenDirectories { get; } = new HashSet(); @@ -19,10 +20,11 @@ namespace LibHac.Fs.Accessors internal bool IsAccessLogEnabled { get; set; } - public FileSystemAccessor(string name, IFileSystem baseFileSystem) + public FileSystemAccessor(string name, IFileSystem baseFileSystem, FileSystemManager fsManager) { Name = name; FileSystem = baseFileSystem; + FsManager = fsManager; } public void CreateDirectory(string path) @@ -153,5 +155,12 @@ namespace LibHac.Fs.Accessors OpenDirectories.Remove(directory); } } + + internal void Close() + { + // Todo: Possibly check for open files and directories + // Nintendo checks for them in DumpUnclosedAccessorList in + // FileSystemAccessor's destructor, but doesn't do anything with it + } } } diff --git a/src/LibHac/Fs/Accessors/IAccessLogger.cs b/src/LibHac/Fs/Accessors/IAccessLog.cs similarity index 86% rename from src/LibHac/Fs/Accessors/IAccessLogger.cs rename to src/LibHac/Fs/Accessors/IAccessLog.cs index 2968435c..3ff9ed53 100644 --- a/src/LibHac/Fs/Accessors/IAccessLogger.cs +++ b/src/LibHac/Fs/Accessors/IAccessLog.cs @@ -3,7 +3,7 @@ using System.Runtime.CompilerServices; namespace LibHac.Fs.Accessors { - public interface IAccessLogger + public interface IAccessLog { void Log(TimeSpan startTime, TimeSpan endTime, int handleId, string message, [CallerMemberName] string caller = ""); } diff --git a/src/LibHac/Fs/Accessors/MountTable.cs b/src/LibHac/Fs/Accessors/MountTable.cs index c0c328d8..0a039240 100644 --- a/src/LibHac/Fs/Accessors/MountTable.cs +++ b/src/LibHac/Fs/Accessors/MountTable.cs @@ -45,11 +45,14 @@ namespace LibHac.Fs.Accessors { lock (_locker) { - if (!Table.Remove(name)) + if (!Table.TryGetValue(name, out FileSystemAccessor fsAccessor)) { return ResultFsMountNameNotFound; } + Table.Remove(name); + fsAccessor.Close(); + return ResultSuccess; } } diff --git a/src/LibHac/Fs/FileSystemExtensions.cs b/src/LibHac/Fs/FileSystemExtensions.cs index a6455648..f78d440d 100644 --- a/src/LibHac/Fs/FileSystemExtensions.cs +++ b/src/LibHac/Fs/FileSystemExtensions.cs @@ -3,10 +3,6 @@ using System.Buffers; using System.Collections.Generic; using System.IO; -#if !NETFRAMEWORK -using System.IO.Enumeration; -#endif - namespace LibHac.Fs { public static class FileSystemExtensions @@ -88,7 +84,7 @@ namespace LibHac.Fs foreach (DirectoryEntry entry in directory.Read()) { - if (MatchesPattern(searchPattern, entry.Name, ignoreCase)) + if (PathTools.MatchesPattern(searchPattern, entry.Name, ignoreCase)) { yield return entry; } @@ -156,17 +152,6 @@ namespace LibHac.Fs return count; } - public static bool MatchesPattern(string searchPattern, string name, bool ignoreCase) - { -#if NETFRAMEWORK - return Compatibility.FileSystemName.MatchesSimpleExpression(searchPattern.AsSpan(), - name.AsSpan(), ignoreCase); -#else - return FileSystemName.MatchesSimpleExpression(searchPattern.AsSpan(), - name.AsSpan(), ignoreCase); -#endif - } - public static NxFileAttributes ToNxAttributes(this FileAttributes attributes) { return (NxFileAttributes)(((int)attributes >> 4) & 3); diff --git a/src/LibHac/Fs/FileSystemManager.cs b/src/LibHac/Fs/FileSystemManager.cs index 5a5287c6..2c66cd0a 100644 --- a/src/LibHac/Fs/FileSystemManager.cs +++ b/src/LibHac/Fs/FileSystemManager.cs @@ -12,33 +12,57 @@ namespace LibHac.Fs { internal Horizon Os { get; } internal ITimeSpanGenerator Time { get; } - internal IAccessLogger Logger { get; } + private IAccessLog AccessLog { get; set; } internal MountTable MountTable { get; } = new MountTable(); - private bool AccessLogEnabled { get; set; } = true; + private bool AccessLogEnabled { get; set; } public FileSystemManager(Horizon os) { Os = os; } - public FileSystemManager(Horizon os, IAccessLogger logger, ITimeSpanGenerator timer) + public FileSystemManager(Horizon os, ITimeSpanGenerator timer) { Os = os; - Logger = logger; Time = timer; } public void Register(string mountName, IFileSystem fileSystem) { - var accessor = new FileSystemAccessor(mountName, fileSystem); + var accessor = new FileSystemAccessor(mountName, fileSystem, this); - MountTable.Mount(accessor); + MountTable.Mount(accessor).ThrowIfFailure(); accessor.IsAccessLogEnabled = IsEnabledAccessLog(); } + public void Unmount(string mountName) + { + MountTable.Find(mountName, out FileSystemAccessor fileSystem).ThrowIfFailure(); + + if (IsEnabledAccessLog() && fileSystem.IsAccessLogEnabled) + { + TimeSpan startTime = Time.GetCurrent(); + MountTable.Unmount(mountName); + TimeSpan endTime = Time.GetCurrent(); + + OutputAccessLog(startTime, endTime, $", name: \"{mountName}\""); + } + else + { + MountTable.Unmount(mountName); + } + } + + public void SetAccessLog(bool isEnabled, IAccessLog accessLog = null) + { + AccessLogEnabled = isEnabled; + + if (accessLog != null) AccessLog = accessLog; + } + public void CreateDirectory(string path) { FindFileSystem(path.AsSpan(), out FileSystemAccessor fileSystem, out ReadOnlySpan subPath) @@ -425,14 +449,14 @@ namespace LibHac.Fs if (IsEnabledAccessLog() && IsEnabledHandleAccessLog(handle)) { TimeSpan startTime = Time.GetCurrent(); - handle.Dispose(); + handle.File.Dispose(); TimeSpan endTime = Time.GetCurrent(); OutputAccessLog(startTime, endTime, handle, string.Empty); } else { - handle.Dispose(); + handle.File.Dispose(); } } @@ -459,6 +483,22 @@ namespace LibHac.Fs return handle.Directory.Read(); } + public void CloseDirectory(DirectoryHandle handle) + { + if (IsEnabledAccessLog() && IsEnabledHandleAccessLog(handle)) + { + TimeSpan startTime = Time.GetCurrent(); + handle.Directory.Dispose(); + TimeSpan endTime = Time.GetCurrent(); + + OutputAccessLog(startTime, endTime, handle, string.Empty); + } + else + { + handle.Directory.Dispose(); + } + } + internal Result FindFileSystem(ReadOnlySpan path, out FileSystemAccessor fileSystem, out ReadOnlySpan subPath) { fileSystem = default; @@ -510,7 +550,7 @@ namespace LibHac.Fs internal bool IsEnabledAccessLog() { - return AccessLogEnabled && Logger != null && Time != null; + return AccessLogEnabled && AccessLog != null && Time != null; } internal bool IsEnabledHandleAccessLog(FileHandle handle) @@ -525,17 +565,17 @@ namespace LibHac.Fs internal void OutputAccessLog(TimeSpan startTime, TimeSpan endTime, string message, [CallerMemberName] string caller = "") { - Logger.Log(startTime, endTime, 0, message, caller); + AccessLog.Log(startTime, endTime, 0, message, caller); } internal void OutputAccessLog(TimeSpan startTime, TimeSpan endTime, FileHandle handle, string message, [CallerMemberName] string caller = "") { - Logger.Log(startTime, endTime, handle.GetId(), message, caller); + AccessLog.Log(startTime, endTime, handle.GetId(), message, caller); } internal void OutputAccessLog(TimeSpan startTime, TimeSpan endTime, DirectoryHandle handle, string message, [CallerMemberName] string caller = "") { - Logger.Log(startTime, endTime, handle.GetId(), message, caller); + AccessLog.Log(startTime, endTime, handle.GetId(), message, caller); } } } diff --git a/src/LibHac/Fs/FileSystemManagerUtils.cs b/src/LibHac/Fs/FileSystemManagerUtils.cs new file mode 100644 index 00000000..704fdf07 --- /dev/null +++ b/src/LibHac/Fs/FileSystemManagerUtils.cs @@ -0,0 +1,113 @@ +using System; +using System.Buffers; +using System.Collections.Generic; +using LibHac.Fs.Accessors; + +namespace LibHac.Fs +{ + public static class FileSystemManagerUtils + { + public static void CopyDirectory(this FileSystemManager fs, string sourcePath, string destPath, + CreateFileOptions options = CreateFileOptions.None, IProgressReport logger = null) + { + using (DirectoryHandle sourceHandle = fs.OpenDirectory(sourcePath, OpenDirectoryMode.All)) + { + foreach (DirectoryEntry entry in fs.ReadDirectory(sourceHandle)) + { + string subSrcPath = PathTools.Normalize(PathTools.Combine(sourcePath, entry.Name)); + string subDstPath = PathTools.Normalize(PathTools.Combine(destPath, entry.Name)); + + if (entry.Type == DirectoryEntryType.Directory) + { + fs.CreateDirectory(subDstPath); + + fs.CopyDirectory(subSrcPath, subDstPath, options, logger); + } + + if (entry.Type == DirectoryEntryType.File) + { + logger?.LogMessage(subSrcPath); + fs.CreateFile(subDstPath, entry.Size, options); + + fs.CopyFile(subSrcPath, subDstPath, logger); + } + } + } + } + + public static void CopyFile(this FileSystemManager fs, string sourcePath, string destPath, IProgressReport logger = null) + { + using (FileHandle sourceHandle = fs.OpenFile(sourcePath, OpenMode.Read)) + using (FileHandle destHandle = fs.OpenFile(destPath, OpenMode.Write | OpenMode.Append)) + { + const int maxBufferSize = 0x10000; + + long fileSize = fs.GetFileSize(sourceHandle); + int bufferSize = (int)Math.Min(maxBufferSize, fileSize); + + logger?.SetTotal(fileSize); + + byte[] buffer = ArrayPool.Shared.Rent(bufferSize); + try + { + for (long offset = 0; offset < fileSize; offset += bufferSize) + { + int toRead = (int)Math.Min(fileSize - offset, bufferSize); + Span buf = buffer.AsSpan(0, toRead); + + fs.ReadFile(sourceHandle, buf, offset); + fs.WriteFile(destHandle, buf, offset); + + logger?.ReportAdd(toRead); + } + } + finally + { + ArrayPool.Shared.Return(buffer); + logger?.SetTotal(0); + } + + fs.FlushFile(destHandle); + } + } + + public static IEnumerable EnumerateEntries(this FileSystemManager fs, string path) + { + return fs.EnumerateEntries(path, "*"); + } + + public static IEnumerable EnumerateEntries(this FileSystemManager fs, string path, string searchPattern) + { + return fs.EnumerateEntries(path, searchPattern, SearchOptions.RecurseSubdirectories); + } + + public static IEnumerable EnumerateEntries(this FileSystemManager fs, string path, string searchPattern, SearchOptions searchOptions) + { + bool ignoreCase = searchOptions.HasFlag(SearchOptions.CaseInsensitive); + bool recurse = searchOptions.HasFlag(SearchOptions.RecurseSubdirectories); + + using (DirectoryHandle sourceHandle = fs.OpenDirectory(path, OpenDirectoryMode.All)) + { + foreach (DirectoryEntry entry in fs.ReadDirectory(sourceHandle)) + { + if (PathTools.MatchesPattern(searchPattern, entry.Name, ignoreCase)) + { + yield return entry; + } + + if (entry.Type != DirectoryEntryType.Directory || !recurse) continue; + + string subPath = PathTools.Normalize(PathTools.Combine(path, entry.Name)); + + IEnumerable subEntries = fs.EnumerateEntries(subPath, searchPattern, searchOptions); + + foreach (DirectoryEntry subEntry in subEntries) + { + subEntry.FullPath = PathTools.Combine(path, subEntry.Name); + yield return subEntry; + } + } + } + } + } +} diff --git a/src/LibHac/Fs/PathTools.cs b/src/LibHac/Fs/PathTools.cs index 129eb5b2..15cb3aaf 100644 --- a/src/LibHac/Fs/PathTools.cs +++ b/src/LibHac/Fs/PathTools.cs @@ -5,6 +5,10 @@ using System.Runtime.CompilerServices; using static LibHac.Results; using static LibHac.Fs.ResultsFs; +#if !NETFRAMEWORK +using System.IO.Enumeration; +#endif + namespace LibHac.Fs { public static class PathTools @@ -381,6 +385,17 @@ namespace LibHac.Fs return ResultFsInvalidMountName; } + public static bool MatchesPattern(string searchPattern, string name, bool ignoreCase) + { +#if NETFRAMEWORK + return Compatibility.FileSystemName.MatchesSimpleExpression(searchPattern.AsSpan(), + name.AsSpan(), ignoreCase); +#else + return FileSystemName.MatchesSimpleExpression(searchPattern.AsSpan(), + name.AsSpan(), ignoreCase); +#endif + } + private static bool IsValidMountNameChar(char c) { c |= (char)0x20; diff --git a/src/LibHac/Horizon.cs b/src/LibHac/Horizon.cs index d0e81d7e..6e55025d 100644 --- a/src/LibHac/Horizon.cs +++ b/src/LibHac/Horizon.cs @@ -1,5 +1,4 @@ using LibHac.Fs; -using LibHac.Fs.Accessors; namespace LibHac { @@ -14,11 +13,11 @@ namespace LibHac Fs = new FileSystemManager(this); } - public Horizon(IAccessLogger logger, ITimeSpanGenerator timer) + public Horizon(ITimeSpanGenerator timer) { Time = timer; - Fs = new FileSystemManager(this, logger, timer); + Fs = new FileSystemManager(this, timer); } } }