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

Revision 1360, 16.2 KB checked in by lowjoel, 4 years ago (diff)

Eraser's still under development, so update the copyright notice.

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