-
Notifications
You must be signed in to change notification settings - Fork 395
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Speed up DirLoader.DirLoader on Windows.
New FileHelper.TryOpenFileRead that doesn't throw if the file doesn't exist. Also used it in a couple other spots.
- Loading branch information
Showing
4 changed files
with
77 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
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) | ||
{ | ||
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; | ||
} | ||
} |