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