3 // Copyright (c) Microsoft Corporation. All rights reserved.
6 /*============================================================
10 ** <OWNER>Microsoft</OWNER>
13 ** Purpose: Long paths
15 ===========================================================*/
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;
24 using System.Security.AccessControl;
27 using Microsoft.Win32.SafeHandles;
28 using System.Collections.Generic;
29 using System.Globalization;
30 using System.Runtime.Versioning;
31 using System.Diagnostics.Contracts;
38 [System.Security.SecurityCritical]
39 [ResourceExposure(ResourceScope.Machine)]
40 [ResourceConsumption(ResourceScope.Machine)]
41 internal unsafe static String NormalizePath(String path)
43 Contract.Requires(path != null);
44 return NormalizePath(path, true);
47 [System.Security.SecurityCritical]
48 [ResourceExposure(ResourceScope.Machine)]
49 [ResourceConsumption(ResourceScope.Machine)]
50 internal unsafe static String NormalizePath(String path, bool fullCheck)
52 Contract.Requires(path != null);
53 return Path.NormalizePath(path, fullCheck, Path.MaxLongPath);
56 internal static String InternalCombine(String path1, String path2)
58 Contract.Requires(path1 != null);
59 Contract.Requires(path2 != null);
60 Contract.Requires(path2.Length != 0);
61 Contract.Requires(!IsPathRooted(path2));
64 String tempPath1 = TryRemoveLongPathPrefix(path1, out removedPrefix);
66 String tempResult = Path.InternalCombine(tempPath1, path2);
70 tempResult = Path.AddLongPathPrefix(tempResult);
75 internal static int GetRootLength(String path)
78 String tempPath = TryRemoveLongPathPrefix(path, out removedPrefix);
80 int root = Path.GetRootLength(tempPath);
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 (":").
92 internal static bool IsPathRooted(String path)
94 Contract.Requires(path != null);
95 String tempPath = Path.RemoveLongPathPrefix(path);
96 return Path.IsPathRooted(tempPath);
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.
108 [System.Security.SecurityCritical]
109 [ResourceExposure(ResourceScope.Machine)]
110 [ResourceConsumption(ResourceScope.Machine)]
111 internal static String GetPathRoot(String path)
113 if (path == null) return null;
116 String tempPath = TryRemoveLongPathPrefix(path, out removedPrefix);
118 tempPath = NormalizePath(tempPath, false);
119 String result = path.Substring(0, GetRootLength(tempPath));
123 result = Path.AddLongPathPrefix(result);
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)
142 String tempPath = TryRemoveLongPathPrefix(path, out removedPrefix);
144 Path.CheckInvalidPathChars(tempPath);
145 path = NormalizePath(tempPath, false);
146 int root = GetRootLength(tempPath);
147 int 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);
156 result = Path.AddLongPathPrefix(result);
165 internal static String TryRemoveLongPathPrefix(String path, out bool removed)
167 Contract.Requires(path != null);
168 removed = Path.HasLongPathPrefix(path);
171 return Path.RemoveLongPathPrefix(path);
176 static class LongPathFile
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
184 // The caller must have certain FileIOPermissions. The caller must have
185 // Read permission to sourceFileName
186 // and Write permissions to destFileName.
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);
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();
202 InternalCopy(fullSourceFileName, fullDestFileName, sourceFileName, destFileName, overwrite);
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);
214 fullSourceFileName = Path.AddLongPathPrefix(fullSourceFileName);
215 fullDestFileName = Path.AddLongPathPrefix(fullDestFileName);
216 bool r = Win32Native.CopyFile(fullSourceFileName, fullDestFileName, !overwrite);
218 // Save Win32 error because subsequent checks will overwrite this HRESULT.
219 int errorCode = Marshal.GetLastWin32Error();
220 String fileName = destFileName;
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;
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);
237 __Error.WinIOError(errorCode, fileName);
240 return fullDestFileName;
243 // Deletes a file. The file specified by the designated path is deleted.
244 // If the file does not exist, Delete succeeds without throwing
247 // On NT, Delete will fail for a file that is open for normal I/O
248 // or a file that is memory mapped.
250 // Your application must have Delete permission to the target file.
252 [System.Security.SecurityCritical]
253 [ResourceExposure(ResourceScope.Machine)]
254 [ResourceConsumption(ResourceScope.Machine)]
255 internal static void Delete(String path) {
256 Contract.Requires(path != null);
258 String fullPath = LongPath.NormalizePath(path);
260 // For security check, path should be resolved to an absolute path.
261 new FileIOPermission(FileIOPermissionAccess.Write, new String[] { fullPath }, false, false ).Demand();
263 String tempPath = Path.AddLongPathPrefix(fullPath);
264 bool r = Win32Native.DeleteFile(tempPath);
266 int hr = Marshal.GetLastWin32Error();
267 if (hr==Win32Native.ERROR_FILE_NOT_FOUND)
270 __Error.WinIOError(hr, fullPath);
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.
279 // Your application must have Read permission for the target directory.
281 [System.Security.SecurityCritical]
282 [ResourceExposure(ResourceScope.Machine)]
283 [ResourceConsumption(ResourceScope.Machine)]
284 internal static bool Exists(String path) {
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])) {
301 new FileIOPermission(FileIOPermissionAccess.Read, new String[] { path }, false, false ).Demand();
303 return InternalExists(path);
305 catch(ArgumentException) {}
306 catch(NotSupportedException) {} // Security can throw this on ":"
307 catch(SecurityException) {}
308 catch(IOException) {}
309 catch(UnauthorizedAccessException) {}
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);
321 [System.Security.SecurityCritical]
322 [ResourceExposure(ResourceScope.Machine)]
323 [ResourceConsumption(ResourceScope.Machine)]
324 internal static DateTimeOffset GetCreationTime(String path)
326 Contract.Requires(path != null);
328 String fullPath = LongPath.NormalizePath(path);
329 new FileIOPermission(FileIOPermissionAccess.Read, new String[] { fullPath }, false, false ).Demand();
331 String tempPath = Path.AddLongPathPrefix(fullPath);
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);
338 long dt = ((long)(data.ftCreationTimeHigh) << 32) | ((long)data.ftCreationTimeLow);
339 DateTime dtLocal = DateTime.FromFileTimeUtc(dt).ToLocalTime();
340 return new DateTimeOffset(dtLocal).ToLocalTime();
343 [System.Security.SecurityCritical]
344 [ResourceExposure(ResourceScope.Machine)]
345 [ResourceConsumption(ResourceScope.Machine)]
346 internal static DateTimeOffset GetLastAccessTime(String path)
348 Contract.Requires(path != null);
350 String fullPath = LongPath.NormalizePath(path);
351 new FileIOPermission(FileIOPermissionAccess.Read, new String[] { fullPath }, false, false ).Demand();
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);
359 long dt = ((long)(data.ftLastAccessTimeHigh) << 32) | ((long)data.ftLastAccessTimeLow);
360 DateTime dtLocal = DateTime.FromFileTimeUtc(dt).ToLocalTime();
361 return new DateTimeOffset(dtLocal).ToLocalTime();
364 [System.Security.SecurityCritical]
365 [ResourceExposure(ResourceScope.Machine)]
366 [ResourceConsumption(ResourceScope.Machine)]
367 internal static DateTimeOffset GetLastWriteTime(String path)
369 Contract.Requires(path != null);
371 String fullPath = LongPath.NormalizePath(path);
372 new FileIOPermission(FileIOPermissionAccess.Read, new String[] { fullPath }, false, false ).Demand();
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);
380 long dt = ((long)data.ftLastWriteTimeHigh << 32) | ((long)data.ftLastWriteTimeLow);
381 DateTime dtLocal = DateTime.FromFileTimeUtc(dt).ToLocalTime();
382 return new DateTimeOffset(dtLocal).ToLocalTime();
385 // Moves a specified file to a new location and potentially a new file name.
386 // This method does work across volumes.
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.
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);
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();
407 if (!LongPathFile.InternalExists(fullSourceFileName))
408 __Error.WinIOError(Win32Native.ERROR_FILE_NOT_FOUND, fullSourceFileName);
410 String tempSourceFileName = Path.AddLongPathPrefix(fullSourceFileName);
411 String tempDestFileName = Path.AddLongPathPrefix(fullDestFileName);
413 if (!Win32Native.MoveFile(tempSourceFileName, tempDestFileName))
415 __Error.WinIOError();
419 // throws FileNotFoundException if not found
420 [System.Security.SecurityCritical]
421 internal static long GetLength(String path)
423 Contract.Requires(path != null);
425 String fullPath = LongPath.NormalizePath(path);
426 new FileIOPermission(FileIOPermissionAccess.Read, new String[] { fullPath }, false, false ).Demand();
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.
434 if ((data.fileAttributes & Win32Native.FILE_ATTRIBUTE_DIRECTORY) != 0)
435 __Error.WinIOError(Win32Native.ERROR_FILE_NOT_FOUND, path);
437 return ((long)data.fileSizeHigh) << 32 | ((long)data.fileSizeLow & 0xFFFFFFFFL);
441 // Defined in WinError.h
442 private const int ERROR_ACCESS_DENIED = 0x5;
446 static class LongPathDirectory
448 [System.Security.SecurityCritical] // auto-generated
449 [ResourceExposure(ResourceScope.Machine)]
450 [ResourceConsumption(ResourceScope.Machine)]
451 internal static void CreateDirectory(String path)
453 Contract.Requires(path != null);
454 Contract.Requires(path.Length > 0);
456 String fullPath = LongPath.NormalizePath(path);
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();
465 InternalCreateDirectory(fullPath, path, null);
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)
474 DirectorySecurity dirSecurity = (DirectorySecurity)dirSecurityObj;
475 #endif // FEATURE_MACL
477 int length = fullPath.Length;
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]))
483 int lengthRoot = LongPath.GetRootLength(fullPath);
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));
489 List<string> stackDir = new List<string>();
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
496 bool somepathexists = false;
498 if (length > lengthRoot)
499 { // Special case root (fullpath = X:\\)
501 while (i >= lengthRoot && !somepathexists)
503 String dir = fullPath.Substring(0, i + 1);
505 if (!InternalExists(dir)) // Create only the ones missing
508 somepathexists = true;
510 while (i > lengthRoot && fullPath[i] != Path.DirectorySeparatorChar && fullPath[i] != Path.AltDirectorySeparatorChar) i--;
515 int count = stackDir.Count;
517 if (stackDir.Count != 0)
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
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();
529 new FileIOPermission(FileIOPermissionAccess.Write, securityList, false, false).Demand();
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;
537 if (dirSecurity != null) {
538 secAttrs = new Win32Native.SECURITY_ATTRIBUTES();
539 secAttrs.nLength = (int)Marshal.SizeOf(secAttrs);
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;
551 String errorString = path;
552 // If all the security checks succeeded create all the directories
553 while (stackDir.Count > 0)
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))
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;
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))
578 firstError = currentError;
579 // Give the user a nice error message, but don't leak path information.
582 new FileIOPermission(FileIOPermissionAccess.PathDiscovery, new String[] { GetDemandDir(name, true) }, false, false).Demand();
585 catch (SecurityException) { }
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)
595 String root = InternalGetDirectoryRoot(fullPath);
596 if (!InternalExists(root))
598 // Extract the root from the passed in path again for security.
599 __Error.WinIOError(Win32Native.ERROR_PATH_NOT_FOUND, InternalGetDirectoryRoot(path));
604 // Only throw an exception if creating the exact directory we
605 // wanted failed to work correctly.
606 if (!r && (firstError != 0))
608 __Error.WinIOError(firstError, errorString);
612 [System.Security.SecurityCritical]
613 [ResourceExposure(ResourceScope.Machine)]
614 [ResourceConsumption(ResourceScope.Machine)]
615 internal static void Move(String sourceDirName, String destDirName)
617 Contract.Requires(sourceDirName != null);
618 Contract.Requires(destDirName != null);
619 Contract.Requires(sourceDirName.Length != 0);
620 Contract.Requires(destDirName.Length != 0);
622 String fullsourceDirName = LongPath.NormalizePath(sourceDirName);
623 String sourcePath = GetDemandDir(fullsourceDirName, false);
625 if (sourcePath.Length >= Path.MaxLongPath)
626 throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong"));
628 String fulldestDirName = LongPath.NormalizePath(destDirName);
629 String destPath = GetDemandDir(fulldestDirName, false);
631 if (destPath.Length >= Path.MaxLongPath)
632 throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong"));
634 new FileIOPermission(FileIOPermissionAccess.Write | FileIOPermissionAccess.Read, new String[] { sourcePath }, false, false).Demand();
635 new FileIOPermission(FileIOPermissionAccess.Write, new String[] { destPath }, false, false).Demand();
637 if (String.Compare(sourcePath, destPath, StringComparison.OrdinalIgnoreCase) == 0)
638 throw new IOException(Environment.GetResourceString("IO.IO_SourceDestMustBeDifferent"));
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"));
646 String tempSourceDirName = Path.AddLongPathPrefix(sourceDirName);
647 String tempDestDirName = Path.AddLongPathPrefix(destDirName);
649 if (!Win32Native.MoveFile(tempSourceDirName, tempDestDirName))
651 int hr = Marshal.GetLastWin32Error();
652 if (hr == Win32Native.ERROR_FILE_NOT_FOUND) // Source dir not found
654 hr = Win32Native.ERROR_PATH_NOT_FOUND;
655 __Error.WinIOError(hr, fullsourceDirName);
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);
664 [System.Security.SecurityCritical]
665 [ResourceExposure(ResourceScope.Machine)]
666 [ResourceConsumption(ResourceScope.Machine)]
667 internal static void Delete(String path, bool recursive)
669 String fullPath = LongPath.NormalizePath(path);
670 InternalDelete(fullPath, path, recursive);
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)
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);
685 // Make sure we have write permission to this directory
686 new FileIOPermission(FileIOPermissionAccess.Write, new String[] { demandPath }, false, false).Demand();
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)
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);
703 if (((FileAttributes)data.fileAttributes & FileAttributes.ReparsePoint) != 0)
706 DeleteHelper(longPath, userPath, recursive, true);
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)
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.
732 Win32Native.WIN32_FIND_DATA data = new Win32Native.WIN32_FIND_DATA();
734 String searchPath = null;
735 if (fullPath.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal))
737 searchPath = fullPath + "*";
741 searchPath = fullPath + Path.DirectorySeparatorChar + "*";
744 // Open a Find handle
745 using (SafeFindHandle hnd = Win32Native.FindFirstFile(searchPath, data))
749 hr = Marshal.GetLastWin32Error();
750 __Error.WinIOError(hr, userPath);
755 bool isDir = (0 != (data.dwFileAttributes & Win32Native.FILE_ATTRIBUTE_DIRECTORY));
759 if (data.cFileName.Equals(".") || data.cFileName.Equals(".."))
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
766 bool shouldRecurse = (0 == (data.dwFileAttributes & (int)FileAttributes.ReparsePoint));
769 String newFullPath = LongPath.InternalCombine(fullPath, data.cFileName);
770 String newUserPath = LongPath.InternalCombine(userPath, data.cFileName);
773 DeleteHelper(newFullPath, newUserPath, recursive, false);
785 // Check to see if this is a mount point, and
787 if (data.dwReserved0 == Win32Native.IO_REPARSE_TAG_MOUNT_POINT)
789 // Use full path plus a trailing '\'
790 String mountPoint = LongPath.InternalCombine(fullPath, data.cFileName + Path.DirectorySeparatorChar);
791 r = Win32Native.DeleteVolumeMountPoint(mountPoint);
794 hr = Marshal.GetLastWin32Error();
795 if (hr != Win32Native.ERROR_PATH_NOT_FOUND)
799 __Error.WinIOError(hr, data.cFileName);
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);
818 hr = Marshal.GetLastWin32Error();
819 if (hr != Win32Native.ERROR_PATH_NOT_FOUND)
823 __Error.WinIOError(hr, data.cFileName);
838 String fileName = LongPath.InternalCombine(fullPath, data.cFileName);
839 r = Win32Native.DeleteFile(fileName);
842 hr = Marshal.GetLastWin32Error();
843 if (hr != Win32Native.ERROR_FILE_NOT_FOUND)
847 __Error.WinIOError(hr, data.cFileName);
859 } while (Win32Native.FindNextFile(hnd, data));
860 // Make sure we quit with a sensible error.
861 hr = Marshal.GetLastWin32Error();
866 if (hr != 0 && hr != Win32Native.ERROR_NO_MORE_FILES)
867 __Error.WinIOError(hr, userPath);
870 r = Win32Native.RemoveDirectory(fullPath);
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));
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)
886 __Error.WinIOError(hr, userPath);
890 [System.Security.SecurityCritical]
891 [ResourceExposure(ResourceScope.Machine)]
892 [ResourceConsumption(ResourceScope.Machine)]
893 internal static bool Exists(String path)
899 if (path.Length == 0)
902 // Get fully qualified file name ending in \* for security check
904 String fullPath = LongPath.NormalizePath(path);
905 String demandPath = GetDemandDir(fullPath, true);
907 new FileIOPermission(FileIOPermissionAccess.Read, new String[] { demandPath }, false, false).Demand();
909 return InternalExists(fullPath);
911 catch (ArgumentException) { }
912 catch (NotSupportedException) { } // Security can throw this on ":"
913 catch (SecurityException) { }
914 catch (IOException) { }
915 catch (UnauthorizedAccessException)
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
924 [System.Security.SecurityCritical] // auto-generated
925 [ResourceExposure(ResourceScope.Machine)]
926 [ResourceConsumption(ResourceScope.Machine)]
927 internal static bool InternalExists(String path)
929 Contract.Requires(path != null);
930 int lastError = Win32Native.ERROR_SUCCESS;
931 return InternalExists(path, out lastError);
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);
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)
952 fullPath = Path.RemoveLongPathPrefix(fullPath);
955 if (fullPath.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)
956 || fullPath.EndsWith(Path.AltDirectorySeparatorChar.ToString(), StringComparison.Ordinal))
957 demandPath = fullPath + '.';
959 demandPath = fullPath + Path.DirectorySeparatorChar + '.';
963 if (!(fullPath.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)
964 || fullPath.EndsWith(Path.AltDirectorySeparatorChar.ToString(), StringComparison.Ordinal)))
965 demandPath = fullPath + Path.DirectorySeparatorChar;
967 demandPath = fullPath;
972 private static String InternalGetDirectoryRoot(String path)
974 if (path == null) return null;
975 return path.Substring(0, LongPath.GetRootLength(path));