b88e5d4b79d0088661378cb7bd587428ce01f6f1
[mono.git] / mcs / class / referencesource / mscorlib / system / io / longpath.cs
1 // ==++==
2 // 
3 //   Copyright (c) Microsoft Corporation.  All rights reserved.
4 // 
5 // ==--==g
6 /*============================================================
7 **
8 ** Class:  File
9 ** 
10 ** <OWNER>Microsoft</OWNER>
11 **
12 **
13 ** Purpose: Long paths
14 **
15 ===========================================================*/
16
17 using System;
18 using System.Security.Permissions;
19 using PermissionSet = System.Security.PermissionSet;
20 using Win32Native = Microsoft.Win32.Win32Native;
21 using System.Runtime.InteropServices;
22 using System.Security;
23 #if FEATURE_MACL
24 using System.Security.AccessControl;
25 #endif
26 using System.Text;
27 using Microsoft.Win32.SafeHandles;
28 using System.Collections.Generic;
29 using System.Globalization;
30 using System.Runtime.Versioning;
31 using System.Diagnostics.Contracts;
32     
33 namespace System.IO {
34  
35     [ComVisible(false)] 
36     static class LongPath
37     {
38         [System.Security.SecurityCritical]
39         [ResourceExposure(ResourceScope.Machine)]
40         [ResourceConsumption(ResourceScope.Machine)]
41         internal unsafe static String NormalizePath(String path)
42         {
43             Contract.Requires(path != null);
44             return NormalizePath(path, true);
45         }
46
47         [System.Security.SecurityCritical]
48         [ResourceExposure(ResourceScope.Machine)]
49         [ResourceConsumption(ResourceScope.Machine)]
50         internal unsafe static String NormalizePath(String path, bool fullCheck)
51         {
52             Contract.Requires(path != null);
53             return Path.NormalizePath(path, fullCheck, Path.MaxLongPath);
54         }
55
56         internal static String InternalCombine(String path1, String path2)
57         {
58             Contract.Requires(path1 != null);
59             Contract.Requires(path2 != null);
60             Contract.Requires(path2.Length != 0);
61             Contract.Requires(!IsPathRooted(path2));
62
63             bool removedPrefix;
64             String tempPath1 = TryRemoveLongPathPrefix(path1, out removedPrefix);
65
66             String tempResult = Path.InternalCombine(tempPath1, path2);
67
68             if (removedPrefix)
69             {
70                 tempResult = Path.AddLongPathPrefix(tempResult);
71             }
72             return tempResult;
73         }
74
75         internal static int GetRootLength(String path)
76         {
77             bool removedPrefix;
78             String tempPath = TryRemoveLongPathPrefix(path, out removedPrefix);
79
80             int root = Path.GetRootLength(tempPath);
81             if (removedPrefix)
82             {
83                 root += 4;
84             }
85             return root;
86         }
87
88         // Tests if the given path contains a root. A path is considered rooted
89         // if it starts with a backslash ("\") or a drive letter and a colon (":").
90         //
91         [Pure]
92         internal static bool IsPathRooted(String path)
93         {
94             Contract.Requires(path != null);
95             String tempPath = Path.RemoveLongPathPrefix(path);
96             return Path.IsPathRooted(tempPath);
97         }
98
99         // Returns the root portion of the given path. The resulting string
100         // consists of those rightmost characters of the path that constitute the
101         // root of the path. Possible patterns for the resulting string are: An
102         // empty string (a relative path on the current drive), "\" (an absolute
103         // path on the current drive), "X:" (a relative path on a given drive,
104         // where X is the drive letter), "X:\" (an absolute path on a given drive),
105         // and "\\server\share" (a UNC path for a given server and share name).
106         // The resulting string is null if path is null.
107         //
108         [System.Security.SecurityCritical]
109         [ResourceExposure(ResourceScope.Machine)]
110         [ResourceConsumption(ResourceScope.Machine)]
111         internal static String GetPathRoot(String path)
112         {
113             if (path == null) return null;
114
115             bool removedPrefix;
116             String tempPath = TryRemoveLongPathPrefix(path, out removedPrefix);
117
118             tempPath = NormalizePath(tempPath, false);
119             String result = path.Substring(0, GetRootLength(tempPath));
120
121             if (removedPrefix)
122             {
123                 result = Path.AddLongPathPrefix(result);
124             }
125             return result;
126         }
127
128         // Returns the directory path of a file path. This method effectively
129         // removes the last element of the given file path, i.e. it returns a
130         // string consisting of all characters up to but not including the last
131         // backslash ("\") in the file path. The returned value is null if the file
132         // path is null or if the file path denotes a root (such as "\", "C:", or
133         // "\\server\share").
134         [System.Security.SecurityCritical]  // auto-generated
135         [ResourceExposure(ResourceScope.None)]
136         [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
137         internal static String GetDirectoryName(String path)
138         {
139             if (path != null)
140             {
141                 bool removedPrefix;
142                 String tempPath = TryRemoveLongPathPrefix(path, out removedPrefix);
143
144                 Path.CheckInvalidPathChars(tempPath);
145                 path = NormalizePath(tempPath, false);
146                 int root = GetRootLength(tempPath);
147                 int i = tempPath.Length;
148                 if (i > root)
149                 {
150                     i = tempPath.Length;
151                     if (i == root) return null;
152                     while (i > root && tempPath[--i] != Path.DirectorySeparatorChar && tempPath[i] != Path.AltDirectorySeparatorChar);
153                     String result = tempPath.Substring(0, i);
154                     if (removedPrefix)
155                     {
156                         result = Path.AddLongPathPrefix(result);
157                     }
158
159                     return result;
160                 }
161             }
162             return null;
163         }
164
165         internal static String TryRemoveLongPathPrefix(String path, out bool removed)
166         {
167             Contract.Requires(path != null);
168             removed = Path.HasLongPathPrefix(path);
169             if (!removed)
170                 return path;
171             return Path.RemoveLongPathPrefix(path);
172         }
173     }
174
175     [ComVisible(false)] 
176     static class LongPathFile
177     {
178
179         // Copies an existing file to a new file. If overwrite is 
180         // false, then an IOException is thrown if the destination file 
181         // already exists.  If overwrite is true, the file is 
182         // overwritten.
183         //
184         // The caller must have certain FileIOPermissions.  The caller must have
185         // Read permission to sourceFileName 
186         // and Write permissions to destFileName.
187         // 
188         [System.Security.SecurityCritical]
189         [ResourceExposure(ResourceScope.Machine)]
190         [ResourceConsumption(ResourceScope.Machine)]
191         internal static void Copy(String sourceFileName, String destFileName, bool overwrite) {
192             Contract.Requires(sourceFileName != null);
193             Contract.Requires(destFileName != null);
194             Contract.Requires(sourceFileName.Length > 0);
195             Contract.Requires(destFileName.Length > 0);
196
197             String fullSourceFileName = LongPath.NormalizePath(sourceFileName);
198             new FileIOPermission(FileIOPermissionAccess.Read, new String[] { fullSourceFileName }, false, false).Demand();
199             String fullDestFileName = LongPath.NormalizePath(destFileName);
200             new FileIOPermission(FileIOPermissionAccess.Write, new String[] { fullDestFileName }, false, false).Demand();
201
202             InternalCopy(fullSourceFileName, fullDestFileName, sourceFileName, destFileName, overwrite);
203         }
204
205         [System.Security.SecurityCritical]
206         [ResourceExposure(ResourceScope.Machine)]
207         [ResourceConsumption(ResourceScope.Machine)]
208         private static String InternalCopy(String fullSourceFileName, String fullDestFileName, String sourceFileName, String destFileName, bool overwrite) {
209             Contract.Requires(fullSourceFileName != null);
210             Contract.Requires(fullDestFileName != null);
211             Contract.Requires(fullSourceFileName.Length > 0);
212             Contract.Requires(fullDestFileName.Length > 0);
213
214             fullSourceFileName = Path.AddLongPathPrefix(fullSourceFileName);
215             fullDestFileName = Path.AddLongPathPrefix(fullDestFileName);
216             bool r = Win32Native.CopyFile(fullSourceFileName, fullDestFileName, !overwrite);
217             if (!r) {
218                 // Save Win32 error because subsequent checks will overwrite this HRESULT.
219                 int errorCode = Marshal.GetLastWin32Error();
220                 String fileName = destFileName;
221
222                 if (errorCode != Win32Native.ERROR_FILE_EXISTS) {
223                     // For a number of error codes (sharing violation, path 
224                     // not found, etc) we don't know if the problem was with
225                     // the source or dest file.  Try reading the source file.
226                     using(SafeFileHandle handle = Win32Native.UnsafeCreateFile(fullSourceFileName, FileStream.GENERIC_READ, FileShare.Read, null, FileMode.Open, 0, IntPtr.Zero)) {
227                         if (handle.IsInvalid)
228                             fileName = sourceFileName;
229                     }
230
231                     if (errorCode == Win32Native.ERROR_ACCESS_DENIED) {
232                         if (LongPathDirectory.InternalExists(fullDestFileName))
233                             throw new IOException(Environment.GetResourceString("Arg_FileIsDirectory_Name", destFileName), Win32Native.ERROR_ACCESS_DENIED, fullDestFileName);
234                     }
235                 }
236
237                 __Error.WinIOError(errorCode, fileName);
238             }
239                 
240             return fullDestFileName;
241         }
242
243         // Deletes a file. The file specified by the designated path is deleted.
244         // If the file does not exist, Delete succeeds without throwing
245         // an exception.
246         // 
247         // On NT, Delete will fail for a file that is open for normal I/O
248         // or a file that is memory mapped.  
249         // 
250         // Your application must have Delete permission to the target file.
251         // 
252         [System.Security.SecurityCritical] 
253         [ResourceExposure(ResourceScope.Machine)]
254         [ResourceConsumption(ResourceScope.Machine)]
255         internal static void Delete(String path) {
256             Contract.Requires(path != null);
257
258             String fullPath = LongPath.NormalizePath(path);
259
260             // For security check, path should be resolved to an absolute path.
261             new FileIOPermission(FileIOPermissionAccess.Write, new String[] { fullPath }, false, false ).Demand();
262
263             String tempPath = Path.AddLongPathPrefix(fullPath);
264             bool r = Win32Native.DeleteFile(tempPath);
265             if (!r) {
266                 int hr = Marshal.GetLastWin32Error();
267                 if (hr==Win32Native.ERROR_FILE_NOT_FOUND)
268                     return;
269                 else
270                     __Error.WinIOError(hr, fullPath);
271             }
272         }
273         
274         // Tests if a file exists. The result is true if the file
275         // given by the specified path exists; otherwise, the result is
276         // false.  Note that if path describes a directory,
277         // Exists will return true.
278         //
279         // Your application must have Read permission for the target directory.
280         // 
281         [System.Security.SecurityCritical] 
282         [ResourceExposure(ResourceScope.Machine)]
283         [ResourceConsumption(ResourceScope.Machine)]
284         internal static bool Exists(String path) {
285             try
286             {
287                 if (path==null)
288                     return false;
289                 if (path.Length==0)
290                     return false;
291             
292                 path = LongPath.NormalizePath(path);
293                 // After normalizing, check whether path ends in directory separator.
294                 // Otherwise, FillAttributeInfo removes it and we may return a false positive.
295                 // GetFullPathInternal should never return null
296                 Contract.Assert(path != null, "File.Exists: GetFullPathInternal returned null");
297                 if (path.Length > 0 && Path.IsDirectorySeparator(path[path.Length - 1])) {
298                     return false;
299                 }
300                     
301                 new FileIOPermission(FileIOPermissionAccess.Read, new String[] { path }, false, false ).Demand();
302
303                 return InternalExists(path);
304             }
305             catch(ArgumentException) {} 
306             catch(NotSupportedException) {} // Security can throw this on ":"
307             catch(SecurityException) {}
308             catch(IOException) {}
309             catch(UnauthorizedAccessException) {}
310
311             return false;
312         }
313
314         [System.Security.SecurityCritical]
315         internal static bool InternalExists(String path) {
316             Contract.Requires(path != null);
317             String tempPath = Path.AddLongPathPrefix(path);
318             return File.InternalExists(tempPath);
319         }
320
321         [System.Security.SecurityCritical]
322         [ResourceExposure(ResourceScope.Machine)]
323         [ResourceConsumption(ResourceScope.Machine)]
324         internal static DateTimeOffset GetCreationTime(String path)
325         {
326             Contract.Requires(path != null);
327
328             String fullPath = LongPath.NormalizePath(path);
329             new FileIOPermission(FileIOPermissionAccess.Read, new String[] { fullPath }, false, false ).Demand();
330
331             String tempPath = Path.AddLongPathPrefix(fullPath);
332
333             Win32Native.WIN32_FILE_ATTRIBUTE_DATA data = new Win32Native.WIN32_FILE_ATTRIBUTE_DATA();
334             int dataInitialised = File.FillAttributeInfo(tempPath, ref data, false, false);
335             if (dataInitialised != 0)
336                 __Error.WinIOError(dataInitialised, fullPath);
337
338             long dt = ((long)(data.ftCreationTimeHigh) << 32) | ((long)data.ftCreationTimeLow);
339             DateTime dtLocal = DateTime.FromFileTimeUtc(dt).ToLocalTime();
340             return new DateTimeOffset(dtLocal).ToLocalTime();
341         }
342
343         [System.Security.SecurityCritical]
344         [ResourceExposure(ResourceScope.Machine)]
345         [ResourceConsumption(ResourceScope.Machine)]
346         internal static DateTimeOffset GetLastAccessTime(String path)
347         {
348             Contract.Requires(path != null);
349
350             String fullPath = LongPath.NormalizePath(path);
351             new FileIOPermission(FileIOPermissionAccess.Read, new String[] { fullPath }, false, false ).Demand();
352
353             String tempPath = Path.AddLongPathPrefix(fullPath);
354             Win32Native.WIN32_FILE_ATTRIBUTE_DATA data = new Win32Native.WIN32_FILE_ATTRIBUTE_DATA();
355             int dataInitialised = File.FillAttributeInfo(tempPath, ref data, false, false);
356             if (dataInitialised != 0)
357                 __Error.WinIOError(dataInitialised, fullPath);
358
359             long dt = ((long)(data.ftLastAccessTimeHigh) << 32) | ((long)data.ftLastAccessTimeLow);
360             DateTime dtLocal = DateTime.FromFileTimeUtc(dt).ToLocalTime();
361             return new DateTimeOffset(dtLocal).ToLocalTime();
362         }
363
364         [System.Security.SecurityCritical] 
365         [ResourceExposure(ResourceScope.Machine)]
366         [ResourceConsumption(ResourceScope.Machine)]
367         internal static DateTimeOffset GetLastWriteTime(String path)
368         {
369             Contract.Requires(path != null);
370
371             String fullPath = LongPath.NormalizePath(path);
372             new FileIOPermission(FileIOPermissionAccess.Read, new String[] { fullPath }, false, false ).Demand();
373
374             String tempPath = Path.AddLongPathPrefix(fullPath);
375             Win32Native.WIN32_FILE_ATTRIBUTE_DATA data = new Win32Native.WIN32_FILE_ATTRIBUTE_DATA();
376             int dataInitialised = File.FillAttributeInfo(tempPath, ref data, false, false);
377             if (dataInitialised != 0)
378                 __Error.WinIOError(dataInitialised, fullPath);
379
380             long dt = ((long)data.ftLastWriteTimeHigh << 32) | ((long)data.ftLastWriteTimeLow);
381             DateTime dtLocal = DateTime.FromFileTimeUtc(dt).ToLocalTime();
382             return new DateTimeOffset(dtLocal).ToLocalTime();
383         }
384
385         // Moves a specified file to a new location and potentially a new file name.
386         // This method does work across volumes.
387         //
388         // The caller must have certain FileIOPermissions.  The caller must
389         // have Read and Write permission to 
390         // sourceFileName and Write 
391         // permissions to destFileName.
392         // 
393         [System.Security.SecurityCritical]
394         [ResourceExposure(ResourceScope.Machine)]
395         [ResourceConsumption(ResourceScope.Machine)]
396         internal static void Move(String sourceFileName, String destFileName) {
397             Contract.Requires(sourceFileName != null);
398             Contract.Requires(destFileName != null);
399             Contract.Requires(sourceFileName.Length > 0);
400             Contract.Requires(destFileName.Length > 0);
401
402             String fullSourceFileName = LongPath.NormalizePath(sourceFileName);
403             new FileIOPermission(FileIOPermissionAccess.Write | FileIOPermissionAccess.Read, new String[] { fullSourceFileName }, false, false).Demand();
404             String fullDestFileName = LongPath.NormalizePath(destFileName);
405             new FileIOPermission(FileIOPermissionAccess.Write, new String[] { fullDestFileName }, false, false).Demand();
406
407             if (!LongPathFile.InternalExists(fullSourceFileName))
408                 __Error.WinIOError(Win32Native.ERROR_FILE_NOT_FOUND, fullSourceFileName);
409
410             String tempSourceFileName = Path.AddLongPathPrefix(fullSourceFileName);
411             String tempDestFileName = Path.AddLongPathPrefix(fullDestFileName);
412
413             if (!Win32Native.MoveFile(tempSourceFileName, tempDestFileName))
414             {
415                 __Error.WinIOError();
416             }
417         }
418
419         // throws FileNotFoundException if not found
420         [System.Security.SecurityCritical]
421         internal static long GetLength(String path)
422         {
423             Contract.Requires(path != null);
424
425             String fullPath = LongPath.NormalizePath(path);
426             new FileIOPermission(FileIOPermissionAccess.Read, new String[] { fullPath }, false, false ).Demand();
427
428             String tempPath = Path.AddLongPathPrefix(fullPath);
429             Win32Native.WIN32_FILE_ATTRIBUTE_DATA data = new Win32Native.WIN32_FILE_ATTRIBUTE_DATA();
430             int dataInitialised = File.FillAttributeInfo(tempPath, ref data, false, true); // return error
431             if (dataInitialised != 0)
432                 __Error.WinIOError(dataInitialised, path); // from FileInfo.
433
434             if ((data.fileAttributes & Win32Native.FILE_ATTRIBUTE_DIRECTORY) != 0)
435                 __Error.WinIOError(Win32Native.ERROR_FILE_NOT_FOUND, path);
436
437             return ((long)data.fileSizeHigh) << 32 | ((long)data.fileSizeLow & 0xFFFFFFFFL);
438             
439         }
440
441          // Defined in WinError.h
442         private const int ERROR_ACCESS_DENIED = 0x5;     
443     }
444
445     [ComVisible(false)] 
446     static class LongPathDirectory
447     {
448         [System.Security.SecurityCritical]  // auto-generated
449         [ResourceExposure(ResourceScope.Machine)]
450         [ResourceConsumption(ResourceScope.Machine)]
451         internal static void CreateDirectory(String path)
452         {
453             Contract.Requires(path != null);
454             Contract.Requires(path.Length > 0);
455
456             String fullPath = LongPath.NormalizePath(path);
457
458             // You need read access to the directory to be returned back and write access to all the directories 
459             // that you need to create. If we fail any security checks we will not create any directories at all.
460             // We attempt to create directories only after all the security checks have passed. This is avoid doing
461             // a demand at every level.
462             String demandDir = GetDemandDir(fullPath, true);
463             new FileIOPermission(FileIOPermissionAccess.Read, new String[] { demandDir }, false, false).Demand();
464
465             InternalCreateDirectory(fullPath, path, null);
466         }
467
468         [System.Security.SecurityCritical]  // auto-generated
469         [ResourceExposure(ResourceScope.Machine)]
470         [ResourceConsumption(ResourceScope.Machine)]
471         private unsafe static void InternalCreateDirectory(String fullPath, String path, Object dirSecurityObj)
472         {
473 #if FEATURE_MACL
474             DirectorySecurity dirSecurity = (DirectorySecurity)dirSecurityObj;
475 #endif // FEATURE_MACL
476
477             int length = fullPath.Length;
478
479             // We need to trim the trailing slash or the code will try to create 2 directories of the same name.
480             if (length >= 2 && Path.IsDirectorySeparator(fullPath[length - 1]))
481                 length--;
482
483             int lengthRoot = LongPath.GetRootLength(fullPath); 
484
485             // For UNC paths that are only // or /// 
486             if (length == 2 && Path.IsDirectorySeparator(fullPath[1]))
487                 throw new IOException(Environment.GetResourceString("IO.IO_CannotCreateDirectory", path));
488
489             List<string> stackDir = new List<string>();
490
491             // Attempt to figure out which directories don't exist, and only
492             // create the ones we need.  Note that InternalExists may fail due
493             // to Win32 ACL's preventing us from seeing a directory, and this
494             // isn't threadsafe.
495
496             bool somepathexists = false;
497
498             if (length > lengthRoot)
499             { // Special case root (fullpath = X:\\)
500                 int i = length - 1;
501                 while (i >= lengthRoot && !somepathexists)
502                 {
503                     String dir = fullPath.Substring(0, i + 1);
504
505                     if (!InternalExists(dir)) // Create only the ones missing
506                         stackDir.Add(dir);
507                     else
508                         somepathexists = true;
509
510                     while (i > lengthRoot && fullPath[i] != Path.DirectorySeparatorChar && fullPath[i] != Path.AltDirectorySeparatorChar) i--;
511                     i--;
512                 }
513             }
514
515             int count = stackDir.Count;
516
517             if (stackDir.Count != 0)
518             {
519                 String[] securityList = new String[stackDir.Count];
520                 stackDir.CopyTo(securityList, 0);
521                 for (int j = 0; j < securityList.Length; j++)
522                     securityList[j] += "\\."; // leaf will never have a slash at the end
523
524                 // Security check for all directories not present only.
525 #if !FEATURE_PAL  && FEATURE_MACL
526                 AccessControlActions control = (dirSecurity == null) ? AccessControlActions.None : AccessControlActions.Change;
527                 new FileIOPermission(FileIOPermissionAccess.Write, control, securityList, false, false ).Demand();
528 #else
529                 new FileIOPermission(FileIOPermissionAccess.Write, securityList, false, false).Demand();
530 #endif
531             }
532
533             // If we were passed a DirectorySecurity, convert it to a security
534             // descriptor and set it in he call to CreateDirectory.
535             Win32Native.SECURITY_ATTRIBUTES secAttrs = null;
536 #if FEATURE_MACL
537             if (dirSecurity != null) {
538                 secAttrs = new Win32Native.SECURITY_ATTRIBUTES();
539                 secAttrs.nLength = (int)Marshal.SizeOf(secAttrs);
540
541                 // For ACL's, get the security descriptor from the FileSecurity.
542                 byte[] sd = dirSecurity.GetSecurityDescriptorBinaryForm();
543                 byte * bytesOnStack = stackalloc byte[sd.Length];
544                 Buffer.Memcpy(bytesOnStack, 0, sd, 0, sd.Length);
545                 secAttrs.pSecurityDescriptor = bytesOnStack;
546             }
547 #endif
548
549             bool r = true;
550             int firstError = 0;
551             String errorString = path;
552             // If all the security checks succeeded create all the directories
553             while (stackDir.Count > 0)
554             {
555                 String name = stackDir[stackDir.Count - 1];
556                 stackDir.RemoveAt(stackDir.Count - 1);
557                 if (name.Length >= Path.MaxLongPath)
558                     throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong"));
559                 r = Win32Native.CreateDirectory(Path.AddLongPathPrefix(name), secAttrs);
560                 if (!r && (firstError == 0))
561                 {
562                     int currentError = Marshal.GetLastWin32Error();
563                     // While we tried to avoid creating directories that don't
564                     // exist above, there are at least two cases that will 
565                     // cause us to see ERROR_ALREADY_EXISTS here.  InternalExists 
566                     // can fail because we didn't have permission to the 
567                     // directory.  Secondly, another thread or process could
568                     // create the directory between the time we check and the
569                     // time we try using the directory.  Thirdly, it could
570                     // fail because the target does exist, but is a file.
571                     if (currentError != Win32Native.ERROR_ALREADY_EXISTS)
572                         firstError = currentError;
573                     else
574                     {
575                         // 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.
576                         if (LongPathFile.InternalExists(name) || (!InternalExists(name, out currentError) && currentError == Win32Native.ERROR_ACCESS_DENIED))
577                         {
578                             firstError = currentError;
579                             // Give the user a nice error message, but don't leak path information.
580                             try
581                             {
582                                 new FileIOPermission(FileIOPermissionAccess.PathDiscovery, new String[] { GetDemandDir(name, true) }, false, false).Demand();
583                                 errorString = name;
584                             }
585                             catch (SecurityException) { }
586                         }
587                     }
588                 }
589             }
590
591             // We need this check to mask OS differences
592             // Handle CreateDirectory("X:\\foo") when X: doesn't exist. Similarly for n/w paths.
593             if ((count == 0) && !somepathexists)
594             {
595                 String root = InternalGetDirectoryRoot(fullPath);
596                 if (!InternalExists(root))
597                 {
598                     // Extract the root from the passed in path again for security.
599                     __Error.WinIOError(Win32Native.ERROR_PATH_NOT_FOUND, InternalGetDirectoryRoot(path));
600                 }
601                 return;
602             }
603
604             // Only throw an exception if creating the exact directory we 
605             // wanted failed to work correctly.
606             if (!r && (firstError != 0))
607             {
608                 __Error.WinIOError(firstError, errorString);
609             }
610         }
611       
612         [System.Security.SecurityCritical] 
613         [ResourceExposure(ResourceScope.Machine)]
614         [ResourceConsumption(ResourceScope.Machine)]
615         internal static void Move(String sourceDirName, String destDirName)
616         {
617             Contract.Requires(sourceDirName != null);
618             Contract.Requires(destDirName != null);
619             Contract.Requires(sourceDirName.Length != 0);
620             Contract.Requires(destDirName.Length != 0);
621
622             String fullsourceDirName = LongPath.NormalizePath(sourceDirName);
623             String sourcePath = GetDemandDir(fullsourceDirName, false);
624
625             if (sourcePath.Length >= Path.MaxLongPath)
626                 throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong"));
627
628             String fulldestDirName = LongPath.NormalizePath(destDirName);
629             String destPath = GetDemandDir(fulldestDirName, false);
630
631             if (destPath.Length >= Path.MaxLongPath)
632                 throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong"));
633
634             new FileIOPermission(FileIOPermissionAccess.Write | FileIOPermissionAccess.Read, new String[] { sourcePath }, false, false).Demand();
635             new FileIOPermission(FileIOPermissionAccess.Write, new String[] { destPath }, false, false).Demand();
636
637             if (String.Compare(sourcePath, destPath, StringComparison.OrdinalIgnoreCase) == 0)
638                 throw new IOException(Environment.GetResourceString("IO.IO_SourceDestMustBeDifferent"));
639
640             String sourceRoot = LongPath.GetPathRoot(sourcePath);
641             String destinationRoot = LongPath.GetPathRoot(destPath);
642             if (String.Compare(sourceRoot, destinationRoot, StringComparison.OrdinalIgnoreCase) != 0)
643                 throw new IOException(Environment.GetResourceString("IO.IO_SourceDestMustHaveSameRoot"));
644
645
646             String tempSourceDirName = Path.AddLongPathPrefix(sourceDirName);
647             String tempDestDirName = Path.AddLongPathPrefix(destDirName);
648
649             if (!Win32Native.MoveFile(tempSourceDirName, tempDestDirName))
650             {
651                 int hr = Marshal.GetLastWin32Error();
652                 if (hr == Win32Native.ERROR_FILE_NOT_FOUND) // Source dir not found
653                 {
654                     hr = Win32Native.ERROR_PATH_NOT_FOUND;
655                     __Error.WinIOError(hr, fullsourceDirName);
656                 }
657                 // 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.
658                 if (hr == Win32Native.ERROR_ACCESS_DENIED) // WinNT throws IOException. This check is for Win9x. We can't change it for backcomp.
659                     throw new IOException(Environment.GetResourceString("UnauthorizedAccess_IODenied_Path", sourceDirName), Win32Native.MakeHRFromErrorCode(hr));
660                 __Error.WinIOError(hr, String.Empty);
661             }
662         }
663
664         [System.Security.SecurityCritical]
665         [ResourceExposure(ResourceScope.Machine)]
666         [ResourceConsumption(ResourceScope.Machine)]
667         internal static void Delete(String path, bool recursive)
668         {
669             String fullPath = LongPath.NormalizePath(path);
670            InternalDelete(fullPath, path, recursive);
671         }
672
673         // FullPath is fully qualified, while the user path is used for feedback in exceptions
674         [System.Security.SecurityCritical]
675         [ResourceExposure(ResourceScope.Machine)]
676         [ResourceConsumption(ResourceScope.Machine)]
677         private static void InternalDelete(String fullPath, String userPath, bool recursive)
678         {
679             String demandPath;
680
681             // If not recursive, do permission check only on this directory
682             // else check for the whole directory structure rooted below 
683             demandPath = GetDemandDir(fullPath, !recursive);
684
685             // Make sure we have write permission to this directory
686             new FileIOPermission(FileIOPermissionAccess.Write, new String[] { demandPath }, false, false).Demand();
687
688             String longPath = Path.AddLongPathPrefix(fullPath);
689             // Do not recursively delete through reparse points.  Perhaps in a 
690             // future version we will add a new flag to control this behavior, 
691             // but for now we're much safer if we err on the conservative side.
692             // This applies to symbolic links and mount points.
693             Win32Native.WIN32_FILE_ATTRIBUTE_DATA data = new Win32Native.WIN32_FILE_ATTRIBUTE_DATA();
694             int dataInitialised = File.FillAttributeInfo(longPath, ref data, false, true);
695             if (dataInitialised != 0)
696             {
697                 // Ensure we throw a DirectoryNotFoundException.
698                 if (dataInitialised == Win32Native.ERROR_FILE_NOT_FOUND)
699                     dataInitialised = Win32Native.ERROR_PATH_NOT_FOUND;
700                 __Error.WinIOError(dataInitialised, fullPath);
701             }
702
703             if (((FileAttributes)data.fileAttributes & FileAttributes.ReparsePoint) != 0)
704                 recursive = false;
705
706             DeleteHelper(longPath, userPath, recursive, true);
707         }
708
709         // Note that fullPath is fully qualified, while userPath may be 
710         // relative.  Use userPath for all exception messages to avoid leaking
711         // fully qualified path information.
712         [System.Security.SecurityCritical]
713         [ResourceExposure(ResourceScope.Machine)]
714         [ResourceConsumption(ResourceScope.Machine)]
715         private static void DeleteHelper(String fullPath, String userPath, bool recursive, bool throwOnTopLevelDirectoryNotFound)
716         {
717             bool r;
718             int hr;
719             Exception ex = null;
720
721             // Do not recursively delete through reparse points.  Perhaps in a 
722             // future version we will add a new flag to control this behavior, 
723             // but for now we're much safer if we err on the conservative side.
724             // This applies to symbolic links and mount points.
725             // Note the logic to check whether fullPath is a reparse point is
726             // in Delete(String, String, bool), and will set "recursive" to false.
727             // Note that Win32's DeleteFile and RemoveDirectory will just delete
728             // the reparse point itself.
729
730             if (recursive)
731             {
732                 Win32Native.WIN32_FIND_DATA data = new Win32Native.WIN32_FIND_DATA();
733
734                 String searchPath = null;
735                 if (fullPath.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal))
736                 {
737                     searchPath = fullPath + "*";
738                 }
739                 else
740                 {
741                     searchPath = fullPath + Path.DirectorySeparatorChar + "*";
742                 }
743
744                 // Open a Find handle
745                 using (SafeFindHandle hnd = Win32Native.FindFirstFile(searchPath, data))
746                 {
747                     if (hnd.IsInvalid)
748                     {
749                         hr = Marshal.GetLastWin32Error();
750                         __Error.WinIOError(hr, userPath);
751                     }
752
753                     do
754                     {
755                         bool isDir = (0 != (data.dwFileAttributes & Win32Native.FILE_ATTRIBUTE_DIRECTORY));
756                         if (isDir)
757                         {
758                             // Skip ".", "..".
759                             if (data.cFileName.Equals(".") || data.cFileName.Equals(".."))
760                                 continue;
761
762                             // Recurse for all directories, unless they are 
763                             // reparse points.  Do not follow mount points nor
764                             // symbolic links, but do delete the reparse point 
765                             // itself.
766                             bool shouldRecurse = (0 == (data.dwFileAttributes & (int)FileAttributes.ReparsePoint));
767                             if (shouldRecurse)
768                             {
769                                 String newFullPath = LongPath.InternalCombine(fullPath, data.cFileName);
770                                 String newUserPath = LongPath.InternalCombine(userPath, data.cFileName);
771                                 try
772                                 {
773                                     DeleteHelper(newFullPath, newUserPath, recursive, false);
774                                 }
775                                 catch (Exception e)
776                                 {
777                                     if (ex == null)
778                                     {
779                                         ex = e;
780                                     }
781                                 }
782                             }
783                             else
784                             {
785                                 // Check to see if this is a mount point, and
786                                 // unmount it.
787                                 if (data.dwReserved0 == Win32Native.IO_REPARSE_TAG_MOUNT_POINT)
788                                 {
789                                     // Use full path plus a trailing '\'
790                                     String mountPoint = LongPath.InternalCombine(fullPath, data.cFileName + Path.DirectorySeparatorChar);
791                                     r = Win32Native.DeleteVolumeMountPoint(mountPoint);
792                                     if (!r)
793                                     {
794                                         hr = Marshal.GetLastWin32Error();
795                                         if (hr != Win32Native.ERROR_PATH_NOT_FOUND)
796                                         {
797                                             try
798                                             {
799                                                 __Error.WinIOError(hr, data.cFileName);
800                                             }
801                                             catch (Exception e)
802                                             {
803                                                 if (ex == null)
804                                                 {
805                                                     ex = e;
806                                                 }
807                                             }
808                                         }
809                                     }
810                                 }
811
812                                 // RemoveDirectory on a symbolic link will
813                                 // remove the link itself.
814                                 String reparsePoint = LongPath.InternalCombine(fullPath, data.cFileName);
815                                 r = Win32Native.RemoveDirectory(reparsePoint);
816                                 if (!r)
817                                 {
818                                     hr = Marshal.GetLastWin32Error();
819                                     if (hr != Win32Native.ERROR_PATH_NOT_FOUND)
820                                     {
821                                         try
822                                         {
823                                             __Error.WinIOError(hr, data.cFileName);
824                                         }
825                                         catch (Exception e)
826                                         {
827                                             if (ex == null)
828                                             {
829                                                 ex = e;
830                                             }
831                                         }
832                                     }
833                                 }
834                             }
835                         }
836                         else
837                         {
838                             String fileName = LongPath.InternalCombine(fullPath, data.cFileName);
839                             r = Win32Native.DeleteFile(fileName);
840                             if (!r)
841                             {
842                                 hr = Marshal.GetLastWin32Error();
843                                 if (hr != Win32Native.ERROR_FILE_NOT_FOUND)
844                                 {
845                                     try
846                                     {
847                                         __Error.WinIOError(hr, data.cFileName);
848                                     }
849                                     catch (Exception e)
850                                     {
851                                         if (ex == null)
852                                         {
853                                             ex = e;
854                                         }
855                                     }
856                                 }
857                             }
858                         }
859                     } while (Win32Native.FindNextFile(hnd, data));
860                     // Make sure we quit with a sensible error.
861                     hr = Marshal.GetLastWin32Error();
862                 }
863
864                 if (ex != null)
865                     throw ex;
866                 if (hr != 0 && hr != Win32Native.ERROR_NO_MORE_FILES)
867                     __Error.WinIOError(hr, userPath);
868             }
869
870             r = Win32Native.RemoveDirectory(fullPath);
871
872             if (!r)
873             {
874                 hr = Marshal.GetLastWin32Error();
875                 if (hr == Win32Native.ERROR_FILE_NOT_FOUND) // A dubious error code.
876                     hr = Win32Native.ERROR_PATH_NOT_FOUND;
877                 // 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.
878                 if (hr == Win32Native.ERROR_ACCESS_DENIED)
879                     throw new IOException(Environment.GetResourceString("UnauthorizedAccess_IODenied_Path", userPath));
880
881                 // don't throw the DirectoryNotFoundException since this is a subdir and there could be a ----
882                 // between two Directory.Delete callers
883                 if (hr == Win32Native.ERROR_PATH_NOT_FOUND && !throwOnTopLevelDirectoryNotFound)
884                     return;  
885
886                 __Error.WinIOError(hr, userPath);
887             }
888         }
889
890         [System.Security.SecurityCritical] 
891         [ResourceExposure(ResourceScope.Machine)]
892         [ResourceConsumption(ResourceScope.Machine)]
893         internal static bool Exists(String path)
894         {
895             try
896             {
897                 if (path == null)
898                     return false;
899                 if (path.Length == 0)
900                     return false;
901
902                 // Get fully qualified file name ending in \* for security check
903
904                 String fullPath = LongPath.NormalizePath(path);
905                 String demandPath = GetDemandDir(fullPath, true);
906
907                 new FileIOPermission(FileIOPermissionAccess.Read, new String[] { demandPath }, false, false).Demand();
908
909                 return InternalExists(fullPath);
910             }
911             catch (ArgumentException) { }
912             catch (NotSupportedException) { }  // Security can throw this on ":"
913             catch (SecurityException) { }
914             catch (IOException) { }
915             catch (UnauthorizedAccessException)
916             {
917 #if !FEATURE_PAL
918                 Contract.Assert(false, "Ignore this assert and file a bug to the BCL team. This assert was tracking purposes only.");
919 #endif //!FEATURE_PAL
920             }
921             return false;
922         }
923
924         [System.Security.SecurityCritical]  // auto-generated
925         [ResourceExposure(ResourceScope.Machine)]
926         [ResourceConsumption(ResourceScope.Machine)]
927         internal static bool InternalExists(String path)
928         {
929             Contract.Requires(path != null);
930             int lastError = Win32Native.ERROR_SUCCESS;
931             return InternalExists(path, out lastError);
932         }
933
934         [System.Security.SecurityCritical]  // auto-generated
935         [ResourceExposure(ResourceScope.Machine)]
936         [ResourceConsumption(ResourceScope.Machine)]
937         internal static bool InternalExists(String path, out int lastError) {
938             Contract.Requires(path != null);            
939             String tempPath = Path.AddLongPathPrefix(path);
940             return Directory.InternalExists(tempPath, out lastError);
941         }
942
943         // Input to this method should already be fullpath. This method will ensure that we append 
944         // the trailing slash only when appropriate and when thisDirOnly is specified append a "." 
945         // at the end of the path to indicate that the demand is only for the fullpath and not 
946         // everything underneath it.
947         [ResourceExposure(ResourceScope.None)]
948         [ResourceConsumption(ResourceScope.None, ResourceScope.None)]
949         private static String GetDemandDir(string fullPath, bool thisDirOnly)
950         {
951             String demandPath;
952             fullPath = Path.RemoveLongPathPrefix(fullPath);
953             if (thisDirOnly)
954             {
955                 if (fullPath.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)
956                     || fullPath.EndsWith(Path.AltDirectorySeparatorChar.ToString(), StringComparison.Ordinal))
957                     demandPath = fullPath + '.';
958                 else
959                     demandPath = fullPath + Path.DirectorySeparatorChar + '.';
960             }
961             else
962             {
963                 if (!(fullPath.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)
964                     || fullPath.EndsWith(Path.AltDirectorySeparatorChar.ToString(), StringComparison.Ordinal)))
965                     demandPath = fullPath + Path.DirectorySeparatorChar;
966                 else
967                     demandPath = fullPath;
968             }
969             return demandPath;
970         }
971
972         private static String InternalGetDirectoryRoot(String path)
973         {
974             if (path == null) return null;
975             return path.Substring(0, LongPath.GetRootLength(path));
976         }
977     }
978 }