Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit eb0d438

Browse files
authored
Fast file enumeration for Windows (#25426)
* Fast file enumeration for Windows Implements an optimized, low allocation replacement for file enumeration on Windows. In preliminary tests this improves performance at least two fold, particularly for recursive searches. Removes aggressive filter validation. Copies and cleans up filename matching code from FileSystemWatcher. Add length setter for ValueStringBuilder. Remove Unix .. check for enumerable.
1 parent 991fb25 commit eb0d438

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+2209
-900
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
internal partial class Interop
6+
{
7+
/// <summary>
8+
/// Blittable version of Windows BOOLEAN type. It is convenient in situations where
9+
/// manual marshalling is required, or to avoid overhead of regular bool marshalling.
10+
/// </summary>
11+
/// <remarks>
12+
/// Some Windows APIs return arbitrary integer values although the return type is defined
13+
/// as BOOLEAN. It is best to never compare BOOLEAN to TRUE. Always use bResult != BOOLEAN.FALSE
14+
/// or bResult == BOOLEAN.FALSE .
15+
/// </remarks>
16+
internal enum BOOLEAN : byte
17+
{
18+
FALSE = 0,
19+
TRUE = 1,
20+
}
21+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
7+
internal partial class Interop
8+
{
9+
/// <summary>
10+
/// 100-nanosecond intervals (ticks) since January 1, 1601 (UTC).
11+
/// </summary>
12+
/// <remarks>
13+
/// For NT times that are defined as longs (LARGE_INTEGER, etc.).
14+
/// Do NOT use for FILETIME unless you are POSITIVE it will fall on an
15+
/// 8 byte boundary.
16+
/// </remarks>
17+
internal struct LongFileTime
18+
{
19+
#pragma warning disable CS0649
20+
/// <summary>
21+
/// 100-nanosecond intervals (ticks) since January 1, 1601 (UTC).
22+
/// </summary>
23+
internal long TicksSince1601;
24+
#pragma warning restore CS0649
25+
26+
internal DateTime ToDateTimeUtc() => DateTime.FromFileTimeUtc(TicksSince1601);
27+
}
28+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.IO;
7+
using System.Runtime.InteropServices;
8+
9+
internal partial class Interop
10+
{
11+
internal partial class NtDll
12+
{
13+
/// <summary>
14+
/// <a href="https://msdn.microsoft.com/en-us/library/windows/hardware/ff540289.aspx">FILE_FULL_DIR_INFORMATION</a> structure.
15+
/// Used with GetFileInformationByHandleEx and FileIdBothDirectoryInfo/RestartInfo as well as NtQueryFileInformation.
16+
/// Equivalent to <a href="https://msdn.microsoft.com/en-us/library/windows/desktop/hh447298.aspx">FILE_FULL_DIR_INFO</a> structure.
17+
/// </summary>
18+
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
19+
public unsafe struct FILE_FULL_DIR_INFORMATION
20+
{
21+
/// <summary>
22+
/// Offset in bytes of the next entry, if any.
23+
/// </summary>
24+
public uint NextEntryOffset;
25+
26+
/// <summary>
27+
/// Byte offset within the parent directory, undefined for NTFS.
28+
/// </summary>
29+
public uint FileIndex;
30+
public LongFileTime CreationTime;
31+
public LongFileTime LastAccessTime;
32+
public LongFileTime LastWriteTime;
33+
public LongFileTime ChangeTime;
34+
public long EndOfFile;
35+
public long AllocationSize;
36+
37+
/// <summary>
38+
/// File attributes.
39+
/// </summary>
40+
/// <remarks>
41+
/// Note that MSDN documentation isn't correct for this- it can return
42+
/// any FILE_ATTRIBUTE that is currently set on the file, not just the
43+
/// ones documented.
44+
/// </remarks>
45+
public FileAttributes FileAttributes;
46+
47+
/// <summary>
48+
/// The length of the file name in bytes (without null).
49+
/// </summary>
50+
public uint FileNameLength;
51+
52+
/// <summary>
53+
/// The extended attribute size OR the reparse tag if a reparse point.
54+
/// </summary>
55+
public uint EaSize;
56+
57+
private char _fileName;
58+
public ReadOnlySpan<char> FileName { get { fixed (char* c = &_fileName) { return new ReadOnlySpan<char>(c, (int)FileNameLength / sizeof(char)); } } }
59+
60+
/// <summary>
61+
/// Gets the next info pointer or null if there are no more.
62+
/// </summary>
63+
public unsafe static FILE_FULL_DIR_INFORMATION* GetNextInfo(FILE_FULL_DIR_INFORMATION* info)
64+
{
65+
uint nextOffset = (*info).NextEntryOffset;
66+
if (nextOffset == 0)
67+
return null;
68+
69+
return (FILE_FULL_DIR_INFORMATION*)((byte*)info + nextOffset);
70+
}
71+
}
72+
}
73+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.Runtime.InteropServices;
7+
8+
internal partial class Interop
9+
{
10+
internal partial class NtDll
11+
{
12+
// https://msdn.microsoft.com/en-us/library/windows/hardware/ff728840.aspx
13+
public enum FILE_INFORMATION_CLASS : uint
14+
{
15+
FileDirectoryInformation = 1,
16+
FileFullDirectoryInformation = 2,
17+
FileBothDirectoryInformation = 3,
18+
FileBasicInformation = 4,
19+
FileStandardInformation = 5,
20+
FileInternalInformation = 6,
21+
FileEaInformation = 7,
22+
FileAccessInformation = 8,
23+
FileNameInformation = 9,
24+
FileRenameInformation = 10,
25+
FileLinkInformation = 11,
26+
FileNamesInformation = 12,
27+
FileDispositionInformation = 13,
28+
FilePositionInformation = 14,
29+
FileFullEaInformation = 15,
30+
FileModeInformation = 16,
31+
FileAlignmentInformation = 17,
32+
FileAllInformation = 18,
33+
FileAllocationInformation = 19,
34+
FileEndOfFileInformation = 20,
35+
FileAlternateNameInformation = 21,
36+
FileStreamInformation = 22,
37+
FilePipeInformation = 23,
38+
FilePipeLocalInformation = 24,
39+
FilePipeRemoteInformation = 25,
40+
FileMailslotQueryInformation = 26,
41+
FileMailslotSetInformation = 27,
42+
FileCompressionInformation = 28,
43+
FileObjectIdInformation = 29,
44+
FileCompletionInformation = 30,
45+
FileMoveClusterInformation = 31,
46+
FileQuotaInformation = 32,
47+
FileReparsePointInformation = 33,
48+
FileNetworkOpenInformation = 34,
49+
FileAttributeTagInformation = 35,
50+
FileTrackingInformation = 36,
51+
FileIdBothDirectoryInformation = 37,
52+
FileIdFullDirectoryInformation = 38,
53+
FileValidDataLengthInformation = 39,
54+
FileShortNameInformation = 40,
55+
FileIoCompletionNotificationInformation = 41,
56+
FileIoStatusBlockRangeInformation = 42,
57+
FileIoPriorityHintInformation = 43,
58+
FileSfioReserveInformation = 44,
59+
FileSfioVolumeInformation = 45,
60+
FileHardLinkInformation = 46,
61+
FileProcessIdsUsingFileInformation = 47,
62+
FileNormalizedNameInformation = 48,
63+
FileNetworkPhysicalNameInformation = 49,
64+
FileIdGlobalTxDirectoryInformation = 50,
65+
FileIsRemoteDeviceInformation = 51,
66+
FileUnusedInformation = 52,
67+
FileNumaNodeInformation = 53,
68+
FileStandardLinkInformation = 54,
69+
FileRemoteProtocolInformation = 55,
70+
FileRenameInformationBypassAccessCheck = 56,
71+
FileLinkInformationBypassAccessCheck = 57,
72+
FileVolumeNameInformation = 58,
73+
FileIdInformation = 59,
74+
FileIdExtdDirectoryInformation = 60,
75+
FileReplaceCompletionInformation = 61,
76+
FileHardLinkFullIdInformation = 62,
77+
FileIdExtdBothDirectoryInformation = 63,
78+
FileDispositionInformationEx = 64,
79+
FileRenameInformationEx = 65,
80+
FileRenameInformationExBypassAccessCheck = 66,
81+
FileDesiredStorageClassInformation = 67,
82+
FileStatInformation = 68
83+
}
84+
}
85+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.Runtime.InteropServices;
7+
8+
internal partial class Interop
9+
{
10+
internal partial class NtDll
11+
{
12+
// https://msdn.microsoft.com/en-us/library/windows/hardware/ff550671.aspx
13+
[StructLayout(LayoutKind.Sequential)]
14+
public struct IO_STATUS_BLOCK
15+
{
16+
/// <summary>
17+
/// Status
18+
/// </summary>
19+
public IO_STATUS Status;
20+
21+
/// <summary>
22+
/// Request dependent value.
23+
/// </summary>
24+
public IntPtr Information;
25+
26+
// This isn't an actual Windows type, it is a union within IO_STATUS_BLOCK. We *have* to separate it out as
27+
// the size of IntPtr varies by architecture and we can't specify the size at compile time to offset the
28+
// Information pointer in the status block.
29+
[StructLayout(LayoutKind.Explicit)]
30+
public struct IO_STATUS
31+
{
32+
/// <summary>
33+
/// The completion status, either STATUS_SUCCESS if the operation was completed successfully or
34+
/// some other informational, warning, or error status.
35+
/// </summary>
36+
[FieldOffset(0)]
37+
public uint Status;
38+
39+
/// <summary>
40+
/// Reserved for internal use.
41+
/// </summary>
42+
[FieldOffset(0)]
43+
public IntPtr Pointer;
44+
}
45+
}
46+
}
47+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.Runtime.InteropServices;
7+
8+
internal partial class Interop
9+
{
10+
internal partial class NtDll
11+
{
12+
// https://msdn.microsoft.com/en-us/library/windows/hardware/ff556633.aspx
13+
// https://msdn.microsoft.com/en-us/library/windows/hardware/ff567047.aspx
14+
[DllImport(Libraries.NtDll, CharSet = CharSet.Unicode, ExactSpelling = true)]
15+
public unsafe static extern int NtQueryDirectoryFile(
16+
IntPtr FileHandle,
17+
IntPtr Event,
18+
IntPtr ApcRoutine,
19+
IntPtr ApcContext,
20+
out IO_STATUS_BLOCK IoStatusBlock,
21+
byte[] FileInformation,
22+
uint Length,
23+
FILE_INFORMATION_CLASS FileInformationClass,
24+
BOOLEAN ReturnSingleEntry,
25+
UNICODE_STRING* FileName,
26+
BOOLEAN RestartScan);
27+
}
28+
}

src/Common/src/Interop/Windows/NtDll/Interop.NtStatus.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ internal class StatusOptions
99
// Error codes from ntstatus.h
1010
internal const uint STATUS_SUCCESS = 0x00000000;
1111
internal const uint STATUS_SOME_NOT_MAPPED = 0x00000107;
12+
internal const uint STATUS_NO_MORE_FILES = 0x80000006;
1213
internal const uint STATUS_INVALID_PARAMETER = 0xC000000D;
1314
internal const uint STATUS_NO_MEMORY = 0xC0000017;
1415
internal const uint STATUS_OBJECT_NAME_NOT_FOUND = 0xC0000034;
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.Runtime.InteropServices;
7+
8+
internal partial class Interop
9+
{
10+
internal partial class NtDll
11+
{
12+
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms680600(v=vs.85).aspx
13+
[DllImport(Libraries.NtDll, ExactSpelling = true)]
14+
public unsafe static extern uint RtlNtStatusToDosError(
15+
int Status);
16+
}
17+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System.Runtime.InteropServices;
6+
7+
internal partial class Interop
8+
{
9+
internal partial class Kernel32
10+
{
11+
// https://msdn.microsoft.com/en-us/library/windows/desktop/dd317762.aspx
12+
[DllImport(Libraries.Kernel32, CharSet = CharSet.Unicode, SetLastError = true, ExactSpelling = true)]
13+
public unsafe static extern int CompareStringOrdinal(
14+
ref char lpString1,
15+
int cchCount1,
16+
ref char lpString2,
17+
int cchCount2,
18+
bool bIgnoreCase);
19+
}
20+
}

src/Common/src/Interop/Windows/kernel32/Interop.CreateFile.cs

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,12 @@ internal partial class Interop
1111
{
1212
internal partial class Kernel32
1313
{
14+
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa363858.aspx
1415
/// <summary>
1516
/// WARNING: The private methods do not implicitly handle long paths. Use CreateFile.
1617
/// </summary>
1718
[DllImport(Libraries.Kernel32, EntryPoint = "CreateFileW", SetLastError = true, CharSet = CharSet.Unicode, BestFitMapping = false, ExactSpelling = true)]
18-
private unsafe static extern SafeFileHandle CreateFilePrivate(
19+
private unsafe static extern IntPtr CreateFilePrivate(
1920
string lpFileName,
2021
int dwDesiredAccess,
2122
FileShare dwShareMode,
@@ -36,7 +37,16 @@ internal unsafe static SafeFileHandle CreateFile(
3637
lpFileName = PathInternal.EnsureExtendedPrefixOverMaxPath(lpFileName);
3738
fixed (SECURITY_ATTRIBUTES* sa = &securityAttrs)
3839
{
39-
return CreateFilePrivate(lpFileName, dwDesiredAccess, dwShareMode, sa, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
40+
IntPtr handle = CreateFilePrivate(lpFileName, dwDesiredAccess, dwShareMode, sa, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
41+
try
42+
{
43+
return new SafeFileHandle(handle, ownsHandle: true);
44+
}
45+
catch
46+
{
47+
CloseHandle(handle);
48+
throw;
49+
}
4050
}
4151
}
4252

@@ -46,6 +56,25 @@ internal unsafe static SafeFileHandle CreateFile(
4656
FileShare dwShareMode,
4757
FileMode dwCreationDisposition,
4858
int dwFlagsAndAttributes)
59+
{
60+
IntPtr handle = CreateFile_IntPtr(lpFileName, dwDesiredAccess, dwShareMode, dwCreationDisposition, dwFlagsAndAttributes);
61+
try
62+
{
63+
return new SafeFileHandle(handle, ownsHandle: true);
64+
}
65+
catch
66+
{
67+
CloseHandle(handle);
68+
throw;
69+
}
70+
}
71+
72+
internal unsafe static IntPtr CreateFile_IntPtr(
73+
string lpFileName,
74+
int dwDesiredAccess,
75+
FileShare dwShareMode,
76+
FileMode dwCreationDisposition,
77+
int dwFlagsAndAttributes)
4978
{
5079
lpFileName = PathInternal.EnsureExtendedPrefixOverMaxPath(lpFileName);
5180
return CreateFilePrivate(lpFileName, dwDesiredAccess, dwShareMode, null, dwCreationDisposition, dwFlagsAndAttributes, IntPtr.Zero);

0 commit comments

Comments
 (0)