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

Revision 2147, 24.4 KB checked in by lowjoel, 4 years ago (diff)

Forward port from Eraser 6.0: Allow erasing of UNC paths.

  • 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                    //If the current directory is a UNC path, then return the VolumeInfo instance
333                    //directly
334                    if (currentDir.Substring(0, 2) == "\\\\" && currentDir.IndexOf('\\', 2) != -1)
335                        return new VolumeInfo(currentDir);
336
337                    //Otherwise, resolve the mountpoint to a UNC path
338                    uint bufferCapacity = (uint)volumeID.Capacity;
339                    uint errorCode = NativeMethods.WNetGetConnection(
340                        currentDir.Substring(0, currentDir.Length - 1),
341                        volumeID, ref bufferCapacity);
342
343                    switch (errorCode)
344                    {
345                        case Win32ErrorCode.Success:
346                            return new VolumeInfo(volumeID.ToString() + '\\');
347
348                        case Win32ErrorCode.BadDevice: //Path is not a network share
349                            break;
350
351                        default:
352                            throw new Win32Exception((int)errorCode);
353                    }
354                }
355                else
356                {
357                    if (!NativeMethods.GetVolumeNameForVolumeMountPoint(currentDir, volumeID, 50))
358                    {
359                        int errorCode = Marshal.GetLastWin32Error();
360                        switch (errorCode)
361                        {
362                            case Win32ErrorCode.InvalidFunction:
363                            case Win32ErrorCode.FileNotFound:
364                            case Win32ErrorCode.PathNotFound:
365                            case Win32ErrorCode.NotAReparsePoint:
366                                break;
367                            default:
368                                throw Win32ErrorCode.GetExceptionForWin32Error(
369                                    Marshal.GetLastWin32Error());
370                        }
371                    }
372                    else
373                    {
374                        return new VolumeInfo(volumeID.ToString());
375                    }
376                }
377
378                mountpointDir = mountpointDir.Parent;
379            }
380            while (mountpointDir != null);
381
382            throw Win32ErrorCode.GetExceptionForWin32Error(Win32ErrorCode.NotAReparsePoint);
383        }
384
385        /// <summary>
386        /// Returns the volume identifier as would be returned from FindFirstVolume.
387        /// </summary>
388        public string VolumeId { get; private set; }
389
390        /// <summary>
391        /// Gets or sets the volume label of a drive.
392        /// </summary>
393        public string VolumeLabel { get; private set; }
394
395        /// <summary>
396        /// Gets the name of the file system, such as NTFS or FAT32.
397        /// </summary>
398        public string VolumeFormat { get; private set; }
399
400        /// <summary>
401        /// Gets the drive type; returns one of the System.IO.DriveType values.
402        /// </summary>
403        public DriveType VolumeType
404        {
405            get
406            {
407                return (DriveType)NativeMethods.GetDriveType(VolumeId);
408            }
409        }
410
411        /// <summary>
412        /// Determines the cluster size of the current volume.
413        /// </summary>
414        public int ClusterSize
415        {
416            get
417            {
418                uint clusterSize, sectorSize, freeClusters, totalClusters;
419                if (NativeMethods.GetDiskFreeSpace(VolumeId, out clusterSize,
420                    out sectorSize, out freeClusters, out totalClusters))
421                {
422                    return (int)(clusterSize * sectorSize);
423                }
424
425                throw Win32ErrorCode.GetExceptionForWin32Error(Marshal.GetLastWin32Error());
426            }
427        }
428
429        /// <summary>
430        /// Determines the sector size of the current volume.
431        /// </summary>
432        public int SectorSize
433        {
434            get
435            {
436                uint clusterSize, sectorSize, freeClusters, totalClusters;
437                if (NativeMethods.GetDiskFreeSpace(VolumeId, out clusterSize,
438                    out sectorSize, out freeClusters, out totalClusters))
439                {
440                    return (int)sectorSize;
441                }
442
443                throw Win32ErrorCode.GetExceptionForWin32Error(Marshal.GetLastWin32Error());
444            }
445        }
446
447        /// <summary>
448        /// Checks if the current user has disk quotas on the current volume.
449        /// </summary>
450        public bool HasQuota
451        {
452            get
453            {
454                ulong freeBytesAvailable, totalNumberOfBytes, totalNumberOfFreeBytes;
455                if (NativeMethods.GetDiskFreeSpaceEx(VolumeId, out freeBytesAvailable,
456                    out totalNumberOfBytes, out totalNumberOfFreeBytes))
457                {
458                    return totalNumberOfFreeBytes != freeBytesAvailable;
459                }
460                else if (Marshal.GetLastWin32Error() == Win32ErrorCode.NotReady)
461                {
462                    //For the lack of more appropriate responses.
463                    return false;
464                }
465
466                throw Win32ErrorCode.GetExceptionForWin32Error(Marshal.GetLastWin32Error());
467            }
468        }
469
470        /// <summary>
471        /// Gets a value indicating whether a drive is ready.
472        /// </summary>
473        public bool IsReady { get; private set; }
474
475        /// <summary>
476        /// Gets the total amount of free space available on a drive.
477        /// </summary>
478        public long TotalFreeSpace
479        {
480            get
481            {
482                ulong result, dummy;
483                if (NativeMethods.GetDiskFreeSpaceEx(VolumeId, out dummy, out dummy, out result))
484                {
485                    return (long)result;
486                }
487
488                throw Win32ErrorCode.GetExceptionForWin32Error(Marshal.GetLastWin32Error());
489            }
490        }
491       
492        /// <summary>
493        /// Gets the total size of storage space on a drive.
494        /// </summary>
495        public long TotalSize
496        {
497            get
498            {
499                ulong result, dummy;
500                if (NativeMethods.GetDiskFreeSpaceEx(VolumeId, out dummy, out result, out dummy))
501                {
502                    return (long)result;
503                }
504
505                throw Win32ErrorCode.GetExceptionForWin32Error(Marshal.GetLastWin32Error());
506            }
507        }
508
509        /// <summary>
510        /// Indicates the amount of available free space on a drive.
511        /// </summary>
512        public long AvailableFreeSpace
513        {
514            get
515            {
516                ulong result, dummy;
517                if (NativeMethods.GetDiskFreeSpaceEx(VolumeId, out result, out dummy, out dummy))
518                {
519                    return (long)result;
520                }
521
522                throw Win32ErrorCode.GetExceptionForWin32Error(Marshal.GetLastWin32Error());
523            }
524        }
525
526        /// <summary>
527        /// Retrieves all mountpoints in the current volume, if the current volume
528        /// contains volume mountpoints.
529        /// </summary>
530        public IList<VolumeInfo> MountedVolumes
531        {
532            get
533            {
534                List<VolumeInfo> result = new List<VolumeInfo>();
535                StringBuilder nextMountpoint = new StringBuilder(NativeMethods.LongPath * sizeof(char));
536
537                SafeHandle handle = NativeMethods.FindFirstVolumeMountPoint(VolumeId,
538                    nextMountpoint, NativeMethods.LongPath);
539                if (handle.IsInvalid)
540                    return result;
541
542                try
543                {
544                    //Iterate over the volume mountpoints
545                    while (NativeMethods.FindNextVolumeMountPoint(handle, nextMountpoint,
546                        NativeMethods.LongPath))
547                    {
548                        result.Add(new VolumeInfo(nextMountpoint.ToString()));
549                    }
550                }
551                finally
552                {
553                    //Close the handle
554                    NativeMethods.FindVolumeMountPointClose(handle);
555                }
556
557                return result.AsReadOnly();
558            }
559        }
560
561        /// <summary>
562        /// The various mountpoints to the root of the volume. This list contains
563        /// paths which may be a drive or a mountpoint. Every string includes the
564        /// trailing backslash.
565        /// </summary>
566        public ReadOnlyCollection<string> MountPoints
567        {
568            get
569            {
570                return (VolumeType == DriveType.Network ?
571                    GetNetworkMountPoints() : GetLocalVolumeMountPoints()).AsReadOnly();
572            }
573        }
574
575        /// <summary>
576        /// Gets whether the current volume is mounted at any place.
577        /// </summary>
578        public bool IsMounted
579        {
580            get { return MountPoints.Count != 0; }
581        }
582
583        /// <summary>
584        /// Opens a file with read, write, or read/write access.
585        /// </summary>
586        /// <param name="access">A System.IO.FileAccess constant specifying whether
587        /// to open the file with Read, Write, or ReadWrite file access.</param>
588        /// <returns>A System.IO.FileStream object opened in the specified mode
589        /// and access, unshared, and no special file options.</returns>
590        public FileStream Open(FileAccess access)
591        {
592            return Open(access, FileShare.None, FileOptions.None);
593        }
594
595        /// <summary>
596        /// Opens a file with read, write, or read/write access and the specified
597        /// sharing option.
598        /// </summary>
599        /// <param name="access">A System.IO.FileAccess constant specifying whether
600        /// to open the file with Read, Write, or ReadWrite file access.</param>
601        /// <param name="share">A System.IO.FileShare constant specifying the type
602        /// of access other FileStream objects have to this file.</param>
603        /// <returns>A System.IO.FileStream object opened with the specified mode,
604        /// access, sharing options, and no special file options.</returns>
605        public FileStream Open(FileAccess access, FileShare share)
606        {
607            return Open(access, share, FileOptions.None);
608        }
609
610        /// <summary>
611        /// Opens a file with read, write, or read/write access, the specified
612        /// sharing option, and other advanced options.
613        /// </summary>
614        /// <param name="mode">A System.IO.FileMode constant specifying the mode
615        /// (for example, Open or Append) in which to open the file.</param>
616        /// <param name="access">A System.IO.FileAccess constant specifying whether
617        /// to open the file with Read, Write, or ReadWrite file access.</param>
618        /// <param name="share">A System.IO.FileShare constant specifying the type
619        /// of access other FileStream objects have to this file.</param>
620        /// <param name="options">The System.IO.FileOptions constant specifying
621        /// the advanced file options to use when opening the file.</param>
622        /// <returns>A System.IO.FileStream object opened with the specified mode,
623        /// access, sharing options, and special file options.</returns>
624        public FileStream Open(FileAccess access, FileShare share, FileOptions options)
625        {
626            SafeFileHandle handle = OpenHandle(access, share, options);
627
628            //Check that the handle is valid
629            if (handle.IsInvalid)
630            {
631                int errorCode = Marshal.GetLastWin32Error();
632                handle.Close();
633                throw Win32ErrorCode.GetExceptionForWin32Error(errorCode);
634            }
635
636            //Return the FileStream
637            return new FileStream(handle, access);
638        }
639
640        internal SafeFileHandle OpenHandle(FileAccess access, FileShare share, FileOptions options)
641        {
642            //Access mode
643            uint iAccess = 0;
644            switch (access)
645            {
646                case FileAccess.Read:
647                    iAccess = NativeMethods.GENERIC_READ;
648                    break;
649                case FileAccess.ReadWrite:
650                    iAccess = NativeMethods.GENERIC_READ | NativeMethods.GENERIC_WRITE;
651                    break;
652                case FileAccess.Write:
653                    iAccess = NativeMethods.GENERIC_WRITE;
654                    break;
655            }
656
657            return OpenHandle(iAccess, share, options);
658        }
659
660        internal SafeFileHandle OpenHandle(uint access, FileShare share, FileOptions options)
661        {
662            //Sharing mode
663            if ((share & FileShare.Inheritable) != 0)
664                throw new NotSupportedException("Inheritable handles are not supported.");
665
666            //Advanced options
667            if ((options & FileOptions.Asynchronous) != 0)
668                throw new NotSupportedException("Asynchronous handles are not implemented.");
669
670            //Create the handle
671            string openPath = VolumeId;
672            if (openPath.Length > 0 && openPath[openPath.Length - 1] == '\\')
673                openPath = openPath.Remove(openPath.Length - 1);
674            return NativeMethods.CreateFile(openPath, access, (uint)share, IntPtr.Zero,
675                (uint)FileMode.Open, (uint)options, IntPtr.Zero);
676        }
677
678        /// <summary>
679        /// Queries the performance information for the given disk.
680        /// </summary>
681        public DiskPerformanceInfo Performance
682        {
683            get
684            {
685                using (SafeFileHandle handle = OpenHandle(NativeMethods.FILE_READ_ATTRIBUTES,
686                    FileShare.ReadWrite, FileOptions.None))
687                {
688                    //This only works if the user has turned on the disk performance
689                    //counters with 'diskperf -y'. These counters are off by default
690                    NativeMethods.DiskPerformanceInfoInternal result =
691                        new NativeMethods.DiskPerformanceInfoInternal();
692                    uint bytesReturned = 0;
693                    if (NativeMethods.DeviceIoControl(handle, NativeMethods.IOCTL_DISK_PERFORMANCE,
694                        IntPtr.Zero, 0, out result, (uint)Marshal.SizeOf(result),
695                        out bytesReturned, IntPtr.Zero))
696                    {
697                        return new DiskPerformanceInfo(result);
698                    }
699
700                    return null;
701                }
702            }
703        }
704
705        /// <summary>
706        /// Gets the mount point of the volume, or the volume ID if the volume is
707        /// not currently mounted.
708        /// </summary>
709        /// <returns>A string containing the mount point of the volume or the volume
710        /// ID.</returns>
711        public override string ToString()
712        {
713            ReadOnlyCollection<string> mountPoints = MountPoints;
714            return mountPoints.Count == 0 ? VolumeId : mountPoints[0];
715        }
716
717        public VolumeLock LockVolume(FileStream stream)
718        {
719            return new VolumeLock(stream);
720        }
721    }
722
723    public sealed class VolumeLock : IDisposable
724    {
725        internal VolumeLock(FileStream stream)
726        {
727            uint result = 0;
728            for (int i = 0; !NativeMethods.DeviceIoControl(stream.SafeFileHandle,
729                    NativeMethods.FSCTL_LOCK_VOLUME, IntPtr.Zero, 0, IntPtr.Zero,
730                    0, out result, IntPtr.Zero); ++i)
731            {
732                if (i > 100)
733                    throw Win32ErrorCode.GetExceptionForWin32Error(Marshal.GetLastWin32Error());
734                System.Threading.Thread.Sleep(100);
735            }
736
737            Stream = stream;
738        }
739
740        ~VolumeLock()
741        {
742            Dispose(false);
743        }
744
745        public void Dispose()
746        {
747            Dispose(true);
748            GC.SuppressFinalize(this);
749        }
750
751        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "disposing")]
752        private void Dispose(bool disposing)
753        {
754            if (Stream == null)
755                return;
756
757            //Flush the contents of the buffer to disk since after we unlock the volume
758            //we can no longer write to the volume.
759            Stream.Flush();
760
761            uint result = 0;
762            if (!NativeMethods.DeviceIoControl(Stream.SafeFileHandle,
763                NativeMethods.FSCTL_UNLOCK_VOLUME, IntPtr.Zero, 0, IntPtr.Zero,
764                0, out result, IntPtr.Zero))
765            {
766                throw new IOException("Could not unlock volume.");
767            }
768
769            //Set the stream to null so that we won't run this function again.
770            Stream = null;
771        }
772
773        private FileStream Stream;
774    }
775
776    public class DiskPerformanceInfo
777    {
778        internal DiskPerformanceInfo(NativeMethods.DiskPerformanceInfoInternal info)
779        {
780            BytesRead = info.BytesRead;
781            BytesWritten = info.BytesWritten;
782            ReadTime = info.ReadTime;
783            WriteTime = info.WriteTime;
784            IdleTime = info.IdleTime;
785            ReadCount = info.ReadCount;
786            WriteCount = info.WriteCount;
787            QueueDepth = info.QueueDepth;
788            SplitCount = info.SplitCount;
789            QueryTime = info.QueryTime;
790            StorageDeviceNumber = info.StorageDeviceNumber;
791            StorageManagerName = info.StorageManagerName;
792        }
793
794        public long BytesRead { get; private set; }
795        public long BytesWritten { get; private set; }
796        public long ReadTime { get; private set; }
797        public long WriteTime { get; private set; }
798        public long IdleTime { get; private set; }
799        public uint ReadCount { get; private set; }
800        public uint WriteCount { get; private set; }
801        public uint QueueDepth { get; private set; }
802        public uint SplitCount { get; private set; }
803        public long QueryTime { get; private set; }
804        public uint StorageDeviceNumber { get; private set; }
805        public string StorageManagerName { get; private set; }
806    }
807}
Note: See TracBrowser for help on using the repository browser.