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

Revision 2149, 24.5 KB checked in by lowjoel, 4 years ago (diff)

Modified all dependent code to compile after the change in the previous revision.

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