source: trunk/eraser/Eraser.Util/VolumeInfo.cs @ 2189

Revision 2189, 25.5 KB checked in by lowjoel, 4 years ago (diff)
  • Define a VolumeStream? class which is a FileStream? customised for raw volume access. This results in the VolumeLock? class being merged into the VolumeStream? class, automatically handling locking and unlocking of volumes.
  • Change all internal OpenHandle? functions to private, since they should not be callable from outside the class.
  • Perform error checking in OpenHandle? so that even callers of OpenHandle? gets error checking.
  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1/*
2 * $Id$
3 * Copyright 2008-2010 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.Linq;
24using System.Collections.Generic;
25using System.Text;
26
27using System.Runtime.InteropServices;
28using System.ComponentModel;
29using System.IO;
30using Microsoft.Win32.SafeHandles;
31using System.Collections.ObjectModel;
32
33namespace Eraser.Util
34{
35    public class VolumeInfo
36    {
37        /// <summary>
38        /// Constructor.
39        /// </summary>
40        /// <param name="volumeId">The ID of the volume, either in the form
41        /// "\\?\Volume{GUID}\" or as a valid UNC path.</param>
42        public VolumeInfo(string volumeId)
43        {
44            //We only accept UNC paths as well as volume identifiers.
45            if (!(volumeId.StartsWith("\\\\?\\") || volumeId.StartsWith("\\\\")))
46                throw new ArgumentException("The volumeId parameter only accepts volume GUID " +
47                    "and UNC paths", "volumeId");
48
49            //Verify that the path ends with a trailing backslash
50            if (!volumeId.EndsWith("\\"))
51                throw new ArgumentException("The volumeId parameter must end with a trailing " +
52                    "backslash.", "volumeId");
53
54            //Set the volume ID
55            VolumeId = volumeId;
56
57            //Fill up the remaining members of the structure: file system, label, etc.
58            StringBuilder volumeName = new StringBuilder(NativeMethods.MaxPath * sizeof(char)),
59                fileSystemName = new StringBuilder(NativeMethods.MaxPath * sizeof(char));
60            uint serialNumber, maxComponentLength, filesystemFlags;
61            if (NativeMethods.GetVolumeInformation(volumeId, volumeName, NativeMethods.MaxPath,
62                out serialNumber, out maxComponentLength, out filesystemFlags, fileSystemName,
63                NativeMethods.MaxPath))
64            {
65                IsReady = true;
66            }
67
68            //If GetVolumeInformation returns zero some of the information may
69            //have been stored, so we just try to extract it.
70            VolumeLabel = volumeName.Length == 0 ? null : volumeName.ToString();
71            VolumeFormat = fileSystemName.Length == 0 ? null : fileSystemName.ToString();
72
73            //Determine whether it is FAT12 or FAT16
74            if (VolumeFormat == "FAT")
75            {
76                uint clusterSize, sectorSize, freeClusters, totalClusters;
77                if (NativeMethods.GetDiskFreeSpace(VolumeId, out clusterSize,
78                    out sectorSize, out freeClusters, out totalClusters))
79                {
80                    if (totalClusters <= 0xFF0)
81                        VolumeFormat += "12";
82                    else
83                        VolumeFormat += "16";
84                }
85            }
86        }
87
88        /// <summary>
89        /// Gets the mountpoints associated with the current volume.
90        /// </summary>
91        /// <returns>A list of volume mount points for the current volume.</returns>
92        private List<string> GetLocalVolumeMountPoints()
93        {
94            return new List<string>(NativeMethods.GetVolumePathNamesForVolumeName(VolumeId));
95        }
96
97        /// <summary>
98        /// Gets the mountpoints associated with the network share.
99        /// </summary>
100        /// <returns>A list of network mount points for the given network share.</returns>
101        private List<string> GetNetworkMountPoints()
102        {
103            List<string> result = new List<string>();
104            foreach (KeyValuePair<string, string> mountpoint in GetNetworkDrivesInternal())
105                if (mountpoint.Value == VolumeId)
106                    result.Add(mountpoint.Key);
107
108            return result;
109        }
110
111        /// <summary>
112        /// Lists all the volumes in the system.
113        /// </summary>
114        /// <returns>Returns a list of volumes representing all volumes present in
115        /// the system.</returns>
116        public static IList<VolumeInfo> Volumes
117        {
118            get
119            {
120                List<VolumeInfo> result = new List<VolumeInfo>();
121                StringBuilder nextVolume = new StringBuilder(NativeMethods.LongPath * sizeof(char));
122                SafeHandle handle = NativeMethods.FindFirstVolume(nextVolume, NativeMethods.LongPath);
123                if (handle.IsInvalid)
124                    return result;
125
126                try
127                {
128                    //Iterate over the volume mountpoints
129                    do
130                        result.Add(new VolumeInfo(nextVolume.ToString()));
131                    while (NativeMethods.FindNextVolume(handle, nextVolume, NativeMethods.LongPath));
132                }
133                finally
134                {
135                    //Close the handle
136                    NativeMethods.FindVolumeClose(handle);
137                }
138
139                return result.AsReadOnly();
140            }
141        }
142
143        /// <summary>
144        /// Lists all mounted network drives on the current computer.
145        /// </summary>
146        public static IList<VolumeInfo> NetworkDrives
147        {
148            get
149            {
150                Dictionary<string, string> localToRemote = GetNetworkDrivesInternal();
151                Dictionary<string, string> remoteToLocal = new Dictionary<string, string>();
152
153                //Flip the dictionary to be indexed by value so we can map UNC paths to
154                //drive letters/mount points.
155                foreach (KeyValuePair<string, string> mountpoint in localToRemote)
156                {
157                    //If there are no UNC path for this current mount point, we just add it.
158                    if (!remoteToLocal.ContainsKey(mountpoint.Value))
159                        remoteToLocal.Add(mountpoint.Value, mountpoint.Key);
160
161                    //Otherwise, we try to maintain the shortest path.
162                    else if (remoteToLocal[mountpoint.Value].Length > mountpoint.Key.Length)
163                        remoteToLocal[mountpoint.Value] = mountpoint.Key;
164                }
165
166                //Return the list of UNC paths mounted.
167                List<VolumeInfo> result = new List<VolumeInfo>();
168                foreach (string uncPath in remoteToLocal.Keys)
169                    result.Add(new VolumeInfo(uncPath));
170
171                return result.AsReadOnly();
172            }
173        }
174
175        /// <summary>
176        /// Lists all mounted network drives on the current computer. The key is
177        /// the local path, the value is the remote path.
178        /// </summary>
179        private static Dictionary<string, string> GetNetworkDrivesInternal()
180        {
181            Dictionary<string, string> result = new Dictionary<string, string>();
182
183            //Open an enumeration handle to list mount points.
184            IntPtr enumHandle;
185            uint errorCode = NativeMethods.WNetOpenEnum(NativeMethods.RESOURCE_CONNECTED,
186                NativeMethods.RESOURCETYPE_DISK, 0, IntPtr.Zero, out enumHandle);
187            if (errorCode != Win32ErrorCode.Success)
188                throw Win32ErrorCode.GetExceptionForWin32Error(Marshal.GetLastWin32Error());
189
190            try
191            {
192                int resultBufferCount = 32;
193                int resultBufferSize = resultBufferCount *
194                    Marshal.SizeOf(typeof(NativeMethods.NETRESOURCE));
195                IntPtr resultBuffer = Marshal.AllocHGlobal(resultBufferSize);
196
197                try
198                {
199                    for ( ; ; )
200                    {
201                        uint resultBufferStored = (uint)resultBufferCount;
202                        uint resultBufferRequiredSize = (uint)resultBufferSize;
203                        errorCode = NativeMethods.WNetEnumResource(enumHandle,
204                            ref resultBufferStored, resultBuffer,
205                            ref resultBufferRequiredSize);
206
207                        if (errorCode == Win32ErrorCode.NoMoreItems)
208                            break;
209                        else if (errorCode != Win32ErrorCode.Success)
210                            throw new Win32Exception((int)errorCode);
211
212                        unsafe
213                        {
214                            //Marshal the memory block to managed structures.
215                            byte* pointer = (byte*)resultBuffer.ToPointer();
216
217                            for (uint i = 0; i < resultBufferStored;
218                                ++i, pointer += Marshal.SizeOf(typeof(NativeMethods.NETRESOURCE)))
219                            {
220                                NativeMethods.NETRESOURCE resource =
221                                    (NativeMethods.NETRESOURCE)Marshal.PtrToStructure(
222                                        (IntPtr)pointer, typeof(NativeMethods.NETRESOURCE));
223
224                                //Skip all UNC paths without a local mount point
225                                if (resource.lpLocalName == null)
226                                    continue;
227
228                                //Ensure that the path in the resource structure ends with a trailing
229                                //backslash as out volume ID ends with one.
230                                if (string.IsNullOrEmpty(resource.lpRemoteName))
231                                    continue;
232                                if (resource.lpRemoteName[resource.lpRemoteName.Length - 1] != '\\')
233                                    resource.lpRemoteName += '\\';
234                                result.Add(resource.lpLocalName, resource.lpRemoteName);
235                            }
236                        }
237                    }
238                }
239                finally
240                {
241                    Marshal.FreeHGlobal(resultBuffer);
242                }
243            }
244            finally
245            {
246                NativeMethods.WNetCloseEnum(enumHandle);
247            }
248
249            return result;
250        }
251
252        /// <summary>
253        /// Creates a Volume object from its mountpoint.
254        /// </summary>
255        /// <param name="mountPoint">The path to the mountpoint.</param>
256        /// <returns>The volume object if such a volume exists, or an exception
257        /// is thrown.</returns>
258        public static VolumeInfo FromMountPoint(string mountPoint)
259        {
260            //Verify that the mountpoint given exists; if it doesn't we'll raise
261            //a DirectoryNotFound exception.
262            DirectoryInfo mountpointDir = new DirectoryInfo(mountPoint);
263            if (!mountpointDir.Exists)
264                throw new DirectoryNotFoundException();
265
266            do
267            {
268                //Ensure that the current path has a trailing backslash
269                string currentDir = mountpointDir.FullName;
270                if (currentDir.Length > 0 && currentDir[currentDir.Length - 1] != '\\')
271                    currentDir += '\\';
272
273                //The path cannot be empty.
274                if (string.IsNullOrEmpty(currentDir))
275                    throw new DirectoryNotFoundException();
276
277                //Get the type of the drive
278                DriveType driveType = (DriveType)NativeMethods.GetDriveType(currentDir);
279
280                //We do different things for different kinds of drives. Network drives
281                //will need us to resolve the drive to a UNC path. Local drives will
282                //be resolved to a volume GUID
283                StringBuilder volumeID = new StringBuilder(NativeMethods.MaxPath);
284                if (driveType == DriveType.Network)
285                {
286                    //If the current directory is a UNC path, then return the VolumeInfo instance
287                    //directly
288                    if (currentDir.Substring(0, 2) == "\\\\" && currentDir.IndexOf('\\', 2) != -1)
289                        return new VolumeInfo(currentDir);
290
291                    //Otherwise, resolve the mountpoint to a UNC path
292                    uint bufferCapacity = (uint)volumeID.Capacity;
293                    uint errorCode = NativeMethods.WNetGetConnection(
294                        currentDir.Substring(0, currentDir.Length - 1),
295                        volumeID, ref bufferCapacity);
296
297                    switch (errorCode)
298                    {
299                        case Win32ErrorCode.Success:
300                            return new VolumeInfo(volumeID.ToString() + '\\');
301
302                        case Win32ErrorCode.BadDevice: //Path is not a network share
303                            break;
304
305                        default:
306                            throw new Win32Exception((int)errorCode);
307                    }
308                }
309                else
310                {
311                    if (!NativeMethods.GetVolumeNameForVolumeMountPoint(currentDir, volumeID, 50))
312                    {
313                        int errorCode = Marshal.GetLastWin32Error();
314                        switch (errorCode)
315                        {
316                            case Win32ErrorCode.InvalidFunction:
317                            case Win32ErrorCode.FileNotFound:
318                            case Win32ErrorCode.PathNotFound:
319                            case Win32ErrorCode.NotAReparsePoint:
320                                break;
321                            default:
322                                throw Win32ErrorCode.GetExceptionForWin32Error(
323                                    Marshal.GetLastWin32Error());
324                        }
325                    }
326                    else
327                    {
328                        return new VolumeInfo(volumeID.ToString());
329                    }
330                }
331
332                mountpointDir = mountpointDir.Parent;
333            }
334            while (mountpointDir != null);
335
336            throw Win32ErrorCode.GetExceptionForWin32Error(Win32ErrorCode.NotAReparsePoint);
337        }
338
339        /// <summary>
340        /// Returns the volume identifier as would be returned from FindFirstVolume.
341        /// </summary>
342        public string VolumeId { get; private set; }
343
344        /// <summary>
345        /// Gets or sets the volume label of a drive.
346        /// </summary>
347        public string VolumeLabel { get; private set; }
348
349        /// <summary>
350        /// Gets the name of the file system, such as NTFS or FAT32.
351        /// </summary>
352        public string VolumeFormat { get; private set; }
353
354        /// <summary>
355        /// Gets the drive type; returns one of the System.IO.DriveType values.
356        /// </summary>
357        public DriveType VolumeType
358        {
359            get
360            {
361                return (DriveType)NativeMethods.GetDriveType(VolumeId);
362            }
363        }
364
365        /// <summary>
366        /// Determines the cluster size of the current volume.
367        /// </summary>
368        public int ClusterSize
369        {
370            get
371            {
372                if (!IsReady)
373                    throw new InvalidOperationException("The volume has not been mounted or is not " +
374                        "currently ready.");
375
376                uint clusterSize, sectorSize, freeClusters, totalClusters;
377                if (NativeMethods.GetDiskFreeSpace(VolumeId, out clusterSize,
378                    out sectorSize, out freeClusters, out totalClusters))
379                {
380                    return (int)(clusterSize * sectorSize);
381                }
382
383                throw Win32ErrorCode.GetExceptionForWin32Error(Marshal.GetLastWin32Error());
384            }
385        }
386
387        /// <summary>
388        /// Determines the sector size of the current volume.
389        /// </summary>
390        public int SectorSize
391        {
392            get
393            {
394                if (!IsReady)
395                    throw new InvalidOperationException("The volume has not been mounted or is not " +
396                        "currently ready.");
397
398                uint clusterSize, sectorSize, freeClusters, totalClusters;
399                if (NativeMethods.GetDiskFreeSpace(VolumeId, out clusterSize,
400                    out sectorSize, out freeClusters, out totalClusters))
401                {
402                    return (int)sectorSize;
403                }
404
405                throw Win32ErrorCode.GetExceptionForWin32Error(Marshal.GetLastWin32Error());
406            }
407        }
408
409        /// <summary>
410        /// Checks if the current user has disk quotas on the current volume.
411        /// </summary>
412        public bool HasQuota
413        {
414            get
415            {
416                ulong freeBytesAvailable, totalNumberOfBytes, totalNumberOfFreeBytes;
417                if (NativeMethods.GetDiskFreeSpaceEx(VolumeId, out freeBytesAvailable,
418                    out totalNumberOfBytes, out totalNumberOfFreeBytes))
419                {
420                    return totalNumberOfFreeBytes != freeBytesAvailable;
421                }
422                else if (Marshal.GetLastWin32Error() == Win32ErrorCode.NotReady)
423                {
424                    //For the lack of more appropriate responses.
425                    return false;
426                }
427
428                throw Win32ErrorCode.GetExceptionForWin32Error(Marshal.GetLastWin32Error());
429            }
430        }
431
432        /// <summary>
433        /// Gets a value indicating whether a drive is ready.
434        /// </summary>
435        public bool IsReady { get; private set; }
436
437        /// <summary>
438        /// Gets the total amount of free space available on a drive.
439        /// </summary>
440        public long TotalFreeSpace
441        {
442            get
443            {
444                if (!IsReady)
445                    throw new InvalidOperationException("The volume has not been mounted or is not " +
446                        "currently ready.");
447
448                ulong result, dummy;
449                if (NativeMethods.GetDiskFreeSpaceEx(VolumeId, out dummy, out dummy, out result))
450                {
451                    return (long)result;
452                }
453
454                throw Win32ErrorCode.GetExceptionForWin32Error(Marshal.GetLastWin32Error());
455            }
456        }
457       
458        /// <summary>
459        /// Gets the total size of storage space on a drive.
460        /// </summary>
461        public long TotalSize
462        {
463            get
464            {
465                ulong result, dummy;
466                if (NativeMethods.GetDiskFreeSpaceEx(VolumeId, out dummy, out result, out dummy))
467                {
468                    return (long)result;
469                }
470
471                //Try the alternative method
472                using (SafeFileHandle handle = OpenHandle(NativeMethods.GENERIC_READ,
473                    FileShare.ReadWrite, FileOptions.None))
474                {
475                    if (handle.IsInvalid)
476                        throw Win32ErrorCode.GetExceptionForWin32Error(Marshal.GetLastWin32Error());
477
478                    long result2;
479                    uint returned = 0;
480                    if (NativeMethods.DeviceIoControl(handle,
481                        NativeMethods.IOCTL_DISK_GET_LENGTH_INFO, IntPtr.Zero, 0, out result2,
482                        out returned, IntPtr.Zero))
483                    {
484                        return result2;
485                    }
486                }
487
488                //Otherwise, throw
489                throw Win32ErrorCode.GetExceptionForWin32Error(Marshal.GetLastWin32Error());
490            }
491        }
492
493        /// <summary>
494        /// Indicates the amount of available free space on a drive.
495        /// </summary>
496        public long AvailableFreeSpace
497        {
498            get
499            {
500                if (!IsReady)
501                    throw new InvalidOperationException("The volume has not been mounted or is not " +
502                        "currently ready.");
503
504                ulong result, dummy;
505                if (NativeMethods.GetDiskFreeSpaceEx(VolumeId, out result, out dummy, out dummy))
506                {
507                    return (long)result;
508                }
509
510                throw Win32ErrorCode.GetExceptionForWin32Error(Marshal.GetLastWin32Error());
511            }
512        }
513
514        /// <summary>
515        /// Retrieves all mountpoints in the current volume, if the current volume
516        /// contains volume mountpoints.
517        /// </summary>
518        public IList<VolumeInfo> MountedVolumes
519        {
520            get
521            {
522                if (!IsReady)
523                    throw new InvalidOperationException("The volume has not been mounted or is not " +
524                        "currently ready.");
525
526                List<VolumeInfo> result = new List<VolumeInfo>();
527                StringBuilder nextMountpoint = new StringBuilder(NativeMethods.LongPath * sizeof(char));
528
529                SafeHandle handle = NativeMethods.FindFirstVolumeMountPoint(VolumeId,
530                    nextMountpoint, NativeMethods.LongPath);
531                if (handle.IsInvalid)
532                    return result;
533
534                try
535                {
536                    //Iterate over the volume mountpoints
537                    while (NativeMethods.FindNextVolumeMountPoint(handle, nextMountpoint,
538                        NativeMethods.LongPath))
539                    {
540                        result.Add(new VolumeInfo(nextMountpoint.ToString()));
541                    }
542                }
543                finally
544                {
545                    //Close the handle
546                    NativeMethods.FindVolumeMountPointClose(handle);
547                }
548
549                return result.AsReadOnly();
550            }
551        }
552
553        /// <summary>
554        /// The various mountpoints to the root of the volume. This list contains
555        /// paths which may be a drive or a mountpoint. Every string includes the
556        /// trailing backslash.
557        /// </summary>
558        public IList<DirectoryInfo> MountPoints
559        {
560            get
561            {
562                List<string> paths = VolumeType == DriveType.Network ?
563                    GetNetworkMountPoints() : GetLocalVolumeMountPoints();
564                return new List<DirectoryInfo>(
565                    paths.Select(x => new DirectoryInfo(x))).AsReadOnly();
566            }
567        }
568
569        /// <summary>
570        /// Gets whether the current volume is mounted at any place.
571        /// </summary>
572        public bool IsMounted
573        {
574            get { return MountPoints.Count != 0; }
575        }
576
577        /// <summary>
578        /// Gets the Physical Drive this Volume is in.
579        /// </summary>
580        public PhysicalDriveInfo PhysicalDrive
581        {
582            get
583            {
584                foreach (PhysicalDriveInfo info in PhysicalDriveInfo.Drives)
585                {
586                    if (info.Volumes.IndexOf(this) != -1)
587                        return info;
588                }
589
590                return null;
591            }
592        }
593
594        /// <summary>
595        /// Opens a file with read, write, or read/write access.
596        /// </summary>
597        /// <param name="access">A System.IO.FileAccess constant specifying whether
598        /// to open the file with Read, Write, or ReadWrite file access.</param>
599        /// <returns>A System.IO.FileStream object opened in the specified mode
600        /// and access, unshared, and no special file options.</returns>
601        public FileStream Open(FileAccess access)
602        {
603            return Open(access, FileShare.None, FileOptions.None);
604        }
605
606        /// <summary>
607        /// Opens a file with read, write, or read/write access and the specified
608        /// sharing option.
609        /// </summary>
610        /// <param name="access">A System.IO.FileAccess constant specifying whether
611        /// to open the file with Read, Write, or ReadWrite file access.</param>
612        /// <param name="share">A System.IO.FileShare constant specifying the type
613        /// of access other FileStream objects have to this file.</param>
614        /// <returns>A System.IO.FileStream object opened with the specified mode,
615        /// access, sharing options, and no special file options.</returns>
616        public FileStream Open(FileAccess access, FileShare share)
617        {
618            return Open(access, share, FileOptions.None);
619        }
620
621        /// <summary>
622        /// Opens a file with read, write, or read/write access, the specified
623        /// sharing option, and other advanced options.
624        /// </summary>
625        /// <param name="mode">A System.IO.FileMode constant specifying the mode
626        /// (for example, Open or Append) in which to open the file.</param>
627        /// <param name="access">A System.IO.FileAccess constant specifying whether
628        /// to open the file with Read, Write, or ReadWrite file access.</param>
629        /// <param name="share">A System.IO.FileShare constant specifying the type
630        /// of access other FileStream objects have to this file.</param>
631        /// <param name="options">The System.IO.FileOptions constant specifying
632        /// the advanced file options to use when opening the file.</param>
633        /// <returns>A System.IO.FileStream object opened with the specified mode,
634        /// access, sharing options, and special file options.</returns>
635        public FileStream Open(FileAccess access, FileShare share, FileOptions options)
636        {
637            return new VolumeStream(this, OpenHandle(access, share, options), access);
638        }
639
640        private SafeFileHandle OpenHandle(FileAccess access, FileShare share, FileOptions options)
641        {
642            //Access mode
643            uint iAccess = 0;
644            switch (access)
645            {
646                case FileAccess.Read:
647                    iAccess = NativeMethods.GENERIC_READ;
648                    break;
649                case FileAccess.ReadWrite:
650                    iAccess = NativeMethods.GENERIC_READ | NativeMethods.GENERIC_WRITE;
651                    break;
652                case FileAccess.Write:
653                    iAccess = NativeMethods.GENERIC_WRITE;
654                    break;
655            }
656
657            return OpenHandle(iAccess, share, options);
658        }
659
660        private SafeFileHandle OpenHandle(uint access, FileShare share, FileOptions options)
661        {
662            //Sharing mode
663            if ((share & FileShare.Inheritable) != 0)
664                throw new NotSupportedException("Inheritable handles are not supported.");
665
666            //Advanced options
667            if ((options & FileOptions.Asynchronous) != 0)
668                throw new NotSupportedException("Asynchronous handles are not implemented.");
669
670            //Create the handle
671            string openPath = VolumeId;
672            if (openPath.Length > 0 && openPath[openPath.Length - 1] == '\\')
673                openPath = openPath.Remove(openPath.Length - 1);
674
675            SafeFileHandle result = NativeMethods.CreateFile(openPath, access, (uint)share,
676                IntPtr.Zero, (uint)FileMode.Open, (uint)options, IntPtr.Zero);
677
678            //Check that the handle is valid
679            if (result.IsInvalid)
680            {
681                int errorCode = Marshal.GetLastWin32Error();
682                result.Close();
683                throw Win32ErrorCode.GetExceptionForWin32Error(errorCode);
684            }
685
686            return result;
687        }
688
689        /// <summary>
690        /// Queries the performance information for the given disk.
691        /// </summary>
692        public DiskPerformanceInfo Performance
693        {
694            get
695            {
696                using (SafeFileHandle handle = OpenHandle(NativeMethods.FILE_READ_ATTRIBUTES,
697                    FileShare.ReadWrite, FileOptions.None))
698                {
699                    //This only works if the user has turned on the disk performance
700                    //counters with 'diskperf -y'. These counters are off by default
701                    NativeMethods.DiskPerformanceInfoInternal result =
702                        new NativeMethods.DiskPerformanceInfoInternal();
703                    uint bytesReturned = 0;
704                    if (NativeMethods.DeviceIoControl(handle, NativeMethods.IOCTL_DISK_PERFORMANCE,
705                        IntPtr.Zero, 0, out result, (uint)Marshal.SizeOf(result),
706                        out bytesReturned, IntPtr.Zero))
707                    {
708                        return new DiskPerformanceInfo(result);
709                    }
710
711                    return null;
712                }
713            }
714        }
715
716        /// <summary>
717        /// Gets the mount point of the volume, or the volume ID if the volume is
718        /// not currently mounted.
719        /// </summary>
720        /// <returns>A string containing the mount point of the volume or the volume
721        /// ID.</returns>
722        public override string ToString()
723        {
724            IList<DirectoryInfo> mountPoints = MountPoints;
725            return mountPoints.Count == 0 ? VolumeId : mountPoints[0].FullName;
726        }
727
728        public override bool Equals(object obj)
729        {
730            VolumeInfo rhs = obj as VolumeInfo;
731            if (rhs == null)
732                return base.Equals(obj);
733
734            return VolumeId == rhs.VolumeId;
735        }
736
737        public override int GetHashCode()
738        {
739            return VolumeId.GetHashCode();
740        }
741    }
742
743    public class VolumeStream : FileStream
744    {
745        internal VolumeStream(VolumeInfo volume, SafeFileHandle handle, FileAccess access)
746            : base(handle, access)
747        {
748            Volume = volume;
749            Access = access;
750
751            if (Access == FileAccess.Write || Access == FileAccess.ReadWrite)
752                LockVolume();
753        }
754
755        protected override void Dispose(bool disposing)
756        {
757            if (Access == FileAccess.Write || Access == FileAccess.ReadWrite)
758                UnlockVolume();
759            base.Dispose(disposing);
760        }
761
762        public override void SetLength(long value)
763        {
764            throw new InvalidOperationException();
765        }
766
767        public override long Length
768        {
769            get
770            {
771                if (IsLocked)
772                    return LengthCache;
773                return Volume.TotalSize;
774            }
775        }
776
777        /// <summary>
778        /// Temporarily stores the length of the disk while the disk is locked.
779        /// </summary>
780        private long LengthCache;
781
782        private void LockVolume()
783        {
784            LengthCache = Length;
785            uint result = 0;
786            for (int i = 0; !NativeMethods.DeviceIoControl(SafeFileHandle,
787                    NativeMethods.FSCTL_LOCK_VOLUME, IntPtr.Zero, 0, IntPtr.Zero,
788                    0, out result, IntPtr.Zero); ++i)
789            {
790                if (i > 100)
791                    throw Win32ErrorCode.GetExceptionForWin32Error(Marshal.GetLastWin32Error());
792                System.Threading.Thread.Sleep(100);
793            }
794
795            IsLocked = true;
796        }
797
798        private void UnlockVolume()
799        {
800            //Flush the contents of the buffer to disk since after we unlock the volume
801            //we can no longer write to the volume.
802            Flush();
803
804            uint result = 0;
805            if (!NativeMethods.DeviceIoControl(SafeFileHandle, NativeMethods.FSCTL_UNLOCK_VOLUME,
806                IntPtr.Zero, 0, IntPtr.Zero, 0, out result, IntPtr.Zero))
807            {
808                throw new IOException("Could not unlock volume.");
809            }
810
811            LengthCache = 0;
812            IsLocked = false;
813        }
814
815        /// <summary>
816        /// Reflects whether the current handle has exclusive access to the volume.
817        /// </summary>
818        private bool IsLocked;
819
820        /// <summary>
821        /// The <see cref="VolumeInfo"/> object this stream is encapsulating.
822        /// </summary>
823        private VolumeInfo Volume;
824
825        /// <summary>
826        /// The access parameter for this stream.
827        /// </summary>
828        private FileAccess Access;
829    }
830
831    public class DiskPerformanceInfo
832    {
833        internal DiskPerformanceInfo(NativeMethods.DiskPerformanceInfoInternal info)
834        {
835            BytesRead = info.BytesRead;
836            BytesWritten = info.BytesWritten;
837            ReadTime = info.ReadTime;
838            WriteTime = info.WriteTime;
839            IdleTime = info.IdleTime;
840            ReadCount = info.ReadCount;
841            WriteCount = info.WriteCount;
842            QueueDepth = info.QueueDepth;
843            SplitCount = info.SplitCount;
844            QueryTime = info.QueryTime;
845            StorageDeviceNumber = info.StorageDeviceNumber;
846            StorageManagerName = info.StorageManagerName;
847        }
848
849        public long BytesRead { get; private set; }
850        public long BytesWritten { get; private set; }
851        public long ReadTime { get; private set; }
852        public long WriteTime { get; private set; }
853        public long IdleTime { get; private set; }
854        public uint ReadCount { get; private set; }
855        public uint WriteCount { get; private set; }
856        public uint QueueDepth { get; private set; }
857        public uint SplitCount { get; private set; }
858        public long QueryTime { get; private set; }
859        public uint StorageDeviceNumber { get; private set; }
860        public string StorageManagerName { get; private set; }
861    }
862}
Note: See TracBrowser for help on using the repository browser.