Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/corefx.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeremy Kuhne <jeremy.kuhne@microsoft.com>2018-02-06 02:28:14 +0300
committerGitHub <noreply@github.com>2018-02-06 02:28:14 +0300
commit66ada723c1b0ae9062a0e7b0b28862014b985019 (patch)
tree213ff5181f3399b1204f95394120da9d7d3e7034 /src/System.IO.FileSystem
parent4adbec49e0bed722d17b1c1f1ee4cbc2124f0937 (diff)
File enumeration extensibility (#26806)
* Windows file enumeration extensibility This is the Windows implementation of System.IO.Enumeration. It adds new find options to existing APIs and a public extensibilty model for richer, low allocation file system enumeration. The Unix implementation is in progress. * Initial naive Unix implementation of FileSystemEnumerator * Move a netcoreapp test to the right group * Fix WinRT build. * Address feedback from @danmosemsft * Remove inline attribute * Remove IsNameDotOrDotDot property * Address futher feedback. * Remove unreferenced common code
Diffstat (limited to 'src/System.IO.FileSystem')
-rw-r--r--src/System.IO.FileSystem/ref/System.IO.FileSystem.cs86
-rw-r--r--src/System.IO.FileSystem/src/System.IO.FileSystem.csproj34
-rw-r--r--src/System.IO.FileSystem/src/System/IO/Directory.cs298
-rw-r--r--src/System.IO.FileSystem/src/System/IO/DirectoryInfo.Windows.cs10
-rw-r--r--src/System.IO.FileSystem/src/System/IO/DirectoryInfo.cs229
-rw-r--r--src/System.IO.FileSystem/src/System/IO/Enumeration/EnumerationOptions.cs84
-rw-r--r--src/System.IO.FileSystem/src/System/IO/Enumeration/FileSystemEntry.Unix.cs86
-rw-r--r--src/System.IO.FileSystem/src/System/IO/Enumeration/FileSystemEntry.Windows.cs91
-rw-r--r--src/System.IO.FileSystem/src/System/IO/Enumeration/FileSystemEnumerable.cs66
-rw-r--r--src/System.IO.FileSystem/src/System/IO/Enumeration/FileSystemEnumerableFactory.cs170
-rw-r--r--src/System.IO.FileSystem/src/System/IO/Enumeration/FileSystemEnumerator.Unix.cs191
-rw-r--r--src/System.IO.FileSystem/src/System/IO/Enumeration/FileSystemEnumerator.Win32.cs (renamed from src/System.IO.FileSystem/src/System/IO/FindEnumerable.Win32.cs)15
-rw-r--r--src/System.IO.FileSystem/src/System/IO/Enumeration/FileSystemEnumerator.WinRT.cs (renamed from src/System.IO.FileSystem/src/System/IO/FindEnumerable.WinRT.cs)15
-rw-r--r--src/System.IO.FileSystem/src/System/IO/Enumeration/FileSystemEnumerator.Windows.cs230
-rw-r--r--src/System.IO.FileSystem/src/System/IO/Enumeration/FileSystemEnumerator.cs89
-rw-r--r--src/System.IO.FileSystem/src/System/IO/Enumeration/FileSystemName.cs (renamed from src/System.IO.FileSystem/src/System/IO/DosMatcher.cs)89
-rw-r--r--src/System.IO.FileSystem/src/System/IO/Enumeration/MatchType.cs20
-rw-r--r--src/System.IO.FileSystem/src/System/IO/FileInfo.Windows.cs10
-rw-r--r--src/System.IO.FileSystem/src/System/IO/FileSystem.Unix.cs255
-rw-r--r--src/System.IO.FileSystem/src/System/IO/FileSystem.Windows.cs32
-rw-r--r--src/System.IO.FileSystem/src/System/IO/FindEnumerable.Windows.cs251
-rw-r--r--src/System.IO.FileSystem/src/System/IO/FindEnumerableFactory.cs138
-rw-r--r--src/System.IO.FileSystem/src/System/IO/FindPredicate.cs11
-rw-r--r--src/System.IO.FileSystem/src/System/IO/FindPredicates.cs18
-rw-r--r--src/System.IO.FileSystem/src/System/IO/FindTransform.cs11
-rw-r--r--src/System.IO.FileSystem/src/System/IO/FindTransforms.cs40
-rw-r--r--src/System.IO.FileSystem/src/System/IO/PathHelpers.Windows.cs14
-rw-r--r--src/System.IO.FileSystem/src/System/IO/PathHelpers.cs1
-rw-r--r--src/System.IO.FileSystem/src/System/IO/RawFindData.cs33
-rw-r--r--src/System.IO.FileSystem/tests/Enumeration/ConstructionTests.netcoreapp.cs46
-rw-r--r--src/System.IO.FileSystem/tests/Enumeration/DosMatcherTests.netcoreapp.cs116
-rw-r--r--src/System.IO.FileSystem/tests/Enumeration/SkipAttributeTests.netcoreapp.cs85
-rw-r--r--src/System.IO.FileSystem/tests/Enumeration/SpecialDirectoryTests.netcoreapp.cs37
-rw-r--r--src/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj7
34 files changed, 1632 insertions, 1276 deletions
diff --git a/src/System.IO.FileSystem/ref/System.IO.FileSystem.cs b/src/System.IO.FileSystem/ref/System.IO.FileSystem.cs
index e4ba63826d..b373a812a9 100644
--- a/src/System.IO.FileSystem/ref/System.IO.FileSystem.cs
+++ b/src/System.IO.FileSystem/ref/System.IO.FileSystem.cs
@@ -20,12 +20,15 @@ namespace System.IO
public static System.Collections.Generic.IEnumerable<string> EnumerateDirectories(string path) { throw null; }
public static System.Collections.Generic.IEnumerable<string> EnumerateDirectories(string path, string searchPattern) { throw null; }
public static System.Collections.Generic.IEnumerable<string> EnumerateDirectories(string path, string searchPattern, System.IO.SearchOption searchOption) { throw null; }
+ public static System.Collections.Generic.IEnumerable<string> EnumerateDirectories(string path, string searchPattern, System.IO.Enumeration.EnumerationOptions enumerationOptions) { throw null; }
public static System.Collections.Generic.IEnumerable<string> EnumerateFiles(string path) { throw null; }
public static System.Collections.Generic.IEnumerable<string> EnumerateFiles(string path, string searchPattern) { throw null; }
public static System.Collections.Generic.IEnumerable<string> EnumerateFiles(string path, string searchPattern, System.IO.SearchOption searchOption) { throw null; }
+ public static System.Collections.Generic.IEnumerable<string> EnumerateFiles(string path, string searchPattern, System.IO.Enumeration.EnumerationOptions enumerationOptions) { throw null; }
public static System.Collections.Generic.IEnumerable<string> EnumerateFileSystemEntries(string path) { throw null; }
public static System.Collections.Generic.IEnumerable<string> EnumerateFileSystemEntries(string path, string searchPattern) { throw null; }
public static System.Collections.Generic.IEnumerable<string> EnumerateFileSystemEntries(string path, string searchPattern, System.IO.SearchOption searchOption) { throw null; }
+ public static System.Collections.Generic.IEnumerable<string> EnumerateFileSystemEntries(string path, string searchPattern, System.IO.Enumeration.EnumerationOptions enumerationOptions) { throw null; }
public static bool Exists(string path) { throw null; }
public static System.DateTime GetCreationTime(string path) { throw null; }
public static System.DateTime GetCreationTimeUtc(string path) { throw null; }
@@ -33,13 +36,16 @@ namespace System.IO
public static string[] GetDirectories(string path) { throw null; }
public static string[] GetDirectories(string path, string searchPattern) { throw null; }
public static string[] GetDirectories(string path, string searchPattern, System.IO.SearchOption searchOption) { throw null; }
+ public static string[] GetDirectories(string path, string searchPattern, System.IO.Enumeration.EnumerationOptions enumerationOptions) { throw null; }
public static string GetDirectoryRoot(string path) { throw null; }
public static string[] GetFiles(string path) { throw null; }
public static string[] GetFiles(string path, string searchPattern) { throw null; }
public static string[] GetFiles(string path, string searchPattern, System.IO.SearchOption searchOption) { throw null; }
+ public static string[] GetFiles(string path, string searchPattern, System.IO.Enumeration.EnumerationOptions enumerationOptions) { throw null; }
public static string[] GetFileSystemEntries(string path) { throw null; }
public static string[] GetFileSystemEntries(string path, string searchPattern) { throw null; }
public static string[] GetFileSystemEntries(string path, string searchPattern, System.IO.SearchOption searchOption) { throw null; }
+ public static string[] GetFileSystemEntries(string path, string searchPattern, System.IO.Enumeration.EnumerationOptions enumerationOptions) { throw null; }
public static System.DateTime GetLastAccessTime(string path) { throw null; }
public static System.DateTime GetLastAccessTimeUtc(string path) { throw null; }
public static System.DateTime GetLastWriteTime(string path) { throw null; }
@@ -67,23 +73,29 @@ namespace System.IO
public override void Delete() { }
public void Delete(bool recursive) { }
public System.Collections.Generic.IEnumerable<System.IO.DirectoryInfo> EnumerateDirectories() { throw null; }
+ public System.Collections.Generic.IEnumerable<System.IO.DirectoryInfo> EnumerateDirectories(string searchPattern, System.IO.Enumeration.EnumerationOptions enumerationOptions) { throw null; }
+ public System.Collections.Generic.IEnumerable<System.IO.FileSystemInfo> EnumerateFileSystemInfos(string searchPattern, System.IO.Enumeration.EnumerationOptions enumerationOptions) { throw null; }
public System.Collections.Generic.IEnumerable<System.IO.DirectoryInfo> EnumerateDirectories(string searchPattern) { throw null; }
- public System.Collections.Generic.IEnumerable<System.IO.DirectoryInfo> EnumerateDirectories(string searchPattern, System.IO.SearchOption searchOption) { throw null; }
public System.Collections.Generic.IEnumerable<System.IO.FileInfo> EnumerateFiles() { throw null; }
public System.Collections.Generic.IEnumerable<System.IO.FileInfo> EnumerateFiles(string searchPattern) { throw null; }
public System.Collections.Generic.IEnumerable<System.IO.FileInfo> EnumerateFiles(string searchPattern, System.IO.SearchOption searchOption) { throw null; }
+ public System.Collections.Generic.IEnumerable<System.IO.FileInfo> EnumerateFiles(string searchPattern, System.IO.Enumeration.EnumerationOptions enumerationOptions) { throw null; }
public System.Collections.Generic.IEnumerable<System.IO.FileSystemInfo> EnumerateFileSystemInfos() { throw null; }
public System.Collections.Generic.IEnumerable<System.IO.FileSystemInfo> EnumerateFileSystemInfos(string searchPattern) { throw null; }
public System.Collections.Generic.IEnumerable<System.IO.FileSystemInfo> EnumerateFileSystemInfos(string searchPattern, System.IO.SearchOption searchOption) { throw null; }
+ public System.Collections.Generic.IEnumerable<System.IO.DirectoryInfo> EnumerateDirectories(string searchPattern, System.IO.SearchOption searchOption) { throw null; }
public System.IO.DirectoryInfo[] GetDirectories() { throw null; }
public System.IO.DirectoryInfo[] GetDirectories(string searchPattern) { throw null; }
public System.IO.DirectoryInfo[] GetDirectories(string searchPattern, System.IO.SearchOption searchOption) { throw null; }
+ public System.IO.DirectoryInfo[] GetDirectories(string searchPattern, System.IO.Enumeration.EnumerationOptions enumerationOptions) { throw null; }
public System.IO.FileInfo[] GetFiles() { throw null; }
public System.IO.FileInfo[] GetFiles(string searchPattern) { throw null; }
public System.IO.FileInfo[] GetFiles(string searchPattern, System.IO.SearchOption searchOption) { throw null; }
+ public System.IO.FileInfo[] GetFiles(string searchPattern, System.IO.Enumeration.EnumerationOptions enumerationOptions) { throw null; }
public System.IO.FileSystemInfo[] GetFileSystemInfos() { throw null; }
public System.IO.FileSystemInfo[] GetFileSystemInfos(string searchPattern) { throw null; }
public System.IO.FileSystemInfo[] GetFileSystemInfos(string searchPattern, System.IO.SearchOption searchOption) { throw null; }
+ public System.IO.FileSystemInfo[] GetFileSystemInfos(string searchPattern, System.IO.Enumeration.EnumerationOptions enumerationOptions) { throw null; }
public void MoveTo(string destDirName) { }
public override string ToString() { throw null; }
}
@@ -212,3 +224,75 @@ namespace System.IO
TopDirectoryOnly = 0,
}
}
+namespace System.IO.Enumeration
+{
+ public enum MatchType
+ {
+ Simple,
+ Dos
+ }
+ public class EnumerationOptions
+ {
+ public EnumerationOptions() { }
+
+ public bool RecurseSubdirectories { get { throw null; } set { } }
+ public bool IgnoreInaccessible { get { throw null; } set { } }
+ public int BufferSize { get { throw null; } set { } }
+ public FileAttributes AttributesToSkip { get { throw null; } set { } }
+ public MatchType MatchType { get { throw null; } set { } }
+ public bool ReturnSpecialDirectories { get { throw null; } set { } }
+ }
+ public ref struct FileSystemEntry
+ {
+ public ReadOnlySpan<char> Directory { get { throw null; } }
+ public string RootDirectory { get { throw null; } }
+ public string OriginalRootDirectory { get { throw null; } }
+ public ReadOnlySpan<char> FileName { get { throw null; } }
+ public FileAttributes Attributes { get { throw null; } }
+ public long Length { get { throw null; } }
+ public DateTimeOffset CreationTimeUtc { get { throw null; } }
+ public DateTimeOffset LastAccessTimeUtc { get { throw null; } }
+ public DateTimeOffset LastWriteTimeUtc { get { throw null; } }
+ public bool IsDirectory { get { throw null; } }
+ public FileSystemInfo ToFileSystemInfo() { throw null; }
+ public string ToSpecifiedFullPath() { throw null; }
+ public string ToFullPath() { throw null; }
+ }
+ public abstract class FileSystemEnumerator<TResult> : Runtime.ConstrainedExecution.CriticalFinalizerObject, Collections.Generic.IEnumerator<TResult>
+ {
+ public FileSystemEnumerator(string directory, EnumerationOptions options = null) { }
+
+ protected virtual bool ShouldIncludeEntry(ref FileSystemEntry entry) { throw null; }
+ protected virtual bool ShouldRecurseIntoEntry(ref FileSystemEntry entry) { throw null; }
+ protected abstract TResult TransformEntry(ref FileSystemEntry entry);
+ protected virtual void OnDirectoryFinished(ReadOnlySpan<char> directory) { throw null; }
+ protected virtual bool ContinueOnError(int error) { throw null; }
+
+ public TResult Current { get { throw null; } }
+ object System.Collections.IEnumerator.Current { get { throw null; } }
+ public bool MoveNext() { throw null; }
+ public void Reset() { throw null; }
+ public void Dispose() { throw null; }
+ protected virtual void Dispose(bool disposing) { throw null; }
+ }
+ public class FileSystemEnumerable<TResult> : Collections.Generic.IEnumerable<TResult>
+ {
+ public FileSystemEnumerable(string directory, FindTransform transform, EnumerationOptions options = null) { }
+
+ public FindPredicate ShouldRecursePredicate { get { throw null; } set { } }
+ public FindPredicate ShouldIncludePredicate { get { throw null; } set { } }
+
+ public Collections.Generic.IEnumerator<TResult> GetEnumerator() { throw null; }
+ Collections.IEnumerator Collections.IEnumerable.GetEnumerator() { throw null; }
+
+
+ public delegate bool FindPredicate(ref FileSystemEntry entry);
+ public delegate TResult FindTransform(ref FileSystemEntry entry);
+ }
+ public static class FileSystemName
+ {
+ public static string TranslateDosExpression(string expression) { throw null; }
+ public static bool MatchesDosExpression(ReadOnlySpan<char> expression, ReadOnlySpan<char> name, bool ignoreCase = true) { throw null; }
+ public static bool MatchesSimpleExpression(ReadOnlySpan<char> expression, ReadOnlySpan<char> name, bool ignoreCase = true) { throw null; }
+ }
+}
diff --git a/src/System.IO.FileSystem/src/System.IO.FileSystem.csproj b/src/System.IO.FileSystem/src/System.IO.FileSystem.csproj
index f60c46292f..b738420619 100644
--- a/src/System.IO.FileSystem/src/System.IO.FileSystem.csproj
+++ b/src/System.IO.FileSystem/src/System.IO.FileSystem.csproj
@@ -19,28 +19,24 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'uap-Windows_NT-Release|AnyCPU'" />
<ItemGroup>
<Compile Include="System\IO\CharSpanExtensions.cs" />
- <Compile Include="System\IO\DosMatcher.cs" />
+ <Compile Include="System\IO\Enumeration\FileSystemEnumerable.cs" />
+ <Compile Include="System\IO\Enumeration\FileSystemEnumerableFactory.cs" />
+ <Compile Include="System\IO\Enumeration\FileSystemEnumerator.cs" />
+ <Compile Include="System\IO\Enumeration\FileSystemName.cs" />
+ <Compile Include="System\IO\Enumeration\MatchType.cs" />
<Compile Include="System\IO\Error.cs" />
<Compile Include="System\IO\Directory.cs" />
<Compile Include="System\IO\DirectoryInfo.cs" />
<Compile Include="System\IO\File.cs" />
<Compile Include="System\IO\FileInfo.cs" />
<Compile Include="System\IO\FileSystemInfo.cs" />
+ <Compile Include="System\IO\Enumeration\EnumerationOptions.cs" />
<Compile Include="System\IO\Iterator.cs" />
<Compile Include="System\IO\PathHelpers.cs" />
<Compile Include="System\IO\PathPair.cs" />
<Compile Include="System\IO\ReadLinesIterator.cs" />
<Compile Include="System\IO\SearchOption.cs" />
<Compile Include="System\IO\SearchTarget.cs" />
- <Compile Include="$(CommonPath)\System\Collections\Generic\ArrayBuilder.cs">
- <Link>Common\System\Collections\Generic\ArrayBuilder.cs</Link>
- </Compile>
- <Compile Include="$(CommonPath)\System\Collections\Generic\EnumerableHelpers.cs">
- <Link>Common\System\Collections\Generic\EnumerableHelpers.cs</Link>
- </Compile>
- <Compile Include="$(CommonPath)\System\Collections\Generic\LargeArrayBuilder.cs">
- <Link>Common\System\Collections\Generic\LargeArrayBuilder.cs</Link>
- </Compile>
<Compile Include="$(CommonPath)\System\IO\StringBuilderCache.cs">
<Link>Common\System\IO\StringBuilderCache.cs</Link>
</Compile>
@@ -62,19 +58,14 @@
</ItemGroup>
<!-- Windows -->
<ItemGroup Condition="'$(TargetsWindows)' == 'true'">
+ <Compile Include="System\IO\Enumeration\FileSystemEnumerator.Windows.cs" />
<Compile Include="System\IO\CharSpanExtensions.Windows.cs" />
<Compile Include="System\IO\DisableMediaInsertionPrompt.cs" />
<Compile Include="System\IO\DirectoryInfo.Windows.cs" />
<Compile Include="System\IO\FileInfo.Windows.cs" />
- <Compile Include="System\IO\FindEnumerable.Windows.cs" />
- <Compile Include="System\IO\FindEnumerableFactory.cs" />
- <Compile Include="System\IO\FindPredicate.cs" />
- <Compile Include="System\IO\FindTransform.cs" />
- <Compile Include="System\IO\FindPredicates.cs" />
- <Compile Include="System\IO\FindTransforms.cs" />
<Compile Include="System\IO\FileSystemInfo.Windows.cs" />
<Compile Include="System\IO\PathHelpers.Windows.cs" />
- <Compile Include="System\IO\RawFindData.cs" />
+ <Compile Include="System\IO\Enumeration\FileSystemEntry.Windows.cs" />
<Compile Include="System\IO\FileSystem.Windows.cs" />
<Compile Include="Microsoft\Win32\SafeHandles\SafeFindHandle.Windows.cs" />
<Compile Include="$(CommonPath)\Interop\Windows\Interop.Libraries.cs">
@@ -246,7 +237,7 @@
</ItemGroup>
<!-- Windows : Win32 only -->
<ItemGroup Condition="'$(TargetsWindows)' == 'true' and '$(UWPCompatible)' != 'true'">
- <Compile Include="System\IO\FindEnumerable.Win32.cs" />
+ <Compile Include="System\IO\Enumeration\FileSystemEnumerator.Win32.cs" />
<Compile Include="$(CommonPath)\Interop\Windows\kernel32\Interop.CreateFile.cs">
<Link>Common\Interop\Windows\Interop.CreateFile.cs</Link>
</Compile>
@@ -274,7 +265,7 @@
</ItemGroup>
<!-- Windows : UAP - Win32 + WinRT -->
<ItemGroup Condition="'$(TargetsWindows)' == 'true' and '$(UWPCompatible)' == 'true'">
- <Compile Include="System\IO\FindEnumerable.WinRT.cs" />
+ <Compile Include="System\IO\Enumeration\FileSystemEnumerator.WinRT.cs" />
<Compile Include="$(CommonPath)\Interop\Windows\kernel32\Interop.CreateFile2.cs">
<Link>Common\Interop\Windows\Interop.CreateFile2.cs</Link>
</Compile>
@@ -290,6 +281,8 @@
</ItemGroup>
<!-- Unix -->
<ItemGroup Condition="'$(TargetsUnix)' == 'true'">
+ <Compile Include="System\IO\Enumeration\FileSystemEntry.Unix.cs" />
+ <Compile Include="System\IO\Enumeration\FileSystemEnumerator.Unix.cs" />
<Compile Include="System\IO\CharSpanExtensions.Unix.cs" />
<Compile Include="System\IO\FileSystemInfo.Unix.cs" />
<Compile Include="System\IO\PathHelpers.Unix.cs" />
@@ -405,6 +398,7 @@
<Reference Include="System.Collections" />
<Reference Include="System.Diagnostics.Debug" />
<Reference Include="System.Diagnostics.Tools" />
+ <Reference Include="System.Linq" />
<Reference Include="System.Memory" />
<Reference Include="System.Resources.ResourceManager" />
<Reference Include="System.Runtime" />
@@ -419,4 +413,4 @@
<Reference Include="System.Threading" />
</ItemGroup>
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
-</Project>
+</Project> \ No newline at end of file
diff --git a/src/System.IO.FileSystem/src/System/IO/Directory.cs b/src/System.IO.FileSystem/src/System/IO/Directory.cs
index 5f4b32550e..1c7fc09638 100644
--- a/src/System.IO.FileSystem/src/System/IO/Directory.cs
+++ b/src/System.IO.FileSystem/src/System/IO/Directory.cs
@@ -3,7 +3,8 @@
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
-using System.Diagnostics;
+using System.IO.Enumeration;
+using System.Linq;
using System.Security;
namespace System.IO
@@ -148,286 +149,94 @@ namespace System.IO
return File.GetLastAccessTimeUtc(path);
}
- // Returns an array of filenames in the DirectoryInfo specified by path
- public static string[] GetFiles(string path)
- {
- if (path == null)
- throw new ArgumentNullException(nameof(path));
+ public static string[] GetFiles(string path) => GetFiles(path, "*", enumerationOptions: EnumerationOptions.Compatible);
- return InternalGetFiles(path, "*", SearchOption.TopDirectoryOnly);
- }
+ public static string[] GetFiles(string path, string searchPattern) => GetFiles(path, searchPattern, enumerationOptions: EnumerationOptions.Compatible);
- // Returns an array of Files in the current DirectoryInfo matching the
- // given search pattern (i.e. "*.txt").
- public static string[] GetFiles(string path, string searchPattern)
- {
- if (path == null)
- throw new ArgumentNullException(nameof(path));
- if (searchPattern == null)
- throw new ArgumentNullException(nameof(searchPattern));
-
- return InternalGetFiles(path, searchPattern, SearchOption.TopDirectoryOnly);
- }
-
- // Returns an array of Files in the current DirectoryInfo matching the
- // given search pattern (i.e. "*.txt") and search option
public static string[] GetFiles(string path, string searchPattern, SearchOption searchOption)
- {
- if (path == null)
- throw new ArgumentNullException(nameof(path));
- if (searchPattern == null)
- throw new ArgumentNullException(nameof(searchPattern));
- if ((searchOption != SearchOption.TopDirectoryOnly) && (searchOption != SearchOption.AllDirectories))
- throw new ArgumentOutOfRangeException(nameof(searchOption), SR.ArgumentOutOfRange_Enum);
+ => GetFiles(path, searchPattern, EnumerationOptions.FromSearchOption(searchOption));
- return InternalGetFiles(path, searchPattern, searchOption);
- }
+ public static string[] GetFiles(string path, string searchPattern, EnumerationOptions enumerationOptions)
+ => InternalEnumeratePaths(path, searchPattern, SearchTarget.Files, enumerationOptions).ToArray();
- // Returns an array of Files in the current DirectoryInfo matching the
- // given search pattern (i.e. "*.txt") and search option
- private static string[] InternalGetFiles(string path, string searchPattern, SearchOption searchOption)
- {
- Debug.Assert(path != null);
- Debug.Assert(searchPattern != null);
- Debug.Assert(searchOption == SearchOption.AllDirectories || searchOption == SearchOption.TopDirectoryOnly);
+ public static string[] GetDirectories(string path) => GetDirectories(path, "*", enumerationOptions: EnumerationOptions.Compatible);
- return InternalGetFileDirectoryNames(path, path, searchPattern, true, false, searchOption);
- }
-
- // Returns an array of Directories in the current directory.
- public static string[] GetDirectories(string path)
- {
- if (path == null)
- throw new ArgumentNullException(nameof(path));
-
- return InternalGetDirectories(path, "*", SearchOption.TopDirectoryOnly);
- }
-
- // Returns an array of Directories in the current DirectoryInfo matching the
- // given search criteria (i.e. "*.txt").
- public static string[] GetDirectories(string path, string searchPattern)
- {
- if (path == null)
- throw new ArgumentNullException(nameof(path));
- if (searchPattern == null)
- throw new ArgumentNullException(nameof(searchPattern));
-
- return InternalGetDirectories(path, searchPattern, SearchOption.TopDirectoryOnly);
- }
+ public static string[] GetDirectories(string path, string searchPattern) => GetDirectories(path, searchPattern, enumerationOptions: EnumerationOptions.Compatible);
- // Returns an array of Directories in the current DirectoryInfo matching the
- // given search criteria (i.e. "*.txt").
public static string[] GetDirectories(string path, string searchPattern, SearchOption searchOption)
- {
- if (path == null)
- throw new ArgumentNullException(nameof(path));
- if (searchPattern == null)
- throw new ArgumentNullException(nameof(searchPattern));
- if ((searchOption != SearchOption.TopDirectoryOnly) && (searchOption != SearchOption.AllDirectories))
- throw new ArgumentOutOfRangeException(nameof(searchOption), SR.ArgumentOutOfRange_Enum);
+ => GetDirectories(path, searchPattern, EnumerationOptions.FromSearchOption(searchOption));
- return InternalGetDirectories(path, searchPattern, searchOption);
- }
+ public static string[] GetDirectories(string path, string searchPattern, EnumerationOptions enumerationOptions)
+ => InternalEnumeratePaths(path, searchPattern, SearchTarget.Directories, enumerationOptions).ToArray();
- // Returns an array of Directories in the current DirectoryInfo matching the
- // given search criteria (i.e. "*.txt").
- private static string[] InternalGetDirectories(string path, string searchPattern, SearchOption searchOption)
- {
- Debug.Assert(path != null);
- Debug.Assert(searchPattern != null);
- Debug.Assert(searchOption == SearchOption.AllDirectories || searchOption == SearchOption.TopDirectoryOnly);
+ public static string[] GetFileSystemEntries(string path) => GetFileSystemEntries(path, "*", enumerationOptions: EnumerationOptions.Compatible);
- return InternalGetFileDirectoryNames(path, path, searchPattern, false, true, searchOption);
- }
+ public static string[] GetFileSystemEntries(string path, string searchPattern) => GetFileSystemEntries(path, searchPattern, enumerationOptions: EnumerationOptions.Compatible);
- // Returns an array of strongly typed FileSystemInfo entries in the path
- public static string[] GetFileSystemEntries(string path)
- {
- if (path == null)
- throw new ArgumentNullException(nameof(path));
-
- return InternalGetFileSystemEntries(path, "*", SearchOption.TopDirectoryOnly);
- }
-
- // Returns an array of strongly typed FileSystemInfo entries in the path with the
- // given search criteria (i.e. "*.txt"). We disallow .. as a part of the search criteria
- public static string[] GetFileSystemEntries(string path, string searchPattern)
- {
- if (path == null)
- throw new ArgumentNullException(nameof(path));
- if (searchPattern == null)
- throw new ArgumentNullException(nameof(searchPattern));
+ public static string[] GetFileSystemEntries(string path, string searchPattern, SearchOption searchOption)
+ => GetFileSystemEntries(path, searchPattern, EnumerationOptions.FromSearchOption(searchOption));
- return InternalGetFileSystemEntries(path, searchPattern, SearchOption.TopDirectoryOnly);
- }
+ public static string[] GetFileSystemEntries(string path, string searchPattern, EnumerationOptions enumerationOptions)
+ => InternalEnumeratePaths(path, searchPattern, SearchTarget.Both, enumerationOptions).ToArray();
- // Returns an array of strongly typed FileSystemInfo entries in the path with the
- // given search criteria (i.e. "*.txt"). We disallow .. as a part of the search criteria
- public static string[] GetFileSystemEntries(string path, string searchPattern, SearchOption searchOption)
+ internal static IEnumerable<string> InternalEnumeratePaths(
+ string path,
+ string searchPattern,
+ SearchTarget searchTarget,
+ EnumerationOptions options)
{
if (path == null)
throw new ArgumentNullException(nameof(path));
if (searchPattern == null)
throw new ArgumentNullException(nameof(searchPattern));
- if ((searchOption != SearchOption.TopDirectoryOnly) && (searchOption != SearchOption.AllDirectories))
- throw new ArgumentOutOfRangeException(nameof(searchOption), SR.ArgumentOutOfRange_Enum);
-
- return InternalGetFileSystemEntries(path, searchPattern, searchOption);
- }
-
- private static string[] InternalGetFileSystemEntries(string path, string searchPattern, SearchOption searchOption)
- {
- Debug.Assert(path != null);
- Debug.Assert(searchPattern != null);
- Debug.Assert(searchOption == SearchOption.AllDirectories || searchOption == SearchOption.TopDirectoryOnly);
-
- return InternalGetFileDirectoryNames(path, path, searchPattern, true, true, searchOption);
- }
-
- // Returns fully qualified user path of dirs/files that matches the search parameters.
- // For recursive search this method will search through all the sub dirs and execute
- // the given search criteria against every dir.
- // For all the dirs/files returned, it will then demand path discovery permission for
- // their parent folders (it will avoid duplicate permission checks)
- internal static string[] InternalGetFileDirectoryNames(string path, string userPathOriginal, string searchPattern, bool includeFiles, bool includeDirs, SearchOption searchOption)
- {
- Debug.Assert(path != null);
- Debug.Assert(userPathOriginal != null);
- Debug.Assert(searchPattern != null);
- Debug.Assert(searchOption == SearchOption.AllDirectories || searchOption == SearchOption.TopDirectoryOnly);
-
- IEnumerable<string> enumerable = FileSystem.EnumeratePaths(path, searchPattern, searchOption,
- (includeFiles ? SearchTarget.Files : 0) | (includeDirs ? SearchTarget.Directories : 0));
- return EnumerableHelpers.ToArray(enumerable);
- }
- public static IEnumerable<string> EnumerateDirectories(string path)
- {
- if (path == null)
- throw new ArgumentNullException(nameof(path));
+ FileSystemEnumerableFactory.NormalizeInputs(ref path, ref searchPattern, options);
- return InternalEnumerateDirectories(path, "*", SearchOption.TopDirectoryOnly);
+ switch (searchTarget)
+ {
+ case SearchTarget.Files:
+ return FileSystemEnumerableFactory.UserFiles(path, searchPattern, options);
+ case SearchTarget.Directories:
+ return FileSystemEnumerableFactory.UserDirectories(path, searchPattern, options);
+ case SearchTarget.Both:
+ return FileSystemEnumerableFactory.UserEntries(path, searchPattern, options);
+ default:
+ throw new ArgumentOutOfRangeException(nameof(searchTarget));
+ }
}
- public static IEnumerable<string> EnumerateDirectories(string path, string searchPattern)
- {
- if (path == null)
- throw new ArgumentNullException(nameof(path));
- if (searchPattern == null)
- throw new ArgumentNullException(nameof(searchPattern));
+ public static IEnumerable<string> EnumerateDirectories(string path) => EnumerateDirectories(path, "*", enumerationOptions: EnumerationOptions.Compatible);
- return InternalEnumerateDirectories(path, searchPattern, SearchOption.TopDirectoryOnly);
- }
+ public static IEnumerable<string> EnumerateDirectories(string path, string searchPattern) => EnumerateDirectories(path, searchPattern, enumerationOptions: EnumerationOptions.Compatible);
public static IEnumerable<string> EnumerateDirectories(string path, string searchPattern, SearchOption searchOption)
- {
- if (path == null)
- throw new ArgumentNullException(nameof(path));
- if (searchPattern == null)
- throw new ArgumentNullException(nameof(searchPattern));
- if ((searchOption != SearchOption.TopDirectoryOnly) && (searchOption != SearchOption.AllDirectories))
- throw new ArgumentOutOfRangeException(nameof(searchOption), SR.ArgumentOutOfRange_Enum);
-
- return InternalEnumerateDirectories(path, searchPattern, searchOption);
- }
-
- private static IEnumerable<string> InternalEnumerateDirectories(string path, string searchPattern, SearchOption searchOption)
- {
- Debug.Assert(path != null);
- Debug.Assert(searchPattern != null);
- Debug.Assert(searchOption == SearchOption.AllDirectories || searchOption == SearchOption.TopDirectoryOnly);
+ => EnumerateDirectories(path, searchPattern, EnumerationOptions.FromSearchOption(searchOption));
- return EnumerateFileSystemNames(path, searchPattern, searchOption, false, true);
- }
+ public static IEnumerable<string> EnumerateDirectories(string path, string searchPattern, EnumerationOptions enumerationOptions)
+ => InternalEnumeratePaths(path, searchPattern, SearchTarget.Directories, enumerationOptions);
- public static IEnumerable<string> EnumerateFiles(string path)
- {
- if (path == null)
- throw new ArgumentNullException(nameof(path));
-
- return InternalEnumerateFiles(path, "*", SearchOption.TopDirectoryOnly);
- }
+ public static IEnumerable<string> EnumerateFiles(string path) => EnumerateFiles(path, "*", enumerationOptions: EnumerationOptions.Compatible);
public static IEnumerable<string> EnumerateFiles(string path, string searchPattern)
- {
- if (path == null)
- throw new ArgumentNullException(nameof(path));
- if (searchPattern == null)
- throw new ArgumentNullException(nameof(searchPattern));
-
- return InternalEnumerateFiles(path, searchPattern, SearchOption.TopDirectoryOnly);
- }
+ => EnumerateFiles(path, searchPattern, enumerationOptions: EnumerationOptions.Compatible);
public static IEnumerable<string> EnumerateFiles(string path, string searchPattern, SearchOption searchOption)
- {
- if (path == null)
- throw new ArgumentNullException(nameof(path));
- if (searchPattern == null)
- throw new ArgumentNullException(nameof(searchPattern));
- if ((searchOption != SearchOption.TopDirectoryOnly) && (searchOption != SearchOption.AllDirectories))
- throw new ArgumentOutOfRangeException(nameof(searchOption), SR.ArgumentOutOfRange_Enum);
+ => EnumerateFiles(path, searchPattern, EnumerationOptions.FromSearchOption(searchOption));
- return InternalEnumerateFiles(path, searchPattern, searchOption);
- }
-
- private static IEnumerable<string> InternalEnumerateFiles(string path, string searchPattern, SearchOption searchOption)
- {
- Debug.Assert(path != null);
- Debug.Assert(searchPattern != null);
- Debug.Assert(searchOption == SearchOption.AllDirectories || searchOption == SearchOption.TopDirectoryOnly);
-
- return EnumerateFileSystemNames(path, searchPattern, searchOption, true, false);
- }
+ public static IEnumerable<string> EnumerateFiles(string path, string searchPattern, EnumerationOptions enumerationOptions)
+ => InternalEnumeratePaths(path, searchPattern, SearchTarget.Files, enumerationOptions);
public static IEnumerable<string> EnumerateFileSystemEntries(string path)
- {
- if (path == null)
- throw new ArgumentNullException(nameof(path));
-
- return InternalEnumerateFileSystemEntries(path, "*", SearchOption.TopDirectoryOnly);
- }
+ => EnumerateFileSystemEntries(path, "*", enumerationOptions: EnumerationOptions.Compatible);
public static IEnumerable<string> EnumerateFileSystemEntries(string path, string searchPattern)
- {
- if (path == null)
- throw new ArgumentNullException(nameof(path));
- if (searchPattern == null)
- throw new ArgumentNullException(nameof(searchPattern));
-
- return InternalEnumerateFileSystemEntries(path, searchPattern, SearchOption.TopDirectoryOnly);
- }
+ => EnumerateFileSystemEntries(path, searchPattern, enumerationOptions: EnumerationOptions.Compatible);
public static IEnumerable<string> EnumerateFileSystemEntries(string path, string searchPattern, SearchOption searchOption)
- {
- if (path == null)
- throw new ArgumentNullException(nameof(path));
- if (searchPattern == null)
- throw new ArgumentNullException(nameof(searchPattern));
- if ((searchOption != SearchOption.TopDirectoryOnly) && (searchOption != SearchOption.AllDirectories))
- throw new ArgumentOutOfRangeException(nameof(searchOption), SR.ArgumentOutOfRange_Enum);
-
- return InternalEnumerateFileSystemEntries(path, searchPattern, searchOption);
- }
-
- private static IEnumerable<string> InternalEnumerateFileSystemEntries(string path, string searchPattern, SearchOption searchOption)
- {
- Debug.Assert(path != null);
- Debug.Assert(searchPattern != null);
- Debug.Assert(searchOption == SearchOption.AllDirectories || searchOption == SearchOption.TopDirectoryOnly);
-
- return EnumerateFileSystemNames(path, searchPattern, searchOption, true, true);
- }
-
- private static IEnumerable<string> EnumerateFileSystemNames(string path, string searchPattern, SearchOption searchOption,
- bool includeFiles, bool includeDirs)
- {
- Debug.Assert(path != null);
- Debug.Assert(searchPattern != null);
- Debug.Assert(searchOption == SearchOption.AllDirectories || searchOption == SearchOption.TopDirectoryOnly);
+ => EnumerateFileSystemEntries(path, searchPattern, EnumerationOptions.FromSearchOption(searchOption));
- return FileSystem.EnumeratePaths(path, searchPattern, searchOption,
- (includeFiles ? SearchTarget.Files : 0) | (includeDirs ? SearchTarget.Directories : 0));
- }
+ public static IEnumerable<string> EnumerateFileSystemEntries(string path, string searchPattern, EnumerationOptions enumerationOptions)
+ => InternalEnumeratePaths(path, searchPattern, SearchTarget.Both, enumerationOptions);
public static string GetDirectoryRoot(string path)
{
@@ -446,13 +255,6 @@ namespace System.IO
return path.Substring(0, PathInternal.GetRootLength(path));
}
- /*===============================CurrentDirectory===============================
- **Action: Provides a getter and setter for the current directory. The original
- ** current DirectoryInfo is the one from which the process was started.
- **Returns: The current DirectoryInfo (from the getter). Void from the setter.
- **Arguments: The current DirectoryInfo to which to switch to the setter.
- **Exceptions:
- ==============================================================================*/
public static string GetCurrentDirectory()
{
return FileSystem.GetCurrentDirectory();
diff --git a/src/System.IO.FileSystem/src/System/IO/DirectoryInfo.Windows.cs b/src/System.IO.FileSystem/src/System/IO/DirectoryInfo.Windows.cs
index 1c1ebd0197..84e26c6820 100644
--- a/src/System.IO.FileSystem/src/System/IO/DirectoryInfo.Windows.cs
+++ b/src/System.IO.FileSystem/src/System/IO/DirectoryInfo.Windows.cs
@@ -2,17 +2,17 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
-using System.Diagnostics;
+using System.IO.Enumeration;
namespace System.IO
{
partial class DirectoryInfo
{
- internal unsafe DirectoryInfo(string fullPath, string fileName, ref RawFindData findData)
- : this(fullPath, fileName: fileName, isNormalized: true)
+ internal static unsafe DirectoryInfo Create(string fullPath, ref FileSystemEntry findData)
{
- Debug.Assert(fileName.Equals(Path.GetFileName(fullPath)));
- Init(findData._info);
+ DirectoryInfo info = new DirectoryInfo(fullPath, fileName: findData.FileName.GetStringFromFixedBuffer(), isNormalized: true);
+ info.Init(findData._info);
+ return info;
}
}
}
diff --git a/src/System.IO.FileSystem/src/System/IO/DirectoryInfo.cs b/src/System.IO.FileSystem/src/System/IO/DirectoryInfo.cs
index a23d9d7b45..1970b8939e 100644
--- a/src/System.IO.FileSystem/src/System/IO/DirectoryInfo.cs
+++ b/src/System.IO.FileSystem/src/System/IO/DirectoryInfo.cs
@@ -4,6 +4,8 @@
using System.Collections.Generic;
using System.Diagnostics;
+using System.IO.Enumeration;
+using System.Linq;
namespace System.IO
{
@@ -63,7 +65,6 @@ namespace System.IO
}
}
-
public DirectoryInfo CreateSubdirectory(string path)
{
if (path == null)
@@ -117,218 +118,104 @@ namespace System.IO
}
}
- // Returns an array of Files in the current DirectoryInfo matching the
- // given search criteria (i.e. "*.txt").
- public FileInfo[] GetFiles(string searchPattern)
- {
- if (searchPattern == null)
- throw new ArgumentNullException(nameof(searchPattern));
-
- return InternalGetFiles(searchPattern, SearchOption.TopDirectoryOnly);
- }
-
- // Returns an array of Files in the current DirectoryInfo matching the
- // given search criteria (i.e. "*.txt").
- public FileInfo[] GetFiles(string searchPattern, SearchOption searchOption)
- {
- if (searchPattern == null)
- throw new ArgumentNullException(nameof(searchPattern));
- if ((searchOption != SearchOption.TopDirectoryOnly) && (searchOption != SearchOption.AllDirectories))
- throw new ArgumentOutOfRangeException(nameof(searchOption), SR.ArgumentOutOfRange_Enum);
-
- return InternalGetFiles(searchPattern, searchOption);
- }
+ // Returns an array of Files in the DirectoryInfo specified by path
+ public FileInfo[] GetFiles() => GetFiles("*", enumerationOptions: EnumerationOptions.Compatible);
// Returns an array of Files in the current DirectoryInfo matching the
// given search criteria (i.e. "*.txt").
- private FileInfo[] InternalGetFiles(string searchPattern, SearchOption searchOption)
- {
- Debug.Assert(searchPattern != null);
- Debug.Assert(searchOption == SearchOption.AllDirectories || searchOption == SearchOption.TopDirectoryOnly);
+ public FileInfo[] GetFiles(string searchPattern) => GetFiles(searchPattern, enumerationOptions: EnumerationOptions.Compatible);
- IEnumerable<FileInfo> enumerable = (IEnumerable<FileInfo>)FileSystem.EnumerateFileSystemInfos(FullPath, searchPattern, searchOption, SearchTarget.Files);
- return EnumerableHelpers.ToArray(enumerable);
- }
+ public FileInfo[] GetFiles(string searchPattern, SearchOption searchOption)
+ => GetFiles(searchPattern, EnumerationOptions.FromSearchOption(searchOption));
- // Returns an array of Files in the DirectoryInfo specified by path
- public FileInfo[] GetFiles()
- {
- return InternalGetFiles("*", SearchOption.TopDirectoryOnly);
- }
+ public FileInfo[] GetFiles(string searchPattern, EnumerationOptions enumerationOptions)
+ => ((IEnumerable<FileInfo>)InternalEnumerateInfos(FullPath, searchPattern, SearchTarget.Files, enumerationOptions)).ToArray();
- // Returns an array of Directories in the current directory.
- public DirectoryInfo[] GetDirectories()
- {
- return InternalGetDirectories("*", SearchOption.TopDirectoryOnly);
- }
+ // Returns an array of strongly typed FileSystemInfo entries which will contain a listing
+ // of all the files and directories.
+ public FileSystemInfo[] GetFileSystemInfos() => GetFileSystemInfos("*", enumerationOptions: EnumerationOptions.Compatible);
// Returns an array of strongly typed FileSystemInfo entries in the path with the
// given search criteria (i.e. "*.txt").
public FileSystemInfo[] GetFileSystemInfos(string searchPattern)
- {
- if (searchPattern == null)
- throw new ArgumentNullException(nameof(searchPattern));
+ => GetFileSystemInfos(searchPattern, enumerationOptions: EnumerationOptions.Compatible);
- return InternalGetFileSystemInfos(searchPattern, SearchOption.TopDirectoryOnly);
- }
-
- // Returns an array of strongly typed FileSystemInfo entries in the path with the
- // given search criteria (i.e. "*.txt").
public FileSystemInfo[] GetFileSystemInfos(string searchPattern, SearchOption searchOption)
- {
- if (searchPattern == null)
- throw new ArgumentNullException(nameof(searchPattern));
- if ((searchOption != SearchOption.TopDirectoryOnly) && (searchOption != SearchOption.AllDirectories))
- throw new ArgumentOutOfRangeException(nameof(searchOption), SR.ArgumentOutOfRange_Enum);
-
- return InternalGetFileSystemInfos(searchPattern, searchOption);
- }
-
- // Returns an array of strongly typed FileSystemInfo entries in the path with the
- // given search criteria (i.e. "*.txt").
- private FileSystemInfo[] InternalGetFileSystemInfos(string searchPattern, SearchOption searchOption)
- {
- Debug.Assert(searchPattern != null);
- Debug.Assert(searchOption == SearchOption.AllDirectories || searchOption == SearchOption.TopDirectoryOnly);
+ => GetFileSystemInfos(searchPattern, EnumerationOptions.FromSearchOption(searchOption));
- IEnumerable<FileSystemInfo> enumerable = FileSystem.EnumerateFileSystemInfos(FullPath, searchPattern, searchOption, SearchTarget.Both);
- return EnumerableHelpers.ToArray(enumerable);
- }
+ public FileSystemInfo[] GetFileSystemInfos(string searchPattern, EnumerationOptions enumerationOptions)
+ => InternalEnumerateInfos(FullPath, searchPattern, SearchTarget.Both, enumerationOptions).ToArray();
- // Returns an array of strongly typed FileSystemInfo entries which will contain a listing
- // of all the files and directories.
- public FileSystemInfo[] GetFileSystemInfos()
- {
- return InternalGetFileSystemInfos("*", SearchOption.TopDirectoryOnly);
- }
+ // Returns an array of Directories in the current directory.
+ public DirectoryInfo[] GetDirectories() => GetDirectories("*", enumerationOptions: EnumerationOptions.Compatible);
// Returns an array of Directories in the current DirectoryInfo matching the
- // given search criteria (i.e. "System*" could match the System & System32
- // directories).
- public DirectoryInfo[] GetDirectories(string searchPattern)
- {
- if (searchPattern == null)
- throw new ArgumentNullException(nameof(searchPattern));
-
- return InternalGetDirectories(searchPattern, SearchOption.TopDirectoryOnly);
- }
+ // given search criteria (i.e. "System*" could match the System & System32 directories).
+ public DirectoryInfo[] GetDirectories(string searchPattern) => GetDirectories(searchPattern, enumerationOptions: EnumerationOptions.Compatible);
- // Returns an array of Directories in the current DirectoryInfo matching the
- // given search criteria (i.e. "System*" could match the System & System32
- // directories).
public DirectoryInfo[] GetDirectories(string searchPattern, SearchOption searchOption)
- {
- if (searchPattern == null)
- throw new ArgumentNullException(nameof(searchPattern));
- if ((searchOption != SearchOption.TopDirectoryOnly) && (searchOption != SearchOption.AllDirectories))
- throw new ArgumentOutOfRangeException(nameof(searchOption), SR.ArgumentOutOfRange_Enum);
-
- return InternalGetDirectories(searchPattern, searchOption);
- }
-
- // Returns an array of Directories in the current DirectoryInfo matching the
- // given search criteria (i.e. "System*" could match the System & System32
- // directories).
- private DirectoryInfo[] InternalGetDirectories(string searchPattern, SearchOption searchOption)
- {
- Debug.Assert(searchPattern != null);
- Debug.Assert(searchOption == SearchOption.AllDirectories || searchOption == SearchOption.TopDirectoryOnly);
+ => GetDirectories(searchPattern, EnumerationOptions.FromSearchOption(searchOption));
- IEnumerable<DirectoryInfo> enumerable = (IEnumerable<DirectoryInfo>)FileSystem.EnumerateFileSystemInfos(FullPath, searchPattern, searchOption, SearchTarget.Directories);
- return EnumerableHelpers.ToArray(enumerable);
- }
+ public DirectoryInfo[] GetDirectories(string searchPattern, EnumerationOptions enumerationOptions)
+ => ((IEnumerable<DirectoryInfo>)InternalEnumerateInfos(FullPath, searchPattern, SearchTarget.Directories, enumerationOptions)).ToArray();
public IEnumerable<DirectoryInfo> EnumerateDirectories()
- {
- return InternalEnumerateDirectories("*", SearchOption.TopDirectoryOnly);
- }
+ => EnumerateDirectories("*", enumerationOptions: EnumerationOptions.Compatible);
public IEnumerable<DirectoryInfo> EnumerateDirectories(string searchPattern)
- {
- if (searchPattern == null)
- throw new ArgumentNullException(nameof(searchPattern));
-
- return InternalEnumerateDirectories(searchPattern, SearchOption.TopDirectoryOnly);
- }
+ => EnumerateDirectories(searchPattern, enumerationOptions: EnumerationOptions.Compatible);
public IEnumerable<DirectoryInfo> EnumerateDirectories(string searchPattern, SearchOption searchOption)
- {
- if (searchPattern == null)
- throw new ArgumentNullException(nameof(searchPattern));
- if ((searchOption != SearchOption.TopDirectoryOnly) && (searchOption != SearchOption.AllDirectories))
- throw new ArgumentOutOfRangeException(nameof(searchOption), SR.ArgumentOutOfRange_Enum);
-
- return InternalEnumerateDirectories(searchPattern, searchOption);
- }
-
- private IEnumerable<DirectoryInfo> InternalEnumerateDirectories(string searchPattern, SearchOption searchOption)
- {
- Debug.Assert(searchPattern != null);
- Debug.Assert(searchOption == SearchOption.AllDirectories || searchOption == SearchOption.TopDirectoryOnly);
+ => EnumerateDirectories(searchPattern, EnumerationOptions.FromSearchOption(searchOption));
- return (IEnumerable<DirectoryInfo>)FileSystem.EnumerateFileSystemInfos(FullPath, searchPattern, searchOption, SearchTarget.Directories);
- }
+ public IEnumerable<DirectoryInfo> EnumerateDirectories(string searchPattern, EnumerationOptions enumerationOptions)
+ => (IEnumerable<DirectoryInfo>)InternalEnumerateInfos(FullPath, searchPattern, SearchTarget.Directories, enumerationOptions);
public IEnumerable<FileInfo> EnumerateFiles()
- {
- return InternalEnumerateFiles("*", SearchOption.TopDirectoryOnly);
- }
+ => EnumerateFiles("*", enumerationOptions: EnumerationOptions.Compatible);
- public IEnumerable<FileInfo> EnumerateFiles(string searchPattern)
- {
- if (searchPattern == null)
- throw new ArgumentNullException(nameof(searchPattern));
-
- return InternalEnumerateFiles(searchPattern, SearchOption.TopDirectoryOnly);
- }
+ public IEnumerable<FileInfo> EnumerateFiles(string searchPattern) => EnumerateFiles(searchPattern, enumerationOptions: EnumerationOptions.Compatible);
public IEnumerable<FileInfo> EnumerateFiles(string searchPattern, SearchOption searchOption)
- {
- if (searchPattern == null)
- throw new ArgumentNullException(nameof(searchPattern));
- if ((searchOption != SearchOption.TopDirectoryOnly) && (searchOption != SearchOption.AllDirectories))
- throw new ArgumentOutOfRangeException(nameof(searchOption), SR.ArgumentOutOfRange_Enum);
+ => EnumerateFiles(searchPattern, EnumerationOptions.FromSearchOption(searchOption));
- return InternalEnumerateFiles(searchPattern, searchOption);
- }
-
- private IEnumerable<FileInfo> InternalEnumerateFiles(string searchPattern, SearchOption searchOption)
- {
- Debug.Assert(searchPattern != null);
- Debug.Assert(searchOption == SearchOption.AllDirectories || searchOption == SearchOption.TopDirectoryOnly);
+ public IEnumerable<FileInfo> EnumerateFiles(string searchPattern, EnumerationOptions enumerationOptions)
+ => (IEnumerable<FileInfo>)InternalEnumerateInfos(FullPath, searchPattern, SearchTarget.Files, enumerationOptions);
- return (IEnumerable<FileInfo>)FileSystem.EnumerateFileSystemInfos(FullPath, searchPattern, searchOption, SearchTarget.Files);
- }
-
- public IEnumerable<FileSystemInfo> EnumerateFileSystemInfos()
- {
- return InternalEnumerateFileSystemInfos("*", SearchOption.TopDirectoryOnly);
- }
+ public IEnumerable<FileSystemInfo> EnumerateFileSystemInfos() => EnumerateFileSystemInfos("*", enumerationOptions: EnumerationOptions.Compatible);
public IEnumerable<FileSystemInfo> EnumerateFileSystemInfos(string searchPattern)
- {
- if (searchPattern == null)
- throw new ArgumentNullException(nameof(searchPattern));
-
- return InternalEnumerateFileSystemInfos(searchPattern, SearchOption.TopDirectoryOnly);
- }
+ => EnumerateFileSystemInfos(searchPattern, enumerationOptions: EnumerationOptions.Compatible);
public IEnumerable<FileSystemInfo> EnumerateFileSystemInfos(string searchPattern, SearchOption searchOption)
+ => EnumerateFileSystemInfos(searchPattern, EnumerationOptions.FromSearchOption(searchOption));
+
+ public IEnumerable<FileSystemInfo> EnumerateFileSystemInfos(string searchPattern, EnumerationOptions enumerationOptions)
+ => InternalEnumerateInfos(FullPath, searchPattern, SearchTarget.Both, enumerationOptions);
+
+ internal static IEnumerable<FileSystemInfo> InternalEnumerateInfos(
+ string path,
+ string searchPattern,
+ SearchTarget searchTarget,
+ EnumerationOptions options)
{
+ Debug.Assert(path != null);
if (searchPattern == null)
throw new ArgumentNullException(nameof(searchPattern));
- if ((searchOption != SearchOption.TopDirectoryOnly) && (searchOption != SearchOption.AllDirectories))
- throw new ArgumentOutOfRangeException(nameof(searchOption), SR.ArgumentOutOfRange_Enum);
-
- return InternalEnumerateFileSystemInfos(searchPattern, searchOption);
- }
- private IEnumerable<FileSystemInfo> InternalEnumerateFileSystemInfos(string searchPattern, SearchOption searchOption)
- {
- Debug.Assert(searchPattern != null);
- Debug.Assert(searchOption == SearchOption.AllDirectories || searchOption == SearchOption.TopDirectoryOnly);
+ FileSystemEnumerableFactory.NormalizeInputs(ref path, ref searchPattern, options);
- return FileSystem.EnumerateFileSystemInfos(FullPath, searchPattern, searchOption, SearchTarget.Both);
+ switch (searchTarget)
+ {
+ case SearchTarget.Directories:
+ return FileSystemEnumerableFactory.DirectoryInfos(path, searchPattern, options);
+ case SearchTarget.Files:
+ return FileSystemEnumerableFactory.FileInfos(path, searchPattern, options);
+ case SearchTarget.Both:
+ return FileSystemEnumerableFactory.FileSystemInfos(path, searchPattern, options);
+ default:
+ throw new ArgumentException(SR.ArgumentOutOfRange_Enum, nameof(searchTarget));
+ }
}
// Returns the root portion of the given path. The resulting string
diff --git a/src/System.IO.FileSystem/src/System/IO/Enumeration/EnumerationOptions.cs b/src/System.IO.FileSystem/src/System/IO/Enumeration/EnumerationOptions.cs
new file mode 100644
index 0000000000..164cf3ed6e
--- /dev/null
+++ b/src/System.IO.FileSystem/src/System/IO/Enumeration/EnumerationOptions.cs
@@ -0,0 +1,84 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.IO.Enumeration
+{
+ public class EnumerationOptions
+ {
+ /// <summary>
+ /// For internal use. These are the options we want to use if calling the existing Directory/File APIs where you don't
+ /// explicitly specify EnumerationOptions.
+ /// </summary>
+ internal static EnumerationOptions Compatible { get; } = new EnumerationOptions { MatchType = MatchType.Dos };
+
+ private static EnumerationOptions CompatibleRecursive { get; } = new EnumerationOptions { RecurseSubdirectories = true, MatchType = MatchType.Dos };
+
+ /// <summary>
+ /// Internal singleton for default options.
+ /// </summary>
+ internal static EnumerationOptions Default { get; } = new EnumerationOptions();
+
+ /// <summary>
+ /// Default constructor. Constructs the options class with recommended default options.
+ /// </summary>
+ public EnumerationOptions()
+ {
+ }
+
+ /// <summary>
+ /// Converts SearchOptions to FindOptions. Throws if undefined SearchOption.
+ /// </summary>
+ internal static EnumerationOptions FromSearchOption(SearchOption searchOption)
+ {
+ if ((searchOption != SearchOption.TopDirectoryOnly) && (searchOption != SearchOption.AllDirectories))
+ throw new ArgumentOutOfRangeException(nameof(searchOption), SR.ArgumentOutOfRange_Enum);
+
+ return searchOption == SearchOption.AllDirectories ? CompatibleRecursive : Compatible;
+ }
+
+ /// <summary>
+ /// Should we recurse into subdirectories while enumerating?
+ /// </summary>
+ public bool RecurseSubdirectories { get; set; }
+
+ /// <summary>
+ /// Skip files/directories when access is denied (e.g. AccessDeniedException/SecurityException)
+ /// </summary>
+ public bool IgnoreInaccessible { get; set; }
+
+ /// <summary>
+ /// Suggested buffer size, in bytes.
+ /// </summary>
+ /// <remarks>
+ /// Not all platforms use user allocated buffers, and some require either fixed buffers or a
+ /// buffer that has enough space to return a full result. One scenario where this option is
+ /// useful is with remote share enumeration on Windows. Having a large buffer may result in
+ /// better performance as more results can be batched over the wire (e.g. over a network
+ /// share). A "large" buffer, for example, would be 16K. Typical is 4K.
+ ///
+ /// We will not use the suggested buffer size if it has no meaning for the native APIs on the
+ /// current platform or if it would be too small for getting at least a single result.
+ /// </remarks>
+ public int BufferSize { get; set; }
+
+ /// <summary>
+ /// Skip entries with the given attributes.
+ /// </summary>
+ public FileAttributes AttributesToSkip { get; set; }
+
+ /// <summary>
+ /// For APIs that allow specifying a match expression this will allow you to specify how
+ /// to interpret the match expression.
+ /// </summary>
+ /// <remarks>
+ /// The default is simple matching where '*' is always 0 or more characters and '?' is a single character.
+ /// </remarks>
+ public MatchType MatchType { get; set; }
+
+ /// <summary>
+ /// Set to true to return "." and ".." directory entries.
+ /// </summary>
+ public bool ReturnSpecialDirectories { get; set; }
+ }
+}
diff --git a/src/System.IO.FileSystem/src/System/IO/Enumeration/FileSystemEntry.Unix.cs b/src/System.IO.FileSystem/src/System/IO/Enumeration/FileSystemEntry.Unix.cs
new file mode 100644
index 0000000000..a4645cdf80
--- /dev/null
+++ b/src/System.IO.FileSystem/src/System/IO/Enumeration/FileSystemEntry.Unix.cs
@@ -0,0 +1,86 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.IO.Enumeration
+{
+ /// <summary>
+ /// Lower level view of FileSystemInfo used for processing and filtering find results.
+ /// </summary>
+ public unsafe ref struct FileSystemEntry
+ {
+ // TODO: Unix implementation https://github.com/dotnet/corefx/issues/26715
+ // Inital implementation is naive and not optimized.
+
+ internal static void Initialize(
+ ref FileSystemEntry entry,
+ Interop.Sys.DirectoryEntry directoryEntry,
+ bool isDirectory,
+ ReadOnlySpan<char> directory,
+ string rootDirectory,
+ string originalRootDirectory)
+ {
+ entry._directoryEntry = directoryEntry;
+ entry._isDirectory = isDirectory;
+ entry.Directory = directory;
+ entry.RootDirectory = rootDirectory;
+ entry.OriginalRootDirectory = originalRootDirectory;
+ }
+
+ internal Interop.Sys.DirectoryEntry _directoryEntry;
+ private FileSystemInfo _info;
+ private bool _isDirectory;
+
+ private FileSystemInfo Info
+ {
+ get
+ {
+ if (_info == null)
+ {
+ string fullPath = PathHelpers.CombineNoChecks(Directory, _directoryEntry.InodeName);
+ _info = _isDirectory
+ ? (FileSystemInfo) new DirectoryInfo(fullPath, fullPath, _directoryEntry.InodeName, isNormalized: true)
+ : new FileInfo(fullPath, fullPath, _directoryEntry.InodeName, isNormalized: true);
+ _info.Refresh();
+ }
+ return _info;
+ }
+ }
+
+ /// <summary>
+ /// The full path of the directory this entry resides in.
+ /// </summary>
+ public ReadOnlySpan<char> Directory { get; private set; }
+
+ /// <summary>
+ /// The full path of the root directory used for the enumeration.
+ /// </summary>
+ public string RootDirectory { get; private set; }
+
+ /// <summary>
+ /// The root directory for the enumeration as specified in the constructor.
+ /// </summary>
+ public string OriginalRootDirectory { get; private set; }
+
+ public ReadOnlySpan<char> FileName => _directoryEntry.InodeName;
+ public FileAttributes Attributes => Info.Attributes;
+ public long Length => Info.LengthCore;
+ public DateTimeOffset CreationTimeUtc => Info.CreationTimeCore;
+ public DateTimeOffset LastAccessTimeUtc => Info.LastAccessTimeCore;
+ public DateTimeOffset LastWriteTimeUtc => Info.LastWriteTimeCore;
+ public bool IsDirectory => _isDirectory;
+ public FileSystemInfo ToFileSystemInfo() => Info;
+
+ /// <summary>
+ /// Returns the full path for find results, based on the initially provided path.
+ /// </summary>
+ public string ToSpecifiedFullPath() =>
+ PathHelpers.CombineNoChecks(OriginalRootDirectory, Directory.Slice(RootDirectory.Length), FileName);
+
+ /// <summary>
+ /// Returns the full path of the find result.
+ /// </summary>
+ public string ToFullPath() =>
+ PathHelpers.CombineNoChecks(Directory, FileName);
+ }
+}
diff --git a/src/System.IO.FileSystem/src/System/IO/Enumeration/FileSystemEntry.Windows.cs b/src/System.IO.FileSystem/src/System/IO/Enumeration/FileSystemEntry.Windows.cs
new file mode 100644
index 0000000000..48bf820148
--- /dev/null
+++ b/src/System.IO.FileSystem/src/System/IO/Enumeration/FileSystemEntry.Windows.cs
@@ -0,0 +1,91 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.IO.Enumeration
+{
+ /// <summary>
+ /// Lower level view of FileSystemInfo used for processing and filtering find results.
+ /// </summary>
+ public unsafe ref struct FileSystemEntry
+ {
+ internal static void Initialize(
+ ref FileSystemEntry entry,
+ Interop.NtDll.FILE_FULL_DIR_INFORMATION* info,
+ ReadOnlySpan<char> directory,
+ string rootDirectory,
+ string originalRootDirectory)
+ {
+ entry._info = info;
+ entry.Directory = directory;
+ entry.RootDirectory = rootDirectory;
+ entry.OriginalRootDirectory = originalRootDirectory;
+ }
+
+ internal unsafe Interop.NtDll.FILE_FULL_DIR_INFORMATION* _info;
+
+ /// <summary>
+ /// The full path of the directory this entry resides in.
+ /// </summary>
+ public ReadOnlySpan<char> Directory { get; private set; }
+
+ /// <summary>
+ /// The full path of the root directory used for the enumeration.
+ /// </summary>
+ public string RootDirectory { get; private set; }
+
+ /// <summary>
+ /// The root directory for the enumeration as specified in the constructor.
+ /// </summary>
+ public string OriginalRootDirectory { get; private set; }
+
+ /// <summary>
+ /// The file name for this entry.
+ /// </summary>
+ public ReadOnlySpan<char> FileName => _info->FileName;
+
+ /// <summary>
+ /// The attributes for this entry.
+ /// </summary>
+ public FileAttributes Attributes => _info->FileAttributes;
+
+ /// <summary>
+ /// The length of file in bytes.
+ /// </summary>
+ public long Length => _info->EndOfFile;
+
+ /// <summary>
+ /// The creation time for the entry or the oldest available time stamp if the
+ /// operating system does not support creation time stamps.
+ /// </summary>
+ public DateTimeOffset CreationTimeUtc => _info->CreationTime.ToDateTimeOffset();
+ public DateTimeOffset LastAccessTimeUtc => _info->LastAccessTime.ToDateTimeOffset();
+ public DateTimeOffset LastWriteTimeUtc => _info->LastWriteTime.ToDateTimeOffset();
+
+ /// <summary>
+ /// Returns true if this entry is a directory.
+ /// </summary>
+ public bool IsDirectory => (Attributes & FileAttributes.Directory) != 0;
+
+ public FileSystemInfo ToFileSystemInfo()
+ {
+ string fullPath = PathHelpers.CombineNoChecks(Directory, FileName);
+
+ return IsDirectory
+ ? DirectoryInfo.Create(fullPath, ref this)
+ : (FileSystemInfo)FileInfo.Create(fullPath, ref this);
+ }
+
+ /// <summary>
+ /// Returns the full path for find results, based on the initially provided path.
+ /// </summary>
+ public string ToSpecifiedFullPath() =>
+ PathHelpers.CombineNoChecks(OriginalRootDirectory, Directory.Slice(RootDirectory.Length), FileName);
+
+ /// <summary>
+ /// Returns the full path of the find result.
+ /// </summary>
+ public string ToFullPath() =>
+ PathHelpers.CombineNoChecks(Directory, FileName);
+ }
+}
diff --git a/src/System.IO.FileSystem/src/System/IO/Enumeration/FileSystemEnumerable.cs b/src/System.IO.FileSystem/src/System/IO/Enumeration/FileSystemEnumerable.cs
new file mode 100644
index 0000000000..b05b1375d6
--- /dev/null
+++ b/src/System.IO.FileSystem/src/System/IO/Enumeration/FileSystemEnumerable.cs
@@ -0,0 +1,66 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections;
+using System.Collections.Generic;
+using System.Threading;
+
+namespace System.IO.Enumeration
+{
+ public class FileSystemEnumerable<TResult> : IEnumerable<TResult>
+ {
+ private DelegateEnumerator _enumerator;
+ private readonly FindTransform _transform;
+ private readonly EnumerationOptions _options;
+ private readonly string _directory;
+
+ public FileSystemEnumerable(string directory, FindTransform transform, EnumerationOptions options = null)
+ {
+ _directory = directory ?? throw new ArgumentNullException(nameof(directory));
+ _transform = transform ?? throw new ArgumentNullException(nameof(transform));
+ _options = options ?? EnumerationOptions.Default;
+
+ // We need to create the enumerator up front to ensure that we throw I/O exceptions for
+ // the root directory on creation of the enumerable.
+ _enumerator = new DelegateEnumerator(this);
+ }
+
+ public FindPredicate ShouldIncludePredicate { get; set; }
+ public FindPredicate ShouldRecursePredicate { get; set; }
+
+ public IEnumerator<TResult> GetEnumerator()
+ {
+ return Interlocked.Exchange(ref _enumerator, null) ?? new DelegateEnumerator(this);
+ }
+
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+
+ /// <summary>
+ /// Delegate for filtering out find results.
+ /// </summary>
+ public delegate bool FindPredicate(ref FileSystemEntry entry);
+
+ /// <summary>
+ /// Delegate for transforming raw find data into a result.
+ /// </summary>
+ public delegate TResult FindTransform(ref FileSystemEntry entry);
+
+ private sealed class DelegateEnumerator : FileSystemEnumerator<TResult>
+ {
+ private readonly FileSystemEnumerable<TResult> _enumerable;
+
+ public DelegateEnumerator(FileSystemEnumerable<TResult> enumerable)
+ : base(enumerable._directory, enumerable._options)
+ {
+ _enumerable = enumerable;
+ }
+
+ protected override TResult TransformEntry(ref FileSystemEntry entry) => _enumerable._transform(ref entry);
+ protected override bool ShouldRecurseIntoEntry(ref FileSystemEntry entry)
+ => _enumerable.ShouldRecursePredicate?.Invoke(ref entry) ?? true;
+ protected override bool ShouldIncludeEntry(ref FileSystemEntry entry)
+ => _enumerable.ShouldIncludePredicate?.Invoke(ref entry) ?? true;
+ }
+ }
+}
diff --git a/src/System.IO.FileSystem/src/System/IO/Enumeration/FileSystemEnumerableFactory.cs b/src/System.IO.FileSystem/src/System/IO/Enumeration/FileSystemEnumerableFactory.cs
new file mode 100644
index 0000000000..a342528545
--- /dev/null
+++ b/src/System.IO.FileSystem/src/System/IO/Enumeration/FileSystemEnumerableFactory.cs
@@ -0,0 +1,170 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file ref the project root for more information.
+
+using System.Collections.Generic;
+
+namespace System.IO.Enumeration
+{
+ internal static class FileSystemEnumerableFactory
+ {
+ // These all have special meaning in DOS name matching. '\' is the escaping character (which conveniently
+ // is the directory separator and cannot be part of any path segment in Windows). The other three are the
+ // special case wildcards that we'll convert some * and ? into. They're also valid as filenames on Unix,
+ // which is not true in Windows and as such we'll escape any that occur on the input string.
+ private readonly static char[] s_unixEscapeChars = { '\\', '"', '<', '>' };
+
+ internal static void NormalizeInputs(ref string directory, ref string expression, EnumerationOptions options)
+ {
+ if (Path.IsPathRooted(expression))
+ throw new ArgumentException(SR.Arg_Path2IsRooted, nameof(expression));
+
+ // We always allowed breaking the passed ref directory and filter to be separated
+ // any way the user wanted. Looking for "C:\foo\*.cs" could be passed as "C:\" and
+ // "foo\*.cs" or "C:\foo" and "*.cs", for example. As such we need to combine and
+ // split the inputs if the expression contains a directory separator.
+ //
+ // We also allowed for expression to be "foo\" which would translate to "foo\*".
+
+ ReadOnlySpan<char> directoryName = PathHelpers.GetDirectoryNameNoChecks(expression.AsReadOnlySpan());
+
+ if (directoryName.Length != 0)
+ {
+ // Need to fix up the input paths
+ directory = PathHelpers.CombineNoChecks(directory, directoryName);
+ expression = expression.Substring(directoryName.Length + 1);
+ }
+
+ switch (options.MatchType)
+ {
+ case MatchType.Dos:
+ if (string.IsNullOrEmpty(expression) || expression == "." || expression == "*.*")
+ {
+ // Historically we always treated "." as "*"
+ expression = "*";
+ }
+ else
+ {
+ if (Path.DirectorySeparatorChar != '\\' && expression.IndexOfAny(s_unixEscapeChars) != -1)
+ {
+ // Backslash isn't the default separator, need to escape (e.g. Unix)
+ expression = expression.Replace("\\", "\\\\");
+
+ // Also need to escape the other special wild characters ('"', '<', and '>')
+ expression = expression.Replace("\"", "\\\"");
+ expression = expression.Replace(">", "\\>");
+ expression = expression.Replace("<", "\\<");
+ }
+
+ // Need to convert the expression to match Win32 behavior
+ expression = FileSystemName.TranslateDosExpression(expression);
+ }
+ break;
+ case MatchType.Simple:
+ break;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(options));
+ }
+ }
+
+ private static bool MatchesPattern(string expression, ReadOnlySpan<char> name, EnumerationOptions options)
+ {
+ switch (options.MatchType)
+ {
+ case MatchType.Simple:
+ return FileSystemName.MatchesSimpleExpression(expression, name, ignoreCase: !PathInternal.IsCaseSensitive);
+ case MatchType.Dos:
+ return FileSystemName.MatchesDosExpression(expression, name, ignoreCase: !PathInternal.IsCaseSensitive);
+ default:
+ throw new ArgumentOutOfRangeException(nameof(options));
+ }
+ }
+
+ internal static IEnumerable<string> UserFiles(string directory,
+ string expression,
+ EnumerationOptions options)
+ {
+ return new FileSystemEnumerable<string>(
+ directory,
+ (ref FileSystemEntry entry) => entry.ToSpecifiedFullPath(),
+ options)
+ {
+ ShouldIncludePredicate = (ref FileSystemEntry entry) =>
+ !entry.IsDirectory && MatchesPattern(expression, entry.FileName, options)
+ };
+ }
+
+ internal static IEnumerable<string> UserDirectories(string directory,
+ string expression,
+ EnumerationOptions options)
+ {
+ return new FileSystemEnumerable<string>(
+ directory,
+ (ref FileSystemEntry entry) => entry.ToSpecifiedFullPath(),
+ options)
+ {
+ ShouldIncludePredicate = (ref FileSystemEntry entry) =>
+ entry.IsDirectory && MatchesPattern(expression, entry.FileName, options)
+ };
+ }
+
+ internal static IEnumerable<string> UserEntries(string directory,
+ string expression,
+ EnumerationOptions options)
+ {
+ return new FileSystemEnumerable<string>(
+ directory,
+ (ref FileSystemEntry entry) => entry.ToSpecifiedFullPath(),
+ options)
+ {
+ ShouldIncludePredicate = (ref FileSystemEntry entry) =>
+ MatchesPattern(expression, entry.FileName, options)
+ };
+ }
+
+ internal static IEnumerable<FileInfo> FileInfos(
+ string directory,
+ string expression,
+ EnumerationOptions options)
+ {
+ return new FileSystemEnumerable<FileInfo>(
+ directory,
+ (ref FileSystemEntry entry) => (FileInfo)entry.ToFileSystemInfo(),
+ options)
+ {
+ ShouldIncludePredicate = (ref FileSystemEntry entry) =>
+ !entry.IsDirectory && MatchesPattern(expression, entry.FileName, options)
+ };
+ }
+
+ internal static IEnumerable<DirectoryInfo> DirectoryInfos(
+ string directory,
+ string expression,
+ EnumerationOptions options)
+ {
+ return new FileSystemEnumerable<DirectoryInfo>(
+ directory,
+ (ref FileSystemEntry entry) => (DirectoryInfo)entry.ToFileSystemInfo(),
+ options)
+ {
+ ShouldIncludePredicate = (ref FileSystemEntry entry) =>
+ entry.IsDirectory && MatchesPattern(expression, entry.FileName, options)
+ };
+ }
+
+ internal static IEnumerable<FileSystemInfo> FileSystemInfos(
+ string directory,
+ string expression,
+ EnumerationOptions options)
+ {
+ return new FileSystemEnumerable<FileSystemInfo>(
+ directory,
+ (ref FileSystemEntry entry) => entry.ToFileSystemInfo(),
+ options)
+ {
+ ShouldIncludePredicate = (ref FileSystemEntry entry) =>
+ MatchesPattern(expression, entry.FileName, options)
+ };
+ }
+ }
+}
diff --git a/src/System.IO.FileSystem/src/System/IO/Enumeration/FileSystemEnumerator.Unix.cs b/src/System.IO.FileSystem/src/System/IO/Enumeration/FileSystemEnumerator.Unix.cs
new file mode 100644
index 0000000000..eaadea9bcb
--- /dev/null
+++ b/src/System.IO.FileSystem/src/System/IO/Enumeration/FileSystemEnumerator.Unix.cs
@@ -0,0 +1,191 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Runtime.ConstrainedExecution;
+using System.Threading;
+using Microsoft.Win32.SafeHandles;
+
+namespace System.IO.Enumeration
+{
+ public unsafe abstract partial class FileSystemEnumerator<TResult> : CriticalFinalizerObject, IEnumerator<TResult>
+ {
+ private readonly string _originalRootDirectory;
+ private readonly string _rootDirectory;
+ private readonly EnumerationOptions _options;
+
+ private readonly object _lock = new object();
+
+ private string _currentPath;
+ private SafeDirectoryHandle _directoryHandle;
+ private bool _lastEntryFound;
+ private Queue<(SafeDirectoryHandle Handle, string Path)> _pending;
+
+ private Interop.Sys.DirectoryEntry _entry;
+ private TResult _current;
+
+ /// <summary>
+ /// Encapsulates a find operation.
+ /// </summary>
+ /// <param name="directory">The directory to search in.</param>
+ /// <param name="options">Enumeration options to use.</param>
+ public FileSystemEnumerator(string directory, EnumerationOptions options = null)
+ {
+ _originalRootDirectory = directory ?? throw new ArgumentNullException(nameof(directory));
+ _rootDirectory = Path.GetFullPath(directory);
+ _options = options ?? EnumerationOptions.Default;
+
+ // We need to initialize the directory handle up front to ensure
+ // we immediately throw IO exceptions for missing directory/etc.
+ _directoryHandle = CreateDirectoryHandle(_rootDirectory);
+ if (_directoryHandle == null)
+ _lastEntryFound = true;
+
+ _currentPath = _rootDirectory;
+ }
+
+ private static SafeDirectoryHandle CreateDirectoryHandle(string path)
+ {
+ // TODO: https://github.com/dotnet/corefx/issues/26715
+ // - Check access denied option and allow through if specified.
+ // - Use IntPtr handle directly
+ Microsoft.Win32.SafeHandles.SafeDirectoryHandle handle = Interop.Sys.OpenDir(path);
+ if (handle.IsInvalid)
+ {
+ throw Interop.GetExceptionForIoErrno(Interop.Sys.GetLastErrorInfo(), path, isDirectory: true);
+ }
+ return handle;
+ }
+
+ private void CloseDirectoryHandle()
+ {
+ _directoryHandle?.Dispose();
+ _directoryHandle = null;
+ }
+
+ public bool MoveNext()
+ {
+ if (_lastEntryFound)
+ return false;
+
+ FileSystemEntry entry = default;
+
+ lock (_lock)
+ {
+ if (_lastEntryFound)
+ return false;
+
+ do
+ {
+ FindNextEntry();
+ if (_lastEntryFound)
+ return false;
+
+ // Get from the dir entry whether the entry is a file or directory.
+ // We classify everything as a file unless we know it to be a directory.
+ // (This includes regular files, FIFOs, etc.)
+ bool isDirectory = false;
+ if (_entry.InodeType == Interop.Sys.NodeType.DT_DIR)
+ {
+ // We know it's a directory.
+ isDirectory = true;
+ }
+ else if (_entry.InodeType == Interop.Sys.NodeType.DT_LNK || _entry.InodeType == Interop.Sys.NodeType.DT_UNKNOWN)
+ {
+ // It's a symlink or unknown: stat to it to see if we can resolve it to a directory.
+ // If we can't (e.g. symlink to a file, broken symlink, etc.), we'll just treat it as a file.
+ isDirectory = FileSystem.DirectoryExists(Path.Combine(_currentPath, _entry.InodeName));
+ }
+
+ if (_options.AttributesToSkip != 0)
+ {
+ if (((_options.AttributesToSkip & FileAttributes.Directory) != 0 && isDirectory)
+ || ((_options.AttributesToSkip & FileAttributes.Hidden) != 0 && _entry.InodeName[0] == '.')
+ || ((_options.AttributesToSkip & FileAttributes.ReparsePoint) != 0 && _entry.InodeType == Interop.Sys.NodeType.DT_LNK))
+ continue;
+
+ // TODO: https://github.com/dotnet/corefx/issues/26715
+ // Handle readonly skipping
+ }
+
+ FileSystemEntry.Initialize(ref entry, _entry, isDirectory, _currentPath, _rootDirectory, _originalRootDirectory);
+
+ if (isDirectory)
+ {
+ // Subdirectory found
+ if (PathHelpers.IsDotOrDotDot(_entry.InodeName))
+ {
+ // "." or "..", don't process unless the option is set
+ if (!_options.ReturnSpecialDirectories)
+ continue;
+ }
+ else if (_options.RecurseSubdirectories && ShouldRecurseIntoEntry(ref entry))
+ {
+ // Recursion is on and the directory was accepted, Queue it
+ string subdirectory = PathHelpers.CombineNoChecks(_currentPath, _entry.InodeName);
+ SafeDirectoryHandle subdirectoryHandle = CreateDirectoryHandle(subdirectory);
+ if (subdirectoryHandle != null)
+ {
+ try
+ {
+ if (_pending == null)
+ _pending = new Queue<(SafeDirectoryHandle, string)>();
+ _pending.Enqueue((subdirectoryHandle, subdirectory));
+ }
+ catch
+ {
+ // Couldn't queue the handle, close it and rethrow
+ subdirectoryHandle.Dispose();
+ throw;
+ }
+ }
+ }
+ }
+
+ if (ShouldIncludeEntry(ref entry))
+ {
+ _current = TransformEntry(ref entry);
+ return true;
+ }
+ } while (true);
+ }
+ }
+
+ private unsafe void FindNextEntry()
+ {
+ // Read each entry from the enumerator
+ if (Interop.Sys.ReadDir(_directoryHandle, out _entry) != 0)
+ {
+ // TODO: https://github.com/dotnet/corefx/issues/26715
+ // - Refactor ReadDir so we can process errors here
+
+ // Directory finished
+ DirectoryFinished();
+ }
+ }
+
+ private void InternalDispose(bool disposing)
+ {
+ // It is possible to fail to allocate the lock, but the finalizer will still run
+ if (_lock != null)
+ {
+ lock(_lock)
+ {
+ _lastEntryFound = true;
+
+ CloseDirectoryHandle();
+
+ if (_pending != null)
+ {
+ while (_pending.Count > 0)
+ _pending.Dequeue().Handle.Dispose();
+ _pending = null;
+ }
+ }
+ }
+
+ Dispose(disposing);
+ }
+ }
+}
diff --git a/src/System.IO.FileSystem/src/System/IO/FindEnumerable.Win32.cs b/src/System.IO.FileSystem/src/System/IO/Enumeration/FileSystemEnumerator.Win32.cs
index a978d7cb85..57d9cbfae0 100644
--- a/src/System.IO.FileSystem/src/System/IO/FindEnumerable.Win32.cs
+++ b/src/System.IO.FileSystem/src/System/IO/Enumeration/FileSystemEnumerator.Win32.cs
@@ -5,10 +5,11 @@
using System.Diagnostics;
using System.Runtime.CompilerServices;
-namespace System.IO
+namespace System.IO.Enumeration
{
- internal partial class FindEnumerable<TResult, TState>
+ public partial class FileSystemEnumerator<TResult>
{
+ /// <returns>'true' if new data was found</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe bool GetData()
{
@@ -36,7 +37,15 @@ namespace System.IO
Debug.Assert(statusBlock.Information.ToInt64() != 0);
return true;
default:
- throw Win32Marshal.GetExceptionForWin32Error((int)Interop.NtDll.RtlNtStatusToDosError(status), _currentPath);
+ int error = (int)Interop.NtDll.RtlNtStatusToDosError(status);
+
+ // Note that there are many NT status codes that convert to ERROR_ACCESS_DENIED.
+ if ((error == Interop.Errors.ERROR_ACCESS_DENIED && _options.IgnoreInaccessible) || ContinueOnError(error))
+ {
+ DirectoryFinished();
+ return false;
+ }
+ throw Win32Marshal.GetExceptionForWin32Error(error, _currentPath);
}
}
}
diff --git a/src/System.IO.FileSystem/src/System/IO/FindEnumerable.WinRT.cs b/src/System.IO.FileSystem/src/System/IO/Enumeration/FileSystemEnumerator.WinRT.cs
index 61e8b05d4d..ee57114803 100644
--- a/src/System.IO.FileSystem/src/System/IO/FindEnumerable.WinRT.cs
+++ b/src/System.IO.FileSystem/src/System/IO/Enumeration/FileSystemEnumerator.WinRT.cs
@@ -5,9 +5,9 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
-namespace System.IO
+namespace System.IO.Enumeration
{
- internal partial class FindEnumerable<TResult, TState>
+ public partial class FileSystemEnumerator<TResult>
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe bool GetData()
@@ -24,9 +24,16 @@ namespace System.IO
case Interop.Errors.ERROR_NO_MORE_FILES:
DirectoryFinished();
return false;
- default:
- throw Win32Marshal.GetExceptionForWin32Error(error, _currentPath);
+ case Interop.Errors.ERROR_ACCESS_DENIED:
+ if (_options.IgnoreInaccessible)
+ {
+ return false;
+ }
+ break;
}
+
+ if (!ContinueOnError(error))
+ throw Win32Marshal.GetExceptionForWin32Error(error, _currentPath);
}
return true;
diff --git a/src/System.IO.FileSystem/src/System/IO/Enumeration/FileSystemEnumerator.Windows.cs b/src/System.IO.FileSystem/src/System/IO/Enumeration/FileSystemEnumerator.Windows.cs
new file mode 100644
index 0000000000..18fa129367
--- /dev/null
+++ b/src/System.IO.FileSystem/src/System/IO/Enumeration/FileSystemEnumerator.Windows.cs
@@ -0,0 +1,230 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Buffers;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using System.Runtime.ConstrainedExecution;
+using System.Runtime.InteropServices;
+using System.Threading;
+
+namespace System.IO.Enumeration
+{
+ public unsafe abstract partial class FileSystemEnumerator<TResult> : CriticalFinalizerObject, IEnumerator<TResult>
+ {
+ private const int StandardBufferSize = 4096;
+
+ // We need to have enough room for at least a single entry. The filename alone can be 512 bytes, we'll ensure we have
+ // a reasonable buffer for all of the other metadata as well.
+ private const int MinimumBufferSize = 1024;
+
+ private readonly string _originalRootDirectory;
+ private readonly string _rootDirectory;
+ private readonly EnumerationOptions _options;
+
+ private readonly object _lock = new object();
+
+ private Interop.NtDll.FILE_FULL_DIR_INFORMATION* _entry;
+ private TResult _current;
+
+ private byte[] _buffer;
+ private IntPtr _directoryHandle;
+ private string _currentPath;
+ private bool _lastEntryFound;
+ private Queue<(IntPtr Handle, string Path)> _pending;
+ private GCHandle _pinnedBuffer;
+
+ /// <summary>
+ /// Encapsulates a find operation.
+ /// </summary>
+ /// <param name="directory">The directory to search in.</param>
+ /// <param name="options">Enumeration options to use.</param>
+ public FileSystemEnumerator(string directory, EnumerationOptions options = null)
+ {
+ _originalRootDirectory = directory ?? throw new ArgumentNullException(nameof(directory));
+ _rootDirectory = Path.GetFullPath(directory);
+ _options = options ?? EnumerationOptions.Default;
+
+ // We'll only suppress the media insertion prompt on the topmost directory as that is the
+ // most likely scenario and we don't want to take the perf hit for large enumerations.
+ // (We weren't consistent with how we handled this historically.)
+ using (new DisableMediaInsertionPrompt())
+ {
+ // We need to initialize the directory handle up front to ensure
+ // we immediately throw IO exceptions for missing directory/etc.
+ _directoryHandle = CreateDirectoryHandle(_rootDirectory);
+ if (_directoryHandle == IntPtr.Zero)
+ _lastEntryFound = true;
+ }
+
+ _currentPath = _rootDirectory;
+
+ int requestedBufferSize = _options.BufferSize;
+ int bufferSize = requestedBufferSize <= 0 ? StandardBufferSize
+ : Math.Max(MinimumBufferSize, requestedBufferSize);
+
+ try
+ {
+ _buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
+ _pinnedBuffer = GCHandle.Alloc(_buffer, GCHandleType.Pinned);
+ }
+ catch
+ {
+ // Close the directory handle right away if we fail to allocate
+ CloseDirectoryHandle();
+ throw;
+ }
+ }
+
+ private void CloseDirectoryHandle()
+ {
+ // As handles can be reused we want to be extra careful to close handles only once
+ IntPtr handle = Interlocked.Exchange(ref _directoryHandle, IntPtr.Zero);
+ if (handle != IntPtr.Zero)
+ Interop.Kernel32.CloseHandle(handle);
+ }
+
+ /// <summary>
+ /// Simple wrapper to allow creating a file handle for an existing directory.
+ /// </summary>
+ private IntPtr CreateDirectoryHandle(string path)
+ {
+ IntPtr handle = Interop.Kernel32.CreateFile_IntPtr(
+ path,
+ Interop.Kernel32.FileOperations.FILE_LIST_DIRECTORY,
+ FileShare.ReadWrite | FileShare.Delete,
+ FileMode.Open,
+ Interop.Kernel32.FileOperations.FILE_FLAG_BACKUP_SEMANTICS);
+
+ if (handle == IntPtr.Zero || handle == (IntPtr)(-1))
+ {
+ int error = Marshal.GetLastWin32Error();
+
+ if ((error == Interop.Errors.ERROR_ACCESS_DENIED &&
+ _options.IgnoreInaccessible) || ContinueOnError(error))
+ {
+ return IntPtr.Zero;
+ }
+
+ if (error == Interop.Errors.ERROR_FILE_NOT_FOUND)
+ {
+ // Historically we throw directory not found rather than file not found
+ error = Interop.Errors.ERROR_PATH_NOT_FOUND;
+ }
+
+ throw Win32Marshal.GetExceptionForWin32Error(error, path);
+ }
+
+ return handle;
+ }
+
+ public bool MoveNext()
+ {
+ if (_lastEntryFound)
+ return false;
+
+ FileSystemEntry entry = default;
+
+ lock (_lock)
+ {
+ if (_lastEntryFound)
+ return false;
+
+ do
+ {
+ FindNextEntry();
+ if (_lastEntryFound)
+ return false;
+
+ // Calling the constructor inside the try block would create a second instance on the stack.
+ FileSystemEntry.Initialize(ref entry, _entry, _currentPath, _rootDirectory, _originalRootDirectory);
+
+ // Skip specified attributes
+ if ((_entry->FileAttributes & _options.AttributesToSkip) != 0)
+ continue;
+
+ if ((_entry->FileAttributes & FileAttributes.Directory) != 0)
+ {
+ // Subdirectory found
+ if (PathHelpers.IsDotOrDotDot(_entry->FileName))
+ {
+ // "." or "..", don't process unless the option is set
+ if (!_options.ReturnSpecialDirectories)
+ continue;
+ }
+ else if (_options.RecurseSubdirectories && ShouldRecurseIntoEntry(ref entry))
+ {
+ // Recursion is on and the directory was accepted, Queue it
+ string subDirectory = PathHelpers.CombineNoChecks(_currentPath, _entry->FileName);
+ IntPtr subDirectoryHandle = CreateDirectoryHandle(subDirectory);
+ if (subDirectoryHandle != IntPtr.Zero)
+ {
+ try
+ {
+ if (_pending == null)
+ _pending = new Queue<(IntPtr, string)>();
+ _pending.Enqueue((subDirectoryHandle, subDirectory));
+ }
+ catch
+ {
+ // Couldn't queue the handle, close it and rethrow
+ Interop.Kernel32.CloseHandle(subDirectoryHandle);
+ throw;
+ }
+ }
+ }
+ }
+
+ if (ShouldIncludeEntry(ref entry))
+ {
+ _current = TransformEntry(ref entry);
+ return true;
+ }
+ } while (true);
+ }
+ }
+
+ private unsafe void FindNextEntry()
+ {
+ _entry = Interop.NtDll.FILE_FULL_DIR_INFORMATION.GetNextInfo(_entry);
+ if (_entry != null)
+ return;
+
+ // We need more data
+ if (GetData())
+ _entry = (Interop.NtDll.FILE_FULL_DIR_INFORMATION*)_pinnedBuffer.AddrOfPinnedObject();
+ }
+
+ private void InternalDispose(bool disposing)
+ {
+ // It is possible to fail to allocate the lock, but the finalizer will still run
+ if (_lock != null)
+ {
+ lock (_lock)
+ {
+ _lastEntryFound = true;
+
+ CloseDirectoryHandle();
+
+ if (_pending != null)
+ {
+ while (_pending.Count > 0)
+ Interop.Kernel32.CloseHandle(_pending.Dequeue().Handle);
+ _pending = null;
+ }
+
+ if (_pinnedBuffer.IsAllocated)
+ _pinnedBuffer.Free();
+
+ if (_buffer != null)
+ ArrayPool<byte>.Shared.Return(_buffer);
+
+ _buffer = null;
+ }
+ }
+
+ Dispose(disposing);
+ }
+ }
+}
diff --git a/src/System.IO.FileSystem/src/System/IO/Enumeration/FileSystemEnumerator.cs b/src/System.IO.FileSystem/src/System/IO/Enumeration/FileSystemEnumerator.cs
new file mode 100644
index 0000000000..01bcd7babd
--- /dev/null
+++ b/src/System.IO.FileSystem/src/System/IO/Enumeration/FileSystemEnumerator.cs
@@ -0,0 +1,89 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections;
+using System.Collections.Generic;
+using System.Runtime.ConstrainedExecution;
+
+namespace System.IO.Enumeration
+{
+ public unsafe abstract partial class FileSystemEnumerator<TResult> : CriticalFinalizerObject, IEnumerator<TResult>
+ {
+ /// <summary>
+ /// Return true if the given file system entry should be included in the results.
+ /// </summary>
+ protected virtual bool ShouldIncludeEntry(ref FileSystemEntry entry) => true;
+
+ /// <summary>
+ /// Return true if the directory entry given should be recursed into.
+ /// </summary>
+ protected virtual bool ShouldRecurseIntoEntry(ref FileSystemEntry entry) => true;
+
+ /// <summary>
+ /// Generate the result type from the current entry;
+ /// </summary>
+ protected abstract TResult TransformEntry(ref FileSystemEntry entry);
+
+ /// <summary>
+ /// Called whenever the end of a directory is reached.
+ /// </summary>
+ /// <param name="directory">The path of the directory that finished.</param>
+ protected virtual void OnDirectoryFinished(ReadOnlySpan<char> directory) { }
+
+ /// <summary>
+ /// Called when a native API returns an error. Return true to continue, or false
+ /// to throw the default exception for the given error.
+ /// </summary>
+ /// <param name="error">The native error code.</param>
+ protected virtual bool ContinueOnError(int error) => false;
+
+ public TResult Current => _current;
+
+ object IEnumerator.Current => Current;
+
+ private void DirectoryFinished()
+ {
+ _entry = default;
+
+ // Close the handle now that we're done
+ CloseDirectoryHandle();
+ OnDirectoryFinished(_currentPath);
+
+ if (_pending == null || _pending.Count == 0)
+ {
+ _lastEntryFound = true;
+ }
+ else
+ {
+ // Grab the next directory to parse
+ (_directoryHandle, _currentPath) = _pending.Dequeue();
+ FindNextEntry();
+ }
+ }
+
+ public void Reset()
+ {
+ throw new NotSupportedException();
+ }
+
+ public void Dispose()
+ {
+ InternalDispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
+
+ /// <summary>
+ /// Override for any additional cleanup.
+ /// </summary>
+ /// <param name="disposing">True if called while disposing. False if called from finalizer.</param>
+ protected virtual void Dispose(bool disposing)
+ {
+ }
+
+ ~FileSystemEnumerator()
+ {
+ InternalDispose(disposing: false);
+ }
+ }
+}
diff --git a/src/System.IO.FileSystem/src/System/IO/DosMatcher.cs b/src/System.IO.FileSystem/src/System/IO/Enumeration/FileSystemName.cs
index b1f677efb6..dfdd1dd42f 100644
--- a/src/System.IO.FileSystem/src/System/IO/DosMatcher.cs
+++ b/src/System.IO.FileSystem/src/System/IO/Enumeration/FileSystemName.cs
@@ -5,9 +5,12 @@
using System;
using System.Text;
-namespace System.IO
+namespace System.IO.Enumeration
{
- internal static class DosMatcher
+ /// <summary>
+ /// Provides methods for matching file system names.
+ /// </summary>
+ public static class FileSystemName
{
// [MS - FSA] 2.1.4.4 Algorithm for Determining if a FileName Is in an Expression
// https://msdn.microsoft.com/en-us/library/ff469270.aspx
@@ -16,11 +19,16 @@ namespace System.IO
'\"', '<', '>', '*', '?'
};
+ private static readonly char[] s_simpleWildcardChars =
+ {
+ '*', '?'
+ };
+
/// <summary>
/// Change '*' and '?' to '&lt;', '&gt;' and '"' to match Win32 behavior. For compatibility, Windows
/// changes some wildcards to provide a closer match to historical DOS 8.3 filename matching.
/// </summary>
- internal static string TranslateExpression(string expression)
+ public static string TranslateDosExpression(string expression)
{
if (string.IsNullOrEmpty(expression) || expression == "*" || expression == "*.*")
return "*";
@@ -64,7 +72,8 @@ namespace System.IO
}
/// <summary>
- /// Return true if the given expression matches the given name.
+ /// Return true if the given expression matches the given name. Supports the following wildcards:
+ /// '*', '?', '&lt;', '&gt;', '"'. The backslash character '\' escapes.
/// </summary>
/// <param name="expression">The expression to match with, such as "*.foo".</param>
/// <param name="name">The name to check against the expression.</param>
@@ -74,16 +83,29 @@ namespace System.IO
/// of RtlIsNameInExpression, which defines the rules for matching DOS wildcards ('*', '?', '&lt;', '&gt;', '"').
///
/// Like PatternMatcher, matching will not line up with Win32 behavior unless you transform the expression
- /// using <see cref="TranslateExpression(string)"/>
+ /// using <see cref="TranslateDosExpression(string)"/>
/// </remarks>
- internal static bool MatchPattern(string expression, ReadOnlySpan<char> name, bool ignoreCase = true)
+ public static bool MatchesDosExpression(ReadOnlySpan<char> expression, ReadOnlySpan<char> name, bool ignoreCase = true)
+ {
+ return MatchPattern(expression, name, ignoreCase, useExtendedWildcards: true);
+ }
+
+ /// <summary>
+ /// Return true if the given expression matches the given name. '*' and '?' are wildcards, '\' escapes.
+ /// </summary>
+ public static bool MatchesSimpleExpression(ReadOnlySpan<char> expression, ReadOnlySpan<char> name, bool ignoreCase = true)
+ {
+ return MatchPattern(expression, name, ignoreCase, useExtendedWildcards: false);
+ }
+
+ private static bool MatchPattern(ReadOnlySpan<char> expression, ReadOnlySpan<char> name, bool ignoreCase, bool useExtendedWildcards)
{
// The idea behind the algorithm is pretty simple. We keep track of all possible locations
// in the regular expression that are matching the name. When the name has been exhausted,
// if one of the locations in the expression is also just exhausted, the name is in the
// language defined by the regular expression.
- if (string.IsNullOrEmpty(expression) || name.Length == 0)
+ if (expression.Length == 0 || name.Length == 0)
return false;
if (expression[0] == '*')
@@ -92,16 +114,17 @@ namespace System.IO
if (expression.Length == 1)
return true;
- if (expression.IndexOfAny(s_wildcardChars, startIndex: 1) == -1)
+ ReadOnlySpan<char> expressionEnd = expression.Slice(1);
+ if (expressionEnd.IndexOfAny(useExtendedWildcards ? s_wildcardChars : s_simpleWildcardChars) == -1)
{
// Handle the special case of a single starting *, which essentially means "ends with"
// If the name doesn't have enough characters to match the remaining expression, it can't be a match.
- if (name.Length < expression.Length - 1)
+ if (name.Length < expressionEnd.Length)
return false;
- // See if we end with the expression (minus the *, of course)
- return name.EndsWithOrdinal(expression.AsReadOnlySpan().Slice(1), ignoreCase);
+ // See if we end with the expression
+ return name.EndsWithOrdinal(expressionEnd, ignoreCase);
}
}
@@ -124,6 +147,26 @@ namespace System.IO
int currentState;
bool nameFinished = false;
+ // Walk through the name string, picking off characters. We go one
+ // character beyond the end because some wild cards are able to match
+ // zero characters beyond the end of the string.
+ //
+ // With each new name character we determine a new set of states that
+ // match the name so far. We use two arrays that we swap back and forth
+ // for this purpose. One array lists the possible expression states for
+ // all name characters up to but not including the current one, and other
+ // array is used to build up the list of states considering the current
+ // name character as well. The arrays are then switched and the process
+ // repeated.
+ //
+ // There is not a one-to-one correspondence between state number and
+ // offset into the expression. State numbering is not continuous.
+ // This allows a simple conversion between state number and expression
+ // offset. Each character in the expression can represent one or two
+ // states. * and DOS_STAR generate two states: expressionOffset * 2 and
+ // expressionOffset * 2 + 1. All other expression characters can produce
+ // only a single state. Thus expressionOffset = currentState / 2.
+
while (!nameFinished)
{
if (nameOffset < name.Length)
@@ -177,7 +220,7 @@ namespace System.IO
// '*' matches any character zero or more times.
goto MatchZeroOrMore;
}
- else if (expressionChar == '<')
+ else if (useExtendedWildcards && expressionChar == '<')
{
// '<' (DOS_STAR) matches any character except '.' zero or more times.
@@ -210,11 +253,11 @@ namespace System.IO
}
else
{
- // The following expression characters all match by consuming
- // a character, thus force the expression, and thus state forward.
+ // The remaining expression characters all match by consuming a character,
+ // so we need to force the expression and state forward.
currentState += 2;
- if (expressionChar == '>')
+ if (useExtendedWildcards && expressionChar == '>')
{
// '>' (DOS_QM) is the most complicated. If the name is finished,
// we can match zero characters. If this name is a '.', we
@@ -226,7 +269,7 @@ namespace System.IO
currentMatches[currentMatch++] = currentState;
goto ExpressionFinished;
}
- else if (expressionChar == '"')
+ else if (useExtendedWildcards && expressionChar == '"')
{
// A '"' (DOS_DOT) can match either a period, or zero characters
// beyond the end of name.
@@ -242,6 +285,19 @@ namespace System.IO
}
else
{
+ if (expressionChar == '\\')
+ {
+ // Escape character, try to move the expression forward again and match literally.
+ if (++expressionOffset == expression.Length)
+ {
+ currentMatches[currentMatch++] = maxState;
+ goto ExpressionFinished;
+ }
+
+ currentState = expressionOffset * 2 + 2;
+ expressionChar = expression[expressionOffset];
+ }
+
// From this point on a name character is required to even
// continue, let alone make a match.
if (nameFinished) goto ExpressionFinished;
@@ -259,7 +315,6 @@ namespace System.IO
currentMatches[currentMatch++] = currentState;
}
- // The expression didn't match so move to the next prior match.
goto ExpressionFinished;
}
}
diff --git a/src/System.IO.FileSystem/src/System/IO/Enumeration/MatchType.cs b/src/System.IO.FileSystem/src/System/IO/Enumeration/MatchType.cs
new file mode 100644
index 0000000000..edd35620bc
--- /dev/null
+++ b/src/System.IO.FileSystem/src/System/IO/Enumeration/MatchType.cs
@@ -0,0 +1,20 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.IO.Enumeration
+{
+ public enum MatchType
+ {
+ /// <summary>
+ /// Match using '*' and '?' wildcards.
+ /// </summary>
+ Simple,
+
+ /// <summary>
+ /// Match using DOS style matching semantics. '*', '?', '&lt;', '&gt;', and '"'
+ /// are all considered wildcards.
+ /// </summary>
+ Dos
+ }
+}
diff --git a/src/System.IO.FileSystem/src/System/IO/FileInfo.Windows.cs b/src/System.IO.FileSystem/src/System/IO/FileInfo.Windows.cs
index cec962f3f4..8820620599 100644
--- a/src/System.IO.FileSystem/src/System/IO/FileInfo.Windows.cs
+++ b/src/System.IO.FileSystem/src/System/IO/FileInfo.Windows.cs
@@ -2,17 +2,17 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
-using System.Diagnostics;
+using System.IO.Enumeration;
namespace System.IO
{
partial class FileInfo
{
- internal unsafe FileInfo(string fullPath, string fileName, ref RawFindData findData)
- : this(fullPath, fileName: fileName, isNormalized: true)
+ internal static unsafe FileInfo Create(string fullPath, ref FileSystemEntry findData)
{
- Debug.Assert(fileName.Equals(Path.GetFileName(fullPath)));
- Init(findData._info);
+ FileInfo info = new FileInfo(fullPath, fileName: findData.FileName.GetStringFromFixedBuffer(), isNormalized: true);
+ info.Init(findData._info);
+ return info;
}
}
}
diff --git a/src/System.IO.FileSystem/src/System/IO/FileSystem.Unix.cs b/src/System.IO.FileSystem/src/System/IO/FileSystem.Unix.cs
index 955d568322..013c09e371 100644
--- a/src/System.IO.FileSystem/src/System/IO/FileSystem.Unix.cs
+++ b/src/System.IO.FileSystem/src/System/IO/FileSystem.Unix.cs
@@ -2,11 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
-using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
-using System.Runtime.InteropServices;
-using System.Threading;
namespace System.IO
{
@@ -337,7 +334,7 @@ namespace System.IO
{
try
{
- foreach (string item in EnumeratePaths(directory.FullName, "*", SearchOption.TopDirectoryOnly, SearchTarget.Both))
+ foreach (string item in Directory.EnumerateFileSystemEntries(directory.FullName))
{
if (!ShouldIgnoreDirectory(Path.GetFileName(item)))
{
@@ -444,249 +441,6 @@ namespace System.IO
((fileinfo.Mode & Interop.Sys.FileTypes.S_IFMT) == Interop.Sys.FileTypes.S_IFDIR);
}
- public static IEnumerable<string> EnumeratePaths(string path, string searchPattern, SearchOption searchOption, SearchTarget searchTarget)
- {
- return new FileSystemEnumerable<string>(path, searchPattern, searchOption, searchTarget, (p, _) => p);
- }
-
- public static IEnumerable<FileSystemInfo> EnumerateFileSystemInfos(string fullPath, string searchPattern, SearchOption searchOption, SearchTarget searchTarget)
- {
- switch (searchTarget)
- {
- case SearchTarget.Files:
- return new FileSystemEnumerable<FileInfo>(fullPath, searchPattern, searchOption, searchTarget, (path, isDir) =>
- {
- var info = new FileInfo(path, null);
- info.Refresh();
- return info;
- });
- case SearchTarget.Directories:
- return new FileSystemEnumerable<DirectoryInfo>(fullPath, searchPattern, searchOption, searchTarget, (path, isDir) =>
- {
- var info = new DirectoryInfo(path, null);
- info.Refresh();
- return info;
- });
- default:
- return new FileSystemEnumerable<FileSystemInfo>(fullPath, searchPattern, searchOption, searchTarget, (path, isDir) =>
- {
- var info = isDir ?
- (FileSystemInfo)new DirectoryInfo(path, null) :
- (FileSystemInfo)new FileInfo(path, null);
- info.Refresh();
- return info;
- });
- }
- }
-
- private sealed class FileSystemEnumerable<T> : IEnumerable<T>
- {
- private readonly PathPair _initialDirectory;
- private readonly string _searchPattern;
- private readonly SearchOption _searchOption;
- private readonly bool _includeFiles;
- private readonly bool _includeDirectories;
- private readonly Func<string, bool, T> _translateResult;
- private IEnumerator<T> _firstEnumerator;
-
- internal FileSystemEnumerable(
- string userPath, string searchPattern,
- SearchOption searchOption, SearchTarget searchTarget,
- Func<string, bool, T> translateResult)
- {
- // Basic validation of the input path
- if (userPath == null)
- {
- throw new ArgumentNullException("path");
- }
- if (string.IsNullOrEmpty(userPath))
- {
- throw new ArgumentException(SR.Argument_EmptyPath, "path");
- }
-
- // Validate and normalize the search pattern. If after doing so it's empty,
- // matching Win32 behavior we can skip all additional validation and effectively
- // return an empty enumerable.
- searchPattern = NormalizeSearchPattern(searchPattern);
- if (searchPattern.Length > 0)
- {
- PathHelpers.ThrowIfEmptyOrRootedPath(searchPattern);
-
- // If the search pattern contains any paths, make sure we factor those into
- // the user path, and then trim them off.
- int lastSlash = searchPattern.LastIndexOf(Path.DirectorySeparatorChar);
- if (lastSlash >= 0)
- {
- if (lastSlash >= 1)
- {
- userPath = Path.Combine(userPath, searchPattern.Substring(0, lastSlash));
- }
- searchPattern = searchPattern.Substring(lastSlash + 1);
- }
-
- // Typically we shouldn't see either of these cases, an upfront check is much faster
- foreach (char c in searchPattern)
- {
- if (c == '\\' || c == '[')
- {
- // We need to escape any escape characters in the search pattern
- searchPattern = searchPattern.Replace(@"\", @"\\");
-
- // And then escape '[' to prevent it being picked up as a wildcard
- searchPattern = searchPattern.Replace(@"[", @"\[");
- break;
- }
- }
-
- string fullPath = Path.GetFullPath(userPath);
-
- // Store everything for the enumerator
- _initialDirectory = new PathPair(userPath, fullPath);
- _searchPattern = searchPattern;
- _searchOption = searchOption;
- _includeFiles = (searchTarget & SearchTarget.Files) != 0;
- _includeDirectories = (searchTarget & SearchTarget.Directories) != 0;
- _translateResult = translateResult;
- }
-
- // Open the first enumerator so that any errors are propagated synchronously.
- _firstEnumerator = Enumerate();
- }
-
- public IEnumerator<T> GetEnumerator()
- {
- return Interlocked.Exchange(ref _firstEnumerator, null) ?? Enumerate();
- }
-
- IEnumerator IEnumerable.GetEnumerator()
- {
- return GetEnumerator();
- }
-
- private IEnumerator<T> Enumerate()
- {
- return Enumerate(
- _initialDirectory.FullPath != null ?
- OpenDirectory(_initialDirectory.FullPath) :
- null);
- }
-
- private IEnumerator<T> Enumerate(Microsoft.Win32.SafeHandles.SafeDirectoryHandle dirHandle)
- {
- if (dirHandle == null)
- {
- // Empty search
- yield break;
- }
-
- Debug.Assert(!dirHandle.IsInvalid);
- Debug.Assert(!dirHandle.IsClosed);
-
- // Maintain a stack of the directories to explore, in the case of SearchOption.AllDirectories
- // Lazily-initialized only if we find subdirectories that will be explored.
- Stack<PathPair> toExplore = null;
- PathPair dirPath = _initialDirectory;
- while (dirHandle != null)
- {
- try
- {
- // Read each entry from the enumerator
- Interop.Sys.DirectoryEntry dirent;
- while (Interop.Sys.ReadDir(dirHandle, out dirent) == 0)
- {
- // Get from the dir entry whether the entry is a file or directory.
- // We classify everything as a file unless we know it to be a directory.
- bool isDir;
- if (dirent.InodeType == Interop.Sys.NodeType.DT_DIR)
- {
- // We know it's a directory.
- isDir = true;
- }
- else if (dirent.InodeType == Interop.Sys.NodeType.DT_LNK || dirent.InodeType == Interop.Sys.NodeType.DT_UNKNOWN)
- {
- // It's a symlink or unknown: stat to it to see if we can resolve it to a directory.
- // If we can't (e.g. symlink to a file, broken symlink, etc.), we'll just treat it as a file.
- Interop.ErrorInfo errnoIgnored;
- isDir = DirectoryExists(Path.Combine(dirPath.FullPath, dirent.InodeName), out errnoIgnored);
- }
- else
- {
- // Otherwise, treat it as a file. This includes regular files, FIFOs, etc.
- isDir = false;
- }
-
- // Yield the result if the user has asked for it. In the case of directories,
- // always explore it by pushing it onto the stack, regardless of whether
- // we're returning directories.
- if (isDir)
- {
- if (!ShouldIgnoreDirectory(dirent.InodeName))
- {
- string userPath = null;
- if (_searchOption == SearchOption.AllDirectories)
- {
- if (toExplore == null)
- {
- toExplore = new Stack<PathPair>();
- }
- userPath = Path.Combine(dirPath.UserPath, dirent.InodeName);
- toExplore.Push(new PathPair(userPath, Path.Combine(dirPath.FullPath, dirent.InodeName)));
- }
- if (_includeDirectories &&
- Interop.Sys.FnMatch(_searchPattern, dirent.InodeName, Interop.Sys.FnMatchFlags.FNM_NONE) == 0)
- {
- yield return _translateResult(userPath ?? Path.Combine(dirPath.UserPath, dirent.InodeName), /*isDirectory*/true);
- }
- }
- }
- else if (_includeFiles &&
- Interop.Sys.FnMatch(_searchPattern, dirent.InodeName, Interop.Sys.FnMatchFlags.FNM_NONE) == 0)
- {
- yield return _translateResult(Path.Combine(dirPath.UserPath, dirent.InodeName), /*isDirectory*/false);
- }
- }
- }
- finally
- {
- // Close the directory enumerator
- dirHandle.Dispose();
- dirHandle = null;
- }
-
- if (toExplore != null && toExplore.Count > 0)
- {
- // Open the next directory.
- dirPath = toExplore.Pop();
- dirHandle = OpenDirectory(dirPath.FullPath);
- }
- }
- }
-
- private static string NormalizeSearchPattern(string searchPattern)
- {
- if (searchPattern == "." || searchPattern == "*.*")
- {
- searchPattern = "*";
- }
- else if (PathHelpers.EndsInDirectorySeparator(searchPattern))
- {
- searchPattern += "*";
- }
-
- return searchPattern;
- }
-
- private static Microsoft.Win32.SafeHandles.SafeDirectoryHandle OpenDirectory(string fullPath)
- {
- Microsoft.Win32.SafeHandles.SafeDirectoryHandle handle = Interop.Sys.OpenDir(fullPath);
- if (handle.IsInvalid)
- {
- throw Interop.GetExceptionForIoErrno(Interop.Sys.GetLastErrorInfo(), fullPath, isDirectory: true);
- }
- return handle;
- }
- }
-
/// <summary>Determines whether the specified directory name should be ignored.</summary>
/// <param name="name">The name to evaluate.</param>
/// <returns>true if the name is "." or ".."; otherwise, false.</returns>
@@ -762,13 +516,6 @@ namespace System.IO
info.LastWriteTimeCore = time;
}
- public static FileSystemInfo GetFileSystemInfo(string fullPath, bool asDirectory)
- {
- return asDirectory ?
- (FileSystemInfo)new DirectoryInfo(fullPath, null) :
- (FileSystemInfo)new FileInfo(fullPath, null);
- }
-
public static string[] GetLogicalDrives()
{
return DriveInfoInternal.GetLogicalDrives();
diff --git a/src/System.IO.FileSystem/src/System/IO/FileSystem.Windows.cs b/src/System.IO.FileSystem/src/System/IO/FileSystem.Windows.cs
index 84f8e6ff87..17bb79d37e 100644
--- a/src/System.IO.FileSystem/src/System/IO/FileSystem.Windows.cs
+++ b/src/System.IO.FileSystem/src/System/IO/FileSystem.Windows.cs
@@ -183,38 +183,6 @@ namespace System.IO
&& ((data.dwFileAttributes & Interop.Kernel32.FileAttributes.FILE_ATTRIBUTE_DIRECTORY) != 0);
}
- public static IEnumerable<string> EnumeratePaths(string fullPath, string searchPattern, SearchOption searchOption, SearchTarget searchTarget)
- {
- FindEnumerableFactory.NormalizeInputs(ref fullPath, ref searchPattern);
- switch (searchTarget)
- {
- case SearchTarget.Files:
- return FindEnumerableFactory.UserFiles(fullPath, searchPattern, searchOption == SearchOption.AllDirectories);
- case SearchTarget.Directories:
- return FindEnumerableFactory.UserDirectories(fullPath, searchPattern, searchOption == SearchOption.AllDirectories);
- case SearchTarget.Both:
- return FindEnumerableFactory.UserEntries(fullPath, searchPattern, searchOption == SearchOption.AllDirectories);
- default:
- throw new ArgumentOutOfRangeException(nameof(searchTarget));
- }
- }
-
- public static IEnumerable<FileSystemInfo> EnumerateFileSystemInfos(string fullPath, string searchPattern, SearchOption searchOption, SearchTarget searchTarget)
- {
- FindEnumerableFactory.NormalizeInputs(ref fullPath, ref searchPattern);
- switch (searchTarget)
- {
- case SearchTarget.Directories:
- return FindEnumerableFactory.DirectoryInfos(fullPath, searchPattern, searchOption == SearchOption.AllDirectories);
- case SearchTarget.Files:
- return FindEnumerableFactory.FileInfos(fullPath, searchPattern, searchOption == SearchOption.AllDirectories);
- case SearchTarget.Both:
- return FindEnumerableFactory.FileSystemInfos(fullPath, searchPattern, searchOption == SearchOption.AllDirectories);
- default:
- throw new ArgumentException(SR.ArgumentOutOfRange_Enum, nameof(searchTarget));
- }
- }
-
/// <summary>
/// Returns 0 on success, otherwise a Win32 error code. Note that
/// classes should use -1 as the uninitialized state for dataInitialized.
diff --git a/src/System.IO.FileSystem/src/System/IO/FindEnumerable.Windows.cs b/src/System.IO.FileSystem/src/System/IO/FindEnumerable.Windows.cs
deleted file mode 100644
index 7e64e97373..0000000000
--- a/src/System.IO.FileSystem/src/System/IO/FindEnumerable.Windows.cs
+++ /dev/null
@@ -1,251 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System.Buffers;
-using System.Collections;
-using System.Collections.Generic;
-using System.Runtime.CompilerServices;
-using System.Runtime.ConstrainedExecution;
-using System.Runtime.InteropServices;
-using System.Threading;
-
-namespace System.IO
-{
- internal unsafe partial class FindEnumerable<TResult, TState> : CriticalFinalizerObject, IEnumerable<TResult>, IEnumerator<TResult>
- {
- private readonly string _originalFullPath;
- private readonly string _originalUserPath;
- private readonly bool _recursive;
- private readonly FindTransform<TResult> _transform;
- private readonly FindPredicate<TState> _predicate;
- private readonly TState _state;
-
- private object _lock = new object();
- private int _enumeratorCreated;
-
- private Interop.NtDll.FILE_FULL_DIR_INFORMATION* _info;
- private TResult _current;
-
- private byte[] _buffer;
- private IntPtr _directoryHandle;
- private string _currentPath;
- private bool _lastEntryFound;
- private Queue<(IntPtr Handle, string Path)> _pending;
- private GCHandle _pinnedBuffer;
-
- /// <summary>
- /// Encapsulates a find operation.
- /// </summary>
- /// <param name="directory">The directory to search in.</param>
- public FindEnumerable(
- string directory,
- FindTransform<TResult> transform,
- FindPredicate<TState> predicate,
- TState state = default,
- bool recursive = false)
- {
- _originalUserPath = directory;
- _originalFullPath = Path.GetFullPath(directory);
- _recursive = recursive;
- _predicate = predicate ?? throw new ArgumentNullException(nameof(predicate));
- _transform = transform ?? throw new ArgumentNullException(nameof(transform));
- _state = state;
- Initialize();
- }
-
- private FindEnumerable(
- string originalUserPath,
- string originalFullPath,
- FindTransform<TResult> transform,
- FindPredicate<TState> predicate,
- TState state,
- bool recursive)
- {
- _originalUserPath = originalUserPath;
- _originalFullPath = originalFullPath;
- _predicate = predicate;
- _transform = transform;
- _state = state;
- _recursive = recursive;
- Initialize();
- }
-
- /// <summary>
- /// Simple wrapper to allow creating a file handle for an existing directory.
- /// </summary>
- public static IntPtr CreateDirectoryHandle(string path)
- {
- IntPtr handle = Interop.Kernel32.CreateFile_IntPtr(
- path,
- Interop.Kernel32.FileOperations.FILE_LIST_DIRECTORY,
- FileShare.ReadWrite | FileShare.Delete,
- FileMode.Open,
- Interop.Kernel32.FileOperations.FILE_FLAG_BACKUP_SEMANTICS);
-
- if (handle == IntPtr.Zero || handle == (IntPtr)(-1))
- {
- // Historically we throw directory not found rather than file not found
- int error = Marshal.GetLastWin32Error();
- if (error == Interop.Errors.ERROR_FILE_NOT_FOUND)
- error = Interop.Errors.ERROR_PATH_NOT_FOUND;
-
- throw Win32Marshal.GetExceptionForWin32Error(error, path);
- }
-
- return handle;
- }
-
- public IEnumerator<TResult> GetEnumerator()
- {
- if (Interlocked.Exchange(ref _enumeratorCreated, 1) == 0)
- {
- return this;
- }
- else
- {
- return new FindEnumerable<TResult, TState>(_originalUserPath, _originalFullPath, _transform, _predicate, _state, _recursive);
- }
- }
-
- private void Initialize()
- {
- _currentPath = _originalFullPath;
- _buffer = ArrayPool<byte>.Shared.Rent(4096);
- _pinnedBuffer = GCHandle.Alloc(_buffer, GCHandleType.Pinned);
- if (_recursive)
- _pending = new Queue<(IntPtr, string)>();
- _directoryHandle = CreateDirectoryHandle(_originalFullPath);
- }
-
- IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
-
- public TResult Current => _current;
-
- object IEnumerator.Current => Current;
-
- public bool MoveNext()
- {
- if (_lastEntryFound)
- return false;
-
- lock (_lock)
- {
- if (_lastEntryFound)
- return false;
-
- RawFindData findData = default;
- do
- {
- FindNextFile();
- if (!_lastEntryFound && _info != null)
- {
- // If needed, stash any subdirectories to process later
- if (_recursive && (_info->FileAttributes & FileAttributes.Directory) != 0
- && !PathHelpers.IsDotOrDotDot(_info->FileName))
- {
- string subDirectory = PathHelpers.CombineNoChecks(_currentPath, _info->FileName);
- IntPtr subDirectoryHandle = CreateDirectoryHandle(subDirectory);
- try
- {
- // It is possible this might allocate and run out of memory
- _pending.Enqueue((subDirectoryHandle, subDirectory));
- }
- catch
- {
- Interop.Kernel32.CloseHandle(subDirectoryHandle);
- throw;
- }
- }
-
- findData = new RawFindData(_info, _currentPath, _originalFullPath, _originalUserPath);
- }
- } while (!_lastEntryFound && !_predicate(ref findData, _state));
-
- if (!_lastEntryFound)
- _current = _transform(ref findData);
-
- return !_lastEntryFound;
- }
- }
-
- private unsafe void FindNextFile()
- {
- Interop.NtDll.FILE_FULL_DIR_INFORMATION* info = _info;
- if (info != null && info->NextEntryOffset != 0)
- {
- // We're already in a buffer and have another entry
- _info = (Interop.NtDll.FILE_FULL_DIR_INFORMATION*)((byte*)info + info->NextEntryOffset);
- return;
- }
-
- // We need more data
- if (GetData())
- _info = (Interop.NtDll.FILE_FULL_DIR_INFORMATION*)_pinnedBuffer.AddrOfPinnedObject();
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void DirectoryFinished()
- {
- _info = null;
- if (_pending == null || _pending.Count == 0)
- {
- _lastEntryFound = true;
- }
- else
- {
- // Grab the next directory to parse
- Interop.Kernel32.CloseHandle(_directoryHandle);
- (_directoryHandle, _currentPath) = _pending.Dequeue();
- FindNextFile();
- }
- }
-
- public void Reset()
- {
- throw new NotSupportedException();
- }
-
- public void Dispose()
- {
- Dispose(disposing: true);
- GC.SuppressFinalize(this);
- }
-
- protected void Dispose(bool disposing)
- {
- // It is possible to fail to allocate the lock, but the finalizer will still run
- if (_lock != null)
- {
- lock (_lock)
- {
- _lastEntryFound = true;
-
- // Don't ever close a valid handle twice as they can be reused- set to zero to ensure this
- Interop.Kernel32.CloseHandle(_directoryHandle);
- _directoryHandle = IntPtr.Zero;
-
- if (_recursive && _pending != null)
- {
- while (_pending.Count > 0)
- Interop.Kernel32.CloseHandle(_pending.Dequeue().Handle);
- _pending = null;
- }
-
- if (_pinnedBuffer.IsAllocated)
- _pinnedBuffer.Free();
-
- if (_buffer != null)
- ArrayPool<byte>.Shared.Return(_buffer);
-
- _buffer = null;
- }
- }
- }
-
- ~FindEnumerable()
- {
- Dispose(disposing: false);
- }
- }
-}
diff --git a/src/System.IO.FileSystem/src/System/IO/FindEnumerableFactory.cs b/src/System.IO.FileSystem/src/System/IO/FindEnumerableFactory.cs
deleted file mode 100644
index 1202ac7372..0000000000
--- a/src/System.IO.FileSystem/src/System/IO/FindEnumerableFactory.cs
+++ /dev/null
@@ -1,138 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-namespace System.IO
-{
- internal static class FindEnumerableFactory
- {
- internal static void NormalizeInputs(ref string directory, ref string expression)
- {
- if (PathHelpers.IsPathRooted(expression))
- throw new ArgumentException(SR.Arg_Path2IsRooted, nameof(expression));
-
- // We always allowed breaking the passed in directory and filter to be separated
- // any way the user wanted. Looking for "C:\foo\*.cs" could be passed as "C:\" and
- // "foo\*.cs" or "C:\foo" and "*.cs", for example. As such we need to combine and
- // split the inputs if the expression contains a directory separator.
- //
- // We also allowed for expression to be "foo\" which would translate to "foo\*".
-
- ReadOnlySpan<char> directoryName = PathHelpers.GetDirectoryNameNoChecks(expression.AsReadOnlySpan());
-
- if (directoryName.Length != 0)
- {
- // Need to fix up the input paths
- directory = PathHelpers.CombineNoChecks(directory, directoryName);
- expression = expression.Substring(directoryName.Length + 1);
- }
-
- // Historically we always treated "." as "*"
- if (string.IsNullOrEmpty(expression) || expression == "." || expression == "*.*")
- expression = "*";
- }
-
- internal static FindEnumerable<string, string> UserFiles(string directory,
- string expression = "*",
- bool recursive = false)
- {
- return new FindEnumerable<string, string>(
- directory,
- (ref RawFindData findData) => FindTransforms.AsUserFullPath(ref findData),
- (ref RawFindData findData, string expr) =>
- {
- return FindPredicates.NotDotOrDotDot(ref findData)
- && !FindPredicates.IsDirectory(ref findData)
- && DosMatcher.MatchPattern(expr, findData.FileName, ignoreCase: true);
- },
- DosMatcher.TranslateExpression(expression),
- recursive);
- }
-
- internal static FindEnumerable<string, string> UserDirectories(string directory,
- string expression = "*",
- bool recursive = false)
- {
- return new FindEnumerable<string, string>(
- directory,
- (ref RawFindData findData) => FindTransforms.AsUserFullPath(ref findData),
- (ref RawFindData findData, string expr) =>
- {
- return FindPredicates.NotDotOrDotDot(ref findData)
- && FindPredicates.IsDirectory(ref findData)
- && DosMatcher.MatchPattern(expr, findData.FileName, ignoreCase: true);
- },
- DosMatcher.TranslateExpression(expression),
- recursive);
- }
-
- internal static FindEnumerable<string, string> UserEntries(string directory,
- string expression = "*",
- bool recursive = false)
- {
- return new FindEnumerable<string, string>(
- directory,
- (ref RawFindData findData) => FindTransforms.AsUserFullPath(ref findData),
- (ref RawFindData findData, string expr) =>
- {
- return FindPredicates.NotDotOrDotDot(ref findData)
- && DosMatcher.MatchPattern(expr, findData.FileName, ignoreCase: true);
- },
- DosMatcher.TranslateExpression(expression),
- recursive);
- }
-
- internal static FindEnumerable<FileInfo, string> FileInfos(
- string directory,
- string expression = "*",
- bool recursive = false)
- {
- return new FindEnumerable<FileInfo, string>(
- directory,
- (ref RawFindData findData) => FindTransforms.AsFileInfo(ref findData),
- (ref RawFindData findData, string expr) =>
- {
- return FindPredicates.NotDotOrDotDot(ref findData)
- && !FindPredicates.IsDirectory(ref findData)
- && DosMatcher.MatchPattern(expr, findData.FileName, ignoreCase: true);
- },
- DosMatcher.TranslateExpression(expression),
- recursive);
- }
-
- internal static FindEnumerable<DirectoryInfo, string> DirectoryInfos(
- string directory,
- string expression = "*",
- bool recursive = false)
- {
- return new FindEnumerable<DirectoryInfo, string>(
- directory,
- (ref RawFindData findData) => FindTransforms.AsDirectoryInfo(ref findData),
- (ref RawFindData findData, string expr) =>
- {
- return FindPredicates.NotDotOrDotDot(ref findData)
- && FindPredicates.IsDirectory(ref findData)
- && DosMatcher.MatchPattern(expr, findData.FileName, ignoreCase: true);
- },
- DosMatcher.TranslateExpression(expression),
- recursive);
- }
-
- internal static FindEnumerable<FileSystemInfo, string> FileSystemInfos(
- string directory,
- string expression = "*",
- bool recursive = false)
- {
- return new FindEnumerable<FileSystemInfo, string>(
- directory,
- (ref RawFindData findData) => FindTransforms.AsFileSystemInfo(ref findData),
- (ref RawFindData findData, string expr) =>
- {
- return FindPredicates.NotDotOrDotDot(ref findData)
- && DosMatcher.MatchPattern(expr, findData.FileName, ignoreCase: true);
- },
- DosMatcher.TranslateExpression(expression),
- recursive);
- }
- }
-}
diff --git a/src/System.IO.FileSystem/src/System/IO/FindPredicate.cs b/src/System.IO.FileSystem/src/System/IO/FindPredicate.cs
deleted file mode 100644
index 2e52eda3db..0000000000
--- a/src/System.IO.FileSystem/src/System/IO/FindPredicate.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-namespace System.IO
-{
- /// <summary>
- /// Interface for filtering out find results.
- /// </summary>
- internal delegate bool FindPredicate<TState>(ref RawFindData findData, TState state);
-}
diff --git a/src/System.IO.FileSystem/src/System/IO/FindPredicates.cs b/src/System.IO.FileSystem/src/System/IO/FindPredicates.cs
deleted file mode 100644
index 9d90aed377..0000000000
--- a/src/System.IO.FileSystem/src/System/IO/FindPredicates.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-namespace System.IO
-{
- internal static partial class FindPredicates
- {
- internal static bool NotDotOrDotDot(ref RawFindData findData) => !PathHelpers.IsDotOrDotDot(findData.FileName);
-
- internal static bool IsDirectory(ref RawFindData findData)
- {
- FileAttributes attributes = findData.Attributes;
- return attributes != (FileAttributes)(-1)
- && (attributes & FileAttributes.Directory) != 0;
- }
- }
-}
diff --git a/src/System.IO.FileSystem/src/System/IO/FindTransform.cs b/src/System.IO.FileSystem/src/System/IO/FindTransform.cs
deleted file mode 100644
index c1b6b71972..0000000000
--- a/src/System.IO.FileSystem/src/System/IO/FindTransform.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-namespace System.IO
-{
- /// <summary>
- /// Delegate for transforming raw find data into a result.
- /// </summary>
- internal delegate T FindTransform<T>(ref RawFindData findData);
-}
diff --git a/src/System.IO.FileSystem/src/System/IO/FindTransforms.cs b/src/System.IO.FileSystem/src/System/IO/FindTransforms.cs
deleted file mode 100644
index db0590a35e..0000000000
--- a/src/System.IO.FileSystem/src/System/IO/FindTransforms.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-namespace System.IO
-{
- internal static partial class FindTransforms
- {
- internal static DirectoryInfo AsDirectoryInfo(ref RawFindData findData)
- {
- string fileName = new string(findData.FileName);
- return new DirectoryInfo(PathHelpers.CombineNoChecks(findData.Directory, fileName), fileName, ref findData);
- }
-
- internal static FileInfo AsFileInfo(ref RawFindData findData)
- {
- string fileName = new string(findData.FileName);
- return new FileInfo(PathHelpers.CombineNoChecks(findData.Directory, fileName), fileName, ref findData);
- }
-
- internal static FileSystemInfo AsFileSystemInfo(ref RawFindData findData)
- {
- string fileName = new string(findData.FileName);
- string fullPath = PathHelpers.CombineNoChecks(findData.Directory, fileName);
-
- return (findData.Attributes & FileAttributes.Directory) != 0
- ? (FileSystemInfo)new DirectoryInfo(fullPath, fileName, ref findData)
- : (FileSystemInfo)new FileInfo(fullPath, fileName, ref findData);
- }
-
- /// <summary>
- /// Returns the full path for find results, based off of the initially provided path.
- /// </summary>
- internal static string AsUserFullPath(ref RawFindData findData)
- {
- ReadOnlySpan<char> subdirectory = findData.Directory.AsReadOnlySpan().Slice(findData.OriginalDirectory.Length);
- return PathHelpers.CombineNoChecks(findData.OriginalUserDirectory, subdirectory, findData.FileName);
- }
- }
-}
diff --git a/src/System.IO.FileSystem/src/System/IO/PathHelpers.Windows.cs b/src/System.IO.FileSystem/src/System/IO/PathHelpers.Windows.cs
index 990527577b..9cd0cf500d 100644
--- a/src/System.IO.FileSystem/src/System/IO/PathHelpers.Windows.cs
+++ b/src/System.IO.FileSystem/src/System/IO/PathHelpers.Windows.cs
@@ -30,19 +30,5 @@ namespace System.IO
EndsInDirectorySeparator(path) ?
path.Substring(0, path.Length - 1) :
path;
-
- public static bool IsPathRooted(string path)
- {
- // Want to avoid PathInternal.CheckInvalidPathChars on Path.IsPathRooted
-
- if (path != null)
- {
- int length = path.Length;
- if ((length >= 1 && PathInternal.IsDirectorySeparator(path[0])) ||
- (length >= 2 && PathInternal.IsValidDriveChar(path[0]) && path[1] == Path.VolumeSeparatorChar))
- return true;
- }
- return false;
- }
}
}
diff --git a/src/System.IO.FileSystem/src/System/IO/PathHelpers.cs b/src/System.IO.FileSystem/src/System/IO/PathHelpers.cs
index dc574c7833..f6563651a1 100644
--- a/src/System.IO.FileSystem/src/System/IO/PathHelpers.cs
+++ b/src/System.IO.FileSystem/src/System/IO/PathHelpers.cs
@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System.Diagnostics;
+using System.IO.Enumeration;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
diff --git a/src/System.IO.FileSystem/src/System/IO/RawFindData.cs b/src/System.IO.FileSystem/src/System/IO/RawFindData.cs
deleted file mode 100644
index 2f8ab43e42..0000000000
--- a/src/System.IO.FileSystem/src/System/IO/RawFindData.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-namespace System.IO
-{
- /// <summary>
- /// Used for processing and filtering find results.
- /// </summary>
- internal unsafe ref struct RawFindData
- {
- internal RawFindData(Interop.NtDll.FILE_FULL_DIR_INFORMATION* info, string directory, string originalDirectory, string originalUserDirectory)
- {
- _info = info;
- Directory = directory;
- OriginalDirectory = originalDirectory;
- OriginalUserDirectory = originalUserDirectory;
- }
-
- internal unsafe Interop.NtDll.FILE_FULL_DIR_INFORMATION* _info;
- public string Directory { get; private set; }
- public string OriginalDirectory { get; private set; }
- public string OriginalUserDirectory { get; private set; }
-
- public ReadOnlySpan<char> FileName => _info->FileName;
- public FileAttributes Attributes => _info->FileAttributes;
- public long Length => _info->EndOfFile;
-
- public DateTime CreationTimeUtc => _info->CreationTime.ToDateTimeUtc();
- public DateTime LastAccessTimeUtc => _info->LastAccessTime.ToDateTimeUtc();
- public DateTime LastWriteTimeUtc => _info->LastWriteTime.ToDateTimeUtc();
- }
-}
diff --git a/src/System.IO.FileSystem/tests/Enumeration/ConstructionTests.netcoreapp.cs b/src/System.IO.FileSystem/tests/Enumeration/ConstructionTests.netcoreapp.cs
new file mode 100644
index 0000000000..4f453200ee
--- /dev/null
+++ b/src/System.IO.FileSystem/tests/Enumeration/ConstructionTests.netcoreapp.cs
@@ -0,0 +1,46 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.IO.Enumeration;
+using Xunit;
+
+namespace System.IO.Tests.Enumeration
+{
+ public class ConstructionTests : FileSystemTest
+ {
+ [Fact]
+ public void Enumerable_NullTransformThrows()
+ {
+ AssertExtensions.Throws<ArgumentNullException>("transform",
+ () => new FileSystemEnumerable<string>(TestDirectory, transform: null));
+ }
+
+ [Fact]
+ public void Enumerable_NullDirectoryThrows()
+ {
+ AssertExtensions.Throws<ArgumentNullException>("directory",
+ () => new FileSystemEnumerable<string>(null, null));
+ }
+
+ private class TestEnumerator : FileSystemEnumerator<string>
+ {
+ public TestEnumerator(string directory, EnumerationOptions options)
+ : base(directory, options)
+ {
+ }
+
+ protected override string TransformEntry(ref FileSystemEntry entry)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ [Fact]
+ public void Enumerator_NullDirectoryThrows()
+ {
+ AssertExtensions.Throws<ArgumentNullException>("directory",
+ () => new TestEnumerator(null, null));
+ }
+ }
+}
diff --git a/src/System.IO.FileSystem/tests/Enumeration/DosMatcherTests.netcoreapp.cs b/src/System.IO.FileSystem/tests/Enumeration/DosMatcherTests.netcoreapp.cs
new file mode 100644
index 0000000000..cdbfbcc27a
--- /dev/null
+++ b/src/System.IO.FileSystem/tests/Enumeration/DosMatcherTests.netcoreapp.cs
@@ -0,0 +1,116 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.IO.Enumeration;
+using Xunit;
+
+namespace System.IO.Tests
+{
+ public class DosMatcherTests
+ {
+ [Theory, MemberData(nameof(DosMatchData)), MemberData(nameof(EscapedDosMatchData))]
+ public static void DosMatch(string expression, string name, bool ignoreCase, bool expected)
+ {
+ Assert.Equal(expected, FileSystemName.MatchesDosExpression(expression, name.AsReadOnlySpan(), ignoreCase));
+ }
+
+ public static TheoryData<string, string, bool, bool> EscapedDosMatchData => new TheoryData<string, string, bool, bool>
+ {
+ // Trailing escape matches as it is considered "invisible"
+ { "\\", "\\", false, true },
+ { "\\", "\\", true, true },
+ { "\\\\", "\\", false, true },
+ { "\\\\", "\\", true, true },
+
+
+ { "\\*", "a", false, false },
+ { "\\*", "a", true, false },
+ { "\\*", "*", false, true },
+ { "\\*", "*", true, true },
+ { "*\\*", "***", false, true },
+ { "*\\*", "***", true, true },
+ { "*\\*", "ABC*", false, true },
+ { "*\\*", "ABC*", true, true },
+ { "*\\*", "***A", false, false },
+ { "*\\*", "***A", true, false },
+ { "*\\*", "ABC*A", false, false },
+ { "*\\*", "ABC*A", true, false },
+
+ { "\\\"", "a", false, false },
+ { "\\\"", "a", true, false },
+ { "\\\"", "\"", false, true },
+ { "\\\"", "\"", true, true },
+ };
+
+ public static TheoryData<string, string, bool, bool> DosMatchData => new TheoryData<string, string, bool, bool>
+ {
+ { null, "", false, false },
+ { null, "", true, false },
+ { "*", "", false, false },
+ { "*", "", true, false },
+ { "*", "ab", false, true },
+ { "*", "AB", true, true },
+ { "*foo", "foo", false, true },
+ { "*foo", "foo", true, true },
+ { "*foo", "FOO", false, false },
+ { "*foo", "FOO", true, true },
+ { "*foo", "nofoo", true, true },
+ { "*foo", "NoFOO", true, true },
+ { "*foo", "noFOO", false, false },
+
+ { @"*", @"foo.txt", true, true },
+ { @".", @"foo.txt", true, false },
+ { @".", @"footxt", true, false },
+ { @"*.*", @"foo.txt", true, true },
+ { @"*.*", @"foo.", true, true },
+ { @"*.*", @".foo", true, true },
+ { @"*.*", @"footxt", true, false },
+ { "<\"*", @"footxt", true, true }, // DOS equivalent of *.*
+ { "<\"*", @"foo.txt", true, true }, // DOS equivalent of *.*
+ { "<\"*", @".foo", true, true }, // DOS equivalent of *.*
+ { "<\"*", @"foo.", true, true }, // DOS equivalent of *.*
+ { ">\">", @"a.b", true, true }, // DOS equivalent of ?.?
+ { ">\">", @"a.", true, true }, // DOS equivalent of ?.?
+ { ">\">", @"a", true, true }, // DOS equivalent of ?.?
+ { ">\">", @"ab", true, false }, // DOS equivalent of ?.?
+ { ">\">", @"a.bc", true, false }, // DOS equivalent of ?.?
+ { ">\">", @"ab.c", true, false }, // DOS equivalent of ?.?
+ { ">>\">>", @"a.b", true, true }, // DOS equivalent of ??.??
+ { ">>\"\">>", @"a.b", true, false }, // Not possible to do from DOS ??""??
+ { ">>\">>", @"a.bc", true, true }, // DOS equivalent of ??.??
+ { ">>\">>", @"ab.ba", true, true }, // DOS equivalent of ??.??
+ { ">>\">>", @"ab.", true, true }, // DOS equivalent of ??.??
+ { ">>\"\"\">>", @"ab.", true, true }, // Not possible to do from DOS ??"""??
+ { ">>b\">>", @"ab.ba", true, false }, // DOS equivalent of ??b.??
+ { "a>>\">>", @"ab.ba", true, true }, // DOS equivalent of a??.??
+ { ">>\">>a", @"ab.ba", true, false }, // DOS equivalent of ??.??a
+ { ">>\"b>>", @"ab.ba", true, true }, // DOS equivalent of ??.b??
+ { ">>\"b>>", @"ab.b", true, true }, // DOS equivalent of ??.b??
+ { ">>b.>>", @"ab.ba", true, false },
+ { "a>>.>>", @"ab.ba", true, true },
+ { ">>.>>a", @"ab.ba", true, false },
+ { ">>.b>>", @"ab.ba", true, true },
+ { ">>.b>>", @"ab.b", true, true },
+ { ">>\">>\">>", @"ab.ba", true, true }, // DOS equivalent of ??.??.?? (The last " is an optional period)
+ { ">>\">>\">>", @"abba", true, false }, // DOS equivalent of ??.??.?? (The first " isn't, so this doesn't match)
+ { ">>\"ab\"ba", @"ab.ba", true, false }, // DOS equivalent of ??.ab.ba
+ { "ab\"ba\">>", @"ab.ba", true, true }, // DOS equivalent of ab.ba.??
+ { "ab\">>\"ba", @"ab.ba", true, false }, // DOS equivalent of ab.??.ba
+ { ">>\">>\">>>", @"ab.ba.cab", true, true }, // DOS equivalent of ??.??.???
+ { "a>>\"b>>\"c>>>", @"ab.ba.cab", true, true }, // DOS equivalent of a??.b??.c???
+ { @"<", @"a", true, true }, // DOS equivalent of *.
+ { @"<", @"a.", true, true }, // DOS equivalent of *.
+ { @"<", @"a. ", true, false }, // DOS equivalent of *.
+ { @"<", @"a.b", true, false }, // DOS equivalent of *.
+ { @"foo<", @"foo.", true, true }, // DOS equivalent of foo*.
+ { @"foo<", @"foo. ", true, false }, // DOS equivalent of foo*.
+ { @"<<", @"a.b", true, true },
+ { @"<<", @"a.b.c", true, true },
+ { "<\"", @"a.b.c", true, false },
+ { @"<.", @"a", true, false },
+ { @"<.", @"a.", true, true },
+ { @"<.", @"a.b", true, false },
+ };
+ }
+}
diff --git a/src/System.IO.FileSystem/tests/Enumeration/SkipAttributeTests.netcoreapp.cs b/src/System.IO.FileSystem/tests/Enumeration/SkipAttributeTests.netcoreapp.cs
new file mode 100644
index 0000000000..7aa609b452
--- /dev/null
+++ b/src/System.IO.FileSystem/tests/Enumeration/SkipAttributeTests.netcoreapp.cs
@@ -0,0 +1,85 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.IO.Enumeration;
+using System.Linq;
+using Xunit;
+
+namespace System.IO.Tests.Enumeration
+{
+ public class SkipAttributeTests : FileSystemTest
+ {
+ protected virtual string[] GetPaths(string directory, EnumerationOptions options)
+ {
+ return new FileSystemEnumerable<string>(
+ directory,
+ (ref FileSystemEntry entry) => entry.ToFullPath(),
+ options)
+ {
+ ShouldIncludePredicate = (ref FileSystemEntry entry) => { return !entry.IsDirectory; }
+ }.ToArray();
+ }
+
+ [Fact]
+ public void SkippingHiddenFiles()
+ {
+ DirectoryInfo testDirectory = Directory.CreateDirectory(GetTestFilePath());
+ DirectoryInfo testSubdirectory = Directory.CreateDirectory(Path.Combine(testDirectory.FullName, GetTestFileName()));
+ FileInfo fileOne = new FileInfo(Path.Combine(testDirectory.FullName, GetTestFileName()));
+
+ // Put a period in front to make it hidden on Unix
+ FileInfo fileTwo = new FileInfo(Path.Combine(testDirectory.FullName, "." + GetTestFileName()));
+ FileInfo fileThree = new FileInfo(Path.Combine(testSubdirectory.FullName, GetTestFileName()));
+ FileInfo fileFour = new FileInfo(Path.Combine(testSubdirectory.FullName, "." + GetTestFileName()));
+
+ fileOne.Create().Dispose();
+ fileTwo.Create().Dispose();
+ if (PlatformDetection.IsWindows)
+ fileTwo.Attributes = fileTwo.Attributes | FileAttributes.Hidden;
+ fileThree.Create().Dispose();
+ fileFour.Create().Dispose();
+ if (PlatformDetection.IsWindows)
+ fileFour.Attributes = fileTwo.Attributes | FileAttributes.Hidden;
+
+ string[] paths = GetPaths(testDirectory.FullName, new EnumerationOptions { AttributesToSkip = FileAttributes.Hidden });
+ Assert.Equal(new string[] { fileOne.FullName }, paths);
+
+ paths = GetPaths(testDirectory.FullName, new EnumerationOptions { AttributesToSkip = FileAttributes.Hidden, RecurseSubdirectories = true });
+ Assert.Equal(new string[] { fileOne.FullName, fileThree.FullName }, paths);
+
+ if (PlatformDetection.IsWindows)
+ {
+ // Shouldn't recurse into the subdirectory now that it is hidden
+ testSubdirectory.Attributes = testSubdirectory.Attributes | FileAttributes.Hidden;
+ }
+ else
+ {
+ Directory.Move(testSubdirectory.FullName, Path.Combine(testDirectory.FullName, "." + testSubdirectory.Name));
+ }
+
+ paths = GetPaths(testDirectory.FullName, new EnumerationOptions { AttributesToSkip = FileAttributes.Hidden, RecurseSubdirectories = true });
+ Assert.Equal(new string[] { fileOne.FullName }, paths);
+ }
+ }
+
+ // Unix implementation not finished
+ [ActiveIssue(26715, TestPlatforms.AnyUnix)]
+ public class SkipAttributeTests_Directory_GetFiles : SkipAttributeTests
+ {
+ protected override string[] GetPaths(string directory, EnumerationOptions options)
+ {
+ return Directory.GetFiles(directory, "*", options);
+ }
+ }
+
+ // Unix implementation not finished
+ [ActiveIssue(26715, TestPlatforms.AnyUnix)]
+ public class SkipAttributeTests_DirectoryInfo_GetFiles : SkipAttributeTests
+ {
+ protected override string[] GetPaths(string directory, EnumerationOptions options)
+ {
+ return new DirectoryInfo(directory).GetFiles("*", options).Select(i => i.FullName).ToArray();
+ }
+ }
+}
diff --git a/src/System.IO.FileSystem/tests/Enumeration/SpecialDirectoryTests.netcoreapp.cs b/src/System.IO.FileSystem/tests/Enumeration/SpecialDirectoryTests.netcoreapp.cs
new file mode 100644
index 0000000000..98f3a2098a
--- /dev/null
+++ b/src/System.IO.FileSystem/tests/Enumeration/SpecialDirectoryTests.netcoreapp.cs
@@ -0,0 +1,37 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.IO.Enumeration;
+using System.Linq;
+using Xunit;
+
+namespace System.IO.Tests.Enumeration
+{
+ public class SpecialDirectoryTests : FileSystemTest
+ {
+ protected virtual string[] GetNames(string directory, EnumerationOptions options)
+ {
+ return new FileSystemEnumerable<string>(
+ directory,
+ (ref FileSystemEntry entry) => new string(entry.FileName),
+ options).ToArray();
+ }
+
+ [Fact]
+ public void SkippingHiddenFiles()
+ {
+ string[] paths = GetNames(TestDirectory, new EnumerationOptions { ReturnSpecialDirectories = true });
+ Assert.Contains(".", paths);
+ Assert.Contains("..", paths);
+ }
+ }
+
+ public class SpecialDirectoryTests_DirectoryInfo_GetDirectories : SpecialDirectoryTests
+ {
+ protected override string[] GetNames(string directory, EnumerationOptions options)
+ {
+ return new DirectoryInfo(directory).GetDirectories("*", options).Select(i => i.Name).ToArray();
+ }
+ }
+}
diff --git a/src/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj b/src/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj
index b6637446b0..c4ed74a5e8 100644
--- a/src/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj
+++ b/src/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj
@@ -53,6 +53,10 @@
<Compile Include="File\ReadWriteAllBytesAsync.cs" />
<Compile Include="File\ReadWriteAllTextAsync.cs" />
<Compile Include="FileStream\ReadWriteSpan.netcoreapp.cs" />
+ <Compile Include="Enumeration\ConstructionTests.netcoreapp.cs" />
+ <Compile Include="Enumeration\SpecialDirectoryTests.netcoreapp.cs" />
+ <Compile Include="Enumeration\SkipAttributeTests.netcoreapp.cs" />
+ <Compile Include="Enumeration\DosMatcherTests.netcoreapp.cs" />
</ItemGroup>
<ItemGroup>
<!-- Rewritten -->
@@ -181,5 +185,8 @@
<ItemGroup>
<EmbeddedResource Include="Resources\$(AssemblyName).rd.xml" />
</ItemGroup>
+ <ItemGroup>
+ <Folder Include="Matchers\" />
+ </ItemGroup>
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
</Project> \ No newline at end of file