Handle more errors in LocalFileSystem

This commit is contained in:
Alex Barney 2019-10-10 19:01:46 -05:00
parent 089b5b4f63
commit 54950c9b68
7 changed files with 211 additions and 181 deletions

View File

@ -0,0 +1,44 @@
using System.Diagnostics.CodeAnalysis;
using LibHac.Fs;
namespace LibHac.Common
{
[SuppressMessage("ReSharper", "InconsistentNaming")]
internal static class HResult
{
public const int ERROR_FILE_NOT_FOUND = unchecked((int)0x80070002);
public const int ERROR_PATH_NOT_FOUND = unchecked((int)0x80070003);
public const int ERROR_ACCESS_DENIED = unchecked((int)0x80070005);
public const int ERROR_SHARING_VIOLATION = unchecked((int)0x80070020);
public const int ERROR_HANDLE_EOF = unchecked((int)0x80070026);
public const int ERROR_HANDLE_DISK_FULL = unchecked((int)0x80070027);
public const int ERROR_FILE_EXISTS = unchecked((int)0x80070050);
public const int ERROR_DISK_FULL = unchecked((int)0x80070070);
public const int ERROR_INVALID_NAME = unchecked((int)0x8007007B);
public const int ERROR_DIR_NOT_EMPTY = unchecked((int)0x80070091);
public const int ERROR_ALREADY_EXISTS = unchecked((int)0x800700B7);
public const int ERROR_DIRECTORY = unchecked((int)0x8007010B);
public const int ERROR_SPACES_NOT_ENOUGH_DRIVES = unchecked((int)0x80E7000B);
public static Result HResultToHorizonResult(int hResult)
{
return hResult switch
{
ERROR_FILE_NOT_FOUND => ResultFs.PathNotFound,
ERROR_PATH_NOT_FOUND => ResultFs.PathNotFound,
ERROR_ACCESS_DENIED => ResultFs.TargetLocked,
ERROR_SHARING_VIOLATION => ResultFs.TargetLocked,
ERROR_HANDLE_EOF => ResultFs.ValueOutOfRange,
ERROR_HANDLE_DISK_FULL => ResultFs.InsufficientFreeSpace,
ERROR_FILE_EXISTS => ResultFs.PathAlreadyExists,
ERROR_DISK_FULL => ResultFs.InsufficientFreeSpace,
ERROR_INVALID_NAME => ResultFs.PathNotFound,
ERROR_DIR_NOT_EMPTY => ResultFs.DirectoryNotEmpty,
ERROR_ALREADY_EXISTS => ResultFs.PathAlreadyExists,
ERROR_DIRECTORY => ResultFs.PathNotFound,
ERROR_SPACES_NOT_ENOUGH_DRIVES => ResultFs.InsufficientFreeSpace,
_ => ResultFs.UnknownHostFileSystemError
};
}
}
}

View File

@ -5,6 +5,6 @@
Result CreateDirectory(string path, NxFileAttributes archiveAttribute);
Result GetFileAttributes(string path, out NxFileAttributes attributes);
Result SetFileAttributes(string path, NxFileAttributes attributes);
long GetFileSize(string path);
Result GetFileSize(out long fileSize, string path);
}
}

View File

@ -75,6 +75,10 @@
public static Result Result4812 => new Result(ModuleFs, 4812);
public static Result UnexpectedErrorInHostFileFlush => new Result(ModuleFs, 5307);
public static Result UnexpectedErrorInHostFileGetSize => new Result(ModuleFs, 5308);
public static Result UnknownHostFileSystemError => new Result(ModuleFs, 5309);
public static Result PreconditionViolation => new Result(ModuleFs, 6000);
public static Result InvalidArgument => new Result(ModuleFs, 6001);
public static Result InvalidPath => new Result(ModuleFs, 6002);

View File

@ -349,7 +349,8 @@ namespace LibHac.FsSystem
for (int i = 0; i < fileCount; i++)
{
size += BaseFileSystem.GetFileSize(GetSubFilePath(path, i));
BaseFileSystem.GetFileSize(out long fileSize, GetSubFilePath(path, i)).ThrowIfFailure();
size += fileSize;
}
return size;

View File

@ -9,33 +9,16 @@ namespace LibHac.FsSystem
{
public class LocalDirectory : IDirectory
{
private string LocalPath { get; }
private OpenDirectoryMode Mode { get; }
private DirectoryInfo DirInfo { get; }
private IEnumerator<FileSystemInfo> EntryEnumerator { get; }
public LocalDirectory(LocalFileSystem fs, string path, OpenDirectoryMode mode)
public LocalDirectory(IEnumerator<FileSystemInfo> entryEnumerator, DirectoryInfo dirInfo,
OpenDirectoryMode mode)
{
LocalPath = fs.ResolveLocalPath(path);
EntryEnumerator = entryEnumerator;
DirInfo = dirInfo;
Mode = mode;
try
{
DirInfo = new DirectoryInfo(LocalPath);
}
catch (Exception ex) when (ex is ArgumentNullException || ex is ArgumentException ||
ex is PathTooLongException)
{
ThrowHelper.ThrowResult(ResultFs.PathNotFound, ex);
throw;
}
if (!DirInfo.Exists)
{
ThrowHelper.ThrowResult(ResultFs.PathNotFound);
}
EntryEnumerator = DirInfo.EnumerateFileSystemInfos().GetEnumerator();
}
public Result Read(out long entriesRead, Span<DirectoryEntry> entryBuffer)

View File

@ -1,22 +1,29 @@
using System;
using System.IO;
using LibHac.Common;
using LibHac.Fs;
namespace LibHac.FsSystem
{
public class LocalFile : FileBase
{
private const int ErrorHandleDiskFull = unchecked((int)0x80070027);
private const int ErrorDiskFull = unchecked((int)0x80070070);
private FileStream Stream { get; }
private StreamFile File { get; }
private OpenMode Mode { get; }
public LocalFile(string path, OpenMode mode)
{
LocalFileSystem.OpenFileInternal(out FileStream stream, path, mode).ThrowIfFailure();
Mode = mode;
Stream = OpenFile(path, mode);
Stream = stream;
File = new StreamFile(Stream, mode);
}
public LocalFile(FileStream stream, OpenMode mode)
{
Mode = mode;
Stream = stream;
File = new StreamFile(Stream, mode);
}
@ -39,14 +46,29 @@ namespace LibHac.FsSystem
}
public override Result FlushImpl()
{
try
{
return File.Flush();
}
catch (Exception ex) when (ex.HResult < 0)
{
return ResultFs.UnexpectedErrorInHostFileFlush.Log();
}
}
public override Result GetSizeImpl(out long size)
{
try
{
return File.GetSize(out size);
}
catch (Exception ex) when (ex.HResult < 0)
{
size = default;
return ResultFs.UnexpectedErrorInHostFileGetSize.Log();
}
}
public override Result SetSizeImpl(long size)
{
@ -54,9 +76,9 @@ namespace LibHac.FsSystem
{
File.SetSize(size);
}
catch (IOException ex) when (ex.HResult == ErrorDiskFull || ex.HResult == ErrorHandleDiskFull)
catch (Exception ex) when (ex.HResult < 0)
{
return ResultFs.InsufficientFreeSpace.Log();
return HResult.HResultToHorizonResult(ex.HResult).Log();
}
return Result.Success;
@ -71,36 +93,5 @@ namespace LibHac.FsSystem
Stream?.Dispose();
}
private static FileAccess GetFileAccess(OpenMode mode)
{
// FileAccess and OpenMode have the same flags
return (FileAccess)(mode & OpenMode.ReadWrite);
}
private static FileShare GetFileShare(OpenMode mode)
{
return mode.HasFlag(OpenMode.Write) ? FileShare.Read : FileShare.ReadWrite;
}
private static FileStream OpenFile(string path, OpenMode mode)
{
try
{
return new FileStream(path, FileMode.Open, GetFileAccess(mode), GetFileShare(mode));
}
catch (Exception ex) when (ex is ArgumentNullException || ex is ArgumentException ||
ex is PathTooLongException || ex is DirectoryNotFoundException ||
ex is FileNotFoundException || ex is NotSupportedException)
{
ThrowHelper.ThrowResult(ResultFs.PathNotFound, ex);
throw;
}
catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException)
{
// todo: Should a HorizonResultException be thrown?
throw;
}
}
}
}

View File

@ -1,17 +1,13 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Security;
using LibHac.Common;
using LibHac.Fs;
namespace LibHac.FsSystem
{
public class LocalFileSystem : IAttributeFileSystem
{
private const int ErrorHandleDiskFull = unchecked((int)0x80070027);
private const int ErrorFileExists = unchecked((int)0x80070050);
private const int ErrorDiskFull = unchecked((int)0x80070070);
private const int ErrorDirNotEmpty = unchecked((int)0x80070091);
private string BasePath { get; }
/// <summary>
@ -36,9 +32,12 @@ namespace LibHac.FsSystem
public Result GetFileAttributes(string path, out NxFileAttributes attributes)
{
attributes = default;
string localPath = ResolveLocalPath(PathTools.Normalize(path));
FileInfo info = GetFileInfo(localPath);
Result rc = GetFileInfo(out FileInfo info, localPath);
if (rc.IsFailure()) return rc;
if (info.Attributes == (FileAttributes)(-1))
{
@ -54,7 +53,8 @@ namespace LibHac.FsSystem
{
string localPath = ResolveLocalPath(PathTools.Normalize(path));
FileInfo info = GetFileInfo(localPath);
Result rc = GetFileInfo(out FileInfo info, localPath);
if (rc.IsFailure()) return rc;
if (info.Attributes == (FileAttributes)(-1))
{
@ -76,12 +76,15 @@ namespace LibHac.FsSystem
return Result.Success;
}
public long GetFileSize(string path)
public Result GetFileSize(out long fileSize, string path)
{
fileSize = default;
string localPath = ResolveLocalPath(PathTools.Normalize(path));
FileInfo info = GetFileInfo(localPath);
return GetSizeInternal(info);
Result rc = GetFileInfo(out FileInfo info, localPath);
if (rc.IsFailure()) return rc;
return GetSizeInternal(out fileSize, info);
}
public Result CreateDirectory(string path)
@ -93,7 +96,8 @@ namespace LibHac.FsSystem
{
string localPath = ResolveLocalPath(PathTools.Normalize(path));
DirectoryInfo dir = GetDirInfo(localPath);
Result rc = GetDirInfo(out DirectoryInfo dir, localPath);
if (rc.IsFailure()) return rc;
if (dir.Exists)
{
@ -112,7 +116,8 @@ namespace LibHac.FsSystem
{
string localPath = ResolveLocalPath(PathTools.Normalize(path));
FileInfo file = GetFileInfo(localPath);
Result rc = GetFileInfo(out FileInfo file, localPath);
if (rc.IsFailure()) return rc;
if (file.Exists)
{
@ -124,7 +129,7 @@ namespace LibHac.FsSystem
return ResultFs.PathNotFound.Log();
}
Result rc = CreateFileInternal(out FileStream stream, file);
rc = CreateFileInternal(out FileStream stream, file);
using (stream)
{
@ -138,7 +143,8 @@ namespace LibHac.FsSystem
{
string localPath = ResolveLocalPath(PathTools.Normalize(path));
DirectoryInfo dir = GetDirInfo(localPath);
Result rc = GetDirInfo(out DirectoryInfo dir, localPath);
if (rc.IsFailure()) return rc;
return DeleteDirectoryInternal(dir, false);
}
@ -147,7 +153,8 @@ namespace LibHac.FsSystem
{
string localPath = ResolveLocalPath(PathTools.Normalize(path));
DirectoryInfo dir = GetDirInfo(localPath);
Result rc = GetDirInfo(out DirectoryInfo dir, localPath);
if (rc.IsFailure()) return rc;
return DeleteDirectoryInternal(dir, true);
}
@ -158,13 +165,19 @@ namespace LibHac.FsSystem
foreach (string file in Directory.EnumerateFiles(localPath))
{
Result rc = DeleteFileInternal(GetFileInfo(file));
Result rc = GetFileInfo(out FileInfo fileInfo, file);
if (rc.IsFailure()) return rc;
rc = DeleteFileInternal(fileInfo);
if (rc.IsFailure()) return rc;
}
foreach (string dir in Directory.EnumerateDirectories(localPath))
{
Result rc = DeleteDirectoryInternal(GetDirInfo(dir), true);
Result rc = GetDirInfo(out DirectoryInfo dirInfo, dir);
if (rc.IsFailure()) return rc;
rc = DeleteDirectoryInternal(dirInfo, true);
if (rc.IsFailure()) return rc;
}
@ -175,28 +188,37 @@ namespace LibHac.FsSystem
{
string localPath = ResolveLocalPath(PathTools.Normalize(path));
FileInfo file = GetFileInfo(localPath);
Result rc = GetFileInfo(out FileInfo file, localPath);
if (rc.IsFailure()) return rc;
return DeleteFileInternal(file);
}
public Result OpenDirectory(out IDirectory directory, string path, OpenDirectoryMode mode)
{
// Getting the local path is done in the LocalDirectory constructor
path = PathTools.Normalize(path);
directory = default;
string localPath = ResolveLocalPath(PathTools.Normalize(path));
Result rc = GetEntryType(out DirectoryEntryType entryType, path);
Result rc = GetDirInfo(out DirectoryInfo dirInfo, localPath);
if (rc.IsFailure()) return rc;
if (entryType == DirectoryEntryType.File)
if (!dirInfo.Attributes.HasFlag(FileAttributes.Directory))
{
return ResultFs.PathNotFound.Log();
}
directory = new LocalDirectory(this, path, mode);
try
{
IEnumerator<FileSystemInfo> entryEnumerator = dirInfo.EnumerateFileSystemInfos().GetEnumerator();
directory = new LocalDirectory(entryEnumerator, dirInfo, mode);
return Result.Success;
}
catch (Exception ex) when (ex.HResult < 0)
{
return HResult.HResultToHorizonResult(ex.HResult).Log();
}
}
public Result OpenFile(out IFile file, string path, OpenMode mode)
{
@ -211,7 +233,10 @@ namespace LibHac.FsSystem
return ResultFs.PathNotFound.Log();
}
file = new LocalFile(localPath, mode);
rc = OpenFileInternal(out FileStream fileStream, localPath, mode);
if (rc.IsFailure()) return rc;
file = new LocalFile(fileStream, mode);
return Result.Success;
}
@ -229,8 +254,11 @@ namespace LibHac.FsSystem
ThrowHelper.ThrowResult(ResultFs.DestinationIsSubPathOfSource);
}
DirectoryInfo srcDir = GetDirInfo(ResolveLocalPath(oldPath));
DirectoryInfo dstDir = GetDirInfo(ResolveLocalPath(newPath));
Result rc = GetDirInfo(out DirectoryInfo srcDir, ResolveLocalPath(oldPath));
if (rc.IsFailure()) return rc;
rc = GetDirInfo(out DirectoryInfo dstDir, ResolveLocalPath(newPath));
if (rc.IsFailure()) return rc;
return RenameDirInternal(srcDir, dstDir);
}
@ -243,17 +271,22 @@ namespace LibHac.FsSystem
// Official FS behavior is to do nothing in this case
if (srcLocalPath == dstLocalPath) return Result.Success;
FileInfo srcFile = GetFileInfo(srcLocalPath);
FileInfo dstFile = GetFileInfo(dstLocalPath);
Result rc = GetFileInfo(out FileInfo srcFile, srcLocalPath);
if (rc.IsFailure()) return rc;
rc = GetFileInfo(out FileInfo dstFile, dstLocalPath);
if (rc.IsFailure()) return rc;
return RenameFileInternal(srcFile, dstFile);
}
public Result GetEntryType(out DirectoryEntryType entryType, string path)
{
entryType = default;
string localPath = ResolveLocalPath(PathTools.Normalize(path));
DirectoryInfo dir = GetDirInfo(localPath);
Result rc = GetDirInfo(out DirectoryInfo dir, localPath);
if (rc.IsFailure()) return rc;
if (dir.Exists)
{
@ -261,7 +294,8 @@ namespace LibHac.FsSystem
return Result.Success;
}
FileInfo file = GetFileInfo(localPath);
rc = GetFileInfo(out FileInfo file, localPath);
if (rc.IsFailure()) return rc;
if (file.Exists)
{
@ -278,7 +312,10 @@ namespace LibHac.FsSystem
timeStamp = default;
string localPath = ResolveLocalPath(PathTools.Normalize(path));
if (!GetFileInfo(localPath).Exists) return ResultFs.PathNotFound.Log();
Result rc = GetFileInfo(out FileInfo file, localPath);
if (rc.IsFailure()) return rc;
if (!file.Exists) return ResultFs.PathNotFound.Log();
timeStamp.Created = new DateTimeOffset(File.GetCreationTime(localPath)).ToUnixTimeSeconds();
timeStamp.Accessed = new DateTimeOffset(File.GetLastAccessTime(localPath)).ToUnixTimeSeconds();
@ -309,21 +346,42 @@ namespace LibHac.FsSystem
return ResultFs.UnsupportedOperation.Log();
}
private static long GetSizeInternal(FileInfo file)
internal static FileAccess GetFileAccess(OpenMode mode)
{
// FileAccess and OpenMode have the same flags
return (FileAccess)(mode & OpenMode.ReadWrite);
}
internal static FileShare GetFileShare(OpenMode mode)
{
return mode.HasFlag(OpenMode.Write) ? FileShare.Read : FileShare.ReadWrite;
}
internal static Result OpenFileInternal(out FileStream stream, string path, OpenMode mode)
{
try
{
return file.Length;
stream = new FileStream(path, FileMode.Open, GetFileAccess(mode), GetFileShare(mode));
return Result.Success;
}
catch (FileNotFoundException ex)
catch (Exception ex) when (ex.HResult < 0)
{
ThrowHelper.ThrowResult(ResultFs.PathNotFound, ex);
throw;
stream = default;
return HResult.HResultToHorizonResult(ex.HResult).Log();
}
catch (Exception ex) when (ex is SecurityException || ex is UnauthorizedAccessException)
}
private static Result GetSizeInternal(out long fileSize, FileInfo file)
{
// todo: Should a HorizonResultException be thrown?
throw;
try
{
fileSize = file.Length;
return Result.Success;
}
catch (Exception ex) when (ex.HResult < 0)
{
fileSize = default;
return HResult.HResultToHorizonResult(ex.HResult).Log();
}
}
@ -335,22 +393,9 @@ namespace LibHac.FsSystem
{
file = new FileStream(fileInfo.FullName, FileMode.CreateNew, FileAccess.ReadWrite);
}
catch (DirectoryNotFoundException)
catch (Exception ex) when (ex.HResult < 0)
{
return ResultFs.PathNotFound.Log();
}
catch (IOException ex) when (ex.HResult == ErrorDiskFull || ex.HResult == ErrorHandleDiskFull)
{
return ResultFs.InsufficientFreeSpace.Log();
}
catch (IOException ex) when (ex.HResult == ErrorFileExists)
{
return ResultFs.PathAlreadyExists.Log();
}
catch (Exception ex) when (ex is SecurityException || ex is UnauthorizedAccessException)
{
// todo: What Result value should be returned?
throw;
return HResult.HResultToHorizonResult(ex.HResult).Log();
}
return Result.Success;
@ -362,9 +407,9 @@ namespace LibHac.FsSystem
{
stream.SetLength(size);
}
catch (IOException ex) when (ex.HResult == ErrorDiskFull || ex.HResult == ErrorHandleDiskFull)
catch (Exception ex) when (ex.HResult < 0)
{
return ResultFs.InsufficientFreeSpace.Log();
return HResult.HResultToHorizonResult(ex.HResult).Log();
}
return Result.Success;
@ -378,18 +423,9 @@ namespace LibHac.FsSystem
{
dir.Delete(recursive);
}
catch (DirectoryNotFoundException)
catch (Exception ex) when (ex.HResult < 0)
{
return ResultFs.PathNotFound.Log();
}
catch (IOException ex) when (ex.HResult == ErrorDirNotEmpty)
{
return ResultFs.DirectoryNotEmpty.Log();
}
catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException)
{
// todo: What Result value should be returned?
throw;
return HResult.HResultToHorizonResult(ex.HResult).Log();
}
EnsureDeleted(dir);
@ -405,18 +441,9 @@ namespace LibHac.FsSystem
{
file.Delete();
}
catch (DirectoryNotFoundException)
catch (Exception ex) when (ex.HResult < 0)
{
return ResultFs.PathNotFound.Log();
}
catch (IOException ex) when (ex.HResult == ErrorDirNotEmpty)
{
return ResultFs.DirectoryNotEmpty.Log();
}
catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException)
{
// todo: What Result value should be returned?
throw;
return HResult.HResultToHorizonResult(ex.HResult).Log();
}
EnsureDeleted(file);
@ -436,18 +463,9 @@ namespace LibHac.FsSystem
dir.Attributes |= FileAttributes.Archive;
}
}
catch (DirectoryNotFoundException)
catch (Exception ex) when (ex.HResult < 0)
{
return ResultFs.PathNotFound.Log();
}
catch (IOException ex) when (ex.HResult == ErrorDiskFull || ex.HResult == ErrorHandleDiskFull)
{
return ResultFs.InsufficientFreeSpace.Log();
}
catch (Exception ex) when (ex is SecurityException || ex is UnauthorizedAccessException)
{
// todo: What Result value should be returned?
throw;
return HResult.HResultToHorizonResult(ex.HResult).Log();
}
return Result.Success;
@ -462,14 +480,9 @@ namespace LibHac.FsSystem
{
source.MoveTo(dest.FullName);
}
catch (DirectoryNotFoundException)
catch (Exception ex) when (ex.HResult < 0)
{
return ResultFs.PathNotFound.Log();
}
catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException)
{
// todo: What Result value should be returned?
throw;
return HResult.HResultToHorizonResult(ex.HResult).Log();
}
return Result.Success;
@ -484,46 +497,40 @@ namespace LibHac.FsSystem
{
source.MoveTo(dest.FullName);
}
catch (DirectoryNotFoundException)
catch (Exception ex) when (ex.HResult < 0)
{
return ResultFs.PathNotFound.Log();
}
catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException)
{
// todo: What Result value should be returned?
throw;
return HResult.HResultToHorizonResult(ex.HResult).Log();
}
return Result.Success;
}
// GetFileInfo and GetDirInfo detect invalid paths
private static FileInfo GetFileInfo(string path)
private static Result GetFileInfo(out FileInfo fileInfo, string path)
{
try
{
return new FileInfo(path);
fileInfo = new FileInfo(path);
return Result.Success;
}
catch (Exception ex) when (ex is ArgumentNullException || ex is ArgumentException ||
ex is PathTooLongException)
catch (Exception ex) when (ex.HResult < 0)
{
ThrowHelper.ThrowResult(ResultFs.PathNotFound, ex);
throw;
fileInfo = default;
return HResult.HResultToHorizonResult(ex.HResult).Log();
}
}
private static DirectoryInfo GetDirInfo(string path)
private static Result GetDirInfo(out DirectoryInfo directoryInfo, string path)
{
try
{
return new DirectoryInfo(path);
directoryInfo = new DirectoryInfo(path);
return Result.Success;
}
catch (Exception ex) when (ex is ArgumentNullException || ex is ArgumentException ||
ex is PathTooLongException)
catch (Exception ex) when (ex.HResult < 0)
{
ThrowHelper.ThrowResult(ResultFs.PathNotFound, ex);
throw;
directoryInfo = default;
return HResult.HResultToHorizonResult(ex.HResult).Log();
}
}