PageRenderTime 58ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/mcs/class/referencesource/mscorlib/system/io/longpath.cs

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