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

Revision 2140, 24.2 KB checked in by lowjoel, 5 years ago (diff)

Forward port from Eraser 6.0: Check that the Win32 error code is not ERROR_NOT_READY when we query the drive's mount points, as certain drives may not be mounted or have a usable filesystem when queried. Fixes crash in http://bbs.heidi.ie/viewtopic.php?f=2&t=6207.

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