1 // Licensed to the .NET Foundation under one or more agreements. 2 // The .NET Foundation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 using Microsoft.Win32.SafeHandles; 6 using System.Collections.Generic; 7 using System.Runtime.InteropServices; 8 using System.Text; 9 10 namespace System.IO 11 { 12 internal static partial class FileSystem 13 { 14 internal const int GENERIC_READ = unchecked((int)0x80000000); 15 CopyFile(string sourceFullPath, string destFullPath, bool overwrite)16 public static void CopyFile(string sourceFullPath, string destFullPath, bool overwrite) 17 { 18 int errorCode = Interop.Kernel32.CopyFile(sourceFullPath, destFullPath, !overwrite); 19 20 if (errorCode != Interop.Errors.ERROR_SUCCESS) 21 { 22 string fileName = destFullPath; 23 24 if (errorCode != Interop.Errors.ERROR_FILE_EXISTS) 25 { 26 // For a number of error codes (sharing violation, path not found, etc) we don't know if the problem was with 27 // the source or dest file. Try reading the source file. 28 using (SafeFileHandle handle = Interop.Kernel32.CreateFile(sourceFullPath, GENERIC_READ, FileShare.Read, FileMode.Open, 0)) 29 { 30 if (handle.IsInvalid) 31 fileName = sourceFullPath; 32 } 33 34 if (errorCode == Interop.Errors.ERROR_ACCESS_DENIED) 35 { 36 if (DirectoryExists(destFullPath)) 37 throw new IOException(SR.Format(SR.Arg_FileIsDirectory_Name, destFullPath), Interop.Errors.ERROR_ACCESS_DENIED); 38 } 39 } 40 41 throw Win32Marshal.GetExceptionForWin32Error(errorCode, fileName); 42 } 43 } 44 ReplaceFile(string sourceFullPath, string destFullPath, string destBackupFullPath, bool ignoreMetadataErrors)45 public static void ReplaceFile(string sourceFullPath, string destFullPath, string destBackupFullPath, bool ignoreMetadataErrors) 46 { 47 int flags = ignoreMetadataErrors ? Interop.Kernel32.REPLACEFILE_IGNORE_MERGE_ERRORS : 0; 48 49 if (!Interop.Kernel32.ReplaceFile(destFullPath, sourceFullPath, destBackupFullPath, flags, IntPtr.Zero, IntPtr.Zero)) 50 { 51 throw Win32Marshal.GetExceptionForWin32Error(Marshal.GetLastWin32Error()); 52 } 53 } 54 CreateDirectory(string fullPath)55 public static void CreateDirectory(string fullPath) 56 { 57 // We can save a bunch of work if the directory we want to create already exists. This also 58 // saves us in the case where sub paths are inaccessible (due to ERROR_ACCESS_DENIED) but the 59 // final path is accessible and the directory already exists. For example, consider trying 60 // to create c:\Foo\Bar\Baz, where everything already exists but ACLS prevent access to c:\Foo 61 // and c:\Foo\Bar. In that case, this code will think it needs to create c:\Foo, and c:\Foo\Bar 62 // and fail to due so, causing an exception to be thrown. This is not what we want. 63 if (DirectoryExists(fullPath)) 64 return; 65 66 List<string> stackDir = new List<string>(); 67 68 // Attempt to figure out which directories don't exist, and only 69 // create the ones we need. Note that InternalExists may fail due 70 // to Win32 ACL's preventing us from seeing a directory, and this 71 // isn't threadsafe. 72 73 bool somepathexists = false; 74 75 int length = fullPath.Length; 76 77 // We need to trim the trailing slash or the code will try to create 2 directories of the same name. 78 if (length >= 2 && PathHelpers.EndsInDirectorySeparator(fullPath)) 79 length--; 80 81 int lengthRoot = PathInternal.GetRootLength(fullPath); 82 83 if (length > lengthRoot) 84 { 85 // Special case root (fullpath = X:\\) 86 int i = length - 1; 87 while (i >= lengthRoot && !somepathexists) 88 { 89 string dir = fullPath.Substring(0, i + 1); 90 91 if (!DirectoryExists(dir)) // Create only the ones missing 92 stackDir.Add(dir); 93 else 94 somepathexists = true; 95 96 while (i > lengthRoot && !PathInternal.IsDirectorySeparator(fullPath[i])) i--; 97 i--; 98 } 99 } 100 101 int count = stackDir.Count; 102 103 // If we were passed a DirectorySecurity, convert it to a security 104 // descriptor and set it in he call to CreateDirectory. 105 Interop.Kernel32.SECURITY_ATTRIBUTES secAttrs = default; 106 107 bool r = true; 108 int firstError = 0; 109 string errorString = fullPath; 110 111 // If all the security checks succeeded create all the directories 112 while (stackDir.Count > 0) 113 { 114 string name = stackDir[stackDir.Count - 1]; 115 stackDir.RemoveAt(stackDir.Count - 1); 116 117 r = Interop.Kernel32.CreateDirectory(name, ref secAttrs); 118 if (!r && (firstError == 0)) 119 { 120 int currentError = Marshal.GetLastWin32Error(); 121 // While we tried to avoid creating directories that don't 122 // exist above, there are at least two cases that will 123 // cause us to see ERROR_ALREADY_EXISTS here. InternalExists 124 // can fail because we didn't have permission to the 125 // directory. Secondly, another thread or process could 126 // create the directory between the time we check and the 127 // time we try using the directory. Thirdly, it could 128 // fail because the target does exist, but is a file. 129 if (currentError != Interop.Errors.ERROR_ALREADY_EXISTS) 130 firstError = currentError; 131 else 132 { 133 // If there's a file in this directory's place, or if we have ERROR_ACCESS_DENIED when checking if the directory already exists throw. 134 if (File.InternalExists(name) || (!DirectoryExists(name, out currentError) && currentError == Interop.Errors.ERROR_ACCESS_DENIED)) 135 { 136 firstError = currentError; 137 errorString = name; 138 } 139 } 140 } 141 } 142 143 // We need this check to mask OS differences 144 // Handle CreateDirectory("X:\\") when X: doesn't exist. Similarly for n/w paths. 145 if ((count == 0) && !somepathexists) 146 { 147 string root = Directory.InternalGetDirectoryRoot(fullPath); 148 if (!DirectoryExists(root)) 149 throw Win32Marshal.GetExceptionForWin32Error(Interop.Errors.ERROR_PATH_NOT_FOUND, root); 150 return; 151 } 152 153 // Only throw an exception if creating the exact directory we 154 // wanted failed to work correctly. 155 if (!r && (firstError != 0)) 156 throw Win32Marshal.GetExceptionForWin32Error(firstError, errorString); 157 } 158 DeleteFile(string fullPath)159 public static void DeleteFile(string fullPath) 160 { 161 bool r = Interop.Kernel32.DeleteFile(fullPath); 162 if (!r) 163 { 164 int errorCode = Marshal.GetLastWin32Error(); 165 if (errorCode == Interop.Errors.ERROR_FILE_NOT_FOUND) 166 return; 167 else 168 throw Win32Marshal.GetExceptionForWin32Error(errorCode, fullPath); 169 } 170 } 171 DirectoryExists(string fullPath)172 public static bool DirectoryExists(string fullPath) 173 { 174 return DirectoryExists(fullPath, out int lastError); 175 } 176 DirectoryExists(string path, out int lastError)177 private static bool DirectoryExists(string path, out int lastError) 178 { 179 Interop.Kernel32.WIN32_FILE_ATTRIBUTE_DATA data = new Interop.Kernel32.WIN32_FILE_ATTRIBUTE_DATA(); 180 lastError = FillAttributeInfo(path, ref data, returnErrorOnNotFound: true); 181 182 return (lastError == 0) && (data.dwFileAttributes != -1) 183 && ((data.dwFileAttributes & Interop.Kernel32.FileAttributes.FILE_ATTRIBUTE_DIRECTORY) != 0); 184 } 185 EnumeratePaths(string fullPath, string searchPattern, SearchOption searchOption, SearchTarget searchTarget)186 public static IEnumerable<string> EnumeratePaths(string fullPath, string searchPattern, SearchOption searchOption, SearchTarget searchTarget) 187 { 188 FindEnumerableFactory.NormalizeInputs(ref fullPath, ref searchPattern); 189 switch (searchTarget) 190 { 191 case SearchTarget.Files: 192 return FindEnumerableFactory.UserFiles(fullPath, searchPattern, searchOption == SearchOption.AllDirectories); 193 case SearchTarget.Directories: 194 return FindEnumerableFactory.UserDirectories(fullPath, searchPattern, searchOption == SearchOption.AllDirectories); 195 case SearchTarget.Both: 196 return FindEnumerableFactory.UserEntries(fullPath, searchPattern, searchOption == SearchOption.AllDirectories); 197 default: 198 throw new ArgumentOutOfRangeException(nameof(searchTarget)); 199 } 200 } 201 EnumerateFileSystemInfos(string fullPath, string searchPattern, SearchOption searchOption, SearchTarget searchTarget)202 public static IEnumerable<FileSystemInfo> EnumerateFileSystemInfos(string fullPath, string searchPattern, SearchOption searchOption, SearchTarget searchTarget) 203 { 204 FindEnumerableFactory.NormalizeInputs(ref fullPath, ref searchPattern); 205 switch (searchTarget) 206 { 207 case SearchTarget.Directories: 208 return FindEnumerableFactory.DirectoryInfos(fullPath, searchPattern, searchOption == SearchOption.AllDirectories); 209 case SearchTarget.Files: 210 return FindEnumerableFactory.FileInfos(fullPath, searchPattern, searchOption == SearchOption.AllDirectories); 211 case SearchTarget.Both: 212 return FindEnumerableFactory.FileSystemInfos(fullPath, searchPattern, searchOption == SearchOption.AllDirectories); 213 default: 214 throw new ArgumentException(SR.ArgumentOutOfRange_Enum, nameof(searchTarget)); 215 } 216 } 217 218 /// <summary> 219 /// Returns 0 on success, otherwise a Win32 error code. Note that 220 /// classes should use -1 as the uninitialized state for dataInitialized. 221 /// </summary> 222 /// <param name="returnErrorOnNotFound">Return the error code for not found errors?</param> FillAttributeInfo(string path, ref Interop.Kernel32.WIN32_FILE_ATTRIBUTE_DATA data, bool returnErrorOnNotFound)223 internal static int FillAttributeInfo(string path, ref Interop.Kernel32.WIN32_FILE_ATTRIBUTE_DATA data, bool returnErrorOnNotFound) 224 { 225 int errorCode = Interop.Errors.ERROR_SUCCESS; 226 227 // Neither GetFileAttributes or FindFirstFile like trailing separators 228 path = path.TrimEnd(PathHelpers.DirectorySeparatorChars); 229 230 using (DisableMediaInsertionPrompt.Create()) 231 { 232 if (!Interop.Kernel32.GetFileAttributesEx(path, Interop.Kernel32.GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, ref data)) 233 { 234 errorCode = Marshal.GetLastWin32Error(); 235 if (errorCode == Interop.Errors.ERROR_ACCESS_DENIED) 236 { 237 // Files that are marked for deletion will not let you GetFileAttributes, 238 // ERROR_ACCESS_DENIED is given back without filling out the data struct. 239 // FindFirstFile, however, will. Historically we always gave back attributes 240 // for marked-for-deletion files. 241 242 var findData = new Interop.Kernel32.WIN32_FIND_DATA(); 243 using (SafeFindHandle handle = Interop.Kernel32.FindFirstFile(path, ref findData)) 244 { 245 if (handle.IsInvalid) 246 { 247 errorCode = Marshal.GetLastWin32Error(); 248 } 249 else 250 { 251 errorCode = Interop.Errors.ERROR_SUCCESS; 252 data.PopulateFrom(ref findData); 253 } 254 } 255 } 256 } 257 } 258 259 if (errorCode != Interop.Errors.ERROR_SUCCESS && !returnErrorOnNotFound) 260 { 261 switch (errorCode) 262 { 263 case Interop.Errors.ERROR_FILE_NOT_FOUND: 264 case Interop.Errors.ERROR_PATH_NOT_FOUND: 265 case Interop.Errors.ERROR_NOT_READY: // Removable media not ready 266 // Return default value for backward compatibility 267 data.dwFileAttributes = -1; 268 return Interop.Errors.ERROR_SUCCESS; 269 } 270 } 271 272 return errorCode; 273 } 274 FileExists(string fullPath)275 public static bool FileExists(string fullPath) 276 { 277 Interop.Kernel32.WIN32_FILE_ATTRIBUTE_DATA data = new Interop.Kernel32.WIN32_FILE_ATTRIBUTE_DATA(); 278 int errorCode = FillAttributeInfo(fullPath, ref data, returnErrorOnNotFound: true); 279 280 return (errorCode == 0) && (data.dwFileAttributes != -1) 281 && ((data.dwFileAttributes & Interop.Kernel32.FileAttributes.FILE_ATTRIBUTE_DIRECTORY) == 0); 282 } 283 GetAttributes(string fullPath)284 public static FileAttributes GetAttributes(string fullPath) 285 { 286 Interop.Kernel32.WIN32_FILE_ATTRIBUTE_DATA data = new Interop.Kernel32.WIN32_FILE_ATTRIBUTE_DATA(); 287 int errorCode = FillAttributeInfo(fullPath, ref data, returnErrorOnNotFound: true); 288 if (errorCode != 0) 289 throw Win32Marshal.GetExceptionForWin32Error(errorCode, fullPath); 290 291 return (FileAttributes)data.dwFileAttributes; 292 } 293 GetCurrentDirectory()294 public static string GetCurrentDirectory() 295 { 296 StringBuilder sb = StringBuilderCache.Acquire(Interop.Kernel32.MAX_PATH + 1); 297 if (Interop.Kernel32.GetCurrentDirectory(sb.Capacity, sb) == 0) 298 throw Win32Marshal.GetExceptionForLastWin32Error(); 299 string currentDirectory = sb.ToString(); 300 // Note that if we have somehow put our command prompt into short 301 // file name mode (i.e. by running edlin or a DOS grep, etc), then 302 // this will return a short file name. 303 if (currentDirectory.IndexOf('~') >= 0) 304 { 305 int r = Interop.Kernel32.GetLongPathName(currentDirectory, sb, sb.Capacity); 306 if (r == 0 || r >= Interop.Kernel32.MAX_PATH) 307 { 308 int errorCode = Marshal.GetLastWin32Error(); 309 if (r >= Interop.Kernel32.MAX_PATH) 310 errorCode = Interop.Errors.ERROR_FILENAME_EXCED_RANGE; 311 if (errorCode != Interop.Errors.ERROR_FILE_NOT_FOUND && 312 errorCode != Interop.Errors.ERROR_PATH_NOT_FOUND && 313 errorCode != Interop.Errors.ERROR_INVALID_FUNCTION && // by design - enough said. 314 errorCode != Interop.Errors.ERROR_ACCESS_DENIED) 315 throw Win32Marshal.GetExceptionForWin32Error(errorCode); 316 } 317 currentDirectory = sb.ToString(); 318 } 319 StringBuilderCache.Release(sb); 320 321 return currentDirectory; 322 } 323 GetCreationTime(string fullPath)324 public static DateTimeOffset GetCreationTime(string fullPath) 325 { 326 Interop.Kernel32.WIN32_FILE_ATTRIBUTE_DATA data = new Interop.Kernel32.WIN32_FILE_ATTRIBUTE_DATA(); 327 int errorCode = FillAttributeInfo(fullPath, ref data, returnErrorOnNotFound: false); 328 if (errorCode != 0) 329 throw Win32Marshal.GetExceptionForWin32Error(errorCode, fullPath); 330 331 return data.ftCreationTime.ToDateTimeOffset(); 332 } 333 GetFileSystemInfo(string fullPath, bool asDirectory)334 public static FileSystemInfo GetFileSystemInfo(string fullPath, bool asDirectory) 335 { 336 return asDirectory ? 337 (FileSystemInfo)new DirectoryInfo(fullPath, null) : 338 (FileSystemInfo)new FileInfo(fullPath, null); 339 } 340 GetLastAccessTime(string fullPath)341 public static DateTimeOffset GetLastAccessTime(string fullPath) 342 { 343 Interop.Kernel32.WIN32_FILE_ATTRIBUTE_DATA data = new Interop.Kernel32.WIN32_FILE_ATTRIBUTE_DATA(); 344 int errorCode = FillAttributeInfo(fullPath, ref data, returnErrorOnNotFound: false); 345 if (errorCode != 0) 346 throw Win32Marshal.GetExceptionForWin32Error(errorCode, fullPath); 347 348 return data.ftLastAccessTime.ToDateTimeOffset(); 349 } 350 GetLastWriteTime(string fullPath)351 public static DateTimeOffset GetLastWriteTime(string fullPath) 352 { 353 Interop.Kernel32.WIN32_FILE_ATTRIBUTE_DATA data = new Interop.Kernel32.WIN32_FILE_ATTRIBUTE_DATA(); 354 int errorCode = FillAttributeInfo(fullPath, ref data, returnErrorOnNotFound: false); 355 if (errorCode != 0) 356 throw Win32Marshal.GetExceptionForWin32Error(errorCode, fullPath); 357 358 return data.ftLastWriteTime.ToDateTimeOffset(); 359 } 360 MoveDirectory(string sourceFullPath, string destFullPath)361 public static void MoveDirectory(string sourceFullPath, string destFullPath) 362 { 363 if (!Interop.Kernel32.MoveFile(sourceFullPath, destFullPath)) 364 { 365 int errorCode = Marshal.GetLastWin32Error(); 366 367 if (errorCode == Interop.Errors.ERROR_FILE_NOT_FOUND) 368 throw Win32Marshal.GetExceptionForWin32Error(Interop.Errors.ERROR_PATH_NOT_FOUND, sourceFullPath); 369 370 // 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. 371 if (errorCode == Interop.Errors.ERROR_ACCESS_DENIED) // WinNT throws IOException. This check is for Win9x. We can't change it for backcomp. 372 throw new IOException(SR.Format(SR.UnauthorizedAccess_IODenied_Path, sourceFullPath), Win32Marshal.MakeHRFromErrorCode(errorCode)); 373 374 throw Win32Marshal.GetExceptionForWin32Error(errorCode); 375 } 376 } 377 MoveFile(string sourceFullPath, string destFullPath)378 public static void MoveFile(string sourceFullPath, string destFullPath) 379 { 380 if (!Interop.Kernel32.MoveFile(sourceFullPath, destFullPath)) 381 { 382 throw Win32Marshal.GetExceptionForLastWin32Error(); 383 } 384 } 385 OpenHandle(string fullPath, bool asDirectory)386 private static SafeFileHandle OpenHandle(string fullPath, bool asDirectory) 387 { 388 string root = fullPath.Substring(0, PathInternal.GetRootLength(fullPath)); 389 if (root == fullPath && root[1] == Path.VolumeSeparatorChar) 390 { 391 // intentionally not fullpath, most upstack public APIs expose this as path. 392 throw new ArgumentException(SR.Arg_PathIsVolume, "path"); 393 } 394 395 SafeFileHandle handle = Interop.Kernel32.CreateFile( 396 fullPath, 397 Interop.Kernel32.GenericOperations.GENERIC_WRITE, 398 FileShare.ReadWrite | FileShare.Delete, 399 FileMode.Open, 400 asDirectory ? Interop.Kernel32.FileOperations.FILE_FLAG_BACKUP_SEMANTICS : 0); 401 402 if (handle.IsInvalid) 403 { 404 int errorCode = Marshal.GetLastWin32Error(); 405 406 // NT5 oddity - when trying to open "C:\" as a File, 407 // we usually get ERROR_PATH_NOT_FOUND from the OS. We should 408 // probably be consistent w/ every other directory. 409 if (!asDirectory && errorCode == Interop.Errors.ERROR_PATH_NOT_FOUND && fullPath.Equals(Directory.GetDirectoryRoot(fullPath))) 410 errorCode = Interop.Errors.ERROR_ACCESS_DENIED; 411 412 throw Win32Marshal.GetExceptionForWin32Error(errorCode, fullPath); 413 } 414 415 return handle; 416 } 417 RemoveDirectory(string fullPath, bool recursive)418 public static void RemoveDirectory(string fullPath, bool recursive) 419 { 420 // Do not recursively delete through reparse points. 421 if (!recursive || IsReparsePoint(fullPath)) 422 { 423 RemoveDirectoryInternal(fullPath, topLevel: true); 424 return; 425 } 426 427 // We want extended syntax so we can delete "extended" subdirectories and files 428 // (most notably ones with trailing whitespace or periods) 429 fullPath = PathInternal.EnsureExtendedPrefix(fullPath); 430 431 Interop.Kernel32.WIN32_FIND_DATA findData = new Interop.Kernel32.WIN32_FIND_DATA(); 432 RemoveDirectoryRecursive(fullPath, ref findData, topLevel: true); 433 } 434 IsReparsePoint(string fullPath)435 private static bool IsReparsePoint(string fullPath) 436 { 437 Interop.Kernel32.WIN32_FILE_ATTRIBUTE_DATA data = new Interop.Kernel32.WIN32_FILE_ATTRIBUTE_DATA(); 438 int errorCode = FillAttributeInfo(fullPath, ref data, returnErrorOnNotFound: true); 439 if (errorCode != Interop.Errors.ERROR_SUCCESS) 440 { 441 // File not found doesn't make much sense coming from a directory delete. 442 if (errorCode == Interop.Errors.ERROR_FILE_NOT_FOUND) 443 errorCode = Interop.Errors.ERROR_PATH_NOT_FOUND; 444 throw Win32Marshal.GetExceptionForWin32Error(errorCode, fullPath); 445 } 446 447 return (((FileAttributes)data.dwFileAttributes & FileAttributes.ReparsePoint) != 0); 448 } 449 RemoveDirectoryRecursive(string fullPath, ref Interop.Kernel32.WIN32_FIND_DATA findData, bool topLevel)450 private static void RemoveDirectoryRecursive(string fullPath, ref Interop.Kernel32.WIN32_FIND_DATA findData, bool topLevel) 451 { 452 int errorCode; 453 Exception exception = null; 454 455 using (SafeFindHandle handle = Interop.Kernel32.FindFirstFile(Directory.EnsureTrailingDirectorySeparator(fullPath) + "*", ref findData)) 456 { 457 if (handle.IsInvalid) 458 throw Win32Marshal.GetExceptionForLastWin32Error(fullPath); 459 460 do 461 { 462 if ((findData.dwFileAttributes & Interop.Kernel32.FileAttributes.FILE_ATTRIBUTE_DIRECTORY) == 0) 463 { 464 // File 465 string fileName = findData.cFileName.GetStringFromFixedBuffer(); 466 if (!Interop.Kernel32.DeleteFile(Path.Combine(fullPath, fileName)) && exception == null) 467 { 468 errorCode = Marshal.GetLastWin32Error(); 469 470 // We don't care if something else deleted the file first 471 if (errorCode != Interop.Errors.ERROR_FILE_NOT_FOUND) 472 { 473 exception = Win32Marshal.GetExceptionForWin32Error(errorCode, fileName); 474 } 475 } 476 } 477 else 478 { 479 // Directory, skip ".", "..". 480 if (findData.cFileName.FixedBufferEqualsString(".") || findData.cFileName.FixedBufferEqualsString("..")) 481 continue; 482 483 string fileName = findData.cFileName.GetStringFromFixedBuffer(); 484 if ((findData.dwFileAttributes & (int)FileAttributes.ReparsePoint) == 0) 485 { 486 // Not a reparse point, recurse. 487 try 488 { 489 RemoveDirectoryRecursive( 490 Path.Combine(fullPath, fileName), 491 findData: ref findData, 492 topLevel: false); 493 } 494 catch (Exception e) 495 { 496 if (exception == null) 497 exception = e; 498 } 499 } 500 else 501 { 502 // Reparse point, don't recurse, just remove. (dwReserved0 is documented for this flag) 503 if (findData.dwReserved0 == Interop.Kernel32.IOReparseOptions.IO_REPARSE_TAG_MOUNT_POINT) 504 { 505 // Mount point. Unmount using full path plus a trailing '\'. 506 // (Note: This doesn't remove the underlying directory) 507 string mountPoint = Path.Combine(fullPath, fileName + PathHelpers.DirectorySeparatorCharAsString); 508 if (!Interop.Kernel32.DeleteVolumeMountPoint(mountPoint) && exception == null) 509 { 510 errorCode = Marshal.GetLastWin32Error(); 511 if (errorCode != Interop.Errors.ERROR_SUCCESS && 512 errorCode != Interop.Errors.ERROR_PATH_NOT_FOUND) 513 { 514 exception = Win32Marshal.GetExceptionForWin32Error(errorCode, fileName); 515 } 516 } 517 } 518 519 // Note that RemoveDirectory on a symbolic link will remove the link itself. 520 if (!Interop.Kernel32.RemoveDirectory(Path.Combine(fullPath, fileName)) && exception == null) 521 { 522 errorCode = Marshal.GetLastWin32Error(); 523 if (errorCode != Interop.Errors.ERROR_PATH_NOT_FOUND) 524 { 525 exception = Win32Marshal.GetExceptionForWin32Error(errorCode, fileName); 526 } 527 } 528 } 529 } 530 } while (Interop.Kernel32.FindNextFile(handle, ref findData)); 531 532 if (exception != null) 533 throw exception; 534 535 errorCode = Marshal.GetLastWin32Error(); 536 if (errorCode != Interop.Errors.ERROR_SUCCESS && errorCode != Interop.Errors.ERROR_NO_MORE_FILES) 537 throw Win32Marshal.GetExceptionForWin32Error(errorCode, fullPath); 538 } 539 540 // As we successfully removed all of the files we shouldn't care about the directory itself 541 // not being empty. As file deletion is just a marker to remove the file when all handles 542 // are closed we could still have contents hanging around. 543 RemoveDirectoryInternal(fullPath, topLevel: topLevel, allowDirectoryNotEmpty: true); 544 } 545 RemoveDirectoryInternal(string fullPath, bool topLevel, bool allowDirectoryNotEmpty = false)546 private static void RemoveDirectoryInternal(string fullPath, bool topLevel, bool allowDirectoryNotEmpty = false) 547 { 548 if (!Interop.Kernel32.RemoveDirectory(fullPath)) 549 { 550 int errorCode = Marshal.GetLastWin32Error(); 551 switch (errorCode) 552 { 553 case Interop.Errors.ERROR_FILE_NOT_FOUND: 554 // File not found doesn't make much sense coming from a directory delete. 555 errorCode = Interop.Errors.ERROR_PATH_NOT_FOUND; 556 goto case Interop.Errors.ERROR_PATH_NOT_FOUND; 557 case Interop.Errors.ERROR_PATH_NOT_FOUND: 558 // We only throw for the top level directory not found, not for any contents. 559 if (!topLevel) 560 return; 561 break; 562 case Interop.Errors.ERROR_DIR_NOT_EMPTY: 563 if (allowDirectoryNotEmpty) 564 return; 565 break; 566 case Interop.Errors.ERROR_ACCESS_DENIED: 567 // This conversion was originally put in for Win9x. Keeping for compatibility. 568 throw new IOException(SR.Format(SR.UnauthorizedAccess_IODenied_Path, fullPath)); 569 } 570 571 throw Win32Marshal.GetExceptionForWin32Error(errorCode, fullPath); 572 } 573 } 574 SetAttributes(string fullPath, FileAttributes attributes)575 public static void SetAttributes(string fullPath, FileAttributes attributes) 576 { 577 if (!Interop.Kernel32.SetFileAttributes(fullPath, (int)attributes)) 578 { 579 int errorCode = Marshal.GetLastWin32Error(); 580 if (errorCode == Interop.Errors.ERROR_INVALID_PARAMETER) 581 throw new ArgumentException(SR.Arg_InvalidFileAttrs, nameof(attributes)); 582 throw Win32Marshal.GetExceptionForWin32Error(errorCode, fullPath); 583 } 584 } 585 SetCreationTime(string fullPath, DateTimeOffset time, bool asDirectory)586 public static void SetCreationTime(string fullPath, DateTimeOffset time, bool asDirectory) 587 { 588 using (SafeFileHandle handle = OpenHandle(fullPath, asDirectory)) 589 { 590 if (!Interop.Kernel32.SetFileTime(handle, creationTime: time.ToFileTime())) 591 { 592 throw Win32Marshal.GetExceptionForLastWin32Error(fullPath); 593 } 594 } 595 } 596 SetCurrentDirectory(string fullPath)597 public static void SetCurrentDirectory(string fullPath) 598 { 599 if (!Interop.Kernel32.SetCurrentDirectory(fullPath)) 600 { 601 // If path doesn't exist, this sets last error to 2 (File 602 // not Found). LEGACY: This may potentially have worked correctly 603 // on Win9x, maybe. 604 int errorCode = Marshal.GetLastWin32Error(); 605 if (errorCode == Interop.Errors.ERROR_FILE_NOT_FOUND) 606 errorCode = Interop.Errors.ERROR_PATH_NOT_FOUND; 607 throw Win32Marshal.GetExceptionForWin32Error(errorCode, fullPath); 608 } 609 } 610 SetLastAccessTime(string fullPath, DateTimeOffset time, bool asDirectory)611 public static void SetLastAccessTime(string fullPath, DateTimeOffset time, bool asDirectory) 612 { 613 using (SafeFileHandle handle = OpenHandle(fullPath, asDirectory)) 614 { 615 if (!Interop.Kernel32.SetFileTime(handle, lastAccessTime: time.ToFileTime())) 616 { 617 throw Win32Marshal.GetExceptionForLastWin32Error(fullPath); 618 } 619 } 620 } 621 SetLastWriteTime(string fullPath, DateTimeOffset time, bool asDirectory)622 public static void SetLastWriteTime(string fullPath, DateTimeOffset time, bool asDirectory) 623 { 624 using (SafeFileHandle handle = OpenHandle(fullPath, asDirectory)) 625 { 626 if (!Interop.Kernel32.SetFileTime(handle, lastWriteTime: time.ToFileTime())) 627 { 628 throw Win32Marshal.GetExceptionForLastWin32Error(fullPath); 629 } 630 } 631 } 632 GetLogicalDrives()633 public static string[] GetLogicalDrives() 634 { 635 return DriveInfoInternal.GetLogicalDrives(); 636 } 637 } 638 } 639