You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

423 lines
22 KiB

  1. /* Copyright (C) 2008-2016 Peter Palotas, Jeffrey Jangli, Alexandr Normuradov
  2. *
  3. * Permission is hereby granted, free of charge, to any person obtaining a copy
  4. * of this software and associated documentation files (the "Software"), to deal
  5. * in the Software without restriction, including without limitation the rights
  6. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  7. * copies of the Software, and to permit persons to whom the Software is
  8. * furnished to do so, subject to the following conditions:
  9. *
  10. * The above copyright notice and this permission notice shall be included in
  11. * all copies or substantial portions of the Software.
  12. *
  13. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  16. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  17. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  18. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  19. * THE SOFTWARE.
  20. */
  21. using System;
  22. using System.Collections.Generic;
  23. using System.Diagnostics.CodeAnalysis;
  24. using System.Globalization;
  25. using System.IO;
  26. using System.Runtime.InteropServices;
  27. using System.Security;
  28. namespace Alphaleonis.Win32.Filesystem
  29. {
  30. partial class Directory
  31. {
  32. #region .NET
  33. /// <summary>Deletes an empty directory from a specified path.</summary>
  34. /// <exception cref="ArgumentException"/>
  35. /// <exception cref="ArgumentNullException"/>
  36. /// <exception cref="DirectoryNotFoundException"/>
  37. /// <exception cref="IOException"/>
  38. /// <exception cref="NotSupportedException"/>
  39. /// <exception cref="UnauthorizedAccessException"/>
  40. /// <exception cref="DirectoryReadOnlyException"/>
  41. /// <param name="path">The name of the empty directory to remove. This directory must be writable and empty.</param>
  42. [SecurityCritical]
  43. public static void Delete(string path)
  44. {
  45. DeleteDirectoryCore(null, null, path, false, false, false, PathFormat.RelativePath);
  46. }
  47. /// <summary>Deletes the specified directory and, if indicated, any subdirectories in the directory.</summary>
  48. /// <exception cref="ArgumentException"/>
  49. /// <exception cref="ArgumentNullException"/>
  50. /// <exception cref="DirectoryNotFoundException"/>
  51. /// <exception cref="IOException"/>
  52. /// <exception cref="NotSupportedException"/>
  53. /// <exception cref="UnauthorizedAccessException"/>
  54. /// <exception cref="DirectoryReadOnlyException"/>
  55. /// <param name="path">The name of the directory to remove.</param>
  56. /// <param name="recursive"><see langword="true"/> to remove directories, subdirectories, and files in <paramref name="path"/>. <see langword="false"/> otherwise.</param>
  57. [SecurityCritical]
  58. public static void Delete(string path, bool recursive)
  59. {
  60. DeleteDirectoryCore(null, null, path, recursive, false, false, PathFormat.RelativePath);
  61. }
  62. #endregion // .NET
  63. /// <summary>[AlphaFS] Deletes an empty directory from a specified path.</summary>
  64. /// <exception cref="ArgumentException"/>
  65. /// <exception cref="ArgumentNullException"/>
  66. /// <exception cref="DirectoryNotFoundException"/>
  67. /// <exception cref="IOException"/>
  68. /// <exception cref="NotSupportedException"/>
  69. /// <exception cref="UnauthorizedAccessException"/>
  70. /// <exception cref="DirectoryReadOnlyException"/>
  71. /// <param name="path">The name of the empty directory to remove. This directory must be writable and empty.</param>
  72. /// <param name="pathFormat">Indicates the format of the path parameter(s).</param>
  73. [SecurityCritical]
  74. public static void Delete(string path, PathFormat pathFormat)
  75. {
  76. DeleteDirectoryCore(null, null, path, false, false, false, pathFormat);
  77. }
  78. /// <summary>[AlphaFS] Deletes the specified directory and, if indicated, any subdirectories in the directory.</summary>
  79. /// <exception cref="ArgumentException"/>
  80. /// <exception cref="ArgumentNullException"/>
  81. /// <exception cref="DirectoryNotFoundException"/>
  82. /// <exception cref="IOException"/>
  83. /// <exception cref="NotSupportedException"/>
  84. /// <exception cref="UnauthorizedAccessException"/>
  85. /// <exception cref="DirectoryReadOnlyException"/>
  86. /// <param name="path">The name of the directory to remove.</param>
  87. /// <param name="recursive"><see langword="true"/> to remove directories, subdirectories, and files in <paramref name="path"/>. <see langword="false"/> otherwise.</param>
  88. /// <param name="pathFormat">Indicates the format of the path parameter(s).</param>
  89. [SecurityCritical]
  90. public static void Delete(string path, bool recursive, PathFormat pathFormat)
  91. {
  92. DeleteDirectoryCore(null, null, path, recursive, false, false, pathFormat);
  93. }
  94. /// <summary>[AlphaFS] Deletes the specified directory and, if indicated, any subdirectories in the directory.</summary>
  95. /// <exception cref="ArgumentException"/>
  96. /// <exception cref="ArgumentNullException"/>
  97. /// <exception cref="DirectoryNotFoundException"/>
  98. /// <exception cref="IOException"/>
  99. /// <exception cref="NotSupportedException"/>
  100. /// <exception cref="UnauthorizedAccessException"/>
  101. /// <exception cref="DirectoryReadOnlyException"/>
  102. /// <param name="path">The name of the directory to remove.</param>
  103. /// <param name="recursive"><see langword="true"/> to remove directories, subdirectories, and files in <paramref name="path"/>. <see langword="false"/> otherwise.</param>
  104. /// <param name="ignoreReadOnly"><see langword="true"/> overrides read only <see cref="FileAttributes"/> of files and directories.</param>
  105. [SecurityCritical]
  106. public static void Delete(string path, bool recursive, bool ignoreReadOnly)
  107. {
  108. DeleteDirectoryCore(null, null, path, recursive, ignoreReadOnly, false, PathFormat.RelativePath);
  109. }
  110. /// <summary>[AlphaFS] Deletes the specified directory and, if indicated, any subdirectories in the directory.</summary>
  111. /// <exception cref="ArgumentException"/>
  112. /// <exception cref="ArgumentNullException"/>
  113. /// <exception cref="DirectoryNotFoundException"/>
  114. /// <exception cref="IOException"/>
  115. /// <exception cref="NotSupportedException"/>
  116. /// <exception cref="UnauthorizedAccessException"/>
  117. /// <exception cref="DirectoryReadOnlyException"/>
  118. /// <param name="path">The name of the directory to remove.</param>
  119. /// <param name="recursive"><see langword="true"/> to remove directories, subdirectories, and files in <paramref name="path"/>. <see langword="false"/> otherwise.</param>
  120. /// <param name="ignoreReadOnly"><see langword="true"/> overrides read only <see cref="FileAttributes"/> of files and directories.</param>
  121. /// <param name="pathFormat">Indicates the format of the path parameter(s).</param>
  122. [SecurityCritical]
  123. public static void Delete(string path, bool recursive, bool ignoreReadOnly, PathFormat pathFormat)
  124. {
  125. DeleteDirectoryCore(null, null, path, recursive, ignoreReadOnly, false, pathFormat);
  126. }
  127. #region Transactional
  128. /// <summary>[AlphaFS] Deletes an empty directory from a specified path.</summary>
  129. /// <exception cref="ArgumentException"/>
  130. /// <exception cref="ArgumentNullException"/>
  131. /// <exception cref="DirectoryNotFoundException"/>
  132. /// <exception cref="IOException"/>
  133. /// <exception cref="NotSupportedException"/>
  134. /// <exception cref="UnauthorizedAccessException"/>
  135. /// <exception cref="DirectoryReadOnlyException"/>
  136. /// <param name="transaction">The transaction.</param>
  137. /// <param name="path">The name of the empty directory to remove. This directory must be writable and empty.</param>
  138. [SecurityCritical]
  139. public static void DeleteTransacted(KernelTransaction transaction, string path)
  140. {
  141. DeleteDirectoryCore(null, transaction, path, false, false, false, PathFormat.RelativePath);
  142. }
  143. /// <summary>[AlphaFS] Deletes an empty directory from a specified path.</summary>
  144. /// <exception cref="ArgumentException"/>
  145. /// <exception cref="ArgumentNullException"/>
  146. /// <exception cref="DirectoryNotFoundException"/>
  147. /// <exception cref="IOException"/>
  148. /// <exception cref="NotSupportedException"/>
  149. /// <exception cref="UnauthorizedAccessException"/>
  150. /// <exception cref="DirectoryReadOnlyException"/>
  151. /// <param name="transaction">The transaction.</param>
  152. /// <param name="path">The name of the empty directory to remove. This directory must be writable and empty.</param>
  153. /// <param name="pathFormat">Indicates the format of the path parameter(s).</param>
  154. [SecurityCritical]
  155. public static void DeleteTransacted(KernelTransaction transaction, string path, PathFormat pathFormat)
  156. {
  157. DeleteDirectoryCore(null, transaction, path, false, false, false, pathFormat);
  158. }
  159. /// <summary>[AlphaFS] Deletes the specified directory and, if indicated, any subdirectories in the directory.</summary>
  160. /// <exception cref="ArgumentException"/>
  161. /// <exception cref="ArgumentNullException"/>
  162. /// <exception cref="DirectoryNotFoundException"/>
  163. /// <exception cref="IOException"/>
  164. /// <exception cref="NotSupportedException"/>
  165. /// <exception cref="UnauthorizedAccessException"/>
  166. /// <exception cref="DirectoryReadOnlyException"/>
  167. /// <param name="transaction">The transaction.</param>
  168. /// <param name="path">The name of the directory to remove.</param>
  169. /// <param name="recursive"><see langword="true"/> to remove directories, subdirectories, and files in <paramref name="path"/>. <see langword="false"/> otherwise.</param>
  170. [SecurityCritical]
  171. public static void DeleteTransacted(KernelTransaction transaction, string path, bool recursive)
  172. {
  173. DeleteDirectoryCore(null, transaction, path, recursive, false, false, PathFormat.RelativePath);
  174. }
  175. /// <summary>[AlphaFS] Deletes the specified directory and, if indicated, any subdirectories in the directory.</summary>
  176. /// <exception cref="ArgumentException"/>
  177. /// <exception cref="ArgumentNullException"/>
  178. /// <exception cref="DirectoryNotFoundException"/>
  179. /// <exception cref="IOException"/>
  180. /// <exception cref="NotSupportedException"/>
  181. /// <exception cref="UnauthorizedAccessException"/>
  182. /// <exception cref="DirectoryReadOnlyException"/>
  183. /// <param name="transaction">The transaction.</param>
  184. /// <param name="path">The name of the directory to remove.</param>
  185. /// <param name="recursive"><see langword="true"/> to remove directories, subdirectories, and files in <paramref name="path"/>. <see langword="false"/> otherwise.</param>
  186. /// <param name="pathFormat">Indicates the format of the path parameter(s).</param>
  187. [SecurityCritical]
  188. public static void DeleteTransacted(KernelTransaction transaction, string path, bool recursive, PathFormat pathFormat)
  189. {
  190. DeleteDirectoryCore(null, transaction, path, recursive, false, false, pathFormat);
  191. }
  192. /// <summary>[AlphaFS] Deletes the specified directory and, if indicated, any subdirectories in the directory.</summary>
  193. /// <exception cref="ArgumentException"/>
  194. /// <exception cref="ArgumentNullException"/>
  195. /// <exception cref="DirectoryNotFoundException"/>
  196. /// <exception cref="IOException"/>
  197. /// <exception cref="NotSupportedException"/>
  198. /// <exception cref="UnauthorizedAccessException"/>
  199. /// <exception cref="DirectoryReadOnlyException"/>
  200. /// <param name="transaction">The transaction.</param>
  201. /// <param name="path">The name of the directory to remove.</param>
  202. /// <param name="recursive"><see langword="true"/> to remove directories, subdirectories, and files in <paramref name="path"/>. <see langword="false"/> otherwise.</param>
  203. /// <param name="ignoreReadOnly"><see langword="true"/> overrides read only <see cref="FileAttributes"/> of files and directories.</param>
  204. [SecurityCritical]
  205. public static void DeleteTransacted(KernelTransaction transaction, string path, bool recursive, bool ignoreReadOnly)
  206. {
  207. DeleteDirectoryCore(null, transaction, path, recursive, ignoreReadOnly, false, PathFormat.RelativePath);
  208. }
  209. /// <summary>[AlphaFS] Deletes the specified directory and, if indicated, any subdirectories in the directory.</summary>
  210. /// <exception cref="ArgumentException"/>
  211. /// <exception cref="ArgumentNullException"/>
  212. /// <exception cref="DirectoryNotFoundException"/>
  213. /// <exception cref="IOException"/>
  214. /// <exception cref="NotSupportedException"/>
  215. /// <exception cref="UnauthorizedAccessException"/>
  216. /// <exception cref="DirectoryReadOnlyException"/>
  217. /// <param name="transaction">The transaction.</param>
  218. /// <param name="path">The name of the directory to remove.</param>
  219. /// <param name="recursive"><see langword="true"/> to remove directories, subdirectories, and files in <paramref name="path"/>. <see langword="false"/> otherwise.</param>
  220. /// <param name="ignoreReadOnly"><see langword="true"/> overrides read only <see cref="FileAttributes"/> of files and directories.</param>
  221. /// <param name="pathFormat">Indicates the format of the path parameter(s).</param>
  222. [SecurityCritical]
  223. public static void DeleteTransacted(KernelTransaction transaction, string path, bool recursive, bool ignoreReadOnly, PathFormat pathFormat)
  224. {
  225. DeleteDirectoryCore(null, transaction, path, recursive, ignoreReadOnly, false, pathFormat);
  226. }
  227. #endregion // Transactional
  228. #region Internal Methods
  229. /// <summary>Deletes the specified directory and, if indicated, any subdirectories in the directory.</summary>
  230. /// <remarks>The RemoveDirectory function marks a directory for deletion on close. Therefore, the directory is not removed until the last handle to the directory is closed.</remarks>
  231. /// <exception cref="ArgumentException"/>
  232. /// <exception cref="ArgumentNullException"/>
  233. /// <exception cref="DirectoryNotFoundException"/>
  234. /// <exception cref="IOException"/>
  235. /// <exception cref="NotSupportedException"/>
  236. /// <exception cref="UnauthorizedAccessException"/>
  237. /// <exception cref="DirectoryReadOnlyException"/>
  238. /// <param name="fsEntryInfo">A FileSystemEntryInfo instance. Use either <paramref name="fsEntryInfo"/> or <paramref name="path"/>, not both.</param>
  239. /// <param name="transaction">The transaction.</param>
  240. /// <param name="path">The name of the directory to remove. Use either <paramref name="path"/> or <paramref name="fsEntryInfo"/>, not both.</param>
  241. /// <param name="recursive"><see langword="true"/> to remove all files and subdirectories recursively; <see langword="false"/> otherwise only the top level empty directory.</param>
  242. /// <param name="ignoreReadOnly"><see langword="true"/> overrides read only attribute of files and directories.</param>
  243. /// <param name="continueOnNotFound">When <see langword="true"/> does not throw an Exception when the directory does not exist.</param>
  244. /// <param name="pathFormat">Indicates the format of the path parameter(s).</param>
  245. [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
  246. [SecurityCritical]
  247. internal static void DeleteDirectoryCore(FileSystemEntryInfo fsEntryInfo, KernelTransaction transaction, string path, bool recursive, bool ignoreReadOnly, bool continueOnNotFound, PathFormat pathFormat)
  248. {
  249. #region Setup
  250. if (fsEntryInfo == null)
  251. {
  252. // MSDN: .NET 3.5+: DirectoryNotFoundException:
  253. // Path does not exist or could not be found.
  254. // Path refers to a file instead of a directory.
  255. // The specified path is invalid (for example, it is on an unmapped drive).
  256. if (pathFormat == PathFormat.RelativePath)
  257. Path.CheckSupportedPathFormat(path, true, true);
  258. fsEntryInfo = File.GetFileSystemEntryInfoCore(true, transaction, Path.GetExtendedLengthPathCore(transaction, path, pathFormat, GetFullPathOptions.TrimEnd | GetFullPathOptions.RemoveTrailingDirectorySeparator), continueOnNotFound, pathFormat);
  259. if (fsEntryInfo == null)
  260. return;
  261. }
  262. #endregion // Setup
  263. // Do not follow mount points nor symbolic links, but do delete the reparse point itself.
  264. // If directory is reparse point, disable recursion.
  265. if (recursive && !fsEntryInfo.IsReparsePoint)
  266. {
  267. var dirs = new Stack<string>(1000);
  268. foreach (var fsei in EnumerateFileSystemEntryInfosCore<FileSystemEntryInfo>(transaction, fsEntryInfo.LongFullPath, Path.WildcardStarMatchAll, DirectoryEnumerationOptions.FilesAndFolders | DirectoryEnumerationOptions.Recursive, PathFormat.LongFullPath))
  269. {
  270. if (fsei.IsDirectory)
  271. {
  272. // Check to see if this is a mount point, and unmount it.
  273. // Now it is safe to delete the actual directory.
  274. if (fsei.IsMountPoint)
  275. Volume.DeleteVolumeMountPointCore(fsei.LongFullPath, false);
  276. dirs.Push(fsei.LongFullPath);
  277. }
  278. else
  279. File.DeleteFileCore(transaction, fsei.LongFullPath, ignoreReadOnly, PathFormat.LongFullPath);
  280. }
  281. while (dirs.Count > 0)
  282. DeleteDirectoryCore(transaction, dirs.Pop(), ignoreReadOnly, continueOnNotFound);
  283. }
  284. // Check to see if this is a mount point, and unmount it.
  285. // Now it is safe to delete the actual directory.
  286. if (fsEntryInfo.IsMountPoint)
  287. Volume.DeleteVolumeMountPointCore(fsEntryInfo.LongFullPath, false);
  288. DeleteDirectoryCore(transaction, fsEntryInfo.LongFullPath, ignoreReadOnly, continueOnNotFound);
  289. }
  290. private static void DeleteDirectoryCore(KernelTransaction transaction, string pathLp, bool ignoreReadOnly, bool continueOnNotFound)
  291. {
  292. startRemoveDirectory:
  293. var success = transaction == null || !NativeMethods.IsAtLeastWindowsVista
  294. // RemoveDirectory() / RemoveDirectoryTransacted()
  295. // In the ANSI version of this function, the name is limited to MAX_PATH characters.
  296. // To extend this limit to 32,767 wide characters, call the Unicode version of the function and prepend "\\?\" to the path.
  297. // 2014-09-09: MSDN confirms LongPath usage.
  298. // RemoveDirectory on a symbolic link will remove the link itself.
  299. ? NativeMethods.RemoveDirectory(pathLp)
  300. : NativeMethods.RemoveDirectoryTransacted(pathLp, transaction.SafeHandle);
  301. var lastError = Marshal.GetLastWin32Error();
  302. if (!success)
  303. {
  304. switch ((uint) lastError)
  305. {
  306. case Win32Errors.ERROR_DIR_NOT_EMPTY:
  307. // MSDN: .NET 3.5+: IOException: The directory specified by path is not an empty directory.
  308. throw new DirectoryNotEmptyException(pathLp);
  309. case Win32Errors.ERROR_DIRECTORY:
  310. // MSDN: .NET 3.5+: DirectoryNotFoundException: Path refers to a file instead of a directory.
  311. if (File.ExistsCore(false, transaction, pathLp, PathFormat.LongFullPath))
  312. throw new DirectoryNotFoundException(string.Format(CultureInfo.CurrentCulture, "({0}) {1}", Win32Errors.ERROR_INVALID_PARAMETER, string.Format(CultureInfo.CurrentCulture, Resources.Target_Directory_Is_A_File, pathLp)));
  313. break;
  314. case Win32Errors.ERROR_PATH_NOT_FOUND:
  315. if (continueOnNotFound)
  316. return;
  317. break;
  318. case Win32Errors.ERROR_SHARING_VIOLATION:
  319. // MSDN: .NET 3.5+: IOException: The directory is being used by another process or there is an open handle on the directory.
  320. NativeError.ThrowException(lastError, pathLp);
  321. break;
  322. case Win32Errors.ERROR_ACCESS_DENIED:
  323. var data = new NativeMethods.WIN32_FILE_ATTRIBUTE_DATA();
  324. var dataInitialised = File.FillAttributeInfoCore(transaction, pathLp, ref data, false, true);
  325. if (data.dwFileAttributes != (FileAttributes) (-1))
  326. {
  327. if ((data.dwFileAttributes & FileAttributes.ReadOnly) != 0)
  328. {
  329. // MSDN: .NET 3.5+: IOException: The directory specified by path is read-only.
  330. if (ignoreReadOnly)
  331. {
  332. // Reset directory attributes.
  333. File.SetAttributesCore(true, transaction, pathLp, FileAttributes.Normal, PathFormat.LongFullPath);
  334. goto startRemoveDirectory;
  335. }
  336. // MSDN: .NET 3.5+: IOException: The directory is read-only.
  337. throw new DirectoryReadOnlyException(pathLp);
  338. }
  339. }
  340. // MSDN: .NET 3.5+: UnauthorizedAccessException: The caller does not have the required permission.
  341. if (dataInitialised == Win32Errors.ERROR_SUCCESS)
  342. NativeError.ThrowException(lastError, pathLp);
  343. break;
  344. }
  345. // MSDN: .NET 3.5+: IOException:
  346. // A file with the same name and location specified by path exists.
  347. // The directory specified by path is read-only, or recursive is false and path is not an empty directory.
  348. // The directory is the application's current working directory.
  349. // The directory contains a read-only file.
  350. // The directory is being used by another process.
  351. NativeError.ThrowException(lastError, pathLp);
  352. }
  353. }
  354. #endregion // Internal Methods
  355. }
  356. }