public static void RemoveDirectory(string fullPath, bool recursive) { if (!recursive) { RemoveDirectoryInternal(fullPath, topLevel: true); return; } Interop.Kernel32.WIN32_FIND_DATA findData = default; // FindFirstFile($path) (used by GetFindData) fails with ACCESS_DENIED when user has no ListDirectory rights // but FindFirstFile($path/*") (used by RemoveDirectoryRecursive) works fine in such scenario. // So we ignore it here and let RemoveDirectoryRecursive throw if FindFirstFile($path/*") fails with ACCESS_DENIED. GetFindData(fullPath, isDirectory: true, ignoreAccessDenied: true, ref findData); if (IsNameSurrogateReparsePoint(ref findData)) { // Don't recurse RemoveDirectoryInternal(fullPath, topLevel: true); return; } // We want extended syntax so we can delete "extended" subdirectories and files // (most notably ones with trailing whitespace or periods) fullPath = PathInternal.EnsureExtendedPrefix(fullPath); RemoveDirectoryRecursive(fullPath, ref findData, topLevel: true); }
private void CommonInit() { Debug.Assert(_searchCriteria != null, "searchCriteria should be initialized"); // Execute searchCriteria against the current directory PathHelpers.ThrowIfEmptyOrRootedPath(_searchCriteria); string searchPath = Path.Combine(_searchData.FullPath, _searchCriteria); Interop.Kernel32.WIN32_FIND_DATA data = new Interop.Kernel32.WIN32_FIND_DATA(); using (new DisableMediaInsertionPrompt()) { // Open a Find handle _hnd = Interop.Kernel32.FindFirstFile(searchPath, ref data); if (_hnd.IsInvalid) { int errorCode = Marshal.GetLastWin32Error(); if (errorCode != Interop.Errors.ERROR_FILE_NOT_FOUND && errorCode != Interop.Errors.ERROR_NO_MORE_FILES) { throw HandleError(errorCode, _searchData.FullPath); } else { // flag this as empty only if we're searching just top directory // Used in fast path for top directory only _empty = _searchOption == SearchOption.TopDirectoryOnly; } } } if (_searchOption == SearchOption.TopDirectoryOnly) { // fast path for TopDirectoryOnly. If we have a result, go ahead and set it to // current. If empty, dispose handle. if (_empty) { _hnd.Dispose(); } else { TSource result; if (IsResultIncluded(ref data, out result)) { current = result; } } } else { // for AllDirectories, we first recurse into dirs, so cleanup and add searchData // to the list _hnd.Dispose(); _searchList = new List <PathPair>(); _searchList.Add(_searchData); } }
internal static int FillAttributeInfo(string path, ref Interop.Kernel32.WIN32_FILE_ATTRIBUTE_DATA data, bool returnErrorOnNotFound) { int errorCode = Interop.Errors.ERROR_SUCCESS; // Neither GetFileAttributes or FindFirstFile like trailing separators path = path.TrimEnd(PathHelpers.DirectorySeparatorChars); using (new DisableMediaInsertionPrompt()) { if (!Interop.Kernel32.GetFileAttributesEx(path, Interop.Kernel32.GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, ref data)) { errorCode = Marshal.GetLastWin32Error(); if (errorCode == Interop.Errors.ERROR_ACCESS_DENIED) { // Files that are marked for deletion will not let you GetFileAttributes, // ERROR_ACCESS_DENIED is given back without filling out the data struct. // FindFirstFile, however, will. Historically we always gave back attributes // for marked-for-deletion files. var findData = new Interop.Kernel32.WIN32_FIND_DATA(); using (SafeFindHandle handle = Interop.Kernel32.FindFirstFile(path, ref findData)) { if (handle.IsInvalid) { errorCode = Marshal.GetLastWin32Error(); } else { errorCode = Interop.Errors.ERROR_SUCCESS; data.PopulateFrom(ref findData); } } } } } if (errorCode != Interop.Errors.ERROR_SUCCESS && !returnErrorOnNotFound) { switch (errorCode) { case Interop.Errors.ERROR_FILE_NOT_FOUND: case Interop.Errors.ERROR_PATH_NOT_FOUND: case Interop.Errors.ERROR_NOT_READY: // Removable media not ready // Return default value for backward compatibility data.fileAttributes = -1; return(Interop.Errors.ERROR_SUCCESS); } } return(errorCode); }
private static void GetFindData(string fullPath, bool isDirectory, ref Interop.Kernel32.WIN32_FIND_DATA findData) { using SafeFindHandle handle = Interop.Kernel32.FindFirstFile(Path.TrimEndingDirectorySeparator(fullPath), ref findData); if (handle.IsInvalid) { int errorCode = Marshal.GetLastWin32Error(); // File not found doesn't make much sense coming from a directory. if (isDirectory && errorCode == Interop.Errors.ERROR_FILE_NOT_FOUND) { errorCode = Interop.Errors.ERROR_PATH_NOT_FOUND; } throw Win32Marshal.GetExceptionForWin32Error(errorCode, fullPath); } }
private static bool IsNameSurrogateReparsePoint(ref Interop.Kernel32.WIN32_FIND_DATA data) { // Name surrogates are reparse points that point to other named entities local to the file system. // Reparse points can be used for other types of files, notably OneDrive placeholder files. We // should treat reparse points that are not name surrogates as any other directory, e.g. recurse // into them. Surrogates should just be detached. // // See // https://github.com/dotnet/corefx/issues/24250 // https://msdn.microsoft.com/en-us/library/windows/desktop/aa365511.aspx // https://msdn.microsoft.com/en-us/library/windows/desktop/aa365197.aspx return(((FileAttributes)data.dwFileAttributes & FileAttributes.ReparsePoint) != 0 && (data.dwReserved0 & 0x20000000) != 0); // IsReparseTagNameSurrogate }
public static void RemoveDirectory(string fullPath, bool recursive) { // Do not recursively delete through reparse points. if (!recursive || IsReparsePoint(fullPath)) { RemoveDirectoryInternal(fullPath, topLevel: true); return; } // We want extended syntax so we can delete "extended" subdirectories and files // (most notably ones with trailing whitespace or periods) fullPath = PathInternal.EnsureExtendedPrefix(fullPath); Interop.Kernel32.WIN32_FIND_DATA findData = new Interop.Kernel32.WIN32_FIND_DATA(); RemoveDirectoryRecursive(fullPath, ref findData, topLevel: true); }
public static void RemoveDirectory(string fullPath, bool recursive) { if (!recursive) { RemoveDirectoryInternal(fullPath, topLevel: true); return; } Interop.Kernel32.WIN32_FIND_DATA findData = default; GetFindData(fullPath, ref findData); if (IsNameSurrogateReparsePoint(ref findData)) { // Don't recurse RemoveDirectoryInternal(fullPath, topLevel: true); return; } // We want extended syntax so we can delete "extended" subdirectories and files // (most notably ones with trailing whitespace or periods) fullPath = PathInternal.EnsureExtendedPrefix(fullPath); RemoveDirectoryRecursive(fullPath, ref findData, topLevel: true); }
[SecurityCritical] // auto-generated internal static bool IsFile(ref Interop.Kernel32.WIN32_FIND_DATA data) { return(0 == (data.dwFileAttributes & Interop.Kernel32.FileAttributes.FILE_ATTRIBUTE_DIRECTORY)); }
internal override bool IsResultIncluded(string fullPath, string userPath, ref Interop.Kernel32.WIN32_FIND_DATA findData, out FileSystemInfo result) { if (Win32FileSystemEnumerableHelpers.IsFile(ref findData)) { string fullPathFinal = Path.Combine(fullPath, findData.cFileName.GetStringFromFixedBuffer()); result = new FileInfo(fullPathFinal, ref findData); return(true); } else if (Win32FileSystemEnumerableHelpers.IsDir(ref findData)) { string fullPathFinal = Path.Combine(fullPath, findData.cFileName.GetStringFromFixedBuffer()); result = new DirectoryInfo(fullPathFinal, ref findData); return(true); } result = null; return(false); }
[SecurityCritical] // auto-generated internal static bool IsDir(ref Interop.Kernel32.WIN32_FIND_DATA data) { // Don't add "." nor ".." return((0 != (data.dwFileAttributes & Interop.Kernel32.FileAttributes.FILE_ATTRIBUTE_DIRECTORY)) && !data.cFileName.FixedBufferEqualsString(".") && !data.cFileName.FixedBufferEqualsString("..")); }
internal abstract bool IsResultIncluded(string fullPath, string userPath, ref Interop.Kernel32.WIN32_FIND_DATA findData, out TSource result);
internal override bool IsResultIncluded(string fullPath, string userPath, ref Interop.Kernel32.WIN32_FIND_DATA findData, out string result) { if ((_includeFiles && Win32FileSystemEnumerableHelpers.IsFile(ref findData)) || (_includeDirs && Win32FileSystemEnumerableHelpers.IsDir(ref findData))) { result = Path.Combine(userPath, findData.cFileName.GetStringFromFixedBuffer()); return(true); } result = null; return(false); }
public override bool MoveNext() { Interop.Kernel32.WIN32_FIND_DATA data = new Interop.Kernel32.WIN32_FIND_DATA(); switch (state) { case STATE_INIT: { if (_empty) { state = STATE_FINISH; goto case STATE_FINISH; } if (_searchOption == SearchOption.TopDirectoryOnly) { state = STATE_FIND_NEXT_FILE; if (current != null) { return(true); } else { goto case STATE_FIND_NEXT_FILE; } } else { state = STATE_SEARCH_NEXT_DIR; goto case STATE_SEARCH_NEXT_DIR; } } case STATE_SEARCH_NEXT_DIR: { Debug.Assert(_searchOption != SearchOption.TopDirectoryOnly, "should not reach this code path if searchOption == TopDirectoryOnly"); Debug.Assert(_searchList != null, "_searchList should not be null"); // Traverse directory structure. We need to get '*' while (_searchList.Count > 0) { int index = _searchList.Count - 1; _searchData = _searchList[index]; Debug.Assert((_searchData.FullPath != null), "fullpath can't be null!"); _searchList.RemoveAt(index); // Traverse the subdirs AddSearchableDirsToList(_searchData); // Execute searchCriteria against the current directory string searchPath = Path.Combine(_searchData.FullPath, _searchCriteria); using (new DisableMediaInsertionPrompt()) { // Open a Find handle _hnd = Interop.Kernel32.FindFirstFile(searchPath, ref data); if (_hnd.IsInvalid) { int errorCode = Marshal.GetLastWin32Error(); switch (errorCode) { case Interop.Errors.ERROR_FILE_NOT_FOUND: case Interop.Errors.ERROR_NO_MORE_FILES: case Interop.Errors.ERROR_PATH_NOT_FOUND: continue; } _hnd.Dispose(); throw HandleError(errorCode, _searchData.FullPath); } } state = STATE_FIND_NEXT_FILE; TSource result; if (IsResultIncluded(ref data, out result)) { current = result; return(true); } else { goto case STATE_FIND_NEXT_FILE; } } state = STATE_FINISH; goto case STATE_FINISH; } case STATE_FIND_NEXT_FILE: { if (_hnd != null) { using (new DisableMediaInsertionPrompt()) { // Keep asking for more matching files/dirs, add it to the list while (Interop.Kernel32.FindNextFile(_hnd, ref data)) { TSource result; if (IsResultIncluded(ref data, out result)) { current = result; return(true); } } } // Make sure we quit with a sensible error. int errorCode = Marshal.GetLastWin32Error(); _hnd?.Dispose(); switch (errorCode) { case Interop.Errors.ERROR_SUCCESS: case Interop.Errors.ERROR_NO_MORE_FILES: // ERROR_FILE_NOT_FOUND is valid here because if the top level // dir doesn't contain any subdirs and matching files then // we will get here with this errorcode from the _searchList walk case Interop.Errors.ERROR_FILE_NOT_FOUND: break; default: throw HandleError(errorCode, _searchData.FullPath); } } if (_searchOption == SearchOption.TopDirectoryOnly) { state = STATE_FINISH; goto case STATE_FINISH; } else { state = STATE_SEARCH_NEXT_DIR; goto case STATE_SEARCH_NEXT_DIR; } } case STATE_FINISH: { Dispose(); break; } } return(false); }
[SecurityCritical] // auto-generated private void AddSearchableDirsToList(PathPair localSearchData) { string searchPath = Path.Combine(localSearchData.FullPath, "*"); SafeFindHandle hnd = null; Interop.Kernel32.WIN32_FIND_DATA data = new Interop.Kernel32.WIN32_FIND_DATA(); try { using (new DisableMediaInsertionPrompt()) { // Get all files and dirs hnd = Interop.Kernel32.FindFirstFile(searchPath, ref data); if (hnd.IsInvalid) { int errorCode = Marshal.GetLastWin32Error(); // This could happen if the dir doesn't contain any files. // Continue with the recursive search though, eventually // _searchList will become empty switch (errorCode) { case Interop.Errors.ERROR_FILE_NOT_FOUND: case Interop.Errors.ERROR_NO_MORE_FILES: case Interop.Errors.ERROR_PATH_NOT_FOUND: return; default: throw HandleError(errorCode, localSearchData.FullPath); } } } // Add subdirs to _searchList. Exempt ReparsePoints as appropriate Debug.Assert(_searchList != null, "_searchList should not be null"); int initialCount = _searchList.Count; do { if (Win32FileSystemEnumerableHelpers.IsDir(ref data)) { string fileName = data.cFileName.GetStringFromFixedBuffer(); Debug.Assert(fileName.Length != 0 && !Path.IsPathRooted(fileName), "Expected file system enumeration to not have empty file/directory name and not have rooted name"); string tempFullPath = Path.Combine(localSearchData.FullPath, fileName); string tempUserPath = Path.Combine(localSearchData.UserPath, fileName); // Setup search data for the sub directory and push it into the list PathPair searchDataSubDir = new PathPair(tempUserPath, tempFullPath); _searchList.Add(searchDataSubDir); } } while (Interop.Kernel32.FindNextFile(hnd, ref data)); // Reverse the items just added to maintain FIFO order if (_searchList.Count > initialCount) { _searchList.Reverse(initialCount, _searchList.Count - initialCount); } // We don't care about errors here } finally { hnd?.Dispose(); } }
internal FileInfo(string fullPath, ref Interop.Kernel32.WIN32_FIND_DATA findData) : this(fullPath, findData.cFileName.GetStringFromFixedBuffer()) { Debug.Assert(findData.cFileName.FixedBufferEqualsString(Path.GetFileName(fullPath))); Init(ref findData); }
private static bool ShouldUseWinRT(string fullPath, bool isCreate) { // The purpose of this method is to determine if we can access a path // via Win32 or if we need to fallback to WinRT. // We prefer Win32 since it is faster, WinRT's APIs eventually just // call into Win32 after all, but it doesn't provide access to, // brokered paths (like Pictures or Documents) nor does it handle // placeholder files. So we'd like to fall back to WinRT whenever // we can't access a path, or if it known to be a placeholder file. bool useWinRt = false; do { // first use GetFileAttributesEx as it is faster than FindFirstFile and requires minimum permissions Interop.Kernel32.WIN32_FILE_ATTRIBUTE_DATA data = new Interop.Kernel32.WIN32_FILE_ATTRIBUTE_DATA(); if (Interop.Kernel32.GetFileAttributesEx(fullPath, Interop.Kernel32.GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, ref data)) { // got the attributes if ((data.fileAttributes & Interop.Kernel32.FileAttributes.FILE_ATTRIBUTE_DIRECTORY) != 0 || (data.fileAttributes & Interop.Kernel32.FileAttributes.FILE_ATTRIBUTE_REPARSE_POINT) == 0) { // we have a directory or a file that is not a reparse point // useWinRt = false; break; } else { // we need to get the find data to determine if it is a placeholder file Interop.Kernel32.WIN32_FIND_DATA findData = new Interop.Kernel32.WIN32_FIND_DATA(); using (SafeFindHandle handle = Interop.Kernel32.FindFirstFile(fullPath, ref findData)) { if (!handle.IsInvalid) { // got the find data, use WinRT for placeholder files Debug.Assert((findData.dwFileAttributes & Interop.Kernel32.FileAttributes.FILE_ATTRIBUTE_DIRECTORY) == 0); Debug.Assert((findData.dwFileAttributes & Interop.Kernel32.FileAttributes.FILE_ATTRIBUTE_REPARSE_POINT) != 0); useWinRt = findData.dwReserved0 == Interop.Kernel32.IOReparseOptions.IO_REPARSE_TAG_FILE_PLACEHOLDER; break; } } } } int error = Marshal.GetLastWin32Error(); Debug.Assert(error != Interop.Errors.ERROR_SUCCESS); if (error == Interop.Errors.ERROR_ACCESS_DENIED) { // The path was not accessible with Win32, so try WinRT useWinRt = true; break; } else if (error != Interop.Errors.ERROR_PATH_NOT_FOUND && error != Interop.Errors.ERROR_FILE_NOT_FOUND) { // We hit some error other than ACCESS_DENIED or NOT_FOUND, // Default to Win32 to provide most accurate error behavior break; } // error was ERROR_PATH_NOT_FOUND or ERROR_FILE_NOT_FOUND // if we are creating a file/directory we cannot assume that Win32 will have access to // the parent directory, so we walk up the path. fullPath = PathHelpers.GetDirectoryNameInternal(fullPath); // only walk up the path if we are creating a file/directory and not at the root } while (isCreate && !String.IsNullOrEmpty(fullPath)); return useWinRt; }
internal void Init(ref Interop.Kernel32.WIN32_FIND_DATA findData) { // Copy the information to data _data.PopulateFrom(ref findData); _dataInitialized = 0; }
private static unsafe string?GetFinalLinkTarget(string linkPath, bool isDirectory) { Interop.Kernel32.WIN32_FIND_DATA data = default; GetFindData(linkPath, isDirectory, ignoreAccessDenied: false, ref data); // The file or directory is not a reparse point. if ((data.dwFileAttributes & (uint)FileAttributes.ReparsePoint) == 0 || // Only symbolic links are supported at the moment. (data.dwReserved0 & Interop.Kernel32.IOReparseOptions.IO_REPARSE_TAG_SYMLINK) == 0) { return(null); } // We try to open the final file since they asked for the final target. using SafeFileHandle handle = OpenSafeFileHandle(linkPath, Interop.Kernel32.FileOperations.OPEN_EXISTING | Interop.Kernel32.FileOperations.FILE_FLAG_BACKUP_SEMANTICS); if (handle.IsInvalid) { // If the handle fails because it is unreachable, is because the link was broken. // We need to fallback to manually traverse the links and return the target of the last resolved link. int error = Marshal.GetLastWin32Error(); if (IsPathUnreachableError(error)) { return(GetFinalLinkTargetSlow(linkPath)); } throw Win32Marshal.GetExceptionForWin32Error(error, linkPath); } const int InitialBufferSize = 4096; char[] buffer = ArrayPool <char> .Shared.Rent(InitialBufferSize); try { uint result = GetFinalPathNameByHandle(handle, buffer); // If the function fails because lpszFilePath is too small to hold the string plus the terminating null character, // the return value is the required buffer size, in TCHARs. This value includes the size of the terminating null character. if (result > buffer.Length) { char[] toReturn = buffer; buffer = ArrayPool <char> .Shared.Rent((int)result); ArrayPool <char> .Shared.Return(toReturn); result = GetFinalPathNameByHandle(handle, buffer); } // If the function fails for any other reason, the return value is zero. if (result == 0) { throw Win32Marshal.GetExceptionForLastWin32Error(linkPath); } Debug.Assert(PathInternal.IsExtended(new string(buffer, 0, (int)result).AsSpan())); // GetFinalPathNameByHandle always returns with extended DOS prefix even if the link target was created without one. // While this does not interfere with correct behavior, it might be unexpected. // Hence we trim it if the passed-in path to the link wasn't extended. int start = PathInternal.IsExtended(linkPath.AsSpan()) ? 0 : 4; return(new string(buffer, start, (int)result - start)); } finally { ArrayPool <char> .Shared.Return(buffer); } uint GetFinalPathNameByHandle(SafeFileHandle handle, char[] buffer) { fixed(char *bufPtr = buffer) { return(Interop.Kernel32.GetFinalPathNameByHandle(handle, bufPtr, (uint)buffer.Length, Interop.Kernel32.FILE_NAME_NORMALIZED)); } } string?GetFinalLinkTargetSlow(string linkPath) { // Since all these paths will be passed to CreateFile, which takes a string anyway, it is pointless to use span. // I am not sure if it's possible to change CreateFile's param to ROS<char> and avoid all these allocations. // We don't throw on error since we already did all the proper validations before. string?current = GetImmediateLinkTarget(linkPath, isDirectory, throwOnError: false, returnFullPath: true); string?prev = null; while (current != null) { prev = current; current = GetImmediateLinkTarget(current, isDirectory, throwOnError: false, returnFullPath: true); } return(prev); } }
// Returns 0 on success, otherwise a Win32 error code. Note that // classes should use -1 as the uninitialized state for dataInitialized. internal static int FillAttributeInfo(String path, ref Interop.Kernel32.WIN32_FILE_ATTRIBUTE_DATA data, bool tryagain, bool returnErrorOnNotFound) { int errorCode = 0; if (tryagain) // someone has a handle to the file open, or other error { Interop.Kernel32.WIN32_FIND_DATA findData; findData = new Interop.Kernel32.WIN32_FIND_DATA(); // Remove trailing slash since this can cause grief to FindFirstFile. You will get an invalid argument error String tempPath = path.TrimEnd(new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }); // For floppy drives, normally the OS will pop up a dialog saying // there is no disk in drive A:, please insert one. We don't want that. // SetThreadErrorMode will let us disable this, but we should set the error // mode back, since this may have wide-ranging effects. uint oldMode; bool setThreadErrorModeSuccess = Interop.Kernel32.SetThreadErrorMode(Interop.Kernel32.SEM_FAILCRITICALERRORS, out oldMode); try { bool error = false; SafeFindHandle handle = Interop.Kernel32.FindFirstFile(tempPath, ref findData); try { if (handle.IsInvalid) { error = true; errorCode = Marshal.GetLastWin32Error(); if (errorCode == Interop.Errors.ERROR_FILE_NOT_FOUND || errorCode == Interop.Errors.ERROR_PATH_NOT_FOUND || errorCode == Interop.Errors.ERROR_NOT_READY) // floppy device not ready { if (!returnErrorOnNotFound) { // Return default value for backward compatibility errorCode = 0; data.fileAttributes = -1; } } return(errorCode); } } finally { // Close the Win32 handle try { handle.Dispose(); } catch { // if we're already returning an error, don't throw another one. if (!error) { throw Win32Marshal.GetExceptionForLastWin32Error(); } } } } finally { if (setThreadErrorModeSuccess) { Interop.Kernel32.SetThreadErrorMode(oldMode, out oldMode); } } // Copy the information to data data.PopulateFrom(ref findData); } else { // For floppy drives, normally the OS will pop up a dialog saying // there is no disk in drive A:, please insert one. We don't want that. // SetThreadErrorMode will let us disable this, but we should set the error // mode back, since this may have wide-ranging effects. bool success = false; uint oldMode; bool setThreadErrorModeSuccess = Interop.Kernel32.SetThreadErrorMode(Interop.Kernel32.SEM_FAILCRITICALERRORS, out oldMode); try { success = Interop.Kernel32.GetFileAttributesEx(path, Interop.Kernel32.GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, ref data); } finally { if (setThreadErrorModeSuccess) { Interop.Kernel32.SetThreadErrorMode(oldMode, out oldMode); } } if (!success) { errorCode = Marshal.GetLastWin32Error(); if (errorCode != Interop.Errors.ERROR_FILE_NOT_FOUND && errorCode != Interop.Errors.ERROR_PATH_NOT_FOUND && errorCode != Interop.Errors.ERROR_NOT_READY) // floppy device not ready { // In case someone latched onto the file. Take the perf hit only for failure return(FillAttributeInfo(path, ref data, true, returnErrorOnNotFound)); } else { if (!returnErrorOnNotFound) { // Return default value for backward compatibility errorCode = 0; data.fileAttributes = -1; } } } } return(errorCode); }
private static bool ShouldUseWinRT(string fullPath, bool isCreate) { // The purpose of this method is to determine if we can access a path // via Win32 or if we need to fallback to WinRT. // We prefer Win32 since it is faster, WinRT's APIs eventually just // call into Win32 after all, but it doesn't provide access to, // brokered paths (like Pictures or Documents) nor does it handle // placeholder files. So we'd like to fall back to WinRT whenever // we can't access a path, or if it known to be a placeholder file. bool useWinRt = false; do { // first use GetFileAttributesEx as it is faster than FindFirstFile and requires minimum permissions Interop.Kernel32.WIN32_FILE_ATTRIBUTE_DATA data = new Interop.Kernel32.WIN32_FILE_ATTRIBUTE_DATA(); if (Interop.Kernel32.GetFileAttributesEx(fullPath, Interop.Kernel32.GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, ref data)) { // got the attributes if ((data.fileAttributes & Interop.Kernel32.FileAttributes.FILE_ATTRIBUTE_DIRECTORY) != 0 || (data.fileAttributes & Interop.Kernel32.FileAttributes.FILE_ATTRIBUTE_REPARSE_POINT) == 0) { // we have a directory or a file that is not a reparse point // useWinRt = false; break; } else { // we need to get the find data to determine if it is a placeholder file Interop.Kernel32.WIN32_FIND_DATA findData = new Interop.Kernel32.WIN32_FIND_DATA(); using (SafeFindHandle handle = Interop.Kernel32.FindFirstFile(fullPath, ref findData)) { if (!handle.IsInvalid) { // got the find data, use WinRT for placeholder files Debug.Assert((findData.dwFileAttributes & Interop.Kernel32.FileAttributes.FILE_ATTRIBUTE_DIRECTORY) == 0); Debug.Assert((findData.dwFileAttributes & Interop.Kernel32.FileAttributes.FILE_ATTRIBUTE_REPARSE_POINT) != 0); useWinRt = findData.dwReserved0 == Interop.Kernel32.IOReparseOptions.IO_REPARSE_TAG_FILE_PLACEHOLDER; break; } } } } int error = Marshal.GetLastWin32Error(); Debug.Assert(error != Interop.Errors.ERROR_SUCCESS); if (error == Interop.Errors.ERROR_ACCESS_DENIED) { // The path was not accessible with Win32, so try WinRT useWinRt = true; break; } else if (error != Interop.Errors.ERROR_PATH_NOT_FOUND && error != Interop.Errors.ERROR_FILE_NOT_FOUND) { // We hit some error other than ACCESS_DENIED or NOT_FOUND, // Default to Win32 to provide most accurate error behavior break; } // error was ERROR_PATH_NOT_FOUND or ERROR_FILE_NOT_FOUND // if we are creating a file/directory we cannot assume that Win32 will have access to // the parent directory, so we walk up the path. fullPath = PathHelpers.GetDirectoryNameInternal(fullPath); // only walk up the path if we are creating a file/directory and not at the root } while (isCreate && !String.IsNullOrEmpty(fullPath)); return(useWinRt); }
private static void RemoveDirectoryRecursive(string fullPath, ref Interop.Kernel32.WIN32_FIND_DATA findData, bool topLevel) { int errorCode; Exception exception = null; using (SafeFindHandle handle = Interop.Kernel32.FindFirstFile(Directory.EnsureTrailingDirectorySeparator(fullPath) + "*", ref findData)) { if (handle.IsInvalid) { throw Win32Marshal.GetExceptionForLastWin32Error(fullPath); } do { if ((findData.dwFileAttributes & Interop.Kernel32.FileAttributes.FILE_ATTRIBUTE_DIRECTORY) == 0) { // File string fileName = findData.cFileName.GetStringFromFixedBuffer(); if (!Interop.Kernel32.DeleteFile(Path.Combine(fullPath, fileName)) && exception == null) { errorCode = Marshal.GetLastWin32Error(); // We don't care if something else deleted the file first if (errorCode != Interop.Errors.ERROR_FILE_NOT_FOUND) { exception = Win32Marshal.GetExceptionForWin32Error(errorCode, fileName); } } } else { // Directory, skip ".", "..". if (findData.cFileName.FixedBufferEqualsString(".") || findData.cFileName.FixedBufferEqualsString("..")) { continue; } string fileName = findData.cFileName.GetStringFromFixedBuffer(); if ((findData.dwFileAttributes & (int)FileAttributes.ReparsePoint) == 0) { // Not a reparse point, recurse. try { RemoveDirectoryRecursive( Path.Combine(fullPath, fileName), findData: ref findData, topLevel: false); } catch (Exception e) { if (exception == null) { exception = e; } } } else { // Reparse point, don't recurse, just remove. (dwReserved0 is documented for this flag) if (findData.dwReserved0 == Interop.Kernel32.IOReparseOptions.IO_REPARSE_TAG_MOUNT_POINT) { // Mount point. Unmount using full path plus a trailing '\'. // (Note: This doesn't remove the underlying directory) string mountPoint = Path.Combine(fullPath, fileName + PathHelpers.DirectorySeparatorCharAsString); if (!Interop.Kernel32.DeleteVolumeMountPoint(mountPoint) && exception == null) { errorCode = Marshal.GetLastWin32Error(); if (errorCode != Interop.Errors.ERROR_SUCCESS && errorCode != Interop.Errors.ERROR_PATH_NOT_FOUND) { exception = Win32Marshal.GetExceptionForWin32Error(errorCode, fileName); } } } // Note that RemoveDirectory on a symbolic link will remove the link itself. if (!Interop.Kernel32.RemoveDirectory(Path.Combine(fullPath, fileName)) && exception == null) { errorCode = Marshal.GetLastWin32Error(); if (errorCode != Interop.Errors.ERROR_PATH_NOT_FOUND) { exception = Win32Marshal.GetExceptionForWin32Error(errorCode, fileName); } } } } } while (Interop.Kernel32.FindNextFile(handle, ref findData)); if (exception != null) { throw exception; } errorCode = Marshal.GetLastWin32Error(); if (errorCode != Interop.Errors.ERROR_SUCCESS && errorCode != Interop.Errors.ERROR_NO_MORE_FILES) { throw Win32Marshal.GetExceptionForWin32Error(errorCode, fullPath); } } RemoveDirectoryInternal(fullPath, topLevel: topLevel); }
[System.Security.SecurityCritical] // auto-generated private static void RemoveDirectoryHelper(string fullPath, bool recursive, bool throwOnTopLevelDirectoryNotFound) { bool r; int errorCode; Exception ex = null; // Do not recursively delete through reparse points. Perhaps in a // future version we will add a new flag to control this behavior, // but for now we're much safer if we err on the conservative side. // This applies to symbolic links and mount points. // Note the logic to check whether fullPath is a reparse point is // in Delete(String, String, bool), and will set "recursive" to false. // Note that Win32's DeleteFile and RemoveDirectory will just delete // the reparse point itself. if (recursive) { Interop.Kernel32.WIN32_FIND_DATA data = new Interop.Kernel32.WIN32_FIND_DATA(); // Open a Find handle using (SafeFindHandle hnd = Interop.Kernel32.FindFirstFile(Directory.EnsureTrailingDirectorySeparator(fullPath) + "*", ref data)) { if (hnd.IsInvalid) throw Win32Marshal.GetExceptionForLastWin32Error(fullPath); do { bool isDir = (0 != (data.dwFileAttributes & Interop.Kernel32.FileAttributes.FILE_ATTRIBUTE_DIRECTORY)); if (isDir) { // Skip ".", "..". if (data.cFileName.Equals(".") || data.cFileName.Equals("..")) continue; // Recurse for all directories, unless they are // reparse points. Do not follow mount points nor // symbolic links, but do delete the reparse point // itself. bool shouldRecurse = (0 == (data.dwFileAttributes & (int)FileAttributes.ReparsePoint)); if (shouldRecurse) { string newFullPath = Path.Combine(fullPath, data.cFileName); try { RemoveDirectoryHelper(newFullPath, recursive, false); } catch (Exception e) { if (ex == null) ex = e; } } else { // Check to see if this is a mount point, and // unmount it. if (data.dwReserved0 == Interop.Kernel32.IOReparseOptions.IO_REPARSE_TAG_MOUNT_POINT) { // Use full path plus a trailing '\' String mountPoint = Path.Combine(fullPath, data.cFileName + PathHelpers.DirectorySeparatorCharAsString); if (!Interop.Kernel32.DeleteVolumeMountPoint(mountPoint)) { errorCode = Marshal.GetLastWin32Error(); if (errorCode != Interop.Errors.ERROR_SUCCESS && errorCode != Interop.Errors.ERROR_PATH_NOT_FOUND) { try { throw Win32Marshal.GetExceptionForWin32Error(errorCode, data.cFileName); } catch (Exception e) { if (ex == null) ex = e; } } } } // RemoveDirectory on a symbolic link will // remove the link itself. String reparsePoint = Path.Combine(fullPath, data.cFileName); r = Interop.Kernel32.RemoveDirectory(reparsePoint); if (!r) { errorCode = Marshal.GetLastWin32Error(); if (errorCode != Interop.Errors.ERROR_PATH_NOT_FOUND) { try { throw Win32Marshal.GetExceptionForWin32Error(errorCode, data.cFileName); } catch (Exception e) { if (ex == null) ex = e; } } } } } else { String fileName = Path.Combine(fullPath, data.cFileName); r = Interop.Kernel32.DeleteFile(fileName); if (!r) { errorCode = Marshal.GetLastWin32Error(); if (errorCode != Interop.Errors.ERROR_FILE_NOT_FOUND) { try { throw Win32Marshal.GetExceptionForWin32Error(errorCode, data.cFileName); } catch (Exception e) { if (ex == null) ex = e; } } } } } while (Interop.Kernel32.FindNextFile(hnd, ref data)); // Make sure we quit with a sensible error. errorCode = Marshal.GetLastWin32Error(); } if (ex != null) throw ex; if (errorCode != 0 && errorCode != Interop.Errors.ERROR_NO_MORE_FILES) throw Win32Marshal.GetExceptionForWin32Error(errorCode, fullPath); } r = Interop.Kernel32.RemoveDirectory(fullPath); if (!r) { errorCode = Marshal.GetLastWin32Error(); if (errorCode == Interop.Errors.ERROR_FILE_NOT_FOUND) // A dubious error code. errorCode = Interop.Errors.ERROR_PATH_NOT_FOUND; // This check was originally put in for Win9x (unfortunately without special casing it to be for Win9x only). We can't change the NT codepath now for backcomp reasons. if (errorCode == Interop.Errors.ERROR_ACCESS_DENIED) throw new IOException(SR.Format(SR.UnauthorizedAccess_IODenied_Path, fullPath)); // don't throw the DirectoryNotFoundException since this is a subdir and // there could be a race condition between two Directory.Delete callers if (errorCode == Interop.Errors.ERROR_PATH_NOT_FOUND && !throwOnTopLevelDirectoryNotFound) return; throw Win32Marshal.GetExceptionForWin32Error(errorCode, fullPath); } }
internal FileInfo(string fullPath, ref Interop.Kernel32.WIN32_FIND_DATA findData) : this(fullPath, findData.cFileName) { Debug.Assert(string.Equals(findData.cFileName, Path.GetFileName(fullPath), StringComparison.Ordinal)); Init(ref findData); }
/// <summary> /// Returns 0 on success, otherwise a Win32 error code. Note that /// classes should use -1 as the uninitialized state for dataInitialized. /// </summary> /// <param name="returnErrorOnNotFound">Return the error code for not found errors?</param> internal static int FillAttributeInfo(string path, ref Interop.Kernel32.WIN32_FILE_ATTRIBUTE_DATA data, bool returnErrorOnNotFound) { int errorCode = Interop.Errors.ERROR_SUCCESS; // Neither GetFileAttributes or FindFirstFile like trailing separators path = PathInternal.TrimEndingDirectorySeparator(path); using (DisableMediaInsertionPrompt.Create()) { if (!Interop.Kernel32.GetFileAttributesEx(path, Interop.Kernel32.GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, ref data)) { errorCode = Marshal.GetLastWin32Error(); if (errorCode != Interop.Errors.ERROR_FILE_NOT_FOUND && errorCode != Interop.Errors.ERROR_PATH_NOT_FOUND && errorCode != Interop.Errors.ERROR_NOT_READY && errorCode != Interop.Errors.ERROR_INVALID_NAME && errorCode != Interop.Errors.ERROR_BAD_PATHNAME && errorCode != Interop.Errors.ERROR_BAD_NETPATH && errorCode != Interop.Errors.ERROR_BAD_NET_NAME && errorCode != Interop.Errors.ERROR_INVALID_PARAMETER && errorCode != Interop.Errors.ERROR_NETWORK_UNREACHABLE) { // Assert so we can track down other cases (if any) to add to our test suite Debug.Assert(errorCode == Interop.Errors.ERROR_ACCESS_DENIED || errorCode == Interop.Errors.ERROR_SHARING_VIOLATION, $"Unexpected error code getting attributes {errorCode}"); // Files that are marked for deletion will not let you GetFileAttributes, // ERROR_ACCESS_DENIED is given back without filling out the data struct. // FindFirstFile, however, will. Historically we always gave back attributes // for marked-for-deletion files. // // Another case where enumeration works is with special system files such as // pagefile.sys that give back ERROR_SHARING_VIOLATION on GetAttributes. // // Ideally we'd only try again for known cases due to the potential performance // hit. The last attempt to do so baked for nearly a year before we found the // pagefile.sys case. As such we're probably stuck filtering out specific // cases that we know we don't want to retry on. var findData = new Interop.Kernel32.WIN32_FIND_DATA(); using (SafeFindHandle handle = Interop.Kernel32.FindFirstFile(path, ref findData)) { if (handle.IsInvalid) { errorCode = Marshal.GetLastWin32Error(); } else { errorCode = Interop.Errors.ERROR_SUCCESS; data.PopulateFrom(ref findData); } } } } } if (errorCode != Interop.Errors.ERROR_SUCCESS && !returnErrorOnNotFound) { switch (errorCode) { case Interop.Errors.ERROR_FILE_NOT_FOUND: case Interop.Errors.ERROR_PATH_NOT_FOUND: case Interop.Errors.ERROR_NOT_READY: // Removable media not ready // Return default value for backward compatibility data.dwFileAttributes = -1; return(Interop.Errors.ERROR_SUCCESS); } } return(errorCode); }
[System.Security.SecurityCritical] // auto-generated private static void RemoveDirectoryHelper(string fullPath, bool recursive, bool throwOnTopLevelDirectoryNotFound) { bool r; int errorCode; Exception ex = null; // Do not recursively delete through reparse points. Perhaps in a // future version we will add a new flag to control this behavior, // but for now we're much safer if we err on the conservative side. // This applies to symbolic links and mount points. // Note the logic to check whether fullPath is a reparse point is // in Delete(string, string, bool), and will set "recursive" to false. // Note that Win32's DeleteFile and RemoveDirectory will just delete // the reparse point itself. if (recursive) { Interop.Kernel32.WIN32_FIND_DATA data = new Interop.Kernel32.WIN32_FIND_DATA(); // Open a Find handle using (SafeFindHandle hnd = Interop.Kernel32.FindFirstFile(Directory.EnsureTrailingDirectorySeparator(fullPath) + "*", ref data)) { if (hnd.IsInvalid) { throw Win32Marshal.GetExceptionForLastWin32Error(fullPath); } do { bool isDir = (0 != (data.dwFileAttributes & Interop.Kernel32.FileAttributes.FILE_ATTRIBUTE_DIRECTORY)); if (isDir) { // Skip ".", "..". if (data.cFileName.Equals(".") || data.cFileName.Equals("..")) { continue; } // Recurse for all directories, unless they are // reparse points. Do not follow mount points nor // symbolic links, but do delete the reparse point // itself. bool shouldRecurse = (0 == (data.dwFileAttributes & (int)FileAttributes.ReparsePoint)); if (shouldRecurse) { string newFullPath = Path.Combine(fullPath, data.cFileName); try { RemoveDirectoryHelper(newFullPath, recursive, false); } catch (Exception e) { if (ex == null) { ex = e; } } } else { // Check to see if this is a mount point, and // unmount it. if (data.dwReserved0 == Interop.Kernel32.IOReparseOptions.IO_REPARSE_TAG_MOUNT_POINT) { // Use full path plus a trailing '\' string mountPoint = Path.Combine(fullPath, data.cFileName + PathHelpers.DirectorySeparatorCharAsString); if (!Interop.Kernel32.DeleteVolumeMountPoint(mountPoint)) { errorCode = Marshal.GetLastWin32Error(); if (errorCode != Interop.Errors.ERROR_SUCCESS && errorCode != Interop.Errors.ERROR_PATH_NOT_FOUND) { try { throw Win32Marshal.GetExceptionForWin32Error(errorCode, data.cFileName); } catch (Exception e) { if (ex == null) { ex = e; } } } } } // RemoveDirectory on a symbolic link will // remove the link itself. string reparsePoint = Path.Combine(fullPath, data.cFileName); r = Interop.Kernel32.RemoveDirectory(reparsePoint); if (!r) { errorCode = Marshal.GetLastWin32Error(); if (errorCode != Interop.Errors.ERROR_PATH_NOT_FOUND) { try { throw Win32Marshal.GetExceptionForWin32Error(errorCode, data.cFileName); } catch (Exception e) { if (ex == null) { ex = e; } } } } } } else { string fileName = Path.Combine(fullPath, data.cFileName); r = Interop.Kernel32.DeleteFile(fileName); if (!r) { errorCode = Marshal.GetLastWin32Error(); if (errorCode != Interop.Errors.ERROR_FILE_NOT_FOUND) { try { throw Win32Marshal.GetExceptionForWin32Error(errorCode, data.cFileName); } catch (Exception e) { if (ex == null) { ex = e; } } } } } } while (Interop.Kernel32.FindNextFile(hnd, ref data)); // Make sure we quit with a sensible error. errorCode = Marshal.GetLastWin32Error(); } if (ex != null) { throw ex; } if (errorCode != 0 && errorCode != Interop.Errors.ERROR_NO_MORE_FILES) { throw Win32Marshal.GetExceptionForWin32Error(errorCode, fullPath); } } r = Interop.Kernel32.RemoveDirectory(fullPath); if (!r) { errorCode = Marshal.GetLastWin32Error(); if (errorCode == Interop.Errors.ERROR_FILE_NOT_FOUND) // A dubious error code. { errorCode = Interop.Errors.ERROR_PATH_NOT_FOUND; } // This check was originally put in for Win9x (unfortunately without special casing it to be for Win9x only). We can't change the NT codepath now for backcomp reasons. if (errorCode == Interop.Errors.ERROR_ACCESS_DENIED) { throw new IOException(SR.Format(SR.UnauthorizedAccess_IODenied_Path, fullPath)); } // don't throw the DirectoryNotFoundException since this is a subdir and // there could be a race condition between two Directory.Delete callers if (errorCode == Interop.Errors.ERROR_PATH_NOT_FOUND && !throwOnTopLevelDirectoryNotFound) { return; } throw Win32Marshal.GetExceptionForWin32Error(errorCode, fullPath); } }
private static void RemoveDirectoryRecursive(string fullPath, ref Interop.Kernel32.WIN32_FIND_DATA findData, bool topLevel) { int errorCode; Exception exception = null; using (SafeFindHandle handle = Interop.Kernel32.FindFirstFile(Path.Join(fullPath, "*"), ref findData)) { if (handle.IsInvalid) { throw Win32Marshal.GetExceptionForLastWin32Error(fullPath); } do { if ((findData.dwFileAttributes & Interop.Kernel32.FileAttributes.FILE_ATTRIBUTE_DIRECTORY) == 0) { // File string fileName = findData.cFileName.GetStringFromFixedBuffer(); if (!Interop.Kernel32.DeleteFile(Path.Combine(fullPath, fileName)) && exception == null) { errorCode = Marshal.GetLastWin32Error(); // We don't care if something else deleted the file first if (errorCode != Interop.Errors.ERROR_FILE_NOT_FOUND) { exception = Win32Marshal.GetExceptionForWin32Error(errorCode, fileName); } } } else { // Directory, skip ".", "..". if (findData.cFileName.FixedBufferEqualsString(".") || findData.cFileName.FixedBufferEqualsString("..")) { continue; } string fileName = findData.cFileName.GetStringFromFixedBuffer(); if (!IsNameSurrogateReparsePoint(ref findData)) { // Not a reparse point, or the reparse point isn't a name surrogate, recurse. try { RemoveDirectoryRecursive( Path.Combine(fullPath, fileName), findData: ref findData, topLevel: false); } catch (Exception e) { if (exception == null) { exception = e; } } } else { // Name surrogate reparse point, don't recurse, simply remove the directory. // If a mount point, we have to delete the mount point first. if (findData.dwReserved0 == Interop.Kernel32.IOReparseOptions.IO_REPARSE_TAG_MOUNT_POINT) { // Mount point. Unmount using full path plus a trailing '\'. // (Note: This doesn't remove the underlying directory) string mountPoint = Path.Join(fullPath, fileName, PathInternal.DirectorySeparatorCharAsString); if (!Interop.Kernel32.DeleteVolumeMountPoint(mountPoint) && exception == null) { errorCode = Marshal.GetLastWin32Error(); if (errorCode != Interop.Errors.ERROR_SUCCESS && errorCode != Interop.Errors.ERROR_PATH_NOT_FOUND) { exception = Win32Marshal.GetExceptionForWin32Error(errorCode, fileName); } } } // Note that RemoveDirectory on a symbolic link will remove the link itself. if (!Interop.Kernel32.RemoveDirectory(Path.Combine(fullPath, fileName)) && exception == null) { errorCode = Marshal.GetLastWin32Error(); if (errorCode != Interop.Errors.ERROR_PATH_NOT_FOUND) { exception = Win32Marshal.GetExceptionForWin32Error(errorCode, fileName); } } } } } while (Interop.Kernel32.FindNextFile(handle, ref findData)); if (exception != null) { throw exception; } errorCode = Marshal.GetLastWin32Error(); if (errorCode != Interop.Errors.ERROR_SUCCESS && errorCode != Interop.Errors.ERROR_NO_MORE_FILES) { throw Win32Marshal.GetExceptionForWin32Error(errorCode, fullPath); } } // As we successfully removed all of the files we shouldn't care about the directory itself // not being empty. As file deletion is just a marker to remove the file when all handles // are closed we could still have contents hanging around. RemoveDirectoryInternal(fullPath, topLevel: topLevel, allowDirectoryNotEmpty: true); }
[System.Security.SecurityCritical] // auto-generated internal static int FillAttributeInfo(String path, ref Interop.Kernel32.WIN32_FILE_ATTRIBUTE_DATA data, bool tryagain, bool returnErrorOnNotFound) { int errorCode = 0; if (tryagain) // someone has a handle to the file open, or other error { Interop.Kernel32.WIN32_FIND_DATA findData; findData = new Interop.Kernel32.WIN32_FIND_DATA(); // Remove trailing slash since this can cause grief to FindFirstFile. You will get an invalid argument error String tempPath = path.TrimEnd(PathHelpers.DirectorySeparatorChars); // For floppy drives, normally the OS will pop up a dialog saying // there is no disk in drive A:, please insert one. We don't want that. // SetErrorMode will let us disable this, but we should set the error // mode back, since this may have wide-ranging effects. uint oldMode = Interop.Kernel32.SetErrorMode(Interop.Kernel32.SEM_FAILCRITICALERRORS); try { bool error = false; SafeFindHandle handle = Interop.Kernel32.FindFirstFile(tempPath, ref findData); try { if (handle.IsInvalid) { error = true; errorCode = Marshal.GetLastWin32Error(); if (errorCode == Interop.Errors.ERROR_FILE_NOT_FOUND || errorCode == Interop.Errors.ERROR_PATH_NOT_FOUND || errorCode == Interop.Errors.ERROR_NOT_READY) // floppy device not ready { if (!returnErrorOnNotFound) { // Return default value for backward compatibility errorCode = 0; data.fileAttributes = -1; } } return errorCode; } } finally { // Close the Win32 handle try { handle.Dispose(); } catch { // if we're already returning an error, don't throw another one. if (!error) { throw Win32Marshal.GetExceptionForLastWin32Error(); } } } } finally { Interop.Kernel32.SetErrorMode(oldMode); } // Copy the information to data data.PopulateFrom(ref findData); } else { // For floppy drives, normally the OS will pop up a dialog saying // there is no disk in drive A:, please insert one. We don't want that. // SetErrorMode will let us disable this, but we should set the error // mode back, since this may have wide-ranging effects. bool success = false; uint oldMode = Interop.Kernel32.SetErrorMode(Interop.Kernel32.SEM_FAILCRITICALERRORS); try { success = Interop.Kernel32.GetFileAttributesEx(path, Interop.Kernel32.GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, ref data); } finally { Interop.Kernel32.SetErrorMode(oldMode); } if (!success) { errorCode = Marshal.GetLastWin32Error(); if (errorCode != Interop.Errors.ERROR_FILE_NOT_FOUND && errorCode != Interop.Errors.ERROR_PATH_NOT_FOUND && errorCode != Interop.Errors.ERROR_NOT_READY) // floppy device not ready { // In case someone latched onto the file. Take the perf hit only for failure return FillAttributeInfo(path, ref data, true, returnErrorOnNotFound); } else { if (!returnErrorOnNotFound) { // Return default value for backward compatibility errorCode = 0; data.fileAttributes = -1; } } } } return errorCode; }