source: branches/eraser6/6.0/Eraser.Util/VolumeInfo.cs @ 1637

Revision 1637, 16.4 KB checked in by lowjoel, 5 years ago (diff)

Fixed exception when the filesystem object being erased is non-existent (i.e. when a task for erasing a removable drive was created, drive was removed and task executed)

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