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

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