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

Revision 2168, 22.1 KB checked in by lowjoel, 4 years ago (diff)

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