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

Revision 2173, 25.4 KB checked in by lowjoel, 4 years ago (diff)

Forward-port from Eraser 6.0: Supplements r2170: some functions can be used when the drive is not mounted.

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