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

Revision 1867, 24.2 KB checked in by lowjoel, 4 years ago (diff)

Compilo fix.

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