source: trunk/eraser6/Eraser.Util/VolumeInfo.cs @ 1223

Revision 1223, 16.2 KB checked in by lowjoel, 5 years ago (diff)

Implemented raw volume access.

  • 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;
30using System.Collections.ObjectModel;
31
32namespace Eraser.Util
33{
34    public class VolumeInfo
35    {
36        /// <summary>
37        /// Constructor.
38        /// </summary>
39        /// <param name="volumeId">The ID of the volume, in the form "\\?\Volume{GUID}\"</param>
40        public VolumeInfo(string volumeId)
41        {
42            //Set the volume Id
43            VolumeId = volumeId;
44
45            //Get the paths of the said volume
46            IntPtr pathNamesBuffer = IntPtr.Zero;
47            string pathNames = string.Empty;
48            try
49            {
50                uint currentBufferSize = KernelApi.NativeMethods.MaxPath;
51                uint returnLength = 0;
52                pathNamesBuffer = Marshal.AllocHGlobal((int)(currentBufferSize * sizeof(char)));
53                while (!KernelApi.NativeMethods.GetVolumePathNamesForVolumeName(VolumeId,
54                    pathNamesBuffer, currentBufferSize, out returnLength))
55                {
56                    if (Marshal.GetLastWin32Error() == 234/*ERROR_MORE_DATA*/)
57                    {
58                        Marshal.FreeHGlobal(pathNamesBuffer);
59                        currentBufferSize *= 2;
60                        pathNamesBuffer = Marshal.AllocHGlobal((int)(currentBufferSize * sizeof(char)));
61                    }
62                    else
63                        throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());
64                }
65
66                pathNames = Marshal.PtrToStringUni(pathNamesBuffer, (int)returnLength);
67            }
68            finally
69            {
70                if (pathNamesBuffer != IntPtr.Zero)
71                    Marshal.FreeHGlobal(pathNamesBuffer);
72            }
73
74            //OK, the marshalling is complete. Convert the pathNames string into a list
75            //of strings containing all of the volumes mountpoints; because the
76            //GetVolumePathNamesForVolumeName function returns a convoluted structure
77            //containing the path names.
78            for (int lastIndex = 0, i = 0; i != pathNames.Length; ++i)
79            {
80                if (pathNames[i] == '\0')
81                {
82                    //If there are no mount points for this volume, the string will only
83                    //have one NULL
84                    if (i - lastIndex == 0)
85                        break;
86
87                    mountPoints.Add(pathNames.Substring(lastIndex, i - lastIndex));
88
89                    lastIndex = i + 1;
90                    if (pathNames[lastIndex] == '\0')
91                        break;
92                }
93            }
94
95            //Fill up the remaining members of the structure: file system, label, etc.
96            StringBuilder volumeName = new StringBuilder(KernelApi.NativeMethods.MaxPath * sizeof(char)),
97                fileSystemName = new StringBuilder(KernelApi.NativeMethods.MaxPath * sizeof(char));
98            uint serialNumber, maxComponentLength, filesystemFlags;
99            if (!KernelApi.NativeMethods.GetVolumeInformation(volumeId, volumeName,
100                KernelApi.NativeMethods.MaxPath, out serialNumber, out maxComponentLength,
101                out filesystemFlags, fileSystemName, KernelApi.NativeMethods.MaxPath))
102            {
103                int lastError = Marshal.GetLastWin32Error();
104                switch (lastError)
105                {
106                    case 0:     //ERROR_NO_ERROR
107                    case 21:    //ERROR_NOT_READY
108                    case 87:    //ERROR_INVALID_PARAMETER: when the volume given is not mounted.
109                    case 1005:  //ERROR_UNRECOGNIZED_VOLUME
110                        break;
111
112                    default:
113                        throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());
114                }
115            }
116            else
117            {
118                IsReady = true;
119                VolumeLabel = volumeName.ToString();
120                VolumeFormat = fileSystemName.ToString();
121            }
122        }
123
124        /// <summary>
125        /// Lists all the volumes in the system.
126        /// </summary>
127        /// <returns>Returns a list of volumes representing all volumes present in
128        /// the system.</returns>
129        public static ICollection<VolumeInfo> Volumes
130        {
131            get
132            {
133                List<VolumeInfo> result = new List<VolumeInfo>();
134                StringBuilder nextVolume = new StringBuilder(
135                    KernelApi.NativeMethods.LongPath * sizeof(char));
136                SafeHandle handle = KernelApi.NativeMethods.FindFirstVolume(nextVolume,
137                    KernelApi.NativeMethods.LongPath);
138                if (handle.IsInvalid)
139                    return result;
140
141                //Iterate over the volume mountpoints
142                do
143                    result.Add(new VolumeInfo(nextVolume.ToString()));
144                while (KernelApi.NativeMethods.FindNextVolume(handle, nextVolume,
145                    KernelApi.NativeMethods.LongPath));
146
147                //Close the handle
148                if (Marshal.GetLastWin32Error() == 18 /*ERROR_NO_MORE_FILES*/)
149                    KernelApi.NativeMethods.FindVolumeClose(handle);
150
151                return result.AsReadOnly();
152            }
153        }
154
155        /// <summary>
156        /// Creates a Volume object from its mountpoint.
157        /// </summary>
158        /// <param name="mountpoint">The path to the mountpoint.</param>
159        /// <returns>The volume object if such a volume exists, or an exception
160        /// is thrown.</returns>
161        public static VolumeInfo FromMountpoint(string mountpoint)
162        {
163            DirectoryInfo mountpointDir = new DirectoryInfo(mountpoint);
164            StringBuilder volumeID = new StringBuilder(50 * sizeof(char));
165
166            do
167            {
168                string currentDir = mountpointDir.FullName;
169                if (currentDir.Length > 0 && currentDir[currentDir.Length - 1] != '\\')
170                    currentDir += '\\';
171                if (KernelApi.NativeMethods.GetVolumeNameForVolumeMountPoint(currentDir,
172                    volumeID, 50))
173                {
174                    return new VolumeInfo(volumeID.ToString());
175                }
176                else
177                {
178                    switch (Marshal.GetLastWin32Error())
179                    {
180                        case 1: //ERROR_INVALID_FUNCTION
181                        case 2: //ERROR_FILE_NOT_FOUND
182                        case 3: //ERROR_PATH_NOT_FOUND
183                        case 4390: //ERROR_NOT_A_REPARSE_POINT
184                            break;
185                        default:
186                            throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());
187                    }
188                }
189
190                mountpointDir = mountpointDir.Parent;
191            }
192            while (mountpointDir != null);
193
194            throw Marshal.GetExceptionForHR(KernelApi.GetHRForWin32Error(
195                4390 /*ERROR_NOT_A_REPARSE_POINT*/));
196        }
197
198        /// <summary>
199        /// Returns the volume identifier as would be returned from FindFirstVolume.
200        /// </summary>
201        public string VolumeId { get; private set; }
202
203        /// <summary>
204        /// Gets or sets the volume label of a drive.
205        /// </summary>
206        public string VolumeLabel { get; private set; }
207
208        /// <summary>
209        /// Gets the name of the file system, such as NTFS or FAT32.
210        /// </summary>
211        public string VolumeFormat { get; private set; }
212
213        /// <summary>
214        /// Gets the drive type; returns one of the System.IO.DriveType values.
215        /// </summary>
216        public DriveType VolumeType
217        {
218            get
219            {
220                return (DriveType)KernelApi.NativeMethods.GetDriveType(VolumeId);
221            }
222        }
223
224        /// <summary>
225        /// Determines the cluster size of the current volume.
226        /// </summary>
227        public int ClusterSize
228        {
229            get
230            {
231                uint clusterSize, sectorSize, freeClusters, totalClusters;
232                if (KernelApi.NativeMethods.GetDiskFreeSpace(VolumeId, out clusterSize,
233                    out sectorSize, out freeClusters, out totalClusters))
234                {
235                    return (int)(clusterSize * sectorSize);
236                }
237
238                throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());
239            }
240        }
241
242        /// <summary>
243        /// Determines the sector size of the current volume.
244        /// </summary>
245        public int SectorSize
246        {
247            get
248            {
249                uint clusterSize, sectorSize, freeClusters, totalClusters;
250                if (KernelApi.NativeMethods.GetDiskFreeSpace(VolumeId, out clusterSize,
251                    out sectorSize, out freeClusters, out totalClusters))
252                {
253                    return (int)sectorSize;
254                }
255
256                throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());
257            }
258        }
259
260        /// <summary>
261        /// Checks if the current user has disk quotas on the current volume.
262        /// </summary>
263        public bool HasQuota
264        {
265            get
266            {
267                ulong freeBytesAvailable, totalNumberOfBytes, totalNumberOfFreeBytes;
268                if (KernelApi.NativeMethods.GetDiskFreeSpaceEx(VolumeId, out freeBytesAvailable,
269                    out totalNumberOfBytes, out totalNumberOfFreeBytes))
270                {
271                    return totalNumberOfFreeBytes != freeBytesAvailable;
272                }
273                else if (Marshal.GetLastWin32Error() == 21 /*ERROR_NOT_READY*/)
274                {
275                    //For the lack of more appropriate responses.
276                    return false;
277                }
278
279                throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());
280            }
281        }
282
283        /// <summary>
284        /// Gets a value indicating whether a drive is ready.
285        /// </summary>
286        public bool IsReady { get; private set; }
287
288        /// <summary>
289        /// Gets the total amount of free space available on a drive.
290        /// </summary>
291        public long TotalFreeSpace
292        {
293            get
294            {
295                ulong result, dummy;
296                if (KernelApi.NativeMethods.GetDiskFreeSpaceEx(VolumeId, out dummy,
297                    out dummy, out result))
298                {
299                    return (long)result;
300                }
301
302                throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());
303            }
304        }
305       
306        /// <summary>
307        /// Gets the total size of storage space on a drive.
308        /// </summary>
309        public long TotalSize
310        {
311            get
312            {
313                ulong result, dummy;
314                if (KernelApi.NativeMethods.GetDiskFreeSpaceEx(VolumeId, out dummy,
315                    out result, out dummy))
316                {
317                    return (long)result;
318                }
319
320                throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());
321            }
322        }
323
324        /// <summary>
325        /// Indicates the amount of available free space on a drive.
326        /// </summary>
327        public long AvailableFreeSpace
328        {
329            get
330            {
331                ulong result, dummy;
332                if (KernelApi.NativeMethods.GetDiskFreeSpaceEx(VolumeId, out result,
333                    out dummy, out dummy))
334                {
335                    return (long)result;
336                }
337
338                throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());
339            }
340        }
341
342        /// <summary>
343        /// Retrieves all mountpoints in the current volume, if the current volume
344        /// contains volume mountpoints.
345        /// </summary>
346        public ICollection<VolumeInfo> MountedVolumes
347        {
348            get
349            {
350                List<VolumeInfo> result = new List<VolumeInfo>();
351                StringBuilder nextMountpoint = new StringBuilder(
352                    KernelApi.NativeMethods.LongPath * sizeof(char));
353
354                SafeHandle handle = KernelApi.NativeMethods.FindFirstVolumeMountPoint(VolumeId,
355                    nextMountpoint, KernelApi.NativeMethods.LongPath);
356                if (handle.IsInvalid)
357                    return result;
358
359                //Iterate over the volume mountpoints
360                while (KernelApi.NativeMethods.FindNextVolumeMountPoint(handle,
361                    nextMountpoint, KernelApi.NativeMethods.LongPath))
362                {
363                    result.Add(new VolumeInfo(nextMountpoint.ToString()));
364                }
365
366                //Close the handle
367                if (Marshal.GetLastWin32Error() == 18 /*ERROR_NO_MORE_FILES*/)
368                    KernelApi.NativeMethods.FindVolumeMountPointClose(handle);
369
370                return result.AsReadOnly();
371            }
372        }
373
374        /// <summary>
375        /// The various mountpoints to the root of the volume. This list contains
376        /// paths which may be a drive or a mountpoint. Every string includes the
377        /// trailing backslash.
378        /// </summary>
379        public ReadOnlyCollection<string> MountPoints
380        {
381            get
382            {
383                return mountPoints.AsReadOnly();
384            }
385        }
386
387        /// <summary>
388        /// Gets whether the current volume is mounted at any place.
389        /// </summary>
390        public bool IsMounted
391        {
392            get { return MountPoints.Count != 0; }
393        }
394
395        /// <summary>
396        /// Opens a file with read, write, or read/write access.
397        /// </summary>
398        /// <param name="access">A System.IO.FileAccess constant specifying whether
399        /// to open the file with Read, Write, or ReadWrite file access.</param>
400        /// <returns>A System.IO.FileStream object opened in the specified mode
401        /// and access, unshared, and no special file options.</returns>
402        public FileStream Open(FileAccess access)
403        {
404            return Open(access, FileShare.None, FileOptions.None);
405        }
406
407        /// <summary>
408        /// Opens a file with read, write, or read/write access and the specified
409        /// sharing option.
410        /// </summary>
411        /// <param name="access">A System.IO.FileAccess constant specifying whether
412        /// to open the file with Read, Write, or ReadWrite file access.</param>
413        /// <param name="share">A System.IO.FileShare constant specifying the type
414        /// of access other FileStream objects have to this file.</param>
415        /// <returns>A System.IO.FileStream object opened with the specified mode,
416        /// access, sharing options, and no special file options.</returns>
417        public FileStream Open(FileAccess access, FileShare share)
418        {
419            return Open(access, share, FileOptions.None);
420        }
421
422        /// <summary>
423        /// Opens a file with read, write, or read/write access, the specified
424        /// sharing option, and other advanced options.
425        /// </summary>
426        /// <param name="mode">A System.IO.FileMode constant specifying the mode
427        /// (for example, Open or Append) in which to open the file.</param>
428        /// <param name="access">A System.IO.FileAccess constant specifying whether
429        /// to open the file with Read, Write, or ReadWrite file access.</param>
430        /// <param name="share">A System.IO.FileShare constant specifying the type
431        /// of access other FileStream objects have to this file.</param>
432        /// <param name="options">The System.IO.FileOptions constant specifying
433        /// the advanced file options to use when opening the file.</param>
434        /// <returns>A System.IO.FileStream object opened with the specified mode,
435        /// access, sharing options, and special file options.</returns>
436        public FileStream Open(FileAccess access, FileShare share, FileOptions options)
437        {
438            SafeFileHandle handle = OpenHandle(access, share, options);
439
440            //Check that the handle is valid
441            if (handle.IsInvalid)
442                throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());
443
444            //Return the FileStream
445            return new FileStream(handle, access);
446        }
447
448        private SafeFileHandle OpenHandle(FileAccess access, FileShare share, FileOptions options)
449        {
450            //Access mode
451            uint iAccess = 0;
452            switch (access)
453            {
454                case FileAccess.Read:
455                    iAccess = KernelApi.NativeMethods.GENERIC_READ;
456                    break;
457                case FileAccess.ReadWrite:
458                    iAccess = KernelApi.NativeMethods.GENERIC_READ |
459                        KernelApi.NativeMethods.GENERIC_WRITE;
460                    break;
461                case FileAccess.Write:
462                    iAccess = KernelApi.NativeMethods.GENERIC_WRITE;
463                    break;
464            }
465
466            //Sharing mode
467            if ((share & FileShare.Inheritable) != 0)
468                throw new NotSupportedException("Inheritable handles are not supported.");
469
470            //Advanced options
471            if ((options & FileOptions.Asynchronous) != 0)
472                throw new NotSupportedException("Asynchronous handles are not implemented.");
473
474            //Create the handle
475            string openPath = VolumeId;
476            if (openPath.Length > 0 && openPath[openPath.Length - 1] == '\\')
477                openPath = openPath.Remove(openPath.Length - 1);
478            SafeFileHandle result = KernelApi.NativeMethods.CreateFile(openPath, iAccess,
479                (uint)share, IntPtr.Zero, (uint)FileMode.Open, (uint)options, IntPtr.Zero);
480            if (result.IsInvalid)
481                throw KernelApi.GetExceptionForWin32Error(Marshal.GetLastWin32Error());
482
483            return result;
484        }
485
486        public VolumeLock LockVolume(FileStream stream)
487        {
488            return new VolumeLock(stream.SafeFileHandle);
489        }
490
491        private List<string> mountPoints = new List<string>();
492    }
493
494    public class VolumeLock : IDisposable
495    {
496        internal VolumeLock(SafeFileHandle handle)
497        {
498            uint result = 0;
499            for (int i = 0; !KernelApi.NativeMethods.DeviceIoControl(handle,
500                    KernelApi.NativeMethods.FSCTL_LOCK_VOLUME, IntPtr.Zero, 0, IntPtr.Zero,
501                    0, out result, IntPtr.Zero); ++i)
502            {
503                if (i > 100)
504                    throw new IOException("Could not lock volume.");
505                System.Threading.Thread.Sleep(100);
506            }
507
508            Handle = handle;
509        }
510
511        ~VolumeLock()
512        {
513            Dispose(false);
514        }
515
516        public void Dispose()
517        {
518            Dispose(true);
519        }
520
521        void Dispose(bool disposing)
522        {
523            if (disposing)
524                GC.SuppressFinalize(this);
525
526            uint result = 0;
527            if (!KernelApi.NativeMethods.DeviceIoControl(Handle, KernelApi.NativeMethods.FSCTL_UNLOCK_VOLUME,
528                IntPtr.Zero, 0, IntPtr.Zero, 0, out result, IntPtr.Zero))
529            {
530                throw new IOException("Could not unlock volume.");
531            }
532        }
533
534        private SafeFileHandle Handle;
535    }
536}
Note: See TracBrowser for help on using the repository browser.