source: branches/eraser6/Util/Drive.cs @ 403

Revision 403, 17.2 KB checked in by lowjoel, 6 years ago (diff)

-Fixed a documentation error with a few of the Volume functions after the class restructuring.
-Tell the Marshaller to use LPWStr because we are using the Unicode functions (we explicitly state we use the <func>W functions)
-GetDriveType? can handle volume ID's, just that I didn't specify a marshalling type so it used the ANSI function when we provided a Unicode string.

  • Property svn:keywords set to Id
Line 
1/*
2 * $Id$
3 * Copyright 2008 The Eraser Project
4 * Original Author: Joel Low <lowjoel@users.sourceforge.net>
5 * Modified By:
6 *
7 * This file is part of Eraser.
8 *
9 * Eraser is free software: you can redistribute it and/or modify it under the
10 * terms of the GNU General Public License as published by the Free Software
11 * Foundation, either version 3 of the License, or (at your option) any later
12 * version.
13 *
14 * Eraser is distributed in the hope that it will be useful, but WITHOUT ANY
15 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
17 *
18 * A copy of the GNU General Public License can be found at
19 * <http://www.gnu.org/licenses/>.
20 */
21
22using System;
23using System.Collections.Generic;
24using System.Text;
25
26using System.Runtime.InteropServices;
27using System.ComponentModel;
28using System.IO;
29using Microsoft.Win32.SafeHandles;
30
31namespace Eraser.Util
32{
33    public class Volume
34    {
35        /// <summary>
36        /// Constructor.
37        /// </summary>
38        /// <param name="volumeID">The ID of the volume.</param>
39        public Volume(string volumeID)
40        {
41            //Set the volume Id
42            this.volumeID = volumeID;
43
44            //Get the paths of the said volume
45            IntPtr pathNamesBuffer = IntPtr.Zero;
46            string pathNames = string.Empty;
47            try
48            {
49                uint currentBufferSize = MaxPath;
50                uint returnLength = 0;
51                pathNamesBuffer = Marshal.AllocHGlobal((int)(currentBufferSize * sizeof(char)));
52                while (!GetVolumePathNamesForVolumeName(volumeID, pathNamesBuffer, currentBufferSize,
53                    out returnLength))
54                {
55                    if (Marshal.GetLastWin32Error() == 234/*ERROR_MORE_DATA*/)
56                    {
57                        currentBufferSize *= 2;
58                        pathNamesBuffer = Marshal.AllocHGlobal((int)(currentBufferSize * sizeof(char)));
59                    }
60                }
61
62                pathNames = Marshal.PtrToStringAuto(pathNamesBuffer, (int)returnLength);
63            }
64            finally
65            {
66                if (pathNamesBuffer != IntPtr.Zero)
67                    Marshal.FreeHGlobal(pathNamesBuffer);
68            }
69
70            //OK, the marshalling is complete. Convert the pathNames string into a list
71            //of strings containing all of the volumes mountpoints; because the
72            //GetVolumePathNamesForVolumeName function returns a convoluted structure
73            //containing the path names.
74            for (int lastIndex = 0, i = 0; i != pathNames.Length; ++i)
75            {
76                if (pathNames[i] == '\0')
77                {
78                    mountPoints.Add(pathNames.Substring(lastIndex, i - lastIndex));
79
80                    lastIndex = i + 1;
81                    if (pathNames[lastIndex] == '\0')
82                        break;
83                }
84            }
85        }
86
87        /// <summary>
88        /// Lists all the volumes in the system.
89        /// </summary>
90        /// <returns>Returns a list of volumes representing all volumes present in
91        /// the system.</returns>
92        public static List<Volume> GetVolumes()
93        {
94            List<Volume> result = new List<Volume>();
95            IntPtr nextVolume = Marshal.AllocHGlobal(MaxPath * sizeof(char));
96            try
97            {
98                SafeHandle handle = FindFirstVolume(nextVolume, MaxPath);
99                if (handle.IsInvalid)
100                    return result;
101
102                //Iterate over the volume mountpoints
103                do
104                    result.Add(new Volume(Marshal.PtrToStringAuto(nextVolume)));
105                while (FindNextVolume(handle, nextVolume, MaxPath));
106
107                //Close the handle
108                if (Marshal.GetLastWin32Error() == 18 /*ERROR_NO_MORE_FILES*/)
109                    FindVolumeClose(handle);
110               
111                return result;
112            }
113            finally
114            {
115                Marshal.FreeHGlobal(nextVolume);
116            }
117        }
118
119        /// <summary>
120        /// Determines the cluster size of the current volume.
121        /// </summary>
122        /// <returns>The size of one cluster, in bytes.</returns>
123        public uint GetClusterSize()
124        {
125            UInt32 clusterSize, sectorSize, freeClusters, totalClusters;
126            if (GetDiskFreeSpace(mountPoints[0], out clusterSize, out sectorSize,
127                out freeClusters, out totalClusters))
128                return clusterSize * sectorSize;
129
130            throw new Win32Exception(Marshal.GetLastWin32Error(),
131                "Eraser.Util.Drive.GetClusterSize");
132        }
133
134        /// <summary>
135        /// Checks if the current user has disk quotas on the current volume.
136        /// </summary>
137        /// <returns>True if quotas are in effect.</returns>
138        public bool HasQuota()
139        {
140            UInt64 freeBytesAvailable, totalNumberOfBytes, totalNumberOfFreeBytes;
141            if (GetDiskFreeSpaceEx(VolumeID, out freeBytesAvailable, out totalNumberOfBytes,
142                out totalNumberOfFreeBytes))
143                return totalNumberOfFreeBytes != freeBytesAvailable;
144
145            throw new Win32Exception(Marshal.GetLastWin32Error(),
146                "Eraser.Util.Drive.HasQuota");
147        }
148
149        /// <summary>
150        /// Retrieves all mountpoints in the current volume, if the current volume
151        /// contains volume mountpoints.
152        /// </summary>
153        /// <returns>A list containing Volume objects, representing each of the
154        /// volumes at the mountpoints.</returns>
155        public List<Volume> GetMountpoints()
156        {
157            List<Volume> result = new List<Volume>();
158            IntPtr nextMountpoint = Marshal.AllocHGlobal(MaxPath * sizeof(char));
159            try
160            {
161                SafeHandle handle = FindFirstVolumeMountPoint(VolumeID,
162                    nextMountpoint, MaxPath);
163                if (handle.IsInvalid)
164                    return result;
165
166                //Iterate over the volume mountpoints
167                while (FindNextVolumeMountPoint(handle, nextMountpoint, MaxPath))
168                    result.Add(new Volume(Marshal.PtrToStringAuto(nextMountpoint)));
169
170                //Close the handle
171                if (Marshal.GetLastWin32Error() == 18 /*ERROR_NO_MORE_FILES*/)
172                    FindVolumeMountPointClose(handle);
173
174                return result;
175            }
176            finally
177            {
178                Marshal.FreeHGlobal(nextMountpoint);
179            }
180        }
181
182        /// <summary>
183        /// Returns the volume identifier as would be returned from FindFirstVolume.
184        /// </summary>
185        public string VolumeID
186        {
187            get { return volumeID; }
188        }
189        private string volumeID;
190
191        /// <summary>
192        /// The various mountpoints to the root of the volume. This list contains
193        /// paths which may be a drive or a mountpoint. Every string includes the
194        /// trailing backslash.
195        /// </summary>
196        public List<string> MountPoints
197        {
198            get { return mountPoints; }
199            set { mountPoints = value; }
200        }
201        private List<string> mountPoints = new List<string>();
202
203        /// <summary>
204        /// Gets whether the current volume is mounted at any place.
205        /// </summary>
206        public bool IsMounted
207        {
208            get { return mountPoints.Count != 0; }
209        }
210
211        /// <summary>
212        /// Gets the drive type; returns one of the System.IO.DriveType values.
213        /// </summary>
214        public DriveType VolumeType
215        {
216            get
217            {
218                return (DriveType)GetDriveType(VolumeID);
219            }
220        }
221
222        internal const int MaxPath = 32768;
223
224        #region Windows API Functions
225        /// <summary>
226        /// Retrieves information about the specified disk, including the amount
227        /// of free space on the disk.
228        ///
229        /// The GetDiskFreeSpace function cannot report volume sizes that are
230        /// greater than 2 gigabytes (GB). To ensure that your application works
231        /// with large capacity hard drives, use the GetDiskFreeSpaceEx function.
232        /// </summary>
233        /// <param name="lpRootPathName">The root directory of the disk for which
234        /// information is to be returned. If this parameter is NULL, the function
235        /// uses the root of the current disk. If this parameter is a UNC name,
236        /// it must include a trailing backslash (for example, \\MyServer\MyShare\).
237        /// Furthermore, a drive specification must have a trailing backslash
238        /// (for example, C:\). The calling application must have FILE_LIST_DIRECTORY
239        /// access rights for this directory.</param>
240        /// <param name="lpSectorsPerCluster">A pointer to a variable that receives
241        /// the number of sectors per cluster.</param>
242        /// <param name="lpBytesPerSector">A pointer to a variable that receives
243        /// the number of bytes per sector.</param>
244        /// <param name="lpNumberOfFreeClusters">A pointer to a variable that
245        /// receives the total number of free clusters on the disk that are
246        /// available to the user who is associated with the calling thread.
247        ///
248        /// If per-user disk quotas are in use, this value may be less than the
249        /// total number of free clusters on the disk.</param>
250        /// <param name="lpTotalNumberOfClusters">A pointer to a variable that
251        /// receives the total number of clusters on the disk that are available
252        /// to the user who is associated with the calling thread.
253        ///
254        /// If per-user disk quotas are in use, this value may be less than the
255        /// total number of clusters on the disk.</param>
256        /// <returns>If the function succeeds, the return value is true. To get
257        /// extended error information, call Marshal.GetLastWin32Error().</returns>
258        [DllImport("Kernel32.dll", SetLastError = true)]
259        [return: MarshalAs(UnmanagedType.Bool)]
260        internal static extern bool GetDiskFreeSpace(
261            [MarshalAs(UnmanagedType.LPStr)] string lpRootPathName,
262            out UInt32 lpSectorsPerCluster, out UInt32 lpBytesPerSector,
263            out UInt32 lpNumberOfFreeClusters, out UInt32 lpTotalNumberOfClusters);
264
265        [DllImport("Kernel32.dll", SetLastError = true)]
266        [return: MarshalAs(UnmanagedType.Bool)]
267        internal static extern bool GetDiskFreeSpaceEx(
268            [MarshalAs(UnmanagedType.LPStr)] string lpDirectoryName,
269            out UInt64 lpFreeBytesAvailable,
270            out UInt64 lpTotalNumberOfBytes,
271            out UInt64 lpTotalNumberOfFreeBytes);
272
273        /// <summary>
274        /// Retrieves the name of a volume on a computer. FindFirstVolume is used
275        /// to begin scanning the volumes of a computer.
276        /// </summary>
277        /// <param name="lpszVolumeName">A pointer to a buffer that receives a
278        /// null-terminated string that specifies the unique volume name of the
279        /// first volume found.</param>
280        /// <param name="cchBufferLength">The length of the buffer to receive the
281        /// name, in TCHARs.</param>
282        /// <returns>If the function succeeds, the return value is a search handle
283        /// used in a subsequent call to the FindNextVolume and FindVolumeClose
284        /// functions.
285        ///
286        /// If the function fails to find any volumes, the return value is the
287        /// INVALID_HANDLE_VALUE error code. To get extended error information,
288        /// call GetLastError.</returns>
289        [DllImport("Kernel32.dll", SetLastError = true, EntryPoint = "FindFirstVolumeW")]
290        internal static extern SafeFileHandle FindFirstVolume(
291            IntPtr lpszVolumeName,
292            uint cchBufferLength);
293
294        /// <summary>
295        /// Continues a volume search started by a call to the FindFirstVolume
296        /// function. FindNextVolume finds one volume per call.
297        /// </summary>
298        /// <param name="hFindVolume">The volume search handle returned by a previous
299        /// call to the FindFirstVolume function.</param>
300        /// <param name="lpszVolumeName">A pointer to a string that receives the
301        /// unique volume name found.</param>
302        /// <param name="cchBufferLength">The length of the buffer that receives
303        /// the name, in TCHARs.</param>
304        /// <returns>If the function succeeds, the return value is nonzero.
305        ///
306        /// If the function fails, the return value is zero. To get extended error
307        /// information, call GetLastError. If no matching files can be found, the
308        /// GetLastError function returns the ERROR_NO_MORE_FILES error code. In
309        /// that case, close the search with the FindVolumeClose function.</returns>
310        [DllImport("Kernel32.dll", SetLastError = true, EntryPoint = "FindNextVolumeW")]
311        [return: MarshalAs(UnmanagedType.Bool)]
312        internal static extern bool FindNextVolume(SafeHandle hFindVolume,
313            IntPtr lpszVolumeName, uint cchBufferLength);
314
315        /// <summary>
316        /// Closes the specified volume search handle. The FindFirstVolume and
317        /// FindNextVolume functions use this search handle to locate volumes.
318        /// </summary>
319        /// <param name="hFindVolume">The volume search handle to be closed. This
320        /// handle must have been previously opened by the FindFirstVolume function.</param>
321        /// <returns>If the function succeeds, the return value is nonzero.
322        ///
323        /// If the function fails, the return value is zero. To get extended error
324        /// information, call GetLastError.</returns>
325        [DllImport("Kernel32.dll", SetLastError = true)]
326        [return: MarshalAs(UnmanagedType.Bool)]
327        internal static extern bool FindVolumeClose(SafeHandle hFindVolume);
328
329        /// <summary>
330        /// Retrieves the name of a volume mount point on the specified volume.
331        /// FindFirstVolumeMountPoint is used to begin scanning the volume mount
332        /// points on a volume.
333        /// </summary>
334        /// <param name="lpszRootPathName">The unique volume name of the volume
335        /// to scan for volume mount points. A trailing backslash is required.</param>
336        /// <param name="lpszVolumeMountPoint">A pointer to a buffer that receives
337        /// the name of the first volume mount point found.</param>
338        /// <param name="cchBufferLength">The length of the buffer that receives
339        /// the volume mount point name, in TCHARs.</param>
340        /// <returns>If the function succeeds, the return value is a search handle
341        /// used in a subsequent call to the FindNextVolumeMountPoint and
342        /// FindVolumeMountPointClose functions.
343        ///
344        /// If the function fails to find a volume mount point on the volume, the
345        /// return value is the INVALID_HANDLE_VALUE error code. To get extended
346        /// error information, call GetLastError.</returns>
347        [DllImport("Kernel32.dll", SetLastError = true, EntryPoint = "FindFirstVolumeMountPointW")]
348        internal static extern SafeFileHandle FindFirstVolumeMountPoint(
349            [MarshalAs(UnmanagedType.LPWStr)] string lpszRootPathName,
350            IntPtr lpszVolumeMountPoint,
351            uint cchBufferLength);
352
353        /// <summary>
354        /// Continues a volume mount point search started by a call to the
355        /// FindFirstVolumeMountPoint function. FindNextVolumeMountPoint finds one
356        /// volume mount point per call.
357        /// </summary>
358        /// <param name="hFindVolumeMountPoint">A mount-point search handle returned
359        /// by a previous call to the FindFirstVolumeMountPoint function.</param>
360        /// <param name="lpszVolumeMountPoint">A pointer to a buffer that receives
361        /// the name of the volume mount point found.</param>
362        /// <param name="cchBufferLength">The length of the buffer that receives
363        /// the names, in TCHARs.</param>
364        /// <returns>If the function succeeds, the return value is nonzero.
365        ///
366        /// If the function fails, the return value is zero. To get extended error
367        /// information, call GetLastError. If no matching files can be found, the
368        /// GetLastError function returns the ERROR_NO_MORE_FILES error code. In
369        /// that case, close the search with the FindVolumeMountPointClose function.</returns>
370        [DllImport("Kernel32.dll", SetLastError = true, EntryPoint = "FindNextVolumeMountPointW")]
371        [return: MarshalAs(UnmanagedType.Bool)]
372        internal static extern bool FindNextVolumeMountPoint(SafeHandle hFindVolumeMountPoint,
373            IntPtr lpszVolumeMountPoint,
374            uint cchBufferLength);
375
376        /// <summary>
377        /// Closes the specified mount-point search handle. The FindFirstVolumeMountPoint
378        /// and FindNextVolumeMountPoint  functions use this search handle to locate
379        /// volume mount points on a specified volume.
380        /// </summary>
381        /// <param name="hFindVolumeMountPoint">The mount-point search handle to
382        /// be closed. This handle must have been previously opened by the
383        /// FindFirstVolumeMountPoint function.</param>
384        /// <returns>If the function succeeds, the return value is nonzero.
385        ///
386        /// If the function fails, the return value is zero. To get extended error
387        /// information, call GetLastError.</returns>
388        [DllImport("Kernel32.dll", SetLastError = true)]
389        [return: MarshalAs(UnmanagedType.Bool)]
390        internal static extern bool FindVolumeMountPointClose(SafeHandle hFindVolumeMountPoint);
391
392        /// <summary>
393        /// Retrieves a list of path names for the specified volume name.
394        /// </summary>
395        /// <param name="lpszVolumeName">The volume name.</param>
396        /// <param name="lpszVolumePathNames">A pointer to a buffer that receives
397        /// the list of volume path names. The list is an array of null-terminated
398        /// strings terminated by an additional NULL character. If the buffer is
399        /// not large enough to hold the complete list, the buffer holds as much
400        /// of the list as possible.</param>
401        /// <param name="cchBufferLength">The length of the lpszVolumePathNames
402        /// buffer, in TCHARs.</param>
403        /// <param name="lpcchReturnLength">If the call is successful, this parameter
404        /// is the number of TCHARs copied to the lpszVolumePathNames buffer. Otherwise,
405        /// this parameter is the size of the buffer required to hold the complete
406        /// list, in TCHARs.</param>
407        /// <returns></returns>
408        [DllImport("Kernel32.dll", SetLastError = true, EntryPoint = "GetVolumePathNamesForVolumeNameW")]
409        internal static extern bool GetVolumePathNamesForVolumeName(
410            [MarshalAs(UnmanagedType.LPWStr)] string lpszVolumeName,
411            IntPtr lpszVolumePathNames,
412            uint cchBufferLength,
413            out uint lpcchReturnLength);
414
415        /// <summary>
416        /// Determines whether a disk drive is a removable, fixed, CD-ROM, RAM disk,
417        /// or network drive.
418        /// </summary>
419        /// <param name="lpRootPathName">The root directory for the drive.
420        ///
421        /// A trailing backslash is required. If this parameter is NULL, the function
422        /// uses the root of the current directory.</param>
423        /// <returns>The return value specifies the type of drive, which can be
424        /// one of the DriveInfo.DriveType values.</returns>
425        [DllImport("Kernel32.dll", SetLastError = true, EntryPoint = "GetDriveTypeW")]
426        internal static extern uint GetDriveType(
427            [MarshalAs(UnmanagedType.LPWStr)] string lpRootPathName);
428        #endregion
429    }
430}
Note: See TracBrowser for help on using the repository browser.