/Libraries/LongPath/Microsoft.Experimental.IO/Microsoft.Experimental.IO/LongPathFile.cs

# · C# · 498 lines · 68 code · 33 blank · 397 comment · 9 complexity · 50f9763a3eba3fdf341d5b81355ad6c5 MD5 · raw file

  1. // Copyright (c) Microsoft Corporation. All rights reserved.
  2. using System;
  3. using System.Diagnostics.CodeAnalysis;
  4. using System.IO;
  5. using Microsoft.Experimental.IO.Interop;
  6. using Microsoft.Win32.SafeHandles;
  7. namespace Microsoft.Experimental.IO {
  8. /// <summary>
  9. /// Provides static methods for creating, copying, deleting, moving, and opening of files
  10. /// with long paths, that is, paths that exceed 259 characters.
  11. /// </summary>
  12. public static class LongPathFile {
  13. /// <summary>
  14. /// Returns a value indicating whether the specified path refers to an existing file.
  15. /// </summary>
  16. /// <param name="path">
  17. /// A <see cref="String"/> containing the path to check.
  18. /// </param>
  19. /// <returns>
  20. /// <see langword="true"/> if <paramref name="path"/> refers to an existing file;
  21. /// otherwise, <see langword="false"/>.
  22. /// </returns>
  23. /// <remarks>
  24. /// Note that this method will return false if any error occurs while trying to determine
  25. /// if the specified file exists. This includes situations that would normally result in
  26. /// thrown exceptions including (but not limited to); passing in a file name with invalid
  27. /// or too many characters, an I/O error such as a failing or missing disk, or if the caller
  28. /// does not have Windows or Code Access Security (CAS) permissions to to read the file.
  29. /// </remarks>
  30. public static bool Exists(string path) {
  31. bool isDirectory;
  32. if (LongPathCommon.Exists(path, out isDirectory)) {
  33. return !isDirectory;
  34. }
  35. return false;
  36. }
  37. /// <summary>
  38. /// Deletes the specified file.
  39. /// </summary>
  40. /// <param name="path">
  41. /// A <see cref="String"/> containing the path of the file to delete.
  42. /// </param>
  43. /// <exception cref="ArgumentNullException">
  44. /// <paramref name="path"/> is <see langword="null"/>.
  45. /// </exception>
  46. /// <exception cref="ArgumentException">
  47. /// <paramref name="path"/> is an empty string (""), contains only white
  48. /// space, or contains one or more invalid characters as defined in
  49. /// <see cref="Path.GetInvalidPathChars()"/>.
  50. /// <para>
  51. /// -or-
  52. /// </para>
  53. /// <paramref name="path"/> contains one or more components that exceed
  54. /// the drive-defined maximum length. For example, on Windows-based
  55. /// platforms, components must not exceed 255 characters.
  56. /// </exception>
  57. /// <exception cref="PathTooLongException">
  58. /// <paramref name="path"/> exceeds the system-defined maximum length.
  59. /// For example, on Windows-based platforms, paths must not exceed
  60. /// 32,000 characters.
  61. /// </exception>
  62. /// <exception cref="FileNotFoundException">
  63. /// <paramref name="path"/> could not be found.
  64. /// </exception>
  65. /// <exception cref="DirectoryNotFoundException">
  66. /// One or more directories in <paramref name="path"/> could not be found.
  67. /// </exception>
  68. /// <exception cref="UnauthorizedAccessException">
  69. /// The caller does not have the required access permissions.
  70. /// <para>
  71. /// -or-
  72. /// </para>
  73. /// <paramref name="path"/> refers to a file that is read-only.
  74. /// <para>
  75. /// -or-
  76. /// </para>
  77. /// <paramref name="path"/> is a directory.
  78. /// </exception>
  79. /// <exception cref="IOException">
  80. /// <paramref name="path"/> refers to a file that is in use.
  81. /// <para>
  82. /// -or-
  83. /// </para>
  84. /// <paramref name="path"/> specifies a device that is not ready.
  85. /// </exception>
  86. public static void Delete(string path) {
  87. string normalizedPath = LongPathCommon.NormalizeLongPath(path);
  88. if (!NativeMethods.DeleteFile(normalizedPath)) {
  89. throw LongPathCommon.GetExceptionFromLastWin32Error();
  90. }
  91. }
  92. /// <summary>
  93. /// Moves the specified file to a new location.
  94. /// </summary>
  95. /// <param name="sourcePath">
  96. /// A <see cref="String"/> containing the path of the file to move.
  97. /// </param>
  98. /// <param name="destinationPath">
  99. /// A <see cref="String"/> containing the new path of the file.
  100. /// </param>
  101. /// <exception cref="ArgumentNullException">
  102. /// <paramref name="sourcePath"/> and/or <paramref name="destinationPath"/> is
  103. /// <see langword="null"/>.
  104. /// </exception>
  105. /// <exception cref="ArgumentException">
  106. /// <paramref name="sourcePath"/> and/or <paramref name="destinationPath"/> is
  107. /// an empty string (""), contains only white space, or contains one or more
  108. /// invalid characters as defined in <see cref="Path.GetInvalidPathChars()"/>.
  109. /// <para>
  110. /// -or-
  111. /// </para>
  112. /// <paramref name="sourcePath"/> and/or <paramref name="destinationPath"/>
  113. /// contains one or more components that exceed the drive-defined maximum length.
  114. /// For example, on Windows-based platforms, components must not exceed 255 characters.
  115. /// </exception>
  116. /// <exception cref="PathTooLongException">
  117. /// <paramref name="sourcePath"/> and/or <paramref name="destinationPath"/>
  118. /// exceeds the system-defined maximum length. For example, on Windows-based platforms,
  119. /// paths must not exceed 32,000 characters.
  120. /// </exception>
  121. /// <exception cref="FileNotFoundException">
  122. /// <paramref name="sourcePath"/> could not be found.
  123. /// </exception>
  124. /// <exception cref="DirectoryNotFoundException">
  125. /// One or more directories in <paramref name="sourcePath"/> and/or
  126. /// <paramref name="destinationPath"/> could not be found.
  127. /// </exception>
  128. /// <exception cref="UnauthorizedAccessException">
  129. /// The caller does not have the required access permissions.
  130. /// </exception>
  131. /// <exception cref="IOException">
  132. /// <paramref name="destinationPath"/> refers to a file that already exists.
  133. /// <para>
  134. /// -or-
  135. /// </para>
  136. /// <paramref name="sourcePath"/> and/or <paramref name="destinationPath"/> is a
  137. /// directory.
  138. /// <para>
  139. /// -or-
  140. /// </para>
  141. /// <paramref name="sourcePath"/> refers to a file that is in use.
  142. /// <para>
  143. /// -or-
  144. /// </para>
  145. /// <paramref name="sourcePath"/> and/or <paramref name="destinationPath"/> specifies
  146. /// a device that is not ready.
  147. /// </exception>
  148. public static void Move(string sourcePath, string destinationPath) {
  149. string normalizedSourcePath = LongPathCommon.NormalizeLongPath(sourcePath, "sourcePath");
  150. string normalizedDestinationPath = LongPathCommon.NormalizeLongPath(destinationPath, "destinationPath");
  151. if (!NativeMethods.MoveFile(normalizedSourcePath, normalizedDestinationPath))
  152. throw LongPathCommon.GetExceptionFromLastWin32Error();
  153. }
  154. /// <summary>
  155. /// Copies the specified file to a specified new file, indicating whether to overwrite an existing file.
  156. /// </summary>
  157. /// <param name="sourcePath">
  158. /// A <see cref="String"/> containing the path of the file to copy.
  159. /// </param>
  160. /// <param name="destinationPath">
  161. /// A <see cref="String"/> containing the new path of the file.
  162. /// </param>
  163. /// <param name="overwrite">
  164. /// <see langword="true"/> if <paramref name="destinationPath"/> should be overwritten
  165. /// if it refers to an existing file, otherwise, <see langword="false"/>.
  166. /// </param>
  167. /// <exception cref="ArgumentNullException">
  168. /// <paramref name="sourcePath"/> and/or <paramref name="destinationPath"/> is
  169. /// <see langword="null"/>.
  170. /// </exception>
  171. /// <exception cref="ArgumentException">
  172. /// <paramref name="sourcePath"/> and/or <paramref name="destinationPath"/> is
  173. /// an empty string (""), contains only white space, or contains one or more
  174. /// invalid characters as defined in <see cref="Path.GetInvalidPathChars()"/>.
  175. /// <para>
  176. /// -or-
  177. /// </para>
  178. /// <paramref name="sourcePath"/> and/or <paramref name="destinationPath"/>
  179. /// contains one or more components that exceed the drive-defined maximum length.
  180. /// For example, on Windows-based platforms, components must not exceed 255 characters.
  181. /// </exception>
  182. /// <exception cref="PathTooLongException">
  183. /// <paramref name="sourcePath"/> and/or <paramref name="destinationPath"/>
  184. /// exceeds the system-defined maximum length. For example, on Windows-based platforms,
  185. /// paths must not exceed 32,000 characters.
  186. /// </exception>
  187. /// <exception cref="FileNotFoundException">
  188. /// <paramref name="sourcePath"/> could not be found.
  189. /// </exception>
  190. /// <exception cref="DirectoryNotFoundException">
  191. /// One or more directories in <paramref name="sourcePath"/> and/or
  192. /// <paramref name="destinationPath"/> could not be found.
  193. /// </exception>
  194. /// <exception cref="UnauthorizedAccessException">
  195. /// The caller does not have the required access permissions.
  196. /// <para>
  197. /// -or-
  198. /// </para>
  199. /// <paramref name="overwrite"/> is true and <paramref name="destinationPath"/> refers to a
  200. /// file that is read-only.
  201. /// </exception>
  202. /// <exception cref="IOException">
  203. /// <paramref name="overwrite"/> is false and <paramref name="destinationPath"/> refers to
  204. /// a file that already exists.
  205. /// <para>
  206. /// -or-
  207. /// </para>
  208. /// <paramref name="sourcePath"/> and/or <paramref name="destinationPath"/> is a
  209. /// directory.
  210. /// <para>
  211. /// -or-
  212. /// </para>
  213. /// <paramref name="overwrite"/> is true and <paramref name="destinationPath"/> refers to
  214. /// a file that already exists and is in use.
  215. /// <para>
  216. /// -or-
  217. /// </para>
  218. /// <paramref name="sourcePath"/> refers to a file that is in use.
  219. /// <para>
  220. /// -or-
  221. /// </para>
  222. /// <paramref name="sourcePath"/> and/or <paramref name="destinationPath"/> specifies
  223. /// a device that is not ready.
  224. /// </exception>
  225. public static void Copy(string sourcePath, string destinationPath, bool overwrite) {
  226. string normalizedSourcePath = LongPathCommon.NormalizeLongPath(sourcePath, "sourcePath");
  227. string normalizedDestinationPath = LongPathCommon.NormalizeLongPath(destinationPath, "destinationPath");
  228. if (!NativeMethods.CopyFile(normalizedSourcePath, normalizedDestinationPath, !overwrite))
  229. throw LongPathCommon.GetExceptionFromLastWin32Error();
  230. }
  231. /// <summary>
  232. /// Opens the specified file.
  233. /// </summary>
  234. /// <param name="path">
  235. /// A <see cref="String"/> containing the path of the file to open.
  236. /// </param>
  237. /// <param name="access">
  238. /// One of the <see cref="FileAccess"/> value that specifies the operations that can be
  239. /// performed on the file.
  240. /// </param>
  241. /// <param name="mode">
  242. /// One of the <see cref="FileMode"/> values that specifies whether a file is created
  243. /// if one does not exist, and determines whether the contents of existing files are
  244. /// retained or overwritten.
  245. /// </param>
  246. /// <returns>
  247. /// A <see cref="FileStream"/> that provides access to the file specified in
  248. /// <paramref name="path"/>.
  249. /// </returns>
  250. /// <exception cref="ArgumentNullException">
  251. /// <paramref name="path"/> is <see langword="null"/>.
  252. /// </exception>
  253. /// <exception cref="ArgumentException">
  254. /// <paramref name="path"/> is an empty string (""), contains only white
  255. /// space, or contains one or more invalid characters as defined in
  256. /// <see cref="Path.GetInvalidPathChars()"/>.
  257. /// <para>
  258. /// -or-
  259. /// </para>
  260. /// <paramref name="path"/> contains one or more components that exceed
  261. /// the drive-defined maximum length. For example, on Windows-based
  262. /// platforms, components must not exceed 255 characters.
  263. /// </exception>
  264. /// <exception cref="PathTooLongException">
  265. /// <paramref name="path"/> exceeds the system-defined maximum length.
  266. /// For example, on Windows-based platforms, paths must not exceed
  267. /// 32,000 characters.
  268. /// </exception>
  269. /// <exception cref="DirectoryNotFoundException">
  270. /// One or more directories in <paramref name="path"/> could not be found.
  271. /// </exception>
  272. /// <exception cref="UnauthorizedAccessException">
  273. /// The caller does not have the required access permissions.
  274. /// <para>
  275. /// -or-
  276. /// </para>
  277. /// <paramref name="path"/> refers to a file that is read-only and <paramref name="access"/>
  278. /// is not <see cref="FileAccess.Read"/>.
  279. /// <para>
  280. /// -or-
  281. /// </para>
  282. /// <paramref name="path"/> is a directory.
  283. /// </exception>
  284. /// <exception cref="IOException">
  285. /// <paramref name="path"/> refers to a file that is in use.
  286. /// <para>
  287. /// -or-
  288. /// </para>
  289. /// <paramref name="path"/> specifies a device that is not ready.
  290. /// </exception>
  291. public static FileStream Open(string path, FileMode mode, FileAccess access) {
  292. return Open(path, mode, access, FileShare.None, 0, FileOptions.None);
  293. }
  294. /// <summary>
  295. /// Opens the specified file.
  296. /// </summary>
  297. /// <param name="path">
  298. /// A <see cref="String"/> containing the path of the file to open.
  299. /// </param>
  300. /// <param name="access">
  301. /// One of the <see cref="FileAccess"/> value that specifies the operations that can be
  302. /// performed on the file.
  303. /// </param>
  304. /// <param name="mode">
  305. /// One of the <see cref="FileMode"/> values that specifies whether a file is created
  306. /// if one does not exist, and determines whether the contents of existing files are
  307. /// retained or overwritten.
  308. /// </param>
  309. /// <param name="share">
  310. /// One of the <see cref="FileShare"/> values specifying the type of access other threads
  311. /// have to the file.
  312. /// </param>
  313. /// <returns>
  314. /// A <see cref="FileStream"/> that provides access to the file specified in
  315. /// <paramref name="path"/>.
  316. /// </returns>
  317. /// <exception cref="ArgumentNullException">
  318. /// <paramref name="path"/> is <see langword="null"/>.
  319. /// </exception>
  320. /// <exception cref="ArgumentException">
  321. /// <paramref name="path"/> is an empty string (""), contains only white
  322. /// space, or contains one or more invalid characters as defined in
  323. /// <see cref="Path.GetInvalidPathChars()"/>.
  324. /// <para>
  325. /// -or-
  326. /// </para>
  327. /// <paramref name="path"/> contains one or more components that exceed
  328. /// the drive-defined maximum length. For example, on Windows-based
  329. /// platforms, components must not exceed 255 characters.
  330. /// </exception>
  331. /// <exception cref="PathTooLongException">
  332. /// <paramref name="path"/> exceeds the system-defined maximum length.
  333. /// For example, on Windows-based platforms, paths must not exceed
  334. /// 32,000 characters.
  335. /// </exception>
  336. /// <exception cref="DirectoryNotFoundException">
  337. /// One or more directories in <paramref name="path"/> could not be found.
  338. /// </exception>
  339. /// <exception cref="UnauthorizedAccessException">
  340. /// The caller does not have the required access permissions.
  341. /// <para>
  342. /// -or-
  343. /// </para>
  344. /// <paramref name="path"/> refers to a file that is read-only and <paramref name="access"/>
  345. /// is not <see cref="FileAccess.Read"/>.
  346. /// <para>
  347. /// -or-
  348. /// </para>
  349. /// <paramref name="path"/> is a directory.
  350. /// </exception>
  351. /// <exception cref="IOException">
  352. /// <paramref name="path"/> refers to a file that is in use.
  353. /// <para>
  354. /// -or-
  355. /// </para>
  356. /// <paramref name="path"/> specifies a device that is not ready.
  357. /// </exception>
  358. public static FileStream Open(string path, FileMode mode, FileAccess access, FileShare share) {
  359. return Open(path, mode, access, share, 0, FileOptions.None);
  360. }
  361. /// <summary>
  362. /// Opens the specified file.
  363. /// </summary>
  364. /// <param name="path">
  365. /// A <see cref="String"/> containing the path of the file to open.
  366. /// </param>
  367. /// <param name="access">
  368. /// One of the <see cref="FileAccess"/> value that specifies the operations that can be
  369. /// performed on the file.
  370. /// </param>
  371. /// <param name="mode">
  372. /// One of the <see cref="FileMode"/> values that specifies whether a file is created
  373. /// if one does not exist, and determines whether the contents of existing files are
  374. /// retained or overwritten.
  375. /// </param>
  376. /// <param name="share">
  377. /// One of the <see cref="FileShare"/> values specifying the type of access other threads
  378. /// have to the file.
  379. /// </param>
  380. /// <param name="bufferSize">
  381. /// An <see cref="Int32"/> containing the number of bytes to buffer for reads and writes
  382. /// to the file, or 0 to specified the default buffer size, 1024.
  383. /// </param>
  384. /// <param name="options">
  385. /// One or more of the <see cref="FileOptions"/> values that describes how to create or
  386. /// overwrite the file.
  387. /// </param>
  388. /// <returns>
  389. /// A <see cref="FileStream"/> that provides access to the file specified in
  390. /// <paramref name="path"/>.
  391. /// </returns>
  392. /// <exception cref="ArgumentNullException">
  393. /// <paramref name="path"/> is <see langword="null"/>.
  394. /// </exception>
  395. /// <exception cref="ArgumentException">
  396. /// <paramref name="path"/> is an empty string (""), contains only white
  397. /// space, or contains one or more invalid characters as defined in
  398. /// <see cref="Path.GetInvalidPathChars()"/>.
  399. /// <para>
  400. /// -or-
  401. /// </para>
  402. /// <paramref name="path"/> contains one or more components that exceed
  403. /// the drive-defined maximum length. For example, on Windows-based
  404. /// platforms, components must not exceed 255 characters.
  405. /// </exception>
  406. /// <exception cref="ArgumentOutOfRangeException">
  407. /// <paramref name="bufferSize"/> is less than 0.
  408. /// </exception>
  409. /// <exception cref="PathTooLongException">
  410. /// <paramref name="path"/> exceeds the system-defined maximum length.
  411. /// For example, on Windows-based platforms, paths must not exceed
  412. /// 32,000 characters.
  413. /// </exception>
  414. /// <exception cref="DirectoryNotFoundException">
  415. /// One or more directories in <paramref name="path"/> could not be found.
  416. /// </exception>
  417. /// <exception cref="UnauthorizedAccessException">
  418. /// The caller does not have the required access permissions.
  419. /// <para>
  420. /// -or-
  421. /// </para>
  422. /// <paramref name="path"/> refers to a file that is read-only and <paramref name="access"/>
  423. /// is not <see cref="FileAccess.Read"/>.
  424. /// <para>
  425. /// -or-
  426. /// </para>
  427. /// <paramref name="path"/> is a directory.
  428. /// </exception>
  429. /// <exception cref="IOException">
  430. /// <paramref name="path"/> refers to a file that is in use.
  431. /// <para>
  432. /// -or-
  433. /// </para>
  434. /// <paramref name="path"/> specifies a device that is not ready.
  435. /// </exception>
  436. public static FileStream Open(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options) {
  437. const int DefaultBufferSize = 1024;
  438. if (bufferSize == 0)
  439. bufferSize = DefaultBufferSize;
  440. string normalizedPath = LongPathCommon.NormalizeLongPath(path);
  441. SafeFileHandle handle = GetFileHandle(normalizedPath, mode, access, share, options);
  442. return new FileStream(handle, access, bufferSize, (options & FileOptions.Asynchronous) == FileOptions.Asynchronous);
  443. }
  444. [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification="False positive")]
  445. private static SafeFileHandle GetFileHandle(string normalizedPath, FileMode mode, FileAccess access, FileShare share, FileOptions options) {
  446. NativeMethods.EFileAccess underlyingAccess = GetUnderlyingAccess(access);
  447. SafeFileHandle handle = NativeMethods.CreateFile(normalizedPath, underlyingAccess, (uint)share, IntPtr.Zero, (uint)mode, (uint)options, IntPtr.Zero);
  448. if (handle.IsInvalid)
  449. throw LongPathCommon.GetExceptionFromLastWin32Error();
  450. return handle;
  451. }
  452. private static NativeMethods.EFileAccess GetUnderlyingAccess(FileAccess access) {
  453. switch (access) {
  454. case FileAccess.Read:
  455. return NativeMethods.EFileAccess.GenericRead;
  456. case FileAccess.Write:
  457. return NativeMethods.EFileAccess.GenericWrite;
  458. case FileAccess.ReadWrite:
  459. return NativeMethods.EFileAccess.GenericRead | NativeMethods.EFileAccess.GenericWrite;
  460. default:
  461. throw new ArgumentOutOfRangeException("access");
  462. }
  463. }
  464. }
  465. }