// Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; namespace Microsoft.Build.Shared.FileSystem { /// /// The type of file artifact to search for /// internal enum FileArtifactType : byte { /// File, /// Directory, /// FileOrDirectory } /// /// Windows-specific implementation of file system operations using Windows native invocations. /// TODO For potential extra perf gains, provide native implementations for all IFileSystem methods and stop inheriting from ManagedFileSystem /// internal class WindowsFileSystem : ManagedFileSystem { private static readonly WindowsFileSystem Instance = new WindowsFileSystem(); public new static WindowsFileSystem Singleton() => WindowsFileSystem.Instance; private WindowsFileSystem(){ } public override IEnumerable EnumerateFiles(string path, string searchPattern, SearchOption searchOption) { return EnumerateFileOrDirectories(path, FileArtifactType.File, searchPattern, searchOption); } public override IEnumerable EnumerateDirectories(string path, string searchPattern, SearchOption searchOption) { return EnumerateFileOrDirectories(path, FileArtifactType.Directory, searchPattern, searchOption); } public override IEnumerable EnumerateFileSystemEntries(string path, string searchPattern, SearchOption searchOption) { return EnumerateFileOrDirectories(path, FileArtifactType.FileOrDirectory, searchPattern, searchOption); } public override bool DirectoryExists(string path) { return NativeMethodsShared.DirectoryExistsWindows(path); } public override bool FileExists(string path) { return NativeMethodsShared.FileExistsWindows(path); } public override bool FileOrDirectoryExists(string path) { return NativeMethodsShared.FileOrDirectoryExistsWindows(path); } public override DateTime GetLastWriteTimeUtc(string path) { var fileLastWriteTime = NativeMethodsShared.GetLastWriteFileUtcTime(path); if (fileLastWriteTime != DateTime.MinValue) { return fileLastWriteTime; } else { NativeMethodsShared.GetLastWriteDirectoryUtcTime(path, out var directoryLastWriteTime); return directoryLastWriteTime; } } private static IEnumerable EnumerateFileOrDirectories( string directoryPath, FileArtifactType fileArtifactType, string searchPattern, SearchOption searchOption = SearchOption.TopDirectoryOnly) { var enumeration = new List(); var result = CustomEnumerateDirectoryEntries( directoryPath, fileArtifactType, searchPattern, searchOption, enumeration); // If the result indicates that the enumeration succeeded or the directory does not exist, then the result is considered success. // In particular, if the globed directory does not exist, then we want to return the empty file, and track for the anti-dependency. if ( !(result.Status == WindowsNative.EnumerateDirectoryStatus.Success || result.Status == WindowsNative.EnumerateDirectoryStatus.SearchDirectoryNotFound)) { throw result.CreateExceptionForError(); } return enumeration; } private static WindowsNative.EnumerateDirectoryResult CustomEnumerateDirectoryEntries( string directoryPath, FileArtifactType fileArtifactType, string pattern, SearchOption searchOption, ICollection result) { var searchDirectoryPath = Path.Combine(directoryPath, "*"); WindowsNative.Win32FindData findResult; using (var findHandle = WindowsNative.FindFirstFileW(searchDirectoryPath, out findResult)) { if (findHandle.IsInvalid) { int hr = Marshal.GetLastWin32Error(); Debug.Assert(hr != WindowsNative.ErrorFileNotFound); WindowsNative.EnumerateDirectoryStatus findHandleOpenStatus = hr switch { WindowsNative.ErrorFileNotFound => WindowsNative.EnumerateDirectoryStatus.SearchDirectoryNotFound, WindowsNative.ErrorPathNotFound => WindowsNative.EnumerateDirectoryStatus.SearchDirectoryNotFound, WindowsNative.ErrorDirectory => WindowsNative.EnumerateDirectoryStatus.CannotEnumerateFile, WindowsNative.ErrorAccessDenied => WindowsNative.EnumerateDirectoryStatus.AccessDenied, _ => WindowsNative.EnumerateDirectoryStatus.UnknownError, }; return new WindowsNative.EnumerateDirectoryResult(directoryPath, findHandleOpenStatus, hr); } while (true) { var isDirectory = (findResult.DwFileAttributes & FileAttributes.Directory) != 0; // There will be entries for the current and parent directories. Ignore those. if (!isDirectory || (findResult.CFileName != "." && findResult.CFileName != "..")) { // Make sure pattern and directory/file filters are honored // We special case the "*" pattern since it is the default when no pattern is specified // so we avoid calling the matching function if (pattern == "*" || WindowsNative.PathMatchSpecExW(findResult.CFileName, pattern, WindowsNative.DwFlags.PmsfNormal) == WindowsNative.ErrorSuccess) { if (fileArtifactType == FileArtifactType.FileOrDirectory || !(fileArtifactType == FileArtifactType.Directory ^ isDirectory)) { result.Add(Path.Combine(directoryPath, findResult.CFileName)); } } // Recursively go into subfolders if specified if (searchOption == SearchOption.AllDirectories && isDirectory) { var recurs = CustomEnumerateDirectoryEntries( Path.Combine(directoryPath, findResult.CFileName), fileArtifactType, pattern, searchOption, result); if (!recurs.Succeeded) { return recurs; } } } if (!WindowsNative.FindNextFileW(findHandle, out findResult)) { int hr = Marshal.GetLastWin32Error(); if (hr == WindowsNative.ErrorNoMoreFiles) { // Graceful completion of enumeration. return new WindowsNative.EnumerateDirectoryResult( directoryPath, WindowsNative.EnumerateDirectoryStatus.Success, hr); } Debug.Assert(hr != WindowsNative.ErrorSuccess); return new WindowsNative.EnumerateDirectoryResult( directoryPath, WindowsNative.EnumerateDirectoryStatus.UnknownError, hr); } } } } } }