/Release/1.1/Source/Samples/AlphaShadow/Infrastructure/Volume.cs

# · C# · 388 lines · 255 code · 57 blank · 76 comment · 58 complexity · 4e4d3e73763fae7e331c0bb7357c62d1 MD5 · raw file

  1. /* Copyright (c) 2008-2012 Peter Palotas
  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.ComponentModel;
  24. using System.IO;
  25. using System.Runtime.InteropServices;
  26. using System.Security.Permissions;
  27. using System.Text;
  28. using AlphaShadow;
  29. using Alphaleonis.Win32.Vss;
  30. namespace Alphaleonis.Win32.Filesystem
  31. {
  32. /// <summary>
  33. /// This class is a slimmed down version of the Volume-class in AlphaFS (http://alphafs.codeplex.com). It is included
  34. /// in this sample to avoid having a dependency on AlphaFS in here, but please use the AlphaFS version for any
  35. /// production code.
  36. /// </summary>
  37. internal static class Volume
  38. {
  39. /// <summary>
  40. /// Retrieves information about MS-DOS device names.
  41. /// The function can obtain the current mapping for a particular MS-DOS device name.
  42. /// The function can also obtain a list of all existing MS-DOS device names.
  43. /// </summary>
  44. /// <param name="device">The device.</param>
  45. /// <returns>An MS-DOS device name string specifying the target of the query. The device name cannot have a
  46. /// trailing backslash. This parameter can be <see langword="null"/>. In that case, the QueryDosDevice function
  47. /// will return an array of all existing MS-DOS device names</returns>
  48. /// <remarks>See documentation on MSDN for the Windows QueryDosDevice() method for more information.</remarks>
  49. [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
  50. public static string[] QueryDosDevice(string device)
  51. {
  52. uint returnSize = 0;
  53. int maxSize = 260;
  54. List<string> l = new List<string>();
  55. while (true)
  56. {
  57. char[] buffer = new char[maxSize];
  58. returnSize = NativeMethods.QueryDosDeviceW(device, buffer, (uint)buffer.Length);
  59. int lastError = Marshal.GetLastWin32Error();
  60. if (lastError == 0 && returnSize > 0)
  61. {
  62. StringBuilder sb = new StringBuilder();
  63. for (int i = 0; i < returnSize; i++)
  64. {
  65. if (buffer[i] != '\0')
  66. sb.Append(buffer[i]);
  67. else if (sb.Length > 0)
  68. {
  69. l.Add(sb.ToString());
  70. sb.Length = 0;
  71. }
  72. }
  73. return l.ToArray();
  74. }
  75. else if (lastError == NativeMethods.ERROR_INSUFFICIENT_BUFFER)
  76. {
  77. maxSize *= 2;
  78. }
  79. else
  80. {
  81. throw new Win32Exception(lastError);
  82. }
  83. }
  84. }
  85. /// <summary>
  86. /// Gets the shortest display name for the specified <paramref name="volumeName"/>.
  87. /// </summary>
  88. /// <param name="volumeName">The volume name.</param>
  89. /// <returns>The shortest display name for the specified volume found, or <see langword="null"/> if no display names were found.</returns>
  90. /// <exception cref="ArgumentNullException"><paramref name="volumeName"/> is a <see langword="null"/> reference</exception>
  91. /// <exception cref="Win32Exception">An error occured during a system call, such as the volume name specified was invalid or did not exist.</exception>
  92. /// <remarks>This method basically returns the shortest string returned by <see cref="GetVolumePathNamesForVolume"/></remarks>
  93. [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
  94. public static string GetDisplayNameForVolume(string volumeName)
  95. {
  96. string[] volumeMountPoints = GetVolumePathNamesForVolume(volumeName);
  97. if (volumeMountPoints.Length == 0)
  98. return null;
  99. string smallestMountPoint = volumeMountPoints[0];
  100. for (int i = 1; i < volumeMountPoints.Length; i++)
  101. {
  102. if (volumeMountPoints[i].Length < smallestMountPoint.Length)
  103. smallestMountPoint = volumeMountPoints[i];
  104. }
  105. return smallestMountPoint;
  106. }
  107. /// <summary>
  108. /// Retrieves a list of path names for the specified volume name.
  109. /// </summary>
  110. /// <param name="volumeName">The volume name.</param>
  111. /// <returns>An array containing the path names for the specified volume.</returns>
  112. /// <exception cref="ArgumentNullException"><paramref name="volumeName"/> is a <see langword="null"/> reference</exception>
  113. /// <exception cref="System.IO.FileNotFoundException">The volume name specified was invalid, did not exist or was not ready.</exception>
  114. /// <remarks>For more information about this method see the MSDN documentation on GetVolumePathNamesForVolumeName().</remarks>
  115. [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
  116. public static string[] GetVolumePathNamesForVolume(string volumeName)
  117. {
  118. if (volumeName == null)
  119. throw new ArgumentNullException("volumeName");
  120. uint requiredLength = 0;
  121. char[] buffer = new char[NativeMethods.MAX_PATH];
  122. if (!NativeMethods.GetVolumePathNamesForVolumeNameW(volumeName, buffer, (uint)buffer.Length, ref requiredLength))
  123. {
  124. // Not enough room in buffer perhaps? Try a bigger one
  125. buffer = new char[requiredLength];
  126. if (!NativeMethods.GetVolumePathNamesForVolumeNameW(volumeName, buffer, (uint)buffer.Length, ref requiredLength))
  127. Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
  128. }
  129. List<string> displayNames = new List<string>();
  130. StringBuilder displayName = new StringBuilder();
  131. for (int i = 0; i < requiredLength; i++)
  132. {
  133. if (buffer[i] == '\0')
  134. {
  135. if (displayName.Length > 0)
  136. displayNames.Add(displayName.ToString());
  137. displayName.Length = 0;
  138. }
  139. else
  140. {
  141. displayName.Append(buffer[i]);
  142. }
  143. }
  144. return displayNames.ToArray();
  145. }
  146. public static bool IsVolume(IUIHost host, string volumePath)
  147. {
  148. if (volumePath == null)
  149. throw new ArgumentNullException("volumePath");
  150. if (volumePath.Length == 0)
  151. return false;
  152. host.WriteVerbose("- Checking if \"{0}\" is a real volume path...", volumePath);
  153. if (!volumePath.EndsWith("\\"))
  154. volumePath = volumePath + "\\";
  155. StringBuilder volumeNameBuilder = new StringBuilder(NativeMethods.MAX_PATH);
  156. if (ClusterIsPathOnSharedVolume(host, volumePath))
  157. {
  158. if (!NativeMethods.ClusterGetVolumeNameForVolumeMountPointW(volumePath, volumeNameBuilder, (uint)volumeNameBuilder.Capacity))
  159. {
  160. host.WriteVerbose("- ClusterGetVolumeNameForVolumeMountPointW(\"{0}\") failed with error code {1}.", volumePath, Marshal.GetLastWin32Error());
  161. return false;
  162. }
  163. return true;
  164. }
  165. else
  166. {
  167. if (!NativeMethods.GetVolumeNameForVolumeMountPointW(volumePath, volumeNameBuilder, (uint)volumeNameBuilder.Capacity))
  168. {
  169. host.WriteVerbose("- GetVolumeNameForVolumeMountPoint(\"{0}\") failed with error code {1}.", volumePath, Marshal.GetLastWin32Error());
  170. return false;
  171. }
  172. return true;
  173. }
  174. }
  175. /// <summary>
  176. /// Retrieves the unique volume name for the specified volume mount point or root directory.
  177. /// </summary>
  178. /// <param name="mountPoint">The path of a volume mount point (with or without a trailing backslash, "\") or a drive letter indicating a root directory (eg. "C:" or "D:\"). A trailing backslash is required.</param>
  179. /// <returns>The unique volume name of the form "\\?\Volume{GUID}\" where GUID is the GUID that identifies the volume.</returns>
  180. /// <exception cref="ArgumentNullException"><paramref name="mountPoint"/> is a <see langword="null"/> reference</exception>
  181. /// <exception cref="ArgumentException"><paramref name="mountPoint"/> is an empty string</exception>
  182. /// <exception cref="Win32Exception">Upon error retreiving the volume name</exception>
  183. /// <remarks>See the MSDN documentation on the method GetVolumeNameForVolumeMountPoint() for more information.</remarks>
  184. public static string GetUniqueVolumeNameForVolumeMountPoint(string mountPoint)
  185. {
  186. if (mountPoint == null)
  187. throw new ArgumentNullException("mountPoint");
  188. if (mountPoint.Length == 0)
  189. throw new ArgumentException("Mount point must be non-empty");
  190. // Get the volume name alias. This may be different from the unique volume name in some
  191. // rare cases.
  192. StringBuilder volumeName = new StringBuilder(NativeMethods.MAX_PATH);
  193. if (!NativeMethods.GetVolumeNameForVolumeMountPointW(mountPoint, volumeName, (uint)volumeName.Capacity))
  194. throw new Win32Exception();
  195. // Get the unique volume name
  196. StringBuilder uniqueVolumeName = new StringBuilder(NativeMethods.MAX_PATH);
  197. if (!NativeMethods.GetVolumeNameForVolumeMountPointW(volumeName.ToString(), uniqueVolumeName, (uint)volumeName.Capacity))
  198. throw new Win32Exception();
  199. return uniqueVolumeName.ToString();
  200. }
  201. public static string GetUniqueVolumeNameForPath(IUIHost host, string path, bool isBackup)
  202. {
  203. if (path == null)
  204. throw new ArgumentNullException("path");
  205. if (path.Length == 0)
  206. throw new ArgumentException("Mount point must be non-empty");
  207. host.WriteVerbose("- Get volume path name for \"{0}\"...", path);
  208. if (!path.EndsWith("\\"))
  209. path = path + "\\";
  210. if (isBackup && ClusterIsPathOnSharedVolume(host, path))
  211. {
  212. string volumeRootPath, volumeUniqueName;
  213. ClusterPrepareSharedVolumeForBackup(path, out volumeRootPath, out volumeUniqueName);
  214. host.WriteVerbose("- Path name: {0}", volumeRootPath);
  215. host.WriteVerbose("- Unique volume name: {0}", volumeUniqueName);
  216. return volumeUniqueName;
  217. }
  218. else
  219. {
  220. // Get the root path of the volume
  221. StringBuilder volumeRootPath = new StringBuilder(NativeMethods.MAX_PATH);
  222. if (!NativeMethods.GetVolumePathNameW(path, volumeRootPath, (uint)volumeRootPath.Capacity))
  223. {
  224. host.WriteVerbose("- GetVolumePathName(\"{0}\") failed with error code {1}", path, Marshal.GetLastWin32Error());
  225. throw new Win32Exception();
  226. }
  227. // Get the volume name alias (might be different from the unique volume name in rare cases)
  228. StringBuilder volumeName = new StringBuilder(NativeMethods.MAX_PATH);
  229. if (!NativeMethods.GetVolumeNameForVolumeMountPointW(volumeRootPath.ToString(), volumeName, (uint)volumeName.Capacity))
  230. {
  231. host.WriteVerbose("- GetVolumeNameForVolumeMountPoint(\"{0}\") failed with error code {1}", volumeRootPath.ToString(), Marshal.GetLastWin32Error());
  232. throw new Win32Exception();
  233. }
  234. // Gte the unique volume name
  235. StringBuilder uniqueVolumeName = new StringBuilder(NativeMethods.MAX_PATH);
  236. if (!NativeMethods.GetVolumeNameForVolumeMountPointW(volumeName.ToString(), uniqueVolumeName, (uint)uniqueVolumeName.Capacity))
  237. {
  238. host.WriteVerbose("- GetVolumeNameForVolumeMountPoint(\"{0}\") failed with error code {1}", volumeName.ToString(), Marshal.GetLastWin32Error());
  239. throw new Win32Exception();
  240. }
  241. return uniqueVolumeName.ToString();
  242. }
  243. }
  244. public static bool ClusterIsPathOnSharedVolume(IUIHost host, string path)
  245. {
  246. if (OperatingSystemInfo.IsAtLeast(OSVersionName.Windows7))
  247. {
  248. host.WriteVerbose("- Calling ClusterIsPathOnSharedVolume(\"{0}\")...", path);
  249. return NativeMethods.ClusterIsPathOnSharedVolume(path);
  250. }
  251. else
  252. {
  253. host.WriteVerbose("- Skipping call to ClusterIsPathOnSharedVolume; function does not exist on this OS.");
  254. return false;
  255. }
  256. }
  257. public static string ClusterGetVolumeNameForVolumeMountPoint(IUIHost host, string volumeMountPoint)
  258. {
  259. host.WriteVerbose("- Calling ClusterGetVolumeNameForVolumeMountPoint(\"{0}\")...", volumeMountPoint);
  260. StringBuilder result = new StringBuilder(NativeMethods.MAX_PATH);
  261. if (!NativeMethods.ClusterGetVolumeNameForVolumeMountPointW(volumeMountPoint, result, (uint)result.Capacity))
  262. throw new Win32Exception();
  263. return result.ToString();
  264. }
  265. private static void ClusterPrepareSharedVolumeForBackup(string fileName, out string volumePathName, out string volumeName)
  266. {
  267. StringBuilder volumeRootPath = new StringBuilder(NativeMethods.MAX_PATH);
  268. StringBuilder volumeUniqueName = new StringBuilder(NativeMethods.MAX_PATH);
  269. int volumeRootPathCount = volumeRootPath.Capacity;
  270. int volumeUniqueNameCount = volumeUniqueName.Capacity;
  271. int ret = NativeMethods.ClusterPrepareSharedVolumeForBackup(fileName, volumeRootPath, ref volumeRootPathCount, volumeUniqueName, ref volumeUniqueNameCount);
  272. if (ret != 0)
  273. throw new Win32Exception(ret);
  274. volumePathName = volumeRootPath.ToString();
  275. volumeName = volumeUniqueName.ToString();
  276. }
  277. /// <summary>
  278. /// Retreives the Win32 device name from the volume name
  279. /// </summary>
  280. /// <param name="volumeName">Name of the volume. A trailing backslash is not allowed.</param>
  281. /// <returns>The Win32 device name from the volume name</returns>
  282. [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
  283. public static string GetDeviceForVolumeName(string volumeName)
  284. {
  285. if (volumeName == null)
  286. throw new ArgumentNullException("volumeName");
  287. if (volumeName.Length == 0)
  288. throw new ArgumentException("Volume name must be non-empty");
  289. // Eliminate the GLOBALROOT prefix if present
  290. const string globalRootPrefix = @"\\?\GLOBALROOT";
  291. if (volumeName.StartsWith(globalRootPrefix, StringComparison.OrdinalIgnoreCase))
  292. return volumeName.Substring(globalRootPrefix.Length);
  293. // If this is a volume name, get the device
  294. const string dosPrefix = @"\\?\";
  295. const string volumePrefix = @"\\?\Volume";
  296. if (volumeName.StartsWith(volumePrefix, StringComparison.OrdinalIgnoreCase))
  297. {
  298. // Isolate the DOS device for the volume name (in the format Volume{GUID})
  299. string dosDevice = volumeName.Substring(dosPrefix.Length);
  300. // Get the real device underneath
  301. return QueryDosDevice(dosDevice)[0];
  302. }
  303. return volumeName;
  304. }
  305. private static class NativeMethods
  306. {
  307. public const int MAX_PATH = 261;
  308. public const uint ERROR_INSUFFICIENT_BUFFER = 122;
  309. [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
  310. [return: MarshalAs(UnmanagedType.Bool)]
  311. public static extern bool GetVolumeNameForVolumeMountPointW([In] string lpszVolumeMountPoint, [Out] StringBuilder lpszVolumeName, uint cchBufferLength);
  312. [DllImport("ResUtils.dll", CharSet = CharSet.Unicode, SetLastError = true)]
  313. [return: MarshalAs(UnmanagedType.Bool)]
  314. public static extern bool ClusterGetVolumeNameForVolumeMountPointW([In] string lpszVolumeMountPoint, [Out] StringBuilder lpszVolumeName, uint cchBufferLength);
  315. [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
  316. [return: MarshalAs(UnmanagedType.Bool)]
  317. public static extern bool GetVolumePathNameW([In] string lpszFileName, [Out] StringBuilder lpszVolumePathName, uint cchBufferLength);
  318. [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
  319. [return: MarshalAs(UnmanagedType.Bool)]
  320. public static extern bool GetVolumePathNamesForVolumeNameW(string lpszVolumeName, char[] lpszVolumePathNames, uint cchBuferLength, ref uint lpcchReturnLength);
  321. [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
  322. internal static extern uint QueryDosDeviceW(string lpDeviceName, [Out] char[] lpTargetPath, uint ucchMax);
  323. [DllImport("ResUtils.dll", CharSet = CharSet.Unicode, SetLastError = true)]
  324. [return: MarshalAs(UnmanagedType.Bool)]
  325. internal static extern bool ClusterIsPathOnSharedVolume(string lpszPathName);
  326. [DllImport("ResUtils.dll", CharSet = CharSet.Unicode, SetLastError = false, PreserveSig=true)]
  327. [return: MarshalAs(UnmanagedType.U4)]
  328. internal static extern int ClusterPrepareSharedVolumeForBackup(string lpszFileName, [Out] StringBuilder lpszVolumePathName, ref int lpcchVolumePathName, [Out] StringBuilder lpszVolumeName, ref int lpcchVolumeName);
  329. }
  330. }
  331. }