source: branches/eraser6/CodeReview/Eraser.Util/VolumeInfo.cs @ 1551

Revision 1551, 15.7 KB checked in by lowjoel, 5 years ago (diff)

Replace all ERROR_* constants with the Win32ErrorCodes class (to be more descriptive.) Addresses #284: Eraser.Util rewrite

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