mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
95 lines
3.1 KiB
C#
95 lines
3.1 KiB
C#
using System;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.IO;
|
|
using System.Runtime.InteropServices;
|
|
using Microsoft.Win32.SafeHandles;
|
|
using TerraFX.Interop.Windows;
|
|
|
|
namespace Robust.Shared.Utility;
|
|
|
|
internal static class FileHelper
|
|
{
|
|
/// <summary>
|
|
/// Try to open a file for reading. If the file does not exist, the operation fails without exception.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This API is not atomic and can thus be vulnerable to TOCTOU attacks. Don't use it if that's relevant.
|
|
/// </remarks>
|
|
/// <param name="path">The path to try to open.</param>
|
|
/// <param name="stream">The resulting file stream.</param>
|
|
/// <returns>True if the file existed and was opened.</returns>
|
|
public static bool TryOpenFileRead(string path, [NotNullWhen(true)] out FileStream? stream)
|
|
{
|
|
// On Windows, the separate File.Exists() call alone adds a ton of weight.
|
|
// The alternative however (opening the file and catching the error) is extremely slow because of .NET exceptions.
|
|
// So we manually call the windows API and make the file handle from that. Problem solved!
|
|
if (OperatingSystem.IsWindows())
|
|
return TryGetFileWindows(path, out stream);
|
|
|
|
if (!File.Exists(path))
|
|
{
|
|
stream = null;
|
|
return false;
|
|
}
|
|
|
|
stream = File.OpenRead(path);
|
|
return true;
|
|
}
|
|
|
|
private static unsafe bool TryGetFileWindows(string path, [NotNullWhen(true)] out FileStream? stream)
|
|
{
|
|
if (path.EndsWith("\\"))
|
|
{
|
|
stream = null;
|
|
return false;
|
|
}
|
|
|
|
try
|
|
{
|
|
HANDLE file;
|
|
fixed (char* pPath = path)
|
|
{
|
|
file = Windows.CreateFileW(
|
|
(ushort*)pPath,
|
|
Windows.GENERIC_READ,
|
|
FILE.FILE_SHARE_READ,
|
|
null,
|
|
OPEN.OPEN_EXISTING,
|
|
FILE.FILE_ATTRIBUTE_NORMAL,
|
|
HANDLE.NULL);
|
|
}
|
|
|
|
if (file == HANDLE.INVALID_VALUE)
|
|
{
|
|
var lastError = Marshal.GetLastWin32Error();
|
|
if (lastError is ERROR.ERROR_FILE_NOT_FOUND or ERROR.ERROR_PATH_NOT_FOUND)
|
|
{
|
|
stream = null;
|
|
return false;
|
|
}
|
|
|
|
Marshal.ThrowExceptionForHR(Windows.HRESULT_FROM_WIN32(lastError));
|
|
}
|
|
|
|
var sf = new SafeFileHandle(file, ownsHandle: true);
|
|
stream = new FileStream(sf, FileAccess.Read);
|
|
return true;
|
|
|
|
}
|
|
catch (UnauthorizedAccessException)
|
|
{
|
|
// UnauthorizedAccessException aka this is a folder not a file because of course that is what that means.
|
|
// Who the fuck though this was the right way of handling that? This should clearly just be a
|
|
// ERROR_FILE_NOT_FOUND or some other result like that,
|
|
// https://github.com/dotnet/runtime/issues/70275
|
|
if (Directory.Exists(path))
|
|
{
|
|
stream = null;
|
|
return false;
|
|
}
|
|
|
|
throw;
|
|
}
|
|
}
|
|
}
|