source: branches/eraser6/6.0/Eraser.Util/VolumeInfo.cs @ 2664

Revision 2664, 23.2 KB checked in by lowjoel, 3 years ago (diff)

Merged revision(s) 2660 from trunk/eraser: There was an exception thrown here in one crash report; however there's no information on what error the system returned. So, include this information when we have to trigger an exception.

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